mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
fix: improve edit mode deactivation and request cancellation handling
- Deactivate edit mode when resetting or loading conversations - Fix cancelled request detection using Selector instead of Copy enum - Standardize quote style in StreamingMarkdownService to double quotes
This commit is contained in:
parent
b82d38e799
commit
f8aa162a63
5 changed files with 69 additions and 60 deletions
|
|
@ -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 `<span class="${Selector.ApiRequestAborted}">Request has been cancelled</span>`;
|
||||
if (message.content.includes(Selector.ApiRequestAborted)) {
|
||||
return `<span class="${Selector.ApiRequestAborted}">${Copy.ApiRequestAborted}</span>`;
|
||||
}
|
||||
|
||||
try {
|
||||
return streamingMarkdownService.formatText(message.content) || `<div>${message.content}</div>`;
|
||||
} catch (err) {
|
||||
console.error('HTML processing failed:', err);
|
||||
console.error("HTML processing failed:", err);
|
||||
return `<div>${message.content}</div>`;
|
||||
}
|
||||
}
|
||||
|
|
@ -268,7 +268,7 @@
|
|||
{#if message.role === Role.User}
|
||||
<div class="message-text-user fade-in-fast">{message.content}</div>
|
||||
{:else}
|
||||
<div class="markdown-content fade-in-fast {currentStreamingMessageId === message.id ? 'streaming' : ''}">
|
||||
<div class="markdown-content fade-in-fast {currentStreamingMessageId === message.id ? "streaming" : ""}">
|
||||
{#if currentStreamingMessageId === message.id}
|
||||
<div use:streamingAction={message.id} class="streaming-content"></div>
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -167,6 +167,11 @@
|
|||
conversationStore.clearLoadFlag();
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
$: if ($conversationStore.shouldDeactivateEditMode) {
|
||||
editModeActive = false;
|
||||
conversationStore.clearEditModeFlag();
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="container">
|
||||
|
|
|
|||
|
|
@ -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, "&")
|
||||
.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('</code></pre>');
|
||||
html.push("</code></pre>");
|
||||
inCodeBlock = false;
|
||||
} else {
|
||||
html.push('<pre><code>');
|
||||
html.push("<pre><code>");
|
||||
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('<ul>');
|
||||
html.push("<ul>");
|
||||
inList = true;
|
||||
}
|
||||
html.push(`<li>${line.substring(2)}</li>`);
|
||||
} else if (inList && line.trim() === '') {
|
||||
html.push('</ul>');
|
||||
} else if (inList && line.trim() === "") {
|
||||
html.push("</ul>");
|
||||
inList = false;
|
||||
} else {
|
||||
if (inList) {
|
||||
html.push('</ul>');
|
||||
html.push("</ul>");
|
||||
inList = false;
|
||||
}
|
||||
|
||||
// Basic formatting
|
||||
const formatted = line
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
.replace(/`(.+?)`/g, '<code>$1</code>');
|
||||
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
||||
.replace(/\*(.+?)\*/g, "<em>$1</em>")
|
||||
.replace(/`(.+?)`/g, "<code>$1</code>");
|
||||
|
||||
if (line.trim()) {
|
||||
html.push(`<p>${formatted}</p>`);
|
||||
|
|
@ -229,9 +229,9 @@ export class StreamingMarkdownService {
|
|||
}
|
||||
}
|
||||
|
||||
if (inList) html.push('</ul>');
|
||||
if (inCodeBlock) html.push('</code></pre>');
|
||||
if (inList) html.push("</ul>");
|
||||
if (inCodeBlock) html.push("</code></pre>");
|
||||
|
||||
return html.join('');
|
||||
return html.join("");
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<ConversationStoreState>({
|
||||
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 }))
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue