From 07b4df5544e834d4948bd3275f66d7eb0cbe0f9a Mon Sep 17 00:00:00 2001 From: Jason Tudisco Date: Mon, 9 Mar 2026 08:24:38 -0600 Subject: [PATCH] feat: add profile page with avatar upload and image paste in chat Add user profile page with custom avatar upload (crop/resize to 256px), avatar display throughout the app, and MD5-based Gravatar fallback. Add image paste/attach support in chat with vision model detection via OpenRouter API. Images can be pasted from clipboard or selected via file picker, uploaded to the server, and sent alongside messages. The AI receives images as base64 data URLs for multimodal models. Models with vision support are indicated with a badge in the model picker. Co-Authored-By: Claude Opus 4.6 --- client/src/components/app.riot | 17 +- client/src/components/chat-room.riot | 176 ++++++++++- client/src/components/chat-sidebar.riot | 21 +- client/src/components/create-room-modal.riot | 10 + client/src/components/message-bubble.riot | 29 ++ client/src/components/profile-page.riot | 292 +++++++++++++++++++ client/src/main.js | 2 + client/src/services/api.js | 34 +++ client/src/services/avatar.js | 17 +- client/src/services/websocket.js | 8 +- client/vite.config.js | 4 + server/Cargo.lock | 2 + server/Cargo.toml | 3 +- server/migrations/005_avatar.sql | 2 + server/migrations/006_image_url.sql | 1 + server/src/handlers/auth.rs | 26 +- server/src/handlers/mod.rs | 2 + server/src/handlers/models.rs | 13 + server/src/handlers/profile.rs | 181 ++++++++++++ server/src/handlers/rooms.rs | 26 +- server/src/handlers/upload.rs | 66 +++++ server/src/handlers/ws.rs | 53 +++- server/src/main.rs | 29 +- server/src/models/mod.rs | 6 + server/src/services/openrouter.rs | 55 +++- 25 files changed, 1023 insertions(+), 52 deletions(-) create mode 100644 client/src/components/profile-page.riot create mode 100644 server/migrations/005_avatar.sql create mode 100644 server/migrations/006_image_url.sql create mode 100644 server/src/handlers/profile.rs create mode 100644 server/src/handlers/upload.rs diff --git a/client/src/components/app.riot b/client/src/components/app.riot index 6700488..e241caf 100644 --- a/client/src/components/app.riot +++ b/client/src/components/app.riot @@ -15,6 +15,7 @@ user={state.user} cb-select-room={selectRoom} cb-create-room={() => update({ showCreateModal: true })} + cb-profile={() => update({ showProfileModal: true })} cb-logout={handleLogout} />
@@ -69,6 +70,13 @@ cb-confirm={confirmClearRoom} cb-close={() => update({ showClearModal: false })} /> + + update({ showProfileModal: false })} + /> @@ -157,6 +165,7 @@ showInviteModal: false, showDeleteModal: false, showClearModal: false, + showProfileModal: false, aiTyping: false, aiToolStatus: null, streamingMessage: null, @@ -368,8 +377,8 @@ } }, - sendMessage({ content, mentions }) { - ws.sendMessage(this.state.activeRoomId, content, mentions) + sendMessage({ content, mentions, imageUrl }) { + ws.sendMessage(this.state.activeRoomId, content, mentions, imageUrl) }, handleDeleteRoom(roomId) { @@ -388,6 +397,10 @@ this.update({ messages: [], showClearModal: false }) }, + handleProfileUpdate(user) { + this.update({ user }) + }, + /** Check URL for /invite/:token and stash it for after login if needed */ checkPendingInvite() { const match = window.location.pathname.match(/^\/invite\/(.+)$/) diff --git a/client/src/components/chat-room.riot b/client/src/components/chat-room.riot index d59ceed..2cb1503 100644 --- a/client/src/components/chat-room.riot +++ b/client/src/components/chat-room.riot @@ -118,7 +118,25 @@
+ +
+ Preview + {state.pendingImage?.name || 'Pasted image'} + +
+ +