@xpert-ai/plugin-drawio 0.1.0
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/.xpertai-plugin/plugin.json +118 -0
- package/README.md +5 -0
- package/assets/composerIcon.svg +6 -0
- package/assets/logo.svg +10 -0
- package/dist/docs/drawio-agent-skill.md +36 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +153 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/constants.d.ts +24 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +43 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/drawio-view.provider.d.ts +14 -0
- package/dist/lib/drawio-view.provider.d.ts.map +1 -0
- package/dist/lib/drawio-view.provider.js +435 -0
- package/dist/lib/drawio-view.provider.js.map +1 -0
- package/dist/lib/drawio.middleware.d.ts +10 -0
- package/dist/lib/drawio.middleware.d.ts.map +1 -0
- package/dist/lib/drawio.middleware.js +159 -0
- package/dist/lib/drawio.middleware.js.map +1 -0
- package/dist/lib/drawio.plugin.d.ts +8 -0
- package/dist/lib/drawio.plugin.d.ts.map +1 -0
- package/dist/lib/drawio.plugin.js +27 -0
- package/dist/lib/drawio.plugin.js.map +1 -0
- package/dist/lib/drawio.service.d.ts +176 -0
- package/dist/lib/drawio.service.d.ts.map +1 -0
- package/dist/lib/drawio.service.js +415 -0
- package/dist/lib/drawio.service.js.map +1 -0
- package/dist/lib/drawio.templates.d.ts +3 -0
- package/dist/lib/drawio.templates.d.ts.map +1 -0
- package/dist/lib/drawio.templates.js +78 -0
- package/dist/lib/drawio.templates.js.map +1 -0
- package/dist/lib/entities/drawio-action-log.entity.d.ts +18 -0
- package/dist/lib/entities/drawio-action-log.entity.d.ts.map +1 -0
- package/dist/lib/entities/drawio-action-log.entity.js +69 -0
- package/dist/lib/entities/drawio-action-log.entity.js.map +1 -0
- package/dist/lib/entities/drawio-drawing-version.entity.d.ts +22 -0
- package/dist/lib/entities/drawio-drawing-version.entity.d.ts.map +1 -0
- package/dist/lib/entities/drawio-drawing-version.entity.js +86 -0
- package/dist/lib/entities/drawio-drawing-version.entity.js.map +1 -0
- package/dist/lib/entities/drawio-drawing.entity.d.ts +24 -0
- package/dist/lib/entities/drawio-drawing.entity.d.ts.map +1 -0
- package/dist/lib/entities/drawio-drawing.entity.js +94 -0
- package/dist/lib/entities/drawio-drawing.entity.js.map +1 -0
- package/dist/lib/entities/index.d.ts +4 -0
- package/dist/lib/entities/index.d.ts.map +1 -0
- package/dist/lib/entities/index.js +4 -0
- package/dist/lib/entities/index.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/app.css +0 -0
- package/dist/lib/remote-components/drawio-workbench/app.js +1151 -0
- package/dist/lib/remote-components/drawio-workbench/src/i18n.d.ts +3 -0
- package/dist/lib/remote-components/drawio-workbench/src/i18n.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/i18n.js +95 -0
- package/dist/lib/remote-components/drawio-workbench/src/i18n.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/i18n.ts +139 -0
- package/dist/lib/remote-components/drawio-workbench/src/main.tsx +916 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-client-shim.d.ts +3 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-client-shim.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-client-shim.js +4 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-client-shim.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-client-shim.ts +4 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-shim.d.ts +11 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-shim.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-shim.js +11 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-shim.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-dom-shim.ts +11 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-jsx-runtime-shim.d.ts +5 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-jsx-runtime-shim.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-jsx-runtime-shim.js +8 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-jsx-runtime-shim.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-jsx-runtime-shim.ts +8 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-shim.d.ts +36 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-shim.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-shim.js +36 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-shim.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/react-shim.ts +36 -0
- package/dist/lib/remote-components/drawio-workbench/src/runtime.d.ts +21 -0
- package/dist/lib/remote-components/drawio-workbench/src/runtime.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/runtime.js +198 -0
- package/dist/lib/remote-components/drawio-workbench/src/runtime.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/runtime.ts +228 -0
- package/dist/lib/remote-components/drawio-workbench/src/styles.d.ts +2 -0
- package/dist/lib/remote-components/drawio-workbench/src/styles.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/styles.js +290 -0
- package/dist/lib/remote-components/drawio-workbench/src/styles.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/styles.ts +289 -0
- package/dist/lib/remote-components/drawio-workbench/src/vendor.d.ts +4 -0
- package/dist/lib/remote-components/drawio-workbench/src/vendor.d.ts.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/vendor.js +4 -0
- package/dist/lib/remote-components/drawio-workbench/src/vendor.js.map +1 -0
- package/dist/lib/remote-components/drawio-workbench/src/vendor.ts +3 -0
- package/dist/lib/types.d.ts +67 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/xpert-drawio-assistant.yaml +130 -0
- package/package.json +85 -0
- package/skills/index/SKILL.md +45 -0
- package/skills/index/agents/xpertai.yaml +6 -0
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Archive,
|
|
3
|
+
Badge,
|
|
4
|
+
Button,
|
|
5
|
+
Check,
|
|
6
|
+
FileJson,
|
|
7
|
+
Input,
|
|
8
|
+
PanelLeftClose,
|
|
9
|
+
PanelLeftOpen,
|
|
10
|
+
PanelRightClose,
|
|
11
|
+
PanelRightOpen,
|
|
12
|
+
Plus,
|
|
13
|
+
RotateCcw,
|
|
14
|
+
Save,
|
|
15
|
+
ScrollArea,
|
|
16
|
+
Select,
|
|
17
|
+
SelectContent,
|
|
18
|
+
SelectItem,
|
|
19
|
+
SelectTrigger,
|
|
20
|
+
SelectValue,
|
|
21
|
+
Send,
|
|
22
|
+
Sidebar,
|
|
23
|
+
SidebarContent,
|
|
24
|
+
SidebarHeader,
|
|
25
|
+
SidebarMenu,
|
|
26
|
+
SidebarMenuButton,
|
|
27
|
+
SidebarMenuItem,
|
|
28
|
+
SidebarRail,
|
|
29
|
+
SidebarTitle,
|
|
30
|
+
SidebarTrigger,
|
|
31
|
+
Textarea,
|
|
32
|
+
Upload,
|
|
33
|
+
installShadcnThemeVars
|
|
34
|
+
} from '@xpert-ai/plugin-shadcn-ui'
|
|
35
|
+
import { React, ReactDOM, h } from './vendor'
|
|
36
|
+
import { createTranslator, TranslationKey } from './i18n'
|
|
37
|
+
import { injectStyles } from './styles'
|
|
38
|
+
import {
|
|
39
|
+
executeAction,
|
|
40
|
+
executeFileAction,
|
|
41
|
+
getErrorMessage,
|
|
42
|
+
getResponsePayload,
|
|
43
|
+
invokeClientCommand,
|
|
44
|
+
notify,
|
|
45
|
+
post,
|
|
46
|
+
reportResize,
|
|
47
|
+
requestData,
|
|
48
|
+
resolveMessage,
|
|
49
|
+
setRuntimeText,
|
|
50
|
+
startRemoteBridge
|
|
51
|
+
} from './runtime'
|
|
52
|
+
|
|
53
|
+
type StatusFilter = '' | 'draft' | 'reviewed' | 'archived'
|
|
54
|
+
type Drawing = Record<string, any>
|
|
55
|
+
type DrawingVersion = Record<string, any>
|
|
56
|
+
type DetailPayload = {
|
|
57
|
+
item?: Drawing
|
|
58
|
+
currentVersion?: DrawingVersion | null
|
|
59
|
+
versions?: DrawingVersion[]
|
|
60
|
+
logs?: any[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DRAWIO_ORIGIN = 'https://embed.diagrams.net'
|
|
64
|
+
const DRAWIO_EDITOR_URL = `${DRAWIO_ORIGIN}/?embed=1&proto=json&spin=1&libraries=1&configure=1&noExitBtn=1&saveAndExit=0&modified=0`
|
|
65
|
+
const EMPTY_DRAWIO_XML = '<mxfile host="xpert"><diagram name="Page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>'
|
|
66
|
+
const DEFAULT_MERMAID = `flowchart TD
|
|
67
|
+
A[User Request] --> B[Agent Plans Diagram]
|
|
68
|
+
B --> C{Best Format?}
|
|
69
|
+
C -->|Flow| D[Save Mermaid Draft]
|
|
70
|
+
C -->|Precise Layout| E[Save draw.io XML]
|
|
71
|
+
D --> F[Workbench Loads diagrams.net]
|
|
72
|
+
E --> G[Human Review]
|
|
73
|
+
F --> G`
|
|
74
|
+
|
|
75
|
+
const SAVE_MERMAID_DRAFT_TOOL_NAME = 'drawio_save_mermaid_draft'
|
|
76
|
+
const DRAWIO_TOOL_NAMES = new Set([
|
|
77
|
+
'drawio_create_diagram',
|
|
78
|
+
'drawio_save_scene_version',
|
|
79
|
+
'drawio_patch_scene',
|
|
80
|
+
SAVE_MERMAID_DRAFT_TOOL_NAME,
|
|
81
|
+
'drawio_search_diagrams',
|
|
82
|
+
'drawio_get_diagram',
|
|
83
|
+
'drawio_update_diagram_status',
|
|
84
|
+
'drawio_report_failure'
|
|
85
|
+
])
|
|
86
|
+
const DRAWIO_MUTATION_TOOL_NAMES = new Set([
|
|
87
|
+
'drawio_create_diagram',
|
|
88
|
+
'drawio_save_scene_version',
|
|
89
|
+
'drawio_patch_scene',
|
|
90
|
+
SAVE_MERMAID_DRAFT_TOOL_NAME,
|
|
91
|
+
'drawio_update_diagram_status',
|
|
92
|
+
'drawio_report_failure'
|
|
93
|
+
])
|
|
94
|
+
|
|
95
|
+
installShadcnThemeVars({ styleId: 'drawio-workbench-shadcn-ui-vars' })
|
|
96
|
+
injectStyles()
|
|
97
|
+
|
|
98
|
+
function App() {
|
|
99
|
+
const [context, setContext] = React.useState<any>(null)
|
|
100
|
+
const [drawings, setDrawings] = React.useState<Drawing[]>([])
|
|
101
|
+
const [detail, setDetail] = React.useState<DetailPayload | null>(null)
|
|
102
|
+
const [selectedId, setSelectedId] = React.useState('')
|
|
103
|
+
const [search, setSearch] = React.useState('')
|
|
104
|
+
const [status, setStatus] = React.useState<StatusFilter>('')
|
|
105
|
+
const [busy, setBusy] = React.useState(false)
|
|
106
|
+
const [dirty, setDirty] = React.useState(false)
|
|
107
|
+
const [editorReady, setEditorReady] = React.useState(false)
|
|
108
|
+
const [newTitle, setNewTitle] = React.useState('')
|
|
109
|
+
const [newDescription, setNewDescription] = React.useState('')
|
|
110
|
+
const [changeSummary, setChangeSummary] = React.useState('')
|
|
111
|
+
const [assistantPrompt, setAssistantPrompt] = React.useState('')
|
|
112
|
+
const [mermaidSource, setMermaidSource] = React.useState(DEFAULT_MERMAID)
|
|
113
|
+
const [xml, setXml] = React.useState(EMPTY_DRAWIO_XML)
|
|
114
|
+
const [leftPanelCollapsed, setLeftPanelCollapsed] = React.useState(true)
|
|
115
|
+
const [rightPanelCollapsed, setRightPanelCollapsed] = React.useState(true)
|
|
116
|
+
const iframeRef = React.useRef<HTMLIFrameElement | null>(null)
|
|
117
|
+
const fileInputRef = React.useRef<HTMLInputElement | null>(null)
|
|
118
|
+
const contextRef = React.useRef<any>(null)
|
|
119
|
+
const selectedIdRef = React.useRef('')
|
|
120
|
+
const searchRef = React.useRef('')
|
|
121
|
+
const statusRef = React.useRef<StatusFilter>('')
|
|
122
|
+
const hostEventSequenceRef = React.useRef(0)
|
|
123
|
+
const pendingSaveActionRef = React.useRef<string | null>(null)
|
|
124
|
+
const t = createTranslator(context?.locale)
|
|
125
|
+
|
|
126
|
+
React.useEffect(() => {
|
|
127
|
+
setRuntimeText({
|
|
128
|
+
requestTimeout: t('requestTimeout'),
|
|
129
|
+
remoteRequestFailed: t('remoteRequestFailed'),
|
|
130
|
+
unknownError: t('unknownError')
|
|
131
|
+
})
|
|
132
|
+
}, [context?.locale])
|
|
133
|
+
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
selectedIdRef.current = selectedId
|
|
136
|
+
}, [selectedId])
|
|
137
|
+
|
|
138
|
+
React.useEffect(() => {
|
|
139
|
+
searchRef.current = search
|
|
140
|
+
}, [search])
|
|
141
|
+
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
statusRef.current = status
|
|
144
|
+
}, [status])
|
|
145
|
+
|
|
146
|
+
React.useEffect(() => {
|
|
147
|
+
startRemoteBridge(
|
|
148
|
+
(nextContext) => {
|
|
149
|
+
contextRef.current = nextContext
|
|
150
|
+
setContext(nextContext)
|
|
151
|
+
hydratePayload(nextContext.payload || null)
|
|
152
|
+
setTimeout(() => reloadList(), 0)
|
|
153
|
+
},
|
|
154
|
+
(event) => {
|
|
155
|
+
void reloadAfterHostEvent(event)
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
post('ready')
|
|
159
|
+
}, [])
|
|
160
|
+
|
|
161
|
+
React.useEffect(reportResize, [drawings, detail, busy, dirty, editorReady, leftPanelCollapsed, rightPanelCollapsed])
|
|
162
|
+
|
|
163
|
+
React.useEffect(() => {
|
|
164
|
+
const handler = (event: MessageEvent) => {
|
|
165
|
+
if (iframeRef.current?.contentWindow && event.source !== iframeRef.current.contentWindow) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
const message = parseMessage(event.data)
|
|
169
|
+
if (!message || typeof message.event !== 'string') {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
if (message.event === 'configure') {
|
|
173
|
+
postToEditor({
|
|
174
|
+
action: 'configure',
|
|
175
|
+
config: {
|
|
176
|
+
defaultFonts: ['Inter', 'Arial'],
|
|
177
|
+
compressXml: true,
|
|
178
|
+
libraries: true
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
if (message.event === 'init') {
|
|
184
|
+
setEditorReady(true)
|
|
185
|
+
loadCurrentSceneIntoEditor()
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
if (message.event === 'save') {
|
|
189
|
+
const nextXml = typeof message.xml === 'string' && message.xml.trim() ? message.xml : xml
|
|
190
|
+
setXml(nextXml)
|
|
191
|
+
setDirty(true)
|
|
192
|
+
const sourceAction = pendingSaveActionRef.current || 'save_scene_version'
|
|
193
|
+
pendingSaveActionRef.current = null
|
|
194
|
+
void saveCurrentScene(nextXml, sourceAction)
|
|
195
|
+
}
|
|
196
|
+
if (message.event === 'exit') {
|
|
197
|
+
pendingSaveActionRef.current = null
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
window.addEventListener('message', handler)
|
|
201
|
+
return () => window.removeEventListener('message', handler)
|
|
202
|
+
}, [xml, selectedId, mermaidSource, detail?.item?.id, context?.theme])
|
|
203
|
+
|
|
204
|
+
function hydratePayload(payload: any) {
|
|
205
|
+
if (!payload) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
if (Array.isArray(payload.items)) {
|
|
209
|
+
setDrawings(payload.items)
|
|
210
|
+
if (!selectedIdRef.current && payload.items[0]?.id) {
|
|
211
|
+
selectDrawing(payload.items[0].id)
|
|
212
|
+
}
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
if (payload.item) {
|
|
216
|
+
applyDetailPayload(payload)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function applyDetailPayload(payload: DetailPayload) {
|
|
221
|
+
setDetail(payload)
|
|
222
|
+
const drawingId = payload.item?.id || ''
|
|
223
|
+
selectedIdRef.current = drawingId
|
|
224
|
+
setSelectedId(drawingId)
|
|
225
|
+
setDirty(false)
|
|
226
|
+
setChangeSummary('')
|
|
227
|
+
const version = payload.currentVersion || null
|
|
228
|
+
const nextXml = typeof version?.xml === 'string' && version.xml.trim() ? version.xml : EMPTY_DRAWIO_XML
|
|
229
|
+
const nextMermaid = typeof version?.mermaidSource === 'string' ? version.mermaidSource : ''
|
|
230
|
+
setXml(nextXml)
|
|
231
|
+
setMermaidSource(nextMermaid)
|
|
232
|
+
if (editorReady) {
|
|
233
|
+
loadVersionIntoEditor(version, payload.item?.title)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function reloadAfterHostEvent(event: unknown) {
|
|
238
|
+
const toolName = extractToolNameFromHostEvent(event)
|
|
239
|
+
if (toolName && !DRAWIO_TOOL_NAMES.has(toolName)) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const sequence = ++hostEventSequenceRef.current
|
|
244
|
+
const eventDrawingId = extractDrawingIdFromHostEvent(event)
|
|
245
|
+
const items = await reloadList()
|
|
246
|
+
if (sequence !== hostEventSequenceRef.current) {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const nextDrawingId = eventDrawingId ?? selectedIdRef.current ?? items[0]?.id
|
|
251
|
+
if (nextDrawingId) {
|
|
252
|
+
await selectDrawing(nextDrawingId)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!toolName || DRAWIO_MUTATION_TOOL_NAMES.has(toolName)) {
|
|
256
|
+
notify('info', createTranslator(contextRef.current?.locale)('agentDrawingUpdated'))
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function reloadList(overrides: Partial<{ search: string; status: StatusFilter }> = {}) {
|
|
261
|
+
const nextSearch = overrides.search ?? searchRef.current
|
|
262
|
+
const nextStatus = overrides.status ?? statusRef.current
|
|
263
|
+
setBusy(true)
|
|
264
|
+
try {
|
|
265
|
+
const response = await requestData({
|
|
266
|
+
page: 1,
|
|
267
|
+
pageSize: 50,
|
|
268
|
+
search: nextSearch,
|
|
269
|
+
parameters: {
|
|
270
|
+
...(nextStatus ? { status: nextStatus } : {})
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
const payload = getResponsePayload(response) || {}
|
|
274
|
+
const items = Array.isArray(payload.items) ? payload.items : []
|
|
275
|
+
setDrawings(items)
|
|
276
|
+
if (!selectedIdRef.current && items[0]?.id) {
|
|
277
|
+
await selectDrawing(items[0].id)
|
|
278
|
+
}
|
|
279
|
+
return items
|
|
280
|
+
} catch (error) {
|
|
281
|
+
notify('error', getErrorMessage(error))
|
|
282
|
+
return []
|
|
283
|
+
} finally {
|
|
284
|
+
setBusy(false)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function selectDrawing(drawingId: string): Promise<DetailPayload | null> {
|
|
289
|
+
if (!drawingId) {
|
|
290
|
+
return null
|
|
291
|
+
}
|
|
292
|
+
setBusy(true)
|
|
293
|
+
try {
|
|
294
|
+
const response = await requestData({ parameters: { drawingId } })
|
|
295
|
+
const payload = getResponsePayload(response) || {}
|
|
296
|
+
applyDetailPayload(payload)
|
|
297
|
+
return payload
|
|
298
|
+
} catch (error) {
|
|
299
|
+
notify('error', getErrorMessage(error))
|
|
300
|
+
return null
|
|
301
|
+
} finally {
|
|
302
|
+
setBusy(false)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function createDrawing() {
|
|
307
|
+
const title = newTitle.trim() || t('untitled')
|
|
308
|
+
setBusy(true)
|
|
309
|
+
try {
|
|
310
|
+
const response = await executeAction('create_drawing', null, {
|
|
311
|
+
title,
|
|
312
|
+
description: newDescription
|
|
313
|
+
})
|
|
314
|
+
const result = getResponsePayload(response)
|
|
315
|
+
notify('success', resolveMessage(result?.message, contextRef.current?.locale) || t('drawingCreated'))
|
|
316
|
+
const drawingId = result?.item?.id || result?.data?.item?.id
|
|
317
|
+
setNewTitle('')
|
|
318
|
+
setNewDescription('')
|
|
319
|
+
setChangeSummary('')
|
|
320
|
+
setXml(EMPTY_DRAWIO_XML)
|
|
321
|
+
setMermaidSource('')
|
|
322
|
+
setDirty(false)
|
|
323
|
+
if (drawingId) {
|
|
324
|
+
await reloadList()
|
|
325
|
+
await selectDrawing(drawingId)
|
|
326
|
+
} else {
|
|
327
|
+
await reloadList()
|
|
328
|
+
}
|
|
329
|
+
} catch (error) {
|
|
330
|
+
notify('error', getErrorMessage(error))
|
|
331
|
+
} finally {
|
|
332
|
+
setBusy(false)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function requestEditorSave(sourceAction = 'save_scene_version') {
|
|
337
|
+
if (!selectedId) {
|
|
338
|
+
notify('warning', t('noDrawing'))
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
if (!editorReady) {
|
|
342
|
+
await saveCurrentScene(xml, sourceAction)
|
|
343
|
+
return
|
|
344
|
+
}
|
|
345
|
+
pendingSaveActionRef.current = sourceAction
|
|
346
|
+
postToEditor({ action: 'save' })
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function saveCurrentScene(xmlValue: string, sourceAction = 'save_scene_version') {
|
|
350
|
+
if (!selectedId) {
|
|
351
|
+
notify('warning', t('noDrawing'))
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
setBusy(true)
|
|
355
|
+
try {
|
|
356
|
+
const response = await executeAction(sourceAction, selectedId, {
|
|
357
|
+
drawingId: selectedId,
|
|
358
|
+
xml: xmlValue,
|
|
359
|
+
mermaidSource,
|
|
360
|
+
descriptor: mermaidSource.trim() ? { format: 'mermaid', data: mermaidSource.trim() } : undefined,
|
|
361
|
+
changeSummary: changeSummary.trim() || undefined
|
|
362
|
+
})
|
|
363
|
+
const result = getResponsePayload(response)
|
|
364
|
+
notify('success', resolveMessage(result?.message, contextRef.current?.locale) || t('operationCompleted'))
|
|
365
|
+
setDirty(false)
|
|
366
|
+
setChangeSummary('')
|
|
367
|
+
await selectDrawing(selectedId)
|
|
368
|
+
await reloadList()
|
|
369
|
+
} catch (error) {
|
|
370
|
+
notify('error', getErrorMessage(error))
|
|
371
|
+
} finally {
|
|
372
|
+
setBusy(false)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function restoreVersion(versionId: string) {
|
|
377
|
+
if (!selectedId || !versionId) {
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
setBusy(true)
|
|
381
|
+
try {
|
|
382
|
+
const response = await executeAction('restore_version', selectedId, {
|
|
383
|
+
drawingId: selectedId,
|
|
384
|
+
versionId,
|
|
385
|
+
changeSummary: changeSummary.trim() || undefined
|
|
386
|
+
})
|
|
387
|
+
const result = getResponsePayload(response)
|
|
388
|
+
notify('success', resolveMessage(result?.message, contextRef.current?.locale) || t('operationCompleted'))
|
|
389
|
+
await selectDrawing(selectedId)
|
|
390
|
+
await reloadList()
|
|
391
|
+
} catch (error) {
|
|
392
|
+
notify('error', getErrorMessage(error))
|
|
393
|
+
} finally {
|
|
394
|
+
setBusy(false)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function archiveDrawing() {
|
|
399
|
+
if (!selectedId) {
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
setBusy(true)
|
|
403
|
+
try {
|
|
404
|
+
await executeAction('archive_drawing', selectedId, { drawingId: selectedId })
|
|
405
|
+
notify('success', t('operationCompleted'))
|
|
406
|
+
setDetail(null)
|
|
407
|
+
setSelectedId('')
|
|
408
|
+
setXml(EMPTY_DRAWIO_XML)
|
|
409
|
+
await reloadList()
|
|
410
|
+
} catch (error) {
|
|
411
|
+
notify('error', getErrorMessage(error))
|
|
412
|
+
} finally {
|
|
413
|
+
setBusy(false)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function setDrawingReviewStatus(nextStatus: 'draft' | 'reviewed') {
|
|
418
|
+
if (!selectedId) {
|
|
419
|
+
return
|
|
420
|
+
}
|
|
421
|
+
setBusy(true)
|
|
422
|
+
try {
|
|
423
|
+
const response = await executeAction(nextStatus === 'reviewed' ? 'mark_reviewed' : 'mark_draft', selectedId, {
|
|
424
|
+
drawingId: selectedId,
|
|
425
|
+
reason: changeSummary.trim() || undefined
|
|
426
|
+
})
|
|
427
|
+
const result = getResponsePayload(response)
|
|
428
|
+
notify('success', resolveMessage(result?.message, contextRef.current?.locale) || t('operationCompleted'))
|
|
429
|
+
setChangeSummary('')
|
|
430
|
+
await selectDrawing(selectedId)
|
|
431
|
+
await reloadList(statusRef.current && statusRef.current !== nextStatus ? { status: '' } : {})
|
|
432
|
+
if (statusRef.current && statusRef.current !== nextStatus) {
|
|
433
|
+
statusRef.current = ''
|
|
434
|
+
setStatus('')
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
notify('error', getErrorMessage(error))
|
|
438
|
+
} finally {
|
|
439
|
+
setBusy(false)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function loadMermaidIntoEditor() {
|
|
444
|
+
const source = mermaidSource.trim()
|
|
445
|
+
if (!source) {
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
postToEditor({
|
|
449
|
+
action: 'load',
|
|
450
|
+
descriptor: { format: 'mermaid', data: source },
|
|
451
|
+
sourceMetadata: { key: 'mermaidSource', value: source },
|
|
452
|
+
title: detail?.item?.title || newTitle || t('untitled'),
|
|
453
|
+
modified: 0,
|
|
454
|
+
noExitBtn: 1,
|
|
455
|
+
saveAndExit: 0,
|
|
456
|
+
exportProtocol: true,
|
|
457
|
+
dark: isDarkTheme(contextRef.current?.theme) ? 1 : 0
|
|
458
|
+
})
|
|
459
|
+
setDirty(true)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function sendAssistantPrompt() {
|
|
463
|
+
const prompt = assistantPrompt.trim()
|
|
464
|
+
if (!prompt) {
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
setBusy(true)
|
|
468
|
+
try {
|
|
469
|
+
const response = await executeAction('prepare_agent_draw_message', selectedId || null, {
|
|
470
|
+
drawingId: selectedId || undefined,
|
|
471
|
+
prompt
|
|
472
|
+
})
|
|
473
|
+
const result = getResponsePayload(response)
|
|
474
|
+
const commandKey = result?.data?.commandKey || result?.commandKey
|
|
475
|
+
const payload = result?.data?.payload || result?.payload
|
|
476
|
+
if (commandKey && payload) {
|
|
477
|
+
await invokeClientCommand(commandKey, payload)
|
|
478
|
+
}
|
|
479
|
+
setAssistantPrompt('')
|
|
480
|
+
notify('success', t('operationCompleted'))
|
|
481
|
+
} catch (error) {
|
|
482
|
+
notify('error', getErrorMessage(error))
|
|
483
|
+
} finally {
|
|
484
|
+
setBusy(false)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function importFile(file: File | null) {
|
|
489
|
+
if (!file) {
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
setBusy(true)
|
|
493
|
+
try {
|
|
494
|
+
const response = await executeFileAction(
|
|
495
|
+
'import_scene_file',
|
|
496
|
+
selectedId || null,
|
|
497
|
+
{
|
|
498
|
+
drawingId: selectedId || undefined,
|
|
499
|
+
title: removeDrawioExtension(file.name)
|
|
500
|
+
},
|
|
501
|
+
{ drawingId: selectedId || undefined },
|
|
502
|
+
file
|
|
503
|
+
)
|
|
504
|
+
const result = getResponsePayload(response)
|
|
505
|
+
notify('success', resolveMessage(result?.message, contextRef.current?.locale) || t('operationCompleted'))
|
|
506
|
+
const drawingId = result?.data?.item?.id || result?.item?.id || selectedId
|
|
507
|
+
await reloadList()
|
|
508
|
+
if (drawingId) {
|
|
509
|
+
await selectDrawing(drawingId)
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
notify('error', getErrorMessage(error))
|
|
513
|
+
} finally {
|
|
514
|
+
setBusy(false)
|
|
515
|
+
if (fileInputRef.current) {
|
|
516
|
+
fileInputRef.current.value = ''
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function exportXml() {
|
|
522
|
+
downloadBlob(new Blob([xml || EMPTY_DRAWIO_XML], { type: 'application/xml' }), `${detail?.item?.title || 'diagram'}.drawio`)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function loadCurrentSceneIntoEditor() {
|
|
526
|
+
loadVersionIntoEditor(detail?.currentVersion || null, detail?.item?.title)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function loadVersionIntoEditor(version: DrawingVersion | null, title?: string) {
|
|
530
|
+
const versionXml = typeof version?.xml === 'string' && version.xml.trim() ? version.xml : ''
|
|
531
|
+
const versionMermaid = typeof version?.mermaidSource === 'string' ? version.mermaidSource.trim() : ''
|
|
532
|
+
const loadMessage: Record<string, unknown> = {
|
|
533
|
+
action: 'load',
|
|
534
|
+
title: title || t('untitled'),
|
|
535
|
+
modified: 0,
|
|
536
|
+
noExitBtn: 1,
|
|
537
|
+
saveAndExit: 0,
|
|
538
|
+
exportProtocol: true,
|
|
539
|
+
dark: isDarkTheme(contextRef.current?.theme) ? 1 : 0
|
|
540
|
+
}
|
|
541
|
+
if (versionXml) {
|
|
542
|
+
loadMessage.xml = versionXml
|
|
543
|
+
} else if (versionMermaid) {
|
|
544
|
+
loadMessage.descriptor = { format: 'mermaid', data: versionMermaid }
|
|
545
|
+
loadMessage.sourceMetadata = { key: 'mermaidSource', value: versionMermaid }
|
|
546
|
+
} else {
|
|
547
|
+
loadMessage.xml = EMPTY_DRAWIO_XML
|
|
548
|
+
}
|
|
549
|
+
postToEditor(loadMessage)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function postToEditor(message: Record<string, unknown>) {
|
|
553
|
+
iframeRef.current?.contentWindow?.postMessage(JSON.stringify(message), DRAWIO_ORIGIN)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const currentVersion = detail?.currentVersion || null
|
|
557
|
+
const drawingStatus = (detail?.item?.status || 'draft') as StatusFilter
|
|
558
|
+
const shellClassName = `dw-shell ${leftPanelCollapsed ? 'left-collapsed' : ''} ${rightPanelCollapsed ? 'right-collapsed' : ''}`
|
|
559
|
+
|
|
560
|
+
return (
|
|
561
|
+
<div className={shellClassName}>
|
|
562
|
+
<Sidebar className="dw-sidebar" side="left" collapsed={leftPanelCollapsed}>
|
|
563
|
+
<SidebarHeader>
|
|
564
|
+
<SidebarTrigger
|
|
565
|
+
variant="ghost"
|
|
566
|
+
size="icon"
|
|
567
|
+
aria-label={leftPanelCollapsed ? t('expandDrawings') : t('collapseDrawings')}
|
|
568
|
+
title={leftPanelCollapsed ? t('expandDrawings') : t('collapseDrawings')}
|
|
569
|
+
onClick={() => setLeftPanelCollapsed((value) => !value)}
|
|
570
|
+
>
|
|
571
|
+
{leftPanelCollapsed ? <PanelLeftOpen className="dw-button-icon" aria-hidden="true" /> : <PanelLeftClose className="dw-button-icon" aria-hidden="true" />}
|
|
572
|
+
</SidebarTrigger>
|
|
573
|
+
{!leftPanelCollapsed ? (
|
|
574
|
+
<>
|
|
575
|
+
<SidebarTitle>{t('drawings')}</SidebarTitle>
|
|
576
|
+
<Badge variant="secondary">{drawings.length}</Badge>
|
|
577
|
+
</>
|
|
578
|
+
) : null}
|
|
579
|
+
</SidebarHeader>
|
|
580
|
+
{leftPanelCollapsed ? (
|
|
581
|
+
<SidebarRail><span>{t('drawings')}</span></SidebarRail>
|
|
582
|
+
) : (
|
|
583
|
+
<SidebarContent>
|
|
584
|
+
<div className="dw-sidebar-controls">
|
|
585
|
+
<Input
|
|
586
|
+
value={search}
|
|
587
|
+
placeholder={t('search')}
|
|
588
|
+
onChange={(event: any) => {
|
|
589
|
+
const next = event.target.value
|
|
590
|
+
searchRef.current = next
|
|
591
|
+
setSearch(next)
|
|
592
|
+
reloadList({ search: next })
|
|
593
|
+
}}
|
|
594
|
+
/>
|
|
595
|
+
<Select
|
|
596
|
+
value={status || 'all'}
|
|
597
|
+
onValueChange={(value: string) => {
|
|
598
|
+
const next = value === 'all' ? '' : (value as StatusFilter)
|
|
599
|
+
statusRef.current = next
|
|
600
|
+
setStatus(next)
|
|
601
|
+
reloadList({ status: next })
|
|
602
|
+
}}
|
|
603
|
+
>
|
|
604
|
+
<SelectTrigger aria-label={t('allStatuses')}>
|
|
605
|
+
<SelectValue placeholder={t('allStatuses')} />
|
|
606
|
+
</SelectTrigger>
|
|
607
|
+
<SelectContent>
|
|
608
|
+
<SelectItem value="all">{t('allStatuses')}</SelectItem>
|
|
609
|
+
<SelectItem value="draft">{t('draft')}</SelectItem>
|
|
610
|
+
<SelectItem value="reviewed">{t('reviewed')}</SelectItem>
|
|
611
|
+
<SelectItem value="archived">{t('archived')}</SelectItem>
|
|
612
|
+
</SelectContent>
|
|
613
|
+
</Select>
|
|
614
|
+
</div>
|
|
615
|
+
<ScrollArea className="dw-list">
|
|
616
|
+
<SidebarMenu>
|
|
617
|
+
{drawings.map((drawing) => (
|
|
618
|
+
<SidebarMenuItem key={drawing.id}>
|
|
619
|
+
<SidebarMenuButton type="button" active={drawing.id === selectedId} onClick={() => selectDrawing(drawing.id)}>
|
|
620
|
+
<span className="dw-item-title">{drawing.title || t('untitled')}</span>
|
|
621
|
+
<span className="dw-item-meta">v{drawing.currentVersionNumber || 0} · {t((drawing.status || 'draft') as TranslationKey)}</span>
|
|
622
|
+
</SidebarMenuButton>
|
|
623
|
+
</SidebarMenuItem>
|
|
624
|
+
))}
|
|
625
|
+
</SidebarMenu>
|
|
626
|
+
</ScrollArea>
|
|
627
|
+
</SidebarContent>
|
|
628
|
+
)}
|
|
629
|
+
</Sidebar>
|
|
630
|
+
|
|
631
|
+
<main className="dw-main">
|
|
632
|
+
<div className="dw-toolbar">
|
|
633
|
+
<div className="dw-toolbar-title">
|
|
634
|
+
<Input className="dw-title-input" value={newTitle} placeholder={t('title')} onChange={(event: any) => setNewTitle(event.target.value)} />
|
|
635
|
+
</div>
|
|
636
|
+
<div className="dw-toolbar-actions">
|
|
637
|
+
<Button type="button" variant="outline" size="sm" disabled={busy} onClick={createDrawing}>
|
|
638
|
+
<Plus className="dw-button-icon" aria-hidden="true" />
|
|
639
|
+
{t('newDrawing')}
|
|
640
|
+
</Button>
|
|
641
|
+
<Button type="button" size="sm" disabled={busy || !selectedId} onClick={() => requestEditorSave()}>
|
|
642
|
+
<Save className="dw-button-icon" aria-hidden="true" />
|
|
643
|
+
{t('save')}
|
|
644
|
+
</Button>
|
|
645
|
+
<Button type="button" variant="outline" size="sm" disabled={busy || !selectedId || !editorReady} onClick={() => postToEditor({ action: 'save' })}>
|
|
646
|
+
<FileJson className="dw-button-icon" aria-hidden="true" />
|
|
647
|
+
{t('syncEditor')}
|
|
648
|
+
</Button>
|
|
649
|
+
<Button type="button" variant="outline" size="sm" disabled={busy} onClick={() => fileInputRef.current?.click()}>
|
|
650
|
+
<Upload className="dw-button-icon" aria-hidden="true" />
|
|
651
|
+
{t('import')}
|
|
652
|
+
</Button>
|
|
653
|
+
<Button type="button" variant="outline" size="sm" disabled={!selectedId} onClick={exportXml}>
|
|
654
|
+
<FileJson className="dw-button-icon" aria-hidden="true" />
|
|
655
|
+
{t('exportXml')}
|
|
656
|
+
</Button>
|
|
657
|
+
<Badge className="dw-status" variant={dirty ? 'warning' : 'secondary'}>{dirty ? t('dirty') : t('saved')}</Badge>
|
|
658
|
+
<Badge variant={editorReady ? 'secondary' : 'outline'}>{editorReady ? t('editorReady') : t('editorLoading')}</Badge>
|
|
659
|
+
</div>
|
|
660
|
+
<input
|
|
661
|
+
ref={fileInputRef}
|
|
662
|
+
className="dw-hidden-file"
|
|
663
|
+
type="file"
|
|
664
|
+
accept=".drawio,.diagram,.xml,.svg,.json,application/xml,text/xml,application/json"
|
|
665
|
+
onChange={(event: any) => importFile(event.target.files?.[0] || null)}
|
|
666
|
+
/>
|
|
667
|
+
</div>
|
|
668
|
+
<div className="dw-editor">
|
|
669
|
+
{selectedId || currentVersion ? (
|
|
670
|
+
<>
|
|
671
|
+
<iframe ref={iframeRef} title="draw.io editor" src={DRAWIO_EDITOR_URL} />
|
|
672
|
+
{!editorReady ? <div className="dw-editor-placeholder">{t('editorLoading')}</div> : null}
|
|
673
|
+
</>
|
|
674
|
+
) : (
|
|
675
|
+
<div className="dw-empty">{t('noDrawing')}</div>
|
|
676
|
+
)}
|
|
677
|
+
</div>
|
|
678
|
+
</main>
|
|
679
|
+
|
|
680
|
+
<Sidebar className="dw-inspector" side="right" collapsed={rightPanelCollapsed}>
|
|
681
|
+
<SidebarHeader>
|
|
682
|
+
{!rightPanelCollapsed ? (
|
|
683
|
+
<>
|
|
684
|
+
<div className="dw-inspector-actions">
|
|
685
|
+
{drawingStatus === 'archived' ? (
|
|
686
|
+
<Badge variant="secondary">{t('archived')}</Badge>
|
|
687
|
+
) : drawingStatus === 'reviewed' ? (
|
|
688
|
+
<Button type="button" variant="outline" size="sm" disabled={busy || !selectedId} onClick={() => setDrawingReviewStatus('draft')}>
|
|
689
|
+
<RotateCcw className="dw-button-icon" aria-hidden="true" />
|
|
690
|
+
{t('backToDraft')}
|
|
691
|
+
</Button>
|
|
692
|
+
) : (
|
|
693
|
+
<Button type="button" variant="outline" size="sm" disabled={busy || !selectedId} onClick={() => setDrawingReviewStatus('reviewed')}>
|
|
694
|
+
<Check className="dw-button-icon" aria-hidden="true" />
|
|
695
|
+
{t('markReviewed')}
|
|
696
|
+
</Button>
|
|
697
|
+
)}
|
|
698
|
+
<Button type="button" variant="destructiveOutline" size="sm" disabled={busy || !selectedId || drawingStatus === 'archived'} onClick={archiveDrawing}>
|
|
699
|
+
<Archive className="dw-button-icon" aria-hidden="true" />
|
|
700
|
+
{t('archive')}
|
|
701
|
+
</Button>
|
|
702
|
+
</div>
|
|
703
|
+
<SidebarTitle className="dw-sidebar-title-truncate">{detail?.item?.title || t('inspector')}</SidebarTitle>
|
|
704
|
+
</>
|
|
705
|
+
) : null}
|
|
706
|
+
<SidebarTrigger
|
|
707
|
+
className="dw-sidebar-trigger-right"
|
|
708
|
+
variant="ghost"
|
|
709
|
+
size="icon"
|
|
710
|
+
aria-label={rightPanelCollapsed ? t('expandInspector') : t('collapseInspector')}
|
|
711
|
+
title={rightPanelCollapsed ? t('expandInspector') : t('collapseInspector')}
|
|
712
|
+
onClick={() => setRightPanelCollapsed((value) => !value)}
|
|
713
|
+
>
|
|
714
|
+
{rightPanelCollapsed ? <PanelRightOpen className="dw-button-icon" aria-hidden="true" /> : <PanelRightClose className="dw-button-icon" aria-hidden="true" />}
|
|
715
|
+
</SidebarTrigger>
|
|
716
|
+
</SidebarHeader>
|
|
717
|
+
{rightPanelCollapsed ? (
|
|
718
|
+
<SidebarRail><span>{t('inspector')}</span></SidebarRail>
|
|
719
|
+
) : (
|
|
720
|
+
<SidebarContent>
|
|
721
|
+
<ScrollArea className="dw-inspector-scroll">
|
|
722
|
+
<div className="dw-inspector-stack">
|
|
723
|
+
<section className="dw-section">
|
|
724
|
+
<div className="dw-section-title">{t('changeSummary')}</div>
|
|
725
|
+
<Input value={changeSummary} placeholder={t('changeSummary')} onChange={(event: any) => setChangeSummary(event.target.value)} />
|
|
726
|
+
</section>
|
|
727
|
+
|
|
728
|
+
<section className="dw-section">
|
|
729
|
+
<div className="dw-section-title">{t('versions')}</div>
|
|
730
|
+
{(detail?.versions || []).map((version) => (
|
|
731
|
+
<div className="dw-version" key={version.id}>
|
|
732
|
+
<div>
|
|
733
|
+
<div>v{version.versionNumber}</div>
|
|
734
|
+
<div className="dw-muted">{version.sourceType || 'workbench'}</div>
|
|
735
|
+
</div>
|
|
736
|
+
<Button className="dw-version-action" type="button" variant="outline" size="icon" title={t('restore')} aria-label={`${t('restore')} v${version.versionNumber}`} disabled={busy} onClick={() => restoreVersion(version.id)}>
|
|
737
|
+
<RotateCcw className="dw-button-icon" aria-hidden="true" />
|
|
738
|
+
</Button>
|
|
739
|
+
</div>
|
|
740
|
+
))}
|
|
741
|
+
</section>
|
|
742
|
+
|
|
743
|
+
<section className="dw-section">
|
|
744
|
+
<div className="dw-section-title">{t('mermaid')}</div>
|
|
745
|
+
<Textarea value={mermaidSource} onChange={(event: any) => {
|
|
746
|
+
setMermaidSource(event.target.value)
|
|
747
|
+
setDirty(true)
|
|
748
|
+
}} />
|
|
749
|
+
<div className="dw-muted">{t('mermaidNotice')}</div>
|
|
750
|
+
<div className="dw-inline-actions">
|
|
751
|
+
<Button type="button" variant="outline" size="sm" disabled={busy || !editorReady || !mermaidSource.trim()} onClick={loadMermaidIntoEditor}>
|
|
752
|
+
{t('loadMermaid')}
|
|
753
|
+
</Button>
|
|
754
|
+
<Button type="button" size="sm" disabled={busy || !selectedId} onClick={() => requestEditorSave('save_converted_mermaid_scene')}>
|
|
755
|
+
{t('saveConverted')}
|
|
756
|
+
</Button>
|
|
757
|
+
</div>
|
|
758
|
+
</section>
|
|
759
|
+
|
|
760
|
+
<section className="dw-section">
|
|
761
|
+
<div className="dw-section-title">{t('drawingRequest')}</div>
|
|
762
|
+
<Textarea value={assistantPrompt} placeholder={t('drawingRequest')} onChange={(event: any) => setAssistantPrompt(event.target.value)} />
|
|
763
|
+
<Button type="button" disabled={busy || !assistantPrompt.trim()} onClick={sendAssistantPrompt}>
|
|
764
|
+
<Send className="dw-button-icon" aria-hidden="true" />
|
|
765
|
+
{t('askAssistant')}
|
|
766
|
+
</Button>
|
|
767
|
+
</section>
|
|
768
|
+
|
|
769
|
+
<section className="dw-section">
|
|
770
|
+
<div className="dw-section-title">{t('description')}</div>
|
|
771
|
+
<Textarea value={newDescription} placeholder={t('description')} onChange={(event: any) => setNewDescription(event.target.value)} />
|
|
772
|
+
</section>
|
|
773
|
+
</div>
|
|
774
|
+
</ScrollArea>
|
|
775
|
+
</SidebarContent>
|
|
776
|
+
)}
|
|
777
|
+
</Sidebar>
|
|
778
|
+
</div>
|
|
779
|
+
)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function parseMessage(data: unknown) {
|
|
783
|
+
if (typeof data === 'string') {
|
|
784
|
+
try {
|
|
785
|
+
return JSON.parse(data)
|
|
786
|
+
} catch {
|
|
787
|
+
return null
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return data && typeof data === 'object' && !Array.isArray(data) ? data as Record<string, any> : null
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function isDarkTheme(theme: unknown) {
|
|
794
|
+
if (typeof theme === 'boolean') {
|
|
795
|
+
return theme
|
|
796
|
+
}
|
|
797
|
+
if (typeof theme === 'string') {
|
|
798
|
+
return /dark|night/i.test(theme)
|
|
799
|
+
}
|
|
800
|
+
return Boolean(theme && typeof theme === 'object' && ((theme as any).dark === true || (theme as any).isDark === true))
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function extractToolNameFromHostEvent(event: unknown) {
|
|
804
|
+
for (const candidate of expandHostEventCandidates(event)) {
|
|
805
|
+
if (!isObject(candidate)) {
|
|
806
|
+
continue
|
|
807
|
+
}
|
|
808
|
+
const direct = readString(candidate, 'toolName') ?? readString(candidate, 'tool_name') ?? readString(candidate, 'name')
|
|
809
|
+
if (direct && DRAWIO_TOOL_NAMES.has(direct)) {
|
|
810
|
+
return direct
|
|
811
|
+
}
|
|
812
|
+
const tool = candidate.tool
|
|
813
|
+
if (isObject(tool)) {
|
|
814
|
+
const toolName = readString(tool, 'name') ?? readString(tool, 'toolName') ?? readString(tool, 'tool_name')
|
|
815
|
+
if (toolName && DRAWIO_TOOL_NAMES.has(toolName)) {
|
|
816
|
+
return toolName
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return null
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function extractDrawingIdFromHostEvent(event: unknown) {
|
|
824
|
+
for (const candidate of expandHostEventCandidates(event)) {
|
|
825
|
+
if (!isObject(candidate)) {
|
|
826
|
+
continue
|
|
827
|
+
}
|
|
828
|
+
const direct = readString(candidate, 'drawingId') ?? readString(candidate, 'drawing_id')
|
|
829
|
+
if (direct) {
|
|
830
|
+
return direct
|
|
831
|
+
}
|
|
832
|
+
if (isObject(candidate.item)) {
|
|
833
|
+
const itemId = readString(candidate.item, 'id')
|
|
834
|
+
if (itemId) {
|
|
835
|
+
return itemId
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (isObject(candidate.drawing)) {
|
|
839
|
+
const drawingId = readString(candidate.drawing, 'drawingId') ?? readString(candidate.drawing, 'drawing_id') ?? readString(candidate.drawing, 'id')
|
|
840
|
+
if (drawingId) {
|
|
841
|
+
return drawingId
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (isObject(candidate.version)) {
|
|
845
|
+
const drawingId = readString(candidate.version, 'drawingId') ?? readString(candidate.version, 'drawing_id')
|
|
846
|
+
if (drawingId) {
|
|
847
|
+
return drawingId
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return null
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function expandHostEventCandidates(event: unknown) {
|
|
855
|
+
const candidates: unknown[] = []
|
|
856
|
+
collectHostEventCandidates(event, candidates, 0)
|
|
857
|
+
return candidates
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function collectHostEventCandidates(value: unknown, candidates: unknown[], depth: number) {
|
|
861
|
+
if (depth > 5 || value == null) {
|
|
862
|
+
return
|
|
863
|
+
}
|
|
864
|
+
const normalized = parseJsonLike(value)
|
|
865
|
+
candidates.push(normalized)
|
|
866
|
+
if (Array.isArray(normalized)) {
|
|
867
|
+
normalized.forEach((item) => collectHostEventCandidates(item, candidates, depth + 1))
|
|
868
|
+
return
|
|
869
|
+
}
|
|
870
|
+
if (!isObject(normalized)) {
|
|
871
|
+
return
|
|
872
|
+
}
|
|
873
|
+
;['payload', 'metadata', 'data', 'result', 'output', 'content', 'message', 'detail', 'response', 'tool', 'toolCall', 'tool_call', 'function', 'arguments', 'args', 'input'].forEach((key) => collectHostEventCandidates(normalized[key], candidates, depth + 1))
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function parseJsonLike(value: unknown) {
|
|
877
|
+
if (typeof value !== 'string') {
|
|
878
|
+
return value
|
|
879
|
+
}
|
|
880
|
+
const trimmed = value.trim()
|
|
881
|
+
if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) {
|
|
882
|
+
return value
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
return JSON.parse(trimmed)
|
|
886
|
+
} catch {
|
|
887
|
+
return value
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
892
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value))
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function readString(record: Record<string, unknown>, key: string) {
|
|
896
|
+
const value = record[key]
|
|
897
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function removeDrawioExtension(name: string) {
|
|
901
|
+
return name.replace(/\.(drawio|diagram|xml)(?:\.json)?$/i, '').replace(/\.json$/i, '') || name
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function downloadBlob(blob: Blob, fileName: string) {
|
|
905
|
+
const url = URL.createObjectURL(blob)
|
|
906
|
+
const anchor = document.createElement('a')
|
|
907
|
+
anchor.href = url
|
|
908
|
+
anchor.download = fileName
|
|
909
|
+
document.body.appendChild(anchor)
|
|
910
|
+
anchor.click()
|
|
911
|
+
anchor.remove()
|
|
912
|
+
URL.revokeObjectURL(url)
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const root = ReactDOM.createRoot(document.getElementById('root')!)
|
|
916
|
+
root.render(<App />)
|