dsh-topic-graph 0.0.0-stage → 0.6.41
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.en.md +266 -0
- package/README.md +264 -2
- package/cordis.patch.yml +36 -0
- package/icon.svg +25 -0
- package/lib/client.js +3502 -0
- package/lib/index.js +2959 -0
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +62 -4
package/lib/client.js
ADDED
|
@@ -0,0 +1,3502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-topic-graph — 会话话题图谱(Client 半)
|
|
3
|
+
*
|
|
4
|
+
* dsh 模块加载器的私有 bundle 格式:window.__ModuleLoader__.load({ id, factory })。
|
|
5
|
+
* factory 是浏览器 CJS 形状,必须自行声明 module/exports;react 由 shell 提供。
|
|
6
|
+
*
|
|
7
|
+
* 注册进 conversation.view(session 级 list 槽位,「对话 / 轨迹」之后的新视图),
|
|
8
|
+
* 渲染一个可拖动 / 可缩放 / 可收起的悬浮窗,含两个 Tab:
|
|
9
|
+
* 1. 链式:阶段 → 话题(含子话题)→ 轮次明细(token、工具、Agent 耗时与占比);
|
|
10
|
+
* 2. 图谱:阶段 / 话题 / 子话题的树状图,Ctrl+滚轮缩放、滚轮上下滚动、
|
|
11
|
+
* 鼠标悬停显示完整信息。
|
|
12
|
+
*
|
|
13
|
+
* 数据全部来自 Host 半的 /plugins/topic-graph/* 路由;样式只使用
|
|
14
|
+
* --dsw-alias-* 主题 token(带兜底值),跟随明暗主题。
|
|
15
|
+
*/
|
|
16
|
+
window.__ModuleLoader__.load({
|
|
17
|
+
id: 'dsh-topic-graph',
|
|
18
|
+
factory: (require) => {
|
|
19
|
+
var module = { exports: {} };
|
|
20
|
+
var exports = module.exports;
|
|
21
|
+
|
|
22
|
+
var React = require('react');
|
|
23
|
+
var useState = React.useState;
|
|
24
|
+
var useEffect = React.useEffect;
|
|
25
|
+
var useRef = React.useRef;
|
|
26
|
+
var useMemo = React.useMemo;
|
|
27
|
+
var useCallback = React.useCallback;
|
|
28
|
+
var h = React.createElement;
|
|
29
|
+
|
|
30
|
+
var ROUTE = '/plugins/topic-graph';
|
|
31
|
+
var CLIENT_VERSION = '0.6.41';
|
|
32
|
+
var LOC_NS = 'dsh-topic-graph';
|
|
33
|
+
var UI_KEY = 'dsh-topic-graph.ui.v2';
|
|
34
|
+
var UI_KEY_V1 = 'dsh-topic-graph.ui.v1';
|
|
35
|
+
var DEFAULT_PANEL_W = 900;
|
|
36
|
+
var DEFAULT_PANEL_H = 680;
|
|
37
|
+
|
|
38
|
+
// ── 文案(同时注册进 dsh 的 locale 服务) ────────────────────────────────
|
|
39
|
+
var DICT = {
|
|
40
|
+
zh: {
|
|
41
|
+
title: '话题图谱',
|
|
42
|
+
panelTitle: '话题',
|
|
43
|
+
tabChain: '话题·工作流',
|
|
44
|
+
tabGraph: '话题·图谱',
|
|
45
|
+
followLatest: '跟随左侧当前会话',
|
|
46
|
+
pickSession: '选择会话',
|
|
47
|
+
noSession: '没有可用的会话',
|
|
48
|
+
loading: '加载中…',
|
|
49
|
+
noTurns: '本会话还没有轮次',
|
|
50
|
+
noTopics: '还没有切分话题',
|
|
51
|
+
segmenting: 'AI 正在切分…',
|
|
52
|
+
waitSegment: '等待 AI 切分',
|
|
53
|
+
pendingTurns: '待切分 {n} 轮',
|
|
54
|
+
aiFailed: 'AI 切分失败',
|
|
55
|
+
retry: '重试',
|
|
56
|
+
segNow: '增量切分',
|
|
57
|
+
resegment: '全部重切',
|
|
58
|
+
collapsed: '已收起',
|
|
59
|
+
expand: '展开',
|
|
60
|
+
collapse: '收起',
|
|
61
|
+
refresh: '刷新',
|
|
62
|
+
zoomIn: '放大',
|
|
63
|
+
zoomOut: '缩小',
|
|
64
|
+
zoomReset: '重置缩放',
|
|
65
|
+
graphHint: 'Ctrl+滚轮缩放 · 滚轮上下滑动 · 悬停看详情',
|
|
66
|
+
modelLabel: '模型',
|
|
67
|
+
modelDefault: '默认(配置)',
|
|
68
|
+
workflowEmpty: '点击左侧话题或阶段查看详情',
|
|
69
|
+
turns: '轮次',
|
|
70
|
+
topics: '话题',
|
|
71
|
+
tokens: 'tokens',
|
|
72
|
+
toolCalls: '工具调用',
|
|
73
|
+
agentTime: 'Agent 耗时',
|
|
74
|
+
stepTime: '步骤活动时间',
|
|
75
|
+
requestCount: '模型调用',
|
|
76
|
+
steps: '步骤',
|
|
77
|
+
statusActive: '进行中',
|
|
78
|
+
statusDone: '已完成',
|
|
79
|
+
statusAborted: '被中断',
|
|
80
|
+
statusError: '出错',
|
|
81
|
+
statusOpen: '运行中',
|
|
82
|
+
sourceLlm: 'AI',
|
|
83
|
+
sourceRule: '规则',
|
|
84
|
+
stage: '阶段',
|
|
85
|
+
topic: '话题',
|
|
86
|
+
subtopics: '子话题',
|
|
87
|
+
tokenBreakdown: 'Token 明细',
|
|
88
|
+
input: '输入',
|
|
89
|
+
output: '输出',
|
|
90
|
+
cacheRead: '缓存读',
|
|
91
|
+
cacheWrite: '缓存写',
|
|
92
|
+
toolErrors: '工具错误',
|
|
93
|
+
prompt: '用户输入',
|
|
94
|
+
assistant: 'AI 回复',
|
|
95
|
+
emptyPrompt: '(无文本输入)',
|
|
96
|
+
toolDistribution: '工具分布',
|
|
97
|
+
range: '轮次',
|
|
98
|
+
ratioOfTotal: '占会话',
|
|
99
|
+
sessionStats: '{turns} 轮 · {topics} 话题 · {tokens} tokens',
|
|
100
|
+
updatedAt: '更新于 {time}',
|
|
101
|
+
aiMode: '切分模式:{mode}',
|
|
102
|
+
noTopicsYet: 'AI 正在为这个会话生成话题结构…',
|
|
103
|
+
ruleFallback: '当前为规则切分(未使用 AI)',
|
|
104
|
+
turnLabel: '轮 {n}',
|
|
105
|
+
openTurn: '进行中',
|
|
106
|
+
sessionPickerSearch: '筛选会话…',
|
|
107
|
+
unnamed: '未命名会话',
|
|
108
|
+
dragHint: '可拖动标题栏移动,右下角拖拽缩放',
|
|
109
|
+
subagent: '子智能体',
|
|
110
|
+
subagents: '子智能体',
|
|
111
|
+
subagentCount: '子智能体 {n}',
|
|
112
|
+
branchTurns: '{n} 轮',
|
|
113
|
+
branchOpen: '查看该子智能体会话',
|
|
114
|
+
backToParent: '返回父会话',
|
|
115
|
+
relations: '关联话题',
|
|
116
|
+
relationFrom: '→ {title} · {label}',
|
|
117
|
+
relationTo: '← {title} · {label}',
|
|
118
|
+
edgeNext: '顺序',
|
|
119
|
+
edgeBelong: '归属',
|
|
120
|
+
edgeRelation: '关联',
|
|
121
|
+
edgeDelegate: '委托',
|
|
122
|
+
parentTopic: '挂靠话题',
|
|
123
|
+
childTopics: '子话题',
|
|
124
|
+
branchTopics: '话题',
|
|
125
|
+
branchPending: '切分中…',
|
|
126
|
+
subagentTotals: '子智能体合计',
|
|
127
|
+
graphLegendNext: 'next 顺序',
|
|
128
|
+
graphLegendBelong: 'belongTo 归属',
|
|
129
|
+
graphLegendRelation: 'has_relation 关联',
|
|
130
|
+
graphModeTimeline: '时间线',
|
|
131
|
+
graphModeTree: '结构图',
|
|
132
|
+
graphLegendDelegate: 'delegates 委托',
|
|
133
|
+
statsBar: '统计',
|
|
134
|
+
statTurns: '轮次',
|
|
135
|
+
statTopics: '话题',
|
|
136
|
+
statStage: '阶段',
|
|
137
|
+
statTokens: 'Token',
|
|
138
|
+
statTools: '工具',
|
|
139
|
+
totalSteps: '总步数',
|
|
140
|
+
jumpToTurn: '跳转到对话',
|
|
141
|
+
expandAll: '全部展开',
|
|
142
|
+
collapseAll: '全部收缩',
|
|
143
|
+
viewWorkflow: '工作流',
|
|
144
|
+
viewGraph: '图谱',
|
|
145
|
+
noSessionTitle: '未选择会话',
|
|
146
|
+
statStepTime: '步骤耗时',
|
|
147
|
+
statWallTime: '总耗时',
|
|
148
|
+
statRelations: '关联',
|
|
149
|
+
stageTopicRelations: '阶段内话题关系',
|
|
150
|
+
statSubagentTokens: '子智能体 Token',
|
|
151
|
+
detailTitle: '话题详情',
|
|
152
|
+
detailOpen: '查看详情',
|
|
153
|
+
openAtFirstTurn: '在对话中打开首轮',
|
|
154
|
+
locating: '正在定位…',
|
|
155
|
+
located: '已在对话中定位到该轮',
|
|
156
|
+
openedOnly: '已打开会话(历史未完全加载,未能自动定位)',
|
|
157
|
+
detailStage: '所属阶段',
|
|
158
|
+
detailParent: '父话题',
|
|
159
|
+
detailChildren: '子话题',
|
|
160
|
+
detailBranches: '子智能体分支',
|
|
161
|
+
detailRelated: '关联话题',
|
|
162
|
+
detailTurns: '本轮话题的轮次',
|
|
163
|
+
close: '关闭',
|
|
164
|
+
jumpHint: '点击话题打开详情;详情内可跳回该话题首轮',
|
|
165
|
+
collapseStage: '收起阶段下的内容',
|
|
166
|
+
expandStage: '展开阶段下的内容',
|
|
167
|
+
collapseTopic: '收起子话题',
|
|
168
|
+
expandTopic: '展开子话题',
|
|
169
|
+
collapseBranch: '收起分支内的话题',
|
|
170
|
+
expandBranch: '展开分支内的话题',
|
|
171
|
+
statIncludesSubtopic: '统计含子话题',
|
|
172
|
+
aiCost: 'AI 切分消耗',
|
|
173
|
+
aiCallsUnit: '次',
|
|
174
|
+
analyzeTurn: 'AI 分析',
|
|
175
|
+
reanalyzeTurn: '重新分析',
|
|
176
|
+
analyzing: 'AI 分析中…',
|
|
177
|
+
analyzeFailed: 'AI 分析失败',
|
|
178
|
+
howtoTitle: '这一轮做了什么(AI 生成)',
|
|
179
|
+
howtoEmpty: '暂无分析,点「AI 分析」生成一份通俗的分步说明',
|
|
180
|
+
stepTools: '工具',
|
|
181
|
+
},
|
|
182
|
+
en: {
|
|
183
|
+
title: 'Topic Graph',
|
|
184
|
+
panelTitle: 'Topics',
|
|
185
|
+
tabChain: 'Topics · Workflow',
|
|
186
|
+
tabGraph: 'Topics · Graph',
|
|
187
|
+
followLatest: 'Follow sidebar session',
|
|
188
|
+
pickSession: 'Sessions',
|
|
189
|
+
noSession: 'No sessions available',
|
|
190
|
+
loading: 'Loading…',
|
|
191
|
+
noTurns: 'This session has no turns yet',
|
|
192
|
+
noTopics: 'No topics segmented yet',
|
|
193
|
+
segmenting: 'AI is segmenting…',
|
|
194
|
+
waitSegment: 'Waiting for AI segmentation',
|
|
195
|
+
pendingTurns: '{n} turns pending',
|
|
196
|
+
aiFailed: 'AI segmentation failed',
|
|
197
|
+
retry: 'Retry',
|
|
198
|
+
segNow: 'Incremental',
|
|
199
|
+
resegment: 'Re-segment all',
|
|
200
|
+
collapsed: 'Collapsed',
|
|
201
|
+
expand: 'Expand',
|
|
202
|
+
collapse: 'Collapse',
|
|
203
|
+
refresh: 'Refresh',
|
|
204
|
+
zoomIn: 'Zoom in',
|
|
205
|
+
zoomOut: 'Zoom out',
|
|
206
|
+
zoomReset: 'Reset zoom',
|
|
207
|
+
graphHint: 'Ctrl+wheel to zoom · wheel to scroll · hover for details',
|
|
208
|
+
turns: 'turns',
|
|
209
|
+
topics: 'topics',
|
|
210
|
+
tokens: 'tokens',
|
|
211
|
+
toolCalls: 'Tool calls',
|
|
212
|
+
agentTime: 'Agent time',
|
|
213
|
+
stepTime: 'Step active time',
|
|
214
|
+
requestCount: 'Model calls',
|
|
215
|
+
steps: 'Steps',
|
|
216
|
+
statusActive: 'Active',
|
|
217
|
+
statusDone: 'Done',
|
|
218
|
+
statusAborted: 'Aborted',
|
|
219
|
+
statusError: 'Error',
|
|
220
|
+
statusOpen: 'Running',
|
|
221
|
+
sourceLlm: 'AI',
|
|
222
|
+
sourceRule: 'Rule',
|
|
223
|
+
stage: 'Stage',
|
|
224
|
+
topic: 'Topic',
|
|
225
|
+
subtopics: 'subtopics',
|
|
226
|
+
tokenBreakdown: 'Token breakdown',
|
|
227
|
+
input: 'Input',
|
|
228
|
+
output: 'Output',
|
|
229
|
+
cacheRead: 'Cache read',
|
|
230
|
+
cacheWrite: 'Cache write',
|
|
231
|
+
toolErrors: 'Tool errors',
|
|
232
|
+
prompt: 'User',
|
|
233
|
+
assistant: 'AI',
|
|
234
|
+
emptyPrompt: '(no text input)',
|
|
235
|
+
toolDistribution: 'Tool distribution',
|
|
236
|
+
range: 'Turns',
|
|
237
|
+
ratioOfTotal: 'of session',
|
|
238
|
+
sessionStats: '{turns} turns · {topics} topics · {tokens} tokens',
|
|
239
|
+
updatedAt: 'updated {time}',
|
|
240
|
+
aiMode: 'Mode: {mode}',
|
|
241
|
+
noTopicsYet: 'AI is building the topic structure for this session…',
|
|
242
|
+
ruleFallback: 'Rule-based segmentation (AI not used)',
|
|
243
|
+
turnLabel: 'Turn {n}',
|
|
244
|
+
openTurn: 'running',
|
|
245
|
+
sessionPickerSearch: 'Filter sessions…',
|
|
246
|
+
unnamed: 'Untitled session',
|
|
247
|
+
dragHint: 'Drag the header to move, drag the corner to resize',
|
|
248
|
+
subagent: 'Subagent',
|
|
249
|
+
subagents: 'Subagents',
|
|
250
|
+
subagentCount: '{n} subagents',
|
|
251
|
+
branchTurns: '{n} turns',
|
|
252
|
+
branchOpen: 'Open this subagent session',
|
|
253
|
+
backToParent: 'Back to parent',
|
|
254
|
+
relations: 'Related topics',
|
|
255
|
+
relationFrom: '→ {title} · {label}',
|
|
256
|
+
relationTo: '← {title} · {label}',
|
|
257
|
+
edgeNext: 'next',
|
|
258
|
+
edgeBelong: 'belongTo',
|
|
259
|
+
edgeRelation: 'has_relation',
|
|
260
|
+
edgeDelegate: 'delegates',
|
|
261
|
+
parentTopic: 'attached topic',
|
|
262
|
+
childTopics: 'Subtopics',
|
|
263
|
+
branchTopics: 'topics',
|
|
264
|
+
branchPending: 'segmenting…',
|
|
265
|
+
subagentTotals: 'Subagent totals',
|
|
266
|
+
graphLegendNext: 'next',
|
|
267
|
+
graphLegendBelong: 'belongTo',
|
|
268
|
+
graphLegendRelation: 'has_relation',
|
|
269
|
+
graphModeTimeline: 'Timeline',
|
|
270
|
+
graphModeTree: 'Structure',
|
|
271
|
+
graphLegendDelegate: 'delegates',
|
|
272
|
+
statsBar: 'Stats',
|
|
273
|
+
statTurns: 'Turns',
|
|
274
|
+
statTopics: 'Topics',
|
|
275
|
+
statStage: 'Stages',
|
|
276
|
+
statTokens: 'Tokens',
|
|
277
|
+
statTools: 'Tools',
|
|
278
|
+
totalSteps: 'Steps',
|
|
279
|
+
jumpToTurn: 'Jump to chat',
|
|
280
|
+
expandAll: 'Expand all',
|
|
281
|
+
collapseAll: 'Collapse all',
|
|
282
|
+
viewWorkflow: 'Workflow',
|
|
283
|
+
viewGraph: 'Graph',
|
|
284
|
+
noSessionTitle: 'No session',
|
|
285
|
+
statStepTime: 'Step time',
|
|
286
|
+
statWallTime: 'Wall time',
|
|
287
|
+
statRelations: 'Relations',
|
|
288
|
+
stageTopicRelations: 'Topic relations in this stage',
|
|
289
|
+
statSubagentTokens: 'Subagent tokens',
|
|
290
|
+
detailTitle: 'Topic detail',
|
|
291
|
+
detailOpen: 'Details',
|
|
292
|
+
openAtFirstTurn: 'Open first turn in chat',
|
|
293
|
+
locating: 'Locating…',
|
|
294
|
+
located: 'Located in chat',
|
|
295
|
+
openedOnly: 'Session opened (history not fully loaded; could not locate)',
|
|
296
|
+
detailStage: 'Stage',
|
|
297
|
+
detailParent: 'Parent topic',
|
|
298
|
+
detailChildren: 'Subtopics',
|
|
299
|
+
detailBranches: 'Subagent branches',
|
|
300
|
+
detailRelated: 'Related topics',
|
|
301
|
+
detailTurns: 'Turns in this topic',
|
|
302
|
+
close: 'Close',
|
|
303
|
+
jumpHint: 'Click a topic for details; jump to its first turn from there',
|
|
304
|
+
collapseStage: 'Collapse topics in this stage',
|
|
305
|
+
expandStage: 'Expand topics in this stage',
|
|
306
|
+
collapseTopic: 'Collapse subtopics',
|
|
307
|
+
expandTopic: 'Expand subtopics',
|
|
308
|
+
collapseBranch: 'Collapse branch topics',
|
|
309
|
+
expandBranch: 'Expand branch topics',
|
|
310
|
+
statIncludesSubtopic: 'incl. subtopics',
|
|
311
|
+
aiCost: 'AI cost',
|
|
312
|
+
aiCallsUnit: 'calls',
|
|
313
|
+
analyzeTurn: 'AI analyze',
|
|
314
|
+
reanalyzeTurn: 'Re-analyze',
|
|
315
|
+
analyzing: 'Analyzing…',
|
|
316
|
+
analyzeFailed: 'AI analysis failed',
|
|
317
|
+
howtoTitle: 'What this turn did (AI)',
|
|
318
|
+
howtoEmpty: 'No analysis yet. Click "AI analyze" for a plain-language step-by-step.',
|
|
319
|
+
stepTools: 'Tools',
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
var localeService = null;
|
|
324
|
+
var translate = null;
|
|
325
|
+
var appCtx = null;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 左侧工作区当前选中的会话由 shell 持久化在 localStorage 的 `dsh.sessions.current`
|
|
329
|
+
* (快照存储的 persist 名称),值形如 { sessionId, subagentAddress? }。
|
|
330
|
+
* 这里只读取这一个公开持久化键,轮询即可跟随左侧切换。
|
|
331
|
+
*/
|
|
332
|
+
function readSidebarCurrentSession() {
|
|
333
|
+
try {
|
|
334
|
+
var raw = localStorage.getItem('dsh.sessions.current');
|
|
335
|
+
if (!raw) return null;
|
|
336
|
+
var value = JSON.parse(raw);
|
|
337
|
+
var id = value && (value.sessionId || (value.state && value.state.sessionId));
|
|
338
|
+
return typeof id === 'string' && id ? id : null;
|
|
339
|
+
} catch (error) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function summarizeText(text, max) {
|
|
345
|
+
var value = String(text == null ? '' : text).replace(/\s+/g, ' ').trim();
|
|
346
|
+
if (value.length <= max) return value;
|
|
347
|
+
return value.slice(0, max) + '…';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function sleep(ms) {
|
|
351
|
+
return new Promise(function (resolve) { setTimeout(resolve, ms); });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** 找聊天区的滚动容器(从聊天 DOM 反查最近的可滚动祖先,只用于跳转定位)。 */
|
|
355
|
+
function findChatScroller() {
|
|
356
|
+
try {
|
|
357
|
+
var el = document.querySelector('[data-chat-flow]') || document.querySelector('[data-chat-turn]');
|
|
358
|
+
while (el && el !== document.body) {
|
|
359
|
+
var style = null;
|
|
360
|
+
try { style = window.getComputedStyle(el); } catch (error) { style = null; }
|
|
361
|
+
if (style && /(auto|scroll)/.test(style.overflowY) && el.scrollHeight > el.clientHeight + 40) return el;
|
|
362
|
+
el = el.parentElement;
|
|
363
|
+
}
|
|
364
|
+
} catch (error) { /* ignore */ }
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** 尽力把聊天滚到某一轮:轮次未加载时反复滚动顶部触发历史分页。 */
|
|
369
|
+
async function revealChatTurn(turnNumber) {
|
|
370
|
+
if (turnNumber == null) return false;
|
|
371
|
+
for (var attempt = 0; attempt < 16; attempt += 1) {
|
|
372
|
+
var target = null;
|
|
373
|
+
try { target = document.querySelector('[data-chat-turn="' + String(turnNumber) + '"]'); } catch (error) { target = null; }
|
|
374
|
+
if (target) {
|
|
375
|
+
try { target.scrollIntoView({ block: 'center', behavior: 'smooth' }); } catch (error) { try { target.scrollIntoView(); } catch (e2) { /* ignore */ } }
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
var scroller = findChatScroller();
|
|
379
|
+
if (scroller) {
|
|
380
|
+
try { scroller.scrollTop = 0; } catch (error) { /* ignore */ }
|
|
381
|
+
}
|
|
382
|
+
await sleep(attempt < 4 ? 220 : 380);
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function initLocale(ctx) {
|
|
388
|
+
try {
|
|
389
|
+
var service = typeof ctx.get === 'function' ? ctx.get('locale') : ctx.locale;
|
|
390
|
+
if (service && typeof service.register === 'function') {
|
|
391
|
+
localeService = service;
|
|
392
|
+
service.register(LOC_NS, DICT);
|
|
393
|
+
if (typeof service.bind === 'function') translate = service.bind(LOC_NS);
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
localeService = null;
|
|
397
|
+
translate = null;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function localeId() {
|
|
402
|
+
try {
|
|
403
|
+
if (localeService && typeof localeService.getSnapshot === 'function') {
|
|
404
|
+
var snap = localeService.getSnapshot();
|
|
405
|
+
var active = snap && snap.active ? String(snap.active) : '';
|
|
406
|
+
if (active) return active.toLowerCase().indexOf('zh') === 0 ? 'zh' : 'en';
|
|
407
|
+
}
|
|
408
|
+
} catch (error) { /* 忽略 */ }
|
|
409
|
+
try {
|
|
410
|
+
return String(navigator.language || '').toLowerCase().indexOf('zh') === 0 ? 'zh' : 'en';
|
|
411
|
+
} catch (error) {
|
|
412
|
+
return 'zh';
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** 文案:优先走 dsh locale 服务(已注册同名 namespace),无服务时用内置字典兜底。 */
|
|
417
|
+
function t(key, params) {
|
|
418
|
+
var value = '';
|
|
419
|
+
if (translate) {
|
|
420
|
+
try { value = translate(key, params); } catch (error) { value = ''; }
|
|
421
|
+
}
|
|
422
|
+
if (!value || value === key) {
|
|
423
|
+
var dict = DICT[localeId()] || DICT.zh;
|
|
424
|
+
value = dict[key] != null ? dict[key] : (DICT.zh[key] != null ? DICT.zh[key] : key);
|
|
425
|
+
}
|
|
426
|
+
if (params && typeof value === 'string') {
|
|
427
|
+
value = value.replace(/\{(\w+)\}/g, function (match, name) {
|
|
428
|
+
return params[name] != null ? String(params[name]) : match;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return value;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** 订阅 locale 变化:切换语言后重渲染。 */
|
|
435
|
+
function useLocaleTick() {
|
|
436
|
+
var subscribe = useCallback(function (onChange) {
|
|
437
|
+
if (!localeService || typeof localeService.subscribe !== 'function') return function () {};
|
|
438
|
+
try { return localeService.subscribe(onChange); } catch (error) { return function () {}; }
|
|
439
|
+
}, []);
|
|
440
|
+
var snapshot = useCallback(function () {
|
|
441
|
+
try {
|
|
442
|
+
if (localeService && typeof localeService.getSnapshot === 'function') {
|
|
443
|
+
var snap = localeService.getSnapshot();
|
|
444
|
+
return snap && snap.revision != null ? snap.revision : 0;
|
|
445
|
+
}
|
|
446
|
+
} catch (error) { /* 忽略 */ }
|
|
447
|
+
return 0;
|
|
448
|
+
}, []);
|
|
449
|
+
if (typeof React.useSyncExternalStore === 'function') {
|
|
450
|
+
try { React.useSyncExternalStore(subscribe, snapshot, snapshot); } catch (error) { /* 忽略 */ }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ── 小工具 ──────────────────────────────────────────────────────────────
|
|
455
|
+
function clamp(value, min, max) {
|
|
456
|
+
return Math.max(min, Math.min(max, value));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function fmtK(value) {
|
|
460
|
+
var n = Number(value) || 0;
|
|
461
|
+
if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B';
|
|
462
|
+
if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
|
|
463
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k';
|
|
464
|
+
return String(Math.round(n));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function fmtPct(ratio) {
|
|
468
|
+
var n = Number(ratio);
|
|
469
|
+
if (!Number.isFinite(n) || n <= 0) return '0%';
|
|
470
|
+
var pct = n * 100;
|
|
471
|
+
if (pct >= 10) return pct.toFixed(0) + '%';
|
|
472
|
+
if (pct >= 1) return pct.toFixed(1) + '%';
|
|
473
|
+
return pct.toFixed(2) + '%';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function fmtDuration(ms) {
|
|
477
|
+
var n = Number(ms);
|
|
478
|
+
if (!Number.isFinite(n) || n <= 0) return '0s';
|
|
479
|
+
var sec = Math.round(n / 1000);
|
|
480
|
+
if (sec < 60) return sec + 's';
|
|
481
|
+
var min = Math.floor(sec / 60);
|
|
482
|
+
var rem = sec % 60;
|
|
483
|
+
if (min < 60) return min + 'm' + (rem ? String(rem).padStart(2, '0') + 's' : '');
|
|
484
|
+
var hr = Math.floor(min / 60);
|
|
485
|
+
return hr + 'h' + String(min % 60).padStart(2, '0') + 'm';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function fmtClock(ms) {
|
|
489
|
+
if (!ms) return '';
|
|
490
|
+
try {
|
|
491
|
+
var d = new Date(ms);
|
|
492
|
+
var hh = String(d.getHours()).padStart(2, '0');
|
|
493
|
+
var mm = String(d.getMinutes()).padStart(2, '0');
|
|
494
|
+
return hh + ':' + mm;
|
|
495
|
+
} catch (error) { return ''; }
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function sessionShortId(id) {
|
|
499
|
+
return String(id || '').replace(/^session-/, '').slice(0, 8);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function sameSession(a, b) {
|
|
503
|
+
return String(a || '').replace(/^session-/, '') === String(b || '').replace(/^session-/, '');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** 面板默认宽度:尽量宽(用户反馈可以加倍),小屏时自动收进视口。 */
|
|
507
|
+
function defaultPanelWidth() {
|
|
508
|
+
try {
|
|
509
|
+
return clamp(Math.min(DEFAULT_PANEL_W, window.innerWidth - 48), 380, 1600);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
return DEFAULT_PANEL_W;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function loadUi() {
|
|
516
|
+
var fallback = {
|
|
517
|
+
x: null, y: null, w: defaultPanelWidth(),
|
|
518
|
+
h: clamp(DEFAULT_PANEL_H, 320, Math.max(360, (window.innerHeight || 900) - 16)),
|
|
519
|
+
collapsed: false, tab: 'chain', pinned: null,
|
|
520
|
+
graphCollapsed: { stages: {}, topics: {}, branches: {} },
|
|
521
|
+
scale: 1,
|
|
522
|
+
};
|
|
523
|
+
function normalize(raw, keepWidth) {
|
|
524
|
+
if (!raw || typeof raw !== 'object') return fallback;
|
|
525
|
+
var width = defaultPanelWidth();
|
|
526
|
+
if (keepWidth) {
|
|
527
|
+
width = clamp(Number(raw.w) || width, 380, Math.max(420, (window.innerWidth || 1200) - 16));
|
|
528
|
+
}
|
|
529
|
+
// 迁移旧版窄面板的位置:宽度变大后要收进视口,否则会跑到屏幕外
|
|
530
|
+
var maxX = Math.max(4, (window.innerWidth || 1200) - width - 8);
|
|
531
|
+
var rawCollapsed = raw.graphCollapsed;
|
|
532
|
+
var graphCollapsed = {
|
|
533
|
+
stages: (rawCollapsed && typeof rawCollapsed.stages === 'object' && rawCollapsed.stages) || {},
|
|
534
|
+
topics: (rawCollapsed && typeof rawCollapsed.topics === 'object' && rawCollapsed.topics) || {},
|
|
535
|
+
branches: (rawCollapsed && typeof rawCollapsed.branches === 'object' && rawCollapsed.branches) || {},
|
|
536
|
+
};
|
|
537
|
+
return {
|
|
538
|
+
x: typeof raw.x === 'number' ? clamp(raw.x, 4, maxX) : null,
|
|
539
|
+
y: typeof raw.y === 'number' ? clamp(raw.y, 4, Math.max(4, (window.innerHeight || 900) - 60)) : null,
|
|
540
|
+
w: width,
|
|
541
|
+
h: clamp(Number(raw.h) || DEFAULT_PANEL_H, 320, Math.max(360, (window.innerHeight || 900) - 16)),
|
|
542
|
+
collapsed: raw.collapsed === true,
|
|
543
|
+
tab: raw.tab === 'graph' ? 'graph' : 'chain',
|
|
544
|
+
pinned: typeof raw.pinned === 'string' && raw.pinned ? raw.pinned : null,
|
|
545
|
+
graphCollapsed: graphCollapsed,
|
|
546
|
+
scale: (typeof raw.scale === 'number' && raw.scale >= 0.35 && raw.scale <= 2.5) ? raw.scale : 1,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
var raw = JSON.parse(localStorage.getItem(UI_KEY) || 'null');
|
|
551
|
+
if (raw && typeof raw === 'object') return normalize(raw, true);
|
|
552
|
+
// v1 → v2:保留位置 / 收起 / 标签 / 固定会话,但宽度改用新的默认宽度(旧默认太窄)
|
|
553
|
+
var old = JSON.parse(localStorage.getItem(UI_KEY_V1) || 'null');
|
|
554
|
+
if (old && typeof old === 'object') return normalize(old, false);
|
|
555
|
+
return fallback;
|
|
556
|
+
} catch (error) {
|
|
557
|
+
return fallback;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function saveUi(ui) {
|
|
562
|
+
try { localStorage.setItem(UI_KEY, JSON.stringify(ui)); } catch (error) { /* 忽略 */ }
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function fetchJson(url) {
|
|
566
|
+
try {
|
|
567
|
+
var response = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
568
|
+
if (!response.ok) return null;
|
|
569
|
+
return await response.json();
|
|
570
|
+
} catch (error) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async function postJson(url, body) {
|
|
576
|
+
try {
|
|
577
|
+
var response = await fetch(url, {
|
|
578
|
+
method: 'POST',
|
|
579
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
580
|
+
body: JSON.stringify(body || {}),
|
|
581
|
+
});
|
|
582
|
+
if (!response.ok) return null;
|
|
583
|
+
return await response.json();
|
|
584
|
+
} catch (error) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ── 图谱布局(纯函数) ──────────────────────────────────────────────────
|
|
590
|
+
var LAYOUT = {
|
|
591
|
+
padX: 22,
|
|
592
|
+
indent: 30,
|
|
593
|
+
nodeH: 44,
|
|
594
|
+
gap: 16,
|
|
595
|
+
branchW: 300,
|
|
596
|
+
branchHead: 26,
|
|
597
|
+
childNodeH: 46,
|
|
598
|
+
childGap: 8,
|
|
599
|
+
childIndent: 14,
|
|
600
|
+
railW: 56,
|
|
601
|
+
railGap: 12,
|
|
602
|
+
stageHead: 36,
|
|
603
|
+
stageGap: 24,
|
|
604
|
+
width: 1000,
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* 图谱布局:左侧主列是阶段 → 根话题 → 子话题的树;右侧把子智能体会话
|
|
609
|
+
* 展开成「分支框 + 子智能体自己的话题树」,与主图同屏显示。
|
|
610
|
+
* 同时把 Host 的逻辑边(next / belongTo / has_relation / delegates)
|
|
611
|
+
* 以及子智能体内部的话题归属换算成 SVG 路径。
|
|
612
|
+
*/
|
|
613
|
+
function buildLayout(graph, options) {
|
|
614
|
+
var opts = options || {};
|
|
615
|
+
var canvasW = Number(opts.width) > 0 ? Math.max(620, Number(opts.width)) : LAYOUT.width;
|
|
616
|
+
// 兼容两种传法:{ stages, topics, branches } 或 { collapsedStages, ... }
|
|
617
|
+
var collapsedStages = opts.stages || opts.collapsedStages || {};
|
|
618
|
+
var collapsedTopics = opts.topics || opts.collapsedTopics || {};
|
|
619
|
+
var collapsedBranches = opts.branches || opts.collapsedBranches || {};
|
|
620
|
+
var topics = Array.isArray(graph && graph.topics) ? graph.topics : [];
|
|
621
|
+
var stages = Array.isArray(graph && graph.stages) ? graph.stages : [];
|
|
622
|
+
var branchList = Array.isArray(graph && graph.branches) ? graph.branches : [];
|
|
623
|
+
var graphEdges = (Array.isArray(graph && graph.edges) ? graph.edges : []).filter(function (edge) {
|
|
624
|
+
return !(edge.type === 'has_relation' && edge.kind === 'extension');
|
|
625
|
+
});
|
|
626
|
+
if (!graphEdges.length && topics.length) {
|
|
627
|
+
var rootList = topics.filter(function (topic) { return !topic.parentId; });
|
|
628
|
+
graphEdges = [];
|
|
629
|
+
for (var ri = 0; ri < rootList.length; ri += 1) {
|
|
630
|
+
var rootNode = rootList[ri];
|
|
631
|
+
if (rootNode.stageId) graphEdges.push({ id: 'belong:' + rootNode.id, type: 'belongTo', kind: 'stage', from: rootNode.id, to: rootNode.stageId });
|
|
632
|
+
if (ri > 0) graphEdges.push({ id: 'next:' + rootList[ri - 1].id + '>' + rootNode.id, type: 'next', from: rootList[ri - 1].id, to: rootNode.id });
|
|
633
|
+
}
|
|
634
|
+
topics.forEach(function (topic) {
|
|
635
|
+
if (topic.parentId) graphEdges.push({ id: 'belong:' + topic.id, type: 'belongTo', kind: 'topic', from: topic.id, to: topic.parentId });
|
|
636
|
+
(Array.isArray(topic.branchIds) ? topic.branchIds : []).forEach(function (sessionId) {
|
|
637
|
+
graphEdges.push({ id: 'deleg:' + topic.id + '>' + sessionId, type: 'delegates', from: topic.id, to: sessionId, label: '子智能体' });
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
var byId = {};
|
|
642
|
+
topics.forEach(function (topic) { byId[topic.id] = topic; });
|
|
643
|
+
var branchBySession = {};
|
|
644
|
+
branchList.forEach(function (branch) { branchBySession[branch.sessionId] = branch; });
|
|
645
|
+
var children = {};
|
|
646
|
+
topics.forEach(function (topic) {
|
|
647
|
+
if (!topic.parentId || !byId[topic.parentId]) return;
|
|
648
|
+
if (!children[topic.parentId]) children[topic.parentId] = [];
|
|
649
|
+
children[topic.parentId].push(topic);
|
|
650
|
+
});
|
|
651
|
+
var hasBranches = branchList.length > 0;
|
|
652
|
+
var branchW = hasBranches ? LAYOUT.branchW : 0;
|
|
653
|
+
var mainW = Math.min(canvasW - 8 - branchW - LAYOUT.railGap * 2 - LAYOUT.railW, 660);
|
|
654
|
+
var railLeft = mainW + LAYOUT.railGap;
|
|
655
|
+
var railRight = railLeft + LAYOUT.railW;
|
|
656
|
+
var railX = (railLeft + railRight) / 2;
|
|
657
|
+
var branchLeft = railRight + LAYOUT.railGap;
|
|
658
|
+
var nodes = [];
|
|
659
|
+
var branchNodes = [];
|
|
660
|
+
var branchBoxes = [];
|
|
661
|
+
var stageBoxes = [];
|
|
662
|
+
var synthesizedEdges = [];
|
|
663
|
+
var y = 14;
|
|
664
|
+
|
|
665
|
+
function branchesOf(topic) {
|
|
666
|
+
var out = [];
|
|
667
|
+
(Array.isArray(topic.branchIds) ? topic.branchIds : []).forEach(function (sessionId) {
|
|
668
|
+
if (branchBySession[sessionId] && out.indexOf(branchBySession[sessionId]) === -1) out.push(branchBySession[sessionId]);
|
|
669
|
+
});
|
|
670
|
+
branchList.forEach(function (branch) {
|
|
671
|
+
if (branch.parentTopicId === topic.id && out.indexOf(branch) === -1) out.push(branch);
|
|
672
|
+
});
|
|
673
|
+
return out;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** 子智能体分支:头部节点 + 它自己的话题树。返回该分支占用的高度。 */
|
|
677
|
+
function layoutBranch(branch, parentNode, top) {
|
|
678
|
+
var left = branchLeft;
|
|
679
|
+
var header = {
|
|
680
|
+
kind: 'branchHeader',
|
|
681
|
+
id: 'branchh:' + branch.sessionId,
|
|
682
|
+
branch: branch,
|
|
683
|
+
parentNodeId: parentNode.id,
|
|
684
|
+
x: left,
|
|
685
|
+
y: top,
|
|
686
|
+
w: LAYOUT.branchW,
|
|
687
|
+
h: LAYOUT.branchHead,
|
|
688
|
+
depth: parentNode.depth + 1,
|
|
689
|
+
};
|
|
690
|
+
var branchCollapsed = collapsedBranches[branch.sessionId] === true;
|
|
691
|
+
header.collapsed = branchCollapsed;
|
|
692
|
+
header.topicCount = Array.isArray(branch.topics) ? branch.topics.length : 0;
|
|
693
|
+
branchNodes.push(header);
|
|
694
|
+
var branchTopics = Array.isArray(branch.topics) ? branch.topics : [];
|
|
695
|
+
var childById = {};
|
|
696
|
+
branchTopics.forEach(function (topic) { childById[topic.id] = topic; });
|
|
697
|
+
var childChildren = {};
|
|
698
|
+
branchTopics.forEach(function (topic) {
|
|
699
|
+
if (!topic.parentId || !childById[topic.parentId]) return;
|
|
700
|
+
if (!childChildren[topic.parentId]) childChildren[topic.parentId] = [];
|
|
701
|
+
childChildren[topic.parentId].push(topic);
|
|
702
|
+
});
|
|
703
|
+
var cursor = top + LAYOUT.branchHead + (branchTopics.length ? 6 : 2);
|
|
704
|
+
var childNodeById = {};
|
|
705
|
+
function layoutChild(topic, depth) {
|
|
706
|
+
var node = {
|
|
707
|
+
kind: 'branchTopic',
|
|
708
|
+
id: 'brancht:' + branch.sessionId + ':' + topic.id,
|
|
709
|
+
branch: branch,
|
|
710
|
+
topic: topic,
|
|
711
|
+
parentNodeId: parentNode.id,
|
|
712
|
+
x: left + 8 + depth * LAYOUT.childIndent,
|
|
713
|
+
y: cursor,
|
|
714
|
+
w: LAYOUT.branchW - 16 - depth * LAYOUT.childIndent,
|
|
715
|
+
h: LAYOUT.childNodeH,
|
|
716
|
+
depth: parentNode.depth + 1 + depth,
|
|
717
|
+
childDepth: depth,
|
|
718
|
+
};
|
|
719
|
+
branchNodes.push(node);
|
|
720
|
+
childNodeById[topic.id] = node;
|
|
721
|
+
cursor += LAYOUT.childNodeH + LAYOUT.childGap;
|
|
722
|
+
(childChildren[topic.id] || []).forEach(function (child) {
|
|
723
|
+
var childNode = layoutChild(child, depth + 1);
|
|
724
|
+
synthesizedEdges.push({ id: 'belong:' + node.id + '>' + childNode.id, type: 'belongTo', kind: 'topic', path: directPath(node, childNode) });
|
|
725
|
+
});
|
|
726
|
+
return node;
|
|
727
|
+
}
|
|
728
|
+
var roots = branchTopics.filter(function (topic) { return !topic.parentId || !childById[topic.parentId]; });
|
|
729
|
+
if (!branchCollapsed) {
|
|
730
|
+
roots.forEach(function (topic) {
|
|
731
|
+
var childNode = layoutChild(topic, 0);
|
|
732
|
+
synthesizedEdges.push({ id: 'belong:' + header.id + '>' + childNode.id, type: 'belongTo', kind: 'branch', path: headerToChildPath(header, childNode) });
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
(branch.relations || []).forEach(function (rel) {
|
|
736
|
+
var fromNode = childNodeById[rel.from];
|
|
737
|
+
var toNode = childNodeById[rel.to];
|
|
738
|
+
if (!fromNode || !toNode) return;
|
|
739
|
+
var fY = fromNode.y + fromNode.h / 2;
|
|
740
|
+
var tY = toNode.y + toNode.h / 2;
|
|
741
|
+
synthesizedEdges.push({
|
|
742
|
+
id: 'rel:' + branch.sessionId + ':' + rel.from + '>' + rel.to,
|
|
743
|
+
type: 'has_relation',
|
|
744
|
+
label: rel.label,
|
|
745
|
+
marker: 'rel',
|
|
746
|
+
path: railElbow(fromNode.x, fY, toNode.x, tY),
|
|
747
|
+
labelX: railX + 6,
|
|
748
|
+
labelY: (fY + tY) / 2 + 3,
|
|
749
|
+
labelBg: labelBox(rel.label, railX, (fY + tY) / 2),
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
var bottom = Math.max(cursor - LAYOUT.childGap + 3, header.y + LAYOUT.branchHead + 4);
|
|
753
|
+
branchBoxes.push({
|
|
754
|
+
kind: 'branchBox',
|
|
755
|
+
id: 'branchb:' + branch.sessionId,
|
|
756
|
+
branch: branch,
|
|
757
|
+
x: left,
|
|
758
|
+
y: top - 4,
|
|
759
|
+
w: LAYOUT.branchW,
|
|
760
|
+
h: bottom - top + 8,
|
|
761
|
+
});
|
|
762
|
+
return bottom - top;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function directPath(parent, child) {
|
|
766
|
+
var y1 = parent.y + parent.h;
|
|
767
|
+
var y2 = child.y;
|
|
768
|
+
var dy = Math.max(12, (y2 - y1) / 2);
|
|
769
|
+
return 'M ' + (parent.x + 18) + ' ' + y1
|
|
770
|
+
+ ' C ' + (parent.x + 18) + ' ' + (y1 + dy) + ', ' + (child.x + 18) + ' ' + (y2 - dy) + ', ' + (child.x + 18) + ' ' + y2;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function headerToChildPath(header, child) {
|
|
774
|
+
var y1 = header.y + header.h;
|
|
775
|
+
var y2 = child.y;
|
|
776
|
+
var dy = Math.max(10, (y2 - y1) / 2);
|
|
777
|
+
return 'M ' + (header.x + 16) + ' ' + y1
|
|
778
|
+
+ ' C ' + (header.x + 16) + ' ' + (y1 + dy) + ', ' + (child.x + 16) + ' ' + (y2 - dy) + ', ' + (child.x + 16) + ' ' + y2;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function layoutTopic(topic, depth) {
|
|
782
|
+
var collapsed = collapsedTopics[topic.id] === true;
|
|
783
|
+
var node = {
|
|
784
|
+
kind: 'topic',
|
|
785
|
+
id: topic.id,
|
|
786
|
+
topic: topic,
|
|
787
|
+
collapsed: collapsed,
|
|
788
|
+
childCount: (children[topic.id] || []).length,
|
|
789
|
+
x: LAYOUT.padX + depth * LAYOUT.indent,
|
|
790
|
+
y: y,
|
|
791
|
+
w: Math.min(mainW - LAYOUT.padX * 2 - depth * LAYOUT.indent, 560),
|
|
792
|
+
h: LAYOUT.nodeH,
|
|
793
|
+
depth: depth,
|
|
794
|
+
};
|
|
795
|
+
nodes.push(node);
|
|
796
|
+
var attached = branchesOf(topic);
|
|
797
|
+
var branchStack = 0;
|
|
798
|
+
var branchTop = node.y + 4;
|
|
799
|
+
attached.forEach(function (branch) {
|
|
800
|
+
branchStack += layoutBranch(branch, node, branchTop) + 26;
|
|
801
|
+
branchTop = node.y + 4 + branchStack;
|
|
802
|
+
});
|
|
803
|
+
y = node.y + Math.max(node.h, branchStack) + LAYOUT.gap;
|
|
804
|
+
if (!collapsed) {
|
|
805
|
+
(children[topic.id] || []).forEach(function (child) {
|
|
806
|
+
layoutTopic(child, depth + 1);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return node;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
stages.forEach(function (stage) {
|
|
813
|
+
var collapsed = collapsedStages[stage.id] === true;
|
|
814
|
+
var roots = (stage.topicIds || []).map(function (id) { return byId[id]; }).filter(Boolean);
|
|
815
|
+
var top = y;
|
|
816
|
+
y += LAYOUT.stageHead + 4;
|
|
817
|
+
if (!collapsed) {
|
|
818
|
+
roots.forEach(function (root) { layoutTopic(root, 0); });
|
|
819
|
+
}
|
|
820
|
+
var bottom = collapsed
|
|
821
|
+
? top + LAYOUT.stageHead + 8
|
|
822
|
+
: Math.max(y - LAYOUT.gap + 10, top + LAYOUT.stageHead + 8);
|
|
823
|
+
stageBoxes.push({
|
|
824
|
+
kind: 'stage',
|
|
825
|
+
id: stage.id,
|
|
826
|
+
stage: stage,
|
|
827
|
+
collapsed: collapsed,
|
|
828
|
+
topicCount: roots.length,
|
|
829
|
+
x: 8,
|
|
830
|
+
y: top,
|
|
831
|
+
w: mainW - 8,
|
|
832
|
+
h: bottom - top,
|
|
833
|
+
depth: 0,
|
|
834
|
+
});
|
|
835
|
+
y = bottom + LAYOUT.stageGap;
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
var nodeByTopic = {};
|
|
839
|
+
nodes.forEach(function (node) { nodeByTopic[node.id] = node; });
|
|
840
|
+
var boxByStage = {};
|
|
841
|
+
stageBoxes.forEach(function (box) { boxByStage[box.id] = box; });
|
|
842
|
+
var headerByBranch = {};
|
|
843
|
+
branchNodes.forEach(function (node) { if (node.kind === 'branchHeader') headerByBranch[node.branch.sessionId] = node; });
|
|
844
|
+
|
|
845
|
+
/** 平滑曲线:端点先向轨道方向伸展,再平滑折回另一端点(单段三次贝塞尔)。 */
|
|
846
|
+
function railElbow(x1, y1, x2, y2) {
|
|
847
|
+
return 'M ' + x1 + ' ' + y1
|
|
848
|
+
+ ' C ' + railX + ' ' + y1
|
|
849
|
+
+ ', ' + railX + ' ' + y2
|
|
850
|
+
+ ', ' + x2 + ' ' + y2;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/** 关系标签的底框(按字符宽度估算)。 */
|
|
854
|
+
function labelBox(text, x, y) {
|
|
855
|
+
var str = String(text == null ? '' : text);
|
|
856
|
+
var w = 10;
|
|
857
|
+
for (var i = 0; i < str.length; i += 1) {
|
|
858
|
+
w += str.charCodeAt(i) > 0x2e80 ? 10 : 6;
|
|
859
|
+
}
|
|
860
|
+
return { x: x - w / 2, y: y - 8, w: w, h: 16 };
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function curve(x1, y1, x2, y2, horizontal) {
|
|
864
|
+
if (horizontal) {
|
|
865
|
+
var dx = Math.max(24, Math.abs(x2 - x1) / 2);
|
|
866
|
+
return 'M ' + x1 + ' ' + y1
|
|
867
|
+
+ ' C ' + (x1 + dx) + ' ' + y1 + ', ' + (x2 - dx) + ' ' + y2 + ', ' + x2 + ' ' + y2;
|
|
868
|
+
}
|
|
869
|
+
var dy = Math.max(24, Math.abs(y2 - y1) / 2);
|
|
870
|
+
return 'M ' + x1 + ' ' + y1
|
|
871
|
+
+ ' C ' + x1 + ' ' + (y1 + dy) + ', ' + x2 + ' ' + (y2 - dy) + ', ' + x2 + ' ' + y2;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
var edges = []
|
|
875
|
+
graphEdges.forEach(function (edge, index) {
|
|
876
|
+
if (!edge || !edge.type) return;
|
|
877
|
+
var out = { id: edge.id || ('e' + index), type: edge.type, label: edge.label || '' };
|
|
878
|
+
if (edge.type === 'next') {
|
|
879
|
+
var a = nodeByTopic[edge.from];
|
|
880
|
+
var b = nodeByTopic[edge.to];
|
|
881
|
+
if (!a || !b) return;
|
|
882
|
+
out.path = railElbow(a.x + a.w, a.y + a.h / 2, b.x + b.w, b.y + b.h / 2);
|
|
883
|
+
out.marker = 'next';
|
|
884
|
+
} else if (edge.type === 'belongTo' && edge.kind === 'stage') {
|
|
885
|
+
var node = nodeByTopic[edge.from];
|
|
886
|
+
var box = boxByStage[edge.to];
|
|
887
|
+
if (!node || !box) return;
|
|
888
|
+
out.path = 'M ' + (box.x + 10) + ' ' + (node.y + 16) + ' L ' + node.x + ' ' + (node.y + 16);
|
|
889
|
+
} else if (edge.type === 'belongTo') {
|
|
890
|
+
var parent = nodeByTopic[edge.to];
|
|
891
|
+
var child = nodeByTopic[edge.from];
|
|
892
|
+
if (!parent || !child) return;
|
|
893
|
+
out.path = curve(parent.x + 26, parent.y + parent.h, child.x + 26, child.y, false);
|
|
894
|
+
} else if (edge.type === 'has_relation') {
|
|
895
|
+
var src = nodeByTopic[edge.from];
|
|
896
|
+
var dst = nodeByTopic[edge.to];
|
|
897
|
+
if (!src || !dst) return;
|
|
898
|
+
var srcY = src.y + src.h / 2;
|
|
899
|
+
var dstY = dst.y + dst.h / 2;
|
|
900
|
+
out.path = railElbow(src.x + src.w, srcY, dst.x + dst.w, dstY);
|
|
901
|
+
out.marker = 'rel';
|
|
902
|
+
out.label = edge.label || '';
|
|
903
|
+
out.labelX = railX;
|
|
904
|
+
out.labelY = (srcY + dstY) / 2 + 3;
|
|
905
|
+
out.labelBg = labelBox(out.label, railX, (srcY + dstY) / 2);
|
|
906
|
+
} else if (edge.type === 'delegates') {
|
|
907
|
+
var sourceNode = nodeByTopic[edge.from];
|
|
908
|
+
var headerNode = headerByBranch[edge.to];
|
|
909
|
+
if (!sourceNode || !headerNode) return;
|
|
910
|
+
var sY = sourceNode.y + sourceNode.h / 2;
|
|
911
|
+
var hY = headerNode.y + headerNode.h / 2;
|
|
912
|
+
out.path = railElbow(sourceNode.x + sourceNode.w, sY, headerNode.x, hY);
|
|
913
|
+
out.marker = 'deleg';
|
|
914
|
+
out.label = edge.label || '';
|
|
915
|
+
out.labelX = railX;
|
|
916
|
+
out.labelY = (sY + hY) / 2 + 3;
|
|
917
|
+
out.labelBg = labelBox(out.label, railX, (sY + hY) / 2);
|
|
918
|
+
} else {
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
edges.push(out);
|
|
922
|
+
});
|
|
923
|
+
synthesizedEdges.forEach(function (edge) { edges.push(edge); });
|
|
924
|
+
|
|
925
|
+
return {
|
|
926
|
+
width: canvasW,
|
|
927
|
+
height: Math.max(y + 10, 240),
|
|
928
|
+
nodes: nodes,
|
|
929
|
+
branchNodes: branchNodes,
|
|
930
|
+
branchBoxes: branchBoxes,
|
|
931
|
+
edges: edges,
|
|
932
|
+
stageBoxes: stageBoxes,
|
|
933
|
+
edgeTypes: edges.map(function (edge) { return edge.type; }),
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
var TIMELINE_PALETTE = ['#679efe', '#22c55e', '#eab308', '#a855f7', '#f97316', '#06b6d4', '#ec4899'];
|
|
938
|
+
|
|
939
|
+
/** 时间线布局:X = 轮次(时间),Y = 阶段泳道;话题是按其轮次跨度定位的细横线/点。 */
|
|
940
|
+
function buildTimelineLayout(graph, viewportWidth) {
|
|
941
|
+
var topics = Array.isArray(graph && graph.topics) ? graph.topics : [];
|
|
942
|
+
var stages = Array.isArray(graph && graph.stages) ? graph.stages : [];
|
|
943
|
+
var edges = Array.isArray(graph && graph.edges) ? graph.edges : [];
|
|
944
|
+
var roots = topics.filter(function (t) { return !t.parentId; })
|
|
945
|
+
.sort(function (a, b) { return (a.turnStart || 0) - (b.turnStart || 0); });
|
|
946
|
+
var indexById = {};
|
|
947
|
+
roots.forEach(function (t, i) { indexById[t.id] = i + 1; });
|
|
948
|
+
var padLeft = 16;
|
|
949
|
+
var padRight = 16;
|
|
950
|
+
var rulerH = 22;
|
|
951
|
+
var laneH = 38;
|
|
952
|
+
var barH = 4;
|
|
953
|
+
var totalTurns = 1;
|
|
954
|
+
topics.forEach(function (t) { totalTurns = Math.max(totalTurns, t.turnEnd || t.turnStart || 1); });
|
|
955
|
+
// 尽量一页显示:按可视宽度反推每轮像素
|
|
956
|
+
var avail = Math.max(320, (viewportWidth || 900) - padLeft - padRight);
|
|
957
|
+
var turnW = Math.min(40, Math.max(2.5, avail / Math.max(1, totalTurns)));
|
|
958
|
+
var width = padLeft + totalTurns * turnW + padRight;
|
|
959
|
+
var lanes = [];
|
|
960
|
+
var posById = {};
|
|
961
|
+
stages.forEach(function (stage, si) {
|
|
962
|
+
var laneTopics = roots.filter(function (t) { return t.stageId === stage.id; });
|
|
963
|
+
var y = rulerH + si * laneH;
|
|
964
|
+
lanes.push({ stage: stage, index: si, y: y, h: laneH, topics: laneTopics });
|
|
965
|
+
laneTopics.forEach(function (topic) {
|
|
966
|
+
var startTurn = topic.turnStart || 1;
|
|
967
|
+
var endTurn = topic.turnEnd || startTurn;
|
|
968
|
+
var span = Math.max(1, endTurn - startTurn + 1);
|
|
969
|
+
posById[topic.id] = {
|
|
970
|
+
x: padLeft + (startTurn - 1) * turnW + 0.5,
|
|
971
|
+
y: y + (laneH - barH) / 2,
|
|
972
|
+
w: Math.max(6, span * turnW - 1),
|
|
973
|
+
h: barH,
|
|
974
|
+
topic: topic,
|
|
975
|
+
stage: stage,
|
|
976
|
+
index: indexById[topic.id],
|
|
977
|
+
};
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
var height = rulerH + Math.max(lanes.length, 1) * laneH + 10;
|
|
981
|
+
var rawStep = Math.max(1, 56 / turnW);
|
|
982
|
+
var niceSteps = [1, 2, 5, 10, 20, 50, 100, 200, 500];
|
|
983
|
+
var step = niceSteps.filter(function (n) { return n >= rawStep; })[0] || 500;
|
|
984
|
+
var ticks = [];
|
|
985
|
+
for (var tn = 1; tn <= totalTurns; tn += step) ticks.push({ turn: tn, x: padLeft + (tn - 1) * turnW });
|
|
986
|
+
var relations = [];
|
|
987
|
+
function bez(t, p0, p1, p2, p3) {
|
|
988
|
+
var mt = 1 - t;
|
|
989
|
+
return mt * mt * mt * p0 + 3 * mt * mt * t * p1 + 3 * mt * t * t * p2 + t * t * t * p3;
|
|
990
|
+
}
|
|
991
|
+
// 时间上相邻的 topic 对(同阶段相邻 / 相邻阶段首尾):若它们之间只有 1 条关系边,视为冗余不画
|
|
992
|
+
var adjacentPairs = {};
|
|
993
|
+
for (var ap = 0; ap < roots.length - 1; ap += 1) {
|
|
994
|
+
adjacentPairs[roots[ap].id + '>' + roots[ap + 1].id] = true;
|
|
995
|
+
adjacentPairs[roots[ap + 1].id + '>' + roots[ap].id] = true;
|
|
996
|
+
}
|
|
997
|
+
var relEdges = edges.filter(function (e) { return e.type === 'has_relation'; });
|
|
998
|
+
var pairCount = {};
|
|
999
|
+
relEdges.forEach(function (e) {
|
|
1000
|
+
var pairKey = [e.from, e.to].sort().join('>');
|
|
1001
|
+
pairCount[pairKey] = (pairCount[pairKey] || 0) + 1;
|
|
1002
|
+
});
|
|
1003
|
+
relEdges = relEdges.filter(function (e) {
|
|
1004
|
+
var pairKey = [e.from, e.to].sort().join('>');
|
|
1005
|
+
var isAdjacent = adjacentPairs[e.from + '>' + e.to] === true;
|
|
1006
|
+
if (isAdjacent && pairCount[pairKey] <= 1) return false;
|
|
1007
|
+
return true;
|
|
1008
|
+
});
|
|
1009
|
+
relEdges.forEach(function (e) {
|
|
1010
|
+
var a = posById[e.from];
|
|
1011
|
+
var b = posById[e.to];
|
|
1012
|
+
if (!a || !b) return;
|
|
1013
|
+
var startX;
|
|
1014
|
+
var startY;
|
|
1015
|
+
var endX;
|
|
1016
|
+
var endY;
|
|
1017
|
+
var c1y;
|
|
1018
|
+
var c2y;
|
|
1019
|
+
var path;
|
|
1020
|
+
var sameLane = a.stage === b.stage;
|
|
1021
|
+
if (sameLane) {
|
|
1022
|
+
if (b.x >= a.x) { startX = a.x + a.w + 2; endX = b.x - 2; }
|
|
1023
|
+
else { startX = a.x - 2; endX = b.x + b.w + 2; }
|
|
1024
|
+
startY = a.y + a.h / 2;
|
|
1025
|
+
endY = b.y + b.h / 2;
|
|
1026
|
+
var lift = 14;
|
|
1027
|
+
c1y = startY - lift;
|
|
1028
|
+
c2y = endY - lift;
|
|
1029
|
+
path = 'M ' + startX + ' ' + startY + ' C ' + startX + ' ' + c1y + ', ' + endX + ' ' + c2y + ', ' + endX + ' ' + endY;
|
|
1030
|
+
} else {
|
|
1031
|
+
var down = b.y > a.y;
|
|
1032
|
+
startX = a.x + a.w / 2;
|
|
1033
|
+
endX = b.x + b.w / 2;
|
|
1034
|
+
startY = down ? a.y + a.h + 2 : a.y - 2;
|
|
1035
|
+
endY = down ? b.y - 2 : b.y + b.h + 2;
|
|
1036
|
+
var spanY = endY - startY;
|
|
1037
|
+
c1y = startY + spanY * 0.45;
|
|
1038
|
+
c2y = endY - spanY * 0.45;
|
|
1039
|
+
path = 'M ' + startX + ' ' + startY + ' C ' + startX + ' ' + c1y + ', ' + endX + ' ' + c2y + ', ' + endX + ' ' + endY;
|
|
1040
|
+
}
|
|
1041
|
+
// 标签沿曲线错开分布 + 向右偏移,避免压线/互相重叠
|
|
1042
|
+
var idx = relations.length;
|
|
1043
|
+
var t = 0.3 + ((idx * 0.173) % 0.4);
|
|
1044
|
+
var labelX = bez(t, startX, startX, endX, endX) + 10;
|
|
1045
|
+
var labelY = bez(t, startY, c1y, c2y, endY) - 2;
|
|
1046
|
+
relations.push({
|
|
1047
|
+
id: e.id || ('tlrel:' + e.from + '>' + e.to),
|
|
1048
|
+
path: path,
|
|
1049
|
+
label: e.label || '',
|
|
1050
|
+
labelX: labelX,
|
|
1051
|
+
labelY: labelY,
|
|
1052
|
+
from: e.from,
|
|
1053
|
+
to: e.to,
|
|
1054
|
+
sameLane: sameLane,
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
var nexts = [];
|
|
1058
|
+
edges.filter(function (e) { return e.type === 'next'; }).forEach(function (e) {
|
|
1059
|
+
var a = posById[e.from];
|
|
1060
|
+
var b = posById[e.to];
|
|
1061
|
+
if (!a || !b) return;
|
|
1062
|
+
var ax = a.x + a.w;
|
|
1063
|
+
var ay = a.y + a.h / 2;
|
|
1064
|
+
var bx = b.x;
|
|
1065
|
+
var by = b.y + b.h / 2;
|
|
1066
|
+
nexts.push({ id: e.id || ('tlnext:' + e.from + '>' + e.to), path: 'M ' + ax + ' ' + ay + ' C ' + (ax + 8) + ' ' + ay + ', ' + (bx - 8) + ' ' + by + ', ' + bx + ' ' + by });
|
|
1067
|
+
});
|
|
1068
|
+
return {
|
|
1069
|
+
width: width, height: height, lanes: lanes, posById: posById,
|
|
1070
|
+
relations: relations, nexts: nexts, ticks: ticks,
|
|
1071
|
+
rulerH: rulerH, laneH: laneH, barH: barH, padLeft: padLeft, turnW: turnW,
|
|
1072
|
+
totalTurns: totalTurns, indexById: indexById,
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// ── 样式(只使用主题 token,带兜底值) ─────────────────────────────────
|
|
1077
|
+
var CSS = [
|
|
1078
|
+
'[class*="viewArea"]:has(> .tg-root),:has(> .tg-root){flex:1 1 0% !important;min-height:0 !important;overflow:hidden !important}',
|
|
1079
|
+
'.tg-root{position:relative;width:100%;height:100%;flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;',
|
|
1080
|
+
' background:transparent;color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1081
|
+
'.tg-debug-banner{position:fixed;top:0;left:0;z-index:99999;background:#e11;color:#fff;padding:3px 8px;font-size:11px;font-weight:700}',
|
|
1082
|
+
'.tg-root-docked{position:relative;left:auto;top:auto;width:100%;height:100%;flex:1;min-height:0;z-index:auto;',
|
|
1083
|
+
' border-radius:0;border:0;box-shadow:none;overflow:hidden;background:transparent}',
|
|
1084
|
+
'.tg-head-docked{cursor:default}',
|
|
1085
|
+
'.tg-model-select{flex:none;max-width:220px;height:26px;border-radius:7px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));',
|
|
1086
|
+
' background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08));color:var(--dsw-alias-label-primary,#f4f4f5);font-size:11px;padding:0 6px}',
|
|
1087
|
+
'.tg-topbar{flex:none;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16))}',
|
|
1088
|
+
'.tg-topbar-row{display:flex;align-items:center;gap:8px;padding:9px 12px}',
|
|
1089
|
+
'.tg-topbar-spacer{flex:1}',
|
|
1090
|
+
' font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Arial,sans-serif}',
|
|
1091
|
+
'.tg-root *{box-sizing:border-box}',
|
|
1092
|
+
'.tg-head{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:move;user-select:none;',
|
|
1093
|
+
' background:linear-gradient(180deg,rgba(127,127,127,.10),rgba(127,127,127,.02));',
|
|
1094
|
+
' border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1095
|
+
'.tg-logo{width:22px;height:22px;border-radius:7px;flex:none;display:flex;align-items:center;justify-content:center;',
|
|
1096
|
+
' background:linear-gradient(135deg,var(--dsw-alias-brand-primary,#679efe),#2563eb);color:#fff;font-size:12px;font-weight:700}',
|
|
1097
|
+
'.tg-title{font-weight:600;font-size:13px;letter-spacing:.2px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1098
|
+
'.tg-head-sub{font-size:10px;font-weight:500;color:var(--dsw-alias-label-secondary,#adb2b8);margin-left:6px}',
|
|
1099
|
+
'.tg-dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--dsw-alias-state-idle-primary,#8b8f96)}',
|
|
1100
|
+
'.tg-dot.running{background:var(--dsw-alias-state-warn-primary,#eab308);animation:tg-pulse 1.1s ease-in-out infinite}',
|
|
1101
|
+
'.tg-dot.error{background:var(--dsw-alias-state-error-primary,#ef4444)}',
|
|
1102
|
+
'.tg-dot.ok{background:var(--dsw-alias-state-success-primary,#22c55e)}',
|
|
1103
|
+
'@keyframes tg-pulse{0%,100%{opacity:1}50%{opacity:.35}}',
|
|
1104
|
+
'.tg-btn{border:0;background:transparent;cursor:pointer;color:var(--dsw-alias-label-secondary,#adb2b8);border-radius:7px;',
|
|
1105
|
+
' min-width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;font-size:13px;line-height:1;padding:0 6px;flex:none;',
|
|
1106
|
+
' transition:background .12s ease,color .12s ease}',
|
|
1107
|
+
'.tg-btn:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.14));color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1108
|
+
'.tg-btn:active{opacity:.75}',
|
|
1109
|
+
'.tg-btn.primary{color:var(--dsw-alias-brand-primary,#679efe);border:1px solid color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 35%,transparent)}',
|
|
1110
|
+
'.tg-btn.primary:hover{background:var(--dsw-alias-bg-layer-2,rgba(103,158,254,.14))}',
|
|
1111
|
+
'.tg-session-row{display:flex;align-items:center;gap:6px;padding:8px 10px 6px;position:relative}',
|
|
1112
|
+
'.tg-session-btn{flex:1;min-width:0;display:flex;align-items:center;gap:6px;cursor:pointer;background:transparent;border:0;',
|
|
1113
|
+
' color:var(--dsw-alias-label-primary,#f4f4f5);font-size:12px;padding:4px 6px;border-radius:8px;text-align:left}',
|
|
1114
|
+
'.tg-session-btn:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12))}',
|
|
1115
|
+
'.tg-session-name{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}',
|
|
1116
|
+
'.tg-session-meta{font-size:10.5px;color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap}',
|
|
1117
|
+
'.tg-pin{font-size:10px;color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1118
|
+
'.tg-picker{position:absolute;left:8px;right:8px;top:100%;z-index:20;max-height:320px;overflow-y:auto;padding:6px;',
|
|
1119
|
+
' border-radius:12px;background:var(--dsw-alias-bg-overlay,#2c2c2e);border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));',
|
|
1120
|
+
' box-shadow:0 16px 40px rgba(0,0,0,.4)}',
|
|
1121
|
+
'.tg-picker input{width:100%;box-sizing:border-box;margin-bottom:5px;padding:5px 8px;border-radius:7px;font-size:12px;outline:none;',
|
|
1122
|
+
' background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));',
|
|
1123
|
+
' color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1124
|
+
'.tg-picker-item{display:flex;align-items:center;gap:6px;padding:6px 8px;border-radius:7px;cursor:pointer;font-size:12px;',
|
|
1125
|
+
' white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--dsw-alias-label-primary,#e6e8ea)}',
|
|
1126
|
+
'.tg-picker-item:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.14))}',
|
|
1127
|
+
'.tg-picker-item.sel{background:var(--dsw-alias-bg-layer-2,rgba(103,158,254,.16));color:var(--dsw-alias-brand-primary,#a8c2ff)}',
|
|
1128
|
+
'.tg-picker-item .tg-picker-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}',
|
|
1129
|
+
'.tg-picker-item .tg-picker-time{font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8);flex:none}',
|
|
1130
|
+
'.tg-tabs{display:flex;gap:4px;padding:0 10px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1131
|
+
'.tg-tab{flex:1;border:0;background:transparent;cursor:pointer;padding:8px 4px;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#adb2b8);',
|
|
1132
|
+
' border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .12s ease,border-color .12s ease}',
|
|
1133
|
+
'.tg-tab.active{color:var(--dsw-alias-brand-primary,#679efe);border-bottom-color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1134
|
+
'.tg-tab .tg-tab-badge{display:inline-block;margin-left:5px;font-size:10px;padding:0 5px;border-radius:8px;',
|
|
1135
|
+
' background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.16));color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1136
|
+
'.tg-body{flex:1;min-height:0;display:flex;flex-direction:column;position:relative}',
|
|
1137
|
+
'.tg-workflow{flex:1;min-height:0;display:flex;overflow:hidden}',
|
|
1138
|
+
'.tg-workflow-tree{flex:none;width:300px;min-width:200px;max-width:46%;min-height:0;border-right:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16));',
|
|
1139
|
+
' overflow-y:auto;background:var(--dsw-alias-bg-layer-1,rgba(0,0,0,.04))}',
|
|
1140
|
+
'.tg-workflow-tree::-webkit-scrollbar{width:8px}',
|
|
1141
|
+
'.tg-workflow-tree::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2,rgba(127,127,127,.3));border-radius:4px}',
|
|
1142
|
+
'.tg-workflow-detail{flex:1;min-width:0;min-height:0;overflow-y:auto}',
|
|
1143
|
+
'.tg-workflow-detail::-webkit-scrollbar{width:8px}',
|
|
1144
|
+
'.tg-workflow-detail::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2,rgba(127,127,127,.3));border-radius:4px}',
|
|
1145
|
+
'.tg-wf-stage{margin:6px 8px}',
|
|
1146
|
+
'.tg-wf-stage-head{display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:7px 8px;border-radius:9px;cursor:pointer;user-select:none;',
|
|
1147
|
+
' background:linear-gradient(90deg,color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 9%,transparent),color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 4%,transparent));',
|
|
1148
|
+
' border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1149
|
+
'.tg-wf-stage-head.active{border-color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1150
|
+
'.tg-wf-stage-idx{flex:none;width:17px;height:17px;border-radius:5px;display:flex;align-items:center;justify-content:center;',
|
|
1151
|
+
' background:var(--dsw-alias-brand-primary,#679efe);color:#fff;font-size:10px;font-weight:700}',
|
|
1152
|
+
'.tg-wf-stage-title{flex:1;min-width:0;font-size:11.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1153
|
+
'.tg-wf-stage-meta{flex:none;font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1154
|
+
'.tg-wf-stage-title-row{display:flex;align-items:center;gap:6px}',
|
|
1155
|
+
'.tg-wf-stage-metrics{display:flex;flex-wrap:wrap;justify-content:flex-end;text-align:right;gap:2px 10px;font-size:9px;',
|
|
1156
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8);padding:0 2px}',
|
|
1157
|
+
'.tg-wf-stage-toggle{flex:none;display:inline-flex;align-items:center;cursor:pointer;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1158
|
+
'.tg-wf-stage-toggle:hover{color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1159
|
+
'.tg-wf-tree-head{display:flex;align-items:center;gap:5px;padding:7px 8px 5px;font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1160
|
+
'.tg-wf-tree-head-title{flex:1;min-width:0}',
|
|
1161
|
+
'.tg-wf-tree-btn{font-size:9.5px;padding:0 6px;height:20px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.2))}',
|
|
1162
|
+
'.tg-wf-metric{font-variant-numeric:tabular-nums}',
|
|
1163
|
+
'.tg-wf-metric-time{color:var(--dsw-alias-state-success-primary,#34d399)}',
|
|
1164
|
+
'.tg-wf-stage-topics{margin:4px 0 2px}',
|
|
1165
|
+
'.tg-wf-topic{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:7px;cursor:pointer;user-select:none;min-width:0}',
|
|
1166
|
+
'.tg-wf-topic:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08))}',
|
|
1167
|
+
'.tg-wf-topic.active{background:color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 3%,transparent)}',
|
|
1168
|
+
'.tg-wf-topic-title{flex:1;min-width:0;font-size:11px;color:var(--dsw-alias-label-primary,#e6e8ea);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1169
|
+
'.tg-wf-topic.active .tg-wf-topic-title{color:var(--dsw-alias-brand-primary,#679efe);font-weight:600}',
|
|
1170
|
+
'.tg-wf-topic-meta{flex:none;font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1171
|
+
'.tg-wf-empty{padding:18px 14px;font-size:11px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1172
|
+
'.tg-wf-stage-detail{padding:12px 14px}',
|
|
1173
|
+
'.tg-wf-stage-detail-title{font-size:14px;font-weight:700;color:var(--dsw-alias-label-primary,#f4f4f5);margin-bottom:6px}',
|
|
1174
|
+
'.tg-wf-stage-detail-summary{font-size:11px;color:var(--dsw-alias-label-secondary,#c2c6cc);line-height:1.6;margin-bottom:8px}',
|
|
1175
|
+
'.tg-wf-stage-detail-meta{font-size:10.5px;color:var(--dsw-alias-label-secondary,#adb2b8);margin-bottom:10px}',
|
|
1176
|
+
'.tg-wf-stage-topics{display:flex;flex-direction:column;gap:8px;margin-top:10px}',
|
|
1177
|
+
'.tg-wf-topic-card{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16));border-radius:10px;overflow:hidden;',
|
|
1178
|
+
' background:var(--dsw-alias-bg-layer-1,rgba(0,0,0,.03))}',
|
|
1179
|
+
'.tg-wf-topic-card.open{border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.3))}',
|
|
1180
|
+
'.tg-wf-topic-card-head{display:flex;align-items:center;gap:7px;padding:8px 10px;user-select:none}',
|
|
1181
|
+
'.tg-wf-topic-card-toggle{display:flex;align-items:center;gap:6px;flex:0 1 auto;min-width:0;max-width:72%;cursor:pointer}',
|
|
1182
|
+
'.tg-wf-topic-card-toggle:hover .tg-wf-topic-card-title{color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1183
|
+
'.tg-wf-topic-card-head:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06))}',
|
|
1184
|
+
'.tg-wf-topic-card-title{flex:0 1 auto;min-width:0;font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e8ea);',
|
|
1185
|
+
' white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1186
|
+
'.tg-wf-topic-card-meta{flex:none;margin-left:auto;font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8);font-variant-numeric:tabular-nums}',
|
|
1187
|
+
'.tg-wf-topic-card-body{padding:4px 10px 10px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.12))}',
|
|
1188
|
+
'.tg-wf-topic-summary{font-size:11px;color:var(--dsw-alias-label-secondary,#c2c6cc);line-height:1.6;margin:6px 0;white-space:pre-wrap;word-break:break-word}',
|
|
1189
|
+
'.tg-wf-mini-wrap{margin:10px 0 4px;padding:8px 10px;border-radius:10px;',
|
|
1190
|
+
' border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16));background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.04))}',
|
|
1191
|
+
'.tg-wf-mini-title{font-size:10px;font-weight:600;color:var(--dsw-alias-label-secondary,#adb2b8);margin-bottom:2px}',
|
|
1192
|
+
'.tg-wf-mini{overflow:visible}',
|
|
1193
|
+
'.tg-wf-mini-base{stroke:var(--dsw-alias-border-l2,rgba(127,127,127,.3));stroke-width:1}',
|
|
1194
|
+
'.tg-wf-mini-arc path{fill:none;stroke:var(--dsw-alias-brand-primary,#679efe);stroke-width:1.2;opacity:.3;stroke-dasharray:3 3;',
|
|
1195
|
+
' transition:opacity .12s ease,stroke-width .12s ease}',
|
|
1196
|
+
'.tg-wf-mini-arc.active path{opacity:1;stroke-width:2;stroke-dasharray:none}',
|
|
1197
|
+
'.tg-wf-mini-arc text{font-size:8px;fill:var(--dsw-alias-brand-primary,#679efe);text-anchor:middle;opacity:.45;font-weight:600}',
|
|
1198
|
+
'.tg-wf-mini-arc.active text{opacity:1;fill:var(--dsw-alias-state-warn-primary,#eab308)}',
|
|
1199
|
+
'.tg-wf-mini-dot{cursor:pointer}',
|
|
1200
|
+
'.tg-wf-mini-dot circle{opacity:.8;transition:opacity .12s ease,stroke .12s ease}',
|
|
1201
|
+
'.tg-wf-mini-dot:hover circle,.tg-wf-mini-dot.active circle{opacity:1;stroke:var(--dsw-alias-label-primary,#f4f4f5);stroke-width:1.5}',
|
|
1202
|
+
'.tg-wf-mini-idx{font-size:8px;fill:var(--dsw-alias-label-secondary,#adb2b8);text-anchor:middle}',
|
|
1203
|
+
'.tg-wf-statgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:10px 0 6px}',
|
|
1204
|
+
'.tg-wf-stat{padding:8px 10px;border-radius:9px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16));',
|
|
1205
|
+
' background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.04))}',
|
|
1206
|
+
'.tg-wf-stat-top{display:flex;align-items:baseline;justify-content:space-between;gap:6px;font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1207
|
+
'.tg-wf-stat-top b{font-size:13px;font-weight:700;color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1208
|
+
'.tg-wf-bar{height:5px;border-radius:3px;overflow:hidden;background:var(--dsw-alias-border-l1,rgba(127,127,127,.16));margin:6px 0 3px}',
|
|
1209
|
+
'.tg-wf-bar i{display:block;height:100%;border-radius:3px;background:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1210
|
+
'.tg-wf-bar i.green{background:var(--dsw-alias-state-success-primary,#34d399)}',
|
|
1211
|
+
'.tg-wf-bar i.warn{background:var(--dsw-alias-state-warn-primary,#eab308)}',
|
|
1212
|
+
'.tg-wf-stat-ratio{font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8);font-variant-numeric:tabular-nums}',
|
|
1213
|
+
'.tg-jump-link{color:var(--dsw-alias-brand-primary,#679efe);cursor:pointer;font-size:10px;white-space:nowrap}',
|
|
1214
|
+
'.tg-jump-link:hover{text-decoration:underline}',
|
|
1215
|
+
'.tg-howto-foot{display:flex;align-items:center;gap:10px;margin-top:6px;font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1216
|
+
'.tg-scroll{flex:1;min-height:0;overflow-y:auto;padding:8px 10px 10px}',
|
|
1217
|
+
'.tg-scroll::-webkit-scrollbar{width:8px;height:8px}',
|
|
1218
|
+
'.tg-scroll::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2,rgba(127,127,127,.35));border-radius:4px}',
|
|
1219
|
+
'.tg-scroll::-webkit-scrollbar-track{background:transparent}',
|
|
1220
|
+
'.tg-banner{margin:8px 10px 0;padding:7px 10px;border-radius:9px;font-size:11px;display:flex;align-items:center;gap:6px;',
|
|
1221
|
+
' background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ef4444) 12%,transparent);',
|
|
1222
|
+
' border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#ef4444) 28%,transparent);',
|
|
1223
|
+
' color:var(--dsw-alias-state-error-primary,#ef4444)}',
|
|
1224
|
+
'.tg-banner.info{background:var(--dsw-alias-bg-layer-2,rgba(103,158,254,.10));border-color:var(--dsw-alias-border-l1,rgba(103,158,254,.24));color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1225
|
+
'.tg-banner .tg-banner-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
1226
|
+
'.tg-banner button{border:0;background:transparent;color:inherit;text-decoration:underline;cursor:pointer;font-size:11px;padding:0}',
|
|
1227
|
+
'.tg-empty{padding:26px 12px;text-align:center;font-size:12px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1228
|
+
'.tg-empty .tg-spin{margin:0 auto 10px}',
|
|
1229
|
+
'.tg-spin{width:18px;height:18px;border-radius:50%;border:2px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));',
|
|
1230
|
+
' border-top-color:var(--dsw-alias-brand-primary,#679efe);animation:tg-spin .8s linear infinite}',
|
|
1231
|
+
'@keyframes tg-spin{to{transform:rotate(360deg)}}',
|
|
1232
|
+
// 阶段
|
|
1233
|
+
'.tg-stage{margin-bottom:10px}',
|
|
1234
|
+
'.tg-stage-head{display:flex;align-items:center;gap:7px;padding:7px 9px;border-radius:10px;cursor:pointer;user-select:none;',
|
|
1235
|
+
' background:linear-gradient(90deg,color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 13%,transparent),var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06)));',
|
|
1236
|
+
' border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1237
|
+
'.tg-stage-head:hover{border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.32))}',
|
|
1238
|
+
'.tg-stage-index{width:19px;height:19px;border-radius:6px;flex:none;display:flex;align-items:center;justify-content:center;',
|
|
1239
|
+
' background:var(--dsw-alias-brand-primary,#679efe);color:#fff;font-size:10.5px;font-weight:700}',
|
|
1240
|
+
'.tg-stage-title{font-weight:700;font-size:12.5px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1241
|
+
'.tg-stage-chips{display:flex;gap:4px;flex:none;font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1242
|
+
'.tg-chip{padding:1px 6px;border-radius:7px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.14));white-space:nowrap}',
|
|
1243
|
+
'.tg-chev{font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8);transition:transform .15s ease;flex:none}',
|
|
1244
|
+
'.tg-chev.open{transform:rotate(90deg)}',
|
|
1245
|
+
'.tg-stage-body{padding:7px 0 0 4px}',
|
|
1246
|
+
// 话题
|
|
1247
|
+
'.tg-topic{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16));border-radius:10px;margin:0 0 7px;overflow:hidden;',
|
|
1248
|
+
' background:var(--dsw-alias-bg-layer-1,transparent)}',
|
|
1249
|
+
'.tg-topic-head{display:flex;align-items:center;gap:6px;padding:7px 9px;cursor:pointer;user-select:none}',
|
|
1250
|
+
'.tg-topic-head:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08))}',
|
|
1251
|
+
'.tg-topic-main{flex:1;min-width:0}',
|
|
1252
|
+
'.tg-topic-title{font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1253
|
+
'.tg-topic-sub{margin-top:2px;display:flex;gap:5px;align-items:center;font-size:10.5px;color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap;overflow:hidden}',
|
|
1254
|
+
'.tg-topic-metrics{flex:none;display:flex;flex-direction:column;align-items:flex-end;gap:2px;font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1255
|
+
'.tg-topic-metrics b{color:var(--dsw-alias-label-primary,#f4f4f5);font-weight:600;font-size:11px}',
|
|
1256
|
+
'.tg-badge{display:inline-flex;align-items:center;padding:0 5px;border-radius:7px;font-size:9.5px;line-height:16px;flex:none;',
|
|
1257
|
+
' background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.16));color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1258
|
+
'.tg-badge.active{color:var(--dsw-alias-state-success-primary,#22c55e);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 14%,transparent)}',
|
|
1259
|
+
'.tg-badge.llm{color:var(--dsw-alias-brand-primary,#679efe);background:color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 13%,transparent)}',
|
|
1260
|
+
'.tg-topic-body{padding:0 9px 9px}',
|
|
1261
|
+
'.tg-topic-summary{font-size:11.5px;color:var(--dsw-alias-label-secondary,#adb2b8);margin:0 0 7px}',
|
|
1262
|
+
'.tg-statgrid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:8px}',
|
|
1263
|
+
'.tg-stat{border-radius:8px;padding:6px 8px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.07));min-width:0}',
|
|
1264
|
+
'.tg-stat-label{font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1265
|
+
'.tg-stat-value{font-size:12px;font-weight:600;margin-top:1px;display:flex;align-items:baseline;gap:4px;flex-wrap:wrap}',
|
|
1266
|
+
'.tg-stat-value span.ratio{font-size:10px;font-weight:500;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1267
|
+
'.tg-bar{height:3px;border-radius:2px;margin-top:4px;overflow:hidden;background:var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1268
|
+
'.tg-bar i{display:block;height:100%;border-radius:2px;background:var(--dsw-alias-brand-primary,#679efe);opacity:.85}',
|
|
1269
|
+
'.tg-bar.tool i{background:var(--dsw-alias-state-warn-primary,#eab308)}',
|
|
1270
|
+
'.tg-bar.time i{background:var(--dsw-alias-state-success-primary,#22c55e)}',
|
|
1271
|
+
'.tg-turn{border-radius:8px;margin-bottom:5px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06));overflow:hidden;',
|
|
1272
|
+
' border:1px solid transparent;cursor:pointer}',
|
|
1273
|
+
'.tg-turn:hover{border-color:var(--dsw-alias-border-l1,rgba(127,127,127,.22))}',
|
|
1274
|
+
'.tg-turn.running{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#eab308) 40%,transparent)}',
|
|
1275
|
+
'.tg-turn-row{display:flex;align-items:center;gap:6px;padding:5px 7px;min-width:0}',
|
|
1276
|
+
'.tg-turn-no{flex:none;font-size:10px;font-weight:700;color:var(--dsw-alias-brand-primary,#679efe);min-width:26px}',
|
|
1277
|
+
'.tg-turn-prompt{flex:1;min-width:0;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--dsw-alias-label-primary,#e6e8ea)}',
|
|
1278
|
+
'.tg-turn-meta{flex:none;display:flex;gap:5px;font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap}',
|
|
1279
|
+
'.tg-turn-meta b{font-weight:600;color:var(--dsw-alias-label-primary,#d4d4d8)}',
|
|
1280
|
+
'.tg-turn-detail{padding:2px 8px 8px;display:grid;grid-template-columns:1fr 1fr;gap:4px 10px;font-size:10.5px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1281
|
+
'.tg-turn-detail .full{grid-column:1/-1;white-space:pre-wrap;word-break:break-word;line-height:1.5;',
|
|
1282
|
+
' background:var(--dsw-alias-bg-layer-1,rgba(0,0,0,.10));border-radius:6px;padding:5px 7px;max-height:120px;overflow:hidden}',
|
|
1283
|
+
'.tg-turn-detail .full b{color:var(--dsw-alias-label-primary,#d4d4d8);display:block;margin-bottom:1px;font-size:10px;font-weight:600}',
|
|
1284
|
+
'.tg-turn-detail .kv{display:flex;justify-content:space-between;gap:6px}',
|
|
1285
|
+
'.tg-turn-detail .kv span:last-child{color:var(--dsw-alias-label-primary,#d4d4d8);font-weight:600;text-align:right}',
|
|
1286
|
+
'.tg-turn-analysis{grid-column:1/-1;margin-top:6px;padding-top:8px;border-top:1px dashed var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1287
|
+
'.tg-turn-analysis-head{display:flex;align-items:center;gap:6px;font-size:10.5px;font-weight:600;color:var(--dsw-alias-label-primary,#d4d4d8);margin-bottom:6px}',
|
|
1288
|
+
'.tg-howto-steps{list-style:none;margin:0 0 6px;padding:0;display:flex;flex-direction:column;gap:6px}',
|
|
1289
|
+
'.tg-howto-steps li{padding:6px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06));',
|
|
1290
|
+
' border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16))}',
|
|
1291
|
+
'.tg-howto-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e8ea)}',
|
|
1292
|
+
'.tg-howto-detail{font-size:10.5px;color:var(--dsw-alias-label-secondary,#c2c6cc);margin-top:2px;line-height:1.5;white-space:pre-wrap;word-break:break-word}',
|
|
1293
|
+
'.tg-howto-tools{font-size:9.5px;color:var(--dsw-alias-brand-primary,#679efe);margin-top:3px}',
|
|
1294
|
+
'.tg-howto-outcome{font-size:10px;color:var(--dsw-alias-state-success-primary,#34d399);margin-top:3px}',
|
|
1295
|
+
'.tg-howto-error{font-size:10.5px;color:var(--dsw-alias-state-error-primary,#ef4444);margin-bottom:4px}',
|
|
1296
|
+
'.tg-howto-empty{font-size:10.5px;color:var(--dsw-alias-label-secondary,#adb2b8);margin-bottom:4px}',
|
|
1297
|
+
'.tg-turn-toggle{display:flex;align-items:center;gap:6px;flex:1;min-width:0;cursor:pointer}',
|
|
1298
|
+
'.tg-turn-toggle:hover .tg-turn-prompt,.tg-turn-toggle:hover .tg-turn-no{color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1299
|
+
'.tg-howto-stepcount{margin-left:6px;font-size:9px;font-weight:600;color:var(--dsw-alias-brand-primary,#679efe);',
|
|
1300
|
+
' background:color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 12%,transparent);border-radius:6px;padding:0 5px}',
|
|
1301
|
+
'.tg-analyze-btn{margin-top:2px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));font-size:10.5px;padding:0 8px;height:24px}',
|
|
1302
|
+
// 图谱
|
|
1303
|
+
'.tg-graph{flex:1;min-height:0;display:flex;flex-direction:column}',
|
|
1304
|
+
'.tg-graph-toolbar{display:flex;align-items:center;gap:5px;padding:6px 10px;font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8);',
|
|
1305
|
+
' border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.14))}',
|
|
1306
|
+
'.tg-graph-hint{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1307
|
+
'.tg-graph-modes{display:inline-flex;gap:2px;padding:2px;border-radius:7px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08))}',
|
|
1308
|
+
'.tg-mode-btn{border:0;background:transparent;cursor:pointer;font-size:10px;font-family:inherit;padding:2px 8px;border-radius:5px;',
|
|
1309
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap}',
|
|
1310
|
+
'.tg-mode-btn:hover{color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1311
|
+
'.tg-mode-btn.active{background:var(--dsw-alias-brand-primary,#679efe);color:#fff;font-weight:600}',
|
|
1312
|
+
'.tg-tl-split{flex:1;min-height:0;display:flex;overflow:hidden}',
|
|
1313
|
+
'.tg-tl-outline{flex:none;width:236px;min-height:0;overflow-y:auto;border-right:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16));padding:6px}',
|
|
1314
|
+
'.tg-tl-outline::-webkit-scrollbar{width:8px}',
|
|
1315
|
+
'.tg-tl-outline::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2,rgba(127,127,127,.3));border-radius:4px}',
|
|
1316
|
+
'.tg-tl-stage{margin-bottom:6px}',
|
|
1317
|
+
'.tg-tl-stage-head{display:flex;align-items:center;gap:6px;padding:5px 6px;border-radius:7px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.07));',
|
|
1318
|
+
' font-weight:600;font-size:10px;color:var(--dsw-alias-label-primary,#e6e8ea)}',
|
|
1319
|
+
'.tg-tl-stage-idx{width:16px;height:16px;border-radius:5px;display:flex;align-items:center;justify-content:center;flex:none;',
|
|
1320
|
+
' background:var(--dsw-alias-brand-primary,#679efe);color:#fff;font-size:9px;font-weight:700}',
|
|
1321
|
+
'.tg-tl-stage-title{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1322
|
+
'.tg-tl-topic{display:flex;align-items:center;gap:6px;padding:3px 6px 3px 14px;border-radius:6px;cursor:pointer;font-size:10px;',
|
|
1323
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1324
|
+
'.tg-tl-topic:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08));color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1325
|
+
'.tg-tl-topic.active{background:color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 8%,transparent);',
|
|
1326
|
+
' color:var(--dsw-alias-brand-primary,#679efe);font-weight:600}',
|
|
1327
|
+
'.tg-tl-topic-idx{flex:none;font-size:8.5px;font-weight:700;color:var(--dsw-alias-brand-primary,#679efe);opacity:.85}',
|
|
1328
|
+
'.tg-tl-topic-title{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1329
|
+
'.tg-tl-scroll{flex:1;min-width:0;min-height:0;overflow:auto;position:relative;padding-bottom:150px}',
|
|
1330
|
+
'.tg-tl-scroll::-webkit-scrollbar{width:9px;height:9px}',
|
|
1331
|
+
'.tg-tl-scroll::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2,rgba(127,127,127,.35));border-radius:5px}',
|
|
1332
|
+
'.tg-tl-svg{display:block;overflow:visible}',
|
|
1333
|
+
'.tg-tl-grid{stroke:var(--dsw-alias-border-l1,rgba(127,127,127,.12));stroke-width:1}',
|
|
1334
|
+
'.tg-tl-tick{font-size:8px;fill:var(--dsw-alias-label-secondary,#adb2b8);text-anchor:middle}',
|
|
1335
|
+
'.tg-tl-lane{fill:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.03))}',
|
|
1336
|
+
'.tg-tl-lane.alt{fill:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.07))}',
|
|
1337
|
+
'.tg-tl-lane-label{font-size:8px;fill:var(--dsw-alias-label-secondary,#adb2b8);opacity:.75}',
|
|
1338
|
+
'.tg-tl-next{fill:none;stroke:var(--dsw-alias-border-l2,rgba(127,127,127,.3));stroke-width:1;stroke-dasharray:2 3;opacity:.5}',
|
|
1339
|
+
'.tg-tl-arrow{fill:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1340
|
+
'.tg-tl-rel-hit{fill:none;stroke:transparent;stroke-width:14;pointer-events:stroke;cursor:pointer}',
|
|
1341
|
+
'.tg-tl-rel-path{fill:none;stroke:var(--dsw-alias-brand-primary,#679efe);stroke-width:1.2;opacity:.42;stroke-dasharray:4 3;',
|
|
1342
|
+
' transition:opacity .12s ease,stroke-width .12s ease}',
|
|
1343
|
+
'.tg-tl-rel.same-lane .tg-tl-rel-path{opacity:.52}',
|
|
1344
|
+
'.tg-tl-rel.active .tg-tl-rel-path{opacity:1;stroke-width:2.2;stroke-dasharray:none}',
|
|
1345
|
+
'.tg-tl-rel-label{font-size:8px;fill:var(--dsw-alias-brand-primary,#679efe);text-anchor:middle;opacity:.18;font-weight:600;',
|
|
1346
|
+
' transition:opacity .12s ease,fill .12s ease}',
|
|
1347
|
+
'.tg-tl-svg.has-active .tg-tl-rel:not(.active) .tg-tl-rel-label{opacity:0}',
|
|
1348
|
+
'.tg-tl-rel.same-lane .tg-tl-rel-label{opacity:0}',
|
|
1349
|
+
'.tg-tl-rel.active .tg-tl-rel-label{opacity:1;fill:var(--dsw-alias-state-warn-primary,#eab308);font-weight:700}',
|
|
1350
|
+
'.tg-tl-bar{cursor:pointer}',
|
|
1351
|
+
'.tg-tl-bar-rect{fill:var(--tg-bar-color,var(--dsw-alias-brand-primary,#679efe));opacity:.85;stroke:none;',
|
|
1352
|
+
' transition:opacity .12s ease,filter .12s ease}',
|
|
1353
|
+
'.tg-tl-bar:hover .tg-tl-bar-rect,.tg-tl-bar.active .tg-tl-bar-rect{',
|
|
1354
|
+
' opacity:1;filter:brightness(1.25) drop-shadow(0 0 3px var(--tg-bar-color,rgba(103,158,254,.7)))}',
|
|
1355
|
+
'.tg-tl-bar-label{font-size:8.5px;fill:var(--dsw-alias-label-primary,#f4f4f5);text-anchor:middle;font-weight:700;pointer-events:none}',
|
|
1356
|
+
'.tg-tl-tip{position:fixed;z-index:9999;max-width:300px;padding:7px 9px;border-radius:8px;background:var(--dsw-alias-bg-overlay,#26262a);',
|
|
1357
|
+
' border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));box-shadow:0 8px 24px rgba(0,0,0,.4);pointer-events:none}',
|
|
1358
|
+
'.tg-tl-tip-title{font-size:11px;font-weight:700;color:var(--dsw-alias-label-primary,#f4f4f5);margin-bottom:3px}',
|
|
1359
|
+
'.tg-tl-tip-line{font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8);line-height:1.5}',
|
|
1360
|
+
'.tg-tl-tip-rels{color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1361
|
+
'.tg-zoom-pct{min-width:38px;text-align:right;font-variant-numeric:tabular-nums}',
|
|
1362
|
+
'.tg-graph-viewport{padding-bottom:150px;flex:1;min-height:0;overflow:auto;position:relative;overscroll-behavior:contain}',
|
|
1363
|
+
'.tg-graph-viewport::-webkit-scrollbar{width:9px;height:9px}',
|
|
1364
|
+
'.tg-graph-viewport::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2,rgba(127,127,127,.35));border-radius:5px}',
|
|
1365
|
+
'.tg-graph-canvas{position:relative;overflow:hidden}',
|
|
1366
|
+
'.tg-graph-content{position:absolute;left:0;top:0;transform-origin:0 0}',
|
|
1367
|
+
'.tg-edges{position:absolute;left:0;top:0;pointer-events:none;overflow:visible;z-index:1}',
|
|
1368
|
+
'.tg-edges path{fill:none;stroke:var(--dsw-alias-border-l2,rgba(127,127,127,.45));stroke-width:1.4}',
|
|
1369
|
+
'.tg-stage-box{position:absolute;border-radius:13px;pointer-events:none;z-index:0;border:1px dashed var(--dsw-alias-border-l2,rgba(127,127,127,.3));',
|
|
1370
|
+
' background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.04))}',
|
|
1371
|
+
'.tg-stage-box-head{position:absolute;left:12px;right:12px;top:8px;height:24px;display:flex;align-items:center;gap:6px;',
|
|
1372
|
+
' pointer-events:none;font-size:11.5px;font-weight:700;color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1373
|
+
'.tg-stage-toggle{pointer-events:auto;flex:none;font-size:9px;line-height:1;padding:3px;border-radius:4px;cursor:pointer;opacity:.6;',
|
|
1374
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1375
|
+
'.tg-stage-toggle:hover{opacity:1;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.16))}',
|
|
1376
|
+
'.tg-stage-box-idx{width:16px;height:16px;border-radius:5px;background:var(--dsw-alias-brand-primary,#679efe);color:#fff;',
|
|
1377
|
+
' display:flex;align-items:center;justify-content:center;font-size:9.5px;flex:none}',
|
|
1378
|
+
'.tg-stage-box-title{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1379
|
+
'.tg-stage-box-chip{font-size:9.5px;font-weight:500;color:var(--dsw-alias-label-secondary,#adb2b8);flex:none}',
|
|
1380
|
+
'.tg-node{position:absolute;border-radius:10px;padding:4px 9px;cursor:pointer;overflow:hidden;z-index:2;',
|
|
1381
|
+
' background:var(--dsw-alias-bg-layer-1,#2c2c2e);border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.26));',
|
|
1382
|
+
' box-shadow:0 2px 10px rgba(0,0,0,.16);transition:border-color .12s ease,transform .12s ease}',
|
|
1383
|
+
'.tg-node:hover{border-color:var(--dsw-alias-brand-primary,#679efe);transform:translateY(-1px)}',
|
|
1384
|
+
'.tg-node.active{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 45%,transparent)}',
|
|
1385
|
+
'.tg-node-title{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:650;color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1386
|
+
'.tg-node-title-text{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1387
|
+
'.tg-node-toggle{flex:none;font-size:9px;line-height:1;padding:2px;border-radius:4px;cursor:pointer;opacity:.55;',
|
|
1388
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8);transition:opacity .12s ease,background .12s ease}',
|
|
1389
|
+
'.tg-node-toggle:hover{opacity:1;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.16))}',
|
|
1390
|
+
'.tg-node-meta{display:flex;align-items:center;gap:6px;font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8);margin-top:2px;white-space:nowrap;overflow:hidden}',
|
|
1391
|
+
'.tg-node-meta b{color:var(--dsw-alias-label-primary,#e4e4e7);font-weight:600}',
|
|
1392
|
+
'.tg-node-bar{height:3px;border-radius:2px;margin-top:3px;background:var(--dsw-alias-border-l1,rgba(127,127,127,.2));overflow:hidden}',
|
|
1393
|
+
'.tg-node-bar i{display:block;height:100%;background:var(--dsw-alias-brand-primary,#679efe);opacity:.85}',
|
|
1394
|
+
'.tg-node-depth-tag{font-size:9px;padding:0 4px;border-radius:5px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.16));color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1395
|
+
// 图谱边(next / belongTo / has_relation / delegates)
|
|
1396
|
+
'.tg-edge{fill:none;stroke-linecap:round}',
|
|
1397
|
+
'.tg-edge-hit{fill:none;stroke:transparent;stroke-width:14;pointer-events:stroke;cursor:pointer}',
|
|
1398
|
+
'.tg-edge.selected{stroke:#22d3ee;stroke-width:3.4;opacity:1;filter:drop-shadow(0 0 3px rgba(34,211,238,.85))}',
|
|
1399
|
+
'.tg-edge-next{stroke:var(--dsw-alias-label-secondary,#8b8f96);stroke-width:1.6;opacity:.55}',
|
|
1400
|
+
'.tg-edge-belongTo{stroke:var(--dsw-alias-border-l2,rgba(127,127,127,.45));stroke-width:1.1;stroke-dasharray:3 4;opacity:.75}',
|
|
1401
|
+
'.tg-edge-has_relation{stroke:var(--dsw-alias-brand-primary,#679efe);stroke-width:1.8;stroke-dasharray:3 3;opacity:1}',
|
|
1402
|
+
'.tg-edge-delegates{stroke:var(--dsw-alias-state-warn-primary,#eab308);stroke-width:1.4;stroke-dasharray:5 4;opacity:.9}',
|
|
1403
|
+
'.tg-edge-label{font-size:9.5px;fill:var(--dsw-alias-brand-primary,#679efe);text-anchor:start;',
|
|
1404
|
+
' pointer-events:none;user-select:none;font-weight:600;text-anchor:middle}',
|
|
1405
|
+
'.tg-edge-label-bg{fill:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-layer-1,#26262a));',
|
|
1406
|
+
' stroke:var(--dsw-alias-border-l1,rgba(127,127,127,.22));cursor:pointer}',
|
|
1407
|
+
'.tg-edge-label-bg.selected{stroke:#22d3ee;stroke-width:1.5}',
|
|
1408
|
+
'.tg-edge-label.selected{fill:#22d3ee;font-weight:700}',
|
|
1409
|
+
'.tg-arrow{fill:var(--dsw-alias-label-secondary,#8b8f96)}',
|
|
1410
|
+
'.tg-arrow-rel{fill:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1411
|
+
'.tg-arrow-deleg{fill:var(--dsw-alias-state-warn-primary,#eab308)}',
|
|
1412
|
+
'.tg-arrow-sel{fill:#22d3ee}',
|
|
1413
|
+
'.tg-legend{display:flex;gap:7px;align-items:center;flex-wrap:wrap;padding:4px 10px 0;font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1414
|
+
'.tg-legend i{display:inline-block;width:14px;height:0;border-top:1.5px solid currentColor;margin-right:3px;vertical-align:middle}',
|
|
1415
|
+
'.tg-legend .n i{color:var(--dsw-alias-label-secondary,#8b8f96)}',
|
|
1416
|
+
'.tg-legend .b i{color:var(--dsw-alias-border-l2,rgba(127,127,127,.55));border-top-style:dashed}',
|
|
1417
|
+
'.tg-legend .r i{color:var(--dsw-alias-brand-primary,#679efe);border-top-style:dotted}',
|
|
1418
|
+
'.tg-legend .d i{color:var(--dsw-alias-state-warn-primary,#eab308);border-top-style:dashed}',
|
|
1419
|
+
// 统计条
|
|
1420
|
+
'.tg-stats{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 10px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.14))}',
|
|
1421
|
+
'.tg-view-toggle{display:inline-flex;gap:2px;padding:3px;border-radius:9px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08))}',
|
|
1422
|
+
'.tg-view-btn{border:0;background:transparent;cursor:pointer;font-size:12px;font-family:inherit;padding:4px 14px;border-radius:7px;',
|
|
1423
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap}',
|
|
1424
|
+
'.tg-view-btn:hover{color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1425
|
+
'.tg-view-btn.active{background:var(--dsw-alias-brand-primary,#679efe);color:#fff;font-weight:600}',
|
|
1426
|
+
'.tg-title-session{flex:0 1 auto;min-width:0;max-width:46%;font-size:11px;font-weight:500;color:var(--dsw-alias-label-secondary,#c2c6cc);',
|
|
1427
|
+
' white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1428
|
+
'.tg-stat-chip{display:inline-flex;align-items:baseline;gap:4px;padding:1px 7px;border-radius:8px;font-size:9.5px;',
|
|
1429
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8);background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08));white-space:nowrap}',
|
|
1430
|
+
'.tg-stat-chip b{font-size:11px;font-weight:700;color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1431
|
+
'.tg-topic-detail-btn{width:22px;min-width:22px;height:22px;font-size:12px;opacity:.65}',
|
|
1432
|
+
'.tg-topic-detail-btn:hover{opacity:1}',
|
|
1433
|
+
// 话题详情浮层
|
|
1434
|
+
'.tg-detail{position:absolute;inset:0;z-index:30;display:flex;flex-direction:column;',
|
|
1435
|
+
' background:var(--dsw-alias-bg-layer-1,#26262a);',
|
|
1436
|
+
' animation:tg-fade .14s ease both}',
|
|
1437
|
+
'@keyframes tg-fade{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}',
|
|
1438
|
+
'.tg-detail-head{display:flex;align-items:center;gap:8px;padding:9px 11px;',
|
|
1439
|
+
' border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1440
|
+
'.tg-detail-title{flex:1;min-width:0;font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1441
|
+
'.tg-detail-body{padding:9px 11px 12px;overflow-y:auto}',
|
|
1442
|
+
'.tg-detail-body::-webkit-scrollbar{width:8px}',
|
|
1443
|
+
'.tg-detail-body::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2,rgba(127,127,127,.35));border-radius:4px}',
|
|
1444
|
+
'.tg-detail-kicker{font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8);margin-bottom:4px}',
|
|
1445
|
+
'.tg-detail-kv{font-size:9.5px;color:var(--dsw-alias-label-secondary,#adb2b8);margin-top:3px;word-break:break-word}',
|
|
1446
|
+
'.tg-detail-section{margin-top:10px}',
|
|
1447
|
+
'.tg-detail-section-title{font-size:10px;font-weight:600;color:var(--dsw-alias-label-secondary,#adb2b8);margin-bottom:4px}',
|
|
1448
|
+
'.tg-detail-foot{display:flex;gap:6px;justify-content:flex-end;padding:8px 11px;',
|
|
1449
|
+
' border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16))}',
|
|
1450
|
+
'.tg-detail-foot .tg-btn{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));font-size:11px;padding:0 10px;height:26px}',
|
|
1451
|
+
'.tg-detail-status{margin-top:8px;font-size:10px;color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1452
|
+
// 子智能体「分支框 + 内部话题树」(与主图同屏)
|
|
1453
|
+
'.tg-branch-ico{flex:none;font-size:10px}',
|
|
1454
|
+
'.tg-branch-toggle{flex:none;font-size:9px;line-height:1;padding:2px;border-radius:4px;cursor:pointer;opacity:.6;',
|
|
1455
|
+
' color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1456
|
+
'.tg-branch-toggle:hover{opacity:1;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.16))}',
|
|
1457
|
+
'.tg-branch-depth{position:absolute;right:5px;top:4px;font-size:8px;padding:0 3px;border-radius:4px;',
|
|
1458
|
+
' background:var(--dsw-alias-bg-layer-1,rgba(127,127,127,.16));color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1459
|
+
'.tg-branch-box{position:absolute;border-radius:12px;pointer-events:none;z-index:0;',
|
|
1460
|
+
' border:1px dashed color-mix(in srgb,var(--dsw-alias-state-warn-primary,#eab308) 38%,transparent);',
|
|
1461
|
+
' background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#eab308) 4%,transparent)}',
|
|
1462
|
+
'.tg-branch-box-label{position:absolute;left:8px;top:-9px;max-width:calc(100% - 16px);padding:0 5px;border-radius:6px;',
|
|
1463
|
+
' font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8);background:var(--dsw-alias-bg-overlay,#26262a);',
|
|
1464
|
+
' white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1465
|
+
'.tg-branch-header{position:absolute;display:flex;align-items:center;gap:5px;padding:0 8px;border-radius:8px;cursor:pointer;z-index:2;',
|
|
1466
|
+
' background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#eab308) 12%,transparent);',
|
|
1467
|
+
' border:1px dashed color-mix(in srgb,var(--dsw-alias-state-warn-primary,#eab308) 45%,transparent)}',
|
|
1468
|
+
'.tg-branch-header:hover{border-style:solid}',
|
|
1469
|
+
'.tg-branch-header.pending{opacity:.72}',
|
|
1470
|
+
'.tg-branch-title-text{flex:1;min-width:0;font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary,#f4f4f5);',
|
|
1471
|
+
' white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1472
|
+
'.tg-branch-header-meta{flex:none;font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap}',
|
|
1473
|
+
'.tg-branch-topic{position:absolute;border-radius:8px;padding:5px 8px;cursor:pointer;overflow:hidden;z-index:2;',
|
|
1474
|
+
' background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22))}',
|
|
1475
|
+
'.tg-branch-topic:hover{border-color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1476
|
+
'.tg-branch-topic-title{font-size:10.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;',
|
|
1477
|
+
' color:var(--dsw-alias-label-primary,#f4f4f5)}',
|
|
1478
|
+
'.tg-branch-topic-meta{display:flex;gap:6px;margin-top:2px;font-size:8.5px;color:var(--dsw-alias-label-secondary,#adb2b8);',
|
|
1479
|
+
' white-space:nowrap;overflow:hidden}',
|
|
1480
|
+
'.tg-subbranch-group{margin-top:3px}',
|
|
1481
|
+
'.tg-subbranch-topics{padding-left:16px}',
|
|
1482
|
+
'.tg-subbranch-topic{display:flex;align-items:center;gap:6px;padding:2px 4px;border-radius:5px;cursor:pointer;',
|
|
1483
|
+
' font-size:10px;color:var(--dsw-alias-label-primary,#e6e8ea);white-space:nowrap;overflow:hidden}',
|
|
1484
|
+
'.tg-subbranch-topic:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12))}',
|
|
1485
|
+
'.tg-subbranch-topic-title{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1486
|
+
'.tg-subbranch-topic.dim{color:var(--dsw-alias-label-secondary,#adb2b8);cursor:default}',
|
|
1487
|
+
// 链式里的关联话题 / 子智能体
|
|
1488
|
+
'.tg-relations{display:flex;flex-wrap:wrap;gap:4px;margin:0 0 7px}',
|
|
1489
|
+
'.tg-rel-chip{display:inline-flex;align-items:center;gap:3px;max-width:100%;padding:1px 7px;border-radius:8px;cursor:pointer;',
|
|
1490
|
+
' font-size:9.5px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));',
|
|
1491
|
+
' background:color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 8%,transparent);',
|
|
1492
|
+
' color:var(--dsw-alias-brand-primary,#679efe);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1493
|
+
'.tg-rel-chip:hover{background:color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 16%,transparent)}',
|
|
1494
|
+
'.tg-rel-chip b{color:var(--dsw-alias-label-secondary,#adb2b8);font-weight:500}',
|
|
1495
|
+
'.tg-subbranches{margin:2px 0 7px;border-radius:9px;border:1px dashed color-mix(in srgb,var(--dsw-alias-state-warn-primary,#eab308) 40%,transparent);',
|
|
1496
|
+
' background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#eab308) 5%,transparent);padding:5px 7px}',
|
|
1497
|
+
'.tg-subbranches-head{font-size:9.5px;font-weight:600;color:var(--dsw-alias-state-warn-primary,#ca8a04);margin-bottom:3px;display:flex;gap:5px;align-items:center}',
|
|
1498
|
+
'.tg-subbranch{display:flex;align-items:center;gap:6px;padding:3px 4px;border-radius:6px;cursor:pointer;font-size:10.5px;',
|
|
1499
|
+
' color:var(--dsw-alias-label-primary,#e6e8ea);white-space:nowrap;overflow:hidden}',
|
|
1500
|
+
'.tg-subbranch:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12))}',
|
|
1501
|
+
'.tg-subbranch-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}',
|
|
1502
|
+
'.tg-subbranch-meta{flex:none;font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8)}',
|
|
1503
|
+
'.tg-back-parent{flex:none;border:0;background:transparent;cursor:pointer;color:var(--dsw-alias-brand-primary,#679efe);',
|
|
1504
|
+
' font-size:10.5px;padding:2px 5px;border-radius:6px}',
|
|
1505
|
+
'.tg-back-parent:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12))}',
|
|
1506
|
+
// tooltip
|
|
1507
|
+
'.tg-tip{position:fixed;z-index:10060;max-width:330px;pointer-events:none;border-radius:11px;padding:9px 11px;font-size:11px;line-height:1.55;',
|
|
1508
|
+
' background:var(--dsw-alias-bg-overlay,#2c2c2e);color:var(--dsw-alias-label-primary,#f4f4f5);',
|
|
1509
|
+
' border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.32));box-shadow:0 14px 40px rgba(0,0,0,.45);',
|
|
1510
|
+
' font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Arial,sans-serif}',
|
|
1511
|
+
'.tg-tip-title{font-weight:700;font-size:12.5px;margin-bottom:2px}',
|
|
1512
|
+
'.tg-tip-kind{font-size:9.5px;color:var(--dsw-alias-brand-primary,#679efe);margin-bottom:4px}',
|
|
1513
|
+
'.tg-tip-summary{color:var(--dsw-alias-label-secondary,#adb2b8);margin-bottom:6px;white-space:pre-wrap;word-break:break-word}',
|
|
1514
|
+
'.tg-tip-grid{display:grid;grid-template-columns:auto 1fr;gap:2px 10px}',
|
|
1515
|
+
'.tg-tip-grid .k{color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap}',
|
|
1516
|
+
'.tg-tip-grid .v{font-weight:600;text-align:right}',
|
|
1517
|
+
'.tg-tip-list{margin-top:5px;color:var(--dsw-alias-label-secondary,#adb2b8);word-break:break-word}',
|
|
1518
|
+
// footer
|
|
1519
|
+
'.tg-foot{display:flex;align-items:center;gap:6px;padding:7px 10px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.16));',
|
|
1520
|
+
' background:var(--dsw-alias-bg-layer-1,rgba(0,0,0,.06))}',
|
|
1521
|
+
'.tg-foot-status{flex:1;min-width:0;font-size:10px;color:var(--dsw-alias-label-secondary,#adb2b8);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1522
|
+
'.tg-foot-status.error{color:var(--dsw-alias-state-error-primary,#ef4444)}',
|
|
1523
|
+
'.tg-foot-status.running{color:var(--dsw-alias-state-warn-primary,#eab308)}',
|
|
1524
|
+
'.tg-foot .tg-btn{font-size:10.5px;padding:0 8px;height:24px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22))}',
|
|
1525
|
+
'.tg-foot .tg-btn.primary{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary,#679efe) 40%,transparent)}',
|
|
1526
|
+
'.tg-progress{flex:none;width:64px;height:4px;border-radius:2px;overflow:hidden;background:var(--dsw-alias-border-l1,rgba(127,127,127,.18))}',
|
|
1527
|
+
'.tg-progress i{display:block;height:100%;background:var(--dsw-alias-brand-primary,#679efe);transition:width .3s ease}',
|
|
1528
|
+
// collapsed pill
|
|
1529
|
+
'.tg-pill{position:fixed;z-index:9998;pointer-events:auto;display:flex;align-items:center;gap:7px;padding:7px 11px;border-radius:999px;',
|
|
1530
|
+
' cursor:grab;user-select:none;font-size:12px;font-weight:600;',
|
|
1531
|
+
' background:var(--dsw-alias-bg-overlay,#26262a);color:var(--dsw-alias-label-primary,#f4f4f5);',
|
|
1532
|
+
' border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));box-shadow:0 10px 28px rgba(0,0,0,.35)}',
|
|
1533
|
+
'.tg-pill:hover{border-color:var(--dsw-alias-brand-primary,#679efe)}',
|
|
1534
|
+
'.tg-pill:active{cursor:grabbing}',
|
|
1535
|
+
'.tg-pill .tg-dot.running{animation:tg-pulse 1.1s ease-in-out infinite}',
|
|
1536
|
+
'.tg-version{flex:none;font-size:9px;color:var(--dsw-alias-label-secondary,#adb2b8);opacity:.55;margin-right:2px}',
|
|
1537
|
+
'.tg-resize{position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;z-index:12}',
|
|
1538
|
+
'.tg-resize::after{content:"";position:absolute;right:4px;bottom:4px;width:8px;height:8px;',
|
|
1539
|
+
' border-right:2px solid var(--dsw-alias-border-l2,rgba(127,127,127,.5));border-bottom:2px solid var(--dsw-alias-border-l2,rgba(127,127,127,.5));border-radius:0 0 2px 0}',
|
|
1540
|
+
].join('\n');
|
|
1541
|
+
|
|
1542
|
+
// ── 悬浮窗组件 ──────────────────────────────────────────────────────────
|
|
1543
|
+
|
|
1544
|
+
function TopicGraphPanel(props) {
|
|
1545
|
+
useLocaleTick();
|
|
1546
|
+
var propsSessionId = props && typeof props.sessionId === 'string' ? props.sessionId : '';
|
|
1547
|
+
var uiState = useState(loadUi);
|
|
1548
|
+
var ui = uiState[0];
|
|
1549
|
+
var setUi = uiState[1];
|
|
1550
|
+
var sessionsState = useState([]);
|
|
1551
|
+
var sessions = sessionsState[0];
|
|
1552
|
+
var setSessions = sessionsState[1];
|
|
1553
|
+
var graphState = useState(null);
|
|
1554
|
+
var graph = graphState[0];
|
|
1555
|
+
var setGraph = graphState[1];
|
|
1556
|
+
var errorState = useState('');
|
|
1557
|
+
var error = errorState[0];
|
|
1558
|
+
var setError = errorState[1];
|
|
1559
|
+
var expandedStagesState = useState({});
|
|
1560
|
+
var expandedStages = expandedStagesState[0];
|
|
1561
|
+
var setExpandedStages = expandedStagesState[1];
|
|
1562
|
+
var expandedTopicsState = useState({});
|
|
1563
|
+
var expandedTopics = expandedTopicsState[0];
|
|
1564
|
+
var setExpandedTopics = expandedTopicsState[1];
|
|
1565
|
+
var expandedTurnsState = useState({});
|
|
1566
|
+
var expandedTurns = expandedTurnsState[0];
|
|
1567
|
+
var setExpandedTurns = expandedTurnsState[1];
|
|
1568
|
+
var hoverState = useState(null);
|
|
1569
|
+
var hover = hoverState[0];
|
|
1570
|
+
var setHover = hoverState[1];
|
|
1571
|
+
var reloadState = useState(0);
|
|
1572
|
+
var reloadKey = reloadState[0];
|
|
1573
|
+
var setReloadKey = reloadState[1];
|
|
1574
|
+
var scaleState = useState(function () {
|
|
1575
|
+
return (typeof ui.scale === 'number' && ui.scale >= 0.35 && ui.scale <= 2.5) ? ui.scale : 1;
|
|
1576
|
+
});
|
|
1577
|
+
var scale = scaleState[0];
|
|
1578
|
+
var setScale = scaleState[1];
|
|
1579
|
+
var actionState = useState('');
|
|
1580
|
+
var actionBusy = actionState[0];
|
|
1581
|
+
var setActionBusy = actionState[1];
|
|
1582
|
+
var detailState = useState(null);
|
|
1583
|
+
var detailTopicId = detailState[0];
|
|
1584
|
+
var setDetailTopicId = detailState[1];
|
|
1585
|
+
var detailSessionState = useState(null);
|
|
1586
|
+
var detailSessionId = detailSessionState[0];
|
|
1587
|
+
var setDetailSessionId = detailSessionState[1];
|
|
1588
|
+
var jumpState = useState(null);
|
|
1589
|
+
var jumpStatus = jumpState[0];
|
|
1590
|
+
var setJumpStatus = jumpState[1];
|
|
1591
|
+
var graphCollapsedState = useState(function () {
|
|
1592
|
+
return ui.graphCollapsed || { stages: {}, topics: {}, branches: {} };
|
|
1593
|
+
});
|
|
1594
|
+
var graphCollapsed = graphCollapsedState[0];
|
|
1595
|
+
var setGraphCollapsed = graphCollapsedState[1];
|
|
1596
|
+
var selectedEdgeState = useState(null);
|
|
1597
|
+
var selectedEdgeId = selectedEdgeState[0];
|
|
1598
|
+
var setSelectedEdgeId = selectedEdgeState[1];
|
|
1599
|
+
var localTurnAnalysesState = useState({});
|
|
1600
|
+
var localTurnAnalyses = localTurnAnalysesState[0];
|
|
1601
|
+
var setLocalTurnAnalyses = localTurnAnalysesState[1];
|
|
1602
|
+
var selectedStageState = useState(null);
|
|
1603
|
+
var selectedStageId = selectedStageState[0];
|
|
1604
|
+
var setSelectedStageId = selectedStageState[1];
|
|
1605
|
+
var selectedTopicState = useState(null);
|
|
1606
|
+
var selectedTopicId = selectedTopicState[0];
|
|
1607
|
+
var setSelectedTopicId = selectedTopicState[1];
|
|
1608
|
+
var wfMiniHoverState = useState(null);
|
|
1609
|
+
var wfMiniHover = wfMiniHoverState[0];
|
|
1610
|
+
var setWfMiniHover = wfMiniHoverState[1];
|
|
1611
|
+
var graphModeState = useState('timeline');
|
|
1612
|
+
var graphMode = graphModeState[0];
|
|
1613
|
+
var setGraphMode = graphModeState[1];
|
|
1614
|
+
var tlHoverState = useState(null);
|
|
1615
|
+
var tlHover = tlHoverState[0];
|
|
1616
|
+
var setTlHover = tlHoverState[1];
|
|
1617
|
+
var tlBoxState = useState(0);
|
|
1618
|
+
var tlBoxW = tlBoxState[0];
|
|
1619
|
+
var setTlBoxW = tlBoxState[1];
|
|
1620
|
+
var tlSelRelState = useState(null);
|
|
1621
|
+
var tlSelRel = tlSelRelState[0];
|
|
1622
|
+
var setTlSelRel = tlSelRelState[1];
|
|
1623
|
+
var structWState = useState(0);
|
|
1624
|
+
var structW = structWState[0];
|
|
1625
|
+
var setStructW = structWState[1];
|
|
1626
|
+
var modelProvidersState = useState([]);
|
|
1627
|
+
var modelProviders = modelProvidersState[0];
|
|
1628
|
+
var setModelProviders = modelProvidersState[1];
|
|
1629
|
+
var modelSelState = useState('');
|
|
1630
|
+
var modelSel = modelSelState[0];
|
|
1631
|
+
var setModelSel = modelSelState[1];
|
|
1632
|
+
|
|
1633
|
+
var uiRef = useRef(ui);
|
|
1634
|
+
var sessionsRef = useRef({ sessions: [], current: null });
|
|
1635
|
+
var failCountRef = useRef(0);
|
|
1636
|
+
var scaleRef = useRef(scale);
|
|
1637
|
+
var versionRef = useRef(null);
|
|
1638
|
+
var graphSessionRef = useRef(null);
|
|
1639
|
+
var viewportRef = useRef(null);
|
|
1640
|
+
var wfDetailRef = useRef(null);
|
|
1641
|
+
var tlScrollRef = useRef(null);
|
|
1642
|
+
var panelRef = useRef(null);
|
|
1643
|
+
|
|
1644
|
+
uiRef.current = ui;
|
|
1645
|
+
scaleRef.current = scale;
|
|
1646
|
+
|
|
1647
|
+
// UI 状态持久化(拖动时防抖,避免每帧写 localStorage)
|
|
1648
|
+
useEffect(function () {
|
|
1649
|
+
var timer = setTimeout(function () {
|
|
1650
|
+
saveUi(Object.assign({}, ui, { graphCollapsed: graphCollapsed, scale: scale }));
|
|
1651
|
+
}, 200);
|
|
1652
|
+
return function () { clearTimeout(timer); };
|
|
1653
|
+
}, [ui, graphCollapsed, scale]);
|
|
1654
|
+
|
|
1655
|
+
// 切换会话时关闭话题详情浮层,并恢复图谱默认展开
|
|
1656
|
+
useEffect(function () {
|
|
1657
|
+
setDetailSessionId(null);
|
|
1658
|
+
setDetailTopicId(null);
|
|
1659
|
+
setJumpStatus(null);
|
|
1660
|
+
setGraphCollapsed({ stages: {}, topics: {}, branches: {} });
|
|
1661
|
+
setSelectedEdgeId(null);
|
|
1662
|
+
setSelectedTopicId(null);
|
|
1663
|
+
setSelectedStageId(null);
|
|
1664
|
+
}, [graph && graph.sessionId]);
|
|
1665
|
+
|
|
1666
|
+
// 载入可选模型(切分/关系/单轮分析共用)
|
|
1667
|
+
useEffect(function () {
|
|
1668
|
+
loadModels();
|
|
1669
|
+
}, []);
|
|
1670
|
+
|
|
1671
|
+
// 默认展开第一个阶段 / 第一层话题
|
|
1672
|
+
useEffect(function () {
|
|
1673
|
+
if (!graph) return;
|
|
1674
|
+
setExpandedStages(function (prev) {
|
|
1675
|
+
if (Object.keys(prev).length) return prev;
|
|
1676
|
+
var next = {};
|
|
1677
|
+
(graph.stages || []).forEach(function (stage, index) { next[stage.id] = index === 0; });
|
|
1678
|
+
return next;
|
|
1679
|
+
});
|
|
1680
|
+
}, [graph]);
|
|
1681
|
+
|
|
1682
|
+
// dsh 的 conversation view 在会话激活时会按内容撑高,导致整页一起滚。
|
|
1683
|
+
// 这里测量最近的可滚动祖先的可见高度,把面板锁成固定高度,让内部左右两栏独立滚动。
|
|
1684
|
+
useEffect(function () {
|
|
1685
|
+
var el = panelRef.current;
|
|
1686
|
+
if (!el) return undefined;
|
|
1687
|
+
var target = null;
|
|
1688
|
+
var node = el.parentElement;
|
|
1689
|
+
var guard = 0;
|
|
1690
|
+
while (node && guard < 12) {
|
|
1691
|
+
var cs = null;
|
|
1692
|
+
try { cs = window.getComputedStyle(node); } catch (error) { cs = null; }
|
|
1693
|
+
if (cs && (cs.overflowY === 'auto' || cs.overflowY === 'scroll')) { target = node; break; }
|
|
1694
|
+
node = node.parentElement;
|
|
1695
|
+
guard += 1;
|
|
1696
|
+
}
|
|
1697
|
+
function apply() {
|
|
1698
|
+
if (!el) return;
|
|
1699
|
+
var h = target ? target.clientHeight : 0;
|
|
1700
|
+
if (h > 0) {
|
|
1701
|
+
el.style.flex = '0 0 auto';
|
|
1702
|
+
el.style.height = h + 'px';
|
|
1703
|
+
el.style.maxHeight = h + 'px';
|
|
1704
|
+
el.style.minHeight = '0';
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
apply();
|
|
1708
|
+
var ro = null;
|
|
1709
|
+
try {
|
|
1710
|
+
if (typeof ResizeObserver !== 'undefined' && target) {
|
|
1711
|
+
ro = new ResizeObserver(function () { apply(); });
|
|
1712
|
+
ro.observe(target);
|
|
1713
|
+
}
|
|
1714
|
+
} catch (error) { /* ResizeObserver 不可用 */ }
|
|
1715
|
+
window.addEventListener('resize', apply);
|
|
1716
|
+
return function () {
|
|
1717
|
+
if (ro) { try { ro.disconnect(); } catch (error) { /* ignore */ } }
|
|
1718
|
+
window.removeEventListener('resize', apply);
|
|
1719
|
+
};
|
|
1720
|
+
}, []);
|
|
1721
|
+
|
|
1722
|
+
// 时间线:测量可视宽度,尽量一页显示
|
|
1723
|
+
useEffect(function () {
|
|
1724
|
+
var el = tlScrollRef.current;
|
|
1725
|
+
if (!el) return undefined;
|
|
1726
|
+
function measure() { setTlBoxW(el.clientWidth || 0); }
|
|
1727
|
+
measure();
|
|
1728
|
+
var ro = typeof ResizeObserver === 'function' ? new ResizeObserver(measure) : null;
|
|
1729
|
+
if (ro) { try { ro.observe(el); } catch (error) { /* ignore */ } }
|
|
1730
|
+
window.addEventListener('resize', measure);
|
|
1731
|
+
return function () {
|
|
1732
|
+
if (ro) { try { ro.disconnect(); } catch (error) { /* ignore */ } }
|
|
1733
|
+
window.removeEventListener('resize', measure);
|
|
1734
|
+
};
|
|
1735
|
+
}, [ui.tab, graphMode]);
|
|
1736
|
+
|
|
1737
|
+
// 结构图:测量可视宽度,让画布铺满视口、不横向裁切
|
|
1738
|
+
useEffect(function () {
|
|
1739
|
+
var el = viewportRef.current;
|
|
1740
|
+
if (!el) return undefined;
|
|
1741
|
+
function measure() { setStructW(el.clientWidth || 0); }
|
|
1742
|
+
measure();
|
|
1743
|
+
var ro = typeof ResizeObserver === 'function' ? new ResizeObserver(measure) : null;
|
|
1744
|
+
if (ro) { try { ro.observe(el); } catch (error) { /* ignore */ } }
|
|
1745
|
+
window.addEventListener('resize', measure);
|
|
1746
|
+
return function () {
|
|
1747
|
+
if (ro) { try { ro.disconnect(); } catch (error) { /* ignore */ } }
|
|
1748
|
+
window.removeEventListener('resize', measure);
|
|
1749
|
+
};
|
|
1750
|
+
}, [ui.tab, graphMode]);
|
|
1751
|
+
|
|
1752
|
+
// 切换阶段/话题时,右栏滚回顶部
|
|
1753
|
+
useEffect(function () {
|
|
1754
|
+
var el = wfDetailRef.current;
|
|
1755
|
+
if (el) { try { el.scrollTop = 0; } catch (error) { /* ignore */ } }
|
|
1756
|
+
}, [selectedStageId, selectedTopicId]);
|
|
1757
|
+
|
|
1758
|
+
// 工作流右栏默认显示第一个阶段,并展开其第一个话题
|
|
1759
|
+
useEffect(function () {
|
|
1760
|
+
if (!graph) return;
|
|
1761
|
+
var stages = graph.stages || [];
|
|
1762
|
+
if (!stages.length) return;
|
|
1763
|
+
var topics = graph.topics || [];
|
|
1764
|
+
setSelectedStageId(function (prev) { return prev || stages[0].id; });
|
|
1765
|
+
var firstTopic = topics.filter(function (t) { return !t.parentId && t.stageId === stages[0].id; })[0];
|
|
1766
|
+
if (firstTopic) setSelectedTopicId(function (prev) { return prev || firstTopic.id; });
|
|
1767
|
+
setExpandedTopics(function (prev) {
|
|
1768
|
+
if (Object.keys(prev).length) return prev;
|
|
1769
|
+
if (!firstTopic) return prev;
|
|
1770
|
+
var next = {}; next[firstTopic.id] = true; return next;
|
|
1771
|
+
});
|
|
1772
|
+
}, [graph, graph && graph.sessionId]);
|
|
1773
|
+
|
|
1774
|
+
// 轮询:会话列表 5 拍一次,图谱每拍一次(版本未变时 Host 只回 unchanged)
|
|
1775
|
+
useEffect(function () {
|
|
1776
|
+
var cancelled = false;
|
|
1777
|
+
var timer = null;
|
|
1778
|
+
var tick = 0;
|
|
1779
|
+
|
|
1780
|
+
async function refreshSessions() {
|
|
1781
|
+
var payload = await fetchJson(ROUTE + '/sessions');
|
|
1782
|
+
if (cancelled || !payload || !Array.isArray(payload.sessions)) return;
|
|
1783
|
+
sessionsRef.current = { sessions: payload.sessions, current: payload.current };
|
|
1784
|
+
setSessions(payload.sessions);
|
|
1785
|
+
var current = payload.current;
|
|
1786
|
+
if (current && graphSessionRef.current && !sameSession(current, graphSessionRef.current)) {
|
|
1787
|
+
versionRef.current = null;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
async function refreshGraph() {
|
|
1792
|
+
var sidebarSession = propsSessionId || readSidebarCurrentSession();
|
|
1793
|
+
var target = sidebarSession || (sessionsRef.current && sessionsRef.current.current);
|
|
1794
|
+
if (!target) return;
|
|
1795
|
+
var url = ROUTE + '/graph?sessionId=' + encodeURIComponent(target);
|
|
1796
|
+
if (versionRef.current != null && graphSessionRef.current && sameSession(graphSessionRef.current, target)) {
|
|
1797
|
+
url += '&version=' + versionRef.current;
|
|
1798
|
+
}
|
|
1799
|
+
var payload = await fetchJson(url);
|
|
1800
|
+
if (cancelled) return;
|
|
1801
|
+
if (!payload) {
|
|
1802
|
+
failCountRef.current += 1;
|
|
1803
|
+
if (failCountRef.current >= 4) {
|
|
1804
|
+
setError(t('aiFailed') + ':' + ROUTE + ' 无响应');
|
|
1805
|
+
}
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
failCountRef.current = 0;
|
|
1809
|
+
if (payload.unchanged) return;
|
|
1810
|
+
if (payload.ok) {
|
|
1811
|
+
versionRef.current = payload.version;
|
|
1812
|
+
graphSessionRef.current = payload.sessionId || target;
|
|
1813
|
+
setGraph(payload);
|
|
1814
|
+
setError('');
|
|
1815
|
+
} else if (payload.empty) {
|
|
1816
|
+
setGraph(null);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
async function loop() {
|
|
1821
|
+
tick += 1;
|
|
1822
|
+
try {
|
|
1823
|
+
if (tick === 1 || tick % 5 === 0) await refreshSessions();
|
|
1824
|
+
await refreshGraph();
|
|
1825
|
+
} catch (loopError) {
|
|
1826
|
+
setError(String(loopError && loopError.message || loopError));
|
|
1827
|
+
}
|
|
1828
|
+
if (!cancelled) timer = setTimeout(loop, 2500);
|
|
1829
|
+
}
|
|
1830
|
+
loop();
|
|
1831
|
+
return function () { cancelled = true; clearTimeout(timer); };
|
|
1832
|
+
}, [reloadKey, propsSessionId]);
|
|
1833
|
+
|
|
1834
|
+
// Ctrl+滚轮缩放(原生监听,需要 non-passive 才能 preventDefault)
|
|
1835
|
+
useEffect(function () {
|
|
1836
|
+
var element = viewportRef.current;
|
|
1837
|
+
if (!element || ui.tab !== 'graph') return undefined;
|
|
1838
|
+
function onWheel(event) {
|
|
1839
|
+
if (!event.ctrlKey) return; // 普通滚轮交给容器原生滚动
|
|
1840
|
+
event.preventDefault();
|
|
1841
|
+
var rect = element.getBoundingClientRect();
|
|
1842
|
+
var pointerX = event.clientX - rect.left;
|
|
1843
|
+
var pointerY = event.clientY - rect.top;
|
|
1844
|
+
var oldScale = scaleRef.current;
|
|
1845
|
+
var factor = Math.exp(-event.deltaY * 0.0016);
|
|
1846
|
+
var nextScale = clamp(oldScale * factor, 0.35, 2.5);
|
|
1847
|
+
if (Math.abs(nextScale - oldScale) < 0.0005) return;
|
|
1848
|
+
var contentX = (element.scrollLeft + pointerX) / oldScale;
|
|
1849
|
+
var contentY = (element.scrollTop + pointerY) / oldScale;
|
|
1850
|
+
setScale(nextScale);
|
|
1851
|
+
requestAnimationFrame(function () {
|
|
1852
|
+
element.scrollLeft = contentX * nextScale - pointerX;
|
|
1853
|
+
element.scrollTop = contentY * nextScale - pointerY;
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
element.addEventListener('wheel', onWheel, { passive: false });
|
|
1857
|
+
return function () { element.removeEventListener('wheel', onWheel); };
|
|
1858
|
+
}, [ui.tab]);
|
|
1859
|
+
|
|
1860
|
+
// ── 操作 ────────────────────────────────────────────────────────────
|
|
1861
|
+
function toggleStage(stageId) {
|
|
1862
|
+
setExpandedStages(function (prev) {
|
|
1863
|
+
var next = Object.assign({}, prev);
|
|
1864
|
+
next[stageId] = !next[stageId];
|
|
1865
|
+
return next;
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
function setAllStages(open) {
|
|
1870
|
+
setExpandedStages(function () {
|
|
1871
|
+
var next = {};
|
|
1872
|
+
(graph && graph.stages ? graph.stages : []).forEach(function (stage) { next[stage.id] = open; });
|
|
1873
|
+
return next;
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
function toggleTopic(topicId) {
|
|
1878
|
+
setExpandedTopics(function (prev) {
|
|
1879
|
+
var next = Object.assign({}, prev);
|
|
1880
|
+
next[topicId] = !next[topicId];
|
|
1881
|
+
return next;
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function toggleTurn(turnNo) {
|
|
1886
|
+
setExpandedTurns(function (prev) {
|
|
1887
|
+
var next = Object.assign({}, prev);
|
|
1888
|
+
next[turnNo] = !next[turnNo];
|
|
1889
|
+
return next;
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
function selectSession(sessionId) {
|
|
1894
|
+
versionRef.current = null;
|
|
1895
|
+
graphSessionRef.current = null;
|
|
1896
|
+
setGraph(null);
|
|
1897
|
+
// 在面板里选会话 = 同时切换 dsh 主对话,避免面板与左侧不一致
|
|
1898
|
+
if (sessionId) {
|
|
1899
|
+
try {
|
|
1900
|
+
var navigation = appCtx && typeof appCtx.get === 'function' ? appCtx.get('uiWorkspace') : null;
|
|
1901
|
+
if (navigation && typeof navigation.openSession === 'function') navigation.openSession(sessionId);
|
|
1902
|
+
} catch (error) { /* 导航不可用时只切换面板 */ }
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
async function triggerSegment(full) {
|
|
1907
|
+
var target = (graph && graph.sessionId) || (sessionsRef.current && sessionsRef.current.current);
|
|
1908
|
+
if (!target || actionBusy) return;
|
|
1909
|
+
setActionBusy(full ? 'full' : 'incremental');
|
|
1910
|
+
var result = await postJson(ROUTE + '/segment', { sessionId: target, full: full === true });
|
|
1911
|
+
setActionBusy('');
|
|
1912
|
+
if (!result || !result.ok) {
|
|
1913
|
+
setError(t('aiFailed'));
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
versionRef.current = null;
|
|
1917
|
+
setReloadKey(function (n) { return n + 1; });
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// ── 派生数据 ────────────────────────────────────────────────────────
|
|
1921
|
+
var layout = useMemo(function () {
|
|
1922
|
+
var opts = Object.assign({}, graphCollapsed);
|
|
1923
|
+
opts.width = structW || 0;
|
|
1924
|
+
return buildLayout(graph || { topics: [], stages: [] }, opts);
|
|
1925
|
+
}, [graph, graphCollapsed, structW]);
|
|
1926
|
+
var byId = useMemo(function () {
|
|
1927
|
+
var map = {};
|
|
1928
|
+
var list = graph && graph.topics ? graph.topics : [];
|
|
1929
|
+
list.forEach(function (topic) { map[topic.id] = topic; });
|
|
1930
|
+
return map;
|
|
1931
|
+
}, [graph]);
|
|
1932
|
+
var turnByNo = useMemo(function () {
|
|
1933
|
+
var map = {};
|
|
1934
|
+
var list = graph && graph.turns ? graph.turns : [];
|
|
1935
|
+
list.forEach(function (turn) { map[turn.turn] = turn; });
|
|
1936
|
+
return map;
|
|
1937
|
+
}, [graph]);
|
|
1938
|
+
var childMap = useMemo(function () {
|
|
1939
|
+
var map = {};
|
|
1940
|
+
var list = graph && graph.topics ? graph.topics : [];
|
|
1941
|
+
list.forEach(function (topic) {
|
|
1942
|
+
if (!topic.parentId) return;
|
|
1943
|
+
if (!map[topic.parentId]) map[topic.parentId] = [];
|
|
1944
|
+
map[topic.parentId].push(topic);
|
|
1945
|
+
});
|
|
1946
|
+
return map;
|
|
1947
|
+
}, [graph]);
|
|
1948
|
+
var branchBySession = useMemo(function () {
|
|
1949
|
+
var map = {};
|
|
1950
|
+
var list = graph && graph.branches ? graph.branches : [];
|
|
1951
|
+
list.forEach(function (branch) { map[branch.sessionId] = branch; });
|
|
1952
|
+
return map;
|
|
1953
|
+
}, [graph]);
|
|
1954
|
+
var branchesByTopic = useMemo(function () {
|
|
1955
|
+
var map = {};
|
|
1956
|
+
var list = graph && graph.branches ? graph.branches : [];
|
|
1957
|
+
list.forEach(function (branch) {
|
|
1958
|
+
if (!branch.parentTopicId) return;
|
|
1959
|
+
if (!map[branch.parentTopicId]) map[branch.parentTopicId] = [];
|
|
1960
|
+
map[branch.parentTopicId].push(branch);
|
|
1961
|
+
});
|
|
1962
|
+
return map;
|
|
1963
|
+
}, [graph]);
|
|
1964
|
+
var relationsByTopic = useMemo(function () {
|
|
1965
|
+
var map = {};
|
|
1966
|
+
var list = graph && graph.relations ? graph.relations : [];
|
|
1967
|
+
list.forEach(function (rel) {
|
|
1968
|
+
if (!map[rel.from]) map[rel.from] = [];
|
|
1969
|
+
map[rel.from].push({ direction: 'out', other: rel.to, label: rel.label });
|
|
1970
|
+
if (!map[rel.to]) map[rel.to] = [];
|
|
1971
|
+
map[rel.to].push({ direction: 'in', other: rel.from, label: rel.label });
|
|
1972
|
+
});
|
|
1973
|
+
return map;
|
|
1974
|
+
}, [graph]);
|
|
1975
|
+
|
|
1976
|
+
function topicTitleOf(id) {
|
|
1977
|
+
return byId[id] ? byId[id].title : id;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function topicBranches(topicId) {
|
|
1981
|
+
return branchesByTopic[topicId] || [];
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
function openSession(sessionId) {
|
|
1985
|
+
if (!sessionId) return;
|
|
1986
|
+
selectSession(sessionId);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// ── 渲染:轮次行 ────────────────────────────────────────────────────
|
|
1990
|
+
function renderTurn(turnNo) {
|
|
1991
|
+
var turn = turnByNo[turnNo];
|
|
1992
|
+
if (!turn) return null;
|
|
1993
|
+
var open = expandedTurns[turnNo];
|
|
1994
|
+
var running = turn.status === 'open';
|
|
1995
|
+
var statusLabel = running ? t('statusOpen') : (turn.status === 'aborted' ? t('statusAborted') : '');
|
|
1996
|
+
return h('div', {
|
|
1997
|
+
key: 'turn-' + turnNo,
|
|
1998
|
+
className: 'tg-turn' + (running ? ' running' : ''),
|
|
1999
|
+
},
|
|
2000
|
+
h('div', { className: 'tg-turn-row' },
|
|
2001
|
+
h('span', {
|
|
2002
|
+
className: 'tg-turn-toggle',
|
|
2003
|
+
onClick: function (event) { event.stopPropagation(); toggleTurn(turnNo); },
|
|
2004
|
+
title: open ? t('collapse') : t('expand'),
|
|
2005
|
+
},
|
|
2006
|
+
h('span', { className: 'tg-turn-no' }, t('turnLabel', { n: turnNo })),
|
|
2007
|
+
h('span', { className: 'tg-turn-prompt' }, turn.prompt || t('emptyPrompt')),
|
|
2008
|
+
),
|
|
2009
|
+
statusLabel ? h('span', { className: 'tg-badge active' }, statusLabel) : null,
|
|
2010
|
+
h('span', { className: 'tg-turn-meta' },
|
|
2011
|
+
h('span', null, h('b', null, String(turn.steps || 0)), ' ' + t('totalSteps')),
|
|
2012
|
+
h('span', null, h('b', null, fmtK(turn.tokens.total)), ' ' + t('tokens')),
|
|
2013
|
+
h('span', null, t('toolCalls') + ' ', h('b', null, String(turn.toolCalls.total))),
|
|
2014
|
+
h('span', null, h('b', null, fmtDuration(turn.wallMs))),
|
|
2015
|
+
),
|
|
2016
|
+
),
|
|
2017
|
+
open ? h('div', { className: 'tg-turn-detail' },
|
|
2018
|
+
h('div', { className: 'kv' }, h('span', null, t('tokens')), h('span', null, fmtK(turn.tokens.total) + ' · ' + fmtPct(turn.tokenRatio))),
|
|
2019
|
+
h('div', { className: 'kv' }, h('span', null, t('agentTime')), h('span', null, fmtDuration(turn.wallMs) + ' · ' + fmtPct(turn.wallRatio))),
|
|
2020
|
+
h('div', { className: 'kv' }, h('span', null, t('stepTime')), h('span', null, fmtDuration(turn.agentMs))),
|
|
2021
|
+
h('div', { className: 'kv' }, h('span', null, t('toolCalls')), h('span', null, String(turn.toolCalls.total) + (turn.toolErrors ? ' (' + turn.toolErrors + ' err)' : ''))),
|
|
2022
|
+
h('div', { className: 'kv' }, h('span', null, t('requestCount')), h('span', null, String(turn.requests))),
|
|
2023
|
+
h('div', { className: 'kv' },
|
|
2024
|
+
h('span', null, t('totalSteps')),
|
|
2025
|
+
h('span', null,
|
|
2026
|
+
h('b', null, String(turn.steps || 0)), ' ',
|
|
2027
|
+
h('span', {
|
|
2028
|
+
className: 'tg-jump-link',
|
|
2029
|
+
title: t('jumpToTurn'),
|
|
2030
|
+
onClick: function (event) { event.stopPropagation(); void jumpToTurn(turnNo); },
|
|
2031
|
+
}, '↗ ' + t('jumpToTurn')),
|
|
2032
|
+
),
|
|
2033
|
+
),
|
|
2034
|
+
h('div', { className: 'kv' }, h('span', null, t('input') + '/' + t('output')), h('span', null, fmtK(turn.tokens.input) + ' / ' + fmtK(turn.tokens.output))),
|
|
2035
|
+
h('div', { className: 'kv' }, h('span', null, t('cacheRead')), h('span', null, fmtK(turn.tokens.cacheRead))),
|
|
2036
|
+
turn.model ? h('div', { className: 'kv' }, h('span', null, 'model'), h('span', null, turn.model)) : null,
|
|
2037
|
+
turn.prompt ? h('div', { className: 'full' }, h('b', null, t('prompt')), turn.prompt) : null,
|
|
2038
|
+
turn.assistant ? h('div', { className: 'full' }, h('b', null, t('assistant')), turn.assistant) : null,
|
|
2039
|
+
(function () {
|
|
2040
|
+
var local = localTurnAnalyses[turnNo];
|
|
2041
|
+
var cached = graph.turnAnalyses && graph.turnAnalyses[turnNo];
|
|
2042
|
+
var cachedSteps = cached && Array.isArray(cached.steps) ? cached.steps : null;
|
|
2043
|
+
var status = local && local.status ? local.status : null;
|
|
2044
|
+
var steps = (local && local.status === 'done' && Array.isArray(local.steps) && local.steps.length)
|
|
2045
|
+
? local.steps
|
|
2046
|
+
: (cachedSteps && cachedSteps.length ? cachedSteps : null);
|
|
2047
|
+
var hasSteps = Boolean(steps && steps.length);
|
|
2048
|
+
var errorText = local && local.status === 'error' ? (local.error || t('analyzeFailed')) : null;
|
|
2049
|
+
var busy = status === 'loading';
|
|
2050
|
+
return h('div', { className: 'tg-turn-analysis' },
|
|
2051
|
+
h('div', { className: 'tg-turn-analysis-head' },
|
|
2052
|
+
h('span', null, t('howtoTitle')),
|
|
2053
|
+
busy ? h('span', { className: 'tg-badge active' }, t('analyzing')) : null,
|
|
2054
|
+
),
|
|
2055
|
+
busy ? null : (hasSteps
|
|
2056
|
+
? h(React.Fragment, null,
|
|
2057
|
+
h('ol', { className: 'tg-howto-steps' }, steps.map(function (step, i) {
|
|
2058
|
+
return h('li', { key: 'howto-' + i },
|
|
2059
|
+
h('div', { className: 'tg-howto-title' },
|
|
2060
|
+
(i + 1) + '. ' + (step.title || '步骤'),
|
|
2061
|
+
step.stepCount ? h('span', { className: 'tg-howto-stepcount' }, String(step.stepCount) + ' 步') : null,
|
|
2062
|
+
),
|
|
2063
|
+
step.detail ? h('div', { className: 'tg-howto-detail' }, step.detail) : null,
|
|
2064
|
+
step.tools && step.tools.length ? h('div', { className: 'tg-howto-tools' }, t('stepTools') + ':' + step.tools.join('、')) : null,
|
|
2065
|
+
step.outcome ? h('div', { className: 'tg-howto-outcome' }, '→ ' + step.outcome) : null,
|
|
2066
|
+
);
|
|
2067
|
+
})),
|
|
2068
|
+
h('div', { className: 'tg-howto-foot' },
|
|
2069
|
+
h('span', null, t('totalSteps') + ' ' + String(turn.steps || 0)),
|
|
2070
|
+
h('span', {
|
|
2071
|
+
className: 'tg-jump-link',
|
|
2072
|
+
onClick: function (event) { event.stopPropagation(); void jumpToTurn(turnNo); },
|
|
2073
|
+
}, '↗ ' + t('jumpToTurn')),
|
|
2074
|
+
),
|
|
2075
|
+
)
|
|
2076
|
+
: (errorText
|
|
2077
|
+
? h('div', { className: 'tg-howto-error' }, errorText)
|
|
2078
|
+
: h('div', { className: 'tg-howto-empty' }, t('howtoEmpty')))),
|
|
2079
|
+
busy ? null : h('button', {
|
|
2080
|
+
className: 'tg-btn tg-analyze-btn',
|
|
2081
|
+
onClick: function (event) { event.stopPropagation(); analyzeTurn(turnNo, hasSteps); },
|
|
2082
|
+
}, hasSteps ? t('reanalyzeTurn') : t('analyzeTurn')),
|
|
2083
|
+
);
|
|
2084
|
+
})(),
|
|
2085
|
+
) : null,
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
// ── 渲染:话题(含子话题) ─────────────────────────────────────────
|
|
2090
|
+
function renderTopic(topic, depth) {
|
|
2091
|
+
if (!topic) return null;
|
|
2092
|
+
var open = expandedTopics[topic.id];
|
|
2093
|
+
var stats = topic.stats || {};
|
|
2094
|
+
var tokens = stats.tokens || {};
|
|
2095
|
+
var kids = childMap[topic.id] || [];
|
|
2096
|
+
var range = topic.turnStart != null ? t('range') + ' ' + topic.turnStart + '-' + topic.turnEnd : '';
|
|
2097
|
+
return h('div', { key: topic.id, className: 'tg-topic', style: { marginLeft: depth ? depth * 10 : 0 } },
|
|
2098
|
+
h('div', { className: 'tg-topic-head', onClick: function () { toggleTopic(topic.id); } },
|
|
2099
|
+
h('span', { className: 'tg-chev' + (open ? ' open' : '') }, '▶'),
|
|
2100
|
+
h('div', { className: 'tg-topic-main' },
|
|
2101
|
+
h('div', { className: 'tg-topic-title' }, (depth ? '· ' : '') + topic.title),
|
|
2102
|
+
h('div', { className: 'tg-topic-sub' },
|
|
2103
|
+
h('span', null, range),
|
|
2104
|
+
h('span', { className: 'tg-badge' + (topic.status === 'active' ? ' active' : '') },
|
|
2105
|
+
topic.status === 'active' ? t('statusActive') : t('statusDone')),
|
|
2106
|
+
h('span', { className: 'tg-badge' + (topic.source === 'llm' ? ' llm' : '') },
|
|
2107
|
+
topic.source === 'llm' ? t('sourceLlm') : t('sourceRule')),
|
|
2108
|
+
kids.length ? h('span', null, kids.length + ' ' + t('subtopics')) : null,
|
|
2109
|
+
kids.length ? h('span', { className: 'tg-badge llm', title: t('statIncludesSubtopic') }, t('statIncludesSubtopic')) : null,
|
|
2110
|
+
topicBranches(topic.id).length
|
|
2111
|
+
? h('span', { className: 'tg-badge', style: { color: 'var(--dsw-alias-state-warn-primary,#ca8a04)' } },
|
|
2112
|
+
'🤖 ' + t('subagentCount', { n: topicBranches(topic.id).length }))
|
|
2113
|
+
: null,
|
|
2114
|
+
),
|
|
2115
|
+
),
|
|
2116
|
+
h('div', { className: 'tg-topic-metrics' },
|
|
2117
|
+
h('span', null, h('b', null, fmtK(tokens.total)), ' · ' + fmtPct(stats.tokenRatio)),
|
|
2118
|
+
h('span', null, fmtDuration(stats.wallMs) + ' · ' + fmtPct(stats.wallRatio)),
|
|
2119
|
+
),
|
|
2120
|
+
h('button', {
|
|
2121
|
+
className: 'tg-btn tg-topic-detail-btn',
|
|
2122
|
+
title: t('detailOpen'),
|
|
2123
|
+
onClick: function (event) { event.stopPropagation(); openDetail(topic.id); },
|
|
2124
|
+
}, 'ⓘ'),
|
|
2125
|
+
),
|
|
2126
|
+
open ? h('div', { className: 'tg-topic-body' },
|
|
2127
|
+
topic.summary ? h('div', { className: 'tg-topic-summary' }, topic.summary) : null,
|
|
2128
|
+
h('div', { className: 'tg-statgrid' },
|
|
2129
|
+
h('div', { className: 'tg-stat' },
|
|
2130
|
+
h('div', { className: 'tg-stat-label' }, t('tokens') + ' · ' + t('ratioOfTotal') + ' ' + fmtPct(stats.tokenRatio)),
|
|
2131
|
+
h('div', { className: 'tg-stat-value' }, fmtK(tokens.total), h('span', { className: 'ratio' }, t('input') + ' ' + fmtK(tokens.input) + ' / ' + t('output') + ' ' + fmtK(tokens.output))),
|
|
2132
|
+
h('div', { className: 'tg-bar' }, h('i', { style: { width: Math.max(1, (stats.tokenRatio || 0) * 100) + '%' } })),
|
|
2133
|
+
),
|
|
2134
|
+
h('div', { className: 'tg-stat' },
|
|
2135
|
+
h('div', { className: 'tg-stat-label' }, t('toolCalls') + ' · ' + fmtPct(stats.toolRatio)),
|
|
2136
|
+
h('div', { className: 'tg-stat-value' }, String((stats.toolCalls && stats.toolCalls.total) || 0)),
|
|
2137
|
+
h('div', { className: 'tg-bar tool' }, h('i', { style: { width: Math.max(1, (stats.toolRatio || 0) * 100) + '%' } })),
|
|
2138
|
+
),
|
|
2139
|
+
h('div', { className: 'tg-stat' },
|
|
2140
|
+
h('div', { className: 'tg-stat-label' }, t('agentTime') + ' · ' + fmtPct(stats.wallRatio)),
|
|
2141
|
+
h('div', { className: 'tg-stat-value' }, fmtDuration(stats.wallMs), h('span', { className: 'ratio' }, t('stepTime') + ' ' + fmtDuration(stats.agentMs))),
|
|
2142
|
+
h('div', { className: 'tg-bar time' }, h('i', { style: { width: Math.max(1, (stats.wallRatio || 0) * 100) + '%' } })),
|
|
2143
|
+
),
|
|
2144
|
+
h('div', { className: 'tg-stat' },
|
|
2145
|
+
h('div', { className: 'tg-stat-label' }, t('turns')),
|
|
2146
|
+
h('div', { className: 'tg-stat-value' }, String(stats.turnCount || 0)),
|
|
2147
|
+
),
|
|
2148
|
+
),
|
|
2149
|
+
(relationsByTopic[topic.id] && relationsByTopic[topic.id].length)
|
|
2150
|
+
? h('div', { className: 'tg-relations' },
|
|
2151
|
+
h('span', { className: 'tg-badge' }, t('relations')),
|
|
2152
|
+
relationsByTopic[topic.id].map(function (rel, index) {
|
|
2153
|
+
return h('span', {
|
|
2154
|
+
key: 'rel-' + index,
|
|
2155
|
+
className: 'tg-rel-chip',
|
|
2156
|
+
title: topicTitleOf(rel.other),
|
|
2157
|
+
onClick: function (event) {
|
|
2158
|
+
event.stopPropagation();
|
|
2159
|
+
setExpandedTopics(function (prev) {
|
|
2160
|
+
var next = Object.assign({}, prev);
|
|
2161
|
+
next[rel.other] = true;
|
|
2162
|
+
return next;
|
|
2163
|
+
});
|
|
2164
|
+
},
|
|
2165
|
+
},
|
|
2166
|
+
rel.direction === 'out' ? '→ ' : '← ',
|
|
2167
|
+
topicTitleOf(rel.other),
|
|
2168
|
+
h('b', null, ' ' + rel.label),
|
|
2169
|
+
);
|
|
2170
|
+
}),
|
|
2171
|
+
)
|
|
2172
|
+
: null,
|
|
2173
|
+
topicBranches(topic.id).length
|
|
2174
|
+
? h('div', { className: 'tg-subbranches' },
|
|
2175
|
+
h('div', { className: 'tg-subbranches-head' }, '🤖 ' + t('subagents') + ' (' + topicBranches(topic.id).length + ')'),
|
|
2176
|
+
topicBranches(topic.id).map(function (branch) {
|
|
2177
|
+
var totals = branch.totals || {};
|
|
2178
|
+
var childTopics = (branch.topics || []).slice(0, 6);
|
|
2179
|
+
var more = Math.max(0, (branch.topics || []).length - childTopics.length);
|
|
2180
|
+
return h('div', { key: branch.sessionId, className: 'tg-subbranch-group' },
|
|
2181
|
+
h('div', {
|
|
2182
|
+
className: 'tg-subbranch',
|
|
2183
|
+
title: t('branchOpen'),
|
|
2184
|
+
onClick: function () { selectSession(branch.sessionId); },
|
|
2185
|
+
},
|
|
2186
|
+
h('span', { className: 'tg-subbranch-name' }, '🤖 ' + (branch.title || sessionShortId(branch.sessionId))),
|
|
2187
|
+
branch.pending ? h('span', { className: 'tg-subbranch-meta' }, t('branchPending')) : null,
|
|
2188
|
+
h('span', { className: 'tg-subbranch-meta' },
|
|
2189
|
+
(totals.turnCount || 0) + ' ' + t('turns') + ' · ' + fmtK((totals.tokens || {}).total || 0)),
|
|
2190
|
+
),
|
|
2191
|
+
childTopics.length
|
|
2192
|
+
? h('div', { className: 'tg-subbranch-topics' }, childTopics.map(function (childTopic) {
|
|
2193
|
+
var childStats = childTopic.stats || {};
|
|
2194
|
+
return h('div', {
|
|
2195
|
+
key: childTopic.id,
|
|
2196
|
+
className: 'tg-subbranch-topic',
|
|
2197
|
+
title: t('detailOpen'),
|
|
2198
|
+
onClick: function (event) {
|
|
2199
|
+
event.stopPropagation();
|
|
2200
|
+
openChildDetail(branch.sessionId, childTopic.id);
|
|
2201
|
+
},
|
|
2202
|
+
},
|
|
2203
|
+
h('span', { className: 'tg-subbranch-topic-title' }, childTopic.title),
|
|
2204
|
+
h('span', { className: 'tg-subbranch-meta' }, fmtK((childStats.tokens || {}).total || 0)),
|
|
2205
|
+
);
|
|
2206
|
+
}), more ? h('div', { className: 'tg-subbranch-topic dim' }, '+' + more + ' …') : null)
|
|
2207
|
+
: null,
|
|
2208
|
+
);
|
|
2209
|
+
}),
|
|
2210
|
+
)
|
|
2211
|
+
: null,
|
|
2212
|
+
topic.ownTurns && topic.ownTurns.length
|
|
2213
|
+
? h('div', null, topic.ownTurns.map(renderTurn))
|
|
2214
|
+
: h('div', { className: 'tg-topic-summary' }, t('noTurns')),
|
|
2215
|
+
kids.length ? h('div', { style: { marginTop: '4px' } }, kids.map(function (kid) { return renderTopic(kid, depth + 1); })) : null,
|
|
2216
|
+
) : null,
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// ── 统计条 / 话题详情 / 跳转 ────────────────────────────────────────
|
|
2221
|
+
function renderStatsStrip() {
|
|
2222
|
+
if (!graph || !graph.totals) return null;
|
|
2223
|
+
var totals = graph.totals || {};
|
|
2224
|
+
var tokens = totals.tokens || {};
|
|
2225
|
+
var toolCalls = totals.toolCalls || { total: 0, byName: {} };
|
|
2226
|
+
var toolEntries = Object.keys(toolCalls.byName || {}).sort(function (a, b) {
|
|
2227
|
+
return toolCalls.byName[b] - toolCalls.byName[a];
|
|
2228
|
+
}).slice(0, 8);
|
|
2229
|
+
var items = [
|
|
2230
|
+
{ key: 'turns', label: t('statTurns'), value: String(totals.turnCount || 0) },
|
|
2231
|
+
{
|
|
2232
|
+
key: 'topics', label: t('statTopics'),
|
|
2233
|
+
value: String(totals.topicCount || 0) + (totals.subtopicCount ? ' + ' + totals.subtopicCount : ''),
|
|
2234
|
+
title: (function () {
|
|
2235
|
+
var subTopics = 0;
|
|
2236
|
+
(graph.branches || []).forEach(function (branch) { subTopics += (branch.topics || []).length; });
|
|
2237
|
+
return t('statTopics') + ' ' + String(totals.topicCount || 0) + ' · ' + t('subtopics') + ' ' + String(totals.subtopicCount || 0)
|
|
2238
|
+
+ (subTopics ? ' · 🤖 ' + subTopics : '');
|
|
2239
|
+
})(),
|
|
2240
|
+
},
|
|
2241
|
+
{ key: 'stages', label: t('statStage'), value: String(totals.stageCount || 0) },
|
|
2242
|
+
{
|
|
2243
|
+
key: 'tokens', label: t('statTokens'), value: fmtK(tokens.total || 0),
|
|
2244
|
+
title: t('input') + ' ' + fmtK(tokens.input) + ' · ' + t('output') + ' ' + fmtK(tokens.output)
|
|
2245
|
+
+ ' · ' + t('cacheRead') + ' ' + fmtK(tokens.cacheRead) + ' · ' + t('cacheWrite') + ' ' + fmtK(tokens.cacheWrite),
|
|
2246
|
+
},
|
|
2247
|
+
{
|
|
2248
|
+
key: 'tools', label: t('statTools'), value: String(toolCalls.total || 0),
|
|
2249
|
+
title: toolEntries.map(function (name) { return name + ' × ' + toolCalls.byName[name]; }).join(' · ') || t('toolCalls'),
|
|
2250
|
+
},
|
|
2251
|
+
{ key: 'step', label: t('statStepTime'), value: fmtDuration(totals.agentMs || 0), title: t('stepTime') },
|
|
2252
|
+
{ key: 'wall', label: t('statWallTime'), value: fmtDuration(totals.wallMs || 0), title: t('agentTime') },
|
|
2253
|
+
{ key: 'relations', label: t('statRelations'), value: String(totals.relationCount || 0) },
|
|
2254
|
+
];
|
|
2255
|
+
var ai = graph.ai || {};
|
|
2256
|
+
var aiTokens = ai.tokens || {};
|
|
2257
|
+
items.push({
|
|
2258
|
+
key: 'aicost', label: t('aiCost'), value: fmtK(aiTokens.total || 0) + ' · ' + (ai.calls || 0) + t('aiCallsUnit'),
|
|
2259
|
+
title: t('aiCost') + ' ' + fmtK(aiTokens.total || 0) + ' tokens · ' + (ai.calls || 0) + ' ' + t('aiCallsUnit') + '(仅切分/关系分析,不含主对话)',
|
|
2260
|
+
});
|
|
2261
|
+
var sub = totals.subagent;
|
|
2262
|
+
if (sub && sub.branchCount) {
|
|
2263
|
+
items.push({
|
|
2264
|
+
key: 'subagent', label: '🤖 ' + t('subagent'), value: fmtK((sub.tokens || {}).total || 0),
|
|
2265
|
+
title: t('statSubagentTokens') + ' ' + fmtK((sub.tokens || {}).total || 0) + ' · ' + t('stepTime') + ' ' + fmtDuration(sub.agentMs || 0),
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
return h('div', { className: 'tg-stats' },
|
|
2269
|
+
items.map(function (item) {
|
|
2270
|
+
return h('span', { key: item.key, className: 'tg-stat-chip', title: item.title || '' },
|
|
2271
|
+
h('b', null, item.value),
|
|
2272
|
+
h('span', null, item.label),
|
|
2273
|
+
);
|
|
2274
|
+
}),
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
/** 解析详情:主图话题用 byId;子智能体话题在 branches[].topics 里。 */
|
|
2279
|
+
function resolveDetail() {
|
|
2280
|
+
if (!detailTopicId) return null;
|
|
2281
|
+
if (!detailSessionId) {
|
|
2282
|
+
var parentTopic = byId[detailTopicId];
|
|
2283
|
+
return parentTopic ? { topic: parentTopic, sessionId: graph.sessionId, branch: null, isChild: false } : null;
|
|
2284
|
+
}
|
|
2285
|
+
var branches = graph.branches || [];
|
|
2286
|
+
for (var i = 0; i < branches.length; i += 1) {
|
|
2287
|
+
var branch = branches[i];
|
|
2288
|
+
if (!sameSession(branch.sessionId, detailSessionId)) continue;
|
|
2289
|
+
var childTopic = (branch.topics || []).filter(function (item) { return item.id === detailTopicId; })[0];
|
|
2290
|
+
if (childTopic) return { topic: childTopic, sessionId: branch.sessionId, branch: branch, isChild: true };
|
|
2291
|
+
}
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function childTopicTitle(branch, topicId) {
|
|
2296
|
+
if (!branch || !topicId) return topicId;
|
|
2297
|
+
var found = (branch.topics || []).filter(function (item) { return item.id === topicId; })[0];
|
|
2298
|
+
return found ? found.title : topicId;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
/** 图谱页的折叠箭头:阶段 / 话题 / 子智能体分支,各自独立记忆。 */
|
|
2302
|
+
function toggleGraphCollapse(kind, id) {
|
|
2303
|
+
setGraphCollapsed(function (prev) {
|
|
2304
|
+
var next = {
|
|
2305
|
+
stages: Object.assign({}, prev.stages),
|
|
2306
|
+
topics: Object.assign({}, prev.topics),
|
|
2307
|
+
branches: Object.assign({}, prev.branches),
|
|
2308
|
+
};
|
|
2309
|
+
next[kind][id] = !next[kind][id];
|
|
2310
|
+
return next;
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
function isCollapsed(kind, id) {
|
|
2315
|
+
return Boolean(graphCollapsed[kind] && graphCollapsed[kind][id]);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
function loadModels() {
|
|
2319
|
+
fetchJson(ROUTE + '/models').then(function (payload) {
|
|
2320
|
+
if (!payload || !payload.ok) return;
|
|
2321
|
+
setModelProviders(Array.isArray(payload.providers) ? payload.providers : []);
|
|
2322
|
+
if (payload.current && payload.current.provider && payload.current.model) {
|
|
2323
|
+
setModelSel(payload.current.provider + ':' + payload.current.model);
|
|
2324
|
+
}
|
|
2325
|
+
}).catch(function () { /* 模型列表失败不影响主功能 */ });
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
function onModelChange(value) {
|
|
2329
|
+
setModelSel(value || '');
|
|
2330
|
+
if (!value) {
|
|
2331
|
+
// 回退默认(config):清掉覆盖文件
|
|
2332
|
+
postJson(ROUTE + '/model-clear', {}).catch(function () {});
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
var idx = value.indexOf(':');
|
|
2336
|
+
if (idx <= 0) return;
|
|
2337
|
+
var provider = value.slice(0, idx);
|
|
2338
|
+
var model = value.slice(idx + 1);
|
|
2339
|
+
postJson(ROUTE + '/model', { provider: provider, model: model }).catch(function () {});
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
function analyzeTurn(turnNo, force) {
|
|
2343
|
+
if (!graph || !graph.sessionId) return;
|
|
2344
|
+
setLocalTurnAnalyses(function (prev) {
|
|
2345
|
+
var next = Object.assign({}, prev);
|
|
2346
|
+
next[turnNo] = { status: 'loading' };
|
|
2347
|
+
return next;
|
|
2348
|
+
});
|
|
2349
|
+
postJson(ROUTE + '/analyze-turn', { sessionId: graph.sessionId, turn: turnNo, force: force }).then(function (payload) {
|
|
2350
|
+
setLocalTurnAnalyses(function (prev) {
|
|
2351
|
+
var next = Object.assign({}, prev);
|
|
2352
|
+
if (payload && payload.ok && payload.analysis && Array.isArray(payload.analysis.steps)) {
|
|
2353
|
+
next[turnNo] = { status: 'done', steps: payload.analysis.steps };
|
|
2354
|
+
} else {
|
|
2355
|
+
next[turnNo] = { status: 'error', error: (payload && payload.error) || t('analyzeFailed') };
|
|
2356
|
+
}
|
|
2357
|
+
return next;
|
|
2358
|
+
});
|
|
2359
|
+
}).catch(function (err) {
|
|
2360
|
+
setLocalTurnAnalyses(function (prev) {
|
|
2361
|
+
var next = Object.assign({}, prev);
|
|
2362
|
+
next[turnNo] = { status: 'error', error: String((err && err.message) || err) };
|
|
2363
|
+
return next;
|
|
2364
|
+
});
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function selectTopicInStage(topic) {
|
|
2369
|
+
if (!topic) return;
|
|
2370
|
+
setDetailTopicId(null);
|
|
2371
|
+
setDetailSessionId(null);
|
|
2372
|
+
setSelectedTopicId(topic.id);
|
|
2373
|
+
setSelectedStageId(topic.stageId || null);
|
|
2374
|
+
setExpandedTopics(function (prev) {
|
|
2375
|
+
var next = Object.assign({}, prev);
|
|
2376
|
+
next[topic.id] = true;
|
|
2377
|
+
return next;
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
function openDetail(topicId) {
|
|
2382
|
+
if (!topicId || !byId[topicId]) return;
|
|
2383
|
+
setDetailSessionId(null);
|
|
2384
|
+
setDetailTopicId(topicId);
|
|
2385
|
+
setJumpStatus(null);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
function openChildDetail(sessionId, topicId) {
|
|
2389
|
+
if (!sessionId || !topicId) return;
|
|
2390
|
+
setDetailSessionId(sessionId);
|
|
2391
|
+
setDetailTopicId(topicId);
|
|
2392
|
+
setJumpStatus(null);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
function closeDetail() {
|
|
2396
|
+
setDetailSessionId(null);
|
|
2397
|
+
setDetailTopicId(null);
|
|
2398
|
+
setJumpStatus(null);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
async function jumpToTurn(turnNo) {
|
|
2402
|
+
if (turnNo == null) return;
|
|
2403
|
+
var sessionId = (graph && graph.sessionId) || '';
|
|
2404
|
+
try {
|
|
2405
|
+
var navigation = appCtx && typeof appCtx.get === 'function' ? appCtx.get('uiWorkspace') : null;
|
|
2406
|
+
if (navigation && typeof navigation.openSession === 'function') navigation.openSession(sessionId);
|
|
2407
|
+
} catch (error) { /* 导航不可用时就地定位 */ }
|
|
2408
|
+
await revealChatTurn(turnNo);
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
async function jumpToTopicStart(topic, sessionIdOverride) {
|
|
2412
|
+
if (!topic || (topic.turnStart == null && !(Array.isArray(topic.ownTurns) && topic.ownTurns.length))) {
|
|
2413
|
+
setJumpStatus('openedOnly');
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
var sessionId = sessionIdOverride || (graph && graph.sessionId);
|
|
2417
|
+
var firstTurn = Array.isArray(topic.ownTurns) && topic.ownTurns.length ? topic.ownTurns[0] : topic.turnStart;
|
|
2418
|
+
setJumpStatus('locating');
|
|
2419
|
+
try {
|
|
2420
|
+
var navigation = appCtx && typeof appCtx.get === 'function' ? appCtx.get('uiWorkspace') : null;
|
|
2421
|
+
if (navigation && typeof navigation.openSession === 'function') navigation.openSession(sessionId);
|
|
2422
|
+
} catch (error) { /* 导航失败时仍尝试在当前会话里定位 */ }
|
|
2423
|
+
var located = await revealChatTurn(firstTurn);
|
|
2424
|
+
setJumpStatus(located ? 'located' : 'openedOnly');
|
|
2425
|
+
setTimeout(function () { setJumpStatus(null); }, 3500);
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
function renderDetail(inline) {
|
|
2429
|
+
var resolved = resolveDetail();
|
|
2430
|
+
if (!resolved) return null;
|
|
2431
|
+
var topic = resolved.topic;
|
|
2432
|
+
var branch = resolved.branch;
|
|
2433
|
+
var isChild = resolved.isChild;
|
|
2434
|
+
var stats = topic.stats || {};
|
|
2435
|
+
var tokens = stats.tokens || {};
|
|
2436
|
+
var toolCalls = stats.toolCalls || { total: 0, byName: {} };
|
|
2437
|
+
var stagePool = isChild ? (branch.stages || []) : (graph.stages || []);
|
|
2438
|
+
var stage = stagePool.filter(function (item) { return item.id === topic.stageId; })[0] || null;
|
|
2439
|
+
var parent = null;
|
|
2440
|
+
var kids = [];
|
|
2441
|
+
var relations = [];
|
|
2442
|
+
if (isChild) {
|
|
2443
|
+
parent = topic.parentId
|
|
2444
|
+
? (branch.topics || []).filter(function (item) { return item.id === topic.parentId; })[0] || null
|
|
2445
|
+
: null;
|
|
2446
|
+
kids = (branch.topics || []).filter(function (item) { return item.parentId === topic.id; });
|
|
2447
|
+
relations = (branch.relations || []).filter(function (rel) {
|
|
2448
|
+
return rel.from === topic.id || rel.to === topic.id;
|
|
2449
|
+
}).map(function (rel) {
|
|
2450
|
+
var other = rel.from === topic.id ? rel.to : rel.from;
|
|
2451
|
+
return { direction: rel.from === topic.id ? 'out' : 'in', other: other, label: rel.label, otherTitle: childTopicTitle(branch, other) };
|
|
2452
|
+
});
|
|
2453
|
+
} else {
|
|
2454
|
+
parent = topic.parentId ? byId[topic.parentId] : null;
|
|
2455
|
+
kids = childMap[topic.id] || [];
|
|
2456
|
+
relations = (relationsByTopic[topic.id] || []).map(function (rel) {
|
|
2457
|
+
return { direction: rel.direction, other: rel.other, label: rel.label, otherTitle: topicTitleOf(rel.other) };
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
var delegatingTopic = isChild && branch.parentTopicId ? byId[branch.parentTopicId] : null;
|
|
2461
|
+
var toolEntries = Object.keys(toolCalls.byName || {}).sort(function (a, b) {
|
|
2462
|
+
return toolCalls.byName[b] - toolCalls.byName[a];
|
|
2463
|
+
}).slice(0, 10);
|
|
2464
|
+
var ownTurns = Array.isArray(topic.ownTurns) ? topic.ownTurns : [];
|
|
2465
|
+
return h('div', { className: 'tg-detail' + (inline ? ' tg-detail-inline' : '') },
|
|
2466
|
+
h('div', { className: 'tg-detail-head' },
|
|
2467
|
+
h('span', { className: 'tg-detail-title' }, (isChild ? '🤖 ' : '') + topic.title),
|
|
2468
|
+
h('button', { className: 'tg-btn', title: t('close'), onClick: closeDetail }, '✕'),
|
|
2469
|
+
),
|
|
2470
|
+
h('div', { className: 'tg-detail-body', style: { flex: 1, minHeight: 0, overflowY: 'auto' } },
|
|
2471
|
+
h('div', { className: 'tg-detail-kicker' },
|
|
2472
|
+
(isChild ? t('subagent') + ' · ' + (branch.title || sessionShortId(branch.sessionId)) + ' · ' : '')
|
|
2473
|
+
+ t('topic') + ' · ' + (topic.status === 'active' ? t('statusActive') : t('statusDone'))
|
|
2474
|
+
+ (topic.turnStart != null ? ' · ' + t('range') + ' ' + topic.turnStart + '-' + topic.turnEnd : '')),
|
|
2475
|
+
topic.summary ? h('div', { className: 'tg-topic-summary' }, topic.summary) : null,
|
|
2476
|
+
h('div', { className: 'tg-statgrid' },
|
|
2477
|
+
h('div', { className: 'tg-stat' },
|
|
2478
|
+
h('div', { className: 'tg-stat-label' }, t('tokens') + ' · ' + fmtPct(stats.tokenRatio)),
|
|
2479
|
+
h('div', { className: 'tg-stat-value' }, fmtK(tokens.total || 0)),
|
|
2480
|
+
h('div', { className: 'tg-detail-kv' }, t('input') + ' ' + fmtK(tokens.input) + ' · ' + t('output') + ' ' + fmtK(tokens.output)),
|
|
2481
|
+
h('div', { className: 'tg-detail-kv' }, t('cacheRead') + ' ' + fmtK(tokens.cacheRead) + ' · ' + t('cacheWrite') + ' ' + fmtK(tokens.cacheWrite)),
|
|
2482
|
+
),
|
|
2483
|
+
h('div', { className: 'tg-stat' },
|
|
2484
|
+
h('div', { className: 'tg-stat-label' }, t('toolCalls') + ' · ' + fmtPct(stats.toolRatio)),
|
|
2485
|
+
h('div', { className: 'tg-stat-value' }, String(toolCalls.total || 0)),
|
|
2486
|
+
h('div', { className: 'tg-detail-kv' }, toolEntries.map(function (name) { return name + '×' + toolCalls.byName[name]; }).join(' · ') || '—'),
|
|
2487
|
+
),
|
|
2488
|
+
h('div', { className: 'tg-stat' },
|
|
2489
|
+
h('div', { className: 'tg-stat-label' }, t('agentTime') + ' · ' + fmtPct(stats.wallRatio)),
|
|
2490
|
+
h('div', { className: 'tg-stat-value' }, fmtDuration(stats.wallMs || 0)),
|
|
2491
|
+
h('div', { className: 'tg-detail-kv' }, t('stepTime') + ' ' + fmtDuration(stats.agentMs || 0)),
|
|
2492
|
+
),
|
|
2493
|
+
h('div', { className: 'tg-stat' },
|
|
2494
|
+
h('div', { className: 'tg-stat-label' }, t('statTurns')),
|
|
2495
|
+
h('div', { className: 'tg-stat-value' }, String(stats.turnCount || 0)),
|
|
2496
|
+
h('div', { className: 'tg-detail-kv' }, stage ? t('detailStage') + ':' + stage.title : ''),
|
|
2497
|
+
),
|
|
2498
|
+
),
|
|
2499
|
+
delegatingTopic ? h('div', { className: 'tg-detail-section' },
|
|
2500
|
+
h('div', { className: 'tg-detail-section-title' }, t('parentTopic')),
|
|
2501
|
+
h('span', { className: 'tg-rel-chip', onClick: function () { openDetail(delegatingTopic.id); } }, delegatingTopic.title),
|
|
2502
|
+
) : null,
|
|
2503
|
+
parent ? h('div', { className: 'tg-detail-section' },
|
|
2504
|
+
h('div', { className: 'tg-detail-section-title' }, t('detailParent')),
|
|
2505
|
+
h('span', {
|
|
2506
|
+
className: 'tg-rel-chip',
|
|
2507
|
+
onClick: function () {
|
|
2508
|
+
if (isChild) openChildDetail(resolved.sessionId, parent.id);
|
|
2509
|
+
else openDetail(parent.id);
|
|
2510
|
+
},
|
|
2511
|
+
}, parent.title),
|
|
2512
|
+
) : null,
|
|
2513
|
+
relations.length ? h('div', { className: 'tg-detail-section' },
|
|
2514
|
+
h('div', { className: 'tg-detail-section-title' }, t('detailRelated')),
|
|
2515
|
+
h('div', { className: 'tg-relations' }, relations.map(function (rel, index) {
|
|
2516
|
+
return h('span', {
|
|
2517
|
+
key: 'drel-' + index,
|
|
2518
|
+
className: 'tg-rel-chip',
|
|
2519
|
+
onClick: function () {
|
|
2520
|
+
if (isChild) openChildDetail(resolved.sessionId, rel.other);
|
|
2521
|
+
else openDetail(rel.other);
|
|
2522
|
+
},
|
|
2523
|
+
}, (rel.direction === 'out' ? '→ ' : '← ') + rel.otherTitle + ' ', h('b', null, rel.label));
|
|
2524
|
+
})),
|
|
2525
|
+
) : null,
|
|
2526
|
+
kids.length ? h('div', { className: 'tg-detail-section' },
|
|
2527
|
+
h('div', { className: 'tg-detail-section-title' }, t('detailChildren')),
|
|
2528
|
+
h('div', { className: 'tg-relations' }, kids.map(function (kid) {
|
|
2529
|
+
return h('span', {
|
|
2530
|
+
key: kid.id,
|
|
2531
|
+
className: 'tg-rel-chip',
|
|
2532
|
+
onClick: function () {
|
|
2533
|
+
if (isChild) openChildDetail(resolved.sessionId, kid.id);
|
|
2534
|
+
else openDetail(kid.id);
|
|
2535
|
+
},
|
|
2536
|
+
}, kid.title);
|
|
2537
|
+
})),
|
|
2538
|
+
) : null,
|
|
2539
|
+
!isChild && topicBranches(topic.id).length ? h('div', { className: 'tg-detail-section' },
|
|
2540
|
+
h('div', { className: 'tg-detail-section-title' }, t('detailBranches')),
|
|
2541
|
+
topicBranches(topic.id).map(function (item) {
|
|
2542
|
+
return h('div', { key: item.sessionId, className: 'tg-subbranch', onClick: function () { selectSession(item.sessionId); closeDetail(); } },
|
|
2543
|
+
h('span', { className: 'tg-subbranch-name' }, '🤖 ' + (item.title || sessionShortId(item.sessionId))),
|
|
2544
|
+
h('span', { className: 'tg-subbranch-meta' }, (item.totals ? item.totals.turnCount : 0) + ' ' + t('turns') + ' · ' + fmtK(item.totals ? (item.totals.tokens || {}).total || 0 : 0)),
|
|
2545
|
+
);
|
|
2546
|
+
}),
|
|
2547
|
+
) : null,
|
|
2548
|
+
h('div', { className: 'tg-detail-section' },
|
|
2549
|
+
h('div', { className: 'tg-detail-section-title' }, t('detailTurns')),
|
|
2550
|
+
ownTurns.length
|
|
2551
|
+
? ownTurns.map(renderTurn)
|
|
2552
|
+
: h('div', { className: 'tg-topic-summary' },
|
|
2553
|
+
(isChild ? t('subagent') + ' · ' + (branch.title || sessionShortId(resolved.sessionId)) + ' · ' : '')
|
|
2554
|
+
+ String(stats.turnCount || 0) + ' ' + t('turns') + ' · ' + t('openAtFirstTurn')),
|
|
2555
|
+
),
|
|
2556
|
+
jumpStatus ? h('div', { className: 'tg-detail-status' },
|
|
2557
|
+
jumpStatus === 'locating' ? t('locating') : (jumpStatus === 'located' ? t('located') : t('openedOnly')),
|
|
2558
|
+
) : null,
|
|
2559
|
+
),
|
|
2560
|
+
h('div', { className: 'tg-detail-foot' },
|
|
2561
|
+
isChild ? h('button', { className: 'tg-btn', onClick: function () { selectSession(resolved.sessionId); closeDetail(); } }, '🤖 ' + t('branchOpen')) : null,
|
|
2562
|
+
h('button', { className: 'tg-btn primary', disabled: jumpStatus === 'locating', onClick: function () { void jumpToTopicStart(topic, resolved.sessionId); } }, t('openAtFirstTurn')),
|
|
2563
|
+
h('button', { className: 'tg-btn', onClick: closeDetail }, t('close')),
|
|
2564
|
+
),
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
// ── 渲染:链式 Tab ──────────────────────────────────────────────────
|
|
2569
|
+
function renderWorkflowTree() {
|
|
2570
|
+
if (!graph) return renderLoading();
|
|
2571
|
+
var stages = graph.stages || [];
|
|
2572
|
+
var topics = graph.topics || [];
|
|
2573
|
+
var rootsByStage = {};
|
|
2574
|
+
topics.forEach(function (topic) {
|
|
2575
|
+
if (topic.parentId) return;
|
|
2576
|
+
if (!rootsByStage[topic.stageId]) rootsByStage[topic.stageId] = [];
|
|
2577
|
+
rootsByStage[topic.stageId].push(topic);
|
|
2578
|
+
});
|
|
2579
|
+
var renderWfTopic = function (topic, depth) {
|
|
2580
|
+
var kids = childMap[topic.id] || [];
|
|
2581
|
+
var active = selectedTopicId === topic.id;
|
|
2582
|
+
var branches = topicBranches(topic.id);
|
|
2583
|
+
var stats = topic.stats || {};
|
|
2584
|
+
return h('div', { key: topic.id },
|
|
2585
|
+
h('div', {
|
|
2586
|
+
className: 'tg-wf-topic' + (active ? ' active' : ''),
|
|
2587
|
+
style: { paddingLeft: (8 + depth * 14) + 'px' },
|
|
2588
|
+
onClick: function () { selectTopicInStage(topic); },
|
|
2589
|
+
title: t('detailOpen'),
|
|
2590
|
+
},
|
|
2591
|
+
h('span', { className: 'tg-wf-topic-title' }, topic.title),
|
|
2592
|
+
h('span', { className: 'tg-wf-topic-meta' },
|
|
2593
|
+
(topic.turnStart != null ? '轮' + topic.turnStart + '-' + topic.turnEnd + ' · ' : '')
|
|
2594
|
+
+ '⏱' + fmtPct(stats.wallRatio) + ' ◈' + fmtPct(stats.tokenRatio)
|
|
2595
|
+
+ (branches.length ? ' · 🤖' + branches.length : '')),
|
|
2596
|
+
),
|
|
2597
|
+
kids.map(function (kid) { return renderWfTopic(kid, depth + 1); }),
|
|
2598
|
+
);
|
|
2599
|
+
};
|
|
2600
|
+
return h('div', { className: 'tg-wf-tree' },
|
|
2601
|
+
h('div', { className: 'tg-wf-tree-head' },
|
|
2602
|
+
h('span', { className: 'tg-wf-tree-head-title' }, stages.length + ' ' + t('statStage')),
|
|
2603
|
+
h('button', { className: 'tg-btn tg-wf-tree-btn', onClick: function () { setAllStages(true); } }, t('expandAll')),
|
|
2604
|
+
h('button', { className: 'tg-btn tg-wf-tree-btn', onClick: function () { setAllStages(false); } }, t('collapseAll')),
|
|
2605
|
+
),
|
|
2606
|
+
stages.length ? stages.map(function (stage) {
|
|
2607
|
+
var roots = rootsByStage[stage.id] || [];
|
|
2608
|
+
var open = expandedStages[stage.id] !== false;
|
|
2609
|
+
var stageActive = selectedStageId === stage.id && !detailTopicId;
|
|
2610
|
+
var stageStats = stage.stats || {};
|
|
2611
|
+
var stageTokens = stageStats.tokens || {};
|
|
2612
|
+
return h('div', { key: stage.id, className: 'tg-wf-stage' },
|
|
2613
|
+
h('div', {
|
|
2614
|
+
className: 'tg-wf-stage-head' + (stageActive ? ' active' : ''),
|
|
2615
|
+
onClick: function () {
|
|
2616
|
+
setDetailTopicId(null);
|
|
2617
|
+
setDetailSessionId(null);
|
|
2618
|
+
setSelectedTopicId(null);
|
|
2619
|
+
setSelectedStageId(stage.id);
|
|
2620
|
+
},
|
|
2621
|
+
},
|
|
2622
|
+
h('div', { className: 'tg-wf-stage-title-row' },
|
|
2623
|
+
h('span', {
|
|
2624
|
+
className: 'tg-wf-stage-toggle',
|
|
2625
|
+
onClick: function (event) { event.stopPropagation(); toggleStage(stage.id); },
|
|
2626
|
+
title: open ? t('collapse') : t('expand'),
|
|
2627
|
+
}, h('span', { className: 'tg-chev' + (open ? ' open' : '') }, '▶')),
|
|
2628
|
+
h('span', { className: 'tg-wf-stage-idx' }, String(stage.order + 1)),
|
|
2629
|
+
h('span', { className: 'tg-wf-stage-title' }, stage.title),
|
|
2630
|
+
h('span', { className: 'tg-wf-stage-meta' }, roots.length + ' ' + t('topics')),
|
|
2631
|
+
),
|
|
2632
|
+
h('div', { className: 'tg-wf-stage-metrics' },
|
|
2633
|
+
h('span', { className: 'tg-wf-metric' }, 'Token ' + fmtK(stageTokens.total || 0) + ' · ' + fmtPct(stageStats.tokenRatio)),
|
|
2634
|
+
h('span', { className: 'tg-wf-metric tg-wf-metric-time' }, '时间 ' + fmtDuration(stageStats.wallMs || 0) + ' · ' + fmtPct(stageStats.wallRatio)),
|
|
2635
|
+
),
|
|
2636
|
+
),
|
|
2637
|
+
open && roots.length ? h('div', { className: 'tg-wf-stage-topics' }, roots.map(function (root) { return renderWfTopic(root, 0); })) : null,
|
|
2638
|
+
);
|
|
2639
|
+
}) : h('div', { className: 'tg-wf-empty' }, t('noTopics')),
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
/** 阶段内 mini 时间线:X=轮次,展示该阶段各话题的点与它们之间的关系边。 */
|
|
2644
|
+
function renderStageMiniTimeline(stage, roots) {
|
|
2645
|
+
var ids = {};
|
|
2646
|
+
roots.forEach(function (topic) { ids[topic.id] = true; });
|
|
2647
|
+
var rels = (graph.relations || []).filter(function (rel) { return ids[rel.from] && ids[rel.to]; });
|
|
2648
|
+
// 阶段内相邻 topic 之间只有 1 条关系边时不画(避免和顺序流重复、显得乱)
|
|
2649
|
+
var adjacentPairs = {};
|
|
2650
|
+
for (var ap = 0; ap < roots.length - 1; ap += 1) {
|
|
2651
|
+
adjacentPairs[roots[ap].id + '>' + roots[ap + 1].id] = true;
|
|
2652
|
+
adjacentPairs[roots[ap + 1].id + '>' + roots[ap].id] = true;
|
|
2653
|
+
}
|
|
2654
|
+
var pairCount = {};
|
|
2655
|
+
rels.forEach(function (rel) {
|
|
2656
|
+
var key = [rel.from, rel.to].sort().join('>');
|
|
2657
|
+
pairCount[key] = (pairCount[key] || 0) + 1;
|
|
2658
|
+
});
|
|
2659
|
+
rels = rels.filter(function (rel) {
|
|
2660
|
+
if (adjacentPairs[rel.from + '>' + rel.to] === true) return false;
|
|
2661
|
+
return true;
|
|
2662
|
+
});
|
|
2663
|
+
var minTurn = Infinity;
|
|
2664
|
+
var maxTurn = -Infinity;
|
|
2665
|
+
roots.forEach(function (topic) {
|
|
2666
|
+
minTurn = Math.min(minTurn, topic.turnStart || 1);
|
|
2667
|
+
maxTurn = Math.max(maxTurn, topic.turnEnd || topic.turnStart || 1);
|
|
2668
|
+
});
|
|
2669
|
+
if (!isFinite(minTurn)) { minTurn = 1; maxTurn = 1; }
|
|
2670
|
+
var span = Math.max(1, maxTurn - minTurn);
|
|
2671
|
+
var W = 600;
|
|
2672
|
+
var laneY = 52;
|
|
2673
|
+
var padX = 30;
|
|
2674
|
+
var pos = {};
|
|
2675
|
+
roots.forEach(function (topic) {
|
|
2676
|
+
var ratio = span > 0 ? ((topic.turnStart || 1) - minTurn) / span : 0.5;
|
|
2677
|
+
pos[topic.id] = { x: padX + ratio * (W - padX * 2), y: laneY, topic: topic };
|
|
2678
|
+
});
|
|
2679
|
+
// 关系边按 x 区间分层,重叠的边抬到不同高度,避免叠在一起
|
|
2680
|
+
var rawArcs = [];
|
|
2681
|
+
rels.forEach(function (rel) {
|
|
2682
|
+
var a = pos[rel.from];
|
|
2683
|
+
var b = pos[rel.to];
|
|
2684
|
+
if (!a || !b) return;
|
|
2685
|
+
rawArcs.push({
|
|
2686
|
+
id: rel.from + '>' + rel.to,
|
|
2687
|
+
x1: a.x,
|
|
2688
|
+
x2: b.x,
|
|
2689
|
+
left: Math.min(a.x, b.x),
|
|
2690
|
+
right: Math.max(a.x, b.x),
|
|
2691
|
+
from: rel.from,
|
|
2692
|
+
to: rel.to,
|
|
2693
|
+
label: rel.label || '',
|
|
2694
|
+
});
|
|
2695
|
+
});
|
|
2696
|
+
rawArcs.sort(function (p, q) { return p.left - q.left || p.right - q.right; });
|
|
2697
|
+
var layerEnd = [];
|
|
2698
|
+
rawArcs.forEach(function (arc) {
|
|
2699
|
+
var layer = 0;
|
|
2700
|
+
while (layer < layerEnd.length && layerEnd[layer] > arc.left) layer += 1;
|
|
2701
|
+
if (layer === layerEnd.length) layerEnd.push(arc.right);
|
|
2702
|
+
else layerEnd[layer] = arc.right;
|
|
2703
|
+
arc.layer = layer;
|
|
2704
|
+
});
|
|
2705
|
+
var maxLayer = Math.max(0, layerEnd.length - 1);
|
|
2706
|
+
var baseLift = 20;
|
|
2707
|
+
var liftStep = 14;
|
|
2708
|
+
var topMost = laneY - baseLift - maxLayer * liftStep - 10;
|
|
2709
|
+
var viewTop = Math.min(6, topMost);
|
|
2710
|
+
var viewH = (laneY + 26) - viewTop;
|
|
2711
|
+
var arcs = rawArcs.map(function (arc) {
|
|
2712
|
+
var lift = baseLift + arc.layer * liftStep;
|
|
2713
|
+
return {
|
|
2714
|
+
id: arc.id,
|
|
2715
|
+
d: 'M ' + arc.x1 + ' ' + laneY + ' C ' + arc.x1 + ' ' + (laneY - lift) + ', ' + arc.x2 + ' ' + (laneY - lift) + ', ' + arc.x2 + ' ' + laneY,
|
|
2716
|
+
label: arc.label,
|
|
2717
|
+
labelX: (arc.x1 + arc.x2) / 2,
|
|
2718
|
+
labelY: laneY - lift - 6,
|
|
2719
|
+
from: arc.from,
|
|
2720
|
+
to: arc.to,
|
|
2721
|
+
};
|
|
2722
|
+
});
|
|
2723
|
+
return h('div', { className: 'tg-wf-mini-wrap' },
|
|
2724
|
+
h('div', { className: 'tg-wf-mini-title' }, t('stageTopicRelations') || '话题关系'),
|
|
2725
|
+
h('svg', {
|
|
2726
|
+
className: 'tg-wf-mini',
|
|
2727
|
+
viewBox: '0 ' + viewTop + ' ' + W + ' ' + viewH,
|
|
2728
|
+
preserveAspectRatio: 'xMidYMid meet',
|
|
2729
|
+
style: { display: 'block', width: '100%', height: 'auto' },
|
|
2730
|
+
},
|
|
2731
|
+
h('line', { className: 'tg-wf-mini-base', x1: 16, y1: laneY, x2: W - 16, y2: laneY }),
|
|
2732
|
+
arcs.map(function (arc) {
|
|
2733
|
+
var active = wfMiniHover && (arc.from === wfMiniHover || arc.to === wfMiniHover);
|
|
2734
|
+
return h('g', { key: 'arc-' + arc.id, className: 'tg-wf-mini-arc' + (active ? ' active' : '') },
|
|
2735
|
+
h('path', { d: arc.d }),
|
|
2736
|
+
arc.label ? h('text', { x: arc.labelX, y: arc.labelY }, arc.label) : null,
|
|
2737
|
+
);
|
|
2738
|
+
}),
|
|
2739
|
+
roots.map(function (topic, i) {
|
|
2740
|
+
var p = pos[topic.id];
|
|
2741
|
+
var active = wfMiniHover === topic.id || selectedTopicId === topic.id;
|
|
2742
|
+
return h('g', {
|
|
2743
|
+
key: 'dot-' + topic.id,
|
|
2744
|
+
className: 'tg-wf-mini-dot' + (active ? ' active' : ''),
|
|
2745
|
+
onMouseEnter: function () { setWfMiniHover(topic.id); },
|
|
2746
|
+
onMouseLeave: function () { setWfMiniHover(null); },
|
|
2747
|
+
onClick: function () { setSelectedTopicId(topic.id); },
|
|
2748
|
+
},
|
|
2749
|
+
h('circle', { cx: p.x, cy: p.y, r: 5, style: { fill: TIMELINE_PALETTE[i % TIMELINE_PALETTE.length] } }),
|
|
2750
|
+
h('text', { className: 'tg-wf-mini-idx', x: p.x, y: laneY + 22 }, topic.turnStart != null ? '轮' + topic.turnStart + '-' + topic.turnEnd : ''),
|
|
2751
|
+
h('title', null, topic.title),
|
|
2752
|
+
);
|
|
2753
|
+
}),
|
|
2754
|
+
),
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
function renderStageTopicCard(topic, depth) {
|
|
2759
|
+
var open = expandedTopics[topic.id] === true;
|
|
2760
|
+
var kids = childMap[topic.id] || [];
|
|
2761
|
+
var stats = topic.stats || {};
|
|
2762
|
+
var tokens = stats.tokens || {};
|
|
2763
|
+
var branches = topicBranches(topic.id);
|
|
2764
|
+
return h('div', { key: topic.id, className: 'tg-wf-topic-card' + (open ? ' open' : '') },
|
|
2765
|
+
h('div', {
|
|
2766
|
+
className: 'tg-wf-topic-card-head',
|
|
2767
|
+
style: { paddingLeft: (10 + depth * 14) + 'px' },
|
|
2768
|
+
},
|
|
2769
|
+
h('span', {
|
|
2770
|
+
className: 'tg-wf-topic-card-toggle',
|
|
2771
|
+
onClick: function (event) { event.stopPropagation(); toggleTopic(topic.id); },
|
|
2772
|
+
title: open ? t('collapse') : t('expand'),
|
|
2773
|
+
},
|
|
2774
|
+
h('span', { className: 'tg-chev' + (open ? ' open' : '') }, '▶'),
|
|
2775
|
+
h('span', { className: 'tg-wf-topic-card-title' }, topic.title),
|
|
2776
|
+
),
|
|
2777
|
+
h('span', { className: 'tg-wf-topic-card-meta' },
|
|
2778
|
+
(topic.turnStart != null ? '轮' + topic.turnStart + '-' + topic.turnEnd + ' · ' : '')
|
|
2779
|
+
+ fmtK(tokens.total || 0) + ' · ' + fmtDuration(stats.wallMs || 0)
|
|
2780
|
+
+ (branches.length ? ' · 🤖' + branches.length : '')),
|
|
2781
|
+
),
|
|
2782
|
+
open ? h('div', { className: 'tg-wf-topic-card-body' },
|
|
2783
|
+
topic.summary ? h('div', { className: 'tg-wf-topic-summary' }, topic.summary) : null,
|
|
2784
|
+
(topic.ownTurns && topic.ownTurns.length) ? h('div', { className: 'tg-detail-section' },
|
|
2785
|
+
h('div', { className: 'tg-detail-section-title' }, t('detailTurns')),
|
|
2786
|
+
topic.ownTurns.map(renderTurn),
|
|
2787
|
+
) : null,
|
|
2788
|
+
kids.map(function (kid) { return renderStageTopicCard(kid, depth + 1); }),
|
|
2789
|
+
) : null,
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
function barPct(ratio) {
|
|
2794
|
+
var value = Number(ratio) || 0;
|
|
2795
|
+
return Math.max(1.5, Math.min(100, value * 100));
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
function renderStageDetail(stage) {
|
|
2799
|
+
var topics = graph.topics || [];
|
|
2800
|
+
var roots = topics.filter(function (t) { return !t.parentId && t.stageId === stage.id; })
|
|
2801
|
+
.sort(function (a, b) { return (a.turnStart || 0) - (b.turnStart || 0); });
|
|
2802
|
+
var stats = stage.stats || {};
|
|
2803
|
+
var tokens = stats.tokens || {};
|
|
2804
|
+
var toolCalls = stats.toolCalls || { total: 0 };
|
|
2805
|
+
var agentMs = stats.agentMs || 0;
|
|
2806
|
+
return h('div', { className: 'tg-wf-stage-detail' },
|
|
2807
|
+
h('div', { className: 'tg-wf-stage-detail-title' }, stage.title),
|
|
2808
|
+
stage.summary ? h('div', { className: 'tg-wf-stage-detail-summary' }, stage.summary) : null,
|
|
2809
|
+
h('div', { className: 'tg-wf-stage-detail-meta' },
|
|
2810
|
+
roots.length + ' ' + t('topics') + ' · ' + (stats.turnCount || 0) + ' ' + t('turns') + ' · ' + t('agentTime') + ' ' + fmtDuration(agentMs)),
|
|
2811
|
+
h('div', { className: 'tg-wf-statgrid' },
|
|
2812
|
+
h('div', { className: 'tg-wf-stat' },
|
|
2813
|
+
h('div', { className: 'tg-wf-stat-top' }, h('span', null, t('statTokens')), h('b', null, fmtK(tokens.total || 0))),
|
|
2814
|
+
h('div', { className: 'tg-wf-bar' }, h('i', { className: 'brand', style: { width: barPct(stats.tokenRatio) + '%' } })),
|
|
2815
|
+
h('div', { className: 'tg-wf-stat-ratio' }, t('statTokens') + ' ' + fmtPct(stats.tokenRatio)),
|
|
2816
|
+
),
|
|
2817
|
+
h('div', { className: 'tg-wf-stat' },
|
|
2818
|
+
h('div', { className: 'tg-wf-stat-top' }, h('span', null, t('statWallTime')), h('b', null, fmtDuration(stats.wallMs || 0))),
|
|
2819
|
+
h('div', { className: 'tg-wf-bar' }, h('i', { className: 'green', style: { width: barPct(stats.wallRatio) + '%' } })),
|
|
2820
|
+
h('div', { className: 'tg-wf-stat-ratio' }, t('statWallTime') + ' ' + fmtPct(stats.wallRatio)),
|
|
2821
|
+
),
|
|
2822
|
+
h('div', { className: 'tg-wf-stat' },
|
|
2823
|
+
h('div', { className: 'tg-wf-stat-top' }, h('span', null, t('statTools')), h('b', null, String(toolCalls.total || 0))),
|
|
2824
|
+
h('div', { className: 'tg-wf-bar' }, h('i', { className: 'warn', style: { width: barPct(stats.toolRatio) + '%' } })),
|
|
2825
|
+
h('div', { className: 'tg-wf-stat-ratio' }, t('statTools') + ' ' + fmtPct(stats.toolRatio)),
|
|
2826
|
+
),
|
|
2827
|
+
),
|
|
2828
|
+
roots.length >= 2 ? renderStageMiniTimeline(stage, roots) : null,
|
|
2829
|
+
roots.length ? h('div', { className: 'tg-wf-stage-topics' }, roots.map(function (root) { return renderStageTopicCard(root, 0); })) : null,
|
|
2830
|
+
);
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
function renderWorkflowDetail() {
|
|
2834
|
+
if (!graph) return null;
|
|
2835
|
+
var stages = graph.stages || [];
|
|
2836
|
+
var stage = selectedStageId ? stages.filter(function (s) { return s.id === selectedStageId; })[0] : stages[0];
|
|
2837
|
+
if (!stage) return h('div', { className: 'tg-wf-empty' }, t('workflowEmpty'));
|
|
2838
|
+
return renderStageDetail(stage);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
function renderChain() {
|
|
2842
|
+
if (!graph) return renderLoading();
|
|
2843
|
+
if (!graph.turns || !graph.turns.length) {
|
|
2844
|
+
return h('div', { className: 'tg-empty' }, t('noTurns'));
|
|
2845
|
+
}
|
|
2846
|
+
var stages = graph.stages || [];
|
|
2847
|
+
if (!stages.length) return renderNoTopics();
|
|
2848
|
+
return h('div', { className: 'tg-chain' },
|
|
2849
|
+
stages.map(function (stage, index) {
|
|
2850
|
+
var open = expandedStages[stage.id] !== false;
|
|
2851
|
+
var stats = stage.stats || {};
|
|
2852
|
+
var topicCount = (stage.topicIds || []).length;
|
|
2853
|
+
return h('div', { key: stage.id, className: 'tg-stage' },
|
|
2854
|
+
h('div', { className: 'tg-stage-head', onClick: function () { toggleStage(stage.id); } },
|
|
2855
|
+
h('span', { className: 'tg-stage-index' }, String(index + 1)),
|
|
2856
|
+
h('span', { className: 'tg-stage-title' }, stage.title || t('stage')),
|
|
2857
|
+
h('span', { className: 'tg-stage-chips' },
|
|
2858
|
+
h('span', { className: 'tg-chip' }, fmtK((stats.tokens && stats.tokens.total) || 0)),
|
|
2859
|
+
h('span', { className: 'tg-chip' }, fmtPct(stats.tokenRatio)),
|
|
2860
|
+
h('span', { className: 'tg-chip' }, topicCount + ' ' + t('topics')),
|
|
2861
|
+
),
|
|
2862
|
+
h('span', { className: 'tg-chev' + (open ? ' open' : '') }, '▶'),
|
|
2863
|
+
),
|
|
2864
|
+
open ? h('div', { className: 'tg-stage-body' },
|
|
2865
|
+
(stage.topicIds || []).map(function (topicId) { return renderTopic(byId[topicId], 0); }),
|
|
2866
|
+
) : null,
|
|
2867
|
+
);
|
|
2868
|
+
}),
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
function renderNoTopics() {
|
|
2873
|
+
var ai = (graph && graph.ai) || {};
|
|
2874
|
+
var running = ai.status === 'running' || (graph && graph.totals && graph.totals.openTurn);
|
|
2875
|
+
return h('div', { className: 'tg-empty' },
|
|
2876
|
+
running ? h('div', { className: 'tg-spin' }) : null,
|
|
2877
|
+
h('div', null, running ? t('segmenting') : t('noTopics')),
|
|
2878
|
+
ai.error ? h('div', { style: { marginTop: '8px', color: 'var(--dsw-alias-state-error-primary,#ef4444)' } }, ai.error) : null,
|
|
2879
|
+
!running && !ai.error ? h('div', { style: { marginTop: '10px' } },
|
|
2880
|
+
h('button', { className: 'tg-btn primary', onClick: function () { void triggerSegment(false); } }, t('segNow')),
|
|
2881
|
+
) : null,
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
function renderLoading() {
|
|
2886
|
+
return h('div', { className: 'tg-empty' },
|
|
2887
|
+
h('div', { className: 'tg-spin' }),
|
|
2888
|
+
h('div', null, t('loading')),
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
// ── 渲染:图谱 Tab ──────────────────────────────────────────────────
|
|
2893
|
+
function branchTooltipContent(branch) {
|
|
2894
|
+
var totals = branch.totals || {};
|
|
2895
|
+
var tokens = totals.tokens || {};
|
|
2896
|
+
var children = branch.topics || [];
|
|
2897
|
+
return h('div', null,
|
|
2898
|
+
h('div', { className: 'tg-tip-kind' }, '🤖 ' + t('subagent') + (branch.depth > 1 ? ' L' + branch.depth : '')),
|
|
2899
|
+
h('div', { className: 'tg-tip-title' }, branch.title || sessionShortId(branch.sessionId)),
|
|
2900
|
+
h('div', { className: 'tg-tip-grid' },
|
|
2901
|
+
h('span', { className: 'k' }, t('parentTopic')),
|
|
2902
|
+
h('span', { className: 'v' }, branch.parentTopicId ? topicTitleOf(branch.parentTopicId) : '-'),
|
|
2903
|
+
h('span', { className: 'k' }, t('range')),
|
|
2904
|
+
h('span', { className: 'v' }, '父轮 ' + (branch.parentTurn != null ? branch.parentTurn : '-') + ' · ' + totals.turnCount + ' ' + t('turns')),
|
|
2905
|
+
h('span', { className: 'k' }, t('tokens')),
|
|
2906
|
+
h('span', { className: 'v' }, fmtK(tokens.total)),
|
|
2907
|
+
h('span', { className: 'k' }, t('input') + '/' + t('output')),
|
|
2908
|
+
h('span', { className: 'v' }, fmtK(tokens.input) + ' / ' + fmtK(tokens.output)),
|
|
2909
|
+
h('span', { className: 'k' }, t('toolCalls')),
|
|
2910
|
+
h('span', { className: 'v' }, String((totals.toolCalls && totals.toolCalls.total) || 0)),
|
|
2911
|
+
h('span', { className: 'k' }, t('agentTime')),
|
|
2912
|
+
h('span', { className: 'v' }, fmtDuration(totals.wallMs)),
|
|
2913
|
+
),
|
|
2914
|
+
children.length ? h('div', { className: 'tg-tip-list' },
|
|
2915
|
+
h('div', null, t('branchTopics') + ' (' + children.length + ')'),
|
|
2916
|
+
children.slice(0, 8).map(function (topic) {
|
|
2917
|
+
return h('div', { key: topic.id }, '· ' + topic.title + (topic.stats ? ' · ' + fmtK(topic.stats.tokens.total) : ''));
|
|
2918
|
+
}),
|
|
2919
|
+
) : h('div', { className: 'tg-tip-list' }, branch.pending ? t('branchPending') : ''),
|
|
2920
|
+
h('div', { className: 'tg-tip-list', style: { marginTop: '4px' } }, '点击查看该子智能体会话'),
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
function branchTopicTooltipContent(node) {
|
|
2925
|
+
var branch = node.branch;
|
|
2926
|
+
var topic = node.topic;
|
|
2927
|
+
var stats = topic.stats || {};
|
|
2928
|
+
var tokens = stats.tokens || {};
|
|
2929
|
+
var toolCalls = stats.toolCalls || { total: 0, byName: {} };
|
|
2930
|
+
var stage = (branch.stages || []).filter(function (item) { return item.id === topic.stageId; })[0] || null;
|
|
2931
|
+
var toolEntries = Object.keys(toolCalls.byName || {}).sort(function (a, b) {
|
|
2932
|
+
return toolCalls.byName[b] - toolCalls.byName[a];
|
|
2933
|
+
}).slice(0, 6);
|
|
2934
|
+
return h('div', null,
|
|
2935
|
+
h('div', { className: 'tg-tip-kind' }, '🤖 ' + t('subagent') + ' · ' + (branch.title || sessionShortId(branch.sessionId))),
|
|
2936
|
+
h('div', { className: 'tg-tip-title' }, topic.title || ''),
|
|
2937
|
+
topic.summary ? h('div', { className: 'tg-tip-summary' }, topic.summary) : null,
|
|
2938
|
+
h('div', { className: 'tg-tip-grid' },
|
|
2939
|
+
h('span', { className: 'k' }, t('range')),
|
|
2940
|
+
h('span', { className: 'v' }, (topic.turnStart != null ? topic.turnStart + '-' + topic.turnEnd : '-') + ' · ' + (stats.turnCount || 0) + ' ' + t('turns')),
|
|
2941
|
+
h('span', { className: 'k' }, t('tokens')),
|
|
2942
|
+
h('span', { className: 'v' }, fmtK(tokens.total || 0) + ' · ' + fmtPct(stats.tokenRatio)),
|
|
2943
|
+
h('span', { className: 'k' }, t('input') + '/' + t('output')),
|
|
2944
|
+
h('span', { className: 'v' }, fmtK(tokens.input) + ' / ' + fmtK(tokens.output)),
|
|
2945
|
+
h('span', { className: 'k' }, t('toolCalls')),
|
|
2946
|
+
h('span', { className: 'v' }, String(toolCalls.total || 0) + ' · ' + fmtPct(stats.toolRatio)),
|
|
2947
|
+
h('span', { className: 'k' }, t('agentTime')),
|
|
2948
|
+
h('span', { className: 'v' }, fmtDuration(stats.wallMs || 0) + ' · ' + fmtPct(stats.wallRatio)),
|
|
2949
|
+
stage ? h('span', { className: 'k' }, t('detailStage')) : null,
|
|
2950
|
+
stage ? h('span', { className: 'v' }, stage.title) : null,
|
|
2951
|
+
),
|
|
2952
|
+
toolEntries.length ? h('div', { className: 'tg-tip-list' },
|
|
2953
|
+
h('div', null, t('toolDistribution')),
|
|
2954
|
+
toolEntries.map(function (name) { return h('div', { key: name }, name + ' × ' + toolCalls.byName[name]); }),
|
|
2955
|
+
) : null,
|
|
2956
|
+
h('div', { className: 'tg-tip-list', style: { marginTop: '4px' } }, t('detailOpen') + ' · ' + t('branchOpen')),
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
function nodeTooltipContent(node) {
|
|
2961
|
+
if (!node) return null;
|
|
2962
|
+
if (node.kind === 'branchHeader' || node.kind === 'branch') return branchTooltipContent(node.branch);
|
|
2963
|
+
if (node.kind === 'branchTopic') return branchTopicTooltipContent(node);
|
|
2964
|
+
var item = node.kind === 'stage' ? node.stage : node.topic;
|
|
2965
|
+
var stats = item.stats || {};
|
|
2966
|
+
var tokens = stats.tokens || {};
|
|
2967
|
+
var toolCalls = stats.toolCalls || { total: 0, byName: {} };
|
|
2968
|
+
var toolEntries = Object.keys(toolCalls.byName || {}).sort(function (a, b) {
|
|
2969
|
+
return toolCalls.byName[b] - toolCalls.byName[a];
|
|
2970
|
+
}).slice(0, 8);
|
|
2971
|
+
return h('div', null,
|
|
2972
|
+
h('div', { className: 'tg-tip-kind' }, node.kind === 'stage' ? t('stage') : (item.parentId ? t('subtopics') : t('topic'))),
|
|
2973
|
+
h('div', { className: 'tg-tip-title' }, item.title || ''),
|
|
2974
|
+
item.summary ? h('div', { className: 'tg-tip-summary' }, item.summary) : null,
|
|
2975
|
+
h('div', { className: 'tg-tip-grid' },
|
|
2976
|
+
h('span', { className: 'k' }, t('range')),
|
|
2977
|
+
h('span', { className: 'v' }, (item.turnStart != null ? item.turnStart + '-' + item.turnEnd : '-') + ' · ' + (stats.turnCount || 0) + ' ' + t('turns')),
|
|
2978
|
+
h('span', { className: 'k' }, t('tokens')),
|
|
2979
|
+
h('span', { className: 'v' }, fmtK(tokens.total) + ' · ' + fmtPct(stats.tokenRatio)),
|
|
2980
|
+
h('span', { className: 'k' }, t('input')),
|
|
2981
|
+
h('span', { className: 'v' }, fmtK(tokens.input)),
|
|
2982
|
+
h('span', { className: 'k' }, t('output')),
|
|
2983
|
+
h('span', { className: 'v' }, fmtK(tokens.output)),
|
|
2984
|
+
h('span', { className: 'k' }, t('cacheRead')),
|
|
2985
|
+
h('span', { className: 'v' }, fmtK(tokens.cacheRead)),
|
|
2986
|
+
h('span', { className: 'k' }, t('toolCalls')),
|
|
2987
|
+
h('span', { className: 'v' }, String(toolCalls.total || 0) + ' · ' + fmtPct(stats.toolRatio)),
|
|
2988
|
+
h('span', { className: 'k' }, t('agentTime')),
|
|
2989
|
+
h('span', { className: 'v' }, fmtDuration(stats.wallMs) + ' · ' + fmtPct(stats.wallRatio)),
|
|
2990
|
+
h('span', { className: 'k' }, t('stepTime')),
|
|
2991
|
+
h('span', { className: 'v' }, fmtDuration(stats.agentMs)),
|
|
2992
|
+
node.kind === 'topic' ? h('span', { className: 'k' }, t('statusActive')) : null,
|
|
2993
|
+
node.kind === 'topic' ? h('span', { className: 'v' }, item.status === 'active' ? t('statusActive') : t('statusDone')) : null,
|
|
2994
|
+
node.kind === 'topic' && item.branchIds && item.branchIds.length ? h('span', { className: 'k' }, t('subagents')) : null,
|
|
2995
|
+
node.kind === 'topic' && item.branchIds && item.branchIds.length ? h('span', { className: 'v' }, String(item.branchIds.length)) : null,
|
|
2996
|
+
),
|
|
2997
|
+
node.kind === 'topic' && item.branchIds && item.branchIds.length
|
|
2998
|
+
? h('div', { className: 'tg-tip-list' },
|
|
2999
|
+
h('div', null, t('subagents')),
|
|
3000
|
+
item.branchIds.slice(0, 6).map(function (sessionId) {
|
|
3001
|
+
var branch = branchBySession[sessionId];
|
|
3002
|
+
return h('div', { key: sessionId }, '🤖 ' + (branch ? (branch.title || sessionShortId(sessionId)) : sessionShortId(sessionId))
|
|
3003
|
+
+ (branch && branch.totals ? ' · ' + fmtK(branch.totals.tokens.total) : ''));
|
|
3004
|
+
}),
|
|
3005
|
+
)
|
|
3006
|
+
: null,
|
|
3007
|
+
toolEntries.length ? h('div', { className: 'tg-tip-list' },
|
|
3008
|
+
h('div', null, t('toolDistribution')),
|
|
3009
|
+
toolEntries.map(function (toolName) {
|
|
3010
|
+
return h('div', { key: toolName }, toolName + ' × ' + toolCalls.byName[toolName]);
|
|
3011
|
+
}),
|
|
3012
|
+
) : null,
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
function renderGraphTooltip() {
|
|
3017
|
+
if (!hover) return null;
|
|
3018
|
+
var tipWidth = 330;
|
|
3019
|
+
var x = clamp(hover.clientX + 16, 8, Math.max(8, window.innerWidth - tipWidth - 12));
|
|
3020
|
+
var y = clamp(hover.clientY + 16, 8, Math.max(8, window.innerHeight - 300));
|
|
3021
|
+
return h('div', { className: 'tg-tip', style: { left: x + 'px', top: y + 'px', width: tipWidth + 'px' } }, nodeTooltipContent(hover.node));
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
function renderGraphToolbar() {
|
|
3025
|
+
var scalePct = Math.round(scale * 100);
|
|
3026
|
+
var modes = [
|
|
3027
|
+
{ key: 'tree', label: t('graphModeTree') },
|
|
3028
|
+
{ key: 'timeline', label: t('graphModeTimeline') },
|
|
3029
|
+
];
|
|
3030
|
+
return h('div', { className: 'tg-graph-toolbar' },
|
|
3031
|
+
h('span', { className: 'tg-graph-hint' }, t('graphHint')),
|
|
3032
|
+
h('span', { className: 'tg-graph-modes' }, modes.map(function (mode) {
|
|
3033
|
+
return h('button', {
|
|
3034
|
+
key: mode.key,
|
|
3035
|
+
className: 'tg-mode-btn' + (graphMode === mode.key ? ' active' : ''),
|
|
3036
|
+
title: mode.label,
|
|
3037
|
+
onClick: function () { setGraphMode(mode.key); },
|
|
3038
|
+
}, mode.label);
|
|
3039
|
+
})),
|
|
3040
|
+
graphMode === 'tree'
|
|
3041
|
+
? h(React.Fragment, null,
|
|
3042
|
+
h('button', { className: 'tg-btn', title: t('zoomOut'), onClick: function () { setScale(clamp(scale * 0.85, 0.35, 2.5)); } }, '−'),
|
|
3043
|
+
h('span', { className: 'tg-zoom-pct' }, scalePct + '%'),
|
|
3044
|
+
h('button', { className: 'tg-btn', title: t('zoomIn'), onClick: function () { setScale(clamp(scale * 1.18, 0.35, 2.5)); } }, '+'),
|
|
3045
|
+
h('button', { className: 'tg-btn', title: t('zoomReset'), onClick: function () { setScale(1); } }, '⟲'))
|
|
3046
|
+
: null,
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
function scrollTimelineTo(topicId) {
|
|
3051
|
+
var el = tlScrollRef.current;
|
|
3052
|
+
if (!el) return;
|
|
3053
|
+
var pos = buildTimelineLayout(graph || {}, tlBoxW || 900).posById[topicId];
|
|
3054
|
+
if (!pos) return;
|
|
3055
|
+
try {
|
|
3056
|
+
el.scrollLeft = Math.max(0, pos.x - 80);
|
|
3057
|
+
el.scrollTop = Math.max(0, pos.y - 100);
|
|
3058
|
+
} catch (error) { /* ignore */ }
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
function renderTimelineTooltip() {
|
|
3062
|
+
if (!tlHover || (!tlHover.topicId && !tlHover.relId)) return null;
|
|
3063
|
+
var style = { left: Math.max(8, tlHover.clientX + 14) + 'px', top: Math.max(8, tlHover.clientY + 12) + 'px' };
|
|
3064
|
+
if (tlHover.topicId) {
|
|
3065
|
+
var topic = byId[tlHover.topicId];
|
|
3066
|
+
if (!topic) return null;
|
|
3067
|
+
var stats = topic.stats || {};
|
|
3068
|
+
var tokens = stats.tokens || {};
|
|
3069
|
+
var rels = (graph.relations || []).filter(function (r) { return r.from === tlHover.topicId || r.to === tlHover.topicId; });
|
|
3070
|
+
return h('div', { className: 'tg-tl-tip', style: style },
|
|
3071
|
+
h('div', { className: 'tg-tl-tip-title' }, topic.title),
|
|
3072
|
+
h('div', { className: 'tg-tl-tip-line' },
|
|
3073
|
+
(topic.turnStart != null ? '轮' + topic.turnStart + '-' + topic.turnEnd + ' · ' : '') + fmtK(tokens.total || 0) + ' tokens · ' + fmtDuration(stats.wallMs || 0)),
|
|
3074
|
+
rels.length ? h('div', { className: 'tg-tl-tip-line tg-tl-tip-rels' },
|
|
3075
|
+
rels.map(function (r) { return (r.from === topic.id ? '→ ' : '← ') + (r.label || ''); }).join(' ')) : null,
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
3078
|
+
var rel = (graph.relations || []).filter(function (r) {
|
|
3079
|
+
return ('tlrel:' + r.from + '>' + r.to) === tlHover.relId;
|
|
3080
|
+
})[0];
|
|
3081
|
+
if (!rel) return null;
|
|
3082
|
+
return h('div', { className: 'tg-tl-tip', style: style },
|
|
3083
|
+
h('div', { className: 'tg-tl-tip-title' }, rel.label || t('statRelations')),
|
|
3084
|
+
h('div', { className: 'tg-tl-tip-line' }, topicTitleOf(rel.from) + ' → ' + topicTitleOf(rel.to)),
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
function renderTimelineViewport() {
|
|
3089
|
+
var lay = buildTimelineLayout(graph || {}, tlBoxW || 900);
|
|
3090
|
+
var hoverTopic = tlHover && tlHover.topicId ? tlHover.topicId : null;
|
|
3091
|
+
var hoverRel = (tlHover && tlHover.relId ? tlHover.relId : null) || tlSelRel || null;
|
|
3092
|
+
var hoverRelObj = hoverRel ? lay.relations.filter(function (r) { return r.id === hoverRel; })[0] : null;
|
|
3093
|
+
function relActive(rel) {
|
|
3094
|
+
if (hoverRel) return rel.id === hoverRel;
|
|
3095
|
+
if (hoverTopic) return rel.from === hoverTopic || rel.to === hoverTopic;
|
|
3096
|
+
return false;
|
|
3097
|
+
}
|
|
3098
|
+
function barActive(topicId) {
|
|
3099
|
+
if (hoverTopic) return topicId === hoverTopic;
|
|
3100
|
+
if (hoverRelObj) return hoverRelObj.from === topicId || hoverRelObj.to === topicId;
|
|
3101
|
+
return selectedTopicId === topicId;
|
|
3102
|
+
}
|
|
3103
|
+
var outline = h('div', { className: 'tg-tl-outline' },
|
|
3104
|
+
lay.lanes.map(function (lane) {
|
|
3105
|
+
return h('div', { key: 'ol-' + lane.stage.id, className: 'tg-tl-stage' },
|
|
3106
|
+
h('div', { className: 'tg-tl-stage-head' },
|
|
3107
|
+
h('span', { className: 'tg-tl-stage-idx' }, String(lane.index + 1)),
|
|
3108
|
+
h('span', { className: 'tg-tl-stage-title', title: lane.stage.title }, lane.stage.title)),
|
|
3109
|
+
lane.topics.map(function (topic) {
|
|
3110
|
+
var idx = lay.indexById[topic.id];
|
|
3111
|
+
var active = selectedTopicId === topic.id || hoverTopic === topic.id;
|
|
3112
|
+
return h('div', {
|
|
3113
|
+
key: 'olt-' + topic.id,
|
|
3114
|
+
className: 'tg-tl-topic' + (active ? ' active' : ''),
|
|
3115
|
+
onClick: function () { setSelectedTopicId(topic.id); scrollTimelineTo(topic.id); },
|
|
3116
|
+
title: topic.title,
|
|
3117
|
+
},
|
|
3118
|
+
h('span', { className: 'tg-tl-topic-idx' }, 'T' + idx),
|
|
3119
|
+
h('span', { className: 'tg-tl-topic-title' }, topic.title));
|
|
3120
|
+
}));
|
|
3121
|
+
}));
|
|
3122
|
+
var svg = h('svg', {
|
|
3123
|
+
className: 'tg-tl-svg' + (hoverRel ? ' has-active' : ''), width: lay.width, height: lay.height,
|
|
3124
|
+
onMouseLeave: function () { setTlHover(null); },
|
|
3125
|
+
onClick: function () { setTlSelRel(null); },
|
|
3126
|
+
},
|
|
3127
|
+
lay.ticks.map(function (tick) {
|
|
3128
|
+
return h('g', { key: 'tk-' + tick.turn },
|
|
3129
|
+
h('line', { className: 'tg-tl-grid', x1: tick.x, y1: lay.rulerH, x2: tick.x, y2: lay.height }),
|
|
3130
|
+
h('text', { className: 'tg-tl-tick', x: tick.x, y: 16 }, String(tick.turn)));
|
|
3131
|
+
}),
|
|
3132
|
+
lay.lanes.map(function (lane) {
|
|
3133
|
+
return h('g', { key: 'lane-' + lane.stage.id },
|
|
3134
|
+
h('rect', { className: 'tg-tl-lane' + (lane.index % 2 ? ' alt' : ''), x: 0, y: lane.y, width: lay.width, height: lane.h }));
|
|
3135
|
+
}),
|
|
3136
|
+
lay.nexts.map(function (n) {
|
|
3137
|
+
return h('path', { key: n.id, className: 'tg-tl-next', d: n.path });
|
|
3138
|
+
}),
|
|
3139
|
+
h('defs', null,
|
|
3140
|
+
h('marker', { id: 'tg-tl-arrow', viewBox: '0 0 8 8', refX: 8, refY: 4, markerWidth: 7, markerHeight: 7, orient: 'auto' },
|
|
3141
|
+
h('path', { d: 'M0,0 L8,4 L0,8 Z', className: 'tg-tl-arrow' }))),
|
|
3142
|
+
lay.relations.map(function (rel) {
|
|
3143
|
+
var active = relActive(rel);
|
|
3144
|
+
return h('g', {
|
|
3145
|
+
key: rel.id,
|
|
3146
|
+
className: 'tg-tl-rel' + (active ? ' active' : '') + (rel.sameLane ? ' same-lane' : ''),
|
|
3147
|
+
onClick: function (event) { event.stopPropagation(); setTlSelRel(rel.id); },
|
|
3148
|
+
onMouseEnter: function (event) { setTlHover({ relId: rel.id, clientX: event.clientX, clientY: event.clientY }); },
|
|
3149
|
+
onMouseMove: function (event) { setTlHover({ relId: rel.id, clientX: event.clientX, clientY: event.clientY }); },
|
|
3150
|
+
onMouseLeave: function () { setTlHover(null); },
|
|
3151
|
+
},
|
|
3152
|
+
h('path', { className: 'tg-tl-rel-hit', d: rel.path }),
|
|
3153
|
+
h('path', { className: 'tg-tl-rel-path', d: rel.path }),
|
|
3154
|
+
h('text', { className: 'tg-tl-rel-label', x: rel.labelX, y: rel.labelY }, rel.label));
|
|
3155
|
+
}),
|
|
3156
|
+
lay.lanes.map(function (lane) {
|
|
3157
|
+
return lane.topics.map(function (topic) {
|
|
3158
|
+
var pos = lay.posById[topic.id];
|
|
3159
|
+
var active = barActive(topic.id);
|
|
3160
|
+
return h('g', {
|
|
3161
|
+
key: 'bar-' + topic.id,
|
|
3162
|
+
className: 'tg-tl-bar' + (active ? ' active' : ''),
|
|
3163
|
+
style: { '--tg-bar-color': TIMELINE_PALETTE[lane.index % TIMELINE_PALETTE.length] },
|
|
3164
|
+
onClick: function () { openDetail(topic.id); },
|
|
3165
|
+
onMouseEnter: function (event) { setTlHover({ topicId: topic.id, clientX: event.clientX, clientY: event.clientY }); },
|
|
3166
|
+
onMouseMove: function (event) { setTlHover({ topicId: topic.id, clientX: event.clientX, clientY: event.clientY }); },
|
|
3167
|
+
onMouseLeave: function () { setTlHover(null); },
|
|
3168
|
+
},
|
|
3169
|
+
h('rect', { className: 'tg-tl-bar-rect', x: pos.x, y: pos.y, width: pos.w, height: pos.h, rx: 2 }));
|
|
3170
|
+
});
|
|
3171
|
+
}),
|
|
3172
|
+
);
|
|
3173
|
+
return h(React.Fragment, null,
|
|
3174
|
+
renderGraphToolbar(),
|
|
3175
|
+
h('div', { className: 'tg-tl-split' },
|
|
3176
|
+
outline,
|
|
3177
|
+
h('div', { className: 'tg-tl-scroll', ref: tlScrollRef, onScroll: function () { setTlHover(null); } }, svg),
|
|
3178
|
+
),
|
|
3179
|
+
renderTimelineTooltip(),
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
function renderGraph() {
|
|
3184
|
+
if (!graph) return renderLoading();
|
|
3185
|
+
if (!graph.turns || !graph.turns.length) return h('div', { className: 'tg-empty' }, t('noTurns'));
|
|
3186
|
+
if (!(graph.stages || []).length) return renderNoTopics();
|
|
3187
|
+
if (graphMode === 'timeline') return renderTimelineViewport();
|
|
3188
|
+
var scalePct = Math.round(scale * 100);
|
|
3189
|
+
var edgeTypes = layout.edgeTypes || [];
|
|
3190
|
+
var legend = [
|
|
3191
|
+
{ key: 'next', label: t('graphLegendNext'), cls: 'n' },
|
|
3192
|
+
{ key: 'belongTo', label: t('graphLegendBelong'), cls: 'b' },
|
|
3193
|
+
{ key: 'has_relation', label: t('graphLegendRelation'), cls: 'r' },
|
|
3194
|
+
{ key: 'delegates', label: t('graphLegendDelegate'), cls: 'd' },
|
|
3195
|
+
].filter(function (item) { return edgeTypes.indexOf(item.key) !== -1; });
|
|
3196
|
+
return h(React.Fragment, null,
|
|
3197
|
+
renderGraphToolbar(),
|
|
3198
|
+
legend.length
|
|
3199
|
+
? h('div', { className: 'tg-legend' }, legend.map(function (item) {
|
|
3200
|
+
return h('span', { key: item.key, className: item.cls }, h('i'), item.label);
|
|
3201
|
+
}))
|
|
3202
|
+
: null,
|
|
3203
|
+
h('div', {
|
|
3204
|
+
className: 'tg-graph-viewport',
|
|
3205
|
+
ref: viewportRef,
|
|
3206
|
+
onMouseLeave: function () { setHover(null); },
|
|
3207
|
+
onClick: function () { setSelectedEdgeId(null); },
|
|
3208
|
+
},
|
|
3209
|
+
h('div', {
|
|
3210
|
+
className: 'tg-graph-canvas',
|
|
3211
|
+
style: { width: layout.width * scale + 'px', height: layout.height * scale + 'px' },
|
|
3212
|
+
},
|
|
3213
|
+
h('div', {
|
|
3214
|
+
className: 'tg-graph-content',
|
|
3215
|
+
style: { width: layout.width + 'px', height: layout.height + 'px', transform: 'scale(' + scale + ')' },
|
|
3216
|
+
},
|
|
3217
|
+
h('svg', { className: 'tg-edges', width: layout.width, height: layout.height },
|
|
3218
|
+
h('defs', null,
|
|
3219
|
+
h('marker', { id: 'tg-arrow-rel', viewBox: '0 0 8 8', refX: 8, refY: 4, markerWidth: 8, markerHeight: 8, orient: 'auto' },
|
|
3220
|
+
h('path', { d: 'M0,0 L8,4 L0,8 Z', className: 'tg-arrow tg-arrow-rel' })),
|
|
3221
|
+
h('marker', { id: 'tg-arrow-next', viewBox: '0 0 8 8', refX: 8, refY: 4, markerWidth: 8, markerHeight: 8, orient: 'auto' },
|
|
3222
|
+
h('path', { d: 'M0,0 L8,4 L0,8 Z', className: 'tg-arrow tg-arrow-next' })),
|
|
3223
|
+
h('marker', { id: 'tg-arrow-deleg', viewBox: '0 0 8 8', refX: 8, refY: 4, markerWidth: 8, markerHeight: 8, orient: 'auto' },
|
|
3224
|
+
h('path', { d: 'M0,0 L8,4 L0,8 Z', className: 'tg-arrow tg-arrow-deleg' })),
|
|
3225
|
+
h('marker', { id: 'tg-arrow-sel', viewBox: '0 0 8 8', refX: 8, refY: 4, markerWidth: 8, markerHeight: 8, orient: 'auto' },
|
|
3226
|
+
h('path', { d: 'M0,0 L8,4 L0,8 Z', className: 'tg-arrow tg-arrow-sel' })),
|
|
3227
|
+
),
|
|
3228
|
+
layout.edges.map(function (edge) {
|
|
3229
|
+
var selected = selectedEdgeId != null && selectedEdgeId === edge.id;
|
|
3230
|
+
var toggleSelect = function (event) {
|
|
3231
|
+
event.stopPropagation();
|
|
3232
|
+
setSelectedEdgeId(selected ? null : edge.id);
|
|
3233
|
+
};
|
|
3234
|
+
return h('g', { key: edge.id },
|
|
3235
|
+
edge.labelBg && edge.label
|
|
3236
|
+
? h('rect', {
|
|
3237
|
+
className: 'tg-edge-label-bg' + (selected ? ' selected' : ''),
|
|
3238
|
+
x: edge.labelBg.x, y: edge.labelBg.y, width: edge.labelBg.w, height: edge.labelBg.h, rx: 8,
|
|
3239
|
+
pointerEvents: 'auto',
|
|
3240
|
+
onClick: toggleSelect,
|
|
3241
|
+
})
|
|
3242
|
+
: null,
|
|
3243
|
+
h('path', {
|
|
3244
|
+
className: 'tg-edge-hit',
|
|
3245
|
+
d: edge.path,
|
|
3246
|
+
title: edge.label || '',
|
|
3247
|
+
onClick: toggleSelect,
|
|
3248
|
+
}),
|
|
3249
|
+
h('path', {
|
|
3250
|
+
className: 'tg-edge tg-edge-' + edge.type + (selected ? ' selected' : ''),
|
|
3251
|
+
d: edge.path,
|
|
3252
|
+
markerEnd: selected ? 'url(#tg-arrow-sel)' : (edge.marker ? 'url(#tg-arrow-' + edge.marker + ')' : undefined),
|
|
3253
|
+
}),
|
|
3254
|
+
edge.label ? h('text', { className: 'tg-edge-label' + (selected ? ' selected' : ''), x: edge.labelX, y: edge.labelY }, edge.label) : null,
|
|
3255
|
+
);
|
|
3256
|
+
}),
|
|
3257
|
+
),
|
|
3258
|
+
// 阶段分组框(belongTo 的容器可视化)
|
|
3259
|
+
layout.stageBoxes.map(function (box) {
|
|
3260
|
+
var stageIndex = (graph.stages || []).findIndex(function (item) { return item.id === box.id; });
|
|
3261
|
+
var branchCount = (graph.branches || []).filter(function (branch) {
|
|
3262
|
+
var topic = byId[branch.parentTopicId];
|
|
3263
|
+
return topic && topic.stageId === box.id;
|
|
3264
|
+
}).length;
|
|
3265
|
+
return h('div', {
|
|
3266
|
+
key: 'stage-box-' + box.id,
|
|
3267
|
+
className: 'tg-stage-box',
|
|
3268
|
+
style: { left: box.x + 'px', top: box.y + 'px', width: box.w + 'px', height: box.h + 'px' },
|
|
3269
|
+
},
|
|
3270
|
+
h('div', { className: 'tg-stage-box-head' },
|
|
3271
|
+
h('span', {
|
|
3272
|
+
className: 'tg-stage-toggle',
|
|
3273
|
+
title: box.collapsed ? t('expandStage') : t('collapseStage'),
|
|
3274
|
+
onClick: function (event) { event.stopPropagation(); toggleGraphCollapse('stages', box.id); },
|
|
3275
|
+
}, box.collapsed ? '▸' : '▾'),
|
|
3276
|
+
h('span', { className: 'tg-stage-box-idx' }, String(stageIndex + 1)),
|
|
3277
|
+
h('span', { className: 'tg-stage-box-title' }, box.stage.title),
|
|
3278
|
+
box.collapsed ? h('span', { className: 'tg-stage-box-chip' }, box.topicCount + ' ' + t('topics')) : null,
|
|
3279
|
+
branchCount ? h('span', { className: 'tg-stage-box-chip' }, '🤖' + branchCount) : null,
|
|
3280
|
+
h('span', { className: 'tg-stage-box-chip' }, t('tokens') + ' ' + fmtK(((box.stage.stats || {}).tokens || {}).total || 0)),
|
|
3281
|
+
),
|
|
3282
|
+
);
|
|
3283
|
+
}),
|
|
3284
|
+
// 子智能体分支:分支框 + 会话头 + 它自己的话题树(与主图同屏)
|
|
3285
|
+
(layout.branchBoxes || []).map(function (box) {
|
|
3286
|
+
var branch = box.branch;
|
|
3287
|
+
var headerText = '🤖 ' + t('subagent') + ' · ' + (branch.title || sessionShortId(branch.sessionId));
|
|
3288
|
+
return h('div', {
|
|
3289
|
+
key: 'branchbox-' + branch.sessionId,
|
|
3290
|
+
className: 'tg-branch-box',
|
|
3291
|
+
style: { left: box.x + 'px', top: box.y + 'px', width: box.w + 'px', height: box.h + 'px' },
|
|
3292
|
+
}, h('span', { className: 'tg-branch-box-label' }, headerText));
|
|
3293
|
+
}),
|
|
3294
|
+
layout.branchNodes.map(function (node) {
|
|
3295
|
+
var branch = node.branch;
|
|
3296
|
+
if (node.kind === 'branchHeader') {
|
|
3297
|
+
var totals = branch.totals || {};
|
|
3298
|
+
return h('div', {
|
|
3299
|
+
key: node.id,
|
|
3300
|
+
className: 'tg-branch-header' + (branch.pending ? ' pending' : ''),
|
|
3301
|
+
style: { left: node.x + 'px', top: node.y + 'px', width: node.w + 'px', height: node.h + 'px', zIndex: 6 },
|
|
3302
|
+
onMouseEnter: function (event) { setHover({ node: node, clientX: event.clientX, clientY: event.clientY }); },
|
|
3303
|
+
onMouseMove: function (event) { setHover({ node: node, clientX: event.clientX, clientY: event.clientY }); },
|
|
3304
|
+
onMouseLeave: function () { setHover(null); },
|
|
3305
|
+
onClick: function () { selectSession(branch.sessionId); },
|
|
3306
|
+
},
|
|
3307
|
+
node.topicCount ? h('span', {
|
|
3308
|
+
className: 'tg-branch-toggle',
|
|
3309
|
+
title: node.collapsed ? t('expandBranch') : t('collapseBranch'),
|
|
3310
|
+
onClick: function (event) { event.stopPropagation(); toggleGraphCollapse('branches', branch.sessionId); },
|
|
3311
|
+
}, node.collapsed ? '▸' : '▾') : null,
|
|
3312
|
+
h('span', { className: 'tg-branch-ico' }, '🤖'),
|
|
3313
|
+
h('span', { className: 'tg-branch-title-text' }, branch.title || sessionShortId(branch.sessionId)),
|
|
3314
|
+
node.collapsed ? h('span', { className: 'tg-branch-header-meta' }, node.topicCount + ' ' + t('topics')) : null,
|
|
3315
|
+
h('span', { className: 'tg-branch-header-meta' },
|
|
3316
|
+
(totals.turnCount || 0) + ' ' + t('turns') + ' · ' + fmtK((totals.tokens || {}).total || 0)),
|
|
3317
|
+
node.depth > 1 ? h('span', { className: 'tg-branch-depth' }, 'L' + node.depth) : null,
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
var topic = node.topic;
|
|
3321
|
+
var stats = topic.stats || {};
|
|
3322
|
+
var tokens = stats.tokens || {};
|
|
3323
|
+
return h('div', {
|
|
3324
|
+
key: node.id,
|
|
3325
|
+
className: 'tg-branch-topic',
|
|
3326
|
+
style: { left: node.x + 'px', top: node.y + 'px', width: node.w + 'px', height: node.h + 'px', zIndex: 6 },
|
|
3327
|
+
onMouseEnter: function (event) { setHover({ node: node, clientX: event.clientX, clientY: event.clientY }); },
|
|
3328
|
+
onMouseMove: function (event) { setHover({ node: node, clientX: event.clientX, clientY: event.clientY }); },
|
|
3329
|
+
onMouseLeave: function () { setHover(null); },
|
|
3330
|
+
onClick: function () { openChildDetail(branch.sessionId, topic.id); },
|
|
3331
|
+
},
|
|
3332
|
+
h('div', { className: 'tg-branch-topic-title' }, topic.title),
|
|
3333
|
+
h('div', { className: 'tg-branch-topic-meta' },
|
|
3334
|
+
h('span', null, topic.turnStart != null ? '轮' + topic.turnStart + '-' + topic.turnEnd : ''),
|
|
3335
|
+
h('span', null, fmtK(tokens.total || 0)),
|
|
3336
|
+
h('span', null, fmtPct(stats.tokenRatio)),
|
|
3337
|
+
h('span', null, fmtDuration(stats.wallMs || 0)),
|
|
3338
|
+
),
|
|
3339
|
+
h('div', { className: 'tg-node-bar' }, h('i', { style: { width: Math.max(1, (stats.tokenRatio || 0) * 100) + '%' } })),
|
|
3340
|
+
);
|
|
3341
|
+
}),
|
|
3342
|
+
// 主列:阶段 → 话题 → 子话题
|
|
3343
|
+
layout.nodes.map(function (node) {
|
|
3344
|
+
var topic = node.topic;
|
|
3345
|
+
var stats = topic.stats || {};
|
|
3346
|
+
var tokens = stats.tokens || {};
|
|
3347
|
+
var isActive = topic.status === 'active';
|
|
3348
|
+
var branchCount = (topic.branchIds || []).length;
|
|
3349
|
+
return h('div', {
|
|
3350
|
+
key: 'node-' + node.id,
|
|
3351
|
+
className: 'tg-node' + (isActive ? ' active' : ''),
|
|
3352
|
+
style: {
|
|
3353
|
+
left: node.x + 'px',
|
|
3354
|
+
top: node.y + 'px',
|
|
3355
|
+
width: node.w + 'px',
|
|
3356
|
+
height: node.h + 'px',
|
|
3357
|
+
zIndex: node.depth + 2,
|
|
3358
|
+
},
|
|
3359
|
+
onMouseEnter: function (event) { setHover({ node: node, clientX: event.clientX, clientY: event.clientY }); },
|
|
3360
|
+
onMouseMove: function (event) { setHover({ node: node, clientX: event.clientX, clientY: event.clientY }); },
|
|
3361
|
+
onMouseLeave: function () { setHover(null); },
|
|
3362
|
+
onClick: function () { openDetail(topic.id); },
|
|
3363
|
+
},
|
|
3364
|
+
h('div', { className: 'tg-node-title' },
|
|
3365
|
+
node.childCount ? h('span', {
|
|
3366
|
+
className: 'tg-node-toggle',
|
|
3367
|
+
title: node.collapsed ? t('expandTopic') : t('collapseTopic'),
|
|
3368
|
+
onClick: function (event) { event.stopPropagation(); toggleGraphCollapse('topics', topic.id); },
|
|
3369
|
+
}, node.collapsed ? '▸' : '▾') : null,
|
|
3370
|
+
h('span', { className: 'tg-node-title-text' }, topic.title),
|
|
3371
|
+
),
|
|
3372
|
+
h('div', { className: 'tg-node-meta' },
|
|
3373
|
+
h('span', null, (topic.turnStart != null ? '轮' + topic.turnStart + '-' + topic.turnEnd : '')),
|
|
3374
|
+
node.collapsed && node.childCount ? h('span', { className: 'tg-node-depth-tag' }, node.childCount + ' ' + t('subtopics')) : null,
|
|
3375
|
+
node.depth > 0 ? h('span', { className: 'tg-node-depth-tag' }, 'L' + (node.depth + 1)) : null,
|
|
3376
|
+
branchCount ? h('span', { className: 'tg-node-depth-tag', style: { color: 'var(--dsw-alias-state-warn-primary,#ca8a04)' } }, '🤖' + branchCount) : null,
|
|
3377
|
+
h('span', null, fmtK(tokens.total)),
|
|
3378
|
+
h('span', null, fmtPct(stats.tokenRatio)),
|
|
3379
|
+
h('span', null, t('toolCalls') + ' ' + String((stats.toolCalls && stats.toolCalls.total) || 0)),
|
|
3380
|
+
h('span', null, fmtDuration(stats.wallMs)),
|
|
3381
|
+
),
|
|
3382
|
+
h('div', { className: 'tg-node-bar' }, h('i', { style: { width: Math.max(1, (stats.tokenRatio || 0) * 100) + '%' } })),
|
|
3383
|
+
);
|
|
3384
|
+
}),
|
|
3385
|
+
),
|
|
3386
|
+
),
|
|
3387
|
+
),
|
|
3388
|
+
renderGraphTooltip(),
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
// ── 渲染:会话选择器 ────────────────────────────────────────────────
|
|
3393
|
+
// ── 渲染:整体 ─────────────────────────────────────────────────────
|
|
3394
|
+
var styleElement = h('style', { key: 'tg-style' }, CSS);
|
|
3395
|
+
|
|
3396
|
+
var totals = (graph && graph.totals) || null;
|
|
3397
|
+
var ai = (graph && graph.ai) || {};
|
|
3398
|
+
var statusClass = ai.status === 'running' ? 'running' : (ai.error ? 'error' : 'ok');
|
|
3399
|
+
var subagentBranchCount = totals && totals.subagent ? (totals.subagent.branchCount || 0) : 0;
|
|
3400
|
+
var subagentPending = (graph && graph.branches ? graph.branches : []).filter(function (branch) {
|
|
3401
|
+
return branch.pending || (branch.ai && (branch.ai.status === 'running' || branch.ai.pendingTurns > 0));
|
|
3402
|
+
}).length;
|
|
3403
|
+
var statusText = ai.status === 'running'
|
|
3404
|
+
? t('segmenting')
|
|
3405
|
+
: (ai.error ? (t('aiFailed') + ':' + ai.error)
|
|
3406
|
+
: (ai.pendingTurns > 0 ? t('pendingTurns', { n: ai.pendingTurns })
|
|
3407
|
+
: (subagentPending > 0 ? t('segmenting') + ' · 🤖' + subagentPending
|
|
3408
|
+
: t('updatedAt', { time: fmtClock(Date.parse(graph && graph.updatedAt) || 0) })
|
|
3409
|
+
+ (ai.relationError ? ' · ' + t('statRelations') + ':' + summarizeText(ai.relationError, 40) : ''))));
|
|
3410
|
+
|
|
3411
|
+
var modelSelect = h('select', {
|
|
3412
|
+
className: 'tg-model-select',
|
|
3413
|
+
value: modelSel,
|
|
3414
|
+
title: t('modelLabel'),
|
|
3415
|
+
onChange: function (event) { onModelChange(event.target.value); },
|
|
3416
|
+
},
|
|
3417
|
+
h('option', { value: '' }, t('modelDefault')),
|
|
3418
|
+
modelProviders.map(function (p) {
|
|
3419
|
+
return h('optgroup', { key: p.id, label: p.name || p.id },
|
|
3420
|
+
(p.models || []).map(function (m) {
|
|
3421
|
+
return h('option', { key: p.id + ':' + m.id, value: p.id + ':' + m.id }, m.name || m.id);
|
|
3422
|
+
}));
|
|
3423
|
+
}),
|
|
3424
|
+
);
|
|
3425
|
+
|
|
3426
|
+
return h(React.Fragment, null, styleElement,
|
|
3427
|
+
h('div', { className: 'tg-root tg-root-docked', ref: panelRef },
|
|
3428
|
+
h('div', { className: 'tg-topbar' },
|
|
3429
|
+
h('div', { className: 'tg-topbar-row' },
|
|
3430
|
+
h('span', { className: 'tg-view-toggle' },
|
|
3431
|
+
h('button', {
|
|
3432
|
+
className: 'tg-view-btn' + (ui.tab === 'chain' ? ' active' : ''),
|
|
3433
|
+
onClick: function () { setUi(function (prev) { return Object.assign({}, prev, { tab: 'chain' }); }); },
|
|
3434
|
+
title: t('tabChain'),
|
|
3435
|
+
}, t('viewWorkflow')),
|
|
3436
|
+
h('button', {
|
|
3437
|
+
className: 'tg-view-btn' + (ui.tab === 'graph' ? ' active' : ''),
|
|
3438
|
+
onClick: function () { setUi(function (prev) { return Object.assign({}, prev, { tab: 'graph' }); }); },
|
|
3439
|
+
title: t('tabGraph'),
|
|
3440
|
+
}, t('viewGraph')),
|
|
3441
|
+
),
|
|
3442
|
+
h('span', { className: 'tg-topbar-spacer' }),
|
|
3443
|
+
h('span', { className: 'tg-dot ' + statusClass }),
|
|
3444
|
+
modelSelect,
|
|
3445
|
+
h('button', { className: 'tg-btn', title: t('refresh'), onClick: function () { versionRef.current = null; setReloadKey(function (n) { return n + 1; }); } }, '↻'),
|
|
3446
|
+
h('button', { className: 'tg-btn', disabled: actionBusy, onClick: function () { void triggerSegment(true); } }, t('resegment')),
|
|
3447
|
+
h('button', { className: 'tg-btn primary', disabled: actionBusy, onClick: function () { void triggerSegment(false); } }, t('segNow')),
|
|
3448
|
+
),
|
|
3449
|
+
renderStatsStrip(),
|
|
3450
|
+
),
|
|
3451
|
+
error ? h('div', { className: 'tg-banner' },
|
|
3452
|
+
h('span', { className: 'tg-banner-text', title: error }, error),
|
|
3453
|
+
h('button', { onClick: function () { versionRef.current = null; setError(''); setReloadKey(function (n) { return n + 1; }); } }, t('retry')),
|
|
3454
|
+
) : null,
|
|
3455
|
+
ai.error && !error ? h('div', { className: 'tg-banner' },
|
|
3456
|
+
h('span', { className: 'tg-banner-text', title: ai.error }, t('aiFailed') + ':' + ai.error),
|
|
3457
|
+
h('button', { onClick: function () { void triggerSegment(false); } }, t('retry')),
|
|
3458
|
+
) : null,
|
|
3459
|
+
h('div', { className: 'tg-body' },
|
|
3460
|
+
ui.tab === 'chain'
|
|
3461
|
+
? h('div', { className: 'tg-workflow' },
|
|
3462
|
+
h('div', { className: 'tg-workflow-tree' }, renderWorkflowTree()),
|
|
3463
|
+
h('div', { className: 'tg-workflow-detail', ref: wfDetailRef }, renderWorkflowDetail()),
|
|
3464
|
+
)
|
|
3465
|
+
: h('div', { className: 'tg-graph' }, renderGraph(), renderDetail(false)),
|
|
3466
|
+
),
|
|
3467
|
+
h('div', { className: 'tg-foot' },
|
|
3468
|
+
ai.status === 'running'
|
|
3469
|
+
? h('div', { className: 'tg-progress', title: Math.round((ai.progress || 0) * 100) + '%' },
|
|
3470
|
+
h('i', { style: { width: Math.max(2, Math.round((ai.progress || 0) * 100)) + '%' } }))
|
|
3471
|
+
: null,
|
|
3472
|
+
h('span', { className: 'tg-foot-status' + (ai.status === 'running' ? ' running' : (ai.error ? ' error' : '')) }, statusText),
|
|
3473
|
+
h('span', { className: 'tg-version', title: 'client bundle v' + CLIENT_VERSION }, 'v' + CLIENT_VERSION),
|
|
3474
|
+
),
|
|
3475
|
+
),
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
// ── 导出 ────────────────────────────────────────────────────────────────
|
|
3480
|
+
|
|
3481
|
+
exports.name = 'dsh-topic-graph';
|
|
3482
|
+
exports.inject = ['slots'];
|
|
3483
|
+
// 仅供本地测试的纯函数钩子(Loader 只读取 name/inject/apply)
|
|
3484
|
+
exports.__test = { buildLayout, buildTimelineLayout, loadUi, defaultPanelWidth, readSidebarCurrentSession };
|
|
3485
|
+
exports.apply = function (ctx) {
|
|
3486
|
+
appCtx = ctx;
|
|
3487
|
+
initLocale(ctx);
|
|
3488
|
+
// 停靠在「对话 / 轨迹」之后的新视图(与 dsh-context 同款 conversation.view 注册)
|
|
3489
|
+
return ctx.slots.inject('conversation.view', function () {
|
|
3490
|
+
return ctx.slots.register({
|
|
3491
|
+
name: 'conversation.view',
|
|
3492
|
+
id: 'topic-graph',
|
|
3493
|
+
order: 20,
|
|
3494
|
+
locale: LOC_NS,
|
|
3495
|
+
label: function () { return t('panelTitle'); },
|
|
3496
|
+
}, TopicGraphPanel);
|
|
3497
|
+
});
|
|
3498
|
+
};
|
|
3499
|
+
|
|
3500
|
+
return module.exports;
|
|
3501
|
+
},
|
|
3502
|
+
});
|