drexler 0.2.14 → 0.2.15
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 +7 -1
- package/package.json +1 -1
- package/src/commands.ts +127 -20
- package/src/config.ts +141 -32
- package/src/conversation.ts +0 -4
- package/src/index.ts +68 -5
- package/src/pet/petState.ts +408 -0
- package/src/repl.ts +1 -1
- package/src/ui/App.tsx +543 -144
- package/src/ui/CommandPalette.tsx +2 -0
- package/src/ui/DealDeskHeader.tsx +0 -5
- package/src/ui/DeathScreen.tsx +110 -0
- package/src/ui/MarkdownBody.tsx +29 -5
- package/src/ui/MascotIntro.tsx +158 -57
- package/src/ui/Message.tsx +2 -105
- package/src/ui/PetPanel.tsx +537 -0
- package/src/ui/TranscriptViewport.tsx +206 -48
- package/src/ui/displayContent.ts +5 -2
|
@@ -29,6 +29,8 @@ const COMMAND_HINTS: Record<string, string> = {
|
|
|
29
29
|
"/save": "/save deal-notes.md",
|
|
30
30
|
"/save-last": "/save-last last-response.md",
|
|
31
31
|
"/copy-last": "copy latest response",
|
|
32
|
+
"/setup": "show config + key source",
|
|
33
|
+
"/update": "bun update -g drexler --latest",
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
const ARGUMENT_TITLES: Record<string, { title: string; hint: string }> = {
|
|
@@ -6,13 +6,8 @@ import { useTheme } from "./ThemeContext.tsx";
|
|
|
6
6
|
export type DealDeskHeaderStatus = "idle" | "streaming" | "error";
|
|
7
7
|
|
|
8
8
|
export interface DealDeskHeaderProps {
|
|
9
|
-
model: string;
|
|
10
9
|
mood: string;
|
|
11
10
|
messageCount: number;
|
|
12
|
-
themeName?: string;
|
|
13
|
-
approximateTokens?: number;
|
|
14
|
-
latencyMs?: number | null;
|
|
15
|
-
fallbackModel?: string | null;
|
|
16
11
|
status?: DealDeskHeaderStatus;
|
|
17
12
|
compact?: boolean;
|
|
18
13
|
notice?: string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { useTheme } from "./ThemeContext.tsx";
|
|
3
|
+
|
|
4
|
+
// Five distinct death variants
|
|
5
|
+
const VARIANTS = [
|
|
6
|
+
{
|
|
7
|
+
headline: "STAKEHOLDERS IN SHAMBLES",
|
|
8
|
+
lines: [
|
|
9
|
+
"The board convenes in emergency session.",
|
|
10
|
+
"Markets react with characteristic cruelty.",
|
|
11
|
+
"Analyst consensus revised to: AVOID.",
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
headline: "PIPELINE: BONE DRY. BOARD: DEVASTATED.",
|
|
16
|
+
lines: [
|
|
17
|
+
"Emergency restructuring announced immediately.",
|
|
18
|
+
"Remaining staff issued terse memo: 'hang tight.'",
|
|
19
|
+
"The deal room is very, very quiet.",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
headline: "ANALYSTS REVISE TARGET PRICE TO ZERO",
|
|
24
|
+
lines: [
|
|
25
|
+
"Short sellers: vindicated, quietly smug.",
|
|
26
|
+
"Drexler could not be reached for comment.",
|
|
27
|
+
"His last email had a typo. The irony.",
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
headline: "SEC OPENS INQUIRY INTO CIRCUMSTANCES",
|
|
32
|
+
lines: [
|
|
33
|
+
"CNBC coverage: 47 minutes, then nothing.",
|
|
34
|
+
"Drexler's legacy: a half-finished term sheet.",
|
|
35
|
+
"The coffee mug on his desk: still warm.",
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
headline: "EMERGENCY CALL SCHEDULED FOR 7AM MONDAY",
|
|
40
|
+
lines: [
|
|
41
|
+
"Consensus: it could have been prevented.",
|
|
42
|
+
"The plant on his desk is already wilting.",
|
|
43
|
+
"Recruiters texted his LinkedIn at 4am.",
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
const REASON_MSGS: Record<string, string> = {
|
|
49
|
+
hunger: "Cause: severe caloric deficiency. The pipeline, unreplenished, consumed itself.",
|
|
50
|
+
happiness: "Cause: total morale collapse. The board's confidence evaporated entirely.",
|
|
51
|
+
energy: "Cause: complete energy depletion. Drexler's systems ceased. Standups continued.",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Stock chart — backslash must be escaped in TS string literals
|
|
55
|
+
const CHART: string[] = [
|
|
56
|
+
" 100 ┤\\",
|
|
57
|
+
" │ \\",
|
|
58
|
+
" │ \\",
|
|
59
|
+
" 50 ┤ \\",
|
|
60
|
+
" │ \\",
|
|
61
|
+
" │ \\________________________________",
|
|
62
|
+
" 0 ┴───────────────────────────────────────",
|
|
63
|
+
" Q1 Q2 Q3 Q4 Q5 now →",
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const INNER_W = 44;
|
|
67
|
+
|
|
68
|
+
function banner(text: string): string {
|
|
69
|
+
const pad = Math.max(0, INNER_W - text.length);
|
|
70
|
+
const lp = Math.floor(pad / 2);
|
|
71
|
+
const rp = pad - lp;
|
|
72
|
+
return "║" + " ".repeat(lp) + text + " ".repeat(rp) + "║";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const TOP = "╔" + "═".repeat(INNER_W) + "╗";
|
|
76
|
+
const BOT = "╚" + "═".repeat(INNER_W) + "╝";
|
|
77
|
+
|
|
78
|
+
interface Props {
|
|
79
|
+
reason?: string;
|
|
80
|
+
variant?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function DeathScreen({ reason = "energy", variant = 0 }: Props) {
|
|
84
|
+
const t = useTheme();
|
|
85
|
+
const v = VARIANTS[variant % VARIANTS.length] ?? VARIANTS[0];
|
|
86
|
+
const reasonMsg = REASON_MSGS[reason] ?? REASON_MSGS.energy;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Box flexDirection="column" paddingX={2} paddingY={1}>
|
|
90
|
+
<Text color={t.error} bold>{TOP}</Text>
|
|
91
|
+
<Text color={t.error} bold>{banner("D R E X L E R H A S D I E D")}</Text>
|
|
92
|
+
<Text color={t.error} bold>{BOT}</Text>
|
|
93
|
+
<Text> </Text>
|
|
94
|
+
<Text color={t.warning} bold> {v.headline}</Text>
|
|
95
|
+
{v.lines.map((line, i) => (
|
|
96
|
+
<Text key={i} color={t.dim}> {line}</Text>
|
|
97
|
+
))}
|
|
98
|
+
<Text> </Text>
|
|
99
|
+
<Text color={t.primaryDim}> {reasonMsg}</Text>
|
|
100
|
+
<Text> </Text>
|
|
101
|
+
<Text color={t.primaryDim}> DRXL Share Price:</Text>
|
|
102
|
+
{CHART.map((line, i) => (
|
|
103
|
+
<Text key={i} color={i < 6 ? t.error : t.dim}> {line}</Text>
|
|
104
|
+
))}
|
|
105
|
+
<Text> </Text>
|
|
106
|
+
<Text color={t.dim}> Stats reset to 50% on next launch.</Text>
|
|
107
|
+
<Text color={t.dim}> Exiting in 5 seconds...</Text>
|
|
108
|
+
</Box>
|
|
109
|
+
);
|
|
110
|
+
}
|
package/src/ui/MarkdownBody.tsx
CHANGED
|
@@ -23,9 +23,20 @@ type Block =
|
|
|
23
23
|
const BULLET_RE = /^(\s*)([*+\-]|\d+\.)\s+(.*)$/;
|
|
24
24
|
const HEADING_RE = /^(#{1,6})\s+(.*)$/;
|
|
25
25
|
const HR_RE = /^\s*([-*_])\1\1[-*_\s]*$/;
|
|
26
|
-
const FENCE_RE = /^\s
|
|
26
|
+
const FENCE_RE = /^\s*(`{3,}|~{3,})(.*)$/;
|
|
27
27
|
const QUOTE_RE = /^\s*>\s?(.*)$/;
|
|
28
28
|
|
|
29
|
+
function isFenceClose(line: string, marker: string): boolean {
|
|
30
|
+
const fenceChar = marker[0];
|
|
31
|
+
if (!fenceChar) return false;
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (trimmed.length < marker.length) return false;
|
|
34
|
+
for (const char of trimmed) {
|
|
35
|
+
if (char !== fenceChar) return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
export function tokenizeInline(input: string): InlineToken[] {
|
|
30
41
|
const tokens: InlineToken[] = [];
|
|
31
42
|
let buf = "";
|
|
@@ -87,7 +98,19 @@ export function tokenizeInline(input: string): InlineToken[] {
|
|
|
87
98
|
if (ch === "[") {
|
|
88
99
|
const closeBracket = input.indexOf("]", i + 1);
|
|
89
100
|
if (closeBracket !== -1 && input[closeBracket + 1] === "(") {
|
|
90
|
-
|
|
101
|
+
let depth = 1;
|
|
102
|
+
let closeParen = -1;
|
|
103
|
+
for (let k = closeBracket + 2; k < input.length; k++) {
|
|
104
|
+
const c = input[k];
|
|
105
|
+
if (c === "(") depth += 1;
|
|
106
|
+
else if (c === ")") {
|
|
107
|
+
depth -= 1;
|
|
108
|
+
if (depth === 0) {
|
|
109
|
+
closeParen = k;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
91
114
|
if (closeParen !== -1) {
|
|
92
115
|
flushBuf();
|
|
93
116
|
const text = input.slice(i + 1, closeBracket);
|
|
@@ -115,14 +138,15 @@ export function parseBlocks(input: string): Block[] {
|
|
|
115
138
|
const line = lines[i]!;
|
|
116
139
|
const fence = FENCE_RE.exec(line);
|
|
117
140
|
if (fence) {
|
|
118
|
-
const
|
|
141
|
+
const marker = fence[1]!;
|
|
142
|
+
const lang = fence[2]?.trim() || undefined;
|
|
119
143
|
const codeLines: string[] = [];
|
|
120
144
|
i += 1;
|
|
121
|
-
while (i < lines.length && !
|
|
145
|
+
while (i < lines.length && !isFenceClose(lines[i]!, marker)) {
|
|
122
146
|
codeLines.push(lines[i]!);
|
|
123
147
|
i += 1;
|
|
124
148
|
}
|
|
125
|
-
i += 1;
|
|
149
|
+
if (i < lines.length) i += 1;
|
|
126
150
|
blocks.push({ kind: "code", lang, lines: codeLines });
|
|
127
151
|
continue;
|
|
128
152
|
}
|
package/src/ui/MascotIntro.tsx
CHANGED
|
@@ -136,6 +136,119 @@ const SPLIT_DIVIDER_ROWS: number[] = Array.from(
|
|
|
136
136
|
);
|
|
137
137
|
const BOOT_BAR_WIDTH = MASCOT_WIDTH - 1;
|
|
138
138
|
|
|
139
|
+
// Width breakpoints (terminal columns).
|
|
140
|
+
const TINY_BREAKPOINT = 21;
|
|
141
|
+
const NARROW_BREAKPOINT = 24;
|
|
142
|
+
const COMPACT_BREAKPOINT = 72;
|
|
143
|
+
const WIDE_BREAKPOINT = 112;
|
|
144
|
+
|
|
145
|
+
// Inner-panel sizing floors / glue.
|
|
146
|
+
const MIN_DASHBOARD_WIDTH = 28;
|
|
147
|
+
const MIN_INNER_WIDTH = 24;
|
|
148
|
+
const MIN_COPY_WIDTH = 18;
|
|
149
|
+
const MIN_RIGHT_COLUMN_WIDTH = 20;
|
|
150
|
+
const MIN_MOOD_PANEL_WIDTH = 18;
|
|
151
|
+
const MAX_MOOD_PANEL_WIDTH = 44;
|
|
152
|
+
const RIGHT_COLUMN_INSET = 1;
|
|
153
|
+
const RIGHT_COLUMN_PAD_RIGHT = 1;
|
|
154
|
+
const LEFT_PANEL_MIN_COPY = 24;
|
|
155
|
+
|
|
156
|
+
export type MascotLayoutMode = "tiny" | "compact" | "stacked" | "split";
|
|
157
|
+
|
|
158
|
+
export interface MascotPanelBox {
|
|
159
|
+
width: number;
|
|
160
|
+
inset: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface MascotLayout {
|
|
164
|
+
mode: MascotLayoutMode;
|
|
165
|
+
available: number;
|
|
166
|
+
innerWidth: number;
|
|
167
|
+
leftPanel: MascotPanelBox;
|
|
168
|
+
rightColumn: MascotPanelBox;
|
|
169
|
+
rightChildWidth: number;
|
|
170
|
+
copy: MascotPanelBox;
|
|
171
|
+
mood: MascotPanelBox;
|
|
172
|
+
tips: MascotPanelBox;
|
|
173
|
+
dealDesk: MascotPanelBox;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function computeMascotLayout(width: number): MascotLayout {
|
|
177
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
178
|
+
if (safeWidth < TINY_BREAKPOINT) {
|
|
179
|
+
const w = safeWidth;
|
|
180
|
+
return {
|
|
181
|
+
mode: "tiny",
|
|
182
|
+
available: w,
|
|
183
|
+
innerWidth: w,
|
|
184
|
+
leftPanel: { width: w, inset: 0 },
|
|
185
|
+
rightColumn: { width: 0, inset: 0 },
|
|
186
|
+
rightChildWidth: 0,
|
|
187
|
+
copy: { width: w, inset: 0 },
|
|
188
|
+
mood: { width: w, inset: 0 },
|
|
189
|
+
tips: { width: w, inset: 0 },
|
|
190
|
+
dealDesk: { width: w, inset: 0 },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (safeWidth < COMPACT_BREAKPOINT) {
|
|
194
|
+
const w = Math.max(1, safeWidth - 1);
|
|
195
|
+
return {
|
|
196
|
+
mode: "compact",
|
|
197
|
+
available: w,
|
|
198
|
+
innerWidth: w,
|
|
199
|
+
leftPanel: { width: w, inset: 1 },
|
|
200
|
+
rightColumn: { width: 0, inset: 0 },
|
|
201
|
+
rightChildWidth: 0,
|
|
202
|
+
copy: { width: w, inset: 0 },
|
|
203
|
+
mood: { width: w, inset: 0 },
|
|
204
|
+
tips: { width: w, inset: 0 },
|
|
205
|
+
dealDesk: { width: w, inset: 0 },
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const available = Math.max(MIN_DASHBOARD_WIDTH, safeWidth);
|
|
209
|
+
const innerWidth = Math.max(MIN_INNER_WIDTH, available - FRAME_CHROME_WIDTH);
|
|
210
|
+
if (safeWidth < WIDE_BREAKPOINT) {
|
|
211
|
+
return {
|
|
212
|
+
mode: "stacked",
|
|
213
|
+
available,
|
|
214
|
+
innerWidth,
|
|
215
|
+
leftPanel: { width: innerWidth, inset: 0 },
|
|
216
|
+
rightColumn: { width: 0, inset: 0 },
|
|
217
|
+
rightChildWidth: innerWidth,
|
|
218
|
+
copy: { width: innerWidth, inset: 0 },
|
|
219
|
+
mood: { width: innerWidth, inset: 0 },
|
|
220
|
+
tips: { width: innerWidth, inset: 0 },
|
|
221
|
+
dealDesk: { width: innerWidth, inset: 0 },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const leftPanelWidth = Math.max(
|
|
225
|
+
MASCOT_WIDTH + GUTTER_WIDTH + LEFT_PANEL_MIN_COPY,
|
|
226
|
+
Math.floor((innerWidth - SPLIT_DIVIDER_WIDTH) / 2),
|
|
227
|
+
);
|
|
228
|
+
const rightColumnWidth = Math.max(
|
|
229
|
+
MIN_RIGHT_COLUMN_WIDTH,
|
|
230
|
+
innerWidth - leftPanelWidth - SPLIT_DIVIDER_WIDTH,
|
|
231
|
+
);
|
|
232
|
+
const rightInner = Math.max(1, rightColumnWidth - RIGHT_COLUMN_PAD_RIGHT);
|
|
233
|
+
const rightChildWidth = Math.max(1, rightInner - RIGHT_COLUMN_INSET);
|
|
234
|
+
const copyWidth = Math.max(
|
|
235
|
+
MIN_COPY_WIDTH,
|
|
236
|
+
leftPanelWidth - MASCOT_WIDTH - GUTTER_WIDTH - 1,
|
|
237
|
+
);
|
|
238
|
+
return {
|
|
239
|
+
mode: "split",
|
|
240
|
+
available,
|
|
241
|
+
innerWidth,
|
|
242
|
+
leftPanel: { width: leftPanelWidth, inset: 0 },
|
|
243
|
+
rightColumn: { width: rightColumnWidth, inset: 0 },
|
|
244
|
+
rightChildWidth,
|
|
245
|
+
copy: { width: copyWidth, inset: 0 },
|
|
246
|
+
mood: { width: copyWidth, inset: 0 },
|
|
247
|
+
tips: { width: rightChildWidth, inset: RIGHT_COLUMN_INSET },
|
|
248
|
+
dealDesk: { width: rightChildWidth, inset: RIGHT_COLUMN_INSET },
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
139
252
|
interface IntroProps {
|
|
140
253
|
greeting: string;
|
|
141
254
|
}
|
|
@@ -219,11 +332,13 @@ function fixedDisplayRows(
|
|
|
219
332
|
type IntroColorPhase = "early" | "middle" | "late";
|
|
220
333
|
|
|
221
334
|
function introTotalFrames(width: number): number {
|
|
222
|
-
return width <
|
|
335
|
+
return width < COMPACT_BREAKPOINT
|
|
336
|
+
? COMPACT_INTRO_NOTES.length
|
|
337
|
+
: INTRO_FRAMES.length;
|
|
223
338
|
}
|
|
224
339
|
|
|
225
340
|
function introFrameDelayMs(frameIdx: number, width: number): number {
|
|
226
|
-
if (width <
|
|
341
|
+
if (width < COMPACT_BREAKPOINT) return COMPACT_INTRO_DELAY_MS;
|
|
227
342
|
return (
|
|
228
343
|
INTRO_FRAMES[frameIdx] ?? INTRO_FRAMES[INTRO_FRAMES.length - 1]!
|
|
229
344
|
).delayMs;
|
|
@@ -247,7 +362,7 @@ export function introPhaseColor(
|
|
|
247
362
|
}
|
|
248
363
|
|
|
249
364
|
function introSnapshot(frameIdx: number, width: number) {
|
|
250
|
-
const compact = width <
|
|
365
|
+
const compact = width < COMPACT_BREAKPOINT;
|
|
251
366
|
const total = introTotalFrames(width);
|
|
252
367
|
const boundedFrameIdx = Math.min(frameIdx, total - 1);
|
|
253
368
|
const state =
|
|
@@ -545,7 +660,7 @@ function MoodReadout({
|
|
|
545
660
|
.toString()
|
|
546
661
|
.padStart(3, " ")}%`;
|
|
547
662
|
|
|
548
|
-
if (width <
|
|
663
|
+
if (width < NARROW_BREAKPOINT) {
|
|
549
664
|
const tinyText =
|
|
550
665
|
boundedProgress >= 1
|
|
551
666
|
? `${normalizedMood} / ${posture.badge}`
|
|
@@ -563,7 +678,7 @@ function MoodReadout({
|
|
|
563
678
|
);
|
|
564
679
|
}
|
|
565
680
|
|
|
566
|
-
const panelWidth = Math.max(
|
|
681
|
+
const panelWidth = Math.max(MIN_MOOD_PANEL_WIDTH, Math.min(MAX_MOOD_PANEL_WIDTH, width));
|
|
567
682
|
const innerWidth = Math.max(1, panelWidth - 4);
|
|
568
683
|
const title = "Mood";
|
|
569
684
|
const isSettled = boundedProgress >= 1;
|
|
@@ -678,42 +793,15 @@ export function MascotDashboard({
|
|
|
678
793
|
}: MascotDashboardProps) {
|
|
679
794
|
const t = useTheme();
|
|
680
795
|
const resolvedBarColor = barColor ?? t.primaryLight;
|
|
681
|
-
const
|
|
682
|
-
const
|
|
683
|
-
const sideBySide = width >= 112;
|
|
684
|
-
const available = compact ? Math.max(1, width - 1) : Math.max(28, width);
|
|
685
|
-
const innerWidth = compact
|
|
686
|
-
? available
|
|
687
|
-
: Math.max(24, available - FRAME_CHROME_WIDTH);
|
|
688
|
-
const leftPanelWidth = compact
|
|
689
|
-
? available
|
|
690
|
-
: sideBySide
|
|
691
|
-
? Math.max(
|
|
692
|
-
MASCOT_WIDTH + GUTTER_WIDTH + 24,
|
|
693
|
-
Math.floor((innerWidth - SPLIT_DIVIDER_WIDTH) / 2),
|
|
694
|
-
)
|
|
695
|
-
: innerWidth;
|
|
696
|
-
const rightColumnWidth = sideBySide
|
|
697
|
-
? Math.max(20, innerWidth - leftPanelWidth - SPLIT_DIVIDER_WIDTH)
|
|
698
|
-
: innerWidth;
|
|
699
|
-
const rightInnerWidth = sideBySide
|
|
700
|
-
? Math.max(1, rightColumnWidth - 1)
|
|
701
|
-
: rightColumnWidth;
|
|
702
|
-
const tipsWidth = sideBySide
|
|
703
|
-
? rightInnerWidth
|
|
704
|
-
: innerWidth;
|
|
705
|
-
const copyWidth = compact
|
|
706
|
-
? available
|
|
707
|
-
: sideBySide
|
|
708
|
-
? Math.max(18, leftPanelWidth - MASCOT_WIDTH - GUTTER_WIDTH - 1)
|
|
709
|
-
: innerWidth;
|
|
796
|
+
const layout = computeMascotLayout(width);
|
|
797
|
+
const sideBySide = layout.mode === "split";
|
|
710
798
|
const wideGreetingRows = sideBySide
|
|
711
|
-
? fixedDisplayRows(greeting,
|
|
799
|
+
? fixedDisplayRows(greeting, layout.copy.width, 2)
|
|
712
800
|
: [];
|
|
713
801
|
|
|
714
|
-
if (
|
|
802
|
+
if (layout.mode === "tiny") {
|
|
715
803
|
return (
|
|
716
|
-
<Box width={available} flexDirection="column">
|
|
804
|
+
<Box width={layout.available} flexDirection="column">
|
|
717
805
|
<Text color={resolvedBarColor}>{mascotStatus}</Text>
|
|
718
806
|
<Text bold color={t.primaryLight}>
|
|
719
807
|
Drexler™
|
|
@@ -724,17 +812,23 @@ export function MascotDashboard({
|
|
|
724
812
|
mood={mood}
|
|
725
813
|
progress={bootProgress}
|
|
726
814
|
progressColor={resolvedBarColor}
|
|
727
|
-
width={
|
|
815
|
+
width={layout.mood.width}
|
|
728
816
|
/>
|
|
729
817
|
</Box>
|
|
730
|
-
{dealDesk ?
|
|
818
|
+
{dealDesk ? (
|
|
819
|
+
<Box marginTop={1}>{dealDesk(layout.dealDesk.width)}</Box>
|
|
820
|
+
) : null}
|
|
731
821
|
</Box>
|
|
732
822
|
);
|
|
733
823
|
}
|
|
734
824
|
|
|
735
|
-
if (compact) {
|
|
825
|
+
if (layout.mode === "compact") {
|
|
736
826
|
return (
|
|
737
|
-
<Box
|
|
827
|
+
<Box
|
|
828
|
+
marginLeft={layout.leftPanel.inset}
|
|
829
|
+
width={layout.available}
|
|
830
|
+
flexDirection="column"
|
|
831
|
+
>
|
|
738
832
|
<Text color={resolvedBarColor}>{bar}</Text>
|
|
739
833
|
<Text color={resolvedBarColor}>{mascotStatus}</Text>
|
|
740
834
|
<Text bold color={t.primaryLight}>
|
|
@@ -746,25 +840,30 @@ export function MascotDashboard({
|
|
|
746
840
|
mood={mood}
|
|
747
841
|
progress={bootProgress}
|
|
748
842
|
progressColor={resolvedBarColor}
|
|
749
|
-
width={
|
|
843
|
+
width={layout.mood.width}
|
|
750
844
|
/>
|
|
751
845
|
</Box>
|
|
752
|
-
{dealDesk ?
|
|
846
|
+
{dealDesk ? (
|
|
847
|
+
<Box marginTop={1}>{dealDesk(layout.dealDesk.width)}</Box>
|
|
848
|
+
) : null}
|
|
753
849
|
</Box>
|
|
754
850
|
);
|
|
755
851
|
}
|
|
756
852
|
|
|
757
853
|
return (
|
|
758
|
-
<Box width={available}>
|
|
854
|
+
<Box width={layout.available}>
|
|
759
855
|
<Box
|
|
760
|
-
width={available}
|
|
856
|
+
width={layout.available}
|
|
761
857
|
borderStyle="round"
|
|
762
858
|
borderColor={t.primary}
|
|
763
859
|
paddingX={1}
|
|
764
860
|
flexDirection={sideBySide ? "row" : "column"}
|
|
765
861
|
alignItems={sideBySide ? "flex-start" : "center"}
|
|
766
862
|
>
|
|
767
|
-
<Box
|
|
863
|
+
<Box
|
|
864
|
+
flexDirection={sideBySide ? "row" : "column"}
|
|
865
|
+
width={layout.leftPanel.width}
|
|
866
|
+
>
|
|
768
867
|
<Box
|
|
769
868
|
width={MASCOT_WIDTH}
|
|
770
869
|
flexShrink={0}
|
|
@@ -778,7 +877,7 @@ export function MascotDashboard({
|
|
|
778
877
|
<Box
|
|
779
878
|
flexDirection="column"
|
|
780
879
|
justifyContent="center"
|
|
781
|
-
width={
|
|
880
|
+
width={layout.copy.width}
|
|
782
881
|
marginTop={sideBySide ? 1 : 0}
|
|
783
882
|
>
|
|
784
883
|
<Text bold color={t.primaryLight}>
|
|
@@ -799,7 +898,7 @@ export function MascotDashboard({
|
|
|
799
898
|
mood={mood}
|
|
800
899
|
progress={bootProgress}
|
|
801
900
|
progressColor={resolvedBarColor}
|
|
802
|
-
width={
|
|
901
|
+
width={layout.mood.width}
|
|
803
902
|
/>
|
|
804
903
|
</Box>
|
|
805
904
|
</Box>
|
|
@@ -814,23 +913,25 @@ export function MascotDashboard({
|
|
|
814
913
|
</Box>
|
|
815
914
|
<Box
|
|
816
915
|
flexDirection="column"
|
|
817
|
-
width={
|
|
818
|
-
paddingRight={
|
|
916
|
+
width={layout.rightColumn.width}
|
|
917
|
+
paddingRight={RIGHT_COLUMN_PAD_RIGHT}
|
|
819
918
|
>
|
|
820
|
-
<Box marginLeft={
|
|
821
|
-
<TipsPanel width={
|
|
919
|
+
<Box marginLeft={layout.tips.inset}>
|
|
920
|
+
<TipsPanel width={layout.tips.width} />
|
|
822
921
|
</Box>
|
|
823
922
|
{dealDesk ? (
|
|
824
|
-
<Box marginLeft={
|
|
825
|
-
{dealDesk(
|
|
923
|
+
<Box marginLeft={layout.dealDesk.inset}>
|
|
924
|
+
{dealDesk(layout.dealDesk.width)}
|
|
826
925
|
</Box>
|
|
827
926
|
) : null}
|
|
828
927
|
</Box>
|
|
829
928
|
</>
|
|
830
929
|
) : (
|
|
831
|
-
<Box marginTop={1} width={
|
|
832
|
-
<TipsPanel width={
|
|
833
|
-
{dealDesk ?
|
|
930
|
+
<Box marginTop={1} width={layout.tips.width} flexDirection="column">
|
|
931
|
+
<TipsPanel width={layout.tips.width} />
|
|
932
|
+
{dealDesk ? (
|
|
933
|
+
<Box marginTop={1}>{dealDesk(layout.dealDesk.width)}</Box>
|
|
934
|
+
) : null}
|
|
834
935
|
</Box>
|
|
835
936
|
)}
|
|
836
937
|
</Box>
|
package/src/ui/Message.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Box, Text } from "ink";
|
|
2
|
-
import { memo
|
|
3
|
-
import { renderMarkdown } from "../renderer.ts";
|
|
2
|
+
import { memo } from "react";
|
|
4
3
|
import {
|
|
5
4
|
firstDisplayLine,
|
|
6
5
|
normalizeAssistantDisplayContent,
|
|
@@ -10,109 +9,7 @@ import { fitDisplayText } from "./graphemes.ts";
|
|
|
10
9
|
import { MarkdownBody } from "./MarkdownBody.tsx";
|
|
11
10
|
import { useTheme } from "./ThemeContext.tsx";
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
role: "user" | "assistant" | "system";
|
|
15
|
-
content: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const SEPARATOR_WIDTH = 44;
|
|
19
|
-
|
|
20
|
-
const ROLE_LABELS: Record<MessageItem["role"], string> = {
|
|
21
|
-
user: "YOU",
|
|
22
|
-
assistant: "DREXLER",
|
|
23
|
-
system: "SYSTEM",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function Separator() {
|
|
27
|
-
const t = useTheme();
|
|
28
|
-
return (
|
|
29
|
-
<Box paddingX={1} marginBottom={1} flexShrink={1}>
|
|
30
|
-
<Text color={t.primaryDim} wrap="truncate">
|
|
31
|
-
{"─".repeat(SEPARATOR_WIDTH)}
|
|
32
|
-
</Text>
|
|
33
|
-
</Box>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function MessageInner({ role, content }: MessageItem) {
|
|
38
|
-
const t = useTheme();
|
|
39
|
-
const displayContent =
|
|
40
|
-
role === "assistant"
|
|
41
|
-
? normalizeAssistantMarkdownRenderContent(content)
|
|
42
|
-
: content;
|
|
43
|
-
const assistantLines = useMemo(
|
|
44
|
-
() =>
|
|
45
|
-
role === "assistant"
|
|
46
|
-
? renderMarkdown(displayContent).trimEnd().split("\n")
|
|
47
|
-
: [],
|
|
48
|
-
[displayContent, role],
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
if (role === "user") {
|
|
52
|
-
return (
|
|
53
|
-
<>
|
|
54
|
-
<Box paddingX={1} marginBottom={1} flexDirection="column">
|
|
55
|
-
<Box>
|
|
56
|
-
<Text color={t.primaryLight} bold>
|
|
57
|
-
{ROLE_LABELS.user}
|
|
58
|
-
</Text>
|
|
59
|
-
<Text color={t.primaryDim}> ─ </Text>
|
|
60
|
-
<Text color={t.dim}>incoming memo</Text>
|
|
61
|
-
</Box>
|
|
62
|
-
<Box paddingLeft={1}>
|
|
63
|
-
<Text color={t.primary}>› </Text>
|
|
64
|
-
<Text color={t.text} wrap="wrap">
|
|
65
|
-
{content}
|
|
66
|
-
</Text>
|
|
67
|
-
</Box>
|
|
68
|
-
</Box>
|
|
69
|
-
</>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
if (role === "system") {
|
|
73
|
-
return (
|
|
74
|
-
<Box paddingX={1} marginBottom={1} flexDirection="column">
|
|
75
|
-
<Box>
|
|
76
|
-
<Text color={t.warning} bold>
|
|
77
|
-
{ROLE_LABELS.system}
|
|
78
|
-
</Text>
|
|
79
|
-
<Text color={t.primaryDim}> ─ </Text>
|
|
80
|
-
<Text color={t.dim}>notice</Text>
|
|
81
|
-
</Box>
|
|
82
|
-
<Box paddingLeft={1}>
|
|
83
|
-
<Text color={t.dim} italic wrap="wrap">
|
|
84
|
-
{content}
|
|
85
|
-
</Text>
|
|
86
|
-
</Box>
|
|
87
|
-
</Box>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<>
|
|
93
|
-
<Box flexDirection="column" marginBottom={1} paddingX={1}>
|
|
94
|
-
<Box>
|
|
95
|
-
<Text color={t.primaryLight} bold>
|
|
96
|
-
{ROLE_LABELS.assistant}
|
|
97
|
-
</Text>
|
|
98
|
-
<Text color={t.primaryDim}> ─ </Text>
|
|
99
|
-
<Text color={t.dim}>response ledger</Text>
|
|
100
|
-
</Box>
|
|
101
|
-
{assistantLines.map((ln, i) => (
|
|
102
|
-
<Box key={i} paddingLeft={1}>
|
|
103
|
-
<Text color={i === 0 ? t.primary : t.primaryDim}>│ </Text>
|
|
104
|
-
<Text color={t.text} wrap="wrap">
|
|
105
|
-
{ln}
|
|
106
|
-
</Text>
|
|
107
|
-
</Box>
|
|
108
|
-
))}
|
|
109
|
-
</Box>
|
|
110
|
-
<Separator />
|
|
111
|
-
</>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export const Message = memo(MessageInner);
|
|
12
|
+
const ROLE_LABELS = { assistant: "DREXLER" } as const;
|
|
116
13
|
|
|
117
14
|
interface StreamingProps {
|
|
118
15
|
content: string;
|