diff --git a/Components/ChatArea.svelte b/Components/ChatArea.svelte index e678385..990d609 100644 --- a/Components/ChatArea.svelte +++ b/Components/ChatArea.svelte @@ -137,14 +137,14 @@ // For assistant messages that aren't streaming, use traditional parsing if (!isCurrentlyStreaming) { // Check if this is a cancelled request message - if (message.content === Copy.ApiRequestAborted) { - return `Request has been cancelled`; + if (message.content.includes(Selector.ApiRequestAborted)) { + return `${Copy.ApiRequestAborted}`; } try { return streamingMarkdownService.formatText(message.content) || `
${message.content}
`; } catch (err) { - console.error('HTML processing failed:', err); + console.error("HTML processing failed:", err); return `
${message.content}
`; } } @@ -268,7 +268,7 @@ {#if message.role === Role.User}
{message.content}
{:else} -
+
{#if currentStreamingMessageId === message.id}
{:else} diff --git a/Components/ChatWindow.svelte b/Components/ChatWindow.svelte index eb0abe0..c9c3801 100644 --- a/Components/ChatWindow.svelte +++ b/Components/ChatWindow.svelte @@ -167,6 +167,11 @@ conversationStore.clearLoadFlag(); scrollToBottom(); } + + $: if ($conversationStore.shouldDeactivateEditMode) { + editModeActive = false; + conversationStore.clearEditModeFlag(); + }
diff --git a/Services/StreamingMarkdownService.ts b/Services/StreamingMarkdownService.ts index c83d788..27fab49 100644 --- a/Services/StreamingMarkdownService.ts +++ b/Services/StreamingMarkdownService.ts @@ -1,17 +1,17 @@ -import { unified, type Processor } from 'unified'; -import remarkParse from 'remark-parse'; -import remarkMath from 'remark-math'; -import remarkGfm from 'remark-gfm'; -import remarkEmoji from 'remark-emoji'; -import remarkRehype from 'remark-rehype'; -import rehypeKatex from 'rehype-katex'; -import rehypeHighlight from 'rehype-highlight'; -import rehypeStringify from 'rehype-stringify'; -import wikiLinkPlugin from 'remark-wiki-link'; -import type { FileSystemService } from './FileSystemService'; -import { Resolve } from './DependencyService'; -import { Services } from './Services'; -import { Selector } from 'Enums/Selector'; +import { unified, type Processor } from "unified"; +import remarkParse from "remark-parse"; +import remarkMath from "remark-math"; +import remarkGfm from "remark-gfm"; +import remarkEmoji from "remark-emoji"; +import remarkRehype from "remark-rehype"; +import rehypeKatex from "rehype-katex"; +import rehypeHighlight from "rehype-highlight"; +import rehypeStringify from "rehype-stringify"; +import wikiLinkPlugin from "remark-wiki-link"; +import type { FileSystemService } from "./FileSystemService"; +import { Resolve } from "./DependencyService"; +import { Services } from "./Services"; +import { Selector } from "Enums/Selector"; interface StreamingState { element: HTMLElement; @@ -38,13 +38,13 @@ export class StreamingMarkdownService { .use(rehypeKatex) .use(rehypeHighlight, { detect: true, - plainText: ['txt', 'text'], + plainText: ["txt", "text"], aliases: { - javascript: ['js', 'jsx'], - typescript: ['ts', 'tsx'], - python: ['py'], - markdown: ['md', 'mdx'], - shell: ['bash', 'sh', 'zsh'] + javascript: ["js", "jsx"], + typescript: ["ts", "tsx"], + python: ["py"], + markdown: ["md", "mdx"], + shell: ["bash", "sh", "zsh"] } }) .use(rehypeStringify, { @@ -66,18 +66,18 @@ export class StreamingMarkdownService { const result = this.processor!.processSync(preprocessed); return String(result); } catch (error) { - console.warn('Markdown processing failed:', error); + console.warn("Markdown processing failed:", error); return this.getFallbackHTML(text); } } // Simplified streaming approach public initializeStream(messageId: string, container: HTMLElement): void { - container.innerHTML = ''; + container.innerHTML = ""; this.streamingStates.set(messageId, { element: container, - buffer: '', + buffer: "", lastProcessedLength: 0, isComplete: false }); @@ -115,7 +115,7 @@ export class StreamingMarkdownService { state.element.innerHTML = html; state.lastProcessedLength = state.buffer.length; } catch (error) { - console.warn('Streaming render failed:', error); + console.warn("Streaming render failed:", error); } this.renderTimeouts.delete(messageId); @@ -154,74 +154,74 @@ export class StreamingMarkdownService { // Simplified and safer preprocessing return content // Normalize line endings - .replace(/\r\n/g, '\n') - .replace(/\r/g, '\n') + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") // Convert LaTeX delimiters .replace(/\\\[([\s\S]*?)\\\]/g, (match, math) => { // Ensure math blocks are on their own lines - return '\n$$\n' + math.trim() + '\n$$\n'; + return "\n$$\n" + math.trim() + "\n$$\n"; }) - .replace(/\\\(([\s\S]*?)\\\)/g, '$$$1$$') + .replace(/\\\(([\s\S]*?)\\\)/g, "$$$1$$") // Ensure headers have blank lines before them (but not at start) - .replace(/([^\n])\n(#{1,6}\s)/g, '$1\n\n$2') + .replace(/([^\n])\n(#{1,6}\s)/g, "$1\n\n$2") // Collapse excessive newlines but preserve intentional spacing - .replace(/\n{4,}/g, '\n\n\n') + .replace(/\n{4,}/g, "\n\n\n") // Clean up list formatting - ensure consistent spacing - .replace(/^(\s*)([*+-]|\d+\.)\s+/gm, '$1$2 ') + .replace(/^(\s*)([*+-]|\d+\.)\s+/gm, "$1$2 ") // Ensure task list checkboxes are properly formatted - .replace(/^(\s*)([*+-])\s*\[([ x])\]/gm, '$1$2 [$3]'); + .replace(/^(\s*)([*+-])\s*\[([ x])\]/gm, "$1$2 [$3]"); } private getFallbackHTML(text: string): string { // Improved fallback with basic markdown support const escaped = text - .replace(/&/g, '&') - .replace(//g, '>'); + .replace(/&/g, "&") + .replace(//g, ">"); - const lines = escaped.split('\n'); + const lines = escaped.split("\n"); const html: string[] = []; let inList = false; let inCodeBlock = false; for (const line of lines) { - if (line.startsWith('```')) { + if (line.startsWith("```")) { if (inCodeBlock) { - html.push(''); + html.push(""); inCodeBlock = false; } else { - html.push('
');
+                    html.push("
");
                     inCodeBlock = true;
                 }
                 continue;
             }
             
             if (inCodeBlock) {
-                html.push(line + '\n');
+                html.push(line + "\n");
                 continue;
             }
             
             // Basic list support
             if (/^[*+-]\s/.test(line)) {
                 if (!inList) {
-                    html.push('
    '); + html.push("
      "); inList = true; } html.push(`
    • ${line.substring(2)}
    • `); - } else if (inList && line.trim() === '') { - html.push('
    '); + } else if (inList && line.trim() === "") { + html.push("
"); inList = false; } else { if (inList) { - html.push(''); + html.push(""); inList = false; } // Basic formatting const formatted = line - .replace(/\*\*(.+?)\*\*/g, '$1') - .replace(/\*(.+?)\*/g, '$1') - .replace(/`(.+?)`/g, '$1'); + .replace(/\*\*(.+?)\*\*/g, "$1") + .replace(/\*(.+?)\*/g, "$1") + .replace(/`(.+?)`/g, "$1"); if (line.trim()) { html.push(`

${formatted}

`); @@ -229,9 +229,9 @@ export class StreamingMarkdownService { } } - if (inList) html.push(''); - if (inCodeBlock) html.push('
'); + if (inList) html.push(""); + if (inCodeBlock) html.push("
"); - return html.join(''); + return html.join(""); } } \ No newline at end of file diff --git a/Services/StreamingService.ts b/Services/StreamingService.ts index 32ba790..efb1878 100644 --- a/Services/StreamingService.ts +++ b/Services/StreamingService.ts @@ -1,5 +1,5 @@ import type { AIFunctionCall } from "AIClasses/AIFunctionCall"; -import { Copy } from "Enums/Copy"; +import { Selector } from "Enums/Selector"; export interface StreamChunk { content: string; @@ -71,7 +71,7 @@ export class StreamingService { // Don't log abort errors as they're intentional if (error instanceof Error && error.name === 'AbortError') { yield { - content: Copy.ApiRequestAborted, + content: Selector.ApiRequestAborted, isComplete: true }; } else { diff --git a/Stores/conversationStore.ts b/Stores/conversationStore.ts index 8327225..b2cb020 100644 --- a/Stores/conversationStore.ts +++ b/Stores/conversationStore.ts @@ -4,21 +4,25 @@ import type { Conversation } from 'Conversations/Conversation'; interface ConversationStoreState { shouldReset: boolean; conversationToLoad: { conversation: Conversation; filePath: string } | null; + shouldDeactivateEditMode: boolean; } function createConversationStore() { const { subscribe, set, update } = writable({ shouldReset: false, - conversationToLoad: null + conversationToLoad: null, + shouldDeactivateEditMode: false }); return { subscribe, - reset: () => set({ shouldReset: true, conversationToLoad: null }), + reset: () => set({ shouldReset: true, conversationToLoad: null, shouldDeactivateEditMode: true }), clearResetFlag: () => update(state => ({ ...state, shouldReset: false })), loadConversation: (conversation: Conversation, filePath: string) => - set({ shouldReset: false, conversationToLoad: { conversation, filePath } }), - clearLoadFlag: () => update(state => ({ ...state, conversationToLoad: null })) + set({ shouldReset: false, conversationToLoad: { conversation, filePath }, shouldDeactivateEditMode: true }), + clearLoadFlag: () => update(state => ({ ...state, conversationToLoad: null })), + deactivateEditMode: () => update(state => ({ ...state, shouldDeactivateEditMode: true })), + clearEditModeFlag: () => update(state => ({ ...state, shouldDeactivateEditMode: false })) }; }