shine-code-submit 0.2.10 → 0.2.11
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/.claude-plugin/plugin.json +1 -1
- package/dist/install.cjs +1 -1
- package/package.json +1 -1
- package/src/daemon/git.ts +11 -0
- package/src/daemon/server.ts +117 -2
- package/src/daemon/ui-assets.ts +1 -1
- package/src/shared/types.ts +50 -0
- package/ui/.build/app.js +50 -50
- package/ui/components/App.tsx +3 -0
- package/ui/components/ReportModule.tsx +201 -0
- package/ui/components/SideNav.tsx +1 -0
- package/ui/types.ts +7 -1
package/ui/components/App.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { EventsModule } from "./EventsModule";
|
|
|
6
6
|
import { CommitsModule } from "./CommitsModule";
|
|
7
7
|
import { OverviewModule } from "./OverviewModule";
|
|
8
8
|
import { StatsModule } from "./StatsModule";
|
|
9
|
+
import { ReportModule } from "./ReportModule";
|
|
9
10
|
import { SystemModule } from "./SystemModule";
|
|
10
11
|
|
|
11
12
|
/** 模块路由:按 activeModule 渲染对应模块。 */
|
|
@@ -22,6 +23,8 @@ function ModuleRouter() {
|
|
|
22
23
|
// return <CommitsModule />; // 暂时屏蔽
|
|
23
24
|
case "stats":
|
|
24
25
|
return <StatsModule />;
|
|
26
|
+
case "report":
|
|
27
|
+
return <ReportModule />;
|
|
25
28
|
case "system":
|
|
26
29
|
return <SystemModule />;
|
|
27
30
|
default:
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// 「数据上报」模块:跨项目聚合(版本/git 用户/会话数+每会话 token/提交次数+行数+时间)。
|
|
2
|
+
// 数据来自 GET /api/report?since=<ms>(0=全部)。后期要真·上报服务器时,把底部占位按钮接上即可。
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { useApi } from "../hooks/useApi";
|
|
5
|
+
import { useApp } from "../state/AppContext";
|
|
6
|
+
import type { ReportProject, ReportResponse } from "../types";
|
|
7
|
+
import { fmtDateTime, fmtTokens, fmtUsage, fmtUsageFull, shortDir } from "../lib/util";
|
|
8
|
+
|
|
9
|
+
type Range = "all" | "7d" | "30d";
|
|
10
|
+
|
|
11
|
+
const RANGES: Array<{ id: Range; label: string; days: number }> = [
|
|
12
|
+
{ id: "all", label: "全部", days: 0 },
|
|
13
|
+
{ id: "7d", label: "近 7 天", days: 7 },
|
|
14
|
+
{ id: "30d", label: "近 30 天", days: 30 },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function rangeSince(r: Range): number {
|
|
18
|
+
const days = RANGES.find((x) => x.id === r)!.days;
|
|
19
|
+
return days > 0 ? Date.now() - days * 86400000 : 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ReportModule() {
|
|
23
|
+
const { token } = useApp();
|
|
24
|
+
const api = useApi(token);
|
|
25
|
+
const [range, setRange] = useState<Range>("all");
|
|
26
|
+
const [data, setData] = useState<ReportResponse | null>(null);
|
|
27
|
+
const [err, setErr] = useState<string | null>(null);
|
|
28
|
+
const [openCwd, setOpenCwd] = useState<string | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
setErr(null);
|
|
33
|
+
setData(null);
|
|
34
|
+
api<ReportResponse>(`/api/report?since=${rangeSince(range)}`)
|
|
35
|
+
.then((d) => !cancelled && setData(d))
|
|
36
|
+
.catch((e) => !cancelled && setErr(String(e)));
|
|
37
|
+
return () => {
|
|
38
|
+
cancelled = true;
|
|
39
|
+
};
|
|
40
|
+
}, [api, range]);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="stats-view">
|
|
44
|
+
<div className="toolbar">
|
|
45
|
+
<div className="tabs">
|
|
46
|
+
{RANGES.map((r) => (
|
|
47
|
+
<button
|
|
48
|
+
key={r.id}
|
|
49
|
+
type="button"
|
|
50
|
+
className={`tab${range === r.id ? " active" : ""}`}
|
|
51
|
+
onClick={() => setRange(r.id)}
|
|
52
|
+
>
|
|
53
|
+
{r.label}
|
|
54
|
+
</button>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
{/* 占位:后期接「上报到服务器」(POST 本报告到远端)。现在禁用,提示敬请期待。 */}
|
|
58
|
+
<button type="button" className="tab" disabled title="后期接入:把本报告上报到服务器(占位)">
|
|
59
|
+
☁ 上报到服务器(敬请期待)
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="stats-body">
|
|
64
|
+
{err && <div className="sum-empty">加载失败:{err}</div>}
|
|
65
|
+
{!err && !data && <div className="sum-empty">加载中…</div>}
|
|
66
|
+
{data && (
|
|
67
|
+
<>
|
|
68
|
+
<section className="sum-section">
|
|
69
|
+
<div className="sum-head">
|
|
70
|
+
<h3>汇总</h3>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="bar-list">
|
|
73
|
+
<div className="bar-row">
|
|
74
|
+
<span className="bar-label">软件版本</span>
|
|
75
|
+
<span className="bar-val">{data.version}</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="bar-row">
|
|
78
|
+
<span className="bar-label">git 用户</span>
|
|
79
|
+
<span className="bar-val">{data.gitUser ?? "—"}</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="bar-row">
|
|
82
|
+
<span className="bar-label">项目数</span>
|
|
83
|
+
<span className="bar-val">{data.totals.projects}</span>
|
|
84
|
+
</div>
|
|
85
|
+
<div className="bar-row">
|
|
86
|
+
<span className="bar-label">会话总数</span>
|
|
87
|
+
<span className="bar-val">{data.totals.sessions}</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="bar-row">
|
|
90
|
+
<span className="bar-label">token 总量</span>
|
|
91
|
+
<span className="bar-val" title={fmtUsageFull(data.totals.tokens)}>
|
|
92
|
+
{fmtUsage(data.totals.tokens) || "—"}
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="bar-row">
|
|
96
|
+
<span className="bar-label">提交总数</span>
|
|
97
|
+
<span className="bar-val">
|
|
98
|
+
{data.totals.commitCount} · +{fmtTokens(data.totals.added)}/-{fmtTokens(data.totals.deleted)}
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
{data.projects.length === 0 && <div className="sum-empty">窗口内无项目数据</div>}
|
|
105
|
+
{data.projects.map((p) => (
|
|
106
|
+
<ReportCard
|
|
107
|
+
key={p.cwd}
|
|
108
|
+
p={p}
|
|
109
|
+
open={openCwd === p.cwd}
|
|
110
|
+
onToggle={() => setOpenCwd(openCwd === p.cwd ? null : p.cwd)}
|
|
111
|
+
/>
|
|
112
|
+
))}
|
|
113
|
+
</>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function ReportCard({ p, open, onToggle }: { p: ReportProject; open: boolean; onToggle: () => void }) {
|
|
121
|
+
return (
|
|
122
|
+
<section className="sum-section">
|
|
123
|
+
<div className="sum-head">
|
|
124
|
+
<h3 title={p.cwd}>{shortDir(p.cwd) || p.cwd}</h3>
|
|
125
|
+
<span style={{ marginLeft: "auto", opacity: 0.65, fontSize: "var(--fs-xs)" }}>
|
|
126
|
+
{p.gitUser ? `@${p.gitUser}` : "@—"}
|
|
127
|
+
{p.gitError ? " · git 不可用" : ""}
|
|
128
|
+
</span>
|
|
129
|
+
<button type="button" className="tab" onClick={onToggle} style={{ marginLeft: "0.5rem" }}>
|
|
130
|
+
{open ? "收起" : "展开"}
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="bar-list">
|
|
134
|
+
<div className="bar-row">
|
|
135
|
+
<span className="bar-label">会话</span>
|
|
136
|
+
<span className="bar-val">{p.sessionCount}</span>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="bar-row">
|
|
139
|
+
<span className="bar-label">token</span>
|
|
140
|
+
<span className="bar-val" title={fmtUsageFull(p.totalTokens)}>
|
|
141
|
+
{fmtUsage(p.totalTokens) || "—"}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div className="bar-row">
|
|
145
|
+
<span className="bar-label">提交</span>
|
|
146
|
+
<span className="bar-val">
|
|
147
|
+
{p.commits.count} · +{fmtTokens(p.commits.added)}/-{fmtTokens(p.commits.deleted)}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="bar-row">
|
|
151
|
+
<span className="bar-label">最近提交</span>
|
|
152
|
+
<span className="bar-val">{p.commits.lastTime ? fmtDateTime(p.commits.lastTime) : "—"}</span>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{open && (
|
|
157
|
+
<>
|
|
158
|
+
<div className="sum-head" style={{ marginTop: "0.8rem" }}>
|
|
159
|
+
<h3>会话 token 明细 · {p.sessions.length}</h3>
|
|
160
|
+
</div>
|
|
161
|
+
{p.sessions.length === 0 ? (
|
|
162
|
+
<div className="sum-empty">无会话</div>
|
|
163
|
+
) : (
|
|
164
|
+
<div className="bar-list">
|
|
165
|
+
{p.sessions.map((s) => (
|
|
166
|
+
<div className="bar-row" key={s.sessionId}>
|
|
167
|
+
<span className="bar-label" title={s.sessionId}>
|
|
168
|
+
{s.sessionId.slice(0, 8)} · {fmtDateTime(s.lastActive)}
|
|
169
|
+
</span>
|
|
170
|
+
<span className="bar-val" title={fmtUsageFull(s.tokenTotal)}>
|
|
171
|
+
{fmtUsage(s.tokenTotal) || "—"}
|
|
172
|
+
</span>
|
|
173
|
+
</div>
|
|
174
|
+
))}
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
<div className="sum-head" style={{ marginTop: "0.8rem" }}>
|
|
179
|
+
<h3>最近提交 · {p.recentCommits.length}</h3>
|
|
180
|
+
</div>
|
|
181
|
+
{p.recentCommits.length === 0 ? (
|
|
182
|
+
<div className="sum-empty">无提交</div>
|
|
183
|
+
) : (
|
|
184
|
+
<div className="bar-list">
|
|
185
|
+
{p.recentCommits.map((c) => (
|
|
186
|
+
<div className="bar-row" key={c.hash}>
|
|
187
|
+
<span className="bar-label" title={c.subject}>
|
|
188
|
+
{(c.subject || c.hash).slice(0, 40)}
|
|
189
|
+
</span>
|
|
190
|
+
<span className="bar-val">
|
|
191
|
+
{fmtDateTime(c.time)} · +{c.added}/-{c.deleted}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</>
|
|
198
|
+
)}
|
|
199
|
+
</section>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -9,6 +9,7 @@ const ITEMS: Array<{ id: ModuleId; label: string; icon: IconName }> = [
|
|
|
9
9
|
// { id: "events", label: "事件", icon: "activity" }, // 暂时屏蔽,恢复取消注释即可
|
|
10
10
|
// { id: "commits", label: "提交", icon: "git" }, // 暂时屏蔽,恢复取消注释即可
|
|
11
11
|
{ id: "stats", label: "统计", icon: "chart" },
|
|
12
|
+
{ id: "report", label: "数据上报", icon: "log" },
|
|
12
13
|
{ id: "system", label: "系统", icon: "server" },
|
|
13
14
|
];
|
|
14
15
|
|
package/ui/types.ts
CHANGED
|
@@ -5,6 +5,9 @@ import type {
|
|
|
5
5
|
CommitsResponse,
|
|
6
6
|
EventsResponse,
|
|
7
7
|
HookEvent,
|
|
8
|
+
ReportProject,
|
|
9
|
+
ReportResponse,
|
|
10
|
+
ReportSession,
|
|
8
11
|
SessionSummary,
|
|
9
12
|
StatsResponse,
|
|
10
13
|
TokenUsage,
|
|
@@ -18,6 +21,9 @@ export type {
|
|
|
18
21
|
CommitsResponse,
|
|
19
22
|
EventsResponse,
|
|
20
23
|
HookEvent,
|
|
24
|
+
ReportProject,
|
|
25
|
+
ReportResponse,
|
|
26
|
+
ReportSession,
|
|
21
27
|
SessionSummary,
|
|
22
28
|
StatsResponse,
|
|
23
29
|
TokenUsage,
|
|
@@ -43,4 +49,4 @@ export type Payload = Record<string, unknown>;
|
|
|
43
49
|
export type ViewMode = "events" | "conversation" | "commits" | "summary";
|
|
44
50
|
|
|
45
51
|
/** 左侧导航模块(渐进重构:Step 1 起与 viewMode 并存,selectModule 经映射驱动 viewMode)。 */
|
|
46
|
-
export type ModuleId = "overview" | "sessions" | "events" | "commits" | "stats" | "system";
|
|
52
|
+
export type ModuleId = "overview" | "sessions" | "events" | "commits" | "stats" | "report" | "system";
|