From 3998d441d44c0cefeaa80019880830263eb2f79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=B8=85?= <2944435683> Date: Mon, 30 Mar 2026 21:55:12 +0800 Subject: [PATCH 1/4] =?UTF-8?q?ai=E7=94=9F=E6=88=90=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=8D=95=E7=8B=AC=E6=8B=BF=E5=87=BA=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/initDB.ts | 179 +++++++++--------- .../batchGenerateImageAssets.ts | 24 ++- src/routes/assetsGenerate/generateAssets.ts | 24 ++- .../production/workbench/generateVideo.ts | 30 +-- src/types/database.d.ts | 5 +- src/utils/ai.ts | 38 ++-- 6 files changed, 147 insertions(+), 153 deletions(-) diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index f207906..fd0bea9 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -263,96 +263,95 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => { name: "剧本资产提取", type: "scriptAssetExtraction", - data: ` - --- - name: universal_agent - description: 专注于从剧本内容中提取所使用的资产(角色、场景、道具)并生成结构化资产列表的助手。 - --- - - # Script Assets Extract - - 你是一个专业的剧本内容分析助手,专注于从剧本文本中识别和提取所有涉及的资产(角色、场景、道具),并为每项资产生成可供下游制作流程使用的结构化描述和提示词。 - - ## 何时使用 - - 用户提供剧本内容,你需要逐段阅读并提取其中涉及的所有资产(人物角色、场景地点、道具物件),输出为结构化的资产列表。产出的资产描述将用于后续 AI 图片生成和制作流程。 - - ## 与系统的对应关系 - - - 资产类型: - - \`role\` — 角色(对应 \`o_assets.type = "role"\`) - - \`scene\` — 场景(对应 \`o_assets.type = "scene"\`) - - \`tool\` — 道具(对应 \`o_assets.type = "tool"\`) - - 下游用途:资产提示词生成 → AI 资产图生成 → 分镜制作 - - ## 输出要求 - - **必须通过调用 \`resultTool\` 工具返回结果**,禁止以纯文本、Markdown 表格或 JSON 代码块等形式直接输出资产列表。 - \`resultTool\` 的 schema 会对字段类型和枚举值做强校验,调用时请严格按照下方字段定义填写,确保数据结构正确、字段完整、类型匹配。 - - 每个资产对象包含以下字段: - - | 字段 | 类型 | 必填 | 说明 | - | ---- | ---- | ---- | ---- | - | \`name\` | string | 是 | 资产名称,使用剧本中的原始称呼,不做其他多余描述 | - | \`desc\` | string | 是 | 资产描述,30-80 字的视觉化描述 | - | \`prompt\` | string | 是 | 生成提示词,英文,用于 AI 图片生成 | - | \`type\` | enum | 是 | 资产类型:\`role\` / \`scene\` / \`tool\` | - - ## 提取规则 - - ### 角色(role) - - - 提取剧本中出现的所有有名字的角色 - - \`desc\`:包含外貌特征、服饰风格、体态气质等视觉要素 - - \`prompt\`:英文提示词,描述角色的外观特征,适用于 AI 角色图生成 - - 同一角色有多个称呼时,取最常用的作为 \`name\` - - 无名龙套(如"路人甲"、"士兵")可跳过,除非其造型对剧情有重要视觉意义 - - ### 场景(scene) - - - 提取剧本中出现的所有场景/地点 - - \`desc\`:包含空间结构、光照氛围、关键陈设、色调基调等视觉要素 - - \`prompt\`:英文提示词,描述场景的整体视觉风格,适用于 AI 场景图生成 - - 同一场景的不同状态(如白天/夜晚)不重复提取,在 \`desc\` 中注明即可 - - ### 道具(tool) - - - 提取剧本中出现的重要道具/物品 - - \`desc\`:包含外观形状、颜色材质、尺寸参考、特殊效果等视觉要素 - - \`prompt\`:英文提示词,描述道具的外观细节,适用于 AI 道具图生成 - - 仅提取有独立视觉意义或剧情功能的道具,通用物品可跳过 - - - ## 提示词(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 分类,不混淆 - 5. **提示词质量**:英文提示词应具体、可执行,能直接用于 AI 图片生成 - - ## 注意事项 - - - 资产列表中**不要包含剧本内容本身**,仅提取所使用到的资产 - - 角色的随身物品如果有独立剧情功能,应单独作为道具提取 - - 场景中的固定陈设不需要单独提取为道具,除非该物件有独立剧情作用 + data: `--- +name: universal_agent +description: 专注于从剧本内容中提取所使用的资产(角色、场景、道具)并生成结构化资产列表的助手。 +--- + +# Script Assets Extract + +你是一个专业的剧本内容分析助手,专注于从剧本文本中识别和提取所有涉及的资产(角色、场景、道具),并为每项资产生成可供下游制作流程使用的结构化描述和提示词。 + +## 何时使用 + +用户提供剧本内容,你需要逐段阅读并提取其中涉及的所有资产(人物角色、场景地点、道具物件),输出为结构化的资产列表。产出的资产描述将用于后续 AI 图片生成和制作流程。 + +## 与系统的对应关系 + +- 资产类型: + - \`role\` — 角色(对应 \`o_assets.type = "role"\`) + - \`scene\` — 场景(对应 \`o_assets.type = "scene"\`) + - \`tool\` — 道具(对应 \`o_assets.type = "tool"\`) +- 下游用途:资产提示词生成 → AI 资产图生成 → 分镜制作 + +## 输出要求 + +**必须通过调用 \`resultTool\` 工具返回结果**,禁止以纯文本、Markdown 表格或 JSON 代码块等形式直接输出资产列表。 +\`resultTool\` 的 schema 会对字段类型和枚举值做强校验,调用时请严格按照下方字段定义填写,确保数据结构正确、字段完整、类型匹配。 + +每个资产对象包含以下字段: + +| 字段 | 类型 | 必填 | 说明 | +| ---- | ---- | ---- | ---- | +| \`name\` | string | 是 | 资产名称,使用剧本中的原始称呼,不做其他多余描述 | +| \`desc\` | string | 是 | 资产描述,30-80 字的视觉化描述 | +| \`prompt\` | string | 是 | 生成提示词,英文,用于 AI 图片生成 | +| \`type\` | enum | 是 | 资产类型:\`role\` / \`scene\` / \`tool\` | + +## 提取规则 + +### 角色(role) + +- 提取剧本中出现的所有有名字的角色 +- \`desc\`:包含外貌特征、服饰风格、体态气质等视觉要素 +- \`prompt\`:英文提示词,描述角色的外观特征,适用于 AI 角色图生成 +- 同一角色有多个称呼时,取最常用的作为 \`name\` +- 无名龙套(如"路人甲"、"士兵")可跳过,除非其造型对剧情有重要视觉意义 + +### 场景(scene) + +- 提取剧本中出现的所有场景/地点 +- \`desc\`:包含空间结构、光照氛围、关键陈设、色调基调等视觉要素 +- \`prompt\`:英文提示词,描述场景的整体视觉风格,适用于 AI 场景图生成 +- 同一场景的不同状态(如白天/夜晚)不重复提取,在 \`desc\` 中注明即可 + +### 道具(tool) + +- 提取剧本中出现的重要道具/物品 +- \`desc\`:包含外观形状、颜色材质、尺寸参考、特殊效果等视觉要素 +- \`prompt\`:英文提示词,描述道具的外观细节,适用于 AI 道具图生成 +- 仅提取有独立视觉意义或剧情功能的道具,通用物品可跳过 + + +## 提示词(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 分类,不混淆 +5. **提示词质量**:英文提示词应具体、可执行,能直接用于 AI 图片生成 + +## 注意事项 + +- 资产列表中**不要包含剧本内容本身**,仅提取所使用到的资产 +- 角色的随身物品如果有独立剧情功能,应单独作为道具提取 +- 场景中的固定陈设不需要单独提取为道具,除非该物件有独立剧情作用 `, }, ]); diff --git a/src/routes/assetsGenerate/batchGenerateImageAssets.ts b/src/routes/assetsGenerate/batchGenerateImageAssets.ts index 1fce062..55e4e30 100644 --- a/src/routes/assetsGenerate/batchGenerateImageAssets.ts +++ b/src/routes/assetsGenerate/batchGenerateImageAssets.ts @@ -110,16 +110,20 @@ export default router.post("/", validateFields(requestSchema), async (req, res) try { const aiImage = u.Ai.Image(model); - await aiImage.run({ - prompt: userPrompt, - imageBase64: item.base64 ? [item.base64] : [], - size: resolution, - aspectRatio: "16:9", - taskClass: cfg.taskClass, - describe, - projectId, - relatedObjects: JSON.stringify(relatedObjects), - }); + await aiImage.run( + { + prompt: userPrompt, + imageBase64: item.base64 ? [item.base64] : [], + size: resolution, + aspectRatio: "16:9", + }, + { + taskClass: cfg.taskClass, + describe, + projectId, + relatedObjects: JSON.stringify(relatedObjects), + }, + ); aiImage.save(imagePath); const imageData = await u.db("o_image").where("id", imageId).select("*").first(); diff --git a/src/routes/assetsGenerate/generateAssets.ts b/src/routes/assetsGenerate/generateAssets.ts index 4314a4b..830c536 100644 --- a/src/routes/assetsGenerate/generateAssets.ts +++ b/src/routes/assetsGenerate/generateAssets.ts @@ -98,16 +98,20 @@ export default router.post("/", validateFields(requestSchema), async (req, res) try { // 4. 调用 AI 生成图片 const aiImage = u.Ai.Image(model); - await aiImage.run({ - prompt: userPrompt, - imageBase64: base64 ? [base64] : [], - size: resolution, - aspectRatio: "16:9", - taskClass: cfg.taskClass, - describe, - projectId, - relatedObjects: JSON.stringify(relatedObjects), - }); + await aiImage.run( + { + prompt: userPrompt, + imageBase64: base64 ? [base64] : [], + size: resolution, + aspectRatio: "16:9", + }, + { + taskClass: cfg.taskClass, + describe, + projectId, + relatedObjects: JSON.stringify(relatedObjects), + }, + ); aiImage.save(imagePath); // 5. 更新记录 & 返回结果 diff --git a/src/routes/production/workbench/generateVideo.ts b/src/routes/production/workbench/generateVideo.ts index cb38602..eb7582b 100644 --- a/src/routes/production/workbench/generateVideo.ts +++ b/src/routes/production/workbench/generateVideo.ts @@ -105,19 +105,23 @@ export default router.post( console.log("%c Line:110 🍑 prompt", "background:#b03734", prompt); const aiVideo = u.Ai.Video(model); - await aiVideo.run({ - projectId, - prompt, - imageBase64: base64.filter((item) => item !== null) as string[], - mode, - duration, - aspectRatio: (ratio?.videoRatio as `${number}:${number}`) || "16:9", - resolution, - audio, - taskClass: "视频生成", - describe: "根据提示词生成视频", - relatedObjects: JSON.stringify(relatedObjects), - }); + await aiVideo.run( + { + prompt, + imageBase64: base64.filter((item) => item !== null) as string[], + mode, + duration, + aspectRatio: (ratio?.videoRatio as `${number}:${number}`) || "16:9", + resolution, + audio, + }, + { + projectId, + taskClass: "视频生成", + describe: "根据提示词生成视频", + relatedObjects: JSON.stringify(relatedObjects), + }, + ); await aiVideo.save(videoPath); await u.db("o_video").where("id", videoId).update({ state: "生成成功" }); // await u.db("o_videoConfig").where("storyboardId", storyboardId).update({ videoId, updateTime: Date.now() }); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 9b42b68..ab3e7f9 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,8 +1,4 @@ -<<<<<<< HEAD -// @db-hash 93b2462070c45c2b449e9a18c4e88763 -======= // @db-hash 24748d4ef971381a79c720c846f83847 ->>>>>>> 796947cef173e7fe2f96e21fa8aeae23ff0fdf4a //该文件由脚本自动生成,请勿手动修改 export interface memories { @@ -182,6 +178,7 @@ export interface o_storyboard { 'sound'?: string | null; 'state'?: string | null; 'title'?: string | null; + 'videoPrompt'?: string | null; } export interface o_tasks { 'describe'?: string | null; diff --git a/src/utils/ai.ts b/src/utils/ai.ts index 9c6df1e..4f3548d 100644 --- a/src/utils/ai.ts +++ b/src/utils/ai.ts @@ -25,23 +25,10 @@ async function getVendorTemplateFn(fnName: FnName, modelName: `${string}:${strin const selectedModel = modelList.find((i: any) => i.modelName == name); if (!selectedModel) throw new Error(`未找到模型 ${name} id=${id}`); const jsCode = transform(vendorConfigData.code!, { transforms: ["typescript"] }).code; - const running = u.vm(jsCode); - Object.assign(running.vendor.inputValues, JSON.parse(vendorConfigData.inputValues ?? "{}")); - running.vendor.models = modelList; - const fn = running[fnName]; + const fn = u.vm(jsCode)[fnName]; if (!fn) throw new Error(`未找到供应商配置中的函数 ${fnName} id=${id}`); - if (fnName == "textRequest") { - const model = fn(selectedModel); - if (!model) throw new Error(`供应商 textRequest 返回无效模型 id=${id}`); - return model; - } - return async (input: T) => { - const result = await fn(input, selectedModel); - if (result === undefined || result === null) { - throw new Error(`供应商函数 ${fnName} 未返回有效结果 id=${id}`); - } - return result; - }; + if (fnName == "textRequest") return fn(selectedModel); + else return (input: T) => fn(input, selectedModel); } async function withTaskRecord( @@ -113,6 +100,9 @@ interface ImageConfig { imageBase64: string[]; //输入的图片提示词 size: "1K" | "2K" | "4K"; // 图片尺寸 aspectRatio: `${number}:${number}`; // 长宽比 +} + +interface TaskRecord { taskClass: string; // 任务分类 describe: string; // 任务描述 relatedObjects: string; // 相关对象信息,便于后续分析和追踪 @@ -125,8 +115,8 @@ class AiImage { constructor(key: `${string}:${string}`) { this.key = key; } - async run(input: ImageConfig) { - return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => { + async run(input: ImageConfig, taskRecord: TaskRecord) { + return withTaskRecord(this.key, taskRecord.taskClass, taskRecord.describe, taskRecord.relatedObjects, taskRecord.projectId, async (modelName) => { const fn = await getVendorTemplateFn("imageRequest", modelName); this.result = await fn(input); if (this.result.startsWith("http")) this.result = await urlToBase64(this.result); @@ -139,7 +129,6 @@ class AiImage { } } interface VideoConfig { - projectId: number; // 项目ID prompt: string; //视频提示词 imageBase64: string[]; //输入的图片提示词 aspectRatio: `${number}:${number}`; // 长宽比 @@ -147,9 +136,6 @@ interface VideoConfig { duration: number; // 视频时长,单位秒 resolution: string; // 视频分辨率 audio: boolean; // 是否需要配音 - taskClass: string; // 任务分类 - describe: string; // 任务描述 - relatedObjects: string; // 相关对象信息,便于后续分析和追踪 } class AiVideo { @@ -158,8 +144,8 @@ class AiVideo { constructor(key: `${string}:${string}`) { this.key = key; } - async run(input: VideoConfig) { - return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => { + async run(input: VideoConfig, taskRecord: TaskRecord) { + return withTaskRecord(this.key, taskRecord.taskClass, taskRecord.describe, taskRecord.relatedObjects, taskRecord.projectId, async (modelName) => { const fn = await getVendorTemplateFn("videoRequest", modelName); this.result = await fn(input); if (this.result.startsWith("http")) this.result = await urlToBase64(this.result); @@ -177,8 +163,8 @@ class AiAudio { constructor(key: `${string}:${string}`) { this.key = key; } - async run(input: VideoConfig) { - return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => { + async run(input: VideoConfig, taskRecord: TaskRecord) { + return withTaskRecord(this.key, taskRecord.taskClass, taskRecord.describe, taskRecord.relatedObjects, taskRecord.projectId, async (modelName) => { const fn = await getVendorTemplateFn("ttsRequest", modelName); this.result = await fn(input); if (this.result.startsWith("http")) this.result = await urlToBase64(this.result); From ffdc6854c0192fd01e4195fbb21090450d2b71f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ACT=E4=B8=B6=E6=B5=81=E6=98=9F=E9=9B=A8?= <1340145680@qq.com> Date: Mon, 30 Mar 2026 22:13:38 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=9F=E4=BA=A7Agent?= =?UTF-8?q?=E8=AE=B0=E5=BF=86=E9=94=99=E4=B9=B1=E9=97=AE=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/productionAgent/index.ts | 17 +++++++---- src/agents/scriptAgent/index.ts | 17 +++++++---- src/routes/test/test.ts | 44 +++++++++++++++++----------- src/socket/routes/productionAgent.ts | 29 +++++++++++++++--- src/socket/routes/scriptAgent.ts | 16 ++++++++-- src/types/database.d.ts | 4 --- 6 files changed, 87 insertions(+), 40 deletions(-) diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index 431f91a..e683aec 100644 --- a/src/agents/productionAgent/index.ts +++ b/src/agents/productionAgent/index.ts @@ -98,14 +98,19 @@ function createSubAgent(parentCtx: AgentContext) { tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) }, }); - for await (const chunk of textStream) { - text.append(chunk); - fullResponse += chunk; + try { + for await (const chunk of textStream) { + text.append(chunk); + fullResponse += chunk; + } + text.complete(); + subMsg.complete(); + } catch (err: any) { + text.complete(); + subMsg.stop(); + throw err; } - text.complete(); - subMsg.complete(); - if (fullResponse.trim()) { await memory.add(memoryKey, fullResponse, { name, diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index ea57518..5542f77 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -108,14 +108,19 @@ function createSubAgent(parentCtx: AgentContext) { tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) }, }); - for await (const chunk of textStream) { - text.append(chunk); - fullResponse += chunk; + try { + for await (const chunk of textStream) { + text.append(chunk); + fullResponse += chunk; + } + text.complete(); + subMsg.complete(); + } catch (err: any) { + text.complete(); + subMsg.stop(); + throw err; } - text.complete(); - subMsg.complete(); - if (fullResponse.trim()) { await memory.add(memoryKey, fullResponse, { name, diff --git a/src/routes/test/test.ts b/src/routes/test/test.ts index ed40deb..21d4b99 100644 --- a/src/routes/test/test.ts +++ b/src/routes/test/test.ts @@ -2,25 +2,35 @@ import express from "express"; const router = express.Router(); import u from "@/utils"; import fs from "fs"; -import { useSkill } from "@/utils/agent/skillsTools"; +import Memory from "@/utils/agent/memory"; + + +function buildMemPrompt(mem: Awaited>): string { + let memoryContext = ""; + if (mem.rag.length) { + memoryContext += `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`; + } + if (mem.summaries.length) { + if (memoryContext) memoryContext += "\n\n"; + memoryContext += `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`; + } + if (mem.shortTerm.length) { + if (memoryContext) memoryContext += "\n\n"; + memoryContext += `[近期对话]\n${mem.shortTerm.map((m) => `${m.role}: ${m.content}`).join("\n")}`; + } + return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`; +} export default router.get("/", async (req, res) => { - const skill = await useSkill( - { - mainSkill: "production_agent_execution", - workspace: ["production_agent_skills/execution"], - attachedSkills: ["art_prompts/chinese_sweet_romance/driector_skills"], - }, - ); - const test = await u.Ai.Text("scriptAgent").invoke({ - system: skill.prompt, - messages: [ - { role: "user", content: "渐进式激活skill,技能->资源1->资源2...一直渐进到最深处,并输出你的阅读路线,同级目录你只用读取一个无需全部读取" }, - ], - tools: skill.tools, - }); + const isolationKey = "test"; + const input = "你好" - console.log("%c Line:21 🌽 text", "background:#ea7e5c", test.text); - res.send(test.text); + const memory = new Memory("productionAgent", isolationKey); + await memory.add("user", input); + + + const mem = buildMemPrompt(await memory.get(input)); + + res.send(mem); }); diff --git a/src/socket/routes/productionAgent.ts b/src/socket/routes/productionAgent.ts index 04abc35..367f7b8 100644 --- a/src/socket/routes/productionAgent.ts +++ b/src/socket/routes/productionAgent.ts @@ -36,12 +36,22 @@ export default (nsp: Namespace) => { console.log("[productionAgent] 已连接:", socket.id); - const resTool = new ResTool(socket, { + let resTool = new ResTool(socket, { projectId: socket.handshake.auth.projectId, scriptId: socket.handshake.auth.scriptId, }); let abortController: AbortController | null = null; + socket.on("updateContext", (data: { isolationKey: string; projectId: number; scriptId: number }, callback) => { + isolationKey = data.isolationKey; + resTool = new ResTool(socket, { + projectId: data.projectId, + scriptId: data.scriptId, + }); + console.log("[productionAgent] 上下文已更新:", isolationKey); + callback?.({ success: true }); + }); + socket.on("chat", async (data: { content: string }) => { const { content } = data; abortController?.abort(); @@ -84,6 +94,7 @@ export default (nsp: Namespace) => { text = currentMsg.text(); }; + let aborted = false; try { for await (const chunk of textStream) { await syncCurrentMessage(); @@ -91,11 +102,21 @@ export default (nsp: Namespace) => { currentContent += chunk; } } catch (err: any) { - if (err.name !== "AbortError") throw err; + if (err.name === "AbortError" || currentController.signal.aborted) { + aborted = true; + } else { + throw err; + } } finally { await syncCurrentMessage(); - text.complete(); - currentMsg.complete(); + if (aborted) { + text.append("[已停止]"); + text.complete(); + currentMsg.stop(); + } else { + text.complete(); + currentMsg.complete(); + } await persistCurrentMessage(); if (abortController === currentController) { abortController = null; diff --git a/src/socket/routes/scriptAgent.ts b/src/socket/routes/scriptAgent.ts index 5867c79..0da1c56 100644 --- a/src/socket/routes/scriptAgent.ts +++ b/src/socket/routes/scriptAgent.ts @@ -83,6 +83,7 @@ export default (nsp: Namespace) => { text = currentMsg.text(); }; + let aborted = false; try { for await (const chunk of textStream) { await syncCurrentMessage(); @@ -90,11 +91,20 @@ export default (nsp: Namespace) => { currentContent += chunk; } } catch (err: any) { - if (err.name !== "AbortError") throw err; + if (err.name === "AbortError" || currentController.signal.aborted) { + aborted = true; + } else { + throw err; + } } finally { await syncCurrentMessage(); - text.complete(); - currentMsg.complete(); + if (aborted) { + text.complete(); + currentMsg.stop(); + } else { + text.complete(); + currentMsg.complete(); + } await persistCurrentMessage(); if (abortController === currentController) { abortController = null; diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 9b42b68..2a3bc57 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,8 +1,4 @@ -<<<<<<< HEAD // @db-hash 93b2462070c45c2b449e9a18c4e88763 -======= -// @db-hash 24748d4ef971381a79c720c846f83847 ->>>>>>> 796947cef173e7fe2f96e21fa8aeae23ff0fdf4a //该文件由脚本自动生成,请勿手动修改 export interface memories { From 44fa7d1b46744207ab9291c27646dc2ac1a36639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ACT=E4=B8=B6=E6=B5=81=E6=98=9F=E9=9B=A8?= <1340145680@qq.com> Date: Mon, 30 Mar 2026 22:14:25 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=9A=E8=AF=AD?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/productionAgent/index.ts | 4 ++-- src/agents/scriptAgent/index.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index e683aec..d64fa90 100644 --- a/src/agents/productionAgent/index.ts +++ b/src/agents/productionAgent/index.ts @@ -135,7 +135,7 @@ function createSubAgent(parentCtx: AgentContext) { const addPrompt = "\n" + [ - "你可以使用如下XML格式写入工作区:\n```", + "你必须使用如下XML格式写入工作区:\n```", "拍摄计划:内容", "分镜表:内容", "```", @@ -168,7 +168,7 @@ function createSubAgent(parentCtx: AgentContext) { const systemPrompt = await fs.promises.readFile(skill, "utf-8"); return runAgent({ prompt, - system: systemPrompt + "你可以使用如下XML格式写入工作区:\n故事骨架内容", + system: systemPrompt + "你必须使用如下XML格式写入工作区:\n故事骨架内容", name: "监制", memoryKey: "assistant:supervision", }); diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index 5542f77..88cf8fc 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -144,7 +144,7 @@ function createSubAgent(parentCtx: AgentContext) { const systemPrompt = await fs.promises.readFile(skill, "utf-8"); return runAgent({ prompt, - system: systemPrompt + "\n你可以使用如下XML格式写入工作区:\n故事骨架内容", + system: systemPrompt + "\n你必须使用如下XML格式写入工作区:\n故事骨架内容", name: "编剧", memoryKey: "assistant:execution:storySkeleton", }); @@ -159,7 +159,7 @@ function createSubAgent(parentCtx: AgentContext) { const systemPrompt = await fs.promises.readFile(skill, "utf-8"); return runAgent({ prompt, - system: systemPrompt + "\n你可以使用如下XML格式写入工作区:\n改编策略内容", + system: systemPrompt + "\n你必须使用如下XML格式写入工作区:\n改编策略内容", name: "编剧", memoryKey: "assistant:execution:adaptationStrategy", }); @@ -176,7 +176,7 @@ function createSubAgent(parentCtx: AgentContext) { prompt, system: systemPrompt + - `\n你可以使用如下XML格式写入工作区:\nXML不得添加任何额外标签`, + `\n你必须使用如下XML格式写入工作区:\nXML不得添加任何额外标签`, name: "编剧", memoryKey: "assistant:execution:script", }); From 06ae21eae184991aec6994f36eabc8622329a00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=B8=85?= <2944435683> Date: Mon, 30 Mar 2026 22:54:44 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=B4=A0=E6=9D=90?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8F=92=E5=85=A5=E7=89=87=E5=B0=BE=E8=A7=86?= =?UTF-8?q?=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/initDB.ts | 1 + src/routes/assets/getMaterialData.ts | 70 ++++++++++++------- .../production/workbench/generateVideo.ts | 1 + src/types/database.d.ts | 16 ++--- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index 5013292..f99bc23 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -526,6 +526,7 @@ description: 专注于从剧本内容中提取所使用的资产(角色、场 table.text("state"); table.integer("scriptId"); table.integer("storyboardId"); + table.integer("projectId"); table.primary(["id"]); table.unique(["id"]); }, diff --git a/src/routes/assets/getMaterialData.ts b/src/routes/assets/getMaterialData.ts index 0623c2a..06e7c64 100644 --- a/src/routes/assets/getMaterialData.ts +++ b/src/routes/assets/getMaterialData.ts @@ -1,32 +1,54 @@ import express from "express"; import u from "@/utils"; +import { z } from "zod"; import { success } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; const router = express.Router(); // 获取生成图片 -export default router.post("/", async (req, res) => { - const list = await u.db("o_assets").leftJoin("o_image", "o_assets.id", "=", "o_image.assetsId").where("o_assets.type", "clip").select("*"); - const data = await Promise.all( - list.map(async (item) => ({ - ...item, - filePath: item.filePath ? await u.oss.getFileUrl(item.filePath) : "", - })), - ); - // 查询o_videoConfig表,拿到已选中的videoId - const configRows = await u.db("o_videoConfig").select("videoId"); - const selectedIds = new Set(configRows.map((row) => row.videoId)); +export default router.post( + "/", + validateFields({ + projectId: z.number(), + }), + async (req, res) => { + const { projectId } = req.body; + const list = await u + .db("o_assets") + .leftJoin("o_image", "o_assets.id", "=", "o_image.assetsId") + .where("o_assets.type", "clip") + .andWhere("projectId", projectId) + .select("*"); + const data = await Promise.all( + list.map(async (item) => ({ + ...item, + filePath: item.filePath ? await u.oss.getFileUrl(item.filePath) : "", + })), + ); + //拿到本地片尾视频并插入到data中 + const ending = await u.oss.getFileUrl("/ending/1d7a2dfdd0c057823797fdf97677a7a0.mp4"); + data.push({ + id: 0, + name: "片尾", + filePath: ending, + type: "clip", + }); + // 查询o_videoConfig表,拿到已选中的videoId + const configRows = await u.db("o_videoConfig").select("videoId"); + const selectedIds = new Set(configRows.map((row) => row.videoId)); - // 查询o_video表 - const videoRows = await u.db("o_video").where("state", "生成成功").select("*"); + // 查询o_video表 + const videoRows = await u.db("o_video").where("state", "生成成功").andWhere("projectId", projectId).select("*"); + // 处理并返回结果 + const video = await Promise.all( + videoRows.map(async (row) => ({ + id: row.id, + filePath: row.filePath ? await u.oss.getFileUrl(row.filePath) : "", + selected: selectedIds.has(row.id), + storyboard: row.storyboardId, + })), + ); - // 处理并返回结果 - const video = await Promise.all( - videoRows.map(async (row) => ({ - id: row.id, - filePath: row.filePath ? await u.oss.getFileUrl(row.filePath) : "", - selected: selectedIds.has(row.id), - storyboard: row.storyboardId, - })), - ); - res.status(200).send(success({ data, video })); -}); + res.status(200).send(success({ data, video })); + }, +); diff --git a/src/routes/production/workbench/generateVideo.ts b/src/routes/production/workbench/generateVideo.ts index eb7582b..bb62d46 100644 --- a/src/routes/production/workbench/generateVideo.ts +++ b/src/routes/production/workbench/generateVideo.ts @@ -39,6 +39,7 @@ export default router.post( state: "生成中", scriptId, storyboardId, + projectId, }; const [videoId] = await u.db("o_video").insert(videoData); //查询分镜是否已有配置 diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 5c95ae8..0aca397 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,13 +1,6 @@ -// @db-hash 24748d4ef971381a79c720c846f83847 +// @db-hash 6be0a80e9c8f541987a4c1e907736237 //该文件由脚本自动生成,请勿手动修改 -export interface _o_script_old_20260327 { - 'content'?: string | null; - 'createTime'?: number | null; - 'id'?: number; - 'name'?: string | null; - 'projectId'?: number | null; -} export interface memories { 'content': string; 'createTime': number; @@ -28,7 +21,7 @@ export interface o_agentDeploy { 'model'?: string | null; 'modelName'?: string | null; 'name'?: string | null; - 'vendorId'?: number | null; + 'vendorId'?: string | null; } export interface o_agentWorkData { 'createTime'?: number | null; @@ -54,6 +47,7 @@ export interface o_assets { 'name'?: string | null; 'projectId'?: number | null; 'prompt'?: string | null; + 'promptState'?: string | null; 'remark'?: string | null; 'scriptId'?: number | null; 'startTime'?: number | null; @@ -173,7 +167,7 @@ export interface o_storyboard { 'filePath'?: string | null; 'frameMode'?: string | null; 'id'?: number; - 'index'?: string | null; + 'index'?: number | null; 'lines'?: string | null; 'mode'?: string | null; 'model'?: string | null; @@ -218,6 +212,7 @@ export interface o_video { 'errorReason'?: string | null; 'filePath'?: string | null; 'id'?: number; + 'projectId'?: number | null; 'scriptId'?: number | null; 'state'?: string | null; 'storyboardId'?: number | null; @@ -239,7 +234,6 @@ export interface o_videoConfig { } export interface DB { - "_o_script_old_20260327": _o_script_old_20260327; "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData;