Add model selection, dashboard chat input, and model warming

- Add GET /api/chat/ps and POST /api/chat/warm endpoints for hot model
  visibility and pre-loading
- Extend PATCH /api/chat/conversations/:id to accept model in addition
  to title
- Add ModelSelector component with hot/cold indicators from Ollama /api/ps
- Add DashboardChatInput component (model selector + note picker + textarea)
  replacing the simple "New Chat" button on the dashboard
- Add model selector dropdown to ChatView header, persisted per-conversation
- Warm default model on dashboard mount via fire-and-forget background task
- Configure Ollama with OLLAMA_MAX_LOADED_MODELS=2 and OLLAMA_KEEP_ALIVE=30m
- Always-visible edit buttons on NoteCard/TaskCard (remove hover-only behavior)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 18:49:06 -05:00
parent 987ec56dc3
commit 953eaf2feb
13 changed files with 710 additions and 306 deletions
+42
View File
@@ -15,6 +15,7 @@ import type {
Message,
OllamaModel,
OllamaStatus,
RunningModel,
SendMessageResponse,
} from "@/types/chat";
@@ -27,6 +28,7 @@ export const useChatStore = defineStore("chat", () => {
const streamingContent = ref("");
const lastContextMeta = ref<ContextMeta | null>(null);
const models = ref<OllamaModel[]>([]);
const runningModels = ref<RunningModel[]>([]);
const ollamaStatus = ref<"checking" | "available" | "unavailable">("checking");
const modelStatus = ref<"checking" | "ready" | "not_found">("checking");
const defaultModel = ref("");
@@ -308,6 +310,42 @@ export const useChatStore = defineStore("chat", () => {
}
}
async function fetchRunningModels() {
try {
const data = await apiGet<{ models: RunningModel[] }>("/api/chat/ps");
runningModels.value = data.models;
} catch {
runningModels.value = [];
}
}
async function warmModel(model: string) {
try {
await apiPost("/api/chat/warm", { model });
} catch {
// Warming is best-effort
}
}
async function updateConversationModel(id: number, model: string) {
try {
const updated = await apiPatch<Conversation>(
`/api/chat/conversations/${id}`,
{ model }
);
const idx = conversations.value.findIndex((c) => c.id === id);
if (idx !== -1) {
conversations.value[idx] = { ...conversations.value[idx], ...updated };
}
if (currentConversation.value?.id === id) {
currentConversation.value.model = updated.model;
}
} catch (e) {
useToastStore().show("Failed to update model", "error");
throw e;
}
}
return {
conversations,
currentConversation,
@@ -317,6 +355,7 @@ export const useChatStore = defineStore("chat", () => {
streamingContent,
lastContextMeta,
models,
runningModels,
ollamaStatus,
modelStatus,
defaultModel,
@@ -331,6 +370,9 @@ export const useChatStore = defineStore("chat", () => {
saveMessageAsNote,
summarizeAsNote,
fetchModels,
fetchRunningModels,
warmModel,
updateConversationModel,
fetchStatus,
startStatusPolling,
stopStatusPolling,