@alpaca-editor/core 1.0.4134 → 1.0.4140
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/dist/config/config.js +7 -0
- package/dist/config/config.js.map +1 -1
- package/dist/editor/FieldListField.js +3 -4
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/Terminal.js +1 -1
- package/dist/editor/Terminal.js.map +1 -1
- package/dist/editor/Titlebar.js +0 -1
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.d.ts +3 -1
- package/dist/editor/ai/AgentCostDisplay.js +26 -2
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentStatusBadge.d.ts +26 -0
- package/dist/editor/ai/AgentStatusBadge.js +110 -0
- package/dist/editor/ai/AgentStatusBadge.js.map +1 -0
- package/dist/editor/ai/AgentTerminal.js +289 -198
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.d.ts +2 -2
- package/dist/editor/ai/Agents.js +115 -19
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +259 -45
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContextInfoBar.js +124 -113
- package/dist/editor/ai/ContextInfoBar.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +1 -0
- package/dist/editor/ai/ToolCallDisplay.js +70 -58
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/useAgentStatus.d.ts +13 -0
- package/dist/editor/ai/useAgentStatus.js +101 -0
- package/dist/editor/ai/useAgentStatus.js.map +1 -0
- package/dist/editor/client/EditorShell.js +23 -8
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/commands/localizeItem/LocalizeItemDialog.js +5 -5
- package/dist/editor/commands/localizeItem/LocalizeItemDialog.js.map +1 -1
- package/dist/editor/control-center/About.js +1 -1
- package/dist/editor/control-center/About.js.map +1 -1
- package/dist/editor/control-center/AllAgentsPanel.d.ts +5 -0
- package/dist/editor/control-center/AllAgentsPanel.js +126 -0
- package/dist/editor/control-center/AllAgentsPanel.js.map +1 -0
- package/dist/editor/control-center/WebSocketMessages.js +1 -0
- package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
- package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js +42 -7
- package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearch.d.ts +1 -1
- package/dist/editor/media-selector/AiImageSearch.js +162 -103
- package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +20 -4
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -2
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +7 -5
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js +2 -2
- package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +18 -11
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +53 -48
- package/dist/editor/services/agentService.js +137 -79
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +1 -1
- package/dist/editor/services/editService.js +1 -0
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/sidebar/GraphQL.js +20 -7
- package/dist/editor/sidebar/GraphQL.js.map +1 -1
- package/dist/editor/sidebar/SEOInfo.js +1 -2
- package/dist/editor/sidebar/SEOInfo.js.map +1 -1
- package/dist/editor/sidebar/Translations.js +10 -7
- package/dist/editor/sidebar/Translations.js.map +1 -1
- package/dist/editor/ui/ItemNameDialogNew.js +1 -1
- package/dist/editor/ui/ItemSearch.js +10 -4
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/page-wizard/steps/CollectStep.js +2 -2
- package/dist/page-wizard/steps/CollectStep.js.map +1 -1
- package/dist/page-wizard/steps/FieldEditor.js +2 -2
- package/dist/page-wizard/steps/FieldEditor.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +2 -2
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +1 -1
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/styles.css +167 -22
- package/dist/tour/Tour.js +15 -11
- package/dist/tour/Tour.js.map +1 -1
- package/package.json +1 -1
- package/src/config/config.tsx +7 -0
- package/src/editor/FieldListField.tsx +13 -13
- package/src/editor/Terminal.tsx +1 -1
- package/src/editor/Titlebar.tsx +0 -1
- package/src/editor/ai/AgentCostDisplay.tsx +57 -1
- package/src/editor/ai/AgentStatusBadge.tsx +144 -0
- package/src/editor/ai/AgentTerminal.tsx +345 -219
- package/src/editor/ai/Agents.tsx +179 -30
- package/src/editor/ai/AiResponseMessage.tsx +411 -114
- package/src/editor/ai/ContextInfoBar.tsx +134 -131
- package/src/editor/ai/ToolCallDisplay.tsx +217 -176
- package/src/editor/ai/useAgentStatus.ts +123 -0
- package/src/editor/client/EditorShell.tsx +34 -8
- package/src/editor/client/itemsRepository.ts +1 -2
- package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +5 -5
- package/src/editor/control-center/About.tsx +0 -14
- package/src/editor/control-center/AllAgentsPanel.tsx +300 -0
- package/src/editor/control-center/WebSocketMessages.tsx +1 -0
- package/src/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.tsx +49 -8
- package/src/editor/media-selector/AiImageSearch.tsx +162 -172
- package/src/editor/media-selector/TreeSelector.tsx +137 -116
- package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +9 -1
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +7 -4
- package/src/editor/page-viewer/DeviceToolbar.tsx +15 -11
- package/src/editor/page-viewer/PageViewerFrame.tsx +20 -14
- package/src/editor/services/agentService.ts +217 -129
- package/src/editor/services/aiService.ts +2 -2
- package/src/editor/services/editService.ts +1 -0
- package/src/editor/sidebar/GraphQL.tsx +143 -117
- package/src/editor/sidebar/SEOInfo.tsx +1 -2
- package/src/editor/sidebar/Translations.tsx +14 -12
- package/src/editor/ui/ItemNameDialogNew.tsx +1 -1
- package/src/editor/ui/ItemSearch.tsx +11 -4
- package/src/editor/ui/SimpleTabs.tsx +1 -1
- package/src/page-wizard/steps/CollectStep.tsx +2 -2
- package/src/page-wizard/steps/FieldEditor.tsx +13 -15
- package/src/revision.ts +2 -2
- package/src/splash-screen/NewPage.tsx +2 -2
- package/src/splash-screen/RecentPages.tsx +1 -1
- package/src/tour/Tour.tsx +61 -48
|
@@ -30,6 +30,127 @@ type ContentSegment =
|
|
|
30
30
|
items: { id?: string; text: string; done?: boolean; note?: string }[];
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
// Helper to extract partial quick actions from potentially incomplete JSON during streaming
|
|
34
|
+
const extractPartialQuickActions = (jsonText: string): QuickAction[] => {
|
|
35
|
+
const actions: QuickAction[] = [];
|
|
36
|
+
|
|
37
|
+
// First try to parse complete JSON
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(jsonText);
|
|
40
|
+
const rawActions = Array.isArray(parsed)
|
|
41
|
+
? parsed
|
|
42
|
+
: parsed?.actions || parsed?.buttons || parsed?.choices || [];
|
|
43
|
+
if (Array.isArray(rawActions)) {
|
|
44
|
+
rawActions.forEach((a: any) => {
|
|
45
|
+
if (!a) return;
|
|
46
|
+
const label = a.label || a.text || String(a.value || a.prompt || "");
|
|
47
|
+
if (label) {
|
|
48
|
+
actions.push({
|
|
49
|
+
id: a.id,
|
|
50
|
+
label,
|
|
51
|
+
prompt: a.prompt,
|
|
52
|
+
value: a.value,
|
|
53
|
+
style: a.style,
|
|
54
|
+
behavior:
|
|
55
|
+
a.behavior &&
|
|
56
|
+
(a.behavior === "compose" || a.behavior === "submit")
|
|
57
|
+
? a.behavior
|
|
58
|
+
: a.submit === false ||
|
|
59
|
+
a.compose === true ||
|
|
60
|
+
a.draft === true ||
|
|
61
|
+
a.mode === "compose"
|
|
62
|
+
? "compose"
|
|
63
|
+
: undefined,
|
|
64
|
+
placeholder: a.placeholder,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return actions;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// JSON is incomplete, try to extract whatever complete action objects we can find
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Look for individual action objects in the partial JSON
|
|
75
|
+
// Match patterns like: { "label": "...", ... }
|
|
76
|
+
const labelPattern = /"(?:label|text)"\s*:\s*"([^"\\]*(\\.[^"\\]*)*)"/g;
|
|
77
|
+
const labelMatches: Array<{ label: string; startIdx: number }> = [];
|
|
78
|
+
let labelMatch;
|
|
79
|
+
while ((labelMatch = labelPattern.exec(jsonText)) !== null) {
|
|
80
|
+
if (labelMatch[1]) {
|
|
81
|
+
labelMatches.push({
|
|
82
|
+
label: labelMatch[1],
|
|
83
|
+
startIdx: labelMatch.index,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// For each label field found, try to find and parse the enclosing object
|
|
89
|
+
for (const { label, startIdx } of labelMatches) {
|
|
90
|
+
// Find the opening brace before this label field
|
|
91
|
+
let openBrace = -1;
|
|
92
|
+
for (let i = startIdx - 1; i >= 0; i--) {
|
|
93
|
+
if (jsonText[i] === "{") {
|
|
94
|
+
openBrace = i;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
if (jsonText[i] === "}") break; // Hit another object's end
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (openBrace === -1) continue;
|
|
101
|
+
|
|
102
|
+
// Find the closing brace after this label field
|
|
103
|
+
let closeBrace = -1;
|
|
104
|
+
let depth = 0;
|
|
105
|
+
for (let i = openBrace; i < jsonText.length; i++) {
|
|
106
|
+
if (jsonText[i] === "{") depth++;
|
|
107
|
+
if (jsonText[i] === "}") {
|
|
108
|
+
depth--;
|
|
109
|
+
if (depth === 0) {
|
|
110
|
+
closeBrace = i;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Extract the object and try to parse it
|
|
117
|
+
const objStr =
|
|
118
|
+
closeBrace !== -1
|
|
119
|
+
? jsonText.substring(openBrace, closeBrace + 1)
|
|
120
|
+
: jsonText.substring(openBrace) + "}"; // Try to close incomplete object
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const obj = JSON.parse(objStr);
|
|
124
|
+
const actionLabel =
|
|
125
|
+
obj.label || obj.text || String(obj.value || obj.prompt || "");
|
|
126
|
+
if (actionLabel) {
|
|
127
|
+
actions.push({
|
|
128
|
+
id: obj.id,
|
|
129
|
+
label: actionLabel,
|
|
130
|
+
prompt: obj.prompt,
|
|
131
|
+
value: obj.value,
|
|
132
|
+
style: obj.style,
|
|
133
|
+
behavior:
|
|
134
|
+
obj.behavior &&
|
|
135
|
+
(obj.behavior === "compose" || obj.behavior === "submit")
|
|
136
|
+
? obj.behavior
|
|
137
|
+
: obj.submit === false ||
|
|
138
|
+
obj.compose === true ||
|
|
139
|
+
obj.draft === true ||
|
|
140
|
+
obj.mode === "compose"
|
|
141
|
+
? "compose"
|
|
142
|
+
: undefined,
|
|
143
|
+
placeholder: obj.placeholder,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Skip malformed objects
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return actions;
|
|
152
|
+
};
|
|
153
|
+
|
|
33
154
|
function parseContentSegments(
|
|
34
155
|
content?: string,
|
|
35
156
|
hideIncomplete?: boolean,
|
|
@@ -151,16 +272,28 @@ function parseContentSegments(
|
|
|
151
272
|
const jsonStart = afterOpenLineStart + openLineLength;
|
|
152
273
|
|
|
153
274
|
const close = content.indexOf("```", jsonStart);
|
|
275
|
+
const jsonText = (
|
|
276
|
+
close === -1
|
|
277
|
+
? content.slice(jsonStart)
|
|
278
|
+
: content.slice(jsonStart, close)
|
|
279
|
+
).trim();
|
|
280
|
+
|
|
154
281
|
if (close === -1) {
|
|
155
|
-
|
|
282
|
+
// Incomplete fenced block during streaming
|
|
283
|
+
if (hideIncomplete && pick.type === "quick") {
|
|
284
|
+
// Try to parse partial quick actions and show what we have so far
|
|
285
|
+
const partialActions = extractPartialQuickActions(jsonText);
|
|
286
|
+
if (partialActions.length > 0) {
|
|
287
|
+
segments.push({ kind: "actions", actions: partialActions });
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
} else if (hideIncomplete) {
|
|
156
291
|
break;
|
|
157
292
|
} else {
|
|
158
293
|
segments.push({ kind: "text", text: content.slice(pick.start) });
|
|
159
294
|
break;
|
|
160
295
|
}
|
|
161
296
|
}
|
|
162
|
-
|
|
163
|
-
const jsonText = content.slice(jsonStart, close).trim();
|
|
164
297
|
try {
|
|
165
298
|
const parsed = JSON.parse(jsonText);
|
|
166
299
|
if (pick.type === "quick") {
|
|
@@ -258,8 +391,22 @@ function parseContentSegments(
|
|
|
258
391
|
}
|
|
259
392
|
|
|
260
393
|
const braceEnd = findMatchingBraceEnd(braceStart);
|
|
394
|
+
const jsonText = (
|
|
395
|
+
braceEnd === -1
|
|
396
|
+
? content.slice(braceStart)
|
|
397
|
+
: content.slice(braceStart, braceEnd + 1)
|
|
398
|
+
).trim();
|
|
399
|
+
|
|
261
400
|
if (braceEnd === -1) {
|
|
262
|
-
|
|
401
|
+
// Incomplete plain JSON block during streaming
|
|
402
|
+
if (hideIncomplete && pick.type === "quick") {
|
|
403
|
+
// Try to parse partial quick actions and show what we have so far
|
|
404
|
+
const partialActions = extractPartialQuickActions(jsonText);
|
|
405
|
+
if (partialActions.length > 0) {
|
|
406
|
+
segments.push({ kind: "actions", actions: partialActions });
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
} else if (hideIncomplete) {
|
|
263
410
|
break;
|
|
264
411
|
} else {
|
|
265
412
|
// Incomplete JSON; include the rest as text
|
|
@@ -268,8 +415,6 @@ function parseContentSegments(
|
|
|
268
415
|
break;
|
|
269
416
|
}
|
|
270
417
|
}
|
|
271
|
-
|
|
272
|
-
const jsonText = content.slice(braceStart, braceEnd + 1).trim();
|
|
273
418
|
try {
|
|
274
419
|
const parsed = JSON.parse(jsonText);
|
|
275
420
|
if (pick.type === "quick") {
|
|
@@ -342,6 +487,28 @@ function parseContentSegments(
|
|
|
342
487
|
return segments;
|
|
343
488
|
}
|
|
344
489
|
|
|
490
|
+
// Helper to extract command links from markdown
|
|
491
|
+
function extractCommandLinks(text: string): {
|
|
492
|
+
text: string;
|
|
493
|
+
links: Array<{ label: string; command: string; index: number }>;
|
|
494
|
+
} {
|
|
495
|
+
const links: Array<{ label: string; command: string; index: number }> = [];
|
|
496
|
+
const commandLinkPattern = /\[([^\]]+)\]\(command:([^)]+)\)/g;
|
|
497
|
+
let match;
|
|
498
|
+
|
|
499
|
+
while ((match = commandLinkPattern.exec(text)) !== null) {
|
|
500
|
+
if (match[1] && match[2]) {
|
|
501
|
+
links.push({
|
|
502
|
+
label: match[1],
|
|
503
|
+
command: match[2],
|
|
504
|
+
index: match.index,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return { text, links };
|
|
510
|
+
}
|
|
511
|
+
|
|
345
512
|
function simpleFormatToHtml(text: string): string {
|
|
346
513
|
return text
|
|
347
514
|
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
|
@@ -452,16 +619,58 @@ export function AiResponseMessage({
|
|
|
452
619
|
[messages],
|
|
453
620
|
);
|
|
454
621
|
|
|
455
|
-
|
|
456
|
-
|
|
622
|
+
// Create a flat structure of content items: Text, ToolCalls, Text, ToolCalls, etc.
|
|
623
|
+
type FlatItem =
|
|
624
|
+
| {
|
|
625
|
+
type: "segment";
|
|
626
|
+
segment: ContentSegment;
|
|
627
|
+
messageId: string;
|
|
628
|
+
message: Message;
|
|
629
|
+
}
|
|
630
|
+
| {
|
|
631
|
+
type: "toolcalls";
|
|
632
|
+
toolCalls: ToolCall[];
|
|
633
|
+
messageId: string;
|
|
634
|
+
message: Message;
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const flatItems = useMemo(() => {
|
|
638
|
+
const items: FlatItem[] = [];
|
|
639
|
+
|
|
457
640
|
nonToolAssistantMessages.forEach((message) => {
|
|
458
641
|
const contentString = (message.content ||
|
|
459
642
|
message.formattedContent ||
|
|
460
643
|
"") as string;
|
|
461
|
-
|
|
644
|
+
const segments = parseContentSegments(contentString, isStreaming);
|
|
645
|
+
|
|
646
|
+
// Add content segments
|
|
647
|
+
segments.forEach((segment) => {
|
|
648
|
+
items.push({
|
|
649
|
+
type: "segment",
|
|
650
|
+
segment,
|
|
651
|
+
messageId: message.id,
|
|
652
|
+
message,
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Add tool calls if present
|
|
657
|
+
const toolCalls =
|
|
658
|
+
message.tool_calls && message.tool_calls.length > 0
|
|
659
|
+
? message.tool_calls
|
|
660
|
+
: preservedToolCalls[message.id] || [];
|
|
661
|
+
|
|
662
|
+
if (toolCalls.length > 0) {
|
|
663
|
+
items.push({
|
|
664
|
+
type: "toolcalls",
|
|
665
|
+
toolCalls,
|
|
666
|
+
messageId: message.id,
|
|
667
|
+
message,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
462
670
|
});
|
|
463
|
-
|
|
464
|
-
|
|
671
|
+
|
|
672
|
+
return items;
|
|
673
|
+
}, [nonToolAssistantMessages, isStreaming, preservedToolCalls]);
|
|
465
674
|
|
|
466
675
|
return (
|
|
467
676
|
<div className="flex gap-3 p-4" data-testid="agent-message">
|
|
@@ -496,124 +705,212 @@ export function AiResponseMessage({
|
|
|
496
705
|
</div>
|
|
497
706
|
)}
|
|
498
707
|
|
|
499
|
-
{
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
{item.note && (
|
|
556
|
-
<div className="text-gray-1 text-xs">
|
|
557
|
-
{item.note}
|
|
558
|
-
</div>
|
|
559
|
-
)}
|
|
560
|
-
</div>
|
|
561
|
-
</label>
|
|
562
|
-
))}
|
|
563
|
-
</div>
|
|
564
|
-
</div>
|
|
708
|
+
{flatItems.map((item, itemIndex) => {
|
|
709
|
+
const prevItem = itemIndex > 0 ? flatItems[itemIndex - 1] : null;
|
|
710
|
+
const shouldAddSpacing =
|
|
711
|
+
prevItem?.type === "toolcalls" && item.type === "segment";
|
|
712
|
+
|
|
713
|
+
if (item.type === "segment") {
|
|
714
|
+
const segment = item.segment;
|
|
715
|
+
const message = item.message;
|
|
716
|
+
|
|
717
|
+
if (segment.kind === "text") {
|
|
718
|
+
const trimmedText = segment.text.trim();
|
|
719
|
+
if (!trimmedText) return null;
|
|
720
|
+
|
|
721
|
+
// Check for command links in the text
|
|
722
|
+
const { text, links } = extractCommandLinks(trimmedText);
|
|
723
|
+
|
|
724
|
+
if (links.length > 0) {
|
|
725
|
+
// Split text around command links and render buttons
|
|
726
|
+
const parts: React.ReactElement[] = [];
|
|
727
|
+
let lastIndex = 0;
|
|
728
|
+
|
|
729
|
+
links.forEach((link, linkIdx) => {
|
|
730
|
+
// Add text before the link
|
|
731
|
+
if (link.index > lastIndex) {
|
|
732
|
+
const textBefore = text.substring(lastIndex, link.index);
|
|
733
|
+
const html = simpleFormatToHtml(textBefore);
|
|
734
|
+
if (html) {
|
|
735
|
+
parts.push(
|
|
736
|
+
<span
|
|
737
|
+
key={`text-${itemIndex}-${linkIdx}`}
|
|
738
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
739
|
+
/>,
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Add the button
|
|
745
|
+
parts.push(
|
|
746
|
+
<Button
|
|
747
|
+
key={`btn-${itemIndex}-${linkIdx}`}
|
|
748
|
+
size="sm"
|
|
749
|
+
variant="outline"
|
|
750
|
+
className="mx-1 inline-flex"
|
|
751
|
+
onClick={() =>
|
|
752
|
+
onQuickAction?.(
|
|
753
|
+
{
|
|
754
|
+
label: link.label,
|
|
755
|
+
value: link.command,
|
|
756
|
+
prompt: link.command,
|
|
757
|
+
},
|
|
758
|
+
message,
|
|
759
|
+
)
|
|
760
|
+
}
|
|
761
|
+
>
|
|
762
|
+
{link.label}
|
|
763
|
+
</Button>,
|
|
565
764
|
);
|
|
765
|
+
|
|
766
|
+
// Find the end of this match in the original text
|
|
767
|
+
const matchEnd =
|
|
768
|
+
link.index +
|
|
769
|
+
`[${link.label}](command:${link.command})`.length;
|
|
770
|
+
lastIndex = matchEnd;
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// Add any remaining text after the last link
|
|
774
|
+
if (lastIndex < text.length) {
|
|
775
|
+
const textAfter = text.substring(lastIndex);
|
|
776
|
+
const html = simpleFormatToHtml(textAfter);
|
|
777
|
+
if (html) {
|
|
778
|
+
parts.push(
|
|
779
|
+
<span
|
|
780
|
+
key={`text-${itemIndex}-end`}
|
|
781
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
782
|
+
/>,
|
|
783
|
+
);
|
|
784
|
+
}
|
|
566
785
|
}
|
|
567
|
-
|
|
786
|
+
|
|
568
787
|
return (
|
|
569
|
-
<div
|
|
570
|
-
{
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
: a.style === "outline"
|
|
575
|
-
? "outline"
|
|
576
|
-
: a.style === "secondary"
|
|
577
|
-
? "secondary"
|
|
578
|
-
: "default"; // primary
|
|
579
|
-
return (
|
|
580
|
-
<Button
|
|
581
|
-
key={(a.id || a.label || "btn") + "-" + aIdx}
|
|
582
|
-
size="sm"
|
|
583
|
-
variant={variant as any}
|
|
584
|
-
onClick={() =>
|
|
585
|
-
onQuickAction?.(
|
|
586
|
-
{
|
|
587
|
-
label: a.label,
|
|
588
|
-
prompt: a.prompt,
|
|
589
|
-
value: a.value,
|
|
590
|
-
behavior: a.behavior,
|
|
591
|
-
placeholder: a.placeholder,
|
|
592
|
-
},
|
|
593
|
-
message,
|
|
594
|
-
)
|
|
595
|
-
}
|
|
596
|
-
>
|
|
597
|
-
{a.label}
|
|
598
|
-
</Button>
|
|
599
|
-
);
|
|
600
|
-
})}
|
|
788
|
+
<div
|
|
789
|
+
key={`item-${itemIndex}`}
|
|
790
|
+
className={`prose prose-sm max-w-none text-xs text-gray-700 select-text ${shouldAddSpacing ? "mt-3" : ""}`}
|
|
791
|
+
>
|
|
792
|
+
{parts}
|
|
601
793
|
</div>
|
|
602
794
|
);
|
|
603
|
-
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// No command links, render as normal
|
|
798
|
+
const html = simpleFormatToHtml(trimmedText);
|
|
799
|
+
if (!html) return null;
|
|
800
|
+
return (
|
|
801
|
+
<div
|
|
802
|
+
key={`item-${itemIndex}`}
|
|
803
|
+
className={`prose prose-sm max-w-none text-xs text-gray-700 select-text ${shouldAddSpacing ? "mt-3" : ""}`}
|
|
804
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
805
|
+
/>
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (segment.kind === "todo") {
|
|
810
|
+
const todo = segment;
|
|
811
|
+
return (
|
|
812
|
+
<div
|
|
813
|
+
key={`item-${itemIndex}`}
|
|
814
|
+
className={`my-2 ${shouldAddSpacing ? "mt-3" : ""}`}
|
|
815
|
+
>
|
|
816
|
+
{todo.title && (
|
|
817
|
+
<div className="mb-1 text-xs font-medium text-gray-800">
|
|
818
|
+
{todo.title}
|
|
819
|
+
</div>
|
|
820
|
+
)}
|
|
821
|
+
<div className="flex flex-col gap-1">
|
|
822
|
+
{todo.items.map((todoItem, iIdx) => (
|
|
823
|
+
<label
|
|
824
|
+
key={
|
|
825
|
+
(todoItem.id || todoItem.text || "todo") + "-" + iIdx
|
|
826
|
+
}
|
|
827
|
+
className="flex items-start gap-2 text-xs text-gray-700"
|
|
828
|
+
>
|
|
829
|
+
<Checkbox
|
|
830
|
+
checked={!!todoItem.done}
|
|
831
|
+
onCheckedChange={() => {}}
|
|
832
|
+
aria-readonly
|
|
833
|
+
className="mt-0.5"
|
|
834
|
+
/>
|
|
835
|
+
<div>
|
|
836
|
+
<div
|
|
837
|
+
className={
|
|
838
|
+
todoItem.done ? "line-through opacity-70" : ""
|
|
839
|
+
}
|
|
840
|
+
>
|
|
841
|
+
{todoItem.text}
|
|
842
|
+
</div>
|
|
843
|
+
{todoItem.note && (
|
|
844
|
+
<div className="text-gray-1 text-xs">
|
|
845
|
+
{todoItem.note}
|
|
846
|
+
</div>
|
|
847
|
+
)}
|
|
848
|
+
</div>
|
|
849
|
+
</label>
|
|
850
|
+
))}
|
|
851
|
+
</div>
|
|
852
|
+
</div>
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// segment.kind === "actions"
|
|
857
|
+
const actions = segment.actions;
|
|
858
|
+
return (
|
|
859
|
+
<div
|
|
860
|
+
key={`item-${itemIndex}`}
|
|
861
|
+
className={`my-2 flex flex-wrap gap-2 ${shouldAddSpacing ? "mt-3" : ""}`}
|
|
862
|
+
>
|
|
863
|
+
{actions.map((a, aIdx) => {
|
|
864
|
+
const variant =
|
|
865
|
+
a.style === "destructive"
|
|
866
|
+
? "destructive"
|
|
867
|
+
: a.style === "outline"
|
|
868
|
+
? "outline"
|
|
869
|
+
: a.style === "secondary"
|
|
870
|
+
? "secondary"
|
|
871
|
+
: "default"; // primary
|
|
872
|
+
return (
|
|
873
|
+
<Button
|
|
874
|
+
key={(a.id || a.label || "btn") + "-" + aIdx}
|
|
875
|
+
size="sm"
|
|
876
|
+
variant={variant as any}
|
|
877
|
+
onClick={() =>
|
|
878
|
+
onQuickAction?.(
|
|
879
|
+
{
|
|
880
|
+
label: a.label,
|
|
881
|
+
prompt: a.prompt,
|
|
882
|
+
value: a.value,
|
|
883
|
+
behavior: a.behavior,
|
|
884
|
+
placeholder: a.placeholder,
|
|
885
|
+
},
|
|
886
|
+
message,
|
|
887
|
+
)
|
|
888
|
+
}
|
|
889
|
+
>
|
|
890
|
+
{a.label}
|
|
891
|
+
</Button>
|
|
892
|
+
);
|
|
893
|
+
})}
|
|
894
|
+
</div>
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// item.type === "toolcalls"
|
|
899
|
+
return (
|
|
900
|
+
<div key={`item-${itemIndex}`} className="mt-3">
|
|
604
901
|
<ToolCallDisplay
|
|
605
|
-
toolCalls={toolCalls}
|
|
902
|
+
toolCalls={item.toolCalls}
|
|
606
903
|
finished={finished}
|
|
607
904
|
openPopovers={openPopovers}
|
|
608
905
|
setOpenPopovers={setOpenPopovers}
|
|
609
|
-
messageId={
|
|
906
|
+
messageId={item.messageId}
|
|
610
907
|
/>
|
|
611
908
|
</div>
|
|
612
909
|
);
|
|
613
910
|
})}
|
|
614
911
|
|
|
615
912
|
{finished && editOperations.length > 0 && (
|
|
616
|
-
<div className="
|
|
913
|
+
<div className="mt-3 flex items-center gap-2">
|
|
617
914
|
<div className="tour-ai-response-message-changes text-xs">
|
|
618
915
|
{editOperations.length} changes
|
|
619
916
|
</div>
|