@alpaca-editor/core 1.0.4086 → 1.0.4088
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.
- package/dist/components/ui/card.d.ts +1 -1
- package/dist/config/config.js +10 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/editor/ContentTree.js +43 -21
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/FieldListField.js +12 -1
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +3 -1
- package/dist/editor/ai/AgentTerminal.js +96 -74
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +46 -2
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +171 -75
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.js +27 -14
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/client/EditorShell.js +110 -17
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +4 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.d.ts +1 -0
- package/dist/editor/client/hooks/useSocketMessageHandler.js +54 -20
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/hooks/useWorkbox.d.ts +1 -1
- package/dist/editor/client/hooks/useWorkbox.js +4 -4
- package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
- package/dist/editor/client/itemsRepository.d.ts +13 -1
- package/dist/editor/client/itemsRepository.js +34 -21
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +1 -1
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/control-center/Setup.js +12 -223
- package/dist/editor/control-center/Setup.js.map +1 -1
- package/dist/editor/control-center/setup-steps/AiSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/AiSetupStep.js +287 -0
- package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +1 -0
- package/dist/editor/control-center/setup-steps/DbSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/DbSetupStep.js +46 -0
- package/dist/editor/control-center/setup-steps/DbSetupStep.js.map +1 -0
- package/dist/editor/control-center/setup-steps/IndexSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/IndexSetupStep.js +34 -0
- package/dist/editor/control-center/setup-steps/IndexSetupStep.js.map +1 -0
- package/dist/editor/control-center/setup-steps/SettingsSetupStep.d.ts +2 -0
- package/dist/editor/control-center/setup-steps/SettingsSetupStep.js +104 -0
- package/dist/editor/control-center/setup-steps/SettingsSetupStep.js.map +1 -0
- package/dist/editor/field-types/RichTextEditorComponent.js +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.js +2 -2
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +1 -1
- package/dist/editor/field-types/richtext/utils/profileServiceCache.js +16 -14
- package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/CompareControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/CompareControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/InlineEditor.js +25 -6
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +9 -2
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +5 -0
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/pageModel.d.ts +1 -0
- package/dist/editor/reviews/Comment.js +1 -1
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +3 -24
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentPopover.js +3 -23
- package/dist/editor/reviews/CommentPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.js +2 -1
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.js +88 -37
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +3 -0
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/sidebar/Debug.js +1 -5
- package/dist/editor/sidebar/Debug.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.js +72 -6
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/Icons.d.ts +5 -0
- package/dist/editor/ui/Icons.js +14 -0
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/SplashScreen.js +2 -2
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +0 -5
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ui/card.tsx +1 -1
- package/src/config/config.tsx +9 -2
- package/src/config/types.ts +5 -0
- package/src/editor/ContentTree.tsx +48 -23
- package/src/editor/FieldListField.tsx +13 -2
- package/src/editor/ai/AgentTerminal.tsx +118 -71
- package/src/editor/ai/Agents.tsx +52 -1
- package/src/editor/ai/AiResponseMessage.tsx +234 -78
- package/src/editor/ai/AiTerminal.tsx +30 -14
- package/src/editor/client/EditorShell.tsx +128 -25
- package/src/editor/client/editContext.ts +1 -0
- package/src/editor/client/hooks/useSocketMessageHandler.ts +70 -21
- package/src/editor/client/hooks/useWorkbox.ts +4 -4
- package/src/editor/client/itemsRepository.ts +56 -25
- package/src/editor/client/pageModelBuilder.ts +1 -1
- package/src/editor/control-center/Setup.tsx +14 -420
- package/src/editor/control-center/setup-steps/AiSetupStep.tsx +462 -0
- package/src/editor/control-center/setup-steps/DbSetupStep.tsx +84 -0
- package/src/editor/control-center/setup-steps/IndexSetupStep.tsx +56 -0
- package/src/editor/control-center/setup-steps/SettingsSetupStep.tsx +176 -0
- package/src/editor/field-types/RichTextEditorComponent.tsx +0 -1
- package/src/editor/field-types/richtext/components/ReactSlate.tsx +14 -6
- package/src/editor/field-types/richtext/utils/profileServiceCache.ts +42 -32
- package/src/editor/menubar/toolbar-sections/CompareControls.tsx +1 -0
- package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
- package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +1 -0
- package/src/editor/page-editor-chrome/InlineEditor.tsx +29 -6
- package/src/editor/page-viewer/EditorForm.tsx +13 -2
- package/src/editor/page-viewer/PageViewerFrame.tsx +4 -0
- package/src/editor/pageModel.ts +1 -0
- package/src/editor/reviews/Comment.tsx +1 -1
- package/src/editor/reviews/CommentDisplayPopover.tsx +2 -22
- package/src/editor/reviews/CommentPopover.tsx +3 -24
- package/src/editor/reviews/CommentView.tsx +3 -2
- package/src/editor/reviews/Comments.tsx +162 -35
- package/src/editor/reviews/commentAi.ts +5 -0
- package/src/editor/sidebar/Debug.tsx +1 -5
- package/src/editor/sidebar/ViewSelector.tsx +144 -28
- package/src/editor/ui/Icons.tsx +55 -0
- package/src/revision.ts +2 -2
- package/src/splash-screen/SplashScreen.tsx +5 -6
- package/src/types.ts +3 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { Card } from "../../../components/ui/card";
|
|
3
|
+
import { Button } from "../../../components/ui/button";
|
|
4
|
+
import { Select } from "../../../components/ui/select";
|
|
5
|
+
import { Input } from "../../../components/ui/input";
|
|
6
|
+
import { useEditContext } from "../../client/editContext";
|
|
7
|
+
import { ItemDescriptor } from "../../pageModel";
|
|
8
|
+
import { getChildren } from "../../services/contentService";
|
|
9
|
+
import type { ItemTreeNodeData } from "../../services/contentService";
|
|
10
|
+
import { CheckCircle, RefreshCw, SparklesIcon, AlertCircle } from "lucide-react";
|
|
11
|
+
import { contentItemId } from "../../../config/config";
|
|
12
|
+
|
|
13
|
+
type StepState = "idle" | "checking" | "success" | "error";
|
|
14
|
+
|
|
15
|
+
function findByName(
|
|
16
|
+
items: ItemTreeNodeData[],
|
|
17
|
+
name: string,
|
|
18
|
+
): ItemTreeNodeData | undefined {
|
|
19
|
+
const target = name.toLowerCase();
|
|
20
|
+
return items.find(
|
|
21
|
+
(x) =>
|
|
22
|
+
(((x.displayName as string) || x.name || "") as string).toLowerCase() ===
|
|
23
|
+
target,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function AiSetupStep() {
|
|
28
|
+
const editContext = useEditContext();
|
|
29
|
+
const [aiState, setAiState] = useState<StepState>("idle");
|
|
30
|
+
const [aiProvider, setAiProvider] = useState<string>("");
|
|
31
|
+
const [aiBusy, setAiBusy] = useState(false);
|
|
32
|
+
const [aiError, setAiError] = useState<string | null>(null);
|
|
33
|
+
const [apiKey, setApiKey] = useState<string>("");
|
|
34
|
+
|
|
35
|
+
const [editorSettingsItem, setEditorSettingsItem] = useState<ItemDescriptor | null>(null);
|
|
36
|
+
const [containersError, setContainersError] = useState<string | null>(null);
|
|
37
|
+
const requiredContainers = useMemo(
|
|
38
|
+
() => [
|
|
39
|
+
{ name: "Ai Models", templateId: "e5f6a7b8-9c0d-1e2f-3a4b-5c6d7e8f9a0b" },
|
|
40
|
+
{ name: "Ai Endpoints", templateId: "4592f2e0-06e4-470e-9820-7fd4ea148e00" },
|
|
41
|
+
{ name: "Ai Profiles", templateId: "4298ab78-2aaa-4d5f-bb3a-0bd321b86063" },
|
|
42
|
+
{ name: "Ai Schemas", templateId: "5214a52b-6c45-4671-97f2-703fe24e3d09" },
|
|
43
|
+
{ name: "Ai Quotas", templateId: "7a08ff28-70ed-459b-a7f2-db2a78d88f46" },
|
|
44
|
+
{ name: "AI Generators", templateId: "9430a3e9-7397-4598-9a55-2b263c927497" },
|
|
45
|
+
],
|
|
46
|
+
[],
|
|
47
|
+
);
|
|
48
|
+
const [containerStates, setContainerStates] = useState<Record<string, StepState>>({});
|
|
49
|
+
const [busyMap, setBusyMap] = useState<Record<string, boolean>>({});
|
|
50
|
+
|
|
51
|
+
const userLang = editContext?.contentEditorItem?.language || "en";
|
|
52
|
+
|
|
53
|
+
const aiOptions = useMemo(
|
|
54
|
+
() => [
|
|
55
|
+
{ value: "openai", label: "OpenAI" },
|
|
56
|
+
{ value: "azure-openai", label: "Azure OpenAI" },
|
|
57
|
+
{ value: "openrouter", label: "OpenRouter" },
|
|
58
|
+
],
|
|
59
|
+
[],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const resolvePathUnderContent = React.useCallback(
|
|
63
|
+
async (names: string[]): Promise<ItemDescriptor | null> => {
|
|
64
|
+
if (!editContext?.sessionId) return null;
|
|
65
|
+
let currentId = contentItemId;
|
|
66
|
+
for (const segment of names) {
|
|
67
|
+
const children = (await getChildren(
|
|
68
|
+
currentId,
|
|
69
|
+
editContext.sessionId,
|
|
70
|
+
[],
|
|
71
|
+
false,
|
|
72
|
+
userLang,
|
|
73
|
+
"path",
|
|
74
|
+
)) as unknown as ItemTreeNodeData[];
|
|
75
|
+
const next = findByName(children, segment);
|
|
76
|
+
if (!next) return null;
|
|
77
|
+
currentId = next.id;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
id: currentId,
|
|
81
|
+
language: userLang,
|
|
82
|
+
version: 0,
|
|
83
|
+
} as ItemDescriptor;
|
|
84
|
+
},
|
|
85
|
+
[editContext?.sessionId, userLang],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const createAiEndpoint = useCallback(async () => {
|
|
89
|
+
if (!aiProvider || !editContext) return;
|
|
90
|
+
try {
|
|
91
|
+
setAiBusy(true);
|
|
92
|
+
setAiError(null);
|
|
93
|
+
|
|
94
|
+
const settingsItem = await resolvePathUnderContent([
|
|
95
|
+
"Settings",
|
|
96
|
+
"Editor",
|
|
97
|
+
]);
|
|
98
|
+
if (!settingsItem)
|
|
99
|
+
throw new Error("Editor settings item not found. Create it first.");
|
|
100
|
+
|
|
101
|
+
const children = (await getChildren(
|
|
102
|
+
settingsItem.id,
|
|
103
|
+
editContext.sessionId!,
|
|
104
|
+
[],
|
|
105
|
+
false,
|
|
106
|
+
userLang,
|
|
107
|
+
"path",
|
|
108
|
+
)) as unknown as ItemTreeNodeData[];
|
|
109
|
+
let aiEndpointsParent = findByName(children, "Ai Endpoints");
|
|
110
|
+
if (!aiEndpointsParent) {
|
|
111
|
+
const aiEndpointsTemplateId = "4592f2e0-06e4-470e-9820-7fd4ea148e00";
|
|
112
|
+
const createdContainer = await editContext.operations.createItem(
|
|
113
|
+
settingsItem,
|
|
114
|
+
aiEndpointsTemplateId,
|
|
115
|
+
"Ai Endpoints",
|
|
116
|
+
);
|
|
117
|
+
if (!createdContainer)
|
|
118
|
+
throw new Error("Failed to create 'Ai Endpoints' container");
|
|
119
|
+
aiEndpointsParent = {
|
|
120
|
+
id: createdContainer.id,
|
|
121
|
+
name: "Ai Endpoints",
|
|
122
|
+
thumbUrl: "",
|
|
123
|
+
previewUrl: "",
|
|
124
|
+
templateId: "",
|
|
125
|
+
icon: "",
|
|
126
|
+
hasChildren: false,
|
|
127
|
+
isComponent: false,
|
|
128
|
+
displayName: "Ai Endpoints",
|
|
129
|
+
language: userLang,
|
|
130
|
+
version: 0,
|
|
131
|
+
hasLayout: false,
|
|
132
|
+
} as unknown as ItemTreeNodeData;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const endpointTemplateId = "a6358b92-a9ea-4dd8-b4e1-7d6d3a3b9c40";
|
|
136
|
+
const endpointName =
|
|
137
|
+
aiProvider === "openai"
|
|
138
|
+
? "OpenAI"
|
|
139
|
+
: aiProvider === "azure-openai"
|
|
140
|
+
? "Azure OpenAI"
|
|
141
|
+
: "Open Router";
|
|
142
|
+
|
|
143
|
+
// Find or create endpoint
|
|
144
|
+
let endpointItem: ItemDescriptor | null = null;
|
|
145
|
+
const epChildren = (await getChildren(
|
|
146
|
+
aiEndpointsParent!.id,
|
|
147
|
+
editContext.sessionId!,
|
|
148
|
+
[],
|
|
149
|
+
false,
|
|
150
|
+
userLang,
|
|
151
|
+
"path",
|
|
152
|
+
)) as unknown as ItemTreeNodeData[];
|
|
153
|
+
const existingEndpoint = findByName(epChildren, endpointName);
|
|
154
|
+
if (existingEndpoint) {
|
|
155
|
+
endpointItem = {
|
|
156
|
+
id: existingEndpoint.id,
|
|
157
|
+
language: userLang,
|
|
158
|
+
version: 0,
|
|
159
|
+
} as ItemDescriptor;
|
|
160
|
+
} else {
|
|
161
|
+
const created = await editContext.operations.createItem(
|
|
162
|
+
{
|
|
163
|
+
id: aiEndpointsParent!.id,
|
|
164
|
+
language: userLang,
|
|
165
|
+
version: 0,
|
|
166
|
+
} as ItemDescriptor,
|
|
167
|
+
endpointTemplateId,
|
|
168
|
+
endpointName,
|
|
169
|
+
);
|
|
170
|
+
if (!created) throw new Error("Failed to create AI endpoint");
|
|
171
|
+
endpointItem = created;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Set API key if provided
|
|
175
|
+
if (apiKey && endpointItem) {
|
|
176
|
+
const apiKeyFieldId = "db07ae53-459a-4314-89b3-ab2721df2b4f";
|
|
177
|
+
await editContext.operations.editField({
|
|
178
|
+
field: {
|
|
179
|
+
fieldId: apiKeyFieldId,
|
|
180
|
+
item: {
|
|
181
|
+
id: endpointItem.id,
|
|
182
|
+
language: userLang,
|
|
183
|
+
version: 0,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
value: apiKey,
|
|
187
|
+
rawValue: apiKey,
|
|
188
|
+
refresh: "delayed",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Ensure Ai Models container exists
|
|
193
|
+
let aiModelsParent = findByName(children, "Ai Models");
|
|
194
|
+
if (!aiModelsParent) {
|
|
195
|
+
const aiModelsTemplateId = "e5f6a7b8-9c0d-1e2f-3a4b-5c6d7e8f9a0b";
|
|
196
|
+
const createdModels = await editContext.operations.createItem(
|
|
197
|
+
settingsItem,
|
|
198
|
+
aiModelsTemplateId,
|
|
199
|
+
"Ai Models",
|
|
200
|
+
);
|
|
201
|
+
if (!createdModels)
|
|
202
|
+
throw new Error("Failed to create 'Ai Models' container");
|
|
203
|
+
aiModelsParent = {
|
|
204
|
+
id: createdModels.id,
|
|
205
|
+
name: "Ai Models",
|
|
206
|
+
thumbUrl: "",
|
|
207
|
+
previewUrl: "",
|
|
208
|
+
templateId: "",
|
|
209
|
+
icon: "",
|
|
210
|
+
hasChildren: false,
|
|
211
|
+
isComponent: false,
|
|
212
|
+
displayName: "Ai Models",
|
|
213
|
+
language: userLang,
|
|
214
|
+
version: 0,
|
|
215
|
+
hasLayout: false,
|
|
216
|
+
} as unknown as ItemTreeNodeData;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Create default models if missing and link to endpoint
|
|
220
|
+
const modelsToEnsure = ["gpt-5", "gpt5-high", "image-1"];
|
|
221
|
+
const modelTemplateId = "d2e3f4a5-4b5c-5c6d-bf0b-3e4f5a6b7c8e";
|
|
222
|
+
const modelNameFieldId = "f1e2d3c4-5b6a-7980-8e9f-1a2b3c4d5e6f";
|
|
223
|
+
const modelEndpointFieldId = "cbade708-ce95-4f54-81b7-308ea61a76a6";
|
|
224
|
+
|
|
225
|
+
const modelChildren = (await getChildren(
|
|
226
|
+
aiModelsParent.id,
|
|
227
|
+
editContext.sessionId!,
|
|
228
|
+
[],
|
|
229
|
+
false,
|
|
230
|
+
userLang,
|
|
231
|
+
"path",
|
|
232
|
+
)) as unknown as ItemTreeNodeData[];
|
|
233
|
+
|
|
234
|
+
for (const modelName of modelsToEnsure) {
|
|
235
|
+
let modelNode = findByName(modelChildren, modelName);
|
|
236
|
+
if (!modelNode) {
|
|
237
|
+
const createdModel = await editContext.operations.createItem(
|
|
238
|
+
{
|
|
239
|
+
id: aiModelsParent.id,
|
|
240
|
+
language: userLang,
|
|
241
|
+
version: 0,
|
|
242
|
+
} as ItemDescriptor,
|
|
243
|
+
modelTemplateId,
|
|
244
|
+
modelName,
|
|
245
|
+
);
|
|
246
|
+
if (!createdModel) throw new Error(`Failed to create model '${modelName}'`);
|
|
247
|
+
modelNode = {
|
|
248
|
+
id: createdModel.id,
|
|
249
|
+
name: modelName,
|
|
250
|
+
displayName: modelName,
|
|
251
|
+
thumbUrl: "",
|
|
252
|
+
previewUrl: "",
|
|
253
|
+
templateId: "",
|
|
254
|
+
icon: "",
|
|
255
|
+
hasChildren: false,
|
|
256
|
+
isComponent: false,
|
|
257
|
+
language: userLang,
|
|
258
|
+
version: 0,
|
|
259
|
+
hasLayout: false,
|
|
260
|
+
} as unknown as ItemTreeNodeData;
|
|
261
|
+
|
|
262
|
+
await editContext.operations.editField({
|
|
263
|
+
field: {
|
|
264
|
+
fieldId: modelNameFieldId,
|
|
265
|
+
item: { id: modelNode.id, language: userLang, version: 0 },
|
|
266
|
+
},
|
|
267
|
+
value: modelName,
|
|
268
|
+
rawValue: modelName,
|
|
269
|
+
refresh: "delayed",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const endpointGuid = `{${(endpointItem!.id as string)
|
|
273
|
+
.replace(/[{}()]/g, "")
|
|
274
|
+
.toUpperCase()}}`;
|
|
275
|
+
await editContext.operations.editField({
|
|
276
|
+
field: {
|
|
277
|
+
fieldId: modelEndpointFieldId,
|
|
278
|
+
item: { id: modelNode.id, language: userLang, version: 0 },
|
|
279
|
+
},
|
|
280
|
+
rawValue: endpointGuid,
|
|
281
|
+
refresh: "delayed",
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
setAiState("success");
|
|
287
|
+
editContext.requestRefresh?.("immediate");
|
|
288
|
+
} catch (e: any) {
|
|
289
|
+
setAiState("error");
|
|
290
|
+
setAiError(e?.message || "Failed to add AI endpoint");
|
|
291
|
+
} finally {
|
|
292
|
+
setAiBusy(false);
|
|
293
|
+
}
|
|
294
|
+
}, [aiProvider, editContext, userLang, resolvePathUnderContent]);
|
|
295
|
+
|
|
296
|
+
const checkRequiredContainers = useCallback(async () => {
|
|
297
|
+
try {
|
|
298
|
+
setContainersError(null);
|
|
299
|
+
const initialStates: Record<string, StepState> = {};
|
|
300
|
+
for (const rc of requiredContainers) initialStates[rc.name] = "checking";
|
|
301
|
+
setContainerStates(initialStates);
|
|
302
|
+
|
|
303
|
+
const settingsItem = await resolvePathUnderContent(["Settings", "Editor"]);
|
|
304
|
+
if (!settingsItem) {
|
|
305
|
+
setEditorSettingsItem(null);
|
|
306
|
+
const errorMsg = "Editor settings item not found. Create it first.";
|
|
307
|
+
setContainersError(errorMsg);
|
|
308
|
+
const errorStates: Record<string, StepState> = {};
|
|
309
|
+
for (const rc of requiredContainers) errorStates[rc.name] = "error";
|
|
310
|
+
setContainerStates(errorStates);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
setEditorSettingsItem(settingsItem);
|
|
315
|
+
const children = (await getChildren(
|
|
316
|
+
settingsItem.id,
|
|
317
|
+
editContext!.sessionId!,
|
|
318
|
+
[],
|
|
319
|
+
false,
|
|
320
|
+
userLang,
|
|
321
|
+
"path",
|
|
322
|
+
)) as unknown as ItemTreeNodeData[];
|
|
323
|
+
|
|
324
|
+
const states: Record<string, StepState> = {};
|
|
325
|
+
for (const rc of requiredContainers) {
|
|
326
|
+
const exists = !!findByName(children, rc.name);
|
|
327
|
+
states[rc.name] = exists ? "success" : "error";
|
|
328
|
+
}
|
|
329
|
+
setContainerStates(states);
|
|
330
|
+
} catch (e: any) {
|
|
331
|
+
setContainersError(e?.message || "Failed to check AI containers");
|
|
332
|
+
const errorStates: Record<string, StepState> = {};
|
|
333
|
+
for (const rc of requiredContainers) errorStates[rc.name] = "error";
|
|
334
|
+
setContainerStates(errorStates);
|
|
335
|
+
}
|
|
336
|
+
}, [editContext, requiredContainers, resolvePathUnderContent, userLang]);
|
|
337
|
+
|
|
338
|
+
React.useEffect(() => {
|
|
339
|
+
checkRequiredContainers();
|
|
340
|
+
}, [checkRequiredContainers]);
|
|
341
|
+
|
|
342
|
+
const createContainer = useCallback(
|
|
343
|
+
async (name: string, templateId: string) => {
|
|
344
|
+
if (!editorSettingsItem || !editContext) return;
|
|
345
|
+
try {
|
|
346
|
+
setBusyMap((s) => ({ ...s, [name]: true }));
|
|
347
|
+
setContainersError(null);
|
|
348
|
+
|
|
349
|
+
const created = await editContext.operations.createItem(
|
|
350
|
+
editorSettingsItem,
|
|
351
|
+
templateId,
|
|
352
|
+
name,
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if (!created) throw new Error(`Failed to create '${name}'`);
|
|
356
|
+
setContainerStates((s) => ({ ...s, [name]: "success" }));
|
|
357
|
+
} catch (e: any) {
|
|
358
|
+
setContainersError(e?.message || `Failed to create '${name}'`);
|
|
359
|
+
setContainerStates((s) => ({ ...s, [name]: "error" }));
|
|
360
|
+
} finally {
|
|
361
|
+
setBusyMap((s) => ({ ...s, [name]: false }));
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
[editorSettingsItem, editContext],
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<Card
|
|
369
|
+
icon={<SparklesIcon strokeWidth={1} className="h-5 w-5" />}
|
|
370
|
+
title="AI setup"
|
|
371
|
+
description="Add an AI endpoint (OpenAI, Azure OpenAI, or OpenRouter)."
|
|
372
|
+
>
|
|
373
|
+
<div className="flex flex-col gap-2">
|
|
374
|
+
{requiredContainers
|
|
375
|
+
.filter((rc) => containerStates[rc.name] === "error")
|
|
376
|
+
.map((rc) => (
|
|
377
|
+
<div key={rc.name} className="flex items-center justify-between gap-2">
|
|
378
|
+
<div className="flex items-center gap-2">
|
|
379
|
+
{containerStates[rc.name] === "checking" ? (
|
|
380
|
+
<RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin text-amber-600" />
|
|
381
|
+
) : containerStates[rc.name] === "success" ? (
|
|
382
|
+
<CheckCircle strokeWidth={1} className="h-4 w-4 text-green-600" />
|
|
383
|
+
) : (
|
|
384
|
+
<AlertCircle strokeWidth={1} className="h-4 w-4 text-red-600" />
|
|
385
|
+
)}
|
|
386
|
+
<span className="text-sm text-gray-700">
|
|
387
|
+
{containerStates[rc.name] === "checking"
|
|
388
|
+
? `Checking ${rc.name}...`
|
|
389
|
+
: containerStates[rc.name] === "success"
|
|
390
|
+
? `${rc.name} present`
|
|
391
|
+
: `${rc.name} missing`}
|
|
392
|
+
</span>
|
|
393
|
+
</div>
|
|
394
|
+
{containerStates[rc.name] !== "success" && (
|
|
395
|
+
<Button
|
|
396
|
+
size="sm"
|
|
397
|
+
onClick={() => createContainer(rc.name, rc.templateId)}
|
|
398
|
+
disabled={!editorSettingsItem || !!busyMap[rc.name]}
|
|
399
|
+
>
|
|
400
|
+
{busyMap[rc.name] ? (
|
|
401
|
+
<RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
|
|
402
|
+
) : (
|
|
403
|
+
<CheckCircle strokeWidth={1} className="h-4 w-4" />
|
|
404
|
+
)}
|
|
405
|
+
Fix
|
|
406
|
+
</Button>
|
|
407
|
+
)}
|
|
408
|
+
</div>
|
|
409
|
+
))}
|
|
410
|
+
</div>
|
|
411
|
+
{containersError && (
|
|
412
|
+
<div className="mt-2 rounded border border-yellow-200 bg-yellow-50 p-2 text-xs whitespace-pre-wrap text-yellow-800">
|
|
413
|
+
{containersError}
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
<div className="flex items-center justify-between gap-2">
|
|
417
|
+
<div className="flex items-center gap-2">
|
|
418
|
+
<CheckCircle strokeWidth={1} className="h-4 w-4 text-green-600" />
|
|
419
|
+
<span className="text-sm text-gray-700">
|
|
420
|
+
Select provider and add endpoint
|
|
421
|
+
</span>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
<div className="mt-3 flex items-center gap-2">
|
|
425
|
+
<Select
|
|
426
|
+
value={aiProvider}
|
|
427
|
+
onValueChange={setAiProvider}
|
|
428
|
+
options={aiOptions}
|
|
429
|
+
placeholder="Choose provider"
|
|
430
|
+
/>
|
|
431
|
+
{aiProvider && (
|
|
432
|
+
<Input
|
|
433
|
+
type="password"
|
|
434
|
+
placeholder="API key"
|
|
435
|
+
value={apiKey}
|
|
436
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
437
|
+
className="w-56"
|
|
438
|
+
/>
|
|
439
|
+
)}
|
|
440
|
+
<Button
|
|
441
|
+
size="sm"
|
|
442
|
+
disabled={!aiProvider || !apiKey || aiBusy}
|
|
443
|
+
onClick={createAiEndpoint}
|
|
444
|
+
>
|
|
445
|
+
{aiBusy ? (
|
|
446
|
+
<RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
|
|
447
|
+
) : (
|
|
448
|
+
<CheckCircle strokeWidth={1} className="h-4 w-4" />
|
|
449
|
+
)}
|
|
450
|
+
Add endpoint
|
|
451
|
+
</Button>
|
|
452
|
+
</div>
|
|
453
|
+
{aiError && (
|
|
454
|
+
<div className="mt-2 rounded border border-red-200 bg-red-50 p-2 text-xs whitespace-pre-wrap text-red-700">
|
|
455
|
+
{aiError}
|
|
456
|
+
</div>
|
|
457
|
+
)}
|
|
458
|
+
</Card>
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export default AiSetupStep;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
import { Card } from "../../../components/ui/card";
|
|
3
|
+
import { Button } from "../../../components/ui/button";
|
|
4
|
+
import { get } from "../../services/serviceHelper";
|
|
5
|
+
import { CheckCircle, AlertCircle, Database, RefreshCw } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
type StepState = "idle" | "checking" | "success" | "error";
|
|
8
|
+
|
|
9
|
+
type SystemStatusResponse = {
|
|
10
|
+
messages?: { message: string; severity: "info" | "warning" | "error" }[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function DbSetupStep() {
|
|
14
|
+
const [dbState, setDbState] = useState<StepState>("checking");
|
|
15
|
+
const [dbError, setDbError] = useState<string | null>(null);
|
|
16
|
+
|
|
17
|
+
const statusIcon = useCallback((state: StepState) => {
|
|
18
|
+
if (state === "success")
|
|
19
|
+
return <CheckCircle className="h-4 w-4 text-green-600" strokeWidth={1} />;
|
|
20
|
+
if (state === "error")
|
|
21
|
+
return <AlertCircle className="h-4 w-4 text-red-600" strokeWidth={1} />;
|
|
22
|
+
return (
|
|
23
|
+
<RefreshCw
|
|
24
|
+
className="h-4 w-4 animate-spin text-amber-600"
|
|
25
|
+
strokeWidth={1}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const checkDb = useCallback(async () => {
|
|
31
|
+
try {
|
|
32
|
+
setDbState("checking");
|
|
33
|
+
setDbError(null);
|
|
34
|
+
const res = await get<SystemStatusResponse>("/alpaca/editor/status");
|
|
35
|
+
const errors =
|
|
36
|
+
res?.data?.messages?.filter((m) => m.severity === "error") || [];
|
|
37
|
+
if (errors.length > 0) {
|
|
38
|
+
setDbState("error");
|
|
39
|
+
setDbError(errors.map((x) => x.message).join("\n"));
|
|
40
|
+
} else {
|
|
41
|
+
setDbState("success");
|
|
42
|
+
}
|
|
43
|
+
} catch (e: any) {
|
|
44
|
+
setDbState("error");
|
|
45
|
+
setDbError(e?.message || "Failed to check DB status");
|
|
46
|
+
}
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
checkDb();
|
|
51
|
+
}, [checkDb]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Card
|
|
55
|
+
icon={<Database strokeWidth={1} className="h-5 w-5" />}
|
|
56
|
+
title="Database setup"
|
|
57
|
+
description="Verifies that all DB migrations ran successfully."
|
|
58
|
+
>
|
|
59
|
+
<div className="flex items-center justify-between">
|
|
60
|
+
<div className="flex items-center gap-2">
|
|
61
|
+
{statusIcon(dbState)}
|
|
62
|
+
<span className="text-sm text-gray-700">
|
|
63
|
+
{dbState === "success"
|
|
64
|
+
? "All migrations are applied"
|
|
65
|
+
: dbState === "error"
|
|
66
|
+
? "There are migration issues"
|
|
67
|
+
: "Checking..."}
|
|
68
|
+
</span>
|
|
69
|
+
</div>
|
|
70
|
+
<Button size="sm" variant="outline" onClick={checkDb}>
|
|
71
|
+
<RefreshCw strokeWidth={1} className="h-4 w-4" />
|
|
72
|
+
Recheck
|
|
73
|
+
</Button>
|
|
74
|
+
</div>
|
|
75
|
+
{dbError && (
|
|
76
|
+
<div className="mt-2 rounded border border-red-200 bg-red-50 p-2 text-xs whitespace-pre-wrap text-red-700">
|
|
77
|
+
{dbError}
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
</Card>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default DbSetupStep;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { Card } from "../../../components/ui/card";
|
|
3
|
+
import { Button } from "../../../components/ui/button";
|
|
4
|
+
import { Badge } from "../../../components/ui/badge";
|
|
5
|
+
import { ExternalLink, Link2 } from "lucide-react";
|
|
6
|
+
import { getCombinedIndexStatus } from "../../services/indexService";
|
|
7
|
+
import { useEditContext } from "../../client/editContext";
|
|
8
|
+
|
|
9
|
+
export function IndexSetupStep() {
|
|
10
|
+
const [indexCount, setIndexCount] = useState<number | null>(null);
|
|
11
|
+
const [indexLoading, setIndexLoading] = useState(false);
|
|
12
|
+
const editContext = useEditContext();
|
|
13
|
+
|
|
14
|
+
const goToIndexPanel = useCallback(() => {
|
|
15
|
+
editContext?.switchView("control-center");
|
|
16
|
+
editContext?.updateUrl({ ccpanel: "index" });
|
|
17
|
+
}, [editContext]);
|
|
18
|
+
|
|
19
|
+
const loadIndex = useCallback(async () => {
|
|
20
|
+
try {
|
|
21
|
+
setIndexLoading(true);
|
|
22
|
+
const data = await getCombinedIndexStatus();
|
|
23
|
+
const count = data?.indexStatus?.numberOfIndexedItems ?? null;
|
|
24
|
+
setIndexCount(typeof count === "number" ? count : null);
|
|
25
|
+
} finally {
|
|
26
|
+
setIndexLoading(false);
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
loadIndex();
|
|
32
|
+
}, [loadIndex]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Card
|
|
36
|
+
icon={<Link2 strokeWidth={1} className="h-5 w-5" />}
|
|
37
|
+
title="Search index"
|
|
38
|
+
description="Shows number of indexed items and links to Index panel."
|
|
39
|
+
>
|
|
40
|
+
<div className="flex items-center justify-between gap-2">
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
<Badge variant="outline">
|
|
43
|
+
Indexed Items:{" "}
|
|
44
|
+
{indexLoading ? "..." : (indexCount ?? 0).toLocaleString()}
|
|
45
|
+
</Badge>
|
|
46
|
+
</div>
|
|
47
|
+
<Button size="sm" variant="outline" onClick={goToIndexPanel}>
|
|
48
|
+
<ExternalLink strokeWidth={1} className="h-4 w-4" />
|
|
49
|
+
Open Index
|
|
50
|
+
</Button>
|
|
51
|
+
</div>
|
|
52
|
+
</Card>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default IndexSetupStep;
|