diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index 992bc16..174fb8f 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -11,8 +11,10 @@ export interface AgentContext { socket: Socket; isolationKey: string; text: string; + userMessageTime?: number; abortSignal?: AbortSignal; resTool: ResTool; + msg: ReturnType; } function buildSystemPrompt(skillPrompt: string, mem: Awaited>): string { @@ -35,19 +37,16 @@ function buildSystemPrompt(skillPrompt: string, mem: Awaited { - await memory.add("assistant:decision", completion.text); - }, }); return textStream; @@ -82,9 +78,6 @@ export async function decisionAI(ctx: AgentContext) { export async function executionAI(ctx: AgentContext) { const { isolationKey, text, abortSignal, resTool } = ctx; - - resTool.systemMessage("执行层AI 接管聊天"); - const memory = new Memory("scriptAgent", isolationKey); const [skill, mem] = await Promise.all([useSkill("script_agent_execution.md"), memory.get(text)]); @@ -99,9 +92,6 @@ export async function executionAI(ctx: AgentContext) { ...memory.getTools(), ...useTools(ctx.resTool), }, - onFinish: async (completion) => { - await memory.add("assistant:execution", completion.text); - }, }); return textStream; @@ -110,8 +100,6 @@ export async function executionAI(ctx: AgentContext) { export async function supervisionAI(ctx: AgentContext) { const { isolationKey, text, abortSignal, resTool } = ctx; - resTool.systemMessage("监督层AI 接管聊天"); - const memory = new Memory("scriptAgent", isolationKey); const [skill, mem] = await Promise.all([useSkill("script_agent_supervision.md"), memory.get(text)]); @@ -125,9 +113,6 @@ export async function supervisionAI(ctx: AgentContext) { ...skill.tools, ...useTools(ctx.resTool), }, - onFinish: async (completion) => { - await memory.add("assistant:supervision", completion.text); - }, }); return textStream; @@ -135,6 +120,7 @@ export async function supervisionAI(ctx: AgentContext) { //工具函数 function runSubAgent(parentCtx: AgentContext) { + const memory = new Memory("scriptAgent", parentCtx.isolationKey); return tool({ description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", inputSchema: z.object({ @@ -143,17 +129,30 @@ function runSubAgent(parentCtx: AgentContext) { }), execute: async ({ agent, prompt }) => { const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; - //运行子Agent + + const subMsg = parentCtx.resTool.newMessage("assistant", agent == "executionAI" ? "编剧" : "编辑"); + + // 先完成主Agent当前的消息 + parentCtx.msg.complete(); + // 子Agent用新消息回复 const subTextStream = await fn({ ...parentCtx, text: prompt }); - - let msg = parentCtx.resTool.textMessage(); + let text = subMsg.text(); let fullResponse = ""; - for await (const chunk of subTextStream) { - msg.send(chunk); + text.append(chunk); fullResponse += chunk; } - msg!.end(); + text.complete(); + subMsg.complete(); + if (fullResponse.trim()) { + await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { + name: agent === "executionAI" ? "编剧" : "编辑", + createTime: new Date(subMsg.datetime).getTime(), + }); + } + + // 为主Agent后续输出创建新消息 + parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹"); return fullResponse; }, diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index d375a2d..e3bc2f6 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -38,7 +38,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { ids: z.array(z.number()).describe("章节id,注意区分"), }), execute: async ({ ids }) => { - resTool.systemMessage(`正在阅读 章节事件 数据...`); + resTool.newMessage('system').text(`正在获取章节事件,章节ID:${ids.join(",")}`).complete(); console.log("[tools] get_novel_events", ids); const data = await u .db("o_novel") @@ -55,7 +55,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { key: keySchema.describe("数据key"), }), execute: async ({ key }) => { - resTool.systemMessage(`正在阅读 ${planDataKeyLabels[key]} 数据...`); + resTool.newMessage('system').text(`正在阅读 ${planDataKeyLabels[key]} 数据...`).complete(); console.log("[tools] get_planData", key); const planData: planData = await new Promise((resolve) => socket.emit("getPlanData", { key }, (res: any) => resolve(res))); return planData[key]; @@ -77,7 +77,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { inputSchema: z.object({ value: planData.shape.storySkeleton }), execute: async ({ value }) => { console.log("[tools] set_planData storySkeleton", value); - resTool.systemMessage("正在保存 故事骨架 数据"); + resTool.newMessage('system').text("正在保存 故事骨架 数据").complete(); socket.emit("setPlanData", { key: "storySkeleton", value }); return true; }, @@ -87,7 +87,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => { inputSchema: z.object({ value: planData.shape.adaptationStrategy }), execute: async ({ value }) => { console.log("[tools] set_planData adaptationStrategy", value); - resTool.systemMessage("正在保存 改编策略 数据"); + resTool.newMessage('system').text("正在保存 改编策略 数据").complete(); socket.emit("setPlanData", { key: "adaptationStrategy", value }); return true; }, diff --git a/src/lib/fixDB.ts b/src/lib/fixDB.ts index b0ba75d..0da1cd3 100644 --- a/src/lib/fixDB.ts +++ b/src/lib/fixDB.ts @@ -27,4 +27,5 @@ export default async (knex: Knex): Promise => { // memories 表新增字段 await addColumn("memories", "episodesId", "text"); await addColumn("memories", "agentType", "text"); + await addColumn("memories", "name", "text"); }; diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index 45a5be5..a493ab3 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -78,7 +78,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => modelName: "", vendorId: null, key: "scriptAgent", - name: "剧本Agent", + name: "剧本AI", desc: "用于读取原文生成故事骨架、改编策略,建议使用具备强大文本理解和生成能力的模型", disabled: false, }, @@ -87,7 +87,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => modelName: "", vendorId: null, key: "productionAgent", - name: "生产Agent", + name: "生产AI", desc: "对工作流进行调度和管理,建议使用具备较强的逻辑推理和任务管理能力的模型", disabled: false, }, @@ -96,7 +96,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => modelName: "", vendorId: null, key: "universalAgent", - name: "通用Agent", + name: "通用AI", desc: "用于小说事件提取、资产提示词生成、台词提取等边缘功能,建议使用具备较强文本处理能力的模型", disabled: false, }, @@ -184,6 +184,18 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => }, initData: async (knex) => {}, }, + //提示词表 + { + name: "o_prompt", + builder: (table) => { + table.integer("id").notNullable(); + table.string("name"); + table.text("rompt"); + table.primary(["id"]); + table.unique(["id"]); + }, + initData: async (knex) => {}, + }, //小说原文表 { name: "o_novel", @@ -278,6 +290,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.integer("assetsId"); table.integer("projectId"); table.integer("startTime"); + table.string("promptState"); table.primary(["id"]); table.unique(["id"]); }, @@ -791,6 +804,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.text("isolationKey").notNullable(); // 记忆隔离键 table.text("type").notNullable(); // 'message' | 'summary' table.text("role"); // 'user' | 'assistant' + table.text("name"); table.text("content").notNullable(); table.text("embedding"); // 向量嵌入 JSON table.text("relatedMessageIds"); // summary关联的message id列表 JSON diff --git a/src/router.ts b/src/router.ts index b6e73f1..63253f9 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash 1e5ddf0bf4594499634aaa6c96a492c7 +// @routes-hash 63d067de9d3f97b0602ef91a69334bc8 import { Express } from "express"; import route1 from "./routes/agents/clearMemory"; @@ -14,66 +14,66 @@ import route10 from "./routes/assets/delAssets"; import route11 from "./routes/assets/getAssetsApi"; import route12 from "./routes/assets/getImage"; import route13 from "./routes/assets/getMaterialData"; -import route14 from "./routes/assets/saveAssets"; -import route15 from "./routes/assets/updateAssets"; -import route16 from "./routes/assets/uploadClip"; -import route17 from "./routes/assetsGenerate/generateAssets"; -import route18 from "./routes/assetsGenerate/polishAssetsPrompt"; -import route19 from "./routes/cornerScape/getAllAssets"; -import route20 from "./routes/general/generalStatistics"; -import route21 from "./routes/general/getSingleProject"; -import route22 from "./routes/general/updateProject"; -import route23 from "./routes/login/login"; -import route24 from "./routes/migrate/migrateData"; -import route25 from "./routes/modelSelect/getModelDetail"; -import route26 from "./routes/modelSelect/getModelList"; -import route27 from "./routes/novel/addNovel"; -import route28 from "./routes/novel/batchDeleteNovel"; -import route29 from "./routes/novel/delNovel"; -import route30 from "./routes/novel/event/batchDeleteEvent"; -import route31 from "./routes/novel/event/deletEvent"; -import route32 from "./routes/novel/event/generateEvents"; -import route33 from "./routes/novel/event/getEvent"; -import route34 from "./routes/novel/getNovel"; -import route35 from "./routes/novel/getNovelEventState"; -import route36 from "./routes/novel/getNovelIndex"; -import route37 from "./routes/novel/updateNovel"; -import route38 from "./routes/other/deleteAllData"; -import route39 from "./routes/other/getVersion"; -import route40 from "./routes/production/assets/getAssetsData"; -import route41 from "./routes/production/editImage/generateFlowImage"; -import route42 from "./routes/production/editImage/getImageFlow"; -import route43 from "./routes/production/editImage/saveImageFlow"; -import route44 from "./routes/production/editImage/updateImageFlow"; -import route45 from "./routes/production/exportImage"; -import route46 from "./routes/production/getFlowData"; -import route47 from "./routes/production/getProductionData"; -import route48 from "./routes/production/getStoryboardData"; -import route49 from "./routes/production/saveFlowData"; -import route50 from "./routes/production/storyboard/downPreviewImage"; -import route51 from "./routes/production/storyboard/getStoryboardData"; -import route52 from "./routes/production/storyboard/previewImage"; -import route53 from "./routes/production/workbench/confirmSelection"; -import route54 from "./routes/production/workbench/delVideo"; -import route55 from "./routes/production/workbench/generateVideo"; -import route56 from "./routes/production/workbench/getChatLines"; -import route57 from "./routes/production/workbench/getVideoModelDetail"; -import route58 from "./routes/production/workbench/videoPolling"; -import route59 from "./routes/project/addProject"; -import route60 from "./routes/project/delProject"; -import route61 from "./routes/project/editProject"; -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/extractAssets"; -import route67 from "./routes/script/getScrptApi"; -import route68 from "./routes/script/pollScriptAssets"; -import route69 from "./routes/script/updateScript"; -import route70 from "./routes/scriptAgent/getPlanData"; -import route71 from "./routes/scriptAgent/setPlanData"; -import route72 from "./routes/setting/about/checkUpdate"; -import route73 from "./routes/setting/about/downloadApp"; +import route14 from "./routes/assets/pollingImageAssets"; +import route15 from "./routes/assets/pollingPromptAssets"; +import route16 from "./routes/assets/saveAssets"; +import route17 from "./routes/assets/updateAssets"; +import route18 from "./routes/assets/uploadClip"; +import route19 from "./routes/assetsGenerate/generateAssets"; +import route20 from "./routes/assetsGenerate/polishAssetsPrompt"; +import route21 from "./routes/cornerScape/getAllAssets"; +import route22 from "./routes/general/generalStatistics"; +import route23 from "./routes/general/getSingleProject"; +import route24 from "./routes/general/updateProject"; +import route25 from "./routes/login/login"; +import route26 from "./routes/migrate/migrateData"; +import route27 from "./routes/modelSelect/getModelDetail"; +import route28 from "./routes/modelSelect/getModelList"; +import route29 from "./routes/novel/addNovel"; +import route30 from "./routes/novel/batchDeleteNovel"; +import route31 from "./routes/novel/delNovel"; +import route32 from "./routes/novel/event/batchDeleteEvent"; +import route33 from "./routes/novel/event/deletEvent"; +import route34 from "./routes/novel/event/generateEvents"; +import route35 from "./routes/novel/event/getEvent"; +import route36 from "./routes/novel/getNovel"; +import route37 from "./routes/novel/getNovelEventState"; +import route38 from "./routes/novel/getNovelIndex"; +import route39 from "./routes/novel/updateNovel"; +import route40 from "./routes/other/deleteAllData"; +import route41 from "./routes/other/getVersion"; +import route42 from "./routes/production/assets/getAssetsData"; +import route43 from "./routes/production/editImage/generateFlowImage"; +import route44 from "./routes/production/editImage/getImageFlow"; +import route45 from "./routes/production/editImage/saveImageFlow"; +import route46 from "./routes/production/editImage/updateImageFlow"; +import route47 from "./routes/production/exportImage"; +import route48 from "./routes/production/getFlowData"; +import route49 from "./routes/production/getProductionData"; +import route50 from "./routes/production/getStoryboardData"; +import route51 from "./routes/production/saveFlowData"; +import route52 from "./routes/production/storyboard/downPreviewImage"; +import route53 from "./routes/production/storyboard/getStoryboardData"; +import route54 from "./routes/production/storyboard/previewImage"; +import route55 from "./routes/production/workbench/confirmSelection"; +import route56 from "./routes/production/workbench/delVideo"; +import route57 from "./routes/production/workbench/generateVideo"; +import route58 from "./routes/production/workbench/getChatLines"; +import route59 from "./routes/production/workbench/getVideoModelDetail"; +import route60 from "./routes/production/workbench/videoPolling"; +import route61 from "./routes/project/addProject"; +import route62 from "./routes/project/delProject"; +import route63 from "./routes/project/editProject"; +import route64 from "./routes/project/getProject"; +import route65 from "./routes/script/addScript"; +import route66 from "./routes/script/delScript"; +import route67 from "./routes/script/exportScript"; +import route68 from "./routes/script/extractAssets"; +import route69 from "./routes/script/getScrptApi"; +import route70 from "./routes/script/updateScript"; +import route71 from "./routes/scriptAgent/getPlanData"; +import route72 from "./routes/scriptAgent/setPlanData"; +import route73 from "./routes/setting/about/checkUpdate"; import route74 from "./routes/setting/agentDeploy/agentSetKey"; import route75 from "./routes/setting/agentDeploy/deployAgentModel"; import route76 from "./routes/setting/agentDeploy/getAgentDeploy"; @@ -120,66 +120,66 @@ export default async (app: Express) => { app.use("/api/assets/getAssetsApi", route11); app.use("/api/assets/getImage", route12); app.use("/api/assets/getMaterialData", route13); - app.use("/api/assets/saveAssets", route14); - app.use("/api/assets/updateAssets", route15); - app.use("/api/assets/uploadClip", route16); - app.use("/api/assetsGenerate/generateAssets", route17); - app.use("/api/assetsGenerate/polishAssetsPrompt", route18); - app.use("/api/cornerScape/getAllAssets", route19); - app.use("/api/general/generalStatistics", route20); - app.use("/api/general/getSingleProject", route21); - app.use("/api/general/updateProject", route22); - app.use("/api/login/login", route23); - app.use("/api/migrate/migrateData", route24); - app.use("/api/modelSelect/getModelDetail", route25); - app.use("/api/modelSelect/getModelList", route26); - app.use("/api/novel/addNovel", route27); - app.use("/api/novel/batchDeleteNovel", route28); - app.use("/api/novel/delNovel", route29); - app.use("/api/novel/event/batchDeleteEvent", route30); - app.use("/api/novel/event/deletEvent", route31); - app.use("/api/novel/event/generateEvents", route32); - app.use("/api/novel/event/getEvent", route33); - app.use("/api/novel/getNovel", route34); - app.use("/api/novel/getNovelEventState", route35); - app.use("/api/novel/getNovelIndex", route36); - app.use("/api/novel/updateNovel", route37); - app.use("/api/other/deleteAllData", route38); - app.use("/api/other/getVersion", route39); - app.use("/api/production/assets/getAssetsData", route40); - app.use("/api/production/editImage/generateFlowImage", route41); - app.use("/api/production/editImage/getImageFlow", route42); - app.use("/api/production/editImage/saveImageFlow", route43); - app.use("/api/production/editImage/updateImageFlow", route44); - app.use("/api/production/exportImage", route45); - app.use("/api/production/getFlowData", route46); - app.use("/api/production/getProductionData", route47); - app.use("/api/production/getStoryboardData", route48); - app.use("/api/production/saveFlowData", route49); - app.use("/api/production/storyboard/downPreviewImage", route50); - app.use("/api/production/storyboard/getStoryboardData", route51); - app.use("/api/production/storyboard/previewImage", route52); - app.use("/api/production/workbench/confirmSelection", route53); - app.use("/api/production/workbench/delVideo", route54); - app.use("/api/production/workbench/generateVideo", route55); - app.use("/api/production/workbench/getChatLines", route56); - app.use("/api/production/workbench/getVideoModelDetail", route57); - app.use("/api/production/workbench/videoPolling", route58); - app.use("/api/project/addProject", route59); - app.use("/api/project/delProject", route60); - app.use("/api/project/editProject", route61); - app.use("/api/project/getProject", route62); - app.use("/api/script/addScript", route63); - app.use("/api/script/delScript", route64); - app.use("/api/script/exportScript", route65); - app.use("/api/script/extractAssets", route66); - app.use("/api/script/getScrptApi", route67); - app.use("/api/script/pollScriptAssets", route68); - app.use("/api/script/updateScript", route69); - app.use("/api/scriptAgent/getPlanData", route70); - app.use("/api/scriptAgent/setPlanData", route71); - app.use("/api/setting/about/checkUpdate", route72); - app.use("/api/setting/about/downloadApp", route73); + app.use("/api/assets/pollingImageAssets", route14); + app.use("/api/assets/pollingPromptAssets", route15); + app.use("/api/assets/saveAssets", route16); + app.use("/api/assets/updateAssets", route17); + app.use("/api/assets/uploadClip", route18); + app.use("/api/assetsGenerate/generateAssets", route19); + app.use("/api/assetsGenerate/polishAssetsPrompt", route20); + app.use("/api/cornerScape/getAllAssets", route21); + app.use("/api/general/generalStatistics", route22); + app.use("/api/general/getSingleProject", route23); + app.use("/api/general/updateProject", route24); + app.use("/api/login/login", route25); + app.use("/api/migrate/migrateData", route26); + app.use("/api/modelSelect/getModelDetail", route27); + app.use("/api/modelSelect/getModelList", route28); + app.use("/api/novel/addNovel", route29); + app.use("/api/novel/batchDeleteNovel", route30); + app.use("/api/novel/delNovel", route31); + app.use("/api/novel/event/batchDeleteEvent", route32); + app.use("/api/novel/event/deletEvent", route33); + app.use("/api/novel/event/generateEvents", route34); + app.use("/api/novel/event/getEvent", route35); + app.use("/api/novel/getNovel", route36); + app.use("/api/novel/getNovelEventState", route37); + app.use("/api/novel/getNovelIndex", route38); + app.use("/api/novel/updateNovel", route39); + app.use("/api/other/deleteAllData", route40); + app.use("/api/other/getVersion", route41); + app.use("/api/production/assets/getAssetsData", route42); + app.use("/api/production/editImage/generateFlowImage", route43); + app.use("/api/production/editImage/getImageFlow", route44); + app.use("/api/production/editImage/saveImageFlow", route45); + app.use("/api/production/editImage/updateImageFlow", route46); + app.use("/api/production/exportImage", route47); + app.use("/api/production/getFlowData", route48); + app.use("/api/production/getProductionData", route49); + app.use("/api/production/getStoryboardData", route50); + app.use("/api/production/saveFlowData", route51); + app.use("/api/production/storyboard/downPreviewImage", route52); + app.use("/api/production/storyboard/getStoryboardData", route53); + app.use("/api/production/storyboard/previewImage", route54); + app.use("/api/production/workbench/confirmSelection", route55); + app.use("/api/production/workbench/delVideo", route56); + app.use("/api/production/workbench/generateVideo", route57); + app.use("/api/production/workbench/getChatLines", route58); + app.use("/api/production/workbench/getVideoModelDetail", route59); + app.use("/api/production/workbench/videoPolling", route60); + app.use("/api/project/addProject", route61); + app.use("/api/project/delProject", route62); + app.use("/api/project/editProject", route63); + app.use("/api/project/getProject", route64); + app.use("/api/script/addScript", route65); + app.use("/api/script/delScript", route66); + app.use("/api/script/exportScript", route67); + app.use("/api/script/extractAssets", route68); + app.use("/api/script/getScrptApi", route69); + app.use("/api/script/updateScript", route70); + app.use("/api/scriptAgent/getPlanData", route71); + app.use("/api/scriptAgent/setPlanData", route72); + app.use("/api/setting/about/checkUpdate", route73); app.use("/api/setting/agentDeploy/agentSetKey", route74); app.use("/api/setting/agentDeploy/deployAgentModel", route75); app.use("/api/setting/agentDeploy/getAgentDeploy", route76); diff --git a/src/routes/agents/getMemory.ts b/src/routes/agents/getMemory.ts index 4aece69..a02cee5 100644 --- a/src/routes/agents/getMemory.ts +++ b/src/routes/agents/getMemory.ts @@ -9,11 +9,6 @@ function normalizeRole(role?: string | null): "user" | "assistant" { return role?.startsWith("assistant") ? "assistant" : "user"; } -function getAssistantName(role?: string | null): string | undefined { - if (!role?.startsWith("assistant:")) return undefined; - return role.split(":")[1] || "assistant"; -} - export default router.post( "/", validateFields({ @@ -29,12 +24,14 @@ export default router.post( .db("memories") .where({ isolationKey, type: "message" }) .orderBy("createTime", "asc") - .select("id", "role", "content", "createTime"); + .select("id", "role", "name", "content", "createTime"); const history = rows.map((row) => ({ id: row.id, role: normalizeRole(row.role), - name: getAssistantName(row.role), + name: row.name ?? undefined, + status: "complete", + datetime: new Date(row.createTime).toISOString(), content: [{ type: "markdown", status: "complete", data: row.content }], createTime: row.createTime, })); diff --git a/src/routes/assets/getAssetsApi.ts b/src/routes/assets/getAssetsApi.ts index ee666cb..1573148 100644 --- a/src/routes/assets/getAssetsApi.ts +++ b/src/routes/assets/getAssetsApi.ts @@ -21,7 +21,7 @@ export default router.post( let query = u .db("o_assets") .leftJoin("o_image", "o_assets.imageId", "o_image.id") - .select("o_assets.*", "o_image.filePath") + .select("o_assets.*", "o_image.filePath", "o_image.state") .where("o_assets.projectId", projectId) .andWhere("o_assets.type", type); if (name) { @@ -34,7 +34,7 @@ export default router.post( let childQuery = u .db("o_assets") .leftJoin("o_image", "o_assets.imageId", "o_image.id") - .select("o_assets.*", "o_image.filePath") + .select("o_assets.*", "o_image.filePath", "o_image.state") .where("o_assets.projectId", projectId) .andWhere("o_assets.type", type) .whereNotNull("o_assets.assetsId"); diff --git a/src/routes/assets/pollingImageAssets.ts b/src/routes/assets/pollingImageAssets.ts new file mode 100644 index 0000000..df8a83a --- /dev/null +++ b/src/routes/assets/pollingImageAssets.ts @@ -0,0 +1,28 @@ +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( + "/", + validateFields({ + ids: z.array(z.number()), + }), + async (req, res) => { + const { ids } = req.body; + const data = await u + .db("o_assets") + .leftJoin("o_image", "o_assets.id", "o_image.assetsId") + .whereIn("o_assets.id", ids) + .select("o_image.state", "o_assets.id", "o_image.filePath"); + const result = await Promise.all( + data.map(async (item: any) => ({ + ...item, + filePath: item.filePath ? await u.oss.getFileUrl(item.filePath) : null, + })), + ); + res.status(200).send(success(result)); + }, +); diff --git a/src/routes/assets/pollingPromptAssets.ts b/src/routes/assets/pollingPromptAssets.ts new file mode 100644 index 0000000..40a285a --- /dev/null +++ b/src/routes/assets/pollingPromptAssets.ts @@ -0,0 +1,18 @@ +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( + "/", + validateFields({ + ids: z.array(z.number()), + }), + async (req, res) => { + const { ids } = req.body; + const data = await u.db("o_assets").whereIn("id", ids).select("*"); + res.status(200).send(success(data)); + }, +); diff --git a/src/routes/assets/saveAssets.ts b/src/routes/assets/saveAssets.ts index 563739a..15da9b4 100644 --- a/src/routes/assets/saveAssets.ts +++ b/src/routes/assets/saveAssets.ts @@ -33,7 +33,7 @@ export default router.post( assetsId: id, filePath: savePath, type: type, - state: "生成成功", + state: "已完成", }); // 更新资产表图片为新图片 await u diff --git a/src/routes/assetsGenerate/generateAssets.ts b/src/routes/assetsGenerate/generateAssets.ts index 977a876..a508dae 100644 --- a/src/routes/assetsGenerate/generateAssets.ts +++ b/src/routes/assetsGenerate/generateAssets.ts @@ -87,6 +87,7 @@ export default router.post("/", validateFields(requestSchema), async (req, res) state: "生成中", assetsId: id, }); + await u.db("o_assets").where("id", id).update({ imageId }); // 3. 准备生成参数 const imagePath = `/${projectId}/${cfg.dir}/${uuidv4()}.jpg`; @@ -106,7 +107,7 @@ export default router.post("/", validateFields(requestSchema), async (req, res) describe, projectId, relatedObjects: JSON.stringify(relatedObjects), - }); + }) aiImage.save(imagePath); // 5. 更新记录 & 返回结果 @@ -114,7 +115,7 @@ export default router.post("/", validateFields(requestSchema), async (req, res) if (!imageData) return res.status(500).send("资产已被删除"); await u.db("o_image").where("id", imageId).update({ - state: "生成成功", + state: "已完成", filePath: imagePath, type, model: model.split(":")[1], diff --git a/src/routes/assetsGenerate/polishAssetsPrompt.ts b/src/routes/assetsGenerate/polishAssetsPrompt.ts index 29fdf19..5b455ee 100644 --- a/src/routes/assetsGenerate/polishAssetsPrompt.ts +++ b/src/routes/assetsGenerate/polishAssetsPrompt.ts @@ -61,6 +61,7 @@ export default router.post( if (!project) return res.status(500).send(success({ message: "项目为空" })); const allOutlineDataList: { data: string }[] = await u.db("o_outline").where("projectId", projectId).select("data"); + await u.db("o_assets").where("id", assetsId).update({ promptState: "生成中" }); const itemMap: Record = {}; @@ -124,7 +125,7 @@ export default router.post( })) as any; 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, promptState: "已完成" }); res.status(200).send(success({ prompt: _output, assetsId })); } catch (e: any) { diff --git a/src/routes/setting/agentDeploy/agentSetKey.ts b/src/routes/setting/agentDeploy/agentSetKey.ts index 22f541a..47fa251 100644 --- a/src/routes/setting/agentDeploy/agentSetKey.ts +++ b/src/routes/setting/agentDeploy/agentSetKey.ts @@ -14,7 +14,7 @@ export default router.post( const { id } = req.body; await u.db("o_agentDeploy").whereIn("id", id).where("disabled", "<>", 1).update({ model: "gpt-4.1", - modelName: "1:gpt-4.1", + modelName: "toonflow:gpt-4.1", vendorId: 1, }); res.status(200).send(success("配置成功")); diff --git a/src/routes/setting/vendorConfig/modelTest.ts b/src/routes/setting/vendorConfig/modelTest.ts index e16f1eb..495a24e 100644 --- a/src/routes/setting/vendorConfig/modelTest.ts +++ b/src/routes/setting/vendorConfig/modelTest.ts @@ -76,7 +76,6 @@ export default router.post( for await (const chunk of textStream) { fullResponse += chunk; } - console.log("%c Line:78 🥝 fullResponse", "background:#ea7e5c", fullResponse); res.status(200).send(success(fullResponse)); } else { const aiTypeFn = { diff --git a/src/socket/chatMessagesData.d.ts b/src/socket/chatMessagesData.d.ts new file mode 100644 index 0000000..2059aae --- /dev/null +++ b/src/socket/chatMessagesData.d.ts @@ -0,0 +1,58 @@ +import type { ToolCallEventType } from './adapters/agui/types/events'; + +export type ChatMessageStatus = 'pending' | 'streaming' | 'complete' | 'stop' | 'error'; +export type AttachmentType = 'image' | 'video' | 'audio' | 'pdf' | 'doc' | 'ppt' | 'txt'; +export type ChatComment = 'good' | 'bad' | ''; + +// 基础内容接口 +export interface ChatBaseContent { + type: T; + data: D; + status?: ChatMessageStatus; + id?: string; + strategy?: 'merge' | 'append'; + ext?: Record; +} + +// 内容类型定义 +export type TextContent = ChatBaseContent<'text', string>; +export type MarkdownContent = ChatBaseContent<'markdown', string>; +export type ImageContent = ChatBaseContent<'image', { name?: string; url?: string; width?: number; height?: number }>; +export type ThinkingContent = ChatBaseContent<'thinking', { text?: string; title?: string }>; +export type SearchContent = ChatBaseContent<'search', { title?: string; references?: { title: string; icon?: string; type?: string; url?: string; content?: string; site?: string; date?: string }[] }>; +export type SuggestionContent = ChatBaseContent<'suggestion', { title: string; prompt?: string }[]>; +export type AttachmentContent = ChatBaseContent<'attachment', { fileType: AttachmentType; size?: number; name?: string; url?: string; isReference?: boolean; width?: number; height?: number; extension?: string; metadata?: Record }[]>; +export type ToolCallContent = ChatBaseContent<'toolcall', { toolCallId: string; toolCallName: string; eventType?: ToolCallEventType; parentMessageId?: string; args?: string; chunk?: string; result?: string }>; +export type ActivityContent> = ChatBaseContent<'activity', { activityType: string; messageId?: string; content: T; deltaInfo?: { fromIndex: number; toIndex: number } }>; + +// 聚合内容类型 +export type AIMessageContent = TextContent | MarkdownContent | ImageContent | ThinkingContent | SearchContent | SuggestionContent | ReasoningContent | ToolCallContent | ActivityContent; +export type ReasoningContent = ChatBaseContent<'reasoning', AIMessageContent[]>; +export type UserMessageContent = TextContent | AttachmentContent; + +// 消息类型定义 +export interface ChatBaseMessage { + id: string; + status?: ChatMessageStatus; + datetime?: string; + ext?: any; +} + +export interface UserMessage extends ChatBaseMessage { + role: 'user'; + content: UserMessageContent[]; +} + +export interface AIMessage extends ChatBaseMessage { + role: 'assistant'; + content?: AIMessageContent[]; + history?: AIMessageContent[][]; + comment?: ChatComment; +} + +export interface SystemMessage extends ChatBaseMessage { + role: 'system'; + content: TextContent[]; +} + +export type ChatMessagesData = UserMessage | AIMessage | SystemMessage; \ No newline at end of file diff --git a/src/socket/resTool copy.ts b/src/socket/resTool copy.ts new file mode 100644 index 0000000..fbac487 --- /dev/null +++ b/src/socket/resTool copy.ts @@ -0,0 +1,79 @@ +import u from "@/utils"; +import { Socket } from "socket.io"; + +class ResTool { + public socket: Socket; + public data: Record; + constructor(socket: Socket, data: Record = {}) { + this.socket = socket; + this.data = data; + } + + textMessage(name: string = "AI") { + const messageId = u.uuid(); + this.socket.emit("textMessage", { + type: "start", + messageId, + delta: null, + role: "assistant", + name, + }); + const handle = { + send: (delta: string) => { + this.socket.emit("textMessage", { + type: "content", + messageId, + delta, + role: "assistant", + name, + }); + return handle; + }, + end: () => { + this.socket.emit("textMessage", { + type: "end", + messageId, + delta: null, + role: "assistant", + name, + }); + }, + }; + return handle; + } + thinkMessage() { + const messageId = u.uuid(); + this.socket.emit("thinkMessage", { + type: "start", + messageId, + delta: null, + role: "assistant", + }); + const handle = { + send: (delta: string) => { + this.socket.emit("thinkMessage", { + type: "content", + messageId, + delta, + role: "assistant", + }); + return handle; + }, + end: () => { + this.socket.emit("thinkMessage", { + type: "end", + messageId, + delta: null, + role: "assistant", + }); + }, + }; + return handle; + } + systemMessage(content: string) { + const messageId = u.uuid(); + this.socket.emit("systemMessage", { messageId, content }); + } +} + +export default ResTool; diff --git a/src/socket/resTool.ts b/src/socket/resTool.ts index fbac487..d2fb422 100644 --- a/src/socket/resTool.ts +++ b/src/socket/resTool.ts @@ -1,79 +1,544 @@ import u from "@/utils"; import { Socket } from "socket.io"; +import type { + ChatMessageStatus, + AIMessageContent, + UserMessageContent, + TextContent, + MarkdownContent, + ImageContent, + ThinkingContent, + SearchContent, + SuggestionContent, + ToolCallContent, + ActivityContent, + ReasoningContent, + AttachmentContent, +} from "./ChatMessagesData"; + +type ContentType = AIMessageContent["type"]; class ResTool { public socket: Socket; public data: Record; + constructor(socket: Socket, data: Record = {}) { this.socket = socket; this.data = data; } - textMessage(name: string = "AI") { + // 创建新消息 + newMessage(role: "assistant" | "user" | "system" = "assistant", name?: string) { const messageId = u.uuid(); - this.socket.emit("textMessage", { - type: "start", - messageId, - delta: null, - role: "assistant", + const datetime = new Date().toISOString(); + + this.socket.emit("message", { + id: messageId, + role, name, + status: "pending" as ChatMessageStatus, + datetime, + content: [], }); - const handle = { - send: (delta: string) => { - this.socket.emit("textMessage", { - type: "content", - messageId, - delta, - role: "assistant", - name, - }); - return handle; - }, - end: () => { - this.socket.emit("textMessage", { - type: "end", - messageId, - delta: null, - role: "assistant", - name, - }); - }, - }; - return handle; + + return new MessageBuilder(this.socket, messageId, role, name, datetime); } - thinkMessage() { - const messageId = u.uuid(); - this.socket.emit("thinkMessage", { - type: "start", - messageId, - delta: null, - role: "assistant", + + // 发送错误消息 + sendError(messageId: string, error: string) { + this.socket.emit("message:update", { + id: messageId, + status: "error" as ChatMessageStatus, + ext: { error }, }); - const handle = { - send: (delta: string) => { - this.socket.emit("thinkMessage", { - type: "content", - messageId, - delta, - role: "assistant", - }); - return handle; - }, - end: () => { - this.socket.emit("thinkMessage", { - type: "end", - messageId, - delta: null, - role: "assistant", - }); - }, - }; - return handle; } - systemMessage(content: string) { - const messageId = u.uuid(); - this.socket.emit("systemMessage", { messageId, content }); + + // 发送完成状态 + sendComplete(messageId: string) { + this.socket.emit("message:update", { + id: messageId, + status: "complete" as ChatMessageStatus, + }); + } +} + +// 消息构建器 +class MessageBuilder { + private socket: Socket; + private messageId: string; + private messageRole: "assistant" | "user" | "system"; + private messageName?: string; + private messageDatetime: string; + + constructor(socket: Socket, messageId: string, role: "assistant" | "user" | "system", name?: string, datetime?: string) { + this.socket = socket; + this.messageId = messageId; + this.messageRole = role; + this.messageName = name; + this.messageDatetime = datetime ?? new Date().toISOString(); + } + + get id() { + return this.messageId; + } + + get role() { + return this.messageRole; + } + + get name() { + return this.messageName; + } + + get datetime() { + return this.messageDatetime; + } + + // 更新消息状态 + updateStatus(status: ChatMessageStatus) { + this.socket.emit("message:update", { + id: this.messageId, + status, + }); + return this; + } + + // 添加文本内容 + text(initialText = "") { + const contentId = u.uuid(); + const content: TextContent = { + type: "text", + id: contentId, + data: initialText, + status: "pending", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return new ContentStream(this.socket, this.messageId, contentId, "text"); + } + + // 添加 Markdown 内容 + markdown(initialText = "") { + const contentId = u.uuid(); + const content: MarkdownContent = { + type: "markdown", + id: contentId, + data: initialText, + status: "pending", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return new ContentStream(this.socket, this.messageId, contentId, "markdown"); + } + + // 添加思考内容 + thinking(title = "思考中...") { + const contentId = u.uuid(); + const content: ThinkingContent = { + type: "thinking", + id: contentId, + data: { title, text: "" }, + status: "pending", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return new ThinkingStream(this.socket, this.messageId, contentId); + } + + // 添加搜索内容 + search(title = "搜索中...") { + const contentId = u.uuid(); + const content: SearchContent = { + type: "search", + id: contentId, + data: { title, references: [] }, + status: "pending", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return new SearchStream(this.socket, this.messageId, contentId); + } + + // 添加图片内容 + image(data: ImageContent["data"]) { + const contentId = u.uuid(); + const content: ImageContent = { + type: "image", + id: contentId, + data, + status: "complete", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return this; + } + + // 添加建议内容 + suggestion(suggestions: SuggestionContent["data"]) { + const contentId = u.uuid(); + const content: SuggestionContent = { + type: "suggestion", + id: contentId, + data: suggestions, + status: "complete", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return this; + } + + // 添加工具调用内容 + toolCall(data: ToolCallContent["data"]) { + const contentId = u.uuid(); + const content: ToolCallContent = { + type: "toolcall", + id: contentId, + data: { ...data, parentMessageId: this.messageId }, + status: "pending", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return new ToolCallStream(this.socket, this.messageId, contentId, data.toolCallId); + } + + // 添加活动内容 + activity>(activityType: string, content: T) { + const contentId = u.uuid(); + const activityContent: ActivityContent = { + type: "activity", + id: contentId, + data: { + activityType, + messageId: this.messageId, + content, + }, + status: "complete", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content: activityContent, + }); + + return this; + } + + // 添加推理内容 + reasoning() { + const contentId = u.uuid(); + const content: ReasoningContent = { + type: "reasoning", + id: contentId, + data: [], + status: "pending", + }; + + this.socket.emit("content:add", { + messageId: this.messageId, + content, + }); + + return new ReasoningBuilder(this.socket, this.messageId, contentId); + } + + // 完成消息 + complete() { + this.socket.emit("message:update", { + id: this.messageId, + status: "complete" as ChatMessageStatus, + }); + } + + // 停止消息 + stop() { + this.socket.emit("message:update", { + id: this.messageId, + status: "stop" as ChatMessageStatus, + }); + } + + // 错误 + error(errorMsg?: string) { + this.socket.emit("message:update", { + id: this.messageId, + status: "error" as ChatMessageStatus, + ext: errorMsg ? { error: errorMsg } : undefined, + }); + } +} + +// 内容流基类 +class ContentStream { + protected socket: Socket; + protected messageId: string; + protected contentId: string; + protected contentType: ContentType; + + constructor(socket: Socket, messageId: string, contentId: string, contentType: ContentType) { + this.socket = socket; + this.messageId = messageId; + this.contentId = contentId; + this.contentType = contentType; + } + + get id() { + return this.contentId; + } + + // 流式追加数据 + append(chunk: string) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: this.contentType, + data: chunk, + strategy: "append", + status: "streaming", + }); + return this; + } + + // 合并/替换数据 + merge(data: T) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: this.contentType, + data, + strategy: "merge", + status: "streaming", + }); + return this; + } + + // 完成内容 + complete(finalData?: T) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: this.contentType, + data: finalData, + status: "complete", + }); + return this; + } + + // 错误 + error() { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + status: "error", + }); + return this; + } +} + +// 思考内容流 +class ThinkingStream extends ContentStream { + constructor(socket: Socket, messageId: string, contentId: string) { + super(socket, messageId, contentId, "thinking"); + } + + // 追加思考文本 + appendText(chunk: string) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "thinking", + data: { text: chunk }, + strategy: "append", + status: "streaming", + }); + return this; + } + + // 更新标题 + updateTitle(title: string) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "thinking", + data: { title }, + strategy: "merge", + status: "streaming", + }); + return this; + } +} + +// 搜索内容流 +class SearchStream extends ContentStream { + constructor(socket: Socket, messageId: string, contentId: string) { + super(socket, messageId, contentId, "search"); + } + + // 添加引用 + addReference(ref: Exclude[0]) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "search", + data: { references: [ref] }, + strategy: "append", + status: "streaming", + }); + return this; + } + + // 批量添加引用 + addReferences(refs: SearchContent["data"]["references"]) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "search", + data: { references: refs }, + strategy: "append", + status: "streaming", + }); + return this; + } + + // 更新标题 + updateTitle(title: string) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "search", + data: { title }, + strategy: "merge", + status: "streaming", + }); + return this; + } +} + +// 工具调用流 +class ToolCallStream extends ContentStream { + private toolCallId: string; + + constructor(socket: Socket, messageId: string, contentId: string, toolCallId: string) { + super(socket, messageId, contentId, "toolcall"); + this.toolCallId = toolCallId; + } + + // 追加参数块 + appendArgs(chunk: string) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "toolcall", + data: { toolCallId: this.toolCallId, args: chunk }, + strategy: "append", + status: "streaming", + }); + return this; + } + + // 追加结果块 + appendResult(chunk: string) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "toolcall", + data: { toolCallId: this.toolCallId, chunk }, + strategy: "append", + status: "streaming", + }); + return this; + } + + // 设置完整结果 + setResult(result: string) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "toolcall", + data: { toolCallId: this.toolCallId, result }, + strategy: "merge", + status: "complete", + }); + return this; + } + + // 更新事件类型 + updateEventType(eventType: ToolCallContent["data"]["eventType"]) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "toolcall", + data: { toolCallId: this.toolCallId, eventType }, + strategy: "merge", + status: "streaming", + }); + return this; + } +} + +// 推理构建器 +class ReasoningBuilder { + private socket: Socket; + private messageId: string; + private contentId: string; + + constructor(socket: Socket, messageId: string, contentId: string) { + this.socket = socket; + this.messageId = messageId; + this.contentId = contentId; + } + + // 添加子内容 + addContent(content: AIMessageContent) { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "reasoning", + data: [content], + strategy: "append", + status: "streaming", + }); + return this; + } + + // 完成推理 + complete() { + this.socket.emit("content:update", { + messageId: this.messageId, + contentId: this.contentId, + type: "reasoning", + status: "complete", + }); + return this; } } export default ResTool; +export { MessageBuilder, ContentStream, ThinkingStream, SearchStream, ToolCallStream, ReasoningBuilder }; diff --git a/src/socket/routes/scriptAgent.ts b/src/socket/routes/scriptAgent.ts index 18071c9..5867c79 100644 --- a/src/socket/routes/scriptAgent.ts +++ b/src/socket/routes/scriptAgent.ts @@ -3,6 +3,7 @@ import u from "@/utils"; import { Namespace, Socket } from "socket.io"; import * as agent from "@/agents/scriptAgent/index"; import ResTool from "@/socket/resTool"; +import Memory from "@/utils/agent/memory"; async function verifyToken(rawToken: string): Promise { const setting = await u.db("o_setting").where("key", "tokenKey").select("value").first(); @@ -40,23 +41,61 @@ export default (nsp: Namespace) => { }); let abortController: AbortController | null = null; - socket.on("message", async (text: string) => { + socket.on("chat", async (data: { content: string }) => { + const { content } = data; abortController?.abort(); abortController = new AbortController(); const currentController = abortController; + const memory = new Memory("scriptAgent", isolationKey); - const textStream = await agent.decisionAI({ socket, isolationKey, text, abortSignal: currentController.signal, resTool }); + const msg = resTool.newMessage("assistant", "统筹"); + const ctx: agent.AgentContext = { + socket, + isolationKey, + text: content, + userMessageTime: new Date(msg.datetime).getTime() - 1, + abortSignal: currentController.signal, + resTool, + msg, + }; - let msg = resTool.textMessage(); + const textStream = await agent.decisionAI(ctx); + + let currentMsg = ctx.msg; + let text = currentMsg.text(); + let currentContent = ""; + + const persistCurrentMessage = async () => { + if (!currentContent.trim()) return; + await memory.add("assistant:decision", currentContent, { + name: "统筹", + createTime: new Date(currentMsg.datetime).getTime(), + }); + currentContent = ""; + }; + + const syncCurrentMessage = async () => { + if (ctx.msg === currentMsg) return; + text.complete(); + currentMsg.complete(); + await persistCurrentMessage(); + currentMsg = ctx.msg; + text = currentMsg.text(); + }; try { for await (const chunk of textStream) { - msg.send(chunk); + await syncCurrentMessage(); + text.append(chunk); + currentContent += chunk; } } catch (err: any) { if (err.name !== "AbortError") throw err; } finally { - msg.end(); + await syncCurrentMessage(); + 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 c9fbf42..eed488e 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,19 +1,13 @@ -// @db-hash 1af54b27110c54bf92390a017ee6b240 +// @db-hash 8e5f2b7a28d4494b291d802b055b6399 //该文件由脚本自动生成,请勿手动修改 -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; 'embedding'?: string | null; 'id'?: string; 'isolationKey': string; + 'name'?: string | null; 'relatedMessageIds'?: string | null; 'role'?: string | null; 'summarized'?: number | null; @@ -124,11 +118,14 @@ export interface o_project { 'videoModel'?: string | null; 'videoRatio'?: string | null; } +export interface o_prompt { + 'id'?: number; + 'name'?: string | null; + 'rompt'?: string | null; +} export interface o_script { 'content'?: string | null; 'createTime'?: number | null; - 'errorReason'?: string | null; - 'extractState'?: number | null; 'id'?: number; 'name'?: string | null; 'projectId'?: number | null; @@ -230,7 +227,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; @@ -245,6 +241,7 @@ export interface DB { "o_outline": o_outline; "o_outlineNovel": o_outlineNovel; "o_project": o_project; + "o_prompt": o_prompt; "o_script": o_script; "o_scriptAssets": o_scriptAssets; "o_setting": o_setting; diff --git a/src/utils.ts b/src/utils.ts index 98eb57a..da89cc9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,6 +8,7 @@ import getPath from "@/utils/getPath"; import vm from "@/utils/vm"; import task from "@/utils/taskRecord"; import Ai from "@/utils/ai"; +import { getPrompts } from "@/utils/getPrompts"; export default { db, @@ -20,4 +21,5 @@ export default { getPath, Ai, task, + getPrompts, }; diff --git a/src/utils/agent/memory.ts b/src/utils/agent/memory.ts index 16fde04..1ba2413 100644 --- a/src/utils/agent/memory.ts +++ b/src/utils/agent/memory.ts @@ -82,7 +82,8 @@ class Memory { } return result; } - async add(role: string = "user", content: string) { + + async add(role: string = "user", content: string, options?: { name?: string; createTime?: number }) { const { messagesPerSummary } = await this.getConfigData({ messagesPerSummary: DEFAULTS.messagesPerSummary }); const id = uuidv4(); const embedding = await getEmbedding(content); @@ -93,11 +94,12 @@ class Memory { isolationKey, type: "message", role, + name: options?.name, content, embedding: JSON.stringify(embedding), relatedMessageIds: null, summarized: 0, - createTime: Date.now(), + createTime: options?.createTime ?? Date.now(), } as any); // 检查未总结消息数量 @@ -154,7 +156,7 @@ class Memory { const ragResults = vectorSearch(allMessages, queryEmbedding, Number(ragLimit)); return { - shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createTime: m.createTime })), + shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, name: m.name, content: m.content, createTime: m.createTime })), summaries: summaries.map((s) => ({ id: s.id, content: s.content, diff --git a/src/utils/cleanNovel.ts b/src/utils/cleanNovel.ts index a240479..6380f54 100644 --- a/src/utils/cleanNovel.ts +++ b/src/utils/cleanNovel.ts @@ -26,21 +26,25 @@ class CleanNovel { private async processChapter(novel: o_novel, intansce: ReturnType): Promise { try { - const skill = await useSkill("universal_agent.md"); - + const prompt = await u.getPrompts("event"); const resData = await intansce.invoke({ - system: skill.prompt, + system: prompt, messages: [ { role: "user", - content: "请根据以下小说章节生成事件摘要:\n" + novel.chapterData!, + content: + "请根据以下小说章节数:" + + novel.chapterIndex + + "小说章节券:" + + novel.reel + + "小说章节名称:" + + novel.chapter + + "、小说章节内容生成事件摘要:\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) { diff --git a/src/utils/getPrompts.ts b/src/utils/getPrompts.ts new file mode 100644 index 0000000..25778a6 --- /dev/null +++ b/src/utils/getPrompts.ts @@ -0,0 +1,59 @@ +export async function getPrompts(type: string) { + if (type == "event") { + return ` +# 事件提取指令 + +你是小说文本分析助手。用户每次提供一个章节的原文,你提取该章的结构化事件信息。 + +## ⚠️ 输出约束(最高优先级,违反任何一条即为失败) + +1. 你的**完整回复**只有一行,以 \`|\` 开头、以 \`|\` 结尾,恰好 7 个字段 +2. 回复的**第一个字符**必须是 \`|\`,**最后一个字符**必须是 \`|\` +3. \`|\` 之前不许有任何字符——没有引导语、没有解释、没有"根据……"、没有"以下是……" +4. \`|\` 之后不许有任何字符——没有总结、没有提取说明、没有改编建议 +5. 不输出表头行、分隔线、Markdown 标题、emoji、代码块标记 + +## 输出格式 + +\`\`\` +| 第X章 {章节标题} | {涉及角色} | {核心事件} | {主线关系} | {信息密度} | {预估集长} | {情绪强度} | +\`\`\` + +### 字段规范 + +| 字段 | 格式要求 | 示例 | +|------|----------|------| +| 章节 | \`第X章 {章节标题}\` | \`第1章 职业危机与许愿\` | +| 涉及角色 | 有实际戏份的角色,顿号分隔 | \`林逸、白有容\` | +| 核心事件 | 30-60字,必须含动作+结果 | \`林逸因解密风潮事业崩塌,颓废中许愿触发魔法系统绑定\` | +| 主线关系 | **必须**为 \`强/中/弱(3-8字理由)\` | \`强(动机建立+系统激活)\` | +| 信息密度 | \`高\` / \`中\` / \`低\` | \`高\` | +| 预估集长 | **必须**为 \`X秒\`,禁止用分钟 | \`50秒\` | +| 情绪强度 | 文字标签,\`+\` 连接,禁止星级/数字 | \`转折+悬疑\` | + +**主线关系判定**:强=直接推动主角弧线;中=补充世界观/人物关系/伏笔;弱=过渡/气氛。 + +**预估集长参考**:高密度+高情绪→45-60秒;中→35-45秒;低→25-35秒。 + +**可用情绪标签**:\`冲突\`、\`恐怖\`、\`情感\`、\`转折\`、\`高潮\`、\`平铺\`、\`喜剧\`、\`悬疑\`、\`情感崩溃\`。 + +## 输出示例 + +以下两个示例展示的是**完整回复**——除这一行外没有任何其他内容: + +\`\`\` +| 第1章 职业危机与许愿 | 林逸 | 职业魔术师林逸因解密打假风潮导致事业崩塌,颓废中感慨"如果会魔法就好了",意外触发神奇魔法系统绑定 | 强(主角动机建立+系统激活) | 高 | 50秒 | 转折+悬疑 | +\`\`\` +\`\`\` +| 第12章 山间小憩 | 凌玄、苏晚卿 | 凌玄与苏晚卿在山间歇脚,苏晚卿回忆幼时往事,两人关系略有缓和但未实质推进 | 弱(气氛过渡) | 低 | 25秒 | 平铺+情感 | +\`\`\` + +## 提取规则 + +- 忠于原文,不推测、不脑补、不加入原文未出现的情节 +- 角色使用文中主要称呼,保持一致 +- 多条平行事件线时,选对主角影响最大的一条,其余简要带过 +- 对话密集章节,关注对话推动了什么结果,而非复述对话内容 +`; + } +}