@alpaca-editor/core 1.0.3832 → 1.0.3833

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 (71) hide show
  1. package/dist/config/config.js +17 -0
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/ContentTree.js +0 -1
  4. package/dist/editor/ContentTree.js.map +1 -1
  5. package/dist/editor/ai/AiResponseMessage.d.ts +5 -6
  6. package/dist/editor/ai/AiResponseMessage.js +8 -6
  7. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  8. package/dist/editor/ai/AiTerminal.d.ts +13 -3
  9. package/dist/editor/ai/AiTerminal.js +107 -71
  10. package/dist/editor/ai/AiTerminal.js.map +1 -1
  11. package/dist/editor/ai/AiToolCall.d.ts +3 -7
  12. package/dist/editor/ai/AiToolCall.js +15 -3
  13. package/dist/editor/ai/AiToolCall.js.map +1 -1
  14. package/dist/editor/ai/GhostWriter.d.ts +1 -0
  15. package/dist/editor/ai/GhostWriter.js +307 -0
  16. package/dist/editor/ai/GhostWriter.js.map +1 -0
  17. package/dist/editor/client/EditorClient.js +3 -0
  18. package/dist/editor/client/EditorClient.js.map +1 -1
  19. package/dist/editor/client/editContext.d.ts +2 -0
  20. package/dist/editor/client/editContext.js.map +1 -1
  21. package/dist/editor/media-selector/AiImageSearch.js +2 -2
  22. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  23. package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -2
  24. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  25. package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
  26. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  27. package/dist/editor/page-editor-chrome/InlineEditor.js +124 -11
  28. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  29. package/dist/editor/page-editor-chrome/useInlineAICompletion.d.ts +7 -0
  30. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +600 -0
  31. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -0
  32. package/dist/editor/page-viewer/PageViewer.js +6 -1
  33. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  34. package/dist/editor/page-viewer/PageViewerFrame.js +0 -1
  35. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  36. package/dist/editor/services/aiService.d.ts +6 -1
  37. package/dist/editor/services/aiService.js +2 -3
  38. package/dist/editor/services/aiService.js.map +1 -1
  39. package/dist/page-wizard/steps/CreatePage.js +5 -2
  40. package/dist/page-wizard/steps/CreatePage.js.map +1 -1
  41. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +2 -1
  42. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +1 -1
  43. package/dist/page-wizard/steps/ImagesStep.js +1 -3
  44. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  45. package/dist/page-wizard/steps/LayoutStep.js +5 -5
  46. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  47. package/dist/page-wizard/steps/SelectStep.js +5 -5
  48. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  49. package/dist/styles.css +9 -0
  50. package/package.json +1 -1
  51. package/src/config/config.tsx +20 -2
  52. package/src/editor/ContentTree.tsx +0 -2
  53. package/src/editor/ai/AiResponseMessage.tsx +43 -19
  54. package/src/editor/ai/AiTerminal.tsx +153 -105
  55. package/src/editor/ai/AiToolCall.tsx +37 -21
  56. package/src/editor/ai/GhostWriter.tsx +432 -0
  57. package/src/editor/client/EditorClient.tsx +4 -0
  58. package/src/editor/client/editContext.ts +3 -0
  59. package/src/editor/media-selector/AiImageSearch.tsx +8 -9
  60. package/src/editor/media-selector/AiImageSearchPrompt.tsx +4 -5
  61. package/src/editor/page-editor-chrome/FrameMenu.tsx +1 -0
  62. package/src/editor/page-editor-chrome/InlineEditor.tsx +177 -18
  63. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +734 -0
  64. package/src/editor/page-viewer/PageViewer.tsx +21 -6
  65. package/src/editor/page-viewer/PageViewerFrame.tsx +0 -1
  66. package/src/editor/services/aiService.ts +10 -6
  67. package/src/page-wizard/steps/CreatePage.tsx +21 -26
  68. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +2 -2
  69. package/src/page-wizard/steps/ImagesStep.tsx +2 -5
  70. package/src/page-wizard/steps/LayoutStep.tsx +12 -13
  71. package/src/page-wizard/steps/SelectStep.tsx +12 -13
@@ -463,8 +463,6 @@ export default function ContentTree({
463
463
  })),
464
464
  );
465
465
 
466
- console.log("ITEMS", items);
467
-
468
466
  if (!items || items.length === 0) return;
469
467
 
470
468
  const menuItems =
@@ -1,25 +1,25 @@
1
1
  import { useState } from "react";
2
- import { AiToolCall, ToolCall } from "./AiToolCall";
3
- import { EditOperation } from "../../types";
2
+
4
3
  import { useEditContext } from "../client/editContext";
4
+ import { EditOperation } from "@/types";
5
+ import { Message } from "./AiTerminal";
6
+ import { AiToolCall } from "./AiToolCall";
5
7
 
6
8
  export function AiResponseMessage({
7
- responseText,
8
- toolcalls,
9
- editOperations,
9
+ messages,
10
10
  finished,
11
+ editOperations,
11
12
  }: {
12
- responseText: string;
13
- toolcalls: ToolCall[];
14
- editOperations: EditOperation[];
13
+ messages: Message[];
15
14
  finished: boolean;
15
+ editOperations: EditOperation[];
16
16
  }) {
17
17
  const editContext = useEditContext();
18
18
  if (!editContext) return <></>;
19
19
 
20
- const [showFunctions, setShowFunctions] = useState(false);
20
+ // const [showFunctions, setShowFunctions] = useState(false);
21
21
  const [changesRejected, setChangesRejected] = useState<boolean | undefined>(
22
- undefined
22
+ undefined,
23
23
  );
24
24
 
25
25
  const reversedEditOperations = [...editOperations].reverse();
@@ -29,41 +29,65 @@ export function AiResponseMessage({
29
29
  reversedEditOperations.find(
30
30
  (x, index) =>
31
31
  editContext.editHistory[index]?.id !== x.id ||
32
- !editContext.editHistory[index]?.canUndo
32
+ !editContext.editHistory[index]?.canUndo,
33
33
  ) === undefined;
34
34
 
35
35
  return (
36
36
  <div>
37
- <div
37
+ {messages
38
+ .filter((x) => x.role !== "tool")
39
+ .map((message, index) => (
40
+ <div key={index}>
41
+ <div
42
+ dangerouslySetInnerHTML={{
43
+ __html: message.formattedContent || message.content || "",
44
+ }}
45
+ />
46
+ {message.tool_calls && message.tool_calls.length > 0 && (
47
+ <div className="text-xs text-gray-400">
48
+ {message.tool_calls.map((x, index) => (
49
+ <AiToolCall
50
+ toolCall={x}
51
+ key={index}
52
+ result={messages.find(
53
+ (m) => m.role === "tool" && m.tool_call_id === x.id,
54
+ )}
55
+ />
56
+ ))}
57
+ </div>
58
+ )}
59
+ </div>
60
+ ))}
61
+ {/* <div
38
62
  dangerouslySetInnerHTML={{
39
63
  __html: responseText.replaceAll("\\n", "<br>"),
40
64
  }}
41
65
  />
42
66
  {toolcalls.length > 0 && (
43
67
  <div
44
- className="text-xs text-gray-400 cursor-pointer flex items-center gap-1 mt-1"
68
+ className="mt-1 flex cursor-pointer items-center gap-1 text-xs text-gray-400"
45
69
  onClick={() => setShowFunctions(!showFunctions)}
46
70
  >
47
71
  <i className="pi pi-wrench text-xs" /> Tool calls
48
72
  </div>
49
- )}
73
+ )} */}
50
74
 
51
- {showFunctions && (
75
+ {/* {showFunctions && (
52
76
  <div className="text-xs text-gray-400">
53
77
  {toolcalls.map((x, index) => (
54
78
  <AiToolCall toolCall={x} key={index} />
55
79
  ))}
56
80
  </div>
57
- )}
81
+ )} */}
58
82
 
59
83
  {finished && editOperations.length > 0 && (
60
- <div className="flex gap-2 my-2 items-center">
61
- <div className="text-xs tour-ai-response-message-changes">
84
+ <div className="my-2 flex items-center gap-2">
85
+ <div className="tour-ai-response-message-changes text-xs">
62
86
  {editOperations.length} changes
63
87
  </div>
64
88
  {canReject && !changesRejected && (
65
89
  <div
66
- className="text-xs text-red-500 cursor-pointer flex items-center gap-1"
90
+ className="flex cursor-pointer items-center gap-1 text-xs text-red-500"
67
91
  onClick={async () => {
68
92
  await editContext?.operations.undo(editOperations.length);
69
93
  setChangesRejected(true);
@@ -19,29 +19,34 @@ import {
19
19
  } from "../../types";
20
20
  import { SimpleIconButton } from "../ui/SimpleIconButton";
21
21
 
22
- type Message = {
23
- content: string;
24
- name: string;
25
- role: string;
26
- };
27
-
28
22
  type Response = {
29
- responseText: string;
23
+ messages: Message[];
30
24
  editOperations: EditOperation[];
31
25
  numInputTokens: number;
32
26
  numOutputTokens: number;
33
27
  numCachedTokens: number;
34
- toolCalls?: ToolCall[];
35
28
  state: string;
36
29
  };
37
30
 
38
- type ToolCall = {
31
+ export type ToolCall = {
32
+ id: string;
33
+ displayName: string;
39
34
  function: {
40
35
  name: string;
41
36
  arguments: string;
42
37
  };
43
38
  };
44
39
 
40
+ export type Message = {
41
+ id: number;
42
+ content: string;
43
+ formattedContent?: string;
44
+ name: string;
45
+ role: string;
46
+ tool_calls?: ToolCall[];
47
+ tool_call_id?: string;
48
+ };
49
+
45
50
  export type AiContext = {
46
51
  promptData: any;
47
52
  endpoint: string;
@@ -78,6 +83,7 @@ export function AiTerminal({
78
83
  const [initialPromptExecuted, setInitialPromptExecuted] = useState(false);
79
84
  const selection = editContext.selection;
80
85
  const terminalRef = useRef<{ submit: () => void }>(null);
86
+ const [responseMessages, setResponseMessages] = useState<Message[]>([]);
81
87
 
82
88
  useEffect(() => {
83
89
  if (options?.initialPrompt && !initialPromptExecuted && model) {
@@ -111,8 +117,70 @@ export function AiTerminal({
111
117
 
112
118
  const messagesRef = useRef(messages);
113
119
  useEffect(() => {
114
- messagesRef.current = messages;
115
- }, [messages]);
120
+ messagesRef.current = responseMessages;
121
+ }, [responseMessages]);
122
+
123
+ function handleResponse(
124
+ response: Response,
125
+ terminalCallback: (text: React.ReactNode, finished: boolean) => void,
126
+ isFinished: boolean,
127
+ ) {
128
+ const currentMessages = messagesRef.current;
129
+ const updatedMessages = response.messages;
130
+
131
+ // Merge updatedMessages into currentMessages if they exist
132
+ if (updatedMessages && Array.isArray(updatedMessages)) {
133
+ // Ensure each message has the required properties
134
+ updatedMessages.forEach((message) => {
135
+ const existingMessageIndex = currentMessages.findIndex(
136
+ (m) => m.id === message.id,
137
+ );
138
+ const formattedContent = message.content
139
+ .trim()
140
+ .replaceAll("\n", "<br>")
141
+ ?.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>");
142
+
143
+ if (existingMessageIndex !== -1) {
144
+ // Update existing message
145
+ currentMessages[existingMessageIndex] = {
146
+ ...currentMessages[existingMessageIndex],
147
+ ...message,
148
+ content: message.content,
149
+ formattedContent: formattedContent,
150
+ tool_calls:
151
+ message.tool_calls ||
152
+ currentMessages[existingMessageIndex]?.tool_calls ||
153
+ [],
154
+ };
155
+ } else {
156
+ // Add new message
157
+ currentMessages.push({
158
+ ...message,
159
+ content: formattedContent,
160
+ tool_calls: message.tool_calls || [], // Ensure toolCalls exists
161
+ });
162
+ }
163
+ });
164
+ }
165
+
166
+ // Update the messages state
167
+ setMessages([...currentMessages]);
168
+ setResponseMessages([...currentMessages]);
169
+ console.log(
170
+ "response",
171
+ messagesRef.current,
172
+ response.messages,
173
+ currentMessages,
174
+ );
175
+ terminalCallback(
176
+ <AiResponseMessage
177
+ messages={currentMessages}
178
+ editOperations={response.editOperations}
179
+ finished={isFinished}
180
+ />,
181
+ isFinished,
182
+ );
183
+ }
116
184
 
117
185
  async function commandHandler(
118
186
  text: string,
@@ -120,7 +188,13 @@ export function AiTerminal({
120
188
  ) {
121
189
  const newMessages = [
122
190
  ...messagesRef.current,
123
- { content: text, role: "user", name: "user" },
191
+ {
192
+ id: Date.now(), // Add unique id
193
+ content: text,
194
+ role: "user",
195
+ name: "user",
196
+ tool_calls: [], // Add empty toolCalls array
197
+ },
124
198
  ];
125
199
 
126
200
  const context = createAiContext({ editContext });
@@ -129,6 +203,8 @@ export function AiTerminal({
129
203
  const selectedText = editContext?.selectedRange?.text || null;
130
204
  if (!activeProfile || !model) return;
131
205
 
206
+ setResponseMessages([]);
207
+
132
208
  const messages = [
133
209
  ...(options?.hiddenSystemPrompt
134
210
  ? [
@@ -136,6 +212,8 @@ export function AiTerminal({
136
212
  role: "system",
137
213
  name: "system",
138
214
  content: options.hiddenSystemPrompt,
215
+ id: 0, // System message id
216
+ toolCalls: [],
139
217
  },
140
218
  ]
141
219
  : []),
@@ -155,61 +233,61 @@ export function AiTerminal({
155
233
  (response: any) => {
156
234
  setResponse(response);
157
235
  handleResponse(response, callback, false);
158
- if (response.editOperations.length) {
159
- if (!editContext) return;
160
-
161
- const newOps = response.editOperations.slice(lastOpIndex);
162
-
163
- if (newOps.length === 0) return;
164
-
165
- const isEditTextFieldOp = (op: EditOperation) => {
166
- if (op.type !== "edit-field") return false;
167
- const editFieldOp = op as EditFieldOperation;
168
- return (
169
- editFieldOp.fieldType &&
170
- editFieldOp.fieldType.indexOf("text") !== -1
171
- );
172
- };
173
-
174
- const isEditTextField = isEditTextFieldOp(newOps[newOps.length - 1]);
175
-
176
- if (isEditTextField) lastOpIndex = response.editOperations.length - 1;
177
- else lastOpIndex = response.editOperations.length;
178
-
179
- newOps.forEach((op: EditOperation) => {
180
- if (isEditTextFieldOp(op)) {
181
- const editFieldOp = op as EditFieldOperation;
182
-
183
- if (editFieldOp.itemId) {
184
- const fieldDescriptor = {
185
- item: {
186
- ...editFieldOp.mainItem,
187
- id: editFieldOp.itemId,
188
- },
189
- fieldId: editFieldOp.fieldId,
190
- };
191
- editContext.itemsRepository.updateFieldValue(
192
- fieldDescriptor,
193
- editFieldOp.user ?? { name: "unknown", ai: false },
194
- false,
195
- editFieldOp.value,
196
- );
197
-
198
- editContext.select([editFieldOp.itemId]);
199
- editContext.setScrollIntoView(editFieldOp.itemId);
200
-
201
- editContext?.setFocusedField(fieldDescriptor, false);
202
- }
203
- } else {
204
- const addOp = op as AddComponentOperation;
205
- if (op.type === "add-component" && addOp.componentId) {
206
- const newComponentId = addOp.componentId;
207
- editContext.select([newComponentId]);
208
- editContext.setScrollIntoView(newComponentId);
209
- }
210
- }
211
- });
212
- }
236
+ // if (response.editOperations.length) {
237
+ // if (!editContext) return;
238
+
239
+ // const newOps = response.editOperations.slice(lastOpIndex);
240
+
241
+ // if (newOps.length === 0) return;
242
+
243
+ // const isEditTextFieldOp = (op: EditOperation) => {
244
+ // if (op.type !== "edit-field") return false;
245
+ // const editFieldOp = op as EditFieldOperation;
246
+ // return (
247
+ // editFieldOp.fieldType &&
248
+ // editFieldOp.fieldType.indexOf("text") !== -1
249
+ // );
250
+ // };
251
+
252
+ // const isEditTextField = isEditTextFieldOp(newOps[newOps.length - 1]);
253
+
254
+ // if (isEditTextField) lastOpIndex = response.editOperations.length - 1;
255
+ // else lastOpIndex = response.editOperations.length;
256
+
257
+ // newOps.forEach((op: EditOperation) => {
258
+ // if (isEditTextFieldOp(op)) {
259
+ // const editFieldOp = op as EditFieldOperation;
260
+
261
+ // if (editFieldOp.itemId) {
262
+ // const fieldDescriptor = {
263
+ // item: {
264
+ // ...editFieldOp.mainItem,
265
+ // id: editFieldOp.itemId,
266
+ // },
267
+ // fieldId: editFieldOp.fieldId,
268
+ // };
269
+ // editContext.itemsRepository.updateFieldValue(
270
+ // fieldDescriptor,
271
+ // editFieldOp.user ?? { name: "unknown", ai: false },
272
+ // false,
273
+ // editFieldOp.value,
274
+ // );
275
+
276
+ // editContext.select([editFieldOp.itemId]);
277
+ // editContext.setScrollIntoView(editFieldOp.itemId);
278
+
279
+ // editContext?.setFocusedField(fieldDescriptor, false);
280
+ // }
281
+ // } else {
282
+ // const addOp = op as AddComponentOperation;
283
+ // if (op.type === "add-component" && addOp.componentId) {
284
+ // const newComponentId = addOp.componentId;
285
+ // editContext.select([newComponentId]);
286
+ // editContext.setScrollIntoView(newComponentId);
287
+ // }
288
+ // }
289
+ // });
290
+ // }
213
291
  },
214
292
  );
215
293
 
@@ -219,15 +297,16 @@ export function AiTerminal({
219
297
  editContext?.requestRefresh("immediate");
220
298
  }
221
299
 
222
- if (response?.responseText)
223
- newMessages.push({
224
- content: response.responseText,
225
- role: "assistant",
226
- name: "assistant",
227
- });
300
+ // if (response?.responseText)
301
+ // newMessages.push({
302
+ // content: response.responseText,
303
+ // role: "assistant",
304
+ // name: "assistant",
305
+ // });
228
306
 
229
307
  setResponse(response ? response : undefined);
230
- setMessages(newMessages);
308
+
309
+ // setMessages(newMessages);
231
310
  }
232
311
 
233
312
  useEffect(() => {
@@ -424,34 +503,3 @@ async function executePrompt(
424
503
 
425
504
  return result;
426
505
  }
427
-
428
- function handleResponse(
429
- response: any,
430
- terminalCallback: (text: React.ReactNode, finished: boolean) => void,
431
- isFinished: boolean,
432
- ) {
433
- const toolcalls: [] =
434
- response?.toolCalls?.map((tool_call: any) => {
435
- return {
436
- func: tool_call.function?.name,
437
- arguments: tool_call.function?.arguments,
438
- result: tool_call.result,
439
- error: tool_call.error,
440
- };
441
- }) || [];
442
-
443
- const responseText = response.responseText
444
- ?.trim()
445
- .replace(/\n/g, "<br/>")
446
- ?.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>");
447
-
448
- terminalCallback(
449
- <AiResponseMessage
450
- responseText={responseText}
451
- toolcalls={toolcalls}
452
- editOperations={response.editOperations}
453
- finished={isFinished}
454
- />,
455
- isFinished,
456
- );
457
- }
@@ -1,36 +1,52 @@
1
- import { useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
 
3
3
  import { JsonView, defaultStyles } from "react-json-view-lite";
4
-
5
- export type ToolCall = {
6
- func: string;
7
- arguments: string;
8
- result: string;
9
- error?: string;
10
- };
11
-
12
- export function AiToolCall({ toolCall }: { toolCall: ToolCall }) {
4
+ import { Message, ToolCall } from "./AiTerminal";
5
+ import { ProgressSpinner } from "primereact/progressspinner";
6
+ export function AiToolCall({
7
+ toolCall,
8
+ result,
9
+ }: {
10
+ toolCall: ToolCall;
11
+ result: Message | undefined;
12
+ }) {
13
13
  const [expanded, setExpaded] = useState(false);
14
+ const [error, setError] = useState<string | undefined>(undefined);
15
+
16
+ useEffect(() => {
17
+ try {
18
+ const data = JSON.parse(result?.content || "{}");
19
+ setError(data.error);
20
+ } catch (err) {
21
+ console.error("Failed to parse tool call result:", err);
22
+ setError("Failed to parse response");
23
+ }
24
+ }, [result]);
14
25
 
15
26
  return (
16
- <div className="mt-2">
27
+ <div className="m-2">
17
28
  <div
18
29
  onClick={() => setExpaded(!expanded)}
19
- className="flex items-center cursor-pointer"
30
+ className="flex cursor-pointer items-center"
20
31
  >
21
- <i className="pi pi-angle-right" /> {toolCall.func}
32
+ <i className="pi pi-angle-right" />
33
+ <div className="flex items-center gap-2">
34
+ {!result && (
35
+ <ProgressSpinner style={{ width: "1rem", height: "1rem" }} />
36
+ )}
37
+ {toolCall.displayName}
38
+ </div>
22
39
  </div>
23
40
  {expanded && (
24
41
  <div className="ml-4">
25
42
  <div className="italic">Args:</div>
26
- <div>{renderJsonOrText(toolCall.arguments)}</div>
27
- <div className="italic mt-1">Result:</div>
28
- <div className="mb-4">
29
- {toolCall.error && (
30
- <div className="text-red">{toolCall.error}</div>
31
- )}
32
- {renderJsonOrText(toolCall.result)}
33
- </div>
43
+ <div>{renderJsonOrText(toolCall.function.arguments)}</div>
44
+ {error && (
45
+ <div className="ml-4 text-red-500">
46
+ <div className="italic">Error:</div>
47
+ <div>{error}</div>
48
+ </div>
49
+ )}
34
50
  </div>
35
51
  )}
36
52
  </div>