@redocly/theme 0.70.0-next.5 → 0.70.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Buttons/ConnectMCPButton.js +8 -13
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
- package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
- package/lib/components/Dropdown/Dropdown.d.ts +4 -2
- package/lib/components/Dropdown/Dropdown.js +28 -8
- package/lib/components/Filter/FilterDateRange.js +36 -2
- package/lib/components/Search/SearchAiActivity.d.ts +10 -0
- package/lib/components/Search/SearchAiActivity.js +278 -0
- package/lib/components/Search/SearchAiDialog.js +5 -1
- package/lib/components/Search/SearchAiMessage.d.ts +2 -1
- package/lib/components/Search/SearchAiMessage.js +20 -108
- package/lib/components/Search/SearchItem.js +1 -8
- package/lib/components/Search/variables.js +15 -2
- package/lib/components/Select/SelectInput.js +2 -8
- package/lib/components/SplitButton/SplitButton.js +37 -3
- package/lib/core/constants/mcp.d.ts +2 -2
- package/lib/core/constants/mcp.js +3 -8
- package/lib/core/constants/search.js +5 -1
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/menu/use-collapse.js +1 -1
- package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
- package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
- package/lib/core/hooks/use-connect-mcp-button.js +2 -1
- package/lib/core/hooks/use-full-height.d.ts +1 -1
- package/lib/core/hooks/use-full-height.js +8 -8
- package/lib/core/styles/global.d.ts +2 -0
- package/lib/core/styles/global.js +80 -76
- package/lib/core/styles/index.d.ts +1 -0
- package/lib/core/styles/index.js +3 -1
- package/lib/core/styles/search-ai-appear.d.ts +1 -0
- package/lib/core/styles/search-ai-appear.js +18 -0
- package/lib/core/types/dropdown.d.ts +1 -0
- package/lib/core/types/dropdown.js +3 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/mcp.d.ts +2 -1
- package/lib/core/types/search.d.ts +23 -0
- package/lib/core/utils/assign-ref.d.ts +2 -0
- package/lib/core/utils/assign-ref.js +12 -0
- package/lib/core/utils/content-segments.d.ts +12 -1
- package/lib/core/utils/content-segments.js +90 -5
- package/lib/core/utils/dropdown.d.ts +2 -0
- package/lib/core/utils/dropdown.js +12 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/mcp.d.ts +2 -1
- package/lib/core/utils/mcp.js +5 -0
- package/lib/core/utils/search-ai-activity.d.ts +4 -0
- package/lib/core/utils/search-ai-activity.js +83 -0
- package/lib/markdoc/tags/connect-mcp.js +15 -0
- package/package.json +2 -2
- package/src/components/Buttons/ConnectMCPButton.tsx +8 -14
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
- package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
- package/src/components/Dropdown/Dropdown.tsx +28 -6
- package/src/components/Filter/FilterDateRange.tsx +3 -2
- package/src/components/Search/SearchAiActivity.tsx +318 -0
- package/src/components/Search/SearchAiDialog.tsx +4 -3
- package/src/components/Search/SearchAiMessage.tsx +27 -135
- package/src/components/Search/SearchItem.tsx +2 -9
- package/src/components/Search/variables.ts +15 -2
- package/src/components/Select/SelectInput.tsx +2 -8
- package/src/components/SplitButton/SplitButton.tsx +8 -2
- package/src/core/constants/mcp.ts +3 -9
- package/src/core/constants/search.ts +5 -1
- package/src/core/hooks/__mocks__/index.ts +1 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/menu/use-collapse.ts +1 -1
- package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
- package/src/core/hooks/use-connect-mcp-button.ts +7 -2
- package/src/core/hooks/use-full-height.ts +8 -8
- package/src/core/styles/global.ts +80 -75
- package/src/core/styles/index.ts +2 -1
- package/src/core/styles/search-ai-appear.ts +16 -0
- package/src/core/types/dropdown.ts +1 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +1 -1
- package/src/core/types/mcp.ts +3 -1
- package/src/core/types/search.ts +21 -0
- package/src/core/utils/assign-ref.ts +9 -0
- package/src/core/utils/content-segments.ts +98 -6
- package/src/core/utils/dropdown.ts +14 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/mcp.ts +7 -1
- package/src/core/utils/search-ai-activity.ts +110 -0
- package/src/markdoc/tags/connect-mcp.ts +16 -1
|
@@ -1330,83 +1330,88 @@ const replay = css`
|
|
|
1330
1330
|
`;
|
|
1331
1331
|
|
|
1332
1332
|
|
|
1333
|
+
/** The light theme tokens without a selector, for a host that sets them on one element. */
|
|
1334
|
+
export const tokens = css`
|
|
1335
|
+
${accordion}
|
|
1336
|
+
${admonition}
|
|
1337
|
+
${apiReferenceDocs}
|
|
1338
|
+
${apiReferencePanels}
|
|
1339
|
+
${badges}
|
|
1340
|
+
${banner}
|
|
1341
|
+
${borders}
|
|
1342
|
+
${consent}
|
|
1343
|
+
${breadcrumbs}
|
|
1344
|
+
${button}
|
|
1345
|
+
${aiAssistantButton}
|
|
1346
|
+
${cards}
|
|
1347
|
+
${catalog}
|
|
1348
|
+
${catalogClassic}
|
|
1349
|
+
${code}
|
|
1350
|
+
${codeWalkthrough}
|
|
1351
|
+
${docsDropdown}
|
|
1352
|
+
${dropdown}
|
|
1353
|
+
${error}
|
|
1354
|
+
${filter}
|
|
1355
|
+
${footer}
|
|
1356
|
+
${headingsTypography}
|
|
1357
|
+
${httpTag}
|
|
1358
|
+
${image}
|
|
1359
|
+
${inputs}
|
|
1360
|
+
${languagePicker}
|
|
1361
|
+
${lastUpdated}
|
|
1362
|
+
${links}
|
|
1363
|
+
${loadProgressBar}
|
|
1364
|
+
${logo}
|
|
1365
|
+
${markdown}
|
|
1366
|
+
${markdownAccordion}
|
|
1367
|
+
${markdownTabs}
|
|
1368
|
+
${markdownNumberedList}
|
|
1369
|
+
${diagram}
|
|
1370
|
+
${menu}
|
|
1371
|
+
${mobileMenu}
|
|
1372
|
+
${modal}
|
|
1373
|
+
${navbar}
|
|
1374
|
+
${pages}
|
|
1375
|
+
${productPicker}
|
|
1376
|
+
${responsePanelColors}
|
|
1377
|
+
${search}
|
|
1378
|
+
${navigationTabs}
|
|
1379
|
+
${select}
|
|
1380
|
+
${sidebar}
|
|
1381
|
+
${sizeAndSpace}
|
|
1382
|
+
${tag}
|
|
1383
|
+
${themeColors}
|
|
1384
|
+
${activeBrandPaletteLight}
|
|
1385
|
+
${tile}
|
|
1386
|
+
${toc}
|
|
1387
|
+
${tooltip}
|
|
1388
|
+
${typography}
|
|
1389
|
+
${userMenu}
|
|
1390
|
+
${versionPicker}
|
|
1391
|
+
${zIndexDepth}
|
|
1392
|
+
${scorecardColors}
|
|
1393
|
+
${statusCode}
|
|
1394
|
+
${tab}
|
|
1395
|
+
${icon}
|
|
1396
|
+
${tree}
|
|
1397
|
+
${segmented}
|
|
1398
|
+
${switcher}
|
|
1399
|
+
${checkbox}
|
|
1400
|
+
${feedback}
|
|
1401
|
+
${scorecard}
|
|
1402
|
+
${datePicker}
|
|
1403
|
+
${replay}
|
|
1404
|
+
${skipContent}
|
|
1405
|
+
${pageActions}
|
|
1406
|
+
${svgViewer}
|
|
1407
|
+
${toast}
|
|
1408
|
+
${pageNavigation}
|
|
1409
|
+
|
|
1410
|
+
`;
|
|
1411
|
+
|
|
1333
1412
|
export const styles = css<{ palette?: string }>`
|
|
1334
1413
|
:root {
|
|
1335
|
-
${
|
|
1336
|
-
${admonition}
|
|
1337
|
-
${apiReferenceDocs}
|
|
1338
|
-
${apiReferencePanels}
|
|
1339
|
-
${badges}
|
|
1340
|
-
${banner}
|
|
1341
|
-
${borders}
|
|
1342
|
-
${consent}
|
|
1343
|
-
${breadcrumbs}
|
|
1344
|
-
${button}
|
|
1345
|
-
${aiAssistantButton}
|
|
1346
|
-
${cards}
|
|
1347
|
-
${catalog}
|
|
1348
|
-
${catalogClassic}
|
|
1349
|
-
${code}
|
|
1350
|
-
${codeWalkthrough}
|
|
1351
|
-
${docsDropdown}
|
|
1352
|
-
${dropdown}
|
|
1353
|
-
${error}
|
|
1354
|
-
${filter}
|
|
1355
|
-
${footer}
|
|
1356
|
-
${headingsTypography}
|
|
1357
|
-
${httpTag}
|
|
1358
|
-
${image}
|
|
1359
|
-
${inputs}
|
|
1360
|
-
${languagePicker}
|
|
1361
|
-
${lastUpdated}
|
|
1362
|
-
${links}
|
|
1363
|
-
${loadProgressBar}
|
|
1364
|
-
${logo}
|
|
1365
|
-
${markdown}
|
|
1366
|
-
${markdownAccordion}
|
|
1367
|
-
${markdownTabs}
|
|
1368
|
-
${markdownNumberedList}
|
|
1369
|
-
${diagram}
|
|
1370
|
-
${menu}
|
|
1371
|
-
${mobileMenu}
|
|
1372
|
-
${modal}
|
|
1373
|
-
${navbar}
|
|
1374
|
-
${pages}
|
|
1375
|
-
${productPicker}
|
|
1376
|
-
${responsePanelColors}
|
|
1377
|
-
${search}
|
|
1378
|
-
${navigationTabs}
|
|
1379
|
-
${select}
|
|
1380
|
-
${sidebar}
|
|
1381
|
-
${sizeAndSpace}
|
|
1382
|
-
${tag}
|
|
1383
|
-
${themeColors}
|
|
1384
|
-
${activeBrandPaletteLight}
|
|
1385
|
-
${tile}
|
|
1386
|
-
${toc}
|
|
1387
|
-
${tooltip}
|
|
1388
|
-
${typography}
|
|
1389
|
-
${userMenu}
|
|
1390
|
-
${versionPicker}
|
|
1391
|
-
${zIndexDepth}
|
|
1392
|
-
${scorecardColors}
|
|
1393
|
-
${statusCode}
|
|
1394
|
-
${tab}
|
|
1395
|
-
${icon}
|
|
1396
|
-
${tree}
|
|
1397
|
-
${segmented}
|
|
1398
|
-
${switcher}
|
|
1399
|
-
${checkbox}
|
|
1400
|
-
${feedback}
|
|
1401
|
-
${scorecard}
|
|
1402
|
-
${datePicker}
|
|
1403
|
-
${replay}
|
|
1404
|
-
${skipContent}
|
|
1405
|
-
${pageActions}
|
|
1406
|
-
${svgViewer}
|
|
1407
|
-
${toast}
|
|
1408
|
-
${pageNavigation}
|
|
1409
|
-
|
|
1414
|
+
${tokens}
|
|
1410
1415
|
background-color: var(--bg-color);
|
|
1411
1416
|
color: var(--text-color-primary);
|
|
1412
1417
|
font-family: var(--font-family-base);
|
package/src/core/styles/index.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { css, keyframes } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
const appear = keyframes`
|
|
4
|
+
from {
|
|
5
|
+
opacity: 0;
|
|
6
|
+
transform: translateY(4px);
|
|
7
|
+
}
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
export const searchAiAppearAnimation = css`
|
|
11
|
+
animation: ${appear} var(--search-ai-activity-fade-duration) ease-out;
|
|
12
|
+
|
|
13
|
+
@media (prefers-reduced-motion: reduce) {
|
|
14
|
+
animation: none;
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type DropdownAlignment = 'start' | 'end';
|
package/src/core/types/index.ts
CHANGED
package/src/core/types/l10n.ts
CHANGED
|
@@ -113,7 +113,7 @@ export type TranslationKey =
|
|
|
113
113
|
| 'search.ai.feedback.thanks'
|
|
114
114
|
| 'search.ai.toolCall.executed'
|
|
115
115
|
| 'search.ai.toolCall.executing'
|
|
116
|
-
| 'search.ai.toolCall.
|
|
116
|
+
| 'search.ai.toolCall.usedTools'
|
|
117
117
|
| 'search.ai.button'
|
|
118
118
|
| 'search.ai.label'
|
|
119
119
|
| 'search.ai.disclaimer'
|
package/src/core/types/mcp.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { MCP_OPTIONS } from '../constants/mcp';
|
|
2
|
+
|
|
1
3
|
export type MCPClientType = 'cursor' | 'vscode';
|
|
2
4
|
|
|
3
|
-
export type MCPOption =
|
|
5
|
+
export type MCPOption = (typeof MCP_OPTIONS)[number];
|
|
4
6
|
|
|
5
7
|
export type McpConnectionParams = {
|
|
6
8
|
serverName: string;
|
package/src/core/types/search.ts
CHANGED
|
@@ -117,6 +117,27 @@ export type ToolCall = {
|
|
|
117
117
|
|
|
118
118
|
export type ContentSegment = { type: 'text'; text: string } | { type: 'tool'; toolCall: ToolCall };
|
|
119
119
|
|
|
120
|
+
export type GroupedContentSegment =
|
|
121
|
+
| { type: 'text'; text: string }
|
|
122
|
+
/** `isActive`: the assistant still works on this group, with nothing written after it yet. */
|
|
123
|
+
| { type: 'tools'; toolCalls: ToolCall[]; isActive: boolean };
|
|
124
|
+
|
|
125
|
+
export type SearchAiActivityKind = 'thinking' | 'search' | 'code' | 'support';
|
|
126
|
+
|
|
127
|
+
export type SearchAiActivityStatus = {
|
|
128
|
+
label: string;
|
|
129
|
+
detail?: string;
|
|
130
|
+
kind: SearchAiActivityKind;
|
|
131
|
+
isRunning: boolean;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type SearchAiActivityStep = {
|
|
135
|
+
id: string;
|
|
136
|
+
label: string;
|
|
137
|
+
kind: SearchAiActivityKind;
|
|
138
|
+
isRunning: boolean;
|
|
139
|
+
};
|
|
140
|
+
|
|
120
141
|
export type AiSearchConversationItem = {
|
|
121
142
|
role: AiSearchConversationRole;
|
|
122
143
|
content: string;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import type { ToolCall, ContentSegment } from '../types/search';
|
|
1
|
+
import type { ToolCall, ContentSegment, GroupedContentSegment } from '../types/search';
|
|
2
2
|
|
|
3
|
+
const SENTENCE_END_MARKS = '.!?:;';
|
|
4
|
+
const CLOSING_MARKS = `"')]*_\``;
|
|
5
|
+
const CODE_FENCE = /^[ \t]*(```|~~~)/gm;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Splits an answer at its tool calls. A call made in the middle of a sentence or a code block is
|
|
9
|
+
* placed at the end of that paragraph, so the sentence reads as one piece.
|
|
10
|
+
*/
|
|
3
11
|
export function splitContentByToolCalls(
|
|
4
12
|
content: string | undefined,
|
|
5
13
|
toolCalls: ToolCall[] | undefined,
|
|
@@ -8,20 +16,104 @@ export function splitContentByToolCalls(
|
|
|
8
16
|
return [{ type: 'text', text: content || '' }];
|
|
9
17
|
}
|
|
10
18
|
|
|
19
|
+
const text = content ?? '';
|
|
20
|
+
const fences = Array.from(text.matchAll(CODE_FENCE), (match) => match.index);
|
|
11
21
|
const segments: ContentSegment[] = [];
|
|
12
22
|
let lastPos = 0;
|
|
13
23
|
|
|
14
24
|
for (const toolCall of toolCalls) {
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
const position = Math.max(lastPos, getBlockBoundary(text, toolCall.position, fences));
|
|
26
|
+
if (position > lastPos) {
|
|
27
|
+
segments.push({ type: 'text', text: text.substring(lastPos, position) });
|
|
17
28
|
}
|
|
18
29
|
segments.push({ type: 'tool', toolCall });
|
|
19
|
-
lastPos =
|
|
30
|
+
lastPos = position;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
if (
|
|
23
|
-
segments.push({ type: 'text', text:
|
|
33
|
+
if (lastPos < text.length) {
|
|
34
|
+
segments.push({ type: 'text', text: text.substring(lastPos) });
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
return segments;
|
|
27
38
|
}
|
|
39
|
+
|
|
40
|
+
function getBlockBoundary(content: string, position: number, fences: number[]): number {
|
|
41
|
+
if (position <= 0 || position >= content.length) {
|
|
42
|
+
return position;
|
|
43
|
+
}
|
|
44
|
+
const isInsideCodeFence = (index: number) => {
|
|
45
|
+
let count = 0;
|
|
46
|
+
while (count < fences.length && fences[count] < index) count++;
|
|
47
|
+
return count % 2 === 1;
|
|
48
|
+
};
|
|
49
|
+
if (!isInsideCodeFence(position) && endsBlockOrSentence(content, position)) {
|
|
50
|
+
return position;
|
|
51
|
+
}
|
|
52
|
+
for (
|
|
53
|
+
let boundary = content.indexOf('\n\n', position);
|
|
54
|
+
boundary !== -1;
|
|
55
|
+
boundary = content.indexOf('\n\n', boundary + 2)
|
|
56
|
+
) {
|
|
57
|
+
if (!isInsideCodeFence(boundary)) {
|
|
58
|
+
return boundary;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return content.length;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function endsBlockOrSentence(content: string, position: number): boolean {
|
|
65
|
+
let index = position - 1;
|
|
66
|
+
while (index >= 0 && /\s/.test(content[index])) {
|
|
67
|
+
if (content[index] === '\n') return true;
|
|
68
|
+
index--;
|
|
69
|
+
}
|
|
70
|
+
while (index >= 0 && CLOSING_MARKS.includes(content[index])) index--;
|
|
71
|
+
return index >= 0 && SENTENCE_END_MARKS.includes(content[index]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Groups consecutive tool calls and drops text that is only whitespace. While the answer is
|
|
76
|
+
* generated, an answer with nothing to show yet is one empty active group (the thinking status),
|
|
77
|
+
* and the last group is active until text that is not whitespace follows it.
|
|
78
|
+
*/
|
|
79
|
+
export function groupToolCallSegments(
|
|
80
|
+
segments: ContentSegment[],
|
|
81
|
+
isGenerating?: boolean,
|
|
82
|
+
): GroupedContentSegment[] {
|
|
83
|
+
const grouped: GroupedContentSegment[] = [];
|
|
84
|
+
let text = '';
|
|
85
|
+
|
|
86
|
+
for (const segment of segments) {
|
|
87
|
+
if (segment.type === 'text') {
|
|
88
|
+
text += segment.text;
|
|
89
|
+
if (segment.text.trim()) {
|
|
90
|
+
grouped.push(segment);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const previous = grouped.at(-1);
|
|
95
|
+
if (previous?.type === 'tools') {
|
|
96
|
+
previous.toolCalls.push(segment.toolCall);
|
|
97
|
+
} else {
|
|
98
|
+
grouped.push({ type: 'tools', toolCalls: [segment.toolCall], isActive: false });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!isGenerating) {
|
|
103
|
+
return grouped;
|
|
104
|
+
}
|
|
105
|
+
const last = grouped.at(-1);
|
|
106
|
+
if (last?.type === 'tools') {
|
|
107
|
+
last.isActive = !text.slice(last.toolCalls.at(-1)?.position ?? 0).trim();
|
|
108
|
+
} else if (grouped.length === 0) {
|
|
109
|
+
return [{ type: 'tools', toolCalls: [], isActive: true }];
|
|
110
|
+
}
|
|
111
|
+
return grouped;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function hasToolCallsAndText(segments: ContentSegment[]): boolean {
|
|
115
|
+
return (
|
|
116
|
+
segments.some((segment) => segment.type === 'tool') &&
|
|
117
|
+
segments.some((segment) => segment.type === 'text' && segment.text.trim().length > 0)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DropdownAlignment } from '../types/dropdown';
|
|
2
|
+
|
|
3
|
+
export function fitDropdownAlignment(
|
|
4
|
+
anchor: DOMRect,
|
|
5
|
+
menuWidth: number,
|
|
6
|
+
alignment: DropdownAlignment = 'start',
|
|
7
|
+
): DropdownAlignment {
|
|
8
|
+
const fits: Record<DropdownAlignment, boolean> = {
|
|
9
|
+
start: anchor.left + menuWidth <= window.innerWidth,
|
|
10
|
+
end: anchor.right - menuWidth >= 0,
|
|
11
|
+
};
|
|
12
|
+
const other: DropdownAlignment = alignment === 'end' ? 'start' : 'end';
|
|
13
|
+
return fits[alignment] || !fits[other] ? alignment : other;
|
|
14
|
+
}
|
package/src/core/utils/index.ts
CHANGED
|
@@ -51,3 +51,5 @@ export * from './telemetry/get-base-data-attributes';
|
|
|
51
51
|
export * from './telemetry/generate-before-after-context';
|
|
52
52
|
export * from './RenderWithLink';
|
|
53
53
|
export * from './consent-settings-link-placement';
|
|
54
|
+
export * from './assign-ref';
|
|
55
|
+
export * from './dropdown';
|
package/src/core/utils/mcp.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type { MCPClientType, McpConnectionParams } from '../types';
|
|
1
|
+
import type { MCPClientType, McpConnectionParams, MCPOption } from '../types';
|
|
2
|
+
|
|
3
|
+
import { MCP_OPTIONS } from '../constants/mcp';
|
|
4
|
+
|
|
5
|
+
export function isMCPOption(value: unknown): value is MCPOption {
|
|
6
|
+
return (MCP_OPTIONS as readonly unknown[]).includes(value);
|
|
7
|
+
}
|
|
2
8
|
|
|
3
9
|
function generateCursorMCPDeepLink(config: McpConnectionParams): string {
|
|
4
10
|
const cursorConfig = {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { TFunction } from '../types/l10n';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ToolCallName,
|
|
5
|
+
type SearchAiActivityStatus,
|
|
6
|
+
type SearchAiActivityStep,
|
|
7
|
+
type ToolCall,
|
|
8
|
+
type SearchAiActivityKind,
|
|
9
|
+
} from '../types/search';
|
|
10
|
+
import { TOOL_CALL_DISPLAY_TEXT } from '../constants/search';
|
|
11
|
+
import { isObject } from './type-guards';
|
|
12
|
+
|
|
13
|
+
export function getSearchAiActivityStatus(
|
|
14
|
+
toolCalls: ToolCall[],
|
|
15
|
+
isActive: boolean,
|
|
16
|
+
runningToolCall: ToolCall | undefined,
|
|
17
|
+
translate: TFunction,
|
|
18
|
+
): SearchAiActivityStatus {
|
|
19
|
+
const latestToolCall = toolCalls.at(-1);
|
|
20
|
+
if (!latestToolCall) {
|
|
21
|
+
return {
|
|
22
|
+
label: translate('search.ai.thinkingText', 'Thinking...'),
|
|
23
|
+
kind: 'thinking',
|
|
24
|
+
isRunning: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isActive || toolCalls.length === 1) {
|
|
29
|
+
const toolCall = runningToolCall ?? latestToolCall;
|
|
30
|
+
return {
|
|
31
|
+
...getToolCallLabel(toolCall, !isActive, translate),
|
|
32
|
+
kind: getSearchAiActivityKind(toolCall.name),
|
|
33
|
+
isRunning: isActive,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isSameTool = toolCalls.every((toolCall) => toolCall.name === latestToolCall.name);
|
|
38
|
+
const sharedText = isSameTool
|
|
39
|
+
? TOOL_CALL_DISPLAY_TEXT[latestToolCall.name]?.completedText
|
|
40
|
+
: undefined;
|
|
41
|
+
return {
|
|
42
|
+
label: sharedText ?? translate('search.ai.toolCall.usedTools', 'Used tools'),
|
|
43
|
+
kind: isSameTool ? getSearchAiActivityKind(latestToolCall.name) : 'code',
|
|
44
|
+
isRunning: false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getSearchAiActivityStep(
|
|
49
|
+
toolCall: ToolCall,
|
|
50
|
+
isRunning: boolean,
|
|
51
|
+
translate: TFunction,
|
|
52
|
+
): SearchAiActivityStep {
|
|
53
|
+
const detail = getToolCallDetail(toolCall);
|
|
54
|
+
const kind = getSearchAiActivityKind(toolCall.name);
|
|
55
|
+
|
|
56
|
+
if (toolCall.name === ToolCallName.SearchDocumentation && detail) {
|
|
57
|
+
return { id: toolCall.id, label: `“${detail}”`, kind, isRunning };
|
|
58
|
+
}
|
|
59
|
+
const { label } = getToolCallLabel(toolCall, !isRunning, translate);
|
|
60
|
+
return { id: toolCall.id, label: detail ? label : stripEllipsis(label), kind, isRunning };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getToolCallLabel(
|
|
64
|
+
toolCall: ToolCall,
|
|
65
|
+
isCompleted: boolean,
|
|
66
|
+
translate: TFunction,
|
|
67
|
+
): { label: string; detail?: string } {
|
|
68
|
+
const detail = getToolCallDetail(toolCall);
|
|
69
|
+
if (toolCall.name === ToolCallName.Execute && detail) {
|
|
70
|
+
return { label: detail };
|
|
71
|
+
}
|
|
72
|
+
const builtInText = TOOL_CALL_DISPLAY_TEXT[toolCall.name];
|
|
73
|
+
if (builtInText) {
|
|
74
|
+
const text = isCompleted ? builtInText.completedText : builtInText.inProgressText;
|
|
75
|
+
return detail ? { label: stripEllipsis(text), detail } : { label: text };
|
|
76
|
+
}
|
|
77
|
+
const prefix = isCompleted
|
|
78
|
+
? translate('search.ai.toolCall.executed', 'Executed')
|
|
79
|
+
: translate('search.ai.toolCall.executing', 'Executing');
|
|
80
|
+
return { label: `${prefix} ${toolCall.name}` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getSearchAiActivityKind(name: string): SearchAiActivityKind {
|
|
84
|
+
if (name === ToolCallName.SearchDocumentation) return 'search';
|
|
85
|
+
if (name === ToolCallName.ContactSupport) return 'support';
|
|
86
|
+
return 'code';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getToolCallDetail(toolCall: ToolCall): string | undefined {
|
|
90
|
+
switch (toolCall.name) {
|
|
91
|
+
case ToolCallName.SearchDocumentation:
|
|
92
|
+
return getStringArg(toolCall.args, 'query');
|
|
93
|
+
case ToolCallName.Execute:
|
|
94
|
+
return getStringArg(toolCall.args, 'description');
|
|
95
|
+
default:
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getStringArg(args: unknown, name: string): string | undefined {
|
|
101
|
+
if (!isObject(args) || !(name in args)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const value: unknown = Reflect.get(args, name);
|
|
105
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function stripEllipsis(text: string): string {
|
|
109
|
+
return text.replace(/(\.{3}|…)$/, '');
|
|
110
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MarkdocSchemaWrapper } from '@redocly/theme/markdoc/tags/types';
|
|
2
2
|
|
|
3
|
-
import { DEFAULT_MCP_OPTIONS } from '@redocly/theme/core/constants';
|
|
3
|
+
import { DEFAULT_MCP_OPTIONS, MCP_OPTIONS } from '@redocly/theme/core/constants';
|
|
4
|
+
import { isMCPOption } from '@redocly/theme/core/utils';
|
|
4
5
|
|
|
5
6
|
export const connectMcp: MarkdocSchemaWrapper = {
|
|
6
7
|
schema: {
|
|
@@ -19,6 +20,20 @@ export const connectMcp: MarkdocSchemaWrapper = {
|
|
|
19
20
|
options: {
|
|
20
21
|
type: Array,
|
|
21
22
|
default: DEFAULT_MCP_OPTIONS,
|
|
23
|
+
validate(value) {
|
|
24
|
+
if (!Array.isArray(value)) return [];
|
|
25
|
+
|
|
26
|
+
const unknownOptions = value.filter((option) => !isMCPOption(option));
|
|
27
|
+
if (unknownOptions.length === 0) return [];
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
id: 'invalid-connect-mcp-options',
|
|
32
|
+
level: 'error',
|
|
33
|
+
message: `Invalid connect-mcp options: ${unknownOptions.join(', ')}. Must be one of: ${MCP_OPTIONS.join(', ')}`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
},
|
|
22
37
|
},
|
|
23
38
|
},
|
|
24
39
|
selfClosing: true,
|