diff --git a/frontend/src/components/DashboardChatInput.vue b/frontend/src/components/DashboardChatInput.vue
index a7018e3..8c8a463 100644
--- a/frontend/src/components/DashboardChatInput.vue
+++ b/frontend/src/components/DashboardChatInput.vue
@@ -1,17 +1,15 @@
-
-
-
-
-
-
diff --git a/frontend/src/components/ToolCallCard.vue b/frontend/src/components/ToolCallCard.vue
index 481656b..11dbdd1 100644
--- a/frontend/src/components/ToolCallCard.vue
+++ b/frontend/src/components/ToolCallCard.vue
@@ -1,11 +1,15 @@
@@ -60,9 +126,40 @@ const searchResults = computed(() => {
+
+ {{ eventData.title }}
+ {{ formatEventTime(eventData.start) }}
+
+
+ {{ eventCount }} found
+
+
{{ title }}
+
+
+
+ Suggested tags:
+
+
@@ -121,4 +218,85 @@ const searchResults = computed(() => {
color: var(--color-text-muted);
margin-right: 0.1rem;
}
+.tool-event-title {
+ font-weight: 600;
+ color: var(--color-text);
+}
+.tool-event-time {
+ color: var(--color-text-muted);
+ font-size: 0.75rem;
+}
+.tool-event-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.2rem;
+ width: 100%;
+}
+.tool-event-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ gap: 0.5rem;
+}
+.tool-event-item-title {
+ color: var(--color-text);
+ font-size: 0.8rem;
+}
+.tool-event-item-time {
+ color: var(--color-text-muted);
+ font-size: 0.75rem;
+ white-space: nowrap;
+}
+.tool-event-more {
+ color: var(--color-text-muted);
+ font-size: 0.75rem;
+ font-style: italic;
+}
+
+/* Tag suggestions */
+.tag-suggestions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.3rem;
+ width: 100%;
+ margin-top: 0.25rem;
+ padding-top: 0.3rem;
+ border-top: 1px solid var(--color-border);
+}
+.tag-suggestions-label {
+ font-size: 0.7rem;
+ color: var(--color-text-muted);
+ font-weight: 500;
+}
+.tag-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.2rem;
+ padding: 0.15rem 0.5rem;
+ border: 1px solid var(--color-primary);
+ border-radius: 999px;
+ background: transparent;
+ color: var(--color-primary);
+ font-size: 0.75rem;
+ cursor: pointer;
+ transition: background 0.15s, color 0.15s;
+}
+.tag-pill:hover:not(:disabled) {
+ background: var(--color-primary);
+ color: #fff;
+}
+.tag-pill.applied {
+ background: var(--color-success, #2ecc71);
+ border-color: var(--color-success, #2ecc71);
+ color: #fff;
+ cursor: default;
+}
+.tag-pill:disabled:not(.applied) {
+ opacity: 0.6;
+ cursor: wait;
+}
+.tag-check {
+ font-size: 0.65rem;
+}
diff --git a/frontend/src/stores/chat.ts b/frontend/src/stores/chat.ts
index 3218f7d..6bbe65f 100644
--- a/frontend/src/stores/chat.ts
+++ b/frontend/src/stores/chat.ts
@@ -13,9 +13,7 @@ import type {
ConversationDetail,
ContextMeta,
Message,
- OllamaModel,
OllamaStatus,
- RunningModel,
SendMessageResponse,
ToolCallRecord,
} from "@/types/chat";
@@ -29,8 +27,6 @@ export const useChatStore = defineStore("chat", () => {
const streamingContent = ref("");
const streamingToolCalls = ref([]);
const lastContextMeta = ref(null);
- const models = ref([]);
- const runningModels = ref([]);
const ollamaStatus = ref<"checking" | "available" | "unavailable">("checking");
const modelStatus = ref<"checking" | "ready" | "not_found">("checking");
const defaultModel = ref("");
@@ -56,14 +52,10 @@ export const useChatStore = defineStore("chat", () => {
}
}
- async function createConversation(
- title = "",
- model = ""
- ): Promise {
+ async function createConversation(title = ""): Promise {
try {
const conv = await apiPost("/api/chat/conversations", {
title,
- model,
});
conversations.value.unshift(conv);
return conv;
@@ -319,24 +311,6 @@ export const useChatStore = defineStore("chat", () => {
}
}
- async function fetchModels() {
- try {
- const data = await apiGet<{ models: OllamaModel[] }>("/api/chat/models");
- models.value = data.models;
- } catch {
- models.value = [];
- }
- }
-
- 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 });
@@ -345,25 +319,6 @@ export const useChatStore = defineStore("chat", () => {
}
}
- async function updateConversationModel(id: number, model: string) {
- try {
- const updated = await apiPatch(
- `/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,
@@ -373,8 +328,6 @@ export const useChatStore = defineStore("chat", () => {
streamingContent,
streamingToolCalls,
lastContextMeta,
- models,
- runningModels,
ollamaStatus,
modelStatus,
defaultModel,
@@ -388,10 +341,7 @@ export const useChatStore = defineStore("chat", () => {
cancelGeneration,
saveMessageAsNote,
summarizeAsNote,
- fetchModels,
- fetchRunningModels,
warmModel,
- updateConversationModel,
fetchStatus,
startStatusPolling,
stopStatusPolling,
diff --git a/frontend/src/stores/settings.ts b/frontend/src/stores/settings.ts
index 717ffbc..130341b 100644
--- a/frontend/src/stores/settings.ts
+++ b/frontend/src/stores/settings.ts
@@ -1,13 +1,12 @@
import { ref, computed } from "vue";
import { defineStore } from "pinia";
-import { apiGet, apiPut, apiPost, apiStreamPost } from "@/api/client";
+import { apiGet, apiPut } from "@/api/client";
import { useToastStore } from "@/stores/toast";
import type { AppSettings } from "@/types/settings";
export const useSettingsStore = defineStore("settings", () => {
const settings = ref({});
const loading = ref(false);
- const installedModels = ref([]);
const assistantName = computed(
() => settings.value.assistant_name || "Fable"
@@ -40,44 +39,12 @@ export const useSettingsStore = defineStore("settings", () => {
}
}
- async function fetchInstalledModels() {
- try {
- const data = await apiGet<{ models: { name: string }[] }>("/api/chat/models");
- installedModels.value = data.models.map((m) => m.name);
- } catch {
- installedModels.value = [];
- }
- }
-
- async function pullModel(
- model: string,
- onProgress?: (data: Record) => void,
- ) {
- await apiStreamPost("/api/chat/models/pull", { model }, (data) => {
- if (onProgress) onProgress(data);
- });
- }
-
- async function deleteModel(model: string) {
- try {
- await apiPost("/api/chat/models/delete", { model });
- await fetchInstalledModels();
- } catch (e) {
- useToastStore().show("Failed to delete model", "error");
- throw e;
- }
- }
-
return {
settings,
loading,
- installedModels,
assistantName,
defaultModel,
fetchSettings,
updateSettings,
- fetchInstalledModels,
- pullModel,
- deleteModel,
};
});
diff --git a/frontend/src/types/chat.ts b/frontend/src/types/chat.ts
index 174d558..02011df 100644
--- a/frontend/src/types/chat.ts
+++ b/frontend/src/types/chat.ts
@@ -1,7 +1,7 @@
export interface ToolCallRecord {
function: string;
arguments: Record;
- result: { success: boolean; type?: string; data?: Record; error?: string };
+ result: { success: boolean; type?: string; data?: Record; error?: string; suggested_tags?: string[] };
status: "success" | "error";
}
@@ -34,18 +34,6 @@ export interface ConversationDetail extends Conversation {
messages: Message[];
}
-export interface OllamaModel {
- name: string;
- size: number;
-}
-
-export interface RunningModel {
- name: string;
- size: number;
- size_vram: number;
- expires_at: string;
-}
-
export interface ContextMeta {
context_note_id: number | null;
context_note_title: string | null;
diff --git a/frontend/src/types/settings.ts b/frontend/src/types/settings.ts
index 6ca20bb..ec4862a 100644
--- a/frontend/src/types/settings.ts
+++ b/frontend/src/types/settings.ts
@@ -3,11 +3,3 @@ export interface AppSettings {
default_model?: string;
[key: string]: string | undefined;
}
-
-export interface ModelInfo {
- name: string;
- description: string;
- size: string;
- bestFor: string;
- category: string;
-}
diff --git a/frontend/src/views/ChatView.vue b/frontend/src/views/ChatView.vue
index 974e636..2b5e16b 100644
--- a/frontend/src/views/ChatView.vue
+++ b/frontend/src/views/ChatView.vue
@@ -6,7 +6,6 @@ import { useSettingsStore } from "@/stores/settings";
import { apiGet } from "@/api/client";
import { renderMarkdown } from "@/utils/markdown";
import ChatMessage from "@/components/ChatMessage.vue";
-import ModelSelector from "@/components/ModelSelector.vue";
import ToolCallCard from "@/components/ToolCallCard.vue";
import type { Note } from "@/types/note";
@@ -30,9 +29,6 @@ const noteSearchResults = ref<{ id: number; title: string }[]>([]);
const noteSearchLoading = ref(false);
let noteSearchTimer: ReturnType | null = null;
-// Model selection
-const selectedModel = ref("");
-
// Exclude tracking (session-scoped per conversation)
const excludedNoteIds = ref>(new Set());
@@ -76,12 +72,6 @@ onMounted(async () => {
await store.fetchConversations();
if (convId.value) {
await store.fetchConversation(convId.value);
- if (store.currentConversation?.model) {
- selectedModel.value = store.currentConversation.model;
- }
- }
- if (!selectedModel.value) {
- selectedModel.value = store.defaultModel;
}
nextTick(() => inputEl.value?.focus());
});
@@ -112,26 +102,6 @@ watch(convId, async (newId) => {
nextTick(() => inputEl.value?.focus());
});
-// Sync selectedModel from loaded conversation
-watch(
- () => store.currentConversation?.model,
- (model) => {
- if (model) selectedModel.value = model;
- }
-);
-
-// Persist model changes to backend
-watch(selectedModel, (newModel, oldModel) => {
- if (
- newModel &&
- oldModel &&
- newModel !== oldModel &&
- store.currentConversation
- ) {
- store.updateConversationModel(store.currentConversation.id, newModel);
- }
-});
-
watch(
() => store.streamingContent,
() => scrollToBottom()
@@ -155,7 +125,7 @@ async function selectConversation(id: number) {
}
async function newConversation() {
- const conv = await store.createConversation("", selectedModel.value);
+ const conv = await store.createConversation();
await store.fetchConversation(conv.id);
router.push(`/chat/${conv.id}`);
}
@@ -344,10 +314,6 @@ function excludeAutoNote(noteId: number) {
-
{{ store.currentConversation.title || "New Chat" }}