Skip to content

Commit 99c53d6

Browse files
authored
webui: Add a "Continue" Action for Assistant Message (ggml-org#16971)
* feat: Add "Continue" action for assistant messages * feat: Continuation logic & prompt improvements * chore: update webui build output * feat: Improve logic for continuing the assistant message * chore: update webui build output * chore: Linting * chore: update webui build output * fix: Remove synthetic prompt logic, use the prefill feature by sending the conversation payload ending with assistant message * chore: update webui build output * feat: Enable "Continue" button based on config & non-reasoning model type * chore: update webui build output * chore: Update packages with `npm audit fix` * fix: Remove redundant error * chore: update webui build output * chore: Update `.gitignore` * fix: Add missing change * feat: Add auto-resizing for Edit Assistant/User Message textareas * chore: update webui build output
1 parent 07b0e7a commit 99c53d6

File tree

14 files changed

+414
-47
lines changed

14 files changed

+414
-47
lines changed

tools/server/public/index.html.gz

1.34 KB
Binary file not shown.

tools/server/webui/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ vite.config.ts.timestamp-*
2525

2626
*storybook.log
2727
storybook-static
28+
*.code-workspace

tools/server/webui/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
class?: string;
1111
message: DatabaseMessage;
1212
onCopy?: (message: DatabaseMessage) => void;
13+
onContinueAssistantMessage?: (message: DatabaseMessage) => void;
1314
onDelete?: (message: DatabaseMessage) => void;
1415
onEditWithBranching?: (message: DatabaseMessage, newContent: string) => void;
1516
onEditWithReplacement?: (
1617
message: DatabaseMessage,
1718
newContent: string,
1819
shouldBranch: boolean
1920
) => void;
21+
onEditUserMessagePreserveResponses?: (message: DatabaseMessage, newContent: string) => void;
2022
onNavigateToSibling?: (siblingId: string) => void;
2123
onRegenerateWithBranching?: (message: DatabaseMessage) => void;
2224
siblingInfo?: ChatMessageSiblingInfo | null;
@@ -26,9 +28,11 @@
2628
class: className = '',
2729
message,
2830
onCopy,
31+
onContinueAssistantMessage,
2932
onDelete,
3033
onEditWithBranching,
3134
onEditWithReplacement,
35+
onEditUserMessagePreserveResponses,
3236
onNavigateToSibling,
3337
onRegenerateWithBranching,
3438
siblingInfo = null
@@ -133,17 +137,33 @@
133137
onRegenerateWithBranching?.(message);
134138
}
135139
140+
function handleContinue() {
141+
onContinueAssistantMessage?.(message);
142+
}
143+
136144
function handleSaveEdit() {
137145
if (message.role === 'user') {
146+
// For user messages, trim to avoid accidental whitespace
138147
onEditWithBranching?.(message, editedContent.trim());
139148
} else {
140-
onEditWithReplacement?.(message, editedContent.trim(), shouldBranchAfterEdit);
149+
// For assistant messages, preserve exact content including trailing whitespace
150+
// This is important for the Continue feature to work properly
151+
onEditWithReplacement?.(message, editedContent, shouldBranchAfterEdit);
141152
}
142153
143154
isEditing = false;
144155
shouldBranchAfterEdit = false;
145156
}
146157
158+
function handleSaveEditOnly() {
159+
if (message.role === 'user') {
160+
// For user messages, trim to avoid accidental whitespace
161+
onEditUserMessagePreserveResponses?.(message, editedContent.trim());
162+
}
163+
164+
isEditing = false;
165+
}
166+
147167
function handleShowDeleteDialogChange(show: boolean) {
148168
showDeleteDialog = show;
149169
}
@@ -166,6 +186,7 @@
166186
onEditedContentChange={handleEditedContentChange}
167187
{onNavigateToSibling}
168188
onSaveEdit={handleSaveEdit}
189+
onSaveEditOnly={handleSaveEditOnly}
169190
onShowDeleteDialogChange={handleShowDeleteDialogChange}
170191
{showDeleteDialog}
171192
{siblingInfo}
@@ -181,6 +202,7 @@
181202
messageContent={message.content}
182203
onCancelEdit={handleCancelEdit}
183204
onConfirmDelete={handleConfirmDelete}
205+
onContinue={handleContinue}
184206
onCopy={handleCopy}
185207
onDelete={handleDelete}
186208
onEdit={handleEdit}

tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageActions.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { Edit, Copy, RefreshCw, Trash2 } from '@lucide/svelte';
2+
import { Edit, Copy, RefreshCw, Trash2, ArrowRight } from '@lucide/svelte';
33
import { ActionButton, ConfirmationDialog } from '$lib/components/app';
44
import ChatMessageBranchingControls from './ChatMessageBranchingControls.svelte';
55
@@ -18,6 +18,7 @@
1818
onCopy: () => void;
1919
onEdit?: () => void;
2020
onRegenerate?: () => void;
21+
onContinue?: () => void;
2122
onDelete: () => void;
2223
onConfirmDelete: () => void;
2324
onNavigateToSibling?: (siblingId: string) => void;
@@ -31,6 +32,7 @@
3132
onCopy,
3233
onEdit,
3334
onConfirmDelete,
35+
onContinue,
3436
onDelete,
3537
onNavigateToSibling,
3638
onShowDeleteDialogChange,
@@ -69,6 +71,10 @@
6971
<ActionButton icon={RefreshCw} tooltip="Regenerate" onclick={onRegenerate} />
7072
{/if}
7173

74+
{#if role === 'assistant' && onContinue}
75+
<ActionButton icon={ArrowRight} tooltip="Continue" onclick={onContinue} />
76+
{/if}
77+
7278
<ActionButton icon={Trash2} tooltip="Delete" onclick={onDelete} />
7379
</div>
7480
</div>

tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { ChatMessageThinkingBlock, MarkdownContent } from '$lib/components/app';
33
import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
44
import { isLoading } from '$lib/stores/chat.svelte';
5+
import autoResizeTextarea from '$lib/utils/autoresize-textarea';
56
import { fade } from 'svelte/transition';
67
import {
78
Check,
@@ -39,6 +40,7 @@
3940
onCancelEdit?: () => void;
4041
onCopy: () => void;
4142
onConfirmDelete: () => void;
43+
onContinue?: () => void;
4244
onDelete: () => void;
4345
onEdit?: () => void;
4446
onEditKeydown?: (event: KeyboardEvent) => void;
@@ -65,6 +67,7 @@
6567
messageContent,
6668
onCancelEdit,
6769
onConfirmDelete,
70+
onContinue,
6871
onCopy,
6972
onDelete,
7073
onEdit,
@@ -107,6 +110,12 @@
107110
void copyToClipboard(model ?? '');
108111
}
109112
113+
$effect(() => {
114+
if (isEditing && textareaElement) {
115+
autoResizeTextarea(textareaElement);
116+
}
117+
});
118+
110119
function formatToolCallBadge(toolCall: ApiChatCompletionToolCall, index: number) {
111120
const callNumber = index + 1;
112121
const functionName = toolCall.function?.name?.trim();
@@ -190,7 +199,10 @@
190199
bind:value={editedContent}
191200
class="min-h-[50vh] w-full resize-y rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
192201
onkeydown={onEditKeydown}
193-
oninput={(e) => onEditedContentChange?.(e.currentTarget.value)}
202+
oninput={(e) => {
203+
autoResizeTextarea(e.currentTarget);
204+
onEditedContentChange?.(e.currentTarget.value);
205+
}}
194206
placeholder="Edit assistant message..."
195207
></textarea>
196208

@@ -335,6 +347,9 @@
335347
{onCopy}
336348
{onEdit}
337349
{onRegenerate}
350+
onContinue={currentConfig.enableContinueGeneration && !thinkingContent
351+
? onContinue
352+
: undefined}
338353
{onDelete}
339354
{onConfirmDelete}
340355
{onNavigateToSibling}

tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<script lang="ts">
2-
import { Check, X } from '@lucide/svelte';
2+
import { Check, X, Send } from '@lucide/svelte';
33
import { Card } from '$lib/components/ui/card';
44
import { Button } from '$lib/components/ui/button';
55
import { ChatAttachmentsList, MarkdownContent } from '$lib/components/app';
66
import { INPUT_CLASSES } from '$lib/constants/input-classes';
77
import { config } from '$lib/stores/settings.svelte';
8+
import autoResizeTextarea from '$lib/utils/autoresize-textarea';
89
import ChatMessageActions from './ChatMessageActions.svelte';
910
1011
interface Props {
@@ -22,6 +23,7 @@
2223
} | null;
2324
onCancelEdit: () => void;
2425
onSaveEdit: () => void;
26+
onSaveEditOnly?: () => void;
2527
onEditKeydown: (event: KeyboardEvent) => void;
2628
onEditedContentChange: (content: string) => void;
2729
onCopy: () => void;
@@ -43,6 +45,7 @@
4345
deletionInfo,
4446
onCancelEdit,
4547
onSaveEdit,
48+
onSaveEditOnly,
4649
onEditKeydown,
4750
onEditedContentChange,
4851
onCopy,
@@ -58,6 +61,12 @@
5861
let messageElement: HTMLElement | undefined = $state();
5962
const currentConfig = config();
6063
64+
$effect(() => {
65+
if (isEditing && textareaElement) {
66+
autoResizeTextarea(textareaElement);
67+
}
68+
});
69+
6170
$effect(() => {
6271
if (!messageElement || !message.content.trim()) return;
6372
@@ -95,20 +104,34 @@
95104
bind:value={editedContent}
96105
class="min-h-[60px] w-full resize-none rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
97106
onkeydown={onEditKeydown}
98-
oninput={(e) => onEditedContentChange(e.currentTarget.value)}
107+
oninput={(e) => {
108+
autoResizeTextarea(e.currentTarget);
109+
onEditedContentChange(e.currentTarget.value);
110+
}}
99111
placeholder="Edit your message..."
100112
></textarea>
101113

102114
<div class="mt-2 flex justify-end gap-2">
103-
<Button class="h-8 px-3" onclick={onCancelEdit} size="sm" variant="outline">
115+
<Button class="h-8 px-3" onclick={onCancelEdit} size="sm" variant="ghost">
104116
<X class="mr-1 h-3 w-3" />
105-
106117
Cancel
107118
</Button>
108119

109-
<Button class="h-8 px-3" onclick={onSaveEdit} disabled={!editedContent.trim()} size="sm">
110-
<Check class="mr-1 h-3 w-3" />
120+
{#if onSaveEditOnly}
121+
<Button
122+
class="h-8 px-3"
123+
onclick={onSaveEditOnly}
124+
disabled={!editedContent.trim()}
125+
size="sm"
126+
variant="outline"
127+
>
128+
<Check class="mr-1 h-3 w-3" />
129+
Save
130+
</Button>
131+
{/if}
111132

133+
<Button class="h-8 px-3" onclick={onSaveEdit} disabled={!editedContent.trim()} size="sm">
134+
<Send class="mr-1 h-3 w-3" />
112135
Send
113136
</Button>
114137
</div>

tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessages.svelte

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import { DatabaseStore } from '$lib/stores/database';
44
import {
55
activeConversation,
6+
continueAssistantMessage,
67
deleteMessage,
7-
navigateToSibling,
8-
editMessageWithBranching,
98
editAssistantMessage,
9+
editMessageWithBranching,
10+
editUserMessagePreserveResponses,
11+
navigateToSibling,
1012
regenerateMessageWithBranching
1113
} from '$lib/stores/chat.svelte';
1214
import { getMessageSiblings } from '$lib/utils/branching';
@@ -93,6 +95,26 @@
9395
9496
refreshAllMessages();
9597
}
98+
99+
async function handleContinueAssistantMessage(message: DatabaseMessage) {
100+
onUserAction?.();
101+
102+
await continueAssistantMessage(message.id);
103+
104+
refreshAllMessages();
105+
}
106+
107+
async function handleEditUserMessagePreserveResponses(
108+
message: DatabaseMessage,
109+
newContent: string
110+
) {
111+
onUserAction?.();
112+
113+
await editUserMessagePreserveResponses(message.id, newContent);
114+
115+
refreshAllMessages();
116+
}
117+
96118
async function handleDeleteMessage(message: DatabaseMessage) {
97119
await deleteMessage(message.id);
98120
@@ -110,7 +132,9 @@
110132
onNavigateToSibling={handleNavigateToSibling}
111133
onEditWithBranching={handleEditWithBranching}
112134
onEditWithReplacement={handleEditWithReplacement}
135+
onEditUserMessagePreserveResponses={handleEditUserMessagePreserveResponses}
113136
onRegenerateWithBranching={handleRegenerateWithBranching}
137+
onContinueAssistantMessage={handleContinueAssistantMessage}
114138
/>
115139
{/each}
116140
</div>

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsDialog.svelte

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
{ value: 'dark', label: 'Dark', icon: Moon }
5353
]
5454
},
55+
{
56+
key: 'pasteLongTextToFileLen',
57+
label: 'Paste long text to file length',
58+
type: 'input'
59+
},
5560
{
5661
key: 'showMessageStats',
5762
label: 'Show message generation statistics',
@@ -68,28 +73,29 @@
6873
type: 'checkbox'
6974
},
7075
{
71-
key: 'askForTitleConfirmation',
72-
label: 'Ask for confirmation before changing conversation title',
76+
key: 'showModelInfo',
77+
label: 'Show model information',
7378
type: 'checkbox'
7479
},
7580
{
76-
key: 'pasteLongTextToFileLen',
77-
label: 'Paste long text to file length',
78-
type: 'input'
81+
key: 'enableContinueGeneration',
82+
label: 'Enable "Continue" button',
83+
type: 'checkbox',
84+
isExperimental: true
7985
},
8086
{
8187
key: 'pdfAsImage',
8288
label: 'Parse PDF as image',
8389
type: 'checkbox'
8490
},
8591
{
86-
key: 'showModelInfo',
87-
label: 'Show model information',
92+
key: 'renderUserContentAsMarkdown',
93+
label: 'Render user content as Markdown',
8894
type: 'checkbox'
8995
},
9096
{
91-
key: 'renderUserContentAsMarkdown',
92-
label: 'Render user content as Markdown',
97+
key: 'askForTitleConfirmation',
98+
label: 'Ask for confirmation before changing conversation title',
9399
type: 'checkbox'
94100
}
95101
]

0 commit comments

Comments
 (0)