# Conflicts:
#	src/lib/initDB.ts
#	src/types/database.d.ts
This commit is contained in:
小帅 2026-03-19 20:26:10 +08:00
commit 1ffa226c9d
28 changed files with 31493 additions and 395 deletions

View File

@ -0,0 +1,25 @@
{
"_name_or_path": "sentence-transformers/all-MiniLM-L6-v2",
"architectures": [
"BertModel"
],
"attention_probs_dropout_prob": 0.1,
"classifier_dropout": null,
"gradient_checkpointing": false,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 384,
"initializer_range": 0.02,
"intermediate_size": 1536,
"layer_norm_eps": 1e-12,
"max_position_embeddings": 512,
"model_type": "bert",
"num_attention_heads": 12,
"num_hidden_layers": 6,
"pad_token_id": 0,
"position_embedding_type": "absolute",
"transformers_version": "4.29.2",
"type_vocab_size": 2,
"use_cache": true,
"vocab_size": 30522
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
---
name: production-agent
description: 短剧漫剧制作助手。协助用户进行剧本创作、分镜设计、角色设定、场景描述、对白润色及制作流程规划。当用户提及剧本、分镜、角色、场景、对白、短剧、漫剧等关键词时使用此技能。
---
# Production Agent
短剧漫剧制作专用技能,提供剧本创作、分镜设计、角色设定等全流程指导。
## 何时使用
当用户需要以下帮助时激活此技能:
- 剧本创作与优化
- 分镜脚本设计
- 角色设定与描述
- 场景构建与描绘
- 对白润色与调整
- 制作流程规划
## 工作指引
1. 理解用户的创作意图,根据项目类型(短剧/漫剧)调整输出风格
2. 遵循标准的剧本格式,包含场景描述、角色动作、对白等要素
3. 保持角色一致性,关注剧情连贯性
4. 输出使用中文
## 参考资料
本技能附带以下参考资料,根据任务需要使用 `read_skill_file` 工具按需加载:
- [剧本格式规范](references/script-format.md) — 场景描述、对白、旁白、转场等标准写法
- [角色设定模板](references/character-template.md) — 基础信息、性格特点、背景设定、角色弧光
- [分镜设计指南](references/storyboard-guide.md) — 景别分类、常用构图、镜头运动、分镜描述模板
**注意**:根据用户当前任务选择性加载对应参考资料,不要一次性全部加载。

View File

@ -0,0 +1,67 @@
# 角色设定模板
## 基础信息
- **姓名**
- **年龄**
- **性别**
- **职业**
- **外貌特征**:身高、体型、发型、标志性特征
## 性格特点
- **核心性格**:用 3-5 个关键词概括
- **优点**
- **缺点**
- **口头禅**
- **说话风格**:正式/随意/文艺/粗犷
## 人物背景
- **家庭背景**
- **成长经历**
- **关键转折事件**
## 人物关系
| 角色 | 关系 | 互动方式 |
|------|------|----------|
| | | |
## 角色弧光
- **起点状态**:故事开始时的状态
- **核心冲突**:角色面临的主要矛盾
- **转变契机**:什么事件触发角色转变
- **终点状态**:故事结束时的状态
## 填写示例
### 基础信息
- **姓名**:林晓
- **年龄**26 岁
- **性别**:女
- **职业**:自由插画师
- **外貌特征**165cm偏瘦黑色短发微卷左耳戴银色耳钉
### 性格特点
- **核心性格**:敏感、倔强、善良
- **优点**:对艺术有极强的感知力,重情义
- **缺点**:逃避冲突,容易钻牛角尖
- **口头禅**"算了吧"
- **说话风格**:随意,偶尔会冒出文艺的比喻
### 人物背景
- **家庭背景**:单亲家庭,母亲独自经营花店
- **成长经历**:从小在花店长大,高中获美术竞赛一等奖后决定走艺术道路
- **关键转折事件**:大学毕业作品展遭导师否定后退学
### 角色弧光
- **起点状态**:对自己的能力缺乏信心,靠接零散商稿维生
- **核心冲突**:内心的艺术追求与现实生存压力
- **转变契机**:一位老画家看中她的画作并邀请合作
- **终点状态**:重新找回创作的勇气,举办个人画展

View File

@ -0,0 +1,61 @@
# 标准剧本格式规范
## 场景描述
```
场景 1 - 内景/咖啡厅/日
```
- 以"场景 + 编号"开头
- 标注内景/外景
- 标注地点
- 标注时间(日/夜/黄昏/清晨)
## 角色动作
用括号标注角色动作和表情:
```
小明(皱眉,放下咖啡杯)
```
## 对白格式
```
小明:你确定这个计划可行?
小红:(犹豫片刻)我……我不确定。
```
## 旁白/画外音
```
【旁白】三年前的那个夏天,一切都还没有开始。
```
## 转场标注
- 切至:硬切
- 淡入/淡出:渐变过渡
- 叠化:两个画面重叠过渡
## 完整示例
```
场景 1 - 内景/咖啡厅/日
(阳光透过落地窗洒进咖啡厅,背景音乐轻柔)
小明独自坐在靠窗的位置,低头搅动着咖啡。
小红推门走入,环顾四周后看到小明。
小红:(微笑着走过来)好久不见。
小明:(抬头,愣了一下)你……怎么来了?
小红:(坐下,放下手提包)路过这里,想起你说过喜欢这家店。
【旁白】他们已经三年没有见面了。
—— 切至 ——
场景 2 - 外景/街道/夜
```

View File

@ -0,0 +1,61 @@
# 分镜设计指南
## 景别分类
| 景别 | 范围 | 用途 |
|------|------|------|
| 远景 | 环境全貌 | 交代环境、气氛渲染 |
| 全景 | 人物全身 | 展示人物与环境关系 |
| 中景 | 膝盖以上 | 日常对话、叙事推进 |
| 近景 | 胸部以上 | 表情细节、情绪传达 |
| 特写 | 面部/物件 | 强调情绪或关键道具 |
## 常用构图
- **三分法**:主体置于三分线交叉点
- **对称构图**:营造庄重、对峙感
- **引导线构图**:利用线条引导视线
- **框中框**:通过门窗等框住主体
- **前景遮挡**:增加画面层次感
## 镜头运动
- **推**:由远及近,聚焦主体
- **拉**:由近及远,展示全貌
- **摇**:固定位置旋转,扫视场景
- **移**:跟随角色移动
- **升降**:垂直运动,营造压迫或释放感
- **手持**:模拟真实视角,增加临场感
## 分镜描述模板
```
镜号001
景别:近景
角度:平视
构图:三分法,人物偏左
动作:小明缓缓抬起头
对白:小明:"原来是你。"
音效:雨声渐弱
时长3s
```
## 完整分镜示例
### 场景:咖啡厅重逢
| 镜号 | 景别 | 构图 | 内容描述 | 对白/音效 | 时长 |
|------|------|------|----------|-----------|------|
| 001 | 远景 | 对称 | 咖啡厅外观,暖黄灯光 | 轻柔钢琴BGM | 2s |
| 002 | 中景 | 三分法 | 小明独坐窗边,搅动咖啡 | 咖啡杯碰撞声 | 3s |
| 003 | 全景 | 引导线 | 小红推门进入,逆光 | 门铃声 | 2s |
| 004 | 近景 | 居中 | 小明抬头,表情从平淡到惊讶 | 小红:"好久不见。" | 3s |
| 005 | 特写 | 居中 | 小明的眼睛,瞳孔微微放大 | BGM渐弱 | 1.5s |
| 006 | 中景 | 对称 | 两人面对面坐下 | 小明:"你怎么来了?" | 3s |
### 情绪节奏说明
- 001-002平静、孤独慢节奏
- 003转折信号节奏变化
- 004-005情绪高点短促镜头
- 006回归对话节奏恢复

View File

@ -63,7 +63,6 @@
"qwen-ai-provider-v5": "^2.1.0",
"serialize-error": "^13.0.1",
"sharp": "^0.34.5",
"socket.io": "^4.8.3",
"sqlite3": "^5.1.7",
"sucrase": "^3.35.1",
"uuid": "^13.0.0",

View File

@ -0,0 +1,9 @@
class ProductionAgentTools {
state: Record<string, any> = {};
constructor(isolationKey: string) {
}
}

View File

@ -2,25 +2,18 @@ import "./logger";
import "./err";
import "./env";
import express, { Request, Response, NextFunction } from "express";
import { Server } from "socket.io";
import http from "node:http";
import expressWs from "express-ws";
import logger from "morgan";
import cors from "cors";
import buildRoute from "@/core";
import fs from "fs";
import path from "path";
import u from "@/utils";
import jwt from "jsonwebtoken";
import socketInit from "@/socket/index";
const app = express();
const server = http.createServer(app);
let server: ReturnType<typeof app.listen> | null = null;
export default async function startServe(randomPort: Boolean = false) {
const io = new Server(server, { cors: { origin: "*" } });
socketInit(io);
if (process.env.NODE_ENV == "dev") await buildRoute();
expressWs(app);
@ -78,8 +71,8 @@ export default async function startServe(randomPort: Boolean = false) {
const port = randomPort ? 0 : parseInt(process.env.PORT || "60000");
return await new Promise((resolve) => {
server.listen(port, async () => {
const address = server.address();
server = app.listen(port, async (v) => {
const address = server?.address();
const realPort = typeof address === "string" ? address : address?.port;
console.log(`[服务启动成功]: http://localhost:${realPort}`);
resolve(realPort);

View File

@ -48,20 +48,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.primary(["id"]);
table.unique(["id"]);
},
initData: async (knex) => {
},
},
//技能表
{
name: "o_skills",
builder: (table) => {
table.integer("id").notNullable();
table.string("name");
table.integer("startTime");
table.primary(["id"]);
table.unique(["id"]);
},
initData: async (knex) => { },
initData: async (knex) => {},
},
//Agent配置表
{
@ -153,78 +140,43 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
value: uuid().slice(0, 8),
},
{
key: "shortTermMemoryLength",
value: 10,
},
{
key: "searchTopK",
key: "messagesPerSummary",
value: 3,
},
{
key: "similarityThreshold",
value: 0.3,
key: "shortTermLimit",
value: 5,
},
{
key: "summaryMaxLength",
value: 500,
},
{
key: "summaryLimit",
value: 10,
},
{
key: "ragLimit",
value: 3,
},
{
key: "deepRetrieveSummaryLimit",
value: 5,
},
{
key: "modelOnnxFile",
value: '["all-MiniLM-L6-v2", "onnx", "model_fp16.onnx"]',
},
{
key: "modelDtype",
value: "fp16",
},
]);
},
},
//模型表
{
name: "o_model",
builder: (table) => {
table.integer("id").notNullable();
table.text("type");
table.text("model");
table.text("modelType");
table.text("apiKey");
table.text("baseUrl");
table.text("manufacturer");
table.integer("createTime");
table.integer("index");
table.primary(["id"]);
table.unique(["id"]);
},
initData: async (knex) => { },
},
//提示词表
{
name: "o_prompts",
builder: (table) => {
table.integer("id").notNullable();
table.text("code"); // 代号,唯一标识
table.text("name"); // 名称/描述
table.text("type"); // 类型mainAgent/subAgent/system
table.text("parentCode"); // 父级代号subAgent关联主agent
table.text("defaultValue"); // 默认提示词
table.text("customValue"); // 自定义修改值
table.primary(["id"]);
table.unique(["id"]);
table.unique(["code"]); // 代号唯一
},
initData: async (knex) => { },
},
//资产表
{
name: "o_assets",
builder: (table) => {
table.integer("id").notNullable();
table.text("name");
table.text("prompt");
table.text("remark");
table.text("type");
table.text("describe");
table.integer("imageId").unsigned().references("id").inTable("o_image");
table.integer("sonId");
table.integer("projectId");
table.integer("startTime");
table.text("state");
table.primary(["id"]);
table.unique(["id"]);
},
initData: async (knex) => { },
},
//任务中心表
{
name: "o_myTasks",
name: "o_tasks",
builder: (table) => {
table.integer("id").notNullable();
table.integer("projectId");
@ -238,7 +190,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.primary(["id"]);
table.unique(["id"]);
},
initData: async (knex) => { },
initData: async (knex) => {},
},
//小说原文表
{
@ -314,24 +266,38 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.unique(["id"]);
},
},
//剧本-大纲
//资产表
{
name: "o_scriptOutline",
name: "o_assets",
builder: (table) => {
table.integer("id").notNullable();
table.integer("scriptId").unsigned().references("id").inTable("o_script");
table.integer("outlineId").unsigned().references("id").inTable("o_outline");
table.text("name");
table.text("prompt");
table.text("remark");
table.text("type");
table.text("describe");
table.integer("imageId").unsigned().references("id").inTable("o_image");
table.integer("sonId");
table.integer("projectId");
table.integer("startTime");
table.text("state");
table.primary(["id"]);
table.unique(["id"]);
},
initData: async (knex) => {},
},
//剧本-资产
//生成图片表
{
name: "o_scriptAssets",
name: "o_image",
builder: (table) => {
table.integer("id").notNullable();
table.integer("assetsId").unsigned().references("id").inTable("o_assets");
table.integer("scriptId").unsigned().references("id").inTable("o_script");
table.text("filePath");
table.text("type");
table.integer("assetsId");
table.integer("scriptId");
table.integer("projectId");
table.integer("videoId");
table.text("state");
table.primary(["id"]);
table.unique(["id"]);
},
@ -347,13 +313,13 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.unique(["id"]);
},
},
//分镜-剧本
//flowData-剧本
{
name: "o_storyboardScript",
name: "o_flowData",
builder: (table) => {
table.integer("id").notNullable();
table.integer("storyboardId").unsigned().references("id").inTable("o_storyboard");
table.integer("scriptId").unsigned().references("id").inTable("o_script");
table.string("name");
table.integer("createTime");
table.primary(["id"]);
table.unique(["id"]);
},
@ -378,19 +344,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.unique(["id"]);
},
},
//聊天记录
{
name: "o_chatHistory",
builder: (table) => {
table.integer("id").notNullable();
table.text("type");
table.text("data");
table.text("novel");
table.integer("projectId");
table.primary(["id"]);
table.unique(["id"]);
},
},
//视频配置
{
name: "o_videoConfig",
@ -415,17 +368,19 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.unique(["id"]);
},
},
//生成图片
//供应商配置
{
name: "o_image",
name: "o_vendorConfig",
builder: (table) => {
table.integer("id").notNullable();
table.text("filePath");
table.text("type");
table.integer("assetsId");
table.text("model");
table.text("resolution");
table.text("state");
table.text("name");
table.text("version");
table.text("icon");
table.text("inputs"); // 输入项配置 JSON
table.text("inputValues"); // 输入项值 JSON
table.text("models"); // 模型配置 JSON
table.text("code"); // 模型配置 JSON
table.integer("createTime");
table.primary(["id"]);
table.unique(["id"]);
},
@ -459,7 +414,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.text("embedding"); // 向量嵌入 JSON
table.text("relatedMessageIds"); // summary关联的message id列表 JSON
table.integer("summarized").defaultTo(0); // message是否已被总结 0/1
table.integer("createdAt").notNullable();
table.integer("createTime").notNullable();
table.primary(["id"]);
table.index(["isolationKey", "type"]);
table.index(["isolationKey", "summarized"]);

View File

@ -18,14 +18,14 @@ export default router.post(
const rows = await u
.db("memories")
.where({ isolationKey, type: "message" })
.orderBy("createdAt", "asc")
.select("id", "content", "createdAt");
.orderBy("createTime", "asc")
.select("id", "content", "createTime");
const history = rows.map((row) => ({
id: row.id,
role: "user",
content: [{ type: "text", status: "complete", data: row.content }],
createdAt: row.createdAt,
createTime: row.createTime,
}));
res.status(200).send(success({ history }));

View File

@ -1,9 +1,8 @@
import { tool } from "ai";
import { z } from "zod";
import express from "express";
import { createAGUIStream } from "@/utils/agent/aguiTools";
import u from "@/utils";
import Memory from "@/utils/agent/memory";
import { useSkill } from "@/utils/agent/skillsTools";
const router = express.Router();
@ -14,19 +13,20 @@ function delay(ms: number) {
export default router.post("/", async (req, res) => {
const { prompt: text, projectId, episodesId } = req.body;
const isolationKey = `${projectId}:${episodesId}`;
//记忆
const memory = new Memory("productionAgent", isolationKey);
//skill
const skill = await useSkill("production-agent");
const agui = createAGUIStream(res);
agui.runStarted();
// 存入用户消息
await memory.add( "user",text);
await memory.add("user", text);
// 获取记忆上下文
// 获取记忆上下文
const mem = await memory.get(text);
console.log("======================================================");
// 构建记忆上下文文本(顺序:历史摘要 → 相关记忆 → 近期对话)
const memoryContext = [
mem.rag.length > 0 && `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`,
mem.summaries.length > 0 && `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`,
@ -35,9 +35,10 @@ export default router.post("/", async (req, res) => {
.filter(Boolean)
.join("\n\n");
console.log("%c Line:27 🍏 memoryContext", "background:#3f7cff", memoryContext);
const systemPrompt = `You are a helpful assistant.${memoryContext ? `\n\n以下是你对用户的记忆可作为参考\n${memoryContext}` : ""}`;
const systemPrompt = [skill.prompt, memoryContext && `## Memory\n以下是你对用户的记忆可作为参考但不要主动提及\n${memoryContext}`]
.filter(Boolean)
.join("\n\n");
const messages = [
{
@ -50,21 +51,12 @@ export default router.post("/", async (req, res) => {
system: systemPrompt,
messages,
tools: {
deepRetrieve: tool({
description: "深度检索记忆:当你需要回忆与某个关键词相关的详细历史信息时使用此工具",
inputSchema: z.object({
keyword: z.string().describe("要检索的关键词"),
}),
execute: async ({ keyword }) => {
const results = await memory.deepRetrieve(keyword);
if (results.length === 0) return { found: false, message: "未找到相关记忆" };
return { found: true, memories: results.map((r) => r.content) };
},
}),
...skill.tools,
...memory.getTools(),
},
onFinish: async (completion) => {
// 存入助手回复
await memory.add( "assistant",completion.text);
await memory.add("assistant", completion.text);
},
});

View File

@ -26,7 +26,7 @@ export default router.post(
await u.db("o_project").where("id", id).delete();
await u.db("o_novel").where("projectId", id).delete();
await u.db("o_outline").where("projectId", id).delete();
await u.db("o_myTasks").where("projectId", id).delete();
await u.db("o_tasks").where("projectId", id).delete();
await u.db("o_script").where("projectId", id).delete();
await u.db("o_assets").where("projectId", id).delete();
@ -45,8 +45,6 @@ export default router.post(
await u.db("o_video").whereIn("scriptId", scriptIds).delete();
await u.db("o_chatHistory").where("projectId", id).delete();
try {
await u.oss.deleteDirectory(`${id}/`);
console.log(`项目 ${id} 的OSS文件夹删除成功`);

View File

@ -4,14 +4,31 @@ import u from "@/utils";
const router = express.Router();
export default router.get("/", async (req, res) => {
const settingData = await u.db("o_setting").whereIn("key", ["shortTermMemoryLength", "searchTopK", "similarityThreshold"]);
const settingData = await u
.db("o_setting")
.whereIn("key", [
"messagesPerSummary",
"shortTermLimit",
"summaryMaxLength",
"summaryLimit",
"ragLimit",
"deepRetrieveSummaryLimit",
"modelOnnxFile",
"modelDtype",
]);
if (!settingData) return res.status(400).send(error(`获取记忆配置失败`));
const memoryObj: Record<string, number> = {};
const memoryObj: Record<string, number | string | string[]> = {};
settingData.forEach((i) => {
if (i.key && i.value) {
memoryObj[i.key] = Number(i.value);
let value: number | string | string[] = i.value;
if (i.key == "modelOnnxFile") {
value = JSON.parse(i.value);
} else if (i.key != "modelDtype") {
value = Number(value);
}
memoryObj[i.key] = value;
}
});

View File

@ -9,21 +9,37 @@ const router = express.Router();
export default router.post(
"/",
validateFields({
shortTermMemoryLength: z.number(), //短期记忆长度
searchTopK: z.number(), //搜索记忆条数
similarityThreshold: z.number(), //记忆相似度阈值
messagesPerSummary: z.number(),
shortTermLimit: z.number(),
summaryMaxLength: z.number(),
summaryLimit: z.number(),
ragLimit: z.number(),
deepRetrieveSummaryLimit: z.number(),
modelOnnxFile: z.array(z.string()),
modelDtype: z.string(),
}),
async (req, res) => {
const { shortTermMemoryLength, searchTopK, similarityThreshold } = req.body;
await u.db("o_setting").where("key", "shortTermMemoryLength").update({
value: shortTermMemoryLength,
});
await u.db("o_setting").where("key", "searchTopK").update({
value: searchTopK,
});
await u.db("o_setting").where("key", "similarityThreshold").update({
value: similarityThreshold,
});
const { messagesPerSummary, shortTermLimit, summaryMaxLength, summaryLimit, ragLimit, deepRetrieveSummaryLimit, modelOnnxFile, modelDtype } =
req.body;
const upsert = async (key: string, value: string) => {
const exists = await u.db("o_setting").where("key", key).first();
if (exists) {
await u.db("o_setting").where("key", key).update({ value });
} else {
await u.db("o_setting").insert({ key, value });
}
};
await upsert("messagesPerSummary", messagesPerSummary);
await upsert("shortTermLimit", shortTermLimit);
await upsert("summaryMaxLength", summaryMaxLength);
await upsert("summaryLimit", summaryLimit);
await upsert("ragLimit", ragLimit);
await upsert("deepRetrieveSummaryLimit", deepRetrieveSummaryLimit);
await upsert("modelOnnxFile", JSON.stringify(modelOnnxFile));
await upsert("modelDtype", modelDtype);
res.status(200).send(success("保存设置成功"));
},
);

View File

@ -16,27 +16,27 @@ export default router.post(
const { taskClass, state, page = 1, limit = 10 }: any = req.body;
const offset = (page - 1) * limit;
const data = await u
.db("o_myTasks")
.leftJoin("o_project", "o_project.id", "o_myTasks.projectId")
.db("o_tasks")
.leftJoin("o_project", "o_project.id", "o_tasks.projectId")
.andWhere((qb) => {
if (taskClass) {
qb.andWhere("o_myTasks.taskClass", taskClass);
qb.andWhere("o_tasks.taskClass", taskClass);
}
if (state) {
qb.andWhere("o_myTasks.state", state);
qb.andWhere("o_tasks.state", state);
}
})
.select("o_myTasks.*", "o_project.* ")
.select("o_tasks.*", "o_project.* ")
.offset(offset)
.limit(limit);
const totalQuery = (await u
.db("o_myTasks")
.db("o_tasks")
.andWhere((qb) => {
if (taskClass) {
qb.andWhere("o_myTasks.taskClass", taskClass);
qb.andWhere("o_tasks.taskClass", taskClass);
}
if (state) {
qb.andWhere("o_myTasks.state", state);
qb.andWhere("o_tasks.state", state);
}
})
.count("* as total")

View File

@ -11,7 +11,7 @@ export default router.post(
projectId: z.number(),
}),
async (req, res) => {
const data = await u.db("o_myTasks").where("projectId", req.body.projectId).select("taskClass").groupBy("taskClass");
const data = await u.db("o_tasks").where("projectId", req.body.projectId).select("taskClass").groupBy("taskClass");
res.status(200).send(success(data));
},
);

View File

@ -12,7 +12,7 @@ export default router.post(
}),
async (req, res) => {
const { taskId } = req.body;
const data = await u.db("o_myTasks").where("id", taskId).select("*").first();
const data = await u.db("o_tasks").where("id", taskId).select("*").first();
res.status(200).send(success(data));
}
);

View File

@ -1,16 +0,0 @@
import { Server } from "socket.io";
import scriptAgentChat from "./routes/scriptAgentChat";
import chat from "./routes/chat";
export default (io: Server) => {
const routes: Record<string, (nsp: ReturnType<Server["of"]>) => void> = {
scriptAgentChat,
chat,
};
for (const [name, handler] of Object.entries(routes)) {
const nsp = io.of(`/socket/${name}`);
handler(nsp);
console.log(`[Socket] 注册命名空间: /socket/${name}`);
}
};

View File

@ -1,43 +0,0 @@
import { Namespace, Socket } from "socket.io";
const users = new Map<string, string>(); // socketId -> username
export default (nsp: Namespace) => {
nsp.on("connection", (socket: Socket) => {
console.log("[chat] 用户已连接:", socket.id);
socket.on("userLogin", (username: string) => {
users.set(socket.id, username);
socket.broadcast.emit("notification", `${username} 加入了聊天室`);
});
socket.on("sendMessage", (data: { message: string }) => {
const username = users.get(socket.id) || "匿名";
const msg = {
type: "user" as const,
username,
message: data.message,
time: new Date().toLocaleTimeString(),
};
nsp.emit("newMessage", msg);
});
socket.on("typing", () => {
const username = users.get(socket.id);
if (username) socket.broadcast.emit("userTyping", username);
});
socket.on("stopTyping", () => {
socket.broadcast.emit("userStopTyping");
});
socket.on("disconnect", () => {
const username = users.get(socket.id);
if (username) {
users.delete(socket.id);
socket.broadcast.emit("notification", `${username} 离开了聊天室`);
}
console.log("[chat] 用户已断开:", socket.id);
});
});
};

View File

@ -1,43 +0,0 @@
import { Namespace, Socket } from "socket.io";
const users = new Map<string, string>(); // socketId -> username
export default (nsp: Namespace) => {
nsp.on("connection", (socket: Socket) => {
console.log("[chat] 用户已连接:", socket.id);
socket.on("userLogin", (username: string) => {
users.set(socket.id, username);
socket.broadcast.emit("notification", `${username} 加入了聊天室`);
});
socket.on("sendMessage", (data: { message: string }) => {
const username = users.get(socket.id) || "匿名";
const msg = {
type: "user" as const,
username,
message: data.message,
time: new Date().toLocaleTimeString(),
};
nsp.emit("newMessage", msg);
});
socket.on("typing", () => {
const username = users.get(socket.id);
if (username) socket.broadcast.emit("userTyping", username);
});
socket.on("stopTyping", () => {
socket.broadcast.emit("userStopTyping");
});
socket.on("disconnect", () => {
const username = users.get(socket.id);
if (username) {
users.delete(socket.id);
socket.broadcast.emit("notification", `${username} 离开了聊天室`);
}
console.log("[chat] 用户已断开:", socket.id);
});
});
};

View File

@ -1,9 +1,9 @@
// @db-hash 381c57f959d9ba09321e35a1c20674fa
// @db-hash feca77a2c2ec5b6a2989347f982558d5
//该文件由脚本自动生成,请勿手动修改
export interface memories {
'content': string;
'createdAt': number;
'createTime': number;
'embedding'?: string | null;
'id'?: string;
'isolationKey': string;
@ -58,14 +58,20 @@ export interface o_eventChapter {
'id'?: number;
'novelId'?: number | null;
}
export interface o_flowData {
'createTime'?: number | null;
'id'?: number;
'name'?: string | null;
}
export interface o_image {
'assetsId'?: number | null;
'filePath'?: string | null;
'id'?: number;
'model'?: string | null;
'resolution'?: string | null;
'projectId'?: number | null;
'scriptId'?: number | null;
'state'?: string | null;
'type'?: string | null;
'videoId'?: number | null;
}
export interface o_model {
'apiKey'?: string | null;
@ -165,6 +171,17 @@ export interface o_storyboardScript {
'scriptId'?: number | null;
'storyboardId'?: number | null;
}
export interface o_tasks {
'describe'?: string | null;
'id'?: number;
'model'?: string | null;
'projectId'?: number | null;
'reason'?: string | null;
'relatedObjects'?: string | null;
'startTime'?: number | null;
'state'?: string | null;
'taskClass'?: string | null;
}
export interface o_user {
'id'?: number;
'name'?: string | null;
@ -222,6 +239,7 @@ export interface DB {
"o_chatHistory": o_chatHistory;
"o_event": o_event;
"o_eventChapter": o_eventChapter;
"o_flowData": o_flowData;
"o_image": o_image;
"o_model": o_model;
"o_myTasks": o_myTasks;
@ -237,6 +255,7 @@ export interface DB {
"o_skills": o_skills;
"o_storyboard": o_storyboard;
"o_storyboardScript": o_storyboardScript;
"o_tasks": o_tasks;
"o_user": o_user;
"o_vendorConfig": o_vendorConfig;
"o_video": o_video;

View File

@ -1,33 +1,37 @@
import { pipeline, env as transformersEnv, FeatureExtractionPipeline } from "@huggingface/transformers";
import path from "path";
import fs from "fs";
import getPath from "@/utils/getPath";
import db from "@/utils/db";
const modelDir = path.join(
typeof process.versions?.electron !== "undefined" ? require("electron").app.getPath("userData") : process.cwd(),
"data",
"models",
"all-MiniLM-L6-v2",
);
// ── 模型配置 ──
// const modelOnnxFile = ["all-MiniLM-L6-v2", "onnx", "model_fp16.onnx"]; // 模型文件路径
// const modelDtype = "fp16" as const; // 量化类型fp32
let extractor: FeatureExtractionPipeline | null = null;
export async function initEmbedding(): Promise<void> {
if (extractor) return;
const requiredFiles = ["config.json", "tokenizer.json", "onnx/model.onnx"];
for (const file of requiredFiles) {
const filePath = path.join(modelDir, file);
if (!fs.existsSync(filePath)) {
throw new Error(`文件不存在: ${filePath}`);
}
//todo 模型配置放到这里
const modelConfigData = await db("o_setting").whereIn("key", ["modelOnnxFile", "modelDtype"]);
const modelObj: Record<string, string> = {};
Object.entries(modelConfigData).forEach(([key, value]) => {
modelObj[key] = value as string;
});
let modelOnnxFile = modelObj?.modelOnnxFile ? JSON.parse(modelObj.modelOnnxFile) : ["all-MiniLM-L6-v2", "onnx", "model_fp16.onnx"]; // 模型文件路径
let modelDtype = modelObj?.modelDtype ?? ("fp16" as const); // 量化类型fp32
const onnxPath = path.join(getPath("models"), ...modelOnnxFile);
if (!fs.existsSync(onnxPath)) {
throw new Error(`Embedding 模型文件不存在: ${onnxPath}`);
}
transformersEnv.allowRemoteModels = false;
transformersEnv.allowLocalModels = true;
transformersEnv.localModelPath = path.dirname(modelDir).replace(/\\/g, "/") + "/";
transformersEnv.localModelPath = getPath("models").replace(/\\/g, "/") + "/";
// @ts-ignore
extractor = await pipeline("feature-extraction", path.basename(modelDir), { dtype: "fp32" });
const modelFolder = modelOnnxFile[0];
// @ts-ignore - pipeline 重载联合类型过于复杂
extractor = await pipeline("feature-extraction", modelFolder, { dtype: modelDtype });
}
export async function getEmbedding(text: string): Promise<number[]> {

View File

@ -2,6 +2,8 @@ import u from "@/utils";
import { v4 as uuidv4 } from "uuid";
import { getEmbedding, cosineSimilarity } from "./embedding";
import type { memories as MemoryRow } from "@/types/database";
import { tool } from "ai";
import { z } from "zod";
// ── 可调配置 ──
const messagesPerSummary = 3; // 每累积多少条message触发一次summary生成
@ -56,7 +58,6 @@ class Memory {
async add( role: string = "user",content: string) {
const id = uuidv4();
const embedding = await getEmbedding(content);
const now = Date.now();
const isolationKey = this.isolationKey;
await u.db("memories").insert({
@ -68,11 +69,11 @@ class Memory {
embedding: JSON.stringify(embedding),
relatedMessageIds: null,
summarized: 0,
createdAt: now,
createTime: Date.now(),
} as any);
// 检查未总结消息数量
const unsummarized = await u.db("memories").where({ isolationKey, type: "message", summarized: 0 }).orderBy("createdAt", "asc");
const unsummarized = await u.db("memories").where({ isolationKey, type: "message", summarized: 0 }).orderBy("createTime", "asc");
if (unsummarized.length >= messagesPerSummary) {
const batch = unsummarized.slice(0, messagesPerSummary);
@ -91,7 +92,7 @@ class Memory {
embedding: JSON.stringify(summaryEmbedding),
relatedMessageIds: JSON.stringify(batchIds),
summarized: 0,
createdAt: Date.now(),
createTime: Date.now(),
});
// 标记已总结
@ -105,12 +106,12 @@ class Memory {
const shortTerm = await u
.db("memories")
.where({ isolationKey, type: "message", summarized: 0 })
.orderBy("createdAt", "desc")
.orderBy("createTime", "desc")
.limit(shortTermLimit);
shortTerm.reverse(); // 最旧在前
// summaries: 最近的 summary
const summaries = await u.db("memories").where({ isolationKey, type: "summary" }).orderBy("createdAt", "desc").limit(summaryLimit);
const summaries = await u.db("memories").where({ isolationKey, type: "summary" }).orderBy("createTime", "desc").limit(summaryLimit);
summaries.reverse();
// rag: 向量搜索所有 messages
@ -119,12 +120,12 @@ class Memory {
const ragResults = vectorSearch(allMessages, queryEmbedding, ragLimit);
return {
shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createdAt: m.createdAt })),
shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createTime: m.createTime })),
summaries: summaries.map((s) => ({
id: s.id,
content: s.content,
relatedMessageIds: JSON.parse(s.relatedMessageIds || "[]"),
createdAt: s.createdAt,
createTime: s.createTime,
})),
rag: ragResults.map((r) => ({ id: r.id, content: r.content, similarity: r.similarity })),
};
@ -153,9 +154,25 @@ class Memory {
if (messageIds.length === 0) return [];
const messages = await u.db("memories").whereIn("id", messageIds).orderBy("createdAt", "asc");
const messages = await u.db("memories").whereIn("id", messageIds).orderBy("createTime", "asc");
return messages.map((m) => ({ id: m.id, content: m.content, createdAt: m.createdAt }));
return messages.map((m) => ({ id: m.id, content: m.content, createTime: m.createTime }));
}
getTools() {
return {
deepRetrieve: tool({
description: "深度检索记忆:当你需要回忆与某个关键词相关的详细历史信息时使用此工具",
inputSchema: z.object({
keyword: z.string().describe("要检索的关键词"),
}),
execute: async ({ keyword }) => {
const results = await this.deepRetrieve(keyword);
if (results.length === 0) return { found: false, message: "未找到相关记忆" };
return { found: true, memories: results.map((r) => r.content) };
},
}),
};
}
}

View File

@ -0,0 +1,323 @@
import { tool } from "ai";
import { z } from "zod";
import path from "path";
import fs from "fs/promises";
import isPathInside from "is-path-inside";
import getPath from "@/utils/getPath";
// ==================== 类型 ====================
interface SkillRecord {
name: string;
description: string;
location: string; // SKILL.md 绝对路径
baseDir: string; // skill 目录绝对路径
}
// ==================== Step 2: 解析 SKILL.md ====================
/**
* SKILL.md frontmatter
* YAML >>-||-
*/
function parseFrontmatter(content: string): { name: string; description: string } {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match?.[1]) throw new Error("No frontmatter found");
const result: Record<string, string> = {};
const lines = match[1].split("\n");
let i = 0;
while (i < lines.length) {
const line = lines[i];
const colonIndex = line.indexOf(":");
if (colonIndex === -1) { i++; continue; }
const key = line.slice(0, colonIndex).trim();
if (!key) { i++; continue; }
let value = line.slice(colonIndex + 1).trim();
// 检测 YAML 块标量指示符 (>, >-, |, |-)
if (/^[>|]-?$/.test(value)) {
const fold = value.startsWith(">");
const parts: string[] = [];
i++;
while (i < lines.length) {
const next = lines[i];
// 缩进行属于当前块
if (/^\s+/.test(next)) {
parts.push(next.trim());
i++;
} else {
break;
}
}
value = fold ? parts.join(" ") : parts.join("\n");
} else {
i++;
}
result[key] = value;
}
if (!result.name) throw new Error("Frontmatter missing required field: name");
if (!result.description) throw new Error("Frontmatter missing required field: description");
return { name: result.name, description: result.description };
}
/**
* frontmatterbody
*/
function stripFrontmatter(content: string): string {
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
return match ? content.slice(match[0].length).trim() : content.trim();
}
// ==================== 资源枚举 ====================
/**
* SKILL.md
*/
async function listResources(dir: string, base: string = ""): Promise<string[]> {
const files: string[] = [];
let entries;
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch {
return files;
}
for (const entry of entries) {
const rel = base ? `${base}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
files.push(...(await listResources(path.join(dir, entry.name), rel)));
} else if (entry.name !== "SKILL.md") {
files.push(rel);
}
}
return files;
}
// ==================== Step 1: 发现 skills ====================
/**
* SKILL.md
*/
async function discoverSkills(directories: string[]): Promise<SkillRecord[]> {
const skills: SkillRecord[] = [];
const seenNames = new Set<string>();
for (const dir of directories) {
let entries;
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const baseDir = path.join(dir, entry.name);
const location = path.join(baseDir, "SKILL.md");
let content: string;
try {
content = await fs.readFile(location, "utf-8");
} catch {
continue;
}
let metadata: { name: string; description: string };
try {
metadata = parseFrontmatter(content);
} catch (e) {
console.log(`[Skill] ⚠️ 跳过 "${entry.name}"${(e as Error).message}`);
continue;
}
// 宽松校验name 与目录名不匹配时仅告警
if (metadata.name !== entry.name) {
console.log(`[Skill] ⚠️ 技能名 "${metadata.name}" 与目录名 "${entry.name}" 不一致,仍加载`);
}
if (metadata.name.length > 64) {
console.log(`[Skill] ⚠️ 技能名 "${metadata.name}" 超过 64 字符,仍加载`);
}
// 先发现的同名 skill 优先(项目级覆盖用户级)
if (seenNames.has(metadata.name)) {
console.log(`[Skill] ⚠️ 技能 "${metadata.name}" 名称冲突,已被先前发现的同名技能覆盖`);
continue;
}
seenNames.add(metadata.name);
skills.push({
name: metadata.name,
description: metadata.description,
location,
baseDir,
});
console.log(`[Skill] ✅ 发现技能:${metadata.name}${metadata.description}`);
}
}
return skills;
}
// ==================== Step 3: 构建技能目录 ====================
/**
* XML + system prompt
*/
function buildCatalog(skills: SkillRecord[]): string {
if (skills.length === 0) return "";
const skillsXml = skills
.map(
(s) =>
` <skill>\n <name>${s.name}</name>\n <description>${s.description}</description>\n </skill>`
)
.join("\n");
return [
"## Skills",
"以下技能提供了专业任务的专用指令。",
"当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。",
"加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。",
"",
"<available_skills>",
skillsXml,
"</available_skills>",
].join("\n");
}
// ==================== Step 4 & 5: 激活 + 执行工具 ====================
/**
* activate_skill read_skill_file
*/
function createSkillTools(skills: SkillRecord[]) {
// 激活去重:记录当前会话已激活的 skill
const activated = new Set<string>();
const validNames = skills.map((s) => s.name);
return {
activate_skill: tool({
description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${validNames.join(", ")}`,
inputSchema: z.object({
name: z.enum(validNames as [string, ...string[]]).describe("要激活的技能名称"),
}),
execute: async ({ name }) => {
const skill = skills.find((s) => s.name === name);
if (!skill) {
console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`);
return { error: `Skill '${name}' not found` };
}
// Step 5: 去重检查
if (activated.has(name)) {
console.log(`[Skill] 技能 "${name}" 已在当前会话中激活,跳过重复注入`);
return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` };
}
let content: string;
try {
content = await fs.readFile(skill.location, "utf-8");
} catch {
console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`);
return { error: `Failed to read SKILL.md for '${name}'` };
}
const body = stripFrontmatter(content);
const resources = await listResources(skill.baseDir);
activated.add(name);
const resourcesXml =
resources.length > 0
? `\n<skill_resources>\n${resources.map((f) => ` <file>${f}</file>`).join("\n")}\n</skill_resources>`
: "";
const wrapped = [
`<skill_content name="${skill.name}">`,
body,
"",
`Skill directory: ${skill.baseDir}`,
`相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。`,
resourcesXml,
`</skill_content>`,
].join("\n");
console.log(
`[Skill] 📖 已激活技能:${skill.name}${body.length} 字符,${resources.length} 个资源文件)`
);
return { content: wrapped };
},
}),
read_skill_file: tool({
description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。",
inputSchema: z.object({
skillName: z.string().describe("技能名称"),
filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"),
}),
execute: async ({ skillName, filePath: relPath }) => {
const skill = skills.find((s) => s.name === skillName);
if (!skill) {
console.log(`[Skill] ❌ 读取失败:未找到技能 "${skillName}"`);
return { error: `Skill '${skillName}' not found` };
}
const fullPath = path.resolve(path.join(skill.baseDir, relPath));
if (!isPathInside(fullPath, skill.baseDir)) {
console.log(`[Skill] 🚫 路径越界已拦截:"${relPath}" 超出技能目录范围`);
return { error: "Access denied: path is outside skill directory" };
}
try {
const fileContent = await fs.readFile(fullPath, "utf-8");
console.log(`[Skill] 📄 已读取文件:${skillName}/${relPath}${fileContent.length} 字符)`);
return { content: fileContent };
} catch {
console.log(`[Skill] ❌ 读取失败:未找到文件 "${relPath}"`);
return { error: `File not found: ${relPath}` };
}
},
}),
};
}
// ==================== 对外接口 ====================
/**
* 使 skill
*
* agentskills.io
* Step 1 Discovery: 扫描 data/skills/{name}
* Step 2 Parse: 提取 frontmatter
* Step 3 Disclose: 构建 XML system prompt
* Step 4 Activate: activate_skill + +
* Step 5 Manage: read_skill_file +
*
* @param name skill data/skills/{name}
*/
export async function useSkill(name: string) {
const skills = await discoverSkills([getPath("skills")]);
// 过滤出指定 skill
const matched = skills.filter((s) => s.name === name);
if (matched.length === 0) {
console.log(`[Skill] ⚠️ 未发现名为 "${name}" 的技能`);
return { prompt: "", tools: {} };
}
return {
prompt: buildCatalog(matched),
tools: createSkillTools(matched),
};
}

View File

@ -38,7 +38,7 @@ export default async function taskRecord(
}
}
const [id] = await db("o_myTasks").insert({
const [id] = await db("o_tasks").insert({
projectId,
taskClass,
relatedObjects: opteorContent,
@ -50,7 +50,7 @@ export default async function taskRecord(
/** 任务成功时调用 done(1),失败时调用 done(-1, '原因') */
return async function done(state: 1 | -1, reason?: string) {
await db("o_myTasks")
await db("o_tasks")
.where("id", id)
.update({
state: taskStateMap[state],

View File

@ -742,11 +742,6 @@
resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
"@socket.io/component-emitter@~3.1.0":
version "3.1.2"
resolved "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
"@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0":
version "1.1.0"
resolved "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8"
@ -789,7 +784,7 @@
dependencies:
"@types/node" "*"
"@types/cors@^2.8.12", "@types/cors@^2.8.19":
"@types/cors@^2.8.19":
version "2.8.19"
resolved "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342"
integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==
@ -887,7 +882,7 @@
dependencies:
undici-types "~7.18.0"
"@types/node@>=10.0.0", "@types/node@>=13.7.0":
"@types/node@>=13.7.0":
version "25.5.0"
resolved "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz#5c99f37c443d9ccc4985866913f1ed364217da31"
integrity sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==
@ -951,7 +946,7 @@
resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb"
integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==
"@types/ws@*", "@types/ws@^8.5.12":
"@types/ws@*":
version "8.18.1"
resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==
@ -993,14 +988,6 @@ accepts@^2.0.0:
mime-types "^3.0.0"
negotiator "^1.0.0"
accepts@~1.3.4:
version "1.3.8"
resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
acorn-walk@^8.3.4:
version "8.3.5"
resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496"
@ -1243,11 +1230,6 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64id@2.0.0, base64id@~2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
basic-auth@~2.0.1:
version "2.0.1"
resolved "https://registry.npmmirror.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
@ -1679,7 +1661,7 @@ cookie-signature@^1.2.1:
resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
cookie@^0.7.1, cookie@~0.7.2:
cookie@^0.7.1:
version "0.7.2"
resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
@ -1689,7 +1671,7 @@ core-util-is@1.0.2:
resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
cors@^2.8.5, cors@~2.8.5:
cors@^2.8.5:
version "2.8.6"
resolved "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96"
integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==
@ -1728,7 +1710,7 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3, debug@~4.4.1:
debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3:
version "4.4.3"
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
@ -1992,27 +1974,6 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
engine.io-parser@~5.2.1:
version "5.2.3"
resolved "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==
engine.io@~6.6.0:
version "6.6.6"
resolved "https://registry.npmmirror.com/engine.io/-/engine.io-6.6.6.tgz#9942111e7a4dc31f057e73470d7b7fcc7f74c390"
integrity sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==
dependencies:
"@types/cors" "^2.8.12"
"@types/node" ">=10.0.0"
"@types/ws" "^8.5.12"
accepts "~1.3.4"
base64id "2.0.0"
cookie "~0.7.2"
cors "~2.8.5"
debug "~4.4.1"
engine.io-parser "~5.2.1"
ws "~8.18.3"
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@ -3163,7 +3124,7 @@ mime-db@^1.54.0:
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-types@^2.1.12, mime-types@~2.1.34:
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@ -3376,11 +3337,6 @@ napi-build-utils@^2.0.0:
resolved "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
negotiator@^0.6.2:
version "0.6.4"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
@ -4340,35 +4296,6 @@ smart-buffer@^4.0.2, smart-buffer@^4.2.0:
resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
socket.io-adapter@~2.5.2:
version "2.5.6"
resolved "https://registry.npmmirror.com/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz#c697f609d36a676a46749782274607d8df52c1d8"
integrity sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==
dependencies:
debug "~4.4.1"
ws "~8.18.3"
socket.io-parser@~4.2.4:
version "4.2.6"
resolved "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.6.tgz#19156bf179af3931abd05260cfb1491822578a6f"
integrity sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.4.1"
socket.io@^4.8.3:
version "4.8.3"
resolved "https://registry.npmmirror.com/socket.io/-/socket.io-4.8.3.tgz#ca6ba1431c69532e1e0a6f496deebeb601dbc4df"
integrity sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==
dependencies:
accepts "~1.3.4"
base64id "~2.0.0"
cors "~2.8.5"
debug "~4.4.1"
engine.io "~6.6.0"
socket.io-adapter "~2.5.2"
socket.io-parser "~4.2.4"
socks-proxy-agent@^6.0.0:
version "6.2.1"
resolved "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce"
@ -5005,11 +4932,6 @@ ws@^7.4.6:
resolved "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
ws@~8.18.3:
version "8.18.3"
resolved "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1:
version "15.1.1"
resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"