From 9e0d09456bf9a23af1e5de5d3e7decd8a79731f8 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Thu, 25 Jun 2026 14:38:26 +0800 Subject: [PATCH 01/14] docs(plugins): plan main route hub --- docs/features/plugins-hub/plan.md | 435 +++++++++++++++++++++++++++++ docs/features/plugins-hub/spec.md | 358 ++++++++++++++++++++++++ docs/features/plugins-hub/tasks.md | 119 ++++++++ 3 files changed, 912 insertions(+) create mode 100644 docs/features/plugins-hub/plan.md create mode 100644 docs/features/plugins-hub/spec.md create mode 100644 docs/features/plugins-hub/tasks.md diff --git a/docs/features/plugins-hub/plan.md b/docs/features/plugins-hub/plan.md new file mode 100644 index 0000000000..b6a59380ca --- /dev/null +++ b/docs/features/plugins-hub/plan.md @@ -0,0 +1,435 @@ +# Plugins Hub Implementation Plan + +## Strategy + +Do not build a new plugin platform and do not add a new window. Move the UI ownership boundary into +the existing main window. + +The smallest reliable implementation is: + +- Add `/plugins` routes to the existing main renderer router. +- Keep `WindowSideBar` and `AppBar` as the app shell around the Plugins page. +- Reuse current clients/stores/components where they already own behavior. +- Treat Remote channels as renderer-only virtual plugin cards backed by existing `remoteControl.*` routes. +- Hide Settings navigation entries for Plugins-owned areas, while keeping compatibility redirects. +- Update the main sidebar expanded layout only; keep collapsed behavior unchanged. + +No data migration is required. + +## Main Route Architecture + +```text +Main window + App.vue + AppBar + WindowSideBar + RouterView + /chat -> ChatTabView + /welcome -> WelcomePage + /plugins -> PluginsHubPage + /plugins + /plugins/skills + /plugins/mcp + /plugins/remote + /plugins/official/:pluginId + /plugins/remote/:channel +``` + +Use the existing `src/renderer/src/router/index.ts`. Do not add `src/renderer/plugins`, a new Vite +entry, or a new BrowserWindow. + +Route names can be: + +```text +plugins +plugins-skills +plugins-mcp +plugins-remote +plugins-official-detail +plugins-remote-detail +``` + +External/main-process callers should not know UI component internals. Add a narrow route or event only where the main process needs to focus the main window and navigate: + +```text +system.openMainRoute({ routeName: 'plugins-mcp', params? }) -> { focused: boolean } +``` + +If an existing main-window navigation route already exists during implementation, reuse it. Do not add a Plugins-specific window route. + +## Affected Boundaries + +| Boundary | Required change | +| --- | --- | +| `src/renderer/src/router/index.ts` | Add `/plugins` route family | +| `src/renderer/src/App.vue` | Keep existing shell; ensure `/plugins` receives same global overlays/theme/i18n | +| `WindowSideBar.vue` | Add expanded command list and route Plugins row to `/plugins` | +| `renderer/api` | Add or reuse a small main-window navigation client only for main-process initiated navigation | +| `shared/contracts/routes` | Add narrow focus/navigate route only if deeplink/main process cannot use existing event path | +| Settings renderer | Remove/hide Plugins-owned nav entries and overview links | +| Deeplink presenter | Route MCP install deeplink to main `/plugins/mcp` page | +| Plugin presenter | Stop using per-plugin BrowserWindow as primary UI path | + +## Data Ownership + +Do not create a persisted unified plugin table. + +Use a renderer-only union for cards: + +```text +CatalogItem = + official plugin item from plugins.list + MCP server summary from mcp store + Skill metadata from skills store + Remote virtual item from remoteControl.listChannels + status +``` + +This union only drives list rendering and search filtering. Writes go back to the current owner: + +| User action | Owner route/client | +| --- | --- | +| Enable official plugin | `PluginClient.enablePlugin` | +| Disable official plugin | `PluginClient.disablePlugin` | +| CUA runtime/permission action | `PluginClient.invokeAction` existing runtime actions | +| MCP add/edit/toggle | `McpClient` / `useMcpStore` existing paths | +| Skill install/edit/delete/sync | `SkillClient` / `SkillSyncClient` / `useSkillsStore` | +| Remote enable/save/pair/remove binding | `RemoteControlClient` | + +## Page Shell + +Create the Plugins UI under the existing renderer: + +```text +src/renderer/src/pages/plugins/ +├── PluginsHubPage.vue +├── PluginsCatalogPage.vue +├── OfficialPluginDetailPage.vue +├── McpPluginsPage.vue +├── SkillsPluginsPage.vue +├── RemotePluginsPage.vue +├── RemotePluginDetailPage.vue +├── components/ +│ ├── PluginsTopTabs.vue +│ ├── PluginCatalogGrid.vue +│ ├── PluginCatalogCard.vue +│ ├── AddedPluginsStrip.vue +│ └── PluginSearchBar.vue +└── composables/ + ├── usePluginCatalog.ts + └── useRemotePluginItems.ts +``` + +Keep this list flexible during implementation; do not split files unless the component becomes hard to read. + +Visual baseline: + +- Main content starts with top tabs (`Plugins`, `Skills`, optionally `MCP`, `Remote`). +- Catalog page uses the Codex-like layout: title, subtitle, search, added strip, segmented filters, sectioned list. +- Avoid settings-style full-width form pages for the catalog. Detail routes may use denser settings sections. +- Cards are individual repeated items only. Do not put page sections inside floating cards. + +## Route and Navigation Behavior + +Renderer-side navigation: + +| Trigger | Behavior | +| --- | --- | +| Sidebar `Plugins` row | `router.push({ name: 'plugins' })` | +| Top tab `Skills` | `router.push({ name: 'plugins-skills' })` | +| Top tab `MCP` | `router.push({ name: 'plugins-mcp' })` | +| Top tab `Remote` | `router.push({ name: 'plugins-remote' })` | +| Official plugin card/detail | `router.push({ name: 'plugins-official-detail', params: { pluginId } })` | +| Remote channel card/detail | `router.push({ name: 'plugins-remote-detail', params: { channel } })` | +| `New Chat` row while on `/plugins` | `router.push({ name: 'chat' })`, then start new conversation | + +Main-process initiated navigation: + +- MCP install deeplink must focus the main window and route to `/plugins/mcp`. +- Historical Settings route redirects can focus main and route to the matching `/plugins...` route. +- If the main window does not exist, create/focus the normal app window, not a Plugins window. + +## Official Plugin Detail + +Current behavior opens `PluginPresenter.openPluginSettingsWindow(pluginId)`. + +Target behavior: + +- List page opens `/plugins/official/:pluginId`. +- Detail page loads `plugins.get(pluginId)`. +- Enable/disable remains in detail and list. +- Runtime status and MCP status remain visible. +- Known first-party plugin actions are exposed as native detail sections: + - `runtime.getStatus` + - `runtime.checkPermissions` + - `runtime.openPermissionGuide` + +Do not add a generic embedded HTML settings host in the first increment. Current shipped plugins are first-party (`cua`, `feishu`), so native Vue detail pages are enough and safer than enabling arbitrary webview/iframe behavior. + +Legacy fallback: + +- Keep `settingsContributions` in manifests during migration. +- Keep `settings.open` action available only as a temporary compatibility path if some old package still calls it. +- The first-party UI must not call `settings.open`. + +When to add a generic plugin settings host: + +- Only when third-party plugin settings contributions are a supported product requirement. +- Use an isolated child WebContents/WebContentsView with the plugin-specific preload, not an iframe without preload. +- Keep external navigation denied. + +## MCP Migration + +`McpSettings.vue` already owns most behavior. Move by reuse, not rewrite. + +Recommended first pass: + +- Create `McpPluginsPage.vue`. +- Import/reuse `McpServers`, `McpBuiltinMarket`, NPM registry controls, guide overlay only if still needed. +- Preserve current route query shape for market view inside Plugins (`/plugins/mcp?view=market`). +- Move deeplink handler from Settings bootstrap to main app or Plugins page bootstrap for MCP install. + +Compatibility: + +- `deepchat://mcp/install` focuses main window and routes to `/plugins/mcp`. +- Hidden `settings-mcp` route can redirect/open main `/plugins/mcp` during transition. + +Settings cleanup: + +- Remove visible `settings-mcp` navigation item. +- Remove MCP Overview metric. +- Remove `start-mcp` quick task or replace it with a non-Plugins Settings task. + +## Skills Migration + +`SkillsSettings.vue` can become a Plugins page with minimal changes: + +- Rename/wrap visually as `SkillsPluginsPage`. +- Keep `SkillCard`, `SkillInstallDialog`, `SkillEditorSheet`, `SkillSyncDialog`, `SyncStatusSection`. +- Keep draft suggestion toggle. +- Keep first-launch sync prompt if product still relies on it. + +Avoid duplicating the skills store or install logic. + +Compatibility: + +- Hidden `settings-skills` route should route the main window to `/plugins/skills`. +- Settings Overview search should not list Skills. + +## Remote Migration + +Do not keep `RemoteSettings.vue` as one giant page inside Plugins. It is already too large. + +Refactor only around real channel boundaries: + +```text +RemotePluginsPage + -> virtual cards from listRemoteChannels() +RemotePluginDetailPage(channel) + -> channel header/status/toggle + -> credentials section + -> default agent/workdir section + -> pairing section when supportsPairing + -> bindings section + -> channel-specific section +``` + +Suggested extracted components: + +| Component | Scope | +| --- | --- | +| `RemotePluginCard` | card summary for one channel | +| `RemotePluginDetailPage` | detail shell and save status | +| `RemoteCredentialsSection` | token/app secret fields; channel-specific props | +| `RemoteDefaultsSection` | default agent and default workdir | +| `RemotePairingSection` | pair code and principals for pairable channels | +| `RemoteBindingsSection` | bound chats/groups/topics | +| `WeixinIlinkAccountsSection` | WeChat iLink login/account controls | + +Keep shared logic tiny: + +- load channel settings +- save channel settings +- load channel status +- load bindings/pairing + +Do not invent a generic form schema for all channels. + +Virtual item mapping: + +```text +remote: + kind: remote + title: channel title + ' Remote' when needed + description: descriptor.descriptionKey + enabled: status.enabled + state: status.state + detailRoute: /plugins/remote/:channel +``` + +## Settings Removal and Redirects + +Change visible navigation source: + +- Remove or mark hidden: + - `settings-mcp` + - `settings-remote` + - `settings-plugins` + - `settings-skills` + +Because `settingsNavigation.ts` is the single source for Settings sidebar and Overview search, this should remove most visible Settings entries without scattered conditions. + +Route compatibility options: + +1. Keep hidden route items so old route names still exist. +2. When entered, focus main window and navigate to the mapped `/plugins...` route. +3. Do not show these items in Settings sidebar/search. + +Mapping: + +| Old Settings route | Main window target | +| --- | --- | +| `settings-mcp` | `/plugins/mcp` | +| `settings-remote` | `/plugins/remote` | +| `settings-plugins` | `/plugins` | +| `settings-skills` | `/plugins/skills` | + +`settings-acp` stays in Settings for this feature. ACP is an agent/provider configuration surface, not part of the four requested Plugins-owned areas. + +## Sidebar Implementation Plan + +Current `WindowSideBar.vue` should not be rewritten. Modify the expanded right column header area. + +Before: + +```text +right column +├── header row: selectedAgentName + group toggle + plus +├── search input +├── pinned section +└── session groups +``` + +After: + +```text +right column +├── title row: selectedAgentName +├── command list +│ ├── New Chat +│ ├── Search +│ └── Plugins +├── compact session controls when needed +├── pinned section +└── session groups +``` + +Command behavior: + +| Row | Existing behavior to call | +| --- | --- | +| New Chat | `router.push({ name: 'chat' })` then `sessionStore.startNewConversation({ refresh: true })` | +| Search | `spotlightStore.toggleSpotlight()` | +| Plugins | `router.push({ name: 'plugins' })` | + +Keep: + +- Agent icon rail. +- collapsed width and transitions. +- session pagination and fill checks. +- pinned collapse behavior. +- project grouping/reorder behavior. +- shortcut badge logic for sessions. + +Question the old inline session search: + +- First increment should remove the inline search input from expanded sidebar to match the requested command-list shape. +- Search row opens Spotlight, which already searches sessions/messages/settings/actions. +- If users later need local-only filtering, add it inside Spotlight or as a session-list filter command, not as a second persistent input. + +## Deeplinks and External Entry Points + +Update callers: + +| Current caller | New behavior | +| --- | --- | +| MCP install deeplink | focus main window, route to `/plugins/mcp`, dispatch MCP install event there | +| Settings sidebar old MCP/Skills/Plugins/Remote | no visible entry | +| Settings activity old route | focus main window and route to matching `/plugins...` page | +| Sidebar remote status button | route to `/plugins/remote` or a selected channel detail | +| Chat input MCP indicator `openSettings` text | route to `/plugins/mcp` | + +Provider install deeplink stays in Settings Provider. Do not route provider/model setup to Plugins. + +## i18n + +Add route/page labels: + +- `routes.plugins` +- `pluginsHub.title` +- `pluginsHub.subtitle` +- `pluginsHub.searchPlaceholder` +- `pluginsHub.tabs.plugins` +- `pluginsHub.tabs.skills` +- `pluginsHub.tabs.mcp` +- `pluginsHub.tabs.remote` +- sidebar command labels if existing `common.newChat` and spotlight labels are not enough. + +Avoid moving existing `settings.mcp`, `settings.skills`, `settings.remote` keys in the first increment. Reuse them from Plugins pages to keep the diff smaller. Later cleanup can rename namespaces if the old naming becomes misleading. + +## Testing Strategy + +Small checks with high signal: + +| Area | Tests | +| --- | --- | +| Main router | `/plugins` and child routes render inside app shell | +| Settings navigation | removed entries do not appear in `getSettingsNavigationGroups`; hidden redirects still resolve | +| Sidebar | expanded command rows render; collapsed state unchanged; Plugins row routes to `/plugins` | +| Remote virtual items | descriptors + status produce cards; detail saves via `remoteControl.saveChannelSettings` | +| Official plugin detail | list/detail enable-disable; settings button no longer calls `settings.open` | +| Deeplink | MCP install focuses main and routes to `/plugins/mcp` | + +Manual visual QA: + +- macOS light/dark with app sidebar and main content. +- Windows light/dark with main app shell. +- Linux opaque backgrounds. +- Narrow width with collapsed and expanded sidebar. +- Long remote token/error/path strings. +- Chinese and English labels. + +Final implementation gates: + +```bash +pnpm run format +pnpm run i18n +pnpm run lint +pnpm run typecheck +``` + +Renderer tests should be run for touched components. Full app smoke test should open Chat, Plugins and Settings separately. + +## Risks and Mitigations + +| Risk | Mitigation | +| --- | --- | +| Settings routes are used by deeplinks/onboarding | Keep hidden compatibility routes and redirect to main `/plugins...` | +| RemoteSettings monolith makes migration risky | Extract per-channel detail only; reuse current route/client behavior | +| Plugin settings HTML depends on plugin preload | Do not embed arbitrary HTML in first increment; build first-party native details | +| Feishu official plugin vs Feishu Remote naming collision | Use explicit `Integration` vs `Remote` labels and category badges | +| Plugins page becomes another Settings | Catalog page stays Codex-like; detail pages are dense only where settings are unavoidable | +| Main route conflicts with chat internal `pageRouter` | Use Vue router for `/plugins`; keep `pageRouter` scoped to ChatTabView | +| Search behavior confusion | Sidebar Search row opens existing Spotlight; do not add a new search engine | + +## Rollout Plan + +1. Land `/plugins` main route skeleton with top tabs and catalog placeholder. +2. Move visible Settings entries out, with redirects to main Plugins routes. +3. Move MCP page and deeplink. +4. Move Skills page. +5. Add official plugin native list/detail and stop first-party UI from opening plugin settings windows. +6. Add Remote virtual plugin list/detail. +7. Update main sidebar expanded command list. +8. Run visual QA and clean up i18n/tests. + +This order keeps each PR reviewable and avoids breaking every surface at once. diff --git a/docs/features/plugins-hub/spec.md b/docs/features/plugins-hub/spec.md new file mode 100644 index 0000000000..1ffa1d2e39 --- /dev/null +++ b/docs/features/plugins-hub/spec.md @@ -0,0 +1,358 @@ +# Plugins Hub Specification + +## User Need + +DeepChat 的 Settings 里混入了高频工具能力、扩展能力和系统偏好。用户想管理 Skills、MCP +servers、official Plugins 和 Remote control channels 时,不应该打开 Settings,也不应该弹一个新的 +插件设置窗口。 + +本目标是把插件型能力移动到主窗口的一级页面和子路由中,形态参考 Codex 的主窗口 Plugins 页面: +左侧仍是主窗口 sidebar,右侧主内容区显示 `Plugins` 页面、顶部 tabs、搜索、已添加项和推荐项。 + +## Product Position + +`Plugins` 是主窗口里的扩展能力页面,不是独立 BrowserWindow,也不是 Settings 的子页面。 + +| Capability | 在主窗口 Plugins 页面中的定位 | 数据事实源 | +| --- | --- | --- | +| Official Plugins | 可启停的 DeepChat first-party plugin package,例如 CUA、Feishu/Lark Integration | `PluginPresenter` + `plugins.*` routes | +| MCP | 工具 server 管理、market、global MCP enablement | `McpPresenter` / `useMcpStore` | +| Skills | agent skill 管理、导入导出、sync、draft suggestion | `SkillPresenter` / `SkillSyncPresenter` / `useSkillsStore` | +| Remote | Telegram、Feishu/Lark、QQBot、Discord、WeChat iLink 作为 virtual plugin card | `RemoteControlPresenter` + `remoteControl.*` routes | + +Remote channel 是 Plugins UI 里的 virtual plugin,不是 `.dcplugin` 安装包。这个建模只改变用户入口和 +展示方式,不改变 remote control 的配置存储、runtime 生命周期或消息协议。 + +## Goals + +- 新增主窗口 route:`/plugins`。 +- 在主窗口主内容区集中管理 Official Plugins、MCP、Skills、Remote。 +- 主窗口左侧 sidebar 展开态显示 `New Chat`、`Search`、`Plugins` command list,点击 `Plugins` 进入 `/plugins`。 +- `Plugins` 页面保留左侧 sidebar,右侧内容区切换,不创建新窗口。 +- Remote 每个 implemented channel 都作为 plugin-like card 出现,并能进入该 channel 的详情子路由。 +- Remote 设置页不再在 Settings 中展示;从列表进入详情时使用主窗口 Plugins 子路由。 +- Official plugin 的详情和设置入口不再弹出 per-plugin BrowserWindow;从列表进入详情时使用主窗口 Plugins 子路由。 +- Settings 侧边栏、Settings Overview 搜索和 quick entry 不再展示 Skills、MCP、Plugins、Remote。 +- 保留 Settings 内部旧 route 的兼容能力,避免 deeplink、onboarding 或历史入口直接 404。 +- `所有 Agents` 标题保留。 +- UI 需要在 macOS、Windows、Linux 以及窄窗口下保持可用和美观。 + +## Non-Goals + +- 不做第三方 plugin marketplace。 +- 不把 Remote channel 改造成真实 `.dcplugin` 包。 +- 不迁移 provider、model、DeepChat Agents、ACP Agents、prompt、memory、knowledge、data、shortcut、about 等系统设置。 +- 不新增 Automations 入口;参考截图中有 Automations,但本目标只做 `New Chat`、`Search`、`Plugins`。 +- 不新增独立 Plugins BrowserWindow。 +- 不新增 `src/renderer/plugins` 独立 renderer entry。 +- 不重写 MCP、Skill、Remote、Plugin presenter。 +- 不新增统一持久化表来存一个“大插件模型”。 +- 不改变 existing Remote commands、pairing protocol、channel binding behavior。 +- 不改变 existing MCP server config schema、Skill sidecar schema 或 plugin manifest schema,除非内嵌设置页确实需要最小 route 补充。 + +## Current State + +Relevant current files: + +| Area | Current files | +| --- | --- | +| Main app shell | `src/renderer/src/App.vue`, `src/renderer/src/router/index.ts` | +| Main sidebar | `src/renderer/src/components/WindowSideBar.vue`, `src/renderer/src/stores/ui/sidebar.ts` | +| Chat page internal route state | `src/renderer/src/stores/ui/pageRouter.ts`, `src/renderer/src/views/ChatTabView.vue` | +| Settings shell and navigation | `src/renderer/settings/App.vue`, `src/renderer/settings/main.ts`, `src/shared/settingsNavigation.ts` | +| Settings window lifecycle | `src/main/presenter/windowPresenter/index.ts`, `src/shared/contracts/routes/system.routes.ts` | +| Plugins settings page | `src/renderer/settings/components/PluginsSettings.vue`, `src/renderer/api/PluginClient.ts`, `src/shared/contracts/routes/plugins.routes.ts` | +| MCP settings page | `src/renderer/settings/components/McpSettings.vue`, `src/renderer/src/components/mcp-config/**`, `src/renderer/src/stores/mcp.ts` | +| Skills settings page | `src/renderer/settings/components/skills/SkillsSettings.vue`, `src/renderer/src/stores/skillsStore.ts` | +| Remote settings page | `src/renderer/settings/components/RemoteSettings.vue`, `src/renderer/api/RemoteControlClient.ts` | + +Important current constraints: + +- Main window Vue router currently exposes `/chat` and `/welcome`. +- Main shell already keeps `WindowSideBar` outside `RouterView`, so adding `/plugins` naturally preserves the sidebar. +- Settings navigation is centralized in `src/shared/settingsNavigation.ts`. +- Settings routes are generated from navigation items in `src/renderer/settings/main.ts`. +- `system.openSettings` only accepts `SettingsRouteNameSchema`. +- MCP install deeplinks currently open Settings and send `DEEPLINK_EVENTS.MCP_INSTALL`. +- Plugin settings currently call `plugins.invokeAction({ actionId: 'settings.open' })`, which opens a per-plugin BrowserWindow. +- Remote channels already expose `RemoteChannelDescriptor`, status, settings, bindings and pairing through typed routes. + +## Proposed Main Route Structure + +```text +src/renderer/src/router/index.ts +├── /chat +├── /welcome +└── /plugins + ├── tab=plugins or child /plugins + ├── /plugins/skills + ├── /plugins/mcp + ├── /plugins/remote + ├── /plugins/official/:pluginId + └── /plugins/remote/:channel +``` + +Implementation can use nested Vue routes or one `/plugins` route with internal tab state. The URL must be shareable enough for internal navigation and redirects: + +| Target | Required addressable route | +| --- | --- | +| Plugins catalog | `/plugins` | +| Skills | `/plugins/skills` | +| MCP | `/plugins/mcp` | +| Remote list | `/plugins/remote` | +| Official plugin detail | `/plugins/official/:pluginId` | +| Remote channel detail | `/plugins/remote/:channel` | + +## Proposed Information Architecture + +Top-level sections: + +| Section | User label | Contents | +| --- | --- | --- | +| Plugins | Plugins | official plugin packages, added/recommended plugin cards, Remote virtual plugin cards when filtered into plugin catalog | +| Skills | Skills | installed skills, install, edit, sync import/export, draft suggestion toggle | +| MCP | MCP Servers | user MCP servers, plugin-owned MCP status, MCP market/add flow | +| Remote | Remote | virtual plugin cards for Telegram, Feishu/Lark Remote, QQBot, Discord, WeChat iLink | + +The visual top tab row can start with `Plugins` and `Skills` as in the Codex screenshot, then add `MCP` and `Remote` if all four areas ship in the same increment. If product wants the screenshot to stay visually lighter, `MCP` and `Remote` can appear as catalog filters/cards inside `Plugins`, but they still need addressable routes. + +Remote naming must avoid collision with official plugins: + +| Existing item | Display name in Plugins | +| --- | --- | +| `com.deepchat.plugins.feishu` official plugin | `Feishu/Lark Integration` | +| `remote:feishu` virtual plugin | `Feishu/Lark Remote` | +| `remote:telegram` virtual plugin | `Telegram Remote` | + +## Main Window Plugins UX + +### Desktop Layout + +```text +┌──────────────────────────────────────────────────────────────────────────────┐ +│ AppBar │ +├───────────────┬──────────────────────────────────────────────────────────────┤ +│ New Chat │ [Plugins] [Skills] [MCP] [Remote] + ↻ │ +│ Search │ │ +│ Plugins │ Plugins │ +│ │ Work with DeepChat across your favorite tools │ +│ Pinned │ ┌────────────────────────────────────────────┐ │ +│ ... │ │ Search plugins, skills, MCP servers... │ │ +│ Projects │ └────────────────────────────────────────────┘ │ +│ ... │ │ +│ Settings │ Added Manage │ +│ │ [CUA] [Feishu] [Telegram] [Skill pack] │ +│ │ │ +│ │ Featured │ +│ │ Computer Use Add Chrome Add │ +│ │ Spreadsheets ... Presentations ... │ +└───────────────┴──────────────────────────────────────────────────────────────┘ +``` + +### Detail Route Layout + +```text +┌──────────────────────────────────────────────────────────────────────────────┐ +│ AppBar │ +├───────────────┬──────────────────────────────────────────────────────────────┤ +│ New Chat │ [Plugins] [Skills] [MCP] [Remote] │ +│ Search │ ← Back to Plugins │ +│ Plugins │ Telegram Remote on/off │ +│ │ Status: running · bindings: 2 · last error: none │ +│ Pinned │ │ +│ ... │ ┌ Credentials ────────────────────────────────────────────┐ │ +│ Settings │ │ Bot token / app credentials │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ +│ │ ┌ Remote Control ────────────────────────────────────────┐ │ +│ │ │ Default agent · Default workdir · Pairing │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ +│ │ ┌ Bindings ──────────────────────────────────────────────┐ │ +│ │ │ Existing chats/channels and remove actions │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ +└───────────────┴──────────────────────────────────────────────────────────────┘ +``` + +### Narrow Main Window Layout + +At constrained widths, keep the same app shell and avoid modal navigation: + +```text +┌────────────────────────────────────┐ +│ AppBar │ +├────┬───────────────────────────────┤ +│ AI │ [Plugins][Skills][MCP] │ +│ .. │ [Remote] │ +│ ├───────────────────────────────┤ +│ │ Search │ +│ │ │ +│ │ Card list / Detail page │ +│ │ │ +└────┴───────────────────────────────┘ +``` + +Narrow behavior: + +- If sidebar is collapsed, it stays collapsed. +- Section navigation becomes a wrapped horizontal tab row. +- Detail pages keep a top back button. +- Long tokens, paths and errors must truncate with tooltip or wrap in a controlled block. +- Forms use one column. + +## Sidebar UX + +### Target Expanded Shape + +```text +┌────┬──────────────────────────────┐ +│ AI │ 所有 Agents │ +│ │ │ +│ │ ┌──────────────────────────┐ │ +│ │ │ ✎ New Chat ⌘N │ │ +│ │ │ 🔍 Search ⌘P │ │ +│ │ │ ⌘ Plugins │ │ +│ │ └──────────────────────────┘ │ +│ │ │ +│ │ 置顶会话 │ +│ │ ... │ +└────┴──────────────────────────────┘ +``` + +Notes: + +- `New Chat` starts a new conversation through the existing session store path and navigates to `/chat` if needed. +- `Search` opens existing Spotlight/search behavior; it is not a second search implementation. +- `Plugins` routes the current main window to `/plugins`. +- Shortcut badges display only for existing registered shortcuts. Do not add a new shortcut just to fill the badge. +- The existing collapsed rail stays visually and behaviorally unchanged. +- The existing Agent icon rail remains the collapsed-state affordance; no new collapsed Plugins icon is added. +- The group-mode toggle and new-chat plus button in the old header should not remain as competing primary actions in expanded mode. Group mode can move near the session list header or stay as a compact secondary control. + +### Collapsed Shape + +Collapsed state remains current: + +```text +┌────┐ +│ ◎ │ all agents / agent icons +│ .. │ +│ 🔍 │ existing search affordance +│ .. │ existing status/theme/sidebar/settings affordances +└────┘ +``` + +## Settings UX + +Settings remains for system/model/account/data preferences: + +```text +Settings +├── Overview +├── Common +├── Display +├── Environments +├── Providers +├── DeepChat Agents +├── ACP +├── Notifications / Hooks +├── Scheduled Tasks +├── Prompt +├── Memory +├── Knowledge Base +├── Database +├── Shortcuts +└── About +``` + +Removed from visible Settings navigation: + +- MCP +- Remote +- Plugins +- Skills + +Compatibility behavior: + +- Existing internal settings routes can remain hidden during migration. +- Direct navigation to removed route names should focus the main window and route to the matching `/plugins...` page when possible. +- Settings Overview search should not list hidden Plugins-owned entries. +- Settings activity records can keep historical `routeName` values; opening them should redirect to Plugins when the route is now Plugins-owned. + +## Acceptance Criteria + +### Main Window Route + +- `/plugins` renders inside the existing main app shell and keeps `WindowSideBar` visible. +- Opening Plugins from the main sidebar navigates the current main window to `/plugins`. +- No new Plugins BrowserWindow is created. +- No new `src/renderer/plugins` renderer entry is added. +- `AppBar`, sidebar, theme, language direction and global overlays continue to work. +- The page has stable responsive behavior and remains usable at narrow widths. +- User-facing strings use i18n keys. + +### Official Plugins + +- Official plugin list keeps enable/disable/status behavior. +- Plugin-owned MCP errors remain visible. +- Opening an official plugin settings/detail uses `/plugins/official/:pluginId`, not Settings and not a per-plugin BrowserWindow. +- CUA detail includes runtime/MCP status, permission checks and permission guide actions. +- Feishu/Lark Integration detail distinguishes MCP/Skill integration from Feishu/Lark Remote control. +- Legacy `settings.open` plugin action is not used as the primary UI path after migration. + +### MCP + +- MCP global enablement, server list, add/edit, market view and NPM registry controls remain available from Plugins. +- Plugin-owned MCP servers remain read-only where their owning plugin controls lifecycle. +- MCP install deeplinks focus the main window and route to `/plugins/mcp` instead of opening Settings. + +### Skills + +- Installed Skills list, search, install, edit, delete, sync import/export and draft suggestion toggle remain available from Plugins. +- First-launch sync prompt remains available if it is still part of the current product flow. +- Skill drag/drop and URL/zip/folder install behavior remains unchanged. + +### Remote + +- Every implemented `RemoteChannelDescriptor` appears as a Remote virtual plugin card. +- Each card shows enabled state, runtime state, binding/pairing summary and last error when present. +- Each card opens `/plugins/remote/:channel`. +- Channel settings preserve current behavior: + - credentials + - enable/disable + - default agent + - default workdir + - pairing + - bindings/principals + - channel-specific login/account controls for WeChat iLink +- Saving a channel setting still uses `remoteControl.saveChannelSettings`. +- Remote status indicator in the main sidebar continues to work. + +### Main Sidebar + +- Expanded sidebar shows `New Chat`, `Search`, `Plugins` before pinned sessions. +- `所有 Agents` remains visible. +- Collapsed sidebar keeps current visual shape and behavior. +- No new collapsed icon button is added. +- Session list pagination, pinned section, project grouping, drag reorder and keyboard shortcut badges continue working. + +### Settings Removal + +- Settings sidebar no longer shows MCP, Remote, Plugins or Skills. +- Settings Overview search no longer returns MCP, Remote, Plugins or Skills as settings pages. +- Settings Overview no longer uses MCP as one of the primary system metrics or quick-start tasks. +- Existing app code that opens `settings-mcp`, `settings-remote`, `settings-plugins` or `settings-skills` is migrated or redirected to `/plugins...`. + +## Platform and Accessibility Requirements + +- Keyboard navigation works across top tabs, search, card list, detail forms and back navigation. +- `Esc` closes transient dialogs only; it does not leave `/plugins`. +- `Tab` order follows visual order. +- Buttons and icon-only controls have accessible labels. +- Status colors are not the only status signal; labels must remain visible. +- Long file paths, tokens, error strings and command lines do not overflow their container. +- Remote credentials remain password inputs by default, preserving current reveal behavior. +- Linux and Windows backgrounds must not rely on macOS-only materials. +- RTL languages should inherit existing app i18n direction handling. + +## Open Questions + +None. diff --git a/docs/features/plugins-hub/tasks.md b/docs/features/plugins-hub/tasks.md new file mode 100644 index 0000000000..bc9faa1fcd --- /dev/null +++ b/docs/features/plugins-hub/tasks.md @@ -0,0 +1,119 @@ +# Plugins Hub Tasks + +## 0. Review Gate + +- [ ] Review `spec.md` with product/maintainers. +- [ ] Review `plan.md` main-route architecture, route compatibility, and sidebar layout. +- [ ] Confirm no unresolved clarification markers exist before implementation. +- [ ] Keep this SDD folder active until the feature lands or is deliberately abandoned. + +## 1. Main Route Skeleton + +- [ ] Add `/plugins` route family to the existing main renderer router. +- [ ] Add `PluginsHubPage.vue` inside `src/renderer/src/pages/plugins/`. +- [ ] Add top tab navigation for Plugins, Skills, MCP and Remote or the chosen first-increment subset. +- [ ] Add Codex-like catalog placeholder with title, subtitle, search, added strip and featured sections. +- [ ] Keep `WindowSideBar`, `AppBar`, global overlays, theme and i18n behavior intact. +- [ ] Add i18n keys for route, page title, subtitle, tabs and search placeholder. +- [ ] Add renderer tests proving `/plugins` renders inside the existing app shell. + +## 2. Main-Process Navigation Compatibility + +- [ ] Add or reuse a typed main-window focus/navigation route for main-process initiated navigation. +- [ ] Ensure the route can focus/create the normal main window and navigate to `/plugins...`. +- [ ] Do not add a Plugins BrowserWindow. +- [ ] Do not add `src/renderer/plugins` or a separate renderer entry. +- [ ] Add tests for focusing main and navigating to `/plugins/mcp`. + +## 3. Settings Navigation Cleanup + +- [ ] Hide or remove visible Settings navigation items for MCP, Remote, Plugins, and Skills. +- [ ] Keep compatibility routes or redirect handlers for old route names. +- [ ] Map old route names to main `/plugins...` routes. +- [ ] Remove MCP from Settings Overview primary metric. +- [ ] Remove or replace Settings Overview `start-mcp` quick task. +- [ ] Ensure Settings Overview search does not return hidden Plugins-owned pages. +- [ ] Update Settings activity click behavior for historical routes. +- [ ] Add tests for Settings navigation groups and hidden route handling. + +## 4. MCP Section + +- [ ] Create `/plugins/mcp` page using current MCP store/client behavior. +- [ ] Reuse `McpServers` for list/add/edit/toggle. +- [ ] Reuse MCP market view inside `/plugins/mcp?view=market`. +- [ ] Reuse NPM registry controls. +- [ ] Move MCP install deeplink target from Settings to main `/plugins/mcp`. +- [ ] Move MCP install event handling into the main app or Plugins route bootstrap. +- [ ] Keep plugin-owned MCP server read-only behavior. +- [ ] Add tests for deeplink route target and MCP page render. + +## 5. Skills Section + +- [ ] Create `/plugins/skills` page from current Skills settings behavior. +- [ ] Reuse skill list, search, install, edit, delete, sync import/export. +- [ ] Preserve draft suggestion toggle. +- [ ] Preserve first-launch sync prompt if still required. +- [ ] Ensure skill dialogs/sheets fit the main Plugins page shell. +- [ ] Add renderer tests for empty/list/search/install entry behavior. + +## 6. Official Plugins Section + +- [ ] Create official plugin list route from `PluginClient.listPlugins`. +- [ ] Add detail route `/plugins/official/:pluginId`. +- [ ] Keep enable/disable actions. +- [ ] Show runtime status, plugin-owned MCP status and last errors. +- [ ] Add native CUA detail sections for runtime status, permissions and permission guide actions. +- [ ] Add native Feishu/Lark Integration detail that distinguishes it from Feishu/Lark Remote. +- [ ] Stop first-party Plugins UI from calling `settings.open`. +- [ ] Keep `settings.open` only as temporary compatibility fallback. +- [ ] Add tests for list/detail action behavior. + +## 7. Remote Virtual Plugins + +- [ ] Build remote virtual cards from `remoteControl.listChannels`. +- [ ] Fetch and display per-channel status. +- [ ] Add `/plugins/remote` list route. +- [ ] Add `/plugins/remote/:channel` detail route. +- [ ] Extract only the needed RemoteSettings channel sections into reusable components. +- [ ] Preserve credentials fields and password reveal behavior. +- [ ] Preserve enable/disable save behavior. +- [ ] Preserve default agent and default workdir behavior. +- [ ] Preserve pairing flow for Telegram, Feishu/Lark, QQBot and Discord. +- [ ] Preserve binding/principal removal behavior. +- [ ] Preserve WeChat iLink login/account controls. +- [ ] Route sidebar remote status button to `/plugins/remote` or selected channel detail. +- [ ] Add tests for card mapping, save, pairing and bindings. + +## 8. Main Sidebar Layout + +- [ ] Replace expanded sidebar header/search area with command list. +- [ ] Keep `所有 Agents` title. +- [ ] Wire `New Chat` row to navigate to `/chat` and start a new conversation. +- [ ] Wire `Search` row to existing Spotlight behavior. +- [ ] Wire `Plugins` row to `router.push({ name: 'plugins' })`. +- [ ] Display shortcut badges only for existing shortcuts. +- [ ] Keep collapsed sidebar visual behavior unchanged. +- [ ] Preserve session list pagination, pinned section, project grouping and reorder. +- [ ] Add renderer tests for expanded rows and collapsed state. +- [ ] Capture before/after ASCII blocks in PR description. + +## 9. Cross-Platform UI QA + +- [ ] Verify macOS light/dark with app shell and sidebar. +- [ ] Verify Windows light/dark with app shell and sidebar. +- [ ] Verify Linux opaque background. +- [ ] Verify narrow main window layout with expanded sidebar. +- [ ] Verify narrow main window layout with collapsed sidebar. +- [ ] Verify long paths/tokens/errors do not overflow. +- [ ] Verify keyboard navigation and focus order. +- [ ] Verify Chinese and English labels. + +## 10. Final Quality Gates + +- [ ] Run `pnpm run format`. +- [ ] Run `pnpm run i18n`. +- [ ] Run `pnpm run lint`. +- [ ] Run `pnpm run typecheck`. +- [ ] Run targeted renderer tests for Plugins route and sidebar. +- [ ] Run targeted main tests for navigation/deeplink behavior. +- [ ] Update durable docs or remove/archive active plan/tasks after implementation lands. From f11cae2550f6a8d6442832903fbb0ff5795543c9 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Thu, 25 Jun 2026 15:15:32 +0800 Subject: [PATCH 02/14] docs: add plugins hub doc --- docs/features/plugins-hub/plan.md | 30 +++++++++- docs/features/plugins-hub/spec.md | 90 ++++++++++++++++++------------ docs/features/plugins-hub/tasks.md | 6 ++ 3 files changed, 86 insertions(+), 40 deletions(-) diff --git a/docs/features/plugins-hub/plan.md b/docs/features/plugins-hub/plan.md index b6a59380ca..aa5a879e01 100644 --- a/docs/features/plugins-hub/plan.md +++ b/docs/features/plugins-hub/plan.md @@ -318,9 +318,11 @@ right column │ ├── New Chat │ ├── Search │ └── Plugins -├── compact session controls when needed -├── pinned section -└── session groups +├── blank spacer +├── pinned section when non-empty +├── Chat group +├── 工作区 header + existing group-mode/sort toggle +└── project groups ``` Command behavior: @@ -334,10 +336,12 @@ Command behavior: Keep: - Agent icon rail. +- Settings/theme/sidebar controls in the existing left rail. - collapsed width and transitions. - session pagination and fill checks. - pinned collapse behavior. - project grouping/reorder behavior. +- existing group-mode/sort behavior, moved to the `工作区` header. - shortcut badge logic for sessions. Question the old inline session search: @@ -346,6 +350,26 @@ Question the old inline session search: - Search row opens Spotlight, which already searches sessions/messages/settings/actions. - If users later need local-only filtering, add it inside Spotlight or as a session-list filter command, not as a second persistent input. +Right column ordering: + +```text +所有 Agents + +New Chat +Search +Plugins + +Pinned (only if any) +... +Chat +... +工作区 [group/sort toggle] +project groups +... +``` + +Do not add Settings, theme, collapse, remote status or other rail controls into this right column. + ## Deeplinks and External Entry Points Update callers: diff --git a/docs/features/plugins-hub/spec.md b/docs/features/plugins-hub/spec.md index 1ffa1d2e39..b5c8bbbc93 100644 --- a/docs/features/plugins-hub/spec.md +++ b/docs/features/plugins-hub/spec.md @@ -27,7 +27,9 @@ Remote channel 是 Plugins UI 里的 virtual plugin,不是 `.dcplugin` 安装 - 新增主窗口 route:`/plugins`。 - 在主窗口主内容区集中管理 Official Plugins、MCP、Skills、Remote。 -- 主窗口左侧 sidebar 展开态显示 `New Chat`、`Search`、`Plugins` command list,点击 `Plugins` 进入 `/plugins`。 +- 主窗口 sidebar 展开态右栏显示 `New Chat`、`Search`、`Plugins` command list,点击 `Plugins` 进入 `/plugins`。 +- `Plugins` command 下方留出空行,再显示 `Pinned`、`Chat`、`工作区` 和 project groups。 +- Settings 等底部 app controls 继续留在现有左侧 rail,不进入展开右栏。 - `Plugins` 页面保留左侧 sidebar,右侧内容区切换,不创建新窗口。 - Remote 每个 implemented channel 都作为 plugin-like card 出现,并能进入该 channel 的详情子路由。 - Remote 设置页不再在 Settings 中展示;从列表进入详情时使用主窗口 Plugins 子路由。 @@ -132,21 +134,21 @@ Remote naming must avoid collision with official plugins: ┌──────────────────────────────────────────────────────────────────────────────┐ │ AppBar │ ├───────────────┬──────────────────────────────────────────────────────────────┤ -│ New Chat │ [Plugins] [Skills] [MCP] [Remote] + ↻ │ -│ Search │ │ -│ Plugins │ Plugins │ -│ │ Work with DeepChat across your favorite tools │ -│ Pinned │ ┌────────────────────────────────────────────┐ │ -│ ... │ │ Search plugins, skills, MCP servers... │ │ -│ Projects │ └────────────────────────────────────────────┘ │ -│ ... │ │ -│ Settings │ Added Manage │ -│ │ [CUA] [Feishu] [Telegram] [Skill pack] │ -│ │ │ -│ │ Featured │ -│ │ Computer Use Add Chrome Add │ -│ │ Spreadsheets ... Presentations ... │ -└───────────────┴──────────────────────────────────────────────────────────────┘ +│rail│ expanded sidebar │ [Plugins] [Skills] [MCP] [Remote] + ↻ │ +│ │ 所有 Agents │ │ +│ │ New Chat │ Plugins │ +│ │ Search │ Work with DeepChat across your favorite tools │ +│ │ Plugins │ ┌────────────────────────────────────────────┐ │ +│ │ │ │ Search plugins, skills, MCP servers... │ │ +│ │ Pinned │ └────────────────────────────────────────────┘ │ +│ │ ... │ │ +│ │ Chat │ Added Manage │ +│ │ ... │ [CUA] [Feishu] [Telegram] [Skill pack] │ +│ │ 工作区 [sort]│ │ +│ │ project A │ Featured │ +│ │ project B │ Computer Use Add Chrome Add │ +│⚙︎ │ │ Spreadsheets ... Presentations ... │ +└────┴──────────────────┴──────────────────────────────────────────────────────────────┘ ``` ### Detail Route Layout @@ -155,21 +157,21 @@ Remote naming must avoid collision with official plugins: ┌──────────────────────────────────────────────────────────────────────────────┐ │ AppBar │ ├───────────────┬──────────────────────────────────────────────────────────────┤ -│ New Chat │ [Plugins] [Skills] [MCP] [Remote] │ -│ Search │ ← Back to Plugins │ -│ Plugins │ Telegram Remote on/off │ -│ │ Status: running · bindings: 2 · last error: none │ -│ Pinned │ │ -│ ... │ ┌ Credentials ────────────────────────────────────────────┐ │ -│ Settings │ │ Bot token / app credentials │ │ -│ │ └─────────────────────────────────────────────────────────┘ │ -│ │ ┌ Remote Control ────────────────────────────────────────┐ │ -│ │ │ Default agent · Default workdir · Pairing │ │ -│ │ └─────────────────────────────────────────────────────────┘ │ -│ │ ┌ Bindings ──────────────────────────────────────────────┐ │ -│ │ │ Existing chats/channels and remove actions │ │ -│ │ └─────────────────────────────────────────────────────────┘ │ -└───────────────┴──────────────────────────────────────────────────────────────┘ +│rail│ expanded sidebar │ [Plugins] [Skills] [MCP] [Remote] │ +│ │ 所有 Agents │ ← Back to Plugins │ +│ │ New Chat │ Telegram Remote on/off │ +│ │ Search │ Status: running · bindings: 2 · last error: none │ +│ │ Plugins │ │ +│ │ │ ┌ Credentials ────────────────────────────────────┐ │ +│ │ Pinned │ │ Bot token / app credentials │ │ +│ │ ... │ └─────────────────────────────────────────────────┘ │ +│ │ Chat │ ┌ Remote Control ────────────────────────────────┐ │ +│ │ 工作区 [sort]│ │ Default agent · Default workdir · Pairing │ │ +│ │ project A │ └─────────────────────────────────────────────────┘ │ +│⚙︎ │ │ ┌ Bindings ──────────────────────────────────────┐ │ +│ │ │ │ Existing chats/channels and remove actions │ │ +│ │ │ └─────────────────────────────────────────────────┘ │ +└────┴──────────────────┴──────────────────────────────────────────────────────┘ ``` ### Narrow Main Window Layout @@ -180,9 +182,9 @@ At constrained widths, keep the same app shell and avoid modal navigation: ┌────────────────────────────────────┐ │ AppBar │ ├────┬───────────────────────────────┤ -│ AI │ [Plugins][Skills][MCP] │ -│ .. │ [Remote] │ -│ ├───────────────────────────────┤ +│rail│ [Plugins][Skills][MCP] │ +│ │ [Remote] │ +│⚙︎ ├───────────────────────────────┤ │ │ Search │ │ │ │ │ │ Card list / Detail page │ @@ -204,7 +206,7 @@ Narrow behavior: ```text ┌────┬──────────────────────────────┐ -│ AI │ 所有 Agents │ +│rail│ 所有 Agents │ │ │ │ │ │ ┌──────────────────────────┐ │ │ │ │ ✎ New Chat ⌘N │ │ @@ -212,8 +214,13 @@ Narrow behavior: │ │ │ ⌘ Plugins │ │ │ │ └──────────────────────────┘ │ │ │ │ -│ │ 置顶会话 │ +│ │ Pinned (if any) │ │ │ ... │ +│ │ Chat │ +│ │ ... │ +│ │ 工作区 [sort] │ +│ │ project groups │ +│⚙︎ │ ... │ └────┴──────────────────────────────┘ ``` @@ -222,10 +229,15 @@ Notes: - `New Chat` starts a new conversation through the existing session store path and navigates to `/chat` if needed. - `Search` opens existing Spotlight/search behavior; it is not a second search implementation. - `Plugins` routes the current main window to `/plugins`. +- There is a blank spacer after `Plugins` before the conversation sections. +- `Pinned` is rendered only when pinned sessions exist. +- `Chat` remains the unprojected/default chat group. +- `工作区` is a section title for project groups; the existing group-mode/sort toggle moves to this row. - Shortcut badges display only for existing registered shortcuts. Do not add a new shortcut just to fill the badge. - The existing collapsed rail stays visually and behaviorally unchanged. - The existing Agent icon rail remains the collapsed-state affordance; no new collapsed Plugins icon is added. -- The group-mode toggle and new-chat plus button in the old header should not remain as competing primary actions in expanded mode. Group mode can move near the session list header or stay as a compact secondary control. +- Settings, theme and other bottom controls stay in the existing left rail. They are not listed under `Plugins` in the expanded right column. +- The old header new-chat plus button is removed as a competing primary action. The existing group-mode/sort control moves to the `工作区` header. ### Collapsed Shape @@ -329,7 +341,11 @@ Compatibility behavior: ### Main Sidebar - Expanded sidebar shows `New Chat`, `Search`, `Plugins` before pinned sessions. +- Expanded sidebar inserts a blank spacer after `Plugins`. +- Expanded sidebar shows `Pinned` only when pinned sessions exist, then `Chat`, then `工作区`. +- The existing group-mode/sort toggle is placed on the `工作区` header row. - `所有 Agents` remains visible. +- Settings and other bottom controls remain in the existing left rail, not in the expanded right column. - Collapsed sidebar keeps current visual shape and behavior. - No new collapsed icon button is added. - Session list pagination, pinned section, project grouping, drag reorder and keyboard shortcut badges continue working. diff --git a/docs/features/plugins-hub/tasks.md b/docs/features/plugins-hub/tasks.md index bc9faa1fcd..65c000a335 100644 --- a/docs/features/plugins-hub/tasks.md +++ b/docs/features/plugins-hub/tasks.md @@ -91,6 +91,12 @@ - [ ] Wire `New Chat` row to navigate to `/chat` and start a new conversation. - [ ] Wire `Search` row to existing Spotlight behavior. - [ ] Wire `Plugins` row to `router.push({ name: 'plugins' })`. +- [ ] Add a blank spacer after the `Plugins` command row. +- [ ] Render `Pinned` only when pinned sessions exist. +- [ ] Keep the `Chat` group after `Pinned`. +- [ ] Add `工作区` header before project groups. +- [ ] Move the existing group-mode/sort toggle to the `工作区` header. +- [ ] Keep Settings/theme/sidebar controls in the existing left rail, not in the expanded right column. - [ ] Display shortcut badges only for existing shortcuts. - [ ] Keep collapsed sidebar visual behavior unchanged. - [ ] Preserve session list pagination, pinned section, project grouping and reorder. From 6cf01389547cb5a0187abb1b38fd0314b02cded3 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Thu, 25 Jun 2026 15:35:26 +0800 Subject: [PATCH 03/14] feat(plugins): add main window plugins hub --- docs/features/plugins-hub/plan.md | 10 +- docs/features/plugins-hub/tasks.md | 132 +++--- src/main/presenter/deeplinkPresenter/index.ts | 9 +- .../settings/components/McpSettings.vue | 12 +- .../settings/components/RemoteSettings.vue | 26 +- .../settings/components/SettingsOverview.vue | 27 -- .../settings/lib/guidedOnboardingSettings.ts | 13 +- src/renderer/src/components/WindowSideBar.vue | 225 ++++++--- .../components/chat-input/McpIndicator.vue | 6 +- .../components/chat-input/SkillsIndicator.vue | 6 +- src/renderer/src/i18n/da-DK/chat.json | 5 +- src/renderer/src/i18n/da-DK/routes.json | 3 +- src/renderer/src/i18n/da-DK/settings.json | 18 + src/renderer/src/i18n/de-DE/chat.json | 5 +- src/renderer/src/i18n/de-DE/routes.json | 3 +- src/renderer/src/i18n/de-DE/settings.json | 18 + src/renderer/src/i18n/en-US/chat.json | 3 + src/renderer/src/i18n/en-US/routes.json | 1 + src/renderer/src/i18n/en-US/settings.json | 18 + src/renderer/src/i18n/es-ES/chat.json | 5 +- src/renderer/src/i18n/es-ES/routes.json | 3 +- src/renderer/src/i18n/es-ES/settings.json | 18 + src/renderer/src/i18n/fa-IR/chat.json | 5 +- src/renderer/src/i18n/fa-IR/routes.json | 3 +- src/renderer/src/i18n/fa-IR/settings.json | 18 + src/renderer/src/i18n/fr-FR/chat.json | 5 +- src/renderer/src/i18n/fr-FR/routes.json | 3 +- src/renderer/src/i18n/fr-FR/settings.json | 18 + src/renderer/src/i18n/he-IL/chat.json | 5 +- src/renderer/src/i18n/he-IL/routes.json | 3 +- src/renderer/src/i18n/he-IL/settings.json | 18 + src/renderer/src/i18n/id-ID/chat.json | 5 +- src/renderer/src/i18n/id-ID/routes.json | 3 +- src/renderer/src/i18n/id-ID/settings.json | 18 + src/renderer/src/i18n/it-IT/chat.json | 5 +- src/renderer/src/i18n/it-IT/routes.json | 3 +- src/renderer/src/i18n/it-IT/settings.json | 18 + src/renderer/src/i18n/ja-JP/chat.json | 5 +- src/renderer/src/i18n/ja-JP/routes.json | 3 +- src/renderer/src/i18n/ja-JP/settings.json | 18 + src/renderer/src/i18n/ko-KR/chat.json | 5 +- src/renderer/src/i18n/ko-KR/routes.json | 3 +- src/renderer/src/i18n/ko-KR/settings.json | 18 + src/renderer/src/i18n/ms-MY/chat.json | 5 +- src/renderer/src/i18n/ms-MY/routes.json | 3 +- src/renderer/src/i18n/ms-MY/settings.json | 18 + src/renderer/src/i18n/pl-PL/chat.json | 5 +- src/renderer/src/i18n/pl-PL/routes.json | 3 +- src/renderer/src/i18n/pl-PL/settings.json | 18 + src/renderer/src/i18n/pt-BR/chat.json | 5 +- src/renderer/src/i18n/pt-BR/routes.json | 3 +- src/renderer/src/i18n/pt-BR/settings.json | 18 + src/renderer/src/i18n/ru-RU/chat.json | 5 +- src/renderer/src/i18n/ru-RU/routes.json | 3 +- src/renderer/src/i18n/ru-RU/settings.json | 18 + src/renderer/src/i18n/tr-TR/chat.json | 5 +- src/renderer/src/i18n/tr-TR/routes.json | 3 +- src/renderer/src/i18n/tr-TR/settings.json | 18 + src/renderer/src/i18n/vi-VN/chat.json | 5 +- src/renderer/src/i18n/vi-VN/routes.json | 3 +- src/renderer/src/i18n/vi-VN/settings.json | 18 + src/renderer/src/i18n/zh-CN/chat.json | 3 + src/renderer/src/i18n/zh-CN/routes.json | 1 + src/renderer/src/i18n/zh-CN/settings.json | 18 + src/renderer/src/i18n/zh-HK/chat.json | 5 +- src/renderer/src/i18n/zh-HK/routes.json | 3 +- src/renderer/src/i18n/zh-HK/settings.json | 18 + src/renderer/src/i18n/zh-TW/chat.json | 5 +- src/renderer/src/i18n/zh-TW/routes.json | 3 +- src/renderer/src/i18n/zh-TW/settings.json | 18 + src/renderer/src/lib/storeInitializer.ts | 13 + src/renderer/src/pages/WelcomePage.vue | 8 + .../src/pages/plugins/McpPluginsPage.vue | 7 + .../plugins/OfficialPluginDetailPage.vue | 241 ++++++++++ .../src/pages/plugins/PluginsCatalogPage.vue | 437 ++++++++++++++++++ .../src/pages/plugins/PluginsHubPage.vue | 79 ++++ .../src/pages/plugins/RemotePluginsPage.vue | 7 + .../src/pages/plugins/SkillsPluginsPage.vue | 7 + src/renderer/src/router/index.ts | 64 +++ src/renderer/src/stores/ui/spotlight.ts | 16 +- src/shared/settingsNavigation.ts | 12 +- .../renderer/components/WindowSideBar.test.ts | 158 +++---- 82 files changed, 1699 insertions(+), 328 deletions(-) create mode 100644 src/renderer/src/pages/plugins/McpPluginsPage.vue create mode 100644 src/renderer/src/pages/plugins/OfficialPluginDetailPage.vue create mode 100644 src/renderer/src/pages/plugins/PluginsCatalogPage.vue create mode 100644 src/renderer/src/pages/plugins/PluginsHubPage.vue create mode 100644 src/renderer/src/pages/plugins/RemotePluginsPage.vue create mode 100644 src/renderer/src/pages/plugins/SkillsPluginsPage.vue diff --git a/docs/features/plugins-hub/plan.md b/docs/features/plugins-hub/plan.md index aa5a879e01..965d3d5bb9 100644 --- a/docs/features/plugins-hub/plan.md +++ b/docs/features/plugins-hub/plan.md @@ -49,13 +49,13 @@ plugins-official-detail plugins-remote-detail ``` -External/main-process callers should not know UI component internals. Add a narrow route or event only where the main process needs to focus the main window and navigate: +External/main-process callers should not know UI component internals. Reuse the existing app-runtime event path where possible. Only add a narrow route if a future main-process caller needs generic main-window navigation: ```text system.openMainRoute({ routeName: 'plugins-mcp', params? }) -> { focused: boolean } ``` -If an existing main-window navigation route already exists during implementation, reuse it. Do not add a Plugins-specific window route. +For the first increment, MCP install deeplinks reuse `DEEPLINK_EVENTS.MCP_INSTALL` and the main app deeplink handler routes the renderer to `/plugins/mcp`. Do not add a Plugins-specific window route. ## Affected Boundaries @@ -64,7 +64,7 @@ If an existing main-window navigation route already exists during implementation | `src/renderer/src/router/index.ts` | Add `/plugins` route family | | `src/renderer/src/App.vue` | Keep existing shell; ensure `/plugins` receives same global overlays/theme/i18n | | `WindowSideBar.vue` | Add expanded command list and route Plugins row to `/plugins` | -| `renderer/api` | Add or reuse a small main-window navigation client only for main-process initiated navigation | +| `renderer/api` | Reuse existing clients; add a main-window navigation client only if a generic main-process caller appears | | `shared/contracts/routes` | Add narrow focus/navigate route only if deeplink/main process cannot use existing event path | | Settings renderer | Remove/hide Plugins-owned nav entries and overview links | | Deeplink presenter | Route MCP install deeplink to main `/plugins/mcp` page | @@ -217,7 +217,9 @@ Compatibility: ## Remote Migration -Do not keep `RemoteSettings.vue` as one giant page inside Plugins. It is already too large. +First increment: reuse `RemoteSettings.vue` inside `/plugins/remote` and `/plugins/remote/:channel`, with route-param synchronization so direct channel links open the matching tab. This keeps the existing credential, pairing, default agent/workdir, bindings and WeChat iLink behavior intact. + +Follow-up refactor: extract channel sections from `RemoteSettings.vue` into reusable components. The file is already large, but splitting it before moving the route would increase regression risk and delay the user-visible entry-point cleanup. Refactor only around real channel boundaries: diff --git a/docs/features/plugins-hub/tasks.md b/docs/features/plugins-hub/tasks.md index 65c000a335..2b68d2a790 100644 --- a/docs/features/plugins-hub/tasks.md +++ b/docs/features/plugins-hub/tasks.md @@ -2,104 +2,104 @@ ## 0. Review Gate -- [ ] Review `spec.md` with product/maintainers. -- [ ] Review `plan.md` main-route architecture, route compatibility, and sidebar layout. -- [ ] Confirm no unresolved clarification markers exist before implementation. +- [x] Review `spec.md` with product/maintainers. +- [x] Review `plan.md` main-route architecture, route compatibility, and sidebar layout. +- [x] Confirm no unresolved clarification markers exist before implementation. - [ ] Keep this SDD folder active until the feature lands or is deliberately abandoned. ## 1. Main Route Skeleton -- [ ] Add `/plugins` route family to the existing main renderer router. -- [ ] Add `PluginsHubPage.vue` inside `src/renderer/src/pages/plugins/`. -- [ ] Add top tab navigation for Plugins, Skills, MCP and Remote or the chosen first-increment subset. -- [ ] Add Codex-like catalog placeholder with title, subtitle, search, added strip and featured sections. -- [ ] Keep `WindowSideBar`, `AppBar`, global overlays, theme and i18n behavior intact. -- [ ] Add i18n keys for route, page title, subtitle, tabs and search placeholder. +- [x] Add `/plugins` route family to the existing main renderer router. +- [x] Add `PluginsHubPage.vue` inside `src/renderer/src/pages/plugins/`. +- [x] Add top tab navigation for Plugins, Skills, MCP and Remote or the chosen first-increment subset. +- [x] Add Codex-like catalog placeholder with title, subtitle, search, added strip and featured sections. +- [x] Keep `WindowSideBar`, `AppBar`, global overlays, theme and i18n behavior intact. +- [x] Add i18n keys for route, page title, subtitle, tabs and search placeholder. - [ ] Add renderer tests proving `/plugins` renders inside the existing app shell. ## 2. Main-Process Navigation Compatibility -- [ ] Add or reuse a typed main-window focus/navigation route for main-process initiated navigation. -- [ ] Ensure the route can focus/create the normal main window and navigate to `/plugins...`. -- [ ] Do not add a Plugins BrowserWindow. -- [ ] Do not add `src/renderer/plugins` or a separate renderer entry. +- [x] Reuse existing deeplink event handling for main-process initiated MCP navigation. +- [x] Ensure MCP install deeplink can focus/create the normal main window and navigate to `/plugins/mcp`. +- [x] Do not add a Plugins BrowserWindow. +- [x] Do not add `src/renderer/plugins` or a separate renderer entry. - [ ] Add tests for focusing main and navigating to `/plugins/mcp`. ## 3. Settings Navigation Cleanup -- [ ] Hide or remove visible Settings navigation items for MCP, Remote, Plugins, and Skills. -- [ ] Keep compatibility routes or redirect handlers for old route names. -- [ ] Map old route names to main `/plugins...` routes. -- [ ] Remove MCP from Settings Overview primary metric. -- [ ] Remove or replace Settings Overview `start-mcp` quick task. -- [ ] Ensure Settings Overview search does not return hidden Plugins-owned pages. +- [x] Hide or remove visible Settings navigation items for MCP, Remote, Plugins, and Skills. +- [x] Keep compatibility routes or redirect handlers for old route names. +- [ ] Map every old route name to main `/plugins...` routes. +- [x] Remove MCP from Settings Overview primary metric. +- [x] Remove or replace Settings Overview `start-mcp` quick task. +- [x] Ensure Settings Overview search does not return hidden Plugins-owned pages. - [ ] Update Settings activity click behavior for historical routes. - [ ] Add tests for Settings navigation groups and hidden route handling. ## 4. MCP Section -- [ ] Create `/plugins/mcp` page using current MCP store/client behavior. -- [ ] Reuse `McpServers` for list/add/edit/toggle. -- [ ] Reuse MCP market view inside `/plugins/mcp?view=market`. -- [ ] Reuse NPM registry controls. -- [ ] Move MCP install deeplink target from Settings to main `/plugins/mcp`. -- [ ] Move MCP install event handling into the main app or Plugins route bootstrap. -- [ ] Keep plugin-owned MCP server read-only behavior. +- [x] Create `/plugins/mcp` page using current MCP store/client behavior. +- [x] Reuse `McpSettings`/current MCP components for list/add/edit/toggle. +- [x] Reuse MCP market view inside `/plugins/mcp?view=market`. +- [x] Reuse NPM registry controls. +- [x] Move MCP install deeplink target from Settings to main `/plugins/mcp`. +- [x] Move MCP install event handling into the main app or Plugins route bootstrap. +- [x] Keep plugin-owned MCP server read-only behavior. - [ ] Add tests for deeplink route target and MCP page render. ## 5. Skills Section -- [ ] Create `/plugins/skills` page from current Skills settings behavior. -- [ ] Reuse skill list, search, install, edit, delete, sync import/export. -- [ ] Preserve draft suggestion toggle. -- [ ] Preserve first-launch sync prompt if still required. -- [ ] Ensure skill dialogs/sheets fit the main Plugins page shell. +- [x] Create `/plugins/skills` page from current Skills settings behavior. +- [x] Reuse skill list, search, install, edit, delete, sync import/export. +- [x] Preserve draft suggestion toggle. +- [x] Preserve first-launch sync prompt if still required. +- [x] Ensure skill dialogs/sheets fit the main Plugins page shell. - [ ] Add renderer tests for empty/list/search/install entry behavior. ## 6. Official Plugins Section -- [ ] Create official plugin list route from `PluginClient.listPlugins`. -- [ ] Add detail route `/plugins/official/:pluginId`. -- [ ] Keep enable/disable actions. -- [ ] Show runtime status, plugin-owned MCP status and last errors. +- [x] Create official plugin list route from `PluginClient.listPlugins`. +- [x] Add detail route `/plugins/official/:pluginId`. +- [x] Keep enable/disable actions. +- [x] Show runtime status, plugin-owned MCP status and last errors. - [ ] Add native CUA detail sections for runtime status, permissions and permission guide actions. - [ ] Add native Feishu/Lark Integration detail that distinguishes it from Feishu/Lark Remote. -- [ ] Stop first-party Plugins UI from calling `settings.open`. -- [ ] Keep `settings.open` only as temporary compatibility fallback. +- [x] Stop first-party Plugins UI from calling `settings.open`. +- [x] Keep `settings.open` only as temporary compatibility fallback. - [ ] Add tests for list/detail action behavior. ## 7. Remote Virtual Plugins -- [ ] Build remote virtual cards from `remoteControl.listChannels`. -- [ ] Fetch and display per-channel status. -- [ ] Add `/plugins/remote` list route. -- [ ] Add `/plugins/remote/:channel` detail route. +- [x] Build remote virtual cards from `remoteControl.listChannels`. +- [x] Fetch and display per-channel status. +- [x] Add `/plugins/remote` route. +- [x] Add `/plugins/remote/:channel` detail route. - [ ] Extract only the needed RemoteSettings channel sections into reusable components. -- [ ] Preserve credentials fields and password reveal behavior. -- [ ] Preserve enable/disable save behavior. -- [ ] Preserve default agent and default workdir behavior. -- [ ] Preserve pairing flow for Telegram, Feishu/Lark, QQBot and Discord. -- [ ] Preserve binding/principal removal behavior. -- [ ] Preserve WeChat iLink login/account controls. -- [ ] Route sidebar remote status button to `/plugins/remote` or selected channel detail. +- [x] Preserve credentials fields and password reveal behavior. +- [x] Preserve enable/disable save behavior. +- [x] Preserve default agent and default workdir behavior. +- [x] Preserve pairing flow for Telegram, Feishu/Lark, QQBot and Discord. +- [x] Preserve binding/principal removal behavior. +- [x] Preserve WeChat iLink login/account controls. +- [x] Route sidebar remote status button to `/plugins/remote` or selected channel detail. - [ ] Add tests for card mapping, save, pairing and bindings. ## 8. Main Sidebar Layout -- [ ] Replace expanded sidebar header/search area with command list. -- [ ] Keep `所有 Agents` title. -- [ ] Wire `New Chat` row to navigate to `/chat` and start a new conversation. -- [ ] Wire `Search` row to existing Spotlight behavior. -- [ ] Wire `Plugins` row to `router.push({ name: 'plugins' })`. -- [ ] Add a blank spacer after the `Plugins` command row. -- [ ] Render `Pinned` only when pinned sessions exist. -- [ ] Keep the `Chat` group after `Pinned`. -- [ ] Add `工作区` header before project groups. -- [ ] Move the existing group-mode/sort toggle to the `工作区` header. -- [ ] Keep Settings/theme/sidebar controls in the existing left rail, not in the expanded right column. -- [ ] Display shortcut badges only for existing shortcuts. -- [ ] Keep collapsed sidebar visual behavior unchanged. -- [ ] Preserve session list pagination, pinned section, project grouping and reorder. +- [x] Replace expanded sidebar header/search area with command list. +- [x] Keep `所有 Agents` title. +- [x] Wire `New Chat` row to navigate to `/chat` and start a new conversation. +- [x] Wire `Search` row to existing Spotlight behavior. +- [x] Wire `Plugins` row to `router.push({ name: 'plugins' })`. +- [x] Add a blank spacer after the `Plugins` command row. +- [x] Render `Pinned` only when pinned sessions exist. +- [x] Keep the `Chat` group after `Pinned`. +- [x] Add `工作区` header before project groups. +- [x] Move the existing group-mode/sort toggle to the `工作区` header. +- [x] Keep Settings/theme/sidebar controls in the existing left rail, not in the expanded right column. +- [x] Display shortcut badges only for existing shortcuts. +- [x] Keep collapsed sidebar visual behavior unchanged. +- [x] Preserve session list pagination, pinned section, project grouping and reorder. - [ ] Add renderer tests for expanded rows and collapsed state. - [ ] Capture before/after ASCII blocks in PR description. @@ -116,10 +116,10 @@ ## 10. Final Quality Gates -- [ ] Run `pnpm run format`. -- [ ] Run `pnpm run i18n`. -- [ ] Run `pnpm run lint`. -- [ ] Run `pnpm run typecheck`. +- [x] Run `pnpm run format`. +- [x] Run `pnpm run i18n`. +- [x] Run `pnpm run lint`. +- [x] Run `pnpm run typecheck`. - [ ] Run targeted renderer tests for Plugins route and sidebar. - [ ] Run targeted main tests for navigation/deeplink behavior. - [ ] Update durable docs or remove/archive active plan/tasks after implementation lands. diff --git a/src/main/presenter/deeplinkPresenter/index.ts b/src/main/presenter/deeplinkPresenter/index.ts index a0a05d6746..c63624f9a9 100644 --- a/src/main/presenter/deeplinkPresenter/index.ts +++ b/src/main/presenter/deeplinkPresenter/index.ts @@ -384,13 +384,14 @@ export class DeeplinkPresenter implements IDeeplinkPresenter { return } - const settingsWindowId = await presenter.windowPresenter.createSettingsWindow() - if (!settingsWindowId) { - console.error('Failed to open Settings window for MCP install deeplink') + const targetWindow = await this.resolveChatWindow() + if (!targetWindow) { + console.error('Failed to resolve main window for MCP install deeplink') return } - presenter.windowPresenter.sendToWindow(settingsWindowId, DEEPLINK_EVENTS.MCP_INSTALL, { + await this.ensureChatWindowReady(targetWindow.id) + presenter.windowPresenter.sendToWindow(targetWindow.id, DEEPLINK_EVENTS.MCP_INSTALL, { mcpConfig: JSON.stringify(completeMcpConfig) }) diff --git a/src/renderer/settings/components/McpSettings.vue b/src/renderer/settings/components/McpSettings.vue index b0f925ac66..39bad89f9f 100644 --- a/src/renderer/settings/components/McpSettings.vue +++ b/src/renderer/settings/components/McpSettings.vue @@ -497,15 +497,23 @@ const closeMarketView = async () => { const nextQuery = { ...route.query } delete nextQuery.view + const routeName = + typeof router.hasRoute === 'function' && router.hasRoute('plugins-mcp') + ? 'plugins-mcp' + : 'settings-mcp' await router.replace({ - name: 'settings-mcp', + name: routeName, query: nextQuery }) } const openMarketView = async () => { + const routeName = + typeof router.hasRoute === 'function' && router.hasRoute('plugins-mcp') + ? 'plugins-mcp' + : 'settings-mcp' await router.push({ - name: 'settings-mcp', + name: routeName, query: { ...route.query, view: 'market' diff --git a/src/renderer/settings/components/RemoteSettings.vue b/src/renderer/settings/components/RemoteSettings.vue index 3579dc5acc..b426fd2175 100644 --- a/src/renderer/settings/components/RemoteSettings.vue +++ b/src/renderer/settings/components/RemoteSettings.vue @@ -1515,8 +1515,9 @@ diff --git a/src/renderer/src/pages/plugins/OfficialPluginDetailPage.vue b/src/renderer/src/pages/plugins/OfficialPluginDetailPage.vue new file mode 100644 index 0000000000..5a9dfbc01c --- /dev/null +++ b/src/renderer/src/pages/plugins/OfficialPluginDetailPage.vue @@ -0,0 +1,241 @@ + + + diff --git a/src/renderer/src/pages/plugins/PluginsCatalogPage.vue b/src/renderer/src/pages/plugins/PluginsCatalogPage.vue new file mode 100644 index 0000000000..f15edaa4a4 --- /dev/null +++ b/src/renderer/src/pages/plugins/PluginsCatalogPage.vue @@ -0,0 +1,437 @@ + + + diff --git a/src/renderer/src/pages/plugins/PluginsHubPage.vue b/src/renderer/src/pages/plugins/PluginsHubPage.vue new file mode 100644 index 0000000000..204f83d2be --- /dev/null +++ b/src/renderer/src/pages/plugins/PluginsHubPage.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/renderer/src/pages/plugins/RemotePluginsPage.vue b/src/renderer/src/pages/plugins/RemotePluginsPage.vue new file mode 100644 index 0000000000..e756b81223 --- /dev/null +++ b/src/renderer/src/pages/plugins/RemotePluginsPage.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/renderer/src/pages/plugins/SkillsPluginsPage.vue b/src/renderer/src/pages/plugins/SkillsPluginsPage.vue new file mode 100644 index 0000000000..7b871bfdc6 --- /dev/null +++ b/src/renderer/src/pages/plugins/SkillsPluginsPage.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/renderer/src/router/index.ts b/src/renderer/src/router/index.ts index f805bcc8c0..338ecdf71b 100644 --- a/src/renderer/src/router/index.ts +++ b/src/renderer/src/router/index.ts @@ -16,6 +16,70 @@ const router = createRouter({ icon: 'lucide:message-square' } }, + { + path: '/plugins', + component: () => import('@/pages/plugins/PluginsHubPage.vue'), + meta: { + titleKey: 'routes.plugins', + icon: 'lucide:puzzle' + }, + children: [ + { + path: '', + name: 'plugins', + component: () => import('@/pages/plugins/PluginsCatalogPage.vue'), + meta: { + titleKey: 'routes.plugins', + icon: 'lucide:puzzle' + } + }, + { + path: 'skills', + name: 'plugins-skills', + component: () => import('@/pages/plugins/SkillsPluginsPage.vue'), + meta: { + titleKey: 'routes.settings-skills', + icon: 'lucide:wand-sparkles' + } + }, + { + path: 'mcp', + name: 'plugins-mcp', + component: () => import('@/pages/plugins/McpPluginsPage.vue'), + meta: { + titleKey: 'routes.settings-mcp', + icon: 'lucide:server' + } + }, + { + path: 'remote', + name: 'plugins-remote', + component: () => import('@/pages/plugins/RemotePluginsPage.vue'), + meta: { + titleKey: 'routes.settings-remote', + icon: 'lucide:smartphone' + } + }, + { + path: 'remote/:channel', + name: 'plugins-remote-detail', + component: () => import('@/pages/plugins/RemotePluginsPage.vue'), + meta: { + titleKey: 'routes.settings-remote', + icon: 'lucide:smartphone' + } + }, + { + path: 'official/:pluginId', + name: 'plugins-official-detail', + component: () => import('@/pages/plugins/OfficialPluginDetailPage.vue'), + meta: { + titleKey: 'routes.plugins', + icon: 'lucide:puzzle' + } + } + ] + }, { path: '/welcome', name: 'welcome', diff --git a/src/renderer/src/stores/ui/spotlight.ts b/src/renderer/src/stores/ui/spotlight.ts index 90da7cd88a..21417565bc 100644 --- a/src/renderer/src/stores/ui/spotlight.ts +++ b/src/renderer/src/stores/ui/spotlight.ts @@ -1,5 +1,6 @@ import { computed, ref, watch } from 'vue' import { defineStore } from 'pinia' +import { useRouter } from 'vue-router' import { useDebounceFn } from '@vueuse/core' import { createSettingsClient } from '@api/SettingsClient' import { createSessionClient } from '@api/SessionClient' @@ -106,7 +107,6 @@ const actionItems: Array<{ { id: 'open-mcp', titleKey: 'routes.settings-mcp', - routeName: 'settings-mcp', icon: 'lucide:server', keywords: ['mcp', 'tools', 'server', '工具'] }, @@ -120,13 +120,13 @@ const actionItems: Array<{ { id: 'open-remote', titleKey: 'routes.settings-remote', - routeName: 'settings-remote', icon: 'lucide:smartphone', keywords: ['remote', 'telegram', 'feishu', '远程'] } ] export const useSpotlightStore = defineStore('spotlight', () => { + const router = useRouter() const sessionClient = createSessionClient() const settingsClient = createSettingsClient() const providerStore = useProviderStore() @@ -251,7 +251,9 @@ export const useSpotlightStore = defineStore('spotlight', () => { .filter((item) => item.score > 0) const buildSettingMatches = (normalizedQuery: string): SpotlightItem[] => - SETTINGS_NAVIGATION_ITEMS.filter((item) => item.routeName !== 'settings-provider') + SETTINGS_NAVIGATION_ITEMS.filter( + (item) => item.routeName !== 'settings-provider' && !item.hiddenInSidebar + ) .map((item) => ({ id: `setting:${item.routeName}`, kind: 'setting' as const, @@ -459,11 +461,15 @@ export const useSpotlightStore = defineStore('spotlight', () => { return case 'open-providers': case 'open-agents': - case 'open-mcp': case 'open-shortcuts': - case 'open-remote': await navigateToSettings(item.routeName) return + case 'open-mcp': + await router.push({ name: 'plugins-mcp' }) + return + case 'open-remote': + await router.push({ name: 'plugins-remote' }) + return default: return } diff --git a/src/shared/settingsNavigation.ts b/src/shared/settingsNavigation.ts index 066b1e9528..ba0f55743d 100644 --- a/src/shared/settingsNavigation.ts +++ b/src/shared/settingsNavigation.ts @@ -166,7 +166,8 @@ export const SETTINGS_NAVIGATION_ITEMS: SettingsNavigationItem[] = [ icon: 'lucide:server', position: 5, groupKey: 'tools', - keywords: ['mcp', 'tools', 'server', 'model context protocol', '工具', '服务'] + keywords: ['mcp', 'tools', 'server', 'model context protocol', '工具', '服务'], + hiddenInSidebar: true }, { routeName: 'settings-remote', @@ -175,7 +176,8 @@ export const SETTINGS_NAVIGATION_ITEMS: SettingsNavigationItem[] = [ icon: 'lucide:smartphone', position: 5.25, groupKey: 'system', - keywords: ['remote', 'telegram', 'feishu', 'control', '远程', '控制'] + keywords: ['remote', 'telegram', 'feishu', 'control', '远程', '控制'], + hiddenInSidebar: true }, { routeName: 'settings-notifications-hooks', @@ -213,7 +215,8 @@ export const SETTINGS_NAVIGATION_ITEMS: SettingsNavigationItem[] = [ position: 5.75, groupKey: 'tools', keywords: ['plugin', 'plugins', 'extension', 'runtime', '插件', '扩展', '运行时'], - supportedTargets: ['darwin/arm64', 'darwin/x64', 'win32/x64', 'win32/arm64', 'linux/x64'] + supportedTargets: ['darwin/arm64', 'darwin/x64', 'win32/x64', 'win32/arm64', 'linux/x64'], + hiddenInSidebar: true }, { routeName: 'settings-skills', @@ -222,7 +225,8 @@ export const SETTINGS_NAVIGATION_ITEMS: SettingsNavigationItem[] = [ icon: 'lucide:wand-sparkles', position: 6, groupKey: 'knowledge', - keywords: ['skill', 'skills', '技能'] + keywords: ['skill', 'skills', '技能'], + hiddenInSidebar: true }, { routeName: 'settings-prompt', diff --git a/test/renderer/components/WindowSideBar.test.ts b/test/renderer/components/WindowSideBar.test.ts index be097b288a..c35a50e004 100644 --- a/test/renderer/components/WindowSideBar.test.ts +++ b/test/renderer/components/WindowSideBar.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { defineComponent, reactive } from 'vue' +import { defineComponent, reactive, ref } from 'vue' import { flushPromises, mount } from '@vue/test-utils' type SetupOptions = { @@ -30,6 +30,7 @@ type SetupOptions = { projectEnvironments?: Array<{ path: string }> archivedProjectEnvironments?: Array<{ path: string }> defaultChatWorkspacePath?: string | null + currentRouteName?: string } const TEST_TIMEOUT_MS = 20000 @@ -225,6 +226,28 @@ const setup = async (options: SetupOptions = {}) => { spotlightStore.open = !spotlightStore.open }) }) + const router = { + currentRoute: ref({ + name: options.currentRouteName ?? 'chat', + query: {}, + params: {} + }), + hasRoute: vi.fn((name: string) => ['chat', 'plugins', 'plugins-remote'].includes(String(name))), + push: vi.fn(async (location: { name?: string }) => { + router.currentRoute.value = { + name: location.name ?? router.currentRoute.value.name, + query: {}, + params: {} + } + }), + replace: vi.fn(async (location: { name?: string }) => { + router.currentRoute.value = { + name: location.name ?? router.currentRoute.value.name, + query: {}, + params: {} + } + }) + } const settingsClient = { openSettings: vi.fn().mockResolvedValue({ windowId: 99 }) } @@ -343,6 +366,9 @@ const setup = async (options: SetupOptions = {}) => { t: (key: string) => key }) })) + vi.doMock('vue-router', () => ({ + useRouter: () => router + })) const passthrough = defineComponent({ template: '
' @@ -461,6 +487,7 @@ const setup = async (options: SetupOptions = {}) => { deviceClient, remoteControlClient, spotlightStore, + router, pageRouterStore, sidebarStore, projectStore @@ -512,11 +539,14 @@ describe('WindowSideBar agent switch', () => { TEST_TIMEOUT_MS ) - it('delegates sidebar new chat clicks to the unified session action', async () => { - const { wrapper, sessionStore } = await setup() + it('routes to chat before delegating sidebar new chat clicks to the unified session action', async () => { + const { wrapper, sessionStore, router } = await setup({ + currentRouteName: 'plugins' + }) await (wrapper.vm as any).handleNewChat() + expect(router.push).toHaveBeenCalledWith({ name: 'chat' }) expect(sessionStore.startNewConversation).toHaveBeenCalledWith({ refresh: true }) }) @@ -642,37 +672,14 @@ describe('WindowSideBar agent switch', () => { ) it( - 'filters pinned and grouped sessions by the sidebar search input', + 'toggles spotlight from the expanded sidebar search command', async () => { - const { wrapper } = await setup({ - pinnedSessions: [ - { - id: 'pinned-1', - title: 'Alpha Session', - status: 'none' - } - ], - groups: [ - { - id: 'common.time.today', - label: 'common.time.today', - labelKey: 'common.time.today', - sessions: [ - { - id: 'group-1', - title: 'Beta Session', - status: 'none' - } - ] - } - ] - }) + const { wrapper, spotlightStore } = await setup() - await wrapper.find('input').setValue('alpha') + await wrapper.get('[data-testid="app-search-command-button"]').trigger('click') await flushPromises() - expect(wrapper.text()).toContain('Alpha Session') - expect(wrapper.text()).not.toContain('Beta Session') + expect(spotlightStore.toggleSpotlight).toHaveBeenCalledTimes(1) }, TEST_TIMEOUT_MS ) @@ -922,9 +929,8 @@ describe('WindowSideBar agent switch', () => { cancelable: true }) - Object.defineProperty(event, 'target', { - value: wrapper.find('input').element - }) + const input = document.createElement('input') + Object.defineProperty(event, 'target', { value: input }) ;(wrapper.vm as any).handleWindowShortcutKeydown(event) await flushPromises() @@ -1081,16 +1087,15 @@ describe('WindowSideBar agent switch', () => { ) it( - 'keeps the sidebar search region interactive outside the drag area', + 'keeps the expanded sidebar command region interactive outside the drag area', async () => { const { wrapper } = await setup() expect(wrapper.get('[data-testid="window-sidebar-session-column"]').classes()).toContain( 'window-no-drag-region' ) - expect(wrapper.get('[data-testid="window-sidebar-search"]').classes()).toContain( - 'window-no-drag-region' - ) + expect(wrapper.get('[data-testid="app-search-command-button"]').exists()).toBe(true) + expect(wrapper.get('[data-testid="app-plugins-button"]').exists()).toBe(true) }, TEST_TIMEOUT_MS ) @@ -1387,24 +1392,16 @@ describe('WindowSideBar agent switch', () => { await wrapper.vm.$nextTick() - expect(wrapper.text()).toContain('chat.sidebar.chats') + expect(wrapper.text()).toContain('chat.sidebar.chatSection') + expect(wrapper.text()).toContain('chat.sidebar.workspace') expect( wrapper.findAll('button[data-group-id]').map((button) => button.attributes('data-group-id')) - ).toEqual(['__pinned__', '/Users/test/Documents/DeepChat', '/work/alpha', '/work/beta']) - expect( - wrapper.get('[data-group-id="/Users/test/Documents/DeepChat"]').attributes('aria-expanded') - ).toBe('false') - expect( - wrapper - .get('[data-group-id="/Users/test/Documents/DeepChat"]') - .find('[data-testid="window-sidebar-group-icon"]') - .attributes('data-icon') - ).toBe('lucide:message-square') + ).toEqual(['__pinned__', '/work/alpha', '/work/beta']) expect(wrapper.findAll('[aria-label="chat.sidebar.projectGroupActions"]')).toHaveLength(2) wrapper .getComponent({ name: 'draggable' }) - .vm.$emit('update:modelValue', [chatGroup, betaGroup, alphaGroup]) + .vm.$emit('update:modelValue', [betaGroup, alphaGroup]) await flushPromises() expect(projectStore.reorderEnvironments).toHaveBeenCalledWith([ @@ -1461,22 +1458,15 @@ describe('WindowSideBar agent switch', () => { await wrapper.vm.$nextTick() - expect(wrapper.text()).toContain('chat.sidebar.chats') + expect(wrapper.text()).toContain('chat.sidebar.chatSection') + expect(wrapper.text()).toContain('chat.sidebar.workspace') expect(wrapper.text()).not.toContain('common.project.none') - expect(wrapper.get('[data-group-id="__no_project__"]').attributes('aria-expanded')).toBe( - 'false' - ) - expect( - wrapper - .get('[data-group-id="__no_project__"]') - .find('[data-testid="window-sidebar-group-icon"]') - .attributes('data-icon') - ).toBe('lucide:message-square') + expect(wrapper.find('[data-group-id="__no_project__"]').exists()).toBe(false) expect(wrapper.findAll('[aria-label="chat.sidebar.projectGroupActions"]')).toHaveLength(2) wrapper .getComponent({ name: 'draggable' }) - .vm.$emit('update:modelValue', [noProjectGroup, betaGroup, alphaGroup]) + .vm.$emit('update:modelValue', [betaGroup, alphaGroup]) await flushPromises() expect(projectStore.reorderEnvironments).toHaveBeenCalledWith(['/work/beta', '/work/alpha']) @@ -1500,7 +1490,7 @@ describe('WindowSideBar agent switch', () => { await wrapper.vm.$nextTick() expect(wrapper.find('[data-group-id="__no_project__"]').exists()).toBe(false) - expect(wrapper.text()).not.toContain('chat.sidebar.chats') + expect(wrapper.text()).not.toContain('chat.sidebar.chatSection') }) it( @@ -1534,7 +1524,7 @@ describe('WindowSideBar agent switch', () => { groups: [alphaGroup, betaGroup] }) - await wrapper.find('input').setValue('shared') + ;(wrapper.vm as any).sessionSearchQuery = 'shared' await flushPromises() const draggable = wrapper.getComponent({ name: 'draggable' }) @@ -1660,8 +1650,8 @@ describe('WindowSideBar agent switch', () => { disabledSetup.wrapper.unmount() }) - it('opens settings and navigates to remote settings when remote button is clicked', async () => { - const { wrapper, settingsClient } = await setup({ + it('routes to Plugins Remote when remote button is clicked', async () => { + const { wrapper, settingsClient, router } = await setup({ remoteStatus: { enabled: true, state: 'running' @@ -1670,10 +1660,8 @@ describe('WindowSideBar agent switch', () => { await wrapper.find('[data-testid=\"remote-control-button\"]').trigger('click') await flushPromises() - expect(settingsClient.openSettings).toHaveBeenCalledTimes(1) - expect(settingsClient.openSettings).toHaveBeenCalledWith({ - routeName: 'settings-remote' - }) + expect(router.push).toHaveBeenCalledWith({ name: 'plugins-remote' }) + expect(settingsClient.openSettings).not.toHaveBeenCalled() wrapper.unmount() }) @@ -1764,38 +1752,4 @@ describe('WindowSideBar viewport auto-fill', () => { }, TEST_TIMEOUT_MS ) - - it( - 'rechecks pagination when local search filters visible sessions below the viewport', - async () => { - const { wrapper, sessionStore } = await setup({ - hasMore: true, - sessions: [{ id: 'session-1' }, { id: 'session-2' }], - groups: [ - { - id: 'common.time.today', - label: 'common.time.today', - labelKey: 'common.time.today', - sessions: [ - { id: 'session-1', title: 'Alpha', status: 'none' }, - { id: 'session-2', title: 'Bravo', status: 'none' } - ] - } - ], - nextPages: [{ items: [{ id: 'session-3' }], hasMore: false }] - }) - setSidebarListSize(wrapper, { scrollHeight: 240, clientHeight: 120 }) - await flushSidebarFillFrame() - expect(sessionStore.loadNextPage).not.toHaveBeenCalled() - - await wrapper.get('[data-testid="window-sidebar-search"] input').setValue('missing older') - setSidebarListSize(wrapper, { scrollHeight: 60, clientHeight: 120 }) - await flushSidebarFillFrame() - - expect(sessionStore.loadNextPage).toHaveBeenCalledTimes(1) - - wrapper.unmount() - }, - TEST_TIMEOUT_MS - ) }) From 811b608bfd9560452094afaf3c1daed618988d72 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Thu, 25 Jun 2026 17:01:14 +0800 Subject: [PATCH 04/14] fix(plugins): remove mcp and skills catalog cards --- docs/features/plugins-hub/plan.md | 7 ++-- docs/features/plugins-hub/spec.md | 6 ++-- docs/features/plugins-hub/tasks.md | 1 + src/renderer/src/i18n/da-DK/settings.json | 4 +-- src/renderer/src/i18n/de-DE/settings.json | 4 +-- src/renderer/src/i18n/en-US/settings.json | 4 +-- src/renderer/src/i18n/es-ES/settings.json | 4 +-- src/renderer/src/i18n/fa-IR/settings.json | 4 +-- src/renderer/src/i18n/fr-FR/settings.json | 4 +-- src/renderer/src/i18n/he-IL/settings.json | 4 +-- src/renderer/src/i18n/id-ID/settings.json | 4 +-- src/renderer/src/i18n/it-IT/settings.json | 4 +-- src/renderer/src/i18n/ja-JP/settings.json | 4 +-- src/renderer/src/i18n/ko-KR/settings.json | 4 +-- src/renderer/src/i18n/ms-MY/settings.json | 4 +-- src/renderer/src/i18n/pl-PL/settings.json | 4 +-- src/renderer/src/i18n/pt-BR/settings.json | 4 +-- src/renderer/src/i18n/ru-RU/settings.json | 4 +-- src/renderer/src/i18n/tr-TR/settings.json | 4 +-- src/renderer/src/i18n/vi-VN/settings.json | 4 +-- src/renderer/src/i18n/zh-CN/settings.json | 4 +-- src/renderer/src/i18n/zh-HK/settings.json | 4 +-- src/renderer/src/i18n/zh-TW/settings.json | 4 +-- .../src/pages/plugins/PluginsCatalogPage.vue | 32 ++----------------- 24 files changed, 29 insertions(+), 97 deletions(-) diff --git a/docs/features/plugins-hub/plan.md b/docs/features/plugins-hub/plan.md index 965d3d5bb9..6fddc356b2 100644 --- a/docs/features/plugins-hub/plan.md +++ b/docs/features/plugins-hub/plan.md @@ -79,12 +79,10 @@ Use a renderer-only union for cards: ```text CatalogItem = official plugin item from plugins.list - MCP server summary from mcp store - Skill metadata from skills store Remote virtual item from remoteControl.listChannels + status ``` -This union only drives list rendering and search filtering. Writes go back to the current owner: +`MCP` and `Skills` are top-level sibling tabs under `/plugins`, not catalog cards. This union only drives plugin catalog rendering and search filtering. Writes go back to the current owner: | User action | Owner route/client | | --- | --- | @@ -123,8 +121,9 @@ Keep this list flexible during implementation; do not split files unless the com Visual baseline: -- Main content starts with top tabs (`Plugins`, `Skills`, optionally `MCP`, `Remote`). +- Main content starts with top tabs (`Plugins`, `Skills`, `MCP`, `Remote`). - Catalog page uses the Codex-like layout: title, subtitle, search, added strip, segmented filters, sectioned list. +- Catalog cards include official plugins and Remote virtual plugins only; MCP and Skills remain reachable through top tabs. - Avoid settings-style full-width form pages for the catalog. Detail routes may use denser settings sections. - Cards are individual repeated items only. Do not put page sections inside floating cards. diff --git a/docs/features/plugins-hub/spec.md b/docs/features/plugins-hub/spec.md index b5c8bbbc93..7ddcc30967 100644 --- a/docs/features/plugins-hub/spec.md +++ b/docs/features/plugins-hub/spec.md @@ -111,12 +111,12 @@ Top-level sections: | Section | User label | Contents | | --- | --- | --- | -| Plugins | Plugins | official plugin packages, added/recommended plugin cards, Remote virtual plugin cards when filtered into plugin catalog | +| Plugins | Plugins | official plugin packages, added/recommended plugin cards, Remote virtual plugin cards | | Skills | Skills | installed skills, install, edit, sync import/export, draft suggestion toggle | | MCP | MCP Servers | user MCP servers, plugin-owned MCP status, MCP market/add flow | | Remote | Remote | virtual plugin cards for Telegram, Feishu/Lark Remote, QQBot, Discord, WeChat iLink | -The visual top tab row can start with `Plugins` and `Skills` as in the Codex screenshot, then add `MCP` and `Remote` if all four areas ship in the same increment. If product wants the screenshot to stay visually lighter, `MCP` and `Remote` can appear as catalog filters/cards inside `Plugins`, but they still need addressable routes. +The visual top tab row uses `Plugins`, `Skills`, `MCP` and `Remote`. `MCP` and `Skills` are sibling tabs, not plugin catalog cards. Remote naming must avoid collision with official plugins: @@ -139,7 +139,7 @@ Remote naming must avoid collision with official plugins: │ │ New Chat │ Plugins │ │ │ Search │ Work with DeepChat across your favorite tools │ │ │ Plugins │ ┌────────────────────────────────────────────┐ │ -│ │ │ │ Search plugins, skills, MCP servers... │ │ +│ │ │ │ Search plugins and remote channels... │ │ │ │ Pinned │ └────────────────────────────────────────────┘ │ │ │ ... │ │ │ │ Chat │ Added Manage │ diff --git a/docs/features/plugins-hub/tasks.md b/docs/features/plugins-hub/tasks.md index 2b68d2a790..5a657d751c 100644 --- a/docs/features/plugins-hub/tasks.md +++ b/docs/features/plugins-hub/tasks.md @@ -13,6 +13,7 @@ - [x] Add `PluginsHubPage.vue` inside `src/renderer/src/pages/plugins/`. - [x] Add top tab navigation for Plugins, Skills, MCP and Remote or the chosen first-increment subset. - [x] Add Codex-like catalog placeholder with title, subtitle, search, added strip and featured sections. +- [x] Keep MCP and Skills as top tabs only, not plugin catalog cards. - [x] Keep `WindowSideBar`, `AppBar`, global overlays, theme and i18n behavior intact. - [x] Add i18n keys for route, page title, subtitle, tabs and search placeholder. - [ ] Add renderer tests proving `/plugins` renders inside the existing app shell. diff --git a/src/renderer/src/i18n/da-DK/settings.json b/src/renderer/src/i18n/da-DK/settings.json index 8ce654568d..f9940e82c5 100644 --- a/src/renderer/src/i18n/da-DK/settings.json +++ b/src/renderer/src/i18n/da-DK/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/de-DE/settings.json b/src/renderer/src/i18n/de-DE/settings.json index 8a6f8fb22e..f0cecf3bf8 100644 --- a/src/renderer/src/i18n/de-DE/settings.json +++ b/src/renderer/src/i18n/de-DE/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/en-US/settings.json b/src/renderer/src/i18n/en-US/settings.json index b3f07ca6f4..19dad928aa 100644 --- a/src/renderer/src/i18n/en-US/settings.json +++ b/src/renderer/src/i18n/en-US/settings.json @@ -2374,7 +2374,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2382,8 +2382,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/es-ES/settings.json b/src/renderer/src/i18n/es-ES/settings.json index 6e9099d412..9f78d23ccf 100644 --- a/src/renderer/src/i18n/es-ES/settings.json +++ b/src/renderer/src/i18n/es-ES/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/fa-IR/settings.json b/src/renderer/src/i18n/fa-IR/settings.json index d4dccb26f3..9548bf5714 100644 --- a/src/renderer/src/i18n/fa-IR/settings.json +++ b/src/renderer/src/i18n/fa-IR/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/fr-FR/settings.json b/src/renderer/src/i18n/fr-FR/settings.json index f24a95b5ba..58980c417c 100644 --- a/src/renderer/src/i18n/fr-FR/settings.json +++ b/src/renderer/src/i18n/fr-FR/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/he-IL/settings.json b/src/renderer/src/i18n/he-IL/settings.json index 17e95087bf..9fcf7c71ba 100644 --- a/src/renderer/src/i18n/he-IL/settings.json +++ b/src/renderer/src/i18n/he-IL/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/id-ID/settings.json b/src/renderer/src/i18n/id-ID/settings.json index 69bf7c653a..6fa582d500 100644 --- a/src/renderer/src/i18n/id-ID/settings.json +++ b/src/renderer/src/i18n/id-ID/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/it-IT/settings.json b/src/renderer/src/i18n/it-IT/settings.json index d3ad316437..4e8e54f9fa 100644 --- a/src/renderer/src/i18n/it-IT/settings.json +++ b/src/renderer/src/i18n/it-IT/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/ja-JP/settings.json b/src/renderer/src/i18n/ja-JP/settings.json index 094e11ee1d..fde61ad49d 100644 --- a/src/renderer/src/i18n/ja-JP/settings.json +++ b/src/renderer/src/i18n/ja-JP/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/ko-KR/settings.json b/src/renderer/src/i18n/ko-KR/settings.json index 97e115da20..7f39b5339c 100644 --- a/src/renderer/src/i18n/ko-KR/settings.json +++ b/src/renderer/src/i18n/ko-KR/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/ms-MY/settings.json b/src/renderer/src/i18n/ms-MY/settings.json index afea526197..dee811fb79 100644 --- a/src/renderer/src/i18n/ms-MY/settings.json +++ b/src/renderer/src/i18n/ms-MY/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/pl-PL/settings.json b/src/renderer/src/i18n/pl-PL/settings.json index cc2d8991c5..6f357bbb56 100644 --- a/src/renderer/src/i18n/pl-PL/settings.json +++ b/src/renderer/src/i18n/pl-PL/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/pt-BR/settings.json b/src/renderer/src/i18n/pt-BR/settings.json index a55558c0b0..0e75e47be1 100644 --- a/src/renderer/src/i18n/pt-BR/settings.json +++ b/src/renderer/src/i18n/pt-BR/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/ru-RU/settings.json b/src/renderer/src/i18n/ru-RU/settings.json index 0b8fad9f82..909c9cec0a 100644 --- a/src/renderer/src/i18n/ru-RU/settings.json +++ b/src/renderer/src/i18n/ru-RU/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2495,8 +2495,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/tr-TR/settings.json b/src/renderer/src/i18n/tr-TR/settings.json index d4e72834bc..117f227df7 100644 --- a/src/renderer/src/i18n/tr-TR/settings.json +++ b/src/renderer/src/i18n/tr-TR/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/vi-VN/settings.json b/src/renderer/src/i18n/vi-VN/settings.json index f0a4347f62..0ba5be299c 100644 --- a/src/renderer/src/i18n/vi-VN/settings.json +++ b/src/renderer/src/i18n/vi-VN/settings.json @@ -2478,7 +2478,7 @@ }, "pluginsHub": { "subtitle": "Work with DeepChat across skills, MCP servers, official plugins, and remote channels.", - "searchPlaceholder": "Search plugins, skills, MCP, and remote channels", + "searchPlaceholder": "Search plugins and remote channels", "added": "Added", "manage": "Manage", "noAdded": "No enabled plugins yet.", @@ -2486,8 +2486,6 @@ "pluginNotFound": "Plugin not found.", "capabilities": "Capabilities", "actionResult": "Action result", - "mcpDescription": "Configure MCP servers and marketplace entries.", - "skillsDescription": "Manage agent skills available to conversations.", "filters": { "official": "By DeepChat", "workspace": "By your workspace", diff --git a/src/renderer/src/i18n/zh-CN/settings.json b/src/renderer/src/i18n/zh-CN/settings.json index 5febd6fd7b..de618bdc3d 100644 --- a/src/renderer/src/i18n/zh-CN/settings.json +++ b/src/renderer/src/i18n/zh-CN/settings.json @@ -2374,7 +2374,7 @@ }, "pluginsHub": { "subtitle": "统一管理 DeepChat 的 Skills、MCP、官方插件和远程渠道。", - "searchPlaceholder": "搜索插件、Skills、MCP 和远程渠道", + "searchPlaceholder": "搜索插件和远程渠道", "added": "已添加", "manage": "管理", "noAdded": "还没有启用任何插件。", @@ -2382,8 +2382,6 @@ "pluginNotFound": "未找到插件。", "capabilities": "能力", "actionResult": "操作结果", - "mcpDescription": "配置 MCP 服务和市场条目。", - "skillsDescription": "管理会话中可用的 Agent Skills。", "filters": { "official": "DeepChat 官方", "workspace": "工作区", diff --git a/src/renderer/src/i18n/zh-HK/settings.json b/src/renderer/src/i18n/zh-HK/settings.json index 336d8cf963..4b62b743aa 100644 --- a/src/renderer/src/i18n/zh-HK/settings.json +++ b/src/renderer/src/i18n/zh-HK/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "統一管理 DeepChat 的 Skills、MCP、官方插件和遠端渠道。", - "searchPlaceholder": "搜尋插件、Skills、MCP 和遠端渠道", + "searchPlaceholder": "搜尋插件和遠端渠道", "added": "已新增", "manage": "管理", "noAdded": "還沒有啟用任何插件。", @@ -2495,8 +2495,6 @@ "pluginNotFound": "找不到插件。", "capabilities": "能力", "actionResult": "操作結果", - "mcpDescription": "設定 MCP 服務和市場項目。", - "skillsDescription": "管理會話中可用的 Agent Skills。", "filters": { "official": "DeepChat 官方", "workspace": "工作區", diff --git a/src/renderer/src/i18n/zh-TW/settings.json b/src/renderer/src/i18n/zh-TW/settings.json index 4c0e9be4ae..e1a8e14a75 100644 --- a/src/renderer/src/i18n/zh-TW/settings.json +++ b/src/renderer/src/i18n/zh-TW/settings.json @@ -2487,7 +2487,7 @@ }, "pluginsHub": { "subtitle": "統一管理 DeepChat 的 Skills、MCP、官方插件和遠端渠道。", - "searchPlaceholder": "搜尋插件、Skills、MCP 和遠端渠道", + "searchPlaceholder": "搜尋插件和遠端渠道", "added": "已新增", "manage": "管理", "noAdded": "還沒有啟用任何插件。", @@ -2495,8 +2495,6 @@ "pluginNotFound": "找不到插件。", "capabilities": "能力", "actionResult": "操作結果", - "mcpDescription": "設定 MCP 服務和市場項目。", - "skillsDescription": "管理會話中可用的 Agent Skills。", "filters": { "official": "DeepChat 官方", "workspace": "工作區", diff --git a/src/renderer/src/pages/plugins/PluginsCatalogPage.vue b/src/renderer/src/pages/plugins/PluginsCatalogPage.vue index f15edaa4a4..00bf51c2a5 100644 --- a/src/renderer/src/pages/plugins/PluginsCatalogPage.vue +++ b/src/renderer/src/pages/plugins/PluginsCatalogPage.vue @@ -149,7 +149,7 @@ type AddedItem = { } type CatalogItem = { id: string - kind: 'official' | 'remote' | 'built-in' + kind: 'official' | 'remote' plugin?: PluginListItem channel?: RemoteChannel title: string @@ -308,26 +308,7 @@ const catalogItems = computed(() => { } }) - const builtInItems: CatalogItem[] = [ - { - id: 'built-in:mcp', - kind: 'built-in', - title: t('routes.settings-mcp'), - description: t('settings.pluginsHub.mcpDescription'), - icon: 'lucide:server', - actionLabel: t('settings.pluginsHub.manage') - }, - { - id: 'built-in:skills', - kind: 'built-in', - title: t('routes.settings-skills'), - description: t('settings.pluginsHub.skillsDescription'), - icon: 'lucide:wand-sparkles', - actionLabel: t('settings.pluginsHub.manage') - } - ] - - return [...builtInItems, ...officialItems, ...remoteItems] + return [...officialItems, ...remoteItems] }) const filteredCatalogItems = computed(() => { @@ -420,15 +401,6 @@ function handleCatalogAction(item: CatalogItem): void { void router.push({ name: 'plugins-remote-detail', params: { channel: item.channel } }) return } - - if (item.id === 'built-in:mcp') { - void router.push({ name: 'plugins-mcp' }) - return - } - - if (item.id === 'built-in:skills') { - void router.push({ name: 'plugins-skills' }) - } } onMounted(() => { From 8312790854a7eb7de7a0d6d52670c11ffcfe35c8 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Thu, 25 Jun 2026 17:02:27 +0800 Subject: [PATCH 05/14] fix(sidebar): localize search command --- docs/features/plugins-hub/tasks.md | 1 + src/renderer/src/i18n/zh-CN/chat.json | 2 +- src/renderer/src/i18n/zh-HK/chat.json | 2 +- src/renderer/src/i18n/zh-TW/chat.json | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/features/plugins-hub/tasks.md b/docs/features/plugins-hub/tasks.md index 5a657d751c..ac470d6e4e 100644 --- a/docs/features/plugins-hub/tasks.md +++ b/docs/features/plugins-hub/tasks.md @@ -91,6 +91,7 @@ - [x] Keep `所有 Agents` title. - [x] Wire `New Chat` row to navigate to `/chat` and start a new conversation. - [x] Wire `Search` row to existing Spotlight behavior. +- [x] Localize the `Search` command label for Chinese locales. - [x] Wire `Plugins` row to `router.push({ name: 'plugins' })`. - [x] Add a blank spacer after the `Plugins` command row. - [x] Render `Pinned` only when pinned sessions exist. diff --git a/src/renderer/src/i18n/zh-CN/chat.json b/src/renderer/src/i18n/zh-CN/chat.json index 354e9405b2..a969984e86 100644 --- a/src/renderer/src/i18n/zh-CN/chat.json +++ b/src/renderer/src/i18n/zh-CN/chat.json @@ -356,7 +356,7 @@ "themeLight": "浅色", "themeDark": "深色", "themeSystem": "跟随系统", - "searchCommand": "Search", + "searchCommand": "搜索", "chatSection": "Chat", "workspace": "工作区", "remoteControlDisabled": "未启用", diff --git a/src/renderer/src/i18n/zh-HK/chat.json b/src/renderer/src/i18n/zh-HK/chat.json index e178824f82..177df51101 100644 --- a/src/renderer/src/i18n/zh-HK/chat.json +++ b/src/renderer/src/i18n/zh-HK/chat.json @@ -345,7 +345,7 @@ "moveProjectGroupUp": "上移", "moveProjectGroupDown": "下移", "moveProjectGroupBottom": "移至最下方", - "searchCommand": "Search", + "searchCommand": "搜尋", "chatSection": "Chat", "workspace": "工作區" }, diff --git a/src/renderer/src/i18n/zh-TW/chat.json b/src/renderer/src/i18n/zh-TW/chat.json index 7931080e15..4f5a12b6ca 100644 --- a/src/renderer/src/i18n/zh-TW/chat.json +++ b/src/renderer/src/i18n/zh-TW/chat.json @@ -345,7 +345,7 @@ "moveProjectGroupUp": "上移", "moveProjectGroupDown": "下移", "moveProjectGroupBottom": "移至最下方", - "searchCommand": "Search", + "searchCommand": "搜尋", "chatSection": "Chat", "workspace": "工作區" }, From 139c6e79475bd4464d6bb211b65c0e4fd40861a4 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Thu, 25 Jun 2026 17:15:25 +0800 Subject: [PATCH 06/14] refactor(plugins): fold remote channels into catalog --- docs/features/plugins-hub/plan.md | 40 ++++------ docs/features/plugins-hub/spec.md | 34 ++++---- docs/features/plugins-hub/tasks.md | 15 ++-- .../presenter/remoteControlPresenter/types.ts | 24 ++++-- .../settings/components/RemoteSettings.vue | 56 ++++++++------ src/renderer/src/components/WindowSideBar.vue | 13 +++- .../plugins/OfficialPluginDetailPage.vue | 42 +++++++++- .../src/pages/plugins/PluginsCatalogPage.vue | 77 +++++++++++-------- .../src/pages/plugins/PluginsHubPage.vue | 9 --- .../src/pages/plugins/RemotePluginsPage.vue | 7 -- src/renderer/src/router/index.ts | 26 +++---- src/renderer/src/stores/ui/spotlight.ts | 2 +- .../remoteBindingStore.test.ts | 46 +++++++++++ .../renderer/components/WindowSideBar.test.ts | 9 ++- 14 files changed, 250 insertions(+), 150 deletions(-) delete mode 100644 src/renderer/src/pages/plugins/RemotePluginsPage.vue diff --git a/docs/features/plugins-hub/plan.md b/docs/features/plugins-hub/plan.md index 6fddc356b2..15f48a312c 100644 --- a/docs/features/plugins-hub/plan.md +++ b/docs/features/plugins-hub/plan.md @@ -30,9 +30,7 @@ Main window /plugins /plugins/skills /plugins/mcp - /plugins/remote - /plugins/official/:pluginId - /plugins/remote/:channel + /plugins/:pluginId ``` Use the existing `src/renderer/src/router/index.ts`. Do not add `src/renderer/plugins`, a new Vite @@ -44,9 +42,7 @@ Route names can be: plugins plugins-skills plugins-mcp -plugins-remote -plugins-official-detail -plugins-remote-detail +plugins-detail ``` External/main-process callers should not know UI component internals. Reuse the existing app-runtime event path where possible. Only add a narrow route if a future main-process caller needs generic main-window navigation: @@ -104,8 +100,6 @@ src/renderer/src/pages/plugins/ ├── OfficialPluginDetailPage.vue ├── McpPluginsPage.vue ├── SkillsPluginsPage.vue -├── RemotePluginsPage.vue -├── RemotePluginDetailPage.vue ├── components/ │ ├── PluginsTopTabs.vue │ ├── PluginCatalogGrid.vue @@ -121,9 +115,10 @@ Keep this list flexible during implementation; do not split files unless the com Visual baseline: -- Main content starts with top tabs (`Plugins`, `Skills`, `MCP`, `Remote`). +- Main content starts with top tabs (`Plugins`, `Skills`, `MCP`). - Catalog page uses the Codex-like layout: title, subtitle, search, added strip, segmented filters, sectioned list. -- Catalog cards include official plugins and Remote virtual plugins only; MCP and Skills remain reachable through top tabs. +- Catalog cards include official plugins and Remote virtual plugins; MCP and Skills remain reachable through top tabs. +- Remote does not have a top tab or product list route. Each channel opens as a virtual plugin detail. - Avoid settings-style full-width form pages for the catalog. Detail routes may use denser settings sections. - Cards are individual repeated items only. Do not put page sections inside floating cards. @@ -136,9 +131,8 @@ Renderer-side navigation: | Sidebar `Plugins` row | `router.push({ name: 'plugins' })` | | Top tab `Skills` | `router.push({ name: 'plugins-skills' })` | | Top tab `MCP` | `router.push({ name: 'plugins-mcp' })` | -| Top tab `Remote` | `router.push({ name: 'plugins-remote' })` | -| Official plugin card/detail | `router.push({ name: 'plugins-official-detail', params: { pluginId } })` | -| Remote channel card/detail | `router.push({ name: 'plugins-remote-detail', params: { channel } })` | +| Plugin card/detail | `router.push({ name: 'plugins-detail', params: { pluginId } })` | +| Remote channel card/detail | `router.push({ name: 'plugins-detail', params: { pluginId: 'remote:' } })` | | `New Chat` row while on `/plugins` | `router.push({ name: 'chat' })`, then start new conversation | Main-process initiated navigation: @@ -153,7 +147,7 @@ Current behavior opens `PluginPresenter.openPluginSettingsWindow(pluginId)`. Target behavior: -- List page opens `/plugins/official/:pluginId`. +- List page opens `/plugins/:pluginId`. - Detail page loads `plugins.get(pluginId)`. - Enable/disable remains in detail and list. - Runtime status and MCP status remain visible. @@ -216,16 +210,16 @@ Compatibility: ## Remote Migration -First increment: reuse `RemoteSettings.vue` inside `/plugins/remote` and `/plugins/remote/:channel`, with route-param synchronization so direct channel links open the matching tab. This keeps the existing credential, pairing, default agent/workdir, bindings and WeChat iLink behavior intact. +First increment: reuse `RemoteSettings.vue` inside `/plugins/:pluginId` for virtual plugin ids such as `remote:telegram`. Single-channel mode hides the old Remote tab strip and renders the existing channel form inside the plugin detail page. This keeps the existing credential, pairing, default agent/workdir, bindings and WeChat iLink behavior intact. Follow-up refactor: extract channel sections from `RemoteSettings.vue` into reusable components. The file is already large, but splitting it before moving the route would increase regression risk and delay the user-visible entry-point cleanup. Refactor only around real channel boundaries: ```text -RemotePluginsPage +PluginsCatalogPage -> virtual cards from listRemoteChannels() -RemotePluginDetailPage(channel) +PluginDetailPage(remote:) -> channel header/status/toggle -> credentials section -> default agent/workdir section @@ -239,7 +233,7 @@ Suggested extracted components: | Component | Scope | | --- | --- | | `RemotePluginCard` | card summary for one channel | -| `RemotePluginDetailPage` | detail shell and save status | +| `PluginDetailPage(remote:)` | detail shell and save status | | `RemoteCredentialsSection` | token/app secret fields; channel-specific props | | `RemoteDefaultsSection` | default agent and default workdir | | `RemotePairingSection` | pair code and principals for pairable channels | @@ -264,7 +258,7 @@ remote: description: descriptor.descriptionKey enabled: status.enabled state: status.state - detailRoute: /plugins/remote/:channel + detailRoute: /plugins/:pluginId ``` ## Settings Removal and Redirects @@ -290,7 +284,7 @@ Mapping: | Old Settings route | Main window target | | --- | --- | | `settings-mcp` | `/plugins/mcp` | -| `settings-remote` | `/plugins/remote` | +| `settings-remote` | `/plugins` | | `settings-plugins` | `/plugins` | | `settings-skills` | `/plugins/skills` | @@ -380,7 +374,7 @@ Update callers: | MCP install deeplink | focus main window, route to `/plugins/mcp`, dispatch MCP install event there | | Settings sidebar old MCP/Skills/Plugins/Remote | no visible entry | | Settings activity old route | focus main window and route to matching `/plugins...` page | -| Sidebar remote status button | route to `/plugins/remote` or a selected channel detail | +| Sidebar remote status button | route to the first enabled `remote:` plugin detail | | Chat input MCP indicator `openSettings` text | route to `/plugins/mcp` | Provider install deeplink stays in Settings Provider. Do not route provider/model setup to Plugins. @@ -439,9 +433,9 @@ Renderer tests should be run for touched components. Full app smoke test should | Risk | Mitigation | | --- | --- | | Settings routes are used by deeplinks/onboarding | Keep hidden compatibility routes and redirect to main `/plugins...` | -| RemoteSettings monolith makes migration risky | Extract per-channel detail only; reuse current route/client behavior | +| RemoteSettings monolith makes migration risky | Reuse it in single-channel mode; extract per-channel sections only when needed | | Plugin settings HTML depends on plugin preload | Do not embed arbitrary HTML in first increment; build first-party native details | -| Feishu official plugin vs Feishu Remote naming collision | Use explicit `Integration` vs `Remote` labels and category badges | +| Feishu official plugin vs Feishu Remote naming collision | Merge Feishu Remote into the Feishu/Lark Integration detail page | | Plugins page becomes another Settings | Catalog page stays Codex-like; detail pages are dense only where settings are unavoidable | | Main route conflicts with chat internal `pageRouter` | Use Vue router for `/plugins`; keep `pageRouter` scoped to ChatTabView | | Search behavior confusion | Sidebar Search row opens existing Spotlight; do not add a new search engine | diff --git a/docs/features/plugins-hub/spec.md b/docs/features/plugins-hub/spec.md index 7ddcc30967..f2e071bc0f 100644 --- a/docs/features/plugins-hub/spec.md +++ b/docs/features/plugins-hub/spec.md @@ -89,9 +89,7 @@ src/renderer/src/router/index.ts ├── tab=plugins or child /plugins ├── /plugins/skills ├── /plugins/mcp - ├── /plugins/remote - ├── /plugins/official/:pluginId - └── /plugins/remote/:channel + └── /plugins/:pluginId ``` Implementation can use nested Vue routes or one `/plugins` route with internal tab state. The URL must be shareable enough for internal navigation and redirects: @@ -101,9 +99,9 @@ Implementation can use nested Vue routes or one `/plugins` route with internal t | Plugins catalog | `/plugins` | | Skills | `/plugins/skills` | | MCP | `/plugins/mcp` | -| Remote list | `/plugins/remote` | -| Official plugin detail | `/plugins/official/:pluginId` | -| Remote channel detail | `/plugins/remote/:channel` | +| Plugin detail | `/plugins/:pluginId` | + +Legacy `/plugins/official/:pluginId`, `/plugins/remote` and `/plugins/remote/:channel` paths may redirect for compatibility, but they are not product routes. ## Proposed Information Architecture @@ -114,17 +112,12 @@ Top-level sections: | Plugins | Plugins | official plugin packages, added/recommended plugin cards, Remote virtual plugin cards | | Skills | Skills | installed skills, install, edit, sync import/export, draft suggestion toggle | | MCP | MCP Servers | user MCP servers, plugin-owned MCP status, MCP market/add flow | -| Remote | Remote | virtual plugin cards for Telegram, Feishu/Lark Remote, QQBot, Discord, WeChat iLink | -The visual top tab row uses `Plugins`, `Skills`, `MCP` and `Remote`. `MCP` and `Skills` are sibling tabs, not plugin catalog cards. +The visual top tab row uses `Plugins`, `Skills` and `MCP`. `Remote` is not a top tab; each remote channel is a virtual plugin card in the catalog. -Remote naming must avoid collision with official plugins: +Remote virtual plugin ids use `remote:`. Feishu/Lark is special: when the official Feishu/Lark Integration plugin is installed, the Feishu/Lark Remote card is merged into that official plugin detail page. -| Existing item | Display name in Plugins | -| --- | --- | -| `com.deepchat.plugins.feishu` official plugin | `Feishu/Lark Integration` | -| `remote:feishu` virtual plugin | `Feishu/Lark Remote` | -| `remote:telegram` virtual plugin | `Telegram Remote` | +Historical remote settings compatibility: if a channel has credentials/accounts from an older configuration and no explicit enabled flag, that virtual plugin starts enabled by default. Explicit `enabled: false` still stays disabled. ## Main Window Plugins UX @@ -134,7 +127,7 @@ Remote naming must avoid collision with official plugins: ┌──────────────────────────────────────────────────────────────────────────────┐ │ AppBar │ ├───────────────┬──────────────────────────────────────────────────────────────┤ -│rail│ expanded sidebar │ [Plugins] [Skills] [MCP] [Remote] + ↻ │ +│rail│ expanded sidebar │ [Plugins] [Skills] [MCP] + ↻ │ │ │ 所有 Agents │ │ │ │ New Chat │ Plugins │ │ │ Search │ Work with DeepChat across your favorite tools │ @@ -157,7 +150,7 @@ Remote naming must avoid collision with official plugins: ┌──────────────────────────────────────────────────────────────────────────────┐ │ AppBar │ ├───────────────┬──────────────────────────────────────────────────────────────┤ -│rail│ expanded sidebar │ [Plugins] [Skills] [MCP] [Remote] │ +│rail│ expanded sidebar │ [Plugins] [Skills] [MCP] │ │ │ 所有 Agents │ ← Back to Plugins │ │ │ New Chat │ Telegram Remote on/off │ │ │ Search │ Status: running · bindings: 2 · last error: none │ @@ -183,7 +176,6 @@ At constrained widths, keep the same app shell and avoid modal navigation: │ AppBar │ ├────┬───────────────────────────────┤ │rail│ [Plugins][Skills][MCP] │ -│ │ [Remote] │ │⚙︎ ├───────────────────────────────┤ │ │ Search │ │ │ │ @@ -305,9 +297,9 @@ Compatibility behavior: - Official plugin list keeps enable/disable/status behavior. - Plugin-owned MCP errors remain visible. -- Opening an official plugin settings/detail uses `/plugins/official/:pluginId`, not Settings and not a per-plugin BrowserWindow. +- Opening a plugin settings/detail uses `/plugins/:pluginId`, not Settings and not a per-plugin BrowserWindow. - CUA detail includes runtime/MCP status, permission checks and permission guide actions. -- Feishu/Lark Integration detail distinguishes MCP/Skill integration from Feishu/Lark Remote control. +- Feishu/Lark Integration detail includes Feishu/Lark Remote configuration instead of showing a separate Feishu/Lark Remote card. - Legacy `settings.open` plugin action is not used as the primary UI path after migration. ### MCP @@ -326,8 +318,8 @@ Compatibility behavior: - Every implemented `RemoteChannelDescriptor` appears as a Remote virtual plugin card. - Each card shows enabled state, runtime state, binding/pairing summary and last error when present. -- Each card opens `/plugins/remote/:channel`. -- Channel settings preserve current behavior: +- Each card opens `/plugins/:pluginId`; remote virtual plugin ids use `remote:`. +- Channel settings render inside the plugin detail page and preserve current behavior: - credentials - enable/disable - default agent diff --git a/docs/features/plugins-hub/tasks.md b/docs/features/plugins-hub/tasks.md index ac470d6e4e..ed82ff4e39 100644 --- a/docs/features/plugins-hub/tasks.md +++ b/docs/features/plugins-hub/tasks.md @@ -11,7 +11,7 @@ - [x] Add `/plugins` route family to the existing main renderer router. - [x] Add `PluginsHubPage.vue` inside `src/renderer/src/pages/plugins/`. -- [x] Add top tab navigation for Plugins, Skills, MCP and Remote or the chosen first-increment subset. +- [x] Add top tab navigation for Plugins, Skills and MCP. - [x] Add Codex-like catalog placeholder with title, subtitle, search, added strip and featured sections. - [x] Keep MCP and Skills as top tabs only, not plugin catalog cards. - [x] Keep `WindowSideBar`, `AppBar`, global overlays, theme and i18n behavior intact. @@ -60,11 +60,11 @@ ## 6. Official Plugins Section - [x] Create official plugin list route from `PluginClient.listPlugins`. -- [x] Add detail route `/plugins/official/:pluginId`. +- [x] Add unified detail route `/plugins/:pluginId`. - [x] Keep enable/disable actions. - [x] Show runtime status, plugin-owned MCP status and last errors. - [ ] Add native CUA detail sections for runtime status, permissions and permission guide actions. -- [ ] Add native Feishu/Lark Integration detail that distinguishes it from Feishu/Lark Remote. +- [x] Merge Feishu/Lark Remote configuration into the Feishu/Lark Integration detail page. - [x] Stop first-party Plugins UI from calling `settings.open`. - [x] Keep `settings.open` only as temporary compatibility fallback. - [ ] Add tests for list/detail action behavior. @@ -73,16 +73,17 @@ - [x] Build remote virtual cards from `remoteControl.listChannels`. - [x] Fetch and display per-channel status. -- [x] Add `/plugins/remote` route. -- [x] Add `/plugins/remote/:channel` detail route. -- [ ] Extract only the needed RemoteSettings channel sections into reusable components. +- [x] Route remote virtual plugin cards through `/plugins/:pluginId` using `remote:` ids. +- [x] Remove the Remote top tab/product list route. +- [x] Reuse `RemoteSettings` in single-channel mode inside plugin detail pages. +- [x] Auto-enable configured legacy channels when the explicit enabled flag is missing. - [x] Preserve credentials fields and password reveal behavior. - [x] Preserve enable/disable save behavior. - [x] Preserve default agent and default workdir behavior. - [x] Preserve pairing flow for Telegram, Feishu/Lark, QQBot and Discord. - [x] Preserve binding/principal removal behavior. - [x] Preserve WeChat iLink login/account controls. -- [x] Route sidebar remote status button to `/plugins/remote` or selected channel detail. +- [x] Route sidebar remote status button to the first enabled remote plugin detail. - [ ] Add tests for card mapping, save, pairing and bindings. ## 8. Main Sidebar Layout diff --git a/src/main/presenter/remoteControlPresenter/types.ts b/src/main/presenter/remoteControlPresenter/types.ts index 6d317a0157..1188939cf5 100644 --- a/src/main/presenter/remoteControlPresenter/types.ts +++ b/src/main/presenter/remoteControlPresenter/types.ts @@ -1127,7 +1127,7 @@ const extractLegacyTelegramConfig = (input: unknown): LegacyTelegramRemoteConfig const record = input as Record if ( - !hasAnyOwn(record, ['allowlist', 'streamMode', 'pollOffset', 'lastFatalError']) && + !hasAnyOwn(record, ['botToken', 'allowlist', 'streamMode', 'pollOffset', 'lastFatalError']) && !hasBindingPrefix(record, 'telegram:') ) { return null @@ -1367,6 +1367,9 @@ const normalizeBindings = ( return bindings } +const resolveRemoteEnabled = (enabled: boolean | undefined, configured: boolean): boolean => + typeof enabled === 'boolean' ? enabled : configured + export const normalizeRemoteControlConfig = (input: unknown): RemoteControlConfig => { const defaults = createDefaultRemoteControlConfig() const parsed = RemoteControlConfigSchema.safeParse(input) @@ -1379,11 +1382,12 @@ export const normalizeRemoteControlConfig = (input: unknown): RemoteControlConfi const qqbot = parsed.data.qqbot ?? extractLegacyQQBotConfig(input) ?? {} const discord = parsed.data.discord ?? extractLegacyDiscordConfig(input) ?? {} const weixinIlink = parsed.data.weixinIlink ?? extractLegacyWeixinIlinkConfig(input) ?? {} + const weixinIlinkAccounts = normalizeWeixinIlinkRuntimeAccounts(weixinIlink.accounts) return { telegram: { botToken: telegram.botToken?.trim() || '', - enabled: Boolean(telegram.enabled), + enabled: resolveRemoteEnabled(telegram.enabled, Boolean(telegram.botToken?.trim())), allowlist: normalizeTelegramUserIds(telegram.allowlist), streamMode: telegram.streamMode === 'final' ? 'final' : defaults.telegram.streamMode, defaultAgentId: telegram.defaultAgentId?.trim() || defaults.telegram.defaultAgentId, @@ -1411,7 +1415,10 @@ export const normalizeRemoteControlConfig = (input: unknown): RemoteControlConfi appSecret: feishu.appSecret?.trim() || '', verificationToken: feishu.verificationToken?.trim() || '', encryptKey: feishu.encryptKey?.trim() || '', - enabled: Boolean(feishu.enabled), + enabled: resolveRemoteEnabled( + feishu.enabled, + Boolean(feishu.appId?.trim() && feishu.appSecret?.trim()) + ), defaultAgentId: feishu.defaultAgentId?.trim() || defaults.feishu.defaultAgentId, defaultWorkdir: feishu.defaultWorkdir?.trim() || '', pairedUserOpenIds: normalizeFeishuOpenIds(feishu.pairedUserOpenIds), @@ -1429,7 +1436,10 @@ export const normalizeRemoteControlConfig = (input: unknown): RemoteControlConfi qqbot: { appId: qqbot.appId?.trim() || '', clientSecret: qqbot.clientSecret?.trim() || '', - enabled: Boolean(qqbot.enabled), + enabled: resolveRemoteEnabled( + qqbot.enabled, + Boolean(qqbot.appId?.trim() && qqbot.clientSecret?.trim()) + ), defaultAgentId: qqbot.defaultAgentId?.trim() || defaults.qqbot.defaultAgentId, defaultWorkdir: qqbot.defaultWorkdir?.trim() || '', pairedUserIds: normalizeQQBotUserIds(qqbot.pairedUserIds), @@ -1447,7 +1457,7 @@ export const normalizeRemoteControlConfig = (input: unknown): RemoteControlConfi }, discord: { botToken: discord.botToken?.trim() || '', - enabled: Boolean(discord.enabled), + enabled: resolveRemoteEnabled(discord.enabled, Boolean(discord.botToken?.trim())), defaultAgentId: discord.defaultAgentId?.trim() || defaults.discord.defaultAgentId, defaultWorkdir: discord.defaultWorkdir?.trim() || '', pairedChannelIds: normalizeDiscordChannelIds(discord.pairedChannelIds), @@ -1464,10 +1474,10 @@ export const normalizeRemoteControlConfig = (input: unknown): RemoteControlConfi bindings: normalizeBindings(discord.bindings, 'discord') }, weixinIlink: { - enabled: Boolean(weixinIlink.enabled), + enabled: resolveRemoteEnabled(weixinIlink.enabled, weixinIlinkAccounts.length > 0), defaultAgentId: weixinIlink.defaultAgentId?.trim() || defaults.weixinIlink.defaultAgentId, defaultWorkdir: weixinIlink.defaultWorkdir?.trim() || '', - accounts: normalizeWeixinIlinkRuntimeAccounts(weixinIlink.accounts) + accounts: weixinIlinkAccounts } } } diff --git a/src/renderer/settings/components/RemoteSettings.vue b/src/renderer/settings/components/RemoteSettings.vue index b426fd2175..6206858f04 100644 --- a/src/renderer/settings/components/RemoteSettings.vue +++ b/src/renderer/settings/components/RemoteSettings.vue @@ -1,5 +1,5 @@