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
+40 -7
View File
@@ -3,9 +3,10 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { readAttachment } from "../attachments";
import { runAutoDelete } from "../cleanup";
import { useChat, type ToolActivity } from "../hooks/useChat";
import { usePreferences } from "../hooks/usePreferences";
import { useVisionSupport } from "../hooks/useVision";
import { buildThreadMarkdown, renderAttachments, type TranscriptOptions } from "../markdown";
import { getLamPreferences } from "../preferences";
import type { ResolvedPreferences } from "../preferences";
import { deleteSession, listSessions, loadSession, summarizeSession } from "../sessions";
import { resolveStartup } from "../startup";
import { buildThread } from "../thread";
@@ -94,17 +95,49 @@ interface SessionRow {
isActive: boolean;
}
export function ChatView({
/** Shown while the conversation and the preferences are still being read. */
function LoadingChat() {
return <List isLoading navigationTitle="Lam" searchBarPlaceholder="Loading conversations…" />;
}
/**
* The chat surface, once its preferences are known.
*
* Split in two so that {@link ChatView} can wait for the model before the state
* machine starts. `useChat` is created with the preferences it will use, so
* handing it preferences that are still loading would pin the conversation to
* whatever the preference said before the selection arrived.
*
* The outer half does nothing but that wait, and the loading state costs one
* LocalStorage read, which is a few milliseconds while the user is still moving
* their hands towards the keyboard.
*/
export function ChatView(props: ChatViewProps) {
const { preferences } = usePreferences();
if (!preferences) return <LoadingChat />;
return <ChatSession {...props} preferences={preferences} />;
}
interface ChatSessionProps extends ChatViewProps {
preferences: ResolvedPreferences;
}
function ChatSession({
preferences,
initialSession,
initialPrompt,
initialSessionId,
initialAttachmentPath,
launchNote,
}: ChatViewProps) {
const preferences = useMemo(() => getLamPreferences(), []);
const chat = useChat({ preferences, initialSession });
}: ChatSessionProps) {
// The capability check is resolved before the chat exists, because its answer
// is part of the system prompt: a model that knows it can read images describes
// the one it is sent, and one that does not know reasons about itself instead.
const vision = useVisionSupport(preferences);
const chat = useChat({ preferences, initialSession, vision: vision.support });
const { replaceSession } = chat;
const { canSeeImages } = useVisionSupport(preferences);
const { canSeeImages } = vision;
const { activity } = chat;
const [input, setInput] = useState(initialPrompt ?? "");
@@ -396,7 +429,7 @@ export function ChatView({
};
if (startup === "loading" || summaries === undefined) {
return <List isLoading navigationTitle="Lam" searchBarPlaceholder="Loading conversations…" />;
return <LoadingChat />;
}
const placeholder = chat.status ? `${chat.status}. Press ⏎ to stop` : "Lam is thinking. Press ⏎ to stop";