Clean up MCP Monaco editor (#21643)
Rebuild the MCP setup block on the shared Monaco editor with theme-driven colors, padding, and auto-height; add the settings description line-height token; revert the clipboard util to the shared navigator.clipboard implementation; and consolidate the code editor's auto-height to a single disposed, reactive effect.
This commit is contained in:
committed by
GitHub
parent
ce92a1f1e1
commit
a67fdf2dac
@@ -79,6 +79,7 @@ const StyledIconChevronRightContainer = styled.span`
|
||||
`;
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
line-height: ${themeCssVariables.text.lineHeight.lg};
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
padding-left: ${themeCssVariables.spacing[7]};
|
||||
`;
|
||||
|
||||
@@ -48,6 +48,7 @@ const StyledLabel = styled.span`
|
||||
const StyledDescription = styled.span`
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
line-height: ${themeCssVariables.text.lineHeight.lg};
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
|
||||
+1
@@ -70,6 +70,7 @@ const StyledLabel = styled.span`
|
||||
const StyledDescription = styled.span`
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
line-height: ${themeCssVariables.text.lineHeight.lg};
|
||||
`;
|
||||
|
||||
const StyledError = styled.span`
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export const StyledSettingsCardTextContainer = styled.div`
|
||||
export const StyledSettingsCardDescription = styled.div`
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
line-height: 1.5;
|
||||
line-height: ${themeCssVariables.text.lineHeight.lg};
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
|
||||
@@ -40,6 +40,7 @@ const StyledTitle = styled.div`
|
||||
const StyledDescription = styled.div`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
line-height: ${themeCssVariables.text.lineHeight.lg};
|
||||
`;
|
||||
|
||||
type SettingsRadioCardProps = {
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_19_13)">
|
||||
<path d="M18 84.8528L85.8822 16.9706C95.2548 7.59798 110.451 7.59798 119.823 16.9706C129.196 26.3431 129.196 41.5391 119.823 50.9117L68.5581 102.177" stroke="currentColor" stroke-width="12" stroke-linecap="round" />
|
||||
<path d="M69.2652 101.47L119.823 50.9117C129.196 41.5391 144.392 41.5391 153.765 50.9117L154.118 51.2652C163.491 60.6378 163.491 75.8338 154.118 85.2063L92.7248 146.6C89.6006 149.724 89.6006 154.789 92.7248 157.913L105.331 170.52" stroke="currentColor" stroke-width="12" stroke-linecap="round" />
|
||||
<path d="M102.853 33.9411L52.6482 84.1457C43.2756 93.5183 43.2756 108.714 52.6482 118.087C62.0208 127.459 77.2167 127.459 86.5893 118.087L136.794 67.8822" stroke="currentColor" stroke-width="12" stroke-linecap="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_19_13">
|
||||
<rect width="180" height="180" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1016 B |
+42
-108
@@ -1,42 +1,24 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { type ReactNode } from 'react';
|
||||
import { H2Title, IconCopy } from 'twenty-ui-deprecated/display';
|
||||
import { LightIconButton } from 'twenty-ui-deprecated/input';
|
||||
import { Card, CardContent, Section } from 'twenty-ui-deprecated/layout';
|
||||
import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton';
|
||||
import ModelContextProtocolLogo from '@/settings/playground/assets/model-context-protocol-logo.svg?react';
|
||||
import { H2Title } from 'twenty-ui-deprecated/display';
|
||||
import { CodeEditor, CoreEditorHeader } from 'twenty-ui-deprecated/input';
|
||||
import { Section } from 'twenty-ui-deprecated/layout';
|
||||
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
|
||||
const StyledMcpConfigContainer = styled.div`
|
||||
position: relative;
|
||||
const StyledMcpEditorHeaderTitle = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledCopyButtonContainer = styled.div`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
`;
|
||||
|
||||
const StyledPre = styled.pre`
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
font-family: monospace;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
`;
|
||||
|
||||
const StyledJsonKey = styled.span`
|
||||
color: ${themeCssVariables.color.blue};
|
||||
`;
|
||||
|
||||
const StyledJsonString = styled.span`
|
||||
color: ${themeCssVariables.color.green};
|
||||
`;
|
||||
|
||||
const StyledJsonPlaceholder = styled.span`
|
||||
color: ${themeCssVariables.color.orange};
|
||||
const StyledMcpIcon = styled(ModelContextProtocolLogo)`
|
||||
color: inherit;
|
||||
flex-shrink: 0;
|
||||
height: calc(${themeCssVariables.icon.size.md} * 1px);
|
||||
width: calc(${themeCssVariables.icon.size.md} * 1px);
|
||||
`;
|
||||
|
||||
const buildMcpConfig = (serverUrl: string) =>
|
||||
@@ -51,91 +33,43 @@ const buildMcpConfig = (serverUrl: string) =>
|
||||
}
|
||||
}`;
|
||||
|
||||
const renderJsonString = (token: string, key: string) => {
|
||||
const apiKeyPlaceholder = '<YOUR_API_KEY>';
|
||||
|
||||
if (!token.includes(apiKeyPlaceholder)) {
|
||||
return <StyledJsonString key={key}>{token}</StyledJsonString>;
|
||||
}
|
||||
|
||||
const [beforePlaceholder, afterPlaceholder] = token.split(apiKeyPlaceholder);
|
||||
|
||||
return (
|
||||
<span key={key}>
|
||||
<StyledJsonString>{beforePlaceholder}</StyledJsonString>
|
||||
<StyledJsonPlaceholder>{apiKeyPlaceholder}</StyledJsonPlaceholder>
|
||||
<StyledJsonString>{afterPlaceholder}</StyledJsonString>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const getHighlightedMcpConfig = (mcpConfig: string) => {
|
||||
const jsonStringTokenRegex = /("(?:[^"\\]|\\.)*")(\s*:)?/g;
|
||||
const tokens: ReactNode[] = [];
|
||||
let previousMatchEndIndex = 0;
|
||||
let tokenIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = jsonStringTokenRegex.exec(mcpConfig)) !== null) {
|
||||
const [fullMatch, quotedString, trailingColon] = match;
|
||||
const matchIndex = match.index;
|
||||
|
||||
if (matchIndex > previousMatchEndIndex) {
|
||||
tokens.push(mcpConfig.slice(previousMatchEndIndex, matchIndex));
|
||||
}
|
||||
|
||||
if (trailingColon) {
|
||||
tokens.push(
|
||||
<StyledJsonKey key={`json-key-${tokenIndex}`}>
|
||||
{quotedString}
|
||||
</StyledJsonKey>,
|
||||
);
|
||||
tokens.push(trailingColon);
|
||||
} else {
|
||||
tokens.push(renderJsonString(quotedString, `json-string-${tokenIndex}`));
|
||||
}
|
||||
|
||||
previousMatchEndIndex = matchIndex + fullMatch.length;
|
||||
tokenIndex += 1;
|
||||
}
|
||||
|
||||
if (previousMatchEndIndex < mcpConfig.length) {
|
||||
tokens.push(mcpConfig.slice(previousMatchEndIndex));
|
||||
}
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
||||
export const SettingsMcpSetup = () => {
|
||||
const { t } = useLingui();
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
const mcpConfig = buildMcpConfig(REACT_APP_SERVER_BASE_URL);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Connect your AI assistant`}
|
||||
description={t`Add Twenty as a Model Context Protocol (MCP) server. Paste this config into Claude Desktop, Cursor, Cline, Continue, Zed, or any other MCP-aware client.`}
|
||||
description={t`Paste this configuration into MCP-aware clients like Claude, Codex, or Cursor.`}
|
||||
/>
|
||||
<CoreEditorHeader
|
||||
leftNodes={[
|
||||
<StyledMcpEditorHeaderTitle>
|
||||
<StyledMcpIcon aria-hidden focusable={false} />
|
||||
<span>{t`MCP client configuration`}</span>
|
||||
</StyledMcpEditorHeaderTitle>,
|
||||
]}
|
||||
rightNodes={[<LightCopyIconButton copyText={mcpConfig} />]}
|
||||
/>
|
||||
<CodeEditor
|
||||
value={mcpConfig}
|
||||
language="json"
|
||||
variant="with-header"
|
||||
contentPadding="comfortable"
|
||||
autoHeight
|
||||
options={{
|
||||
readOnly: true,
|
||||
domReadOnly: true,
|
||||
lineNumbers: 'off',
|
||||
lineNumbersMinChars: 0,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
scrollBeyondLastLine: false,
|
||||
renderLineHighlight: 'none',
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
/>
|
||||
<Card rounded>
|
||||
<CardContent>
|
||||
<StyledMcpConfigContainer>
|
||||
<StyledPre>{getHighlightedMcpConfig(mcpConfig)}</StyledPre>
|
||||
<StyledCopyButtonContainer>
|
||||
<LightIconButton
|
||||
Icon={IconCopy}
|
||||
size="small"
|
||||
accent="tertiary"
|
||||
aria-label={t`Copy MCP config`}
|
||||
title={t`Copy MCP config`}
|
||||
onClick={() =>
|
||||
copyToClipboard(mcpConfig, t`MCP config copied to clipboard`)
|
||||
}
|
||||
/>
|
||||
</StyledCopyButtonContainer>
|
||||
</StyledMcpConfigContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
-2
@@ -358,7 +358,6 @@ export const WorkflowEditActionCode = ({
|
||||
readOnly: actionOptions.readonly,
|
||||
domReadOnly: actionOptions.readonly,
|
||||
scrollBeyondLastLine: false,
|
||||
padding: { top: 4, bottom: 4 },
|
||||
}}
|
||||
/>
|
||||
</StyledFullScreenCodeEditorContainer>
|
||||
@@ -397,7 +396,6 @@ export const WorkflowEditActionCode = ({
|
||||
readOnly: actionOptions.readonly,
|
||||
domReadOnly: actionOptions.readonly,
|
||||
scrollBeyondLastLine: false,
|
||||
padding: { top: 4, bottom: 4 },
|
||||
lineNumbersMinChars: 2,
|
||||
fixedOverflowWidgets: true,
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user