code-ollama 0.18.1 → 0.19.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.
- package/dist/assets/{tui-GfzUJAWj.js → tui-JyjdXasW.js} +388 -107
- package/dist/cli.js +124 -5
- package/package.json +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { readdirSync } from "node:fs";
|
|
1
|
+
import { A as WARNING, B as LABEL, C as loadConfig, D as resetSystemMessage, E as saveClipboardImage, F as USER, G as VERSION, H as SAFE, I as PLAN_GENERATION_INSTRUCTION, K as LIST, L as BACK, M as getTheme, N as ASSISTANT, O as withSystemMessage, P as SYSTEM, R as CATALOG, S as streamChat, T as removeClipboardImage, U as APPROVE, V as PLAN, W as REJECT, _ as setClearHandler, a as tick, b as listModels, c as appendMessage, d as deleteSessionIfEmpty, f as listSessions, g as reset, h as clear, i as WRITE_TOOLS, j as LIST$1, k as HEADER_PREFIX, l as createSession, m as updateSessionModel, n as READ_TOOLS, o as color, p as loadSession, r as TOOLS, s as write, t as executeTool, u as deleteSession, v as checkHealth, w as saveConfig, x as pullModel, y as deleteModel, z as AUTO } from "../cli.js";
|
|
2
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { join, relative } from "node:path";
|
|
4
|
+
import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
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";
|
|
@@ -416,6 +416,26 @@ function Message({ message, isStreaming = false, theme }) {
|
|
|
416
416
|
children: message.content
|
|
417
417
|
})
|
|
418
418
|
});
|
|
419
|
+
if (isUser) {
|
|
420
|
+
// v8 ignore start
|
|
421
|
+
const attachmentPrefix = (message.images ?? []).map((path) => `[${path.split(/[\\/]/).at(-1) ?? path}]`).join(" ");
|
|
422
|
+
// v8 ignore stop
|
|
423
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
424
|
+
flexDirection: "column",
|
|
425
|
+
marginBottom: 1,
|
|
426
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
427
|
+
color: messageColor,
|
|
428
|
+
children: [
|
|
429
|
+
"> ",
|
|
430
|
+
attachmentPrefix ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
|
|
431
|
+
color: theme.colors.accent,
|
|
432
|
+
children: attachmentPrefix
|
|
433
|
+
}), message.content ? " " : ""] }) : null,
|
|
434
|
+
message.content
|
|
435
|
+
]
|
|
436
|
+
})
|
|
437
|
+
});
|
|
438
|
+
}
|
|
419
439
|
const segments = parseContent(message.content);
|
|
420
440
|
const availableWidth = getAssistantContentWidth(stdout.columns);
|
|
421
441
|
if (stickyHeightRef.current.columns !== stdout.columns) stickyHeightRef.current = {
|
|
@@ -436,11 +456,7 @@ function Message({ message, isStreaming = false, theme }) {
|
|
|
436
456
|
flexDirection: "column",
|
|
437
457
|
marginBottom: 1,
|
|
438
458
|
children: [segments.map((segment, index) => {
|
|
439
|
-
|
|
440
|
-
if (segment.type === "code") return isUser ? /* @__PURE__ */ jsx(Text, {
|
|
441
|
-
color: messageColor,
|
|
442
|
-
children: segment.content
|
|
443
|
-
}, index) : /* @__PURE__ */ jsx(Box, {
|
|
459
|
+
if (segment.type === "code") return /* @__PURE__ */ jsx(Box, {
|
|
444
460
|
marginX: 2,
|
|
445
461
|
children: /* @__PURE__ */ jsx(CodeBlock, {
|
|
446
462
|
code: segment.content,
|
|
@@ -461,17 +477,13 @@ function Message({ message, isStreaming = false, theme }) {
|
|
|
461
477
|
})
|
|
462
478
|
}, index);
|
|
463
479
|
}
|
|
464
|
-
|
|
465
|
-
type: "markdown",
|
|
466
|
-
content: segment.content
|
|
467
|
-
}];
|
|
468
|
-
return isUser ? /* @__PURE__ */ jsx(Text, {
|
|
469
|
-
color: messageColor,
|
|
470
|
-
children: prefix + segment.content
|
|
471
|
-
}, index) : /* @__PURE__ */ jsx(Box, {
|
|
480
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
472
481
|
flexDirection: "column",
|
|
473
482
|
marginX: 2,
|
|
474
|
-
children:
|
|
483
|
+
children: [{
|
|
484
|
+
type: "markdown",
|
|
485
|
+
content: segment.content
|
|
486
|
+
}].map((part, partIndex) => /* @__PURE__ */ jsx(Markdown, {
|
|
475
487
|
content: part.content,
|
|
476
488
|
theme
|
|
477
489
|
}, partIndex))
|
|
@@ -509,20 +521,38 @@ function Messages({ messages, isLoading, sessionId, streamingMessage, theme = ge
|
|
|
509
521
|
//#endregion
|
|
510
522
|
//#region src/components/SelectPrompt/SelectPrompt.tsx
|
|
511
523
|
function SelectPrompt({ borderStyle, children, onCancel, ...selectProps }) {
|
|
524
|
+
const [isInteractive, setIsInteractive] = useState(false);
|
|
525
|
+
useEffect(() => {
|
|
526
|
+
let isMounted = true;
|
|
527
|
+
tick().then(() => {
|
|
528
|
+
// v8 ignore next
|
|
529
|
+
if (isMounted) setIsInteractive(true);
|
|
530
|
+
});
|
|
531
|
+
return () => {
|
|
532
|
+
isMounted = false;
|
|
533
|
+
};
|
|
534
|
+
}, []);
|
|
512
535
|
useInput((input, key) => {
|
|
513
536
|
if (key.escape || key.ctrl && input === "c") onCancel?.();
|
|
514
537
|
});
|
|
515
538
|
return /* @__PURE__ */ jsxs(Box, {
|
|
516
539
|
borderStyle,
|
|
517
540
|
flexDirection: "column",
|
|
518
|
-
children: [children, /* @__PURE__ */ jsx(Select, {
|
|
541
|
+
children: [children, /* @__PURE__ */ jsx(Select, {
|
|
542
|
+
...selectProps,
|
|
543
|
+
isDisabled: selectProps.isDisabled ?? !isInteractive
|
|
544
|
+
})]
|
|
519
545
|
});
|
|
520
546
|
}
|
|
521
547
|
//#endregion
|
|
522
548
|
//#region src/components/SelectPrompt/SelectPromptHint.tsx
|
|
549
|
+
/**
|
|
550
|
+
* Select prompt hint component that displays:
|
|
551
|
+
* Select option (↑↓ + Enter to confirm, Esc/Ctrl+C to cancel)
|
|
552
|
+
*/
|
|
523
553
|
function SelectPromptHint({ message = "Select option", escapeLabel = "cancel" }) {
|
|
524
|
-
return /* @__PURE__ */ jsxs(
|
|
525
|
-
|
|
554
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
555
|
+
color: getTheme().colors.secondary,
|
|
526
556
|
children: [
|
|
527
557
|
/* @__PURE__ */ jsxs(Text, {
|
|
528
558
|
dimColor: true,
|
|
@@ -548,6 +578,14 @@ function SelectPromptHint({ message = "Select option", escapeLabel = "cancel" })
|
|
|
548
578
|
bold: true,
|
|
549
579
|
children: "Esc"
|
|
550
580
|
}),
|
|
581
|
+
/* @__PURE__ */ jsx(Text, {
|
|
582
|
+
dimColor: true,
|
|
583
|
+
children: "/"
|
|
584
|
+
}),
|
|
585
|
+
/* @__PURE__ */ jsx(Text, {
|
|
586
|
+
bold: true,
|
|
587
|
+
children: "Ctrl+C"
|
|
588
|
+
}),
|
|
551
589
|
/* @__PURE__ */ jsxs(Text, {
|
|
552
590
|
dimColor: true,
|
|
553
591
|
children: [
|
|
@@ -560,7 +598,7 @@ function SelectPromptHint({ message = "Select option", escapeLabel = "cancel" })
|
|
|
560
598
|
});
|
|
561
599
|
}
|
|
562
600
|
//#endregion
|
|
563
|
-
//#region src/components/PlanApproval.tsx
|
|
601
|
+
//#region src/components/PlanApproval/PlanApproval.tsx
|
|
564
602
|
var options$1 = [
|
|
565
603
|
{
|
|
566
604
|
label: "Auto - Execute tools automatically",
|
|
@@ -606,7 +644,7 @@ function PlanApproval({ planContent, onModeChange, theme = getTheme() }) {
|
|
|
606
644
|
});
|
|
607
645
|
}
|
|
608
646
|
//#endregion
|
|
609
|
-
//#region src/components/ToolApproval.tsx
|
|
647
|
+
//#region src/components/ToolApproval/ToolApproval.tsx
|
|
610
648
|
var options = [{
|
|
611
649
|
label: "Approve tool call",
|
|
612
650
|
value: APPROVE
|
|
@@ -766,6 +804,7 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
|
|
|
766
804
|
setCursorPosition(value.length);
|
|
767
805
|
return;
|
|
768
806
|
}
|
|
807
|
+
if (key.ctrl) return;
|
|
769
808
|
// v8 ignore start
|
|
770
809
|
if (input) {
|
|
771
810
|
onChange(value.slice(0, cursorPosition) + input + value.slice(cursorPosition));
|
|
@@ -802,6 +841,74 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
|
|
|
802
841
|
});
|
|
803
842
|
}
|
|
804
843
|
//#endregion
|
|
844
|
+
//#region src/components/Chat/attachments.ts
|
|
845
|
+
var IMAGE_EXTENSIONS = new Set([
|
|
846
|
+
".avif",
|
|
847
|
+
".bmp",
|
|
848
|
+
".gif",
|
|
849
|
+
".heic",
|
|
850
|
+
".heif",
|
|
851
|
+
".jpeg",
|
|
852
|
+
".jpg",
|
|
853
|
+
".png",
|
|
854
|
+
".tif",
|
|
855
|
+
".tiff",
|
|
856
|
+
".webp"
|
|
857
|
+
]);
|
|
858
|
+
var PATH_CANDIDATE_PATTERN = /"([^"\n\r]+\.(?:avif|bmp|gif|heic|heif|jpeg|jpg|png|tif|tiff|webp))"|'([^'\n\r]+\.(?:avif|bmp|gif|heic|heif|jpeg|jpg|png|tif|tiff|webp))'|([^\s"'`]+\.(?:avif|bmp|gif|heic|heif|jpeg|jpg|png|tif|tiff|webp))/gi;
|
|
859
|
+
function normalizeCandidatePath(value) {
|
|
860
|
+
return value.replaceAll(String.raw`\ `, " ");
|
|
861
|
+
}
|
|
862
|
+
function isPathLikeCandidate(candidate, matchedValue) {
|
|
863
|
+
return matchedValue.startsWith("\"") || matchedValue.startsWith("'") || candidate.includes("/") || candidate.includes("\\") || candidate.startsWith(".");
|
|
864
|
+
}
|
|
865
|
+
function getAttachmentLabel(path) {
|
|
866
|
+
return basename(path);
|
|
867
|
+
}
|
|
868
|
+
function isReadableImagePath(path) {
|
|
869
|
+
const normalizedPath = normalizeCandidatePath(path);
|
|
870
|
+
const extension = extname(normalizedPath).toLowerCase();
|
|
871
|
+
if (!IMAGE_EXTENSIONS.has(extension)) return false;
|
|
872
|
+
const resolvedPath = isAbsolute(normalizedPath) ? normalizedPath : resolve(normalizedPath);
|
|
873
|
+
if (!existsSync(resolvedPath)) return false;
|
|
874
|
+
try {
|
|
875
|
+
return statSync(resolvedPath).isFile();
|
|
876
|
+
} catch {
|
|
877
|
+
// v8 ignore next
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function resolveAttachmentPath(path) {
|
|
882
|
+
const normalizedPath = normalizeCandidatePath(path);
|
|
883
|
+
return isAbsolute(normalizedPath) ? normalizedPath : resolve(normalizedPath);
|
|
884
|
+
}
|
|
885
|
+
function extractImageAttachments(input) {
|
|
886
|
+
const attachments = [];
|
|
887
|
+
const segments = [];
|
|
888
|
+
let lastIndex = 0;
|
|
889
|
+
for (const match of input.matchAll(PATH_CANDIDATE_PATTERN)) {
|
|
890
|
+
const matchedValue = match[0];
|
|
891
|
+
const candidate = match.slice(1).find((value) => Boolean(value));
|
|
892
|
+
// v8 ignore start
|
|
893
|
+
if (candidate === void 0) continue;
|
|
894
|
+
// v8 ignore stop
|
|
895
|
+
if (!isPathLikeCandidate(candidate, matchedValue)) continue;
|
|
896
|
+
if (!isReadableImagePath(candidate)) continue;
|
|
897
|
+
attachments.push(resolveAttachmentPath(candidate));
|
|
898
|
+
segments.push(input.slice(lastIndex, match.index));
|
|
899
|
+
lastIndex = match.index + matchedValue.length;
|
|
900
|
+
}
|
|
901
|
+
if (!attachments.length) return {
|
|
902
|
+
attachments,
|
|
903
|
+
remainingInput: input
|
|
904
|
+
};
|
|
905
|
+
segments.push(input.slice(lastIndex));
|
|
906
|
+
return {
|
|
907
|
+
attachments,
|
|
908
|
+
remainingInput: segments.join("").replaceAll(/\s{2,}/g, " ").trim()
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
//#endregion
|
|
805
912
|
//#region src/components/Chat/CommandMenu.tsx
|
|
806
913
|
function getMatchingCommands(input) {
|
|
807
914
|
const normalizedInput = input.trim().toLowerCase();
|
|
@@ -821,7 +928,7 @@ function CommandMenu({ input, onSubmit }) {
|
|
|
821
928
|
});
|
|
822
929
|
}
|
|
823
930
|
//#endregion
|
|
824
|
-
//#region src/components/Suggestions.tsx
|
|
931
|
+
//#region src/components/Suggestions/Suggestions.tsx
|
|
825
932
|
var DEFAULT_MAX_VISIBLE_OPTIONS = 5;
|
|
826
933
|
function Suggestions({ options, isDisabled = false, maxVisibleOptions = DEFAULT_MAX_VISIBLE_OPTIONS, resetKey, onHighlight, onSelect }) {
|
|
827
934
|
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
@@ -992,28 +1099,81 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
|
|
|
992
1099
|
function hasFileSuggestionQuery(input) {
|
|
993
1100
|
return /(^|.)@\S+/.test(input);
|
|
994
1101
|
}
|
|
995
|
-
function
|
|
1102
|
+
function toAttachment(path, index, isTemp = false) {
|
|
1103
|
+
return {
|
|
1104
|
+
id: `${path}-${String(index)}`,
|
|
1105
|
+
isTemp,
|
|
1106
|
+
label: getAttachmentLabel(path),
|
|
1107
|
+
path
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function cleanupAttachments(attachments) {
|
|
1111
|
+
for (const attachment of attachments) if (attachment.isTemp) removeClipboardImage(attachment.path);
|
|
1112
|
+
}
|
|
1113
|
+
function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, onSubmit, theme = getTheme() }) {
|
|
996
1114
|
const { exit } = useApp();
|
|
997
1115
|
const [history, setHistory] = useState(sessionHistory);
|
|
998
1116
|
const [historyIndex, setHistoryIndex] = useState(null);
|
|
999
1117
|
const [input, setInput] = useState("");
|
|
1000
1118
|
const [cursorPosition, setCursorPosition] = useState(void 0);
|
|
1119
|
+
const [attachments, setAttachments] = useState([]);
|
|
1120
|
+
const [error, setError] = useState(null);
|
|
1001
1121
|
const fileSuggestionRef = useRef(null);
|
|
1122
|
+
const nextClipboardImageRef = useRef(1);
|
|
1123
|
+
const hasAttachments = attachments.length > 0;
|
|
1002
1124
|
useEffect(() => {
|
|
1003
1125
|
setHistory(sessionHistory);
|
|
1004
1126
|
setHistoryIndex(null);
|
|
1005
1127
|
setInput("");
|
|
1006
1128
|
setCursorPosition(void 0);
|
|
1129
|
+
setError(null);
|
|
1007
1130
|
fileSuggestionRef.current = null;
|
|
1131
|
+
nextClipboardImageRef.current = 1;
|
|
1132
|
+
setAttachments((previousAttachments) => {
|
|
1133
|
+
cleanupAttachments(previousAttachments);
|
|
1134
|
+
return [];
|
|
1135
|
+
});
|
|
1008
1136
|
}, [sessionHistory]);
|
|
1009
|
-
const resetInput = useCallback(() => {
|
|
1137
|
+
const resetInput = useCallback((deleteTempAttachments = false) => {
|
|
1010
1138
|
setInput("");
|
|
1011
1139
|
setCursorPosition(void 0);
|
|
1012
1140
|
setHistoryIndex(null);
|
|
1141
|
+
setError(null);
|
|
1142
|
+
if (deleteTempAttachments) {
|
|
1143
|
+
setAttachments((previousAttachments) => {
|
|
1144
|
+
cleanupAttachments(previousAttachments);
|
|
1145
|
+
return [];
|
|
1146
|
+
});
|
|
1147
|
+
nextClipboardImageRef.current = 1;
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
setAttachments([]);
|
|
1151
|
+
}, []);
|
|
1152
|
+
const removeLastAttachment = useCallback(() => {
|
|
1153
|
+
setAttachments((previousAttachments) => {
|
|
1154
|
+
const removedAttachment = previousAttachments.at(-1);
|
|
1155
|
+
if (removedAttachment?.isTemp) removeClipboardImage(removedAttachment.path);
|
|
1156
|
+
return previousAttachments.slice(0, -1);
|
|
1157
|
+
});
|
|
1158
|
+
setError(null);
|
|
1013
1159
|
}, []);
|
|
1160
|
+
const stageAttachments = useCallback((paths, isTemp = false) => {
|
|
1161
|
+
setAttachments((previousAttachments) => [...previousAttachments, ...paths.map((path, index) => toAttachment(path, previousAttachments.length + index, isTemp))]);
|
|
1162
|
+
setError(null);
|
|
1163
|
+
}, []);
|
|
1164
|
+
const attachClipboardImage = useCallback(() => {
|
|
1165
|
+
try {
|
|
1166
|
+
const path = saveClipboardImage(`image-${String(nextClipboardImageRef.current)}`);
|
|
1167
|
+
nextClipboardImageRef.current += 1;
|
|
1168
|
+
stageAttachments([path], true);
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
1171
|
+
}
|
|
1172
|
+
}, [stageAttachments]);
|
|
1014
1173
|
const handleSelectFileSuggestion = useCallback((nextInput) => {
|
|
1015
1174
|
setInput(nextInput.value);
|
|
1016
1175
|
setCursorPosition(nextInput.cursorPosition);
|
|
1176
|
+
setError(null);
|
|
1017
1177
|
}, []);
|
|
1018
1178
|
const handleFileSuggestionChange = useCallback((nextInput) => {
|
|
1019
1179
|
if (nextInput) {
|
|
@@ -1034,21 +1194,40 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
|
|
|
1034
1194
|
} else fileSuggestionRef.current = null;
|
|
1035
1195
|
}, [input]);
|
|
1036
1196
|
const handleInputChange = useCallback((nextInput) => {
|
|
1197
|
+
if (nextInput.length - input.length > 1) {
|
|
1198
|
+
const { attachments: nextAttachments, remainingInput } = extractImageAttachments(nextInput);
|
|
1199
|
+
if (nextAttachments.length) {
|
|
1200
|
+
stageAttachments(nextAttachments);
|
|
1201
|
+
setInput(remainingInput);
|
|
1202
|
+
setCursorPosition(remainingInput.length);
|
|
1203
|
+
setHistoryIndex(null);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1037
1207
|
setInput(nextInput);
|
|
1038
1208
|
setHistoryIndex(null);
|
|
1039
|
-
|
|
1209
|
+
setError(null);
|
|
1210
|
+
}, [input, stageAttachments]);
|
|
1040
1211
|
const submitAndReset = useCallback((input) => {
|
|
1041
1212
|
const trimmedInput = input.trim();
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1213
|
+
const imagePaths = attachments.map(({ path }) => path);
|
|
1214
|
+
if (!trimmedInput && !imagePaths.length) return;
|
|
1215
|
+
onSubmit({
|
|
1216
|
+
content: trimmedInput,
|
|
1217
|
+
...imagePaths.length ? { images: imagePaths } : {}
|
|
1218
|
+
});
|
|
1219
|
+
if (trimmedInput && !trimmedInput.startsWith("/")) setHistory((previousHistory) => [...previousHistory, trimmedInput]);
|
|
1220
|
+
resetInput(trimmedInput.startsWith("/"));
|
|
1046
1221
|
fileSuggestionRef.current = null;
|
|
1047
|
-
}, [
|
|
1222
|
+
}, [
|
|
1223
|
+
attachments,
|
|
1224
|
+
onSubmit,
|
|
1225
|
+
resetInput
|
|
1226
|
+
]);
|
|
1048
1227
|
const showCommandMenu = input.startsWith("/");
|
|
1049
1228
|
const showFileSuggestions = !showCommandMenu && hasFileSuggestionQuery(input);
|
|
1050
1229
|
const handleHistoryNavigation = useCallback((direction) => {
|
|
1051
|
-
if (!history.length || showFileSuggestions) return;
|
|
1230
|
+
if (!history.length || showFileSuggestions || hasAttachments) return;
|
|
1052
1231
|
if (direction === "up") {
|
|
1053
1232
|
if (historyIndex === null) {
|
|
1054
1233
|
if (input) return;
|
|
@@ -1080,21 +1259,22 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
|
|
|
1080
1259
|
setInput(nextInput);
|
|
1081
1260
|
setCursorPosition(nextInput.length);
|
|
1082
1261
|
}, [
|
|
1262
|
+
hasAttachments,
|
|
1083
1263
|
history,
|
|
1084
1264
|
historyIndex,
|
|
1085
1265
|
input,
|
|
1086
1266
|
showFileSuggestions
|
|
1087
1267
|
]);
|
|
1088
|
-
const handleSubmitText = useCallback((
|
|
1089
|
-
if (
|
|
1090
|
-
if (hasFileSuggestionQuery(
|
|
1268
|
+
const handleSubmitText = useCallback((value) => {
|
|
1269
|
+
if (value.startsWith("/")) return;
|
|
1270
|
+
if (hasFileSuggestionQuery(value)) {
|
|
1091
1271
|
if (fileSuggestionRef.current) handleSelectFileSuggestion(fileSuggestionRef.current);
|
|
1092
1272
|
return;
|
|
1093
1273
|
}
|
|
1094
|
-
submitAndReset(
|
|
1274
|
+
submitAndReset(value);
|
|
1095
1275
|
}, [handleSelectFileSuggestion, submitAndReset]);
|
|
1096
|
-
const handleSubmitCommand = useCallback((
|
|
1097
|
-
if (LIST.find(({ name }) => name ===
|
|
1276
|
+
const handleSubmitCommand = useCallback((value) => {
|
|
1277
|
+
if (LIST.find(({ name }) => name === value)) submitAndReset(value);
|
|
1098
1278
|
}, [submitAndReset]);
|
|
1099
1279
|
useInput((inputKey, key) => {
|
|
1100
1280
|
const isCtrlC = key.ctrl && inputKey === "c";
|
|
@@ -1102,6 +1282,14 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
|
|
|
1102
1282
|
if (key.escape || isCtrlC) onInterrupt?.();
|
|
1103
1283
|
return;
|
|
1104
1284
|
}
|
|
1285
|
+
if (key.ctrl && inputKey === "v") {
|
|
1286
|
+
attachClipboardImage();
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if ((key.backspace || key.delete || inputKey === "") && !input) {
|
|
1290
|
+
if (hasAttachments) removeLastAttachment();
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1105
1293
|
if (isCtrlC) {
|
|
1106
1294
|
if (input) {
|
|
1107
1295
|
resetInput();
|
|
@@ -1115,18 +1303,35 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
|
|
|
1115
1303
|
}
|
|
1116
1304
|
if (key.downArrow) handleHistoryNavigation("down");
|
|
1117
1305
|
});
|
|
1306
|
+
const attachmentPrefix = attachments.map(({ label }) => `[${label}]`).join(" ");
|
|
1307
|
+
const wrapIndent = 2 + (attachmentPrefix ? attachmentPrefix.length + 1 : 0);
|
|
1118
1308
|
return /* @__PURE__ */ jsxs(Box, {
|
|
1119
1309
|
flexDirection: "column",
|
|
1120
1310
|
children: [
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1311
|
+
error && /* @__PURE__ */ jsx(Box, {
|
|
1312
|
+
marginBottom: 1,
|
|
1313
|
+
marginX: 2,
|
|
1314
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1315
|
+
color: theme.colors.error,
|
|
1316
|
+
children: error
|
|
1317
|
+
})
|
|
1318
|
+
}),
|
|
1319
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
1320
|
+
/* @__PURE__ */ jsx(Text, { children: "> " }),
|
|
1321
|
+
hasAttachments && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1322
|
+
color: theme.colors.accent,
|
|
1323
|
+
children: attachmentPrefix
|
|
1324
|
+
}), /* @__PURE__ */ jsx(Text, { children: " " })] }),
|
|
1325
|
+
/* @__PURE__ */ jsx(TextInput, {
|
|
1326
|
+
value: input,
|
|
1327
|
+
isDisabled,
|
|
1328
|
+
cursorPosition,
|
|
1329
|
+
wrapIndent,
|
|
1330
|
+
onChange: handleInputChange,
|
|
1331
|
+
onSubmit: handleSubmitText,
|
|
1332
|
+
placeholder: hasAttachments ? void 0 : "Ask anything... (/ commands, @ files, Ctrl+V images)"
|
|
1333
|
+
})
|
|
1334
|
+
] }),
|
|
1130
1335
|
showCommandMenu && /* @__PURE__ */ jsx(CommandMenu, {
|
|
1131
1336
|
input,
|
|
1132
1337
|
onSubmit: handleSubmitCommand
|
|
@@ -1468,10 +1673,10 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1468
1673
|
messages,
|
|
1469
1674
|
processStream
|
|
1470
1675
|
]);
|
|
1471
|
-
const handleSubmit = useCallback(async (
|
|
1676
|
+
const handleSubmit = useCallback(async ({ content, images }) => {
|
|
1472
1677
|
setInterruptReason(null);
|
|
1473
|
-
const userContent =
|
|
1474
|
-
if (!userContent) return;
|
|
1678
|
+
const userContent = content.trim();
|
|
1679
|
+
if (!userContent && !images?.length) return;
|
|
1475
1680
|
if (userContent.startsWith("/")) {
|
|
1476
1681
|
onCommand(userContent);
|
|
1477
1682
|
return;
|
|
@@ -1479,7 +1684,8 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1479
1684
|
setIsLoading(true);
|
|
1480
1685
|
const userMessage = {
|
|
1481
1686
|
role: USER,
|
|
1482
|
-
content: userContent
|
|
1687
|
+
content: userContent,
|
|
1688
|
+
...images?.length ? { images } : {}
|
|
1483
1689
|
};
|
|
1484
1690
|
const updatedMessages = [...messages, userMessage];
|
|
1485
1691
|
setMessages(updatedMessages);
|
|
@@ -1525,14 +1731,15 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1525
1731
|
history,
|
|
1526
1732
|
isDisabled: isLoading,
|
|
1527
1733
|
onInterrupt: handleInterrupt,
|
|
1528
|
-
onSubmit: handleSubmit
|
|
1734
|
+
onSubmit: handleSubmit,
|
|
1735
|
+
theme
|
|
1529
1736
|
})
|
|
1530
1737
|
})
|
|
1531
1738
|
]
|
|
1532
1739
|
});
|
|
1533
1740
|
}
|
|
1534
1741
|
//#endregion
|
|
1535
|
-
//#region src/components/Footer.tsx
|
|
1742
|
+
//#region src/components/Footer/Footer.tsx
|
|
1536
1743
|
function getModeColor(mode, theme) {
|
|
1537
1744
|
switch (mode) {
|
|
1538
1745
|
case PLAN: return theme.colors.modePlan;
|
|
@@ -1574,7 +1781,7 @@ function Footer({ mode, model, onToggleMode, theme = getTheme() }) {
|
|
|
1574
1781
|
});
|
|
1575
1782
|
}
|
|
1576
1783
|
//#endregion
|
|
1577
|
-
//#region src/components/Header.tsx
|
|
1784
|
+
//#region src/components/Header/Header.tsx
|
|
1578
1785
|
function abbreviatePath(dir) {
|
|
1579
1786
|
const home = homedir();
|
|
1580
1787
|
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
@@ -1813,11 +2020,15 @@ function ModelCustomDownloadView({ downloadDraft, notice, theme, onDraftChange,
|
|
|
1813
2020
|
onSelect: onSelectSuggestion
|
|
1814
2021
|
}),
|
|
1815
2022
|
renderNotice(),
|
|
1816
|
-
/* @__PURE__ */
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2023
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2024
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2025
|
+
color: theme.colors.secondary,
|
|
2026
|
+
dimColor: true,
|
|
2027
|
+
children: "Press Enter to download."
|
|
2028
|
+
}),
|
|
2029
|
+
" ",
|
|
2030
|
+
/* @__PURE__ */ jsx(ExitHint, {})
|
|
2031
|
+
] })
|
|
1821
2032
|
]
|
|
1822
2033
|
});
|
|
1823
2034
|
}
|
|
@@ -1858,6 +2069,7 @@ function ModelDeleteConfirmView({ deleteCandidate, isDeleting, notice, theme, on
|
|
|
1858
2069
|
//#endregion
|
|
1859
2070
|
//#region src/components/ModelManager/ModelDeleteView.tsx
|
|
1860
2071
|
function ModelDeleteView({ currentModel, installedModels, isLoading, notice, theme, onCancel, onSelect }) {
|
|
2072
|
+
// v8 ignore next
|
|
1861
2073
|
if (isLoading) return /* @__PURE__ */ jsx(Spinner, { label: "Loading models..." });
|
|
1862
2074
|
return /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
1863
2075
|
options: [...buildInstalledModelOptions(installedModels.filter((model) => model !== currentModel), currentModel), BACK],
|
|
@@ -2017,6 +2229,10 @@ function ModelManager({ currentModel, onSelect, onClose, theme = getTheme() }) {
|
|
|
2017
2229
|
useInput((input, key) => {
|
|
2018
2230
|
const isEscape = key.escape || input === "\x1B\x1B";
|
|
2019
2231
|
const isCtrlC = key.ctrl && input === "c" || input === "";
|
|
2232
|
+
if (loadError && view !== View.Menu && (isEscape || isCtrlC)) {
|
|
2233
|
+
handleBackToMenu();
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2020
2236
|
if (view === View.CustomDownload && (isEscape || isCtrlC)) {
|
|
2021
2237
|
setNotice(null);
|
|
2022
2238
|
setHighlightedSuggestion(null);
|
|
@@ -2167,17 +2383,10 @@ function ModelManager({ currentModel, onSelect, onClose, theme = getTheme() }) {
|
|
|
2167
2383
|
color: notice.tone === "error" ? theme.colors.error : notice.tone === "success" ? theme.colors.status : theme.colors.secondary,
|
|
2168
2384
|
children: notice.text
|
|
2169
2385
|
}) : null;
|
|
2170
|
-
if (loadError && view !== View.Menu) return /* @__PURE__ */ jsxs(
|
|
2171
|
-
|
|
2172
|
-
children: [
|
|
2173
|
-
|
|
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
|
-
})]
|
|
2180
|
-
});
|
|
2386
|
+
if (loadError && view !== View.Menu) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2387
|
+
color: theme.colors.error,
|
|
2388
|
+
children: ["Error loading models: ", loadError]
|
|
2389
|
+
}), /* @__PURE__ */ jsx(ExitHint, {})] });
|
|
2181
2390
|
if (view === View.Downloading && downloadProgress) return /* @__PURE__ */ jsx(ModelDownloadingView, {
|
|
2182
2391
|
progress: downloadProgress,
|
|
2183
2392
|
theme,
|
|
@@ -2243,7 +2452,7 @@ function ModelManager({ currentModel, onSelect, onClose, theme = getTheme() }) {
|
|
|
2243
2452
|
});
|
|
2244
2453
|
}
|
|
2245
2454
|
//#endregion
|
|
2246
|
-
//#region src/components/SearchSettings.tsx
|
|
2455
|
+
//#region src/components/SearchSettings/SearchSettings.tsx
|
|
2247
2456
|
function SearchSettings({ currentUrl, onClose, onSave, theme = getTheme() }) {
|
|
2248
2457
|
const [view, setView] = useState("menu");
|
|
2249
2458
|
const [draftUrl, setDraftUrl] = useState(currentUrl ?? "");
|
|
@@ -2319,11 +2528,15 @@ function SearchSettings({ currentUrl, onClose, onSave, theme = getTheme() }) {
|
|
|
2319
2528
|
color: theme.colors.error,
|
|
2320
2529
|
children: error
|
|
2321
2530
|
}),
|
|
2322
|
-
/* @__PURE__ */
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2531
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2532
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2533
|
+
color: theme.colors.secondary,
|
|
2534
|
+
dimColor: true,
|
|
2535
|
+
children: "Press Enter to save."
|
|
2536
|
+
}),
|
|
2537
|
+
" ",
|
|
2538
|
+
/* @__PURE__ */ jsx(ExitHint, {})
|
|
2539
|
+
] })
|
|
2327
2540
|
]
|
|
2328
2541
|
});
|
|
2329
2542
|
return /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
@@ -2345,7 +2558,7 @@ function SearchSettings({ currentUrl, onClose, onSave, theme = getTheme() }) {
|
|
|
2345
2558
|
});
|
|
2346
2559
|
}
|
|
2347
2560
|
//#endregion
|
|
2348
|
-
//#region src/components/SessionManager.tsx
|
|
2561
|
+
//#region src/components/SessionManager/SessionManager.tsx
|
|
2349
2562
|
var ACTION = {
|
|
2350
2563
|
CLOSE: "close",
|
|
2351
2564
|
DELETE_MENU: "delete-menu",
|
|
@@ -2355,6 +2568,24 @@ var ACTION = {
|
|
|
2355
2568
|
OPEN_PREFIX: "open:"
|
|
2356
2569
|
};
|
|
2357
2570
|
var SESSION_LABEL_PADDING = 4;
|
|
2571
|
+
var MAIN_OPTIONS = [
|
|
2572
|
+
{
|
|
2573
|
+
label: "New session",
|
|
2574
|
+
value: ACTION.NEW
|
|
2575
|
+
},
|
|
2576
|
+
{
|
|
2577
|
+
label: "Open session",
|
|
2578
|
+
value: ACTION.OPEN_MENU
|
|
2579
|
+
},
|
|
2580
|
+
{
|
|
2581
|
+
label: "Delete session",
|
|
2582
|
+
value: ACTION.DELETE_MENU
|
|
2583
|
+
},
|
|
2584
|
+
{
|
|
2585
|
+
label: "Close",
|
|
2586
|
+
value: ACTION.CLOSE
|
|
2587
|
+
}
|
|
2588
|
+
];
|
|
2358
2589
|
function truncate(value, maxLength) {
|
|
2359
2590
|
return value.length > maxLength ? `${value.slice(0, maxLength - 1).trimEnd()}…` : value;
|
|
2360
2591
|
}
|
|
@@ -2367,34 +2598,29 @@ function formatSessionLabel(session, maxWidth, prefix = "") {
|
|
|
2367
2598
|
function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen, theme = getTheme() }) {
|
|
2368
2599
|
const [view, setView] = useState("main");
|
|
2369
2600
|
const [error, setError] = useState();
|
|
2370
|
-
const [, refreshSessionList] = useState(0);
|
|
2601
|
+
const [sessionListVersion, refreshSessionList] = useState(0);
|
|
2371
2602
|
const { stdout } = useStdout();
|
|
2372
2603
|
const sessions = listSessions();
|
|
2373
2604
|
const maxLabelWidth = Math.max(1, stdout.columns - SESSION_LABEL_PADDING);
|
|
2374
|
-
const options =
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
{
|
|
2386
|
-
label: "Open session",
|
|
2387
|
-
value: ACTION.OPEN_MENU
|
|
2388
|
-
},
|
|
2389
|
-
{
|
|
2390
|
-
label: "Delete session",
|
|
2391
|
-
value: ACTION.DELETE_MENU
|
|
2392
|
-
},
|
|
2393
|
-
{
|
|
2394
|
-
label: "Close",
|
|
2395
|
-
value: ACTION.CLOSE
|
|
2605
|
+
const options = useMemo(() => {
|
|
2606
|
+
switch (view) {
|
|
2607
|
+
case "open": return [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
2608
|
+
label: formatSessionLabel(session, maxLabelWidth),
|
|
2609
|
+
value: `${ACTION.OPEN_PREFIX}${session.id}`
|
|
2610
|
+
})), BACK];
|
|
2611
|
+
case "delete": return [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
2612
|
+
label: formatSessionLabel(session, maxLabelWidth, "Delete "),
|
|
2613
|
+
value: `${ACTION.DELETE_PREFIX}${session.id}`
|
|
2614
|
+
})), BACK];
|
|
2615
|
+
default: return MAIN_OPTIONS;
|
|
2396
2616
|
}
|
|
2397
|
-
|
|
2617
|
+
}, [
|
|
2618
|
+
currentSessionId,
|
|
2619
|
+
maxLabelWidth,
|
|
2620
|
+
sessionListVersion,
|
|
2621
|
+
sessions,
|
|
2622
|
+
view
|
|
2623
|
+
]);
|
|
2398
2624
|
const handleChange = useCallback((value) => {
|
|
2399
2625
|
switch (true) {
|
|
2400
2626
|
case value === ACTION.CLOSE:
|
|
@@ -2452,12 +2678,12 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen, th
|
|
|
2452
2678
|
options,
|
|
2453
2679
|
onCancel: onClose,
|
|
2454
2680
|
onChange: handleChange
|
|
2455
|
-
}
|
|
2681
|
+
})
|
|
2456
2682
|
]
|
|
2457
2683
|
});
|
|
2458
2684
|
}
|
|
2459
2685
|
//#endregion
|
|
2460
|
-
//#region src/components/ThemeSettings.tsx
|
|
2686
|
+
//#region src/components/ThemeSettings/ThemeSettings.tsx
|
|
2461
2687
|
function ThemeSettings({ currentTheme, onClose, onPreview, onSave }) {
|
|
2462
2688
|
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
2463
2689
|
const initialIndex = LIST$1.findIndex(({ id }) => id === currentTheme);
|
|
@@ -2701,11 +2927,13 @@ var ReadinessState = /* @__PURE__ */ function(ReadinessState) {
|
|
|
2701
2927
|
ReadinessState["Ready"] = "ready";
|
|
2702
2928
|
ReadinessState["MissingModelConfig"] = "missing-model-config";
|
|
2703
2929
|
ReadinessState["NoInstalledModels"] = "no-installed-models";
|
|
2930
|
+
ReadinessState["ServerUnavailable"] = "server-unavailable";
|
|
2704
2931
|
ReadinessState["ModelLoadError"] = "model-load-error";
|
|
2705
2932
|
return ReadinessState;
|
|
2706
2933
|
}({});
|
|
2707
2934
|
function getTitle(setupState) {
|
|
2708
2935
|
switch (setupState) {
|
|
2936
|
+
case "server-unavailable": return "Ollama Server Unavailable";
|
|
2709
2937
|
case "model-load-error": return "Connection Error";
|
|
2710
2938
|
case "missing-model-config": return "No Model Configured";
|
|
2711
2939
|
case "no-installed-models": return "No Model Installed";
|
|
@@ -2714,7 +2942,7 @@ function getTitle(setupState) {
|
|
|
2714
2942
|
function getMessage(setupState, errorMessage) {
|
|
2715
2943
|
const theme = getTheme();
|
|
2716
2944
|
switch (setupState) {
|
|
2717
|
-
case "checking": return /* @__PURE__ */ jsx(Text, { children: "Checking model setup..." });
|
|
2945
|
+
case "checking": return /* @__PURE__ */ jsx(Text, { children: "Checking Ollama server and model setup..." });
|
|
2718
2946
|
case "missing-model-config": return /* @__PURE__ */ jsxs(Text, { children: [
|
|
2719
2947
|
"Select or download a model with",
|
|
2720
2948
|
" ",
|
|
@@ -2727,6 +2955,15 @@ function getMessage(setupState, errorMessage) {
|
|
|
2727
2955
|
color: theme.colors.command,
|
|
2728
2956
|
children: "/model"
|
|
2729
2957
|
})] });
|
|
2958
|
+
case "server-unavailable": return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, { children: "Ollama server is not running or unreachable." }), /* @__PURE__ */ jsxs(Text, { children: [
|
|
2959
|
+
"Start it with ",
|
|
2960
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2961
|
+
color: theme.colors.command,
|
|
2962
|
+
children: "ollama serve"
|
|
2963
|
+
}),
|
|
2964
|
+
" ",
|
|
2965
|
+
"and restart the app"
|
|
2966
|
+
] })] });
|
|
2730
2967
|
case "model-load-error": return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
2731
2968
|
"Error loading models",
|
|
2732
2969
|
errorMessage ? `: ${errorMessage}` : "",
|
|
@@ -2756,7 +2993,10 @@ function ReadinessCheck({ errorMessage, onCommand, setupState, theme = getTheme(
|
|
|
2756
2993
|
}), getMessage(setupState, errorMessage)]
|
|
2757
2994
|
}), /* @__PURE__ */ jsx(ChatInput, {
|
|
2758
2995
|
history: [],
|
|
2759
|
-
onSubmit:
|
|
2996
|
+
onSubmit: ({ content }) => {
|
|
2997
|
+
onCommand(content);
|
|
2998
|
+
},
|
|
2999
|
+
theme
|
|
2760
3000
|
})]
|
|
2761
3001
|
});
|
|
2762
3002
|
}
|
|
@@ -2792,9 +3032,13 @@ function App({ sessionId }) {
|
|
|
2792
3032
|
setSetupState(ReadinessState.Checking);
|
|
2793
3033
|
}
|
|
2794
3034
|
try {
|
|
2795
|
-
const
|
|
3035
|
+
const isHealthy = await checkHealth();
|
|
2796
3036
|
if (!isMounted) return;
|
|
2797
|
-
|
|
3037
|
+
if (!isHealthy) {
|
|
3038
|
+
setSetupState(ReadinessState.ServerUnavailable);
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
setSetupState((await listModels()).length > 0 ? ReadinessState.Ready : ReadinessState.NoInstalledModels);
|
|
2798
3042
|
} catch (error) {
|
|
2799
3043
|
// v8 ignore start
|
|
2800
3044
|
if (!isMounted) return;
|
|
@@ -2937,6 +3181,43 @@ function App({ sessionId }) {
|
|
|
2937
3181
|
});
|
|
2938
3182
|
}
|
|
2939
3183
|
//#endregion
|
|
3184
|
+
//#region src/components/ExitHint/ExitHint.tsx
|
|
3185
|
+
/**
|
|
3186
|
+
* Exit hint component that displays:
|
|
3187
|
+
* Press Esc/Ctrl+C to go back.
|
|
3188
|
+
*/
|
|
3189
|
+
function ExitHint({ action = "go back" }) {
|
|
3190
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
3191
|
+
color: getTheme().colors.secondary,
|
|
3192
|
+
children: [
|
|
3193
|
+
/* @__PURE__ */ jsx(Text, {
|
|
3194
|
+
dimColor: true,
|
|
3195
|
+
children: "Press "
|
|
3196
|
+
}),
|
|
3197
|
+
/* @__PURE__ */ jsx(Text, {
|
|
3198
|
+
bold: true,
|
|
3199
|
+
children: "Esc"
|
|
3200
|
+
}),
|
|
3201
|
+
/* @__PURE__ */ jsx(Text, {
|
|
3202
|
+
dimColor: true,
|
|
3203
|
+
children: "/"
|
|
3204
|
+
}),
|
|
3205
|
+
/* @__PURE__ */ jsx(Text, {
|
|
3206
|
+
bold: true,
|
|
3207
|
+
children: "Ctrl+C"
|
|
3208
|
+
}),
|
|
3209
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
3210
|
+
dimColor: true,
|
|
3211
|
+
children: [
|
|
3212
|
+
" to ",
|
|
3213
|
+
action,
|
|
3214
|
+
"."
|
|
3215
|
+
]
|
|
3216
|
+
})
|
|
3217
|
+
]
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3220
|
+
//#endregion
|
|
2940
3221
|
//#region src/tui.tsx
|
|
2941
3222
|
function renderApp(sessionId) {
|
|
2942
3223
|
let resetKey = 0;
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import cac from "cac";
|
|
4
|
-
import { homedir } from "node:os";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { exec, execFileSync, spawnSync } from "node:child_process";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
6
8
|
import { Ollama } from "ollama";
|
|
7
9
|
import { v7 } from "uuid";
|
|
8
|
-
import { exec } from "node:child_process";
|
|
9
10
|
import { promisify } from "node:util";
|
|
10
11
|
//#region src/constants/command.ts
|
|
11
12
|
var LIST$1 = [
|
|
@@ -37,7 +38,7 @@ var LIST$1 = [
|
|
|
37
38
|
//#endregion
|
|
38
39
|
//#region package.json
|
|
39
40
|
var name = "code-ollama";
|
|
40
|
-
var version = "0.
|
|
41
|
+
var version = "0.19.0";
|
|
41
42
|
//#endregion
|
|
42
43
|
//#region src/constants/package.ts
|
|
43
44
|
var NAME = name;
|
|
@@ -326,6 +327,114 @@ function withSystemMessage(messages) {
|
|
|
326
327
|
return [systemMessage, ...messages];
|
|
327
328
|
}
|
|
328
329
|
//#endregion
|
|
330
|
+
//#region src/utils/clipboard.ts
|
|
331
|
+
var TEMP_IMAGES_DIRECTORY = join(tmpdir(), "code-ollama", "images");
|
|
332
|
+
var WINDOWS_CLIPBOARD_EXIT_CODE = 11;
|
|
333
|
+
function ensureTempDirectory(directory) {
|
|
334
|
+
mkdirSync(directory, { recursive: true });
|
|
335
|
+
return directory;
|
|
336
|
+
}
|
|
337
|
+
function buildTargetPath(directory, extension) {
|
|
338
|
+
const uniqueName = `${randomUUID()}.${extension}`;
|
|
339
|
+
return join(ensureTempDirectory(directory), uniqueName);
|
|
340
|
+
}
|
|
341
|
+
function readMacClipboardImage(path) {
|
|
342
|
+
execFileSync("osascript", ["-e", `
|
|
343
|
+
set outputPath to POSIX file ${JSON.stringify(path)}
|
|
344
|
+
try
|
|
345
|
+
set clipboardData to the clipboard as «class PNGf»
|
|
346
|
+
on error
|
|
347
|
+
error "Clipboard does not contain an image"
|
|
348
|
+
end try
|
|
349
|
+
set fileHandle to open for access outputPath with write permission
|
|
350
|
+
try
|
|
351
|
+
set eof fileHandle to 0
|
|
352
|
+
write clipboardData to fileHandle
|
|
353
|
+
on error errorMessage
|
|
354
|
+
close access fileHandle
|
|
355
|
+
error errorMessage
|
|
356
|
+
end try
|
|
357
|
+
close access fileHandle
|
|
358
|
+
`], { stdio: "ignore" });
|
|
359
|
+
}
|
|
360
|
+
function getClipboardErrorMessage(error) {
|
|
361
|
+
if (error.message.includes("Clipboard does not contain an image")) return "Clipboard does not contain an image.";
|
|
362
|
+
return "Clipboard image paste failed. Paste an image path instead.";
|
|
363
|
+
}
|
|
364
|
+
function readWindowsClipboardImage(path) {
|
|
365
|
+
execFileSync("powershell", [
|
|
366
|
+
"-NoProfile",
|
|
367
|
+
"-Command",
|
|
368
|
+
`
|
|
369
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
370
|
+
Add-Type -AssemblyName System.Drawing
|
|
371
|
+
$image = [Windows.Forms.Clipboard]::GetImage()
|
|
372
|
+
if ($null -eq $image) { exit ${String(WINDOWS_CLIPBOARD_EXIT_CODE)} }
|
|
373
|
+
$image.Save($args[0], [System.Drawing.Imaging.ImageFormat]::Png)
|
|
374
|
+
`,
|
|
375
|
+
path
|
|
376
|
+
], { stdio: "ignore" });
|
|
377
|
+
}
|
|
378
|
+
function readLinuxClipboardImage(directory) {
|
|
379
|
+
const wlPng = spawnSync("wl-paste", [
|
|
380
|
+
"--no-newline",
|
|
381
|
+
"--type",
|
|
382
|
+
"image/png"
|
|
383
|
+
], { encoding: "buffer" });
|
|
384
|
+
if (wlPng.status === 0 && wlPng.stdout.length > 0) {
|
|
385
|
+
const path = buildTargetPath(directory, "png");
|
|
386
|
+
writeClipboardImageFile(path, wlPng.stdout);
|
|
387
|
+
return path;
|
|
388
|
+
}
|
|
389
|
+
const xclipPng = spawnSync("xclip", [
|
|
390
|
+
"-selection",
|
|
391
|
+
"clipboard",
|
|
392
|
+
"-t",
|
|
393
|
+
"image/png",
|
|
394
|
+
"-o"
|
|
395
|
+
], { encoding: "buffer" });
|
|
396
|
+
if (xclipPng.status === 0 && xclipPng.stdout.length > 0) {
|
|
397
|
+
const path = buildTargetPath(directory, "png");
|
|
398
|
+
writeClipboardImageFile(path, xclipPng.stdout);
|
|
399
|
+
return path;
|
|
400
|
+
}
|
|
401
|
+
throw new Error("Clipboard image paste is unavailable. Paste an image path instead.");
|
|
402
|
+
}
|
|
403
|
+
function writeClipboardImageFile(path, data) {
|
|
404
|
+
writeFileSync(path, data, {
|
|
405
|
+
flag: "wx",
|
|
406
|
+
mode: 384
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function saveClipboardImage(baseName, directory = TEMP_IMAGES_DIRECTORY) {
|
|
410
|
+
try {
|
|
411
|
+
switch (process.platform) {
|
|
412
|
+
case "darwin": {
|
|
413
|
+
const path = buildTargetPath(directory, "png");
|
|
414
|
+
readMacClipboardImage(path);
|
|
415
|
+
return path;
|
|
416
|
+
}
|
|
417
|
+
case "win32": {
|
|
418
|
+
const path = buildTargetPath(directory, "png");
|
|
419
|
+
readWindowsClipboardImage(path);
|
|
420
|
+
return path;
|
|
421
|
+
}
|
|
422
|
+
case "linux": return readLinuxClipboardImage(directory);
|
|
423
|
+
default: throw new Error("Clipboard image paste is not supported on this platform. Paste an image path instead.");
|
|
424
|
+
}
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (error instanceof Error && "status" in error && error.status === WINDOWS_CLIPBOARD_EXIT_CODE) throw new Error("Clipboard does not contain an image.", { cause: error });
|
|
427
|
+
const path = join(directory, `${baseName}.png`);
|
|
428
|
+
if (existsSync(path)) rmSync(path, { force: true });
|
|
429
|
+
if (error instanceof Error) throw new Error(getClipboardErrorMessage(error), { cause: error });
|
|
430
|
+
// v8 ignore next
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function removeClipboardImage(path) {
|
|
435
|
+
if (existsSync(path)) rmSync(path, { force: true });
|
|
436
|
+
}
|
|
437
|
+
//#endregion
|
|
329
438
|
//#region src/utils/config.ts
|
|
330
439
|
var CONFIG_PATH = join(DIRECTORY, "config.json");
|
|
331
440
|
var DEFAULT_HOST = "http://localhost:11434";
|
|
@@ -358,6 +467,13 @@ function saveConfig(patch) {
|
|
|
358
467
|
//#region src/utils/ollama.ts
|
|
359
468
|
var { host } = loadConfig();
|
|
360
469
|
var client = new Ollama({ host });
|
|
470
|
+
async function checkHealth() {
|
|
471
|
+
try {
|
|
472
|
+
return (await fetch(host)).ok;
|
|
473
|
+
} catch {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
361
477
|
async function* streamChat(messages, model, tools, signal) {
|
|
362
478
|
const response = await client.chat({
|
|
363
479
|
model,
|
|
@@ -577,6 +693,9 @@ function writeError(text) {
|
|
|
577
693
|
process.stderr.write(text);
|
|
578
694
|
}
|
|
579
695
|
//#endregion
|
|
696
|
+
//#region src/utils/time.ts
|
|
697
|
+
var tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
698
|
+
//#endregion
|
|
580
699
|
//#region src/utils/tools/definitions.ts
|
|
581
700
|
/**
|
|
582
701
|
* Helper to define tool parameters
|
|
@@ -1123,7 +1242,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1123
1242
|
else await launchTui();
|
|
1124
1243
|
}
|
|
1125
1244
|
async function launchTui(sessionId) {
|
|
1126
|
-
const { renderApp } = await import("./assets/tui-
|
|
1245
|
+
const { renderApp } = await import("./assets/tui-JyjdXasW.js");
|
|
1127
1246
|
reset();
|
|
1128
1247
|
renderApp(sessionId);
|
|
1129
1248
|
}
|
|
@@ -1139,4 +1258,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
1139
1258
|
if (isEntrypoint()) main();
|
|
1140
1259
|
// v8 ignore stop
|
|
1141
1260
|
//#endregion
|
|
1142
|
-
export {
|
|
1261
|
+
export { WARNING as A, LABEL as B, loadConfig as C, resetSystemMessage as D, saveClipboardImage as E, USER as F, VERSION as G, SAFE as H, PLAN_GENERATION_INSTRUCTION as I, LIST$1 as K, BACK as L, getTheme as M, ASSISTANT as N, withSystemMessage as O, SYSTEM as P, CATALOG as R, streamChat as S, removeClipboardImage as T, APPROVE as U, PLAN as V, REJECT as W, setClearHandler as _, tick as a, listModels as b, appendMessage as c, deleteSessionIfEmpty as d, listSessions as f, reset as g, clear as h, WRITE_TOOLS as i, LIST as j, HEADER_PREFIX as k, createSession as l, updateSessionModel as m, main, READ_TOOLS as n, color as o, loadSession as p, TOOLS as r, write as s, executeTool as t, deleteSession as u, checkHealth as v, saveConfig as w, pullModel as x, deleteModel as y, AUTO as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Ollama coding agent that runs in your terminal",
|
|
5
5
|
"author": "Mark <mark@remarkablemark.org> (https://remarkablemark.org)",
|
|
6
6
|
"type": "module",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@inkjs/ui": "2.0.0",
|
|
43
|
-
"@shikijs/cli": "4.0
|
|
43
|
+
"@shikijs/cli": "4.1.0",
|
|
44
44
|
"cac": "7.0.0",
|
|
45
45
|
"ink": "7.0.3",
|
|
46
46
|
"marked": "15.0.12",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@commitlint/cli": "21.0.1",
|
|
54
54
|
"@commitlint/config-conventional": "21.0.1",
|
|
55
55
|
"@eslint/js": "10.0.1",
|
|
56
|
-
"@types/node": "25.
|
|
56
|
+
"@types/node": "25.9.0",
|
|
57
57
|
"@types/react": "19.2.14",
|
|
58
58
|
"@vitest/coverage-v8": "4.1.6",
|
|
59
59
|
"eslint": "10.4.0",
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"lint-staged": "17.0.5",
|
|
66
66
|
"prettier": "3.8.3",
|
|
67
67
|
"publint": "0.3.21",
|
|
68
|
-
"tsx": "4.22.
|
|
68
|
+
"tsx": "4.22.2",
|
|
69
69
|
"typescript": "6.0.3",
|
|
70
|
-
"typescript-eslint": "8.59.
|
|
70
|
+
"typescript-eslint": "8.59.4",
|
|
71
71
|
"vite": "8.0.13",
|
|
72
72
|
"vitest": "4.1.6"
|
|
73
73
|
},
|