From 190af98edadc8e19c7e7b66c3cc61cac0e44f27e Mon Sep 17 00:00:00 2001 From: coso Date: Mon, 17 Aug 2026 23:14:00 +0800 Subject: [PATCH 1/9] feat: add provider management and move web app --- .dockerignore | 4 +- .github/workflows/ci.yml | 6 +- .gitignore | 8 +- CHANGELOG.md | 17 + DESIGN.md | 7 +- Dockerfile.server | 8 +- Makefile | 12 +- README.md | 4 +- VERSION | 2 +- {web => apps/web}/index.html | 2 +- {web => apps/web}/package.json | 2 +- {web => apps/web}/public/favicon.svg | 0 .../public/images/content-work-os-dawn.png | Bin .../public/images/content-work-os-dawn.webp | Bin .../images/content-work-os-pixel-field.webp | Bin 0 -> 7732 bytes .../public/images/content-work-os-studio.webp | Bin .../content-work-os-studio-editorial-v2.png | Bin .../content-work-os-studio-editorial-v2.webp | Bin {web => apps/web}/src/admin/AdminRoute.tsx | 0 {web => apps/web}/src/admin/AdminShell.tsx | 68 +-- .../web}/src/admin/WorkOSConfigPanels.tsx | 70 +-- {web => apps/web}/src/admin/components.tsx | 6 +- .../src/admin/contentCapabilities.test.tsx | 0 {web => apps/web}/src/admin/context.test.ts | 0 {web => apps/web}/src/admin/context.tsx | 2 +- .../web}/src/admin/operationsData.test.ts | 0 {web => apps/web}/src/admin/operationsData.ts | 0 .../web}/src/admin/operationsPages.test.tsx | 68 +-- {web => apps/web}/src/admin/routes.test.ts | 5 +- {web => apps/web}/src/admin/routes.ts | 3 +- .../web}/src/admin/runtimePage.test.tsx | 8 +- .../src/admin/views/AdminOperationsPages.tsx | 254 ++++++++++ .../web/src/admin/views/AdminProviderPage.tsx | 134 ++++++ .../web}/src/admin/views/AdminRuntimePage.tsx | 45 +- apps/web/src/admin/workOSLabels.test.ts | 29 ++ {web => apps/web}/src/admin/workOSLabels.ts | 36 +- {web => apps/web}/src/agentHandoff.test.ts | 2 +- {web => apps/web}/src/agentHandoff.ts | 24 +- {web => apps/web}/src/api.test.ts | 0 {web => apps/web}/src/api.ts | 0 {web => apps/web}/src/components/Brand.tsx | 0 .../src/components/CreateProjectModal.tsx | 6 +- {web => apps/web}/src/components/ui.tsx | 0 .../web}/src/connectBootstrap.test.ts | 10 +- {web => apps/web}/src/connectBootstrap.ts | 30 +- {web => apps/web}/src/consoleRoutes.test.ts | 0 {web => apps/web}/src/consoleRoutes.ts | 0 {web => apps/web}/src/devBootstrap.test.ts | 0 {web => apps/web}/src/devBootstrap.ts | 0 {web => apps/web}/src/docs/DocsRoutes.tsx | 0 {web => apps/web}/src/docs/DocsViews.tsx | 14 +- {web => apps/web}/src/docs/docs.css | 0 {web => apps/web}/src/docs/docs.test.ts | 0 {web => apps/web}/src/docs/docs.ts | 0 {web => apps/web}/src/main.tsx | 0 apps/web/src/marketing/MarketingHome.test.tsx | 52 ++ .../web}/src/marketing/MarketingHome.tsx | 142 ++++-- .../src/marketing/MarketingProductVisuals.tsx | 193 ++++++++ {web => apps/web}/src/marketing/marketing.css | 225 +++++++++ apps/web/src/providerApi.ts | 46 ++ {web => apps/web}/src/router.tsx | 1 + .../web}/src/studio/CustomerStudioShell.tsx | 8 +- .../web}/src/studio/StudioContext.tsx | 0 .../web}/src/studio/StudioKnowledgePage.tsx | 0 {web => apps/web}/src/studio/StudioPages.tsx | 108 ++--- apps/web/src/studio/StudioProviderStatus.tsx | 33 ++ .../web}/src/studio/StudioTeamPage.tsx | 0 .../web}/src/studio/apiBoundary.test.ts | 0 .../src/studio/assets/AssetDetailPages.tsx | 14 +- .../web}/src/studio/assets/AssetViewers.tsx | 0 .../src/studio/assets/assetFormat.test.ts | 0 .../web}/src/studio/assets/assetFormat.ts | 0 {web => apps/web}/src/studio/assets/index.ts | 0 .../src/studio/knowledge-navigation.test.ts | 0 .../knowledge/GovernedKnowledgePage.tsx | 109 +++++ .../web}/src/studio/knowledge/knowledgeApi.ts | 0 {web => apps/web}/src/studio/routes.test.ts | 0 {web => apps/web}/src/studio/studio.css | 4 + {web => apps/web}/src/studio/studioApi.ts | 0 .../web}/src/studio/studioData.test.ts | 0 {web => apps/web}/src/studio/studioData.ts | 0 {web => apps/web}/src/studio/studioTypes.ts | 0 {web => apps/web}/src/styles.css | 10 + {web => apps/web}/src/styles/workSurface.css | 0 {web => apps/web}/src/types.ts | 2 +- {web => apps/web}/src/uiLabels.test.ts | 20 +- {web => apps/web}/src/uiLabels.ts | 110 ++--- {web => apps/web}/src/views/PublicRoutes.tsx | 0 {web => apps/web}/src/views/PublicViews.tsx | 6 +- {web => apps/web}/src/views/ReviewContent.tsx | 0 {web => apps/web}/src/views/TeamView.tsx | 12 +- .../web}/src/views/auth/AuthLayout.tsx | 0 .../web}/src/views/auth/AuthRoutes.tsx | 0 .../web}/src/views/auth/LoginView.tsx | 0 .../web}/src/views/auth/RegisterView.tsx | 4 +- {web => apps/web}/src/views/auth/fields.tsx | 0 .../web}/src/views/auth/returnPath.test.ts | 0 .../web}/src/views/auth/returnPath.ts | 0 .../web}/src/views/auth/validate.test.ts | 0 {web => apps/web}/src/views/auth/validate.ts | 0 .../web}/src/views/reviewSubject.test.tsx | 0 {web => apps/web}/src/views/reviewSubject.ts | 0 {web => apps/web}/tsconfig.app.json | 0 {web => apps/web}/tsconfig.json | 0 {web => apps/web}/tsconfig.node.json | 0 {web => apps/web}/vite.config.ts | 0 cmd/contentcloud-server/main.go | 2 +- docs/content/README.md | 42 +- docs/content/catalog.json | 24 +- docs/content/clients/claude-code.md | 36 +- docs/content/clients/codex.md | 44 +- docs/content/concepts/governed-workflow.md | 85 ++-- docs/content/content-types/marketing-video.md | 46 +- docs/content/content-types/wechat-article.md | 26 +- docs/content/contentdocs.go | 16 +- docs/content/getting-started.md | 50 +- docs/content/guides/marketing-video/codex.md | 104 ++-- docs/content/guides/wechat-article/codex.md | 50 +- .../troubleshooting/workspace-and-handoff.md | 48 +- docs/foundation/01-product-planes.md | 46 +- docs/foundation/03-system-and-runtime.md | 11 + docs/foundation/04-code-organization.md | 72 +-- docs/foundation/05-contracts-and-extension.md | 10 +- docs/foundation/07-migration-and-delivery.md | 450 ++++++------------ docs/foundation/10-current-state-inventory.md | 122 ++--- docs/foundation/README.md | 30 +- ...-asset-library-information-architecture.md | 13 +- ...ADR-0013-customer-result-asset-boundary.md | 4 +- .../ADR-0015-operations-control-plane.md | 19 +- ...desktop-surface-and-repository-topology.md | 172 +++++++ .../ADR-0019-local-cloud-sync-authority.md | 123 +++++ docs/foundation/decisions/README.md | 4 +- docs/infra/01-capability-map.md | 14 +- docs/infra/03-delivery-roadmap.md | 16 +- .../04-architecture-and-flow-diagrams.md | 46 +- .../05-content-production-scenario-matrix.md | 4 +- docs/infra/README.md | 13 +- docs/plugin/README.md | 6 +- docs/product/00-product-narrative.md | 44 +- docs/product/README.md | 1 + .../02-architecture-and-technology.md | 213 +++++++++ .../03-sync-review-upload.md | 214 +++++++++ .../04-delivery-plan.md | 121 +++++ .../product/content-work-os-desktop/README.md | 118 +++++ .../01-product-requirements.md | 14 +- .../02-domain-and-projection.md | 4 +- .../03-delivery-plan.md | 14 +- docs/product/creative-asset-library/README.md | 16 +- .../01-product-planes-and-architecture.md | 16 +- .../04-execution-client-connection.md | 6 +- .../05-local-workbench-browser.md | 10 +- .../customer-creation-studio/README.md | 17 +- .../01-information-architecture.md | 8 +- .../02-domain-model-and-contracts.md | 6 +- .../03-workflows-and-migration.md | 14 +- .../04-page-blueprints-and-language.md | 4 +- .../operations-control-plane/README.md | 9 +- .../01-product-positioning-and-job-model.md | 8 +- .../roadmap/v8/02-host-capability-evidence.md | 2 +- .../v8/05-state-context-and-durability.md | 4 + ...xecution-planes-security-and-governance.md | 2 + ...eb-console-observability-and-operations.md | 4 +- .../v8/08-migration-testing-and-acceptance.md | 32 +- docs/roadmap/v8/09-runtime-infra-v2.md | 12 +- .../v8/10-runtime-operations-runbook.md | 2 +- .../v8/11-seedance25-provider-integration.md | 4 +- docs/roadmap/v8/README.md | 7 +- docs/tech/README.md | 20 + ...cloud-agentic-job-runtime-architecture.mmd | 102 ++++ ...cloud-agentic-job-runtime-architecture.png | Bin 0 -> 427985 bytes ...cloud-agentic-job-runtime-architecture.svg | 1 + .../contentcloud-desktop-architecture.mmd | 63 +++ internal/app/provider_management.go | 12 +- internal/app/provider_management_test.go | 8 + internal/cli/bootstrap_commands_test.go | 12 +- internal/cli/plugin_host.go | 2 +- internal/cli/root.go | 2 +- internal/cli/workspace_commands_test.go | 10 +- internal/httpapi/bootstrap.md | 14 +- internal/httpapi/bootstrap_test.go | 2 +- internal/httpapi/codex.go | 2 +- .../httpapi/provider_management_handlers.go | 28 +- .../provider_management_handlers_test.go | 28 ++ internal/httpapi/server.go | 2 +- internal/workbench/ui/mcp-app.html | 2 +- package.json | 10 +- packages/contentcloud/package.json | 4 +- pnpm-lock.yaml | 6 +- pnpm-workspace.yaml | 2 +- scripts/append-refinements.mjs | 2 +- scripts/check-architecture.mjs | 10 +- scripts/check-content-governance.mjs | 8 +- scripts/check-v3-legacy.mjs | 8 +- scripts/dev.sh | 6 +- scripts/recolor.mjs | 6 +- scripts/reradius.mjs | 4 +- scripts/retype.mjs | 4 +- scripts/validate-plugin-release.mjs | 35 +- web/src/admin/views/AdminOperationsPages.tsx | 238 --------- web/src/admin/workOSLabels.test.ts | 29 -- web/src/marketing/MarketingHome.test.tsx | 40 -- web/src/marketing/MarketingProductVisuals.tsx | 225 --------- .../knowledge/GovernedKnowledgePage.tsx | 109 ----- 203 files changed, 3727 insertions(+), 1988 deletions(-) rename {web => apps/web}/index.html (72%) rename {web => apps/web}/package.json (96%) rename {web => apps/web}/public/favicon.svg (100%) rename {web => apps/web}/public/images/content-work-os-dawn.png (100%) rename {web => apps/web}/public/images/content-work-os-dawn.webp (100%) create mode 100644 apps/web/public/images/content-work-os-pixel-field.webp rename {web => apps/web}/public/images/content-work-os-studio.webp (100%) rename {web => apps/web}/public/images/homepage-candidates/content-work-os-studio-editorial-v2.png (100%) rename {web => apps/web}/public/images/homepage-candidates/content-work-os-studio-editorial-v2.webp (100%) rename {web => apps/web}/src/admin/AdminRoute.tsx (100%) rename {web => apps/web}/src/admin/AdminShell.tsx (59%) rename {web => apps/web}/src/admin/WorkOSConfigPanels.tsx (77%) rename {web => apps/web}/src/admin/components.tsx (94%) rename {web => apps/web}/src/admin/contentCapabilities.test.tsx (100%) rename {web => apps/web}/src/admin/context.test.ts (100%) rename {web => apps/web}/src/admin/context.tsx (99%) rename {web => apps/web}/src/admin/operationsData.test.ts (100%) rename {web => apps/web}/src/admin/operationsData.ts (100%) rename {web => apps/web}/src/admin/operationsPages.test.tsx (88%) rename {web => apps/web}/src/admin/routes.test.ts (96%) rename {web => apps/web}/src/admin/routes.ts (93%) rename {web => apps/web}/src/admin/runtimePage.test.tsx (92%) create mode 100644 apps/web/src/admin/views/AdminOperationsPages.tsx create mode 100644 apps/web/src/admin/views/AdminProviderPage.tsx rename {web => apps/web}/src/admin/views/AdminRuntimePage.tsx (58%) create mode 100644 apps/web/src/admin/workOSLabels.test.ts rename {web => apps/web}/src/admin/workOSLabels.ts (52%) rename {web => apps/web}/src/agentHandoff.test.ts (99%) rename {web => apps/web}/src/agentHandoff.ts (91%) rename {web => apps/web}/src/api.test.ts (100%) rename {web => apps/web}/src/api.ts (100%) rename {web => apps/web}/src/components/Brand.tsx (100%) rename {web => apps/web}/src/components/CreateProjectModal.tsx (95%) rename {web => apps/web}/src/components/ui.tsx (100%) rename {web => apps/web}/src/connectBootstrap.test.ts (90%) rename {web => apps/web}/src/connectBootstrap.ts (66%) rename {web => apps/web}/src/consoleRoutes.test.ts (100%) rename {web => apps/web}/src/consoleRoutes.ts (100%) rename {web => apps/web}/src/devBootstrap.test.ts (100%) rename {web => apps/web}/src/devBootstrap.ts (100%) rename {web => apps/web}/src/docs/DocsRoutes.tsx (100%) rename {web => apps/web}/src/docs/DocsViews.tsx (75%) rename {web => apps/web}/src/docs/docs.css (100%) rename {web => apps/web}/src/docs/docs.test.ts (100%) rename {web => apps/web}/src/docs/docs.ts (100%) rename {web => apps/web}/src/main.tsx (100%) create mode 100644 apps/web/src/marketing/MarketingHome.test.tsx rename {web => apps/web}/src/marketing/MarketingHome.tsx (55%) create mode 100644 apps/web/src/marketing/MarketingProductVisuals.tsx rename {web => apps/web}/src/marketing/marketing.css (70%) create mode 100644 apps/web/src/providerApi.ts rename {web => apps/web}/src/router.tsx (98%) rename {web => apps/web}/src/studio/CustomerStudioShell.tsx (76%) rename {web => apps/web}/src/studio/StudioContext.tsx (100%) rename {web => apps/web}/src/studio/StudioKnowledgePage.tsx (100%) rename {web => apps/web}/src/studio/StudioPages.tsx (73%) create mode 100644 apps/web/src/studio/StudioProviderStatus.tsx rename {web => apps/web}/src/studio/StudioTeamPage.tsx (100%) rename {web => apps/web}/src/studio/apiBoundary.test.ts (100%) rename {web => apps/web}/src/studio/assets/AssetDetailPages.tsx (95%) rename {web => apps/web}/src/studio/assets/AssetViewers.tsx (100%) rename {web => apps/web}/src/studio/assets/assetFormat.test.ts (100%) rename {web => apps/web}/src/studio/assets/assetFormat.ts (100%) rename {web => apps/web}/src/studio/assets/index.ts (100%) rename {web => apps/web}/src/studio/knowledge-navigation.test.ts (100%) create mode 100644 apps/web/src/studio/knowledge/GovernedKnowledgePage.tsx rename {web => apps/web}/src/studio/knowledge/knowledgeApi.ts (100%) rename {web => apps/web}/src/studio/routes.test.ts (100%) rename {web => apps/web}/src/studio/studio.css (96%) rename {web => apps/web}/src/studio/studioApi.ts (100%) rename {web => apps/web}/src/studio/studioData.test.ts (100%) rename {web => apps/web}/src/studio/studioData.ts (100%) rename {web => apps/web}/src/studio/studioTypes.ts (100%) rename {web => apps/web}/src/styles.css (95%) rename {web => apps/web}/src/styles/workSurface.css (100%) rename {web => apps/web}/src/types.ts (99%) rename {web => apps/web}/src/uiLabels.test.ts (83%) rename {web => apps/web}/src/uiLabels.ts (74%) rename {web => apps/web}/src/views/PublicRoutes.tsx (100%) rename {web => apps/web}/src/views/PublicViews.tsx (64%) rename {web => apps/web}/src/views/ReviewContent.tsx (100%) rename {web => apps/web}/src/views/TeamView.tsx (91%) rename {web => apps/web}/src/views/auth/AuthLayout.tsx (100%) rename {web => apps/web}/src/views/auth/AuthRoutes.tsx (100%) rename {web => apps/web}/src/views/auth/LoginView.tsx (100%) rename {web => apps/web}/src/views/auth/RegisterView.tsx (90%) rename {web => apps/web}/src/views/auth/fields.tsx (100%) rename {web => apps/web}/src/views/auth/returnPath.test.ts (100%) rename {web => apps/web}/src/views/auth/returnPath.ts (100%) rename {web => apps/web}/src/views/auth/validate.test.ts (100%) rename {web => apps/web}/src/views/auth/validate.ts (100%) rename {web => apps/web}/src/views/reviewSubject.test.tsx (100%) rename {web => apps/web}/src/views/reviewSubject.ts (100%) rename {web => apps/web}/tsconfig.app.json (100%) rename {web => apps/web}/tsconfig.json (100%) rename {web => apps/web}/tsconfig.node.json (100%) rename {web => apps/web}/vite.config.ts (100%) create mode 100644 docs/foundation/decisions/ADR-0018-desktop-surface-and-repository-topology.md create mode 100644 docs/foundation/decisions/ADR-0019-local-cloud-sync-authority.md create mode 100644 docs/product/content-work-os-desktop/02-architecture-and-technology.md create mode 100644 docs/product/content-work-os-desktop/03-sync-review-upload.md create mode 100644 docs/product/content-work-os-desktop/04-delivery-plan.md create mode 100644 docs/product/content-work-os-desktop/README.md create mode 100644 docs/tech/README.md create mode 100644 docs/tech/contentcloud-agentic-job-runtime-architecture.mmd create mode 100644 docs/tech/contentcloud-agentic-job-runtime-architecture.png create mode 100644 docs/tech/contentcloud-agentic-job-runtime-architecture.svg create mode 100644 docs/tech/contentcloud-desktop-architecture.mmd create mode 100644 internal/httpapi/provider_management_handlers_test.go delete mode 100644 web/src/admin/views/AdminOperationsPages.tsx delete mode 100644 web/src/admin/workOSLabels.test.ts delete mode 100644 web/src/marketing/MarketingHome.test.tsx delete mode 100644 web/src/marketing/MarketingProductVisuals.tsx delete mode 100644 web/src/studio/knowledge/GovernedKnowledgePage.tsx diff --git a/.dockerignore b/.dockerignore index 7c1057c..9decaca 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,6 @@ bin coverage node_modules var -web/dist -web/node_modules +apps/web/dist +apps/web/node_modules *.log diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ada8cd2..fe21b3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - run: pnpm evaluate:plugin - run: pnpm test:plugin-signing - run: pnpm check:plugin - - run: pnpm --dir web typecheck - - run: pnpm --dir web test - - run: pnpm --dir web build + - run: pnpm --dir apps/web typecheck + - run: pnpm --dir apps/web test + - run: pnpm --dir apps/web build - run: node --check packages/contentcloud/bin/contentcloud.js diff --git a/.gitignore b/.gitignore index 6195947..ebad509 100644 --- a/.gitignore +++ b/.gitignore @@ -5,13 +5,13 @@ dist/ coverage/ node_modules/ -web/dist/ -web/node_modules/ +apps/*/dist/ +apps/*/node_modules/ var/ .contentcloud/ *.tsbuildinfo -web/vite.config.js -web/vite.config.d.ts +apps/web/vite.config.js +apps/web/vite.config.d.ts *.log .playwright-mcp/ register-create.png diff --git a/CHANGELOG.md b/CHANGELOG.md index b968700..19c380f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ ContentCloud 的重要变更记录在此文件中。 +## [0.28.0] - 2026-08-17 + +### Added + +- 增加 Provider Profile、Provider Binding 和管理员 Provider 管理页面,支持按租户配置出口策略、预算和服务商路由。 +- Provider 凭据引用只允许写入,不在 API 响应中回显,并增加对应 HTTP/应用层回归测试。 + +### Changed + +- Content Work OS 前端迁移到 `apps/web`,同步 Workspace、Docker、CI、治理脚本和开发文档路径。 +- 更新工作台、Studio、运营页和官网视觉层级,统一 Provider 状态与白标工作流文案。 +- 应用发布版本与独立签名的 Scene Plugin 版本解耦;应用升级到 `0.28.0`,生产插件继续固定在已验证的 `0.27.0`。 + +### Security + +- Provider Binding 查询不再泄漏 `credential_ref`,仅返回是否已配置凭据。 + ## [0.27.0] - 2026-08-15 ### Added diff --git a/DESIGN.md b/DESIGN.md index 3985743..6734804 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -9,7 +9,7 @@ ## Visual Baseline -- `web/src/marketing/marketing.css` 中当前首页是品牌视觉基准。首页以白色为主表面,用不对称的品牌叙事和真实任务工作面组成首屏;自然光创作空间只承载右侧场景氛围,不能压过产品品牌、任务状态和客户下一步。 +- `apps/web/src/marketing/marketing.css` 中当前首页是品牌视觉基准。首页首屏使用浅青绿像素场景、居中品牌叙事和真实任务摘要组成单一构图;产品品牌、任务状态和客户下一步保持主导。 - 工作台、平台后台、登录注册、文档和公开页面复用首页的中性灰白、清亮蓝焦点、Logo、字体与图标语言,但不复制营销页的大标题或内容编排。 - 工作面以白色、浅灰背景、中性边框和严格网格承载高频操作;近黑色用于主要命令,清亮蓝只用于品牌、选中态、链接和焦点,侧栏保持白色,不得形成大面积蓝色区域。 - 颜色用于区分品牌交互、对象类型和系统状态。品牌主题以白色为主、明亮蓝为辅,不使用粉色主题,也不把整个界面染成单一蓝色。 @@ -99,8 +99,8 @@ ### Public Website -- 首屏以白色为主表面,用品牌标题与真实任务工作面形成单一构图;继续使用现有 `/images/content-work-os-studio.webp` 自然光场景,不替换图片、不生成新背景,也不增加蓝色滤镜。 -- Hero 保持品牌优先、单一 CTA 组和下一段露出;任务画布承载固定输入、人工确认、版本状态和唯一下一步,不做漂浮节点或纯装饰流程图。 +- 首屏使用独立的浅青绿像素场景 `/images/content-work-os-pixel-field.webp`,采用居中品牌标题、双 CTA、四周白色像素片段和底部任务摘要露出的构图;登录注册继续使用 `/images/content-work-os-studio.webp` 自然光场景。 +- Hero 保持品牌优先,像素片段只承担轻量场景氛围;下一段任务摘要承载固定输入、人工确认、版本状态和唯一下一步,不使用与业务无关的漂浮节点或装饰流程图。 - 首页紧接客户创作流程、结果资产目录、资产复用闭环、三执行平面和能力状态表,视觉内容必须来自产品文档与当前代码事实。 - 首页内容状态来自 Registry 和文档目录,不静态承诺未实现能力。 @@ -154,3 +154,4 @@ | 2026-08-11 | 官网能力叙事以 Infra 文档和公开目录为事实源 | 明确区分本地可用、服务端可用、执行框架已接通与外部依赖,避免把底层实现写成真实账号或自动发布已接通。 | | 2026-08-12 | 对标参考站的中性工作室层级,蓝色只保留为焦点 | 用户否定大面积皇家蓝和高饱和天蓝;采用白色主表面、浅灰工作区、近黑命令、清亮蓝焦点,并将 Studio 调整为 `112px` 紧凑导航、约 `404px` 上下文栏和开放工作面。 | | 2026-08-12 | 采用参考站的白色留白、胶囊控件和柔和浮层,品牌色改为冰川蓝 | 用户再次否定普通企业蓝观感;颜色只替代参考站的粉色强调,不把页面整体染蓝,官网保留原自然光背景图,登录、Studio 与后台共享同一套冷白表面和墨色命令。 | +| 2026-08-17 | 首页改为 Catpaw 同类居中绿色像素场景 | 复用透明导航、居中 Hero、白色像素片段、双 CTA 和下一段露出的构图与动效机制;不复制对方品牌素材、Logo 或文案,继续使用 Content Work OS 的真实任务内容。 | diff --git a/Dockerfile.server b/Dockerfile.server index b24cdb5..c8d26e6 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -2,11 +2,11 @@ FROM node:22-bookworm-slim AS web-build WORKDIR /src RUN corepack enable COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY web/package.json web/package.json +COPY apps/web/package.json apps/web/package.json COPY packages/contentcloud/package.json packages/contentcloud/package.json RUN pnpm install --frozen-lockfile -COPY web web -RUN pnpm --dir web build +COPY apps/web apps/web +RUN pnpm --dir apps/web build FROM golang:1.24-bookworm AS go-build WORKDIR /src @@ -24,7 +24,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates RUN useradd --system --uid 10001 --create-home contentcloud WORKDIR /app COPY --from=go-build /out/contentcloud-server /usr/local/bin/contentcloud-server -COPY --from=web-build /src/web/dist /app/web/dist +COPY --from=web-build /src/apps/web/dist /app/web/dist USER contentcloud ENV CONTENTCLOUD_ADDR=:8080 CONTENTCLOUD_WEB_DIST=/app/web/dist CONTENTCLOUD_DEV_MODE=0 EXPOSE 8080 diff --git a/Makefile b/Makefile index eae006f..cbb136c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: dev preview server web worker cli build test check check-plugin install-cli migrate-up CONTENTCLOUD_ADDR ?= :8080 -CONTENTCLOUD_WEB_DIST ?= web/dist +CONTENTCLOUD_WEB_DIST ?= apps/web/dist dev: ./scripts/dev.sh @@ -13,7 +13,7 @@ server: go run ./cmd/contentcloud-server web: - pnpm --dir web dev + pnpm --dir apps/web dev worker: go run ./cmd/contentcloud-worker @@ -28,11 +28,11 @@ build: build-web go build -o bin/contentcloud ./cmd/contentcloud build-web: - pnpm --dir web build + pnpm --dir apps/web build test: go test ./... - pnpm --dir web test + pnpm --dir apps/web test check: go fmt ./... @@ -43,8 +43,8 @@ check: pnpm governance:content pnpm test:plugin-signing pnpm check:plugin - pnpm --dir web typecheck - pnpm --dir web build + pnpm --dir apps/web typecheck + pnpm --dir apps/web build check-plugin: pnpm evaluate:plugin diff --git a/README.md b/README.md index 768c991..dcde367 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ contracts/ OpenAPI 3.1 与 JSON Schema migrations/ PostgreSQL schema、RLS 与 runtime role skills/ 随 CLI 内嵌的本地 Agent Skill packages/contentcloud/ 零依赖 npm 校验安装器 -web/ React + TypeScript 工作台与客户审批页 +apps/web/ React + TypeScript 工作台与客户审批页 docs/foundation/ 平台基线、分层规范、迁移与工程门禁 docs/product/ 客户产品需求与平台叙事 docs/roadmap/v8/ Agentic Job Runtime 专项路线图 @@ -150,7 +150,7 @@ project: "品牌 / 单品" ```bash make check go test -race ./... -pnpm --dir web test +pnpm --dir apps/web test pnpm check:plugin ``` diff --git a/VERSION b/VERSION index 1b58cc1..697f087 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.27.0 +0.28.0 diff --git a/web/index.html b/apps/web/index.html similarity index 72% rename from web/index.html rename to apps/web/index.html index 0b4664b..8b43ba2 100644 --- a/web/index.html +++ b/apps/web/index.html @@ -5,7 +5,7 @@ - + Content Work OS diff --git a/web/package.json b/apps/web/package.json similarity index 96% rename from web/package.json rename to apps/web/package.json index 96cfff5..6c50bb0 100644 --- a/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@limecloud/contentcloud-web", "private": true, - "version": "0.27.0", + "version": "0.28.0", "type": "module", "scripts": { "dev": "vite --config vite.config.ts --host 0.0.0.0", diff --git a/web/public/favicon.svg b/apps/web/public/favicon.svg similarity index 100% rename from web/public/favicon.svg rename to apps/web/public/favicon.svg diff --git a/web/public/images/content-work-os-dawn.png b/apps/web/public/images/content-work-os-dawn.png similarity index 100% rename from web/public/images/content-work-os-dawn.png rename to apps/web/public/images/content-work-os-dawn.png diff --git a/web/public/images/content-work-os-dawn.webp b/apps/web/public/images/content-work-os-dawn.webp similarity index 100% rename from web/public/images/content-work-os-dawn.webp rename to apps/web/public/images/content-work-os-dawn.webp diff --git a/apps/web/public/images/content-work-os-pixel-field.webp b/apps/web/public/images/content-work-os-pixel-field.webp new file mode 100644 index 0000000000000000000000000000000000000000..5c8116f64f8f47194275fd9dac5e9b993f88f807 GIT binary patch literal 7732 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 [adminPath('dashboard')]:'运营总览', - [adminPath('products')]:'创作产品', - [adminPath('releases')]:'发布记录', - [adminPath('customers')]:'客户开通', - [adminPath('capabilities')]:'能力目录', - [adminPath('skills')]:'技能包', - [adminPath('executors')]:'执行端', - [adminPath('jobs')]:'任务记录', - [adminPath('alerts')]:'异常处理', - [adminPath('tenants')]:'客户管理', - [adminPath('audit')]:'操作审计', - [adminPath('costs')]:'任务用量' + [adminPath('products')]:'创作流程', + [adminPath('releases')]:'发布版本', + [adminPath('customers')]:'客户设置', + [adminPath('capabilities')]:'功能清单', + [adminPath('skills')]:'自动化工具', + [adminPath('executors')]:'连接的电脑', + [adminPath('providers')]:'视频服务', + [adminPath('jobs')]:'任务进度', + [adminPath('alerts')]:'需要处理', + [adminPath('tenants')]:'客户列表', + [adminPath('audit')]:'变更记录', + [adminPath('costs')]:'任务统计' }; export function AdminShell() { @@ -29,33 +30,34 @@ export function AdminShell() { const [mobileOpen,setMobileOpen]=useState(false); const logout=async()=>{await post('/api/bff/session/logout');navigate('/login',{replace:true})}; return
-
setMobileOpen(true)}>平台运营后台
+
setMobileOpen(true)}>后台管理
{mobileOpen&&
:} ; @@ -66,5 +68,5 @@ export function AdminFeedback({error,onClose}:{error:string;onClose:()=>void}) { } function AdminNav({to,icon:Icon,label,count,onClick}:{to:string;icon:LucideIcon;label:string;count?:number;onClick:()=>void}) {return isActive?'active':''} onClick={onClick}>{label}{count!==undefined&&{count}}} -function adminRouteTitle(pathname:string) { return routeTitles[pathname]||(pathname.startsWith(`${adminPath('products')}/`)?'创作产品':pathname.startsWith(`${adminPath('releases')}/`)?'发布结果':pathname.startsWith(`${adminPath('customers')}/`)?'客户开通':pathname.startsWith(`${adminPath('capabilities')}/`)?'能力详情':'平台运营后台'); } +function adminRouteTitle(pathname:string) { return routeTitles[pathname]||(pathname.startsWith(`${adminPath('products')}/`)?'创作流程':pathname.startsWith(`${adminPath('releases')}/`)?'发布结果':pathname.startsWith(`${adminPath('customers')}/`)?'客户设置':pathname.startsWith(`${adminPath('capabilities')}/`)?'功能详情':'后台管理'); } const formatDateTime=(value:string)=>new Intl.DateTimeFormat('zh-CN',{month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}).format(new Date(value)); diff --git a/web/src/admin/WorkOSConfigPanels.tsx b/apps/web/src/admin/WorkOSConfigPanels.tsx similarity index 77% rename from web/src/admin/WorkOSConfigPanels.tsx rename to apps/web/src/admin/WorkOSConfigPanels.tsx index aa41675..8f36807 100644 --- a/web/src/admin/WorkOSConfigPanels.tsx +++ b/apps/web/src/admin/WorkOSConfigPanels.tsx @@ -118,9 +118,9 @@ export function AdminEnvironmentPanel({environments, sops, onSaved, initialEnvir setSaving(true); setNotice(''); try { await patch(`/api/bff/admin/environments/${encodeURIComponent(form.id)}`, {name: form.name, slug: form.slug, status: form.status, default_sop_id: form.default_sop_id || '', default_sop_version: form.default_sop_version || 0, capabilities: form.capabilities}); - setNotice(enrollment ? '客户开通已保存。新配置只影响后续创建的任务,历史任务和结果保持不变。' : '执行环境已保存,后续新任务会读取这份配置。'); + setNotice(enrollment ? '客户使用设置已保存。新设置只影响后续创建的任务,历史任务和结果保持不变。' : '客户设置已保存,后续新任务会使用这份设置。'); await onSaved(); - } catch (value) { setNotice(value instanceof Error ? value.message : enrollment ? '客户开通保存失败' : '执行环境保存失败'); } + } catch (value) { setNotice(value instanceof Error ? value.message : enrollment ? '客户使用设置保存失败' : '客户设置保存失败'); } finally { setSaving(false); } }; const create = async () => { @@ -128,10 +128,10 @@ export function AdminEnvironmentPanel({environments, sops, onSaved, initialEnvir try { const created = normalizeEnvironment(await post('/api/bff/admin/environments', createForm)); closeCreate(); setSelectedID(created.id); - setNotice(enrollment ? `客户「${created.name}」已开通。` : `执行环境「${created.name}」已创建。`); + setNotice(enrollment ? `客户「${created.name}」已开通。` : `客户设置「${created.name}」已创建。`); await onSaved(); onCreated?.(created.id); - } catch (value) { setNotice(value instanceof Error ? value.message : enrollment ? '客户开通创建失败' : '执行环境创建失败'); } + } catch (value) { setNotice(value instanceof Error ? value.message : enrollment ? '客户开通创建失败' : '客户设置创建失败'); } finally { setSaving(false); } }; @@ -143,12 +143,12 @@ export function AdminEnvironmentPanel({environments, sops, onSaved, initialEnvir const createRequired = requiredCapabilityIDs(sops, createForm.default_sop_id, createForm.default_sop_version); const createEnabled = new Set(createForm.capabilities.filter(capability => capability.enabled).map(capability => capability.id)); const createMissing = createRequired.filter(capabilityID => !createEnabled.has(capabilityID)); - const emptyAction = ; + const emptyAction = ; const directory = !scoped &&
-
{filterProductID ? `${filtered.length}/${environments.length}` : environments.length} 个{enrollment ? '客户开通' : '执行环境'}
-
{filtered.length === 0 ? : filtered.map(environment => { +
{filterProductID ? `${filtered.length}/${environments.length}` : environments.length} 个{enrollment ? '客户设置' : '客户设置'}
+
{filtered.length === 0 ? : filtered.map(environment => { const product = publishedSOPs.find(version => version.sop_id === environment.default_sop_id && version.version === environment.default_sop_version); - const row = <>{enrollment ? : }{environment.name}{enrollment ? `${product?.label || '尚未绑定已发布产品'} · ${environment.capabilities.filter(capability => capability.enabled).length} 项能力` : `${environment.slug} · ${environment.manifest_digest ? `配置摘要:${environment.manifest_digest.slice(0, 18)}…` : '尚未登记配置摘要'}`}{environment.status === 'active' ? (enrollment ? '已启用' : '运行中') : '已暂停'}; + const row = <>{enrollment ? : }{environment.name}{enrollment ? `${product?.label || '尚未选择创作流程'} · ${environment.capabilities.filter(capability => capability.enabled).length} 项可用功能` : `${environment.slug} · ${environment.manifest_digest ? `设置记录:${environment.manifest_digest.slice(0, 18)}…` : '还没有设置记录'}`}{environment.status === 'active' ? '已启用' : '已暂停'}; return environmentPath ? selectEnvironment(environment.id)}>{row} : ; })}
{notice && directoryOnly &&
{notice}
} @@ -157,20 +157,20 @@ export function AdminEnvironmentPanel({environments, sops, onSaved, initialEnvir return <> {directory} {!directoryOnly && form &&
-
{enrollment ? '客户开通配置' : `${workOSTerm.environment}配置`}

{form.name}

{form.status === 'active' ? '可创建新任务' : '暂停新任务'}
- {enrollment &&
固定产品版本{selectedProduct?.label || '尚未选择'}能力覆盖{required.length === 0 ? '产品未声明' : `${required.length - missing.length}/${required.length}`}当前影响{form.status === 'active' ? '允许新任务' : '阻止新任务'}
} -
setForm({...form, name: event.target.value})}/> setForm({...form, slug: event.target.value})}/>
-
{enrollment ? '客户可用能力范围' : '已声明的本地能力'}{editorCapabilities.length === 0 ? : editorCapabilities.map(capability => )}
- {enrollment &&
{missing.length ? : }
{missing.length ? `缺少 ${missing.length} 项产品所需能力` : '当前能力范围覆盖产品声明'}{missing.length ? `${missing.join('、')} 尚未启用,后续任务可能无法完成对应步骤。` : '保存后只影响新任务;历史任务、审批和创作结果不会被改写。'}
} -
{notice &&
{notice}
} +
{enrollment ? '客户使用设置' : '客户设置'}

{form.name}

{form.status === 'active' ? '可以开始新创作' : '暂时不能开始新创作'}
+ {enrollment &&
当前使用版本{selectedProduct?.label || '尚未选择'}功能准备情况{required.length === 0 ? '产品未说明' : `${required.length - missing.length}/${required.length}`}当前影响{form.status === 'active' ? '允许新任务' : '阻止新任务'}
} +
setForm({...form, name: event.target.value})}/> setForm({...form, slug: event.target.value})}/>
+
{enrollment ? '客户可用功能' : '电脑可用功能'}{editorCapabilities.length === 0 ? : editorCapabilities.map(capability => )}
+ {enrollment &&
{missing.length ? : }
{missing.length ? `还缺少 ${missing.length} 项必需功能` : '当前功能已准备齐全'}{missing.length ? `${missing.join('、')} 尚未开启,后续任务可能无法完成对应步骤。` : '保存后只影响新任务;历史任务、审批和创作结果不会被改写。'}
} +
{notice &&
{notice}
}
} {createOpen && (enrollment ?
    {['客户','产品版本','能力范围','确认影响'].map((label,index) =>
  1. index + 1 ? 'is-complete' : ''} key={label}>{index + 1}{label}
  2. )}
{createStep === 1 &&
setCreateForm({...createForm, name: event.target.value})} placeholder="例如:青云品牌团队"/> setCreateForm({...createForm, slug: event.target.value})} placeholder="例如:qingyun-brand"/>
} {createStep === 2 &&
{publishedSOPs.length === 0 &&
没有可开通的产品版本先发布一个创作产品版本,再建立客户开通。
}
} - {createStep === 3 &&
客户可用能力范围{capabilityOptions(availableCapabilities, createForm.capabilities).length === 0 ? : capabilityOptions(availableCapabilities, createForm.capabilities).map(capability => )}
} - {createStep === 4 &&
客户{createForm.name}创作产品{publishedSOPs.find(version => version.sop_id === createForm.default_sop_id && version.version === createForm.default_sop_version)?.label || '尚未选择'}能力覆盖{createRequired.length===0?'产品未声明':`${createRequired.length-createMissing.length}/${createRequired.length}`}
{createMissing.length?:}
{createMissing.length?`缺少 ${createMissing.length} 项产品所需能力`:'开通只约束后续新任务'}{createMissing.length?`${createMissing.join('、')} 尚未启用;可以先保存为暂停,补齐能力后再启用。`:'暂停或修改开通不会隐藏历史任务、审批记录和创作结果。'}
} + {createStep === 3 &&
客户可用功能{capabilityOptions(availableCapabilities, createForm.capabilities).length === 0 ? : capabilityOptions(availableCapabilities, createForm.capabilities).map(capability => )}
} + {createStep === 4 &&
客户{createForm.name}创作产品{publishedSOPs.find(version => version.sop_id === createForm.default_sop_id && version.version === createForm.default_sop_version)?.label || '尚未选择'}功能准备情况{createRequired.length===0?'产品未说明':`${createRequired.length-createMissing.length}/${createRequired.length}`}
{createMissing.length?:}
{createMissing.length?`还缺少 ${createMissing.length} 项必需功能`:'开通只约束后续新任务'}{createMissing.length?`${createMissing.join('、')} 尚未开启;可以先保存为暂停,补齐功能后再启用。`:'暂停或修改开通不会隐藏历史任务、审批记录和创作结果。'}
}
{createStep < 4 ? : }
-
:
setCreateForm({...createForm, name: event.target.value})} placeholder="例如:内容审核环境"/> setCreateForm({...createForm, slug: event.target.value})} placeholder="例如:review"/>
)} +
:
setCreateForm({...createForm, name: event.target.value})} placeholder="例如:内容审核客户"/> setCreateForm({...createForm, slug: event.target.value})} placeholder="例如:review"/>
)} ; } @@ -210,7 +210,7 @@ export function AdminSOPPanel({sops, environments=[], onChanged, onPublished, in try { const created = await createAdminProduct(createForm); setCreateOpen(false); setCreateForm(emptyAdminProductDraft()); setNotice(`创作产品「${created.definition.name}」已创建为 v1 草稿。`); await onChanged(); selectVersion(created.definition.id, 1); - } catch (value) { setNotice(value instanceof Error ? value.message : '流程规范创建失败'); } + } catch (value) { setNotice(value instanceof Error ? value.message : '创作流程创建失败'); } finally { setBusy(false); } }; if (!summary || !draft) return
setCreateOpen(true)}>新建创作产品}/>{createOpen && setCreateOpen(false)} onCreate={createSOP}/>}
; @@ -218,14 +218,14 @@ export function AdminSOPPanel({sops, environments=[], onChanged, onPublished, in const updateDraft = (next: SOPVersion) => { setDraft(next); setLint(undefined); }; const createDraft = async () => { setBusy(true); setNotice(''); try { const created = normalizeSOPVersion(await post(`/api/bff/admin/sops/${encodeURIComponent(summary.definition.id)}/versions`, {source_version: draft.version})); setNotice(`已创建 v${created.version} 草稿。`); await onChanged(); selectVersion(created.sop_id, created.version); } catch (value) { setNotice(value instanceof Error ? value.message : '草稿创建失败'); } finally { setBusy(false); } }; const cloneAsCustom = async () => { setBusy(true); setNotice(''); try { const created = normalizeSOPSummary(await post('/api/bff/admin/sops', {name: `${draft.name}(自定义)`, description: draft.description, content_types: draft.content_types, stages: draft.stages, gates: draft.gates, default_execution_mode: draft.default_execution_mode})); setNotice(`已复制为「${created.definition.name}」草稿,可继续修改。`); await onChanged(); selectVersion(created.definition.id, 1); } catch (value) { setNotice(value instanceof Error ? value.message : '复制创作产品失败'); } finally { setBusy(false); } }; - const saveDraft = async () => { if (!editable) return; setBusy(true); setNotice(''); try { const saved = normalizeSOPVersion(await patch(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}`, {name: draft.name, description: draft.description, content_types: draft.content_types, stages: draft.stages, gates: draft.gates, default_execution_mode: draft.default_execution_mode})); setDraft(saved); setNotice('流程规范草稿已保存。'); await onChanged(); } catch (value) { setNotice(value instanceof Error ? value.message : '流程规范草稿保存失败'); } finally { setBusy(false); } }; - const runLint = async () => { setBusy(true); setNotice(''); try { setLint(await api(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/lint`)); } catch (value) { setNotice(value instanceof Error ? value.message : '流程规范检查失败'); } finally { setBusy(false); } }; - const publish = async () => { setBusy(true); setNotice(''); try { const report = await api(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/lint`); setLint(report); if (!report.valid) { setNotice('发布前检查未通过,请先修正配置。'); return; } const published = normalizeSOPVersion(await post(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/publish`)); setDraft(published); setNotice(`流程规范 v${published.version} 已发布,配置摘要已固定。`); await onChanged(); onPublished?.(published.sop_id, published.version); } catch (value) { setNotice(value instanceof Error ? value.message : '流程规范发布失败'); } finally { setBusy(false); } }; + const saveDraft = async () => { if (!editable) return; setBusy(true); setNotice(''); try { const saved = normalizeSOPVersion(await patch(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}`, {name: draft.name, description: draft.description, content_types: draft.content_types, stages: draft.stages, gates: draft.gates, default_execution_mode: draft.default_execution_mode})); setDraft(saved); setNotice('创作流程草稿已保存。'); await onChanged(); } catch (value) { setNotice(value instanceof Error ? value.message : '创作流程草稿保存失败'); } finally { setBusy(false); } }; + const runLint = async () => { setBusy(true); setNotice(''); try { setLint(await api(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/lint`)); } catch (value) { setNotice(value instanceof Error ? value.message : '创作流程检查失败'); } finally { setBusy(false); } }; + const publish = async () => { setBusy(true); setNotice(''); try { const report = await api(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/lint`); setLint(report); if (!report.valid) { setNotice('发布前检查未通过,请先修正配置。'); return; } const published = normalizeSOPVersion(await post(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/publish`)); setDraft(published); setNotice(`创作流程 v${published.version} 已发布,设置记录已固定。`); await onChanged(); onPublished?.(published.sop_id, published.version); } catch (value) { setNotice(value instanceof Error ? value.message : '创作流程发布失败'); } finally { setBusy(false); } }; const compare = async () => { if (!compareVersion) return; setBusy(true); setNotice(''); try { setDiff(await api(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${compareVersion}/diff/${draft.version}`)); } catch (value) { setNotice(value instanceof Error ? value.message : '版本比较失败'); } finally { setBusy(false); } }; const loadImpact = async () => { setBusy(true); setNotice(''); try { setImpact(await api(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/impact`)); } catch (value) { setNotice(value instanceof Error ? value.message : '影响分析失败'); } finally { setBusy(false); } }; const loadPreview = async () => { setBusy(true); setNotice(''); try { const query = previewEnvironmentID ? `?environment_id=${encodeURIComponent(previewEnvironmentID)}` : ''; setPreview(normalizeSOPVersionPreview(await api(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/preview${query}`))); } catch (value) { setNotice(value instanceof Error ? value.message : '发布预览读取失败'); } finally { setBusy(false); } }; - const rollback = async () => { const target = summary.versions.find(candidate => candidate.version === compareVersion); if (!target || compareVersion >= summary.definition.current_version || !window.confirm(`确认回滚到 v${compareVersion}?历史执行记录不会改变,绑定当前版本的未来执行环境和项目会切换到新版本。`)) return; setBusy(true); setNotice(''); try { const result = await post<{version:SOPVersion; rebound_environments:number; rebound_projects:number}>(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/rollback`, {target_version: compareVersion}); setSelectedVersion(result.version.version); setNotice(`已创建回滚版本 v${result.version.version},切换 ${result.rebound_environments} 个执行环境、${result.rebound_projects} 个项目。`); await onChanged(); } catch (value) { setNotice(value instanceof Error ? value.message : '流程规范回滚失败'); } finally { setBusy(false); } }; - const retire = async () => { if (draft.status !== 'published' || draft.version === summary.definition.current_version || !window.confirm(`确认停用流程规范 v${draft.version}?已绑定的历史任务仍可只读完成。`)) return; setBusy(true); setNotice(''); try { await post(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/retire`, {}); setNotice(`流程规范 v${draft.version} 已停用,新任务不能再绑定它。`); await onChanged(); } catch (value) { setNotice(value instanceof Error ? value.message : '流程规范停用失败'); } finally { setBusy(false); } }; + const rollback = async () => { const target = summary.versions.find(candidate => candidate.version === compareVersion); if (!target || compareVersion >= summary.definition.current_version || !window.confirm(`确认恢复到 v${compareVersion}?历史执行记录不会改变,绑定当前版本的未来客户设置和项目会切换到新版本。`)) return; setBusy(true); setNotice(''); try { const result = await post<{version:SOPVersion; rebound_environments:number; rebound_projects:number}>(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/rollback`, {target_version: compareVersion}); setSelectedVersion(result.version.version); setNotice(`已创建恢复版本 v${result.version.version},切换 ${result.rebound_environments} 个客户设置、${result.rebound_projects} 个项目。`); await onChanged(); } catch (value) { setNotice(value instanceof Error ? value.message : '创作流程恢复失败'); } finally { setBusy(false); } }; + const retire = async () => { if (draft.status !== 'published' || draft.version === summary.definition.current_version || !window.confirm(`确认停用创作流程 v${draft.version}?已绑定的历史任务仍可只读完成。`)) return; setBusy(true); setNotice(''); try { await post(`/api/bff/admin/sops/${encodeURIComponent(draft.sop_id)}/versions/${draft.version}/retire`, {}); setNotice(`创作流程 v${draft.version} 已停用,新任务不能再绑定它。`); await onChanged(); } catch (value) { setNotice(value instanceof Error ? value.message : '创作流程停用失败'); } finally { setBusy(false); } }; const updateStage = (index: number, changes: Partial) => updateDraft({...draft, stages: draft.stages.map((stage, stageIndex) => stageIndex === index ? {...stage, ...changes} : stage)}); const addStage = () => updateDraft({...draft, stages: [...draft.stages, {stage_id: `stage-${draft.stages.length + 1}`, name: '新流程阶段', order: nextOrder(draft.stages), owner_roles: [], input_refs: [], output_schema: 'contentcloud.output/1.0', output_schema_refs: [], accepted_input_types: [], required_output_types: [], required_capabilities: [], execution_modes: [draft.default_execution_mode || 'local'], checks: [], gate_ids: [], retry_max_attempts: 0, retry_policy: {}, cost_policy: {}}]}); const removeStage = (index: number) => updateDraft({...draft, stages: draft.stages.filter((_, stageIndex) => stageIndex !== index).map((stage, stageIndex) => ({...stage, order: (stageIndex + 1) * 10}))}); @@ -236,22 +236,22 @@ export function AdminSOPPanel({sops, environments=[], onChanged, onPublished, in return <>
{scoped?选择要查看或维护的产品版本:}{summary.versions.length} 个版本{!scoped&&}
{summary.versions.map(candidate => )}
创作产品版本

{draft.name} · v{draft.version}

{summary.definition.built_in ? `内置产品 · ${summary.definition.source_ref || 'Content Work OS'}` : '平台自定义创作产品'}
{editable ? 可编辑草稿 : 已发布,不可直接修改}
updateDraft({...draft, name: event.target.value})}/>