@alpaca-editor/core 1.0.4085 → 1.0.4088
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/components/ui/card.d.ts +1 -1
- package/dist/components/ui/paste-button.d.ts +14 -0
- package/dist/components/ui/paste-button.js +114 -0
- package/dist/components/ui/paste-button.js.map +1 -0
- package/dist/config/config.js +60 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +25 -0
- package/dist/editor/ContentTree.js +43 -21
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/FieldListField.js +62 -2
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +3 -1
- package/dist/editor/ai/AgentTerminal.js +96 -74
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +46 -2
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +171 -75
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.js +27 -14
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/client/EditorShell.js +110 -17
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +4 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.d.ts +1 -0
- package/dist/editor/client/hooks/useSocketMessageHandler.js +54 -20
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/hooks/useWorkbox.d.ts +1 -1
- package/dist/editor/client/hooks/useWorkbox.js +4 -4
- package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
- package/dist/editor/client/itemsRepository.d.ts +13 -1
- package/dist/editor/client/itemsRepository.js +34 -21
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +1 -1
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/control-center/Setup.d.ts +1 -0
- package/dist/editor/control-center/Setup.js +18 -0
- package/dist/editor/control-center/Setup.js.map +1 -0
- package/dist/editor/control-center/setup-steps/AiSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/AiSetupStep.js +287 -0
- package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +1 -0
- package/dist/editor/control-center/setup-steps/DbSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/DbSetupStep.js +46 -0
- package/dist/editor/control-center/setup-steps/DbSetupStep.js.map +1 -0
- package/dist/editor/control-center/setup-steps/IndexSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/IndexSetupStep.js +34 -0
- package/dist/editor/control-center/setup-steps/IndexSetupStep.js.map +1 -0
- package/dist/editor/control-center/setup-steps/SettingsSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/SettingsSetupStep.js +104 -0
- package/dist/editor/control-center/setup-steps/SettingsSetupStep.js.map +1 -0
- package/dist/editor/field-types/InternalLinkFieldEditor.js +3 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.js +2 -2
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +1 -1
- package/dist/editor/field-types/richtext/utils/profileServiceCache.js +16 -14
- package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/CompareControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/CompareControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/InlineEditor.js +25 -6
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +9 -2
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +5 -0
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/pageModel.d.ts +1 -0
- package/dist/editor/reviews/Comment.js +1 -1
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +3 -24
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentPopover.js +3 -23
- package/dist/editor/reviews/CommentPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.js +2 -1
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.js +88 -37
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +3 -0
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/sidebar/Debug.js +1 -5
- package/dist/editor/sidebar/Debug.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.js +72 -6
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/Icons.d.ts +5 -0
- package/dist/editor/ui/Icons.js +14 -0
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/editor/utils.d.ts +5 -0
- package/dist/editor/utils.js +29 -0
- package/dist/editor/utils.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/SplashScreen.js +2 -2
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +3 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/paste-button.tsx +163 -0
- package/src/config/config.tsx +68 -2
- package/src/config/types.ts +26 -0
- package/src/editor/ContentTree.tsx +48 -23
- package/src/editor/FieldListField.tsx +75 -2
- package/src/editor/ai/AgentTerminal.tsx +118 -71
- package/src/editor/ai/Agents.tsx +52 -1
- package/src/editor/ai/AiResponseMessage.tsx +234 -78
- package/src/editor/ai/AiTerminal.tsx +30 -14
- package/src/editor/client/EditorShell.tsx +128 -25
- package/src/editor/client/editContext.ts +1 -0
- package/src/editor/client/hooks/useSocketMessageHandler.ts +70 -21
- package/src/editor/client/hooks/useWorkbox.ts +4 -4
- package/src/editor/client/itemsRepository.ts +56 -25
- package/src/editor/client/pageModelBuilder.ts +1 -1
- package/src/editor/control-center/Setup.tsx +26 -0
- package/src/editor/control-center/setup-steps/AiSetupStep.tsx +462 -0
- package/src/editor/control-center/setup-steps/DbSetupStep.tsx +84 -0
- package/src/editor/control-center/setup-steps/IndexSetupStep.tsx +56 -0
- package/src/editor/control-center/setup-steps/SettingsSetupStep.tsx +176 -0
- package/src/editor/field-types/InternalLinkFieldEditor.tsx +3 -1
- package/src/editor/field-types/RichTextEditorComponent.tsx +0 -1
- package/src/editor/field-types/richtext/components/ReactSlate.tsx +14 -6
- package/src/editor/field-types/richtext/utils/profileServiceCache.ts +42 -32
- package/src/editor/menubar/toolbar-sections/CompareControls.tsx +1 -0
- package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
- package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +1 -0
- package/src/editor/page-editor-chrome/InlineEditor.tsx +29 -6
- package/src/editor/page-viewer/EditorForm.tsx +13 -2
- package/src/editor/page-viewer/PageViewerFrame.tsx +4 -0
- package/src/editor/pageModel.ts +1 -0
- package/src/editor/reviews/Comment.tsx +1 -1
- package/src/editor/reviews/CommentDisplayPopover.tsx +2 -22
- package/src/editor/reviews/CommentPopover.tsx +3 -24
- package/src/editor/reviews/CommentView.tsx +3 -2
- package/src/editor/reviews/Comments.tsx +162 -35
- package/src/editor/reviews/commentAi.ts +5 -0
- package/src/editor/sidebar/Debug.tsx +1 -5
- package/src/editor/sidebar/ViewSelector.tsx +144 -28
- package/src/editor/ui/Icons.tsx +55 -0
- package/src/editor/utils.ts +33 -0
- package/src/revision.ts +2 -2
- package/src/splash-screen/SplashScreen.tsx +5 -6
- package/src/types.ts +3 -0
|
@@ -2,11 +2,12 @@ import React, { useState, useEffect, useMemo } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { useEditContext } from "../client/editContext";
|
|
4
4
|
import { EditOperation } from "../../types";
|
|
5
|
-
import { Message } from "./AiTerminal";
|
|
5
|
+
import { Message, ToolCall } from "./AiTerminal";
|
|
6
6
|
import { ToolCallDisplay } from "./ToolCallDisplay";
|
|
7
7
|
|
|
8
8
|
import { X, Bot, Loader2 } from "lucide-react";
|
|
9
9
|
import { Button } from "../../components/ui/button";
|
|
10
|
+
import { Checkbox } from "../../components/ui/checkbox";
|
|
10
11
|
|
|
11
12
|
type QuickAction = {
|
|
12
13
|
id?: string;
|
|
@@ -18,7 +19,12 @@ type QuickAction = {
|
|
|
18
19
|
|
|
19
20
|
type ContentSegment =
|
|
20
21
|
| { kind: "text"; text: string }
|
|
21
|
-
| { kind: "actions"; actions: QuickAction[] }
|
|
22
|
+
| { kind: "actions"; actions: QuickAction[] }
|
|
23
|
+
| {
|
|
24
|
+
kind: "todo";
|
|
25
|
+
title?: string;
|
|
26
|
+
items: { id?: string; text: string; done?: boolean; note?: string }[];
|
|
27
|
+
};
|
|
22
28
|
|
|
23
29
|
function parseContentSegments(
|
|
24
30
|
content?: string,
|
|
@@ -27,22 +33,24 @@ function parseContentSegments(
|
|
|
27
33
|
if (!content) return [];
|
|
28
34
|
|
|
29
35
|
const segments: ContentSegment[] = [];
|
|
30
|
-
const
|
|
31
|
-
const
|
|
36
|
+
const fencedQuickToken = "```quick_action_buttons";
|
|
37
|
+
const plainQuickToken = "quick_action_buttons";
|
|
38
|
+
const fencedTodoToken = "```todo_list";
|
|
39
|
+
const plainTodoToken = "todo_list";
|
|
32
40
|
|
|
33
41
|
// Helper: find next valid plain token occurrence (at line start or after newline)
|
|
34
|
-
const
|
|
35
|
-
let idx = content.indexOf(
|
|
42
|
+
const findNextPlainHeader = (token: string, fromIndex: number): number => {
|
|
43
|
+
let idx = content.indexOf(token, fromIndex);
|
|
36
44
|
while (idx !== -1) {
|
|
37
45
|
const before = idx > 0 ? content[idx - 1] : "\n";
|
|
38
|
-
const after = content[idx +
|
|
46
|
+
const after = content[idx + token.length] || "\n";
|
|
39
47
|
const isBoundaryBefore = before === "\n" || before === "\r";
|
|
40
48
|
const isBoundaryAfter =
|
|
41
49
|
after === "\n" || after === "\r" || after === ":" || /\s/.test(after);
|
|
42
50
|
// Ensure it's not the fenced token which would have ``` right before
|
|
43
51
|
const isFencedPrefix = content.slice(Math.max(0, idx - 3), idx) === "```";
|
|
44
52
|
if (isBoundaryBefore && isBoundaryAfter && !isFencedPrefix) return idx;
|
|
45
|
-
idx = content.indexOf(
|
|
53
|
+
idx = content.indexOf(token, idx + 1);
|
|
46
54
|
}
|
|
47
55
|
return -1;
|
|
48
56
|
};
|
|
@@ -84,32 +92,54 @@ function parseContentSegments(
|
|
|
84
92
|
|
|
85
93
|
let cursor = 0;
|
|
86
94
|
while (cursor < content.length) {
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
const nextFencedQuick = content.indexOf(fencedQuickToken, cursor);
|
|
96
|
+
const nextPlainQuick = findNextPlainHeader(plainQuickToken, cursor);
|
|
97
|
+
const nextFencedTodo = content.indexOf(fencedTodoToken, cursor);
|
|
98
|
+
const nextPlainTodo = findNextPlainHeader(plainTodoToken, cursor);
|
|
99
|
+
|
|
100
|
+
// Determine which token occurs first among all supported tokens
|
|
101
|
+
type TokenPick = {
|
|
102
|
+
start: number;
|
|
103
|
+
kind: "fenced" | "plain";
|
|
104
|
+
type: "quick" | "todo";
|
|
105
|
+
};
|
|
106
|
+
const candidates: TokenPick[] = [];
|
|
107
|
+
if (nextFencedQuick !== -1)
|
|
108
|
+
candidates.push({
|
|
109
|
+
start: nextFencedQuick,
|
|
110
|
+
kind: "fenced",
|
|
111
|
+
type: "quick",
|
|
112
|
+
});
|
|
113
|
+
if (nextPlainQuick !== -1)
|
|
114
|
+
candidates.push({ start: nextPlainQuick, kind: "plain", type: "quick" });
|
|
115
|
+
if (nextFencedTodo !== -1)
|
|
116
|
+
candidates.push({ start: nextFencedTodo, kind: "fenced", type: "todo" });
|
|
117
|
+
if (nextPlainTodo !== -1)
|
|
118
|
+
candidates.push({ start: nextPlainTodo, kind: "plain", type: "todo" });
|
|
119
|
+
|
|
120
|
+
let pick: TokenPick | null = null;
|
|
121
|
+
if (candidates.length > 0) {
|
|
122
|
+
pick = candidates.reduce((min, cur) =>
|
|
123
|
+
cur.start < min.start ? cur : min,
|
|
124
|
+
);
|
|
99
125
|
}
|
|
100
126
|
|
|
101
|
-
if (
|
|
127
|
+
if (pick === null) {
|
|
102
128
|
const tail = content.slice(cursor);
|
|
103
129
|
if (tail) segments.push({ kind: "text", text: tail });
|
|
104
130
|
break;
|
|
105
131
|
}
|
|
106
132
|
|
|
107
133
|
// Push any text before the token
|
|
108
|
-
const textBefore = content.slice(cursor, start);
|
|
134
|
+
const textBefore = content.slice(cursor, pick.start);
|
|
109
135
|
if (textBefore) segments.push({ kind: "text", text: textBefore });
|
|
110
136
|
|
|
111
|
-
if (
|
|
112
|
-
const
|
|
137
|
+
if (pick.kind === "fenced") {
|
|
138
|
+
const openTokenLength =
|
|
139
|
+
pick.type === "quick"
|
|
140
|
+
? fencedQuickToken.length
|
|
141
|
+
: fencedTodoToken.length;
|
|
142
|
+
const afterOpenLineStart = pick.start + openTokenLength;
|
|
113
143
|
const openLineMatch = content
|
|
114
144
|
.slice(afterOpenLineStart)
|
|
115
145
|
.match(/^[^\n\r]*\r?\n?/);
|
|
@@ -121,7 +151,7 @@ function parseContentSegments(
|
|
|
121
151
|
if (hideIncomplete) {
|
|
122
152
|
break;
|
|
123
153
|
} else {
|
|
124
|
-
segments.push({ kind: "text", text: content.slice(start) });
|
|
154
|
+
segments.push({ kind: "text", text: content.slice(pick.start) });
|
|
125
155
|
break;
|
|
126
156
|
}
|
|
127
157
|
}
|
|
@@ -129,31 +159,60 @@ function parseContentSegments(
|
|
|
129
159
|
const jsonText = content.slice(jsonStart, close).trim();
|
|
130
160
|
try {
|
|
131
161
|
const parsed = JSON.parse(jsonText);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
162
|
+
if (pick.type === "quick") {
|
|
163
|
+
const rawActions = Array.isArray(parsed)
|
|
164
|
+
? parsed
|
|
165
|
+
: parsed?.actions || parsed?.buttons || parsed?.choices || [];
|
|
166
|
+
const actions: QuickAction[] = [];
|
|
167
|
+
if (Array.isArray(rawActions)) {
|
|
168
|
+
rawActions.forEach((a) => {
|
|
169
|
+
if (!a) return;
|
|
170
|
+
const action: QuickAction = {
|
|
171
|
+
id: a.id,
|
|
172
|
+
label: a.label || a.text || String(a.value || a.prompt || ""),
|
|
173
|
+
prompt: a.prompt,
|
|
174
|
+
value: a.value,
|
|
175
|
+
style: a.style,
|
|
176
|
+
};
|
|
177
|
+
if (action.label) actions.push(action);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (actions.length > 0) {
|
|
181
|
+
segments.push({ kind: "actions", actions });
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// todo_list
|
|
185
|
+
const todoItems = Array.isArray(parsed)
|
|
186
|
+
? parsed
|
|
187
|
+
: parsed?.items || [];
|
|
188
|
+
const title = Array.isArray(parsed) ? undefined : parsed?.title;
|
|
189
|
+
const cleaned = (Array.isArray(todoItems) ? todoItems : [])
|
|
190
|
+
.map((i) => {
|
|
191
|
+
if (!i) return null;
|
|
192
|
+
const text = i.text || i.label || String(i.task || i.title || "");
|
|
193
|
+
if (!text) return null;
|
|
194
|
+
return {
|
|
195
|
+
id: i.id,
|
|
196
|
+
text,
|
|
197
|
+
done: !!(i.done ?? i.completed ?? i.checked),
|
|
198
|
+
note: i.note || i.description,
|
|
199
|
+
} as { id?: string; text: string; done?: boolean; note?: string };
|
|
200
|
+
})
|
|
201
|
+
.filter(Boolean) as {
|
|
202
|
+
id?: string;
|
|
203
|
+
text: string;
|
|
204
|
+
done?: boolean;
|
|
205
|
+
note?: string;
|
|
206
|
+
}[];
|
|
207
|
+
if (cleaned.length > 0) {
|
|
208
|
+
segments.push({ kind: "todo", title, items: cleaned });
|
|
209
|
+
}
|
|
151
210
|
}
|
|
152
211
|
} catch {
|
|
153
212
|
if (!hideIncomplete) {
|
|
154
213
|
segments.push({
|
|
155
214
|
kind: "text",
|
|
156
|
-
text: content.slice(start, close + 3),
|
|
215
|
+
text: content.slice(pick.start, close + 3),
|
|
157
216
|
});
|
|
158
217
|
}
|
|
159
218
|
}
|
|
@@ -162,7 +221,9 @@ function parseContentSegments(
|
|
|
162
221
|
}
|
|
163
222
|
|
|
164
223
|
// Plain header without backticks
|
|
165
|
-
const
|
|
224
|
+
const headerTokenLength =
|
|
225
|
+
pick.type === "quick" ? plainQuickToken.length : plainTodoToken.length;
|
|
226
|
+
const afterHeaderStart = pick.start + headerTokenLength;
|
|
166
227
|
const headerRestMatch = content
|
|
167
228
|
.slice(afterHeaderStart)
|
|
168
229
|
.match(/^[^\n\r]*\r?\n?/);
|
|
@@ -187,7 +248,7 @@ function parseContentSegments(
|
|
|
187
248
|
break;
|
|
188
249
|
} else {
|
|
189
250
|
// Incomplete JSON; include the rest as text
|
|
190
|
-
const remaining = content.slice(start);
|
|
251
|
+
const remaining = content.slice(pick.start);
|
|
191
252
|
if (remaining) segments.push({ kind: "text", text: remaining });
|
|
192
253
|
break;
|
|
193
254
|
}
|
|
@@ -196,29 +257,55 @@ function parseContentSegments(
|
|
|
196
257
|
const jsonText = content.slice(braceStart, braceEnd + 1).trim();
|
|
197
258
|
try {
|
|
198
259
|
const parsed = JSON.parse(jsonText);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
260
|
+
if (pick.type === "quick") {
|
|
261
|
+
const rawActions = Array.isArray(parsed)
|
|
262
|
+
? parsed
|
|
263
|
+
: parsed?.actions || parsed?.buttons || parsed?.choices || [];
|
|
264
|
+
const actions: QuickAction[] = [];
|
|
265
|
+
if (Array.isArray(rawActions)) {
|
|
266
|
+
rawActions.forEach((a) => {
|
|
267
|
+
if (!a) return;
|
|
268
|
+
const action: QuickAction = {
|
|
269
|
+
id: a.id,
|
|
270
|
+
label: a.label || a.text || String(a.value || a.prompt || ""),
|
|
271
|
+
prompt: a.prompt,
|
|
272
|
+
value: a.value,
|
|
273
|
+
style: a.style,
|
|
274
|
+
};
|
|
275
|
+
if (action.label) actions.push(action);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (actions.length > 0) {
|
|
279
|
+
segments.push({ kind: "actions", actions });
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
const todoItems = Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
283
|
+
const title = Array.isArray(parsed) ? undefined : parsed?.title;
|
|
284
|
+
const cleaned = (Array.isArray(todoItems) ? todoItems : [])
|
|
285
|
+
.map((i) => {
|
|
286
|
+
if (!i) return null;
|
|
287
|
+
const text = i.text || i.label || String(i.task || i.title || "");
|
|
288
|
+
if (!text) return null;
|
|
289
|
+
return {
|
|
290
|
+
id: i.id,
|
|
291
|
+
text,
|
|
292
|
+
done: !!(i.done ?? i.completed ?? i.checked),
|
|
293
|
+
note: i.note || i.description,
|
|
294
|
+
} as { id?: string; text: string; done?: boolean; note?: string };
|
|
295
|
+
})
|
|
296
|
+
.filter(Boolean) as {
|
|
297
|
+
id?: string;
|
|
298
|
+
text: string;
|
|
299
|
+
done?: boolean;
|
|
300
|
+
note?: string;
|
|
301
|
+
}[];
|
|
302
|
+
if (cleaned.length > 0) {
|
|
303
|
+
segments.push({ kind: "todo", title, items: cleaned });
|
|
304
|
+
}
|
|
218
305
|
}
|
|
219
306
|
} catch {
|
|
220
307
|
// On parse failure, keep original as text (excluding already-pushed prefix)
|
|
221
|
-
const fallbackText = content.slice(start, braceEnd + 1);
|
|
308
|
+
const fallbackText = content.slice(pick.start, braceEnd + 1);
|
|
222
309
|
if (!hideIncomplete && fallbackText) {
|
|
223
310
|
segments.push({ kind: "text", text: fallbackText });
|
|
224
311
|
}
|
|
@@ -260,7 +347,7 @@ export function AiResponseMessage({
|
|
|
260
347
|
|
|
261
348
|
// Store tool calls to preserve them during streaming
|
|
262
349
|
const [preservedToolCalls, setPreservedToolCalls] = useState<{
|
|
263
|
-
[messageId: string]:
|
|
350
|
+
[messageId: string]: ToolCall[];
|
|
264
351
|
}>({});
|
|
265
352
|
|
|
266
353
|
// Track popover open state for each tool call
|
|
@@ -268,18 +355,46 @@ export function AiResponseMessage({
|
|
|
268
355
|
[key: string]: boolean;
|
|
269
356
|
}>({});
|
|
270
357
|
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
|
|
358
|
+
// Helper to avoid unnecessary updates when tool calls are unchanged
|
|
359
|
+
const areToolCallsEqual = (a?: ToolCall[], b?: ToolCall[]): boolean => {
|
|
360
|
+
if (a === b) return true;
|
|
361
|
+
if (!a || !b) return false;
|
|
362
|
+
if (a.length !== b.length) return false;
|
|
363
|
+
for (let i = 0; i < a.length; i++) {
|
|
364
|
+
const ai = a[i];
|
|
365
|
+
const bi = b[i];
|
|
366
|
+
if (
|
|
367
|
+
ai?.id !== bi?.id ||
|
|
368
|
+
ai?.displayName !== bi?.displayName ||
|
|
369
|
+
ai?.function?.name !== bi?.function?.name ||
|
|
370
|
+
ai?.function?.arguments !== bi?.function?.arguments
|
|
371
|
+
) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
376
|
+
};
|
|
274
377
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
378
|
+
// Update preserved tool calls when messages change, but only if values actually change
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
setPreservedToolCalls((prev) => {
|
|
381
|
+
let changed = false;
|
|
382
|
+
const next = { ...prev };
|
|
383
|
+
|
|
384
|
+
for (const message of messages) {
|
|
385
|
+
const hasToolCalls = !!(
|
|
386
|
+
message.tool_calls && message.tool_calls.length > 0
|
|
387
|
+
);
|
|
388
|
+
if (!hasToolCalls) continue;
|
|
389
|
+
|
|
390
|
+
if (!areToolCallsEqual(next[message.id], message.tool_calls)) {
|
|
391
|
+
next[message.id] = message.tool_calls!;
|
|
392
|
+
changed = true;
|
|
393
|
+
}
|
|
279
394
|
}
|
|
280
|
-
});
|
|
281
395
|
|
|
282
|
-
|
|
396
|
+
return changed ? next : prev;
|
|
397
|
+
});
|
|
283
398
|
}, [messages]);
|
|
284
399
|
|
|
285
400
|
const reversedEditOperations = [...editOperations].reverse();
|
|
@@ -317,7 +432,7 @@ export function AiResponseMessage({
|
|
|
317
432
|
}, [nonToolAssistantMessages, isStreaming]);
|
|
318
433
|
|
|
319
434
|
return (
|
|
320
|
-
<div className="flex gap-3 p-4">
|
|
435
|
+
<div className="flex gap-3 p-4" data-testid="agent-message">
|
|
321
436
|
<div className="flex-shrink-0">
|
|
322
437
|
<Bot className="h-6 w-6 text-green-600" strokeWidth={1} />
|
|
323
438
|
</div>
|
|
@@ -376,6 +491,47 @@ export function AiResponseMessage({
|
|
|
376
491
|
/>
|
|
377
492
|
);
|
|
378
493
|
}
|
|
494
|
+
if (segment.kind === "todo") {
|
|
495
|
+
const todo = segment;
|
|
496
|
+
return (
|
|
497
|
+
<div key={"todo-" + idx} className="my-2">
|
|
498
|
+
{todo.title && (
|
|
499
|
+
<div className="mb-1 text-sm font-medium text-gray-800">
|
|
500
|
+
{todo.title}
|
|
501
|
+
</div>
|
|
502
|
+
)}
|
|
503
|
+
<div className="flex flex-col gap-1">
|
|
504
|
+
{todo.items.map((item, iIdx) => (
|
|
505
|
+
<label
|
|
506
|
+
key={(item.id || item.text || "todo") + "-" + iIdx}
|
|
507
|
+
className="flex items-start gap-2 text-sm text-gray-700"
|
|
508
|
+
>
|
|
509
|
+
<Checkbox
|
|
510
|
+
checked={!!item.done}
|
|
511
|
+
onCheckedChange={() => {}}
|
|
512
|
+
aria-readonly
|
|
513
|
+
className="mt-0.5"
|
|
514
|
+
/>
|
|
515
|
+
<div>
|
|
516
|
+
<div
|
|
517
|
+
className={
|
|
518
|
+
item.done ? "line-through opacity-70" : ""
|
|
519
|
+
}
|
|
520
|
+
>
|
|
521
|
+
{item.text}
|
|
522
|
+
</div>
|
|
523
|
+
{item.note && (
|
|
524
|
+
<div className="text-xs text-gray-500">
|
|
525
|
+
{item.note}
|
|
526
|
+
</div>
|
|
527
|
+
)}
|
|
528
|
+
</div>
|
|
529
|
+
</label>
|
|
530
|
+
))}
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
379
535
|
const actions = segment.actions;
|
|
380
536
|
return (
|
|
381
537
|
<div key={"act-" + idx} className="my-2 flex flex-wrap gap-2">
|
|
@@ -342,20 +342,36 @@ export function AiTerminal({
|
|
|
342
342
|
|
|
343
343
|
// Build complete message history for API call
|
|
344
344
|
const conversationHistory = [...messagesRef.current, userMessage];
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
345
|
+
const shouldAddTodoFormat = /\b(to-?do|check\s*list|task\s*list)\b/i.test(
|
|
346
|
+
text,
|
|
347
|
+
);
|
|
348
|
+
const todoFormatInstructions =
|
|
349
|
+
"If you produce a to-do list, output it as a 'todo_list' JSON block. Prefer one of these two forms only, with no extra commentary before/after it: \n" +
|
|
350
|
+
'```todo_list\n{ "title": "<optional title>", "items": [ { "text": "<task>", "done": false, "note": "<optional>" } ] }\n```\n' +
|
|
351
|
+
"or a plain header: todo_list: { ... }. Include only fields: title (optional), items (array of {text, done?, note?}).";
|
|
352
|
+
const systemMessages = options?.hiddenSystemPrompt
|
|
353
|
+
? [
|
|
354
|
+
{
|
|
355
|
+
role: "system",
|
|
356
|
+
name: "system",
|
|
357
|
+
content: options.hiddenSystemPrompt,
|
|
358
|
+
id: crypto.randomUUID(),
|
|
359
|
+
toolCalls: [],
|
|
360
|
+
},
|
|
361
|
+
]
|
|
362
|
+
: [];
|
|
363
|
+
const todoSystem = shouldAddTodoFormat
|
|
364
|
+
? [
|
|
365
|
+
{
|
|
366
|
+
role: "system",
|
|
367
|
+
name: "system",
|
|
368
|
+
content: todoFormatInstructions,
|
|
369
|
+
id: crypto.randomUUID(),
|
|
370
|
+
toolCalls: [],
|
|
371
|
+
},
|
|
372
|
+
]
|
|
373
|
+
: [];
|
|
374
|
+
const messages = [...systemMessages, ...todoSystem, ...conversationHistory];
|
|
359
375
|
|
|
360
376
|
const response = await executePrompt(
|
|
361
377
|
messages,
|