@alpaca-editor/core 1.0.4013 → 1.0.4015

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 (79) hide show
  1. package/dist/components/ui/popover.js +6 -3
  2. package/dist/components/ui/popover.js.map +1 -1
  3. package/dist/editor/Terminal.d.ts +6 -0
  4. package/dist/editor/Terminal.js +10 -4
  5. package/dist/editor/Terminal.js.map +1 -1
  6. package/dist/editor/ai/Agents.js +133 -13
  7. package/dist/editor/ai/Agents.js.map +1 -1
  8. package/dist/editor/ai/AiTerminal.d.ts +2 -1
  9. package/dist/editor/ai/AiTerminal.js +86 -139
  10. package/dist/editor/ai/AiTerminal.js.map +1 -1
  11. package/dist/editor/media-selector/AiImageSearch.js +2 -1
  12. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  13. package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -1
  14. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  15. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +4 -2
  16. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  17. package/dist/editor/services/agentService.d.ts +59 -0
  18. package/dist/editor/services/agentService.js +26 -0
  19. package/dist/editor/services/agentService.js.map +1 -0
  20. package/dist/editor/services/aiService.d.ts +22 -4
  21. package/dist/editor/services/aiService.js +131 -20
  22. package/dist/editor/services/aiService.js.map +1 -1
  23. package/dist/editor/services/contextService.js +6 -4
  24. package/dist/editor/services/contextService.js.map +1 -1
  25. package/dist/editor/sidebar/SidebarView.js +2 -5
  26. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  27. package/dist/editor/sidebar/ViewSelector.js +1 -1
  28. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  29. package/dist/editor/ui/SimpleIconButton.d.ts +2 -2
  30. package/dist/editor/ui/SimpleIconButton.js +5 -3
  31. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  32. package/dist/editor/utils/jsonCleaner.d.ts +8 -0
  33. package/dist/editor/utils/jsonCleaner.js +76 -0
  34. package/dist/editor/utils/jsonCleaner.js.map +1 -0
  35. package/dist/editor/views/CompareView.js +2 -2
  36. package/dist/editor/views/CompareView.js.map +1 -1
  37. package/dist/page-wizard/steps/ContentStep.js +7 -2
  38. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  39. package/dist/page-wizard/steps/FindItemsStep.js +4 -1
  40. package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
  41. package/dist/page-wizard/steps/ImagesStep.js +13 -7
  42. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  43. package/dist/page-wizard/steps/LayoutStep.d.ts +1 -1
  44. package/dist/page-wizard/steps/LayoutStep.js +22 -20
  45. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  46. package/dist/page-wizard/steps/MetaDataStep.js +8 -15
  47. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  48. package/dist/page-wizard/steps/SelectStep.js +9 -4
  49. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  50. package/dist/page-wizard/steps/StructureStep.js +3 -1
  51. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  52. package/dist/revision.d.ts +2 -2
  53. package/dist/revision.js +2 -2
  54. package/dist/styles.css +10 -11
  55. package/package.json +1 -1
  56. package/src/components/ui/popover.tsx +6 -3
  57. package/src/editor/Terminal.tsx +12 -3
  58. package/src/editor/ai/Agents.tsx +212 -16
  59. package/src/editor/ai/AiTerminal.tsx +113 -173
  60. package/src/editor/ai/GhostWriter.tsx_ +3 -3
  61. package/src/editor/media-selector/AiImageSearch.tsx +2 -2
  62. package/src/editor/media-selector/AiImageSearchPrompt.tsx +2 -2
  63. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +5 -5
  64. package/src/editor/services/agentService.ts +83 -0
  65. package/src/editor/services/aiService.ts +176 -33
  66. package/src/editor/services/contextService.ts +5 -6
  67. package/src/editor/sidebar/SidebarView.tsx +10 -6
  68. package/src/editor/sidebar/ViewSelector.tsx +2 -2
  69. package/src/editor/ui/SimpleIconButton.tsx +20 -14
  70. package/src/editor/utils/jsonCleaner.ts +92 -0
  71. package/src/editor/views/CompareView.tsx +2 -2
  72. package/src/page-wizard/steps/ContentStep.tsx +10 -9
  73. package/src/page-wizard/steps/FindItemsStep.tsx +7 -5
  74. package/src/page-wizard/steps/ImagesStep.tsx +16 -11
  75. package/src/page-wizard/steps/LayoutStep.tsx +24 -28
  76. package/src/page-wizard/steps/MetaDataStep.tsx +11 -21
  77. package/src/page-wizard/steps/SelectStep.tsx +11 -8
  78. package/src/page-wizard/steps/StructureStep.tsx +4 -5
  79. package/src/revision.ts +2 -2
@@ -1,6 +1,7 @@
1
1
  import { AiContext } from "../ai/AiTerminal";
2
2
  import { EditContextType } from "../client/editContext";
3
3
  import { FieldDescriptor, ItemDescriptor } from "../pageModel";
4
+ import { JsonCleaner } from "../utils/jsonCleaner";
4
5
 
5
6
  import { ExecutionResult, get, post } from "./serviceHelper";
6
7
 
@@ -32,6 +33,9 @@ type Message = {
32
33
  content: string;
33
34
  name: string;
34
35
  role: string;
36
+ id: string; // Changed from number to string to support UUIDs
37
+ tool_calls?: any[]; // Optional tool_calls for compatibility
38
+ tool_call_id?: string; // Optional tool_call_id for compatibility
35
39
  };
36
40
 
37
41
  export interface ExecutePromptResponse {
@@ -41,49 +45,117 @@ export interface ExecutePromptResponse {
41
45
  numOutputTokens: number;
42
46
  numCachedTokens: number;
43
47
  state: string;
48
+ // New properties for incremental updates
49
+ isIncremental?: boolean;
50
+ deltaContent?: string;
51
+ previousContentLength?: number;
52
+ totalContentLength?: number;
44
53
  }
45
54
 
55
+ // Unified options interface that supports both calling patterns
56
+ export interface ExecutePromptOptions {
57
+ // Common options
58
+ sessionId?: string | null;
59
+ model?: string | null;
60
+
61
+ // AiTerminal style options
62
+ profileId?: string;
63
+ selection?: string[];
64
+ selectedText?: string | null;
65
+ agentId?: string;
66
+ addSelectedComponents?: boolean;
67
+
68
+ // Other callers style options
69
+ allowedFunctions?: string[];
70
+ addContextContent?: boolean;
71
+ addAllContent?: boolean;
72
+ profile?: string;
73
+ }
74
+
75
+ // Unified function signature
46
76
  export async function executePrompt(
47
77
  messages: Message[],
48
- editContext: EditContextType,
49
- createAiContext: ({ editContext }: { editContext: any }) => AiContext,
50
- promptOptions: {
51
- allowedFunctions?: string[];
52
- addContextContent?: boolean;
53
- addAllContent?: boolean;
54
- profile?: string;
55
- },
56
-
57
- options?: RequestInit,
58
- model?: string,
78
+ context:
79
+ | AiContext
80
+ | {
81
+ editContext: EditContextType;
82
+ createAiContext: ({ editContext }: { editContext: any }) => AiContext;
83
+ },
84
+ options: ExecutePromptOptions = {},
85
+ requestOptions?: RequestInit,
59
86
  callback?: (response: any) => void,
60
87
  ): Promise<ExecutePromptResponse | null> {
61
- const context = createAiContext({ editContext });
88
+ let endpoint: string;
89
+ let requestBody: any;
90
+ let finalRequestOptions: RequestInit;
62
91
 
63
- const response = await fetch(context.endpoint, {
64
- method: "POST",
65
- body: JSON.stringify({
66
- messages,
67
- ...context.promptData,
68
- sessionId: editContext.sessionId,
69
- ...promptOptions,
70
- model,
71
- }),
92
+ // Handle context - either direct AiContext or need to create it
93
+ let aiContext: AiContext;
94
+ if ("endpoint" in context) {
95
+ // Direct AiContext (AiTerminal style)
96
+ aiContext = context;
97
+ } else {
98
+ // Create context (other callers style)
99
+ aiContext = context.createAiContext({ editContext: context.editContext });
100
+ // Default sessionId from editContext if not provided
101
+ if (options.sessionId === undefined) {
102
+ options.sessionId = context.editContext.sessionId;
103
+ }
104
+ }
105
+
106
+ endpoint = aiContext.endpoint;
72
107
 
108
+ // Build unified request body
109
+ requestBody = {
110
+ messages,
111
+ ...aiContext.promptData,
112
+ sessionId: options.sessionId,
113
+ model: options.model,
114
+
115
+ // Include all provided options
116
+ ...options,
117
+ };
118
+
119
+ finalRequestOptions = {
120
+ method: "POST",
121
+ body: JSON.stringify(requestBody),
73
122
  credentials: "include",
74
123
  headers: {
75
124
  "Content-Type": "application/json",
76
125
  },
77
- ...options,
78
- });
126
+ ...requestOptions,
127
+ };
128
+
129
+ const response = await fetch(endpoint, finalRequestOptions);
130
+
131
+ if (!response.ok) {
132
+ // Always return rich error format
133
+ const text = await response.text();
134
+ return {
135
+ messages: [
136
+ {
137
+ content: "There was an error processing your request: " + text,
138
+ name: "assistant",
139
+ role: "assistant",
140
+ id: crypto.randomUUID(),
141
+ tool_calls: [],
142
+ },
143
+ ],
144
+ editOperations: [],
145
+ numInputTokens: 0,
146
+ numOutputTokens: 0,
147
+ numCachedTokens: 0,
148
+ state: "error",
149
+ };
150
+ }
79
151
 
80
152
  if (!response?.body) return null;
81
153
 
82
154
  const reader = response.body.getReader();
83
155
  const decoder = new TextDecoder();
84
156
  let buffer = "";
85
-
86
157
  let result = null;
158
+ let accumulatedContent = "";
87
159
 
88
160
  while (true) {
89
161
  const { done, value } = await reader.read();
@@ -92,21 +164,58 @@ export async function executePrompt(
92
164
  break;
93
165
  }
94
166
 
95
- buffer += decoder.decode(value, { stream: true }); // 'stream: true' ensures that any incomplete multi-byte characters aren't malformed.
96
-
97
- // Split the buffer by newline and keep the last partial line for the next iteration.
167
+ buffer += decoder.decode(value, { stream: true });
98
168
  const lines = buffer.split("\n");
99
169
 
100
170
  if (lines.length > 0) {
101
- buffer = lines.pop() || ""; // Incomplete line (if any) is kept for the next iteration.
171
+ buffer = lines.pop() || "";
102
172
 
103
173
  for (let line of lines) {
104
- if (line.trim() === "") continue; // Skip empty lines if any.
174
+ if (line.trim() === "") continue;
105
175
 
106
176
  try {
107
177
  const jsonData = JSON.parse(line);
108
- callback?.(jsonData);
109
- result = jsonData;
178
+
179
+ if (jsonData.isIncremental) {
180
+ if (jsonData.deltaContent) {
181
+ accumulatedContent += jsonData.deltaContent;
182
+ }
183
+
184
+ // Always apply JsonCleaner for streaming responses
185
+ const cleanupResult = JsonCleaner.cleanupJson(accumulatedContent);
186
+
187
+ const incrementalResponse = {
188
+ ...jsonData,
189
+ messages: [
190
+ {
191
+ content: cleanupResult.cleanedJson,
192
+ role: "assistant",
193
+ name: "assistant",
194
+ id: Date.now(),
195
+ tool_calls: [],
196
+ },
197
+ ],
198
+ };
199
+
200
+ callback?.(incrementalResponse);
201
+ result = incrementalResponse;
202
+ } else {
203
+ // Always ensure messages have IDs and tool_calls
204
+ let processedResponse = jsonData;
205
+ if (jsonData.messages) {
206
+ processedResponse = {
207
+ ...jsonData,
208
+ messages: jsonData.messages.map((msg: any, index: number) => ({
209
+ ...msg,
210
+ id: msg.id || Date.now() + index,
211
+ tool_calls: msg.tool_calls || [],
212
+ })),
213
+ };
214
+ }
215
+
216
+ callback?.(processedResponse);
217
+ result = processedResponse;
218
+ }
110
219
  } catch (e) {
111
220
  console.error("Error parsing line:" + line, e);
112
221
  }
@@ -114,11 +223,45 @@ export async function executePrompt(
114
223
  }
115
224
  }
116
225
 
117
- // If there's any remaining content in the buffer after processing all chunks, try to process it.
226
+ // Handle final buffer
118
227
  if (buffer.trim() !== "") {
119
228
  try {
120
229
  const jsonData = JSON.parse(buffer);
121
- result = jsonData;
230
+
231
+ if (jsonData.isIncremental) {
232
+ if (jsonData.deltaContent) {
233
+ accumulatedContent += jsonData.deltaContent;
234
+ }
235
+
236
+ const cleanupResult = JsonCleaner.cleanupJson(accumulatedContent);
237
+
238
+ result = {
239
+ ...jsonData,
240
+ messages: [
241
+ {
242
+ content: cleanupResult.cleanedJson,
243
+ role: "assistant",
244
+ name: "assistant",
245
+ id: Date.now(),
246
+ tool_calls: [],
247
+ },
248
+ ],
249
+ };
250
+ } else {
251
+ // Always ensure messages have IDs
252
+ if (jsonData.messages) {
253
+ result = {
254
+ ...jsonData,
255
+ messages: jsonData.messages.map((msg: any, index: number) => ({
256
+ ...msg,
257
+ id: msg.id || Date.now() + index,
258
+ tool_calls: msg.tool_calls || [],
259
+ })),
260
+ };
261
+ } else {
262
+ result = jsonData;
263
+ }
264
+ }
122
265
  } catch (e) {
123
266
  console.error("Error parsing the final buffer content:", e);
124
267
  }
@@ -51,28 +51,27 @@ export async function generatePageContext(
51
51
  role: "system",
52
52
  content:
53
53
  "You are a content summarization tool. You have access to a get-content function that can extract text content from the current page. Use this function to get the page content, then create a concise abstract in exactly 100 tokens or less. Focus on the main topics, purpose, and key information. Be specific and informative. Your summary will be fed into a text completion prompt, so make it as concise as possible.",
54
+ id: crypto.randomUUID(), // Use proper UUID instead of Date.now()
54
55
  },
55
56
  ];
56
57
 
57
58
  const result = await executePrompt(
58
59
  messages,
59
- editContext,
60
- () => ({
60
+ {
61
61
  endpoint: "/alpaca/editor/ai/prompt",
62
62
  promptData: {
63
63
  itemid: editContext.contentEditorItem!.id,
64
64
  language: editContext.contentEditorItem!.language,
65
65
  version: editContext.contentEditorItem!.version,
66
66
  },
67
- allowedFunctions: ["get-content"],
68
- }),
67
+ },
69
68
  {
70
69
  profile: "context-generation",
71
70
  addContextContent: false,
72
71
  addAllContent: false,
72
+ model: "gpt-4o-mini", // Use a fast, efficient model for context generation
73
+ allowedFunctions: ["get-content"],
73
74
  },
74
- undefined,
75
- "gpt-4o-mini", // Use a fast, efficient model for context generation
76
75
  );
77
76
 
78
77
  console.log("result", result);
@@ -52,10 +52,6 @@ export function SidebarView({
52
52
  );
53
53
  };
54
54
 
55
- if (!active) {
56
- return <div className="hidden h-full" />;
57
- }
58
-
59
55
  // Single panel - no splitter needed
60
56
  if (resolvedPanels.length === 1) {
61
57
  const panel = resolvedPanels[0];
@@ -63,7 +59,11 @@ export function SidebarView({
63
59
 
64
60
  return (
65
61
  <div
66
- className={cn("h-full", detached ? "p-2" : "border-gray-3 border-r")}
62
+ className={cn(
63
+ "h-full",
64
+ detached ? "p-2" : "border-gray-3 border-r",
65
+ !active ? "hidden" : ""
66
+ )}
67
67
  >
68
68
  <div
69
69
  className={cn(
@@ -111,7 +111,11 @@ export function SidebarView({
111
111
  );
112
112
 
113
113
  return (
114
- <div className={cn("h-full", detached ? "p-2" : "")}>
114
+ <div className={cn(
115
+ "h-full",
116
+ detached ? "p-2" : "",
117
+ !active ? "hidden" : ""
118
+ )}>
115
119
  <Splitter
116
120
  panels={splitterPanels}
117
121
  direction="vertical"
@@ -140,8 +140,8 @@ export function ViewSelector() {
140
140
  <VerticalDotsIcon />
141
141
  </Button>
142
142
  </PopoverTrigger>
143
- </TooltipTrigger>
144
- <TooltipContent>More views</TooltipContent>
143
+ </TooltipTrigger >
144
+ <TooltipContent side="right">More views</TooltipContent>
145
145
  </Tooltip>
146
146
  <PopoverContent
147
147
  className="w-56 p-2"
@@ -5,9 +5,22 @@ import {
5
5
  TooltipTrigger,
6
6
  } from "../../components/ui/tooltip";
7
7
  import { cn } from "../../lib/utils";
8
- import { MouseEventHandler } from "react";
8
+ import { MouseEventHandler, forwardRef } from "react";
9
9
 
10
- export function SimpleIconButton({
10
+ export const SimpleIconButton = forwardRef<
11
+ HTMLButtonElement,
12
+ {
13
+ onClick: MouseEventHandler;
14
+ className?: string;
15
+ icon?: React.ReactNode;
16
+ label: string;
17
+ disabled?: boolean;
18
+ id?: string;
19
+ size?: "large" | "small";
20
+ selected?: boolean;
21
+ children?: React.ReactNode;
22
+ }
23
+ >(({
11
24
  onClick,
12
25
  className,
13
26
  icon,
@@ -17,21 +30,12 @@ export function SimpleIconButton({
17
30
  id,
18
31
  selected,
19
32
  children,
20
- }: {
21
- onClick: MouseEventHandler;
22
- className?: string;
23
- icon?: React.ReactNode;
24
- label: string;
25
- disabled?: boolean;
26
- id?: string;
27
- size?: "large" | "small";
28
- selected?: boolean;
29
- children?: React.ReactNode;
30
- }) {
33
+ }, ref) => {
31
34
  return (
32
35
  <Tooltip>
33
36
  <TooltipTrigger asChild>
34
37
  <button
38
+ ref={ref}
35
39
  id={id}
36
40
  disabled={disabled}
37
41
  className={cn(
@@ -54,4 +58,6 @@ export function SimpleIconButton({
54
58
  <TooltipContent>{label}</TooltipContent>
55
59
  </Tooltip>
56
60
  );
57
- }
61
+ });
62
+
63
+ SimpleIconButton.displayName = "SimpleIconButton";
@@ -0,0 +1,92 @@
1
+ export interface JsonCleanupResult {
2
+ cleanedJson: string;
3
+ isComplete: boolean;
4
+ }
5
+
6
+ export class JsonCleaner {
7
+ public static cleanupJson(json: string): JsonCleanupResult {
8
+ // Handle dangling commas
9
+ json = json.replace(/,}/g, "}");
10
+ json = json.replace(/,]/g, "]");
11
+
12
+ const stringResult = this.correctUnendedStrings(json);
13
+ json = stringResult.correctedJson;
14
+ let isComplete = stringResult.isComplete;
15
+
16
+ // Process the JSON string character by character
17
+ let isInString = false;
18
+ let isEscape = false;
19
+ const openStack: string[] = [];
20
+
21
+ for (let i = 0; i < json.length; i++) {
22
+ const c = json[i];
23
+
24
+ if (c === '"' && !isEscape) {
25
+ isInString = !isInString;
26
+ }
27
+
28
+ if (!isInString) {
29
+ switch (c) {
30
+ case '{':
31
+ openStack.push(c);
32
+ break;
33
+ case '}':
34
+ if (openStack.length > 0) {
35
+ openStack.pop();
36
+ }
37
+ break;
38
+ case '[':
39
+ openStack.push(c);
40
+ break;
41
+ case ']':
42
+ if (openStack.length > 0) {
43
+ openStack.pop();
44
+ }
45
+ break;
46
+ }
47
+ }
48
+
49
+ isEscape = isInString && c === '\\' && !isEscape;
50
+ }
51
+
52
+ // Append missing closing brackets
53
+ while (openStack.length > 0) {
54
+ const open = openStack.pop();
55
+ if (open === '{') {
56
+ json += "}";
57
+ } else if (open === '[') {
58
+ json += "]";
59
+ }
60
+ isComplete = false;
61
+ }
62
+
63
+ return {
64
+ cleanedJson: json,
65
+ isComplete: isComplete
66
+ };
67
+ }
68
+
69
+ private static correctUnendedStrings(json: string): { correctedJson: string; isComplete: boolean } {
70
+ let isComplete = true;
71
+ let isInString = false;
72
+ let isEscape = false;
73
+
74
+ for (let i = 0; i < json.length; i++) {
75
+ if (json[i] === '"' && !isEscape) {
76
+ isInString = !isInString;
77
+ }
78
+
79
+ isEscape = isInString && json[i] === '\\' && !isEscape;
80
+ }
81
+
82
+ if (isInString) {
83
+ json += '[...]"';
84
+ isComplete = false;
85
+ }
86
+
87
+ return {
88
+ correctedJson: json,
89
+ isComplete: isComplete
90
+ };
91
+ }
92
+ }
@@ -115,7 +115,7 @@ export function CompareView() {
115
115
  }
116
116
 
117
117
  const originalSelector = (
118
- <div className="flex flex-1 flex-shrink-0 items-center justify-center border-b bg-white p-1 text-sm text-gray-600">
118
+ <div className=" gap-1 flex flex-1 flex-shrink-0 items-center justify-center border-b bg-white p-1 text-sm text-gray-600">
119
119
  <LanguageSelector
120
120
  selectedLanguage={editContext.contentEditorItem?.descriptor?.language}
121
121
  showAllLanguagesSwitch={false}
@@ -149,7 +149,7 @@ export function CompareView() {
149
149
  );
150
150
 
151
151
  const compareToSelector = (
152
- <div className="flex flex-1 flex-shrink-0 items-center justify-center border-b bg-white p-1 text-sm text-gray-600">
152
+ <div className="gap-1 flex flex-1 flex-shrink-0 items-center justify-center border-b bg-white p-1 text-sm text-gray-600">
153
153
  <LanguageSelector
154
154
  selectedLanguage={compareTo?.language}
155
155
  showAllLanguagesSwitch={false}
@@ -333,13 +333,12 @@ export function ContentStep({
333
333
  Input data: ${JSON.stringify(data)}`,
334
334
  name: "system",
335
335
  role: "system",
336
+ id: crypto.randomUUID(), // Use proper UUID instead of Date.now()
336
337
  },
337
338
  ],
338
- editContext,
339
- createWizardAiContext,
340
- {},
339
+ { editContext, createAiContext: createWizardAiContext },
340
+ { model: "o4-mini-high" },
341
341
  { signal: abortController.signal },
342
- "o4-mini-high",
343
342
  (response) => {
344
343
  try {
345
344
  const newLayout = JSON.parse(response.content) as WizardPageModel;
@@ -592,6 +591,7 @@ export function ContentStep({
592
591
 
593
592
  name: "system",
594
593
  role: "system",
594
+ id: crypto.randomUUID(), // Add required id property
595
595
  },
596
596
  ];
597
597
 
@@ -603,12 +603,13 @@ export function ContentStep({
603
603
 
604
604
  const result = await executePrompt(
605
605
  prompt,
606
- editContextRef.current!,
607
- createWizardAiContext,
608
- { allowedFunctions: [] },
606
+ {
607
+ editContext: editContextRef.current!,
608
+ createAiContext: createWizardAiContext,
609
+ },
610
+ { allowedFunctions: [], model: step.fields.aiModel || "gpt-4.1" },
609
611
  { signal: localAbortController.signal },
610
- step.fields.aiModel || "gpt-4.1",
611
- (response) => {
612
+ (response: any) => {
612
613
  try {
613
614
  // Handle streaming response - might have different structure during streaming
614
615
  const content =
@@ -216,24 +216,26 @@ Return a JSON object with suggested item IDs in this format:
216
216
  );
217
217
 
218
218
  try {
219
+ const localAbortController = new AbortController();
220
+
219
221
  const result = await executePrompt(
220
222
  [
221
223
  {
222
224
  content: `You are an expert content curator helping to find relevant items from a content tree. You can traverse the tree using the get-children function. Return ONLY a valid JSON object with suggestions and reasoning.`,
223
225
  name: "system",
224
226
  role: "system",
227
+ id: crypto.randomUUID(), // Use proper UUID instead of Date.now()
225
228
  },
226
229
  {
227
230
  content: basePrompt,
228
231
  name: "user",
229
232
  role: "user",
233
+ id: crypto.randomUUID(), // Use proper UUID instead of Date.now()
230
234
  },
231
235
  ],
232
- editContext,
233
- createWizardAiContext,
234
- { allowedFunctions: ["get-children"] },
235
- undefined,
236
- step.fields.aiModel || "gpt-4.1",
236
+ { editContext, createAiContext: createWizardAiContext },
237
+ { model: step.fields.aiModel || "gpt-4.1" },
238
+ { signal: localAbortController.signal },
237
239
  );
238
240
 
239
241
  console.log("AI RESULT: ", result);
@@ -98,25 +98,30 @@ export const ImagesStep = (props: StepComponentProps) => {
98
98
  }));
99
99
 
100
100
  try {
101
- // First, ask AI for keywords based on the content
101
+ const localAbortController = new AbortController();
102
+ const imageInstructions =
103
+ props.step.fields.instructions ||
104
+ "Please analyze the provided data and suggest images.";
105
+
102
106
  const aiPromptResult = await executePrompt(
103
107
  [
104
108
  {
105
- content: `You are an AI assistant for building a web page. You will later have to create images for the page.
106
- Please anaylze the provided data and suggest a number of images and specific keywords per image. The keywords will be used to find matching images in the image library.
107
- Response JSON format: { images: Image[] } type Image = { title: string, keywords: string[] }
108
- Input data: ${JSON.stringify(props.data)}
109
- Instructions: ${props.step.fields.instructions}`,
109
+ content: `You are an AI assistant for building a web page. You will later have to create images for the page.
110
+ ${imageInstructions}
111
+
112
+ Reply with a json object of type WizardPageModel = { message: string; components: Component[]; };
110
113
 
114
+ Where components are of type Component = { id: string; name: string; imageFields: {name: string; value: string; alt: string; prompt: string; }[]; };
115
+ You should update images existing images in fields in the components array. Update the prompt field for each image, keep name, value and alt unchanged. Make sure you populate prompt field for every image.
116
+ `,
111
117
  name: "system",
112
118
  role: "system",
119
+ id: crypto.randomUUID(), // Use proper UUID instead of Date.now()
113
120
  },
114
121
  ],
115
- editContext!,
116
- createWizardAiContext,
117
- {},
118
- undefined,
119
- "gpt-4.1",
122
+ { editContext: editContext!, createAiContext: createWizardAiContext },
123
+ { model: "gpt-4.1" },
124
+ { signal: localAbortController.signal },
120
125
  );
121
126
 
122
127
  let images: Image[] = [];