Merge branch '108' of https://github.com/HBAI-Ltd/Toonflow-app into 108
# Conflicts: # src/router.ts
This commit is contained in:
commit
7516870104
95
data/skills/references/script_assets_extract.md
Normal file
95
data/skills/references/script_assets_extract.md
Normal file
@ -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 图片生成
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 资产列表中**不要包含剧本内容本身**,仅提取所使用到的资产
|
||||||
|
- 角色的随身物品如果有独立剧情功能,应单独作为道具提取
|
||||||
|
- 场景中的固定陈设不需要单独提取为道具,除非该物件有独立剧情作用
|
||||||
@ -44,6 +44,13 @@ description: 通用文本分析与内容提取 Agent,支持小说事件提取
|
|||||||
- **资产类型**:`tool`(对应 `o_assets.type = "tool"`)
|
- **资产类型**:`tool`(对应 `o_assets.type = "tool"`)
|
||||||
- **输出**:结构化道具资产表(道具名称、类别、外观描述、尺寸参考、材质质感、功能/用途、首次出场、关联角色、状态变体)+ 高频道具排名
|
- **输出**:结构化道具资产表(道具名称、类别、外观描述、尺寸参考、材质质感、功能/用途、首次出场、关联角色、状态变体)+ 高频道具排名
|
||||||
|
|
||||||
|
### 6. 剧本资产提取(script_assets_extract)
|
||||||
|
|
||||||
|
- **触发条件**:用户提供剧本内容,要求从剧本中提取所使用的资产(角色、场景、道具、素材片段)
|
||||||
|
- **参考文件**:`references/script_assets_extract.md`
|
||||||
|
- **资产类型**:`role`、`scene`、`tool`、`clip`(对应 `o_assets.type`)
|
||||||
|
- **输出**:结构化资产列表(资产名称、资产描述、生成提示词、资产类型),通过 `resultTool` 工具调用返回
|
||||||
|
|
||||||
## 资产提取分工说明
|
## 资产提取分工说明
|
||||||
|
|
||||||
当用户要求从小说中提取"所有资产"或"角色场景道具"时,三个资产提取技能应按以下分工协作:
|
当用户要求从小说中提取"所有资产"或"角色场景道具"时,三个资产提取技能应按以下分工协作:
|
||||||
|
|||||||
@ -123,6 +123,7 @@ function runSubAgent(parentCtx: AgentContext) {
|
|||||||
prompt: z.string().max(100).describe("交给子Agent的任务简约描述"),
|
prompt: z.string().max(100).describe("交给子Agent的任务简约描述"),
|
||||||
}),
|
}),
|
||||||
execute: async ({ agent, prompt }) => {
|
execute: async ({ agent, prompt }) => {
|
||||||
|
//todo 传入md有问题
|
||||||
const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)];
|
const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)];
|
||||||
//运行子Agent
|
//运行子Agent
|
||||||
const subTextStream = await fn({ ...parentCtx, text: prompt });
|
const subTextStream = await fn({ ...parentCtx, text: prompt });
|
||||||
|
|||||||
@ -46,7 +46,8 @@ export async function decisionAI(ctx: AgentContext) {
|
|||||||
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
|
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
|
||||||
|
|
||||||
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first();
|
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");
|
||||||
|
console.log("%c Line:50 🥒 novelData", "background:#2eafb0", novelData);
|
||||||
|
|
||||||
const projectInfo = [
|
const projectInfo = [
|
||||||
"## 项目信息",
|
"## 项目信息",
|
||||||
@ -57,7 +58,7 @@ export async function decisionAI(ctx: AgentContext) {
|
|||||||
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
|
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
|
||||||
].join("\n");
|
].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({
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
system: prefixSystem + systemPrompt,
|
system: prefixSystem + systemPrompt,
|
||||||
@ -70,6 +71,7 @@ export async function decisionAI(ctx: AgentContext) {
|
|||||||
...useTools(ctx.resTool),
|
...useTools(ctx.resTool),
|
||||||
},
|
},
|
||||||
onFinish: async (completion) => {
|
onFinish: async (completion) => {
|
||||||
|
console.log("%c Line:73 🍧 completion", "background:#93c0a4", completion);
|
||||||
await memory.add("assistant:decision", completion.text);
|
await memory.add("assistant:decision", completion.text);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -99,6 +101,7 @@ export async function executionAI(ctx: AgentContext) {
|
|||||||
...useTools(ctx.resTool),
|
...useTools(ctx.resTool),
|
||||||
},
|
},
|
||||||
onFinish: async (completion) => {
|
onFinish: async (completion) => {
|
||||||
|
console.log("%c Line:102 🍻 completion", "background:#fca650", completion);
|
||||||
await memory.add("assistant:execution", completion.text);
|
await memory.add("assistant:execution", completion.text);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -125,6 +128,7 @@ export async function supervisionAI(ctx: AgentContext) {
|
|||||||
...useTools(ctx.resTool),
|
...useTools(ctx.resTool),
|
||||||
},
|
},
|
||||||
onFinish: async (completion) => {
|
onFinish: async (completion) => {
|
||||||
|
console.log("%c Line:129 🍣 completion", "background:#3f7cff", completion);
|
||||||
await memory.add("assistant:supervision", completion.text);
|
await memory.add("assistant:supervision", completion.text);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const AssetSchema = z.object({
|
|||||||
type: z.enum(["role", "tool", "scene", "clip"]).describe("资产类型"),
|
type: z.enum(["role", "tool", "scene", "clip"]).describe("资产类型"),
|
||||||
});
|
});
|
||||||
export const ScriptSchema = z.object({
|
export const ScriptSchema = z.object({
|
||||||
id: z.number().describe("剧本ID,如果新增则为空").optional(),
|
id: z.number().describe("剧本ID"),
|
||||||
name: z.string().describe("剧本名称"),
|
name: z.string().describe("剧本名称"),
|
||||||
content: z.string().describe("剧本内容"),
|
content: z.string().describe("剧本内容"),
|
||||||
});
|
});
|
||||||
@ -35,13 +35,14 @@ export default (resTool: ResTool, toolsNames?: string[]) => {
|
|||||||
get_novel_events: tool({
|
get_novel_events: tool({
|
||||||
description: "获取章节事件",
|
description: "获取章节事件",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
ids: z.array(z.number()).describe("章节id"),
|
ids: z.array(z.number()).describe("章节id,注意区分"),
|
||||||
}),
|
}),
|
||||||
execute: async ({ ids }) => {
|
execute: async ({ ids }) => {
|
||||||
resTool.systemMessage(`正在阅读 章节事件 数据...`);
|
resTool.systemMessage(`正在阅读 章节事件 数据...`);
|
||||||
console.log("[tools] get_novel_events", ids);
|
console.log("[tools] get_novel_events", ids);
|
||||||
const data = await u
|
const data = await u
|
||||||
.db("o_novel")
|
.db("o_novel")
|
||||||
|
.where("projectId", resTool.data.projectId)
|
||||||
.select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState")
|
.select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState")
|
||||||
.whereIn("id", ids);
|
.whereIn("id", ids);
|
||||||
const eventString = data.map((i: any) => [`第${i.index}章,标题:${i.chapter},事件:${i.event}`].join("\n")).join("\n");
|
const eventString = data.map((i: any) => [`第${i.index}章,标题:${i.chapter},事件:${i.event}`].join("\n")).join("\n");
|
||||||
@ -90,41 +91,48 @@ export default (resTool: ResTool, toolsNames?: string[]) => {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
insert_script_to_sqlite: tool({
|
update_script_to_sqlite: tool({
|
||||||
description: "将剧本内容插入sqlite数据库,供后续业务使用",
|
description: "更新剧本,修改数据库对应剧本,供后续业务使用",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
script: ScriptSchema,
|
script: ScriptSchema,
|
||||||
// assetsList: z.array(AssetSchema).describe("剧本所使用资产列表,注意不要包含剧本内容,仅为所使用到的 道具、人物、场景、素材"),
|
|
||||||
}),
|
}),
|
||||||
execute: async ({ script }) => {
|
execute: async ({ script }) => {
|
||||||
console.log("%c Line:103 🍷 script", "background:#42b983", 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({
|
const [scriptId] = await u.db("o_script").insert({
|
||||||
name: script.name,
|
name: script.name,
|
||||||
content: script.content,
|
content: script.content,
|
||||||
projectId: resTool.data.projectId,
|
projectId: resTool.data.projectId,
|
||||||
createTime: Date.now(),
|
createTime: Date.now(),
|
||||||
});
|
});
|
||||||
// if (assetsList && assetsList.length) {
|
socket.emit("setPlanData", { key: "script", value: scriptId });
|
||||||
// const assetId = [];
|
return true;
|
||||||
// for (const i of assetsList) {
|
},
|
||||||
// if (i.id) {
|
}),
|
||||||
// assetId.push(i.id);
|
delete_script_to_sqlite: tool({
|
||||||
// continue;
|
description: "删除剧本,将剧本内容从sqlite数据库中删除",
|
||||||
// }
|
inputSchema: z.object({
|
||||||
// const [id] = await u.db("o_assets").insert({
|
scriptId: z.string().describe("剧本id"),
|
||||||
// name: i.name,
|
}),
|
||||||
// prompt: i.prompt,
|
execute: async ({ scriptId }) => {
|
||||||
// type: i.type,
|
console.log("[tools] delete_script_to_sqlite", scriptId);
|
||||||
// describe: i.desc,
|
await u.db("o_script").where({ id: scriptId }).delete();
|
||||||
// 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 });
|
socket.emit("setPlanData", { key: "script", value: scriptId });
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// @routes-hash 8dc4df496e2771e0081f32f4d8b2c2ae
|
// @routes-hash 557dfd43a824a4bd4170d0e2c9a6b45c
|
||||||
import { Express } from "express";
|
import { Express } from "express";
|
||||||
|
|
||||||
import route1 from "./routes/agents/clearMemory";
|
import route1 from "./routes/agents/clearMemory";
|
||||||
@ -71,30 +71,30 @@ import route67 from "./routes/script/getScrptApi";
|
|||||||
import route68 from "./routes/script/updateScript";
|
import route68 from "./routes/script/updateScript";
|
||||||
import route69 from "./routes/scriptAgent/getPlanData";
|
import route69 from "./routes/scriptAgent/getPlanData";
|
||||||
import route70 from "./routes/scriptAgent/setPlanData";
|
import route70 from "./routes/scriptAgent/setPlanData";
|
||||||
import route71 from "./routes/setting/about/checkUpdate";
|
import route71 from "./routes/setting/agentDeploy/agentSetKey";
|
||||||
import route72 from "./routes/setting/agentDeploy/agentSetKey";
|
import route72 from "./routes/setting/agentDeploy/deployAgentModel";
|
||||||
import route73 from "./routes/setting/agentDeploy/deployAgentModel";
|
import route73 from "./routes/setting/agentDeploy/getAgentDeploy";
|
||||||
import route74 from "./routes/setting/agentDeploy/getAgentDeploy";
|
import route74 from "./routes/setting/dbConfig/clearData";
|
||||||
import route75 from "./routes/setting/dbConfig/clearData";
|
import route75 from "./routes/setting/fileManagement/openFolder";
|
||||||
import route76 from "./routes/setting/fileManagement/openFolder";
|
import route76 from "./routes/setting/getTextModel";
|
||||||
import route77 from "./routes/setting/getTextModel";
|
import route77 from "./routes/setting/loginConfig/getUser";
|
||||||
import route78 from "./routes/setting/loginConfig/getUser";
|
import route78 from "./routes/setting/loginConfig/updateUserPwd";
|
||||||
import route79 from "./routes/setting/loginConfig/updateUserPwd";
|
import route79 from "./routes/setting/memoryConfig/delAllMemory";
|
||||||
import route80 from "./routes/setting/memoryConfig/delAllMemory";
|
import route80 from "./routes/setting/memoryConfig/getMemory";
|
||||||
import route81 from "./routes/setting/memoryConfig/getMemory";
|
import route81 from "./routes/setting/memoryConfig/sureMemory";
|
||||||
import route82 from "./routes/setting/memoryConfig/sureMemory";
|
import route82 from "./routes/setting/skillManagement/addSkill";
|
||||||
import route83 from "./routes/setting/skillManagement/addSkill";
|
import route83 from "./routes/setting/skillManagement/deleteSkill";
|
||||||
import route84 from "./routes/setting/skillManagement/deleteSkill";
|
import route84 from "./routes/setting/skillManagement/embeddingSkill";
|
||||||
import route85 from "./routes/setting/skillManagement/embeddingSkill";
|
import route85 from "./routes/setting/skillManagement/generateDescription";
|
||||||
import route86 from "./routes/setting/skillManagement/generateDescription";
|
import route86 from "./routes/setting/skillManagement/getSkillList";
|
||||||
import route87 from "./routes/setting/skillManagement/getSkillList";
|
import route87 from "./routes/setting/skillManagement/scanSkills";
|
||||||
import route88 from "./routes/setting/skillManagement/scanSkills";
|
import route88 from "./routes/setting/skillManagement/updateSkill";
|
||||||
import route89 from "./routes/setting/skillManagement/updateSkill";
|
import route89 from "./routes/setting/vendorConfig/addVendor";
|
||||||
import route90 from "./routes/setting/vendorConfig/addVendor";
|
import route90 from "./routes/setting/vendorConfig/deleteVendor";
|
||||||
import route91 from "./routes/setting/vendorConfig/deleteVendor";
|
import route91 from "./routes/setting/vendorConfig/getVendorList";
|
||||||
import route92 from "./routes/setting/vendorConfig/getVendorList";
|
import route92 from "./routes/setting/vendorConfig/modelTest";
|
||||||
import route93 from "./routes/setting/vendorConfig/modelTest";
|
import route93 from "./routes/setting/vendorConfig/updateVendor";
|
||||||
import route94 from "./routes/setting/vendorConfig/updateVendor";
|
import route94 from "./routes/task/getProject";
|
||||||
import route95 from "./routes/task/getTaskApi";
|
import route95 from "./routes/task/getTaskApi";
|
||||||
import route96 from "./routes/task/getTaskCategories";
|
import route96 from "./routes/task/getTaskCategories";
|
||||||
import route97 from "./routes/task/taskDetails";
|
import route97 from "./routes/task/taskDetails";
|
||||||
@ -171,30 +171,30 @@ export default async (app: Express) => {
|
|||||||
app.use("/api/script/updateScript", route68);
|
app.use("/api/script/updateScript", route68);
|
||||||
app.use("/api/scriptAgent/getPlanData", route69);
|
app.use("/api/scriptAgent/getPlanData", route69);
|
||||||
app.use("/api/scriptAgent/setPlanData", route70);
|
app.use("/api/scriptAgent/setPlanData", route70);
|
||||||
app.use("/api/setting/about/checkUpdate", route71);
|
app.use("/api/setting/agentDeploy/agentSetKey", route71);
|
||||||
app.use("/api/setting/agentDeploy/agentSetKey", route72);
|
app.use("/api/setting/agentDeploy/deployAgentModel", route72);
|
||||||
app.use("/api/setting/agentDeploy/deployAgentModel", route73);
|
app.use("/api/setting/agentDeploy/getAgentDeploy", route73);
|
||||||
app.use("/api/setting/agentDeploy/getAgentDeploy", route74);
|
app.use("/api/setting/dbConfig/clearData", route74);
|
||||||
app.use("/api/setting/dbConfig/clearData", route75);
|
app.use("/api/setting/fileManagement/openFolder", route75);
|
||||||
app.use("/api/setting/fileManagement/openFolder", route76);
|
app.use("/api/setting/getTextModel", route76);
|
||||||
app.use("/api/setting/getTextModel", route77);
|
app.use("/api/setting/loginConfig/getUser", route77);
|
||||||
app.use("/api/setting/loginConfig/getUser", route78);
|
app.use("/api/setting/loginConfig/updateUserPwd", route78);
|
||||||
app.use("/api/setting/loginConfig/updateUserPwd", route79);
|
app.use("/api/setting/memoryConfig/delAllMemory", route79);
|
||||||
app.use("/api/setting/memoryConfig/delAllMemory", route80);
|
app.use("/api/setting/memoryConfig/getMemory", route80);
|
||||||
app.use("/api/setting/memoryConfig/getMemory", route81);
|
app.use("/api/setting/memoryConfig/sureMemory", route81);
|
||||||
app.use("/api/setting/memoryConfig/sureMemory", route82);
|
app.use("/api/setting/skillManagement/addSkill", route82);
|
||||||
app.use("/api/setting/skillManagement/addSkill", route83);
|
app.use("/api/setting/skillManagement/deleteSkill", route83);
|
||||||
app.use("/api/setting/skillManagement/deleteSkill", route84);
|
app.use("/api/setting/skillManagement/embeddingSkill", route84);
|
||||||
app.use("/api/setting/skillManagement/embeddingSkill", route85);
|
app.use("/api/setting/skillManagement/generateDescription", route85);
|
||||||
app.use("/api/setting/skillManagement/generateDescription", route86);
|
app.use("/api/setting/skillManagement/getSkillList", route86);
|
||||||
app.use("/api/setting/skillManagement/getSkillList", route87);
|
app.use("/api/setting/skillManagement/scanSkills", route87);
|
||||||
app.use("/api/setting/skillManagement/scanSkills", route88);
|
app.use("/api/setting/skillManagement/updateSkill", route88);
|
||||||
app.use("/api/setting/skillManagement/updateSkill", route89);
|
app.use("/api/setting/vendorConfig/addVendor", route89);
|
||||||
app.use("/api/setting/vendorConfig/addVendor", route90);
|
app.use("/api/setting/vendorConfig/deleteVendor", route90);
|
||||||
app.use("/api/setting/vendorConfig/deleteVendor", route91);
|
app.use("/api/setting/vendorConfig/getVendorList", route91);
|
||||||
app.use("/api/setting/vendorConfig/getVendorList", route92);
|
app.use("/api/setting/vendorConfig/modelTest", route92);
|
||||||
app.use("/api/setting/vendorConfig/modelTest", route93);
|
app.use("/api/setting/vendorConfig/updateVendor", route93);
|
||||||
app.use("/api/setting/vendorConfig/updateVendor", route94);
|
app.use("/api/task/getProject", route94);
|
||||||
app.use("/api/task/getTaskApi", route95);
|
app.use("/api/task/getTaskApi", route95);
|
||||||
app.use("/api/task/getTaskCategories", route96);
|
app.use("/api/task/getTaskCategories", route96);
|
||||||
app.use("/api/task/taskDetails", route97);
|
app.use("/api/task/taskDetails", route97);
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export default router.post(
|
|||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { assetsId } = req.body;
|
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");
|
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 = {
|
const data = {
|
||||||
id: assets!.id,
|
id: assets!.id,
|
||||||
state: assets!.state,
|
|
||||||
imageId: assets!.imageId ?? null,
|
imageId: assets!.imageId ?? null,
|
||||||
tempAssets,
|
tempAssets,
|
||||||
};
|
};
|
||||||
|
console.log("%c Line:30 🥤 data", "background:#465975", data);
|
||||||
res.status(200).send(success(data));
|
res.status(200).send(success(data));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -122,6 +122,7 @@ export default router.post(
|
|||||||
messages: [{ role: "user", content: "小说原文" + novelText }],
|
messages: [{ role: "user", content: "小说原文" + novelText }],
|
||||||
tools: skill.tools,
|
tools: skill.tools,
|
||||||
})) as any;
|
})) as any;
|
||||||
|
|
||||||
if (!_output) return res.status(500).send("失败");
|
if (!_output) return res.status(500).send("失败");
|
||||||
await u.db("o_assets").where("id", assetsId).update({ prompt: _output });
|
await u.db("o_assets").where("id", assetsId).update({ prompt: _output });
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,13 @@ export default router.post(
|
|||||||
data: flowDataSchema,
|
data: flowDataSchema,
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
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();
|
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) {
|
if (!sqlData) {
|
||||||
await u.db("o_agentWorkData").insert({
|
await u.db("o_agentWorkData").insert({
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@ -7,13 +7,13 @@ const router = express.Router();
|
|||||||
|
|
||||||
// 删除剧本
|
// 删除剧本
|
||||||
export default router.post(
|
export default router.post(
|
||||||
"/",
|
"/",
|
||||||
validateFields({
|
validateFields({
|
||||||
id: z.number(),
|
id: z.array(z.number()),
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
await u.db("o_script").where({ id }).delete();
|
await u.db("o_script").whereIn("id", id).delete();
|
||||||
res.status(200).send(success({ message: "删除剧本成功" }));
|
res.status(200).send(success({ message: "删除剧本成功" }));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,30 +5,88 @@ import { error, success } from "@/lib/responseFormat";
|
|||||||
import compressing from "compressing";
|
import compressing from "compressing";
|
||||||
import { validateFields } from "@/middleware/middleware";
|
import { validateFields } from "@/middleware/middleware";
|
||||||
import { useSkill } from "@/utils/agent/skillsTools";
|
import { useSkill } from "@/utils/agent/skillsTools";
|
||||||
|
import { Output, tool } from "ai";
|
||||||
|
|
||||||
const router = express.Router();
|
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(
|
export default router.post(
|
||||||
"/",
|
"/",
|
||||||
validateFields({
|
validateFields({
|
||||||
scriptIds: z.array(z.number()),
|
scriptIds: z.array(z.number()),
|
||||||
|
projectId: z.number(),
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { scriptIds } = req.body;
|
const { scriptIds, projectId } = req.body;
|
||||||
if (!scriptIds.length) return res.status(400).send(error("请先选择剧本"));
|
if (!scriptIds.length) return res.status(400).send(error("请先选择剧本"));
|
||||||
const scripts = await u.db("o_script").whereIn("id", scriptIds);
|
const scripts = await u.db("o_script").whereIn("id", scriptIds);
|
||||||
const intansce = u.Ai.Text("universalAgent");
|
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({
|
async function getAssets() {
|
||||||
system: skill.prompt,
|
return await u.db("o_assets").where("projectId", projectId).select("id", "name");
|
||||||
messages: [
|
}
|
||||||
{
|
for (const scriptId of scriptIds) {
|
||||||
role: "user",
|
const resultTool = tool({
|
||||||
content: "请根据以下小说章节生成事件摘要:\n",
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,11 +13,11 @@ export default router.post(
|
|||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { projectId, agentType } = req.body;
|
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) {
|
if (!data) {
|
||||||
await u.db("o_agentWorkData").insert({
|
await u.db("o_agentWorkData").insert({
|
||||||
id: projectId,
|
projectId: projectId,
|
||||||
key: agentType,
|
key: agentType,
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
storySkeleton: "",
|
storySkeleton: "",
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export default router.post(
|
|||||||
const { projectId, agentType, data } = req.body;
|
const { projectId, agentType, data } = req.body;
|
||||||
await u
|
await u
|
||||||
.db("o_agentWorkData")
|
.db("o_agentWorkData")
|
||||||
.where({ id: projectId, key: agentType })
|
.where({ projectId: projectId, key: agentType })
|
||||||
.update({
|
.update({
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
|||||||
10
src/routes/task/getProject.ts
Normal file
10
src/routes/task/getProject.ts
Normal file
@ -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));
|
||||||
|
});
|
||||||
@ -9,11 +9,12 @@ export default router.post(
|
|||||||
validateFields({
|
validateFields({
|
||||||
state: z.string().optional().nullable(),
|
state: z.string().optional().nullable(),
|
||||||
taskClass: z.string().optional().nullable(),
|
taskClass: z.string().optional().nullable(),
|
||||||
|
projectId: z.number().optional().nullable(),
|
||||||
page: z.number(),
|
page: z.number(),
|
||||||
limit: z.number(),
|
limit: z.number(),
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
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 offset = (page - 1) * limit;
|
||||||
const data = await u
|
const data = await u
|
||||||
.db("o_tasks")
|
.db("o_tasks")
|
||||||
@ -25,6 +26,9 @@ export default router.post(
|
|||||||
if (state) {
|
if (state) {
|
||||||
qb.andWhere("o_tasks.state", state);
|
qb.andWhere("o_tasks.state", state);
|
||||||
}
|
}
|
||||||
|
if (projectId) {
|
||||||
|
qb.andWhere("o_tasks.projectId", projectId);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.select("o_tasks.*", "o_project.* ")
|
.select("o_tasks.*", "o_project.* ")
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
@ -36,6 +40,9 @@ export default router.post(
|
|||||||
if (taskClass) {
|
if (taskClass) {
|
||||||
qb.andWhere("o_tasks.taskClass", taskClass);
|
qb.andWhere("o_tasks.taskClass", taskClass);
|
||||||
}
|
}
|
||||||
|
if (projectId) {
|
||||||
|
qb.andWhere("o_tasks.projectId", projectId);
|
||||||
|
}
|
||||||
if (state) {
|
if (state) {
|
||||||
qb.andWhere("o_tasks.state", state);
|
qb.andWhere("o_tasks.state", state);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,10 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
import { success } from "@/lib/responseFormat";
|
import { success } from "@/lib/responseFormat";
|
||||||
import { validateFields } from "@/middleware/middleware";
|
|
||||||
import { number, z } from "zod";
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
export default router.post(
|
export default router.post("/", async (req, res) => {
|
||||||
"/",
|
const list = await u.db("o_tasks").select("taskClass").groupBy("taskClass");
|
||||||
validateFields({
|
const data = list.filter((item) => item.taskClass);
|
||||||
projectId: z.number(),
|
res.status(200).send(success(data));
|
||||||
}),
|
});
|
||||||
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));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
34
src/types/database.d.ts
vendored
34
src/types/database.d.ts
vendored
@ -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 {
|
export interface memories {
|
||||||
'content': string;
|
'content': string;
|
||||||
'createTime': number;
|
'createTime': number;
|
||||||
@ -220,6 +250,8 @@ export interface o_videoConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DB {
|
export interface DB {
|
||||||
|
"_o_project_old_20260326": _o_project_old_20260326;
|
||||||
|
"_o_storyboard_old_20260325": _o_storyboard_old_20260325;
|
||||||
"memories": memories;
|
"memories": memories;
|
||||||
"o_agentDeploy": o_agentDeploy;
|
"o_agentDeploy": o_agentDeploy;
|
||||||
"o_agentWorkData": o_agentWorkData;
|
"o_agentWorkData": o_agentWorkData;
|
||||||
|
|||||||
@ -137,8 +137,6 @@ class AiVideo {
|
|||||||
async run(input: VideoConfig) {
|
async run(input: VideoConfig) {
|
||||||
return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => {
|
return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => {
|
||||||
const fn = await getVendorTemplateFn("videoRequest", modelName);
|
const fn = await getVendorTemplateFn("videoRequest", modelName);
|
||||||
|
|
||||||
console.log("%c Line:142 🎂 input", "background:#42b983", input);
|
|
||||||
this.result = await fn(input);
|
this.result = await fn(input);
|
||||||
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
|
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@ -16,46 +16,68 @@ export interface EventType {
|
|||||||
|
|
||||||
class CleanNovel {
|
class CleanNovel {
|
||||||
emitter: EventEmitter;
|
emitter: EventEmitter;
|
||||||
constructor() {
|
/** 最大并发数 */
|
||||||
|
concurrency: number;
|
||||||
|
|
||||||
|
constructor(concurrency: number = 5) {
|
||||||
this.emitter = new EventEmitter();
|
this.emitter = new EventEmitter();
|
||||||
|
this.concurrency = concurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async processChapter(novel: o_novel, intansce: ReturnType<typeof u.Ai.Text>): Promise<EventType | null> {
|
||||||
|
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<EventType[]> {
|
async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> {
|
||||||
//所有事件
|
const totalEvent: EventType[] = [];
|
||||||
let totalEvent: EventType[] = [];
|
|
||||||
const intansce = u.Ai.Text("universalAgent");
|
const intansce = u.Ai.Text("universalAgent");
|
||||||
|
|
||||||
try {
|
// 并发控制:通过信号量限制同时执行的任务数
|
||||||
for (let gi = 0; gi < allChapters.length; gi++) {
|
let running = 0;
|
||||||
const novel = allChapters[gi];
|
let index = 0;
|
||||||
let resData;
|
const results: Promise<void>[] = [];
|
||||||
try {
|
|
||||||
const skill = await useSkill("universal_agent.md");
|
|
||||||
|
|
||||||
resData = await intansce.invoke({
|
const runNext = (): Promise<void> => {
|
||||||
system: skill.prompt,
|
if (index >= allChapters.length) return Promise.resolve();
|
||||||
messages: [
|
const novel = allChapters[index++];
|
||||||
{
|
running++;
|
||||||
role: "user",
|
|
||||||
content: "请根据以下小说章节生成事件摘要:\n" + novel.chapterData!,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tools: skill.tools,
|
|
||||||
});
|
|
||||||
console.log("%c Line:35 🍆 resData", "background:#fca650", resData);
|
|
||||||
|
|
||||||
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;
|
return totalEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
106
src/utils/stripThink.ts
Normal file
106
src/utils/stripThink.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* 去除深度思考模型输出的 <think>...</think> 标签及其内容
|
||||||
|
*
|
||||||
|
* 1. stripThink(text) — 用于非流式,直接去除完整文本中的 <think> 块
|
||||||
|
* 2. createThinkStreamFilter() — 用于流式,返回有状态的过滤器,逐 chunk 过滤
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非流式:去除完整文本中的 <think>...</think>
|
||||||
|
*/
|
||||||
|
export function stripThink(text: string): string {
|
||||||
|
return text.replace(/<think>[\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) {
|
||||||
|
// 正在 <think> 内部,寻找 </think>
|
||||||
|
const closeIdx = chunk.indexOf("</think>", i);
|
||||||
|
if (closeIdx !== -1) {
|
||||||
|
// 找到闭合标签,跳过标签内容
|
||||||
|
insideThink = false;
|
||||||
|
i = closeIdx + "</think>".length;
|
||||||
|
} else {
|
||||||
|
// 整个剩余 chunk 都在 think 内,全部丢弃
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 不在 <think> 内部
|
||||||
|
const openIdx = chunk.indexOf("<think>", i);
|
||||||
|
if (openIdx !== -1) {
|
||||||
|
// 找到开启标签,输出标签之前的内容
|
||||||
|
output += buffer + chunk.slice(i, openIdx);
|
||||||
|
buffer = "";
|
||||||
|
insideThink = true;
|
||||||
|
i = openIdx + "<think>".length;
|
||||||
|
} else {
|
||||||
|
// 没有发现 <think>,但可能 chunk 末尾是不完整的 "<thi..."
|
||||||
|
// 缓冲末尾可能是 "<" 开头的不完整标签片段
|
||||||
|
const potentialStart = findPartialTag(chunk, i);
|
||||||
|
if (potentialStart !== -1) {
|
||||||
|
output += buffer + chunk.slice(i, potentialStart);
|
||||||
|
buffer = chunk.slice(potentialStart);
|
||||||
|
} else {
|
||||||
|
output += buffer + chunk.slice(i);
|
||||||
|
buffer = "";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流结束时调用,刷出缓冲区中残留的内容
|
||||||
|
*/
|
||||||
|
flush(): string {
|
||||||
|
const remaining = buffer;
|
||||||
|
buffer = "";
|
||||||
|
return remaining;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 chunk[startIdx..] 的末尾是否包含 "<think>" 的不完整前缀
|
||||||
|
* 如 "<", "<t", "<th", "<thi", "<thin", "<think"
|
||||||
|
* 返回不完整前缀的起始位置,未找到则返回 -1
|
||||||
|
*/
|
||||||
|
function findPartialTag(chunk: string, startIdx: number): number {
|
||||||
|
const tag = "<think>";
|
||||||
|
// 只需检查末尾最多 tag.length - 1 个字符
|
||||||
|
const searchStart = Math.max(startIdx, chunk.length - (tag.length - 1));
|
||||||
|
for (let i = searchStart; i < chunk.length; i++) {
|
||||||
|
const remaining = chunk.slice(i);
|
||||||
|
if (tag.startsWith(remaining)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
@ -44,7 +44,6 @@ export default function runCode(code: string) {
|
|||||||
|
|
||||||
return exports as Record<string, any>;
|
return exports as Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 压缩图片,目标字节数不高于 size
|
* 压缩图片,目标字节数不高于 size
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user