@p-dsh-market/conversation-knowledge-map 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/README.md +22 -0
- package/cordis.patch.yml +4 -0
- package/lib/client.js +575 -0
- package/lib/generation-orchestrator.js +401 -0
- package/lib/index.js +286 -0
- package/lib/knowledge-graph-schema.js +89 -0
- package/lib/mind-map-schema.js +94 -0
- package/lib/protocol.js +104 -0
- package/lib/session-source.js +188 -0
- package/lib/workspace-storage.js +224 -0
- package/package.json +74 -0
- package/skills/conversation-knowledge-map/SKILL.md +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @p-dsh-market/conversation-knowledge-map
|
|
2
|
+
|
|
3
|
+
DSH 的“知识视图”插件,把同一工作路径下用户明确选择的多个历史对话整理成:
|
|
4
|
+
|
|
5
|
+
- 有阶段性段落的思维导图;
|
|
6
|
+
- 只读、可筛选、可回溯来源的静态知识图谱。
|
|
7
|
+
|
|
8
|
+
首版通过 `sessionQuery.filterSessions()`、`readTitleSnapshots()` 和 `readSurface()` 读取会话,不扫描 `$DSH_HOME/sessions`。结果保存到当前工作路径的 `.g-dsh-market-knowledge/`,只保存图数据、来源事件序号和生成参数,不复制完整聊天正文。
|
|
9
|
+
|
|
10
|
+
生成流程必须经过菜单栏配置和应用内确认。Host 会再次校验锚点 Session、`cwd`、选中 Session、revision 和固定保存目录;模型输出先通过图数据 Schema 校验,失败或取消不会替换旧结果。
|
|
11
|
+
|
|
12
|
+
节点“继续对话”只形成一个可编辑的后续问题。确认导航后,插件使用公开的 Session 导航入口;如果当前 Runtime 没有向该槽位暴露草稿镜像,则提供“打开并复制问题”的安全降级,不自动发送消息。
|
|
13
|
+
|
|
14
|
+
## 本地验证
|
|
15
|
+
|
|
16
|
+
```powershell
|
|
17
|
+
node --test tests/conversation-knowledge-map.test.mjs
|
|
18
|
+
node --check market/conversation-knowledge-map/lib/index.js
|
|
19
|
+
node --check market/conversation-knowledge-map/lib/client.js
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
本地实现未执行 `npm pack`、发布或真实 Profile 安装;真实 DSH Web 视觉回放仍需在插件安装后验证。
|
package/cordis.patch.yml
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
// 知识视图 — browser half
|
|
2
|
+
//
|
|
3
|
+
// The client intentionally keeps graph data in the host-owned workspace and
|
|
4
|
+
// treats the knowledge graph as read-only. It uses only React nodes for text;
|
|
5
|
+
// model output is never inserted as HTML.
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: '@p-dsh-market/conversation-knowledge-map',
|
|
8
|
+
factory: (require) => {
|
|
9
|
+
var module = { exports: {} }
|
|
10
|
+
var exports = module.exports
|
|
11
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
12
|
+
var React = require('react')
|
|
13
|
+
|
|
14
|
+
var PLUGIN_ID = '@p-dsh-market/conversation-knowledge-map'
|
|
15
|
+
var DESKTOP_RPC_METHOD = 'conversationKnowledgeMap.open'
|
|
16
|
+
var BASE = '/conversation-knowledge-map'
|
|
17
|
+
var stores = Object.create(null)
|
|
18
|
+
var overlay = { open: false, version: 0, listeners: [] }
|
|
19
|
+
var navigation = { pending: null, version: 0, listeners: [] }
|
|
20
|
+
|
|
21
|
+
function notify(target) {
|
|
22
|
+
target.version += 1
|
|
23
|
+
target.listeners.slice().forEach(function (listener) { try { listener() } catch (_) {} })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function subscribe(target, listener) {
|
|
27
|
+
target.listeners.push(listener)
|
|
28
|
+
return function () { target.listeners = target.listeners.filter(function (item) { return item !== listener }) }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function useVersion(target) {
|
|
32
|
+
var pair = React.useState(target.version)
|
|
33
|
+
React.useEffect(function () {
|
|
34
|
+
return subscribe(target, function () { pair[1](function (value) { return value + 1 }) })
|
|
35
|
+
}, [target])
|
|
36
|
+
return pair[0]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function setOverlayOpen(open) {
|
|
40
|
+
var next = open === true
|
|
41
|
+
if (overlay.open === next) return
|
|
42
|
+
overlay.open = next
|
|
43
|
+
notify(overlay)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function setPendingNavigation(value) {
|
|
47
|
+
navigation.pending = value || null
|
|
48
|
+
notify(navigation)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveSessionId(props) {
|
|
52
|
+
var value = props && (props.sessionId || props.id || (props.session && props.session.id))
|
|
53
|
+
value = String(value || '').trim()
|
|
54
|
+
return value && value !== 'active' ? value : null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function currentSessionId(sessions) {
|
|
58
|
+
if (!sessions) return null
|
|
59
|
+
var value = typeof sessions.current === 'function' ? sessions.current() : (sessions.current || sessions.active || '')
|
|
60
|
+
if (!value && sessions.list && typeof sessions.list.getSnapshot === 'function') {
|
|
61
|
+
var snapshot = sessions.list.getSnapshot()
|
|
62
|
+
value = snapshot && (snapshot.current || snapshot.currentId || snapshot.sessionId || '')
|
|
63
|
+
}
|
|
64
|
+
if (value && typeof value === 'object') value = value.id
|
|
65
|
+
return resolveSessionId({ sessionId: value })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function useCurrentSessionId(sessions) {
|
|
69
|
+
var source = sessions && sessions.list
|
|
70
|
+
var pair = React.useState(function () {
|
|
71
|
+
return source && typeof source.getSnapshot === 'function' ? source.getSnapshot() : null
|
|
72
|
+
})
|
|
73
|
+
var snapshot = pair[0]
|
|
74
|
+
var setSnapshot = pair[1]
|
|
75
|
+
React.useEffect(function () {
|
|
76
|
+
if (!source || typeof source.getSnapshot !== 'function' || typeof source.subscribe !== 'function') {
|
|
77
|
+
setSnapshot(null)
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
function update() { setSnapshot(source.getSnapshot()) }
|
|
81
|
+
update()
|
|
82
|
+
return source.subscribe(update)
|
|
83
|
+
}, [source])
|
|
84
|
+
return currentSessionId({ current: snapshot && (snapshot.current || snapshot.currentId || snapshot.sessionId) })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getStore(sessionId) {
|
|
88
|
+
var key = String(sessionId || 'no-session')
|
|
89
|
+
if (!stores[key]) {
|
|
90
|
+
stores[key] = {
|
|
91
|
+
sessionId: sessionId,
|
|
92
|
+
context: null,
|
|
93
|
+
state: null,
|
|
94
|
+
sessions: [],
|
|
95
|
+
generation: null,
|
|
96
|
+
loading: false,
|
|
97
|
+
loaded: false,
|
|
98
|
+
error: '',
|
|
99
|
+
listeners: [],
|
|
100
|
+
sourceRequest: 0,
|
|
101
|
+
stream: null,
|
|
102
|
+
pollTimer: null,
|
|
103
|
+
mode: 'mind-map'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return stores[key]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function notifyStore(store) {
|
|
110
|
+
store.listeners.slice().forEach(function (listener) { try { listener() } catch (_) {} })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function useStore(sessionId) {
|
|
114
|
+
var store = getStore(sessionId)
|
|
115
|
+
var pair = React.useState(0)
|
|
116
|
+
React.useEffect(function () {
|
|
117
|
+
var listener = function () { pair[1](function (value) { return value + 1 }) }
|
|
118
|
+
store.listeners.push(listener)
|
|
119
|
+
return function () { store.listeners = store.listeners.filter(function (item) { return item !== listener }) }
|
|
120
|
+
}, [store])
|
|
121
|
+
React.useEffect(function () {
|
|
122
|
+
if (sessionId && store.sessionId === sessionId && !store.loaded && !store.loading) loadStore(store)
|
|
123
|
+
}, [sessionId, store])
|
|
124
|
+
return store
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function request(path, options) {
|
|
128
|
+
var init = Object.assign({ headers: { accept: 'application/json' } }, options || {})
|
|
129
|
+
if (init.body && typeof init.body !== 'string') {
|
|
130
|
+
init.body = JSON.stringify(init.body)
|
|
131
|
+
init.headers = Object.assign({}, init.headers, { 'content-type': 'application/json' })
|
|
132
|
+
}
|
|
133
|
+
var response = await fetch(BASE + path, init)
|
|
134
|
+
var body = await response.json().catch(function () { return {} })
|
|
135
|
+
if (!response.ok || body.ok === false) throw new Error(body.error || ('请求失败(HTTP ' + response.status + ')'))
|
|
136
|
+
return body
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function closeStream(store) {
|
|
140
|
+
if (store.stream) store.stream.close()
|
|
141
|
+
store.stream = null
|
|
142
|
+
if (store.pollTimer) window.clearInterval(store.pollTimer)
|
|
143
|
+
store.pollTimer = null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function applyGeneration(store, generation) {
|
|
147
|
+
store.generation = generation || null
|
|
148
|
+
notifyStore(store)
|
|
149
|
+
if (!generation || !generation.id) return
|
|
150
|
+
if (generation.status === 'completed') {
|
|
151
|
+
request('/state?anchorSessionId=' + encodeURIComponent(store.sessionId)).then(function (body) {
|
|
152
|
+
store.context = body.context
|
|
153
|
+
store.state = body.state
|
|
154
|
+
notifyStore(store)
|
|
155
|
+
}).catch(function (error) { store.error = error.message || String(error); notifyStore(store) })
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function openGenerationStream(store, id) {
|
|
160
|
+
closeStream(store)
|
|
161
|
+
if (typeof EventSource === 'function') {
|
|
162
|
+
var stream = new EventSource(BASE + '/generations/' + encodeURIComponent(id) + '/events')
|
|
163
|
+
store.stream = stream
|
|
164
|
+
stream.addEventListener('snapshot', function (event) {
|
|
165
|
+
try { applyGeneration(store, JSON.parse(event.data).generation) } catch (_) {}
|
|
166
|
+
})
|
|
167
|
+
stream.addEventListener('update', function (event) {
|
|
168
|
+
try {
|
|
169
|
+
var generation = JSON.parse(event.data).generation
|
|
170
|
+
applyGeneration(store, generation)
|
|
171
|
+
if (generation.status === 'completed' || generation.status === 'failed' || generation.status === 'cancelled') stream.close()
|
|
172
|
+
} catch (_) {}
|
|
173
|
+
})
|
|
174
|
+
stream.onerror = function () {
|
|
175
|
+
if (store.generation && !['completed', 'failed', 'cancelled'].includes(store.generation.status)) startGenerationPolling(store, id)
|
|
176
|
+
}
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
startGenerationPolling(store, id)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function startGenerationPolling(store, id) {
|
|
183
|
+
if (store.pollTimer) return
|
|
184
|
+
store.pollTimer = window.setInterval(function () {
|
|
185
|
+
request('/generations/' + encodeURIComponent(id)).then(function (body) {
|
|
186
|
+
applyGeneration(store, body.generation)
|
|
187
|
+
if (['completed', 'failed', 'cancelled'].includes(body.generation.status)) closeStream(store)
|
|
188
|
+
}).catch(function (error) { store.error = error.message || String(error); notifyStore(store) })
|
|
189
|
+
}, 900)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function loadStore(store) {
|
|
193
|
+
var requestId = ++store.sourceRequest
|
|
194
|
+
store.loading = true
|
|
195
|
+
store.error = ''
|
|
196
|
+
notifyStore(store)
|
|
197
|
+
try {
|
|
198
|
+
var contextBody = await request('/context?sessionId=' + encodeURIComponent(store.sessionId || ''))
|
|
199
|
+
if (requestId !== store.sourceRequest) return
|
|
200
|
+
store.context = contextBody.context
|
|
201
|
+
if (store.context && store.context.ready) {
|
|
202
|
+
var stateBody = await request('/state?anchorSessionId=' + encodeURIComponent(store.sessionId))
|
|
203
|
+
if (requestId !== store.sourceRequest) return
|
|
204
|
+
store.context = stateBody.context
|
|
205
|
+
store.state = stateBody.state
|
|
206
|
+
} else {
|
|
207
|
+
store.state = null
|
|
208
|
+
}
|
|
209
|
+
store.loaded = true
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (requestId === store.sourceRequest) store.error = error.message || String(error)
|
|
212
|
+
} finally {
|
|
213
|
+
if (requestId === store.sourceRequest) { store.loading = false; notifyStore(store) }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function useOverlay() {
|
|
218
|
+
useVersion(overlay)
|
|
219
|
+
return overlay
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function useNavigation() {
|
|
223
|
+
useVersion(navigation)
|
|
224
|
+
return navigation
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function Button(props, children) {
|
|
228
|
+
return React.createElement('button', Object.assign({ type: 'button' }, props || {}), children)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function EmptyState(props) {
|
|
232
|
+
return React.createElement('div', { className: 'ckm-empty' }, [
|
|
233
|
+
React.createElement('div', { key: 'icon', className: 'ckm-empty-icon' }, '⌘'),
|
|
234
|
+
React.createElement('h2', { key: 'title' }, props.title),
|
|
235
|
+
React.createElement('p', { key: 'copy' }, props.copy)
|
|
236
|
+
])
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function KnowledgeView(props) {
|
|
240
|
+
var sessionId = resolveSessionId(props)
|
|
241
|
+
var store = useStore(sessionId)
|
|
242
|
+
var selectedPair = React.useState(null)
|
|
243
|
+
var selectedNodeId = selectedPair[0]
|
|
244
|
+
var setSelectedNodeId = selectedPair[1]
|
|
245
|
+
if (!sessionId) return React.createElement(EmptyState, { title: '请先打开一个已有对话', copy: '确定对话及其工作路径后,才能选择多个历史对话并生成知识视图。' })
|
|
246
|
+
if (store.loading && !store.context) return React.createElement(EmptyState, { title: '正在读取当前对话上下文…', copy: '这里只读取 Session 元数据,不会自动读取历史对话正文。' })
|
|
247
|
+
if (!store.context || !store.context.ready) return React.createElement(EmptyState, { title: '当前对话没有可用工作路径', copy: '知识视图需要从当前 Session 的 header.cwd 确定工作区。' })
|
|
248
|
+
if (store.error && !store.state) return React.createElement(EmptyState, { title: '知识视图暂时不可用', copy: store.error })
|
|
249
|
+
var state = store.state
|
|
250
|
+
var mode = store.mode
|
|
251
|
+
return React.createElement('div', { className: 'ckm-page' }, [
|
|
252
|
+
React.createElement('header', { key: 'header', className: 'ckm-page-header' }, [
|
|
253
|
+
React.createElement('div', { key: 'title' }, [React.createElement('h2', { key: 'h' }, '思维与知识'), React.createElement('p', { key: 'p' }, store.context.cwd)]),
|
|
254
|
+
React.createElement('div', { key: 'actions', className: 'ckm-page-actions' }, [
|
|
255
|
+
Button({ className: 'ckm-tab', 'data-active': mode === 'mind-map', onClick: function () { store.mode = 'mind-map'; notifyStore(store) } }, '思维导图'),
|
|
256
|
+
Button({ className: 'ckm-tab', 'data-active': mode === 'knowledge-graph', onClick: function () { store.mode = 'knowledge-graph'; notifyStore(store) } }, '知识图谱'),
|
|
257
|
+
Button({ className: 'ckm-secondary', onClick: function () { setOverlayOpen(true) } }, '配置 / 重新生成')
|
|
258
|
+
])
|
|
259
|
+
]),
|
|
260
|
+
store.generation && store.generation.status !== 'completed' ? React.createElement(GenerationStrip, { key: 'generation', store: store }) : null,
|
|
261
|
+
state && state.compatibility && !state.compatibility.supported ? React.createElement('div', { key: 'compatibility', className: 'ckm-warning ckm-compatibility-warning' }, state.compatibility.message) : null,
|
|
262
|
+
!state || (!state.mindMap && !state.knowledgeGraph) ? React.createElement(EmptyState, { key: 'empty', title: '尚未生成知识视图', copy: '从标题栏打开“知识视图”,选择同工作路径下的对话并确认生成。' }) : (mode === 'mind-map'
|
|
263
|
+
? React.createElement(MindMapPanel, { key: 'mind', store: store, selectedNodeId: selectedNodeId, setSelectedNodeId: setSelectedNodeId })
|
|
264
|
+
: React.createElement(KnowledgeGraphPanel, { key: 'graph', store: store }))
|
|
265
|
+
])
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function GenerationStrip(props) {
|
|
269
|
+
var generation = props.store.generation
|
|
270
|
+
return React.createElement('div', { className: 'ckm-generation-strip', 'data-status': generation.status }, [
|
|
271
|
+
React.createElement('strong', { key: 'status' }, generation.message || generation.status),
|
|
272
|
+
generation.error ? React.createElement('span', { key: 'error' }, generation.error) : null,
|
|
273
|
+
['failed', 'cancelled'].includes(generation.status) ? null : Button({ key: 'cancel', className: 'ckm-danger', onClick: function () { request('/generations/' + encodeURIComponent(generation.id) + '/cancel', { method: 'POST' }).then(function (body) { applyGeneration(props.store, body.generation) }).catch(function (error) { props.store.error = error.message; notifyStore(props.store) }) } }, '取消')
|
|
274
|
+
])
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function mindNodes(map) {
|
|
278
|
+
var nodes = (map && map.nodes) || []
|
|
279
|
+
var byParent = Object.create(null)
|
|
280
|
+
nodes.forEach(function (node) { var key = node.parentId || ''; (byParent[key] || (byParent[key] = [])).push(node) })
|
|
281
|
+
var result = []
|
|
282
|
+
function visit(node, depth) {
|
|
283
|
+
result.push({ node: node, depth: depth })
|
|
284
|
+
;(byParent[node.id] || []).forEach(function (child) { visit(child, depth + 1) })
|
|
285
|
+
}
|
|
286
|
+
var root = nodes.filter(function (node) { return node.id === map.rootId })[0]
|
|
287
|
+
if (root) visit(root, 0)
|
|
288
|
+
return result
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function MindMapPanel(props) {
|
|
292
|
+
var map = props.store.state && props.store.state.mindMap
|
|
293
|
+
var selected = map && map.nodes.filter(function (node) { return node.id === props.selectedNodeId })[0]
|
|
294
|
+
var rows = map ? mindNodes(map) : []
|
|
295
|
+
var followPair = React.useState(null)
|
|
296
|
+
var followUp = followPair[0]
|
|
297
|
+
var setFollowUp = followPair[1]
|
|
298
|
+
var messagePair = React.useState('')
|
|
299
|
+
var message = messagePair[0]
|
|
300
|
+
var setMessage = messagePair[1]
|
|
301
|
+
function formQuestion(node) {
|
|
302
|
+
var target = node.primarySourceSessionId || (node.sourceRefs && node.sourceRefs[0] && node.sourceRefs[0].sessionId)
|
|
303
|
+
setMessage('正在形成后续问题…')
|
|
304
|
+
request('/mind-map/follow-up-question', { method: 'POST', body: { anchorSessionId: props.store.sessionId, nodeId: node.id, targetSessionId: target } }).then(function (body) {
|
|
305
|
+
setMessage('')
|
|
306
|
+
setFollowUp({ node: node, value: body.followUp, targets: node.sourceRefs || [] })
|
|
307
|
+
}).catch(function (error) { setMessage(error.message || String(error)) })
|
|
308
|
+
}
|
|
309
|
+
return React.createElement('div', { className: 'ckm-workspace' }, [
|
|
310
|
+
React.createElement('section', { key: 'canvas', className: 'ckm-mind-canvas' }, [
|
|
311
|
+
React.createElement('div', { key: 'hint', className: 'ckm-panel-hint' }, '节点代表阶段性认知;点击后可形成问题,但不会直接修改脑图。'),
|
|
312
|
+
React.createElement('div', { key: 'tree', className: 'ckm-tree' }, rows.map(function (row) {
|
|
313
|
+
return Button({ key: row.node.id, className: 'ckm-tree-node', 'data-active': row.node.id === props.selectedNodeId, style: { marginLeft: (row.depth * 24) + 'px' }, onClick: function () { props.setSelectedNodeId(row.node.id) } }, [
|
|
314
|
+
React.createElement('span', { key: 'type', className: 'ckm-node-type' }, row.node.type),
|
|
315
|
+
React.createElement('strong', { key: 'title' }, row.node.title),
|
|
316
|
+
React.createElement('span', { key: 'narrative' }, row.node.narrative)
|
|
317
|
+
])
|
|
318
|
+
}))
|
|
319
|
+
]),
|
|
320
|
+
React.createElement('aside', { key: 'detail', className: 'ckm-detail' }, [
|
|
321
|
+
selected ? [
|
|
322
|
+
React.createElement('div', { key: 'head', className: 'ckm-detail-head' }, [React.createElement('span', { key: 'type', className: 'ckm-node-type' }, selected.type), React.createElement('h3', { key: 'title' }, selected.title)]),
|
|
323
|
+
React.createElement('p', { key: 'narrative', className: 'ckm-narrative' }, selected.narrative),
|
|
324
|
+
selected.openQuestions && selected.openQuestions.length ? React.createElement('div', { key: 'questions', className: 'ckm-source-box' }, [React.createElement('strong', { key: 'label' }, '未决问题'), React.createElement('ul', { key: 'list' }, selected.openQuestions.map(function (item) { return React.createElement('li', { key: item }, item) }))]) : null,
|
|
325
|
+
React.createElement('div', { key: 'sources', className: 'ckm-source-box' }, [React.createElement('strong', { key: 'label' }, '来源对话'), React.createElement('ul', { key: 'list' }, (selected.sourceRefs || []).map(function (ref) { return React.createElement('li', { key: ref.sessionId + ':' + ref.eventSeqs.join(',') }, ref.sessionId.slice(0, 12) + ' · seq ' + ref.eventSeqs.join(', ')) }))]),
|
|
326
|
+
Button({ key: 'follow', className: 'ckm-primary', onClick: function () { formQuestion(selected) } }, '基于此节点继续对话'),
|
|
327
|
+
message ? React.createElement('p', { key: 'message', className: 'ckm-error' }, message) : null
|
|
328
|
+
] : React.createElement('div', { className: 'ckm-detail-empty' }, '选择一个节点查看阶段性说明和来源。'),
|
|
329
|
+
followUp ? React.createElement(FollowUpModal, { key: 'follow-up', store: props.store, state: followUp, close: function () { setFollowUp(null) } }) : null
|
|
330
|
+
])
|
|
331
|
+
])
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function FollowUpModal(props) {
|
|
335
|
+
var value = props.state.value
|
|
336
|
+
var targetPair = React.useState(value.targetSessionId)
|
|
337
|
+
var target = targetPair[0]
|
|
338
|
+
var setTarget = targetPair[1]
|
|
339
|
+
var questionPair = React.useState(value.question)
|
|
340
|
+
var question = questionPair[0]
|
|
341
|
+
var setQuestion = questionPair[1]
|
|
342
|
+
var busyPair = React.useState(false)
|
|
343
|
+
var busy = busyPair[0]
|
|
344
|
+
var setBusy = busyPair[1]
|
|
345
|
+
function confirm() {
|
|
346
|
+
setBusy(true)
|
|
347
|
+
request('/navigation/confirm', { method: 'POST', body: { anchorSessionId: props.store.sessionId, nodeId: props.state.node.id, targetSessionId: target, question: question } }).then(function (body) {
|
|
348
|
+
setPendingNavigation(body.navigation)
|
|
349
|
+
props.close()
|
|
350
|
+
}).catch(function (error) { setBusy(false); setQuestion(question + '\n\n[确认失败:' + (error.message || String(error)) + ']') })
|
|
351
|
+
}
|
|
352
|
+
var targets = (props.state.targets || []).map(function (ref) { return ref.sessionId }).filter(function (id, index, list) { return list.indexOf(id) === index })
|
|
353
|
+
return React.createElement('div', { className: 'ckm-modal-backdrop' }, React.createElement('div', { className: 'ckm-modal', role: 'dialog', 'aria-modal': 'true' }, [
|
|
354
|
+
React.createElement('h3', { key: 'title' }, '确认返回原对话'),
|
|
355
|
+
React.createElement('p', { key: 'hint' }, '问题会进入目标对话的下一步操作;插件不会自动发送。'),
|
|
356
|
+
React.createElement('label', { key: 'target' }, ['目标对话', React.createElement('select', { key: 'select', value: target, onChange: function (event) { setTarget(event.target.value) } }, targets.map(function (id) { return React.createElement('option', { key: id, value: id }, id) }))]),
|
|
357
|
+
React.createElement('label', { key: 'question' }, ['后续问题', React.createElement('textarea', { key: 'textarea', rows: 6, value: question, onChange: function (event) { setQuestion(event.target.value) } })]),
|
|
358
|
+
React.createElement('div', { key: 'actions', className: 'ckm-modal-actions' }, [Button({ key: 'cancel', className: 'ckm-secondary', onClick: props.close, disabled: busy }, '返回修改'), Button({ key: 'confirm', className: 'ckm-primary', onClick: confirm, disabled: busy || !question.trim() }, busy ? '确认中…' : '确认并返回')])
|
|
359
|
+
]))
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function KnowledgeGraphPanel(props) {
|
|
363
|
+
var graph = props.store.state && props.store.state.knowledgeGraph
|
|
364
|
+
var queryPair = React.useState('')
|
|
365
|
+
var query = queryPair[0]
|
|
366
|
+
var setQuery = queryPair[1]
|
|
367
|
+
var confidencePair = React.useState('all')
|
|
368
|
+
var confidence = confidencePair[0]
|
|
369
|
+
var setConfidence = confidencePair[1]
|
|
370
|
+
var selectedPair = React.useState(null)
|
|
371
|
+
var selected = selectedPair[0]
|
|
372
|
+
var setSelected = selectedPair[1]
|
|
373
|
+
if (!graph) return React.createElement(EmptyState, { title: '尚未生成知识图谱', copy: '从标题栏打开知识视图并选择生成知识图谱。' })
|
|
374
|
+
var entities = graph.entities.filter(function (entity) {
|
|
375
|
+
return (confidence === 'all' || entity.confidence === confidence) && (!query.trim() || (entity.name + ' ' + entity.summary).toLowerCase().indexOf(query.trim().toLowerCase()) >= 0)
|
|
376
|
+
})
|
|
377
|
+
var visible = Object.create(null)
|
|
378
|
+
entities.forEach(function (entity) { visible[entity.id] = true })
|
|
379
|
+
var positions = entities.map(function (entity, index) {
|
|
380
|
+
var angle = index / Math.max(1, entities.length) * Math.PI * 2
|
|
381
|
+
return { entity: entity, x: 400 + Math.cos(angle) * 260, y: 230 + Math.sin(angle) * 160 }
|
|
382
|
+
})
|
|
383
|
+
var byId = Object.create(null)
|
|
384
|
+
positions.forEach(function (item) { byId[item.entity.id] = item })
|
|
385
|
+
var relations = graph.relations.filter(function (relation) { return visible[relation.from] && visible[relation.to] })
|
|
386
|
+
return React.createElement('div', { className: 'ckm-workspace ckm-graph-layout' }, [
|
|
387
|
+
React.createElement('section', { key: 'graph', className: 'ckm-graph-canvas' }, [
|
|
388
|
+
React.createElement('div', { key: 'toolbar', className: 'ckm-graph-toolbar' }, [React.createElement('input', { key: 'search', value: query, placeholder: '搜索实体…', onChange: function (event) { setQuery(event.target.value) } }), React.createElement('select', { key: 'confidence', value: confidence, onChange: function (event) { setConfidence(event.target.value) } }, [React.createElement('option', { key: 'all', value: 'all' }, '全部置信度'), React.createElement('option', { key: 'confirmed', value: 'confirmed' }, '已确认'), React.createElement('option', { key: 'inferred', value: 'inferred' }, '推测'), React.createElement('option', { key: 'conflicted', value: 'conflicted' }, '有冲突')])]),
|
|
389
|
+
React.createElement('p', { key: 'static', className: 'ckm-panel-hint' }, '知识图谱是静态结果,只能通过菜单栏完整重新生成;画布不支持编辑或节点发散。'),
|
|
390
|
+
React.createElement('svg', { key: 'svg', className: 'ckm-graph-svg', viewBox: '0 0 800 460', role: 'img', 'aria-label': '知识图谱' }, [
|
|
391
|
+
relations.map(function (relation) { var from = byId[relation.from]; var to = byId[relation.to]; return React.createElement('line', { key: relation.id, x1: from.x, y1: from.y, x2: to.x, y2: to.y, className: 'ckm-edge' }) }),
|
|
392
|
+
positions.map(function (item) { return React.createElement('g', { key: item.entity.id, className: 'ckm-graph-node', 'data-active': selected && selected.id === item.entity.id, onClick: function () { setSelected(item.entity) } }, [React.createElement('circle', { key: 'circle', cx: item.x, cy: item.y, r: 26 }), React.createElement('text', { key: 'text', x: item.x, y: item.y + 44, textAnchor: 'middle' }, item.entity.name.slice(0, 18))]) })
|
|
393
|
+
])
|
|
394
|
+
]),
|
|
395
|
+
React.createElement('aside', { key: 'detail', className: 'ckm-detail' }, [
|
|
396
|
+
selected ? [
|
|
397
|
+
React.createElement('span', { key: 'type', className: 'ckm-node-type' }, selected.type + ' · ' + selected.confidence),
|
|
398
|
+
React.createElement('h3', { key: 'name' }, selected.name),
|
|
399
|
+
React.createElement('p', { key: 'summary', className: 'ckm-narrative' }, selected.summary),
|
|
400
|
+
React.createElement('div', { key: 'sources', className: 'ckm-source-box' }, [
|
|
401
|
+
React.createElement('strong', { key: 'label' }, '来源证据'),
|
|
402
|
+
React.createElement('ul', { key: 'list' }, (selected.sourceRefs || []).map(function (ref) {
|
|
403
|
+
return React.createElement('li', { key: ref.sessionId + ref.eventSeqs.join(',') }, ref.sessionId.slice(0, 12) + ' · seq ' + ref.eventSeqs.join(', '))
|
|
404
|
+
}))
|
|
405
|
+
]),
|
|
406
|
+
React.createElement('div', { key: 'relations', className: 'ckm-source-box' }, [
|
|
407
|
+
React.createElement('strong', { key: 'label' }, '相关关系'),
|
|
408
|
+
React.createElement('ul', { key: 'list' }, relations.filter(function (relation) {
|
|
409
|
+
return relation.from === selected.id || relation.to === selected.id
|
|
410
|
+
}).map(function (relation) {
|
|
411
|
+
return React.createElement('li', { key: relation.id }, relation.type + ' → ' + (relation.from === selected.id ? relation.to : relation.from))
|
|
412
|
+
}))
|
|
413
|
+
])
|
|
414
|
+
] : React.createElement('div', { className: 'ckm-detail-empty' }, '选择实体查看摘要、置信度、关系和来源。')
|
|
415
|
+
])
|
|
416
|
+
])
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function KnowledgeHeaderAction(props) {
|
|
420
|
+
var pending = useNavigation().pending
|
|
421
|
+
var sessionId = resolveSessionId(props)
|
|
422
|
+
if (!sessionId) return null
|
|
423
|
+
if (pending && typeof props.open === 'function') {
|
|
424
|
+
return Button({ className: 'ckm-header-action ckm-header-pending', title: '打开目标对话并复制后续问题', onClick: function () {
|
|
425
|
+
props.open(pending.targetSessionId)
|
|
426
|
+
try { navigator.clipboard && navigator.clipboard.writeText(pending.question) } catch (_) {}
|
|
427
|
+
setPendingNavigation(null)
|
|
428
|
+
} }, '返回并复制问题')
|
|
429
|
+
}
|
|
430
|
+
return Button({ className: 'ckm-header-action', title: '打开知识视图配置', onClick: function () { setOverlayOpen(true) } }, '知识视图')
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function ConfigurationOverlay(props) {
|
|
434
|
+
var store = props.store
|
|
435
|
+
var sessionsPair = React.useState(null)
|
|
436
|
+
var available = sessionsPair[0]
|
|
437
|
+
var setAvailable = sessionsPair[1]
|
|
438
|
+
var selectedPair = React.useState([])
|
|
439
|
+
var selected = selectedPair[0]
|
|
440
|
+
var setSelected = selectedPair[1]
|
|
441
|
+
var modePair = React.useState('both')
|
|
442
|
+
var mode = modePair[0]
|
|
443
|
+
var setMode = modePair[1]
|
|
444
|
+
var promptPair = React.useState('')
|
|
445
|
+
var prompt = promptPair[0]
|
|
446
|
+
var setPrompt = promptPair[1]
|
|
447
|
+
var strictPair = React.useState(true)
|
|
448
|
+
var strict = strictPair[0]
|
|
449
|
+
var setStrict = strictPair[1]
|
|
450
|
+
var includePair = React.useState(false)
|
|
451
|
+
var includeSubagents = includePair[0]
|
|
452
|
+
var setIncludeSubagents = includePair[1]
|
|
453
|
+
var confirmationPair = React.useState(null)
|
|
454
|
+
var confirmation = confirmationPair[0]
|
|
455
|
+
var setConfirmation = confirmationPair[1]
|
|
456
|
+
var messagePair = React.useState('')
|
|
457
|
+
var message = messagePair[0]
|
|
458
|
+
var setMessage = messagePair[1]
|
|
459
|
+
var busyPair = React.useState(false)
|
|
460
|
+
var busy = busyPair[0]
|
|
461
|
+
var setBusy = busyPair[1]
|
|
462
|
+
React.useEffect(function () {
|
|
463
|
+
if (!store.context || !store.context.ready) return
|
|
464
|
+
request('/sessions?anchorSessionId=' + encodeURIComponent(store.sessionId) + '&includeSubagents=' + includeSubagents).then(function (body) {
|
|
465
|
+
setAvailable(body.sessions || [])
|
|
466
|
+
setSelected(function (old) { return old.length ? old.filter(function (id) { return (body.sessions || []).some(function (item) { return item.id === id }) }) : [store.sessionId] })
|
|
467
|
+
}).catch(function (error) { setMessage(error.message || String(error)) })
|
|
468
|
+
}, [store.context && store.context.cwd, includeSubagents])
|
|
469
|
+
function toggle(id) { setSelected(function (old) { return old.indexOf(id) >= 0 ? old.filter(function (item) { return item !== id }) : old.concat([id]) }) }
|
|
470
|
+
function confirm() {
|
|
471
|
+
if (!selected.length) { setMessage('至少选择一个对话。'); return }
|
|
472
|
+
setBusy(true)
|
|
473
|
+
request('/confirm', { method: 'POST', body: { anchorSessionId: store.sessionId, selectedSessionIds: selected, outputMode: mode, prompt: prompt, strict: strict, includeSubagents: includeSubagents, expectedRevision: store.state ? store.state.revision : 0 } }).then(function (body) { setConfirmation(body.confirmation); setMessage(''); setBusy(false) }).catch(function (error) { setMessage(error.message || String(error)); setBusy(false) })
|
|
474
|
+
}
|
|
475
|
+
function start() {
|
|
476
|
+
setBusy(true)
|
|
477
|
+
request('/generations', { method: 'POST', body: { token: confirmation.token, anchorSessionId: store.sessionId, selectedSessionIds: selected, outputMode: mode, prompt: prompt, strict: strict, includeSubagents: includeSubagents, expectedRevision: confirmation.revision } }).then(function (body) {
|
|
478
|
+
setConfirmation(null)
|
|
479
|
+
setBusy(false)
|
|
480
|
+
applyGeneration(store, body.generation)
|
|
481
|
+
openGenerationStream(store, body.generation.id)
|
|
482
|
+
}).catch(function (error) { setMessage(error.message || String(error)); setBusy(false) })
|
|
483
|
+
}
|
|
484
|
+
if (!store.context || !store.context.ready) return React.createElement('div', { className: 'ckm-modal-backdrop' }, React.createElement('div', { className: 'ckm-modal' }, [React.createElement('h3', { key: 'title' }, '知识视图'), React.createElement('p', { key: 'copy' }, '请先打开一个有明确工作路径的已有对话。'), Button({ key: 'close', className: 'ckm-primary', onClick: function () { setOverlayOpen(false) } }, '关闭')]))
|
|
485
|
+
return React.createElement('div', { className: 'ckm-modal-backdrop' }, React.createElement('div', { className: 'ckm-modal ckm-config-modal', role: 'dialog', 'aria-modal': 'true' }, confirmation ? [
|
|
486
|
+
React.createElement('h3', { key: 'title' }, '确认生成知识视图?'),
|
|
487
|
+
React.createElement('dl', { key: 'summary', className: 'ckm-confirm-summary' }, [React.createElement('dt', { key: 'cwd-label' }, '工作路径'), React.createElement('dd', { key: 'cwd' }, store.context.cwd), React.createElement('dt', { key: 'source-label' }, '来源'), React.createElement('dd', { key: 'sources' }, confirmation.selectedSessions.length + ' 个已选择对话'), React.createElement('dt', { key: 'output-label' }, '生成'), React.createElement('dd', { key: 'output' }, mode === 'both' ? '思维导图 + 知识图谱' : mode), React.createElement('dt', { key: 'strict-label' }, '约束'), React.createElement('dd', { key: 'strict' }, strict ? '严格约束已开启' : '普通约束'), React.createElement('dt', { key: 'save-label' }, '保存'), React.createElement('dd', { key: 'save' }, '.g-dsh-market-knowledge' + (confirmation.overwrite ? '(将替换已有结果)' : ''))]),
|
|
488
|
+
React.createElement('p', { key: 'note', className: 'ckm-warning' }, '确认后才会读取所选对话正文、调用模型并写入工作区;不会自动发送消息。'),
|
|
489
|
+
React.createElement('div', { key: 'actions', className: 'ckm-modal-actions' }, [Button({ key: 'back', className: 'ckm-secondary', onClick: function () { setConfirmation(null) }, disabled: busy }, '返回修改'), Button({ key: 'ok', className: 'ckm-primary', onClick: start, disabled: busy }, busy ? '生成中…' : '确认并生成')])
|
|
490
|
+
] : [
|
|
491
|
+
React.createElement('div', { key: 'head', className: 'ckm-modal-head' }, [React.createElement('h3', { key: 'title' }, '知识视图配置'), Button({ key: 'close', className: 'ckm-icon-close', onClick: function () { setOverlayOpen(false) } }, '×')]),
|
|
492
|
+
React.createElement('p', { key: 'cwd', className: 'ckm-workspace-label' }, '当前工作路径:' + store.context.cwd),
|
|
493
|
+
React.createElement('label', { key: 'sessions', className: 'ckm-field' }, [
|
|
494
|
+
'参与生成的对话',
|
|
495
|
+
React.createElement('div', { key: 'list', className: 'ckm-session-list' }, available ? available.map(function (item) {
|
|
496
|
+
return React.createElement('label', { key: item.id, className: 'ckm-session-option' }, [
|
|
497
|
+
React.createElement('input', { key: 'check', type: 'checkbox', checked: selected.indexOf(item.id) >= 0, onChange: function () { toggle(item.id) } }),
|
|
498
|
+
React.createElement('span', { key: 'copy' }, [
|
|
499
|
+
React.createElement('strong', { key: 'title' }, (item.current ? '当前对话:' : '') + item.title),
|
|
500
|
+
React.createElement('small', { key: 'meta' }, item.id.slice(0, 12) + ' · ' + (item.origin === 'subagent' ? '子 Agent' : '普通对话'))
|
|
501
|
+
])
|
|
502
|
+
])
|
|
503
|
+
}) : React.createElement('span', { key: 'loading' }, '正在加载对话列表…')),
|
|
504
|
+
React.createElement('label', { key: 'include', className: 'ckm-inline-field' }, [
|
|
505
|
+
React.createElement('input', { key: 'check', type: 'checkbox', checked: includeSubagents, onChange: function (event) { setIncludeSubagents(event.target.checked) } }),
|
|
506
|
+
'包含子 Agent 对话(默认关闭)'
|
|
507
|
+
])
|
|
508
|
+
]),
|
|
509
|
+
React.createElement('label', { key: 'mode', className: 'ckm-field' }, ['生成内容', React.createElement('select', { key: 'select', value: mode, onChange: function (event) { setMode(event.target.value) } }, [React.createElement('option', { key: 'both', value: 'both' }, '思维导图 + 知识图谱'), React.createElement('option', { key: 'mind', value: 'mind-map' }, '仅思维导图'), React.createElement('option', { key: 'graph', value: 'knowledge-graph' }, '仅知识图谱')])]),
|
|
510
|
+
React.createElement('label', { key: 'prompt', className: 'ckm-field' }, ['额外要求', React.createElement('textarea', { key: 'textarea', rows: 4, maxLength: 4000, value: prompt, placeholder: '请输入形成思维导图或知识图谱时需要遵守的 Prompt…', onChange: function (event) { setPrompt(event.target.value) } })]),
|
|
511
|
+
React.createElement('label', { key: 'strict', className: 'ckm-inline-field' }, [React.createElement('input', { key: 'check', type: 'checkbox', checked: strict, onChange: function (event) { setStrict(event.target.checked) } }), '严格约束模式(来源、路径、工具和写入均由 Host 校验)']),
|
|
512
|
+
message ? React.createElement('p', { key: 'message', className: 'ckm-error' }, message) : null,
|
|
513
|
+
React.createElement('div', { key: 'actions', className: 'ckm-modal-actions' }, [Button({ key: 'cancel', className: 'ckm-secondary', onClick: function () { setOverlayOpen(false) }, disabled: busy }, '取消'), Button({ key: 'next', className: 'ckm-primary', onClick: confirm, disabled: busy || !available || !selected.length }, busy ? '读取中…' : '下一步:确认')])
|
|
514
|
+
]))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function KnowledgeOverlay(props) {
|
|
518
|
+
var state = useOverlay()
|
|
519
|
+
var sessionId = useCurrentSessionId(props.sessions)
|
|
520
|
+
var store = useStore(sessionId)
|
|
521
|
+
if (!state.open) return null
|
|
522
|
+
return React.createElement(ConfigurationOverlay, { store: store })
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
var CSS = '\n' +
|
|
526
|
+
'.ckm-page,.ckm-modal,.ckm-detail,.ckm-empty{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base)}' +
|
|
527
|
+
'.ckm-page{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}' +
|
|
528
|
+
'.ckm-page-header{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:14px 20px;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1)}' +
|
|
529
|
+
'.ckm-page-header h2{margin:0;font-size:16px}.ckm-page-header p{margin:3px 0 0;color:var(--dsw-alias-label-secondary);font-size:11px;word-break:break-all}' +
|
|
530
|
+
'.ckm-page-actions,.ckm-modal-actions{display:flex;align-items:center;gap:8px}.ckm-tab,.ckm-secondary,.ckm-primary,.ckm-danger,.ckm-header-action{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 11px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;font-size:12px}.ckm-tab[data-active=true],.ckm-primary{border-color:var(--dsw-alias-button-info-fill);background:var(--dsw-alias-button-info-fill);color:var(--dsw-alias-label-primary-foreground)}.ckm-tab:hover,.ckm-secondary:hover,.ckm-header-action:hover{background:var(--dsw-alias-interactive-bg-hover)}.ckm-primary:hover{background:var(--dsw-alias-button-info-hover)}.ckm-danger{border-color:#d36b6b;color:#ffb7b7}.ckm-danger:hover{background:#d94a4a22}.ckm-page button:disabled,.ckm-modal button:disabled{opacity:.5;cursor:not-allowed}' +
|
|
531
|
+
'.ckm-empty{display:flex;flex:1;min-height:260px;flex-direction:column;align-items:center;justify-content:center;padding:28px;text-align:center}.ckm-empty-icon{display:grid;place-items:center;width:44px;height:44px;margin-bottom:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;color:var(--dsw-alias-state-business-primary);font-size:24px}.ckm-empty h2{margin:0 0 8px;font-size:17px}.ckm-empty p{max-width:520px;margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.7}' +
|
|
532
|
+
'.ckm-generation-strip{display:flex;align-items:center;gap:12px;padding:9px 20px;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-state-business-tertiary);font-size:12px}.ckm-generation-strip span{color:var(--dsw-alias-label-secondary)}.ckm-generation-strip .ckm-danger{margin-left:auto}' +
|
|
533
|
+
'.ckm-workspace{display:grid;grid-template-columns:minmax(0,1fr) 330px;min-height:0;flex:1}.ckm-mind-canvas,.ckm-graph-canvas{min-width:0;min-height:0;overflow:auto;padding:20px;background:var(--dsw-alias-bg-base)}.ckm-detail{min-width:0;min-height:0;overflow:auto;padding:20px;border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1)}.ckm-panel-hint{margin:0 0 14px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.6}.ckm-tree{display:flex;flex-direction:column;gap:8px;max-width:900px;margin:0 auto}.ckm-tree-node{display:flex;flex-direction:column;align-items:flex-start;gap:5px;width:calc(100% - 0px);padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer}.ckm-tree-node[data-active=true]{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}.ckm-tree-node:hover{background:var(--dsw-alias-interactive-bg-hover)}.ckm-tree-node strong{font-size:13px}.ckm-tree-node span:last-child{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.65}.ckm-node-type{display:inline-block;color:var(--dsw-alias-state-business-primary);font-size:10px;letter-spacing:.04em;text-transform:uppercase}.ckm-detail-head{display:flex;flex-direction:column;gap:5px}.ckm-detail h3{margin:0;font-size:16px;line-height:1.45}.ckm-narrative{font-size:13px;line-height:1.8}.ckm-source-box{margin:16px 0;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-base);font-size:11px;line-height:1.6}.ckm-source-box strong{display:block;margin-bottom:5px}.ckm-source-box ul{margin:0;padding-left:18px;color:var(--dsw-alias-label-secondary)}.ckm-detail-empty{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.7}' +
|
|
534
|
+
'.ckm-graph-layout{grid-template-columns:minmax(0,1fr) 330px}.ckm-graph-toolbar{display:flex;gap:8px}.ckm-graph-toolbar input,.ckm-graph-toolbar select,.ckm-field textarea,.ckm-field select,.ckm-field input,.ckm-modal textarea,.ckm-modal select{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 9px;background:var(--dsw-specific-input-major);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px}.ckm-graph-toolbar input{flex:1}.ckm-graph-svg{display:block;width:100%;min-height:420px;margin-top:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.ckm-edge{stroke:var(--dsw-alias-border-l2);stroke-width:1.5}.ckm-graph-node{cursor:pointer}.ckm-graph-node circle{fill:var(--dsw-alias-state-business-tertiary);stroke:var(--dsw-alias-state-business-primary);stroke-width:1.5}.ckm-graph-node[data-active=true] circle{fill:var(--dsw-alias-button-info-fill);stroke:var(--dsw-alias-label-primary)}.ckm-graph-node text{fill:var(--dsw-alias-label-primary);font-size:11px}' +
|
|
535
|
+
'.ckm-modal-backdrop{position:fixed;inset:0;z-index:120;display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(0,0,0,.46)}.ckm-modal{width:min(680px,calc(100vw - 40px));max-height:min(760px,calc(100vh - 40px));overflow:auto;padding:22px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;box-shadow:0 18px 56px #0008}.ckm-modal h3{margin:0;font-size:17px}.ckm-modal p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.65}.ckm-modal-head{display:flex;align-items:center;justify-content:space-between}.ckm-icon-close{border:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:20px;cursor:pointer}.ckm-workspace-label{padding:9px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);word-break:break-all}.ckm-field{display:flex;flex-direction:column;gap:7px;margin:14px 0;color:var(--dsw-alias-label-secondary);font-size:12px}.ckm-field textarea{resize:vertical}.ckm-session-list{display:flex;max-height:220px;flex-direction:column;gap:5px;overflow:auto}.ckm-session-option{display:flex;align-items:flex-start;gap:8px;padding:8px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-bg-layer-1);cursor:pointer}.ckm-session-option:hover{border-color:var(--dsw-alias-border-l2)}.ckm-session-option input,.ckm-inline-field input{margin-top:3px}.ckm-session-option span{display:flex;flex-direction:column;gap:3px}.ckm-session-option small{color:var(--dsw-alias-label-secondary);font-size:10px}.ckm-inline-field{display:flex;align-items:flex-start;gap:7px;margin:10px 0;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.5}.ckm-confirm-summary{display:grid;grid-template-columns:100px 1fr;gap:7px 12px;margin:18px 0;font-size:12px}.ckm-confirm-summary dt{color:var(--dsw-alias-label-secondary)}.ckm-confirm-summary dd{margin:0;word-break:break-all}.ckm-warning{padding:10px;border-radius:8px;background:#d29c2518;color:var(--dsw-alias-label-secondary)}.ckm-error{color:#ff9898!important}.ckm-modal-actions{justify-content:flex-end;margin-top:18px}.ckm-header-pending{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}' +
|
|
536
|
+
'@media(max-width:800px){.ckm-workspace,.ckm-graph-layout{display:flex;flex-direction:column}.ckm-detail{border-top:1px solid var(--dsw-alias-border-l2);border-left:0}.ckm-page-header{align-items:flex-start;flex-direction:column}.ckm-page-actions{width:100%;flex-wrap:wrap}}'
|
|
537
|
+
|
|
538
|
+
function apply(ctx) {
|
|
539
|
+
var sessions = ctx.sessions || (ctx.get && ctx.get('sessions'))
|
|
540
|
+
ctx.effect(function () {
|
|
541
|
+
var style = document.createElement('style')
|
|
542
|
+
style.dataset.plugin = PLUGIN_ID
|
|
543
|
+
style.textContent = CSS
|
|
544
|
+
document.head.appendChild(style)
|
|
545
|
+
return function () { style.remove() }
|
|
546
|
+
})
|
|
547
|
+
ctx.effect(function () {
|
|
548
|
+
function onDesktopMessage(event) {
|
|
549
|
+
if (event.source !== window.parent) return
|
|
550
|
+
var data = event.data
|
|
551
|
+
if (!data || data.source !== 'dsh-desktop') return
|
|
552
|
+
if (data.type === 'plugin-rpc' && data.pluginId === PLUGIN_ID && data.method === DESKTOP_RPC_METHOD) setOverlayOpen(data.open !== false)
|
|
553
|
+
}
|
|
554
|
+
window.addEventListener('message', onDesktopMessage)
|
|
555
|
+
return function () { window.removeEventListener('message', onDesktopMessage) }
|
|
556
|
+
})
|
|
557
|
+
ctx.slots.inject('conversation.view', function () {
|
|
558
|
+
return ctx.slots.register({ name: 'conversation.view', id: 'conversation-knowledge-map', order: 35, label: '思维与知识' }, function (props) { return React.createElement(KnowledgeView, props) })
|
|
559
|
+
})
|
|
560
|
+
ctx.slots.inject('conversation.session.header.actions', function () {
|
|
561
|
+
return ctx.slots.register({ name: 'conversation.session.header.actions', id: 'conversation-knowledge-map', order: 35, label: '知识视图' }, KnowledgeHeaderAction)
|
|
562
|
+
})
|
|
563
|
+
ctx.slots.inject('shell.overlay', function () {
|
|
564
|
+
return ctx.slots.register({ name: 'shell.overlay', id: 'conversation-knowledge-map', order: 125, label: '知识视图配置' }, function () { return React.createElement(KnowledgeOverlay, { sessions: sessions }) })
|
|
565
|
+
})
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
exports.inject = ['slots', 'sessions']
|
|
569
|
+
exports.apply = apply
|
|
570
|
+
exports.resolveSessionId = resolveSessionId
|
|
571
|
+
exports.currentSessionId = currentSessionId
|
|
572
|
+
exports.setOverlayOpen = setOverlayOpen
|
|
573
|
+
return module.exports
|
|
574
|
+
}
|
|
575
|
+
})
|