From f2c75755b6576c7ae6ff5c819d4c6aaeb5a11804 Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 30 Mar 2026 20:12:49 +0800 Subject: [PATCH] fix: stop false missing-capability warnings --- web/storyforge-web-v4/assets/app.js | 72 +++++++++++++++---- .../tests/workbench-pages.test.mjs | 8 +++ 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 82231e8..325baf5 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -589,6 +589,13 @@ function formatActionErrorMessage(error, fallbackTitle = "执行失败") { return [detail.title, detail.summary, detail.hint].filter(Boolean).join(" · "); } +function isMissingBackendCapability(error) { + const status = Number(error?.status || error?.payload?.status_code || 0); + if (status === 404) return true; + const raw = String(error?.message || error?.payload?.detail || error?.statusText || "").trim().toLowerCase(); + return raw.includes("not found") || raw.includes("404"); +} + function presentActionFailure(error, fallbackTitle = "执行失败") { const detail = describeActionError(error, fallbackTitle); rememberAction(detail.title, [detail.summary, detail.hint].filter(Boolean).join(" · "), detail.tone); @@ -2642,16 +2649,25 @@ async function bootstrap() { async function markTrackingDigestRead() { const platform = getCurrentPlatformValue(); const trackingCursorPath = getWorkbenchRoute(platform, "trackingCursor"); - if (!trackingCursorPath || !backendSupports(trackingCursorPath)) { - rememberAction("当前后端暂不支持", "这套 live collector 还没有接入跟踪已读游标。", "orange"); + if (!trackingCursorPath) { + rememberAction("当前平台待接入", getPendingWorkbenchReason(platform), "orange"); renderAll(); return; } const nextSeenAt = new Date().toISOString(); - await storyforgeFetch(trackingCursorPath, { - method: "POST", - body: { last_seen_at: nextSeenAt } - }); + try { + await storyforgeFetch(trackingCursorPath, { + method: "POST", + body: { last_seen_at: nextSeenAt } + }); + } catch (error) { + if (isMissingBackendCapability(error)) { + rememberAction("当前实例未提供", `这套 ${platformLabel(platform)} collector 当前没有开放跟踪已读游标接口。`, "orange"); + renderAll(); + return; + } + throw error; + } appState.trackingCursorMap = { ...(appState.trackingCursorMap || {}), [platform]: nextSeenAt @@ -2671,8 +2687,8 @@ async function markTrackingDigestRead() { async function refreshTrackingAccountsAction() { const platform = getCurrentPlatformValue(); const trackingRefreshPath = getWorkbenchRoute(platform, "trackingRefresh"); - if (!trackingRefreshPath || !backendSupports(trackingRefreshPath)) { - rememberAction("当前后端暂不支持", "这套 live collector 还没有接入批量跟踪同步。", "orange"); + if (!trackingRefreshPath) { + rememberAction("当前平台待接入", getPendingWorkbenchReason(platform), "orange"); renderAll(); return; } @@ -2680,7 +2696,15 @@ async function refreshTrackingAccountsAction() { try { const payload = await storyforgeFetch(trackingRefreshPath, { method: "POST" + }).catch((error) => { + if (isMissingBackendCapability(error)) { + rememberAction("当前实例未提供", `这套 ${platformLabel(platform)} collector 当前没有开放批量跟踪同步。`, "orange"); + renderAll(); + return null; + } + throw error; }); + if (!payload) return; const refreshNotice = summarizeTrackingRefreshPayload(payload, platform, "batch"); rememberTrackingRefreshNotice(refreshNotice); rememberAction( @@ -2702,8 +2726,8 @@ async function refreshTrackedAccountAction(trackedAccountId) { const trackedItem = getTrackingAccounts().find((item) => item.tracked_account_id === trackedAccountId); const platform = trackedItem?.platform || getCurrentPlatformValue(); const trackingRefreshPath = getWorkbenchRoute(platform, "trackingAccountRefresh", trackedAccountId); - if (!trackingRefreshPath || !backendSupports(`/v2/${platform}/tracking/accounts/{tracked_account_id}/refresh`)) { - rememberAction("当前后端暂不支持", "这套 live collector 还没有接入单账号跟踪同步。", "orange"); + if (!trackingRefreshPath) { + rememberAction("当前平台待接入", getPendingWorkbenchReason(platform), "orange"); renderAll(); return; } @@ -2711,7 +2735,15 @@ async function refreshTrackedAccountAction(trackedAccountId) { try { const payload = await storyforgeFetch(trackingRefreshPath, { method: "POST" + }).catch((error) => { + if (isMissingBackendCapability(error)) { + rememberAction("当前实例未提供", `这套 ${platformLabel(platform)} collector 当前没有开放单账号跟踪同步。`, "orange"); + renderAll(); + return null; + } + throw error; }); + if (!payload) return; const refreshNotice = summarizeTrackingRefreshPayload(payload, platform, "single"); rememberTrackingRefreshNotice(refreshNotice); const success = payload.success !== false; @@ -9391,9 +9423,6 @@ function openPlatformSkillReviewAction(platform, skillId, accepted) { { name: "score", label: "得分", type: "number", value: accepted ? 0.9 : 0.45, min: 0, max: 1, step: 0.05 } ], onSubmit: async (values) => { - if (!backendSupports("/v2/platform-agents/{platform}/skills/{skill_id}/review")) { - throw new Error("当前后端还没有接入平台技能验收接口。"); - } const saved = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/skills/${encodeURIComponent(skillId)}/review`, { method: "POST", body: { @@ -9403,6 +9432,11 @@ function openPlatformSkillReviewAction(platform, skillId, accepted) { summary: values.summary || "", review_notes: values.reviewNotes || "" } + }).catch((error) => { + if (isMissingBackendCapability(error)) { + throw new Error("当前实例还没有开放平台技能验收接口。"); + } + throw error; }); rememberAction( accepted ? "平台技能已通过" : "平台技能待优化", @@ -9665,8 +9699,8 @@ function openAnalyzeTopVideosAction() { } const platform = gate.platform; const analyzePath = getWorkbenchRoute(platform, "analyzeTopVideos", account.id); - if (!analyzePath || !backendSupports(`/v2/${platform}/accounts/{account_id}/videos/analyze-top`)) { - rememberAction("当前后端暂不支持", "这套 live collector 还没有接入高分作品批量分析。", "orange"); + if (!analyzePath) { + rememberAction("当前平台待接入", getPendingWorkbenchReason(platform), "orange"); renderAll(); return; } @@ -9687,7 +9721,15 @@ function openAnalyzeTopVideosAction() { min_score: Number(values.minScore || 45), temperature: 0.25 } + }).catch((error) => { + if (isMissingBackendCapability(error)) { + rememberAction("当前实例未提供", `这套 ${platformLabel(platform)} collector 当前没有开放高分作品批量分析。`, "orange"); + renderAll(); + return null; + } + throw error; }); + if (!result) return; appState.topVideoAnalysisResults = { ...(appState.topVideoAnalysisResults || {}), [account.id]: { diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index c5017a4..46bc152 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -288,15 +288,23 @@ test("quota and review screens foreground live next-step guidance", () => { test("tracking refresh and top-video analysis flows expose async feedback inside the workbench", () => { const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()"); const discovery = extractBetween(APP, "function renderDiscoveryOverviewSection(", "function renderDiscoveryRelationsSection("); + const trackingActions = extractBetween(APP, "async function markTrackingDigestRead()", "function createEmptyTrackingDigest("); + const skillReview = extractBetween(APP, "function openPlatformSkillReviewAction(", "function openPlatformSkillRollbackAction("); + const topVideoAction = extractBetween(APP, "function openAnalyzeTopVideosAction()", "function openSimilaritySearchAction()"); assert.match(APP, /function summarizeTrackingRefreshPayload\(/); assert.match(APP, /function rememberTrackingRefreshNotice\(/); + assert.match(APP, /function isMissingBackendCapability\(/); assert.match(tracking, /批量同步已排队|单账号同步已排队|后台同步状态/); assert.match(tracking, /去生产中心/); assert.match(APP, /function getSelectedTopVideoAnalysisResult\(/); assert.match(discovery, /最近高分拆解/); assert.match(discovery, /这批结果已经回流到当前账号页/); + assert.doesNotMatch(trackingActions, /当前后端暂不支持.*跟踪已读游标|当前后端暂不支持.*批量跟踪同步|当前后端暂不支持.*单账号跟踪同步/s); + assert.doesNotMatch(skillReview, /当前后端还没有接入平台技能验收接口/); + assert.doesNotMatch(topVideoAction, /当前后端暂不支持.*高分作品批量分析/s); + assert.match(topVideoAction, /当前实例未提供/); }); test("discovery and production screens expose compact mobile flow summaries", () => {