dsh-context 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +9 -17
  2. package/lib/client.js +532 -498
  3. package/lib/index.js +239 -279
  4. package/package.json +10 -3
package/lib/client.js CHANGED
@@ -1,562 +1,596 @@
1
- /* dsh-context client bundle — generated by scripts/build.mjs from src/client.js */
1
+ /* dsh-context client bundle — generated by scripts/build.mjs from src/client.ts */
2
2
  window.__ModuleLoader__.load({
3
3
  id: "dsh-context",
4
4
  factory: (require) => {
5
5
  var module = { exports: {} };
6
6
  var exports = module.exports;
7
- /**
8
- * dsh-context — Client half (installed package bundle).
9
- *
10
- * Registers a "上下文/Context" tab in the conversation view ring
11
- * (`conversation.view` slot, beside Chat/Trajectory) and renders the
12
- * context-composition timeline served by the Host half over the generic
13
- * Connection RPC channel `/dsh-context`: current makeup, per-request
14
- * stacked-bar history, context events, and the live message list.
15
- *
16
- * This file is the body of the package's `./client` bundle: build.mjs wraps
17
- * it into the web boot handoff (`window.__ModuleLoader__.load({id, factory})`),
18
- * so it runs inside the browser module table. React arrives via the injected
19
- * `require` (a platform seed word), UI text is bilingual (zh/en) through the
20
- * client `locale` service, and the Host sends structured event/node records
21
- * which this half localizes.
22
- */
23
-
24
- var React = require('react')
25
- var h = React.createElement
26
-
27
- var DICT_ZH = {
28
- 'tab': '上下文',
29
- 'cat.system': '系统提示', 'cat.tools': '工具定义', 'cat.user': '用户消息',
30
- 'cat.inject': '注入上下文', 'cat.assistant': '助手回复', 'cat.tool': '工具结果',
31
- 'overview.title': '当前构成',
32
- 'overview.ofWindow': 'tokens(约 {p}%)',
33
- 'overview.estimate': 'tokens(估算)',
34
- 'tools.top': '工具定义 Top:',
35
- 'tools.more': '等 {n} 个',
36
- 'trend.title': '历史趋势',
37
- 'trend.hint': '每次模型请求一段;点击柱子查看详情,✂ 表示压缩/剪枝',
38
- 'trend.empty': '发起一轮对话后,这里会展示每次模型请求的上下文构成',
39
- 'detail.step': 'T{t} · {s} 步',
40
- 'detail.estTotal': '估算合计 {n}',
41
- 'detail.actual': '实际 prompt {n}',
42
- 'detail.output': '输出 {n}',
43
- 'events.title': '上下文事件',
44
- 'events.empty': '暂无上下文事件(压缩、注入、模型切换会出现在这里)',
45
- 'nodes.title': '消息构成',
46
- 'nodes.hint': '当前模型可见的消息,最新在前',
47
- 'nodes.more': '… 更早的 {n} 条消息已省略',
48
- 'nodes.empty': '当前没有模型可见的消息',
49
- 'loading': '正在读取会话日志…',
50
- 'error': '上下文数据读取失败:',
51
- 'footer': '估算口径:与 dsh 内置 tokenMeter 相同的固定密度启发式(约 4 字符 ≈ 1 token);「实际」为供应商上报用量。',
52
- 'tip.step': 'T{t} · 第{s}步',
53
- 'tip.total': '合计 ≈ {n}',
54
- 'tip.actual': '(实际 {n})',
55
- 'ev.compaction': '压缩上下文(摘要替换 {n} 条消息)',
56
- 'ev.prune': '剪枝工具输出',
57
- 'ev.skill': 'Skill 注入({name})',
58
- 'ev.model': '模型切换:{a} → {b}',
59
- 'form.instructions': '指令注入', 'form.catalog': '目录更新', 'form.snapshot': '状态快照',
60
- 'form.notice': '通知', 'form.relay': '代理转发', 'form.recall': '历史召回', 'form.context': '上下文注入',
61
- 'node.toolResult': '工具结果',
62
- 'node.calls': '调用 ',
63
- 'node.empty': '(空回复)',
64
- 'node.nonText': '(非文本消息)',
65
- 'node.snapshot': '快照: ',
66
- }
67
-
68
- var DICT_EN = {
69
- 'tab': 'Context',
70
- 'cat.system': 'System', 'cat.tools': 'Tool schemas', 'cat.user': 'User',
71
- 'cat.inject': 'Injected', 'cat.assistant': 'Assistant', 'cat.tool': 'Tool results',
72
- 'overview.title': 'Current composition',
73
- 'overview.ofWindow': 'tokens (~{p}%)',
74
- 'overview.estimate': 'tokens (estimated)',
75
- 'tools.top': 'Top tool schemas:',
76
- 'tools.more': 'of {n}',
77
- 'trend.title': 'History',
78
- 'trend.hint': 'one bar per model request; click a bar for details, ✂ marks compaction/prune',
79
- 'trend.empty': 'Send a message and each model request’s context makeup shows up here',
80
- 'detail.step': 'T{t} · step {s}',
81
- 'detail.estTotal': 'estimated ≈ {n}',
82
- 'detail.actual': 'actual prompt {n}',
83
- 'detail.output': 'output {n}',
84
- 'events.title': 'Context events',
85
- 'events.empty': 'No context events yet (compaction, injections, model switches appear here)',
86
- 'nodes.title': 'Messages',
87
- 'nodes.hint': 'currently model-visible, newest first',
88
- 'nodes.more': '… {n} earlier messages omitted',
89
- 'nodes.empty': 'No model-visible messages right now',
90
- 'loading': 'Reading the session log…',
91
- 'error': 'Failed to read context data: ',
92
- 'footer': 'Estimate: same fixed-density heuristic as dsh’s built-in tokenMeter (~4 chars ≈ 1 token); “actual” is provider-reported usage.',
93
- 'tip.step': 'T{t} · step {s}',
94
- 'tip.total': 'total {n}',
95
- 'tip.actual': ' (actual {n})',
96
- 'ev.compaction': 'Context compacted (summary replaced {n} messages)',
97
- 'ev.prune': 'Tool output pruned',
98
- 'ev.skill': 'Skill injected ({name})',
99
- 'ev.model': 'Model switched: {a} → {b}',
100
- 'form.instructions': 'Instructions', 'form.catalog': 'Catalog update', 'form.snapshot': 'State snapshot',
101
- 'form.notice': 'Notice', 'form.relay': 'Agent relay', 'form.recall': 'Recall', 'form.context': 'Context injection',
102
- 'node.toolResult': 'Tool result',
103
- 'node.calls': 'calls ',
104
- 'node.empty': '(empty reply)',
105
- 'node.nonText': '(non-text message)',
106
- 'node.snapshot': 'snapshot: ',
107
- }
108
-
109
- var EVENT_ICONS = { compaction: '✂', prune: '✂', inject: '+', model: '⇄' }
110
-
7
+ "use strict";
8
+ const React = require("react");
9
+ const h = React.createElement;
10
+ const DICT_ZH = {
11
+ "tab": "\u4E0A\u4E0B\u6587",
12
+ "cat.system": "\u7CFB\u7EDF\u63D0\u793A",
13
+ "cat.tools": "\u5DE5\u5177\u5B9A\u4E49",
14
+ "cat.user": "\u7528\u6237\u6D88\u606F",
15
+ "cat.inject": "\u6CE8\u5165\u4E0A\u4E0B\u6587",
16
+ "cat.assistant": "\u52A9\u624B\u56DE\u590D",
17
+ "cat.tool": "\u5DE5\u5177\u7ED3\u679C",
18
+ "overview.title": "\u5F53\u524D\u6784\u6210",
19
+ "overview.ofWindow": "tokens\uFF08\u7EA6 {p}%\uFF09",
20
+ "overview.estimate": "tokens\uFF08\u4F30\u7B97\uFF09",
21
+ "tools.top": "\u5DE5\u5177\u5B9A\u4E49 Top\uFF1A",
22
+ "tools.more": "\u7B49 {n} \u4E2A",
23
+ "trend.title": "\u5386\u53F2\u8D8B\u52BF",
24
+ "trend.hint": "\u6BCF\u6B21\u6A21\u578B\u8BF7\u6C42\u4E00\u6BB5\uFF1B\u70B9\u51FB\u67F1\u5B50\u67E5\u770B\u8BE6\u60C5\uFF0C\u2702 \u8868\u793A\u538B\u7F29/\u526A\u679D",
25
+ "trend.empty": "\u53D1\u8D77\u4E00\u8F6E\u5BF9\u8BDD\u540E\uFF0C\u8FD9\u91CC\u4F1A\u5C55\u793A\u6BCF\u6B21\u6A21\u578B\u8BF7\u6C42\u7684\u4E0A\u4E0B\u6587\u6784\u6210",
26
+ "detail.step": "T{t} \xB7 \u7B2C {s} \u6B65",
27
+ "detail.estTotal": "\u4F30\u7B97\u5408\u8BA1 \u2248 {n}",
28
+ "detail.actual": "\u5B9E\u9645 prompt {n}",
29
+ "detail.output": "\u8F93\u51FA {n}",
30
+ "events.title": "\u4E0A\u4E0B\u6587\u4E8B\u4EF6",
31
+ "events.empty": "\u6682\u65E0\u4E0A\u4E0B\u6587\u4E8B\u4EF6\uFF08\u538B\u7F29\u3001\u6CE8\u5165\u3001\u6A21\u578B\u5207\u6362\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\uFF09",
32
+ "nodes.title": "\u6D88\u606F\u6784\u6210",
33
+ "nodes.hint": "\u5F53\u524D\u6A21\u578B\u53EF\u89C1\u7684\u6D88\u606F\uFF0C\u6700\u65B0\u5728\u524D",
34
+ "nodes.more": "\u2026 \u66F4\u65E9\u7684 {n} \u6761\u6D88\u606F\u5DF2\u7701\u7565",
35
+ "nodes.empty": "\u5F53\u524D\u6CA1\u6709\u6A21\u578B\u53EF\u89C1\u7684\u6D88\u606F",
36
+ "loading": "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7\u2026",
37
+ "error": "\u4E0A\u4E0B\u6587\u6570\u636E\u8BFB\u53D6\u5931\u8D25\uFF1A",
38
+ "footer": "\u4F30\u7B97\u53E3\u5F84\uFF1A\u4E0E dsh \u5185\u7F6E tokenMeter \u76F8\u540C\u7684\u56FA\u5B9A\u5BC6\u5EA6\u542F\u53D1\u5F0F\uFF08\u7EA6 4 \u5B57\u7B26 \u2248 1 token\uFF09\uFF1B\u300C\u5B9E\u9645\u300D\u4E3A\u4F9B\u5E94\u5546\u4E0A\u62A5\u7528\u91CF\u3002",
39
+ "tip.step": "T{t} \xB7 \u7B2C{s}\u6B65",
40
+ "tip.total": "\u5408\u8BA1 \u2248 {n}",
41
+ "tip.actual": "\uFF08\u5B9E\u9645 {n}\uFF09",
42
+ "ev.compaction": "\u538B\u7F29\u4E0A\u4E0B\u6587\uFF08\u6458\u8981\u66FF\u6362 {n} \u6761\u6D88\u606F\uFF09",
43
+ "ev.prune": "\u526A\u679D\u5DE5\u5177\u8F93\u51FA",
44
+ "ev.skill": "Skill \u6CE8\u5165\uFF08{name}\uFF09",
45
+ "ev.model": "\u6A21\u578B\u5207\u6362\uFF1A{a} \u2192 {b}",
46
+ "form.instructions": "\u6307\u4EE4\u6CE8\u5165",
47
+ "form.catalog": "\u76EE\u5F55\u66F4\u65B0",
48
+ "form.snapshot": "\u72B6\u6001\u5FEB\u7167",
49
+ "form.notice": "\u901A\u77E5",
50
+ "form.relay": "\u4EE3\u7406\u8F6C\u53D1",
51
+ "form.recall": "\u5386\u53F2\u53EC\u56DE",
52
+ "form.context": "\u4E0A\u4E0B\u6587\u6CE8\u5165",
53
+ "node.toolResult": "\u5DE5\u5177\u7ED3\u679C",
54
+ "node.calls": "\u8C03\u7528 ",
55
+ "node.empty": "(\u7A7A\u56DE\u590D)",
56
+ "node.nonText": "(\u975E\u6587\u672C\u6D88\u606F)",
57
+ "node.snapshot": "\u5FEB\u7167: "
58
+ };
59
+ const DICT_EN = {
60
+ "tab": "Context",
61
+ "cat.system": "System",
62
+ "cat.tools": "Tool schemas",
63
+ "cat.user": "User",
64
+ "cat.inject": "Injected",
65
+ "cat.assistant": "Assistant",
66
+ "cat.tool": "Tool results",
67
+ "overview.title": "Current composition",
68
+ "overview.ofWindow": "tokens (~{p}%)",
69
+ "overview.estimate": "tokens (estimated)",
70
+ "tools.top": "Top tool schemas:",
71
+ "tools.more": "of {n}",
72
+ "trend.title": "History",
73
+ "trend.hint": "one bar per model request; click a bar for details, \u2702 marks compaction/prune",
74
+ "trend.empty": "Send a message and each model request\u2019s context makeup shows up here",
75
+ "detail.step": "T{t} \xB7 step {s}",
76
+ "detail.estTotal": "estimated \u2248 {n}",
77
+ "detail.actual": "actual prompt {n}",
78
+ "detail.output": "output {n}",
79
+ "events.title": "Context events",
80
+ "events.empty": "No context events yet (compaction, injections, model switches appear here)",
81
+ "nodes.title": "Messages",
82
+ "nodes.hint": "currently model-visible, newest first",
83
+ "nodes.more": "\u2026 {n} earlier messages omitted",
84
+ "nodes.empty": "No model-visible messages right now",
85
+ "loading": "Reading the session log\u2026",
86
+ "error": "Failed to read context data: ",
87
+ "footer": "Estimate: same fixed-density heuristic as dsh\u2019s built-in tokenMeter (~4 chars \u2248 1 token); \u201Cactual\u201D is provider-reported usage.",
88
+ "tip.step": "T{t} \xB7 step {s}",
89
+ "tip.total": "total \u2248 {n}",
90
+ "tip.actual": " (actual {n})",
91
+ "ev.compaction": "Context compacted (summary replaced {n} messages)",
92
+ "ev.prune": "Tool output pruned",
93
+ "ev.skill": "Skill injected ({name})",
94
+ "ev.model": "Model switched: {a} \u2192 {b}",
95
+ "form.instructions": "Instructions",
96
+ "form.catalog": "Catalog update",
97
+ "form.snapshot": "State snapshot",
98
+ "form.notice": "Notice",
99
+ "form.relay": "Agent relay",
100
+ "form.recall": "Recall",
101
+ "form.context": "Context injection",
102
+ "node.toolResult": "Tool result",
103
+ "node.calls": "calls ",
104
+ "node.empty": "(empty reply)",
105
+ "node.nonText": "(non-text message)",
106
+ "node.snapshot": "snapshot: "
107
+ };
108
+ const EVENT_ICONS = { compaction: "\u2702", prune: "\u2702", inject: "\uFF0B", model: "\u21C4" };
111
109
  function fmt(n) {
112
- if (n === undefined || n === null || isNaN(n)) return '—'
113
- if (n >= 1000) return (n / 1000).toFixed(1) + 'k'
114
- return String(Math.round(n))
110
+ if (n === void 0 || n === null || isNaN(n)) return "\u2014";
111
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
112
+ return String(Math.round(n));
115
113
  }
116
-
117
114
  function fmtTime(t) {
118
- var d = new Date(t)
119
- function p(x) { return (x < 10 ? '0' : '') + x }
120
- return p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds())
115
+ const d = new Date(t);
116
+ function p(x) {
117
+ return (x < 10 ? "0" : "") + x;
118
+ }
119
+ return p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds());
121
120
  }
122
-
123
121
  function makeView(ctx, t) {
124
122
  function tr(key, vars) {
125
- return t(key, vars)
123
+ return t(key, vars);
124
+ }
125
+ const CATS = [
126
+ { key: "system", color: "#6366f1" },
127
+ { key: "tools", color: "#f59e0b" },
128
+ { key: "user", color: "#22c55e" },
129
+ { key: "inject", color: "#a855f7" },
130
+ { key: "assistant", color: "#3b82f6" },
131
+ { key: "tool", color: "#14b8a6" }
132
+ ];
133
+ function catLabel(key) {
134
+ return t("cat." + key);
126
135
  }
127
-
128
- var CATS = [
129
- { key: 'system', color: '#6366f1' },
130
- { key: 'tools', color: '#f59e0b' },
131
- { key: 'user', color: '#22c55e' },
132
- { key: 'inject', color: '#a855f7' },
133
- { key: 'assistant', color: '#3b82f6' },
134
- { key: 'tool', color: '#14b8a6' },
135
- ]
136
-
137
- function catLabel(key) { return t('cat.' + key) }
138
-
139
136
  function eventLabel(ev) {
140
- if (ev.kind === 'compaction') return tr('ev.compaction', { n: ev.count || 0 })
141
- if (ev.kind === 'prune') return t('ev.prune')
142
- if (ev.kind === 'model') return tr('ev.model', { a: ev.from || '?', b: ev.to || '?' })
143
- if (ev.kind === 'inject') {
144
- if (ev.sub === 'skill') return tr('ev.skill', { name: ev.name || '?' })
145
- var base = t('form.' + (ev.form || 'context'))
146
- return ev.name ? base + ' · ' + ev.name : base
137
+ if (ev.kind === "compaction") return tr("ev.compaction", { n: ev.count || 0 });
138
+ if (ev.kind === "prune") return t("ev.prune");
139
+ if (ev.kind === "model") return tr("ev.model", { a: ev.from || "?", b: ev.to || "?" });
140
+ if (ev.kind === "inject") {
141
+ if (ev.sub === "skill") return tr("ev.skill", { name: ev.name || "?" });
142
+ const base = t("form." + (ev.form || "context"));
143
+ return ev.name ? base + " \xB7 " + ev.name : base;
147
144
  }
148
- return ev.kind
145
+ return ev.kind;
149
146
  }
150
-
151
147
  function nodeText(n) {
152
- if (n.cat === 'tool') {
153
- return t('node.toolResult') + (n.tool ? ' ' + n.tool : '') + (n.err ? ' ⚠' : '')
148
+ if (n.cat === "tool") {
149
+ return t("node.toolResult") + (n.tool ? " \u2190 " + n.tool : "") + (n.err ? " \u26A0" : "");
154
150
  }
155
- if (n.skill) return 'Skill: ' + n.skill
156
- if (n.calls) return t('node.calls') + n.calls.join(', ')
157
- if (n.text) return n.form === 'snapshot' ? t('node.snapshot') + n.text : n.text
158
- if (n.cat === 'assistant') return t('node.empty')
159
- if (n.cat === 'inject') return t('form.' + (n.form || 'context'))
160
- return t('node.nonText')
151
+ if (n.skill) return "Skill: " + n.skill;
152
+ if (n.calls) return t("node.calls") + n.calls.join(", ");
153
+ if (n.text) return n.form === "snapshot" ? t("node.snapshot") + n.text : n.text;
154
+ if (n.cat === "assistant") return t("node.empty");
155
+ if (n.cat === "inject") return t("form." + (n.form || "context"));
156
+ return t("node.nonText");
161
157
  }
162
-
163
158
  function StackedBar(props) {
164
- // props.parts: [{key,color,value}]; optional props.max: when max exceeds
165
- // the parts' total, the remainder shows as empty track.
166
- var total = 0
167
- for (var i = 0; i < props.parts.length; i++) total += props.parts[i].value
168
- var scale = props.max !== undefined && props.max > total ? props.max : total
169
- return h('div', { className: 'lc-stacked', style: { height: (props.height || 14) + 'px' } },
170
- total <= 0
171
- ? null
172
- : props.parts.map(function (p) {
173
- if (!p.value) return null
174
- return h('div', {
175
- key: p.key,
176
- title: catLabel(p.key) + ' ' + fmt(p.value) + ' (' + Math.round(p.value / total * 100) + '%)',
177
- style: { width: (p.value / scale * 100) + '%', background: p.color },
178
- })
179
- }))
159
+ let total = 0;
160
+ for (const p of props.parts) total += p.value;
161
+ const scale = props.max !== void 0 && props.max > total ? props.max : total;
162
+ return h(
163
+ "div",
164
+ { className: "lc-stacked", style: { height: (props.height || 14) + "px" } },
165
+ total <= 0 ? null : props.parts.map((p) => {
166
+ if (!p.value) return null;
167
+ return h("div", {
168
+ key: p.key,
169
+ title: catLabel(p.key) + " " + fmt(p.value) + " (" + Math.round(p.value / total * 100) + "%)",
170
+ style: { width: p.value / scale * 100 + "%", background: p.color }
171
+ });
172
+ })
173
+ );
180
174
  }
181
-
182
175
  function Legend(props) {
183
- var total = 0
184
- for (var i = 0; i < props.parts.length; i++) total += props.parts[i].value
185
- return h('div', { className: 'lc-legend' },
186
- props.parts.map(function (p) {
187
- return h('span', { key: p.key, className: 'lc-chip' },
188
- h('i', { style: { background: p.color } }),
189
- catLabel(p.key) + ' ' + fmt(p.value),
190
- total > 0 ? h('em', null, Math.round(p.value / total * 100) + '%') : null)
191
- }))
176
+ let total = 0;
177
+ for (const p of props.parts) total += p.value;
178
+ return h(
179
+ "div",
180
+ { className: "lc-legend" },
181
+ props.parts.map((p) => {
182
+ return h(
183
+ "span",
184
+ { key: p.key, className: "lc-chip" },
185
+ h("i", { style: { background: p.color } }),
186
+ catLabel(p.key) + " " + fmt(p.value),
187
+ total > 0 ? h("em", null, Math.round(p.value / total * 100) + "%") : null
188
+ );
189
+ })
190
+ );
192
191
  }
193
-
194
192
  function partsOf(breakdown) {
195
- return CATS.map(function (c) {
196
- return { key: c.key, color: c.color, value: breakdown[c.key] || 0 }
197
- })
193
+ return CATS.map((c) => {
194
+ return { key: c.key, color: c.color, value: breakdown[c.key] || 0 };
195
+ });
198
196
  }
199
-
200
- // Plot height in px (the marker lane above it is 18px).
201
- var CHART_H = 112
202
- // Fixed column geometry: constant bar width keeps sparse histories from
203
- // stretching bars, and dense histories scroll horizontally instead of
204
- // compressing. The turn tick row below mirrors the same column grid.
205
- var BAR_W = 14
206
- var BAR_GAP = 2
207
-
197
+ const CHART_H = 112;
198
+ const BAR_W = 14;
199
+ const BAR_GAP = 2;
208
200
  function TrendChart(props) {
209
- var requests = props.requests
210
- var maxTotal = 1
211
- for (var i = 0; i < requests.length; i++) if (requests[i].total > maxTotal) maxTotal = requests[i].total
212
-
213
- // Compaction/prune markers: attach each to the first request logged after it.
214
- var markers = {}
215
- for (var m = 0; m < props.events.length; m++) {
216
- var ev = props.events[m]
217
- if (ev.kind !== 'compaction' && ev.kind !== 'prune') continue
218
- for (var r = 0; r < requests.length; r++) {
201
+ const requests = props.requests;
202
+ let maxTotal = 1;
203
+ for (const req of requests) if (req.total > maxTotal) maxTotal = req.total;
204
+ const markers = {};
205
+ for (const ev of props.events) {
206
+ if (ev.kind !== "compaction" && ev.kind !== "prune") continue;
207
+ for (let r = 0; r < requests.length; r++) {
219
208
  if (requests[r].seq >= ev.seq) {
220
- if (!markers[r]) markers[r] = ev
221
- break
209
+ if (!markers[r]) markers[r] = ev;
210
+ break;
222
211
  }
223
212
  }
224
213
  }
225
-
226
- // Consecutive requests of the same turn collapse into one labeled range.
227
- var groups = []
228
- for (var g = 0; g < requests.length; g++) {
229
- var grp = groups.length > 0 ? groups[groups.length - 1] : null
230
- if (grp === null || grp.turn !== requests[g].turn) {
231
- grp = { turn: requests[g].turn, count: 0 }
232
- groups.push(grp)
214
+ const groups = [];
215
+ for (const req of requests) {
216
+ let grp = groups.length > 0 ? groups[groups.length - 1] : null;
217
+ if (grp === null || grp.turn !== req.turn) {
218
+ grp = { turn: req.turn ?? 0, count: 0 };
219
+ groups.push(grp);
233
220
  }
234
- grp.count++
221
+ grp.count++;
235
222
  }
236
-
237
- // Stick to the newest bars unless the user scrolled away from the end.
238
- var scrollRef = React.useRef(null)
239
- React.useEffect(function () {
240
- var el = scrollRef.current
241
- if (el === null) return
242
- if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) el.scrollLeft = el.scrollWidth
243
- })
244
-
245
- return h('div', { className: 'lc-chartrow' },
246
- h('div', { className: 'lc-axis' },
247
- h('span', { className: 'lc-axis-top' }, fmt(maxTotal)),
248
- h('span', { className: 'lc-axis-mid' }, fmt(Math.round(maxTotal / 2))),
249
- h('span', { className: 'lc-axis-bot' }, '0')),
250
- h('div', { className: 'lc-chart-scroll', ref: scrollRef },
251
- h('div', { className: 'lc-chart' },
252
- h('div', { className: 'lc-grid lc-grid-top' }),
253
- h('div', { className: 'lc-grid lc-grid-mid' }),
254
- requests.map(function (req, i) {
255
- var selected = props.selectedSeq === req.seq
256
- var tip = tr('tip.step', { t: req.turn, s: req.step }) + ' · ' + fmtTime(req.time) + '\n'
257
- + tr('tip.total', { n: fmt(req.total) })
258
- + (req.prompt !== undefined ? tr('tip.actual', { n: fmt(req.prompt) }) : '') + '\n'
259
- + CATS.map(function (c) { return catLabel(c.key) + ' ' + fmt(req[c.key] || 0) }).join(' / ')
260
- return h('div', {
261
- key: req.seq,
262
- className: 'lc-bar' + (selected ? ' lc-bar-selected' : ''),
263
- title: tip,
264
- onClick: function () { props.onSelect(selected ? null : req.seq) },
265
- },
266
- markers[i] ? h('span', { className: 'lc-bar-marker', title: eventLabel(markers[i]) }, '✂') : null,
267
- h('div', { className: 'lc-bar-stack' },
268
- CATS.map(function (c) {
269
- var v = req[c.key] || 0
270
- if (!v) return null
271
- // px heights: the stack's height is content-driven, so
272
- // percentage heights would collapse against an indefinite base.
273
- return h('div', { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + 'px', background: c.color } })
274
- })))
275
- })),
276
- h('div', { className: 'lc-turns' },
277
- groups.map(function (grp, gi) {
278
- // One column per bar plus the shared gap; the flex gap between
279
- // ticks restores the inter-group gap, so tick spans line up with
280
- // their bars exactly.
281
- return h('span', {
282
- key: 'turn-' + gi,
283
- className: 'lc-turn',
284
- style: { width: (grp.count * (BAR_W + BAR_GAP) - BAR_GAP) + 'px' },
285
- }, 'T' + grp.turn)
286
- }))))
223
+ const scrollRef = React.useRef(null);
224
+ React.useEffect(() => {
225
+ const el = scrollRef.current;
226
+ if (el === null) return;
227
+ if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) el.scrollLeft = el.scrollWidth;
228
+ });
229
+ return h(
230
+ "div",
231
+ { className: "lc-chartrow" },
232
+ h(
233
+ "div",
234
+ { className: "lc-axis" },
235
+ h("span", { className: "lc-axis-top" }, fmt(maxTotal)),
236
+ h("span", { className: "lc-axis-mid" }, fmt(Math.round(maxTotal / 2))),
237
+ h("span", { className: "lc-axis-bot" }, "0")
238
+ ),
239
+ h(
240
+ "div",
241
+ { className: "lc-chart-scroll", ref: scrollRef },
242
+ h(
243
+ "div",
244
+ { className: "lc-chart" },
245
+ h("div", { className: "lc-grid lc-grid-top" }),
246
+ h("div", { className: "lc-grid lc-grid-mid" }),
247
+ requests.map((req, i) => {
248
+ const selected = props.selectedSeq === req.seq;
249
+ const tip = tr("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 }) + " \xB7 " + fmtTime(req.time) + "\n" + tr("tip.total", { n: fmt(req.total) }) + (req.prompt !== void 0 ? tr("tip.actual", { n: fmt(req.prompt) }) : "") + "\n" + CATS.map((c) => catLabel(c.key) + " " + fmt(req[c.key] || 0)).join(" / ");
250
+ return h(
251
+ "div",
252
+ {
253
+ key: req.seq,
254
+ className: "lc-bar" + (selected ? " lc-bar-selected" : ""),
255
+ title: tip,
256
+ onClick: () => {
257
+ props.onSelect(selected ? null : req.seq);
258
+ }
259
+ },
260
+ markers[i] ? h("span", { className: "lc-bar-marker", title: eventLabel(markers[i]) }, "\u2702") : null,
261
+ h(
262
+ "div",
263
+ { className: "lc-bar-stack" },
264
+ CATS.map((c) => {
265
+ const v = req[c.key] || 0;
266
+ if (!v) return null;
267
+ return h("div", { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + "px", background: c.color } });
268
+ })
269
+ )
270
+ );
271
+ })
272
+ ),
273
+ h(
274
+ "div",
275
+ { className: "lc-turns" },
276
+ groups.map((grp, gi) => {
277
+ return h("span", {
278
+ key: "turn-" + gi,
279
+ className: "lc-turn",
280
+ style: { width: grp.count * (BAR_W + BAR_GAP) - BAR_GAP + "px" }
281
+ }, "T" + grp.turn);
282
+ })
283
+ )
284
+ )
285
+ );
287
286
  }
288
-
289
287
  function RequestDetail(props) {
290
- var req = props.request
291
- if (!req) return null
292
- return h('div', { className: 'lc-detail' },
293
- h('div', { className: 'lc-detail-head' },
294
- h('b', null, tr('detail.step', { t: req.turn, s: req.step })),
295
- h('span', null, fmtTime(req.time)),
296
- h('span', null, tr('detail.estTotal', { n: fmt(req.total) })),
297
- req.prompt !== undefined ? h('span', { className: 'lc-actual' }, tr('detail.actual', { n: fmt(req.prompt) })) : null,
298
- req.output !== undefined ? h('span', null, tr('detail.output', { n: fmt(req.output) })) : null),
288
+ const req = props.request;
289
+ if (!req) return null;
290
+ return h(
291
+ "div",
292
+ { className: "lc-detail" },
293
+ h(
294
+ "div",
295
+ { className: "lc-detail-head" },
296
+ h("b", null, tr("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 })),
297
+ h("span", null, fmtTime(req.time)),
298
+ h("span", null, tr("detail.estTotal", { n: fmt(req.total) })),
299
+ req.prompt !== void 0 ? h("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt(req.prompt) })) : null,
300
+ req.output !== void 0 ? h("span", null, tr("detail.output", { n: fmt(req.output) })) : null
301
+ ),
299
302
  h(StackedBar, { parts: partsOf(req), height: 10 }),
300
- h('div', { className: 'lc-detail-rows' },
301
- CATS.map(function (c) {
302
- var v = req[c.key] || 0
303
- return h('div', { key: c.key, className: 'lc-detail-row' },
304
- h('i', { style: { background: c.color } }),
305
- h('span', { className: 'lc-detail-label' }, catLabel(c.key)),
306
- h('span', { className: 'lc-bar-track' },
307
- h('span', { className: 'lc-bar-fill', style: { width: (req.total > 0 ? v / req.total * 100 : 0) + '%', background: c.color } })),
308
- h('span', { className: 'lc-detail-num' }, fmt(v)),
309
- h('span', { className: 'lc-detail-pct' }, req.total > 0 ? Math.round(v / req.total * 100) + '%' : ''))
310
- })))
303
+ h(
304
+ "div",
305
+ { className: "lc-detail-rows" },
306
+ CATS.map((c) => {
307
+ const v = req[c.key] || 0;
308
+ return h(
309
+ "div",
310
+ { key: c.key, className: "lc-detail-row" },
311
+ h("i", { style: { background: c.color } }),
312
+ h("span", { className: "lc-detail-label" }, catLabel(c.key)),
313
+ h(
314
+ "span",
315
+ { className: "lc-bar-track" },
316
+ h("span", { className: "lc-bar-fill", style: { width: (req.total > 0 ? v / req.total * 100 : 0) + "%", background: c.color } })
317
+ ),
318
+ h("span", { className: "lc-detail-num" }, fmt(v)),
319
+ h("span", { className: "lc-detail-pct" }, req.total > 0 ? Math.round(v / req.total * 100) + "%" : "")
320
+ );
321
+ })
322
+ )
323
+ );
311
324
  }
312
-
313
325
  function EventList(props) {
314
326
  if (props.events.length === 0) {
315
- return h('div', { className: 'lc-empty' }, t('events.empty'))
327
+ return h("div", { className: "lc-empty" }, t("events.empty"));
316
328
  }
317
- var sorted = props.events.slice().reverse()
318
- return h('div', { className: 'lc-events' },
319
- sorted.map(function (ev, i) {
320
- var label = eventLabel(ev)
321
- return h('div', { key: ev.seq + '-' + i, className: 'lc-event' },
322
- h('span', { className: 'lc-event-icon lc-event-' + ev.kind }, EVENT_ICONS[ev.kind] || '•'),
323
- h('span', { className: 'lc-event-label', title: label }, label),
324
- ev.tokens ? h('span', { className: 'lc-event-tokens' + (ev.kind === 'inject' ? ' lc-up' : ' lc-down') },
325
- (ev.kind === 'inject' ? '+' : '−') + fmt(ev.tokens)) : null,
326
- h('span', { className: 'lc-event-time' }, fmtTime(ev.time)))
327
- }))
329
+ const sorted = props.events.slice().reverse();
330
+ return h(
331
+ "div",
332
+ { className: "lc-events" },
333
+ sorted.map((ev, i) => {
334
+ const label = eventLabel(ev);
335
+ return h(
336
+ "div",
337
+ { key: ev.seq + "-" + i, className: "lc-event" },
338
+ h("span", { className: "lc-event-icon lc-event-" + ev.kind }, EVENT_ICONS[ev.kind] || "\u2022"),
339
+ h("span", { className: "lc-event-label", title: label }, label),
340
+ ev.tokens ? h(
341
+ "span",
342
+ { className: "lc-event-tokens" + (ev.kind === "inject" ? " lc-up" : " lc-down") },
343
+ (ev.kind === "inject" ? "+" : "\u2212") + fmt(ev.tokens)
344
+ ) : null,
345
+ h("span", { className: "lc-event-time" }, fmtTime(ev.time))
346
+ );
347
+ })
348
+ );
328
349
  }
329
-
330
350
  function NodeList(props) {
331
351
  if (props.nodes.length === 0) {
332
- return h('div', { className: 'lc-empty' }, t('nodes.empty'))
352
+ return h("div", { className: "lc-empty" }, t("nodes.empty"));
333
353
  }
334
- var catColor = {}
335
- CATS.forEach(function (c) { catColor[c.key] = c.color })
336
- var rows = props.nodes.slice().reverse()
337
- return h('div', { className: 'lc-nodes' },
338
- props.dropped > 0 ? h('div', { className: 'lc-nodes-more' }, tr('nodes.more', { n: props.dropped })) : null,
339
- rows.map(function (n) {
340
- var text = nodeText(n)
341
- return h('div', { key: n.seq, className: 'lc-node' },
342
- h('i', { style: { background: catColor[n.cat] || '#999' } }),
343
- h('span', { className: 'lc-node-preview', title: text }, text),
344
- h('span', { className: 'lc-node-tokens' }, fmt(n.tokens)))
345
- }))
354
+ const catColor = {};
355
+ for (const c of CATS) catColor[c.key] = c.color;
356
+ const rows = props.nodes.slice().reverse();
357
+ return h(
358
+ "div",
359
+ { className: "lc-nodes" },
360
+ props.dropped > 0 ? h("div", { className: "lc-nodes-more" }, tr("nodes.more", { n: props.dropped })) : null,
361
+ rows.map((n) => {
362
+ const text = nodeText(n);
363
+ return h(
364
+ "div",
365
+ { key: n.seq, className: "lc-node" },
366
+ h("i", { style: { background: catColor[n.cat] || "#999" } }),
367
+ h("span", { className: "lc-node-preview", title: text }, text),
368
+ h("span", { className: "lc-node-tokens" }, fmt(n.tokens))
369
+ );
370
+ })
371
+ );
346
372
  }
347
-
348
373
  function ContextView(props) {
349
- var sessionId = props.sessionId
350
- var state = React.useState(null)
351
- var data = state[0]
352
- var setData = state[1]
353
- var errState = React.useState(null)
354
- var error = errState[0]
355
- var setError = errState[1]
356
- var selState = React.useState(null)
357
- var selectedSeq = selState[0]
358
- var setSelectedSeq = selState[1]
359
- var tickState = React.useState(0)
360
- var setTick = tickState[1]
361
-
362
- React.useEffect(function () {
363
- if (typeof sessionId !== 'string' || sessionId === '') return undefined
364
- var alive = true
365
- var load = function () {
366
- // Generic Connection RPC channel served by the Host half.
367
- ctx.connection.rpc.call('/dsh-context', 'snapshot', { sessionId: sessionId }).then(function (res) {
368
- if (!alive) return
369
- if (res && res.ok) { setData(res.value); setError(null) }
370
- else setError(res && res.error ? String(res.error.message || res.error.code) : 'failed')
371
- }, function (err) {
372
- if (alive) setError(String(err && err.message ? err.message : err))
373
- })
374
- }
375
- load()
376
- var timerId = setInterval(load, 2000)
377
- return function () { alive = false; clearInterval(timerId) }
378
- }, [sessionId])
379
-
380
- // Re-render on locale switch.
381
- React.useEffect(function () {
382
- var localeSvc = ctx.get('locale')
383
- if (!localeSvc) return undefined
384
- return localeSvc.subscribe(function () { setTick(function (x) { return x + 1 }) })
385
- }, [])
386
-
374
+ const sessionId = props.sessionId;
375
+ const [data, setData] = React.useState(null);
376
+ const [error, setError] = React.useState(null);
377
+ const [selectedSeq, setSelectedSeq] = React.useState(null);
378
+ const [tick, setTick] = React.useState(0);
379
+ React.useEffect(() => {
380
+ if (typeof sessionId !== "string" || sessionId === "") return void 0;
381
+ let alive = true;
382
+ const load = () => {
383
+ ctx.connection.rpc.call("/dsh-context", "snapshot", { sessionId }).then((res) => {
384
+ if (!alive) return;
385
+ if (res && res.ok) {
386
+ setData(res.value);
387
+ setError(null);
388
+ } else setError(res && res.error ? String(res.error.message || res.error.code) : "failed");
389
+ }, (err) => {
390
+ if (alive) setError(String(err instanceof Error ? err.message : err));
391
+ });
392
+ };
393
+ load();
394
+ const timerId = setInterval(load, 2e3);
395
+ return () => {
396
+ alive = false;
397
+ clearInterval(timerId);
398
+ };
399
+ }, [sessionId]);
400
+ React.useEffect(() => {
401
+ const localeSvc = ctx.get("locale");
402
+ if (!localeSvc) return void 0;
403
+ return localeSvc.subscribe(() => setTick((x) => x + 1));
404
+ }, []);
405
+ void tick;
387
406
  if (error) {
388
- return h('div', { className: 'lc-root' }, h('div', { className: 'lc-empty' }, t('error') + error))
407
+ return h("div", { className: "lc-root" }, h("div", { className: "lc-empty" }, t("error") + error));
389
408
  }
390
409
  if (!data) {
391
- return h('div', { className: 'lc-root' }, h('div', { className: 'lc-empty' }, t('loading')))
410
+ return h("div", { className: "lc-root" }, h("div", { className: "lc-empty" }, t("loading")));
392
411
  }
393
-
394
- var current = data.current
395
- var requests = data.requests || []
396
- var events = data.events || []
397
- var nodes = data.nodes || []
398
-
399
- var selReq = null
400
- for (var i = 0; i < requests.length; i++) if (requests[i].seq === selectedSeq) selReq = requests[i]
401
- if (!selReq && requests.length > 0) selReq = requests[requests.length - 1]
402
-
403
- var windowPct = data.contextWindow ? Math.min(100, Math.round(current.total / data.contextWindow * 100)) : null
404
-
405
- return h('div', { className: 'lc-root' },
406
-
412
+ const current = data.current;
413
+ const requests = data.requests || [];
414
+ const events = data.events || [];
415
+ const nodes = data.nodes || [];
416
+ let selReq = null;
417
+ for (const req of requests) if (req.seq === selectedSeq) selReq = req;
418
+ if (!selReq && requests.length > 0) selReq = requests[requests.length - 1];
419
+ const windowPct = data.contextWindow ? Math.min(100, Math.round(current.total / data.contextWindow * 100)) : null;
420
+ return h(
421
+ "div",
422
+ { className: "lc-root" },
407
423
  // ---- overview ----
408
- h('div', { className: 'lc-card' },
409
- h('div', { className: 'lc-card-title' },
410
- t('overview.title'),
411
- h('span', { className: 'lc-card-sub' },
412
- (data.model ? data.model : '') + (data.provider ? ' · ' + data.provider : ''))),
413
- h('div', { className: 'lc-overview-num' },
414
- h('b', null, fmt(current.total)),
415
- h('span', null, data.contextWindow
416
- ? ' / ' + fmt(data.contextWindow) + ' ' + tr('overview.ofWindow', { p: windowPct })
417
- : ' ' + t('overview.estimate'))),
424
+ h(
425
+ "div",
426
+ { className: "lc-card" },
427
+ h(
428
+ "div",
429
+ { className: "lc-card-title" },
430
+ t("overview.title"),
431
+ h(
432
+ "span",
433
+ { className: "lc-card-sub" },
434
+ (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : "")
435
+ )
436
+ ),
437
+ h(
438
+ "div",
439
+ { className: "lc-overview-num" },
440
+ h("b", null, fmt(current.total)),
441
+ h("span", null, data.contextWindow ? " / " + fmt(data.contextWindow) + " " + tr("overview.ofWindow", { p: windowPct ?? 0 }) : " " + t("overview.estimate"))
442
+ ),
418
443
  h(StackedBar, { parts: partsOf(current), height: 16, max: data.contextWindow }),
419
444
  h(Legend, { parts: partsOf(current) }),
420
- (data.toolList && data.toolList.length > 0) ? h('div', { className: 'lc-tools' },
421
- t('tools.top'),
422
- data.toolList.slice().sort(function (a, b) { return b.tokens - a.tokens }).slice(0, 5).map(function (tool) {
423
- return h('span', { key: tool.name, className: 'lc-tool-chip' }, tool.name + ' ' + fmt(tool.tokens))
445
+ data.toolList && data.toolList.length > 0 ? h(
446
+ "div",
447
+ { className: "lc-tools" },
448
+ t("tools.top"),
449
+ data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
450
+ return h("span", { key: tool.name, className: "lc-tool-chip" }, tool.name + " " + fmt(tool.tokens));
424
451
  }),
425
- data.toolList.length > 5 ? h('span', { className: 'lc-card-sub' }, ' ' + tr('tools.more', { n: data.toolList.length })) : null) : null),
426
-
452
+ data.toolList.length > 5 ? h("span", { className: "lc-card-sub" }, " " + tr("tools.more", { n: data.toolList.length })) : null
453
+ ) : null
454
+ ),
427
455
  // ---- trend ----
428
- h('div', { className: 'lc-card' },
429
- h('div', { className: 'lc-card-title' },
430
- t('trend.title'),
431
- h('span', { className: 'lc-card-sub' }, t('trend.hint'))),
432
- requests.length === 0
433
- ? h('div', { className: 'lc-empty' }, t('trend.empty'))
434
- : h('div', null,
435
- h(TrendChart, { requests: requests.slice(-80), events: events, selectedSeq: selReq ? selReq.seq : null, onSelect: setSelectedSeq }),
436
- h(RequestDetail, { request: selReq }))),
437
-
456
+ h(
457
+ "div",
458
+ { className: "lc-card" },
459
+ h(
460
+ "div",
461
+ { className: "lc-card-title" },
462
+ t("trend.title"),
463
+ h("span", { className: "lc-card-sub" }, t("trend.hint"))
464
+ ),
465
+ requests.length === 0 ? h("div", { className: "lc-empty" }, t("trend.empty")) : h(
466
+ "div",
467
+ null,
468
+ h(TrendChart, { requests: requests.slice(-80), events, selectedSeq: selReq ? selReq.seq : null, onSelect: setSelectedSeq }),
469
+ h(RequestDetail, { request: selReq })
470
+ )
471
+ ),
438
472
  // ---- events + messages ----
439
- h('div', { className: 'lc-cols' },
440
- h('div', { className: 'lc-card lc-col' },
441
- h('div', { className: 'lc-card-title' }, t('events.title')),
442
- h(EventList, { events: events })),
443
- h('div', { className: 'lc-card lc-col' },
444
- h('div', { className: 'lc-card-title' },
445
- t('nodes.title'),
446
- h('span', { className: 'lc-card-sub' }, t('nodes.hint'))),
447
- h(NodeList, { nodes: nodes, dropped: data.droppedNodes || 0 }))),
448
-
449
- h('div', { className: 'lc-foot' }, t('footer')))
473
+ h(
474
+ "div",
475
+ { className: "lc-cols" },
476
+ h(
477
+ "div",
478
+ { className: "lc-card lc-col" },
479
+ h("div", { className: "lc-card-title" }, t("events.title")),
480
+ h(EventList, { events })
481
+ ),
482
+ h(
483
+ "div",
484
+ { className: "lc-card lc-col" },
485
+ h(
486
+ "div",
487
+ { className: "lc-card-title" },
488
+ t("nodes.title"),
489
+ h("span", { className: "lc-card-sub" }, t("nodes.hint"))
490
+ ),
491
+ h(NodeList, { nodes, dropped: data.droppedNodes || 0 })
492
+ )
493
+ ),
494
+ h("div", { className: "lc-foot" }, t("footer"))
495
+ );
450
496
  }
451
-
452
- return ContextView
497
+ return ContextView;
453
498
  }
454
-
455
- var STYLES = [
456
- '.lc-root { padding: 16px 20px 32px; overflow-y: auto; height: 100%; box-sizing: border-box; color: var(--dsw-alias-label-primary); font-size: 13px; }',
457
- '.lc-card { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; }',
458
- '.lc-card-title { font-weight: 600; margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px; }',
459
- '.lc-card-sub { font-weight: 400; color: var(--dsw-alias-label-secondary); font-size: 12px; }',
460
- '.lc-overview-num { margin-bottom: 8px; }',
461
- '.lc-overview-num b { font-size: 20px; }',
462
- '.lc-overview-num span { color: var(--dsw-alias-label-secondary); }',
463
- '.lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); }',
464
- '.lc-stacked > div { height: 100%; }',
465
- '.lc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; }',
466
- '.lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); }',
467
- '.lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }',
468
- '.lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); }',
469
- '.lc-tools { margin-top: 10px; color: var(--dsw-alias-label-secondary); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }',
470
- '.lc-tool-chip { background: var(--dsw-alias-bg-layer-2); border-radius: 4px; padding: 1px 7px; font-size: 12px; color: var(--dsw-alias-label-primary); }',
471
- '.lc-chartrow { display: flex; gap: 6px; align-items: stretch; }',
472
- '.lc-axis { position: relative; width: 40px; height: 150px; padding-top: 18px; box-sizing: border-box; color: var(--dsw-alias-label-secondary); font-size: 11px; }',
473
- '.lc-axis span { position: absolute; right: 0; line-height: 1; }',
474
- '.lc-axis-top { top: 13px; }',
475
- '.lc-axis-mid { top: 69px; }',
476
- '.lc-axis-bot { top: 125px; }',
477
- '.lc-chart-scroll { flex: 1; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: thin; }',
478
- '.lc-chart-scroll::-webkit-scrollbar { height: 6px; }',
479
- '.lc-chart-scroll::-webkit-scrollbar-thumb { background: var(--dsw-alias-border-l1); border-radius: 3px; }',
480
- '.lc-chart-scroll::-webkit-scrollbar-track { background: transparent; }',
481
- '.lc-chart { position: relative; display: flex; align-items: flex-end; gap: 2px; height: 130px; padding-top: 18px; box-sizing: border-box; width: max-content; min-width: 100%; }',
482
- '.lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }',
483
- '.lc-grid-top { top: 18px; }',
484
- '.lc-grid-mid { top: 74px; }',
485
- '.lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; }',
486
- '.lc-bar:hover { background: var(--dsw-alias-bg-layer-2); }',
487
- '.lc-bar-selected { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }',
488
- '.lc-bar-stack { display: flex; flex-direction: column-reverse; width: 100%; }',
489
- '.lc-bar-stack > div { width: 100%; }',
490
- '.lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }',
491
- '.lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; }',
492
- '.lc-turn { flex: none; min-width: 24px; box-sizing: border-box; text-align: center; font-size: 11px; line-height: 18px; color: var(--dsw-alias-label-secondary); border-top: 1px solid var(--dsw-alias-border-l1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
493
- '.lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }',
494
- '.lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }',
495
- '.lc-detail-head b { color: var(--dsw-alias-label-primary); }',
496
- '.lc-detail-head .lc-actual { color: var(--dsw-alias-state-success-primary); }',
497
- '.lc-detail-rows { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; }',
498
- '.lc-detail-row { display: flex; align-items: center; gap: 8px; }',
499
- '.lc-detail-label { min-width: 70px; white-space: nowrap; color: var(--dsw-alias-label-secondary); }',
500
- '.lc-bar-track { flex: 1; height: 5px; border-radius: 3px; background: rgba(128,128,128,0.18); overflow: hidden; display: block; }',
501
- '.lc-bar-fill { display: block; height: 100%; border-radius: 3px; }',
502
- '.lc-detail-num { width: 44px; text-align: right; }',
503
- '.lc-detail-pct { width: 34px; text-align: right; color: var(--dsw-alias-label-secondary); }',
504
- '.lc-cols { display: flex; gap: 14px; flex-wrap: wrap; }',
505
- '.lc-col { flex: 1; min-width: 280px; }',
506
- '.lc-events, .lc-nodes { display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow-y: auto; }',
507
- '.lc-event { display: flex; align-items: center; gap: 8px; padding: 3px 0; }',
508
- '.lc-event-icon { width: 18px; text-align: center; color: var(--dsw-alias-state-warn-primary); }',
509
- '.lc-event-icon.lc-event-inject { color: #a855f7; }',
510
- '.lc-event-icon.lc-event-model { color: var(--dsw-alias-brand-primary); }',
511
- '.lc-event-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
512
- '.lc-event-tokens { color: var(--dsw-alias-state-success-primary); }',
513
- '.lc-event-tokens.lc-up { color: var(--dsw-alias-state-warn-primary); }',
514
- '.lc-event-time { color: var(--dsw-alias-label-secondary); font-size: 12px; }',
515
- '.lc-node { display: flex; align-items: center; gap: 8px; padding: 3px 0; }',
516
- '.lc-node-preview { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }',
517
- '.lc-node-tokens { color: var(--dsw-alias-label-secondary); }',
518
- '.lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }',
519
- '.lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }',
520
- '.lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }',
521
- ].join('\n')
522
-
499
+ const STYLES = [
500
+ ".lc-root { padding: 16px 20px 32px; overflow-y: auto; height: 100%; box-sizing: border-box; color: var(--dsw-alias-label-primary); font-size: 13px; }",
501
+ ".lc-card { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; }",
502
+ ".lc-card-title { font-weight: 600; margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px; }",
503
+ ".lc-card-sub { font-weight: 400; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
504
+ ".lc-overview-num { margin-bottom: 8px; }",
505
+ ".lc-overview-num b { font-size: 20px; }",
506
+ ".lc-overview-num span { color: var(--dsw-alias-label-secondary); }",
507
+ ".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); }",
508
+ ".lc-stacked > div { height: 100%; }",
509
+ ".lc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; }",
510
+ ".lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); }",
511
+ ".lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }",
512
+ ".lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); }",
513
+ ".lc-tools { margin-top: 10px; color: var(--dsw-alias-label-secondary); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }",
514
+ ".lc-tool-chip { background: var(--dsw-alias-bg-layer-2); border-radius: 4px; padding: 1px 7px; font-size: 12px; color: var(--dsw-alias-label-primary); }",
515
+ ".lc-chartrow { display: flex; gap: 6px; align-items: stretch; }",
516
+ ".lc-axis { position: relative; width: 40px; height: 150px; padding-top: 18px; box-sizing: border-box; color: var(--dsw-alias-label-secondary); font-size: 11px; }",
517
+ ".lc-axis span { position: absolute; right: 0; line-height: 1; }",
518
+ ".lc-axis-top { top: 13px; }",
519
+ ".lc-axis-mid { top: 69px; }",
520
+ ".lc-axis-bot { top: 125px; }",
521
+ ".lc-chart-scroll { flex: 1; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: thin; }",
522
+ ".lc-chart-scroll::-webkit-scrollbar { height: 6px; }",
523
+ ".lc-chart-scroll::-webkit-scrollbar-thumb { background: var(--dsw-alias-border-l1); border-radius: 3px; }",
524
+ ".lc-chart-scroll::-webkit-scrollbar-track { background: transparent; }",
525
+ ".lc-chart { position: relative; display: flex; align-items: flex-end; gap: 2px; height: 130px; padding-top: 18px; box-sizing: border-box; width: max-content; min-width: 100%; }",
526
+ ".lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }",
527
+ ".lc-grid-top { top: 18px; }",
528
+ ".lc-grid-mid { top: 74px; }",
529
+ ".lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; }",
530
+ ".lc-bar:hover { background: var(--dsw-alias-bg-layer-2); }",
531
+ ".lc-bar-selected { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }",
532
+ ".lc-bar-stack { display: flex; flex-direction: column-reverse; width: 100%; }",
533
+ ".lc-bar-stack > div { width: 100%; }",
534
+ ".lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }",
535
+ ".lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; }",
536
+ ".lc-turn { flex: none; min-width: 24px; box-sizing: border-box; text-align: center; font-size: 11px; line-height: 18px; color: var(--dsw-alias-label-secondary); border-top: 1px solid var(--dsw-alias-border-l1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
537
+ ".lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }",
538
+ ".lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
539
+ ".lc-detail-head b { color: var(--dsw-alias-label-primary); }",
540
+ ".lc-detail-head .lc-actual { color: var(--dsw-alias-state-success-primary); }",
541
+ ".lc-detail-rows { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; }",
542
+ ".lc-detail-row { display: flex; align-items: center; gap: 8px; }",
543
+ ".lc-detail-label { min-width: 70px; white-space: nowrap; color: var(--dsw-alias-label-secondary); }",
544
+ ".lc-bar-track { flex: 1; height: 5px; border-radius: 3px; background: rgba(128,128,128,0.18); overflow: hidden; display: block; }",
545
+ ".lc-bar-fill { display: block; height: 100%; border-radius: 3px; }",
546
+ ".lc-detail-num { width: 44px; text-align: right; }",
547
+ ".lc-detail-pct { width: 34px; text-align: right; color: var(--dsw-alias-label-secondary); }",
548
+ ".lc-cols { display: flex; gap: 14px; flex-wrap: wrap; }",
549
+ ".lc-col { flex: 1; min-width: 280px; }",
550
+ ".lc-events, .lc-nodes { display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow-y: auto; }",
551
+ ".lc-event { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
552
+ ".lc-event-icon { width: 18px; text-align: center; color: var(--dsw-alias-state-warn-primary); }",
553
+ ".lc-event-icon.lc-event-inject { color: #a855f7; }",
554
+ ".lc-event-icon.lc-event-model { color: var(--dsw-alias-brand-primary); }",
555
+ ".lc-event-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
556
+ ".lc-event-tokens { color: var(--dsw-alias-state-success-primary); }",
557
+ ".lc-event-tokens.lc-up { color: var(--dsw-alias-state-warn-primary); }",
558
+ ".lc-event-time { color: var(--dsw-alias-label-secondary); font-size: 12px; }",
559
+ ".lc-node { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
560
+ ".lc-node-preview { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }",
561
+ ".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
562
+ ".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
563
+ ".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
564
+ ".lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }"
565
+ ].join("\n");
523
566
  function apply(ctx) {
524
- // Bilingual dictionaries; the tab label thunk and all UI text follow the
525
- // active locale through the bound translate (missing keys fall back to
526
- // zh, then the key itself). The registration rides ctx.effect, so a stop
527
- // or HMR reload disposes it.
528
- ctx.effect(function () {
529
- return ctx.locale.register('dsh-context', { zh: DICT_ZH, en: DICT_EN })
530
- }, 'dsh-context: dictionaries')
531
- var t = ctx.locale.bind('dsh-context')
532
-
533
- // Theme-native styles, injected as a plugin-owned <style> tag (the web
534
- // boot loader claims and removes tags carrying data-plugin on unload).
535
- ctx.effect(function () {
536
- var tag = document.createElement('style')
537
- tag.setAttribute('data-plugin', 'dsh-context')
538
- tag.textContent = STYLES
539
- document.head.appendChild(tag)
540
- return function () {
541
- if (tag.parentNode !== null) tag.parentNode.removeChild(tag)
542
- }
543
- }, 'dsh-context: styles')
544
-
545
- var ContextView = makeView(ctx, t)
546
- ctx.slots.inject('conversation.view', function () {
567
+ ctx.effect(() => {
568
+ return ctx.locale.register("dsh-context", { zh: DICT_ZH, en: DICT_EN });
569
+ }, "dsh-context: dictionaries");
570
+ const t = ctx.locale.bind("dsh-context");
571
+ ctx.effect(() => {
572
+ const tag = document.createElement("style");
573
+ tag.setAttribute("data-plugin", "dsh-context");
574
+ tag.textContent = STYLES;
575
+ document.head.appendChild(tag);
576
+ return () => {
577
+ if (tag.parentNode !== null) tag.parentNode.removeChild(tag);
578
+ };
579
+ }, "dsh-context: styles");
580
+ const ContextView = makeView(ctx, t);
581
+ ctx.slots.inject("conversation.view", () => {
547
582
  return ctx.slots.register(
548
583
  // order 20 renders right of Chat (0) and Trajectory (10).
549
- { name: 'conversation.view', id: 'context', order: 20, label: function () { return t('tab') } },
550
- function (props) { return h(ContextView, props) },
551
- )
552
- })
584
+ { name: "conversation.view", id: "context", order: 20, label: () => t("tab") },
585
+ (props) => h(ContextView, props)
586
+ );
587
+ });
553
588
  }
554
-
555
589
  module.exports = {
556
- name: 'dsh-context',
557
- inject: ['connection', 'slots', 'locale'],
558
- apply: apply,
559
- }
590
+ name: "dsh-context",
591
+ inject: ["connection", "slots", "locale"],
592
+ apply
593
+ };
560
594
 
561
595
  return module.exports;
562
596
  },