From bca13a13fa2b1562ddb8f1d5c03773e28645109e Mon Sep 17 00:00:00 2001 From: Acbox Liu Date: Sat, 28 Mar 2026 19:15:39 +0800 Subject: [PATCH] feat(web): introduce a brand new web ui (#281) * feat(web): introduce a brand new web ui * refactor(ui): align chat sidebar and UI components with Figma design - Restyle chat page sidebar: header with icon/title, search input, section labels, and "new session" footer button - Simplify bot-sidebar and session-sidebar to card-based layout matching Figma session card design (58px height, 26px avatar, status dots) - Update master-detail-sidebar-layout with bg-sidebar and 53px header - Unify border-radius across UI components to rounded-lg (8px): Card, Toggle, Alert, Popover, Item; Dialog uses rounded-xl (12px) * refactor(ui): move shared theme and design tokens from web to ui package CSS variables, @theme inline mappings, @custom-variant, and base layer styles now live in @memohai/ui/style.css. The web app imports them via @import "@memohai/ui/style.css", keeping only the Tailwind entry point and web-specific imports (markstream-vue, @source). * refactor(ui): apply flat design system from Figma spec Overhaul @memohai/ui component styles to match the new "high-contrast, flat" design language defined in the Figma design spec (DESIGN.md). Theme: - --primary-foreground: pure white -> #fafafa - --ring: purple -> foreground color (focus rings no longer use brand purple) Atoms (zero shadow, monochrome): - Button: default bg-primary -> bg-foreground; add explicit "primary" variant for Send CTA - Badge: rounded-full -> rounded-sm; default bg-primary -> bg-foreground; add warning/outline/size variants - Alert: rounded-lg -> rounded-[10px]; remove shadow-sm; destructive drops bg-red-50 - Card: add shadow-lg, rounded-lg -> rounded-xl, py-6 -> p-6 - Input/Textarea: remove shadow, text-sm -> text-[16px], focus ring non-purple - Checkbox: checked bg-primary -> bg-foreground - Switch: checked bg-primary -> bg-foreground - RadioGroup: indicator fill-primary -> fill-foreground - Slider: range/thumb border-primary -> border-foreground Floating panels (shadow-md): - DropdownMenu/Combobox/Select/ContextMenu Content: shadow-lg -> shadow-md - Sheet: shadow-2xl -> shadow-lg - MenuItem destructive focus: bg-red-50 -> bg-accent Other: - Pagination active: bg-foreground text-background (black, not purple) - Item variants: bg-transparent -> bg-background/bg-accent - Tabs active: shadow-sm -> border-border - Toggle: remove shadow-xs, unify hover to accent - SelectTrigger/NativeSelect: remove shadow, unify focus ring Docs: - Add packages/ui/DESIGN.md with full design system spec - Simplify apps/web/AGENTS.md, remove duplicated design info, reference DESIGN.md * refactor(chat-ui): restructure chat page components and styles (#288) * refactor(chat-ui): restructure chat page components and styles * feat(chat): add collapsible sidebar for both sides * feat(ui): add PinInput and BadgeCount components, align styles with Figma spec New components: - PinInput (OTP input): PinInput, PinInputGroup, PinInputSlot, PinInputSeparator based on reka-ui PinInput primitives with flat border-stitching design - BadgeCount: circular numeric counter with default/destructive/secondary variants Style updates to match Figma design: - Sonner: border-radius from 1rem to var(--radius-lg) (10px) - Table: add border border-border rounded-sm to container - TagsInput: remove shadow-xs, rounded-md -> rounded-lg, ring-[3px] -> ring-2 Updated DESIGN.md with all new component specifications. * chore: move up css to ui package * refactor: change npm package from @memoh to @memohai * Feat/chat layout (#295) * refactor(chat-ui): restructure chat page components and styles * feat(chat): add collapsible sidebar for both sides * fix: update chat page icon * style: refine UI components appearance * style: refine UI components appearance * chore(ci): update lock file * refactor: new layout * chore: adjust style * fix: tauri ui size * chore: remove bot session metadata * refactor: text size and muted color * fix: indirect height of bot-details pages * feat: add 5 icons * refactor: polish chat flow and settings navigation labels Persist chat selection across pages, simplify provider/settings sidebars, and refine chat/session UX so navigation and composer behavior feel consistent without extra session/provider jumps. * docs(web): refresh AGENTS frontend architecture guide Expand and align the web AGENTS documentation with the current route structure, component inventory, chat transport flow, and store responsibilities so implementation guidance matches the codebase. --------- Co-authored-by: Quincy <69751197+dqygit@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 2 +- apps/desktop/src-tauri/tauri.conf.json | 4 +- apps/web/AGENTS.md | 290 ++++++++++++------ apps/web/package.json | 3 + .../web/src/components/add-provider/index.vue | 4 +- .../src/components/chat/chat-status/index.vue | 32 ++ .../src/components/chat/chat-step/index.vue | 8 + .../web/src/components/create-model/index.vue | 2 +- .../src/components/file-manager/file-list.vue | 2 +- .../components/file-manager/file-viewer.vue | 4 +- .../web/src/components/file-manager/index.vue | 4 +- .../components/import-models-dialog/index.vue | 2 +- .../src/components/main-container/index.vue | 88 +----- .../master-detail-sidebar-layout/index.vue | 104 ++++--- .../searchable-select-popover/index.vue | 6 +- .../src/components/settings-sidebar/index.vue | 139 +++++++++ apps/web/src/components/sidebar/bot-item.vue | 64 ++++ apps/web/src/components/sidebar/index.vue | 198 ++++++------ .../src/components/warning-banner/index.vue | 2 +- apps/web/src/i18n/locales/en.json | 54 ++-- apps/web/src/i18n/locales/zh.json | 8 +- apps/web/src/layout/main-layout/index.vue | 10 +- .../src/pages/bots/components/bot-access.vue | 32 +- .../src/pages/bots/components/bot-card.vue | 4 +- .../pages/bots/components/bot-channels.vue | 10 +- .../pages/bots/components/bot-compaction.vue | 12 +- .../pages/bots/components/bot-container.vue | 34 +- .../src/pages/bots/components/bot-email.vue | 24 +- .../pages/bots/components/bot-heartbeat.vue | 12 +- .../web/src/pages/bots/components/bot-mcp.vue | 20 +- .../src/pages/bots/components/bot-memory.vue | 16 +- .../pages/bots/components/bot-overview.vue | 12 +- .../pages/bots/components/bot-schedule.vue | 10 +- .../pages/bots/components/bot-settings.vue | 26 +- .../src/pages/bots/components/bot-skills.vue | 10 +- .../components/channel-settings-panel.vue | 8 +- .../components/container-create-progress.vue | 2 +- .../src/pages/bots/components/create-bot.vue | 2 +- .../pages/bots/components/message-list.vue | 4 +- .../pages/bots/components/weixin-qr-login.vue | 12 +- apps/web/src/pages/bots/detail.vue | 25 +- apps/web/src/pages/bots/index.vue | 2 +- .../components/context-setting.vue | 6 +- apps/web/src/pages/browser-contexts/index.vue | 30 +- .../src/pages/chat/components/bot-sidebar.vue | 83 ----- .../pages/chat/components/session-sidebar.vue | 245 --------------- apps/web/src/pages/chat/index.vue | 84 ----- .../components/provider-setting.vue | 4 +- apps/web/src/pages/email-providers/index.vue | 30 +- .../components/attachment-block.vue | 6 +- .../src/pages/home/components/bot-item.vue | 153 +++++++++ .../src/pages/home/components/bot-sidebar.vue | 192 ++++++++++++ .../{chat => home}/components/chat-area.vue | 42 +-- .../src/pages/home/components/chat-header.vue | 28 ++ .../components/media-gallery-lightbox.vue | 2 +- .../components/message-item.vue | 91 ++---- .../pages/home/components/session-item.vue | 171 +++++++++++ .../home/components/session-metadata.vue | 194 ++++++++++++ .../pages/home/components/session-sidebar.vue | 203 ++++++++++++ .../components/thinking-block.vue | 4 +- .../components/tool-call-block.vue | 0 .../components/tool-call-browser.vue | 2 +- .../components/tool-call-contacts.vue | 2 +- .../components/tool-call-edit.vue | 2 +- .../components/tool-call-email.vue | 2 +- .../components/tool-call-exec.vue | 2 +- .../components/tool-call-generic.vue | 2 +- .../components/tool-call-list.vue | 2 +- .../components/tool-call-memory.vue | 2 +- .../components/tool-call-message.vue | 4 +- .../components/tool-call-read.vue | 2 +- .../components/tool-call-schedule.vue | 2 +- .../components/tool-call-skill.vue | 2 +- .../components/tool-call-subagent.vue | 2 +- .../components/tool-call-web-fetch.vue | 2 +- .../components/tool-call-web-search.vue | 2 +- .../components/tool-call-write.vue | 2 +- .../composables/useFileManagerProvider.ts | 0 .../composables/useMediaGallery.ts | 0 apps/web/src/pages/home/index.vue | 83 ++++- apps/web/src/pages/login/index.vue | 4 +- .../components/provider-setting.vue | 28 +- apps/web/src/pages/memory-providers/index.vue | 28 +- .../pages/models/components/provider-form.vue | 45 +-- apps/web/src/pages/models/index.vue | 52 +--- apps/web/src/pages/models/model-setting.vue | 48 ++- apps/web/src/pages/oauth/mcp-callback.vue | 2 +- .../platform/components/platform-card.vue | 2 +- .../components/provider-setting.vue | 4 +- apps/web/src/pages/search-providers/index.vue | 34 +- apps/web/src/pages/settings-section/index.vue | 26 ++ .../settings/components/bind-code-section.vue | 4 +- .../settings/components/password-section.vue | 4 +- .../settings/components/profile-section.vue | 4 +- apps/web/src/pages/settings/index.vue | 18 +- .../components/model-config-editor.vue | 8 +- .../components/provider-setting.vue | 8 +- apps/web/src/pages/tts-providers/index.vue | 30 +- apps/web/src/pages/usage/index.vue | 8 +- apps/web/src/router.ts | 42 +-- apps/web/src/store/chat-list.ts | 27 +- apps/web/src/store/chat-selection.ts | 28 ++ apps/web/src/style.css | 151 +-------- apps/web/src/utils/useControlVisibleStatus.ts | 22 ++ apps/web/vite.config.ts | 5 +- .../adapters/telegram/telegram_test.go | 1 + internal/channel/inbound/channel_test.go | 1 + internal/channel/outbound_test.go | 1 + .../conversation/service_integration_test.go | 1 + .../memory/adapters/builtin/builtin_test.go | 1 + .../memory/adapters/builtin/formation_test.go | 1 + packages/icons/icons/discord.svg | 2 +- packages/icons/icons/feishu.svg | 2 +- packages/icons/icons/qq.svg | 2 +- packages/icons/icons/telegram.svg | 2 +- packages/icons/icons/wechat.svg | 10 +- packages/icons/src/icons/BedrockColor.vue | 2 +- packages/icons/src/icons/Discord.vue | 8 +- packages/icons/src/icons/Feishu.vue | 17 +- packages/icons/src/icons/Qq.vue | 23 +- packages/icons/src/icons/Telegram.vue | 32 +- packages/icons/src/icons/Wechat.vue | 11 +- packages/ui/DESIGN.md | 115 +++++++ packages/ui/README.md | 30 ++ packages/ui/package.json | 1 + .../src/components/alert/AlertDescription.vue | 2 +- packages/ui/src/components/alert/index.ts | 6 +- packages/ui/src/components/badge/Badge.vue | 3 +- .../ui/src/components/badge/BadgeCount.vue | 46 +++ packages/ui/src/components/badge/index.ts | 18 +- .../breadcrumb/BreadcrumbEllipsis.vue | 2 +- .../components/breadcrumb/BreadcrumbLink.vue | 2 +- .../components/breadcrumb/BreadcrumbList.vue | 2 +- .../breadcrumb/BreadcrumbSeparator.vue | 2 +- .../button-group/ButtonGroupText.vue | 2 +- .../ui/src/components/button-group/index.ts | 7 +- packages/ui/src/components/button/index.ts | 13 +- packages/ui/src/components/card/Card.vue | 2 +- .../src/components/card/CardDescription.vue | 2 +- .../ui/src/components/checkbox/Checkbox.vue | 6 +- .../collapsible/CollapsibleContent.vue | 1 + .../src/components/combobox/ComboboxEmpty.vue | 2 +- .../src/components/combobox/ComboboxInput.vue | 2 +- .../src/components/combobox/ComboboxItem.vue | 2 +- .../src/components/combobox/ComboboxList.vue | 2 +- .../ui/src/components/command/Command.vue | 87 ++++++ .../src/components/command/CommandDialog.vue | 34 ++ .../src/components/command/CommandEmpty.vue | 28 ++ .../src/components/command/CommandGroup.vue | 49 +++ .../src/components/command/CommandInput.vue | 39 +++ .../ui/src/components/command/CommandItem.vue | 76 +++++ .../ui/src/components/command/CommandList.vue | 25 ++ .../components/command/CommandSeparator.vue | 21 ++ .../components/command/CommandShortcut.vue | 17 + packages/ui/src/components/command/index.ts | 25 ++ .../context-menu/ContextMenuCheckboxItem.vue | 2 +- .../context-menu/ContextMenuContent.vue | 2 +- .../context-menu/ContextMenuItem.vue | 4 +- .../context-menu/ContextMenuLabel.vue | 2 +- .../context-menu/ContextMenuRadioItem.vue | 2 +- .../context-menu/ContextMenuSubTrigger.vue | 2 +- .../src/components/dialog/DialogContent.vue | 4 +- .../components/dialog/DialogDescription.vue | 2 +- .../ui/src/components/dialog/DialogTitle.vue | 2 +- .../DropdownMenuCheckboxItem.vue | 2 +- .../dropdown-menu/DropdownMenuContent.vue | 2 +- .../dropdown-menu/DropdownMenuItem.vue | 4 +- .../dropdown-menu/DropdownMenuLabel.vue | 2 +- .../dropdown-menu/DropdownMenuRadioItem.vue | 2 +- .../dropdown-menu/DropdownMenuSubTrigger.vue | 2 +- .../ui/src/components/empty/EmptyContent.vue | 2 +- .../src/components/empty/EmptyDescription.vue | 2 +- .../ui/src/components/empty/EmptyTitle.vue | 2 +- .../src/components/form/FormDescription.vue | 2 +- .../ui/src/components/form/FormMessage.vue | 2 +- .../components/input-group/InputGroupText.vue | 2 +- .../ui/src/components/input-group/index.ts | 4 +- .../ui/src/components/input-otp/InputOTP.vue | 28 ++ .../components/input-otp/InputOTPGroup.vue | 22 ++ .../input-otp/InputOTPSeparator.vue | 21 ++ .../src/components/input-otp/InputOTPSlot.vue | 35 +++ packages/ui/src/components/input-otp/index.ts | 4 + packages/ui/src/components/input/Input.vue | 8 +- .../src/components/item/ItemDescription.vue | 2 +- packages/ui/src/components/item/ItemTitle.vue | 2 +- packages/ui/src/components/item/index.ts | 8 +- packages/ui/src/components/label/Label.vue | 2 +- .../components/native-select/NativeSelect.vue | 4 +- .../components/pagination/PaginationItem.vue | 7 +- .../ui/src/components/pin-input/PinInput.vue | 23 ++ .../components/pin-input/PinInputGroup.vue | 17 + .../pin-input/PinInputSeparator.vue | 21 ++ .../src/components/pin-input/PinInputSlot.vue | 31 ++ packages/ui/src/components/pin-input/index.ts | 4 + .../src/components/popover/PopoverContent.vue | 2 +- .../components/radio-group/RadioGroupItem.vue | 6 +- .../src/components/select/SelectContent.vue | 2 +- .../ui/src/components/select/SelectItem.vue | 2 +- .../src/components/select/SelectTrigger.vue | 4 +- .../ui/src/components/sheet/SheetContent.vue | 14 +- .../src/components/sheet/SheetDescription.vue | 2 +- .../sidebar/SidebarGroupContent.vue | 2 +- .../src/components/sidebar/SidebarHeader.vue | 2 +- .../sidebar/SidebarMenuSubButton.vue | 2 +- packages/ui/src/components/sidebar/index.ts | 16 +- packages/ui/src/components/sidebar/utils.ts | 2 +- packages/ui/src/components/slider/Slider.vue | 6 +- packages/ui/src/components/sonner/Sonner.vue | 2 +- packages/ui/src/components/switch/Switch.vue | 12 +- packages/ui/src/components/table/Table.vue | 4 +- .../ui/src/components/table/TableCaption.vue | 2 +- .../ui/src/components/table/TableCell.vue | 2 +- .../ui/src/components/table/TableEmpty.vue | 2 +- .../ui/src/components/table/TableFooter.vue | 2 +- .../ui/src/components/table/TableHead.vue | 2 +- .../ui/src/components/table/TableHeader.vue | 2 +- packages/ui/src/components/table/TableRow.vue | 2 +- .../ui/src/components/tabs/TabsTrigger.vue | 6 +- .../src/components/tags-input/TagsInput.vue | 4 +- .../components/tags-input/TagsInputInput.vue | 2 +- .../tags-input/TagsInputItemText.vue | 2 +- .../ui/src/components/textarea/Textarea.vue | 6 +- packages/ui/src/components/toggle/index.ts | 4 +- packages/ui/src/index.ts | 4 + packages/ui/src/style.css | 147 ++++++++- pnpm-lock.yaml | 44 +++ 226 files changed, 3377 insertions(+), 1639 deletions(-) create mode 100644 apps/web/src/components/chat/chat-status/index.vue create mode 100644 apps/web/src/components/chat/chat-step/index.vue create mode 100644 apps/web/src/components/settings-sidebar/index.vue create mode 100644 apps/web/src/components/sidebar/bot-item.vue delete mode 100644 apps/web/src/pages/chat/components/bot-sidebar.vue delete mode 100644 apps/web/src/pages/chat/components/session-sidebar.vue delete mode 100644 apps/web/src/pages/chat/index.vue rename apps/web/src/pages/{chat => home}/components/attachment-block.vue (96%) create mode 100755 apps/web/src/pages/home/components/bot-item.vue create mode 100644 apps/web/src/pages/home/components/bot-sidebar.vue rename apps/web/src/pages/{chat => home}/components/chat-area.vue (90%) create mode 100644 apps/web/src/pages/home/components/chat-header.vue rename apps/web/src/pages/{chat => home}/components/media-gallery-lightbox.vue (98%) rename apps/web/src/pages/{chat => home}/components/message-item.vue (73%) create mode 100644 apps/web/src/pages/home/components/session-item.vue create mode 100644 apps/web/src/pages/home/components/session-metadata.vue create mode 100644 apps/web/src/pages/home/components/session-sidebar.vue rename apps/web/src/pages/{chat => home}/components/thinking-block.vue (90%) rename apps/web/src/pages/{chat => home}/components/tool-call-block.vue (100%) rename apps/web/src/pages/{chat => home}/components/tool-call-browser.vue (98%) rename apps/web/src/pages/{chat => home}/components/tool-call-contacts.vue (97%) rename apps/web/src/pages/{chat => home}/components/tool-call-edit.vue (98%) rename apps/web/src/pages/{chat => home}/components/tool-call-email.vue (98%) rename apps/web/src/pages/{chat => home}/components/tool-call-exec.vue (98%) rename apps/web/src/pages/{chat => home}/components/tool-call-generic.vue (97%) rename apps/web/src/pages/{chat => home}/components/tool-call-list.vue (96%) rename apps/web/src/pages/{chat => home}/components/tool-call-memory.vue (97%) rename apps/web/src/pages/{chat => home}/components/tool-call-message.vue (97%) rename apps/web/src/pages/{chat => home}/components/tool-call-read.vue (96%) rename apps/web/src/pages/{chat => home}/components/tool-call-schedule.vue (97%) rename apps/web/src/pages/{chat => home}/components/tool-call-skill.vue (95%) rename apps/web/src/pages/{chat => home}/components/tool-call-subagent.vue (98%) rename apps/web/src/pages/{chat => home}/components/tool-call-web-fetch.vue (98%) rename apps/web/src/pages/{chat => home}/components/tool-call-web-search.vue (98%) rename apps/web/src/pages/{chat => home}/components/tool-call-write.vue (98%) rename apps/web/src/pages/{chat => home}/composables/useFileManagerProvider.ts (100%) rename apps/web/src/pages/{chat => home}/composables/useMediaGallery.ts (100%) create mode 100644 apps/web/src/pages/settings-section/index.vue create mode 100644 apps/web/src/store/chat-selection.ts create mode 100644 apps/web/src/utils/useControlVisibleStatus.ts create mode 100644 packages/ui/DESIGN.md create mode 100644 packages/ui/src/components/badge/BadgeCount.vue create mode 100644 packages/ui/src/components/command/Command.vue create mode 100644 packages/ui/src/components/command/CommandDialog.vue create mode 100644 packages/ui/src/components/command/CommandEmpty.vue create mode 100644 packages/ui/src/components/command/CommandGroup.vue create mode 100644 packages/ui/src/components/command/CommandInput.vue create mode 100644 packages/ui/src/components/command/CommandItem.vue create mode 100644 packages/ui/src/components/command/CommandList.vue create mode 100644 packages/ui/src/components/command/CommandSeparator.vue create mode 100644 packages/ui/src/components/command/CommandShortcut.vue create mode 100644 packages/ui/src/components/command/index.ts create mode 100644 packages/ui/src/components/input-otp/InputOTP.vue create mode 100644 packages/ui/src/components/input-otp/InputOTPGroup.vue create mode 100644 packages/ui/src/components/input-otp/InputOTPSeparator.vue create mode 100644 packages/ui/src/components/input-otp/InputOTPSlot.vue create mode 100644 packages/ui/src/components/input-otp/index.ts create mode 100644 packages/ui/src/components/pin-input/PinInput.vue create mode 100644 packages/ui/src/components/pin-input/PinInputGroup.vue create mode 100644 packages/ui/src/components/pin-input/PinInputSeparator.vue create mode 100644 packages/ui/src/components/pin-input/PinInputSlot.vue create mode 100644 packages/ui/src/components/pin-input/index.ts diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index fb9c22d8..f36b7f16 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -2,7 +2,7 @@ use tauri::LogicalSize; #[tauri::command] fn resize_for_route(window: tauri::Window, route: String) { - let is_login = route == "/login" || route == "/"; + let is_login = route == "/login"; if is_login { let _ = window.set_min_size(None::); let _ = window.set_size(tauri::Size::Logical(LogicalSize::new(480.0, 700.0))); diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index be5060be..6435a070 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -13,8 +13,8 @@ "windows": [ { "title": "Memoh", - "width": 480, - "height": 700 + "width": 1280, + "height": 800 } ], "security": { diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md index 132ef13e..2f2d7c0d 100644 --- a/apps/web/AGENTS.md +++ b/apps/web/AGENTS.md @@ -2,7 +2,7 @@ ## Overview -`@memohai/web` is the management UI for Memoh, built with Vue 3 + Vite. It provides visual configuration for bots, models, channels, memory, and more. +`@memohai/web` is the management UI for Memoh, built with Vue 3 + Vite. It provides a chat interface for interacting with bots, plus visual configuration for bots, models, channels, memory, and more. ## Tech Stack @@ -16,7 +16,7 @@ | Data Fetching | Pinia Colada (`@pinia/colada`) + `@memohai/sdk` | | Forms | vee-validate + `@vee-validate/zod` + Zod | | i18n | vue-i18n (en / zh) | -| Icons | FontAwesome (primary) + lucide-vue-next (secondary) | +| Icons | FontAwesome (primary: fas/far/fab) + lucide-vue-next (secondary) | | Toast | vue-sonner | | Tables | @tanstack/vue-table | | Markdown | markstream-vue + Shiki + Mermaid + KaTeX | @@ -27,38 +27,66 @@ ``` src/ -├── App.vue # Root component (RouterView + Toaster) +├── App.vue # Root component (RouterView + Toaster + settings init) ├── main.ts # App entry (plugins, global components, API client setup) -├── router.ts # Route definitions and auth guard -├── style.css # Tailwind imports, CSS variables, theme tokens +├── router.ts # Route definitions, auth guard, chunk error recovery +├── style.css # Tailwind imports (delegates to @memohai/ui/style.css) ├── i18n.ts # vue-i18n configuration -├── assets/ # Static assets +├── assets/ # Static assets (logo.png, vue.svg) ├── components/ # Shared components -│ ├── sidebar/ # App sidebar navigation -│ ├── main-container/ # Main content area (header + breadcrumb + content) +│ ├── sidebar/ # Bot list sidebar (collapsible, bot items, settings link) +│ │ ├── index.vue # Sidebar with bot list + settings gear footer +│ │ └── bot-item.vue # Individual bot entry in sidebar +│ ├── settings-sidebar/ # Settings section sidebar (back-to-chat + nav items) +│ ├── main-container/ # Main content area (KeepAlive RouterView) │ ├── master-detail-sidebar-layout/ # Master-detail layout pattern +│ ├── chat-list/ # Chat list helpers +│ │ └── channel-badge/ # Channel badge component +│ ├── chat/ # Chat UI sub-components +│ │ ├── chat-status/ # Chat connection status indicator +│ │ └── chat-step/ # Chat processing step indicator │ ├── data-table/ # TanStack table wrapper +│ ├── file-manager/ # File browser (list + viewer) +│ ├── monaco-editor/ # Monaco code editor wrapper │ ├── form-dialog-shell/ # Dialog wrapper for forms │ ├── confirm-popover/ # Confirmation popover │ ├── loading-button/ # Button with loading state │ ├── status-dot/ # Status indicator dot │ ├── warning-banner/ # Warning banner -│ ├── search-provider-logo/ # Search provider icons +│ ├── channel-icon/ # Channel platform icon +│ ├── provider-icon/ # LLM provider icon (icons.ts + index.vue) +│ ├── search-provider-logo/ # Search provider icons (custom-icons.ts + index.vue) │ ├── searchable-select-popover/ # Searchable dropdown +│ ├── timezone-select/ # Timezone selector +│ ├── key-value-editor/ # Key-value pair editor +│ ├── import-models-dialog/ # Bulk model import dialog │ ├── add-platform/ # Add platform dialog │ ├── add-provider/ # Add LLM provider dialog -│ ├── create-model/ # Create model dialog -│ └── chat-list/ # Chat list helpers +│ └── create-model/ # Create model dialog ├── composables/ # Reusable composition functions -│ ├── api/ # API-related composables (chat, SSE, platform) +│ ├── api/ # API-related composables +│ │ ├── useChat.ts # Aggregated re-export of chat composables +│ │ ├── useChat.types.ts # Bot, Session, Message, StreamEvent types +│ │ ├── useChat.chat-api.ts # Bot/session CRUD (fetchBots, fetchSessions, etc.) +│ │ ├── useChat.message-api.ts # Message fetch, SSE streaming, local channel +│ │ ├── useChat.sse.ts # SSE stream reader and parser +│ │ ├── useChat.ws.ts # WebSocket connection (send, abort, reconnect) +│ │ ├── useChat.content.ts # Message content parsing (tool calls, text, reasoning) +│ │ ├── useChat.sse.test.ts # SSE parser tests +│ │ ├── useContainerStream.ts # Container creation SSE stream +│ │ └── usePlatform.ts # Platform list query + create mutation │ ├── useDialogMutation.ts # Mutation wrapper with toast error handling │ ├── useRetryingStream.ts # SSE retry with exponential backoff │ ├── useSyncedQueryParam.ts # URL query param sync │ ├── useBotStatusMeta.ts # Bot status metadata │ ├── useAvatarInitials.ts # Avatar initial generation │ ├── useClipboard.ts # Clipboard utilities -│ └── useKeyValueTags.ts # Tag management -├── constants/ # Constants (client types, etc.) +│ ├── useKeyValueTags.ts # Tag management +│ ├── useShikiHighlighter.ts # Shiki syntax highlighter singleton +│ └── useTerminalCache.ts # Terminal output cache +├── constants/ # Constants +│ ├── client-types.ts # LLM client type definitions +│ └── compatibilities.ts # Feature compatibility flags ├── i18n/locales/ # Translation files (en.json, zh.json) ├── layout/ │ └── main-layout/ # Top-level layout (SidebarProvider) @@ -66,105 +94,169 @@ src/ │ └── api-client.ts # SDK client setup (base URL, auth interceptor) ├── pages/ # Route page components │ ├── login/ # Login page -│ ├── main-section/ # Authenticated layout wrapper -│ ├── home/ # Home page -│ ├── chat/ # Chat interface (SSE streaming) +│ ├── main-section/ # Chat section layout (bot sidebar + main container) +│ ├── settings-section/ # Settings section layout (settings sidebar + KeepAlive) +│ ├── home/ # Chat interface (used by both `/` and `/chat/:botId?/:sessionId?`) +│ │ ├── index.vue # Route ↔ store sync, session sidebar + chat area +│ │ ├── composables/ # Page-specific composables +│ │ │ ├── useFileManagerProvider.ts # File manager context +│ │ │ └── useMediaGallery.ts # Media gallery state +│ │ └── components/ # Chat UI components (26 files) +│ │ ├── chat-area.vue # Main chat area (messages, input, attachments) +│ │ ├── session-sidebar.vue # Session list sidebar (search, filter, CRUD) +│ │ ├── bot-sidebar.vue # Alternative bot sidebar layout +│ │ ├── chat-header.vue # Chat top bar (status, step indicator) +│ │ ├── message-item.vue # Single message (user/assistant, markdown, blocks) +│ │ ├── session-item.vue # Session list row (avatar, title, timestamp) +│ │ ├── session-metadata.vue # Collapsible session metadata panel +│ │ ├── bot-item.vue # Bot item in sidebar +│ │ ├── thinking-block.vue # Collapsible thinking/reasoning block +│ │ ├── attachment-block.vue # Attachment grid (images, audio, files) +│ │ ├── media-gallery-lightbox.vue # Fullscreen media lightbox +│ │ ├── tool-call-generic.vue # Generic tool call (name, status, JSON I/O) +│ │ ├── tool-call-list.vue # File listing tool display +│ │ ├── tool-call-read.vue # File read tool display +│ │ ├── tool-call-write.vue # File write tool display +│ │ ├── tool-call-edit.vue # File edit tool display +│ │ ├── tool-call-exec.vue # Command execution tool display +│ │ ├── tool-call-web-search.vue # Web search tool display +│ │ ├── tool-call-web-fetch.vue # Web fetch tool display +│ │ ├── tool-call-browser.vue # Browser action tool display +│ │ ├── tool-call-memory.vue # Memory read/write tool display +│ │ ├── tool-call-message.vue # Send message tool display +│ │ ├── tool-call-email.vue # Email tool display +│ │ ├── tool-call-schedule.vue # Schedule tool display +│ │ ├── tool-call-contacts.vue # Contacts tool display +│ │ ├── tool-call-subagent.vue # Sub-agent tool display +│ │ └── tool-call-skill.vue # Skill activation tool display │ ├── bots/ # Bot list + detail (tabs: overview, memory, channels, etc.) +│ │ ├── index.vue # Bot grid with create dialog +│ │ ├── detail.vue # Bot detail with tabbed interface +│ │ └── components/ # Bot sub-components (25+ files) │ ├── models/ # LLM provider & model management -│ ├── search-providers/ # Search provider management +│ ├── search-providers/ # Search provider management (12+ engine configs) +│ ├── memory-providers/ # Memory provider management +│ ├── tts-providers/ # TTS provider & model management │ ├── email-providers/ # Email provider management -│ ├── settings/ # User settings (profile, password, theme, channels) -│ └── platform/ # Platform management +│ ├── browser-contexts/ # Browser context management +│ ├── usage/ # Token usage statistics +│ ├── settings/ # User settings (profile, password, bind codes) +│ ├── platform/ # Platform management +│ └── oauth/ # OAuth callback pages +│ └── mcp-callback.vue # MCP OAuth callback handler ├── store/ # Pinia stores │ ├── user.ts # User state, JWT token, login/logout │ ├── settings.ts # UI settings (theme, language) │ ├── capabilities.ts # Server capabilities (container backend) -│ └── chat-list.ts # Chat state, messages, SSE streaming +│ ├── chat-selection.ts # Current bot/session selection (localStorage persisted) +│ └── chat-list.ts # Chat messages, streaming state, SSE/WS event processing └── utils/ # Utility functions ├── api-error.ts # API error message extraction ├── date-time.ts # Date/time formatting + ├── date-time.test.ts # Date/time tests ├── channel-icons.ts # Channel platform icons - └── key-value-tags.ts # Tag ↔ Record conversion + ├── key-value-tags.ts # Tag ↔ Record conversion + ├── key-value-tags.test.ts # Tag conversion tests + ├── image-ref.ts # Image reference URL resolution + ├── image-ref.test.ts # Image ref tests + ├── timezones.ts # Timezone list and utilities + └── useControlVisibleStatus.ts # Visibility control utility ``` ## Routes +The app uses a two-section layout architecture: + +### Chat Section (`/`) + +| Path | Name | Component | Description | +|------|------|-----------|-------------| +| `/` | home | `home/index.vue` | Home — empty state when no bot selected | +| `/chat/:botId?/:sessionId?` | chat | `home/index.vue` | Chat interface with bot + session params | + +Both routes render the same `home/index.vue` component. The `home` route shows an empty state; the `chat` route auto-selects a bot and optionally a session based on URL params. URL and store state are bidirectionally synced. + +### Settings Section (`/settings`) + +| Path | Name | Component | Description | +|------|------|-----------|-------------| +| `/settings/bots` | bots | `bots/index.vue` | Bot list grid | +| `/settings/bots/:botId` | bot-detail | `bots/detail.vue` | Bot detail with tabs | +| `/settings/models` | models | `models/index.vue` | LLM provider & model management | +| `/settings/search-providers` | search-providers | `search-providers/index.vue` | Search provider management | +| `/settings/memory-providers` | memory-providers | `memory-providers/index.vue` | Memory provider management | +| `/settings/tts-providers` | tts-providers | `tts-providers/index.vue` | TTS provider & model management | +| `/settings/email-providers` | email-providers | `email-providers/index.vue` | Email provider management | +| `/settings/browser-contexts` | browser-contexts | `browser-contexts/index.vue` | Browser context management | +| `/settings/usage` | usage | `usage/index.vue` | Token usage statistics | +| `/settings/profile` | settings | `settings/index.vue` | User profile settings | +| `/settings/platform` | platform | `platform/index.vue` | Platform management | + +`/settings` redirects to `/settings/bots` by default. + +### Standalone Routes + | Path | Name | Component | Description | |------|------|-----------|-------------| | `/login` | Login | `login/index.vue` | Login form (no auth required) | -| `/chat` | chat | `chat/index.vue` | Chat interface with bot sidebar | -| `/home` | home | `home/index.vue` | Home dashboard | -| `/bots` | bots | `bots/index.vue` | Bot list grid | -| `/bots/:botId` | bot-detail | `bots/detail.vue` | Bot detail with tabs | -| `/models` | models | `models/index.vue` | LLM provider & model management | -| `/search-providers` | search-providers | `search-providers/index.vue` | Search provider management | -| `/email-providers` | email-providers | `email-providers/index.vue` | Email provider management | -| `/settings` | settings | `settings/index.vue` | User settings | -| `/platform` | platform | `platform/index.vue` | Platform management | +| `/oauth/mcp/callback` | oauth-mcp-callback | `oauth/mcp-callback.vue` | MCP OAuth callback (no auth required) | -Auth guard: all routes except `/login` require `localStorage.getItem('token')`. Logged-in users accessing `/login` are redirected to `/chat`. +### Auth Guard + +- All routes except `/login` and `/oauth/*` require `localStorage.getItem('token')`. +- Logged-in users accessing `/login` are redirected to `/`. +- Chunk load errors (dynamic import failures) trigger an automatic page reload. +- Tauri integration: `afterEach` hook calls `resize_for_route` when running inside Tauri. ## Layout System -Three-tier layout architecture: +Two-section layout architecture, both sharing the same `MainLayout` wrapper: 1. **MainLayout** (`layout/main-layout/`) — Top-level wrapper using `SidebarProvider` from `@memohai/ui`. Provides `#sidebar` and `#main` slots. -2. **Sidebar** (`components/sidebar/`) — Collapsible navigation with logo, menu items, and user avatar footer. Active route highlighting. -3. **MainContainer** (`components/main-container/`) — Header (sidebar trigger + breadcrumb) + scrollable content area with `` wrapped ``. -Several pages use **MasterDetailSidebarLayout** (`components/master-detail-sidebar-layout/`) for left-sidebar + detail-panel patterns (chat, models, search providers, email providers). +2. **Chat Section** (`pages/main-section/`) — Uses `MainLayout` with: + - **Sidebar** (`components/sidebar/`) — Bot list sidebar (collapsible). Header shows "Bots" label + create button. Body lists all bots as `BotItem` entries. Footer has a settings gear link to `/settings`. + - **MainContainer** (`components/main-container/`) — `` wrapped `` for chat pages. + +3. **Settings Section** (`pages/settings-section/`) — Uses `MainLayout` with: + - **SettingsSidebar** (`components/settings-sidebar/`) — Collapsible settings navigation. Top has a "back to chat" button that restores the last selected bot/session. Menu items: Bots, Models, Search Providers, Memory Providers, TTS Providers, Email Providers, Browser Contexts, Usage, Settings, Platform. + - **SidebarInset** — `` wrapped `` for settings pages. + +4. **Home/Chat Page** (`pages/home/`) — Internal layout: + - **SessionSidebar** — Left panel: session search, source filter, new session button, session list. + - **ChatArea** — Center panel: message list with scroll, input area with attachments. + - **SessionMetadata** — Right panel (currently disabled): collapsible session metadata. + +Several settings pages use **MasterDetailSidebarLayout** (`components/master-detail-sidebar-layout/`) for left-sidebar + detail-panel patterns (models, search providers, email providers, memory providers, tts providers, browser contexts). ## CSS & Theming +Design tokens, color palette, typography, elevation strategy, and component visual specs are defined in `packages/ui/DESIGN.md`. **Read that file before making any UI changes.** + ### Tailwind CSS 4 -CSS-based configuration in `style.css` (no `tailwind.config.*` file): +CSS-based configuration (no `tailwind.config.*` file). All design tokens (CSS variables, `@theme inline` mapping, base styles) live in `packages/ui/src/style.css`. The web app imports them via: ```css -@import "tailwindcss"; +@import "@memohai/ui/style.css"; ``` -Design tokens are CSS custom properties in `:root` / `.dark` using OKLCH color space with a unified subtle purple hue (285): -- Colors: `--background`, `--foreground`, `--primary`, `--secondary`, `--muted`, `--accent`, `--destructive`, `--border`, `--input`, `--ring` -- Sidebar: `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, etc. -- Radius: `--radius` with size variants (`--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`) -- Chart: `--chart-1` through `--chart-5` - -Tokens are mapped to Tailwind via `@theme inline` block in `style.css`. - -### Color Design Principles - -The color system avoids extreme black/white to reduce visual strain and create a layered, warm feel: - -- **No pure black or pure white**: Light mode foreground is dark charcoal (L=25%), not black. Dark mode background is dark gray (L=20.5%), not black. Dark mode foreground is soft off-white (L=88%), not pure white. -- **Subtle color temperature**: All neutral grays carry a tiny chroma (0.004–0.006) at hue 285 (purple), preventing a sterile "dead gray" feel. -- **Text hierarchy through lightness**: Three distinct levels — `text-foreground` (primary text), `text-secondary-foreground` (emphasis text on secondary surfaces), `text-muted-foreground` (secondary/helper text). -- **Surface layering**: `background` < `card`/`popover` — card surfaces are slightly lighter than the page background for visual depth. - ### Dark Mode -- CSS: `@custom-variant dark (&:is(.dark *))` in `style.css` - Runtime: `useColorMode` from `@vueuse/core` in `store/settings.ts` - Storage: theme preference persisted via `useStorage` - Toggle: Available in Settings page and login page -- Usage: semantic tokens auto-switch; no `dark:` prefix needed for themed colors +- Usage: semantic tokens auto-switch; no `dark:` prefix needed -### Styling Convention +### Styling Rules -- **Utility-first**: Tailwind classes as primary styling method. Minimal `