@alpaca-editor/core 1.0.4174 → 1.0.4177

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 (58) hide show
  1. package/dist/agents-view/AgentsView.js +0 -20
  2. package/dist/agents-view/AgentsView.js.map +1 -1
  3. package/dist/editor/QuickItemSwitcher.js +5 -1
  4. package/dist/editor/QuickItemSwitcher.js.map +1 -1
  5. package/dist/editor/ai/AgentTerminal.js +47 -83
  6. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  7. package/dist/editor/ai/Agents.js +7 -5
  8. package/dist/editor/ai/Agents.js.map +1 -1
  9. package/dist/editor/ai/AiResponseMessage.js +1 -1
  10. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  11. package/dist/editor/ai/ContextInfoBar.js +30 -64
  12. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  13. package/dist/editor/ai/useAgentStatus.js +81 -2
  14. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  15. package/dist/editor/client/EditorShell.js +44 -7
  16. package/dist/editor/client/EditorShell.js.map +1 -1
  17. package/dist/editor/client/operations.js +30 -3
  18. package/dist/editor/client/operations.js.map +1 -1
  19. package/dist/editor/control-center/WebSocketMessages.js +0 -1
  20. package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
  21. package/dist/editor/page-viewer/PageViewerFrame.js +14 -3
  22. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  23. package/dist/editor/reviews/commentAi.js +43 -32
  24. package/dist/editor/reviews/commentAi.js.map +1 -1
  25. package/dist/editor/services/agentService.d.ts +8 -4
  26. package/dist/editor/services/agentService.js +30 -53
  27. package/dist/editor/services/agentService.js.map +1 -1
  28. package/dist/editor/services/aiService.d.ts +19 -1
  29. package/dist/editor/services/aiService.js +78 -2
  30. package/dist/editor/services/aiService.js.map +1 -1
  31. package/dist/page-wizard/steps/ContentStep.js +35 -57
  32. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  33. package/dist/page-wizard/steps/MetaDataStep.js +14 -23
  34. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  35. package/dist/page-wizard/steps/SelectStep.js +12 -42
  36. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  37. package/dist/revision.d.ts +2 -2
  38. package/dist/revision.js +2 -2
  39. package/dist/styles.css +0 -8
  40. package/package.json +1 -1
  41. package/src/agents-view/AgentsView.tsx +1 -21
  42. package/src/editor/QuickItemSwitcher.tsx +23 -19
  43. package/src/editor/ai/AgentTerminal.tsx +83 -142
  44. package/src/editor/ai/Agents.tsx +9 -6
  45. package/src/editor/ai/AiResponseMessage.tsx +5 -1
  46. package/src/editor/ai/ContextInfoBar.tsx +34 -75
  47. package/src/editor/ai/useAgentStatus.ts +82 -4
  48. package/src/editor/client/EditorShell.tsx +62 -9
  49. package/src/editor/client/operations.ts +42 -4
  50. package/src/editor/control-center/WebSocketMessages.tsx +5 -1
  51. package/src/editor/page-viewer/PageViewerFrame.tsx +15 -2
  52. package/src/editor/reviews/commentAi.ts +48 -36
  53. package/src/editor/services/agentService.ts +38 -55
  54. package/src/editor/services/aiService.ts +110 -3
  55. package/src/page-wizard/steps/ContentStep.tsx +51 -81
  56. package/src/page-wizard/steps/MetaDataStep.tsx +52 -60
  57. package/src/page-wizard/steps/SelectStep.tsx +13 -51
  58. package/src/revision.ts +2 -2
@@ -169,12 +169,16 @@ export type AgentContextData = {
169
169
  name?: string;
170
170
  };
171
171
  }>;
172
- /** @deprecated Use components instead to get page information per component */
173
- componentIds?: string[];
174
172
  field?: {
175
173
  fieldId: string;
176
- itemId: string;
177
- name?: string;
174
+ fieldName?: string;
175
+ item?: {
176
+ id: string;
177
+ language: string;
178
+ version: number;
179
+ path?: string;
180
+ name?: string;
181
+ };
178
182
  };
179
183
  comment?: {
180
184
  id: string;
@@ -842,71 +846,63 @@ export function canonicalizeAgentMetadata(
842
846
  }
843
847
  }
844
848
 
845
- // Process pages (legacy) and items
849
+ // Process items
846
850
  const allPages: any[] = [];
847
- if (Array.isArray(m.pages)) allPages.push(...m.pages); // legacy
848
851
  if (Array.isArray(m.items)) allPages.push(...m.items);
849
852
  for (const ctx of contextSources) {
850
- if (Array.isArray(ctx.pages)) allPages.push(...ctx.pages); // legacy
851
853
  if (Array.isArray(ctx.items)) allPages.push(...ctx.items);
852
854
  }
853
855
  if (allPages.length > 0) {
854
856
  const dedup: any[] = [];
855
857
  const seen = new Set<string>();
856
858
  for (const p of allPages) {
857
- const id = p?.id ?? p?.Id;
859
+ const id = p?.id;
858
860
  if (!id) continue;
859
- const lang = (p?.language ?? p?.Language ?? "").toString();
860
- const ver = (p?.version ?? p?.Version ?? "").toString();
861
+ const lang = (p?.language ?? "").toString();
862
+ const ver = (p?.version ?? "").toString();
861
863
  const key = `${String(id)}-${lang}-${ver}`.toLowerCase();
862
864
  if (seen.has(key)) continue;
863
865
  seen.add(key);
864
866
  dedup.push({
865
867
  id: String(id),
866
- language: p?.language ?? p?.Language,
867
- version: p?.version ?? p?.Version,
868
- name: p?.name ?? p?.Name,
869
- path: p?.path ?? p?.Path,
868
+ language: p?.language,
869
+ version: p?.version,
870
+ name: p?.name,
871
+ path: p?.path,
870
872
  });
871
873
  }
872
874
  m.items = dedup;
873
875
  } else {
874
- // Clear both if no items
875
876
  delete m.items;
876
877
  }
877
878
 
878
- // Delete legacy pages property to avoid sending both
879
- delete m.pages;
880
-
881
- // Process components with page info (new structure)
879
+ // Process components with page info
882
880
  const allComponents: any[] = [];
883
881
  if (Array.isArray(m.components)) allComponents.push(...m.components);
884
- if (Array.isArray(m.Components)) allComponents.push(...m.Components);
885
882
  for (const ctx of contextSources) {
886
883
  if (Array.isArray(ctx.components)) allComponents.push(...ctx.components);
887
- if (Array.isArray(ctx.Components)) allComponents.push(...ctx.Components);
888
884
  }
889
885
  if (allComponents.length > 0) {
890
886
  const dedup: any[] = [];
891
887
  const seen = new Set<string>();
892
888
  for (const c of allComponents) {
893
889
  if (!c) continue;
894
- const componentId = c.componentId ?? c.ComponentId ?? c.id ?? c.Id;
890
+ const componentId = c.componentId;
895
891
  if (!componentId || typeof componentId !== "string") continue;
896
892
  const key = componentId.toLowerCase();
897
893
  if (seen.has(key)) continue;
898
894
  seen.add(key);
899
895
 
900
- const pageItem = c.pageItem ?? c.PageItem;
896
+ const pageItem = c.pageItem;
901
897
  dedup.push({
902
898
  componentId: String(componentId),
903
899
  pageItem: pageItem
904
900
  ? {
905
- id: String(pageItem.id ?? pageItem.Id ?? ""),
906
- language: pageItem.language ?? pageItem.Language,
907
- version: pageItem.version ?? pageItem.Version,
908
- name: pageItem.name ?? pageItem.Name,
909
- path: pageItem.path ?? pageItem.Path,
901
+ id: String(pageItem.id ?? ""),
902
+ language: pageItem.language,
903
+ version: pageItem.version,
904
+ name: pageItem.name,
905
+ path: pageItem.path,
910
906
  }
911
907
  : undefined,
912
908
  });
@@ -914,35 +910,25 @@ export function canonicalizeAgentMetadata(
914
910
  m.components = dedup;
915
911
  }
916
912
 
917
- // Process componentIds (legacy - for backward compatibility)
918
- const allComponentIds: any[] = [];
919
- if (Array.isArray(m.componentIds)) allComponentIds.push(...m.componentIds);
913
+ // Process field - merge/complete from context sources if needed
920
914
  for (const ctx of contextSources) {
921
- if (Array.isArray(ctx.componentIds))
922
- allComponentIds.push(...ctx.componentIds);
923
- }
924
- if (allComponentIds.length > 0) {
925
- const ids = allComponentIds
926
- .map((x) => (typeof x === "string" ? x : (x?.id ?? x?.Id)))
927
- .filter((x): x is string => !!x && typeof x === "string");
928
- m.componentIds = Array.from(new Set(ids));
929
- }
930
-
931
- // Process field
932
- if (!m.field) {
933
- for (const ctx of contextSources) {
934
- if (ctx.field) {
915
+ if (ctx.field) {
916
+ // If field doesn't exist, create it from context
917
+ if (!m.field) {
935
918
  m.field = {
936
- fieldId:
937
- ctx.field.fieldId ??
938
- ctx.field.FieldId ??
939
- ctx.field.id ??
940
- ctx.field.Id,
941
- itemId: ctx.field.itemId ?? ctx.field.ItemId,
942
- name: ctx.field.name ?? ctx.field.Name,
919
+ fieldId: ctx.field.fieldId,
920
+ fieldName: ctx.field.fieldName,
921
+ item: ctx.field.item,
943
922
  };
944
923
  break;
945
924
  }
925
+ // If field exists but is incomplete, fill in missing properties
926
+ if (m.field && !m.field.item && ctx.field.item) {
927
+ m.field.item = ctx.field.item;
928
+ }
929
+ if (m.field && !m.field.fieldName && ctx.field.fieldName) {
930
+ m.field.fieldName = ctx.field.fieldName;
931
+ }
946
932
  }
947
933
  }
948
934
 
@@ -967,9 +953,6 @@ export function canonicalizeAgentMetadata(
967
953
  }
968
954
 
969
955
  m.additionalData = Object.keys(additional).length ? additional : undefined;
970
- if (Object.prototype.hasOwnProperty.call(m, "AdditionalData")) {
971
- delete m.AdditionalData;
972
- }
973
956
 
974
957
  return m as AgentContextData;
975
958
  }
@@ -86,7 +86,8 @@ type Message = {
86
86
 
87
87
  export interface ExecutePromptResponse {
88
88
  editOperations: any[];
89
- messages: Message[];
89
+ content: string;
90
+ messages?: Message[];
90
91
  numInputTokens: number;
91
92
  numOutputTokens: number;
92
93
  numCachedTokens: number;
@@ -184,23 +185,25 @@ export async function executePrompt(
184
185
  };
185
186
 
186
187
  // Use migrated endpoint under agent controller
187
- const endpoint = options.endpoint || "/alpaca/editor/agent/prompt";
188
+ const endpoint = options.endpoint || "/alpaca/editor/page-wizard/prompt";
188
189
 
189
190
  const response = await fetch(endpoint, finalRequestOptions);
190
191
 
191
192
  if (!response.ok) {
192
193
  // Always return rich error format
193
194
  const text = await response.text();
195
+ const errorContent = "There was an error processing your request: " + text;
194
196
  return {
195
197
  messages: [
196
198
  {
197
- content: "There was an error processing your request: " + text,
199
+ content: errorContent,
198
200
  name: "assistant",
199
201
  role: "assistant",
200
202
  id: crypto.randomUUID(),
201
203
  tool_calls: [],
202
204
  },
203
205
  ],
206
+ content: errorContent,
204
207
  editOperations: [],
205
208
  numInputTokens: 0,
206
209
  numOutputTokens: 0,
@@ -330,6 +333,110 @@ export async function executePrompt(
330
333
  return result;
331
334
  }
332
335
 
336
+ /**
337
+ * Helper function to parse JSON content from AI response
338
+ * Strips markdown code blocks if present and parses the JSON
339
+ * Handles incomplete markdown blocks during streaming
340
+ */
341
+ function parseJsonContent<T>(content: string): T {
342
+ let cleanedContent = content;
343
+
344
+ // Strip markdown code blocks if present
345
+ if (cleanedContent.startsWith("```json")) {
346
+ // Remove the opening ```json
347
+ cleanedContent = cleanedContent.substring(7).trim();
348
+ // Remove the closing ``` if present
349
+ if (cleanedContent.endsWith("```")) {
350
+ cleanedContent = cleanedContent
351
+ .substring(0, cleanedContent.length - 3)
352
+ .trim();
353
+ }
354
+ } else if (cleanedContent.startsWith("```")) {
355
+ // Remove the opening ```
356
+ cleanedContent = cleanedContent.substring(3).trim();
357
+ // Remove the closing ``` if present
358
+ if (cleanedContent.endsWith("```")) {
359
+ cleanedContent = cleanedContent
360
+ .substring(0, cleanedContent.length - 3)
361
+ .trim();
362
+ }
363
+ }
364
+
365
+ // Replace JavaScript undefined with JSON null
366
+ // Pattern matches: undefined as a value (not inside strings)
367
+ cleanedContent = cleanedContent.replace(
368
+ /:\s*undefined\s*([,\}])/g,
369
+ ": null$1",
370
+ );
371
+
372
+ // Content is already cleaned by executePrompt for streaming responses
373
+ // and by backend for non-streaming responses, so just parse it
374
+ return JSON.parse(cleanedContent) as T;
375
+ }
376
+
377
+ /**
378
+ * Executes a prompt and returns the parsed JSON result
379
+ * @param messages - The messages to send
380
+ * @param context - The AI context
381
+ * @param options - Execution options
382
+ * @param requestOptions - Additional request options
383
+ * @param callback - Optional callback for streaming responses
384
+ * @param stream - Whether to stream the response
385
+ * @returns The parsed JSON object from the response content
386
+ * @throws Error if the response cannot be parsed or is not in the expected format
387
+ */
388
+ export async function executePromptWithJsonResult<T = any>(
389
+ messages: Message[],
390
+ context:
391
+ | AiContext
392
+ | {
393
+ editContext: EditContextType;
394
+ createAiContext: ({ editContext }: { editContext: any }) => AiContext;
395
+ },
396
+ options: ExecutePromptOptions = {},
397
+ requestOptions?: RequestInit,
398
+ callback?: (parsedJson: T) => void,
399
+ stream?: boolean,
400
+ ): Promise<T> {
401
+ // Wrap the callback to parse JSON before passing to the original callback
402
+ const wrappedCallback = callback
403
+ ? (response: any) => {
404
+ if (!response || !response.content) {
405
+ return;
406
+ }
407
+
408
+ try {
409
+ const parsed = parseJsonContent<T>(response.content);
410
+ callback(parsed);
411
+ } catch (parseError) {
412
+ // During streaming, JSON may still be incomplete even after cleaning
413
+ // Only invoke callback when we have valid parseable JSON
414
+ }
415
+ }
416
+ : undefined;
417
+
418
+ const result = await executePrompt(
419
+ messages,
420
+ context,
421
+ options,
422
+ requestOptions,
423
+ wrappedCallback,
424
+ stream,
425
+ );
426
+
427
+ if (!result || !result.content) {
428
+ throw new Error("No content in response");
429
+ }
430
+
431
+ try {
432
+ return parseJsonContent<T>(result.content);
433
+ } catch (parseError) {
434
+ throw new Error(
435
+ `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
436
+ );
437
+ }
438
+ }
439
+
333
440
  // moved to searchService.ts
334
441
 
335
442
  export async function generateImage(
@@ -21,7 +21,7 @@ import { convertPageSchemaToWizardComponents } from "./schema";
21
21
  import { WizardPageModel } from "../PageWizard";
22
22
  import { createWizardAiContext, wipeComponents } from "../service";
23
23
 
24
- import { executePrompt } from "../../editor/services/aiService";
24
+ import { executePromptWithJsonResult } from "../../editor/services/aiService";
25
25
  import { usePageCreator } from "./usePageCreator";
26
26
  import { useThrottledCallback } from "use-debounce";
27
27
  import { Textarea } from "../../components/ui/textarea";
@@ -152,7 +152,10 @@ export function ContentStep({
152
152
 
153
153
  console.log("PROMPT (selectTargetItem):", promptContent);
154
154
 
155
- const result = await executePrompt(
155
+ const result = await executePromptWithJsonResult<{
156
+ id: string;
157
+ path: string;
158
+ }>(
156
159
  [
157
160
  {
158
161
  content: promptContent,
@@ -169,16 +172,11 @@ export function ContentStep({
169
172
  { signal: abortController.signal },
170
173
  (response) => {
171
174
  try {
172
- const folderResult = JSON.parse(response.content) as {
173
- id: string;
174
- path: string;
175
- };
176
-
177
- if (folderResult.id && folderResult.path) {
178
- setFolderSelectionResult(folderResult);
175
+ if (response.id && response.path) {
176
+ setFolderSelectionResult(response);
179
177
  // Update target folder with the selected folder
180
178
  setTargetFolder({
181
- id: folderResult.id,
179
+ id: response.id,
182
180
  language: language,
183
181
  } as ItemDescriptor);
184
182
  }
@@ -186,23 +184,13 @@ export function ContentStep({
186
184
  },
187
185
  );
188
186
 
189
- if (result && result.messages && result.messages.length > 0) {
190
- const lastMessage = result.messages[result.messages.length - 1];
191
- if (lastMessage && lastMessage.content) {
192
- const folderResult = JSON.parse(lastMessage.content) as {
193
- id: string;
194
- path: string;
195
- };
196
-
197
- if (folderResult.id && folderResult.path) {
198
- setFolderSelectionResult(folderResult);
199
- // Update target folder with the selected folder
200
- setTargetFolder({
201
- id: folderResult.id,
202
- language: language,
203
- } as ItemDescriptor);
204
- }
205
- }
187
+ if (result && result.id && result.path) {
188
+ setFolderSelectionResult(result);
189
+ // Update target folder with the selected folder
190
+ setTargetFolder({
191
+ id: result.id,
192
+ language: language,
193
+ } as ItemDescriptor);
206
194
  }
207
195
  } catch (error) {
208
196
  console.error("Error selecting target folder", error);
@@ -288,38 +276,30 @@ export function ContentStep({
288
276
 
289
277
  console.log("PROMPT (generatePageName):", promptContent);
290
278
 
291
- const result = await executePrompt(
292
- [
293
- {
294
- content: promptContent,
295
- name: "system",
296
- role: "system",
297
- id: crypto.randomUUID(),
298
- },
299
- ],
300
- { editContext, createAiContext: createWizardAiContext },
301
- { model: step.fields.aiModel || undefined },
302
- { signal: abortController.signal },
303
- (response) => {
304
- try {
305
- const newLayout = JSON.parse(response.content) as WizardPageModel;
306
- if (newLayout?.name) {
307
- setPageModel((prev) => ({ ...prev, name: newLayout.name }));
308
- }
309
- } catch (parseError: unknown) {}
310
- },
311
- );
279
+ const pageModelResult =
280
+ await executePromptWithJsonResult<WizardPageModel>(
281
+ [
282
+ {
283
+ content: `${processedInstructions?.trim()} Reply with a json object of type PageModel = { name: string; };
284
+ The language of the page is ${language}.`,
285
+ name: "system",
286
+ role: "system",
287
+ id: crypto.randomUUID(),
288
+ },
289
+ {
290
+ content: promptContent,
291
+ name: "user",
292
+ role: "user",
293
+ id: crypto.randomUUID(),
294
+ },
295
+ ],
296
+ { editContext, createAiContext: createWizardAiContext },
297
+ { model: step.fields.aiModel || undefined },
298
+ { signal: abortController.signal },
299
+ );
312
300
 
313
- if (result && result.messages && result.messages.length > 0) {
314
- const lastMessage = result.messages[result.messages.length - 1];
315
- if (lastMessage && lastMessage.content) {
316
- const pageModelResult = JSON.parse(
317
- lastMessage.content,
318
- ) as WizardPageModel;
319
- if (pageModelResult?.name) {
320
- setPageModel((prev) => ({ ...prev, name: pageModelResult.name }));
321
- }
322
- }
301
+ if (pageModelResult && pageModelResult.name) {
302
+ setPageModel((prev) => ({ ...prev, name: pageModelResult.name }));
323
303
  }
324
304
  } catch (error) {
325
305
  console.error("Error generating page name", error);
@@ -752,6 +732,13 @@ export function ContentStep({
752
732
 
753
733
  pageLoadedRef.current = false;
754
734
 
735
+ // Reload the current page item to ensure we stay on the same page
736
+ if (currentPageItem) {
737
+ editContextRef.current?.loadItem(getItemDescriptor(currentPageItem), {
738
+ addToBrowseHistory: false,
739
+ });
740
+ }
741
+
755
742
  editContextRef.current?.requestRefresh("immediate");
756
743
  await waitForPageLoaded();
757
744
  console.log("Page loaded after wipe");
@@ -838,7 +825,7 @@ export function ContentStep({
838
825
 
839
826
  console.log("PROMPT: ", prompt, filteredSchema, existingPageModel);
840
827
 
841
- const result = await executePrompt(
828
+ const result = await executePromptWithJsonResult<WizardPageModel>(
842
829
  prompt,
843
830
  {
844
831
  editContext: editContextRef.current!,
@@ -850,33 +837,16 @@ export function ContentStep({
850
837
  endpoint: "/alpaca/editor/page-wizard/prompt",
851
838
  },
852
839
  { signal: localAbortController.signal },
853
- (response: any) => {
840
+ (response: WizardPageModel) => {
854
841
  try {
855
- // Handle streaming response - might have different structure during streaming
856
- const content =
857
- response.content ||
858
- response.messages?.[response.messages.length - 1]?.content;
859
- if (content) {
860
- const newLayout = JSON.parse(content) as WizardPageModel;
861
-
862
- if (newLayout) {
863
- setPageModel((prev) => mergeLayout(prev, newLayout));
864
- }
865
-
866
- setMessage(newLayout.message);
867
- }
842
+ setPageModel((prev) => mergeLayout(prev, response));
843
+ setMessage(response.message);
868
844
  } catch (parseError: unknown) {}
869
845
  },
870
846
  );
871
847
 
872
- if (result && result.messages && result.messages.length > 0) {
873
- const lastMessage = result.messages[result.messages.length - 1];
874
- if (lastMessage && lastMessage.content) {
875
- const finalLayout = JSON.parse(lastMessage.content);
876
- console.log("RESULT LAYOUT: ", finalLayout);
877
- setPageModel((prev) => mergeLayout(prev, finalLayout));
878
- }
879
- }
848
+ console.log("RESULT LAYOUT: ", result);
849
+ setPageModel((prev) => mergeLayout(prev, result));
880
850
  setStepCompleted(true);
881
851
  } catch (error) {
882
852
  console.error(error);
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import { FullItem } from "../../editor/pageModel";
3
3
  import { WizardPageModel } from "../PageWizard";
4
- import { executePrompt } from "../../editor/services/aiService";
4
+ import { executePromptWithJsonResult } from "../../editor/services/aiService";
5
5
  import { createWizardAiContext } from "../service";
6
6
  import { useEditContext } from "../../editor/client/editContext";
7
7
  import { Textarea } from "../../components/ui/textarea";
@@ -24,7 +24,6 @@ export function MetaDataStep({
24
24
  setStepCompleted,
25
25
  }: StepComponentProps) {
26
26
  const [isGenerating, setIsGenerating] = useState(false);
27
- const [fullParentItem, setFullParentItem] = useState<FullItem>();
28
27
  const editContext = useEditContext();
29
28
 
30
29
  useEffect(() => {
@@ -35,15 +34,6 @@ export function MetaDataStep({
35
34
  }
36
35
  }, [editContext]);
37
36
 
38
- useEffect(() => {
39
- const loadParentItem = async () => {
40
- if (!parentItem) return;
41
- const item = await editContext?.itemsRepository.getItem(parentItem);
42
- setFullParentItem(item);
43
- };
44
- loadParentItem();
45
- }, [parentItem]);
46
-
47
37
  // Mark step as completed immediately since this is now just informational
48
38
  useEffect(() => {
49
39
  setStepCompleted(true);
@@ -87,56 +77,58 @@ export function MetaDataStep({
87
77
  try {
88
78
  const abortController = new AbortController();
89
79
 
90
- const result = await executePrompt(
91
- [
92
- {
93
- content: `${processedSystemInstructions}\nYou are a helpful assistant that generates SEO metadata for a page.\n\nReturn ONLY valid JSON in this exact shape: {"metaDescription": string, "metaKeywords": string}.\nLanguage: ${parentItem?.language || "en"}.`,
94
- name: "system",
95
- role: "system",
96
- id: crypto.randomUUID(),
97
- },
98
- {
99
- content: `${processedInstructions}\n\nCurrent data: ${JSON.stringify(
100
- inputData,
101
- null,
102
- 2,
103
- )}`,
104
- name: "user",
105
- role: "user",
106
- id: crypto.randomUUID(),
107
- },
108
- ],
109
- { editContext, createAiContext: createWizardAiContext },
110
- { model: step.fields.aiModel || undefined },
111
- { signal: abortController.signal },
112
- (response: any) => {
113
- try {
114
- const newLayout = JSON.parse(response.content) as WizardPageModel;
115
- if (newLayout && setPageModel) {
116
- setPageModel((prev) => ({
117
- ...prev,
118
- metaDescription:
119
- (newLayout as any)?.metaDescription ?? prev.metaDescription,
120
- metaKeywords:
121
- (newLayout as any)?.metaKeywords ?? prev.metaKeywords,
122
- }));
80
+ const pageModelResult =
81
+ await executePromptWithJsonResult<WizardPageModel>(
82
+ [
83
+ {
84
+ content: `${processedSystemInstructions}\nYou are a helpful assistant that generates SEO metadata for a page.\n\nReturn ONLY valid JSON in this exact shape: {"metaDescription": string, "metaKeywords": string}.\nLanguage: ${parentItem?.language || "en"}.`,
85
+ name: "system",
86
+ role: "system",
87
+ id: crypto.randomUUID(),
88
+ },
89
+ {
90
+ content: `${processedInstructions}\n\nCurrent data: ${JSON.stringify(
91
+ inputData,
92
+ null,
93
+ 2,
94
+ )}`,
95
+ name: "user",
96
+ role: "user",
97
+ id: crypto.randomUUID(),
98
+ },
99
+ ],
100
+ { editContext, createAiContext: createWizardAiContext },
101
+ { model: step.fields.aiModel || undefined },
102
+ { signal: abortController.signal },
103
+ (response: any) => {
104
+ try {
105
+ const newLayout = JSON.parse(response.content) as WizardPageModel;
106
+ if (newLayout && setPageModel) {
107
+ setPageModel((prev) => ({
108
+ ...prev,
109
+ metaDescription:
110
+ (newLayout as any)?.metaDescription ?? prev.metaDescription,
111
+ metaKeywords:
112
+ (newLayout as any)?.metaKeywords ?? prev.metaKeywords,
113
+ }));
114
+ }
115
+ } catch {
116
+ // Ignore parsing errors during streaming
123
117
  }
124
- } catch {}
125
- },
126
- );
127
-
128
- if (result && result.messages && result.messages.length > 0) {
129
- const lastMessage = result.messages[result.messages.length - 1];
130
- if (lastMessage && lastMessage.content) {
131
- const pageModelResult = JSON.parse(
132
- lastMessage.content,
133
- ) as WizardPageModel;
134
- setPageModel((prev) => ({
135
- ...prev,
136
- metaDescription: pageModelResult.metaDescription,
137
- metaKeywords: pageModelResult.metaKeywords,
138
- }));
139
- }
118
+ },
119
+ );
120
+
121
+ // Parse the final result
122
+ if (
123
+ pageModelResult &&
124
+ pageModelResult.metaDescription &&
125
+ pageModelResult.metaKeywords
126
+ ) {
127
+ setPageModel((prev) => ({
128
+ ...prev,
129
+ metaDescription: pageModelResult.metaDescription,
130
+ metaKeywords: pageModelResult.metaKeywords,
131
+ }));
140
132
  }
141
133
  } catch (error) {
142
134
  console.error("Error generating meta fields", error);