@alpaca-editor/core 1.0.4049 → 1.0.4053

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.
Files changed (69) hide show
  1. package/dist/components/ui/textarea.js +1 -1
  2. package/dist/components/ui/textarea.js.map +1 -1
  3. package/dist/editor/Terminal.js +3 -3
  4. package/dist/editor/Terminal.js.map +1 -1
  5. package/dist/editor/ai/AgentCostDisplay.js +2 -2
  6. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  7. package/dist/editor/ai/AgentHistory.d.ts +4 -4
  8. package/dist/editor/ai/AgentHistory.js +1 -1
  9. package/dist/editor/ai/AgentHistory.js.map +1 -1
  10. package/dist/editor/ai/AgentTerminal.d.ts +4 -0
  11. package/dist/editor/ai/AgentTerminal.js +753 -0
  12. package/dist/editor/ai/AgentTerminal.js.map +1 -0
  13. package/dist/editor/ai/Agents.d.ts +1 -3
  14. package/dist/editor/ai/Agents.js +213 -353
  15. package/dist/editor/ai/Agents.js.map +1 -1
  16. package/dist/editor/ai/AiPromptPopover.js +2 -2
  17. package/dist/editor/ai/AiPromptPopover.js.map +1 -1
  18. package/dist/editor/ai/AiResponseMessage.d.ts +0 -1
  19. package/dist/editor/ai/AiResponseMessage.js +23 -143
  20. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  21. package/dist/editor/ai/AiTerminal.d.ts +5 -23
  22. package/dist/editor/ai/AiTerminal.js +81 -824
  23. package/dist/editor/ai/AiTerminal.js.map +1 -1
  24. package/dist/editor/ai/DancingDots.d.ts +1 -0
  25. package/dist/editor/ai/DancingDots.js +6 -0
  26. package/dist/editor/ai/DancingDots.js.map +1 -0
  27. package/dist/editor/ai/ToolCallDisplay.d.ts +37 -0
  28. package/dist/editor/ai/ToolCallDisplay.js +154 -0
  29. package/dist/editor/ai/ToolCallDisplay.js.map +1 -0
  30. package/dist/editor/client/EditorClient.js +5 -1
  31. package/dist/editor/client/EditorClient.js.map +1 -1
  32. package/dist/editor/services/agentService.d.ts +23 -30
  33. package/dist/editor/services/agentService.js +62 -124
  34. package/dist/editor/services/agentService.js.map +1 -1
  35. package/dist/editor/sidebar/GraphQL.js +1 -0
  36. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  37. package/dist/editor/sidebar/ViewSelector.js +8 -6
  38. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  39. package/dist/editor/ui/Section.js +4 -3
  40. package/dist/editor/ui/Section.js.map +1 -1
  41. package/dist/editor/utils.d.ts +4 -0
  42. package/dist/editor/utils.js +23 -0
  43. package/dist/editor/utils.js.map +1 -1
  44. package/dist/revision.d.ts +2 -2
  45. package/dist/revision.js +2 -2
  46. package/dist/styles.css +18 -33
  47. package/package.json +1 -1
  48. package/src/components/ui/textarea.tsx +1 -1
  49. package/src/editor/Terminal.tsx +4 -4
  50. package/src/editor/ai/AgentCostDisplay.tsx +7 -11
  51. package/src/editor/ai/AgentHistory.tsx +7 -9
  52. package/src/editor/ai/AgentTerminal.tsx +1094 -0
  53. package/src/editor/ai/Agents.tsx +340 -477
  54. package/src/editor/ai/AiPromptPopover.tsx +2 -2
  55. package/src/editor/ai/AiResponseMessage.tsx +85 -366
  56. package/src/editor/ai/AiTerminal.tsx +142 -1213
  57. package/src/editor/ai/DancingDots.tsx +14 -0
  58. package/src/editor/ai/ToolCallDisplay.tsx +363 -0
  59. package/src/editor/client/EditorClient.tsx +6 -1
  60. package/src/editor/services/agentService.ts +89 -162
  61. package/src/editor/sidebar/GraphQL.tsx +1 -0
  62. package/src/editor/sidebar/ViewSelector.tsx +82 -57
  63. package/src/editor/ui/Section.tsx +4 -3
  64. package/src/editor/utils.ts +29 -0
  65. package/src/revision.ts +2 -2
  66. package/dist/editor/ai/EditorAiTerminal.d.ts +0 -6
  67. package/dist/editor/ai/EditorAiTerminal.js +0 -7
  68. package/dist/editor/ai/EditorAiTerminal.js.map +0 -1
  69. package/src/editor/ai/EditorAiTerminal.tsx +0 -23
@@ -117,7 +117,7 @@ User request: ${prompt.trim()}`;
117
117
 
118
118
  const startRequest: StartAgentRequest = {
119
119
  agentId: newAgentId,
120
- messages,
120
+ message: initialPrompt,
121
121
  sessionId: editContext.sessionId,
122
122
  profileId: "Editor",
123
123
  itemid: editContext.currentItemDescriptor.id,
@@ -128,7 +128,7 @@ User request: ${prompt.trim()}`;
128
128
  profile: "Editor",
129
129
  };
130
130
 
131
- await startAgent(startRequest, aiContext);
131
+ await startAgent(startRequest);
132
132
  // Agents view will reflect the new agent via its own refresh mechanisms
133
133
  };
134
134
 
@@ -1,78 +1,11 @@
1
1
  import React, { useState, useEffect } from "react";
2
- import { JsonView, defaultStyles } from "react-json-view-lite";
3
- import "react-json-view-lite/dist/index.css";
4
2
 
5
3
  import { useEditContext } from "../client/editContext";
6
4
  import { EditOperation } from "../../types";
7
5
  import { Message } from "./AiTerminal";
6
+ import { ToolCallDisplay } from "./ToolCallDisplay";
8
7
 
9
- import {
10
- Popover,
11
- PopoverContent,
12
- PopoverTrigger,
13
- } from "../../components/ui/popover";
14
- import { SimpleTabs, Tab } from "../ui/SimpleTabs";
15
- import { Spinner } from "../ui/Spinner";
16
- import { SimpleIconButton } from "../ui/SimpleIconButton";
17
- import {
18
- X,
19
- FileText,
20
- Search,
21
- Code,
22
- Terminal,
23
- FolderOpen,
24
- FileSearch,
25
- Trash2,
26
- Globe,
27
- Brain,
28
- CheckSquare,
29
- Edit,
30
- FileEdit,
31
- Wrench,
32
- } from "lucide-react";
33
-
34
- // Function to get the appropriate icon for each tool
35
- const getToolIcon = (toolName: string) => {
36
- const iconMap: { [key: string]: React.ReactElement } = {
37
- // Content operations
38
- "get-content": <FileText strokeWidth={1} size={14} />,
39
- "edit-field": <Edit strokeWidth={1} size={14} />,
40
- "suggest-field-edit": <Edit strokeWidth={1} size={14} />,
41
-
42
- // Item operations
43
- "create-item": <FileEdit strokeWidth={1} size={14} />,
44
- "load-item": <FileText strokeWidth={1} size={14} />,
45
- "get-items": <FolderOpen strokeWidth={1} size={14} />,
46
- "get-children": <FolderOpen strokeWidth={1} size={14} />,
47
- "get-insertoptions": <FolderOpen strokeWidth={1} size={14} />,
48
-
49
- // Component operations
50
- "add-component": <Code strokeWidth={1} size={14} />,
51
- "remove-component": <Trash2 strokeWidth={1} size={14} />,
52
- "move-components": <Edit strokeWidth={1} size={14} />,
53
- "get-placeholders": <Code strokeWidth={1} size={14} />,
54
- "get-component-props-and-placeholders": <Code strokeWidth={1} size={14} />,
55
-
56
- // Search operations
57
- "search-content": <Search strokeWidth={1} size={14} />,
58
- "search-images": <FileSearch strokeWidth={1} size={14} />,
59
- "google-search": <Globe strokeWidth={1} size={14} />,
60
-
61
- // Image operations
62
- "edit-image": <FileEdit strokeWidth={1} size={14} />,
63
- "get-picture-description": <FileText strokeWidth={1} size={14} />,
64
-
65
- // Comments and suggestions
66
- "add-comment": <CheckSquare strokeWidth={1} size={14} />,
67
- "get-comments": <CheckSquare strokeWidth={1} size={14} />,
68
- "get-suggestions": <Brain strokeWidth={1} size={14} />,
69
-
70
- // Default fallback
71
- default: <Wrench strokeWidth={1} size={14} />,
72
- };
73
-
74
- return iconMap[toolName] || iconMap.default;
75
- };
8
+ import { X, Bot, Loader2 } from "lucide-react";
76
9
 
77
10
  export function AiResponseMessage({
78
11
  messages,
@@ -95,7 +28,7 @@ export function AiResponseMessage({
95
28
 
96
29
  // Store tool calls to preserve them during streaming
97
30
  const [preservedToolCalls, setPreservedToolCalls] = useState<{
98
- [messageIndex: number]: any[];
31
+ [messageId: string]: any[];
99
32
  }>({});
100
33
 
101
34
  // Track popover open state for each tool call
@@ -107,10 +40,10 @@ export function AiResponseMessage({
107
40
  useEffect(() => {
108
41
  const newPreservedToolCalls = { ...preservedToolCalls };
109
42
 
110
- messages.forEach((message, index) => {
43
+ messages.forEach((message) => {
111
44
  if (message.tool_calls && message.tool_calls.length > 0) {
112
45
  // Store non-empty tool calls
113
- newPreservedToolCalls[index] = message.tool_calls;
46
+ newPreservedToolCalls[message.id] = message.tool_calls;
114
47
  }
115
48
  });
116
49
 
@@ -127,312 +60,98 @@ export function AiResponseMessage({
127
60
  !editContext.editHistory[index]?.canUndo,
128
61
  ) === undefined;
129
62
 
130
- // Helper function to render JSON or text
131
- const renderJsonOrText = (json: string | object) => {
132
- // If it's already an object, use it directly
133
- if (typeof json === "object" && json !== null) {
134
- return (
135
- <div className="font-mono text-xs" style={{ fontSize: "12px" }}>
136
- <JsonView
137
- data={json}
138
- shouldExpandNode={(level) => level < 2}
139
- style={defaultStyles}
140
- />
141
- </div>
142
- );
143
- }
144
-
145
- // Convert to string if not already
146
- const jsonString = typeof json === "string" ? json : String(json);
63
+ // Calculate if we're currently streaming (for loading indicator)
64
+ const isStreaming = !finished && messages.length > 0;
147
65
 
148
- // Try to parse as JSON
149
- try {
150
- let parsed = JSON.parse(jsonString);
66
+ // Get the last message for timestamp
67
+ const lastMessage = messages[messages.length - 1];
151
68
 
152
- // If the result is still a string that looks like JSON, parse it again
153
- if (
154
- typeof parsed === "string" &&
155
- (parsed.startsWith("{") || parsed.startsWith("["))
156
- ) {
157
- try {
158
- parsed = JSON.parse(parsed);
159
- } catch (e2) {
160
- console.log("Second parse failed, using first result");
161
- }
162
- }
163
-
164
- return (
165
- <div className="font-mono text-xs" style={{ fontSize: "12px" }}>
166
- <JsonView
167
- data={parsed}
168
- shouldExpandNode={(level) => level < 2}
169
- style={defaultStyles}
170
- />
69
+ return (
70
+ <div className="flex gap-3 p-4">
71
+ <div className="flex-shrink-0">
72
+ <Bot className="h-6 w-6 text-green-600" strokeWidth={1} />
73
+ </div>
74
+ <div className="min-w-0 flex-1 select-text">
75
+ <div className="mb-1 flex items-center gap-2">
76
+ <span className="text-sm font-medium text-gray-900">Agent</span>
77
+
78
+ {lastMessage && (
79
+ <span className="text-xs text-gray-400">
80
+ {new Date().toLocaleTimeString()}
81
+ </span>
82
+ )}
171
83
  </div>
172
- );
173
- } catch (e) {
174
- console.log("JSON parse failed:", e, "Trying to handle as string...");
175
-
176
- // Try to handle double-escaped JSON
177
- try {
178
- const unescaped = jsonString
179
- .replace(/\\"/g, '"')
180
- .replace(/\\\\/g, "\\");
181
- const parsed = JSON.parse(unescaped);
182
-
183
- return (
184
- <div className="font-mono text-xs" style={{ fontSize: "12px" }}>
185
- <JsonView
186
- data={parsed}
187
- shouldExpandNode={(level) => level < 2}
188
- style={defaultStyles}
189
- />
190
- </div>
191
- );
192
- } catch (e2) {
193
- console.log("Unescaping also failed:", e2);
194
- // If all parsing fails, display as plain text
195
- return (
196
- <div className="font-mono text-xs break-words whitespace-pre-wrap">
197
- {jsonString}
198
- </div>
199
- );
200
- }
201
- }
202
- };
203
84
 
204
- // Helper function to create tabs for a tool call
205
- const createToolCallTabs = (toolCall: any, result: string): Tab[] => {
206
- const tabs: Tab[] = [
207
- {
208
- id: `input-${toolCall.id}`,
209
- label: "Input",
210
- content: (
211
- <div className="h-[250px] w-full overflow-auto p-2">
212
- <div className="text-xs">
213
- {renderJsonOrText(toolCall.function.arguments)}
85
+ {error && (
86
+ <div
87
+ className="mb-3 rounded-lg border-l-4 border-red-500 bg-red-50 p-3"
88
+ data-testid="agent-error"
89
+ >
90
+ <div className="flex items-start">
91
+ <div className="flex-shrink-0">
92
+ <X className="h-5 w-5 text-red-400" strokeWidth={1} />
93
+ </div>
94
+ <div className="ml-3">
95
+ <p className="text-sm font-medium text-red-800">Agent Error</p>
96
+ <p className="mt-1 text-sm text-red-700">{error}</p>
97
+ </div>
214
98
  </div>
215
99
  </div>
216
- ),
217
- },
218
- ];
219
-
220
- // Add output tab if there's a result or error
221
- if (result || toolCall.function.error) {
222
- const hasError = toolCall.function.error;
223
- tabs.push({
224
- id: `output-${toolCall.id}`,
225
- label: hasError ? "Error" : "Output",
226
- content: (
227
- <div className="h-[250px] w-full overflow-auto p-2">
228
- {hasError ? (
229
- <div className="rounded border-l-4 border-red-500 bg-red-50 p-3 text-xs text-red-600">
230
- <div className="mb-1 font-medium">Tool Error:</div>
231
- <div>{toolCall.function.error}</div>
100
+ )}
101
+
102
+ {messages
103
+ .filter((x) => x.role !== "tool" && x.role !== "user")
104
+ .map((message, filteredIndex) => {
105
+ // Use preserved tool calls if current message has empty tool calls
106
+ const toolCalls =
107
+ message.tool_calls && message.tool_calls.length > 0
108
+ ? message.tool_calls
109
+ : preservedToolCalls[message.id] || [];
110
+
111
+ return (
112
+ <div
113
+ key={filteredIndex}
114
+ className={toolCalls.length > 0 ? "mb-2" : ""}
115
+ >
116
+ <div
117
+ className="prose prose-sm max-w-none text-sm text-gray-700 select-text"
118
+ dangerouslySetInnerHTML={{
119
+ __html: message.formattedContent || message.content || "",
120
+ }}
121
+ />
122
+ <ToolCallDisplay
123
+ toolCalls={toolCalls}
124
+ finished={finished}
125
+ openPopovers={openPopovers}
126
+ setOpenPopovers={setOpenPopovers}
127
+ messageId={message.id}
128
+ />
232
129
  </div>
233
- ) : (
234
- <div className="text-xs">{renderJsonOrText(result || "")}</div>
235
- )}
236
- </div>
237
- ),
238
- });
239
- }
240
-
241
- return tabs;
242
- };
243
-
244
- // Helper function to create tool call popover content
245
- const ToolCallPopover = ({
246
- toolCall,
247
- result,
248
- onClose,
249
- }: {
250
- toolCall: any;
251
- result?: string;
252
- onClose: () => void;
253
- }) => {
254
- const [activeTab, setActiveTab] = useState(0);
255
- const tabs = createToolCallTabs(toolCall, result || "");
256
-
257
- return (
258
- <div className="flex flex-col p-3">
259
- <div className="mb-3 flex items-center justify-between border-b border-gray-200 px-2 pb-1">
260
- <div className="flex items-center gap-2 text-sm font-semibold text-gray-800">
261
- {getToolIcon(toolCall?.function?.name || "")}
262
- <span>{toolCall.displayName || toolCall.function.name}</span>
263
- </div>
264
- <SimpleIconButton
265
- onClick={onClose}
266
- icon={<X strokeWidth={1} size={16} />}
267
- label="Close"
268
- showTooltip={false}
269
- size="small"
270
- />
271
- </div>
272
- <SimpleTabs
273
- tabs={tabs}
274
- activeTab={activeTab}
275
- setActiveTab={setActiveTab}
276
- className="mb-3 border-b border-gray-200"
277
- tabClassName="text-xs"
278
- />
279
- </div>
280
- );
281
- };
130
+ );
131
+ })}
282
132
 
283
- return (
284
- <div>
285
- {error && (
286
- <div
287
- className="mb-3 rounded-lg border-l-4 border-red-500 bg-red-50 p-3"
288
- data-testid="agent-error"
289
- >
290
- <div className="flex items-start">
291
- <div className="flex-shrink-0">
292
- <X className="h-5 w-5 text-red-400" strokeWidth={1} />
293
- </div>
294
- <div className="ml-3">
295
- <p className="text-sm font-medium text-red-800">Agent Error</p>
296
- <p className="mt-1 text-sm text-red-700">{error}</p>
133
+ {finished && editOperations.length > 0 && (
134
+ <div className="my-2 flex items-center gap-2">
135
+ <div className="tour-ai-response-message-changes text-xs">
136
+ {editOperations.length} changes
297
137
  </div>
298
- </div>
299
- </div>
300
- )}
301
- {messages
302
- .filter((x) => x.role !== "tool" && x.role !== "user")
303
- .map((message, filteredIndex) => {
304
- // Find the original index of this message in the unfiltered array
305
- const originalIndex = messages.findIndex((m) => m.id === message.id);
306
- // Use preserved tool calls if current message has empty tool calls
307
- const toolCalls =
308
- message.tool_calls && message.tool_calls.length > 0
309
- ? message.tool_calls
310
- : preservedToolCalls[originalIndex] || [];
311
- return (
312
- <div
313
- key={filteredIndex}
314
- className={toolCalls.length > 0 ? "mb-2" : ""}
315
- >
138
+ {canReject && !changesRejected && (
316
139
  <div
317
- dangerouslySetInnerHTML={{
318
- __html: message.formattedContent || message.content || "",
140
+ className="flex cursor-pointer items-center gap-1 text-xs text-red-500"
141
+ onClick={async () => {
142
+ await editContext?.operations.undo(editOperations.length);
143
+ setChangesRejected(true);
319
144
  }}
320
- />
321
- {toolCalls.length > 0 && (
322
- <div className="mt-2 flex flex-col gap-2">
323
- {toolCalls.map((toolCall, toolIndex) => {
324
- const toolResult = toolCall.function.result;
325
- const popoverKey = `${originalIndex}-${toolIndex}`;
326
-
327
- return (
328
- <Popover
329
- key={toolIndex}
330
- enableIframeClickDetection={false}
331
- open={openPopovers[popoverKey] || false}
332
- onOpenChange={(open) => {
333
- setOpenPopovers((prev) => ({
334
- ...prev,
335
- [popoverKey]: open,
336
- }));
337
- }}
338
- >
339
- <PopoverTrigger asChild>
340
- <div className="inline-flex cursor-pointer items-center gap-1 text-xs text-gray-500 hover:text-gray-700">
341
- {toolResult || toolCall.function?.error ? (
342
- <div
343
- className={`flex items-center ${toolCall.function?.error ? "text-red-500" : ""}`}
344
- >
345
- {getToolIcon(toolCall?.function?.name || "")}
346
- </div>
347
- ) : finished ? (
348
- <div className="flex items-center opacity-50">
349
- {getToolIcon(toolCall?.function?.name || "")}
350
- </div>
351
- ) : (
352
- <Spinner size="xs" />
353
- )}
354
- <span
355
- className={
356
- toolCall.function?.error ? "text-red-500" : ""
357
- }
358
- >
359
- {toolCall?.displayName ||
360
- toolCall?.function?.name ||
361
- "tool call"}
362
- {toolCall.function?.error && " (error)"}
363
- </span>
364
- </div>
365
- </PopoverTrigger>
366
- <PopoverContent
367
- className="w-[450px] p-0"
368
- align="start"
369
- side="bottom"
370
- sideOffset={8}
371
- onMouseDown={(e) => e.stopPropagation()}
372
- onClick={(e) => e.stopPropagation()}
373
- >
374
- <ToolCallPopover
375
- toolCall={toolCall}
376
- result={toolResult}
377
- onClose={() => {
378
- setOpenPopovers((prev) => ({
379
- ...prev,
380
- [popoverKey]: false,
381
- }));
382
- }}
383
- />
384
- </PopoverContent>
385
- </Popover>
386
- );
387
- })}
388
- </div>
389
- )}
390
- </div>
391
- );
392
- })}
393
- {/* <div
394
- dangerouslySetInnerHTML={{
395
- __html: responseText.replaceAll("\\n", "<br>"),
396
- }}
397
- />
398
- {toolcalls.length > 0 && (
399
- <div
400
- className="mt-1 flex cursor-pointer items-center gap-1 text-xs text-gray-400"
401
- onClick={() => setShowFunctions(!showFunctions)}
402
- >
403
- <i className="pi pi-wrench text-xs" /> Tool calls
404
- </div>
405
- )} */}
406
-
407
- {/* {showFunctions && (
408
- <div className="text-xs text-gray-400">
409
- {toolcalls.map((x, index) => (
410
- <AiToolCall toolCall={x} key={index} />
411
- ))}
412
- </div>
413
- )} */}
414
-
415
- {finished && editOperations.length > 0 && (
416
- <div className="my-2 flex items-center gap-2">
417
- <div className="tour-ai-response-message-changes text-xs">
418
- {editOperations.length} changes
145
+ >
146
+ <X strokeWidth={1} size={14} /> Reject
147
+ </div>
148
+ )}
149
+ {changesRejected && (
150
+ <div className="text-xs text-red-500">rejected</div>
151
+ )}
419
152
  </div>
420
- {canReject && !changesRejected && (
421
- <div
422
- className="flex cursor-pointer items-center gap-1 text-xs text-red-500"
423
- onClick={async () => {
424
- await editContext?.operations.undo(editOperations.length);
425
- setChangesRejected(true);
426
- }}
427
- >
428
- <X strokeWidth={1} size={14} /> Reject
429
- </div>
430
- )}
431
- {changesRejected && (
432
- <div className="text-xs text-red-500">rejected</div>
433
- )}
434
- </div>
435
- )}
153
+ )}
154
+ </div>
436
155
  </div>
437
156
  );
438
157
  }