From 3f742fbaca16ffa74f3f541223dc5c3ae3d5b934 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 3 Jun 2026 10:24:03 +0800 Subject: [PATCH] feat: unify creation entry templates --- .hermes/shared-memory/decision-log.md | 8 + .hermes/shared-memory/pitfalls.md | 8 + ...玩法创作】平å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md | 8 +- .../src/creation_entry_config.rs | 91 +++++- .../BigFishAgentWorkspace.tsx | 3 + .../CreationAgentWorkspace.test.tsx | 10 +- .../creation-agent/CreationAgentWorkspace.tsx | 40 +-- .../creative-agent/CreativeAgentWorkspace.tsx | 28 +- .../CustomWorldAgentWorkspace.tsx | 3 + .../CustomWorldCreationHub.test.tsx | 126 +++++++-- .../CustomWorldCreationHub.tsx | 68 ++--- .../CustomWorldCreationStartCard.tsx | 154 +++++----- .../custom-world-home/creationWorkShelf.ts | 2 +- .../PlatformEntryFlowShellImpl.tsx | 266 +++++++++++------- ...gEntryFlowShell.agent.interaction.test.tsx | 32 ++- .../SquareHoleAgentWorkspace.tsx | 3 + .../UnifiedCreationPage.test.tsx | 4 +- .../unified-creation/UnifiedCreationPage.tsx | 8 +- .../UnifiedGenerationPage.tsx | 4 +- .../unifiedCreationSpecs.test.ts | 38 ++- .../unified-creation/unifiedCreationSpecs.ts | 172 ++++++++++- .../unified-creation/unifiedGenerationCopy.ts | 9 +- src/index.css | 40 +++ src/index.test.ts | 21 ++ src/services/creationEntryConfigService.ts | 20 +- 25 files changed, 820 insertions(+), 346 deletions(-) diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index bdc69789..2a616fff 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -16,6 +16,14 @@ --- +## 2026-06-03 最近创作åªå¤ç”¨åˆ›ä½œæ¨¡æ¿å…¥å£ + +- 背景:底部加å·åˆ›ä½œå…¥å£çš„â€œæœ€è¿‘åˆ›ä½œâ€æœ€åˆç”±çœŸå®žä½œå“架摘è¦é©±åŠ¨ï¼Œä½†é¡µé¢æ›¾æŒ‰ä½œå“æ ‡é¢˜ã€æ‘˜è¦å’Œç”ŸæˆçŠ¶æ€æ¸²æŸ“独立最近创作å¡ï¼Œå’Œå…¶å®ƒæ¨¡æ¿é¡µç­¾çš„å¡ç‰‡æ ·å¼åŠç‚¹å‡»è¯­ä¹‰ä¸ä¸€è‡´ã€‚ +- 决策:“最近创作â€ä»åªç”±çœŸå®žåŽç«¯ä½œå“架摘è¦å†³å®šæ˜¯å¦å±•示,但åªçº³å…¥ `updatedAt` 在最近 7 天内的摘è¦ï¼Œä¸”摘è¦åªç”¨äºŽæŽ¨å¯¼æœ€è¿‘ä½¿ç”¨è¿‡çš„æ¨¡æ¿ ID;实际列表必须从åŽç«¯å…¥å£é…置的 `creationTypes` 中筛出对应模æ¿ï¼Œå¤ç”¨å…¶å®ƒé¡µç­¾çš„æ¨¡æ¿å¡ç»“æž„ã€æ–‡æ¡ˆå’Œ `onCreateType` 点击行为,ä¸å±•示具体作å“åç§°ã€ä½œå“æ‘˜è¦æˆ–è‰ç¨¿ / 生æˆçжæ€ï¼Œä¹Ÿä¸æ–°å¢žç‹¬ç«‹æœ€è¿‘创作组件。最近创作页签激活时,页é¢å¿…须显示“仅显示最近7天内使用过的模æ¿â€ã€‚ +- å½±å“范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`ã€`src/components/custom-world-home/CustomWorldCreationHub.tsx`ã€`src/components/platform-entry`ã€åˆ›ä½œå…¥å£ç›¸å…³æµ‹è¯•与玩法链路文档。 +- éªŒè¯æ–¹å¼ï¼š`CustomWorldCreationHub` æµ‹è¯•åº”æ–­è¨€æœ€è¿‘åˆ›ä½œé¡µç­¾åŒ…å« `creation-template-card`ã€æ¨¡æ¿æ ‡é¢˜ / 副标题,并且ä¸å‡ºçŽ°æ—§ `creation-recent-work-grid`ã€ä½œå“标题ã€ä½œå“æ‘˜è¦æˆ–â€œæ‰“å¼€æœ€è¿‘åˆ›ä½œâ€æŒ‰é’®æ–‡æ¡ˆï¼›RPG å…¥å£äº¤äº’æµ‹è¯•åº”æ–­è¨€æœ€è¿‘åˆ›ä½œé»˜è®¤é¡µç­¾å±•ç¤ºâ€œæ–‡å­—å†’é™©â€æ¨¡æ¿å¡ã€‚ +- å…³è”æ–‡æ¡£ï¼š`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 + ## 2026-06-02 底部加å·åˆ›ä½œå…¥å£é¡µ banner 与最近创作å£å¾„ - 背景:创作入å£é¡µ banner 曾固定为å‰ç«¯ä¸¤å¼ ä¸»é¢˜èµ›å¡ï¼Œä¸”模æ¿åˆ†ç±»å…œåº•会产生 `recent` / `最近创作` 页签,和åŽå°é…ç½®åŠçœŸå®žä½œå“æ•°æ®å£å¾„冲çªã€‚ diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index b80df113..8fb8aacb 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -23,6 +23,14 @@ - 验è¯ï¼šè§¦å‘任一平å°çº§å¼‚步失败时,页é¢åº”出现包å«â€œé”™è¯¯æ¥æºâ€å’Œâ€œé”™è¯¯å†…容â€çš„弹窗;å¤åˆ¶å†…å®¹åº”åŒ…å«æ¥æºå’Œé”™è¯¯æ­£æ–‡ï¼›æ—§é¡µé¢å†…错误 banner ä¸å†é‡å¤å‡ºçŽ°ã€‚ - å…³è”:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`ã€`src/components/platform-entry/PlatformErrorDialog.tsx`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 +## 暗色创作进度å¡ä¸è¦è¢« platform-remap-surface æ”¹æˆæ·±è‰²æ–‡å­— + +- 现象:统一创作页里的暗色进度å¡èƒŒæ™¯æ˜¯æ·±ç»¿ / æ·±è“,但“创作进度â€ã€ç™¾åˆ†æ¯”和进度æç¤ºæ˜¾ç¤ºæˆæ·±è‰²ï¼Œç§»åŠ¨ç«¯å‡ ä¹Žçœ‹ä¸æ¸…。 +- 原因:`platform-remap-surface` 在浅色主题下会把åŽä»£ `[class*='text-white']` å¼ºåˆ¶é‡æ˜ å°„æˆ `var(--platform-text-strong)`,并且使用 `!important`;暗色 hero å¡ç‰‡å¦‚æžœåªå†™é€šç”¨ `text-white*`,刷新åŽä»ä¼šè¢«å…¨å±€ remap è¦†ç›–æˆæ·±è‰²ã€‚早期还混用了 `text-white/72`ã€`text-white/88`ã€`border-white/14`ã€`bg-white/12` ç­‰ä¸ç¨³é€æ˜Žåº¦æ¡£ä½ï¼Œè¿›ä¸€æ­¥æ”¾å¤§äº†é—®é¢˜ã€‚ +- 处ç†ï¼šç»™æš—色 hero 加组件专属 class,例如 `creation-agent-hero__progress-label`ã€`creation-agent-hero__progress-value`ã€`creation-agent-hero__progress-hint`,并在 `src/index.css` çš„ remap 规则之åŽç”¨æ›´å…·ä½“选择器和 `!important` å›ºå®šç™½è‰²é€æ˜Žåº¦ã€è¾¹æ¡†å’Œè¿›åº¦æ¡åº•色。 +- 验è¯ï¼š`CreationAgentWorkspace` 测试应断言进度标题ã€ç™¾åˆ†æ¯”å’Œæç¤ºæ–‡æœ¬å¸¦ä¸“属 classï¼›`src/index.test.ts` 应断言这些 class 在 remap surface 内有白色覆盖规则;移动端截图中暗色å¡ç‰‡æ–‡å­—åº”ä¿æŒå¯è¯»ã€‚ +- å…³è”:`src/components/creation-agent/CreationAgentWorkspace.tsx`ã€`src/components/creation-agent/CreationAgentWorkspace.test.tsx`ã€`src/index.css`ã€`src/index.test.ts`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 + ## VectorEngine å›¾ç‰‡ç”Ÿæˆ SendRequest è¶…æ—¶è¦æŒ‰ä¼ è¾“失败排查 - 现象:`external_api_call_failure` 里看到 `failureStage=request_send`ã€`timeout=true`ã€`statusCode=null`,`errorSource` å¯èƒ½æ˜¯ `client error (SendRequest)` 或更完整的 reqwest 底层错误链,å‰ç«¯åªçŸ¥é“图片生æˆå¤±è´¥ã€‚ diff --git a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md index 414a976c..b5d29797 100644 --- a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md +++ b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md @@ -1,16 +1,16 @@ # å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯ -更新时间:`2026-05-15` +更新时间:`2026-06-03` ## å¹³å°åˆ›ä½œå…¥å£ 创作入å£é…置事实æºåœ¨ SpacetimeDB,通过 `GET /api/creation-entry/config` 下å‘ï¼›åŽå°é€šè¿‡ `/admin/api/creation-entry/config` 管ç†ã€‚å‰ç«¯åªåœ¨å±•示层派生å¯è§å¡ç‰‡å’Œå…¥å£çжæ€ï¼Œ`api-server` 路由熔断也使用åŒä¸€ä»½é…置。ä¸è¦æ¢å¤å‰ç«¯ç¡¬ç¼–ç å…¥å£é…置文件。 -当å‰ç‚¹å‡»åº•部加å·è¿›å…¥çš„创作入å£é¡µæ‰¿è½½åŽå°å…¬å‘Šä½ã€åˆ›ä½œå…¥å£é¡µç­¾å’Œä¸¤åˆ—模æ¿å¡ï¼›é¡µç­¾ä¸­åªæœ‰çœŸå®žåŽç«¯ä½œå“架摘è¦å­˜åœ¨æ—¶æ‰å±•示“最近创作â€ï¼Œå…¶ä½™ä¸ºçŽ©æ³•æ¨¡æ¿åˆ†ç±»ã€‚点击模æ¿å¡åŽç›´æŽ¥è¿›å…¥å¯¹åº”玩法已有的入å£åˆ›ä½œè¡¨å• stage,ä¸å†ç»è¿‡ç©ºç™½å ä½é¡µï¼Œä¹Ÿä¸æŠŠæ—§è¡¨å•嵌进创作入å£é¡µã€‚移动端创作入å£é¡µé¡¶æ åœ¨ `陶泥儿` å“牌åŒä¸€è¡Œæ˜¾ç¤ºçœŸå®žè´¦æˆ·æ³¥ç‚¹æ•°ï¼Œæ•°æ®æ¥è‡ª `profileDashboard.walletBalance`,ä¸å¾—å†æŠŠå…¬å‘Šå†…å®¹æˆ–æ´»åŠ¨å¥–æ± å½“ä½œè´¦å·ä½™é¢å±•示。创作入å£é¡µå…¬å‘Šä½æ•°æ®ä¼˜å…ˆè¯»å– `GET /api/creation-entry/config` çš„ `eventBanners` 数组,多æ¡é…置时å‰ç«¯è‡ªåŠ¨è½®æ’­ï¼Œæ—§ `eventBanner` ä»…ä½œä¸ºå•æ¡å…¼å®¹å…œåº•。åŽå°å…¬å‘Šé…ç½®é¢å‘表å•ï¼šæ¯æ¡å…¬å‘ŠåŒ…嫿 ‡é¢˜å’Œ HTML 内容,åŽå°ä¿å­˜æ—¶åºåˆ—化为åŽç«¯ `eventBannersJson` 传输字段,由å‰ç«¯ç©ºæƒé™æ²™ç®± iframe 展示;旧结构化 banner 字段仅ä¿ç•™å›žæ˜¾å…¼å®¹ï¼Œä¸å†ä½œä¸ºåŽå°å…¬å‘Šé…置主格å¼ï¼›ä¸å¾—执行 JSX 或把åŽå°ä»£ç ç›´æŽ¥æ³¨å…¥ DOM。玩法列表ä¸å†å¥—外部边框å¡ç‰‡ï¼Œç§»åŠ¨ç«¯éœ€è¦åŽ‹ç¼©æ¨ªå‘è¾¹è·å’Œä¸¤åˆ—é—´è·ï¼›çŽ©æ³•å¡ç»Ÿä¸€æŒ‰â€œä¸Šå›¾ã€å·¦ä¸ŠçŠ¶æ€æ ‡ç­¾ï¼ˆä»…éžå¼€æ”¾æ€æ˜¾ç¤ºï¼‰ã€å°é¢å³ä¸‹ `10-20泥点数`ã€ä¸‹æ–¹ç™½åº•标题/æè¿°â€ç»“构展示,å¡ç‰‡é«˜åº¦ä¿æŒç´§å‡‘ä½†æ ‡é¢˜ã€æè¿°å’Œé¢„ä¼°æ¶ˆè€—ç‚¹æ•°éƒ½å¿…é¡»å¯è§ã€‚创作入å£é¡µæ ¹å®¹å™¨ä¸å†ä½¿ç”¨ `platform-page-stage` 这类全局内容å¡ç‰‡å£³ï¼Œä½†ç»§ç»­ä¿ç•™ `platform-remap-surface` 作为主题和输入框样å¼å‘½ä¸­é’©å­ã€‚创作入å£é¡µå­—å·éœ€è¦å¯¹é½å¹³å°æ™®é€š UI æ¡£ä½ï¼šé¡¶æ æ³¥ç‚¹ç»„ä»¶ã€å…¬å‘Šæ­£æ–‡ã€åˆ†ç±» Tab å’ŒçŽ©æ³•å¡æ ‡é¢˜ / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,ä¸ä½¿ç”¨ `text-lg`ã€`text-xl` 或更大的展示级字å·ã€‚è‰ç¨¿ Tab ç»§ç»­æ‰¿æŽ¥ä½œå“æž¶ï¼›åº•部加å·å…¥å£é¡µçš„â€œæœ€è¿‘åˆ›ä½œâ€æ¥è‡ªçœŸå®žåŽç«¯ä½œå“架摘è¦ï¼Œç”Ÿæˆä¸­å’Œç”Ÿæˆå¤±è´¥çš„è‰ç¨¿æ‘˜è¦éƒ½åº”进入最近创作并显示对应状æ€ã€‚RPGã€RPG 之外的å„玩法入å£åˆ†åˆ«è½åˆ°æ—¢æœ‰çš„ `agent-workspace`ã€`big-fish-agent-workspace`ã€`match3d-agent-workspace`ã€`square-hole-agent-workspace`ã€`jump-hop-workspace`ã€`wooden-fish-workspace`ã€`puzzle-agent-workspace`ã€`bark-battle-workspace`ã€`visual-novel-agent-workspace`ã€`baby-object-match-workspace`,这些入å£ç»§ç»­æ‰¿æŽ¥å„玩法自己的表å•ã€è‰ç¨¿æ¢å¤å’ŒåŽç»­ç¼–排,ä¸ä½œä¸ºåˆ›ä½œå…¥å£é¡µå†…容。 +当å‰ç‚¹å‡»åº•部加å·è¿›å…¥çš„创作入å£é¡µæ‰¿è½½åŽå°å…¬å‘Šä½ã€åˆ›ä½œå…¥å£é¡µç­¾å’Œä¸¤åˆ—模æ¿å¡ï¼›é¡µç­¾ä¸­åªæœ‰çœŸå®žåŽç«¯ä½œå“架摘è¦å­˜åœ¨æ—¶æ‰å±•示“最近创作â€ï¼Œå…¶ä½™ä¸ºçŽ©æ³•æ¨¡æ¿åˆ†ç±»ã€‚点击模æ¿å¡åŽç›´æŽ¥è¿›å…¥å¯¹åº”玩法已有的入å£åˆ›ä½œè¡¨å• stage,ä¸å†ç»è¿‡ç©ºç™½å ä½é¡µï¼Œä¹Ÿä¸æŠŠæ—§è¡¨å•嵌进创作入å£é¡µã€‚移动端创作入å£é¡µé¡¶æ åœ¨ `陶泥儿` å“牌åŒä¸€è¡Œæ˜¾ç¤ºçœŸå®žè´¦æˆ·æ³¥ç‚¹æ•°ï¼Œæ•°æ®æ¥è‡ª `profileDashboard.walletBalance`,ä¸å¾—å†æŠŠå…¬å‘Šå†…å®¹æˆ–æ´»åŠ¨å¥–æ± å½“ä½œè´¦å·ä½™é¢å±•示。创作入å£é¡µå…¬å‘Šä½æ•°æ®ä¼˜å…ˆè¯»å– `GET /api/creation-entry/config` çš„ `eventBanners` 数组,多æ¡é…置时å‰ç«¯è‡ªåŠ¨è½®æ’­ï¼Œæ—§ `eventBanner` ä»…ä½œä¸ºå•æ¡å…¼å®¹å…œåº•。åŽå°å…¬å‘Šé…ç½®é¢å‘表å•ï¼šæ¯æ¡å…¬å‘ŠåŒ…嫿 ‡é¢˜å’Œ HTML 内容,åŽå°ä¿å­˜æ—¶åºåˆ—化为åŽç«¯ `eventBannersJson` 传输字段,由å‰ç«¯ç©ºæƒé™æ²™ç®± iframe 展示;旧结构化 banner 字段仅ä¿ç•™å›žæ˜¾å…¼å®¹ï¼Œä¸å†ä½œä¸ºåŽå°å…¬å‘Šé…置主格å¼ï¼›ä¸å¾—执行 JSX 或把åŽå°ä»£ç ç›´æŽ¥æ³¨å…¥ DOM。玩法列表ä¸å†å¥—外部边框å¡ç‰‡ï¼Œç§»åŠ¨ç«¯éœ€è¦åŽ‹ç¼©æ¨ªå‘è¾¹è·å’Œä¸¤åˆ—é—´è·ï¼›çŽ©æ³•å¡ç»Ÿä¸€æŒ‰â€œä¸Šå›¾ã€å·¦ä¸ŠçŠ¶æ€æ ‡ç­¾ï¼ˆä»…éžå¼€æ”¾æ€æ˜¾ç¤ºï¼‰ã€å°é¢å³ä¸‹ `10-20泥点数`ã€ä¸‹æ–¹ç™½åº•标题/æè¿°â€ç»“构展示,å¡ç‰‡é«˜åº¦ä¿æŒç´§å‡‘ä½†æ ‡é¢˜ã€æè¿°å’Œé¢„ä¼°æ¶ˆè€—ç‚¹æ•°éƒ½å¿…é¡»å¯è§ã€‚创作入å£é¡µæ ¹å®¹å™¨ä¸å†ä½¿ç”¨ `platform-page-stage` 这类全局内容å¡ç‰‡å£³ï¼Œä½†ç»§ç»­ä¿ç•™ `platform-remap-surface` 作为主题和输入框样å¼å‘½ä¸­é’©å­ã€‚创作入å£é¡µå­—å·éœ€è¦å¯¹é½å¹³å°æ™®é€š UI æ¡£ä½ï¼šé¡¶æ æ³¥ç‚¹ç»„ä»¶ã€å…¬å‘Šæ­£æ–‡ã€åˆ†ç±» Tab å’ŒçŽ©æ³•å¡æ ‡é¢˜ / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,ä¸ä½¿ç”¨ `text-lg`ã€`text-xl` 或更大的展示级字å·ã€‚è‰ç¨¿ Tab ç»§ç»­æ‰¿æŽ¥ä½œå“æž¶ï¼›åº•部加å·å…¥å£é¡µçš„“最近创作â€åªç”¨ 7 天内的真实åŽç«¯ä½œå“架摘è¦åˆ¤æ–­æ˜¯å¦å±•示,并从摘è¦é‡ŒæŽ¨å¯¼æœ€è¿‘ä½¿ç”¨è¿‡çš„æ¨¡æ¿ ID,页é¢å¿…须展示“仅显示最近7天内使用过的模æ¿â€æç¤ºï¼Œåˆ—表内容必须å¤ç”¨å…¶å®ƒé¡µç­¾é‡Œçš„æ¨¡æ¿å¡æ ·å¼ã€æ–‡æ¡ˆå’Œç‚¹å‡»è¡Œä¸ºï¼Œä¸å±•示具体作å“åç§°ã€æ‘˜è¦æˆ–生æˆçжæ€ï¼Œä¹Ÿä¸æ–°å¢žç‹¬ç«‹æœ€è¿‘创作å¡ç»„件。RPGã€RPG 之外的å„玩法入å£åˆ†åˆ«è½åˆ°æ—¢æœ‰çš„ `agent-workspace`ã€`big-fish-agent-workspace`ã€`match3d-agent-workspace`ã€`square-hole-agent-workspace`ã€`jump-hop-workspace`ã€`wooden-fish-workspace`ã€`puzzle-agent-workspace`ã€`bark-battle-workspace`ã€`visual-novel-agent-workspace`ã€`baby-object-match-workspace`,这些入å£ç»§ç»­æ‰¿æŽ¥å„玩法自己的表å•ã€è‰ç¨¿æ¢å¤å’ŒåŽç»­ç¼–排,ä¸ä½œä¸ºåˆ›ä½œå…¥å£é¡µå†…容。 创作æ¢å¤å‚æ•°åªä¿ç•™ `sessionId`ã€`profileId`ã€`draftId`ã€`workId` è¿™å››ä¸ªç§æœ‰ query。它们åªå…许在åŒä¸€æ¡åˆ›ä½œé“¾è·¯çš„结果页ã€ç”Ÿæˆé¡µã€å·¥ä½œå°ä¹‹é—´ä¿ç•™ï¼›åˆ‡åˆ°é¦–页ã€å…¬å¼€ä½œå“详情ã€runtime 或å¦ä¸€æ¡çŽ©æ³•é“¾è·¯æ—¶å¿…é¡»æ¸…æŽ‰ã€‚ç”Ÿæˆé¡µç­‰å¾…时间统一以生æˆçжæ€é‡Œçš„ `startedAtMs` ä¸ºå‡†ï¼›åˆ›å»ºè¯¥çŠ¶æ€æ—¶ä¼˜å…ˆä½¿ç”¨åŽç«¯ session 下å‘çš„æ—¶é—´æˆ³ï¼Œä½œå“æ‘˜è¦é‡Œçš„ `updatedAt` ä»åªç”¨äºŽæŽ’åºä¸Žæ‘˜è¦å±•示,ä¸ä½œä¸ºå‰ç«¯è‡ªè¡ŒæŽ¨å¯¼ä¸šåŠ¡çŠ¶æ€çš„真相。 -一期创作æµç¨‹ç»Ÿä¸€åŒ–è¦†ç›–æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼ã€‚四者在å‰ç«¯ç»Ÿä¸€ç»è¿‡ `UnifiedCreationWorkspace` å’Œ `UnifiedGenerationPage`:`UnifiedCreationWorkspace` 作为平å°å£³å”¯ä¸€ä¾èµ–的统一创作编排层,å†å†…部调用 `src/components/unified-creation/workspaces/` 下的 `PuzzleCreationWorkspace`ã€`Match3DCreationWorkspace`ã€`JumpHopCreationWorkspace` å’Œ `WoodenFishCreationWorkspace`;创作页字段清å•ç”±åŽç«¯åœ¨ `GET /api/creation-entry/config` çš„ `creationTypes[].unifiedCreationSpec` 下å‘,å‰ç«¯ä»…在该扩展ä½ç¼ºå¤±æ—¶å›žé€€åˆ°æœ¬åœ°é»˜è®¤ spec;首期字段类型åªä¿ç•™ `text`ã€`select`ã€`image`ã€`audio`。`UnifiedCreationPage` æä¾›ç»Ÿä¸€æ ‡é¢˜æ ã€ç»Ÿä¸€è¿”回入å£ã€é¡µé¢çº§çºµå‘滚动ã€å†…容区和éšè—字段契约,ä¸åœ¨ UI 中é¢å¤–展示字段说明 chip;竖å±ç§»åŠ¨ç«¯å¿…é¡»èƒ½ä»Žæ ‡é¢˜ã€è¡¨å•一路滑到æäº¤æŒ‰é’®ã€‚å„玩法工作å°è´Ÿè´£æ¸²æŸ“真实输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ï¼Œä½†è¿”回按钮åªä¿ç•™åœ¨ç»Ÿä¸€é¡µå¤´ï¼Œå·¥ä½œå°å†…部ä¸å†é‡å¤æ¸²æŸ“ã€‚æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼çš„工作å°å®žçŽ°éƒ½å·²æ”¶å£åˆ°ç»Ÿä¸€ç›®å½•,åªä¿ç•™å„自输入逻辑ã€ç´ æé€‰æ‹©å’Œæäº¤æ ¡éªŒï¼Œä¸å†ç”±å¹³å°å£³ç›´æŽ¥ä¾èµ–æ—§å·¥ä½œå°æ–‡ä»¶ã€‚æ•²æœ¨é±¼çš„éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡é¢æ¿ä¸å¾—放进独立内部滚动容器,移动端应跟éšé¡µé¢è‡ªç„¶æ»šåŠ¨å±•å¼€ã€‚ç”Ÿæˆé¡µç»Ÿä¸€å±•示阶段ã€å½“剿­¥éª¤ã€æ€»è¿›åº¦ã€é”™è¯¯å’Œé‡è¯•动作。视觉å°è¯´ã€`airp`ã€`component`ã€æ±ªæ±ªå£°æµªã€æ–¹æ´žã€å¤§é±¼å’Œå®è´è¯†ç‰©ä¸è¿›å…¥ä¸€æœŸæŽ¥çº¿èŒƒå›´ï¼Œå·²æœ‰é“¾è·¯ä¿æŒçŽ°çŠ¶ã€‚ +统一创作入å£è¦†ç›–当å‰å¯è¿›å…¥åˆ›ä½œé“¾è·¯çš„已有模æ¿ï¼š`rpg`ã€`big-fish`ã€`puzzle`ã€`match3d`ã€`jump-hop`ã€`wooden-fish`ã€`square-hole`ã€`bark-battle`ã€`visual-novel`ã€`baby-object-match` å’Œ `creative-agent`ï¼›`airp` 仿˜¯æœªå¼€æ”¾å ä½ï¼Œä¸ä½œä¸ºå½“å‰ç»Ÿä¸€åˆ›ä½œé“¾è·¯ç›®æ ‡ã€‚æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼åœ¨å‰ç«¯ç»§ç»­ç»è¿‡ `UnifiedCreationWorkspace` å’Œ `UnifiedGenerationPage`:`UnifiedCreationWorkspace` 作为平å°å£³ä¾èµ–的统一创作编排层,å†å†…部调用 `src/components/unified-creation/workspaces/` 下的 `PuzzleCreationWorkspace`ã€`Match3DCreationWorkspace`ã€`JumpHopCreationWorkspace` å’Œ `WoodenFishCreationWorkspace`。其它已有模æ¿ç”±å¹³å°å£³ç”¨ `UnifiedCreationPage` åŒ…ä½æ—¢æœ‰å·¥ä½œå°ï¼Œå¤ç”¨ç»Ÿä¸€æ ‡é¢˜æ ã€è¿”回入å£ã€é¡µé¢çº§çºµå‘滚动和éšè—å­—æ®µå¥‘çº¦ï¼ŒåŒæ—¶ä¿ç•™å„玩法自己的表å•ã€è‰ç¨¿æ¢å¤å’ŒåŽç»­ç¼–排。创作页字段清å•ç”±åŽç«¯åœ¨ `GET /api/creation-entry/config` çš„ `creationTypes[].unifiedCreationSpec` 下å‘,å‰ç«¯ä»…在该扩展ä½ç¼ºå¤±æ—¶å›žé€€åˆ°æœ¬åœ°é»˜è®¤ spec;字段类型åªä¿ç•™ `text`ã€`select`ã€`image`ã€`audio`。`UnifiedCreationPage` ä¸åœ¨ UI 中é¢å¤–展示字段说明 chip,也ä¸åœ¨å³ä¸Šè§’显示内部 `playId`ã€æ¨¡æ¿ ID 或工作å°é˜¶æ®µå;竖å±ç§»åŠ¨ç«¯å¿…é¡»èƒ½ä»Žæ ‡é¢˜ã€è¡¨å•一路滑到æäº¤æŒ‰é’®ã€‚å„玩法工作å°è´Ÿè´£æ¸²æŸ“真实输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ï¼Œä½†è¿”回按钮åªä¿ç•™åœ¨ç»Ÿä¸€é¡µå¤´ï¼Œå·¥ä½œå°å†…部ä¸å†é‡å¤æ¸²æŸ“。暗色创作进度å¡ç‰‡ä½äºŽ `platform-remap-surface` 内时,必须用组件专属 class 覆盖浅色主题 remap,确ä¿ç™½å­—ã€æµ…色边框和进度æ¡åº•色ä¸ä¼šè¢«å…¨å±€è§„åˆ™æ”¹æˆæ·±è‰²ï¼›ä¸è¦åªä¾èµ–通用 `text-white*` ç±»ã€‚æ•²æœ¨é±¼çš„éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡é¢æ¿ä¸å¾—放进独立内部滚动容器,移动端应跟éšé¡µé¢è‡ªç„¶æ»šåŠ¨å±•å¼€ã€‚ç”Ÿæˆé¡µç»Ÿä¸€å±•示阶段ã€å½“剿­¥éª¤ã€æ€»è¿›åº¦ã€é”™è¯¯å’Œé‡è¯•动作。 åˆ›ä½œè¡¨å•æäº¤å‰çš„æ³¥ç‚¹ä½™é¢å‰ç½®æ ¡éªŒåªå…许用独立弹窗æç¤ºå¤±è´¥åŽŸå› ï¼Œä¸å¾—æŠŠç”¨æˆ·é€€å›žåˆ›ä½œå…¥å£æˆ–玩法模æ¿åˆ—表,也ä¸å¾—清空当å‰è¡¨å•状æ€ã€‚当å‰é€‚ç”¨æ‹¼å›¾ã€æŠ“å¤§é¹…å’Œæ±ªæ±ªå£°æµªç­‰ä¼šåœ¨å‰ç«¯æäº¤å‰æ ¡éªŒæ³¥ç‚¹çš„生æˆå…¥å£ï¼›ä½™é¢ä¸è¶³ã€ä½™é¢è¯»å–失败都应åœç•™åœ¨å½“å‰å·¥ä½œå°ï¼Œç”±ç”¨æˆ·å…³é—­æç¤ºåŽç»§ç»­ç¼–辑或自行补足泥点。 @@ -20,7 +20,7 @@ `PlatformEntryFlowShellImpl.tsx` 仿˜¯å¹³å°å…¥å£ç¼–排壳,åŽç»­ç»´æŠ¤æ—¶åº”优先把独立 UI 片段ã€å…¬å¼€ä½œå“映射ã€è‰ç¨¿ç”Ÿæˆ notice å’Œè¿è¡Œæ€çŠ¶æ€ helper 拆到 `src/components/platform-entry/PlatformEntryFlowShellImpl/` 或åŒç›®å½•ç´§é‚» helper 文件。拆分åªå…è®¸æ”¹å˜æ–‡ä»¶ç»„ç»‡ï¼Œä¸æ”¹å˜å…¥å£é…置事实æºã€é»˜è®¤å¯¼å‡ºã€propsã€é¡µé¢é˜¶æ®µã€UI 文案或现有交互;其中拼图首访 onboarding 已拆为 `PlatformEntryFlowShellImpl/PuzzleOnboardingView.tsx`。 -`platformEntryCreationTypes.ts` åªåšå‰ç«¯å±•示派生,分组时必须把åŽç«¯ `creationTypes` 里的 `categoryId` / `categoryLabel` 当作å¯ç¼ºå¤±å­—段处ç†ï¼Œç©ºå€¼ç»Ÿä¸€å›žé€€åˆ° `recommended` / `热门推è`ï¼Œå¹¶æŠŠåŽ†å² `recent` / `最近创作` 归一到推è分类。`最近创作` ä¸å±žäºŽæ¨¡æ¿åˆ†ç±»é¡µç­¾ï¼Œåªèƒ½ç”±çœŸå®žè‰ç¨¿ / ä½œå“æž¶åŽç«¯æ•°æ®å†³å®šæ˜¯å¦å±•示。 +`platformEntryCreationTypes.ts` åªåšå‰ç«¯å±•示派生,分组时必须把åŽç«¯ `creationTypes` 里的 `categoryId` / `categoryLabel` 当作å¯ç¼ºå¤±å­—段处ç†ï¼Œç©ºå€¼ç»Ÿä¸€å›žé€€åˆ° `recommended` / `热门推è`ï¼Œå¹¶æŠŠåŽ†å² `recent` / `最近创作` 归一到推è分类。`最近创作` ä¸å±žäºŽæ¨¡æ¿åˆ†ç±»é¡µç­¾ï¼Œåªèƒ½ç”± 7 天内的真实è‰ç¨¿ / ä½œå“æž¶åŽç«¯æ•°æ®å†³å®šæ˜¯å¦å±•示;展示内容ä»ç„¶ä»ŽåŽç«¯å…¥å£é…置的模æ¿å¡ä¸­ç­›é€‰ï¼Œä¸è¯»å–æˆ–æ¸²æŸ“ä½œå“æ ‡é¢˜ã€ä½œå“摘è¦ã€è‰ç¨¿é˜¶æ®µæ–‡æ¡ˆã€‚ ç§»åŠ¨ç«¯åº•éƒ¨ä¸€çº§å¯¼èˆªæ˜¯å…¨å±€å¹³å°æ ·å¼ï¼Œä¸æŒ‰å•一玩法分å‰ã€‚当å‰è§†è§‰ç»Ÿä¸€ä¸ºç±³ç™½æµ®åŠ¨èƒ¶å›Šåº•åº§ã€æµ…æ£•åˆ†éš”çº¿ã€æ£•è‰²çº¿æ€§å›¾æ ‡ã€æ©˜è‰²é€‰ä¸­æ€å’Œåº•部短下划线;中间 `创作` å…¥å£ä¿æŒå‡¸èµ·åœ†å½¢ä¸»æŒ‰é’®ï¼Œä½†å‡¸èµ·ä½ç§»åªèƒ½ä½œç”¨åœ¨æŒ‰é’®å†…容层,ä¸èƒ½ç§»åŠ¨æ‰¿è½½åˆ†éš”çº¿çš„ Tab 按钮容器,确ä¿åˆ›ä½œå·¦å³åˆ†éš”线与其他分隔线垂直ä½ç½®ä¸€è‡´ã€‚Tab åç§°å’Œå¯è§æ€§ä»ç”±çŽ°æœ‰ `PlatformHomeTab` / 登录æ€è§„则决定,样å¼è°ƒæ•´ä¸å¾—改写 Tab 文案或导航状æ€ã€‚ diff --git a/server-rs/crates/shared-contracts/src/creation_entry_config.rs b/server-rs/crates/shared-contracts/src/creation_entry_config.rs index 5f1cacad..03525884 100644 --- a/server-rs/crates/shared-contracts/src/creation_entry_config.rs +++ b/server-rs/crates/shared-contracts/src/creation_entry_config.rs @@ -102,6 +102,18 @@ pub const UNIFIED_CREATION_FIELD_KINDS: [&str; 4] = ["text", "select", "image", pub fn build_phase1_unified_creation_spec(play_id: &str) -> Option { let (workspace_stage, generation_stage, result_stage, fields) = match play_id { + "rpg" => ( + "agent-workspace", + "custom-world-generating", + "custom-world-result", + vec![unified_creation_field("message", "text", "创作想法", true)], + ), + "big-fish" => ( + "big-fish-agent-workspace", + "big-fish-generating", + "big-fish-result", + vec![unified_creation_field("message", "text", "玩法想法", true)], + ), "puzzle" => ( "puzzle-agent-workspace", "puzzle-generating", @@ -147,6 +159,62 @@ pub fn build_phase1_unified_creation_spec(play_id: &str) -> Option ( + "square-hole-agent-workspace", + "square-hole-generating", + "square-hole-result", + vec![unified_creation_field("message", "text", "玩法想法", true)], + ), + "bark-battle" => ( + "bark-battle-workspace", + "bark-battle-generating", + "bark-battle-result", + vec![ + unified_creation_field("title", "text", "ä½œå“æ ‡é¢˜", true), + unified_creation_field("themeDescription", "text", "主题/场景æè¿°", true), + unified_creation_field( + "playerImageDescription", + "text", + "玩家形象æè¿°", + true, + ), + unified_creation_field( + "opponentImageDescription", + "text", + "对手形象æè¿°", + true, + ), + unified_creation_field("onomatopoeia", "text", "拟声è¯", false), + unified_creation_field("difficultyPreset", "select", "难度", true), + ], + ), + "visual-novel" => ( + "visual-novel-agent-workspace", + "visual-novel-generating", + "visual-novel-result", + vec![ + unified_creation_field("ideaText", "text", "一å¥è¯åˆ›ä½œ", true), + unified_creation_field("visualStyleId", "select", "视觉画风", true), + ], + ), + "baby-object-match" => ( + "baby-object-match-workspace", + "baby-object-match-generating", + "baby-object-match-result", + vec![ + unified_creation_field("itemAName", "text", "ç‰©å“ A", true), + unified_creation_field("itemBName", "text", "ç‰©å“ B", true), + ], + ), + "creative-agent" => ( + "creative-agent-workspace", + "puzzle-generating", + "puzzle-result", + vec![ + unified_creation_field("message", "text", "创作想法", true), + unified_creation_field("referenceImage", "image", "å‚考图", false), + ], + ), _ => return None, }; @@ -268,7 +336,7 @@ mod tests { use super::*; #[test] - fn phase1_unified_creation_specs_cover_four_templates() { + fn phase1_unified_creation_specs_cover_existing_templates() { let puzzle = build_phase1_unified_creation_spec("puzzle").expect("puzzle spec"); assert_eq!(puzzle.fields[0].id, "pictureDescription"); assert_eq!(puzzle.fields[1].kind, "image"); @@ -300,8 +368,25 @@ mod tests { let wooden_fish = build_phase1_unified_creation_spec("wooden-fish").expect("wooden-fish spec"); assert!(wooden_fish.fields.iter().any(|field| field.kind == "audio")); - assert!(build_phase1_unified_creation_spec("visual-novel").is_none()); - assert!(build_phase1_unified_creation_spec("bark-battle").is_none()); + + let visual_novel = + build_phase1_unified_creation_spec("visual-novel").expect("visual-novel spec"); + assert_eq!(visual_novel.workspace_stage, "visual-novel-agent-workspace"); + + let bark_battle = + build_phase1_unified_creation_spec("bark-battle").expect("bark-battle spec"); + assert_eq!(bark_battle.generation_stage, "bark-battle-generating"); + + let baby_object_match = build_phase1_unified_creation_spec("baby-object-match") + .expect("baby-object-match spec"); + assert_eq!( + baby_object_match + .fields + .iter() + .filter(|field| field.kind == "text") + .count(), + 2 + ); } #[test] diff --git a/src/components/big-fish-creation/BigFishAgentWorkspace.tsx b/src/components/big-fish-creation/BigFishAgentWorkspace.tsx index f6578817..c650a34f 100644 --- a/src/components/big-fish-creation/BigFishAgentWorkspace.tsx +++ b/src/components/big-fish-creation/BigFishAgentWorkspace.tsx @@ -26,6 +26,7 @@ type BigFishAgentWorkspaceProps = { onBack: () => void; onSubmitMessage: (payload: SendBigFishMessageRequest) => void; onExecuteAction: (payload: ExecuteBigFishActionRequest) => void; + showBackButton?: boolean; }; const BIG_FISH_AGENT_THEME: CreationAgentTheme = { @@ -87,6 +88,7 @@ export function BigFishAgentWorkspace({ onBack, onSubmitMessage, onExecuteAction, + showBackButton = true, }: BigFishAgentWorkspaceProps) { return ( { onSubmitMessage( diff --git a/src/components/creation-agent/CreationAgentWorkspace.test.tsx b/src/components/creation-agent/CreationAgentWorkspace.test.tsx index b5c9b4b2..2ceaa232 100644 --- a/src/components/creation-agent/CreationAgentWorkspace.test.tsx +++ b/src/components/creation-agent/CreationAgentWorkspace.test.tsx @@ -359,7 +359,15 @@ test('creation agent workspace hides hero copy area when title and summary are a ); expect(screen.queryByText('统一共创')).toBeNull(); - expect(screen.getByText('创作进度')).toBeTruthy(); + expect(screen.getByText('创作进度').className).toContain( + 'creation-agent-hero__progress-label', + ); + expect(screen.getByText('60%').className).toContain( + 'creation-agent-hero__progress-value', + ); + expect(screen.getByText(/æ–¹å‘å·²ç»æˆå½¢/u).className).toContain( + 'creation-agent-hero__progress-hint', + ); }); test('creation agent workspace stops auto-follow when user scrolls away from bottom', () => { diff --git a/src/components/creation-agent/CreationAgentWorkspace.tsx b/src/components/creation-agent/CreationAgentWorkspace.tsx index 1bfa19e1..50c93d3d 100644 --- a/src/components/creation-agent/CreationAgentWorkspace.tsx +++ b/src/components/creation-agent/CreationAgentWorkspace.tsx @@ -78,6 +78,7 @@ type CreationAgentWorkspaceProps = { referenceImagePreviewSrc?: string | null; referenceImageLabel?: string | null; referenceImageError?: string | null; + showBackButton?: boolean; onBack: () => void; onSubmitText: (text: string, quickActionKey?: string) => void; onPrimaryAction: () => void; @@ -299,6 +300,7 @@ export function CreationAgentWorkspace({ referenceImagePreviewSrc = null, referenceImageLabel = null, referenceImageError = null, + showBackButton = true, onBack, onSubmitText, onPrimaryAction, @@ -465,18 +467,22 @@ export function CreationAgentWorkspace({ return (
- + {showBackButton ? ( + + ) : ( +
{referenceImagePreviewSrc ? ( -
+
void; + showBackButton?: boolean; onSubmitMessage: (payload: { clientMessageId: string; content: CreativeAgentInputPart[]; @@ -101,6 +102,7 @@ export function CreativeAgentWorkspace({ error, eventLog, onBack, + showBackButton = true, onSubmitMessage, onConfirmTemplate, onCancelTemplate, @@ -131,17 +133,21 @@ export function CreativeAgentWorkspace({ return (
- + {showBackButton ? ( + + ) : ( +
- ) : ( -
- {visibleCreationTypes.map((item) => { - const disabled = item.locked || busy; - - return ( - - ); - })} -
- )} + ); + })} +
); diff --git a/src/components/custom-world-home/creationWorkShelf.ts b/src/components/custom-world-home/creationWorkShelf.ts index 600694a2..1b12420f 100644 --- a/src/components/custom-world-home/creationWorkShelf.ts +++ b/src/components/custom-world-home/creationWorkShelf.ts @@ -1113,7 +1113,7 @@ function isPersistedCreationWorkGenerating(item: CreationWorkShelfItem) { case 'match3d': return item.source.item.generationStatus === 'generating'; case 'jump-hop': - // 中文注释:跳一跳åŽç«¯ç”Ÿæˆä¸­è‰ç¨¿ä¹Ÿè¦åŒæ­¥åˆ°ä½œå“架与最近创作状æ€ã€‚ + // 中文注释:跳一跳åŽç«¯ç”Ÿæˆä¸­è‰ç¨¿ä¹Ÿè¦åŒæ­¥åˆ°ä½œå“架,并å‚ä¸Žæœ€è¿‘æ¨¡æ¿æŽ¨å¯¼ã€‚ return item.source.item.generationStatus === 'generating'; case 'puzzle': return isPersistedPuzzleDraftGenerating(item.source.item); diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index ba98a989..b305027c 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -222,7 +222,11 @@ import { type MiniGameDraftGenerationState, } from '../../services/miniGameDraftGenerationProgress'; import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient'; -import { getUnifiedCreationSpec } from '../unified-creation/unifiedCreationSpecs'; +import { UnifiedCreationPage } from '../unified-creation/UnifiedCreationPage'; +import { + getUnifiedCreationSpec, + type UnifiedCreationPlayId, +} from '../unified-creation/unifiedCreationSpecs'; import { buildBabyObjectMatchPublicWorkCode, buildBarkBattlePublicWorkCode, @@ -3825,6 +3829,11 @@ export function PlatformEntryFlowShellImpl({ const entries = creationEntryConfig?.creationTypes ?? []; return new Map(entries.map((entry) => [entry.id, entry])); }, [creationEntryConfig]); + const getUnifiedSpec = useCallback( + (playId: UnifiedCreationPlayId) => + getUnifiedCreationSpec(playId, unifiedCreationConfigById.get(playId)), + [unifiedCreationConfigById], + ); const isBigFishCreationVisible = isPlatformCreationTypeVisible( creationEntryTypes, 'big-fish', @@ -16614,30 +16623,42 @@ export function PlatformEntryFlowShellImpl({ } > - {sessionController.agentSession ? ( - { - void sessionController.submitAgentMessage(payload); - }} - onExecuteAction={(payload) => { - void sessionController.executeAgentAction(payload); - }} - /> - ) : ( -
-
- {sessionController.isLoadingAgentSession - ? '正在准备 Agent 共创工作区...' - : sessionController.agentWorkspaceRestoreError || - '正在æ¢å¤åˆ›ä½œå·¥ä½œåŒº...'} + + {sessionController.agentSession ? ( + { + void sessionController.submitAgentMessage(payload); + }} + onExecuteAction={(payload) => { + void sessionController.executeAgentAction(payload); + }} + /> + ) : ( +
+
+ {sessionController.isLoadingAgentSession + ? '正在准备 Agent 共创工作区...' + : sessionController.agentWorkspaceRestoreError || + '正在æ¢å¤åˆ›ä½œå·¥ä½œåŒº...'} +
-
- )} + )} + )} @@ -16655,20 +16676,27 @@ export function PlatformEntryFlowShellImpl({ } > - { - void submitBigFishMessage(payload); - }} - onExecuteAction={(payload) => { - void executeBigFishAction(payload); - }} - /> + isBackDisabled={isBigFishBusy || isStreamingBigFishReply} + > + { + void submitBigFishMessage(payload); + }} + onExecuteAction={(payload) => { + void executeBigFishAction(payload); + }} + /> + )} @@ -16807,10 +16835,7 @@ export function PlatformEntryFlowShellImpl({ > } > - { - void createBabyObjectMatchDraftFromForm(payload); - }} - /> + isBackDisabled={isBabyObjectMatchBusy} + > + { + void createBabyObjectMatchDraftFromForm(payload); + }} + /> + )} @@ -17219,14 +17252,22 @@ export function PlatformEntryFlowShellImpl({ } > - { - void createBarkBattleGeneratingDraft(payload); - }} - /> + isBackDisabled={isBarkBattleBusy} + > + { + void createBarkBattleGeneratingDraft(payload); + }} + /> + )} @@ -17244,20 +17285,27 @@ export function PlatformEntryFlowShellImpl({ } > - { - void submitSquareHoleMessage(payload); - }} - onExecuteAction={(payload) => { - void executeSquareHoleAction(payload); - }} - /> + isBackDisabled={isSquareHoleBusy || isStreamingSquareHoleReply} + > + { + void submitSquareHoleMessage(payload); + }} + onExecuteAction={(payload) => { + void executeSquareHoleAction(payload); + }} + /> + )} @@ -17462,10 +17510,7 @@ export function PlatformEntryFlowShellImpl({ > } > - { - void submitCreativeAgentMessage(payload); - }} - onConfirmTemplate={(selection) => { - void confirmCreativeTemplateSelection(selection); - }} - onOpenTarget={() => { - void openCreativeAgentTarget(); - }} - /> + isBackDisabled={isCreativeAgentBusy || isPuzzleBusy} + > + { + void submitCreativeAgentMessage(payload); + }} + onConfirmTemplate={(selection) => { + void confirmCreativeTemplateSelection(selection); + }} + onOpenTarget={() => { + void openCreativeAgentTarget(); + }} + /> + )} @@ -17758,10 +17807,7 @@ export function PlatformEntryFlowShellImpl({ > } > - { - void createVisualNovelDraftFromForm(payload); - }} - /> + isBackDisabled={isVisualNovelBusy || isVisualNovelStreamingReply} + > + { + void createVisualNovelDraftFromForm(payload); + }} + /> + )} diff --git a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx index 04a3e7fd..37bfb3ee 100644 --- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx +++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx @@ -3752,14 +3752,14 @@ test('create tab shows template tabs and embeds puzzle form by default', async ( expect(createPuzzleAgentSession).not.toHaveBeenCalled(); }); -test('create tab shows recent tab when backend returns failed drafts', async () => { +test('create tab shows recent template cards when backend returns failed drafts', async () => { const user = userEvent.setup(); mockExistingRpgDraftShelf({ title: 'å…¥å£å¯è§çš„失败è‰ç¨¿', summary: '失败è‰ç¨¿ä¹Ÿè¦è¿›å…¥åˆ›ä½œå…¥å£æœ€è¿‘创作。', stage: 'failed', stageLabel: '生æˆå¤±è´¥å¾…处ç†', - updatedAt: '2026-06-02T10:00:00.000Z', + updatedAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), }); render(); @@ -3775,11 +3775,18 @@ test('create tab shows recent tab when backend returns failed drafts', async () .getByRole('tab', { name: '最近创作' }) .getAttribute('aria-selected'), ).toBe('true'); - expect(await within(panel).findByText('å…¥å£å¯è§çš„失败è‰ç¨¿')).toBeTruthy(); expect( - within(panel).getByText('失败è‰ç¨¿ä¹Ÿè¦è¿›å…¥åˆ›ä½œå…¥å£æœ€è¿‘创作。'), + await within(panel).findByRole('button', { name: /文字冒险/u }), ).toBeTruthy(); - expect(within(panel).getByText('生æˆå¤±è´¥å¾…处ç†')).toBeTruthy(); + expect( + within(panel).getByText('仅显示最近7天内使用过的模æ¿'), + ).toBeTruthy(); + expect(within(panel).getByText('ç»å…¸ RPG 体验')).toBeTruthy(); + expect(within(panel).queryByText('å…¥å£å¯è§çš„失败è‰ç¨¿')).toBeNull(); + expect( + within(panel).queryByText('失败è‰ç¨¿ä¹Ÿè¦è¿›å…¥åˆ›ä½œå…¥å£æœ€è¿‘创作。'), + ).toBeNull(); + expect(within(panel).queryByText('生æˆå¤±è´¥å¾…处ç†')).toBeNull(); }); test('create tab refreshes recent works after opening from an empty draft shelf', async () => { @@ -3789,7 +3796,7 @@ test('create tab refreshes recent works after opening from an empty draft shelf' summary: '创作入å£éœ€è¦åœ¨è¿›å…¥æ—¶é‡æ–°è¯»å–çœŸå®žä½œå“æž¶ã€‚', stage: 'error', stageLabel: 'å‘生错误', - updatedAt: '2026-06-02T10:30:00.000Z', + updatedAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), }); vi.mocked(listRpgCreationWorks) .mockResolvedValueOnce([]) @@ -3803,11 +3810,18 @@ test('create tab refreshes recent works after opening from an empty draft shelf' await clickFirstButtonByName(user, '创作'); const panel = getPlatformTabPanel('create'); - expect(await within(panel).findByText('点击创作åŽå‡ºçŽ°çš„å¤±è´¥è‰ç¨¿')).toBeTruthy(); expect( - within(panel).getByText('创作入å£éœ€è¦åœ¨è¿›å…¥æ—¶é‡æ–°è¯»å–çœŸå®žä½œå“æž¶ã€‚'), + await within(panel).findByRole('button', { name: /文字冒险/u }), ).toBeTruthy(); - expect(within(panel).getByText('å‘生错误')).toBeTruthy(); + expect( + within(panel).getByText('仅显示最近7天内使用过的模æ¿'), + ).toBeTruthy(); + expect(within(panel).getByText('ç»å…¸ RPG 体验')).toBeTruthy(); + expect(within(panel).queryByText('点击创作åŽå‡ºçŽ°çš„å¤±è´¥è‰ç¨¿')).toBeNull(); + expect( + within(panel).queryByText('创作入å£éœ€è¦åœ¨è¿›å…¥æ—¶é‡æ–°è¯»å–çœŸå®žä½œå“æž¶ã€‚'), + ).toBeNull(); + expect(within(panel).queryByText('å‘生错误')).toBeNull(); await waitFor(() => { expect( vi.mocked(listRpgCreationWorks).mock.calls.length, diff --git a/src/components/square-hole-creation/SquareHoleAgentWorkspace.tsx b/src/components/square-hole-creation/SquareHoleAgentWorkspace.tsx index b8bbccad..4f7f254d 100644 --- a/src/components/square-hole-creation/SquareHoleAgentWorkspace.tsx +++ b/src/components/square-hole-creation/SquareHoleAgentWorkspace.tsx @@ -26,6 +26,7 @@ type SquareHoleAgentWorkspaceProps = { onBack: () => void; onSubmitMessage: (payload: SendSquareHoleMessageRequest) => void; onExecuteAction: (payload: ExecuteSquareHoleActionRequest) => void; + showBackButton?: boolean; }; const SQUARE_HOLE_AGENT_THEME: CreationAgentTheme = { @@ -108,6 +109,7 @@ export function SquareHoleAgentWorkspace({ onBack, onSubmitMessage, onExecuteAction, + showBackButton = true, }: SquareHoleAgentWorkspaceProps) { return ( { onSubmitMessage(buildSquareHoleChatPayload({ text })); diff --git a/src/components/unified-creation/UnifiedCreationPage.test.tsx b/src/components/unified-creation/UnifiedCreationPage.test.tsx index ec6c0c07..5f47c6fa 100644 --- a/src/components/unified-creation/UnifiedCreationPage.test.tsx +++ b/src/components/unified-creation/UnifiedCreationPage.test.tsx @@ -43,9 +43,7 @@ describe('UnifiedCreationPage', () => { ]); expect(fields[2]?.getAttribute('data-field-kind')).toBe('audio'); expect(fields[3]?.getAttribute('data-required')).toBe('true'); - expect(screen.getByTestId('unified-creation-play-badge').textContent).toBe( - 'wooden-fish', - ); + expect(screen.queryByTestId('unified-creation-play-badge')).toBeNull(); fireEvent.click(screen.getByRole('button', { name: '返回' })); expect(onBack).toHaveBeenCalledTimes(1); expect(screen.queryByLabelText('创作字段')).toBeNull(); diff --git a/src/components/unified-creation/UnifiedCreationPage.tsx b/src/components/unified-creation/UnifiedCreationPage.tsx index ddfc2580..1f027c06 100644 --- a/src/components/unified-creation/UnifiedCreationPage.tsx +++ b/src/components/unified-creation/UnifiedCreationPage.tsx @@ -26,7 +26,7 @@ export function UnifiedCreationPage({ data-result-stage={spec.resultStage} >
-
+
{onBack ? (

diff --git a/src/components/unified-creation/UnifiedGenerationPage.tsx b/src/components/unified-creation/UnifiedGenerationPage.tsx index b15c5d1e..95ea551f 100644 --- a/src/components/unified-creation/UnifiedGenerationPage.tsx +++ b/src/components/unified-creation/UnifiedGenerationPage.tsx @@ -1,11 +1,11 @@ import type { CustomWorldGenerationProgress } from '../../../packages/shared/src/contracts/runtime'; import type { CustomWorldStructuredAnchorEntry } from '../../services/customWorldAgentGenerationProgress'; import { CustomWorldGenerationView } from '../CustomWorldGenerationView'; -import type { UnifiedCreationPlayId } from './unifiedCreationSpecs'; import { getUnifiedGenerationCopy } from './unifiedGenerationCopy'; +import type { UnifiedGenerationPlayId } from './unifiedGenerationCopy'; type UnifiedGenerationPageProps = { - playId: UnifiedCreationPlayId; + playId: UnifiedGenerationPlayId; settingText: string; anchorEntries?: CustomWorldStructuredAnchorEntry[]; progress: CustomWorldGenerationProgress | null; diff --git a/src/components/unified-creation/unifiedCreationSpecs.test.ts b/src/components/unified-creation/unifiedCreationSpecs.test.ts index cebb5657..8e6daa98 100644 --- a/src/components/unified-creation/unifiedCreationSpecs.test.ts +++ b/src/components/unified-creation/unifiedCreationSpecs.test.ts @@ -6,9 +6,21 @@ import { } from './unifiedCreationSpecs'; describe('unified creation specs', () => { - test('统一壳当å‰è¦†ç›–æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼', () => { + test('统一壳覆盖所有已有创作模æ¿å·¥ä½œå°', () => { expect(listUnifiedCreationSpecs().map((spec) => spec.playId).sort()).toEqual( - ['jump-hop', 'match3d', 'puzzle', 'wooden-fish'], + [ + 'baby-object-match', + 'bark-battle', + 'big-fish', + 'creative-agent', + 'jump-hop', + 'match3d', + 'puzzle', + 'rpg', + 'square-hole', + 'visual-novel', + 'wooden-fish', + ], ); }); @@ -22,7 +34,12 @@ describe('unified creation specs', () => { expect([...fieldKinds].sort()).toEqual(['audio', 'image', 'select', 'text']); }); - test('å››æ¡é“¾è·¯éƒ½æ˜ å°„到统一创作ã€ç”Ÿæˆã€ç»“果阶段', () => { + test('主è¦é“¾è·¯éƒ½æ˜ å°„到统一创作ã€ç”Ÿæˆã€ç»“果阶段', () => { + expect(getUnifiedCreationSpec('rpg')).toMatchObject({ + workspaceStage: 'agent-workspace', + generationStage: 'custom-world-generating', + resultStage: 'custom-world-result', + }); expect(getUnifiedCreationSpec('puzzle')).toMatchObject({ workspaceStage: 'puzzle-agent-workspace', generationStage: 'puzzle-generating', @@ -43,5 +60,20 @@ describe('unified creation specs', () => { generationStage: 'wooden-fish-generating', resultStage: 'wooden-fish-result', }); + expect(getUnifiedCreationSpec('bark-battle')).toMatchObject({ + workspaceStage: 'bark-battle-workspace', + generationStage: 'bark-battle-generating', + resultStage: 'bark-battle-result', + }); + expect(getUnifiedCreationSpec('visual-novel')).toMatchObject({ + workspaceStage: 'visual-novel-agent-workspace', + generationStage: 'visual-novel-generating', + resultStage: 'visual-novel-result', + }); + expect(getUnifiedCreationSpec('baby-object-match')).toMatchObject({ + workspaceStage: 'baby-object-match-workspace', + generationStage: 'baby-object-match-generating', + resultStage: 'baby-object-match-result', + }); }); }); diff --git a/src/components/unified-creation/unifiedCreationSpecs.ts b/src/components/unified-creation/unifiedCreationSpecs.ts index 170ef6e2..2996ea9d 100644 --- a/src/components/unified-creation/unifiedCreationSpecs.ts +++ b/src/components/unified-creation/unifiedCreationSpecs.ts @@ -3,13 +3,58 @@ import type { UnifiedCreationSpec, } from '../../services/creationEntryConfigService'; -export type UnifiedCreationPlayId = UnifiedCreationSpec['playId']; +export const UNIFIED_CREATION_PLAY_IDS = [ + 'rpg', + 'big-fish', + 'puzzle', + 'match3d', + 'jump-hop', + 'wooden-fish', + 'square-hole', + 'bark-battle', + 'visual-novel', + 'baby-object-match', + 'creative-agent', +] as const; + +export type UnifiedCreationPlayId = + (typeof UNIFIED_CREATION_PLAY_IDS)[number]; export type { UnifiedCreationSpec }; const FALLBACK_UNIFIED_CREATION_SPECS: Record< UnifiedCreationPlayId, UnifiedCreationSpec > = { + rpg: { + playId: 'rpg', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'agent-workspace', + generationStage: 'custom-world-generating', + resultStage: 'custom-world-result', + fields: [ + { + id: 'message', + kind: 'text', + label: '创作想法', + required: true, + }, + ], + }, + 'big-fish': { + playId: 'big-fish', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'big-fish-agent-workspace', + generationStage: 'big-fish-generating', + resultStage: 'big-fish-result', + fields: [ + { + id: 'message', + kind: 'text', + label: '玩法想法', + required: true, + }, + ], + }, puzzle: { playId: 'puzzle', title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', @@ -148,12 +193,135 @@ const FALLBACK_UNIFIED_CREATION_SPECS: Record< }, ], }, + 'square-hole': { + playId: 'square-hole', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'square-hole-agent-workspace', + generationStage: 'square-hole-generating', + resultStage: 'square-hole-result', + fields: [ + { + id: 'message', + kind: 'text', + label: '玩法想法', + required: true, + }, + ], + }, + 'bark-battle': { + playId: 'bark-battle', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'bark-battle-workspace', + generationStage: 'bark-battle-generating', + resultStage: 'bark-battle-result', + fields: [ + { + id: 'title', + kind: 'text', + label: 'ä½œå“æ ‡é¢˜', + required: true, + }, + { + id: 'themeDescription', + kind: 'text', + label: '主题/场景æè¿°', + required: true, + }, + { + id: 'playerImageDescription', + kind: 'text', + label: '玩家形象æè¿°', + required: true, + }, + { + id: 'opponentImageDescription', + kind: 'text', + label: '对手形象æè¿°', + required: true, + }, + { + id: 'onomatopoeia', + kind: 'text', + label: '拟声è¯', + required: false, + }, + { + id: 'difficultyPreset', + kind: 'select', + label: '难度', + required: true, + }, + ], + }, + 'visual-novel': { + playId: 'visual-novel', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'visual-novel-agent-workspace', + generationStage: 'visual-novel-generating', + resultStage: 'visual-novel-result', + fields: [ + { + id: 'ideaText', + kind: 'text', + label: '一å¥è¯åˆ›ä½œ', + required: true, + }, + { + id: 'visualStyleId', + kind: 'select', + label: '视觉画风', + required: true, + }, + ], + }, + 'baby-object-match': { + playId: 'baby-object-match', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'baby-object-match-workspace', + generationStage: 'baby-object-match-generating', + resultStage: 'baby-object-match-result', + fields: [ + { + id: 'itemAName', + kind: 'text', + label: 'ç‰©å“ A', + required: true, + }, + { + id: 'itemBName', + kind: 'text', + label: 'ç‰©å“ B', + required: true, + }, + ], + }, + 'creative-agent': { + playId: 'creative-agent', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'creative-agent-workspace', + generationStage: 'puzzle-generating', + resultStage: 'puzzle-result', + fields: [ + { + id: 'message', + kind: 'text', + label: '创作想法', + required: true, + }, + { + id: 'referenceImage', + kind: 'image', + label: 'å‚考图', + required: false, + }, + ], + }, }; export function getUnifiedCreationSpec( playId: UnifiedCreationPlayId, configType?: CreationEntryTypeConfig | null, -) { +): UnifiedCreationSpec { return ( configType?.unifiedCreationSpec ?? FALLBACK_UNIFIED_CREATION_SPECS[playId] ); diff --git a/src/components/unified-creation/unifiedGenerationCopy.ts b/src/components/unified-creation/unifiedGenerationCopy.ts index 4b3bdd2d..ec02f145 100644 --- a/src/components/unified-creation/unifiedGenerationCopy.ts +++ b/src/components/unified-creation/unifiedGenerationCopy.ts @@ -1,5 +1,10 @@ import type { UnifiedCreationPlayId } from './unifiedCreationSpecs'; +export type UnifiedGenerationPlayId = Extract< + UnifiedCreationPlayId, + 'puzzle' | 'match3d' | 'jump-hop' | 'wooden-fish' +>; + const UNIFIED_GENERATION_COPY = { puzzle: { retryLabel: '釿–°ç”Ÿæˆå›¾ç‰‡', @@ -26,7 +31,7 @@ const UNIFIED_GENERATION_COPY = { activeBadgeLabel: 'ç´ æç”Ÿæˆä¸­', }, } as const satisfies Record< - UnifiedCreationPlayId, + UnifiedGenerationPlayId, { retryLabel: string; settingTitle: string; @@ -35,6 +40,6 @@ const UNIFIED_GENERATION_COPY = { } >; -export function getUnifiedGenerationCopy(playId: UnifiedCreationPlayId) { +export function getUnifiedGenerationCopy(playId: UnifiedGenerationPlayId) { return UNIFIED_GENERATION_COPY[playId]; } diff --git a/src/index.css b/src/index.css index 8509abaa..4df24a40 100644 --- a/src/index.css +++ b/src/index.css @@ -7096,6 +7096,46 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { color: var(--platform-text-base) !important; } +.platform-theme--light + .platform-remap-surface + .creation-agent-hero + :where( + .creation-agent-hero__icon-button, + .creation-agent-hero__quick-action + ) { + border-color: rgba(255, 255, 255, 0.2) !important; + background: rgba(255, 255, 255, 0.1) !important; + color: rgba(255, 255, 255, 0.82) !important; +} + +.platform-theme--light + .platform-remap-surface + .creation-agent-hero + :where(.creation-agent-hero__summary, .creation-agent-hero__progress-label) { + color: rgba(255, 255, 255, 0.76) !important; +} + +.platform-theme--light + .platform-remap-surface + .creation-agent-hero + .creation-agent-hero__progress-value { + color: rgba(255, 255, 255, 0.92) !important; +} + +.platform-theme--light + .platform-remap-surface + .creation-agent-hero + .creation-agent-hero__progress-hint { + color: rgba(255, 255, 255, 0.72) !important; +} + +.platform-theme--light + .platform-remap-surface + .creation-agent-hero + .creation-agent-hero__progress-track { + background: rgba(255, 255, 255, 0.2) !important; +} + .platform-theme--light .platform-remap-surface :where( diff --git a/src/index.test.ts b/src/index.test.ts index b98f01b5..6a54c2f9 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -80,3 +80,24 @@ describe('index stylesheet draft mobile cards', () => { expect(editableBlock).toContain('-webkit-touch-callout: default;'); }); }); + +describe('index stylesheet creation agent hero contrast', () => { + it('keeps dark creation progress hero text light inside remap surfaces', () => { + const css = readIndexCss(); + + expect(css).toContain('.platform-remap-surface'); + expect(css).toContain('.creation-agent-hero'); + + const labelBlock = getCssBlock( + css, + ':where(.creation-agent-hero__summary, .creation-agent-hero__progress-label)', + ); + expect(labelBlock).toContain('rgba(255, 255, 255, 0.76) !important'); + + const valueBlock = getCssBlock(css, '.creation-agent-hero__progress-value'); + expect(valueBlock).toContain('rgba(255, 255, 255, 0.92) !important'); + + const hintBlock = getCssBlock(css, '.creation-agent-hero__progress-hint'); + expect(hintBlock).toContain('rgba(255, 255, 255, 0.72) !important'); + }); +}); diff --git a/src/services/creationEntryConfigService.ts b/src/services/creationEntryConfigService.ts index bf6e987f..7ca6adef 100644 --- a/src/services/creationEntryConfigService.ts +++ b/src/services/creationEntryConfigService.ts @@ -27,23 +27,11 @@ export type UnifiedCreationField = { /** 统一创作工作å°å¥‘约,把入å£ç±»åž‹æ˜ å°„到工作å°ã€ç”Ÿæˆé¡µå’Œç»“果页阶段。 */ export type UnifiedCreationSpec = { - playId: 'puzzle' | 'match3d' | 'jump-hop' | 'wooden-fish'; + playId: string; title: string; - workspaceStage: - | 'puzzle-agent-workspace' - | 'match3d-agent-workspace' - | 'jump-hop-workspace' - | 'wooden-fish-workspace'; - generationStage: - | 'puzzle-generating' - | 'match3d-generating' - | 'jump-hop-generating' - | 'wooden-fish-generating'; - resultStage: - | 'puzzle-result' - | 'match3d-result' - | 'jump-hop-result' - | 'wooden-fish-result'; + workspaceStage: string; + generationStage: string; + resultStage: string; fields: UnifiedCreationField[]; };