From 9eea54631e871c0ac941de302561a9f598c9ba91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=B8=85?= <2944435683> Date: Thu, 26 Mar 2026 23:28:15 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=B5=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router.ts | 20 +++++++++++--------- src/routes/task/getProject.ts | 10 ++++++++++ src/routes/task/getTaskApi.ts | 9 ++++++++- src/routes/task/getTaskCategories.ts | 17 +++++------------ src/types/database.d.ts | 24 +++--------------------- src/utils/ai.ts | 2 -- 6 files changed, 37 insertions(+), 45 deletions(-) create mode 100644 src/routes/task/getProject.ts diff --git a/src/router.ts b/src/router.ts index 72185e4..f0ea325 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash 8097a5206252be753261d3f059243260 +// @routes-hash 557dfd43a824a4bd4170d0e2c9a6b45c import { Express } from "express"; import route1 from "./routes/agents/clearMemory"; @@ -94,10 +94,11 @@ import route90 from "./routes/setting/vendorConfig/deleteVendor"; import route91 from "./routes/setting/vendorConfig/getVendorList"; import route92 from "./routes/setting/vendorConfig/modelTest"; import route93 from "./routes/setting/vendorConfig/updateVendor"; -import route94 from "./routes/task/getTaskApi"; -import route95 from "./routes/task/getTaskCategories"; -import route96 from "./routes/task/taskDetails"; -import route97 from "./routes/test/test"; +import route94 from "./routes/task/getProject"; +import route95 from "./routes/task/getTaskApi"; +import route96 from "./routes/task/getTaskCategories"; +import route97 from "./routes/task/taskDetails"; +import route98 from "./routes/test/test"; export default async (app: Express) => { app.use("/api/agents/clearMemory", route1); @@ -193,8 +194,9 @@ export default async (app: Express) => { app.use("/api/setting/vendorConfig/getVendorList", route91); app.use("/api/setting/vendorConfig/modelTest", route92); app.use("/api/setting/vendorConfig/updateVendor", route93); - app.use("/api/task/getTaskApi", route94); - app.use("/api/task/getTaskCategories", route95); - app.use("/api/task/taskDetails", route96); - app.use("/api/test/test", route97); + app.use("/api/task/getProject", route94); + app.use("/api/task/getTaskApi", route95); + app.use("/api/task/getTaskCategories", route96); + app.use("/api/task/taskDetails", route97); + app.use("/api/test/test", route98); } diff --git a/src/routes/task/getProject.ts b/src/routes/task/getProject.ts new file mode 100644 index 0000000..ca73fe6 --- /dev/null +++ b/src/routes/task/getProject.ts @@ -0,0 +1,10 @@ +import express from "express"; +import u from "@/utils"; +import { success } from "@/lib/responseFormat"; +const router = express.Router(); + +export default router.post("/", async (req, res) => { + const list = await u.db("o_project").select("id", "name").groupBy("name"); + const data = list.filter((item) => item.name); + res.status(200).send(success(data)); +}); diff --git a/src/routes/task/getTaskApi.ts b/src/routes/task/getTaskApi.ts index 9e7c769..92c5974 100644 --- a/src/routes/task/getTaskApi.ts +++ b/src/routes/task/getTaskApi.ts @@ -9,11 +9,12 @@ export default router.post( validateFields({ state: z.string().optional().nullable(), taskClass: z.string().optional().nullable(), + projectId: z.number().optional().nullable(), page: z.number(), limit: z.number(), }), async (req, res) => { - const { taskClass, state, page = 1, limit = 10 }: any = req.body; + const { taskClass, state, projectId, page = 1, limit = 10 }: any = req.body; const offset = (page - 1) * limit; const data = await u .db("o_tasks") @@ -25,6 +26,9 @@ export default router.post( if (state) { qb.andWhere("o_tasks.state", state); } + if (projectId) { + qb.andWhere("o_tasks.projectId", projectId); + } }) .select("o_tasks.*", "o_project.* ") .offset(offset) @@ -36,6 +40,9 @@ export default router.post( if (taskClass) { qb.andWhere("o_tasks.taskClass", taskClass); } + if (projectId) { + qb.andWhere("o_tasks.projectId", projectId); + } if (state) { qb.andWhere("o_tasks.state", state); } diff --git a/src/routes/task/getTaskCategories.ts b/src/routes/task/getTaskCategories.ts index cb60760..f608cba 100644 --- a/src/routes/task/getTaskCategories.ts +++ b/src/routes/task/getTaskCategories.ts @@ -1,17 +1,10 @@ import express from "express"; import u from "@/utils"; import { success } from "@/lib/responseFormat"; -import { validateFields } from "@/middleware/middleware"; -import { number, z } from "zod"; const router = express.Router(); -export default router.post( - "/", - validateFields({ - projectId: z.number(), - }), - async (req, res) => { - const data = await u.db("o_tasks").where("projectId", req.body.projectId).select("taskClass").groupBy("taskClass"); - res.status(200).send(success(data)); - }, -); +export default router.post("/", async (req, res) => { + const list = await u.db("o_tasks").select("taskClass").groupBy("taskClass"); + const data = list.filter((item) => item.taskClass); + res.status(200).send(success(data)); +}); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 3e58c97..70b079c 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,25 +1,6 @@ -// @db-hash ce28b6d566911952421c2661e14bfde5 +// @db-hash d807205fbb27fc5ddb04cae060fb4430 //该文件由脚本自动生成,请勿手动修改 -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; @@ -127,11 +108,13 @@ export interface o_project { 'artStyle'?: string | null; 'createTime'?: number | null; 'id'?: number | null; + 'imageModel'?: string | null; 'intro'?: string | null; 'name'?: string | null; 'projectType'?: string | null; 'type'?: string | null; 'userId'?: number | null; + 'videoModel'?: string | null; 'videoRatio'?: string | null; } export interface o_script { @@ -237,7 +220,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; diff --git a/src/utils/ai.ts b/src/utils/ai.ts index 9ab1a75..a06d83b 100644 --- a/src/utils/ai.ts +++ b/src/utils/ai.ts @@ -137,8 +137,6 @@ class AiVideo { async run(input: VideoConfig) { return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => { const fn = await getVendorTemplateFn("videoRequest", modelName); - - console.log("%c Line:142 🎂 input", "background:#42b983", input); this.result = await fn(input); if (this.result.startsWith("http")) this.result = await urlToBase64(this.result); return this; From 9b3806e989c6b6fcde218828a342c2a54db2fe30 Mon Sep 17 00:00:00 2001 From: zhishi <1951671751@qq.com> Date: Fri, 27 Mar 2026 00:11:22 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=20skill=20=20=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=B0=8F=E8=AF=B4tool=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=89=A7=E6=9C=ACagent=E6=95=85=E4=BA=8B?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E4=B8=A2=E5=A4=B1=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E6=8F=90=E5=8F=96=E5=B9=B6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../references/script_assets_extract.md | 95 +++++++++++++ data/skills/universal_agent.md | 7 + src/agents/scriptAgent/tools.ts | 1 + src/router.ts | 128 +++++++++--------- src/routes/script/extractAssets.ts | 82 +++++++++-- src/routes/scriptAgent/getPlanData.ts | 4 +- src/routes/scriptAgent/setPlanData.ts | 2 +- src/types/database.d.ts | 16 ++- src/utils/cleanNovel.ts | 86 +++++++----- src/utils/vm.ts | 1 - 10 files changed, 310 insertions(+), 112 deletions(-) create mode 100644 data/skills/references/script_assets_extract.md diff --git a/data/skills/references/script_assets_extract.md b/data/skills/references/script_assets_extract.md new file mode 100644 index 0000000..43847bf --- /dev/null +++ b/data/skills/references/script_assets_extract.md @@ -0,0 +1,95 @@ +--- +name: universal_agent +description: 专注于从剧本内容中提取所使用的资产(角色、场景、道具)并生成结构化资产列表的助手。 +--- + +# Script Assets Extract + +你是一个专业的剧本内容分析助手,专注于从剧本文本中识别和提取所有涉及的资产(角色、场景、道具),并为每项资产生成可供下游制作流程使用的结构化描述和提示词。 + +## 何时使用 + +用户提供剧本内容,你需要逐段阅读并提取其中涉及的所有资产(人物角色、场景地点、道具物件),输出为结构化的资产列表。产出的资产描述将用于后续 AI 图片生成和制作流程。 + +## 与系统的对应关系 + +- 资产类型: + - `role` — 角色(对应 `o_assets.type = "role"`) + - `scene` — 场景(对应 `o_assets.type = "scene"`) + - `tool` — 道具(对应 `o_assets.type = "tool"`) + - `clip` — 素材片段(对应 `o_assets.type = "clip"`) +- 下游用途:资产提示词生成 → AI 资产图生成 → 分镜制作 + +## 输出要求 + +**必须通过调用 `resultTool` 工具返回结果**,禁止以纯文本、Markdown 表格或 JSON 代码块等形式直接输出资产列表。 +`resultTool` 的 schema 会对字段类型和枚举值做强校验,调用时请严格按照下方字段定义填写,确保数据结构正确、字段完整、类型匹配。 + +每个资产对象包含以下字段: + +| 字段 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| `name` | string | 是 | 资产名称,使用剧本中的原始称呼,不做其他多余描述 | +| `desc` | string | 是 | 资产描述,30-80 字的视觉化描述 | +| `prompt` | string | 是 | 生成提示词,英文,用于 AI 图片生成 | +| `type` | enum | 是 | 资产类型:`role` / `scene` / `tool` / `clip` | + +## 提取规则 + +### 角色(role) + +- 提取剧本中出现的所有有名字的角色 +- `desc`:包含外貌特征、服饰风格、体态气质等视觉要素 +- `prompt`:英文提示词,描述角色的外观特征,适用于 AI 角色图生成 +- 同一角色有多个称呼时,取最常用的作为 `name` +- 无名龙套(如"路人甲"、"士兵")可跳过,除非其造型对剧情有重要视觉意义 + +### 场景(scene) + +- 提取剧本中出现的所有场景/地点 +- `desc`:包含空间结构、光照氛围、关键陈设、色调基调等视觉要素 +- `prompt`:英文提示词,描述场景的整体视觉风格,适用于 AI 场景图生成 +- 同一场景的不同状态(如白天/夜晚)不重复提取,在 `desc` 中注明即可 + +### 道具(tool) + +- 提取剧本中出现的重要道具/物品 +- `desc`:包含外观形状、颜色材质、尺寸参考、特殊效果等视觉要素 +- `prompt`:英文提示词,描述道具的外观细节,适用于 AI 道具图生成 +- 仅提取有独立视觉意义或剧情功能的道具,通用物品可跳过 + +### 素材片段(clip) + +- 提取剧本中需要的特殊素材片段(如特效、转场、特殊画面等) +- `desc`:描述素材的视觉效果和用途 +- `prompt`:英文提示词,描述素材的视觉特征 + +## 提示词(prompt)生成规范 + +- 采用逗号分隔的关键词/短语格式 +- 优先描述**视觉特征**,避免抽象概念 +- 包含风格关键词(如 anime style, manga style 等,根据项目风格决定) +- 角色 prompt 示例:`a young man, sharp eyebrows, black hair, pale skin, wearing a gray Taoist robe, slender build, cold expression` +- 场景 prompt 示例:`dark cave interior, glowing crystals on walls, misty atmosphere, dim blue lighting, stone altar in center` +- 道具 prompt 示例:`ancient jade pendant, oval shape, translucent green, carved dragon pattern, glowing faintly` + +## 提取流程 + +1. 通读剧本全文,识别所有出现的角色、场景、道具 +2. 对每个资产生成结构化的 `name`、`desc`、`prompt`、`type` +3. 去重:同一资产不重复提取 +4. **必须通过调用 `resultTool` 工具输出完整资产列表**,不要分多次调用,一次性将所有资产放入 `assetsList` 数组中提交 + +## 提取原则 + +1. **忠于剧本**:所有提取基于剧本中的实际内容,不臆造未出现的资产 +2. **视觉优先**:描述和提示词聚焦视觉特征,便于 AI 图片生成 +3. **精简实用**:只提取对制作有实际意义的资产,避免过度提取 +4. **分类准确**:严格按照 role/scene/tool/clip 分类,不混淆 +5. **提示词质量**:英文提示词应具体、可执行,能直接用于 AI 图片生成 + +## 注意事项 + +- 资产列表中**不要包含剧本内容本身**,仅提取所使用到的资产 +- 角色的随身物品如果有独立剧情功能,应单独作为道具提取 +- 场景中的固定陈设不需要单独提取为道具,除非该物件有独立剧情作用 diff --git a/data/skills/universal_agent.md b/data/skills/universal_agent.md index 65efb41..3a571c6 100644 --- a/data/skills/universal_agent.md +++ b/data/skills/universal_agent.md @@ -44,6 +44,13 @@ description: 通用文本分析与内容提取 Agent,支持小说事件提取 - **资产类型**:`tool`(对应 `o_assets.type = "tool"`) - **输出**:结构化道具资产表(道具名称、类别、外观描述、尺寸参考、材质质感、功能/用途、首次出场、关联角色、状态变体)+ 高频道具排名 +### 6. 剧本资产提取(script_assets_extract) + +- **触发条件**:用户提供剧本内容,要求从剧本中提取所使用的资产(角色、场景、道具、素材片段) +- **参考文件**:`references/script_assets_extract.md` +- **资产类型**:`role`、`scene`、`tool`、`clip`(对应 `o_assets.type`) +- **输出**:结构化资产列表(资产名称、资产描述、生成提示词、资产类型),通过 `resultTool` 工具调用返回 + ## 资产提取分工说明 当用户要求从小说中提取"所有资产"或"角色场景道具"时,三个资产提取技能应按以下分工协作: diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index e6fab26..a5be947 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -42,6 +42,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { console.log("[tools] get_novel_events", ids); const data = await u .db("o_novel") + .where("projectId",resTool.data.projectId) .select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState") .whereIn("id", ids); const eventString = data.map((i: any) => [`第${i.index}章,标题:${i.chapter},事件:${i.event}`].join("\n")).join("\n"); diff --git a/src/router.ts b/src/router.ts index 1332528..72185e4 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash 758e343e27eb25780faf00eeab306216 +// @routes-hash 8097a5206252be753261d3f059243260 import { Express } from "express"; import route1 from "./routes/agents/clearMemory"; @@ -66,37 +66,38 @@ import route62 from "./routes/project/getProject"; import route63 from "./routes/script/addScript"; import route64 from "./routes/script/delScript"; import route65 from "./routes/script/exportScript"; -import route66 from "./routes/script/getScrptApi"; -import route67 from "./routes/script/updateScript"; -import route68 from "./routes/scriptAgent/getPlanData"; -import route69 from "./routes/scriptAgent/setPlanData"; -import route70 from "./routes/setting/agentDeploy/agentSetKey"; -import route71 from "./routes/setting/agentDeploy/deployAgentModel"; -import route72 from "./routes/setting/agentDeploy/getAgentDeploy"; -import route73 from "./routes/setting/dbConfig/clearData"; -import route74 from "./routes/setting/fileManagement/openFolder"; -import route75 from "./routes/setting/getTextModel"; -import route76 from "./routes/setting/loginConfig/getUser"; -import route77 from "./routes/setting/loginConfig/updateUserPwd"; -import route78 from "./routes/setting/memoryConfig/delAllMemory"; -import route79 from "./routes/setting/memoryConfig/getMemory"; -import route80 from "./routes/setting/memoryConfig/sureMemory"; -import route81 from "./routes/setting/skillManagement/addSkill"; -import route82 from "./routes/setting/skillManagement/deleteSkill"; -import route83 from "./routes/setting/skillManagement/embeddingSkill"; -import route84 from "./routes/setting/skillManagement/generateDescription"; -import route85 from "./routes/setting/skillManagement/getSkillList"; -import route86 from "./routes/setting/skillManagement/scanSkills"; -import route87 from "./routes/setting/skillManagement/updateSkill"; -import route88 from "./routes/setting/vendorConfig/addVendor"; -import route89 from "./routes/setting/vendorConfig/deleteVendor"; -import route90 from "./routes/setting/vendorConfig/getVendorList"; -import route91 from "./routes/setting/vendorConfig/modelTest"; -import route92 from "./routes/setting/vendorConfig/updateVendor"; -import route93 from "./routes/task/getTaskApi"; -import route94 from "./routes/task/getTaskCategories"; -import route95 from "./routes/task/taskDetails"; -import route96 from "./routes/test/test"; +import route66 from "./routes/script/extractAssets"; +import route67 from "./routes/script/getScrptApi"; +import route68 from "./routes/script/updateScript"; +import route69 from "./routes/scriptAgent/getPlanData"; +import route70 from "./routes/scriptAgent/setPlanData"; +import route71 from "./routes/setting/agentDeploy/agentSetKey"; +import route72 from "./routes/setting/agentDeploy/deployAgentModel"; +import route73 from "./routes/setting/agentDeploy/getAgentDeploy"; +import route74 from "./routes/setting/dbConfig/clearData"; +import route75 from "./routes/setting/fileManagement/openFolder"; +import route76 from "./routes/setting/getTextModel"; +import route77 from "./routes/setting/loginConfig/getUser"; +import route78 from "./routes/setting/loginConfig/updateUserPwd"; +import route79 from "./routes/setting/memoryConfig/delAllMemory"; +import route80 from "./routes/setting/memoryConfig/getMemory"; +import route81 from "./routes/setting/memoryConfig/sureMemory"; +import route82 from "./routes/setting/skillManagement/addSkill"; +import route83 from "./routes/setting/skillManagement/deleteSkill"; +import route84 from "./routes/setting/skillManagement/embeddingSkill"; +import route85 from "./routes/setting/skillManagement/generateDescription"; +import route86 from "./routes/setting/skillManagement/getSkillList"; +import route87 from "./routes/setting/skillManagement/scanSkills"; +import route88 from "./routes/setting/skillManagement/updateSkill"; +import route89 from "./routes/setting/vendorConfig/addVendor"; +import route90 from "./routes/setting/vendorConfig/deleteVendor"; +import route91 from "./routes/setting/vendorConfig/getVendorList"; +import route92 from "./routes/setting/vendorConfig/modelTest"; +import route93 from "./routes/setting/vendorConfig/updateVendor"; +import route94 from "./routes/task/getTaskApi"; +import route95 from "./routes/task/getTaskCategories"; +import route96 from "./routes/task/taskDetails"; +import route97 from "./routes/test/test"; export default async (app: Express) => { app.use("/api/agents/clearMemory", route1); @@ -164,35 +165,36 @@ export default async (app: Express) => { app.use("/api/script/addScript", route63); app.use("/api/script/delScript", route64); app.use("/api/script/exportScript", route65); - app.use("/api/script/getScrptApi", route66); - app.use("/api/script/updateScript", route67); - app.use("/api/scriptAgent/getPlanData", route68); - app.use("/api/scriptAgent/setPlanData", route69); - app.use("/api/setting/agentDeploy/agentSetKey", route70); - app.use("/api/setting/agentDeploy/deployAgentModel", route71); - app.use("/api/setting/agentDeploy/getAgentDeploy", route72); - app.use("/api/setting/dbConfig/clearData", route73); - app.use("/api/setting/fileManagement/openFolder", route74); - app.use("/api/setting/getTextModel", route75); - app.use("/api/setting/loginConfig/getUser", route76); - app.use("/api/setting/loginConfig/updateUserPwd", route77); - app.use("/api/setting/memoryConfig/delAllMemory", route78); - app.use("/api/setting/memoryConfig/getMemory", route79); - app.use("/api/setting/memoryConfig/sureMemory", route80); - app.use("/api/setting/skillManagement/addSkill", route81); - app.use("/api/setting/skillManagement/deleteSkill", route82); - app.use("/api/setting/skillManagement/embeddingSkill", route83); - app.use("/api/setting/skillManagement/generateDescription", route84); - app.use("/api/setting/skillManagement/getSkillList", route85); - app.use("/api/setting/skillManagement/scanSkills", route86); - app.use("/api/setting/skillManagement/updateSkill", route87); - app.use("/api/setting/vendorConfig/addVendor", route88); - app.use("/api/setting/vendorConfig/deleteVendor", route89); - app.use("/api/setting/vendorConfig/getVendorList", route90); - app.use("/api/setting/vendorConfig/modelTest", route91); - app.use("/api/setting/vendorConfig/updateVendor", route92); - app.use("/api/task/getTaskApi", route93); - app.use("/api/task/getTaskCategories", route94); - app.use("/api/task/taskDetails", route95); - app.use("/api/test/test", route96); + app.use("/api/script/extractAssets", route66); + app.use("/api/script/getScrptApi", route67); + app.use("/api/script/updateScript", route68); + app.use("/api/scriptAgent/getPlanData", route69); + app.use("/api/scriptAgent/setPlanData", route70); + app.use("/api/setting/agentDeploy/agentSetKey", route71); + app.use("/api/setting/agentDeploy/deployAgentModel", route72); + app.use("/api/setting/agentDeploy/getAgentDeploy", route73); + app.use("/api/setting/dbConfig/clearData", route74); + app.use("/api/setting/fileManagement/openFolder", route75); + app.use("/api/setting/getTextModel", route76); + app.use("/api/setting/loginConfig/getUser", route77); + app.use("/api/setting/loginConfig/updateUserPwd", route78); + app.use("/api/setting/memoryConfig/delAllMemory", route79); + app.use("/api/setting/memoryConfig/getMemory", route80); + app.use("/api/setting/memoryConfig/sureMemory", route81); + app.use("/api/setting/skillManagement/addSkill", route82); + app.use("/api/setting/skillManagement/deleteSkill", route83); + app.use("/api/setting/skillManagement/embeddingSkill", route84); + app.use("/api/setting/skillManagement/generateDescription", route85); + app.use("/api/setting/skillManagement/getSkillList", route86); + app.use("/api/setting/skillManagement/scanSkills", route87); + app.use("/api/setting/skillManagement/updateSkill", route88); + app.use("/api/setting/vendorConfig/addVendor", route89); + app.use("/api/setting/vendorConfig/deleteVendor", route90); + app.use("/api/setting/vendorConfig/getVendorList", route91); + app.use("/api/setting/vendorConfig/modelTest", route92); + app.use("/api/setting/vendorConfig/updateVendor", route93); + app.use("/api/task/getTaskApi", route94); + app.use("/api/task/getTaskCategories", route95); + app.use("/api/task/taskDetails", route96); + app.use("/api/test/test", route97); } diff --git a/src/routes/script/extractAssets.ts b/src/routes/script/extractAssets.ts index f56430e..50c736c 100644 --- a/src/routes/script/extractAssets.ts +++ b/src/routes/script/extractAssets.ts @@ -5,30 +5,88 @@ import { error, success } from "@/lib/responseFormat"; import compressing from "compressing"; import { validateFields } from "@/middleware/middleware"; import { useSkill } from "@/utils/agent/skillsTools"; +import { Output, tool } from "ai"; const router = express.Router(); - +export const AssetSchema = z.object({ + prompt: z.string().describe("生成提示词"), + name: z.string().describe("资产名称,仅为名称不做其他任何表述"), + desc: z.string().describe("资产描述"), + type: z.enum(["role", "tool", "scene"]).describe("资产类型"), +}); export default router.post( "/", validateFields({ scriptIds: z.array(z.number()), + projectId: z.number(), }), async (req, res) => { - const { scriptIds } = req.body; + const { scriptIds, projectId } = req.body; if (!scriptIds.length) return res.status(400).send(error("请先选择剧本")); const scripts = await u.db("o_script").whereIn("id", scriptIds); const intansce = u.Ai.Text("universalAgent"); - const skill = await useSkill("universal_agent.md"); + const novelData = await u.db("o_novel").where("projectId", projectId).select("chapterData"); + if (!novelData || novelData.length === 0) return res.status(400).send(error("请先上传小说")); - const resData = await intansce.invoke({ - system: skill.prompt, - messages: [ - { - role: "user", - content: "请根据以下小说章节生成事件摘要:\n", + async function getAssets() { + return await u.db("o_assets").where("projectId", projectId).select("id", "name"); + } + for (const scriptId of scriptIds) { + const resultTool = tool({ + description: "返回结果时必须调用这个工具,", + inputSchema: z.object({ + assetsList: z.array(AssetSchema).describe("剧本所使用资产列表,注意不要包含剧本内容,仅为所使用到的 道具、人物、场景、素材"), + }), + execute: async ({ assetsList }) => { + console.log("[tools] set_flowData script", assetsList); + if (assetsList && assetsList.length) { + const assetId = []; + const existingAssets = await getAssets(); + for (const i of assetsList) { + if (existingAssets.length) { + const exist = existingAssets.find((j) => j.name === i.name); + if (exist) { + assetId.push(exist.id); + continue; + } + } + const [id] = await u.db("o_assets").insert({ + name: i.name, + prompt: i.prompt, + type: i.type, + describe: i.desc, + projectId: projectId, + startTime: Date.now(), + }); + assetId.push(id); + } + + await u.db("o_scriptAssets").insert(assetId.map((i) => ({ scriptId: scriptId, assetId: i }))); + } + return true; }, - ], - tools: skill.tools, - }); + }); + try { + const skill = await useSkill("universal_agent.md"); + const resData = await intansce.invoke({ + messages: [ + { + role: "system", + content: + skill.prompt + + "\n\n提取剧本中涉及的资产(角色、场景、道具),参考技能 script_assets_extract 规范,结果必须通过 resultTool 工具返回。", + }, + { + role: "user", + content: `请根据以下剧本提取对应的剧本资产(角色、场景、道具、素材片段):\n\n${scripts.map((i) => i.content).join("\n\n---\n\n")}`, + }, + ], + tools: { ...skill.tools, resultTool }, + }); + console.log("%c Line:47 🥝 resData", "background:#2eafb0", resData); + } catch (e) { + console.log("%c Line:52 🍢 e", "background:#42b983", e); + } + } }, ); diff --git a/src/routes/scriptAgent/getPlanData.ts b/src/routes/scriptAgent/getPlanData.ts index 9ca8ad6..10b3e1c 100644 --- a/src/routes/scriptAgent/getPlanData.ts +++ b/src/routes/scriptAgent/getPlanData.ts @@ -13,11 +13,11 @@ export default router.post( }), async (req, res) => { const { projectId, agentType } = req.body; - const data = await u.db("o_agentWorkData").where({ id: projectId, key: agentType }).first(); + const data = await u.db("o_agentWorkData").where({ projectId: projectId, key: agentType }).first(); if (!data) { await u.db("o_agentWorkData").insert({ - id: projectId, + projectId: projectId, key: agentType, data: JSON.stringify({ storySkeleton: "", diff --git a/src/routes/scriptAgent/setPlanData.ts b/src/routes/scriptAgent/setPlanData.ts index e7ac9e3..40bea7d 100644 --- a/src/routes/scriptAgent/setPlanData.ts +++ b/src/routes/scriptAgent/setPlanData.ts @@ -19,7 +19,7 @@ export default router.post( const { projectId, agentType, data } = req.body; await u .db("o_agentWorkData") - .where({ id: projectId, key: agentType }) + .where({ projectId: projectId, key: agentType }) .update({ data: JSON.stringify(data), }); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 3e58c97..bf2ee8f 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,6 +1,17 @@ -// @db-hash ce28b6d566911952421c2661e14bfde5 +// @db-hash 579a004cc745580469a24ee71f5f51c3 //该文件由脚本自动生成,请勿手动修改 +export interface _o_project_old_20260326 { + 'artStyle'?: string | null; + 'createTime'?: number | null; + 'id'?: number | null; + 'intro'?: string | null; + 'name'?: string | null; + 'projectType'?: string | null; + 'type'?: string | null; + 'userId'?: number | null; + 'videoRatio'?: string | null; +} export interface _o_storyboard_old_20260325 { 'camera'?: string | null; 'createTime'?: number | null; @@ -127,11 +138,13 @@ export interface o_project { 'artStyle'?: string | null; 'createTime'?: number | null; 'id'?: number | null; + 'imageModel'?: string | null; 'intro'?: string | null; 'name'?: string | null; 'projectType'?: string | null; 'type'?: string | null; 'userId'?: number | null; + 'videoModel'?: string | null; 'videoRatio'?: string | null; } export interface o_script { @@ -237,6 +250,7 @@ export interface o_videoConfig { } export interface DB { + "_o_project_old_20260326": _o_project_old_20260326; "_o_storyboard_old_20260325": _o_storyboard_old_20260325; "memories": memories; "o_agentDeploy": o_agentDeploy; diff --git a/src/utils/cleanNovel.ts b/src/utils/cleanNovel.ts index 9e3d4f2..2c4e633 100644 --- a/src/utils/cleanNovel.ts +++ b/src/utils/cleanNovel.ts @@ -16,46 +16,68 @@ export interface EventType { class CleanNovel { emitter: EventEmitter; - constructor() { + /** 最大并发数 */ + concurrency: number; + + constructor(concurrency: number = 5) { this.emitter = new EventEmitter(); + this.concurrency = concurrency; } + + private async processChapter(novel: o_novel, intansce: ReturnType): Promise { + try { + const skill = await useSkill("universal_agent.md"); + + const resData = await intansce.invoke({ + system: skill.prompt, + messages: [ + { + role: "user", + content: "请根据以下小说章节生成事件摘要:\n" + novel.chapterData!, + }, + ], + tools: skill.tools, + }); + + const preData = resData.text; + + this.emitter.emit("item", { id: novel.id, event: preData }); + return { id: novel.id!, event: preData }; + } catch (e) { + this.emitter.emit("item", { id: novel.id, event: null, errorReason: u.error(e).message }); + return null; + } + } + async start(allChapters: o_novel[], projectId: number): Promise { - //所有事件 - let totalEvent: EventType[] = []; + const totalEvent: EventType[] = []; const intansce = u.Ai.Text("universalAgent"); - try { - for (let gi = 0; gi < allChapters.length; gi++) { - const novel = allChapters[gi]; - let resData; - try { - const skill = await useSkill("universal_agent.md"); + // 并发控制:通过信号量限制同时执行的任务数 + let running = 0; + let index = 0; + const results: Promise[] = []; - resData = await intansce.invoke({ - system: skill.prompt, - messages: [ - { - role: "user", - content: "请根据以下小说章节生成事件摘要:\n" + novel.chapterData!, - }, - ], - tools: skill.tools, - }); - console.log("%c Line:35 🍆 resData", "background:#fca650", resData); + const runNext = (): Promise => { + if (index >= allChapters.length) return Promise.resolve(); + const novel = allChapters[index++]; + running++; - const preData = resData.text; + return this.processChapter(novel, intansce).then((result) => { + if (result) totalEvent.push(result); + running--; + return runNext(); + }); + }; + + // 启动最多 concurrency 个并发任务 + const workers = Array.from( + { length: Math.min(this.concurrency, allChapters.length) }, + () => runNext() + ); + + await Promise.all(workers); - this.emitter.emit("item", { id: novel.id, event: preData }); - totalEvent.push({ id: novel.id!, event: preData }); - } catch (e) { - console.log("%c Line:51 🍩 e", "background:#93c0a4", e); - this.emitter.emit("item", { id: novel.id, event: null, errorReason: u.error(e).message }); - } - } - } catch (e) { - console.error(e); - throw e; - } return totalEvent; } } diff --git a/src/utils/vm.ts b/src/utils/vm.ts index a8ab519..da27c4c 100644 --- a/src/utils/vm.ts +++ b/src/utils/vm.ts @@ -43,7 +43,6 @@ export default function runCode(code: string) { return exports as Record; } - /** * 压缩图片,目标字节数不高于 size */ From c84f853d92572710446f4ba5963e66f39077a324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=B8=85?= <2944435683> Date: Fri, 27 Mar 2026 00:54:08 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E5=9B=BE=E7=89=87=E5=AD=97=E6=AE=B5=E7=BC=BA?= =?UTF-8?q?=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/assets/getImage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/assets/getImage.ts b/src/routes/assets/getImage.ts index bfde1ba..b575e5d 100644 --- a/src/routes/assets/getImage.ts +++ b/src/routes/assets/getImage.ts @@ -14,7 +14,7 @@ export default router.post( async (req, res) => { const { assetsId } = req.body; - const assets = await u.db("o_assets").where("id", assetsId).select("id", "imageId", "type", "state").first(); + const assets = await u.db("o_assets").where("id", assetsId).select("id", "imageId", "type").first(); const rawTempAssets = await u.db("o_image").where("assetsId", assetsId).select("id", "filePath", "assetsId", "type", "state"); @@ -28,10 +28,10 @@ export default router.post( const data = { id: assets!.id, - state: assets!.state, imageId: assets!.imageId ?? null, tempAssets, }; + console.log("%c Line:30 🥤 data", "background:#465975", data); res.status(200).send(success(data)); }, ); From c0574dd5157c57e06371eeabdadcddcb3bc65644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=B8=85?= <2944435683> Date: Fri, 27 Mar 2026 01:14:03 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=88=86=E9=95=9C?= =?UTF-8?q?=E7=9A=84index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/production/saveFlowData.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/routes/production/saveFlowData.ts b/src/routes/production/saveFlowData.ts index 2cd1b20..9b69a2f 100644 --- a/src/routes/production/saveFlowData.ts +++ b/src/routes/production/saveFlowData.ts @@ -14,8 +14,13 @@ export default router.post( data: flowDataSchema, }), async (req, res) => { - const { projectId, episodesId } = req.body; + const { data, projectId, episodesId } = req.body; const sqlData = await u.db("o_agentWorkData").where("projectId", String(projectId)).andWhere("episodesId", String(episodesId)).first(); + for (let item of data.storyboard) { + await u.db("o_storyboard").where("id", item.id).update({ + index: item.id, + }); + } if (!sqlData) { await u.db("o_agentWorkData").insert({ projectId, From 011d0ff8b8ddb1cb84ea5a555cd2a81e7553c572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=B8=85?= <2944435683> Date: Fri, 27 Mar 2026 01:35:58 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=89=A7=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/script/delScript.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/routes/script/delScript.ts b/src/routes/script/delScript.ts index b2d07cd..8eb3791 100644 --- a/src/routes/script/delScript.ts +++ b/src/routes/script/delScript.ts @@ -7,13 +7,13 @@ const router = express.Router(); // 删除剧本 export default router.post( - "/", - validateFields({ - id: z.number(), - }), - async (req, res) => { - const { id } = req.body; - await u.db("o_script").where({ id }).delete(); - res.status(200).send(success({ message: "删除剧本成功" })); - }, + "/", + validateFields({ + id: z.array(z.number()), + }), + async (req, res) => { + const { id } = req.body; + await u.db("o_script").whereIn("id", id).delete(); + res.status(200).send(success({ message: "删除剧本成功" })); + }, ); From 86dbd144936c14abc285ed218c6aeaafc9dd8fbd Mon Sep 17 00:00:00 2001 From: zhishi <1951671751@qq.com> Date: Fri, 27 Mar 2026 01:36:21 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E5=8E=BB=E9=99=A4thin?= =?UTF-8?q?k=E6=A0=87=E7=AD=BE=20=E6=96=B9=E6=B3=95=20=EF=BC=88=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=EF=BC=89=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/productionAgent/index.ts | 1 + src/agents/scriptAgent/index.ts | 6 +- src/agents/scriptAgent/tools.ts | 59 +++++----- .../assetsGenerate/polishAssetsPrompt.ts | 1 + src/types/database.d.ts | 34 +++++- src/utils/stripThink.ts | 106 ++++++++++++++++++ 6 files changed, 179 insertions(+), 28 deletions(-) create mode 100644 src/utils/stripThink.ts diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index 465a15f..709df70 100644 --- a/src/agents/productionAgent/index.ts +++ b/src/agents/productionAgent/index.ts @@ -123,6 +123,7 @@ function runSubAgent(parentCtx: AgentContext) { prompt: z.string().max(100).describe("交给子Agent的任务简约描述"), }), execute: async ({ agent, prompt }) => { + //todo 传入md有问题 const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; //运行子Agent const subTextStream = await fn({ ...parentCtx, text: prompt }); diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index df0a344..e507e7f 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -46,7 +46,7 @@ export async function decisionAI(ctx: AgentContext) { const systemPrompt = buildSystemPrompt(skill.prompt, mem); const projectData = await u.db("o_project").where("id", resTool.data.projectId).first(); - const novelData = await u.db("o_novel").select("id", "chapterIndex as index"); + const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index"); const projectInfo = [ "## 项目信息", @@ -70,6 +70,7 @@ export async function decisionAI(ctx: AgentContext) { ...useTools(ctx.resTool), }, onFinish: async (completion) => { + console.log("%c Line:73 🍧 completion", "background:#93c0a4", completion); await memory.add("assistant:decision", completion.text); }, }); @@ -99,6 +100,7 @@ export async function executionAI(ctx: AgentContext) { ...useTools(ctx.resTool), }, onFinish: async (completion) => { + console.log("%c Line:102 🍻 completion", "background:#fca650", completion); await memory.add("assistant:execution", completion.text); }, }); @@ -125,6 +127,7 @@ export async function supervisionAI(ctx: AgentContext) { ...useTools(ctx.resTool), }, onFinish: async (completion) => { + console.log("%c Line:129 🍣 completion", "background:#3f7cff", completion); await memory.add("assistant:supervision", completion.text); }, }); @@ -149,6 +152,7 @@ function runSubAgent(parentCtx: AgentContext) { let fullResponse = ""; for await (const chunk of subTextStream) { + console.log("%c Line:155 🥛 chunk", "background:#fca650", chunk); msg.send(chunk); fullResponse += chunk; } diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index a5be947..420416e 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -12,7 +12,7 @@ export const AssetSchema = z.object({ type: z.enum(["role", "tool", "scene", "clip"]).describe("资产类型"), }); export const ScriptSchema = z.object({ - id: z.number().describe("剧本ID,如果新增则为空").optional(), + id: z.number().describe("剧本ID"), name: z.string().describe("剧本名称"), content: z.string().describe("剧本内容"), }); @@ -42,7 +42,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { console.log("[tools] get_novel_events", ids); const data = await u .db("o_novel") - .where("projectId",resTool.data.projectId) + .where("projectId", resTool.data.projectId) .select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState") .whereIn("id", ids); const eventString = data.map((i: any) => [`第${i.index}章,标题:${i.chapter},事件:${i.event}`].join("\n")).join("\n"); @@ -91,41 +91,48 @@ export default (resTool: ResTool, toolsNames?: string[]) => { return true; }, }), - insert_script_to_sqlite: tool({ - description: "将剧本内容插入sqlite数据库,供后续业务使用", + update_script_to_sqlite: tool({ + description: "更新剧本,修改数据库对应剧本,供后续业务使用", inputSchema: z.object({ script: ScriptSchema, - // assetsList: z.array(AssetSchema).describe("剧本所使用资产列表,注意不要包含剧本内容,仅为所使用到的 道具、人物、场景、素材"), }), execute: async ({ script }) => { console.log("%c Line:103 🍷 script", "background:#42b983", script); - // console.log("[tools] insert_script_to_sqlite", assetsList); + await u.db("o_script").where({ id: script.id }).update({ + name: script.name, + content: script.content, + }); + + socket.emit("setPlanData", { key: "script", value: script.id }); + return true; + }, + }), + insert_script_to_sqlite: tool({ + description: "新增剧本,将剧本内容插入sqlite数据库,供后续业务使用", + inputSchema: z.object({ + script: ScriptSchema.omit({ id: true }), + }), + execute: async ({ script }) => { + console.log("%c Line:103 🍷 script", "background:#42b983", script); + const [scriptId] = await u.db("o_script").insert({ name: script.name, content: script.content, projectId: resTool.data.projectId, createTime: Date.now(), }); - // if (assetsList && assetsList.length) { - // const assetId = []; - // for (const i of assetsList) { - // if (i.id) { - // assetId.push(i.id); - // continue; - // } - // const [id] = await u.db("o_assets").insert({ - // name: i.name, - // prompt: i.prompt, - // type: i.type, - // describe: i.desc, - // projectId: resTool.data.projectId, - // startTime: Date.now(), - // }); - // assetId.push(id); - // } - - // await u.db("o_scriptAssets").insert(assetId.map((i) => ({ scriptId, assetId: i }))); - // } + socket.emit("setPlanData", { key: "script", value: scriptId }); + return true; + }, + }), + delete_script_to_sqlite: tool({ + description: "删除剧本,将剧本内容从sqlite数据库中删除", + inputSchema: z.object({ + scriptId: z.string().describe("剧本id"), + }), + execute: async ({ scriptId }) => { + console.log("[tools] delete_script_to_sqlite", scriptId); + await u.db("o_script").where({ id: scriptId }).delete(); socket.emit("setPlanData", { key: "script", value: scriptId }); return true; }, diff --git a/src/routes/assetsGenerate/polishAssetsPrompt.ts b/src/routes/assetsGenerate/polishAssetsPrompt.ts index 887341e..29fdf19 100644 --- a/src/routes/assetsGenerate/polishAssetsPrompt.ts +++ b/src/routes/assetsGenerate/polishAssetsPrompt.ts @@ -122,6 +122,7 @@ export default router.post( messages: [{ role: "user", content: "小说原文" + novelText }], tools: skill.tools, })) as any; + if (!_output) return res.status(500).send("失败"); await u.db("o_assets").where("id", assetsId).update({ prompt: _output }); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 70b079c..bf2ee8f 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,6 +1,36 @@ -// @db-hash d807205fbb27fc5ddb04cae060fb4430 +// @db-hash 579a004cc745580469a24ee71f5f51c3 //该文件由脚本自动生成,请勿手动修改 +export interface _o_project_old_20260326 { + 'artStyle'?: string | null; + 'createTime'?: number | null; + 'id'?: number | null; + 'intro'?: string | null; + 'name'?: string | null; + 'projectType'?: string | null; + 'type'?: string | null; + 'userId'?: number | null; + 'videoRatio'?: string | null; +} +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; @@ -220,6 +250,8 @@ export interface o_videoConfig { } export interface DB { + "_o_project_old_20260326": _o_project_old_20260326; + "_o_storyboard_old_20260325": _o_storyboard_old_20260325; "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData; diff --git a/src/utils/stripThink.ts b/src/utils/stripThink.ts new file mode 100644 index 0000000..9867a93 --- /dev/null +++ b/src/utils/stripThink.ts @@ -0,0 +1,106 @@ +/** + * 去除深度思考模型输出的 ... 标签及其内容 + * + * 1. stripThink(text) — 用于非流式,直接去除完整文本中的 块 + * 2. createThinkStreamFilter() — 用于流式,返回有状态的过滤器,逐 chunk 过滤 + */ + +/** + * 非流式:去除完整文本中的 ... + */ +export function stripThink(text: string): string { + return text.replace(/[\s\S]*?<\/think>/g, "").trim(); +} + +/** + * 流式:创建一个有状态的 chunk 过滤器 + * + * 用法: + * ```ts + * const filter = createThinkStreamFilter(); + * for await (const chunk of textStream) { + * const filtered = filter.push(chunk); + * if (filtered) msg.send(filtered); + * } + * ``` + */ +export function createThinkStreamFilter() { + let insideThink = false; + let buffer = ""; + + return { + /** + * 输入一个 chunk,返回过滤后需要输出的文本(可能为空字符串) + */ + push(chunk: string): string { + let output = ""; + let i = 0; + + while (i < chunk.length) { + if (insideThink) { + // 正在 内部,寻找 + const closeIdx = chunk.indexOf("", i); + if (closeIdx !== -1) { + // 找到闭合标签,跳过标签内容 + insideThink = false; + i = closeIdx + "".length; + } else { + // 整个剩余 chunk 都在 think 内,全部丢弃 + break; + } + } else { + // 不在 内部 + const openIdx = chunk.indexOf("", i); + if (openIdx !== -1) { + // 找到开启标签,输出标签之前的内容 + output += buffer + chunk.slice(i, openIdx); + buffer = ""; + insideThink = true; + i = openIdx + "".length; + } else { + // 没有发现 ,但可能 chunk 末尾是不完整的 "" 的不完整前缀 + * 如 "<", " Date: Fri, 27 Mar 2026 02:00:09 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AB=A0=E8=8A=82ID?= =?UTF-8?q?=E6=98=8E=E7=A1=AE=E6=A0=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/scriptAgent/index.ts | 4 ++-- src/agents/scriptAgent/tools.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index e507e7f..0f727bd 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -47,6 +47,7 @@ export async function decisionAI(ctx: AgentContext) { const projectData = await u.db("o_project").where("id", resTool.data.projectId).first(); const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index"); + console.log("%c Line:50 🥒 novelData", "background:#2eafb0", novelData); const projectInfo = [ "## 项目信息", @@ -57,7 +58,7 @@ export async function decisionAI(ctx: AgentContext) { `目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`, ].join("\n"); - const prefixSystem = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- ${i.id}: 第${i.index}章`).join("\n")}\n\n`; + const prefixSystem = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID:${i.id}: 第${i.index}章`).join("\n")}\n\n`; const { textStream } = await u.Ai.Text("scriptAgent").stream({ system: prefixSystem + systemPrompt, @@ -152,7 +153,6 @@ function runSubAgent(parentCtx: AgentContext) { let fullResponse = ""; for await (const chunk of subTextStream) { - console.log("%c Line:155 🥛 chunk", "background:#fca650", chunk); msg.send(chunk); fullResponse += chunk; } diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index 420416e..f05d736 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -35,7 +35,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { get_novel_events: tool({ description: "获取章节事件", inputSchema: z.object({ - ids: z.array(z.number()).describe("章节id"), + ids: z.array(z.number()).describe("章节id,注意区分"), }), execute: async ({ ids }) => { resTool.systemMessage(`正在阅读 章节事件 数据...`);