@alpaca-editor/core 1.0.4096 → 1.0.4098

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 (136) hide show
  1. package/dist/components/ui/button.d.ts +1 -1
  2. package/dist/components/ui/button.js +1 -0
  3. package/dist/components/ui/button.js.map +1 -1
  4. package/dist/components/ui/context-menu.d.ts +2 -2
  5. package/dist/components/ui/context-menu.js +16 -16
  6. package/dist/components/ui/context-menu.js.map +1 -1
  7. package/dist/editor/Editor.js +1 -1
  8. package/dist/editor/Editor.js.map +1 -1
  9. package/dist/editor/FieldActionsOverlay.js +40 -26
  10. package/dist/editor/FieldActionsOverlay.js.map +1 -1
  11. package/dist/editor/FieldListField.js +1 -1
  12. package/dist/editor/FieldListField.js.map +1 -1
  13. package/dist/editor/LinkEditorDialog.js +1 -1
  14. package/dist/editor/ai/AgentTerminal.js +56 -5
  15. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  16. package/dist/editor/ai/Agents.js +23 -9
  17. package/dist/editor/ai/Agents.js.map +1 -1
  18. package/dist/editor/ai/ContextInfoBar.js +15 -5
  19. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  20. package/dist/editor/ai/ToolCallDisplay.js +2 -0
  21. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  22. package/dist/editor/client/AboutDialog.js +7 -5
  23. package/dist/editor/client/AboutDialog.js.map +1 -1
  24. package/dist/editor/client/EditorShell.js +7 -2
  25. package/dist/editor/client/EditorShell.js.map +1 -1
  26. package/dist/editor/client/editContext.d.ts +2 -2
  27. package/dist/editor/client/hooks/useSocketMessageHandler.js +5 -0
  28. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  29. package/dist/editor/client/itemsRepository.js +1 -0
  30. package/dist/editor/client/itemsRepository.js.map +1 -1
  31. package/dist/editor/control-center/Setup.js +1 -1
  32. package/dist/editor/control-center/Setup.js.map +1 -1
  33. package/dist/editor/control-center/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +2 -0
  34. package/dist/editor/control-center/setup-steps/AiSetupStep/EmbeddingsModelSection.js +195 -0
  35. package/dist/editor/control-center/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +1 -0
  36. package/dist/editor/control-center/setup-steps/AiSetupStep/index.d.ts +0 -1
  37. package/dist/editor/control-center/setup-steps/AiSetupStep/index.js +3 -506
  38. package/dist/editor/control-center/setup-steps/AiSetupStep/index.js.map +1 -1
  39. package/dist/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +1 -15
  40. package/dist/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.js +226 -3
  41. package/dist/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.js.map +1 -1
  42. package/dist/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +1 -1
  43. package/dist/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +1 -0
  44. package/dist/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +94 -0
  45. package/dist/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +1 -0
  46. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.d.ts +1 -7
  47. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js +323 -3
  48. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js.map +1 -1
  49. package/dist/editor/control-center/setup-steps/AiSetupStep/types.d.ts +1 -0
  50. package/dist/editor/control-center/setup-steps/AiSetupStep/types.js +2 -0
  51. package/dist/editor/control-center/setup-steps/AiSetupStep/types.js.map +1 -0
  52. package/dist/editor/control-center/setup-steps/AiSetupStep/utils.d.ts +4 -0
  53. package/dist/editor/control-center/setup-steps/AiSetupStep/utils.js +25 -0
  54. package/dist/editor/control-center/setup-steps/AiSetupStep/utils.js.map +1 -0
  55. package/dist/editor/control-center/setup-steps/IndexSetupStep.js +2 -1
  56. package/dist/editor/control-center/setup-steps/IndexSetupStep.js.map +1 -1
  57. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  58. package/dist/editor/field-types/TreeListEditor.js +102 -16
  59. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  60. package/dist/editor/field-types/richtext/components/ReactSlate.js +1 -1
  61. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js +1 -1
  62. package/dist/editor/menubar/toolbar-sections/EditControls.js +2 -2
  63. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  64. package/dist/editor/menubar/toolbar-sections/InsertControls.js +1 -1
  65. package/dist/editor/menubar/toolbar-sections/InsertControls.js.map +1 -1
  66. package/dist/editor/page-editor-chrome/FrameMenu.js +25 -12
  67. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  68. package/dist/editor/page-editor-chrome/SuggestionHighlighting.js +2 -2
  69. package/dist/editor/page-editor-chrome/SuggestionHighlighting.js.map +1 -1
  70. package/dist/editor/page-viewer/PageViewer.js +2 -1
  71. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  72. package/dist/editor/reviews/Comments.js +4 -4
  73. package/dist/editor/reviews/Comments.js.map +1 -1
  74. package/dist/editor/services/agentService.d.ts +2 -0
  75. package/dist/editor/services/agentService.js.map +1 -1
  76. package/dist/editor/services/aiService.d.ts +1 -0
  77. package/dist/editor/services/aiService.js.map +1 -1
  78. package/dist/editor/sidebar/ComponentTree.js +18 -6
  79. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  80. package/dist/editor/sidebar/Insert.js +1 -1
  81. package/dist/editor/ui/Splitter.js +2 -1
  82. package/dist/editor/ui/Splitter.js.map +1 -1
  83. package/dist/editor/utils.js +0 -1
  84. package/dist/editor/utils.js.map +1 -1
  85. package/dist/page-wizard/steps/MetaDataStep.js +43 -41
  86. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  87. package/dist/page-wizard/steps/SelectStep.js +19 -4
  88. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  89. package/dist/revision.d.ts +2 -2
  90. package/dist/revision.js +2 -2
  91. package/dist/styles.css +15 -18
  92. package/package.json +1 -1
  93. package/src/components/ui/button.tsx +1 -0
  94. package/src/components/ui/context-menu.tsx +44 -20
  95. package/src/editor/Editor.tsx +1 -1
  96. package/src/editor/FieldActionsOverlay.tsx +113 -91
  97. package/src/editor/FieldListField.tsx +4 -1
  98. package/src/editor/LinkEditorDialog.tsx +1 -1
  99. package/src/editor/ai/AgentTerminal.tsx +84 -4
  100. package/src/editor/ai/Agents.tsx +18 -5
  101. package/src/editor/ai/ContextInfoBar.tsx +22 -6
  102. package/src/editor/ai/ToolCallDisplay.tsx +2 -0
  103. package/src/editor/client/AboutDialog.tsx +22 -17
  104. package/src/editor/client/EditorShell.tsx +8 -2
  105. package/src/editor/client/editContext.ts +2 -2
  106. package/src/editor/client/hooks/useSocketMessageHandler.ts +8 -0
  107. package/src/editor/client/itemsRepository.ts +1 -0
  108. package/src/editor/control-center/Setup.tsx +1 -1
  109. package/src/editor/control-center/setup-steps/AiSetupStep/EmbeddingsModelSection.tsx +296 -0
  110. package/src/editor/control-center/setup-steps/AiSetupStep/index.tsx +7 -660
  111. package/src/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.tsx +363 -24
  112. package/src/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersList.tsx +1 -1
  113. package/src/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.tsx +139 -0
  114. package/src/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.tsx +385 -8
  115. package/src/editor/control-center/setup-steps/AiSetupStep/types.ts +1 -0
  116. package/src/editor/control-center/setup-steps/AiSetupStep/utils.ts +43 -0
  117. package/src/editor/control-center/setup-steps/IndexSetupStep.tsx +2 -0
  118. package/src/editor/field-types/SingleLineText.tsx +0 -9
  119. package/src/editor/field-types/TreeListEditor.tsx +115 -16
  120. package/src/editor/field-types/richtext/components/ReactSlate.tsx +1 -1
  121. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.tsx +1 -1
  122. package/src/editor/menubar/toolbar-sections/EditControls.tsx +2 -2
  123. package/src/editor/menubar/toolbar-sections/InsertControls.tsx +1 -1
  124. package/src/editor/page-editor-chrome/FrameMenu.tsx +81 -68
  125. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +5 -5
  126. package/src/editor/page-viewer/PageViewer.tsx +3 -2
  127. package/src/editor/reviews/Comments.tsx +13 -8
  128. package/src/editor/services/agentService.ts +3 -0
  129. package/src/editor/services/aiService.ts +2 -0
  130. package/src/editor/sidebar/ComponentTree.tsx +20 -6
  131. package/src/editor/sidebar/Insert.tsx +1 -1
  132. package/src/editor/ui/Splitter.tsx +2 -2
  133. package/src/editor/utils.ts +0 -1
  134. package/src/page-wizard/steps/MetaDataStep.tsx +61 -50
  135. package/src/page-wizard/steps/SelectStep.tsx +37 -4
  136. package/src/revision.ts +2 -2
@@ -3,32 +3,318 @@ import { Select } from "../../../../../components/ui/select";
3
3
  import { Input } from "../../../../../components/ui/input";
4
4
  import { Button } from "../../../../../components/ui/button";
5
5
  import { CheckCircle, RefreshCw } from "lucide-react";
6
+ import { useEditContext } from "../../../../client/editContext";
7
+ import type { ItemDescriptor } from "../../../../pageModel";
8
+ import { getChildren } from "../../../../services/contentService";
9
+ import type { ItemTreeNodeData } from "../../../../services/contentService";
10
+ import { findByName, resolvePathUnderContent } from "../utils";
6
11
 
7
12
  interface ProviderOption {
8
13
  value: string;
9
14
  label: string;
10
15
  }
11
16
 
12
- interface ProviderSectionProps {
13
- aiProvider: string;
14
- aiOptions: ProviderOption[];
15
- onProviderChange: (value: string) => void;
16
- apiKey: string;
17
- onApiKeyChange: (value: string) => void;
18
- aiBusy: boolean;
19
- onAddEndpoint: () => void;
20
- }
17
+ export function ProviderSection() {
18
+ const editContext = useEditContext();
19
+ const [aiProvider, setAiProvider] = React.useState<string>("");
20
+ const [apiKey, setApiKey] = React.useState<string>("");
21
+ const [aiBusy, setAiBusy] = React.useState(false);
22
+ const [aiError, setAiError] = React.useState<string | null>(null);
23
+ const [endpoints, setEndpoints] = React.useState<ItemTreeNodeData[]>([]);
24
+ const [loadingEndpoints, setLoadingEndpoints] = React.useState(false);
25
+ const [endpointsError, setEndpointsError] = React.useState<string | null>(
26
+ null,
27
+ );
28
+ const lastLoadAtRef = React.useRef<number>(0);
29
+ const isLoadingRef = React.useRef<boolean>(false);
30
+
31
+ const userLang = editContext?.contentEditorItem?.language || "en";
32
+ const sessionId = editContext?.sessionId;
33
+
34
+ const aiOptions = React.useMemo<ProviderOption[]>(
35
+ () => [
36
+ { value: "openai", label: "OpenAI" },
37
+ { value: "azure-openai", label: "Azure OpenAI" },
38
+ { value: "openrouter", label: "OpenRouter" },
39
+ ],
40
+ [],
41
+ );
42
+
43
+ const loadEndpoints = React.useCallback(
44
+ async (options?: { force?: boolean }) => {
45
+ if (!sessionId) return;
46
+ try {
47
+ const now = Date.now();
48
+ const MIN_INTERVAL_MS = 2000;
49
+ if (!options?.force) {
50
+ if (isLoadingRef.current) return;
51
+ if (now - lastLoadAtRef.current < MIN_INTERVAL_MS) return;
52
+ }
53
+ isLoadingRef.current = true;
54
+ lastLoadAtRef.current = now;
55
+ setLoadingEndpoints(true);
56
+ setEndpointsError(null);
57
+
58
+ const settingsItem = await resolvePathUnderContent(
59
+ sessionId,
60
+ userLang,
61
+ ["Settings", "Editor"],
62
+ );
63
+ if (!settingsItem) {
64
+ setEndpoints([]);
65
+ return;
66
+ }
67
+
68
+ const children = (await getChildren(
69
+ settingsItem.id,
70
+ sessionId!,
71
+ [],
72
+ false,
73
+ userLang,
74
+ "path",
75
+ )) as unknown as ItemTreeNodeData[];
76
+ const aiEndpointsParent = findByName(children, "Ai Endpoints");
77
+ if (!aiEndpointsParent) {
78
+ setEndpoints([]);
79
+ return;
80
+ }
81
+
82
+ const epChildren = (await getChildren(
83
+ aiEndpointsParent.id,
84
+ sessionId!,
85
+ [],
86
+ false,
87
+ userLang,
88
+ "path",
89
+ )) as unknown as ItemTreeNodeData[];
90
+ setEndpoints(epChildren || []);
91
+ } catch (e: any) {
92
+ setEndpointsError(e?.message || "Failed to load AI endpoints");
93
+ } finally {
94
+ isLoadingRef.current = false;
95
+ setLoadingEndpoints(false);
96
+ }
97
+ },
98
+ [sessionId, userLang],
99
+ );
100
+
101
+ const createAiEndpoint = React.useCallback(async () => {
102
+ if (!aiProvider || !editContext) return;
103
+ try {
104
+ setAiBusy(true);
105
+ setAiError(null);
106
+
107
+ const settingsItem = await resolvePathUnderContent(
108
+ editContext.sessionId,
109
+ userLang,
110
+ ["Settings", "Editor"],
111
+ );
112
+ if (!settingsItem)
113
+ throw new Error("Editor settings item not found. Create it first.");
114
+
115
+ const children = (await getChildren(
116
+ settingsItem.id,
117
+ editContext.sessionId!,
118
+ [],
119
+ false,
120
+ userLang,
121
+ "path",
122
+ )) as unknown as ItemTreeNodeData[];
123
+ let aiEndpointsParent = findByName(children, "Ai Endpoints");
124
+ if (!aiEndpointsParent) {
125
+ const aiEndpointsTemplateId = "4592f2e0-06e4-470e-9820-7fd4ea148e00";
126
+ const createdContainer = await editContext.operations.createItem(
127
+ settingsItem,
128
+ aiEndpointsTemplateId,
129
+ "Ai Endpoints",
130
+ );
131
+ if (!createdContainer)
132
+ throw new Error("Failed to create 'Ai Endpoints' container");
133
+ aiEndpointsParent = {
134
+ id: createdContainer.id,
135
+ name: "Ai Endpoints",
136
+ thumbUrl: "",
137
+ previewUrl: "",
138
+ templateId: "",
139
+ icon: "",
140
+ hasChildren: false,
141
+ isComponent: false,
142
+ displayName: "Ai Endpoints",
143
+ language: userLang,
144
+ version: 0,
145
+ hasLayout: false,
146
+ } as unknown as ItemTreeNodeData;
147
+ }
148
+
149
+ const endpointTemplateId = "a6358b92-a9ea-4dd8-b4e1-7d6d3a3b9c40";
150
+ const endpointName =
151
+ aiProvider === "openai"
152
+ ? "OpenAI"
153
+ : aiProvider === "azure-openai"
154
+ ? "Azure OpenAI"
155
+ : "Open Router";
156
+
157
+ // Find or create endpoint
158
+ let endpointItem: ItemDescriptor | null = null;
159
+ const epChildren = (await getChildren(
160
+ aiEndpointsParent!.id,
161
+ editContext.sessionId!,
162
+ [],
163
+ false,
164
+ userLang,
165
+ "path",
166
+ )) as unknown as ItemTreeNodeData[];
167
+ const existingEndpoint = findByName(epChildren, endpointName);
168
+ if (existingEndpoint) {
169
+ endpointItem = {
170
+ id: existingEndpoint.id,
171
+ language: userLang,
172
+ version: 0,
173
+ } as ItemDescriptor;
174
+ } else {
175
+ const created = await editContext.operations.createItem(
176
+ {
177
+ id: aiEndpointsParent!.id,
178
+ language: userLang,
179
+ version: 0,
180
+ } as ItemDescriptor,
181
+ endpointTemplateId,
182
+ endpointName,
183
+ );
184
+ if (!created) throw new Error("Failed to create AI endpoint");
185
+ endpointItem = created;
186
+ }
21
187
 
22
- export function ProviderSection(props: ProviderSectionProps) {
23
- const {
24
- aiProvider,
25
- aiOptions,
26
- onProviderChange,
27
- apiKey,
28
- onApiKeyChange,
29
- aiBusy,
30
- onAddEndpoint,
31
- } = props;
188
+ // Set API key if provided
189
+ if (apiKey && endpointItem) {
190
+ const apiKeyFieldId = "db07ae53-459a-4314-89b3-ab2721df2b4f";
191
+ await editContext.operations.editField({
192
+ field: {
193
+ fieldId: apiKeyFieldId,
194
+ item: {
195
+ id: endpointItem.id,
196
+ language: userLang,
197
+ version: 0,
198
+ },
199
+ },
200
+ value: apiKey,
201
+ rawValue: apiKey,
202
+ refresh: "delayed",
203
+ });
204
+ }
205
+
206
+ // Ensure Ai Models container exists
207
+ let aiModelsParent = findByName(children, "Ai Models");
208
+ if (!aiModelsParent) {
209
+ const aiModelsTemplateId = "e5f6a7b8-9c0d-1e2f-3a4b-5c6d7e8f9a0b";
210
+ const createdModels = await editContext.operations.createItem(
211
+ settingsItem,
212
+ aiModelsTemplateId,
213
+ "Ai Models",
214
+ );
215
+ if (!createdModels)
216
+ throw new Error("Failed to create 'Ai Models' container");
217
+ aiModelsParent = {
218
+ id: createdModels.id,
219
+ name: "Ai Models",
220
+ thumbUrl: "",
221
+ previewUrl: "",
222
+ templateId: "",
223
+ icon: "",
224
+ hasChildren: false,
225
+ isComponent: false,
226
+ displayName: "Ai Models",
227
+ language: userLang,
228
+ version: 0,
229
+ hasLayout: false,
230
+ } as unknown as ItemTreeNodeData;
231
+ }
232
+
233
+ // Create default models if missing and link to endpoint
234
+ const modelsToEnsure = ["gpt-5", "gpt5-high", "image-1"];
235
+ const modelTemplateId = "d2e3f4a5-4b5c-5c6d-bf0b-3e4f5a6b7c8e";
236
+ const modelNameFieldId = "f1e2d3c4-5b6a-7980-8e9f-1a2b3c4d5e6f";
237
+ const modelEndpointFieldId = "cbade708-ce95-4f54-81b7-308ea61a76a6";
238
+
239
+ const modelChildren = (await getChildren(
240
+ aiModelsParent.id,
241
+ editContext.sessionId!,
242
+ [],
243
+ false,
244
+ userLang,
245
+ "path",
246
+ )) as unknown as ItemTreeNodeData[];
247
+
248
+ for (const modelName of modelsToEnsure) {
249
+ let modelNode = findByName(modelChildren, modelName);
250
+ if (!modelNode) {
251
+ const createdModel = await editContext.operations.createItem(
252
+ {
253
+ id: aiModelsParent.id,
254
+ language: userLang,
255
+ version: 0,
256
+ } as ItemDescriptor,
257
+ modelTemplateId,
258
+ modelName,
259
+ );
260
+ if (!createdModel)
261
+ throw new Error(`Failed to create model '${modelName}'`);
262
+ modelNode = {
263
+ id: createdModel.id,
264
+ name: modelName,
265
+ displayName: modelName,
266
+ thumbUrl: "",
267
+ previewUrl: "",
268
+ templateId: "",
269
+ icon: "",
270
+ hasChildren: false,
271
+ isComponent: false,
272
+ language: userLang,
273
+ version: 0,
274
+ hasLayout: false,
275
+ } as unknown as ItemTreeNodeData;
276
+
277
+ await editContext.operations.editField({
278
+ field: {
279
+ fieldId: modelNameFieldId,
280
+ item: { id: modelNode.id, language: userLang, version: 0 },
281
+ },
282
+ value: modelName,
283
+ rawValue: modelName,
284
+ refresh: "delayed",
285
+ });
286
+
287
+ const endpointGuid = `{${(endpointItem!.id as string)
288
+ .replace(/[{}()]/g, "")
289
+ .toUpperCase()}}`;
290
+ await editContext.operations.editField({
291
+ field: {
292
+ fieldId: modelEndpointFieldId,
293
+ item: { id: modelNode.id, language: userLang, version: 0 },
294
+ },
295
+ rawValue: endpointGuid,
296
+ refresh: "delayed",
297
+ });
298
+ }
299
+ }
300
+
301
+ editContext.requestRefresh?.("immediate");
302
+ editContext.showToast?.(
303
+ `Added '${endpointName}' AI endpoint successfully`,
304
+ );
305
+ await loadEndpoints({ force: true });
306
+ setAiProvider("");
307
+ setApiKey("");
308
+ } catch (e: any) {
309
+ setAiError(e?.message || "Failed to add AI endpoint");
310
+ } finally {
311
+ setAiBusy(false);
312
+ }
313
+ }, [aiProvider, editContext, userLang, apiKey, loadEndpoints]);
314
+
315
+ React.useEffect(() => {
316
+ loadEndpoints({ force: true });
317
+ }, [loadEndpoints]);
32
318
 
33
319
  return (
34
320
  <>
@@ -40,10 +326,58 @@ export function ProviderSection(props: ProviderSectionProps) {
40
326
  </span>
41
327
  </div>
42
328
  </div>
43
- <div className="mt-3 flex items-center gap-2">
329
+ <div className="mt-3">
330
+ <div className="flex items-center justify-between gap-2">
331
+ <span className="text-xs text-gray-600">Existing endpoints</span>
332
+ <Button
333
+ size="sm"
334
+ disabled={loadingEndpoints}
335
+ onClick={() => loadEndpoints({ force: true })}
336
+ >
337
+ {loadingEndpoints ? (
338
+ <RefreshCw
339
+ strokeWidth={1}
340
+ className="h-4 w-4 animate-spin"
341
+ />
342
+ ) : (
343
+ <RefreshCw strokeWidth={1} className="h-4 w-4" />
344
+ )}
345
+ Refresh
346
+ </Button>
347
+ </div>
348
+ <div className="mt-2">
349
+ {endpointsError && (
350
+ <div className="rounded border border-red-200 bg-red-50 p-2 text-xs whitespace-pre-wrap text-red-700">
351
+ {endpointsError}
352
+ </div>
353
+ )}
354
+ {!endpointsError && (
355
+ <>
356
+ {loadingEndpoints ? (
357
+ <div className="text-xs text-gray-500 flex items-center gap-2">
358
+ <RefreshCw
359
+ strokeWidth={1}
360
+ className="h-4 w-4 animate-spin"
361
+ />
362
+ Loading endpoints…
363
+ </div>
364
+ ) : endpoints.length > 0 ? (
365
+ <ul className="list-disc pl-5 text-xs text-gray-700">
366
+ {endpoints.map((ep) => (
367
+ <li key={ep.id}>{ep.displayName || ep.name}</li>
368
+ ))}
369
+ </ul>
370
+ ) : (
371
+ <div className="text-xs text-gray-500">No AI endpoints found.</div>
372
+ )}
373
+ </>
374
+ )}
375
+ </div>
376
+ </div>
377
+ <div className="mt-3 flex flex-col items-stretch gap-2">
44
378
  <Select
45
379
  value={aiProvider}
46
- onValueChange={onProviderChange}
380
+ onValueChange={setAiProvider}
47
381
  options={aiOptions}
48
382
  placeholder="Choose provider"
49
383
  />
@@ -52,14 +386,14 @@ export function ProviderSection(props: ProviderSectionProps) {
52
386
  type="password"
53
387
  placeholder="API key"
54
388
  value={apiKey}
55
- onChange={(e) => onApiKeyChange(e.target.value)}
56
- className="w-56"
389
+ onChange={(e) => setApiKey(e.target.value)}
390
+
57
391
  />
58
392
  )}
59
393
  <Button
60
394
  size="sm"
61
395
  disabled={!aiProvider || !apiKey || aiBusy}
62
- onClick={onAddEndpoint}
396
+ onClick={createAiEndpoint}
63
397
  >
64
398
  {aiBusy ? (
65
399
  <RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
@@ -69,6 +403,11 @@ export function ProviderSection(props: ProviderSectionProps) {
69
403
  Add endpoint
70
404
  </Button>
71
405
  </div>
406
+ {aiError && (
407
+ <div className="mt-2 rounded border border-red-200 bg-red-50 p-2 text-xs whitespace-pre-wrap text-red-700">
408
+ {aiError}
409
+ </div>
410
+ )}
72
411
  </>
73
412
  );
74
413
  }
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { Button } from "../../../../../components/ui/button";
3
3
  import { AlertCircle, CheckCircle, RefreshCw } from "lucide-react";
4
- import type { StepState } from "../index";
4
+ import type { StepState } from "../types";
5
5
 
6
6
  interface RequiredContainerDef {
7
7
  name: string;
@@ -0,0 +1,139 @@
1
+ import React from "react";
2
+ import { useEditContext } from "../../../../client/editContext";
3
+ import type { ItemDescriptor } from "../../../../pageModel";
4
+ import { getChildren } from "../../../../services/contentService";
5
+ import type { ItemTreeNodeData } from "../../../../services/contentService";
6
+ import type { StepState } from "../types";
7
+ import { RequiredContainersList } from "./RequiredContainersList";
8
+ import { findByName, resolvePathUnderContent } from "../utils";
9
+
10
+ interface RequiredContainerDef {
11
+ name: string;
12
+ templateId: string;
13
+ }
14
+
15
+ export function RequiredContainersSection() {
16
+ const editContext = useEditContext();
17
+ const userLang = editContext?.contentEditorItem?.language || "en";
18
+
19
+ const requiredContainers = React.useMemo<RequiredContainerDef[]>(
20
+ () => [
21
+ { name: "Ai Models", templateId: "e5f6a7b8-9c0d-1e2f-3a4b-5c6d7e8f9a0b" },
22
+ {
23
+ name: "Ai Endpoints",
24
+ templateId: "4592f2e0-06e4-470e-9820-7fd4ea148e00",
25
+ },
26
+ {
27
+ name: "Ai Profiles",
28
+ templateId: "4298ab78-2aaa-4d5f-bb3a-0bd321b86063",
29
+ },
30
+ {
31
+ name: "Ai Schemas",
32
+ templateId: "5214a52b-6c45-4671-97f2-703fe24e3d09",
33
+ },
34
+ { name: "Ai Quotas", templateId: "7a08ff28-70ed-459b-a7f2-db2a78d88f46" },
35
+ {
36
+ name: "AI Generators",
37
+ templateId: "9430a3e9-7397-4598-9a55-2b263c927497",
38
+ },
39
+ { name: "AI Tools", templateId: "7e3f9d9d-8e84-4e57-b12e-6e9c99d4fd11" },
40
+ ],
41
+ [],
42
+ );
43
+
44
+ const [editorSettingsItem, setEditorSettingsItem] =
45
+ React.useState<ItemDescriptor | null>(null);
46
+ const [containersError, setContainersError] = React.useState<string | null>(
47
+ null,
48
+ );
49
+ const [containerStates, setContainerStates] = React.useState<
50
+ Record<string, StepState>
51
+ >({});
52
+ const [busyMap, setBusyMap] = React.useState<Record<string, boolean>>({});
53
+
54
+ const checkRequiredContainers = React.useCallback(async () => {
55
+ try {
56
+ setContainersError(null);
57
+ const initialStates: Record<string, StepState> = {};
58
+ for (const rc of requiredContainers) initialStates[rc.name] = "checking";
59
+ setContainerStates(initialStates);
60
+
61
+ const settingsItem = await resolvePathUnderContent(
62
+ editContext?.sessionId,
63
+ userLang,
64
+ ["Settings", "Editor"],
65
+ );
66
+ if (!settingsItem) {
67
+ setEditorSettingsItem(null);
68
+ const errorMsg = "Editor settings item not found. Create it first.";
69
+ setContainersError(errorMsg);
70
+ const errorStates: Record<string, StepState> = {};
71
+ for (const rc of requiredContainers) errorStates[rc.name] = "error";
72
+ setContainerStates(errorStates);
73
+ return;
74
+ }
75
+
76
+ setEditorSettingsItem(settingsItem);
77
+ const children = (await getChildren(
78
+ settingsItem.id,
79
+ editContext!.sessionId!,
80
+ [],
81
+ false,
82
+ userLang,
83
+ "path",
84
+ )) as unknown as ItemTreeNodeData[];
85
+
86
+ const states: Record<string, StepState> = {};
87
+ for (const rc of requiredContainers) {
88
+ const exists = !!findByName(children, rc.name);
89
+ states[rc.name] = exists ? "success" : "error";
90
+ }
91
+ setContainerStates(states);
92
+ } catch (e: any) {
93
+ setContainersError(e?.message || "Failed to check AI containers");
94
+ const errorStates: Record<string, StepState> = {};
95
+ for (const rc of requiredContainers) errorStates[rc.name] = "error";
96
+ setContainerStates(errorStates);
97
+ }
98
+ }, [editContext, requiredContainers, userLang]);
99
+
100
+ React.useEffect(() => {
101
+ checkRequiredContainers();
102
+ }, [checkRequiredContainers]);
103
+
104
+ const createContainer = React.useCallback(
105
+ async (name: string, templateId: string) => {
106
+ if (!editorSettingsItem || !editContext) return;
107
+ try {
108
+ setBusyMap((s) => ({ ...s, [name]: true }));
109
+ setContainersError(null);
110
+
111
+ const created = await editContext.operations.createItem(
112
+ editorSettingsItem,
113
+ templateId,
114
+ name,
115
+ );
116
+
117
+ if (!created) throw new Error(`Failed to create '${name}'`);
118
+ setContainerStates((s) => ({ ...s, [name]: "success" }));
119
+ } catch (e: any) {
120
+ setContainersError(e?.message || `Failed to create '${name}'`);
121
+ setContainerStates((s) => ({ ...s, [name]: "error" }));
122
+ } finally {
123
+ setBusyMap((s) => ({ ...s, [name]: false }));
124
+ }
125
+ },
126
+ [editorSettingsItem, editContext],
127
+ );
128
+
129
+ return (
130
+ <RequiredContainersList
131
+ requiredContainers={requiredContainers}
132
+ containerStates={containerStates}
133
+ busyMap={busyMap}
134
+ canFix={!!editorSettingsItem}
135
+ containersError={containersError}
136
+ onFixContainer={createContainer}
137
+ />
138
+ );
139
+ }