drexler 0.2.7 → 0.2.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/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/src/ui/TranscriptViewport.tsx +68 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.8
|
|
4
|
+
|
|
5
|
+
- Improved transcript readability by wrapping long user and Drexler message lines instead of truncating them.
|
|
6
|
+
- Kept wrapped continuation rows visually aligned inside the existing turn blocks.
|
|
7
|
+
|
|
3
8
|
## 0.2.7
|
|
4
9
|
|
|
5
10
|
- Stabilized `/synergy` animation layout with fixed row budgeting, a capped centered event panel, and completion only at 100%.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box, Text } from "ink";
|
|
2
2
|
import { Children, memo, useMemo, type ReactNode } from "react";
|
|
3
|
-
import { displayWidth, fitDisplayText } from "./graphemes.ts";
|
|
3
|
+
import { displayWidth, fitDisplayText, splitGraphemes } from "./graphemes.ts";
|
|
4
4
|
import { useTheme } from "./ThemeContext.tsx";
|
|
5
5
|
|
|
6
6
|
export interface TranscriptViewportItem {
|
|
@@ -47,14 +47,71 @@ const ROLE_MARKERS: Record<TranscriptViewportItem["role"], string> = {
|
|
|
47
47
|
system: "!",
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
function wrapDisplayLine(input: string, maxWidth: number): string[] {
|
|
51
|
+
const width = Math.max(1, maxWidth);
|
|
52
|
+
if (input.length === 0) return [""];
|
|
53
|
+
if (displayWidth(input) <= width) return [input];
|
|
54
|
+
|
|
55
|
+
const parts = splitGraphemes(input);
|
|
56
|
+
const rows: string[] = [];
|
|
57
|
+
let current = "";
|
|
58
|
+
let lastBreakAt = -1;
|
|
59
|
+
|
|
60
|
+
for (const part of parts) {
|
|
61
|
+
const next = `${current}${part}`;
|
|
62
|
+
if (displayWidth(next) <= width) {
|
|
63
|
+
current = next;
|
|
64
|
+
if (/\s/u.test(part)) lastBreakAt = current.length;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (lastBreakAt > 0) {
|
|
69
|
+
const head = current.slice(0, lastBreakAt).trimEnd();
|
|
70
|
+
const tail = current.slice(lastBreakAt).trimStart();
|
|
71
|
+
rows.push(head);
|
|
72
|
+
current = `${tail}${part}`;
|
|
73
|
+
} else {
|
|
74
|
+
if (current.length > 0) rows.push(current);
|
|
75
|
+
current = part;
|
|
76
|
+
}
|
|
77
|
+
lastBreakAt = /\s/u.test(part) ? current.length : -1;
|
|
78
|
+
|
|
79
|
+
while (displayWidth(current) > width) {
|
|
80
|
+
let clipped = "";
|
|
81
|
+
for (const grapheme of splitGraphemes(current)) {
|
|
82
|
+
if (displayWidth(`${clipped}${grapheme}`) > width) break;
|
|
83
|
+
clipped += grapheme;
|
|
84
|
+
}
|
|
85
|
+
if (clipped.length === 0) {
|
|
86
|
+
const [first = ""] = splitGraphemes(current);
|
|
87
|
+
rows.push(fitDisplayText(first, width));
|
|
88
|
+
current = current.slice(first.length);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
rows.push(clipped);
|
|
92
|
+
current = current.slice(clipped.length);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
rows.push(current.trimEnd());
|
|
97
|
+
return rows.filter((row, index) => row.length > 0 || index === 0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function wrappedContentRows(content: string, width: number): string[] {
|
|
101
|
+
return content
|
|
102
|
+
.split("\n")
|
|
103
|
+
.flatMap((line) => wrapDisplayLine(line, width));
|
|
53
104
|
}
|
|
54
105
|
|
|
55
|
-
function itemRows(
|
|
106
|
+
function itemRows(
|
|
107
|
+
item: TranscriptViewportItem,
|
|
108
|
+
compact: boolean,
|
|
109
|
+
cols: number,
|
|
110
|
+
): number {
|
|
56
111
|
if (compact) return 1;
|
|
57
|
-
|
|
112
|
+
const bodyPrefix = `${ROLE_MARKERS[item.role]} `;
|
|
113
|
+
const contentWidth = Math.max(1, cols - displayWidth(bodyPrefix));
|
|
114
|
+
return 2 + wrappedContentRows(item.content, contentWidth).length;
|
|
58
115
|
}
|
|
59
116
|
|
|
60
117
|
function roleAccentColor(
|
|
@@ -129,13 +186,13 @@ function DefaultTranscriptItem({
|
|
|
129
186
|
cols,
|
|
130
187
|
)}
|
|
131
188
|
</Text>
|
|
132
|
-
{item.content
|
|
189
|
+
{wrappedContentRows(item.content, contentWidth).map((line, index) => (
|
|
133
190
|
<Box key={index} width={cols} flexShrink={1}>
|
|
134
191
|
<Text color={accent} bold={item.role === "user"}>
|
|
135
|
-
{bodyPrefix}
|
|
192
|
+
{index === 0 || item.role === "assistant" ? bodyPrefix : " "}
|
|
136
193
|
</Text>
|
|
137
|
-
<Text color={roleBodyColor(item.role, t)}
|
|
138
|
-
{
|
|
194
|
+
<Text color={roleBodyColor(item.role, t)}>
|
|
195
|
+
{line}
|
|
139
196
|
</Text>
|
|
140
197
|
</Box>
|
|
141
198
|
))}
|
|
@@ -172,7 +229,7 @@ function itemsToEntries({
|
|
|
172
229
|
) : (
|
|
173
230
|
<DefaultTranscriptItem item={item} compact={compact} cols={cols} />
|
|
174
231
|
),
|
|
175
|
-
estimatedRows: itemRows(item, compact),
|
|
232
|
+
estimatedRows: itemRows(item, compact, cols),
|
|
176
233
|
}));
|
|
177
234
|
}
|
|
178
235
|
|