shine-code-submit 1.0.10 → 1.0.12
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/dist/install.cjs +1 -1
- package/package.json +1 -1
- package/src/daemon/claude-scan.ts +128 -0
- package/src/daemon/server.ts +38 -24
- package/src/daemon/token-cache.ts +13 -10
- package/src/daemon/transcript.ts +243 -15
- package/src/daemon/ui-assets.ts +1 -1
- package/ui/.build/app.js +31 -31
- package/ui/components/OverviewModule.tsx +21 -8
- package/ui/components/ReportModule.tsx +5 -5
- package/ui/components/SessionDetail.tsx +2 -2
- package/ui/components/SessionTree.tsx +3 -3
- package/ui/components/StatsModule.tsx +3 -3
- package/ui/components/SummaryView.tsx +3 -3
- package/ui/lib/aggregate.ts +2 -4
- package/ui/lib/util.ts +11 -12
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { useApi } from "../hooks/useApi";
|
|
3
3
|
import { useEvents } from "../hooks/useEvents";
|
|
4
4
|
import { useAllCommits } from "../hooks/useAllCommits";
|
|
5
5
|
import { useApp } from "../state/AppContext";
|
|
6
|
-
import { fmtDateTime, fmtTokens,
|
|
6
|
+
import { fmtDateTime, fmtTokens, rawTotal, shortDir } from "../lib/util";
|
|
7
|
+
import type { ReportResponse } from "../types";
|
|
7
8
|
|
|
8
9
|
interface TimelineItem {
|
|
9
10
|
ts: number;
|
|
@@ -12,14 +13,26 @@ interface TimelineItem {
|
|
|
12
13
|
cwd: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
/** 概览首页:KPI 卡(token
|
|
16
|
+
/** 概览首页:KPI 卡(token 总量来自 /api/report 扫描全部 transcript,= ccusage session 总量)+ 近期活动时间线。 */
|
|
16
17
|
export function OverviewModule() {
|
|
17
18
|
const { token, sessions, stats } = useApp();
|
|
18
19
|
const api = useApi(token);
|
|
19
20
|
const { events } = useEvents(api, null, true);
|
|
20
21
|
const { commits } = useAllCommits(api, sessions, true);
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
// token 总量走 /api/report(扫描所有 transcript,ccusage 口径),不用 hook session 的局部累加
|
|
24
|
+
const [report, setReport] = useState<ReportResponse | null>(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
let cancelled = false;
|
|
27
|
+
api<ReportResponse>("/api/report?since=0")
|
|
28
|
+
.then((d) => !cancelled && setReport(d))
|
|
29
|
+
.catch(() => {});
|
|
30
|
+
return () => {
|
|
31
|
+
cancelled = true;
|
|
32
|
+
};
|
|
33
|
+
}, [api]);
|
|
34
|
+
const tot = report?.totals.tokens ?? null;
|
|
35
|
+
|
|
23
36
|
const recent = useMemo<TimelineItem[]>(() => {
|
|
24
37
|
const es: TimelineItem[] = events.slice(0, 20).map((e) => ({
|
|
25
38
|
ts: e.timestamp,
|
|
@@ -40,16 +53,16 @@ export function OverviewModule() {
|
|
|
40
53
|
<div className="overview-view">
|
|
41
54
|
<div className="kpi-grid">
|
|
42
55
|
<div className="kpi-card">
|
|
43
|
-
<span className="kpi-label">Token
|
|
44
|
-
<b className="kpi-value">{fmtTokens(
|
|
56
|
+
<span className="kpi-label">Token 总量</span>
|
|
57
|
+
<b className="kpi-value">{tot ? fmtTokens(rawTotal(tot)) : "…"}</b>
|
|
45
58
|
</div>
|
|
46
59
|
<div className="kpi-card">
|
|
47
60
|
<span className="kpi-label">Token 输出</span>
|
|
48
|
-
<b className="kpi-value">{fmtTokens(
|
|
61
|
+
<b className="kpi-value">{tot ? fmtTokens(tot.output) : "…"}</b>
|
|
49
62
|
</div>
|
|
50
63
|
<div className="kpi-card">
|
|
51
64
|
<span className="kpi-label">会话数</span>
|
|
52
|
-
<b className="kpi-value">{sessions.length}</b>
|
|
65
|
+
<b className="kpi-value">{report?.totals.sessions ?? sessions.length}</b>
|
|
53
66
|
</div>
|
|
54
67
|
<div className="kpi-card">
|
|
55
68
|
<span className="kpi-label">事件总数</span>
|
|
@@ -7,7 +7,7 @@ import { useApp } from "../state/AppContext";
|
|
|
7
7
|
import { Icon } from "./Icon";
|
|
8
8
|
import { Splitter } from "./Splitter";
|
|
9
9
|
import type { ReportProject, ReportResponse } from "../types";
|
|
10
|
-
import { fmtDateTime, fmtTokens, fmtUsageFull, fmtUsageLabeled,
|
|
10
|
+
import { fmtDateTime, fmtTokens, fmtUsageFull, fmtUsageLabeled, rawTotal, shortDir } from "../lib/util";
|
|
11
11
|
|
|
12
12
|
const PAGE = 20; // 每页 session 数
|
|
13
13
|
|
|
@@ -117,7 +117,7 @@ export function ReportModule() {
|
|
|
117
117
|
>
|
|
118
118
|
<span>{shortDir(p.cwd) || p.cwd}</span>
|
|
119
119
|
<span className="nav-tokens" title={fmtUsageFull(p.totalTokens)}>
|
|
120
|
-
{fmtTokens(
|
|
120
|
+
{fmtTokens(rawTotal(p.totalTokens))}
|
|
121
121
|
</span>
|
|
122
122
|
</li>
|
|
123
123
|
))}
|
|
@@ -147,7 +147,7 @@ export function ReportModule() {
|
|
|
147
147
|
function ProjectDetail({ p }: { p: ReportProject }) {
|
|
148
148
|
const [page, setPage] = useState(1);
|
|
149
149
|
// 过滤掉 0 token 的 session(tokenTotal 为 null 或 真实输入+输出=0)
|
|
150
|
-
const rows = p.sessions.filter((s) => s.tokenTotal &&
|
|
150
|
+
const rows = p.sessions.filter((s) => s.tokenTotal && rawTotal(s.tokenTotal) > 0);
|
|
151
151
|
const pageCount = Math.max(1, Math.ceil(rows.length / PAGE));
|
|
152
152
|
const cur = Math.min(page, pageCount);
|
|
153
153
|
const pageRows = rows.slice((cur - 1) * PAGE, cur * PAGE);
|
|
@@ -198,9 +198,9 @@ function ProjectDetail({ p }: { p: ReportProject }) {
|
|
|
198
198
|
{s.sessionId.slice(0, 8)}
|
|
199
199
|
</td>
|
|
200
200
|
<td>{fmtDateTime(s.lastActive)}</td>
|
|
201
|
-
<td className="rt-num">{fmtTokens(
|
|
201
|
+
<td className="rt-num">{fmtTokens(s.tokenTotal!.input)}</td>
|
|
202
202
|
<td className="rt-num">{fmtTokens(s.tokenTotal!.output)}</td>
|
|
203
|
-
<td className="rt-num">{fmtTokens(
|
|
203
|
+
<td className="rt-num">{fmtTokens(rawTotal(s.tokenTotal!))}</td>
|
|
204
204
|
<td className="rt-num" title={s.linesTotal ? `+${s.linesTotal.added} -${s.linesTotal.deleted} M${s.linesTotal.modified}` : ""}>
|
|
205
205
|
{s.linesTotal ? `+${s.linesTotal.added} -${s.linesTotal.deleted} M${s.linesTotal.modified}` : "-"}
|
|
206
206
|
</td>
|
|
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|
|
2
2
|
import { useApi } from "../hooks/useApi";
|
|
3
3
|
import { useConversation } from "../hooks/useConversation";
|
|
4
4
|
import { useApp } from "../state/AppContext";
|
|
5
|
-
import { fmtUsageFull, fmtUsageLabeled,
|
|
5
|
+
import { fmtUsageFull, fmtUsageLabeled, rawTotal, shortDir } from "../lib/util";
|
|
6
6
|
import { Conversation } from "./Conversation";
|
|
7
7
|
|
|
8
8
|
/** 会话详情(右侧):顶部摘要(sid/cwd/token)+ 该会话对话。
|
|
@@ -27,7 +27,7 @@ export function SessionDetail({ sessionId }: { sessionId: string }) {
|
|
|
27
27
|
{shortDir(session.cwd)}
|
|
28
28
|
</span>
|
|
29
29
|
)}
|
|
30
|
-
{tokenTotal && (
|
|
30
|
+
{tokenTotal && rawTotal(tokenTotal) > 0 && (
|
|
31
31
|
<span className="detail-token" title={fmtUsageFull(tokenTotal)}>
|
|
32
32
|
{fmtUsageLabeled(tokenTotal)}
|
|
33
33
|
</span>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
2
|
import { useApp } from "../state/AppContext";
|
|
3
|
-
import { fmtDateTime, fmtTokens, fmtUsageFull,
|
|
3
|
+
import { fmtDateTime, fmtTokens, fmtUsageFull, rawTotal } from "../lib/util";
|
|
4
4
|
import type { SessionSummary } from "../types";
|
|
5
5
|
|
|
6
6
|
/** 会话树(按 cwd 分组、可折叠、token 角标、时间点)。纯展示 + onSelect 回调。
|
|
@@ -72,9 +72,9 @@ export function SessionTree({
|
|
|
72
72
|
<div className="sess-row">
|
|
73
73
|
<span className="sess-time">{fmtDateTime(s.lastActive)}</span>
|
|
74
74
|
<span className="sess-sid">{s.sessionId.slice(0, 8)}</span>
|
|
75
|
-
{s.tokenTotal && (
|
|
75
|
+
{s.tokenTotal && rawTotal(s.tokenTotal) > 0 && (
|
|
76
76
|
<span className="sess-tokens" title={fmtUsageFull(s.tokenTotal)}>
|
|
77
|
-
{fmtTokens(
|
|
77
|
+
{fmtTokens(rawTotal(s.tokenTotal))}
|
|
78
78
|
</span>
|
|
79
79
|
)}
|
|
80
80
|
</div>
|
|
@@ -3,7 +3,7 @@ import { useApi } from "../hooks/useApi";
|
|
|
3
3
|
import { useAllCommits } from "../hooks/useAllCommits";
|
|
4
4
|
import { useApp } from "../state/AppContext";
|
|
5
5
|
import { aggregateCommitsByProject, aggregateTokenByProject, bucketByDay } from "../lib/aggregate";
|
|
6
|
-
import { fmtUsage,
|
|
6
|
+
import { fmtUsage, rawTotal, shortDir } from "../lib/util";
|
|
7
7
|
|
|
8
8
|
type StatTab = "token" | "commits" | "day";
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@ export function StatsModule() {
|
|
|
18
18
|
const projCommits = useMemo(() => aggregateCommitsByProject(commits), [commits]);
|
|
19
19
|
const commitsByDay = useMemo(() => bucketByDay(commits.map((c) => c.time), 7), [commits]);
|
|
20
20
|
|
|
21
|
-
const maxToken = Math.max(1, ...projTokens.map((r) =>
|
|
21
|
+
const maxToken = Math.max(1, ...projTokens.map((r) => rawTotal(r.token)));
|
|
22
22
|
const maxCommitCount = Math.max(1, ...projCommits.map((r) => r.count));
|
|
23
23
|
const maxDayCount = Math.max(1, ...commitsByDay.map((b) => b.count));
|
|
24
24
|
|
|
@@ -60,7 +60,7 @@ export function StatsModule() {
|
|
|
60
60
|
) : (
|
|
61
61
|
<div className="bar-list">
|
|
62
62
|
{projTokens.map((r) => {
|
|
63
|
-
const v =
|
|
63
|
+
const v = rawTotal(r.token);
|
|
64
64
|
return (
|
|
65
65
|
<div className="bar-row" key={r.cwd}>
|
|
66
66
|
<span className="bar-label" title={r.cwd}>
|
|
@@ -2,7 +2,7 @@ import { useMemo } from "react";
|
|
|
2
2
|
import { useApi } from "../hooks/useApi";
|
|
3
3
|
import { useAllCommits } from "../hooks/useAllCommits";
|
|
4
4
|
import { useApp } from "../state/AppContext";
|
|
5
|
-
import { fmtDateTime, fmtUsage, fmtUsageFull,
|
|
5
|
+
import { fmtDateTime, fmtUsage, fmtUsageFull, rawTotal, shortDir, sumTokenUsage } from "../lib/util";
|
|
6
6
|
|
|
7
7
|
/** 汇总视图(system 模块临时占位,Step 5 替换为 SystemModule):
|
|
8
8
|
* Token 按会话 + 代码提交按时间。提交拉取改用 useAllCommits(与 Overview/Stats 共用)。 */
|
|
@@ -13,7 +13,7 @@ export function SummaryView() {
|
|
|
13
13
|
const tokenRows = useMemo(
|
|
14
14
|
() =>
|
|
15
15
|
sessions
|
|
16
|
-
.filter((s) => s.tokenTotal && (
|
|
16
|
+
.filter((s) => s.tokenTotal && rawTotal(s.tokenTotal) > 0)
|
|
17
17
|
.slice()
|
|
18
18
|
.sort((a, b) => b.lastActive - a.lastActive),
|
|
19
19
|
[sessions],
|
|
@@ -24,7 +24,7 @@ export function SummaryView() {
|
|
|
24
24
|
const commitCount = commits.length;
|
|
25
25
|
const added = commits.reduce((n, c) => n + (c.added || 0), 0);
|
|
26
26
|
const deleted = commits.reduce((n, c) => n + (c.deleted || 0), 0);
|
|
27
|
-
const hasTokens =
|
|
27
|
+
const hasTokens = rawTotal(tokenSum.total) > 0;
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
30
|
<div id="summary-view" className="summary-view">
|
package/ui/lib/aggregate.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 纯聚合函数(统计模块用):按项目/按天分组,无副作用。
|
|
2
2
|
import type { SessionSummary, TokenUsage } from "../types";
|
|
3
|
-
import {
|
|
3
|
+
import { rawTotal } from "./util";
|
|
4
4
|
|
|
5
5
|
export interface ProjTokenRow {
|
|
6
6
|
cwd: string;
|
|
@@ -28,9 +28,7 @@ export function aggregateTokenByProject(sessions: SessionSummary[]): ProjTokenRo
|
|
|
28
28
|
}
|
|
29
29
|
m.set(s.cwd, r);
|
|
30
30
|
}
|
|
31
|
-
return [...m.values()].sort(
|
|
32
|
-
(a, b) => realInput(b.token) + b.token.output - (realInput(a.token) + a.token.output),
|
|
33
|
-
);
|
|
31
|
+
return [...m.values()].sort((a, b) => rawTotal(b.token) - rawTotal(a.token));
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
export interface ProjCommitRow {
|
package/ui/lib/util.ts
CHANGED
|
@@ -50,35 +50,34 @@ export function fmtTokens(n: number): string {
|
|
|
50
50
|
return trimZero((n / 1_000_000_000_000).toFixed(2)) + "T";
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
export function realInput(u?: TokenUsage | null): number {
|
|
53
|
+
/** 原始 token 总量 = input + output + cacheCreation + cacheRead(= ccusage totalTokens,四字段原始全量,不加权)。 */
|
|
54
|
+
export function rawTotal(u?: TokenUsage | null): number {
|
|
56
55
|
if (!u) return 0;
|
|
57
|
-
return
|
|
56
|
+
return u.input + u.output + u.cacheCreation + u.cacheRead;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
/** token
|
|
59
|
+
/** token 用量简写:↑输入 ↓输出(原始值)。无值返回空串。 */
|
|
61
60
|
export function fmtUsage(u?: TokenUsage | null): string {
|
|
62
61
|
if (!u) return "";
|
|
63
|
-
return `↑${fmtTokens(
|
|
62
|
+
return `↑${fmtTokens(u.input)} ↓${fmtTokens(u.output)}`;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
/** token
|
|
65
|
+
/** token 用量三段式:↑输入 ↓输出 · 总数(原始四字段和)。报表/会话导航与详情用。 */
|
|
67
66
|
export function fmtUsageTotal(u?: TokenUsage | null): string {
|
|
68
67
|
if (!u) return "";
|
|
69
|
-
return `↑${fmtTokens(
|
|
68
|
+
return `↑${fmtTokens(u.input)} ↓${fmtTokens(u.output)} · ${fmtTokens(rawTotal(u))}`;
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
/** 完整 token 用量,用于 title
|
|
71
|
+
/** 完整 token 用量,用于 title 提示:总数 + 输入/输出 + 缓存写/缓存读(均原始值)。 */
|
|
73
72
|
export function fmtUsageFull(u?: TokenUsage | null): string {
|
|
74
73
|
if (!u) return "";
|
|
75
|
-
return
|
|
74
|
+
return `总数 ${rawTotal(u)} · 输入 ${u.input} · 输出 ${u.output} · 缓存写 ${u.cacheCreation} · 缓存读 ${u.cacheRead}`;
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
/** token 用量带标签:输入 X · 输出 Y · 总数 Z(
|
|
77
|
+
/** token 用量带标签:输入 X · 输出 Y · 总数 Z(原始四字段和)。会话详情/报表标题用。 */
|
|
79
78
|
export function fmtUsageLabeled(u?: TokenUsage | null): string {
|
|
80
79
|
if (!u) return "";
|
|
81
|
-
return `输入 ${fmtTokens(
|
|
80
|
+
return `输入 ${fmtTokens(u.input)} · 输出 ${fmtTokens(u.output)} · 总数 ${fmtTokens(rawTotal(u))}`;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
function trimZero(s: string): string {
|