@parhelia/core 0.1.12325 → 0.1.12337
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/config/config.js +4 -27
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +2 -2
- package/dist/config/types.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +4 -2
- package/dist/editor/ai/AgentTerminal.js +86 -15
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +2 -1
- package/dist/editor/ai/AiResponseMessage.js +4 -4
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/dialogs/AgentDialogHandler.js +22 -4
- package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
- package/dist/editor/commands/templateBuilderCommands.d.ts +8 -0
- package/dist/editor/commands/templateBuilderCommands.js +51 -0
- package/dist/editor/commands/templateBuilderCommands.js.map +1 -0
- package/dist/editor/context-menu/InsertMenu.js +14 -1
- package/dist/editor/context-menu/InsertMenu.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +0 -1
- package/dist/editor/services/contentService.js +0 -9
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/templateBuilderService.d.ts +133 -0
- package/dist/editor/services/templateBuilderService.js +289 -0
- package/dist/editor/services/templateBuilderService.js.map +1 -0
- package/dist/editor/template-wizard/TemplateBuilderDialog.d.ts +11 -0
- package/dist/editor/template-wizard/TemplateBuilderDialog.js +17 -0
- package/dist/editor/template-wizard/TemplateBuilderDialog.js.map +1 -0
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.d.ts +16 -0
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js +1336 -0
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js.map +1 -0
- package/dist/editor/ui/Icons.js +1 -1
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/editor/views/ItemEditor.js +7 -3
- package/dist/editor/views/ItemEditor.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/task-board/TaskBoardWorkspace.js +3 -0
- package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
- package/dist/task-board/components/CreateTaskDialog.js +1 -0
- package/dist/task-board/components/CreateTaskDialog.js.map +1 -1
- package/dist/task-board/components/ItemCollectionPicker.js +5 -3
- package/dist/task-board/components/ItemCollectionPicker.js.map +1 -1
- package/dist/task-board/components/ProjectDashboard.d.ts +1 -0
- package/dist/task-board/components/ProjectDashboard.js +6 -2
- package/dist/task-board/components/ProjectDashboard.js.map +1 -1
- package/dist/task-board/components/ProjectSettingsDialog.js +7 -0
- package/dist/task-board/components/ProjectSettingsDialog.js.map +1 -1
- package/dist/task-board/components/TaskAttachmentsSection.d.ts +7 -0
- package/dist/task-board/components/TaskAttachmentsSection.js +137 -0
- package/dist/task-board/components/TaskAttachmentsSection.js.map +1 -0
- package/dist/task-board/components/TaskDetailPanel.js +6 -123
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/dist/task-board/components/TaskReviewActions.d.ts +1 -1
- package/dist/task-board/components/TaskReviewActions.js +2 -2
- package/dist/task-board/components/TaskReviewActions.js.map +1 -1
- package/dist/task-board/components/WizardCommunicationShared.js +11 -5
- package/dist/task-board/components/WizardCommunicationShared.js.map +1 -1
- package/dist/task-board/components/WizardTaskDetailsPanel.js +4 -2
- package/dist/task-board/components/WizardTaskDetailsPanel.js.map +1 -1
- package/dist/task-board/services/taskService.d.ts +1 -0
- package/dist/task-board/services/taskService.js.map +1 -1
- package/dist/task-board/taskExecutionStatus.js +12 -1
- package/dist/task-board/taskExecutionStatus.js.map +1 -1
- package/dist/task-board/taskStatus.js +3 -0
- package/dist/task-board/taskStatus.js.map +1 -1
- package/dist/task-board/types.d.ts +6 -2
- package/dist/task-board/views/KanbanView.js +2 -1
- package/dist/task-board/views/KanbanView.js.map +1 -1
- package/dist/task-board/views/ListView.js +1 -0
- package/dist/task-board/views/ListView.js.map +1 -1
- package/dist/task-board/views/WizardView.js +38 -33
- package/dist/task-board/views/WizardView.js.map +1 -1
- package/dist/tour/Tour.d.ts +1 -1
- package/dist/tour/Tour.js +80 -55
- package/dist/tour/Tour.js.map +1 -1
- package/dist/tour/default-tour.js +186 -80
- package/dist/tour/default-tour.js.map +1 -1
- package/package.json +1 -1
- package/dist/editor/services-server/graphQL.d.ts +0 -29
- package/dist/editor/services-server/graphQL.js +0 -53
- package/dist/editor/services-server/graphQL.js.map +0 -1
- package/dist/editor/sidebar/Debug.d.ts +0 -1
- package/dist/editor/sidebar/Debug.js +0 -70
- package/dist/editor/sidebar/Debug.js.map +0 -1
- package/dist/editor/sidebar/GraphQL.d.ts +0 -2
- package/dist/editor/sidebar/GraphQL.js +0 -234
- package/dist/editor/sidebar/GraphQL.js.map +0 -1
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Bot, Check, ChevronDown, ChevronRight, GripVertical, Plus, Search, Settings2, Trash2, X, } from "lucide-react";
|
|
5
|
+
import { AgentTerminal } from "../ai/AgentTerminal";
|
|
6
|
+
import { useEditContext } from "../client/editContext";
|
|
7
|
+
import TreeListSelector from "../ui/TreeListSelector";
|
|
8
|
+
import { parseTemplateBuilderSuggestionText, TEMPLATE_BUILDER_AGENT_PROFILE_ID, TEMPLATE_BUILDER_AGENT_PROFILE_NAME, TEMPLATE_BUILDER_SUGGESTION_SCHEMA_DESCRIPTION, templateBuilderCreate, templateBuilderFieldTypes, templateBuilderGet, templateBuilderUpdate, } from "../services/templateBuilderService";
|
|
9
|
+
import { loadAiProfiles } from "../services/aiService";
|
|
10
|
+
import { normalizeGuid } from "../utils";
|
|
11
|
+
const TEMPLATE_TEMPLATE_ID = "AB86861A-6030-46C5-B394-E8F99E8B87DB";
|
|
12
|
+
const TEMPLATES_ROOT_ID = "{3C1715FE-6A13-4FCF-845F-DE308BA9741D}";
|
|
13
|
+
const defaultFieldTypeOptions = [
|
|
14
|
+
"Single-Line Text",
|
|
15
|
+
"Multi-Line Text",
|
|
16
|
+
"Rich Text",
|
|
17
|
+
"Checkbox",
|
|
18
|
+
"Date",
|
|
19
|
+
"Datetime",
|
|
20
|
+
"Droplink",
|
|
21
|
+
"Droplist",
|
|
22
|
+
"Treelist",
|
|
23
|
+
"Image",
|
|
24
|
+
"General Link",
|
|
25
|
+
];
|
|
26
|
+
const normalizeName = (value) => (value || "").trim().toLowerCase();
|
|
27
|
+
const createGuid = () => {
|
|
28
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
29
|
+
return crypto.randomUUID();
|
|
30
|
+
}
|
|
31
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (character) => {
|
|
32
|
+
const random = Math.floor(Math.random() * 16);
|
|
33
|
+
const value = character === "x" ? random : (random & 0x3) | 0x8;
|
|
34
|
+
return value.toString(16);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const createLocalId = (prefix) => {
|
|
38
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
39
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
40
|
+
}
|
|
41
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
42
|
+
};
|
|
43
|
+
const createEmptyField = () => ({
|
|
44
|
+
localId: createLocalId("field"),
|
|
45
|
+
name: "",
|
|
46
|
+
type: "Single-Line Text",
|
|
47
|
+
source: "",
|
|
48
|
+
title: "",
|
|
49
|
+
shortDescription: "",
|
|
50
|
+
defaultValue: "",
|
|
51
|
+
sortOrder: "",
|
|
52
|
+
shared: false,
|
|
53
|
+
unversioned: false,
|
|
54
|
+
});
|
|
55
|
+
const toWizardField = (field) => ({
|
|
56
|
+
...createEmptyField(),
|
|
57
|
+
name: field?.name || "",
|
|
58
|
+
type: field?.type || "Single-Line Text",
|
|
59
|
+
source: field?.source || "",
|
|
60
|
+
title: field?.title || "",
|
|
61
|
+
shortDescription: field?.shortDescription || "",
|
|
62
|
+
defaultValue: field?.defaultValue || "",
|
|
63
|
+
sortOrder: field?.sortOrder || "",
|
|
64
|
+
shared: !!field?.shared,
|
|
65
|
+
unversioned: !!field?.unversioned,
|
|
66
|
+
});
|
|
67
|
+
const toWizardSection = (section) => ({
|
|
68
|
+
localId: createLocalId("section"),
|
|
69
|
+
name: section?.name || "",
|
|
70
|
+
fields: (section?.fields || []).map((field) => toWizardField(field)),
|
|
71
|
+
});
|
|
72
|
+
function toRequestSections(sections) {
|
|
73
|
+
return sections
|
|
74
|
+
.filter((section) => section.name.trim())
|
|
75
|
+
.map((section) => ({
|
|
76
|
+
name: section.name.trim(),
|
|
77
|
+
fields: section.fields
|
|
78
|
+
.filter((field) => field.name.trim())
|
|
79
|
+
.map((field, index) => ({
|
|
80
|
+
name: field.name.trim(),
|
|
81
|
+
type: field.type,
|
|
82
|
+
source: field.source?.trim() || undefined,
|
|
83
|
+
title: field.title?.trim() || undefined,
|
|
84
|
+
shortDescription: field.shortDescription?.trim() || undefined,
|
|
85
|
+
defaultValue: field.defaultValue || undefined,
|
|
86
|
+
sortOrder: String(index),
|
|
87
|
+
shared: field.shared,
|
|
88
|
+
unversioned: field.unversioned,
|
|
89
|
+
})),
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
export function TemplateStructureInlineEditor({ item, parentItem, initialName, initialBaseTemplates, onCreated, onClose, }) {
|
|
93
|
+
const editContext = useEditContext();
|
|
94
|
+
const editContextRef = useRef(editContext);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
editContextRef.current = editContext;
|
|
97
|
+
}, [editContext]);
|
|
98
|
+
const isCreateMode = !item && !!parentItem;
|
|
99
|
+
const activeLanguage = item?.language || parentItem?.language || "en";
|
|
100
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
101
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
102
|
+
const [fieldTypeGroups, setFieldTypeGroups] = useState([{ name: "General", types: defaultFieldTypeOptions }]);
|
|
103
|
+
const [templateName, setTemplateName] = useState(initialName || "New Template");
|
|
104
|
+
const [baseTemplates, setBaseTemplates] = useState([]);
|
|
105
|
+
const [selectedTemplateNodesInTree, setSelectedTemplateNodesInTree] = useState([]);
|
|
106
|
+
const [fieldSearchQuery, setFieldSearchQuery] = useState("");
|
|
107
|
+
const [hasStandardValues, setHasStandardValues] = useState(false);
|
|
108
|
+
const [sections, setSections] = useState([]);
|
|
109
|
+
const [newSectionName, setNewSectionName] = useState("");
|
|
110
|
+
const [newFieldDrafts, setNewFieldDrafts] = useState({});
|
|
111
|
+
const [selectedSectionIndex, setSelectedSectionIndex] = useState(0);
|
|
112
|
+
const [selectedFieldKey, setSelectedFieldKey] = useState(null);
|
|
113
|
+
const [collapsedSections, setCollapsedSections] = useState({});
|
|
114
|
+
const [draggedSectionIdx, setDraggedSectionIdx] = useState(null);
|
|
115
|
+
const [dragOverSectionIdx, setDragOverSectionIdx] = useState(null);
|
|
116
|
+
const [isDragOverSectionEnd, setIsDragOverSectionEnd] = useState(false);
|
|
117
|
+
const [sectionDeleteConfirmIdx, setSectionDeleteConfirmIdx] = useState(null);
|
|
118
|
+
const [draggedFieldRef, setDraggedFieldRef] = useState(null);
|
|
119
|
+
const [dragOverFieldRef, setDragOverFieldRef] = useState(null);
|
|
120
|
+
const [dragOverFieldEndSectionIdx, setDragOverFieldEndSectionIdx] = useState(null);
|
|
121
|
+
const [focusFieldDraftSectionIndex, setFocusFieldDraftSectionIndex] = useState(null);
|
|
122
|
+
const [aiProfiles, setAiProfiles] = useState([]);
|
|
123
|
+
const [pendingSuggestions, setPendingSuggestions] = useState([]);
|
|
124
|
+
const [latestSuggestionSummary, setLatestSuggestionSummary] = useState(null);
|
|
125
|
+
const [suggestionParseError, setSuggestionParseError] = useState(null);
|
|
126
|
+
const [suggestionStatus, setSuggestionStatus] = useState(null);
|
|
127
|
+
const newFieldNameInputRefs = useRef({});
|
|
128
|
+
const processedSuggestionMessageIdsRef = useRef(new Set());
|
|
129
|
+
const flatFieldTypeOptions = fieldTypeGroups.flatMap((group) => group.types || []);
|
|
130
|
+
const createEmptyFieldWithDefaultType = () => ({
|
|
131
|
+
...createEmptyField(),
|
|
132
|
+
type: flatFieldTypeOptions[0] || "Single-Line Text",
|
|
133
|
+
});
|
|
134
|
+
const createEmptySectionWithDefaultType = () => ({
|
|
135
|
+
localId: createLocalId("section"),
|
|
136
|
+
name: "",
|
|
137
|
+
fields: [createEmptyFieldWithDefaultType()],
|
|
138
|
+
});
|
|
139
|
+
const [templateBuilderAgent] = useState(() => ({
|
|
140
|
+
id: createGuid(),
|
|
141
|
+
name: "Template Builder",
|
|
142
|
+
userId: "",
|
|
143
|
+
updatedDate: new Date().toISOString(),
|
|
144
|
+
status: "new",
|
|
145
|
+
profileId: TEMPLATE_BUILDER_AGENT_PROFILE_ID,
|
|
146
|
+
profileName: TEMPLATE_BUILDER_AGENT_PROFILE_NAME,
|
|
147
|
+
}));
|
|
148
|
+
const getDefaultType = () => "Single-Line Text";
|
|
149
|
+
const getFieldDraft = (sectionIndex) => {
|
|
150
|
+
return (newFieldDrafts[sectionIndex] || {
|
|
151
|
+
...createEmptyField(),
|
|
152
|
+
type: getDefaultType(),
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
const updateFieldDraft = (sectionIndex, updates) => {
|
|
156
|
+
setNewFieldDrafts((prev) => ({
|
|
157
|
+
...prev,
|
|
158
|
+
[sectionIndex]: {
|
|
159
|
+
...(prev[sectionIndex] || {
|
|
160
|
+
...createEmptyField(),
|
|
161
|
+
type: getDefaultType(),
|
|
162
|
+
}),
|
|
163
|
+
...updates,
|
|
164
|
+
},
|
|
165
|
+
}));
|
|
166
|
+
};
|
|
167
|
+
const resetFieldDraft = (sectionIndex) => {
|
|
168
|
+
setNewFieldDrafts((prev) => ({
|
|
169
|
+
...prev,
|
|
170
|
+
[sectionIndex]: {
|
|
171
|
+
...createEmptyField(),
|
|
172
|
+
type: getDefaultType(),
|
|
173
|
+
},
|
|
174
|
+
}));
|
|
175
|
+
};
|
|
176
|
+
const hasDuplicateSectionName = (name, ignoreIndex) => {
|
|
177
|
+
const normalized = name.trim().toLowerCase();
|
|
178
|
+
if (!normalized)
|
|
179
|
+
return false;
|
|
180
|
+
return sections.some((section, index) => index !== ignoreIndex && section.name.trim().toLowerCase() === normalized);
|
|
181
|
+
};
|
|
182
|
+
const hasDuplicateFieldNameInSection = (sectionIndex, name, ignoreIndex) => {
|
|
183
|
+
const normalized = name.trim().toLowerCase();
|
|
184
|
+
if (!normalized)
|
|
185
|
+
return false;
|
|
186
|
+
const section = sections[sectionIndex];
|
|
187
|
+
if (!section)
|
|
188
|
+
return false;
|
|
189
|
+
return section.fields.some((field, index) => index !== ignoreIndex && field.name.trim().toLowerCase() === normalized);
|
|
190
|
+
};
|
|
191
|
+
const appendFieldToSection = (sectionIndex) => {
|
|
192
|
+
const draft = getFieldDraft(sectionIndex);
|
|
193
|
+
if (!draft.name.trim())
|
|
194
|
+
return;
|
|
195
|
+
if (hasDuplicateFieldNameInSection(sectionIndex, draft.name)) {
|
|
196
|
+
editContext?.showErrorToast({
|
|
197
|
+
summary: "Duplicate field name",
|
|
198
|
+
details: "Field names must be unique within a section.",
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
setSections((prev) => prev.map((section, idx) => idx !== sectionIndex
|
|
203
|
+
? section
|
|
204
|
+
: {
|
|
205
|
+
...section,
|
|
206
|
+
fields: [
|
|
207
|
+
...section.fields,
|
|
208
|
+
{ ...draft, localId: createLocalId("field"), name: draft.name.trim() },
|
|
209
|
+
],
|
|
210
|
+
}));
|
|
211
|
+
resetFieldDraft(sectionIndex);
|
|
212
|
+
};
|
|
213
|
+
const appendSection = () => {
|
|
214
|
+
if (!newSectionName.trim())
|
|
215
|
+
return;
|
|
216
|
+
if (hasDuplicateSectionName(newSectionName)) {
|
|
217
|
+
editContext?.showErrorToast({
|
|
218
|
+
summary: "Duplicate section name",
|
|
219
|
+
details: "Section names must be unique.",
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const nextSectionIndex = sections.length;
|
|
224
|
+
setSections((prev) => [
|
|
225
|
+
...prev,
|
|
226
|
+
{ localId: createLocalId("section"), name: newSectionName.trim(), fields: [] },
|
|
227
|
+
]);
|
|
228
|
+
setNewSectionName("");
|
|
229
|
+
setFocusFieldDraftSectionIndex(nextSectionIndex);
|
|
230
|
+
setSelectedSectionIndex(nextSectionIndex);
|
|
231
|
+
};
|
|
232
|
+
const updateSectionName = (sectionIndex, name) => {
|
|
233
|
+
if (name.trim() && hasDuplicateSectionName(name, sectionIndex)) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
setSections((prev) => prev.map((x, i) => (i === sectionIndex ? { ...x, name } : x)));
|
|
237
|
+
};
|
|
238
|
+
const removeSection = (sectionIndex) => {
|
|
239
|
+
setSections((prev) => prev.filter((_, i) => i !== sectionIndex));
|
|
240
|
+
setSelectedSectionIndex((prev) => Math.max(0, Math.min(prev, sectionIndex - 1)));
|
|
241
|
+
setSelectedFieldKey((prev) => {
|
|
242
|
+
if (!prev)
|
|
243
|
+
return prev;
|
|
244
|
+
if (prev.si === sectionIndex)
|
|
245
|
+
return null;
|
|
246
|
+
if (prev.si > sectionIndex)
|
|
247
|
+
return { ...prev, si: prev.si - 1 };
|
|
248
|
+
return prev;
|
|
249
|
+
});
|
|
250
|
+
setNewFieldDrafts((prev) => {
|
|
251
|
+
const next = {};
|
|
252
|
+
for (const [key, value] of Object.entries(prev)) {
|
|
253
|
+
const idx = Number.parseInt(key, 10);
|
|
254
|
+
if (Number.isNaN(idx) || idx === sectionIndex)
|
|
255
|
+
continue;
|
|
256
|
+
next[idx > sectionIndex ? idx - 1 : idx] = value;
|
|
257
|
+
}
|
|
258
|
+
return next;
|
|
259
|
+
});
|
|
260
|
+
setCollapsedSections((prev) => {
|
|
261
|
+
const next = {};
|
|
262
|
+
for (const [key, value] of Object.entries(prev)) {
|
|
263
|
+
const idx = Number.parseInt(key, 10);
|
|
264
|
+
if (Number.isNaN(idx) || idx === sectionIndex)
|
|
265
|
+
continue;
|
|
266
|
+
next[idx > sectionIndex ? idx - 1 : idx] = value;
|
|
267
|
+
}
|
|
268
|
+
return next;
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
const updateField = (sectionIndex, fieldIndex, updates) => {
|
|
272
|
+
if (updates.name !== undefined &&
|
|
273
|
+
updates.name.trim() &&
|
|
274
|
+
hasDuplicateFieldNameInSection(sectionIndex, updates.name, fieldIndex)) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
setSections((prev) => prev.map((s, si) => si !== sectionIndex
|
|
278
|
+
? s
|
|
279
|
+
: {
|
|
280
|
+
...s,
|
|
281
|
+
fields: s.fields.map((f, fi) => (fi === fieldIndex ? { ...f, ...updates } : f)),
|
|
282
|
+
}));
|
|
283
|
+
};
|
|
284
|
+
const removeField = (sectionIndex, fieldIndex) => {
|
|
285
|
+
setSections((prev) => prev.map((s, si) => si !== sectionIndex
|
|
286
|
+
? s
|
|
287
|
+
: {
|
|
288
|
+
...s,
|
|
289
|
+
fields: s.fields.filter((_, fi) => fi !== fieldIndex),
|
|
290
|
+
}));
|
|
291
|
+
};
|
|
292
|
+
const moveField = (sectionIndex, fromIdx, toIdx) => {
|
|
293
|
+
if (fromIdx === toIdx)
|
|
294
|
+
return;
|
|
295
|
+
setSections((prev) => prev.map((s, si) => {
|
|
296
|
+
if (si !== sectionIndex)
|
|
297
|
+
return s;
|
|
298
|
+
const newFields = [...s.fields];
|
|
299
|
+
const moved = newFields[fromIdx];
|
|
300
|
+
if (!moved)
|
|
301
|
+
return s;
|
|
302
|
+
newFields.splice(fromIdx, 1);
|
|
303
|
+
newFields.splice(toIdx, 0, moved);
|
|
304
|
+
return { ...s, fields: newFields };
|
|
305
|
+
}));
|
|
306
|
+
// Update selection if the moved field was selected
|
|
307
|
+
if (selectedFieldKey?.si === sectionIndex && selectedFieldKey.fi === fromIdx) {
|
|
308
|
+
setSelectedFieldKey({ si: sectionIndex, fi: toIdx });
|
|
309
|
+
}
|
|
310
|
+
else if (selectedFieldKey?.si === sectionIndex) {
|
|
311
|
+
// Adjust selection if other fields moved around it
|
|
312
|
+
let newFi = selectedFieldKey.fi;
|
|
313
|
+
if (fromIdx < newFi && toIdx >= newFi)
|
|
314
|
+
newFi--;
|
|
315
|
+
else if (fromIdx > newFi && toIdx <= newFi)
|
|
316
|
+
newFi++;
|
|
317
|
+
setSelectedFieldKey({ si: sectionIndex, fi: newFi });
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const moveSection = (fromIdx, toIdx) => {
|
|
321
|
+
if (fromIdx === toIdx)
|
|
322
|
+
return;
|
|
323
|
+
setSections((prev) => {
|
|
324
|
+
const next = [...prev];
|
|
325
|
+
const moved = next[fromIdx];
|
|
326
|
+
if (!moved)
|
|
327
|
+
return prev;
|
|
328
|
+
next.splice(fromIdx, 1);
|
|
329
|
+
next.splice(toIdx, 0, moved);
|
|
330
|
+
return next;
|
|
331
|
+
});
|
|
332
|
+
setSelectedSectionIndex((prev) => {
|
|
333
|
+
if (prev === fromIdx)
|
|
334
|
+
return toIdx;
|
|
335
|
+
if (fromIdx < prev && toIdx >= prev)
|
|
336
|
+
return prev - 1;
|
|
337
|
+
if (fromIdx > prev && toIdx <= prev)
|
|
338
|
+
return prev + 1;
|
|
339
|
+
return prev;
|
|
340
|
+
});
|
|
341
|
+
setSelectedFieldKey((prev) => {
|
|
342
|
+
if (!prev)
|
|
343
|
+
return prev;
|
|
344
|
+
if (prev.si === fromIdx)
|
|
345
|
+
return { ...prev, si: toIdx };
|
|
346
|
+
if (fromIdx < prev.si && toIdx >= prev.si)
|
|
347
|
+
return { ...prev, si: prev.si - 1 };
|
|
348
|
+
if (fromIdx > prev.si && toIdx <= prev.si)
|
|
349
|
+
return { ...prev, si: prev.si + 1 };
|
|
350
|
+
return prev;
|
|
351
|
+
});
|
|
352
|
+
setNewFieldDrafts((prev) => {
|
|
353
|
+
const next = {};
|
|
354
|
+
for (const [key, value] of Object.entries(prev)) {
|
|
355
|
+
const idx = Number.parseInt(key, 10);
|
|
356
|
+
if (Number.isNaN(idx))
|
|
357
|
+
continue;
|
|
358
|
+
let newIdx = idx;
|
|
359
|
+
if (idx === fromIdx)
|
|
360
|
+
newIdx = toIdx;
|
|
361
|
+
else if (fromIdx < idx && idx <= toIdx)
|
|
362
|
+
newIdx = idx - 1;
|
|
363
|
+
else if (toIdx <= idx && idx < fromIdx)
|
|
364
|
+
newIdx = idx + 1;
|
|
365
|
+
next[newIdx] = value;
|
|
366
|
+
}
|
|
367
|
+
return next;
|
|
368
|
+
});
|
|
369
|
+
setCollapsedSections((prev) => {
|
|
370
|
+
const next = {};
|
|
371
|
+
for (const [key, value] of Object.entries(prev)) {
|
|
372
|
+
const idx = Number.parseInt(key, 10);
|
|
373
|
+
if (Number.isNaN(idx))
|
|
374
|
+
continue;
|
|
375
|
+
let newIdx = idx;
|
|
376
|
+
if (idx === fromIdx)
|
|
377
|
+
newIdx = toIdx;
|
|
378
|
+
else if (fromIdx < idx && idx <= toIdx)
|
|
379
|
+
newIdx = idx - 1;
|
|
380
|
+
else if (toIdx <= idx && idx < fromIdx)
|
|
381
|
+
newIdx = idx + 1;
|
|
382
|
+
next[newIdx] = value;
|
|
383
|
+
}
|
|
384
|
+
return next;
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
const toggleSectionCollapsed = (sectionIndex) => {
|
|
388
|
+
setCollapsedSections((prev) => ({
|
|
389
|
+
...prev,
|
|
390
|
+
[sectionIndex]: !prev[sectionIndex],
|
|
391
|
+
}));
|
|
392
|
+
};
|
|
393
|
+
const moveFieldToSection = (fromSi, fromFi, toSi, toFi) => {
|
|
394
|
+
if (fromSi === toSi) {
|
|
395
|
+
moveField(fromSi, fromFi, toFi);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const movedField = sections[fromSi]?.fields[fromFi];
|
|
399
|
+
if (!movedField)
|
|
400
|
+
return;
|
|
401
|
+
if (movedField.name.trim() && hasDuplicateFieldNameInSection(toSi, movedField.name)) {
|
|
402
|
+
editContext?.showErrorToast({
|
|
403
|
+
summary: "Duplicate field name",
|
|
404
|
+
details: "Field names must be unique within a section.",
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const targetLength = sections[toSi]?.fields.length ?? 0;
|
|
409
|
+
const insertAt = Math.max(0, Math.min(toFi, targetLength));
|
|
410
|
+
setSections((prev) => {
|
|
411
|
+
const next = prev.map((s) => ({ ...s, fields: [...s.fields] }));
|
|
412
|
+
const source = next[fromSi];
|
|
413
|
+
const target = next[toSi];
|
|
414
|
+
if (!source || !target)
|
|
415
|
+
return prev;
|
|
416
|
+
const moved = source.fields[fromFi];
|
|
417
|
+
if (!moved)
|
|
418
|
+
return prev;
|
|
419
|
+
source.fields.splice(fromFi, 1);
|
|
420
|
+
target.fields.splice(insertAt, 0, moved);
|
|
421
|
+
return next;
|
|
422
|
+
});
|
|
423
|
+
setSelectedFieldKey((prev) => {
|
|
424
|
+
if (!prev)
|
|
425
|
+
return prev;
|
|
426
|
+
if (prev.si === fromSi && prev.fi === fromFi) {
|
|
427
|
+
return { si: toSi, fi: insertAt };
|
|
428
|
+
}
|
|
429
|
+
if (prev.si === fromSi && prev.fi > fromFi) {
|
|
430
|
+
return { ...prev, fi: prev.fi - 1 };
|
|
431
|
+
}
|
|
432
|
+
if (prev.si === toSi && prev.fi >= insertAt) {
|
|
433
|
+
return { ...prev, fi: prev.fi + 1 };
|
|
434
|
+
}
|
|
435
|
+
return prev;
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
const normalizedFieldSearchQuery = fieldSearchQuery.trim().toLowerCase();
|
|
439
|
+
const isSearching = normalizedFieldSearchQuery.length > 0;
|
|
440
|
+
const fieldMatchesSearch = (field) => {
|
|
441
|
+
if (!normalizedFieldSearchQuery)
|
|
442
|
+
return true;
|
|
443
|
+
return [
|
|
444
|
+
field.name,
|
|
445
|
+
field.title,
|
|
446
|
+
field.shortDescription,
|
|
447
|
+
field.type,
|
|
448
|
+
field.source,
|
|
449
|
+
]
|
|
450
|
+
.join(" ")
|
|
451
|
+
.toLowerCase()
|
|
452
|
+
.includes(normalizedFieldSearchQuery);
|
|
453
|
+
};
|
|
454
|
+
const sectionMatchesSearch = (sectionName) => {
|
|
455
|
+
if (!normalizedFieldSearchQuery)
|
|
456
|
+
return true;
|
|
457
|
+
return sectionName.toLowerCase().includes(normalizedFieldSearchQuery);
|
|
458
|
+
};
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
if (!isSearching)
|
|
461
|
+
return;
|
|
462
|
+
setDraggedSectionIdx(null);
|
|
463
|
+
setDragOverSectionIdx(null);
|
|
464
|
+
setIsDragOverSectionEnd(false);
|
|
465
|
+
setDraggedFieldRef(null);
|
|
466
|
+
setDragOverFieldRef(null);
|
|
467
|
+
setDragOverFieldEndSectionIdx(null);
|
|
468
|
+
}, [isSearching]);
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
if (focusFieldDraftSectionIndex == null)
|
|
471
|
+
return;
|
|
472
|
+
const fieldNameInput = newFieldNameInputRefs.current[focusFieldDraftSectionIndex];
|
|
473
|
+
if (!fieldNameInput)
|
|
474
|
+
return;
|
|
475
|
+
fieldNameInput.focus();
|
|
476
|
+
fieldNameInput.select();
|
|
477
|
+
setFocusFieldDraftSectionIndex(null);
|
|
478
|
+
}, [sections, focusFieldDraftSectionIndex]);
|
|
479
|
+
useEffect(() => {
|
|
480
|
+
if (sections.length === 0) {
|
|
481
|
+
setSelectedSectionIndex(0);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (selectedSectionIndex > sections.length - 1) {
|
|
485
|
+
setSelectedSectionIndex(sections.length - 1);
|
|
486
|
+
}
|
|
487
|
+
}, [sections, selectedSectionIndex]);
|
|
488
|
+
useEffect(() => {
|
|
489
|
+
const load = async () => {
|
|
490
|
+
setIsLoading(true);
|
|
491
|
+
setPendingSuggestions([]);
|
|
492
|
+
setLatestSuggestionSummary(null);
|
|
493
|
+
setSuggestionParseError(null);
|
|
494
|
+
setSuggestionStatus(null);
|
|
495
|
+
processedSuggestionMessageIdsRef.current = new Set();
|
|
496
|
+
try {
|
|
497
|
+
const liveFieldTypeGroups = await templateBuilderFieldTypes();
|
|
498
|
+
if (liveFieldTypeGroups.length > 0) {
|
|
499
|
+
setFieldTypeGroups(liveFieldTypeGroups);
|
|
500
|
+
}
|
|
501
|
+
if (isCreateMode) {
|
|
502
|
+
setTemplateName(initialName || "New Template");
|
|
503
|
+
setBaseTemplates(initialBaseTemplates || []);
|
|
504
|
+
setSelectedTemplateNodesInTree([]);
|
|
505
|
+
setHasStandardValues(false);
|
|
506
|
+
setSections([createEmptySectionWithDefaultType()]);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (!item) {
|
|
510
|
+
setSections([createEmptySectionWithDefaultType()]);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const result = await templateBuilderGet({ template: item.descriptor });
|
|
514
|
+
if (result.type !== "success" || !result.data) {
|
|
515
|
+
editContextRef.current?.showErrorToast({
|
|
516
|
+
summary: result.summary || "Failed to load template structure.",
|
|
517
|
+
details: result.details,
|
|
518
|
+
});
|
|
519
|
+
setSections([createEmptySectionWithDefaultType()]);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const loadedBaseTemplateIds = result.data.baseTemplateIds || [];
|
|
523
|
+
if (loadedBaseTemplateIds.length > 0) {
|
|
524
|
+
const loadedItems = await editContextRef.current.itemsRepository.getItems(loadedBaseTemplateIds.map((id) => ({
|
|
525
|
+
id,
|
|
526
|
+
language: item.language,
|
|
527
|
+
version: 0,
|
|
528
|
+
})));
|
|
529
|
+
setBaseTemplates(loadedItems.map((x) => ({
|
|
530
|
+
id: x.id,
|
|
531
|
+
name: x.name,
|
|
532
|
+
path: x.path,
|
|
533
|
+
icon: x.icon,
|
|
534
|
+
idPath: x.idPath,
|
|
535
|
+
})));
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
setBaseTemplates([]);
|
|
539
|
+
}
|
|
540
|
+
setHasStandardValues(!!result.data.hasStandardValues);
|
|
541
|
+
const parseSortOrder = (value) => {
|
|
542
|
+
const parsed = Number.parseInt(value || "", 10);
|
|
543
|
+
return Number.isFinite(parsed) ? parsed : Number.MAX_SAFE_INTEGER;
|
|
544
|
+
};
|
|
545
|
+
const loadedSections = result.data.sections?.map((section) => ({
|
|
546
|
+
localId: createLocalId("section"),
|
|
547
|
+
name: section.name || "",
|
|
548
|
+
fields: [...(section.fields || [])]
|
|
549
|
+
.sort((a, b) => parseSortOrder(a.sortOrder) - parseSortOrder(b.sortOrder))
|
|
550
|
+
.map((field) => ({
|
|
551
|
+
localId: createLocalId("field"),
|
|
552
|
+
name: field.name || "",
|
|
553
|
+
type: field.type ||
|
|
554
|
+
(liveFieldTypeGroups.flatMap((x) => x.types || [])[0] ||
|
|
555
|
+
defaultFieldTypeOptions[0] ||
|
|
556
|
+
"Single-Line Text"),
|
|
557
|
+
source: field.source || "",
|
|
558
|
+
title: field.title || "",
|
|
559
|
+
shortDescription: field.shortDescription || "",
|
|
560
|
+
defaultValue: field.defaultValue || "",
|
|
561
|
+
sortOrder: field.sortOrder || "",
|
|
562
|
+
shared: !!field.shared,
|
|
563
|
+
unversioned: !!field.unversioned,
|
|
564
|
+
})) || [createEmptyFieldWithDefaultType()],
|
|
565
|
+
})) || [];
|
|
566
|
+
setSections(loadedSections.length > 0
|
|
567
|
+
? loadedSections
|
|
568
|
+
: [createEmptySectionWithDefaultType()]);
|
|
569
|
+
}
|
|
570
|
+
finally {
|
|
571
|
+
setIsLoading(false);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
load();
|
|
575
|
+
}, [
|
|
576
|
+
initialBaseTemplates,
|
|
577
|
+
initialName,
|
|
578
|
+
isCreateMode,
|
|
579
|
+
item,
|
|
580
|
+
]);
|
|
581
|
+
const save = async () => {
|
|
582
|
+
if (isCreateMode && !templateName.trim()) {
|
|
583
|
+
editContext?.showErrorToast({
|
|
584
|
+
summary: "Template name is required",
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const normalizedSectionNames = new Set();
|
|
589
|
+
for (const section of sections) {
|
|
590
|
+
const sectionName = section.name.trim().toLowerCase();
|
|
591
|
+
if (!sectionName)
|
|
592
|
+
continue;
|
|
593
|
+
if (normalizedSectionNames.has(sectionName)) {
|
|
594
|
+
editContext?.showErrorToast({
|
|
595
|
+
summary: "Duplicate section names",
|
|
596
|
+
details: "Section names must be unique before saving.",
|
|
597
|
+
});
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
normalizedSectionNames.add(sectionName);
|
|
601
|
+
}
|
|
602
|
+
for (const section of sections) {
|
|
603
|
+
const normalizedFieldNames = new Set();
|
|
604
|
+
for (const field of section.fields) {
|
|
605
|
+
const fieldName = field.name.trim().toLowerCase();
|
|
606
|
+
if (!fieldName)
|
|
607
|
+
continue;
|
|
608
|
+
if (normalizedFieldNames.has(fieldName)) {
|
|
609
|
+
editContext?.showErrorToast({
|
|
610
|
+
summary: "Duplicate field names",
|
|
611
|
+
details: `Field names in section "${section.name || "Unnamed"}" must be unique before saving.`,
|
|
612
|
+
});
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
normalizedFieldNames.add(fieldName);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
setIsSaving(true);
|
|
619
|
+
try {
|
|
620
|
+
const baseTemplateIds = baseTemplates.map((x) => x.id);
|
|
621
|
+
if (isCreateMode) {
|
|
622
|
+
if (!parentItem) {
|
|
623
|
+
editContext?.showErrorToast({
|
|
624
|
+
summary: "Parent item is required",
|
|
625
|
+
});
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const result = await templateBuilderCreate({
|
|
629
|
+
parent: parentItem.descriptor,
|
|
630
|
+
name: templateName.trim(),
|
|
631
|
+
baseTemplateIds,
|
|
632
|
+
sections: toRequestSections(sections),
|
|
633
|
+
createStandardValues: hasStandardValues,
|
|
634
|
+
openStandardValues: false,
|
|
635
|
+
});
|
|
636
|
+
if (result.type !== "success") {
|
|
637
|
+
editContext?.showErrorToast({
|
|
638
|
+
summary: result.summary || "Failed to create template.",
|
|
639
|
+
details: result.details,
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (!result.data?.template) {
|
|
644
|
+
editContext?.showErrorToast({
|
|
645
|
+
summary: "Failed to create template.",
|
|
646
|
+
});
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (result.data.warnings?.length) {
|
|
650
|
+
editContext?.showInfoToast({
|
|
651
|
+
summary: "Template created with warnings",
|
|
652
|
+
details: result.data.warnings.join(" | "),
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
editContext?.showInfoToast({ summary: "Template created" });
|
|
657
|
+
}
|
|
658
|
+
await editContext?.itemsRepository.refreshItems([parentItem.descriptor], {
|
|
659
|
+
properties: true,
|
|
660
|
+
children: true,
|
|
661
|
+
});
|
|
662
|
+
editContext?.requestRefresh("immediate");
|
|
663
|
+
await editContext?.loadItem(result.data.template);
|
|
664
|
+
onCreated?.(result.data.template);
|
|
665
|
+
if (!onCreated)
|
|
666
|
+
onClose?.();
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (!item) {
|
|
670
|
+
editContext?.showErrorToast({
|
|
671
|
+
summary: "Template item is required",
|
|
672
|
+
});
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const result = await templateBuilderUpdate({
|
|
676
|
+
template: item.descriptor,
|
|
677
|
+
baseTemplateIds,
|
|
678
|
+
sections: toRequestSections(sections),
|
|
679
|
+
createStandardValues: hasStandardValues,
|
|
680
|
+
});
|
|
681
|
+
if (result.type !== "success") {
|
|
682
|
+
editContext?.showErrorToast({
|
|
683
|
+
summary: result.summary || "Failed to save template structure.",
|
|
684
|
+
details: result.details,
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (result.data?.warnings?.length) {
|
|
689
|
+
editContext?.showInfoToast({
|
|
690
|
+
summary: "Template updated with warnings",
|
|
691
|
+
details: result.data.warnings.join(" | "),
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
editContext?.showInfoToast({ summary: "Template structure saved" });
|
|
696
|
+
}
|
|
697
|
+
await editContext?.itemsRepository.refreshItems([item.descriptor], {
|
|
698
|
+
properties: true,
|
|
699
|
+
children: true,
|
|
700
|
+
});
|
|
701
|
+
editContext?.requestRefresh("immediate");
|
|
702
|
+
onClose?.();
|
|
703
|
+
}
|
|
704
|
+
finally {
|
|
705
|
+
setIsSaving(false);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const addBaseTemplates = (templatesToAdd) => {
|
|
709
|
+
if (templatesToAdd.length === 0)
|
|
710
|
+
return;
|
|
711
|
+
const merged = [...baseTemplates];
|
|
712
|
+
const existing = new Set(baseTemplates.map((x) => normalizeGuid(x.id)));
|
|
713
|
+
let blockedSelfInheritance = false;
|
|
714
|
+
for (const template of templatesToAdd) {
|
|
715
|
+
const key = normalizeGuid(template.id);
|
|
716
|
+
if (currentTemplateId && key === currentTemplateId) {
|
|
717
|
+
blockedSelfInheritance = true;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (!existing.has(key)) {
|
|
721
|
+
existing.add(key);
|
|
722
|
+
merged.push(template);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
setBaseTemplates(merged);
|
|
726
|
+
setSelectedTemplateNodesInTree([]);
|
|
727
|
+
if (blockedSelfInheritance) {
|
|
728
|
+
editContext?.showErrorToast({
|
|
729
|
+
summary: "A template cannot inherit from itself",
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
const removeBaseTemplates = (templatesToRemove) => {
|
|
734
|
+
if (templatesToRemove.length === 0)
|
|
735
|
+
return;
|
|
736
|
+
const idsToRemove = new Set(templatesToRemove.map((x) => normalizeGuid(x.id)));
|
|
737
|
+
setBaseTemplates((prev) => prev.filter((x) => !idsToRemove.has(normalizeGuid(x.id))));
|
|
738
|
+
};
|
|
739
|
+
useEffect(() => {
|
|
740
|
+
let cancelled = false;
|
|
741
|
+
loadAiProfiles()
|
|
742
|
+
.then((loadedProfiles) => {
|
|
743
|
+
if (cancelled)
|
|
744
|
+
return;
|
|
745
|
+
setAiProfiles(loadedProfiles);
|
|
746
|
+
})
|
|
747
|
+
.catch(() => {
|
|
748
|
+
if (!cancelled) {
|
|
749
|
+
setAiProfiles([]);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
return () => {
|
|
753
|
+
cancelled = true;
|
|
754
|
+
};
|
|
755
|
+
}, []);
|
|
756
|
+
const currentTemplateBuilderState = useMemo(() => ({
|
|
757
|
+
mode: isCreateMode ? "create" : "edit",
|
|
758
|
+
templateName: templateName.trim() || "New Template",
|
|
759
|
+
template: item?.descriptor,
|
|
760
|
+
parent: parentItem?.descriptor,
|
|
761
|
+
baseTemplates: baseTemplates.map((template) => ({
|
|
762
|
+
id: template.id,
|
|
763
|
+
name: template.name,
|
|
764
|
+
path: template.path,
|
|
765
|
+
icon: template.icon,
|
|
766
|
+
})),
|
|
767
|
+
sections: sections.map((sectionItem) => ({
|
|
768
|
+
name: sectionItem.name,
|
|
769
|
+
fields: sectionItem.fields.map((field) => ({
|
|
770
|
+
name: field.name,
|
|
771
|
+
type: field.type,
|
|
772
|
+
source: field.source || undefined,
|
|
773
|
+
title: field.title || undefined,
|
|
774
|
+
shortDescription: field.shortDescription || undefined,
|
|
775
|
+
defaultValue: field.defaultValue || undefined,
|
|
776
|
+
sortOrder: field.sortOrder || undefined,
|
|
777
|
+
shared: field.shared,
|
|
778
|
+
unversioned: field.unversioned,
|
|
779
|
+
})),
|
|
780
|
+
})),
|
|
781
|
+
}), [baseTemplates, isCreateMode, item, parentItem, sections, templateName]);
|
|
782
|
+
const currentTemplateId = useMemo(() => (item?.id ? normalizeGuid(item.id) : ""), [item?.id]);
|
|
783
|
+
const templateBuilderAgentMetadata = useMemo(() => ({
|
|
784
|
+
mode: "read-only",
|
|
785
|
+
profile: TEMPLATE_BUILDER_AGENT_PROFILE_NAME,
|
|
786
|
+
additionalData: {
|
|
787
|
+
profileId: TEMPLATE_BUILDER_AGENT_PROFILE_ID,
|
|
788
|
+
profileName: TEMPLATE_BUILDER_AGENT_PROFILE_NAME,
|
|
789
|
+
outputSchema: TEMPLATE_BUILDER_SUGGESTION_SCHEMA_DESCRIPTION,
|
|
790
|
+
templateBuilderState: currentTemplateBuilderState,
|
|
791
|
+
},
|
|
792
|
+
}), [currentTemplateBuilderState]);
|
|
793
|
+
const effectiveAiProfiles = useMemo(() => {
|
|
794
|
+
if (aiProfiles.some((profile) => normalizeGuid(profile.id) === normalizeGuid(TEMPLATE_BUILDER_AGENT_PROFILE_ID))) {
|
|
795
|
+
return aiProfiles;
|
|
796
|
+
}
|
|
797
|
+
return [
|
|
798
|
+
{
|
|
799
|
+
id: TEMPLATE_BUILDER_AGENT_PROFILE_ID,
|
|
800
|
+
name: TEMPLATE_BUILDER_AGENT_PROFILE_NAME,
|
|
801
|
+
defaultModelId: "",
|
|
802
|
+
models: [],
|
|
803
|
+
prompts: [],
|
|
804
|
+
hiddenFromOverview: true,
|
|
805
|
+
},
|
|
806
|
+
...aiProfiles,
|
|
807
|
+
];
|
|
808
|
+
}, [aiProfiles]);
|
|
809
|
+
const hydrateFieldFromPatch = useCallback((patch, fallback) => ({
|
|
810
|
+
localId: createLocalId("field"),
|
|
811
|
+
name: patch.name ?? fallback?.name ?? "",
|
|
812
|
+
type: patch.type ??
|
|
813
|
+
fallback?.type ??
|
|
814
|
+
flatFieldTypeOptions[0] ??
|
|
815
|
+
defaultFieldTypeOptions[0] ??
|
|
816
|
+
"Single-Line Text",
|
|
817
|
+
source: patch.source ?? fallback?.source ?? "",
|
|
818
|
+
title: patch.title ?? fallback?.title ?? "",
|
|
819
|
+
shortDescription: patch.shortDescription ?? fallback?.shortDescription ?? "",
|
|
820
|
+
defaultValue: patch.defaultValue ?? fallback?.defaultValue ?? "",
|
|
821
|
+
sortOrder: patch.sortOrder ?? fallback?.sortOrder ?? "",
|
|
822
|
+
shared: patch.shared ?? fallback?.shared ?? false,
|
|
823
|
+
unversioned: patch.unversioned ?? fallback?.unversioned ?? false,
|
|
824
|
+
}), [flatFieldTypeOptions]);
|
|
825
|
+
const convertSuggestionToPendingChanges = useCallback((suggestion) => {
|
|
826
|
+
const nextChanges = [];
|
|
827
|
+
for (const baseTemplateChange of suggestion.baseTemplates || []) {
|
|
828
|
+
nextChanges.push({
|
|
829
|
+
id: createLocalId("base-template-suggestion"),
|
|
830
|
+
kind: "baseTemplate",
|
|
831
|
+
action: baseTemplateChange.action,
|
|
832
|
+
templateId: baseTemplateChange.templateId,
|
|
833
|
+
templateName: baseTemplateChange.templateName,
|
|
834
|
+
templatePath: baseTemplateChange.templatePath,
|
|
835
|
+
reason: baseTemplateChange.reason,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
for (const sectionChange of suggestion.sections || []) {
|
|
839
|
+
if (sectionChange.action === "add") {
|
|
840
|
+
nextChanges.push({
|
|
841
|
+
id: createLocalId("section-suggestion"),
|
|
842
|
+
kind: "section",
|
|
843
|
+
action: "add",
|
|
844
|
+
section: {
|
|
845
|
+
...toWizardSection(sectionChange.section),
|
|
846
|
+
fields: (sectionChange.section.fields || []).map((field) => hydrateFieldFromPatch(field)),
|
|
847
|
+
},
|
|
848
|
+
reason: sectionChange.reason,
|
|
849
|
+
});
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const matchedSection = sections.find((sectionItem) => normalizeName(sectionItem.name) ===
|
|
853
|
+
normalizeName(sectionChange.targetSectionName));
|
|
854
|
+
if (!matchedSection)
|
|
855
|
+
continue;
|
|
856
|
+
if (sectionChange.action === "update") {
|
|
857
|
+
nextChanges.push({
|
|
858
|
+
id: createLocalId("section-suggestion"),
|
|
859
|
+
kind: "section",
|
|
860
|
+
action: "update",
|
|
861
|
+
section: {
|
|
862
|
+
...matchedSection,
|
|
863
|
+
name: sectionChange.section.name ?? matchedSection.name,
|
|
864
|
+
fields: matchedSection.fields,
|
|
865
|
+
},
|
|
866
|
+
targetSectionLocalId: matchedSection.localId,
|
|
867
|
+
targetSectionName: matchedSection.name,
|
|
868
|
+
reason: sectionChange.reason,
|
|
869
|
+
});
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
nextChanges.push({
|
|
873
|
+
id: createLocalId("section-suggestion"),
|
|
874
|
+
kind: "section",
|
|
875
|
+
action: "remove",
|
|
876
|
+
section: matchedSection,
|
|
877
|
+
targetSectionLocalId: matchedSection.localId,
|
|
878
|
+
targetSectionName: matchedSection.name,
|
|
879
|
+
reason: sectionChange.reason,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
for (const fieldChange of suggestion.fields || []) {
|
|
883
|
+
const matchedSection = sections.find((sectionItem) => normalizeName(sectionItem.name) === normalizeName(fieldChange.targetSectionName));
|
|
884
|
+
if (!matchedSection)
|
|
885
|
+
continue;
|
|
886
|
+
if (fieldChange.action === "add") {
|
|
887
|
+
nextChanges.push({
|
|
888
|
+
id: createLocalId("field-suggestion"),
|
|
889
|
+
kind: "field",
|
|
890
|
+
action: "add",
|
|
891
|
+
sectionLocalId: matchedSection.localId,
|
|
892
|
+
targetSectionName: matchedSection.name,
|
|
893
|
+
field: hydrateFieldFromPatch(fieldChange.field),
|
|
894
|
+
reason: fieldChange.reason,
|
|
895
|
+
});
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const matchedField = matchedSection.fields.find((fieldItem) => normalizeName(fieldItem.name) === normalizeName(fieldChange.targetFieldName));
|
|
899
|
+
if (!matchedField)
|
|
900
|
+
continue;
|
|
901
|
+
if (fieldChange.action === "update") {
|
|
902
|
+
nextChanges.push({
|
|
903
|
+
id: createLocalId("field-suggestion"),
|
|
904
|
+
kind: "field",
|
|
905
|
+
action: "update",
|
|
906
|
+
sectionLocalId: matchedSection.localId,
|
|
907
|
+
targetSectionName: matchedSection.name,
|
|
908
|
+
field: hydrateFieldFromPatch(fieldChange.field, matchedField),
|
|
909
|
+
targetFieldLocalId: matchedField.localId,
|
|
910
|
+
targetFieldName: matchedField.name,
|
|
911
|
+
reason: fieldChange.reason,
|
|
912
|
+
});
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
nextChanges.push({
|
|
916
|
+
id: createLocalId("field-suggestion"),
|
|
917
|
+
kind: "field",
|
|
918
|
+
action: "remove",
|
|
919
|
+
sectionLocalId: matchedSection.localId,
|
|
920
|
+
targetSectionName: matchedSection.name,
|
|
921
|
+
field: matchedField,
|
|
922
|
+
targetFieldLocalId: matchedField.localId,
|
|
923
|
+
targetFieldName: matchedField.name,
|
|
924
|
+
reason: fieldChange.reason,
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return nextChanges;
|
|
928
|
+
}, [hydrateFieldFromPatch, sections]);
|
|
929
|
+
const handleAgentMessage = useCallback((message) => {
|
|
930
|
+
if (!message.id ||
|
|
931
|
+
!message.isCompleted ||
|
|
932
|
+
message.role === "user" ||
|
|
933
|
+
message.messageType === "user" ||
|
|
934
|
+
processedSuggestionMessageIdsRef.current.has(message.id)) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
processedSuggestionMessageIdsRef.current.add(message.id);
|
|
938
|
+
const content = message.content?.trim();
|
|
939
|
+
if (!content)
|
|
940
|
+
return;
|
|
941
|
+
try {
|
|
942
|
+
const suggestion = parseTemplateBuilderSuggestionText(content);
|
|
943
|
+
if (!suggestion)
|
|
944
|
+
return;
|
|
945
|
+
const changes = convertSuggestionToPendingChanges(suggestion);
|
|
946
|
+
if (changes.length === 0)
|
|
947
|
+
return;
|
|
948
|
+
setPendingSuggestions((prev) => [...prev, ...changes]);
|
|
949
|
+
setLatestSuggestionSummary(suggestion.summary || null);
|
|
950
|
+
setSuggestionStatus(`${changes.length} AI suggestion${changes.length === 1 ? "" : "s"} ready for review.`);
|
|
951
|
+
setSuggestionParseError(null);
|
|
952
|
+
}
|
|
953
|
+
catch (error) {
|
|
954
|
+
setSuggestionParseError(error instanceof Error
|
|
955
|
+
? error.message
|
|
956
|
+
: "Failed to parse template builder suggestion.");
|
|
957
|
+
}
|
|
958
|
+
}, [convertSuggestionToPendingChanges]);
|
|
959
|
+
const clearSuggestion = useCallback((suggestionId) => {
|
|
960
|
+
setPendingSuggestions((prev) => prev.filter((entry) => entry.id !== suggestionId));
|
|
961
|
+
}, []);
|
|
962
|
+
const approveSuggestion = useCallback(async (suggestionId) => {
|
|
963
|
+
const change = pendingSuggestions.find((entry) => entry.id === suggestionId);
|
|
964
|
+
if (!change)
|
|
965
|
+
return;
|
|
966
|
+
if (change.kind === "baseTemplate") {
|
|
967
|
+
if (change.action === "remove") {
|
|
968
|
+
const matches = baseTemplates.filter((template) => (!!change.templateId &&
|
|
969
|
+
normalizeGuid(template.id) === normalizeGuid(change.templateId)) ||
|
|
970
|
+
normalizeName(template.name) === normalizeName(change.templateName) ||
|
|
971
|
+
(!!change.templatePath &&
|
|
972
|
+
normalizeName(template.path) === normalizeName(change.templatePath)));
|
|
973
|
+
removeBaseTemplates(matches);
|
|
974
|
+
clearSuggestion(suggestionId);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
let resolvedTemplate = selectedTemplateNodesInTree.find((template) => (!!change.templateId &&
|
|
978
|
+
normalizeGuid(template.id) === normalizeGuid(change.templateId)) ||
|
|
979
|
+
normalizeName(template.name) === normalizeName(change.templateName) ||
|
|
980
|
+
(!!change.templatePath &&
|
|
981
|
+
normalizeName(template.path) === normalizeName(change.templatePath))) || null;
|
|
982
|
+
if (!resolvedTemplate && change.templateId) {
|
|
983
|
+
const loadedItem = await editContext?.itemsRepository.getItem({
|
|
984
|
+
id: change.templateId,
|
|
985
|
+
language: activeLanguage,
|
|
986
|
+
version: 0,
|
|
987
|
+
});
|
|
988
|
+
if (loadedItem) {
|
|
989
|
+
resolvedTemplate = {
|
|
990
|
+
id: loadedItem.id,
|
|
991
|
+
name: loadedItem.name,
|
|
992
|
+
path: loadedItem.path,
|
|
993
|
+
icon: loadedItem.icon,
|
|
994
|
+
idPath: loadedItem.idPath,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (!resolvedTemplate) {
|
|
999
|
+
editContext?.showErrorToast({
|
|
1000
|
+
summary: "Unable to resolve suggested base template",
|
|
1001
|
+
details: "Ask the AI to include a concrete template id or select the template in the tree before approving.",
|
|
1002
|
+
});
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
addBaseTemplates([resolvedTemplate]);
|
|
1006
|
+
clearSuggestion(suggestionId);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if (change.kind === "section") {
|
|
1010
|
+
if (change.action === "add") {
|
|
1011
|
+
setSections((prev) => [...prev, toWizardSection(change.section)]);
|
|
1012
|
+
clearSuggestion(suggestionId);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const sectionIndex = sections.findIndex((sectionItem) => sectionItem.localId === change.targetSectionLocalId);
|
|
1016
|
+
if (sectionIndex === -1) {
|
|
1017
|
+
clearSuggestion(suggestionId);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (change.action === "remove") {
|
|
1021
|
+
removeSection(sectionIndex);
|
|
1022
|
+
clearSuggestion(suggestionId);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
updateSectionName(sectionIndex, change.section.name);
|
|
1026
|
+
clearSuggestion(suggestionId);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
const sectionIndex = sections.findIndex((sectionItem) => sectionItem.localId === change.sectionLocalId);
|
|
1030
|
+
if (sectionIndex === -1) {
|
|
1031
|
+
clearSuggestion(suggestionId);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (change.action === "add") {
|
|
1035
|
+
setSections((prev) => prev.map((sectionItem, idx) => idx !== sectionIndex
|
|
1036
|
+
? sectionItem
|
|
1037
|
+
: {
|
|
1038
|
+
...sectionItem,
|
|
1039
|
+
fields: [...sectionItem.fields, hydrateFieldFromPatch(change.field)],
|
|
1040
|
+
}));
|
|
1041
|
+
clearSuggestion(suggestionId);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const fieldIndex = sections[sectionIndex]?.fields.findIndex((fieldItem) => fieldItem.localId === change.targetFieldLocalId);
|
|
1045
|
+
if (fieldIndex === undefined || fieldIndex < 0) {
|
|
1046
|
+
clearSuggestion(suggestionId);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (change.action === "remove") {
|
|
1050
|
+
removeField(sectionIndex, fieldIndex);
|
|
1051
|
+
clearSuggestion(suggestionId);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
updateField(sectionIndex, fieldIndex, {
|
|
1055
|
+
name: change.field.name,
|
|
1056
|
+
type: change.field.type,
|
|
1057
|
+
source: change.field.source,
|
|
1058
|
+
title: change.field.title,
|
|
1059
|
+
shortDescription: change.field.shortDescription,
|
|
1060
|
+
defaultValue: change.field.defaultValue,
|
|
1061
|
+
sortOrder: change.field.sortOrder,
|
|
1062
|
+
shared: change.field.shared,
|
|
1063
|
+
unversioned: change.field.unversioned,
|
|
1064
|
+
});
|
|
1065
|
+
clearSuggestion(suggestionId);
|
|
1066
|
+
}, [
|
|
1067
|
+
activeLanguage,
|
|
1068
|
+
addBaseTemplates,
|
|
1069
|
+
baseTemplates,
|
|
1070
|
+
clearSuggestion,
|
|
1071
|
+
editContext,
|
|
1072
|
+
hydrateFieldFromPatch,
|
|
1073
|
+
removeBaseTemplates,
|
|
1074
|
+
removeField,
|
|
1075
|
+
removeSection,
|
|
1076
|
+
pendingSuggestions,
|
|
1077
|
+
sections,
|
|
1078
|
+
selectedTemplateNodesInTree,
|
|
1079
|
+
updateField,
|
|
1080
|
+
updateSectionName,
|
|
1081
|
+
]);
|
|
1082
|
+
const pendingSectionAdditions = pendingSuggestions.filter((entry) => entry.kind === "section" && entry.action === "add");
|
|
1083
|
+
const pendingBaseTemplateAdditions = pendingSuggestions.filter((entry) => entry.kind === "baseTemplate" && entry.action === "add");
|
|
1084
|
+
const getBaseTemplateSuggestions = useCallback((template) => pendingSuggestions.filter((entry) => entry.kind === "baseTemplate" &&
|
|
1085
|
+
((!!entry.templateId &&
|
|
1086
|
+
normalizeGuid(entry.templateId) === normalizeGuid(template.id)) ||
|
|
1087
|
+
normalizeName(entry.templateName) === normalizeName(template.name) ||
|
|
1088
|
+
(!!entry.templatePath &&
|
|
1089
|
+
normalizeName(entry.templatePath) === normalizeName(template.path)))), [pendingSuggestions]);
|
|
1090
|
+
const getSectionSuggestions = useCallback((sectionLocalId) => pendingSuggestions.filter((entry) => entry.kind === "section" &&
|
|
1091
|
+
entry.action !== "add" &&
|
|
1092
|
+
entry.targetSectionLocalId === sectionLocalId), [pendingSuggestions]);
|
|
1093
|
+
const getFieldSuggestions = useCallback((fieldLocalId) => pendingSuggestions.filter((entry) => entry.kind === "field" &&
|
|
1094
|
+
entry.action !== "add" &&
|
|
1095
|
+
entry.targetFieldLocalId === fieldLocalId), [pendingSuggestions]);
|
|
1096
|
+
const getFieldAdditionsForSection = useCallback((sectionLocalId) => pendingSuggestions.filter((entry) => entry.kind === "field" &&
|
|
1097
|
+
entry.action === "add" &&
|
|
1098
|
+
entry.sectionLocalId === sectionLocalId), [pendingSuggestions]);
|
|
1099
|
+
if (isLoading) {
|
|
1100
|
+
return _jsx("div", { className: "p-4 text-sm text-gray-500", children: "Loading template structure..." });
|
|
1101
|
+
}
|
|
1102
|
+
/* ────────────────────────────────────────────────────────────
|
|
1103
|
+
* Shared sub-renderers
|
|
1104
|
+
* ──────────────────────────────────────────────────────────── */
|
|
1105
|
+
const cls = {
|
|
1106
|
+
input: "w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm outline-none transition focus:border-blue-400 focus:ring-2 focus:ring-blue-100",
|
|
1107
|
+
select: "w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm outline-none transition focus:border-blue-400 focus:ring-2 focus:ring-blue-100",
|
|
1108
|
+
};
|
|
1109
|
+
const renderToggle = (checked, onClick) => (_jsx("button", { type: "button", role: "switch", "aria-checked": checked, className: `relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition ${checked ? "bg-blue-600" : "bg-gray-300"}`, onClick: onClick, children: _jsx("span", { className: `inline-block h-4 w-4 transform rounded-full bg-white transition ${checked ? "translate-x-4" : "translate-x-0.5"}` }) }));
|
|
1110
|
+
const renderTypeSelect = (value, onChange) => (_jsx("select", { className: cls.select, value: value, onChange: (e) => onChange(e.target.value), children: fieldTypeGroups.map((group) => (_jsx("optgroup", { label: group.name, children: (group.types || []).map((t) => (_jsx("option", { value: t, children: t }, `${group.name}-${t}`))) }, group.name))) }));
|
|
1111
|
+
const renderSuggestionActions = (change) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", className: "rounded border border-violet-200 bg-white px-2 py-0.5 text-[10px] font-semibold text-violet-700 transition hover:bg-violet-50", onClick: (event) => {
|
|
1112
|
+
event.stopPropagation();
|
|
1113
|
+
void approveSuggestion(change.id);
|
|
1114
|
+
}, children: "Approve" }), _jsx("button", { type: "button", className: "rounded border border-gray-200 bg-white px-2 py-0.5 text-[10px] font-semibold text-gray-600 transition hover:bg-gray-50", onClick: (event) => {
|
|
1115
|
+
event.stopPropagation();
|
|
1116
|
+
clearSuggestion(change.id);
|
|
1117
|
+
}, children: "Reject" })] }));
|
|
1118
|
+
const renderFooter = () => (_jsxs("div", { className: "mt-auto flex items-center justify-between border-t border-gray-200 px-4 pt-4 pb-1", children: [_jsxs("label", { className: "inline-flex items-center gap-2 text-[11px] text-gray-500 font-medium", children: ["Standard Values", renderToggle(hasStandardValues, () => setHasStandardValues((v) => !v))] }), _jsxs("div", { className: "flex items-center gap-2", children: [onClose && (_jsxs("button", { className: "inline-flex items-center gap-2 rounded border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-50", onClick: onClose, disabled: isSaving, children: [_jsx(X, { className: "h-4 w-4" }), "Cancel"] })), _jsxs("button", { className: "inline-flex items-center gap-2 rounded bg-blue-600 px-6 py-2.5 text-sm font-semibold text-white shadow-md transition-all hover:bg-blue-700 hover:shadow-lg active:scale-95 disabled:opacity-50", onClick: save, disabled: isSaving, children: [_jsx(Check, { className: "h-4 w-4" }), isSaving ? "Confirming..." : "Confirm"] })] })] }));
|
|
1119
|
+
/* ────────────────────────────────────────────────────────────
|
|
1120
|
+
* Studio Layout
|
|
1121
|
+
* Three-column: left = inheritance tree, center = field list,
|
|
1122
|
+
* right = property inspector for selected field
|
|
1123
|
+
* ──────────────────────────────────────────────────────────── */
|
|
1124
|
+
const selectedFieldRef = (() => {
|
|
1125
|
+
if (!selectedFieldKey)
|
|
1126
|
+
return null;
|
|
1127
|
+
const section = sections[selectedFieldKey.si];
|
|
1128
|
+
if (!section)
|
|
1129
|
+
return null;
|
|
1130
|
+
const field = section.fields[selectedFieldKey.fi];
|
|
1131
|
+
if (!field)
|
|
1132
|
+
return null;
|
|
1133
|
+
return {
|
|
1134
|
+
si: selectedFieldKey.si,
|
|
1135
|
+
fi: selectedFieldKey.fi,
|
|
1136
|
+
field,
|
|
1137
|
+
section,
|
|
1138
|
+
};
|
|
1139
|
+
})();
|
|
1140
|
+
const field = selectedFieldRef?.field;
|
|
1141
|
+
const section = selectedFieldRef?.section;
|
|
1142
|
+
const si = selectedFieldRef?.si;
|
|
1143
|
+
const fi = selectedFieldRef?.fi;
|
|
1144
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-lg border border-gray-200 bg-white text-gray-900", children: [isCreateMode && (_jsx("div", { className: "border-b border-gray-100 bg-gray-50/40 px-4 py-3", children: _jsxs("div", { className: "max-w-md", children: [_jsx("div", { className: "mb-1 text-[10px] font-bold uppercase tracking-wider text-gray-400", children: "Template name" }), _jsx("input", { className: cls.input, value: templateName, onChange: (e) => setTemplateName(e.target.value), placeholder: "New Template" })] }) })), _jsxs("div", { className: "grid min-h-0 flex-1 grid-cols-1 gap-0 lg:grid-cols-[240px_1fr_280px_420px]", children: [_jsx("div", { className: "flex flex-col gap-0 overflow-y-auto border-r border-gray-100 bg-gray-50/30", children: _jsxs("div", { className: "flex-1 overflow-y-auto p-3", children: [_jsx("div", { className: "mb-1.5 text-[10px] font-bold uppercase tracking-widest text-gray-400", children: "Template tree" }), _jsx("div", { className: "h-[240px] rounded border border-gray-200 bg-white shadow-inner", children: _jsx(TreeListSelector, { language: activeLanguage, rootItemIds: [TEMPLATES_ROOT_ID], selectedItemIds: selectedTemplateNodesInTree.map((x) => x.id), onSelectionChange: (nodes) => {
|
|
1145
|
+
const selected = nodes
|
|
1146
|
+
.filter((n) => normalizeGuid(n.templateId) === normalizeGuid(TEMPLATE_TEMPLATE_ID))
|
|
1147
|
+
.map((n) => ({ id: n.id, name: n.name, path: n.path, icon: n.icon, idPath: n.idPath }));
|
|
1148
|
+
setSelectedTemplateNodesInTree(selected);
|
|
1149
|
+
}, onDoubleClick: (node) => {
|
|
1150
|
+
if (normalizeGuid(node.templateId) === normalizeGuid(TEMPLATE_TEMPLATE_ID)) {
|
|
1151
|
+
addBaseTemplates([{ id: node.id, name: node.name, path: node.path, icon: node.icon, idPath: node.idPath }]);
|
|
1152
|
+
}
|
|
1153
|
+
}, onItemSelected: async (node) => {
|
|
1154
|
+
const sel = await editContext.itemsRepository.getItem({ id: node.id, language: activeLanguage, version: 0 });
|
|
1155
|
+
if (sel && normalizeGuid(sel.templateId) === normalizeGuid(TEMPLATE_TEMPLATE_ID)) {
|
|
1156
|
+
addBaseTemplates([{ id: sel.id, name: sel.name, path: sel.path, icon: sel.icon, idPath: sel.idPath }]);
|
|
1157
|
+
}
|
|
1158
|
+
} }) }), _jsxs("div", { className: "mt-3", children: [_jsx("div", { className: "mb-1.5 text-[10px] font-bold uppercase tracking-widest text-gray-400", children: "Inherited templates" }), baseTemplates.length === 0 ? (_jsx("div", { className: "text-[10px] text-gray-400 italic", children: "None" })) : (_jsxs("div", { className: "space-y-1", children: [baseTemplates.map((t) => {
|
|
1159
|
+
const templateSuggestions = getBaseTemplateSuggestions(t);
|
|
1160
|
+
const hasRemoveSuggestion = templateSuggestions.some((entry) => entry.action === "remove");
|
|
1161
|
+
return (_jsxs("div", { className: `space-y-1 rounded border px-2 py-1 text-[11px] ${hasRemoveSuggestion
|
|
1162
|
+
? "border-violet-200 bg-violet-50/80 text-violet-900"
|
|
1163
|
+
: "border-gray-200 bg-white text-gray-700"}`, children: [_jsxs("div", { className: "group flex items-center gap-1", children: [t.icon && _jsx("img", { src: t.icon, className: "h-3 w-3 shrink-0" }), _jsx("span", { className: `flex-1 truncate ${hasRemoveSuggestion ? "line-through" : ""}`, children: t.name }), _jsx("button", { className: "text-gray-400 transition hover:text-red-500", onClick: () => removeBaseTemplates([t]), children: _jsx(X, { className: "h-2.5 w-2.5" }) })] }), templateSuggestions.map((entry) => (_jsxs("div", { className: "flex items-center justify-between gap-2 rounded border border-violet-200 bg-white/80 px-2 py-1 text-[10px] text-violet-700", children: [_jsxs("span", { className: "min-w-0 flex-1 truncate", children: ["AI suggests ", entry.action === "remove" ? "removing" : "adding", " this base template", entry.reason ? ` · ${entry.reason}` : ""] }), renderSuggestionActions(entry)] }, entry.id)))] }, t.id));
|
|
1164
|
+
}), pendingBaseTemplateAdditions.map((entry) => (_jsx("div", { className: "rounded border border-dashed border-violet-300 bg-violet-50 px-2 py-1 text-[11px] text-violet-800", children: _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-semibold", children: entry.templateName }), _jsxs("div", { className: "truncate text-[10px] text-violet-600", children: ["AI suggestion", entry.reason ? ` · ${entry.reason}` : ""] })] }), renderSuggestionActions(entry)] }) }, entry.id)))] }))] })] }) }), _jsxs("div", { className: "flex flex-col overflow-y-auto bg-white", children: [_jsx("div", { className: "border-b border-gray-100 bg-gray-50/30 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-2 rounded border border-gray-200 bg-white px-2 py-1.5", children: [_jsx(Search, { className: "h-3.5 w-3.5 text-gray-400" }), _jsx("input", { className: "w-full bg-transparent text-xs text-gray-800 outline-none placeholder:text-gray-400", placeholder: "Search fields or sections...", value: fieldSearchQuery, onChange: (e) => setFieldSearchQuery(e.target.value) })] }) }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [sections.map((sectionItem, si_idx) => {
|
|
1165
|
+
const isSectionDragging = draggedSectionIdx === si_idx;
|
|
1166
|
+
const isSectionDragOver = dragOverSectionIdx === si_idx;
|
|
1167
|
+
const sectionSuggestions = getSectionSuggestions(sectionItem.localId);
|
|
1168
|
+
const pendingFieldAdditions = getFieldAdditionsForSection(sectionItem.localId);
|
|
1169
|
+
const sectionMatches = sectionMatchesSearch(sectionItem.name || "");
|
|
1170
|
+
const matchingFields = sectionItem.fields
|
|
1171
|
+
.map((fld, fi_idx) => ({ fld, fi_idx }))
|
|
1172
|
+
.filter(({ fld }) => fieldMatchesSearch(fld));
|
|
1173
|
+
const hasSearchMatch = sectionMatches || matchingFields.length > 0;
|
|
1174
|
+
const isCollapsed = isSearching ? !hasSearchMatch : !!collapsedSections[si_idx];
|
|
1175
|
+
const visibleFields = isSearching && sectionMatches
|
|
1176
|
+
? sectionItem.fields.map((fld, fi_idx) => ({ fld, fi_idx }))
|
|
1177
|
+
: matchingFields;
|
|
1178
|
+
return (_jsxs("div", { className: "border-b border-gray-100", children: [_jsxs("div", { draggable: !isSearching, onDragStart: (e) => {
|
|
1179
|
+
if (isSearching)
|
|
1180
|
+
return;
|
|
1181
|
+
setDraggedSectionIdx(si_idx);
|
|
1182
|
+
setSectionDeleteConfirmIdx(null);
|
|
1183
|
+
setIsDragOverSectionEnd(false);
|
|
1184
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1185
|
+
}, onDragEnd: () => {
|
|
1186
|
+
setDraggedSectionIdx(null);
|
|
1187
|
+
setDragOverSectionIdx(null);
|
|
1188
|
+
setIsDragOverSectionEnd(false);
|
|
1189
|
+
}, onDragOver: (e) => {
|
|
1190
|
+
if (isSearching)
|
|
1191
|
+
return;
|
|
1192
|
+
e.preventDefault();
|
|
1193
|
+
if (draggedSectionIdx === null)
|
|
1194
|
+
return;
|
|
1195
|
+
if (draggedSectionIdx === si_idx) {
|
|
1196
|
+
setDragOverSectionIdx(null);
|
|
1197
|
+
setIsDragOverSectionEnd(false);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
setDragOverSectionIdx(si_idx);
|
|
1201
|
+
setIsDragOverSectionEnd(si_idx === sections.length - 1);
|
|
1202
|
+
}, onDrop: (e) => {
|
|
1203
|
+
if (isSearching)
|
|
1204
|
+
return;
|
|
1205
|
+
e.preventDefault();
|
|
1206
|
+
if (draggedFieldRef) {
|
|
1207
|
+
moveFieldToSection(draggedFieldRef.si, draggedFieldRef.fi, si_idx, sectionItem.fields.length);
|
|
1208
|
+
setDraggedFieldRef(null);
|
|
1209
|
+
setDragOverFieldRef(null);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (draggedSectionIdx !== null && draggedSectionIdx !== si_idx) {
|
|
1213
|
+
moveSection(draggedSectionIdx, si_idx);
|
|
1214
|
+
}
|
|
1215
|
+
setDraggedSectionIdx(null);
|
|
1216
|
+
setDragOverSectionIdx(null);
|
|
1217
|
+
setIsDragOverSectionEnd(false);
|
|
1218
|
+
}, className: `group flex items-center gap-2 px-3 py-2 transition ${isSectionDragOver && draggedSectionIdx !== null
|
|
1219
|
+
? draggedSectionIdx < si_idx
|
|
1220
|
+
? "border-b-2 border-blue-500"
|
|
1221
|
+
: "border-t-2 border-blue-500"
|
|
1222
|
+
: ""} ${isSectionDragging ? "opacity-40" : ""} ${sectionSuggestions.length > 0 ? "bg-violet-50/70" : ""}`, onClick: () => {
|
|
1223
|
+
setSelectedSectionIndex(si_idx);
|
|
1224
|
+
setSelectedFieldKey(null);
|
|
1225
|
+
}, children: [_jsx(GripVertical, { className: "h-3.5 w-3.5 cursor-grab text-gray-300 active:cursor-grabbing" }), _jsx("button", { className: "text-gray-400 hover:text-gray-600", onClick: (e) => {
|
|
1226
|
+
e.stopPropagation();
|
|
1227
|
+
toggleSectionCollapsed(si_idx);
|
|
1228
|
+
}, children: isCollapsed ? (_jsx(ChevronRight, { className: "h-3.5 w-3.5" })) : (_jsx(ChevronDown, { className: "h-3.5 w-3.5" })) }), _jsx("input", { className: `flex-1 rounded-md border bg-white px-3 py-1.5 text-sm font-semibold shadow-sm outline-none placeholder:text-gray-400 focus:border-blue-400 focus:ring-2 focus:ring-blue-100 ${sectionSuggestions.length > 0
|
|
1229
|
+
? "border-violet-200 text-violet-900"
|
|
1230
|
+
: "border-gray-200 text-gray-900"}`, value: sectionItem.name || "", placeholder: `Section ${si_idx + 1}`, onChange: (e) => updateSectionName(si_idx, e.target.value), onClick: (e) => e.stopPropagation() }), _jsx("span", { className: "text-[10px] text-gray-400", children: sectionItem.fields.length }), _jsxs("div", { className: "relative h-5 w-12 shrink-0", children: [_jsx("div", { className: `absolute inset-0 flex items-center justify-center transition-all duration-150 ${sectionDeleteConfirmIdx === si_idx
|
|
1231
|
+
? "scale-90 opacity-0 pointer-events-none"
|
|
1232
|
+
: "scale-100 opacity-100"}`, children: _jsx("button", { className: "text-gray-300 transition hover:text-red-500", onClick: (e) => {
|
|
1233
|
+
e.stopPropagation();
|
|
1234
|
+
setSectionDeleteConfirmIdx(si_idx);
|
|
1235
|
+
}, title: "Delete section", "aria-label": "Delete section", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) }), _jsxs("div", { className: `absolute inset-0 flex items-center justify-center gap-1 transition-all duration-150 ${sectionDeleteConfirmIdx === si_idx
|
|
1236
|
+
? "scale-100 opacity-100"
|
|
1237
|
+
: "scale-90 opacity-0 pointer-events-none"}`, children: [_jsx("button", { className: "text-green-600 transition hover:text-green-700", onClick: (e) => {
|
|
1238
|
+
e.stopPropagation();
|
|
1239
|
+
removeSection(si_idx);
|
|
1240
|
+
setSectionDeleteConfirmIdx(null);
|
|
1241
|
+
}, title: "Confirm section delete", "aria-label": "Confirm section delete", children: _jsx(Check, { className: "h-3.5 w-3.5" }) }), _jsx("button", { className: "text-red-500 transition hover:text-red-600", onClick: (e) => {
|
|
1242
|
+
e.stopPropagation();
|
|
1243
|
+
setSectionDeleteConfirmIdx(null);
|
|
1244
|
+
}, title: "Cancel section delete", "aria-label": "Cancel section delete", children: _jsx(X, { className: "h-3.5 w-3.5" }) })] })] })] }), sectionSuggestions.length > 0 && (_jsx("div", { className: "mx-3 mb-2 rounded border border-violet-200 bg-violet-50 px-3 py-2 text-[11px] text-violet-800", children: sectionSuggestions.map((entry) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-1", children: [_jsxs("span", { className: "min-w-0 flex-1 truncate", children: [entry.action === "remove"
|
|
1245
|
+
? "AI suggests removing this section"
|
|
1246
|
+
: `AI suggests renaming this section to "${entry.section.name}"`, entry.reason ? ` · ${entry.reason}` : ""] }), renderSuggestionActions(entry)] }, entry.id))) })), !isCollapsed && (_jsxs("div", { className: "pb-2", children: [_jsxs("div", { className: "space-y-1 px-3", children: [visibleFields.map(({ fld, fi_idx }) => {
|
|
1247
|
+
const isSelected = selectedFieldKey?.si === si_idx && selectedFieldKey?.fi === fi_idx;
|
|
1248
|
+
const isDragging = draggedFieldRef?.si === si_idx && draggedFieldRef?.fi === fi_idx;
|
|
1249
|
+
const isDragOver = dragOverFieldRef?.si === si_idx && dragOverFieldRef?.fi === fi_idx;
|
|
1250
|
+
const fieldSuggestions = getFieldSuggestions(fld.localId);
|
|
1251
|
+
const hasFieldRemovalSuggestion = fieldSuggestions.some((entry) => entry.action === "remove");
|
|
1252
|
+
const fieldUpdateSuggestion = fieldSuggestions.find((entry) => entry.action === "update");
|
|
1253
|
+
return (_jsxs("div", { draggable: !isSearching, onDragStart: (e) => {
|
|
1254
|
+
if (isSearching)
|
|
1255
|
+
return;
|
|
1256
|
+
setDraggedFieldRef({ si: si_idx, fi: fi_idx });
|
|
1257
|
+
setDragOverFieldRef(null);
|
|
1258
|
+
setDragOverFieldEndSectionIdx(null);
|
|
1259
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1260
|
+
}, onDragEnd: () => {
|
|
1261
|
+
setDraggedFieldRef(null);
|
|
1262
|
+
setDragOverFieldRef(null);
|
|
1263
|
+
setDragOverFieldEndSectionIdx(null);
|
|
1264
|
+
}, onDragOver: (e) => {
|
|
1265
|
+
if (isSearching)
|
|
1266
|
+
return;
|
|
1267
|
+
e.preventDefault();
|
|
1268
|
+
if (!draggedFieldRef)
|
|
1269
|
+
return;
|
|
1270
|
+
const isOwnRow = draggedFieldRef.si === si_idx && draggedFieldRef.fi === fi_idx;
|
|
1271
|
+
if (isOwnRow) {
|
|
1272
|
+
setDragOverFieldRef(null);
|
|
1273
|
+
setDragOverFieldEndSectionIdx(null);
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
setDragOverFieldRef({ si: si_idx, fi: fi_idx });
|
|
1277
|
+
setDragOverFieldEndSectionIdx(null);
|
|
1278
|
+
}, onDrop: (e) => {
|
|
1279
|
+
if (isSearching)
|
|
1280
|
+
return;
|
|
1281
|
+
e.preventDefault();
|
|
1282
|
+
if (draggedFieldRef &&
|
|
1283
|
+
(draggedFieldRef.si !== si_idx || draggedFieldRef.fi !== fi_idx)) {
|
|
1284
|
+
moveFieldToSection(draggedFieldRef.si, draggedFieldRef.fi, si_idx, fi_idx);
|
|
1285
|
+
}
|
|
1286
|
+
setDraggedFieldRef(null);
|
|
1287
|
+
setDragOverFieldRef(null);
|
|
1288
|
+
setDragOverFieldEndSectionIdx(null);
|
|
1289
|
+
}, className: `ml-8 flex cursor-pointer items-center gap-3 rounded px-3 py-2 transition ${isSelected
|
|
1290
|
+
? "bg-blue-50/70 ring-1 ring-inset ring-blue-200"
|
|
1291
|
+
: "hover:bg-gray-50"} ${isDragging ? "bg-gray-100 opacity-30" : ""} ${isDragOver && draggedFieldRef
|
|
1292
|
+
? draggedFieldRef.si === si_idx && draggedFieldRef.fi < fi_idx
|
|
1293
|
+
? "shadow-[inset_0_-2px_0_0_#3b82f6]"
|
|
1294
|
+
: "shadow-[inset_0_2px_0_0_#3b82f6]"
|
|
1295
|
+
: ""} ${fieldSuggestions.length > 0 ? "bg-violet-50/60 ring-1 ring-violet-200" : ""} ${hasFieldRemovalSuggestion ? "opacity-80" : ""}`, onClick: () => setSelectedFieldKey({ si: si_idx, fi: fi_idx }), children: [_jsx(GripVertical, { className: "h-3.5 w-3.5 cursor-grab text-gray-300 active:cursor-grabbing" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: `text-sm font-semibold ${fieldSuggestions.length > 0
|
|
1296
|
+
? "text-violet-800"
|
|
1297
|
+
: isSelected
|
|
1298
|
+
? "text-blue-700"
|
|
1299
|
+
: "text-gray-700"} ${hasFieldRemovalSuggestion ? "line-through" : ""}`, children: fieldUpdateSuggestion?.field.name ||
|
|
1300
|
+
fld.name || (_jsx("span", { className: "font-normal italic text-gray-400", children: "Unnamed" })) }), _jsxs("div", { className: "text-[11px] text-gray-500", children: [fld.type, fld.source ? ` · ${fld.source}` : ""] }), fieldSuggestions.map((entry) => (_jsxs("div", { className: "mt-1 flex items-center justify-between gap-2 text-[10px] text-violet-700", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.action === "remove"
|
|
1301
|
+
? "AI suggests removing this field"
|
|
1302
|
+
: `AI suggests updating this field${entry.reason ? ` · ${entry.reason}` : ""}` }), renderSuggestionActions(entry)] }, entry.id)))] }), _jsxs("div", { className: "flex items-center gap-2", children: [fld.shared && _jsx("span", { className: "rounded border border-blue-100 bg-blue-50 px-1.5 py-0.5 text-[10px] font-bold text-blue-600", children: "S" }), fld.unversioned && _jsx("span", { className: "rounded border border-amber-100 bg-amber-50 px-1.5 py-0.5 text-[10px] font-bold text-amber-600", children: "U" })] }), _jsx("button", { className: "text-gray-300 transition hover:text-red-500", onClick: (e) => { e.stopPropagation(); removeField(si_idx, fi_idx); }, children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }, fld.localId));
|
|
1303
|
+
}), visibleFields.length === 0 && normalizedFieldSearchQuery ? (_jsx("div", { className: "ml-8 px-3 py-2 text-[11px] text-gray-400 italic", children: "No matching fields" })) : null] }), !isSearching &&
|
|
1304
|
+
pendingFieldAdditions.map((entry) => (_jsx("div", { className: "mt-2 mr-3 ml-11 rounded border border-dashed border-violet-300 bg-violet-50 px-3 py-2 text-[11px] text-violet-800", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate font-semibold", children: entry.field.name || "Suggested field" }), _jsxs("div", { className: "truncate text-[10px] text-violet-600", children: [entry.field.type, entry.reason ? ` · ${entry.reason}` : ""] })] }), renderSuggestionActions(entry)] }) }, entry.id))), !isSearching && draggedFieldRef ? (_jsx("div", { className: `mt-2 mr-3 ml-11 rounded border border-dashed px-2 py-1.5 text-[10px] transition ${dragOverFieldEndSectionIdx === si_idx
|
|
1305
|
+
? "border-blue-300 bg-blue-50 text-blue-600"
|
|
1306
|
+
: "border-gray-200 bg-white text-gray-400"}`, onDragOver: (e) => {
|
|
1307
|
+
e.preventDefault();
|
|
1308
|
+
setDragOverFieldRef(null);
|
|
1309
|
+
setDragOverFieldEndSectionIdx(si_idx);
|
|
1310
|
+
}, onDrop: (e) => {
|
|
1311
|
+
e.preventDefault();
|
|
1312
|
+
moveFieldToSection(draggedFieldRef.si, draggedFieldRef.fi, si_idx, sectionItem.fields.length);
|
|
1313
|
+
setDraggedFieldRef(null);
|
|
1314
|
+
setDragOverFieldRef(null);
|
|
1315
|
+
setDragOverFieldEndSectionIdx(null);
|
|
1316
|
+
}, children: "Drop field here to place at end" })) : !isSearching ? (_jsxs("div", { className: "mt-2 mr-3 ml-11 flex items-center gap-2", children: [_jsx("input", { ref: (el) => { newFieldNameInputRefs.current[si_idx] = el; }, className: "flex-1 rounded border border-gray-200 bg-white px-2 py-1.5 text-xs text-gray-800 outline-none placeholder:text-gray-400 focus:border-blue-400", placeholder: "Add field...", value: getFieldDraft(si_idx).name, onChange: (e) => updateFieldDraft(si_idx, { name: e.target.value }), onKeyDown: (e) => { if (e.key === "Enter")
|
|
1317
|
+
appendFieldToSection(si_idx); } }), _jsx("button", { className: "rounded bg-blue-600 p-1.5 text-white transition hover:bg-blue-700 shadow-sm", onClick: () => appendFieldToSection(si_idx), children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })) : null] }))] }, sectionItem.localId));
|
|
1318
|
+
}), !isSearching &&
|
|
1319
|
+
pendingSectionAdditions.map((entry) => (_jsx("div", { className: "mx-3 mt-2 rounded border border-dashed border-violet-300 bg-violet-50 px-3 py-3 text-violet-900", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-semibold", children: entry.section.name || "Suggested section" }), _jsxs("div", { className: "text-[11px] text-violet-700", children: ["AI section suggestion", entry.reason ? ` · ${entry.reason}` : ""] }), entry.section.fields.length > 0 && (_jsx("div", { className: "mt-2 space-y-1", children: entry.section.fields.map((field) => (_jsxs("div", { className: "rounded border border-violet-200 bg-white/70 px-2 py-1 text-[10px] text-violet-700", children: [field.name || "Suggested field", " \u00B7 ", field.type] }, field.localId))) }))] }), renderSuggestionActions(entry)] }) }, entry.id))), !isSearching && draggedSectionIdx !== null ? (_jsx("div", { className: `mx-3 mt-2 mb-3 rounded border border-dashed px-2 py-1.5 text-[10px] transition ${isDragOverSectionEnd
|
|
1320
|
+
? "border-blue-300 bg-blue-50 text-blue-600"
|
|
1321
|
+
: "border-gray-200 bg-white text-gray-400"}`, onDragOver: (e) => {
|
|
1322
|
+
e.preventDefault();
|
|
1323
|
+
setDragOverSectionIdx(sections.length > 0 ? sections.length - 1 : null);
|
|
1324
|
+
setIsDragOverSectionEnd(true);
|
|
1325
|
+
}, onDrop: (e) => {
|
|
1326
|
+
e.preventDefault();
|
|
1327
|
+
if (draggedSectionIdx !== null) {
|
|
1328
|
+
moveSection(draggedSectionIdx, sections.length - 1);
|
|
1329
|
+
}
|
|
1330
|
+
setDraggedSectionIdx(null);
|
|
1331
|
+
setDragOverSectionIdx(null);
|
|
1332
|
+
setIsDragOverSectionEnd(false);
|
|
1333
|
+
}, children: "Drop section here to place at end" })) : !isSearching ? (_jsxs("div", { className: "mx-3 mt-2 mb-3 flex items-center gap-2", children: [_jsx("input", { className: "flex-1 rounded border border-gray-200 bg-white px-2 py-1 text-xs text-gray-800 outline-none placeholder:text-gray-400 focus:border-blue-400", placeholder: "New section...", value: newSectionName, onChange: (e) => setNewSectionName(e.target.value), onKeyDown: (e) => { if (e.key === "Enter")
|
|
1334
|
+
appendSection(); } }), _jsx("button", { className: "rounded bg-blue-600 p-1 text-white transition hover:bg-blue-700 shadow-sm", onClick: appendSection, children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })) : null] })] }), _jsxs("div", { className: "flex flex-col overflow-y-auto border-l border-gray-100 bg-gray-50/20", children: [_jsx("div", { className: "border-b border-gray-100 px-4 py-2 bg-gray-50/30", children: _jsxs("div", { className: "flex items-center gap-2 text-xs font-bold text-gray-500 uppercase tracking-wider", children: [_jsx(Settings2, { className: "h-3.5 w-3.5" }), " Properties"] }) }), field && section && si !== undefined && fi !== undefined ? (_jsxs("div", { className: "space-y-4 p-4", children: [_jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[10px] font-bold uppercase tracking-wider text-gray-400", children: "Name" }), _jsx("input", { className: cls.input, value: field.name, onChange: (e) => updateField(si, fi, { name: e.target.value }) })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[10px] font-bold uppercase tracking-wider text-gray-400", children: "Type" }), renderTypeSelect(field.type, (v) => updateField(si, fi, { type: v }))] }), _jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[10px] font-bold uppercase tracking-wider text-gray-400", children: "Source" }), _jsx("input", { className: cls.input, placeholder: "Optional", value: field.source, onChange: (e) => updateField(si, fi, { source: e.target.value }) })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[10px] font-bold uppercase tracking-wider text-gray-400", children: "Short description" }), _jsx("input", { className: cls.input, placeholder: "Optional", value: field.shortDescription, onChange: (e) => updateField(si, fi, { shortDescription: e.target.value }) })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[10px] font-bold uppercase tracking-wider text-gray-400", children: "Default value" }), _jsx("input", { className: cls.input, placeholder: "Optional", value: field.defaultValue, onChange: (e) => updateField(si, fi, { defaultValue: e.target.value }) })] }), _jsxs("div", { className: "space-y-3 border-t border-gray-100 pt-3", children: [_jsxs("label", { className: "flex items-center justify-between text-xs text-gray-600 font-medium", children: [_jsx("span", { children: "Shared" }), renderToggle(field.shared, () => updateField(si, fi, { shared: !field.shared }))] }), _jsxs("label", { className: "flex items-center justify-between text-xs text-gray-600 font-medium", children: [_jsx("span", { children: "Unversioned" }), renderToggle(field.unversioned, () => updateField(si, fi, { unversioned: !field.unversioned }))] })] }), _jsxs("button", { className: "mt-2 flex w-full items-center justify-center gap-1.5 rounded-md border border-red-200 bg-red-50 py-2 text-xs font-semibold text-red-600 transition hover:bg-red-100", onClick: () => { removeField(si, fi); setSelectedFieldKey(null); }, children: [_jsx(Trash2, { className: "h-3.5 w-3.5" }), " Remove field"] })] })) : (_jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-2 p-6 text-gray-300", children: [_jsx(Settings2, { className: "h-8 w-8" }), _jsx("p", { className: "text-xs font-medium", children: "Select a field to inspect" })] }))] }), _jsxs("div", { className: "flex min-h-0 flex-col border-l border-gray-100 bg-white", children: [_jsx("div", { className: "border-b border-gray-100 bg-gray-50/30 px-4 py-2", children: _jsxs("div", { className: "flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-gray-500", children: [_jsx(Bot, { className: "h-3.5 w-3.5" }), " Template Builder AI"] }) }), _jsxs("div", { className: "border-b border-gray-100 px-4 py-3", children: [latestSuggestionSummary && (_jsxs("div", { className: "rounded border border-violet-200 bg-violet-50 px-3 py-2 text-[11px] text-violet-800", children: [_jsx("span", { className: "font-semibold", children: "Latest suggestion:" }), " ", latestSuggestionSummary] })), suggestionStatus && (_jsx("div", { className: "mt-2 text-[11px] text-gray-500", children: suggestionStatus })), suggestionParseError && (_jsxs("div", { className: "mt-2 rounded border border-red-200 bg-red-50 px-3 py-2 text-[11px] text-red-700", children: ["Failed to parse AI response: ", suggestionParseError] })), pendingSuggestions.length > 0 && (_jsxs("div", { className: "mt-2 text-[11px] text-gray-600", children: [pendingSuggestions.length, " pending suggestion", pendingSuggestions.length === 1 ? "" : "s", " highlighted in purple."] }))] }), _jsx("div", { className: "min-h-0 flex-1", children: _jsx(AgentTerminal, { agentStub: templateBuilderAgent, profiles: effectiveAiProfiles, compact: true, simpleMode: true, hideContext: true, defaultCollapseJson: true, initialMetadata: templateBuilderAgentMetadata, onMessage: handleAgentMessage, className: "h-full" }) })] })] }), renderFooter()] }));
|
|
1335
|
+
}
|
|
1336
|
+
//# sourceMappingURL=TemplateStructureInlineEditor.js.map
|