@yinan_chen/pi-claude-code-ui 0.7.0 → 0.8.1

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/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  ### 演示
6
6
 
7
- ![demo](assets/demo.mp4)
7
+ ![demo](https://github.com/user-attachments/assets/d264935a-79fc-434e-be97-05c8210144fe)
8
8
 
9
- > 若上方未内嵌播放,直接下载观看:[assets/demo.mp4](assets/demo.mp4)
9
+ 动图预览(不渲染视频的平台,如 npm): ![gif](assets/demo.gif)
10
10
 
11
11
  > 原名 lean-tidy —— Claude Code 风格的 Pi 界面增强扩展:Claude 式对话转录视图、富文本 diff 渲染、子代理面板与工作状态提示。
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yinan_chen/pi-claude-code-ui",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Claude Code-style UI for Pi: transcript view, rich diffs, subagent panels and working-message status.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,10 +28,34 @@ function labelOf(customType: string): string {
28
28
  return tail.charAt(0).toUpperCase() + tail.slice(1);
29
29
  }
30
30
 
31
- /** 内容来源:message.content / entry.data(对象优先取 name/description,否则 JSON)。 */
31
+ /** JSON 内容的智能首行:query/title 等字段优先,数组给条数,否则紧凑化。 */
32
+ function jsonPreview(text: string): string | undefined {
33
+ const trimmed = text.trim();
34
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return undefined;
35
+ try {
36
+ const parsed = JSON.parse(trimmed);
37
+ if (Array.isArray(parsed)) return `${parsed.length} item${parsed.length === 1 ? "" : "s"}`;
38
+ if (parsed && typeof parsed === "object") {
39
+ for (const key of ["query", "q", "title", "summary", "text", "message"]) {
40
+ const value = (parsed as Record<string, unknown>)[key];
41
+ if (typeof value === "string" && value.trim()) return value;
42
+ }
43
+ const firstString = Object.values(parsed).find((v) => typeof v === "string" && String(v).trim());
44
+ if (firstString !== undefined) return String(firstString);
45
+ return trimmed.replace(/\s+/g, " ").slice(0, 200);
46
+ }
47
+ } catch { /* 非 JSON 或超长,回退原文 */ }
48
+ return undefined;
49
+ }
50
+
51
+ /** 内容来源:message.content / entry.data(对象优先取 name/description,JSON 字符串智能摘要)。 */
32
52
  function bodyOf(payload: any): string {
33
53
  const source = typeof payload?.content === "string" ? payload.content : payload?.data;
34
- if (typeof source === "string") return source;
54
+ if (typeof source === "string") {
55
+ const preview = jsonPreview(source);
56
+ if (preview !== undefined) return preview;
57
+ return source;
58
+ }
35
59
  if (source && typeof source === "object") {
36
60
  const name = typeof source.name === "string" ? source.name : "";
37
61
  const desc = typeof source.description === "string" ? source.description : "";
@@ -95,8 +95,9 @@ function panelLines(d: any, expanded: boolean, theme: any, width: number): strin
95
95
  out.push(...prefixed(fg(theme, "dim", `transcript: ${d.outputFile}`), width, first, BRANCH_REST()));
96
96
  }
97
97
  } else if (typeof d?.resultPreview === "string" && d.resultPreview.trim()) {
98
- // 折叠态不显示正文,但保留可展开提示(有正文才提示)
99
- out.push(...prefixed(expandHint(theme), width, first, BRANCH_REST()));
98
+ // 折叠态不显示正文,但保留可展开提示(有正文才提示);格式与工具块 `… +N lines (ctrl+o to expand)` 一致。
99
+ const hidden = linesOf(d.resultPreview).length;
100
+ out.push(...prefixed(fg(theme, "dim", `… +${hidden} line${hidden === 1 ? "" : "s"}`) + expandHint(theme), width, first, BRANCH_REST()));
100
101
  }
101
102
  return out;
102
103
  }
@@ -17,6 +17,8 @@ export interface DiffRenderOptions {
17
17
  previousContent?: string;
18
18
  fileExistedBeforeWrite?: boolean;
19
19
  headerLabel?: string;
20
+ /** 折叠态隐藏的正文行数(write 折叠提示 `… +N lines` 用)。 */
21
+ collapsedHiddenLines?: number;
20
22
  /** Live hover state for the collapsed hint row (muted → text on hover). */
21
23
  isHovered?: () => boolean;
22
24
  invalidate?: () => void;
@@ -1,6 +1,6 @@
1
1
  import { Text, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
2
2
  import { sanitizeToolResultText } from "../tool-result-sanitize.ts";
3
- import { showMoreHintText } from "../show-more-hint.ts";
3
+ import { expandShortcutText } from "../show-more-hint.ts";
4
4
  import { splitWriteContentLines } from "./diff-text.ts";
5
5
  import {
6
6
  collectDiffStats,
@@ -291,21 +291,26 @@ function renderWriteOverwriteGuardRows(
291
291
  return renderSingleDiffRow(buildWriteOverwriteGuardText(guard, width), "warning", width, theme);
292
292
  }
293
293
 
294
- /** Folded write with `writeDiffCollapsedLines: 0`: `↳ created • click to show more`. */
294
+ /** Folded write with `writeDiffCollapsedLines: 0`: `… +N lines (ctrl+o to expand)`(与工具块/subagent 折叠提示同款;无行数时退化为裸快捷键提示)。 */
295
295
  function renderWriteCollapsedHintLine(
296
296
  wasOverwrite: boolean,
297
297
  width: number,
298
298
  theme: DiffTheme,
299
299
  hovered: boolean,
300
300
  headerLabel?: string,
301
+ hiddenCount?: number,
301
302
  ): string[] {
302
303
  const safeWidth = normalizeDiffRenderWidth(width);
303
304
  if (safeWidth === 0) {
304
305
  return [""];
305
306
  }
306
- const actionLabel = headerLabel?.trim() || (wasOverwrite ? "overwritten" : "created");
307
- const clickLabel = showMoreHintText();
308
- const candidates = [`↳ ${actionLabel} • ${clickLabel}`, `↳ ${actionLabel}`, actionLabel, "…"];
307
+ void wasOverwrite; void headerLabel;
308
+ const clickLabel = `(${expandShortcutText()} to expand)`;
309
+ const counted =
310
+ typeof hiddenCount === "number" && hiddenCount > 0
311
+ ? `… +${hiddenCount} line${hiddenCount === 1 ? "" : "s"} ${clickLabel}`
312
+ : "";
313
+ const candidates = [counted, clickLabel.slice(1, -1), "…"].filter((candidate) => candidate !== "");
309
314
  let text = candidates[candidates.length - 1] ?? "…";
310
315
  for (const candidate of candidates) {
311
316
  if (visibleWidth(candidate) <= safeWidth) {
@@ -446,6 +451,7 @@ export function renderWriteDiffResult(
446
451
  theme,
447
452
  hovered,
448
453
  options.headerLabel,
454
+ options.collapsedHiddenLines,
449
455
  );
450
456
  return cache.set(
451
457
  safeWidth,
@@ -67,13 +67,15 @@ export function renderRichToolResult(
67
67
  if (metadata.diffUnavailableReason) {
68
68
  return unavailableComponent(metadata.diffUnavailableReason, theme);
69
69
  }
70
+ const writeContent = typeof context?.args?.content === "string" ? context.args.content : undefined;
70
71
  return renderWriteDiffResult(
71
- typeof context?.args?.content === "string" ? context.args.content : undefined,
72
+ writeContent,
72
73
  {
73
74
  expanded,
74
75
  filePath,
75
76
  previousContent: metadata.previousContent,
76
77
  fileExistedBeforeWrite: metadata.fileExistedBeforeWrite,
78
+ collapsedHiddenLines: writeContent !== undefined ? writeContent.replace(/\r?\n$/, "").split(/\r?\n/).length : undefined,
77
79
  isHovered: options?.isHovered,
78
80
  invalidate: () => context?.invalidate?.(),
79
81
  },