diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index 764c0b0..45af157 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -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", diff --git a/src/routes/novel/addNovel.ts b/src/routes/novel/addNovel.ts index f629679..60415ba 100644 --- a/src/routes/novel/addNovel.ts +++ b/src/routes/novel/addNovel.ts @@ -37,6 +37,7 @@ export default router.post( const chapterAllList = await u.db("o_novel").where("projectId", projectId).whereIn("id", totalNovelId); const novelClass = new u.cleanNovel(); novelClass.emitter.on("item", async (item) => { + console.log("%c Line:40 🍇 item", "background:#6ec1c2", item); await u .db("o_novel") .where("id", item.id) diff --git a/src/types/database.d.ts b/src/types/database.d.ts index c3a5bd3..58f4ecc 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,4 +1,4 @@ -// @db-hash 0041ea9843a4bb46f03412c516ec323b +// @db-hash 982ecc457e8b79aea4521c60afd06753 //该文件由脚本自动生成,请勿手动修改 export interface memories { @@ -117,6 +117,11 @@ 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; @@ -235,6 +240,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/cleanNovel.ts b/src/utils/cleanNovel.ts index 2c4e633..7a03698 100644 --- a/src/utils/cleanNovel.ts +++ b/src/utils/cleanNovel.ts @@ -26,21 +26,17 @@ 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!, }, ], - tools: skill.tools, }); - const preData = resData.text; - this.emitter.emit("item", { id: novel.id, event: preData }); return { id: novel.id!, event: preData }; } catch (e) { @@ -71,10 +67,7 @@ class CleanNovel { }; // 启动最多 concurrency 个并发任务 - const workers = Array.from( - { length: Math.min(this.concurrency, allChapters.length) }, - () => runNext() - ); + const workers = Array.from({ length: Math.min(this.concurrency, allChapters.length) }, () => runNext()); await Promise.all(workers); 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秒 | 平铺+情感 | +\`\`\` + +## 提取规则 + +- 忠于原文,不推测、不脑补、不加入原文未出现的情节 +- 角色使用文中主要称呼,保持一致 +- 多条平行事件线时,选对主角影响最大的一条,其余简要带过 +- 对话密集章节,关注对话推动了什么结果,而非复述对话内容 +`; + } +}