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.
@@ -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, realInput, shortDir, sumTokenUsage } from "../lib/util";
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
- const tokenSum = useMemo(() => sumTokenUsage(sessions.map((s) => s.tokenTotal)), [sessions]);
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 输入</span>
44
- <b className="kpi-value">{fmtTokens(realInput(tokenSum.total))}</b>
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(tokenSum.total.output)}</b>
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, realInput, shortDir } from "../lib/util";
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(realInput(p.totalTokens) + p.totalTokens.output)}
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 && realInput(s.tokenTotal) + s.tokenTotal.output > 0);
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(realInput(s.tokenTotal!))}</td>
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(realInput(s.tokenTotal!) + s.tokenTotal!.output)}</td>
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, realInput, shortDir } from "../lib/util";
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 && (realInput(tokenTotal) > 0 || tokenTotal.output > 0) && (
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, realInput } from "../lib/util";
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 && (realInput(s.tokenTotal) > 0 || s.tokenTotal.output > 0) && (
75
+ {s.tokenTotal && rawTotal(s.tokenTotal) > 0 && (
76
76
  <span className="sess-tokens" title={fmtUsageFull(s.tokenTotal)}>
77
- {fmtTokens(realInput(s.tokenTotal) + s.tokenTotal.output)}
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, realInput, shortDir } from "../lib/util";
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) => realInput(r.token) + r.token.output));
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 = realInput(r.token) + r.token.output;
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, realInput, shortDir, sumTokenUsage } from "../lib/util";
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 && (realInput(s.tokenTotal) > 0 || s.tokenTotal.output > 0))
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 = realInput(tokenSum.total) > 0 || tokenSum.total.output > 0;
27
+ const hasTokens = rawTotal(tokenSum.total) > 0;
28
28
 
29
29
  return (
30
30
  <div id="summary-view" className="summary-view">
@@ -1,6 +1,6 @@
1
1
  // 纯聚合函数(统计模块用):按项目/按天分组,无副作用。
2
2
  import type { SessionSummary, TokenUsage } from "../types";
3
- import { realInput } from "./util";
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
- /** 计费输入 token = 未缓存输入 + 缓存写×1.25 + 缓存读×0.1(Anthropic 计费口径,对齐官方/智谱后台)。
54
- * cache_creation 加价 1.25x,cache_read 命中折扣 0.1x;Math.round 避免浮点。 */
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 Math.round(u.input + u.cacheCreation * 1.25 + u.cacheRead * 0.1);
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(realInput(u))} ↓${fmtTokens(u.output)}`;
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(realInput(u))} ↓${fmtTokens(u.output)} · ${fmtTokens(realInput(u) + u.output)}`;
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 `真实输入 ${realInput(u)} · 输出 ${u.output} · (未缓存 ${u.input} · 缓存写 ${u.cacheCreation} · 缓存读 ${u.cacheRead})`;
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(realInput(u))} · 输出 ${fmtTokens(u.output)} · 总数 ${fmtTokens(realInput(u) + u.output)}`;
80
+ return `输入 ${fmtTokens(u.input)} · 输出 ${fmtTokens(u.output)} · 总数 ${fmtTokens(rawTotal(u))}`;
82
81
  }
83
82
 
84
83
  function trimZero(s: string): string {