feat: improve ai video entry and video recorder discoverability
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -4,6 +4,12 @@
|
||||
|
||||
## 2026-04-07
|
||||
|
||||
### AI 视频表单可直接跳到火山视频配置状态
|
||||
|
||||
- `创建 AI 视频任务` 里的 `Seedance 配置` 提示现在不再只是静态文案,而是新增了 `查看火山配置状态` 入口。
|
||||
- 点击后会直接跳到 `自动流程 -> 依赖健康 -> Huobao` 卡片,立刻看到当前火山视频配置是否就绪、部署位置和配置提示,不用再自己记 `/settings/ai-config -> 视频 -> 火山引擎` 再手动找入口。
|
||||
- 同时 `依赖健康` 里的各张集成卡现在都带稳定锚点,后续其他配置提示也可以直接把用户带到最相关的健康卡,而不是只停在说明文字里。
|
||||
|
||||
### 修复额度页套餐建议引起的全局渲染报错
|
||||
|
||||
- `额度` 页面现在会先初始化 `packageRecommendation` 再渲染套餐建议,不再因为变量未定义把整个工作台渲染链打断。
|
||||
@@ -662,3 +668,9 @@
|
||||
- `创建 Agent / 编辑 Agent` 这两张表单也补成了带上下文和知识库联动的产品化表单:创建时切项目会同步刷新默认知识库,编辑时可以直接更新默认知识库,不必再回别处改。
|
||||
- 额度页残留的半成品口径已收口,不再出现“后端尚未完全接入真实预算”这类提示;未配置独立额度策略时,会直接引导按预算基线和动作池去建立试用、增长或规模套餐。
|
||||
- `smoke_public_storyforge.sh` 和 `smoke_fnos_storyforge_lan.sh` 现在会显式校验 `integrations/health` 的关键依赖状态、部署位置和 `local_model=not_configured` 口径,不再只看页面能打开和基础 healthz。
|
||||
|
||||
# 2026-04-07
|
||||
|
||||
- 顶层 `AI 视频 / 实拍剪辑` 主按钮改回“先开配置表单”,会自动承接最近完成任务作为默认来源,但不再直接跳过配置页;只有任务上下文里的 `做 AI 视频 / 做实拍剪辑` 仍保持 direct-execute。
|
||||
- `AI 视频` 表单新增 `Seedance 配置` 提示,明确说明当前 `Seedance 2.0` 走火山视频配置,默认应在 `Huobao /settings/ai-config -> 视频 -> 火山引擎` 配置;如果不用页面配置,也支持通过 `HUOBAO_VIDEO_BASE_URL / HUOBAO_VIDEO_API_KEY / HUOBAO_VIDEO_MODELS` 环境变量覆盖。
|
||||
- `integrations/health` 新增 `huobao` 视频配置摘要,能直接看出当前 `Huobao` 视频配置页是否已经录入视频引擎配置,以及对应的配置页路径,减少排查 `Seedance` 任务为什么只建单不出片的歧义。
|
||||
|
||||
@@ -3188,6 +3188,39 @@ def probe_http_json(url: str, path: str = "", timeout: float = 3.0) -> dict[str,
|
||||
return detail
|
||||
|
||||
|
||||
def probe_huobao_video_config(url: str, timeout: float = 5.0) -> dict[str, Any]:
|
||||
detail = {
|
||||
"video_config_route": "/settings/ai-config -> 视频 -> 火山引擎",
|
||||
"video_config_count": 0,
|
||||
"video_config_ready": False,
|
||||
}
|
||||
if not url:
|
||||
return detail
|
||||
target_url = urljoin(url if url.endswith("/") else f"{url}/", "api/v1/ai-configs")
|
||||
try:
|
||||
response = httpx.get(
|
||||
target_url,
|
||||
params={"service_type": "video"},
|
||||
timeout=timeout,
|
||||
follow_redirects=True,
|
||||
)
|
||||
if response.status_code >= 500:
|
||||
return detail
|
||||
payload = response.json()
|
||||
items: list[dict[str, Any]] = []
|
||||
if isinstance(payload, list):
|
||||
items = [item for item in payload if isinstance(item, dict)]
|
||||
elif isinstance(payload, dict):
|
||||
raw_items = payload.get("data") if isinstance(payload.get("data"), list) else payload.get("configs")
|
||||
if isinstance(raw_items, list):
|
||||
items = [item for item in raw_items if isinstance(item, dict)]
|
||||
detail["video_config_count"] = len(items)
|
||||
detail["video_config_ready"] = bool(items)
|
||||
except Exception:
|
||||
pass
|
||||
return detail
|
||||
|
||||
|
||||
def live_recorder_request(method: str, path: str, payload: dict[str, Any] | None = None, timeout: float = 20.0) -> Any:
|
||||
if not LIVE_RECORDER_BASE_URL:
|
||||
raise HTTPException(status_code=503, detail="LIVE_RECORDER_BASE_URL is not configured")
|
||||
@@ -3312,6 +3345,8 @@ def integrations_health(account: dict[str, Any] = Depends(require_approved)) ->
|
||||
cutvideo_uploads = probe_http(CUTVIDEO_BASE_URL, "/api/uploads", timeout=5.0)
|
||||
asr_probe = probe_http_json(ASR_HTTP_BASE_URL, "/health", timeout=5.0)
|
||||
asr_runtime = asr_probe.get("json") if isinstance(asr_probe.get("json"), dict) else {}
|
||||
huobao_probe = probe_http(HUOBAO_BASE_URL, "/health", timeout=5.0)
|
||||
huobao_video_config = probe_huobao_video_config(HUOBAO_BASE_URL, timeout=5.0)
|
||||
cutvideo_supports_uploads = bool(
|
||||
cutvideo_uploads.get("configured")
|
||||
and cutvideo_uploads.get("reachable")
|
||||
@@ -3336,7 +3371,8 @@ def integrations_health(account: dict[str, Any] = Depends(require_approved)) ->
|
||||
"huobao": {
|
||||
"base_url": HUOBAO_BASE_URL,
|
||||
**integration_deployment_payload("huobao", HUOBAO_BASE_URL),
|
||||
**probe_http(HUOBAO_BASE_URL, "/health"),
|
||||
**huobao_probe,
|
||||
**huobao_video_config,
|
||||
},
|
||||
"n8n": {
|
||||
"base_url": N8N_BASE_URL,
|
||||
|
||||
@@ -352,6 +352,7 @@ class ProductionBaselineTests(unittest.TestCase):
|
||||
original_cutvideo = self.core.CUTVIDEO_BASE_URL
|
||||
original_probe_http = self.core.probe_http
|
||||
original_probe_http_json = getattr(self.core, "probe_http_json", None)
|
||||
original_probe_huobao_video_config = getattr(self.core, "probe_huobao_video_config", None)
|
||||
try:
|
||||
self.core.N8N_BASE_URL = "http://127.0.0.1:25670"
|
||||
self.core.HUOBAO_BASE_URL = "http://127.0.0.1:25678"
|
||||
@@ -381,8 +382,16 @@ class ProductionBaselineTests(unittest.TestCase):
|
||||
}
|
||||
return detail
|
||||
|
||||
def fake_probe_huobao_video_config(url: str, timeout: float = 5.0) -> dict[str, Any]:
|
||||
return {
|
||||
"video_config_route": "/settings/ai-config -> 视频 -> 火山引擎",
|
||||
"video_config_count": 1,
|
||||
"video_config_ready": True,
|
||||
}
|
||||
|
||||
self.core.probe_http = fake_probe_http
|
||||
self.core.probe_http_json = fake_probe_http_json
|
||||
self.core.probe_huobao_video_config = fake_probe_huobao_video_config
|
||||
response = self.client.get("/v2/integrations/health", headers=headers)
|
||||
finally:
|
||||
self.core.N8N_BASE_URL = original_n8n
|
||||
@@ -398,11 +407,20 @@ class ProductionBaselineTests(unittest.TestCase):
|
||||
pass
|
||||
else:
|
||||
self.core.probe_http_json = original_probe_http_json
|
||||
if original_probe_huobao_video_config is None:
|
||||
try:
|
||||
delattr(self.core, "probe_huobao_video_config")
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.core.probe_huobao_video_config = original_probe_huobao_video_config
|
||||
|
||||
self.assertEqual(response.status_code, 200, response.text)
|
||||
payload = response.json()
|
||||
self.assertEqual(payload["n8n"]["deployment_label"], "服务器")
|
||||
self.assertEqual(payload["huobao"]["deployment_label"], "服务器")
|
||||
self.assertEqual(payload["huobao"]["video_config_route"], "/settings/ai-config -> 视频 -> 火山引擎")
|
||||
self.assertTrue(payload["huobao"]["video_config_ready"])
|
||||
self.assertEqual(payload["asr"]["deployment_label"], "Windows")
|
||||
self.assertEqual(payload["live_recorder"]["deployment_label"], "NAS")
|
||||
self.assertEqual(payload["cutvideo"]["deployment_label"], "NAS 隧道")
|
||||
|
||||
@@ -206,13 +206,13 @@ const INTEGRATION_META = {
|
||||
const PIPELINE_GUARDS = {
|
||||
aiVideo: {
|
||||
label: "AI 视频",
|
||||
openAction: "direct-create-ai-video",
|
||||
openAction: "open-ai-video",
|
||||
jobAction: "direct-create-ai-video",
|
||||
dependencies: ["n8n", "huobao"]
|
||||
},
|
||||
realCut: {
|
||||
label: "实拍剪辑",
|
||||
openAction: "direct-create-real-cut",
|
||||
openAction: "open-real-cut",
|
||||
jobAction: "direct-create-real-cut",
|
||||
dependencies: ["n8n", "cutvideo"]
|
||||
}
|
||||
@@ -4299,7 +4299,10 @@ function getIntegrationDetail(key) {
|
||||
activeDevice: String(raw?.active_device || ""),
|
||||
activeComputeType: String(raw?.active_compute_type || ""),
|
||||
languageMode: String(raw?.language_mode || ""),
|
||||
modelName: String(raw?.model_name || "")
|
||||
modelName: String(raw?.model_name || ""),
|
||||
videoConfigRoute: String(raw?.video_config_route || ""),
|
||||
videoConfigCount: Number(raw?.video_config_count || 0),
|
||||
videoConfigReady: Boolean(raw?.video_config_ready),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4458,6 +4461,16 @@ function getIntegrationCards() {
|
||||
if (detail.modelName) {
|
||||
extra += ` · 当前模型:${detail.modelName}`;
|
||||
}
|
||||
} else if (key === "huobao") {
|
||||
const configLabel = detail.videoConfigCount
|
||||
? `视频配置 ${detail.videoConfigCount} 条`
|
||||
: "视频配置未录入";
|
||||
extra = `部署:${detail.deploymentLabel || "待确认"} · ${configLabel}`;
|
||||
if (detail.videoConfigRoute) {
|
||||
note = detail.videoConfigReady
|
||||
? `Huobao 视频配置已就绪:${detail.videoConfigRoute}`
|
||||
: `Seedance 2.0 走火山视频配置,请在 ${detail.videoConfigRoute} 补齐`;
|
||||
}
|
||||
} else if (detail.deploymentLabel) {
|
||||
extra = `部署:${detail.deploymentLabel}`;
|
||||
}
|
||||
@@ -5647,6 +5660,7 @@ function renderIntegrationOverviewPanel(options = {}) {
|
||||
<div class="integration-actions">
|
||||
${renderPipelineButton("aiVideo", "primary")}
|
||||
${renderPipelineButton("realCut")}
|
||||
${button("视频录制", "focus-live-recorder-maintenance", "secondary")}
|
||||
</div>
|
||||
` : ""}
|
||||
</div>
|
||||
@@ -5655,7 +5669,7 @@ function renderIntegrationOverviewPanel(options = {}) {
|
||||
</div>
|
||||
<div class="layout-grid grid-4 integration-grid">
|
||||
${cards.map((item) => `
|
||||
<div class="integration-card ${item.status.tone}">
|
||||
<div class="integration-card ${item.status.tone}" id="integration-${escapeHtml(item.key)}-anchor">
|
||||
<div class="integration-card-head">
|
||||
<div>
|
||||
<h4>${escapeHtml(item.meta.label)}</h4>
|
||||
@@ -6523,6 +6537,16 @@ function focusLiveRecorderMaintenance() {
|
||||
});
|
||||
}
|
||||
|
||||
function focusAutomationHealthWorkspace(anchorId = "integration-huobao-anchor") {
|
||||
appState.automationDetailTab = "health";
|
||||
setScreen("automation");
|
||||
renderAll();
|
||||
window.requestAnimationFrame(() => {
|
||||
(document.getElementById(anchorId) || document.querySelector('[data-screen="automation"] .integration-panel, [data-screen="automation"] .panel'))
|
||||
?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
function focusReviewWorkspace(reviewId = "") {
|
||||
appState.reviewFocusId = reviewId || "";
|
||||
setScreen("review");
|
||||
@@ -7541,7 +7565,7 @@ function renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, rec
|
||||
<p>${escapeHtml(status.running ? `Live Recorder 正在运行,当前有 ${activeCount} 路活动录制。` : "先确认 Live Recorder 是否在线,再检查录制源和文件。")}</p>
|
||||
<div class="task-meta">
|
||||
<span class="tag ${status.running ? "green" : "orange"}">${escapeHtml(status.running ? "运行中" : "待检查")}</span>
|
||||
${actionTag("录制维护", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recorder"`)}
|
||||
${actionTag("视频录制", "focus-live-recorder-maintenance")}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
@@ -7554,12 +7578,12 @@ function renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, rec
|
||||
sourceScreen: "production",
|
||||
sourceActionKey: "production-mobile-recorder-handoff",
|
||||
intentKey: "production_coordination",
|
||||
title: "继续处理录制维护",
|
||||
goal: "继续处理录制维护",
|
||||
summary: "结合录制维护状态给出下一步动作。",
|
||||
title: "继续处理视频录制",
|
||||
goal: "继续处理视频录制",
|
||||
summary: "结合视频录制状态给出下一步动作。",
|
||||
platform: getPreferredPlatform(),
|
||||
platformScope: "single_platform",
|
||||
planSteps: ["读取录制维护状态", "识别当前阻塞项", "生成下一步处理动作"]
|
||||
planSteps: ["读取视频录制状态", "识别当前阻塞项", "生成下一步处理动作"]
|
||||
}))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -7662,13 +7686,13 @@ function renderProductionScreen() {
|
||||
const tabs = [
|
||||
{ value: "queue", label: "生产队列" },
|
||||
{ value: "recovery", label: "失败恢复" },
|
||||
{ value: "recorder", label: "录制维护" },
|
||||
{ value: "recorder", label: "视频录制" },
|
||||
{ value: "outputs", label: "作品与产物" }
|
||||
];
|
||||
const activeTab = getActiveDetailTab("productionDetailTab", tabs);
|
||||
const productionActionsHtml = isMobileUi
|
||||
? `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })}`
|
||||
: `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`;
|
||||
? `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("视频录制", "focus-live-recorder-maintenance", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })}`
|
||||
: `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("视频录制", "focus-live-recorder-maintenance", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`;
|
||||
return screenShell(
|
||||
"生产中心",
|
||||
"这里已经接上真实任务、失败恢复和知识库文档,适合直接推进生产、恢复和复盘。",
|
||||
@@ -7717,7 +7741,7 @@ function renderProductionScreen() {
|
||||
: activeTab === "outputs"
|
||||
? `${actionTag("去复盘", "goto-review")} ${actionTag("查看产物", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="outputs"`)}`
|
||||
: activeTab === "recorder"
|
||||
? `${actionTag("录制维护", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recorder"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", productionHandoffAttrs)}`
|
||||
? `${actionTag("视频录制", "focus-live-recorder-maintenance")} ${actionTag("交给主 Agent", "handoff-to-main-agent", productionHandoffAttrs)}`
|
||||
: `${actionTag("批量恢复", "batch-recover-jobs")} ${actionTag("交给主 Agent", "handoff-to-main-agent", productionHandoffAttrs)}`
|
||||
}
|
||||
</div>
|
||||
@@ -12082,6 +12106,29 @@ function openGenerateCopyAction(defaults = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function renderAiVideoProviderHintHtml(provider = "doubao") {
|
||||
const huobao = getIntegrationDetail("huobao");
|
||||
const route = huobao.videoConfigRoute || "/settings/ai-config -> 视频 -> 火山引擎";
|
||||
const providerLabel = String(provider || "doubao").trim() === "seedance2" ? "Seedance 2.0" : "当前默认视频引擎";
|
||||
const configStatus = huobao.videoConfigReady
|
||||
? `Huobao 视频配置已就绪${huobao.videoConfigCount ? `(${huobao.videoConfigCount} 条)` : ""}`
|
||||
: "Huobao 视频配置页当前还没有录入视频引擎配置";
|
||||
return `
|
||||
<div class="sheet-html">
|
||||
<div class="task-item compact">
|
||||
<h4>${escapeHtml(`${providerLabel} 走火山视频配置`)}</h4>
|
||||
<p>${escapeHtml(`请在 Huobao 服务里配置火山视频 Key:${route}`)}</p>
|
||||
<p>${escapeHtml("如果不是走页面配置,也可以在 huobao 服务环境变量里覆盖 HUOBAO_VIDEO_BASE_URL / HUOBAO_VIDEO_API_KEY / HUOBAO_VIDEO_MODELS。")}</p>
|
||||
<div class="task-meta">
|
||||
<span class="tag ${huobao.videoConfigReady ? "green" : "orange"}">${escapeHtml(configStatus)}</span>
|
||||
${huobao.deploymentLabel ? `<span class="tag">${escapeHtml(`部署:${huobao.deploymentLabel}`)}</span>` : ""}
|
||||
<span class="tag clickable-tag" data-action="focus-huobao-video-config">查看火山配置状态</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function openCreateAiVideoAction(defaults = {}) {
|
||||
const guard = getPipelineGuard("aiVideo");
|
||||
if (!guard.enabled) {
|
||||
@@ -12126,6 +12173,12 @@ function openCreateAiVideoAction(defaults = {}) {
|
||||
value: defaultVideoModel,
|
||||
placeholder: "例如:seedance-2.0-pro",
|
||||
},
|
||||
{
|
||||
name: "videoProviderHint",
|
||||
label: "Seedance 配置",
|
||||
type: "html",
|
||||
html: renderAiVideoProviderHintHtml(defaultVideoProvider),
|
||||
},
|
||||
{ name: "style", label: "风格", value: defaults.style || recommendCreativeStyle(sourceJob) },
|
||||
{
|
||||
name: "aspectRatio",
|
||||
@@ -12788,6 +12841,16 @@ document.addEventListener("click", async (event) => {
|
||||
setScreen("production");
|
||||
return;
|
||||
}
|
||||
if (name === "focus-live-recorder-maintenance") {
|
||||
captureMainAgentLandingContext(action, "goto-production");
|
||||
focusLiveRecorderMaintenance();
|
||||
return;
|
||||
}
|
||||
if (name === "focus-huobao-video-config") {
|
||||
captureMainAgentLandingContext(action, "goto-automation");
|
||||
focusAutomationHealthWorkspace("integration-huobao-anchor");
|
||||
return;
|
||||
}
|
||||
if (name === "goto-strategy") {
|
||||
captureMainAgentLandingContext(action, "goto-strategy");
|
||||
setScreen("strategy");
|
||||
@@ -13258,15 +13321,7 @@ document.addEventListener("click", async (event) => {
|
||||
}
|
||||
if (name === "open-ai-video") {
|
||||
const fallbackJob = getLatestCompletedProjectJob();
|
||||
if (fallbackJob?.id) {
|
||||
await runDirectWorkbenchAction("create-ai-video", {
|
||||
busyLabel: "正在创建 AI 视频任务...",
|
||||
errorTitle: "创建 AI 视频任务失败",
|
||||
payload: { source_job_id: fallbackJob.id }
|
||||
});
|
||||
return;
|
||||
}
|
||||
openCreateAiVideoAction();
|
||||
openCreateAiVideoAction(fallbackJob?.id ? { sourceJobId: fallbackJob.id, sourceJob: fallbackJob } : {});
|
||||
return;
|
||||
}
|
||||
if (name === "direct-create-ai-video") {
|
||||
@@ -13282,15 +13337,7 @@ document.addEventListener("click", async (event) => {
|
||||
}
|
||||
if (name === "open-real-cut") {
|
||||
const fallbackJob = getLatestCompletedProjectJob();
|
||||
if (fallbackJob?.id) {
|
||||
await runDirectWorkbenchAction("create-real-cut", {
|
||||
busyLabel: "正在创建实拍剪辑任务...",
|
||||
errorTitle: "创建实拍剪辑任务失败",
|
||||
payload: { source_job_id: fallbackJob.id }
|
||||
});
|
||||
return;
|
||||
}
|
||||
openCreateRealCutAction();
|
||||
openCreateRealCutAction(fallbackJob?.id ? { sourceJobId: fallbackJob.id, sourceJob: fallbackJob } : {});
|
||||
return;
|
||||
}
|
||||
if (name === "direct-create-real-cut") {
|
||||
|
||||
@@ -254,6 +254,7 @@ test("discovery, production, and admin screens use page tabs for heavy content",
|
||||
|
||||
assert.match(discovery, /renderDetailTabs\("discoveryDetailTab"/);
|
||||
assert.match(production, /renderDetailTabs\("productionDetailTab"/);
|
||||
assert.match(production, /value: "recorder", label: "视频录制"/);
|
||||
assert.match(admin, /renderDetailTabs\("adminWorkbenchTab"/);
|
||||
assert.match(admin, /renderAdminGovernanceSummaryPanel\(/);
|
||||
assert.match(admin, /覆盖与审计/);
|
||||
@@ -417,8 +418,8 @@ test("pipeline follow-up tags route source-job actions through direct execute ha
|
||||
const pipelineGuards = extractBetween(APP, "const PIPELINE_GUARDS = {", "const ONELINER_INTENT_LABELS = {");
|
||||
const clickActions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
||||
|
||||
assert.match(pipelineGuards, /openAction:\s*"direct-create-ai-video"/);
|
||||
assert.match(pipelineGuards, /openAction:\s*"direct-create-real-cut"/);
|
||||
assert.match(pipelineGuards, /openAction:\s*"open-ai-video"/);
|
||||
assert.match(pipelineGuards, /openAction:\s*"open-real-cut"/);
|
||||
assert.match(pipelineGuards, /jobAction:\s*"direct-create-ai-video"/);
|
||||
assert.match(pipelineGuards, /jobAction:\s*"direct-create-real-cut"/);
|
||||
assert.match(clickActions, /name === "direct-create-ai-video"[\s\S]*if \(action\.dataset\.jobId\) \{[\s\S]*closeActionModal\(\);/);
|
||||
@@ -472,6 +473,7 @@ test("discovery and production screens expose mobile focus cards with next-step
|
||||
test("mobile discovery and production simplify duplicated top-level actions", () => {
|
||||
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
||||
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
||||
const clickActions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "navButtons.forEach((button) => {");
|
||||
|
||||
assert.match(APP, /function isMobileViewport\(\)/);
|
||||
assert.match(discovery, /const isMobileUi = isMobileViewport\(\);/);
|
||||
@@ -482,8 +484,10 @@ test("mobile discovery and production simplify duplicated top-level actions", ()
|
||||
assert.match(discovery, /saveBenchmarkActionName = similarCandidates\.length \? "direct-save-benchmark-link" : "open-benchmark-link"/);
|
||||
assert.match(production, /const isMobileUi = isMobileViewport\(\);/);
|
||||
assert.match(production, /const productionActionsHtml = isMobileUi/);
|
||||
assert.match(production, /button\("视频录制", "focus-live-recorder-maintenance", "secondary"\)/);
|
||||
assert.match(production, /button\("交给主 Agent", "handoff-to-main-agent"/);
|
||||
assert.match(production, /button\("去复盘", "goto-review", "primary"\)/);
|
||||
assert.match(clickActions, /name === "focus-live-recorder-maintenance"[\s\S]*captureMainAgentLandingContext\(action,\s*"goto-production"\);[\s\S]*focusLiveRecorderMaintenance\(\)/);
|
||||
});
|
||||
|
||||
test("discovery page promotes selected-account actions into direct execute flows", () => {
|
||||
@@ -1080,6 +1084,8 @@ test("playbook and review high-frequency actions now reuse direct execute handle
|
||||
const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
|
||||
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
||||
const clickActions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
||||
const openAiVideoBlock = extractBetween(clickActions, 'if (name === "open-ai-video") {', 'if (name === "direct-create-ai-video") {');
|
||||
const openRealCutBlock = extractBetween(clickActions, 'if (name === "open-real-cut") {', 'if (name === "direct-create-real-cut") {');
|
||||
assert.match(playbook, /direct-create-assistant/);
|
||||
assert.match(review, /direct-review-draft/);
|
||||
assert.match(APP, /payload: action\.dataset\.jobId \? \{ source_job_id: action\.dataset\.jobId \} : \{\}/);
|
||||
@@ -1091,12 +1097,12 @@ test("playbook and review high-frequency actions now reuse direct execute handle
|
||||
assert.match(clickActions, /name === "open-generate-copy"[\s\S]*openGenerateCopyAction\(\)/);
|
||||
assert.match(clickActions, /name === "open-review-from-job"[\s\S]*runDirectWorkbenchAction\("review-draft"/);
|
||||
assert.match(clickActions, /name === "open-review-from-job"[\s\S]*payload: \{ source_job_id: jobId \}/);
|
||||
assert.match(clickActions, /name === "open-ai-video"[\s\S]*const fallbackJob = getLatestCompletedProjectJob\(\)/);
|
||||
assert.match(clickActions, /name === "open-ai-video"[\s\S]*runDirectWorkbenchAction\("create-ai-video"/);
|
||||
assert.match(clickActions, /name === "open-ai-video"[\s\S]*openCreateAiVideoAction\(\)/);
|
||||
assert.match(clickActions, /name === "open-real-cut"[\s\S]*const fallbackJob = getLatestCompletedProjectJob\(\)/);
|
||||
assert.match(clickActions, /name === "open-real-cut"[\s\S]*runDirectWorkbenchAction\("create-real-cut"/);
|
||||
assert.match(clickActions, /name === "open-real-cut"[\s\S]*openCreateRealCutAction\(\)/);
|
||||
assert.match(openAiVideoBlock, /const fallbackJob = getLatestCompletedProjectJob\(\)/);
|
||||
assert.match(openAiVideoBlock, /openCreateAiVideoAction\(/);
|
||||
assert.doesNotMatch(openAiVideoBlock, /runDirectWorkbenchAction\("create-ai-video"/);
|
||||
assert.match(openRealCutBlock, /const fallbackJob = getLatestCompletedProjectJob\(\)/);
|
||||
assert.match(openRealCutBlock, /openCreateRealCutAction\(/);
|
||||
assert.doesNotMatch(openRealCutBlock, /runDirectWorkbenchAction\("create-real-cut"/);
|
||||
assert.match(clickActions, /name === "open-create-assistant"[\s\S]*const project = getSelectedProject\(\)/);
|
||||
assert.match(clickActions, /name === "open-create-assistant"[\s\S]*runDirectWorkbenchAction\("create-assistant"/);
|
||||
assert.match(clickActions, /name === "open-create-assistant"[\s\S]*openCreateAssistantAction\(\)/);
|
||||
@@ -1105,6 +1111,23 @@ test("playbook and review high-frequency actions now reuse direct execute handle
|
||||
assert.match(clickActions, /name === "open-import-homepage"[\s\S]*openImportHomepageAction\(\)/);
|
||||
});
|
||||
|
||||
test("ai video form explains where Seedance 火山配置 lives", () => {
|
||||
const clickActions = extractBetween(
|
||||
APP,
|
||||
'document.addEventListener("click", async (event) => {',
|
||||
'navButtons.forEach((button) => {'
|
||||
);
|
||||
assert.match(APP, /function renderAiVideoProviderHintHtml\(provider = "doubao"\)/);
|
||||
assert.match(APP, /Seedance 2\.0 走火山视频配置/);
|
||||
assert.match(APP, /\/settings\/ai-config/);
|
||||
assert.match(APP, /视频 -> 火山引擎/);
|
||||
assert.match(APP, /HUOBAO_VIDEO_API_KEY/);
|
||||
assert.match(APP, /data-action="focus-huobao-video-config"/);
|
||||
assert.match(APP, /id="integration-\$\{escapeHtml\(item\.key\)\}-anchor"/);
|
||||
assert.match(APP, /function focusAutomationHealthWorkspace\(anchorId = "integration-huobao-anchor"\)/);
|
||||
assert.match(clickActions, /name === "focus-huobao-video-config"[\s\S]*focusAutomationHealthWorkspace\("integration-huobao-anchor"\)/);
|
||||
});
|
||||
|
||||
test("main agent landing notices expose a compact mobile follow-up strip", () => {
|
||||
const landing = extractBetween(APP, "function renderMainAgentLandingNotice(screenKey)", "function renderEmptyState(title, description)");
|
||||
assert.match(landing, /mobile-only compact-summary-row/);
|
||||
|
||||
Reference in New Issue
Block a user