feat: unify import flows and harden sqlite fallback
Some checks failed
StoryForge CI / Baseline checks (push) Has been cancelled
StoryForge CI / Backend tests (push) Has been cancelled
StoryForge CI / Web tests (push) Has been cancelled

This commit is contained in:
kris
2026-04-05 08:02:20 +08:00
parent 696f90b3fe
commit 78d90542cc
6 changed files with 328 additions and 107 deletions

View File

@@ -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` 直接起不来。

View File

@@ -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")

View File

@@ -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,

View File

@@ -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(

View File

@@ -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);
}
}
});
}

View File

@@ -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\(\)/);