From d1db2c150103cd5998ad5d7d3854d9cadc65c8a0 Mon Sep 17 00:00:00 2001 From: chenchen Date: Thu, 7 May 2026 17:39:02 +0800 Subject: [PATCH] feat(resources): add Resources tab shell aligned with Heicode full-stack vision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per docs/vision-heicode-full-stack-agentic-dev.md and plan.md P1, Heicode is a full-stack agentic dev platform — not a chat-only client. cc-haha needs a "Resources" entry where users bind Git repos / SK / project docs / cloud accounts and grant them to sub-agents. mcp-server team has the P1 9 endpoints live (POST/GET/PUT/DELETE /api/resources, POST/GET/DELETE /api/resource-grants — see Heicode-接口契约文档.md §2-§3). cc-haha has not consumed them yet. This commit ships slice 2 (page shell): - tabStore: new TabType 'resources' + RESOURCES_TAB_ID export - Sidebar: new nav entry between 'scheduled' and 'terminal' (link icon) - ContentRouter: route 'resources' tab to - pages/ResourceBindings.tsx: header + "coming soon" placeholder card - i18n: sidebar.resources + resources.* keys (zh + en) Slice 3 (next): persist mcp-server JWT in provider record so the local Bun server can proxy /api/heicode-resources/* to apimtaiji with a fresh Authorization Bearer header. Refresh logic on the 24h boundary. Slice 4 (next): actual Bindings list + Create/Delete + Grants list + Create/Revoke modals. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/layout/ContentRouter.tsx | 3 ++ .../desktop/src/components/layout/Sidebar.tsx | 11 +++- cc-haha/desktop/src/i18n/locales/en.ts | 8 +++ cc-haha/desktop/src/i18n/locales/zh.ts | 8 +++ .../desktop/src/pages/ResourceBindings.tsx | 53 +++++++++++++++++++ cc-haha/desktop/src/stores/tabStore.ts | 7 +-- 6 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 cc-haha/desktop/src/pages/ResourceBindings.tsx diff --git a/cc-haha/desktop/src/components/layout/ContentRouter.tsx b/cc-haha/desktop/src/components/layout/ContentRouter.tsx index c79176c..6b534c0 100644 --- a/cc-haha/desktop/src/components/layout/ContentRouter.tsx +++ b/cc-haha/desktop/src/components/layout/ContentRouter.tsx @@ -4,6 +4,7 @@ import { EmptySession } from '../../pages/EmptySession' import { ActiveSession } from '../../pages/ActiveSession' import { ScheduledTasks } from '../../pages/ScheduledTasks' import { Settings } from '../../pages/Settings' +import { ResourceBindings } from '../../pages/ResourceBindings' import { TerminalSettings } from '../../pages/TerminalSettings' export function ContentRouter() { @@ -19,6 +20,8 @@ export function ContentRouter() { page = } else if (activeTabType === 'scheduled') { page = + } else if (activeTabType === 'resources') { + page = } else if (activeTabType !== 'terminal') { page = } diff --git a/cc-haha/desktop/src/components/layout/Sidebar.tsx b/cc-haha/desktop/src/components/layout/Sidebar.tsx index 83ee65f..c61a16f 100644 --- a/cc-haha/desktop/src/components/layout/Sidebar.tsx +++ b/cc-haha/desktop/src/components/layout/Sidebar.tsx @@ -5,7 +5,7 @@ import { useTranslation } from '../../i18n' import { ProjectFilter } from './ProjectFilter' import { ConfirmDialog } from '../shared/ConfirmDialog' import type { SessionListItem } from '../../types/session' -import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID } from '../../stores/tabStore' +import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID, RESOURCES_TAB_ID } from '../../stores/tabStore' import { useChatStore } from '../../stores/chatStore' import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore' @@ -195,6 +195,15 @@ export function Sidebar() { > {t('sidebar.scheduled')} + useTabStore.getState().openTab(RESOURCES_TAB_ID, t('sidebar.resources'), 'resources')} + icon={link} + > + {t('sidebar.resources')} + = { // ─── Sidebar ────────────────────────────────────── 'sidebar.newSession': '新建会话', 'sidebar.scheduled': '定时任务', + 'sidebar.resources': '资源绑定', 'sidebar.terminal': '终端', 'sidebar.settings': '设置', 'sidebar.searchPlaceholder': '搜索会话...', @@ -957,6 +958,13 @@ export const zh: Record = { 'serverVerb.Task started': '任务已启动', 'serverVerb.Task in progress': '任务进行中', + // ─── 资源绑定(接 mcp-server P1 9 接口)────────────── + 'resources.title': '资源绑定', + 'resources.subtitle': '绑定 Git 仓库 / SK / 项目文档 / 云账号 / 云资源,并把它们授给子 Agent 使用。', + 'resources.empty.title': '尚未实装', + 'resources.empty.body': '本页对接 Heicode Manager 的 P1 资源模型接口(mcp-server 已上线 9 个端点)。后续将支持创建 / 列表 / 撤销绑定与授权。', + 'resources.empty.comingSoon': '即将上线 — Manager 服务凭据接入与授权 UI 开发中', + // ─── HeiCode 登录 ────────────────────────────────────── 'login.subtitle': '登录以开始使用 Heicode', 'login.footnote': 'Heicode 直接连 TaijiAICloud,API Key 仅保存在你这台机器上。', diff --git a/cc-haha/desktop/src/pages/ResourceBindings.tsx b/cc-haha/desktop/src/pages/ResourceBindings.tsx new file mode 100644 index 0000000..207bec2 --- /dev/null +++ b/cc-haha/desktop/src/pages/ResourceBindings.tsx @@ -0,0 +1,53 @@ +// desktop/src/pages/ResourceBindings.tsx +// +// 资源绑定页面 — 接 mcp-server 的 P1 9 接口(已上线): +// GET /api/resources 列出当前用户所有 ResourceBinding +// POST /api/resources 新建 binding(git/sk/project_doc/cloud_account/cloud_resource) +// GET /api/resources/{id} 单个 binding 详情 +// PUT /api/resources/{id} 更新 binding +// DELETE /api/resources/{id} 软删除(status=revoked) +// POST /api/resource-grants 把 binding 授给 role/agent +// GET /api/resource-grants 列出当前用户所有 grant +// GET /api/resource-grants/{id} 单个 grant +// DELETE /api/resource-grants/{id} 撤销 +// +// 当前为切片 2 的 shell —— 显示标题 + 占位说明,让用户能看到入口。 +// 切片 3+4 接入实际 CRUD。 + +import { useTranslation } from '../i18n' + +export function ResourceBindings() { + const t = useTranslation() + return ( +
+
+
+

+ {t('resources.title')} +

+

+ {t('resources.subtitle')} +

+
+ +
+
+ link +
+

+ {t('resources.empty.title')} +

+

+ {t('resources.empty.body')} +

+

+ {t('resources.empty.comingSoon')} +

+
+
+
+ ) +} diff --git a/cc-haha/desktop/src/stores/tabStore.ts b/cc-haha/desktop/src/stores/tabStore.ts index 0d7a023..4d21e94 100644 --- a/cc-haha/desktop/src/stores/tabStore.ts +++ b/cc-haha/desktop/src/stores/tabStore.ts @@ -5,9 +5,10 @@ const TAB_STORAGE_KEY = 'cc-haha-open-tabs' export const SETTINGS_TAB_ID = '__settings__' export const SCHEDULED_TAB_ID = '__scheduled__' +export const RESOURCES_TAB_ID = '__resources__' export const TERMINAL_TAB_PREFIX = '__terminal__' -export type TabType = 'session' | 'settings' | 'scheduled' | 'terminal' +export type TabType = 'session' | 'settings' | 'scheduled' | 'resources' | 'terminal' export type Tab = { sessionId: string @@ -162,13 +163,13 @@ export const useTabStore = create((set, get) => ({ const validTabs: Tab[] = data.openTabs .filter((t) => { // Special tabs are always valid - if (t.type === 'settings' || t.type === 'scheduled') return true + if (t.type === 'settings' || t.type === 'scheduled' || t.type === 'resources') return true if (t.type === 'terminal') return false // Session tabs must exist on server return existingIds.has(t.sessionId) }) .map((t) => { - if (t.type === 'settings' || t.type === 'scheduled') { + if (t.type === 'settings' || t.type === 'scheduled' || t.type === 'resources') { return { sessionId: t.sessionId, title: t.title, type: t.type, status: 'idle' as const } } return {