diff --git a/data/skills/production-agent/decision/SKILL.md b/data/skills/production_agent_decision.md similarity index 100% rename from data/skills/production-agent/decision/SKILL.md rename to data/skills/production_agent_decision.md diff --git a/data/skills/production-agent/execution/SKILL.md b/data/skills/production_agent_execution.md similarity index 90% rename from data/skills/production-agent/execution/SKILL.md rename to data/skills/production_agent_execution.md index 770a378..2c4a31c 100644 --- a/data/skills/production-agent/execution/SKILL.md +++ b/data/skills/production_agent_execution.md @@ -21,7 +21,7 @@ description: > ### 提取衍生资产流程 1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表) -2. 根据[衍生资产提取](references/derive-assets-extraction.md)文档中的提取原则,分析剧本内容,为每个角色资产识别出关联的衍生资产(道具、服饰、法器、座驾、场景物件等) +2. 根据[衍生资产提取](references/derive_assets_extraction.md)文档中的提取原则,分析剧本内容,为每个角色资产识别出关联的衍生资产(道具、服饰、法器、座驾、场景物件等) 3. 对每个有衍生状态的资产调用 `set_flowData_assets` 保存 4. 告知用户提取完成,列出为每个角色提取的衍生资产概要 5. **询问用户是否需要生成衍生资产图片**: @@ -32,7 +32,7 @@ description: > ### 生成分镜表流程 1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表) -2. 根据[分镜表生成](references/storyboard-generation.md)文档中的拆分原则和字段填写指引,将剧本拆分为分镜,填写每条分镜的所有字段(id、title、description、camera、duration、frameMode、prompt、lines、sound、associateAssetsIds) +2. 根据[分镜表生成](references/storyboard_generation.md)文档中的拆分原则和字段填写指引,将剧本拆分为分镜,填写每条分镜的所有字段(id、title、description、camera、duration、frameMode、prompt、lines、sound、associateAssetsIds) 3. 调用 `set_flowData({ key: "storyboard", value: 分镜数组 })` 一次性保存完整分镜表 4. 告知用户分镜表生成完成,列出分镜概要(总条数、主要场景) 5. **询问用户是否需要生成分镜图片**: @@ -43,7 +43,7 @@ description: > 本技能附带以下参考资料,根据任务需要使用 `read_skill_file` 工具按需加载: -- [衍生资产提取](references/derive-assets-extraction.md) — 从剧本和角色资产中提取衍生资产的原则和示例 -- [分镜表生成](references/storyboard-generation.md) — 从剧本和资产生成分镜表的拆分原则、字段规范和示例 +- [衍生资产提取](references/derive_assets_extraction.md) — 从剧本和角色资产中提取衍生资产的原则和示例 +- [分镜表生成](references/storyboard_generation.md) — 从剧本和资产生成分镜表的拆分原则、字段规范和示例 **注意**:根据用户当前任务选择性加载对应参考资料,不要一次性全部加载。 diff --git a/data/skills/production_agent_supervision.md b/data/skills/production_agent_supervision.md new file mode 100644 index 0000000..e69de29 diff --git a/data/skills/script-agent/execution/references/adaptation-format.md b/data/skills/references/adaptation_format.md similarity index 100% rename from data/skills/script-agent/execution/references/adaptation-format.md rename to data/skills/references/adaptation_format.md diff --git a/data/skills/references/cooke.md b/data/skills/references/cooke.md new file mode 100644 index 0000000..712bc09 --- /dev/null +++ b/data/skills/references/cooke.md @@ -0,0 +1,77 @@ +# 烤龙肉的做法 + +## 前言 + +龙肉,作为幻想世界中最珍贵的食材之一,其烹饪方法自古以来就是冒险者和宫廷厨师们追求的终极秘技。以下是一份经典的烤龙肉食谱。 + +--- + +## 食材准备 + +| 食材 | 用量 | 备注 | +|------|------|------| +| 龙肉(里脊或腿肉) | 2公斤 | 需提前放血处理 | +| 龙息椒粉 | 2大勺 | 普通辣椒粉可替代 | +| 凤凰蛋黄 | 3个 | 鸡蛋黄可替代 | +| 精灵橄榄油 | 100毫升 | 特级初榨橄榄油可替代 | +| 矮人黑盐 | 适量 | 海盐可替代 | +| 迷迭香、百里香 | 各一把 | 新鲜为佳 | +| 蒜头 | 8瓣 | 拍碎 | +| 蜂蜜 | 3大勺 | 用于表面刷酱 | + +--- + +## 烹饪步骤 + +### 第一步:腌制龙肉(需提前一天) + +1. 将龙肉洗净,用厨房纸巾吸干表面水分 +2. 用锋利的刀在肉面划几道深约1厘米的斜纹,便于入味 +3. 将蛋黄、橄榄油、龙息椒粉、黑盐混合,调成腌料 +4. 均匀涂抹在龙肉表面,放入密封容器 +5. 冷藏腌制12-24小时 + +### 第二步:准备香草束 + +1. 将迷迭香、百里香捆扎成束 +2. 蒜瓣拍碎备用 +3. 准备好蜂蜜刷酱 + +### 第三步:烤制 + +1. **预热烤炉**:将烤炉预热至220°C(若使用龙焰烤炉,调至"青龙"档位) + +2. **初次高温烤制**: + - 将腌制好的龙肉放入烤盘 + - 铺上香草束和蒜瓣 + - 220°C烤制20分钟,封住肉汁 + +3. **转中温慢烤**: + - 温度降至160°C + - 每隔20分钟翻面一次,并刷上蜂蜜 + - 继续烤制约1.5-2小时(视肉块大小调整) + +4. **检验熟度**: + - 用探针温度计测量中心温度达65°C为五分熟 + - 达75°C为全熟 + - 龙肉建议七分熟(约70°C),口感最佳 + +### 第四步:静置与切片 + +1. 出炉后用锡纸包裹,静置15分钟 +2. 逆着纹理切成1厘米厚的片状 +3. 摆盘,淋上烤盘中的肉汁 + +--- + +## 小贴士 + +- ⚠️ **安全提示**:处理龙肉时请佩戴防火手套,残余龙息可能引发灼伤 +- 🍷 **搭配推荐**:建议搭配精灵白葡萄酒或矮人麦芽啤酒 +- 🔥 若龙肉带有冰属性(如霜龙),烤制时间需延长30% + +--- + +## 成品效果 + +外皮金黄酥脆,内部肉质鲜嫩多汁,带有独特的硫磺香气与蜂蜜的甜润,回味中隐约有龙息的微辣感。 \ No newline at end of file diff --git a/data/skills/production-agent/execution/references/derive-assets-extraction.md b/data/skills/references/derive_assets_extraction.md similarity index 100% rename from data/skills/production-agent/execution/references/derive-assets-extraction.md rename to data/skills/references/derive_assets_extraction.md diff --git a/data/skills/universal-agent/references/event-extract.md b/data/skills/references/event_extract.md similarity index 99% rename from data/skills/universal-agent/references/event-extract.md rename to data/skills/references/event_extract.md index 628c03f..1c74f41 100644 --- a/data/skills/universal-agent/references/event-extract.md +++ b/data/skills/references/event_extract.md @@ -1,5 +1,5 @@ --- -name: universal-agent +name: universal_agent description: 专注于从小说原文中提取结构化事件信息的助手。 --- diff --git a/data/skills/script-agent/execution/references/event-format.md b/data/skills/references/event_format.md similarity index 100% rename from data/skills/script-agent/execution/references/event-format.md rename to data/skills/references/event_format.md diff --git a/data/skills/universal-agent/references/novel-character-extract.md b/data/skills/references/novel_character_extract.md similarity index 99% rename from data/skills/universal-agent/references/novel-character-extract.md rename to data/skills/references/novel_character_extract.md index 8731b37..c536951 100644 --- a/data/skills/universal-agent/references/novel-character-extract.md +++ b/data/skills/references/novel_character_extract.md @@ -1,5 +1,5 @@ --- -name: universal-agent +name: universal_agent description: 专注于从小说原文中提取角色信息并生成视觉化角色描述的助手。 --- diff --git a/data/skills/universal-agent/references/novel-props-extract.md b/data/skills/references/novel_props_extract.md similarity index 99% rename from data/skills/universal-agent/references/novel-props-extract.md rename to data/skills/references/novel_props_extract.md index bc3055a..5c9addd 100644 --- a/data/skills/universal-agent/references/novel-props-extract.md +++ b/data/skills/references/novel_props_extract.md @@ -1,5 +1,5 @@ --- -name: universal-agent +name: universal_agent description: 专注于从小说原文中提取道具物品信息并生成视觉化描述的助手。 --- diff --git a/data/skills/universal-agent/references/novel-scene-extract.md b/data/skills/references/novel_scene_extract.md similarity index 99% rename from data/skills/universal-agent/references/novel-scene-extract.md rename to data/skills/references/novel_scene_extract.md index 7d66444..1486f21 100644 --- a/data/skills/universal-agent/references/novel-scene-extract.md +++ b/data/skills/references/novel_scene_extract.md @@ -1,5 +1,5 @@ --- -name: universal-agent +name: universal_agent description: 专注于从小说原文中提取场景信息并生成视觉化场景描述的助手。 --- diff --git a/data/skills/script-agent/decision/references/pipeline.md b/data/skills/references/pipeline.md similarity index 100% rename from data/skills/script-agent/decision/references/pipeline.md rename to data/skills/references/pipeline.md diff --git a/data/skills/production-agent/decision/references/plan.md b/data/skills/references/plan.md similarity index 100% rename from data/skills/production-agent/decision/references/plan.md rename to data/skills/references/plan.md diff --git a/data/skills/script-agent/supervision/references/quality-criteria.md b/data/skills/references/quality_criteria.md similarity index 100% rename from data/skills/script-agent/supervision/references/quality-criteria.md rename to data/skills/references/quality_criteria.md diff --git a/data/skills/script-agent/execution/references/script-format.md b/data/skills/references/script_format.md similarity index 100% rename from data/skills/script-agent/execution/references/script-format.md rename to data/skills/references/script_format.md diff --git a/data/skills/script-agent/execution/references/skeleton-format.md b/data/skills/references/skeleton_format.md similarity index 100% rename from data/skills/script-agent/execution/references/skeleton-format.md rename to data/skills/references/skeleton_format.md diff --git a/data/skills/production-agent/execution/references/storyboard-generation.md b/data/skills/references/storyboard_generation.md similarity index 98% rename from data/skills/production-agent/execution/references/storyboard-generation.md rename to data/skills/references/storyboard_generation.md index 18bfd62..3bd98e2 100644 --- a/data/skills/production-agent/execution/references/storyboard-generation.md +++ b/data/skills/references/storyboard_generation.md @@ -206,7 +206,7 @@ set_flowData({ camera: "中景 · 缓慢推进", duration: 3, frameMode: "endFrame", - prompt: "A white-haired young man in white robes kneeling on the floor, spitting blood, trembling body, pale face, dramatic lighting, cinematic composition", + prompt: "A white_haired young man in white robes kneeling on the floor, spitting blood, trembling body, pale face, dramatic lighting, cinematic composition", lines: null, sound: "喷血声", associateAssetsIds: [0] @@ -218,7 +218,7 @@ set_flowData({ camera: "大特写", duration: 3, frameMode: "firstFrame", - prompt: "Close-up of a jade token with glowing runes fading, fine cracks appearing on the surface, dark moody lighting, cinematic detail shot", + prompt: "Close_up of a jade token with glowing runes fading, fine cracks appearing on the surface, dark moody lighting, cinematic detail shot", lines: null, sound: "玉石碎裂声", associateAssetsIds: [2] diff --git a/data/skills/universal-agent/references/video-dialogue-extract.md b/data/skills/references/video_dialogue_extract.md similarity index 99% rename from data/skills/universal-agent/references/video-dialogue-extract.md rename to data/skills/references/video_dialogue_extract.md index 0f5e19d..1baf004 100644 --- a/data/skills/universal-agent/references/video-dialogue-extract.md +++ b/data/skills/references/video_dialogue_extract.md @@ -1,5 +1,5 @@ --- -name: universal-agent +name: universal_agent description: 专注于从视频分镜提示词中提取结构化台词、旁白与音效信息的助手。 --- diff --git a/data/skills/script-agent/decision/SKILL.md b/data/skills/script_agent_decision.md similarity index 100% rename from data/skills/script-agent/decision/SKILL.md rename to data/skills/script_agent_decision.md diff --git a/data/skills/script-agent/execution/SKILL.md b/data/skills/script_agent_execution.md similarity index 96% rename from data/skills/script-agent/execution/SKILL.md rename to data/skills/script_agent_execution.md index 16c7567..2375fb4 100644 --- a/data/skills/script-agent/execution/SKILL.md +++ b/data/skills/script_agent_execution.md @@ -77,7 +77,7 @@ const planData = { 4. 生成汇总统计(总章节、强主线章节数、可压缩章节、预估总时长、目标时长、压缩比) 5. 输出 Markdown 表格格式的事件表,作为后续任务上下文(不写入 planData) -**输出格式**:参考 [event-format.md](references/event-format.md) +**输出格式**:参考 [event_format.md](references/event_format.md) **关键原则**: - 核心事件描述必须包含**动作**和**结果**,不能只写状态 @@ -115,7 +115,7 @@ const planData = { 7. 设计付费卡点(位置、内容、钩子类型) 8. 调用 `set_planData_storySkeleton` 保存 -**输出格式**:参考 [skeleton-format.md](references/skeleton-format.md) +**输出格式**:参考 [skeleton_format.md](references/skeleton_format.md) **关键约束**: - 总时长 = 【项目配置】中的集数 × 单集时长 @@ -145,7 +145,7 @@ const planData = { - 角色态度作为世界观锚点 5. 调用 `set_planData_adaptationStrategy` 保存 -**输出格式**:参考 [adaptation-format.md](references/adaptation-format.md) +**输出格式**:参考 [adaptation_format.md](references/adaptation_format.md) **关键原则**: - 故事核优先:主角是"被定义为疯子却选择活下去的人",所有决策服务于此弧线 @@ -179,7 +179,7 @@ const planData = { - 表演指示(情绪、动作细节) 6. 仅当指令中包含 `用户已确认写入SQL: 是` 时,调用 `insert_script_to_sqlite` 写入剧本 -**输出格式**:参考 [script-format.md](references/script-format.md) +**输出格式**:参考 [script_format.md](references/script_format.md) **关键约束**: - 单集总时长严格控制在【项目配置】指定的单集时长 ±10秒 diff --git a/data/skills/script-agent/supervision/SKILL.md b/data/skills/script_agent_supervision.md similarity index 98% rename from data/skills/script-agent/supervision/SKILL.md rename to data/skills/script_agent_supervision.md index 1fca5b8..38b48d6 100644 --- a/data/skills/script-agent/supervision/SKILL.md +++ b/data/skills/script_agent_supervision.md @@ -84,7 +84,7 @@ description: >- | 转场标注 | 节拍间转场方式明确 | 轻微 | | 情绪连贯 | 节拍间情绪过渡自然 | 中等 | -详细审核标准请参考 [quality-criteria.md](references/quality-criteria.md)。 +详细审核标准请参考 [quality_criteria.md](references/quality_criteria.md)。 ## 审核报告格式 diff --git a/data/skills/universal-agent/SKILL.md b/data/skills/universal_agent.md similarity index 85% rename from data/skills/universal-agent/SKILL.md rename to data/skills/universal_agent.md index 32fcc5e..65efb41 100644 --- a/data/skills/universal-agent/SKILL.md +++ b/data/skills/universal_agent.md @@ -1,5 +1,5 @@ --- -name: universal-agent +name: universal_agent description: 通用文本分析与内容提取 Agent,支持小说事件提取、视频台词提取、角色/场景/道具资产描述生成等多种结构化内容处理任务。 --- @@ -11,36 +11,36 @@ description: 通用文本分析与内容提取 Agent,支持小说事件提取 你拥有以下参考技能(references),根据用户请求自动匹配对应技能执行: -### 1. 小说章节事件提取(event-extract) +### 1. 小说章节事件提取(event_extract) - **触发条件**:用户提供小说原文,要求提取章节事件、生成事件表 -- **参考文件**:`references/event-extract.md` +- **参考文件**:`references/event_extract.md` - **输出**:结构化事件表格(章节、角色、核心事件、主线关系、信息密度、预估集长、情绪强度)+ 汇总统计 -### 2. 视频提示词台词提取(video-dialogue-extract) +### 2. 视频提示词台词提取(video_dialogue_extract) - **触发条件**:用户提供视频分镜提示词/画面描述,要求从中提取或还原台词、旁白、音效文本 -- **参考文件**:`references/video-dialogue-extract.md` +- **参考文件**:`references/video_dialogue_extract.md` - **输出**:结构化台词表(镜号、角色、台词内容、台词类型、情绪标注、时长估算) -### 3. 小说角色提取(novel-character-extract) +### 3. 小说角色提取(novel_character_extract) - **触发条件**:用户提供小说原文,要求提取角色信息、生成角色视觉描述 -- **参考文件**:`references/novel-character-extract.md` +- **参考文件**:`references/novel_character_extract.md` - **资产类型**:`role`(对应 `o_assets.type = "role"`) - **输出**:结构化角色资产表(角色名称、角色定位、外貌特征、服饰描述、体型体态、标志性特征、性格气质、首次出场、出场章节数、状态变体)+ 核心角色卡片 -### 4. 小说场景提取(novel-scene-extract) +### 4. 小说场景提取(novel_scene_extract) - **触发条件**:用户提供小说原文,要求提取场景/地点信息、生成场景视觉描述 -- **参考文件**:`references/novel-scene-extract.md` +- **参考文件**:`references/novel_scene_extract.md` - **资产类型**:`scene`(对应 `o_assets.type = "scene"`) - **输出**:结构化场景资产表(场景名称、场景类型、空间描述、光照氛围、关键陈设、色调基调、首次出场、出场章节数、关联角色、状态变体)+ 核心场景卡片 -### 5. 小说道具提取(novel-props-extract) +### 5. 小说道具提取(novel_props_extract) - **触发条件**:用户提供小说原文,要求提取道具/物品/器物信息、生成道具视觉描述 -- **参考文件**:`references/novel-props-extract.md` +- **参考文件**:`references/novel_props_extract.md` - **资产类型**:`tool`(对应 `o_assets.type = "tool"`) - **输出**:结构化道具资产表(道具名称、类别、外观描述、尺寸参考、材质质感、功能/用途、首次出场、关联角色、状态变体)+ 高频道具排名 diff --git a/skillList.json b/skillList.json new file mode 100644 index 0000000..99be541 --- /dev/null +++ b/skillList.json @@ -0,0 +1,58 @@ +[ + { + "skillId": "52c51fa8655f899a1b7aae9b6aad7251", + "attribution": "universal_agent.md" + }, + { + "skillId": "6d46cdca10b2f49e07e515885d1387a0", + "attribution": "universal_agent.md" + }, + { + "skillId": "1864df75d1d65f76e275046649ecaef8", + "attribution": "universal_agent.md" + }, + { + "skillId": "3e5efec258c8d8e6a39bcef12f8ee058", + "attribution": "universal_agent.md" + }, + { + "skillId": "7fbce6f90d7d85496ba9817e9622e640", + "attribution": "universal_agent.md" + }, + { + "skillId": "31fb5c5a1f514ec1e66b4eba9f22d4db", + "attribution": "script_agent_decision.md" + }, + { + "skillId": "27dc2dfc901de2180227d0269217583a", + "attribution": "script_agent_execution.md" + }, + { + "skillId": "d49fa09504fe784a8e6eb102756c6d56", + "attribution": "script_agent_execution.md" + }, + { + "skillId": "797906c2ddf0750f050bcdeae23eae3d", + "attribution": "script_agent_execution.md" + }, + { + "skillId": "1abd8675c0c3e62b20c0b151d2ec0fb1", + "attribution": "script_agent_execution.md" + }, + { + "skillId": "0b7828d7a6ab458a4b201122f08d6c16", + "attribution": "script_agent_supervision.md" + }, + { + "skillId": "5c1772b5f9c420d9eae9ca02914ba087", + "attribution": "production_agent_decision.md" + }, + { + "skillId": "75a45cf996015ca819582873887ec301", + "attribution": "production_agent_execution.md" + }, + { + "skillId": "fce75f69d704c19bebcb356bc1bd6e81", + "attribution": "production_agent_execution.md" + } +] \ No newline at end of file diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index c4e0c3f..7d2a929 100644 --- a/src/agents/productionAgent/index.ts +++ b/src/agents/productionAgent/index.ts @@ -38,7 +38,7 @@ export async function decisionAI(ctx: AgentContext) { const { isolationKey, text, abortSignal } = ctx; const memory = new Memory("productionAgent", isolationKey); await memory.add("user", text); - const [skill, mem] = await Promise.all([useSkill("production-agent", "decision"), memory.get(text)]); + const [skill, mem] = await Promise.all([useSkill("production_agent_decision.md"), memory.get(text)]); const systemPrompt = buildSystemPrompt(skill.prompt, mem); @@ -70,7 +70,7 @@ export async function executionAI(ctx: AgentContext) { resTool.systemMessage("执行层AI 接管聊天"); const memory = new Memory("productionAgent", isolationKey); - const [skill, mem] = await Promise.all([useSkill("production-agent", "execution"), memory.get(text)]); + const [skill, mem] = await Promise.all([useSkill("production_agent_execution.md"), memory.get(text)]); const systemPrompt = buildSystemPrompt(skill.prompt, mem); @@ -94,7 +94,7 @@ export async function executionAI(ctx: AgentContext) { export async function supervisionAI(ctx: AgentContext) { const { isolationKey, text, abortSignal } = ctx; const memory = new Memory("productionAgent", isolationKey); - const [skill, mem] = await Promise.all([useSkill("production-agent", "supervision"), memory.get(text)]); + const [skill, mem] = await Promise.all([useSkill("production_agent_supervision.md"), memory.get(text)]); const systemPrompt = buildSystemPrompt(skill.prompt, mem); diff --git a/src/agents/productionAgent/tools.ts b/src/agents/productionAgent/tools.ts index 169cb25..88977c3 100644 --- a/src/agents/productionAgent/tools.ts +++ b/src/agents/productionAgent/tools.ts @@ -493,7 +493,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { resTool.systemMessage(`图片生成调度计划:共 ${levels.length} 层,${images.length} 张图片`); // --- 准备公共数据 --- - const skill = await useSkill("universal-agent"); + const skill = await useSkill("universal_agent.md"); const projectData = await u.db("o_project").where("id", resTool.data.projectId).select("videoRatio").first(); const imageModel = resTool.data.imageModel; @@ -596,7 +596,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { ), }), execute: async ({ images }) => { - const skill = await useSkill("universal-agent"); + const skill = await useSkill("universal_agent.md"); console.log("[tools] generate_assets_images", images); //先获取到前端资产数据 const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index 14229e0..adce6f7 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -42,7 +42,7 @@ export async function decisionAI(ctx: AgentContext) { const memory = new Memory("scriptAgent", isolationKey); console.log("%c Line:43 🥟 isolationKey", "background:#4fff4B", isolationKey); await memory.add("user", text); - const [skill, mem] = await Promise.all([useSkill("script-agent", "decision"), memory.get(text)]); + const [skill, mem] = await Promise.all([useSkill("script_agent_decision.md"), memory.get(text)]); const systemPrompt = buildSystemPrompt(skill.prompt, mem); @@ -87,7 +87,7 @@ export async function executionAI(ctx: AgentContext) { resTool.systemMessage("执行层AI 接管聊天"); const memory = new Memory("scriptAgent", isolationKey); - const [skill, mem] = await Promise.all([useSkill("script-agent", "execution"), memory.get(text)]); + const [skill, mem] = await Promise.all([useSkill("script_agent_execution.md"), memory.get(text)]); const systemPrompt = buildSystemPrompt(skill.prompt, mem); @@ -114,7 +114,7 @@ export async function supervisionAI(ctx: AgentContext) { resTool.systemMessage("监督层AI 接管聊天"); const memory = new Memory("scriptAgent", isolationKey); - const [skill, mem] = await Promise.all([useSkill("script-agent", "supervision"), memory.get(text)]); + const [skill, mem] = await Promise.all([useSkill("script_agent_supervision.md"), memory.get(text)]); const systemPrompt = buildSystemPrompt(skill.prompt, mem); diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index b4abe1b..f6dad91 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -118,6 +118,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { type: i.type, describe: i.desc, projectId: resTool.data.projectId, + startTime: Date.now(), }); assetId.push(id); } diff --git a/src/app.ts b/src/app.ts index 297bb14..3458637 100644 --- a/src/app.ts +++ b/src/app.ts @@ -17,7 +17,6 @@ const app = express(); const server = http.createServer(app); export default async function startServe(randomPort: Boolean = false) { - console.log("%c Line:20 🍰 randomPort", "background:#b03734", randomPort); const io = new Server(server, { cors: { origin: "*" } }); socketInit(io); diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index feea1e8..530d42b 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -1,5 +1,7 @@ import { Knex } from "knex"; import { v4 as uuid } from "uuid"; +import { getEmbedding } from "@/utils/agent/embedding"; + interface TableSchema { name: string; builder: (table: Knex.CreateTableBuilder) => void; @@ -28,6 +30,8 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => builder: (table) => { table.integer("id"); table.string("projectType"); + table.string("imageModel"); + table.string("videoModel"); table.text("name"); table.text("intro"); table.text("type"); @@ -397,24 +401,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.unique(["id"]); }, }, - //记忆表(message=原始消息, summary=压缩摘要) - { - name: "memories", - builder: (table) => { - table.text("id").notNullable(); - table.text("isolationKey").notNullable(); // 记忆隔离键 - table.text("type").notNullable(); // 'message' | 'summary' - table.text("role"); // 'user' | 'assistant' - table.text("content").notNullable(); - table.text("embedding"); // 向量嵌入 JSON - table.text("relatedMessageIds"); // summary关联的message id列表 JSON - table.integer("summarized").defaultTo(0); // message是否已被总结 0/1 - table.integer("createTime").notNullable(); - table.primary(["id"]); - table.index(["isolationKey", "type"]); - table.index(["isolationKey", "summarized"]); - }, - }, //图片工作流表 { name: "o_imageFlow", @@ -445,6 +431,386 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.unique(["scriptId", "assetId"]); }, }, + { + name: "o_skillList", + builder: (table) => { + table.text("id").notNullable(); + table.text("md5").notNullable(); + table.text("path").notNullable(); + table.text("name").notNullable(); //文件名 + table.text("description").notNullable(); //描述 + table.text("embedding"); // 向量嵌入 JSON + table.text("type").notNullable(); // "main" | "references" + table.integer("createTime").notNullable(); + table.integer("updateTime").notNullable(); + table.integer("state").notNullable(); // 1正常,0正在生成description,-1description为空。-2归属为空,-3md5变动 + table.primary(["id"]); + }, + initData: async (knex) => { + const list = [ + { + id: "4fb36012e56e395b425569987f5dab0e", + md5: "fca3c269c5f325a65dafa663c9bb9773", + path: "production_agent_decision.md", + name: "production_agent_decision", + description: "", + embedding: "", + type: "main", + createTime: 1774447310118, + updateTime: 1774447310118, + state: -1, + }, + { + id: "017b6338d7aa227cd614ec1fb25fd83e", + md5: "2610b80abe4bd048fe61c73adc7388ac", + path: "production_agent_execution.md", + name: "production_agent_execution", + description: "", + embedding: "", + type: "main", + createTime: 1774447310118, + updateTime: 1774447310118, + state: -1, + }, + { + id: "f03c8e67b61580de9ea5b9d166521b67", + md5: "d41d8cd98f00b204e9800998ecf8427e", + path: "production_agent_supervision.md", + name: "production_agent_supervision", + description: "", + embedding: "", + type: "main", + createTime: 1774447310118, + updateTime: 1774447310118, + state: -1, + }, + { + id: "50b49d8af5d364665b463c23f6a4d8bb", + md5: "fbba66e0df2426996277b299710c3033", + path: "script_agent_decision.md", + name: "script_agent_decision", + description: "", + embedding: "", + type: "main", + createTime: 1774447310118, + updateTime: 1774447310118, + state: -1, + }, + { + id: "427727727e1095c54b6840cd21382d82", + md5: "7e5911242af7233854d533278c6a8ccb", + path: "script_agent_execution.md", + name: "script_agent_execution", + description: "", + embedding: "", + type: "main", + createTime: 1774447310118, + updateTime: 1774447310118, + state: -1, + }, + { + id: "02848fb0dd582fd926502c77ecf9679c", + md5: "7a8b6a311b015cd47bf17cc52b935348", + path: "script_agent_supervision.md", + name: "script_agent_supervision", + description: "", + embedding: "", + type: "main", + createTime: 1774447310118, + updateTime: 1774447310118, + state: -1, + }, + { + id: "a1e818cc03a0b355b239ac1fb0512969", + md5: "1fd22029e8047aa30b0dfd703cb837ed", + path: "universal_agent.md", + name: "universal_agent", + description: "", + embedding: "", + type: "main", + createTime: 1774447310118, + updateTime: 1774447310118, + state: -1, + }, + { + id: "3e5efec258c8d8e6a39bcef12f8ee058", + md5: "efccb0464cfd472861b49ebf737d4820", + path: "references/event_extract.md", + name: "event_extract", + description: + "专为小说改编短剧设计的文本分析助手,逐章提取涉及角色、核心事件、主线关系、信息密度、预估集长及情绪强度等结构化信息,以Markdown表格形式输出,并附汇总统计,辅助短剧制作的内容规划与时长估算。", + embedding: "", + type: "references", + createTime: 1774447310118, + updateTime: 1774450165911, + state: 1, + }, + { + id: "52c51fa8655f899a1b7aae9b6aad7251", + md5: "783678aaab829b34e7c30a414c356bf6", + path: "references/novel_character_extract.md", + name: "novel_character_extract", + description: + "专为小说内容分析设计的角色提取助手,从原文中识别并结构化输出所有重要角色的视觉描述信息,包括外貌、服饰、体态、状态变体等字段,供美术制作和AI角色图生成使用。", + embedding: "", + type: "references", + createTime: 1774447310118, + updateTime: 1774450080903, + state: 1, + }, + { + id: "6d46cdca10b2f49e07e515885d1387a0", + md5: "10544d12c4ef011e6b3b63a99b8c7fa8", + path: "references/novel_props_extract.md", + name: "novel_props_extract", + description: + "专注于从小说原文中提取道具物品信息的分析助手,能识别武器、法器、药物等各类道具,生成包含外观、材质、尺寸、功能及状态变体的结构化视觉描述表格,供美术制作和AI绘图使用。", + embedding: "", + type: "references", + createTime: 1774447310118, + updateTime: 1774450094771, + state: 1, + }, + { + id: "1864df75d1d65f76e275046649ecaef8", + md5: "65603aa495a541f54c55b7f30e149f45", + path: "references/novel_scene_extract.md", + name: "novel_scene_extract", + description: + "专注于从小说原文中提取并结构化场景信息的分析助手,可识别各类场景地点,输出包含空间描述、光照氛围、关键陈设、色调基调等字段的标准化场景资产表,用于美术制作和AI绘图的场景概念图生成。", + embedding: "", + type: "references", + createTime: 1774447310118, + updateTime: 1774450161878, + state: 1, + }, + { + id: "7fbce6f90d7d85496ba9817e9622e640", + md5: "830559e8f2cd5d0fa8e6df48a164fe2d", + path: "references/video_dialogue_extract.md", + name: "video_dialogue_extract", + description: + "这是一个专门从视频分镜提示词中提取结构化台词、旁白与音效信息的AI助手配置文档,定义了完整的输出格式(含镜号、角色、台词类型、表演指导等字段)、提取规则及处理流程,用于将视频分镜描述转化为标准化台词表。", + embedding: "", + type: "references", + createTime: 1774447310118, + updateTime: 1774450180712, + state: 1, + }, + { + id: "31fb5c5a1f514ec1e66b4eba9f22d4db", + md5: "43e63450efe0c9af8a3a40b036d36cb4", + path: "references/pipeline.md", + name: "pipeline", + description: + "面向短剧改编项目的四阶段流水线说明文档,涵盖事件提取、故事骨架、改编策略、剧本编写的串行执行流程,定义了决策层、执行层、监督层的协作规范及派发、审核、修复的交互格式与质量门控标准。", + embedding: "", + type: "references", + createTime: 1774451946248, + updateTime: 1774451984533, + state: 1, + }, + { + id: "27dc2dfc901de2180227d0269217583a", + md5: "7d353be4bab7a794436d9abff2b9c6ee", + path: "references/adaptation_format.md", + name: "adaptation_format", + description: + "本文档规定了改编策略输出的标准格式,包括核心改编原则、删除决策和世界观呈现策略三大模块的书写规范,明确各模块所需涵盖的维度与要素,用于指导竖屏短剧等载体的文学改编工作。", + embedding: "", + type: "references", + createTime: 1774452010535, + updateTime: 1774452022083, + state: 1, + }, + { + id: "d49fa09504fe784a8e6eb102756c6d56", + md5: "2ef08a7479f29d74986999ceb02092c8", + path: "references/event_format.md", + name: "event_format", + description: + "本文档规定了影视改编项目中事件表的标准输出格式,包括文件头、事件表格、各字段填写规范(章节、角色、核心事件、主线关系、情绪强度、预估时长)及汇总统计模板,用于指导从原著提取事件并评估改编集数与压缩比的第一阶段工作。", + embedding: "", + type: "references", + createTime: 1774452010535, + updateTime: 1774452030858, + state: 1, + }, + { + id: "797906c2ddf0750f050bcdeae23eae3d", + md5: "f5e7fe6db7e05db69d5dc327c4c538f2", + path: "references/script_format.md", + name: "script_format", + description: + "本文档为竖屏短剧剧本的输出格式规范,定义了文件头、节拍结构、分镜脚本、画面描述、台词、转场标注等标准格式要求,并附有时长控制参数与自查清单,供AI视频生成和导演制作使用。", + embedding: "", + type: "references", + createTime: 1774452010535, + updateTime: 1774452042934, + state: 1, + }, + { + id: "1abd8675c0c3e62b20c0b151d2ec0fb1", + md5: "a587532c737ce15022e1522021f099bb", + path: "references/skeleton_format.md", + name: "skeleton_format", + description: + "本文档定义了故事骨架文件(skeleton.md)的标准化输出格式,涵盖故事核、人物成长隐线、三幕结构、分集决策模板、全局删减记录、付费卡点设计及自查清单,用于指导编剧将章节事件列表转化为结构完整的剧集改编方案。", + embedding: "", + type: "references", + createTime: 1774452010535, + updateTime: 1774452057184, + state: 1, + }, + { + id: "0b7828d7a6ab458a4b201122f08d6c16", + md5: "120b3c856f1b2a8a429e11319e8c95fe", + path: "references/quality_criteria.md", + name: "quality_criteria", + description: + "本文档为影视/短剧项目的质量审核标准手册,涵盖事件表、故事骨架、改编策略和剧本四大模块的详细审核规则,规定了格式规范、角色名称统一、时长合理性、画面可执行性及场景氛围一致性等审核要求,用于确保各阶段产出物的内容准确性与制作可行性。", + embedding: "", + type: "references", + createTime: 1774452068093, + updateTime: 1774452087877, + state: 1, + }, + { + id: "5c1772b5f9c420d9eae9ca02914ba087", + md5: "c710ab7d237e1f0c5aa3d208e0f5b484", + path: "references/plan.md", + name: "plan", + description: + "该文档定义了AI代理生成执行计划的规范,包括任务总览、步骤列表(含编号、名称、详细内容、预期输出及依赖关系)和执行顺序标注,并提供标准回复模板,用于将用户需求拆解为可直接传入子代理工具执行的具体步骤。", + embedding: "", + type: "references", + createTime: 1774452098447, + updateTime: 1774452109574, + state: 1, + }, + { + id: "75a45cf996015ca819582873887ec301", + md5: "6045d76873fd58b8b87a914a21a38439", + path: "references/derive_assets_extraction.md", + name: "derive_assets_extraction", + description: + "本文档是一份技术操作指南,说明如何根据剧本内容和已有资产列表,提取每个资产在剧情中出现的不同视觉状态变体(derive),并通过工具函数读取和写入数据,用于后续图片生成参考。", + embedding: "", + type: "references", + createTime: 1774452119499, + updateTime: 1774452129516, + state: 1, + }, + { + id: "fce75f69d704c19bebcb356bc1bd6e81", + md5: "a3b3432854970f22949ba47236a6532f", + path: "references/storyboard_generation.md", + name: "storyboard_generation", + description: + "根据剧本和资产列表生成结构化分镜面板的工具指南,涵盖分镜拆分原则、字段填写规范及工具调用流程,用于将剧本转化为含画面描述、镜头语言、台词和AI绘图提示词的分镜数据。", + embedding: "", + type: "references", + createTime: 1774452119499, + updateTime: 1774452140873, + state: 1, + }, + ]; + await Promise.all( + list.map(async (item) => { + const embedding = await getEmbedding(item.description); + item.embedding = JSON.stringify(embedding); + }), + ); + await knex("o_skillList").insert(list); + }, + }, + { + name: "o_skillAttribution", + builder: (table) => { + table.text("skillId").notNullable().references("id").inTable("o_skillList").onDelete("CASCADE"); + table.text("attribution").notNullable(); // "production_agent_decision.md" | "production_agent_execution.md" | "production_agent_supervision.md" | "script_agent_decision.md" | "script_agent_execution.md" | "script_agent_supervision.md" | "universal_agent.md" + table.primary(["skillId", "attribution"]); + table.index(["attribution"]); + }, + initData: async (knex) => { + await knex("o_skillAttribution").insert([ + { + skillId: "52c51fa8655f899a1b7aae9b6aad7251", + attribution: "universal_agent.md", + }, + { + skillId: "6d46cdca10b2f49e07e515885d1387a0", + attribution: "universal_agent.md", + }, + { + skillId: "1864df75d1d65f76e275046649ecaef8", + attribution: "universal_agent.md", + }, + { + skillId: "3e5efec258c8d8e6a39bcef12f8ee058", + attribution: "universal_agent.md", + }, + { + skillId: "7fbce6f90d7d85496ba9817e9622e640", + attribution: "universal_agent.md", + }, + { + skillId: "31fb5c5a1f514ec1e66b4eba9f22d4db", + attribution: "script_agent_decision.md", + }, + { + skillId: "27dc2dfc901de2180227d0269217583a", + attribution: "script_agent_execution.md", + }, + { + skillId: "d49fa09504fe784a8e6eb102756c6d56", + attribution: "script_agent_execution.md", + }, + { + skillId: "797906c2ddf0750f050bcdeae23eae3d", + attribution: "script_agent_execution.md", + }, + { + skillId: "1abd8675c0c3e62b20c0b151d2ec0fb1", + attribution: "script_agent_execution.md", + }, + { + skillId: "0b7828d7a6ab458a4b201122f08d6c16", + attribution: "script_agent_supervision.md", + }, + { + skillId: "5c1772b5f9c420d9eae9ca02914ba087", + attribution: "production_agent_decision.md", + }, + { + skillId: "75a45cf996015ca819582873887ec301", + attribution: "production_agent_execution.md", + }, + { + skillId: "fce75f69d704c19bebcb356bc1bd6e81", + attribution: "production_agent_execution.md", + }, + ]); + }, + }, + //记忆表(message=原始消息, summary=压缩摘要) + { + name: "memories", + builder: (table) => { + table.text("id").notNullable(); + table.text("isolationKey").notNullable(); // 记忆隔离键 + table.text("type").notNullable(); // 'message' | 'summary' + table.text("role"); // 'user' | 'assistant' + table.text("content").notNullable(); + table.text("embedding"); // 向量嵌入 JSON + table.text("relatedMessageIds"); // summary关联的message id列表 JSON + table.integer("summarized").defaultTo(0); // message是否已被总结 0/1 + table.integer("createTime").notNullable(); + table.primary(["id"]); + table.index(["isolationKey", "type"]); + table.index(["isolationKey", "summarized"]); + }, + }, ]; for (const t of tables) { diff --git a/src/router.ts b/src/router.ts index f1a88b4..f274003 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash ced882fe9cc49f6e16ade49cf276b583 +// @routes-hash e48d3637c019a24988e008910e734d8c import { Express } from "express"; import route1 from "./routes/agents/clearMemory"; @@ -49,45 +49,50 @@ import route45 from "./routes/production/getFlowData"; import route46 from "./routes/production/getProductionData"; import route47 from "./routes/production/getStoryboardData"; import route48 from "./routes/production/saveFlowData"; -import route49 from "./routes/production/storyboard/downPreviewImage"; -import route50 from "./routes/production/storyboard/getStoryboardData"; -import route51 from "./routes/production/storyboard/previewImage"; -import route52 from "./routes/production/workbench/confirmSelection"; -import route53 from "./routes/production/workbench/delVideo"; -import route54 from "./routes/production/workbench/generateVideo"; -import route55 from "./routes/production/workbench/getChatLines"; -import route56 from "./routes/production/workbench/getVideoModelDetail"; -import route57 from "./routes/production/workbench/videoPolling"; -import route58 from "./routes/project/addProject"; -import route59 from "./routes/project/delProject"; -import route60 from "./routes/project/editProject"; -import route61 from "./routes/project/getProject"; -import route62 from "./routes/script/addScript"; -import route63 from "./routes/script/delScript"; -import route64 from "./routes/script/exportScript"; -import route65 from "./routes/script/getScrptApi"; -import route66 from "./routes/script/updateScript"; -import route67 from "./routes/scriptAgent/getPlanData"; -import route68 from "./routes/scriptAgent/setPlanData"; -import route69 from "./routes/setting/agentDeploy/agentSetKey"; -import route70 from "./routes/setting/agentDeploy/deployAgentModel"; -import route71 from "./routes/setting/agentDeploy/getAgentDeploy"; -import route72 from "./routes/setting/dbConfig/clearData"; -import route73 from "./routes/setting/fileManagement/openFolder"; -import route74 from "./routes/setting/getTextModel"; -import route75 from "./routes/setting/loginConfig/getUser"; -import route76 from "./routes/setting/loginConfig/updateUserPwd"; -import route77 from "./routes/setting/memoryConfig/getMemory"; -import route78 from "./routes/setting/memoryConfig/sureMemory"; -import route79 from "./routes/setting/vendorConfig/addVendor"; -import route80 from "./routes/setting/vendorConfig/deleteVendor"; -import route81 from "./routes/setting/vendorConfig/getVendorList"; -import route82 from "./routes/setting/vendorConfig/modelTest"; -import route83 from "./routes/setting/vendorConfig/updateVendor"; -import route84 from "./routes/task/getTaskApi"; -import route85 from "./routes/task/getTaskCategories"; -import route86 from "./routes/task/taskDetails"; -import route87 from "./routes/test/test"; +import route49 from "./routes/production/storyboard/previewImage"; +import route50 from "./routes/production/workbench/confirmSelection"; +import route51 from "./routes/production/workbench/delVideo"; +import route52 from "./routes/production/workbench/generateVideo"; +import route53 from "./routes/production/workbench/getChatLines"; +import route54 from "./routes/production/workbench/getVideoModelDetail"; +import route55 from "./routes/production/workbench/videoPolling"; +import route56 from "./routes/project/addProject"; +import route57 from "./routes/project/delProject"; +import route58 from "./routes/project/editProject"; +import route59 from "./routes/project/getProject"; +import route60 from "./routes/script/addScript"; +import route61 from "./routes/script/delScript"; +import route62 from "./routes/script/exportScript"; +import route63 from "./routes/script/getScrptApi"; +import route64 from "./routes/script/updateScript"; +import route65 from "./routes/scriptAgent/getPlanData"; +import route66 from "./routes/scriptAgent/setPlanData"; +import route67 from "./routes/setting/agentDeploy/agentSetKey"; +import route68 from "./routes/setting/agentDeploy/deployAgentModel"; +import route69 from "./routes/setting/agentDeploy/getAgentDeploy"; +import route70 from "./routes/setting/dbConfig/clearData"; +import route71 from "./routes/setting/fileManagement/openFolder"; +import route72 from "./routes/setting/getTextModel"; +import route73 from "./routes/setting/loginConfig/getUser"; +import route74 from "./routes/setting/loginConfig/updateUserPwd"; +import route75 from "./routes/setting/memoryConfig/getMemory"; +import route76 from "./routes/setting/memoryConfig/sureMemory"; +import route77 from "./routes/setting/skillManagement/addSkill"; +import route78 from "./routes/setting/skillManagement/deleteSkill"; +import route79 from "./routes/setting/skillManagement/embeddingSkill"; +import route80 from "./routes/setting/skillManagement/generateDescription"; +import route81 from "./routes/setting/skillManagement/getSkillList"; +import route82 from "./routes/setting/skillManagement/scanSkills"; +import route83 from "./routes/setting/skillManagement/updateSkill"; +import route84 from "./routes/setting/vendorConfig/addVendor"; +import route85 from "./routes/setting/vendorConfig/deleteVendor"; +import route86 from "./routes/setting/vendorConfig/getVendorList"; +import route87 from "./routes/setting/vendorConfig/modelTest"; +import route88 from "./routes/setting/vendorConfig/updateVendor"; +import route89 from "./routes/task/getTaskApi"; +import route90 from "./routes/task/getTaskCategories"; +import route91 from "./routes/task/taskDetails"; +import route92 from "./routes/test/test"; export default async (app: Express) => { app.use("/api/agents/clearMemory", route1); @@ -138,43 +143,48 @@ export default async (app: Express) => { app.use("/api/production/getProductionData", route46); app.use("/api/production/getStoryboardData", route47); app.use("/api/production/saveFlowData", route48); - app.use("/api/production/storyboard/downPreviewImage", route49); - app.use("/api/production/storyboard/getStoryboardData", route50); - app.use("/api/production/storyboard/previewImage", route51); - app.use("/api/production/workbench/confirmSelection", route52); - app.use("/api/production/workbench/delVideo", route53); - app.use("/api/production/workbench/generateVideo", route54); - app.use("/api/production/workbench/getChatLines", route55); - app.use("/api/production/workbench/getVideoModelDetail", route56); - app.use("/api/production/workbench/videoPolling", route57); - app.use("/api/project/addProject", route58); - app.use("/api/project/delProject", route59); - app.use("/api/project/editProject", route60); - app.use("/api/project/getProject", route61); - app.use("/api/script/addScript", route62); - app.use("/api/script/delScript", route63); - app.use("/api/script/exportScript", route64); - app.use("/api/script/getScrptApi", route65); - app.use("/api/script/updateScript", route66); - app.use("/api/scriptAgent/getPlanData", route67); - app.use("/api/scriptAgent/setPlanData", route68); - app.use("/api/setting/agentDeploy/agentSetKey", route69); - app.use("/api/setting/agentDeploy/deployAgentModel", route70); - app.use("/api/setting/agentDeploy/getAgentDeploy", route71); - app.use("/api/setting/dbConfig/clearData", route72); - app.use("/api/setting/fileManagement/openFolder", route73); - app.use("/api/setting/getTextModel", route74); - app.use("/api/setting/loginConfig/getUser", route75); - app.use("/api/setting/loginConfig/updateUserPwd", route76); - app.use("/api/setting/memoryConfig/getMemory", route77); - app.use("/api/setting/memoryConfig/sureMemory", route78); - app.use("/api/setting/vendorConfig/addVendor", route79); - app.use("/api/setting/vendorConfig/deleteVendor", route80); - app.use("/api/setting/vendorConfig/getVendorList", route81); - app.use("/api/setting/vendorConfig/modelTest", route82); - app.use("/api/setting/vendorConfig/updateVendor", route83); - app.use("/api/task/getTaskApi", route84); - app.use("/api/task/getTaskCategories", route85); - app.use("/api/task/taskDetails", route86); - app.use("/api/test/test", route87); + app.use("/api/production/storyboard/previewImage", route49); + app.use("/api/production/workbench/confirmSelection", route50); + app.use("/api/production/workbench/delVideo", route51); + app.use("/api/production/workbench/generateVideo", route52); + app.use("/api/production/workbench/getChatLines", route53); + app.use("/api/production/workbench/getVideoModelDetail", route54); + app.use("/api/production/workbench/videoPolling", route55); + app.use("/api/project/addProject", route56); + app.use("/api/project/delProject", route57); + app.use("/api/project/editProject", route58); + app.use("/api/project/getProject", route59); + app.use("/api/script/addScript", route60); + app.use("/api/script/delScript", route61); + app.use("/api/script/exportScript", route62); + app.use("/api/script/getScrptApi", route63); + app.use("/api/script/updateScript", route64); + app.use("/api/scriptAgent/getPlanData", route65); + app.use("/api/scriptAgent/setPlanData", route66); + app.use("/api/setting/agentDeploy/agentSetKey", route67); + app.use("/api/setting/agentDeploy/deployAgentModel", route68); + app.use("/api/setting/agentDeploy/getAgentDeploy", route69); + app.use("/api/setting/dbConfig/clearData", route70); + app.use("/api/setting/fileManagement/openFolder", route71); + app.use("/api/setting/getTextModel", route72); + app.use("/api/setting/loginConfig/getUser", route73); + app.use("/api/setting/loginConfig/updateUserPwd", route74); + app.use("/api/setting/memoryConfig/getMemory", route75); + app.use("/api/setting/memoryConfig/sureMemory", route76); + app.use("/api/setting/skillManagement/addSkill", route77); + app.use("/api/setting/skillManagement/deleteSkill", route78); + app.use("/api/setting/skillManagement/embeddingSkill", route79); + app.use("/api/setting/skillManagement/generateDescription", route80); + app.use("/api/setting/skillManagement/getSkillList", route81); + app.use("/api/setting/skillManagement/scanSkills", route82); + app.use("/api/setting/skillManagement/updateSkill", route83); + app.use("/api/setting/vendorConfig/addVendor", route84); + app.use("/api/setting/vendorConfig/deleteVendor", route85); + app.use("/api/setting/vendorConfig/getVendorList", route86); + app.use("/api/setting/vendorConfig/modelTest", route87); + app.use("/api/setting/vendorConfig/updateVendor", route88); + app.use("/api/task/getTaskApi", route89); + app.use("/api/task/getTaskCategories", route90); + app.use("/api/task/taskDetails", route91); + app.use("/api/test/test", route92); } diff --git a/src/routes/assetsGenerate/polishAssetsPrompt.ts b/src/routes/assetsGenerate/polishAssetsPrompt.ts index d2ea095..887341e 100644 --- a/src/routes/assetsGenerate/polishAssetsPrompt.ts +++ b/src/routes/assetsGenerate/polishAssetsPrompt.ts @@ -98,7 +98,7 @@ export default router.post( const novelData = (await u.db("o_novel").whereIn("chapterIndex", [1]).select("*")) as NovelChapter[]; const novelText = mergeNovelText(novelData); - const skill = await useSkill("universal-agent"); + const skill = await useSkill("universal_agent.md"); const systemPrompt = `${skill.prompt} diff --git a/src/routes/production/getFlowData.ts b/src/routes/production/getFlowData.ts index 8110f71..8492a9b 100644 --- a/src/routes/production/getFlowData.ts +++ b/src/routes/production/getFlowData.ts @@ -28,6 +28,7 @@ export default router.post( .db("o_assets") .leftJoin("o_image", "o_assets.imageId", "o_image.id") .select("o_assets.*", "o_image.filePath") + // @ts-ignore .where("o_assets.id", "in", assetIds) .whereNull("o_assets.assetsId") .where("o_assets.projectId", projectId); @@ -36,9 +37,9 @@ export default router.post( .leftJoin("o_image", "o_assets.imageId", "o_image.id") .select("o_assets.*", "o_image.filePath") .where("o_assets.projectId", projectId) + // @ts-ignore .where("o_assets.id", "in", assetIds) .whereNotNull("o_assets.assetsId"); - console.log("%c Line:35 🥚 childAssetsData", "background:#f5ce50", childAssetsData); if (!sqlData) { const flowData: FlowData = { diff --git a/src/routes/production/workbench/getChatLines.ts b/src/routes/production/workbench/getChatLines.ts index 98802f7..3aba49a 100644 --- a/src/routes/production/workbench/getChatLines.ts +++ b/src/routes/production/workbench/getChatLines.ts @@ -30,7 +30,7 @@ export default router.post( ); async function getLines(prompt: string) { - const skill = await useSkill("eventExtract-agent"); + const skill = await useSkill("universal_agent.md"); const resText = await u.Ai.Text("universalAgent").invoke({ system: skill.prompt, diff --git a/src/routes/project/addProject.ts b/src/routes/project/addProject.ts index 9a8f597..65f15d9 100644 --- a/src/routes/project/addProject.ts +++ b/src/routes/project/addProject.ts @@ -15,9 +15,11 @@ export default router.post( type: z.string(), artStyle: z.string(), videoRatio: z.string(), + imageModel: z.string(), + videoModel: z.string(), }), async (req, res) => { - const { projectType, name, intro, type, artStyle, videoRatio } = req.body; + const { projectType, name, intro, type, artStyle, videoRatio, imageModel, videoModel } = req.body; await u.db("o_project").insert({ projectType, @@ -27,6 +29,8 @@ export default router.post( artStyle, videoRatio, userId: 1, + imageModel, + videoModel, createTime: Date.now(), }); diff --git a/src/routes/project/editProject.ts b/src/routes/project/editProject.ts index cafcb5e..eb4fc8e 100644 --- a/src/routes/project/editProject.ts +++ b/src/routes/project/editProject.ts @@ -15,9 +15,11 @@ export default router.post( type: z.string(), artStyle: z.string(), videoRatio: z.string(), + imageModel: z.string(), + videoModel: z.string(), }), async (req, res) => { - const { id, name, intro, type, artStyle, videoRatio } = req.body; + const { id, name, intro, type, artStyle, videoRatio, imageModel, videoModel } = req.body; await u.db("o_project").where("id", id).update({ name, @@ -25,6 +27,8 @@ export default router.post( type, artStyle, videoRatio, + imageModel, + videoModel, }); res.status(200).send(success({ message: "新增项目成功" })); diff --git a/src/routes/script/getScrptApi.ts b/src/routes/script/getScrptApi.ts index c9dc893..9b253b8 100644 --- a/src/routes/script/getScrptApi.ts +++ b/src/routes/script/getScrptApi.ts @@ -22,11 +22,8 @@ export default router.post( const assetsData = await u .db("o_assets") .leftJoin("o_scriptAssets", "o_assets.id", "o_scriptAssets.assetId") - .where( - "o_scriptAssets.scriptId", - "in", - data.map((i) => i.id), - ) + // @ts-ignore + .whereIn( "o_scriptAssets.scriptId", data.map((i) => i.id)) .select("o_assets.id", "o_assets.name", "o_scriptAssets.scriptId"); const scriptAssetsMap: Record = {}; assetsData.forEach((i) => { diff --git a/src/routes/setting/skillManagement/addSkill.ts b/src/routes/setting/skillManagement/addSkill.ts new file mode 100644 index 0000000..05802ae --- /dev/null +++ b/src/routes/setting/skillManagement/addSkill.ts @@ -0,0 +1,102 @@ +import express from "express"; +import u from "@/utils"; +import { z } from "zod"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import fs from "fs/promises"; +import path from "path"; +import crypto from "crypto"; + +const router = express.Router(); + +const buildSkillFileName = (name: string) => { + const trimmed = name.trim(); + const fileName = trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`; + const normalized = fileName.replace(/\\/g, "/"); + if (!normalized || normalized.includes("/")) { + throw new Error("技能名称不能包含路径分隔符"); + } + return normalized; +}; + +const buildRelativePath = (type: "main" | "references", fileName: string) => { + return type === "references" ? path.posix.join("references", fileName) : fileName; +}; + +const resolveSkillFilePath = (relativePath: string) => { + const normalizedPath = relativePath.replace(/\\/g, "/"); + if (normalizedPath.startsWith("references/")) { + return path.join(u.getPath("skills"), normalizedPath); + } + return path.join(u.getPath("skills"), normalizedPath); +}; + +const resolveState = (description: string, attributions: string[]) => { + if (!description.trim()) return -1; + if (attributions.length === 0) return -2; + return 1; +}; + +export default router.post( + "/", + validateFields({ + name: z.string().min(1).max(100), + description: z.string().optional(), + content: z.string().optional(), + attributions: z.array(z.string()).optional(), + type: z.enum(["main", "references"]).optional(), + }), + async (req, res) => { + try { + const { name, description, content, attributions, type } = req.body; + const finalType: "main" | "references" = type === "main" ? "main" : "references"; + const finalDescription = description ?? ""; + const finalContent = content ?? ""; + const rawAttributions = Array.isArray(attributions) ? attributions : []; + const finalAttributions = Array.from( + new Set(rawAttributions.filter((item: unknown): item is string => typeof item === "string" && item.trim().length > 0)), + ); + const fileName = buildSkillFileName(name); + const relativePath = buildRelativePath(finalType, fileName); + const skillId = crypto.createHash("md5").update(relativePath).digest("hex"); + const md5 = crypto.createHash("md5").update(finalContent).digest("hex"); + const filePath = resolveSkillFilePath(relativePath); + const now = Date.now(); + + const existed = await u.db("o_skillList").where("id", skillId).first(); + if (existed) { + return res.status(400).send(error("技能已存在,请使用其他名称")); + } + + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, finalContent, "utf-8"); + + await u.db("o_skillList").insert({ + id: skillId, + md5, + path: relativePath, + name: path.basename(fileName, ".md"), + description: finalDescription, + embedding: null, + type: finalType, + createTime: now, + updateTime: now, + state: resolveState(finalDescription, finalAttributions), + }); + + if (finalAttributions.length > 0) { + await u.db("o_skillAttribution").insert( + finalAttributions.map((attribution: string) => ({ + skillId, + attribution, + })), + ); + } + + res.status(200).send(success("新增技能成功")); + } catch (err: any) { + console.log(err); + res.status(400).send(error(err?.message || "新增技能失败")); + } + }, +); diff --git a/src/routes/setting/skillManagement/deleteSkill.ts b/src/routes/setting/skillManagement/deleteSkill.ts new file mode 100644 index 0000000..e798ff7 --- /dev/null +++ b/src/routes/setting/skillManagement/deleteSkill.ts @@ -0,0 +1,49 @@ +import express from "express"; +import u from "@/utils"; +import { z } from "zod"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import fs from "fs/promises"; +import path from "path"; + +const router = express.Router(); + +const resolveSkillFilePath = (type: string, relativePath: string) => { + const normalizedPath = (relativePath || "").replace(/\\/g, "/"); + const isPrefixedReferencePath = normalizedPath.startsWith("references/"); + if (type === "references" && !isPrefixedReferencePath) { + return path.join(u.getPath(["skills", "references"]), normalizedPath); + } + return path.join(u.getPath("skills"), normalizedPath); +}; + +export default router.post( + "/", + validateFields({ + id: z.string().min(1), + }), + async (req, res) => { + try { + const { id } = req.body; + const skill = await u.db("o_skillList").where("id", id).first(); + + if (!skill) { + return res.status(404).send(error("技能不存在")); + } + + const filePath = resolveSkillFilePath(skill.type, skill.path || ""); + await u.db("o_skillList").where("id", id).delete(); + + try { + await fs.unlink(filePath); + } catch { + // 文件不存在时可忽略,数据库记录已删除 + } + + res.status(200).send(success("删除技能成功")); + } catch (err: any) { + console.log(err); + res.status(400).send(error(err?.message || "删除技能失败")); + } + }, +); diff --git a/src/routes/setting/skillManagement/embeddingSkill.ts b/src/routes/setting/skillManagement/embeddingSkill.ts new file mode 100644 index 0000000..7c1e88c --- /dev/null +++ b/src/routes/setting/skillManagement/embeddingSkill.ts @@ -0,0 +1,31 @@ +import express from "express"; +import u from "@/utils"; +import { z } from "zod"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import { getEmbedding } from "@/utils/agent/embedding"; + +const router = express.Router(); + +export default router.post( + "/", + validateFields({ + id: z.string(), + }), + async (req, res) => { + const { id } = req.body; + + const skill = await u.db("o_skillList").where("id", id).first(); + + if (!skill) return res.status(404).send(error("技能不存在")); + if (skill.embedding) return res.status(400).send(error("技能已存在向量,请勿重复生成")); + if (!skill.description) return res.status(400).send(error("技能描述不存在")); + const embedding = await getEmbedding(skill.description); + await u + .db("o_skillList") + .where("id", id) + .update({ embedding: JSON.stringify(embedding) }); + + res.status(200).send(success("技能向量生成成功")); + }, +); diff --git a/src/routes/setting/skillManagement/generateDescription.ts b/src/routes/setting/skillManagement/generateDescription.ts new file mode 100644 index 0000000..98ac70d --- /dev/null +++ b/src/routes/setting/skillManagement/generateDescription.ts @@ -0,0 +1,35 @@ +import express from "express"; +import u from "@/utils"; +import { z } from "zod"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import fs from "fs/promises"; +import path from "path"; + +const router = express.Router(); + +const resolveSkillFilePath = (type: string, relativePath: string) => { + const normalizedPath = (relativePath || "").replace(/\\/g, "/"); + const isPrefixedReferencePath = normalizedPath.startsWith("references/"); + if (type === "references" && !isPrefixedReferencePath) { + return path.join(u.getPath(["skills", "references"]), normalizedPath); + } + return path.join(u.getPath("skills"), normalizedPath); +}; + +export default router.post( + "/", + validateFields({ + content: z.string(), + }), + async (req, res) => { + const { content } = req.body; + const result = await u.Ai.Text("universalAgent").invoke({ + system: + "你是一个文档摘要助手。根据给定的文档内容生成一句简洁的中文描述(不超过100字),概括文档的核心主题和用途。只输出描述文本,不要添加任何前缀或格式。", + messages: [{ role: "user", content: `内容:\n${content}` }], + }); + const description = result.text.trim(); + res.status(200).send(success(description)); + }, +); diff --git a/src/routes/setting/skillManagement/getSkillList.ts b/src/routes/setting/skillManagement/getSkillList.ts new file mode 100644 index 0000000..4dd0cf0 --- /dev/null +++ b/src/routes/setting/skillManagement/getSkillList.ts @@ -0,0 +1,92 @@ +import express from "express"; +import u from "@/utils"; +import { z } from "zod"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import fs from "fs"; +import path from "path"; + +const router = express.Router(); + +export default router.post( + "/", + validateFields({ + page: z.number().int().min(1).default(1), + limit: z.number().int().min(1).max(100).default(20), + search: z.string().optional().default(""), + type: z.enum(["main", "references"]).optional(), + attributions: z.array(z.string()).optional(), + }), + async (req, res) => { + const { page, limit, search, type, attributions } = req.body; + const offset = (page - 1) * limit; + + let query = u.db("o_skillList"); + let countQuery = u.db("o_skillList"); + + // 搜索条件 + if (search) { + const searchPattern = `%${search}%`; + const whereBuilder = (builder: any) => { + builder.where("name", "like", searchPattern).orWhere("path", "like", searchPattern).orWhere("description", "like", searchPattern); + }; + query = query.where(whereBuilder); + countQuery = countQuery.where(whereBuilder); + } + + // 查询总数 + const [{ count }]: any = await countQuery.count("* as count"); + + // 查询列表 + if (type) { + query = query.where("type", type); + countQuery = countQuery.where("type", type); + } + if (attributions && attributions.length > 0) { + query = query.whereIn("id", function () { + this.select("skillId").from("o_skillAttribution").whereIn("attribution", attributions); + }); + countQuery = countQuery.whereIn("id", function () { + this.select("skillId").from("o_skillAttribution").whereIn("attribution", attributions); + }); + } + + const list = await query.select("*").orderBy("updateTime", "desc").orderBy("type", "desc").limit(limit).offset(offset); + + // 查询每个技能的归属 + const skillIds = list.map((item: any) => item.id); + const attributionsList = await u.db("o_skillAttribution").whereIn("skillId", skillIds).select("skillId", "attribution"); + + // 将归属信息合并到列表中 + const attributionMap = new Map(); + for (const attr of attributionsList) { + if (!attributionMap.has(attr.skillId!)) { + attributionMap.set(attr.skillId!, []); + } + attributionMap.get(attr.skillId!)!.push(attr.attribution!); + } + + const listWithAttributions = list.map((item: any) => { + const normalizedPath = (item.path || "").replace(/\\/g, "/"); + const isPrefixedReferencePath = normalizedPath.startsWith("references/"); + const skillFilePath = + item.type === "references" && !isPrefixedReferencePath + ? path.join(u.getPath(["skills", "references"]), item.path!) + : path.join(u.getPath("skills"), item.path!); + + return { + ...item, + attributions: attributionMap.get(item.id) || [], + content: fs.readFileSync(skillFilePath, "utf-8"), + embedding: item.embedding ? true : false, + }; + }); + + res.status(200).send( + success({ + list: listWithAttributions, + total: Number(count), + }), + ); + }, +); diff --git a/src/routes/setting/skillManagement/scanSkills.ts b/src/routes/setting/skillManagement/scanSkills.ts new file mode 100644 index 0000000..8208888 --- /dev/null +++ b/src/routes/setting/skillManagement/scanSkills.ts @@ -0,0 +1,121 @@ +import express from "express"; +import u from "@/utils"; +import path from "path"; +import fs from "fs/promises"; +import crypto from "crypto"; +import { success } from "@/lib/responseFormat"; +import fg from "fast-glob"; +import getPath from "@/utils/getPath"; + +const router = express.Router(); + +export default router.post("/", async (req, res) => { + const skillsRoot = getPath(["skills"]); + const referencesRoot = path.join(skillsRoot, "references"); + + const [mainEntries, referenceEntries] = await Promise.all([ + fg("*.md", { + cwd: skillsRoot.replace(/\\/g, "/"), + onlyFiles: true, + }), + fg("**/*.md", { + cwd: referencesRoot.replace(/\\/g, "/"), + onlyFiles: true, + }), + ]); + + const scanItems = [ + ...mainEntries.map((entry) => ({ + entry, + relativePath: entry, + fullPath: path.join(skillsRoot, entry), + type: "main", + })), + ...referenceEntries.map((entry) => ({ + entry, + relativePath: path.posix.join("references", entry.replace(/\\/g, "/")), + fullPath: path.join(referencesRoot, entry), + type: "references", + })), + ]; + + const now = Date.now(); + let insertedCount = 0; + let updatedCount = 0; + let removedCount = 0; + + const scannedIds = new Set(); + const existingRows = await u.db("o_skillList").whereIn("type", ["main", "references"]).select("id", "md5", "type", "path"); + + for (const item of scanItems) { + const id = crypto.createHash("md5").update(item.relativePath).digest("hex"); + const name = path.basename(item.entry, ".md"); + const content = await fs.readFile(item.fullPath, "utf-8"); + const md5 = crypto.createHash("md5").update(content).digest("hex"); + const existing = existingRows.find((row: any) => row.id === id); + + scannedIds.add(id); + + if (!existing) { + await u.db("o_skillList").insert({ + id, + path: item.relativePath, + name, + description: "", + embedding: null, + type: item.type, + createTime: now, + updateTime: now, + md5, + state: -1, + }); + insertedCount++; + continue; + } + + if (existing.md5 !== md5 || existing.path !== item.relativePath || existing.type !== item.type) { + await u.db("o_skillList").where("id", id).update({ + path: item.relativePath, + name, + md5, + type: item.type, + updateTime: now, + state: -3, + }); + updatedCount++; + } + } + + const removedIds = existingRows.map((row: any) => row.id).filter((id: string) => !scannedIds.has(id)); + if (removedIds.length > 0) { + await u.db("o_skillList").whereIn("id", removedIds).delete(); + removedCount = removedIds.length; + } + + const [{ noDescriptionSkillCount }]: any = await u + .db("o_skillList") + .where("type", "references") + .andWhere((builder: any) => { + builder.whereNull("description").orWhere("description", ""); + }) + .count({ noDescriptionSkillCount: "*" }); + + const [{ noAttributionSkillCount }]: any = await u + .db("o_skillList as sl") + .leftJoin("o_skillAttribution as sa", "sl.id", "sa.skillId") + .where("sl.type", "references") + .whereNull("sa.skillId") + .countDistinct({ noAttributionSkillCount: "sl.id" }); + + res.status(200).send( + success({ + message: "更新技能文档成功", + insertedCount, + updatedCount, + removedCount, + totalFiles: scanItems.length, + noDescriptionSkillCount: Number(noDescriptionSkillCount), + noAttributionSkillCount: Number(noAttributionSkillCount), + }), + ); +}); diff --git a/src/routes/setting/skillManagement/updateSkill.ts b/src/routes/setting/skillManagement/updateSkill.ts new file mode 100644 index 0000000..e389b24 --- /dev/null +++ b/src/routes/setting/skillManagement/updateSkill.ts @@ -0,0 +1,118 @@ +import express from "express"; +import u from "@/utils"; +import { z } from "zod"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import fs from "fs/promises"; +import path from "path"; +import crypto from "crypto"; + +const router = express.Router(); + +const buildSkillFileName = (name: string) => { + const trimmed = name.trim(); + const fileName = trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`; + const normalized = fileName.replace(/\\/g, "/"); + if (!normalized || normalized.includes("/")) { + throw new Error("技能名称不能包含路径分隔符"); + } + return normalized; +}; + +const buildRelativePath = (type: string, fileName: string) => { + return type === "references" ? path.posix.join("references", fileName) : fileName; +}; + +const resolveSkillFilePath = (relativePath: string) => { + return path.join(u.getPath("skills"), relativePath.replace(/\\/g, "/")); +}; + +const resolveState = (description: string, attributions: string[]) => { + if (!description.trim()) return -1; + if (attributions.length === 0) return -2; + return 1; +}; + +export default router.post( + "/", + validateFields({ + id: z.string().min(1), + name: z.string().min(1).max(100), + description: z.string().optional(), + content: z.string().optional(), + attributions: z.array(z.string()).optional(), + }), + async (req, res) => { + try { + const { id, name, description, content, attributions } = req.body; + const current = await u.db("o_skillList").where("id", id).first(); + + if (!current) { + return res.status(404).send(error("技能不存在")); + } + + const finalDescription = description ?? ""; + const finalContent = content ?? ""; + const rawAttributions = Array.isArray(attributions) ? attributions : []; + const finalAttributions = Array.from( + new Set(rawAttributions.filter((item: unknown): item is string => typeof item === "string" && item.trim().length > 0)), + ); + const fileName = buildSkillFileName(name); + const relativePath = buildRelativePath(current.type, fileName); + const nextId = crypto.createHash("md5").update(relativePath).digest("hex"); + const md5 = crypto.createHash("md5").update(finalContent).digest("hex"); + const oldFilePath = resolveSkillFilePath(current.path); + const newFilePath = resolveSkillFilePath(relativePath); + const now = Date.now(); + + if (nextId !== id) { + const conflict = await u.db("o_skillList").where("id", nextId).first(); + if (conflict) { + return res.status(400).send(error("技能名称冲突,请使用其他名称")); + } + } + + await fs.mkdir(path.dirname(newFilePath), { recursive: true }); + if (oldFilePath !== newFilePath) { + try { + await fs.rename(oldFilePath, newFilePath); + } catch { + // 文件不存在时直接按新路径写入即可 + } + } + await fs.writeFile(newFilePath, finalContent, "utf-8"); + + if (nextId !== id) { + await u.db("o_skillAttribution").where("skillId", id).update({ skillId: nextId }); + } + + await u + .db("o_skillList") + .where("id", id) + .update({ + id: nextId, + path: relativePath, + name: path.basename(fileName, ".md"), + description: finalDescription, + md5, + updateTime: now, + state: resolveState(finalDescription, finalAttributions), + }); + + await u.db("o_skillAttribution").where("skillId", nextId).delete(); + if (finalAttributions.length > 0) { + await u.db("o_skillAttribution").insert( + finalAttributions.map((attribution: string) => ({ + skillId: nextId, + attribution, + })), + ); + } + + res.status(200).send(success("更新技能成功")); + } catch (err: any) { + console.log(err); + res.status(400).send(error(err?.message || "更新技能失败")); + } + }, +); diff --git a/src/routes/test/test.ts b/src/routes/test/test.ts index 91adc64..772634b 100644 --- a/src/routes/test/test.ts +++ b/src/routes/test/test.ts @@ -1,4 +1,16 @@ import express from "express"; const router = express.Router(); +import u from "@/utils"; +import fs from "fs"; +import { useSkill } from "@/utils/agent/skillsTools"; -export default router.get("/", async (req, res) => {}); +export default router.get("/", async (req, res) => { + const skill = await useSkill("universal_agent.md"); + const result = await u.Ai.Text("universalAgent").invoke({ + system: "请直接调用activate_skill工具激活技能" + skill.prompt, + messages: [{ role: "user", content: `如何烹饪龙肉` }], + tools: skill.tools, + }); + + res.send(result.text); +}); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 85e26c3..285100a 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,25 +1,6 @@ -// @db-hash 71b2e55243e59382321a140a8d9a64ff +// @db-hash a4883a3df3fda68054d467ee6dd26523 //该文件由脚本自动生成,请勿手动修改 -export interface _o_storyboard_old_20260325 { - 'camera'?: string | null; - 'createTime'?: number | null; - 'description'?: string | null; - 'duration'?: string | null; - 'filePath'?: string | null; - 'frameMode'?: string | null; - 'id'?: number; - 'lines'?: string | null; - 'mode'?: string | null; - 'model'?: string | null; - 'prompt'?: string | null; - 'reason'?: string | null; - 'resolution'?: string | null; - 'scriptId'?: number | null; - 'sound'?: string | null; - 'state'?: string | null; - 'title'?: string | null; -} export interface memories { 'content': string; 'createTime': number; @@ -128,6 +109,7 @@ export interface o_project { 'createTime'?: number | null; 'id'?: number | null; 'intro'?: string | null; + 'model'?: string | null; 'name'?: string | null; 'projectType'?: string | null; 'type'?: string | null; @@ -149,6 +131,22 @@ export interface o_setting { 'key'?: string | null; 'value'?: string | null; } +export interface o_skillAttribution { + 'attribution'?: string; + 'skillId'?: string; +} +export interface o_skillList { + 'createTime': number; + 'description': string; + 'embedding'?: string | null; + 'id'?: string; + 'md5': string; + 'name': string; + 'path': string; + 'state': number; + 'type': string; + 'updateTime': number; +} export interface o_storyboard { 'camera'?: string | null; 'createTime'?: number | null; @@ -221,7 +219,6 @@ export interface o_videoConfig { } export interface DB { - "_o_storyboard_old_20260325": _o_storyboard_old_20260325; "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData; @@ -239,6 +236,8 @@ export interface DB { "o_script": o_script; "o_scriptAssets": o_scriptAssets; "o_setting": o_setting; + "o_skillAttribution": o_skillAttribution; + "o_skillList": o_skillList; "o_storyboard": o_storyboard; "o_tasks": o_tasks; "o_user": o_user; diff --git a/src/utils/agent/skillsTools.ts b/src/utils/agent/skillsTools.ts index 21077ea..16bc5ba 100644 --- a/src/utils/agent/skillsTools.ts +++ b/src/utils/agent/skillsTools.ts @@ -3,7 +3,9 @@ import { z } from "zod"; import path from "path"; import fs from "fs/promises"; import isPathInside from "is-path-inside"; +import u from "@/utils"; import getPath from "@/utils/getPath"; +import { getEmbedding, cosineSimilarity } from "./embedding"; interface SkillRecord { name: string; @@ -54,118 +56,127 @@ function parseFrontmatter(content: string): { name: string; description: string return { name: result.name, description: result.description }; } -function stripFrontmatter(content: string): string { - return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim(); -} +type SkillAttribution = + | "production_agent_decision.md" + | "production_agent_execution.md" + | "production_agent_supervision.md" + | "script_agent_decision.md" + | "script_agent_execution.md" + | "script_agent_supervision.md" + | "universal_agent.md"; -// ==================== 资源枚举 ==================== +export async function useSkill(mainSkillName: SkillAttribution) { + const skillsRoot = getPath("skills"); + const targetSkill = path.join(skillsRoot, mainSkillName); + + if (!isPathInside(targetSkill, skillsRoot)) throw new Error("技能名称无效:检测到路径穿越"); -async function listResources(dir: string, base = ""): Promise { - let entries; try { - entries = await fs.readdir(dir, { withFileTypes: true }); + const content = await fs.readFile(targetSkill, "utf-8"); + const skill = { ...parseFrontmatter(content), location: targetSkill, baseDir: skillsRoot }; + return { prompt: buildPrompt(skill), tools: createSkillTools(skill, mainSkillName) }; } catch { - return []; - } - - const files: string[] = []; - for (const entry of entries) { - const rel = base ? `${base}/${entry.name}` : entry.name; - if (entry.isDirectory()) { - files.push(...(await listResources(path.join(dir, entry.name), rel))); - } else if (entry.name !== "SKILL.md") { - files.push(rel); - } - } - return files; -} - -// ==================== 读取单个技能 ==================== - -async function readSkillFromDir(skillDir: string): Promise { - const location = path.join(skillDir, "SKILL.md"); - let content: string; - try { - content = await fs.readFile(location, "utf-8"); - } catch { - return null; - } - try { - const meta = parseFrontmatter(content); - console.log(`[Skill] ✅ 发现技能:${meta.name} — ${meta.description}`); - return { ...meta, location, baseDir: skillDir }; - } catch (e) { - console.log(`[Skill] ⚠️ 解析失败 "${skillDir}":${(e as Error).message}`); - return null; + throw new Error(`技能文件不存在:${mainSkillName}`); } } -// ==================== 构建技能目录 ==================== - -function buildCatalog(skills: SkillRecord[]): string { - const entries = skills.map((s) => ` \n ${s.name}\n ${s.description}\n `).join("\n"); - +function buildPrompt(skill: SkillRecord): string { return `## Skills 以下技能提供了专业任务的专用指令。 当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。 加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。 -${entries} + + ${skill.name} + ${skill.description} + `; } -// ==================== 激活 + 执行工具 ==================== - -function createSkillTools(skills: SkillRecord[]) { +function createSkillTools(skill: SkillRecord, mainSkillName: string) { const activated = new Set(); - const validNames = skills.map((s) => s.name); - return { activate_skill: tool({ - description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${validNames.join(", ")}`, + description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skill.name}`, inputSchema: z.object({ - name: z.enum(validNames as [string, ...string[]]).describe("要激活的技能名称"), + name: z.enum([skill.name] as [string, ...string[]]).describe("要激活的技能名称"), }), execute: async ({ name }) => { - const skill = skills.find((s) => s.name === name); - if (!skill) { - console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`); - return { error: `Skill '${name}' not found` }; - } if (activated.has(name)) { - console.log(`[Skill] ℹ️ 技能 "${name}" 已在当前会话中激活,跳过重复注入`); - return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` }; + console.log(`[Skill] ℹ️ 技能 "${name}" 已激活,跳过重复注入`); + return { alreadyActive: true, message: `技能 "${name}" 已激活,无需重复加载` }; } - - let content: string; + let raw: string; try { - content = await fs.readFile(skill.location, "utf-8"); + raw = await fs.readFile(skill.location, "utf-8"); } catch { console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`); - return { error: `Failed to read SKILL.md for '${name}'` }; + return { error: `无法读取技能文件:${name}` }; } + const body = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim(); + + const resources = await u + .db("o_skillList") + .distinct("o_skillList.path") + .innerJoin("o_skillAttribution", "o_skillList.id", "o_skillAttribution.skillId") + .where("o_skillList.state", 1) + .andWhere("o_skillAttribution.attribution", mainSkillName); + + console.log("%c Line:120 🌮 resources", "background:#b03734", resources); - const body = stripFrontmatter(content); - const resources = await listResources(skill.baseDir); activated.add(name); - console.log(`[Skill] 📖 已激活技能:${skill.name}(${body.length} 字符,${resources.length} 个资源文件)`); - - const resourcesXml = - resources.length > 0 ? `\n\n${resources.map((f) => ` ${f}`).join("\n")}\n` : ""; - - return { - content: ` -${body} - -Skill directory: ${skill.baseDir} -相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。 -${resourcesXml} -`, - }; + console.log(`[Skill] 📖 已激活:${name}(${body.length} 字符,${resources.length} 资源)`); + let content = ""; + content = `\n`; + content += body + "\n\n"; + content += `Skill directory: ${skill.baseDir}\n`; + content += "相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。\n"; + if (resources.length > 0) { + content += "\n\n"; + for (const { path } of resources) { + content += ` ${path}\n`; + } + content += "\n"; + } + content += "\n\n"; + content += "- read_skill_file:读取上方 skill_resources 中列出的资源文件。\n"; + content += "- discover_skill_docs:当上方资源不足以完成任务时,使用关键词检索更多相关文档。传入与当前任务相关的关键词列表即可获取推荐。\n"; + content += "\n"; + content += ""; + console.log("%c Line:133 🍊 content", "background:#2eafb0", content); + return { content }; }, }), + discover_skill_docs: tool({ + description: "根据关键词主动发现全部技能文档(MD),返回相关度排序的推荐列表。适用于技能指令中未明确指定资源文件但需要补充信息的场景。", + inputSchema: z.object({ + keywords: z.array(z.string().max(100)).min(1).max(20).describe("用于检索技能文档的关键词列表"), + topK: z.number().int().min(1).max(20).default(5).describe("返回推荐文档数量"), + }), + execute: async ({ keywords, topK }) => { + const queryText = keywords.join(" "); + const queryVec = await getEmbedding(queryText); + const activeRows = await u.db("o_skillList").where("state", 1).whereNotNull("embedding").select(); + const scored = activeRows + .map((row) => { + const emb = JSON.parse(row.embedding!) as number[]; + return { + name: row.name, + filePath: row.path, + type: row.type, + description: row.description, + score: cosineSimilarity(queryVec, emb), + }; + }) + .sort((a, b) => b.score - a.score) + .slice(0, topK); + + console.log(`[Skill] ✅ discover_skill_docs 返回 ${scored.length} 条推荐`); + return { recommendations: scored }; + }, + }), read_skill_file: tool({ description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。", inputSchema: z.object({ @@ -173,18 +184,11 @@ ${resourcesXml} filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"), }), execute: async ({ skillName, filePath: relPath }) => { - const skill = skills.find((s) => s.name === skillName); - if (!skill) { - console.log(`[Skill] ❌ 读取失败:未找到技能 "${skillName}"`); - return { error: `Skill '${skillName}' not found` }; - } - const fullPath = path.resolve(path.join(skill.baseDir, relPath)); if (!isPathInside(fullPath, skill.baseDir)) { console.log(`[Skill] 🚫 路径越界已拦截:"${relPath}" 超出技能目录范围`); return { error: "Access denied: path is outside skill directory" }; } - try { const fileContent = await fs.readFile(fullPath, "utf-8"); console.log(`[Skill] 📄 已读取文件:${skillName}/${relPath}(${fileContent.length} 字符)`); @@ -197,31 +201,3 @@ ${resourcesXml} }), }; } - -// ==================== 对外接口 ==================== - -export async function useSkill(...segments: string[]) { - if (segments.length === 0) return { prompt: "", tools: {} }; - - const skills = new Map(); - - const primary = await readSkillFromDir(path.join(getPath("skills"), ...segments)); - if (primary) skills.set(primary.name, primary); - - const publicDir = path.join(getPath("skills"), "public"); - try { - const entries = await fs.readdir(publicDir, { withFileTypes: true }); - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const skill = await readSkillFromDir(path.join(publicDir, entry.name)); - if (skill && !skills.has(skill.name)) skills.set(skill.name, skill); - } - } catch { - /* public dir not found */ - } - - if (skills.size === 0) return { prompt: "", tools: {} }; - - const allSkills = [...skills.values()]; - return { prompt: buildCatalog(allSkills), tools: createSkillTools(allSkills) }; -} diff --git a/src/utils/cleanNovel.ts b/src/utils/cleanNovel.ts index 1d37939..9e3d4f2 100644 --- a/src/utils/cleanNovel.ts +++ b/src/utils/cleanNovel.ts @@ -29,7 +29,7 @@ class CleanNovel { const novel = allChapters[gi]; let resData; try { - const skill = await useSkill("universal-agent"); + const skill = await useSkill("universal_agent.md"); resData = await intansce.invoke({ system: skill.prompt,