code-ollama 0.17.0 → 0.18.1
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/dist/assets/{tui-CCHcdC7V.js → tui-GfzUJAWj.js} +831 -141
- package/dist/cli.js +49 -11
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as SYSTEM, B as REJECT, C as resetSystemMessage, D as LIST$1, E as WARNING, F as AUTO, H as LIST, I as LABEL, L as PLAN, M as PLAN_GENERATION_INSTRUCTION, N as BACK, O as getTheme, P as CATALOG, R as SAFE, S as saveConfig, T as HEADER_PREFIX, V as VERSION, _ as deleteModel, a as color, b as streamChat, c as createSession, d as listSessions, f as loadSession, g as setClearHandler, h as reset, i as WRITE_TOOLS, j as USER, k as ASSISTANT, l as deleteSession, m as clear, n as READ_TOOLS, o as write, p as updateSessionModel, r as TOOLS, s as appendMessage, t as executeTool, u as deleteSessionIfEmpty, v as listModels, w as withSystemMessage, x as loadConfig, y as pullModel, z as APPROVE } from "../cli.js";
|
|
2
2
|
import { readdirSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, relative } from "node:path";
|
|
@@ -6,7 +6,7 @@ import { exec } from "node:child_process";
|
|
|
6
6
|
import { Box, Static, Text, render, useApp, useInput, useStdout } from "ink";
|
|
7
7
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
import { Select, Spinner } from "@inkjs/ui";
|
|
9
|
+
import { ProgressBar, Select, Spinner } from "@inkjs/ui";
|
|
10
10
|
import { Marked } from "marked";
|
|
11
11
|
import { markedTerminal } from "marked-terminal";
|
|
12
12
|
//#region src/components/CodeBlock/CodeBlock.tsx
|
|
@@ -630,9 +630,13 @@ function ToolApproval({ toolCall, onDecision, theme = getTheme() }) {
|
|
|
630
630
|
onChange: handleChange,
|
|
631
631
|
onCancel: handleEscape,
|
|
632
632
|
children: [
|
|
633
|
-
/* @__PURE__ */
|
|
633
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
634
634
|
color: theme.colors.warning,
|
|
635
|
-
children:
|
|
635
|
+
children: [
|
|
636
|
+
"Tool requires approval ",
|
|
637
|
+
WARNING,
|
|
638
|
+
" "
|
|
639
|
+
]
|
|
636
640
|
}),
|
|
637
641
|
/* @__PURE__ */ jsxs(Box, {
|
|
638
642
|
flexDirection: "column",
|
|
@@ -817,8 +821,57 @@ function CommandMenu({ input, onSubmit }) {
|
|
|
817
821
|
});
|
|
818
822
|
}
|
|
819
823
|
//#endregion
|
|
824
|
+
//#region src/components/Suggestions.tsx
|
|
825
|
+
var DEFAULT_MAX_VISIBLE_OPTIONS = 5;
|
|
826
|
+
function Suggestions({ options, isDisabled = false, maxVisibleOptions = DEFAULT_MAX_VISIBLE_OPTIONS, resetKey, onHighlight, onSelect }) {
|
|
827
|
+
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
828
|
+
useEffect(() => {
|
|
829
|
+
setFocusedIndex(0);
|
|
830
|
+
}, [resetKey]);
|
|
831
|
+
useEffect(() => {
|
|
832
|
+
if (!options.length) {
|
|
833
|
+
setFocusedIndex(0);
|
|
834
|
+
onHighlight?.(null);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
setFocusedIndex((currentIndex) => Math.min(currentIndex, options.length - 1));
|
|
838
|
+
}, [onHighlight, options]);
|
|
839
|
+
useEffect(() => {
|
|
840
|
+
onHighlight?.(options[focusedIndex] ?? null);
|
|
841
|
+
}, [
|
|
842
|
+
focusedIndex,
|
|
843
|
+
onHighlight,
|
|
844
|
+
options
|
|
845
|
+
]);
|
|
846
|
+
useInput((input, key) => {
|
|
847
|
+
if (isDisabled || !options.length) return;
|
|
848
|
+
if (key.downArrow || input === "\x1B[B") {
|
|
849
|
+
setFocusedIndex((currentIndex) => Math.min(currentIndex + 1, options.length - 1));
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (key.upArrow || input === "\x1B[A") {
|
|
853
|
+
setFocusedIndex((currentIndex) => Math.max(currentIndex - 1, 0));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (key.tab || key.return || input === " " || input === "\r") onSelect(options[focusedIndex]);
|
|
857
|
+
});
|
|
858
|
+
if (!options.length) return null;
|
|
859
|
+
const visibleStart = Math.min(Math.max(0, focusedIndex - maxVisibleOptions + 1), Math.max(0, options.length - maxVisibleOptions));
|
|
860
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
861
|
+
flexDirection: "column",
|
|
862
|
+
children: options.slice(visibleStart, visibleStart + maxVisibleOptions).map((option, index) => {
|
|
863
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
864
|
+
marginLeft: 2,
|
|
865
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
866
|
+
color: visibleStart + index === focusedIndex ? "cyan" : void 0,
|
|
867
|
+
children: option.label
|
|
868
|
+
})
|
|
869
|
+
}, option.label);
|
|
870
|
+
})
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
//#endregion
|
|
820
874
|
//#region src/components/Chat/FileSuggestions.tsx
|
|
821
|
-
var MAX_VISIBLE_OPTIONS = 5;
|
|
822
875
|
var MENTION_PATTERN = /(^|.)@(\S+)/;
|
|
823
876
|
var RIPGREP_MAX_BUFFER = 10 * 1024 * 1024;
|
|
824
877
|
function normalizePath(filePath) {
|
|
@@ -897,7 +950,6 @@ async function listProjectFiles(rootDir) {
|
|
|
897
950
|
}
|
|
898
951
|
function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
|
|
899
952
|
const [filePaths, setFilePaths] = useState([]);
|
|
900
|
-
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
901
953
|
useEffect(() => {
|
|
902
954
|
async function loadProjectFiles() {
|
|
903
955
|
setFilePaths(await listProjectFiles(process.cwd()));
|
|
@@ -910,55 +962,29 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
|
|
|
910
962
|
const normalizedQuery = mentionMatch.query.toLowerCase();
|
|
911
963
|
return filePaths.filter((filePath) => filePath.toLowerCase().includes(normalizedQuery));
|
|
912
964
|
}, [filePaths, mentionMatch]);
|
|
913
|
-
useEffect(() => {
|
|
914
|
-
setFocusedIndex(0);
|
|
915
|
-
}, [input]);
|
|
916
|
-
useEffect(() => {
|
|
917
|
-
if (!options.length) {
|
|
918
|
-
setFocusedIndex(0);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
setFocusedIndex((currentIndex) => Math.min(currentIndex, options.length - 1));
|
|
922
|
-
}, [options]);
|
|
923
965
|
useEffect(() => {
|
|
924
966
|
if (!onChange) return;
|
|
925
|
-
if (!mentionMatch || !options.length)
|
|
926
|
-
onChange(null);
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
onChange(buildNextInput(input, options[focusedIndex]).value);
|
|
967
|
+
if (!mentionMatch || !options.length) onChange(null);
|
|
930
968
|
}, [
|
|
931
|
-
focusedIndex,
|
|
932
|
-
input,
|
|
933
969
|
mentionMatch,
|
|
934
970
|
onChange,
|
|
935
971
|
options
|
|
936
972
|
]);
|
|
937
|
-
useInput((_, key) => {
|
|
938
|
-
if (isDisabled || !options.length) return;
|
|
939
|
-
if (key.downArrow) {
|
|
940
|
-
setFocusedIndex((currentIndex) => Math.min(currentIndex + 1, options.length - 1));
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
if (key.upArrow) {
|
|
944
|
-
setFocusedIndex((currentIndex) => Math.max(currentIndex - 1, 0));
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
if (key.tab || key.return) onSelect(buildNextInput(input, options[focusedIndex]));
|
|
948
|
-
});
|
|
949
973
|
if (!mentionMatch || !options.length) return null;
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
974
|
+
return /* @__PURE__ */ jsx(Suggestions, {
|
|
975
|
+
isDisabled,
|
|
976
|
+
options: options.map((option) => ({
|
|
977
|
+
label: option,
|
|
978
|
+
value: option
|
|
979
|
+
})),
|
|
980
|
+
resetKey: input,
|
|
981
|
+
onHighlight: (option) => {
|
|
982
|
+
// v8 ignore next
|
|
983
|
+
onChange?.(option ? buildNextInput(input, option.value).value : null);
|
|
984
|
+
},
|
|
985
|
+
onSelect: (option) => {
|
|
986
|
+
onSelect(buildNextInput(input, option.value));
|
|
987
|
+
}
|
|
962
988
|
});
|
|
963
989
|
}
|
|
964
990
|
//#endregion
|
|
@@ -1119,10 +1145,10 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
|
|
|
1119
1145
|
var ACTION_NOT_PERFORMED = "The requested action was NOT performed";
|
|
1120
1146
|
var PLAN_CHECKLIST_REMINDER = "Then display the execution plan as an unchecked Markdown checklist only";
|
|
1121
1147
|
var PLAN_EXECUTION_REMINDER = `Do not claim success and do not call ${Array.from(WRITE_TOOLS).join(", ")} until the user approves execution`;
|
|
1122
|
-
var
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
return
|
|
1148
|
+
var InterruptReason = /* @__PURE__ */ function(InterruptReason) {
|
|
1149
|
+
InterruptReason["Interrupted"] = "interrupted";
|
|
1150
|
+
InterruptReason["Rejected"] = "rejected";
|
|
1151
|
+
return InterruptReason;
|
|
1126
1152
|
}({});
|
|
1127
1153
|
//#endregion
|
|
1128
1154
|
//#region src/components/Chat/plan.ts
|
|
@@ -1190,13 +1216,15 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1190
1216
|
abortControllerRef.current = null;
|
|
1191
1217
|
setIsLoading(false);
|
|
1192
1218
|
setStreamingMessage(null);
|
|
1193
|
-
setInterruptReason(
|
|
1219
|
+
setInterruptReason(InterruptReason.Interrupted);
|
|
1194
1220
|
setMessages((prev) => [...prev, {
|
|
1195
1221
|
role: USER,
|
|
1196
1222
|
content: TURN_ABORTED_MESSAGE
|
|
1197
1223
|
}]);
|
|
1198
1224
|
}, []);
|
|
1199
1225
|
const processStream = useCallback(async (currentMessages, executionMode = mode) => {
|
|
1226
|
+
// v8 ignore next
|
|
1227
|
+
if (!model) throw new Error("Model is required");
|
|
1200
1228
|
const controller = new AbortController();
|
|
1201
1229
|
abortControllerRef.current = controller;
|
|
1202
1230
|
const assistantMessage = {
|
|
@@ -1271,6 +1299,9 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1271
1299
|
theme
|
|
1272
1300
|
]);
|
|
1273
1301
|
const processStreamReadOnly = useCallback(async (currentMessages) => {
|
|
1302
|
+
const modelName = model;
|
|
1303
|
+
// v8 ignore next
|
|
1304
|
+
if (!modelName) throw new Error("Model is required");
|
|
1274
1305
|
const controller = new AbortController();
|
|
1275
1306
|
abortControllerRef.current = controller;
|
|
1276
1307
|
const assistantMessage = {
|
|
@@ -1301,7 +1332,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1301
1332
|
setStreamingMessage(assistantMessage);
|
|
1302
1333
|
try {
|
|
1303
1334
|
const readOnlyTools = TOOLS.filter((tool) => READ_TOOLS.has(tool.function.name));
|
|
1304
|
-
for await (const chunk of streamChat(withSystemMessage(currentMessages),
|
|
1335
|
+
for await (const chunk of streamChat(withSystemMessage(currentMessages), modelName, readOnlyTools, controller.signal)) {
|
|
1305
1336
|
// v8 ignore next 3
|
|
1306
1337
|
if (controller.signal.aborted) return;
|
|
1307
1338
|
if (chunk.type === "content") {
|
|
@@ -1337,7 +1368,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1337
1368
|
};
|
|
1338
1369
|
setStreamingMessage(planAssistantMessage);
|
|
1339
1370
|
try {
|
|
1340
|
-
for await (const chunk of streamChat(withSystemMessage(planMessages),
|
|
1371
|
+
for await (const chunk of streamChat(withSystemMessage(planMessages), modelName, [], controller.signal)) {
|
|
1341
1372
|
// v8 ignore next 3
|
|
1342
1373
|
if (controller.signal.aborted) return;
|
|
1343
1374
|
if (chunk.type === "content") {
|
|
@@ -1429,7 +1460,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1429
1460
|
content: TURN_ABORTED_MESSAGE
|
|
1430
1461
|
}]);
|
|
1431
1462
|
setIsLoading(false);
|
|
1432
|
-
setInterruptReason(
|
|
1463
|
+
setInterruptReason(InterruptReason.Rejected);
|
|
1433
1464
|
break;
|
|
1434
1465
|
}
|
|
1435
1466
|
}, [
|
|
@@ -1485,7 +1516,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1485
1516
|
marginBottom: 1,
|
|
1486
1517
|
children: /* @__PURE__ */ jsx(Text, {
|
|
1487
1518
|
color: theme.colors.error,
|
|
1488
|
-
children: interruptReason ===
|
|
1519
|
+
children: interruptReason === InterruptReason.Rejected ? `❗ Tool call rejected.` : `❗ Execution interrupted.`
|
|
1489
1520
|
})
|
|
1490
1521
|
}),
|
|
1491
1522
|
!pendingPlan && !pendingToolCall && /* @__PURE__ */ jsx(Box, {
|
|
@@ -1512,6 +1543,7 @@ function getModeColor(mode, theme) {
|
|
|
1512
1543
|
}
|
|
1513
1544
|
}
|
|
1514
1545
|
function Footer({ mode, model, onToggleMode, theme = getTheme() }) {
|
|
1546
|
+
const modelLabel = model || "not configured";
|
|
1515
1547
|
useInput((_, key) => {
|
|
1516
1548
|
if (key.tab && key.shift) onToggleMode();
|
|
1517
1549
|
});
|
|
@@ -1535,7 +1567,7 @@ function Footer({ mode, model, onToggleMode, theme = getTheme() }) {
|
|
|
1535
1567
|
" Model: ",
|
|
1536
1568
|
/* @__PURE__ */ jsx(Text, {
|
|
1537
1569
|
color: theme.colors.model,
|
|
1538
|
-
children:
|
|
1570
|
+
children: modelLabel
|
|
1539
1571
|
})
|
|
1540
1572
|
]
|
|
1541
1573
|
})
|
|
@@ -1549,6 +1581,7 @@ function abbreviatePath(dir) {
|
|
|
1549
1581
|
}
|
|
1550
1582
|
function Header({ model, onLoad, theme = getTheme() }) {
|
|
1551
1583
|
const directory = abbreviatePath(process.cwd());
|
|
1584
|
+
const modelLabel = model || "not configured";
|
|
1552
1585
|
useEffect(() => {
|
|
1553
1586
|
onLoad();
|
|
1554
1587
|
}, []);
|
|
@@ -1580,7 +1613,7 @@ function Header({ model, onLoad, theme = getTheme() }) {
|
|
|
1580
1613
|
dimColor: true,
|
|
1581
1614
|
children: "model:".padEnd(11)
|
|
1582
1615
|
}),
|
|
1583
|
-
/* @__PURE__ */ jsx(Text, { children:
|
|
1616
|
+
/* @__PURE__ */ jsx(Text, { children: modelLabel.padEnd(modelLabel.length + 3) }),
|
|
1584
1617
|
/* @__PURE__ */ jsx(Text, {
|
|
1585
1618
|
color: theme.colors.command,
|
|
1586
1619
|
children: "/model"
|
|
@@ -1588,7 +1621,7 @@ function Header({ model, onLoad, theme = getTheme() }) {
|
|
|
1588
1621
|
/* @__PURE__ */ jsxs(Text, {
|
|
1589
1622
|
color: theme.colors.secondary,
|
|
1590
1623
|
dimColor: true,
|
|
1591
|
-
children: [" ", "to
|
|
1624
|
+
children: [" ", "to manage"]
|
|
1592
1625
|
})
|
|
1593
1626
|
]
|
|
1594
1627
|
}),
|
|
@@ -1602,48 +1635,611 @@ function Header({ model, onLoad, theme = getTheme() }) {
|
|
|
1602
1635
|
});
|
|
1603
1636
|
}
|
|
1604
1637
|
//#endregion
|
|
1605
|
-
//#region src/components/
|
|
1606
|
-
function
|
|
1607
|
-
const
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1638
|
+
//#region src/components/ModelManager/ModelSuggestions.tsx
|
|
1639
|
+
function rankCatalogMatch(entry, normalizedInput) {
|
|
1640
|
+
const normalizedValue = entry.value.toLowerCase();
|
|
1641
|
+
const normalizedLabel = entry.label.toLowerCase();
|
|
1642
|
+
// v8 ignore start
|
|
1643
|
+
switch (true) {
|
|
1644
|
+
case normalizedValue.startsWith(normalizedInput): return 0;
|
|
1645
|
+
case normalizedLabel.startsWith(normalizedInput): return 1;
|
|
1646
|
+
case normalizedValue.includes(normalizedInput): return 2;
|
|
1647
|
+
case normalizedLabel.includes(normalizedInput): return 3;
|
|
1648
|
+
default: return Number.MAX_SAFE_INTEGER;
|
|
1649
|
+
}
|
|
1650
|
+
// v8 ignore stop
|
|
1651
|
+
}
|
|
1652
|
+
function ModelSuggestions({ catalog, input, isDisabled = false, onHighlight, onSelect }) {
|
|
1653
|
+
const normalizedInput = input.trim().toLowerCase();
|
|
1654
|
+
return /* @__PURE__ */ jsx(Suggestions, {
|
|
1655
|
+
isDisabled,
|
|
1656
|
+
options: useMemo(() => {
|
|
1657
|
+
if (!normalizedInput) return [];
|
|
1658
|
+
return catalog.filter((entry) => entry.value.toLowerCase().includes(normalizedInput) || entry.label.toLowerCase().includes(normalizedInput)).sort((left, right) => rankCatalogMatch(left, normalizedInput) - rankCatalogMatch(right, normalizedInput) || left.label.localeCompare(right.label)).map((entry) => ({
|
|
1659
|
+
label: entry.value,
|
|
1660
|
+
value: entry.value
|
|
1661
|
+
}));
|
|
1662
|
+
}, [catalog, normalizedInput]),
|
|
1663
|
+
resetKey: input,
|
|
1664
|
+
/* v8 ignore next */
|
|
1665
|
+
onHighlight: (option) => onHighlight?.(option?.value ?? null),
|
|
1666
|
+
onSelect: (option) => {
|
|
1667
|
+
onSelect(option.value);
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
//#endregion
|
|
1672
|
+
//#region src/components/ModelManager/types.ts
|
|
1673
|
+
var View = /* @__PURE__ */ function(View) {
|
|
1674
|
+
View["Menu"] = "menu";
|
|
1675
|
+
View["Switch"] = "switch";
|
|
1676
|
+
View["Download"] = "download";
|
|
1677
|
+
View["CustomDownload"] = "custom-download";
|
|
1678
|
+
View["Downloading"] = "downloading";
|
|
1679
|
+
View["Delete"] = "delete";
|
|
1680
|
+
View["DeleteConfirm"] = "delete-confirm";
|
|
1681
|
+
return View;
|
|
1682
|
+
}({});
|
|
1683
|
+
var MenuAction = /* @__PURE__ */ function(MenuAction) {
|
|
1684
|
+
MenuAction["Switch"] = "switch";
|
|
1685
|
+
MenuAction["Download"] = "download";
|
|
1686
|
+
MenuAction["Delete"] = "delete";
|
|
1687
|
+
MenuAction["Cancel"] = "cancel";
|
|
1688
|
+
return MenuAction;
|
|
1689
|
+
}({});
|
|
1690
|
+
var DownloadAction = /* @__PURE__ */ function(DownloadAction) {
|
|
1691
|
+
DownloadAction["Custom"] = "custom";
|
|
1692
|
+
return DownloadAction;
|
|
1693
|
+
}({});
|
|
1694
|
+
var ConfirmDeleteAction = /* @__PURE__ */ function(ConfirmDeleteAction) {
|
|
1695
|
+
ConfirmDeleteAction["Delete"] = "delete";
|
|
1696
|
+
return ConfirmDeleteAction;
|
|
1697
|
+
}({});
|
|
1698
|
+
//#endregion
|
|
1699
|
+
//#region src/components/ModelManager/utils.ts
|
|
1700
|
+
function buildMenuOptions() {
|
|
1701
|
+
return [
|
|
1702
|
+
{
|
|
1703
|
+
label: "Switch model",
|
|
1704
|
+
value: MenuAction.Switch
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
label: "Download model",
|
|
1708
|
+
value: MenuAction.Download
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
label: "Delete model",
|
|
1712
|
+
value: MenuAction.Delete
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
label: "Cancel",
|
|
1716
|
+
value: MenuAction.Cancel
|
|
1616
1717
|
}
|
|
1718
|
+
];
|
|
1719
|
+
}
|
|
1720
|
+
function buildInstalledModelOptions(models, currentModel) {
|
|
1721
|
+
const nextModels = [...models];
|
|
1722
|
+
if (nextModels.includes(currentModel)) {
|
|
1723
|
+
nextModels.splice(nextModels.indexOf(currentModel), 1);
|
|
1724
|
+
nextModels.unshift(currentModel);
|
|
1725
|
+
}
|
|
1726
|
+
return nextModels.map((model) => ({
|
|
1727
|
+
label: model === currentModel ? `${model} (current model)` : model,
|
|
1728
|
+
value: model
|
|
1729
|
+
}));
|
|
1730
|
+
}
|
|
1731
|
+
function buildDownloadOptions(installedModels) {
|
|
1732
|
+
const installedModelSet = new Set(installedModels);
|
|
1733
|
+
const availableCatalog = CATALOG.filter(({ value, alias }) => !installedModelSet.has(value) && !(alias && installedModelSet.has(alias)));
|
|
1734
|
+
return [
|
|
1735
|
+
{
|
|
1736
|
+
label: "Enter custom model...",
|
|
1737
|
+
value: DownloadAction.Custom
|
|
1738
|
+
},
|
|
1739
|
+
...availableCatalog.map(({ label, value }) => ({
|
|
1740
|
+
label,
|
|
1741
|
+
value
|
|
1742
|
+
})),
|
|
1743
|
+
BACK
|
|
1744
|
+
];
|
|
1745
|
+
}
|
|
1746
|
+
function getNoticeColor(tone, theme) {
|
|
1747
|
+
switch (tone) {
|
|
1748
|
+
case "error": return theme.colors.error;
|
|
1749
|
+
case "success": return theme.colors.status;
|
|
1750
|
+
default: return theme.colors.secondary;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function formatBytes(bytes) {
|
|
1754
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
1755
|
+
const units = [
|
|
1756
|
+
"B",
|
|
1757
|
+
"KB",
|
|
1758
|
+
"MB",
|
|
1759
|
+
"GB",
|
|
1760
|
+
"TB"
|
|
1761
|
+
];
|
|
1762
|
+
let value = bytes;
|
|
1763
|
+
let index = 0;
|
|
1764
|
+
while (value >= 1024 && index < units.length - 1) {
|
|
1765
|
+
value /= 1024;
|
|
1766
|
+
index += 1;
|
|
1767
|
+
}
|
|
1768
|
+
const fractionDigits = value >= 10 || index === 0 ? 0 : 1;
|
|
1769
|
+
return `${value.toFixed(fractionDigits)} ${units[index]}`;
|
|
1770
|
+
}
|
|
1771
|
+
function isAbortError(error) {
|
|
1772
|
+
return error instanceof Error && error.name === "AbortError";
|
|
1773
|
+
}
|
|
1774
|
+
function mergeDownloadProgress(previous, model, status, completed, total) {
|
|
1775
|
+
const nextCompleted = typeof completed === "number" && Number.isFinite(completed) && completed >= 0 ? completed : null;
|
|
1776
|
+
const nextTotal = typeof total === "number" && Number.isFinite(total) && total > 0 ? total : null;
|
|
1777
|
+
if (nextTotal !== null && nextCompleted !== null) return {
|
|
1778
|
+
model,
|
|
1779
|
+
status,
|
|
1780
|
+
completed: nextCompleted,
|
|
1781
|
+
total: nextTotal
|
|
1782
|
+
};
|
|
1783
|
+
const hasPreviousProgress = previous?.model === model && previous.total > 0;
|
|
1784
|
+
return {
|
|
1785
|
+
model,
|
|
1786
|
+
status,
|
|
1787
|
+
completed: hasPreviousProgress ? previous.completed : 0,
|
|
1788
|
+
total: hasPreviousProgress ? previous.total : 0
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
//#endregion
|
|
1792
|
+
//#region src/components/ModelManager/ModelCustomDownloadView.tsx
|
|
1793
|
+
function ModelCustomDownloadView({ downloadDraft, notice, theme, onDraftChange, onHighlight, onSelectSuggestion, onSubmit }) {
|
|
1794
|
+
const renderNotice = () => notice ? /* @__PURE__ */ jsx(Text, {
|
|
1795
|
+
color: getNoticeColor(notice.tone, theme),
|
|
1796
|
+
children: notice.text
|
|
1797
|
+
}) : null;
|
|
1798
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1799
|
+
flexDirection: "column",
|
|
1800
|
+
children: [
|
|
1801
|
+
/* @__PURE__ */ jsx(Text, { children: "Enter an Ollama model name to download." }),
|
|
1802
|
+
/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
|
|
1803
|
+
value: downloadDraft,
|
|
1804
|
+
placeholder: "name:tag",
|
|
1805
|
+
wrapIndent: 2,
|
|
1806
|
+
onChange: onDraftChange,
|
|
1807
|
+
onSubmit
|
|
1808
|
+
})] }),
|
|
1809
|
+
/* @__PURE__ */ jsx(ModelSuggestions, {
|
|
1810
|
+
catalog: [],
|
|
1811
|
+
input: downloadDraft,
|
|
1812
|
+
onHighlight,
|
|
1813
|
+
onSelect: onSelectSuggestion
|
|
1814
|
+
}),
|
|
1815
|
+
renderNotice(),
|
|
1816
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1817
|
+
color: theme.colors.secondary,
|
|
1818
|
+
dimColor: true,
|
|
1819
|
+
children: "Press Enter to download, Esc or Ctrl+C to go back."
|
|
1820
|
+
})
|
|
1821
|
+
]
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
//#endregion
|
|
1825
|
+
//#region src/components/ModelManager/ModelDeleteConfirmView.tsx
|
|
1826
|
+
function ModelDeleteConfirmView({ deleteCandidate, isDeleting, notice, theme, onCancel, onConfirm }) {
|
|
1827
|
+
if (isDeleting) return /* @__PURE__ */ jsx(Spinner, { label: `Deleting model ${deleteCandidate}...` });
|
|
1828
|
+
const renderNotice = () => notice ? /* @__PURE__ */ jsx(Text, {
|
|
1829
|
+
color: getNoticeColor(notice.tone, theme),
|
|
1830
|
+
children: notice.text
|
|
1831
|
+
}) : null;
|
|
1832
|
+
return /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
1833
|
+
options: [{
|
|
1834
|
+
label: `Yes, delete ${deleteCandidate}`,
|
|
1835
|
+
value: ConfirmDeleteAction.Delete
|
|
1836
|
+
}, {
|
|
1837
|
+
...BACK,
|
|
1838
|
+
label: "No"
|
|
1839
|
+
}],
|
|
1840
|
+
onCancel,
|
|
1841
|
+
onChange: onConfirm,
|
|
1842
|
+
children: [
|
|
1843
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1844
|
+
WARNING,
|
|
1845
|
+
" Delete model",
|
|
1846
|
+
" ",
|
|
1847
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1848
|
+
color: theme.colors.model,
|
|
1849
|
+
children: deleteCandidate
|
|
1850
|
+
}),
|
|
1851
|
+
"?"
|
|
1852
|
+
] }),
|
|
1853
|
+
renderNotice(),
|
|
1854
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: "This action cannot be undone" })
|
|
1855
|
+
]
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
//#endregion
|
|
1859
|
+
//#region src/components/ModelManager/ModelDeleteView.tsx
|
|
1860
|
+
function ModelDeleteView({ currentModel, installedModels, isLoading, notice, theme, onCancel, onSelect }) {
|
|
1861
|
+
if (isLoading) return /* @__PURE__ */ jsx(Spinner, { label: "Loading models..." });
|
|
1862
|
+
return /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
1863
|
+
options: [...buildInstalledModelOptions(installedModels.filter((model) => model !== currentModel), currentModel), BACK],
|
|
1864
|
+
onCancel,
|
|
1865
|
+
onChange: (value) => {
|
|
1866
|
+
if (value === BACK.value) onCancel();
|
|
1867
|
+
else onSelect(value);
|
|
1868
|
+
},
|
|
1869
|
+
children: [
|
|
1870
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1871
|
+
"Delete an installed model (current model",
|
|
1872
|
+
" ",
|
|
1873
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1874
|
+
color: theme.colors.model,
|
|
1875
|
+
children: currentModel
|
|
1876
|
+
}),
|
|
1877
|
+
" cannot be deleted)."
|
|
1878
|
+
] }),
|
|
1879
|
+
notice && /* @__PURE__ */ jsx(Text, {
|
|
1880
|
+
color: getNoticeColor(notice.tone, theme),
|
|
1881
|
+
children: notice.text
|
|
1882
|
+
}),
|
|
1883
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: "Delete models" })
|
|
1884
|
+
]
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
//#endregion
|
|
1888
|
+
//#region src/components/ModelManager/ModelDownloadingView.tsx
|
|
1889
|
+
function ModelDownloadingView({ progress, theme, onCancel }) {
|
|
1890
|
+
const percent = progress.total > 0 && Number.isFinite(progress.completed) && Number.isFinite(progress.total) ? Math.round(progress.completed / progress.total * 100) : null;
|
|
1891
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1892
|
+
flexDirection: "column",
|
|
1893
|
+
children: [
|
|
1894
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1895
|
+
"Downloading model:",
|
|
1896
|
+
" ",
|
|
1897
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1898
|
+
color: theme.colors.model,
|
|
1899
|
+
children: progress.model
|
|
1900
|
+
})
|
|
1901
|
+
] }),
|
|
1902
|
+
/* @__PURE__ */ jsx(Text, { children: progress.status }),
|
|
1903
|
+
percent !== null ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
1904
|
+
percent,
|
|
1905
|
+
"% (",
|
|
1906
|
+
formatBytes(progress.completed),
|
|
1907
|
+
" /",
|
|
1908
|
+
" ",
|
|
1909
|
+
formatBytes(progress.total),
|
|
1910
|
+
")"
|
|
1911
|
+
] }), /* @__PURE__ */ jsx(ProgressBar, { value: Math.max(0, Math.min(100, percent)) })] }) : /* @__PURE__ */ jsx(Text, {
|
|
1912
|
+
color: theme.colors.secondary,
|
|
1913
|
+
dimColor: true,
|
|
1914
|
+
children: "Progress details unavailable. Waiting for Ollama updates..."
|
|
1915
|
+
}),
|
|
1916
|
+
/* @__PURE__ */ jsx(SelectPrompt, {
|
|
1917
|
+
options: [{
|
|
1918
|
+
label: "Cancel download",
|
|
1919
|
+
value: "cancel-download"
|
|
1920
|
+
}],
|
|
1921
|
+
onCancel,
|
|
1922
|
+
onChange: onCancel,
|
|
1923
|
+
children: /* @__PURE__ */ jsx(SelectPromptHint, { message: "Press Enter, Esc, or Ctrl+C to cancel" })
|
|
1924
|
+
})
|
|
1925
|
+
]
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region src/components/ModelManager/ModelDownloadView.tsx
|
|
1930
|
+
function ModelDownloadView({ installedModels, notice, theme, onCancel, onChange }) {
|
|
1931
|
+
return /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
1932
|
+
options: buildDownloadOptions(installedModels),
|
|
1933
|
+
onCancel,
|
|
1934
|
+
onChange: (value) => {
|
|
1935
|
+
switch (value) {
|
|
1936
|
+
case "custom":
|
|
1937
|
+
onChange("custom");
|
|
1938
|
+
break;
|
|
1939
|
+
case "back":
|
|
1940
|
+
onCancel();
|
|
1941
|
+
break;
|
|
1942
|
+
default:
|
|
1943
|
+
onChange(value);
|
|
1944
|
+
break;
|
|
1945
|
+
}
|
|
1946
|
+
},
|
|
1947
|
+
children: [
|
|
1948
|
+
/* @__PURE__ */ jsx(Text, { children: "Choose a model to download or use a custom model name." }),
|
|
1949
|
+
notice && /* @__PURE__ */ jsx(Text, {
|
|
1950
|
+
color: getNoticeColor(notice.tone, theme),
|
|
1951
|
+
children: notice.text
|
|
1952
|
+
}),
|
|
1953
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: "Download models" })
|
|
1954
|
+
]
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
//#endregion
|
|
1958
|
+
//#region src/components/ModelManager/ModelSwitchView.tsx
|
|
1959
|
+
function ModelSwitchView({ currentModel, installedModels, isLoading, onCancel, onSelect }) {
|
|
1960
|
+
if (isLoading) return /* @__PURE__ */ jsx(Spinner, { label: "Loading models..." });
|
|
1961
|
+
return /* @__PURE__ */ jsx(SelectPrompt, {
|
|
1962
|
+
options: [...buildInstalledModelOptions(installedModels, currentModel), BACK],
|
|
1963
|
+
onCancel,
|
|
1964
|
+
onChange: (value) => {
|
|
1965
|
+
if (value === BACK.value) onCancel();
|
|
1966
|
+
else onSelect(value);
|
|
1967
|
+
},
|
|
1968
|
+
children: /* @__PURE__ */ jsx(SelectPromptHint, { message: "Switch models" })
|
|
1617
1969
|
});
|
|
1970
|
+
}
|
|
1971
|
+
//#endregion
|
|
1972
|
+
//#region src/components/ModelManager/ModelManager.tsx
|
|
1973
|
+
function ModelManager({ currentModel, onSelect, onClose, theme = getTheme() }) {
|
|
1974
|
+
const [view, setView] = useState(View.Menu);
|
|
1975
|
+
const [installedModels, setInstalledModels] = useState([]);
|
|
1976
|
+
const [isLoadingModels, setIsLoadingModels] = useState(true);
|
|
1977
|
+
const [loadError, setLoadError] = useState(null);
|
|
1978
|
+
const [notice, setNotice] = useState(null);
|
|
1979
|
+
const [downloadDraft, setDownloadDraft] = useState("");
|
|
1980
|
+
const [highlightedSuggestion, setHighlightedSuggestion] = useState(null);
|
|
1981
|
+
const [downloadProgress, setDownloadProgress] = useState(null);
|
|
1982
|
+
const [deleteCandidate, setDeleteCandidate] = useState(null);
|
|
1983
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
1984
|
+
const isDeletingRef = useRef(false);
|
|
1985
|
+
const pullRef = useRef(null);
|
|
1986
|
+
const loadInstalledModels = useCallback(async () => {
|
|
1987
|
+
setIsLoadingModels(true);
|
|
1988
|
+
setLoadError(null);
|
|
1989
|
+
try {
|
|
1990
|
+
setInstalledModels(await listModels());
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
setLoadError(error instanceof Error ? error.message : /* v8 ignore next */ String(error));
|
|
1993
|
+
} finally {
|
|
1994
|
+
setIsLoadingModels(false);
|
|
1995
|
+
}
|
|
1996
|
+
}, []);
|
|
1618
1997
|
useEffect(() => {
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1998
|
+
loadInstalledModels();
|
|
1999
|
+
}, [loadInstalledModels]);
|
|
2000
|
+
const resetDownloadState = useCallback(() => {
|
|
2001
|
+
setDownloadDraft("");
|
|
2002
|
+
setHighlightedSuggestion(null);
|
|
2003
|
+
setDownloadProgress(null);
|
|
2004
|
+
pullRef.current = null;
|
|
2005
|
+
}, []);
|
|
2006
|
+
const handleBackToMenu = useCallback(() => {
|
|
2007
|
+
setNotice(null);
|
|
2008
|
+
setDeleteCandidate(null);
|
|
2009
|
+
setIsDeleting(false);
|
|
2010
|
+
isDeletingRef.current = false;
|
|
2011
|
+
resetDownloadState();
|
|
2012
|
+
setView(View.Menu);
|
|
2013
|
+
}, [resetDownloadState]);
|
|
2014
|
+
const cancelActivePull = useCallback(() => {
|
|
2015
|
+
pullRef.current?.abort();
|
|
2016
|
+
}, []);
|
|
2017
|
+
useInput((input, key) => {
|
|
2018
|
+
const isEscape = key.escape || input === "\x1B\x1B";
|
|
2019
|
+
const isCtrlC = key.ctrl && input === "c" || input === "";
|
|
2020
|
+
if (view === View.CustomDownload && (isEscape || isCtrlC)) {
|
|
2021
|
+
setNotice(null);
|
|
2022
|
+
setHighlightedSuggestion(null);
|
|
2023
|
+
setView(View.Download);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
// v8 ignore next
|
|
2027
|
+
if (view === View.Downloading && (isEscape || isCtrlC)) cancelActivePull();
|
|
2028
|
+
});
|
|
2029
|
+
const handleMenuChange = useCallback((value) => {
|
|
2030
|
+
setNotice(null);
|
|
2031
|
+
switch (value) {
|
|
2032
|
+
case "switch":
|
|
2033
|
+
setView(View.Switch);
|
|
2034
|
+
break;
|
|
2035
|
+
case "download":
|
|
2036
|
+
setView(View.Download);
|
|
2037
|
+
break;
|
|
2038
|
+
case "delete":
|
|
2039
|
+
setView(View.Delete);
|
|
2040
|
+
break;
|
|
2041
|
+
default: onClose();
|
|
2042
|
+
}
|
|
2043
|
+
}, [onClose]);
|
|
2044
|
+
const handleSwitchChange = useCallback((model) => {
|
|
2045
|
+
onSelect({ model });
|
|
2046
|
+
}, [onSelect]);
|
|
2047
|
+
const startPull = useCallback(async (model) => {
|
|
2048
|
+
const normalizedModel = model.trim();
|
|
2049
|
+
if (!normalizedModel) {
|
|
2050
|
+
setNotice({
|
|
2051
|
+
tone: "error",
|
|
2052
|
+
text: `❗ Enter a model name to download`
|
|
2053
|
+
});
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (installedModels.includes(normalizedModel)) {
|
|
2057
|
+
setNotice({
|
|
2058
|
+
tone: "info",
|
|
2059
|
+
text: `${normalizedModel} is already installed`
|
|
2060
|
+
});
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
setNotice(null);
|
|
2064
|
+
setDownloadProgress({
|
|
2065
|
+
model: normalizedModel,
|
|
2066
|
+
status: "Starting download...",
|
|
2067
|
+
completed: 0,
|
|
2068
|
+
total: 0
|
|
2069
|
+
});
|
|
2070
|
+
setView(View.Downloading);
|
|
2071
|
+
try {
|
|
2072
|
+
const pull = await pullModel(normalizedModel);
|
|
2073
|
+
pullRef.current = pull;
|
|
2074
|
+
for await (const update of pull) setDownloadProgress((previous) => {
|
|
2075
|
+
return mergeDownloadProgress(previous, normalizedModel, update.status, update.completed, update.total);
|
|
2076
|
+
});
|
|
2077
|
+
pullRef.current = null;
|
|
2078
|
+
resetDownloadState();
|
|
2079
|
+
await loadInstalledModels();
|
|
2080
|
+
setNotice({
|
|
2081
|
+
tone: "success",
|
|
2082
|
+
text: `✅ ${normalizedModel} downloaded successfully`
|
|
2083
|
+
});
|
|
2084
|
+
setView(View.Menu);
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
pullRef.current = null;
|
|
2087
|
+
if (isAbortError(error)) {
|
|
2088
|
+
setNotice({
|
|
2089
|
+
tone: "error",
|
|
2090
|
+
text: `❌ Download canceled for ${normalizedModel}`
|
|
2091
|
+
});
|
|
2092
|
+
setDownloadProgress(null);
|
|
2093
|
+
setView(View.Download);
|
|
2094
|
+
return;
|
|
1632
2095
|
}
|
|
2096
|
+
setNotice({
|
|
2097
|
+
tone: "error",
|
|
2098
|
+
text: `❗ Error downloading model: ${error instanceof Error ? error.message : /* v8 ignore next */ String(error)}`
|
|
2099
|
+
});
|
|
2100
|
+
setDownloadProgress(null);
|
|
2101
|
+
setView(View.CustomDownload);
|
|
2102
|
+
}
|
|
2103
|
+
}, [
|
|
2104
|
+
installedModels,
|
|
2105
|
+
loadInstalledModels,
|
|
2106
|
+
resetDownloadState
|
|
2107
|
+
]);
|
|
2108
|
+
const handleDownloadChange = useCallback((value) => {
|
|
2109
|
+
if (value === "custom") {
|
|
2110
|
+
setNotice(null);
|
|
2111
|
+
setView(View.CustomDownload);
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
// v8 ignore next 3
|
|
2115
|
+
if (value === "back") {
|
|
2116
|
+
handleBackToMenu();
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
setDownloadDraft(value);
|
|
2120
|
+
startPull(value);
|
|
2121
|
+
}, [handleBackToMenu, startPull]);
|
|
2122
|
+
const handleCustomDownloadSubmit = useCallback((value) => {
|
|
2123
|
+
const nextValue = highlightedSuggestion ?? value.trim();
|
|
2124
|
+
setDownloadDraft(nextValue);
|
|
2125
|
+
startPull(nextValue);
|
|
2126
|
+
}, [highlightedSuggestion, startPull]);
|
|
2127
|
+
const handleDeleteChange = useCallback((model) => {
|
|
2128
|
+
setNotice(null);
|
|
2129
|
+
setDeleteCandidate(model);
|
|
2130
|
+
setView(View.DeleteConfirm);
|
|
2131
|
+
}, []);
|
|
2132
|
+
const handleDeleteConfirm = useCallback(async (value) => {
|
|
2133
|
+
if (isDeletingRef.current) return;
|
|
2134
|
+
if (value === "back") {
|
|
2135
|
+
setView(View.Delete);
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
// v8 ignore next 3
|
|
2139
|
+
if (!deleteCandidate) {
|
|
2140
|
+
setView(View.Delete);
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
try {
|
|
2144
|
+
isDeletingRef.current = true;
|
|
2145
|
+
setIsDeleting(true);
|
|
2146
|
+
await deleteModel(deleteCandidate);
|
|
2147
|
+
await loadInstalledModels();
|
|
2148
|
+
setNotice({
|
|
2149
|
+
tone: "success",
|
|
2150
|
+
text: `✅ ${deleteCandidate} deleted successfully`
|
|
2151
|
+
});
|
|
2152
|
+
isDeletingRef.current = false;
|
|
2153
|
+
setIsDeleting(false);
|
|
2154
|
+
setDeleteCandidate(null);
|
|
2155
|
+
setView(View.Delete);
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
isDeletingRef.current = false;
|
|
2158
|
+
setIsDeleting(false);
|
|
2159
|
+
setNotice({
|
|
2160
|
+
tone: "error",
|
|
2161
|
+
text: `❗ Error deleting model: ${error instanceof Error ? error.message : /* v8 ignore next */ String(error)}`
|
|
2162
|
+
});
|
|
2163
|
+
setView(View.Delete);
|
|
1633
2164
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
2165
|
+
}, [deleteCandidate, loadInstalledModels]);
|
|
2166
|
+
const renderNotice = () => notice ? /* @__PURE__ */ jsx(Text, {
|
|
2167
|
+
color: notice.tone === "error" ? theme.colors.error : notice.tone === "success" ? theme.colors.status : theme.colors.secondary,
|
|
2168
|
+
children: notice.text
|
|
2169
|
+
}) : null;
|
|
2170
|
+
if (loadError && view !== View.Menu) return /* @__PURE__ */ jsxs(Box, {
|
|
2171
|
+
flexDirection: "column",
|
|
2172
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
2173
|
+
color: theme.colors.error,
|
|
2174
|
+
children: ["Error loading models: ", loadError]
|
|
2175
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2176
|
+
color: theme.colors.secondary,
|
|
2177
|
+
dimColor: true,
|
|
2178
|
+
children: "Press Esc to go back."
|
|
2179
|
+
})]
|
|
1639
2180
|
});
|
|
1640
|
-
if (
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
2181
|
+
if (view === View.Downloading && downloadProgress) return /* @__PURE__ */ jsx(ModelDownloadingView, {
|
|
2182
|
+
progress: downloadProgress,
|
|
2183
|
+
theme,
|
|
2184
|
+
onCancel: cancelActivePull
|
|
2185
|
+
});
|
|
2186
|
+
if (view === View.CustomDownload) return /* @__PURE__ */ jsx(ModelCustomDownloadView, {
|
|
2187
|
+
downloadDraft,
|
|
2188
|
+
notice,
|
|
2189
|
+
theme,
|
|
2190
|
+
onDraftChange: setDownloadDraft,
|
|
2191
|
+
onHighlight: setHighlightedSuggestion,
|
|
2192
|
+
onSelectSuggestion: (value) => {
|
|
2193
|
+
setDownloadDraft(value);
|
|
2194
|
+
setHighlightedSuggestion(value);
|
|
2195
|
+
},
|
|
2196
|
+
onSubmit: handleCustomDownloadSubmit
|
|
2197
|
+
});
|
|
2198
|
+
if (view === View.Switch) return /* @__PURE__ */ jsx(ModelSwitchView, {
|
|
2199
|
+
currentModel,
|
|
2200
|
+
installedModels,
|
|
2201
|
+
isLoading: isLoadingModels,
|
|
2202
|
+
onCancel: handleBackToMenu,
|
|
2203
|
+
onSelect: handleSwitchChange
|
|
2204
|
+
});
|
|
2205
|
+
if (view === View.Download) return /* @__PURE__ */ jsx(ModelDownloadView, {
|
|
2206
|
+
installedModels,
|
|
2207
|
+
notice,
|
|
2208
|
+
theme,
|
|
2209
|
+
onCancel: handleBackToMenu,
|
|
2210
|
+
onChange: handleDownloadChange
|
|
2211
|
+
});
|
|
2212
|
+
if (view === View.Delete) return /* @__PURE__ */ jsx(ModelDeleteView, {
|
|
2213
|
+
currentModel,
|
|
2214
|
+
installedModels,
|
|
2215
|
+
isLoading: isLoadingModels,
|
|
2216
|
+
notice,
|
|
2217
|
+
theme,
|
|
2218
|
+
onCancel: handleBackToMenu,
|
|
2219
|
+
onSelect: handleDeleteChange
|
|
2220
|
+
});
|
|
2221
|
+
if (view === View.DeleteConfirm && deleteCandidate) return /* @__PURE__ */ jsx(ModelDeleteConfirmView, {
|
|
2222
|
+
deleteCandidate,
|
|
2223
|
+
isDeleting,
|
|
2224
|
+
notice,
|
|
2225
|
+
theme,
|
|
2226
|
+
onCancel: () => {
|
|
2227
|
+
setView(View.Delete);
|
|
2228
|
+
},
|
|
2229
|
+
onConfirm: handleDeleteConfirm
|
|
2230
|
+
});
|
|
2231
|
+
return /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
2232
|
+
options: buildMenuOptions(),
|
|
1645
2233
|
onCancel: onClose,
|
|
1646
|
-
|
|
2234
|
+
onChange: handleMenuChange,
|
|
2235
|
+
children: [
|
|
2236
|
+
/* @__PURE__ */ jsxs(Text, { children: ["Current model: ", /* @__PURE__ */ jsx(Text, {
|
|
2237
|
+
color: theme.colors.model,
|
|
2238
|
+
children: currentModel
|
|
2239
|
+
})] }),
|
|
2240
|
+
renderNotice(),
|
|
2241
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: "Manage models" })
|
|
2242
|
+
]
|
|
1647
2243
|
});
|
|
1648
2244
|
}
|
|
1649
2245
|
//#endregion
|
|
@@ -1751,7 +2347,6 @@ function SearchSettings({ currentUrl, onClose, onSave, theme = getTheme() }) {
|
|
|
1751
2347
|
//#endregion
|
|
1752
2348
|
//#region src/components/SessionManager.tsx
|
|
1753
2349
|
var ACTION = {
|
|
1754
|
-
BACK: "back",
|
|
1755
2350
|
CLOSE: "close",
|
|
1756
2351
|
DELETE_MENU: "delete-menu",
|
|
1757
2352
|
DELETE_PREFIX: "delete:",
|
|
@@ -1779,16 +2374,10 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen, th
|
|
|
1779
2374
|
const options = view === "open" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
1780
2375
|
label: formatSessionLabel(session, maxLabelWidth),
|
|
1781
2376
|
value: `${ACTION.OPEN_PREFIX}${session.id}`
|
|
1782
|
-
})), {
|
|
1783
|
-
label: "Back",
|
|
1784
|
-
value: ACTION.BACK
|
|
1785
|
-
}] : view === "delete" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
2377
|
+
})), BACK] : view === "delete" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
1786
2378
|
label: formatSessionLabel(session, maxLabelWidth, "Delete "),
|
|
1787
2379
|
value: `${ACTION.DELETE_PREFIX}${session.id}`
|
|
1788
|
-
})),
|
|
1789
|
-
label: "Back",
|
|
1790
|
-
value: ACTION.BACK
|
|
1791
|
-
}] : [
|
|
2380
|
+
})), BACK] : [
|
|
1792
2381
|
{
|
|
1793
2382
|
label: "New session",
|
|
1794
2383
|
value: ACTION.NEW
|
|
@@ -1820,7 +2409,7 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen, th
|
|
|
1820
2409
|
case value === ACTION.OPEN_MENU:
|
|
1821
2410
|
setView("open");
|
|
1822
2411
|
break;
|
|
1823
|
-
case value ===
|
|
2412
|
+
case value === BACK.value:
|
|
1824
2413
|
setView("main");
|
|
1825
2414
|
break;
|
|
1826
2415
|
case value.startsWith(ACTION.DELETE_PREFIX):
|
|
@@ -1970,45 +2559,45 @@ function ThemeSettings({ currentTheme, onClose, onPreview, onSave }) {
|
|
|
1970
2559
|
}
|
|
1971
2560
|
//#endregion
|
|
1972
2561
|
//#region src/components/App/constants.ts
|
|
1973
|
-
var
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
return
|
|
2562
|
+
var Screen = /* @__PURE__ */ function(Screen) {
|
|
2563
|
+
Screen["Chat"] = "chat";
|
|
2564
|
+
Screen["ModelManager"] = "model-manager";
|
|
2565
|
+
Screen["SearchSettings"] = "search-settings";
|
|
2566
|
+
Screen["SessionManager"] = "session-manager";
|
|
2567
|
+
Screen["ThemeSettings"] = "theme-settings";
|
|
2568
|
+
return Screen;
|
|
1980
2569
|
}({});
|
|
1981
2570
|
//#endregion
|
|
1982
2571
|
//#region src/components/App/hooks/useScreenRouter.ts
|
|
1983
2572
|
function useScreenRouter() {
|
|
1984
2573
|
const { exit } = useApp();
|
|
1985
|
-
const [currentScreen, setScreen] = useState(
|
|
2574
|
+
const [currentScreen, setScreen] = useState(Screen.Chat);
|
|
1986
2575
|
return {
|
|
1987
2576
|
currentScreen,
|
|
1988
2577
|
setScreen,
|
|
1989
2578
|
handleClose: useCallback(() => {
|
|
1990
|
-
setScreen(
|
|
2579
|
+
setScreen(Screen.Chat);
|
|
1991
2580
|
}, []),
|
|
1992
2581
|
handleCommand: useCallback((command, callbacks) => {
|
|
1993
2582
|
const { onCreateSession, onSetPreviewThemeId, model, theme } = callbacks;
|
|
1994
2583
|
switch (command) {
|
|
1995
2584
|
case "/session":
|
|
1996
|
-
setScreen(
|
|
2585
|
+
setScreen(Screen.SessionManager);
|
|
1997
2586
|
break;
|
|
1998
2587
|
case "/model":
|
|
1999
|
-
setScreen(
|
|
2588
|
+
setScreen(Screen.ModelManager);
|
|
2000
2589
|
break;
|
|
2001
2590
|
case "/search":
|
|
2002
|
-
setScreen(
|
|
2591
|
+
setScreen(Screen.SearchSettings);
|
|
2003
2592
|
break;
|
|
2004
2593
|
case "/theme":
|
|
2005
2594
|
onSetPreviewThemeId(theme);
|
|
2006
|
-
setScreen(
|
|
2595
|
+
setScreen(Screen.ThemeSettings);
|
|
2007
2596
|
break;
|
|
2008
2597
|
case "/clear": {
|
|
2009
2598
|
resetSystemMessage();
|
|
2010
2599
|
const nextSession = onCreateSession(model);
|
|
2011
|
-
setScreen(
|
|
2600
|
+
setScreen(Screen.Chat);
|
|
2012
2601
|
clear(nextSession.metadata.id);
|
|
2013
2602
|
break;
|
|
2014
2603
|
}
|
|
@@ -2049,8 +2638,6 @@ function useSessionManager({ sessionId, model, commandColor }) {
|
|
|
2049
2638
|
}, []);
|
|
2050
2639
|
return {
|
|
2051
2640
|
activeSession,
|
|
2052
|
-
sessionRef,
|
|
2053
|
-
setActiveSession,
|
|
2054
2641
|
setSession,
|
|
2055
2642
|
handleCreateSession: useCallback(() => {
|
|
2056
2643
|
const nextSession = createSession(modelRef.current);
|
|
@@ -2089,39 +2676,137 @@ function useSessionManager({ sessionId, model, commandColor }) {
|
|
|
2089
2676
|
//#region src/components/App/hooks/useThemeSettings.ts
|
|
2090
2677
|
function useThemeSettings({ currentTheme, onUpdateConfig, setScreen }) {
|
|
2091
2678
|
const [previewThemeId, setPreviewThemeId] = useState(null);
|
|
2092
|
-
const
|
|
2093
|
-
const activeTheme = getTheme(activeThemeId);
|
|
2679
|
+
const activeTheme = getTheme(previewThemeId ?? currentTheme);
|
|
2094
2680
|
const handleThemePreview = useCallback((themeId) => {
|
|
2095
2681
|
setPreviewThemeId(themeId);
|
|
2096
2682
|
}, []);
|
|
2097
2683
|
return {
|
|
2098
2684
|
activeTheme,
|
|
2099
|
-
activeThemeId,
|
|
2100
2685
|
handleThemeClose: useCallback(() => {
|
|
2101
2686
|
setPreviewThemeId(null);
|
|
2102
|
-
setScreen(
|
|
2687
|
+
setScreen(Screen.Chat);
|
|
2103
2688
|
}, [setScreen]),
|
|
2104
2689
|
handleThemePreview,
|
|
2105
2690
|
handleThemeSave: useCallback((themeId) => {
|
|
2106
2691
|
setPreviewThemeId(null);
|
|
2107
2692
|
onUpdateConfig({ theme: themeId });
|
|
2108
2693
|
}, [onUpdateConfig]),
|
|
2109
|
-
previewThemeId,
|
|
2110
2694
|
setPreviewThemeId
|
|
2111
2695
|
};
|
|
2112
2696
|
}
|
|
2113
2697
|
//#endregion
|
|
2698
|
+
//#region src/components/App/ReadinessCheck.tsx
|
|
2699
|
+
var ReadinessState = /* @__PURE__ */ function(ReadinessState) {
|
|
2700
|
+
ReadinessState["Checking"] = "checking";
|
|
2701
|
+
ReadinessState["Ready"] = "ready";
|
|
2702
|
+
ReadinessState["MissingModelConfig"] = "missing-model-config";
|
|
2703
|
+
ReadinessState["NoInstalledModels"] = "no-installed-models";
|
|
2704
|
+
ReadinessState["ModelLoadError"] = "model-load-error";
|
|
2705
|
+
return ReadinessState;
|
|
2706
|
+
}({});
|
|
2707
|
+
function getTitle(setupState) {
|
|
2708
|
+
switch (setupState) {
|
|
2709
|
+
case "model-load-error": return "Connection Error";
|
|
2710
|
+
case "missing-model-config": return "No Model Configured";
|
|
2711
|
+
case "no-installed-models": return "No Model Installed";
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
function getMessage(setupState, errorMessage) {
|
|
2715
|
+
const theme = getTheme();
|
|
2716
|
+
switch (setupState) {
|
|
2717
|
+
case "checking": return /* @__PURE__ */ jsx(Text, { children: "Checking model setup..." });
|
|
2718
|
+
case "missing-model-config": return /* @__PURE__ */ jsxs(Text, { children: [
|
|
2719
|
+
"Select or download a model with",
|
|
2720
|
+
" ",
|
|
2721
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2722
|
+
color: theme.colors.command,
|
|
2723
|
+
children: "/model"
|
|
2724
|
+
})
|
|
2725
|
+
] });
|
|
2726
|
+
case "no-installed-models": return /* @__PURE__ */ jsxs(Text, { children: ["Download a model with ", /* @__PURE__ */ jsx(Text, {
|
|
2727
|
+
color: theme.colors.command,
|
|
2728
|
+
children: "/model"
|
|
2729
|
+
})] });
|
|
2730
|
+
case "model-load-error": return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
2731
|
+
"Error loading models",
|
|
2732
|
+
errorMessage ? `: ${errorMessage}` : "",
|
|
2733
|
+
"."
|
|
2734
|
+
] }), /* @__PURE__ */ jsx(Text, { children: "Fix the connection and restart the app" })] });
|
|
2735
|
+
default: return null;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
function ReadinessCheck({ errorMessage, onCommand, setupState, theme = getTheme() }) {
|
|
2739
|
+
const title = getTitle(setupState);
|
|
2740
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2741
|
+
flexDirection: "column",
|
|
2742
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
2743
|
+
borderStyle: "round",
|
|
2744
|
+
flexDirection: "column",
|
|
2745
|
+
marginBottom: 1,
|
|
2746
|
+
paddingX: 1,
|
|
2747
|
+
paddingY: 1,
|
|
2748
|
+
children: [title && /* @__PURE__ */ jsxs(Text, {
|
|
2749
|
+
bold: true,
|
|
2750
|
+
color: theme.colors.error,
|
|
2751
|
+
children: [
|
|
2752
|
+
"❗",
|
|
2753
|
+
" ",
|
|
2754
|
+
title
|
|
2755
|
+
]
|
|
2756
|
+
}), getMessage(setupState, errorMessage)]
|
|
2757
|
+
}), /* @__PURE__ */ jsx(ChatInput, {
|
|
2758
|
+
history: [],
|
|
2759
|
+
onSubmit: onCommand
|
|
2760
|
+
})]
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
//#endregion
|
|
2114
2764
|
//#region src/components/App/App.tsx
|
|
2115
2765
|
function App({ sessionId }) {
|
|
2116
2766
|
const [appConfig, setConfig] = useState(() => loadConfig());
|
|
2117
2767
|
const [mode, setMode] = useState(SAFE);
|
|
2118
2768
|
const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
|
|
2769
|
+
const [setupState, setSetupState] = useState(() => appConfig.model ? ReadinessState.Ready : ReadinessState.MissingModelConfig);
|
|
2770
|
+
const [setupErrorMessage, setSetupErrorMessage] = useState(null);
|
|
2119
2771
|
const { currentScreen, setScreen, handleClose, handleCommand } = useScreenRouter();
|
|
2120
2772
|
const { activeSession, setSession, handleCreateSession, handleOpenSession, handleDeleteSession, handleMessagesChange } = useSessionManager({
|
|
2121
2773
|
sessionId,
|
|
2122
|
-
model: appConfig.model,
|
|
2774
|
+
model: appConfig.model ?? "",
|
|
2123
2775
|
commandColor: getTheme(appConfig.theme).colors.command
|
|
2124
2776
|
});
|
|
2777
|
+
useEffect(() => {
|
|
2778
|
+
let isMounted = true;
|
|
2779
|
+
async function refreshSetupState() {
|
|
2780
|
+
if (!appConfig.model) {
|
|
2781
|
+
// v8 ignore next
|
|
2782
|
+
if (isMounted) {
|
|
2783
|
+
setSetupErrorMessage(null);
|
|
2784
|
+
setSetupState(ReadinessState.MissingModelConfig);
|
|
2785
|
+
}
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
if (currentScreen !== Screen.Chat) return;
|
|
2789
|
+
// v8 ignore next
|
|
2790
|
+
if (isMounted) {
|
|
2791
|
+
setSetupErrorMessage(null);
|
|
2792
|
+
setSetupState(ReadinessState.Checking);
|
|
2793
|
+
}
|
|
2794
|
+
try {
|
|
2795
|
+
const installedModels = await listModels();
|
|
2796
|
+
if (!isMounted) return;
|
|
2797
|
+
setSetupState(installedModels.length > 0 ? ReadinessState.Ready : ReadinessState.NoInstalledModels);
|
|
2798
|
+
} catch (error) {
|
|
2799
|
+
// v8 ignore start
|
|
2800
|
+
if (!isMounted) return;
|
|
2801
|
+
setSetupErrorMessage(error instanceof Error ? error.message : String(error));
|
|
2802
|
+
setSetupState(ReadinessState.ModelLoadError);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
refreshSetupState();
|
|
2806
|
+
return () => {
|
|
2807
|
+
isMounted = false;
|
|
2808
|
+
};
|
|
2809
|
+
}, [appConfig.model, currentScreen]);
|
|
2125
2810
|
const handleUpdateConfig = useCallback((update) => {
|
|
2126
2811
|
setConfig((current) => ({
|
|
2127
2812
|
...current,
|
|
@@ -2133,7 +2818,7 @@ function App({ sessionId }) {
|
|
|
2133
2818
|
...current,
|
|
2134
2819
|
metadata: updateSessionModel(current.metadata.id, newModel)
|
|
2135
2820
|
}));
|
|
2136
|
-
setScreen(
|
|
2821
|
+
setScreen(Screen.Chat);
|
|
2137
2822
|
}, [setScreen, setSession]);
|
|
2138
2823
|
const { activeTheme, handleThemeClose, handleThemePreview, handleThemeSave, setPreviewThemeId } = useThemeSettings({
|
|
2139
2824
|
currentTheme: appConfig.theme,
|
|
@@ -2155,7 +2840,7 @@ function App({ sessionId }) {
|
|
|
2155
2840
|
}, []);
|
|
2156
2841
|
const handleChatCommand = useCallback((command) => {
|
|
2157
2842
|
handleCommand(command, {
|
|
2158
|
-
model: appConfig.model,
|
|
2843
|
+
model: appConfig.model ?? "",
|
|
2159
2844
|
theme: appConfig.theme,
|
|
2160
2845
|
onCreateSession: handleCreateSession,
|
|
2161
2846
|
onSetPreviewThemeId: setPreviewThemeId
|
|
@@ -2169,27 +2854,27 @@ function App({ sessionId }) {
|
|
|
2169
2854
|
]);
|
|
2170
2855
|
const handleDeleteSessionAndStay = useCallback((sid) => {
|
|
2171
2856
|
handleDeleteSession(sid);
|
|
2172
|
-
setScreen(
|
|
2857
|
+
setScreen(Screen.SessionManager);
|
|
2173
2858
|
}, [handleDeleteSession, setScreen]);
|
|
2174
2859
|
const handleOpenSessionAndNavigate = useCallback((sid) => {
|
|
2175
2860
|
handleOpenSession(sid);
|
|
2176
|
-
setScreen(
|
|
2861
|
+
setScreen(Screen.Chat);
|
|
2177
2862
|
}, [handleOpenSession, setScreen]);
|
|
2178
2863
|
const handleCreateSessionAndNavigate = useCallback(() => {
|
|
2179
2864
|
handleCreateSession();
|
|
2180
|
-
setScreen(
|
|
2865
|
+
setScreen(Screen.Chat);
|
|
2181
2866
|
}, [handleCreateSession, setScreen]);
|
|
2182
2867
|
let screenContent;
|
|
2183
2868
|
switch (currentScreen) {
|
|
2184
|
-
case
|
|
2185
|
-
screenContent = /* @__PURE__ */ jsx(
|
|
2186
|
-
currentModel: appConfig.model,
|
|
2869
|
+
case Screen.ModelManager:
|
|
2870
|
+
screenContent = /* @__PURE__ */ jsx(ModelManager, {
|
|
2871
|
+
currentModel: appConfig.model ?? "",
|
|
2187
2872
|
onSelect: handleUpdateConfig,
|
|
2188
2873
|
onClose: handleClose,
|
|
2189
2874
|
theme: activeTheme
|
|
2190
2875
|
});
|
|
2191
2876
|
break;
|
|
2192
|
-
case
|
|
2877
|
+
case Screen.SearchSettings:
|
|
2193
2878
|
screenContent = /* @__PURE__ */ jsx(SearchSettings, {
|
|
2194
2879
|
currentUrl: appConfig.searxngBaseUrl,
|
|
2195
2880
|
onSave: handleUpdateConfig,
|
|
@@ -2197,7 +2882,7 @@ function App({ sessionId }) {
|
|
|
2197
2882
|
theme: activeTheme
|
|
2198
2883
|
});
|
|
2199
2884
|
break;
|
|
2200
|
-
case
|
|
2885
|
+
case Screen.SessionManager:
|
|
2201
2886
|
screenContent = /* @__PURE__ */ jsx(SessionManager, {
|
|
2202
2887
|
currentSessionId: activeSession.metadata.id,
|
|
2203
2888
|
onClose: handleClose,
|
|
@@ -2207,7 +2892,7 @@ function App({ sessionId }) {
|
|
|
2207
2892
|
theme: activeTheme
|
|
2208
2893
|
});
|
|
2209
2894
|
break;
|
|
2210
|
-
case
|
|
2895
|
+
case Screen.ThemeSettings:
|
|
2211
2896
|
screenContent = /* @__PURE__ */ jsx(ThemeSettings, {
|
|
2212
2897
|
currentTheme: appConfig.theme,
|
|
2213
2898
|
onClose: handleThemeClose,
|
|
@@ -2215,8 +2900,8 @@ function App({ sessionId }) {
|
|
|
2215
2900
|
onSave: handleThemeSave
|
|
2216
2901
|
});
|
|
2217
2902
|
break;
|
|
2218
|
-
case
|
|
2219
|
-
screenContent = /* @__PURE__ */ jsx(Chat, {
|
|
2903
|
+
case Screen.Chat:
|
|
2904
|
+
screenContent = setupState === ReadinessState.Ready ? /* @__PURE__ */ jsx(Chat, {
|
|
2220
2905
|
initialMessages: activeSession.messages,
|
|
2221
2906
|
model: appConfig.model,
|
|
2222
2907
|
onCommand: handleChatCommand,
|
|
@@ -2225,6 +2910,11 @@ function App({ sessionId }) {
|
|
|
2225
2910
|
onModeChange: setMode,
|
|
2226
2911
|
sessionId: activeSession.metadata.id,
|
|
2227
2912
|
theme: activeTheme
|
|
2913
|
+
}) : /* @__PURE__ */ jsx(ReadinessCheck, {
|
|
2914
|
+
errorMessage: setupErrorMessage,
|
|
2915
|
+
onCommand: handleChatCommand,
|
|
2916
|
+
setupState,
|
|
2917
|
+
theme: activeTheme
|
|
2228
2918
|
});
|
|
2229
2919
|
break;
|
|
2230
2920
|
}
|
|
@@ -2232,14 +2922,14 @@ function App({ sessionId }) {
|
|
|
2232
2922
|
flexDirection: "column",
|
|
2233
2923
|
children: [
|
|
2234
2924
|
/* @__PURE__ */ jsx(Header, {
|
|
2235
|
-
model: appConfig.model,
|
|
2925
|
+
model: appConfig.model ?? "",
|
|
2236
2926
|
onLoad: handleHeaderLoad,
|
|
2237
2927
|
theme: activeTheme
|
|
2238
2928
|
}),
|
|
2239
2929
|
isHeaderLoaded && screenContent,
|
|
2240
2930
|
/* @__PURE__ */ jsx(Footer, {
|
|
2241
2931
|
mode,
|
|
2242
|
-
model: appConfig.model,
|
|
2932
|
+
model: appConfig.model ?? "",
|
|
2243
2933
|
onToggleMode: handleToggleMode,
|
|
2244
2934
|
theme: activeTheme
|
|
2245
2935
|
})
|