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:
2026-02-10 21:32:02 -05:00
parent 834fd80640
commit 38b1ac933e
20 changed files with 1257 additions and 236 deletions
+15 -4
View File
@@ -2,20 +2,31 @@ import { marked } from "marked";
import DOMPurify from "dompurify";
import { linkifyTags, linkifyWikilinks } from "@/utils/tags";
function decodeEntities(text: string): string {
const textarea = document.createElement("textarea");
textarea.innerHTML = text;
return textarea.value;
}
export function renderMarkdown(text: string): string {
const html = marked(text) as string;
const decoded = decodeEntities(text);
const html = marked(decoded) as string;
const withTags = linkifyTags(html);
const withLinks = linkifyWikilinks(withTags);
return DOMPurify.sanitize(withLinks, {
const sanitized = DOMPurify.sanitize(withLinks, {
ADD_ATTR: ["data-tag", "data-title"],
});
// marked escapes ' to &#39; — replace after sanitization to ensure clean rendering
return sanitized.replace(/&#39;/g, "'");
}
export function renderPreview(text: string): string {
const html = marked(text) as string;
const decoded = decodeEntities(text);
const html = marked(decoded) as string;
const withTags = linkifyTags(html);
const withLinks = linkifyWikilinks(withTags);
return DOMPurify.sanitize(withLinks, {
const sanitized = DOMPurify.sanitize(withLinks, {
FORBID_TAGS: ["a", "img"],
});
return sanitized.replace(/&#39;/g, "'");
}