]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
webui: Per-conversation system message with UI displaying, edition & branching (...
authorAleksander Grygier <redacted>
Sat, 6 Dec 2025 12:19:05 +0000 (13:19 +0100)
committerGitHub <redacted>
Sat, 6 Dec 2025 12:19:05 +0000 (13:19 +0100)
* feat: Per-conversation system message with optional display in UI, edition and branching (WIP)

* chore: update webui build output

14 files changed:
tools/server/public/index.html.gz
tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte
tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageSystem.svelte [new file with mode: 0644]
tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte
tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessages.svelte
tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettings.svelte
tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte
tools/server/webui/src/lib/components/app/index.ts
tools/server/webui/src/lib/components/app/misc/MarkdownContent.svelte
tools/server/webui/src/lib/constants/settings-config.ts
tools/server/webui/src/lib/services/chat.ts
tools/server/webui/src/lib/services/database.ts
tools/server/webui/src/lib/stores/chat.svelte.ts
tools/server/webui/src/lib/types/chat.d.ts

index 4527cb335677f2921182e4a3d5085312b79988e5..38927736683ab580fd9dceb450e6022427d773a7 100644 (file)
Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ
index 23156113616778e8691bcaaecc123cc4429d8432..96ed56a7758aa6f09bc3ab5cfdbc75642b842205 100644 (file)
@@ -3,6 +3,7 @@
        import { copyToClipboard, isIMEComposing } from '$lib/utils';
        import ChatMessageAssistant from './ChatMessageAssistant.svelte';
        import ChatMessageUser from './ChatMessageUser.svelte';
+       import ChatMessageSystem from './ChatMessageSystem.svelte';
 
        interface Props {
                class?: string;
        }
 
        function handleSaveEdit() {
-               if (message.role === 'user') {
-                       // For user messages, trim to avoid accidental whitespace
+               if (message.role === 'user' || message.role === 'system') {
                        onEditWithBranching?.(message, editedContent.trim());
                } else {
                        // For assistant messages, preserve exact content including trailing whitespace
        }
 </script>
 
-{#if message.role === 'user'}
+{#if message.role === 'system'}
+       <ChatMessageSystem
+               bind:textareaElement
+               class={className}
+               {deletionInfo}
+               {editedContent}
+               {isEditing}
+               {message}
+               onCancelEdit={handleCancelEdit}
+               onConfirmDelete={handleConfirmDelete}
+               onCopy={handleCopy}
+               onDelete={handleDelete}
+               onEdit={handleEdit}
+               onEditKeydown={handleEditKeydown}
+               onEditedContentChange={handleEditedContentChange}
+               {onNavigateToSibling}
+               onSaveEdit={handleSaveEdit}
+               onShowDeleteDialogChange={handleShowDeleteDialogChange}
+               {showDeleteDialog}
+               {siblingInfo}
+       />
+{:else if message.role === 'user'}
        <ChatMessageUser
                bind:textareaElement
                class={className}
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageSystem.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageSystem.svelte
new file mode 100644 (file)
index 0000000..c203822
--- /dev/null
@@ -0,0 +1,216 @@
+<script lang="ts">
+       import { Check, X } from '@lucide/svelte';
+       import { Card } from '$lib/components/ui/card';
+       import { Button } from '$lib/components/ui/button';
+       import { MarkdownContent } from '$lib/components/app';
+       import { INPUT_CLASSES } from '$lib/constants/input-classes';
+       import { config } from '$lib/stores/settings.svelte';
+       import ChatMessageActions from './ChatMessageActions.svelte';
+
+       interface Props {
+               class?: string;
+               message: DatabaseMessage;
+               isEditing: boolean;
+               editedContent: string;
+               siblingInfo?: ChatMessageSiblingInfo | null;
+               showDeleteDialog: boolean;
+               deletionInfo: {
+                       totalCount: number;
+                       userMessages: number;
+                       assistantMessages: number;
+                       messageTypes: string[];
+               } | null;
+               onCancelEdit: () => void;
+               onSaveEdit: () => void;
+               onEditKeydown: (event: KeyboardEvent) => void;
+               onEditedContentChange: (content: string) => void;
+               onCopy: () => void;
+               onEdit: () => void;
+               onDelete: () => void;
+               onConfirmDelete: () => void;
+               onNavigateToSibling?: (siblingId: string) => void;
+               onShowDeleteDialogChange: (show: boolean) => void;
+               textareaElement?: HTMLTextAreaElement;
+       }
+
+       let {
+               class: className = '',
+               message,
+               isEditing,
+               editedContent,
+               siblingInfo = null,
+               showDeleteDialog,
+               deletionInfo,
+               onCancelEdit,
+               onSaveEdit,
+               onEditKeydown,
+               onEditedContentChange,
+               onCopy,
+               onEdit,
+               onDelete,
+               onConfirmDelete,
+               onNavigateToSibling,
+               onShowDeleteDialogChange,
+               textareaElement = $bindable()
+       }: Props = $props();
+
+       let isMultiline = $state(false);
+       let messageElement: HTMLElement | undefined = $state();
+       let isExpanded = $state(false);
+       let contentHeight = $state(0);
+       const MAX_HEIGHT = 200; // pixels
+       const currentConfig = config();
+
+       let showExpandButton = $derived(contentHeight > MAX_HEIGHT);
+
+       $effect(() => {
+               if (!messageElement || !message.content.trim()) return;
+
+               if (message.content.includes('\n')) {
+                       isMultiline = true;
+               }
+
+               const resizeObserver = new ResizeObserver((entries) => {
+                       for (const entry of entries) {
+                               const element = entry.target as HTMLElement;
+                               const estimatedSingleLineHeight = 24;
+
+                               isMultiline = element.offsetHeight > estimatedSingleLineHeight * 1.5;
+                               contentHeight = element.scrollHeight;
+                       }
+               });
+
+               resizeObserver.observe(messageElement);
+
+               return () => {
+                       resizeObserver.disconnect();
+               };
+       });
+
+       function toggleExpand() {
+               isExpanded = !isExpanded;
+       }
+</script>
+
+<div
+       aria-label="System message with actions"
+       class="group flex flex-col items-end gap-3 md:gap-2 {className}"
+       role="group"
+>
+       {#if isEditing}
+               <div class="w-full max-w-[80%]">
+                       <textarea
+                               bind:this={textareaElement}
+                               bind:value={editedContent}
+                               class="min-h-[60px] w-full resize-none rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
+                               onkeydown={onEditKeydown}
+                               oninput={(e) => onEditedContentChange(e.currentTarget.value)}
+                               placeholder="Edit system message..."
+                       ></textarea>
+
+                       <div class="mt-2 flex justify-end gap-2">
+                               <Button class="h-8 px-3" onclick={onCancelEdit} size="sm" variant="outline">
+                                       <X class="mr-1 h-3 w-3" />
+                                       Cancel
+                               </Button>
+
+                               <Button class="h-8 px-3" onclick={onSaveEdit} disabled={!editedContent.trim()} size="sm">
+                                       <Check class="mr-1 h-3 w-3" />
+                                       Send
+                               </Button>
+                       </div>
+               </div>
+       {:else}
+               {#if message.content.trim()}
+                       <div class="relative max-w-[80%]">
+                               <button
+                                       class="group/expand w-full text-left {!isExpanded && showExpandButton
+                                               ? 'cursor-pointer'
+                                               : 'cursor-auto'}"
+                                       onclick={showExpandButton && !isExpanded ? toggleExpand : undefined}
+                                       type="button"
+                               >
+                                       <Card
+                                               class="rounded-[1.125rem] !border-2 !border-dashed !border-border/50 bg-muted px-3.75 py-1.5 data-[multiline]:py-2.5"
+                                               data-multiline={isMultiline ? '' : undefined}
+                                               style="border: 2px dashed hsl(var(--border));"
+                                       >
+                                               <div
+                                                       class="relative overflow-hidden transition-all duration-300 {isExpanded
+                                                               ? 'cursor-text select-text'
+                                                               : 'select-none'}"
+                                                       style={!isExpanded && showExpandButton
+                                                               ? `max-height: ${MAX_HEIGHT}px;`
+                                                               : 'max-height: none;'}
+                                               >
+                                                       {#if currentConfig.renderUserContentAsMarkdown}
+                                                               <div bind:this={messageElement} class="text-md {isExpanded ? 'cursor-text' : ''}">
+                                                                       <MarkdownContent class="markdown-system-content" content={message.content} />
+                                                               </div>
+                                                       {:else}
+                                                               <span
+                                                                       bind:this={messageElement}
+                                                                       class="text-md whitespace-pre-wrap {isExpanded ? 'cursor-text' : ''}"
+                                                               >
+                                                                       {message.content}
+                                                               </span>
+                                                       {/if}
+
+                                                       {#if !isExpanded && showExpandButton}
+                                                               <div
+                                                                       class="pointer-events-none absolute right-0 bottom-0 left-0 h-48 bg-gradient-to-t from-muted to-transparent"
+                                                               ></div>
+                                                               <div
+                                                                       class="pointer-events-none absolute right-0 bottom-4 left-0 flex justify-center opacity-0 transition-opacity group-hover/expand:opacity-100"
+                                                               >
+                                                                       <Button
+                                                                               class="rounded-full px-4 py-1.5 text-xs shadow-md"
+                                                                               size="sm"
+                                                                               variant="outline"
+                                                                       >
+                                                                               Show full system message
+                                                                       </Button>
+                                                               </div>
+                                                       {/if}
+                                               </div>
+
+                                               {#if isExpanded && showExpandButton}
+                                                       <div class="mb-2 flex justify-center">
+                                                               <Button
+                                                                       class="rounded-full px-4 py-1.5 text-xs"
+                                                                       onclick={(e) => {
+                                                                               e.stopPropagation();
+                                                                               toggleExpand();
+                                                                       }}
+                                                                       size="sm"
+                                                                       variant="outline"
+                                                               >
+                                                                       Collapse System Message
+                                                               </Button>
+                                                       </div>
+                                               {/if}
+                                       </Card>
+                               </button>
+                       </div>
+               {/if}
+
+               {#if message.timestamp}
+                       <div class="max-w-[80%]">
+                               <ChatMessageActions
+                                       actionsPosition="right"
+                                       {deletionInfo}
+                                       justify="end"
+                                       {onConfirmDelete}
+                                       {onCopy}
+                                       {onDelete}
+                                       {onEdit}
+                                       {onNavigateToSibling}
+                                       {onShowDeleteDialogChange}
+                                       {siblingInfo}
+                                       {showDeleteDialog}
+                                       role="user"
+                               />
+                       </div>
+               {/if}
+       {/if}
+</div>
index 8556cbef5b926f8c2cf2f2771141693c7f478397..3d2b8dd35b4d974e48002c626cb9a75f5fcf54e1 100644 (file)
 
                {#if message.content.trim()}
                        <Card
-                               class="max-w-[80%] rounded-[1.125rem] bg-primary px-3.75 py-1.5 text-primary-foreground data-[multiline]:py-2.5"
+                               class="max-w-[80%] rounded-[1.125rem] border-none bg-primary px-3.75 py-1.5 text-primary-foreground data-[multiline]:py-2.5"
                                data-multiline={isMultiline ? '' : undefined}
                        >
                                {#if currentConfig.renderUserContentAsMarkdown}
index f307f829bc6de21eaa77b1abab608615393ac430..2e5f57cb61430ede2d05d0ad271290bf037bf970 100644 (file)
@@ -2,6 +2,7 @@
        import { ChatMessage } from '$lib/components/app';
        import { chatStore } from '$lib/stores/chat.svelte';
        import { conversationsStore, activeConversation } from '$lib/stores/conversations.svelte';
+       import { config } from '$lib/stores/settings.svelte';
        import { getMessageSiblings } from '$lib/utils';
 
        interface Props {
@@ -13,6 +14,7 @@
        let { class: className, messages = [], onUserAction }: Props = $props();
 
        let allConversationMessages = $state<DatabaseMessage[]>([]);
+       const currentConfig = config();
 
        function refreshAllMessages() {
                const conversation = activeConversation();
                        return [];
                }
 
-               return messages.map((message) => {
+               // Filter out system messages if showSystemMessage is false
+               const filteredMessages = currentConfig.showSystemMessage
+                       ? messages
+                       : messages.filter((msg) => msg.type !== 'system');
+
+               return filteredMessages.map((message) => {
                        const siblingInfo = getMessageSiblings(allConversationMessages, message.id);
 
                        return {
index 67df20439c678f8c03f2d4e0fedf4348b71761aa..45640e42a0174cf940c33dda305797a53ef916a8 100644 (file)
                        title: 'General',
                        icon: Settings,
                        fields: [
-                               { key: 'apiKey', label: 'API Key', type: 'input' },
-                               {
-                                       key: 'systemMessage',
-                                       label: 'System Message (will be disabled if left empty)',
-                                       type: 'textarea'
-                               },
                                {
                                        key: 'theme',
                                        label: 'Theme',
                                                { value: 'dark', label: 'Dark', icon: Moon }
                                        ]
                                },
+                               { key: 'apiKey', label: 'API Key', type: 'input' },
+                               {
+                                       key: 'systemMessage',
+                                       label: 'System Message',
+                                       type: 'textarea'
+                               },
                                {
                                        key: 'pasteLongTextToFileLen',
                                        label: 'Paste long text to file length',
index ef46e2d1764ba9cb3cae3c705671a6b777472f24..a6f51f47d6e7c955de9ac4f08ebcfa6bd79bde36 100644 (file)
@@ -95,7 +95,7 @@
                        </div>
                        {#if field.help || SETTING_CONFIG_INFO[field.key]}
                                <p class="mt-1 text-xs text-muted-foreground">
-                                       {field.help || SETTING_CONFIG_INFO[field.key]}
+                                       {@html field.help || SETTING_CONFIG_INFO[field.key]}
                                </p>
                        {/if}
                {:else if field.type === 'textarea'}
                                value={String(localConfig[field.key] ?? '')}
                                onchange={(e) => onConfigChange(field.key, e.currentTarget.value)}
                                placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`}
-                               class="min-h-[100px] w-full md:max-w-2xl"
+                               class="min-h-[10rem] w-full md:max-w-2xl"
                        />
+
                        {#if field.help || SETTING_CONFIG_INFO[field.key]}
                                <p class="mt-1 text-xs text-muted-foreground">
                                        {field.help || SETTING_CONFIG_INFO[field.key]}
                                </p>
                        {/if}
+
+                       {#if field.key === 'systemMessage'}
+                               <div class="mt-3 flex items-center gap-2">
+                                       <Checkbox
+                                               id="showSystemMessage"
+                                               checked={Boolean(localConfig.showSystemMessage ?? true)}
+                                               onCheckedChange={(checked) => onConfigChange('showSystemMessage', Boolean(checked))}
+                                       />
+
+                                       <Label for="showSystemMessage" class="cursor-pointer text-sm font-normal">
+                                               Show system message in conversations
+                                       </Label>
+                               </div>
+                       {/if}
                {:else if field.type === 'select'}
                        {@const selectedOption = field.options?.find(
                                (opt: { value: string; label: string; icon?: Component }) =>
index cf4d7495e2f2e5e712907ca0f247557c4af13af6..87b24598b722f168e134115453aebdf95bd0aa1d 100644 (file)
@@ -19,8 +19,10 @@ export { default as ChatMessage } from './chat/ChatMessages/ChatMessage.svelte';
 export { default as ChatMessageActions } from './chat/ChatMessages/ChatMessageActions.svelte';
 export { default as ChatMessageBranchingControls } from './chat/ChatMessages/ChatMessageBranchingControls.svelte';
 export { default as ChatMessageStatistics } from './chat/ChatMessages/ChatMessageStatistics.svelte';
+export { default as ChatMessageSystem } from './chat/ChatMessages/ChatMessageSystem.svelte';
 export { default as ChatMessageThinkingBlock } from './chat/ChatMessages/ChatMessageThinkingBlock.svelte';
 export { default as ChatMessages } from './chat/ChatMessages/ChatMessages.svelte';
+export { default as MessageBranchingControls } from './chat/ChatMessages/ChatMessageBranchingControls.svelte';
 
 export { default as ChatScreen } from './chat/ChatScreen/ChatScreen.svelte';
 export { default as ChatScreenHeader } from './chat/ChatScreen/ChatScreenHeader.svelte';
index 99d6e21e1319cc084b18c9d16e43821500c215c2..9c37bde0d493848f24577f6af672661b30f1dc5e 100644 (file)
                line-height: 1.75;
        }
 
+       div :global(:is(h1, h2, h3, h4, h5, h6):first-child) {
+               margin-top: 0;
+       }
+
        /* Headers with consistent spacing */
        div :global(h1) {
                font-size: 1.875rem;
                font-weight: 700;
-               margin: 1.5rem 0 0.75rem 0;
                line-height: 1.2;
+               margin: 1.5rem 0 0.75rem 0;
        }
 
        div :global(h2) {
                font-size: 1.5rem;
                font-weight: 600;
-               margin: 1.25rem 0 0.5rem 0;
                line-height: 1.3;
+               margin: 1.25rem 0 0.5rem 0;
        }
 
        div :global(h3) {
index 1fc35b48c41e86e6be529b110e6f38a2f16abf1e..3764a2856b52d4a879ef671f1ce73def1c1a7198 100644 (file)
@@ -3,6 +3,7 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
        // Do not use nested objects, keep it single level. Prefix the key if you need to group them.
        apiKey: '',
        systemMessage: '',
+       showSystemMessage: true,
        theme: 'system',
        showThoughtInProgress: false,
        showToolCalls: false,
@@ -42,8 +43,9 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
 };
 
 export const SETTING_CONFIG_INFO: Record<string, string> = {
-       apiKey: 'Set the API Key if you are using --api-key option for the server.',
+       apiKey: 'Set the API Key if you are using <code>--api-key</code> option for the server.',
        systemMessage: 'The starting message that defines how model should behave.',
+       showSystemMessage: 'Display the system message at the top of each conversation.',
        theme:
                'Choose the color theme for the interface. You can choose between System (follows your device settings), Light, or Dark.',
        pasteLongTextToFileLen:
index a6a68124035637c2be38705cbe60cb0bb4e598cf..c03b764419f5cacc762efa1903db808538c65442 100644 (file)
@@ -89,7 +89,6 @@ export class ChatService {
                        custom,
                        timings_per_token,
                        // Config options
-                       systemMessage,
                        disableReasoningFormat
                } = options;
 
@@ -103,6 +102,7 @@ export class ChatService {
                                }
                        })
                        .filter((msg) => {
+                               // Filter out empty system messages
                                if (msg.role === 'system') {
                                        const content = typeof msg.content === 'string' ? msg.content : '';
 
@@ -112,10 +112,8 @@ export class ChatService {
                                return true;
                        });
 
-               const processedMessages = ChatService.injectSystemMessage(normalizedMessages, systemMessage);
-
                const requestBody: ApiChatCompletionRequest = {
-                       messages: processedMessages.map((msg: ApiChatMessageData) => ({
+                       messages: normalizedMessages.map((msg: ApiChatMessageData) => ({
                                role: msg.role,
                                content: msg.content
                        })),
@@ -677,46 +675,6 @@ export class ChatService {
        // Utilities
        // ─────────────────────────────────────────────────────────────────────────────
 
-       /**
-        * Injects a system message at the beginning of the conversation if provided.
-        * Checks for existing system messages to avoid duplication.
-        *
-        * @param messages - Array of chat messages to process
-        * @param systemMessage - Optional system message to inject
-        * @returns Array of messages with system message injected at the beginning if provided
-        * @private
-        */
-       private static injectSystemMessage(
-               messages: ApiChatMessageData[],
-               systemMessage?: string
-       ): ApiChatMessageData[] {
-               const trimmedSystemMessage = systemMessage?.trim();
-
-               if (!trimmedSystemMessage) {
-                       return messages;
-               }
-
-               if (messages.length > 0 && messages[0].role === 'system') {
-                       if (messages[0].content !== trimmedSystemMessage) {
-                               const updatedMessages = [...messages];
-                               updatedMessages[0] = {
-                                       role: 'system',
-                                       content: trimmedSystemMessage
-                               };
-                               return updatedMessages;
-                       }
-
-                       return messages;
-               }
-
-               const systemMsg: ApiChatMessageData = {
-                       role: 'system',
-                       content: trimmedSystemMessage
-               };
-
-               return [systemMsg, ...messages];
-       }
-
        /**
         * Parses error response and creates appropriate error with context information
         * @param response - HTTP response object
index 185a598c3bb5179e2ac08713869bc0bced33a7da..3b24628cffaf871caba7765dab13cfed40085bed 100644 (file)
@@ -166,6 +166,49 @@ export class DatabaseService {
                return rootMessage.id;
        }
 
+       /**
+        * Creates a system prompt message for a conversation.
+        *
+        * @param convId - Conversation ID
+        * @param systemPrompt - The system prompt content (must be non-empty)
+        * @param parentId - Parent message ID (typically the root message)
+        * @returns The created system message
+        * @throws Error if systemPrompt is empty
+        */
+       static async createSystemMessage(
+               convId: string,
+               systemPrompt: string,
+               parentId: string
+       ): Promise<DatabaseMessage> {
+               const trimmedPrompt = systemPrompt.trim();
+               if (!trimmedPrompt) {
+                       throw new Error('Cannot create system message with empty content');
+               }
+
+               const systemMessage: DatabaseMessage = {
+                       id: uuid(),
+                       convId,
+                       type: 'system',
+                       timestamp: Date.now(),
+                       role: 'system',
+                       content: trimmedPrompt,
+                       parent: parentId,
+                       thinking: '',
+                       children: []
+               };
+
+               await db.messages.add(systemMessage);
+
+               const parentMessage = await db.messages.get(parentId);
+               if (parentMessage) {
+                       await db.messages.update(parentId, {
+                               children: [...parentMessage.children, systemMessage.id]
+                       });
+               }
+
+               return systemMessage;
+       }
+
        /**
         * Deletes a conversation and all its messages.
         *
index f21e29116336f070d13c11498c909b87c0cc0bf6..dd6b77e71ae8ab005c0705b955ef71295f1524e3 100644 (file)
@@ -624,6 +624,22 @@ class ChatStore {
                this.clearChatStreaming(currentConv.id);
 
                try {
+                       if (isNewConversation) {
+                               const rootId = await DatabaseService.createRootMessage(currentConv.id);
+                               const currentConfig = config();
+                               const systemPrompt = currentConfig.systemMessage?.toString().trim();
+
+                               if (systemPrompt) {
+                                       const systemMessage = await DatabaseService.createSystemMessage(
+                                               currentConv.id,
+                                               systemPrompt,
+                                               rootId
+                                       );
+
+                                       conversationsStore.addMessageToActive(systemMessage);
+                               }
+                       }
+
                        const userMessage = await this.addMessage('user', content, 'text', '-1', extras);
                        if (!userMessage) throw new Error('Failed to add user message');
                        if (isNewConversation && content)
@@ -999,14 +1015,20 @@ class ChatStore {
                const activeConv = conversationsStore.activeConversation;
                if (!activeConv || this.isLoading) return;
 
-               const result = this.getMessageByIdWithRole(messageId, 'user');
+               let result = this.getMessageByIdWithRole(messageId, 'user');
+
+               if (!result) {
+                       result = this.getMessageByIdWithRole(messageId, 'system');
+               }
+
                if (!result) return;
                const { message: msg } = result;
 
                try {
                        const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
                        const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
-                       const isFirstUserMessage = rootMessage && msg.parent === rootMessage.id;
+                       const isFirstUserMessage =
+                               msg.role === 'user' && rootMessage && msg.parent === rootMessage.id;
 
                        const parentId = msg.parent || rootMessage?.id;
                        if (!parentId) return;
@@ -1037,7 +1059,10 @@ class ChatStore {
                                );
                        }
                        await conversationsStore.refreshActiveMessages();
-                       await this.generateResponseForMessage(newMessage.id);
+
+                       if (msg.role === 'user') {
+                               await this.generateResponseForMessage(newMessage.id);
+                       }
                } catch (error) {
                        console.error('Failed to edit message with branching:', error);
                }
index 0eafb80cbfd6321c538e5c22be690ce56a7a64e2..0e706b72b66014e8d0459c8baa508df9bbda4788 100644 (file)
@@ -1,4 +1,4 @@
-export type ChatMessageType = 'root' | 'text' | 'think';
+export type ChatMessageType = 'root' | 'text' | 'think' | 'system';
 export type ChatRole = 'user' | 'assistant' | 'system';
 
 export interface ChatUploadedFile {