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