cc-viewer 1.5.39 → 1.5.40
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/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<title>Claude Code Viewer</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico?v=1">
|
|
8
8
|
<link rel="shortcut icon" href="/favicon.ico?v=1">
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-H8Yrp2aN.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-GCG7psrt.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
package/lib/stats-worker.js
CHANGED
|
@@ -4,15 +4,63 @@ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from '
|
|
|
4
4
|
import { join, basename } from 'node:path';
|
|
5
5
|
|
|
6
6
|
// 统计 schema 版本号,新增统计字段时递增,强制旧缓存失效重新解析
|
|
7
|
-
const STATS_VERSION =
|
|
7
|
+
const STATS_VERSION = 8;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* 判断文本是否为系统注入文本(与 src/utils/contentFilter.js:isSystemText 保持同步)
|
|
11
|
+
*/
|
|
12
|
+
function isSystemText(text) {
|
|
13
|
+
if (!text) return true;
|
|
14
|
+
const trimmed = text.trim();
|
|
15
|
+
if (!trimmed) return true;
|
|
16
|
+
if (/^<[a-zA-Z_][\w-]*[\s>]/i.test(trimmed)) return true;
|
|
17
|
+
if (/^\[SUGGESTION MODE:/i.test(trimmed)) return true;
|
|
18
|
+
if (/^Your response was cut off because it exceeded the output token limit/i.test(trimmed)) return true;
|
|
19
|
+
if (/^Base directory for this skill:/i.test(trimmed)) return true;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 剥离系统注入标签,保留标签外的用户文本(仅用于 string 类型 content 的混合内容场景)
|
|
11
25
|
*/
|
|
12
26
|
function stripSystemTags(text) {
|
|
13
27
|
return text.replace(/<(system-reminder|local-command-caveat|project-reminder|important-instruction-reminders|file-modified-reminder|todo-reminder|user-prompt-submit-hook|local-command-stdout|command-name|task-notification|environment_details|context)\b[^>]*>[\s\S]*?<\/\1>/gi, '').trim();
|
|
14
28
|
}
|
|
15
29
|
|
|
30
|
+
/**
|
|
31
|
+
* 从 messages 数组中提取用户 prompt 文本列表
|
|
32
|
+
* (与 src/App.jsx:extractUserTexts + src/utils/contentFilter.js:classifyUserContent 保持同步)
|
|
33
|
+
*/
|
|
34
|
+
function extractUserTexts(messages) {
|
|
35
|
+
const texts = [];
|
|
36
|
+
for (const msg of messages) {
|
|
37
|
+
if (msg.role !== 'user') continue;
|
|
38
|
+
if (typeof msg.content === 'string') {
|
|
39
|
+
// string content 可能混合系统标签与用户文本,先剥离标签再判断
|
|
40
|
+
const text = stripSystemTags(msg.content).trim();
|
|
41
|
+
if (text && !isSystemText(text)) {
|
|
42
|
+
if (/^Implement the following plan:/i.test(text)) continue;
|
|
43
|
+
texts.push(text);
|
|
44
|
+
}
|
|
45
|
+
} else if (Array.isArray(msg.content)) {
|
|
46
|
+
const hasCommand = msg.content.some(b => b.type === 'text' && /<command-message>/i.test(b.text || ''));
|
|
47
|
+
const userParts = [];
|
|
48
|
+
for (const b of msg.content) {
|
|
49
|
+
if (b.type !== 'text') continue;
|
|
50
|
+
const text = (b.text || '').trim();
|
|
51
|
+
if (!text || isSystemText(text)) continue;
|
|
52
|
+
if (hasCommand && /<command-message>/i.test(text)) continue;
|
|
53
|
+
if (/^Implement the following plan:/i.test(text)) continue;
|
|
54
|
+
userParts.push(text);
|
|
55
|
+
}
|
|
56
|
+
if (userParts.length > 0) {
|
|
57
|
+
texts.push(userParts.join(' '));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return texts;
|
|
62
|
+
}
|
|
63
|
+
|
|
16
64
|
/**
|
|
17
65
|
* 检测请求是否为 SUGGESTION MODE(预测用户下一步输入)
|
|
18
66
|
*/
|
|
@@ -44,6 +92,8 @@ function parseJsonlFile(filePath) {
|
|
|
44
92
|
let totalCacheRead = 0;
|
|
45
93
|
let totalCacheCreation = 0;
|
|
46
94
|
const preview = [];
|
|
95
|
+
let prevTextCount = 0;
|
|
96
|
+
let lastCollectedSig = ''; // 上次收集的 prompt 签名,用于去重同轮重复请求
|
|
47
97
|
|
|
48
98
|
try {
|
|
49
99
|
const content = readFileSync(filePath, 'utf-8');
|
|
@@ -63,6 +113,7 @@ function parseJsonlFile(filePath) {
|
|
|
63
113
|
// messages 数量大幅缩减:新会话(/clear 等),重置追踪
|
|
64
114
|
if (len < maxMsgLen * 0.5 && (maxMsgLen - len) > 4) {
|
|
65
115
|
maxMsgLen = 0;
|
|
116
|
+
prevTextCount = 0;
|
|
66
117
|
}
|
|
67
118
|
const isNewTurn = len > maxMsgLen;
|
|
68
119
|
if (isNewTurn) {
|
|
@@ -70,23 +121,25 @@ function parseJsonlFile(filePath) {
|
|
|
70
121
|
turnCount++;
|
|
71
122
|
}
|
|
72
123
|
// 收集用户 prompt:新轮次 或 新会话(len===1 且非首次)
|
|
124
|
+
// 用内容签名去重,避免同一轮的重复请求重复收集
|
|
73
125
|
if (isNewTurn || (len === 1 && sessionCount > 0)) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
126
|
+
if (len === 1 && !isNewTurn) {
|
|
127
|
+
// 新会话但未触发 shrink 检测(上一会话较短),重置基线
|
|
128
|
+
prevTextCount = 0;
|
|
129
|
+
maxMsgLen = len;
|
|
77
130
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (text && !text.startsWith('<') && !text.startsWith('[SUGGESTION MODE:')) {
|
|
88
|
-
preview.push(text.slice(0, 100));
|
|
131
|
+
const texts = extractUserTexts(msgs);
|
|
132
|
+
// 生成本次 prompt 签名,用于跳过同轮重复请求
|
|
133
|
+
const sig = texts.join('\x00');
|
|
134
|
+
if (sig === lastCollectedSig && !isNewTurn) {
|
|
135
|
+
// 同一轮的重复请求(内容完全相同),跳过
|
|
136
|
+
} else {
|
|
137
|
+
for (let ti = prevTextCount; ti < texts.length; ti++) {
|
|
138
|
+
const flat = texts[ti].replace(/[\r\n]+/g, ' ').trim();
|
|
139
|
+
if (flat) preview.push(flat.slice(0, 100));
|
|
89
140
|
}
|
|
141
|
+
prevTextCount = texts.length;
|
|
142
|
+
lastCollectedSig = sig;
|
|
90
143
|
}
|
|
91
144
|
}
|
|
92
145
|
if (len === 1) sessionCount++;
|
|
@@ -128,6 +181,16 @@ function parseJsonlFile(filePath) {
|
|
|
128
181
|
// 文件读取失败
|
|
129
182
|
}
|
|
130
183
|
|
|
184
|
+
// 去重 preview:保留首次出现顺序,移除重复文本
|
|
185
|
+
const seenPreview = new Set();
|
|
186
|
+
const uniquePreview = [];
|
|
187
|
+
for (const p of preview) {
|
|
188
|
+
if (!seenPreview.has(p)) {
|
|
189
|
+
seenPreview.add(p);
|
|
190
|
+
uniquePreview.push(p);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
131
194
|
return {
|
|
132
195
|
models,
|
|
133
196
|
summary: {
|
|
@@ -139,7 +202,7 @@ function parseJsonlFile(filePath) {
|
|
|
139
202
|
cache_read_input_tokens: totalCacheRead,
|
|
140
203
|
cache_creation_input_tokens: totalCacheCreation,
|
|
141
204
|
},
|
|
142
|
-
preview,
|
|
205
|
+
preview: uniquePreview,
|
|
143
206
|
};
|
|
144
207
|
}
|
|
145
208
|
|