@rexvane/piweb 0.3.15 → 0.3.17
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +4 -4
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/react-loadable-manifest.json +3 -3
- package/.next/required-server-files.js +1 -3
- package/.next/required-server-files.json +1 -3
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route.js +3 -3
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +1 -1
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +4 -4
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/686.js +4 -4
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/2716.93be8ad5c970c516.js +1 -0
- package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
- package/.next/static/chunks/4673.c1fb1580efc6ace7.js +1 -0
- package/.next/static/chunks/4686-f4a037d609cea3f2.js +1 -0
- package/.next/static/chunks/app/page-b51087831582ee37.js +1 -0
- package/.next/static/chunks/{webpack-42c5aae79b0e5d8c.js → webpack-c31263fd83d75ce6.js} +1 -1
- package/.next/static/css/850c2fd130ffa2fc.css +1 -0
- package/README.md +17 -19
- package/assets/archive.jpg +0 -0
- package/assets/growth-1.jpg +0 -0
- package/assets/growth-2.jpg +0 -0
- package/assets/import.jpg +0 -0
- package/assets/main.jpg +0 -0
- package/assets/outline.jpg +0 -0
- package/assets/prompts-1.jpg +0 -0
- package/assets/prompts-2.jpg +0 -0
- package/assets/providers-1.jpg +0 -0
- package/assets/providers-2.jpg +0 -0
- package/assets/providers-3.jpg +0 -0
- package/assets/trace-1.jpg +0 -0
- package/assets/trace-2.png +0 -0
- package/assets/trace-3.png +0 -0
- package/assets/trace-4.png +0 -0
- package/assets/viewer-1.jpg +0 -0
- package/assets/viewer-2.jpg +0 -0
- package/bin/pi.js +50 -0
- package/next.config.ts +32 -7
- package/package.json +2 -6
- package/scripts/launcher.mjs +1 -1
- package/scripts/verify-package.mjs +0 -53
- package/src/app/api/growth/route.ts +14 -14
- package/src/app/api/models/route.ts +2 -1
- package/src/app/globals.css +90 -0
- package/src/components/AppShell.tsx +30 -16
- package/src/components/ChatWindow.tsx +192 -6
- package/src/components/FileViewer.tsx +10 -9
- package/src/components/ProjectPanel.tsx +111 -94
- package/src/components/SessionImportSection.tsx +20 -36
- package/src/hooks/useGrowth.ts +123 -156
- package/src/hooks/usePiWeb.ts +7 -10
- package/src/i18n.tsx +39 -39
- package/src/lib/agent-manager.ts +10 -8
- package/src/lib/growth-service.ts +390 -166
- package/src/lib/growth-tracker.ts +64 -264
- package/src/lib/growth-turns.ts +31 -1
- package/src/lib/models/model-scope.ts +15 -0
- package/src/lib/models-service.ts +16 -0
- package/src/lib/pi-settings.ts +7 -0
- package/src/lib/session-import/claude.ts +2 -13
- package/src/lib/session-import/codex.ts +8 -44
- package/src/lib/session-import/files.ts +0 -14
- package/src/lib/session-import/grok.ts +24 -54
- package/src/lib/session-import/types.ts +0 -12
- package/src/lib/types.ts +27 -22
- package/tsconfig.json +2 -1
- package/.next/static/chunks/2716.a9e808190a4e2424.js +0 -1
- package/.next/static/chunks/4632.0ed92ae42905ce40.js +0 -1
- package/.next/static/chunks/4673.d3d0a7f2599080fb.js +0 -1
- package/.next/static/chunks/4686-7d694def920ff093.js +0 -1
- package/.next/static/chunks/app/page-9a5440047c5a415c.js +0 -1
- package/.next/static/css/b2d35abc828d4713.css +0 -1
- package/assets/archive.png +0 -0
- package/assets/growth-1.png +0 -0
- package/assets/growth-2.png +0 -0
- package/assets/import.png +0 -0
- package/assets/main.png +0 -0
- package/assets/outline.png +0 -0
- package/assets/prompts-1.png +0 -0
- package/assets/prompts-2.png +0 -0
- package/assets/providers-1.png +0 -0
- package/assets/providers-2.png +0 -0
- package/assets/providers-3.png +0 -0
- package/assets/trace-1.png +0 -0
- package/assets/viewer-1.png +0 -0
- package/assets/viewer-2.png +0 -0
- package/scripts/managed-niubash-assets.mjs +0 -146
- package/scripts/prepare-niubash-packages.mjs +0 -182
- package/scripts/smoke-managed-niubash.mjs +0 -62
- package/src/lib/growth-rounds.ts +0 -84
- package/src/lib/managed-niubash.ts +0 -227
- /package/.next/static/{DdBwPYoHBKtaJ97QIr6Co → zsynuO_9Kx6nrXgkTUJ04}/_buildManifest.js +0 -0
- /package/.next/static/{DdBwPYoHBKtaJ97QIr6Co → zsynuO_9Kx6nrXgkTUJ04}/_ssgManifest.js +0 -0
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import fs from "node:fs/promises";
|
|
10
|
-
import { eachJsonLine, listFiles, readHead,
|
|
10
|
+
import { eachJsonLine, listFiles, readHead, reachedLimit, sortByRecency } from "./files";
|
|
11
11
|
import { codexRoot } from "./paths";
|
|
12
12
|
import type { ExternalSessionSummary, ImportedBlock, ImportedEntry, ImportedSession, ImportSourceModule, ScanOptions } from "./types";
|
|
13
|
-
import { cleanTitle, compactBlocks,
|
|
13
|
+
import { cleanTitle, compactBlocks, outputBlocks, parseArguments, textBlock, toEpochMs } from "./types";
|
|
14
14
|
|
|
15
15
|
const ROOT = codexRoot;
|
|
16
16
|
|
|
@@ -80,7 +80,6 @@ function messageBlocks(payload: Record<string, unknown>): ImportedBlock[] {
|
|
|
80
80
|
if (!Array.isArray(content)) return compactBlocks([textBlock(typeof content === "string" ? content : undefined)]);
|
|
81
81
|
return compactBlocks(content.flatMap((raw): Array<ImportedBlock | null> => {
|
|
82
82
|
const block = raw as Record<string, unknown>;
|
|
83
|
-
if (block?.type === "input_image") return [dataUrlImageBlock(block.image_url, block.mime_type)];
|
|
84
83
|
if (typeof block?.text === "string") return [textBlock(block.text)];
|
|
85
84
|
return [];
|
|
86
85
|
}));
|
|
@@ -96,17 +95,6 @@ function reasoningText(payload: Record<string, unknown>): ImportedBlock | null {
|
|
|
96
95
|
return null;
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
/** 文件 mtime 可能因复制/恢复失真;Codex 每行自带时间,末尾记录才是权威的最近活动时间 */
|
|
100
|
-
function latestTimestamp(records: Record<string, unknown>[]): number | undefined {
|
|
101
|
-
let latest: number | undefined;
|
|
102
|
-
for (const record of records) {
|
|
103
|
-
const payload = record.payload as Record<string, unknown> | undefined;
|
|
104
|
-
const at = toEpochMs(record.timestamp) ?? toEpochMs(payload?.timestamp);
|
|
105
|
-
if (at !== undefined) latest = Math.max(latest ?? at, at);
|
|
106
|
-
}
|
|
107
|
-
return latest;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
98
|
/** 标题:Codex 不写标题字段,用首条真人用户消息(rollout 头几十行里就有) */
|
|
111
99
|
function titleFrom(records: Record<string, unknown>[]): string | undefined {
|
|
112
100
|
for (const record of records) {
|
|
@@ -125,22 +113,10 @@ export const codexSource: ImportSourceModule = {
|
|
|
125
113
|
|
|
126
114
|
async scan(options?: ScanOptions): Promise<ExternalSessionSummary[]> {
|
|
127
115
|
const limit = options?.limit ?? 0;
|
|
128
|
-
const
|
|
129
|
-
// 只读每个文件末尾 64KB 就能按记录时间正确排序;不能先按 mtime 截断,否则复制过的旧文件会挤掉真正最近的会话。
|
|
130
|
-
const candidates = await Promise.all(files.map(async (file) => {
|
|
131
|
-
const mtime = await statMtime(file);
|
|
132
|
-
try {
|
|
133
|
-
const updatedAt = latestTimestamp([...eachJsonLine(await readTail(file))]);
|
|
134
|
-
return { file, mtime, updatedAt: updatedAt ?? mtime };
|
|
135
|
-
} catch {
|
|
136
|
-
return { file, mtime, updatedAt: mtime };
|
|
137
|
-
}
|
|
138
|
-
}));
|
|
139
|
-
candidates.sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0));
|
|
140
|
-
|
|
116
|
+
const candidates = await sortByRecency(await listFiles(ROOT(), { match: (name) => name.startsWith("rollout-") && name.endsWith(".jsonl") }));
|
|
141
117
|
const out: ExternalSessionSummary[] = [];
|
|
142
|
-
for (const { file, mtime
|
|
143
|
-
if (reachedLimit(out.length, limit)) break;
|
|
118
|
+
for (const { file, mtime } of candidates) {
|
|
119
|
+
if (reachedLimit(out.length, limit)) break; // 只读最近的若干条
|
|
144
120
|
let head: string;
|
|
145
121
|
try {
|
|
146
122
|
head = await readHead(file);
|
|
@@ -160,7 +136,7 @@ export const codexSource: ImportSourceModule = {
|
|
|
160
136
|
model: meta.model,
|
|
161
137
|
provider: meta.provider,
|
|
162
138
|
createdAt: meta.createdAt ?? mtime,
|
|
163
|
-
updatedAt,
|
|
139
|
+
updatedAt: mtime,
|
|
164
140
|
location: file,
|
|
165
141
|
});
|
|
166
142
|
}
|
|
@@ -178,18 +154,11 @@ export const codexSource: ImportSourceModule = {
|
|
|
178
154
|
const skipped: string[] = [];
|
|
179
155
|
let synthetic = 0;
|
|
180
156
|
let reasoningSkipped = 0;
|
|
181
|
-
let runtimeContext = 0;
|
|
182
157
|
let meta: Meta = { cwd: summary.projectPath, createdAt: summary.createdAt, model: summary.model, provider: summary.provider };
|
|
183
158
|
|
|
184
159
|
for (const record of eachJsonLine(text)) {
|
|
185
160
|
if (record.type === "session_meta") {
|
|
186
161
|
meta = { ...meta, ...metaFrom([record]) };
|
|
187
|
-
const payload = record.payload as Record<string, unknown> | undefined;
|
|
188
|
-
if (payload?.base_instructions) runtimeContext += 1;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (record.type === "world_state" || record.type === "turn_context") {
|
|
192
|
-
runtimeContext += 1;
|
|
193
162
|
continue;
|
|
194
163
|
}
|
|
195
164
|
if (record.type !== "response_item") continue;
|
|
@@ -200,15 +169,11 @@ export const codexSource: ImportSourceModule = {
|
|
|
200
169
|
switch (payload.type) {
|
|
201
170
|
case "message": {
|
|
202
171
|
const role = payload.role;
|
|
203
|
-
if (role === "developer")
|
|
204
|
-
runtimeContext += 1; // 系统/开发者上下文由 pi 按当前工作区重新生成
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
172
|
+
if (role === "developer") continue; // 系统提示,不进对话
|
|
207
173
|
const blocks = messageBlocks(payload);
|
|
208
174
|
const plain = blocks.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
209
175
|
if (role === "user") {
|
|
210
|
-
|
|
211
|
-
if (!blocks.length || (plain && isSynthetic(plain))) {
|
|
176
|
+
if (!plain || isSynthetic(plain)) {
|
|
212
177
|
synthetic += 1;
|
|
213
178
|
continue;
|
|
214
179
|
}
|
|
@@ -252,7 +217,6 @@ export const codexSource: ImportSourceModule = {
|
|
|
252
217
|
|
|
253
218
|
if (synthetic) skipped.push(`跳过 ${synthetic} 条合成输入`);
|
|
254
219
|
if (reasoningSkipped) skipped.push(`跳过 ${reasoningSkipped} 条仅含加密内容的推理记录`);
|
|
255
|
-
if (runtimeContext) skipped.push(`跳过 ${runtimeContext} 条源工具运行时上下文(pi 会按当前工作区重新生成)`);
|
|
256
220
|
if (!entries.some((entry) => entry.type === "message")) return null;
|
|
257
221
|
return { summary: { ...summary, ...meta, projectPath: meta.cwd, model: meta.model, provider: meta.provider }, entries, skipped };
|
|
258
222
|
},
|
|
@@ -13,20 +13,6 @@ export async function readHead(file: string, bytes = 64 * 1024): Promise<string>
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** 读文件末尾若干字节;首行可能是被截断的半行,JSONL 解析时会自然跳过 */
|
|
17
|
-
export async function readTail(file: string, bytes = 64 * 1024): Promise<string> {
|
|
18
|
-
const handle = await fs.open(file, "r");
|
|
19
|
-
try {
|
|
20
|
-
const size = (await handle.stat()).size;
|
|
21
|
-
const length = Math.min(size, bytes);
|
|
22
|
-
const buffer = Buffer.alloc(length);
|
|
23
|
-
const { bytesRead } = await handle.read(buffer, 0, length, size - length);
|
|
24
|
-
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
25
|
-
} finally {
|
|
26
|
-
await handle.close();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
16
|
/** 逐行解析 JSONL;单行坏掉只跳过该行,不放弃整个文件 */
|
|
31
17
|
export function* eachJsonLine(text: string): Generator<Record<string, unknown>> {
|
|
32
18
|
for (const line of text.split("\n")) {
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
|
-
import { eachJsonLine, listFiles, readHead, reachedLimit,
|
|
9
|
+
import { eachJsonLine, listFiles, readHead, reachedLimit, sortByRecency } from "./files";
|
|
10
10
|
import { grokRoot } from "./paths";
|
|
11
11
|
import type { ExternalSessionSummary, ImportedBlock, ImportedEntry, ImportedSession, ImportSourceModule, ScanOptions } from "./types";
|
|
12
|
-
import { cleanTitle, compactBlocks,
|
|
12
|
+
import { cleanTitle, compactBlocks, outputBlocks, parseArguments, textBlock, toEpochMs } from "./types";
|
|
13
13
|
|
|
14
14
|
const ROOT = grokRoot;
|
|
15
15
|
|
|
@@ -30,10 +30,10 @@ function decodeProjectDir(name: string): string | undefined {
|
|
|
30
30
|
function importable(records: Record<string, unknown>[]): boolean {
|
|
31
31
|
for (const record of records) {
|
|
32
32
|
if (record.type === "assistant" || record.type === "tool_result") return true;
|
|
33
|
-
if (record.type === "reasoning" &&
|
|
33
|
+
if (record.type === "reasoning" && typeof record.summary === "string" && record.summary.trim()) return true;
|
|
34
34
|
if (record.type !== "user" || record.synthetic_reason) continue;
|
|
35
35
|
const blocks = userBlocks(record.content);
|
|
36
|
-
if (blocks.some((block) => block.type === "
|
|
36
|
+
if (blocks.some((block) => block.type === "text" && block.text.replace(/<[^>]+>/g, "").trim())) return true;
|
|
37
37
|
}
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
@@ -46,69 +46,41 @@ async function hasRealMessage(history: string): Promise<boolean> {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/** user 的 content
|
|
50
|
-
function userBlocks(content: unknown): ImportedBlock[] {
|
|
49
|
+
/** user 的 content 可能是字符串或块数组 */function userBlocks(content: unknown): ImportedBlock[] {
|
|
51
50
|
if (typeof content === "string") return compactBlocks([textBlock(content)]);
|
|
52
51
|
if (!Array.isArray(content)) return [];
|
|
53
52
|
return compactBlocks(content.flatMap((raw): Array<ImportedBlock | null> => {
|
|
54
53
|
if (typeof raw === "string") return [textBlock(raw)];
|
|
55
54
|
const block = raw as Record<string, unknown>;
|
|
56
55
|
if (typeof block?.text === "string") return [textBlock(block.text)];
|
|
57
|
-
if (block?.type === "image") return [dataUrlImageBlock(block.url, block.mime ?? block.mimeType)];
|
|
58
56
|
return [];
|
|
59
57
|
}));
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
/** Grok 新版把 reasoning.summary 从字符串改成了 summary_text 块数组;两代格式都要读 */
|
|
63
|
-
function reasoningText(summary: unknown): string {
|
|
64
|
-
if (typeof summary === "string") return summary.trim();
|
|
65
|
-
if (!Array.isArray(summary)) return "";
|
|
66
|
-
return summary
|
|
67
|
-
.map((item) => {
|
|
68
|
-
if (typeof item === "string") return item;
|
|
69
|
-
const block = item as { text?: unknown; summary_text?: unknown };
|
|
70
|
-
return typeof block?.text === "string" ? block.text : typeof block?.summary_text === "string" ? block.summary_text : "";
|
|
71
|
-
})
|
|
72
|
-
.filter(Boolean)
|
|
73
|
-
.join("\n")
|
|
74
|
-
.trim();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
60
|
export const grokSource: ImportSourceModule = {
|
|
78
61
|
id: "grok",
|
|
79
62
|
|
|
80
63
|
async scan(options?: ScanOptions): Promise<ExternalSessionSummary[]> {
|
|
64
|
+
// 先按 chat_history 的 mtime 排序(只 stat,不读内容),再从最近往下读:
|
|
65
|
+
// summary.json 里有标题/模型/时间/消息数,读它即可,正文只在确认有真实对话时碰一下头部
|
|
81
66
|
const limit = options?.limit ?? 0;
|
|
82
67
|
const files = await listFiles(ROOT(), { match: (name) => name === "summary.json" });
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const
|
|
68
|
+
const candidates = await sortByRecency(files.map((file) => path.join(path.dirname(file), "chat_history.jsonl")));
|
|
69
|
+
const out: ExternalSessionSummary[] = [];
|
|
70
|
+
for (const { file: history, mtime } of candidates) {
|
|
71
|
+
if (reachedLimit(out.length, limit)) break; // 只读最近的若干条
|
|
72
|
+
const dir = path.dirname(history);
|
|
73
|
+
let summary: Record<string, unknown>;
|
|
88
74
|
try {
|
|
89
|
-
|
|
90
|
-
const mtime = await statMtime(history);
|
|
91
|
-
if (mtime === undefined) return null;
|
|
92
|
-
return {
|
|
93
|
-
dir,
|
|
94
|
-
history,
|
|
95
|
-
summary,
|
|
96
|
-
mtime,
|
|
97
|
-
updatedAt: toEpochMs(summary.updated_at ?? summary.last_active_at) ?? mtime,
|
|
98
|
-
};
|
|
75
|
+
summary = JSON.parse(await fs.readFile(path.join(dir, "summary.json"), "utf8")) as Record<string, unknown>;
|
|
99
76
|
} catch {
|
|
100
|
-
|
|
77
|
+
continue;
|
|
101
78
|
}
|
|
102
|
-
|
|
103
|
-
.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
104
|
-
|
|
105
|
-
const out: ExternalSessionSummary[] = [];
|
|
106
|
-
for (const { dir, history, summary, mtime, updatedAt } of candidates) {
|
|
107
|
-
if (reachedLimit(out.length, limit)) break;
|
|
79
|
+
if (mtime === undefined) continue; // 没有正文就没什么可导
|
|
108
80
|
const info = summary.info as Record<string, unknown> | undefined;
|
|
109
|
-
|
|
110
|
-
const numMessages = Number(summary.num_chat_messages ?? summary.num_messages);
|
|
81
|
+
const numMessages = Number(summary.num_messages ?? summary.num_chat_messages);
|
|
111
82
|
// 子代理会话(grok 的 spawn_subagent:subagent / subagent_fork / subagent_resume,本机 24 条)
|
|
83
|
+
// 以及它们的派生:agent_name 是 general-purpose / explore 之类,不是你在用的那条对话
|
|
112
84
|
if (typeof summary.session_kind === "string" && summary.session_kind.startsWith("subagent")) continue;
|
|
113
85
|
if (numMessages === 0) continue; // 开了没用的空会话
|
|
114
86
|
// 只有系统提示与合成提醒(system_reminder)的会话读不出对话,别列进列表
|
|
@@ -120,7 +92,7 @@ export const grokSource: ImportSourceModule = {
|
|
|
120
92
|
projectPath: typeof info?.cwd === "string" ? info.cwd : decodeProjectDir(path.basename(path.dirname(dir))),
|
|
121
93
|
model: typeof summary.current_model_id === "string" ? summary.current_model_id : undefined,
|
|
122
94
|
createdAt: toEpochMs(summary.created_at) ?? mtime,
|
|
123
|
-
updatedAt,
|
|
95
|
+
updatedAt: toEpochMs(summary.updated_at ?? summary.last_active_at) ?? mtime,
|
|
124
96
|
...(Number.isFinite(numMessages) ? { messageCount: numMessages } : {}),
|
|
125
97
|
location: history,
|
|
126
98
|
});
|
|
@@ -139,18 +111,17 @@ export const grokSource: ImportSourceModule = {
|
|
|
139
111
|
const skipped: string[] = [];
|
|
140
112
|
let encryptedReasoning = 0;
|
|
141
113
|
let synthetic = 0;
|
|
142
|
-
let runtimeContext = 0;
|
|
143
114
|
let model = summary.model;
|
|
144
115
|
|
|
145
116
|
for (const record of eachJsonLine(text)) {
|
|
146
117
|
const timestamp = toEpochMs(record.timestamp ?? record.time ?? record.created_at) ?? summary.createdAt ?? Date.now();
|
|
147
118
|
switch (record.type) {
|
|
148
119
|
case "system":
|
|
149
|
-
|
|
150
|
-
continue; // 系统提示由 pi 按目标工作区重新生成,不冒充用户消息
|
|
120
|
+
continue; // 系统提示不进对话
|
|
151
121
|
case "user": {
|
|
152
122
|
const blocks = userBlocks(record.content);
|
|
153
|
-
|
|
123
|
+
const plain = blocks.map((block) => (block.type === "text" ? block.text : "")).join("\n").trim();
|
|
124
|
+
if (record.synthetic_reason || !plain) {
|
|
154
125
|
synthetic += 1;
|
|
155
126
|
continue;
|
|
156
127
|
}
|
|
@@ -174,8 +145,8 @@ export const grokSource: ImportSourceModule = {
|
|
|
174
145
|
continue;
|
|
175
146
|
}
|
|
176
147
|
case "reasoning": {
|
|
177
|
-
const text =
|
|
178
|
-
if (text) entries.push({ type: "message", message: { role: "assistant", content: [{ type: "thinking", thinking: text }], timestamp, ...(model ? { model } : {}) } });
|
|
148
|
+
const text = typeof record.summary === "string" ? record.summary : "";
|
|
149
|
+
if (text.trim()) entries.push({ type: "message", message: { role: "assistant", content: [{ type: "thinking", thinking: text }], timestamp, ...(model ? { model } : {}) } });
|
|
179
150
|
else encryptedReasoning += 1; // 加密推理无正文可导:计数后汇总成一行,不要刷屏
|
|
180
151
|
continue;
|
|
181
152
|
}
|
|
@@ -192,7 +163,6 @@ export const grokSource: ImportSourceModule = {
|
|
|
192
163
|
|
|
193
164
|
if (synthetic) skipped.push(`跳过 ${synthetic} 条合成输入`);
|
|
194
165
|
if (encryptedReasoning) skipped.push(`跳过 ${encryptedReasoning} 条仅含加密内容的推理记录`);
|
|
195
|
-
if (runtimeContext) skipped.push(`跳过 ${runtimeContext} 条源工具运行时上下文(pi 会按当前工作区重新生成)`);
|
|
196
166
|
if (!entries.some((entry) => entry.type === "message")) return null;
|
|
197
167
|
return { summary: { ...summary, model }, entries, skipped };
|
|
198
168
|
},
|
|
@@ -121,18 +121,6 @@ export function compactBlocks(blocks: Array<ImportedBlock | null | undefined>):
|
|
|
121
121
|
return blocks.filter((block): block is ImportedBlock => Boolean(block));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
/** 外部工具常把图片内联成 data URL;只接受 base64,避免把本地路径或远程 URL 塞进 pi 会话 */
|
|
125
|
-
export function dataUrlImageBlock(value: unknown, explicitMime?: unknown): ImportedBlock | null {
|
|
126
|
-
if (typeof value !== "string") return null;
|
|
127
|
-
const match = /^data:([^;,]+);base64,(.+)$/s.exec(value);
|
|
128
|
-
if (!match) return null;
|
|
129
|
-
return {
|
|
130
|
-
type: "image",
|
|
131
|
-
data: match[2],
|
|
132
|
-
mimeType: typeof explicitMime === "string" && explicitMime.trim() ? explicitMime : match[1],
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
124
|
/** 工具返回值可能是字符串、对象或数组,统一成内容块 */
|
|
137
125
|
export function outputBlocks(value: unknown): ImportedBlock[] {
|
|
138
126
|
if (typeof value === "string") return compactBlocks([textBlock(value)]);
|
package/src/lib/types.ts
CHANGED
|
@@ -144,10 +144,8 @@ export type WebEvent =
|
|
|
144
144
|
| { type: "error"; message: string; ts: number }
|
|
145
145
|
/** 自动重试:流程内通知,界面聚合成一条「重试 n/max」,不作为错误条目累积 */
|
|
146
146
|
| { type: "retry"; attempt: number; maxAttempts: number; message: string; ts: number }
|
|
147
|
-
/**
|
|
148
|
-
| { type: "growth";
|
|
149
|
-
/** 项目生长:改盘类工具运行期间目录监听看到的新路径(累计;空数组 = 清空) */
|
|
150
|
-
| { type: "growth_pending"; paths: string[]; ts: number }
|
|
147
|
+
/** 项目生长:提交了一个 commit(pi 的一轮 / 用户两轮之间的修改 / 工作区基线) */
|
|
148
|
+
| { type: "growth"; round: GrowthRound; ts: number }
|
|
151
149
|
| { type: "growth_error"; message: string | null; ts: number };
|
|
152
150
|
|
|
153
151
|
export interface WebStats {
|
|
@@ -165,9 +163,11 @@ export interface WebStats {
|
|
|
165
163
|
decodeTokens: number;
|
|
166
164
|
}
|
|
167
165
|
|
|
168
|
-
// ----------
|
|
166
|
+
// ---------- 项目生长(每轮一个 git commit) ----------
|
|
169
167
|
|
|
170
|
-
|
|
168
|
+
/** round = pi 的一轮;user = 用户在两轮之间自己的修改;baseline = 工作区第一个 commit(起点,不算一轮) */
|
|
169
|
+
export type GrowthRoundKind = "round" | "user" | "baseline";
|
|
170
|
+
export type GrowthRoundStatus = "done" | "aborted" | "error";
|
|
171
171
|
|
|
172
172
|
export interface GrowthChange {
|
|
173
173
|
status: "A" | "M" | "D" | "R";
|
|
@@ -180,26 +180,31 @@ export interface GrowthChange {
|
|
|
180
180
|
binary?: boolean;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
/**
|
|
184
|
+
* refs/piweb/rounds/<key> 链上的一个 commit。元信息全部来自 commit 本身(标题 + Piweb-* trailers),
|
|
185
|
+
* 变更清单与行数来自 git log --raw --numstat(相对上一个 commit)。
|
|
186
|
+
*/
|
|
187
|
+
export interface GrowthRound {
|
|
188
|
+
commit: string;
|
|
189
|
+
/** 上一个 commit;链上第一个 commit 为 null */
|
|
190
|
+
parent: string | null;
|
|
191
|
+
tree: string;
|
|
192
|
+
/** 上一个 commit 的 tree(链上第一个 commit 为空树):本轮改动 = git diff parentTree tree */
|
|
193
|
+
parentTree: string;
|
|
194
|
+
/** 提交时间(毫秒) */
|
|
186
195
|
ts: number;
|
|
187
|
-
|
|
196
|
+
kind: GrowthRoundKind;
|
|
197
|
+
/** round:本轮提问首行;其余类型为空 */
|
|
198
|
+
title: string;
|
|
199
|
+
/** 所属会话的 JSONL 文件名(不含目录) */
|
|
188
200
|
session: string;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
tree: string;
|
|
194
|
-
/** 这一步之前的 tree 哈希(工作区首张快照为 empty tree) */
|
|
195
|
-
parent: string;
|
|
196
|
-
commit?: string;
|
|
197
|
-
toolCallId?: string;
|
|
198
|
-
toolName?: string;
|
|
199
|
-
/** 相对上一步的变更(超过上限时截断,truncated=true) */
|
|
201
|
+
/** 本轮的用户消息 entryId(对话里的提问与这一轮互相跳转用) */
|
|
202
|
+
promptIds: string[];
|
|
203
|
+
status: GrowthRoundStatus;
|
|
204
|
+
/** 相对上一个 commit 的变更(超过上限时截断,truncated=true) */
|
|
200
205
|
changes: GrowthChange[];
|
|
201
206
|
truncated?: boolean;
|
|
202
|
-
/**
|
|
207
|
+
/** 链上第一个 commit:没有上一版可比,不列变更 */
|
|
203
208
|
initial?: boolean;
|
|
204
209
|
stats: { added: number; modified: number; deleted: number; renamed: number; add: number; del: number };
|
|
205
210
|
}
|
package/tsconfig.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2716],{2716:(e,t,n)=>{n.r(t),n.d(t,{FileViewer:()=>y});var i=n(5155),l=n(2115),a=n(8426),r=n(3437),s=n(7890),o=n(8476),c=n(1859),d=n(9665);let h=(0,l.memo)(function({lines:e,language:t,folded:n,handle:a,initialChange:r}){let{t:s}=(0,c.s)(),[o,h]=(0,l.useState)(()=>new Set),u=(0,l.useRef)(null),[p,f]=(0,l.useState)({top:0,height:800}),[m,w]=(0,l.useState)(-1),x=(0,l.useRef)(new Map);(0,l.useEffect)(()=>h(new Set),[e]);let{rows:v,blocks:g}=(0,l.useMemo)(()=>(function(e,t,n){let i=[],l=[],a=e=>"add"===e.kind||"del"===e.kind,r=-1,s=-1;for(let t=0;t<e.length;t+=1)a(e[t])&&(r<0&&(r=t),s=t);let o=0,c=!1;for(;o<e.length;){let d=e[o];if("hunk"===d.kind||"file"===d.kind){o+=1;continue}if(a(d)){c||l.push(i.length),i.push({kind:"line",line:d,index:o}),c=!0,o+=1;continue}c=!1;let h=o;for(;h<e.length&&"ctx"===e[h].kind;)h+=1;let u=h-o,p=r<0||r>=o?0:3,f=s<h?0:3;if(!t||n.has(o)||u<=p+f+2)for(let t=o;t<h;t+=1)i.push({kind:"line",line:e[t],index:t});else{for(let t=o;t<o+p;t+=1)i.push({kind:"line",line:e[t],index:t});i.push({kind:"fold",from:o,count:u-p-f});for(let t=h-f;t<h;t+=1)i.push({kind:"line",line:e[t],index:t})}o=h}return{rows:i,blocks:l}})(e,n,o),[e,n,o]),y=(0,l.useMemo)(()=>String(function(e){let t=1;for(let n of e){let e=n.new??n.old??0;e>t&&(t=e)}return t}(e)).length,[e]),j=!!t&&v.length<=6e3;(0,l.useEffect)(()=>{let e=u.current;if(!e)return;let t=()=>f({top:e.scrollTop,height:e.clientHeight});t(),e.addEventListener("scroll",t,{passive:!0});let n=new ResizeObserver(t);return n.observe(e),()=>{e.removeEventListener("scroll",t),n.disconnect()}},[]);let b=(0,l.useCallback)(e=>{let t=u.current,n=g[e];t&&void 0!==n&&(w(e),t.scrollTo({top:Math.max(0,20*n-t.clientHeight/3),behavior:"smooth"}))},[g]);(0,l.useImperativeHandle)(a,()=>({nextChange:()=>b(Math.min(g.length-1,m+1)),prevChange:()=>b(Math.max(0,m-1))}),[g.length,m,b]);let k=(0,l.useRef)(!1);(0,l.useEffect)(()=>{k.current=!1},[e]),(0,l.useEffect)(()=>{if(k.current||!r||!g.length)return;k.current=!0;let e=u.current;e&&(e.scrollTop=Math.max(0,20*g[0]-e.clientHeight/3),w(0))},[g,r,v]);let N=Math.max(0,Math.floor(p.top/20)-20),C=Math.min(v.length,Math.ceil((p.top+p.height)/20)+20);return(0,i.jsxs)("div",{className:"pw-fdiff relative min-h-0 flex-1",children:[(0,i.jsx)("div",{ref:u,className:"pw-fdiff-scroll h-full overflow-auto",children:(0,i.jsx)("div",{style:{height:20*v.length,position:"relative",minWidth:"100%"},children:v.slice(N,C).map((e,n)=>{let l=(N+n)*20;if("fold"===e.kind)return(0,i.jsx)("button",{type:"button",className:"pw-fdiff-fold",style:{top:l},onClick:()=>h(t=>new Set(t).add(e.from)),children:s.viewerFoldedLines.replace("{n}",String(e.count))},`f${e.from}`);let a=e.line,r=(e=>{if(!j||!e.trim())return null;let n=x.current.get(e);if(void 0!==n)return n;let i=(0,d.NK)(e,t);return x.current.size>5e3&&x.current.clear(),x.current.set(e,i),i})(a.text);return(0,i.jsxs)("div",{className:"pw-fdiff-row","data-kind":a.kind,style:{top:l},children:[(0,i.jsxs)("span",{className:"pw-fdiff-gutter",style:{width:`${2*y+3}ch`},children:[(0,i.jsx)("span",{children:"add"===a.kind?"":String(a.old??"").padStart(y)}),(0,i.jsx)("span",{children:"del"===a.kind?"":String(a.new??"").padStart(y)})]}),(0,i.jsx)("span",{className:"pw-fdiff-sign",children:"add"===a.kind?"+":"del"===a.kind?"−":" "}),(0,i.jsx)("span",{className:"pw-fdiff-text hljs",children:r?(0,i.jsx)("span",{dangerouslySetInnerHTML:{__html:r}}):a.text})]},e.index)})})}),v.length>0&&g.length>0&&(0,i.jsx)("div",{className:"pw-fdiff-ticks","aria-hidden":!0,children:g.map((e,t)=>{let n=v[e],l=n?.kind==="line"?n.line.kind:"add";return(0,i.jsx)("span",{className:"pw-fdiff-tick","data-kind":l,"data-current":t===m||void 0,style:{top:`${e/v.length*100}%`},onClick:()=>b(t)},t)})})]})});var u=n(2155),p=n(9992),f=n(2253),m=n(1135);let w=new Map;async function x(e){let t=await fetch(e),n=await t.json();if(!t.ok||!n.success){let e=Error(n.error||`request failed (${t.status})`);throw e.status=t.status,e}return n.data}function v(e){let t=w.get(e);return!t&&((t=x(e)).catch(()=>w.delete(e)),w.set(e,t),w.size>200&&w.delete(w.keys().next().value)),t}function g({content:e,name:t}){let n=(0,l.useMemo)(()=>(0,d.wY)(e,(0,d.f3)(t)),[e,t]),a=e.split("\n").length,r=`${String(a).length+1}ch`;return(0,i.jsxs)("div",{className:"flex min-h-0 flex-1 overflow-auto",style:{fontFamily:"var(--font-mono)",fontSize:12,lineHeight:"20px"},children:[(0,i.jsx)("pre",{"aria-hidden":!0,className:"flex-none select-none py-2 pl-3 pr-2 text-right",style:{width:r,minWidth:r,color:"var(--dsw-label-caption)",margin:0,position:"sticky",left:0,background:"var(--dsw-glass-modal)"},children:Array.from({length:a},(e,t)=>t+1).join("\n")}),null!==n?(0,i.jsx)("pre",{className:"hljs min-w-0 flex-1 py-2 pl-3 pr-4",style:{margin:0,background:"transparent",color:"var(--dsw-label-secondary)"},children:(0,i.jsx)("code",{dangerouslySetInnerHTML:{__html:n}})}):(0,i.jsx)("pre",{className:"min-w-0 flex-1 py-2 pl-3 pr-4",style:{margin:0,color:"var(--dsw-label-secondary)"},children:e})]})}function y({state:e,growth:t,cwd:n,onClose:w,onSelectTab:j,onCloseTab:b,onOpen:k,onReference:N,onOpenEditor:C}){let{t:$}=(0,c.s)(),[S,M]=(0,l.useState)(!1),[z,E]=(0,l.useState)("diff"),[R,L]=(0,l.useState)(!1),[T,D]=(0,l.useState)(!0),[F,P]=(0,l.useState)(!1),[A,I]=(0,l.useState)(""),[H,U]=(0,l.useState)(0),[_,q]=(0,l.useState)(""),[O,W]=(0,l.useState)(null),[K,B]=(0,l.useState)(null),Q=(0,l.useRef)(null),V=(0,l.useRef)(null),G=(0,l.useRef)(null),X=e.tabs.find(t=>t.path===e.active)??null,Y=(0,l.useMemo)(()=>X?t.changes.find(e=>e.path===X.path)??null:null,[X,t.changes]),J=!!(X&&/\.(md|mdx|markdown)$/i.test(X.path)),Z=t.range;(0,l.useEffect)(()=>{L(!1)},[e.active]),(0,l.useEffect)(()=>{!R&&X&&E(Y&&!X.lazy?"diff":J?"render":"content")},[X,Y,J,R]),(0,l.useEffect)(()=>{if(!X)return void B(null);let e=`${X.path}|${z}|${Z?.from??""}|${Z?.to??""}|${Y?.status??""}`,i=!0,l=t=>{i&&B({key:e,diff:null,content:null,binary:!1,truncated:!1,notice:null,error:null,...t})};if(void 0!==X.staticContent)return l({content:X.staticContent}),()=>{i=!1};if(!n)return void B(null);let a=`cwd=${encodeURIComponent(n)}`,r=async e=>{try{let t=await x(`/api/files?read=1&${a}&path=${encodeURIComponent(X.path)}`);l({content:t.content,binary:t.binary,truncated:t.truncated,notice:e})}catch(e){if(400!==e.status)throw e;l({notice:"missing"})}},s=async(e,n)=>{try{let t=await v(`/api/growth?${a}&tree=${e}&path=${encodeURIComponent(X.path)}&content=1`);l({content:t.content,binary:t.binary,truncated:t.truncated,notice:n})}catch(n){if(404===n.status){let n=[...t.steps].reverse().find(e=>e.seq<=(t.selected?.seq??1/0)&&e.changes.some(e=>e.path===X.path||e.from===X.path)),i=n?.changes.some(e=>"D"===e.status&&e.path===X.path||"R"===e.status&&e.from===X.path)?n.parent:n?.tree;if(i&&i!==e)try{let e=await v(`/api/growth?${a}&tree=${i}&path=${encodeURIComponent(X.path)}&content=1`);l({content:e.content,binary:e.binary,truncated:e.truncated,notice:"historical"});return}catch(e){if(404!==e.status)throw e}await r("disk");return}throw n}};return(async()=>{try{if(X.lazy||!Z)return void await r(X.lazy&&t.steps.length>0?"disk":null);if("diff"===z){if(!Y)return void await s(Z.to,"nochange");let e=await v(`/api/growth?${a}&from=${Z.from}&to=${Z.to}&path=${encodeURIComponent(X.path)}${X.from??Y.from?`&renamedFrom=${encodeURIComponent(X.from??Y.from??"")}`:""}`);if(e.binary)return void l({binary:!0});let t=(0,o.rj)(e.patch);if(!t)return void await s(Z.to,"nochange");l({diff:t,truncated:e.truncated,notice:"D"===Y.status?"deleted":null});return}await s(Z.to,null)}catch(e){l({error:e instanceof Error?e.message:$.viewerLoadFailed})}})(),()=>{i=!1}},[X,n,z,Z,Y,t.steps,t.selected,t.pending,$.viewerLoadFailed]),(0,l.useEffect)(()=>{if(!e.open)return;let t=t=>{let n=t.target,i=n&&("INPUT"===n.tagName||"TEXTAREA"===n.tagName||n.isContentEditable);if("Escape"===t.key){t.preventDefault(),F?P(!1):w();return}let l=t.ctrlKey||t.metaKey;if(l&&"w"===t.key.toLowerCase()){t.preventDefault(),e.active?b(e.active):w();return}if(l&&"p"===t.key.toLowerCase()){t.preventDefault(),P(!0);return}i||l||"diff"!==z||("]"===t.key?Q.current?.nextChange():"["===t.key&&Q.current?.prevChange())};return document.addEventListener("keydown",t),()=>document.removeEventListener("keydown",t)},[e.open,e.active,F,z,w,b]),(0,l.useEffect)(()=>{F&&(I(""),U(0),setTimeout(()=>G.current?.focus(),0))},[F]),(0,l.useEffect)(()=>{e.open&&setTimeout(()=>V.current?.focus(),0)},[e.open]);let ee=(0,l.useMemo)(()=>{if(!F)return[];let e=A.trim();return t.filePaths.map(t=>({p:t,s:(0,m.dt)(e,t)})).filter(e=>e.s>=0).sort((e,t)=>t.s-e.s||e.p.length-t.p.length).slice(0,50).map(e=>e.p)},[F,A,t.filePaths]);if(!e.open)return null;let et=X?X.path.split("/"):[],en=t.selectedRound?.id??0,ei=en?`${$.growthRoundLabel.replace("{n}",String(en))}${Y?` \xb7 +${Y.add??0} −${Y.del??0}`:""}`:"",el="diff"===z&&!!K?.diff,ea=K?.notice==="deleted"?$.viewerDeleted:K?.notice==="historical"?$.viewerHistorical:K?.notice==="missing"?$.viewerMissing:K?.notice==="disk"?$.viewerNotInSnapshot:K?.notice==="nochange"?$.viewerNoChanges:"";return(0,i.jsx)("div",{className:"pw-viewer-mask modal-mask fixed inset-0 z-[105] flex items-center justify-center",onMouseDown:e=>e.target===e.currentTarget&&w(),children:(0,i.jsxs)("div",{ref:V,className:"pw-viewer glass-modal flex flex-col overflow-hidden outline-none","data-fullscreen":S||void 0,role:"dialog","aria-modal":"true",tabIndex:-1,"data-testid":"file-viewer",children:[(0,i.jsxs)("div",{className:"pw-viewer-tabs hairline-b",onDoubleClick:e=>e.target===e.currentTarget&&M(e=>!e),children:[(0,i.jsxs)("button",{type:"button",className:"pw-viewer-tab","data-active":null===e.active,onClick:()=>j(null),children:[(0,i.jsx)("img",{className:"pw-tree-icon",src:"/file-icons/folder-open.svg",alt:""}),(0,i.jsx)("span",{children:$.projectPanel})]}),(0,i.jsx)("div",{className:"pw-viewer-tabstrip",children:e.tabs.map(t=>(0,i.jsxs)("div",{className:"pw-viewer-tab","data-active":e.active===t.path,onAuxClick:e=>{1===e.button&&b(t.path)},children:[(0,i.jsxs)("button",{type:"button",className:"pw-viewer-tab-main",title:t.path,onClick:()=>j(t.path),children:[(0,i.jsx)("img",{className:"pw-tree-icon",src:(0,f.s)(t.path.split("/").pop()??t.path),alt:""}),(0,i.jsx)("span",{className:"truncate",children:t.path.split("/").pop()})]}),(0,i.jsx)("button",{type:"button",className:"pw-viewer-tab-close",title:$.viewerCloseTab,"aria-label":`${$.viewerCloseTab}: ${t.path}`,onClick:()=>b(t.path),children:(0,i.jsx)(p.Bh,{size:10})})]},t.path))}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:26,height:26},title:`${$.viewerQuickOpen} (Ctrl/⌘+P)`,onClick:()=>P(!0),children:(0,i.jsx)(p.Xh,{size:13})}),(0,i.jsx)("div",{className:"flex-1"}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:26,height:26},title:S?$.viewerExitFullscreen:$.viewerFullscreen,onClick:()=>M(e=>!e),"data-testid":"viewer-fullscreen",children:(0,i.jsx)("svg",{width:"14",height:"14",viewBox:"0 0 16 16",fill:"none","aria-hidden":!0,children:S?(0,i.jsx)("path",{d:"M6 1v5H1M10 1v5h5M6 15v-5H1M10 15v-5h5",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round"}):(0,i.jsx)("path",{d:"M1 6V1h5M15 6V1h-5M1 10v5h5M15 10v5h-5",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round"})})}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:26,height:26},title:`${$.close} (Esc)`,onClick:w,"data-testid":"viewer-close",children:(0,i.jsx)(p.Bh,{size:13})})]}),X?(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)("div",{className:"pw-viewer-toolbar hairline-b",children:[(0,i.jsx)("div",{className:"pw-crumb min-w-0 flex-1",children:et.map((e,n)=>(0,i.jsxs)("span",{className:"flex items-center",children:[n>0&&(0,i.jsx)("span",{className:"pw-crumb-sep",children:"/"}),n<et.length-1?(0,i.jsx)("button",{type:"button",onClick:()=>{let e;return e=et.slice(0,n+1).join("/"),void(t.revealPath(e),W({path:e,key:Date.now()}),j(null))},children:e}):(0,i.jsx)("span",{style:{color:"var(--dsw-label-primary)",fontWeight:500},children:e})]},n))}),ei&&!X.lazy&&(0,i.jsx)("span",{className:"flex-none truncate",style:{fontSize:11.5,color:"var(--dsw-label-caption)",maxWidth:220},children:ei}),!X.lazy&&(0,i.jsx)("span",{className:"pw-seg",role:"radiogroup",children:["diff","content",...J?["render"]:[]].map(e=>(0,i.jsx)("button",{type:"button",role:"radio","aria-checked":z===e,"data-active":z===e,onClick:()=>{L(!0),E(e)},children:"diff"===e?$.viewerModeDiff:"content"===e?$.viewerModeContent:$.viewerModeRender},e))}),X.lazy&&J&&(0,i.jsx)("span",{className:"pw-seg",role:"radiogroup",children:["content","render"].map(e=>(0,i.jsx)("button",{type:"button",role:"radio","aria-checked":z===e,"data-active":z===e,onClick:()=>{L(!0),E(e)},children:"content"===e?$.viewerModeContent:$.viewerModeRender},e))}),el&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("button",{type:"button",className:"pw-chip","data-active":T,onClick:()=>D(e=>!e),children:T?$.viewerFold:$.viewerUnfold}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:`${$.viewerPrevChange} ([)`,onClick:()=>Q.current?.prevChange(),children:(0,i.jsx)(p.tr,{size:12})}),(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:`${$.viewerNextChange} (])`,onClick:()=>Q.current?.nextChange(),children:(0,i.jsx)(p.Cd,{size:12})})]}),N&&(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:$.fileReference,onClick:()=>N(X.path),children:(0,i.jsx)(p.PT,{size:13})}),C&&(0,i.jsx)("button",{type:"button",className:"icon-btn",style:{width:24,height:24},title:$.fileOpenEditor,onClick:()=>C(X.path),children:(0,i.jsx)(p.lx,{size:13})})]}),ea&&(0,i.jsx)("div",{className:"pw-viewer-notice","data-kind":K?.notice,children:ea}),K?.truncated&&(0,i.jsx)("div",{className:"pw-viewer-notice","data-kind":"warn",children:"diff"===z?$.viewerPatchTruncated:$.filePreviewTruncated}),K&&K.key===`${X.path}|${z}|${Z?.from??""}|${Z?.to??""}|${Y?.status??""}`?K.error?(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-danger)"},children:K.error}):"missing"===K.notice?(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-label-caption)"},children:$.viewerMissing}):K.binary?(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-label-caption)"},children:$.fileBinary}):K.diff?(0,i.jsx)(h,{lines:K.diff,language:(0,d.f3)(X.path),folded:T,handle:Q,initialChange:!0}):"render"===z&&null!==K.content?(0,i.jsx)("div",{className:"md min-h-0 flex-1 overflow-auto px-6 py-4",style:{fontSize:14},children:(0,i.jsx)(a.oz,{remarkPlugins:[s.A],rehypePlugins:[[r.A,{detect:!1,ignoreMissing:!0}]],children:K.content})}):null!==K.content?(0,i.jsx)(g,{content:K.content,name:X.path}):null:(0,i.jsx)("div",{className:"flex min-h-0 flex-1 items-center justify-center",style:{fontSize:12.5,color:"var(--dsw-label-caption)"},children:$.panelLoading})]}):(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)("div",{className:"pw-viewer-toolbar hairline-b",children:[(0,i.jsxs)("div",{className:"flex min-w-0 flex-1 items-center gap-1.5 rounded-lg px-2",style:{background:"var(--dsw-hover)",height:26,maxWidth:360},children:[(0,i.jsx)(p.FG,{size:12,style:{flex:"none",color:"var(--dsw-label-caption)"}}),(0,i.jsx)("input",{value:_,onChange:e=>q(e.target.value),placeholder:$.growthFilter,className:"min-w-0 flex-1 bg-transparent",style:{fontSize:12}})]}),(0,i.jsx)("span",{style:{fontSize:11.5,color:"var(--dsw-label-caption)"},children:en?$.growthRoundLabel.replace("{n}",String(en)):""}),(0,i.jsx)("button",{type:"button",className:"pw-chip",onClick:t.expandAll,children:$.growthExpandAll}),(0,i.jsx)("button",{type:"button",className:"pw-chip",onClick:t.collapseAll,children:$.growthCollapseAll})]}),(0,i.jsx)(u.Q,{tree:t.tree,expanded:t.expanded,fresh:t.fresh,filter:_,selectedPath:null,onToggleDir:t.toggleDir,onExpandLazy:t.expandLazy,onOpenFile:e=>k(e.path,{from:e.from,lazy:e.lazy}),onReference:N,onOpenEditor:C,revealPath:O?.path??null,revealKey:O?.key,emptyText:$.viewerEmpty})]}),F&&(0,i.jsx)("div",{className:"pw-quick",onMouseDown:e=>e.target===e.currentTarget&&P(!1),children:(0,i.jsxs)("div",{className:"pw-quick-box popover",children:[(0,i.jsxs)("div",{className:"flex items-center gap-2 px-2",style:{height:34},children:[(0,i.jsx)(p.FG,{size:14,style:{flex:"none",color:"var(--dsw-label-caption)"}}),(0,i.jsx)("input",{ref:G,value:A,onChange:e=>{I(e.target.value),U(0)},placeholder:$.viewerQuickOpen,className:"min-w-0 flex-1 bg-transparent",style:{fontSize:13},onKeyDown:e=>{if("ArrowDown"===e.key)e.preventDefault(),U(e=>Math.min(ee.length-1,e+1));else if("ArrowUp"===e.key)e.preventDefault(),U(e=>Math.max(0,e-1));else if("Enter"===e.key){e.preventDefault();let t=ee[H];t&&(k(t),P(!1))}}})]}),(0,i.jsx)("div",{className:"pw-quick-list",children:0===ee.length?(0,i.jsx)("div",{style:{fontSize:12.5,color:"var(--dsw-label-caption)",padding:"8px 10px"},children:$.viewerQuickOpenEmpty}):ee.map((e,t)=>(0,i.jsxs)("button",{type:"button",className:"pw-quick-item","data-active":t===H,onMouseEnter:()=>U(t),onClick:()=>{k(e),P(!1)},children:[(0,i.jsx)("img",{className:"pw-tree-icon",src:(0,f.s)(e.split("/").pop()??e),alt:""}),(0,i.jsx)("span",{className:"truncate",children:e.split("/").pop()}),(0,i.jsx)("span",{className:"min-w-0 truncate",style:{color:"var(--dsw-label-caption)",fontSize:11.5},children:e})]},e))})]})})]})})}}}]);
|