Skip to content

Commit acb73d8

Browse files
authored
webui: Add editing attachments in user messages (#18147)
* feat: Enable editing attachments in user messages * feat: Improvements for data handling & UI * docs: Update Architecture diagrams * chore: update webui build output * refactor: Exports * chore: update webui build output * feat: Add handling paste for Chat Message Edit Form * chore: update webui build output * refactor: Cleanup * chore: update webui build output
1 parent 0a271d8 commit acb73d8

File tree

14 files changed

+775
-241
lines changed

14 files changed

+775
-241
lines changed

tools/server/public/index.html.gz

2.77 KB
Binary file not shown.

tools/server/webui/docs/architecture/high-level-architecture-simplified.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ flowchart TB
1111
C_Screen["ChatScreen"]
1212
C_Form["ChatForm"]
1313
C_Messages["ChatMessages"]
14+
C_Message["ChatMessage"]
15+
C_MessageEditForm["ChatMessageEditForm"]
1416
C_ModelsSelector["ModelsSelector"]
1517
C_Settings["ChatSettings"]
1618
end
@@ -54,7 +56,9 @@ flowchart TB
5456
5557
%% Component hierarchy
5658
C_Screen --> C_Form & C_Messages & C_Settings
57-
C_Form & C_Messages --> C_ModelsSelector
59+
C_Messages --> C_Message
60+
C_Message --> C_MessageEditForm
61+
C_Form & C_MessageEditForm --> C_ModelsSelector
5862
5963
%% Components → Hooks → Stores
6064
C_Form & C_Messages --> H1 & H2
@@ -93,7 +97,7 @@ flowchart TB
9397
classDef apiStyle fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
9498
9599
class R1,R2,RL routeStyle
96-
class C_Sidebar,C_Screen,C_Form,C_Messages,C_ModelsSelector,C_Settings componentStyle
100+
class C_Sidebar,C_Screen,C_Form,C_Messages,C_Message,C_MessageEditForm,C_ModelsSelector,C_Settings componentStyle
97101
class H1,H2 hookStyle
98102
class S1,S2,S3,S4,S5 storeStyle
99103
class SV1,SV2,SV3,SV4,SV5 serviceStyle

tools/server/webui/docs/architecture/high-level-architecture.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ end
1616
C_Form["ChatForm"]
1717
C_Messages["ChatMessages"]
1818
C_Message["ChatMessage"]
19+
C_MessageUser["ChatMessageUser"]
20+
C_MessageEditForm["ChatMessageEditForm"]
1921
C_Attach["ChatAttachments"]
2022
C_ModelsSelector["ModelsSelector"]
2123
C_Settings["ChatSettings"]
@@ -38,7 +40,7 @@ end
3840
S1Error["<b>Error Handling:</b><br/>showErrorDialog()<br/>dismissErrorDialog()<br/>isAbortError()"]
3941
S1Msg["<b>Message Operations:</b><br/>addMessage()<br/>sendMessage()<br/>updateMessage()<br/>deleteMessage()<br/>getDeletionInfo()"]
4042
S1Regen["<b>Regeneration:</b><br/>regenerateMessage()<br/>regenerateMessageWithBranching()<br/>continueAssistantMessage()"]
41-
S1Edit["<b>Editing:</b><br/>editAssistantMessage()<br/>editUserMessagePreserveResponses()<br/>editMessageWithBranching()"]
43+
S1Edit["<b>Editing:</b><br/>editAssistantMessage()<br/>editUserMessagePreserveResponses()<br/>editMessageWithBranching()<br/>clearEditMode()<br/>isEditModeActive()<br/>getAddFilesHandler()<br/>setEditModeActive()"]
4244
S1Utils["<b>Utilities:</b><br/>getApiOptions()<br/>parseTimingData()<br/>getOrCreateAbortController()<br/>getConversationModel()"]
4345
end
4446
subgraph S2["conversationsStore"]
@@ -88,6 +90,10 @@ end
8890
RE7["getChatStreaming()"]
8991
RE8["getAllLoadingChats()"]
9092
RE9["getAllStreamingChats()"]
93+
RE9a["isEditModeActive()"]
94+
RE9b["getAddFilesHandler()"]
95+
RE9c["setEditModeActive()"]
96+
RE9d["clearEditMode()"]
9197
end
9298
subgraph ConvExports["conversationsStore"]
9399
RE10["conversations()"]
@@ -182,14 +188,18 @@ end
182188
%% Component hierarchy
183189
C_Screen --> C_Form & C_Messages & C_Settings
184190
C_Messages --> C_Message
185-
C_Message --> C_ModelsSelector
191+
C_Message --> C_MessageUser
192+
C_MessageUser --> C_MessageEditForm
193+
C_MessageEditForm --> C_ModelsSelector
194+
C_MessageEditForm --> C_Attach
186195
C_Form --> C_ModelsSelector
187196
C_Form --> C_Attach
188197
C_Message --> C_Attach
189198
190199
%% Components use Hooks
191200
C_Form --> H1
192201
C_Message --> H1 & H2
202+
C_MessageEditForm --> H1
193203
C_Screen --> H2
194204
195205
%% Hooks use Stores
@@ -244,7 +254,7 @@ end
244254
classDef apiStyle fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
245255
246256
class R1,R2,RL routeStyle
247-
class C_Sidebar,C_Screen,C_Form,C_Messages,C_Message componentStyle
257+
class C_Sidebar,C_Screen,C_Form,C_Messages,C_Message,C_MessageUser,C_MessageEditForm componentStyle
248258
class C_ModelsSelector,C_Settings componentStyle
249259
class C_Attach componentStyle
250260
class H1,H2,H3 methodStyle

tools/server/webui/package-lock.json

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

tools/server/webui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"@chromatic-com/storybook": "^4.1.2",
2727
"@eslint/compat": "^1.2.5",
2828
"@eslint/js": "^9.18.0",
29-
"@internationalized/date": "^3.8.2",
29+
"@internationalized/date": "^3.10.1",
3030
"@lucide/svelte": "^0.515.0",
3131
"@playwright/test": "^1.49.1",
3232
"@storybook/addon-a11y": "^10.0.7",

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ChatFormTextarea
99
} from '$lib/components/app';
1010
import { INPUT_CLASSES } from '$lib/constants/input-classes';
11+
import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
1112
import { config } from '$lib/stores/settings.svelte';
1213
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
1314
import { isRouterMode } from '$lib/stores/server.svelte';
@@ -66,7 +67,7 @@
6667
let message = $state('');
6768
let pasteLongTextToFileLength = $derived.by(() => {
6869
const n = Number(currentConfig.pasteLongTextToFileLen);
69-
return Number.isNaN(n) ? 2500 : n;
70+
return Number.isNaN(n) ? Number(SETTING_CONFIG_DEFAULT.pasteLongTextToFileLen) : n;
7071
});
7172
let previousIsLoading = $state(isLoading);
7273
let recordingSupported = $state(false);

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

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,21 @@
1212
onCopy?: (message: DatabaseMessage) => void;
1313
onContinueAssistantMessage?: (message: DatabaseMessage) => void;
1414
onDelete?: (message: DatabaseMessage) => void;
15-
onEditWithBranching?: (message: DatabaseMessage, newContent: string) => void;
15+
onEditWithBranching?: (
16+
message: DatabaseMessage,
17+
newContent: string,
18+
newExtras?: DatabaseMessageExtra[]
19+
) => void;
1620
onEditWithReplacement?: (
1721
message: DatabaseMessage,
1822
newContent: string,
1923
shouldBranch: boolean
2024
) => void;
21-
onEditUserMessagePreserveResponses?: (message: DatabaseMessage, newContent: string) => void;
25+
onEditUserMessagePreserveResponses?: (
26+
message: DatabaseMessage,
27+
newContent: string,
28+
newExtras?: DatabaseMessageExtra[]
29+
) => void;
2230
onNavigateToSibling?: (siblingId: string) => void;
2331
onRegenerateWithBranching?: (message: DatabaseMessage, modelOverride?: string) => void;
2432
siblingInfo?: ChatMessageSiblingInfo | null;
@@ -45,6 +53,8 @@
4553
messageTypes: string[];
4654
} | null>(null);
4755
let editedContent = $state(message.content);
56+
let editedExtras = $state<DatabaseMessageExtra[]>(message.extra ? [...message.extra] : []);
57+
let editedUploadedFiles = $state<ChatUploadedFile[]>([]);
4858
let isEditing = $state(false);
4959
let showDeleteDialog = $state(false);
5060
let shouldBranchAfterEdit = $state(false);
@@ -85,6 +95,16 @@
8595
function handleCancelEdit() {
8696
isEditing = false;
8797
editedContent = message.content;
98+
editedExtras = message.extra ? [...message.extra] : [];
99+
editedUploadedFiles = [];
100+
}
101+
102+
function handleEditedExtrasChange(extras: DatabaseMessageExtra[]) {
103+
editedExtras = extras;
104+
}
105+
106+
function handleEditedUploadedFilesChange(files: ChatUploadedFile[]) {
107+
editedUploadedFiles = files;
88108
}
89109
90110
async function handleCopy() {
@@ -107,6 +127,8 @@
107127
function handleEdit() {
108128
isEditing = true;
109129
editedContent = message.content;
130+
editedExtras = message.extra ? [...message.extra] : [];
131+
editedUploadedFiles = [];
110132
111133
setTimeout(() => {
112134
if (textareaElement) {
@@ -143,9 +165,10 @@
143165
onContinueAssistantMessage?.(message);
144166
}
145167
146-
function handleSaveEdit() {
168+
async function handleSaveEdit() {
147169
if (message.role === 'user' || message.role === 'system') {
148-
onEditWithBranching?.(message, editedContent.trim());
170+
const finalExtras = await getMergedExtras();
171+
onEditWithBranching?.(message, editedContent.trim(), finalExtras);
149172
} else {
150173
// For assistant messages, preserve exact content including trailing whitespace
151174
// This is important for the Continue feature to work properly
@@ -154,15 +177,30 @@
154177
155178
isEditing = false;
156179
shouldBranchAfterEdit = false;
180+
editedUploadedFiles = [];
157181
}
158182
159-
function handleSaveEditOnly() {
183+
async function handleSaveEditOnly() {
160184
if (message.role === 'user') {
161185
// For user messages, trim to avoid accidental whitespace
162-
onEditUserMessagePreserveResponses?.(message, editedContent.trim());
186+
const finalExtras = await getMergedExtras();
187+
onEditUserMessagePreserveResponses?.(message, editedContent.trim(), finalExtras);
163188
}
164189
165190
isEditing = false;
191+
editedUploadedFiles = [];
192+
}
193+
194+
async function getMergedExtras(): Promise<DatabaseMessageExtra[]> {
195+
if (editedUploadedFiles.length === 0) {
196+
return editedExtras;
197+
}
198+
199+
const { parseFilesToMessageExtras } = await import('$lib/utils/browser-only');
200+
const result = await parseFilesToMessageExtras(editedUploadedFiles);
201+
const newExtras = result?.extras || [];
202+
203+
return [...editedExtras, ...newExtras];
166204
}
167205
168206
function handleShowDeleteDialogChange(show: boolean) {
@@ -197,6 +235,8 @@
197235
class={className}
198236
{deletionInfo}
199237
{editedContent}
238+
{editedExtras}
239+
{editedUploadedFiles}
200240
{isEditing}
201241
{message}
202242
onCancelEdit={handleCancelEdit}
@@ -206,6 +246,8 @@
206246
onEdit={handleEdit}
207247
onEditKeydown={handleEditKeydown}
208248
onEditedContentChange={handleEditedContentChange}
249+
onEditedExtrasChange={handleEditedExtrasChange}
250+
onEditedUploadedFilesChange={handleEditedUploadedFilesChange}
209251
{onNavigateToSibling}
210252
onSaveEdit={handleSaveEdit}
211253
onSaveEditOnly={handleSaveEditOnly}

0 commit comments

Comments
 (0)