feat: unify import flows and harden sqlite fallback
This commit is contained in:
@@ -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` 直接起不来。
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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\(\)/);
|
||||
|
||||
Reference in New Issue
Block a user