drexler 0.2.14 → 0.2.16
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 +13 -0
- package/README.md +17 -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 +70 -5
- package/src/pet/petState.ts +408 -0
- package/src/repl.ts +1 -1
- package/src/ui/App.tsx +571 -148
- 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 +673 -0
- package/src/ui/TranscriptViewport.tsx +206 -48
- package/src/ui/displayContent.ts +5 -2
package/src/ui/App.tsx
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
import { Box, Text, useApp, useInput, useStdout } from "ink";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
accrueLifetimeDeals,
|
|
5
|
+
actionCooldown,
|
|
6
|
+
applyFeed,
|
|
7
|
+
applyMinuteDecay,
|
|
8
|
+
applyName,
|
|
9
|
+
applyPlay,
|
|
10
|
+
applyPraise,
|
|
11
|
+
applyRest,
|
|
12
|
+
applyVibe,
|
|
13
|
+
applyWork,
|
|
14
|
+
formatCooldownRemaining,
|
|
15
|
+
formatTenure,
|
|
16
|
+
getPetMood,
|
|
17
|
+
getPetRank,
|
|
18
|
+
isPetDead,
|
|
19
|
+
loadPetState,
|
|
20
|
+
petTenureMs,
|
|
21
|
+
rankLabel,
|
|
22
|
+
sanitizePetName,
|
|
23
|
+
savePetState,
|
|
24
|
+
stampAction,
|
|
25
|
+
type PetActionKey,
|
|
26
|
+
type PetActivity,
|
|
27
|
+
type PetStats,
|
|
28
|
+
} from "../pet/petState.ts";
|
|
29
|
+
import { DeathScreen } from "./DeathScreen.tsx";
|
|
30
|
+
import {
|
|
31
|
+
CompactPetPanel,
|
|
32
|
+
COMPACT_PET_PANEL_MIN_WIDTH,
|
|
33
|
+
COMPACT_PET_PANEL_ROWS,
|
|
34
|
+
PetPanel,
|
|
35
|
+
PET_PANEL_WIDTH,
|
|
36
|
+
TINY_PET_PANEL_ROWS,
|
|
37
|
+
type Environment,
|
|
38
|
+
} from "./PetPanel.tsx";
|
|
3
39
|
import {
|
|
4
40
|
dispatch,
|
|
5
41
|
filterPaletteByPrefix,
|
|
42
|
+
isArgumentParentCommand,
|
|
6
43
|
isSlash,
|
|
7
44
|
type CommandAction,
|
|
8
45
|
} from "../commands.ts";
|
|
@@ -23,7 +60,6 @@ import {
|
|
|
23
60
|
WITTICISMS,
|
|
24
61
|
} from "../sayings.ts";
|
|
25
62
|
import { type Config } from "../types.ts";
|
|
26
|
-
import { THEME_NAMES } from "../types.ts";
|
|
27
63
|
import { CommandPalette } from "./CommandPalette.tsx";
|
|
28
64
|
import { DealDeskHeader } from "./DealDeskHeader.tsx";
|
|
29
65
|
import {
|
|
@@ -50,10 +86,17 @@ import {
|
|
|
50
86
|
type SynergyEventDefinition,
|
|
51
87
|
} from "./SynergyEvent.tsx";
|
|
52
88
|
import { ThemeProvider } from "./ThemeContext.tsx";
|
|
53
|
-
import {
|
|
54
|
-
|
|
89
|
+
import {
|
|
90
|
+
estimateTranscriptRows,
|
|
91
|
+
TranscriptViewport,
|
|
92
|
+
} from "./TranscriptViewport.tsx";
|
|
93
|
+
import { getActiveTheme } from "./themes.ts";
|
|
55
94
|
|
|
56
95
|
const TRANSCRIPT_CHROME_ROWS = 12;
|
|
96
|
+
const PET_PANEL_MIN_MAIN_COLUMNS = 75;
|
|
97
|
+
const PET_PANEL_GAP_COLUMNS = 1;
|
|
98
|
+
const PET_PANEL_MIN_COLUMNS =
|
|
99
|
+
PET_PANEL_WIDTH + PET_PANEL_GAP_COLUMNS + PET_PANEL_MIN_MAIN_COLUMNS;
|
|
57
100
|
|
|
58
101
|
export function transcriptRowsForTerminalRows(rows: number): number {
|
|
59
102
|
return Math.max(1, Math.min(24, rows - TRANSCRIPT_CHROME_ROWS));
|
|
@@ -62,15 +105,22 @@ export function transcriptRowsForTerminalRows(rows: number): number {
|
|
|
62
105
|
export function nextTranscriptScrollOffset({
|
|
63
106
|
current,
|
|
64
107
|
itemCount,
|
|
108
|
+
totalRows,
|
|
109
|
+
visibleRows,
|
|
65
110
|
direction,
|
|
66
111
|
step = 3,
|
|
67
112
|
}: {
|
|
68
113
|
current: number;
|
|
69
|
-
itemCount
|
|
114
|
+
itemCount?: number;
|
|
115
|
+
totalRows?: number;
|
|
116
|
+
visibleRows?: number;
|
|
70
117
|
direction: "older" | "newer";
|
|
71
118
|
step?: number;
|
|
72
119
|
}): number {
|
|
73
|
-
const maxOffset =
|
|
120
|
+
const maxOffset =
|
|
121
|
+
totalRows !== undefined
|
|
122
|
+
? Math.max(0, totalRows - Math.max(1, visibleRows ?? 1))
|
|
123
|
+
: Math.max(0, (itemCount ?? 0) - 1);
|
|
74
124
|
if (direction === "older") {
|
|
75
125
|
return Math.min(maxOffset, current + step);
|
|
76
126
|
}
|
|
@@ -83,6 +133,46 @@ export function shouldRemoveVisibleAssistantForAction(
|
|
|
83
133
|
return action.type === "regenerate" && action.removedAssistant;
|
|
84
134
|
}
|
|
85
135
|
|
|
136
|
+
export interface HistoryNavState {
|
|
137
|
+
historyIdx: number | null;
|
|
138
|
+
draft: { value: string; cursor: number };
|
|
139
|
+
historyDraft: { value: string; cursor: number } | null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function historyNavStep(
|
|
143
|
+
state: HistoryNavState,
|
|
144
|
+
history: readonly string[],
|
|
145
|
+
direction: "up" | "down",
|
|
146
|
+
): HistoryNavState {
|
|
147
|
+
if (direction === "up") {
|
|
148
|
+
if (history.length === 0) return state;
|
|
149
|
+
const snapshot =
|
|
150
|
+
state.historyIdx === null ? { ...state.draft } : state.historyDraft;
|
|
151
|
+
const idx =
|
|
152
|
+
state.historyIdx === null
|
|
153
|
+
? history.length - 1
|
|
154
|
+
: Math.max(0, state.historyIdx - 1);
|
|
155
|
+
const entry = history[idx] ?? "";
|
|
156
|
+
return {
|
|
157
|
+
historyIdx: idx,
|
|
158
|
+
draft: { value: entry, cursor: graphemeLength(entry) },
|
|
159
|
+
historyDraft: snapshot,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (state.historyIdx === null) return state;
|
|
163
|
+
const next = state.historyIdx + 1;
|
|
164
|
+
if (next >= history.length) {
|
|
165
|
+
const restored = state.historyDraft ?? { value: "", cursor: 0 };
|
|
166
|
+
return { historyIdx: null, draft: restored, historyDraft: null };
|
|
167
|
+
}
|
|
168
|
+
const entry = history[next] ?? "";
|
|
169
|
+
return {
|
|
170
|
+
historyIdx: next,
|
|
171
|
+
draft: { value: entry, cursor: graphemeLength(entry) },
|
|
172
|
+
historyDraft: state.historyDraft,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
86
176
|
function pick<T>(arr: readonly T[]): T {
|
|
87
177
|
if (arr.length === 0) {
|
|
88
178
|
throw new Error("pick called on empty array");
|
|
@@ -136,21 +226,38 @@ export function App({
|
|
|
136
226
|
};
|
|
137
227
|
}, [stdout]);
|
|
138
228
|
const mode = useMemo(() => pickLayout(cols), [cols]);
|
|
139
|
-
const inputWidth = useMemo(() => Math.max(1, cols), [cols]);
|
|
140
229
|
const chromeWidth = useMemo(() => Math.max(1, cols), [cols]);
|
|
141
|
-
const statusBarWidth = inputWidth;
|
|
142
230
|
const isCompact = mode === "very-narrow";
|
|
231
|
+
const [introDone, setIntroDone] = useState(false);
|
|
143
232
|
const integratedIntro =
|
|
144
|
-
showIntroChrome &&
|
|
233
|
+
showIntroChrome &&
|
|
234
|
+
typeof greeting === "string" &&
|
|
235
|
+
rows >= 32 &&
|
|
236
|
+
!introDone;
|
|
237
|
+
const showPetSidePanel = cols >= PET_PANEL_MIN_COLUMNS && !integratedIntro;
|
|
238
|
+
const showCompactPetPanel = !showPetSidePanel && !integratedIntro;
|
|
239
|
+
const compactPetRowBudget = showCompactPetPanel
|
|
240
|
+
? cols >= COMPACT_PET_PANEL_MIN_WIDTH
|
|
241
|
+
? COMPACT_PET_PANEL_ROWS
|
|
242
|
+
: TINY_PET_PANEL_ROWS
|
|
243
|
+
: 0;
|
|
244
|
+
const petPanelReservedWidth = showPetSidePanel
|
|
245
|
+
? PET_PANEL_WIDTH + PET_PANEL_GAP_COLUMNS
|
|
246
|
+
: 0;
|
|
247
|
+
const contentWidth = showPetSidePanel
|
|
248
|
+
? Math.max(1, cols - petPanelReservedWidth)
|
|
249
|
+
: chromeWidth;
|
|
250
|
+
const contentInputWidth = Math.max(1, contentWidth);
|
|
251
|
+
const contentStatusWidth = Math.max(1, contentInputWidth - 2);
|
|
145
252
|
const introRowBudget =
|
|
146
253
|
integratedIntro ? (chromeWidth >= 112 ? 14 : chromeWidth >= 72 ? 26 : 6) : 0;
|
|
147
254
|
const maxTranscriptRows = useMemo(
|
|
148
255
|
() =>
|
|
149
256
|
Math.max(
|
|
150
257
|
1,
|
|
151
|
-
transcriptRowsForTerminalRows(rows) - introRowBudget,
|
|
258
|
+
transcriptRowsForTerminalRows(rows) - introRowBudget - compactPetRowBudget,
|
|
152
259
|
),
|
|
153
|
-
[introRowBudget, rows],
|
|
260
|
+
[compactPetRowBudget, introRowBudget, rows],
|
|
154
261
|
);
|
|
155
262
|
|
|
156
263
|
const [items, setItems] = useState<ChatItem[]>([]);
|
|
@@ -197,18 +304,155 @@ export function App({
|
|
|
197
304
|
const [witticism, setWitticism] = useState<string>(pick(WITTICISMS));
|
|
198
305
|
const [model, setModel] = useState<string>(config.model);
|
|
199
306
|
const [msgCount, setMsgCount] = useState<number>(0);
|
|
200
|
-
const [tokenCount, setTokenCount] = useState<number>(
|
|
201
|
-
conversation.approximateTokens(),
|
|
202
|
-
);
|
|
203
|
-
const [lastLatencyMs, setLastLatencyMs] = useState<number | null>(null);
|
|
204
|
-
const [fallbackModel, setFallbackModel] = useState<string | null>(null);
|
|
205
307
|
const [deskStatus, setDeskStatus] = useState<"idle" | "error">("idle");
|
|
206
308
|
const [deskNotice, setDeskNotice] = useState<string | null>(null);
|
|
207
309
|
const [history, setHistory] = useState<string[]>([]);
|
|
208
310
|
const [historyIdx, setHistoryIdx] = useState<number | null>(null);
|
|
209
311
|
const [paletteIdx, setPaletteIdx] = useState(0);
|
|
210
312
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
211
|
-
const
|
|
313
|
+
const handleIntroComplete = useCallback(() => {
|
|
314
|
+
setIntroDone(true);
|
|
315
|
+
}, []);
|
|
316
|
+
const intro = useIntroAnimation(
|
|
317
|
+
chromeWidth,
|
|
318
|
+
integratedIntro,
|
|
319
|
+
handleIntroComplete,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const [petStats, setPetStats] = useState<PetStats>(() => loadPetState());
|
|
323
|
+
const [petActivity, setPetActivity] = useState<PetActivity>("idle");
|
|
324
|
+
const [isDead, setIsDead] = useState(false);
|
|
325
|
+
const [deathReason, setDeathReason] = useState("energy");
|
|
326
|
+
const [deathVariant, setDeathVariant] = useState(0);
|
|
327
|
+
const petStatsRef = useRef<PetStats>(petStats);
|
|
328
|
+
const petActivityTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
329
|
+
const petDecayTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
330
|
+
|
|
331
|
+
const petEnv = useMemo((): Environment => {
|
|
332
|
+
const h = new Date().getHours();
|
|
333
|
+
if (h >= 9 && h < 18) return "office";
|
|
334
|
+
if (h >= 6 && h < 23) return "home";
|
|
335
|
+
return "outdoors";
|
|
336
|
+
}, []);
|
|
337
|
+
|
|
338
|
+
const PET_MESSAGES = useMemo(() => ({
|
|
339
|
+
feed: [
|
|
340
|
+
"Drexler receives deal memo. Hunger: satisfied. Pipeline: expanding.",
|
|
341
|
+
"Drexler consumes quarterly report. Fortifying.",
|
|
342
|
+
"Deal deck delivered. Drexler is replenished.",
|
|
343
|
+
"Nutrition acquired via term sheet. Excellent.",
|
|
344
|
+
"Drexler ingests synergy bundle. Caloric intake: maximized.",
|
|
345
|
+
"Pipeline refueled. Drexler gives brief nod of approval.",
|
|
346
|
+
],
|
|
347
|
+
play: [
|
|
348
|
+
"Drexler engages in corporate synergy games. Morale: elevated.",
|
|
349
|
+
"Drexler attempts leisure. Unfamiliar but effective.",
|
|
350
|
+
"Golf simulation initiated. Handicap: nonexistent.",
|
|
351
|
+
"Corporate retreat protocols engaged. Team building: successful.",
|
|
352
|
+
"Drexler plays. Competitors watch nervously.",
|
|
353
|
+
"Recreational time allocated. ROI: unclear but positive.",
|
|
354
|
+
],
|
|
355
|
+
work: [
|
|
356
|
+
"Drexler retreats to deal desk. Pipeline throughput: increasing.",
|
|
357
|
+
"Grind mode initiated. Coffee consumed preemptively.",
|
|
358
|
+
"Drexler is doing the work. Others take note.",
|
|
359
|
+
"Deal origination in progress. Board is watching.",
|
|
360
|
+
"Drexler enters flow state. Productivity: exceptional.",
|
|
361
|
+
"All-nighter commenced. Regrets: minimal.",
|
|
362
|
+
],
|
|
363
|
+
praise: [
|
|
364
|
+
"Drexler acknowledges commendation. Briefly.",
|
|
365
|
+
"Praise received. Filed under: expected.",
|
|
366
|
+
"Drexler nods. One singular nod.",
|
|
367
|
+
"Affirmation noted. Drexler remains unmoved. Mostly.",
|
|
368
|
+
"Kind words processed. Ego: appropriately inflated.",
|
|
369
|
+
"Drexler accepts compliment with characteristic restraint.",
|
|
370
|
+
],
|
|
371
|
+
rest: [
|
|
372
|
+
"Drexler retires briefly. Strategic recharge in progress.",
|
|
373
|
+
"Under-desk nap initiated. Do not disturb.",
|
|
374
|
+
"Drexler powers down. Temporarily.",
|
|
375
|
+
"Rest mode engaged. Energy recovery: imminent.",
|
|
376
|
+
"Strategic downtime commenced. Drexler will return stronger.",
|
|
377
|
+
"Drexler sleeps. Dreams of closed deals.",
|
|
378
|
+
],
|
|
379
|
+
}), []);
|
|
380
|
+
|
|
381
|
+
const triggerPetActivity = useCallback(
|
|
382
|
+
(activity: PetActivity, durationMs: number) => {
|
|
383
|
+
if (petActivityTimerRef.current !== null) {
|
|
384
|
+
clearTimeout(petActivityTimerRef.current);
|
|
385
|
+
}
|
|
386
|
+
setPetActivity(activity);
|
|
387
|
+
petActivityTimerRef.current = setTimeout(() => {
|
|
388
|
+
setPetActivity("idle");
|
|
389
|
+
petActivityTimerRef.current = null;
|
|
390
|
+
}, durationMs);
|
|
391
|
+
},
|
|
392
|
+
[],
|
|
393
|
+
);
|
|
394
|
+
const updatePetStats = useCallback((updater: (stats: PetStats) => PetStats) => {
|
|
395
|
+
setPetStats((stats) => {
|
|
396
|
+
const next = updater(stats);
|
|
397
|
+
petStatsRef.current = next;
|
|
398
|
+
savePetState(next);
|
|
399
|
+
return next;
|
|
400
|
+
});
|
|
401
|
+
}, []);
|
|
402
|
+
const applyPetAction = useCallback(
|
|
403
|
+
(action: PetActionKey, mutator: (stats: PetStats) => PetStats) => {
|
|
404
|
+
const before = getPetRank(petStatsRef.current);
|
|
405
|
+
updatePetStats((s) => {
|
|
406
|
+
const next = stampAction(mutator(s), action);
|
|
407
|
+
return accrueLifetimeDeals(next, action);
|
|
408
|
+
});
|
|
409
|
+
const after = getPetRank(petStatsRef.current);
|
|
410
|
+
if (after !== before) {
|
|
411
|
+
addItem(
|
|
412
|
+
"system",
|
|
413
|
+
`PROMOTION MEMO: Drexler ranked up to ${rankLabel(after)}. Reward: more meetings.`,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
[updatePetStats, addItem],
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
useEffect(() => {
|
|
421
|
+
petStatsRef.current = petStats;
|
|
422
|
+
}, [petStats]);
|
|
423
|
+
|
|
424
|
+
// Real-time stat decay matches the offline per-hour decay rate.
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
petDecayTimerRef.current = setInterval(() => {
|
|
427
|
+
updatePetStats(applyMinuteDecay);
|
|
428
|
+
}, 60_000);
|
|
429
|
+
return () => {
|
|
430
|
+
if (petDecayTimerRef.current !== null) {
|
|
431
|
+
clearInterval(petDecayTimerRef.current);
|
|
432
|
+
petDecayTimerRef.current = null;
|
|
433
|
+
}
|
|
434
|
+
if (petActivityTimerRef.current !== null) {
|
|
435
|
+
clearTimeout(petActivityTimerRef.current);
|
|
436
|
+
petActivityTimerRef.current = null;
|
|
437
|
+
}
|
|
438
|
+
savePetState(petStatsRef.current);
|
|
439
|
+
};
|
|
440
|
+
}, [updatePetStats]);
|
|
441
|
+
|
|
442
|
+
// Death detection
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
if (isDead || !isPetDead(petStats)) return;
|
|
445
|
+
const reason =
|
|
446
|
+
petStats.hunger <= 0 ? "hunger" :
|
|
447
|
+
petStats.happiness <= 0 ? "happiness" : "energy";
|
|
448
|
+
setDeathReason(reason);
|
|
449
|
+
setDeathVariant(Math.floor(Math.random() * 5));
|
|
450
|
+
setIsDead(true);
|
|
451
|
+
const deadStats = { ...petStats, dead: true };
|
|
452
|
+
petStatsRef.current = deadStats;
|
|
453
|
+
savePetState(deadStats);
|
|
454
|
+
exitTimerRef.current = setTimeout(() => exit(), 5000);
|
|
455
|
+
}, [petStats, isDead, exit]);
|
|
212
456
|
|
|
213
457
|
const paletteItems = useMemo(() => filterPaletteByPrefix(input), [input]);
|
|
214
458
|
const paletteOpen = paletteItems.length > 0;
|
|
@@ -220,23 +464,17 @@ export function App({
|
|
|
220
464
|
setScrollOffset(0);
|
|
221
465
|
}, [items.length]);
|
|
222
466
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
THEME_NAMES.find((name) => THEMES[name] === active) ??
|
|
231
|
-
config.theme ??
|
|
232
|
-
"apollo"
|
|
233
|
-
);
|
|
234
|
-
}, [activeTheme, config.theme]);
|
|
235
|
-
|
|
467
|
+
const visibleTranscriptRows = synergyEvent
|
|
468
|
+
? Math.max(1, maxTranscriptRows - synergyEventRows(contentWidth, isCompact))
|
|
469
|
+
: maxTranscriptRows;
|
|
470
|
+
const estimatedTranscriptRows = useMemo(
|
|
471
|
+
() => estimateTranscriptRows(items, isCompact, contentWidth),
|
|
472
|
+
[items, isCompact, contentWidth],
|
|
473
|
+
);
|
|
236
474
|
const scrollHint = useMemo(() => {
|
|
237
|
-
if (
|
|
475
|
+
if (estimatedTranscriptRows <= visibleTranscriptRows) return undefined;
|
|
238
476
|
return scrollOffset > 0 ? "PageDown newer" : "PageUp scrollback";
|
|
239
|
-
}, [
|
|
477
|
+
}, [estimatedTranscriptRows, visibleTranscriptRows, scrollOffset]);
|
|
240
478
|
|
|
241
479
|
// throttle streaming updates so React doesn't re-render every token
|
|
242
480
|
const streamBufRef = useRef("");
|
|
@@ -249,6 +487,7 @@ export function App({
|
|
|
249
487
|
const exitingRef = useRef(false);
|
|
250
488
|
const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
251
489
|
const synergyTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
490
|
+
const historyDraftRef = useRef<{ value: string; cursor: number } | null>(null);
|
|
252
491
|
const flushStream = useCallback(() => {
|
|
253
492
|
if (!mountedRef.current) return;
|
|
254
493
|
setStreaming(streamBufRef.current);
|
|
@@ -260,6 +499,7 @@ export function App({
|
|
|
260
499
|
if (exitingRef.current) return;
|
|
261
500
|
exitingRef.current = true;
|
|
262
501
|
abortRef.current?.abort();
|
|
502
|
+
savePetState(petStatsRef.current);
|
|
263
503
|
if (streamTimerRef.current !== null) {
|
|
264
504
|
clearTimeout(streamTimerRef.current);
|
|
265
505
|
streamTimerRef.current = null;
|
|
@@ -333,12 +573,10 @@ export function App({
|
|
|
333
573
|
if (requestInFlightRef.current) return;
|
|
334
574
|
requestInFlightRef.current = true;
|
|
335
575
|
setRequestInFlight(true);
|
|
336
|
-
const startedAt = Date.now();
|
|
337
576
|
try {
|
|
338
577
|
setThinking(pick(THINKING_LINES));
|
|
339
578
|
setDeskStatus("idle");
|
|
340
579
|
setDeskNotice(null);
|
|
341
|
-
setFallbackModel(null);
|
|
342
580
|
streamBufRef.current = "";
|
|
343
581
|
setStreaming(null);
|
|
344
582
|
let firstToken = true;
|
|
@@ -389,7 +627,6 @@ export function App({
|
|
|
389
627
|
}
|
|
390
628
|
setThinking(null);
|
|
391
629
|
setStreaming(null);
|
|
392
|
-
setLastLatencyMs(Date.now() - startedAt);
|
|
393
630
|
if (cancelledRef.current) {
|
|
394
631
|
cancelledRef.current = false;
|
|
395
632
|
if (result?.content) {
|
|
@@ -405,7 +642,6 @@ export function App({
|
|
|
405
642
|
if (result.fellBack) {
|
|
406
643
|
addItem("system", `(fell back to ${result.modelUsed})`);
|
|
407
644
|
notices.push(`fallback ${result.modelUsed}`);
|
|
408
|
-
setFallbackModel(result.modelUsed);
|
|
409
645
|
}
|
|
410
646
|
if (detectPersonaDrift(result.content)) {
|
|
411
647
|
addItem("system", `(persona drift detected — model used 'I')`);
|
|
@@ -425,7 +661,6 @@ export function App({
|
|
|
425
661
|
setDeskNotice(result?.error ?? "stream error");
|
|
426
662
|
}
|
|
427
663
|
setMsgCount(conversation.length);
|
|
428
|
-
setTokenCount(conversation.approximateTokens());
|
|
429
664
|
setWitticism(pick(WITTICISMS));
|
|
430
665
|
} finally {
|
|
431
666
|
requestInFlightRef.current = false;
|
|
@@ -444,6 +679,134 @@ export function App({
|
|
|
444
679
|
|
|
445
680
|
const handleSlashWithMutation = useCallback(
|
|
446
681
|
async (line: string): Promise<void> => {
|
|
682
|
+
const lower = line.toLowerCase().trim();
|
|
683
|
+
const [slashCommand = lower] = lower.split(/\s+/, 1);
|
|
684
|
+
|
|
685
|
+
// Pet commands — handled before dispatch so they don't hit the unknown-command path
|
|
686
|
+
if (lower === "/synergy") {
|
|
687
|
+
runSynergyEvent();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const isPetCommand =
|
|
691
|
+
slashCommand === "/feed" ||
|
|
692
|
+
slashCommand === "/play" ||
|
|
693
|
+
slashCommand === "/work" ||
|
|
694
|
+
slashCommand === "/praise" ||
|
|
695
|
+
slashCommand === "/rest" ||
|
|
696
|
+
slashCommand === "/vibe";
|
|
697
|
+
if (isPetCommand && isDead) {
|
|
698
|
+
addItem("system", "Drexler is in HR. Restructuring paperwork pending — try again after revival.");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const cooldownAction: PetActionKey | null =
|
|
702
|
+
slashCommand === "/feed" ? "feed"
|
|
703
|
+
: slashCommand === "/play" ? "play"
|
|
704
|
+
: slashCommand === "/work" ? "work"
|
|
705
|
+
: slashCommand === "/praise" ? "praise"
|
|
706
|
+
: slashCommand === "/rest" ? "rest"
|
|
707
|
+
: slashCommand === "/vibe" ? "vibe"
|
|
708
|
+
: null;
|
|
709
|
+
if (cooldownAction !== null) {
|
|
710
|
+
const cd = actionCooldown(petStatsRef.current, cooldownAction);
|
|
711
|
+
if (!cd.ok) {
|
|
712
|
+
addItem(
|
|
713
|
+
"system",
|
|
714
|
+
`Drexler ${cooldownAction === "feed" ? "just ate" : cooldownAction === "play" ? "just played" : cooldownAction === "work" ? "just worked" : cooldownAction === "praise" ? "just got praised" : cooldownAction === "rest" ? "just rested" : "just vibed"}. Wait ${formatCooldownRemaining(cd.remainingMs)} before the next attempt. Drexler resents micromanagement.`,
|
|
715
|
+
);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (slashCommand === "/feed") {
|
|
720
|
+
applyPetAction("feed", applyFeed);
|
|
721
|
+
triggerPetActivity("eating", 3500);
|
|
722
|
+
addItem("system", pick(PET_MESSAGES.feed));
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (slashCommand === "/play") {
|
|
726
|
+
applyPetAction("play", applyPlay);
|
|
727
|
+
triggerPetActivity("playing", 4000);
|
|
728
|
+
addItem("system", pick(PET_MESSAGES.play));
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (slashCommand === "/work") {
|
|
732
|
+
applyPetAction("work", applyWork);
|
|
733
|
+
triggerPetActivity("working", 5000);
|
|
734
|
+
addItem("system", pick(PET_MESSAGES.work));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (slashCommand === "/praise") {
|
|
738
|
+
applyPetAction("praise", applyPraise);
|
|
739
|
+
triggerPetActivity("praised", 3000);
|
|
740
|
+
addItem("system", pick(PET_MESSAGES.praise));
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (slashCommand === "/rest") {
|
|
744
|
+
applyPetAction("rest", applyRest);
|
|
745
|
+
triggerPetActivity("sleeping", 5000);
|
|
746
|
+
addItem("system", pick(PET_MESSAGES.rest));
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (slashCommand === "/vibe") {
|
|
750
|
+
const result = applyVibe(petStatsRef.current);
|
|
751
|
+
applyPetAction("vibe", () => result.stats);
|
|
752
|
+
triggerPetActivity("vibing", 3500);
|
|
753
|
+
addItem("system", result.message);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (slashCommand === "/name") {
|
|
757
|
+
const arg = line.slice("/name".length).trim();
|
|
758
|
+
if (arg.length === 0) {
|
|
759
|
+
const current = petStatsRef.current.name;
|
|
760
|
+
addItem(
|
|
761
|
+
"system",
|
|
762
|
+
current
|
|
763
|
+
? `Drexler's pet name on file: "${current}". /name <new> to reassign.`
|
|
764
|
+
: "No pet name on file. /name <name> to issue corporate identity.",
|
|
765
|
+
);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const cleaned = sanitizePetName(arg);
|
|
769
|
+
if (cleaned.length === 0) {
|
|
770
|
+
addItem(
|
|
771
|
+
"system",
|
|
772
|
+
"Drexler refuses unprintable identity. Pick letters, numbers, spaces, dots, or apostrophes (≤16 chars).",
|
|
773
|
+
);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
updatePetStats((s) => applyName(s, cleaned));
|
|
777
|
+
addItem("system", `Pet renamed: "${cleaned}". Memo distributed to all departments.`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (slashCommand === "/profile") {
|
|
781
|
+
const s = petStatsRef.current;
|
|
782
|
+
const tenure = formatTenure(petTenureMs(s));
|
|
783
|
+
const mood = getPetMood(s);
|
|
784
|
+
const stats = [
|
|
785
|
+
["hunger", s.hunger],
|
|
786
|
+
["happiness", s.happiness],
|
|
787
|
+
["energy", s.energy],
|
|
788
|
+
["deals", s.deals],
|
|
789
|
+
] as const;
|
|
790
|
+
const dominant = stats.reduce(
|
|
791
|
+
(best, cur) => (cur[1] > best[1] ? cur : best),
|
|
792
|
+
);
|
|
793
|
+
const rank = getPetRank(s);
|
|
794
|
+
const lines = [
|
|
795
|
+
"Drexler personnel file:",
|
|
796
|
+
` name : ${s.name ?? "(unnamed associate)"}`,
|
|
797
|
+
` rank : ${rankLabel(rank)}`,
|
|
798
|
+
` tenure : ${tenure}`,
|
|
799
|
+
` mood : ${mood}`,
|
|
800
|
+
` hunger : ${Math.round(s.hunger)}%`,
|
|
801
|
+
` happiness : ${Math.round(s.happiness)}%`,
|
|
802
|
+
` energy : ${Math.round(s.energy)}%`,
|
|
803
|
+
` deals : ${Math.round(s.deals)}%`,
|
|
804
|
+
` standout : ${dominant[0]} (${Math.round(dominant[1])}%)`,
|
|
805
|
+
];
|
|
806
|
+
addItem("system", lines.join("\n"));
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
447
810
|
let captured = "";
|
|
448
811
|
const mutableConfig: Config = { ...config, model };
|
|
449
812
|
const action = dispatch(line, {
|
|
@@ -453,15 +816,8 @@ export function App({
|
|
|
453
816
|
captured += (captured ? "\n" : "") + s;
|
|
454
817
|
},
|
|
455
818
|
});
|
|
456
|
-
const lower = line.toLowerCase().trim();
|
|
457
|
-
if (lower === "/synergy") {
|
|
458
|
-
runSynergyEvent();
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
819
|
if (lower === "/clear" || lower.startsWith("/clear ")) {
|
|
462
820
|
setItems([]);
|
|
463
|
-
setLastLatencyMs(null);
|
|
464
|
-
setFallbackModel(null);
|
|
465
821
|
}
|
|
466
822
|
if (mutableConfig.model !== model) {
|
|
467
823
|
setModel(mutableConfig.model);
|
|
@@ -498,7 +854,6 @@ export function App({
|
|
|
498
854
|
await runLLM(action.instruction);
|
|
499
855
|
}
|
|
500
856
|
setMsgCount(conversation.length);
|
|
501
|
-
setTokenCount(conversation.approximateTokens());
|
|
502
857
|
},
|
|
503
858
|
[
|
|
504
859
|
addItem,
|
|
@@ -506,10 +861,14 @@ export function App({
|
|
|
506
861
|
config,
|
|
507
862
|
activeTheme,
|
|
508
863
|
model,
|
|
864
|
+
petStats,
|
|
865
|
+
PET_MESSAGES,
|
|
509
866
|
removeLastAssistantItem,
|
|
510
867
|
runLLM,
|
|
511
868
|
runSynergyEvent,
|
|
512
869
|
triggerExit,
|
|
870
|
+
triggerPetActivity,
|
|
871
|
+
updatePetStats,
|
|
513
872
|
],
|
|
514
873
|
);
|
|
515
874
|
|
|
@@ -528,13 +887,46 @@ export function App({
|
|
|
528
887
|
addItem("user", line);
|
|
529
888
|
conversation.push("user", line);
|
|
530
889
|
setMsgCount(conversation.length);
|
|
531
|
-
setTokenCount(conversation.approximateTokens());
|
|
532
890
|
await runLLM();
|
|
533
891
|
},
|
|
534
892
|
[addItem, conversation, handleSlashWithMutation, runLLM],
|
|
535
893
|
);
|
|
536
894
|
|
|
895
|
+
const reportSubmitError = useCallback(
|
|
896
|
+
(err: unknown) => {
|
|
897
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
898
|
+
addItem("system", `${STREAM_ERROR} [${msg}]`);
|
|
899
|
+
setDeskStatus("error");
|
|
900
|
+
setDeskNotice("submit failed");
|
|
901
|
+
},
|
|
902
|
+
[addItem],
|
|
903
|
+
);
|
|
904
|
+
|
|
537
905
|
useInput((char, key) => {
|
|
906
|
+
// Scroll keys are always live — they only mutate scrollOffset and never
|
|
907
|
+
// commit input, so we let the user review history during streaming.
|
|
908
|
+
if (key.pageUp) {
|
|
909
|
+
setScrollOffset((offset) =>
|
|
910
|
+
nextTranscriptScrollOffset({
|
|
911
|
+
current: offset,
|
|
912
|
+
totalRows: estimatedTranscriptRows,
|
|
913
|
+
visibleRows: visibleTranscriptRows,
|
|
914
|
+
direction: "older",
|
|
915
|
+
}),
|
|
916
|
+
);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (key.pageDown) {
|
|
920
|
+
setScrollOffset((offset) =>
|
|
921
|
+
nextTranscriptScrollOffset({
|
|
922
|
+
current: offset,
|
|
923
|
+
totalRows: estimatedTranscriptRows,
|
|
924
|
+
visibleRows: visibleTranscriptRows,
|
|
925
|
+
direction: "newer",
|
|
926
|
+
}),
|
|
927
|
+
);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
538
930
|
const busy =
|
|
539
931
|
requestInFlightRef.current ||
|
|
540
932
|
synergyActiveRef.current ||
|
|
@@ -565,32 +957,25 @@ export function App({
|
|
|
565
957
|
}
|
|
566
958
|
return;
|
|
567
959
|
}
|
|
568
|
-
if (key.pageUp) {
|
|
569
|
-
setScrollOffset((offset) =>
|
|
570
|
-
nextTranscriptScrollOffset({
|
|
571
|
-
current: offset,
|
|
572
|
-
itemCount: items.length,
|
|
573
|
-
direction: "older",
|
|
574
|
-
}),
|
|
575
|
-
);
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
if (key.pageDown) {
|
|
579
|
-
setScrollOffset((offset) =>
|
|
580
|
-
nextTranscriptScrollOffset({
|
|
581
|
-
current: offset,
|
|
582
|
-
itemCount: items.length,
|
|
583
|
-
direction: "newer",
|
|
584
|
-
}),
|
|
585
|
-
);
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
960
|
if (paletteOpen && key.return) {
|
|
589
961
|
const sel = paletteItems[paletteIdx];
|
|
590
962
|
if (sel) {
|
|
963
|
+
// Bare /theme, /model, etc. — open the chooser, do not execute.
|
|
964
|
+
if (isArgumentParentCommand(sel.name)) {
|
|
965
|
+
const filled = sel.name + " ";
|
|
966
|
+
updateDraft({
|
|
967
|
+
value: filled,
|
|
968
|
+
cursor: graphemeLength(filled),
|
|
969
|
+
});
|
|
970
|
+
setPaletteIdx(0);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
591
973
|
updateDraft({ value: "", cursor: 0 });
|
|
592
974
|
setHistoryIdx(null);
|
|
593
|
-
|
|
975
|
+
historyDraftRef.current = null;
|
|
976
|
+
onSubmit(sel.name).catch((err) => {
|
|
977
|
+
reportSubmitError(err);
|
|
978
|
+
});
|
|
594
979
|
}
|
|
595
980
|
return;
|
|
596
981
|
}
|
|
@@ -598,6 +983,7 @@ export function App({
|
|
|
598
983
|
const submitted = draftRef.current.value;
|
|
599
984
|
updateDraft({ value: "", cursor: 0 });
|
|
600
985
|
setHistoryIdx(null);
|
|
986
|
+
historyDraftRef.current = null;
|
|
601
987
|
const trimmedSubmit = submitted.trim();
|
|
602
988
|
if (trimmedSubmit.length > 0) {
|
|
603
989
|
setHistory((prev) => {
|
|
@@ -605,7 +991,9 @@ export function App({
|
|
|
605
991
|
return next.length > 50 ? next.slice(-50) : next;
|
|
606
992
|
});
|
|
607
993
|
}
|
|
608
|
-
|
|
994
|
+
onSubmit(submitted).catch((err) => {
|
|
995
|
+
reportSubmitError(err);
|
|
996
|
+
});
|
|
609
997
|
return;
|
|
610
998
|
}
|
|
611
999
|
if (key.ctrl && char === "c") {
|
|
@@ -649,10 +1037,18 @@ export function App({
|
|
|
649
1037
|
return;
|
|
650
1038
|
}
|
|
651
1039
|
if (history.length === 0) return;
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1040
|
+
const next = historyNavStep(
|
|
1041
|
+
{
|
|
1042
|
+
historyIdx,
|
|
1043
|
+
draft: draftRef.current,
|
|
1044
|
+
historyDraft: historyDraftRef.current,
|
|
1045
|
+
},
|
|
1046
|
+
history,
|
|
1047
|
+
"up",
|
|
1048
|
+
);
|
|
1049
|
+
historyDraftRef.current = next.historyDraft;
|
|
1050
|
+
setHistoryIdx(next.historyIdx);
|
|
1051
|
+
updateDraft(next.draft);
|
|
656
1052
|
return;
|
|
657
1053
|
}
|
|
658
1054
|
if (key.downArrow) {
|
|
@@ -661,15 +1057,18 @@ export function App({
|
|
|
661
1057
|
return;
|
|
662
1058
|
}
|
|
663
1059
|
if (historyIdx === null) return;
|
|
664
|
-
const next =
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1060
|
+
const next = historyNavStep(
|
|
1061
|
+
{
|
|
1062
|
+
historyIdx,
|
|
1063
|
+
draft: draftRef.current,
|
|
1064
|
+
historyDraft: historyDraftRef.current,
|
|
1065
|
+
},
|
|
1066
|
+
history,
|
|
1067
|
+
"down",
|
|
1068
|
+
);
|
|
1069
|
+
historyDraftRef.current = next.historyDraft;
|
|
1070
|
+
setHistoryIdx(next.historyIdx);
|
|
1071
|
+
updateDraft(next.draft);
|
|
673
1072
|
return;
|
|
674
1073
|
}
|
|
675
1074
|
if (key.ctrl && char === "a") {
|
|
@@ -726,13 +1125,8 @@ export function App({
|
|
|
726
1125
|
const headerStatus = isBusy ? "streaming" : deskStatus;
|
|
727
1126
|
const renderDealDeskHeader = (width: number) => (
|
|
728
1127
|
<DealDeskHeader
|
|
729
|
-
model={model}
|
|
730
1128
|
mood={mood}
|
|
731
1129
|
messageCount={msgCount}
|
|
732
|
-
themeName={themeName}
|
|
733
|
-
approximateTokens={tokenCount}
|
|
734
|
-
latencyMs={lastLatencyMs}
|
|
735
|
-
fallbackModel={fallbackModel}
|
|
736
1130
|
status={headerStatus}
|
|
737
1131
|
compact={isCompact}
|
|
738
1132
|
notice={!integratedIntro ? deskNotice ?? undefined : undefined}
|
|
@@ -741,11 +1135,16 @@ export function App({
|
|
|
741
1135
|
/>
|
|
742
1136
|
);
|
|
743
1137
|
const dealDeskHeader = renderDealDeskHeader(chromeWidth);
|
|
744
|
-
const visibleTranscriptRows = synergyEvent
|
|
745
|
-
? Math.max(1, maxTranscriptRows - synergyEventRows(chromeWidth, isCompact))
|
|
746
|
-
: maxTranscriptRows;
|
|
747
1138
|
const introBarColor = introPhaseColor(intro.colorPhase, t);
|
|
748
1139
|
|
|
1140
|
+
if (isDead) {
|
|
1141
|
+
return (
|
|
1142
|
+
<ThemeProvider value={activeTheme}>
|
|
1143
|
+
<DeathScreen reason={deathReason} variant={deathVariant} />
|
|
1144
|
+
</ThemeProvider>
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
749
1148
|
return (
|
|
750
1149
|
<ThemeProvider value={activeTheme}>
|
|
751
1150
|
<Box flexDirection="column">
|
|
@@ -766,73 +1165,97 @@ export function App({
|
|
|
766
1165
|
) : (
|
|
767
1166
|
dealDeskHeader
|
|
768
1167
|
)}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1168
|
+
{showCompactPetPanel && (
|
|
1169
|
+
<Box marginBottom={1}>
|
|
1170
|
+
<CompactPetPanel
|
|
1171
|
+
stats={petStats}
|
|
1172
|
+
activity={petActivity}
|
|
1173
|
+
env={petEnv}
|
|
1174
|
+
isPaused={isBusy}
|
|
1175
|
+
width={chromeWidth}
|
|
1176
|
+
/>
|
|
1177
|
+
</Box>
|
|
1178
|
+
)}
|
|
1179
|
+
<Box flexDirection="row" alignItems="flex-start">
|
|
1180
|
+
{showPetSidePanel && (
|
|
1181
|
+
<Box marginRight={PET_PANEL_GAP_COLUMNS}>
|
|
1182
|
+
<PetPanel
|
|
1183
|
+
stats={petStats}
|
|
1184
|
+
activity={petActivity}
|
|
1185
|
+
env={petEnv}
|
|
1186
|
+
isPaused={isBusy}
|
|
1187
|
+
/>
|
|
786
1188
|
</Box>
|
|
787
1189
|
)}
|
|
788
|
-
|
|
789
|
-
<
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1190
|
+
<Box flexDirection="column" flexGrow={1}>
|
|
1191
|
+
<TranscriptViewport
|
|
1192
|
+
items={items}
|
|
1193
|
+
maxRows={visibleTranscriptRows}
|
|
1194
|
+
cols={contentWidth}
|
|
793
1195
|
compact={isCompact}
|
|
1196
|
+
scrollOffset={scrollOffset}
|
|
794
1197
|
/>
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
</Text>
|
|
801
|
-
</Box>
|
|
802
|
-
) : (
|
|
803
|
-
<>
|
|
804
|
-
{paletteOpen && (
|
|
805
|
-
<CommandPalette
|
|
806
|
-
items={paletteItems}
|
|
807
|
-
selectedIdx={paletteIdx}
|
|
808
|
-
width={chromeWidth}
|
|
809
|
-
/>
|
|
1198
|
+
<Box flexDirection="column">
|
|
1199
|
+
{streaming !== null && (
|
|
1200
|
+
<Box marginBottom={1}>
|
|
1201
|
+
<StreamingMessage content={streaming} width={contentWidth} />
|
|
1202
|
+
</Box>
|
|
810
1203
|
)}
|
|
811
|
-
|
|
812
|
-
<
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
width={inputWidth}
|
|
822
|
-
/>
|
|
823
|
-
</Box>
|
|
824
|
-
<Box paddingLeft={2}>
|
|
825
|
-
<StatusBar
|
|
826
|
-
messageCount={msgCount}
|
|
827
|
-
witticism={witticism}
|
|
828
|
-
maxWidth={Math.max(1, statusBarWidth - 2)}
|
|
829
|
-
status={isBusy ? "streaming" : deskStatus}
|
|
1204
|
+
{thinking !== null && streaming === null && (
|
|
1205
|
+
<Box paddingX={1} marginBottom={1}>
|
|
1206
|
+
<Spinner label={thinking} width={contentWidth} />
|
|
1207
|
+
</Box>
|
|
1208
|
+
)}
|
|
1209
|
+
{synergyEvent !== null && (
|
|
1210
|
+
<SynergyEvent
|
|
1211
|
+
event={synergyEvent.event}
|
|
1212
|
+
frame={synergyEvent.frame}
|
|
1213
|
+
width={contentWidth}
|
|
830
1214
|
compact={isCompact}
|
|
831
|
-
scrollHint={scrollHint}
|
|
832
1215
|
/>
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1216
|
+
)}
|
|
1217
|
+
{exitMsg !== null ? (
|
|
1218
|
+
<Box paddingX={1} marginBottom={1}>
|
|
1219
|
+
<Text color={t.primaryLight} bold>
|
|
1220
|
+
{exitMsg}
|
|
1221
|
+
</Text>
|
|
1222
|
+
</Box>
|
|
1223
|
+
) : (
|
|
1224
|
+
<>
|
|
1225
|
+
{paletteOpen && (
|
|
1226
|
+
<CommandPalette
|
|
1227
|
+
items={paletteItems}
|
|
1228
|
+
selectedIdx={paletteIdx}
|
|
1229
|
+
width={contentWidth}
|
|
1230
|
+
/>
|
|
1231
|
+
)}
|
|
1232
|
+
<Box flexDirection="column">
|
|
1233
|
+
<InputBox
|
|
1234
|
+
value={input}
|
|
1235
|
+
cursor={cursor}
|
|
1236
|
+
disabled={isBusy}
|
|
1237
|
+
disabledLabel={
|
|
1238
|
+
synergyEvent !== null
|
|
1239
|
+
? "(Synergy event running... boardroom locked)"
|
|
1240
|
+
: undefined
|
|
1241
|
+
}
|
|
1242
|
+
width={contentInputWidth}
|
|
1243
|
+
/>
|
|
1244
|
+
</Box>
|
|
1245
|
+
<Box paddingLeft={2}>
|
|
1246
|
+
<StatusBar
|
|
1247
|
+
messageCount={msgCount}
|
|
1248
|
+
witticism={witticism}
|
|
1249
|
+
maxWidth={contentStatusWidth}
|
|
1250
|
+
status={isBusy ? "streaming" : deskStatus}
|
|
1251
|
+
compact={isCompact}
|
|
1252
|
+
scrollHint={scrollHint}
|
|
1253
|
+
/>
|
|
1254
|
+
</Box>
|
|
1255
|
+
</>
|
|
1256
|
+
)}
|
|
1257
|
+
</Box>
|
|
1258
|
+
</Box>
|
|
836
1259
|
</Box>
|
|
837
1260
|
</Box>
|
|
838
1261
|
</ThemeProvider>
|