bashio 1.1.0 → 2.0.0

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.
@@ -0,0 +1,2378 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CHATGPT_SUBSCRIPTION_MODELS,
4
+ CLAUDE_MODELS,
5
+ CLAUDE_SUBSCRIPTION_MODELS,
6
+ COPILOT_MODELS,
7
+ OLLAMA_RECOMMENDED_MODELS,
8
+ OPENAI_MODELS,
9
+ OPENROUTER_MODELS,
10
+ THEMES,
11
+ configExists,
12
+ copyToClipboard,
13
+ createProvider,
14
+ getThemeByName,
15
+ loadConfig,
16
+ runAuthSetup,
17
+ saveConfig
18
+ } from "./chunk-PYTBTPZN.js";
19
+
20
+ // src/chat/App.tsx
21
+ import { MouseProvider } from "@ink-tools/ink-mouse";
22
+ import { Box as Box11, render, Text as Text11, useApp, useInput as useInput7, useStdout as useStdout4 } from "ink";
23
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef8, useState as useState7 } from "react";
24
+
25
+ // src/chat/components/InputBox.tsx
26
+ import { Box as Box2, Text as Text2, useInput } from "ink";
27
+ import { memo as memo2, useMemo as useMemo2, useRef as useRef2, useState } from "react";
28
+
29
+ // src/chat/utils/slashCommands.ts
30
+ var SLASH_COMMANDS = [
31
+ {
32
+ name: "models",
33
+ description: "Switch AI model",
34
+ action: "openModelSwitcher"
35
+ },
36
+ {
37
+ name: "sessions",
38
+ description: "Browse chat sessions",
39
+ action: "openSessionPicker"
40
+ },
41
+ {
42
+ name: "theme",
43
+ description: "Change color theme",
44
+ action: "openThemePicker"
45
+ },
46
+ { name: "new", description: "Start new chat session", action: "newSession" },
47
+ { name: "clear", description: "Clear current chat", action: "clearChat" },
48
+ { name: "exit", description: "Exit chat", action: "exitChat" }
49
+ ];
50
+ function filterCommands(query) {
51
+ const normalizedQuery = query.toLowerCase().replace(/^\//, "");
52
+ if (!normalizedQuery) return SLASH_COMMANDS;
53
+ return SLASH_COMMANDS.filter(
54
+ (cmd) => cmd.name.toLowerCase().startsWith(normalizedQuery) || cmd.description.toLowerCase().includes(normalizedQuery)
55
+ );
56
+ }
57
+
58
+ // src/chat/utils/ThemeContext.tsx
59
+ import { createContext, useContext } from "react";
60
+ var ThemeContext = createContext(THEMES[0]);
61
+ var ThemeProvider = ThemeContext.Provider;
62
+ function useTheme() {
63
+ return useContext(ThemeContext);
64
+ }
65
+
66
+ // src/chat/components/SlashCommandMenu.tsx
67
+ import { useOnClick, useOnMouseMove } from "@ink-tools/ink-mouse";
68
+ import { Box, Text } from "ink";
69
+ import { memo, useMemo, useRef } from "react";
70
+ import { jsx, jsxs } from "react/jsx-runtime";
71
+ var VISIBLE_ROWS = 5;
72
+ var MenuItem = memo(function MenuItem2({
73
+ cmd,
74
+ isSelected,
75
+ width,
76
+ index,
77
+ accent,
78
+ textPrimary,
79
+ onHover,
80
+ onClick
81
+ }) {
82
+ const rowRef = useRef(null);
83
+ const nameColWidth = 15;
84
+ const nameText = `/${cmd.name}`.padEnd(nameColWidth);
85
+ const descWidth = width - nameColWidth - 6;
86
+ const descText = cmd.description.length > descWidth ? `${cmd.description.slice(0, descWidth - 3)}...` : cmd.description;
87
+ useOnMouseMove(rowRef, () => {
88
+ onHover(index);
89
+ });
90
+ useOnClick(rowRef, () => {
91
+ onClick(index);
92
+ });
93
+ if (isSelected) {
94
+ return /* @__PURE__ */ jsx(Box, { ref: rowRef, backgroundColor: accent, paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { color: "white", bold: true, children: [
95
+ nameText,
96
+ descText
97
+ ] }) });
98
+ }
99
+ return /* @__PURE__ */ jsxs(Box, { ref: rowRef, paddingX: 1, children: [
100
+ /* @__PURE__ */ jsx(Text, { color: textPrimary, children: nameText }),
101
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: descText })
102
+ ] });
103
+ });
104
+ var SlashCommandMenu = memo(function SlashCommandMenu2({
105
+ commands,
106
+ selectedIndex,
107
+ width,
108
+ onHover,
109
+ onSelect
110
+ }) {
111
+ const theme = useTheme();
112
+ const menuWidth = width;
113
+ const { visibleCommands, startIndex } = useMemo(() => {
114
+ let start = 0;
115
+ if (commands.length > VISIBLE_ROWS) {
116
+ start = Math.max(
117
+ 0,
118
+ Math.min(
119
+ selectedIndex - Math.floor(VISIBLE_ROWS / 2),
120
+ commands.length - VISIBLE_ROWS
121
+ )
122
+ );
123
+ }
124
+ return {
125
+ visibleCommands: commands.slice(start, start + VISIBLE_ROWS),
126
+ startIndex: start
127
+ };
128
+ }, [commands, selectedIndex]);
129
+ const handleHover = (index) => {
130
+ onHover?.(index);
131
+ };
132
+ const handleClick = (index) => {
133
+ onSelect?.(index);
134
+ };
135
+ if (commands.length === 0) {
136
+ return /* @__PURE__ */ jsx(
137
+ Box,
138
+ {
139
+ borderStyle: "round",
140
+ borderColor: "gray",
141
+ paddingX: 1,
142
+ width: menuWidth,
143
+ flexDirection: "column",
144
+ children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" })
145
+ }
146
+ );
147
+ }
148
+ return /* @__PURE__ */ jsx(
149
+ Box,
150
+ {
151
+ borderStyle: "round",
152
+ borderColor: theme.accent,
153
+ backgroundColor: theme.secondaryBg,
154
+ flexDirection: "column",
155
+ width: menuWidth,
156
+ children: visibleCommands.map((cmd, i) => /* @__PURE__ */ jsx(
157
+ MenuItem,
158
+ {
159
+ cmd,
160
+ isSelected: startIndex + i === selectedIndex,
161
+ width: menuWidth,
162
+ index: startIndex + i,
163
+ accent: theme.accent,
164
+ textPrimary: theme.textPrimary,
165
+ onHover: handleHover,
166
+ onClick: handleClick
167
+ },
168
+ cmd.name
169
+ ))
170
+ }
171
+ );
172
+ });
173
+
174
+ // src/chat/components/InputBox.tsx
175
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
176
+ var InputBox = memo2(function InputBox2({
177
+ onSubmit,
178
+ onSlashCommand,
179
+ slashModeRef,
180
+ modelName,
181
+ disabled = false,
182
+ width = 50,
183
+ placeholder = "Ask anything..."
184
+ }) {
185
+ const [value, setValue] = useState("");
186
+ const [cursorPos, setCursorPos] = useState(0);
187
+ const [menuIndex, setMenuIndex] = useState(0);
188
+ const isSlashMode = value.startsWith("/") && !value.includes("\n");
189
+ const filteredCommands = useMemo2(
190
+ () => isSlashMode ? filterCommands(value) : [],
191
+ [isSlashMode, value]
192
+ );
193
+ const prevSlashMode = useRef2(false);
194
+ if (prevSlashMode.current !== isSlashMode) {
195
+ prevSlashMode.current = isSlashMode;
196
+ slashModeRef.current = isSlashMode;
197
+ }
198
+ useInput(
199
+ (input, key) => {
200
+ if (disabled) return;
201
+ const normalizedInput = input.replaceAll("\x1B[200~", "").replaceAll("\x1B[201~", "").replaceAll("\r", "");
202
+ if (isSlashMode && filteredCommands.length > 0) {
203
+ if (key.upArrow) {
204
+ setMenuIndex((i) => Math.max(0, i - 1));
205
+ return;
206
+ }
207
+ if (key.downArrow) {
208
+ setMenuIndex((i) => Math.min(filteredCommands.length - 1, i + 1));
209
+ return;
210
+ }
211
+ if (key.tab) {
212
+ const selected = filteredCommands[menuIndex];
213
+ if (selected) {
214
+ setValue(`/${selected.name}`);
215
+ setCursorPos(selected.name.length + 1);
216
+ }
217
+ return;
218
+ }
219
+ if (key.return && !key.shift) {
220
+ const selected = filteredCommands[menuIndex];
221
+ if (selected) {
222
+ onSlashCommand(selected.action);
223
+ setValue("");
224
+ setCursorPos(0);
225
+ setMenuIndex(0);
226
+ }
227
+ return;
228
+ }
229
+ }
230
+ if (key.return && !key.shift) {
231
+ if (value.trim()) {
232
+ onSubmit(value);
233
+ setValue("");
234
+ setCursorPos(0);
235
+ setMenuIndex(0);
236
+ }
237
+ return;
238
+ }
239
+ if (key.escape && isSlashMode) {
240
+ setValue("");
241
+ setCursorPos(0);
242
+ setMenuIndex(0);
243
+ return;
244
+ }
245
+ if (key.return && key.shift || key.ctrl && input === "j") {
246
+ setValue((v) => `${v.slice(0, cursorPos)}
247
+ ${v.slice(cursorPos)}`);
248
+ setCursorPos((p) => p + 1);
249
+ return;
250
+ }
251
+ const isControlSequence = normalizedInput.length > 1 && (/^\[<?\d+;/.test(normalizedInput) || /^\[[0-9;]*[A-Za-z~]$/.test(normalizedInput));
252
+ if (isControlSequence) {
253
+ return;
254
+ }
255
+ if (normalizedInput.length > 1 && !key.ctrl && !key.meta && !normalizedInput.includes("\x1B")) {
256
+ setValue(
257
+ (v) => v.slice(0, cursorPos) + normalizedInput + v.slice(cursorPos)
258
+ );
259
+ setCursorPos((p) => p + normalizedInput.length);
260
+ setMenuIndex(0);
261
+ return;
262
+ }
263
+ if (key.backspace || key.delete) {
264
+ if (cursorPos > 0) {
265
+ setValue((v) => v.slice(0, cursorPos - 1) + v.slice(cursorPos));
266
+ setCursorPos((p) => p - 1);
267
+ setMenuIndex(0);
268
+ }
269
+ return;
270
+ }
271
+ if (key.leftArrow) {
272
+ if (key.ctrl) {
273
+ const before = value.slice(0, cursorPos);
274
+ const match = before.match(/\S+\s*$/);
275
+ setCursorPos((p) => match ? p - match[0].length : 0);
276
+ } else {
277
+ setCursorPos((p) => Math.max(0, p - 1));
278
+ }
279
+ return;
280
+ }
281
+ if (key.rightArrow) {
282
+ if (key.ctrl) {
283
+ const after = value.slice(cursorPos);
284
+ const match = after.match(/^\s*\S+/);
285
+ setCursorPos((p) => match ? p + match[0].length : value.length);
286
+ } else {
287
+ setCursorPos((p) => Math.min(value.length, p + 1));
288
+ }
289
+ return;
290
+ }
291
+ if (key.ctrl && input === "a") {
292
+ setCursorPos(0);
293
+ return;
294
+ }
295
+ if (key.ctrl && input === "e") {
296
+ setCursorPos(value.length);
297
+ return;
298
+ }
299
+ if (key.ctrl && input === "u") {
300
+ setValue("");
301
+ setCursorPos(0);
302
+ setMenuIndex(0);
303
+ return;
304
+ }
305
+ if (normalizedInput && !key.ctrl && !key.meta && normalizedInput.length === 1) {
306
+ const charCode = normalizedInput.charCodeAt(0);
307
+ if (charCode >= 32) {
308
+ setValue(
309
+ (v) => v.slice(0, cursorPos) + normalizedInput + v.slice(cursorPos)
310
+ );
311
+ setCursorPos((p) => p + 1);
312
+ setMenuIndex(0);
313
+ }
314
+ }
315
+ },
316
+ { isActive: !disabled }
317
+ );
318
+ const beforeCursor = value.slice(0, cursorPos);
319
+ const atCursor = value[cursorPos] || " ";
320
+ const afterCursor = value.slice(cursorPos + 1);
321
+ const isEmpty = value.length === 0;
322
+ const menuHeight = filteredCommands.length > 0 ? Math.min(filteredCommands.length, 5) + 2 : 3;
323
+ const cursor = "\u258B";
324
+ const theme = useTheme();
325
+ const handleMenuHover = (index) => {
326
+ setMenuIndex(index);
327
+ };
328
+ const handleMenuSelect = (index) => {
329
+ const selected = filteredCommands[index];
330
+ if (selected) {
331
+ onSlashCommand(selected.action);
332
+ setValue("");
333
+ setCursorPos(0);
334
+ setMenuIndex(0);
335
+ }
336
+ };
337
+ return /* @__PURE__ */ jsxs2(
338
+ Box2,
339
+ {
340
+ flexDirection: "column",
341
+ width,
342
+ backgroundColor: theme.background,
343
+ children: [
344
+ isSlashMode && /* @__PURE__ */ jsx2(Box2, { position: "absolute", marginTop: -menuHeight, marginLeft: 0, children: /* @__PURE__ */ jsx2(
345
+ SlashCommandMenu,
346
+ {
347
+ commands: filteredCommands,
348
+ selectedIndex: menuIndex,
349
+ width: width - 2,
350
+ onHover: handleMenuHover,
351
+ onSelect: handleMenuSelect
352
+ }
353
+ ) }),
354
+ /* @__PURE__ */ jsx2(
355
+ Box2,
356
+ {
357
+ flexDirection: "column",
358
+ paddingTop: 1,
359
+ backgroundColor: theme.background,
360
+ children: /* @__PURE__ */ jsx2(
361
+ Box2,
362
+ {
363
+ flexDirection: "column",
364
+ borderStyle: "round",
365
+ borderColor: theme.accent,
366
+ backgroundColor: theme.secondaryBg,
367
+ paddingX: 2,
368
+ paddingY: 1,
369
+ children: /* @__PURE__ */ jsx2(Box2, { children: isEmpty && !disabled ? /* @__PURE__ */ jsxs2(Text2, { children: [
370
+ /* @__PURE__ */ jsx2(Text2, { color: theme.textPrimary, children: cursor }),
371
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: placeholder })
372
+ ] }) : /* @__PURE__ */ jsxs2(
373
+ Text2,
374
+ {
375
+ color: disabled ? "gray" : isSlashMode ? theme.accent : theme.textPrimary,
376
+ children: [
377
+ beforeCursor,
378
+ !disabled && /* @__PURE__ */ jsx2(Text2, { color: theme.textPrimary, children: cursor }),
379
+ atCursor !== " " && atCursor,
380
+ afterCursor.replace(/\n/g, "\u21B5")
381
+ ]
382
+ }
383
+ ) })
384
+ }
385
+ )
386
+ }
387
+ ),
388
+ /* @__PURE__ */ jsxs2(
389
+ Box2,
390
+ {
391
+ justifyContent: "space-between",
392
+ paddingX: 1,
393
+ backgroundColor: theme.background,
394
+ children: [
395
+ /* @__PURE__ */ jsxs2(Box2, { children: [
396
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: "\u25C6 " }),
397
+ /* @__PURE__ */ jsx2(Text2, { color: theme.textPrimary, children: modelName })
398
+ ] }),
399
+ /* @__PURE__ */ jsxs2(Box2, { children: [
400
+ /* @__PURE__ */ jsx2(Text2, { color: theme.textPrimary, bold: true, children: "/" }),
401
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " commands " }),
402
+ /* @__PURE__ */ jsx2(Text2, { color: theme.textPrimary, bold: true, children: "ctrl+c" }),
403
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " exit" })
404
+ ] })
405
+ ]
406
+ }
407
+ )
408
+ ]
409
+ }
410
+ );
411
+ });
412
+
413
+ // src/chat/components/MessageList.tsx
414
+ import { useOnClick as useOnClick3, useOnWheel } from "@ink-tools/ink-mouse";
415
+ import { highlight } from "cli-highlight";
416
+ import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
417
+ import { ScrollView } from "ink-scroll-view";
418
+ import Spinner from "ink-spinner";
419
+ import {
420
+ forwardRef,
421
+ memo as memo4,
422
+ useCallback,
423
+ useEffect,
424
+ useImperativeHandle,
425
+ useMemo as useMemo3,
426
+ useRef as useRef4,
427
+ useState as useState3
428
+ } from "react";
429
+
430
+ // src/chat/components/MessageContextMenu.tsx
431
+ import { useOnClick as useOnClick2, useOnMouseMove as useOnMouseMove2 } from "@ink-tools/ink-mouse";
432
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
433
+ import { memo as memo3, useRef as useRef3, useState as useState2 } from "react";
434
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
435
+ var menuItems = [
436
+ { label: "Copy", description: "message text to clipboard", action: "copy" },
437
+ { label: "Exit", description: "close this menu", action: "exit" }
438
+ ];
439
+ var MenuItemRow = memo3(function MenuItemRow2({
440
+ item,
441
+ index,
442
+ isSelected,
443
+ rowWidth,
444
+ accent,
445
+ onHover,
446
+ onClick
447
+ }) {
448
+ const rowRef = useRef3(null);
449
+ useOnMouseMove2(rowRef, () => {
450
+ onHover(index);
451
+ });
452
+ useOnClick2(rowRef, () => {
453
+ onClick(item.action);
454
+ });
455
+ return /* @__PURE__ */ jsxs3(
456
+ Box3,
457
+ {
458
+ ref: rowRef,
459
+ backgroundColor: isSelected ? accent : void 0,
460
+ width: rowWidth,
461
+ justifyContent: "space-between",
462
+ paddingX: 1,
463
+ children: [
464
+ /* @__PURE__ */ jsx3(Text3, { color: isSelected ? "white" : "white", bold: isSelected, children: item.label }),
465
+ isSelected ? /* @__PURE__ */ jsx3(Text3, { color: "white", children: item.description }) : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: item.description })
466
+ ]
467
+ }
468
+ );
469
+ });
470
+ var MessageContextMenu = memo3(function MessageContextMenu2({
471
+ onSelect,
472
+ onClose,
473
+ width = 50
474
+ }) {
475
+ const [selectedIndex, setSelectedIndex] = useState2(0);
476
+ useInput2((input, key) => {
477
+ if (key.escape) {
478
+ onClose();
479
+ return;
480
+ }
481
+ if (key.return) {
482
+ const selected = menuItems[selectedIndex];
483
+ if (selected) {
484
+ if (selected.action === "exit") {
485
+ onClose();
486
+ } else {
487
+ onSelect(selected.action);
488
+ }
489
+ }
490
+ return;
491
+ }
492
+ if (key.upArrow || input === "k") {
493
+ setSelectedIndex((i) => Math.max(0, i - 1));
494
+ }
495
+ if (key.downArrow || input === "j") {
496
+ setSelectedIndex((i) => Math.min(menuItems.length - 1, i + 1));
497
+ }
498
+ });
499
+ const handleHover = (index) => {
500
+ setSelectedIndex(index);
501
+ };
502
+ const handleClick = (action) => {
503
+ if (action === "exit") {
504
+ onClose();
505
+ } else {
506
+ onSelect(action);
507
+ }
508
+ };
509
+ const theme = useTheme();
510
+ const menuWidth = Math.min(width, 50);
511
+ return /* @__PURE__ */ jsxs3(
512
+ Box3,
513
+ {
514
+ flexDirection: "column",
515
+ width: menuWidth,
516
+ borderStyle: "round",
517
+ borderColor: theme.accent,
518
+ backgroundColor: theme.background,
519
+ children: [
520
+ /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, justifyContent: "space-between", children: [
521
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.textPrimary, children: "Message Actions" }),
522
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "esc" })
523
+ ] }),
524
+ /* @__PURE__ */ jsx3(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(menuWidth - 4) }) }),
525
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", paddingBottom: 1, children: menuItems.map((item, index) => /* @__PURE__ */ jsx3(
526
+ MenuItemRow,
527
+ {
528
+ item,
529
+ index,
530
+ isSelected: index === selectedIndex,
531
+ rowWidth: menuWidth - 2,
532
+ accent: theme.accent,
533
+ onHover: handleHover,
534
+ onClick: handleClick
535
+ },
536
+ item.action
537
+ )) })
538
+ ]
539
+ }
540
+ );
541
+ });
542
+
543
+ // src/chat/components/MessageList.tsx
544
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
545
+ function parseInlineMarkdown(text, keyPrefix) {
546
+ const nodes = [];
547
+ const inlineRegex = /(\*\*|__)(.+?)\1|(\*|_)(.+?)\3|`([^`]+)`/g;
548
+ let lastIndex = 0;
549
+ let match;
550
+ let idx = 0;
551
+ match = inlineRegex.exec(text);
552
+ while (match !== null) {
553
+ if (match.index > lastIndex) {
554
+ nodes.push(text.slice(lastIndex, match.index));
555
+ }
556
+ if (match[2]) {
557
+ nodes.push(
558
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: match[2] }, `${keyPrefix}-b-${idx++}`)
559
+ );
560
+ } else if (match[4]) {
561
+ nodes.push(
562
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: match[4] }, `${keyPrefix}-i-${idx++}`)
563
+ );
564
+ } else if (match[5]) {
565
+ nodes.push(
566
+ /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: match[5] }, `${keyPrefix}-c-${idx++}`)
567
+ );
568
+ }
569
+ lastIndex = match.index + match[0].length;
570
+ match = inlineRegex.exec(text);
571
+ }
572
+ if (lastIndex < text.length) {
573
+ nodes.push(text.slice(lastIndex));
574
+ }
575
+ return nodes.length > 0 ? nodes : [text];
576
+ }
577
+ function parseLine(line, keyIndex, _maxWidth) {
578
+ const trimmed = line.trimStart();
579
+ const indent = line.length - trimmed.length;
580
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
581
+ return null;
582
+ }
583
+ const headerMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
584
+ if (headerMatch) {
585
+ const level = headerMatch[1].length;
586
+ const headerText = headerMatch[2];
587
+ const parsed2 = parseInlineMarkdown(headerText, `h-${keyIndex}`);
588
+ if (level === 1) {
589
+ return /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: parsed2 }, keyIndex);
590
+ }
591
+ if (level === 2) {
592
+ return /* @__PURE__ */ jsx4(Text4, { bold: true, color: "white", children: parsed2 }, keyIndex);
593
+ }
594
+ return /* @__PURE__ */ jsx4(Text4, { bold: true, children: parsed2 }, keyIndex);
595
+ }
596
+ const bulletMatch = trimmed.match(/^[-*]\s+(.+)$/);
597
+ if (bulletMatch) {
598
+ const bulletText = bulletMatch[1];
599
+ const parsed2 = parseInlineMarkdown(bulletText, `li-${keyIndex}`);
600
+ return /* @__PURE__ */ jsx4(Box4, { paddingLeft: indent, children: /* @__PURE__ */ jsxs4(Text4, { children: [
601
+ /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "\u2022" }),
602
+ " ",
603
+ parsed2
604
+ ] }) }, keyIndex);
605
+ }
606
+ const numberedMatch = trimmed.match(/^(\d+)\.\s+(.+)$/);
607
+ if (numberedMatch) {
608
+ const num = numberedMatch[1];
609
+ const itemText = numberedMatch[2];
610
+ const parsed2 = parseInlineMarkdown(itemText, `ol-${keyIndex}`);
611
+ return /* @__PURE__ */ jsx4(Box4, { paddingLeft: indent, children: /* @__PURE__ */ jsxs4(Text4, { children: [
612
+ /* @__PURE__ */ jsxs4(Text4, { color: "yellow", children: [
613
+ num,
614
+ "."
615
+ ] }),
616
+ " ",
617
+ parsed2
618
+ ] }) }, keyIndex);
619
+ }
620
+ const quoteMatch = trimmed.match(/^>\s*(.*)$/);
621
+ if (quoteMatch) {
622
+ const quoteText = quoteMatch[1];
623
+ const parsed2 = parseInlineMarkdown(quoteText, `q-${keyIndex}`);
624
+ return /* @__PURE__ */ jsx4(Box4, { paddingLeft: 1, children: /* @__PURE__ */ jsxs4(Text4, { children: [
625
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u2502" }),
626
+ " ",
627
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: parsed2 })
628
+ ] }) }, keyIndex);
629
+ }
630
+ if (trimmed.length === 0) {
631
+ return /* @__PURE__ */ jsx4(Text4, { children: " " }, keyIndex);
632
+ }
633
+ const parsed = parseInlineMarkdown(line, `p-${keyIndex}`);
634
+ return /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: parsed }, keyIndex);
635
+ }
636
+ var BOX = {
637
+ topLeft: "\u250C",
638
+ topRight: "\u2510",
639
+ bottomLeft: "\u2514",
640
+ bottomRight: "\u2518",
641
+ horizontal: "\u2500",
642
+ vertical: "\u2502",
643
+ topMid: "\u252C",
644
+ bottomMid: "\u2534",
645
+ leftMid: "\u251C",
646
+ rightMid: "\u2524",
647
+ midMid: "\u253C"
648
+ };
649
+ function wrapText(text, maxWidth) {
650
+ if (text.length <= maxWidth) {
651
+ return [text];
652
+ }
653
+ const words = text.split(" ");
654
+ const lines = [];
655
+ let currentLine = "";
656
+ for (const word of words) {
657
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
658
+ if (testLine.length <= maxWidth) {
659
+ currentLine = testLine;
660
+ } else {
661
+ if (currentLine) {
662
+ lines.push(currentLine);
663
+ }
664
+ if (word.length > maxWidth) {
665
+ let remaining = word;
666
+ while (remaining.length > maxWidth) {
667
+ lines.push(remaining.slice(0, maxWidth));
668
+ remaining = remaining.slice(maxWidth);
669
+ }
670
+ currentLine = remaining;
671
+ } else {
672
+ currentLine = word;
673
+ }
674
+ }
675
+ }
676
+ if (currentLine) {
677
+ lines.push(currentLine);
678
+ }
679
+ return lines.length > 0 ? lines : [""];
680
+ }
681
+ function parseTable(lines, startKey, maxWidth) {
682
+ const headerLine = lines[0];
683
+ const headers = headerLine.split("|").map((h) => h.trim()).filter((h) => h.length > 0);
684
+ if (headers.length === 0) {
685
+ return { node: null, linesConsumed: 0 };
686
+ }
687
+ const separatorLine = lines[1];
688
+ if (!separatorLine || !/^\|?[\s-:|]+\|?$/.test(separatorLine)) {
689
+ return { node: null, linesConsumed: 0 };
690
+ }
691
+ const dataRows = [];
692
+ let i = 2;
693
+ while (i < lines.length && lines[i].includes("|")) {
694
+ const row = lines[i].split("|").map((c) => c.trim()).filter((_, idx, arr) => {
695
+ if (idx === 0 && arr[idx] === "") return false;
696
+ if (idx === arr.length - 1 && arr[idx] === "") return false;
697
+ return true;
698
+ });
699
+ if (row.length > 0) {
700
+ dataRows.push(row);
701
+ }
702
+ i++;
703
+ }
704
+ const numCols = headers.length;
705
+ const availableWidth = maxWidth - (numCols + 1) - numCols * 2;
706
+ const colWidth = Math.max(Math.floor(availableWidth / numCols), 15);
707
+ const colWidths = headers.map(() => colWidth);
708
+ const tableRows = [];
709
+ let keyIdx = startKey;
710
+ let topBorder = BOX.topLeft;
711
+ for (let c = 0; c < numCols; c++) {
712
+ topBorder += BOX.horizontal.repeat(colWidths[c] + 2);
713
+ topBorder += c < numCols - 1 ? BOX.topMid : BOX.topRight;
714
+ }
715
+ tableRows.push(
716
+ /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: topBorder }) }, `top-${keyIdx++}`)
717
+ );
718
+ const wrappedHeaders = headers.map((h, idx) => wrapText(h, colWidths[idx]));
719
+ const headerLineCount = Math.max(...wrappedHeaders.map((w) => w.length));
720
+ for (let lineIdx = 0; lineIdx < headerLineCount; lineIdx++) {
721
+ tableRows.push(
722
+ /* @__PURE__ */ jsxs4(Box4, { children: [
723
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: BOX.vertical }),
724
+ headers.map((_, colIdx) => {
725
+ const wrappedLines = wrappedHeaders[colIdx];
726
+ const lineText = wrappedLines[lineIdx] || "";
727
+ return /* @__PURE__ */ jsxs4(Text4, { children: [
728
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "yellow", children: [
729
+ " ",
730
+ lineText.padEnd(colWidths[colIdx]),
731
+ " "
732
+ ] }),
733
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: BOX.vertical })
734
+ ] }, `h-${colIdx}-${lineIdx}`);
735
+ })
736
+ ] }, `header-${keyIdx++}-${lineIdx}`)
737
+ );
738
+ }
739
+ let headerSep = BOX.leftMid;
740
+ for (let c = 0; c < numCols; c++) {
741
+ headerSep += BOX.horizontal.repeat(colWidths[c] + 2);
742
+ headerSep += c < numCols - 1 ? BOX.midMid : BOX.rightMid;
743
+ }
744
+ tableRows.push(
745
+ /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: headerSep }) }, `sep-${keyIdx++}`)
746
+ );
747
+ for (let rowIdx = 0; rowIdx < dataRows.length; rowIdx++) {
748
+ const row = dataRows[rowIdx];
749
+ const wrappedCells = headers.map((_, colIdx) => {
750
+ const cell = row[colIdx] || "";
751
+ return wrapText(cell, colWidths[colIdx]);
752
+ });
753
+ const maxLines = Math.max(...wrappedCells.map((w) => w.length));
754
+ for (let lineIdx = 0; lineIdx < maxLines; lineIdx++) {
755
+ tableRows.push(
756
+ /* @__PURE__ */ jsxs4(Box4, { children: [
757
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: BOX.vertical }),
758
+ headers.map((_, colIdx) => {
759
+ const wrappedLines = wrappedCells[colIdx];
760
+ const lineText = wrappedLines[lineIdx] || "";
761
+ const paddedText = lineText.padEnd(colWidths[colIdx]);
762
+ const parsed = parseInlineMarkdown(
763
+ paddedText,
764
+ `cell-${rowIdx}-${colIdx}-${lineIdx}`
765
+ );
766
+ return /* @__PURE__ */ jsxs4(Text4, { children: [
767
+ " ",
768
+ parsed,
769
+ " ",
770
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: BOX.vertical })
771
+ ] }, `c-${colIdx}-${rowIdx}-${lineIdx}`);
772
+ })
773
+ ] }, `row-${keyIdx++}-${rowIdx}-${lineIdx}`)
774
+ );
775
+ }
776
+ }
777
+ let bottomBorder = BOX.bottomLeft;
778
+ for (let c = 0; c < numCols; c++) {
779
+ bottomBorder += BOX.horizontal.repeat(colWidths[c] + 2);
780
+ bottomBorder += c < numCols - 1 ? BOX.bottomMid : BOX.bottomRight;
781
+ }
782
+ tableRows.push(
783
+ /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: bottomBorder }) }, `bottom-${keyIdx++}`)
784
+ );
785
+ return {
786
+ node: /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: tableRows }, startKey),
787
+ linesConsumed: i
788
+ };
789
+ }
790
+ function isTableRow(line) {
791
+ const trimmed = line.trim();
792
+ return trimmed.startsWith("|") && trimmed.endsWith("|") && trimmed.length > 2;
793
+ }
794
+ function processLines(lines, startKeyIndex, maxWidth) {
795
+ const parts = [];
796
+ let keyIndex = startKeyIndex;
797
+ let i = 0;
798
+ while (i < lines.length) {
799
+ const line = lines[i];
800
+ if (isTableRow(line) && i + 1 < lines.length) {
801
+ const nextLine = lines[i + 1];
802
+ if (/^\|?[\s-:|]+\|?$/.test(nextLine.trim())) {
803
+ const tableLines = lines.slice(i);
804
+ const { node, linesConsumed } = parseTable(
805
+ tableLines,
806
+ keyIndex,
807
+ maxWidth
808
+ );
809
+ if (node && linesConsumed > 0) {
810
+ parts.push(node);
811
+ keyIndex++;
812
+ i += linesConsumed;
813
+ continue;
814
+ }
815
+ }
816
+ }
817
+ parts.push(parseLine(line, keyIndex++, maxWidth));
818
+ i++;
819
+ }
820
+ return { parts, keyIndex };
821
+ }
822
+ function parseContent(content, maxWidth) {
823
+ const parts = [];
824
+ const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
825
+ let lastIndex = 0;
826
+ let match;
827
+ let keyIndex = 0;
828
+ match = codeBlockRegex.exec(content);
829
+ while (match !== null) {
830
+ if (match.index > lastIndex) {
831
+ const text = content.slice(lastIndex, match.index);
832
+ const lines = text.split("\n");
833
+ const result = processLines(lines, keyIndex, maxWidth);
834
+ parts.push(...result.parts);
835
+ keyIndex = result.keyIndex;
836
+ }
837
+ const language = match[1] || "bash";
838
+ const code = match[2].trim();
839
+ try {
840
+ const highlighted = highlight(code, { language, ignoreIllegals: true });
841
+ parts.push(
842
+ /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", width: maxWidth - 4, children: /* @__PURE__ */ jsxs4(
843
+ Box4,
844
+ {
845
+ borderStyle: "single",
846
+ borderColor: "gray",
847
+ paddingX: 1,
848
+ flexDirection: "column",
849
+ children: [
850
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: language }),
851
+ /* @__PURE__ */ jsx4(Text4, { children: highlighted })
852
+ ]
853
+ }
854
+ ) }, keyIndex++)
855
+ );
856
+ } catch {
857
+ parts.push(
858
+ /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", width: maxWidth - 4, children: /* @__PURE__ */ jsxs4(
859
+ Box4,
860
+ {
861
+ borderStyle: "single",
862
+ borderColor: "gray",
863
+ paddingX: 1,
864
+ flexDirection: "column",
865
+ children: [
866
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: language }),
867
+ /* @__PURE__ */ jsx4(Text4, { children: code })
868
+ ]
869
+ }
870
+ ) }, keyIndex++)
871
+ );
872
+ }
873
+ lastIndex = match.index + match[0].length;
874
+ match = codeBlockRegex.exec(content);
875
+ }
876
+ if (lastIndex < content.length) {
877
+ const text = content.slice(lastIndex);
878
+ const lines = text.split("\n");
879
+ const result = processLines(lines, keyIndex, maxWidth);
880
+ parts.push(...result.parts);
881
+ }
882
+ return parts.length > 0 ? parts : [
883
+ /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: content }, 0)
884
+ ];
885
+ }
886
+ var Message = memo4(function Message2({
887
+ message,
888
+ width,
889
+ messageIndex,
890
+ accent,
891
+ onMessageClick
892
+ }) {
893
+ const isUser = message.role === "user";
894
+ const messageRef = useRef4(null);
895
+ const parsedContent = useMemo3(
896
+ () => parseContent(message.content, width - 4),
897
+ [message.content, width]
898
+ );
899
+ useOnClick3(
900
+ messageRef,
901
+ !isUser && onMessageClick ? () => {
902
+ onMessageClick(messageIndex, message.content);
903
+ } : null
904
+ );
905
+ return /* @__PURE__ */ jsxs4(Box4, { ref: messageRef, flexDirection: "column", marginY: 1, width, children: [
906
+ /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { bold: true, color: isUser ? accent : "yellow", children: [
907
+ isUser ? "You" : "Bashio",
908
+ ":"
909
+ ] }) }),
910
+ /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", paddingLeft: 2, children: parsedContent })
911
+ ] });
912
+ });
913
+ var EmptyState = memo4(function EmptyState2({ height }) {
914
+ return /* @__PURE__ */ jsxs4(
915
+ Box4,
916
+ {
917
+ flexDirection: "column",
918
+ height,
919
+ paddingX: 1,
920
+ justifyContent: "center",
921
+ alignItems: "center",
922
+ children: [
923
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Start a conversation by typing a message below." }),
924
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Type / for commands or Ctrl+P to switch models." })
925
+ ]
926
+ }
927
+ );
928
+ });
929
+ var MessageList = memo4(
930
+ forwardRef(function MessageList2({ messages, currentResponse, isLoading, height, width, slashModeRef }, ref) {
931
+ const theme = useTheme();
932
+ const scrollRef = useRef4(null);
933
+ const mouseRef = useRef4(null);
934
+ const [followOutput, setFollowOutput] = useState3(true);
935
+ const [contextMenu, setContextMenu] = useState3(null);
936
+ const handleMessageClick = useCallback(
937
+ (messageIndex, content) => {
938
+ setContextMenu({ messageIndex, content });
939
+ },
940
+ []
941
+ );
942
+ const handleContextMenuSelect = useCallback(
943
+ async (action) => {
944
+ if (action === "copy" && contextMenu) {
945
+ await copyToClipboard(contextMenu.content);
946
+ }
947
+ setContextMenu(null);
948
+ },
949
+ [contextMenu]
950
+ );
951
+ const handleContextMenuClose = useCallback(() => {
952
+ setContextMenu(null);
953
+ }, []);
954
+ const boundedScrollBy = useCallback((delta) => {
955
+ const sv = scrollRef.current;
956
+ if (!sv) return;
957
+ const currentOffset = sv.getScrollOffset();
958
+ const maxOffset = sv.getBottomOffset();
959
+ const newOffset = Math.max(0, Math.min(maxOffset, currentOffset + delta));
960
+ sv.scrollTo(newOffset);
961
+ setFollowOutput(newOffset >= maxOffset);
962
+ }, []);
963
+ const boundedScrollToBottom = useCallback(() => {
964
+ const sv = scrollRef.current;
965
+ if (!sv) return;
966
+ const maxOffset = sv.getBottomOffset();
967
+ sv.scrollTo(Math.max(0, maxOffset));
968
+ setFollowOutput(true);
969
+ }, []);
970
+ useImperativeHandle(
971
+ ref,
972
+ () => ({
973
+ scrollBy: boundedScrollBy,
974
+ scrollToBottom: boundedScrollToBottom
975
+ }),
976
+ [boundedScrollBy, boundedScrollToBottom]
977
+ );
978
+ const messagesCount = messages.length;
979
+ const hasResponse = Boolean(currentResponse);
980
+ useEffect(() => {
981
+ if (!followOutput) return;
982
+ const timer = setTimeout(() => {
983
+ boundedScrollToBottom();
984
+ }, 10);
985
+ return () => clearTimeout(timer);
986
+ }, [messagesCount, hasResponse, boundedScrollToBottom, followOutput]);
987
+ useInput3((input, key) => {
988
+ if (slashModeRef.current) return;
989
+ if (input.startsWith("\x1B[<")) {
990
+ return;
991
+ }
992
+ const isUpSequence = input === "\x1B[A";
993
+ const isDownSequence = input === "\x1B[B";
994
+ const isPageUpSequence = input === "\x1B[5~";
995
+ const isPageDownSequence = input === "\x1B[6~";
996
+ if (key.ctrl && input === "l") {
997
+ boundedScrollToBottom();
998
+ return;
999
+ }
1000
+ if (key.upArrow || isUpSequence) boundedScrollBy(-1);
1001
+ if (key.downArrow || isDownSequence) boundedScrollBy(1);
1002
+ if (key.pageUp || isPageUpSequence) {
1003
+ boundedScrollBy(-Math.floor(height / 2));
1004
+ }
1005
+ if (key.pageDown || isPageDownSequence) {
1006
+ boundedScrollBy(Math.floor(height / 2));
1007
+ }
1008
+ if (input === "k" && key.ctrl) boundedScrollBy(-3);
1009
+ if (input === "k" && key.meta) boundedScrollBy(-3);
1010
+ if (input === "j" && key.meta) boundedScrollBy(3);
1011
+ });
1012
+ useOnWheel(mouseRef, (event) => {
1013
+ if (event.button === "wheel-up") {
1014
+ boundedScrollBy(-3);
1015
+ } else if (event.button === "wheel-down") {
1016
+ boundedScrollBy(3);
1017
+ }
1018
+ });
1019
+ useEffect(() => {
1020
+ if (followOutput) return;
1021
+ const sv = scrollRef.current;
1022
+ if (!sv) return;
1023
+ const currentOffset = sv.getScrollOffset();
1024
+ const maxOffset = sv.getBottomOffset();
1025
+ if (maxOffset === 0 || currentOffset >= maxOffset) {
1026
+ setFollowOutput(true);
1027
+ }
1028
+ }, [followOutput]);
1029
+ const streamingContent = useMemo3(
1030
+ () => currentResponse ? parseContent(currentResponse, width - 4) : null,
1031
+ [currentResponse, width]
1032
+ );
1033
+ if (messages.length === 0 && !currentResponse) {
1034
+ return /* @__PURE__ */ jsx4(EmptyState, { height });
1035
+ }
1036
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height, width, children: [
1037
+ /* @__PURE__ */ jsx4(Box4, { ref: mouseRef, height, paddingX: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs4(ScrollView, { ref: scrollRef, height, children: [
1038
+ messages.map((msg, i) => /* @__PURE__ */ jsx4(
1039
+ Message,
1040
+ {
1041
+ message: msg,
1042
+ width,
1043
+ messageIndex: i,
1044
+ accent: theme.accent,
1045
+ onMessageClick: contextMenu === null ? handleMessageClick : void 0
1046
+ },
1047
+ `msg-${i}-${msg.role}`
1048
+ )),
1049
+ streamingContent && /* @__PURE__ */ jsxs4(
1050
+ Box4,
1051
+ {
1052
+ flexDirection: "column",
1053
+ marginY: 1,
1054
+ width,
1055
+ children: [
1056
+ /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: "Bashio:" }) }),
1057
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 2, children: [
1058
+ streamingContent,
1059
+ /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: "|" })
1060
+ ] })
1061
+ ]
1062
+ },
1063
+ "streaming"
1064
+ ),
1065
+ isLoading && !currentResponse && /* @__PURE__ */ jsxs4(Box4, { marginY: 1, children: [
1066
+ /* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: /* @__PURE__ */ jsx4(Spinner, { type: "dots" }) }),
1067
+ /* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: " Thinking..." })
1068
+ ] }, "loading")
1069
+ ] }) }),
1070
+ contextMenu !== null && /* @__PURE__ */ jsx4(
1071
+ Box4,
1072
+ {
1073
+ position: "absolute",
1074
+ width,
1075
+ height,
1076
+ justifyContent: "center",
1077
+ alignItems: "center",
1078
+ children: /* @__PURE__ */ jsx4(
1079
+ MessageContextMenu,
1080
+ {
1081
+ onSelect: handleContextMenuSelect,
1082
+ onClose: handleContextMenuClose,
1083
+ width: Math.min(width - 4, 50)
1084
+ }
1085
+ )
1086
+ }
1087
+ )
1088
+ ] });
1089
+ })
1090
+ );
1091
+
1092
+ // src/chat/components/ModelSwitcher.tsx
1093
+ import { useOnClick as useOnClick4, useOnMouseMove as useOnMouseMove3 } from "@ink-tools/ink-mouse";
1094
+ import { Box as Box5, Text as Text5, useInput as useInput4, useStdout } from "ink";
1095
+ import { memo as memo5, useRef as useRef5, useState as useState4 } from "react";
1096
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1097
+ var PROVIDER_LABELS = {
1098
+ "claude-subscription": "Claude Pro/Max",
1099
+ "chatgpt-subscription": "ChatGPT Plus/Pro",
1100
+ copilot: "GitHub Copilot",
1101
+ claude: "Claude API",
1102
+ openai: "OpenAI API",
1103
+ ollama: "Ollama",
1104
+ openrouter: "OpenRouter"
1105
+ };
1106
+ var ModelRow = memo5(function ModelRow2({
1107
+ option,
1108
+ isSelected,
1109
+ isCurrent,
1110
+ rowWidth,
1111
+ accent,
1112
+ onHover,
1113
+ onClick
1114
+ }) {
1115
+ const rowRef = useRef5(null);
1116
+ useOnMouseMove3(rowRef, onHover);
1117
+ useOnClick4(rowRef, onClick);
1118
+ const prefix = isSelected ? ">" : isCurrent ? "\u2713" : " ";
1119
+ const labelWithPrefix = ` ${prefix} ${option.label}`;
1120
+ const padding = Math.max(0, rowWidth - labelWithPrefix.length);
1121
+ const fullLine = labelWithPrefix + " ".repeat(padding);
1122
+ return /* @__PURE__ */ jsx5(Box5, { ref: rowRef, children: /* @__PURE__ */ jsx5(
1123
+ Text5,
1124
+ {
1125
+ backgroundColor: isSelected ? accent : void 0,
1126
+ color: isSelected ? "#000000" : void 0,
1127
+ children: fullLine
1128
+ }
1129
+ ) });
1130
+ });
1131
+ function getModelsForProvider(provider) {
1132
+ switch (provider) {
1133
+ case "claude":
1134
+ return CLAUDE_MODELS;
1135
+ case "claude-subscription":
1136
+ return CLAUDE_SUBSCRIPTION_MODELS;
1137
+ case "openai":
1138
+ return OPENAI_MODELS;
1139
+ case "chatgpt-subscription":
1140
+ return CHATGPT_SUBSCRIPTION_MODELS;
1141
+ case "copilot":
1142
+ return COPILOT_MODELS;
1143
+ case "ollama":
1144
+ return OLLAMA_RECOMMENDED_MODELS.map((m) => ({ value: m, label: m }));
1145
+ case "openrouter":
1146
+ return OPENROUTER_MODELS;
1147
+ default:
1148
+ return [];
1149
+ }
1150
+ }
1151
+ function ModelSwitcher({
1152
+ config,
1153
+ onSelect,
1154
+ onClose
1155
+ }) {
1156
+ const { stdout } = useStdout();
1157
+ const width = stdout?.columns ?? 80;
1158
+ const height = stdout?.rows ?? 24;
1159
+ const theme = useTheme();
1160
+ const currentProvider = config.activeProvider;
1161
+ const currentModel = config.providers[currentProvider]?.model;
1162
+ const options = [];
1163
+ const providers = Object.keys(config.providers);
1164
+ for (const provider of providers) {
1165
+ const models = getModelsForProvider(provider);
1166
+ const providerLabel = PROVIDER_LABELS[provider] || provider;
1167
+ for (const model of models) {
1168
+ options.push({
1169
+ provider,
1170
+ providerLabel,
1171
+ model: model.value,
1172
+ label: model.label
1173
+ });
1174
+ }
1175
+ }
1176
+ const currentIndex = options.findIndex(
1177
+ (o) => o.provider === currentProvider && o.model === currentModel
1178
+ );
1179
+ const [selectedIndex, setSelectedIndex] = useState4(
1180
+ currentIndex >= 0 ? currentIndex : 0
1181
+ );
1182
+ const handleHover = (index) => {
1183
+ setSelectedIndex(index);
1184
+ };
1185
+ const handleClick = (index) => {
1186
+ const selected = options[index];
1187
+ if (selected) {
1188
+ onSelect(selected.provider, selected.model);
1189
+ }
1190
+ };
1191
+ useInput4((input, key) => {
1192
+ if (key.escape) {
1193
+ onClose();
1194
+ return;
1195
+ }
1196
+ if (key.return) {
1197
+ const selected = options[selectedIndex];
1198
+ if (selected) {
1199
+ onSelect(selected.provider, selected.model);
1200
+ }
1201
+ return;
1202
+ }
1203
+ if (key.upArrow || input === "k") {
1204
+ setSelectedIndex((i) => Math.max(0, i - 1));
1205
+ }
1206
+ if (key.downArrow || input === "j") {
1207
+ setSelectedIndex((i) => Math.min(options.length - 1, i + 1));
1208
+ }
1209
+ });
1210
+ const boxWidth = Math.min(60, width - 4);
1211
+ const maxListHeight = 12;
1212
+ const listHeight = Math.min(options.length, maxListHeight, height - 8);
1213
+ let startIndex = 0;
1214
+ if (selectedIndex >= listHeight) {
1215
+ startIndex = selectedIndex - listHeight + 1;
1216
+ }
1217
+ const visibleOptions = options.slice(startIndex, startIndex + listHeight);
1218
+ let lastProvider = null;
1219
+ return /* @__PURE__ */ jsx5(
1220
+ Box5,
1221
+ {
1222
+ flexDirection: "column",
1223
+ width,
1224
+ height,
1225
+ justifyContent: "center",
1226
+ alignItems: "center",
1227
+ backgroundColor: theme.background,
1228
+ children: /* @__PURE__ */ jsxs5(
1229
+ Box5,
1230
+ {
1231
+ flexDirection: "column",
1232
+ width: boxWidth,
1233
+ borderStyle: "round",
1234
+ borderColor: theme.accent,
1235
+ paddingX: 1,
1236
+ children: [
1237
+ /* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, justifyContent: "space-between", children: [
1238
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.accent, children: "Model Selector" }),
1239
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "esc to exit" })
1240
+ ] }),
1241
+ /* @__PURE__ */ jsxs5(Box5, { children: [
1242
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: "Current: " }),
1243
+ /* @__PURE__ */ jsxs5(Text5, { children: [
1244
+ PROVIDER_LABELS[currentProvider],
1245
+ " / ",
1246
+ currentModel
1247
+ ] })
1248
+ ] }),
1249
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500".repeat(boxWidth - 4) }),
1250
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: visibleOptions.map((option, i) => {
1251
+ const actualIndex = startIndex + i;
1252
+ const isSelected = actualIndex === selectedIndex;
1253
+ const isCurrent = option.provider === currentProvider && option.model === currentModel;
1254
+ const showProviderHeader = option.provider !== lastProvider;
1255
+ lastProvider = option.provider;
1256
+ return /* @__PURE__ */ jsxs5(
1257
+ Box5,
1258
+ {
1259
+ flexDirection: "column",
1260
+ children: [
1261
+ showProviderHeader && /* @__PURE__ */ jsxs5(Text5, { bold: true, color: theme.accent, children: [
1262
+ i > 0 ? "\n" : "",
1263
+ option.providerLabel
1264
+ ] }),
1265
+ /* @__PURE__ */ jsx5(
1266
+ ModelRow,
1267
+ {
1268
+ option,
1269
+ isSelected,
1270
+ isCurrent,
1271
+ rowWidth: boxWidth - 4,
1272
+ accent: theme.accent,
1273
+ onHover: () => handleHover(actualIndex),
1274
+ onClick: () => handleClick(actualIndex)
1275
+ }
1276
+ )
1277
+ ]
1278
+ },
1279
+ `${option.provider}-${option.model}`
1280
+ );
1281
+ }) }),
1282
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500".repeat(boxWidth - 4) }),
1283
+ /* @__PURE__ */ jsxs5(Box5, { justifyContent: "space-between", children: [
1284
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 navigate" }),
1285
+ options.length > listHeight && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1286
+ startIndex + 1,
1287
+ "-",
1288
+ Math.min(startIndex + listHeight, options.length),
1289
+ "/",
1290
+ options.length
1291
+ ] }),
1292
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u21B5 select" })
1293
+ ] })
1294
+ ]
1295
+ }
1296
+ )
1297
+ }
1298
+ );
1299
+ }
1300
+
1301
+ // src/chat/components/SessionHeader.tsx
1302
+ import { Box as Box6, Text as Text6 } from "ink";
1303
+ import { memo as memo6 } from "react";
1304
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1305
+ var SessionHeader = memo6(function SessionHeader2({
1306
+ sessionTitle,
1307
+ width
1308
+ }) {
1309
+ const theme = useTheme();
1310
+ return /* @__PURE__ */ jsxs6(
1311
+ Box6,
1312
+ {
1313
+ borderStyle: "single",
1314
+ borderLeft: true,
1315
+ borderRight: false,
1316
+ borderTop: false,
1317
+ borderBottom: false,
1318
+ borderColor: theme.accent,
1319
+ backgroundColor: theme.secondaryBg,
1320
+ paddingX: 1,
1321
+ paddingY: 1,
1322
+ gap: 1,
1323
+ width,
1324
+ children: [
1325
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: "#" }),
1326
+ /* @__PURE__ */ jsx6(Text6, { color: theme.textPrimary, children: sessionTitle })
1327
+ ]
1328
+ }
1329
+ );
1330
+ });
1331
+
1332
+ // src/chat/components/SessionPicker.tsx
1333
+ import { useOnClick as useOnClick5, useOnMouseMove as useOnMouseMove4 } from "@ink-tools/ink-mouse";
1334
+ import { Box as Box7, Text as Text7, useInput as useInput5, useStdout as useStdout2 } from "ink";
1335
+ import { memo as memo7, useRef as useRef6, useState as useState5 } from "react";
1336
+
1337
+ // src/chat/utils/sessions.ts
1338
+ import {
1339
+ existsSync,
1340
+ mkdirSync,
1341
+ readdirSync,
1342
+ readFileSync,
1343
+ unlinkSync,
1344
+ writeFileSync
1345
+ } from "fs";
1346
+ import { homedir } from "os";
1347
+ import { join } from "path";
1348
+ var MAX_SESSIONS = 50;
1349
+ function getSessionsDir() {
1350
+ const dir = join(homedir(), ".bashio", "sessions");
1351
+ if (!existsSync(dir)) {
1352
+ mkdirSync(dir, { recursive: true });
1353
+ }
1354
+ return dir;
1355
+ }
1356
+ function getSessionPath(id) {
1357
+ return join(getSessionsDir(), `${id}.json`);
1358
+ }
1359
+ function generateId() {
1360
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1361
+ }
1362
+ function generateTitle(messages) {
1363
+ const firstUserMsg = messages.find((m) => m.role === "user");
1364
+ if (!firstUserMsg) return "New Chat";
1365
+ const content = firstUserMsg.content.trim();
1366
+ if (content.length <= 50) return content;
1367
+ return `${content.slice(0, 47)}...`;
1368
+ }
1369
+ function listSessions() {
1370
+ const dir = getSessionsDir();
1371
+ const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
1372
+ const sessions = [];
1373
+ for (const file of files) {
1374
+ try {
1375
+ const content = readFileSync(join(dir, file), "utf-8");
1376
+ const session = JSON.parse(content);
1377
+ sessions.push({
1378
+ id: session.id,
1379
+ title: session.title,
1380
+ createdAt: session.createdAt,
1381
+ updatedAt: session.updatedAt,
1382
+ messageCount: session.messages.length,
1383
+ model: session.model,
1384
+ provider: session.provider
1385
+ });
1386
+ } catch {
1387
+ }
1388
+ }
1389
+ return sessions.sort(
1390
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
1391
+ );
1392
+ }
1393
+ function loadSession(id) {
1394
+ const path = getSessionPath(id);
1395
+ if (!existsSync(path)) return null;
1396
+ try {
1397
+ const content = readFileSync(path, "utf-8");
1398
+ return JSON.parse(content);
1399
+ } catch {
1400
+ return null;
1401
+ }
1402
+ }
1403
+ function saveSession(session) {
1404
+ const path = getSessionPath(session.id);
1405
+ if (session.messages.length > 0 && session.title === "New Chat") {
1406
+ session.title = generateTitle(session.messages);
1407
+ }
1408
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1409
+ session.messageCount = session.messages.length;
1410
+ writeFileSync(path, JSON.stringify(session, null, 2));
1411
+ cleanupOldSessions();
1412
+ }
1413
+ function createSession(model, provider) {
1414
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1415
+ return {
1416
+ id: generateId(),
1417
+ title: "New Chat",
1418
+ createdAt: now,
1419
+ updatedAt: now,
1420
+ messageCount: 0,
1421
+ model,
1422
+ provider,
1423
+ messages: []
1424
+ };
1425
+ }
1426
+ function deleteSession(id) {
1427
+ const path = getSessionPath(id);
1428
+ if (!existsSync(path)) return false;
1429
+ try {
1430
+ unlinkSync(path);
1431
+ return true;
1432
+ } catch {
1433
+ return false;
1434
+ }
1435
+ }
1436
+ function cleanupOldSessions() {
1437
+ const sessions = listSessions();
1438
+ if (sessions.length <= MAX_SESSIONS) return;
1439
+ const toDelete = sessions.slice(MAX_SESSIONS);
1440
+ for (const session of toDelete) {
1441
+ deleteSession(session.id);
1442
+ }
1443
+ }
1444
+
1445
+ // src/chat/components/SessionPicker.tsx
1446
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1447
+ function formatDate(isoString) {
1448
+ const date = new Date(isoString);
1449
+ const now = /* @__PURE__ */ new Date();
1450
+ const diffMs = now.getTime() - date.getTime();
1451
+ const diffMins = Math.floor(diffMs / 6e4);
1452
+ const diffHours = Math.floor(diffMs / 36e5);
1453
+ const diffDays = Math.floor(diffMs / 864e5);
1454
+ if (diffMins < 1) return "just now";
1455
+ if (diffMins < 60) return `${diffMins}m ago`;
1456
+ if (diffHours < 24) return `${diffHours}h ago`;
1457
+ if (diffDays < 7) return `${diffDays}d ago`;
1458
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
1459
+ }
1460
+ var SessionRow = memo7(function SessionRow2({
1461
+ isNew,
1462
+ session,
1463
+ isSelected,
1464
+ rowWidth,
1465
+ titleWidth,
1466
+ index,
1467
+ accent,
1468
+ onHover,
1469
+ onClick
1470
+ }) {
1471
+ const rowRef = useRef6(null);
1472
+ useOnMouseMove4(rowRef, () => {
1473
+ onHover(index);
1474
+ });
1475
+ useOnClick5(rowRef, () => {
1476
+ onClick(index);
1477
+ });
1478
+ if (isNew) {
1479
+ return /* @__PURE__ */ jsx7(
1480
+ Box7,
1481
+ {
1482
+ ref: rowRef,
1483
+ backgroundColor: isSelected ? accent : void 0,
1484
+ width: rowWidth,
1485
+ paddingX: 1,
1486
+ children: /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "white" : "green", bold: isSelected, children: [
1487
+ isSelected ? " > " : " ",
1488
+ "+ New Chat"
1489
+ ] })
1490
+ }
1491
+ );
1492
+ }
1493
+ if (!session) return null;
1494
+ const title = session.title.length > titleWidth ? `${session.title.slice(0, titleWidth - 3)}...` : session.title.padEnd(titleWidth);
1495
+ return /* @__PURE__ */ jsxs7(
1496
+ Box7,
1497
+ {
1498
+ ref: rowRef,
1499
+ backgroundColor: isSelected ? accent : void 0,
1500
+ width: rowWidth,
1501
+ paddingX: 1,
1502
+ children: [
1503
+ /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "white" : void 0, bold: isSelected, children: [
1504
+ isSelected ? " > " : " ",
1505
+ title
1506
+ ] }),
1507
+ /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "white" : void 0, dimColor: !isSelected, children: [
1508
+ " ",
1509
+ session.messageCount,
1510
+ " msgs | ",
1511
+ formatDate(session.updatedAt)
1512
+ ] })
1513
+ ]
1514
+ }
1515
+ );
1516
+ });
1517
+ function SessionPicker({ onSelect, onClose }) {
1518
+ const { stdout } = useStdout2();
1519
+ const width = stdout?.columns ?? 80;
1520
+ const height = stdout?.rows ?? 24;
1521
+ const [sessions, setSessions] = useState5(() => listSessions());
1522
+ const [selectedIndex, setSelectedIndex] = useState5(0);
1523
+ const totalItems = sessions.length + 1;
1524
+ const handleSelect = (index) => {
1525
+ if (index === 0) {
1526
+ onSelect(null);
1527
+ } else {
1528
+ const session = sessions[index - 1];
1529
+ if (session) {
1530
+ onSelect(session.id);
1531
+ }
1532
+ }
1533
+ };
1534
+ const handleHover = (index) => {
1535
+ setSelectedIndex(index);
1536
+ };
1537
+ const handleClick = (index) => {
1538
+ handleSelect(index);
1539
+ };
1540
+ useInput5((input, key) => {
1541
+ if (key.escape) {
1542
+ onClose();
1543
+ return;
1544
+ }
1545
+ if (key.return) {
1546
+ handleSelect(selectedIndex);
1547
+ return;
1548
+ }
1549
+ if (key.upArrow || input === "k") {
1550
+ setSelectedIndex((i) => Math.max(0, i - 1));
1551
+ }
1552
+ if (key.downArrow || input === "j") {
1553
+ setSelectedIndex((i) => Math.min(totalItems - 1, i + 1));
1554
+ }
1555
+ if (key.ctrl && input === "d") {
1556
+ if (selectedIndex > 0) {
1557
+ const session = sessions[selectedIndex - 1];
1558
+ if (session) {
1559
+ deleteSession(session.id);
1560
+ setSessions(listSessions());
1561
+ if (selectedIndex >= sessions.length) {
1562
+ setSelectedIndex(Math.max(0, sessions.length - 1));
1563
+ }
1564
+ }
1565
+ }
1566
+ }
1567
+ });
1568
+ const listHeight = height - 8;
1569
+ const visibleCount = Math.max(1, listHeight);
1570
+ let startIndex = 0;
1571
+ if (selectedIndex >= visibleCount) {
1572
+ startIndex = selectedIndex - visibleCount + 1;
1573
+ }
1574
+ const allItems = [
1575
+ { type: "new" },
1576
+ ...sessions.map((session) => ({ type: "session", session }))
1577
+ ];
1578
+ const visibleItems = allItems.slice(startIndex, startIndex + visibleCount);
1579
+ const theme = useTheme();
1580
+ const rowWidth = Math.min(76, width - 6);
1581
+ const titleWidth = Math.min(40, width - 30);
1582
+ return /* @__PURE__ */ jsx7(
1583
+ Box7,
1584
+ {
1585
+ flexDirection: "column",
1586
+ width,
1587
+ height,
1588
+ justifyContent: "center",
1589
+ alignItems: "center",
1590
+ backgroundColor: theme.background,
1591
+ children: /* @__PURE__ */ jsxs7(
1592
+ Box7,
1593
+ {
1594
+ flexDirection: "column",
1595
+ width: Math.min(80, width - 4),
1596
+ borderStyle: "double",
1597
+ borderColor: theme.accent,
1598
+ children: [
1599
+ /* @__PURE__ */ jsxs7(Box7, { paddingX: 2, paddingY: 1, justifyContent: "space-between", children: [
1600
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accent, children: "Chat Sessions" }),
1601
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Esc: close | Enter: select | Ctrl+D: delete" })
1602
+ ] }),
1603
+ /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2500".repeat(Math.min(76, width - 8)) }) }),
1604
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
1605
+ visibleItems.map((item, i) => {
1606
+ const actualIndex = startIndex + i;
1607
+ const isSelected = actualIndex === selectedIndex;
1608
+ if (item.type === "new") {
1609
+ return /* @__PURE__ */ jsx7(
1610
+ SessionRow,
1611
+ {
1612
+ isNew: true,
1613
+ isSelected,
1614
+ rowWidth,
1615
+ titleWidth,
1616
+ index: actualIndex,
1617
+ accent: theme.accent,
1618
+ onHover: handleHover,
1619
+ onClick: handleClick
1620
+ },
1621
+ "new-chat"
1622
+ );
1623
+ }
1624
+ return /* @__PURE__ */ jsx7(
1625
+ SessionRow,
1626
+ {
1627
+ session: item.session,
1628
+ isSelected,
1629
+ rowWidth,
1630
+ titleWidth,
1631
+ index: actualIndex,
1632
+ accent: theme.accent,
1633
+ onHover: handleHover,
1634
+ onClick: handleClick
1635
+ },
1636
+ item.session.id
1637
+ );
1638
+ }),
1639
+ sessions.length === 0 && /* @__PURE__ */ jsx7(Box7, { paddingY: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "No previous sessions" }) })
1640
+ ] }),
1641
+ totalItems > visibleCount && /* @__PURE__ */ jsx7(Box7, { paddingX: 2, paddingBottom: 1, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1642
+ "Showing ",
1643
+ startIndex + 1,
1644
+ "-",
1645
+ Math.min(startIndex + visibleCount, totalItems),
1646
+ " of ",
1647
+ totalItems
1648
+ ] }) })
1649
+ ]
1650
+ }
1651
+ )
1652
+ }
1653
+ );
1654
+ }
1655
+
1656
+ // src/chat/components/ThemePicker.tsx
1657
+ import { useOnClick as useOnClick6, useOnMouseMove as useOnMouseMove5, useOnWheel as useOnWheel2 } from "@ink-tools/ink-mouse";
1658
+ import { Box as Box8, Text as Text8, useInput as useInput6, useStdout as useStdout3 } from "ink";
1659
+ import { memo as memo8, useRef as useRef7, useState as useState6 } from "react";
1660
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1661
+ var MAX_VISIBLE_THEMES = 12;
1662
+ var ThemeRow = memo8(function ThemeRow2({
1663
+ theme,
1664
+ isSelected,
1665
+ isCurrent,
1666
+ rowWidth,
1667
+ selectedTheme,
1668
+ onHover,
1669
+ onClick
1670
+ }) {
1671
+ const rowRef = useRef7(null);
1672
+ useOnMouseMove5(rowRef, onHover);
1673
+ useOnClick6(rowRef, onClick);
1674
+ return /* @__PURE__ */ jsx8(
1675
+ Box8,
1676
+ {
1677
+ ref: rowRef,
1678
+ backgroundColor: isSelected ? selectedTheme.accent : void 0,
1679
+ width: rowWidth,
1680
+ paddingX: 1,
1681
+ children: /* @__PURE__ */ jsxs8(Text8, { color: isSelected ? "white" : void 0, bold: isSelected, children: [
1682
+ isCurrent && !isSelected && /* @__PURE__ */ jsx8(Text8, { color: selectedTheme.accent, children: "\u25CF " }),
1683
+ isCurrent && isSelected && /* @__PURE__ */ jsx8(Text8, { color: "white", children: "\u25CF " }),
1684
+ !isCurrent && " ",
1685
+ theme.displayName
1686
+ ] })
1687
+ }
1688
+ );
1689
+ });
1690
+ function ThemePicker({
1691
+ currentTheme,
1692
+ onSelect,
1693
+ onClose
1694
+ }) {
1695
+ const { stdout } = useStdout3();
1696
+ const width = stdout?.columns ?? 80;
1697
+ const height = stdout?.rows ?? 24;
1698
+ const listRef = useRef7(null);
1699
+ const currentIndex = THEMES.findIndex((t) => t.name === currentTheme);
1700
+ const [selectedIndex, setSelectedIndex] = useState6(
1701
+ currentIndex >= 0 ? currentIndex : 0
1702
+ );
1703
+ const visibleCount = Math.min(MAX_VISIBLE_THEMES, height - 10);
1704
+ const handleHover = (actualIndex) => {
1705
+ setSelectedIndex(actualIndex);
1706
+ };
1707
+ const handleClick = (actualIndex) => {
1708
+ const selected = THEMES[actualIndex];
1709
+ if (selected) {
1710
+ onSelect(selected.name);
1711
+ }
1712
+ };
1713
+ useInput6((input, key) => {
1714
+ if (key.escape) {
1715
+ onClose();
1716
+ return;
1717
+ }
1718
+ if (key.return) {
1719
+ const selected = THEMES[selectedIndex];
1720
+ if (selected) {
1721
+ onSelect(selected.name);
1722
+ }
1723
+ return;
1724
+ }
1725
+ if (key.upArrow || input === "k") {
1726
+ setSelectedIndex((i) => Math.max(0, i - 1));
1727
+ }
1728
+ if (key.downArrow || input === "j") {
1729
+ setSelectedIndex((i) => Math.min(THEMES.length - 1, i + 1));
1730
+ }
1731
+ if (key.pageUp) {
1732
+ setSelectedIndex((i) => Math.max(0, i - visibleCount));
1733
+ }
1734
+ if (key.pageDown) {
1735
+ setSelectedIndex((i) => Math.min(THEMES.length - 1, i + visibleCount));
1736
+ }
1737
+ });
1738
+ useOnWheel2(listRef, (event) => {
1739
+ if (event.button === "wheel-up") {
1740
+ setSelectedIndex((i) => Math.max(0, i - 1));
1741
+ } else if (event.button === "wheel-down") {
1742
+ setSelectedIndex((i) => Math.min(THEMES.length - 1, i + 1));
1743
+ }
1744
+ });
1745
+ let startIndex = 0;
1746
+ if (selectedIndex >= visibleCount) {
1747
+ startIndex = Math.min(
1748
+ selectedIndex - Math.floor(visibleCount / 2),
1749
+ THEMES.length - visibleCount
1750
+ );
1751
+ }
1752
+ startIndex = Math.max(0, startIndex);
1753
+ const visibleThemes = THEMES.slice(startIndex, startIndex + visibleCount);
1754
+ const selectedTheme = THEMES[selectedIndex] ?? THEMES[0];
1755
+ const rowWidth = Math.min(60, width - 8);
1756
+ return /* @__PURE__ */ jsx8(
1757
+ Box8,
1758
+ {
1759
+ flexDirection: "column",
1760
+ width,
1761
+ height,
1762
+ justifyContent: "center",
1763
+ alignItems: "center",
1764
+ backgroundColor: selectedTheme.background,
1765
+ children: /* @__PURE__ */ jsxs8(
1766
+ Box8,
1767
+ {
1768
+ flexDirection: "column",
1769
+ width: Math.min(64, width - 4),
1770
+ borderStyle: "round",
1771
+ borderColor: selectedTheme.accent,
1772
+ children: [
1773
+ /* @__PURE__ */ jsxs8(Box8, { paddingX: 2, paddingTop: 1, justifyContent: "space-between", children: [
1774
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: selectedTheme.textPrimary, children: "Themes" }),
1775
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "esc" })
1776
+ ] }),
1777
+ /* @__PURE__ */ jsx8(Box8, { paddingX: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u2500".repeat(Math.min(60, width - 8)) }) }),
1778
+ /* @__PURE__ */ jsx8(Box8, { ref: listRef, flexDirection: "column", paddingY: 1, children: visibleThemes.map((theme, i) => {
1779
+ const actualIndex = startIndex + i;
1780
+ return /* @__PURE__ */ jsx8(
1781
+ ThemeRow,
1782
+ {
1783
+ theme,
1784
+ isSelected: actualIndex === selectedIndex,
1785
+ isCurrent: theme.name === currentTheme,
1786
+ rowWidth,
1787
+ selectedTheme,
1788
+ onHover: () => handleHover(actualIndex),
1789
+ onClick: () => handleClick(actualIndex)
1790
+ },
1791
+ theme.name
1792
+ );
1793
+ }) }),
1794
+ /* @__PURE__ */ jsxs8(Box8, { paddingX: 2, paddingBottom: 1, justifyContent: "space-between", children: [
1795
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "j/k or arrows to navigate" }),
1796
+ THEMES.length > visibleCount && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
1797
+ startIndex + 1,
1798
+ "-",
1799
+ Math.min(startIndex + visibleCount, THEMES.length),
1800
+ "/",
1801
+ THEMES.length
1802
+ ] })
1803
+ ] })
1804
+ ]
1805
+ }
1806
+ )
1807
+ }
1808
+ );
1809
+ }
1810
+
1811
+ // src/chat/components/WelcomeScreen.tsx
1812
+ import { Box as Box10, Text as Text10 } from "ink";
1813
+ import { memo as memo10 } from "react";
1814
+
1815
+ // src/chat/components/CatIcon.tsx
1816
+ import { Box as Box9, Text as Text9 } from "ink";
1817
+ import { memo as memo9, useMemo as useMemo4 } from "react";
1818
+ import { jsx as jsx9 } from "react/jsx-runtime";
1819
+ var CAT_LINES = [
1820
+ " \u2588\u2588 \u2588\u2588 ",
1821
+ " \u2588\u2588\u2591\u2591\u2588\u2588\u2588\u2588\u2591\u2591\u2588\u2588 ",
1822
+ "\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2588\u2588",
1823
+ "\u2588\u2588\u2591\u2593\u2591\u2591\u2591\u2591\u2591\u2591\u2593\u2591\u2588\u2588",
1824
+ "\u2588\u2588\u2591\u2591\u2591\u2592\u2592\u2592\u2592\u2591\u2591\u2591\u2588\u2588",
1825
+ "\u2588\u2588\u2591\u2591\u2592\u2592\u2593\u2593\u2592\u2592\u2591\u2591\u2588\u2588",
1826
+ " \u2588\u2588\u2591\u2592\u2592\u2592\u2592\u2592\u2592\u2591\u2588\u2588 ",
1827
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 "
1828
+ ];
1829
+ var CatIcon = memo9(function CatIcon2() {
1830
+ const theme = useTheme();
1831
+ const colors = useMemo4(
1832
+ () => ({
1833
+ "\u2588": "black",
1834
+ "\u2591": theme.accent,
1835
+ "\u2593": "#33221fff",
1836
+ "\u2592": "#fbfaf7ff"
1837
+ }),
1838
+ [theme.accent]
1839
+ );
1840
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", alignItems: "center", children: CAT_LINES.map((line, i) => (
1841
+ // biome-ignore lint/suspicious/noArrayIndexKey: Static ASCII art
1842
+ /* @__PURE__ */ jsx9(Text9, { children: line.split("").map((char, j) => {
1843
+ const color = colors[char];
1844
+ if (color) {
1845
+ return (
1846
+ // biome-ignore lint/suspicious/noArrayIndexKey: Static char
1847
+ /* @__PURE__ */ jsx9(Text9, { color, children: "\u2588" }, `char-${i}-${j}`)
1848
+ );
1849
+ }
1850
+ return /* @__PURE__ */ jsx9(Text9, { children: " " }, `char-${i}-${j}`);
1851
+ }) }, `line-${i}`)
1852
+ )) });
1853
+ });
1854
+
1855
+ // src/chat/components/WelcomeScreen.tsx
1856
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1857
+ var BASHIO_ASCII = `
1858
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557
1859
+ \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557
1860
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
1861
+ \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
1862
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
1863
+ \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
1864
+ `.trim();
1865
+ var WelcomeScreen = memo10(function WelcomeScreen2({
1866
+ width,
1867
+ height,
1868
+ children
1869
+ }) {
1870
+ const lines = BASHIO_ASCII.split("\n");
1871
+ const theme = useTheme();
1872
+ return /* @__PURE__ */ jsx10(
1873
+ Box10,
1874
+ {
1875
+ flexDirection: "column",
1876
+ width,
1877
+ height,
1878
+ justifyContent: "center",
1879
+ alignItems: "center",
1880
+ backgroundColor: theme.background,
1881
+ children: /* @__PURE__ */ jsxs9(
1882
+ Box10,
1883
+ {
1884
+ flexDirection: "column",
1885
+ alignItems: "center",
1886
+ backgroundColor: theme.background,
1887
+ children: [
1888
+ /* @__PURE__ */ jsx10(CatIcon, {}),
1889
+ /* @__PURE__ */ jsx10(Box10, { paddingTop: 1, backgroundColor: theme.background }),
1890
+ lines.map((line, i) => (
1891
+ // biome-ignore lint/suspicious/noArrayIndexKey: Static ASCII art lines
1892
+ /* @__PURE__ */ jsx10(Text10, { color: theme.textPrimary, bold: true, children: line }, `ascii-${i}`)
1893
+ )),
1894
+ children && /* @__PURE__ */ jsx10(Box10, { paddingTop: 2, backgroundColor: theme.background, children })
1895
+ ]
1896
+ }
1897
+ )
1898
+ }
1899
+ );
1900
+ });
1901
+
1902
+ // src/chat/utils/syncOutput.ts
1903
+ import { Writable } from "stream";
1904
+ var SYNC_START = "\x1B[?2026h";
1905
+ var SYNC_END = "\x1B[?2026l";
1906
+ function createSyncOutputStream(stdout) {
1907
+ let buffer = "";
1908
+ let flushTimeout = null;
1909
+ const FLUSH_DELAY = 4;
1910
+ const flush = () => {
1911
+ if (buffer.length > 0) {
1912
+ stdout.write(SYNC_START + buffer + SYNC_END);
1913
+ buffer = "";
1914
+ }
1915
+ flushTimeout = null;
1916
+ };
1917
+ const stream = new Writable({
1918
+ write(chunk, _encoding, callback) {
1919
+ const str = typeof chunk === "string" ? chunk : chunk.toString("utf8");
1920
+ buffer += str;
1921
+ if (!flushTimeout) {
1922
+ flushTimeout = setTimeout(flush, FLUSH_DELAY);
1923
+ }
1924
+ callback();
1925
+ },
1926
+ final(callback) {
1927
+ if (flushTimeout) {
1928
+ clearTimeout(flushTimeout);
1929
+ flushTimeout = null;
1930
+ }
1931
+ flush();
1932
+ callback();
1933
+ }
1934
+ });
1935
+ Object.defineProperty(stream, "columns", {
1936
+ get: () => stdout.columns
1937
+ });
1938
+ Object.defineProperty(stream, "rows", {
1939
+ get: () => stdout.rows
1940
+ });
1941
+ Object.defineProperty(stream, "isTTY", {
1942
+ get: () => stdout.isTTY
1943
+ });
1944
+ stdout.on("resize", () => {
1945
+ stream.emit("resize");
1946
+ });
1947
+ const proxy = Object.create(
1948
+ stdout,
1949
+ Object.getOwnPropertyDescriptors(stream)
1950
+ );
1951
+ proxy._flushSyncOutput = flush;
1952
+ return proxy;
1953
+ }
1954
+
1955
+ // src/chat/App.tsx
1956
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1957
+ function ChatApp() {
1958
+ const { exit } = useApp();
1959
+ const { stdout } = useStdout4();
1960
+ const [height, setHeight] = useState7(stdout?.rows ?? 24);
1961
+ const [width, setWidth] = useState7(stdout?.columns ?? 80);
1962
+ const messageListRef = useRef8(null);
1963
+ const slashModeRef = useRef8(false);
1964
+ const [config, setConfig] = useState7(null);
1965
+ const [provider, setProvider] = useState7(null);
1966
+ const [currentSession, setCurrentSession] = useState7(null);
1967
+ const [state, setState] = useState7({
1968
+ messages: [],
1969
+ isLoading: false,
1970
+ currentResponse: "",
1971
+ showModelSwitcher: false,
1972
+ showSessionPicker: false,
1973
+ showThemePicker: false,
1974
+ error: null
1975
+ });
1976
+ useEffect2(() => {
1977
+ const handleResize = () => {
1978
+ if (stdout) {
1979
+ setHeight(stdout.rows);
1980
+ setWidth(stdout.columns);
1981
+ }
1982
+ };
1983
+ stdout?.on("resize", handleResize);
1984
+ return () => {
1985
+ stdout?.off("resize", handleResize);
1986
+ };
1987
+ }, [stdout]);
1988
+ useEffect2(() => {
1989
+ const loadedConfig = loadConfig();
1990
+ if (loadedConfig) {
1991
+ setConfig(loadedConfig);
1992
+ setProvider(createProvider(loadedConfig));
1993
+ const activeProvider2 = loadedConfig.activeProvider;
1994
+ const model = loadedConfig.providers[activeProvider2]?.model ?? "unknown";
1995
+ const session = createSession(model, activeProvider2);
1996
+ setCurrentSession(session);
1997
+ } else {
1998
+ exit();
1999
+ }
2000
+ }, [exit]);
2001
+ const messagesLength = state.messages.length;
2002
+ const isLoading = state.isLoading;
2003
+ useEffect2(() => {
2004
+ if (currentSession && messagesLength > 0 && !isLoading) {
2005
+ const updatedSession = {
2006
+ ...currentSession,
2007
+ messages: state.messages,
2008
+ messageCount: messagesLength
2009
+ };
2010
+ saveSession(updatedSession);
2011
+ setCurrentSession(updatedSession);
2012
+ }
2013
+ }, [messagesLength, isLoading]);
2014
+ const noPickerOpen = !state.showModelSwitcher && !state.showSessionPicker && !state.showThemePicker;
2015
+ const handleGlobalInput = useCallback2(
2016
+ (input, key) => {
2017
+ if (key.ctrl && input === "c") {
2018
+ exit();
2019
+ }
2020
+ if (!slashModeRef.current && noPickerOpen) {
2021
+ if (key.ctrl && input === "m" || key.ctrl && input === "p") {
2022
+ setState((s) => ({ ...s, showModelSwitcher: true }));
2023
+ }
2024
+ if (key.ctrl && input === "o") {
2025
+ setState((s) => ({ ...s, showSessionPicker: true }));
2026
+ }
2027
+ if (key.ctrl && input === "t") {
2028
+ setState((s) => ({ ...s, showThemePicker: true }));
2029
+ }
2030
+ }
2031
+ if (key.escape) {
2032
+ if (state.showModelSwitcher) {
2033
+ setState((s) => ({ ...s, showModelSwitcher: false }));
2034
+ }
2035
+ if (state.showSessionPicker) {
2036
+ setState((s) => ({ ...s, showSessionPicker: false }));
2037
+ }
2038
+ if (state.showThemePicker) {
2039
+ setState((s) => ({ ...s, showThemePicker: false }));
2040
+ }
2041
+ }
2042
+ },
2043
+ [
2044
+ exit,
2045
+ noPickerOpen,
2046
+ state.showModelSwitcher,
2047
+ state.showSessionPicker,
2048
+ state.showThemePicker
2049
+ ]
2050
+ );
2051
+ useInput7(handleGlobalInput);
2052
+ const handleSubmit = useCallback2(
2053
+ async (message) => {
2054
+ if (!provider || !message.trim()) return;
2055
+ const userMessage = {
2056
+ role: "user",
2057
+ content: message.trim()
2058
+ };
2059
+ let messagesWithUser = [];
2060
+ setState((s) => {
2061
+ if (s.isLoading) return s;
2062
+ messagesWithUser = [...s.messages, userMessage];
2063
+ return {
2064
+ ...s,
2065
+ messages: messagesWithUser,
2066
+ isLoading: true,
2067
+ currentResponse: "",
2068
+ error: null
2069
+ };
2070
+ });
2071
+ if (messagesWithUser.length === 0) return;
2072
+ try {
2073
+ if (provider.streamChat) {
2074
+ await provider.streamChat(messagesWithUser, (chunk) => {
2075
+ setState((s) => ({
2076
+ ...s,
2077
+ currentResponse: s.currentResponse + chunk
2078
+ }));
2079
+ });
2080
+ setState((s) => ({
2081
+ ...s,
2082
+ messages: [
2083
+ ...s.messages,
2084
+ { role: "assistant", content: s.currentResponse }
2085
+ ],
2086
+ isLoading: false,
2087
+ currentResponse: ""
2088
+ }));
2089
+ } else {
2090
+ setState((s) => ({
2091
+ ...s,
2092
+ messages: [
2093
+ ...s.messages,
2094
+ {
2095
+ role: "assistant",
2096
+ content: "Streaming not supported for this provider."
2097
+ }
2098
+ ],
2099
+ isLoading: false
2100
+ }));
2101
+ }
2102
+ } catch (err) {
2103
+ setState((s) => ({
2104
+ ...s,
2105
+ isLoading: false,
2106
+ error: err instanceof Error ? err.message : "Unknown error"
2107
+ }));
2108
+ }
2109
+ },
2110
+ [provider]
2111
+ );
2112
+ const handleModelSelect = useCallback2(
2113
+ (providerName, model) => {
2114
+ if (!config) return;
2115
+ const newConfig = { ...config, activeProvider: providerName };
2116
+ const providerSettings = newConfig.providers[providerName];
2117
+ if (providerSettings) {
2118
+ providerSettings.model = model;
2119
+ }
2120
+ setConfig(newConfig);
2121
+ setProvider(createProvider(newConfig));
2122
+ setState((s) => ({ ...s, showModelSwitcher: false }));
2123
+ if (currentSession) {
2124
+ setCurrentSession({
2125
+ ...currentSession,
2126
+ model,
2127
+ provider: providerName
2128
+ });
2129
+ }
2130
+ },
2131
+ [config, currentSession]
2132
+ );
2133
+ const handleModelSwitcherClose = useCallback2(() => {
2134
+ setState((s) => ({ ...s, showModelSwitcher: false }));
2135
+ }, []);
2136
+ const handleSessionSelect = useCallback2(
2137
+ (sessionId) => {
2138
+ if (!config) return;
2139
+ if (sessionId === null) {
2140
+ const activeProvider2 = config.activeProvider;
2141
+ const model = config.providers[activeProvider2]?.model ?? "unknown";
2142
+ const session = createSession(model, activeProvider2);
2143
+ setCurrentSession(session);
2144
+ setState((s) => ({
2145
+ ...s,
2146
+ messages: [],
2147
+ showSessionPicker: false,
2148
+ error: null
2149
+ }));
2150
+ } else {
2151
+ const session = loadSession(sessionId);
2152
+ if (session) {
2153
+ setCurrentSession(session);
2154
+ setState((s) => ({
2155
+ ...s,
2156
+ messages: session.messages,
2157
+ showSessionPicker: false,
2158
+ error: null
2159
+ }));
2160
+ }
2161
+ }
2162
+ },
2163
+ [config]
2164
+ );
2165
+ const handleSessionPickerClose = useCallback2(() => {
2166
+ setState((s) => ({ ...s, showSessionPicker: false }));
2167
+ }, []);
2168
+ const handleThemeSelect = useCallback2(
2169
+ (themeName) => {
2170
+ if (!config) return;
2171
+ const cs = config.settings;
2172
+ const newConfig = {
2173
+ ...config,
2174
+ settings: {
2175
+ confirmBeforeExecute: cs?.confirmBeforeExecute ?? true,
2176
+ historyEnabled: cs?.historyEnabled ?? true,
2177
+ historyRetentionDays: cs?.historyRetentionDays ?? 30,
2178
+ historyMaxEntries: cs?.historyMaxEntries ?? 2e3,
2179
+ autoConfirmShortcuts: cs?.autoConfirmShortcuts ?? false,
2180
+ theme: themeName
2181
+ }
2182
+ };
2183
+ setConfig(newConfig);
2184
+ saveConfig(newConfig);
2185
+ setState((s) => ({ ...s, showThemePicker: false }));
2186
+ },
2187
+ [config]
2188
+ );
2189
+ const handleThemePickerClose = useCallback2(() => {
2190
+ setState((s) => ({ ...s, showThemePicker: false }));
2191
+ }, []);
2192
+ const handleSlashCommand = useCallback2(
2193
+ (action) => {
2194
+ switch (action) {
2195
+ case "openModelSwitcher":
2196
+ setState((s) => ({ ...s, showModelSwitcher: true }));
2197
+ break;
2198
+ case "openSessionPicker":
2199
+ setState((s) => ({ ...s, showSessionPicker: true }));
2200
+ break;
2201
+ case "openThemePicker":
2202
+ setState((s) => ({ ...s, showThemePicker: true }));
2203
+ break;
2204
+ case "newSession":
2205
+ handleSessionSelect(null);
2206
+ break;
2207
+ case "clearChat":
2208
+ setState((s) => ({ ...s, messages: [], error: null }));
2209
+ if (currentSession) {
2210
+ const clearedSession = {
2211
+ ...currentSession,
2212
+ messages: [],
2213
+ messageCount: 0
2214
+ };
2215
+ saveSession(clearedSession);
2216
+ setCurrentSession(clearedSession);
2217
+ }
2218
+ break;
2219
+ case "exitChat":
2220
+ exit();
2221
+ break;
2222
+ }
2223
+ },
2224
+ [handleSessionSelect, currentSession, exit]
2225
+ );
2226
+ const currentThemeName = config?.settings?.theme ?? "bashio";
2227
+ const theme = useMemo5(
2228
+ () => getThemeByName(currentThemeName),
2229
+ [currentThemeName]
2230
+ );
2231
+ if (!config || !provider) {
2232
+ return /* @__PURE__ */ jsx11(
2233
+ Box11,
2234
+ {
2235
+ width,
2236
+ height,
2237
+ justifyContent: "center",
2238
+ alignItems: "center",
2239
+ children: /* @__PURE__ */ jsx11(Text11, { children: "Loading..." })
2240
+ }
2241
+ );
2242
+ }
2243
+ if (state.showThemePicker) {
2244
+ return /* @__PURE__ */ jsx11(
2245
+ ThemePicker,
2246
+ {
2247
+ currentTheme: currentThemeName,
2248
+ onSelect: handleThemeSelect,
2249
+ onClose: handleThemePickerClose
2250
+ }
2251
+ );
2252
+ }
2253
+ if (state.showSessionPicker) {
2254
+ return /* @__PURE__ */ jsx11(ThemeProvider, { value: theme, children: /* @__PURE__ */ jsx11(
2255
+ SessionPicker,
2256
+ {
2257
+ onSelect: handleSessionSelect,
2258
+ onClose: handleSessionPickerClose
2259
+ }
2260
+ ) });
2261
+ }
2262
+ if (state.showModelSwitcher) {
2263
+ return /* @__PURE__ */ jsx11(ThemeProvider, { value: theme, children: /* @__PURE__ */ jsx11(
2264
+ ModelSwitcher,
2265
+ {
2266
+ config,
2267
+ onSelect: handleModelSelect,
2268
+ onClose: handleModelSwitcherClose
2269
+ }
2270
+ ) });
2271
+ }
2272
+ const activeProvider = config.activeProvider;
2273
+ const currentModel = config.providers[activeProvider]?.model ?? "unknown";
2274
+ const isEmptyChat = state.messages.length === 0 && !state.currentResponse;
2275
+ if (isEmptyChat) {
2276
+ return /* @__PURE__ */ jsx11(ThemeProvider, { value: theme, children: /* @__PURE__ */ jsx11(
2277
+ Box11,
2278
+ {
2279
+ flexDirection: "column",
2280
+ width,
2281
+ height,
2282
+ backgroundColor: theme.background,
2283
+ children: /* @__PURE__ */ jsx11(WelcomeScreen, { width, height, children: /* @__PURE__ */ jsx11(
2284
+ InputBox,
2285
+ {
2286
+ onSubmit: handleSubmit,
2287
+ onSlashCommand: handleSlashCommand,
2288
+ slashModeRef,
2289
+ modelName: currentModel,
2290
+ disabled: state.isLoading,
2291
+ width: Math.min(width - 4, 80),
2292
+ placeholder: "Ask anything..."
2293
+ }
2294
+ ) })
2295
+ }
2296
+ ) });
2297
+ }
2298
+ const inputBoxHeight = 6;
2299
+ const sessionHeaderHeight = 4;
2300
+ const messageListHeight = Math.max(
2301
+ 5,
2302
+ height - inputBoxHeight - sessionHeaderHeight - 2
2303
+ );
2304
+ return /* @__PURE__ */ jsx11(ThemeProvider, { value: theme, children: /* @__PURE__ */ jsxs10(
2305
+ Box11,
2306
+ {
2307
+ flexDirection: "column",
2308
+ width,
2309
+ height,
2310
+ backgroundColor: theme.background,
2311
+ children: [
2312
+ /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx11(
2313
+ SessionHeader,
2314
+ {
2315
+ sessionTitle: currentSession?.title ?? "New Chat",
2316
+ width: width - 2
2317
+ }
2318
+ ) }),
2319
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", height: messageListHeight, children: /* @__PURE__ */ jsx11(
2320
+ MessageList,
2321
+ {
2322
+ ref: messageListRef,
2323
+ messages: state.messages,
2324
+ currentResponse: state.currentResponse,
2325
+ isLoading: state.isLoading,
2326
+ height: messageListHeight,
2327
+ width: width - 2,
2328
+ slashModeRef
2329
+ }
2330
+ ) }),
2331
+ state.error && /* @__PURE__ */ jsx11(Box11, { paddingX: 1, children: /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
2332
+ "Error: ",
2333
+ state.error
2334
+ ] }) }),
2335
+ /* @__PURE__ */ jsx11(Box11, { paddingX: 1, children: /* @__PURE__ */ jsx11(
2336
+ InputBox,
2337
+ {
2338
+ onSubmit: handleSubmit,
2339
+ onSlashCommand: handleSlashCommand,
2340
+ slashModeRef,
2341
+ modelName: currentModel,
2342
+ disabled: state.isLoading,
2343
+ width: width - 2
2344
+ }
2345
+ ) })
2346
+ ]
2347
+ }
2348
+ ) });
2349
+ }
2350
+ async function runChat() {
2351
+ if (!configExists()) {
2352
+ const success = await runAuthSetup();
2353
+ if (!success) {
2354
+ return 1;
2355
+ }
2356
+ console.log();
2357
+ }
2358
+ const syncOutput = createSyncOutputStream(process.stdout);
2359
+ process.stdout.write("\x1B[?1049h");
2360
+ process.stdout.write("\x1B[?25l");
2361
+ process.stdout.write("\x1B[2J\x1B[H");
2362
+ const instance = render(
2363
+ /* @__PURE__ */ jsx11(MouseProvider, { children: /* @__PURE__ */ jsx11(ChatApp, {}) }),
2364
+ {
2365
+ exitOnCtrlC: false,
2366
+ stdout: syncOutput
2367
+ }
2368
+ );
2369
+ await instance.waitUntilExit();
2370
+ syncOutput._flushSyncOutput?.();
2371
+ process.stdout.write("\x1B[?25h");
2372
+ process.stdout.write("\x1B[?1049l");
2373
+ return 0;
2374
+ }
2375
+ export {
2376
+ runChat
2377
+ };
2378
+ //# sourceMappingURL=App-SRRDMIMW.js.map