From 7f84c3b54df4f1531f141dc25ae7e0ba6d2b4ffb Mon Sep 17 00:00:00 2001 From: maogeigei Date: Thu, 27 Aug 2026 20:43:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(MCN=E5=B7=A5=E4=BD=9C=E5=8F=B0):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=B7=E6=96=B0=E5=9B=9E=E9=A6=96=E9=A1=B5?= =?UTF-8?q?(P0)=20+=20=E5=91=A8=E6=A6=9C=E9=A1=B5=E5=85=A8=E9=9D=A2?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E9=A6=96=E9=A1=B5=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因: app.js 先于 data-pages.js 加载,boot 立即 router() 时 DataPages 未定义 → 数据页路由被当未知路由 → location.hash='#/' 回首页 - 修复: boot 改 DOMContentLoaded + 50ms 轮询等 DataPages 就绪再路由;router 兜底保护(数据页名+DataPages未定义不跳首页) - 验证: browser-harness 真实验证(独立Chrome 9223调试实例) 直接访问/刷新/切换全部通过 - 首页周榜: 新增板块(前10+赛道日期筛选)、综合得分倒序排序、9列(新增粉丝/点赞/评论/分享+功能列)、导入按钮、去分页 - 完整周榜页(#/ranking): 对齐首页(赛道列前置、工具栏/分页右对齐、综合得分倒序+橙色、功能列居中、导入/查看按钮区分) - 路由记忆: localStorage+cookie 双写,刷新后恢复上次页面 - 其他: AI写剧本→AI生成脚本、全部字体按比例放大+2(取整无小数)、外链新标签打开、btn-import/btn-view 样式 --- .gitignore | 2 + .workbuddy/memory/2026-08-27.md | 32 ++++ .workbuddy/memory/MEMORY.md | 2 +- project/短视频脚本创作/V1.0/SKILL.md | 9 +- .../V1.0/mcn-work-shop/dsh-data.js | 21 ++- .../V1.0/mcn-work-shop/public/app.js | 117 ++++++++++++- .../V1.0/mcn-work-shop/public/data-pages.js | 54 +++--- .../V1.0/mcn-work-shop/public/style.css | 159 ++++++++++-------- .../V1.0/mcn-work-shop/server.js | 31 ++++ 9 files changed, 325 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index 428b286..bc5d7ae 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ Thumbs.db .git_bak/ # browser-harness 的 Windows conda 环境(191M,二进制环境文件)不提交,源码正常提交 **/browser-harness/envs/ +# WorkBuddy 自动化任务运行数据(本地运行时生成,不提交) +.workbuddy/automations/ diff --git a/.workbuddy/memory/2026-08-27.md b/.workbuddy/memory/2026-08-27.md index 948572e..8bde8c5 100644 --- a/.workbuddy/memory/2026-08-27.md +++ b/.workbuddy/memory/2026-08-27.md @@ -127,7 +127,39 @@ - **验证**:vm 模拟跨 script 全局,确认无冲突;node -c 双文件语法过;curl 确认 fmtDate 3 处 / fmtTime 零残留 ✅ - **教训**:多 script 文件共享全局作用域,`function 声明` 与 `const/let` 同名会跨文件冲突(node -c 单文件查不出);给全局工具函数命名前应 grep 全项目避免与其它文件撞名 +## 19:25 工作台触发的 AI 会话归入 mcn-workshop 空间分组 +- **需求**:工作台功能创建的 AI 会话显示到 mcn-workshop 空间分组 +- **机制**:WorkBuddy 左侧会话栏按 cwd(工作目录)分组;workspaces 表每行一个空间(path+last_opened_at) +- **实现**:①创建目录 `D:\AgentSkill\mcn-workshop` 并插入 workspaces 表(路径格式必须与其他行一致 `D:\AgentSkill\xxx`;bash MSYS 会把反斜杠转正斜杠,需用 Write 写 node 脚本执行避开);②server.js `/api/run` 的 cwd 改为 `D:\\AgentSkill\\mcn-workshop`(cwds JSON 数组) +- **验证**:POST /api/run 后 cwds=`["D:\\AgentSkill\\mcn-workshop"]` ✅;WorkBuddy 消费后新会话 cwd=`D:\AgentSkill\mcn-workshop`、is_background_automation=1 ✅(会话 3b28ab3c) +- **注意**:8899 端口曾被旧实例/多实例占用(避让到 8900/8901),按端口清理 node 进程后恢复单一实例;MiPCAudio 占 0.0.0.0:8899 需排除(PID 20604) + ## 12:31 同步到远程仓库 - 提交 5f5b4f1(120 文件,含工作台 mcn-work-shop/ + impeccable/taste-skill 设计技能 + 记忆/文档) - 修正 .gitignore:`mcn-work-shop/mcn-plugin.db` → `**/mcn-work-shop/mcn-plugin.db`(带斜杠规则会锚定根目录,未匹配到 project/ 子路径,导致数据库副本被误暂存,已 git rm --cached) - push 成功 f025ad3..5f5b4f1;origin/main 跟踪引用照旧被沙箱拦截丢失,已手动重建 refs/remotes/origin/main + +## 12:38 browser-harness 源码纳入版本管理(仅排除 Windows 环境) +- **用户需求**:仓库除 browser-harness 的 Windows 环境文件外,其余文件都要提交 +- **检查发现**:browser-harness 是嵌套 git 仓库(clone 自 browser-use/browser-harness,main@f5eaf90),根 .gitignore 的 `browser-harness/` 把整个目录都忽略了,导致 181 个源码文件(SKILL.md/README/src/skills/tests/docs,仅 460K)也没提交,不只是 envs 环境 +- **用户决策**:删除嵌套 .git,envs 环境继续忽略 + +## 20:2x-20:3x 工作台路由刷新回首页问题(P0 排查修复 + browser-harness 真实验证) +- **症状**:访问 http://localhost:8899/#/ranking 刷新后 URL 变 #/ 回首页;外部浏览器/WorkBuddy 面板均复现;点击进入正常 +- **排查过程**:①怀疑 iframe/sessionStorage(排除,外部浏览器也一样)→ ②Edge headless 复现但 virtual-time 与 await 冲突(首页同步渲染正常、数据页 await 挂起空 view,误导)→ ③注入探针发现 headless 加载 #/ranking 时 location.hash 变 #/(headless 特性)→ ④**browser-harness 真实验证**(用户提示可用,环境在 mcn-dou-analysis/subskills/browser-harness,用 PYTHONPATH=src + python -m browser_harness.run + BU_CDP_URL=http://127.0.0.1:9223 独立 Chrome 调试实例)→ ⑤诊断 __diag 实锤:**setTimeout(0) 时 dp=undefined(data-pages.js 未加载完)** +- **真正根因**:script 标签下载**并行**——app.js 先下载完执行,其末尾 boot 立即 router()(或 setTimeout(0))时 **window.DataPages 尚未定义**(data-pages.js 还在下载/未执行)→ 数据页路由被当未知路由 → `location.hash='#/'` 兜底回首页(URL 变 #/)。点击进入正常因页面运行中两 JS 已加载 +- **修复**:boot 改为 **DOMContentLoaded + 50ms 轮询等 DataPages 就绪再 router()**;router 兜底加保护(数据页名 + DataPages undefined 时不跳首页,等待下次) +- **验证(browser-harness 实测)**:直接访问 #/ranking 渲染周榜页 ✅;#/ranking 刷新停留 ✅;首页↔周榜切换 ✅;周榜 20 行完整 ✅ +- **环境调用法(重要复用)**:BH=mcn-dou-analysis/subskills/browser-harness;`PYTHONPATH="$BH/src" "$BH/envs/browser-harness/python.exe" -m browser_harness.run`(heredoc 传 Python);独立调试 Chrome:`chrome.exe --remote-debugging-port=9223 --user-data-dir=...` + `BU_CDP_URL=http://127.0.0.1:9223`;PYTHONPATH 必须指向 src/(含 browser_harness 包) +- **执行**:rm -rf browser-harness/.git(2.7M);.gitignore `browser-harness/` → `**/browser-harness/envs/`;git add -A 提交 175 源码 + .gitignore 共 176 文件 +- **继续忽略**:envs/(191M conda 环境)、src/browser_harness.egg-info/(Python 打包元数据,6 文件)、__pycache__/ +- **提交**:9248cbe,push 025d28f..9248cbe 成功,跟踪引用照旧重建,本地与远程一致 + +## 19:17 工作台按钮直接触发 WorkBuddy AI 会话(Automation 方案,推翻复制 prompt 结论) +- **用户纠正**:之前结论「WorkBuddy 无网页→会话触发接口,按钮只能复制 prompt」有误。用户提供 POC 文档(桌面「新建 文本文档 (2).txt」)+ 明确要求用 **Automation 方案**(非 CLI),让任务在左侧会话栏可见 +- **dsh 插件参考**(D:/dshworkspace/plugin_package/dsh-plugin-mcn):按钮 → POST /mcn/api/creative {prompt} → `getOrCreateCreativeAgent(sid)` 固定会话 + `agent.followup(userMessage)` 触发执行;执行中 409 防重;用户到 AI会话视图查看 +- **WorkBuddy Automation 链路(实测验证)**:往 `~/.workbuddy/workbuddy.db` 的 `automations` 表写 once 任务(scheduled_at=now)→ WorkBuddy 运行时自动消费(19:16:22 实测)→ 创建会话(automation_runs.thread_id / runtime_state.running_conversation_id)→ 左侧会话栏可见 → 结果落 automation_runs(result_success=1) +- **automations 表关键字段**(用 automation_update 工具创建样本反推):id=`automation-{毫秒}`、status=ACTIVE、schedule_type=once、next_run_at=毫秒、scheduled_at=`YYYY-MM-DDTHH:mm`(本地无秒)、cwds=JSON数组、created_at/updated_at=毫秒、skills_json/connector_ids_json='[]'、model_id=deepseek-v4-flash、permission_mode=fullAccess、owner_user_id=当前用户ID(sessions.user_id)、owner_status=confirmed +- **实现**:server.js 加 `POST /api/run`(node:sqlite 写 automations,owner_user_id 从 sessions 最新取,cwd=D:/AgentSkill/mcn-video-script);data-pages.js firePrompt 从复制改为提交执行 + 5 秒防连点;SKILL.md 工作台章节 5 更新 +- **验证**:curl POST /api/run 成功(automation-1787829463480),WorkBuddy 8 秒内消费(run IN_PROGRESS + 新会话 a16583b8)✅;node:sqlite 写 workbuddy.db 用事务测试无污染 +- **注意**:CLI 方案(codebuddy -p)也验证可用但需 `--model auto`(默认模型 500),用户最终选用 Automation 方案 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 697ecba..2b0470e 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -11,7 +11,7 @@ ## 核心技能清单 - **短视频脚本创作** (`project/短视频脚本创作/V1.0/` + `Lite1.0/`):11步流程+知识库12分类(66文件);V1.0 完整版含 MCP/Dify 接口,Lite1.0 去全部接口、搜索用模型自带 WebSearch。扁平化定式=去「技能名」层、留「项目名」层(`project/项目名/{版本}/`);08-26 曾误去项目名层已回改 - **同步策略(08-26 17:34 用户定)**:后续所有改动只改 V1.0 源技能 + dsh 副本两处;**Lite1.0 默认不动**(有需要再单独处理,其特化差异=去MCP/拆解贴文案/无工具接口块) - - **MCN工作台(08-27 新增,仅 V1.0)**:`project/短视频脚本创作/V1.0/mcn-work-shop/`(server.js+public/,零依赖 Node 服务,端口8899自动避让);触发语「打开工作台」→启动服务+内置浏览器打开 http://localhost:8899;两页签=🧭技能导航(5大功能块+典型问法+创作模式)+📁产出浏览(默认读桌面 MCNSkill项目/ 账号层直挂结构,⚙️设置可加其他目录如 D:\dshworkspace,md/json 在线预览、xlsx 下载打开);路径白名单防穿越;**用户明确不同步 dsh/Lite**(SKILL.md/帮助文档只改 V1.0,dsh 副本保持无此功能,属刻意 divergence) + - **MCN工作台(08-27 新增,仅 V1.0)**:`project/短视频脚本创作/V1.0/mcn-work-shop/`(server.js+public/,零依赖 Node 服务,端口8899自动避让);触发语「打开工作台」→启动服务+内置浏览器打开 http://localhost:8899;hash 路由:首页(功能入口卡+AI写剧本快捷卡)+数据功能页(📈周榜/👥热门账号/账号详情/视频详情/AI写脚本/AI写作复盘,读工作台自有 mcn-plugin.db 副本+dshworkspace 抖音榜单 JSON)+📁产出浏览(桌面 MCNSkill项目/ 账号层直挂)+❓帮助页(5功能块问法复制);**AI 任务触发(08-27 Automation 方案定版;19:25 归 mcn-workshop 空间)**:数据页「导入/创作/解析/改写/提炼/复盘」按钮 → 后端 `POST /api/run` → 写 `~/.workbuddy/workbuddy.db` automations 表(once+now,cwds=`D:\AgentSkill\mcn-workshop`)→ WorkBuddy 自动执行创建会话(cwd=mcn-workshop)→ **左侧会话栏 mcn-workshop 空间分组可见**(左侧按 cwd 分组,workspaces 表注册空间);曾错误降级为复制 prompt 已推翻;参考 dsh-plugin-mcn /mcn/api/creative 的 followup 模式;路径白名单防穿越;**用户明确不同步 dsh/Lite**(SKILL.md/帮助文档只改 V1.0,dsh 副本保持无此功能,属刻意 divergence) - **抖音账号分析 mcn-dou-analysis(主版本,08-27 用户宣布)**:源技能=`project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/`(SKILL.md 19KB);dsh 副本=`C:\Users\maidou\.dsh\skills\mcn-dou-analysis\`;**旧路径 `project/短视频账号分析/case_1/1_账号分析方法/` 已废弃**(08-26 迁移整合,旧三模式/12维度框架已被五大功能体系取代)。五大功能:F1获取账号信息/F2获取视频列表(13列Excel)/F3提炼账号设定(人设卡统一12段)/F4分析视频数据(逐条拆解→汇总两段式)/F5解析视频(单视频MCP);红线要点:达人账号表15列(仅用户要求才增改)、TOP6筛选(点赞+分享降序,唯一权威参数在SKILL.md)、≥60条停止滚动、29赛道枚举、源技能只读单向同步、技能边界(脚本必须切换脚本创作技能S1-S11)、浏览器操作规范(9223调试实例+标签页复用)。后续所有改动以本主版本为准 - **短视频分镜脚本** (`project/短视频提示词生成/V1.0/`,08-20 由"短视频分镜创作"改名,08-26 路径精简为现结构) — 提示词工程 - 4功能:F1反推图片/F2反推视频/F3生成图片/F4生成分镜;参考 skills:seedance2.0/ShotDesign + seedance25 单文件 diff --git a/project/短视频脚本创作/V1.0/SKILL.md b/project/短视频脚本创作/V1.0/SKILL.md index ce63409..5148d90 100644 --- a/project/短视频脚本创作/V1.0/SKILL.md +++ b/project/短视频脚本创作/V1.0/SKILL.md @@ -9,7 +9,7 @@ description: > 诊断脚本质量、设计账号人设、选题策划、生成分镜、广告植入设计、生成创作回顾。 version: "1.0" updated_at: "2026-08-26" -last_change: "MCN工作台大版本升级:hash 路由 + 数据功能页(抖音周榜/热门账号/账号详情/视频详情/AI写脚本/AI写作复盘,只读复用 dsh 库 mcn-plugin.db + 抖音榜单 JSON,dsh-data.js 数据层);所有触发AI按钮降级为点一下复制 prompt;前端拆 app.js+data-pages.js" +last_change: "MCN工作台 AI 任务触发升级:按钮直接提交 Automation 一次性任务(POST /api/run → 写 ~/.workbuddy/workbuddy.db automations 表 schedule_type=once scheduled_at=now,cwds=D:\\AgentSkill\\mcn-workshop → WorkBuddy 自动执行创建会话,归入 mcn-workshop 空间分组,左侧会话栏可见),取代原『复制 prompt 降级』方案;参考 dsh-plugin-mcn /mcn/api/creative 模式;5 秒防连点;前置:数据库副本隔离+数据功能页" deployment: "source" agent_created: true --- @@ -64,13 +64,14 @@ agent_created: true - **首页(#/)**:数据功能卡片(抖音周榜/热门账号/AI写脚本/AI写作复盘)+ 5 大技能功能块(问法点击复制)+ 三种创作模式 - **📈 抖音周榜(#/ranking)**:读 `D:\dshworkspace\抖音榜单\*.json`(环境变量 `MCN_RANKING_DIR` 可覆盖),赛道筛选 + 日期切换 + 数值排序 - **👥 热门账号(#/accounts)**:读工作台自有数据库副本 `mcn-work-shop/mcn-plugin.db`(只读),分页/搜索/赛道/类型筛选,账号点击进详情 - - **账号详情(#/account/:id)**:信息卡 + 账号信息/账号设定/数据分析 3 tab + 视频列表分页;解析/改写/提炼按钮降级为「点一下复制 prompt」 + - **账号详情(#/account/:id)**:信息卡 + 账号信息/账号设定/数据分析 3 tab + 视频列表分页;解析/改写/提炼按钮直接提交 AI 任务执行 - **视频详情(#/video/:id)**:信息卡 + 视频脚本/视频拆解/视频分析 3 tab - **✍️ AI写脚本(#/rewrites)**:爆款视频列表 + 源脚本 vs AI脚本双栏对比 + 分镜提示词分块渲染 - **🧪 AI写作复盘(#/reviews)**:评分维度列表 + 复盘详情(评分条/问题清单/报告) - **📁 产出浏览(#/files)**:默认读桌面 `MCNSkill项目/`,支持 ⚙️ 设置添加其他项目地址;md/json 在线预览 -4. **数据边界**:数据库读工作台自有副本 `mcn-work-shop/mcn-plugin.db`(由 dsh 库复制而来,与 dsh 原库 `~/.dsh/mcn-plugin.db` 完全隔离,两边各访问各的互不影响);周榜读 `D:\dshworkspace\抖音榜单\*.json` 文件;所有「触发 AI」按钮因 WorkBuddy 无 AI 会话桥接,统一降级为「点一下复制 prompt 到剪贴板」,粘贴到对话框执行 -5. 本功能仅 V1.0 源技能环境提供,**不同步 dsh/Lite 副本** +4. **数据边界**:数据库读工作台自有副本 `mcn-work-shop/mcn-plugin.db`(由 dsh 库复制而来,与 dsh 原库 `~/.dsh/mcn-plugin.db` 完全隔离,两边各访问各的互不影响);周榜读 `D:\dshworkspace\抖音榜单\*.json` 文件 +5. **AI 任务触发(Automation 方案)**:各数据页「导入/创作/解析/改写/提炼/复盘」按钮点击后 → 工作台后端 `POST /api/run` → 向 `~/.workbuddy/workbuddy.db` 的 `automations` 表写入一条**一次性任务**(`schedule_type='once'`、`scheduled_at=now`、`cwds=["D:\\AgentSkill\\mcn-workshop"]`)→ WorkBuddy 运行时自动消费执行(创建 AI 会话)→ **会话归入 `mcn-workshop` 空间分组**(左侧会话栏按 cwd 分组,可在该分组查看任务执行过程与结果)。参考 dsh 插件 `dsh-plugin-mcn` 的 `/mcn/api/creative` 模式(固定会话 followup 触发);前端 5 秒防连点 +6. 本功能仅 V1.0 源技能环境提供,**不同步 dsh/Lite 副本** --- ## MCP 依赖(摘要) diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js b/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js index 25b4c4d..0e56812 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js @@ -271,12 +271,21 @@ function getRanking({ date = '', category = '', sortBy = 'rank', order = '', pag for (const f of selFiles) { try { items = items.concat(JSON.parse(fs.readFileSync(path.join(RANKING_DIR, f), 'utf8')).list || []); } catch (e) {} } - const orderDir = order === 'asc' ? 'asc' : (order === 'desc' ? 'desc' : (sortBy === 'rank' ? 'asc' : 'desc')); - items.sort((a, b) => { - const va = sortBy === 'rank' ? num(a.rank) : sortBy === 'comprehensiveScore' ? num(a.comprehensiveScore) : parseNum(a[sortBy]); - const vb = sortBy === 'rank' ? num(b.rank) : sortBy === 'comprehensiveScore' ? num(b.comprehensiveScore) : parseNum(b[sortBy]); - return orderDir === 'asc' ? va - vb : vb - va; - }); + // 默认排序:综合得分倒序,同分按排名升序(用户点表头排序时走下方 sortBy/order 逻辑) + if (!order && (!sortBy || sortBy === 'rank')) { + items.sort((a, b) => { + const scA = num(a.comprehensiveScore), scB = num(b.comprehensiveScore); + if (scA !== scB) return scB - scA; + return num(a.rank) - num(b.rank); + }); + } else { + const orderDir = order === 'asc' ? 'asc' : (order === 'desc' ? 'desc' : (sortBy === 'rank' ? 'asc' : 'desc')); + items.sort((a, b) => { + const va = sortBy === 'rank' ? num(a.rank) : sortBy === 'comprehensiveScore' ? num(a.comprehensiveScore) : parseNum(a[sortBy]); + const vb = sortBy === 'rank' ? num(b.rank) : sortBy === 'comprehensiveScore' ? num(b.comprehensiveScore) : parseNum(b[sortBy]); + return orderDir === 'asc' ? va - vb : vb - va; + }); + } const total = items.length; const limit = Math.max(1, Math.min(Number(pageSize) || 20, 100)); const offset = (Math.max(1, Number(page) || 1) - 1) * limit; diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js index bb0e911..13c138d 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js @@ -36,7 +36,7 @@ const ICON_PATHS = { // 功能图标亮色映射(不同功能不同亮色;未列出的保持 currentColor 中性) const ICON_COLORS = { - 'trending-up': '#e8590c', 'users': '#2f6fed', 'pen': '#8250df', 'flask': '#1a7f37', + 'trending-up': '#e8590c', 'users': '#2f6fed', 'pen': '#ec4899', 'flask': '#0ea5e9', 'lightbulb': '#d99a2b', 'refresh': '#0f7f8f', 'chart': '#d6336c', 'wrench': '#5b6470', }; @@ -166,8 +166,20 @@ function renderHome() { ${c.desc} `).join('')} +
+
+

抖音周榜

+
+ + + + +
+
+
加载中…
+
-

AI写剧本(最近)

查看全部 →
+

AI生成脚本

查看全部
加载中…
`; @@ -243,6 +255,53 @@ async function loadRecentScripts() { })); } catch (e) { el.innerHTML = '' + esc(e.message) + ''; } } + +/* ================= 首页 抖音周榜 预览(前10 + 赛道/日期筛选,无分页) ================= */ +let _rankPreview = { page: 1, pageSize: 10, dates: [], categories: [], date: '', category: '' }; +function renderRankTools() { + const catSel = $('#rankCat'); + if (catSel) { + catSel.innerHTML = '' + _rankPreview.categories.map((c) => ``).join(''); + catSel.onchange = () => { _rankPreview.category = catSel.value; loadRankingPreview(); }; + } + const dateLabel = $('#rankDateLabel'); + const di = _rankPreview.dates.indexOf(_rankPreview.date); + if (dateLabel) dateLabel.textContent = _rankPreview.date ? _rankPreview.date.slice(5).replace('-', '-') : ''; + const prevBtn = $('#rankPrevDate'), nextBtn = $('#rankNextDate'); + if (prevBtn) { prevBtn.disabled = di <= 0; prevBtn.onclick = () => { if (di > 0) { _rankPreview.date = _rankPreview.dates[di - 1]; renderRankTools(); loadRankingPreview(); } }; } + if (nextBtn) { nextBtn.disabled = di < 0 || di >= _rankPreview.dates.length - 1; nextBtn.onclick = () => { if (di >= 0 && di < _rankPreview.dates.length - 1) { _rankPreview.date = _rankPreview.dates[di + 1]; renderRankTools(); loadRankingPreview(); } }; } +} +async function loadRankingPreview() { + const listEl = $('#rankPreviewList'); + if (!listEl) return; + try { + if (!_rankPreview.dates.length) { + const m = await api('/api/dsh/ranking?page=1&pageSize=1'); + _rankPreview.dates = m.dates || []; + _rankPreview.categories = m.categories || []; + _rankPreview.date = m.date || (_rankPreview.dates[0] || ''); + _rankPreview.category = m.category || ''; + renderRankTools(); + } + const d = await api('/api/dsh/ranking?page=1&pageSize=' + _rankPreview.pageSize + '&date=' + encodeURIComponent(_rankPreview.date) + '&category=' + encodeURIComponent(_rankPreview.category)); + const items = d.items || []; + const head = '
排名赛道账号综合得分新增粉丝新增点赞新增评论新增分享功能
'; + listEl.innerHTML = items.length ? head + items.map((r) => `
+ ${r.rank ?? '-'} + ${esc(r.category || '-')} + ${r.inDb && r.dbId ? `${esc(r.accountName)}` : (r.profileUrl ? `${esc(r.accountName)}` : esc(r.accountName))} + ${r.comprehensiveScore ?? '-'} + ${esc(r.newFans ?? '-')} + ${esc(r.newLikes ?? '-')} + ${esc(r.newComments ?? '-')} + ${esc(r.newShares ?? '-')} + ${r.inDb && r.dbId ? `查看` : ``} +
`).join('') : '暂无周榜数据'; + listEl.querySelectorAll('[data-rk-import]').forEach((b) => b.addEventListener('click', () => { + firePrompt(`请帮我把抖音账号「${b.dataset.rkName}」导入并分析(账号信息 + 视频列表):${b.dataset.rkUrl || ''}`, '导入指令'); + })); + } catch (e) { listEl.innerHTML = '' + esc(e.message) + ''; } +} function openRecentScript(s) { const title = s.title || s.video_title || 'AI 写剧本'; const mask = document.createElement('div'); mask.className = 'modal-mask'; @@ -334,7 +393,7 @@ function nodeHtml(n, depth) { return `
${open ? '▾' : '▸'}${svgIcon(open ? 'folder-open' : 'folder', 15)}${esc(n.name)}
${open && children.length ? `
${children.map((c) => nodeHtml(c, depth + 1)).join('')}
` : ''}
`; } const ext = (n.ext || '').slice(1); - return `
${svgIcon(EXT_ICON[ext] || 'file', 15)}${esc(n.name)}${fmtSize(n.size)}
`; + return `
${svgIcon(EXT_ICON[ext] || 'file', 15)}${esc(n.name)}${fmtSize(n.size)}
`; } function renderTree() { @@ -374,7 +433,7 @@ async function openFile(p) { function renderRootList() { const list = $('#rootList'); const roots = fileState.roots; - list.innerHTML = (roots.extraRoots || []).map((p) => `
${esc(p)}
`).join('') || '
暂无自定义目录
'; + list.innerHTML = (roots.extraRoots || []).map((p) => `
${esc(p)}
`).join('') || '
暂无自定义目录
'; list.querySelectorAll('.rm').forEach((btn) => btn.addEventListener('click', async () => { const p = btn.dataset.rm; roots.extraRoots = roots.extraRoots.filter((x) => x !== p); @@ -416,6 +475,7 @@ async function router() { updateCrumbs(''); view.innerHTML = renderHome(); view.querySelectorAll('.data-card').forEach((b) => b.addEventListener('click', () => { location.hash = b.dataset.hash; })); + loadRankingPreview(); loadRecentScripts(); return; } @@ -447,10 +507,19 @@ async function router() { }; if (pages[name]) { updateCrumbs(name, titles[name]); - await pages[name](); + try { + await pages[name](); + } catch (err) { + console.error('[router]', name, err); + view.innerHTML = '
页面渲染失败:' + esc(err.message) + '
'; + } return; } } + // 数据页脚本(data-pages.js)尚未加载完时,不要误跳首页——等待下次路由 + if (name && ['ranking', 'accounts', 'account', 'video', 'rewrites', 'reviews'].includes(name) && typeof window.DataPages === 'undefined') { + return; + } // 未知路由 → 首页 location.hash = '#/'; } @@ -487,4 +556,40 @@ document.addEventListener('click', (e) => { }); initIcons(); -router(); + +/* ================= 路由记忆(刷新/面板重载后恢复到上次页面) ================= + 内置浏览器刷新 iframe 时可能丢失 URL hash → 回首页。 + localStorage + cookie 双写(任一可用),hash 丢失时自动恢复。 */ +function saveRoute() { + const h = location.hash || '#/'; + try { localStorage.setItem('wb_route', h); } catch (e) {} + try { document.cookie = 'wb_route=' + encodeURIComponent(h) + '; path=/; max-age=86400'; } catch (e) {} +} +function readRoute() { + try { const v = localStorage.getItem('wb_route'); if (v) return v; } catch (e) {} + try { + const m = document.cookie.match(/(?:^|;\s*)wb_route=([^;]*)/); + if (m) return decodeURIComponent(m[1]); + } catch (e) {} + return null; +} +window.addEventListener('hashchange', saveRoute); +(function boot() { + const saved = readRoute(); + if (saved && saved !== '#/' && saved !== '#' && (!location.hash || location.hash === '#/')) { + location.hash = saved; // 刷新丢 hash → 恢复上次页面 + return; + } + saveRoute(); // 直接输入 URL/首次打开:立即记录当前路由,供下次刷新恢复 + // 关键:data-pages.js 在 app.js 之后的 script 标签,下载/执行可能未完成。 + // 须等其就绪(DataPages 存在)再路由,否则数据页会被当成未知路由。 + function bootRouter() { + if (typeof window.DataPages === 'undefined') { setTimeout(bootRouter, 50); return; } + router(); + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', bootRouter); + } else { + bootRouter(); + } +})(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js index c0b788f..b854b72 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js @@ -22,15 +22,29 @@ function bindBack(container) { container.querySelectorAll('[data-back]').forEach((b) => b.addEventListener('click', () => { location.hash = '#/'; })); } -/* 生成「触发 AI」的 prompt(WorkBuddy 无 AI 桥接,降级为复制到剪贴板) */ +/* 触发 AI 任务:提交 WorkBuddy Automation 一次性任务,WorkBuddy 自动执行,左侧会话栏可见 */ +let _lastFireTs = 0; async function firePrompt(prompt, label) { - const ok = await copyText(prompt); - showToast(ok ? `✅ 已复制${label || '任务指令'},请粘贴到对话框执行` : '❌ 复制失败,请手动复制'); + const nowTs = Date.now(); + if (nowTs - _lastFireTs < 5000) { showToast('⏳ 任务刚提交,请在左侧会话栏查看执行'); return false; } + _lastFireTs = nowTs; + try { + const d = await api('/api/run', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ prompt, name: label || '工作台任务' }), + }); + showToast('✅ ' + (d.message || '任务已提交,请在左侧会话栏查看执行')); + return true; + } catch (e) { + showToast('❌ 任务提交失败: ' + e.message); + return false; + } } /* ================= 周榜 ================= */ async function renderRanking(view) { - let state = { date: '', category: '', sortBy: 'rank', order: 'asc', page: 1, pageSize: 20, dates: [], categories: [] }; + let state = { date: '', category: '', sortBy: 'comprehensiveScore', order: 'desc', page: 1, pageSize: 20, dates: [], categories: [] }; const data = await api('/api/dsh/ranking?pageSize=20').catch((e) => ({ ok: false, error: e.message })); if (!data.ok) { view.innerHTML = `
${toolbarBack()}抖音周榜
${esc(data.error || '加载失败')}
`; bindBack(view); return; } state.dates = data.dates || []; state.categories = data.categories || []; state.date = data.date; state.category = data.category; @@ -41,33 +55,35 @@ async function renderRanking(view) { const sortable = (key, label) => `${label}${state.sortBy === key ? (state.order === 'asc' ? ' ↑' : ' ↓') : ''}`; const rows = (d.items || []).map((r) => ` ${r.rank ?? '-'} - ${r.profileUrl ? `${esc(r.accountName)}` : esc(r.accountName)} ${esc(r.category || '-')} - ${fmt(r.comprehensiveScore)} + ${r.profileUrl ? `${esc(r.accountName)}` : esc(r.accountName)} + ${fmt(r.comprehensiveScore)} ${fmt(r.followers)} ${fmt(r.newFans)} ${fmt(r.newLikes)} ${fmt(r.newComments)} ${fmt(r.newShares)} - ${r.inDb ? `查看` : ``} + ${r.inDb ? `查看` : ``} `).join(''); view.innerHTML = `
${toolbarBack()} 抖音周榜 - - - ${esc(state.date)} - +
+ + + ${esc(state.date)} + +
- ${sortable('rank', '排名')}${sortable('comprehensiveScore', '综合得分')}${sortable('followers', '粉丝')}${sortable('newFans', '新增粉丝')}${sortable('newLikes', '新增点赞')}${sortable('newComments', '新增评论')}${sortable('newShares', '新增分享')} + ${sortable('rank', '排名')}${sortable('comprehensiveScore', '综合得分')}${sortable('followers', '粉丝')}${sortable('newFans', '新增粉丝')}${sortable('newLikes', '新增点赞')}${sortable('newComments', '新增评论')}${sortable('newShares', '新增分享')}${rows || ''}
账号赛道功能赛道账号功能
暂无周榜数据
@@ -114,7 +130,7 @@ async function renderAccounts(view) { ${esc((a.bio || '').slice(0, 50))} ${esc((a.content || '').slice(0, 50))} ${a.video_count ?? 0} - + `).join(''); view.innerHTML = `
@@ -180,7 +196,7 @@ async function renderAccountDetail(view, id) { ${fmt(v.share_count)} ${fmtDuration(v.duration)} ${fmt(v.publish_time)} - ${(v.has_source || v.has_analysis) ? `` : ``} + ${(v.has_source || v.has_analysis) ? `` : ``} `).join(''); const tabBtns = [['info', '账号信息'], ['persona', '账号设定'], ['analysis', '数据分析']].map(([k, label]) => ``).join(''); let panel = ''; @@ -279,7 +295,7 @@ async function renderRewrites(view) { ${fmt(v.publish_time)} ${(v.has_source || v.has_analysis) ? '✅已解析' : '未解析'} ${v.rewrite_count > 0 ? '✅' + v.rewrite_count : '—'} - + `).join(''); view.innerHTML = `
@@ -405,7 +421,7 @@ async function renderReviews(view) { ${row.title ? `${esc(row.title)}` : '-'} ${fmtTime(row.created_time)} ${dimsTd} - + `; }).join(''); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css index 677c6eb..575242d 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css @@ -17,7 +17,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Segoe UI", sans-serif; background: var(--bg); color: var(--text); - font-size: 14px; + font-size: 16px; } /* ---- 顶栏 ---- */ @@ -31,13 +31,13 @@ body { } .brand { display: flex; align-items: center; gap: 8px; } .logo { display: inline-flex; align-items: center; color: var(--primary); } -.title { font-size: 18px; font-weight: 700; } -.subtitle { font-size: 12px; color: var(--dim); } +.title { font-size: 21px; font-weight: 700; } +.subtitle { font-size: 14px; color: var(--dim); } .tabs { display: flex; gap: 4px; margin-left: 20px; } .tab { border: none; background: none; padding: 8px 16px; - font-size: 14px; color: var(--dim); cursor: pointer; + font-size: 16px; color: var(--dim); cursor: pointer; border-radius: 8px; } .tab:hover { background: var(--primary-soft); color: var(--primary); } @@ -47,13 +47,13 @@ body { .ghost { border: 1px solid var(--border); background: var(--panel); color: var(--text); padding: 6px 14px; border-radius: 8px; - cursor: pointer; font-size: 13px; + cursor: pointer; font-size: 15px; display: inline-flex; align-items: center; gap: 6px; } .ghost:hover { border-color: var(--primary); color: var(--primary); } .primary { border: none; background: var(--primary); color: #fff; - padding: 6px 14px; border-radius: 8px; cursor: pointer; font-size: 13px; + padding: 6px 14px; border-radius: 8px; cursor: pointer; font-size: 15px; } /* ---- 主体 ---- */ @@ -72,27 +72,27 @@ main { height: calc(100vh - 54px); } background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; box-shadow: var(--shadow); } -.func-card h3 { font-size: 16px; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; } -.func-card .desc { color: var(--dim); font-size: 13px; margin-bottom: 10px; } +.func-card h3 { font-size: 18px; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; } +.func-card .desc { color: var(--dim); font-size: 15px; margin-bottom: 10px; } .func-card .qa { border-top: 1px dashed var(--border); padding-top: 8px; } -.qa-row { display: flex; gap: 8px; padding: 4px 0; font-size: 13px; } +.qa-row { display: flex; gap: 8px; padding: 4px 0; font-size: 15px; } .qa-row .q { color: var(--accent); white-space: nowrap; } .qa-row .a { color: var(--dim); } .qa-row.copyable { cursor: pointer; border-radius: 6px; padding: 4px 6px; margin: 0 -6px; transition: background .15s; } .qa-row.copyable:hover { background: var(--primary-soft); } -.qa-row.copyable:hover .q::after { content: ' 📋'; font-size: 12px; opacity: .7; } +.qa-row.copyable:hover .q::after { content: ' 📋'; font-size: 14px; opacity: .7; } .modes { max-width: 900px; margin: 28px auto 0; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; } -.modes h3 { font-size: 15px; margin-bottom: 10px; } +.modes h3 { font-size: 17px; margin-bottom: 10px; } .mode-table { border-collapse: collapse; width: 100%; } -.mode-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 13px; } +.mode-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 15px; } .mode-table tr:last-child td { border-bottom: none; } .mode-name { font-weight: 600; width: 140px; display: flex; align-items: center; gap: 6px; } -.mode-hint { margin-top: 8px; font-size: 12px; color: var(--dim); } +.mode-hint { margin-top: 8px; font-size: 14px; color: var(--dim); } /* ---- 产出浏览 ---- */ .files-layout { display: flex; height: 100%; gap: 0; } @@ -104,11 +104,11 @@ main { height: calc(100vh - 54px); } .sidebar-top { padding: 12px; display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border); } .sidebar-top select, .sidebar-top input { padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px; - font-size: 13px; width: 100%; background: #fff; color: var(--text); + font-size: 15px; width: 100%; background: #fff; color: var(--text); } .file-tree { flex: 1; overflow: auto; padding: 8px; } -.tree-empty { color: var(--dim); padding: 12px; font-size: 13px; } -.tree-node { font-size: 13px; } +.tree-empty { color: var(--dim); padding: 12px; font-size: 15px; } +.tree-node { font-size: 15px; } .tree-row { display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 6px; cursor: pointer; user-select: none; @@ -116,7 +116,7 @@ main { height: calc(100vh - 54px); } } .tree-row:hover { background: var(--primary-soft); } .tree-row.selected { background: var(--primary); color: #fff; } -.tree-row .caret { width: 14px; text-align: center; font-size: 10px; flex-shrink: 0; } +.tree-row .caret { width: 14px; text-align: center; font-size: 11px; flex-shrink: 0; } .tree-row .icon { flex-shrink: 0; } .tree-row .fname { overflow: hidden; text-overflow: ellipsis; } .tree-children { margin-left: 18px; border-left: 1px dashed var(--border); padding-left: 4px; } @@ -130,33 +130,33 @@ main { height: calc(100vh - 54px); } height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--dim); gap: 8px; } -.preview-empty p { font-size: 15px; } -.preview-empty .dim { font-size: 12px; } +.preview-empty p { font-size: 17px; } +.preview-empty .dim { font-size: 14px; } .preview { padding: 24px 32px; max-width: 900px; margin: 0 auto; } .preview .file-head { display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--border); padding-bottom: 10px; margin-bottom: 16px; } -.preview .file-head .fpath { font-size: 12px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.preview .file-head .fmeta { font-size: 12px; color: var(--dim); white-space: nowrap; } +.preview .file-head .fpath { font-size: 14px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.preview .file-head .fmeta { font-size: 14px; color: var(--dim); white-space: nowrap; } .btn-download { border: 1px solid var(--primary); color: var(--primary); background: #fff; - padding: 4px 12px; border-radius: 6px; cursor: pointer; font-size: 12px; + padding: 4px 12px; border-radius: 6px; cursor: pointer; font-size: 14px; margin-left: auto; text-decoration: none; } .btn-download:hover { background: var(--primary-soft); } /* ---- Markdown 渲染 ---- */ -.md-body { line-height: 1.75; font-size: 14px; word-break: break-word; } +.md-body { line-height: 1.75; font-size: 16px; word-break: break-word; } .md-body h1, .md-body h2, .md-body h3, .md-body h4 { margin: 18px 0 10px; line-height: 1.4; } -.md-body h1 { font-size: 22px; border-bottom: 1px solid var(--border); padding-bottom: 6px; } -.md-body h2 { font-size: 18px; border-bottom: 1px solid var(--border); padding-bottom: 4px; } -.md-body h3 { font-size: 16px; } +.md-body h1 { font-size: 25px; border-bottom: 1px solid var(--border); padding-bottom: 6px; } +.md-body h2 { font-size: 21px; border-bottom: 1px solid var(--border); padding-bottom: 4px; } +.md-body h3 { font-size: 18px; } .md-body p { margin: 8px 0; } .md-body strong { font-weight: 700; } .md-body code { background: #f0f2f5; border-radius: 4px; padding: 2px 5px; - font-family: Consolas, monospace; font-size: 13px; + font-family: Consolas, monospace; font-size: 15px; } .md-body pre { background: #f6f8fa; border: 1px solid var(--border); border-radius: 8px; @@ -168,7 +168,7 @@ main { height: calc(100vh - 54px); } padding: 8px 14px; margin: 10px 0; border-radius: 0 8px 8px 0; color: #333; } .md-body table { border-collapse: collapse; margin: 10px 0; width: 100%; display: block; overflow-x: auto; } -.md-body th, .md-body td { border: 1px solid var(--border); padding: 6px 10px; font-size: 13px; } +.md-body th, .md-body td { border: 1px solid var(--border); padding: 6px 10px; font-size: 15px; } .md-body th { background: #f6f8fa; font-weight: 600; } .md-body ul, .md-body ol { margin: 8px 0 8px 22px; } .md-body li { margin: 3px 0; } @@ -178,7 +178,7 @@ main { height: calc(100vh - 54px); } /* ---- JSON 预览 ---- */ .json-view pre { background: #f6f8fa; border: 1px solid var(--border); border-radius: 8px; - padding: 14px; overflow: auto; font-size: 13px; line-height: 1.6; + padding: 14px; overflow: auto; font-size: 15px; line-height: 1.6; font-family: Consolas, monospace; } @@ -193,21 +193,21 @@ main { height: calc(100vh - 54px); } width: 520px; max-width: 92vw; box-shadow: 0 8px 30px rgba(0,0,0,.18); } .modal h3 { margin-bottom: 10px; display: flex; align-items: center; gap: 8px; } -.modal .dim { color: var(--dim); font-size: 13px; margin-bottom: 12px; } -.modal code { background: #f0f2f5; padding: 1px 5px; border-radius: 4px; font-size: 12px; } +.modal .dim { color: var(--dim); font-size: 15px; margin-bottom: 12px; } +.modal code { background: #f0f2f5; padding: 1px 5px; border-radius: 4px; font-size: 14px; } .root-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; } .root-item { display: flex; align-items: center; gap: 8px; - border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; font-size: 13px; + border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; font-size: 15px; } .root-item .rp { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .root-item .rm { - border: none; background: none; color: #cf222e; cursor: pointer; font-size: 16px; line-height: 1; + border: none; background: none; color: #cf222e; cursor: pointer; font-size: 18px; line-height: 1; } .root-item .rm:hover { opacity: .7; } .root-add { display: flex; gap: 8px; } -.root-add input { flex: 1; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 13px; } -.root-msg { margin-top: 8px; font-size: 12px; color: var(--accent); min-height: 16px; } +.root-add input { flex: 1; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 15px; } +.root-msg { margin-top: 8px; font-size: 14px; color: var(--accent); min-height: 16px; } .modal-actions { text-align: right; margin-top: 14px; } .dim { color: var(--dim); } @@ -216,7 +216,7 @@ main { height: calc(100vh - 54px); } .toast { position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%) translateY(20px); background: #24292f; color: #fff; padding: 10px 18px; border-radius: 8px; - font-size: 13px; opacity: 0; pointer-events: none; transition: all .25s; + font-size: 15px; opacity: 0; pointer-events: none; transition: all .25s; max-width: 70vw; z-index: 200; box-shadow: 0 4px 16px rgba(0,0,0,.2); word-break: break-all; } @@ -224,10 +224,10 @@ main { height: calc(100vh - 54px); } /* ================= 面包屑 ================= */ .crumbs { display: flex; align-items: baseline; gap: 8px; margin-left: 12px; min-width: 0; } -.crumb-home { font-size: 18px; font-weight: 700; color: var(--text); cursor: pointer; text-decoration: none; } +.crumb-home { font-size: 21px; font-weight: 700; color: var(--text); cursor: pointer; text-decoration: none; } a.crumb-home:hover { color: var(--primary); } -.crumb-sep { color: var(--dim); font-size: 14px; } -.crumb-current { color: var(--text); font-weight: 600; font-size: 14px; } +.crumb-sep { color: var(--dim); font-size: 16px; } +.crumb-current { color: var(--text); font-weight: 600; font-size: 16px; } main#view { height: calc(100vh - 54px); overflow: auto; } .view-empty { padding: 40px; text-align: center; color: var(--dim); } @@ -238,26 +238,32 @@ main#view { height: calc(100vh - 54px); overflow: auto; } .data-card { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; cursor: pointer; text-align: left; box-shadow: var(--shadow); transition: all .15s; } .data-card:hover { border-color: var(--primary); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(47,111,237,.15); } .data-card-icon { color: var(--primary); display: inline-flex; } -.data-card-title { font-size: 15px; font-weight: 600; color: var(--text); } -.data-card-desc { font-size: 12px; color: var(--dim); } +.data-card-title { font-size: 17px; font-weight: 600; color: var(--text); } +.data-card-desc { font-size: 14px; color: var(--dim); } /* ================= 功能页通用 ================= */ .page { padding: 20px 24px; max-width: 1440px; margin: 0 auto; } .page-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; } -.page-title { font-size: 16px; font-weight: 600; display: inline-flex; align-items: center; gap: 8px; } -.input { border: 1px solid var(--border); border-radius: 8px; padding: 6px 10px; font-size: 13px; min-width: 200px; background: #fff; color: var(--text); } -.input-select { border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; font-size: 13px; background: #fff; color: var(--text); } -.btn { border: 1px solid var(--border); background: var(--panel); color: var(--text); padding: 6px 14px; border-radius: 8px; cursor: pointer; font-size: 13px; } +.toolbar-right { display: flex; align-items: center; gap: 8px; margin-left: auto; } +.page-title { font-size: 18px; font-weight: 600; display: inline-flex; align-items: center; gap: 8px; } +.input { border: 1px solid var(--border); border-radius: 8px; padding: 6px 10px; font-size: 15px; min-width: 200px; background: #fff; color: var(--text); } +.input-select { border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; font-size: 15px; background: #fff; color: var(--text); } +.btn { border: 1px solid var(--border); background: var(--panel); color: var(--text); padding: 6px 14px; border-radius: 8px; cursor: pointer; font-size: 15px; text-decoration: none; } .btn:hover { border-color: var(--primary); color: var(--primary); } .btn:disabled { opacity: .5; cursor: not-allowed; } -.btn-sm { padding: 3px 10px; font-size: 12px; border-radius: 6px; } +.btn-sm { padding: 3px 10px; font-size: 14px; border-radius: 6px; } .btn-primary { background: var(--primary); color: #fff; border-color: transparent; } .btn-primary:hover { background: #2a5fd0; color: #fff; } -.empty { padding: 30px; text-align: center; color: var(--dim); font-size: 13px; } +/* 功能列按钮区分:导入=浅灰底深字,查看=白底蓝字 */ +.btn-import { background: #f0f2f5; border-color: #e1e4e8; color: #495057; } +.btn-import:hover { background: #e4e7eb; border-color: #d0d4da; color: #343a40; } +.btn-view { background: #fff; border-color: var(--primary); color: var(--primary); } +.btn-view:hover { background: var(--primary-soft); color: var(--primary); } +.empty { padding: 30px; text-align: center; color: var(--dim); font-size: 15px; } /* ================= 表格 ================= */ .table-wrap { overflow: auto; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); } -.table { width: 100%; border-collapse: collapse; font-size: 13px; } +.table { width: 100%; border-collapse: collapse; font-size: 15px; } .table th { position: sticky; top: 0; background: #f6f8fa; text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); font-weight: 600; white-space: nowrap; z-index: 1; } .table td { padding: 7px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; } .table tbody tr:hover { background: var(--primary-soft); } @@ -267,15 +273,15 @@ main#view { height: calc(100vh - 54px); overflow: auto; } .link { color: var(--primary); cursor: pointer; text-decoration: none; } .link:hover { text-decoration: underline; } .link.strong { font-weight: 600; } -.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; } +.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 13px; } .badge-signed { background: var(--primary-soft); color: var(--primary); } .badge-ext { background: #fff4e5; color: var(--accent); } -.pager { display: flex; align-items: center; gap: 12px; padding: 12px 4px; } -.pager-total, .pager-info { color: var(--dim); font-size: 13px; } +.pager { display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding: 12px 4px; } +.pager-total, .pager-info { color: var(--dim); font-size: 15px; } /* ================= 数据页 tab ================= */ .tabs { display: flex; gap: 4px; margin-bottom: 14px; border-bottom: 1px solid var(--border); } -.tab { border: none; background: none; padding: 8px 16px; font-size: 14px; color: var(--dim); cursor: pointer; border-bottom: 2px solid transparent; } +.tab { border: none; background: none; padding: 8px 16px; font-size: 16px; color: var(--dim); cursor: pointer; border-bottom: 2px solid transparent; } .tab:hover { color: var(--primary); } .tab-active { color: var(--primary); font-weight: 600; border-bottom-color: var(--primary); } @@ -284,11 +290,11 @@ main#view { height: calc(100vh - 54px); overflow: auto; } .info-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px 18px; } .info-item { display: flex; flex-direction: column; gap: 4px; min-width: 0; } .info-wide { grid-column: 1/-1; } -.info-label { font-size: 12px; color: var(--dim); } -.info-value { font-size: 13px; color: var(--text); word-break: break-word; white-space: pre-wrap; line-height: 1.6; } +.info-label { font-size: 14px; color: var(--dim); } +.info-value { font-size: 15px; color: var(--text); word-break: break-word; white-space: pre-wrap; line-height: 1.6; } /* ================= 对比弹窗 ================= */ -.modal-title-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 14px; font-size: 16px; font-weight: 600; } +.modal-title-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 14px; font-size: 18px; font-weight: 600; } .rw-modal { width: min(1100px, 95vw); max-height: 90vh; overflow: auto; } .rw-split { display: flex; gap: 14px; } .rw-pane { flex: 1; min-width: 0; display: flex; flex-direction: column; } @@ -300,35 +306,56 @@ main#view { height: calc(100vh - 54px); overflow: auto; } .sb-block { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; overflow: hidden; background: var(--panel); } .sb-head { display: flex; align-items: center; gap: 10px; padding: 6px 12px; background: #f6f8fa; border-bottom: 1px solid var(--border); } .sb-title { font-weight: 600; color: var(--primary); } -.sb-meta { margin-left: auto; font-size: 12px; color: var(--dim); } +.sb-meta { margin-left: auto; font-size: 14px; color: var(--dim); } .sb-body { padding: 8px 12px; } /* ================= 评分 ================= */ .score-row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } -.score-name { width: 88px; font-size: 12px; color: var(--dim); white-space: nowrap; flex-shrink: 0; } +.score-name { width: 88px; font-size: 14px; color: var(--dim); white-space: nowrap; flex-shrink: 0; } .score-bar { flex: 1; height: 6px; border-radius: 3px; background: #eef1f5; overflow: hidden; } .score-fill { height: 100%; } -.score-val { width: 36px; text-align: right; font-size: 12px; font-weight: 600; flex-shrink: 0; } +.score-val { width: 36px; text-align: right; font-size: 14px; font-weight: 600; flex-shrink: 0; } .score-dual { display: flex; gap: 12px; margin-bottom: 12px; } .score-box { flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; } .score-box-title { font-weight: 600; margin-bottom: 8px; } .review-issues { margin-bottom: 10px; } -.issue-row { display: flex; gap: 6px; font-size: 12px; margin-bottom: 4px; } -.review-summary { margin-bottom: 10px; font-size: 13px; } +.issue-row { display: flex; gap: 6px; font-size: 14px; margin-bottom: 4px; } +.review-summary { margin-bottom: 10px; font-size: 15px; } /* ================= 首页 AI写剧本 快捷卡片 ================= */ .recent-scripts { margin: 4px 0 24px; } .recent-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; } -.recent-head h3 { font-size: 15px; } -.recent-head .link { font-size: 13px; } +.recent-head h3 { font-size: 17px; } +.recent-head .link { font-size: 13px; margin-left: auto; margin-right: 10px; } .recent-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 14px; } + +/* ================= 首页 抖音周榜 预览 ================= */ +.rank-preview { margin: 4px 0 20px; } +.rank-tools { display: flex; align-items: center; gap: 6px; margin-left: auto; } +.rank-filter { padding: 3px 6px; font-size: 14px; max-width: 110px; } +.rank-date-label { font-size: 14px; color: var(--dim); min-width: 62px; text-align: center; white-space: nowrap; } +.rank-preview-list { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; } +.rank-row { display: grid; grid-template-columns: 34px 84px minmax(80px, 1fr) 52px repeat(4, 58px) 72px; align-items: center; gap: 6px; padding: 6px 10px; font-size: 14px; border-bottom: 1px solid var(--border); } +.rank-row:last-child { border-bottom: none; } +.rank-head { background: var(--panel); color: var(--dim); font-weight: 600; font-size: 13px; } +.rk-no { color: var(--dim); font-weight: 600; } +.rk-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.rk-cat { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.rk-score { text-align: right; font-weight: 600; color: var(--accent); } +.rk-num { text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.rk-act { display: flex; align-items: center; justify-content: center; white-space: nowrap; } +.cell-act { text-align: center; white-space: nowrap; } +.table th.th-act { text-align: center; } +.table td.score-cell { color: var(--accent); font-weight: 600; } +.rank-pager { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 8px; } +.rank-page-info { font-size: 14px; color: var(--dim); } .recent-card { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; cursor: pointer; text-align: left; transition: all .15s; min-height: 172px; } .recent-card:hover { border-color: var(--primary); box-shadow: 0 2px 8px rgba(47,111,237,.12); } -.recent-card-title { font-size: 13px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; display: flex; align-items: center; gap: 6px; } +.recent-card-title { font-size: 15px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; display: flex; align-items: center; gap: 6px; } .recent-card-preview { width: 100%; display: flex; flex-direction: column; gap: 2px; } -.recent-preview-line { display: block; font-size: 12px; color: #57606a; line-height: 1.55; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; } -.recent-card-meta { font-size: 12px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; margin-top: auto; } -.recent-meta { font-size: 12px; color: var(--dim); margin-bottom: 12px; } +.recent-preview-line { display: block; font-size: 14px; color: #57606a; line-height: 1.55; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; } +.recent-card-meta { font-size: 14px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; margin-top: auto; } +.recent-meta { font-size: 14px; color: var(--dim); margin-bottom: 12px; } /* ================= 扁平化向量图标 ================= */ .ic { display: inline-block; vertical-align: -0.18em; flex-shrink: 0; } diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js b/project/短视频脚本创作/V1.0/mcn-work-shop/server.js index bda5d07..b973918 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/server.js @@ -289,6 +289,37 @@ const server = http.createServer((req, res) => { } catch (e) { return sendErr(500, e.message); } } + // ---------- 提交 AI 任务(Automation 一次性任务,WorkBuddy 自动执行,左侧会话栏可见)---------- + if (p === '/api/run' && req.method === 'POST') { + let body = ''; + req.on('data', (c) => { body += c; if (body.length > 1e6) req.destroy(); }); + req.on('end', () => { + try { + const { prompt, name } = JSON.parse(body || '{}'); + if (!prompt || typeof prompt !== 'string' || !prompt.trim()) return sendErr(400, 'prompt 不能为空'); + const { DatabaseSync } = require('node:sqlite'); + const db = new DatabaseSync(process.env.WORKBUDDY_DB || path.join(os.homedir(), '.workbuddy', 'workbuddy.db')); + const now = Date.now(); + const id = 'automation-' + now; + const cwd = 'D:\\AgentSkill\\mcn-workshop'; // 工作台触发的会话归入 mcn-workshop 空间分组 + const d = new Date(); + const pad = (n) => String(n).padStart(2, '0'); + const scheduledAt = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; + const uidRow = db.prepare("SELECT user_id FROM sessions WHERE user_id IS NOT NULL AND user_id <> '' ORDER BY created_at DESC LIMIT 1").get(); + const ownerUserId = uidRow ? uidRow.user_id : ''; + db.prepare(`INSERT INTO automations (id,name,prompt,status,schedule_type,next_run_at,scheduled_at,rrule,cwds,created_at,updated_at,skills_json,connector_ids_json,model_id,permission_mode,owner_user_id,owner_status) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`) + .run(id, name || '工作台任务', prompt.trim(), 'ACTIVE', 'once', now, scheduledAt, '', JSON.stringify([cwd]), now, now, '[]', '[]', 'deepseek-v4-flash', 'fullAccess', ownerUserId, 'confirmed'); + db.close(); + log('已提交 AI 任务: ' + id + (name ? ' (' + name + ')' : '')); + return sendJSON(200, { ok: true, id, message: '任务已提交,请在左侧会话栏查看执行' }); + } catch (e) { + return sendErr(500, '任务提交失败: ' + e.message); + } + }); + return; + } + // ---------- 静态文件 ---------- let filePath = path.join(PUBLIC_DIR, p === '/' ? 'index.html' : p); // 防穿越