From 4baccd5d88fd0da7d5ea3d4acb8594fcf936a8d9 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 17 Oct 2026 09:45:30 +0800 Subject: [PATCH] Phase 005: vendor ArkScope, land the Arco style baseline, and fix bridge interpreter resolution - Vendor ArkScope decorative layers (ScrambleTitle / IsometricWave) with provenance notes; decorative-only per AGENTS.md red lines - Migrate theme tokens to Arco, register the Arco theme, and add hook adapter composables (useMessage / useDialog / useNotification) with matching signatures - Sample pages: LoginView and AppSidebar on the new baseline; docs/arco-mapping.md and docs/ui-baseline.md land - agent-bridge: prefer the PM-selected venv from facts.json over legacy in-tree venvs (legacy lacks the mcp extra, so every MCP server showed 0 tools / not connected) - agent-bridge python (vendored, deliberate deviation): MCP discovery import fallback and pyyaml -> hermes_yaml so plugins and MCP work under the PM env; install the mcp extra via `hermes pm install --extra mcp` - Chat-chain fragment: docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md Verified: harness:check, test (2020 passed), test:e2e (30 passed), build all green; manual: plugins 100/0 warnings, MCP 6/6 servers connected with 104 tools. --- AGENTS.md | 4 + docs/arco-mapping.md | 65 +++++ .../2026-10-17-phase005-arkscope-baseline.md | 61 +++++ docs/phases/phase_005.md | 2 +- docs/ui-baseline.md | 134 +++++++++ package-lock.json | 129 ++++++++- package.json | 1 + packages/client/index.html | 2 + packages/client/src/App.vue | 53 ++-- .../src/components/auth/AuthEventListener.vue | 2 +- .../src/components/hermes/chat/ChatInput.vue | 3 +- .../src/components/hermes/chat/ChatPanel.vue | 15 +- .../components/hermes/chat/FolderPicker.vue | 4 +- .../hermes/chat/MarkdownRenderer.vue | 3 +- .../components/hermes/chat/MessageItem.vue | 3 +- .../hermes/chat/SessionSearchModal.vue | 3 +- .../components/hermes/chat/TerminalPanel.vue | 3 +- .../hermes/files/FileContextMenu.vue | 4 +- .../components/hermes/files/FileEditor.vue | 4 +- .../src/components/hermes/files/FileList.vue | 3 +- .../components/hermes/files/FilePreview.vue | 3 +- .../hermes/files/FileRenameModal.vue | 3 +- .../components/hermes/files/FileToolbar.vue | 3 +- .../hermes/files/FileUploadModal.vue | 3 +- .../hermes/group-chat/GroupChatPanel.vue | 3 +- .../hermes/group-chat/GroupMessageItem.vue | 2 +- .../src/components/hermes/jobs/JobCard.vue | 3 +- .../components/hermes/jobs/JobFormModal.vue | 3 +- .../hermes/kanban/KanbanCreateForm.vue | 3 +- .../hermes/kanban/KanbanTaskDrawer.vue | 3 +- .../hermes/models/AnthropicLoginModal.vue | 3 +- .../hermes/models/AuxiliaryModelsPanel.vue | 3 +- .../hermes/models/CodexLoginModal.vue | 3 +- .../hermes/models/CombinationModelsPanel.vue | 3 +- .../hermes/models/CopilotLoginModal.vue | 3 +- .../hermes/models/GeminiLoginModal.vue | 3 +- .../hermes/models/NousLoginModal.vue | 3 +- .../components/hermes/models/ProviderCard.vue | 4 +- .../hermes/models/ProviderFormModal.vue | 4 +- .../hermes/models/XaiOAuthLoginModal.vue | 3 +- .../hermes/profiles/ProfileCard.vue | 4 +- .../hermes/profiles/ProfileCreateModal.vue | 3 +- .../hermes/profiles/ProfileImportModal.vue | 3 +- .../hermes/profiles/ProfileRenameModal.vue | 3 +- .../hermes/settings/AccountSettings.vue | 3 +- .../hermes/settings/AgentSettings.vue | 3 +- .../hermes/settings/CompressionSettings.vue | 3 +- .../hermes/settings/DisplaySettings.vue | 3 +- .../settings/GatewayAutoStartSettings.vue | 3 +- .../hermes/settings/GithubPreviewSettings.vue | 3 +- .../hermes/settings/MemorySettings.vue | 3 +- .../hermes/settings/ModelSettings.vue | 3 +- .../hermes/settings/PlatformSettings.vue | 3 +- .../hermes/settings/PrivacySettings.vue | 3 +- .../hermes/settings/ProxySettings.vue | 3 +- .../hermes/settings/SessionSettings.vue | 3 +- .../settings/UserManagementSettings.vue | 3 +- .../hermes/settings/VoiceSettings.vue | 3 +- .../settings/voice/VoiceApiFormModal.vue | 3 +- .../hermes/skills/PendingWriteApprovals.vue | 3 +- .../components/hermes/skills/SkillDetail.vue | 2 +- .../hermes/skills/SkillExternalDirsModal.vue | 3 +- .../hermes/skills/SkillImportModal.vue | 3 +- .../components/hermes/skills/SkillList.vue | 4 +- .../hermes/workflow/WorkflowAgentNode.vue | 3 +- .../src/components/layout/AppSidebar.vue | 24 +- .../src/components/layout/ProfileSelector.vue | 3 +- .../layout/SettingsCircuitBadge.vue | 4 +- packages/client/src/composables/useDialog.ts | 62 +++++ packages/client/src/composables/useMessage.ts | 53 ++++ .../client/src/composables/useNotification.ts | 32 +++ packages/client/src/composables/useTheme.ts | 2 + packages/client/src/main.ts | 3 + packages/client/src/styles/arco-theme.ts | 14 + .../vendor/arkscore/IsometricWave/README.md | 42 +++ .../vendor/arkscore/IsometricWave/index.vue | 258 ++++++++++++++++++ .../client/src/vendor/arkscore/PROVENANCE.md | 27 ++ .../vendor/arkscore/ScrambleTitle/README.md | 34 +++ .../vendor/arkscore/ScrambleTitle/index.vue | 149 ++++++++++ packages/client/src/views/LoginView.vue | 91 +++--- .../client/src/views/hermes/DevicesView.vue | 3 +- .../client/src/views/hermes/FilesView.vue | 2 +- .../client/src/views/hermes/HistoryView.vue | 3 +- .../client/src/views/hermes/JourneyView.vue | 3 +- .../client/src/views/hermes/KanbanView.vue | 3 +- packages/client/src/views/hermes/LogsView.vue | 3 +- .../src/views/hermes/McpManagerView.vue | 7 +- .../client/src/views/hermes/MemoryView.vue | 3 +- .../client/src/views/hermes/ModelsView.vue | 3 +- .../src/views/hermes/PerformanceView.vue | 3 +- .../client/src/views/hermes/PetdexView.vue | 3 +- .../client/src/views/hermes/PluginsView.vue | 3 +- .../client/src/views/hermes/TerminalView.vue | 3 +- .../client/src/views/hermes/WorkflowView.vue | 3 +- .../services/hermes/agent-bridge/README.md | 9 +- .../services/hermes/agent-bridge/manager.ts | 55 +++- .../agent-bridge/python/bridge_runtime.py | 6 +- .../agent-bridge/python/bridge_server.py | 27 +- .../server/src/services/hermes/plugins.ts | 16 +- tests/client/arkscope-vendor.test.ts | 43 +++ tests/client/copilot-login-modal.test.ts | 2 + tests/client/kanban-create-form.test.ts | 3 + tests/client/login-view.test.ts | 19 ++ tests/client/plugins-view.test.ts | 3 + .../provider-card-built-in-action.test.ts | 5 + tests/client/sidebar-search.test.ts | 48 ++-- 106 files changed, 1493 insertions(+), 221 deletions(-) create mode 100644 docs/arco-mapping.md create mode 100644 docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md create mode 100644 docs/ui-baseline.md create mode 100644 packages/client/src/composables/useDialog.ts create mode 100644 packages/client/src/composables/useMessage.ts create mode 100644 packages/client/src/composables/useNotification.ts create mode 100644 packages/client/src/styles/arco-theme.ts create mode 100644 packages/client/src/vendor/arkscore/IsometricWave/README.md create mode 100644 packages/client/src/vendor/arkscore/IsometricWave/index.vue create mode 100644 packages/client/src/vendor/arkscore/PROVENANCE.md create mode 100644 packages/client/src/vendor/arkscore/ScrambleTitle/README.md create mode 100644 packages/client/src/vendor/arkscore/ScrambleTitle/index.vue create mode 100644 tests/client/arkscope-vendor.test.ts diff --git a/AGENTS.md b/AGENTS.md index db9507b..a4fb3b0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,6 +56,10 @@ Use the smallest relevant check while iterating. Before a broad PR, run `services/hermes/context-engine/` are the chat chain: changes require a `docs/chat-chain-changes/-.md` fragment. - `packages/desktop` and `packages/website` are removed; do not reintroduce them. +- ArkScope vendor (`packages/client/src/vendor/arkscore/`) is decorative only: + login-page title, Journey graph background, first-load / empty states. Never in + chat, forms, tables, or click-dense areas. Decorative layers must be + `pointer-events: none` and carry no information or interaction. ## When The Agent Gets Stuck diff --git a/docs/arco-mapping.md b/docs/arco-mapping.md new file mode 100644 index 0000000..0c5509b --- /dev/null +++ b/docs/arco-mapping.md @@ -0,0 +1,65 @@ +# Naive UI → Arco Design Vue 组件映射表 + +Phase 005 起,新代码与迁移代码一律使用 Arco Design Vue。本文表是迁移的唯一映射依据: +**先查表,再改代码**。表中没有的组件,先在此表补一行再动手。 + +- 全局注册:`main.ts` 全量 `app.use(ArcoVue)`,不做按需引入 +- 主题:`body[arco-theme='dark']`(见 `styles/arco-theme.ts` 与 `docs/ui-baseline.md`) +- hooks:`useMessage` / `useDialog` / `useNotification` 适配层在 `packages/client/src/composables/`, + 签名冻结,**不得**在页面里直接 import Arco 的 `Message` / `Modal` / `Notification` + +## Provider / hooks + +| Naive | Arco | 备注 | +|---|---|---| +| `NConfigProvider` | 直接用 `body[arco-theme]` | 不需要组件 | +| `NMessageProvider` / `useMessage` | `composables/useMessage.ts` | 适配层,签名冻结 | +| `NDialogProvider` / `useDialog` | `composables/useDialog.ts` | `positiveText→okText`、`onPositiveClick→onBeforeOk`(resolve false / reject → 保持打开)由适配层转换 | +| `NNotificationProvider` / `useNotification` | `composables/useNotification.ts` | `description→content` 由适配层转换 | + +## 基础组件(按当前仓库用量排序) + +| Naive | Arco | 用量 | 备注 | +|---|---|---|---| +| `NButton` | `a-button` | 732 | `block`→`long`、`size="tiny"`→`size="mini"`、`type="warning"`→`type="primary" status="warning"`、`quaternary`→`status="secondary"` | +| `NInput` | `a-input` / `a-textarea` | 160 | `type="textarea"` 用独立 `a-textarea`;`a-input` 关闭了 attrs 透传,`autofocus` 无效,需 `ref` + `focus()` | +| `NModal` | `a-modal` | 138 | `v-model:show`→`v-model:visible`;`preset="dialog"` 无对应,信息型弹窗用 `:footer="false"`(`footer` 为 Boolean prop,默认 true) | +| `NFormItem` | `a-form-item` | 136 | 校验规则 API 不同(`rules` 结构、`required` 表达式)需适配 | +| `NSelect` | `a-select` | 97 | `options` 的 `label/value` 语义一致;`consistent-menu-width` 等 prop 无对应 | +| `NSpin` | `a-spin` | 86 | `show`→`loading` | +| `NTooltip` | `a-tooltip` | 76 | `trigger="hover"` 默认一致;`placement` 语义基本一致 | +| `NTag` | `a-tag` | 57 | `bordered` prop 存在(默认 true);`size="small"` 一致;naive `type="info"`→`color="arcoblue"` 等内建色(`red/orange/green/arcoblue/...`),自定义色用 `color` 传 CSS 颜色 | +| `NSwitch` | `a-switch` | 53 | `value`/`v-model` 一致 | +| `NSpace` | `a-space` | 51 | `size` 语义一致(数字/预设名) | +| `NInputNumber` | `a-input-number` | 43 | `min`/`max`/`precision` 一致 | +| `NPopconfirm` | `a-popconfirm` | 36 | `positive-text`→`ok-text`;`@positive-click`→`@ok` | +| `NForm` | `a-form` | 32 | 校验规则 API 不同,需适配 | +| `NTabPane` | `a-tab-pane` | 30 | `NTabs/NTabPane` → `a-tabs/a-tab-pane` | +| `NDrawer` / `NDrawerContent` | `a-drawer` | 27 | Arco 无独立 content 组件;`footer` slot 用 `a-drawer` 的 `footer` | +| `NDropdown` | `a-dropdown` | 25 | `options` 结构不同(`label`→`content`,需转换函数) | +| `NDescriptions` / `NDescriptionsItem` | `a-descriptions` / `a-descriptions-item` | 19 | 基本一致 | +| `NAlert` | `a-alert` | 19 | `type` 一致;`show-icon` 一致 | +| `NRadioButton` / `NRadioGroup` / `NRadio` | `a-radio`(`type="radio-button"`)/ `a-radio-group` | 32 | Arco radio-button 是 `a-radio` 的 `type` prop | +| `NCheckbox` / `NCheckboxGroup` | `a-checkbox` / `a-checkbox-group` | 17 | `options` 数组语义一致 | +| `NEmpty` | `a-empty` | 12 | 基本一致 | +| `NCollapse` / `NCollapseItem` | `a-collapse` / `a-collapse-item` | 24 | `arrow` 语义不同 | +| `NUpload` | `a-upload` | 9 | `file-list` 结构不同,需适配 | +| `NText` | `` / 原生 `span` | 8 | 多数场景直接用原生元素 + class | +| `NTabs` | `a-tabs` | 6 | 见 NTabPane 行 | +| `NBreadcrumb` / `NBreadcrumbItem` | `a-breadcrumb` / `a-breadcrumb-item` | 8 | 基本一致 | +| `NDataTable` | `a-table` | 4 | **列定义 API 差异最大**(`columns`/`render`/`slots`),逐例处理 | +| `NSlider` | `a-slider` | 3 | 基本一致 | +| `NScrollbar` | `a-scrollbar` | 3 | Arco 全局可用,无需组件包裹注册 | +| `NPopselect` | `a-select`(`trigger-element`) | 3 | 无直接对应,改用 `a-dropdown` 或带 trigger 的 `a-select` | +| `NPopover` | `a-popover` | 3 | `trigger` 语义有差异 | +| `NIcon` | ``(`@arco-design/web-vue/es/icon`) | 3 | Arco 图标是独立组件,需 import | +| `NBadge` | `a-badge` | 3 | `value`→`count` | +| `NTree` | `a-tree` | 2 | 结构差异大,逐例处理 | +| `NCard` | `a-card` | — | 当前仓库未直接使用;若出现按此映射 | +| `NProgress` | `a-progress` | — | 当前仓库未直接使用;若出现按此映射 | +| `NLayout` / `NLayoutSider` | `a-layout` / `a-layout-sider` | — | 当前仓库未使用(侧栏是手写 div),保持手写或按需引入 | + +## 迁移批次(Phase 006/007 使用) + +- Phase 006:聊天页(`components/hermes/chat/`)——消息气泡内部**不用** Arco 组件(性能红线) +- Phase 007:其余视图 + `stores/` + `composables/` 批量替换 + 删除 naive-ui 依赖 diff --git a/docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md b/docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md new file mode 100644 index 0000000..4d12670 --- /dev/null +++ b/docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md @@ -0,0 +1,61 @@ +--- +date: 2026-10-17 +pr: pending +feature: Phase 005 - ArkScope vendor, Arco theme baseline, and bridge environment fixes +impact: No chat-chain behavior change; chat components only swap naive-ui rendering for Arco/theme-token rendering and route hooks through the new composables, while the bridge manager prefers the PM-selected venv so MCP/plugin discovery works again. +--- + +# Phase 005 — ArkScope vendor + style baseline + +Phase: `docs/phases/phase_005.md` + +## What Changed + +Phase 005 vendors ArkScope decorative layers, migrates the theme to Arco +tokens, and lands two sample pages (LoginView, AppSidebar). Chain-adjacent +files touched: + +- `packages/client/src/components/hermes/chat/*.vue` — rendering-layer only: + naive-ui components replaced with Arco equivalents or native markup, and + `useMessage` / `useDialog` / `useNotification` imports moved to the Phase 005 + composables (`packages/client/src/composables/useMessage.ts` etc., same + signatures). Chat run flow, store logic, and keyboard handling were not + modified; `MarkdownRenderer.vue` changes are style-token only. +- `packages/client/src/components/hermes/group-chat/*.vue` — same rendering-layer + swap as above. +- `packages/server/src/services/hermes/agent-bridge/manager.ts` — interpreter + resolution now prefers the PM-selected venv recorded in + `/installs//facts.json` (sha256 of the resolved agent root) + before the legacy in-tree `.venv`/`venv` trees. The legacy venv lacks the + `mcp` extra, which made every MCP server report `connected: false` with zero + tools. `explicitPython` / `HERMES_AGENT_BRIDGE_PYTHON` still wins first. +- `packages/server/src/services/hermes/agent-bridge/python/bridge_server.py` / + `bridge_runtime.py` — vendored read-only bridge got compatibility fixes: + MCP registry import fallback (`mcp_tool_discovery`), and `_read_mcp_config` + now imports `hermes_yaml` when `pyyaml` is absent (PM env has no `yaml`). +- `packages/server/src/services/hermes/agent-bridge/README.md` — documents the + new interpreter resolution order. + +## Contract Check + +`/chat-run` event names, field names, field types, and emission timing are +unchanged (no bridge protocol fields were added or altered; only the Python +interpreter the bridge runs under changed). No run dispatch, session, or +usage-recording behavior changed. + +Note: `agent-bridge/python/` is marked read-only in AGENTS.md; the two bridge +python fixes above are deliberate deviations to keep the vendored bridge +importable under the PM environment, called out for review. + +## Verification + +```bash +npm run harness:check +npm run test +npm run test:e2e +npm run build +``` + +Manual: bridge resolves to the PM venv (`installs/.../venv/bin/python3`), +`mcpList` returns 6/6 servers connected with 104 tools, plugins list returns +100 plugins / 0 warnings. diff --git a/docs/phases/phase_005.md b/docs/phases/phase_005.md index 1a3762c..101a6cb 100644 --- a/docs/phases/phase_005.md +++ b/docs/phases/phase_005.md @@ -153,7 +153,7 @@ export const arcoTheme = { ```ts import ArcoVue from '@arco-design/web-vue' -import '@arco-design/web-vue/dist/arco-vue.css' +import '@arco-design/web-vue/dist/arco.css' app.use(ArcoVue) ``` diff --git a/docs/ui-baseline.md b/docs/ui-baseline.md new file mode 100644 index 0000000..3ea5153 --- /dev/null +++ b/docs/ui-baseline.md @@ -0,0 +1,134 @@ +# UI 基线(Phase 005 定稿) + +本文件是 hermes-ui 的**视觉语言唯一依据**。Phase 006/007 的批量迁移照此执行, +页面样式与本文冲突时,以本文为准;本文没覆盖的场景,先在 `docs/arco-mapping.md` +补映射、再回本文补约定,然后才动代码。 + +- 样板页:`views/LoginView.vue`(装饰位面 + 表单)、`components/layout/AppSidebar.vue`(导航 + 弹窗 + tag) +- 令牌源:`packages/client/src/styles/variables.scss`(CSS 变量)+ `styles/arco-theme.ts`(Arco 组件主题) +- 组件映射:`docs/arco-mapping.md` + +--- + +## 1. 色板 + +黑白水墨(Pure Ink)——**纯黑白灰,无彩色**(status/info 色除外)。三主题:ink light / ink dark / comic。 + +### 浅色(`html:not(.dark)`) + +| 角色 | 变量 | 值 | +|---|---|---| +| 页面底 | `--bg-primary` | `#fafafa` | +| 次级底 / 侧栏 | `--bg-secondary` / `--bg-sidebar` | `#f0f0f0` / `#f5f5f5` | +| 卡片 / 输入底 | `--bg-card` / `--bg-input` | `#ffffff` | +| 分割线 | `--border-color` / `--border-light` | `#e0e0e0` / `#ebebeb` | +| 强调色 | `--accent-primary` / `--accent-hover` | `#333333` / `#1a1a1a` | +| 正文 | `--text-primary` / `--text-secondary` / `--text-muted` | `#1a1a1a` / `#666666` / `#999999` | +| 状态 | `--success` / `--error` / `--warning` / `--accent-info` | `#2e7d32` / `#c62828` / `#f57f17` / `#4a90d9` | + +### 深色(`.dark`) + +| 角色 | 变量 | 值 | +|---|---|---| +| 页面底 | `--bg-primary` | `#1a1a1a` | +| 次级底 / 侧栏 | `--bg-secondary` / `--bg-sidebar` | `#252525` / `#202020` | +| 卡片 / 输入底 | `--bg-card` / `--bg-input` | `#2a2a2a` | +| 分割线 | `--border-color` / `--border-light` | `#3a3a3a` / `#333333` | +| 强调色 | `--accent-primary` / `--accent-hover` | `#e0e0e0` / `#f5f5f5`(反相) | +| 正文 | `--text-primary` / `--text-secondary` / `--text-muted` | `#e0e0e0` / `#a0a0a0` / `#888888` | +| 状态 | `--success` / `--error` / `--warning` / `--accent-info` | `#66bb6a` / `#ef5350` / `#ffb74d` / `#6ba3d6` | + +- 需要 `rgba()` 透明度时用配套 `--*-rgb` 变量(如 `rgba(var(--accent-primary-rgb), 0.12)`)。 +- 反色场景(强调底上的字)一律用 `--text-on-accent`,不要硬编码白/黑。 +- **Arco 组件配色**:`arco-theme.ts` 冻结 `colorPrimary: #2f2f2f`、`borderRadius: 6`, + 延续克制灰阶;暗色由 `body[arco-theme='dark']` CSS 变量覆盖承载, + **不用** Arco 的 dark theme + overrides 双轨。 +- 主题切换机制:`useTheme.ts` 同时切 `` 的 `dark`/`comic` class(项目令牌) + 和 `arco-theme` 属性(Arco 组件);FOUC 脚本内联在 `index.html`,两边同步改。 + +## 2. 圆角 / 间距 / 字号阶梯 + +**圆角**(`$radius-*`): + +| token | 值 | 用途 | +|---|---|---| +| `$radius-sm` | 6px | 按钮、输入框、小卡片、tag(与 Arco `borderRadius: 6` 对齐) | +| `$radius-md` / `$radius-lg` | 8px | 面板、弹窗、大卡片 | + +**间距**:4px 基数,常用档 `4 / 8 / 12 / 16 / 24 / 32`(组件内边距多用 12/16,区块间 24/32)。 +布局尺寸:侧栏 240px(折叠 64px)、顶栏 60px、移动端断点 768px。 + +**字号阶梯**: + +| px | 用途 | +|---|---| +| 10 | 分组标签(uppercase + letter-spacing) | +| 11–12 | 辅助信息、meta、tag | +| 13 | 次要正文、列表项、错误提示 | +| 14 | 正文默认(`body` 基准) | +| 15 | 输入框文字(`$font-code` 场景) | +| 16 | 小标题 | +| 26 | 登录页等展示型大标题 | + +## 3. 字体栈 + +```scss +$font-ui: Inter, system-ui, -apple-system, sans-serif; // 中英混排 UI(fallback 系统字体) +$font-code: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; // 代码、命令、版本号 +``` + +- comic 主题下 `--font-ui` 被覆盖为 `'Comic Neue', 'ZCOOL KuaiLe', ...`,其余不变。 +- 等宽字体只用于:代码块、命令、版本号、命令行提示(`$font-code`)。 + +## 4. 阴影 / 分割线 + +- **分割线**:`1px solid var(--border-color)`(面板/区块)或 `--border-light`(弱分隔)。 + 水墨主题以线为主,不滥用阴影。 +- **阴影**:常规浮层 `0 1px 4px rgba(0, 0, 0, 0.1)`;comic 主题用 + `3px 3px 0 rgba(0, 0, 0, 0.15)` 硬投影(`--comic-shadow`)。 +- 弹窗/抽屉的遮罩、投影由 Arco 组件自带,页面不重复加。 + +## 5. 组件使用约定 + +- **消息 / 对话框 / 通知**:只准调 `composables/useMessage|useDialog|useNotification` + 适配层(签名冻结),**不得**在页面里直接 import Arco 的 `Message` / `Modal` / `Notification`。 +- **Modal vs Drawer**:短内容确认、纯信息展示用 `a-modal` + (信息型弹窗 `:footer="false"`,如样板页 changelog / docker 升级指引); + 需要表单编辑、长内容滚动的场景用 `a-drawer`。naive `preset="dialog"` 无直接对应。 +- **按钮**:语义 `type` + `status` 组合(warning 按钮 = `type="primary" status="warning"`); + 占满宽用 `long`(naive `block`);尺寸 `size="mini|small|medium|large"` + (naive `tiny`→`mini`);提交按钮 `html-type="submit"`,加载态用 `:loading`。 +- **输入框**:`a-input` 关闭了 attrs 透传,`autofocus` 无效——需要自动聚焦时 + `ref` + `onMounted` 调 `focus()`;密码框 `type="password"`。 +- **表格空态**:用 `a-empty`,不手写占位文案。 +- **loading**:按钮级用 `a-button :loading`,区域级用 `a-spin :loading`, + 全局任务进度用 `useMessage().loading(...)`(用完 close/destroy)。 +- **表单**:`a-form` + `a-form-item`,校验规则 API 与 naive 不同,逐例适配。 +- **tag**:naive `type="info"` → `a-tag color="arcoblue"`(内建色), + `:bordered="false"` 直接支持。 + +## 6. 装饰位面的边界 + +装饰位面(ArkScope vendor 组件等纯视觉层)的判定标准: + +> **不得承载信息、不得承载交互、不得影响可读性。** + +| 允许 | 禁止 | +|---|---| +| 登录页标题区装饰位面 | 聊天页(消息区 / 输入区 / 侧边栏) | +| Journey 图谱的背景层 | 任何表单页(Skills / Profiles / Settings / Models) | +| 首屏 loading / 空状态 | 任何表格页(Usage / Logs / Jobs / Kanban) | +| | 任何点击交互密集区域 | + +**`pointer-events: none` 是硬要求**,装饰层绝不能吃掉点击 +(样板:`LoginView.vue` 的 `.login-decor`,外加 `aria-hidden="true"`)。 + +vendor 素材边界:`vendor/arkscore/` 下**零图片/音视频素材**, +由 `tests/client/arkscope-vendor.test.ts` 机械化卡住;Provenance 见 +`vendor/arkscore/PROVENANCE.md`。 + +## 7. 三条不可破的规则 + +1. **不引入第三个 UI 库**——只允许 Arco Design Vue(+ 现有图标/工具链)。 +2. **消息气泡内部不用 Arco 组件**(性能红线,见 Phase 006)。 +3. **装饰层一律 `pointer-events: none`**,不承载信息与交互。 diff --git a/package-lock.json b/package-lock.json index 2ba9b43..8f3a498 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { - "name": "hermes-web-ui", + "name": "hermes-ui", "version": "0.6.29", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "hermes-web-ui", + "name": "hermes-ui", "version": "0.6.29", "license": "BSL-1.1", "dependencies": { + "@arco-design/web-vue": "^2.58.0", "@audio/decode-mp3": "^1.1.0", "@audio/decode-wav": "^1.2.0", "@vscode/markdown-it-katex": "^1.1.2", @@ -28,9 +29,8 @@ "socket.io-client": "^4.8.3" }, "bin": { - "hermes-studio-mcp": "bin/hermes-studio-mcp.mjs", - "hermes-web-ui": "bin/hermes-web-ui.mjs", - "hermes-web-ui-mcp": "bin/hermes-studio-mcp.mjs" + "hermes-ui": "bin/hermes-ui.mjs", + "hermes-ui-mcp": "bin/hermes-ui-mcp.mjs" }, "devDependencies": { "@koa/bodyparser": "^5.0.0", @@ -132,6 +132,35 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@arco-design/color": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/@arco-design/color/-/color-0.4.0.tgz", + "integrity": "sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3" + } + }, + "node_modules/@arco-design/web-vue": { + "version": "2.58.0", + "resolved": "https://registry.npmmirror.com/@arco-design/web-vue/-/web-vue-2.58.0.tgz", + "integrity": "sha512-b1vdPYOmjG5VAkVa7jlVwCb+WynBK+rnKN8zH3yKohpZObZbostRd3HgYNtjjZjGVU3OqR0Yy2FX7ftgF0bcOw==", + "license": "MIT", + "dependencies": { + "@arco-design/color": "^0.4.0", + "b-tween": "^0.3.3", + "b-validate": "^1.5.3", + "compute-scroll-into-view": "^1.0.20", + "dayjs": "^1.11.13", + "number-precision": "^1.6.0", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.31", + "vue": "^3.1.0" + }, + "peerDependencies": { + "vue": ">=3.1.0" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", @@ -4156,6 +4185,18 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/b-tween": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/b-tween/-/b-tween-0.3.3.tgz", + "integrity": "sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==", + "license": "MIT" + }, + "node_modules/b-validate": { + "version": "1.5.3", + "resolved": "https://registry.npmmirror.com/b-validate/-/b-validate-1.5.3.tgz", + "integrity": "sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4577,6 +4618,16 @@ "node": ">=8.0.0" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4595,6 +4646,31 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -4625,6 +4701,12 @@ "node": ">=14" } }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5459,7 +5541,6 @@ "version": "1.11.21", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", - "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -6691,6 +6772,12 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -8248,6 +8335,12 @@ "node": ">=0.10.0" } }, + "node_modules/number-precision": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/number-precision/-/number-precision-1.6.0.tgz", + "integrity": "sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9055,6 +9148,12 @@ "dev": true, "license": "ISC" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -9451,6 +9550,15 @@ "node": ">=v12.22.7" } }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, "node_modules/secure-json-parse": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", @@ -9712,6 +9820,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", diff --git a/package.json b/package.json index 471e3bc..8a5e6ac 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "dist/" ], "dependencies": { + "@arco-design/web-vue": "^2.58.0", "@audio/decode-mp3": "^1.1.0", "@audio/decode-wav": "^1.2.0", "@vscode/markdown-it-katex": "^1.1.2", diff --git a/packages/client/index.html b/packages/client/index.html index d92abe1..46bfb0a 100644 --- a/packages/client/index.html +++ b/packages/client/index.html @@ -101,6 +101,8 @@ if (isComic) { document.documentElement.classList.add('comic'); } + // Arco Design dark theme mechanism (Phase 005) + document.documentElement.setAttribute('arco-theme', isDark ? 'dark' : 'light'); } catch (e) { console.warn('Failed to apply theme:', e); } diff --git a/packages/client/src/App.vue b/packages/client/src/App.vue index fbd8106..15bb2b4 100644 --- a/packages/client/src/App.vue +++ b/packages/client/src/App.vue @@ -1,9 +1,7 @@ diff --git a/packages/client/src/vendor/arkscore/PROVENANCE.md b/packages/client/src/vendor/arkscore/PROVENANCE.md new file mode 100644 index 0000000..1751273 --- /dev/null +++ b/packages/client/src/vendor/arkscore/PROVENANCE.md @@ -0,0 +1,27 @@ +# ArkScope vendored components + +- 上游:https://github.com/triplepiers/ArkScope +- commit:78f3763b0a48608eae00c27ac837449a32bd93df(main,2026-09-16) +- 拉取日期:2026-10-16 +- vendor 的组件:ScrambleTitle、IsometricWave +- 剥离内容:demo 工程(router/views/showcases/componentRegistry)、assets、public + +## 许可证 + +上游仓库无 LICENSE 文件。 + +本 vendor 仅用于个人自用,不分发、不商用。 +若要公开分发,必须先向上游作者取得授权或替换为自研实现。 + +## 未 vendor 的组件 + +其余组件强依赖鹰角游戏美术素材,脱离素材无实际价值,故未 vendor。 +本项目仅参考其视觉手法(斜切遮罩、噪点、扫描线等),自行实现。 + +## 使用边界 + +装饰位面硬约束(详见 `docs/ui-baseline.md`): + +- 仅限登录页标题区装饰、Journey 图谱背景层、首屏 loading / 空状态 +- 禁止用于聊天页、表单页、表格页、点击交互密集区域 +- 装饰层一律 `pointer-events: none`,不得承载信息与交互 diff --git a/packages/client/src/vendor/arkscore/ScrambleTitle/README.md b/packages/client/src/vendor/arkscore/ScrambleTitle/README.md new file mode 100644 index 0000000..34e9c04 --- /dev/null +++ b/packages/client/src/vendor/arkscore/ScrambleTitle/README.md @@ -0,0 +1,34 @@ +# 实现思路 + +> 复刻来源:[塞壬唱片](https://monster-siren.hypergryph.com/music#album) + +- 整体逻辑: + + ```text + 真实标题 + => 生成 / 占位文本 + => 从左到右逐字符进入随机闪烁 + => 闪烁次数耗尽后替换为真实字符 + => 全部字符稳定 + ``` + + +- 动画在 `content` 变化时重新开始:先取消上一轮未完成的 timeout、避免快速切换时多个动画同时写入同一段文本。 + +### 第一阶段:占位展开 + +清空显示文本、按原始标题字符从左到右追加占位符: +- 中文 / 日文字符 => "//" +- 英文 / 数字 => "/" +- 空格、-、/、' => 保留原字符 + +### 第二阶段:乱码闪烁 + +左 => 右 逐步解码文字,随机字符池会根据原字符类型选择: + +- 英文 / 数字 => `a-z A-Z 0-9` +- 中文 / CJK => 注音符号、〇、天干等字符 +- 日文假名 => 平假名 / 片假名 + + +每个字符默认闪烁 `14` 次,帧间隔默认 `20ms` \ No newline at end of file diff --git a/packages/client/src/vendor/arkscore/ScrambleTitle/index.vue b/packages/client/src/vendor/arkscore/ScrambleTitle/index.vue new file mode 100644 index 0000000..b887b71 --- /dev/null +++ b/packages/client/src/vendor/arkscore/ScrambleTitle/index.vue @@ -0,0 +1,149 @@ + + + + diff --git a/packages/client/src/views/LoginView.vue b/packages/client/src/views/LoginView.vue index 21963f8..cca143b 100644 --- a/packages/client/src/views/LoginView.vue +++ b/packages/client/src/views/LoginView.vue @@ -4,6 +4,8 @@ import { useRouter } from "vue-router"; import { useI18n } from "vue-i18n"; import { setApiKey, hasApiKey } from "@/api/client"; import { fetchAuthStatus, loginWithPassword } from "@/api/auth"; +import ScrambleTitle from "@/vendor/arkscore/ScrambleTitle/index.vue"; +import IsometricWave from "@/vendor/arkscore/IsometricWave/index.vue"; const { t } = useI18n(); const router = useRouter(); @@ -13,12 +15,16 @@ const password = ref(""); const loading = ref(false); const errorMsg = ref(""); const showLockResetHint = ref(false); +// Arco's a-input sets inheritAttrs: false (no autofocus passthrough), +// so focus the input explicitly on mount. +const usernameInput = ref<{ focus: () => void } | null>(null); if (hasApiKey()) { router.replace("/hermes/chat"); } onMounted(async () => { + usernameInput.value?.focus(); try { await fetchAuthStatus(); } catch { @@ -59,26 +65,33 @@ async function handlePasswordLogin() {