feat: Ollama models command, with load/pull/delete and unload (#16)

This commit was merged in pull request #16.
This commit is contained in:
2026-10-05 09:43:00 +00:00
parent 05fd9ff13b
commit 26e05dea5b
22 changed files with 1948 additions and 65 deletions
+25 -2
View File
@@ -3,11 +3,12 @@ import { runAgent } from "../agent";
import { toStoredAttachment } from "../attachments";
import { ipPlace } from "../geo";
import { buildMessages, composeSystemPrompt } from "../history";
import { isSelfHosted } from "../ollama";
import { OllamaError } from "../ollama";
import type { ResolvedPreferences } from "../preferences";
import { createSession, createTurn, saveSession, withTitleFromFirstTurn } from "../sessions";
import { toolsFor } from "../tools";
import type { PendingAttachment, Session } from "../types";
import type { PendingAttachment, Session, VisionSupport } from "../types";
import { errorMessage, isAbortError } from "../utils";
/**
@@ -33,6 +34,14 @@ export interface UseChatOptions {
preferences: ResolvedPreferences;
/** Restores an existing conversation, e.g. when reopening from the sessions command. */
initialSession?: Session;
/**
* Whether the model can read images, for the system prompt.
*
* Passed in rather than checked here because the answer is already known to the
* view: the snip action is gated on the same value, and asking twice would mean
* two capability requests per model change.
*/
vision?: VisionSupport;
}
/** Which tool is running right now, and how stale it is getting. */
@@ -85,6 +94,11 @@ export function useChat(options: UseChatOptions): UseChat {
const sessionRef = useRef(session);
const preferencesRef = useRef(options.preferences);
/**
* Read through a ref for the same reason as the preferences: `ask` is a stable
* callback, and the capability check lands after the first render.
*/
const visionRef = useRef<VisionSupport>(options.vision ?? "unknown");
const abortRef = useRef<AbortController | null>(null);
const liveRef = useRef(false);
const responseRef = useRef("");
@@ -93,6 +107,9 @@ export function useChat(options: UseChatOptions): UseChat {
const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
preferencesRef.current = options.preferences;
// Only ever forwards. The check starts as "unknown", and a value arriving is
// news; going back to "unknown" would be the view telling us less than before.
if (options.vision) visionRef.current = options.vision;
/** Updates state and the ref together, so `ask` never reads a stale session. */
const commit = useCallback((next: Session) => {
@@ -181,7 +198,13 @@ export function useChat(options: UseChatOptions): UseChat {
abortRef.current = controller;
const messages = buildMessages(
composeSystemPrompt(preferences.systemPrompt, preferences.webEnabled),
composeSystemPrompt(preferences.systemPrompt, {
model: preferences.model,
host: preferences.host,
selfHosted: isSelfHosted(preferences.host),
webTools: preferences.webEnabled,
vision: visionRef.current,
}),
base.turns,
prompt,
attachment ? [attachment.base64] : undefined,