From 52306bf201b80d9246358ac72a31e1dae25e56a5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 18:15:24 +0000 Subject: [PATCH 1/2] =?UTF-8?q?U-2=E2=91=A1:=20=E5=89=8D=E7=AB=AF=E8=B3=87?= =?UTF-8?q?=E8=A8=8A=E5=9C=96=E5=8D=A1=E9=80=90=E5=8D=80=20refine=20?= =?UTF-8?q?=E6=8E=A5=E4=B8=8A=20#81=20=E5=BE=8C=E7=AB=AF=E5=A5=91=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /app 視覺站的逐區微調原送舊形狀 {infographic, sectionId} 並讀 data.data, 與 #81 實際落地的 POST /api/refine-section(收/回單一 section)對不上,逐區 微調實際打不通。改成與 sibling 單頁微調 /api/refine 同形狀:送 section、 讀 data.section、patch 回 result.data.sections[idx]。純前端一檔,vite build 通過。 docs/PRODUCT_READINESS.md U-2 標 [x](①②③到齊)。 https://claude.ai/code/session_011KcsjSFoGvB92odmj4VtKK --- docs/PRODUCT_READINESS.md | 13 ++++++++++--- frontend/edustudio/app.jsx | 11 ++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/PRODUCT_READINESS.md b/docs/PRODUCT_READINESS.md index 75b3533..e0384e5 100644 --- a/docs/PRODUCT_READINESS.md +++ b/docs/PRODUCT_READINESS.md @@ -196,7 +196,7 @@ - [ ] 🔴 **U-1 `/studio` 直連 Gemini 改走後端**(offline,前端 + 確認後端端點)— 把 `/studio` 仍 client-side 呼叫 Gemini 的路徑改打 `/api/generate` 等後端端點(後端大多現成),堵住 「繞過計費 + 繞過審查」漏洞。或者若 U-3 直接退場 /studio,則本項併入「功能搬進 /app」。 -- [~] 🟡 **U-2 `/app` 補齊 `/studio` 缺的視覺功能 — 含逐區 refine**(offline,**拍板要做 +- [x] 🟡 **U-2 `/app` 補齊 `/studio` 缺的視覺功能 — 含逐區 refine**(offline,**拍板要做 2026-06-07**)— 盤點顯示 `/app` 視覺站缺「海報/圖卡逐區 refine、區域選擇」(後端 refine 圖卡未移植 = 唯一「大」缺口)。**定案:移植後端逐區 refine + 前端區域選擇 UI**(不是首發 砍項)。其餘(16 主題/密度/長寬比/自訂 prompt)UI_WIRING 標已接完。拆小:①後端 refine @@ -206,8 +206,15 @@ 避免燒額度,策略對齊既有 `refine_presentation_slide`)+ 新端點 `POST /api/refine-section` (掛 rate_limit)。補 8 測(prompt 組裝 / merge 保留原欄位 / iconType coerce / imagePrompt 變更才重生圖 / 不變不生圖 / 關閉重生 / 端點,**全程 mock Gemini**,真實生圖燒額度部分不打 API)。 - 海報為單圖無「區」概念,整圖 refine 已由 `/api/generate` 的 `refinement` 涵蓋。待做:②前端 - 區域選擇 / 逐區 refine UI(另開 PR)。 + 海報為單圖無「區」概念,整圖 refine 已由 `/api/generate` 的 `refinement` 涵蓋。 + - ✅ 2026-06-15(②)— 前端區域選擇 / 逐區 refine UI 接上 #81 後端。`/app` 視覺站已有的區塊點選 + (`pickSection`/`onPickSection`)+ 逐區微調面板(指令 + `regenerateImage` 開關)原本送的是 + **舊形狀** `{infographic, sectionId}` 並讀 `data.data`,與 #81 實際落地的 `POST /api/refine-section` + (收**單一** `section`、回**單一** `section`)對不上 → 逐區微調實際打不通。本刀把 `refineSection` + 改成與 sibling 單頁微調 `/api/refine` 同形狀:送 `{section: sections[idx], instruction, + regenerateImage}`、讀 `data.section`、patch 回 `result.data.sections[idx]`。**純前端一檔** + (`frontend/edustudio/app.jsx`),`npm run build`(vite/node22)編譯通過;後端 8 測 + (`test_infocards_refine.py`)已涵蓋端點,視覺由人後驗。至此 U-2 ①②③ 到齊。 - [ ] 🟡 **U-3 `/ui` `/studio` 標 legacy / 退場**(offline)— 在舊 UI 頁頂加 banner「此介面 將退場,請用 /app」+ README/介面表標 legacy。完全移除 build 產物等 `/app` 功能對等確認後 再做(避免反悔)。 diff --git a/frontend/edustudio/app.jsx b/frontend/edustudio/app.jsx index ec08c5c..13e00e2 100644 --- a/frontend/edustudio/app.jsx +++ b/frontend/edustudio/app.jsx @@ -2263,7 +2263,8 @@ function VisualComposer({ projectId }) { }; // 資訊圖卡逐區 refine:選一個區塊(區域選擇)送修改指令 → POST /api/refine-section → - // 後端回更新後的整張圖卡,整份替換 result.data。regenerateImage 可控是否一併重生配圖(省額度)。 + // 後端回更新後的「單一區塊」(與 /api/refine 單頁微調同形狀),patch 回 + // result.data.sections[idx]。regenerateImage 可控是否一併重生配圖(省額度)。 const [secOpen, setSecOpen] = useState(false); const [secIdx, setSecIdx] = useState(0); const [secInstr, setSecInstr] = useState(""); @@ -2279,13 +2280,13 @@ function VisualComposer({ projectId }) { try { const r = await fetch("/api/refine-section", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ infographic: result.data, sectionId: sections[idx].id, - instruction: secInstr, regenerateImage: secRegenImg, - style, customStylePrompt: style === "custom" ? customPrompt : "" }), + body: JSON.stringify({ section: sections[idx], instruction: secInstr, + regenerateImage: secRegenImg }), }); const data = await r.json(); if (!r.ok || data.success === false) throw new Error(data.detail || "逐區微調失敗"); - setResult({ ...result, data: data.data }); // 後端回整張更新後圖卡 + const next = sections.slice(); next[idx] = data.section; // 後端回更新後的單一區塊 + setResult({ ...result, data: { ...result.data, sections: next } }); setSecInstr(""); setSecOpen(false); } catch (e) { setErr("逐區微調發生錯誤:" + ((e && e.message) || e)); } finally { setSecBusy(false); } From edf18232b05216a6e9b893bcbfff6928414a32da Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 18:24:42 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test:=20=E7=A7=BB=E9=99=A4=20refine-section?= =?UTF-8?q?=20=E8=88=8A=E5=A5=91=E7=B4=84=E8=B7=AF=E7=94=B1=E6=B8=AC?= =?UTF-8?q?=E8=A9=A6=EF=BC=88=E5=B0=8D=E9=BD=8A=20#81=20=E5=96=AE=E4=B8=80?= =?UTF-8?q?=20section=20=E8=B7=AF=E7=94=B1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #81 把 /api/refine-section 路由改採「收/回單一 section」契約並加 tests/test_infocards_refine.py 覆蓋,但遺留 test_infocards_infographic.py ::TestRefineSection 的兩個路由測試仍送舊形狀 {infographic, sectionId} → 回 422, 使 main CI 紅。移除這兩個過時路由測試(端到端覆蓋已在 test_infocards_refine.py), 保留其餘 infographic_service 函式單元測試。 https://claude.ai/code/session_011KcsjSFoGvB92odmj4VtKK --- tests/test_infocards_infographic.py | 54 +++-------------------------- 1 file changed, 5 insertions(+), 49 deletions(-) diff --git a/tests/test_infocards_infographic.py b/tests/test_infocards_infographic.py index f015baf..d0b0ce6 100644 --- a/tests/test_infocards_infographic.py +++ b/tests/test_infocards_infographic.py @@ -162,52 +162,8 @@ def test_clearing_prompt_drops_image(self, monkeypatch): out = info.refine_infographic_section(data, "s1", "移除圖片") assert next(s for s in out.sections if s.id == "s1").imageUrl is None - def test_route(self, tmp_path, monkeypatch): - import pytest - pytest.importorskip("fastapi.testclient") - pytest.importorskip("multipart") - from fastapi.testclient import TestClient - - import core.infocards.infographic_service as infs - import server.routes.infocards as ic - from core.infocards.share_store import ShareStore - from server.main import create_app - - monkeypatch.setattr(ic, "get_share_store", lambda: ShareStore(db_path=str(tmp_path / "s.db"))) - monkeypatch.setattr(infs, "generate_json", - lambda prompt, model=None, response_schema=None: {"title": "改好的區塊"}) - monkeypatch.setattr(infs, "generate_image_b64", - lambda prompt, model=None: "data:image/png;base64,IMG") - app = create_app() - with TestClient(app) as c: - r = c.post("/api/refine-section", json={ - "infographic": {**_FAKE, "style": "academic"}, - "sectionId": "s1", "instruction": "改標題", - }) - assert r.status_code == 200 - body = r.json() - assert body["success"] is True and body["type"] == "infographic" - sec = next(s for s in body["data"]["sections"] if s["id"] == "s1") - assert sec["title"] == "改好的區塊" - - def test_route_unknown_section_404(self, tmp_path, monkeypatch): - import pytest - pytest.importorskip("fastapi.testclient") - pytest.importorskip("multipart") - from fastapi.testclient import TestClient - - import core.infocards.infographic_service as infs - import server.routes.infocards as ic - from core.infocards.share_store import ShareStore - from server.main import create_app - - monkeypatch.setattr(ic, "get_share_store", lambda: ShareStore(db_path=str(tmp_path / "s.db"))) - monkeypatch.setattr(infs, "generate_json", - lambda prompt, model=None, response_schema=None: {}) - app = create_app() - with TestClient(app) as c: - r = c.post("/api/refine-section", json={ - "infographic": {**_FAKE, "style": "academic"}, - "sectionId": "ghost", "instruction": "x", - }) - assert r.status_code == 404 + # 註:`POST /api/refine-section` 路由的端到端測試已由 + # tests/test_infocards_refine.py::TestRefineSectionRoute 涵蓋(現行契約=收/回 + # 單一 section)。本檔原有的兩個路由測試送的是舊形狀 {infographic, sectionId}、 + # 讀 data.data,與 #81 落地的單一 section 契約不符(回 422),已移除以對齊現行 + # 路由;本類別其餘仍為 infographic_service.refine_infographic_section 的函式單元測試。