feat: expose multi-platform and recorder controls in web v4

This commit is contained in:
kris
2026-03-23 09:21:15 +08:00
parent f9e34287db
commit 660f539204

View File

@@ -35,7 +35,7 @@ const appState = {
lastJobDetail: null
};
const INTEGRATION_ORDER = ["local_model", "cutvideo", "huobao", "n8n", "asr"];
const INTEGRATION_ORDER = ["local_model", "live_recorder", "cutvideo", "huobao", "n8n", "asr"];
const ACTIVE_PLATFORMS = [
{ value: "douyin", label: "抖音" },
{ value: "xiaohongshu", label: "小红书" },
@@ -44,50 +44,54 @@ const ACTIVE_PLATFORMS = [
{ value: "wechat_video", label: "微信视频号" }
];
const ACTIVE_PLATFORM_CHIPS = ["全平台", "抖音", "小红书", "B站", "快手", "视频号"];
function makePlatformRoutes(platform) {
return {
accounts: `/v2/${platform}/accounts`,
workspace: (accountId) => `/v2/${platform}/accounts/${encodeURIComponent(accountId)}/workspace`,
videos: (accountId) => `/v2/${platform}/accounts/${encodeURIComponent(accountId)}/videos?limit=80`,
analyzeAccount: (accountId) => `/v2/${platform}/accounts/${encodeURIComponent(accountId)}/analysis`,
analyzeTopVideos: (accountId) => `/v2/${platform}/accounts/${encodeURIComponent(accountId)}/videos/analyze-top`,
similarSearches: `/v2/${platform}/similar-searches`,
similarSearchDetail: (searchId) => `/v2/${platform}/similar-searches/${encodeURIComponent(searchId)}`,
benchmarkLinks: (accountId) => `/v2/${platform}/accounts/${encodeURIComponent(accountId)}/benchmark-links`,
trackingAccounts: `/v2/${platform}/tracking/accounts`,
trackingDigest: `/v2/${platform}/tracking/digest`,
trackingRefresh: `/v2/${platform}/tracking/refresh`,
trackingCursor: `/v2/${platform}/tracking/cursor`,
trackingAccountRefresh: (trackedAccountId) => `/v2/${platform}/tracking/accounts/${encodeURIComponent(trackedAccountId)}/refresh`
};
}
const PLATFORM_REGISTRY = {
douyin: {
label: "抖音",
shortLabel: "抖音",
workbenchReady: true,
routes: {
accounts: "/v2/douyin/accounts",
workspace: (accountId) => `/v2/douyin/accounts/${encodeURIComponent(accountId)}/workspace`,
videos: (accountId) => `/v2/douyin/accounts/${encodeURIComponent(accountId)}/videos?limit=80`,
analyzeAccount: (accountId) => `/v2/douyin/accounts/${encodeURIComponent(accountId)}/analysis`,
analyzeTopVideos: (accountId) => `/v2/douyin/accounts/${encodeURIComponent(accountId)}/videos/analyze-top`,
similarSearches: "/v2/douyin/similar-searches",
similarSearchDetail: (searchId) => `/v2/douyin/similar-searches/${encodeURIComponent(searchId)}`,
benchmarkLinks: (accountId) => `/v2/douyin/accounts/${encodeURIComponent(accountId)}/benchmark-links`,
trackingAccounts: "/v2/douyin/tracking/accounts",
trackingDigest: "/v2/douyin/tracking/digest",
trackingRefresh: "/v2/douyin/tracking/refresh",
trackingCursor: "/v2/douyin/tracking/cursor",
trackingAccountRefresh: (trackedAccountId) => `/v2/douyin/tracking/accounts/${encodeURIComponent(trackedAccountId)}/refresh`
}
routes: makePlatformRoutes("douyin")
},
xiaohongshu: {
label: "小红书",
shortLabel: "小红书",
workbenchReady: false,
pendingText: "小红书工作台待接入"
workbenchReady: true,
routes: makePlatformRoutes("xiaohongshu")
},
bilibili: {
label: "哔哩哔哩",
shortLabel: "B站",
workbenchReady: false,
pendingText: "B站工作台待接入"
workbenchReady: true,
routes: makePlatformRoutes("bilibili")
},
kuaishou: {
label: "快手",
shortLabel: "快手",
workbenchReady: false,
pendingText: "快手工作台待接入"
workbenchReady: true,
routes: makePlatformRoutes("kuaishou")
},
wechat_video: {
label: "微信视频号",
shortLabel: "视频号",
workbenchReady: false,
pendingText: "视频号工作台待接入"
workbenchReady: true,
routes: makePlatformRoutes("wechat_video")
}
};
const INTEGRATION_META = {
@@ -96,6 +100,11 @@ const INTEGRATION_META = {
hint: "OpenAI-compatible",
impacts: ["账号分析", "高分分析", "文案生成"]
},
live_recorder: {
label: "直播录制",
hint: "fnOS NAS",
impacts: ["直播源导入", "录制控制"]
},
cutvideo: {
label: "自动剪辑",
hint: "Windows cutvideo",
@@ -1251,6 +1260,10 @@ function getIntegrationCards() {
`<span class="tag clickable-tag" data-action="open-preferred-model">设主模型</span>`
].filter(Boolean).join("");
}
if (key === "live_recorder") {
extra = detail.baseUrl ? `服务地址:${detail.baseUrl}` : "当前未配置 NAS 录制服务";
actions = `<span class="tag clickable-tag" data-action="open-live-recorder">录制控制</span>`;
}
return {
key,
meta,
@@ -1286,7 +1299,7 @@ function getIntegrationOverview() {
? `自动链路受阻:${blockedActions.length}`
: `${reachableCount}/${cards.length} 项依赖在线`;
const subtitle = !availableCount
? "刷新后会显示 cutvideo / huobao / n8n / ASR 的真实状态。"
? "刷新后会显示直播录制 / cutvideo / huobao / n8n / ASR 的真实状态。"
: blockedActions.length
? blockedActions.join("")
: "AI 视频与实拍剪辑链路当前可直接发起。";
@@ -3333,6 +3346,38 @@ function openCreateRealCutAction(defaults = {}) {
});
}
function openLiveRecorderAction() {
const status = getIntegrationDetail("live_recorder");
openActionModal({
title: "直播录制控制",
description: status.reachable
? "把直播间链接导入到 NAS 录制服务,必要时直接触发开始录制。"
: "当前 NAS 录制服务不可达,先检查集成健康。",
submitLabel: "提交到录制服务",
fields: [
{ name: "raw", label: "直播源", type: "textarea", rows: 4, value: "原画,https://live.kuaishou.com/u/storyforge_anchor", placeholder: "一行一条,例如:原画,https://live.kuaishou.com/u/anchor" },
{ name: "autoStart", label: "导入后立即开始", type: "checkbox", value: true }
],
onSubmit: async (values) => {
if (!values.raw?.trim()) throw new Error("请填写直播源链接");
const imported = await storyforgeFetch("/v2/live-recorder/url-config/import", {
method: "POST",
body: { raw: values.raw.trim() }
});
let started = null;
if (values.autoStart) {
try {
started = await storyforgeFetch("/v2/live-recorder/recorder/start", { method: "POST", body: {} });
} catch (error) {
started = { ok: false, message: error.message };
}
}
rememberAction("直播录制已下发", "NAS 录制服务已接收最新直播源。", "green", { imported, started });
await bootstrap();
}
});
}
function openReviewAction(defaults = {}) {
const project = requireSelectedProject();
const assistants = getAssistantOptions(project.id);
@@ -3449,6 +3494,10 @@ document.addEventListener("click", async (event) => {
await refreshTrackingAccountsAction();
return;
}
if (name === "open-live-recorder") {
openLiveRecorderAction();
return;
}
if (name === "mark-tracking-read") {
await markTrackingDigestRead();
rememberAction("日报已标记", "当前跟踪摘要已更新为已读,下次会从新的时间点继续汇总。", "green");