diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js
index fa8a2ca..7457157 100644
--- a/web/storyforge-web-v4/assets/app.js
+++ b/web/storyforge-web-v4/assets/app.js
@@ -35,6 +35,14 @@ const appState = {
};
const INTEGRATION_ORDER = ["local_model", "cutvideo", "huobao", "n8n", "asr"];
+const ACTIVE_PLATFORMS = [
+ { value: "douyin", label: "抖音" },
+ { value: "xiaohongshu", label: "小红书" },
+ { value: "bilibili", label: "哔哩哔哩" },
+ { value: "kuaishou", label: "快手" },
+ { value: "wechat_video", label: "微信视频号" }
+];
+const ACTIVE_PLATFORM_CHIPS = ["全平台", "抖音", "小红书", "B站", "快手", "视频号"];
const INTEGRATION_META = {
local_model: {
label: "本机模型",
@@ -81,6 +89,24 @@ function safeArray(value) {
return Array.isArray(value) ? value : [];
}
+function getPlatformOptions() {
+ return ACTIVE_PLATFORMS.map((item) => ({ value: item.value, label: item.label }));
+}
+
+function normalizePlatformValue(value, fallback = "douyin") {
+ const normalized = String(value || "").trim().toLowerCase();
+ if (!normalized) return fallback;
+ const byValue = ACTIVE_PLATFORMS.find((item) => item.value === normalized);
+ if (byValue) return byValue.value;
+ const byLabel = ACTIVE_PLATFORMS.find((item) => item.label === value);
+ return byLabel?.value || fallback;
+}
+
+function platformLabel(value) {
+ const matched = ACTIVE_PLATFORMS.find((item) => item.value === normalizePlatformValue(value, ""));
+ return matched?.label || String(value || "抖音");
+}
+
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&")
@@ -2136,7 +2162,7 @@ function renderReviewScreen() {
${escapeHtml(brief(review.highlights || review.next_actions || review.notes || "已保存复盘,待继续补充表现数据。", 92))}
-
${escapeHtml(review.platform || "douyin")}
+
${escapeHtml(platformLabel(review.platform || "douyin"))}
${escapeHtml(review.verdict || "已记录")}
${review.publish_url ? `
打开链接` : ""}
编辑
@@ -2221,8 +2247,7 @@ function renderTopbar() {
topPills[2].textContent = `任务 ${formatNumber(appState.dashboard?.recent_jobs?.length || 0)}`;
}
if (platforms) {
- const chips = ["全平台", "抖音", "小红书", "B站", "YouTube"];
- platforms.innerHTML = chips.map((label, index) => `
${escapeHtml(label)}`).join("");
+ platforms.innerHTML = ACTIVE_PLATFORM_CHIPS.map((label, index) => `
${escapeHtml(label)}`).join("");
}
}
@@ -2399,18 +2424,11 @@ function openImportHomepageAction() {
const assistants = getAssistantOptions(project.id);
openActionModal({
title: "导入主页并同步",
- description: "适合抖音 / 小红书 / B站 / YouTube 主页。先建内容源,再触发同步与分析。",
+ description: "适合抖音 / 小红书 / B站 / 快手 / 视频号主页。先建内容源,再触发同步与分析。",
submitLabel: "开始同步",
fields: [
{ name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() },
- { name: "platform", label: "平台", type: "select", value: "douyin", options: [
- { value: "douyin", label: "抖音" },
- { value: "xiaohongshu", label: "小红书" },
- { value: "bilibili", label: "哔哩哔哩" },
- { value: "youtube", label: "YouTube" },
- { value: "kuaishou", label: "快手" },
- { value: "wechat_video", label: "微信视频号" }
- ] },
+ { name: "platform", label: "平台", type: "select", value: "douyin", options: getPlatformOptions() },
{ name: "title", label: "标题", placeholder: "例如:创业口播对标账号" },
{ name: "handle", label: "账号名 / handle", placeholder: "可选" },
{ name: "sourceUrl", label: "主页链接", type: "url", placeholder: "https://..." },
@@ -2420,12 +2438,13 @@ function openImportHomepageAction() {
onSubmit: async (values) => {
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: values.platform || "douyin",
+ platform,
handle: values.handle || "",
source_url: values.sourceUrl.trim(),
title: values.title || values.handle || "主页对标",
@@ -2439,7 +2458,7 @@ function openImportHomepageAction() {
knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "",
assistant_id: values.assistantId || "",
content_source_id: source.id,
- platform: values.platform || "douyin",
+ platform,
handle: values.handle || "",
source_url: values.sourceUrl.trim(),
title: values.title || values.handle || "主页对标",
@@ -2469,14 +2488,7 @@ function openImportSelectedAccountAction() {
submitLabel: currentSource ? "继续同步" : "导入并同步",
fields: [
{ name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() },
- { name: "platform", label: "平台", type: "select", value: "douyin", options: [
- { value: "douyin", label: "抖音" },
- { value: "xiaohongshu", label: "小红书" },
- { value: "bilibili", label: "哔哩哔哩" },
- { value: "youtube", label: "YouTube" },
- { value: "kuaishou", label: "快手" },
- { value: "wechat_video", label: "微信视频号" }
- ] },
+ { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(currentSource?.platform || "douyin"), options: getPlatformOptions() },
{ name: "title", label: "内容源标题", value: currentSource?.title || `${account.nickname || account.douyin_id || "对标账号"} 对标主页` },
{ name: "handle", label: "账号标识", value: currentSource?.handle || account.douyin_id || "" },
{ name: "sourceUrl", label: "主页链接", type: "url", value: currentSource?.source_url || account.profile_url || "", placeholder: "https://..." },
@@ -2488,6 +2500,7 @@ function openImportSelectedAccountAction() {
onSubmit: async (values) => {
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", {
@@ -2495,7 +2508,7 @@ function openImportSelectedAccountAction() {
body: {
project_id: projectId,
source_kind: "creator_account",
- platform: values.platform || "douyin",
+ platform,
handle: values.handle || "",
source_url: values.sourceUrl.trim(),
title: values.title || values.handle || account.nickname || "对标主页",
@@ -2512,7 +2525,7 @@ function openImportSelectedAccountAction() {
knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "",
assistant_id: values.assistantId || "",
content_source_id: source.id,
- platform: values.platform || "douyin",
+ platform,
handle: values.handle || account.douyin_id || "",
source_url: values.sourceUrl.trim(),
title: values.title || account.nickname || values.handle || "对标主页",
@@ -2993,12 +3006,7 @@ function openGenerateCopyAction(defaults = {}) {
submitLabel: "开始生成",
fields: [
{ name: "brief", label: "创作需求", type: "textarea", rows: 5, value: defaults.brief || getJobSeedBrief(sourceJob), placeholder: "例如:给创业者写一条 60 字内的短视频开场文案" },
- { name: "platform", label: "平台", type: "select", value: "抖音", options: [
- { value: "抖音", label: "抖音" },
- { value: "小红书", label: "小红书" },
- { value: "哔哩哔哩", label: "哔哩哔哩" },
- { value: "YouTube", label: "YouTube" }
- ] },
+ { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(defaults.platform || "douyin"), options: getPlatformOptions() },
{ name: "audience", label: "受众", value: "创业者" },
{ name: "extraRequirements", label: "额外要求", placeholder: "例如:强结论开头,结尾带 CTA" }
],
@@ -3008,7 +3016,7 @@ function openGenerateCopyAction(defaults = {}) {
method: "POST",
body: {
brief: values.brief.trim(),
- platform: values.platform || "抖音",
+ platform: platformLabel(values.platform || "douyin"),
audience: values.audience || "创业者",
extra_requirements: values.extraRequirements || "",
knowledge_base_ids: safeArray(assistant.knowledge_base_ids)
@@ -3127,14 +3135,7 @@ function openReviewAction(defaults = {}) {
{ name: "title", label: "标题", value: existingReview?.title || defaults.title || sourceJob?.title || "", placeholder: "例如:创业口播 3 月 22 日复盘" },
{ name: "sourceJobId", label: "关联任务", type: "select", value: existingReview?.source_job_id || defaults.sourceJobId || sourceJob?.id || "", options: [{ value: "", label: "不关联任务" }, ...getCompletedJobOptions()] },
{ name: "assistantId", label: "负责 Agent", type: "select", value: existingReview?.assistant_id || getSelectedAssistant()?.id || assistants[0]?.value || "", options: [{ value: "", label: "先不绑定" }, ...assistants] },
- { name: "platform", label: "平台", type: "select", value: existingReview?.platform || defaults.platform || "douyin", options: [
- { value: "douyin", label: "抖音" },
- { value: "xiaohongshu", label: "小红书" },
- { value: "bilibili", label: "哔哩哔哩" },
- { value: "youtube", label: "YouTube" },
- { value: "kuaishou", label: "快手" },
- { value: "wechat_video", label: "微信视频号" }
- ] },
+ { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(existingReview?.platform || defaults.platform || "douyin"), options: getPlatformOptions() },
{ name: "contentType", label: "内容类型", type: "select", value: existingReview?.content_type || "video", options: [
{ value: "video", label: "视频" },
{ value: "image_text", label: "图文" },
@@ -3164,7 +3165,7 @@ function openReviewAction(defaults = {}) {
source_job_id: values.sourceJobId || "",
assistant_id: values.assistantId || "",
title: values.title.trim(),
- platform: values.platform || "douyin",
+ platform: normalizePlatformValue(values.platform, "douyin"),
content_type: values.contentType || "video",
publish_url: values.publishUrl || "",
published_at: values.publishedAt || "",
diff --git a/web/storyforge-web-v4/index.html b/web/storyforge-web-v4/index.html
index f482df1..a8ba636 100644
--- a/web/storyforge-web-v4/index.html
+++ b/web/storyforge-web-v4/index.html
@@ -94,7 +94,8 @@
抖音
小红书
B站
-
YouTube
+
快手
+
视频号