Add settings page, model management, and chat UX improvements
- Settings infrastructure: key-value settings table, GET/PUT API, Pinia store - Configurable assistant name (default "Fable") in settings and LLM system prompt - Model catalog with 18 models in 3 categories (General Purpose, Coding, Uncensored / Creative Writing) with download/select/remove functionality - Move Ollama status indicator from chat views to global nav bar - Chat bubble layout: user messages right-aligned, assistant left-aligned - Floating dark input bar with auto-focus and circular send button - Fix HTML entity rendering (' apostrophe issue in marked/DOMPurify pipeline) - Fix new chat button navigation (fetchConversation before router.push) - Recent chats section on home page with "New Chat" button - Update summary.md with Phase 4.5 changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { apiGet } from "@/api/client";
|
||||
import type { Note, NoteListResponse } from "@/types/note";
|
||||
import type { Task, TaskListResponse } from "@/types/task";
|
||||
import type { Conversation } from "@/types/chat";
|
||||
import NoteCard from "@/components/NoteCard.vue";
|
||||
import TaskCard from "@/components/TaskCard.vue";
|
||||
import type { TaskStatus } from "@/types/task";
|
||||
import { useTasksStore } from "@/stores/tasks";
|
||||
import { useChatStore } from "@/stores/chat";
|
||||
|
||||
const router = useRouter();
|
||||
const recentNotes = ref<Note[]>([]);
|
||||
const recentTasks = ref<Task[]>([]);
|
||||
const recentChats = ref<Conversation[]>([]);
|
||||
const loading = ref(true);
|
||||
const tasksStore = useTasksStore();
|
||||
|
||||
@@ -32,6 +37,15 @@ onMounted(async () => {
|
||||
console.error("Failed to load recent tasks:", e);
|
||||
}
|
||||
|
||||
try {
|
||||
const chatData = await apiGet<{ conversations: Conversation[]; total: number }>(
|
||||
"/api/chat/conversations?limit=3&offset=0"
|
||||
);
|
||||
recentChats.value = chatData.conversations;
|
||||
} catch (e) {
|
||||
console.error("Failed to load recent chats:", e);
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
@@ -43,6 +57,13 @@ function onStatusToggle(id: number, status: TaskStatus) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function newChat() {
|
||||
const chatStore = useChatStore();
|
||||
const conv = await chatStore.createConversation();
|
||||
await chatStore.fetchConversation(conv.id);
|
||||
router.push(`/chat/${conv.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -52,6 +73,28 @@ function onStatusToggle(id: number, status: TaskStatus) {
|
||||
<p v-if="loading" class="loading">Loading...</p>
|
||||
|
||||
<template v-else>
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<h2>Recent Chats</h2>
|
||||
<router-link to="/chat" class="see-all">See all</router-link>
|
||||
</div>
|
||||
<div v-if="recentChats.length" class="cards">
|
||||
<router-link
|
||||
v-for="chat in recentChats"
|
||||
:key="chat.id"
|
||||
:to="`/chat/${chat.id}`"
|
||||
class="chat-card"
|
||||
>
|
||||
<span class="chat-card-title">{{ chat.title || "Untitled" }}</span>
|
||||
<span class="chat-card-meta">{{ chat.message_count }} messages</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<p class="empty-text">No chats yet.</p>
|
||||
</div>
|
||||
<button class="btn-cta btn-new-chat" @click="newChat">+ New Chat</button>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<h2>Recent Notes</h2>
|
||||
@@ -130,6 +173,38 @@ function onStatusToggle(id: number, status: TaskStatus) {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.chat-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.chat-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.chat-card-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.chat-card-meta {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-new-chat {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 1.5rem 0;
|
||||
@@ -143,8 +218,10 @@ function onStatusToggle(id: number, status: TaskStatus) {
|
||||
padding: 0.4rem 1rem;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user