From 1320611421688c38bd13896edb3d133f90c87108 Mon Sep 17 00:00:00 2001 From: AVBharath10 Date: Mon, 26 Jan 2026 13:11:37 +0530 Subject: [PATCH] feat(ui): add user name and avatar customization --- src/config/schema.ts | 9 +- src/config/zod-schema.ts | 7 + ui/src/ui/app-render.ts | 741 ++++++++++++++++--------------- ui/src/ui/chat/grouped-render.ts | 83 ++-- ui/src/ui/views/chat.ts | 124 +++--- 5 files changed, 502 insertions(+), 462 deletions(-) diff --git a/src/config/schema.ts b/src/config/schema.ts index bb8d8c0bb..e3732a6cd 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -275,7 +275,10 @@ const FIELD_LABELS: Record = { "commands.useAccessGroups": "Use Access Groups", "ui.seamColor": "Accent Color", "ui.assistant.name": "Assistant Name", + "ui.assistant.name": "Assistant Name", "ui.assistant.avatar": "Assistant Avatar", + "ui.user.name": "User Name", + "ui.user.avatar": "User Avatar", "browser.controlUrl": "Browser Control URL", "browser.snapshotDefaults": "Browser Snapshot Defaults", "browser.snapshotDefaults.mode": "Browser Snapshot Mode", @@ -875,9 +878,9 @@ function applyPluginSchemas(schema: ConfigSchema, plugins: PluginUiMetadata[]): const pluginSchema = asSchemaObject(plugin.configSchema); const nextConfigSchema = baseConfigSchema && - pluginSchema && - isObjectSchema(baseConfigSchema) && - isObjectSchema(pluginSchema) + pluginSchema && + isObjectSchema(baseConfigSchema) && + isObjectSchema(pluginSchema) ? mergeObjectSchema(baseConfigSchema, pluginSchema) : cloneSchema(plugin.configSchema); diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index b3d157355..efffa8ac3 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -177,6 +177,13 @@ export const ClawdbotSchema = z }) .strict() .optional(), + user: z + .object({ + name: z.string().max(50).optional(), + avatar: z.string().max(200).optional(), + }) + .strict() + .optional(), }) .strict() .optional(), diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index fe67c86f1..8187bf8d0 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -111,6 +111,11 @@ export function renderApp(state: AppViewState) { const assistantAvatarUrl = resolveAssistantAvatarUrl(state); const chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null; + // Resolve user identity from config + const uiConfig = (state.configSnapshot?.config as any)?.ui; + const userName = uiConfig?.user?.name; + const userAvatar = uiConfig?.user?.avatar; + return html`
@@ -118,10 +123,10 @@ export function renderApp(state: AppViewState) {
`; - })} + })} diff --git a/ui/src/ui/chat/grouped-render.ts b/ui/src/ui/chat/grouped-render.ts index 4a9ccec14..00b6dd20b 100644 --- a/ui/src/ui/chat/grouped-render.ts +++ b/ui/src/ui/chat/grouped-render.ts @@ -87,14 +87,14 @@ export function renderStreamingGroup( ${renderAvatar("assistant", assistant)}
${renderGroupedMessage( - { - role: "assistant", - content: [{ type: "text", text }], - timestamp: startedAt, - }, - { isStreaming: true, showReasoning: false }, - onOpenSidebar, - )} + { + role: "assistant", + content: [{ type: "text", text }], + timestamp: startedAt, + }, + { isStreaming: true, showReasoning: false }, + onOpenSidebar, + )}
` - : nothing} + : nothing}
${renderAttachmentPreview(props)} @@ -330,15 +334,15 @@ export function renderChat(props: ChatProps) { .value=${props.draft} ?disabled=${!props.connected} @keydown=${(e: KeyboardEvent) => { - if (e.key !== "Enter") return; - if (e.isComposing || e.keyCode === 229) return; - if (e.shiftKey) return; // Allow Shift+Enter for line breaks - if (!props.connected) return; - e.preventDefault(); - if (canCompose) props.onSend(); - }} + if (e.key !== "Enter") return; + if (e.isComposing || e.keyCode === 229) return; + if (e.shiftKey) return; // Allow Shift+Enter for line breaks + if (!props.connected) return; + e.preventDefault(); + if (canCompose) props.onSend(); + }} @input=${(e: Event) => - props.onDraftChange((e.target as HTMLTextAreaElement).value)} + props.onDraftChange((e.target as HTMLTextAreaElement).value)} @paste=${(e: ClipboardEvent) => handlePaste(e, props)} placeholder=${composePlaceholder} >