dsh-my-observability 0.3.0 → 0.3.2

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.
@@ -1,386 +0,0 @@
1
- // ── 轨迹回放面板(时间轴)──────────────────────────────────────────
2
- // 拆分的审计视图组件(搜索/组合过滤/导出/统计/高亮/hook)位于
3
- // replay-ext.js 片段(见 client.src.js 占位符顺序)。
4
-
5
- /** 请求插件 API(非 2xx 抛错;返回响应 JSON 的 value 字段)。 */
6
- function apiJson(path, options) {
7
- return fetch(path, options).then(async (res) => {
8
- const data = await res.json()
9
- if (!res.ok) throw new Error(data.error?.message || `HTTP ${res.status}`)
10
- return data.value
11
- })
12
- }
13
-
14
- /** 事件类型 → 中文标签。 */
15
- function typeLabel(event) {
16
- switch (event.type) {
17
- case 'agent_status':
18
- return strings.typeAgentStatus()
19
- case 'llm_stream':
20
- return strings.typeLlmStream()
21
- case 'tool_call':
22
- return strings.typeToolCall()
23
- case 'tool_result':
24
- return strings.typeToolResult()
25
- case 'plugin_event':
26
- return strings.typePluginEvent()
27
- default:
28
- return event.type
29
- }
30
- }
31
-
32
- /** 事件类型 → 视觉类别(徽标/图标/节点共用,颜色语义一致):
33
- * status=info / llm=warn / call=accent / result=success / fail=danger /
34
- * plugin=info(插件事件复用 info 色,图标区分)。 */
35
- function typeKind(event) {
36
- if (event.type === 'agent_status') return 'status'
37
- if (event.type === 'llm_stream') return 'llm'
38
- if (event.type === 'tool_call') return 'call'
39
- if (event.type === 'plugin_event') return 'plugin'
40
- return event.data?.ok === false ? 'fail' : 'result'
41
- }
42
-
43
- /** 事件类型 → 类型图标(共享线性图标集,stroke=currentColor)。 */
44
- function typeIcon(event) {
45
- const kind = typeKind(event)
46
- if (kind === 'status') return icon.clock(15)
47
- if (kind === 'llm') return icon.file(15)
48
- if (kind === 'call') return icon.external(15)
49
- if (kind === 'plugin') return icon.alert(15)
50
- if (kind === 'fail') return icon.close(15)
51
- return icon.check(15)
52
- }
53
-
54
- /** 时间戳 → HH:MM:SS。 */
55
- function timeText(time) {
56
- try {
57
- const date = new Date(time)
58
- const pad = (n) => String(n).padStart(2, '0')
59
- return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
60
- } catch {
61
- return ''
62
- }
63
- }
64
-
65
- /** agent 类型标记 → 中文。 */
66
- function agentTypeText(agentType) {
67
- if (agentType === 'top') return strings.agentTop()
68
- if (agentType === 'subagent') return strings.agentSub()
69
- return strings.agentUnknown()
70
- }
71
-
72
- /** 模型流阶段 → 中文。 */
73
- function phaseText(phase) {
74
- if (phase === 'start') return strings.phaseStart()
75
- if (phase === 'end') return strings.phaseEnd()
76
- if (phase === 'error') return strings.phaseError()
77
- return phase
78
- }
79
-
80
- /** agent 状态事件摘要。 */
81
- function agentMeta(data) {
82
- return `状态 ${data.status} · ${agentTypeText(data.agentType)}`
83
- }
84
-
85
- /** 模型流事件摘要(开始/结束/错误 + 统计)。 */
86
- function llmMeta(data) {
87
- const stats = data.phase === 'start' ? '' : ` · ${data.chunks} chunks / ${data.chars} chars / ${data.ms}ms`
88
- const error = data.message !== undefined ? `:${data.message}` : ''
89
- return `${phaseText(data.phase)}${stats}${error}`
90
- }
91
-
92
- /** 工具调用事件摘要(名称 + 参数摘要)。 */
93
- function toolCallMeta(data) {
94
- const args = data.args && data.args.summary !== undefined ? ` — ${data.args.summary}` : ''
95
- return `${data.name}${args}`
96
- }
97
-
98
- /** 工具结果事件摘要(名称 + 成败 + 耗时)。 */
99
- function toolResultMeta(data) {
100
- const result = data.ok === false ? strings.toolFail() : strings.toolOk()
101
- return `${data.name} · ${result} · ${data.ms}ms`
102
- }
103
-
104
- /** 插件事件摘要(插件名 · 事件名 · 动作;issue #154)。 */
105
- function pluginEventMeta(data) {
106
- const action = typeof data.action === 'string' && data.action !== '' ? data.action : data.event
107
- return `${data.plugin} · ${data.event} · ${action}`
108
- }
109
-
110
- /** 事件 → 摘要文本(单行,尽力而为)。 */
111
- function eventMeta(event) {
112
- const data = event.data || {}
113
- if (event.type === 'agent_status') return agentMeta(data)
114
- if (event.type === 'llm_stream') return llmMeta(data)
115
- if (event.type === 'tool_call') return toolCallMeta(data)
116
- if (event.type === 'tool_result') return toolResultMeta(data)
117
- if (event.type === 'plugin_event') return pluginEventMeta(data)
118
- return ''
119
- }
120
-
121
- /** 插件事件详情行(原因 + 参数键值对;非插件事件返回 null 不可展开)。 */
122
- function eventDetail(event) {
123
- if (event?.type !== 'plugin_event') return null
124
- const data = event.data || {}
125
- const rows = []
126
- if (typeof data.reason === 'string' && data.reason !== '') {
127
- rows.push(
128
- createElement(
129
- 'div',
130
- { key: 'reason', className: 'dsh-my-observability-detail-row' },
131
- createElement('span', { className: 'dsh-my-observability-detail-key' }, strings.detailReason()),
132
- createElement('span', { className: 'dsh-my-observability-detail-value' }, data.reason),
133
- ),
134
- )
135
- }
136
- if (data.params !== null && typeof data.params === 'object') {
137
- for (const [key, value] of Object.entries(data.params)) {
138
- rows.push(
139
- createElement(
140
- 'div',
141
- { key, className: 'dsh-my-observability-detail-row' },
142
- createElement('span', { className: 'dsh-my-observability-detail-key' }, key),
143
- createElement('span', { className: 'dsh-my-observability-detail-value' }, String(value)),
144
- ),
145
- )
146
- }
147
- }
148
- return rows.length > 0 ? rows : null
149
- }
150
-
151
- /** 单条事件行:节点圆点 + 类型图标 + 徽标/时间 + 摘要(hover/active 反馈)。
152
- * 摘要命中关键词时以 mark 高亮;插件事件可点击展开详情(原因/参数)。 */
153
- function EventRow({ event, keyword }) {
154
- const meta = eventMeta(event)
155
- const kind = typeKind(event)
156
- const detail = eventDetail(event)
157
- const [open, setOpen] = useState(false)
158
- return createElement(
159
- 'button',
160
- {
161
- className: 'dsh-my-observability-event',
162
- type: 'button',
163
- 'aria-expanded': detail !== null ? open : undefined,
164
- onClick: detail !== null ? () => setOpen(!open) : undefined,
165
- },
166
- createElement('span', { className: `dsh-my-observability-node dsh-my-observability-node-${kind}` }),
167
- createElement(
168
- 'span',
169
- { className: `dsh-my-observability-event-icon dsh-my-observability-icon-${kind}` },
170
- typeIcon(event),
171
- ),
172
- createElement(
173
- 'span',
174
- { className: 'dsh-my-observability-event-body' },
175
- createElement(
176
- 'span',
177
- { className: 'dsh-my-observability-event-head' },
178
- createElement(
179
- 'span',
180
- { className: `dsh-my-observability-badge dsh-my-observability-badge-${kind}` },
181
- typeLabel(event),
182
- ),
183
- createElement('span', { className: 'dsh-my-observability-time' }, timeText(event.time)),
184
- ),
185
- meta !== ''
186
- ? createElement(
187
- 'span',
188
- { className: 'dsh-my-observability-event-meta' },
189
- createElement(HighlightText, { text: meta, keyword }),
190
- )
191
- : null,
192
- detail !== null && open ? createElement('div', { className: 'dsh-my-observability-event-detail' }, detail) : null,
193
- ),
194
- )
195
- }
196
-
197
- /** 类型过滤按钮组(aria-pressed 选中态;plugin 过滤插件事件,issue #154)。 */
198
- function TypeFilter({ filter, onFilter }) {
199
- const options = [
200
- ['', strings.filterAll()],
201
- ['agent_status', strings.filterStatus()],
202
- ['llm_stream', strings.filterLlm()],
203
- ['tool', strings.filterTools()],
204
- ['plugin', strings.filterPlugin()],
205
- ]
206
- return createElement(
207
- 'div',
208
- { className: 'dsh-my-observability-filters' },
209
- options.map(([value, label]) =>
210
- createElement(
211
- 'button',
212
- {
213
- key: value,
214
- type: 'button',
215
- className: `dsh-my-observability-chip${filter === value ? ' dsh-my-observability-chip-active' : ''}`,
216
- 'aria-pressed': filter === value,
217
- onClick: () => onFilter(value),
218
- },
219
- label,
220
- ),
221
- ),
222
- )
223
- }
224
-
225
- /** 拉取会话列表与事件(选中为空时自动选当前/首个会话)。 */
226
- async function loadReplayData(selected, currentSession, setters) {
227
- try {
228
- const list = await apiJson('/observability/api/sessions')
229
- setters.setSessions(list)
230
- if (selected === '' && list.length > 0) {
231
- const preferred = list.some((s) => s.sessionId === currentSession) ? currentSession : list[0].sessionId
232
- setters.setSelected(preferred)
233
- return
234
- }
235
- const query =
236
- selected !== ''
237
- ? `/observability/api/events?sessionId=${encodeURIComponent(selected)}&limit=300`
238
- : '/observability/api/events?limit=0'
239
- setters.setEvents(await apiJson(query))
240
- setters.setError('')
241
- } catch (err) {
242
- setters.setError(err instanceof Error ? err.message : String(err))
243
- } finally {
244
- setters.setLoading(false)
245
- }
246
- }
247
-
248
- /** 下拉选项文案:可读标题(首条用户消息)优先,无标题回退 UUID 短显;
249
- * 附加事件数与时间,用户一眼看出"哪个对话"。 */
250
- function sessionOptionLabel(s) {
251
- const title = typeof s.title === 'string' && s.title !== '' ? s.title : strings.sessionFallback(shortId(s.sessionId))
252
- return `${title} · ${strings.eventCount(s.count)}`
253
- }
254
-
255
- /** 会话 id 短显示(UUID 取前 8 位)。 */
256
- function shortId(sessionId) {
257
- return typeof sessionId === 'string' && sessionId.length > 8 ? `${sessionId.slice(0, 8)}…` : sessionId || ''
258
- }
259
-
260
- /** 工具栏:会话选择 + 手动刷新 + 类型过滤。 */
261
- function ReplayToolbar({ sessions, selected, onSelect, filter, onFilter, onRefresh }) {
262
- return createElement(
263
- 'div',
264
- { className: 'dsh-my-observability-toolbar' },
265
- createElement(
266
- 'div',
267
- { className: 'dsh-my-observability-toolbar-row' },
268
- createElement(
269
- 'select',
270
- {
271
- className: 'dsh-my-observability-select',
272
- value: selected,
273
- disabled: sessions.length === 0,
274
- onChange: (e) => onSelect(e.target.value),
275
- },
276
- sessions.length === 0
277
- ? createElement('option', { value: '' }, strings.allSessions())
278
- : sessions.map((s) =>
279
- createElement('option', { key: s.sessionId, value: s.sessionId }, sessionOptionLabel(s)),
280
- ),
281
- ),
282
- createElement(
283
- 'button',
284
- {
285
- type: 'button',
286
- className: 'dsh-my-observability-iconbtn',
287
- 'aria-label': strings.refresh(),
288
- title: strings.refresh(),
289
- onClick: onRefresh,
290
- },
291
- icon.refresh(15),
292
- ),
293
- ),
294
- createElement(TypeFilter, { filter, onFilter }),
295
- )
296
- }
297
-
298
- /** 加载中状态(旋转刷新图标 + 次级色文案,不阻塞布局)。 */
299
- function LoadingState() {
300
- return createElement(
301
- 'div',
302
- { className: 'dsh-my-observability-state' },
303
- icon.refresh(14),
304
- createElement('span', null, strings.loading()),
305
- )
306
- }
307
-
308
- /** 空状态(图标 + 主文案 + hint 两行结构)。 */
309
- function EmptyState() {
310
- return createElement(
311
- 'div',
312
- { className: 'dsh-my-observability-empty' },
313
- createElement('span', { className: 'dsh-my-observability-empty-icon' }, icon.clock(20)),
314
- createElement('span', null, strings.emptyEvents()),
315
- createElement('span', { className: 'dsh-my-observability-empty-hint' }, strings.emptyEventsHint()),
316
- )
317
- }
318
-
319
- /** 错误状态(错误色文案 + 重试按钮)。 */
320
- function ErrorState({ message, onRetry }) {
321
- return createElement(
322
- 'div',
323
- { className: 'dsh-my-observability-error' },
324
- createElement('span', { className: 'dsh-my-observability-error-text' }, `${strings.loadError()}:${message}`),
325
- createElement(
326
- 'button',
327
- {
328
- type: 'button',
329
- className: 'dsh-my-observability-iconbtn',
330
- 'aria-label': strings.retry(),
331
- title: strings.retry(),
332
- onClick: onRetry,
333
- },
334
- icon.refresh(15),
335
- ),
336
- )
337
- }
338
-
339
- /** 轨迹回放主面板:会话选择 + 类型过滤 + 搜索/组合过滤 + 导出 + 统计 + 时间轴。
340
- * 状态与派生值集中在 replay-ext.js 的 useReplayState;本组件只拼装视图。
341
- * 面板可见时轮询、隐藏时暂停。 */
342
- function ReplayPanel(props) {
343
- const s = useReplayState(props)
344
- return createElement(
345
- 'div',
346
- { className: 'dsh-my-observability-panel' },
347
- createElement(ResourcePanel, { resource: s.resource }),
348
- createElement(ReplayToolbar, {
349
- sessions: s.sessions,
350
- selected: s.selected,
351
- onSelect: s.setSelected,
352
- filter: s.filter,
353
- onFilter: s.setFilter,
354
- onRefresh: s.retry,
355
- }),
356
- createElement(SearchFilterBar, {
357
- keyword: s.keyword,
358
- onKeyword: s.setKeyword,
359
- timeStart: s.timeStart,
360
- onTimeStart: s.setTimeStart,
361
- timeEnd: s.timeEnd,
362
- onTimeEnd: s.setTimeEnd,
363
- result: s.result,
364
- onResult: s.setResult,
365
- onClear: s.clearFilters,
366
- }),
367
- createElement(ExportBar, {
368
- scope: s.scope,
369
- onScope: s.setScope,
370
- onExportJson: () => void s.onExport('json'),
371
- onExportCsv: () => void s.onExport('csv'),
372
- showStats: s.showStats,
373
- onToggleStats: () => s.setShowStats((value) => !value),
374
- disabled: !s.canExport,
375
- }),
376
- s.error !== '' ? createElement(ErrorState, { message: s.error, onRetry: s.retry }) : null,
377
- s.loading && s.error === '' ? createElement(LoadingState, null) : null,
378
- !s.loading && s.error === '' && s.filtered.length === 0
379
- ? s.hasFilter
380
- ? createElement(NoMatchesState, null)
381
- : createElement(EmptyState, null)
382
- : null,
383
- s.showStats ? createElement(StatsPanel, { events: s.filtered }) : null,
384
- createElement('div', { className: 'dsh-my-observability-timeline' }, s.rows),
385
- )
386
- }
@@ -1,80 +0,0 @@
1
- // ── 资源监控区块(写放大/资源超限预警,见 lib/resource-monitor.js)──────
2
- // 依赖 replay.js 先拼接(apiJson)与 i18n.js(strings)。纯函数声明文本。
3
-
4
- const RESOURCE_POLL_MS = 15000
5
-
6
- function fmtResourceBytes(bytes) {
7
- if (!Number.isFinite(bytes)) return '-'
8
- return `${(bytes / 1048576).toFixed(1)} MB`
9
- }
10
-
11
- /** 资源采样状态:可见时每 15s 轮询 /observability/api/resources。 */
12
- function useResourceState(visible) {
13
- const [resource, setResource] = useState(null)
14
- useEffect(() => {
15
- if (!visible) return undefined
16
- let alive = true
17
- const tick = () => {
18
- if (alive)
19
- apiJson('/observability/api/resources')
20
- .then(setResource)
21
- .catch(() => {})
22
- }
23
- tick()
24
- const timer = setInterval(tick, RESOURCE_POLL_MS)
25
- return () => {
26
- alive = false
27
- clearInterval(timer)
28
- }
29
- }, [visible])
30
- return resource
31
- }
32
-
33
- function ResourceMetric({ label, value }) {
34
- return createElement(
35
- 'div',
36
- { className: 'dsh-my-observability-resource-metric' },
37
- createElement('span', { className: 'dsh-my-observability-resource-label' }, label),
38
- createElement('span', { className: 'dsh-my-observability-resource-value' }, value),
39
- )
40
- }
41
-
42
- /** 资源面板:四指标 + 告警列表(write-rate/file-size level=error 红色,cpu/memory warn 黄色)。 */
43
- function ResourcePanel({ resource }) {
44
- if (resource === null || resource === undefined) {
45
- return createElement('div', { className: 'dsh-my-observability-resource' }, strings.resourceLoading())
46
- }
47
- const alerts = Array.isArray(resource.alerts) ? resource.alerts : []
48
- return createElement(
49
- 'div',
50
- { className: 'dsh-my-observability-resource' },
51
- createElement('div', { className: 'dsh-my-observability-resource-head' }, strings.resourceTitle()),
52
- createElement(
53
- 'div',
54
- { className: 'dsh-my-observability-resource-grid' },
55
- createElement(ResourceMetric, { label: strings.resourceFile(), value: fmtResourceBytes(resource.fileBytes) }),
56
- createElement(ResourceMetric, {
57
- label: strings.resourceRate(),
58
- value: `${fmtResourceBytes(resource.writeRateBytesPerHour)}/h`,
59
- }),
60
- createElement(ResourceMetric, {
61
- label: strings.resourceCpu(),
62
- value: `${Math.round(resource.cpuPercent ?? 0)}%`,
63
- }),
64
- createElement(ResourceMetric, { label: strings.resourceMem(), value: fmtResourceBytes(resource.memoryBytes) }),
65
- ),
66
- alerts.length > 0
67
- ? createElement(
68
- 'div',
69
- { className: 'dsh-my-observability-resource-alerts' },
70
- alerts.map((alert) =>
71
- createElement(
72
- 'div',
73
- { className: `dsh-my-observability-resource-alert dsh-my-observability-resource-alert-${alert.level}` },
74
- alert.message,
75
- ),
76
- ),
77
- )
78
- : null,
79
- )
80
- }
@@ -1,156 +0,0 @@
1
- // ── 样式(DSH 语义 token,随 activation 注入 / teardown 卸载)──────
2
- // 前缀 dsh-my-observability-(issue #54:与 dsh-my-guard 前缀分离,消除跨插件类名冲突)。
3
- const STYLES = `
4
- .dsh-my-observability-panel{display:flex;flex-direction:column;gap:10px;padding:2px 6px 8px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14)}
5
- .dsh-my-observability-toolbar{display:flex;flex-direction:column;gap:8px}
6
- .dsh-my-observability-toolbar-row{display:flex;align-items:center;gap:6px}
7
- .dsh-my-observability-select{flex:1;min-width:0;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-primary);
8
- background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 8px}
9
- .dsh-my-observability-select:disabled{opacity:.4;cursor:default}
10
- .dsh-my-observability-input{flex:1;min-width:0;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-primary);
11
- background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 8px}
12
- .dsh-my-observability-input::placeholder{color:var(--dsw-alias-label-tertiary)}
13
- .dsh-my-observability-repo-row{display:flex;gap:8px;align-items:center}
14
- .dsh-my-observability-repo-input{flex:1}
15
- .dsh-my-observability-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;
16
- border:none;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:none;
17
- transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out), color var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
18
- .dsh-my-observability-iconbtn svg{display:block}
19
- .dsh-my-observability-iconbtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
20
- .dsh-my-observability-iconbtn:disabled{opacity:.4;cursor:default}
21
- .dsh-my-observability-filters{display:flex;gap:6px;flex-wrap:wrap}
22
- .dsh-my-observability-chip{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);background:transparent;
23
- border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:2px 10px;cursor:pointer;
24
- transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out), color var(--ds-transition-duration-slow) var(--ds-ease-in-out), border-color var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
25
- .dsh-my-observability-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
26
- .dsh-my-observability-chip-active{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-interactive-primary);
27
- background:color-mix(in srgb, var(--dsw-alias-interactive-primary) 12%, transparent)}
28
- /* ── 时间轴:左侧竖线 + 类型色节点圆点 + 类型图标行 ── */
29
- .dsh-my-observability-timeline{display:flex;flex-direction:column;gap:2px;max-height:calc(100vh - 240px);overflow-y:auto;
30
- padding-left:14px;position:relative}
31
- .dsh-my-observability-timeline::before{content:'';position:absolute;left:5px;top:8px;bottom:8px;width:2px;border-radius:1px;
32
- background:var(--dsw-alias-border-l2)}
33
- .dsh-my-observability-event{position:relative;display:flex;align-items:flex-start;gap:8px;box-sizing:border-box;width:100%;
34
- margin:0;padding:5px 8px 5px 0;border:none;background:transparent;border-radius:8px;cursor:pointer;text-align:left;
35
- font:var(--dsw-font-s-14);color:var(--dsw-alias-label-primary);
36
- animation:dsh-my-observability-row-in 150ms var(--ds-ease-in-out);
37
- transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
38
- .dsh-my-observability-event:hover{background:var(--dsw-alias-interactive-bg-hover)}
39
- .dsh-my-observability-event:active{background:color-mix(in srgb, var(--dsw-alias-interactive-bg-hover) 55%, transparent)}
40
- .dsh-my-observability-node{position:absolute;left:-14px;top:50%;transform:translateY(-50%);width:12px;height:12px;flex:none;
41
- box-sizing:border-box;border-radius:50%;background:var(--dsw-alias-bg-layer-2);border:2px solid var(--dsw-alias-label-tertiary)}
42
- .dsh-my-observability-node-status{border-color:var(--dsw-alias-state-info-primary)}
43
- .dsh-my-observability-node-llm{border-color:var(--dsw-alias-state-warn-primary)}
44
- .dsh-my-observability-node-call{border-color:var(--dsw-alias-accent)}
45
- .dsh-my-observability-node-plugin{border-color:var(--dsw-alias-state-info-primary)}
46
- .dsh-my-observability-node-result{border-color:var(--dsw-alias-state-success-primary)}
47
- .dsh-my-observability-node-fail{border-color:var(--dsw-alias-state-error-primary)}
48
- .dsh-my-observability-event-icon{flex:none;display:flex;align-items:center;margin-top:1px}
49
- .dsh-my-observability-icon-status{color:var(--dsw-alias-state-info-primary)}
50
- .dsh-my-observability-icon-llm{color:var(--dsw-alias-state-warn-primary)}
51
- .dsh-my-observability-icon-call{color:var(--dsw-alias-accent)}
52
- .dsh-my-observability-icon-plugin{color:var(--dsw-alias-state-info-primary)}
53
- .dsh-my-observability-icon-result{color:var(--dsw-alias-state-success-primary)}
54
- .dsh-my-observability-icon-fail{color:var(--dsw-alias-state-error-primary)}
55
- .dsh-my-observability-event-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
56
- .dsh-my-observability-event-head{display:flex;align-items:center;gap:8px;justify-content:space-between}
57
- .dsh-my-observability-badge{flex:none;font:var(--dsw-font-xxxs-strong-11);border-radius:4px;padding:1px 6px}
58
- .dsh-my-observability-badge-status{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
59
- .dsh-my-observability-badge-llm{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent)}
60
- .dsh-my-observability-badge-call{color:var(--dsw-alias-accent);background:color-mix(in srgb, var(--dsw-alias-accent) 12%, transparent)}
61
- .dsh-my-observability-badge-plugin{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
62
- .dsh-my-observability-badge-result{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)}
63
- .dsh-my-observability-badge-fail{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent)}
64
- .dsh-my-observability-time{flex:none;font:var(--dsw-font-xxxs-11);color:var(--dsw-alias-label-tertiary);white-space:nowrap}
65
- .dsh-my-observability-event-meta{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);line-height:1.6;word-break:break-word}
66
- /* ── 插件事件详情展开(issue #154:原因/参数可查)── */
67
- .dsh-my-observability-event-detail{display:flex;flex-direction:column;gap:2px;margin-top:4px;padding:6px 8px;
68
- border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1)}
69
- .dsh-my-observability-detail-row{display:flex;gap:8px;font:var(--dsw-font-xxs-12);line-height:1.5;word-break:break-word}
70
- .dsh-my-observability-detail-key{flex:none;font:var(--dsw-font-xxxs-strong-11);color:var(--dsw-alias-label-tertiary);min-width:56px}
71
- .dsh-my-observability-detail-value{color:var(--dsw-alias-label-primary)}
72
- /* ── 状态区:loading / 空 / 错误 ── */
73
- .dsh-my-observability-state{display:flex;align-items:center;gap:6px;padding:8px 6px;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary)}
74
- .dsh-my-observability-state svg{flex:none;animation:dsh-my-observability-spin 1s linear infinite}
75
- .dsh-my-observability-empty{display:flex;flex-direction:column;align-items:center;gap:4px;padding:16px 8px;text-align:center;
76
- font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary);line-height:1.7}
77
- .dsh-my-observability-empty-icon{display:flex;color:var(--dsw-alias-label-tertiary)}
78
- .dsh-my-observability-empty-hint{display:block;color:var(--dsw-alias-label-dimmed);font:var(--dsw-font-xxxs-11)}
79
- .dsh-my-observability-error{display:flex;align-items:center;gap:6px;padding:8px 6px;font:var(--dsw-font-xxs-12);
80
- color:var(--dsw-alias-state-error-primary);white-space:pre-wrap;word-break:break-all;line-height:1.7}
81
- .dsh-my-observability-error-text{flex:1;min-width:0}
82
- @keyframes dsh-my-observability-row-in{from{opacity:0;transform:translateY(1px)}to{opacity:1;transform:none}}
83
- @keyframes dsh-my-observability-spin{to{transform:rotate(360deg)}}
84
- /* ── 审计视图:搜索 / 组合过滤 / 导出 / 统计 / 高亮 ── */
85
- .dsh-my-observability-search-row{display:flex;align-items:center;gap:6px}
86
- .dsh-my-observability-search-row .dsh-my-observability-input{flex:1}
87
- .dsh-my-observability-time-row{display:flex;align-items:center;gap:6px}
88
- .dsh-my-observability-time-label{flex:none;font:var(--dsw-font-xxxs-11);color:var(--dsw-alias-label-tertiary)}
89
- .dsh-my-observability-time-input{flex:1;min-width:0}
90
- .dsh-my-observability-mark{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 30%, transparent);
91
- color:var(--dsw-alias-label-primary);border-radius:2px;padding:0 1px}
92
- .dsh-my-observability-export{display:flex;flex-direction:column;gap:6px}
93
- .dsh-my-observability-stats{display:flex;flex-direction:column;gap:6px;border:1px solid var(--dsw-alias-border-l2);
94
- border-radius:6px;padding:8px}
95
- .dsh-my-observability-stats-title{font:var(--dsw-font-xs-strong-13);color:var(--dsw-alias-label-primary)}
96
- .dsh-my-observability-stats-table{width:100%;border-collapse:collapse;font:var(--dsw-font-xxs-12)}
97
- .dsh-my-observability-stats-table th,.dsh-my-observability-stats-table td{text-align:left;padding:3px 6px;
98
- border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary)}
99
- .dsh-my-observability-stats-table th{font:var(--dsw-font-xxxs-strong-11);color:var(--dsw-alias-label-tertiary)}
100
- .dsh-my-observability-stats-empty{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary);padding:6px 2px}
101
- /* ── Git 面板 ── */
102
- .dsh-my-observability-status{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-label-secondary)}
103
- .dsh-my-observability-actions{display:flex;gap:8px;flex-wrap:wrap}
104
- .dsh-my-observability-btn{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);
105
- border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 12px;cursor:pointer;
106
- transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out), color var(--ds-transition-duration-slow) var(--ds-ease-in-out), border-color var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
107
- .dsh-my-observability-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
108
- .dsh-my-observability-btn:disabled{opacity:.5;cursor:default}
109
- .dsh-my-observability-btn-primary{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-interactive-primary);
110
- background:color-mix(in srgb, var(--dsw-alias-interactive-primary) 16%, transparent)}
111
- .dsh-my-observability-section{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:8px}
112
- .dsh-my-observability-section-title{font:var(--dsw-font-xs-strong-13);color:var(--dsw-alias-label-primary)}
113
- .dsh-my-observability-diff{max-height:240px;overflow:auto;font:var(--dsw-font-mono-xxs);font-size:11px;line-height:1.5;
114
- color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);
115
- border-radius:6px;padding:8px;white-space:pre-wrap;word-break:break-all}
116
- .dsh-my-observability-form{display:flex;flex-direction:column;gap:6px}
117
- .dsh-my-observability-type{flex:none;width:96px}
118
- .dsh-my-observability-textarea{min-height:52px;resize:vertical;font:var(--dsw-font-xxs-12)}
119
- .dsh-my-observability-feedback{font:var(--dsw-font-xxs-12);word-break:break-all;line-height:1.5}
120
- .dsh-my-observability-feedback-ok{color:var(--dsw-alias-state-success-primary)}
121
- .dsh-my-observability-feedback-error{color:var(--dsw-alias-state-error-primary)}
122
- .dsh-my-observability-issue{display:flex;flex-direction:column;gap:2px;border-radius:6px;padding:6px 8px;font:var(--dsw-font-xxs-12)}
123
- .dsh-my-observability-issue-error{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}
124
- .dsh-my-observability-issue-warning{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent)}
125
- .dsh-my-observability-issue-info{background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 10%, transparent)}
126
- .dsh-my-observability-issue-sev{font:var(--dsw-font-xxxs-strong-11);text-transform:uppercase}
127
- .dsh-my-observability-issue-error .dsh-my-observability-issue-sev{color:var(--dsw-alias-state-error-primary)}
128
- .dsh-my-observability-issue-warning .dsh-my-observability-issue-sev{color:var(--dsw-alias-state-warn-primary)}
129
- .dsh-my-observability-issue-info .dsh-my-observability-issue-sev{color:var(--dsw-alias-state-info-primary)}
130
- .dsh-my-observability-issue-rule{font:var(--dsw-font-mono-xxs);font-size:11px;color:var(--dsw-alias-label-secondary)}
131
- .dsh-my-observability-issue-msg{color:var(--dsw-alias-label-primary);line-height:1.5}
132
- .dsh-my-observability-review-ok{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-state-success-primary)}
133
- .dsh-my-observability-ai{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);line-height:1.5;
134
- border:1px dashed var(--dsw-alias-border-l2);border-radius:6px;padding:6px 8px}
135
- .dsh-my-observability-resource{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px;margin:0 0 8px}
136
- .dsh-my-observability-resource-head{font:var(--dsw-font-xxs-strong-12);color:var(--dsw-alias-label-primary);margin-bottom:6px}
137
- .dsh-my-observability-resource-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 10px}
138
- .dsh-my-observability-resource-metric{display:flex;justify-content:space-between;gap:8px;font:var(--dsw-font-xxs-12)}
139
- .dsh-my-observability-resource-label{color:var(--dsw-alias-label-secondary)}
140
- .dsh-my-observability-resource-value{color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-mono-xxs)}
141
- .dsh-my-observability-resource-alerts{margin-top:6px;display:flex;flex-direction:column;gap:4px}
142
- .dsh-my-observability-resource-alert{font:var(--dsw-font-xxxs-11);border-radius:4px;padding:2px 6px}
143
- .dsh-my-observability-resource-alert-error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent)}
144
- .dsh-my-observability-resource-alert-warn{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent)}
145
- `
146
-
147
- function injectStyles() {
148
- if (typeof document === 'undefined' || typeof document.head === 'undefined') return () => {}
149
- const style = document.createElement('style')
150
- style.setAttribute('data-dsh-my-observability', 'styles')
151
- style.textContent = STYLES
152
- document.head.appendChild(style)
153
- return () => {
154
- if (style.parentNode !== null) style.parentNode.removeChild(style)
155
- }
156
- }