const STORAGE_KEY = "storyforge-web-v4-session"; const SESSION_STORE = StoryForgeSessionStore.create(STORAGE_KEY); const DEFAULT_BACKEND_URL = StoryForgeApiClient.detectDefaultBackendUrl(); const RECOVERY_HISTORY_KEY = STORAGE_KEY + ":recovery-history"; const navButtons = document.querySelectorAll("[data-screen-target]"); const screens = Array.from(document.querySelectorAll("[data-screen]")); const screenMap = Object.fromEntries(screens.map((screen) => [screen.dataset.screen, screen])); const mobileScreenTitle = document.querySelector('[data-role="mobile-screen-title"]'); const mobileProjectTitle = document.querySelector('[data-role="mobile-project-title"]'); const mobileShellStatus = document.querySelector(".mobile-shell-status"); const SCREEN_LABELS = { dashboard: "项目总台", intake: "我的项目", discovery: "找对标", tracking: "跟踪账号", owned: "我的账号", playbook: "Agent", strategy: "我的策略", production: "生产中心", review: "发布与复盘", automation: "自动流程", credits: "额度", settings: "设置", "admin-workbench": "管理员配置台" }; const appState = { screen: window.location.hash.replace("#", "") || "dashboard", session: SESSION_STORE.loadStoredSession(), me: null, dashboard: null, contentSources: [], accounts: [], selectedAccountId: "", selectedAccountRequestToken: 0, selectedWorkspace: null, selectedVideos: { items: [], meta: {}, top_scored_video_ids: [], latest_video_ids: [], high_score_threshold: 60 }, snapshots: [], selectedSnapshotId: "", selectedSnapshotDetail: null, creatorFields: null, analysisReports: [], documents: [], discoveryQuery: "", currentPlatform: localStorage.getItem(STORAGE_KEY + ":currentPlatform") || "", selectedProjectId: "", dashboardOverviewTab: "project_progress", discoveryDetailTab: "overview", playbookDetailTab: "workspace", strategyDetailTab: "effective", productionDetailTab: "queue", automationDetailTab: "health", adminWorkbenchTab: "integrations", settingsDetailTab: "workspace", selectedAssistantId: "", lastSeenAt: SESSION_STORE.getLastSeenAt(Date.now()), trackingCursorMap: {}, trackingAccounts: [], trackingDigest: null, trackingRefreshNotice: null, reviews: [], reviewFocusId: "", liveRecorderSources: [], liveRecorderStatus: null, liveRecorderFiles: [], liveRecorderHealth: null, storageStatus: null, integrationHealth: null, localModelCatalog: null, backendCapabilities: null, onelinerProfile: null, onelinerSessions: [], selectedOnelinerSessionId: "", onelinerRuns: [], onelinerRunFilter: "focus", selectedOnelinerRunId: "", lastCompletedOnelinerRunId: "", onelinerMessages: [], onelinerActionRegistry: [], platformAgents: [], onelinerGovernanceEffective: null, userGlobalPolicy: null, userCurrentPlatformPolicy: null, userPolicyAudits: [], adminSystemMainPolicy: null, adminSystemPlatformPolicies: [], adminGovernanceDirectory: null, adminOverrideTarget: null, adminOverridePolicy: null, adminPolicyAudits: [], tenantQuota: null, tenantUsage: null, adminOpsOverview: null, adminFixRuns: [], recoveryRecords: [], workspaceHydrating: false, autoConnectAttempted: false, autoConnectSuppressed: false, autoConnectError: "", dashboardActionReason: null, busy: false, message: "", lastAction: null, mainAgentLanding: null, lastGeneratedCopy: null, lastSimilaritySearch: null, lastJobDetail: null, topVideoAnalysisResults: {} }; let PLATFORM_RUNTIME = null; let onelinerRunPollTimer = null; let bootstrapHydrationToken = 0; const API_CLIENT = StoryForgeApiClient.create({ getSession: () => appState.session, defaultBackendUrl: DEFAULT_BACKEND_URL, getCapabilities: () => appState.backendCapabilities }); const INTEGRATION_ORDER = ["local_model", "live_recorder", "cutvideo", "huobao", "n8n", "asr"]; const ACTIVE_PLATFORMS = [ { value: "douyin", label: "抖音" }, { value: "xiaohongshu", label: "小红书" }, { value: "bilibili", label: "哔哩哔哩" }, { value: "kuaishou", label: "快手" }, { value: "wechat_video", label: "微信视频号" } ]; const makePlatformRoutes = StoryForgePlatformRuntime.makePlatformRoutes; const PLATFORM_REGISTRY = { douyin: { label: "抖音", shortLabel: "抖音", workbenchReady: true, routes: makePlatformRoutes("douyin") }, xiaohongshu: { label: "小红书", shortLabel: "小红书", workbenchReady: true, routes: makePlatformRoutes("xiaohongshu") }, bilibili: { label: "哔哩哔哩", shortLabel: "B站", workbenchReady: true, routes: makePlatformRoutes("bilibili") }, kuaishou: { label: "快手", shortLabel: "快手", workbenchReady: true, routes: makePlatformRoutes("kuaishou") }, wechat_video: { label: "微信视频号", shortLabel: "视频号", workbenchReady: true, routes: makePlatformRoutes("wechat_video") } }; PLATFORM_RUNTIME = StoryForgePlatformRuntime.create({ activePlatforms: ACTIVE_PLATFORMS, platformRegistry: PLATFORM_REGISTRY, appState, storage: localStorage, storageKey: STORAGE_KEY }); const INTEGRATION_META = { local_model: { label: "本机模型", hint: "OpenAI-compatible", impacts: ["账号分析", "高分分析", "文案生成"] }, live_recorder: { label: "直播录制", hint: "fnOS NAS", impacts: ["直播源导入", "录制控制"] }, cutvideo: { label: "自动剪辑", hint: "cutvideo 直连", impacts: ["实拍剪辑"] }, huobao: { label: "AI 视频", hint: "huobao-drama", impacts: ["AI 视频"] }, n8n: { label: "编排", hint: "n8n workflow", impacts: ["AI 视频", "实拍剪辑", "自动链路"] }, asr: { label: "ASR", hint: "素材转写", impacts: ["分析转写"] } }; const PIPELINE_GUARDS = { aiVideo: { label: "AI 视频", openAction: "direct-create-ai-video", jobAction: "direct-create-ai-video", dependencies: ["n8n", "huobao"] }, realCut: { label: "实拍剪辑", openAction: "direct-create-real-cut", jobAction: "direct-create-real-cut", dependencies: ["n8n", "cutvideo"] } }; const ONELINER_INTENT_LABELS = { create_project: "创建项目", create_assistant: "创建 Agent", import_homepage: "导入主页", track_account: "跟踪账号", analyze_account: "分析账号", analyze_top_videos: "分析高分作品", generate_copy: "生成文案", ai_video: "AI 视频", real_cut: "实拍剪辑", review: "发布复盘", live_recorder: "直播录制", storage_status: "存储状态", ops_admin: "运维巡检", custom: "自定义任务" }; function safeArray(value) { return Array.isArray(value) ? value : []; } function parseJsonSafe(value, fallback) { if (typeof value !== "string" || !value.trim()) return fallback; try { return JSON.parse(value); } catch { return fallback; } } function getRuntimePlatformValues() { return PLATFORM_RUNTIME.getRuntimePlatformValues(); } function getPlatformOptions() { return PLATFORM_RUNTIME.getPlatformOptions(); } function normalizePlatformValue(value, fallback = "douyin") { return PLATFORM_RUNTIME.normalizePlatformValue(value, fallback); } function platformLabel(value) { return PLATFORM_RUNTIME.platformLabel(value); } function getPlatformMeta(value) { return PLATFORM_RUNTIME.getPlatformMeta(value); } function getPlatformShortLabel(value) { return PLATFORM_RUNTIME.getPlatformShortLabel(value); } function getPlatformChips() { return PLATFORM_RUNTIME.getPlatformChips(); } function isWorkbenchPlatform(value) { return PLATFORM_RUNTIME.isWorkbenchPlatform(value); } function getWorkbenchRoute(platform, key, ...args) { return PLATFORM_RUNTIME.getWorkbenchRoute(platform, key, ...args); } function setCurrentPlatform(value) { return PLATFORM_RUNTIME.setCurrentPlatform(value); } function getAccountPlatform(account) { return PLATFORM_RUNTIME.getAccountPlatform(account); } function getAccountHandle(account) { return PLATFORM_RUNTIME.getAccountHandle(account); } function getAccountProfileUrl(account) { return PLATFORM_RUNTIME.getAccountProfileUrl(account); } function getAccountName(account) { return PLATFORM_RUNTIME.getAccountName(account); } function getAccountSubtitle(account) { return PLATFORM_RUNTIME.getAccountSubtitle(account); } function getPendingWorkbenchReason(platform) { return PLATFORM_RUNTIME.getPendingWorkbenchReason(platform); } function getAccountWorkbenchGate(account) { return PLATFORM_RUNTIME.getAccountWorkbenchGate(account); } function getPreferredPlatform() { return PLATFORM_RUNTIME.getPreferredPlatform(); } function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function brief(value, max = 88) { const text = String(value ?? "").trim(); if (!text) return "暂无"; return text.length > max ? text.slice(0, max).trimEnd() + "…" : text; } function formatNumber(value) { const num = Number(value || 0); if (!Number.isFinite(num)) return "-"; if (num >= 100000000) return (num / 100000000).toFixed(1).replace(/\.0$/, "") + "亿"; if (num >= 10000) return (num / 10000).toFixed(1).replace(/\.0$/, "") + "w"; if (num >= 1000) return num.toLocaleString("zh-CN"); return String(Math.round(num * 10) / 10); } function formatBytes(value) { const num = Number(value || 0); if (!Number.isFinite(num) || num <= 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = num; let idx = 0; while (size >= 1024 && idx < units.length - 1) { size /= 1024; idx += 1; } const fixed = size >= 10 || idx === 0 ? size.toFixed(0) : size.toFixed(1); return `${fixed}${units[idx]}`; } function renderIntakeActionContextHtml(projectId = "", assistantId = "") { const project = safeArray(appState.dashboard?.projects).find((item) => item.id === projectId) || getSelectedProject(); const assistant = safeArray(appState.dashboard?.assistants).find((item) => item.id === assistantId) || getSelectedAssistant(); const knowledgeBase = getProjectKnowledgeBases(project?.id || "")[0] || null; return `

这次会写入哪里

项目:${escapeHtml(project?.name || "当前项目")} · Agent:${escapeHtml(assistant?.name || "暂不绑定")} · 知识库:${escapeHtml(knowledgeBase?.name || "默认知识库")}

`; } function renderSourceJobContextHtml(job) { if (!job) return ""; return `

当前承接任务

${escapeHtml(job.title || "未命名任务")} · 平台:${escapeHtml(platformLabel(job.platform || "douyin"))} · 状态:${escapeHtml(job.status || "unknown")}

`; } function recommendAudienceForPlatform(platform) { const normalized = normalizePlatformValue(platform, "douyin"); if (normalized === "bilibili") return "深度内容观众"; if (normalized === "xiaohongshu") return "兴趣消费人群"; if (normalized === "kuaishou") return "成交导向受众"; if (normalized === "wechat_video") return "私域关系链用户"; return "创业者"; } function recommendAspectRatioForPlatform(platform) { const normalized = normalizePlatformValue(platform, "douyin"); return normalized === "bilibili" ? "16:9" : "9:16"; } function recommendCreativeStyle(sourceJob) { const style = String(sourceJob?.artifacts?.style || sourceJob?.style || "").trim(); if (style) return style; if (String(sourceJob?.job_type || "").includes("review")) return "structured"; return "realistic"; } function recommendRealCutDuration(sourceJob) { const candidate = Number( sourceJob?.artifacts?.target_duration_sec || sourceJob?.artifacts?.duration || sourceJob?.result?.target_duration_sec || 0 ); if (Number.isFinite(candidate) && candidate > 0) { return Math.max(10, Math.min(300, Math.round(candidate))); } return 60; } function recommendAiVideoShotDuration(sourceJob) { const candidate = Number( sourceJob?.artifacts?.duration || sourceJob?.result?.duration || 0 ); if (Number.isFinite(candidate) && candidate > 0) { return Math.max(3, Math.min(12, Math.round(candidate))); } return 5; } function recommendDerivativeJobTitle(sourceJob, suffix, fallback) { const base = String(sourceJob?.title || "").trim(); if (base) return `${base} · ${suffix}`; return fallback; } function recommendRealCutObjective(sourceJob) { const briefText = brief(getJobSeedBrief(sourceJob), 48); if (briefText && briefText !== "暂无") { return `围绕「${briefText}」保留高信息密度片段,输出适合短视频平台的粗剪结果`; } return "保留高信息密度片段,输出适合短视频平台的粗剪结果"; } function recommendLiveRecorderTitle(project, platform) { const normalized = normalizePlatformValue(platform, "kuaishou"); const projectName = String(project?.name || "").trim(); const platformName = platformLabel(normalized); if (projectName) return `${brief(projectName, 12)} · ${platformName}直播跟踪`; return `${platformName}直播跟踪`; } function recommendLiveRecorderImportSamples(platform) { const normalized = normalizePlatformValue(platform, "kuaishou"); if (normalized === "douyin") { return [ "https://live.douyin.com/1234567890", "# 关闭的源会以 # 开头", "原画, https://live.douyin.com/9988776655, 抖音主场跟踪" ].join("\n"); } return [ "https://live.kuaishou.com/u/abcdef", "# 关闭的源会以 # 开头", "高清, https://live.kuaishou.com/u/abcdef, 快手主场跟踪" ].join("\n"); } function recommendManualIntakeTitle(project, platform, kind) { const normalized = normalizePlatformValue(platform, "douyin"); const projectName = String(project?.name || "").trim(); const platformName = platformLabel(normalized); const suffix = String(kind || "").trim() || "素材"; if (projectName) return `${brief(projectName, 12)} · ${platformName}${suffix}`; return `${platformName}${suffix}`; } function bindManualIntakeTitleRecommendation(fields, kind, options = {}) { const titleInput = fields.querySelector('[data-action-field="title"]'); if (!(titleInput instanceof HTMLInputElement)) return; const projectField = options.projectField || "projectId"; const platformField = options.platformField || "platform"; const defaultPlatform = options.defaultPlatform || "douyin"; const projectSelect = fields.querySelector(`[data-action-field="${projectField}"]`); const platformSelect = fields.querySelector(`[data-action-field="${platformField}"]`); const sync = () => { const projectId = projectSelect instanceof HTMLSelectElement ? projectSelect.value : ""; const project = safeArray(appState.dashboard?.projects).find((item) => item.id === projectId) || getSelectedProject() || null; const platform = normalizePlatformValue( platformSelect instanceof HTMLSelectElement ? platformSelect.value : getPreferredPlatform(), defaultPlatform ); titleInput.placeholder = recommendManualIntakeTitle(project, platform, kind); }; projectSelect?.addEventListener("change", sync); platformSelect?.addEventListener("change", sync); sync(); } function bindActionContextRecommendation(fields, options = {}) { const contextHtml = fields.querySelector('[data-action-field="context"] .sheet-html'); const assistantSelect = fields.querySelector('[data-action-field="assistantId"]'); const projectField = options.projectField === undefined ? "projectId" : options.projectField; const projectSelect = projectField ? fields.querySelector(`[data-action-field="${projectField}"]`) : null; const defaultAssistantId = options.defaultAssistantId || ""; const syncAssistantOptions = () => { if (!(assistantSelect instanceof HTMLSelectElement) || !(projectSelect instanceof HTMLSelectElement)) return; const assistants = getAssistantOptions(projectSelect.value || ""); const currentValue = assistantSelect.value || ""; const fallbackValue = assistants.some((item) => item.value === currentValue) ? currentValue : assistants.some((item) => item.value === defaultAssistantId) ? defaultAssistantId : assistants[0]?.value || ""; assistantSelect.innerHTML = [{ value: "", label: "暂不绑定" }, ...assistants].map((item) => ` `).join(""); assistantSelect.value = fallbackValue; }; const sync = () => { syncAssistantOptions(); if (!(contextHtml instanceof HTMLElement)) return; const projectId = projectSelect instanceof HTMLSelectElement ? projectSelect.value : getSelectedProject()?.id || ""; const assistantId = assistantSelect instanceof HTMLSelectElement ? assistantSelect.value : defaultAssistantId; contextHtml.innerHTML = renderIntakeActionContextHtml(projectId, assistantId); }; projectSelect?.addEventListener("change", sync); assistantSelect?.addEventListener("change", sync); sync(); } function bindAssistantSheetRecommendations(fields, options = {}) { const contextHtml = fields.querySelector('[data-action-field="context"] .sheet-html'); const projectSelect = fields.querySelector('[data-action-field="projectId"]'); const knowledgeBaseSelect = fields.querySelector('[data-action-field="knowledgeBaseId"]'); const defaultProjectId = String(options.defaultProjectId || getSelectedProject()?.id || "").trim(); const defaultKnowledgeBaseId = String(options.defaultKnowledgeBaseId || "").trim(); const defaultAssistantId = String(options.defaultAssistantId || "").trim(); const syncKnowledgeBaseOptions = () => { if (!(knowledgeBaseSelect instanceof HTMLSelectElement)) return; const projectId = projectSelect instanceof HTMLSelectElement ? projectSelect.value : defaultProjectId; const knowledgeBases = getKnowledgeBaseOptions(projectId); const currentValue = knowledgeBaseSelect.value || ""; const fallbackValue = knowledgeBases.some((item) => item.value === currentValue) ? currentValue : knowledgeBases.some((item) => item.value === defaultKnowledgeBaseId) ? defaultKnowledgeBaseId : knowledgeBases[0]?.value || ""; knowledgeBaseSelect.innerHTML = [{ value: "", label: "暂不绑定" }, ...knowledgeBases].map((item) => ` `).join(""); knowledgeBaseSelect.value = fallbackValue; }; const syncContext = () => { if (!(contextHtml instanceof HTMLElement)) return; const projectId = projectSelect instanceof HTMLSelectElement ? projectSelect.value : defaultProjectId; contextHtml.innerHTML = renderIntakeActionContextHtml(projectId, defaultAssistantId); }; const sync = () => { syncKnowledgeBaseOptions(); syncContext(); }; projectSelect?.addEventListener("change", sync); sync(); } function getCompletedJobById(jobId = "") { const normalizedId = String(jobId || "").trim(); if (!normalizedId) return null; return safeArray(appState.dashboard?.recent_jobs).find((item) => item.id === normalizedId) || (appState.lastJobDetail?.job?.id === normalizedId ? appState.lastJobDetail.job : null) || null; } function bindCreativeSourceJobRecommendations(fields, options = {}) { const sourceJobSelect = fields.querySelector('[data-action-field="sourceJobId"]'); if (!(sourceJobSelect instanceof HTMLSelectElement)) return; const sourceContext = fields.querySelector('[data-action-field="sourceJobContext"] .sheet-html'); const contextHtml = fields.querySelector('[data-action-field="context"] .sheet-html'); const assistantSelect = fields.querySelector('[data-action-field="assistantId"]'); const platformSelect = fields.querySelector(`[data-action-field="${options.platformField || "platform"}"]`); const defaultPlatform = options.defaultPlatform || "douyin"; const defaultAssistantId = String(options.defaultAssistantId || "").trim(); const projectId = String(options.projectId || getSelectedProject()?.id || "").trim(); const managedFields = []; const resolveSourceJob = () => getCompletedJobById(sourceJobSelect.value) || options.sourceJob || null; const recommendedPlatform = (job) => normalizePlatformValue(job?.platform || getPreferredPlatform(), defaultPlatform); const registerManagedField = (field, compute, eventName = field instanceof HTMLSelectElement ? "change" : "input") => { if (!(field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement || field instanceof HTMLSelectElement)) return; field.dataset.recommendationMode = field.dataset.recommendationMode || "auto"; field.addEventListener(eventName, () => { if (field.dataset.recommendationApplying === "1") return; field.dataset.recommendationMode = "manual"; }); managedFields.push({ field, compute }); }; const applyManagedValue = (field, value) => { if (value == null || value === "") return; field.dataset.recommendationApplying = "1"; field.value = String(value); field.dataset.recommendationMode = "auto"; delete field.dataset.recommendationApplying; }; const titleInput = fields.querySelector('[data-action-field="title"]'); if (options.titleSuffix && titleInput instanceof HTMLInputElement) { registerManagedField(titleInput, (job) => recommendDerivativeJobTitle(job, options.titleSuffix, options.titleFallback || "")); } if (platformSelect instanceof HTMLSelectElement) { registerManagedField(platformSelect, (job) => recommendedPlatform(job), "change"); } if (assistantSelect instanceof HTMLSelectElement) { registerManagedField(assistantSelect, (job) => { const preferredAssistantId = String(job?.assistant_id || defaultAssistantId).trim(); if (preferredAssistantId && safeArray(Array.from(assistantSelect.options)).some((item) => item.value === preferredAssistantId)) { return preferredAssistantId; } if (defaultAssistantId && safeArray(Array.from(assistantSelect.options)).some((item) => item.value === defaultAssistantId)) { return defaultAssistantId; } return assistantSelect.value || ""; }, "change"); } const audienceInput = fields.querySelector('[data-action-field="audience"]'); if (audienceInput instanceof HTMLInputElement) { registerManagedField(audienceInput, (job) => { const platformValue = platformSelect instanceof HTMLSelectElement ? platformSelect.value : recommendedPlatform(job); return recommendAudienceForPlatform(platformValue); }); } const styleInput = fields.querySelector('[data-action-field="style"]'); if (styleInput instanceof HTMLInputElement) { registerManagedField(styleInput, (job) => recommendCreativeStyle(job)); } const aspectRatioField = fields.querySelector('[data-action-field="aspectRatio"]'); if (aspectRatioField instanceof HTMLInputElement || aspectRatioField instanceof HTMLSelectElement) { registerManagedField( aspectRatioField, (job) => { const platformValue = platformSelect instanceof HTMLSelectElement ? platformSelect.value : recommendedPlatform(job); return job?.artifacts?.aspect_ratio || recommendAspectRatioForPlatform(platformValue); }, aspectRatioField instanceof HTMLSelectElement ? "change" : "input" ); } const durationField = fields.querySelector('[data-action-field="duration"]'); if (durationField instanceof HTMLInputElement) { registerManagedField(durationField, (job) => recommendAiVideoShotDuration(job)); } const targetDurationField = fields.querySelector('[data-action-field="targetDurationSec"]'); if (targetDurationField instanceof HTMLInputElement) { registerManagedField(targetDurationField, (job) => recommendRealCutDuration(job)); } const objectiveField = fields.querySelector('[data-action-field="objective"]'); if (objectiveField instanceof HTMLTextAreaElement) { registerManagedField(objectiveField, (job) => recommendRealCutObjective(job)); } const sync = () => { const sourceJob = resolveSourceJob(); if (sourceContext instanceof HTMLElement) { sourceContext.innerHTML = sourceJob ? renderSourceJobContextHtml(sourceJob) : ""; sourceContext.parentElement?.classList.toggle("hidden", !sourceJob); } managedFields.forEach(({ field, compute }) => { if (field.dataset.recommendationMode === "manual") return; applyManagedValue(field, compute(sourceJob)); }); if (contextHtml instanceof HTMLElement) { const assistantId = assistantSelect instanceof HTMLSelectElement ? assistantSelect.value : defaultAssistantId; contextHtml.innerHTML = renderIntakeActionContextHtml(projectId, assistantId); } }; sourceJobSelect.addEventListener("change", sync); platformSelect?.addEventListener("change", sync); assistantSelect?.addEventListener("change", sync); sync(); } function bindLiveRecorderSheetRecommendations(fields, options = {}) { const projectSelect = fields.querySelector('[data-action-field="projectId"]'); const platformSelect = fields.querySelector('[data-action-field="platform"]'); const titleInput = fields.querySelector('[data-action-field="title"]'); const assistantSelect = fields.querySelector('[data-action-field="assistantId"]'); const rawTextarea = fields.querySelector('[data-action-field="raw"]'); const defaultPlatform = options.defaultPlatform || "kuaishou"; const syncAssistants = () => { if (!(assistantSelect instanceof HTMLSelectElement) || !(projectSelect instanceof HTMLSelectElement)) return; const projectId = projectSelect.value || ""; const assistants = getAssistantOptions(projectId); const currentValue = assistantSelect.value || ""; assistantSelect.innerHTML = [ { value: "", label: "暂不绑定" }, ...assistants ].map((item) => ` `).join(""); const stillExists = safeArray(assistants).some((item) => item.value === currentValue); if (!stillExists && currentValue) { assistantSelect.value = ""; } }; const syncTitle = () => { if (!(titleInput instanceof HTMLInputElement)) return; const projectId = projectSelect instanceof HTMLSelectElement ? projectSelect.value : ""; const project = safeArray(appState.dashboard?.projects).find((item) => item.id === projectId) || getSelectedProject() || null; const platform = normalizePlatformValue( platformSelect instanceof HTMLSelectElement ? platformSelect.value : getPreferredPlatform(), defaultPlatform ); titleInput.placeholder = recommendLiveRecorderTitle(project, platform); }; const syncRawSamples = () => { if (!(rawTextarea instanceof HTMLTextAreaElement)) return; if (rawTextarea.dataset.recommendationMode === "manual") return; const platform = normalizePlatformValue( platformSelect instanceof HTMLSelectElement ? platformSelect.value : getPreferredPlatform(), defaultPlatform ); rawTextarea.dataset.recommendationApplying = "1"; rawTextarea.value = recommendLiveRecorderImportSamples(platform); rawTextarea.dataset.recommendationMode = "auto"; delete rawTextarea.dataset.recommendationApplying; }; rawTextarea?.addEventListener("input", () => { if (rawTextarea.dataset.recommendationApplying === "1") return; rawTextarea.dataset.recommendationMode = "manual"; }); const sync = () => { syncAssistants(); syncTitle(); syncRawSamples(); }; projectSelect?.addEventListener("change", sync); platformSelect?.addEventListener("change", sync); sync(); } function bindLiveRecorderSheetRecommendations(fields, options = {}) { const projectSelect = fields.querySelector('[data-action-field="projectId"]'); const assistantSelect = fields.querySelector('[data-action-field="assistantId"]'); const platformSelect = fields.querySelector('[data-action-field="platform"]'); const titleInput = fields.querySelector('[data-action-field="title"]'); const rawTextarea = fields.querySelector('[data-action-field="raw"]'); const contextHtml = fields.querySelector('[data-action-field="context"] .sheet-html'); const defaultPlatform = options.defaultPlatform || "kuaishou"; const defaultAssistantId = options.defaultAssistantId || ""; const keepAutoValue = (field, value) => { if (!(field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement) || !String(value || "").trim()) return; field.placeholder = String(value); if (field.dataset.recommendationMode === "manual") return; field.dataset.recommendationApplying = "1"; field.value = String(value); field.dataset.recommendationMode = "auto"; delete field.dataset.recommendationApplying; }; const bindManualEscape = (field) => { if (!(field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement)) return; field.dataset.recommendationMode = field.dataset.recommendationMode || "auto"; field.addEventListener("input", () => { if (field.dataset.recommendationApplying === "1") return; field.dataset.recommendationMode = "manual"; }); }; bindManualEscape(titleInput); bindManualEscape(rawTextarea); const getProject = () => { const projectId = projectSelect instanceof HTMLSelectElement ? projectSelect.value : ""; return safeArray(appState.dashboard?.projects).find((item) => item.id === projectId) || getSelectedProject() || null; }; const getPlatform = () => normalizePlatformValue( platformSelect instanceof HTMLSelectElement ? platformSelect.value : getPreferredPlatform(), defaultPlatform ); const syncAssistantOptions = () => { if (!(assistantSelect instanceof HTMLSelectElement) || !(projectSelect instanceof HTMLSelectElement)) return; const currentValue = assistantSelect.value || ""; const assistants = getAssistantOptions(projectSelect.value || ""); const fallbackValue = assistants.some((item) => item.value === currentValue) ? currentValue : assistants.some((item) => item.value === defaultAssistantId) ? defaultAssistantId : assistants[0]?.value || ""; assistantSelect.innerHTML = [{ value: "", label: "暂不绑定" }, ...assistants].map((item) => ` `).join(""); assistantSelect.value = fallbackValue; }; const syncContext = () => { if (!(contextHtml instanceof HTMLElement)) return; const project = getProject(); const assistantId = assistantSelect instanceof HTMLSelectElement ? assistantSelect.value : defaultAssistantId; contextHtml.innerHTML = renderIntakeActionContextHtml(project?.id || "", assistantId); }; const syncTitle = () => { const project = getProject(); const platform = getPlatform(); const recommended = recommendLiveRecorderTitle(project, platform); if (titleInput instanceof HTMLInputElement) { keepAutoValue(titleInput, recommended); } }; const syncRawSamples = () => { if (!(rawTextarea instanceof HTMLTextAreaElement)) return; keepAutoValue(rawTextarea, recommendLiveRecorderImportSamples(getPlatform())); }; const syncDescription = () => { if (!(options.description instanceof HTMLElement)) return; options.description.textContent = `按行粘贴${platformLabel(getPlatform())}直播源,支持用逗号附带清晰度和标题,注释行会被视为停用源。`; }; const sync = () => { syncAssistantOptions(); syncContext(); syncTitle(); syncRawSamples(); syncDescription(); }; projectSelect?.addEventListener("change", sync); assistantSelect?.addEventListener("change", syncContext); platformSelect?.addEventListener("change", sync); sync(); } function formatDateTime(value) { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return String(value); return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }).format(date); } function formatDate(value) { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return String(value); return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit" }).format(date); } function daysSince(value) { if (!value) return "-"; const time = new Date(value).getTime(); if (!Number.isFinite(time)) return "-"; const diff = Date.now() - time; return Math.max(0, Math.floor(diff / 86400000)); } function initials(value) { const raw = String(value || "").trim(); if (!raw) return "SF"; return raw.slice(0, 2).toUpperCase(); } function statusTone(status) { const normalized = String(status || "").toLowerCase(); if (["completed", "ready", "approved", "ok"].includes(normalized)) return "green"; if (["failed", "error", "rejected"].includes(normalized)) return "red"; if (["worth_scaling", "good_reference"].includes(normalized)) return "green"; if (["needs_rework"].includes(normalized)) return "red"; if (["hold"].includes(normalized)) return "orange"; if (["running", "processing", "pending", "queued"].includes(normalized)) return "orange"; return "blue"; } function readRecoveryRecords() { try { const raw = localStorage.getItem(RECOVERY_HISTORY_KEY); const parsed = raw ? JSON.parse(raw) : []; return safeArray(parsed); } catch { return []; } } function persistRecoveryRecords(records) { try { localStorage.setItem(RECOVERY_HISTORY_KEY, JSON.stringify(safeArray(records).slice(0, 40))); } catch {} } function normalizeRecoveryRecord(record) { if (!record || typeof record !== "object") return null; return { id: String(record.id || `recovery_${Date.now()}`), created_at: record.created_at || new Date().toISOString(), account_id: String(record.account_id || ""), project_id: String(record.project_id || ""), job_id: String(record.job_id || ""), job_title: String(record.job_title || ""), job_line_type: String(record.job_line_type || ""), job_source_type: String(record.job_source_type || ""), job_status: String(record.job_status || "failed"), action_key: String(record.action_key || "retry-job"), mode: String(record.mode || "single"), summary: String(record.summary || ""), reason: String(record.reason || ""), target_job_id: String(record.target_job_id || ""), target_job_title: String(record.target_job_title || ""), target_status: String(record.target_status || ""), result_label: String(record.result_label || ""), result_reason: String(record.result_reason || ""), user_feedback: String(record.user_feedback || "") }; } function getRecoveryRecords() { const accountId = String(appState.session?.account?.id || ""); return readRecoveryRecords() .map((item) => normalizeRecoveryRecord(item)) .filter(Boolean) .filter((item) => !accountId || item.account_id === accountId) .sort((left, right) => compareDateDesc(left.created_at, right.created_at)); } function recordRecoveryEvent(record) { const normalized = normalizeRecoveryRecord({ ...record, account_id: record.account_id || appState.session?.account?.id || "", project_id: record.project_id || appState.selectedProjectId || "", created_at: record.created_at || new Date().toISOString() }); if (!normalized) return null; const nextRecords = [normalized, ...readRecoveryRecords()] .filter((item, index, list) => item && list.findIndex((candidate) => candidate.id === item.id) === index) .sort((left, right) => compareDateDesc(left.created_at, right.created_at)) .slice(0, 40); persistRecoveryRecords(nextRecords); appState.recoveryRecords = getRecoveryRecords(); return normalized; } function clearRecoveryRecords() { appState.recoveryRecords = []; } function isQuotaRelatedMessage(message) { const text = String(message || ""); return /配额|额度|预算|storage_over_limit|quota/i.test(text); } function describeActionError(error, fallbackTitle = "执行失败") { const raw = String(error?.message || error || "").trim(); const status = Number(error?.status || 0); const result = { title: fallbackTitle, summary: raw || "请稍后重试", tone: status === 403 ? "orange" : "red", hint: "" }; if (!raw) return result; const quotaPatterns = [ { pattern: /analysis 配额已用完/i, title: "分析额度已用完", summary: "当前租户本周期的分析额度已经用完,暂时不能继续发起分析类任务。", hint: "可以先补额度,或者等待下个周期再恢复。" }, { pattern: /copy 配额已用完/i, title: "文案额度已用完", summary: "当前租户本周期的文案额度已经用完,暂时不能继续生成文案。", hint: "可以先补额度,或者改用已有产物继续复盘。" }, { pattern: /ai_video 配额已用完/i, title: "AI 视频额度已用完", summary: "当前租户本周期的 AI 视频额度已经用完,暂时不能继续发起 AI 视频任务。", hint: "可以先补额度,再恢复这条任务。" }, { pattern: /real_cut 配额已用完/i, title: "实拍剪辑额度已用完", summary: "当前租户本周期的实拍剪辑额度已经用完,暂时不能继续发起剪辑任务。", hint: "可以先补额度,或者改为只做分析类恢复。" }, { pattern: /recorder 配额已用完/i, title: "录制额度已用完", summary: "当前租户本周期的录制额度已经用完,暂时不能新增直播录制源。", hint: "可以先补额度,再继续新增录制源。" }, { pattern: /预算不足|monthly budget/i, title: "本周期预算不足", summary: "当前租户本周期预算已经不够继续执行这项动作。", hint: "可以先扩容预算,再重新发起恢复。" }, { pattern: /存储额度已满|storage_over_limit/i, title: "存储额度已满", summary: "当前租户存储额度已触顶,系统已经停止继续产生大文件缓存。", hint: "建议先清理旧产物或提升存储上限。" } ]; for (const item of quotaPatterns) { if (item.pattern.test(raw)) { return { ...result, title: item.title, summary: item.summary, hint: item.hint, tone: "orange" }; } } if (status === 403 && /permission|not allowed|required|admin/i.test(raw)) { return { ...result, title: "权限不足", summary: "当前账号没有执行这项动作的权限。", hint: "请确认账号审批状态,或者切换到超级管理员账号。", tone: "orange" }; } if (status === 409) { return { ...result, title: "任务状态冲突", summary: raw, hint: "请刷新后重新查看当前任务状态。", tone: "orange" }; } if (status === 404) { return { ...result, title: "资源不存在", summary: raw, hint: "请刷新页面后再试一次。", tone: "orange" }; } if (isQuotaRelatedMessage(raw)) { return { ...result, title: "额度拦截", summary: raw, hint: "系统已经阻止继续执行,先补额度或清理存储再恢复。", tone: "orange" }; } return result; } function formatActionErrorMessage(error, fallbackTitle = "执行失败") { const detail = describeActionError(error, fallbackTitle); return [detail.title, detail.summary, detail.hint].filter(Boolean).join(" · "); } function isMissingBackendCapability(error) { const status = Number(error?.status || error?.payload?.status_code || 0); if (status === 404) return true; const raw = String(error?.message || error?.payload?.detail || error?.statusText || "").trim().toLowerCase(); return raw.includes("not found") || raw.includes("404"); } function presentActionFailure(error, fallbackTitle = "执行失败") { const detail = describeActionError(error, fallbackTitle); rememberAction(detail.title, [detail.summary, detail.hint].filter(Boolean).join(" · "), detail.tone); renderAll(); return detail; } function loadStoredSession() { return SESSION_STORE.loadStoredSession(); } function persistSession(session) { appState.session = session; SESSION_STORE.persistSession(session); } function setLastSeenAt(value) { appState.lastSeenAt = SESSION_STORE.setLastSeenAt(value); } function normalizeBackendUrlValue(value) { return String(value || "").trim().replace(/\/$/, ""); } function hasSessionBackendMismatch(expectedBackendUrl = DEFAULT_BACKEND_URL) { if (!appState.session) return false; const expected = normalizeBackendUrlValue(expectedBackendUrl); const current = normalizeBackendUrlValue(appState.session.backendUrl); return Boolean(expected && current && expected !== current); } function isMobileViewport() { return typeof window !== "undefined" && Boolean(window.matchMedia?.("(max-width: 760px)")?.matches); } function formatBackendDisplayLabel(value = DEFAULT_BACKEND_URL) { const normalized = normalizeBackendUrlValue(value || DEFAULT_BACKEND_URL); if (!normalized) return "未配置后端"; try { const parsed = new URL(normalized); return parsed.host || normalized; } catch { return normalized; } } function compareDateDesc(leftValue, rightValue) { return new Date(rightValue || 0).getTime() - new Date(leftValue || 0).getTime(); } function buildAssistantNameMap(items) { return new Map( safeArray(items) .filter((item) => item?.id) .map((item) => [item.id, item.name || ""]) ); } function getTrackingCursorForPlatform(platform = getCurrentPlatformValue()) { const normalizedPlatform = normalizePlatformValue(platform, ""); return appState.trackingCursorMap?.[normalizedPlatform] || ""; } function getTrackingSinceIso(platform = getCurrentPlatformValue()) { const cursor = getTrackingCursorForPlatform(platform) || appState.lastSeenAt || Date.now(); const date = new Date(cursor); if (Number.isNaN(date.getTime())) return new Date(Date.now() - 86400000).toISOString(); return date.toISOString(); } function enrichTrackingAccounts(items, accountIndex, assistantNameMap, fallbackPlatform) { return safeArray(items) .map((item) => { const account = accountIndex.get(item.tracked_account_id) || item.account || null; const normalizedPlatform = normalizePlatformValue( item.platform || account?.platform || fallbackPlatform || "", fallbackPlatform || "douyin" ); return { ...item, platform: normalizedPlatform, assistant_name: item.assistant_name || assistantNameMap.get(item.assistant_id) || "", account }; }) .sort((left, right) => compareDateDesc(left.updated_at || left.created_at, right.updated_at || right.created_at)); } function enrichTrackingDigestItems(items, accountIndex, fallbackPlatform) { return safeArray(items) .map((item) => { const account = item.account || accountIndex.get(item.tracked_account_id) || null; const platform = normalizePlatformValue( item.platform || item.video?.platform || account?.platform || fallbackPlatform || "", fallbackPlatform || "douyin" ); const performanceScore = Number(item.video?.score?.performance_score || 0); return { ...item, platform, account, summary: item.summary || item.summary_text || item.video?.description || item.video?.title || "已发现更新内容", is_high_value: typeof item.is_high_value === "boolean" ? item.is_high_value : performanceScore >= 60 }; }) .sort((left, right) => compareDateDesc( left.video?.published_at || left.created_at, right.video?.published_at || right.created_at )); } function markSeenNow() { setLastSeenAt(Date.now()); } function setBusy(next, message = "") { appState.busy = next; appState.message = message; renderAuthUi(); } function isWorkspaceBusy() { return appState.busy || appState.workspaceHydrating; } function getScreenLabel(screenId = appState.screen) { return SCREEN_LABELS[screenId] || "StoryForge"; } function getMobileProjectLabel() { const selectedProject = typeof getSelectedProject === "function" ? getSelectedProject() : null; if (selectedProject?.name) return selectedProject.name; if (appState.selectedWorkspace?.project?.name) return appState.selectedWorkspace.project.name; if (appState.selectedWorkspace?.account?.display_name) return appState.selectedWorkspace.account.display_name; return appState.me?.display_name || appState.me?.username || "当前工作区"; } function setMobileSidebarOpen(next) { document.body.classList.toggle("mobile-sidebar-open", Boolean(next)); } function syncMobileShell() { if (mobileScreenTitle) mobileScreenTitle.textContent = getScreenLabel(); if (mobileProjectTitle) mobileProjectTitle.textContent = getMobileProjectLabel(); if (mobileShellStatus) { mobileShellStatus.textContent = isWorkspaceBusy() ? "同步中" : (appState.session ? "已连接" : "连接状态"); } } function getScreenFromHash() { const next = window.location.hash.replace("#", ""); return screenMap[next] ? next : "dashboard"; } function getMobileTabGroup(screenId = appState.screen) { const groups = { dashboard: "dashboard", credits: "dashboard", settings: "dashboard", intake: "intake", owned: "intake", discovery: "discovery", tracking: "discovery", production: "production", review: "production", automation: "production", playbook: "playbook", strategy: "playbook", "admin-workbench": "playbook" }; return groups[screenId] || screenId || "dashboard"; } function setScreen(id, options = {}) { const { updateHash = true } = options; const resolvedId = screenMap[id] ? id : "dashboard"; const mobileGroup = getMobileTabGroup(resolvedId); setMobileSidebarOpen(false); appState.screen = resolvedId; navButtons.forEach((button) => { const active = button.dataset.screenTarget === resolvedId; const mobileGroupActive = button.classList.contains("mobile-tabbar-item") && button.dataset.screenTarget === mobileGroup; button.classList.toggle("is-active", active || mobileGroupActive); }); screens.forEach((screen) => { screen.classList.toggle("is-active", screen.dataset.screen === resolvedId); }); if (updateHash && window.location.hash !== `#${resolvedId}`) { window.location.hash = resolvedId; } syncMobileShell(); } function ensureAuthUi() { const topbarRight = document.querySelector(".topbar-right"); if (!topbarRight) return; if (!document.querySelector(".auth-inline")) { const inline = document.createElement("div"); inline.className = "auth-inline"; inline.innerHTML = ` `; topbarRight.insertBefore(inline, topbarRight.firstChild); } if (!document.querySelector(".auth-modal")) { const modal = document.createElement("div"); modal.className = "auth-modal-backdrop hidden"; modal.innerHTML = `

连接 StoryForge

当前站点会直接向后端请求自动会话,不再要求用户输入账号密码。

自动连接说明

前端只会请求固定后端的自动会话接口;如果当前部署没有开启自动建会话,这里会直接显示服务端返回的原因。

`; document.body.appendChild(modal); } } function renderAuthUi() { ensureAuthUi(); const session = appState.session; const openButton = document.querySelector('[data-action="open-auth"]'); const logoutButtons = document.querySelectorAll('[data-action="logout-session"]'); const status = document.querySelector(".auth-status"); const message = document.querySelector('[data-role="auth-message"]'); if (openButton) openButton.textContent = session ? "连接状态" : "自动连接"; logoutButtons.forEach((button) => { button.hidden = !session; }); if (status) { status.textContent = isWorkspaceBusy() ? appState.message || "正在加载..." : session ? `${session.account?.display_name || session.account?.username || "已连接"} · ${session.backendUrl}` : appState.autoConnectError || "等待自动连接"; } if (message) { message.textContent = isWorkspaceBusy() ? appState.message : (appState.autoConnectError || ""); } syncMobileShell(); } function openAuthModal() { ensureAuthUi(); const modal = document.querySelector(".auth-modal-backdrop"); if (!modal) return; const session = appState.session; document.body.classList.add("sheet-open", "auth-sheet-open"); modal.classList.remove("hidden"); setAuthField("backendUrl", session?.backendUrl || DEFAULT_BACKEND_URL); } function closeAuthModal() { document.body.classList.remove("sheet-open", "auth-sheet-open"); document.querySelector(".auth-modal-backdrop")?.classList.add("hidden"); } function setAuthField(name, value) { const input = document.querySelector(`[data-auth-field="${name}"]`); if (input) input.value = value ?? ""; } function readAuthForm() { return { backendUrl: document.querySelector('[data-auth-field="backendUrl"]')?.value?.trim() || DEFAULT_BACKEND_URL }; } let currentActionConfig = null; function ensureActionUi() { if (document.querySelector(".action-modal-backdrop")) return; const modal = document.createElement("div"); modal.className = "action-modal-backdrop hidden"; modal.innerHTML = `

快速操作

根据当前工作区执行动作。

`; document.body.appendChild(modal); } function renderActionFields(fields) { return fields.map((field) => { if (field.hidden) { return ""; } const common = `data-action-field="${escapeHtml(field.name)}"`; if (field.type === "html") { return `
${field.html || ""}
`; } if (field.type === "textarea") { return `
`; } if (field.type === "select") { return `
`; } if (field.type === "checkbox") { return ` `; } if (field.type === "file") { return `
`; } return `
`; }).join(""); } function openActionModal(config) { ensureActionUi(); currentActionConfig = config; const modal = document.querySelector(".action-modal-backdrop"); const title = document.querySelector('[data-role="action-title"]'); const description = document.querySelector('[data-role="action-description"]'); const fields = document.querySelector('[data-role="action-fields"]'); const message = document.querySelector('[data-role="action-message"]'); const submit = document.querySelector('[data-action="submit-sheet"]'); if (!modal || !title || !description || !fields || !message || !submit) return; title.textContent = config.title || "快速操作"; description.textContent = config.description || ""; fields.innerHTML = renderActionFields(config.fields || []); message.textContent = ""; submit.textContent = config.submitLabel || "执行"; submit.disabled = false; submit.hidden = Boolean(config.hideSubmit); document.body.classList.add("sheet-open", "action-sheet-open"); modal.classList.remove("hidden"); if (typeof config.onOpen === "function") { config.onOpen({ modal, title, description, fields, message, submit }); } } function closeActionModal() { currentActionConfig = null; document.body.classList.remove("sheet-open", "action-sheet-open"); document.querySelector(".action-modal-backdrop")?.classList.add("hidden"); } function ensureOneLinerUi() { if (!document.querySelector(".oneliner-fab")) { const fab = document.createElement("button"); fab.className = "oneliner-fab"; fab.type = "button"; fab.dataset.action = "open-oneliner"; fab.innerHTML = ` 1 OneLiner `; document.body.appendChild(fab); } if (!document.querySelector(".oneliner-backdrop")) { const panel = document.createElement("div"); panel.className = "oneliner-backdrop hidden"; panel.innerHTML = `

OneLiner

前端没上的需求,先由总控主 Agent 承接。

`; document.body.appendChild(panel); } } function renderOneLinerSessionTabs() { const sessions = safeArray(appState.onelinerSessions).slice(0, 6); if (!sessions.length) { return `
还没有会话发送第一条需求后自动创建
`; } const currentId = getCurrentOneLinerSession()?.id || ""; return `
${sessions.map((session) => ` ${escapeHtml(brief(session.title || "新会话", 14))} `).join("")}
`; } function renderOneLinerRunsHtml() { const runs = safeArray(appState.onelinerRuns); const currentRun = getCurrentOneLinerRun(); if (!runs.length || !currentRun) { return `

还没有主 Agent 运行任务

你在首页、策略页或 Agent 页点击“交给主 Agent”后,这里会先出现待确认执行卡。

`; } const runEvents = safeArray(currentRun.events).slice(-3); const planSteps = safeArray(currentRun.plan?.steps).slice(0, 4); const resultPayload = currentRun.result && typeof currentRun.result === "object" ? currentRun.result : null; const previewAction = currentRun.recommended_preview_action || null; const recommendedAction = resultPayload?.recommended_action || null; const pendingRunCount = safeArray(runs).filter((item) => item.run_status === "needs_confirmation").length; const activeRunCount = safeArray(runs).filter((item) => ["queued", "running", "blocked"].includes(item.run_status)).length; const completedRunCount = safeArray(runs).filter((item) => item.run_status === "done").length; const problemRunCount = safeArray(runs).filter((item) => ["blocked", "failed", "cancelled"].includes(item.run_status)).length; const filterKey = String(appState.onelinerRunFilter || "").trim() || (activeRunCount || pendingRunCount ? "focus" : completedRunCount ? "done" : "problems"); const runFilterPredicates = { focus: (item) => ["needs_confirmation", "queued", "running", "blocked"].includes(item.run_status), done: (item) => item.run_status === "done", problems: (item) => ["blocked", "failed", "cancelled"].includes(item.run_status), all: () => true }; const filteredRuns = safeArray(runs).filter(runFilterPredicates[filterKey] || runFilterPredicates.focus); const visibleRuns = (filteredRuns.length ? filteredRuns : runs).slice(0, 8); const recentCompletedRuns = safeArray(runs) .filter((item) => item.run_status === "done" && item.id !== currentRun.id) .slice(0, 3); const previewLandingAttrs = buildMainAgentLandingAttrs({ runId: currentRun.id || "", screen: previewAction?.screen || "", title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务", summary: previewAction?.summary || currentRun.summary || "" }); const resultLandingAttrs = buildRecommendedActionAttrs(recommendedAction, { runId: currentRun.id || "", screen: recommendedAction?.screen || "", title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务", summary: recommendedAction?.summary || currentRun.status_summary || "" }); const hasResultPayload = Boolean(resultPayload && Object.keys(resultPayload).length); const runStatusLabel = { needs_confirmation: "待确认", queued: "排队中", running: "执行中", blocked: "已阻塞", done: "已完成", failed: "已失败", cancelled: "已取消" }[currentRun.run_status] || currentRun.run_status || "运行中"; const statusTone = currentRun.run_status === "needs_confirmation" ? "blue" : currentRun.run_status === "running" ? "green" : currentRun.run_status === "queued" ? "orange" : currentRun.run_status === "failed" ? "orange" : ""; const canRetryCurrentRun = ["blocked", "failed", "cancelled"].includes(currentRun.run_status); const currentRunConfigVersion = currentRun.governance?.oneliner_profile_version || currentRun.governance?.oneliner_profile?.current_version || {}; const currentRunPlatformAgentProfile = currentRun.result?.execution_card?.platform_agent_profile || currentRun.governance?.platform_agent_profile || {}; const latestOnelinerConfigVersion = appState.onelinerProfile?.current_version || {}; const latestPlatformAgentProfile = safeArray(appState.platformAgents).find((item) => item.platform === currentRunPlatformAgentProfile.platform) || {}; const currentRunOnelinerConfigStale = isConfigurationVersionStale(currentRunConfigVersion, latestOnelinerConfigVersion); const currentRunPlatformAgentConfigStale = isConfigurationVersionStale( currentRunPlatformAgentProfile, latestPlatformAgentProfile.current_version || {} ); const retryRunLabel = currentRunOnelinerConfigStale || currentRunPlatformAgentConfigStale ? "按当前配置重跑" : "重新执行"; return `

近期运行概况

先看待确认和执行中的任务,再切回当前 run 继续推进。

待确认 ${escapeHtml(formatNumber(pendingRunCount))} 执行中 ${escapeHtml(formatNumber(activeRunCount))} 已完成 ${escapeHtml(formatNumber(completedRunCount))} 异常 ${escapeHtml(formatNumber(problemRunCount))}
重点运行 已完成 异常运行 全部
${runs.length > 1 ? `
${visibleRuns.map((item) => ` ${escapeHtml(brief(item.title || item.plan?.goal || "主 Agent 任务", 14))} · ${escapeHtml({ needs_confirmation: "待确认", queued: "排队中", running: "执行中", blocked: "阻塞", done: "已完成", failed: "失败", cancelled: "已取消" }[item.run_status] || item.run_status || "运行中")} `).join("")}
${!filteredRuns.length ? `
当前筛选下还没有对应运行,已临时显示全部任务。
` : ""} ` : ""}
当前运行 ${escapeHtml(runStatusLabel)} ${escapeHtml(currentRun.platform_scope === "all_platforms" ? "全平台" : "单平台")} ${currentRunConfigVersion.version_no ? `${escapeHtml(`配置 v${formatNumber(currentRunConfigVersion.version_no || 0)}`)}` : ""} ${escapeHtml(recommendedAction?.label || "继续这个任务")}
当前运行 ${escapeHtml(runStatusLabel)}

${escapeHtml(currentRun.summary || currentRun.status_summary || "主 Agent 会先给你一张确认卡,再继续执行。")}

${currentRun.run_status === "needs_confirmation" ? ` 确认执行 取消本轮 ` : ""} ${canRetryCurrentRun ? `${escapeHtml(retryRunLabel)}` : ""} ${hasResultPayload ? `查看结果` : ""} ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "继续这个任务")}` : ""}

${escapeHtml(currentRun.title || currentRun.plan?.goal || "主 Agent 任务")}

${escapeHtml(currentRun.summary || currentRun.status_summary || "主 Agent 会先给你一张确认卡,再继续执行。")}
${escapeHtml(runStatusLabel)} ${currentRun.platform_label ? `${escapeHtml(currentRun.platform_label)}` : ""} ${escapeHtml(onelinerIntentLabel(currentRun.intent_key))} ${currentRunConfigVersion.version_no ? `配置 v${escapeHtml(formatNumber(currentRunConfigVersion.version_no || 0))}` : ""} ${currentRun.source_screen ? `${escapeHtml(currentRun.source_screen)}` : ""}
${currentRun.active_admin_override_notice?.title ? `

管理员覆盖生效中

${escapeHtml(currentRun.active_admin_override_notice.summary || "当前运行会优先遵循管理员覆盖层。")}

` : ""}

当前计划

${escapeHtml(currentRun.plan?.summary || currentRun.summary || "主 Agent 会先按这张确认卡理解目标,再继续执行。")}

${escapeHtml(currentRun.source_action_key || "manual-handoff")} ${escapeHtml(currentRun.platform_scope === "all_platforms" ? "全平台" : "单平台")} ${currentRun.delivery_mode ? `${escapeHtml(currentRun.delivery_mode)}` : ""} ${currentRunConfigVersion.version_no ? `配置版本 ${escapeHtml(formatNumber(currentRunConfigVersion.version_no || 0))}` : ""} ${previewAction?.action ? `${escapeHtml(`预计落点 · ${previewAction.label || "对应页面"}`)}` : ""}
${currentRunConfigVersion.version_no ? `

本轮主配置版本

${escapeHtml(currentRunConfigVersion.summary || currentRunConfigVersion.title || `OneLiner 配置 v${formatNumber(currentRunConfigVersion.version_no || 0)}`)}

配置 v${escapeHtml(formatNumber(currentRunConfigVersion.version_no || 0))} ${currentRunOnelinerConfigStale ? `主配置已更新` : ""} 查看配置历史
` : ""} ${currentRunPlatformAgentProfile.platform ? `

本轮平台 Agent

${escapeHtml(currentRunPlatformAgentProfile.mission || currentRunPlatformAgentProfile.name || `${currentRunPlatformAgentProfile.platform_label || platformLabel(currentRunPlatformAgentProfile.platform)} Agent 正在承接这轮执行。`)}

${escapeHtml(currentRunPlatformAgentProfile.platform_label || platformLabel(currentRunPlatformAgentProfile.platform))} ${currentRunPlatformAgentProfile.name ? `${escapeHtml(currentRunPlatformAgentProfile.name)}` : ""} ${currentRunPlatformAgentProfile.assistant_name ? `${escapeHtml(currentRunPlatformAgentProfile.assistant_name)}` : ""} ${currentRunPlatformAgentProfile.readiness_label ? `= 50 ? "blue" : "orange"}">${escapeHtml(currentRunPlatformAgentProfile.readiness_label)} ${escapeHtml(formatNumber(currentRunPlatformAgentProfile.readiness_score || 0))}` : ""} ${currentRunPlatformAgentConfigStale ? `平台 Agent 已更新` : ""}
` : ""} ${!hasResultPayload && previewAction?.summary ? `
${escapeHtml(previewAction.summary)}
` : ""} ${planSteps.length ? `
${planSteps.map((step, index) => `

步骤 ${escapeHtml(formatNumber(index + 1))}

${escapeHtml(step)}

`).join("")}
` : ""}
${currentRun.run_status === "needs_confirmation" ? ` 确认执行 取消本轮 ` : ` ${escapeHtml(currentRun.status_summary || "主 Agent 正在推进中")} `} ${canRetryCurrentRun ? `${escapeHtml(retryRunLabel)}` : ""} ${hasResultPayload ? `查看结果` : ""} ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "回到对应页面")}` : ""}
${recommendedAction?.summary ? `
${escapeHtml(recommendedAction.summary)}
` : ""} ${hasResultPayload ? `

执行结果

${renderOneLinerExecutionPayloadHtml(currentRun.result)}
` : ""} ${runEvents.length ? `
${runEvents.map((item) => `

${escapeHtml(item.event_type || "run.progress")}

${escapeHtml(item.summary || "运行状态已更新。")}

`).join("")}
` : ""} ${recentCompletedRuns.length ? `

最近完成

${recentCompletedRuns.map((item) => { const doneAction = item.result?.recommended_action || null; const doneLandingAttrs = buildRecommendedActionAttrs(doneAction, { runId: item.id || "", screen: doneAction?.screen || "", title: item.title || item.plan?.goal || "主 Agent 任务", summary: doneAction?.summary || item.status_summary || "" }); return `

${escapeHtml(item.title || item.plan?.goal || "主 Agent 任务")}

${escapeHtml(item.status_summary || item.summary || "已完成,可继续回看结果。")}

已完成 查看结果 ${doneAction?.action ? `${escapeHtml(doneAction.label || "回到对应页面")}` : ""}
`; }).join("")}
` : ""}
`; } function renderOneLinerMessagesHtml() { const messages = safeArray(appState.onelinerMessages); if (!messages.length) { return `

还没有对话

你可以直接说目标,不用先理解平台有什么按钮。OneLiner 会先拆目标,再决定交给哪个平台 Agent。

`; } return messages.map((message) => { const roleClass = message.role === "assistant" ? "assistant" : "user"; const result = message.result || {}; const plan = message.plan || {}; const executionCard = result.execution_card || {}; const activeAdminOverrideNotice = executionCard.active_admin_override_notice || null; const profileVersion = executionCard.oneliner_profile_version || {}; const platformAgentProfile = executionCard.platform_agent_profile || {}; const actions = safeArray(plan.suggested_actions); const primaryAction = executionCard.primary_action || {}; const secondaryActions = safeArray(executionCard.secondary_actions); const buildOnelinerActionAttrs = (item) => { const attrs = [ item.executor_key ? `data-executor-key="${escapeHtml(item.executor_key)}"` : "", item.platform ? `data-platform="${escapeHtml(item.platform)}"` : "", message.session_id ? `data-session-id="${escapeHtml(message.session_id)}"` : "", ]; Object.entries(item.payload || {}).forEach(([payloadKey, payloadValue]) => { const attrKey = String(payloadKey || "") .replace(/([a-z0-9])([A-Z])/g, "$1-$2") .replace(/_/g, "-") .toLowerCase(); const serialized = typeof payloadValue === "string" ? payloadValue : JSON.stringify(payloadValue); attrs.push(`data-${escapeHtml(attrKey)}="${escapeHtml(serialized)}"`); }); return attrs.filter(Boolean).join(" "); }; return `
${escapeHtml(message.role === "assistant" ? "OneLiner" : "你")}

${escapeHtml(message.content || result.summary_text || "")}

${plan.intent_key ? `
${escapeHtml(onelinerIntentLabel(plan.intent_key))} ${plan.platform_label ? `${escapeHtml(plan.platform_label)}` : ""} ${plan.delivery_mode ? `${escapeHtml(plan.delivery_mode === "oneliner" ? "对话承接" : "可走前端")}` : ""}
` : ""} ${actions.length ? `
${actions.map((item) => actionTag( item.label || item.executor_label || item.key || "执行动作", item.key || "", buildOnelinerActionAttrs(item), { disabledReason: item.disabled_reason || "" } )).join("")}
` : ""} ${message.role === "assistant" && (executionCard.intent_label || executionCard.platform_label || executionCard.primary_action?.label || safeArray(executionCard.evidence).length) ? `

${escapeHtml(executionCard.intent_label || "本轮执行建议")}

${escapeHtml(executionCard.blocked_reason || `${executionCard.platform_label || "待判断平台"} · ${executionCard.delivery_mode === "ui" ? "优先走前端固定动作" : "优先由 OneLiner 对话承接"}`)}

${executionCard.platform_label ? `${escapeHtml(executionCard.platform_label)}` : ""} ${executionCard.platform_agent_name ? `${escapeHtml(executionCard.platform_agent_name)}` : ""} ${executionCard.assistant_name ? `${escapeHtml(executionCard.assistant_name)}` : ""} ${profileVersion.version_no ? `配置 v${escapeHtml(formatNumber(profileVersion.version_no || 0))}` : ""} ${profileVersion.version_no ? `看主配置历史` : ""} ${executionCard.platform && platformAgentProfile.version_no ? `看平台配置历史` : ""} ${executionCard.readiness_label ? `= 50 ? "blue" : "orange"}">${escapeHtml(executionCard.readiness_label)} ${escapeHtml(formatNumber(executionCard.readiness_score || 0))}` : ""} ${primaryAction.key ? actionTag( primaryAction.label || "执行下一步", primaryAction.key || "", buildOnelinerActionAttrs(primaryAction), { disabledReason: primaryAction.disabled_reason || "" } ) : ""}
${profileVersion.version_no ? `
${escapeHtml(profileVersion.summary || profileVersion.title || `当前按 OneLiner 配置 v${formatNumber(profileVersion.version_no || 0)} 执行。`)}
` : ""} ${activeAdminOverrideNotice?.title ? `

管理员覆盖生效中

${escapeHtml(activeAdminOverrideNotice.summary || "当前这轮执行会优先遵循管理员覆盖,再叠加你的个人策略。")}

${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")} ${activeAdminOverrideNotice.platform_label ? `${escapeHtml(activeAdminOverrideNotice.platform_label)}` : ""}
` : ""} ${safeArray(executionCard.evidence).length ? `
${safeArray(executionCard.evidence).slice(0, 2).map((item) => `

${escapeHtml(item.kind === "skill" ? "技能证据" : "记忆证据")} · ${escapeHtml(item.title || "未命名")}

${escapeHtml(item.summary || "暂无摘要")}

`).join("")}
` : ""} ${safeArray(executionCard.next_steps).length ? `
${safeArray(executionCard.next_steps).slice(0, 3).map((item) => `${escapeHtml(item)}`).join("")}
` : ""} ${secondaryActions.length ? `
${secondaryActions.map((item) => actionTag( item.label || item.key || "执行", item.key || "", buildOnelinerActionAttrs(item), { disabledReason: item.disabled_reason || "" } )).join("")}
` : ""}
` : ""}
`; }).join(""); } function renderOneLinerUi() { ensureOneLinerUi(); const fab = document.querySelector(".oneliner-fab"); const meta = document.querySelector('[data-role="oneliner-meta"]'); const runs = document.querySelector('[data-role="oneliner-runs"]'); const sessions = document.querySelector('[data-role="oneliner-sessions"]'); const messages = document.querySelector('[data-role="oneliner-messages"]'); const status = document.querySelector('[data-role="oneliner-status"]'); const input = document.querySelector('[data-role="oneliner-input"]'); const profile = appState.onelinerProfile; const effective = appState.onelinerGovernanceEffective; const activeAdminOverrideNotice = effective?.active_admin_override_notice || null; const highlights = summarizePolicyHighlights(effective?.effective_policy || {}, effective?.platform || ""); const layers = safeArray(effective?.layers); const currentRun = getCurrentOneLinerRun(); const activeRuns = safeArray(appState.onelinerRuns).filter((item) => !["done", "failed", "cancelled"].includes(item.run_status)); if (fab) { fab.hidden = !appState.session; const mark = fab.querySelector(".oneliner-fab-mark"); const text = fab.querySelector(".oneliner-fab-text"); if (mark) mark.textContent = String(activeRuns.length || 1); if (text) { text.textContent = currentRun ? `OneLiner · ${currentRun.run_status === "needs_confirmation" ? "待确认" : currentRun.run_status === "running" ? "执行中" : currentRun.run_status === "queued" ? "排队中" : "工作中"}` : "OneLiner"; } } if (meta) { meta.innerHTML = `
${escapeHtml(profile?.display_name || "OneLiner")} ${escapeHtml(getSelectedProject()?.name || "未选项目")} ${escapeHtml(profile?.default_platform ? platformLabel(profile.default_platform) : "未设默认平台")} ${profile?.current_version?.version_no ? `配置 v${escapeHtml(formatNumber(profile.current_version.version_no || 0))}` : ""} ${escapeHtml(formatNumber(safeArray(appState.platformAgents).length))} 个平台 Agent
${escapeHtml(profile?.long_term_goal || "当前还没有设长期目标。你可以先直接告诉主 Agent 你要推进的方向,它会按当前项目持续收敛执行策略。")}
${layers.map((layer) => `${escapeHtml(policyScopeTagLabel(layer.scope_kind, layer.scope?.platform || effective?.platform || ""))}`).join("") || `还没有策略层`} ${highlights.map((item) => `${escapeHtml(item)}`).join("")} 我的策略
${activeAdminOverrideNotice?.title ? `

管理员覆盖生效中

${escapeHtml(activeAdminOverrideNotice.summary || "当前主 Agent 会优先遵循管理员覆盖层。")}

${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")} 查看我的策略
` : ""} `; } if (runs) runs.innerHTML = renderOneLinerRunsHtml(); if (sessions) sessions.innerHTML = renderOneLinerSessionTabs(); if (messages) { messages.innerHTML = renderOneLinerMessagesHtml(); messages.scrollTop = messages.scrollHeight; } if (status) { status.textContent = appState.busy ? appState.message || "处理中..." : ""; } if (input && !input.value && !safeArray(appState.onelinerMessages).length) { input.value = ""; } syncOneLinerRunPolling(); } function clearOneLinerRunPollTimer() { if (onelinerRunPollTimer) { clearTimeout(onelinerRunPollTimer); onelinerRunPollTimer = null; } } function syncOneLinerRunPolling() { clearOneLinerRunPollTimer(); const panel = document.querySelector(".oneliner-backdrop"); const isPanelVisible = Boolean(panel && !panel.classList.contains("hidden")); const currentRun = getCurrentOneLinerRun(); const shouldPoll = Boolean( isPanelVisible && currentRun?.id && ["queued", "running", "blocked"].includes(currentRun.run_status) ); if (!shouldPoll) { return; } onelinerRunPollTimer = setTimeout(async () => { try { await hydrateSelectedOneLinerRun(); renderAll(); } catch { clearOneLinerRunPollTimer(); } }, 1500); } function openOneLinerPanel() { ensureOneLinerUi(); document.body.classList.add("sheet-open", "oneliner-open"); document.querySelector(".oneliner-backdrop")?.classList.remove("hidden"); syncOneLinerRunPolling(); } function closeOneLinerPanel() { document.body.classList.remove("sheet-open", "oneliner-open"); document.querySelector(".oneliner-backdrop")?.classList.add("hidden"); clearOneLinerRunPollTimer(); } function readActionForm() { const values = {}; document.querySelectorAll("[data-action-field]").forEach((element) => { const name = element.getAttribute("data-action-field"); if (!name) return; if (element instanceof HTMLInputElement && element.type === "checkbox") { values[name] = element.checked; return; } if (element instanceof HTMLInputElement && element.type === "file") { values[name] = element.files?.[0] || null; return; } values[name] = element.value; }); return values; } async function submitActionModal() { if (!currentActionConfig?.onSubmit) return; const message = document.querySelector('[data-role="action-message"]'); const submit = document.querySelector('[data-action="submit-sheet"]'); const values = readActionForm(); if (submit) submit.disabled = true; if (message) message.textContent = "正在执行..."; try { const result = await currentActionConfig.onSubmit(values); if (result?.keepOpen) { if (message) message.textContent = result.message || "已完成"; } else { closeActionModal(); } } catch (error) { if (message) message.textContent = formatActionErrorMessage(error); if (submit) submit.disabled = false; return; } if (submit) submit.disabled = false; } async function storyforgeFetch(path, options = {}) { return API_CLIENT.fetchJson(path, options); } async function storyforgeFetchBlob(path, options = {}) { return API_CLIENT.fetchBlob(path, options); } async function loadBackendCapabilities(backendUrl) { return API_CLIENT.loadBackendCapabilities(backendUrl); } function backendSupports(path) { return API_CLIENT.backendSupports(path); } async function loginWithAutoSession(backendUrl = DEFAULT_BACKEND_URL) { const payload = await storyforgeFetch("/v2/auth/auto-session", { backendUrl, auth: false, method: "POST", body: {} }); persistSession({ backendUrl, token: payload.token, account: payload.account }); appState.autoConnectError = ""; appState.autoConnectAttempted = true; appState.autoConnectSuppressed = false; } async function ensureAutoSession(options = {}) { const backendUrl = options.backendUrl || readAuthForm().backendUrl || DEFAULT_BACKEND_URL; const force = Boolean(options.force); const backendMismatch = hasSessionBackendMismatch(backendUrl); if (backendMismatch) { persistSession(null); } if (appState.session && !force) { return true; } if (appState.autoConnectSuppressed && !force) { return false; } if (appState.autoConnectAttempted && !force) { return Boolean(appState.session); } appState.autoConnectAttempted = true; renderAll(); try { await loginWithAutoSession(backendUrl); return true; } catch (error) { appState.autoConnectError = formatActionErrorMessage(error, "自动连接失败"); persistSession(null); renderAll(); return false; } } function isAutoConnectionPending() { return !appState.session && appState.autoConnectAttempted && !appState.autoConnectSuppressed && !appState.autoConnectError; } function renderAutoConnectingScreen(screenTitle, nextStepText) { return screenShell( screenTitle, "正在自动连接工作区,成功后会自动替换成真实内容。", `${button("连接状态", "open-auth", "primary")}`, renderEmptyState( "正在自动连接工作区", nextStepText || "如果超过几秒仍未进入真实页面,可以点右上角查看连接状态。" ) ); } async function refreshFromAuthModal() { const modal = document.querySelector(".auth-modal-backdrop"); const visible = modal && !modal.classList.contains("hidden"); if (!visible) { await bootstrap(); return; } appState.autoConnectSuppressed = false; appState.autoConnectAttempted = false; await ensureAutoSession({ force: true }); if (appState.session) closeAuthModal(); await bootstrap(); } async function logoutSession() { try { if (appState.session) { await storyforgeFetch("/v2/auth/logout", { method: "POST" }); } } catch {} persistSession(null); appState.autoConnectAttempted = true; appState.autoConnectSuppressed = true; appState.autoConnectError = "当前会话已退出。需要时可以点右上角重新自动连接。"; appState.me = null; appState.dashboard = null; appState.contentSources = []; appState.accounts = []; appState.selectedAccountId = ""; appState.currentPlatform = ""; appState.selectedAssistantId = ""; appState.selectedWorkspace = null; appState.selectedVideos = { items: [], meta: {}, top_scored_video_ids: [], latest_video_ids: [], high_score_threshold: 60 }; appState.snapshots = []; appState.selectedSnapshotId = ""; appState.selectedSnapshotDetail = null; appState.creatorFields = null; appState.analysisReports = []; appState.documents = []; appState.trackingAccounts = []; appState.trackingDigest = null; appState.reviews = []; appState.onelinerProfile = null; appState.onelinerSessions = []; appState.selectedOnelinerSessionId = ""; appState.onelinerRuns = []; appState.selectedOnelinerRunId = ""; appState.lastCompletedOnelinerRunId = ""; appState.onelinerMessages = []; appState.onelinerActionRegistry = []; appState.platformAgents = []; appState.onelinerGovernanceEffective = null; appState.userGlobalPolicy = null; appState.userCurrentPlatformPolicy = null; appState.userPolicyAudits = []; appState.adminSystemMainPolicy = null; appState.adminSystemPlatformPolicies = []; appState.adminGovernanceDirectory = null; appState.adminOverrideTarget = null; appState.adminOverridePolicy = null; appState.adminPolicyAudits = []; appState.tenantQuota = null; appState.tenantUsage = null; appState.adminOpsOverview = null; appState.adminFixRuns = []; clearRecoveryRecords(); appState.integrationHealth = null; appState.storageStatus = null; appState.liveRecorderHealth = null; appState.backendCapabilities = null; appState.lastAction = null; appState.lastGeneratedCopy = null; appState.lastSimilaritySearch = null; appState.lastJobDetail = null; localStorage.removeItem(STORAGE_KEY + ":currentPlatform"); renderAll(); } async function loadKnowledgeDocuments(knowledgeBases) { const targets = safeArray(knowledgeBases).slice(0, 3); if (!targets.length) return []; const groups = await Promise.all( targets.map((kb) => storyforgeFetch(`/v2/knowledge-bases/${encodeURIComponent(kb.id)}/documents`).catch(() => []) ) ); return groups.flat().slice(0, 12); } async function loadStorageStatus(projectId = "") { const suffix = projectId ? `?project_id=${encodeURIComponent(projectId)}` : ""; const payload = await storyforgeFetch(`/v2/storage/status${suffix}`).catch(() => null); appState.storageStatus = payload; return payload; } async function hydrateSelectedOneLinerRun() { const runId = appState.selectedOnelinerRunId || ""; if (!runId) { return null; } const detail = await storyforgeFetch(`/v2/oneliner/runs/${encodeURIComponent(runId)}`).catch(() => null); if (!detail?.id) return null; const runs = safeArray(appState.onelinerRuns); const nextRuns = runs.some((item) => item.id === detail.id) ? runs.map((item) => (item.id === detail.id ? detail : item)) : [detail, ...runs]; appState.onelinerRuns = nextRuns; if (detail.run_status === "done" && detail.id && appState.lastCompletedOnelinerRunId !== detail.id) { rememberAction("主 Agent 已完成本轮", detail.result?.execution_summary || detail.status_summary || detail.summary || "当前运行已经完成,可以继续执行下一步。", "green", detail); appState.lastCompletedOnelinerRunId = detail.id; } return detail; } async function loadAgentControlSurfaces(projectId = "") { const normalizedProjectId = projectId || getOneLinerProjectId(); const governancePlatform = normalizePlatformValue(getPreferredPlatform(), "douyin"); const [profile, sessionsPayload, runsPayload, actionRegistryPayload, platformAgentsPayload, governanceEffective, userGlobalPolicy, userCurrentPlatformPolicy, userPolicyAuditsPayload, adminSystemMainPolicy, adminSystemPlatformPolicies, adminGovernanceDirectory, tenantQuota, tenantUsage, adminOpsOverview, adminFixRunsPayload] = await Promise.all([ storyforgeFetch(`/v2/oneliner/profile?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null), storyforgeFetch(`/v2/oneliner/sessions?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })), storyforgeFetch(`/v2/oneliner/runs?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })), storyforgeFetch(`/v2/oneliner/action-registry?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })), storyforgeFetch(`/v2/platform-agents?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => ({ items: [] })), storyforgeFetch(`/v2/oneliner/governance/effective?project_id=${encodeURIComponent(normalizedProjectId)}&platform=${encodeURIComponent(governancePlatform)}`).catch(() => null), storyforgeFetch(`/v2/oneliner/governance/user/global?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null), storyforgeFetch(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(governancePlatform)}?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null), storyforgeFetch(`/v2/oneliner/governance/user/audits?project_id=${encodeURIComponent(normalizedProjectId)}&platform=${encodeURIComponent(governancePlatform)}`).catch(() => ({ items: [] })), isSuperAdmin() ? storyforgeFetch("/v2/admin/oneliner/governance/system/main-agent").catch(() => null) : Promise.resolve(null), isSuperAdmin() ? Promise.all(ACTIVE_PLATFORMS.map((item) => storyforgeFetch(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(item.value)}`).catch(() => null) )) : Promise.resolve([]), isSuperAdmin() ? storyforgeFetch("/v2/admin/oneliner/governance/directory").catch(() => ({ items: [] })) : Promise.resolve({ items: [] }), storyforgeFetch(`/v2/tenant/quota?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null), storyforgeFetch(`/v2/tenant/usage?project_id=${encodeURIComponent(normalizedProjectId)}`).catch(() => null), isSuperAdmin() ? storyforgeFetch("/v2/admin/ops/overview").catch(() => null) : Promise.resolve(null), isSuperAdmin() ? storyforgeFetch("/v2/admin/ops/fix-runs").catch(() => ({ items: [] })) : Promise.resolve({ items: [] }) ]); appState.onelinerProfile = profile; appState.onelinerSessions = safeArray(sessionsPayload?.items || sessionsPayload); appState.onelinerRuns = safeArray(runsPayload?.items || runsPayload); appState.onelinerActionRegistry = safeArray(actionRegistryPayload?.items || actionRegistryPayload); if (!appState.selectedOnelinerSessionId || !safeArray(appState.onelinerSessions).some((item) => item.id === appState.selectedOnelinerSessionId)) { appState.selectedOnelinerSessionId = safeArray(appState.onelinerSessions)[0]?.id || ""; } appState.selectedOnelinerRunId = choosePreferredOneLinerRunId(appState.onelinerRuns, appState.selectedOnelinerRunId || ""); if (appState.selectedOnelinerRunId) { await hydrateSelectedOneLinerRun(); } appState.platformAgents = safeArray(platformAgentsPayload?.items || platformAgentsPayload); appState.onelinerGovernanceEffective = governanceEffective; appState.userGlobalPolicy = userGlobalPolicy; appState.userCurrentPlatformPolicy = userCurrentPlatformPolicy; appState.userPolicyAudits = safeArray(userPolicyAuditsPayload?.items || userPolicyAuditsPayload); appState.adminSystemMainPolicy = adminSystemMainPolicy; appState.adminSystemPlatformPolicies = safeArray(adminSystemPlatformPolicies); appState.adminGovernanceDirectory = safeArray(adminGovernanceDirectory?.items || adminGovernanceDirectory); if (isSuperAdmin() && appState.adminGovernanceDirectory.length) { const existingTarget = appState.adminOverrideTarget || {}; const hasExistingProjectTarget = Object.prototype.hasOwnProperty.call(existingTarget, "targetProjectId") || Object.prototype.hasOwnProperty.call(existingTarget, "target_project_id"); const targetUserId = String(existingTarget.targetUserId || existingTarget.target_user_id || appState.adminGovernanceDirectory[0]?.id || ""); const targetUser = appState.adminGovernanceDirectory.find((item) => item.id === targetUserId) || appState.adminGovernanceDirectory[0] || null; const targetProjects = safeArray(targetUser?.projects); const requestedProjectId = hasExistingProjectTarget ? String(existingTarget.targetProjectId ?? existingTarget.target_project_id ?? "") : String(targetProjects[0]?.id || ""); const targetProjectId = requestedProjectId === "" ? "" : (targetProjects.some((item) => String(item?.id || "") === requestedProjectId) ? requestedProjectId : String(targetProjects[0]?.id || "")); const targetPlatform = normalizePlatformValue(existingTarget.platform || governancePlatform, "douyin"); appState.adminOverrideTarget = { targetUserId, targetProjectId, platform: targetPlatform }; appState.adminOverridePolicy = await storyforgeFetch(`/v2/admin/oneliner/governance/overrides?target_user_id=${encodeURIComponent(targetUserId)}&target_project_id=${encodeURIComponent(targetProjectId)}&platform=${encodeURIComponent(targetPlatform)}`).catch(() => null); appState.adminPolicyAudits = safeArray((await storyforgeFetch(`/v2/admin/oneliner/governance/audits?target_user_id=${encodeURIComponent(targetUserId)}&target_project_id=${encodeURIComponent(targetProjectId)}&platform=${encodeURIComponent(targetPlatform)}&include_system=1`).catch(() => ({ items: [] })))?.items || []); } else { appState.adminOverrideTarget = null; appState.adminOverridePolicy = null; appState.adminPolicyAudits = []; } appState.tenantQuota = tenantQuota; appState.tenantUsage = tenantUsage; appState.adminOpsOverview = adminOpsOverview; appState.adminFixRuns = safeArray(adminFixRunsPayload?.items || adminFixRunsPayload); } async function loadOneLinerMessages(sessionId) { if (!sessionId) { appState.onelinerMessages = []; return []; } const payload = await storyforgeFetch(`/v2/oneliner/sessions/${encodeURIComponent(sessionId)}/messages`).catch(() => ({ items: [] })); appState.onelinerMessages = safeArray(payload?.items || payload); return appState.onelinerMessages; } async function ensureOneLinerSession() { const projectId = getOneLinerProjectId(); if (!projectId) throw new Error("当前还没有项目,OneLiner 需要先绑定项目上下文。"); let session = getCurrentOneLinerSession(); if (!session) { session = await storyforgeFetch("/v2/oneliner/sessions", { method: "POST", body: { project_id: projectId, preferred_platform: getPreferredPlatform() } }); appState.onelinerSessions = [session, ...safeArray(appState.onelinerSessions)]; appState.selectedOnelinerSessionId = session.id; } await loadOneLinerMessages(session.id); return session; } async function submitOneLinerMessage(content) { const projectId = getOneLinerProjectId(); const session = await ensureOneLinerSession(); const payload = await storyforgeFetch(`/v2/oneliner/sessions/${encodeURIComponent(session.id)}/messages`, { method: "POST", body: { content, project_id: projectId, platform: getPreferredPlatform() } }); appState.selectedOnelinerSessionId = payload.session?.id || session.id; await loadAgentControlSurfaces(projectId); if (appState.selectedOnelinerSessionId) { await loadOneLinerMessages(appState.selectedOnelinerSessionId); } else { appState.onelinerMessages = [ ...safeArray(appState.onelinerMessages), payload.user_message, payload.assistant_message ].filter(Boolean); } appState.onelinerProfile = payload.result?.context?.oneliner_profile || appState.onelinerProfile || null; rememberAction("OneLiner 已响应", payload.result?.summary_text || "已返回一版任务拆解。", "blue", payload); return payload; } async function createOneLinerRun(runRequest) { const projectId = getOneLinerProjectId(); const payload = await storyforgeFetch("/v2/oneliner/runs", { method: "POST", body: { project_id: projectId, platform: getPreferredPlatform(), platform_scope: "single_platform", delivery_mode: "hybrid", scheduling_mode: "queued", ...runRequest } }); await loadAgentControlSurfaces(projectId); appState.selectedOnelinerRunId = payload?.id || choosePreferredOneLinerRunId(appState.onelinerRuns, ""); rememberAction("主 Agent 已接单", payload?.title || payload?.plan?.goal || "已创建待确认任务。", "blue", payload); return payload; } async function confirmOneLinerRun(runId, reason = "") { const payload = await storyforgeFetch(`/v2/oneliner/runs/${encodeURIComponent(runId)}/confirm`, { method: "POST", body: { reason } }); await loadAgentControlSurfaces(getOneLinerProjectId()); appState.selectedOnelinerRunId = payload?.id || runId; rememberAction("主 Agent 已确认执行", payload?.status_summary || "当前任务已进入执行流。", "green", payload); return payload; } async function cancelOneLinerRun(runId, reason = "") { const payload = await storyforgeFetch(`/v2/oneliner/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST", body: { reason } }); await loadAgentControlSurfaces(getOneLinerProjectId()); appState.selectedOnelinerRunId = choosePreferredOneLinerRunId(appState.onelinerRuns, ""); rememberAction("主 Agent 任务已取消", payload?.status_summary || "当前任务已取消。", "orange", payload); return payload; } async function retryOneLinerRun(runId, reason = "") { const payload = await storyforgeFetch(`/v2/oneliner/runs/${encodeURIComponent(runId)}/retry`, { method: "POST", body: { reason } }); await loadAgentControlSurfaces(getOneLinerProjectId()); appState.selectedOnelinerRunId = payload?.id || choosePreferredOneLinerRunId(appState.onelinerRuns, ""); appState.onelinerRunFilter = "focus"; rememberAction("主 Agent 已重新生成待确认卡", payload?.title || payload?.plan?.goal || "你可以先确认新的执行计划。", "green", payload); return payload; } function renderOneLinerExecutionPayloadHtml(payload) { if (!payload || typeof payload !== "object") { return `

没有返回执行结果

当前执行器没有附带额外数据。

`; } const recommendedAction = payload.recommended_action && typeof payload.recommended_action === "object" ? payload.recommended_action : null; if (payload.result_kind === "main_agent_plan") { const landingRunId = String(payload.run_id || "").trim(); const landingScreen = String(payload.recommended_action?.screen || "").trim(); const landingTitle = String(payload.goal || "主 Agent 执行建议").trim(); const landingSummary = String( payload.recommended_action?.summary || payload.execution_summary || payload.summary_text || "" ).trim(); const resultSections = payload.result_sections && typeof payload.result_sections === "object" ? payload.result_sections : {}; const resultCards = safeArray(resultSections.cards).slice(0, 4); const configVersion = payload.execution_card?.oneliner_profile_version || payload.context?.oneliner_profile?.current_version || {}; const platformAgentProfile = payload.execution_card?.platform_agent_profile || {}; const latestOnelinerConfigVersion = appState.onelinerProfile?.current_version || {}; const latestPlatformAgentProfile = safeArray(appState.platformAgents).find((item) => item.platform === platformAgentProfile.platform) || {}; const currentRunOnelinerConfigStale = isConfigurationVersionStale(configVersion, latestOnelinerConfigVersion); const currentRunPlatformAgentConfigStale = isConfigurationVersionStale( platformAgentProfile, latestPlatformAgentProfile.current_version || {} ); const landingAttrs = buildRecommendedActionAttrs(payload.recommended_action, { runId: landingRunId, screen: landingScreen, title: landingTitle, summary: landingSummary }); return `

${escapeHtml(payload.goal || "主 Agent 执行建议")}

${escapeHtml(payload.execution_summary || payload.summary_text || "已形成一版可继续执行的主 Agent 建议。")}

${payload.platform ? `${escapeHtml(platformLabel(payload.platform))}` : ""} ${escapeHtml(payload.platform_scope === "all_platforms" ? "全平台" : "单平台")} ${configVersion.version_no ? `配置 v${escapeHtml(formatNumber(configVersion.version_no || 0))}` : ""} ${currentRunOnelinerConfigStale ? `主配置已更新` : ""} ${currentRunPlatformAgentConfigStale ? `平台 Agent 已更新` : ""} 已收口 ${payload.recommended_action?.action ? `${escapeHtml(payload.recommended_action.label || "回到对应页面")}` : ""}
${configVersion.version_no ? `

本轮使用的主配置

${escapeHtml(configVersion.summary || configVersion.title || `OneLiner 主配置版本 v${formatNumber(configVersion.version_no || 0)}`)}

配置 v${escapeHtml(formatNumber(configVersion.version_no || 0))} ${currentRunOnelinerConfigStale ? `主配置已更新` : ""} 查看配置历史
` : ""} ${platformAgentProfile.platform ? `

本轮平台 Agent

${escapeHtml(platformAgentProfile.mission || platformAgentProfile.name || `${platformAgentProfile.platform_label || platformLabel(platformAgentProfile.platform)} Agent 正在承接这轮执行。`)}

${escapeHtml(platformAgentProfile.platform_label || platformLabel(platformAgentProfile.platform))} ${platformAgentProfile.name ? `${escapeHtml(platformAgentProfile.name)}` : ""} ${platformAgentProfile.assistant_name ? `${escapeHtml(platformAgentProfile.assistant_name)}` : ""} ${platformAgentProfile.version_no ? `${escapeHtml(platformLabel(platformAgentProfile.platform || payload.platform || ""))} Agent v${escapeHtml(formatNumber(platformAgentProfile.version_no || 0))}` : ""} ${currentRunPlatformAgentConfigStale ? `平台 Agent 已更新` : ""} ${platformAgentProfile.platform && platformAgentProfile.version_no ? `看平台配置历史` : ""} ${platformAgentProfile.readiness_label ? `= 50 ? "blue" : "orange"}">${escapeHtml(platformAgentProfile.readiness_label)} ${escapeHtml(formatNumber(platformAgentProfile.readiness_score || 0))}` : ""}
` : ""} ${resultCards.length ? `

${escapeHtml(resultSections.workstream_label || "执行结果分组")}

${escapeHtml(payload.summary_text || "主 Agent 已将当前执行结果整理为可继续推进的结构化建议。")}

${resultCards.map((card, index) => { const tags = safeArray(card?.tags).slice(0, 3); const tone = String(card?.tone || "").trim(); const toneClass = tone === "orange" ? "orange" : tone === "green" ? "green" : "blue"; return `

${escapeHtml(card?.title || (index === 0 ? "当前焦点" : "执行结果"))}

${escapeHtml(card?.body || "主 Agent 已生成一条可继续执行的结果说明。")}

${escapeHtml(card?.title || (index === 0 ? "当前焦点" : "结果卡片"))} ${tags.map((tag) => `${escapeHtml(tag)}`).join("")}
`; }).join("")}
` : ""} ${payload.recommended_action?.summary ? `

建议回跳

${escapeHtml(payload.recommended_action.summary)}

` : ""} ${safeArray(payload.next_steps).length ? `
${safeArray(payload.next_steps).slice(0, 4).map((step, index) => `

下一步 ${escapeHtml(formatNumber(index + 1))}

${escapeHtml(step)}

`).join("")}
` : ""} `; } if (payload.job) { const job = payload.job || {}; const sourceJob = payload.source_job || {}; const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: "production", title: job.title || "任务已创建", summary: recommendedAction?.summary || payload.brief || sourceJob.title || "" }); return `

${escapeHtml(job.title || "任务已创建")}

${escapeHtml(payload.brief || sourceJob.title || `已创建 ${job.line_type || job.source_type || "任务"},你可以继续进入生产中心查看进度。`)}

${escapeHtml(job.line_type || job.source_type || "analysis")} ${escapeHtml(job.status || "queued")} ${recommendedAction?.action ? actionTag(recommendedAction.label || "看任务详情", recommendedAction.action, recommendedAttrs) : job.id ? `看任务详情` : ""} 去生产中心
`; } if (payload.saved || payload.started) { const saved = payload.saved || {}; const item = saved.item || {}; const started = payload.started || {}; const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: "production", title: item.binding_title || payload.source_url || "录制源已保存", summary: recommendedAction?.summary || item.source_url || payload.source_url || "" }); return `

${escapeHtml(item.binding_title || payload.source_url || "录制源已保存")}

${escapeHtml(item.source_url || payload.source_url || "直播源已经保存到当前租户的录制配置。")}

${escapeHtml(platformLabel(payload.platform || item.platform || "kuaishou"))} ${escapeHtml(item.quality || "原画")} ${escapeHtml(started && started.ok === false ? "启动待重试" : "已同步")} ${recommendedAction?.action ? actionTag(recommendedAction.label || "打开录制控制", recommendedAction.action, recommendedAttrs) : `打开录制控制`}
`; } if (payload.analyzed_count !== undefined && safeArray(payload.items).length) { const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: "discovery", title: "OneLiner 已分析高分作品", summary: recommendedAction?.summary || "" }); return `
平台${escapeHtml(platformLabel(payload.platform || payload.account?.platform || "douyin"))}
账号${escapeHtml(payload.account?.title || payload.account?.handle || payload.account?.source_url || "当前账号")}
拆解作品${escapeHtml(formatNumber(payload.analyzed_count || 0))}
已写记忆${escapeHtml(payload.memory?.title ? "是" : "否")}
${safeArray(payload.items).slice(0, 4).map((item) => `

${escapeHtml(item.video_title || "高分作品")}

${escapeHtml(item.summary_text || "已完成拆解。")}

得分 ${escapeHtml(formatNumber(item.performance_score || 0))} ${item.latest_job_id ? `看原分析` : ""}
`).join("")}
${recommendedAction?.action ? `
${actionTag(recommendedAction.label || "回到找对标", recommendedAction.action, recommendedAttrs)}
` : ""} `; } if (payload.route_checks) { const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: recommendedAction?.screen || "playbook", title: payload.platform_label || payload.platform || "平台自检", summary: recommendedAction?.summary || "" }); return `
平台${escapeHtml(payload.platform_label || payload.platform || "-")}
得分${escapeHtml(formatNumber(payload.score || 0))}
状态${escapeHtml(payload.readiness_label || payload.verdict || "-")}
账号源${escapeHtml(formatNumber(payload.source_count || 0))}
路由检查
${safeArray(payload.route_checks).map((item) => `

${escapeHtml(item.key || item.path || "route")}

${escapeHtml(item.path || "")}

${escapeHtml(item.ok ? "可用" : "缺失")}
`).join("")}
建议动作
${(safeArray(payload.suggestions).length ? safeArray(payload.suggestions) : ["当前已经达到可运行状态。"]).map((item) => `

下一步

${escapeHtml(item)}

`).join("")}
${recommendedAction?.action ? `
${actionTag(recommendedAction.label || "查看平台 Agent", recommendedAction.action, recommendedAttrs)}
` : ""} `; } if (payload.strategy && payload.tenant_usage) { const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: "automation", title: "当前存储状态", summary: recommendedAction?.summary || "" }); return `
jobs${escapeHtml(payload.tenant_usage?.project_jobs?.human_size || "0B")}
downloads${escapeHtml(payload.tenant_usage?.project_downloads?.human_size || "0B")}
模型目录${escapeHtml(payload.strategy?.models?.mode || "-")}
录像${escapeHtml(payload.strategy?.live_recorder?.mode || "-")}
${recommendedAction?.action ? `
${actionTag(recommendedAction.label || "去自动流程", recommendedAction.action, recommendedAttrs)}
` : ""} `; } if (payload.items || payload.files) { const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: "production", title: "直播录制状态", summary: recommendedAction?.summary || "" }); return `
录制源${escapeHtml(formatNumber(safeArray(payload.items).length))}
最近文件${escapeHtml(formatNumber(safeArray(payload.files).length))}
${recommendedAction?.action ? `
${actionTag(recommendedAction.label || "打开录制维护", recommendedAction.action, recommendedAttrs)}
` : ""} `; } if (payload.content) { const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: "playbook", title: "生成文案", summary: recommendedAction?.summary || "" }); return `

生成文案

${escapeHtml(brief(payload.content, 1200))}

${payload.assistant_id ? `${escapeHtml(payload.assistant_id)}` : ""} ${escapeHtml(formatNumber(safeArray(payload.used_documents).length))} 个参考素材 ${recommendedAction?.action ? actionTag(recommendedAction.label || "继续调文案", recommendedAction.action, recommendedAttrs) : ""}
`; } if (payload.verdict !== undefined || payload.next_actions !== undefined || payload.highlights !== undefined) { const recommendedAttrs = buildRecommendedActionAttrs(recommendedAction, { screen: "review", title: payload.title || "复盘草稿", summary: recommendedAction?.summary || "" }); return `

${escapeHtml(payload.title || "复盘草稿")}

${escapeHtml(payload.highlights || payload.notes || "已生成一版待补充的复盘草稿。")}

${escapeHtml(platformLabel(payload.platform || "douyin"))} ${escapeHtml(payload.verdict || "待补充")} ${payload.source_job_id ? `看任务详情` : ""} ${recommendedAction?.action ? actionTag(recommendedAction.label || "打开复盘", recommendedAction.action, recommendedAttrs) : payload.id ? `打开复盘` : ""}
`; } return `

原始结果

${escapeHtml(brief(JSON.stringify(payload, null, 2), 1200))}

`; } function parseOneLinerActionPayloadValue(value) { const text = String(value ?? "").trim(); if (!text) return ""; if (text === "true") return true; if (text === "false") return false; if (/^-?\d+(\.\d+)?$/.test(text)) return Number(text); if ((text.startsWith("{") && text.endsWith("}")) || (text.startsWith("[") && text.endsWith("]"))) { try { return JSON.parse(text); } catch (error) { return text; } } return text; } function collectOneLinerActionPayload(action) { const reserved = new Set(["action", "executorKey", "platform", "sessionId", "disabledReason"]); const payload = {}; Object.entries(action?.dataset || {}).forEach(([key, value]) => { if (reserved.has(key)) return; if (value === undefined || value === null || value === "") return; payload[key] = parseOneLinerActionPayloadValue(value); }); return payload; } async function executeOneLinerAction(executorKey, options = {}) { const projectId = options.projectId || getOneLinerProjectId(); const session = getCurrentOneLinerSession() || await ensureOneLinerSession(); const payload = await storyforgeFetch("/v2/oneliner/actions/execute", { method: "POST", body: { action_key: executorKey, project_id: projectId, platform: options.platform || getPreferredPlatform(), session_id: options.sessionId || session?.id || "", payload: options.payload || {} } }); await loadAgentControlSurfaces(projectId); if (appState.selectedOnelinerSessionId) { await loadOneLinerMessages(appState.selectedOnelinerSessionId); } if (options.showResultModal !== false) { openActionModal({ title: payload.title || "OneLiner 执行结果", description: payload.summary || "已完成一次对话内执行。", hideSubmit: true, fields: [ { type: "html", label: "执行结果", html: `
${renderOneLinerExecutionPayloadHtml(payload.payload || {})}
` } ] }); } rememberAction("OneLiner 已执行", payload.summary || "当前动作已在对话内执行。", "green", payload); renderAll(); return payload; } async function followRecommendedActionResult(payload, options = {}) { const recommendedAction = payload?.recommended_action && typeof payload.recommended_action === "object" ? payload.recommended_action : null; if (!recommendedAction?.action) return false; const actionName = String(recommendedAction.action || "").trim(); const platform = normalizePlatformValue( recommendedAction.platform || payload?.payload?.platform || options.platform || getPreferredPlatform(), getPreferredPlatform() ); if (options.refreshWorkbench) { await bootstrap(); } if (actionName === "open-job-detail" && recommendedAction.job_id) { closeActionModal(); openJobDetailAction(recommendedAction.job_id); return true; } if (actionName === "select-account" && recommendedAction.account_id) { closeActionModal(); await loadPlatformAccount(platform, recommendedAction.account_id); if (options.discoveryFocus === "relations") { focusDiscoveryRelations(); } else if (options.discoveryFocus === "snapshots") { focusDiscoveryInsights(); } else if (options.discoveryFocus === "top-videos") { focusDiscoveryTopVideoInsights(); } else { focusDiscoveryDetailTab("overview"); } return true; } if (actionName === "goto-tracking") { closeActionModal(); focusTrackingWorkspace(); return true; } if (actionName === "open-edit-assistant" && recommendedAction.assistant_id) { closeActionModal(); focusPlaybookWorkspace(recommendedAction.assistant_id); return true; } if (actionName === "open-review-edit" && recommendedAction.review_id) { closeActionModal(); focusReviewWorkspace(recommendedAction.review_id); return true; } if (actionName === "goto-discovery") { closeActionModal(); if (options.discoveryFocus === "relations") { focusDiscoveryRelations(); } else { focusDiscoveryDetailTab("overview"); } return true; } return false; } async function runDirectDiscoveryAction(executorKey, payload, options = {}) { const account = requireSelectedAccountRow(); 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 }); await followRecommendedActionResult(result, { platform, refreshWorkbench: true, discoveryFocus: options.discoveryFocus || "" }); return result; } catch (error) { presentActionFailure(error, options.errorTitle || "执行失败"); throw error; } finally { setBusy(false, ""); } } async function runDirectWorkbenchAction(executorKey, options = {}) { const platform = normalizePlatformValue(options.platform || getPreferredPlatform(), getPreferredPlatform()); setBusy(true, options.busyLabel || "正在执行当前动作..."); try { const result = await executeOneLinerAction(executorKey, { platform, payload: options.payload || {}, showResultModal: false }); if (typeof options.afterResult === "function") { await options.afterResult(result); } if (options.followRecommendedAction !== false) { await followRecommendedActionResult(result, { platform, refreshWorkbench: options.refreshWorkbench !== false, discoveryFocus: options.discoveryFocus || "" }); } return result; } catch (error) { presentActionFailure(error, options.errorTitle || "执行失败"); throw error; } finally { setBusy(false, ""); } } function openCurrentOneLinerRunResultAction(runId = "") { openOneLinerRunContextAction(runId) .then((currentRun) => { if (!currentRun?.id) { rememberAction("还没有可查看的结果", "当前主 Agent 任务还没有返回可展示的执行结果。", "orange"); renderAll(); return; } if (!currentRun.result || !Object.keys(currentRun.result || {}).length) { rememberAction("结果还在生成中", currentRun.status_summary || "当前主 Agent 任务还没有返回执行结果。", "orange", currentRun); renderAll(); return; } appState.selectedOnelinerRunId = currentRun.id; appState.onelinerRunFilter = currentRun.run_status === "done" ? "done" : appState.onelinerRunFilter; openActionModal({ title: currentRun.title || currentRun.plan?.goal || "主 Agent 执行结果", description: currentRun.result?.execution_summary || currentRun.status_summary || "这是当前主 Agent 任务的执行结果。", hideSubmit: true, fields: [ { type: "html", label: "执行结果", html: `
${renderOneLinerExecutionPayloadHtml(currentRun.result)}
` } ] }); }) .catch(() => { rememberAction("还没有可查看的结果", "当前主 Agent 任务还没有返回可展示的执行结果。", "orange"); renderAll(); }); } async function openOneLinerRunContextAction(runId = "") { const targetRunId = runId || appState.selectedOnelinerRunId || ""; if (!targetRunId) { rememberAction("还没有可查看的运行", "当前还没有主 Agent 运行可继续查看。", "orange"); renderAll(); return null; } const currentProjectId = appState.selectedProjectId || getOneLinerProjectId(); if (!safeArray(appState.onelinerRuns).some((item) => item.id === targetRunId) && currentProjectId) { await loadAgentControlSurfaces(currentProjectId); } appState.selectedOnelinerRunId = targetRunId; const hydrated = await hydrateSelectedOneLinerRun(); const currentRun = hydrated || safeArray(appState.onelinerRuns).find((item) => item.id === targetRunId) || null; if (currentRun?.run_status === "done") { appState.onelinerRunFilter = "done"; } openOneLinerPanel(); renderAll(); return currentRun; } function openConfirmOneLinerRunAction(runId = "") { const run = safeArray(appState.onelinerRuns).find((item) => item.id === runId) || getCurrentOneLinerRun(); if (!run?.id) { rememberAction("还没有可确认的任务", "当前没有主 Agent 待确认任务。", "orange"); renderAll(); return; } const planSteps = safeArray(run.plan?.steps).slice(0, 5); const previewAction = run.recommended_preview_action || null; const tags = [ run.platform_label ? `${escapeHtml(run.platform_label)}` : "", `${escapeHtml(run.platform_scope === "all_platforms" ? "全平台" : "单平台")}`, `${escapeHtml(onelinerIntentLabel(run.intent_key))}`, run.source_screen ? `${escapeHtml(run.source_screen)}` : "" ].filter(Boolean).join(""); const planHtml = `

当前计划

${escapeHtml(run.plan?.summary || run.summary || "主 Agent 会先按这张确认卡理解目标,再继续执行。")}

${tags}
${planSteps.length ? `
${planSteps.map((step, index) => `

步骤 ${escapeHtml(formatNumber(index + 1))}

${escapeHtml(step)}

`).join("")}
` : ""} ${previewAction ? `

预计落点

${escapeHtml(previewAction.summary || "执行后会回到更合适的业务页面继续推进。")}

${escapeHtml(previewAction.label || "回到对应页面")} ${previewAction.screen ? `${escapeHtml(previewAction.screen)}` : ""}
` : ""}
`; openActionModal({ title: "确认主 Agent 执行计划", description: "确认后,主 Agent 会按当前计划进入执行流;你也可以先补一句执行说明。", submitLabel: "确认执行", fields: [ { name: "plan", type: "html", label: "执行计划", html: `
${planHtml}
` }, { name: "reason", type: "textarea", label: "补充说明", rows: 3, placeholder: "可选:比如优先做抖音,或者先给我一版更保守的执行建议。", value: "" } ], onSubmit: async (values) => { const payload = await confirmOneLinerRun(run.id, values.reason || "user confirmed"); return { keepOpen: false, payload }; } }); } async function loadPlatformAccount(platform, accountId, requestToken = 0) { if (!accountId) return; const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform()); const token = requestToken || ((appState.selectedAccountRequestToken || 0) + 1); if (!requestToken) { appState.selectedAccountRequestToken = token; } appState.selectedAccountId = accountId; setCurrentPlatform(normalizedPlatform); if (!isWorkbenchPlatform(normalizedPlatform)) { if (token !== appState.selectedAccountRequestToken) { return false; } appState.selectedWorkspace = null; appState.selectedVideos = { items: [], meta: {}, top_scored_video_ids: [], latest_video_ids: [], high_score_threshold: 60 }; appState.snapshots = []; appState.selectedSnapshotId = ""; appState.selectedSnapshotDetail = null; appState.creatorFields = null; appState.analysisReports = []; appState.similarSearchDetail = null; return true; } const workspacePath = getWorkbenchRoute(normalizedPlatform, "workspace", accountId); if (!workspacePath) { if (token !== appState.selectedAccountRequestToken) { return false; } appState.selectedWorkspace = null; appState.selectedVideos = { items: [], meta: {}, top_scored_video_ids: [], latest_video_ids: [], high_score_threshold: 60 }; appState.snapshots = []; appState.selectedSnapshotId = ""; appState.selectedSnapshotDetail = null; appState.creatorFields = null; appState.analysisReports = []; appState.similarSearchDetail = null; return true; } const videosPath = getWorkbenchRoute(normalizedPlatform, "videos", accountId); try { const [workspace, videos, snapshotsPayload, analysisReportsPayload] = await Promise.all([ storyforgeFetch(workspacePath), videosPath ? storyforgeFetch(videosPath).catch(() => ({ items: [], meta: {}, top_scored_video_ids: [], latest_video_ids: [], high_score_threshold: 60 })) : Promise.resolve({ items: [], meta: {}, top_scored_video_ids: [], latest_video_ids: [], high_score_threshold: 60 }), normalizedPlatform === "douyin" ? storyforgeFetch(`/v2/douyin/accounts/${encodeURIComponent(accountId)}/snapshots`).catch(() => []) : Promise.resolve([]), normalizedPlatform === "douyin" ? storyforgeFetch(`/v2/douyin/accounts/${encodeURIComponent(accountId)}/analysis-reports`).catch(() => []) : Promise.resolve([]) ]); if (token !== appState.selectedAccountRequestToken) { return false; } appState.selectedWorkspace = workspace; appState.selectedVideos = videos; if (normalizedPlatform === "douyin") { appState.snapshots = safeArray(snapshotsPayload?.items || snapshotsPayload); appState.creatorFields = hasCreatorCenterSnapshot(appState.snapshots) ? await storyforgeFetch(`/v2/douyin/accounts/${encodeURIComponent(accountId)}/creator-fields`).catch(() => null) : null; appState.analysisReports = safeArray(analysisReportsPayload?.items || analysisReportsPayload); const nextSnapshotId = appState.snapshots.find((item) => item.id === appState.selectedSnapshotId)?.id || appState.snapshots[0]?.id || ""; appState.selectedSnapshotId = nextSnapshotId; appState.selectedSnapshotDetail = nextSnapshotId ? await storyforgeFetch(`/v2/douyin/accounts/${encodeURIComponent(accountId)}/snapshots/${encodeURIComponent(nextSnapshotId)}`).catch(() => null) : null; } else { appState.snapshots = []; appState.selectedSnapshotId = ""; appState.selectedSnapshotDetail = null; appState.creatorFields = null; appState.analysisReports = []; } return true; } catch (error) { if (token !== appState.selectedAccountRequestToken) { return false; } throw error; } } async function hydrateWorkbenchDataAfterBootstrap(dashboard, preferredPlatform, requestToken) { const runtimePlatforms = getRuntimePlatformValues(); try { appState.backendCapabilities = await loadBackendCapabilities(appState.session.backendUrl).catch(() => null); const [contentSources, platformPayloads, reviews, integrationHealth, localModelCatalog, liveRecorderSourcesPayload] = await Promise.all([ storyforgeFetch("/v2/content-sources").catch(() => []), Promise.all(runtimePlatforms.map(async (platform) => { const accountListPath = getWorkbenchRoute(platform, "accounts"); const trackingAccountsPath = getWorkbenchRoute(platform, "trackingAccounts"); const trackingDigestPath = getWorkbenchRoute(platform, "trackingDigest"); const accounts = accountListPath ? await storyforgeFetch(accountListPath).catch(() => []) : []; const trackingAccountsPayload = trackingAccountsPath ? await storyforgeFetch(trackingAccountsPath).catch(() => ({ items: [], cursor_last_seen_at: "" })) : { items: [], cursor_last_seen_at: "" }; const trackingCursorLastSeenAt = trackingAccountsPayload?.cursor_last_seen_at || ""; const trackingDigest = trackingDigestPath ? await storyforgeFetch(`${trackingDigestPath}?since=${encodeURIComponent(trackingCursorLastSeenAt || getTrackingSinceIso(platform))}&limit=24`).catch(() => ({ items: [], tracked_accounts: [], cursor_last_seen_at: trackingCursorLastSeenAt })) : { items: [], tracked_accounts: [], cursor_last_seen_at: trackingCursorLastSeenAt }; return { platform, accounts: safeArray(accounts).map((item) => ({ ...item, platform: normalizePlatformValue(item?.platform || platform, platform) })), trackingAccountsPayload, trackingDigest }; })), storyforgeFetch("/v2/reviews").catch(() => []), storyforgeFetch("/v2/integrations/health").catch(() => null), storyforgeFetch("/v2/integrations/local-models").catch(() => null), storyforgeFetch("/v2/live-recorder/sources").catch(() => ({ items: [] })) ]); if (requestToken !== bootstrapHydrationToken) return; const liveRecorderIntegration = integrationHealth?.live_recorder || null; const canLoadLiveRecorderRuntime = Boolean(liveRecorderIntegration?.reachable); const [liveRecorderStatus, liveRecorderFilesPayload, liveRecorderHealth] = await Promise.all([ canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/status").catch(() => null) : Promise.resolve(null), canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/files?limit=16").catch(() => ({ items: [] })) : Promise.resolve({ items: [] }), canLoadLiveRecorderRuntime ? storyforgeFetch("/v2/live-recorder/health").catch(() => null) : Promise.resolve(null) ]); if (requestToken !== bootstrapHydrationToken) return; const mergedAccounts = safeArray(platformPayloads) .flatMap((entry) => safeArray(entry.accounts)) .sort((a, b) => { const platformCompare = platformLabel(getAccountPlatform(a)).localeCompare(platformLabel(getAccountPlatform(b)), "zh-Hans-CN"); if (platformCompare !== 0) return platformCompare; return getAccountName(a).localeCompare(getAccountName(b), "zh-Hans-CN"); }); const accountIndex = new Map(mergedAccounts.map((item) => [item.id, item])); const assistantNameMap = buildAssistantNameMap(dashboard.assistants); const trackingCursorMap = Object.fromEntries( safeArray(platformPayloads) .map((entry) => [entry.platform, entry.trackingAccountsPayload?.cursor_last_seen_at || ""]) .filter(([, value]) => value) ); const currentCursor = trackingCursorMap[preferredPlatform] || pickLatestIso(Object.values(trackingCursorMap)) || ""; if (currentCursor) { setLastSeenAt(currentCursor); } appState.contentSources = safeArray(contentSources); appState.accounts = mergedAccounts; appState.trackingCursorMap = trackingCursorMap; appState.trackingAccounts = safeArray(platformPayloads).flatMap((entry) => enrichTrackingAccounts( entry.trackingAccountsPayload?.items || entry.trackingAccountsPayload, accountIndex, assistantNameMap, entry.platform ) ); appState.trackingDigest = { cursor_last_seen_at: currentCursor, items: safeArray(platformPayloads).flatMap((entry) => enrichTrackingDigestItems(entry.trackingDigest?.items, accountIndex, entry.platform)), tracked_accounts: safeArray(platformPayloads).flatMap((entry) => safeArray(entry.trackingDigest?.tracked_accounts).map((item) => ({ ...item, platform: normalizePlatformValue(item.platform || entry.platform, entry.platform) })) ) }; appState.reviews = safeArray(reviews); appState.liveRecorderSources = safeArray(liveRecorderSourcesPayload?.items || liveRecorderSourcesPayload); appState.liveRecorderStatus = liveRecorderStatus; appState.liveRecorderFiles = safeArray(liveRecorderFilesPayload?.items || liveRecorderFilesPayload); appState.integrationHealth = integrationHealth; appState.localModelCatalog = localModelCatalog; appState.liveRecorderHealth = liveRecorderHealth; const selectedAssistantExists = safeArray(dashboard.assistants).some((item) => item.id === appState.selectedAssistantId); appState.selectedAssistantId = selectedAssistantExists ? appState.selectedAssistantId : (dashboard.assistants?.[0]?.id || ""); const platformAccounts = getAccountsForPlatform(preferredPlatform); const selectedAccountExists = platformAccounts.some((item) => item.id === appState.selectedAccountId); const nextAccountId = selectedAccountExists ? appState.selectedAccountId : platformAccounts[0]?.id || appState.accounts[0]?.id || ""; if (!nextAccountId) { appState.selectedAccountId = ""; appState.selectedWorkspace = null; appState.selectedVideos = { items: [], meta: {}, top_scored_video_ids: [], latest_video_ids: [], high_score_threshold: 60 }; } renderAll(); appState.workspaceHydrating = false; renderAll(); void loadKnowledgeDocuments(dashboard.knowledge_bases) .then((documents) => { if (requestToken !== bootstrapHydrationToken) return; appState.documents = documents; renderAll(); }) .catch(() => {}); void Promise.all([ loadStorageStatus(appState.selectedProjectId || ""), loadAgentControlSurfaces(appState.selectedProjectId || "") ]).then(async () => { if (requestToken !== bootstrapHydrationToken) return; if (appState.selectedOnelinerSessionId) { await loadOneLinerMessages(appState.selectedOnelinerSessionId).catch(() => {}); } else { appState.onelinerMessages = []; } renderAll(); }).catch(() => {}); if (nextAccountId) { const nextAccount = appState.accounts.find((item) => item.id === nextAccountId) || null; void loadPlatformAccount(getAccountPlatform(nextAccount), nextAccountId).catch(() => {}); } } catch (error) { if (requestToken !== bootstrapHydrationToken) return; appState.workspaceHydrating = false; appState.message = error.message; renderAll(); } finally { if (requestToken === bootstrapHydrationToken) { appState.recoveryRecords = getRecoveryRecords(); renderAll(); } } } async function bootstrap() { renderAll(); const requestToken = ++bootstrapHydrationToken; const backendMismatch = hasSessionBackendMismatch(); if (!appState.session || backendMismatch) { setBusy(true, backendMismatch ? "正在切换到当前工作区后端..." : "正在自动连接后端..."); try { await ensureAutoSession({ force: backendMismatch }); } finally { setBusy(false, ""); } } if (!appState.session) { renderAuthUi(); return; } setBusy(true, "正在同步工作区..."); try { const [me, dashboard] = await Promise.all([ storyforgeFetch("/v2/me"), storyforgeFetch("/v2/me/dashboard") ]); appState.me = me; if (requestToken !== bootstrapHydrationToken) return; if (appState.me.approval_status !== "approved" && appState.me.role !== "super_admin") { appState.dashboard = null; appState.accounts = []; appState.contentSources = []; appState.trackingAccounts = []; appState.trackingDigest = null; appState.trackingCursorMap = {}; appState.documents = []; renderAll(); return; } if (requestToken !== bootstrapHydrationToken) return; appState.dashboard = dashboard; const preferredPlatform = getCurrentPlatformValue(); setCurrentPlatform(preferredPlatform); appState.selectedProjectId = appState.selectedProjectId || dashboard.projects?.[0]?.id || ""; const selectedAssistantExists = safeArray(dashboard.assistants).some((item) => item.id === appState.selectedAssistantId); appState.selectedAssistantId = selectedAssistantExists ? appState.selectedAssistantId : (dashboard.assistants?.[0]?.id || ""); appState.workspaceHydrating = true; setBusy(false, ""); renderAll(); void hydrateWorkbenchDataAfterBootstrap(dashboard, preferredPlatform, requestToken); return; } catch (error) { appState.message = error.message; if ( String(error.message || "").includes("401") || String(error.message || "").includes("Not authenticated") || String(error.message || "").includes("Invalid token") ) { persistSession(null); appState.autoConnectAttempted = false; } } finally { appState.recoveryRecords = getRecoveryRecords(); if (appState.busy) { setBusy(false, ""); } renderAll(); } } async function markTrackingDigestRead() { const platform = getCurrentPlatformValue(); const trackingCursorPath = getWorkbenchRoute(platform, "trackingCursor"); const nextSeenAt = new Date().toISOString(); await storyforgeFetch(trackingCursorPath, { method: "POST", body: { last_seen_at: nextSeenAt } }); appState.trackingCursorMap = { ...(appState.trackingCursorMap || {}), [platform]: nextSeenAt }; if (appState.trackingDigest) { appState.trackingDigest = { ...appState.trackingDigest, items: safeArray(appState.trackingDigest.items).filter((item) => item.platform !== platform), cursor_last_seen_at: platform === getCurrentPlatformValue() ? nextSeenAt : (appState.trackingDigest.cursor_last_seen_at || "") }; } setLastSeenAt(nextSeenAt); } async function refreshTrackingAccountsAction() { const platform = getCurrentPlatformValue(); const trackingRefreshPath = getWorkbenchRoute(platform, "trackingRefresh"); setBusy(true, "正在同步跟踪账号..."); try { const payload = await storyforgeFetch(trackingRefreshPath, { method: "POST" }); const refreshNotice = summarizeTrackingRefreshPayload(payload, platform, "batch"); rememberTrackingRefreshNotice(refreshNotice); rememberAction( refreshNotice?.title || "跟踪已同步", refreshNotice?.summary || `已刷新 ${formatNumber(payload.refreshed || 0)} 个账号${payload.failed ? `,失败 ${formatNumber(payload.failed)} 个` : ""}。`, refreshNotice?.tone || (payload.failed ? "orange" : "green"), payload ); await bootstrap(); } finally { setBusy(false, ""); } } async function refreshTrackedAccountAction(trackedAccountId) { if (!trackedAccountId) { throw new Error("trackedAccountId is required"); } const trackedItem = getTrackingAccounts().find((item) => item.tracked_account_id === trackedAccountId); const platform = trackedItem?.platform || getCurrentPlatformValue(); const trackingRefreshPath = getWorkbenchRoute(platform, "trackingAccountRefresh", trackedAccountId); setBusy(true, "正在同步该跟踪账号..."); try { const payload = await storyforgeFetch(trackingRefreshPath, { method: "POST" }); const refreshNotice = summarizeTrackingRefreshPayload(payload, platform, "single"); rememberTrackingRefreshNotice(refreshNotice); const success = payload.success !== false; rememberAction( refreshNotice?.title || (success ? "单账号已同步" : "单账号刷新失败"), refreshNotice?.summary || (success ? `已刷新「${payload.account?.nickname || trackedAccountId}」的最新作品。` : `暂时无法刷新「${payload.account?.nickname || trackedAccountId}」:${payload.message || "请稍后重试"}`), refreshNotice?.tone || (success ? (safeArray(payload.sync_errors).length ? "orange" : "green") : "orange"), payload ); await bootstrap(); if (payload.sync_job_id) { openJobDetailAction(payload.sync_job_id); } } finally { setBusy(false, ""); } } function getSelectedProject() { const projects = safeArray(appState.dashboard?.projects); return projects.find((item) => item.id === appState.selectedProjectId) || projects[0] || null; } function isSuperAdmin() { return appState.me?.role === "super_admin"; } function getOneLinerProjectId() { return getSelectedProject()?.id || appState.selectedProjectId || safeArray(appState.dashboard?.projects)[0]?.id || ""; } function getCurrentOneLinerSession() { const sessions = safeArray(appState.onelinerSessions); return sessions.find((item) => item.id === appState.selectedOnelinerSessionId) || sessions[0] || null; } function choosePreferredOneLinerRunId(items, currentId = "") { const runs = safeArray(items); if (currentId && runs.some((item) => item.id === currentId)) { return currentId; } return runs.find((item) => item.run_status === "needs_confirmation")?.id || runs[0]?.id || ""; } function getCurrentOneLinerRun() { const runs = safeArray(appState.onelinerRuns); const preferredId = choosePreferredOneLinerRunId(runs, appState.selectedOnelinerRunId || ""); return runs.find((item) => item.id === preferredId) || null; } function onelinerIntentLabel(value) { return ONELINER_INTENT_LABELS[value] || value || "自定义任务"; } function getProjectKnowledgeBases(projectId) { return safeArray(appState.dashboard?.knowledge_bases).filter((item) => item.project_id === projectId); } function getProjectAssistants(projectId) { return safeArray(appState.dashboard?.assistants).filter((item) => item.project_id === projectId); } function getSelectedAssistant() { const assistants = safeArray(appState.dashboard?.assistants); return assistants.find((item) => item.id === appState.selectedAssistantId) || assistants[0] || null; } function getProjectOptions() { return safeArray(appState.dashboard?.projects).map((project) => ({ value: project.id, label: project.name })); } function getAssistantOptions(projectId) { return getProjectAssistants(projectId).map((assistant) => ({ value: assistant.id, label: assistant.name })); } function getKnowledgeBaseOptions(projectId) { return getProjectKnowledgeBases(projectId).map((kb) => ({ value: kb.id, label: kb.name })); } function getModelOptions() { return safeArray(appState.dashboard?.model_profiles).map((model) => ({ value: model.id, label: model.name })); } function getCurrentModelProfile() { const models = safeArray(appState.dashboard?.model_profiles); const currentId = appState.me?.preferred_analysis_model_id || models.find((item) => item.is_default)?.id || ""; return models.find((item) => item.id === currentId) || models.find((item) => item.is_default) || models[0] || null; } function getCompletedJobOptions() { return safeArray(appState.dashboard?.recent_jobs) .filter((item) => item.status === "completed") .map((job) => ({ value: job.id, label: `${job.title} · ${job.line_type || "analysis"}` })); } function getLatestCompletedProjectJob() { const projectId = appState.selectedProjectId || ""; return safeArray(appState.dashboard?.recent_jobs).find((item) => { if (item.status !== "completed") return false; if (!projectId) return true; return String(item.project_id || "") === projectId; }) || null; } function getProjectStats(projectId) { const dashboard = appState.dashboard || {}; const knowledgeBases = safeArray(dashboard.knowledge_bases).filter((item) => item.project_id === projectId); const assistants = safeArray(dashboard.assistants).filter((item) => item.project_id === projectId); const jobs = safeArray(dashboard.recent_jobs).filter((item) => item.project_id === projectId); const sources = safeArray(appState.contentSources).filter((item) => item.project_id === projectId); return { knowledgeBases, assistants, jobs, sources }; } function getProjectReviews(projectId) { return safeArray(appState.reviews).filter((item) => item.project_id === projectId); } function getReviewById(reviewId) { return safeArray(appState.reviews).find((item) => item.id === reviewId) || null; } function getContentSourcesForAccount(account) { if (!account) return []; const platform = getAccountPlatform(account); const profileUrl = getAccountProfileUrl(account); const handle = getAccountHandle(account); const nickname = getAccountName(account); return safeArray(appState.contentSources).filter((source) => { const sourceUrl = String(source.source_url || "").trim(); const sourceHandle = String(source.handle || "").trim(); const title = String(source.title || "").trim(); const sourcePlatform = normalizePlatformValue(source.platform || "", platform); return ( sourcePlatform === platform && ( (profileUrl && sourceUrl === profileUrl) || (handle && sourceHandle === handle) || (nickname && title.includes(nickname)) ) ); }); } function getCurrentProjectSourcesForAccount(account, projectId) { return getContentSourcesForAccount(account).filter((source) => source.project_id === projectId); } function getCurrentPlatformValue() { const available = getRuntimePlatformValues(); const fallback = available[0] || "douyin"; const current = normalizePlatformValue(appState.currentPlatform, ""); if (current && available.includes(current)) return current; return normalizePlatformValue(getPreferredPlatform(), fallback); } function getAccountsForPlatform(platform) { const normalizedPlatform = normalizePlatformValue(platform, getCurrentPlatformValue()); return safeArray(appState.accounts).filter((item) => getAccountPlatform(item) === normalizedPlatform); } function parseIsoTime(value) { const text = String(value || "").trim(); if (!text) return 0; const timestamp = new Date(text).getTime(); return Number.isNaN(timestamp) ? 0 : timestamp; } function sortItemsByIsoDesc(items, fieldName) { return items .slice() .sort((left, right) => parseIsoTime(right?.[fieldName]) - parseIsoTime(left?.[fieldName])); } function pickLatestIso(values) { return values .map((value) => String(value || "").trim()) .filter(Boolean) .sort((left, right) => parseIsoTime(right) - parseIsoTime(left))[0] || ""; } function createEmptyTrackingDigest(cursorLastSeenAt = "") { return { items: [], tracked_accounts: [], cursor_last_seen_at: cursorLastSeenAt }; } function getAssistantById(assistantId) { if (!assistantId) return null; return safeArray(appState.dashboard?.assistants).find((item) => item.id === assistantId) || null; } function getTrackedAccountDisplay(item) { const account = item?.account || safeArray(appState.accounts).find((row) => row.id === item?.tracked_account_id) || null; const assistant = getAssistantById(item?.assistant_id || ""); const platform = normalizePlatformValue(item?.platform || account?.platform || getCurrentPlatformValue(), getCurrentPlatformValue()); return { ...item, account, platform, assistant_name: item?.assistant_name || assistant?.name || "", note: item?.note || "" }; } function isTrackedAccount(accountId) { return safeArray(appState.trackingAccounts).some((item) => item.tracked_account_id === accountId); } function getTrackingAccounts() { return sortItemsByIsoDesc( safeArray(appState.trackingAccounts).map((item) => getTrackedAccountDisplay(item)), "updated_at" ); } function getTrackingAccountsForProject(projectId) { if (!projectId) return getTrackingAccounts(); const assistantIds = new Set(getProjectAssistants(projectId).map((item) => item.id)); const scoped = getTrackingAccounts().filter((item) => item.assistant_id && assistantIds.has(item.assistant_id)); return scoped.length ? scoped : getTrackingAccounts(); } function dashboardTabLabel(value) { return ({ project_progress: "项目进度", focus_accounts: "重点账号 / 对标", production_jobs: "生产任务" })[value] || "项目进度"; } function getDashboardActionSourceLabel() { return appState.onelinerProfile ? "主 Agent 优先推荐" : "规则推荐"; } function hasCreatorCenterSnapshot(items) { return safeArray(items).some((item) => String(item?.snapshot_type || "").toLowerCase() === "creator_center"); } function getDashboardProjectProgressSummary(project, stats, trackedAccounts) { const total = 5; const completed = [ Boolean(project), stats.assistants.length > 0, stats.sources.length > 0 || appState.accounts.length > 0, trackedAccounts.length > 0, stats.jobs.length > 0 ].filter(Boolean).length; const failedJobs = stats.jobs.filter((item) => item.status === "failed").length; let nextStep = "先创建当前项目"; if (project && !stats.assistants.length) { nextStep = "补第一个 Agent"; } else if (project && !stats.sources.length && !appState.accounts.length) { nextStep = "导入主页或作品"; } else if (project && !trackedAccounts.length) { nextStep = "加一个重点跟踪"; } else if (project && !stats.jobs.length) { nextStep = "发起第一条生产任务"; } else if (project) { nextStep = "继续推进当前主流程"; } const risk = failedJobs ? `${failedJobs} 条任务失败待处理` : (!trackedAccounts.length && project) ? "还没有重点账号跟踪" : "当前主流程没有明显阻塞"; return { total, completed, nextStep, risk }; } function buildDashboardOverviewTabs(project, stats, trackedAccounts) { const progress = getDashboardProjectProgressSummary(project, stats, trackedAccounts); const pendingJobs = stats.jobs.filter((item) => item.status !== "completed").length; return [ { key: "project_progress", label: "项目进度", value: `${progress.completed} / ${progress.total}`, hint: progress.nextStep, active: appState.dashboardOverviewTab === "project_progress" }, { key: "focus_accounts", label: "重点账号 / 对标", value: formatNumber(trackedAccounts.length || appState.accounts.length), hint: trackedAccounts.length ? "优先看变化最多的对象" : "先挑一个重点对象开始跟进", active: appState.dashboardOverviewTab === "focus_accounts" }, { key: "production_jobs", label: "生产任务", value: formatNumber(stats.jobs.length), hint: pendingJobs ? `${formatNumber(pendingJobs)} 条待推进` : "可发起下一条任务", active: appState.dashboardOverviewTab === "production_jobs" } ]; } function renderDashboardProjectProgressBody(project, stats, trackedAccounts) { const progress = getDashboardProjectProgressSummary(project, stats, trackedAccounts); return `
当前项目 ${escapeHtml(project?.name || "还没有项目")} ${escapeHtml(project?.description || "先选定项目,首页动作才会真正收敛。")}
主流程进度 ${escapeHtml(`${progress.completed} / ${progress.total}`)} ${escapeHtml(progress.nextStep)}
阶段风险 ${escapeHtml(progress.risk)} ${escapeHtml(stats.jobs.length ? `当前项目任务 ${formatNumber(stats.jobs.length)} 条` : "还没有生产任务")}

下一步建议

${escapeHtml(project ? `当前项目建议先「${progress.nextStep}」,做完后再回到首页看下一条动作。` : "先创建项目或切换到已有项目,然后首页动作区会自动切到该项目。")}

${actionTag(project ? "切换项目" : "去建项目", project ? "open-dashboard-project-switcher" : "goto-intake")} ${actionTag("去生产中心", "goto-production")}
`; } function renderDashboardFocusAccountsBody(project, trackedAccounts) { const fallbackAccounts = safeArray(appState.accounts).slice(0, 3).map((item) => ({ platform: getAccountPlatform(item), assistant_name: "", account: item })); const items = (trackedAccounts.length ? trackedAccounts : fallbackAccounts).slice(0, 3); if (!items.length) { return `

还没有重点账号 / 对标

${escapeHtml(project ? "先导入主页或把一个重点账号加入跟踪,首页才会持续给出更聪明的动作推荐。" : "先创建项目,再导入第一个重点账号。")}

${actionTag("去找对标", "goto-discovery")} ${actionTag("去跟踪账号", "goto-tracking")}
`; } return `
${items.map((item) => { const account = item.account || item; const accountId = account?.id || item.tracked_account_id || ""; return `
${escapeHtml(initials(getAccountName(account)))}
${escapeHtml(getAccountName(account))}
${escapeHtml(platformLabel(item.platform || getAccountPlatform(account)))} · ${escapeHtml(item.assistant_name || account.signature || "重点关注对象")}
${escapeHtml(account.video_summary?.count ? `作品 ${formatNumber(account.video_summary.count)}` : "重点对象")} ${account.sync_status ? `${escapeHtml(account.sync_status)}` : ""} ${accountId ? actionTag("查看详情", "select-account", `data-account-id="${escapeHtml(accountId)}"`) : ""}
`; }).join("")}
`; } function renderDashboardProductionJobsBody(stats) { const jobs = sortItemsByIsoDesc(stats.jobs, "updated_at").slice(0, 4); if (!jobs.length) { return `

当前项目还没有生产任务

先补完项目和对标,再从首页动作区或生产中心发起第一条任务。

${actionTag("去生产中心", "goto-production")} ${actionTag("去找对标", "goto-discovery")}
`; } return `
${jobs.map((job) => `

${escapeHtml(job.title || job.id)}

${escapeHtml(brief(job.error || job.summary || `最近更新于 ${formatDateTime(job.updated_at || job.created_at)}`, 120))}

${escapeHtml(job.status || "-")} ${job.line_type ? `${escapeHtml(job.line_type)}` : ""} ${actionTag("查看详情", "open-job-detail", `data-job-id="${escapeHtml(job.id)}"`)}
`).join("")}
`; } function renderDashboardOverviewBody(tab, context) { if (tab === "focus_accounts") { return renderDashboardFocusAccountsBody(context.project, context.trackedAccounts); } if (tab === "production_jobs") { return renderDashboardProductionJobsBody(context.stats); } return renderDashboardProjectProgressBody(context.project, context.stats, context.trackedAccounts); } function buildDashboardHomeModel() { const project = getSelectedProject(); const emptyStats = { knowledgeBases: [], assistants: [], jobs: [], sources: [] }; const stats = project ? getProjectStats(project.id) : emptyStats; const trackedAccounts = project ? getTrackingAccountsForProject(project.id) : getTrackingAccounts(); const dashboardModule = window.StoryForgeDashboardHome; const summaryTabs = buildDashboardOverviewTabs(project, stats, trackedAccounts); const activeTabLabel = dashboardTabLabel(appState.dashboardOverviewTab); const baseModel = dashboardModule?.createDashboardHomeModel ? dashboardModule.createDashboardHomeModel({ workspaceLabel: appState.me?.display_name || appState.me?.username || "当前工作区", currentProjectName: project?.name || "还没有项目", trackedAccountsCount: trackedAccounts.length || appState.accounts.length, assistantCount: stats.assistants.length, jobCount: stats.jobs.length, hasProject: Boolean(project), actionSourceLabel: getDashboardActionSourceLabel(), dashboardOverviewTab: appState.dashboardOverviewTab, summaryTabs, activeTabLabel, contextLinks: [ { label: "账号", value: formatNumber(trackedAccounts.length || appState.accounts.length), action: "goto-owned" }, { label: "任务", value: formatNumber(stats.jobs.length), action: "goto-production" }, { label: "Agent", value: formatNumber(stats.assistants.length), action: "goto-playbook" } ] }) : { workspaceLabel: appState.me?.display_name || appState.me?.username || "当前工作区", currentProjectName: project?.name || "还没有项目", actionSourceLabel: getDashboardActionSourceLabel(), contextLinks: [ { label: "账号", value: formatNumber(trackedAccounts.length || appState.accounts.length), action: "goto-owned" }, { label: "任务", value: formatNumber(stats.jobs.length), action: "goto-production" }, { label: "Agent", value: formatNumber(stats.assistants.length), action: "goto-playbook" } ], primaryAction: { title: project ? "继续推进当前项目主流程" : "先创建或切换到一个项目", reason: project ? "从首页动作区进入当前最该做的事。" : "首页动作和概览都跟随当前项目。", badges: ["默认动作"], goAction: project ? "goto-production" : "goto-intake", goLabel: project ? "去处理" : "去项目", agentLabel: "交给主 Agent" }, secondaryActions: [], summaryTabs, activeTabLabel }; return { ...baseModel, summaryTabs, activeTabLabel, overviewBodyHtml: renderDashboardOverviewBody(appState.dashboardOverviewTab, { project, stats, trackedAccounts }) }; } async function applySelectedProject(projectId = "") { appState.selectedProjectId = projectId || ""; setBusy(true, "正在切换项目视图..."); try { await loadStorageStatus(appState.selectedProjectId || ""); await loadAgentControlSurfaces(appState.selectedProjectId || ""); if (appState.selectedOnelinerSessionId) { await loadOneLinerMessages(appState.selectedOnelinerSessionId); } } finally { setBusy(false, ""); } rememberAction("当前项目已切换", `已切换到「${getSelectedProject()?.name || "所选项目"}」,你现在看到的首页、Agent 和任务都会跟随更新。`, "green"); focusDashboardWorkspace("dashboard-workspace-anchor"); } function openDashboardProjectSwitcher() { const options = getProjectOptions(); if (!options.length) { rememberAction("还没有项目", "先创建一个项目,再让首页跟着当前项目切换。", "orange"); setScreen("intake"); return; } const selectedProject = getSelectedProject(); const projects = safeArray(appState.dashboard?.projects); const isMobileViewport = typeof window !== "undefined" && window.matchMedia?.("(max-width: 760px)")?.matches; const projectCards = projects.map((project) => { const stats = getProjectStats(project.id); const reviewCount = getProjectReviews(project.id).length; const isActive = project.id === selectedProject?.id; return ` `; }).join(""); openActionModal({ title: "切换当前项目", description: "首页上下文、今日动作和项目概览都会跟着当前项目一起切换。手机端会优先让你先扫一眼当前项目和最近任务。", submitLabel: "切换项目", fields: [ { name: "projectSummary", label: "项目速览", type: "html", html: `

${escapeHtml(selectedProject?.name || "当前还没有项目")}

${escapeHtml(selectedProject?.description || "切换项目后,首页、OneLiner 和工作台会一起同步到对应上下文。")}

当前项目 最近任务 ${escapeHtml(formatNumber(getProjectStats(selectedProject?.id || "").jobs.length))} 切换到这个项目后会同步总台、Agent 和生产中心

最近任务

优先切到你现在真正要推进的项目,再从首页和主 Agent 继续往下走。

${projectCards}
` }, { name: "projectId", label: "当前项目", type: "select", value: getSelectedProject()?.id || "", options, hidden: isMobileViewport } ], onOpen: ({ fields, submit }) => { const select = fields.querySelector('[data-action-field="projectId"]'); if (submit && isMobileViewport) { submit.hidden = true; } fields.querySelectorAll("[data-project-choice]").forEach((button) => { button.addEventListener("click", async () => { const nextProjectId = button.dataset.projectChoice || ""; if (select) { select.value = nextProjectId; } fields.querySelectorAll("[data-project-choice]").forEach((item) => item.classList.remove("active")); button.classList.add("active"); if (isMobileViewport && nextProjectId) { closeActionModal(); await applySelectedProject(nextProjectId); } }); }); }, onSubmit: async (payload) => { await applySelectedProject(payload.projectId || ""); } }); } function openDashboardActionReasonAction(index) { const model = buildDashboardHomeModel(); const actions = [model.primaryAction, ...safeArray(model.secondaryActions)]; const action = actions[Number(index)] || actions[0]; if (!action) return; appState.dashboardActionReason = { title: action.title, reason: action.reason, sourceLabel: model.actionSourceLabel, badges: safeArray(action.badges), goLabel: action.goLabel || "去处理" }; openActionModal({ title: "动作原因", description: "首页只放最短判断,这里再把原因展开一层。", hideSubmit: true, fields: [ { type: "html", label: "动作详情", html: `

${escapeHtml(appState.dashboardActionReason.title)}

${escapeHtml(appState.dashboardActionReason.reason)}

${escapeHtml(appState.dashboardActionReason.sourceLabel)} ${appState.dashboardActionReason.badges.map((item) => `${escapeHtml(item)}`).join("")}
` } ] }); } function getTrackingDigestItems(limit = 6, options = {}) { const targetPlatform = normalizePlatformValue(options.platform || "", ""); const fallbackPlatform = targetPlatform || getCurrentPlatformValue(); return safeArray(appState.trackingDigest?.items) .map((item) => { const tracked = getTrackedAccountDisplay(item); const summary = item?.summary || item?.summary_text || item?.video?.description || item?.video?.title || item?.description || ""; const video = item?.video || {}; const isHighValue = item?.is_high_value != null ? Boolean(item.is_high_value) : Number(video?.score?.performance_score || 0) >= 60; return { ...item, account: item?.account || tracked.account, platform: tracked.platform || fallbackPlatform, assistant_name: item?.assistant_name || tracked.assistant_name || "", summary, borrowing_points: safeArray(item?.borrowing_points), is_high_value: isHighValue }; }) .filter((item) => !targetPlatform || item.platform === targetPlatform) .sort((left, right) => parseIsoTime(right?.created_at || right?.video?.published_at) - parseIsoTime(left?.created_at || left?.video?.published_at)) .slice(0, limit); } function getSelectedAccount() { return appState.selectedWorkspace?.account || appState.accounts.find((item) => item.id === appState.selectedAccountId) || null; } function getHighScoreVideos(limit = 3) { const items = safeArray(appState.selectedVideos?.items); const fallback = safeArray(getSelectedAccount()?.video_summary?.videos); const pool = items.length ? items : fallback; return pool .slice() .sort((a, b) => Number(b.score?.performance_score || 0) - Number(a.score?.performance_score || 0)) .slice(0, limit); } function getLatestVideos(limit = 3) { const items = safeArray(appState.selectedVideos?.items); const fallback = safeArray(getSelectedAccount()?.video_summary?.videos); const pool = items.length ? items : fallback; return pool .slice() .sort((a, b) => new Date(b.published_at || 0).getTime() - new Date(a.published_at || 0).getTime()) .slice(0, limit); } function getCurrentTrackingRefreshNotice(platform = getCurrentPlatformValue()) { const current = appState.trackingRefreshNotice || null; if (!current) return null; return normalizePlatformValue(current.platform || "", "") === normalizePlatformValue(platform || "", "") ? current : null; } function rememberTrackingRefreshNotice(notice) { appState.trackingRefreshNotice = notice ? { ...notice, created_at: notice.created_at || new Date().toISOString() } : null; } function summarizeTrackingRefreshPayload(payload, platform, mode = "batch") { const normalizedPlatform = normalizePlatformValue(platform || getCurrentPlatformValue(), getCurrentPlatformValue()); if (!payload || typeof payload !== "object") { return null; } if (mode === "single") { if (payload.success === false) { return { platform: normalizedPlatform, mode, tone: "orange", title: "单账号同步失败", summary: `暂时无法刷新该账号:${payload.message || "请稍后重试"}`, items: [] }; } if (payload.sync_job_id) { return { platform: normalizedPlatform, mode, tone: "blue", title: "单账号同步已排队", summary: `已为「${payload.account?.nickname || payload.tracked_account_id || "当前账号"}」创建后台同步任务,稍后会把结果回流到日报和作品区。`, items: [{ tracked_account_id: payload.tracked_account_id || "", sync_job_id: payload.sync_job_id, status: payload.status || "queued" }] }; } return { platform: normalizedPlatform, mode, tone: safeArray(payload.sync_errors).length ? "orange" : "green", title: "单账号已刷新", summary: `已刷新「${payload.account?.nickname || payload.tracked_account_id || "当前账号"}」的最新作品。`, items: [] }; } const queuedItems = safeArray(payload.items).filter((item) => item.sync_job_id); if (queuedItems.length) { return { platform: normalizedPlatform, mode, tone: payload.failed ? "orange" : "blue", title: "批量同步已排队", summary: `已为 ${formatNumber(queuedItems.length)} 个跟踪账号创建后台同步任务${payload.failed ? `,另有 ${formatNumber(payload.failed)} 个失败` : ""}。`, items: queuedItems }; } return { platform: normalizedPlatform, mode, tone: payload.failed ? "orange" : "green", title: "批量同步已完成", summary: `已刷新 ${formatNumber(payload.refreshed || 0)} 个账号${payload.failed ? `,失败 ${formatNumber(payload.failed)} 个` : ""}。`, items: [] }; } function getSelectedTopVideoAnalysisResult() { const accountId = String(getSelectedAccount()?.id || "").trim(); if (!accountId) return null; return appState.topVideoAnalysisResults?.[accountId] || null; } function getProductionWorks(limit = 6) { const preferred = safeArray(appState.selectedVideos?.items); const fallback = safeArray(appState.accounts) .flatMap((account) => safeArray(account.video_summary?.videos)) .filter(Boolean); const pool = preferred.length ? preferred : fallback; const scored = pool .slice() .sort((a, b) => Number(b.score?.performance_score || 0) - Number(a.score?.performance_score || 0)) .slice(0, Math.ceil(limit / 2)); const latest = pool .slice() .sort((a, b) => new Date(b.published_at || 0).getTime() - new Date(a.published_at || 0).getTime()) .slice(0, Math.ceil(limit / 2) + 1); const deduped = []; const seen = new Set(); [...scored, ...latest].forEach((item) => { const key = item.aweme_id || item.share_url || item.title || item.description; if (!key || seen.has(key)) return; seen.add(key); deduped.push(item); }); return deduped.slice(0, limit); } function describeVideo(video) { return video.title || video.description || video.aweme_id || "未命名作品"; } function getVideoLink(video) { return video.share_url || video.play_url || ""; } async function loadJobDetail(jobId) { const [job, events, childJobs] = await Promise.all([ storyforgeFetch(`/v2/explore/jobs/${encodeURIComponent(jobId)}`), storyforgeFetch(`/v2/explore/jobs/${encodeURIComponent(jobId)}/events`).catch(() => []), storyforgeFetch(`/v2/explore/jobs?parent_job_id=${encodeURIComponent(jobId)}`).catch(() => []) ]); appState.lastJobDetail = { job, events: safeArray(events), childJobs: safeArray(childJobs) }; return appState.lastJobDetail; } function isJobCompleted(job) { return String(job?.status || "").toLowerCase() === "completed"; } function canDeriveAiVideo(job) { if (!job || !isJobCompleted(job)) return false; return String(job.line_type || "").toLowerCase() !== "ai_video"; } function canDeriveRealCut(job) { if (!job || !isJobCompleted(job)) return false; const sourceType = String(job.source_type || "").toLowerCase(); return ["video_link", "upload_video"].includes(sourceType); } function hasIntegrationHealthData() { return Boolean(appState.integrationHealth && typeof appState.integrationHealth === "object"); } function getIntegrationDetail(key) { const raw = hasIntegrationHealthData() ? appState.integrationHealth?.[key] : null; return { key, available: Boolean(raw && typeof raw === "object"), configured: Boolean(raw?.configured), reachable: Boolean(raw?.reachable), statusCode: Number(raw?.status_code || 0), error: String(raw?.error || ""), url: String(raw?.url || raw?.base_url || ""), baseUrl: String(raw?.base_url || ""), routeMode: String(raw?.route_mode || ""), deploymentScope: String(raw?.deployment_scope || ""), deploymentLabel: String(raw?.deployment_label || ""), supportsUploads: raw?.supports_uploads !== undefined ? Boolean(raw?.supports_uploads) : true, uploadStatusCode: Number(raw?.upload_status_code || 0), uploadError: String(raw?.upload_error || ""), uploadUrl: String(raw?.upload_url || ""), runtimeDeviceMode: String(raw?.runtime_device_mode || ""), runtimeComputeTypeMode: String(raw?.runtime_compute_type_mode || ""), activeDevice: String(raw?.active_device || ""), activeComputeType: String(raw?.active_compute_type || ""), languageMode: String(raw?.language_mode || ""), modelName: String(raw?.model_name || "") }; } function isFnosTunnelCutvideo(detail) { if (!detail || detail.key !== "cutvideo") return false; if (detail.routeMode) return detail.routeMode === "fnos_tunnel"; const baseUrl = String(detail.baseUrl || detail.url || ""); return /:19186(?:\/|$)/.test(baseUrl); } function getCutvideoIntegrationHint(detail) { if (isFnosTunnelCutvideo(detail)) { return "fnOS NAS 隧道入口"; } return "Windows 直连"; } function getCutvideoIntegrationUrlLabel(detail) { return isFnosTunnelCutvideo(detail) ? "fnOS NAS 隧道入口" : "Windows 直连"; } function getAsrRuntimeBadge(detail) { if (!detail || detail.key !== "asr") return ""; const activeDevice = String(detail.activeDevice || "").trim().toLowerCase(); if (activeDevice === "cuda") return "GPU"; if (activeDevice === "cpu") return "CPU"; const runtimeMode = String(detail.runtimeDeviceMode || "").trim().toLowerCase(); if (runtimeMode === "auto") return "自动"; return runtimeMode ? runtimeMode.toUpperCase() : ""; } function getIntegrationStatus(detail) { if (!detail.available) { return { tone: "blue", summary: "未拉取" }; } if (detail.key === "cutvideo" && detail.reachable && !detail.supportsUploads) { return { tone: "orange", summary: "缺上传能力" }; } if (detail.reachable) { if (detail.key === "asr") { const runtimeBadge = getAsrRuntimeBadge(detail); return { tone: "green", summary: runtimeBadge ? `在线 · ${runtimeBadge}` : "在线" }; } return { tone: "green", summary: "在线" }; } if (detail.configured) { return { tone: "red", summary: "不可达" }; } return { tone: "orange", summary: "未配置" }; } function describeIntegrationFailure(key) { const detail = getIntegrationDetail(key); const meta = INTEGRATION_META[key] || { label: key }; if (!detail.available) return `${meta.label}健康状态未拉取`; if (key === "cutvideo" && detail.reachable && !detail.supportsUploads) { return `${meta.label}缺少 /api/uploads`; } if (!detail.configured) return `${meta.label}未配置`; if (detail.statusCode) return `${meta.label}返回 HTTP ${detail.statusCode}`; if (detail.error) return `${meta.label}${brief(detail.error, 42)}`; return `${meta.label}不可达`; } function getPipelineGuard(kind) { const config = PIPELINE_GUARDS[kind]; if (!config) { return { enabled: true, reason: "", blocked: [] }; } const blocked = config.dependencies .map((key) => ({ key, detail: getIntegrationDetail(key), meta: INTEGRATION_META[key] || { label: key } })) .filter((item) => { if (!item.detail.available) return false; if (!item.detail.reachable) return true; if (item.key === "cutvideo" && !item.detail.supportsUploads) return true; return false; }); if (!blocked.length) { return { enabled: true, reason: "", blocked: [] }; } return { enabled: false, blocked, reason: `${config.label}暂不可用:${blocked.map((item) => describeIntegrationFailure(item.key)).join(";")}` }; } function getIntegrationCards() { const currentModel = getCurrentModelProfile(); const localCatalog = appState.localModelCatalog || {}; return INTEGRATION_ORDER.map((key) => { const detail = getIntegrationDetail(key); const status = getIntegrationStatus(detail); const meta = INTEGRATION_META[key] || { label: key, hint: key, impacts: [] }; const metaHint = key === "cutvideo" ? getCutvideoIntegrationHint(detail) : meta.hint; let note = "尚未获取健康检查数据"; if (detail.available) { if (detail.reachable) { if (key === "cutvideo" && !detail.supportsUploads) { note = detail.uploadStatusCode ? `主服务在线,但 /api/uploads 返回 HTTP ${detail.uploadStatusCode}` : (detail.uploadError ? brief(detail.uploadError, 72) : "主服务在线,但缺少上传接口"); } else if (key === "cutvideo" && isFnosTunnelCutvideo(detail)) { note = "当前走 fnOS NAS 隧道,不是 Windows 直连 cutvideo"; } else { note = detail.statusCode ? `健康探测返回 HTTP ${detail.statusCode}` : "TCP 探测已通过"; } } else if (!detail.configured) { note = "后端还没有配置该依赖地址"; } else if (detail.statusCode) { note = `探测返回 HTTP ${detail.statusCode}`; } else if (detail.error) { note = brief(detail.error, 72); } else { note = "探测失败,请检查服务进程和网络"; } } let extra = ""; let actions = ""; if (key === "local_model") { const availableModels = safeArray(localCatalog.models).map((item) => item.id).filter(Boolean); extra = currentModel ? `当前主模型:${currentModel.name} · ${currentModel.model_name || "-"}` : `默认模型:${localCatalog.default_model || "GLM-5"}`; if (availableModels.length) { extra += ` · 可用:${availableModels.slice(0, 4).join(" / ")}${availableModels.length > 4 ? "…" : ""}`; } actions = [ localCatalog.management_url ? `打开管理页` : "", `设主模型` ].filter(Boolean).join(""); } if (key === "live_recorder") { const ownedSources = safeArray(appState.liveRecorderSources); const ownedFiles = safeArray(appState.liveRecorderFiles); const activeCount = Number(appState.liveRecorderStatus?.recording_count || 0); extra = ownedSources.length ? `我的录制源 ${ownedSources.length} · 录像 ${ownedFiles.length} · 正在录制 ${activeCount}` : "当前还没有你的录制源"; actions = `录制控制`; } if (key === "cutvideo") { extra = isFnosTunnelCutvideo(detail) ? `当前通过 fnOS NAS 隧道访问 ${detail.baseUrl || detail.url || "cutvideo"}` : `当前直连 ${detail.baseUrl || detail.url || "cutvideo"}`; } if (key === "asr") { const runtimeBadge = getAsrRuntimeBadge(detail) || "待热身"; const computeLabel = detail.activeComputeType || detail.runtimeComputeTypeMode || "auto"; const languageLabel = detail.languageMode || "auto"; extra = `部署:${detail.deploymentLabel || "待确认"} · 当前转写:${runtimeBadge} · ${computeLabel} · 语言 ${languageLabel}`; if (detail.modelName) { extra += ` · 当前模型:${detail.modelName}`; } } else if (detail.deploymentLabel) { extra = `部署:${detail.deploymentLabel}`; } if (detail.available && !detail.configured && isSuperAdmin()) { actions = [ actions, `去管理员配置台` ].filter(Boolean).join(""); } return { key, meta: { ...meta, hint: metaHint }, detail, status, note, extra, actions }; }); } function renderLiveRecorderSummaryHtml() { const sources = safeArray(appState.liveRecorderSources); const files = safeArray(appState.liveRecorderFiles); const status = appState.liveRecorderStatus || {}; const activeItems = safeArray(status.active_recordings); const sourceHtml = sources.slice(0, 4).map((item) => `

${escapeHtml(item.title || item.remote_name || item.source_url || "录制源")}

${escapeHtml(platformLabel(item.platform))} · ${escapeHtml(item.quality || "原画")} · ${escapeHtml(item.enabled ? "启用中" : "已停用")}

`).join(""); const fileHtml = files.slice(0, 4).map((item) => `

${escapeHtml(item.title || item.name || "录像文件")}

${escapeHtml(item.mtime || "-")} · ${escapeHtml(item.name || item.relative_path || "-")}

打开录像
`).join(""); return `

租户隔离状态

当前只展示你自己名下的录制源、活动录制和录像文件。全局 NAS 配置不会直接暴露给前端。

${escapeHtml(`录制源 ${sources.length}`)} ${escapeHtml(`活动 ${activeItems.length}`)} ${escapeHtml(`文件 ${files.length}`)}
${sourceHtml || `

还没有录制源

新增直播源后会自动挂到你的租户空间下。

`} ${fileHtml || `

还没有录像文件

录制完成后的文件会只出现在你的当前租户视图里。

`} `; } function getStorageItemPath(item) { return ( item?.artifacts?.source_path || item?.artifacts?.uploaded_path || item?.artifacts?.output_path || item?.artifacts?.file_path || item?.result?.source_path || item?.result?.output_path || item?.result?.file_path || item?.result?.path || item?.save_path || item?.path || item?.relative_path || item?.content_url || item?.job_id || item?.id || "-" ); } function renderStorageJobCards(items, emptyTitle, emptyText) { return safeArray(items).slice(0, 4).map((item) => `

${escapeHtml(item.title || item.name || item.job_id || "任务")}

${escapeHtml(brief(getStorageItemPath(item), 140))}

${escapeHtml(item.status || "-")} ${item.project_name ? `${escapeHtml(item.project_name)}` : ""} ${item.line_type || item.source_type ? `${escapeHtml(item.line_type || item.source_type)}` : ""} ${item.id ? `看详情` : ""}
`).join("") || `

${escapeHtml(emptyTitle)}

${escapeHtml(emptyText)}

`; } function renderStorageFileCards(items, emptyTitle, emptyText) { return safeArray(items).slice(0, 4).map((item) => `

${escapeHtml(item.title || item.name || item.relative_path || "文件")}

${escapeHtml(brief(item.relative_path || item.name || item.content_url || "-", 140))}

${(item.updated_at || item.mtime) ? `${escapeHtml(formatDateTime(item.updated_at || item.mtime))}` : ""} ${(item.size_bytes || item.size) ? `${escapeHtml(formatBytes(item.size_bytes || item.size))}` : ""} ${item.id ? `打开文件` : ""}
`).join("") || `

${escapeHtml(emptyTitle)}

${escapeHtml(emptyText)}

`; } function renderStorageStatusPanel() { const storage = appState.storageStatus; const dashboardJobs = safeArray(appState.dashboard?.recent_jobs); const currentProject = getSelectedProject(); const currentAccount = getSelectedAccount(); const liveRecorderSources = safeArray(appState.liveRecorderSources); const liveRecorderFiles = safeArray(appState.liveRecorderFiles); const fallbackRecentJobs = dashboardJobs.slice(0, 4); const recentJobs = safeArray(storage?.tenant_usage?.recent_jobs).length ? safeArray(storage?.tenant_usage?.recent_jobs) : fallbackRecentJobs; const recentFiles = [ ...safeArray(storage?.tenant_usage?.recent_download_artifacts), ...safeArray(storage?.tenant_usage?.recent_job_artifacts), ...safeArray(storage?.recent_files || storage?.recent_artifacts || storage?.tenant_usage?.recent_files || storage?.tenant_usage?.recent_artifacts) ]; if (!storage) { const projectJobCount = appState.selectedProjectId ? dashboardJobs.filter((item) => item.project_id === appState.selectedProjectId).length : dashboardJobs.length; const accountJobCount = appState.selectedAccountId ? dashboardJobs.filter((item) => item.account_id === appState.selectedAccountId).length : dashboardJobs.length; return `

存储状态

当前实例没有返回存储策略时,先用任务和录像文件做本地观察
降级视图

未拉取到 NAS 策略

当 storage/status 暂时拿不到时,这里会自动退回到任务和录像文件的降级视图。

最近任务 ${escapeHtml(formatNumber(projectJobCount))} 录制源 ${escapeHtml(formatNumber(liveRecorderSources.length))} 录像文件 ${escapeHtml(formatNumber(liveRecorderFiles.length))}
当前项目 ${escapeHtml(currentProject?.name || appState.selectedProjectId || "未选择")} 任务 ${escapeHtml(formatNumber(projectJobCount))}
当前账号 ${escapeHtml(currentAccount ? getAccountName(currentAccount) : "未选择")} 任务 ${escapeHtml(formatNumber(accountJobCount))}
录像源 ${escapeHtml(formatNumber(liveRecorderSources.length))} 仅当前租户可见
最近文件 ${escapeHtml(formatNumber(liveRecorderFiles.length))} 可直接打开

最近任务

优先展示 dashboard.recent_jobs,方便在没有 storage/status 时也能继续追踪产物。

${renderStorageJobCards( fallbackRecentJobs, "还没有任务样本", "等你完成一次分析、下载或剪辑后,这里就会出现最近的任务路径和详情入口。" )}

最近录像文件

录像文件沿用 live-recorder 的当前租户视图,支持直接打开查看。

${renderStorageFileCards( liveRecorderFiles, "还没有录像文件", "录制完成后,这里会直接暴露当前租户的最近文件入口。" )}
`; } const strategy = storage.strategy || {}; const disk = storage.disk || {}; const usage = storage.tenant_usage || {}; const strategyMode = (strategy.jobs?.mode || "local").toUpperCase(); const projectName = currentProject?.name || appState.selectedProjectId || "未选择"; const accountName = currentAccount ? getAccountName(currentAccount) : "未选择"; const strategyTags = [ `数据库 ${strategy.database?.mode || "local"}`, `分析缓存 ${strategy.jobs?.mode || "local"}`, `下载缓存 ${strategy.downloads?.mode || "local"}`, `直播录制 ${strategy.live_recorder?.mode || "nas_service"}`, ]; const usageCards = [ { label: "当前项目缓存", value: formatBytes(usage.project_jobs?.bytes), sub: `文件 ${formatNumber(usage.project_jobs?.file_count)}` }, { label: "当前项目下载", value: formatBytes(usage.project_downloads?.bytes), sub: `文件 ${formatNumber(usage.project_downloads?.file_count)}` }, { label: "当前账号缓存", value: formatBytes(usage.account_jobs?.bytes), sub: `文件 ${formatNumber(usage.account_jobs?.file_count)}` }, { label: "当前账号下载", value: formatBytes(usage.account_downloads?.bytes), sub: `文件 ${formatNumber(usage.account_downloads?.file_count)}` }, { label: "NAS 剩余", value: formatBytes(disk.jobs?.free_bytes), sub: `总量 ${formatBytes(disk.jobs?.total_bytes)}` } ]; return `

存储状态

数据库留本机,大文件缓存优先走 NAS
${escapeHtml(strategyMode)}

当前观察范围

${escapeHtml(`项目 ${projectName} · 账号 ${accountName} · 最近任务 ${recentJobs.length} 条 · 最近录像 ${liveRecorderFiles.length} 个`)}

${strategyTags.map((item) => `${escapeHtml(item)}`).join("")}
${usageCards.map((item) => `
${escapeHtml(item.label)} ${escapeHtml(item.value)} ${escapeHtml(item.sub)}
`).join("")}

目录策略

${escapeHtml([ `数据库 ${strategy.database?.path || "本机"}`, `任务缓存 ${usage.project_jobs?.path || strategy.jobs?.path || "-"}`, `下载缓存 ${usage.project_downloads?.path || strategy.downloads?.path || "-"}`, `录制缓存 ${strategy.live_recorder?.path || strategy.live_recorder?.base_path || "-"}` ].join(" · "))}

项目层 ${escapeHtml(usage.project_jobs?.path || strategy.jobs?.path || "-")} 账号层 ${escapeHtml(usage.account_jobs?.path || "-")}

产物入口

最近任务、分析产物和录像文件都能直接点开,便于从 NAS 面板跳回详情或原文件。

任务 ${escapeHtml(formatNumber(recentJobs.length))} 产物 ${escapeHtml(formatNumber(recentFiles.length))} 录像 ${escapeHtml(formatNumber(liveRecorderFiles.length))}

最近任务样本

默认取 storage.status 里的 recent_jobs;如果后端没给,会退回到 dashboard.recent_jobs。

${renderStorageJobCards( recentJobs, "还没有缓存样本", "上传视频、导入作品后,这里会显示最近写入 NAS 的缓存路径。" )} ${recentFiles.length ? `

最近产物文件

后端如果提供产物文件索引,这里会优先直接露出最近写入的文件入口。

${renderStorageFileCards( recentFiles, "还没有产物文件", "当前 storage/status 没有返回可直接打开的产物文件。" )} ` : ""}

最近录像文件

当前租户一旦写入新录像文件,这里会直接露出最近可回看的录制结果。

${renderStorageFileCards( liveRecorderFiles, "还没有录像文件", "录制完成后的文件会出现在当前租户的录像列表里。" )}
`; } function renderOneLinerActionRegistryPanel() { const items = safeArray(appState.onelinerActionRegistry); if (!items.length) { return `

OneLiner 动作注册表

当前项目还没有单独动作配置,OneLiner 会继续沿用系统默认动作。

当前沿用系统默认动作

你可以等真实使用场景稳定后,再回来给当前项目单独开关、改名或补充动作说明。

系统默认仍可执行 当前项目未单独覆盖
`; } const grouped = items.reduce((acc, item) => { const category = item.category || "custom"; acc[category] = acc[category] || []; acc[category].push(item); return acc; }, {}); return `

OneLiner 动作注册表

把 OneLiner 可执行动作做成租户级注册中心,便于商业化灰度和定制。
${escapeHtml(formatNumber(items.length))} 条
${Object.entries(grouped).map(([category, list]) => `

${escapeHtml(category)}

${escapeHtml(`当前分类下 ${list.length} 条动作。`)}

${list.map((item) => ` ${escapeHtml(item.label || item.action_key || "action")} `).join("")}
`).join("")}
`; } function renderTenantQuotaPanel() { const quota = appState.tenantQuota; const usage = appState.tenantUsage || quota?.usage || {}; const quotaConfig = quota?.config || {}; const quotaNotice = renderQuotaBlockingNotice(); if (!quota && !usage) { return `

租户额度与审计

当前项目还没有额度配置,会先按默认不限额模式运行。

当前项目还没有额度配置

先给这个项目补预算、动作配额和存储上限,再逐步收紧风险控制。

创建额度策略 默认不限额
`; } const categories = usage?.categories || {}; const recentItems = safeArray(usage?.recent_items); const categoryEntries = Object.values(categories || {}).sort((left, right) => (right?.cost_cents || 0) - (left?.cost_cents || 0)); const topCategory = categoryEntries[0] || null; const hasHardLimit = Boolean( (quota?.monthly_budget_cents || 0) > 0 || (quota?.storage_limit_bytes || 0) > 0 || (quota?.analysis_quota || 0) > 0 || (quota?.copy_quota || 0) > 0 || (quota?.ai_video_quota || 0) > 0 || (quota?.real_cut_quota || 0) > 0 || (quota?.recorder_quota || 0) > 0 ); const usageCount = recentItems.length; const packageLabel = String(quotaConfig.package_title || quotaConfig.package_label || "").trim() || (hasHardLimit ? "自定义套餐" : "未设套餐"); const packageFocus = String(quotaConfig.package_focus || "").trim(); const warnThreshold = Number(quotaConfig.warn_threshold ?? 0.8); const forecast = getTenantQuotaForecastValues(quota || {}, usage); const packageRecommendation = getTenantQuotaPackageRecommendation({ ...quota, package_label: quotaConfig.package_label || "custom" }, usage); const quotaTaskTitle = quota?.storage_over_limit ? "先处理存储超限" : quota?.enabled === false ? "先恢复额度保护" : !hasHardLimit ? "先补项目额度策略" : usageCount ? "先检查本周期消耗" : "先跑出第一条计量"; const quotaTaskSummary = quota?.storage_over_limit ? "当前项目已经命中存储上限,先调整额度或清理产物,再继续高成本动作。" : quota?.enabled === false ? "额度保护已关闭,当前项目会按无限制模式运行,建议尽快恢复预算与动作保护。" : !hasHardLimit ? "当前项目虽然可继续运行,但还没有预算和动作配额,先把保护线补齐更稳。" : usageCount ? "本周期已经开始消耗额度,先看最主要的消耗来源,再决定是否收紧策略。" : "额度已经建好,但当前周期还没有实际计量,先触发一次真实动作更容易校准策略。"; const quotaTaskActionLabel = quota?.storage_over_limit || !hasHardLimit || quota?.enabled === false ? "调整额度" : usageCount ? "查看最近计量" : "去跑动作"; const quotaTaskAction = quotaTaskActionLabel === "去跑动作" ? "goto-production" : quotaTaskActionLabel === "查看最近计量" ? "" : "open-tenant-quota"; const usagePeriodLabel = usage?.cycle_start ? formatDateTime(usage.cycle_start).slice(0, 10) : "本周期"; const quotaHealthTags = [ quota?.enabled === false ? `额度保护关闭` : `额度保护开启`, quota?.storage_over_limit ? `存储超限` : `存储正常`, topCategory ? `${escapeHtml(`主要消耗 ${topCategory.category || "usage"}`)}` : `本周期未产生消耗`, `${escapeHtml(`推荐 ${packageRecommendation.title}`)}` ]; const cards = [ { label: "套餐档位", value: packageLabel, sub: packageFocus || `预警阈值 ${formatNumber((warnThreshold || 0.8) * 100)}%` }, { label: "预算", value: `${formatNumber((quota?.monthly_budget_cents || 0) / 100)} 元`, sub: `已用 ${formatNumber((usage?.total_cost_cents || 0) / 100)} 元 · 剩余 ${formatNumber(forecast.remainingBudgetCents / 100)} 元` }, { label: "分析配额", value: formatNumber(quota?.analysis_quota || 0), sub: `已用 ${formatNumber(categories.analysis?.quantity || 0)} · 剩余 ${formatNumber(forecast.remainingAnalysisQuota)}` }, { label: "文案配额", value: formatNumber(quota?.copy_quota || 0), sub: `已用 ${formatNumber(categories.copy?.quantity || 0)} · 剩余 ${formatNumber(forecast.remainingCopyQuota)}` }, { label: "AI 视频配额", value: formatNumber(quota?.ai_video_quota || 0), sub: `已用 ${formatNumber(categories.ai_video?.quantity || 0)} · 剩余 ${formatNumber(forecast.remainingAiVideoQuota)}` }, { label: "实拍剪辑配额", value: formatNumber(quota?.real_cut_quota || 0), sub: `已用 ${formatNumber(categories.real_cut?.quantity || 0)} · 剩余 ${formatNumber(forecast.remainingRealCutQuota)}` }, { label: "存储上限", value: formatBytes(quota?.storage_limit_bytes || 0), sub: `当前 ${formatBytes(usage?.storage_bytes || 0)} · 剩余 ${formatBytes(forecast.remainingStorageBytes)}` } ]; return `

租户额度与审计

${escapeHtml(packageFocus || "预算、动作配额和最近计量都按租户 + 项目隔离,首屏先看风险和下一步。")}
${escapeHtml(quota?.enabled === false ? "已停用额度保护" : "额度保护开启")} ${escapeHtml(packageLabel)} ${quota?.storage_over_limit ? `存储超限` : ""} 编辑额度

${escapeHtml(quotaTaskTitle)}

${escapeHtml(quotaTaskSummary)}

${quotaTaskAction ? `${escapeHtml(quotaTaskActionLabel)}` : `${escapeHtml(quotaTaskActionLabel)}`} ${quotaHealthTags.join("")}
${escapeHtml(`套餐 ${packageLabel}`)} ${escapeHtml(`周期 ${usagePeriodLabel}`)} ${escapeHtml(`计量 ${formatNumber(usageCount)} 条`)} ${escapeHtml(`成本 ${(usage?.total_cost_cents || 0) / 100} 元`)} ${escapeHtml(`预警 ${(warnThreshold || 0.8) * 100}%`)}
${renderTenantQuotaForecastTags(quota || {}, usage)}
${quotaNotice}
${cards.map((item) => `
${escapeHtml(item.label)} ${escapeHtml(item.value)} ${escapeHtml(item.sub)}
`).join("")}

最近计量记录

动作执行后会写入租户级 ledger,便于后面做商业化配额、成本和审计。

${recentItems.map((item) => `

${escapeHtml(item.category || "usage")}

${escapeHtml(formatDateTime(item.created_at))}

次数 ${escapeHtml(formatNumber(item.quantity || 0))} 成本 ${(item.cost_cents || 0) / 100} 元 ${item.reference_type ? `${escapeHtml(item.reference_type)}` : ""} ${item.reference_id ? `${escapeHtml(brief(item.reference_id, 14))}` : ""}
`).join("") || `

还没有计量记录

等 OneLiner 或生产动作实际执行后,这里会累积本周期的 usage ledger。

`}
`; } function policyScopeTagLabel(scopeKind, platform = "") { if (scopeKind === "system_main") return "系统默认"; if (scopeKind === "system_platform") return `${platformLabel(platform || "douyin")} 默认`; if (scopeKind === "user_global") return "我的全局"; if (scopeKind === "user_platform") return `${platformLabel(platform || "douyin")} 我的策略`; if (scopeKind === "admin_override") return "管理员覆盖"; return "策略层"; } function summarizePolicyHighlights(policy = {}, platform = "") { const items = []; if (policy?.tone?.style) items.push(`语气 ${policy.tone.style}`); if (policy?.actions?.max_cards != null) items.push(`首页动作 ${formatNumber(policy.actions.max_cards)} 条`); if (policy?.memory?.default_window) items.push(`记忆窗口 ${policy.memory.default_window}`); if (platform && policy?.[platform]?.benchmark_mode) items.push(`${platformLabel(platform)} 对标 ${policy[platform].benchmark_mode}`); if (policy?.guardrails?.require_admin_review) items.push("需管理员复核"); return items.slice(0, 4); } function getGovernanceDirectoryItems() { return safeArray(appState.adminGovernanceDirectory?.items || appState.adminGovernanceDirectory); } function parseAdminOverrideScopeValue(value) { const normalized = String(value || "").trim(); if (!normalized) return { targetUserId: "", targetProjectId: "" }; if (normalized.startsWith("project:")) { const parts = normalized.slice("project:".length).split("|"); return { targetUserId: parts[0] || "", targetProjectId: parts[1] || "" }; } if (normalized.startsWith("user:")) { return { targetUserId: normalized.slice("user:".length), targetProjectId: "" }; } return { targetUserId: normalized, targetProjectId: "" }; } function getAdminOverrideTargetOptions(directoryItems = getGovernanceDirectoryItems()) { return safeArray(directoryItems).flatMap((account) => { const accountLabel = account.display_name || account.username || account.id; const projects = safeArray(account.projects); return [ { value: `user:${account.id}`, label: `${accountLabel} · 全部项目` }, ...projects.map((project) => ({ value: `project:${account.id}|${project.id}`, label: `${accountLabel} / ${project.name || project.id}` })) ]; }); } function normalizeAdminOverrideTarget(target, directoryItems = getGovernanceDirectoryItems(), fallbackPlatform = "") { const items = safeArray(directoryItems); if (!items.length) { return { targetUserId: "", targetProjectId: "", platform: normalizePlatformValue(fallbackPlatform, "douyin") }; } const preferred = target && target.targetUserId ? items.find((item) => item.id === target.targetUserId) : items.find((item) => item.role !== "super_admin") || items[0]; const preferredProjects = safeArray(preferred?.projects); const project = target?.targetProjectId ? preferredProjects.find((item) => item.id === target.targetProjectId) : preferredProjects[0] || null; return { targetUserId: preferred?.id || "", targetProjectId: project?.id || "", platform: target?.platform === "" ? "" : normalizePlatformValue(target?.platform || fallbackPlatform, "douyin") }; } function findGovernanceDirectoryAccount(userId) { return getGovernanceDirectoryItems().find((item) => item.id === userId) || null; } function findGovernanceDirectoryProject(userId, projectId) { const account = findGovernanceDirectoryAccount(userId); return safeArray(account?.projects).find((item) => item.id === projectId) || null; } function getAdminOverrideTargetSummary(target = appState.adminOverrideTarget) { const normalized = target || {}; const account = findGovernanceDirectoryAccount(normalized.targetUserId || ""); const project = normalized.targetProjectId ? findGovernanceDirectoryProject(normalized.targetUserId || "", normalized.targetProjectId) : null; return { account, project, accountLabel: account?.display_name || account?.username || normalized.targetUserId || "未选择用户", projectLabel: project?.name || (normalized.targetProjectId ? normalized.targetProjectId : "全部项目"), platformLabel: normalized.platform ? platformLabel(normalized.platform) : "全部平台" }; } function renderGovernanceSummaryCard({ title, subtitle, effective, primaryAction = "", primaryLabel = "编辑策略", secondaryAction = "", secondaryLabel = "", secondaryPlatform = "", actions = null }) { const layers = safeArray(effective?.layers); const highlights = summarizePolicyHighlights(effective?.effective_policy || {}, effective?.platform || secondaryPlatform || ""); const activeAdminOverrideNotice = effective?.active_admin_override_notice || null; const resolvedActions = safeArray(actions?.length ? actions : [ primaryAction ? { action: primaryAction, label: primaryLabel } : null, secondaryAction ? { action: secondaryAction, label: secondaryLabel, platform: secondaryPlatform } : null ].filter(Boolean)); return `

${escapeHtml(title)}

${escapeHtml(subtitle || "当前还没有策略摘要。")}

${layers.map((layer) => `${escapeHtml(policyScopeTagLabel(layer.scope_kind, layer.scope?.platform || effective?.platform || ""))}`).join("") || `尚未发布`} ${highlights.map((item) => `${escapeHtml(item)}`).join("")}
${activeAdminOverrideNotice?.title ? `

管理员覆盖生效中

${escapeHtml(activeAdminOverrideNotice.summary || activeAdminOverrideNotice.title || "当前这层管理员覆盖会优先于你的个人策略生效。")}

${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")} ${activeAdminOverrideNotice.platform_label ? `${escapeHtml(activeAdminOverrideNotice.platform_label)}` : ""}
` : ""} ${resolvedActions.length ? `
${resolvedActions.map((item) => ` ${escapeHtml(item.label || "查看")} `).join("")}
` : ""}
`; } function renderAdminGovernanceSummaryPanel() { const systemMain = appState.adminSystemMainPolicy; const systemPlatforms = safeArray(appState.adminSystemPlatformPolicies); const configuredPlatforms = systemPlatforms.filter((item) => item?.current_version); const targetSummary = getAdminOverrideTargetSummary(); const overrideBundle = appState.adminOverridePolicy; return `

系统级主 Agent 治理

先管系统默认主 Agent,再按平台补默认策略,普通用户的个性化覆盖会叠加在这些底座之上。
系统主 Agent ${escapeHtml(systemMain?.current_version ? "已发布" : "未发布")} ${escapeHtml(formatNumber(configuredPlatforms.length))} 个平台默认策略 编辑系统主 Agent
系统主 Agent
${escapeHtml(systemMain?.current_version?.summary || "还没有发布系统默认主 Agent 策略。")}
${escapeHtml(systemMain?.current_version ? `版本 ${formatNumber(systemMain.current_version.version_no)}` : "未发布")} 历史 ${escapeHtml(formatNumber(systemMain?.versions?.count || 0))} 历史与回滚
管理员覆盖
${escapeHtml(overrideBundle?.current_version?.summary || `${targetSummary.accountLabel} / ${targetSummary.projectLabel} / ${targetSummary.platformLabel}`)}
${escapeHtml(targetSummary.accountLabel)} ${escapeHtml(targetSummary.projectLabel)} ${escapeHtml(targetSummary.platformLabel)} ${escapeHtml(overrideBundle?.current_version ? `版本 ${formatNumber(overrideBundle.current_version.version_no)}` : "未发布")} 切换目标 编辑覆盖 历史与回滚
${ACTIVE_PLATFORMS.map((platformItem) => { const item = systemPlatforms.find((entry) => entry?.scope?.platform === platformItem.value) || null; return `
${escapeHtml(platformItem.label)} 默认策略
${escapeHtml(item?.current_version?.summary || "还没有平台默认策略,当前会沿用系统主 Agent 默认。")}
${escapeHtml(item?.current_version ? `版本 ${formatNumber(item.current_version.version_no)}` : "沿用系统默认")} 历史 ${escapeHtml(formatNumber(item?.versions?.count || 0))} 编辑 历史与回滚
`; }).join("")}
`; } function renderAdminGovernanceAuditPanel() { const targetSummary = getAdminOverrideTargetSummary(); const audits = safeArray(appState.adminPolicyAudits); return `

覆盖与审计

按当前目标查看管理员覆盖、系统默认和相关策略变更,避免治理动作只留在弹窗里。
${escapeHtml(targetSummary.accountLabel)} ${escapeHtml(targetSummary.projectLabel)} ${escapeHtml(targetSummary.platformLabel)} 切换目标

当前管理员覆盖

${escapeHtml(appState.adminOverridePolicy?.current_version?.summary || "当前目标还没有管理员覆盖版本。")}

${escapeHtml(appState.adminOverridePolicy?.current_version ? `版本 ${formatNumber(appState.adminOverridePolicy.current_version.version_no || 0)}` : "未发布")} 编辑覆盖 历史与回滚

治理说明

管理员对用户策略的代改不会抹掉用户自己的历史,只会形成更高优先级的覆盖层,并在这里留下审计记录。

最近策略审计

系统默认、用户策略和管理员覆盖都会在这里形成时间线。
${renderPolicyAuditFeed(audits, "当前目标还没有策略审计记录。")}
`; } function renderPlatformAgentPanel() { const items = safeArray(appState.platformAgents); if (!items.length) { return `

平台 Agent

当前项目还没有平台 Agent 配置,先从最常用的平台补起。
${ACTIVE_PLATFORMS.map((platformItem) => `
${escapeHtml(platformItem.label)} Agent
当前还没有单独配置,主 Agent 会先沿用系统默认平台方法论。
未单独配置 开始配置
`).join("")}
`; } return `

平台 Agent

按用户 + 平台隔离,沉淀该平台的方法论、记忆和技能。
${escapeHtml(formatNumber(items.length))} 个
${items.map((item) => ` ${(() => { const recentExecutionOnelinerConfigStale = isConfigurationVersionStale( item.recent_execution || {}, appState.onelinerProfile?.current_version || {} ); const recentExecutionPlatformConfigStale = isConfigurationVersionStale( { version_id: item.recent_execution?.platform_agent_profile_version_id, version_no: item.recent_execution?.platform_agent_profile_version_no, }, item.current_version || {} ); return `
${escapeHtml(item.name || item.platform_label)}
${escapeHtml(item.mission || item.notes || "先绑定执行 Agent,再完善任务目标和方法论。")}
${escapeHtml(item.status || "draft")} ${item.readiness_label ? `= 50 ? "blue" : "orange"}">${escapeHtml(item.readiness_label)} ${escapeHtml(formatNumber(item.readiness_score || 0))}` : ""} 记忆 ${escapeHtml(formatNumber(item.memory_count))} 技能 ${escapeHtml(formatNumber(item.skill_count))} ${escapeHtml(item.assistant?.name || "未绑 Agent")}
${item.recent_memory || item.recent_skill ? `
${item.recent_memory ? `

最近记忆 · ${escapeHtml(item.recent_memory.title || item.recent_memory.memory_key || "未命名")}

${escapeHtml(brief(item.recent_memory.summary || "暂无摘要", 68))}

` : ""} ${item.recent_skill ? `

最近技能 · ${escapeHtml(item.recent_skill.name || item.recent_skill.skill_key || "未命名")}

${escapeHtml(brief(item.recent_skill.test_spec?.summary || item.recent_skill.method?.summary || "暂无方法摘要", 68))}

${escapeHtml(item.recent_skill.status || "draft")} 得分 ${escapeHtml(formatNumber(item.recent_skill.last_score || 0))}
` : ""}
` : ""} ${item.recent_execution?.run_id ? `

最近执行

${escapeHtml(item.recent_execution.title || item.recent_execution.goal || item.recent_execution.summary || "最近一次主 Agent 执行已回写到当前平台 Agent。")}

${escapeHtml(item.recent_execution.intent_label || "主 Agent 任务")} ${escapeHtml(item.recent_execution.run_status || "done")} ${item.recent_execution.platform_scope ? `${escapeHtml(item.recent_execution.platform_scope === "all_platforms" ? "全平台" : "单平台")}` : ""} ${item.recent_execution.delivery_mode ? `${escapeHtml(item.recent_execution.delivery_mode)}` : ""} ${item.recent_execution.workstream_label ? `${escapeHtml(item.recent_execution.workstream_label)}` : ""} ${item.recent_execution.oneliner_profile_version_no ? `配置 v${escapeHtml(formatNumber(item.recent_execution.oneliner_profile_version_no))}` : ""} ${recentExecutionOnelinerConfigStale ? `主配置已更新` : ""} ${item.recent_execution.platform_agent_profile_version_no ? `${escapeHtml(item.platform_label || platformLabel(item.platform))} Agent v${escapeHtml(formatNumber(item.recent_execution.platform_agent_profile_version_no))}` : ""} ${recentExecutionPlatformConfigStale ? `${escapeHtml(item.platform_label || platformLabel(item.platform))} Agent 已更新` : ""} ${item.recent_execution.source_screen ? `${escapeHtml(screenLabel(item.recent_execution.source_screen) || item.recent_execution.source_screen)}` : ""}
${item.recent_execution.recommended_action?.action ? `${escapeHtml(item.recent_execution.recommended_action.label || "回到业务页")}` : ""} ${item.recent_execution.oneliner_profile_version_no ? `主配置历史` : ""} ${item.recent_execution.platform_agent_profile_version_no ? `平台配置历史` : ""} 查看执行结果 回到主 Agent 查看
` : ""}
查看详情 配置 看配置历史 补记忆 补技能
`; })()} `).join("")}
`; } function renderAdminOpsPanel() { if (!isSuperAdmin()) return ""; const overview = appState.adminOpsOverview; if (!overview) { return `

运维与审计 Agent

仅平台最高权限用户可见。

尚未拉到概览

刷新后会自动读取失败任务、集成健康和待审事件。

${renderAdminFixRunsPanel()} ${renderRecoveryHistoryPanel()}
`; } const incidents = safeArray(overview.incidents).slice(0, 6); const audits = safeArray(overview.recent_audits).slice(0, 5); return `

运维与审计 Agent

只给管理员开放,主要盯日志、失败任务和集成异常。
${escapeHtml(formatNumber(overview.incident_count))} 条事件 待处理 ${escapeHtml(formatNumber(overview.open_incident_count || 0))} 错误 ${escapeHtml(formatNumber(overview.severity_counts?.error || 0))} ${escapeHtml(formatNumber(overview.failed_job_count))} 个失败任务 修复计划 ${escapeHtml(formatNumber(overview.fix_run_count || 0))} 重新扫描
${incidents.map((item) => `

${escapeHtml(item.title)}

${escapeHtml(item.summary || "待补详情")}

${escapeHtml(item.severity || "warn")} ${escapeHtml(item.status || "open")} ${item.source_type ? `${escapeHtml(item.source_type)}` : ""} ${item.tenant_user_id ? `租户 ${escapeHtml(brief(item.tenant_user_id, 12))}` : ""} ${item.source_type === "job" ? actionTag("看任务详情", "open-job-detail", `data-job-id="${escapeHtml(item.source_id || "")}"`) : ""} ${item.source_type === "integration" ? actionTag("去自动流程", "goto-automation") : ""} ${item.tenant_project_id ? actionTag("去生产中心", "goto-production") : ""} 生成修复计划 审计处理
`).join("") || `

当前没有待处理事件

最近主链比较稳定,继续观察即可。

`}

最近审计记录

保留管理员扫描、放行、驳回等动作,方便商业化量产时追责和复盘。

${audits.map((item) => `

${escapeHtml(item.summary || item.action_key || "审计记录")}

${escapeHtml(formatDateTime(item.created_at))}

${escapeHtml(item.action_key || "audit")} ${escapeHtml(item.status || "recorded")} ${item.incident_id ? `事件 ${escapeHtml(brief(item.incident_id, 10))}` : ""}
`).join("") || `

还没有审计记录

等管理员做一次扫描或审计处理后,这里会自动出现。

`}
${renderAdminFixRunsPanel()} ${renderRecoveryHistoryPanel()}
`; } function getIntegrationOverview() { const cards = getIntegrationCards(); const reachableCount = cards.filter((item) => item.detail.available && item.detail.reachable).length; const availableCount = cards.filter((item) => item.detail.available).length; const aiVideoGuard = getPipelineGuard("aiVideo"); const realCutGuard = getPipelineGuard("realCut"); const blockedActions = [ !aiVideoGuard.enabled ? aiVideoGuard.reason : "", !realCutGuard.enabled ? realCutGuard.reason : "" ].filter(Boolean); const tone = !availableCount ? "blue" : blockedActions.length ? "red" : cards.some((item) => item.detail.available && !item.detail.reachable) ? "orange" : "green"; const headline = !availableCount ? "依赖健康尚未拉取" : blockedActions.length ? `自动链路受阻:${blockedActions.length} 项` : `${reachableCount}/${cards.length} 项依赖在线`; const subtitle = !availableCount ? "刷新后会显示直播录制 / cutvideo / huobao / n8n / ASR 的真实状态。" : blockedActions.length ? blockedActions.join(";") : "AI 视频与实拍剪辑链路当前可直接发起。"; return { cards, tone, headline, subtitle }; } function getJobSeedBrief(job) { return [ job?.style_summary, job?.transcript_text, job?.result?.summary, job?.artifacts?.brief, job?.title ].find((value) => String(value || "").trim()) || ""; } function collectHttpLinks(input, path = "result", bucket = []) { if (!input) return bucket; if (typeof input === "string") { const value = input.trim(); if (/^https?:\/\//i.test(value)) { bucket.push({ label: path, url: value }); } return bucket; } if (Array.isArray(input)) { input.forEach((item, index) => collectHttpLinks(item, `${path}[${index}]`, bucket)); return bucket; } if (typeof input === "object") { Object.entries(input).forEach(([key, value]) => collectHttpLinks(value, `${path}.${key}`, bucket)); } return bucket; } function getJobPreviewLinks(job) { const deduped = []; const seen = new Set(); collectHttpLinks(job?.result, "result", deduped); collectHttpLinks(job?.artifacts, "artifacts", deduped); return deduped.filter((item) => { if (!item.url || seen.has(item.url)) return false; seen.add(item.url); return true; }).slice(0, 8); } function isCandidateLinked(candidate, links) { const accountId = String(candidate?.candidate_account_id || ""); const profileUrl = String(candidate?.candidate_profile_url || ""); return safeArray(links).some((link) => ( (accountId && String(link.target_account_id || "") === accountId) || (profileUrl && String(link.target_profile_url || "") === profileUrl) )); } function markSavedCandidate(candidate, links) { const nextCandidates = safeArray(appState.lastSimilaritySearch?.candidates).map((item) => { const sameAccount = String(item.candidate_account_id || "") && String(item.candidate_account_id || "") === String(candidate.candidate_account_id || ""); const sameUrl = String(item.candidate_profile_url || "") && String(item.candidate_profile_url || "") === String(candidate.candidate_profile_url || ""); if (!sameAccount && !sameUrl) return item; return { ...item, saved: true }; }); if (appState.lastSimilaritySearch) { appState.lastSimilaritySearch = { ...appState.lastSimilaritySearch, candidates: nextCandidates }; } if (appState.selectedWorkspace) { appState.selectedWorkspace = { ...appState.selectedWorkspace, linked_accounts: safeArray(links) }; } } async function saveCandidateAsBenchmark(candidateIndex, relationType = "benchmark") { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); const benchmarkPath = getWorkbenchRoute(platform, "benchmarkLinks", account.id); const candidate = safeArray(appState.lastSimilaritySearch?.candidates)[Number(candidateIndex)]; if (!candidate) throw new Error("当前候选不存在,请先重新查相似"); const payload = { target_account_ids: candidate.candidate_account_id ? [candidate.candidate_account_id] : [], target_profile_urls: candidate.candidate_account_id ? [] : [candidate.candidate_profile_url].filter(Boolean), relation_type: relationType, note: brief(candidate.rationale_text || "由相似搜索自动加入对标库", 120), search_id: appState.lastSimilaritySearch?.id || "" }; if (!payload.target_account_ids.length && !payload.target_profile_urls.length) { throw new Error("当前候选没有可保存的账号或主页链接"); } const result = await storyforgeFetch(benchmarkPath, { method: "POST", body: payload }); markSavedCandidate(candidate, result.links); rememberAction("候选已存对标", `已把「${candidate.candidate_nickname || candidate.candidate_profile_url || "候选账号"}」加入对标关系。`, "green", result); focusDiscoveryRelations(); } function screenShell(title, subtitle, actionsHtml, bodyHtml) { const actionLayout = splitPrimaryAction(actionsHtml); const body = String(bodyHtml || ""); const hasMobileFocusCard = body.includes("mobile-flow-focus-card"); const headClassName = ["screen-head"]; if (hasMobileFocusCard) headClassName.push("screen-head-has-mobile-focus"); return `

${escapeHtml(title)}

${escapeHtml(subtitle)}

${actionLayout.primary ? `
${actionLayout.primary}
` : ""} ${actionLayout.secondary ? `
${actionLayout.secondary}
` : ""}
${body} `; } function splitPrimaryAction(actionsHtml) { const source = String(actionsHtml || "").trim(); if (!source) return { primary: "", secondary: "" }; const actions = source .split(/(?= item.trim()) .filter(Boolean); if (!actions.length) return { primary: source, secondary: "" }; return { primary: actions[0], secondary: actions.slice(1).join("") }; } function button(label, action, tone = "secondary", options = {}) { const classes = ["btn", `btn-${tone}`]; if (options.className) classes.push(options.className); if (options.disabledReason) classes.push("is-disabled"); const targetAction = options.disabledReason ? "show-disabled-reason" : action; const title = options.disabledReason || options.title || ""; const attrs = options.attrs || ""; return ` `.replace(/\s+/g, " ").trim(); } function actionTag(label, action, attrs = "", options = {}) { const classes = ["tag"]; const targetAction = options.disabledReason ? "show-disabled-reason" : action; if (options.disabledReason) { classes.push("tag-disabled"); } else if (targetAction) { classes.push("clickable-tag"); } const title = options.disabledReason || options.title || ""; if (targetAction) { return ` `.replace(/\s+/g, " ").trim(); } return ` ${escapeHtml(label)} `.replace(/\s+/g, " ").trim(); } function renderPipelineButton(kind, tone = "secondary") { const config = PIPELINE_GUARDS[kind]; if (!config) return ""; const guard = getPipelineGuard(kind); const quotaGuard = kind === "aiVideo" ? getQuotaSummaryForCategory("ai_video") : kind === "realCut" ? getQuotaSummaryForCategory("real_cut") : { blocked: false, reason: "" }; return button(config.label, config.openAction, tone, { disabledReason: guard.enabled ? (quotaGuard.blocked ? quotaGuard.reason : "") : guard.reason }); } function renderPipelineJobTag(kind, job, label) { const config = PIPELINE_GUARDS[kind]; if (!config || !job?.id) return ""; const guard = getPipelineGuard(kind); const quotaGuard = kind === "aiVideo" ? getQuotaSummaryForCategory("ai_video") : kind === "realCut" ? getQuotaSummaryForCategory("real_cut") : { blocked: false, reason: "" }; return actionTag(label, config.jobAction, `data-job-id="${escapeHtml(job.id)}"`, { disabledReason: guard.enabled ? (quotaGuard.blocked ? quotaGuard.reason : "") : guard.reason }); } function renderIntegrationOverviewPanel(options = {}) { const overview = getIntegrationOverview(); const cards = overview.cards; const showActions = options.showActions !== false; return `
${escapeHtml(overview.headline)}

${escapeHtml(overview.subtitle)}

${showActions ? `
${renderPipelineButton("aiVideo", "primary")} ${renderPipelineButton("realCut")}
` : ""}
${cards.map((item) => `${escapeHtml(item.meta.label)} ${escapeHtml(item.status.summary)}`).join("")}
${cards.map((item) => `

${escapeHtml(item.meta.label)}

${escapeHtml(item.meta.hint)}

${escapeHtml(item.status.summary)}
${safeArray(item.meta.impacts).map((impact) => `${escapeHtml(impact)}`).join("")} ${item.detail.statusCode ? `HTTP ${escapeHtml(item.detail.statusCode)}` : ""}
${escapeHtml(item.note)}
${item.extra ? `
${escapeHtml(item.extra)}
` : ""}
${escapeHtml(item.key === "live_recorder" ? "仅通过当前租户的后端代理访问" : (item.key === "cutvideo" ? `${getCutvideoIntegrationUrlLabel(item.detail)}:${item.detail.url || item.detail.baseUrl || "等待配置探测地址"}` : (item.detail.url || item.detail.baseUrl || "等待配置探测地址")))}
${item.actions ? `
${item.actions}
` : ""}
`).join("")}
`; } function buildMainAgentHandoffAttrs({ sourceScreen = "", sourceActionKey = "", intentKey = "custom", title = "", goal = "", summary = "", platform = "", platformScope = "single_platform", planSteps = [] } = {}) { const attrs = [ `data-source-screen="${escapeHtml(sourceScreen || appState.screen || "dashboard")}"`, `data-source-action-key="${escapeHtml(sourceActionKey || "main-agent-handoff")}"`, `data-intent-key="${escapeHtml(intentKey || "custom")}"`, `data-title="${escapeHtml(title || goal || "交给主 Agent 处理")}"`, `data-goal="${escapeHtml(goal || title || "交给主 Agent 处理")}"`, `data-summary="${escapeHtml(summary || "")}"`, `data-platform-scope="${escapeHtml(platformScope || "single_platform")}"` ]; if (platform) { attrs.push(`data-platform="${escapeHtml(platform)}"`); } if (safeArray(planSteps).length) { attrs.push(`data-plan-steps="${escapeHtml(JSON.stringify(safeArray(planSteps)))}"`); } return attrs.join(" "); } function buildMainAgentLandingAttrs({ runId = "", screen = "", title = "", summary = "" } = {}) { const attrs = []; if (runId) attrs.push(`data-main-agent-run-id="${escapeHtml(runId)}"`); if (screen) attrs.push(`data-main-agent-screen="${escapeHtml(screen)}"`); if (title) attrs.push(`data-main-agent-title="${escapeHtml(title)}"`); if (summary) attrs.push(`data-main-agent-summary="${escapeHtml(summary)}"`); return attrs.join(" "); } function resolveMainAgentLandingScreen(target) { const value = String(target || "").trim(); if (!value) return ""; if (!value.startsWith("goto-")) return value; const routeMap = { "goto-discovery": "discovery", "goto-intake": "intake", "goto-automation": "automation", "goto-playbook": "playbook", "goto-admin-workbench": "admin-workbench", "goto-tracking": "tracking", "goto-production": "production", "goto-strategy": "strategy", "goto-review": "review" }; return routeMap[value] || value.replace(/^goto-/, ""); } function captureMainAgentLandingContext(action, targetScreen) { const runId = String(action?.dataset?.mainAgentRunId || action?.dataset?.runId || "").trim(); const screen = resolveMainAgentLandingScreen(action?.dataset?.mainAgentScreen || targetScreen || ""); const title = String(action?.dataset?.mainAgentTitle || "").trim(); const summary = String(action?.dataset?.mainAgentSummary || "").trim(); if (!screen || (!runId && !title && !summary)) { return; } appState.mainAgentLanding = { screen, runId, title, summary, createdAt: new Date().toISOString() }; } function renderMainAgentLandingQuickActions(screenKey) { const selectedAccount = getSelectedAccountRow(); const discoveryImportAction = selectedAccount ? "direct-import-selected-account" : "open-import-homepage"; const discoveryImportLabel = selectedAccount ? "导入当前对标" : "导入主页"; const actionMap = { intake: [ { action: "create-project", label: "新建项目" }, { action: "open-import-video-link", label: "导入作品" }, { action: "open-upload-video", label: "上传视频" } ], discovery: [ { action: discoveryImportAction, label: discoveryImportLabel }, { action: "direct-analyze-top-videos", label: "高分分析" }, { action: "direct-search-similar", label: "查相似" } ], tracking: [ { action: "refresh-tracking", label: "同步全部" }, { action: "mark-tracking-read", label: "标记已读" }, { action: "goto-discovery", label: "跳到找对标" } ], automation: [ { action: "refresh-data", label: "刷新状态" }, { action: "goto-production", label: "去生产中心" } ], playbook: [ { action: "open-oneliner-profile", label: "配置 OneLiner" }, { action: "direct-create-assistant", label: "新建 Agent" }, { action: "goto-production", label: "去生产中心" } ], production: [ { action: "direct-review-draft", label: "生成复盘" }, { action: "direct-create-ai-video", label: "做 AI 视频" }, { action: "direct-create-real-cut", label: "做实拍剪辑" } ], review: [ { action: "direct-review-draft", label: "生成复盘" }, { action: "goto-production", label: "去生产中心" }, { action: "refresh-data", label: "刷新结果" } ], strategy: [ { action: "open-user-global-policy", label: "编辑全局策略" }, { action: "open-user-platform-policy", label: "编辑当前平台策略" }, { action: "open-oneliner-profile", label: "调整 OneLiner" } ] }; const actions = safeArray(actionMap[screenKey]).slice(0, 3); if (!actions.length) return ""; return `
${actions.map((item) => `${escapeHtml(item.label)}`).join("")}
`; } function renderMainAgentLandingNotice(screenKey) { const landing = appState.mainAgentLanding; if (!landing || landing.screen !== screenKey) return ""; const landingRun = safeArray(appState.onelinerRuns).find((item) => item.id === landing.runId); const resultSections = landingRun?.result?.result_sections && typeof landingRun.result.result_sections === "object" ? landingRun.result.result_sections : {}; const cards = safeArray(resultSections.cards).slice(0, 2); const recommendedAction = landingRun?.result?.recommended_action || null; return `
主 Agent 结果 ${escapeHtml(resultSections.workstream_label || recommendedAction?.label || landing.title || "继续处理")} ${escapeHtml(landing.runId ? "可继续处理" : "已生成提示")} ${escapeHtml(recommendedAction?.label || "继续处理")}
主 Agent 结果 ${escapeHtml(resultSections.workstream_label || "继续处理")}

${escapeHtml(landing.summary || landing.title || "这是主 Agent 刚刚给出的下一步落点。")}

${landing.runId ? `查看结果` : ""} 收起提示

你正在处理主 Agent 的结果

${escapeHtml(landing.summary || landing.title || "这是主 Agent 刚刚给出的下一步落点。")}

${cards.length ? `
${cards.map((card, index) => `

${escapeHtml(card?.title || (index === 0 ? "当前焦点" : "结果摘要"))}

${escapeHtml(card?.body || "主 Agent 已整理出一条可继续推进的建议。")}

`).join("")}
` : ""} ${renderMainAgentLandingQuickActions(screenKey)}
${landing.title ? `${escapeHtml(landing.title)}` : ""} ${resultSections.workstream_label ? `${escapeHtml(resultSections.workstream_label)}` : ""} ${recommendedAction?.label ? `${escapeHtml(recommendedAction.label)}` : ""} ${landing.runId ? `查看结果` : ""} 收起提示
`; } function renderEmptyState(title, description) { return `
${escapeHtml(title)}

${escapeHtml(description)}

`; } function renderPlatformSwitchChips(currentPlatform) { return getPlatformOptions().map((item) => ` ${escapeHtml(getPlatformShortLabel(item.value))} `).join(""); } function getProjectNameById(projectId) { return safeArray(appState.dashboard?.projects).find((project) => project.id === projectId)?.name || projectId || "-"; } function formatSnapshotFieldValue(value) { if (value == null) return "-"; const text = typeof value === "string" ? value : JSON.stringify(value); return brief(text, 120); } function renderSnapshotFieldRows(fields, limit = 8) { return safeArray(fields) .slice(0, limit) .map((field) => `

${escapeHtml(field.field_path || field.path || "field")}

${escapeHtml(formatSnapshotFieldValue(field.field_value_text || field.value || field.summary || ""))}

${field.field_type ? `${escapeHtml(field.field_type)}` : ""}
`).join(""); } function renderDouyinInsightPanel() { const selected = getSelectedAccount(); if (!selected || getAccountPlatform(selected) !== "douyin") { return ""; } const snapshots = safeArray(appState.snapshots); const selectedSnapshot = appState.selectedSnapshotDetail || snapshots.find((item) => item.id === appState.selectedSnapshotId) || null; const creatorFields = appState.creatorFields || null; const analysisReports = safeArray(appState.analysisReports.length ? appState.analysisReports : appState.selectedWorkspace?.recent_reports); const snapshotSummary = selectedSnapshot?.summary || {}; const creatorSummary = creatorFields?.summary || {}; const selectedSnapshotFields = safeArray(selectedSnapshot?.fields); const creatorSnapshotFields = safeArray(creatorFields?.fields); return `

抖音快照详情

快照、创作者字段和分析报告统一在这里看
${escapeHtml(formatNumber(snapshots.length))} 个快照 ${escapeHtml(formatNumber(creatorSnapshotFields.length || creatorFields?.field_count || 0))} 个字段 ${escapeHtml(formatNumber(analysisReports.length))} 条报告 刷新
快照类型 ${escapeHtml(selectedSnapshot?.snapshot_type || "未选中")} ${escapeHtml(selectedSnapshot?.collected_at ? formatDateTime(selectedSnapshot.collected_at) : "等待选择")}
字段数 ${escapeHtml(formatNumber(selectedSnapshot?.field_count || 0))} ${escapeHtml(selectedSnapshot?.source_url ? brief(selectedSnapshot.source_url, 28) : "暂无来源")}
创作者字段 ${escapeHtml(formatNumber(creatorFields?.field_count || 0))} ${escapeHtml(creatorFields?.collected_at ? formatDateTime(creatorFields.collected_at) : "尚未拉取")}
分析报告 ${escapeHtml(formatNumber(analysisReports.length))} ${escapeHtml(analysisReports[0]?.created_at ? formatDateTime(analysisReports[0].created_at) : "暂无报告")}

快照列表

点击任意快照可以切换右侧详情,便于比对公开页和 creator center 的变化。

${snapshots.map((snapshot) => `

${escapeHtml(snapshot.snapshot_type || "snapshot")} · ${escapeHtml(formatDateTime(snapshot.collected_at))}

${escapeHtml(brief(JSON.stringify(snapshot.summary || {}), 96))}

${escapeHtml(formatNumber(snapshot.field_count || 0))} 字段 查看详情
`).join("") || `

还没有快照

同步账号后,这里会自动出现 public profile 和 creator center 快照。

`}

当前快照详情

${escapeHtml(selectedSnapshot ? brief(JSON.stringify(snapshotSummary), 120) : "先从左侧选择一个快照")}

${selectedSnapshot?.source_url ? `打开来源` : ""} ${selectedSnapshot?.snapshot_type ? `${escapeHtml(selectedSnapshot.snapshot_type)}` : ""}
${selectedSnapshotFields.length ? renderSnapshotFieldRows(selectedSnapshotFields, 6) : `

暂无字段

选中快照后会显示原始字段明细。

`}

Creator Fields

${escapeHtml(creatorFields ? brief(JSON.stringify(creatorSummary), 120) : "尚未拉取 creator center 字段")}

${creatorFields?.source_url ? `打开 creator center` : ""} ${creatorFields?.snapshot_type ? `${escapeHtml(creatorFields.snapshot_type)}` : ""} ${creatorFields?.field_count != null ? `${escapeHtml(formatNumber(creatorFields.field_count))} 字段` : ""}
${creatorSnapshotFields.length ? renderSnapshotFieldRows(creatorSnapshotFields, 6) : `

还没有 creator 字段

等 creator center 快照同步后,这里会展示字段明细。

`}

分析报告

分析报告来自 /analysis-reports,可直接对照结论和建议。

${analysisReports.map((report) => { const suggestion = safeArray(report.suggestions)[0] || null; const summary = suggestion?.parsed_json?.executive_summary || suggestion?.suggestion_text || report.focus_text || "暂无结论"; return `

${escapeHtml(brief(report.focus_text || "分析报告", 34))}

${escapeHtml(brief(summary, 120))}

${report.created_at ? `${escapeHtml(formatDateTime(report.created_at))}` : ""} ${suggestion?.model_label ? `${escapeHtml(suggestion.model_label)}` : ""}
`; }).join("") || `

还没有分析报告

对当前账号跑一次分析后,这里会自动出现结论和建议。

`}
`; } async function openDouyinSnapshotDetailAction(snapshotId) { const selected = getSelectedAccount(); if (!selected || getAccountPlatform(selected) !== "douyin") { return; } if (!snapshotId) { return; } setBusy(true, "正在加载快照详情..."); try { const detail = await storyforgeFetch(`/v2/douyin/accounts/${encodeURIComponent(selected.id)}/snapshots/${encodeURIComponent(snapshotId)}`); appState.selectedSnapshotId = snapshotId; appState.selectedSnapshotDetail = detail; rememberAction("快照已切换", `已打开 ${detail.snapshot_type || "snapshot"} 的完整详情。`, "green", detail); renderAll(); } finally { setBusy(false, ""); } } function renderLiveRecorderManagementPanel() { const sources = safeArray(appState.liveRecorderSources); const status = appState.liveRecorderStatus || {}; const health = getIntegrationDetail("live_recorder"); const liveRecorderHealth = appState.liveRecorderHealth || {}; const files = safeArray(appState.liveRecorderFiles); const activeItems = safeArray(status.active_recordings); const runtimeBits = [ health.available ? health.reachable ? "在线" : (health.configured ? "不可达" : "未配置") : "未拉取", status.running ? `运行中 pid ${status.pid || "-"}` : "未运行", `活动录制 ${formatNumber(activeItems.length)}`, `最近文件 ${formatNumber(files.length)}` ]; const directHealthText = liveRecorderHealth ? (liveRecorderHealth.ok || String(liveRecorderHealth.status || "").toLowerCase() === "ok" ? "HTTP 健康:ok" : `HTTP 健康:${liveRecorderHealth.status || liveRecorderHealth.message || "异常"}`) : "HTTP 健康:未拉取"; return `

Live Recorder 维护面板

编辑录制源、查看健康状态、导入配置和删除源都在这里
${escapeHtml(health.reachable ? "健康" : health.configured ? "待检查" : "未配置")} ${escapeHtml(status.running ? "运行中" : "已停止")} 刷新 新增录制源 导入 URL 配置
${runtimeBits.map((item, index) => `
${escapeHtml(["健康", "运行", "活动", "文件"][index])} ${escapeHtml(item)} ${escapeHtml(index === 0 ? (health.url || health.baseUrl || "未拉取健康数据") : index === 1 ? (status.started_at ? formatDateTime(status.started_at) : "暂无启动时间") : index === 2 ? "当前租户录制状态" : "当前租户录像索引")}
`).join("")}

直连健康

${escapeHtml(directHealthText)}

${liveRecorderHealth?.base_url ? `${escapeHtml(brief(liveRecorderHealth.base_url, 32))}` : ""} ${liveRecorderHealth?.url ? `${escapeHtml(brief(liveRecorderHealth.url, 32))}` : ""} ${liveRecorderHealth?.pid ? `${escapeHtml(`pid ${liveRecorderHealth.pid}`)}` : ""}

录制源列表

默认按当前租户筛选,编辑时可改项目、Agent、标题、清晰度和启停状态。

${sources.map((source) => `

${escapeHtml(source.title || source.remote_name || source.source_url || "录制源")}

${escapeHtml(source.source_url || "暂无源链接")}

${escapeHtml(platformLabel(source.platform || "kuaishou"))} ${escapeHtml(source.quality || "原画")} ${escapeHtml(source.enabled ? "启用" : "停用")} ${escapeHtml(getProjectNameById(source.project_id || ""))} ${source.recording_count ? `${escapeHtml(formatNumber(source.recording_count))} 个活动录制` : ""}
编辑 ${escapeHtml(source.enabled ? "停用" : "启用")} 删除
`).join("") || `

还没有录制源

先导入或新增一个直播源,后端会自动同步到租户视图。

`}

健康检查与运行状态

${escapeHtml([ health.available ? `健康接口:${health.reachable ? "在线" : "不可达"}` : "还没有拉取健康接口", status.url_info?.service_url ? `服务地址:${status.url_info.service_url}` : "", activeItems.length ? `活动录制:${activeItems.length}` : "当前没有活动录制" ].filter(Boolean).join(" · "))}

${status.pid ? `PID ${escapeHtml(status.pid)}` : ""} ${status.last_exit_code != null ? `${escapeHtml(`退出码 ${status.last_exit_code}`)}` : ""} ${status.url_info?.base_url ? `打开服务` : ""}

最近文件

文件沿用当前租户视图,支持直接打开查看。

${files.slice(0, 5).map((file) => `

${escapeHtml(file.title || file.name || file.relative_path || "录像文件")}

${escapeHtml(file.relative_path || file.name || file.content_url || "-")}

${file.mtime ? `${escapeHtml(formatDateTime(file.mtime))}` : ""} ${file.id ? `打开文件` : ""}
`).join("") || `

还没有文件

开始录制后,最新文件会出现在这里。

`}
`; } function renderAdminFixRunsPanel() { if (!isSuperAdmin()) return ""; const overview = appState.adminOpsOverview || {}; const items = safeArray(appState.adminFixRuns.length ? appState.adminFixRuns : overview.recent_fix_runs); if (!items.length) { return `

修复计划列表

还没有拉到修复计划

暂无修复计划

生成修复计划后,这里会展示完整的 audit 列表。

`; } return `

修复计划列表

完整展示最近的 fix runs,并支持直接审计
${escapeHtml(formatNumber(items.length))} 条 ${escapeHtml(formatNumber(items.filter((item) => item.audit_status === "approved").length))} 已通过 ${escapeHtml(formatNumber(items.filter((item) => item.audit_status === "watching").length))} 观察中 刷新
${items.map((item) => { const plan = item.plan || {}; const verification = item.verification || {}; return `

${escapeHtml(plan.summary || item.id || "修复计划")}

${escapeHtml(brief(safeArray(plan.steps).join(";") || verification.summary || "暂无修复步骤", 140))}

${escapeHtml(item.plan_scope || "plan")} ${escapeHtml(item.audit_status || "pending")} ${item.status ? `${escapeHtml(item.status)}` : ""} ${item.incident_id ? `${escapeHtml(brief(item.incident_id, 12))}` : ""} ${item.updated_at ? `${escapeHtml(formatDateTime(item.updated_at))}` : ""} 查看详情 审计放行
`; }).join("")}
`; } function renderAdminWorkbenchScreen() { if (!isSuperAdmin()) { return screenShell( "管理员配置台", "仅超级管理员可见。", "", renderEmptyState("无权限", "请使用超级管理员账号访问管理员配置台。") ); } const tabs = [ { value: "integrations", label: "依赖健康" }, { value: "storage", label: "存储状态" }, { value: "agents", label: "Agent 治理" }, { value: "governance_audit", label: "覆盖与审计" }, { value: "ops", label: "运维审计" } ]; const activeTab = getActiveDetailTab("adminWorkbenchTab", tabs); return screenShell( "管理员配置台", "系统级依赖、存储、平台 Agent 与运维治理。", "", `

系统治理工作区

按系统依赖、存储、Agent 治理和运维审计分区查看,不再整页堆叠。
${renderDetailTabs("adminWorkbenchTab", tabs)} ${activeTab === "integrations" ? renderIntegrationOverviewPanel({ showActions: false }) : activeTab === "storage" ? renderStorageStatusPanel() : activeTab === "agents" ? `${renderAdminGovernanceSummaryPanel()}${renderPlatformAgentPanel()}
${renderOneLinerActionRegistryPanel()}
` : activeTab === "governance_audit" ? renderAdminGovernanceAuditPanel() : renderAdminOpsPanel()}
` ); } function renderDashboardScreen() { if (!appState.session) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("项目总台", "会话签发成功后,这里会自动切成真实项目总台。"); } return screenShell( "项目总台", "先自动连接工作区,再加载项目、对标、Agent 和生产状态。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("还没有连接 StoryForge", "自动连接成功后,这里会替换成真实项目总台。") ); } if (!appState.dashboard) { return screenShell( "项目总台", appState.me?.approval_status === "pending" ? "当前账号还在等待审批。" : "正在加载项目数据。", `${button("刷新", "refresh-data")} ${button("切换连接", "open-auth")}`, renderEmptyState("工作区暂未就绪", appState.message || "如果账号未审批通过,当前页会先停在待审批状态。") ); } const dashboardHomeRenderer = window.StoryForgeDashboardHome; const homeModel = buildDashboardHomeModel(); return screenShell( "项目总台", "先做最能推进当前项目的一步,再按需看概览。", `${button("新建项目", "create-project")} ${button("导入主页", "open-import-homepage")} ${button("创建 Agent", "direct-create-assistant", "primary")}`, dashboardHomeRenderer?.renderDashboardHome ? `
${dashboardHomeRenderer.renderDashboardHome(homeModel, { escapeHtml })}
` : renderEmptyState("首页模块未加载", "请刷新页面后重试。") ); } function renderProjectsScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("我的项目", "工作区就绪后,这里会自动加载真实项目和导入队列。"); } return screenShell("我的项目", "先完成工作区自动连接,再加载项目。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("项目未加载", "自动连接成功后,这里会显示真实项目和导入队列。")); } const projects = safeArray(appState.dashboard.projects); const selectedProject = getSelectedProject(); const intakeHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "intake", sourceActionKey: "project-intake-handoff", intentKey: "project_intake", title: selectedProject ? `继续推进项目 ${selectedProject.name}` : "继续梳理项目工作区", goal: selectedProject ? `围绕项目 ${selectedProject.name} 生成下一步推进计划` : "根据当前项目列表和导入情况,生成下一步项目推进计划", summary: selectedProject ? "主 Agent 会结合当前项目状态、账号和任务规模,整理下一步动作。" : "主 Agent 会先看项目列表和内容导入情况,再整理下一步动作。", platform: getPreferredPlatform(), platformScope: "single_platform", planSteps: [ "读取当前项目、账号和任务状态", "检查是否需要补导入或切换项目", "生成下一步项目推进计划" ] }); return screenShell( "我的项目", "先建项目,再决定是否绑定自己的账号。", `${button("新建项目", "create-project", "primary")} ${button("导入作品", "open-import-video-link")} ${button("导入文本", "open-import-text")} ${button("上传视频", "open-upload-video")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: intakeHandoffAttrs })}`, ` ${renderMainAgentLandingNotice("intake")}

当前项目

${escapeHtml(selectedProject?.name || "还没有项目")} · ${escapeHtml(selectedProject?.description || "创建后即可承接对标、Agent 和生产任务。")}

${projects.map((project) => `${escapeHtml(project.name)}`).join("") || `暂无项目`}
当前项目任务 ${escapeHtml(selectedProject?.name || "未选项目")}

${escapeHtml( selectedProject ? `先围绕 ${selectedProject.name} 决定是继续导入内容、切换项目,还是直接交给主 Agent 推进。` : "先创建或切换到一个项目,再继续绑定账号和推进生产。" )}

${actionTag(selectedProject ? "导入作品" : "新建项目", selectedProject ? "open-import-video-link" : "create-project")} ${actionTag("切换项目", "open-dashboard-project-switcher")} ${actionTag("交给主 Agent", "handoff-to-main-agent", intakeHandoffAttrs)}
当前项目 ${escapeHtml(selectedProject?.name || "未选项目")} 项目 ${escapeHtml(formatNumber(projects.length))} 内容源 ${escapeHtml(formatNumber(appState.contentSources.length))} ${escapeHtml(formatNumber(safeArray(appState.dashboard.recent_jobs).length))} 个任务

项目状态

真实项目列表
${projects.map((project) => { const stats = getProjectStats(project.id); return `
${escapeHtml(project.name)}
${escapeHtml(project.description || "未填写说明")}
知识库 ${escapeHtml(formatNumber(stats.knowledgeBases.length))} Agent ${escapeHtml(formatNumber(stats.assistants.length))} 任务 ${escapeHtml(formatNumber(stats.jobs.length))}
`; }).join("") || `
当前还没有项目。
`}

最近导入队列

按内容源展示
${safeArray(appState.contentSources).slice(0, 6).map((source) => `

${escapeHtml(source.title || source.handle || source.source_url || source.id)}

${escapeHtml(source.platform || source.source_kind || "内容源")} · ${escapeHtml(source.source_url || source.local_path || "暂无链接")}

${source.project_id === appState.selectedProjectId ? "当前项目" : "其他项目"} ${escapeHtml(source.source_kind || "-")}
`).join("") || `

还没有导入内容

先去“找对标”导入主页、作品或本地视频。

`}
` ); } function getActiveDetailTab(stateKey, tabs) { const fallback = tabs[0]?.value || ""; const values = tabs.map((item) => item.value); const active = values.includes(appState[stateKey]) ? appState[stateKey] : fallback; appState[stateKey] = active; return active; } function renderDetailTabs(stateKey, tabs) { const active = getActiveDetailTab(stateKey, tabs); return `
${tabs.map((tab) => ` `).join("")}
`; } function focusDiscoveryDetailTab(tabValue) { appState.discoveryDetailTab = tabValue; appState.screen = "discovery"; renderAll(); window.requestAnimationFrame(() => { document.getElementById("selected-account-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusDiscoveryInsights() { appState.discoveryDetailTab = "snapshots"; appState.screen = "discovery"; renderAll(); window.requestAnimationFrame(() => { (document.getElementById("douyin-insight-anchor") || document.getElementById("selected-account-anchor")) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusDiscoveryTopVideoInsights() { appState.discoveryDetailTab = "overview"; appState.screen = "discovery"; renderAll(); window.requestAnimationFrame(() => { (document.getElementById("top-video-batch-anchor") || document.getElementById("selected-account-anchor")) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusDiscoveryRelations() { appState.discoveryDetailTab = "relations"; appState.screen = "discovery"; renderAll(); window.requestAnimationFrame(() => { (document.getElementById("discovery-relations-anchor") || document.getElementById("selected-account-anchor")) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusTrackingWorkspace() { setScreen("tracking"); renderAll(); window.requestAnimationFrame(() => { document.querySelector('[data-screen="tracking"] .mobile-flow-focus-card, [data-screen="tracking"] .panel')?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusDashboardWorkspace(anchorId = "dashboard-workspace-anchor") { setScreen("dashboard"); renderAll(); window.requestAnimationFrame(() => { (document.getElementById(anchorId) || document.querySelector('[data-screen="dashboard"] .panel')) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusCreditsWorkspace(anchorId = "credits-quota-anchor") { setScreen("credits"); renderAll(); window.requestAnimationFrame(() => { (document.getElementById(anchorId) || document.querySelector('[data-screen="credits"] .mobile-flow-focus-card, [data-screen="credits"] .panel')) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusProductionDetailTab(tabValue) { appState.productionDetailTab = tabValue; setScreen("production"); renderAll(); window.requestAnimationFrame(() => { document.querySelector('[data-screen="production"] .mobile-flow-focus-card, [data-screen="production"] .panel')?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusRecentGeneratedCopy() { appState.playbookDetailTab = "workspace"; setScreen("playbook"); renderAll(); window.requestAnimationFrame(() => { document.getElementById("recent-generated-copy-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusPlaybookWorkspace(assistantId = "") { appState.playbookDetailTab = "workspace"; if (assistantId) appState.selectedAssistantId = assistantId; setScreen("playbook"); renderAll(); window.requestAnimationFrame(() => { const selector = assistantId ? `[data-assistant-id="${String(assistantId).replaceAll('"', '\\"')}"]` : "#current-agent-anchor"; (document.querySelector(selector) || document.getElementById("current-agent-anchor")) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusLiveRecorderMaintenance() { appState.productionDetailTab = "recorder"; setScreen("production"); renderAll(); window.requestAnimationFrame(() => { document.getElementById("live-recorder-maintenance-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusReviewWorkspace(reviewId = "") { appState.reviewFocusId = reviewId || ""; setScreen("review"); renderAll(); window.requestAnimationFrame(() => { const selector = reviewId ? `[data-review-id="${String(reviewId).replaceAll('"', '\\"')}"]` : "#review-workspace-anchor"; (document.querySelector(selector) || document.getElementById("review-workspace-anchor")) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function focusAdminOpsWorkspace(anchorId = "admin-ops-anchor") { appState.adminWorkbenchTab = "ops"; setScreen("admin-workbench"); renderAll(); window.requestAnimationFrame(() => { (document.getElementById(anchorId) || document.querySelector('[data-screen="admin-workbench"] .mobile-flow-focus-card, [data-screen="admin-workbench"] .panel')) ?.scrollIntoView({ behavior: "smooth", block: "start" }); }); } function renderDiscoveryOverviewSection({ selected, selectedProject, importedSources, tracked, topVideos, reports, latestVideos, currentPlatformLabel, topVideoBatchResult }) { return `

接入当前项目

把当前对标导入到项目,并绑定 Agent 做持续同步
${escapeHtml(importedSources.length ? "已接入" : "未接入")}
${selected ? `

${escapeHtml(selectedProject?.name || "未选项目")}

${escapeHtml(importedSources.length ? `当前项目已接入 ${formatNumber(importedSources.length)} 个内容源,可继续同步或换 Agent。` : "当前项目还没有接入这个对标账号,可直接导入主页并绑定 Agent。")}

${escapeHtml(selectedProject?.name || "未选项目")} ${escapeHtml(getSelectedAssistant()?.name || "未选 Agent")} ${actionTag(importedSources.length ? "继续同步" : "导入当前对标", "direct-import-selected-account")} ${tracked ? `已在跟踪` : actionTag("加入跟踪", "direct-track-selected-account")}
` : `

还没有选中账号

先从上方列表选一个对标账号,再决定是否导入到当前项目。

`}

账号画像

  • ${escapeHtml(selected?.signature || "暂无签名")}
  • ${escapeHtml("平台:" + currentPlatformLabel)}
  • ${escapeHtml("标签:" + (safeArray(selected?.tags).slice(0, 4).join(" / ") || "暂无标签"))}
  • ${escapeHtml("同步状态:" + (selected?.sync_status || "-"))}

高分作品

    ${topVideos.map((video) => `
  • ${escapeHtml(describeVideo(video))}
  • `).join("") || "
  • 暂无高分作品
  • "}

最近报告

    ${reports.slice(0, 3).map((report) => { const suggestion = safeArray(report.suggestions)[0]; const summary = suggestion?.parsed_json?.executive_summary || suggestion?.suggestion_text || report.focus_text || "暂无结论"; return `
  • ${escapeHtml(brief(summary, 48))}
  • `; }).join("") || "
  • 暂无分析报告
  • "}
${topVideoBatchResult ? `

最近高分拆解

这批结果已经回流到当前账号页,不会只停留在顶部提醒里。
${escapeHtml(formatNumber(topVideoBatchResult.analyzed_count || safeArray(topVideoBatchResult.items).length))} 条
${safeArray(topVideoBatchResult.items).slice(0, 3).map((item) => `

${escapeHtml(item.video_title || "高分作品")}

${escapeHtml(item.summary_text || "已完成拆解。")}

得分 ${escapeHtml(formatNumber(item.performance_score || 0))} ${safeArray(item.parsed_json?.borrow_points).slice(0, 2).map((point) => `${escapeHtml(point)}`).join("")}
`).join("")}
` : ""}

最新作品

优先看近期更新与窗口期题材
${escapeHtml(formatNumber(latestVideos.length))} 条
${latestVideos.map((video) => `

${escapeHtml(describeVideo(video))}

发布时间 ${escapeHtml(formatDateTime(video.published_at))} · 播放 ${escapeHtml(formatNumber(video.stats?.play))} · 点赞 ${escapeHtml(formatNumber(video.stats?.like))}

${escapeHtml(video.content_type || "video")} 得分 ${escapeHtml(formatNumber(video.score?.performance_score || 0))} ${getVideoLink(video) ? `打开原作品` : ""}
`).join("") || `

还没有最近作品

当前账号只同步了基础信息,还没拉到完整作品列表。

`}
`; } function renderDiscoveryRelationsSection(linkedAccounts, similarCandidates) { return `

已绑关系

当前账号已经保存的对标关系
${escapeHtml(formatNumber(linkedAccounts.length))} 个
${linkedAccounts.map((link) => `

${escapeHtml(link.target_nickname || link.target_profile_url || "未命名对标")}

${escapeHtml(link.note || link.target_profile_url || "已保存对标关系")}

${escapeHtml(link.relation_type || "benchmark")} ${link.target_account_id ? `看详情` : ""} ${link.target_profile_url ? `打开主页` : ""}
`).join("") || `

暂无已保存对标

当前账号还没有保存过对标关系。

`}

最近相似候选

由 Agent 辅助生成
${escapeHtml(formatNumber(similarCandidates.length))} 个
${similarCandidates.map((candidate, index) => `

${escapeHtml(candidate.candidate_nickname || candidate.candidate_profile_url || "候选账号")}

${escapeHtml(brief(candidate.rationale_text || "暂无理由", 96))}

启发分 ${escapeHtml(formatNumber(candidate.agent_score || candidate.heuristic_score || 0))} ${candidate.candidate_account_id ? `看详情` : ""} ${isCandidateLinked(candidate, linkedAccounts) || candidate.saved ? `已保存` : `存对标`} ${candidate.candidate_profile_url ? `打开主页` : ""}
`).join("") || `

还没有相似候选

先点“查相似”,这里会展示最近一轮结果。

`}
`; } function renderDiscoveryScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("找对标", "工作区就绪后,这里会自动显示当前平台的账号列表和详情。"); } return screenShell("找对标", "完成工作区自动连接后才能加载真实对标账号。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("对标库未加载", "自动连接成功后,这里会显示当前平台的账号列表和详情。")); } const query = appState.discoveryQuery.toLowerCase(); const currentPlatform = getCurrentPlatformValue(); const currentPlatformLabel = getPlatformShortLabel(currentPlatform); const accounts = getAccountsForPlatform(currentPlatform).filter((account) => { if (!query) return true; return [getAccountName(account), account.signature, getAccountProfileUrl(account), getAccountHandle(account), ...safeArray(account.tags), ...safeArray(account.keywords)] .join(" ") .toLowerCase() .includes(query); }); const selected = getSelectedAccount(); const selectedPlatform = getAccountPlatform(selected); const effectivePlatform = selectedPlatform || currentPlatform; const reports = safeArray(appState.analysisReports.length ? appState.analysisReports : appState.selectedWorkspace?.recent_reports); const linkedAccounts = safeArray(appState.selectedWorkspace?.linked_accounts); const videos = safeArray(appState.selectedVideos?.items); const fallbackVideos = safeArray(selected?.video_summary?.videos); const effectiveVideos = videos.length ? videos : fallbackVideos; const topVideos = getHighScoreVideos(3); const latestVideos = getLatestVideos(2); const similarCandidates = safeArray(appState.lastSimilaritySearch?.candidates).slice(0, 5); const saveBenchmarkActionName = similarCandidates.length ? "direct-save-benchmark-link" : "open-benchmark-link"; const saveBenchmarkActionLabel = similarCandidates.length ? "直接存对标" : "存对标"; const selectedProject = getSelectedProject(); const importedSources = getCurrentProjectSourcesForAccount(selected, selectedProject?.id || ""); const tracked = selected?.id ? isTrackedAccount(selected.id) : false; const topVideoBatchResult = getSelectedTopVideoAnalysisResult(); const isMobileUi = isMobileViewport(); const discoveryHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "discovery", sourceActionKey: "discovery-handoff", intentKey: "benchmark_discovery", title: selected ? `继续处理对标 ${getAccountName(selected)}` : "继续推进对标工作", goal: selected ? `围绕 ${getAccountName(selected)} 输出一版下一步对标计划` : `根据当前${currentPlatformLabel}账号列表,生成下一步对标推进计划`, summary: selected ? "结合当前选中的对标账号、分析报告和相似关系,生成下一步动作。" : "结合当前账号池和已导入内容,整理一版可执行的对标计划。", platform: effectivePlatform || currentPlatform, platformScope: "single_platform", planSteps: [ "读取当前对标账号与已导入内容", "检查分析报告与相似关系", "生成下一步对标推进计划" ] }); const detailTabs = [ { value: "overview", label: "账号概览" }, { value: "snapshots", label: "快照 / 字段 / 报告" }, { value: "relations", label: "相似对标 / 已绑关系" } ]; const activeTab = getActiveDetailTab("discoveryDetailTab", detailTabs); const selectedSummaryHtml = `
${escapeHtml(initials(getAccountName(selected) || "SF"))}

${escapeHtml(getAccountName(selected) || "还没有选中账号")}

${escapeHtml(getAccountProfileUrl(selected) || selected?.signature || "先从上方列表选一个账号,这里会展示当前对象。")}

作品数${escapeHtml(formatNumber(selected?.video_summary?.count))}
高分作品${escapeHtml(formatNumber(topVideos.length))}
报告数${escapeHtml(formatNumber(reports.length))}
已绑对标${escapeHtml(formatNumber(linkedAccounts.length))}
`; let detailBodyHtml = ""; if (activeTab === "overview") { detailBodyHtml = renderDiscoveryOverviewSection({ selected, selectedProject, importedSources, tracked, topVideos, reports, latestVideos, currentPlatformLabel, topVideoBatchResult }); } else if (activeTab === "snapshots") { detailBodyHtml = renderDouyinInsightPanel(); } else { detailBodyHtml = renderDiscoveryRelationsSection(linkedAccounts, similarCandidates); } const discoveryActionsHtml = isMobileUi ? `${button("导入主页", "open-import-homepage")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: discoveryHandoffAttrs })} ${button(saveBenchmarkActionLabel, saveBenchmarkActionName, "primary")}` : `${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "direct-import-selected-account", "secondary")} ${button(tracked ? "更新跟踪" : "加入跟踪", "direct-track-selected-account", "secondary")} ${button("账号分析", "direct-analyze-selected-account", "secondary")} ${button("高分分析", "direct-analyze-top-videos", "secondary")} ${button("查相似", "direct-search-similar", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: discoveryHandoffAttrs })} ${button(saveBenchmarkActionLabel, saveBenchmarkActionName, "primary")}`; return screenShell( "找对标", `这里已经接入真实${currentPlatformLabel}账号列表和单账号详情。`, discoveryActionsHtml, ` ${renderMainAgentLandingNotice("discovery")}
平台:${escapeHtml(currentPlatformLabel)}
账号数:${escapeHtml(formatNumber(accounts.length))}
报告:${escapeHtml(formatNumber(reports.length))}
作品:${escapeHtml(formatNumber(effectiveVideos.length))}
${renderPlatformSwitchChips(currentPlatform)}
下一步先做 ${escapeHtml(getAccountName(selected) || "未选中")}

${escapeHtml(selected ? `先围绕 ${getAccountName(selected)} 做导入、分析和相似扩展。` : "先从账号池里选一个对象,再继续导入和分析。")}

${actionTag("导入当前对标", "direct-import-selected-account")} ${actionTag("账号分析", "direct-analyze-selected-account")} ${actionTag("查相似", "direct-search-similar")}
当前对标 ${escapeHtml(getAccountName(selected) || "未选中")} ${escapeHtml(importedSources.length ? `已接入 ${importedSources.length}` : "未接入项目")} ${escapeHtml(tracked ? "已加入跟踪" : "未加入跟踪")} ${escapeHtml(reports.length ? `报告 ${reports.length}` : "暂无报告")}
${accounts.map((account) => ` `).join("") || ``}

当前选中对标

先看核心信息,再按分类切换深层内容。
${escapeHtml(getAccountName(selected) || "未选中")}
${isMobileUi ? "" : `
下一步先做 ${escapeHtml(detailTabs.find((tab) => tab.value === activeTab)?.label || "账号概览")}

${escapeHtml(selected ? `先围绕 ${getAccountName(selected)} 做导入、分析和相似扩展。` : "先从账号池里选一个对象,再继续导入和分析。")}

${actionTag("导入当前对标", "direct-import-selected-account")} ${actionTag("账号分析", "direct-analyze-selected-account")} ${actionTag("查相似", "direct-search-similar")}
当前对标 ${escapeHtml(getAccountName(selected) || "未选中")} ${escapeHtml(importedSources.length ? `已接入 ${importedSources.length}` : "未接入项目")} ${escapeHtml(tracked ? "已加入跟踪" : "未加入跟踪")} ${escapeHtml(reports.length ? `报告 ${reports.length}` : "暂无报告")}
`} ${selectedSummaryHtml} ${renderDetailTabs("discoveryDetailTab", detailTabs)} ${detailBodyHtml}
` ); } function renderTrackingScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("跟踪账号", "工作区就绪后,这里会自动加载真实跟踪对象和日报。"); } return screenShell("跟踪账号", "完成工作区自动连接后才能生成真实日报。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("日报未加载", "当前还没有可用的对标账号数据。")); } const currentPlatform = getCurrentPlatformValue(); const trackedAccounts = getTrackingAccounts().filter((item) => item.platform === currentPlatform); const digestItems = getTrackingDigestItems(12, { platform: currentPlatform }); const platformCursor = getTrackingCursorForPlatform(currentPlatform) || appState.lastSeenAt; const cursorLabel = platformCursor ? formatDateTime(platformCursor) : "尚未记录"; const trackingNotice = getCurrentTrackingRefreshNotice(currentPlatform); const trackingHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "tracking", sourceActionKey: "tracking-handoff", intentKey: "tracking_digest", title: `继续处理${getPlatformShortLabel(currentPlatform)}跟踪日报`, goal: `基于当前${getPlatformShortLabel(currentPlatform)}跟踪账号和日报,生成下一步跟进计划`, summary: "主 Agent 会结合已跟踪账号、日报窗口和更新摘要,给出下一步动作。", platform: currentPlatform, platformScope: "single_platform", planSteps: [ "读取当前平台跟踪账号和最新日报", "识别值得继续跟进的账号或内容", "生成一版跟踪推进计划" ] }); return screenShell( "跟踪账号", `这里已经接上真实${getPlatformShortLabel(currentPlatform)}跟踪对象和按上次打开后的更新日报。`, `${button("同步全部", "refresh-tracking")} ${button("标记已读", "mark-tracking-read")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: trackingHandoffAttrs })} ${button("跳到找对标", "goto-discovery", "primary")}`, ` ${renderMainAgentLandingNotice("tracking")}

日报逻辑

按上次打开后汇总。上次打开距今 ${escapeHtml(daysSince(platformCursor))} 天,本次优先展示有更新且值得借鉴的内容。

${renderPlatformSwitchChips(currentPlatform)} 上次已读 ${escapeHtml(cursorLabel)}
${trackingNotice ? `

${escapeHtml(trackingNotice.title || "后台同步状态")}

${escapeHtml(trackingNotice.summary || "最近一次跟踪同步已经进入后台执行。")}

${escapeHtml(trackingNotice.mode === "single" ? "单账号" : "批量同步")} ${trackingNotice.items?.[0]?.sync_job_id ? `看任务详情` : ""} ${actionTag("去生产中心", "goto-production")}
` : ""}
当前跟踪任务 ${escapeHtml(getPlatformShortLabel(currentPlatform))}

${escapeHtml( digestItems.length ? `先看最近 ${Math.min(digestItems.length, 12)} 条更新日报,再决定同步重点账号还是回到找对标。` : trackedAccounts.length ? "先同步重点跟踪账号,等新作品出现后再回来看日报。" : "先去找对标把值得持续观察的账号加入跟踪。" )}

${actionTag(digestItems.length ? "标记已读" : "同步全部", digestItems.length ? "mark-tracking-read" : "refresh-tracking")} ${actionTag("跳到找对标", "goto-discovery")} ${actionTag("交给主 Agent", "handoff-to-main-agent", trackingHandoffAttrs)}
跟踪 ${escapeHtml(formatNumber(trackedAccounts.length))} 日报 ${escapeHtml(formatNumber(digestItems.length))} ${escapeHtml(daysSince(platformCursor))} 天窗口 ${escapeHtml(getPlatformShortLabel(currentPlatform))}

跟踪列表

真实跟踪对象与绑定 Agent
${escapeHtml(formatNumber(trackedAccounts.length))} 个
${trackedAccounts.map((item) => `

${escapeHtml(item.account?.nickname || "未命名账号")}

最近作品 ${escapeHtml(formatNumber(item.account?.video_summary?.count))} 条 · 平均播放 ${escapeHtml(formatNumber(item.account?.video_summary?.avg_play))}

已跟踪 ${escapeHtml(item.assistant_name || "未绑 Agent")} ${actionTag("立即同步", "refresh-tracked-account", `data-tracked-account-id="${escapeHtml(item.tracked_account_id)}"`)} ${actionTag("看详情", "select-account", `data-account-id="${escapeHtml(item.tracked_account_id)}"`)}
`).join("") || `

暂无跟踪账号

先去找对标把重点账号加入跟踪。

`}

更新日报

优先看最近更新的作品摘要
${escapeHtml(formatNumber(digestItems.length))} 条
${digestItems.map((item) => `

${escapeHtml(item.account?.nickname || "账号")} · ${escapeHtml(item.video?.title || item.video?.description || "最新作品")}

${escapeHtml(item.summary || `发布时间 ${formatDateTime(item.video?.published_at)},建议继续判断借鉴点。`)}

${escapeHtml(getPlatformShortLabel(item.platform || currentPlatform))} ${escapeHtml(item.is_high_value ? "高价值" : "可学习")} ${item.assistant_name ? `${escapeHtml(item.assistant_name)}` : ""} ${item.video?.share_url ? `打开作品` : ""}
${safeArray(item.borrowing_points).length ? `
${safeArray(item.borrowing_points).slice(0, 3).map((point) => `${escapeHtml(point)}`).join("")}
` : ""}
`).join("") || `

暂无日报

先把账号加入跟踪,并等待新作品更新。

`}
` ); } function renderAutomationScreen() { const jobs = safeArray(appState.dashboard?.recent_jobs); const analysisJobs = jobs.filter((item) => item.line_type === "analysis").length; const aiVideoJobs = jobs.filter((item) => item.line_type === "ai_video").length; const realCutJobs = jobs.filter((item) => item.line_type === "real_cut").length; const overview = getIntegrationOverview(); const tabs = [ { value: "health", label: "依赖健康" }, { value: "guards", label: "动作防呆" } ]; const activeTab = getActiveDetailTab("automationDetailTab", tabs); const automationHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "automation", sourceActionKey: "automation-main-agent-handoff", intentKey: "ops_admin", title: "继续检查自动流程", goal: "继续检查自动流程", summary: "让主 Agent 结合依赖健康和动作防呆状态,给出下一步处理建议。", platform: getPreferredPlatform(), planSteps: ["读取当前依赖健康", "检查动作防呆和拦截状态", "生成下一步处理建议"] }); return screenShell( "自动流程", "自动同步、日报生成和失败补跑先统一看这里。", `${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: automationHandoffAttrs })} ${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去生产", "goto-production", "primary")}`, ` ${renderMainAgentLandingNotice("automation")}

自动流程

当前按真实任务量和依赖健康状态给出看板,自动流程受阻时会直接在这里拦住动作。

分析任务${escapeHtml(formatNumber(analysisJobs))}
AI 视频${escapeHtml(formatNumber(aiVideoJobs))}
实拍剪辑${escapeHtml(formatNumber(realCutJobs))}
内容源${escapeHtml(formatNumber(appState.contentSources.length))}

自动链路工作区

普通用户只看健康状态和动作是否可执行,系统治理移到管理员配置台。
${escapeHtml(overview.headline)}
当前自动流程任务 ${escapeHtml(tabs.find((tab) => tab.value === activeTab)?.label || "依赖健康")}

${escapeHtml( activeTab === "guards" ? "先确认 AI 视频、实拍剪辑这类动作是否被拦截,再决定回到生产还是交给主 Agent。" : "先看当前依赖健康,再决定哪些动作可以继续自动推进。" )}

${activeTab === "guards" ? `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${actionTag("交给主 Agent", "handoff-to-main-agent", automationHandoffAttrs)}` : `${actionTag("刷新", "refresh-data")} ${actionTag("动作防呆", "select-page-tab", `data-page-tab-key="automationDetailTab" data-page-tab-value="guards"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", automationHandoffAttrs)}` }
分析 ${escapeHtml(formatNumber(analysisJobs))} AI 视频 ${escapeHtml(formatNumber(aiVideoJobs))} ${escapeHtml(formatNumber(realCutJobs))} 实拍 ${escapeHtml(overview.headline)}
${renderDetailTabs("automationDetailTab", tabs)} ${activeTab === "health" ? renderIntegrationOverviewPanel({ showActions: false }) : `

动作防呆

依赖不可用时,相关动作会在这里和生产页一起被拦住。
${escapeHtml(overview.headline)}
AI 视频 ${escapeHtml(getPipelineGuard("aiVideo").enabled ? "可执行" : "已拦截")} 实拍剪辑 ${escapeHtml(getPipelineGuard("realCut").enabled ? "可执行" : "已拦截")} ASR ${escapeHtml(getIntegrationStatus(getIntegrationDetail("asr")).summary)}
${renderPipelineButton("aiVideo", "primary")} ${renderPipelineButton("realCut")}
${escapeHtml(overview.subtitle)}
`}
` ); } function renderOwnedScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("我的账号", "工作区就绪后,这里会自动显示当前账号和建议动作。"); } return screenShell("我的账号", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("我的账号未加载", "自动连接成功后,这里会展示当前账号和建议动作。")); } const me = appState.me || appState.session?.account || {}; const firstAssistant = safeArray(appState.dashboard.assistants)[0]; const selectedProject = getSelectedProject(); const jobs = safeArray(appState.dashboard.recent_jobs); const completedJobs = jobs.filter((item) => item.status === "completed").length; const activeJobs = jobs.filter((item) => item.status !== "completed").length; return screenShell( "我的账号", "这里先用当前登录账号和最近产出组合成第一版总览。", `${button("刷新", "refresh-data")} ${button("去 Agent", "goto-playbook", "primary")}`, `
${escapeHtml(initials(me.display_name || me.username))}

${escapeHtml(me.display_name || me.username || "当前账号")}

${escapeHtml(firstAssistant?.generation_goal || "先创建 Agent,再把平台目标和变现方式补齐。")}

项目${escapeHtml(formatNumber(appState.dashboard.projects?.length))}
Agent${escapeHtml(formatNumber(appState.dashboard.assistants?.length))}
任务${escapeHtml(formatNumber(appState.dashboard.recent_jobs?.length))}
素材${escapeHtml(formatNumber(appState.documents.length))}
当前账号任务 ${escapeHtml(me.display_name || me.username || "当前账号")}

${escapeHtml( activeJobs ? "先处理当前待推进任务,再决定是否继续补 Agent 或整理项目信息。" : "当前任务压力不高,更适合补 Agent 策略、整理项目说明或继续跟踪。" )}

${actionTag(activeJobs ? "去生产中心" : "去 Agent", activeJobs ? "goto-production" : "goto-playbook")} ${actionTag("去我的项目", "goto-intake")} ${actionTag("看跟踪账号", "goto-tracking")}
当前项目 ${escapeHtml(selectedProject?.name || "未选项目")} ${escapeHtml(getSelectedAssistant()?.name || "未选 Agent")} 待推进 ${escapeHtml(formatNumber(activeJobs))} 已完成 ${escapeHtml(formatNumber(completedJobs))}

当前负责范围

把你当前最常用的工作上下文放在一起。

当前项目 · ${escapeHtml(selectedProject?.name || "未选项目")}

${escapeHtml(selectedProject?.description || "当前还没有清晰项目说明,可以去“我的项目”补齐。")}

项目 ${escapeHtml(formatNumber(appState.dashboard.projects?.length))} ${escapeHtml(getSelectedAssistant()?.name || "未选 Agent")} ${actionTag("去我的项目", "goto-intake")}

当前主 Agent

${escapeHtml(firstAssistant?.generation_goal || firstAssistant?.description || "先在 Agent 页面补齐你的默认协作方式。")}

${escapeHtml(firstAssistant?.name || "默认文案助手")} ${actionTag("去 Agent", "goto-playbook")}

最近工作摘要

用最少的信息告诉你最近跑了什么。
待推进任务${escapeHtml(formatNumber(activeJobs))}
已完成任务${escapeHtml(formatNumber(completedJobs))}
平台数${escapeHtml(formatNumber(getPlatformOptions().length))}
对标数${escapeHtml(formatNumber(appState.accounts.length))}

推荐下一步

${escapeHtml(activeJobs ? "先去生产中心处理待推进任务,再回到首页看下一条动作。" : "当前任务不重,适合补 Agent 策略或整理项目说明。")}

${actionTag(activeJobs ? "去生产中心" : "去 Agent", activeJobs ? "goto-production" : "goto-playbook")} ${actionTag("看跟踪账号", "goto-tracking")}
` ); } function renderPlaybookScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("Agent", "工作区就绪后,这里会自动加载真实 Agent 列表和模型。"); } return screenShell("Agent", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("Agent 未加载", "自动连接成功后,这里会展示真实 Agent 列表和模型。")); } const assistants = safeArray(appState.dashboard.assistants); const models = safeArray(appState.dashboard.model_profiles); const currentModel = getCurrentModelProfile(); const currentAssistant = getSelectedAssistant(); const localCatalog = appState.localModelCatalog || {}; const activeAdminOverrideNotice = appState.onelinerGovernanceEffective?.active_admin_override_notice || null; const gatewayModels = safeArray(localCatalog.models).map((item) => item.id).filter(Boolean); const playbookHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "playbook", sourceActionKey: "playbook-main-agent-handoff", intentKey: "custom", title: "继续梳理当前 Agent 工作区", goal: "继续梳理当前 Agent 工作区", summary: "让主 Agent 结合当前 Agent、模型和策略状态,给出下一步执行建议。", platform: appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform(), planSteps: ["读取当前 Agent 与模型配置", "检查当前策略与平台 Agent 缺口", "生成下一步执行建议"] }); const tabs = [ { value: "workspace", label: "当前 Agent 工作台" }, { value: "platform_agents", label: "平台 Agent" }, { value: "models", label: "模型与学习" } ]; const activeTab = getActiveDetailTab("playbookDetailTab", tabs); return screenShell( "Agent", "这里接真实 Agent 列表,当前已经支持切换和编辑 Agent。", `${button("配置 OneLiner", "open-oneliner-profile")} ${button("看配置历史", "open-oneliner-profile-history", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: playbookHandoffAttrs })} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "direct-create-assistant")} ${button("去生产", "goto-production", "primary")}`, ` ${renderMainAgentLandingNotice("playbook")}

Agent 概览

先定项目、平台和主模型,再导入内容让 Agent 学习。

${models.slice(0, 6).map((model) => `${escapeHtml(model.name)}`).join("") || `暂无模型`}

Agent 工作区

先处理你当前真的会用到的 Agent 信息,系统治理内容已移到管理员配置台。
当前 Agent 任务 ${escapeHtml(tabs.find((tab) => tab.value === activeTab)?.label || "当前 Agent 工作台")}

${escapeHtml( activeTab === "platform_agents" ? "先确认当前平台 Agent 是否已接上默认策略,再决定是否继续下放个性化策略。" : activeTab === "models" ? "先确定主模型和本机网关状态,再回到 Agent 工作区继续执行。" : activeAdminOverrideNotice?.title ? "先看当前管理员覆盖,再决定是调整我的策略还是继续交给主 Agent。" : currentAssistant ? `先围绕 ${currentAssistant.name} 调整配置,再决定是否继续交给主 Agent。` : "先配置 OneLiner 或创建第一个 Agent,再把当前项目交给它。" )}

${activeTab === "platform_agents" ? `${actionTag("看平台 Agent", "select-page-tab", `data-page-tab-key="playbookDetailTab" data-page-tab-value="platform_agents"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}` : activeTab === "models" ? `${actionTag("设主模型", "open-preferred-model")} ${actionTag("回工作区", "select-page-tab", `data-page-tab-key="playbookDetailTab" data-page-tab-value="workspace"`)}` : `${actionTag("配置 OneLiner", "open-oneliner-profile")} ${actionTag("看配置历史", "open-oneliner-profile-history")} ${actionTag(currentAssistant ? "去生产" : "新建 Agent", currentAssistant ? "goto-production" : "direct-create-assistant")} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}` }
主 Agent ${escapeHtml(appState.onelinerProfile?.display_name || "OneLiner")} ${escapeHtml(currentAssistant ? `当前 ${currentAssistant.name}` : "当前未选 Agent")} 模型 ${escapeHtml(formatNumber(models.length))} ${escapeHtml(activeAdminOverrideNotice?.title ? "有管理员覆盖" : "无管理员覆盖")}
${renderDetailTabs("playbookDetailTab", tabs)} ${activeTab === "workspace" ? `

OneLiner 主 Agent

前端还没上的功能由它兜底承接,并调度平台 Agent。
${escapeHtml(appState.onelinerProfile?.display_name || "OneLiner")} ${escapeHtml(appState.onelinerProfile?.default_platform ? platformLabel(appState.onelinerProfile.default_platform) : "未设默认平台")} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}

${escapeHtml(appState.onelinerProfile?.long_term_goal || "还没有设置长期目标")}

${escapeHtml(appState.onelinerProfile?.notes || "你可以把用户长期目标、账号目标、默认平台都绑给 OneLiner,再让它去调度平台 Agent。")}

会话 ${escapeHtml(formatNumber(safeArray(appState.onelinerSessions).length))} 平台 Agent ${escapeHtml(formatNumber(safeArray(appState.platformAgents).length))} 编辑配置 历史与回滚
${activeAdminOverrideNotice?.title ? `

管理员覆盖生效中

${escapeHtml(activeAdminOverrideNotice.summary || "当前 OneLiner 和平台 Agent 都会先遵循管理员覆盖层。")}

${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")} 看我的策略
` : ""} ${renderGovernanceSummaryCard({ title: "我的策略与历史", subtitle: appState.userGlobalPolicy?.current_version?.summary || "你和主 Agent 的策略对话,会先沉淀成用户全局策略,再按需要下放到单平台。", effective: appState.onelinerGovernanceEffective, actions: [ { action: "open-user-global-policy", label: `编辑全局策略 · 历史 ${formatNumber(appState.userGlobalPolicy?.versions?.count || 0)}` }, { action: "open-user-global-policy-history", label: "查看全局历史" }, { action: "open-user-platform-policy", label: `编辑当前平台策略 · 历史 ${formatNumber(appState.userCurrentPlatformPolicy?.versions?.count || 0)}`, platform: appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform() }, { action: "open-user-platform-policy-history", label: "查看当前平台历史", platform: appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform() } ] })}

当前 Agent

文案生成、对标绑定和复盘都会优先使用这里选中的 Agent。
${currentAssistant ? `已选` : `未选`} ${currentAssistant ? `编辑` : ""}
${currentAssistant ? `

${escapeHtml(currentAssistant.name)}

${escapeHtml(currentAssistant.generation_goal || currentAssistant.description || "先补齐这个 Agent 的目标和说明。")}

${escapeHtml(models.find((item) => item.id === currentAssistant.model_profile_id)?.name || "默认模型")} ${escapeHtml(formatNumber(safeArray(currentAssistant.knowledge_base_ids).length))} 条知识库 ${escapeHtml(brief(currentAssistant.description || "暂无说明", 22))}
` : `

还没有可用 Agent

先创建一个 Agent,再把当前项目的内容都交给它学习。

`}

Agent 列表

当前接的是后端 assistants
${assistants.map((assistant) => `

${escapeHtml(assistant.name)}

${escapeHtml(assistant.description || assistant.generation_goal || "暂无说明")}

知识库 ${escapeHtml(formatNumber(safeArray(assistant.knowledge_base_ids).length))} ${escapeHtml(models.find((item) => item.id === assistant.model_profile_id)?.name || "默认模型")} ${assistant.id === currentAssistant?.id ? "当前 Agent" : "设为当前"} 编辑
`).join("") || `

还没有 Agent

下一步可以直接把创建动作接进来。

`}

最近生成

当前先承接文案生成结果
${appState.lastGeneratedCopy ? `

${escapeHtml(appState.lastGeneratedCopy.assistantName)}

${escapeHtml(appState.lastGeneratedCopy.content)}

需求:${escapeHtml(brief(appState.lastGeneratedCopy.prompt, 24))} ${escapeHtml(formatNumber(appState.lastGeneratedCopy.usedDocuments.length))} 条参考
` : `

还没有生成结果

先点“生成文案”,这里会保留最近一次结果。

`}
` : activeTab === "platform_agents" ? `
${renderPlatformAgentPanel()}
` : `

本机模型网关

当前默认分析会优先走本机 cli-proxy-api。
${escapeHtml(localCatalog.reachable ? "在线" : "离线")} ${localCatalog.management_url ? `打开管理页` : ""}

${escapeHtml(currentModel?.name || localCatalog.default_model || "GLM-5")}

${escapeHtml(currentModel ? `${currentModel.model_name || "-"} · ${currentModel.base_url || "-"}` : (localCatalog.public_base_url || localCatalog.base_url || "尚未读取到网关地址"))}

${gatewayModels.slice(0, 6).map((model) => `${escapeHtml(model)}`).join("") || `暂无可见模型`}

模型列表

来自真实 model_profiles
${models.map((model) => `

${escapeHtml(model.name)}

${escapeHtml(model.model_name || "-")} · ${escapeHtml(model.base_url || "-")}

`).join("") || `

暂无模型

先在“我的”里配置模型。

`}

最近学习素材

从知识库文档取最近内容
${appState.documents.slice(0, 4).map((doc) => `

${escapeHtml(doc.title)}

${escapeHtml(brief(doc.style_summary || doc.transcript_text || doc.combined_text, 72))}

`).join("") || `

还没有学习素材

先去找对标导入一条主页或作品。

`}
`}
` ); } function renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, recoverableCount, works, recentDocs }) { const taskCards = []; if (activeTab === "recovery") { const nextRecoverable = failedJobs.find((item) => item.recovery.recoverable) || null; if (nextRecoverable) { taskCards.push(`

当前要先处理

${escapeHtml(`${nextRecoverable.job.title || nextRecoverable.job.id} 仍可恢复,建议优先重开。`)}

${escapeHtml(nextRecoverable.recovery.label)} ${actionTag(nextRecoverable.recovery.actionLabel || "立即恢复", "recover-job", `data-job-id="${escapeHtml(nextRecoverable.job.id)}"`)}
`); } taskCards.push(`

恢复面板

${escapeHtml(recoverableCount ? `当前有 ${recoverableCount} 条任务可以直接恢复。` : "当前没有可直接恢复的失败任务。")}

${actionTag("批量恢复", "batch-recover-jobs")} ${actionTag("恢复记录", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recovery"`)}
`); } else if (activeTab === "recorder") { const status = appState.liveRecorderStatus || {}; const activeCount = safeArray(status.active_recordings).length; taskCards.push(`

当前要先处理

${escapeHtml(status.running ? `Live Recorder 正在运行,当前有 ${activeCount} 路活动录制。` : "先确认 Live Recorder 是否在线,再检查录制源和文件。")}

${escapeHtml(status.running ? "运行中" : "待检查")} ${actionTag("录制维护", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recorder"`)}
`); taskCards.push(`

录制源与文件

${escapeHtml(`录制源 ${formatNumber(safeArray(appState.liveRecorderSources).length)} 个 · 文件 ${formatNumber(safeArray(appState.liveRecorderFiles).length)} 个`)}

${actionTag("交给主 Agent", "handoff-to-main-agent", buildMainAgentHandoffAttrs({ sourceScreen: "production", sourceActionKey: "production-mobile-recorder-handoff", intentKey: "production_coordination", title: "继续处理录制维护", goal: "继续处理录制维护", summary: "结合录制维护状态给出下一步动作。", platform: getPreferredPlatform(), platformScope: "single_platform", planSteps: ["读取录制维护状态", "识别当前阻塞项", "生成下一步处理动作"] }))}
`); } else if (activeTab === "outputs") { const topWork = works[0] || null; taskCards.push(`

当前要先处理

${escapeHtml(topWork ? `先看 ${describeVideo(topWork)} 的结果,再决定是否回到复盘。` : "先看最近产物和学习素材,再决定是否继续复盘或返回生产。")}

${actionTag("去复盘", "goto-review")} ${actionTag("查看产物", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="outputs"`)}
`); if (recentDocs.length) { taskCards.push(`

最近学习素材

${escapeHtml(brief(recentDocs[0].title || recentDocs[0].style_summary || "最近文档", 72))}

学习素材 ${escapeHtml(recentDocs[0].source_type || "document")}
`); } } else { const topJob = (activeJobs.length ? activeJobs : []).slice(0, 1)[0] || null; taskCards.push(`

当前要先处理

${escapeHtml(topJob ? `${topJob.title} 还在推进中,建议先看状态再决定是否做 AI 视频或实拍剪辑。` : "先看当前生产队列,再决定是否继续恢复或进入复盘。")}

${actionTag("交给主 Agent", "handoff-to-main-agent", buildMainAgentHandoffAttrs({ sourceScreen: "production", sourceActionKey: "production-mobile-queue-handoff", intentKey: "production_coordination", title: "继续推进生产队列", goal: "继续推进生产队列", summary: "结合当前生产队列给出下一步动作。", platform: getPreferredPlatform(), platformScope: "single_platform", planSteps: ["读取当前生产队列", "识别最该优先推进的项", "生成下一步处理动作"] }))} ${recoverableCount ? actionTag("看失败恢复", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recovery"`) : actionTag("去复盘", "goto-review")}
`); if (topJob) { taskCards.push(`

${escapeHtml(topJob.title)}

${escapeHtml(brief(topJob.style_summary || topJob.transcript_text || topJob.error || "暂无摘要", 84))}

${escapeHtml(topJob.status)} ${escapeHtml(topJob.line_type || "analysis")}
`); } } return `
${taskCards.slice(0, 2).join("")}
`; } function renderProductionScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("生产中心", "工作区就绪后,这里会自动加载真实任务和作品。"); } return screenShell("生产中心", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("生产中心未加载", "自动连接成功后,这里会展示真实任务和作品。")); } const jobs = safeArray(appState.dashboard.recent_jobs); const activeJobs = jobs.filter((item) => item.status !== "completed").slice(0, 4); const failedJobs = getRecoverableFailedJobs(); const recoverableCount = failedJobs.filter((item) => item.recovery.recoverable).length; const recentDocs = appState.documents.slice(0, 3); const works = getProductionWorks(6); const isMobileUi = isMobileViewport(); const productionHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "production", sourceActionKey: "production-handoff", intentKey: "production_coordination", title: "继续推进生产中心", goal: "基于当前生产队列、失败任务和产物,生成下一步生产推进计划", summary: "主 Agent 会结合生产队列、失败恢复和产物状态,给出下一步动作。", platform: getPreferredPlatform(), platformScope: "single_platform", planSteps: [ "读取当前生产队列和失败任务", "识别最该优先推进或恢复的项", "生成一版生产推进计划" ] }); const tabs = [ { value: "queue", label: "生产队列" }, { value: "recovery", label: "失败恢复" }, { value: "recorder", label: "录制维护" }, { value: "outputs", label: "作品与产物" } ]; const activeTab = getActiveDetailTab("productionDetailTab", tabs); const productionActionsHtml = isMobileUi ? `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })}` : `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`; return screenShell( "生产中心", "这里已经接上真实任务、失败恢复和知识库文档,适合直接推进生产、恢复和复盘。", productionActionsHtml, ` ${renderMainAgentLandingNotice("production")}

生产队列

最近任务的真实状态

分析任务

最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "analysis").length))} 条

实拍剪辑

最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "real_cut").length))} 条

AI 视频

最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "ai_video").length))} 条

内容源同步

最近 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "content_source_sync").length))} 条

分析 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "analysis").length))} 实拍 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "real_cut").length))} AI 视频 ${escapeHtml(formatNumber(jobs.filter((item) => item.line_type === "ai_video").length))}
${renderQuotaBlockingNotice()}

生产工作区

把队列、恢复、录制和产物拆开看,减少一次性信息量。
当前工作流 ${escapeHtml(tabs.find((tab) => tab.value === activeTab)?.label || "生产队列")}

${escapeHtml( activeTab === "recovery" ? "先处理失败任务和可恢复项,再决定是否批量重开。" : activeTab === "recorder" ? "先确认录制服务和文件状态,再回到队列继续推进。" : activeTab === "outputs" ? "先看产物和作品,再决定是否回到复盘或继续生产。" : "先看处理中任务,再把异常和产物安排到下一步。" )}

${activeTab === "recovery" ? `${actionTag("批量恢复", "batch-recover-jobs")} ${actionTag("查看恢复记录", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recovery"`)}` : activeTab === "outputs" ? `${actionTag("去复盘", "goto-review")} ${actionTag("查看产物", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="outputs"`)}` : activeTab === "recorder" ? `${actionTag("录制维护", "select-page-tab", `data-page-tab-key="productionDetailTab" data-page-tab-value="recorder"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", productionHandoffAttrs)}` : `${actionTag("批量恢复", "batch-recover-jobs")} ${actionTag("交给主 Agent", "handoff-to-main-agent", productionHandoffAttrs)}` }
处理中 ${escapeHtml(formatNumber(activeJobs.length || jobs.filter((item) => item.status !== "completed").length))} 失败 ${escapeHtml(formatNumber(failedJobs.length))} 可恢复 ${escapeHtml(formatNumber(recoverableCount))} 产物 ${escapeHtml(formatNumber(works.length))}
${renderProductionMobileTaskDeck({ activeTab, activeJobs, failedJobs, recoverableCount, works, recentDocs })} ${renderDetailTabs("productionDetailTab", tabs)} ${activeTab === "queue" ? `

当前任务

来自 recent_jobs
${(activeJobs.length ? activeJobs : jobs.slice(0, 4)).map((job) => `

${escapeHtml(job.title)}

${escapeHtml(brief(job.style_summary || job.transcript_text || job.error || "暂无摘要", 80))}

${escapeHtml(job.status)} ${escapeHtml(job.line_type || "analysis")} ${canDeriveAiVideo(job) ? renderPipelineJobTag("aiVideo", job, "做 AI 视频") : ""} ${canDeriveRealCut(job) ? renderPipelineJobTag("realCut", job, "做实拍剪辑") : ""} ${actionTag("看详情", "open-job-detail", `data-job-id="${escapeHtml(job.id)}"`)}
`).join("") || `

还没有任务

先去找对标导入内容。

`}
` : activeTab === "recovery" ? `

失败任务恢复

把最近失败任务按恢复可行性分组,批量恢复入口在这里。
${escapeHtml(formatNumber(failedJobs.filter((item) => item.recovery.recoverable).length))} 可恢复 ${escapeHtml(formatNumber(failedJobs.filter((item) => !item.recovery.recoverable).length))} 需人工 批量恢复
${failedJobs.map(({ job, recovery }) => `

${escapeHtml(job.title || job.id)}

${escapeHtml(brief(job.error || recovery.reason || "任务失败,请查看恢复说明。", 120))}

${escapeHtml(recovery.label)} ${escapeHtml(job.line_type || job.source_type || "analysis")} ${job.updated_at ? `${escapeHtml(formatDateTime(job.updated_at))}` : ""} ${recovery.recoverable ? `${escapeHtml(recovery.actionLabel)}` : `${escapeHtml(recovery.reason)}`} 看详情
`).join("") || `

当前没有失败任务

最近任务都在正常推进,暂时不需要恢复。

`}
${renderRecoveryHistoryPanel()}
` : activeTab === "recorder" ? `
${renderLiveRecorderManagementPanel()}
` : `

作品与成片

先看真实作品,再继续整理文档与成片
${works.map((video) => `

${escapeHtml(describeVideo(video))}

${escapeHtml(`发布时间 ${formatDateTime(video.published_at)} · 播放 ${formatNumber(video.stats?.play)} · 点赞 ${formatNumber(video.stats?.like)}`)}

${escapeHtml(video.content_type || "video")} 得分 ${escapeHtml(formatNumber(video.score?.performance_score || 0))} ${getVideoLink(video) ? `打开原作品` : ""}
`).join("")} ${recentDocs.map((doc) => `

${escapeHtml(doc.title)}

${escapeHtml(brief(doc.style_summary || doc.combined_text || doc.transcript_text, 92))}

${escapeHtml(doc.source_type || "document")}学习素材
`).join("") || (works.length ? "" : `

还没有作品

先导入内容或跑一次分析任务。

`)}
${renderLastJobDetailCard()}
`}
` ); } function renderReviewScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("发布与复盘", "工作区就绪后,这里会自动生成复盘入口和最近完成任务。"); } return screenShell("发布与复盘", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("复盘未加载", "自动连接成功后,这里会先用最近任务生成一版复盘入口。")); } const project = getSelectedProject(); const completed = safeArray(appState.dashboard.recent_jobs).filter((item) => item.status === "completed").slice(0, 4); const reviews = getProjectReviews(project?.id || "").slice(0, 8); const verdictCounts = reviews.reduce((acc, review) => { const key = review?.verdict?.trim() || "待补结论"; acc[key] = (acc[key] || 0) + 1; return acc; }, {}); const topVerdict = Object.entries(verdictCounts).sort((left, right) => right[1] - left[1])[0] || null; const publishedReviewCount = reviews.filter((review) => review.publish_url || review.published_at).length; const reviewTaskTitle = completed.length ? "先把最近完成任务写成复盘" : reviews.length ? "先回看高频结论" : "先跑出第一条可复盘任务"; const reviewTaskSummary = completed.length ? "最近已经有完成任务,先沉淀成结构化复盘,再决定是否回到生产继续放大。" : reviews.length ? "当前项目已经有复盘沉淀,先看出现次数最多的结论,再决定下一步继续做什么。" : "当前还没有复盘和完成任务,先去生产中心跑出一条完整链路。"; const reviewHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "review", sourceActionKey: "review-handoff", intentKey: "review_followup", title: project ? `继续沉淀项目复盘 · ${project.name}` : "继续沉淀复盘", goal: "基于最近完成任务和已保存复盘,生成下一步复盘与发布计划", summary: "主 Agent 会结合最近完成任务和现有复盘,整理下一步复盘动作。", platform: getPreferredPlatform(), platformScope: "single_platform", planSteps: [ "读取最近完成任务和现有复盘", "识别还缺的复盘或发布动作", "生成下一步复盘推进计划" ] }); return screenShell( "发布与复盘", "先看已保存复盘,再把完成任务转成结构化复盘。", `${button("写复盘", "direct-review-draft")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`, ` ${renderMainAgentLandingNotice("review")}

复盘工作区

${escapeHtml(project?.name || "当前项目")} · 先把完成任务沉淀成可追溯复盘,再决定是否回到生产继续推进。

已保存${escapeHtml(formatNumber(reviews.length))}
最近完成${escapeHtml(formatNumber(completed.length))}
当前项目${escapeHtml(project?.name || "未选项目")}
高频结论${escapeHtml(topVerdict ? topVerdict[0] : "待补结论")}

${escapeHtml(reviewTaskTitle)}

${escapeHtml(reviewTaskSummary)}

${actionTag(completed.length ? "写复盘" : reviews.length ? "看复盘" : "去生产", completed.length ? "direct-review-draft" : reviews.length ? "goto-review" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")} ${actionTag("交给主 Agent", "handoff-to-main-agent", reviewHandoffAttrs)} ${escapeHtml(`已保存 ${formatNumber(reviews.length)} 条`)} ${escapeHtml(`已发布 ${formatNumber(publishedReviewCount)} 条`)} ${escapeHtml(`高频结论 ${topVerdict ? topVerdict[0] : "待补"}`)}
当前复盘任务 ${escapeHtml(project?.name || "当前项目")}

${escapeHtml( completed.length ? "先把最近完成任务写成复盘,再决定是否继续沉淀发布结论。" : reviews.length ? "先回看已保存复盘,再决定是否回到生产继续推进。" : "当前还没有可用复盘,先回到生产中心跑出一条完成链路。" )}

${actionTag(completed.length ? "写复盘" : "去生产", completed.length ? "direct-review-draft" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")} ${actionTag("刷新", "refresh-data")} ${actionTag("交给主 Agent", "handoff-to-main-agent", reviewHandoffAttrs)}
已保存 ${escapeHtml(formatNumber(reviews.length))} 最近完成 ${escapeHtml(formatNumber(completed.length))} ${escapeHtml(`已发布 ${formatNumber(publishedReviewCount)}`)} ${escapeHtml(topVerdict ? `高频 ${topVerdict[0]}` : (completed.length ? "可继续写复盘" : "先回生产"))}

已保存复盘

当前项目的真实复盘记录
${escapeHtml(formatNumber(reviews.length))} 条
${reviews.map((review) => `

${escapeHtml(review.title)}

${escapeHtml(brief(review.highlights || review.next_actions || review.notes || "已保存复盘,待继续补充表现数据。", 92))}

${escapeHtml(platformLabel(review.platform || "douyin"))} ${escapeHtml(review.verdict || "已记录")} ${review.publish_url ? `打开链接` : ""} 编辑
`).join("") || `

还没有复盘

可以把最近完成任务直接写成一条复盘。

`}

最近完成

从完成任务继续写复盘或进入下一步生产
${completed.map((job) => `

${escapeHtml(job.title)}

${escapeHtml(brief(job.style_summary || job.transcript_text || "已完成,待补复盘。", 84))}

已完成 ${escapeHtml(job.line_type || "analysis")} ${actionTag("写复盘", "direct-review-draft", `data-job-id="${escapeHtml(job.id)}"`)} ${canDeriveAiVideo(job) ? renderPipelineJobTag("aiVideo", job, "做 AI 视频") : ""} ${canDeriveRealCut(job) ? renderPipelineJobTag("realCut", job, "做实拍剪辑") : ""} ${actionTag("看详情", "open-job-detail", `data-job-id="${escapeHtml(job.id)}"`)}
`).join("") || `

还没有完成任务

先去生产中心跑一条链路。

`}
${renderLastJobDetailCard()} ` ); } function renderStrategyScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("我的策略", "工作区就绪后,这里会自动展示你自己的策略层和治理记录。"); } return screenShell("我的策略", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("策略未加载", "自动连接成功后,这里会展示你的全局策略、平台策略和治理记录。")); } const tabs = [ { value: "effective", label: "当前生效" }, { value: "global", label: "全局策略" }, { value: "platform", label: "当前平台策略" }, { value: "activity", label: "变更记录" } ]; const activeTab = getActiveDetailTab("strategyDetailTab", tabs); const project = getSelectedProject(); const platform = appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform(); const activeAdminOverrideNotice = appState.onelinerGovernanceEffective?.active_admin_override_notice || null; const strategyHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "strategy", sourceActionKey: "strategy-main-agent-handoff", intentKey: "custom", title: "继续调整我的策略", goal: "继续调整我的策略", summary: "让主 Agent 结合当前生效层、个人策略和管理员覆盖,给出下一步治理建议。", platform, planSteps: ["读取当前生效策略", "检查用户层与管理员覆盖差异", "生成下一步治理建议"] }); return screenShell( "我的策略", "把你和主 Agent 的对话沉淀成可查看、可回滚、可追溯的个人策略层。", `${button("编辑全局策略", "open-user-global-policy")} ${button("编辑当前平台策略", "open-user-platform-policy", "primary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: strategyHandoffAttrs })}`, ` ${renderMainAgentLandingNotice("strategy")}

当前策略工作区

${escapeHtml(project?.name || "当前项目")} · ${escapeHtml(platformLabel(platform))}。这里展示系统默认、你的个性化策略和管理员覆盖是如何叠加生效的。

${activeAdminOverrideNotice?.title ? `

管理员覆盖生效中

${escapeHtml(activeAdminOverrideNotice.summary || "当前项目下的部分策略被管理员覆盖层托底。")}

${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")} ${activeAdminOverrideNotice.platform_label ? `${escapeHtml(activeAdminOverrideNotice.platform_label)}` : ""}
` : ""}

策略治理

先看当前生效,再回看你自己的历史和管理员覆盖,不必再通过多个弹窗来回切。
当前策略任务 ${escapeHtml(tabs.find((tab) => tab.value === activeTab)?.label || "当前生效")}

${escapeHtml( activeTab === "global" ? "先确认你的全局策略是否还贴合当前项目,再决定是否发布新版本。" : activeTab === "platform" ? `先看 ${platformLabel(platform)} 当前平台策略,再决定是否只改这个平台。` : activeTab === "activity" ? "先看最近变更和回滚,再决定是否继续交给主 Agent 调整。" : activeAdminOverrideNotice?.title ? "先确认管理员覆盖为什么生效,再决定要不要继续改自己的策略。" : "先看当前生效层,再决定调整全局还是当前平台。" )}

${activeTab === "global" ? `${actionTag("编辑全局策略", "open-user-global-policy")} ${actionTag("看全局历史", "open-user-global-policy-history")}` : activeTab === "platform" ? `${actionTag("编辑当前平台策略", "open-user-platform-policy", `data-platform="${escapeHtml(platform)}"`)} ${actionTag("看平台历史", "open-user-platform-policy-history", `data-platform="${escapeHtml(platform)}"`)}` : activeTab === "activity" ? `${actionTag("交给主 Agent", "handoff-to-main-agent", strategyHandoffAttrs)} ${actionTag("回当前生效", "select-page-tab", `data-page-tab-key="strategyDetailTab" data-page-tab-value="effective"`)}` : `${actionTag("编辑全局策略", "open-user-global-policy")} ${actionTag("编辑当前平台策略", "open-user-platform-policy", `data-platform="${escapeHtml(platform)}"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", strategyHandoffAttrs)}` }
当前生效 ${escapeHtml(formatNumber(safeArray(appState.onelinerGovernanceEffective?.layers).length))} 层 ${escapeHtml(platformLabel(platform))} 平台策略 变更 ${escapeHtml(formatNumber(safeArray(appState.userPolicyAudits).length))} ${escapeHtml(activeAdminOverrideNotice?.title ? "管理员覆盖生效" : "无管理员覆盖")}
${renderDetailTabs("strategyDetailTab", tabs)} ${activeTab === "effective" ? `
${renderGovernanceSummaryCard({ title: "当前生效策略", subtitle: appState.onelinerGovernanceEffective?.summary || "当前主 Agent 会按下面这些层级叠加执行。", effective: appState.onelinerGovernanceEffective, actions: [ { action: "open-user-global-policy", label: "编辑我的全局策略" }, { action: "open-user-platform-policy", label: "编辑当前平台策略", platform }, { action: "handoff-to-main-agent", label: "交给主 Agent 调整", platform, sourceScreen: "strategy", sourceActionKey: "governance-summary-handoff", intentKey: "custom", title: "调整当前策略", goal: "调整当前策略", summary: "先由主 Agent 读取当前治理层,再给一版确认卡。", planSteps: ["读取当前生效策略", "结合管理员覆盖与个人策略生成方案", "等待用户确认后执行"] } ] })}

当前叠加层

系统默认、用户层和管理员覆盖会按优先级叠加。
${safeArray(appState.onelinerGovernanceEffective?.layers).map((layer) => `

${escapeHtml(policyScopeTagLabel(layer.scope_kind, layer.scope?.platform || platform))}

${escapeHtml(layer.current_version?.summary || layer.scope?.summary || "当前层还没有补充摘要。")}

${layer.current_version?.version_no ? `版本 ${escapeHtml(formatNumber(layer.current_version.version_no || 0))}` : ""} ${layer.scope?.platform ? `${escapeHtml(platformLabel(layer.scope.platform))}` : ""}
`).join("") || `

还没有策略层

当前会话还没有拉到治理层信息。

`}
` : activeTab === "global" ? `

我的全局策略

这层只影响你自己,会先于平台策略被主 Agent 读取。
${renderPolicyVersionSummary(appState.userGlobalPolicy || {}, "你还没有发布自己的全局策略。")}
编辑 历史与回滚

最近全局版本

你的全局策略回滚不会覆盖旧记录,而是生成一个新的版本。
${renderPolicyVersionsHtml(appState.userGlobalPolicy?.versions?.items || appState.userGlobalPolicy?.versions || [], "你的全局策略还没有历史版本。")}
` : activeTab === "platform" ? `

${escapeHtml(platformLabel(platform))} 当前平台策略

只影响当前平台,不会连带改动其他平台。
${renderPolicyVersionSummary(appState.userCurrentPlatformPolicy || {}, `你还没有发布 ${platformLabel(platform)} 平台策略。`)}
编辑 历史与回滚

最近平台版本

当前平台的个性化策略会覆盖你的全局策略,但仍然只对你自己生效。
${renderPolicyVersionsHtml(appState.userCurrentPlatformPolicy?.versions?.items || appState.userCurrentPlatformPolicy?.versions || [], `${platformLabel(platform)} 还没有历史版本。`)}
` : `

最近策略变更

你的发布、回滚,以及管理员对当前项目的覆盖动作都会留痕在这里。
${renderPolicyAuditFeed(appState.userPolicyAudits, "当前项目还没有治理记录。")}

治理提醒

让你快速判断当前是该继续微调,还是该让管理员介入。

用户层不会影响其他人

你发布或回滚自己的策略,只会影响你当前账户下的工作方式。

管理员覆盖会明确可见

如果管理员对你当前项目施加了覆盖层,这里会出现对应的记录与摘要。

`}
` ); } function renderCreditsScreen() { if (!appState.dashboard) { if (isAutoConnectionPending()) { return renderAutoConnectingScreen("额度", "工作区就绪后,这里会自动展示真实额度和运营看板。"); } return screenShell("额度", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("额度未加载", "自动连接成功后,这里会展示真实额度和运营看板。")); } const jobs = safeArray(appState.dashboard.recent_jobs); const quota = appState.tenantQuota; const usage = appState.tenantUsage || quota?.usage || {}; const categories = usage?.categories || {}; const forecast = getTenantQuotaForecastValues(quota || {}, usage); const estimatedVideoUsage = (categories.ai_video?.quantity || 0) + (categories.real_cut?.quantity || 0); const budgetAmount = (quota?.monthly_budget_cents || usage?.total_cost_cents || 0) / 100; const usedAmount = (usage?.total_cost_cents || 0) / 100; const quotaProtectionLabel = quota?.enabled === false ? "额度保护关闭" : "额度保护开启"; const riskLabel = quota?.storage_over_limit ? "存储超限" : "当前风险可控"; return screenShell( "额度", "在接真实计费前,先按任务量给出运营看板。", `${button("刷新", "refresh-data")}`, `
当前额度任务 ${escapeHtml(quotaProtectionLabel)}

${escapeHtml( quota ? `先确认预算 ${formatNumber(budgetAmount)} 元和已用 ${formatNumber(usedAmount)} 元,再决定是继续放量还是先收紧高成本动作。` : "先看预算和高成本动作的预估消耗,再决定是否要继续做视频或剪辑任务。" )}

${actionTag("刷新额度", "refresh-data")} ${actionTag("去生产中心", "goto-production")} ${actionTag("交给主 Agent", "handoff-to-main-agent", buildMainAgentHandoffAttrs({ sourceScreen: "credits", sourceActionKey: "credits-main-agent-handoff", intentKey: "custom", title: "评估当前额度和预算风险", goal: "评估当前额度和预算风险", summary: "让主 Agent 结合当前预算、已用额度和高成本动作,给出下一步建议。", planSteps: ["读取当前额度看板", "判断预算与高成本动作风险", "给出下一步控制建议"] }))}
预算 ${escapeHtml(formatNumber(budgetAmount))} 元 ${escapeHtml(riskLabel)} ${escapeHtml(formatNumber(categories.copy?.quantity || jobs.filter((item) => item.line_type === "analysis").length))} 条文案 ${escapeHtml(formatNumber(estimatedVideoUsage || jobs.filter((item) => item.line_type === "ai_video" || item.line_type === "real_cut").length))} 次视频
${renderTenantQuotaForecastTags(quota || {}, usage)}
文案消耗预估${escapeHtml(formatNumber(categories.copy?.quantity || jobs.filter((item) => item.line_type === "analysis").length))}
分析 / 生成链路按任务量估算
本周期预算${escapeHtml(formatNumber((quota?.monthly_budget_cents || usage?.total_cost_cents || 0) / 100))}
已用 ${escapeHtml(formatNumber((usage?.total_cost_cents || 0) / 100))} 元 · 剩余 ${escapeHtml(formatNumber(forecast.remainingBudgetCents / 100))} 元
视频消耗预估${escapeHtml(formatNumber(estimatedVideoUsage || jobs.filter((item) => item.line_type === "ai_video" || item.line_type === "real_cut").length))}
AI 视频 / 实拍剪辑可做套餐

当前额度策略

先让用户能看懂“还剩多少、风险在哪、下一步怎么做”。

预算与已用

${escapeHtml(quota ? `当前预算 ${formatNumber((quota.monthly_budget_cents || 0) / 100)} 元,已用 ${formatNumber((usage?.total_cost_cents || 0) / 100)} 元,剩余 ${formatNumber(forecast.remainingBudgetCents / 100)} 元。` : "当前项目还没有独立额度策略,先按最近动作量和成本信号建立可执行的预算基线。")}

动作额度

${escapeHtml(quota ? `文案剩余 ${formatNumber(forecast.remainingCopyQuota)} / AI 视频剩余 ${formatNumber(forecast.remainingAiVideoQuota)} / 实拍剪辑剩余 ${formatNumber(forecast.remainingRealCutQuota)}。` : "文案、AI 视频和实拍剪辑会按动作池分开管理,适合先按项目阶段配置试用、增长或规模套餐。")}

使用建议

${escapeHtml((quota?.enabled === false) ? "当前额度保护已关闭,适合内部联调,不适合正式对外。":"当前额度保护已开启,适合逐步转向对外产品表达。")}

套餐建议

${escapeHtml(packageRecommendation.summary)}

${escapeHtml(`推荐 ${packageRecommendation.title}`)} ${escapeHtml(packageRecommendation.reason)}

用户能理解的表达

不要只给数字,要给解释和风险提示。

文案额度

适合高频迭代,建议和项目阶段绑定,而不是裸数字展示。

视频额度

成本更高,适合明确写出已用、剩余和推荐使用场景。

风险提示

${escapeHtml(quota?.storage_over_limit ? "当前存储已超限,优先处理清理或扩容,再继续放量。" : "当前没有明显超限风险,适合把预算、动作池和项目阶段绑定成正式套餐。")}

` ); } const TENANT_QUOTA_PACKAGE_PRESETS = { trial: { title: "试用套餐", description: "适合先跑通主流程的小规模项目,预算和动作池会优先保护试错成本。", focus: "先验证项目是否跑得通,再决定是否扩容。", warnThreshold: 0.7, monthlyBudgetCents: 9900, storageLimitBytes: 5 * 1024 * 1024 * 1024, analysisQuota: 30, copyQuota: 60, aiVideoQuota: 2, realCutQuota: 1, recorderQuota: 4 }, growth: { title: "增长套餐", description: "适合已经形成固定内容节奏的项目,兼顾分析、文案和视频动作的持续投放。", focus: "先把稳定增长跑顺,再看哪里需要单独加码。", warnThreshold: 0.8, monthlyBudgetCents: 49900, storageLimitBytes: 20 * 1024 * 1024 * 1024, analysisQuota: 160, copyQuota: 320, aiVideoQuota: 12, realCutQuota: 8, recorderQuota: 20 }, scale: { title: "规模套餐", description: "适合多账号、多批次的量产项目,预算、存储和视频动作都会按高负载配置。", focus: "优先保证量产吞吐,再按平台专项做局部优化。", warnThreshold: 0.85, monthlyBudgetCents: 199000, storageLimitBytes: 80 * 1024 * 1024 * 1024, analysisQuota: 800, copyQuota: 1600, aiVideoQuota: 40, realCutQuota: 24, recorderQuota: 80 } }; function getTenantQuotaPackagePreset(label) { return TENANT_QUOTA_PACKAGE_PRESETS[String(label || "").trim().toLowerCase()] || null; } function getTenantQuotaForecastValues(values = {}, usage = null) { const resolvedUsage = usage || appState.tenantUsage || {}; const categories = resolvedUsage?.categories || {}; const readValue = (camelKey, snakeKey) => Number(values?.[camelKey] ?? values?.[snakeKey] ?? 0); const readUsageCount = (key) => Number(categories?.[key]?.quantity || 0); const monthlyBudgetCents = readValue("monthlyBudgetCents", "monthly_budget_cents"); const storageLimitBytes = readValue("storageLimitBytes", "storage_limit_bytes"); const analysisQuota = readValue("analysisQuota", "analysis_quota"); const copyQuota = readValue("copyQuota", "copy_quota"); const aiVideoQuota = readValue("aiVideoQuota", "ai_video_quota"); const realCutQuota = readValue("realCutQuota", "real_cut_quota"); const recorderQuota = readValue("recorderQuota", "recorder_quota"); const totalCostCents = Number(resolvedUsage?.total_cost_cents || 0); const storageBytes = Number(resolvedUsage?.storage_bytes || 0); return { monthlyBudgetCents, remainingBudgetCents: Math.max(monthlyBudgetCents - totalCostCents, 0), storageLimitBytes, remainingStorageBytes: Math.max(storageLimitBytes - storageBytes, 0), analysisQuota, remainingAnalysisQuota: Math.max(analysisQuota - readUsageCount("analysis"), 0), copyQuota, remainingCopyQuota: Math.max(copyQuota - readUsageCount("copy"), 0), aiVideoQuota, remainingAiVideoQuota: Math.max(aiVideoQuota - readUsageCount("ai_video"), 0), realCutQuota, remainingRealCutQuota: Math.max(realCutQuota - readUsageCount("real_cut"), 0), recorderQuota, remainingRecorderQuota: Math.max(recorderQuota - readUsageCount("live_recorder"), 0) }; } function renderTenantQuotaForecastTags(values, usage = null) { const forecast = getTenantQuotaForecastValues(values, usage); return ` ${escapeHtml(`剩余预算 ${formatNumber(forecast.remainingBudgetCents / 100)} 元`)} ${escapeHtml(`剩余文案 ${formatNumber(forecast.remainingCopyQuota)}`)} ${escapeHtml(`剩余 AI 视频 ${formatNumber(forecast.remainingAiVideoQuota)}`)} ${escapeHtml(`剩余实拍 ${formatNumber(forecast.remainingRealCutQuota)}`)} ${escapeHtml(`剩余存储 ${formatBytes(forecast.remainingStorageBytes)}`)} `; } function getTenantQuotaPackageRecommendation(values = {}, usage = null) { const resolvedUsage = usage || appState.tenantUsage || {}; const categories = resolvedUsage?.categories || {}; const totalCostCents = Number(resolvedUsage?.total_cost_cents || 0); const storageBytes = Number(resolvedUsage?.storage_bytes || 0); const usageCounts = { analysis: Number(categories.analysis?.quantity || 0), copy: Number(categories.copy?.quantity || 0), aiVideo: Number(categories.ai_video?.quantity || 0), realCut: Number(categories.real_cut?.quantity || 0), recorder: Number(categories.live_recorder?.quantity || 0) }; const hasUsage = totalCostCents > 0 || storageBytes > 0 || Object.values(usageCounts).some((value) => value > 0); const requiredCapacity = { monthlyBudgetCents: hasUsage ? Math.max(Math.ceil(totalCostCents * 1.5), 9900) : 9900, storageLimitBytes: hasUsage ? Math.max(Math.ceil(storageBytes * 1.5), 5 * 1024 * 1024 * 1024) : 5 * 1024 * 1024 * 1024, analysisQuota: usageCounts.analysis > 0 ? Math.max(usageCounts.analysis * 2, 30) : 30, copyQuota: usageCounts.copy > 0 ? Math.max(usageCounts.copy * 2, 60) : 60, aiVideoQuota: usageCounts.aiVideo > 0 ? Math.max(usageCounts.aiVideo * 2, 2) : 2, realCutQuota: usageCounts.realCut > 0 ? Math.max(usageCounts.realCut * 2, 1) : 1, recorderQuota: usageCounts.recorder > 0 ? Math.max(usageCounts.recorder * 2, 4) : 4 }; const currentLabel = String(values?.packageLabel ?? values?.package_label ?? "custom").trim().toLowerCase() || "custom"; const presetOrder = ["trial", "growth", "scale"]; const recommendedLabel = presetOrder.find((label) => { const preset = getTenantQuotaPackagePreset(label); if (!preset) return false; return preset.monthlyBudgetCents >= requiredCapacity.monthlyBudgetCents && preset.storageLimitBytes >= requiredCapacity.storageLimitBytes && preset.analysisQuota >= requiredCapacity.analysisQuota && preset.copyQuota >= requiredCapacity.copyQuota && preset.aiVideoQuota >= requiredCapacity.aiVideoQuota && preset.realCutQuota >= requiredCapacity.realCutQuota && preset.recorderQuota >= requiredCapacity.recorderQuota; }) || "custom"; const preset = getTenantQuotaPackagePreset(recommendedLabel); const packageTitle = preset?.title || "自定义套餐"; const reasonParts = []; if (totalCostCents > 0) reasonParts.push(`本周期已用 ${formatNumber(totalCostCents / 100)} 元`); if (usageCounts.aiVideo > 0 || usageCounts.realCut > 0) reasonParts.push(`视频动作 ${formatNumber(usageCounts.aiVideo + usageCounts.realCut)} 次`); if (usageCounts.copy > 0) reasonParts.push(`文案动作 ${formatNumber(usageCounts.copy)} 次`); if (storageBytes > 0) reasonParts.push(`存储 ${formatBytes(storageBytes)}`); return { label: recommendedLabel, title: packageTitle, currentLabel, matchesCurrent: currentLabel === recommendedLabel, statusTone: recommendedLabel === "custom" ? "orange" : currentLabel === recommendedLabel ? "green" : "blue", summary: currentLabel === recommendedLabel ? `${packageTitle} 已覆盖当前使用节奏,可以继续观察本周期消耗。` : `按当前使用节奏,更适合切到 ${packageTitle}。`, reason: reasonParts[0] ? `重点参考 ${reasonParts.slice(0, 2).join(",")}` : "当前还在起步阶段,建议先从试用套餐起步。" }; } function renderTenantQuotaPackagePreview(label, values = null, usage = null) { const preset = getTenantQuotaPackagePreset(label); const packageLabel = String(label || "custom").trim() || "custom"; const packageTitle = preset?.title || "自定义套餐"; const packageDescription = preset?.description || "按当前项目的预算、动作池和阶段手动配置套餐。"; const packageFocus = preset?.focus || "适合已经明确成本模型或需要特殊额度策略的项目。"; const currentValues = values || {}; const warnThreshold = Number(currentValues.warnThreshold ?? preset?.warnThreshold ?? 0.8) || 0.8; const recommendation = getTenantQuotaPackageRecommendation({ ...currentValues, packageLabel }, usage); return `

${escapeHtml(packageTitle)}

${escapeHtml(packageDescription)}

${escapeHtml(packageLabel)} ${escapeHtml(`预警 ${(warnThreshold * 100).toFixed(0)}%`)} ${preset ? `预设已锁定` : `支持自定义`}

${escapeHtml(packageFocus)}

套餐建议

${escapeHtml(recommendation.summary)}

${escapeHtml(`推荐 ${recommendation.title}`)} ${escapeHtml(recommendation.reason)}
${escapeHtml(`预算 ${formatNumber(Number(currentValues.monthlyBudgetCents || 0) / 100)} 元`)} ${escapeHtml(`文案 ${formatNumber(currentValues.copyQuota || 0)}`)} ${escapeHtml(`AI 视频 ${formatNumber(currentValues.aiVideoQuota || 0)}`)} ${escapeHtml(`实拍剪辑 ${formatNumber(currentValues.realCutQuota || 0)}`)}
${renderTenantQuotaForecastTags(currentValues)}
`; } function renderSettingsScreen() { const session = appState.session; const project = getSelectedProject(); const tabs = [ { value: "workspace", label: "连接与工作区" }, { value: "display", label: "界面与帮助" } ]; const activeTab = getActiveDetailTab("settingsDetailTab", tabs); const sessionConnected = Boolean(session); const settingsHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "settings", sourceActionKey: "settings-main-agent-handoff", intentKey: "custom", title: "检查当前设置和工作区连接", goal: "检查当前设置和工作区连接", summary: "让主 Agent 读取当前连接状态、工作区和常用入口,再给出下一步建议。", planSteps: ["读取当前连接与项目上下文", "确认当前工作区和入口状态", "生成下一步建议"] }); return screenShell( "设置", "这里不放系统治理内容,只处理当前用户需要理解的连接、界面和帮助信息。", `${isSuperAdmin() ? button("管理员配置台", "goto-admin-workbench", "primary") : button("刷新", "refresh-data", "primary")}`, `

设置与帮助

把连接状态、当前工作区和使用说明放在一起,避免和管理员控制面混在同一页。

当前设置

先看你现在接到了哪里,再决定是否要调整。
当前设置任务 ${escapeHtml(sessionConnected ? "已自动连接" : "等待连接")}

${escapeHtml( sessionConnected ? `当前已经连到 ${session?.account?.display_name || session?.account?.username || "当前工作区"},先确认工作区和常用入口,再决定是否切项目或回到业务页。` : "先确认当前站点是否已自动连接到工作区,再决定是重试连接还是回到业务页。" )}

${actionTag("打开连接状态", "open-auth")} ${project ? actionTag("去我的项目", "goto-intake") : ""} ${actionTag("交给主 Agent", "handoff-to-main-agent", settingsHandoffAttrs)}
${escapeHtml(sessionConnected ? "已自动连接" : "等待连接")} ${escapeHtml(project?.name || "未选项目")} ${escapeHtml(formatBackendDisplayLabel(session?.backendUrl || DEFAULT_BACKEND_URL))} ${escapeHtml(activeTab === "workspace" ? "连接与工作区" : "界面与帮助")}
${renderDetailTabs("settingsDetailTab", tabs)} ${activeTab === "workspace" ? `

当前连接

当前前端与后端的真实连接状态。

${escapeHtml(session?.account?.display_name || session?.account?.username || "未连接")}

${escapeHtml(session?.backendUrl || DEFAULT_BACKEND_URL)}

${escapeHtml(session ? "已自动连接" : "等待连接")} ${project ? `${escapeHtml(project.name)}` : ""}

自动连接说明

当前站点不会要求用户手输账号密码,而是直接向固定后端请求自动会话。

快捷入口

从设置页快速回到最常用的工作区。

项目与 Agent

如果你想切项目、看默认 Agent 或确认当前工作上下文,可以直接从这里回去。

${actionTag("去我的项目", "goto-intake")} ${actionTag("去 Agent", "goto-playbook")}

生产与跟踪

如果你更关心最近任务和跟踪动态,可以直接回到这两个工作页。

${actionTag("去生产中心", "goto-production")} ${actionTag("去跟踪账号", "goto-tracking")}
` : `

界面原则

帮助用户理解当前产品的使用方式。

首页优先看动作

首页只负责“今天先做什么”,深层信息回到各工作页处理。

单页只做一类决策

重页面已经收成页内 tab,避免一次看到太多层信息。

系统治理不混进用户页

管理员相关配置统一收口到管理员配置台。

帮助入口

用户先看到帮助和定位,不直接掉进系统控制面。

连接状态

如果页面看起来没有数据,先确认当前工作区是否已自动连接。

${actionTag("打开连接状态", "open-auth")}
${isSuperAdmin() ? `

管理员配置台

系统依赖、存储、平台 Agent 和运维审计都在管理员配置台里,不放在普通用户页。

${actionTag("去管理员配置台", "goto-admin-workbench")}
` : ""}
`}
` ); } function renderTopbar() { const workspaceStrong = document.querySelector(".workspace-switch strong"); const workspaceSpan = document.querySelector(".workspace-switch span"); const mobileWorkspaceProject = document.querySelector('[data-role="mobile-workspace-project"]'); const mobileWorkspacePlatforms = document.querySelector('[data-role="mobile-workspace-platforms"]'); const searchInput = document.querySelector(".search input"); const avatar = document.querySelector(".avatar"); const topPills = document.querySelectorAll(".top-pill"); const platforms = document.querySelector(".topbar-left .chip-row"); const project = getSelectedProject(); const currentPlatform = getCurrentPlatformValue(); const connectedLabel = appState.busy ? "同步中" : (appState.session ? "已连接" : "待连接"); if (workspaceStrong) { workspaceStrong.textContent = project?.name || (appState.session ? "已连接工作区" : "未连接工作区"); } if (workspaceSpan) { workspaceSpan.textContent = appState.dashboard ? `${safeArray(appState.dashboard.projects).length} 个项目 · ${safeArray(appState.dashboard.assistants).length} 个 Agent` : "连接后加载项目和 Agent"; } if (searchInput) { searchInput.value = ""; searchInput.placeholder = "搜项目、账号、内容、Agent"; } if (avatar) { avatar.textContent = initials(appState.me?.display_name || appState.me?.username || appState.session?.account?.display_name || "SF"); } if (topPills.length >= 3) { topPills[0].textContent = `项目 ${formatNumber(appState.dashboard?.projects?.length || 0)}`; topPills[1].textContent = `对标 ${formatNumber(appState.accounts.length)}`; topPills[2].textContent = `任务 ${formatNumber(appState.dashboard?.recent_jobs?.length || 0)}`; } if (mobileWorkspaceProject) { mobileWorkspaceProject.dataset.action = "open-dashboard-project-switcher"; mobileWorkspaceProject.innerHTML = ` 当前项目 ${escapeHtml(project?.name || (appState.session ? "已连接工作区" : "未连接工作区"))} `; } if (mobileWorkspacePlatforms) { mobileWorkspacePlatforms.innerHTML = getPlatformOptions().map((item) => ` `).join(""); } if (platforms) { platforms.innerHTML = [ `已接入平台`, ...getPlatformOptions().map((item) => ` ${escapeHtml(getPlatformShortLabel(item.value))} `) ].join(""); } } function syncRoleGatedNav() { const allowAdmin = isSuperAdmin(); document.querySelectorAll("[data-role-gate]").forEach((element) => { const gate = element.getAttribute("data-role-gate"); const visible = gate === "super_admin" ? allowAdmin : true; element.classList.toggle("hidden", !visible); element.hidden = !visible; }); if (!allowAdmin && appState.screen === "admin-workbench") { appState.screen = "dashboard"; } } function renderAll() { renderTopbar(); renderAuthUi(); syncRoleGatedNav(); screenMap.dashboard.innerHTML = renderDashboardScreen(); screenMap.intake.innerHTML = renderProjectsScreen(); screenMap.discovery.innerHTML = renderDiscoveryScreen(); screenMap.tracking.innerHTML = renderTrackingScreen(); screenMap.automation.innerHTML = renderAutomationScreen(); screenMap.owned.innerHTML = renderOwnedScreen(); screenMap.playbook.innerHTML = renderPlaybookScreen(); if (screenMap.strategy) { screenMap.strategy.innerHTML = renderStrategyScreen(); } screenMap.production.innerHTML = renderProductionScreen(); screenMap.review.innerHTML = renderReviewScreen(); screenMap.credits.innerHTML = renderCreditsScreen(); if (screenMap.settings) { screenMap.settings.innerHTML = renderSettingsScreen(); } if (screenMap["admin-workbench"]) { screenMap["admin-workbench"].innerHTML = renderAdminWorkbenchScreen(); } renderOneLinerUi(); setScreen(screenMap[appState.screen] ? appState.screen : "dashboard"); } async function createProject() { if (!appState.session) { openAuthModal(); return; } const existingProjects = safeArray(appState.dashboard?.projects); openActionModal({ title: "新建项目", description: "先把项目建起来,首页动作、主 Agent 和记忆都会跟着这个项目继续往下走。", submitLabel: "创建并进入项目", fields: [ { name: "projectGuide", label: "创建提示", type: "html", html: `

先建一个可继续推进的项目

推荐直接按你现在的业务目标来命名,例如某个平台增长、某个账号矩阵或某个内容专题。

当前已有 ${escapeHtml(formatNumber(existingProjects.length))} 个项目 创建后会自动切过去 主 Agent 会沿用这个项目上下文
` }, { name: "name", label: "项目名称", placeholder: "例如:创业 IP 增长实验室" }, { name: "description", label: "项目说明", type: "textarea", rows: 4, placeholder: "写一句这个项目主要解决什么问题、接下来准备推进什么" } ], onOpen: ({ fields }) => { fields.querySelector('[data-action-field="name"]')?.focus(); }, onSubmit: async (values) => { const name = String(values.name || "").trim(); if (!name) throw new Error("请填写项目名称"); setBusy(true, "正在创建项目..."); try { const project = await storyforgeFetch("/v2/projects", { method: "POST", body: { name, description: String(values.description || "").trim() } }); appState.selectedProjectId = project.id || appState.selectedProjectId; rememberAction("项目已创建", `已创建「${project.name || name}」,并切到这个项目继续推进。`, "green", project); await bootstrap(); } catch (error) { presentActionFailure(error, "创建项目失败"); throw error; } finally { setBusy(false, ""); } } }); } function openPreferredModelAction() { const models = getModelOptions(); const currentProfile = getCurrentModelProfile(); const currentId = currentProfile?.id || models[0]?.value || ""; const localCatalog = appState.localModelCatalog || {}; const gatewayModels = safeArray(localCatalog.models).map((item) => item.id).filter(Boolean); openActionModal({ title: "设置分析主模型", description: "导入分析、市场调研和风格学习都会优先使用这里设置的模型。", submitLabel: "保存模型", fields: [ { type: "html", label: "本机模型网关", html: `

${escapeHtml(localCatalog.reachable ? "网关在线" : "网关离线")}

${escapeHtml(currentProfile ? `当前主模型:${currentProfile.name} · ${currentProfile.model_name || "-"}` : `默认模型:${localCatalog.default_model || "GLM-5"}`)}

${gatewayModels.slice(0, 6).map((model) => `${escapeHtml(model)}`).join("") || `暂未读取到模型目录`} ${localCatalog.management_url ? `打开管理页` : ""}
` }, { name: "modelProfileId", label: "主模型", type: "select", value: currentId, options: models } ], onSubmit: async (values) => { if (!values.modelProfileId) throw new Error("请先选择一个模型"); await storyforgeFetch("/v2/me/preferences/analysis-model", { method: "POST", body: { model_profile_id: values.modelProfileId } }); rememberAction("主模型已更新", "新的分析主模型已经保存。", "green"); await bootstrap(); } }); } function rememberAction(title, summary, tone = "blue", payload = null) { appState.lastAction = { title, summary, tone, payload, createdAt: new Date().toISOString() }; } function getVersionIdentity(version = {}) { return String(version?.version_id || version?.id || "").trim(); } function isConfigurationVersionStale(runVersion, currentVersion) { const runIdentity = getVersionIdentity(runVersion); const currentIdentity = getVersionIdentity(currentVersion); if (runIdentity && currentIdentity) return runIdentity !== currentIdentity; const runNumber = Number(runVersion?.version_no || 0); const currentNumber = Number(currentVersion?.version_no || 0); return runNumber > 0 && currentNumber > 0 && runNumber !== currentNumber; } function extractGeneratedCopy(payload) { const raw = payload?.content || payload?.text || payload?.copy || payload?.result?.content || ""; return brief(raw, 2400); } function cacheGeneratedCopyResult(payload, options = {}) { const executionPayload = payload?.payload || payload || {}; const assistantId = executionPayload.assistant_id || options.assistantId || getSelectedAssistant()?.id || ""; const assistant = safeArray(appState.assistants).find((item) => item.id === assistantId) || getSelectedAssistant() || null; appState.lastGeneratedCopy = { assistantId: assistantId || "", assistantName: assistant?.name || options.assistantName || "当前 Agent", prompt: String(options.prompt || options.brief || executionPayload.brief || "").trim(), content: extractGeneratedCopy(executionPayload), usedDocuments: safeArray(executionPayload.used_documents).slice(0, 3) }; } function buildRecommendedActionAttrs(recommendedAction, landing = {}) { const action = recommendedAction && typeof recommendedAction === "object" ? recommendedAction : {}; const attrs = []; const landingAttrs = buildMainAgentLandingAttrs({ runId: landing.runId || "", screen: action.screen || landing.screen || "", title: landing.title || "", summary: action.summary || landing.summary || "" }); if (landingAttrs) attrs.push(landingAttrs); const attrMap = { job_id: "data-job-id", review_id: "data-review-id", platform: "data-platform", account_id: "data-account-id", tracked_account_id: "data-tracked-account-id", assistant_id: "data-assistant-id", source_id: "data-source-id", file_id: "data-file-id", incident_id: "data-incident-id", run_id: "data-run-id" }; Object.entries(attrMap).forEach(([key, attr]) => { const value = action[key]; const text = String(value ?? "").trim(); if (!text) return; attrs.push(`${attr}="${escapeHtml(text)}"`); }); return attrs.join(" "); } function renderLastActionCard() { if (!appState.lastAction) return ""; const payload = appState.lastAction.payload || {}; const recommendedAction = payload?.result?.recommended_action || payload?.recommended_action || null; const runId = payload?.id || payload?.run_id || ""; const actionAttrs = buildRecommendedActionAttrs(recommendedAction, { runId, screen: recommendedAction?.screen || "", title: appState.lastAction.title || "", summary: recommendedAction?.summary || appState.lastAction.summary || "" }); return `

最近动作

${escapeHtml(formatDateTime(appState.lastAction.createdAt))}
${escapeHtml(appState.lastAction.title)}

${escapeHtml(appState.lastAction.title)}

${escapeHtml(appState.lastAction.summary)}

${(runId || recommendedAction?.action) ? `
${runId ? `查看结果` : ""} ${recommendedAction?.action ? actionTag(recommendedAction.label || "回到对应页面", recommendedAction.action, actionAttrs) : ""}
` : ""}
`; } function getJobRecoveryCategory(job) { const sourceType = String(job?.source_type || "").toLowerCase(); const lineType = String(job?.line_type || "").toLowerCase(); if (sourceType === "text" || lineType === "analysis") return "analysis"; if (sourceType === "video_link") return "analysis"; if (sourceType === "content_source_sync") return "analysis"; if (sourceType === "upload_video") return "analysis"; if (lineType === "copy") return "copy"; if (lineType === "ai_video" || sourceType === "ai_video") return "ai_video"; if (lineType === "real_cut" || sourceType === "real_cut") return "real_cut"; if (lineType === "live_recorder" || sourceType === "live_recorder") return "recorder"; return ""; } function getQuotaSummaryForCategory(category) { const quota = appState.tenantQuota || {}; const usage = appState.tenantUsage || quota?.usage || {}; if (!quota || quota.enabled === false || !category) { return { enabled: true, blocked: false, reason: "", usage, quota }; } const categoryQuotaField = { analysis: "analysis_quota", copy: "copy_quota", ai_video: "ai_video_quota", real_cut: "real_cut_quota", recorder: "recorder_quota" }[category]; const categoryUsage = usage?.categories?.[category] || {}; const consumed = Number(categoryUsage.quantity || 0); const allowed = Number(quota[categoryQuotaField] || 0); if (allowed && consumed >= allowed) { return { enabled: true, blocked: true, reason: `当前租户本周期的 ${category} 配额已用完`, usage, quota }; } const budget = Number(quota.monthly_budget_cents || 0); const totalCost = Number(usage.total_cost_cents || 0); const categoryCost = { analysis: 6, copy: 3, ai_video: 30, real_cut: 20, recorder: 2 }[category] || 0; if (budget && totalCost + categoryCost > budget) { return { enabled: true, blocked: true, reason: "当前租户本周期预算不足,已阻止本次动作执行", usage, quota }; } if (quota.storage_over_limit && ["analysis", "ai_video", "real_cut"].includes(category)) { return { enabled: true, blocked: true, reason: "当前租户存储额度已满,已阻止继续产生大文件缓存", usage, quota }; } return { enabled: true, blocked: false, reason: "", usage, quota }; } function getJobRecoverability(job) { const status = String(job?.status || "").toLowerCase(); const sourceType = String(job?.source_type || "").toLowerCase(); const lineType = String(job?.line_type || "").toLowerCase(); const category = getJobRecoveryCategory(job); const quotaGuard = getQuotaSummaryForCategory(category); const sourceJobId = String(job?.artifacts?.source_job_id || job?.result?.source_job_id || job?.parent_job_id || "").trim(); const sourceAccountUrl = String(job?.artifacts?.source_account_url || job?.source_url || "").trim(); const uploadedPath = String(job?.artifacts?.uploaded_path || "").trim(); const cutvideoRequest = job?.artifacts?.cutvideo_request || {}; const base = { status, sourceType, lineType, category, quotaGuard, sourceJobId, sourceAccountUrl, uploadedPath, cutvideoRequest }; if (!job?.id) { return { ...base, state: "missing", label: "未加载", reason: "当前任务详情还没有加载完成。", recoverable: false, actionLabel: "刷新后再试", actionKey: "refresh-data" }; } if (["completed", "done", "succeeded"].includes(status)) { return { ...base, state: "completed", label: "已完成", reason: "这条任务已经完成,不需要恢复。", recoverable: false, actionLabel: "查看详情", actionKey: "open-job-detail" }; } if (status !== "failed") { return { ...base, state: "active", label: "运行中", reason: "这条任务还没有失败,先等待当前流程跑完。", recoverable: false, actionLabel: "继续观察", actionKey: "open-job-detail" }; } if (quotaGuard.blocked) { return { ...base, state: "blocked", label: "额度拦截", reason: quotaGuard.reason, recoverable: false, actionLabel: "先补额度", actionKey: "open-tenant-quota" }; } if (sourceType === "upload_video") { if (uploadedPath) { return { ...base, state: "recoverable", label: "可恢复", reason: "上传素材仍可复用,可以直接重新入队这条分析任务。", recoverable: true, actionLabel: "恢复上传分析", actionKey: "recover-job" }; } return { ...base, state: "manual", label: "需补素材", reason: uploadedPath ? "这条上传任务需要重新上传原始素材后再恢复。" : "这条任务缺少可复用的上传素材,需要先补回原文件。", recoverable: false, actionLabel: "重新上传", actionKey: "open-upload-video" }; } if (lineType === "real_cut") { if (!sourceJobId) { return { ...base, state: "manual", label: "缺少源任务", reason: "实拍剪辑缺少源任务,请先补回源任务后再重跑。", recoverable: false, actionLabel: "看源任务", actionKey: "open-job-detail" }; } return { ...base, state: "recoverable", label: "可恢复", reason: "可以基于源任务重新发起实拍剪辑。", recoverable: true, actionLabel: "恢复实拍剪辑", actionKey: "recover-job" }; } if (lineType === "ai_video") { if (!sourceJobId) { return { ...base, state: "manual", label: "缺少源任务", reason: "AI 视频缺少源任务,请先补回源任务后再重跑。", recoverable: false, actionLabel: "看源任务", actionKey: "open-job-detail" }; } return { ...base, state: "recoverable", label: "可恢复", reason: "可以基于源任务和当前 brief 重新发起 AI 视频。", recoverable: true, actionLabel: "恢复 AI 视频", actionKey: "recover-job" }; } if (sourceType === "content_source_sync") { if (!sourceAccountUrl) { return { ...base, state: "manual", label: "缺少主页", reason: "内容源同步缺少主页地址,请先补回主页后再同步。", recoverable: false, actionLabel: "去导入主页", actionKey: "open-import-homepage" }; } return { ...base, state: "recoverable", label: "可恢复", reason: "可以基于同一主页重新触发内容源同步。", recoverable: true, actionLabel: "恢复同步", actionKey: "recover-job" }; } if (sourceType === "text" || sourceType === "video_link") { const sourceValue = sourceType === "text" ? String(job?.artifacts?.input_text || job?.transcript_text || "").trim() : String(job?.source_url || job?.artifacts?.video_url || job?.artifacts?.source_url || "").trim(); if (!sourceValue) { return { ...base, state: "manual", label: "缺少输入", reason: sourceType === "text" ? "缺少原始文本,请先补回文本后再重跑。" : "缺少原始视频链接,请先补回链接后再重跑。", recoverable: false, actionLabel: "查看详情", actionKey: "open-job-detail" }; } return { ...base, state: "recoverable", label: "可恢复", reason: sourceType === "text" ? "可以直接用原始文本重新发起分析。" : "可以直接用原始视频链接重新发起分析。", recoverable: true, actionLabel: sourceType === "text" ? "恢复分析" : "恢复分析", actionKey: "recover-job" }; } return { ...base, state: "manual", label: "需站内处理", reason: "当前链路没有命中可自动恢复模板,先在站内补齐缺失的素材、源任务、主页或额度,再继续推进。", recoverable: false, actionLabel: "去生产中心", actionKey: "goto-production" }; } function getRecoverJobGuidance(job, recovery) { const jobId = String(job?.id || "").trim(); const jobTitle = String(job?.title || jobId || "失败任务").trim(); const sourceJobId = String(recovery?.sourceJobId || "").trim(); const jobDetailAction = jobId ? { label: "看任务详情", action: "open-job-detail", attrs: `data-job-id="${escapeHtml(jobId)}"` } : null; const sourceDetailAction = sourceJobId ? { label: "看源任务", action: "open-job-detail", attrs: `data-job-id="${escapeHtml(sourceJobId)}"` } : jobDetailAction; const productionAction = { label: "去生产中心", action: "goto-production", attrs: "" }; const quotaAction = { label: "去额度", action: "open-tenant-quota", attrs: "" }; const uploadAction = { label: "重新上传", action: "open-upload-video", attrs: "" }; const importHomepageAction = { label: "去导入主页", action: "open-import-homepage", attrs: "" }; const handoffAction = { label: "交给主 Agent", action: "handoff-to-main-agent", attrs: buildMainAgentHandoffAttrs({ sourceScreen: "production", sourceActionKey: "recover-job-handoff", intentKey: "custom", title: `处理失败任务 ${jobTitle}`, goal: "结合失败原因和当前任务上下文,给出下一步恢复建议", summary: "主 Agent 会先判断这条任务缺的是素材、额度还是源任务,再给出具体处理路径。", planSteps: ["读取失败任务详情", "判断缺失输入或依赖", "生成下一步恢复建议"] }) }; const steps = []; const addStep = (title, body, label, action, tone = "orange") => { steps.push({ title, body, label, action, tone }); }; let primaryAction = sourceDetailAction || productionAction; const secondaryActions = []; const pushSecondaryAction = (action) => { if (!action) return; if (primaryAction && primaryAction.action === action.action && primaryAction.attrs === action.attrs) return; if (secondaryActions.some((item) => item.action === action.action && item.attrs === action.attrs)) return; secondaryActions.push(action); }; let heading = "先补链路再继续"; let summary = recovery?.reason || "当前链路没有命中自动恢复模板,先补齐缺失项后再继续。"; if (recovery?.state === "blocked") { heading = "先补额度再恢复"; summary = recovery.reason || "当前任务被额度拦截,先补完额度后再回到生产中心重试。"; addStep("打开额度面板", "查看本周期剩余额度和拦截原因。", "去额度", quotaAction, "orange"); addStep("补完额度后回到生产中心", "额度恢复后,再回到生产中心重新发起这条任务。", "去生产中心", productionAction, "blue"); pushSecondaryAction(productionAction); pushSecondaryAction(handoffAction); primaryAction = quotaAction; return { heading, summary, primaryAction: quotaAction, secondaryActions, steps, categoryLabel: "额度拦截" }; } if (recovery?.sourceType === "upload_video") { heading = "先补上传素材"; summary = recovery.reason || "上传素材缺失时,先重新上传再继续恢复。"; addStep("重新上传原始素材", "补回原文件后,这条任务才能再次入队。", "重新上传", uploadAction, "orange"); addStep("如果不确定缺什么,先看任务详情", "任务详情里会保留失败原因和素材信息。", "看任务详情", jobDetailAction, "blue"); pushSecondaryAction(jobDetailAction); pushSecondaryAction(handoffAction); primaryAction = uploadAction; return { heading, summary, primaryAction: uploadAction, secondaryActions, steps, categoryLabel: "素材缺失" }; } if (recovery?.lineType === "real_cut") { heading = "先补实拍源任务"; summary = recovery.reason || "实拍剪辑缺少源任务时,先补回源任务再重跑。"; addStep("打开源任务", "确认源任务是否已完成,或者是否需要先补回源任务。", "看源任务", sourceDetailAction, "orange"); addStep("补完源任务后再发起实拍剪辑", "源任务恢复后,再回到生产中心继续处理。", "去生产中心", productionAction, "blue"); pushSecondaryAction(productionAction); pushSecondaryAction(handoffAction); primaryAction = sourceDetailAction; return { heading, summary, primaryAction: sourceDetailAction, secondaryActions, steps, categoryLabel: "实拍剪辑" }; } if (recovery?.lineType === "ai_video") { heading = "先补 AI 视频源任务"; summary = recovery.reason || "AI 视频缺少源任务时,先补回源任务再重跑。"; addStep("打开源任务", "确认源任务和当前 brief 是否需要一起补齐。", "看源任务", sourceDetailAction, "orange"); addStep("源任务补齐后再继续 AI 视频", "回到生产中心重新发起这条任务。", "去生产中心", productionAction, "blue"); pushSecondaryAction(productionAction); pushSecondaryAction(handoffAction); primaryAction = sourceDetailAction; return { heading, summary, primaryAction: sourceDetailAction, secondaryActions, steps, categoryLabel: "AI 视频" }; } if (recovery?.sourceType === "content_source_sync") { heading = "先补主页再同步"; summary = recovery.reason || "内容源同步缺少主页时,先补回主页再触发同步。"; addStep("去导入主页", "先把主页地址补回,再重新触发内容源同步。", "去导入主页", importHomepageAction, "orange"); addStep("如果主页已存在,直接看任务详情", "任务详情里会保留失败时的输入和状态。", "看任务详情", jobDetailAction, "blue"); pushSecondaryAction(jobDetailAction); pushSecondaryAction(handoffAction); primaryAction = importHomepageAction; return { heading, summary, primaryAction: importHomepageAction, secondaryActions, steps, categoryLabel: "主页缺失" }; } if (recovery?.sourceType === "text" || recovery?.sourceType === "video_link") { heading = "先补输入再恢复"; summary = recovery.reason || "原始输入缺失时,先补回输入再继续恢复。"; addStep("打开任务详情补输入", "把原始文本或视频链接补回后,再重新发起恢复。", "看任务详情", jobDetailAction, "orange"); addStep("如果想让系统代办,交给主 Agent", "主 Agent 会先判断应该补哪一段输入,再给出下一步。", "交给主 Agent", handoffAction, "blue"); pushSecondaryAction(handoffAction); pushSecondaryAction(productionAction); primaryAction = jobDetailAction || productionAction; return { heading, summary, primaryAction: jobDetailAction || productionAction, secondaryActions, steps, categoryLabel: "输入缺失" }; } addStep("打开任务详情", "先确认这条失败任务缺的是素材、源任务、主页还是额度。", "看任务详情", jobDetailAction, "orange"); addStep("回到生产中心继续处理", "补完信息后回到生产中心重新判断是否能恢复。", "去生产中心", productionAction, "blue"); addStep("交给主 Agent", "如果不确定该补哪一步,可以让主 Agent 先给出处理建议。", "交给主 Agent", handoffAction, "blue"); pushSecondaryAction(jobDetailAction); pushSecondaryAction(handoffAction); return { heading, summary, primaryAction: primaryAction || productionAction, secondaryActions, steps, categoryLabel: "站内处理" }; } function renderRecoverJobGuidanceHtml(job, recovery, guidance) { const primaryAction = guidance?.primaryAction || null; const secondaryActions = safeArray(guidance?.secondaryActions); const steps = safeArray(guidance?.steps); return `

${escapeHtml(guidance?.heading || "失败任务处理建议")}

${escapeHtml(guidance?.summary || recovery?.reason || "先补齐缺失项,再继续推进。")}

${escapeHtml(recovery?.label || "需站内处理")} ${escapeHtml(guidance?.categoryLabel || recovery?.lineType || recovery?.sourceType || "manual")} ${primaryAction ? actionTag(primaryAction.label, primaryAction.action, primaryAction.attrs) : ""} ${secondaryActions.map((item) => actionTag(item.label, item.action, item.attrs)).join("")}
${steps.map((step) => `

${escapeHtml(step.title)}

${escapeHtml(step.body)}

${escapeHtml(step.label)} ${step.action ? actionTag(step.action.label, step.action.action, step.action.attrs) : ""}
`).join("")}
`; } function getJobRecoveryRequest(job) { const recovery = getJobRecoverability(job); if (!recovery.recoverable) { throw new Error(recovery.reason || "当前任务需要人工处理后再继续"); } const projectId = job?.project_id || appState.selectedProjectId || ""; const assistantId = job?.assistant_id || ""; const knowledgeBaseId = job?.knowledge_base_id || ""; const analysisModelProfileId = job?.analysis_model_profile_id || ""; const title = String(job?.title || "任务").trim(); if (job?.source_type === "text") { return { endpoint: "/v2/explore/text", body: { project_id: projectId, knowledge_base_id: knowledgeBaseId, assistant_id: assistantId, analysis_model_profile_id: analysisModelProfileId, title, content: String(job?.artifacts?.input_text || job?.transcript_text || "").trim() }, reason: "基于原始文本重新发起分析" }; } if (job?.source_type === "video_link") { return { endpoint: "/v2/explore/video-link", body: { project_id: projectId, knowledge_base_id: knowledgeBaseId, assistant_id: assistantId, analysis_model_profile_id: analysisModelProfileId, title, language: job?.language || "auto", video_url: String(job?.source_url || job?.artifacts?.video_url || job?.artifacts?.source_url || "").trim() }, reason: "基于原始视频链接重新发起分析" }; } if (job?.source_type === "content_source_sync") { const sourceUrl = String(job?.artifacts?.source_account_url || job?.source_url || "").trim(); return { endpoint: "/v2/pipelines/content-source-sync", body: { project_id: projectId, knowledge_base_id: knowledgeBaseId, assistant_id: assistantId, analysis_model_profile_id: analysisModelProfileId, content_source_id: String(job?.content_source_id || ""), platform: String(job?.artifacts?.platform || ""), handle: String(job?.artifacts?.handle || ""), source_url: sourceUrl, title, language: job?.language || "auto", max_items: Number(job?.artifacts?.max_items || 5), skip_existing: Boolean(job?.artifacts?.skip_existing !== false), auto_trigger_analysis: Boolean(job?.artifacts?.auto_trigger_analysis !== false) }, reason: "基于同一主页重新触发同步" }; } if (job?.line_type === "real_cut" || job?.source_type === "real_cut") { const request = job?.artifacts?.cutvideo_request || {}; const sourceJobId = String(job?.artifacts?.source_job_id || job?.parent_job_id || "").trim(); return { endpoint: "/v2/pipelines/real-cut", body: { project_id: projectId, title, source_job_id: sourceJobId, base_config: String(request.base_config || ""), objective: String(request.objective || ""), target_duration_sec: Number(request.target_duration_sec || 60), target_aspect_ratio: String(request.target_aspect_ratio || "9:16"), ideal_segment_duration_sec: Number(request.ideal_segment_duration_sec || 8), max_segment_duration_sec: Number(request.max_segment_duration_sec || 20), transcript_backend: String(request.transcript_backend || "auto"), transcript_device: String(request.transcript_device || ""), review_enabled: Boolean(request.review_enabled), dry_run: Boolean(request.dry_run) }, reason: "基于源任务重新发起实拍剪辑" }; } if (job?.line_type === "ai_video" || job?.source_type === "ai_video") { const sourceJobId = String(job?.artifacts?.source_job_id || job?.result?.source_job_id || job?.parent_job_id || "").trim(); return { endpoint: "/v2/pipelines/ai-video", body: { project_id: projectId, assistant_id: assistantId, knowledge_base_id: knowledgeBaseId, source_job_id: sourceJobId, title, brief: String(job?.artifacts?.brief || job?.result?.brief || job?.style_summary || job?.transcript_text || "").trim(), style: String(job?.artifacts?.style || "realistic"), shots: Number(job?.artifacts?.shots || 4), duration: Number(job?.artifacts?.duration || 5), image_provider: String(job?.artifacts?.image_provider || ""), image_model: String(job?.artifacts?.image_model || ""), video_provider: String(job?.artifacts?.video_provider || ""), video_model: String(job?.artifacts?.video_model || ""), aspect_ratio: String(job?.artifacts?.aspect_ratio || "9:16") }, reason: "基于源任务重新发起 AI 视频" }; } throw new Error("当前任务需要人工处理后再继续"); } async function recoverJobAction(jobId, options = {}) { const sourceJob = safeArray(appState.dashboard?.recent_jobs).find((item) => item.id === jobId) || (appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null); const job = options.job || sourceJob || await storyforgeFetch(`/v2/explore/jobs/${encodeURIComponent(jobId)}`).catch(() => null); if (!job) { throw new Error("没有找到这条任务"); } const recovery = getJobRecoverability(job); if (!recovery.recoverable) { throw new Error(recovery.reason || "当前任务需要人工处理后再继续"); } try { const retried = await storyforgeFetch(`/v2/explore/jobs/${encodeURIComponent(job.id)}/retry`, { method: "POST" }); const retriedJob = retried?.job || retried; recordRecoveryEvent({ job_id: job.id, job_title: job.title, job_line_type: job.line_type, job_source_type: job.source_type, job_status: job.status, action_key: "recover-job", mode: options.mode || "single", summary: `${job.title || job.id} 已重新入队`, reason: "通过任务重试接口恢复", target_job_id: retriedJob?.id || job.id, target_job_title: retriedJob?.title || job.title || "", target_status: retriedJob?.status || "", result_label: "已重新入队", result_reason: recovery.reason, user_feedback: String(options.user_feedback || "") }); return { created: retriedJob, source: job, request: { endpoint: `/v2/explore/jobs/${encodeURIComponent(job.id)}/retry`, body: {}, reason: "通过任务重试接口恢复" } }; } catch (error) { if (!isMissingBackendCapability(error)) { throw error; } if (job?.source_type === "upload_video") { throw new Error("当前实例没有开放上传任务重试接口,暂时无法自动恢复已上传素材。"); } } const request = getJobRecoveryRequest(job); const payload = await storyforgeFetch(request.endpoint, { method: "POST", body: request.body }); const created = payload?.job || payload?.result?.job || payload; const createdJobId = created?.id || created?.job_id || ""; recordRecoveryEvent({ job_id: job.id, job_title: job.title, job_line_type: job.line_type, job_source_type: job.source_type, job_status: job.status, action_key: "recover-job", mode: options.mode || "single", summary: `${job.title || job.id} 已重新发起`, reason: request.reason, target_job_id: createdJobId, target_job_title: created?.title || created?.job?.title || "", target_status: created?.status || created?.job?.status || "", result_label: "已创建恢复任务", result_reason: request.reason, user_feedback: String(options.user_feedback || "") }); return { created, source: job, request }; } function getRecoverableFailedJobs() { return safeArray(appState.dashboard?.recent_jobs) .filter((job) => String(job?.status || "").toLowerCase() === "failed") .map((job) => ({ job, recovery: getJobRecoverability(job) })) .sort((left, right) => compareDateDesc(left.job.updated_at || left.job.created_at, right.job.updated_at || right.job.created_at)); } function renderRecoveryHistoryPanel() { const records = getRecoveryRecords(); const recentRecords = records.slice(0, 6); return `

最近恢复记录

本地记录最近一次恢复动作,方便量产阶段回看恢复是否真的跑通。
${escapeHtml(formatNumber(records.length))} 条
${recentRecords.map((item) => `

${escapeHtml(item.job_title || "恢复记录")}

${escapeHtml(item.summary || item.reason || "恢复动作已完成。")}

${escapeHtml(item.mode || "single")} ${escapeHtml(item.target_job_id ? "已创建恢复任务" : "待确认结果")} ${item.job_line_type ? `${escapeHtml(item.job_line_type)}` : ""} ${item.created_at ? `${escapeHtml(formatDateTime(item.created_at))}` : ""} ${item.target_job_id ? `看恢复任务` : ""}
`).join("") || `

还没有恢复记录

你执行过的恢复动作会出现在这里。

`}
`; } function renderQuotaBlockingNotice() { const quota = appState.tenantQuota || {}; const usage = appState.tenantUsage || quota?.usage || {}; const labels = [ { key: "analysis", label: "分析" }, { key: "copy", label: "文案" }, { key: "ai_video", label: "AI 视频" }, { key: "real_cut", label: "实拍剪辑" }, { key: "recorder", label: "录制" } ]; const blocked = labels .map((item) => { const guard = getQuotaSummaryForCategory(item.key); return { ...item, guard }; }) .filter((item) => item.guard.blocked); const storageBlocked = Boolean(quota?.storage_over_limit); if (!blocked.length && !storageBlocked) return ""; return `

租户拦截提示

当前租户已经触发了硬拦截,前端会先提示原因,再引导你补额度或清理存储。
已拦截

${escapeHtml(storageBlocked ? "存储额度已满" : "动作额度已满")}

${escapeHtml(storageBlocked ? "当前租户存储已经达到上限,建议先清理旧产物或提升存储配额。" : blocked.map((item) => item.guard.reason).join(";"))}

${blocked.map((item) => `${escapeHtml(item.label)}已满`).join("")} ${storageBlocked ? `存储超限` : ""} 调整额度
预算 ${escapeHtml(formatNumber((quota?.monthly_budget_cents || 0) / 100))} 元 已用 ${escapeHtml(formatNumber((usage?.total_cost_cents || 0) / 100))} 元
存储 ${escapeHtml(formatBytes(usage?.storage_bytes || 0))} ${escapeHtml(quota?.storage_limit_bytes ? `上限 ${formatBytes(quota.storage_limit_bytes)}` : "未设置上限")}
分析 ${escapeHtml(formatNumber(usage?.categories?.analysis?.quantity || 0))} ${escapeHtml(`上限 ${formatNumber(quota?.analysis_quota || 0)}`)}
AI 视频 ${escapeHtml(formatNumber(usage?.categories?.ai_video?.quantity || 0))} ${escapeHtml(`上限 ${formatNumber(quota?.ai_video_quota || 0)}`)}
`; } function renderLastJobDetailCard() { const detail = appState.lastJobDetail; if (!detail?.job) return ""; const previewLinks = getJobPreviewLinks(detail.job); const recovery = getJobRecoverability(detail.job); return `

最近任务详情

${escapeHtml(formatDateTime(detail.job.created_at))}
${escapeHtml(detail.job.status || "-")} ${escapeHtml(recovery.label)}

${escapeHtml(detail.job.title || detail.job.id)}

${escapeHtml(brief(detail.job.style_summary || detail.job.transcript_text || detail.job.error || "暂无摘要", 120))}

${escapeHtml(detail.job.line_type || "-")} ${detail.job.status === "failed" ? `${escapeHtml(recovery.reason)}` : ""} ${detail.job.status === "completed" ? actionTag("写复盘", "direct-review-draft", `data-job-id="${escapeHtml(detail.job.id)}"`) : ""} ${detail.job.status === "failed" ? actionTag( recovery.actionLabel, recovery.recoverable ? "recover-job" : recovery.actionKey, `data-job-id="${escapeHtml(detail.job.id)}"`, { disabledReason: recovery.recoverable ? "" : recovery.reason, title: recovery.reason } ) : ""} ${canDeriveAiVideo(detail.job) ? renderPipelineJobTag("aiVideo", detail.job, "做 AI 视频") : ""} ${canDeriveRealCut(detail.job) ? renderPipelineJobTag("realCut", detail.job, "做实拍剪辑") : ""} ${actionTag("看详情", "open-job-detail", `data-job-id="${escapeHtml(detail.job.id)}"`)}
${detail.job.status === "failed" ? `

恢复判断

${escapeHtml(recovery.reason)}

${escapeHtml(recovery.actionLabel)} ${recovery.sourceJobId ? `源任务 ${escapeHtml(brief(recovery.sourceJobId, 12))}` : ""} ${recovery.quotaGuard?.blocked ? `额度拦截` : ""}
` : ""} ${previewLinks.length ? `
${previewLinks.slice(0, 3).map((item) => `

${escapeHtml(item.label.replace(/^result\./, "").replace(/^artifacts\./, ""))}

${escapeHtml(item.url)}

`).join("")}
` : ""}
`; } function requireSelectedProject() { const project = getSelectedProject(); if (!project) throw new Error("请先创建项目"); return project; } function requireSelectedAssistant() { const assistant = getSelectedAssistant(); if (!assistant) throw new Error("请先创建 Agent"); return assistant; } function requireSelectedAccountRow() { const account = getSelectedAccount(); if (!account) throw new Error("请先在“找对标”里选中一个账号"); return account; } function openImportHomepageAction() { const project = requireSelectedProject(); const kb = getProjectKnowledgeBases(project.id)[0]; const assistants = getAssistantOptions(project.id); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "douyin"); const titlePlaceholder = recommendManualIntakeTitle(project, defaultPlatform, "主页对标"); openActionModal({ title: "导入主页并同步", description: "适合抖音 / 小红书 / B站 / 快手 / 视频号主页。先建内容源,再触发同步与分析。", submitLabel: "开始同步", onOpen: ({ fields }) => { bindManualIntakeTitleRecommendation(fields, "主页对标", { defaultPlatform: "douyin" }); bindActionContextRecommendation(fields, { defaultAssistantId }); }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, { name: "platform", label: "平台", type: "select", value: defaultPlatform, options: getPlatformOptions() }, { name: "title", label: "标题", placeholder: titlePlaceholder }, { name: "handle", label: "账号名 / handle", placeholder: "可选" }, { name: "sourceUrl", label: "主页链接", type: "url", placeholder: "https://..." }, { name: "assistantId", label: "绑定 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, { name: "maxItems", label: "最多同步作品数", type: "number", value: 5, min: 1, max: 20 } ], onSubmit: async (values) => { if (!values.sourceUrl?.trim()) throw new Error("请填写主页链接"); const projectId = values.projectId || project.id; const platform = normalizePlatformValue(values.platform, "douyin"); await runDirectWorkbenchAction("import-homepage", { projectId, platform, payload: { source_url: values.sourceUrl.trim(), title: values.title || values.handle || "主页对标", handle: values.handle || "", assistant_id: values.assistantId || "", knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || kb?.id || "", max_items: Number(values.maxItems || 5), skip_existing: true, auto_trigger_analysis: true }, busyLabel: "正在导入主页并同步...", errorTitle: "导入主页失败" }); } }); } function openImportSelectedAccountAction() { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const currentSources = getCurrentProjectSourcesForAccount(account, project.id); const currentSource = currentSources[0]; const kb = getProjectKnowledgeBases(project.id)[0]; openActionModal({ title: currentSource ? "继续同步当前对标" : "导入当前对标", description: currentSource ? "当前项目里已经有这个对标账号,继续触发同步并可切换绑定 Agent。" : "把当前选中的对标账号加入项目,并绑定 Agent 进入持续同步。", submitLabel: currentSource ? "继续同步" : "导入并同步", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(currentSource?.platform || platform), options: getPlatformOptions() }, { name: "title", label: "内容源标题", value: currentSource?.title || `${getAccountName(account)} 对标主页` }, { name: "handle", label: "账号标识", value: currentSource?.handle || getAccountHandle(account) || "" }, { name: "sourceUrl", label: "主页链接", type: "url", value: currentSource?.source_url || getAccountProfileUrl(account) || "", placeholder: "https://..." }, { name: "assistantId", label: "绑定 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, { name: "maxItems", label: "最多同步作品数", type: "number", value: Number(currentSource?.metadata?.max_items || 6), min: 1, max: 20 }, { name: "skipExisting", label: "跳过已存在作品", type: "checkbox", value: true }, { name: "autoAnalyze", label: "同步后自动分析", type: "checkbox", value: true } ], onOpen: ({ fields }) => { bindActionContextRecommendation(fields, { defaultAssistantId }); }, onSubmit: async (values) => { if (!values.sourceUrl?.trim()) throw new Error("请先填写主页链接"); const projectId = values.projectId || project.id; const platform = normalizePlatformValue(values.platform, "douyin"); 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 ? "继续同步当前对标失败" : "导入当前对标失败" }); } }); } function openTrackSelectedAccountAction() { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const trackedItem = safeArray(appState.trackingAccounts).find((item) => item.tracked_account_id === account.id); const defaultAssistantId = trackedItem?.assistant_id || getSelectedAssistant()?.id || assistants[0]?.value || ""; openActionModal({ title: trackedItem ? "更新跟踪账号" : "加入跟踪", description: trackedItem ? "这个账号已经在跟踪中,可以切换负责 Agent 或补充备注。" : "把当前对标账号加入每日跟踪,系统会自动生成更新日报。", submitLabel: trackedItem ? "保存跟踪" : "开始跟踪", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "accountName", label: "账号", type: "html", html: `
${escapeHtml(getAccountName(account) || "未命名账号")}

${escapeHtml(getAccountProfileUrl(account) || account.signature || "")}

` }, { name: "assistantId", label: "负责 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "先不绑定" }, ...assistants] }, { name: "note", label: "跟踪备注", value: trackedItem?.note || "", placeholder: "例如:重点观察开头结构、成交句式和更新频率" } ], onOpen: ({ fields }) => { bindActionContextRecommendation(fields, { defaultAssistantId, projectField: "" }); }, onSubmit: async (values) => { 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 ? "更新跟踪失败" : "加入跟踪失败" }); } }); } function openImportVideoLinkAction() { const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "douyin"); const titlePlaceholder = recommendManualIntakeTitle(project, defaultPlatform, "单条作品"); openActionModal({ title: "导入作品链接", description: "直接把单条视频链接送进分析链。", submitLabel: "开始分析", onOpen: ({ fields }) => { bindManualIntakeTitleRecommendation(fields, "单条作品", { defaultPlatform: "douyin" }); bindActionContextRecommendation(fields, { defaultAssistantId }); }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, { name: "title", label: "标题", placeholder: titlePlaceholder }, { name: "videoUrl", label: "作品链接", type: "url", placeholder: "https://..." }, { name: "assistantId", label: "绑定 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, { name: "language", label: "语言", type: "select", value: "auto", options: [{ value: "auto", label: "自动" }, { value: "zh-CN", label: "中文" }] } ], onSubmit: async (values) => { if (!values.videoUrl?.trim()) throw new Error("请填写作品链接"); const projectId = values.projectId || project.id; await runDirectWorkbenchAction("import-video-link", { projectId, payload: { video_url: values.videoUrl.trim(), title: values.title || "", knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || "", assistant_id: values.assistantId || "", language: values.language || "auto" }, busyLabel: "正在导入作品链接并分析...", errorTitle: "导入作品链接失败" }); } }); } function openImportTextAction() { const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "douyin"); const titlePlaceholder = recommendManualIntakeTitle(project, defaultPlatform, "文本素材"); openActionModal({ title: "导入文本素材", description: "把口播稿、拆解稿或灵感文本直接送进知识与分析链。", submitLabel: "开始分析", onOpen: ({ fields }) => { bindManualIntakeTitleRecommendation(fields, "文本素材", { defaultPlatform: "douyin" }); bindActionContextRecommendation(fields, { defaultAssistantId }); }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, { name: "title", label: "标题", placeholder: titlePlaceholder }, { name: "content", label: "正文", type: "textarea", rows: 8, placeholder: "粘贴需要分析的文本" }, { name: "assistantId", label: "绑定 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] } ], onSubmit: async (values) => { if (!values.title?.trim()) throw new Error("请填写标题"); if (!values.content?.trim()) throw new Error("请填写正文"); const projectId = values.projectId || project.id; await runDirectWorkbenchAction("import-text", { projectId, payload: { title: values.title.trim(), content: values.content.trim(), knowledge_base_id: getProjectKnowledgeBases(projectId)[0]?.id || "", assistant_id: values.assistantId || "" }, busyLabel: "正在导入文本并分析...", errorTitle: "导入文本失败" }); } }); } function openUploadVideoAction() { const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "douyin"); const titlePlaceholder = recommendManualIntakeTitle(project, defaultPlatform, "本地视频"); openActionModal({ title: "上传本地视频", description: "上传本地素材,直接进入分析链。", submitLabel: "上传并分析", onOpen: ({ fields }) => { bindManualIntakeTitleRecommendation(fields, "本地视频", { defaultPlatform: "douyin" }); bindActionContextRecommendation(fields, { defaultAssistantId }); }, fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, { name: "title", label: "标题", placeholder: titlePlaceholder }, { name: "assistantId", label: "绑定 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, { name: "file", label: "本地视频", type: "file", accept: ".mp4,.mov,.m4v,.avi,.mkv,.webm" } ], onSubmit: async (values) => { if (!values.file) throw new Error("请先选择本地视频"); const projectId = values.projectId || project.id; const form = new FormData(); form.append("file", values.file); form.append("title", values.title || ""); form.append("project_id", projectId); form.append("knowledge_base_id", getProjectKnowledgeBases(projectId)[0]?.id || ""); form.append("assistant_id", values.assistantId || ""); const job = await storyforgeFetch("/v2/explore/upload-video", { method: "POST", body: form }); rememberAction("上传分析已启动", `已上传素材并创建任务 ${job.title || job.id}。`, "blue", job); await bootstrap(); if (job?.id) { openJobDetailAction(job.id); } } }); } function focusPlaybookOneLinerWorkspace(anchorId = "oneliner-profile-anchor") { appState.playbookDetailTab = "workspace"; setScreen("playbook"); setTimeout(() => { document.getElementById(anchorId)?.scrollIntoView({ block: "start", behavior: "smooth" }); }, 0); } function openOneLinerProfileAction() { const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const profile = appState.onelinerProfile || {}; openActionModal({ title: "配置 OneLiner", description: "绑定总控主 Agent 的默认平台、长期目标和默认执行 Agent。", submitLabel: "保存配置", fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(profile, "你还没有发布过 OneLiner 主配置,当前会沿用默认初始化版本。") }, { name: "assistantId", label: "默认执行 Agent", type: "select", value: profile.assistant_id || getSelectedAssistant()?.id || assistants[0]?.value || "", options: [{ value: "", label: "先不绑定" }, ...assistants] }, { name: "displayName", label: "显示名", value: profile.display_name || "OneLiner", placeholder: "例如:增长总控 OneLiner" }, { name: "defaultPlatform", label: "默认平台", type: "select", value: normalizePlatformValue(profile.default_platform || getPreferredPlatform(), "douyin"), options: getPlatformOptions() }, { name: "longTermGoal", label: "长期目标", type: "textarea", rows: 4, value: profile.long_term_goal || "", placeholder: "例如:围绕创业 IP 做跨平台增长与成交转化" }, { name: "notes", label: "补充说明", type: "textarea", rows: 4, value: profile.notes || "", placeholder: "例如:前端没产品化的需求先由 OneLiner 承接,不允许直接改核心代码" }, { name: "reason", label: "变更原因", type: "textarea", rows: 3, value: "", placeholder: "例如:本轮主 Agent 默认改为围绕抖音增长执行" } ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/oneliner/profile", { method: "PUT", body: { project_id: project.id, assistant_id: values.assistantId || "", display_name: values.displayName || "OneLiner", default_platform: values.defaultPlatform || "douyin", long_term_goal: values.longTermGoal || "", notes: values.notes || "", reason: values.reason || "", config: { chat_only_for_unreleased_ui: true, commercial_ready: true, tenant_isolation_required: true } } }); appState.onelinerProfile = saved; await loadAgentControlSurfaces(project.id); rememberAction("OneLiner 已保存", `已更新 OneLiner「${saved.display_name || "OneLiner"}」配置,当前版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); focusPlaybookOneLinerWorkspace("oneliner-profile-anchor"); } }); } function resolvePreferredVersionId(history, preferredVersionId = "") { const items = safeArray(history?.items || history); const normalizedPreferred = String(preferredVersionId || "").trim(); if (normalizedPreferred && items.some((item) => String(item?.id || "") === normalizedPreferred)) { return normalizedPreferred; } return String(items[0]?.id || ""); } async function openOneLinerProfileHistoryAction(preferredVersionId = "") { const project = requireSelectedProject(); const history = await loadPolicyVersions(`/v2/oneliner/profile/versions?project_id=${encodeURIComponent(project.id)}`); const auditsPayload = await storyforgeFetch(`/v2/oneliner/profile/audits?project_id=${encodeURIComponent(project.id)}`).catch(() => ({ items: [] })); const audits = safeArray(auditsPayload?.items || auditsPayload); const selectedVersionId = resolvePreferredVersionId(history, preferredVersionId); openActionModal({ title: "OneLiner 主配置历史", description: "回看主 Agent 核心配置的历史版本、变更原因和回滚记录。", submitLabel: "回滚到这个版本", hideSubmit: !selectedVersionId, fields: [ { type: "html", label: "当前配置状态", html: renderPolicyVersionSummary(appState.onelinerProfile || {}, "当前项目的 OneLiner 还没有历史版本。") }, ...( selectedVersionId ? [ { name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: buildPolicyVersionOptions(history) }, { name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:回到上一个稳定版本,继续沿用既有执行节奏" } ] : [] ), { type: "html", label: "历史版本", html: `
${renderPolicyVersionsHtml(history.items, "OneLiner 主配置还没有历史版本。")}
` }, { type: "html", label: "最近审计", html: `
${renderPolicyAuditFeed(audits, "还没有 OneLiner 主配置变更记录。")}
` } ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/oneliner/profile/rollback", { method: "POST", body: { project_id: project.id, version_id: values.versionId, reason: values.reason || "" } }); appState.onelinerProfile = saved; await loadAgentControlSurfaces(project.id); rememberAction("OneLiner 已回滚", `已回滚到版本 ${saved.current_version?.version_no || "指定版本"}。`, "green", saved); renderAll(); focusPlaybookOneLinerWorkspace("oneliner-profile-anchor"); } }); } function parsePolicyJsonField(rawValue, label = "策略 JSON") { const text = String(rawValue || "").trim(); if (!text) return {}; try { const parsed = JSON.parse(text); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error(`${label} 必须是 JSON 对象`); } return parsed; } catch (error) { throw new Error(`${label} 格式不正确:${error.message}`); } } function renderPolicyVersionSummary(bundle, emptyText) { if (!bundle?.current_version) { return `

还没有已发布版本

${escapeHtml(emptyText)}

`; } return `

${escapeHtml(bundle.current_version.title || bundle.scope?.title || "当前版本")}

${escapeHtml(bundle.current_version.summary || "当前版本还没有补摘要。")}

版本 ${escapeHtml(formatNumber(bundle.current_version.version_no || 0))} 历史 ${escapeHtml(formatNumber(bundle.versions?.count || 0))} ${bundle.effectivity?.effect_mode ? `${escapeHtml(bundle.effectivity.effect_mode)}` : ""}
`; } function getAdminGovernanceDirectoryItems() { return safeArray(appState.adminGovernanceDirectory); } function findAdminGovernanceDirectoryItem(targetUserId) { return getAdminGovernanceDirectoryItems().find((item) => item.id === targetUserId) || null; } function getAdminOverrideTargetState() { const directoryItems = getAdminGovernanceDirectoryItems(); const existing = appState.adminOverrideTarget || {}; const targetUserId = String(existing.targetUserId || existing.target_user_id || directoryItems[0]?.id || ""); const targetUser = findAdminGovernanceDirectoryItem(targetUserId) || directoryItems[0] || null; const targetProjects = safeArray(targetUser?.projects); const targetProjectId = String(existing.targetProjectId || existing.target_project_id || targetProjects[0]?.id || ""); return { targetUserId, targetProjectId, platform: normalizePlatformValue(existing.platform || getPreferredPlatform(), "douyin") }; } function formatAdminGovernanceTargetLabel(target) { const directoryItem = findAdminGovernanceDirectoryItem(target?.targetUserId || target?.target_user_id || ""); const project = safeArray(directoryItem?.projects).find((item) => item.id === (target?.targetProjectId || target?.target_project_id || "")); const userLabel = directoryItem ? `${directoryItem.display_name || directoryItem.username || directoryItem.id}${directoryItem.role ? ` · ${directoryItem.role}` : ""}` : "未选择目标"; const projectLabel = project ? project.name || project.id : "默认用户全局"; return `${userLabel} / ${projectLabel}`; } function getAdminGovernanceDirectoryUserOptions() { return getAdminGovernanceDirectoryItems().map((item) => ({ value: item.id, label: `${item.display_name || item.username || item.id}${item.project_count ? ` · ${formatNumber(item.project_count)} 项目` : ""}` })); } function getAdminGovernanceDirectoryProjectOptions(targetUserId) { const directoryItem = findAdminGovernanceDirectoryItem(targetUserId); return safeArray(directoryItem?.projects).map((item) => ({ value: item.id, label: item.name || item.id })); } function renderPolicyVersionsHtml(items, emptyText = "暂无历史版本。", selectedVersionId = "") { const versions = safeArray(items); if (!versions.length) { return `

还没有历史版本

${escapeHtml(emptyText)}

`; } return versions.slice(0, 8).map((version) => `

${escapeHtml(version.title || `版本 ${formatNumber(version.version_no || 0)}`)}

${escapeHtml(version.summary || "没有补充摘要。")}

版本 ${escapeHtml(formatNumber(version.version_no || 0))} ${String(version.id || "") === String(selectedVersionId || "") ? `当前预选` : ""} ${version.created_at ? `${escapeHtml(formatDateTime(version.created_at))}` : ""} ${version.rollback_from_version_id ? `回滚生成` : ""}
`).join(""); } async function loadPolicyVersions(url) { const payload = await storyforgeFetch(url).catch(() => ({ items: [] })); const items = safeArray(payload?.items || payload); return { items, count: Number(payload?.count || items.length) }; } function renderPolicyAuditFeed(items, emptyText = "还没有策略变更记录。") { const records = safeArray(items); if (!records.length) { return `

还没有治理动作

${escapeHtml(emptyText)}

`; } return records.slice(0, 10).map((item) => { const scopeKind = item.scope_kind || item.scope?.scope_kind || ""; const platform = item.platform || item.scope?.platform || ""; const version = item.version || {}; const details = item.details || {}; const rollbackId = version.rollback_from_version_id || details.rollback_to_version_id || ""; return `

${escapeHtml(item.summary || version.title || item.action_key || "策略变更")}

${escapeHtml(version.summary || version.reason || "当前记录没有补充摘要。")}

${escapeHtml(policyScopeTagLabel(scopeKind, platform))} ${version.version_no ? `版本 ${escapeHtml(formatNumber(version.version_no || 0))}` : ""} ${item.action_key ? `${escapeHtml(item.action_key)}` : ""} ${platform ? `${escapeHtml(platformLabel(platform))}` : ""} ${rollbackId ? `回滚动作` : ""} ${item.created_at ? `${escapeHtml(formatDateTime(item.created_at))}` : ""}
`; }).join(""); } function buildPolicyVersionOptions(history) { return safeArray(history?.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })); } function ensureAdminGovernanceAccess() { if (isSuperAdmin()) return true; rememberAction("需要管理员权限", "当前动作仅超级管理员可用,请切换到管理员账号后再继续。", "orange"); renderAll(); return false; } function ensureAdminOverrideTargetReady(target) { if (!ensureAdminGovernanceAccess()) return false; if (target?.targetUserId) return true; rememberAction("还没有可治理目标", "当前治理目录里还没有可选用户,暂时无法执行管理员覆盖动作。", "orange"); renderAll(); return false; } function focusUserGovernanceWorkspace(tab = "effective", anchorId = "strategy-governance-anchor") { appState.strategyDetailTab = tab; setScreen("strategy"); setTimeout(() => { document.getElementById(anchorId)?.scrollIntoView({ block: "start", behavior: "smooth" }); }, 0); } function focusAdminGovernanceAuditWorkspace(anchorId = "admin-override-audit-anchor") { appState.adminWorkbenchTab = "governance_audit"; setScreen("admin-workbench"); setTimeout(() => { document.getElementById(anchorId)?.scrollIntoView({ block: "start", behavior: "smooth" }); }, 0); } function openUserGlobalPolicyAction() { const project = requireSelectedProject(); const bundle = appState.userGlobalPolicy || {}; const current = bundle.current_version || {}; openActionModal({ title: "编辑我的全局策略", description: "这层策略只影响你自己,会优先被主 Agent 读取,再决定是否下发到各个平台 Agent。", submitLabel: "保存全局策略", fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, "你还没有发布自己的全局策略,当前会沿用系统默认和平台默认。") }, { name: "title", label: "策略标题", value: current.title || bundle.scope?.title || "用户全局策略", placeholder: "例如:创业内容增长策略" }, { name: "summary", label: "摘要", type: "textarea", rows: 3, value: current.summary || "", placeholder: "写清楚这层策略主要在约束什么、优化什么" }, { name: "policyJson", label: "策略 JSON", type: "textarea", rows: 8, value: JSON.stringify(current.policy || {}, null, 2), placeholder: "{\"tone\":{\"style\":\"analytical\"}}" }, { name: "reason", label: "变更原因", type: "textarea", rows: 3, value: "", placeholder: "例如:用户要求首页动作更聚焦,默认走分析型语气" } ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/oneliner/governance/user/global", { method: "PUT", body: { project_id: project.id, title: values.title || "用户全局策略", summary: values.summary || "", policy: parsePolicyJsonField(values.policyJson, "全局策略 JSON"), reason: values.reason || "" } }); appState.userGlobalPolicy = saved; await loadAgentControlSurfaces(project.id); rememberAction("我的全局策略已保存", `已发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); focusUserGovernanceWorkspace("global", "strategy-governance-anchor"); } }); } function openUserPlatformPolicyAction(platform) { const normalizedPlatform = normalizePlatformValue(platform || getPreferredPlatform(), "douyin"); const project = requireSelectedProject(); const bundle = appState.userCurrentPlatformPolicy || {}; const current = bundle.current_version || {}; openActionModal({ title: `编辑 ${platformLabel(normalizedPlatform)} 平台策略`, description: "这层策略只作用于你当前项目下的单个平台,会覆盖你的全局策略,但不会影响其他平台。", submitLabel: "保存平台策略", fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, "你还没有发布单平台策略,当前会沿用全局策略和系统默认。") }, { name: "title", label: "策略标题", value: current.title || `${platformLabel(normalizedPlatform)} 用户平台策略`, placeholder: "例如:抖音对标拆解策略" }, { name: "summary", label: "摘要", type: "textarea", rows: 3, value: current.summary || "", placeholder: "写清楚这个平台的特殊规则和工作方式" }, { name: "policyJson", label: "策略 JSON", type: "textarea", rows: 8, value: JSON.stringify(current.policy || {}, null, 2), placeholder: "{\"actions\":{\"max_cards\":1}}" }, { name: "reason", label: "变更原因", type: "textarea", rows: 3, value: "", placeholder: "例如:抖音只保留 1 条首页动作,优先高分作品拆解" } ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(normalizedPlatform)}`, { method: "PUT", body: { project_id: project.id, title: values.title || `${platformLabel(normalizedPlatform)} 用户平台策略`, summary: values.summary || "", policy: parsePolicyJsonField(values.policyJson, "平台策略 JSON"), reason: values.reason || "" } }); appState.userCurrentPlatformPolicy = saved; await loadAgentControlSurfaces(project.id); rememberAction(`${platformLabel(normalizedPlatform)} 平台策略已保存`, `已发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); focusUserGovernanceWorkspace("platform", "strategy-governance-anchor"); } }); } async function openUserGlobalPolicyHistoryAction(preferredVersionId = "") { const project = requireSelectedProject(); const history = await loadPolicyVersions(`/v2/oneliner/governance/user/global/versions?project_id=${encodeURIComponent(project.id)}`); const selectedVersionId = resolvePreferredVersionId(history, preferredVersionId); const versionOptions = buildPolicyVersionOptions(history); openActionModal({ title: "我的全局策略历史", description: "查看你自己的全局策略版本,并从历史里选择一个版本回滚。回滚不会改旧记录,而是会生成一个新的生效版本。", submitLabel: "回滚到所选版本", hideSubmit: !selectedVersionId, fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(appState.userGlobalPolicy || {}, "你还没有发布自己的全局策略。") }, { type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, "你的全局策略还没有历史版本。", selectedVersionId) }, ...(selectedVersionId ? [ { name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: versionOptions }, { name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到更稳妥的首页动作和语气策略" } ] : []) ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/oneliner/governance/user/global/rollback", { method: "POST", body: { project_id: project.id, version_id: values.versionId || selectedVersionId, reason: values.reason || "" } }); appState.userGlobalPolicy = saved; await loadAgentControlSurfaces(project.id); rememberAction("我的全局策略已回滚", `已生成回滚版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); focusUserGovernanceWorkspace("global", "strategy-governance-anchor"); } }); } async function openUserPlatformPolicyHistoryAction(platform, preferredVersionId = "") { const normalizedPlatform = normalizePlatformValue(platform || getPreferredPlatform(), "douyin"); const project = requireSelectedProject(); const history = await loadPolicyVersions(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(normalizedPlatform)}/versions?project_id=${encodeURIComponent(project.id)}`); const selectedVersionId = resolvePreferredVersionId(history, preferredVersionId); const versionOptions = buildPolicyVersionOptions(history); openActionModal({ title: `${platformLabel(normalizedPlatform)} 平台策略历史`, description: "查看该平台的个人策略版本,并从历史里选择一个版本回滚。回滚只影响当前平台,不会改动其他平台。", submitLabel: "回滚到所选版本", hideSubmit: !selectedVersionId, fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(appState.userCurrentPlatformPolicy || {}, `你还没有发布 ${platformLabel(normalizedPlatform)} 平台策略。`) }, { type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, `${platformLabel(normalizedPlatform)} 还没有历史版本。`, selectedVersionId) }, ...(selectedVersionId ? [ { name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: versionOptions }, { name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到更适合这个平台的拆解方式" } ] : []) ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/oneliner/governance/user/platforms/${encodeURIComponent(normalizedPlatform)}/rollback`, { method: "POST", body: { project_id: project.id, version_id: values.versionId || selectedVersionId, reason: values.reason || "" } }); appState.userCurrentPlatformPolicy = saved; await loadAgentControlSurfaces(project.id); rememberAction(`${platformLabel(normalizedPlatform)} 平台策略已回滚`, `已生成回滚版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); focusUserGovernanceWorkspace("platform", "strategy-governance-anchor"); } }); } function focusAdminGovernanceAgentsWorkspace(anchorId = "admin-governance-anchor") { appState.adminWorkbenchTab = "agents"; setScreen("admin-workbench"); setTimeout(() => { document.getElementById(anchorId)?.scrollIntoView({ block: "start", behavior: "smooth" }); }, 0); } function openSystemMainPolicyAction() { if (!ensureAdminGovernanceAccess()) return; const projectId = getOneLinerProjectId(); const bundle = appState.adminSystemMainPolicy || {}; const current = bundle.current_version || {}; openActionModal({ title: "编辑系统主 Agent 策略", description: "这是所有用户共享的系统级主 Agent 基座能力,用户层和管理员覆盖都会叠加在它上面。", submitLabel: "保存系统策略", fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, "系统主 Agent 还没有系统默认策略。") }, { name: "title", label: "策略标题", value: current.title || bundle.scope?.title || "系统主 Agent 策略", placeholder: "例如:StoryForge 主 Agent 默认策略" }, { name: "summary", label: "摘要", type: "textarea", rows: 3, value: current.summary || "", placeholder: "写清楚当前系统主 Agent 主要服务的方向和约束" }, { name: "policyJson", label: "策略 JSON", type: "textarea", rows: 8, value: JSON.stringify(current.policy || {}, null, 2), placeholder: "{\"homepage\":{\"focus\":\"ops\"}}" }, { name: "reason", label: "发布原因", type: "textarea", rows: 3, value: "", placeholder: "例如:更新市场节奏后,需要调整首页推荐和调度逻辑" } ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/admin/oneliner/governance/system/main-agent", { method: "PUT", body: { title: values.title || "系统主 Agent 策略", summary: values.summary || "", policy: parsePolicyJsonField(values.policyJson, "系统策略 JSON"), reason: values.reason || "" } }); appState.adminSystemMainPolicy = saved; await loadAgentControlSurfaces(projectId); rememberAction("系统主 Agent 策略已保存", `已发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); focusAdminGovernanceAgentsWorkspace("admin-governance-anchor"); } }); } function openSystemPlatformPolicyAction(platform) { if (!ensureAdminGovernanceAccess()) return; const normalizedPlatform = normalizePlatformValue(platform, "douyin"); const projectId = getOneLinerProjectId(); const bundle = safeArray(appState.adminSystemPlatformPolicies).find((item) => item?.scope?.platform === normalizedPlatform) || {}; const current = bundle.current_version || {}; openActionModal({ title: `编辑 ${platformLabel(normalizedPlatform)} 系统平台策略`, description: "这是所有用户共享的系统级平台默认策略,用户自己的平台偏好会在这层之上覆盖。", submitLabel: "保存平台默认策略", fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, `当前 ${platformLabel(normalizedPlatform)} 还没有系统平台默认策略。`) }, { name: "title", label: "策略标题", value: current.title || `${platformLabel(normalizedPlatform)} 系统平台策略`, placeholder: "例如:抖音系统平台策略" }, { name: "summary", label: "摘要", type: "textarea", rows: 3, value: current.summary || "", placeholder: "写清楚这个平台默认遵循的拆解与执行逻辑" }, { name: "policyJson", label: "策略 JSON", type: "textarea", rows: 8, value: JSON.stringify(current.policy || {}, null, 2), placeholder: "{\"douyin\":{\"benchmark_mode\":\"strict\"}}" }, { name: "reason", label: "发布原因", type: "textarea", rows: 3, value: "", placeholder: "例如:平台节奏变化,需要调整系统默认方法论" } ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(normalizedPlatform)}`, { method: "PUT", body: { title: values.title || `${platformLabel(normalizedPlatform)} 系统平台策略`, summary: values.summary || "", policy: parsePolicyJsonField(values.policyJson, "平台默认策略 JSON"), reason: values.reason || "" } }); appState.adminSystemPlatformPolicies = safeArray(appState.adminSystemPlatformPolicies) .filter((item) => item?.scope?.platform !== normalizedPlatform) .concat(saved) .sort((a, b) => String(a?.scope?.platform || "").localeCompare(String(b?.scope?.platform || ""))); await loadAgentControlSurfaces(projectId); rememberAction(`${platformLabel(normalizedPlatform)} 系统平台策略已保存`, `已发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); focusAdminGovernanceAgentsWorkspace("admin-governance-anchor"); } }); } async function openAdminOverrideTargetAction() { if (!ensureAdminGovernanceAccess()) return; const current = getAdminOverrideTargetState(); const directoryItems = getAdminGovernanceDirectoryItems(); openActionModal({ title: "选择管理员覆盖目标", description: "先选中要覆盖的用户、项目和平台,再去编辑覆盖策略或查看历史。", submitLabel: "保存目标", hideSubmit: !directoryItems.length, fields: [ { type: "html", label: "当前目标", html: renderPolicyVersionSummary(appState.adminOverridePolicy || {}, `当前目标是 ${formatAdminGovernanceTargetLabel(current)}。`) }, ...(directoryItems.length ? [ { name: "targetUserId", label: "目标用户", type: "select", value: current.targetUserId, options: getAdminGovernanceDirectoryUserOptions() }, { name: "targetProjectId", label: "目标项目", type: "select", value: current.targetProjectId, options: [{ value: "", label: "用户全局" }, ...getAdminGovernanceDirectoryProjectOptions(current.targetUserId)] }, { name: "platform", label: "平台", type: "select", value: current.platform, options: getPlatformOptions() } ] : []), { type: "html", label: "目录提示", html: directoryItems.length ? `

可选目标

当前目录里有 ${escapeHtml(formatNumber(directoryItems.length))} 位已审核账号。

` : `

目录为空

后端还没有返回可选账号。

` } ], onOpen: () => { const userSelect = document.querySelector('[data-action-field="targetUserId"]'); if (!(userSelect instanceof HTMLSelectElement)) return; syncAdminOverrideProjectOptions(userSelect.value, current.targetProjectId); userSelect.addEventListener("change", () => { syncAdminOverrideProjectOptions(userSelect.value, ""); }); }, onSubmit: async (values) => { appState.adminOverrideTarget = { targetUserId: String(values.targetUserId || ""), targetProjectId: String(values.targetProjectId || ""), platform: normalizePlatformValue(values.platform || "douyin", "douyin") }; await loadAgentControlSurfaces(getOneLinerProjectId()); rememberAction("管理员覆盖目标已更新", `当前目标已切换到 ${formatAdminGovernanceTargetLabel(appState.adminOverrideTarget)}。`, "green"); renderAll(); focusAdminGovernanceAuditWorkspace("admin-override-audit-anchor"); } }); } function syncAdminOverrideProjectOptions(targetUserId, preferredProjectId = "") { const projectSelect = document.querySelector('[data-action-field="targetProjectId"]'); if (!(projectSelect instanceof HTMLSelectElement)) return; const options = [{ value: "", label: "用户全局" }, ...getAdminGovernanceDirectoryProjectOptions(targetUserId)]; projectSelect.innerHTML = options.map((option) => ` `).join(""); const normalizedPreferred = String(preferredProjectId ?? ""); const nextValue = normalizedPreferred === "" || options.some((option) => String(option.value) === normalizedPreferred) ? normalizedPreferred : String(options[0]?.value || ""); projectSelect.value = nextValue; } function openAdminOverridePolicyAction() { const target = getAdminOverrideTargetState(); if (!ensureAdminOverrideTargetReady(target)) return; const bundle = appState.adminOverridePolicy || {}; const current = bundle.current_version || {}; openActionModal({ title: "编辑管理员覆盖策略", description: "这层策略只作用于当前选中的目标,会叠加在用户策略和系统默认之上。", submitLabel: "保存覆盖策略", fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, `当前还没有为 ${formatAdminGovernanceTargetLabel(target)} 发布覆盖策略。`) }, { name: "title", label: "策略标题", value: current.title || `管理员覆盖:${formatAdminGovernanceTargetLabel(target)}`, placeholder: "例如:重点账号短期放量覆盖" }, { name: "summary", label: "摘要", type: "textarea", rows: 3, value: current.summary || "", placeholder: "写清楚这层覆盖是为了什么目标" }, { name: "policyJson", label: "策略 JSON", type: "textarea", rows: 8, value: JSON.stringify(current.policy || {}, null, 2), placeholder: "{\"guardrails\":{\"require_admin_review\":true}}" }, { name: "reason", label: "变更原因", type: "textarea", rows: 3, value: "", placeholder: "例如:对该账号/项目临时放宽首页动作数量" } ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/admin/oneliner/governance/overrides", { method: "POST", body: { target_user_id: target.targetUserId, target_project_id: target.targetProjectId, platform: target.platform, title: values.title || `管理员覆盖:${formatAdminGovernanceTargetLabel(target)}`, summary: values.summary || "", policy: parsePolicyJsonField(values.policyJson, "管理员覆盖策略 JSON"), reason: values.reason || "" } }); appState.adminOverridePolicy = saved; await loadAgentControlSurfaces(getOneLinerProjectId()); rememberAction("管理员覆盖策略已保存", `已为 ${formatAdminGovernanceTargetLabel(target)} 发布版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); focusAdminGovernanceAuditWorkspace("admin-override-audit-anchor"); } }); } async function openAdminOverrideHistoryAction(preferredVersionId = "") { const target = getAdminOverrideTargetState(); if (!ensureAdminOverrideTargetReady(target)) return; const history = await loadPolicyVersions(`/v2/admin/oneliner/governance/overrides/versions?target_user_id=${encodeURIComponent(target.targetUserId)}&target_project_id=${encodeURIComponent(target.targetProjectId)}&platform=${encodeURIComponent(target.platform)}`); const selectedVersionId = resolvePreferredVersionId(history, preferredVersionId); openActionModal({ title: "管理员覆盖历史", description: "查看当前目标的管理员覆盖版本,并从历史里选择一个版本回滚。", submitLabel: "回滚到所选版本", hideSubmit: !selectedVersionId, fields: [ { type: "html", label: "当前目标", html: renderPolicyVersionSummary(appState.adminOverridePolicy || {}, `当前查看的是 ${formatAdminGovernanceTargetLabel(target)} 的覆盖历史。`) }, { type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, "当前目标还没有历史版本。", selectedVersionId) }, ...(selectedVersionId ? [ { name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: safeArray(history.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })) }, { name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:这版覆盖太激进,需要恢复到上一版" } ] : []) ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/admin/oneliner/governance/overrides/rollback", { method: "POST", body: { target_user_id: target.targetUserId, target_project_id: target.targetProjectId, platform: target.platform, version_id: values.versionId || selectedVersionId, reason: values.reason || "" } }); appState.adminOverridePolicy = saved; await loadAgentControlSurfaces(getOneLinerProjectId()); rememberAction("管理员覆盖已回滚", `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); focusAdminGovernanceAuditWorkspace("admin-override-audit-anchor"); } }); } async function openSystemMainPolicyHistoryAction(preferredVersionId = "") { if (!ensureAdminGovernanceAccess()) return; const history = await loadPolicyVersions("/v2/admin/oneliner/governance/system/main-agent/versions"); const selectedVersionId = resolvePreferredVersionId(history, preferredVersionId); openActionModal({ title: "系统主 Agent 历史", description: "查看系统主 Agent 的历史版本,并选择某个版本回滚。", submitLabel: "回滚到所选版本", hideSubmit: !selectedVersionId, fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(appState.adminSystemMainPolicy || {}, "系统主 Agent 还没有历史版本。") }, { type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, "系统主 Agent 还没有历史版本。", selectedVersionId) }, ...(selectedVersionId ? [ { name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: safeArray(history.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })) }, { name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到上一版系统主 Agent 策略" } ] : []) ], onSubmit: async (values) => { const saved = await storyforgeFetch("/v2/admin/oneliner/governance/system/main-agent/rollback", { method: "POST", body: { version_id: values.versionId || selectedVersionId, reason: values.reason || "" } }); appState.adminSystemMainPolicy = saved; await loadAgentControlSurfaces(getOneLinerProjectId()); rememberAction("系统主 Agent 已回滚", `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); focusAdminGovernanceAgentsWorkspace("admin-governance-anchor"); } }); } async function openSystemPlatformPolicyHistoryAction(platform, preferredVersionId = "") { if (!ensureAdminGovernanceAccess()) return; const normalizedPlatform = normalizePlatformValue(platform || getPreferredPlatform(), "douyin"); const history = await loadPolicyVersions(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(normalizedPlatform)}/versions`); const selectedVersionId = resolvePreferredVersionId(history, preferredVersionId); const bundle = safeArray(appState.adminSystemPlatformPolicies).find((item) => item?.scope?.platform === normalizedPlatform) || {}; openActionModal({ title: `${platformLabel(normalizedPlatform)} 系统平台历史`, description: "查看该平台的系统默认策略历史,并选择某个版本回滚。", submitLabel: "回滚到所选版本", hideSubmit: !selectedVersionId, fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, `当前 ${platformLabel(normalizedPlatform)} 还没有系统平台历史版本。`) }, { type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, `${platformLabel(normalizedPlatform)} 还没有历史版本。`, selectedVersionId) }, ...(selectedVersionId ? [ { name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: safeArray(history.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })) }, { name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到上一版平台默认方法论" } ] : []) ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/admin/oneliner/governance/system/platforms/${encodeURIComponent(normalizedPlatform)}/rollback`, { method: "POST", body: { version_id: values.versionId || selectedVersionId, reason: values.reason || "" } }); appState.adminSystemPlatformPolicies = safeArray(appState.adminSystemPlatformPolicies) .filter((item) => item?.scope?.platform !== normalizedPlatform) .concat(saved) .sort((a, b) => String(a?.scope?.platform || "").localeCompare(String(b?.scope?.platform || ""))); await loadAgentControlSurfaces(getOneLinerProjectId()); rememberAction(`${platformLabel(normalizedPlatform)} 系统平台策略已回滚`, `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); focusAdminGovernanceAgentsWorkspace("admin-governance-anchor"); } }); } function reopenPlatformAgentDetailSoon(platform) { setTimeout(() => { void openPlatformAgentDetailAction(platform); }, 0); } function openPlatformAgentProfileAction(platform) { const project = requireSelectedProject(); const agents = safeArray(appState.platformAgents); const current = agents.find((item) => item.platform === platform) || {}; const assistants = getAssistantOptions(project.id); openActionModal({ title: `配置 ${platformLabel(platform)} Agent`, description: "给这个平台绑定自己的执行 Agent,并补充任务目标和方法论定位。", submitLabel: "保存平台 Agent", fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(current, `当前 ${platformLabel(platform)} Agent 还没有历史版本。`) }, { name: "assistantId", label: "绑定执行 Agent", type: "select", value: current.assistant_id || assistants[0]?.value || "", options: [{ value: "", label: "先不绑定" }, ...assistants] }, { name: "name", label: "名称", value: current.name || `${platformLabel(platform)} Agent`, placeholder: "例如:快手增长 Agent" }, { name: "mission", label: "任务目标", type: "textarea", rows: 4, value: current.mission || "", placeholder: "例如:沉淀快手平台的开场结构、停留逻辑和转化方法论" }, { name: "notes", label: "补充说明", type: "textarea", rows: 4, value: current.notes || "", placeholder: "例如:优先观察短句节奏、直播切片和成交句式" }, { name: "status", label: "状态", type: "select", value: current.status || "active", options: [{ value: "active", label: "启用" }, { value: "draft", label: "草稿" }, { value: "paused", label: "暂停" }] }, { name: "reason", label: "变更原因", type: "textarea", rows: 3, value: "", placeholder: "例如:调整当前平台 Agent 的拆解重点和执行方向" } ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(platform)}/profile`, { method: "PUT", body: { project_id: project.id, assistant_id: values.assistantId || "", name: values.name || `${platformLabel(platform)} Agent`, mission: values.mission || "", notes: values.notes || "", status: values.status || "active", reason: values.reason || "", config: { self_optimize: true, tenant_scoped_memory: true, ui_escalation_via_oneliner: true } } }); appState.platformAgents = safeArray(appState.platformAgents).filter((item) => item.platform !== platform).concat(saved).sort((a, b) => String(a.platform).localeCompare(String(b.platform))); rememberAction("平台 Agent 已保存", `已更新 ${platformLabel(platform)} Agent,当前版本 ${saved.current_version?.version_no || 1}。`, "green", saved); renderAll(); reopenPlatformAgentDetailSoon(platform); } }); } async function openPlatformAgentProfileHistoryAction(platform, preferredVersionId = "") { const project = requireSelectedProject(); const normalizedPlatform = normalizePlatformValue(platform || getPreferredPlatform(), "douyin"); const history = await loadPolicyVersions(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/profile/versions?project_id=${encodeURIComponent(project.id)}`); const audits = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/profile/audits?project_id=${encodeURIComponent(project.id)}`).catch(() => ({ items: [] })); const current = safeArray(appState.platformAgents).find((item) => item.platform === normalizedPlatform) || {}; const selectedVersionId = resolvePreferredVersionId(history, preferredVersionId); openActionModal({ title: `${platformLabel(normalizedPlatform)} Agent 配置历史`, description: "查看平台 Agent 配置版本,并从历史里选择一个版本回滚。", submitLabel: "回滚到所选版本", hideSubmit: !selectedVersionId, fields: [ { type: "html", label: "当前版本", html: renderPolicyVersionSummary(current, `当前 ${platformLabel(normalizedPlatform)} Agent 还没有历史版本。`) }, { type: "html", label: "历史版本", html: renderPolicyVersionsHtml(history.items, `当前 ${platformLabel(normalizedPlatform)} Agent 还没有历史版本。`, selectedVersionId) }, { type: "html", label: "审计记录", html: renderPolicyAuditsHtml(safeArray(audits.items || audits), "当前还没有审计记录。") }, ...(selectedVersionId ? [ { name: "versionId", label: "回滚版本", type: "select", value: selectedVersionId, options: safeArray(history.items).map((item) => ({ value: item.id, label: `v${formatNumber(item.version_no || 0)} · ${item.title || brief(item.summary || item.id, 24)}` })) }, { name: "reason", label: "回滚原因", type: "textarea", rows: 3, value: "", placeholder: "例如:恢复到上一版平台 Agent 方法论" } ] : []) ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/profile/rollback`, { method: "POST", body: { project_id: project.id, version_id: values.versionId || selectedVersionId, reason: values.reason || "" } }); appState.platformAgents = safeArray(appState.platformAgents).filter((item) => item.platform !== normalizedPlatform).concat(saved).sort((a, b) => String(a.platform).localeCompare(String(b.platform))); rememberAction(`${platformLabel(normalizedPlatform)} Agent 已回滚`, `已回滚到版本 ${saved.current_version?.version_no || "所选版本"}。`, "green", saved); renderAll(); reopenPlatformAgentDetailSoon(normalizedPlatform); } }); } function openPlatformAgentMemoryAction(platform) { const project = requireSelectedProject(); openActionModal({ title: `补充 ${platformLabel(platform)} Agent 记忆`, description: "把当前阶段已经验证有效的平台方法、结论或注意事项沉淀为租户级长期记忆。", submitLabel: "保存记忆", fields: [ { name: "memoryKey", label: "记忆键", value: "lesson.current", placeholder: "例如:hook.pattern.v1" }, { name: "title", label: "标题", placeholder: "例如:快手直播切片更吃冲突前置" }, { name: "summary", label: "摘要", type: "textarea", rows: 4, placeholder: "写清楚这条记忆的结论和适用场景" } ], onSubmit: async (values) => { if (!values.memoryKey?.trim()) throw new Error("请填写记忆键"); if (!values.summary?.trim()) throw new Error("请填写记忆摘要"); const saved = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(platform)}/memories`, { method: "POST", body: { project_id: project.id, memory_key: values.memoryKey.trim(), title: values.title || values.memoryKey.trim(), summary: values.summary.trim(), subject_type: "project", subject_id: project.id, details: { source: "manual-ui", platform, captured_at: new Date().toISOString() }, confidence: 0.82 } }); rememberAction("平台记忆已保存", `已把这条方法沉淀到 ${platformLabel(platform)} Agent 记忆中。`, "green", saved); await loadAgentControlSurfaces(project.id); renderAll(); reopenPlatformAgentDetailSoon(platform); } }); } function openPlatformAgentSkillAction(platform) { const project = requireSelectedProject(); openActionModal({ title: `补充 ${platformLabel(platform)} Agent 技能`, description: "把子 Agent 当前阶段验证过的方法论固化成可复用技能,并保留测试规范。", submitLabel: "保存技能", fields: [ { name: "skillKey", label: "技能键", value: "skill.current", placeholder: "例如:crawler.profile.dom.v2" }, { name: "name", label: "名称", placeholder: "例如:主页结构适配技能" }, { name: "status", label: "状态", type: "select", value: "validated", options: [{ value: "draft", label: "草稿" }, { value: "validated", label: "已验证" }, { value: "paused", label: "暂停" }] }, { name: "method", label: "方法摘要", type: "textarea", rows: 4, placeholder: "写清楚当前方法是怎么拿到结果的" }, { name: "testSpec", label: "验收标准", type: "textarea", rows: 4, placeholder: "例如:主页抓取成功率 >= 95%,作品标题和发布时间都齐全" } ], onSubmit: async (values) => { if (!values.skillKey?.trim()) throw new Error("请填写技能键"); if (!values.name?.trim()) throw new Error("请填写技能名称"); const saved = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(platform)}/skills`, { method: "POST", body: { project_id: project.id, skill_key: values.skillKey.trim(), name: values.name.trim(), status: values.status || "validated", method: { summary: values.method || "" }, test_spec: { summary: values.testSpec || "" }, last_result: { source: "manual-ui" }, success_count: 1, failure_count: 0, last_score: 0.9 } }); rememberAction("平台技能已保存", `已把方法固化到 ${platformLabel(platform)} Agent 技能中。`, "green", saved); await loadAgentControlSurfaces(project.id); renderAll(); reopenPlatformAgentDetailSoon(platform); } }); } async function openPlatformAgentDetailAction(platform) { const project = requireSelectedProject(); const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform()); const profile = safeArray(appState.platformAgents).find((item) => item.platform === normalizedPlatform) || null; if (!profile) { rememberAction("平台 Agent 不存在", "当前没有找到这条平台 Agent,请先刷新当前页面。", "orange"); renderAll(); return; } const [memoriesPayload, skillsPayload] = await Promise.all([ storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/memories?project_id=${encodeURIComponent(project.id)}`).catch(() => ({ items: [] })), storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/skills?project_id=${encodeURIComponent(project.id)}`).catch(() => ({ items: [] })) ]); const memories = safeArray(memoriesPayload?.items || memoriesPayload).slice(0, 6); const skills = safeArray(skillsPayload?.items || skillsPayload).slice(0, 6); const recentExecutionOnelinerConfigStale = isConfigurationVersionStale( profile?.recent_execution || {}, appState.onelinerProfile?.current_version || {} ); const recentExecutionPlatformConfigStale = isConfigurationVersionStale( { version_id: profile?.recent_execution?.platform_agent_profile_version_id, version_no: profile?.recent_execution?.platform_agent_profile_version_no, }, profile?.current_version || {} ); const skillVersionEntries = await Promise.all( skills.map(async (item) => { const payload = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/skills/${encodeURIComponent(item.id)}/versions?project_id=${encodeURIComponent(project.id)}`).catch(() => ({ items: [] })); return [item.id, safeArray(payload?.items || payload).slice(0, 3)]; }) ); const skillVersions = Object.fromEntries(skillVersionEntries); openActionModal({ title: `${platformLabel(normalizedPlatform)} Agent 详情`, description: "查看当前平台 Agent 最近沉淀的记忆、技能和就绪度。", hideSubmit: true, fields: [ { type: "html", label: "详情", html: `

${escapeHtml(profile.name || `${platformLabel(normalizedPlatform)} Agent`)}

${escapeHtml(profile.mission || profile.notes || "暂无任务目标说明")}

${escapeHtml(profile.status || "draft")} ${profile.readiness_label ? `= 50 ? "blue" : "orange"}">${escapeHtml(profile.readiness_label)} ${escapeHtml(formatNumber(profile.readiness_score || 0))}` : ""} ${escapeHtml(profile.assistant?.name || "未绑执行 Agent")}
${profile.recent_execution?.run_id ? `

最近执行

${escapeHtml(profile.recent_execution.title || profile.recent_execution.goal || profile.recent_execution.summary || "最近一次主 Agent 执行已回写到当前平台 Agent。")}

${escapeHtml(profile.recent_execution.intent_label || "主 Agent 任务")} ${escapeHtml(profile.recent_execution.run_status || "done")} ${profile.recent_execution.platform_scope ? `${escapeHtml(profile.recent_execution.platform_scope === "all_platforms" ? "全平台" : "单平台")}` : ""} ${profile.recent_execution.delivery_mode ? `${escapeHtml(profile.recent_execution.delivery_mode)}` : ""} ${profile.recent_execution.workstream_label ? `${escapeHtml(profile.recent_execution.workstream_label)}` : ""} ${profile.recent_execution.oneliner_profile_version_no ? `配置 v${escapeHtml(formatNumber(profile.recent_execution.oneliner_profile_version_no))}` : ""} ${recentExecutionOnelinerConfigStale ? `主配置已更新` : ""} ${profile.recent_execution.platform_agent_profile_version_no ? `${escapeHtml(platformLabel(normalizedPlatform))} Agent v${escapeHtml(formatNumber(profile.recent_execution.platform_agent_profile_version_no))}` : ""} ${recentExecutionPlatformConfigStale ? `${escapeHtml(platformLabel(normalizedPlatform))} Agent 已更新` : ""} ${profile.recent_execution.source_screen ? `${escapeHtml(screenLabel(profile.recent_execution.source_screen) || profile.recent_execution.source_screen)}` : ""}
${profile.recent_execution.recommended_action?.action ? `${escapeHtml(profile.recent_execution.recommended_action.label || "回到业务页")}` : ""} ${profile.recent_execution.oneliner_profile_version_no ? `主配置历史` : ""} ${profile.recent_execution.platform_agent_profile_version_no ? `平台配置历史` : ""} 查看执行结果 回到主 Agent 查看
` : ""}
最近记忆
${memories.map((item) => `

${escapeHtml(item.title || item.memory_key || "未命名")}

${escapeHtml(item.summary || "暂无摘要")}

${escapeHtml(item.memory_key || "memory")}${escapeHtml(formatNumber(item.confidence || 0))}
`).join("") || `

还没有平台记忆

先把这段时间验证有效的方法沉淀进来。

`}
最近技能
${skills.map((item) => `

${escapeHtml(item.name || item.skill_key || "未命名")}

${escapeHtml(item.test_spec?.summary || item.method?.summary || "暂无方法摘要")}

${escapeHtml(item.status || "draft")} 得分 ${escapeHtml(formatNumber(item.last_score || 0))} 验收通过 标记待优化
${safeArray(skillVersions[item.id]).length ? `
${safeArray(skillVersions[item.id]).map((version, index) => ` ${escapeHtml(`v${formatNumber(version.version_no || 0)} · ${version.snapshot_reason || "snapshot"}`)} `).join("")}
` : ""}
`).join("") || `

还没有平台技能

等子 Agent 跑出稳定结果后,把方法固化成技能。

`}
运行平台自检 编辑配置 看配置历史 继续补记忆 继续补技能 交给主 Agent 继续
` } ] }); } function openPlatformSkillReviewAction(platform, skillId, accepted) { const project = requireSelectedProject(); const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform()); const profile = safeArray(appState.platformAgents).find((item) => item.platform === normalizedPlatform) || null; const skill = safeArray(profile?.recent_skill ? [profile.recent_skill] : []) .concat([]) .find((item) => item.id === skillId) || null; openActionModal({ title: accepted ? "验收平台技能" : "标记技能待优化", description: accepted ? `把这条 ${platformLabel(normalizedPlatform)} 技能标记为当前可复用的方法。` : `这条 ${platformLabel(normalizedPlatform)} 技能暂时不通过,要求继续优化。`, submitLabel: accepted ? "确认通过" : "确认待优化", fields: [ { name: "summary", label: "结论摘要", placeholder: accepted ? "例如:当前抓取结果和验收数据一致,可固化成技能" : "例如:账号匹配不稳定,需要继续优化抓取方式" }, { name: "reviewNotes", label: "审计备注", type: "textarea", rows: 4, value: skill?.last_result?.review_notes || "", placeholder: "写清楚为什么通过或退回" }, { name: "score", label: "得分", type: "number", value: accepted ? 0.9 : 0.45, min: 0, max: 1, step: 0.05 } ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/skills/${encodeURIComponent(skillId)}/review`, { method: "POST", body: { project_id: project.id, accepted, score: Number(values.score || (accepted ? 0.9 : 0.45)), summary: values.summary || "", review_notes: values.reviewNotes || "" } }); rememberAction( accepted ? "平台技能已通过" : "平台技能待优化", `技能「${saved.name || saved.skill_key || skillId}」已更新为 ${saved.status || (accepted ? "validated" : "needs_revision")}。`, accepted ? "green" : "orange", saved ); await loadAgentControlSurfaces(project.id); renderAll(); reopenPlatformAgentDetailSoon(normalizedPlatform); } }); } function openPlatformSkillRollbackAction(platform, skillId, versionId) { const project = requireSelectedProject(); const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform()); openActionModal({ title: "回滚平台技能", description: "把当前技能回退到旧版本,并保留新的回滚快照,方便继续追踪。", submitLabel: "确认回滚", fields: [ { name: "summary", label: "回滚说明", type: "html", html: `

${escapeHtml(platformLabel(normalizedPlatform))} 技能回滚

${escapeHtml(`将 skill ${skillId} 回滚到版本 ${versionId}。`)}

` } ], onSubmit: async () => { const payload = await storyforgeFetch(`/v2/platform-agents/${encodeURIComponent(normalizedPlatform)}/skills/${encodeURIComponent(skillId)}/rollback`, { method: "POST", body: { project_id: project.id, version_id: versionId } }); rememberAction("技能已回滚", `已回滚到版本 ${payload.rollback_from_version?.version_no || "指定版本"}。`, "green", payload); await loadAgentControlSurfaces(project.id); renderAll(); reopenPlatformAgentDetailSoon(normalizedPlatform); } }); } function openActionRegistryEditAction(actionKey) { const project = requireSelectedProject(); const actionDef = safeArray(appState.onelinerActionRegistry).find((item) => item.action_key === actionKey) || null; if (!actionDef) { rememberAction("动作定义不存在", "当前没有找到这条 OneLiner 动作定义,请先刷新当前页面。", "orange"); renderAll(); return; } openActionModal({ title: "编辑 OneLiner 动作", description: "在租户范围内控制动作名称、说明、开关和少量配置。", submitLabel: "保存动作", fields: [ { name: "label", label: "动作名称", value: actionDef.label || "" }, { name: "description", label: "动作说明", type: "textarea", rows: 4, value: actionDef.description || "" }, { name: "status", label: "状态", type: "select", value: actionDef.status || "enabled", options: [{ value: "enabled", label: "启用" }, { value: "disabled", label: "禁用" }] }, { name: "configJson", label: "配置 JSON", type: "textarea", rows: 5, value: JSON.stringify(actionDef.config || {}, null, 2) } ], onSubmit: async (values) => { let config = {}; if (String(values.configJson || "").trim()) { config = JSON.parse(values.configJson); } const saved = await storyforgeFetch(`/v2/oneliner/action-registry/${encodeURIComponent(actionKey)}?project_id=${encodeURIComponent(project.id)}`, { method: "PUT", body: { label: values.label || "", description: values.description || "", category: actionDef.category || "custom", status: values.status || "enabled", config } }); rememberAction("动作已更新", `OneLiner 动作「${saved.label || saved.action_key}」已保存。`, "green", saved); await loadAgentControlSurfaces(project.id); renderAll(); focusAdminGovernanceAgentsWorkspace("admin-action-registry-anchor"); } }); } function openTenantQuotaAction() { const project = requireSelectedProject(); const quota = appState.tenantQuota || {}; const usage = appState.tenantUsage || {}; const quotaConfig = quota.config || {}; const defaultDraft = { warnThreshold: Number(quotaConfig.warn_threshold ?? 0.8) || 0.8, monthlyBudgetCents: Number(quota.monthly_budget_cents || 0), storageLimitBytes: Number(quota.storage_limit_bytes || 0), analysisQuota: Number(quota.analysis_quota || 0), copyQuota: Number(quota.copy_quota || 0), aiVideoQuota: Number(quota.ai_video_quota || 0), realCutQuota: Number(quota.real_cut_quota || 0), recorderQuota: Number(quota.recorder_quota || 0) }; openActionModal({ title: "编辑租户额度", description: "当前额度按租户 + 项目隔离;选择预设套餐时,服务端会自动应用对应预算、动作池和存储保护。", submitLabel: "保存额度", fields: [ { name: "enabled", label: "启用额度保护", type: "checkbox", value: quota.enabled !== false }, { name: "packageLabel", label: "套餐档位", type: "select", value: quotaConfig.package_label || "custom", options: [ { value: "trial", label: "试用套餐" }, { value: "growth", label: "增长套餐" }, { value: "scale", label: "规模套餐" }, { value: "custom", label: "自定义套餐" } ] }, { type: "html", label: "套餐预览", html: `
${renderTenantQuotaPackagePreview(quotaConfig.package_label || "custom", defaultDraft, usage)}
` }, { name: "warnThreshold", label: "预算预警阈值", type: "number", value: defaultDraft.warnThreshold, min: 0, max: 1, step: 0.05 }, { name: "monthlyBudgetCents", label: "月预算(分)", type: "number", value: defaultDraft.monthlyBudgetCents, min: 0, step: 100 }, { name: "storageLimitBytes", label: "存储上限(字节)", type: "number", value: defaultDraft.storageLimitBytes, min: 0, step: 1024 }, { name: "analysisQuota", label: "分析配额", type: "number", value: defaultDraft.analysisQuota, min: 0, step: 1 }, { name: "copyQuota", label: "文案配额", type: "number", value: defaultDraft.copyQuota, min: 0, step: 1 }, { name: "aiVideoQuota", label: "AI 视频配额", type: "number", value: defaultDraft.aiVideoQuota, min: 0, step: 1 }, { name: "realCutQuota", label: "实拍剪辑配额", type: "number", value: defaultDraft.realCutQuota, min: 0, step: 1 }, { name: "recorderQuota", label: "录制配额", type: "number", value: defaultDraft.recorderQuota, min: 0, step: 1 } ], onOpen: ({ fields }) => { const packageSelect = fields.querySelector('[data-action-field="packageLabel"]'); const preview = fields.querySelector('[data-role="quota-package-preview"]'); const numericFieldNames = [ "warnThreshold", "monthlyBudgetCents", "storageLimitBytes", "analysisQuota", "copyQuota", "aiVideoQuota", "realCutQuota", "recorderQuota" ]; const currentCustomDraft = { ...defaultDraft }; const getField = (name) => fields.querySelector(`[data-action-field="${name}"]`); const syncPreview = () => { const packageLabel = packageSelect?.value || "custom"; const preset = getTenantQuotaPackagePreset(packageLabel); if (preset) { currentCustomDraft.warnThreshold = Number(getField("warnThreshold")?.value || currentCustomDraft.warnThreshold || 0.8); currentCustomDraft.monthlyBudgetCents = Number(getField("monthlyBudgetCents")?.value || currentCustomDraft.monthlyBudgetCents || 0); currentCustomDraft.storageLimitBytes = Number(getField("storageLimitBytes")?.value || currentCustomDraft.storageLimitBytes || 0); currentCustomDraft.analysisQuota = Number(getField("analysisQuota")?.value || currentCustomDraft.analysisQuota || 0); currentCustomDraft.copyQuota = Number(getField("copyQuota")?.value || currentCustomDraft.copyQuota || 0); currentCustomDraft.aiVideoQuota = Number(getField("aiVideoQuota")?.value || currentCustomDraft.aiVideoQuota || 0); currentCustomDraft.realCutQuota = Number(getField("realCutQuota")?.value || currentCustomDraft.realCutQuota || 0); currentCustomDraft.recorderQuota = Number(getField("recorderQuota")?.value || currentCustomDraft.recorderQuota || 0); } const nextValues = preset ? { warnThreshold: preset.warnThreshold, monthlyBudgetCents: preset.monthlyBudgetCents, storageLimitBytes: preset.storageLimitBytes, analysisQuota: preset.analysisQuota, copyQuota: preset.copyQuota, aiVideoQuota: preset.aiVideoQuota, realCutQuota: preset.realCutQuota, recorderQuota: preset.recorderQuota } : currentCustomDraft; numericFieldNames.forEach((name) => { const input = getField(name); if (!input) return; input.value = String(nextValues[name] ?? ""); input.disabled = Boolean(preset); }); if (preview) { preview.innerHTML = renderTenantQuotaPackagePreview(packageLabel, nextValues, usage); } }; packageSelect?.addEventListener("change", syncPreview); numericFieldNames.forEach((name) => { getField(name)?.addEventListener("input", () => { if ((packageSelect?.value || "custom") !== "custom") return; currentCustomDraft[name] = Number(getField(name)?.value || 0); if (preview) preview.innerHTML = renderTenantQuotaPackagePreview("custom", currentCustomDraft, usage); }); }); syncPreview(); }, onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/tenant/quota?project_id=${encodeURIComponent(project.id)}`, { method: "PUT", body: { package_label: String(values.packageLabel || "custom"), enabled: Boolean(values.enabled), monthly_budget_cents: Number(values.monthlyBudgetCents || 0), storage_limit_bytes: Number(values.storageLimitBytes || 0), analysis_quota: Number(values.analysisQuota || 0), copy_quota: Number(values.copyQuota || 0), ai_video_quota: Number(values.aiVideoQuota || 0), real_cut_quota: Number(values.realCutQuota || 0), recorder_quota: Number(values.recorderQuota || 0), config: { ...(quota.config || {}), package_label: String(values.packageLabel || "custom"), warn_threshold: Number(values.warnThreshold ?? 0.8) || 0.8 } } }); rememberAction("租户额度已更新", "当前项目的预算与配额已经保存。", "green", saved); await loadAgentControlSurfaces(project.id); focusCreditsWorkspace("credits-quota-anchor"); } }); } function openCreateAssistantAction() { const project = requireSelectedProject(); const kbOptions = getKnowledgeBaseOptions(project.id); const modelOptions = getModelOptions(); openActionModal({ title: "创建 Agent", description: "先定义用途、平台与目标,再让 Agent 学习内容。", submitLabel: "创建 Agent", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, "") }, { name: "projectId", label: "归属项目", type: "select", value: project.id, options: getProjectOptions() }, { name: "name", label: "名称", placeholder: "例如:创业成交助手" }, { name: "description", label: "说明", placeholder: "例如:服务创业 IP 与成交型短视频" }, { name: "goal", label: "生成目标", placeholder: "例如:输出创业口播、对标拆解和成交文案" }, { name: "systemPrompt", label: "系统提示词", type: "textarea", rows: 5, placeholder: "可选,可先留空,后面随时补充" }, { name: "knowledgeBaseId", label: "默认知识库", type: "select", value: kbOptions[0]?.value || "", options: [{ value: "", label: "暂不绑定" }, ...kbOptions] }, { name: "modelProfileId", label: "主模型", type: "select", value: modelOptions.find((item) => item.value === safeArray(appState.dashboard?.model_profiles).find((m) => m.is_default)?.id)?.value || modelOptions[0]?.value || "", options: modelOptions } ], onOpen: ({ fields }) => { bindAssistantSheetRecommendations(fields, { defaultProjectId: project.id, defaultKnowledgeBaseId: kbOptions[0]?.value || "" }); fields.querySelector('[data-action-field="name"]')?.focus(); }, onSubmit: async (values) => { if (!values.name?.trim()) throw new Error("请填写 Agent 名称"); const projectId = values.projectId || project.id; const assistant = await storyforgeFetch("/v2/assistants", { method: "POST", body: { project_id: projectId, name: values.name.trim(), description: values.description || "", generation_goal: values.goal || "", system_prompt: values.systemPrompt || "", knowledge_base_ids: values.knowledgeBaseId ? [values.knowledgeBaseId] : [], model_profile_id: values.modelProfileId || "" } }); appState.selectedAssistantId = assistant.id; rememberAction("Agent 已创建", `已创建 Agent「${assistant.name}」。`, "green", assistant); await bootstrap(); focusPlaybookWorkspace(assistant.id); } }); } function openEditAssistantAction(assistantId = "") { const assistant = safeArray(appState.dashboard?.assistants).find((item) => item.id === assistantId) || getSelectedAssistant(); if (!assistant) { rememberAction("请先选择 Agent", "当前还没有可编辑的 Agent,先在项目里选择或创建一个。", "orange"); renderAll(); return; } const modelOptions = getModelOptions(); const kbOptions = getKnowledgeBaseOptions(assistant.project_id || ""); openActionModal({ title: "编辑 Agent", description: "更新当前 Agent 的名称、目标和主模型,不会影响已完成任务。", submitLabel: "保存 Agent", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(assistant.project_id || "", assistant.id) }, { name: "name", label: "名称", value: assistant.name || "", placeholder: "例如:创业成交助手" }, { name: "description", label: "说明", value: assistant.description || "", placeholder: "例如:服务创业 IP 与成交型短视频" }, { name: "goal", label: "生成目标", value: assistant.generation_goal || "", placeholder: "例如:输出创业口播、对标拆解和成交文案" }, { name: "systemPrompt", label: "系统提示词", type: "textarea", rows: 5, value: assistant.system_prompt || "", placeholder: "可选,可先留空,后面随时补充" }, { name: "knowledgeBaseId", label: "默认知识库", type: "select", value: safeArray(assistant.knowledge_base_ids)[0] || "", options: [{ value: "", label: "暂不绑定" }, ...kbOptions] }, { name: "modelProfileId", label: "主模型", type: "select", value: assistant.model_profile_id || modelOptions[0]?.value || "", options: modelOptions } ], onOpen: ({ fields }) => { bindAssistantSheetRecommendations(fields, { defaultProjectId: assistant.project_id || "", defaultKnowledgeBaseId: safeArray(assistant.knowledge_base_ids)[0] || "", defaultAssistantId: assistant.id }); }, onSubmit: async (values) => { if (!values.name?.trim()) throw new Error("请填写 Agent 名称"); const updated = await storyforgeFetch(`/v2/assistants/${encodeURIComponent(assistant.id)}`, { method: "PATCH", body: { name: values.name.trim(), description: values.description || "", generation_goal: values.goal || "", system_prompt: values.systemPrompt || "", knowledge_base_ids: values.knowledgeBaseId ? [values.knowledgeBaseId] : [], model_profile_id: values.modelProfileId || "" } }); appState.selectedAssistantId = updated.id; rememberAction("Agent 已更新", `已更新 Agent「${updated.name}」。`, "green", updated); await bootstrap(); focusPlaybookWorkspace(updated.id); } }); } function openAnalyzeSelectedAccountAction() { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); const analyzePath = getWorkbenchRoute(platform, "analyzeAccount", account.id); openActionModal({ title: "分析当前对标账号", description: "从商业化和内容运营角度重跑一次账号分析。", submitLabel: "开始分析", fields: [ { name: "maxVideos", label: "纳入分析作品数", type: "number", value: 6, min: 3, max: 20 }, { name: "extraFocus", label: "额外关注点", type: "textarea", rows: 4, placeholder: "例如:更关注商业化承接与私域转化" }, { name: "autoAnalyzeTopVideos", label: "分析后自动补高分作品", type: "checkbox", value: true }, { name: "topVideoCount", label: "高分作品分析数", type: "number", value: 4, min: 1, max: 10 } ], onSubmit: async (values) => { const result = await storyforgeFetch(analyzePath, { method: "POST", body: { model_profile_ids: [], linked_account_ids: [], include_linked_accounts: true, include_recent_similar_candidates: true, max_videos: Number(values.maxVideos || 6), extra_focus: values.extraFocus || "", temperature: 0.35, auto_analyze_top_videos: Boolean(values.autoAnalyzeTopVideos), top_video_analysis_count: Number(values.topVideoCount || 4) } }); const summary = result?.suggestions?.[0]?.parsed_json?.executive_summary || result?.suggestions?.[0]?.suggestion_text || "已生成新的账号分析。"; rememberAction("对标账号分析完成", brief(summary, 120), "green", result); await loadPlatformAccount(platform, account.id); focusDiscoveryInsights(); } }); } function openAnalyzeTopVideosAction() { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); const analyzePath = getWorkbenchRoute(platform, "analyzeTopVideos", account.id); openActionModal({ title: "分析高分作品", description: "对当前对标账号的高分作品批量补分析。", submitLabel: "开始分析", fields: [ { name: "topVideoCount", label: "分析作品数", type: "number", value: 5, min: 1, max: 12 }, { name: "minScore", label: "最低分阈值", type: "number", value: 45, min: 0, max: 100 } ], onSubmit: async (values) => { const result = await storyforgeFetch(analyzePath, { method: "POST", body: { model_profile_id: "", top_video_count: Number(values.topVideoCount || 5), min_score: Number(values.minScore || 45), temperature: 0.25 } }); appState.topVideoAnalysisResults = { ...(appState.topVideoAnalysisResults || {}), [account.id]: { ...result, account_id: account.id, platform, created_at: new Date().toISOString() } }; rememberAction("高分作品分析完成", `已补分析 ${formatNumber(result.analyzed_count)} 条高分作品。`, "green", result); await loadPlatformAccount(platform, account.id); focusDiscoveryTopVideoInsights(); } }); } function openSimilaritySearchAction() { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); const createPath = getWorkbenchRoute(platform, "similarSearches"); openActionModal({ title: "查相似账号", description: "让 Agent 基于当前账号画像找更多可借鉴对象。", submitLabel: "开始查找", fields: [ { name: "maxCandidates", label: "最多候选数", type: "number", value: 8, min: 3, max: 20 }, { name: "extraRequirements", label: "额外要求", type: "textarea", rows: 4, placeholder: "例如:优先找创业成交类、口播结构强的账号" } ], onSubmit: async (values) => { const created = await storyforgeFetch(createPath, { method: "POST", body: { source_account_id: account.id, candidate_urls: [], seed_linked_accounts: true, search_public_pages: true, model_profile_id: "", max_candidates: Number(values.maxCandidates || 8), extra_requirements: values.extraRequirements || "" } }); const searchId = created.id || created.search_id; const detailPath = searchId ? getWorkbenchRoute(platform, "similarSearchDetail", searchId) : ""; const detail = searchId ? await storyforgeFetch(detailPath) : created; appState.lastSimilaritySearch = detail; rememberAction("相似账号已生成", `已生成 ${formatNumber(safeArray(detail.candidates).length)} 个候选账号。`, "green", detail); await loadPlatformAccount(platform, account.id); focusDiscoveryRelations(); } }); } function openBenchmarkLinkAction(defaults = {}) { const account = requireSelectedAccountRow(); const platform = getAccountPlatform(account); const benchmarkPath = getWorkbenchRoute(platform, "benchmarkLinks", account.id); const options = safeArray(appState.accounts) .filter((item) => item.id !== account.id) .map((item) => ({ value: item.id, label: getAccountName(item) || item.id })); const candidate = typeof defaults.candidateIndex === "number" ? safeArray(appState.lastSimilaritySearch?.candidates)[defaults.candidateIndex] || null : null; openActionModal({ title: "保存对标关系", description: "把当前账号和另一个账号关联成对标关系,便于持续跟踪。", submitLabel: "保存关系", fields: [ { name: "targetAccountId", label: "目标账号", type: "select", value: defaults.targetAccountId || candidate?.candidate_account_id || options[0]?.value || "", options: [{ value: "", label: "仅保存主页链接" }, ...options] }, { name: "targetProfileUrl", label: "目标主页链接", type: "url", value: defaults.targetProfileUrl || candidate?.candidate_profile_url || "", placeholder: "没有本地账号时可直接保存主页链接" }, { name: "relationType", label: "关系类型", type: "select", value: "benchmark", options: [ { value: "benchmark", label: "对标" }, { value: "learn", label: "学习" }, { value: "watch", label: "跟踪" } ] }, { name: "note", label: "备注", value: defaults.note || brief(candidate?.rationale_text || "", 120), placeholder: "例如:开场结构很强,适合持续跟踪" } ], onSubmit: async (values) => { if (!values.targetAccountId && !values.targetProfileUrl?.trim()) throw new Error("请先选择一个目标账号或填写主页链接"); const result = await storyforgeFetch(benchmarkPath, { method: "POST", body: { target_account_ids: values.targetAccountId ? [values.targetAccountId] : [], target_profile_urls: values.targetAccountId ? [] : [values.targetProfileUrl.trim()], relation_type: values.relationType || "benchmark", note: values.note || "", search_id: appState.lastSimilaritySearch?.id || "" } }); if (candidate) { markSavedCandidate(candidate, result.links); } else if (appState.selectedWorkspace) { appState.selectedWorkspace = { ...appState.selectedWorkspace, linked_accounts: safeArray(result.links) }; } rememberAction("对标关系已保存", "当前账号的对标关系已更新。", "green"); focusDiscoveryRelations(); } }); } async function scanAdminOpsAction() { if (!isSuperAdmin()) throw new Error("只有平台管理者才能调用运维 Agent。"); setBusy(true, "运维 Agent 正在扫描故障事件..."); let shouldRefocus = false; try { const payload = await storyforgeFetch("/v2/admin/ops/incidents/scan", { method: "POST", body: {} }); rememberAction("运维扫描已完成", `本轮共归集 ${formatNumber(payload.count)} 条故障事件。`, payload.count ? "orange" : "green", payload); await loadAgentControlSurfaces(getOneLinerProjectId()); shouldRefocus = true; } finally { setBusy(false, ""); if (shouldRefocus) { focusAdminOpsWorkspace("admin-ops-anchor"); } else { renderAll(); } } } function openAdminIncidentReviewAction(incidentId) { if (!isSuperAdmin()) { rememberAction("权限不足", "只有平台管理者才能审计处理故障事件。", "orange"); renderAll(); return; } const incident = safeArray(appState.adminOpsOverview?.incidents).find((item) => item.id === incidentId); if (!incident) { rememberAction("故障事件不存在", "当前没有找到这条故障事件,请先重新扫描。", "orange"); renderAll(); return; } openActionModal({ title: "审计处理故障事件", description: "这里代表管理员侧审计 Agent 的放行/退回动作。", submitLabel: "保存审计结果", fields: [ { name: "summary", label: "事件摘要", type: "html", html: `

${escapeHtml(incident.title)}

${escapeHtml(incident.summary || "暂无摘要")}

${escapeHtml(incident.severity || "warn")} ${escapeHtml(incident.status || "open")} ${incident.source_type ? `${escapeHtml(incident.source_type)}` : ""} ${incident.tenant_user_id ? `租户 ${escapeHtml(brief(incident.tenant_user_id, 12))}` : ""}
${incident.source_type === "job" ? actionTag("看任务详情", "open-job-detail", `data-job-id="${escapeHtml(incident.source_id || "")}"`) : ""} ${incident.source_type === "integration" ? actionTag("去自动流程", "goto-automation") : ""} ${incident.tenant_project_id ? actionTag("去生产中心", "goto-production") : ""} 重新扫描
` }, { name: "status", label: "处理状态", type: "select", value: incident.status || "reviewed", options: [{ value: "reviewed", label: "已审阅" }, { value: "watching", label: "继续观察" }, { value: "resolved", label: "已解决" }, { value: "rejected", label: "驳回修复方案" }] }, { name: "reviewNotes", label: "审计备注", type: "textarea", rows: 5, value: incident.review_notes || "", placeholder: "写清楚为什么放行、退回或继续观察" } ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/admin/ops/incidents/${encodeURIComponent(incident.id)}`, { method: "PATCH", body: { status: values.status || "reviewed", review_notes: values.reviewNotes || "" } }); rememberAction("审计结果已保存", `事件「${saved.title}」已更新为 ${saved.status}。`, "green", saved); await loadAgentControlSurfaces(getOneLinerProjectId()); focusAdminOpsWorkspace("admin-ops-anchor"); } }); } function openAdminRepairPlanAction(incidentId) { if (!isSuperAdmin()) { rememberAction("权限不足", "只有平台管理者才能生成修复计划。", "orange"); renderAll(); return; } const incident = safeArray(appState.adminOpsOverview?.incidents).find((item) => item.id === incidentId); if (!incident) { rememberAction("故障事件不存在", "当前没有找到这条故障事件,请先重新扫描。", "orange"); renderAll(); return; } openActionModal({ title: "生成修复计划", description: "让运维 Agent 先生成一版 repair plan,再由审计 Agent 决定是否放行。", submitLabel: "生成计划", fields: [ { name: "scope", label: "计划范围", type: "select", value: "plan", options: [{ value: "plan", label: "标准计划" }, { value: "hotfix", label: "热修建议" }, { value: "watch", label: "仅观察" }] }, { name: "notes", label: "附加说明", type: "textarea", rows: 4, placeholder: "例如:优先验证 cutvideo 上传链,不要动核心代码" } ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/admin/ops/incidents/${encodeURIComponent(incidentId)}/repair-plan`, { method: "POST", body: { incident_id: incidentId, scope: values.scope || "plan", notes: values.notes || "" } }); rememberAction("修复计划已生成", `已为事件「${incident.title}」生成 repair plan。`, "green", saved); await loadAgentControlSurfaces(getOneLinerProjectId()); focusAdminOpsWorkspace("admin-ops-anchor"); } }); } function openAdminFixRunDetailAction(runId) { if (!isSuperAdmin()) { rememberAction("权限不足", "只有平台管理者才能查看修复计划。", "orange"); renderAll(); return; } const run = safeArray(appState.adminFixRuns.length ? appState.adminFixRuns : appState.adminOpsOverview?.recent_fix_runs).find((item) => item.id === runId); if (!run) { rememberAction("修复计划不存在", "当前没有找到这条修复计划,请先刷新管理员配置台。", "orange"); renderAll(); return; } openActionModal({ title: "修复计划详情", description: "查看这条修复计划的完整上下文,再决定是否放行。", hideSubmit: true, fields: [ { type: "html", label: "详情", html: `

${escapeHtml(run.plan?.summary || run.id)}

${escapeHtml(safeArray(run.plan?.steps).join(";") || "暂无步骤")}

${escapeHtml(run.plan_scope || "plan")} ${escapeHtml(run.audit_status || "pending")} ${run.status ? `${escapeHtml(run.status)}` : ""} ${run.incident_id ? `${escapeHtml(brief(run.incident_id, 12))}` : ""}

Plan

${escapeHtml(JSON.stringify(run.plan || {}, null, 2))}

Verification

${escapeHtml(JSON.stringify(run.verification || {}, null, 2))}
` } ] }); } function openAdminFixRunAuditAction(runId) { if (!isSuperAdmin()) { rememberAction("权限不足", "只有平台管理者才能审计修复计划。", "orange"); renderAll(); return; } const run = safeArray(appState.adminFixRuns.length ? appState.adminFixRuns : appState.adminOpsOverview?.recent_fix_runs).find((item) => item.id === runId); if (!run) { rememberAction("修复计划不存在", "当前没有找到这条修复计划,请先刷新管理员配置台。", "orange"); renderAll(); return; } openActionModal({ title: "审计修复计划", description: "审计 Agent 只做放行、驳回或继续观察,不会直接让用户一句话改核心代码。", submitLabel: "保存审计", fields: [ { name: "summary", label: "计划摘要", type: "html", html: `

${escapeHtml(run.plan?.summary || run.id)}

${escapeHtml(safeArray(run.plan?.steps).join(";") || "暂无步骤")}

${run.incident_id ? `事件 ${escapeHtml(brief(run.incident_id, 12))}` : ""} ${run.updated_at ? `${escapeHtml(formatDateTime(run.updated_at))}` : ""} 查看详情
` }, { name: "reviewStatus", label: "审计状态", type: "select", value: run.audit_status || "approved", options: [{ value: "approved", label: "通过" }, { value: "watching", label: "继续观察" }, { value: "rejected", label: "驳回" }] }, { name: "reviewNotes", label: "审计备注", type: "textarea", rows: 4, value: run.review_notes || "", placeholder: "写清楚为什么通过、驳回或继续观察" } ], onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/admin/ops/fix-runs/${encodeURIComponent(runId)}/audit`, { method: "POST", body: { review_status: values.reviewStatus || "approved", review_notes: values.reviewNotes || "" } }); rememberAction("修复计划已审计", `修复计划 ${runId} 已更新为 ${saved.audit_status || values.reviewStatus}。`, "green", saved); await loadAgentControlSurfaces(getOneLinerProjectId()); focusAdminOpsWorkspace("admin-ops-anchor"); } }); } function openJobDetailAction(jobId) { if (!jobId) return; setBusy(true, "正在加载任务详情..."); loadJobDetail(jobId) .then(({ job, events, childJobs }) => { const artifacts = JSON.stringify(job.artifacts || {}, null, 2); const result = JSON.stringify(job.result || {}, null, 2); const previewLinks = getJobPreviewLinks(job); const recovery = getJobRecoverability(job); openActionModal({ title: job.title || "任务详情", description: `状态:${job.status || "-"} · 类型:${job.line_type || job.source_type || "-"}`, hideSubmit: true, fields: [ { type: "html", label: "任务摘要", html: `
任务 ID${escapeHtml(job.id)}
状态${escapeHtml(job.status || "-")}
链路${escapeHtml(job.line_type || "-")}
创建时间${escapeHtml(formatDateTime(job.created_at))}
` }, { type: "html", label: "事件时间线", html: `
${safeArray(events).slice(-6).map((event) => `

${escapeHtml(event.event_type || "event")}

${escapeHtml(brief(event.message || JSON.stringify(event.payload || {}), 120))}

`).join("") || `

暂无事件

当前任务还没有可显示的事件。

`}
` }, { type: "html", label: "结果预览", html: `
${previewLinks.map((item) => `

${escapeHtml(item.label.replace(/^result\./, "").replace(/^artifacts\./, ""))}

${escapeHtml(item.url)}

`).join("") || `

暂无外部结果链接

当前先保留 artifacts 和 result 原始数据供查看。

`}
` }, { type: "html", label: "下一步动作", html: `
${job.status === "failed" ? actionTag( recovery.actionLabel, recovery.recoverable ? "recover-job" : recovery.actionKey, `data-job-id="${escapeHtml(job.id)}"`, { disabledReason: recovery.recoverable ? "" : recovery.reason, title: recovery.reason } ) : ""} ${canDeriveAiVideo(job) ? renderPipelineJobTag("aiVideo", job, "继续做 AI 视频") : ""} ${canDeriveRealCut(job) ? renderPipelineJobTag("realCut", job, "继续做实拍剪辑") : ""} ${actionTag("用摘要写文案", "direct-generate-copy", `data-job-id="${escapeHtml(job.id)}"`)}
` }, { type: "html", label: "恢复判断", html: `

${escapeHtml(recovery.label)}

${escapeHtml(recovery.reason)}

${escapeHtml(recovery.actionLabel)} ${recovery.sourceJobId ? `源任务 ${escapeHtml(brief(recovery.sourceJobId, 12))}` : ""} ${recovery.quotaGuard?.blocked ? `额度拦截` : ""}
` }, { type: "html", label: "子任务", html: `
${safeArray(childJobs).slice(0, 6).map((item) => `

${escapeHtml(item.title || item.id)}

${escapeHtml(brief(item.style_summary || item.transcript_text || item.error || "暂无摘要", 96))}

${escapeHtml(item.status || "-")} ${escapeHtml(item.line_type || "-")} 看详情
`).join("") || `

暂无子任务

当前任务还没有派生出下一层任务。

`}
` }, { type: "textarea", name: "artifactsReadonly", label: "Artifacts", value: artifacts, rows: 8 }, { type: "textarea", name: "resultReadonly", label: "Result", value: result, rows: 8 } ] }); document.querySelector('[data-action-field="artifactsReadonly"]')?.setAttribute("readonly", "readonly"); document.querySelector('[data-action-field="resultReadonly"]')?.setAttribute("readonly", "readonly"); }) .catch((error) => { presentActionFailure(error, "加载任务详情失败"); }) .finally(() => { setBusy(false, ""); }); } function openRecoverJobAction(jobId) { const job = safeArray(appState.dashboard?.recent_jobs).find((item) => item.id === jobId) || (appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null); if (!job) { rememberAction("任务不存在", "当前没有找到这条任务,请先刷新生产中心。", "orange"); renderAll(); return; } const recovery = getJobRecoverability(job); if (!recovery.recoverable) { const guidance = getRecoverJobGuidance(job, recovery); openActionModal({ title: "失败任务处理建议", description: guidance.summary || recovery.reason || "先补信息,再继续推进。", hideSubmit: true, fields: [ { type: "html", label: "处理建议", html: renderRecoverJobGuidanceHtml(job, recovery, guidance) } ] }); return; } openActionModal({ title: "恢复失败任务", description: recovery.reason, submitLabel: recovery.actionLabel, fields: [ { type: "html", label: "恢复摘要", html: `

${escapeHtml(job.title || job.id)}

${escapeHtml(brief(job.error || recovery.reason || "任务失败,可重新发起。", 120))}

${escapeHtml(recovery.label)} ${escapeHtml(job.line_type || job.source_type || "analysis")} ${recovery.sourceJobId ? `源任务 ${escapeHtml(brief(recovery.sourceJobId, 12))}` : ""}
${recovery.quotaGuard?.blocked ? `

额度提醒

${escapeHtml(recovery.quotaGuard.reason)}

` : ""}
` }, { name: "note", label: "恢复备注", type: "textarea", rows: 4, value: "", placeholder: "可写明为什么需要恢复,或补充额外背景" } ], onSubmit: async (values) => { setBusy(true, "正在恢复任务..."); try { const result = await recoverJobAction(job.id, { mode: "single", job, user_feedback: values.note?.trim() || "" }); rememberAction("任务已恢复", `${job.title || job.id} 已重新发起,下一步可以去生产中心继续跟进。`, "green", result); await bootstrap(); if (result?.created?.id) { openJobDetailAction(result.created.id); } else { focusProductionDetailTab("recovery"); } } finally { setBusy(false, ""); } } }); } function openBatchRecoverJobsAction() { const failedJobs = getRecoverableFailedJobs(); const selectable = failedJobs.filter((item) => item.recovery.recoverable); if (!selectable.length) { rememberAction("没有可恢复任务", "当前没有可直接恢复的失败任务。", "orange"); renderAll(); return; } openActionModal({ title: "批量恢复失败任务", description: "只会选中当前可自动恢复的失败任务;需要人工补素材或补额度的任务会保留在列表里。", submitLabel: "批量恢复", fields: [ { type: "html", label: "批量摘要", html: `

可恢复 ${escapeHtml(formatNumber(selectable.length))} 条

${escapeHtml(`最近失败任务共 ${formatNumber(failedJobs.length)} 条,其中 ${formatNumber(failedJobs.length - selectable.length)} 条需要人工处理。`)}

${escapeHtml(formatNumber(selectable.length))} 可恢复 ${escapeHtml(formatNumber(failedJobs.length - selectable.length))} 需人工
` }, ...selectable.map(({ job, recovery }, index) => ({ name: `recoverJob_${index}`, label: `${job.title || job.id} · ${recovery.reason}`, type: "checkbox", value: true })), { name: "note", label: "批量恢复备注", type: "textarea", rows: 4, value: "", placeholder: "可写明这次批量恢复的背景或处理策略" } ], onSubmit: async (values) => { const chosen = selectable.filter((item, index) => values[`recoverJob_${index}`]).map((item) => item.job); if (!chosen.length) throw new Error("请至少选择一个可恢复任务"); setBusy(true, "正在批量恢复任务..."); const successes = []; const failures = []; try { for (const job of chosen) { try { const result = await recoverJobAction(job.id, { mode: "batch", job }); successes.push({ job, result }); } catch (error) { failures.push({ job, error: formatActionErrorMessage(error, "恢复失败") }); } } rememberAction( "批量恢复已完成", failures.length ? `已恢复 ${successes.length} 条,另有 ${failures.length} 条失败。` : `已恢复 ${successes.length} 条失败任务。`, failures.length ? "orange" : "green", { successes, failures } ); if (values.note?.trim()) { recordRecoveryEvent({ id: `batch_${Date.now()}`, account_id: appState.session?.account?.id || "", project_id: appState.selectedProjectId || "", job_title: "批量恢复", job_line_type: "batch", job_source_type: "production", job_status: "completed", action_key: "batch-recover-jobs", mode: "batch", summary: `批量恢复 ${successes.length} 条任务`, reason: values.note.trim(), result_label: failures.length ? "部分成功" : "全部成功", result_reason: failures.length ? `失败 ${failures.length} 条` : "全部成功" }); } await bootstrap(); if (successes[0]?.result?.created?.id) { openJobDetailAction(successes[0].result.created.id); } else { focusProductionDetailTab("recovery"); } } finally { setBusy(false, ""); } } }); } function openGenerateCopyAction(defaults = {}) { const assistant = getSelectedAssistant() || requireSelectedAssistant(); const sourceJob = defaults.sourceJob || null; const project = getSelectedProject(); const defaultPlatform = normalizePlatformValue(defaults.platform || sourceJob?.platform || "douyin"); openActionModal({ title: "生成文案", description: "用当前 Agent 和知识库生成一版短视频文案。", submitLabel: "开始生成", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project?.id || "", assistant.id) }, ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []), { name: "brief", label: "创作需求", type: "textarea", rows: 5, value: defaults.brief || getJobSeedBrief(sourceJob), placeholder: "例如:给创业者写一条 60 字内的短视频开场文案" }, { name: "platform", label: "平台", type: "select", value: defaultPlatform, options: getPlatformOptions() }, { name: "audience", label: "受众", value: defaults.audience || recommendAudienceForPlatform(defaultPlatform) }, { name: "extraRequirements", label: "额外要求", placeholder: "例如:强结论开头,结尾带 CTA" } ], onOpen: ({ fields }) => { bindCreativeSourceJobRecommendations(fields, { sourceJob, defaultPlatform }); }, onSubmit: async (values) => { if (!values.brief?.trim()) throw new Error("请填写创作需求"); const result = await storyforgeFetch(`/v2/assistants/${encodeURIComponent(assistant.id)}/generate`, { method: "POST", body: { brief: values.brief.trim(), platform: platformLabel(values.platform || "douyin"), audience: values.audience || "创业者", extra_requirements: values.extraRequirements || "", knowledge_base_ids: safeArray(assistant.knowledge_base_ids) } }); appState.lastGeneratedCopy = { assistantId: assistant.id, assistantName: assistant.name, prompt: values.brief.trim(), content: extractGeneratedCopy(result), usedDocuments: safeArray(result.used_documents).slice(0, 3) }; rememberAction("文案生成完成", `已用 Agent「${assistant.name}」生成一版文案。`, "green", result); focusRecentGeneratedCopy(); } }); } function openCreateAiVideoAction(defaults = {}) { const guard = getPipelineGuard("aiVideo"); if (!guard.enabled) { presentActionFailure(new Error(guard.reason), "AI 视频暂不可用"); return; } const project = requireSelectedProject(); const assistant = getSelectedAssistant(); const kb = getProjectKnowledgeBases(project.id)[0]; const defaultAssistantId = assistant?.id || ""; const sourceJob = defaults.sourceJob || null; const defaultPlatform = normalizePlatformValue(defaults.platform || sourceJob?.platform || "douyin"); const defaultVideoProvider = String( defaults.videoProvider || defaults.video_provider || sourceJob?.artifacts?.video_provider || "doubao" ).trim() || "doubao"; const defaultVideoModel = String( defaults.videoModel || defaults.video_model || sourceJob?.artifacts?.video_model || "" ).trim() || (defaultVideoProvider === "seedance2" ? "seedance-2.0-pro" : ""); openActionModal({ title: "创建 AI 视频任务", description: "输入 brief 后,直接触发 AI 视频链。需要更强镜头语言时,可以切到 Seedance 2.0。", submitLabel: "开始生产", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []), { name: "title", label: "任务标题", value: defaults.title || recommendDerivativeJobTitle(sourceJob, "AI 视频", ""), placeholder: "例如:创业口播 AI 视频测试" }, { name: "brief", label: "视频 brief", type: "textarea", rows: 5, value: defaults.brief || getJobSeedBrief(sourceJob), placeholder: "写明主题、风格、镜头和目标受众" }, { name: "sourceJobId", label: "关联源任务", type: "select", value: defaults.sourceJobId || sourceJob?.id || "", options: [{ value: "", label: "不关联" }, ...getCompletedJobOptions()] }, { name: "videoProvider", label: "视频引擎", type: "select", value: defaultVideoProvider, options: [ { value: "doubao", label: "当前默认引擎" }, { value: "seedance2", label: "Seedance 2.0" }, ], }, { name: "videoModel", label: "引擎模型", value: defaultVideoModel, placeholder: "例如:seedance-2.0-pro", }, { name: "style", label: "风格", value: defaults.style || recommendCreativeStyle(sourceJob) }, { name: "aspectRatio", label: "画幅", type: "select", value: defaults.aspectRatio || defaults.aspect_ratio || sourceJob?.artifacts?.aspect_ratio || recommendAspectRatioForPlatform(defaultPlatform), options: [ { value: "9:16", label: "9:16 竖屏" }, { value: "16:9", label: "16:9 横屏" }, { value: "1:1", label: "1:1 方形" }, ], }, { name: "shots", label: "镜头数", type: "number", value: defaults.shots || 4, min: 1, max: 12 }, { name: "duration", label: "单镜头秒数", type: "number", value: defaults.duration || recommendAiVideoShotDuration(sourceJob), min: 3, max: 12 }, { name: "cameraLanguage", label: "镜头语言", type: "textarea", rows: 3, value: defaults.cameraLanguage || defaults.camera_language || "", placeholder: "例如:开场推近,中段快速切换,结尾定格主卖点", }, { name: "motionRhythm", label: "运动节奏", value: defaults.motionRhythm || defaults.motion_rhythm || "", placeholder: "例如:强节奏推进,镜头切换干净利落", }, { name: "visualGuardrails", label: "风格约束", type: "textarea", rows: 3, value: defaults.visualGuardrails || defaults.visual_guardrails || "", placeholder: "例如:避免过暗,人物手部自然,保持真实商业质感", } ], onOpen: ({ fields }) => { bindCreativeSourceJobRecommendations(fields, { sourceJob, defaultPlatform, titleSuffix: "AI 视频" }); }, onSubmit: async (values) => { if (!values.title?.trim()) throw new Error("请填写任务标题"); if (!values.brief?.trim()) throw new Error("请填写视频 brief"); const normalizedProvider = String(values.videoProvider || "doubao").trim() || "doubao"; const normalizedVideoModel = String(values.videoModel || "").trim() || (normalizedProvider === "seedance2" ? "seedance-2.0-pro" : ""); const seedanceSuffix = normalizedProvider === "seedance2" ? [ values.cameraLanguage?.trim() ? `镜头语言:${values.cameraLanguage.trim()}` : "", values.motionRhythm?.trim() ? `运动节奏:${values.motionRhythm.trim()}` : "", values.visualGuardrails?.trim() ? `风格约束:${values.visualGuardrails.trim()}` : "", ].filter(Boolean).join("\n") : ""; const finalBrief = [values.brief.trim(), seedanceSuffix].filter(Boolean).join("\n\n"); const job = await storyforgeFetch("/v2/pipelines/ai-video", { method: "POST", body: { project_id: project.id, assistant_id: assistant?.id || "", knowledge_base_id: kb?.id || "", source_job_id: values.sourceJobId || "", title: values.title.trim(), brief: finalBrief, style: values.style || "realistic", aspect_ratio: values.aspectRatio || "9:16", shots: Number(values.shots || 4), duration: Number(values.duration || 5), video_provider: values.videoProvider || "doubao", video_model: values.videoModel || "", } }); rememberAction( "AI 视频任务已创建", `已创建任务 ${job.title || job.id},引擎 ${normalizedProvider === "seedance2" ? "Seedance 2.0" : "当前默认引擎"}。`, "blue", job ); await bootstrap(); if (job?.id) { openJobDetailAction(job.id); } } }); } function openCreateRealCutAction(defaults = {}) { const guard = getPipelineGuard("realCut"); if (!guard.enabled) { presentActionFailure(new Error(guard.reason), "实拍剪辑暂不可用"); return; } const project = requireSelectedProject(); const sourceJob = defaults.sourceJob || null; const assistant = getSelectedAssistant(); const defaultPlatform = normalizePlatformValue(defaults.platform || sourceJob?.platform || "douyin"); openActionModal({ title: "创建实拍剪辑任务", description: "基于已完成的源任务,把素材发到 cutvideo。", submitLabel: "开始剪辑", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, assistant?.id || "") }, ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []), { name: "title", label: "任务标题", value: defaults.title || recommendDerivativeJobTitle(sourceJob, "实拍剪辑", ""), placeholder: "例如:创业素材粗剪" }, { name: "sourceJobId", label: "源任务", type: "select", value: defaults.sourceJobId || sourceJob?.id || getCompletedJobOptions()[0]?.value || "", options: getCompletedJobOptions() }, { name: "targetDurationSec", label: "目标时长(秒)", type: "number", value: defaults.targetDurationSec || recommendRealCutDuration(sourceJob), min: 10, max: 300 }, { name: "aspectRatio", label: "画幅", value: defaults.aspectRatio || sourceJob?.artifacts?.aspect_ratio || recommendAspectRatioForPlatform(defaultPlatform) }, { name: "objective", label: "目标", type: "textarea", rows: 4, value: defaults.objective || recommendRealCutObjective(sourceJob), placeholder: "例如:保留高信息密度片段,输出适合短视频平台的粗剪结果" } ], onOpen: ({ fields }) => { bindCreativeSourceJobRecommendations(fields, { sourceJob, defaultPlatform, titleSuffix: "实拍剪辑" }); }, onSubmit: async (values) => { if (!values.title?.trim()) throw new Error("请填写任务标题"); if (!values.sourceJobId) throw new Error("请先选择一个已完成的源任务"); const job = await storyforgeFetch("/v2/pipelines/real-cut", { method: "POST", body: { project_id: project.id, title: values.title.trim(), source_job_id: values.sourceJobId, target_duration_sec: Number(values.targetDurationSec || 60), target_aspect_ratio: values.aspectRatio || "9:16", objective: values.objective || "保留高信息密度片段,输出适合短视频平台的粗剪结果" } }); rememberAction("实拍剪辑任务已创建", `已创建任务 ${job.title || job.id}。`, "blue", job); await bootstrap(); if (job?.id) { openJobDetailAction(job.id); } } }); } function openLiveRecorderAction() { focusLiveRecorderMaintenance(); } function openLiveRecorderCreateAction() { const status = getIntegrationDetail("live_recorder"); const project = getSelectedProject() || appState.dashboard?.projects?.[0] || null; const assistants = getAssistantOptions(project?.id || ""); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "kuaishou"); const defaultTitle = recommendLiveRecorderTitle(project, defaultPlatform); openActionModal({ title: "新增录制源", description: status.reachable ? "新增的是你当前租户名下的录制源。文件访问和录制状态也只会回到你的账号视图里。" : "当前 NAS 录制服务不可达,先检查集成健康。", submitLabel: "保存录制源", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project?.id || "", defaultAssistantId) }, { type: "html", label: "当前租户", html: renderLiveRecorderSummaryHtml() }, { name: "projectId", label: "归属项目", type: "select", value: project?.id || "", options: getProjectOptions() }, { name: "assistantId", label: "关联 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, { name: "platform", label: "平台", type: "select", value: defaultPlatform, options: getPlatformOptions() }, { name: "title", label: "录制名称", value: defaultTitle, placeholder: defaultTitle }, { name: "quality", label: "清晰度", type: "select", value: "原画", options: ["原画", "蓝光", "超清", "高清", "标清", "流畅"].map((item) => ({ value: item, label: item })) }, { name: "sourceUrl", label: "直播源", type: "url", placeholder: "https://..." }, { name: "autoStart", label: "导入后立即开始", type: "checkbox", value: true } ], onOpen: ({ fields }) => { bindLiveRecorderSheetRecommendations(fields, { defaultPlatform, defaultAssistantId }); }, onSubmit: async (values) => { if (!values.sourceUrl?.trim()) throw new Error("请填写直播源链接"); const saved = await storyforgeFetch("/v2/live-recorder/sources", { method: "POST", body: { project_id: values.projectId || project?.id || "", assistant_id: values.assistantId || "", platform: normalizePlatformValue(values.platform, "kuaishou"), source_url: values.sourceUrl.trim(), title: values.title || "", quality: values.quality || "原画", enabled: true } }); 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", { saved, started }); await bootstrap(); focusLiveRecorderMaintenance(); } }); } function openLiveRecorderSourceAction(sourceId) { const source = safeArray(appState.liveRecorderSources).find((item) => item.id === sourceId); if (!source) { rememberAction("录制源不存在", "当前没有找到这条录制源,请先刷新录制维护。", "orange"); renderAll(); return; } const currentProject = getSelectedProject() || safeArray(appState.dashboard?.projects).find((item) => item.id === source.project_id) || appState.dashboard?.projects?.[0] || null; const assistants = getAssistantOptions(currentProject?.id || source.project_id || ""); const defaultAssistantId = source.assistant_id || getSelectedAssistant()?.id || assistants[0]?.value || ""; const titlePlaceholder = recommendLiveRecorderTitle(currentProject, source.platform || "kuaishou"); openActionModal({ title: "编辑录制源", description: "可以更新项目归属、Agent、标题、清晰度和启停状态;链接本身若要变更,请删除后重建。", submitLabel: "保存修改", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(currentProject?.id || source.project_id || "", defaultAssistantId) }, { type: "html", label: "源信息", html: `

${escapeHtml(source.title || source.remote_name || "录制源")}

${escapeHtml(source.source_url || "暂无链接")}

${escapeHtml(platformLabel(source.platform || "kuaishou"))} ${escapeHtml(source.quality || "原画")} ${escapeHtml(source.enabled ? "启用" : "停用")}
` }, { name: "projectId", label: "归属项目", type: "select", value: source.project_id || currentProject?.id || "", options: getProjectOptions() }, { name: "assistantId", label: "关联 Agent", type: "select", value: defaultAssistantId, options: [{ value: "", label: "暂不绑定" }, ...assistants] }, { name: "title", label: "录制名称", value: source.title || "", placeholder: titlePlaceholder }, { name: "quality", label: "清晰度", type: "select", value: source.quality || "原画", options: ["原画", "蓝光", "超清", "高清", "标清", "流畅"].map((item) => ({ value: item, label: item })) }, { name: "enabled", label: "启用录制源", type: "checkbox", value: Boolean(source.enabled) } ], onOpen: ({ fields }) => { bindLiveRecorderSheetRecommendations(fields, { defaultPlatform: source.platform || "kuaishou", defaultAssistantId }); }, onSubmit: async (values) => { const saved = await storyforgeFetch(`/v2/live-recorder/sources/${encodeURIComponent(source.id)}`, { method: "PATCH", body: { project_id: values.projectId || "", assistant_id: values.assistantId || "", title: values.title || "", quality: values.quality || "原画", enabled: Boolean(values.enabled) } }); rememberAction("录制源已更新", `已保存「${saved.item?.title || source.title || "录制源"}」。`, "green", saved); await bootstrap(); focusLiveRecorderMaintenance(); } }); } function openLiveRecorderImportAction() { const project = getSelectedProject() || appState.dashboard?.projects?.[0] || null; const assistants = getAssistantOptions(project?.id || ""); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const defaultPlatform = normalizePlatformValue(getPreferredPlatform(), "kuaishou"); const samples = recommendLiveRecorderImportSamples(defaultPlatform); openActionModal({ title: "导入 URL 配置", description: `按行粘贴${platformLabel(defaultPlatform)}直播源,支持用逗号附带清晰度和标题,注释行会被视为停用源。`, submitLabel: "导入并同步", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project?.id || "", defaultAssistantId) }, { name: "platform", label: "平台", type: "select", value: defaultPlatform, options: getPlatformOptions() }, { name: "raw", label: "配置文本", type: "textarea", rows: 10, value: samples, placeholder: "一行一个 URL,支持 # 注释和 逗号分隔的清晰度/标题" } ], onOpen: ({ fields, description }) => { bindLiveRecorderSheetRecommendations(fields, { defaultPlatform, description }); }, onSubmit: async (values) => { if (!String(values.raw || "").trim()) throw new Error("请先粘贴配置文本"); const saved = await storyforgeFetch("/v2/live-recorder/url-config/import", { method: "POST", body: { raw: values.raw } }); rememberAction("URL 配置已导入", `已导入 ${formatNumber(saved.count || 0)} 条录制源。`, "green", saved); await bootstrap(); focusLiveRecorderMaintenance(); } }); } async function toggleLiveRecorderSourceAction(sourceId, nextEnabled) { const source = safeArray(appState.liveRecorderSources).find((item) => item.id === sourceId); if (!source) { rememberAction("录制源不存在", "当前没有找到这条录制源,请先刷新录制维护。", "orange"); renderAll(); return; } setBusy(true, nextEnabled ? "正在启用录制源..." : "正在停用录制源..."); try { await storyforgeFetch(`/v2/live-recorder/sources/${encodeURIComponent(source.id)}`, { method: "PATCH", body: { enabled: Boolean(nextEnabled) } }); rememberAction(nextEnabled ? "录制源已启用" : "录制源已停用", `${source.title || source.source_url || "录制源"} 已更新。`, "green"); await bootstrap(); focusLiveRecorderMaintenance(); } finally { setBusy(false, ""); } } async function deleteLiveRecorderSourceAction(sourceId) { const source = safeArray(appState.liveRecorderSources).find((item) => item.id === sourceId); if (!source) { rememberAction("录制源不存在", "当前没有找到这条录制源,请先刷新录制维护。", "orange"); renderAll(); return; } openActionModal({ title: "确认删除录制源", description: `确认删除「${source.title || source.source_url || "录制源"}」吗?删除后需要重新导入。`, submitLabel: "确认删除", onSubmit: async () => { setBusy(true, "正在删除录制源..."); try { await storyforgeFetch(`/v2/live-recorder/sources/${encodeURIComponent(source.id)}`, { method: "DELETE" }); rememberAction("录制源已删除", `${source.title || source.source_url || "录制源"} 已从租户视图中移除。`, "green"); await bootstrap(); focusLiveRecorderMaintenance(); } finally { setBusy(false, ""); } } }); } async function openLiveRecorderFileAction(fileId) { const target = safeArray(appState.liveRecorderFiles).find((item) => item.id === fileId); if (!target?.content_url) { throw new Error("当前录像文件不存在,可能已经被移除"); } const blob = await storyforgeFetchBlob(target.content_url); const blobUrl = URL.createObjectURL(blob); window.open(blobUrl, "_blank", "noopener,noreferrer"); window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60000); } async function openStorageArtifactAction(fileId) { const usage = appState.storageStatus?.tenant_usage || {}; const candidates = [ ...safeArray(usage.recent_download_artifacts), ...safeArray(usage.recent_job_artifacts), ...safeArray(appState.storageStatus?.recent_files), ...safeArray(appState.storageStatus?.recent_artifacts) ]; const target = candidates.find((item) => item.id === fileId); if (!target?.content_url) { throw new Error("当前产物不存在,可能已经被清理"); } const blob = await storyforgeFetchBlob(target.content_url); const blobUrl = URL.createObjectURL(blob); window.open(blobUrl, "_blank", "noopener,noreferrer"); window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60000); } function openReviewAction(defaults = {}) { const project = requireSelectedProject(); const assistants = getAssistantOptions(project.id); const defaultAssistantId = getSelectedAssistant()?.id || assistants[0]?.value || ""; const sourceJob = defaults.sourceJob || null; const existingReview = defaults.review || null; const metrics = existingReview?.metrics || {}; openActionModal({ title: existingReview ? "编辑复盘" : "写复盘", description: existingReview ? "补充表现数据、判断和下一步动作,持续迭代项目策略。" : "把完成任务写成一条可追踪复盘,可按项目累计。", submitLabel: existingReview ? "保存复盘" : "创建复盘", fields: [ { name: "context", label: "当前上下文", type: "html", html: renderIntakeActionContextHtml(project.id, defaultAssistantId) }, ...(sourceJob ? [{ name: "sourceJobContext", label: "来源任务", type: "html", html: renderSourceJobContextHtml(sourceJob) }] : []), { name: "title", label: "标题", value: existingReview?.title || defaults.title || recommendDerivativeJobTitle(sourceJob, "复盘", ""), 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 || defaultAssistantId, options: [{ value: "", label: "先不绑定" }, ...assistants] }, { name: "platform", label: "平台", type: "select", value: normalizePlatformValue(existingReview?.platform || defaults.platform || sourceJob?.platform || "douyin"), options: getPlatformOptions() }, { name: "contentType", label: "内容类型", type: "select", value: existingReview?.content_type || "video", options: [ { value: "video", label: "视频" }, { value: "image_text", label: "图文" }, { value: "live_clip", label: "直播切片" } ] }, { name: "publishUrl", label: "发布链接", type: "url", value: existingReview?.publish_url || "", placeholder: "https://..." }, { name: "publishedAt", label: "发布时间", value: existingReview?.published_at || "", placeholder: "2026-03-22T20:00:00+08:00" }, { name: "playCount", label: "播放", type: "number", value: metrics.play_count || 0, min: 0 }, { name: "likeCount", label: "点赞", type: "number", value: metrics.like_count || 0, min: 0 }, { name: "commentCount", label: "评论", type: "number", value: metrics.comment_count || 0, min: 0 }, { name: "shareCount", label: "分享", type: "number", value: metrics.share_count || 0, min: 0 }, { name: "verdict", label: "结论", type: "select", value: existingReview?.verdict || "", options: [ { value: "", label: "先不下结论" }, { value: "worth_scaling", label: "值得放大" }, { value: "needs_rework", label: "需要重做" }, { value: "good_reference", label: "适合借鉴" }, { value: "hold", label: "先观察" } ] }, { name: "highlights", label: "亮点", type: "textarea", rows: 4, value: existingReview?.highlights || "", placeholder: "例如:开头 3 秒抓人、评论区问题很集中" }, { name: "nextActions", label: "下一步", type: "textarea", rows: 4, value: existingReview?.next_actions || "", placeholder: "例如:保留结构,换一个细分人群再做一条" }, { name: "notes", label: "备注", type: "textarea", rows: 4, value: existingReview?.notes || "", placeholder: "补充团队讨论、平台环境、发布时间段等信息" } ], onOpen: ({ fields }) => { bindCreativeSourceJobRecommendations(fields, { sourceJob, defaultPlatform: normalizePlatformValue(existingReview?.platform || defaults.platform || sourceJob?.platform || "douyin"), titleSuffix: "复盘", projectId: project.id, defaultAssistantId: existingReview?.assistant_id || defaultAssistantId }); }, onSubmit: async (values) => { if (!values.title?.trim()) throw new Error("请填写复盘标题"); const payload = { project_id: project.id, source_job_id: values.sourceJobId || "", assistant_id: values.assistantId || "", title: values.title.trim(), platform: normalizePlatformValue(values.platform, "douyin"), content_type: values.contentType || "video", publish_url: values.publishUrl || "", published_at: values.publishedAt || "", metrics: { play_count: Number(values.playCount || 0), like_count: Number(values.likeCount || 0), comment_count: Number(values.commentCount || 0), share_count: Number(values.shareCount || 0) }, verdict: values.verdict || "", highlights: values.highlights || "", next_actions: values.nextActions || "", notes: values.notes || "" }; const review = existingReview ? await storyforgeFetch(`/v2/reviews/${encodeURIComponent(existingReview.id)}`, { method: "PATCH", body: payload }) : await storyforgeFetch("/v2/reviews", { method: "POST", body: payload }); rememberAction(existingReview ? "复盘已更新" : "复盘已创建", `已保存「${review.title}」并回写到项目复盘。`, "green", review); await bootstrap(); focusReviewWorkspace(review.id); } }); } document.addEventListener("click", async (event) => { if (event.target instanceof HTMLElement && event.target.classList.contains("oneliner-backdrop")) { closeOneLinerPanel(); return; } const action = event.target.closest("[data-action]"); if (action) { const name = action.dataset.action; if (name === "open-mobile-sidebar") { setMobileSidebarOpen(true); return; } if (name === "close-mobile-sidebar") { if (action.closest(".sidebar") || action.classList.contains("mobile-sidebar-backdrop")) { setMobileSidebarOpen(false); } else { setMobileSidebarOpen(false); } return; } if (name === "open-auth") { openAuthModal(); return; } if (name === "close-auth") { closeAuthModal(); return; } if (name === "open-oneliner") { try { setBusy(true, "正在打开 OneLiner..."); if (appState.session) { await loadAgentControlSurfaces(appState.selectedProjectId || ""); if (appState.selectedOnelinerSessionId) { await loadOneLinerMessages(appState.selectedOnelinerSessionId); } else { await ensureOneLinerSession(); } } openOneLinerPanel(); renderAll(); } finally { setBusy(false, ""); renderAll(); } return; } if (name === "close-oneliner") { closeOneLinerPanel(); return; } if (name === "close-sheet") { closeActionModal(); return; } if (name === "submit-sheet") { await submitActionModal(); return; } if (name === "submit-auth") { setBusy(true, "正在自动连接并加载..."); try { const message = document.querySelector('[data-role="auth-message"]'); if (message) message.textContent = ""; appState.autoConnectSuppressed = false; appState.autoConnectAttempted = false; await ensureAutoSession({ force: true }); closeAuthModal(); await bootstrap(); } catch (error) { const message = document.querySelector('[data-role="auth-message"]'); if (message) message.textContent = formatActionErrorMessage(error, "自动连接失败"); } finally { setBusy(false, ""); } return; } if (name === "show-disabled-reason") { const reason = action.dataset.disabledReason || action.title || "当前动作暂不可用"; rememberAction("动作已拦截", reason, "orange"); renderAll(); return; } if (name === "auth-refresh" || name === "refresh-data") { setBusy(true, name === "auth-refresh" ? "正在重新自动连接..." : "正在刷新数据..."); try { if (name === "auth-refresh") { const message = document.querySelector('[data-role="auth-message"]'); if (message) message.textContent = ""; await refreshFromAuthModal(); } else { await bootstrap(); } } catch (error) { const message = document.querySelector('[data-role="auth-message"]'); if (name === "auth-refresh" && message) { message.textContent = formatActionErrorMessage(error, "自动连接失败"); } else { presentActionFailure(error, "刷新数据失败"); } } finally { setBusy(false, ""); } return; } if (name === "refresh-tracking") { try { await refreshTrackingAccountsAction(); } catch (error) { presentActionFailure(error, "刷新跟踪账号失败"); } return; } if (name === "open-live-recorder") { openLiveRecorderAction(); return; } if (name === "open-live-recorder-create") { openLiveRecorderCreateAction(); return; } if (name === "import-live-recorder-config") { openLiveRecorderImportAction(); return; } if (name === "edit-live-recorder-source") { openLiveRecorderSourceAction(action.dataset.sourceId || ""); return; } if (name === "toggle-live-recorder-source") { await toggleLiveRecorderSourceAction(action.dataset.sourceId || "", action.dataset.nextEnabled === "true"); return; } if (name === "delete-live-recorder-source") { await deleteLiveRecorderSourceAction(action.dataset.sourceId || ""); return; } if (name === "open-live-recorder-file") { await openLiveRecorderFileAction(action.dataset.fileId || ""); return; } if (name === "open-storage-artifact") { await openStorageArtifactAction(action.dataset.fileId || ""); return; } if (name === "mark-tracking-read") { try { await markTrackingDigestRead(); rememberAction("日报已标记", "当前跟踪摘要已更新为已读,下次会从新的时间点继续汇总。", "green"); await bootstrap(); focusTrackingWorkspace(); } catch (error) { presentActionFailure(error, "标记日报已读失败"); } return; } if (name === "logout-session") { await logoutSession(); return; } if (name === "dismiss-main-agent-landing") { appState.mainAgentLanding = null; renderAll(); return; } if (name === "goto-discovery") { captureMainAgentLandingContext(action, "goto-discovery"); setScreen("discovery"); return; } if (name === "goto-intake") { captureMainAgentLandingContext(action, "goto-intake"); setScreen("intake"); return; } if (name === "goto-automation") { captureMainAgentLandingContext(action, "goto-automation"); setScreen("automation"); return; } if (name === "goto-playbook") { captureMainAgentLandingContext(action, "goto-playbook"); setScreen("playbook"); return; } if (name === "goto-owned") { setScreen("owned"); return; } if (name === "goto-tracking") { captureMainAgentLandingContext(action, "goto-tracking"); setScreen("tracking"); return; } if (name === "goto-production") { captureMainAgentLandingContext(action, "goto-production"); setScreen("production"); return; } if (name === "goto-strategy") { captureMainAgentLandingContext(action, "goto-strategy"); setScreen("strategy"); return; } if (name === "goto-review") { captureMainAgentLandingContext(action, "goto-review"); setScreen("review"); return; } if (name === "goto-settings") { setScreen("settings"); return; } if (name === "goto-admin-workbench") { setScreen("admin-workbench"); return; } if (name === "select-page-tab") { const key = action.dataset.pageTabKey; const value = action.dataset.pageTabValue; if (key && value) { appState[key] = value; renderAll(); } return; } if (name === "open-import-homepage") { const selectedAccount = getSelectedAccountRow(); const sourceUrl = String(getAccountProfileUrl(selectedAccount) || "").trim(); if (selectedAccount?.id && sourceUrl) { await runDirectDiscoveryAction("import-homepage", { source_url: sourceUrl, title: `${getAccountName(selectedAccount) || "当前对标"} 对标主页`, handle: getAccountHandle(selectedAccount) || "" }, { busyLabel: "正在把当前对标接入项目...", errorTitle: "导入主页失败" }); return; } openImportHomepageAction(); return; } if (name === "open-import-selected-account") { const account = getSelectedAccount(); const project = getSelectedProject(); if (account?.id && project?.id) { const currentSource = getCurrentProjectSourcesForAccount(account, project.id)[0]; const sourceUrl = String(currentSource?.source_url || getAccountProfileUrl(account) || "").trim(); if (sourceUrl) { await runDirectDiscoveryAction("import-homepage", { source_url: sourceUrl, title: currentSource?.title || `${getAccountName(account) || "当前对标"} 对标主页`, handle: currentSource?.handle || getAccountHandle(account) || "", assistant_id: currentSource?.assistant_id || getSelectedAssistant()?.id || "", knowledge_base_id: getProjectKnowledgeBases(project.id)[0]?.id || "", max_items: Number(currentSource?.metadata?.max_items || 6), skip_existing: true, auto_trigger_analysis: true }, { projectId: project.id, platform: normalizePlatformValue(currentSource?.platform || getAccountPlatform(account)), busyLabel: currentSource ? "正在继续同步当前对标..." : "正在导入当前对标...", errorTitle: currentSource ? "继续同步当前对标失败" : "导入当前对标失败" }); return; } } openImportSelectedAccountAction(); return; } if (name === "open-track-selected-account") { const account = getSelectedAccount(); const trackedItem = account?.id ? safeArray(appState.trackingAccounts).find((item) => item.tracked_account_id === account.id) : null; const project = getSelectedProject(); if (account?.id && trackedItem && project?.id) { await runDirectDiscoveryAction("track-account", { target_account_id: account.id, assistant_id: trackedItem.assistant_id || getSelectedAssistant()?.id || "", note: trackedItem.note || "", refresh_now: true }, { projectId: project.id, platform: normalizePlatformValue(trackedItem.platform || getAccountPlatform(account)), busyLabel: "正在更新跟踪账号...", errorTitle: "更新跟踪失败" }); return; } openTrackSelectedAccountAction(); return; } if (name === "direct-import-selected-account") { const account = requireSelectedAccountRow(); const sourceUrl = String(getAccountProfileUrl(account) || "").trim(); if (!sourceUrl) { openImportSelectedAccountAction(); return; } await runDirectDiscoveryAction("import-homepage", { source_url: sourceUrl, title: `${getAccountName(account) || "当前对标"} 对标主页`, handle: getAccountHandle(account) || "" }, { busyLabel: "正在把当前对标接入项目...", errorTitle: "导入当前对标失败" }); return; } if (name === "direct-track-selected-account") { const account = requireSelectedAccountRow(); await runDirectDiscoveryAction("track-account", { target_account_id: account.id, note: "由对标工作台直接加入跟踪。", refresh_now: true }, { busyLabel: "正在更新跟踪状态...", errorTitle: "加入跟踪失败" }); return; } if (name === "refresh-tracked-account") { try { await refreshTrackedAccountAction(action.dataset.trackedAccountId || ""); } catch (error) { presentActionFailure(error, "同步跟踪账号失败"); } return; } if (name === "open-import-video-link") { openImportVideoLinkAction(); return; } if (name === "open-import-text") { openImportTextAction(); return; } if (name === "open-upload-video") { openUploadVideoAction(); return; } if (name === "open-create-assistant") { const project = getSelectedProject(); if (project?.id) { await runDirectWorkbenchAction("create-assistant", { busyLabel: "正在创建 Agent...", errorTitle: "创建 Agent 失败" }); return; } openCreateAssistantAction(); return; } if (name === "direct-create-assistant") { await runDirectWorkbenchAction("create-assistant", { busyLabel: "正在创建 Agent...", errorTitle: "创建 Agent 失败" }); return; } if (name === "open-dashboard-project-switcher") { openDashboardProjectSwitcher(); return; } if (name === "open-dashboard-action-reason") { openDashboardActionReasonAction(action.dataset.dashboardActionIndex || "0"); return; } if (name === "select-dashboard-tab") { appState.dashboardOverviewTab = action.dataset.dashboardTab || "project_progress"; renderAll(); return; } if (name === "open-oneliner-profile") { openOneLinerProfileAction(); return; } if (name === "open-oneliner-profile-history") { await openOneLinerProfileHistoryAction(action.dataset.versionId || ""); return; } if (name === "open-user-global-policy") { openUserGlobalPolicyAction(); return; } if (name === "open-user-global-policy-history") { await openUserGlobalPolicyHistoryAction(action.dataset.versionId || ""); return; } if (name === "open-user-platform-policy") { openUserPlatformPolicyAction(action.dataset.platform || ""); return; } if (name === "open-user-platform-policy-history") { await openUserPlatformPolicyHistoryAction(action.dataset.platform || "", action.dataset.versionId || ""); return; } if (name === "open-system-main-policy") { openSystemMainPolicyAction(); return; } if (name === "open-system-main-policy-history") { await openSystemMainPolicyHistoryAction(action.dataset.versionId || ""); return; } if (name === "open-system-platform-policy") { openSystemPlatformPolicyAction(action.dataset.platform || ""); return; } if (name === "open-system-platform-policy-history") { await openSystemPlatformPolicyHistoryAction(action.dataset.platform || "", action.dataset.versionId || ""); return; } if (name === "open-admin-override-target") { await openAdminOverrideTargetAction(); return; } if (name === "open-admin-override-policy") { openAdminOverridePolicyAction(); return; } if (name === "open-admin-override-history") { await openAdminOverrideHistoryAction(action.dataset.versionId || ""); return; } if (name === "handoff-to-main-agent") { try { setBusy(true, "正在为主 Agent 创建执行计划..."); const payload = await createOneLinerRun({ source_screen: action.dataset.sourceScreen || appState.screen || "dashboard", source_action_key: action.dataset.sourceActionKey || name, title: action.dataset.title || action.textContent?.trim() || "交给主 Agent 处理", summary: action.dataset.summary || "", intent_key: action.dataset.intentKey || "custom", platform: action.dataset.platform || getPreferredPlatform(), platform_scope: action.dataset.platformScope || "single_platform", plan_request: { goal: action.dataset.goal || action.dataset.title || action.textContent?.trim() || "交给主 Agent 处理", steps: parseJsonSafe(action.dataset.planSteps, []), summary: action.dataset.summary || "" } }); appState.selectedOnelinerRunId = payload?.id || ""; openOneLinerPanel(); renderAll(); } catch (error) { presentActionFailure(error, "主 Agent 接单失败"); openOneLinerPanel(); } finally { setBusy(false, ""); } return; } if (name === "open-oneliner-run-result") { openCurrentOneLinerRunResultAction(action.dataset.runId || ""); return; } if (name === "open-oneliner-run-context") { try { setBusy(true, "正在切到主 Agent 运行上下文..."); await openOneLinerRunContextAction(action.dataset.runId || ""); } catch (error) { presentActionFailure(error, "打开主 Agent 运行失败"); } finally { setBusy(false, ""); } return; } if (name === "confirm-oneliner-run") { openConfirmOneLinerRunAction(action.dataset.runId || ""); return; } if (name === "cancel-oneliner-run") { try { setBusy(true, "正在取消当前任务..."); await cancelOneLinerRun(action.dataset.runId || "", "user cancelled"); } catch (error) { presentActionFailure(error, "主 Agent 取消失败"); } finally { setBusy(false, ""); } return; } if (name === "retry-oneliner-run") { try { setBusy(true, "正在重新生成待确认卡..."); await retryOneLinerRun(action.dataset.runId || "", "user requested retry"); openOneLinerPanel(); } catch (error) { presentActionFailure(error, "主 Agent 重开失败"); } finally { setBusy(false, ""); } return; } if (name === "select-oneliner-run") { appState.selectedOnelinerRunId = action.dataset.runId || ""; renderAll(); return; } if (name === "select-oneliner-run-filter") { appState.onelinerRunFilter = action.dataset.runFilter || "focus"; renderAll(); return; } if (name === "select-oneliner-session") { appState.selectedOnelinerSessionId = action.dataset.sessionId || ""; await loadOneLinerMessages(appState.selectedOnelinerSessionId); renderAll(); return; } if (name === "select-assistant") { appState.selectedAssistantId = action.dataset.assistantId || ""; rememberAction("已切换当前 Agent", `当前默认 Agent 已更新为「${getSelectedAssistant()?.name || "未选择"}」。`, "green"); focusPlaybookWorkspace(appState.selectedAssistantId); return; } if (name === "open-edit-assistant") { openEditAssistantAction(action.dataset.assistantId || ""); return; } if (name === "open-platform-agent-profile") { openPlatformAgentProfileAction(action.dataset.platform || ""); return; } if (name === "open-platform-agent-profile-history") { await openPlatformAgentProfileHistoryAction(action.dataset.platform || "", action.dataset.versionId || ""); return; } if (name === "open-platform-agent-detail") { await openPlatformAgentDetailAction(action.dataset.platform || ""); return; } if (name === "open-action-registry-edit") { openActionRegistryEditAction(action.dataset.actionKey || ""); return; } if (name === "open-tenant-quota") { openTenantQuotaAction(); return; } if (name === "run-oneliner-action") { setBusy(true, "OneLiner 正在执行动作..."); try { await executeOneLinerAction(action.dataset.executorKey || "", { platform: action.dataset.platform || "", sessionId: action.dataset.sessionId || "", payload: collectOneLinerActionPayload(action) }); } catch (error) { presentActionFailure(error, "OneLiner 动作失败"); } finally { setBusy(false, ""); } return; } if (name === "open-platform-agent-memory") { openPlatformAgentMemoryAction(action.dataset.platform || ""); return; } if (name === "open-platform-agent-skill") { openPlatformAgentSkillAction(action.dataset.platform || ""); return; } if (name === "review-platform-skill") { openPlatformSkillReviewAction(action.dataset.platform || "", action.dataset.skillId || "", action.dataset.accepted !== "false"); return; } if (name === "rollback-platform-skill") { openPlatformSkillRollbackAction(action.dataset.platform || "", action.dataset.skillId || "", action.dataset.versionId || ""); return; } if (name === "analyze-selected-account") { openAnalyzeSelectedAccountAction(); return; } if (name === "direct-analyze-selected-account") { const account = getSelectedAccount(); if (!account?.id) { openAnalyzeSelectedAccountAction(); return; } await runDirectDiscoveryAction("analyze-account", { target_account_id: account.id, max_videos: 6, extra_focus: "", temperature: 0.35, auto_analyze_top_videos: true, top_video_analysis_count: 4 }, { busyLabel: "正在分析当前对标账号...", errorTitle: "账号分析失败", discoveryFocus: "snapshots" }); return; } if (name === "analyze-top-videos") { openAnalyzeTopVideosAction(); return; } if (name === "direct-analyze-top-videos") { const account = getSelectedAccount(); if (!account?.id) { openAnalyzeTopVideosAction(); return; } await runDirectDiscoveryAction("analyze-top-videos", { target_account_id: account.id, top_video_count: 5, min_score: 45, temperature: 0.25 }, { busyLabel: "正在分析高分作品...", errorTitle: "高分作品分析失败", discoveryFocus: "top-videos" }); return; } if (name === "open-generate-copy") { const fallbackJob = getLatestCompletedProjectJob(); if (fallbackJob?.id) { const brief = `基于任务「${fallbackJob.title}」的结果,生成一版可发布的短视频文案。参考摘要:${getJobSeedBrief(fallbackJob)}`; await runDirectWorkbenchAction("generate-copy", { busyLabel: "正在生成文案...", errorTitle: "生成文案失败", followRecommendedAction: false, payload: { source_job_id: fallbackJob.id, brief }, afterResult: async (result) => { cacheGeneratedCopyResult(result, { brief }); focusRecentGeneratedCopy(); } }); return; } openGenerateCopyAction(); return; } if (name === "open-generated-copy-results") { closeActionModal(); focusRecentGeneratedCopy(); return; } if (name === "direct-generate-copy") { const jobId = action.dataset.jobId || ""; const detail = appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null; const brief = detail ? `基于任务「${detail.title}」的结果,生成一版可发布的短视频文案。参考摘要:${getJobSeedBrief(detail)}` : ""; closeActionModal(); await runDirectWorkbenchAction("generate-copy", { busyLabel: "正在生成文案...", errorTitle: "生成文案失败", followRecommendedAction: false, payload: { source_job_id: jobId, brief }, afterResult: async (result) => { cacheGeneratedCopyResult(result, { brief }); focusRecentGeneratedCopy(); } }); return; } if (name === "open-ai-video") { const fallbackJob = getLatestCompletedProjectJob(); if (fallbackJob?.id) { await runDirectWorkbenchAction("create-ai-video", { busyLabel: "正在创建 AI 视频任务...", errorTitle: "创建 AI 视频任务失败", payload: { source_job_id: fallbackJob.id } }); return; } openCreateAiVideoAction(); return; } if (name === "direct-create-ai-video") { if (action.dataset.jobId) { closeActionModal(); } await runDirectWorkbenchAction("create-ai-video", { busyLabel: "正在创建 AI 视频任务...", errorTitle: "创建 AI 视频任务失败", payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {} }); return; } if (name === "open-real-cut") { const fallbackJob = getLatestCompletedProjectJob(); if (fallbackJob?.id) { await runDirectWorkbenchAction("create-real-cut", { busyLabel: "正在创建实拍剪辑任务...", errorTitle: "创建实拍剪辑任务失败", payload: { source_job_id: fallbackJob.id } }); return; } openCreateRealCutAction(); return; } if (name === "direct-create-real-cut") { if (action.dataset.jobId) { closeActionModal(); } await runDirectWorkbenchAction("create-real-cut", { busyLabel: "正在创建实拍剪辑任务...", errorTitle: "创建实拍剪辑任务失败", payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {} }); return; } if (name === "open-create-review") { const fallbackJob = getLatestCompletedProjectJob(); if (fallbackJob?.id) { await runDirectWorkbenchAction("review-draft", { busyLabel: "正在生成复盘草稿...", errorTitle: "生成复盘草稿失败", payload: { source_job_id: fallbackJob.id } }); return; } openReviewAction(); return; } if (name === "direct-review-draft") { await runDirectWorkbenchAction("review-draft", { busyLabel: "正在生成复盘草稿...", errorTitle: "生成复盘草稿失败", payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {} }); return; } if (name === "open-preferred-model") { openPreferredModelAction(); return; } if (name === "open-review-from-job") { const jobId = action.dataset.jobId || ""; if (jobId) { await runDirectWorkbenchAction("review-draft", { busyLabel: "正在生成复盘草稿...", errorTitle: "生成复盘草稿失败", payload: { source_job_id: jobId } }); return; } const fromDashboard = safeArray(appState.dashboard?.recent_jobs).find((item) => item.id === jobId) || null; const fromDetail = appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null; openReviewAction({ sourceJobId: jobId, sourceJob: fromDetail || fromDashboard, title: (fromDetail || fromDashboard)?.title || "" }); return; } if (name === "open-review-edit") { const review = getReviewById(action.dataset.reviewId || ""); if (!review) { rememberAction("复盘记录不存在", "当前没有找到这条复盘记录,请先刷新当前页面。", "orange"); renderAll(); return; } openReviewAction({ review }); return; } if (name === "open-similar-search") { const account = getSelectedAccount(); if (account?.id) { await runDirectDiscoveryAction("search-similar-accounts", { target_account_id: account.id, max_candidates: 8, extra_requirements: "" }, { busyLabel: "正在查找相似账号...", errorTitle: "查相似失败", discoveryFocus: "relations" }); return; } openSimilaritySearchAction(); return; } if (name === "direct-search-similar") { const account = getSelectedAccount(); if (!account?.id) { openSimilaritySearchAction(); return; } await runDirectDiscoveryAction("search-similar-accounts", { target_account_id: account.id, max_candidates: 8, extra_requirements: "" }, { busyLabel: "正在查找相似账号...", errorTitle: "查相似失败", discoveryFocus: "relations" }); return; } if (name === "open-benchmark-link") { const account = getSelectedAccount(); const candidate = safeArray(appState.lastSimilaritySearch?.candidates)[0] || null; if (account?.id && candidate) { await runDirectDiscoveryAction("save-benchmark-link", { source_account_id: account.id, target_account_id: candidate.candidate_account_id || "", target_profile_url: candidate.candidate_account_id ? "" : (candidate.candidate_profile_url || ""), search_id: appState.lastSimilaritySearch?.id || "", note: brief(candidate.rationale_text || "由对标工作台直接加入对标库。", 120) }, { busyLabel: "正在保存对标关系...", errorTitle: "保存对标失败", discoveryFocus: "relations" }); return; } openBenchmarkLinkAction(); return; } if (name === "direct-save-benchmark-link") { const account = getSelectedAccount(); if (!account?.id) { openBenchmarkLinkAction(); return; } const candidate = safeArray(appState.lastSimilaritySearch?.candidates)[0] || null; if (!candidate) { openBenchmarkLinkAction(); return; } await runDirectDiscoveryAction("save-benchmark-link", { source_account_id: account.id, target_account_id: candidate.candidate_account_id || "", target_profile_url: candidate.candidate_account_id ? "" : (candidate.candidate_profile_url || ""), search_id: appState.lastSimilaritySearch?.id || "", note: brief(candidate.rationale_text || "由对标工作台直接加入对标库。", 120) }, { busyLabel: "正在保存对标关系...", errorTitle: "保存对标失败", discoveryFocus: "relations" }); return; } if (name === "save-candidate-benchmark") { setBusy(true, "正在保存对标关系..."); try { await saveCandidateAsBenchmark(action.dataset.candidateIndex || ""); } catch (error) { presentActionFailure(error, "保存对标失败"); } finally { setBusy(false, ""); } return; } if (name === "open-job-detail") { openJobDetailAction(action.dataset.jobId || ""); return; } if (name === "recover-job") { await openRecoverJobAction(action.dataset.jobId || ""); return; } if (name === "batch-recover-jobs") { await openBatchRecoverJobsAction(); return; } if (name === "scan-admin-ops") { await scanAdminOpsAction(); return; } if (name === "review-admin-incident") { openAdminIncidentReviewAction(action.dataset.incidentId || ""); return; } if (name === "open-admin-repair-plan") { openAdminRepairPlanAction(action.dataset.incidentId || ""); return; } if (name === "open-admin-fix-run-audit") { openAdminFixRunAuditAction(action.dataset.runId || ""); return; } if (name === "open-admin-fix-run-detail") { openAdminFixRunDetailAction(action.dataset.runId || ""); return; } if (name === "select-douyin-snapshot") { await openDouyinSnapshotDetailAction(action.dataset.snapshotId || ""); return; } if (name === "job-to-ai-video") { const jobId = action.dataset.jobId || ""; const detail = appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null; closeActionModal(); openCreateAiVideoAction({ sourceJobId: jobId, sourceJob: detail }); return; } if (name === "job-to-real-cut") { const jobId = action.dataset.jobId || ""; const detail = appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null; closeActionModal(); openCreateRealCutAction({ sourceJobId: jobId, sourceJob: detail, objective: detail ? `基于任务「${detail.title}」保留高信息密度片段,输出适合短视频平台的粗剪结果。` : "" }); return; } if (name === "job-to-generate-copy") { const jobId = action.dataset.jobId || ""; const detail = appState.lastJobDetail?.job?.id === jobId ? appState.lastJobDetail.job : null; closeActionModal(); const brief = detail ? `基于任务「${detail.title}」的结果,生成一版可发布的短视频文案。参考摘要:${getJobSeedBrief(detail)}` : ""; await runDirectWorkbenchAction("generate-copy", { busyLabel: "正在生成文案...", errorTitle: "生成文案失败", followRecommendedAction: false, payload: { source_job_id: jobId, brief }, afterResult: async (result) => { cacheGeneratedCopyResult(result, { brief }); focusRecentGeneratedCopy(); } }); return; } if (name === "create-project") { await createProject(); return; } if (name === "select-project") { try { await applySelectedProject(action.dataset.projectId || ""); } catch (error) { presentActionFailure(error, "切换项目失败"); } return; } if (name === "select-platform") { const nextPlatform = normalizePlatformValue(action.dataset.platform || "", ""); if (!nextPlatform || nextPlatform === getCurrentPlatformValue()) { renderAll(); return; } setCurrentPlatform(nextPlatform); setBusy(true, `正在切换到${platformLabel(nextPlatform)}...`); try { await bootstrap(); } finally { setBusy(false, ""); } return; } if (name === "select-account") { const accountId = action.dataset.accountId; if (!accountId) return; const requestToken = (appState.selectedAccountRequestToken || 0) + 1; appState.selectedAccountRequestToken = requestToken; setBusy(true, "正在加载对标详情..."); try { const account = safeArray(appState.accounts).find((item) => item.id === accountId) || null; const committed = await loadPlatformAccount(getAccountPlatform(account), accountId, requestToken); if (committed && requestToken === appState.selectedAccountRequestToken) { renderAll(); } } catch (error) { if (requestToken === appState.selectedAccountRequestToken) { presentActionFailure(error, "加载对标详情失败"); } } finally { if (requestToken === appState.selectedAccountRequestToken) { setBusy(false, ""); } } return; } if (name === "submit-oneliner") { return; } if (name === "scroll-selected") { document.getElementById("selected-account-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" }); return; } rememberAction("暂未识别当前动作", `前端还没有识别动作「${name}」的精确落点,当前上下文仍可继续交给 OneLiner 承接。`, "orange"); renderAll(); return; } }); document.addEventListener("input", (event) => { const target = event.target; if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return; if (target.dataset.action === "discovery-query") { appState.discoveryQuery = target.value.trim(); screenMap.discovery.innerHTML = renderDiscoveryScreen(); } }); document.addEventListener("submit", async (event) => { const form = event.target; if (!(form instanceof HTMLFormElement)) return; if (form.dataset.role === "auth-form") { event.preventDefault(); setBusy(true, "正在自动连接并加载..."); try { appState.autoConnectSuppressed = false; appState.autoConnectAttempted = false; await ensureAutoSession({ force: true }); closeAuthModal(); await bootstrap(); } catch (error) { const message = document.querySelector('[data-role="auth-message"]'); if (message) message.textContent = formatActionErrorMessage(error, "自动连接失败"); } finally { setBusy(false, ""); } return; } if (form.dataset.role === "oneliner-form") { event.preventDefault(); const input = form.querySelector('[data-role="oneliner-input"]'); const value = input instanceof HTMLTextAreaElement ? input.value.trim() : ""; if (!value) return; setBusy(true, "OneLiner 正在拆解任务..."); try { await submitOneLinerMessage(value); if (input instanceof HTMLTextAreaElement) input.value = ""; openOneLinerPanel(); renderAll(); } catch (error) { presentActionFailure(error, "OneLiner 调度失败"); openOneLinerPanel(); } finally { setBusy(false, ""); } } }); navButtons.forEach((button) => { button.addEventListener("click", () => { const next = button.dataset.screenTarget; setScreen(next); }); }); window.addEventListener("hashchange", () => { const next = getScreenFromHash(); if (next !== appState.screen) { setScreen(next, { updateHash: false }); } }); window.addEventListener("resize", () => { if (window.innerWidth > 760) { setMobileSidebarOpen(false); } }); ensureAuthUi(); renderAll(); bootstrap();