@yinan_chen/pi-claude-code-ui 0.8.4 → 0.8.8
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/package.json +1 -1
- package/src/claude-tools.ts +11 -1
- package/src/claude-view.ts +13 -4
- package/src/generic-card.ts +34 -14
package/package.json
CHANGED
package/src/claude-tools.ts
CHANGED
|
@@ -99,7 +99,7 @@ export function codemodeTarget(code: string): string {
|
|
|
99
99
|
}
|
|
100
100
|
export function targetOf(name: string, a: Record<string, any>, cwd?: string): { text: string; path: boolean } {
|
|
101
101
|
// codemode 头行括号提示已停用(一南 2026-09-30):注释/行数不再上头行;codemodeTarget 保留备用。
|
|
102
|
-
if (name === "codemode") return { text: "", path: false };
|
|
102
|
+
if (name === "codemode") return { text: codemodeTarget(typeof a.code === "string" ? a.code : ""), path: false };
|
|
103
103
|
if (typeof a.command === "string") return { text: oneLine(a.command), path: false };
|
|
104
104
|
if ((name === "grep" || name === "find") && a.pattern) return { text: `pattern: ${JSON.stringify(a.pattern)}${a.path ? `, path: ${shortPath(a.path, cwd)}` : ""}`, path: false };
|
|
105
105
|
if (typeof a.path === "string") {
|
|
@@ -110,6 +110,16 @@ export function targetOf(name: string, a: Record<string, any>, cwd?: string): {
|
|
|
110
110
|
for (const k of ["url", "urls", "query", "queries", "pattern", "claim", "action", "name", "tool", "search", "describe", "project_id"]) {
|
|
111
111
|
if (a[k]) return { text: oneLine(Array.isArray(a[k]) ? a[k].join("; ") : a[k]), path: false };
|
|
112
112
|
}
|
|
113
|
+
// 兜底(一南 2026-10-05):任何工具——含未来未知第三方/MCP——头行都带括号提示;
|
|
114
|
+
// 取首个非空参数做 k=v 摘要,args 完全为空才光头(无参数工具本无信息可显)。
|
|
115
|
+
for (const [k, v] of Object.entries(a)) {
|
|
116
|
+
if (v === undefined || v === null || v === "") continue;
|
|
117
|
+
let text: string;
|
|
118
|
+
if (typeof v === "string") text = v;
|
|
119
|
+
else if (Array.isArray(v)) text = v.join("; ");
|
|
120
|
+
else { try { text = JSON.stringify(v); } catch { text = String(v); } }
|
|
121
|
+
if (text.trim()) return { text: `${k}: ${oneLine(text)}`, path: false };
|
|
122
|
+
}
|
|
113
123
|
return { text: "", path: false };
|
|
114
124
|
}
|
|
115
125
|
export function headerOf(v: ToolView, theme: any, width: number): string[] {
|
package/src/claude-view.ts
CHANGED
|
@@ -303,8 +303,15 @@ export function createClaudeView(
|
|
|
303
303
|
: 0;
|
|
304
304
|
for (const native of nativeChildren) {
|
|
305
305
|
if (!native || typeof native.render !== "function") continue;
|
|
306
|
+
// 第三方子组件 render 可能抛异常或返回非数组(防御:不炸整块,退化为单行错误提示)。
|
|
307
|
+
let raw: string[];
|
|
308
|
+
try {
|
|
309
|
+
const out = native.render(bodyWidth);
|
|
310
|
+
raw = Array.isArray(out) ? blankTrim(out) : [];
|
|
311
|
+
} catch (error) {
|
|
312
|
+
raw = [fg(theme(), "error", `renderer error: ${error instanceof Error ? error.message : String(error)}`)];
|
|
313
|
+
}
|
|
306
314
|
// pi 的 renderResult 以 "\n" 开头分段;剥掉首尾空行,别让 ⎿ 拐角行空挂。
|
|
307
|
-
let raw: string[] = blankTrim(native.render(bodyWidth));
|
|
308
315
|
if (cmCalls > 0 && rowsLeft !== Infinity) {
|
|
309
316
|
// calls 段与 stdout 段以一个分段空行相隔(pi renderResult 用 join("\n\n") 分段)。
|
|
310
317
|
const sep = raw.findIndex(isBlank);
|
|
@@ -368,9 +375,11 @@ export function createClaudeView(
|
|
|
368
375
|
const head = headerOf(v, t, width), body = foreignBody(self, width, v.expanded);
|
|
369
376
|
lines = [...head, ...(body?.lines ?? [])];
|
|
370
377
|
entries = [{ component: toggleTool(self), height: head.length }, ...(body?.entries ?? [])];
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
|
|
378
|
+
if (!body?.lines.length) {
|
|
379
|
+
// 空 body 占位:运行中(含 result 未回的非流式调用)显示 Running…,完成但无输出显示 (no output),卡片不光杆。
|
|
380
|
+
const label = v.partial || self.result === undefined ? (v.started ? "Running…" : "Waiting…") : "(no output)";
|
|
381
|
+
const placeholder = prefixed(fg(t, "dim", label), width, resultIndent(t).first, RESULT_CONT());
|
|
382
|
+
lines.push(...placeholder); entries.push({ component: {}, height: placeholder.length });
|
|
374
383
|
}
|
|
375
384
|
} else {
|
|
376
385
|
const painted = paintTool(v, t, width, config);
|
package/src/generic-card.ts
CHANGED
|
@@ -40,22 +40,42 @@ function firstMeaningfulLine(lines: readonly string[]): string {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/** JSON 内容的智能首行:query/title 等字段优先,数组给条数,否则紧凑化。 */
|
|
43
|
+
/** 无语义键:预览跳过(id/时间戳等对读者无信息量)。 */
|
|
44
|
+
const JSON_PREVIEW_SKIP_KEYS = new Set(["id", "responseId", "requestId", "ts", "timestamp", "createdAt", "updatedAt"]);
|
|
45
|
+
|
|
46
|
+
/** 已 parse 的 JSON 摘要:query/queries/provider 等语义字段优先,数组给条数,否则紧凑化。 */
|
|
47
|
+
function summarizeJson(parsed: unknown): string | undefined {
|
|
48
|
+
if (Array.isArray(parsed)) return `${parsed.length} item${parsed.length === 1 ? "" : "s"}`;
|
|
49
|
+
if (parsed && typeof parsed === "object") {
|
|
50
|
+
const record = parsed as Record<string, unknown>;
|
|
51
|
+
const queries = record.queries;
|
|
52
|
+
if (Array.isArray(queries) && queries.length) return `queries: ${queries.map(String).join("; ")}`;
|
|
53
|
+
for (const key of ["query", "q", "title", "summary", "text", "message", "provider", "providers"]) {
|
|
54
|
+
const value = record[key];
|
|
55
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
56
|
+
if (Array.isArray(value) && value.length && value.every((v) => typeof v === "string")) return value.join("; ");
|
|
57
|
+
}
|
|
58
|
+
const firstString = Object.entries(record).find(([k, v]) => typeof v === "string" && String(v).trim() && !JSON_PREVIEW_SKIP_KEYS.has(k));
|
|
59
|
+
if (firstString !== undefined) return String(firstString[1]);
|
|
60
|
+
return JSON.stringify(record).replace(/\s+/g, " ").slice(0, 200);
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** JSON 智能预览:允许 JSON 外裹文本(markdown 等)——从首个 {/[ 开头行逐行累积 parse,成功即摘要。 */
|
|
43
66
|
function jsonPreview(text: string): string | undefined {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
const firstString = Object.values(parsed).find((v) => typeof v === "string" && String(v).trim());
|
|
55
|
-
if (firstString !== undefined) return String(firstString);
|
|
56
|
-
return trimmed.replace(/\s+/g, " ").slice(0, 200);
|
|
67
|
+
const lines = text.trim().split("\n");
|
|
68
|
+
for (let i = 0; i < lines.length; i++) {
|
|
69
|
+
const head = lines[i].trim();
|
|
70
|
+
if (!head.startsWith("{") && !head.startsWith("[")) continue;
|
|
71
|
+
let buffer = "";
|
|
72
|
+
for (let j = i; j < lines.length && j - i < 200; j++) {
|
|
73
|
+
buffer += `${lines[j]}\n`;
|
|
74
|
+
try {
|
|
75
|
+
return summarizeJson(JSON.parse(buffer));
|
|
76
|
+
} catch { /* 继续累积 */ }
|
|
57
77
|
}
|
|
58
|
-
}
|
|
78
|
+
}
|
|
59
79
|
return undefined;
|
|
60
80
|
}
|
|
61
81
|
|