diff --git a/CHANGELOG.md b/CHANGELOG.md index d2fb07f..be56dea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -470,3 +470,10 @@ - 主 Agent 落到 `找对标 / Agent / 生产中心 / 发布与复盘` 后,快捷动作里原先的 `高分分析 / 新建 Agent / 写复盘 / 做 AI 视频 / 做实拍剪辑` 已优先改成 direct-execute。 - 这些动作现在直接调用 `OneLiner` 执行器并按真实结果继续落到对象详情、Agent 编辑页、复盘页或任务详情,而不是先打开旧表单。 - `review-draft` 现在支持显式 `source_job_id`,所以从任务详情、复盘页和最近完成任务入口点“写复盘”,会围绕指定任务直接生成草稿,不再总是退回“最近一条任务”。 + +### 导入与跟踪表单统一收进执行器 + +- `导入主页 / 导入当前对标 / 加入跟踪 / 导入作品链接 / 导入文本` 这批高频表单现在都统一走 `OneLiner` 执行器,不再一部分直接调业务接口、一部分走主 Agent。 +- 后端新增了 `import-video-link / import-text` 两条真实执行动作,并且 `generate-copy / import-homepage / track-account / create-ai-video` 现在都会优先尊重显式 `assistant_id`,避免切到执行器后丢失用户在表单里选定的 Agent。 +- `runDirectWorkbenchAction / runDirectDiscoveryAction` 也已支持显式 `projectId / platform`,所以这批旧表单里的“归属项目 / 平台”选择不会在切换到执行器后失效。 +- SQLite 连接现在保持 `WAL` 优先,但在临时盘或受限文件系统无法启用 `WAL` 时会自动回退到 `DELETE`,避免测试环境和受限部署因为 `disk I/O error` 直接起不来。 diff --git a/collector-service/app/database.py b/collector-service/app/database.py index 45a9f65..cad41d6 100644 --- a/collector-service/app/database.py +++ b/collector-service/app/database.py @@ -29,7 +29,13 @@ class Database: def connect(self) -> sqlite3.Connection: conn = sqlite3.connect(self.path, timeout=SQLITE_CONNECT_TIMEOUT_SEC) conn.row_factory = dict_factory - conn.execute("PRAGMA journal_mode = WAL") + try: + conn.execute("PRAGMA journal_mode = WAL") + except sqlite3.OperationalError: + # Some temporary or restricted filesystems used by tests cannot + # enable WAL mode reliably. Fall back to the default journal mode + # so the database remains usable instead of failing to open. + conn.execute("PRAGMA journal_mode = DELETE") conn.execute("PRAGMA synchronous = NORMAL") conn.execute(f"PRAGMA busy_timeout = {SQLITE_BUSY_TIMEOUT_MS}") conn.execute("PRAGMA foreign_keys = ON") diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index 0e0c1ce..4b06ed0 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -306,6 +306,26 @@ ACTION_REGISTRY_DEFAULTS: dict[str, dict[str, Any]] = { "requires_platform": True, "config": {"auto_trigger_analysis": True}, }, + "import-video-link": { + "label": "直接导入作品链接", + "description": "把单条作品链接直接送进分析链,并创建可追踪的分析任务。", + "category": "analysis", + "handler_key": "import-video-link", + "status": "enabled", + "admin_only": False, + "requires_platform": False, + "config": {}, + }, + "import-text": { + "label": "直接导入文本素材", + "description": "把文本素材直接送进分析链,并创建可追踪的分析任务。", + "category": "analysis", + "handler_key": "import-text", + "status": "enabled", + "admin_only": False, + "requires_platform": False, + "config": {}, + }, "analyze-top-videos": { "label": "直接分析高分作品", "description": "拆解当前平台账号的高分作品,并把结论沉淀到平台记忆。", @@ -474,6 +494,8 @@ ACTION_USAGE_KEYS: dict[str, str] = { "generate-copy": "copy", "review-draft": "review", "import-homepage": "content_source_sync", + "import-video-link": "analysis", + "import-text": "analysis", "analyze-top-videos": "analysis", "analyze-account": "analysis", "track-account": "content_source_sync", @@ -5703,7 +5725,16 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: } async def _run_generate_copy() -> dict[str, Any]: - assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform) + requested_assistant_id = str( + requested_payload.get("assistant_id") + or requested_payload.get("assistantId") + or "" + ).strip() + assistant = ( + _resolve_assistant(account, requested_assistant_id, project["id"]) + if requested_assistant_id + else _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform) + ) if not assistant: raise HTTPException(status_code=404, detail="No execution assistant available") brief = str((request.payload or {}).get("brief") or latest_user_message or "").strip() @@ -5817,7 +5848,16 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: or normalized_platform or legacy.infer_platform_from_url(source_url) ) or _safe_platform(legacy.infer_platform_from_url(source_url), fallback="douyin") - assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=inferred_platform) + requested_assistant_id = str( + requested_payload.get("assistant_id") + or requested_payload.get("assistantId") + or "" + ).strip() + assistant = ( + _resolve_assistant(account, requested_assistant_id, project["id"]) + if requested_assistant_id + else _resolve_execution_assistant(account, project_id=project["id"], platform=inferred_platform) + ) existing_source = _find_creator_source_by_url( account, project_id=project["id"], @@ -5861,6 +5901,56 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: ), } + async def _run_import_video_link() -> dict[str, Any]: + video_url = str( + requested_payload.get("video_url") + or requested_payload.get("videoUrl") + or _extract_first_url(latest_user_message) + or "" + ).strip() + if not video_url: + raise HTTPException(status_code=400, detail="No video URL available for import") + requested_assistant_id = str( + requested_payload.get("assistant_id") + or requested_payload.get("assistantId") + or "" + ).strip() + job = await legacy.create_video_link_job( + legacy.ExploreVideoLinkRequest( + project_id=project["id"], + knowledge_base_id=str( + requested_payload.get("knowledge_base_id") + or requested_payload.get("knowledgeBaseId") + or "" + ), + assistant_id=requested_assistant_id, + analysis_model_profile_id=str( + requested_payload.get("analysis_model_profile_id") + or requested_payload.get("analysisModelProfileId") + or "" + ), + title=str(requested_payload.get("title") or "").strip(), + video_url=video_url, + language=str(requested_payload.get("language") or "auto"), + ), + account, + ) + return { + "title": "OneLiner 已导入作品链接", + "summary": "已把作品链接送入分析链,并创建可追踪的分析任务。", + "payload": { + "job": job, + "video_url": video_url, + }, + "recommended_action": _recommended_action( + "open-job-detail", + label="看任务详情", + summary="继续查看这条作品分析任务的进度和结果。", + screen="production", + job_id=job.get("id", ""), + ), + } + async def _run_analyze_account() -> dict[str, Any]: if not normalized_platform: raise HTTPException(status_code=400, detail="Platform is required for account analysis") @@ -5921,7 +6011,16 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: ) if not target_account: raise HTTPException(status_code=404, detail="No platform account available for tracking") - assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform) + requested_assistant_id = str( + requested_payload.get("assistant_id") + or requested_payload.get("assistantId") + or "" + ).strip() + assistant = ( + _resolve_assistant(account, requested_assistant_id, project["id"]) + if requested_assistant_id + else _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform) + ) tracked_payload = await _call_local_api( account, method="POST", @@ -5970,6 +6069,53 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "recommended_action": recommended_action, } + async def _run_import_text() -> dict[str, Any]: + title = str(requested_payload.get("title") or "").strip() + content = str(requested_payload.get("content") or "").strip() + if not title: + raise HTTPException(status_code=400, detail="No text title available for import") + if not content: + raise HTTPException(status_code=400, detail="No text content available for import") + requested_assistant_id = str( + requested_payload.get("assistant_id") + or requested_payload.get("assistantId") + or "" + ).strip() + job = await legacy.create_text_job( + legacy.ExploreTextRequest( + project_id=project["id"], + knowledge_base_id=str( + requested_payload.get("knowledge_base_id") + or requested_payload.get("knowledgeBaseId") + or "" + ), + assistant_id=requested_assistant_id, + analysis_model_profile_id=str( + requested_payload.get("analysis_model_profile_id") + or requested_payload.get("analysisModelProfileId") + or "" + ), + title=title, + content=content, + ), + account, + ) + return { + "title": "OneLiner 已导入文本素材", + "summary": "已把文本素材送入分析链,并创建可追踪的分析任务。", + "payload": { + "job": job, + "title": title, + }, + "recommended_action": _recommended_action( + "open-job-detail", + label="看任务详情", + summary="继续查看这条文本分析任务的进度和结果。", + screen="production", + job_id=job.get("id", ""), + ), + } + async def _run_refresh_tracking() -> dict[str, Any]: if not normalized_platform: raise HTTPException(status_code=400, detail="Platform is required for tracking refresh") @@ -6305,7 +6451,16 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: ) if not source_job: raise HTTPException(status_code=404, detail="No completed source job available for AI video") - assistant = _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform) + requested_assistant_id = str( + requested_payload.get("assistant_id") + or requested_payload.get("assistantId") + or "" + ).strip() + assistant = ( + _resolve_assistant(account, requested_assistant_id, project["id"]) + if requested_assistant_id + else _resolve_execution_assistant(account, project_id=project["id"], platform=normalized_platform) + ) brief = str( requested_payload.get("brief") or requested_payload.get("video_brief") @@ -6497,6 +6652,8 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "generate-copy": _run_generate_copy, "review-draft": _run_review_draft, "import-homepage": _run_import_homepage, + "import-video-link": _run_import_video_link, + "import-text": _run_import_text, "analyze-account": _run_analyze_account, "track-account": _run_track_account, "refresh-tracking": _run_refresh_tracking, diff --git a/tests/test_main_agent_governance.py b/tests/test_main_agent_governance.py index e923a17..2d7c639 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -1163,7 +1163,82 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertEqual(captured_request["video_provider"], "seedance2") self.assertEqual(captured_request["video_model"], "seedance-2.0-pro") self.assertEqual(payload["payload"]["job"]["artifacts"]["video_provider"], "seedance2") - self.assertEqual(payload["payload"]["job"]["artifacts"]["video_model"], "seedance-2.0-pro") + + def test_import_video_link_and_text_actions_execute_through_oneliner(self) -> None: + self._insert_assistant() + + captured_video_request: dict[str, Any] = {} + captured_text_request: dict[str, Any] = {} + + async def fake_video_link_job(request: Any, account: dict[str, Any]) -> dict[str, Any]: + captured_video_request.update( + { + "project_id": request.project_id, + "knowledge_base_id": request.knowledge_base_id, + "assistant_id": request.assistant_id, + "title": request.title, + "video_url": request.video_url, + "language": request.language, + } + ) + return {"id": "job_video_link_direct", "title": request.title or "视频链接分析任务"} + + async def fake_text_job(request: Any, account: dict[str, Any]) -> dict[str, Any]: + captured_text_request.update( + { + "project_id": request.project_id, + "knowledge_base_id": request.knowledge_base_id, + "assistant_id": request.assistant_id, + "title": request.title, + "content": request.content, + } + ) + return {"id": "job_text_direct", "title": request.title} + + with patch.object(self.core, "create_video_link_job", new=AsyncMock(side_effect=fake_video_link_job)): + video_response = self.client.post( + "/v2/oneliner/actions/execute", + headers=self.ctx["member_headers"], + json={ + "action_key": "import-video-link", + "project_id": self.ctx["project_id"], + "platform": "douyin", + "payload": { + "title": "短视频链接导入", + "video_url": "https://example.com/video/123", + "assistant_id": "asst_member_default", + "language": "zh-CN", + }, + }, + ) + self.assertEqual(video_response.status_code, 200, video_response.text) + video_payload = video_response.json() + self.assertEqual(captured_video_request["video_url"], "https://example.com/video/123") + self.assertEqual(captured_video_request["assistant_id"], "asst_member_default") + self.assertEqual(video_payload["recommended_action"]["action"], "open-job-detail") + self.assertEqual(video_payload["recommended_action"]["job_id"], "job_video_link_direct") + + with patch.object(self.core, "create_text_job", new=AsyncMock(side_effect=fake_text_job)): + text_response = self.client.post( + "/v2/oneliner/actions/execute", + headers=self.ctx["member_headers"], + json={ + "action_key": "import-text", + "project_id": self.ctx["project_id"], + "platform": "douyin", + "payload": { + "title": "文本导入", + "content": "这是一段需要分析的文本。", + "assistant_id": "asst_member_default", + }, + }, + ) + self.assertEqual(text_response.status_code, 200, text_response.text) + text_payload = text_response.json() + self.assertEqual(captured_text_request["title"], "文本导入") + self.assertEqual(captured_text_request["assistant_id"], "asst_member_default") + self.assertEqual(text_payload["recommended_action"]["action"], "open-job-detail") + self.assertEqual(text_payload["recommended_action"]["job_id"], "job_text_direct") def test_direct_oneliner_actions_execute_real_account_and_agent_flows(self) -> None: source_id = self._insert_content_source_account( diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 1c717d4..090ca38 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -2345,7 +2345,7 @@ function collectOneLinerActionPayload(action) { } async function executeOneLinerAction(executorKey, options = {}) { - const projectId = getOneLinerProjectId(); + const projectId = options.projectId || getOneLinerProjectId(); const session = getCurrentOneLinerSession() || await ensureOneLinerSession(); const payload = await storyforgeFetch("/v2/oneliner/actions/execute", { method: "POST", @@ -2441,10 +2441,12 @@ async function followRecommendedActionResult(payload, options = {}) { async function runDirectDiscoveryAction(executorKey, payload, options = {}) { const account = requireSelectedAccountRow(); - const platform = getAccountPlatform(account); + const defaultPlatform = getAccountPlatform(account); + const platform = normalizePlatformValue(options.platform || defaultPlatform, defaultPlatform); setBusy(true, options.busyLabel || "正在执行当前动作..."); try { const result = await executeOneLinerAction(executorKey, { + projectId: options.projectId || getSelectedProject()?.id || "", platform, payload, showResultModal: false @@ -9062,39 +9064,22 @@ function openImportHomepageAction() { if (!values.sourceUrl?.trim()) throw new Error("请填写主页链接"); const projectId = values.projectId || project.id; const platform = normalizePlatformValue(values.platform, "douyin"); - const source = await storyforgeFetch("/v2/content-sources", { - method: "POST", - body: { - project_id: projectId, - source_kind: "creator_account", - platform, - handle: values.handle || "", + await runDirectWorkbenchAction("import-homepage", { + projectId, + platform, + payload: { source_url: values.sourceUrl.trim(), title: values.title || values.handle || "主页对标", - metadata: {} - } - }); - const job = await storyforgeFetch("/v2/pipelines/content-source-sync", { - method: "POST", - body: { - project_id: projectId, - knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "", + handle: values.handle || "", assistant_id: values.assistantId || "", - content_source_id: source.id, - platform, - handle: values.handle || "", - source_url: values.sourceUrl.trim(), - title: values.title || values.handle || "主页对标", + knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "", max_items: Number(values.maxItems || 5), skip_existing: true, auto_trigger_analysis: true - } + }, + busyLabel: "正在导入主页并同步...", + errorTitle: "导入主页失败" }); - rememberAction("主页同步已启动", `已把主页加入项目,并创建同步任务 ${job.title || job.id}。`, "blue", job); - await bootstrap(); - if (job?.id) { - openJobDetailAction(job.id); - } } }); } @@ -9128,44 +9113,21 @@ function openImportSelectedAccountAction() { if (!values.sourceUrl?.trim()) throw new Error("请先填写主页链接"); const projectId = values.projectId || project.id; const platform = normalizePlatformValue(values.platform, "douyin"); - const source = currentSource && currentSource.project_id === projectId - ? currentSource - : await storyforgeFetch("/v2/content-sources", { - method: "POST", - body: { - project_id: projectId, - source_kind: "creator_account", - platform, - handle: values.handle || "", - source_url: values.sourceUrl.trim(), - title: values.title || values.handle || getAccountName(account) || "对标主页", - metadata: { - imported_from_account_id: account.id, - imported_from_workspace: "discovery" - } - } - }); - const job = await storyforgeFetch("/v2/pipelines/content-source-sync", { - method: "POST", - body: { - project_id: projectId, - knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "", - assistant_id: values.assistantId || "", - content_source_id: source.id, - platform, - handle: values.handle || getAccountHandle(account) || "", - source_url: values.sourceUrl.trim(), - title: values.title || getAccountName(account) || values.handle || "对标主页", - max_items: Number(values.maxItems || 6), - skip_existing: Boolean(values.skipExisting), - auto_trigger_analysis: Boolean(values.autoAnalyze) - } + await runDirectDiscoveryAction("import-homepage", { + source_url: values.sourceUrl.trim(), + title: values.title || getAccountName(account) || values.handle || "对标主页", + handle: values.handle || getAccountHandle(account) || "", + assistant_id: values.assistantId || "", + knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "", + max_items: Number(values.maxItems || 6), + skip_existing: Boolean(values.skipExisting), + auto_trigger_analysis: Boolean(values.autoAnalyze) + }, { + projectId, + platform, + busyLabel: currentSource ? "正在继续同步当前对标..." : "正在导入当前对标...", + errorTitle: currentSource ? "继续同步当前对标失败" : "导入当前对标失败" }); - rememberAction("对标已接入项目", `已把「${getAccountName(account) || "当前对标"}」接入项目,并创建同步任务 ${job.title || job.id}。`, "green", { source, job }); - await bootstrap(); - if (job?.id) { - openJobDetailAction(job.id); - } } }); } @@ -9173,7 +9135,6 @@ function openImportSelectedAccountAction() { function openTrackSelectedAccountAction() { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); - const trackingAccountsPath = getWorkbenchRoute(platform, "trackingAccounts"); const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const trackedItem = safeArray(appState.trackingAccounts).find((item) => item.tracked_account_id === account.id); @@ -9189,17 +9150,17 @@ function openTrackSelectedAccountAction() { { name: "note", label: "跟踪备注", value: trackedItem?.note || "", placeholder: "例如:重点观察开头结构、成交句式和更新频率" } ], onSubmit: async (values) => { - await storyforgeFetch(trackingAccountsPath, { - method: "POST", - body: { - tracked_account_id: account.id, - assistant_id: values.assistantId || "", - note: values.note || "" - } + await runDirectDiscoveryAction("track-account", { + target_account_id: account.id, + assistant_id: values.assistantId || "", + note: values.note || "", + refresh_now: true + }, { + projectId: project.id, + platform, + busyLabel: trackedItem ? "正在更新跟踪账号..." : "正在把账号加入跟踪...", + errorTitle: trackedItem ? "更新跟踪失败" : "加入跟踪失败" }); - rememberAction(trackedItem ? "跟踪已更新" : "已加入跟踪", `账号「${getAccountName(account) || "当前对标"}」现在会进入更新日报。`, "green"); - await bootstrap(); - focusTrackingWorkspace(); } }); } @@ -9221,22 +9182,18 @@ function openImportVideoLinkAction() { onSubmit: async (values) => { if (!values.videoUrl?.trim()) throw new Error("请填写作品链接"); const projectId = values.projectId || project.id; - const job = await storyforgeFetch("/v2/explore/video-link", { - method: "POST", - body: { + await runDirectWorkbenchAction("import-video-link", { + projectId, + payload: { video_url: values.videoUrl.trim(), title: values.title || "", - project_id: projectId, knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || "", assistant_id: values.assistantId || "", language: values.language || "auto" - } + }, + busyLabel: "正在导入作品链接并分析...", + errorTitle: "导入作品链接失败" }); - rememberAction("作品分析已启动", `已创建分析任务 ${job.title || job.id}。`, "blue", job); - await bootstrap(); - if (job?.id) { - openJobDetailAction(job.id); - } } }); } @@ -9258,21 +9215,17 @@ function openImportTextAction() { if (!values.title?.trim()) throw new Error("请填写标题"); if (!values.content?.trim()) throw new Error("请填写正文"); const projectId = values.projectId || project.id; - const job = await storyforgeFetch("/v2/explore/text", { - method: "POST", - body: { + await runDirectWorkbenchAction("import-text", { + projectId, + payload: { title: values.title.trim(), content: values.content.trim(), - project_id: projectId, knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || "", assistant_id: values.assistantId || "" - } + }, + busyLabel: "正在导入文本并分析...", + errorTitle: "导入文本失败" }); - rememberAction("文本分析已启动", `已创建文本分析任务 ${job.title || job.id}。`, "blue", job); - await bootstrap(); - if (job?.id) { - openJobDetailAction(job.id); - } } }); } diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 16e1cf7..7d27b38 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -1079,15 +1079,38 @@ test("job-creating workbench actions jump straight into the created job detail", const uploadVideo = extractBetween(APP, "function openUploadVideoAction()", "function openOneLinerProfileAction()"); const aiVideo = extractBetween(APP, "function openCreateAiVideoAction(defaults = {})", "function openCreateRealCutAction(defaults = {})"); const realCut = extractBetween(APP, "function openCreateRealCutAction(defaults = {})", "function openLiveRecorderAction()"); - assert.match(importHomepage, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/); - assert.match(importSelected, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/); - assert.match(importVideo, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/); - assert.match(importText, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/); + assert.match(importHomepage, /runDirectWorkbenchAction\("import-homepage"/); + assert.match(importSelected, /runDirectDiscoveryAction\("import-homepage"/); + assert.match(importVideo, /runDirectWorkbenchAction\("import-video-link"/); + assert.match(importText, /runDirectWorkbenchAction\("import-text"/); assert.match(uploadVideo, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/); assert.match(aiVideo, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/); assert.match(realCut, /if \(job\?\.id\) \{\s*openJobDetailAction\(job\.id\);/); }); +test("import and tracking sheets submit through direct execute handlers", () => { + const importHomepage = extractBetween(APP, "function openImportHomepageAction()", "function openImportSelectedAccountAction()"); + const importSelected = extractBetween(APP, "function openImportSelectedAccountAction()", "function openTrackSelectedAccountAction()"); + const trackSelected = extractBetween(APP, "function openTrackSelectedAccountAction()", "function openImportVideoLinkAction()"); + const importVideo = extractBetween(APP, "function openImportVideoLinkAction()", "function openImportTextAction()"); + const importText = extractBetween(APP, "function openImportTextAction()", "function openUploadVideoAction()"); + assert.match(importHomepage, /runDirectWorkbenchAction\("import-homepage"/); + assert.match(importHomepage, /assistant_id:\s*values\.assistantId \|\| ""/); + assert.doesNotMatch(importHomepage, /storyforgeFetch\("\/v2\/content-sources"/); + assert.doesNotMatch(importHomepage, /storyforgeFetch\("\/v2\/pipelines\/content-source-sync"/); + assert.match(importSelected, /runDirectDiscoveryAction\("import-homepage"/); + assert.match(importSelected, /assistant_id:\s*values\.assistantId \|\| ""/); + assert.doesNotMatch(importSelected, /storyforgeFetch\("\/v2\/content-sources"/); + assert.doesNotMatch(importSelected, /storyforgeFetch\("\/v2\/pipelines\/content-source-sync"/); + assert.match(trackSelected, /runDirectDiscoveryAction\("track-account"/); + assert.match(trackSelected, /assistant_id:\s*values\.assistantId \|\| ""/); + assert.doesNotMatch(trackSelected, /storyforgeFetch\(trackingAccountsPath/); + assert.match(importVideo, /runDirectWorkbenchAction\("import-video-link"/); + assert.doesNotMatch(importVideo, /storyforgeFetch\("\/v2\/explore\/video-link"/); + assert.match(importText, /runDirectWorkbenchAction\("import-text"/); + assert.doesNotMatch(importText, /storyforgeFetch\("\/v2\/explore\/text"/); +}); + test("discovery analysis actions focus the most relevant detail tab after success", () => { const analyzeAccount = extractBetween(APP, "function openAnalyzeSelectedAccountAction()", "function openAnalyzeTopVideosAction()"); const analyzeTopVideos = extractBetween(APP, "function openAnalyzeTopVideosAction()", "function openSimilaritySearchAction()"); @@ -1108,7 +1131,7 @@ test("tracking and benchmark actions land on the most relevant workbench area af const clickHandler = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {"); assert.match(APP, /function focusTrackingWorkspace\(\)/); assert.match(APP, /function focusDiscoveryRelations\(\)/); - assert.match(trackSelected, /focusTrackingWorkspace\(\)/); + assert.match(trackSelected, /runDirectDiscoveryAction\("track-account"/); assert.match(saveCandidate, /focusDiscoveryRelations\(\)/); assert.match(openBenchmark, /focusDiscoveryRelations\(\)/); assert.match(clickHandler, /name === "mark-tracking-read"[\s\S]*focusTrackingWorkspace\(\)/);