dsh-local-telemetry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/index.html ADDED
@@ -0,0 +1,87 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>dsh-local-telemetry · 本地遥测</title>
7
+ <link rel="stylesheet" href="/style.css" />
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header class="header">
12
+ <div class="brand">
13
+ <div class="brand-mark">T</div>
14
+ <div>
15
+ <h1>DSH Telemetry</h1>
16
+ <div class="sub">Harness 运行遥测 · 本地只读视图</div>
17
+ </div>
18
+ </div>
19
+ <div class="ranges" id="ranges">
20
+ <button data-range="1h">1h</button>
21
+ <button data-range="24h" class="active">24h</button>
22
+ <button data-range="7d">7d</button>
23
+ <button data-range="30d">30d</button>
24
+ <button data-range="all">全部</button>
25
+ </div>
26
+ <button class="refresh" id="refresh">↻ 刷新</button>
27
+ </header>
28
+
29
+ <div class="chips" id="chips"></div>
30
+
31
+ <section class="kpi-grid" id="kpis"></section>
32
+
33
+ <div class="grid-2">
34
+ <section class="panel">
35
+ <h2>Token / 请求趋势(按日)</h2>
36
+ <div class="desc">输入 + 输出 Token 的面积图;柱高为当日请求数。悬停查看明细。</div>
37
+ <div class="trend-wrap" id="trend"></div>
38
+ </section>
39
+ <section class="panel">
40
+ <h2>错误分类</h2>
41
+ <div class="desc">窗口内失败与取消事件按 error.kind 分组。</div>
42
+ <div id="errors"></div>
43
+ </section>
44
+ </div>
45
+
46
+ <section class="panel">
47
+ <h2>模型比较</h2>
48
+ <div class="desc">按 provider / model / request_type 聚合;分位数为 nearest-rank;n 为样本数。</div>
49
+ <div id="models"></div>
50
+ </section>
51
+
52
+ <div class="grid-2">
53
+ <section class="panel">
54
+ <h2>工具耗时</h2>
55
+ <div class="desc">按工具名聚合;循环调用提示见下方徽标。</div>
56
+ <div id="tools"></div>
57
+ </section>
58
+ <section class="panel">
59
+ <h2>插件 Hook</h2>
60
+ <div class="desc">按插件与 hook 聚合的耗时与错误。</div>
61
+ <div id="plugins"></div>
62
+ </section>
63
+ </div>
64
+
65
+ <div class="grid-2">
66
+ <section class="panel">
67
+ <h2>请求时间线</h2>
68
+ <div class="desc">最近请求;点击查看 trace/span 树。默认不含任何内容字段。</div>
69
+ <div class="req-list" id="requests"></div>
70
+ </section>
71
+ <section class="panel" id="trace-panel">
72
+ <h2>Trace 详情 <span class="desc" id="trace-title" style="display:inline"></span></h2>
73
+ <div class="desc">span 树 + 事件时间线(白名单字段)。</div>
74
+ <div class="trace" id="trace"><div class="empty">选择左侧请求查看 trace 树。</div></div>
75
+ </section>
76
+ </div>
77
+
78
+ <footer class="footer">
79
+ <span class="privacy">本地存储 · 只读接口 · 默认不采集 prompt / response / 文件内容 / 密钥</span>
80
+ <span>dsh-local-telemetry v0.1.0 · schema 1.0 · <a href="/api/report.md" id="report-link">Markdown 报告</a></span>
81
+ </footer>
82
+ </div>
83
+
84
+ <div id="trend-tip"></div>
85
+ <script src="/app.js"></script>
86
+ </body>
87
+ </html>
package/web/style.css ADDED
@@ -0,0 +1,158 @@
1
+ /* dsh-local-telemetry Web UI — 本地只读仪表盘。
2
+ 设计复刻自 21st.dev「Advanced Stats」仪表盘模式:
3
+ 暗色底、KPI 卡片、渐变面积图、极简柱状趋势、时间线。零外部依赖。 */
4
+
5
+ :root {
6
+ --bg: #09090b;
7
+ --card: #101013;
8
+ --card-2: #141419;
9
+ --border: #1f1f24;
10
+ --text: #e4e4e7;
11
+ --muted: #8b8b94;
12
+ --accent-a: #8b5cf6;
13
+ --accent-b: #06b6d4;
14
+ --success: #34d399;
15
+ --error: #f87171;
16
+ --warn: #fbbf24;
17
+ --cancelled: #94a3b8;
18
+ --radius: 14px;
19
+ --font: "Segoe UI", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
20
+ --mono: "Cascadia Code", "JetBrains Mono", Consolas, monospace;
21
+ }
22
+
23
+ * { box-sizing: border-box; }
24
+ html, body { margin: 0; padding: 0; }
25
+ body {
26
+ background: radial-gradient(1200px 500px at 70% -10%, rgba(139, 92, 246, 0.08), transparent),
27
+ radial-gradient(900px 420px at 10% -10%, rgba(6, 182, 212, 0.06), transparent),
28
+ var(--bg);
29
+ color: var(--text);
30
+ font-family: var(--font);
31
+ font-size: 14px;
32
+ line-height: 1.5;
33
+ min-height: 100vh;
34
+ }
35
+
36
+ .container { max-width: 1180px; margin: 0 auto; padding: 28px 22px 60px; }
37
+
38
+ /* ---------- header ---------- */
39
+ .header { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-bottom: 22px; }
40
+ .brand { display: flex; align-items: center; gap: 12px; margin-right: auto; }
41
+ .brand-mark {
42
+ width: 38px; height: 38px; border-radius: 11px;
43
+ background: linear-gradient(135deg, var(--accent-a), var(--accent-b));
44
+ display: flex; align-items: center; justify-content: center;
45
+ font-weight: 700; color: #0b0b0f; font-size: 17px;
46
+ }
47
+ .brand h1 { font-size: 18px; margin: 0; font-weight: 650; letter-spacing: 0.2px; }
48
+ .brand .sub { color: var(--muted); font-size: 12px; margin-top: 1px; }
49
+ .ranges { display: flex; gap: 4px; background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 3px; }
50
+ .ranges button {
51
+ border: 0; background: transparent; color: var(--muted); font: inherit; font-size: 12.5px;
52
+ padding: 5px 11px; border-radius: 8px; cursor: pointer; transition: all 0.15s;
53
+ }
54
+ .ranges button:hover { color: var(--text); }
55
+ .ranges button.active { background: rgba(139, 92, 246, 0.16); color: #c4b5fd; }
56
+ .refresh {
57
+ border: 1px solid var(--border); background: var(--card); color: var(--text);
58
+ font: inherit; font-size: 12.5px; padding: 6px 13px; border-radius: 10px; cursor: pointer;
59
+ }
60
+ .refresh:hover { border-color: rgba(139, 92, 246, 0.5); }
61
+
62
+ .chips { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 20px; }
63
+ .chip {
64
+ font-size: 11.5px; color: var(--muted); background: var(--card);
65
+ border: 1px solid var(--border); padding: 3px 10px; border-radius: 999px;
66
+ }
67
+ .chip b { color: var(--text); font-weight: 600; }
68
+ .chip.ok b { color: var(--success); }
69
+ .chip.bad b { color: var(--error); }
70
+
71
+ /* ---------- kpi cards ---------- */
72
+ .kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; margin-bottom: 14px; }
73
+ .kpi {
74
+ background: linear-gradient(180deg, var(--card-2), var(--card));
75
+ border: 1px solid var(--border); border-radius: var(--radius); padding: 15px 16px 13px;
76
+ position: relative; overflow: hidden;
77
+ }
78
+ .kpi::after {
79
+ content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
80
+ background: linear-gradient(90deg, var(--accent-a), var(--accent-b));
81
+ opacity: 0.65;
82
+ }
83
+ .kpi .label { color: var(--muted); font-size: 12px; }
84
+ .kpi .value { font-size: 23px; font-weight: 700; margin-top: 3px; letter-spacing: -0.3px; font-variant-numeric: tabular-nums; }
85
+ .kpi .value small { font-size: 13px; color: var(--muted); font-weight: 500; }
86
+ .kpi .note { color: var(--muted); font-size: 11.5px; margin-top: 3px; }
87
+
88
+ /* ---------- panels & charts ---------- */
89
+ .panel {
90
+ background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
91
+ padding: 17px 18px; margin-bottom: 14px;
92
+ }
93
+ .panel h2 { font-size: 13.5px; margin: 0 0 3px; font-weight: 650; color: #d4d4d8; }
94
+ .panel .desc { color: var(--muted); font-size: 11.5px; margin-bottom: 13px; }
95
+ .grid-2 { display: grid; grid-template-columns: 1.35fr 1fr; gap: 14px; }
96
+ @media (max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }
97
+
98
+ .trend-wrap svg, .spark svg { display: block; width: 100%; }
99
+ .bar-row { transition: opacity 0.15s; }
100
+ .bar-row:hover { opacity: 0.85; }
101
+ #trend-tip {
102
+ position: fixed; pointer-events: none; z-index: 50; display: none;
103
+ background: #17171c; border: 1px solid #2c2c33; border-radius: 9px;
104
+ padding: 8px 11px; font-size: 12px; color: var(--text); box-shadow: 0 6px 24px rgba(0,0,0,0.5);
105
+ }
106
+ #trend-tip .t { color: var(--muted); margin-bottom: 3px; }
107
+
108
+ /* ---------- tables ---------- */
109
+ table { width: 100%; border-collapse: collapse; font-size: 12.8px; }
110
+ th {
111
+ text-align: left; color: var(--muted); font-weight: 500; font-size: 11.5px;
112
+ padding: 7px 9px; border-bottom: 1px solid var(--border); white-space: nowrap;
113
+ }
114
+ td { padding: 8px 9px; border-bottom: 1px solid rgba(31, 31, 36, 0.6); font-variant-numeric: tabular-nums; white-space: nowrap; }
115
+ tr:last-child td { border-bottom: 0; }
116
+ td.name { font-weight: 600; color: #e8e8ec; }
117
+ td.num, th.num { text-align: right; }
118
+ .lat-bar { position: relative; background: rgba(139, 92, 246, 0.10); border-radius: 5px; height: 6px; min-width: 70px; overflow: hidden; }
119
+ .lat-bar i { position: absolute; inset: 0 auto 0 0; background: linear-gradient(90deg, var(--accent-a), var(--accent-b)); border-radius: 5px; }
120
+ .empty { color: var(--muted); font-size: 12.5px; padding: 14px 0; text-align: center; }
121
+
122
+ /* ---------- requests timeline ---------- */
123
+ .req-list { display: flex; flex-direction: column; }
124
+ .req {
125
+ display: flex; align-items: center; gap: 11px; padding: 8px 6px;
126
+ border-bottom: 1px solid rgba(31, 31, 36, 0.6); cursor: pointer; border-radius: 8px;
127
+ }
128
+ .req:hover { background: rgba(139, 92, 246, 0.06); }
129
+ .req.active { background: rgba(139, 92, 246, 0.12); }
130
+ .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
131
+ .dot.success { background: var(--success); box-shadow: 0 0 8px rgba(52, 211, 153, 0.5); }
132
+ .dot.failed { background: var(--error); box-shadow: 0 0 8px rgba(248, 113, 113, 0.5); }
133
+ .dot.cancelled { background: var(--cancelled); }
134
+ .dot.incomplete { background: var(--warn); }
135
+ .req .t { color: var(--muted); font-size: 11.5px; width: 148px; flex: none; font-family: var(--mono); }
136
+ .req .dur { width: 74px; text-align: right; font-variant-numeric: tabular-nums; flex: none; }
137
+ .req .model { color: #d4d4d8; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
138
+ .req .tok { color: var(--muted); font-size: 11.5px; flex: none; }
139
+
140
+ /* ---------- trace tree ---------- */
141
+ .trace { font-family: var(--mono); font-size: 12px; }
142
+ .span-row { display: flex; align-items: baseline; gap: 9px; padding: 3.5px 4px; border-radius: 6px; }
143
+ .span-row:hover { background: rgba(139, 92, 246, 0.07); }
144
+ .span-row .ev { color: #c4b5fd; }
145
+ .span-row .who { color: var(--text); }
146
+ .span-row .st { font-size: 10.5px; padding: 0 6px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); }
147
+ .span-row .st.failed { color: var(--error); border-color: rgba(248, 113, 113, 0.4); }
148
+ .span-row .st.success { color: var(--success); border-color: rgba(52, 211, 153, 0.4); }
149
+ .span-row .st.cancelled { color: var(--cancelled); }
150
+ .span-row .ms { margin-left: auto; color: var(--muted); }
151
+ .span-children { border-left: 1px dashed rgba(139, 92, 246, 0.25); margin-left: 10px; padding-left: 12px; }
152
+
153
+ /* ---------- footer ---------- */
154
+ .footer { color: var(--muted); font-size: 11.5px; display: flex; flex-wrap: wrap; gap: 8px; justify-content: space-between; margin-top: 26px; }
155
+ .footer .privacy { color: #6d6d78; }
156
+ a { color: #a78bfa; text-decoration: none; }
157
+ a:hover { text-decoration: underline; }
158
+ .hidden { display: none !important; }