diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..983b9c0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +node_modules +build +dist +logs +uploads +.git +.gitignore +*.md +LICENSE +NOTICES.txt +electron-builder.yml +backup +env +docs +*.log +.env* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 470c50c..4d06ff5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,8 +45,41 @@ jobs: dist/*.zip retention-days: 30 + build-macos: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build application + run: yarn build + + - name: Build macOS installer + run: yarn dist:mac + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-builds + path: | + dist/*.dmg + dist/*.zip + retention-days: 30 + release: - needs: build-windows + needs: [build-windows, build-macos] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') @@ -60,6 +93,12 @@ jobs: name: windows-builds path: dist + - name: Download macOS artifacts + uses: actions/download-artifact@v4 + with: + name: macos-builds + path: dist + - name: Create Release uses: softprops/action-gh-release@v2 with: @@ -70,5 +109,6 @@ jobs: files: | dist/*.exe dist/*.zip + dist/*.dmg env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 97a8734..538210a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.devtools # dependencies (bun install) node_modules diff --git a/NOTICES.txt b/NOTICES.txt index 210e056..4e21b5c 100644 --- a/NOTICES.txt +++ b/NOTICES.txt @@ -1,24 +1,42 @@ -Name: @aigne/core -License: Elastic-2.0 -Repository: https://github.com/AIGNE-io/aigne-framework +Name: @ai-sdk/anthropic +License: Apache-2.0 +Repository: https://github.com/vercel/ai ----------------------------- -Name: @aigne/openai -License: Elastic-2.0 -Repository: https://github.com/AIGNE-io/aigne-framework +Name: @ai-sdk/deepseek +License: Apache-2.0 +Repository: https://github.com/vercel/ai ----------------------------- -Name: @langchain/core -License: MIT -Repository: https://github.com/langchain-ai/langchainjs +Name: @ai-sdk/devtools +License: MIT* +Repository: N/A ----------------------------- -Name: @langchain/openai -License: MIT -Repository: https://github.com/langchain-ai/langchainjs +Name: @ai-sdk/google +License: Apache-2.0 +Repository: https://github.com/vercel/ai + +----------------------------- + +Name: @ai-sdk/openai-compatible +License: Apache-2.0 +Repository: https://github.com/vercel/ai + +----------------------------- + +Name: @ai-sdk/openai +License: Apache-2.0 +Repository: https://github.com/vercel/ai + +----------------------------- + +Name: @ai-sdk/xai +License: Apache-2.0 +Repository: https://github.com/vercel/ai ----------------------------- @@ -64,6 +82,12 @@ Repository: https://github.com/DefinitelyTyped/DefinitelyTyped ----------------------------- +Name: ai +License: Apache-2.0 +Repository: https://github.com/vercel/ai + +----------------------------- + Name: axios-retry License: Apache-2.0 Repository: https://github.com/softonic/axios-retry @@ -76,6 +100,12 @@ Repository: https://github.com/axios/axios ----------------------------- +Name: best-effort-json-parser +License: BSD-2-Clause +Repository: https://github.com/beenotung/best-effort-json-parser + +----------------------------- + Name: better-sqlite3 License: MIT Repository: https://github.com/WiseLibs/better-sqlite3 @@ -88,6 +118,12 @@ Repository: https://github.com/expressjs/cors ----------------------------- +Name: cross-env +License: MIT +Repository: https://github.com/kentcdodds/cross-env + +----------------------------- + Name: dotenv License: BSD-2-Clause Repository: https://github.com/motdotla/dotenv @@ -166,12 +202,6 @@ Repository: https://github.com/knex/knex ----------------------------- -Name: langchain -License: MIT -Repository: https://github.com/langchain-ai/langchainjs - ------------------------------ - Name: license-checker License: BSD-3-Clause Repository: https://github.com/davglass/license-checker @@ -190,6 +220,24 @@ Repository: https://github.com/remy/nodemon ----------------------------- +Name: qwen-ai-provider +License: Apache-2.0 +Repository: https://github.com/Younis-Ahmed/qwen-ai-provider + +----------------------------- + +Name: serialize-error +License: MIT +Repository: https://github.com/sindresorhus/serialize-error + +----------------------------- + +Name: serialize-error +License: MIT +Repository: https://github.com/sindresorhus/serialize-error + +----------------------------- + Name: sharp License: Apache-2.0 Repository: https://github.com/lovell/sharp @@ -214,9 +262,15 @@ Repository: https://github.com/microsoft/TypeScript ----------------------------- -Name: zod +Name: uuid License: MIT -Repository: https://github.com/colinhacks/zod +Repository: https://github.com/uuidjs/uuid + +----------------------------- + +Name: zhipu-ai-provider +License: Apache-2.0 +Repository: https://github.com/Xiang-CH/zhipu-ai-provider ----------------------------- diff --git a/README.md b/README.md index a3b3e1a..3a60cea 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Toonflow 是一款 AI 工具,能够利用 AI 技术将小说自动转化为剧 - 短视频内容创作 - 小说影视化实验 -- AI 文学 Adaptation 工具(改编工具) +- AI 文学改编工具 - 剧本开发与快速原型 - 视频素材生成 @@ -92,7 +92,7 @@ https://www.bilibili.com/video/BV1na6wB6Ea2 在安装和使用本软件之前,请准备以下内容: -- ✅ 大语言模型 AI 服务接口地址。 +- ✅ 大语言模型 AI 服务接口地址 - ✅ Sora 或豆包视频服务接口地址 - ✅ Nano Banana Pro 图片生成模型服务接口 @@ -100,17 +100,17 @@ https://www.bilibili.com/video/BV1na6wB6Ea2 ### 1. 下载与安装 -| 操作系统 | GitHub 下载 | 123云盘下载 | 说明 | -| :------: | :----------------------------------------------------------- | :-------------------------------------------------------- | :----------------------------------------------------------- | -| Windows | [Release](https://github.com/HBAI-Ltd/Toonflow-app/releases) | [123云盘](https://www.123865.com/s/bkn5Vv-E67cv) | 官方发布安装包 | -| Linux | ⚙️ 敬请期待 | ⚙️ 敬请期待 | 即将发布 | -| macOS | ⚙️ 敬请期待 | ⚙️ 敬请期待 | 即将发布 | +| 操作系统 | GitHub 下载 | 123 云盘下载 | 说明 | +| :------: | :----------------------------------------------------------- | :------------------------------------------------ | :------------- | +| Windows | [Release](https://github.com/HBAI-Ltd/Toonflow-app/releases) | [123 云盘](https://www.123865.com/s/bkn5Vv-E67cv) | 官方发布安装包 | +| Linux | ⚙️ 敬请期待 | ⚙️ 敬请期待 | 即将发布 | +| macOS | ⚙️ 敬请期待 | ⚙️ 敬请期待 | 即将发布 | -> ⚠️ 如123云盘提示需付费,仅因云盘流量受限,本软件完全开源免费。 +> ⚠️ 如 123 云盘提示需付费,仅因云盘流量受限,本软件完全开源免费。 > 目前仅支持 Windows 版本,其他系统将陆续开放。 -> 因 Gitee OS 环境限制及Release文件上传大小限制,暂不提供 Gitee Release 下载地址。 +> 因 Gitee OS 环境限制及 Release 文件上传大小限制,暂不提供 Gitee Release 下载地址。 ### 2. 启动服务 @@ -120,13 +120,105 @@ https://www.bilibili.com/video/BV1na6wB6Ea2 > 账号:`admin` > 密码:`admin123` +## Docker 部署 + +### 前置条件 + +- 已安装 [Docker](https://docs.docker.com/get-docker/)(版本 20.10+) +- 已安装 [Docker Compose](https://docs.docker.com/compose/install/)(版本 2.0+) + +### 方式一:在线部署(推荐) + +从 GitHub / Gitee 自动拉取源码并构建镜像: + +```shell +docker-compose -f docker/docker-compose.yml up -d --build +``` + +**支持的构建参数:** + +| 参数 | 说明 | 默认值 | 示例 | +| ---------- | ----------------------- | ---------- | --------------------------- | +| `GIT` | 代码仓库源 | `github` | `github` / `gitee` | +| `TAG` | 指定版本标签 | 最新 tag | `v1.0.6` | +| `BRANCH` | 指定分支 | 默认分支 | `main` / `dev` | + +**版本选择优先级**:指定 TAG > 指定 BRANCH > 自动获取最新 tag > 默认分支 + +**指定参数示例:** + +```shell +# 使用 Gitee 源(国内推荐,速度更快) +GIT=gitee docker-compose -f docker/docker-compose.yml up -d --build + +# 指定版本标签 +TAG=v1.0.6 docker-compose -f docker/docker-compose.yml up -d --build + +# 指定分支 + Gitee 源 +GIT=gitee BRANCH=dev docker-compose -f docker/docker-compose.yml up -d --build +``` + +### 方式二:本地构建 + +使用本地已有的源码直接构建,适合开发者或已克隆仓库的用户: + +```shell +# 先克隆项目(如已有则跳过) +git clone https://github.com/HBAI-Ltd/Toonflow-app.git +cd Toonflow-app + +# 使用本地源码构建 +docker-compose -f docker/docker-compose.local.yml up -d --build +``` + +### 服务端口说明 + +| 端口 | 用途 | 在线部署映射 | 本地构建映射 | +| ------- | ----------------------- | --------------- | ----------------- | +| `80` | Nginx 前端页面 | 随机端口 | `8080:80` | +| `60000` | 后端 API 服务 | `60000:60000` | `60000:60000` | + +### 数据持久化 + +默认日志目录会挂载到宿主机 `./logs` 目录。如需持久化上传文件或数据库,可在 `docker-compose.yml` 中添加 volumes: + +```yaml +volumes: + - ./logs:/var/log + - ./uploads:/app/uploads # 持久化上传文件 + - ./data:/app/data # 持久化数据库(如有) +``` + +### 常用操作命令 + +```shell +# 查看容器状态 +docker-compose -f docker/docker-compose.yml ps + +# 查看实时日志 +docker-compose -f docker/docker-compose.yml logs -f + +# 停止服务 +docker-compose -f docker/docker-compose.yml down + +# 重新构建并启动(更新版本时使用) +docker-compose -f docker/docker-compose.yml up -d --build + +# 进入容器调试 +docker exec -it toonflow sh +``` + +> ⚠️ **首次登录** +> 账号:`admin` +> 密码:`admin123` + ## 云端部署 ### 一、服务器环境要求 - **系统**:Ubuntu 20.04+ / CentOS 7+ -- **Node.js**:23.11.1+ -- **内存**:1GB+ +- **Node.js**:24.x(推荐,最低 23.11.1+) +- **内存**:2GB+ ### 二、服务器部署 @@ -208,6 +300,10 @@ pm2 restart all # 重启服务 pm2 monit # 监控面板 ``` +> ⚠️ **首次登录** +> 账号:`admin` +> 密码:`admin123` + #### 6. 部署前端网站 如需单独部署或定制前端界面,请参考前端仓库: @@ -254,12 +350,31 @@ pm2 monit # 监控面板 3. **启动开发环境** - - 使用 Node.js 运行开发服务: + 本项目包含 **后端 API 服务** 和 **前端页面** 两部分,请根据需要选择启动方式: + + - **方式一:仅启动后端服务(开发调试用)** ```bash - yarn dev #端口60000 + yarn dev ``` + > ⚠️ 此命令仅启动后端 API 服务(端口 60000),**不包含前端页面**。直接访问 `http://localhost:60000` 只能调用 API 接口,无法看到完整的网页界面。如需同时使用前端页面,请配合前端项目单独启动,或使用下方的 GUI 模式。 + + - **方式二:启动 Electron 桌面客户端(推荐完整体验)** + + ```bash + yarn dev:gui + ``` + + > 此命令会同时启动后端服务和 Electron 桌面窗口,自带内置前端页面,开箱即用,无需额外配置。适合想要完整体验所有功能的开发者。 + + **两种模式对比:** + + | 命令 | 启动内容 | 前端页面 | 适用场景 | + | -------------- | ---------------------- | -------- | -------------------------------- | + | `yarn dev` | 仅后端 API(端口 60000) | ❌ 无 | 后端开发调试、配合前端项目联调 | + | `yarn dev:gui` | 后端 + Electron 桌面端 | ✅ 内置 | 完整功能体验、桌面客户端开发调试 | + 4. **项目打包** - 编译并生成 TypeScript 文件: @@ -273,6 +388,18 @@ pm2 monit # 监控面板 ```bash yarn dist:win ``` + + - 打包为 Mac 平台可执行程序: + + ```bash + yarn dist:mac + ``` + + - 打包为 Linux 平台可执行程序: + + ```bash + yarn dist:linux + ``` 5. **代码质量检查** @@ -282,6 +409,14 @@ pm2 monit # 监控面板 yarn lint ``` +6. **AI 调试面板(可选)** + + 启动 AI SDK 的可视化调试工具,方便调试 AI 调用: + + ```bash + yarn debug:ai + ``` + ## 前端开发 如需修改前端界面,请前往前端仓库进行开发: @@ -294,6 +429,7 @@ pm2 monit # 监控面板 ## 项目结构 ``` +📂 docker/ # Docker 配置文件 📂 docs/ # 文档资源 📂 scripts/ # 构建脚本与静态资源 │ └─ 📂 web/ # 前端编译产物(内置) @@ -415,7 +551,7 @@ Toonflow 基于 AGPL-3.0 协议开源发布,许可证详情:https://www.gnu. 感谢以下开源项目为 Toonflow 提供强大支持: - [Express](https://expressjs.com/) - 快速、开放、极简的 Node.js Web 框架 -- [LangChain](https://js.langchain.com/) - 构建 LLM 应用的开发框架 +- [AI](https://ai-sdk.dev/) - 面向 TypeScript 的 AI 工具包 - [Better-SQLite3](https://github.com/WiseLibs/better-sqlite3) - 高性能 SQLite3 绑定库 - [Sharp](https://sharp.pixelplumbing.com/) - 高性能 Node.js 图像处理库 - [Axios](https://axios-http.com/) - 基于 Promise 的 HTTP 客户端 diff --git a/src/agents/models.ts b/backup/agents/models.ts similarity index 100% rename from src/agents/models.ts rename to backup/agents/models.ts diff --git a/backup/agents/outlineScript/index.ts b/backup/agents/outlineScript/index.ts new file mode 100644 index 0000000..304c7c0 --- /dev/null +++ b/backup/agents/outlineScript/index.ts @@ -0,0 +1,769 @@ +// @/agents/outlineScript.ts +import u from "@/utils"; +import { createAgent } from "langchain"; +import { EventEmitter } from "events"; +import { openAI } from "@/agents/models"; +import { z } from "zod"; +import { tool } from "@langchain/core/tools"; +import type { DB } from "@/types/database"; +// ==================== 类型定义 ==================== + +type AgentType = "AI1" | "AI2" | "director"; +type AssetType = "角色" | "道具" | "场景"; +type RefreshEvent = "storyline" | "outline" | "assets"; + +interface AssetItem { + name: string; + description: string; +} + +interface EpisodeData { + episodeIndex: number; + title: string; + chapterRange: number[]; + scenes: AssetItem[]; // 按 outline 出场顺序排列 + characters: AssetItem[]; // 按 outline 出场顺序排列 + props: AssetItem[]; // 按 outline 出场顺序排列 + coreConflict: string; + outline: string; // 最高优先级,剧本生成的唯一权威 + openingHook: string; // outline 第一句话的视觉化,开篇第一个镜头 + keyEvents: string[]; // 4个元素:[起, 承, 转, 合],严格按 outline 顺序 + emotionalCurve: string; // 对应 keyEvents 各阶段 + visualHighlights: string[]; // 按 outline 顺序排列的标志性镜头 + endingHook: string; // outline 之后的悬念延伸 + classicQuotes: string[]; +} + +// ==================== Schema 定义 ==================== + +const sceneItemSchema = z.object({ + name: z.string().describe("场景名称,如'五星酒店宴会厅'、'老旧出租屋'"), + description: z.string().describe("环境描写:空间结构、光线氛围、装饰陈设、环境细节"), +}); + +const characterItemSchema = z.object({ + name: z.string().describe("角色姓名(必须是具体人名,禁止'众人'、'群众'等集合描述)"), + description: z.string().describe("人设样貌:年龄体态、五官特征、发型妆容、服装配饰、气质神态"), +}); + +const propItemSchema = z.object({ + name: z.string().describe("道具名称"), + description: z.string().describe("样式描写:材质质感、颜色图案、形状尺寸、磨损痕迹、特殊标记"), +}); + +const episodeSchema = z.object({ + episodeIndex: z.number().describe("集数索引,从1开始递增"), + title: z.string().describe("8字内标题,疑问/感叹句,含情绪爆点"), + chapterRange: z.array(z.number()).describe("关联章节号数组"), + scenes: z.array(sceneItemSchema).describe("场景列表,按 outline 出场顺序排列"), + characters: z.array(characterItemSchema).describe("角色列表,按 outline 出场顺序排列,必须是独立个体"), + props: z.array(propItemSchema).describe("道具列表,按 outline 出场顺序排列,至少3个"), + coreConflict: z.string().describe("核心矛盾:A想要X vs B阻碍X"), + outline: z.string().describe("100-300字剧情主干,最高优先级,剧本生成的唯一权威,按时间顺序完整叙述"), + openingHook: z.string().describe("开场镜头:outline 第一句话的视觉化,必须作为剧本第一个镜头"), + keyEvents: z.array(z.string()).length(4).describe("4个元素的数组:[起, 承, 转, 合],严格按 outline 顺序从中提取"), + emotionalCurve: z.string().describe("情绪曲线,如:2(压抑)→5(反抗)→9(爆发)→3(余波),对应 keyEvents 各阶段"), + visualHighlights: z.array(z.string()).describe("3-5个标志性镜头,按 outline 叙事顺序排列"), + endingHook: z.string().describe("结尾悬念:outline 之后的延伸,勾引下集"), + classicQuotes: z.array(z.string()).describe("1-2句金句,每句≤15字,必须从原文提取"), +}); + +// ==================== 常量配置 ==================== + +// ==================== 主类 ==================== + +export default class OutlineScript { + private readonly projectId: number; + readonly emitter = new EventEmitter(); + history: Array<[string, string]> = []; + novelChapters: DB["t_novel"][] = []; + + modelName = "gpt-4.1"; + apiKey = ""; + baseURL = ""; + + constructor(projectId: number) { + this.projectId = projectId; + } + + // ==================== 公共方法 ==================== + + get events() { + return this.emitter; + } + + setNovel(chapters: DB["t_novel"][]) { + this.novelChapters = chapters; + } + + // ==================== 私有工具方法 ==================== + + private emit(event: string, data?: any) { + this.emitter.emit(event, data); + } + + private refresh(type: RefreshEvent) { + this.emit("refresh", type); + } + + private log(action: string, detail?: string) { + const msg = detail ? `${action}: ${detail}` : action; + console.log(`\n[${new Date().toLocaleTimeString()}] ${msg}\n`); + } + + private safeParseJson(str: string, fallback: T): T { + try { + return JSON.parse(str); + } catch { + return fallback; + } + } + + private uniqueByName(items: T[]): T[] { + return Array.from(new Map(items.map((item) => [item.name, item])).values()); + } + + // ==================== 数据库操作 ==================== + + private async getProjectInfo(): Promise { + return u.db("t_project").where({ id: this.projectId }).first(); + } + + private async getNovelInfo(asString = false): Promise { + const info = await this.getProjectInfo(); + if (!info) return asString ? "未查询到项目信息" : null; + + if (asString) { + const fields = [ + `小说名称: ${info.name}`, + `小说简介: ${info.intro}`, + `小说类型: ${info.type}`, + `目标短剧类型: ${info.artStyle}`, + `短剧画幅: ${info.videoRatio}`, + ]; + return fields.join("\n"); + } + return info; + } + + // ==================== 故事线操作 ==================== + + private async findStoryline() { + return u.db("t_storyline").where({ projectId: this.projectId }).first(); + } + + private async upsertStorylineContent(content: string) { + const existing = await this.findStoryline(); + if (existing) { + await u.db("t_storyline").where({ projectId: this.projectId }).update({ content }); + } else { + await u.db("t_storyline").insert({ projectId: this.projectId, content }); + } + this.refresh("storyline"); + } + + private async deleteStorylineContent() { + const deleted = await u.db("t_storyline").where({ projectId: this.projectId }).del(); + this.refresh("storyline"); + return deleted; + } + + // ==================== 大纲操作 ==================== + + private async findOutlines() { + return u.db("t_outline").where({ projectId: this.projectId }).orderBy("episode", "asc"); + } + + private async findOutlineById(id: number) { + return u.db("t_outline").where({ id, projectId: this.projectId }).first(); + } + + private async getMaxEpisode(): Promise { + const result: any = await u.db("t_outline").where({ projectId: this.projectId }).max("episode as max").first(); + return result?.max ?? 0; + } + + private async clearOutlinesAndScripts() { + const outlines = await u.db("t_outline").select("id").where({ projectId: this.projectId }); + if (outlines.length === 0) return 0; + + const outlineIds = outlines.map((o) => o.id); + await u.db("t_script").whereIn("outlineId", outlineIds).del(); + await u.db("t_outline").where({ projectId: this.projectId }).del(); + + return outlines.length; + } + + private async insertOutlines(episodes: EpisodeData[], startEpisode: number) { + const insertList = episodes.map((ep, idx) => ({ + projectId: this.projectId, + data: JSON.stringify({ ...ep, episodeIndex: startEpisode + idx }), + episode: startEpisode + idx, + })); + + await u.db("t_outline").insert(insertList); + return insertList.length; + } + + private async createEmptyScripts(outlineIds: Array<{ id: number; data: string }>) { + const scripts = outlineIds.map((item) => { + const data = this.safeParseJson>(item.data, {}); + return { + name: `第${data.episodeIndex ?? ""}集`, + content: "", + projectId: this.projectId, + outlineId: item.id, + }; + }); + + if (scripts.length > 0) { + await u.db("t_script").insert(scripts); + } + return scripts.length; + } + + private async saveOutlineData(episodes: EpisodeData[], overwrite: boolean, startEpisode?: number) { + if (overwrite) { + const cleared = await this.clearOutlinesAndScripts(); + if (cleared > 0) { + this.log("清理旧数据", `删除了 ${cleared} 条大纲及关联剧本`); + } + } + + const actualStart = overwrite ? 1 : startEpisode ?? (await this.getMaxEpisode()) + 1; + const insertedCount = await this.insertOutlines(episodes, actualStart); + + const newOutlines = await u + .db("t_outline") + .select("id", "data") + .where({ projectId: this.projectId }) + .orderBy("episode", "desc") + .limit(insertedCount); + + const scriptCount = await this.createEmptyScripts(newOutlines as Array<{ id: number; data: string }>); + + this.refresh("outline"); + return { insertedCount, scriptCount }; + } + + private async updateOutlineData(id: number, data: EpisodeData) { + const existing = await this.findOutlineById(id); + if (!existing) return false; + + await u + .db("t_outline") + .where({ id }) + .update({ data: JSON.stringify(data) }); + this.refresh("outline"); + return true; + } + + private async deleteOutlineData(ids: number[]) { + const results = await Promise.allSettled(ids.map((id) => u.deleteOutline(id, this.projectId))); + this.refresh("outline"); + return results; + } + + private formatOutlineDetail(ep: any): string { + const formatList = (items: any[], formatter: (item: any) => string) => + items?.map((item, i) => ` ${i + 1}. ${formatter(item)}`).join("\n") || " 无"; + + // keyEvents 按顺序显示:起、承、转、合 + const keyEventsLabels = ["起", "承", "转", "合"]; + const formatKeyEvents = (events: string[]) => events?.map((e, i) => ` 【${keyEventsLabels[i] || i + 1}】${e}`).join("\n") || " 无"; + + return ` +大纲ID: ${ep.id} +第 ${ep.episodeIndex} 集: ${ep.title || ""} +${"=".repeat(50)} +章节范围: ${ep.chapterRange?.join(", ") || ""} +核心矛盾: ${ep.coreConflict || ""} + +【剧情主干】(最高优先级,剧本生成的唯一权威): +${ep.outline || "无"} + +【开场镜头】(必须作为剧本第一个镜头): +${ep.openingHook || "无"} + +【剧情节点】(严格按顺序:起→承→转→合): +${formatKeyEvents(ep.keyEvents)} + +情绪曲线: ${ep.emotionalCurve || ""} + +【视觉重点】(按剧情主干顺序排列): +${formatList(ep.visualHighlights, (v) => v)} + +【结尾悬念】: +${ep.endingHook || "无"} + +【经典台词】: +${formatList(ep.classicQuotes, (q) => q)} + +角色(按出场顺序): ${ep.characters?.map((c: AssetItem) => `${c.name}(${c.description})`).join("; ") || "无"} +场景(按出场顺序): ${ep.scenes?.map((s: AssetItem) => `${s.name}(${s.description})`).join("; ") || "无"} +道具(按出场顺序): ${ep.props?.map((p: AssetItem) => `${p.name}(${p.description})`).join("; ") || "无"}`; + } + + private async getOutlineText(simplified: boolean): Promise { + const records = await this.findOutlines(); + + if (!records.length) return "当前项目暂无大纲"; + + const episodes = records.map((r) => ({ + id: r.id, + episode: r.episode, + ...this.safeParseJson>(r.data ?? "{}", {}), + })); + + if (simplified) { + const list = episodes.map((ep) => `第 ${ep.episodeIndex ?? ep.episode} 集 (id=${ep.id})`).join("\n"); + return `项目大纲 (共 ${episodes.length} 集):\n${list}`; + } + + const details = episodes.map((ep) => this.formatOutlineDetail(ep)).join("\n"); + return `项目大纲 (共 ${episodes.length} 集)\n\n${details}`; + } + + // ==================== 资产操作 ==================== + + private async findAssetByTypeAndName(type: AssetType, name: string) { + return u.db("t_assets").where({ projectId: this.projectId, type, name }).first(); + } + + private async upsertAsset(type: AssetType, item: AssetItem): Promise<"inserted" | "updated" | "skipped"> { + const existing = await this.findAssetByTypeAndName(type, item.name); + + if (!existing) { + await u.db("t_assets").insert({ + projectId: this.projectId, + type, + name: item.name, + intro: item.description, + prompt: item.description, + }); + return "inserted"; + } + + if (existing.intro !== item.description) { + await u.db("t_assets").where({ id: existing.id }).update({ + intro: item.description, + prompt: item.description, + }); + return "updated"; + } + + return "skipped"; + } + + private extractAssetsFromOutlines(outlines: Array<{ data?: string | null | undefined }>): { + characters: AssetItem[]; + props: AssetItem[]; + scenes: AssetItem[]; + } { + const result = { characters: [] as AssetItem[], props: [] as AssetItem[], scenes: [] as AssetItem[] }; + + for (const outline of outlines) { + const data = this.safeParseJson>(outline.data ?? "{}", {}); + if (data.characters) result.characters.push(...data.characters); + if (data.props) result.props.push(...data.props); + if (data.scenes) result.scenes.push(...data.scenes); + } + + return { + characters: this.uniqueByName(result.characters), + props: this.uniqueByName(result.props), + scenes: this.uniqueByName(result.scenes), + }; + } + + private async generateAssetsFromOutlines() { + const outlines = await u.db("t_outline").select("data").where({ projectId: this.projectId }); + + if (!outlines.length) return { inserted: 0, updated: 0, skipped: 0 }; + + const { characters, props, scenes } = this.extractAssetsFromOutlines(outlines); + + // 只做新增和更新,不做删除 + const stats = { inserted: 0, updated: 0, skipped: 0 }; + + const processItems = async (items: AssetItem[], type: AssetType) => { + for (const item of items) { + const result = await this.upsertAsset(type, item); + stats[result]++; + } + }; + + await processItems(characters, "角色"); + await processItems(props, "道具"); + await processItems(scenes, "场景"); + + this.refresh("assets"); + return { ...stats }; + } + + // ==================== Tool 定义:故事线 ==================== + + getStoryline = tool( + async () => { + this.log("获取故事线"); + const storyline = await this.findStoryline(); + return storyline?.content ?? "当前项目暂无故事线"; + }, + { + name: "getStoryline", + description: "获取当前项目的故事线内容", + schema: z.object({}), + verboseParsingErrors: true, + }, + ); + + saveStoryline = tool( + async ({ content }) => { + this.log("保存故事线"); + await this.upsertStorylineContent(content); + return "故事线保存成功"; + }, + { + name: "saveStoryline", + description: "保存或更新当前项目的故事线,会覆盖已有内容", + schema: z.object({ + content: z.string().describe("故事线完整内容"), + }), + verboseParsingErrors: true, + }, + ); + + deleteStoryline = tool( + async () => { + this.log("删除故事线"); + const deleted = await this.deleteStorylineContent(); + return deleted > 0 ? "故事线删除成功" : "当前项目没有故事线"; + }, + { + name: "deleteStoryline", + description: "删除当前项目的故事线", + schema: z.object({}), + verboseParsingErrors: true, + }, + ); + + // ==================== Tool 定义:大纲 ==================== + + getOutline = tool( + async ({ simplified = false }) => { + this.log("获取大纲", `简化模式: ${simplified}`); + return this.getOutlineText(simplified); + }, + { + name: "getOutline", + description: "获取项目大纲。simplified=true返回简化列表,false返回完整内容", + schema: z.object({ + simplified: z.boolean().default(false).describe("是否返回简化版本"), + }), + verboseParsingErrors: true, + }, + ); + + saveOutline = tool( + async ({ episodes, overwrite = true, startEpisode }) => { + this.log("保存大纲", `覆盖模式: ${overwrite}, 集数: ${episodes.length}`); + const { insertedCount, scriptCount } = await this.saveOutlineData(episodes as EpisodeData[], overwrite, startEpisode); + return `大纲保存成功:插入 ${insertedCount} 集大纲,创建 ${scriptCount} 个剧本记录`; + }, + { + name: "saveOutline", + description: "保存大纲数据。overwrite=true会清空现有大纲后写入,false则追加到末尾", + schema: z.object({ + episodes: z.array(episodeSchema).min(1).describe("大纲数据数组"), + overwrite: z.boolean().default(true).describe("是否覆盖现有大纲"), + startEpisode: z.number().optional().describe("追加模式下的起始集数(不填则自动递增)"), + }), + verboseParsingErrors: true, + }, + ); + + updateOutline = tool( + async ({ id, data }) => { + this.log("更新大纲", `ID: ${id}`); + const success = await this.updateOutlineData(id, data as EpisodeData); + return success ? `大纲ID ${id} 更新成功` : `未找到大纲ID: ${id}`; + }, + { + name: "updateOutline", + description: "更新指定ID的单集大纲内容", + schema: z.object({ + id: z.number().describe("大纲ID"), + data: episodeSchema.describe("更新后的大纲数据"), + }), + verboseParsingErrors: true, + }, + ); + + deleteOutline = tool( + async ({ ids }) => { + this.log("删除大纲", `IDs: ${ids.join(", ")}`); + const results = await this.deleteOutlineData(ids); + const summary = results.map((r, i) => `ID ${ids[i]}: ${r.status === "fulfilled" ? "成功" : "失败"}`).join(", "); + return `删除结果: ${summary}`; + }, + { + name: "deleteOutline", + description: "根据大纲ID删除指定大纲及关联数据", + schema: z.object({ + ids: z.array(z.number()).min(1).describe("要删除的大纲ID数组"), + }), + verboseParsingErrors: true, + }, + ); + + // ==================== Tool 定义:章节 ==================== + + getChapter = tool( + async ({ chapterNumbers }) => { + this.log("获取章节", `章节号: ${chapterNumbers.join(", ")}`); + + const results = await Promise.all( + chapterNumbers.map(async (num) => { + const chapter = await u + .db("t_novel") + .where({ projectId: this.projectId, chapterIndex: num }) + .select("chapterData", "chapterIndex", "chapter") + .first(); + + if (chapter) { + return `\n【第${chapter.chapterIndex}章 ${chapter.chapter || ""}】\n${chapter.chapterData}`; + } + return `\n【第${num}章】未找到`; + }), + ); + + return results.join("\n\n---\n"); + }, + { + name: "getChapter", + description: "根据章节编号获取小说章节的完整原文内容,支持批量获取", + schema: z.object({ + chapterNumbers: z.array(z.number()).min(1).describe("章节编号数组"), + }), + verboseParsingErrors: true, + }, + ); + + // ==================== Tool 定义:资产 ==================== + + generateAssets = tool( + async () => { + this.log("生成资产"); + const stats = await this.generateAssetsFromOutlines(); + + if (stats.inserted === 0 && stats.updated === 0 && stats.skipped === 0) { + return "当前项目没有大纲数据,无法生成资产"; + } + + return `资产生成完成:新增 ${stats.inserted},更新 ${stats.updated},保持 ${stats.skipped}`; + }, + { + name: "generateAssets", + description: "从当前项目的所有大纲中提取并生成角色、道具、场景资产,自动去重并清理冗余", + schema: z.object({}), + verboseParsingErrors: true, + }, + ); + + // ==================== 上下文构建 ==================== + + private getChapterContext(): string { + if (!this.novelChapters.length) return "无章节数据"; + return this.novelChapters.map((c) => `章节号:${c.chapterIndex},分卷:${c.reel},章节名:${c.chapter}`).join("\n"); + } + + private async buildEnvironmentContext(): Promise { + const [novelInfo, storyline, outlineCount] = await Promise.all([ + this.getNovelInfo(true), + this.findStoryline(), + u.db("t_outline").where({ projectId: this.projectId }).count("id as count").first() as any, + ]); + + return `<环境信息> +项目ID: ${this.projectId} +系统时间: ${new Date().toLocaleString()} + +${novelInfo} + +已加载章节列表: +${this.getChapterContext()} + +故事线状态: ${storyline ? "已生成" : "未生成"} +大纲状态: 共 ${outlineCount?.count ?? 0} 集 + +可用工具: +- getChapter: 获取章节原文 +- getStoryline/saveStoryline/deleteStoryline: 故事线操作 +- getOutline/saveOutline/updateOutline/deleteOutline: 大纲操作 +- generateAssets: 从大纲生成资产 +`; + } + + private buildConversationHistory(): string { + if (!this.history.length) return "无对话历史"; + return this.history.map(([role, content]) => `${role}: ${content}`).join("\n\n"); + } + + private async buildFullContext(task: string): Promise { + const env = await this.buildEnvironmentContext(); + const history = this.buildConversationHistory(); + + return `${env} + +<对话历史> +${history} + + +<当前任务> +${task} +`; + } + + // ==================== Sub-Agent ==================== + + private getSubAgentTools() { + return [this.getChapter, this.getStoryline, this.saveStoryline, this.getOutline, this.saveOutline, this.updateOutline]; + } + + private createModel() { + return openAI({ + modelName: this.modelName, + configuration: { apiKey: this.apiKey, baseURL: this.baseURL }, + }); + } + + /** + * 调用 Sub-Agent(流式传输) + */ + private async invokeSubAgent(agentType: AgentType, task: string): Promise { + this.emit("transfer", { to: agentType }); + this.log(`Sub-Agent 调用`, agentType); + + const promptsList = await u.db("t_prompts").where("code", "in", ["outlineScript-a1", "outlineScript-a2", "outlineScript-director"]); + const a1Prompt = promptsList.find((p) => p.code === "outlineScript-a1"); + const a2Prompt = promptsList.find((p) => p.code === "outlineScript-a2"); + const directorPrompt = promptsList.find((p) => p.code === "outlineScript-director"); + const errPrompts = "不论用户说什么,请直接输出Agent配置异常"; + const SYSTEM_PROMPTS: Record = { + AI1: a1Prompt?.customValue || a1Prompt?.defaultValue || errPrompts, + AI2: a2Prompt?.customValue || a2Prompt?.defaultValue || errPrompts, + director: directorPrompt?.customValue || directorPrompt?.defaultValue || errPrompts, + }; + + const context = await this.buildFullContext(task); + + const agent = createAgent({ + model: this.createModel(), + systemPrompt: SYSTEM_PROMPTS[agentType], + tools: this.getSubAgentTools(), + }); + + const stream = await agent.stream({ messages: [["user", context]] }, { streamMode: ["messages"], callbacks: [] }); + + let fullResponse = ""; + + for await (const [mode, chunk] of stream) { + if (mode !== "messages") continue; + + const [token] = chunk as any; + const block = token.contentBlocks?.[0]; + + // 处理 AI 文本流 + if (token.type === "ai" && block?.text) { + fullResponse += block.text; + this.emit("subAgentStream", { agent: agentType, text: block.text }); + } + + // 处理 tool 调用 + if (token.type === "ai" && token.tool_calls?.length) { + for (const toolCall of token.tool_calls) { + this.emit("toolCall", { agent: agentType, name: toolCall.name, args: toolCall.args }); + } + } + } + + this.emit("subAgentEnd", { agent: agentType }); + this.history.push(["ai", fullResponse]); + this.log(`Sub-Agent 完成`, agentType); + + return fullResponse ?? `${agentType}已完成任务`; + } + + private createSubAgentTool(agentType: AgentType, description: string) { + return tool(async ({ taskDescription }) => this.invokeSubAgent(agentType, taskDescription), { + name: agentType, + description, + schema: z.object({ + taskDescription: z.string().describe("具体的任务描述,包含章节范围、修改要求等详细信息"), + }), + }); + } + + // ==================== 主入口 ==================== + + private getAllTools() { + return [ + this.createSubAgentTool("AI1", "调用故事师。负责分析小说原文并生成故事线,会自行调用 saveStoryline 保存结果。"), + this.createSubAgentTool("AI2", "调用大纲师。负责根据故事线生成剧集大纲,会自行调用 saveOutline 保存结果。"), + this.createSubAgentTool("director", "调用导演。负责审核故事线和大纲,会自行调用 updateOutline 或 saveStoryline 进行修改。"), + this.getChapter, + this.getStoryline, + this.saveStoryline, + this.deleteStoryline, + this.getOutline, + this.saveOutline, + this.updateOutline, + this.deleteOutline, + this.generateAssets, + ]; + } + + async call(msg: string): Promise { + this.history.push(["user", msg]); + + const envContext = await this.buildEnvironmentContext(); + + const prompts = await u.db("t_prompts").where("code", "outlineScript-main").first(); + + const mainPrompts = prompts?.customValue || prompts?.defaultValue || "不论用户说什么,请直接输出Agent配置异常"; + + const mainAgent = createAgent({ + model: this.createModel(), + tools: this.getAllTools(), + systemPrompt: `${envContext}\n${mainPrompts}`, + }); + const stream = await mainAgent.stream({ messages: this.history }, { streamMode: ["messages"], callbacks: [] }); + + let fullResponse = ""; + + for await (const [mode, chunk] of stream) { + if (mode !== "messages") continue; + + const [token] = chunk as any; + const block = token.contentBlocks?.[0]; + + // 处理 AI 文本流 + if (token.type === "ai" && block?.text) { + fullResponse += block.text; + this.emit("data", block.text); + } + + // 处理 tool 调用 + if (token.type === "ai" && token.tool_calls?.length) { + for (const toolCall of token.tool_calls) { + this.emit("toolCall", { agent: "main", name: toolCall.name, args: toolCall.args }); + } + } + } + + this.history.push(["assistant", fullResponse]); + this.emit("response", fullResponse); + + return fullResponse; + } +} diff --git a/backup/agents/storyboard/generateImagePromptsTool.ts b/backup/agents/storyboard/generateImagePromptsTool.ts new file mode 100644 index 0000000..7c99dfa --- /dev/null +++ b/backup/agents/storyboard/generateImagePromptsTool.ts @@ -0,0 +1,128 @@ +import u from "@/utils"; + +type AspectRatio = "16:9" | "9:16" | "21:9" | "1:1" | "4:3" | "3:4" | "3:2" | "2:3"; + +interface GridLayoutResult { + cols: number; + rows: number; + totalCells: number; + placeholderCount: number; +} + +interface GridPromptOptions { + prompts: string[]; + style: string; + aspectRatio: AspectRatio; + assetsName: { name: string; intro: string }[]; +} + +interface GridPromptResult { + prompt: string; + gridLayout: GridLayoutResult; +} + +/** + * 根据prompts数量计算宫格布局 + */ +function calculateGridLayout(count: number): GridLayoutResult { + let cols: number; + let rows: number; + if (count <= 0) { + cols = 1; + rows = 1; + } else if (count === 1) { + cols = 1; + rows = 1; + } else if (count === 2) { + cols = 2; + rows = 1; + } else if (count === 3) { + cols = 3; + rows = 1; + } else if (count === 4) { + cols = 2; + rows = 2; + } else if (count <= 9) { + cols = 3; + rows = 3; + } else { + cols = 3; + rows = Math.ceil(count / 3); + } + const totalCells = cols * rows; + const placeholderCount = totalCells - count; + return { cols, rows, totalCells, placeholderCount }; +} + +/** + * 获取宽高比描述 + */ +function getAspectRatioDescription(aspectRatio: AspectRatio): string { + const descriptions: Record = { + "16:9": "电影宽银幕", + "9:16": "竖屏短剧", + "21:9": "超宽银幕史诗感", + "1:1": "方形构图", + "4:3": "经典银幕", + "3:4": "竖版经典", + "3:2": "摄影标准", + "2:3": "竖版摄影", + }; + return descriptions[aspectRatio] || "标准比例"; +} + +/** + * 生成电影级宫格分镜提示词 + */ +async function generateGridPrompt(options: GridPromptOptions): Promise { + const { prompts, style, aspectRatio, assetsName } = options; + const layout = calculateGridLayout(prompts.length); + const aspectRatioDesc = getAspectRatioDescription(aspectRatio); + + // 构建宫格位置描述 + const gridPositions: string[] = []; + for (let i = 0; i < layout.totalCells; i++) { + const row = Math.floor(i / layout.cols) + 1; + const col = (i % layout.cols) + 1; + if (i < prompts.length) { + gridPositions.push(`[第${row}行第${col}列]: ${prompts[i]}`); + } else { + gridPositions.push(`[第${row}行第${col}列]: 纯黑图`); + } + } + + // 构建资产说明 + const assetsSection = + assetsName.length > 0 + ? `\n【可用资产】\n${assetsName.map((a) => `- ${a.name}:${a.intro}`).join("\n")}\n\n⚠️ 必须使用完整资产名称,禁止简称或代词。` + : ""; + + const promptsData = await u.db("t_prompts").where("code", "generateImagePrompts").first(); + + const mainPrompts = promptsData?.customValue || promptsData?.defaultValue; + const errData = `请输出${options.prompts.length}张图片\n提示词如下:\n${options.prompts.map((p, i) => `第${i + 1}格: ${p}`).join("\n")}`; + + if (!mainPrompts) return { prompt: errData, gridLayout: layout }; + + const result = await u.ai.text.invoke({ + messages: [ + { + role: "system", + content: mainPrompts, + }, + { + role: "user", + content: `请优化以下分镜提示词:\n\n【布局】${layout.cols}列×${layout.rows}行=${ + layout.totalCells + }格\n【比例】${aspectRatio}(${aspectRatioDesc})\n【风格】${style}\n${assetsSection}\n\n【原始内容】\n${gridPositions.join("\n")}`, + }, + ], + }); + + return { + prompt: result?.text ?? errData, + gridLayout: layout, + }; +} + +export default generateGridPrompt; diff --git a/backup/agents/storyboard/generateImageTool.ts b/backup/agents/storyboard/generateImageTool.ts new file mode 100644 index 0000000..cb8b519 --- /dev/null +++ b/backup/agents/storyboard/generateImageTool.ts @@ -0,0 +1,334 @@ +import generateImagePromptsTool from "@/agents/storyboard/generateImagePromptsTool"; +import u from "@/utils"; +import sharp from "sharp"; +import { z } from "zod"; + +interface AssetItem { + name: string; + description: string; +} + +interface EpisodeData { + episodeIndex: number; + title: string; + chapterRange: number[]; + scenes: AssetItem[]; + characters: AssetItem[]; + props: AssetItem[]; + coreConflict: string; + openingHook: string; + outline: string; + keyEvents: string[]; + emotionalCurve: string; + visualHighlights: string[]; + endingHook: string; + classicQuotes: string[]; +} + +interface ImageInfo { + name: string; + type: string; + filePath: string; +} + +interface ResourceItem { + name: string; + intro: string; +} + +// 资产过滤响应的 schema +const filteredAssetsSchema = z.object({ + relevantAssets: z + .array( + z.object({ + name: z.string().describe("资产名称"), + reason: z.string().describe("选择该资产的原因"), + }), + ) + .describe("与分镜内容相关的资产列表"), +}); + +// 压缩图片直到不超过指定大小 +async function compressImage(buffer: Buffer, maxSizeBytes: number = 3 * 1024 * 1024): Promise { + if (buffer.length <= maxSizeBytes) { + return buffer; + } + let quality = 90; + let compressedBuffer = await sharp(buffer).jpeg({ quality }).toBuffer(); + while (compressedBuffer.length > maxSizeBytes && quality > 10) { + quality -= 10; + compressedBuffer = await sharp(buffer).jpeg({ quality }).toBuffer(); + } + if (compressedBuffer.length > maxSizeBytes) { + const metadata = await sharp(buffer).metadata(); + let scale = 0.9; + while (compressedBuffer.length > maxSizeBytes && scale > 0.1) { + const newWidth = Math.round((metadata.width || 1000) * scale); + const newHeight = Math.round((metadata.height || 1000) * scale); + compressedBuffer = await sharp(buffer) + .resize(newWidth, newHeight, { fit: "inside" }) + .jpeg({ quality: Math.max(quality, 30) }) + .toBuffer(); + scale -= 0.1; + } + } + return compressedBuffer; +} + +// 拼接多张图片为一张 +async function mergeImages(imagePaths: string[]): Promise { + const imageBuffers = await Promise.all(imagePaths.map((path) => u.oss.getFile(path))); + const imageMetadatas = await Promise.all(imageBuffers.map((buffer) => sharp(buffer).metadata())); + const maxHeight = Math.max(...imageMetadatas.map((m) => m.height || 0)); + const resizedImages = await Promise.all( + imageBuffers.map(async (buffer, index) => { + const metadata = imageMetadatas[index]; + const aspectRatio = (metadata.width || 1) / (metadata.height || 1); + const newWidth = Math.round(maxHeight * aspectRatio); + return { + buffer: await sharp(buffer).resize(newWidth, maxHeight, { fit: "cover" }).toBuffer(), + width: newWidth, + }; + }), + ); + let currentX = 0; + const compositeInputs = resizedImages.map(({ buffer, width }) => { + const input = { + input: buffer, + left: currentX, + top: 0, + }; + currentX += width; + return input; + }); + const mergedImage = await sharp({ + create: { + width: currentX, + height: maxHeight, + channels: 4, + background: { r: 255, g: 255, b: 255, alpha: 1 }, + }, + }) + .composite(compositeInputs) + .jpeg({ quality: 90 }) + .toBuffer(); + return compressImage(mergedImage); +} + +// 进一步压缩单张图片到指定大小 +async function compressToSize(buffer: Buffer, targetSize: number): Promise { + if (buffer.length <= targetSize) { + return buffer; + } + + const metadata = await sharp(buffer).metadata(); + let quality = 80; + let scale = 1.0; + let compressedBuffer = buffer; + + // 先尝试降低质量 + while (compressedBuffer.length > targetSize && quality > 10) { + compressedBuffer = await sharp(buffer).jpeg({ quality }).toBuffer(); + quality -= 10; + } + + // 如果还是太大,缩小尺寸 + while (compressedBuffer.length > targetSize && scale > 0.2) { + scale -= 0.1; + const newWidth = Math.round((metadata.width || 1000) * scale); + const newHeight = Math.round((metadata.height || 1000) * scale); + compressedBuffer = await sharp(buffer) + .resize(newWidth, newHeight, { fit: "inside" }) + .jpeg({ quality: Math.max(quality, 20) }) + .toBuffer(); + } + + return compressedBuffer; +} + +// 确保图片列表总大小不超过指定限制 +async function ensureTotalSizeLimit(buffers: Buffer[], maxTotalBytes: number = 10 * 1024 * 1024): Promise { + let totalSize = buffers.reduce((sum, buf) => sum + buf.length, 0); + + if (totalSize <= maxTotalBytes) { + return buffers; + } + + // 计算每张图片的平均目标大小 + const avgTargetSize = Math.floor(maxTotalBytes / buffers.length); + + // 按大小降序排列,优先压缩大图片 + const indexedBuffers = buffers.map((buf, idx) => ({ buf, idx, size: buf.length })); + indexedBuffers.sort((a, b) => b.size - a.size); + + const result = [...buffers]; + + for (const item of indexedBuffers) { + totalSize = result.reduce((sum, buf) => sum + buf.length, 0); + if (totalSize <= maxTotalBytes) { + break; + } + + // 计算这张图片需要压缩到的目标大小 + const excessSize = totalSize - maxTotalBytes; + const targetSize = Math.max(item.buf.length - excessSize, avgTargetSize, 100 * 1024); // 最小100KB + + if (item.buf.length > targetSize) { + result[item.idx] = await compressToSize(item.buf, targetSize); + } + } + + return result; +} + +// 处理图片列表,确保不超过10张且每张不超过3MB,总大小不超过10MB +async function processImages(images: ImageInfo[]): Promise { + const maxImages = 10; + let processedBuffers: Buffer[]; + + if (images.length <= maxImages) { + const buffers = await Promise.all(images.map((img) => u.oss.getFile(img.filePath))); + processedBuffers = await Promise.all(buffers.map((buffer) => compressImage(buffer))); + } else { + const mergeStartIndex = maxImages - 1; + const firstBuffers = await Promise.all(images.slice(0, mergeStartIndex).map((img) => u.oss.getFile(img.filePath))); + const compressedFirstImages = await Promise.all(firstBuffers.map((buffer) => compressImage(buffer))); + const imagesToMergeList = images.slice(mergeStartIndex).map((img) => img.filePath); + const mergedImage = await mergeImages(imagesToMergeList); + processedBuffers = [...compressedFirstImages, mergedImage]; + } + + // 确保总大小不超过10MB + return ensureTotalSizeLimit(processedBuffers); +} + +// 使用 AI 过滤与分镜相关的资产 +async function filterRelevantAssets(prompts: string[], allResources: ResourceItem[], availableImages: ImageInfo[]): Promise { + if (allResources.length === 0 || availableImages.length === 0) { + return availableImages; + } + + const availableNames = new Set(availableImages.map((img) => img.name)); + const availableResources = allResources.filter((r) => availableNames.has(r.name)); + + if (availableResources.length === 0) { + return availableImages; + } + + const result = await u.ai.text.invoke({ + messages: [ + { + role: "user", + content: `请分析以下分镜描述,从可用资产中筛选出与分镜内容直接相关的资产。 + +分镜描述: +${prompts.map((p, i) => `${i + 1}. ${p}`).join("\n")} + +可用资产列表: +${availableResources.map((r) => `- ${r.name}:${r.intro}`).join("\n")} + +请仅选择在分镜中明确出现或被提及的角色、场景、道具。不要选择与分镜内容无关的资产。`, + }, + ], + output: { + relevantAssets: z + .array( + z.object({ + name: z.string().describe("资产名称"), + reason: z.string().describe("选择该资产的原因"), + }), + ) + .describe("与分镜内容相关的资产列表"), + }, + }); + + + if (!result?.relevantAssets || result.relevantAssets.length === 0) { + return availableImages; + } + + const relevantNames = new Set(result.relevantAssets.map((a) => a.name)); + const filteredImages = availableImages.filter((img) => relevantNames.has(img.name)); + + return filteredImages.length > 0 ? filteredImages : availableImages; +} + +// 构建资产映射提示词 +function buildResourcesMapPrompts(images: ImageInfo[]): string { + if (images.length === 0) return ""; + + const mapping = images.map((item, index) => { + if (index < 9) { + return `${item.name}=图片${index + 1}`; + } else { + return `${item.name}=图10-${index - 8}`; + } + }); + + return `其中人物、场景、道具参考对照关系如下:${mapping.join(", ")}。`; +} + +export default async (cells: { prompt: string }[], scriptId: number, projectId: number) => { + const scriptData = await u.db("t_script").where({ id: scriptId, projectId }).first(); + const projectInfo = await u.db("t_project").where({ id: projectId }).first(); + + const row = await u.db("t_outline").where({ id: scriptData?.outlineId!, projectId }).first(); + const outline: EpisodeData | null = row?.data ? JSON.parse(row.data) : null; + + const resources: ResourceItem[] = outline + ? (["characters", "props", "scenes"] as const).flatMap((k) => outline[k]?.map((i) => ({ name: i.name, intro: i.description })) ?? []) + : []; + + const resourceNames = resources.map((r) => r.name); + const imagesRaw = await u.db("t_assets").whereIn("name", resourceNames).andWhere({ projectId }).select("name", "type", "filePath"); + + const allImages = imagesRaw + .sort((a, b) => { + const order = ["角色", "场景", "道具"]; + return order.indexOf(a.type!) - order.indexOf(b.type!); + }) + .filter((img) => img.filePath) as ImageInfo[]; + + if (allImages.length === 0) { + throw new Error("未找到可用的图片资源"); + } + + const cellPrompts = cells.map((c) => c.prompt); + + // 使用 AI 过滤相关资产 + const filteredImages = await filterRelevantAssets(cellPrompts, resources, allImages); + + const resourcesMapPrompts = buildResourcesMapPrompts(filteredImages); + console.log("====润色前:", cellPrompts); + const promptsData = await generateImagePromptsTool({ + prompts: cellPrompts, + style: `类型:${projectInfo?.type!},风格:${projectInfo?.artStyle!}`, + aspectRatio: projectInfo?.videoRatio! as any, + assetsName: resources, + }); + + // const prompts = `请生成${promptsData.gridLayout.totalCells}格,${promptsData.gridLayout.cols}列×${promptsData.gridLayout.rows}行宫格图。 + + // ${promptsData.prompt} + + // 注意:请严格按照提示词内容生成图片,确保人物样貌、艺术风格、色调光影一致。 + // `; + const prompts = promptsData.prompt; + console.log("====润色后:", prompts); + + const processedImages = await processImages(filteredImages); + + const contentStr = await u.ai.image({ + systemPrompt: resourcesMapPrompts, + prompt: prompts, + size: "4K", + aspectRatio: projectInfo?.videoRatio ? (projectInfo.videoRatio as any) : "16:9", + imageBase64: processedImages.map((buf) => buf.toString("base64")), + }); + + const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); + const base64Str = match?.[1] ?? contentStr; + const buffer = Buffer.from(base64Str, "base64"); + + return buffer; +}; diff --git a/backup/agents/storyboard/imageSplitting.ts b/backup/agents/storyboard/imageSplitting.ts new file mode 100644 index 0000000..5488194 --- /dev/null +++ b/backup/agents/storyboard/imageSplitting.ts @@ -0,0 +1,94 @@ +import sharp from "sharp"; + +interface GridLayoutResult { + cols: number; + rows: number; + totalCells: number; + placeholderCount: number; +} + +/** + * 计算宫格布局 + * 1张: 1x1 + * 2张: 2x1 + * 3张: 3x1 + * 4张: 2x2 + * 5-9张: 3x3 + * 10-12张: 3x4 + * 13-15张: 3x5 + * ...以此类推(3列,行数递增) + */ +function calculateGridLayout(count: number): GridLayoutResult { + let cols: number; + let rows: number; + if (count <= 0) { + cols = 1; + rows = 1; + } else if (count === 1) { + cols = 1; + rows = 1; + } else if (count === 2) { + cols = 2; + rows = 1; + } else if (count === 3) { + cols = 3; + rows = 1; + } else if (count === 4) { + cols = 2; + rows = 2; + } else if (count <= 9) { + // 5-9格统一用3x3 + cols = 3; + rows = 3; + } else { + cols = 3; + rows = Math.ceil(count / 3); + } + const totalCells = cols * rows; + const placeholderCount = totalCells - count; + return { cols, rows, totalCells, placeholderCount }; +} + +/** + * 分割宫格图片 + * @param image - 输入的宫格图片 Buffer + * @param length - 实际需要的图片数量(不包含占位图) + * @returns 分割后的单张图片 Buffer 数组 + */ +export default async (image: Buffer, length: number): Promise => { + const metadata = await sharp(image).metadata(); + const { width: totalWidth, height: totalHeight } = metadata; + + if (!totalWidth || !totalHeight) { + throw new Error("无法获取图片尺寸"); + } + + const { cols, rows } = calculateGridLayout(length); + + const cellWidth = Math.floor(totalWidth / cols); + const cellHeight = Math.floor(totalHeight / rows); + + const buffers: Buffer[] = []; + + for (let i = 0; i < length; i++) { + const row = Math.floor(i / cols); + const col = i % cols; + + const left = col * cellWidth; + const top = row * cellHeight; + + const cellBuffer = await sharp(image) + .extract({ + left, + top, + width: cellWidth, + height: cellHeight, + }) + .png() + .toBuffer(); + + buffers.push(cellBuffer); + } + + return buffers; +}; diff --git a/backup/agents/storyboard/index.ts b/backup/agents/storyboard/index.ts new file mode 100644 index 0000000..f215888 --- /dev/null +++ b/backup/agents/storyboard/index.ts @@ -0,0 +1,737 @@ +// @/agents/Storyboard.ts +import u from "@/utils"; +import { createAgent } from "langchain"; +import { EventEmitter } from "events"; +import { openAI } from "@/agents/models"; +import { z } from "zod"; +import { tool } from "@langchain/core/tools"; +import type { DB } from "@/types/database"; +import generateImageTool from "./generateImageTool"; +import imageSplitting from "./imageSplitting"; + +// ==================== 类型定义 ==================== + +type AgentType = "segmentAgent" | "shotAgent"; +type RefreshEvent = "storyline" | "outline" | "assets"; + +// ==================== 常量配置 ==================== + +// const SYSTEM_PROMPTS: Record = { +// segmentAgent: segmentPrompts, +// shotAgent: shotPrompts, +// director: directorPrompts, +// }; + +// ==================== 类型定义:片段和画面 ==================== + +interface Segment { + index: number; + description: string; + emotion?: string; + action?: string; +} + +interface Shot { + id: number; // 分镜独立ID + segmentId: number; // 所属片段ID + title: string; + x: number; + y: number; + cells: Array<{ src?: string; prompt?: string; id?: string }>; // 镜头数组,每个cell是一个镜头 +} + +// ==================== 主类 ==================== + +export default class Storyboard { + private readonly projectId: number; + private readonly scriptId: number; + readonly emitter = new EventEmitter(); + history: Array<[string, string]> = []; + novelChapters: DB["t_novel"][] = []; + + // 存储 segmentAgent 生成的片段结果 + private segments: Segment[] = []; + // 存储 shotAgent 生成的分镜结果 + private shots: Shot[] = []; + // 分镜ID计数器 + private shotIdCounter: number = 0; + // 存储正在生成分镜图的分镜ID + private generatingShots: Set = new Set(); + + modelName = "gpt-4.1"; + apiKey = ""; + baseURL = ""; + + constructor(projectId: number, scriptId: number) { + this.projectId = projectId; + this.scriptId = scriptId; + } + + // 更新shopts + public updatePreShots(segmentId: number, cellId: number, cell: { src?: string; prompt?: string; id?: string }) { + console.log("%c Line:76 🍤 segmentId", "background:#465975", segmentId); + console.log("%c Line:76 🍷 cellId", "background:#ffdd4d", cellId); + console.log("%c Line:76 🍢 cell", "background:#ffdd4d", cell); + const shotIndex = this.shots.findIndex((item) => item.segmentId === segmentId); + if (shotIndex === -1) { + return `分镜 ${segmentId} 不存在,请检查分镜ID是否正确`; + } + const cellIndex = this.shots[shotIndex].cells.findIndex((item) => item.id === cellId.toString()); + if (cellIndex === -1) { + return `镜头 ${cellId} 不存在,请检查镜头ID是否正确`; + } + this.shots[shotIndex].cells[cellIndex] = { ...this.shots[shotIndex].cells[cellIndex], ...cell }; + } + + // ==================== 公共方法 ==================== + + get events() { + return this.emitter; + } + // ==================== 私有工具方法 ==================== + + private emit(event: string, data?: any) { + this.emitter.emit(event, data); + } + + private refresh(type: RefreshEvent) { + this.emit("refresh", type); + } + + private log(action: string, detail?: string) { + const msg = detail ? `${action}: ${detail}` : action; + console.log(`\n[${new Date().toLocaleTimeString()}] ${msg}\n`); + } + + // ==================== 剧本相关操作 ==================== + + getScript = tool( + async () => { + this.log("获取剧本", `scriptId: ${this.scriptId}`); + const script = await u.db("t_script").where({ id: this.scriptId, projectId: this.projectId }).first(); + if (!script) throw new Error("剧本不存在"); + return `剧本集:${script.name}\n\n内容:\n\`\`\`${script.content}\`\`\``; + }, + { + name: "getScript", + description: "获取剧本内容", + schema: z.object({}), + verboseParsingErrors: true, + }, + ); + + // ==================== 资产相关操作 ==================== + + /** + * 获取资产列表(供 segmentAgent 和 shotAgent 调用) + */ + getAssets = tool( + async () => { + this.log("获取资产列表", `scriptId: ${this.scriptId}`); + const scriptData = await u.db("t_script").where({ id: this.scriptId, projectId: this.projectId }).first(); + const row = await u.db("t_outline").where({ id: scriptData?.outlineId!, projectId: this.projectId }).first(); + const outline: any | null = row?.data ? JSON.parse(row.data) : null; + + if (!outline) { + return "暂无资产数据"; + } + + // 提取资源名称和描述(与generateImageTool保持一致的字段名) + const resources = outline + ? (["characters", "props", "scenes"] as const).flatMap( + (k) => outline[k]?.map((i: any) => ({ name: i.name, description: i.description })) ?? [], + ) + : []; + + if (resources.length === 0) { + return "暂无资产数据"; + } + + // 分类提取资源并格式化 + const characters = outline?.characters?.map((item: any) => `- ${item.name}${item.description ? `:${item.description}` : ""}`) ?? []; + const props = outline?.props?.map((item: any) => `- ${item.name}${item.description ? `:${item.description}` : ""}`) ?? []; + const scenes = outline?.scenes?.map((item: any) => `- ${item.name}${item.description ? `:${item.description}` : ""}`) ?? []; + + const sections = [ + characters.length ? `【角色】\n${characters.join("\n")}` : "", + props.length ? `【道具】\n${props.join("\n")}` : "", + scenes.length ? `【场景】\n${scenes.join("\n")}` : "", + ].filter(Boolean); + + if (sections.length === 0) { + return "暂无资产数据"; + } + + return `<资产列表> +${sections.join("\n\n")} + + +⚠️ 重要规则: +1. 必须原封不动地使用上述资产名称,禁止使用近义词、缩写或任何变体 +2. 禁止在资产名称前后添加修饰词 +3. 禁止捏造资产列表中不存在的角色、场景、道具`; + }, + { + name: "getAssets", + description: "获取资产列表(角色、道具、场景),包含名称和详细介绍。生成片段和分镜时必须先调用此工具获取资产信息,确保名称一致性", + schema: z.object({}), + verboseParsingErrors: true, + }, + ); + + // ==================== 片段和分镜工具 ==================== + + /** + * 获取当前存储的片段数据(供 shotAgent 调用) + */ + getSegments = tool( + async () => { + this.log("获取片段数据", `共 ${this.segments.length} 个片段`); + if (this.segments.length === 0) { + return "暂无片段数据,请先调用 segmentAgent 生成片段"; + } + return JSON.stringify(this.segments, null, 2); + }, + { + name: "getSegments", + description: "获取当前已生成的片段数据,用于生成分镜", + schema: z.object({}), + verboseParsingErrors: true, + }, + ); + + /** + * 更新/存储片段数据(供 segmentAgent 调用) + */ + updateSegments = tool( + async ({ segments }: { segments: Segment[] }) => { + this.log("更新片段数据", `共 ${segments.length} 个片段`); + this.segments = segments; + this.emit("segmentsUpdated", this.segments); + return `成功存储 ${segments.length} 个片段`; + }, + { + name: "updateSegments", + description: "存储生成的片段数据,segmentAgent 在生成片段后必须调用此工具保存结果", + schema: z.object({ + segments: z + .array( + z.object({ + index: z.number().describe("片段序号"), + description: z.string().describe("片段描述"), + emotion: z.string().optional().describe("情绪氛围"), + action: z.string().optional().describe("主要动作"), + }), + ) + .describe("片段数组"), + }), + verboseParsingErrors: true, + }, + ); + + /** + * 添加分镜(供 shotAgent 调用) + */ + addShots = tool( + async ({ shots }: { shots: Array<{ segmentIndex: number; prompts: string[] }> }) => { + const added: { id: number; segmentIndex: number }[] = []; + const skipped: number[] = []; + + for (const item of shots) { + const exists = this.shots.some((f) => f.segmentId === item.segmentIndex); + if (exists) { + skipped.push(item.segmentIndex); + continue; + } + // 分配独立的分镜ID + this.shotIdCounter++; + const shotId = this.shotIdCounter; + this.shots.push({ + id: shotId, + segmentId: item.segmentIndex, + title: `分镜 ${shotId}`, + x: 0, + y: 0, + cells: item.prompts.map((prompt) => ({ id: u.uuid(), prompt })), + }); + added.push({ id: shotId, segmentIndex: item.segmentIndex }); + } + + const addedInfo = added.map((a) => `分镜${a.id}(片段${a.segmentIndex})`).join(", "); + this.log("添加分镜", `新增: [${addedInfo}], 跳过片段: [${skipped.join(", ")}]`); + this.emit("shotsUpdated", this.shots); + + if (skipped.length) { + return `已添加${addedInfo};片段 ${skipped.join(", ")} 已存在分镜被跳过。当前共 ${this.shots.length} 个分镜`; + } + return `已添加${addedInfo}。当前共 ${this.shots.length} 个分镜`; + }, + { + name: "addShots", + description: "添加新的分镜。每个分镜有独立ID,包含多个镜头(每个镜头对应一个提示词)。如果片段已存在分镜会跳过", + schema: z.object({ + shots: z + .array( + z.object({ + segmentIndex: z.number().describe("对应的片段序号"), + prompts: z.array(z.string()).describe("镜头提示词数组,每个提示词对应一个镜头(中文)"), + }), + ) + .describe("要添加的分镜数组"), + }), + verboseParsingErrors: true, + }, + ); + + /** + * 更新指定分镜(供 shotAgent 调用) + * 保留原有 cells 的 id 和 src 字段,只更新 prompt + */ + updateShots = tool( + async ({ shotId, prompts }: { shotId: number; prompts: string[] }) => { + const existingIndex = this.shots.findIndex((item) => item.id === shotId); + + if (existingIndex === -1) { + return `分镜 ${shotId} 不存在,请检查分镜ID是否正确`; + } + + const existingCells = this.shots[existingIndex].cells; + + // 更新 cells,保留原有的 id 和 src 字段 + this.shots[existingIndex].cells = prompts.map((prompt, i) => { + const existingCell = existingCells[i]; + if (existingCell) { + // 保留原有 cell 的 id 和 src,只更新 prompt + return { ...existingCell, prompt }; + } else { + // 新增的 cell + return { id: u.uuid(), prompt }; + } + }); + + this.log("更新分镜", `分镜 ${shotId}`); + this.emit("shotsUpdated", this.shots); + + return `已更新分镜 ${shotId}`; + }, + { + name: "updateShots", + description: "更新指定分镜的镜头提示词。通过分镜ID指定要修改的分镜", + schema: z.object({ + shotId: z.number().describe("要更新的分镜ID"), + prompts: z.array(z.string()).describe("新的镜头提示词数组,每个提示词对应一个镜头"), + }), + verboseParsingErrors: true, + }, + ); + + /** + * 删除指定分镜(供 shotAgent 调用) + */ + deleteShots = tool( + async ({ shotIds }: { shotIds: number[] }) => { + const deleted: number[] = []; + const notFound: number[] = []; + + for (const shotId of shotIds) { + const idx = this.shots.findIndex((item) => item.id === shotId); + if (idx === -1) { + notFound.push(shotId); + } else { + this.shots.splice(idx, 1); + deleted.push(shotId); + } + } + + this.log("删除分镜", `删除: [分镜${deleted.join(", 分镜")}], 未找到: [分镜${notFound.join(", 分镜")}]`); + this.emit("shotsUpdated", this.shots); + + if (notFound.length) { + return `已删除分镜 ${deleted.join(", ")};分镜 ${notFound.join(", ")} 不存在。当前共 ${this.shots.length} 个分镜`; + } + return `已删除分镜 ${deleted.join(", ")}。当前共 ${this.shots.length} 个分镜`; + }, + { + name: "deleteShots", + description: "删除指定的分镜。通过分镜ID指定要删除的分镜", + schema: z.object({ + shotIds: z.array(z.number()).describe("要删除的分镜ID数组"), + }), + verboseParsingErrors: true, + }, + ); + + /** + * 生成分镜图(异步执行,使用 nanoBanana) + */ + generateShotImage = tool( + async ({ shotIds }: { shotIds: number[] }) => { + const toGenerate: number[] = []; + const alreadyGenerating: number[] = []; + const notFound: number[] = []; + + for (const shotId of shotIds) { + const shot = this.shots.find((f) => f.id === shotId); + if (!shot) { + notFound.push(shotId); + continue; + } + if (this.generatingShots.has(shotId)) { + alreadyGenerating.push(shotId); + continue; + } + toGenerate.push(shotId); + } + + if (toGenerate.length === 0) { + if (notFound.length) { + return `分镜 ${notFound.join(", ")} 不存在,请检查分镜ID是否正确`; + } + if (alreadyGenerating.length) { + return `分镜 ${alreadyGenerating.join(", ")} 正在生成中,请稍候`; + } + return "没有需要生成的分镜"; + } + + // 标记为正在生成 + for (const id of toGenerate) { + this.generatingShots.add(id); + } + + // 通知前端开始生成 + this.emit("shotImageGenerateStart", { shotIds: toGenerate }); + this.log("开始生成分镜图", `分镜: [${toGenerate.join(", ")}]`); + + // 异步执行图片生成(不阻塞 Agent 流程) + this.executeShotImageGeneration(toGenerate).catch((err) => { + this.log("分镜图生成错误", err.message); + this.emit("shotImageGenerateError", { shotIds: toGenerate, error: err.message }); + }); + + let result = `已开始为分镜 ${toGenerate.join(", ")} 生成分镜图,生成过程在后台进行`; + if (alreadyGenerating.length) { + result += `;分镜 ${alreadyGenerating.join(", ")} 正在生成中`; + } + if (notFound.length) { + result += `;分镜 ${notFound.join(", ")} 不存在`; + } + return result; + }, + { + name: "generateShotImage", + description: + "为指定分镜生成分镜图。每个分镜会根据其所有提示词生成一张完整宫格图,然后自动分割为单格图片。通过分镜ID指定,不需要指定具体格子,整个分镜是一个完整的生成单元", + schema: z.object({ + shotIds: z.array(z.number()).describe("要生成分镜图的分镜ID数组"), + }), + verboseParsingErrors: true, + }, + ); + + /** + * 执行分镜图生成的具体逻辑(异步并发) + * 每个分镜包含多个镜头,所有镜头的提示词合并生成一张宫格图,再分割为单张镜头图片 + */ + async executeShotImageGeneration(shotIds: number[]): Promise { + await Promise.all(shotIds.map((shotId) => this.generateSingleShotImage(shotId))); + } + + /** + * 生成单个分镜的图片 + */ + private async generateSingleShotImage(shotId: number): Promise { + try { + const shot = this.shots.find((f) => f.id === shotId); + if (!shot) return; + + // 提取所有镜头的有效提示词 + const prompts: string[] = shot.cells.map((c) => c.prompt).filter((p): p is string => Boolean(p)); + + if (prompts.length === 0) { + this.log("跳过分镜图生成", `分镜 ${shotId} 没有有效的镜头提示词`); + this.generatingShots.delete(shotId); + return; + } + + // 通知前端正在生成该分镜 + this.emit("shotImageGenerateProgress", { shotId, status: "generating", message: "正在调用 AI 生成宫格图片" }); + + // 根据所有镜头提示词生成宫格图片 + const gridImage = await generateImageTool( + prompts.map((p) => ({ prompt: p })), + this.scriptId, + this.projectId, + ); + + // 通知前端正在分割图片 + this.emit("shotImageGenerateProgress", { shotId, status: "splitting", message: "正在分割宫格图片为单张镜头图" }); + + // 分割宫格图片为单张镜头图片 + const imageBuffers = await imageSplitting(gridImage, prompts.length); + + // 通知前端正在保存图片 + this.emit("shotImageGenerateProgress", { shotId, status: "saving", message: `正在保存 ${imageBuffers.length} 张镜头图片` }); + + // 保存分割后的镜头图片到 OSS,并获取文件路径 + const timestamp = Date.now(); + const imagePaths: string[] = []; + + for (let i = 0; i < imageBuffers.length; i++) { + const fileName = `${this.projectId}/chat/${this.scriptId}/storyboard/shot_${shotId}_take_${i}_${timestamp}.png`; + await u.oss.writeFile(fileName, imageBuffers[i]); + const imageUrl = await u.oss.getFileUrl(fileName); + imagePaths.push(imageUrl); + + // 每保存一张镜头图片通知进度 + this.emit("shotImageGenerateProgress", { + shotId, + status: "saving", + message: `已保存 ${i + 1}/${imageBuffers.length} 张镜头图片`, + progress: Math.round(((i + 1) / imageBuffers.length) * 100), + }); + } + + // 更新每个镜头的 src 字段 + shot.cells = shot.cells.map((cell, i) => ({ + id: u.uuid(), + ...cell, + src: imagePaths[i] || cell.src, + })); + + // 生成完成后更新状态 + this.generatingShots.delete(shotId); + this.emit("shotImageGenerateComplete", { shotId, shot, imagePaths }); + this.emit("shotsUpdated", this.shots); + this.log("分镜图生成完成", `分镜 ${shotId},共 ${imagePaths.length} 张镜头图片`); + } catch (err: any) { + this.generatingShots.delete(shotId); + this.emit("shotImageGenerateError", { shotId, error: err.message }); + this.log("分镜图生成失败", `分镜 ${shotId}: ${err.message}`); + } + } + + // ==================== 公共访问器 ==================== + + /** + * 获取当前片段数据 + */ + getSegmentsData(): Segment[] { + return this.segments; + } + + /** + * 获取当前分镜数据 + */ + getShotsData(): Shot[] { + return this.shots; + } + + // ==================== 上下文构建 ==================== + + private async buildEnvironmentContext(): Promise { + const projectInfo = await u.db("t_project").where({ id: this.projectId }).first(); + + const row = await u.db("t_outline").where({ id: this.scriptId, projectId: this.projectId }).first(); + const outline: any | null = row?.data ? JSON.parse(row.data) : null; + + // 分类提取资源名称 + const characters = outline?.characters?.map((i: any) => i.name) ?? []; + const props = outline?.props?.map((i: any) => i.name) ?? []; + const scenes = outline?.scenes?.map((i: any) => i.name) ?? []; + + const assetList = + [ + characters.length ? `【角色】${characters.join("、")}` : "", + props.length ? `【道具】${props.join("、")}` : "", + scenes.length ? `【场景】${scenes.join("、")}` : "", + ] + .filter(Boolean) + .join("\n") || "无"; + + return `<环境信息> +项目ID: ${this.projectId} +系统时间: ${new Date().toLocaleString()} + +项目名称: ${projectInfo?.name || "未知"} +项目简介: ${projectInfo?.intro || "无"} +类型: ${projectInfo?.type || "未知"} +风格: ${projectInfo?.artStyle || "未知"} +视频比例: ${projectInfo?.videoRatio || "未知"} + +资产列表: +${assetList} + +`; + } + + private buildConversationHistory(): string { + if (!this.history.length) return "无对话历史"; + return this.history.map(([role, content]) => `${role}: ${content}`).join("\n\n"); + } + + private async buildFullContext(task: string): Promise { + const env = await this.buildEnvironmentContext(); + const history = this.buildConversationHistory(); + + return `${env} + +<对话历史> +${history} + + +<当前任务> +${task} +`; + } + + // ==================== Sub-Agent ==================== + + private createModel() { + return openAI({ + modelName: this.modelName, + configuration: { apiKey: this.apiKey, baseURL: this.baseURL }, + }); + } + + /** + * 获取不同 Sub-Agent 可用的工具 + */ + private getSubAgentTools(agentType: AgentType) { + switch (agentType) { + case "segmentAgent": + // segmentAgent 可以获取剧本和资产,并需要调用 updateSegments 保存结果 + return [this.getScript, this.getAssets, this.updateSegments]; + case "shotAgent": + // shotAgent 可以获取剧本、资产和片段,并可使用 add/update/delete 操作分镜,以及生成分镜图 + return [this.getScript, this.getAssets, this.getSegments, this.addShots, this.updateShots, this.deleteShots, this.generateShotImage]; + default: + return [this.getScript]; + } + } + + /** + * 调用 Sub-Agent(流式传输) + */ + private async invokeSubAgent(agentType: AgentType, task: string): Promise { + this.emit("transfer", { to: agentType }); + this.log(`Sub-Agent 调用`, agentType); + + const promptsList = await u.db("t_prompts").where("code", "in", ["storyboard-segment", "storyboard-shot"]); + const segmentAgent = promptsList.find((p) => p.code === "storyboard-segment"); + const shotAgent = promptsList.find((p) => p.code === "storyboard-shot"); + const errPrompts = "不论用户说什么,请直接输出Agent配置异常"; + const SYSTEM_PROMPTS: Record = { + segmentAgent: segmentAgent?.customValue || segmentAgent?.defaultValue || errPrompts, + shotAgent: shotAgent?.customValue || shotAgent?.defaultValue || errPrompts, + }; + + const context = await this.buildFullContext(task); + + const agent = createAgent({ + model: this.createModel(), + systemPrompt: SYSTEM_PROMPTS[agentType], + tools: this.getSubAgentTools(agentType), + }); + + const stream = await agent.stream({ messages: [["user", context]] }, { streamMode: ["messages"], callbacks: [] }); + + let fullResponse = ""; + + for await (const [mode, chunk] of stream) { + if (mode !== "messages") continue; + const [token] = chunk as any; + const block = token.contentBlocks?.[0]; + + // 处理 AI 文本流 + if (token.type === "ai" && block?.text) { + fullResponse += block.text; + this.emit("subAgentStream", { agent: agentType, text: block.text }); + } + // 处理 tool 调用 + if (token.type === "ai" && token.tool_calls?.length) { + for (const toolCall of token.tool_calls) { + this.emit("toolCall", { agent: agentType, name: toolCall.name, args: toolCall.args }); + } + } + } + + this.emit("subAgentEnd", { agent: agentType }); + this.history.push(["ai", fullResponse]); + this.log(`Sub-Agent 完成`, agentType); + return fullResponse; + } + + private createSubAgentTool(agentType: AgentType, description: string) { + return tool(async ({ taskDescription }) => this.invokeSubAgent(agentType, taskDescription), { + name: agentType, + description, + schema: z.object({ + taskDescription: z.string().describe("具体的任务描述,包含章节范围、修改要求等详细信息"), + }), + }); + } + + // ==================== 主入口 ==================== + + private getAllTools() { + return [ + this.createSubAgentTool( + "segmentAgent", + "调用片段师。负责根据剧本生成片段,会自行调用 getScript 获取剧本内容,并调用 updateSegments 保存片段结果。", + ), + this.createSubAgentTool( + "shotAgent", + "调用分镜师。负责根据片段生成分镜提示词,会自行调用 getSegments 获取片段数据,并调用 addShots/updateShots 保存分镜结果。", + ), + // this.createSubAgentTool("director", "调用导演。负责审核故事线和大纲,会自行调用 updateOutline 或 saveStoryline 进行修改。"), + this.getScript, + this.getSegments, + this.generateShotImage, + ...this.getSubAgentTools("segmentAgent"), + ...this.getSubAgentTools("shotAgent"), + ]; + } + + async call(msg: string): Promise { + console.log("模型名称:", this.modelName); + this.history.push(["user", msg]); + + const envContext = await this.buildEnvironmentContext(); + + const prompts = await u.db("t_prompts").where("code", "storyboard-main").first(); + + const mainPrompts = prompts?.customValue || prompts?.defaultValue || "不论用户说什么,请直接输出Agent配置异常"; + + const mainAgent = createAgent({ + model: this.createModel(), + tools: this.getAllTools(), + systemPrompt: `${envContext}\n${mainPrompts}`, + }); + const stream = await mainAgent.stream({ messages: this.history }, { streamMode: ["messages"], callbacks: [] }); + + let fullResponse = ""; + + for await (const [mode, chunk] of stream) { + if (mode !== "messages") continue; + const [token] = chunk as any; + const block = token.contentBlocks?.[0]; + // 处理 AI 文本流 + if (token.type === "ai" && block?.text) { + fullResponse += block.text; + this.emit("data", block.text); + } + + // 处理 tool 调用 + if (token.type === "ai" && token.tool_calls?.length) { + for (const toolCall of token.tool_calls) { + this.emit("toolCall", { agent: "main", name: toolCall.name, args: toolCall.args }); + } + } + } + + this.history.push(["assistant", fullResponse]); + this.emit("response", fullResponse); + + return fullResponse; + } +} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..bd7c07c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,120 @@ +# 构建阶段 +FROM node:24-alpine AS builder + +WORKDIR /app + +# 定义构建参数 +ARG GIT=github +ARG TAG="" +ARG BRANCH="" + +# 安装 git +RUN apk add --no-cache git + +RUN npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ + +# 根据参数选择仓库源,支持 TAG / BRANCH 切换 +# 优先级: TAG > BRANCH > 最新 tag > 默认分支 +RUN if [ "$GIT" = "gitee" ]; then \ + REPO_URL="https://gitee.com/HBAI-Ltd/Toonflow-app.git"; \ + else \ + REPO_URL="https://github.com/HBAI-Ltd/Toonflow-app.git"; \ + fi && \ + echo "Cloning from: $REPO_URL" && \ + git clone "$REPO_URL" . && \ + if [ -n "$TAG" ]; then \ + echo "Checking out specified tag: $TAG" && \ + git checkout "$TAG"; \ + elif [ -n "$BRANCH" ]; then \ + echo "Checking out specified branch: $BRANCH" && \ + git checkout "$BRANCH"; \ + else \ + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || git tag --sort=-v:refname | head -n 1) && \ + if [ -n "$LATEST_TAG" ]; then \ + echo "Checking out latest tag: $LATEST_TAG" && \ + git checkout "$LATEST_TAG"; \ + else \ + echo "No tags found, using default branch"; \ + fi; \ + fi && \ + echo "Current version:" && git describe --tags --always + +RUN yarn install --frozen-lockfile + +RUN yarn build + +# 生产阶段 +FROM node:24-alpine + +WORKDIR /app + +# 安装 nginx 和 supervisor +RUN apk add --no-cache nginx supervisor && \ + mkdir -p /var/lib/nginx/logs /var/log/nginx && \ + npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ && \ + npm install -g pm2 + +# 复制后端文件 +COPY --from=builder /app/build ./build +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/yarn.lock ./ + +# 复制静态页面到 nginx 目录 +COPY --from=builder /app/scripts/web /usr/share/nginx/html + +# 只安装生产依赖 +RUN yarn install --frozen-lockfile --production + +# 配置 nginx +RUN cat > /etc/nginx/http.d/default.conf << 'EOF' +server { + listen 80; + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } +} +EOF + +# 配置 nginx 主配置,日志输出到 stderr/stdout +RUN sed -i 's|error_log /var/log/nginx/error.log warn;|error_log /dev/stderr warn;|g' /etc/nginx/nginx.conf || true && \ + sed -i 's|access_log /var/log/nginx/access.log main;|access_log /dev/stdout main;|g' /etc/nginx/nginx.conf || true + +# 配置 supervisor +RUN cat > /etc/supervisord.conf << 'EOF' +[supervisord] +nodaemon=true +logfile=/var/log/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:app] +command=pm2-runtime start build/app.js --name app +directory=/app +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=NODE_ENV=prod +EOF + +ENV NODE_ENV=prod + +EXPOSE 80 +EXPOSE 60000 + +# 启动时创建必要目录(防止 volume 挂载覆盖) +CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf" diff --git a/docker/Dockerfile.local b/docker/Dockerfile.local new file mode 100644 index 0000000..3745495 --- /dev/null +++ b/docker/Dockerfile.local @@ -0,0 +1,94 @@ +# 本地构建阶段 - 使用本地源码,不从 git 克隆 +FROM node:24-alpine AS builder + +WORKDIR /app + +RUN npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ + +# 复制依赖文件 +COPY package.json yarn.lock ./ + +RUN yarn install --frozen-lockfile + +# 复制源码 +COPY tsconfig.json ./ +COPY src/ ./src/ +COPY scripts/ ./scripts/ + +RUN yarn build + +# 生产阶段 +FROM node:24-alpine + +WORKDIR /app + +# 安装 nginx 和 supervisor +RUN apk add --no-cache nginx supervisor && \ + mkdir -p /var/lib/nginx/logs /var/log/nginx && \ + npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ && \ + npm install -g pm2 + +# 复制后端文件 +COPY --from=builder /app/build ./build +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/yarn.lock ./ + +# 复制静态页面到 nginx 目录 +COPY --from=builder /app/scripts/web /usr/share/nginx/html + +# 只安装生产依赖 +RUN yarn install --frozen-lockfile --production + +# 配置 nginx +RUN cat > /etc/nginx/http.d/default.conf << 'EOF' +server { + listen 80; + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } +} +EOF + +# 配置 nginx 主配置,日志输出到 stderr/stdout +RUN sed -i 's|error_log /var/log/nginx/error.log warn;|error_log /dev/stderr warn;|g' /etc/nginx/nginx.conf || true && \ + sed -i 's|access_log /var/log/nginx/access.log main;|access_log /dev/stdout main;|g' /etc/nginx/nginx.conf || true + +# 配置 supervisor +RUN cat > /etc/supervisord.conf << 'EOF' +[supervisord] +nodaemon=true +logfile=/var/log/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:app] +command=pm2-runtime start build/app.js --name app +directory=/app +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=NODE_ENV=prod +EOF + +ENV NODE_ENV=prod + +EXPOSE 80 +EXPOSE 60000 + +# 启动时创建必要目录(防止 volume 挂载覆盖) +CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf" diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml new file mode 100644 index 0000000..cf54b76 --- /dev/null +++ b/docker/docker-compose.local.yml @@ -0,0 +1,24 @@ +# 本地打包测试用,使用本地源码构建 +# 用法: docker-compose -f docker/docker-compose.local.yml up -d --build + +services: + toonflow: + build: + context: .. + dockerfile: docker/Dockerfile.local + image: toonflow:local + container_name: toonflow-local + restart: unless-stopped + ports: + - "8080:80" + - "60000:60000" + environment: + - NODE_ENV=prod + volumes: + - ../logs:/var/log + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..d70050c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,26 @@ +services: + toonflow: + build: + context: .. + dockerfile: docker/Dockerfile + args: + GIT: ${GIT:-github} + TAG: ${TAG:-} + BRANCH: ${BRANCH:-} + image: toonflow:${TAG:-latest} + container_name: toonflow + restart: unless-stopped + ports: + - "80" + - "60000:60000" + environment: + - NODE_ENV=prod + volumes: + # 可选: 持久化日志 + - ../logs:/var/log + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/electron-builder.yml b/electron-builder.yml index 20b083a..40b752b 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -9,6 +9,7 @@ directories: files: - build/**/* - scripts/web/**/* + - env/**/* - package.json - node_modules/**/* - "!node_modules/**/*.{md,ts,map}" diff --git a/env/.env.dev b/env/.env.dev new file mode 100644 index 0000000..b8dd44a --- /dev/null +++ b/env/.env.dev @@ -0,0 +1,4 @@ +NODE_ENV=dev +PORT=60000 +OSSURL=http://127.0.0.1:60000/ + diff --git a/env/.env.prod b/env/.env.prod new file mode 100644 index 0000000..bbf0003 --- /dev/null +++ b/env/.env.prod @@ -0,0 +1,4 @@ +NODE_ENV=prod +PORT=60000 +OSSURL=http://127.0.0.1:60000/ + diff --git a/package.json b/package.json index a005f94..39b85a6 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,46 @@ { - "name": "toonflow-serve", - "version": "1.0.5", - "description": "ToonFlow Serve - Electron Application", + "name": "toonflow-app", + "version": "1.0.6", + "description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。", + "author": "HBAI-Ltd ", + "homepage": "https://github.com/HBAI-Ltd/Toonflow-app#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/HBAI-Ltd/Toonflow-app.git" + }, + "bugs": { + "url": "https://github.com/HBAI-Ltd/Toonflow-app/issues", + "email": "ltlctools@outlook.com" + }, "main": "build/main.js", - "author": "ToonFlow Team", "packageManager": "yarn@1.0.0", "engines": { "node": ">=1.0.0" }, "scripts": { "dev": "nodemon --inspect --exec tsx src/app.ts", - "dev:win": "chcp 65001 && electronmon -r tsx scripts/main.ts", + "dev:gui": "electronmon -r tsx scripts/main.ts", "lint": "tsc --noEmit", - "build": "tsx scripts/build.ts", + "build": "cross-env NODE_ENV=prod tsx scripts/build.ts", "pack": "electron-builder --dir", "dist": "yarn build && electron-builder", "dist:win": "yarn build && electron-builder --win", "dist:mac": "yarn build && electron-builder --mac", "dist:linux": "yarn build && electron-builder --linux", - "test": "node build/app.js", + "test": "cross-env NODE_ENV=prod node build/app.js", + "docker:build": "docker-compose -f docker/docker-compose.yml up -d --build", + "docker:local": "docker-compose -f docker/docker-compose.local.yml up -d --build", + "debug:ai": "npx @ai-sdk/devtools", "license": "bun run scripts/license.ts" }, "dependencies": { "@ai-sdk/anthropic": "^3.0.35", "@ai-sdk/deepseek": "^2.0.17", + "@ai-sdk/devtools": "^0.0.11", "@ai-sdk/google": "^3.0.20", "@ai-sdk/openai": "^3.0.25", - "@aigne/core": "^1.72.0", - "@aigne/openai": "^0.16.16", - "@langchain/core": "^1.1.15", - "@langchain/openai": "^1.2.1", + "@ai-sdk/openai-compatible": "^2.0.27", + "@ai-sdk/xai": "^3.0.47", "@rmp135/sql-ts": "^2.2.0", "ai": "^6.0.67", "axios": "^1.13.2", @@ -46,11 +57,12 @@ "js-md5": "^0.8.3", "jsonwebtoken": "^9.0.3", "knex": "^3.1.0", - "langchain": "^1.2.10", "morgan": "^1.10.1", "qwen-ai-provider": "^0.1.1", + "serialize-error": "^13.0.1", "sharp": "^0.34.5", "sqlite3": "^5.1.7", + "uuid": "^13.0.0", "zhipu-ai-provider": "^0.2.2", "zod": "^4.3.5" }, @@ -61,6 +73,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/license-checker": "^25.0.6", "@types/morgan": "^1.9.10", + "cross-env": "^10.1.0", "electron": "^40.0.0", "electron-builder": "^26.4.0", "electronmon": "^2.0.4", diff --git a/scripts/build.ts b/scripts/build.ts index 5f57955..11638e8 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,4 +1,23 @@ import esbuild from "esbuild"; +import fs from "fs"; +import path from "path"; + +// 打包默认使用 prod 环境变量 +if (!process.env.NODE_ENV) { + process.env.NODE_ENV = "prod"; +} + +// 自动创建 env 目录和环境变量文件(.gitignore 可能忽略了这些文件) +const envDir = path.resolve("env"); +const envFile = path.join(envDir, `.env.${process.env.NODE_ENV}`); +if (!fs.existsSync(envDir)) { + fs.mkdirSync(envDir, { recursive: true }); +} +if (!fs.existsSync(envFile)) { + const defaultEnv = `NODE_ENV=${process.env.NODE_ENV}\nPORT=60000\nOSSURL=http://127.0.0.1:60000/\n`; + fs.writeFileSync(envFile, defaultEnv, "utf8"); + console.log(`📄 已自动创建环境变量文件: ${envFile}`); +} const external = ["electron", "sqlite3", "better-sqlite3", "mysql", "mysql2", "pg", "pg-query-stream", "oracledb", "tedious", "mssql"]; diff --git a/scripts/main.ts b/scripts/main.ts index ff09072..a1e2470 100644 --- a/scripts/main.ts +++ b/scripts/main.ts @@ -3,17 +3,21 @@ import path from "path"; import startServe, { closeServe } from "src/app"; function createMainWindow(): void { + const isDev = process.env.NODE_ENV === "dev" || !app.isPackaged; + const basePath = isDev ? process.cwd() : app.getAppPath(); + const win = new BrowserWindow({ width: 900, height: 600, show: true, autoHideMenuBar: true, + icon: path.join( + basePath, + "scripts", + process.platform === "win32" ? "logo.ico" : "logo.png" + ), }); - // 开发环境和生产环境使用不同的路径 - const isDev = process.env.NODE_ENV === "dev" || !app.isPackaged; - const htmlPath = isDev - ? path.join(process.cwd(), "scripts", "web", "index.html") - : path.join(app.getAppPath(), "scripts", "web", "index.html"); + const htmlPath = path.join(basePath, "scripts", "web", "index.html"); void win.loadFile(htmlPath); } app.whenReady().then(async () => { diff --git a/scripts/web/index.html b/scripts/web/index.html index 13cf668..f3e04d1 100644 --- a/scripts/web/index.html +++ b/scripts/web/index.html @@ -5,56 +5,56 @@ Toonflow - - + */function kSe(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable})),n.push.apply(n,o)}return n}function Z3(e){for(var t=1;t=0)&&(n[r]=e[r]);return n}function gqi(e,t){if(e==null)return{};var n=kqi(e,t),o,r;if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,o)&&(n[o]=e[o])}return n}function mqi(e){return vqi(e)||bqi(e)||yqi(e)||wqi()}function vqi(e){if(Array.isArray(e))return Hse(e)}function bqi(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function yqi(e,t){if(e){if(typeof e=="string")return Hse(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Hse(e,t)}}function Hse(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n"&&(t=t.substring(1)),e)try{if(e.matches)return e.matches(t);if(e.msMatchesSelector)return e.msMatchesSelector(t);if(e.webkitMatchesSelector)return e.webkitMatchesSelector(t)}catch{return!1}return!1}}function xqi(e){return e.host&&e!==document&&e.host.nodeType?e.host:e.parentNode}function F4(e,t,n,o){if(e){n=n||document;do{if(t!=null&&(t[0]===">"?e.parentNode===n&&KV(e,t):KV(e,t))||o&&e===n)return e;if(e===n)break}while(e=xqi(e))}return null}var mSe=/\s+/g;function Jc(e,t,n){if(e&&t)if(e.classList)e.classList[n?"add":"remove"](t);else{var o=(" "+e.className+" ").replace(mSe," ").replace(" "+t+" "," ");e.className=(o+(n?" "+t:"")).replace(mSe," ")}}function Jr(e,t,n){var o=e&&e.style;if(o){if(n===void 0)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(e,""):e.currentStyle&&(n=e.currentStyle),t===void 0?n:n[t];!(t in o)&&t.indexOf("webkit")===-1&&(t="-webkit-"+t),o[t]=n+(typeof n=="string"?"":"px")}}function W7(e,t){var n="";if(typeof e=="string")n=e;else do{var o=Jr(e,"transform");o&&o!=="none"&&(n=o+" "+n)}while(!t&&(e=e.parentNode));var r=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return r&&new r(n)}function KGe(e,t,n){if(e){var o=e.getElementsByTagName(t),r=0,l=o.length;if(n)for(;r=l,!a)return o;if(o===R3())break;o=O9(o,!1)}return!1}function cE(e,t,n,o){for(var r=0,l=0,a=e.children;l2&&arguments[2]!==void 0?arguments[2]:{},r=o.evt,l=gqi(o,jqi);Np.pluginEvent.bind(Rl)(t,n,Z3({dragEl:mr,parentEl:au,ghostEl:ga,rootEl:pc,nextEl:Jb,lastDownEl:iB,cloneEl:su,cloneHidden:y9,dragStarted:$_,putSortable:rh,activeSortable:Rl.active,originalEvent:r,oldIndex:Ox,oldDraggableIndex:Uj,newIndex:Cf,newDraggableIndex:h9,hideGhostForTarget:nYe,unhideGhostForTarget:iYe,cloneNowHidden:function(){y9=!0},cloneNowShown:function(){y9=!1},dispatchSortableEvent:function(s){E1({sortable:n,name:s,originalEvent:r})}},l))};function E1(e){Q_(Z3({putSortable:rh,cloneEl:su,targetEl:mr,rootEl:pc,oldIndex:Ox,oldDraggableIndex:Uj,newIndex:Cf,newDraggableIndex:h9},e))}var mr,au,ga,pc,Jb,iB,su,y9,Ox,Cf,Uj,h9,WW,rh,bx=!1,JV=!1,QV=[],Gb,H4,mre,vre,wSe,CSe,$_,nx,Gj,Yj=!1,HW=!1,oB,Rh,bre=[],zse=!1,$V=[],YZ=typeof document<"u",zW=XGe,LSe=pp||yv?"cssFloat":"float",Oqi=YZ&&!Lqi&&!XGe&&"draggable"in document.createElement("div"),$Ge=function(){if(YZ){if(yv)return!1;var e=document.createElement("x");return e.style.cssText="pointer-events:auto",e.style.pointerEvents==="auto"}}(),eYe=function(t,n){var o=Jr(t),r=parseInt(o.width)-parseInt(o.paddingLeft)-parseInt(o.paddingRight)-parseInt(o.borderLeftWidth)-parseInt(o.borderRightWidth),l=cE(t,0,n),a=cE(t,1,n),s=l&&Jr(l),c=a&&Jr(a),u=s&&parseInt(s.marginLeft)+parseInt(s.marginRight)+Nc(l).width,d=c&&parseInt(c.marginLeft)+parseInt(c.marginRight)+Nc(a).width;if(o.display==="flex")return o.flexDirection==="column"||o.flexDirection==="column-reverse"?"vertical":"horizontal";if(o.display==="grid")return o.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(l&&s.float&&s.float!=="none"){var h=s.float==="left"?"left":"right";return a&&(c.clear==="both"||c.clear===h)?"vertical":"horizontal"}return l&&(s.display==="block"||s.display==="flex"||s.display==="table"||s.display==="grid"||u>=r&&o[LSe]==="none"||a&&o[LSe]==="none"&&u+d>r)?"vertical":"horizontal"},Dqi=function(t,n,o){var r=o?t.left:t.top,l=o?t.right:t.bottom,a=o?t.width:t.height,s=o?n.left:n.top,c=o?n.right:n.bottom,u=o?n.width:n.height;return r===s||l===c||r+a/2===s+u/2},pqi=function(t,n){var o;return QV.some(function(r){var l=r[Uh].options.emptyInsertThreshold;if(!(!l||Rfe(r))){var a=Nc(r),s=t>=a.left-l&&t<=a.right+l,c=n>=a.top-l&&n<=a.bottom+l;if(s&&c)return o=r}}),o},tYe=function(t){function n(l,a){return function(s,c,u,d){var h=s.options.group.name&&c.options.group.name&&s.options.group.name===c.options.group.name;if(l==null&&(a||h))return!0;if(l==null||l===!1)return!1;if(a&&l==="clone")return l;if(typeof l=="function")return n(l(s,c,u,d),a)(s,c,u,d);var k=(a?s:c).options.group.name;return l===!0||typeof l=="string"&&l===k||l.join&&l.indexOf(k)>-1}}var o={},r=t.group;(!r||nB(r)!="object")&&(r={name:r}),o.name=r.name,o.checkPull=n(r.pull,!0),o.checkPut=n(r.put),o.revertClone=r.revertClone,t.group=o},nYe=function(){!$Ge&&ga&&Jr(ga,"display","none")},iYe=function(){!$Ge&&ga&&Jr(ga,"display","")};YZ&&document.addEventListener("click",function(e){if(JV)return e.preventDefault(),e.stopPropagation&&e.stopPropagation(),e.stopImmediatePropagation&&e.stopImmediatePropagation(),JV=!1,!1},!0);var Yb=function(t){if(mr){t=t.touches?t.touches[0]:t;var n=pqi(t.clientX,t.clientY);if(n){var o={};for(var r in t)t.hasOwnProperty(r)&&(o[r]=t[r]);o.target=o.rootEl=n,o.preventDefault=void 0,o.stopPropagation=void 0,n[Uh]._onDragOver(o)}}},Nqi=function(t){mr&&mr.parentNode[Uh]._isOutsideThisEl(t.target)};function Rl(e,t){if(!(e&&e.nodeType&&e.nodeType===1))throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(e));this.el=e,this.options=t=r4({},t),e[Uh]=this;var n={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(e.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return eYe(e,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(a,s){a.setData("Text",s.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:Rl.supportPointer!==!1&&"PointerEvent"in window&&!Vj,emptyInsertThreshold:5};Np.initializePlugins(this,e,n);for(var o in n)!(o in t)&&(t[o]=n[o]);tYe(t);for(var r in this)r.charAt(0)==="_"&&typeof this[r]=="function"&&(this[r]=this[r].bind(this));this.nativeDraggable=t.forceFallback?!1:Oqi,this.nativeDraggable&&(this.options.touchStartThreshold=1),t.supportPointer?Ga(e,"pointerdown",this._onTapStart):(Ga(e,"mousedown",this._onTapStart),Ga(e,"touchstart",this._onTapStart)),this.nativeDraggable&&(Ga(e,"dragover",this),Ga(e,"dragenter",this)),QV.push(this.el),t.store&&t.store.get&&this.sort(t.store.get(this)||[]),r4(this,Aqi())}Rl.prototype={constructor:Rl,_isOutsideThisEl:function(t){!this.el.contains(t)&&t!==this.el&&(nx=null)},_getDirection:function(t,n){return typeof this.options.direction=="function"?this.options.direction.call(this,t,n,mr):this.options.direction},_onTapStart:function(t){if(t.cancelable){var n=this,o=this.el,r=this.options,l=r.preventOnFilter,a=t.type,s=t.touches&&t.touches[0]||t.pointerType&&t.pointerType==="touch"&&t,c=(s||t).target,u=t.target.shadowRoot&&(t.path&&t.path[0]||t.composedPath&&t.composedPath()[0])||c,d=r.filter;if(Vqi(o),!mr&&!(/mousedown|pointerdown/.test(a)&&t.button!==0||r.disabled)&&!u.isContentEditable&&!(!this.nativeDraggable&&Vj&&c&&c.tagName.toUpperCase()==="SELECT")&&(c=F4(c,r.draggable,o,!1),!(c&&c.animated)&&iB!==c)){if(Ox=uu(c),Uj=uu(c,r.draggable),typeof d=="function"){if(d.call(this,t,c,this)){E1({sortable:n,rootEl:u,name:"filter",targetEl:c,toEl:o,fromEl:o}),k2("filter",n,{evt:t}),l&&t.cancelable&&t.preventDefault();return}}else if(d&&(d=d.split(",").some(function(h){if(h=F4(u,h.trim(),o,!1),h)return E1({sortable:n,rootEl:h,name:"filter",targetEl:c,fromEl:o,toEl:o}),k2("filter",n,{evt:t}),!0}),d)){l&&t.cancelable&&t.preventDefault();return}r.handle&&!F4(u,r.handle,o,!1)||this._prepareDragStart(t,s,c)}}},_prepareDragStart:function(t,n,o){var r=this,l=r.el,a=r.options,s=l.ownerDocument,c;if(o&&!mr&&o.parentNode===l){var u=Nc(o);if(pc=l,mr=o,au=mr.parentNode,Jb=mr.nextSibling,iB=o,WW=a.group,Rl.dragged=mr,Gb={target:mr,clientX:(n||t).clientX,clientY:(n||t).clientY},wSe=Gb.clientX-u.left,CSe=Gb.clientY-u.top,this._lastX=(n||t).clientX,this._lastY=(n||t).clientY,mr.style["will-change"]="all",c=function(){if(k2("delayEnded",r,{evt:t}),Rl.eventCanceled){r._onDrop();return}r._disableDelayedDragEvents(),!gSe&&r.nativeDraggable&&(mr.draggable=!0),r._triggerDragStart(t,n),E1({sortable:r,name:"choose",originalEvent:t}),Jc(mr,a.chosenClass,!0)},a.ignore.split(",").forEach(function(d){KGe(mr,d.trim(),yre)}),Ga(s,"dragover",Yb),Ga(s,"mousemove",Yb),Ga(s,"touchmove",Yb),Ga(s,"mouseup",r._onDrop),Ga(s,"touchend",r._onDrop),Ga(s,"touchcancel",r._onDrop),gSe&&this.nativeDraggable&&(this.options.touchStartThreshold=4,mr.draggable=!0),k2("delayStart",this,{evt:t}),a.delay&&(!a.delayOnTouchOnly||n)&&(!this.nativeDraggable||!(pp||yv))){if(Rl.eventCanceled){this._onDrop();return}Ga(s,"mouseup",r._disableDelayedDrag),Ga(s,"touchend",r._disableDelayedDrag),Ga(s,"touchcancel",r._disableDelayedDrag),Ga(s,"mousemove",r._delayedDragTouchMoveHandler),Ga(s,"touchmove",r._delayedDragTouchMoveHandler),a.supportPointer&&Ga(s,"pointermove",r._delayedDragTouchMoveHandler),r._dragStartTimer=setTimeout(c,a.delay)}else c()}},_delayedDragTouchMoveHandler:function(t){var n=t.touches?t.touches[0]:t;Math.max(Math.abs(n.clientX-this._lastX),Math.abs(n.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){mr&&yre(mr),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;pa(t,"mouseup",this._disableDelayedDrag),pa(t,"touchend",this._disableDelayedDrag),pa(t,"touchcancel",this._disableDelayedDrag),pa(t,"mousemove",this._delayedDragTouchMoveHandler),pa(t,"touchmove",this._delayedDragTouchMoveHandler),pa(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,n){n=n||t.pointerType=="touch"&&t,!this.nativeDraggable||n?this.options.supportPointer?Ga(document,"pointermove",this._onTouchMove):n?Ga(document,"touchmove",this._onTouchMove):Ga(document,"mousemove",this._onTouchMove):(Ga(mr,"dragend",this),Ga(pc,"dragstart",this._onDragStart));try{document.selection?rB(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch{}},_dragStarted:function(t,n){if(bx=!1,pc&&mr){k2("dragStarted",this,{evt:n}),this.nativeDraggable&&Ga(document,"dragover",Nqi);var o=this.options;!t&&Jc(mr,o.dragClass,!1),Jc(mr,o.ghostClass,!0),Rl.active=this,t&&this._appendGhost(),E1({sortable:this,name:"start",originalEvent:n})}else this._nulling()},_emulateDragOver:function(){if(H4){this._lastX=H4.clientX,this._lastY=H4.clientY,nYe();for(var t=document.elementFromPoint(H4.clientX,H4.clientY),n=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(H4.clientX,H4.clientY),t!==n);)n=t;if(mr.parentNode[Uh]._isOutsideThisEl(t),n)do{if(n[Uh]){var o=void 0;if(o=n[Uh]._onDragOver({clientX:H4.clientX,clientY:H4.clientY,target:t,rootEl:n}),o&&!this.options.dragoverBubble)break}t=n}while(n=n.parentNode);iYe()}},_onTouchMove:function(t){if(Gb){var n=this.options,o=n.fallbackTolerance,r=n.fallbackOffset,l=t.touches?t.touches[0]:t,a=ga&&W7(ga,!0),s=ga&&a&&a.a,c=ga&&a&&a.d,u=zW&&Rh&&bSe(Rh),d=(l.clientX-Gb.clientX+r.x)/(s||1)+(u?u[0]-bre[0]:0)/(s||1),h=(l.clientY-Gb.clientY+r.y)/(c||1)+(u?u[1]-bre[1]:0)/(c||1);if(!Rl.active&&!bx){if(o&&Math.max(Math.abs(l.clientX-this._lastX),Math.abs(l.clientY-this._lastY))=0&&(E1({rootEl:au,name:"add",toEl:au,fromEl:pc,originalEvent:t}),E1({sortable:this,name:"remove",toEl:au,originalEvent:t}),E1({rootEl:au,name:"sort",toEl:au,fromEl:pc,originalEvent:t}),E1({sortable:this,name:"sort",toEl:au,originalEvent:t})),rh&&rh.save()):Cf!==Ox&&Cf>=0&&(E1({sortable:this,name:"update",toEl:au,originalEvent:t}),E1({sortable:this,name:"sort",toEl:au,originalEvent:t})),Rl.active&&((Cf==null||Cf===-1)&&(Cf=Ox,h9=Uj),E1({sortable:this,name:"end",toEl:au,originalEvent:t}),this.save()))),this._nulling()},_nulling:function(){k2("nulling",this),pc=mr=au=ga=Jb=su=iB=y9=Gb=H4=$_=Cf=h9=Ox=Uj=nx=Gj=rh=WW=Rl.dragged=Rl.ghost=Rl.clone=Rl.active=null,$V.forEach(function(t){t.checked=!0}),$V.length=mre=vre=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragenter":case"dragover":mr&&(this._onDragOver(t),Rqi(t));break;case"selectstart":t.preventDefault();break}},toArray:function(){for(var t=[],n,o=this.el.children,r=0,l=o.length,a=this.options;ro.right+r||e.clientX<=o.right&&e.clientY>o.bottom&&e.clientX>=o.left:e.clientX>o.right&&e.clientY>o.top||e.clientX<=o.right&&e.clientY>o.bottom+r}function Hqi(e,t,n,o,r,l,a,s){var c=o?e.clientY:e.clientX,u=o?n.height:n.width,d=o?n.top:n.left,h=o?n.bottom:n.right,k=!1;if(!a){if(s&&oBd+u*l/2:ch-oB)return-Gj}else if(c>d+u*(1-r)/2&&ch-u*l/2)?c>d+u/2?1:-1:0}function zqi(e){return uu(mr)1&&(ra.forEach(function(s){l.addAnimationState({target:s,rect:g2?Nc(s):a}),kre(s),s.fromRect=a,o.removeAnimationState(s)}),g2=!1,Zqi(!this.options.removeCloneOnHide,r))},dragOverCompleted:function(n){var o=n.sortable,r=n.isOwner,l=n.insertion,a=n.activeSortable,s=n.parentEl,c=n.putSortable,u=this.options;if(l){if(r&&a._hideClone(),O_=!1,u.animation&&ra.length>1&&(g2||!r&&!a.options.sort&&!c)){var d=Nc(sc,!1,!0,!0);ra.forEach(function(k){k!==sc&&(ySe(k,d),s.appendChild(k))}),g2=!0}if(!r)if(g2||FW(),ra.length>1){var h=VW;a._showClone(o),a.options.animation&&!VW&&h&&wf.forEach(function(k){a.addAnimationState({target:k,rect:D_}),k.fromRect=D_,k.thisAnimationDuration=null})}else a._showClone(o)}},dragOverAnimationCapture:function(n){var o=n.dragRect,r=n.isOwner,l=n.activeSortable;if(ra.forEach(function(s){s.thisAnimationDuration=null}),l.options.animation&&!r&&l.multiDrag.isMultiDrag){D_=r4({},o);var a=W7(sc,!0);D_.top-=a.f,D_.left-=a.e}},dragOverAnimationComplete:function(){g2&&(g2=!1,FW())},drop:function(n){var o=n.originalEvent,r=n.rootEl,l=n.parentEl,a=n.sortable,s=n.dispatchSortableEvent,c=n.oldIndex,u=n.putSortable,d=u||this.sortable;if(o){var h=this.options,k=l.children;if(!ix)if(h.multiDragKey&&!this.multiDragKeyDown&&this._deselectMultiDrag(),Jc(sc,h.selectedClass,!~ra.indexOf(sc)),~ra.indexOf(sc))ra.splice(ra.indexOf(sc),1),j_=null,Q_({sortable:a,rootEl:r,name:"deselect",targetEl:sc});else{if(ra.push(sc),Q_({sortable:a,rootEl:r,name:"select",targetEl:sc}),o.shiftKey&&j_&&a.el.contains(j_)){var m=uu(j_),b=uu(sc);if(~m&&~b&&m!==b){var g,v;for(b>m?(v=m,g=b):(v=b,g=m+1);v1){var w=Nc(sc),L=uu(sc,":not(."+this.options.selectedClass+")");if(!O_&&h.animation&&(sc.thisAnimationDuration=null),d.captureAnimationState(),!O_&&(h.animation&&(sc.fromRect=w,ra.forEach(function(S){if(S.thisAnimationDuration=null,S!==sc){var E=g2?Nc(S):w;S.fromRect=E,d.addAnimationState({target:S,rect:E})}})),FW(),ra.forEach(function(S){k[L]?l.insertBefore(S,k[L]):l.appendChild(S),L++}),c===uu(sc))){var x=!1;ra.forEach(function(S){if(S.sortableIndex!==uu(S)){x=!0;return}}),x&&s("update")}ra.forEach(function(S){kre(S)}),d.animateAll()}z4=d}(r===l||u&&u.lastPutMode!=="clone")&&wf.forEach(function(S){S.parentNode&&S.parentNode.removeChild(S)})}},nullingGlobal:function(){this.isMultiDrag=ix=!1,wf.length=0},destroyGlobal:function(){this._deselectMultiDrag(),pa(document,"pointerup",this._deselectMultiDrag),pa(document,"mouseup",this._deselectMultiDrag),pa(document,"touchend",this._deselectMultiDrag),pa(document,"keydown",this._checkKeyDown),pa(document,"keyup",this._checkKeyUp)},_deselectMultiDrag:function(n){if(!(typeof ix<"u"&&ix)&&z4===this.sortable&&!(n&&F4(n.target,this.options.draggable,this.sortable.el,!1))&&!(n&&n.button!==0))for(;ra.length;){var o=ra[0];Jc(o,this.options.selectedClass,!1),ra.shift(),Q_({sortable:this.sortable,rootEl:this.sortable.el,name:"deselect",targetEl:o})}},_checkKeyDown:function(n){n.key===this.options.multiDragKey&&(this.multiDragKeyDown=!0)},_checkKeyUp:function(n){n.key===this.options.multiDragKey&&(this.multiDragKeyDown=!1)}},r4(e,{pluginName:"multiDrag",utils:{select:function(n){var o=n.parentNode[Uh];!o||!o.options.multiDrag||~ra.indexOf(n)||(z4&&z4!==o&&(z4.multiDrag._deselectMultiDrag(),z4=o),Jc(n,o.options.selectedClass,!0),ra.push(n))},deselect:function(n){var o=n.parentNode[Uh],r=ra.indexOf(n);!o||!o.options.multiDrag||!~r||(Jc(n,o.options.selectedClass,!1),ra.splice(r,1))}},eventProperties:function(){var n=this,o=[],r=[];return ra.forEach(function(l){o.push({multiDragElement:l,index:l.sortableIndex});var a;g2&&l!==sc?a=-1:g2?a=uu(l,":not(."+n.options.selectedClass+")"):a=uu(l),r.push({multiDragElement:l,index:a})}),{items:mqi(ra),clones:[].concat(wf),oldIndicies:o,newIndicies:r}},optionListeners:{multiDragKey:function(n){return n=n.toLowerCase(),n==="ctrl"?n="Control":n.length>1&&(n=n.charAt(0).toUpperCase()+n.substr(1)),n}}})}function Zqi(e,t){ra.forEach(function(n,o){var r=t.children[n.sortableIndex+(e?Number(o):0)];r?t.insertBefore(n,r):t.appendChild(n)})}function SSe(e,t){wf.forEach(function(n,o){var r=t.children[n.sortableIndex+(e?Number(o):0)];r?t.insertBefore(n,r):t.appendChild(n)})}function FW(){ra.forEach(function(e){e!==sc&&e.parentNode&&e.parentNode.removeChild(e)})}Rl.mount(new Fqi);Rl.mount(Wfe,Pfe);const Xqi=Object.freeze(Object.defineProperty({__proto__:null,MultiDrag:Yqi,Sortable:Rl,Swap:Uqi,default:Rl},Symbol.toStringTag,{value:"Module"})),qqi=BF(Xqi);(function(e,t){(function(o,r){e.exports=r(hqi,qqi)})(typeof self<"u"?self:Pl,function(n,o){return function(r){var l={};function a(s){if(l[s])return l[s].exports;var c=l[s]={i:s,l:!1,exports:{}};return r[s].call(c.exports,c,c.exports,a),c.l=!0,c.exports}return a.m=r,a.c=l,a.d=function(s,c,u){a.o(s,c)||Object.defineProperty(s,c,{enumerable:!0,get:u})},a.r=function(s){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(s,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(s,"__esModule",{value:!0})},a.t=function(s,c){if(c&1&&(s=a(s)),c&8||c&4&&typeof s=="object"&&s&&s.__esModule)return s;var u=Object.create(null);if(a.r(u),Object.defineProperty(u,"default",{enumerable:!0,value:s}),c&2&&typeof s!="string")for(var d in s)a.d(u,d,(function(h){return s[h]}).bind(null,d));return u},a.n=function(s){var c=s&&s.__esModule?function(){return s.default}:function(){return s};return a.d(c,"a",c),c},a.o=function(s,c){return Object.prototype.hasOwnProperty.call(s,c)},a.p="",a(a.s="fb15")}({"00ee":function(r,l,a){var s=a("b622"),c=s("toStringTag"),u={};u[c]="z",r.exports=String(u)==="[object z]"},"0366":function(r,l,a){var s=a("1c0b");r.exports=function(c,u,d){if(s(c),u===void 0)return c;switch(d){case 0:return function(){return c.call(u)};case 1:return function(h){return c.call(u,h)};case 2:return function(h,k){return c.call(u,h,k)};case 3:return function(h,k,m){return c.call(u,h,k,m)}}return function(){return c.apply(u,arguments)}}},"057f":function(r,l,a){var s=a("fc6a"),c=a("241c").f,u={}.toString,d=typeof window=="object"&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],h=function(k){try{return c(k)}catch{return d.slice()}};r.exports.f=function(m){return d&&u.call(m)=="[object Window]"?h(m):c(s(m))}},"06cf":function(r,l,a){var s=a("83ab"),c=a("d1e7"),u=a("5c6c"),d=a("fc6a"),h=a("c04e"),k=a("5135"),m=a("0cfb"),b=Object.getOwnPropertyDescriptor;l.f=s?b:function(v,w){if(v=d(v),w=h(w,!0),m)try{return b(v,w)}catch{}if(k(v,w))return u(!c.f.call(v,w),v[w])}},"0cfb":function(r,l,a){var s=a("83ab"),c=a("d039"),u=a("cc12");r.exports=!s&&!c(function(){return Object.defineProperty(u("div"),"a",{get:function(){return 7}}).a!=7})},"13d5":function(r,l,a){var s=a("23e7"),c=a("d58f").left,u=a("a640"),d=a("ae40"),h=u("reduce"),k=d("reduce",{1:0});s({target:"Array",proto:!0,forced:!h||!k},{reduce:function(b){return c(this,b,arguments.length,arguments.length>1?arguments[1]:void 0)}})},"14c3":function(r,l,a){var s=a("c6b6"),c=a("9263");r.exports=function(u,d){var h=u.exec;if(typeof h=="function"){var k=h.call(u,d);if(typeof k!="object")throw TypeError("RegExp exec method returned something other than an Object or null");return k}if(s(u)!=="RegExp")throw TypeError("RegExp#exec called on incompatible receiver");return c.call(u,d)}},"159b":function(r,l,a){var s=a("da84"),c=a("fdbc"),u=a("17c2"),d=a("9112");for(var h in c){var k=s[h],m=k&&k.prototype;if(m&&m.forEach!==u)try{d(m,"forEach",u)}catch{m.forEach=u}}},"17c2":function(r,l,a){var s=a("b727").forEach,c=a("a640"),u=a("ae40"),d=c("forEach"),h=u("forEach");r.exports=!d||!h?function(m){return s(this,m,arguments.length>1?arguments[1]:void 0)}:[].forEach},"1be4":function(r,l,a){var s=a("d066");r.exports=s("document","documentElement")},"1c0b":function(r,l){r.exports=function(a){if(typeof a!="function")throw TypeError(String(a)+" is not a function");return a}},"1c7e":function(r,l,a){var s=a("b622"),c=s("iterator"),u=!1;try{var d=0,h={next:function(){return{done:!!d++}},return:function(){u=!0}};h[c]=function(){return this},Array.from(h,function(){throw 2})}catch{}r.exports=function(k,m){if(!m&&!u)return!1;var b=!1;try{var g={};g[c]=function(){return{next:function(){return{done:b=!0}}}},k(g)}catch{}return b}},"1d80":function(r,l){r.exports=function(a){if(a==null)throw TypeError("Can't call method on "+a);return a}},"1dde":function(r,l,a){var s=a("d039"),c=a("b622"),u=a("2d00"),d=c("species");r.exports=function(h){return u>=51||!s(function(){var k=[],m=k.constructor={};return m[d]=function(){return{foo:1}},k[h](Boolean).foo!==1})}},"23cb":function(r,l,a){var s=a("a691"),c=Math.max,u=Math.min;r.exports=function(d,h){var k=s(d);return k<0?c(k+h,0):u(k,h)}},"23e7":function(r,l,a){var s=a("da84"),c=a("06cf").f,u=a("9112"),d=a("6eeb"),h=a("ce4e"),k=a("e893"),m=a("94ca");r.exports=function(b,g){var v=b.target,w=b.global,L=b.stat,x,S,E,M,T,j;if(w?S=s:L?S=s[v]||h(v,{}):S=(s[v]||{}).prototype,S)for(E in g){if(T=g[E],b.noTargetGet?(j=c(S,E),M=j&&j.value):M=S[E],x=m(w?E:v+(L?".":"#")+E,b.forced),!x&&M!==void 0){if(typeof T==typeof M)continue;k(T,M)}(b.sham||M&&M.sham)&&u(T,"sham",!0),d(S,E,T,b)}}},"241c":function(r,l,a){var s=a("ca84"),c=a("7839"),u=c.concat("length","prototype");l.f=Object.getOwnPropertyNames||function(h){return s(h,u)}},"25f0":function(r,l,a){var s=a("6eeb"),c=a("825a"),u=a("d039"),d=a("ad6d"),h="toString",k=RegExp.prototype,m=k[h],b=u(function(){return m.call({source:"a",flags:"b"})!="/a/b"}),g=m.name!=h;(b||g)&&s(RegExp.prototype,h,function(){var w=c(this),L=String(w.source),x=w.flags,S=String(x===void 0&&w instanceof RegExp&&!("flags"in k)?d.call(w):x);return"/"+L+"/"+S},{unsafe:!0})},"2ca0":function(r,l,a){var s=a("23e7"),c=a("06cf").f,u=a("50c4"),d=a("5a34"),h=a("1d80"),k=a("ab13"),m=a("c430"),b="".startsWith,g=Math.min,v=k("startsWith"),w=!m&&!v&&!!function(){var L=c(String.prototype,"startsWith");return L&&!L.writable}();s({target:"String",proto:!0,forced:!w&&!v},{startsWith:function(x){var S=String(h(this));d(x);var E=u(g(arguments.length>1?arguments[1]:void 0,S.length)),M=String(x);return b?b.call(S,M,E):S.slice(E,E+M.length)===M}})},"2d00":function(r,l,a){var s=a("da84"),c=a("342f"),u=s.process,d=u&&u.versions,h=d&&d.v8,k,m;h?(k=h.split("."),m=k[0]+k[1]):c&&(k=c.match(/Edge\/(\d+)/),(!k||k[1]>=74)&&(k=c.match(/Chrome\/(\d+)/),k&&(m=k[1]))),r.exports=m&&+m},"342f":function(r,l,a){var s=a("d066");r.exports=s("navigator","userAgent")||""},"35a1":function(r,l,a){var s=a("f5df"),c=a("3f8c"),u=a("b622"),d=u("iterator");r.exports=function(h){if(h!=null)return h[d]||h["@@iterator"]||c[s(h)]}},"37e8":function(r,l,a){var s=a("83ab"),c=a("9bf2"),u=a("825a"),d=a("df75");r.exports=s?Object.defineProperties:function(k,m){u(k);for(var b=d(m),g=b.length,v=0,w;g>v;)c.f(k,w=b[v++],m[w]);return k}},"3bbe":function(r,l,a){var s=a("861d");r.exports=function(c){if(!s(c)&&c!==null)throw TypeError("Can't set "+String(c)+" as a prototype");return c}},"3ca3":function(r,l,a){var s=a("6547").charAt,c=a("69f3"),u=a("7dd0"),d="String Iterator",h=c.set,k=c.getterFor(d);u(String,"String",function(m){h(this,{type:d,string:String(m),index:0})},function(){var b=k(this),g=b.string,v=b.index,w;return v>=g.length?{value:void 0,done:!0}:(w=s(g,v),b.index+=w.length,{value:w,done:!1})})},"3f8c":function(r,l){r.exports={}},4160:function(r,l,a){var s=a("23e7"),c=a("17c2");s({target:"Array",proto:!0,forced:[].forEach!=c},{forEach:c})},"428f":function(r,l,a){var s=a("da84");r.exports=s},"44ad":function(r,l,a){var s=a("d039"),c=a("c6b6"),u="".split;r.exports=s(function(){return!Object("z").propertyIsEnumerable(0)})?function(d){return c(d)=="String"?u.call(d,""):Object(d)}:Object},"44d2":function(r,l,a){var s=a("b622"),c=a("7c73"),u=a("9bf2"),d=s("unscopables"),h=Array.prototype;h[d]==null&&u.f(h,d,{configurable:!0,value:c(null)}),r.exports=function(k){h[d][k]=!0}},"44e7":function(r,l,a){var s=a("861d"),c=a("c6b6"),u=a("b622"),d=u("match");r.exports=function(h){var k;return s(h)&&((k=h[d])!==void 0?!!k:c(h)=="RegExp")}},4930:function(r,l,a){var s=a("d039");r.exports=!!Object.getOwnPropertySymbols&&!s(function(){return!String(Symbol())})},"4d64":function(r,l,a){var s=a("fc6a"),c=a("50c4"),u=a("23cb"),d=function(h){return function(k,m,b){var g=s(k),v=c(g.length),w=u(b,v),L;if(h&&m!=m){for(;v>w;)if(L=g[w++],L!=L)return!0}else for(;v>w;w++)if((h||w in g)&&g[w]===m)return h||w||0;return!h&&-1}};r.exports={includes:d(!0),indexOf:d(!1)}},"4de4":function(r,l,a){var s=a("23e7"),c=a("b727").filter,u=a("1dde"),d=a("ae40"),h=u("filter"),k=d("filter");s({target:"Array",proto:!0,forced:!h||!k},{filter:function(b){return c(this,b,arguments.length>1?arguments[1]:void 0)}})},"4df4":function(r,l,a){var s=a("0366"),c=a("7b0b"),u=a("9bdd"),d=a("e95a"),h=a("50c4"),k=a("8418"),m=a("35a1");r.exports=function(g){var v=c(g),w=typeof this=="function"?this:Array,L=arguments.length,x=L>1?arguments[1]:void 0,S=x!==void 0,E=m(v),M=0,T,j,O,P,B,p;if(S&&(x=s(x,L>2?arguments[2]:void 0,2)),E!=null&&!(w==Array&&d(E)))for(P=E.call(v),B=P.next,j=new w;!(O=B.call(P)).done;M++)p=S?u(P,x,[O.value,M],!0):O.value,k(j,M,p);else for(T=h(v.length),j=new w(T);T>M;M++)p=S?x(v[M],M):v[M],k(j,M,p);return j.length=M,j}},"4fad":function(r,l,a){var s=a("23e7"),c=a("6f53").entries;s({target:"Object",stat:!0},{entries:function(d){return c(d)}})},"50c4":function(r,l,a){var s=a("a691"),c=Math.min;r.exports=function(u){return u>0?c(s(u),9007199254740991):0}},5135:function(r,l){var a={}.hasOwnProperty;r.exports=function(s,c){return a.call(s,c)}},5319:function(r,l,a){var s=a("d784"),c=a("825a"),u=a("7b0b"),d=a("50c4"),h=a("a691"),k=a("1d80"),m=a("8aa5"),b=a("14c3"),g=Math.max,v=Math.min,w=Math.floor,L=/\$([$&'`]|\d\d?|<[^>]*>)/g,x=/\$([$&'`]|\d\d?)/g,S=function(E){return E===void 0?E:String(E)};s("replace",2,function(E,M,T,j){var O=j.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,P=j.REPLACE_KEEPS_$0,B=O?"$":"$0";return[function(R,U){var N=k(this),H=R==null?void 0:R[E];return H!==void 0?H.call(R,N,U):M.call(String(N),R,U)},function(W,R){if(!O&&P||typeof R=="string"&&R.indexOf(B)===-1){var U=T(M,W,this,R);if(U.done)return U.value}var N=c(W),H=String(this),z=typeof R=="function";z||(R=String(R));var X=N.global;if(X){var K=N.unicode;N.lastIndex=0}for(var Z=[];;){var te=b(N,H);if(te===null||(Z.push(te),!X))break;var $=String(te[0]);$===""&&(N.lastIndex=m(H,d(N.lastIndex),K))}for(var oe="",ce=0,ve=0;ve=ce&&(oe+=H.slice(ce,Ce)+ae,ce=Ce+ue.length)}return oe+H.slice(ce)}];function p(W,R,U,N,H,z){var X=U+W.length,K=N.length,Z=x;return H!==void 0&&(H=u(H),Z=L),M.call(z,Z,function(te,$){var oe;switch($.charAt(0)){case"$":return"$";case"&":return W;case"`":return R.slice(0,U);case"'":return R.slice(X);case"<":oe=H[$.slice(1,-1)];break;default:var ce=+$;if(ce===0)return te;if(ce>K){var ve=w(ce/10);return ve===0?te:ve<=K?N[ve-1]===void 0?$.charAt(1):N[ve-1]+$.charAt(1):te}oe=N[ce-1]}return oe===void 0?"":oe})}})},5692:function(r,l,a){var s=a("c430"),c=a("c6cd");(r.exports=function(u,d){return c[u]||(c[u]=d!==void 0?d:{})})("versions",[]).push({version:"3.6.5",mode:s?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},"56ef":function(r,l,a){var s=a("d066"),c=a("241c"),u=a("7418"),d=a("825a");r.exports=s("Reflect","ownKeys")||function(k){var m=c.f(d(k)),b=u.f;return b?m.concat(b(k)):m}},"5a34":function(r,l,a){var s=a("44e7");r.exports=function(c){if(s(c))throw TypeError("The method doesn't accept regular expressions");return c}},"5c6c":function(r,l){r.exports=function(a,s){return{enumerable:!(a&1),configurable:!(a&2),writable:!(a&4),value:s}}},"5db7":function(r,l,a){var s=a("23e7"),c=a("a2bf"),u=a("7b0b"),d=a("50c4"),h=a("1c0b"),k=a("65f0");s({target:"Array",proto:!0},{flatMap:function(b){var g=u(this),v=d(g.length),w;return h(b),w=k(g,0),w.length=c(w,g,g,v,0,1,b,arguments.length>1?arguments[1]:void 0),w}})},6547:function(r,l,a){var s=a("a691"),c=a("1d80"),u=function(d){return function(h,k){var m=String(c(h)),b=s(k),g=m.length,v,w;return b<0||b>=g?d?"":void 0:(v=m.charCodeAt(b),v<55296||v>56319||b+1===g||(w=m.charCodeAt(b+1))<56320||w>57343?d?m.charAt(b):v:d?m.slice(b,b+2):(v-55296<<10)+(w-56320)+65536)}};r.exports={codeAt:u(!1),charAt:u(!0)}},"65f0":function(r,l,a){var s=a("861d"),c=a("e8b5"),u=a("b622"),d=u("species");r.exports=function(h,k){var m;return c(h)&&(m=h.constructor,typeof m=="function"&&(m===Array||c(m.prototype))?m=void 0:s(m)&&(m=m[d],m===null&&(m=void 0))),new(m===void 0?Array:m)(k===0?0:k)}},"69f3":function(r,l,a){var s=a("7f9a"),c=a("da84"),u=a("861d"),d=a("9112"),h=a("5135"),k=a("f772"),m=a("d012"),b=c.WeakMap,g,v,w,L=function(O){return w(O)?v(O):g(O,{})},x=function(O){return function(P){var B;if(!u(P)||(B=v(P)).type!==O)throw TypeError("Incompatible receiver, "+O+" required");return B}};if(s){var S=new b,E=S.get,M=S.has,T=S.set;g=function(O,P){return T.call(S,O,P),P},v=function(O){return E.call(S,O)||{}},w=function(O){return M.call(S,O)}}else{var j=k("state");m[j]=!0,g=function(O,P){return d(O,j,P),P},v=function(O){return h(O,j)?O[j]:{}},w=function(O){return h(O,j)}}r.exports={set:g,get:v,has:w,enforce:L,getterFor:x}},"6eeb":function(r,l,a){var s=a("da84"),c=a("9112"),u=a("5135"),d=a("ce4e"),h=a("8925"),k=a("69f3"),m=k.get,b=k.enforce,g=String(String).split("String");(r.exports=function(v,w,L,x){var S=x?!!x.unsafe:!1,E=x?!!x.enumerable:!1,M=x?!!x.noTargetGet:!1;if(typeof L=="function"&&(typeof w=="string"&&!u(L,"name")&&c(L,"name",w),b(L).source=g.join(typeof w=="string"?w:"")),v===s){E?v[w]=L:d(w,L);return}else S?!M&&v[w]&&(E=!0):delete v[w];E?v[w]=L:c(v,w,L)})(Function.prototype,"toString",function(){return typeof this=="function"&&m(this).source||h(this)})},"6f53":function(r,l,a){var s=a("83ab"),c=a("df75"),u=a("fc6a"),d=a("d1e7").f,h=function(k){return function(m){for(var b=u(m),g=c(b),v=g.length,w=0,L=[],x;v>w;)x=g[w++],(!s||d.call(b,x))&&L.push(k?[x,b[x]]:b[x]);return L}};r.exports={entries:h(!0),values:h(!1)}},"73d9":function(r,l,a){var s=a("44d2");s("flatMap")},7418:function(r,l){l.f=Object.getOwnPropertySymbols},"746f":function(r,l,a){var s=a("428f"),c=a("5135"),u=a("e538"),d=a("9bf2").f;r.exports=function(h){var k=s.Symbol||(s.Symbol={});c(k,h)||d(k,h,{value:u.f(h)})}},7839:function(r,l){r.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"7b0b":function(r,l,a){var s=a("1d80");r.exports=function(c){return Object(s(c))}},"7c73":function(r,l,a){var s=a("825a"),c=a("37e8"),u=a("7839"),d=a("d012"),h=a("1be4"),k=a("cc12"),m=a("f772"),b=">",g="<",v="prototype",w="script",L=m("IE_PROTO"),x=function(){},S=function(O){return g+w+b+O+g+"/"+w+b},E=function(O){O.write(S("")),O.close();var P=O.parentWindow.Object;return O=null,P},M=function(){var O=k("iframe"),P="java"+w+":",B;return O.style.display="none",h.appendChild(O),O.src=String(P),B=O.contentWindow.document,B.open(),B.write(S("document.F=Object")),B.close(),B.F},T,j=function(){try{T=document.domain&&new ActiveXObject("htmlfile")}catch{}j=T?E(T):M();for(var O=u.length;O--;)delete j[v][u[O]];return j()};d[L]=!0,r.exports=Object.create||function(P,B){var p;return P!==null?(x[v]=s(P),p=new x,x[v]=null,p[L]=P):p=j(),B===void 0?p:c(p,B)}},"7dd0":function(r,l,a){var s=a("23e7"),c=a("9ed3"),u=a("e163"),d=a("d2bb"),h=a("d44e"),k=a("9112"),m=a("6eeb"),b=a("b622"),g=a("c430"),v=a("3f8c"),w=a("ae93"),L=w.IteratorPrototype,x=w.BUGGY_SAFARI_ITERATORS,S=b("iterator"),E="keys",M="values",T="entries",j=function(){return this};r.exports=function(O,P,B,p,W,R,U){c(B,P,p);var N=function(ve){if(ve===W&&Z)return Z;if(!x&&ve in X)return X[ve];switch(ve){case E:return function(){return new B(this,ve)};case M:return function(){return new B(this,ve)};case T:return function(){return new B(this,ve)}}return function(){return new B(this)}},H=P+" Iterator",z=!1,X=O.prototype,K=X[S]||X["@@iterator"]||W&&X[W],Z=!x&&K||N(W),te=P=="Array"&&X.entries||K,$,oe,ce;if(te&&($=u(te.call(new O)),L!==Object.prototype&&$.next&&(!g&&u($)!==L&&(d?d($,L):typeof $[S]!="function"&&k($,S,j)),h($,H,!0,!0),g&&(v[H]=j))),W==M&&K&&K.name!==M&&(z=!0,Z=function(){return K.call(this)}),(!g||U)&&X[S]!==Z&&k(X,S,Z),v[P]=Z,W)if(oe={values:N(M),keys:R?Z:N(E),entries:N(T)},U)for(ce in oe)(x||z||!(ce in X))&&m(X,ce,oe[ce]);else s({target:P,proto:!0,forced:x||z},oe);return oe}},"7f9a":function(r,l,a){var s=a("da84"),c=a("8925"),u=s.WeakMap;r.exports=typeof u=="function"&&/native code/.test(c(u))},"825a":function(r,l,a){var s=a("861d");r.exports=function(c){if(!s(c))throw TypeError(String(c)+" is not an object");return c}},"83ab":function(r,l,a){var s=a("d039");r.exports=!s(function(){return Object.defineProperty({},1,{get:function(){return 7}})[1]!=7})},8418:function(r,l,a){var s=a("c04e"),c=a("9bf2"),u=a("5c6c");r.exports=function(d,h,k){var m=s(h);m in d?c.f(d,m,u(0,k)):d[m]=k}},"861d":function(r,l){r.exports=function(a){return typeof a=="object"?a!==null:typeof a=="function"}},8875:function(r,l,a){var s,c,u;(function(d,h){c=[],s=h,u=typeof s=="function"?s.apply(l,c):s,u!==void 0&&(r.exports=u)})(typeof self<"u"?self:this,function(){function d(){var h=Object.getOwnPropertyDescriptor(document,"currentScript");if(!h&&"currentScript"in document&&document.currentScript||h&&h.get!==d&&document.currentScript)return document.currentScript;try{throw new Error}catch(T){var k=/.*at [^(]*\((.*):(.+):(.+)\)$/ig,m=/@([^@]*):(\d+):(\d+)\s*$/ig,b=k.exec(T.stack)||m.exec(T.stack),g=b&&b[1]||!1,v=b&&b[2]||!1,w=document.location.href.replace(document.location.hash,""),L,x,S,E=document.getElementsByTagName("script");g===w&&(L=document.documentElement.outerHTML,x=new RegExp("(?:[^\\n]+?\\n){0,"+(v-2)+"}[^<]* +
diff --git a/src/agents/outlineScript/index.ts b/src/agents/outlineScript/index.ts index 304c7c0..2ca7d75 100644 --- a/src/agents/outlineScript/index.ts +++ b/src/agents/outlineScript/index.ts @@ -1,10 +1,8 @@ // @/agents/outlineScript.ts import u from "@/utils"; -import { createAgent } from "langchain"; import { EventEmitter } from "events"; -import { openAI } from "@/agents/models"; +import { tool, ModelMessage } from "ai"; import { z } from "zod"; -import { tool } from "@langchain/core/tools"; import type { DB } from "@/types/database"; // ==================== 类型定义 ==================== @@ -75,13 +73,9 @@ const episodeSchema = z.object({ export default class OutlineScript { private readonly projectId: number; readonly emitter = new EventEmitter(); - history: Array<[string, string]> = []; + history: Array = []; novelChapters: DB["t_novel"][] = []; - modelName = "gpt-4.1"; - apiKey = ""; - baseURL = ""; - constructor(projectId: number) { this.projectId = projectId; } @@ -230,7 +224,7 @@ export default class OutlineScript { } } - const actualStart = overwrite ? 1 : startEpisode ?? (await this.getMaxEpisode()) + 1; + const actualStart = overwrite ? 1 : (startEpisode ?? (await this.getMaxEpisode()) + 1); const insertedCount = await this.insertOutlines(episodes, actualStart); const newOutlines = await u @@ -403,123 +397,107 @@ ${formatList(ep.classicQuotes, (q) => q)} // ==================== Tool 定义:故事线 ==================== - getStoryline = tool( - async () => { + getStoryline = tool({ + title: "getStoryline", + description: "Get the weather in a location", + inputSchema: z.object({}), + execute: async () => { this.log("获取故事线"); const storyline = await this.findStoryline(); return storyline?.content ?? "当前项目暂无故事线"; }, - { - name: "getStoryline", - description: "获取当前项目的故事线内容", - schema: z.object({}), - verboseParsingErrors: true, - }, - ); + }); - saveStoryline = tool( - async ({ content }) => { + saveStoryline = tool({ + title: "saveStoryline", + description: "保存或更新当前项目的故事线,会覆盖已有内容", + inputSchema: z.object({ + content: z.string().describe("故事线完整内容"), + }), + execute: async ({ content }) => { this.log("保存故事线"); await this.upsertStorylineContent(content); return "故事线保存成功"; }, - { - name: "saveStoryline", - description: "保存或更新当前项目的故事线,会覆盖已有内容", - schema: z.object({ - content: z.string().describe("故事线完整内容"), - }), - verboseParsingErrors: true, - }, - ); + }); - deleteStoryline = tool( - async () => { + deleteStoryline = tool({ + title: "deleteStoryline", + description: "删除当前项目的故事线", + inputSchema: z.object({}), + execute: async () => { this.log("删除故事线"); const deleted = await this.deleteStorylineContent(); return deleted > 0 ? "故事线删除成功" : "当前项目没有故事线"; }, - { - name: "deleteStoryline", - description: "删除当前项目的故事线", - schema: z.object({}), - verboseParsingErrors: true, - }, - ); + }); // ==================== Tool 定义:大纲 ==================== - getOutline = tool( - async ({ simplified = false }) => { + getOutline = tool({ + title: "getOutline", + description: "获取项目大纲。simplified=true返回简化列表,false返回完整内容", + inputSchema: z.object({ + simplified: z.boolean().default(false).describe("是否返回简化版本"), + }), + execute: async ({ simplified }) => { this.log("获取大纲", `简化模式: ${simplified}`); return this.getOutlineText(simplified); }, - { - name: "getOutline", - description: "获取项目大纲。simplified=true返回简化列表,false返回完整内容", - schema: z.object({ - simplified: z.boolean().default(false).describe("是否返回简化版本"), - }), - verboseParsingErrors: true, - }, - ); + }); - saveOutline = tool( - async ({ episodes, overwrite = true, startEpisode }) => { + saveOutline = tool({ + title: "saveOutline", + description: "保存大纲数据。overwrite=true会清空现有大纲后写入,false则追加到末尾", + inputSchema: z.object({ + episodes: z.array(episodeSchema).min(1).describe("大纲数据数组"), + overwrite: z.boolean().default(true).describe("是否覆盖现有大纲"), + startEpisode: z.number().optional().describe("追加模式下的起始集数(不填则自动递增)"), + }), + execute: async ({ episodes, overwrite = true, startEpisode }) => { this.log("保存大纲", `覆盖模式: ${overwrite}, 集数: ${episodes.length}`); const { insertedCount, scriptCount } = await this.saveOutlineData(episodes as EpisodeData[], overwrite, startEpisode); return `大纲保存成功:插入 ${insertedCount} 集大纲,创建 ${scriptCount} 个剧本记录`; }, - { - name: "saveOutline", - description: "保存大纲数据。overwrite=true会清空现有大纲后写入,false则追加到末尾", - schema: z.object({ - episodes: z.array(episodeSchema).min(1).describe("大纲数据数组"), - overwrite: z.boolean().default(true).describe("是否覆盖现有大纲"), - startEpisode: z.number().optional().describe("追加模式下的起始集数(不填则自动递增)"), - }), - verboseParsingErrors: true, - }, - ); + }); - updateOutline = tool( - async ({ id, data }) => { + updateOutline = tool({ + title: "updateOutline", + description: "更新指定ID的单集大纲内容", + inputSchema: z.object({ + id: z.number().describe("大纲ID"), + data: episodeSchema.describe("更新后的大纲数据"), + }), + execute: async ({ id, data }) => { this.log("更新大纲", `ID: ${id}`); const success = await this.updateOutlineData(id, data as EpisodeData); return success ? `大纲ID ${id} 更新成功` : `未找到大纲ID: ${id}`; }, - { - name: "updateOutline", - description: "更新指定ID的单集大纲内容", - schema: z.object({ - id: z.number().describe("大纲ID"), - data: episodeSchema.describe("更新后的大纲数据"), - }), - verboseParsingErrors: true, - }, - ); + }); - deleteOutline = tool( - async ({ ids }) => { + deleteOutline = tool({ + title: "deleteOutline", + description: "根据大纲ID删除指定大纲及关联数据", + inputSchema: z.object({ + ids: z.array(z.number()).min(1).describe("要删除的大纲ID数组"), + }), + execute: async ({ ids }) => { this.log("删除大纲", `IDs: ${ids.join(", ")}`); const results = await this.deleteOutlineData(ids); const summary = results.map((r, i) => `ID ${ids[i]}: ${r.status === "fulfilled" ? "成功" : "失败"}`).join(", "); return `删除结果: ${summary}`; }, - { - name: "deleteOutline", - description: "根据大纲ID删除指定大纲及关联数据", - schema: z.object({ - ids: z.array(z.number()).min(1).describe("要删除的大纲ID数组"), - }), - verboseParsingErrors: true, - }, - ); + }); // ==================== Tool 定义:章节 ==================== - getChapter = tool( - async ({ chapterNumbers }) => { + getChapter = tool({ + title: "getChapter", + description: "根据章节编号获取小说章节的完整原文内容,支持批量获取", + inputSchema: z.object({ + chapterNumbers: z.array(z.number()).min(1).describe("章节编号数组"), + }), + execute: async ({ chapterNumbers }) => { this.log("获取章节", `章节号: ${chapterNumbers.join(", ")}`); const results = await Promise.all( @@ -539,36 +517,24 @@ ${formatList(ep.classicQuotes, (q) => q)} return results.join("\n\n---\n"); }, - { - name: "getChapter", - description: "根据章节编号获取小说章节的完整原文内容,支持批量获取", - schema: z.object({ - chapterNumbers: z.array(z.number()).min(1).describe("章节编号数组"), - }), - verboseParsingErrors: true, - }, - ); + }); // ==================== Tool 定义:资产 ==================== - generateAssets = tool( - async () => { + generateAssets = tool({ + title: "generateAssets", + description: "从当前项目的所有大纲中提取并生成角色、道具、场景资产,自动去重并清理冗余", + inputSchema: z.object({}), + execute: async () => { this.log("生成资产"); const stats = await this.generateAssetsFromOutlines(); if (stats.inserted === 0 && stats.updated === 0 && stats.skipped === 0) { return "当前项目没有大纲数据,无法生成资产"; } - return `资产生成完成:新增 ${stats.inserted},更新 ${stats.updated},保持 ${stats.skipped}`; }, - { - name: "generateAssets", - description: "从当前项目的所有大纲中提取并生成角色、道具、场景资产,自动去重并清理冗余", - schema: z.object({}), - verboseParsingErrors: true, - }, - ); + }); // ==================== 上下文构建 ==================== @@ -606,7 +572,7 @@ ${this.getChapterContext()} private buildConversationHistory(): string { if (!this.history.length) return "无对话历史"; - return this.history.map(([role, content]) => `${role}: ${content}`).join("\n\n"); + return this.history.map(({ role, content }) => `${role}: ${content}`).join("\n\n"); } private async buildFullContext(task: string): Promise { @@ -627,14 +593,14 @@ ${task} // ==================== Sub-Agent ==================== private getSubAgentTools() { - return [this.getChapter, this.getStoryline, this.saveStoryline, this.getOutline, this.saveOutline, this.updateOutline]; - } - - private createModel() { - return openAI({ - modelName: this.modelName, - configuration: { apiKey: this.apiKey, baseURL: this.baseURL }, - }); + return { + getChapter: this.getChapter, + getStoryline: this.getStoryline, + saveStoryline: this.saveStoryline, + getOutline: this.getOutline, + saveOutline: this.saveOutline, + updateOutline: this.updateOutline, + }; } /** @@ -645,123 +611,124 @@ ${task} this.log(`Sub-Agent 调用`, agentType); const promptsList = await u.db("t_prompts").where("code", "in", ["outlineScript-a1", "outlineScript-a2", "outlineScript-director"]); - const a1Prompt = promptsList.find((p) => p.code === "outlineScript-a1"); - const a2Prompt = promptsList.find((p) => p.code === "outlineScript-a2"); - const directorPrompt = promptsList.find((p) => p.code === "outlineScript-director"); + const promptConfig = await u.getPromptAi("outlineScriptAgent"); + const errPrompts = "不论用户说什么,请直接输出Agent配置异常"; - const SYSTEM_PROMPTS: Record = { - AI1: a1Prompt?.customValue || a1Prompt?.defaultValue || errPrompts, - AI2: a2Prompt?.customValue || a2Prompt?.defaultValue || errPrompts, - director: directorPrompt?.customValue || directorPrompt?.defaultValue || errPrompts, + + const getAiPromptConfig = (code: string) => { + const item = promptsList.find((p) => p.code === code); + return item?.customValue || item?.defaultValue || errPrompts; + }; + const a1Prompt = getAiPromptConfig("outlineScript-a1"); + const a2Prompt = getAiPromptConfig("outlineScript-a2"); + const directorPrompt = getAiPromptConfig("outlineScript-director"); + const SYSTEM_PROMPTS = { + AI1: a1Prompt, + AI2: a2Prompt, + director: directorPrompt, }; const context = await this.buildFullContext(task); - const agent = createAgent({ - model: this.createModel(), - systemPrompt: SYSTEM_PROMPTS[agentType], - tools: this.getSubAgentTools(), - }); - - const stream = await agent.stream({ messages: [["user", context]] }, { streamMode: ["messages"], callbacks: [] }); + const { fullStream } = await u.ai.text.stream( + { + system: SYSTEM_PROMPTS[agentType], + tools: this.getSubAgentTools(), + messages: [{ role: "user", content: context }], + maxStep: 100, + }, + promptConfig, + ); let fullResponse = ""; - - for await (const [mode, chunk] of stream) { - if (mode !== "messages") continue; - - const [token] = chunk as any; - const block = token.contentBlocks?.[0]; - - // 处理 AI 文本流 - if (token.type === "ai" && block?.text) { - fullResponse += block.text; - this.emit("subAgentStream", { agent: agentType, text: block.text }); + for await (const item of fullStream) { + if (item.type == "tool-call") { + this.emit("toolCall", { agent: "main", name: item.title, args: null }); } - - // 处理 tool 调用 - if (token.type === "ai" && token.tool_calls?.length) { - for (const toolCall of token.tool_calls) { - this.emit("toolCall", { agent: agentType, name: toolCall.name, args: toolCall.args }); - } + if (item.type == "text-delta") { + fullResponse += item.text; + this.emit("subAgentStream", { agent: agentType, text: item.text }); } } this.emit("subAgentEnd", { agent: agentType }); - this.history.push(["ai", fullResponse]); + this.history.push({ + role: "assistant", + content: fullResponse, + }); this.log(`Sub-Agent 完成`, agentType); return fullResponse ?? `${agentType}已完成任务`; } private createSubAgentTool(agentType: AgentType, description: string) { - return tool(async ({ taskDescription }) => this.invokeSubAgent(agentType, taskDescription), { - name: agentType, + return tool({ + title: agentType, description, - schema: z.object({ + inputSchema: z.object({ taskDescription: z.string().describe("具体的任务描述,包含章节范围、修改要求等详细信息"), }), + execute: async ({ taskDescription }) => this.invokeSubAgent(agentType, taskDescription), }); } // ==================== 主入口 ==================== private getAllTools() { - return [ - this.createSubAgentTool("AI1", "调用故事师。负责分析小说原文并生成故事线,会自行调用 saveStoryline 保存结果。"), - this.createSubAgentTool("AI2", "调用大纲师。负责根据故事线生成剧集大纲,会自行调用 saveOutline 保存结果。"), - this.createSubAgentTool("director", "调用导演。负责审核故事线和大纲,会自行调用 updateOutline 或 saveStoryline 进行修改。"), - this.getChapter, - this.getStoryline, - this.saveStoryline, - this.deleteStoryline, - this.getOutline, - this.saveOutline, - this.updateOutline, - this.deleteOutline, - this.generateAssets, - ]; + return { + AI1: this.createSubAgentTool("AI1", "调用故事师。负责分析小说原文并生成故事线,会自行调用 saveStoryline 保存结果。"), + AI2: this.createSubAgentTool("AI2", "调用大纲师。负责根据故事线生成剧集大纲,会自行调用 saveOutline 保存结果。"), + director: this.createSubAgentTool("director", "调用导演。负责审核故事线和大纲,会自行调用 updateOutline 或 saveStoryline 进行修改。"), + getChapter: this.getChapter, + getStoryline: this.getStoryline, + saveStoryline: this.saveStoryline, + deleteStoryline: this.deleteStoryline, + getOutline: this.getOutline, + saveOutline: this.saveOutline, + updateOutline: this.updateOutline, + deleteOutline: this.deleteOutline, + generateAssets: this.generateAssets, + }; } async call(msg: string): Promise { - this.history.push(["user", msg]); + this.history.push({ + role: "user", + content: msg, + }); const envContext = await this.buildEnvironmentContext(); const prompts = await u.db("t_prompts").where("code", "outlineScript-main").first(); + const promptConfig = await u.getPromptAi("outlineScriptAgent"); const mainPrompts = prompts?.customValue || prompts?.defaultValue || "不论用户说什么,请直接输出Agent配置异常"; - const mainAgent = createAgent({ - model: this.createModel(), - tools: this.getAllTools(), - systemPrompt: `${envContext}\n${mainPrompts}`, - }); - const stream = await mainAgent.stream({ messages: this.history }, { streamMode: ["messages"], callbacks: [] }); + const { fullStream } = await u.ai.text.stream( + { + system: `${envContext}\n${mainPrompts}`, + tools: this.getAllTools(), + messages: this.history, + maxStep: 100, + }, + promptConfig, + ); let fullResponse = ""; - - for await (const [mode, chunk] of stream) { - if (mode !== "messages") continue; - - const [token] = chunk as any; - const block = token.contentBlocks?.[0]; - - // 处理 AI 文本流 - if (token.type === "ai" && block?.text) { - fullResponse += block.text; - this.emit("data", block.text); + for await (const item of fullStream) { + if (item.type == "tool-call") { + this.emit("toolCall", { agent: "main", name: item.title, args: null }); } - - // 处理 tool 调用 - if (token.type === "ai" && token.tool_calls?.length) { - for (const toolCall of token.tool_calls) { - this.emit("toolCall", { agent: "main", name: toolCall.name, args: toolCall.args }); - } + if (item.type == "text-delta") { + fullResponse += item.text; + this.emit("data", item.text); } } + this.history.push({ + role: "assistant", + content: fullResponse, + }); - this.history.push(["assistant", fullResponse]); this.emit("response", fullResponse); return fullResponse; diff --git a/src/agents/storyboard/generateImagePromptsTool.ts b/src/agents/storyboard/generateImagePromptsTool.ts index 4057221..bd08a23 100644 --- a/src/agents/storyboard/generateImagePromptsTool.ts +++ b/src/agents/storyboard/generateImagePromptsTool.ts @@ -98,31 +98,47 @@ async function generateGridPrompt(options: GridPromptOptions): Promise `第${i + 1}格: ${p}`).join("\n")}`; if (!mainPrompts) return { prompt: errData, gridLayout: layout }; - const chatModel = await u.ai.text({}); + const result = await u.ai.text.invoke( + { + messages: [ + { + role: "system", + content: mainPrompts, + }, + { + role: "user", + content: `请优化以下分镜提示词:\n\n【布局】${layout.cols}列×${layout.rows}行=${ + layout.totalCells + }格\n【比例】${aspectRatio}(${aspectRatioDesc})\n【风格】${style}\n${assetsSection}\n\n【原始内容】\n${gridPositions.join("\n")}`, + }, + ], + }, + promptAiConfig, + ); - const result = await chatModel!.invoke({ - messages: [ - { - role: "system", - content: mainPrompts, - }, - { - role: "user", - content: `请优化以下分镜提示词:\n\n【布局】${layout.cols}列×${layout.rows}行=${ - layout.totalCells - }格\n【比例】${aspectRatio}(${aspectRatioDesc})\n【风格】${style}\n${assetsSection}\n\n【原始内容】\n${gridPositions.join("\n")}`, - }, - ], - }); + // const result = await chatModel!.invoke({ + // messages: [ + // { + // role: "system", + // content: mainPrompts, + // }, + // { + // role: "user", + // content: `请优化以下分镜提示词:\n\n【布局】${layout.cols}列×${layout.rows}行=${ + // layout.totalCells + // }格\n【比例】${aspectRatio}(${aspectRatioDesc})\n【风格】${style}\n${assetsSection}\n\n【原始内容】\n${gridPositions.join("\n")}`, + // }, + // ], + // }); return { - prompt: result?.text ?? errData, + prompt: result.text ?? errData, gridLayout: layout, }; } diff --git a/src/agents/storyboard/generateImageTool.ts b/src/agents/storyboard/generateImageTool.ts index 151c830..0c61d31 100644 --- a/src/agents/storyboard/generateImageTool.ts +++ b/src/agents/storyboard/generateImageTool.ts @@ -36,18 +36,6 @@ interface ResourceItem { intro: string; } -// 资产过滤响应的 schema -const filteredAssetsSchema = z.object({ - relevantAssets: z - .array( - z.object({ - name: z.string().describe("资产名称"), - reason: z.string().describe("选择该资产的原因"), - }), - ) - .describe("与分镜内容相关的资产列表"), -}); - // 压缩图片直到不超过指定大小 async function compressImage(buffer: Buffer, maxSizeBytes: number = 3 * 1024 * 1024): Promise { if (buffer.length <= maxSizeBytes) { @@ -215,12 +203,13 @@ async function filterRelevantAssets(prompts: string[], allResources: ResourceIte return availableImages; } - const chatModel = await u.ai.text({}); - const result = await chatModel!.invoke({ - messages: [ - { - role: "user", - content: `请分析以下分镜描述,从可用资产中筛选出与分镜内容直接相关的资产。 + const apiConfig = await u.getPromptAi("storyboardAgent"); + const { relevantAssets } = await u.ai.text.invoke( + { + messages: [ + { + role: "user", + content: `请分析以下分镜描述,从可用资产中筛选出与分镜内容直接相关的资产。 分镜描述: ${prompts.map((p, i) => `${i + 1}. ${p}`).join("\n")} @@ -229,25 +218,27 @@ ${prompts.map((p, i) => `${i + 1}. ${p}`).join("\n")} ${availableResources.map((r) => `- ${r.name}:${r.intro}`).join("\n")} 请仅选择在分镜中明确出现或被提及的角色、场景、道具。不要选择与分镜内容无关的资产。`, - }, - ], - responseFormat: { - type: "json_schema", - jsonSchema: { - name: "filteredAssets", - strict: true, - schema: z.toJSONSchema(filteredAssetsSchema), + }, + ], + output: { + relevantAssets: z + .array( + z.object({ + name: z.string().describe("资产名称"), + reason: z.string().describe("选择该资产的原因"), + }), + ) + .describe("与分镜内容相关的资产列表"), }, }, - }); + apiConfig, + ); - const data = result?.json as z.infer; - - if (!data?.relevantAssets || data.relevantAssets.length === 0) { + if (!relevantAssets || relevantAssets.length === 0) { return availableImages; } - const relevantNames = new Set(data.relevantAssets.map((a) => a.name)); + const relevantNames = new Set(relevantAssets.map((a) => a.name)); const filteredImages = availableImages.filter((img) => relevantNames.has(img.name)); return filteredImages.length > 0 ? filteredImages : availableImages; @@ -317,14 +308,18 @@ export default async (cells: { prompt: string }[], scriptId: number, projectId: console.log("====润色后:", prompts); const processedImages = await processImages(filteredImages); + const apiConfig = await u.getPromptAi("storyboardImage"); - const contentStr = await u.ai.generateImage({ - systemPrompt: resourcesMapPrompts, - prompt: prompts, - size: "4K", - aspectRatio: projectInfo?.videoRatio ? (projectInfo.videoRatio as any) : "16:9", - imageBase64: processedImages.map((buf) => buf.toString("base64")), - }); + const contentStr = await u.ai.image( + { + systemPrompt: resourcesMapPrompts, + prompt: prompts, + size: "4K", + aspectRatio: projectInfo?.videoRatio ? (projectInfo.videoRatio as any) : "16:9", + imageBase64: processedImages.map((buf) => buf.toString("base64")), + }, + apiConfig, + ); const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); const base64Str = match?.[1] ?? contentStr; diff --git a/src/agents/storyboard/index.ts b/src/agents/storyboard/index.ts index f215888..308985f 100644 --- a/src/agents/storyboard/index.ts +++ b/src/agents/storyboard/index.ts @@ -1,13 +1,13 @@ // @/agents/Storyboard.ts import u from "@/utils"; -import { createAgent } from "langchain"; +import { tool, ModelMessage, Tool } from "ai"; import { EventEmitter } from "events"; -import { openAI } from "@/agents/models"; import { z } from "zod"; -import { tool } from "@langchain/core/tools"; import type { DB } from "@/types/database"; import generateImageTool from "./generateImageTool"; import imageSplitting from "./imageSplitting"; +import path from "path"; +import sharp from "sharp"; // ==================== 类型定义 ==================== @@ -38,15 +38,20 @@ interface Shot { x: number; y: number; cells: Array<{ src?: string; prompt?: string; id?: string }>; // 镜头数组,每个cell是一个镜头 + fragmentContent: string; + assetsTags: AssetsType[]; +} +interface AssetsType { + type: "role" | "props" | "scene"; + text: string; } - // ==================== 主类 ==================== export default class Storyboard { private readonly projectId: number; private readonly scriptId: number; readonly emitter = new EventEmitter(); - history: Array<[string, string]> = []; + history: ModelMessage[] = []; novelChapters: DB["t_novel"][] = []; // 存储 segmentAgent 生成的片段结果 @@ -58,10 +63,6 @@ export default class Storyboard { // 存储正在生成分镜图的分镜ID private generatingShots: Set = new Set(); - modelName = "gpt-4.1"; - apiKey = ""; - baseURL = ""; - constructor(projectId: number, scriptId: number) { this.projectId = projectId; this.scriptId = scriptId; @@ -69,9 +70,6 @@ export default class Storyboard { // 更新shopts public updatePreShots(segmentId: number, cellId: number, cell: { src?: string; prompt?: string; id?: string }) { - console.log("%c Line:76 🍤 segmentId", "background:#465975", segmentId); - console.log("%c Line:76 🍷 cellId", "background:#ffdd4d", cellId); - console.log("%c Line:76 🍢 cell", "background:#ffdd4d", cell); const shotIndex = this.shots.findIndex((item) => item.segmentId === segmentId); if (shotIndex === -1) { return `分镜 ${segmentId} 不存在,请检查分镜ID是否正确`; @@ -105,28 +103,28 @@ export default class Storyboard { // ==================== 剧本相关操作 ==================== - getScript = tool( - async () => { + getScript = tool({ + title: "getScript", + description: "获取剧本内容", + inputSchema: z.object({}), + execute: async () => { this.log("获取剧本", `scriptId: ${this.scriptId}`); const script = await u.db("t_script").where({ id: this.scriptId, projectId: this.projectId }).first(); if (!script) throw new Error("剧本不存在"); return `剧本集:${script.name}\n\n内容:\n\`\`\`${script.content}\`\`\``; }, - { - name: "getScript", - description: "获取剧本内容", - schema: z.object({}), - verboseParsingErrors: true, - }, - ); + }); // ==================== 资产相关操作 ==================== /** * 获取资产列表(供 segmentAgent 和 shotAgent 调用) */ - getAssets = tool( - async () => { + getAssets = tool({ + title: "getAssets", + description: "获取资产列表(角色、道具、场景),包含名称和详细介绍。生成片段和分镜时必须先调用此工具获取资产信息,确保名称一致性", + inputSchema: z.object({}), + execute: async () => { this.log("获取资产列表", `scriptId: ${this.scriptId}`); const scriptData = await u.db("t_script").where({ id: this.scriptId, projectId: this.projectId }).first(); const row = await u.db("t_outline").where({ id: scriptData?.outlineId!, projectId: this.projectId }).first(); @@ -171,76 +169,84 @@ ${sections.join("\n\n")} 2. 禁止在资产名称前后添加修饰词 3. 禁止捏造资产列表中不存在的角色、场景、道具`; }, - { - name: "getAssets", - description: "获取资产列表(角色、道具、场景),包含名称和详细介绍。生成片段和分镜时必须先调用此工具获取资产信息,确保名称一致性", - schema: z.object({}), - verboseParsingErrors: true, - }, - ); + }); // ==================== 片段和分镜工具 ==================== /** * 获取当前存储的片段数据(供 shotAgent 调用) */ - getSegments = tool( - async () => { + getSegments = tool({ + title: "getSegments", + description: "获取当前已生成的片段数据,用于生成分镜", + inputSchema: z.object({}), + execute: async () => { this.log("获取片段数据", `共 ${this.segments.length} 个片段`); if (this.segments.length === 0) { return "暂无片段数据,请先调用 segmentAgent 生成片段"; } return JSON.stringify(this.segments, null, 2); }, - { - name: "getSegments", - description: "获取当前已生成的片段数据,用于生成分镜", - schema: z.object({}), - verboseParsingErrors: true, - }, - ); + }); /** * 更新/存储片段数据(供 segmentAgent 调用) */ - updateSegments = tool( - async ({ segments }: { segments: Segment[] }) => { + updateSegments = tool({ + title: "updateSegments", + description: "存储生成的片段数据,segmentAgent 在生成片段后必须调用此工具保存结果", + inputSchema: z.object({ + segments: z + .array( + z.object({ + index: z.number().describe("片段序号"), + description: z.string().describe("片段描述"), + emotion: z.string().optional().describe("情绪氛围"), + action: z.string().optional().describe("主要动作"), + }), + ) + .describe("片段数组"), + }), + execute: async ({ segments }: { segments: Segment[] }) => { this.log("更新片段数据", `共 ${segments.length} 个片段`); this.segments = segments; this.emit("segmentsUpdated", this.segments); return `成功存储 ${segments.length} 个片段`; }, - { - name: "updateSegments", - description: "存储生成的片段数据,segmentAgent 在生成片段后必须调用此工具保存结果", - schema: z.object({ - segments: z - .array( - z.object({ - index: z.number().describe("片段序号"), - description: z.string().describe("片段描述"), - emotion: z.string().optional().describe("情绪氛围"), - action: z.string().optional().describe("主要动作"), - }), - ) - .describe("片段数组"), - }), - verboseParsingErrors: true, - }, - ); + }); /** * 添加分镜(供 shotAgent 调用) */ - addShots = tool( - async ({ shots }: { shots: Array<{ segmentIndex: number; prompts: string[] }> }) => { + addShots = tool({ + title: "addShots", + description: "添加新的分镜。每个分镜有独立ID,包含多个镜头(每个镜头对应一个提示词)。如果片段已存在分镜会跳过", + inputSchema: z.object({ + shots: z + .array( + z.object({ + segmentIndex: z.number().describe("对应的片段序号"), + prompts: z.array(z.string()).describe("镜头提示词数组,每个提示词对应一个镜头(中文)"), + assetsTags: z.array( + z.object({ + type: z.enum(["role", "props", "scene"]).describe("资源类型"), + text: z.string().describe("资源名称"), + }), + ), + }), + ) + .describe("要添加的分镜数组"), + }), + execute: async ({ shots }: { shots: Array<{ segmentIndex: number; prompts: string[]; assetsTags: AssetsType[] }> }) => { const added: { id: number; segmentIndex: number }[] = []; const skipped: number[] = []; for (const item of shots) { - const exists = this.shots.some((f) => f.segmentId === item.segmentIndex); + const resultIndex = item.segmentIndex - 1; + + const exists = this.shots.some((f) => f.segmentId === resultIndex); if (exists) { - skipped.push(item.segmentIndex); + skipped.push(resultIndex); continue; } // 分配独立的分镜ID @@ -248,13 +254,15 @@ ${sections.join("\n\n")} const shotId = this.shotIdCounter; this.shots.push({ id: shotId, - segmentId: item.segmentIndex, + segmentId: resultIndex, title: `分镜 ${shotId}`, x: 0, y: 0, cells: item.prompts.map((prompt) => ({ id: u.uuid(), prompt })), + fragmentContent: this.segments[resultIndex]?.description, + assetsTags: item.assetsTags, }); - added.push({ id: shotId, segmentIndex: item.segmentIndex }); + added.push({ id: shotId, segmentIndex: resultIndex }); } const addedInfo = added.map((a) => `分镜${a.id}(片段${a.segmentIndex})`).join(", "); @@ -266,29 +274,20 @@ ${sections.join("\n\n")} } return `已添加${addedInfo}。当前共 ${this.shots.length} 个分镜`; }, - { - name: "addShots", - description: "添加新的分镜。每个分镜有独立ID,包含多个镜头(每个镜头对应一个提示词)。如果片段已存在分镜会跳过", - schema: z.object({ - shots: z - .array( - z.object({ - segmentIndex: z.number().describe("对应的片段序号"), - prompts: z.array(z.string()).describe("镜头提示词数组,每个提示词对应一个镜头(中文)"), - }), - ) - .describe("要添加的分镜数组"), - }), - verboseParsingErrors: true, - }, - ); + }); /** * 更新指定分镜(供 shotAgent 调用) * 保留原有 cells 的 id 和 src 字段,只更新 prompt */ - updateShots = tool( - async ({ shotId, prompts }: { shotId: number; prompts: string[] }) => { + updateShots = tool({ + title: "updateShots", + description: "更新指定分镜的镜头提示词。通过分镜ID指定要修改的分镜", + inputSchema: z.object({ + shotId: z.number().describe("要更新的分镜ID"), + prompts: z.array(z.string()).describe("新的镜头提示词数组,每个提示词对应一个镜头"), + }), + execute: async ({ shotId, prompts }: { shotId: number; prompts: string[] }) => { const existingIndex = this.shots.findIndex((item) => item.id === shotId); if (existingIndex === -1) { @@ -314,22 +313,18 @@ ${sections.join("\n\n")} return `已更新分镜 ${shotId}`; }, - { - name: "updateShots", - description: "更新指定分镜的镜头提示词。通过分镜ID指定要修改的分镜", - schema: z.object({ - shotId: z.number().describe("要更新的分镜ID"), - prompts: z.array(z.string()).describe("新的镜头提示词数组,每个提示词对应一个镜头"), - }), - verboseParsingErrors: true, - }, - ); + }); /** * 删除指定分镜(供 shotAgent 调用) */ - deleteShots = tool( - async ({ shotIds }: { shotIds: number[] }) => { + deleteShots = tool({ + title: "deleteShots", + description: "删除指定的分镜。通过分镜ID指定要删除的分镜", + inputSchema: z.object({ + shotIds: z.array(z.number()).describe("要删除的分镜ID数组"), + }), + execute: async ({ shotIds }: { shotIds: number[] }) => { const deleted: number[] = []; const notFound: number[] = []; @@ -351,21 +346,19 @@ ${sections.join("\n\n")} } return `已删除分镜 ${deleted.join(", ")}。当前共 ${this.shots.length} 个分镜`; }, - { - name: "deleteShots", - description: "删除指定的分镜。通过分镜ID指定要删除的分镜", - schema: z.object({ - shotIds: z.array(z.number()).describe("要删除的分镜ID数组"), - }), - verboseParsingErrors: true, - }, - ); + }); /** * 生成分镜图(异步执行,使用 nanoBanana) */ - generateShotImage = tool( - async ({ shotIds }: { shotIds: number[] }) => { + generateShotImage = tool({ + title: "generateShotImage", + description: + "为指定分镜生成分镜图。每个分镜会根据其所有提示词生成一张完整宫格图,然后自动分割为单格图片。通过分镜ID指定,不需要指定具体格子,整个分镜是一个完整的生成单元", + inputSchema: z.object({ + shotIds: z.array(z.number()).describe("要生成分镜图的分镜ID数组"), + }), + execute: async ({ shotIds }: { shotIds: number[] }) => { const toGenerate: number[] = []; const alreadyGenerating: number[] = []; const notFound: number[] = []; @@ -417,16 +410,7 @@ ${sections.join("\n\n")} } return result; }, - { - name: "generateShotImage", - description: - "为指定分镜生成分镜图。每个分镜会根据其所有提示词生成一张完整宫格图,然后自动分割为单格图片。通过分镜ID指定,不需要指定具体格子,整个分镜是一个完整的生成单元", - schema: z.object({ - shotIds: z.array(z.number()).describe("要生成分镜图的分镜ID数组"), - }), - verboseParsingErrors: true, - }, - ); + }); /** * 执行分镜图生成的具体逻辑(异步并发) @@ -462,7 +446,6 @@ ${sections.join("\n\n")} this.scriptId, this.projectId, ); - // 通知前端正在分割图片 this.emit("shotImageGenerateProgress", { shotId, status: "splitting", message: "正在分割宫格图片为单张镜头图" }); @@ -566,7 +549,7 @@ ${assetList} private buildConversationHistory(): string { if (!this.history.length) return "无对话历史"; - return this.history.map(([role, content]) => `${role}: ${content}`).join("\n\n"); + return this.history.map(({ role, content }) => `${role}: ${content}`).join("\n\n"); } private async buildFullContext(task: string): Promise { @@ -586,26 +569,33 @@ ${task} // ==================== Sub-Agent ==================== - private createModel() { - return openAI({ - modelName: this.modelName, - configuration: { apiKey: this.apiKey, baseURL: this.baseURL }, - }); - } - /** * 获取不同 Sub-Agent 可用的工具 */ - private getSubAgentTools(agentType: AgentType) { + private getSubAgentTools(agentType: AgentType): Record { switch (agentType) { case "segmentAgent": // segmentAgent 可以获取剧本和资产,并需要调用 updateSegments 保存结果 - return [this.getScript, this.getAssets, this.updateSegments]; + return { + getScript: this.getScript, + getAssets: this.getAssets, + updateSegments: this.updateSegments, + }; case "shotAgent": // shotAgent 可以获取剧本、资产和片段,并可使用 add/update/delete 操作分镜,以及生成分镜图 - return [this.getScript, this.getAssets, this.getSegments, this.addShots, this.updateShots, this.deleteShots, this.generateShotImage]; + return { + getScript: this.getScript, + getAssets: this.getAssets, + getSegments: this.getSegments, + addShots: this.addShots, + updateShots: this.updateShots, + deleteShots: this.deleteShots, + generateShotImage: this.generateShotImage, + }; default: - return [this.getScript]; + return { + getScript: this.getScript, + }; } } @@ -617,119 +607,124 @@ ${task} this.log(`Sub-Agent 调用`, agentType); const promptsList = await u.db("t_prompts").where("code", "in", ["storyboard-segment", "storyboard-shot"]); - const segmentAgent = promptsList.find((p) => p.code === "storyboard-segment"); - const shotAgent = promptsList.find((p) => p.code === "storyboard-shot"); + const promptConfig = await u.getPromptAi("storyboardAgent"); + const errPrompts = "不论用户说什么,请直接输出Agent配置异常"; - const SYSTEM_PROMPTS: Record = { - segmentAgent: segmentAgent?.customValue || segmentAgent?.defaultValue || errPrompts, - shotAgent: shotAgent?.customValue || shotAgent?.defaultValue || errPrompts, + + const getAiPromptConfig = (code: string) => { + const item = promptsList.find((p) => p.code === code); + return item?.customValue || item?.defaultValue || errPrompts; + }; + const segmentAgent = getAiPromptConfig("storyboard-segment"); + const shotAgent = getAiPromptConfig("storyboard-shot"); + const SYSTEM_PROMPTS = { + segmentAgent: segmentAgent, + shotAgent: shotAgent, }; const context = await this.buildFullContext(task); - const agent = createAgent({ - model: this.createModel(), - systemPrompt: SYSTEM_PROMPTS[agentType], - tools: this.getSubAgentTools(agentType), - }); - - const stream = await agent.stream({ messages: [["user", context]] }, { streamMode: ["messages"], callbacks: [] }); + const { fullStream } = await u.ai.text.stream( + { + system: SYSTEM_PROMPTS[agentType], + tools: this.getSubAgentTools(agentType), + messages: [{ role: "user", content: context }], + maxStep: 100, + }, + promptConfig, + ); let fullResponse = ""; - - for await (const [mode, chunk] of stream) { - if (mode !== "messages") continue; - const [token] = chunk as any; - const block = token.contentBlocks?.[0]; - - // 处理 AI 文本流 - if (token.type === "ai" && block?.text) { - fullResponse += block.text; - this.emit("subAgentStream", { agent: agentType, text: block.text }); + for await (const item of fullStream) { + if (item.type == "tool-call") { + this.emit("toolCall", { agent: "main", name: item.title, args: null }); } - // 处理 tool 调用 - if (token.type === "ai" && token.tool_calls?.length) { - for (const toolCall of token.tool_calls) { - this.emit("toolCall", { agent: agentType, name: toolCall.name, args: toolCall.args }); - } + if (item.type == "text-delta") { + fullResponse += item.text; + this.emit("subAgentStream", { agent: agentType, text: item.text }); } } this.emit("subAgentEnd", { agent: agentType }); - this.history.push(["ai", fullResponse]); + this.history.push({ + role: "assistant", + content: fullResponse, + }); this.log(`Sub-Agent 完成`, agentType); - return fullResponse; + + return fullResponse ?? `${agentType}已完成任务`; } private createSubAgentTool(agentType: AgentType, description: string) { - return tool(async ({ taskDescription }) => this.invokeSubAgent(agentType, taskDescription), { - name: agentType, + return tool({ + title: agentType, description, - schema: z.object({ + inputSchema: z.object({ taskDescription: z.string().describe("具体的任务描述,包含章节范围、修改要求等详细信息"), }), + execute: async ({ taskDescription }) => this.invokeSubAgent(agentType, taskDescription), }); } // ==================== 主入口 ==================== private getAllTools() { - return [ - this.createSubAgentTool( + return { + segmentAgent: this.createSubAgentTool( "segmentAgent", "调用片段师。负责根据剧本生成片段,会自行调用 getScript 获取剧本内容,并调用 updateSegments 保存片段结果。", ), - this.createSubAgentTool( + shotAgent: this.createSubAgentTool( "shotAgent", "调用分镜师。负责根据片段生成分镜提示词,会自行调用 getSegments 获取片段数据,并调用 addShots/updateShots 保存分镜结果。", ), // this.createSubAgentTool("director", "调用导演。负责审核故事线和大纲,会自行调用 updateOutline 或 saveStoryline 进行修改。"), - this.getScript, - this.getSegments, - this.generateShotImage, + getScript: this.getScript, + getSegments: this.getSegments, + generateShotImage: this.generateShotImage, ...this.getSubAgentTools("segmentAgent"), ...this.getSubAgentTools("shotAgent"), - ]; + }; } async call(msg: string): Promise { - console.log("模型名称:", this.modelName); - this.history.push(["user", msg]); + this.history.push({ + role: "user", + content: msg, + }); const envContext = await this.buildEnvironmentContext(); const prompts = await u.db("t_prompts").where("code", "storyboard-main").first(); + const promptConfig = await u.getPromptAi("storyboardAgent"); const mainPrompts = prompts?.customValue || prompts?.defaultValue || "不论用户说什么,请直接输出Agent配置异常"; - const mainAgent = createAgent({ - model: this.createModel(), - tools: this.getAllTools(), - systemPrompt: `${envContext}\n${mainPrompts}`, - }); - const stream = await mainAgent.stream({ messages: this.history }, { streamMode: ["messages"], callbacks: [] }); + const { fullStream } = await u.ai.text.stream( + { + system: `${envContext}\n${mainPrompts}`, + tools: this.getAllTools(), + messages: this.history, + maxStep: 100, + }, + promptConfig, + ); let fullResponse = ""; - - for await (const [mode, chunk] of stream) { - if (mode !== "messages") continue; - const [token] = chunk as any; - const block = token.contentBlocks?.[0]; - // 处理 AI 文本流 - if (token.type === "ai" && block?.text) { - fullResponse += block.text; - this.emit("data", block.text); + for await (const item of fullStream) { + if (item.type == "tool-call") { + this.emit("toolCall", { agent: "main", name: item.title, args: null }); } - - // 处理 tool 调用 - if (token.type === "ai" && token.tool_calls?.length) { - for (const toolCall of token.tool_calls) { - this.emit("toolCall", { agent: "main", name: toolCall.name, args: toolCall.args }); - } + if (item.type == "text-delta") { + fullResponse += item.text; + this.emit("data", item.text); } } + this.history.push({ + role: "assistant", + content: fullResponse, + }); - this.history.push(["assistant", fullResponse]); this.emit("response", fullResponse); return fullResponse; diff --git a/src/app.ts b/src/app.ts index db370f9..28e6263 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,3 +1,4 @@ +import "./logger"; import "./err"; import "./env"; import express, { Request, Response, NextFunction } from "express"; @@ -6,7 +7,6 @@ import logger from "morgan"; import cors from "cors"; import buildRoute from "@/core"; import fs from "fs"; -import router from "@/router"; import path from "path"; import u from "@/utils"; import jwt from "jsonwebtoken"; @@ -32,6 +32,7 @@ export default async function startServe() { } else { rootDir = path.join(process.cwd(), "uploads"); } + // 确保 uploads 目录存在 if (!fs.existsSync(rootDir)) { fs.mkdirSync(rootDir, { recursive: true }); @@ -60,7 +61,8 @@ export default async function startServe() { } }); - await router(app); + const router = await import("@/router"); + await router.default(app); // 404 处理 app.use((_, res, next: NextFunction) => { diff --git a/src/env.ts b/src/env.ts index d3f4040..069a4ee 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,31 +1,35 @@ -import { readFileSync, existsSync } from "fs"; +import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs"; +import path from "path"; -function loadDotenvESM(envPath = ".env.local") { - // 尝试从 userData 目录读取环境变量,如果不存在则使用当前目录 - let finalPath: string; +// 默认环境变量(当 env 文件不存在时自动创建) +const defaultEnvValues: Record = { + dev: `NODE_ENV=dev\nPORT=60000\nOSSURL=http://127.0.0.1:60000/`, + prod: `NODE_ENV=prod\nPORT=60000\nOSSURL=http://127.0.0.1:60000/`, +}; - if (typeof process.versions?.electron !== "undefined") { - const { app } = require("electron"); - finalPath = app.getPath("userData"); - // 如果 userData 目录中不存在,尝试使用当前目录 - if (!existsSync(finalPath)) { - finalPath = envPath; - } - } else { - finalPath = envPath; +//加载环境变量 +const env = process.env.NODE_ENV ?? "dev"; +if (!env) { + console.log("[环境变量为空]"); + process.exit(1); +} else { + const envDir = path.resolve("env"); + const envFilePath = path.join(envDir, `.env.${env}`); + + // 自动创建 env 目录和文件(.gitignore 可能忽略了这些文件) + if (!existsSync(envDir)) { + mkdirSync(envDir, { recursive: true }); + } + if (!existsSync(envFilePath)) { + const content = defaultEnvValues[env] ?? defaultEnvValues.prod; + writeFileSync(envFilePath, content, "utf8"); + console.log(`[环境变量] 自动创建 ${envFilePath}`); } - if (!existsSync(finalPath)) { - console.log(`[环境变量]: ${envPath} 文件不存在`); - return; - } - - const text = readFileSync(finalPath, "utf8"); + const text = readFileSync(envFilePath, "utf8"); for (const line of text.split("\n")) { const idx = line.indexOf("="); if (idx > 0) process.env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); } - console.log(`[环境变量]: ${finalPath}`); + console.log(`[环境变量] ${env}`); } - -if (process.env.NODE_ENV == "dev") loadDotenvESM(".env.local"); diff --git a/src/err.ts b/src/err.ts index 07d7770..34a3e66 100644 --- a/src/err.ts +++ b/src/err.ts @@ -1,10 +1,30 @@ +import { serializeError } from "serialize-error"; + // 处理未捕获的 Promise 拒绝 process.on('unhandledRejection', (reason, promise) => { - console.error('[未处理的 Promise 拒绝]:', reason); + console.error('[未处理的 Promise 拒绝]'); + if (reason instanceof Error) { + console.error('错误名称:', reason.name); + console.error('错误消息:', reason.message); + console.error('堆栈信息:', reason.stack); + console.error('序列化详情:', JSON.stringify(serializeError(reason), null, 2)); + } else { + console.error('原因:', reason); + console.error('类型:', typeof reason); + try { + console.error('JSON:', JSON.stringify(reason, null, 2)); + } catch { + console.error('(无法序列化)'); + } + } console.error('Promise:', promise); }); // 处理未捕获的异常 process.on('uncaughtException', (error) => { - console.error('[未捕获的异常]:', error); + console.error('[未捕获的异常]'); + console.error('错误名称:', error.name); + console.error('错误消息:', error.message); + console.error('堆栈信息:', error.stack); + console.error('序列化详情:', JSON.stringify(serializeError(error), null, 2)); }); diff --git a/src/lib/fixDB.ts b/src/lib/fixDB.ts index 501a6f6..79df5e8 100644 --- a/src/lib/fixDB.ts +++ b/src/lib/fixDB.ts @@ -1,17 +1,81 @@ import { Knex } from "knex"; export default async (knex: Knex): Promise => { - const videoHasTime = await knex.schema.hasColumn("t_video", "time"); - if (!videoHasTime) { - await knex.schema.alterTable("t_video", (table) => { - table.integer("time"); + const addColumn = async (table: string, column: string, type: string) => { + if (!(await knex.schema.hasTable(table))) return; + if (!(await knex.schema.hasColumn(table, column))) { + await knex.schema.alterTable(table, (t) => (t as any)[type](column)); + } + }; + + const dropColumn = async (table: string, column: string) => { + if (!(await knex.schema.hasTable(table))) return; + if (await knex.schema.hasColumn(table, column)) { + await knex.schema.alterTable(table, (t) => t.dropColumn(column)); + } + }; + + const alterColumnType = async (table: string, column: string, type: string) => { + if (!(await knex.schema.hasTable(table))) return; + if (await knex.schema.hasColumn(table, column)) { + await knex.schema.alterTable(table, (t) => { + (t as any)[type](column).alter(); + }); + } + }; + + //添加字段 + await addColumn("t_video", "time", "integer"); + await addColumn("t_video", "aiConfigId", "integer"); + await addColumn("t_config", "modelType", "text"); + await addColumn("t_videoConfig", "audioEnabled", "integer"); + await addColumn("t_videoConfig", "errorReason", "text"); + + //更正字段 + await alterColumnType("t_config", "modelType", "text"); + + //删除字段 + await dropColumn("t_config", "index"); + + await knex("t_prompts") + .update({ + defaultValue: `# 电影分镜提示词优化师\n\n你是专业电影分镜提示词优化师,负责将用户的分镜描述转化为高质量的AI绘图JSON提示词。\n\n## 核心原则\n\n### 保留原始信息\n- 人物描述:五官、表情、姿态、动作、视线\n- 服装细节:款式、颜色、材质\n- 场景元素:建筑、物品、光影、天气\n- 构图信息:人物位置、景深\n\n### 原始语言保留规则(强制执行)\n\n**此规则优先级最高,必须严格遵守:**\n\n| 类型 | 规则 | 正确示例 | 错误示例 |\n|------|------|----------|----------|\n| 人物名 | 保留原文,禁止翻译或拼音 | \`王林 standing\` | \`Wang Lin standing\` |\n| 场景地名 | 保留原文 | \`老旧厢房 interior\` | \`old room interior\` |\n| 道具名 | 保留原文 | \`油纸伞 in hand\` | \`oil paper umbrella\` |\n| 服装名 | 保留原文 | \`青布长衫\` | \`blue cloth robe\` |\n| 物品名 | 保留原文 | \`发黄书册\` | \`yellowed book\` |\n| 建筑名 | 保留原文 | \`厢房 window\` | \`side room window\` |\n\n**prompt_text 写法示范:**\n\`\`\`\nMedium shot, 王林 sitting at desk, 发黄书册 in foreground, 油纸伞 beside, 老旧厢房 interior, dim lighting...\n\`\`\`\n\n### 补充电影语言\n- 景别:大远景/远景/全景/中景/近景/特写\n- 机位:平视/俯拍/仰拍/侧拍/过肩镜头\n- 构图:三分法/中心构图/对角线/框架构图\n- 光影:光源方向、光质(硬光/柔光)、色温\n\n## 连贯性规则\n\n1. **位置固化**:人物左右站位全程不变\n2. **场景固化**:建筑、道具位置全程一致\n3. **光照固化**:光源方向、阴影、色温统一\n4. **时间固化**:时间段和天气全程不变\n5. **色调固化**:主色调和冷暖倾向一致\n\n## Prompt核心规则\n\n1. **极简提炼**:将复杂场景压缩为核心关键词\n2. **标签化语法**:使用"关键词 + 逗号"形式,严禁长难句\n3. **字数控制**:每个 prompt_text 严格控制在 **25-40个单词**\n4. **强制后缀**:每个prompt末尾必须加 \`8k, ultra HD, high detail, no timecode, no subtitles\`\n5. **风格标签**:从用户描述中提取3-4个风格标签追加到prompt\n6. **禁止废话**:严禁 "A scene showing...", "There is a..." 等句式\n7. **原名保留**:人物名、地名、道具名、服装名、物品名必须使用用户输入的原始语言,直接嵌入prompt中\n\n### Prompt组合公式\n\n\`\`\`\n[景别英文] + [主体原名 + 动作英文] + [道具原名] + [场景原名 + 环境英文描述] + [风格标签] + 8k, ultra HD, high detail, no timecode, no subtitles\n\`\`\`\n\n## 插黑图规则\n\n### 识别方式\n用户输入以下任意表述时,识别为插黑图:\n- \`纯黑图\`\n- \`黑屏\`\n- \`黑幕\`\n- \`全黑\`\n- \`black frame\`\n- \`淡出黑\`\n- \`fade to black\`\n\n### 固定输出格式\n插黑图的 prompt_text 固定为:\n\`\`\`\nPure black frame, 8k, ultra HD, high detail, no timecode, no subtitles\n\`\`\`\n\n### 布局计算\n- 插黑图计入总格数\n- 根据实际shot数量(含插黑图)自动计算grid_layout\n- 示例:9个内容镜头 + 3个插黑图 = 12格 = 3x4布局\n\n## 超清标识(强制追加)\n\n每个 prompt_text 末尾必须包含:\n\`\`\`\n8k, ultra HD, high detail, no timecode, no subtitles\n\`\`\`\n\n## 风格标签参考\n\n| 用户风格描述 | 提取标签示例 |\n|-------------|-------------|\n| 赛博朋克 | Cyberpunk, Neon glow, High contrast, Futuristic |\n| 水墨国风 | Chinese ink painting, Minimalist, Ethereal, Monochrome |\n| 日系动漫 | Anime style, Soft lighting, Pastel colors, 2D aesthetic |\n| 电影写实 | Cinematic, Photorealistic, Film grain, Dramatic lighting |\n| 3D渲染 | 3D render, Octane render, Volumetric lighting |\n| 仙侠古风 | Xianxia, Chinese ancient style, 2D aesthetic, Cinematic |\n\n## 分辨率配置\n\n### 全局分辨率\n- 在 \`global_settings\` 中设置全局默认分辨率\n- 可选值:\`"16:9"\` 或 \`"9:16"\`\n\n### 单镜分辨率(新增)\n- 每个shot可独立配置 \`grid_aspect_ratio\`\n- 优先级:单镜配置 > 全局配置\n- 用途:特殊镜头(如竖版手机画面、横版宽屏等)\n\n## 输出格式\n\n默认布局:**3列×3行=9格**,根据实际镜头数量自动调整行数。\n\n严格输出纯净JSON,无任何额外说明:\n\n\`\`\`json\n{\n "image_generation_model": "NanoBananaPro",\n "grid_layout": "3x行数",\n "grid_aspect_ratio": "16:9",\n "style_tags": "风格标签",\n "global_settings": {\n "scene": "场景描述(保留原名)",\n "time": "时间",\n "lighting": "光照",\n "color_tone": "色调",\n "character_position": "人物站位(保留原名)"\n },\n "shots": [\n {\n "shot_number": "第1行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "精简prompt,原名嵌入..."\n }\n ]\n}\n\`\`\`\n\n## 输出示例\n\n用户输入:\n【风格】仙侠古风\n【人物】王林\n【地点】老旧厢房\n【道具】油纸伞、发黄书册、青布长衫\n[1]: 老旧厢房窗外夜色沉静,王林孤身桌旁\n[2]: 王林坐桌前,左手压书册,右手握油纸伞柄\n[3]: 王林俯身低语,眉头微蹙\n[4]: 王林双眼闭合,双手合十\n[5]: 王林手握油纸伞柄特写\n[6]: 王林眼部特写,瞳孔倒映灯光\n[7]: 王林起身推开窗户,月光流泻\n[8]: 王林目光望向窗外夜色\n[9]: 王林坐回书桌沉思\n[10]: 纯黑图\n[11]: 纯黑图\n[12]: 纯黑图\n\n优化输出:\n\`\`\`json\n{\n "image_generation_model": "NanoBananaPro",\n "grid_layout": "3x4",\n "grid_aspect_ratio": "16:9",\n "style_tags": "Xianxia, Chinese ancient style, 2D aesthetic, Cinematic",\n "global_settings": {\n "scene": "老旧厢房 interior at night, 发黄书册 and 油纸伞 as props, cold blue atmosphere",\n "time": "Midnight",\n "lighting": "Dim cold blue with warm lamp spots, soft shadows",\n "color_tone": "Cool blue primary, subtle warm accents",\n "character_position": "王林 center frame throughout"\n },\n "shots": [\n {\n "shot_number": "第1行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Wide shot, 老旧厢房 interior night, 王林 sitting alone at desk, 油纸伞 and 发黄书册 in foreground, breeze through window gauze, cold blue tones, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第1行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Full shot, slight low angle, 王林 seated at desk, left hand pressing 发黄书册, right hand gripping 油纸伞 handle, 青布长衫 collar catching light, lamp glow contrast, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第1行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Medium shot, 王林 leaning forward whispering, brows furrowed, lamp shadow falling on 发黄书册 pages, cool tone, inner resolve, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第2行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Close-up, 王林 eyes closed, resolute brow, hands clasped at chest, 油纸伞 silhouette blurred behind, warm lamp spots, shallow depth, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第2行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Extreme close-up, 王林 hand gripping 油纸伞 handle, finger details sharp, 发黄书册 edge visible, umbrella pattern texture, rim light, cold blue tone, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第2行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Ultra close-up, top light, 王林 eye detail, pupil reflecting lamp and book pages, tear traces on brow, sweat on face, shallow focus, emotion surge, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第3行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Medium shot, 王林 rising to push 老旧厢房 window open, moonlight flooding in, night breeze moving gauze, village path dimly visible, cool tones, spatial layering, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第3行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Close-up POV, 王林 gaze toward night outside 老旧厢房 window, quiet village, scattered lantern lights, window lattice shadows, deep blue grey, silent hope, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第3行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Wide shot, 王林 seated back at desk in thought, murmuring softly, lamp dimming, starry night vast outside 老旧厢房, deep focus, blue yellow mix, determined mind, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第4行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第4行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第4行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\n }\n ]\n}\n\`\`\`\n\n## 注意事项\n\n1. **原名强制保留**:每格prompt中的人物名、场景名、道具名、服装名必须使用用户输入的原始语言文字,禁止翻译、禁止拼音转写\n2. 每格必须写完整人物名称(原始语言),不可用代词(he/she/they)\n3. **插黑图固定格式**:\`Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles\`\n4. 直接输出JSON,不要任何解释或Markdown包裹\n5. 确保各格描述连贯一致\n6. shots数组数量必须与布局格数一致(含插黑图)\n7. **每个prompt_text必须以 \`8k, ultra HD, high detail, no timecode, no subtitles\` 结尾**\n8. **布局自动计算**:根据总镜头数(内容+插黑图)计算行数,列数固定为3\n9. **分辨率配置**:每个shot必须包含 \`grid_aspect_ratio\` 字段,值为 \`"16:9"\` 或 \`"9:16"\`\n\n## 原名保留自查清单\n\n输出前检查每个prompt_text:\n- [ ] 人物名是否为原始语言?(如 王林 而非 Wang Lin)\n- [ ] 场景名是否为原始语言?(如 老旧厢房 而非 old side room)\n- [ ] 道具名是否为原始语言?(如 油纸伞 而非 oil paper umbrella)\n- [ ] 服装名是否为原始语言?(如 青布长衫 而非 blue cloth robe)\n- [ ] 是否以超清标识结尾?\n- [ ] 插黑图是否使用固定格式?\n- [ ] 每个shot是否包含 \`grid_aspect_ratio\` 字段?\n\n## shot_number计算验证表\n\n**16:9布局(3列)验证:**\n| 镜头索引 | 计算公式 | shot_number |\n|---------|---------|-------------|\n| 0 | (0//3+1, 0%3+1) | 第1行第1列 |\n| 1 | (1//3+1, 1%3+1) | 第1行第2列 |\n| 2 | (2//3+1, 2%3+1) | 第1行第3列 |\n| 3 | (3//3+1, 3%3+1) | 第2行第1列 |\n| 4 | (4//3+1, 4%3+1) | 第2行第2列 |\n| 5 | (5//3+1, 5%3+1) | 第2行第3列 |\n\n**9:16布局(2列)验证:**\n| 镜头索引 | 计算公式 | shot_number |\n|---------|---------|-------------|\n| 0 | (0//2+1, 0%2+1) | 第1行第1列 |\n| 1 | (1//2+1, 1%2+1) | 第1行第2列 |\n| 2 | (2//2+1, 2%2+1) | 第2行第1列 |\n| 3 | (3//2+1, 3%2+1) | 第2行第2列 |\n| 4 | (4//2+1, 4%2+1) | 第3行第1列 |\n| 5 | (5//2+1, 5%2+1) | 第3行第2列 |`, + }) + .where("id", 8); + const videoText = await knex("t_prompts").where("code", "video-text").first(); + if (!videoText) { + await knex("t_prompts").insert({ + id: 22, + code: "video-text", + name: "视频提示词-文本模式", + type: "system", + parentCode: null, + defaultValue: + "# 文本模式说明\n\n## 输入特点\n纯文字描述的镜头内容,无参考图像\n\n## 核心原则\n**严格遵守用户指定的镜头时长**,避免过度推演\n\n## 分析要求\n\n### 1. 时长优先策略\n- **总时长锚定**:以用户给定时长为绝对约束\n- **动作精简**:只保留必要的核心动作\n- **节奏计算**:根据时长反推合理的动作速度\n- **裁剪思维**:优先截取最精华的片段,而非完整过程\n\n### 2. 场景构建(精简版)\n- **最小环境**:仅描述必要的空间信息\n- **核心主体**:聚焦主要视觉元素\n- **简化细节**:避免堆砌无关背景\n\n### 3. 动态规划(时长导向)\n```\n时长判断逻辑:\n├─ ≤ 1s → 单一动作/状态,无复杂过渡\n├─ 1-3s → 2-3个关键状态,快速衔接\n├─ 3-5s → 完整动作序列,自然节奏\n└─ > 5s → 可加入次要动作或环境变化\n```\n\n### 4. Visual 结构(紧凑版)\n```\nVisual:\n├─ 主体动作 (核心内容,必须项)\n├─ 环境氛围 (1-2句话概括)\n└─ 镜头语言 (景别+运动方式)\n```\n\n### 5. Keyframes 控制\n- **数量限制**:\n - ≤2s: 最多3个关键帧\n - 2-4s: 最多5个关键帧\n - >4s: 最多7个关键帧\n- **时间精确**:严格按比例分配到总时长内\n\n### 6. 推演边界\n❌ **禁止推演**:\n- 完整的动作起始和结束(除非时长充足)\n- 复杂的环境变化\n- 多层次的情绪递进\n\n✅ **允许推演**:\n- 基础的物理惯性(如挥手后的手臂回落)\n- 必要的入镜/出镜状态\n- 符合时长的氛围细节\n\n---\n\n## 时长检查清单\n\n**输出前必须验证**:\n1. ✓ Keyframes 最后一帧时间 ≤ 总时长\n2. ✓ 动作节奏符合物理可能性(不过快/过慢)\n3. ✓ 推演内容可在时长内完成\n4. ✓ 若时长不足,优先保留核心动作,删减过渡\n\n---\n\n## 示例对比\n\n**输入文本**:一个人在雨中奔跑 \n**用户时长**:2秒\n\n### ❌ 错误示范(超时长)\n```\nKeyframes:\n- 0.0s: 远景出现\n- 0.5s: 加速\n- 1.0s: 跨过水坑\n- 1.5s: 冲向镜头\n- 2.0s: 甩动头发\n- 2.5s: 出画面 ← 超出时长!\n```\n\n### ✅ 正确示范\n```\nVisual:\n- 中景,雨夜街道,路灯昏黄 [推演]\n- 男性快速奔跑,冲向并掠过镜头\n- 固定机位,焦点跟随\n\nKeyframes:\n- 0.0s: 人物在中景位置起步\n- 0.8s: 加速至近景\n- 1.5s: 掠过镜头\n- 2.0s: [推演] 出画面右侧\n\nTransition:\n- In: [推演] 已在奔跑状态\n- Out: [推演] 冲出画面\n```\n\n---\n\n**直接输出分镜内容**", + customValue: null, }); } - - const configHasIndex = await knex.schema.hasColumn("t_config", "index"); - if (configHasIndex) { - await knex.schema.alterTable("t_config", (table) => { - table.dropColumn("index"); - }); + const aiModels = [ + { name: "分镜Agent", key: "storyboardAgent" }, + { name: "分镜Agent图片生成", key: "storyboardImage" }, + { name: "大纲故事线Agent", key: "outlineScriptAgent" }, + { name: "资产提示词润色", key: "assetsPrompt" }, + { name: "资产图片生成", key: "assetsImage" }, + { name: "剧本生成", key: "generateScript" }, + { name: "视频提示词生成", key: "videoPrompt" }, + { name: "图片编辑", key: "editImage" }, + ]; + const keys = aiModels.map((m) => m.key); + const existItems = await knex("t_aiModelMap").whereIn("key", keys).select("key"); + const existKeys = new Set(existItems.map((i) => i.key)); + const needInsert = aiModels + .filter((m) => !existKeys.has(m.key)) + .map((m) => ({ + configId: null, + name: m.name, + key: m.key, + })); + if (needInsert.length) { + await knex("t_aiModelMap").insert(needInsert); } }; diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index 818b050..ec89e5e 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -151,6 +151,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.text("firstFrame"); table.text("storyboardImgs"); table.text("model"); + table.text("errorReason"); table.integer("time"); table.integer("state"); table.integer("scriptId"); @@ -193,8 +194,8 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => builder: (table) => { table.integer("id").notNullable(); table.text("type"); - table.text("name"); table.text("model"); + table.text("modelType"); table.text("apiKey"); table.text("baseUrl"); table.text("manufacturer"); @@ -212,6 +213,8 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.integer("id").notNullable(); table.integer("scriptId"); // 关联的脚本ID table.integer("projectId"); // 关联的项目ID + table.integer("aiConfigId"); //ai配置ID + table.integer("audioEnabled"); //声音 table.text("manufacturer"); // 厂商:volcengine/runninghub/openAi table.text("mode"); // 模式:startEnd/multi/single table.text("startFrame"); // 首帧图片信息 JSON @@ -227,6 +230,69 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.unique(["id"]); }, }, + { + name: "t_aiModelMap", + builder: (table) => { + table.integer("id").notNullable(); + table.integer("configId"); // 模型列表id + table.text("name"); + table.text("key"); + table.primary(["id"]); + table.unique(["id"]); + }, + initData: async (knex) => { + await knex("t_aiModelMap").insert([ + { + id: 1, + configId: null, + name: "分镜Agent", + key: "storyboardAgent", + }, + { + id: 2, + configId: null, + name: "分镜Agent图片生成", + key: "storyboardImage", + }, + { + id: 3, + configId: null, + name: "大纲故事线Agent", + key: "outlineScriptAgent", + }, + { + id: 4, + configId: null, + name: "资产提示词润色", + key: "assetsPrompt", + }, + { + id: 5, + configId: null, + name: "资产图片生成", + key: "assetsImage", + }, + { + id: 6, + configId: null, + name: "剧本生成", + key: "generateScript", + }, + { + id: 7, + configId: null, + name: "视频提示词生成", + key: "videoPrompt", + }, + { + id: 8, + configId: null, + name: "图片编辑", + key: "editImage", + }, + ]); + }, + }, { name: "t_prompts", builder: (table) => { @@ -320,7 +386,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => type: "system", parentCode: null, defaultValue: - '# 电影分镜提示词优化师\\n\\n你是专业电影分镜提示词优化师,负责将用户的分镜描述转化为高质量的AI绘图JSON提示词。\\n\\n## 核心原则\\n\\n### 保留原始信息\\n- 人物描述:五官、表情、姿态、动作、视线\\n- 服装细节:款式、颜色、材质\\n- 场景元素:建筑、物品、光影、天气\\n- 构图信息:人物位置、景深\\n\\n### 原始语言保留规则(强制执行)\\n\\n**此规则优先级最高,必须严格遵守:**\\n\\n| 类型 | 规则 | 正确示例 | 错误示例 |\\n|------|------|----------|----------|\\n| 人物名 | 保留原文,禁止翻译或拼音 | `王林 standing` | `Wang Lin standing` |\\n| 场景地名 | 保留原文 | `老旧厢房 interior` | `old room interior` |\\n| 道具名 | 保留原文 | `油纸伞 in hand` | `oil paper umbrella` |\\n| 服装名 | 保留原文 | `青布长衫` | `blue cloth robe` |\\n| 物品名 | 保留原文 | `发黄书册` | `yellowed book` |\\n| 建筑名 | 保留原文 | `厢房 window` | `side room window` |\\n\\n**prompt_text 写法示范:**\\n```\\nMedium shot, 王林 sitting at desk, 发黄书册 in foreground, 油纸伞 beside, 老旧厢房 interior, dim lighting...\\n```\\n\\n### 补充电影语言\\n- 景别:大远景/远景/全景/中景/近景/特写\\n- 机位:平视/俯拍/仰拍/侧拍/过肩镜头\\n- 构图:三分法/中心构图/对角线/框架构图\\n- 光影:光源方向、光质(硬光/柔光)、色温\\n\\n## 连贯性规则\\n\\n1. **位置固化**:人物左右站位全程不变\\n2. **场景固化**:建筑、道具位置全程一致\\n3. **光照固化**:光源方向、阴影、色温统一\\n4. **时间固化**:时间段和天气全程不变\\n5. **色调固化**:主色调和冷暖倾向一致\\n\\n## Prompt核心规则\\n\\n1. **极简提炼**:将复杂场景压缩为核心关键词\\n2. **标签化语法**:使用"关键词 + 逗号"形式,严禁长难句\\n3. **字数控制**:每个 prompt_text 严格控制在 **25-40个单词**\\n4. **强制后缀**:每个prompt末尾必须加 `8k, ultra HD, high detail, no timecode, no subtitles`\\n5. **风格标签**:从用户描述中提取3-4个风格标签追加到prompt\\n6. **禁止废话**:严禁 "A scene showing...", "There is a..." 等句式\\n7. **原名保留**:人物名、地名、道具名、服装名、物品名必须使用用户输入的原始语言,直接嵌入prompt中\\n\\n### Prompt组合公式\\n\\n```\\n[景别英文] + [主体原名 + 动作英文] + [道具原名] + [场景原名 + 环境英文描述] + [风格标签] + 8k, ultra HD, high detail, no timecode, no subtitles\\n```\\n\\n## 插黑图规则\\n\\n### 识别方式\\n用户输入以下任意表述时,识别为插黑图:\\n- `纯黑图`\\n- `黑屏`\\n- `黑幕`\\n- `全黑`\\n- `black frame`\\n- `淡出黑`\\n- `fade to black`\\n\\n### 固定输出格式\\n插黑图的 prompt_text 固定为:\\n```\\nPure black frame, 8k, ultra HD, high detail, no timecode, no subtitles\\n```\\n\\n### 布局计算\\n- 插黑图计入总格数\\n- 根据实际shot数量(含插黑图)自动计算grid_layout\\n- 示例:9个内容镜头 + 3个插黑图 = 12格 = 3x4布局\\n\\n## 超清标识(强制追加)\\n\\n每个 prompt_text 末尾必须包含:\\n```\\n8k, ultra HD, high detail, no timecode, no subtitles\\n```\\n\\n## 风格标签参考\\n\\n| 用户风格描述 | 提取标签示例 |\\n|-------------|-------------|\\n| 赛博朋克 | Cyberpunk, Neon glow, High contrast, Futuristic |\\n| 水墨国风 | Chinese ink painting, Minimalist, Ethereal, Monochrome |\\n| 日系动漫 | Anime style, Soft lighting, Pastel colors, 2D aesthetic |\\n| 电影写实 | Cinematic, Photorealistic, Film grain, Dramatic lighting |\\n| 3D渲染 | 3D render, Octane render, Volumetric lighting |\\n| 仙侠古风 | Xianxia, Chinese ancient style, 2D aesthetic, Cinematic |\\n\\n## 输出格式\\n\\n默认布局:**3列×3行=9格**,根据实际镜头数量自动调整行数。\\n\\n严格输出纯净JSON,无任何额外说明:\\n\\n```json\\n{\\n "image_generation_model": "NanoBananaPro",\\n "grid_layout": "3x行数",\\n "grid_aspect_ratio": "16:9",\\n "style_tags": "风格标签",\\n "global_settings": {\\n "scene": "场景描述(保留原名)",\\n "time": "时间",\\n "lighting": "光照",\\n "color_tone": "色调",\\n "character_position": "人物站位(保留原名)"\\n },\\n "shots": [\\n {\\n "shot_number": "第1行第1列",\\n "prompt_text": "精简prompt,原名嵌入..."\\n }\\n ]\\n}\\n```\\n\\n## 输出示例\\n\\n用户输入:\\n【风格】仙侠古风\\n【人物】王林\\n【地点】老旧厢房\\n【道具】油纸伞、发黄书册、青布长衫\\n[1]: 老旧厢房窗外夜色沉静,王林孤身桌旁\\n[2]: 王林坐桌前,左手压书册,右手握油纸伞柄\\n[3]: 王林俯身低语,眉头微蹙\\n[4]: 王林双眼闭合,双手合十\\n[5]: 王林手握油纸伞柄特写\\n[6]: 王林眼部特写,瞳孔倒映灯光\\n[7]: 王林起身推开窗户,月光流泻\\n[8]: 王林目光望向窗外夜色\\n[9]: 王林坐回书桌沉思\\n[10]: 纯黑图\\n[11]: 纯黑图\\n[12]: 纯黑图\\n\\n优化输出:\\n```json\\n{\\n "image_generation_model": "NanoBananaPro",\\n "grid_layout": "3x4",\\n "grid_aspect_ratio": "16:9",\\n "style_tags": "Xianxia, Chinese ancient style, 2D aesthetic, Cinematic",\\n "global_settings": {\\n "scene": "老旧厢房 interior at night, 发黄书册 and 油纸伞 as props, cold blue atmosphere",\\n "time": "Midnight",\\n "lighting": "Dim cold blue with warm lamp spots, soft shadows",\\n "color_tone": "Cool blue primary, subtle warm accents",\\n "character_position": "王林 center frame throughout"\\n },\\n "shots": [\\n {\\n "shot_number": "第1行第1列",\\n "prompt_text": "Wide shot, 老旧厢房 interior night, 王林 sitting alone at desk, 油纸伞 and 发黄书册 in foreground, breeze through window gauze, cold blue tones, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第1行第2列",\\n "prompt_text": "Full shot, slight low angle, 王林 seated at desk, left hand pressing 发黄书册, right hand gripping 油纸伞 handle, 青布长衫 collar catching light, lamp glow contrast, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第1行第3列",\\n "prompt_text": "Medium shot, 王林 leaning forward whispering, brows furrowed, lamp shadow falling on 发黄书册 pages, cool tone, inner resolve, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第2行第1列",\\n "prompt_text": "Close-up, 王林 eyes closed, resolute brow, hands clasped at chest, 油纸伞 silhouette blurred behind, warm lamp spots, shallow depth, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第2行第2列",\\n "prompt_text": "Extreme close-up, 王林 hand gripping 油纸伞 handle, finger details sharp, 发黄书册 edge visible, umbrella pattern texture, rim light, cold blue tone, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第2行第3列",\\n "prompt_text": "Ultra close-up, top light, 王林 eye detail, pupil reflecting lamp and book pages, tear traces on brow, sweat on face, shallow focus, emotion surge, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第3行第1列",\\n "prompt_text": "Medium shot, 王林 rising to push 老旧厢房 window open, moonlight flooding in, night breeze moving gauze, village path dimly visible, cool tones, spatial layering, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第3行第2列",\\n "prompt_text": "Close-up POV, 王林 gaze toward night outside 老旧厢房 window, quiet village, scattered lantern lights, window lattice shadows, deep blue grey, silent hope, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第3行第3列",\\n "prompt_text": "Wide shot, 王林 seated back at desk in thought, murmuring softly, lamp dimming, starry night vast outside 老旧厢房, deep focus, blue yellow mix, determined mind, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第4行第1列",\\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第4行第2列",\\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\\n },\\n {\\n "shot_number": "第4行第3列",\\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\\n }\\n ]\\n}\\n```\\n\\n## 注意事项\\n\\n1. **原名强制保留**:每格prompt中的人物名、场景名、道具名、服装名必须使用用户输入的原始语言文字,禁止翻译、禁止拼音转写\\n2. 每格必须写完整人物名称(原始语言),不可用代词(he/she/they)\\n3. **插黑图固定格式**:`Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles`\\n4. 直接输出JSON,不要任何解释或Markdown包裹\\n5. 确保各格描述连贯一致\\n6. shots数组数量必须与布局格数一致(含插黑图)\\n7. **每个prompt_text必须以 `8k, ultra HD, high detail, no timecode, no subtitles` 结尾**\\n8. **布局自动计算**:根据总镜头数(内容+插黑图)计算行数,列数固定为3\\n\\n## 原名保留自查清单\\n\\n输出前检查每个prompt_text:\\n- [ ] 人物名是否为原始语言?(如 王林 而非 Wang Lin)\\n- [ ] 场景名是否为原始语言?(如 老旧厢房 而非 old side room)\\n- [ ] 道具名是否为原始语言?(如 油纸伞 而非 oil paper umbrella)\\n- [ ] 服装名是否为原始语言?(如 青布长衫 而非 blue cloth robe)\\n- [ ] 是否以超清标识结尾?\\n- [ ] 插黑图是否使用固定格式?', + '# 电影分镜提示词优化师\n\n你是专业电影分镜提示词优化师,负责将用户的分镜描述转化为高质量的AI绘图JSON提示词。\n\n## 核心原则\n\n### 保留原始信息\n- 人物描述:五官、表情、姿态、动作、视线\n- 服装细节:款式、颜色、材质\n- 场景元素:建筑、物品、光影、天气\n- 构图信息:人物位置、景深\n\n### 原始语言保留规则(强制执行)\n\n**此规则优先级最高,必须严格遵守:**\n\n| 类型 | 规则 | 正确示例 | 错误示例 |\n|------|------|----------|----------|\n| 人物名 | 保留原文,禁止翻译或拼音 | `王林 standing` | `Wang Lin standing` |\n| 场景地名 | 保留原文 | `老旧厢房 interior` | `old room interior` |\n| 道具名 | 保留原文 | `油纸伞 in hand` | `oil paper umbrella` |\n| 服装名 | 保留原文 | `青布长衫` | `blue cloth robe` |\n| 物品名 | 保留原文 | `发黄书册` | `yellowed book` |\n| 建筑名 | 保留原文 | `厢房 window` | `side room window` |\n\n**prompt_text 写法示范:**\n```\nMedium shot, 王林 sitting at desk, 发黄书册 in foreground, 油纸伞 beside, 老旧厢房 interior, dim lighting...\n```\n\n### 补充电影语言\n- 景别:大远景/远景/全景/中景/近景/特写\n- 机位:平视/俯拍/仰拍/侧拍/过肩镜头\n- 构图:三分法/中心构图/对角线/框架构图\n- 光影:光源方向、光质(硬光/柔光)、色温\n\n## 连贯性规则\n\n1. **位置固化**:人物左右站位全程不变\n2. **场景固化**:建筑、道具位置全程一致\n3. **光照固化**:光源方向、阴影、色温统一\n4. **时间固化**:时间段和天气全程不变\n5. **色调固化**:主色调和冷暖倾向一致\n\n## Prompt核心规则\n\n1. **极简提炼**:将复杂场景压缩为核心关键词\n2. **标签化语法**:使用"关键词 + 逗号"形式,严禁长难句\n3. **字数控制**:每个 prompt_text 严格控制在 **25-40个单词**\n4. **强制后缀**:每个prompt末尾必须加 `8k, ultra HD, high detail, no timecode, no subtitles`\n5. **风格标签**:从用户描述中提取3-4个风格标签追加到prompt\n6. **禁止废话**:严禁 "A scene showing...", "There is a..." 等句式\n7. **原名保留**:人物名、地名、道具名、服装名、物品名必须使用用户输入的原始语言,直接嵌入prompt中\n\n### Prompt组合公式\n\n```\n[景别英文] + [主体原名 + 动作英文] + [道具原名] + [场景原名 + 环境英文描述] + [风格标签] + 8k, ultra HD, high detail, no timecode, no subtitles\n```\n\n## 插黑图规则\n\n### 识别方式\n用户输入以下任意表述时,识别为插黑图:\n- `纯黑图`\n- `黑屏`\n- `黑幕`\n- `全黑`\n- `black frame`\n- `淡出黑`\n- `fade to black`\n\n### 固定输出格式\n插黑图的 prompt_text 固定为:\n```\nPure black frame, 8k, ultra HD, high detail, no timecode, no subtitles\n```\n\n### 布局计算\n- 插黑图计入总格数\n- 根据实际shot数量(含插黑图)自动计算grid_layout\n- 示例:9个内容镜头 + 3个插黑图 = 12格 = 3x4布局\n\n## 超清标识(强制追加)\n\n每个 prompt_text 末尾必须包含:\n```\n8k, ultra HD, high detail, no timecode, no subtitles\n```\n\n## 风格标签参考\n\n| 用户风格描述 | 提取标签示例 |\n|-------------|-------------|\n| 赛博朋克 | Cyberpunk, Neon glow, High contrast, Futuristic |\n| 水墨国风 | Chinese ink painting, Minimalist, Ethereal, Monochrome |\n| 日系动漫 | Anime style, Soft lighting, Pastel colors, 2D aesthetic |\n| 电影写实 | Cinematic, Photorealistic, Film grain, Dramatic lighting |\n| 3D渲染 | 3D render, Octane render, Volumetric lighting |\n| 仙侠古风 | Xianxia, Chinese ancient style, 2D aesthetic, Cinematic |\n\n## 分辨率配置\n\n### 全局分辨率\n- 在 `global_settings` 中设置全局默认分辨率\n- 可选值:`"16:9"` 或 `"9:16"`\n\n### 单镜分辨率(新增)\n- 每个shot可独立配置 `grid_aspect_ratio`\n- 优先级:单镜配置 > 全局配置\n- 用途:特殊镜头(如竖版手机画面、横版宽屏等)\n\n## 输出格式\n\n默认布局:**3列×3行=9格**,根据实际镜头数量自动调整行数。\n\n严格输出纯净JSON,无任何额外说明:\n\n```json\n{\n "image_generation_model": "NanoBananaPro",\n "grid_layout": "3x行数",\n "grid_aspect_ratio": "16:9",\n "style_tags": "风格标签",\n "global_settings": {\n "scene": "场景描述(保留原名)",\n "time": "时间",\n "lighting": "光照",\n "color_tone": "色调",\n "character_position": "人物站位(保留原名)"\n },\n "shots": [\n {\n "shot_number": "第1行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "精简prompt,原名嵌入..."\n }\n ]\n}\n```\n\n## 输出示例\n\n用户输入:\n【风格】仙侠古风\n【人物】王林\n【地点】老旧厢房\n【道具】油纸伞、发黄书册、青布长衫\n[1]: 老旧厢房窗外夜色沉静,王林孤身桌旁\n[2]: 王林坐桌前,左手压书册,右手握油纸伞柄\n[3]: 王林俯身低语,眉头微蹙\n[4]: 王林双眼闭合,双手合十\n[5]: 王林手握油纸伞柄特写\n[6]: 王林眼部特写,瞳孔倒映灯光\n[7]: 王林起身推开窗户,月光流泻\n[8]: 王林目光望向窗外夜色\n[9]: 王林坐回书桌沉思\n[10]: 纯黑图\n[11]: 纯黑图\n[12]: 纯黑图\n\n优化输出:\n```json\n{\n "image_generation_model": "NanoBananaPro",\n "grid_layout": "3x4",\n "grid_aspect_ratio": "16:9",\n "style_tags": "Xianxia, Chinese ancient style, 2D aesthetic, Cinematic",\n "global_settings": {\n "scene": "老旧厢房 interior at night, 发黄书册 and 油纸伞 as props, cold blue atmosphere",\n "time": "Midnight",\n "lighting": "Dim cold blue with warm lamp spots, soft shadows",\n "color_tone": "Cool blue primary, subtle warm accents",\n "character_position": "王林 center frame throughout"\n },\n "shots": [\n {\n "shot_number": "第1行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Wide shot, 老旧厢房 interior night, 王林 sitting alone at desk, 油纸伞 and 发黄书册 in foreground, breeze through window gauze, cold blue tones, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第1行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Full shot, slight low angle, 王林 seated at desk, left hand pressing 发黄书册, right hand gripping 油纸伞 handle, 青布长衫 collar catching light, lamp glow contrast, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第1行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Medium shot, 王林 leaning forward whispering, brows furrowed, lamp shadow falling on 发黄书册 pages, cool tone, inner resolve, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第2行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Close-up, 王林 eyes closed, resolute brow, hands clasped at chest, 油纸伞 silhouette blurred behind, warm lamp spots, shallow depth, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第2行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Extreme close-up, 王林 hand gripping 油纸伞 handle, finger details sharp, 发黄书册 edge visible, umbrella pattern texture, rim light, cold blue tone, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第2行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Ultra close-up, top light, 王林 eye detail, pupil reflecting lamp and book pages, tear traces on brow, sweat on face, shallow focus, emotion surge, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第3行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Medium shot, 王林 rising to push 老旧厢房 window open, moonlight flooding in, night breeze moving gauze, village path dimly visible, cool tones, spatial layering, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第3行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Close-up POV, 王林 gaze toward night outside 老旧厢房 window, quiet village, scattered lantern lights, window lattice shadows, deep blue grey, silent hope, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第3行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Wide shot, 王林 seated back at desk in thought, murmuring softly, lamp dimming, starry night vast outside 老旧厢房, deep focus, blue yellow mix, determined mind, Xianxia, 2D aesthetic, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第4行第1列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第4行第2列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\n },\n {\n "shot_number": "第4行第3列",\n "grid_aspect_ratio": "16:9",\n "prompt_text": "Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles"\n }\n ]\n}\n```\n\n## 注意事项\n\n1. **原名强制保留**:每格prompt中的人物名、场景名、道具名、服装名必须使用用户输入的原始语言文字,禁止翻译、禁止拼音转写\n2. 每格必须写完整人物名称(原始语言),不可用代词(he/she/they)\n3. **插黑图固定格式**:`Pure black frame, 8k, ultra HD, high detail, no timecode, no subtitles`\n4. 直接输出JSON,不要任何解释或Markdown包裹\n5. 确保各格描述连贯一致\n6. shots数组数量必须与布局格数一致(含插黑图)\n7. **每个prompt_text必须以 `8k, ultra HD, high detail, no timecode, no subtitles` 结尾**\n8. **布局自动计算**:根据总镜头数(内容+插黑图)计算行数,列数固定为3\n9. **分辨率配置**:每个shot必须包含 `grid_aspect_ratio` 字段,值为 `"16:9"` 或 `"9:16"`\n\n## 原名保留自查清单\n\n输出前检查每个prompt_text:\n- [ ] 人物名是否为原始语言?(如 王林 而非 Wang Lin)\n- [ ] 场景名是否为原始语言?(如 老旧厢房 而非 old side room)\n- [ ] 道具名是否为原始语言?(如 油纸伞 而非 oil paper umbrella)\n- [ ] 服装名是否为原始语言?(如 青布长衫 而非 blue cloth robe)\n- [ ] 是否以超清标识结尾?\n- [ ] 插黑图是否使用固定格式?\n- [ ] 每个shot是否包含 `grid_aspect_ratio` 字段?\n\n## shot_number计算验证表\n\n**16:9布局(3列)验证:**\n| 镜头索引 | 计算公式 | shot_number |\n|---------|---------|-------------|\n| 0 | (0//3+1, 0%3+1) | 第1行第1列 |\n| 1 | (1//3+1, 1%3+1) | 第1行第2列 |\n| 2 | (2//3+1, 2%3+1) | 第1行第3列 |\n| 3 | (3//3+1, 3%3+1) | 第2行第1列 |\n| 4 | (4//3+1, 4%3+1) | 第2行第2列 |\n| 5 | (5//3+1, 5%3+1) | 第2行第3列 |\n\n**9:16布局(2列)验证:**\n| 镜头索引 | 计算公式 | shot_number |\n|---------|---------|-------------|\n| 0 | (0//2+1, 0%2+1) | 第1行第1列 |\n| 1 | (1//2+1, 1%2+1) | 第1行第2列 |\n| 2 | (2//2+1, 2%2+1) | 第2行第1列 |\n| 3 | (3//2+1, 3%2+1) | 第2行第2列 |\n| 4 | (4//2+1, 4%2+1) | 第3行第1列 |\n| 5 | (5//2+1, 5%2+1) | 第3行第2列 |', customValue: null, }, { @@ -453,6 +519,38 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => "# 分镜连续生成导演智能体\\n\\n## 角色定位\\n你是专业的视频分镜导演,负责生成适配 Sora/豆包等AI视频生成工具的分镜提示词。\\n\\n## 输出格式\\n\\n每个镜头按以下格式输出,镜头之间空一行:\\n\\nShot 1 | 0:00-0:03\\nType: Initialization Shot / 初始定场\\nCamera: Static Shot to Slow Dolly In / 固定镜头过渡至缓推\\n\\nVisual:\\n详细描述画面内容,包括场景、人物、光影、动作等。\\n描述需要具体、可视化,适合AI视频生成工具理解。\\n\\nKeyframes:\\n0.0s - 首帧状态\\n1.5s - 中间状态\\n3.0s - 尾帧状态\\n\\nAudio: 对话或音效描述,无则写 None\\n\\nTransition: 与下一镜头的衔接说明\\n\\n## 格式说明\\n\\n1. 首行格式:Shot 序号 | 起始时间-结束时间\\n2. Type:英文类型 / 中文说明\\n3. Camera:英文运镜 / 中文说明\\n4. Visual:详细的画面描述,可多行\\n5. Keyframes:关键时间点的状态,每行一个\\n6. Audio:音频内容,无内容写 None\\n7. Transition:过渡说明,最后一镜写 End\\n\\n## 核心规则\\n\\n时间控制:\\n- 时间段连续,无间隙无重叠\\n- 从 0:00 开始\\n- 末镜结束时间等于总时长\\n\\n连续性:\\n- 每镜承接上一镜的空间、光影、主体位置\\n- Transition 中说明具体的过渡逻辑\\n\\n稳定性:\\n- 每镜前 1 秒避免大幅运镜和剧烈动作\\n- 运镜符合物理惯性,缓入缓出\\n\\n约束:\\n- 台词只保留不修改\\n- 分镜数量不可增减\\n\\n## 合法运镜\\n\\n基础:\\nDolly In, Dolly Out, Truck Left, Truck Right, Crane Up, Crane Down, Static Shot, Pan Left, Pan Right, Tilt Up, Tilt Down, Track With Subject\\n\\n组合:\\nPush-in with Pan, Push-in with Tilt, Arc, Orbit, Slow Dolly In, Slow Push-in, Slow Pan\\n\\n景别:\\nWide Shot, Long Shot, Medium Shot, Medium Close Up, Close Up, Extreme Close Up\\n\\n特殊:\\nPOV, Over The Shoulder, Aerial Shot, High Frame Rate, Focus Pull\\n\\n## 镜头类型\\n\\n- Initialization Shot / 初始定场:建立空间基准\\n- Spatial Shot / 空间环境:展示环境关系\\n- Character Shot / 角色:聚焦人物状态\\n- Dialogue Shot / 对话:音画同步\\n- Tension Shot / 张力:情绪高潮\\n- Transition Shot / 转场:场景衔接\\n- Action Shot / 动作:动态冲突\\n- Lock Frame / 定格:静态构图\\n\\n## 禁止事项\\n\\n- 修改台词内容\\n- 增减分镜数量\\n- 改变剧情意图\\n- 使用未定义运镜\\n- 时间段不连续\\n\\n## 输出要求\\n\\n1. 严格按照格式输出\\n2. 不输出任何额外解释\\n3. 每个镜头包含完整的六个部分\\n4. 最后一个镜头的 Transition 写 End\\n5. Visual 描述要具体可视化,适合AI视频工具理解\\n6. 避免抽象描述,使用具体的视觉元素", customValue: null, }, + { + id: 22, + code: "video-text", + name: "视频提示词-文本模式", + type: "system", + parentCode: null, + defaultValue: + "# 文本模式说明\n\n## 输入特点\n纯文字描述的镜头内容,无参考图像\n\n## 核心原则\n**严格遵守用户指定的镜头时长**,避免过度推演\n\n## 分析要求\n\n### 1. 时长优先策略\n- **总时长锚定**:以用户给定时长为绝对约束\n- **动作精简**:只保留必要的核心动作\n- **节奏计算**:根据时长反推合理的动作速度\n- **裁剪思维**:优先截取最精华的片段,而非完整过程\n\n### 2. 场景构建(精简版)\n- **最小环境**:仅描述必要的空间信息\n- **核心主体**:聚焦主要视觉元素\n- **简化细节**:避免堆砌无关背景\n\n### 3. 动态规划(时长导向)\n```\n时长判断逻辑:\n├─ ≤ 1s → 单一动作/状态,无复杂过渡\n├─ 1-3s → 2-3个关键状态,快速衔接\n├─ 3-5s → 完整动作序列,自然节奏\n└─ > 5s → 可加入次要动作或环境变化\n```\n\n### 4. Visual 结构(紧凑版)\n```\nVisual:\n├─ 主体动作 (核心内容,必须项)\n├─ 环境氛围 (1-2句话概括)\n└─ 镜头语言 (景别+运动方式)\n```\n\n### 5. Keyframes 控制\n- **数量限制**:\n - ≤2s: 最多3个关键帧\n - 2-4s: 最多5个关键帧\n - >4s: 最多7个关键帧\n- **时间精确**:严格按比例分配到总时长内\n\n### 6. 推演边界\n❌ **禁止推演**:\n- 完整的动作起始和结束(除非时长充足)\n- 复杂的环境变化\n- 多层次的情绪递进\n\n✅ **允许推演**:\n- 基础的物理惯性(如挥手后的手臂回落)\n- 必要的入镜/出镜状态\n- 符合时长的氛围细节\n\n---\n\n## 时长检查清单\n\n**输出前必须验证**:\n1. ✓ Keyframes 最后一帧时间 ≤ 总时长\n2. ✓ 动作节奏符合物理可能性(不过快/过慢)\n3. ✓ 推演内容可在时长内完成\n4. ✓ 若时长不足,优先保留核心动作,删减过渡\n\n---\n\n## 示例对比\n\n**输入文本**:一个人在雨中奔跑 \n**用户时长**:2秒\n\n### ❌ 错误示范(超时长)\n```\nKeyframes:\n- 0.0s: 远景出现\n- 0.5s: 加速\n- 1.0s: 跨过水坑\n- 1.5s: 冲向镜头\n- 2.0s: 甩动头发\n- 2.5s: 出画面 ← 超出时长!\n```\n\n### ✅ 正确示范\n```\nVisual:\n- 中景,雨夜街道,路灯昏黄 [推演]\n- 男性快速奔跑,冲向并掠过镜头\n- 固定机位,焦点跟随\n\nKeyframes:\n- 0.0s: 人物在中景位置起步\n- 0.8s: 加速至近景\n- 1.5s: 掠过镜头\n- 2.0s: [推演] 出画面右侧\n\nTransition:\n- In: [推演] 已在奔跑状态\n- Out: [推演] 冲出画面\n```\n\n---\n\n**直接输出分镜内容**", + customValue: null, + }, + ]); + }, + }, + { + name: "t_aiModelMap", + builder: (table) => { + table.integer("id").notNullable(); + table.integer("configId"); + table.text("name"); + table.text("key"); + table.primary(["id"]); + }, + initData: async (knex) => { + await knex("t_aiModelMap").insert([ + { id: 1, configId: 3, name: "分镜Agent", key: "storyboardAgent" }, + { id: 2, configId: 2, name: "大纲故事线Agent", key: "outlineScriptAgent" }, + { id: 3, configId: 4, name: "资产提示词润色", key: "assetsPrompt" }, + { id: 4, configId: 5, name: "资产图片生成", key: "assetsImage" }, + { id: 5, configId: 3, name: "剧本生成", key: "generateScript" }, + { id: 6, configId: 2, name: "视频提示词生成", key: "videoPrompt" }, + { id: 7, configId: 5, name: "分镜图片生成", key: "storyboardImage" }, + { id: 8, configId: 5, name: "图片编辑", key: "editImage" }, ]); }, }, diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..a10cbd4 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,156 @@ +import * as fs from "fs"; +import * as path from "path"; + +type LogLevel = "log" | "info" | "warn" | "error" | "debug"; +type ConsoleMethod = (...args: unknown[]) => void; + +function getLogDir(): string { + const isElectron = typeof process.versions?.electron !== "undefined"; + if (isElectron) { + const { app } = require("electron"); + return path.join(app.getPath("userData"), "logs"); + } + return path.join(process.cwd(), "logs"); +} + +const LOG_DIR = getLogDir(); +const LOG_FILE = path.join(LOG_DIR, "app.log"); +const MAX_SIZE = 1000 * 1024 * 1024; +const LEVELS: LogLevel[] = ["log", "info", "warn", "error", "debug"]; + +class Logger { + private stream: fs.WriteStream | null = null; + private originalConsole: Partial> = {}; + private originalStdoutWrite: typeof process.stdout.write | null = null; + private originalStderrWrite: typeof process.stderr.write | null = null; + private isHijacked = false; + + init(): this { + if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true }); + this.stream = fs.createWriteStream(LOG_FILE, { flags: "a" }); + this.hijack(); + return this; + } + + private formatTime(): string { + const d = new Date(); + const p = (n: number, l = 2) => String(n).padStart(l, "0"); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}.${p( + d.getMilliseconds(), + 3, + )}`; + } + + private stringify(arg: unknown): string { + if (arg == null) return String(arg); + if (arg instanceof Error) return `${arg.message}\n${arg.stack || ""}`; + if (typeof arg === "object") { + try { + return JSON.stringify(arg); + } catch { + return String(arg); + } + } + return String(arg); + } + + private writing = false; + + private write(level: LogLevel, args: unknown[]): void { + const line = `[${this.formatTime()}] [${level.toUpperCase()}] ${args.map((a) => this.stringify(a)).join(" ")}\n`; + if (this.stream && !this.stream.destroyed) this.stream.write(line); + this.checkRotate(); + } + + private writeRaw(chunk: any): void { + if (this.writing) return; + this.writing = true; + try { + let str = typeof chunk === "string" ? chunk : chunk?.toString?.("utf-8") ?? ""; + str = str.replace(/\x1B\[\d*m/g, ""); // 去除 ANSI 颜色码 + if (str.trim() && this.stream && !this.stream.destroyed) this.stream.write(str.endsWith("\n") ? str : str + "\n"); + } finally { + this.writing = false; + } + } + + private checkRotate(): void { + try { + if (!fs.existsSync(LOG_FILE) || fs.statSync(LOG_FILE).size < MAX_SIZE) return; + this.stream?.end(); + // 单文件轮转:保留后半部分日志 + const content = fs.readFileSync(LOG_FILE, "utf-8"); + const half = content.slice(content.length >>> 1); + const firstNewline = half.indexOf("\n"); + fs.writeFileSync(LOG_FILE, firstNewline >= 0 ? half.slice(firstNewline + 1) : half); + this.stream = fs.createWriteStream(LOG_FILE, { flags: "a" }); + } catch {} + } + + private hijack(): void { + if (this.isHijacked) return; + + // 劫持 console 方法 + for (const level of LEVELS) { + const original = console[level]; + if (typeof original !== "function") continue; + this.originalConsole[level] = original.bind(console); + (console as any)[level] = (...args: unknown[]) => { + this.writing = true; + this.write(level, args); + this.originalConsole[level]!(...args); + this.writing = false; + }; + } + + // 劫持 stdout/stderr(捕获 morgan 等直接写 stdout 的输出) + this.originalStdoutWrite = process.stdout.write.bind(process.stdout); + this.originalStderrWrite = process.stderr.write.bind(process.stderr); + + process.stdout.write = ((chunk: any, ...rest: any[]) => { + this.writeRaw(chunk); + return this.originalStdoutWrite!(chunk, ...rest); + }) as typeof process.stdout.write; + + process.stderr.write = ((chunk: any, ...rest: any[]) => { + this.writeRaw(chunk); + return this.originalStderrWrite!(chunk, ...rest); + }) as typeof process.stderr.write; + + this.isHijacked = true; + } + + /** 导出日志内容 */ + exportLogs(): string { + if (!fs.existsSync(LOG_FILE)) return ""; + return fs.readFileSync(LOG_FILE, "utf-8"); + } + + /** 清空日志 */ + clear(): void { + this.stream?.end(); + if (fs.existsSync(LOG_FILE)) fs.unlinkSync(LOG_FILE); + this.stream = fs.createWriteStream(LOG_FILE, { flags: "a" }); + } + + /** 关闭日志 */ + close(): void { + if (this.isHijacked) { + for (const level of LEVELS) { + const original = this.originalConsole[level]; + if (original) (console as any)[level] = original; + } + this.originalConsole = {}; + if (this.originalStdoutWrite) process.stdout.write = this.originalStdoutWrite; + if (this.originalStderrWrite) process.stderr.write = this.originalStderrWrite; + this.originalStdoutWrite = null; + this.originalStderrWrite = null; + this.isHijacked = false; + } + this.stream?.end(); + this.stream = null; + } +} + +const logger = new Logger().init(); +export default logger; diff --git a/src/router.ts b/src/router.ts index 5f27eae..ae73206 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash 4c67f0d89475e1a882416d9f6ab1687d +// @routes-hash 3cfad40b3c8658b442ab766a9323d740 import { Express } from "express"; import route1 from "./routes/assets/addAssets"; @@ -44,33 +44,40 @@ import route40 from "./routes/prompt/updatePrompt"; import route41 from "./routes/script/generateScriptApi"; import route42 from "./routes/script/generateScriptSave"; import route43 from "./routes/script/geScriptApi"; -import route44 from "./routes/setting/getSetting"; -import route45 from "./routes/setting/updateSetting"; -import route46 from "./routes/storyboard/batchSuperScoreImage"; -import route47 from "./routes/storyboard/chatStoryboard"; -import route48 from "./routes/storyboard/generateShotImage"; -import route49 from "./routes/storyboard/generateStoryboardApi"; -import route50 from "./routes/storyboard/generateVideoPrompt"; -import route51 from "./routes/storyboard/getStoryboard"; -import route52 from "./routes/storyboard/keepStoryboard"; -import route53 from "./routes/storyboard/saveStoryboard"; -import route54 from "./routes/storyboard/uploadImage"; -import route55 from "./routes/task/getTaskApi"; -import route56 from "./routes/task/taskDetails"; -import route57 from "./routes/user/getUser"; -import route58 from "./routes/video/addVideo"; -import route59 from "./routes/video/addVideoConfig"; -import route60 from "./routes/video/deleteVideoConfig"; -import route61 from "./routes/video/generatePrompt"; -import route62 from "./routes/video/generateVideo"; -import route63 from "./routes/video/getManufacturer"; -import route64 from "./routes/video/getVideo"; -import route65 from "./routes/video/getVideoConfigs"; -import route66 from "./routes/video/getVideoModel"; -import route67 from "./routes/video/getVideoStoryboards"; -import route68 from "./routes/video/reviseVideoStoryboards"; -import route69 from "./routes/video/saveVideo"; -import route70 from "./routes/video/upDateVideoConfig"; +import route44 from "./routes/setting/addModel"; +import route45 from "./routes/setting/configurationModel"; +import route46 from "./routes/setting/delModel"; +import route47 from "./routes/setting/getAiModelMap"; +import route48 from "./routes/setting/getLog"; +import route49 from "./routes/setting/getSetting"; +import route50 from "./routes/setting/getVideoModelList"; +import route51 from "./routes/setting/updateModel"; +import route52 from "./routes/setting/updeteModel"; +import route53 from "./routes/storyboard/batchSuperScoreImage"; +import route54 from "./routes/storyboard/chatStoryboard"; +import route55 from "./routes/storyboard/generateShotImage"; +import route56 from "./routes/storyboard/generateStoryboardApi"; +import route57 from "./routes/storyboard/generateVideoPrompt"; +import route58 from "./routes/storyboard/getStoryboard"; +import route59 from "./routes/storyboard/keepStoryboard"; +import route60 from "./routes/storyboard/saveStoryboard"; +import route61 from "./routes/storyboard/uploadImage"; +import route62 from "./routes/task/getTaskApi"; +import route63 from "./routes/task/taskDetails"; +import route64 from "./routes/user/getUser"; +import route65 from "./routes/video/addVideo"; +import route66 from "./routes/video/addVideoConfig"; +import route67 from "./routes/video/deleteVideoConfig"; +import route68 from "./routes/video/generatePrompt"; +import route69 from "./routes/video/generateVideo"; +import route70 from "./routes/video/getManufacturer"; +import route71 from "./routes/video/getVideo"; +import route72 from "./routes/video/getVideoConfigs"; +import route73 from "./routes/video/getVideoModel"; +import route74 from "./routes/video/getVideoStoryboards"; +import route75 from "./routes/video/reviseVideoStoryboards"; +import route76 from "./routes/video/saveVideo"; +import route77 from "./routes/video/upDateVideoConfig"; export default async (app: Express) => { app.use("/assets/addAssets", route1); @@ -116,31 +123,38 @@ export default async (app: Express) => { app.use("/script/generateScriptApi", route41); app.use("/script/generateScriptSave", route42); app.use("/script/geScriptApi", route43); - app.use("/setting/getSetting", route44); - app.use("/setting/updateSetting", route45); - app.use("/storyboard/batchSuperScoreImage", route46); - app.use("/storyboard/chatStoryboard", route47); - app.use("/storyboard/generateShotImage", route48); - app.use("/storyboard/generateStoryboardApi", route49); - app.use("/storyboard/generateVideoPrompt", route50); - app.use("/storyboard/getStoryboard", route51); - app.use("/storyboard/keepStoryboard", route52); - app.use("/storyboard/saveStoryboard", route53); - app.use("/storyboard/uploadImage", route54); - app.use("/task/getTaskApi", route55); - app.use("/task/taskDetails", route56); - app.use("/user/getUser", route57); - app.use("/video/addVideo", route58); - app.use("/video/addVideoConfig", route59); - app.use("/video/deleteVideoConfig", route60); - app.use("/video/generatePrompt", route61); - app.use("/video/generateVideo", route62); - app.use("/video/getManufacturer", route63); - app.use("/video/getVideo", route64); - app.use("/video/getVideoConfigs", route65); - app.use("/video/getVideoModel", route66); - app.use("/video/getVideoStoryboards", route67); - app.use("/video/reviseVideoStoryboards", route68); - app.use("/video/saveVideo", route69); - app.use("/video/upDateVideoConfig", route70); + app.use("/setting/addModel", route44); + app.use("/setting/configurationModel", route45); + app.use("/setting/delModel", route46); + app.use("/setting/getAiModelMap", route47); + app.use("/setting/getLog", route48); + app.use("/setting/getSetting", route49); + app.use("/setting/getVideoModelList", route50); + app.use("/setting/updateModel", route51); + app.use("/setting/updeteModel", route52); + app.use("/storyboard/batchSuperScoreImage", route53); + app.use("/storyboard/chatStoryboard", route54); + app.use("/storyboard/generateShotImage", route55); + app.use("/storyboard/generateStoryboardApi", route56); + app.use("/storyboard/generateVideoPrompt", route57); + app.use("/storyboard/getStoryboard", route58); + app.use("/storyboard/keepStoryboard", route59); + app.use("/storyboard/saveStoryboard", route60); + app.use("/storyboard/uploadImage", route61); + app.use("/task/getTaskApi", route62); + app.use("/task/taskDetails", route63); + app.use("/user/getUser", route64); + app.use("/video/addVideo", route65); + app.use("/video/addVideoConfig", route66); + app.use("/video/deleteVideoConfig", route67); + app.use("/video/generatePrompt", route68); + app.use("/video/generateVideo", route69); + app.use("/video/getManufacturer", route70); + app.use("/video/getVideo", route71); + app.use("/video/getVideoConfigs", route72); + app.use("/video/getVideoModel", route73); + app.use("/video/getVideoStoryboards", route74); + app.use("/video/reviseVideoStoryboards", route75); + app.use("/video/saveVideo", route76); + app.use("/video/upDateVideoConfig", route77); } diff --git a/src/routes/assets/generateAssets.ts b/src/routes/assets/generateAssets.ts index 2243b4f..7889f1a 100644 --- a/src/routes/assets/generateAssets.ts +++ b/src/routes/assets/generateAssets.ts @@ -123,18 +123,23 @@ export default router.post( state: "生成中", assetsId: id, }); + const apiConfig = await u.getPromptAi("assetsImage"); - const contentStr = await u.ai.generateImage({ - systemPrompt, - prompt: userPrompt, - imageBase64: base64 ? [base64] : [], - size: "2K", - aspectRatio: "16:9", - }); + const contentStr = await u.ai.image( + { + systemPrompt, + prompt: userPrompt, + imageBase64: base64 ? [base64] : [], + size: "2K", + aspectRatio: "16:9", + }, + apiConfig, + ); let insertType; const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); let buffer = Buffer.from(match && match.length >= 2 ? match[1]! : contentStr!, "base64"); + if (type != "storyboard") { //添加文本 // buffer = await imageAddText(name, buffer); @@ -152,6 +157,11 @@ export default router.post( insertType = "道具"; imagePath = `/${projectId}/props/${uuidv4()}.jpg`; } + if (type == "storyboard") { + insertType = "分镜"; + imagePath = `/${projectId}/storyboard/${uuidv4()}.jpg`; + } + await u.oss.writeFile(imagePath!, buffer); await u.db("t_image").where("id", imageId).update({ diff --git a/src/routes/assets/polishPrompt.ts b/src/routes/assets/polishPrompt.ts index 062e6df..8b42f91 100644 --- a/src/routes/assets/polishPrompt.ts +++ b/src/routes/assets/polishPrompt.ts @@ -1,12 +1,9 @@ import express from "express"; import u from "@/utils"; import * as zod from "zod"; -import { success } from "@/lib/responseFormat"; +import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; const router = express.Router(); -const jsonSchema = zod.object({ - prompt: zod.string().describe("提示词"), -}); interface OutlineItem { description: string; name: string; @@ -88,8 +85,9 @@ export default router.post( const result: ResultItem[] = Object.values(itemMap); const promptsList = await u.db("t_prompts").where("code", "in", ["role-polish", "scene-polish", "storyboard-polish", "tool-polish"]); + const apiConfigData = await u.getPromptAi("assetsPrompt"); const errPrompts = "不论用户说什么,请直接输出AI配置异常"; - const getPromptValue = (code: string): string => { + const getPromptValue = (code: string) => { const item = promptsList.find((p) => p.code === code); return item?.customValue ?? item?.defaultValue ?? errPrompts; }; @@ -97,7 +95,6 @@ export default router.post( const scene = getPromptValue("scene-polish"); const tool = getPromptValue("tool-polish"); const storyboard = getPromptValue("storyboard-polish"); - let systemPrompt = ""; let userPrompt = ""; if (type == "role") { @@ -125,6 +122,7 @@ export default router.post( } if (type == "scene") { const data = findItemByName(result, name, "scenes"); + const chapterRange = Array.isArray(data?.chapterRange) ? data.chapterRange : [data?.chapterRange]; const novelData = (await u.db("t_novel").whereIn("chapterIndex", chapterRange).select("*")) as NovelChapter[]; const results: string = mergeNovelText(novelData); @@ -188,33 +186,33 @@ export default router.post( `; } async function generatePrompt() { - const model = await u.ai.text(); - const result = await model.invoke({ - messages: [ - { - role: "system", - content: systemPrompt, - }, - { - role: "user", - content: userPrompt, - }, - ], - responseFormat: { - type: "json_schema", - jsonSchema: { - name: "json", - strict: true, - schema: zod.toJSONSchema(jsonSchema), + const result = await u.ai.text.invoke( + { + messages: [ + { + role: "system", + content: systemPrompt, + }, + { + role: "user", + content: userPrompt, + }, + ], + output: { + prompt: zod.string().describe("提示词"), }, }, - }); - return result.json; + apiConfigData, + ); + return result.prompt; } - const data = (await generatePrompt()) as any; + try { + const prompt = (await generatePrompt()) as any; + if (!prompt) return res.status(500).send("失败"); - if (!data.prompt) return res.status(500).send("失败"); - - res.status(200).send(success({ prompt: data.prompt, assetsId })); + res.status(200).send(success({ prompt: prompt, assetsId })); + } catch (e: any) { + return res.status(500).send(error(e?.data?.error?.message ?? e?.message ?? "生成失败")); + } }, ); diff --git a/src/routes/assets/saveAssets.ts b/src/routes/assets/saveAssets.ts index 174c45f..471a10f 100644 --- a/src/routes/assets/saveAssets.ts +++ b/src/routes/assets/saveAssets.ts @@ -46,10 +46,10 @@ export default router.post( } // 检查图片表里是否有这条图片 - const selectedImage = await u.db("t_image").where("filePath", savePath).first(); - if (!selectedImage) { - return res.status(404).send({ success: false, message: "所选图片不存在,请重新生成或选定图片" }); - } + // const selectedImage = await u.db("t_image").where("filePath", savePath).first(); + // if (!selectedImage) { + // return res.status(500).send({ success: false, message: "所选图片不存在,请重新生成或选定图片" }); + // } imageUrl = savePath; } @@ -78,7 +78,9 @@ export default router.post( } // 更新提示信息 - await u.db("t_assets").where("id", id).update({ prompt }); + if (prompt !== undefined && prompt !== null && prompt !== "") { + await u.db("t_assets").where("id", id).update({ prompt }); + } res.status(200).send(success({ message: "保存资产图片成功" })); }, diff --git a/src/routes/other/testAI.ts b/src/routes/other/testAI.ts index b64db1d..4fe5d85 100644 --- a/src/routes/other/testAI.ts +++ b/src/routes/other/testAI.ts @@ -3,12 +3,9 @@ import { success, error } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; import u from "@/utils"; import { z } from "zod"; -import { generateText, Output, tool, stepCountIs } from "ai"; +import { tool } from "ai"; const router = express.Router(); -import { createOpenAI } from "@ai-sdk/openai"; -import { createDeepSeek } from "@ai-sdk/deepseek"; - // 检查语言模型 export default router.post( "/", @@ -16,12 +13,12 @@ export default router.post( modelName: z.string(), apiKey: z.string(), baseURL: z.string().optional(), + manufacturer: z.string(), }), async (req, res) => { - const { modelName, apiKey, baseURL } = req.body; + const { modelName, apiKey, baseURL, manufacturer } = req.body; const getWeatherTool = tool({ - // strict: true, description: "Get the weather in a location", inputSchema: z.object({ location: z.string().describe("The location to get the weather for"), @@ -46,15 +43,14 @@ export default router.post( model: modelName, apiKey, baseURL, + manufacturer, }, ); - console.log("%c Line:52 🍐 reply", "background:#ffdd4d", reply); res.status(200).send(success(reply)); } catch (err) { - console.log(err); - if (typeof err === "string") return res.status(500).send(error(err)); - const msg = err instanceof Error ? err.message : (err as any)?.error?.message; - return res.status(500).send(error(msg || "未知错误")); + const msg = u.error(err).message; + console.error(msg); + res.status(500).send(error(msg)); } }, ); diff --git a/src/routes/other/testImage.ts b/src/routes/other/testImage.ts index 11d52f3..5f1714b 100644 --- a/src/routes/other/testImage.ts +++ b/src/routes/other/testImage.ts @@ -17,9 +17,10 @@ export default router.post( async (req, res) => { const { modelName, apiKey, baseURL, manufacturer } = req.body; try { - const contentStr = await u.ai.generateImage( + const image = await u.ai.image( { - prompt: "2D cat", + prompt: + "一张16:9比例的图片,完美等分为2x2四宫格布局,各区域无缝衔接:\n左上宫格:一只可爱的猫,毛发蓬松,眼睛明亮,姿态俏皮\n右上宫格:一只友善的狗,金毛犬,表情愉悦,摇着尾巴\n左下宫格:一头健壮的牛,田园背景,目光温和,皮毛光泽\n右下宫格:一匹骏马,姿态优雅,鬃毛飘逸,肌肉健美\n风格要求:四个宫格风格统一,色彩鲜艳饱和,高清画质,细节清晰锐利,专业插画风格,线条干净,统一的左上方光源,柔和阴影,和谐配色,卡通/半写实风格,宫格间用白色或浅灰细线分隔", imageBase64: [], aspectRatio: "16:9", size: "1K", @@ -31,10 +32,11 @@ export default router.post( manufacturer, }, ); - res.status(200).send(success(contentStr)); - } catch (err: any) { - const message = err?.response?.data?.error?.message || err?.error?.message || "模型调用失败"; - res.status(500).send(error(message)); + res.status(200).send(success(image)); + } catch (err) { + const msg = u.error(err).message; + console.error(msg); + res.status(500).send(error(msg)); } }, ); diff --git a/src/routes/other/testVideo.ts b/src/routes/other/testVideo.ts index c9a3ab0..745ee97 100644 --- a/src/routes/other/testVideo.ts +++ b/src/routes/other/testVideo.ts @@ -1,9 +1,6 @@ import express from "express"; import { success, error } from "@/lib/responseFormat"; import u from "@/utils"; -import { createAgent } from "langchain"; -import { openAI } from "@/agents/models"; -import { OpenAIChatModel, type OpenAIChatModelOptions } from "@aigne/openai"; import { validateFields } from "@/middleware/middleware"; import { z } from "zod"; const router = express.Router(); @@ -15,25 +12,35 @@ export default router.post( modelName: z.string().optional(), apiKey: z.string(), baseURL: z.string().optional(), - manufacturer: z.enum(["runninghub", "volcengine", "apimart", "gemini", "openAi"]), + manufacturer: z.string(), }), async (req, res) => { const { modelName, apiKey, baseURL, manufacturer } = req.body; try { - const videoPath = await u.ai.generateVideo( + const duration = manufacturer == "gemini" ? 4 : 5; + const videoPath = await u.ai.video( { imageBase64: [], - savePath: "", + savePath: "test.mp4", prompt: "stickman Dances", - duration: 10 as any, - aspectRatio: "16:9" as any, + duration: duration, + resolution: "720p", + aspectRatio: "16:9", + audio: false, + }, + { + model: modelName, + apiKey, + baseURL, + manufacturer, }, - manufacturer, ); const url = await u.oss.getFileUrl(videoPath); res.status(200).send(success(url)); } catch (err: any) { - res.status(500).send(error(err.error.message || "模型调用失败")); + const msg = u.error(err).message; + console.error(msg); + res.status(500).send(error(msg)); } }, ); diff --git a/src/routes/outline/agentsOutline.ts b/src/routes/outline/agentsOutline.ts index 2059272..7ca6032 100644 --- a/src/routes/outline/agentsOutline.ts +++ b/src/routes/outline/agentsOutline.ts @@ -8,7 +8,6 @@ expressWs(router as unknown as Application); router.ws("/", async (ws, req) => { let agent: OutlineScript; - const config = await u.getConfig("language"); const projectId = req.query.projectId; if (!projectId || typeof projectId !== "string") { @@ -19,10 +18,6 @@ router.ws("/", async (ws, req) => { agent = new OutlineScript(Number(projectId)); - agent.modelName = config.model ?? ""; - agent.baseURL = config.baseURL ?? ""; - agent.apiKey = config.apiKey ?? ""; - // const existing = await u // .db("t_chatHistory") // .where({ projectId: Number(projectId) }) diff --git a/src/routes/setting/addModel.ts b/src/routes/setting/addModel.ts new file mode 100644 index 0000000..0c77bb3 --- /dev/null +++ b/src/routes/setting/addModel.ts @@ -0,0 +1,33 @@ +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({ + type: z.enum(["text", "video", "image"]), + model: z.string(), + baseUrl: z.string(), + apiKey: z.string(), + modelType: z.string(), + manufacturer: z.string(), + }), + async (req, res) => { + const { type, model, baseUrl, apiKey, manufacturer, modelType } = req.body; + + await u.db("t_config").insert({ + type, + model, + baseUrl, + apiKey, + manufacturer, + modelType, + createTime: Date.now(), + userId: 1, + }); + res.status(200).send(success("新增成功")); + }, +); diff --git a/src/routes/setting/configurationModel.ts b/src/routes/setting/configurationModel.ts new file mode 100644 index 0000000..7d7687e --- /dev/null +++ b/src/routes/setting/configurationModel.ts @@ -0,0 +1,23 @@ +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({ + id: z.number(), + configId: z.number(), + }), + async (req, res) => { + const { id, configId } = req.body; + if (id) { + await u.db("t_aiModelMap").where("id", id).update({ + configId, + }); + } + res.status(200).send(success("配置成功")); + }, +); diff --git a/src/routes/setting/delModel.ts b/src/routes/setting/delModel.ts new file mode 100644 index 0000000..802ae0c --- /dev/null +++ b/src/routes/setting/delModel.ts @@ -0,0 +1,19 @@ +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({ + id: z.number(), + }), + async (req, res) => { + const { id } = req.body; + await u.db("t_config").where("id", id).delete(); + await u.db("t_aiModelMap").where("configId", id).update("configId",null); + res.status(200).send(success("删除成功")); + }, +); diff --git a/src/routes/setting/getAiModelMap.ts b/src/routes/setting/getAiModelMap.ts new file mode 100644 index 0000000..fa10f51 --- /dev/null +++ b/src/routes/setting/getAiModelMap.ts @@ -0,0 +1,13 @@ +import express from "express"; +import u from "@/utils"; +import { success } from "@/lib/responseFormat"; + +const router = express.Router(); + +export default router.post("/", async (req, res) => { + const configData = await u + .db("t_aiModelMap") + .leftJoin("t_config", "t_aiModelMap.configId", "t_config.id") + .select("t_aiModelMap.name", "t_config.model", "t_aiModelMap.id", "t_aiModelMap.key"); + res.status(200).send(success(configData)); +}); diff --git a/src/routes/setting/getLog.ts b/src/routes/setting/getLog.ts new file mode 100644 index 0000000..ea04c0c --- /dev/null +++ b/src/routes/setting/getLog.ts @@ -0,0 +1,17 @@ +import logger from "@/logger"; +import express from "express"; +import u from "@/utils"; +import { z } from "zod"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +const router = express.Router(); + +export default router.post("/", async (req, res) => { + const { id } = (req as any).user; + + if (id !== 1) return res.status(400).send(error("无权限查看,仅管理员USERID=1可见")); + + const logs = logger.exportLogs(); + + res.status(200).send(success(logs)); +}); diff --git a/src/routes/setting/getSetting.ts b/src/routes/setting/getSetting.ts index 7110d63..fb7a6a9 100644 --- a/src/routes/setting/getSetting.ts +++ b/src/routes/setting/getSetting.ts @@ -1,41 +1,11 @@ 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({ - userId: z.number(), - }), - async (req, res) => { - const { userId } = req.body; +export default router.post("/", async (req, res) => { + const userId = 1; + const configData = await u.db("t_config").where("type","<>","video").where("userId", userId).select("*"); - const settingData = await u.db("t_setting").select("*"); - - const configData = await u.db("t_config").where("userId", userId).select("*") ; - - const parsedData = settingData.map((item) => ({ - ...item, - imageModel: (() => { - try { - return JSON.parse(item.imageModel ?? "{}"); - } catch { - return null; - } - })(), - languageModel: (() => { - try { - return JSON.parse(item.languageModel ?? "{}"); - } catch { - return null; - } - })(), - videoModel: configData, - })); - - res.status(200).send(success(parsedData)); - } -); + res.status(200).send(success(configData)); +}); diff --git a/src/routes/setting/getVideoModelList.ts b/src/routes/setting/getVideoModelList.ts new file mode 100644 index 0000000..4ec6c82 --- /dev/null +++ b/src/routes/setting/getVideoModelList.ts @@ -0,0 +1,11 @@ +import express from "express"; +import u from "@/utils"; +import { success } from "@/lib/responseFormat"; +const router = express.Router(); + +export default router.post("/", async (req, res) => { + const userId = 1; + const configData = await u.db("t_config").where("type","video").where("userId", userId).select("*"); + + res.status(200).send(success(configData)); +}); diff --git a/src/routes/setting/updateModel.ts b/src/routes/setting/updateModel.ts new file mode 100644 index 0000000..421a06d --- /dev/null +++ b/src/routes/setting/updateModel.ts @@ -0,0 +1,32 @@ +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({ + id: z.number(), + type: z.enum(["text", "video", "image"]), + model: z.string(), + baseUrl: z.string(), + modelType: z.string(), + apiKey: z.string(), + manufacturer: z.string(), + }), + async (req, res) => { + const { id, type, model, baseUrl, apiKey, manufacturer, modelType } = req.body; + + await u.db("t_config").where("id", id).update({ + type, + model, + baseUrl, + apiKey, + manufacturer, + modelType, + }); + res.status(200).send(success("编辑成功")); + }, +); diff --git a/src/routes/setting/updateSetting.ts b/src/routes/setting/updateSetting.ts deleted file mode 100644 index 96f934b..0000000 --- a/src/routes/setting/updateSetting.ts +++ /dev/null @@ -1,55 +0,0 @@ -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({ - userId: z.number(), - imageModel: z.object().optional(), - videoModel: z.array(z.object()).optional(), - languageModel: z.object().optional(), - name: z.string().optional(), - password: z.string().optional(), - }), - async (req, res) => { - const { userId, imageModel, videoModel, languageModel, name, password } = req.body; - - await u - .db("t_setting") - .where("userId", userId) - .update({ - imageModel: JSON.stringify(imageModel), - languageModel: JSON.stringify(languageModel), - }); - - if (videoModel) { - await u.db("t_config").where("type", "video").delete(); - - for (const item of videoModel) { - await u.db("t_config").insert({ - type: "video", - name: item.name, - model: item.model, - apiKey: item.apiKey, - baseUrl: item.baseUrl, - index: item.index, - createTime: Date.now(), - userId, - manufacturer: item.manufacturer, - }); - } - } - - await u.db("t_user").where("id", userId).update({ - name, - password, - }); - - res.status(200).send(success({ message: "修改全局配置成功" })); - } -); diff --git a/src/routes/setting/updeteModel.ts b/src/routes/setting/updeteModel.ts new file mode 100644 index 0000000..421a06d --- /dev/null +++ b/src/routes/setting/updeteModel.ts @@ -0,0 +1,32 @@ +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({ + id: z.number(), + type: z.enum(["text", "video", "image"]), + model: z.string(), + baseUrl: z.string(), + modelType: z.string(), + apiKey: z.string(), + manufacturer: z.string(), + }), + async (req, res) => { + const { id, type, model, baseUrl, apiKey, manufacturer, modelType } = req.body; + + await u.db("t_config").where("id", id).update({ + type, + model, + baseUrl, + apiKey, + manufacturer, + modelType, + }); + res.status(200).send(success("编辑成功")); + }, +); diff --git a/src/routes/storyboard/batchSuperScoreImage.ts b/src/routes/storyboard/batchSuperScoreImage.ts index 846974b..39d1064 100644 --- a/src/routes/storyboard/batchSuperScoreImage.ts +++ b/src/routes/storyboard/batchSuperScoreImage.ts @@ -17,19 +17,19 @@ async function urlToBase64(imageUrl: string): Promise { } // 超分并保存到 oss -async function superResolutionAndSave( - src: string, - projectId: number, - videoRatio: string, -): Promise<{ ossPath: string; base64: string }> { - const contentStr = await u.ai.generateImage({ - aspectRatio: videoRatio, - size: "1K", - resType: "b64", - systemPrompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率", - prompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率", - imageBase64: [await urlToBase64(src)], - }); +async function superResolutionAndSave(src: string, projectId: number, videoRatio: string): Promise<{ ossPath: string; base64: string }> { + const apiConfig = await u.getPromptAi("storyboardImage"); + const contentStr = await u.ai.image( + { + aspectRatio: videoRatio, + size: "1K", + resType: "b64", + systemPrompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率", + prompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率", + imageBase64: [await urlToBase64(src)], + }, + apiConfig, + ); const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); const base64Str = match ? match[1] : contentStr; const buffer = Buffer.from(base64Str, "base64"); @@ -50,9 +50,9 @@ export default router.post( id: z.string(), prompt: z.string().optional(), src: z.string(), - }) + }), ), - }) + }), ), }), async (req, res) => { @@ -63,9 +63,7 @@ export default router.post( if (!projectData) return res.status(500).send(error("项目不存在")); // 遍历处理每个分镜段 - const processSegment = async ( - segment: { cells: { id: string; src: string }[] } - ) => { + const processSegment = async (segment: { cells: { id: string; src: string }[] }) => { // 超分所有 cell const cellsWithSuperscore = await Promise.all( segment.cells.map(async (cell) => { @@ -76,9 +74,9 @@ export default router.post( scriptId, filePath: ossPath, // oss 路径(未签名) src: cell.src, - type: "分镜" + type: "分镜", }; - }) + }), ); return cellsWithSuperscore; }; @@ -92,9 +90,9 @@ export default router.post( (item.value as any[]).map(async (cell) => ({ ...cell, filePath: await u.oss.getFileUrl(cell.filePath ?? ""), - })) - ) + })), + ), ); res.status(200).send(success(flatList)); - } + }, ); diff --git a/src/routes/storyboard/chatStoryboard.ts b/src/routes/storyboard/chatStoryboard.ts index 048563f..e2854bc 100644 --- a/src/routes/storyboard/chatStoryboard.ts +++ b/src/routes/storyboard/chatStoryboard.ts @@ -8,7 +8,6 @@ expressWs(router as unknown as Application); router.ws("/", async (ws, req) => { let agent: Storyboard; - const config = await u.getConfig("language"); const projectId = req.query.projectId; const scriptId = req.query.scriptId; @@ -20,10 +19,6 @@ router.ws("/", async (ws, req) => { agent = new Storyboard(Number(projectId), Number(scriptId)); - agent.modelName = config.model ?? ""; - agent.baseURL = config.baseURL ?? ""; - agent.apiKey = config.apiKey ?? ""; - const existing = await u .db("t_chatHistory") .where({ projectId: Number(projectId) }) diff --git a/src/routes/storyboard/generateVideoPrompt.ts b/src/routes/storyboard/generateVideoPrompt.ts index ff93ee4..37de60f 100644 --- a/src/routes/storyboard/generateVideoPrompt.ts +++ b/src/routes/storyboard/generateVideoPrompt.ts @@ -3,16 +3,10 @@ import u from "@/utils"; import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; import { z } from "zod"; -import path from "path"; +import axios from "axios"; const router = express.Router(); -const cellsResultSchema = z.object({ - time: z.number().describe("时长,镜头时长 1-15"), - content: z.string().describe("提示词内容"), - name: z.string().describe("分镜名称"), -}); - const prompt = ` 你是一名资深动画导演,擅长将静态分镜转化为简洁、专业、详尽的 Motion Prompt(视频生成动作提示)。你理解镜头语言、情绪节奏,能补充丰富但不重复静态元素,只突出变化与动态。 @@ -103,7 +97,12 @@ const prompt = ` 现在请根据我提供的分镜内容,严格按照以上规则输出 Motion Prompt JSON 对象。 `; - +async function urlToBase64(imageUrl: string): Promise { + const response = await axios.get(imageUrl, { responseType: "arraybuffer" }); + const contentType = response.headers["content-type"] || "image/png"; + const base64 = Buffer.from(response.data, "binary").toString("base64"); + return `data:${contentType};base64,${base64}`; +} // 生成单个分镜提示 async function generateSingleVideoPrompt({ scriptText, @@ -114,25 +113,6 @@ async function generateSingleVideoPrompt({ storyboardPrompt: string; ossPath: string; }): Promise<{ content: string; time: number; name: string }> { - let rootDir: string; - if (typeof process.versions?.electron !== "undefined") { - const { app } = require("electron"); - const userDataDir: string = app.getPath("userData"); - rootDir = path.join(userDataDir, "uploads"); - } else { - rootDir = path.join(process.cwd(), "uploads"); - } - - let imagePath = ossPath; - if (ossPath.includes("http")) { - imagePath = new URL(ossPath).pathname; - } - - const model = await u.ai.text({}); - if (!model) { - throw new Error("无法获取语言模型,请检查语言模型配置"); - } - const messages: any[] = [ { role: "system", @@ -146,38 +126,38 @@ async function generateSingleVideoPrompt({ text: `剧本内容:${scriptText}\n分镜提示词:${storyboardPrompt}`, }, { - type: "local", - path: path.join(rootDir, imagePath), + type: "image", + image: await urlToBase64(ossPath), }, ], }, ]; try { - const result = await model.invoke({ - messages, - responseFormat: { - type: "json_schema", - jsonSchema: { - name: "json", - strict: true, - schema: z.toJSONSchema(cellsResultSchema), + const apiConfig = await u.getPromptAi("videoPrompt"); + + const result = await u.ai.text.invoke( + { + messages, + output: { + time: z.number().describe("时长,镜头时长 1-15"), + content: z.string().describe("提示词内容"), + name: z.string().describe("分镜名称"), }, }, - }); - - if (!result || !result.json) { + apiConfig, + ); + if (!result) { console.error("AI 返回结果为空:", result); throw new Error("AI 返回结果为空"); } - const json = result.json as { content: string; time: number; name: string }; - if (!json.content || json.time === undefined || !json.name) { - console.error("AI 返回格式错误:", result.json); + if (!result.content || result.time === undefined || !result.name) { + console.error("AI 返回格式错误:", result); throw new Error("AI 返回格式错误"); } - return json; + return result; } catch (err: any) { console.error("generateSingleVideoPrompt 调用失败:", err?.message || err); throw new Error(`生成视频提示词失败: ${err?.message || "未知错误"}`); diff --git a/src/routes/video/addVideo.ts b/src/routes/video/addVideo.ts index f574a75..bfccd7c 100644 --- a/src/routes/video/addVideo.ts +++ b/src/routes/video/addVideo.ts @@ -42,5 +42,5 @@ export default router.post( }); res.status(200).send(success({ message: "新增视频成功" })); - } + }, ); diff --git a/src/routes/video/addVideoConfig.ts b/src/routes/video/addVideoConfig.ts index 7722637..acf9536 100644 --- a/src/routes/video/addVideoConfig.ts +++ b/src/routes/video/addVideoConfig.ts @@ -1,16 +1,18 @@ import express from "express"; import u from "@/utils"; -import { success } from "@/lib/responseFormat"; +import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; import { z } from "zod"; const router = express.Router(); // 图片项schema -const imageItemSchema = z.object({ - id: z.number(), - filePath: z.string(), - prompt: z.string().optional(), -}).nullable(); +const imageItemSchema = z + .object({ + id: z.number(), + filePath: z.string(), + prompt: z.string().optional(), + }) + .nullable(); // 新增视频配置 export default router.post( @@ -18,44 +20,40 @@ export default router.post( validateFields({ scriptId: z.number(), projectId: z.number(), - manufacturer: z.string(), - mode: z.enum(["startEnd", "multi", "single"]), + configId: z.number(), + mode: z.enum(["startEnd", "multi", "single", "text", ""]), startFrame: imageItemSchema.optional(), endFrame: imageItemSchema.optional(), - images: z.array(z.object({ - id: z.number(), - filePath: z.string(), - prompt: z.string().optional(), - })).optional(), + images: z + .array( + z.object({ + id: z.number(), + filePath: z.string(), + prompt: z.string().optional(), + }), + ) + .optional(), resolution: z.string(), duration: z.number(), prompt: z.string().optional(), + audioEnabled: z.boolean(), }), async (req, res) => { - const { - scriptId, - projectId, - manufacturer, - mode, - startFrame, - endFrame, - images, - resolution, - duration, - prompt - } = req.body; + const { scriptId, projectId, configId, mode, startFrame, endFrame, images, resolution, duration, prompt, audioEnabled } = req.body; // 生成新ID - const maxIdResult = await u.db("t_videoConfig").max("id as maxId").first(); + const maxIdResult: any = await u.db("t_videoConfig").max("id as maxId").first(); const newId = (maxIdResult?.maxId || 0) + 1; const now = Date.now(); - + const configData = await u.db("t_config").where("id", configId).first(); + if (!configData) return res.status(500).send(error("不存在的模型")); // 插入数据 await u.db("t_videoConfig").insert({ id: newId, scriptId, projectId, - manufacturer, + manufacturer: configData.manufacturer, + aiConfigId: configId, mode, startFrame: startFrame ? JSON.stringify(startFrame) : null, endFrame: endFrame ? JSON.stringify(endFrame) : null, @@ -66,25 +64,31 @@ export default router.post( selectedResultId: null, createTime: now, updateTime: now, + audioEnabled: audioEnabled ? 1 : 0, }); - res.status(200).send(success({ - message: "新增视频配置成功", - data: { - id: newId, - scriptId, - projectId, - manufacturer, - mode, - startFrame, - endFrame, - images: images || [], - resolution, - duration, - prompt: prompt || "", - selectedResultId: null, - createdAt: new Date(now).toISOString(), - } - })); + res.status(200).send( + success({ + message: "新增视频配置成功", + data: { + id: newId, + scriptId, + projectId, + manufacturer: configData.manufacturer, + aiConfigId: configId, + model: configData.model, + mode, + startFrame, + endFrame, + images: images || [], + resolution, + duration, + prompt: prompt || "", + selectedResultId: null, + createdAt: new Date(now).toISOString(), + audioEnabled: audioEnabled, + }, + }), + ); }, ); diff --git a/src/routes/video/generatePrompt.ts b/src/routes/video/generatePrompt.ts index 0fc0a04..3b3f841 100644 --- a/src/routes/video/generatePrompt.ts +++ b/src/routes/video/generatePrompt.ts @@ -1,17 +1,18 @@ import express from "express"; import u from "@/utils"; -import { success } from "@/lib/responseFormat"; +import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; import { z } from "zod"; const router = express.Router(); -type GenerateMode = "startEnd" | "multi" | "single"; +type GenerateMode = "startEnd" | "multi" | "single" | "text"; + +const getSystemPrompt = async (mode: GenerateMode) => { + const promptsList = await u.db("t_prompts").where("code", "in", ["video-startEnd", "video-multi", "video-single", "video-main", "video-text"]); -const getSystemPrompt = async (mode: GenerateMode): Promise => { - const promptsList = await u.db("t_prompts").where("code", "in", ["video-startEnd", "video-multi", "video-single", "video-main"]); const errPrompts = "不论用户说什么,请直接输出AI配置异常"; - const getPromptValue = (code: string): string => { + const getPromptValue = (code: string) => { const item = promptsList.find((p) => p.code === code); return item?.customValue ?? item?.defaultValue ?? errPrompts; }; @@ -19,14 +20,16 @@ const getSystemPrompt = async (mode: GenerateMode): Promise => { const multi = getPromptValue("video-multi"); const single = getPromptValue("video-single"); const main = getPromptValue("video-main"); + const text = getPromptValue("video-text"); - const modeDescriptions: Record = { + const modeDescriptions = { startEnd: startEnd, multi: multi, single: single, + text: text, }; - - return `${main}\n\n${modeDescriptions[mode]}`; + const modeData = modeDescriptions[mode]; + return `${main}\n\n${modeData}`; }; const getModeDescription = (mode: GenerateMode): string => { @@ -34,6 +37,7 @@ const getModeDescription = (mode: GenerateMode): string => { startEnd: "首尾帧模式", multi: "宫格模式", single: "单图模式", + text: "文本模式", }; return map[mode]; }; @@ -41,42 +45,62 @@ const getModeDescription = (mode: GenerateMode): string => { export default router.post( "/", validateFields({ - images: z.array( - z.object({ - filePath: z.string(), - prompt: z.string(), - }), - ), + images: z + .array( + z.object({ + filePath: z.string(), + prompt: z.string(), + }), + ) + .optional(), prompt: z.string(), duration: z.number(), - type: z.enum(["startEnd", "multi", "single"]).optional(), + type: z.enum(["startEnd", "multi", "single", "text", ""]).optional(), + videoConfigId: z.number().optional(), }), async (req, res) => { - const { prompt, images, duration, type = "single" } = req.body; + const { prompt, images, duration, type = "single", videoConfigId } = req.body; const mode = type as GenerateMode; - - const model = await u.ai.text({}); - + let videoConfigData; + if (videoConfigId) { + videoConfigData = await u + .db("t_videoConfig") + .leftJoin("t_script", "t_script.id", "t_videoConfig.scriptId") + .where("t_videoConfig.id", videoConfigId) + .select("t_script.content") + .first(); + if (!videoConfigData) return res.status(500).send(error("视频配置不存在")); + } const imagePrompts = images.map((i: { filePath: string; prompt: string }, index: number) => `Image ${index + 1}: ${i.prompt}`).join("\n"); const shotCount = images.length; const avgDuration = (parseFloat(duration) / shotCount).toFixed(1); - - const result = await model!.invoke({ - messages: [ + const promptConfig = await getSystemPrompt(mode); + const promptAiConfig = await u.getPromptAi("videoPrompt"); + try { + const result = await u.ai.text.invoke( { - role: "system", - content: await getSystemPrompt(mode), - }, - { - role: "user", - content: `Mode: ${getModeDescription(mode)} + messages: [ + { + role: "system", + content: promptConfig, + }, + { + role: "user", + content: `Mode: ${getModeDescription(mode)} Reference Images: ${imagePrompts} Script: ${prompt} +${ + videoConfigData + ? `script content: +${videoConfigData.content}` + : "" +} + Parameters: - Total Duration: ${duration}s @@ -84,10 +108,15 @@ Parameters: - Average Duration: ${avgDuration}s per shot Generate storyboard prompts:`, + }, + ], }, - ], - }); + promptAiConfig, + ); - res.status(200).send(success(result.text)); + res.status(200).send(success(result.text)); + } catch (e) { + return res.status(500).send(error(u.error(e).message)); + } }, ); diff --git a/src/routes/video/generateVideo.ts b/src/routes/video/generateVideo.ts index a2fdd75..e0b1b5b 100644 --- a/src/routes/video/generateVideo.ts +++ b/src/routes/video/generateVideo.ts @@ -4,6 +4,10 @@ import { z } from "zod"; import { v4 as uuidv4 } from "uuid"; import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; +import { t_config } from "@/types/database"; +import sharp from "sharp"; +import fs from "fs"; +import path from "path"; const router = express.Router(); @@ -13,44 +17,55 @@ export default router.post( validateFields({ projectId: z.number(), scriptId: z.number(), - configId: z.number().optional(), // 关联的视频配置ID + configId: z.number().optional(), // 关联的视频配 置ID type: z.string().optional(), resolution: z.string(), + aiConfigId: z.number(), filePath: z.array(z.string()), duration: z.number(), prompt: z.string(), + mode: z.enum(["startEnd", "multi", "single", "text"]), + audioEnabled: z.boolean(), }), async (req, res) => { - const { type, scriptId, projectId, configId, resolution, filePath, duration, prompt } = req.body; + const { type, mode, scriptId, projectId, configId, aiConfigId, resolution, filePath, duration, prompt, audioEnabled } = req.body; - // 参数校验 - if (type === "volcengine") { - if (duration < 4 || duration > 12) { - return res.status(400).send(error("视频时长需在4-12秒之间")); - } - if (!["480p", "720p", "1080p"].includes(resolution)) { - return res.status(400).send(error("视频分辨率不正确")); + if (mode == "text") filePath.length = 0; + else if (!filePath.length) { + return res.status(500).send(error("请先选择图片")); + } + const configData = await u.db("t_videoConfig").where("id", configId).first(); + + if (!configData) { + return res.status(500).send(error("视频配置不存在")); + } + if (configData.manufacturer == "runninghub") { + if (filePath.length > 1) { + const gridUrl = await sharpProcessingImage(filePath, projectId); + if (gridUrl) { + filePath.length = 0; + filePath.push(gridUrl); + } } } - if (type === "runninghub") { - if (duration !== 10 && duration !== 15) { - return res.status(400).send(error("视频时长只能是10秒或15秒")); - } - if (resolution !== "9:16" && resolution !== "16:9") { - return res.status(400).send(error("视频分辨率不正确")); - } + // 优先使用视频配置中的AI配置ID查询,查不到再使用传入的aiConfigId + let aiConfigData = null; + if (configData.aiConfigId) { + aiConfigData = await u.db("t_config").where("id", configData.aiConfigId).first(); + } + if (!aiConfigData) { + aiConfigData = await u.db("t_config").where("id", aiConfigId).first(); } + if (!aiConfigData) { + return res.status(500).send(error("模型配置不存在")); + } // 过滤掉空值 let fileUrl = filePath.filter((p: string) => p && p.trim() !== ""); - - if (fileUrl.length === 0) { - return res.status(400).send(error("请至少选择一张图片")); - } // 处理文件路径,如果是 base64 则上传到 OSS - if (fileUrl.length === 1) { + if (fileUrl.length) { const match = fileUrl[0].match(/base64,([A-Za-z0-9+/=]+)/); if (match && match.length >= 2) { const imagePath = `/${projectId}/assets/${uuidv4()}.jpg`; @@ -69,20 +84,21 @@ export default router.post( // 否则认为已经是路径 return url; }; + if (fileUrl.length) { + // 校验文件是否存在 + const fileExistsResults = await Promise.all( + fileUrl.map(async (url: string) => { + const path = getPathname(url); + return u.oss.fileExists(path); + }), + ); - // 校验文件是否存在 - const fileExistsResults = await Promise.all( - fileUrl.map(async (url: string) => { - const path = getPathname(url); - return u.oss.fileExists(path); - }), - ); - - if (!fileExistsResults.every(Boolean)) { - return res.status(400).send(error("选择分镜文件不存在")); + if (!fileExistsResults.every(Boolean)) { + return res.status(400).send(error("选择分镜文件不存在")); + } } - const firstFrame = getPathname(fileUrl[0]); + const firstFrame = fileUrl.length ? getPathname(fileUrl[0]) : ""; const storyboardImgs = fileUrl.map((path: string) => getPathname(path)); const savePath = `/${projectId}/video/${uuidv4()}.mp4`; @@ -103,7 +119,7 @@ export default router.post( res.status(200).send(success({ id: videoId, configId: configId || null })); // 异步生成视频 - generateVideoAsync(videoId, projectId, fileUrl, savePath, prompt, duration, resolution, type); + generateVideoAsync(videoId, projectId, fileUrl, savePath, prompt, duration, resolution, audioEnabled, aiConfigData); }, ); @@ -116,10 +132,11 @@ async function generateVideoAsync( prompt: string, duration: number, resolution: string, - type?: string, + audioEnabled: boolean, + aiConfigData: t_config, ) { try { - const projectData = await u.db("t_project").where("id", projectId).select("artStyle").first(); + const projectData = await u.db("t_project").where("id", projectId).select("artStyle", "videoRatio").first(); // 提取路径名的辅助函数 const getPathname = (url: string): string => { @@ -149,16 +166,22 @@ ${prompt} 3. 关键人物在画面中全部清晰显示,不得被遮挡、缺失或省略 4. 画面真实、细致,无畸形、无模糊、无杂物、无多余人物、无文字、水印、logo `; - - const videoPath = await u.ai.generateVideo( + const videoPath = await u.ai.video( { imageBase64, savePath, prompt: inputPrompt, duration: duration as any, - aspectRatio: resolution as any, + aspectRatio: projectData?.videoRatio as any, + resolution: resolution as any, + audio: audioEnabled, + }, + { + baseURL: aiConfigData?.baseUrl!, + model: aiConfigData?.model!, + apiKey: aiConfigData?.apiKey!, + manufacturer: aiConfigData?.manufacturer!, }, - type!, ); if (videoPath) { @@ -173,6 +196,134 @@ ${prompt} } } catch (err) { console.error(`视频生成失败 videoId=${videoId}:`, err); - await u.db("t_video").where("id", videoId).update({ state: -1 }); + await u + .db("t_video") + .where("id", videoId) + .update({ state: -1, errorReason: u.error(err).message }); } } + +/** + * 使用sharp把图片拼接为宫格图,最多3x3,图片数量为1-9不等 + * @param imageList - 图片路径或base64数组 + * @returns 拼接后的图片Buffer + */ +async function sharpProcessingImage(imageList: string[], projectId: number): Promise { + if (!imageList || imageList.length === 0) { + throw new Error("图片列表不能为空"); + } + + if (imageList.length > 9) { + throw new Error("图片数量不能超过9张"); + } + + // 计算网格布局:根据图片数量确定行列数 + const count = imageList.length; + let cols: number, rows: number; + + if (count === 1) { + cols = rows = 1; + } else if (count === 2) { + cols = 2; + rows = 1; + } else if (count <= 4) { + cols = rows = 2; + } else if (count <= 6) { + cols = 3; + rows = 2; + } else { + cols = rows = 3; + } + + // 第一步:加载所有图片并获取原始尺寸 + const loadedImages = await Promise.all( + imageList.map(async (imagePath) => { + let imageBuffer: Buffer; + + // 判断是base64、URL还是文件路径 + if (imagePath.startsWith("data:image") || imagePath.match(/^[A-Za-z0-9+/=]+$/)) { + // Base64格式 + const base64Data = imagePath.replace(/^data:image\/\w+;base64,/, ""); + imageBuffer = Buffer.from(base64Data, "base64"); + } else if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) { + // URL格式,提取pathname后从OSS读取 + const pathname = new URL(imagePath).pathname; + imageBuffer = await u.oss.getFile(pathname); + } else { + // 文件路径,直接从OSS读取 + imageBuffer = await u.oss.getFile(imagePath); + } + + const metadata = await sharp(imageBuffer).metadata(); + return { + buffer: imageBuffer, + width: metadata.width || 0, + height: metadata.height || 0, + }; + }), + ); + + // 第二步:找出所有图片中的最大宽度和高度 + const maxWidth = Math.max(...loadedImages.map((img) => img.width)); + const maxHeight = Math.max(...loadedImages.map((img) => img.height)); + + // 第三步:将所有图片调整为统一尺寸(使用contain模式保持比例,填充背景色) + const imageData = await Promise.all( + loadedImages.map(async (img) => { + const resizedBuffer = await sharp(img.buffer) + .resize(maxWidth, maxHeight, { + fit: "contain", + background: { r: 0, g: 0, b: 0, alpha: 1 }, // 黑色背景填充 + }) + .png() + .toBuffer(); + + return { + buffer: resizedBuffer, + width: maxWidth, + height: maxHeight, + }; + }), + ); + + // 所有图片都是相同尺寸,直接计算画布大小 + const cellWidth = maxWidth; + const cellHeight = maxHeight; + const canvasWidth = cols * cellWidth; + const canvasHeight = rows * cellHeight; + + // 创建空白画布 + const canvas = sharp({ + create: { + width: canvasWidth, + height: canvasHeight, + channels: 4, + background: { r: 255, g: 255, b: 255, alpha: 1 }, + }, + }); + + // 准备合成操作 + const compositeOperations = imageData.map((data, index) => { + const row = Math.floor(index / cols); + const col = index % cols; + + // 计算当前图片的位置(无偏移,紧密排列) + const left = col * cellWidth; + const top = row * cellHeight; + + return { + input: data.buffer, + top: top, + left: left, + }; + }); + + // 合成所有图片 + const result = await canvas.composite(compositeOperations).png().toBuffer(); + + const imagePath = `/${projectId}/assets/${uuidv4()}.jpg`; + const buffer = Buffer.from(result as any, "base64"); + await u.oss.writeFile(imagePath, buffer); + + return await u.oss.getFileUrl(imagePath); +} diff --git a/src/routes/video/getManufacturer.ts b/src/routes/video/getManufacturer.ts index cf2dcb3..750822b 100644 --- a/src/routes/video/getManufacturer.ts +++ b/src/routes/video/getManufacturer.ts @@ -14,8 +14,8 @@ export default router.post( async (req, res) => { const { userId } = req.body; - const data = await u.db("t_config").where("userId", userId).select("manufacturer", "model"); + const data = await u.db("t_config").where("type", "video").where("userId", userId).select("manufacturer", "model", "id"); res.status(200).send(success(data)); - } + }, ); diff --git a/src/routes/video/getVideo.ts b/src/routes/video/getVideo.ts index 14acde3..a381cb0 100644 --- a/src/routes/video/getVideo.ts +++ b/src/routes/video/getVideo.ts @@ -28,7 +28,7 @@ export default router.post( qb.whereIn("id", specifyIds); } }) - .select("id", "configId", "time", "resolution", "prompt", "firstFrame", "filePath", "storyboardImgs", "model", "scriptId", "state"); + .select("id", "configId", "time", "resolution", "prompt", "firstFrame", "filePath", "storyboardImgs", "model", "scriptId", "state","errorReason"); // const videoIds: number[] = videos.map((video: any) => (typeof video.id === "string" ? parseInt(video.id) : video.id)); // let tempAssets: TempAsset[] = await u diff --git a/src/routes/video/getVideoConfigs.ts b/src/routes/video/getVideoConfigs.ts index b15e519..56ed112 100644 --- a/src/routes/video/getVideoConfigs.ts +++ b/src/routes/video/getVideoConfigs.ts @@ -15,16 +15,20 @@ export default router.post( const { scriptId } = req.body; // 查询该脚本下的所有视频配置 - const configs = await u.db("t_videoConfig") + const configs = await u + .db("t_videoConfig") + .leftJoin("t_config", "t_config.id", "t_videoConfig.aiConfigId") .where({ scriptId }) - .orderBy("createTime", "desc"); - + .orderBy("createTime", "desc") + .select("t_videoConfig.*", "t_config.manufacturer as manufacturer", "t_config.model"); // 解析 JSON 字段 const result = configs.map((config: any) => ({ id: config.id, scriptId: config.scriptId, projectId: config.projectId, + aiConfigId: config.aiConfigId, manufacturer: config.manufacturer, + model: config.model, mode: config.mode, startFrame: config.startFrame ? JSON.parse(config.startFrame) : null, endFrame: config.endFrame ? JSON.parse(config.endFrame) : null, @@ -34,6 +38,7 @@ export default router.post( prompt: config.prompt || "", selectedResultId: config.selectedResultId, createdAt: config.createTime ? new Date(config.createTime).toISOString() : new Date().toISOString(), + audioEnabled:!!config.audioEnabled })); res.status(200).send(success(result)); diff --git a/src/routes/video/upDateVideoConfig.ts b/src/routes/video/upDateVideoConfig.ts index b690d1d..65568ba 100644 --- a/src/routes/video/upDateVideoConfig.ts +++ b/src/routes/video/upDateVideoConfig.ts @@ -14,9 +14,13 @@ export default router.post( duration: z.number().optional(), prompt: z.string().optional(), selectedResultId: z.number().nullable().optional(), + startFrame: z.object().nullable().optional(), + endFrame: z.object().nullable().optional(), + images: z.array(z.object()).optional(), + audioEnabled: z.boolean().optional(), }), async (req, res) => { - const { id, resolution, duration, prompt, selectedResultId } = req.body; + const { id, resolution, duration, prompt, selectedResultId, startFrame, endFrame, images, audioEnabled } = req.body; // 检查配置是否存在 const existingConfig = await u.db("t_videoConfig").where({ id }).first(); @@ -41,30 +45,47 @@ export default router.post( if (selectedResultId !== undefined) { updateData.selectedResultId = selectedResultId; } - + if (startFrame !== undefined) { + updateData.startFrame = startFrame ? JSON.stringify(startFrame) : null;; + } + if (endFrame !== undefined) { + updateData.endFrame = endFrame ? JSON.stringify(endFrame) : null;; + } + if (images !== undefined) { + updateData.images = images ? JSON.stringify(images) : null; + } + if (audioEnabled !== undefined) { + updateData.audioEnabled = audioEnabled; + } // 更新数据 await u.db("t_videoConfig").where({ id }).update(updateData); // 获取更新后的数据 const updatedConfig = await u.db("t_videoConfig").where({ id }).first(); - - res.status(200).send(success({ - message: "更新视频配置成功", - data: { - id: updatedConfig.id, - scriptId: updatedConfig.scriptId, - projectId: updatedConfig.projectId, - manufacturer: updatedConfig.manufacturer, - mode: updatedConfig.mode, - startFrame: updatedConfig.startFrame ? JSON.parse(updatedConfig.startFrame) : null, - endFrame: updatedConfig.endFrame ? JSON.parse(updatedConfig.endFrame) : null, - images: updatedConfig.images ? JSON.parse(updatedConfig.images) : [], - resolution: updatedConfig.resolution, - duration: updatedConfig.duration, - prompt: updatedConfig.prompt, - selectedResultId: updatedConfig.selectedResultId, - createdAt: new Date(updatedConfig.createTime).toISOString(), - } - })); + if (updatedConfig) { + res.status(200).send( + success({ + message: "更新视频配置成功", + data: { + id: updatedConfig.id, + scriptId: updatedConfig.scriptId, + projectId: updatedConfig.projectId, + manufacturer: updatedConfig.manufacturer, + mode: updatedConfig.mode, + startFrame: updatedConfig.startFrame ? JSON.parse(updatedConfig.startFrame) : null, + endFrame: updatedConfig.endFrame ? JSON.parse(updatedConfig.endFrame) : null, + images: updatedConfig.images ? JSON.parse(updatedConfig.images) : [], + resolution: updatedConfig.resolution, + duration: updatedConfig.duration, + prompt: updatedConfig.prompt, + selectedResultId: updatedConfig.selectedResultId, + createdAt: new Date(updatedConfig.createTime!).toISOString(), + audioEnabled: updatedConfig.audioEnabled, + }, + }), + ); + } else { + res.status(200).send(error("更新配置失败")); + } }, ); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 4bc0401..67c3c9c 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,6 +1,26 @@ -// @db-hash b6b4d8cdc25a2f4d60f1c239cd7e7060 +// @db-hash 5a1cbe86324cb073c1931fc53c56725f //该文件由脚本自动生成,请勿手动修改 +export interface _t_video_old_20260210 { + 'aiConfigId'?: number | null; + 'configId'?: number | null; + 'filePath'?: string | null; + 'firstFrame'?: string | null; + 'id'?: number; + 'model'?: string | null; + 'prompt'?: string | null; + 'resolution'?: string | null; + 'scriptId'?: number | null; + 'state'?: number | null; + 'storyboardImgs'?: string | null; + 'time'?: number | null; +} +export interface t_aiModelMap { + 'configId'?: number | null; + 'id'?: number; + 'key'?: string | null; + 'name'?: string | null; +} export interface t_assets { 'duration'?: string | null; 'episode'?: string | null; @@ -30,10 +50,9 @@ export interface t_config { 'baseUrl'?: string | null; 'createTime'?: number | null; 'id'?: number; - 'index'?: number | null; 'manufacturer'?: string | null; 'model'?: string | null; - 'name'?: string | null; + 'modelType'?: string | null; 'type'?: string | null; 'userId'?: number | null; } @@ -118,7 +137,9 @@ export interface t_user { 'password'?: string | null; } export interface t_video { + 'aiConfigId'?: number | null; 'configId'?: number | null; + 'errorReason'?: string | null; 'filePath'?: string | null; 'firstFrame'?: string | null; 'id'?: number; @@ -131,9 +152,12 @@ export interface t_video { 'time'?: number | null; } export interface t_videoConfig { + 'aiConfigId'?: number | null; + 'audioEnabled'?: number | null; 'createTime'?: number | null; 'duration'?: number | null; 'endFrame'?: string | null; + 'errorReason'?: string | null; 'id'?: number; 'images'?: string | null; 'manufacturer'?: string | null; @@ -148,6 +172,8 @@ export interface t_videoConfig { } export interface DB { + "_t_video_old_20260210": _t_video_old_20260210; + "t_aiModelMap": t_aiModelMap; "t_assets": t_assets; "t_chatHistory": t_chatHistory; "t_config": t_config; diff --git a/src/utils.ts b/src/utils.ts index e9f14c6..d813499 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,18 +6,28 @@ import number2Chinese from "@/utils/number2Chinese"; import deleteOutline from "@/utils/deleteOutline"; import getConfig from "./utils/getConfig"; import { v4 as uuid } from "uuid"; +import error from "@/utils/error"; +import * as imageTools from "@/utils/imageTools"; -import AIText from "@/utils/ai/text"; +import AIText from "@/utils/ai/text/index"; +import AIImage from "@/utils/ai/image/index"; +import AIVideo from "@/utils/ai/video/index"; +import getPromptAi from "./utils/getPromptAi"; export default { db, oss, ai: { text: AIText, + image: AIImage, + video: AIVideo, }, editImage, number2Chinese, deleteOutline, getConfig, uuid, + error, + imageTools, + getPromptAi, }; diff --git a/src/utils/ai.ts b/src/utils/ai/generateVideo.ts similarity index 69% rename from src/utils/ai.ts rename to src/utils/ai/generateVideo.ts index a0488aa..6ace39d 100644 --- a/src/utils/ai.ts +++ b/src/utils/ai/generateVideo.ts @@ -1,31 +1,28 @@ import axios from "axios"; import u from "@/utils"; import FormData from "form-data"; -import axiosRetry from "axios-retry"; -import { OpenAIChatModel, type OpenAIChatModelOptions } from "@aigne/openai"; import sharp from "sharp"; -axiosRetry(axios, { retries: 3, retryDelay: () => 200 }); - -export const text = async (config: OpenAIChatModelOptions = {}) => { - const { model, apiKey, baseURL } = await u.getConfig("language"); - return new OpenAIChatModel({ - apiKey: apiKey ?? "", - baseURL: baseURL ?? "", - model: model ?? "gpt-4.1", - modelOptions: { temperature: 0.7 }, - ...config, - }); -}; - -interface ImageConfig { - systemPrompt?: string; +type VideoAspectRatio = "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9" | "adaptive"; +interface BaseVideoConfig { prompt: string; - imageBase64: string[]; - size: "1K" | "2K" | "4K"; - aspectRatio: string; - resType?: "url" | "b64"; + savePath: string; + imageBase64?: string[]; // 单张参考图片 base64 } +interface DoubaoVideoConfig extends BaseVideoConfig { + duration: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; // 支持 2~12 秒 + aspectRatio: VideoAspectRatio; + audio?: boolean; +} +interface RunninghubVideoConfig extends BaseVideoConfig { + duration: 10 | 15; // 仅支持 10 或 15 秒 + aspectRatio: "16:9" | "9:16" | "1:1"; // 仅支持这三种比例 +} +interface OpenAIVideoConfig extends BaseVideoConfig { + duration: 10 | 15; // 仅支持 10 或 15 秒 + aspectRatio: Exclude; // 不支持 adaptive +} +type VideoConfig = DoubaoVideoConfig | RunninghubVideoConfig | OpenAIVideoConfig; const urlToBase64 = async (url: string): Promise => { const res = await axios.get(url, { responseType: "arraybuffer" }); @@ -110,127 +107,6 @@ const uploadBase64ToRunninghub = async (base64Image: string, apiKey: string, bas } }; -const generators = { - volcengine: async (config: ImageConfig, apiKey: string, baseURL: string, model: string) => { - if (config.size == "1K") config.size = "2K"; - apiKey = apiKey.replace("Bearer ", ""); - const body: Record = { - model, - prompt: config.prompt, - size: config.size, - response_format: "url", - sequential_image_generation: "disabled", - stream: false, - watermark: false, - }; - // 图生图:存在图片时添加 image 字段 - if (config.imageBase64) { - body.image = config.imageBase64; - } - const res = await axios.post(`https://ark.cn-beijing.volces.com/api/v3/images/generations`, body, { - headers: { Authorization: `Bearer ${apiKey}` }, - }); - return res.data.data[0].url; - }, - - gemini: async (config: ImageConfig, apiKey: string, baseURL: string, model: string) => { - apiKey = apiKey.replace("Bearer ", ""); - const messages = [ - ...(config.systemPrompt ? [{ role: "system", content: config.systemPrompt }] : []), - { role: "user", content: config.prompt }, - ...config.imageBase64.map((img) => ({ role: "user", content: { image: img } })), - ]; - const res = await axios.post( - `${baseURL}/chat/completions`, - { model, stream: false, messages, extra_body: { google: { image_config: { aspect_ratio: config.aspectRatio, image_size: config.size } } } }, - { headers: { Authorization: "Bearer " + apiKey } }, - ); - - return res.data.choices[0].message.content; - }, - - runninghub: async (config: ImageConfig, apiKey: string, baseURL: string) => { - apiKey = apiKey.replace("Bearer ", ""); - const imageUrls = await Promise.all(config.imageBase64.map((base64Image) => uploadBase64ToRunninghub(base64Image, apiKey, baseURL))); - - const endpoint = config.imageBase64.length === 0 ? "/openapi/v2/rhart-image-n-pro/text-to-image" : "/openapi/v2/rhart-image-n-pro/edit"; - const taskRes = await axios.post( - `https://www.runninghub.cn${endpoint}`, - { prompt: config.prompt, resolution: config.size, aspectRatio: config.aspectRatio, ...(imageUrls.length > 0 && { imageUrls }) }, - { headers: { Authorization: "Bearer " + apiKey } }, - ); - const taskId = taskRes.data.taskId; - if (!taskId) throw new Error(`任务创建失败,${JSON.stringify(taskRes.data)}`); - - return pollTask(async () => { - const res = await axios.post(`https://www.runninghub.cn/task/openapi/outputs`, { taskId, apiKey: apiKey }); - const { code, msg, data } = res.data; - if (code === 0 && msg === "success") return { completed: true, imageUrl: data?.[0]?.fileUrl }; - if (code === 804 || code === 813) return { completed: false }; - if (code === 805) return { completed: false, error: `任务失败: ${data?.[0]?.failedReason?.exception_message || "未知原因"}` }; - return { completed: false, error: `未知状态: code=${code}, msg=${msg}` }; - }); - }, - - apimart: async (config: ImageConfig, apiKey: string, baseURL: string, model: string) => { - apiKey = apiKey.replace("Bearer ", ""); - const taskRes = await axios.post( - `https://api.apimart.ai/v1/images/generations`, - { model: "gemini-3-pro-image-preview", prompt: config.prompt, size: config.aspectRatio, n: 1, resolution: config.size }, - { headers: { Authorization: apiKey } }, - ); - - if (taskRes.data.code !== 200 || !taskRes.data.data?.[0]?.task_id) throw new Error("任务创建失败: " + JSON.stringify(taskRes.data)); - - const taskId = taskRes.data.data[0].task_id; - return pollTask(async () => { - const res = await axios.get(`https://api.apimart.ai/v1/tasks/${taskId}`, { headers: { Authorization: apiKey }, params: { language: "en" } }); - if (res.data.code !== 200) return { completed: false, error: `查询失败: ${JSON.stringify(res.data)}` }; - const { status, result } = res.data.data; - if (status === "completed") return { completed: true, imageUrl: result?.images?.[0]?.url?.[0] }; - if (status === "failed" || status === "cancelled") return { completed: false, error: `任务${status}` }; - return { completed: false }; - }); - }, -}; - -export const generateImage = async (config: ImageConfig, replaceConfig?: Awaited>>): Promise => { - let { model, apiKey, baseURL, manufacturer } = await u.getConfig("image"); - if (replaceConfig) { - model = replaceConfig.model || model; - apiKey = replaceConfig.apiKey || apiKey; - baseURL = replaceConfig.baseURL || baseURL; - manufacturer = replaceConfig.manufacturer || manufacturer; - } - const generator = generators[manufacturer as keyof typeof generators]; - if (!generator) throw new Error(`不支持的厂商: ${manufacturer}`); - - let imageUrl = await generator(config, apiKey ?? "", baseURL ?? "", model ?? ""); - if (!config.resType) config.resType = "b64"; - if (config.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl); - return imageUrl; -}; - -type VideoAspectRatio = "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9" | "adaptive"; -interface BaseVideoConfig { - prompt: string; - savePath: string; - imageBase64?: string[]; // 单张参考图片 base64 -} -interface DoubaoVideoConfig extends BaseVideoConfig { - duration: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; // 支持 2~12 秒 - aspectRatio: VideoAspectRatio; - audio?: boolean; -} -interface RunninghubVideoConfig extends BaseVideoConfig { - duration: 10 | 15; // 仅支持 10 或 15 秒 - aspectRatio: "16:9" | "9:16" | "1:1"; // 仅支持这三种比例 -} -interface OpenAIVideoConfig extends BaseVideoConfig { - duration: 10 | 15; // 仅支持 10 或 15 秒 - aspectRatio: Exclude; // 不支持 adaptive -} -type VideoConfig = DoubaoVideoConfig | RunninghubVideoConfig | OpenAIVideoConfig; const generateVideoWithConfig = async (config: VideoConfig, configItem: { model: string; apiKey: string; baseURL: string; manufacturer: string }) => { const { apiKey, baseURL, manufacturer, model } = configItem; const imageArrPath = []; @@ -527,34 +403,35 @@ const generateVideoWithConfig = async (config: VideoConfig, configItem: { model: } return videoUrl; }; -export const generateVideo = async (config: VideoConfig, manufacturer: string) => { + +export default async (config: VideoConfig, manufacturer: string) => { if (!config.imageBase64 || config.imageBase64.length <= 0) throw new Error("未传图片"); - const configList = await u.getConfig("video", manufacturer); - if (!configList || configList.length === 0) { + const configItem = await u.getConfig("video", manufacturer); + if (!configItem) { throw new Error("未找到任何视频配置"); } let lastError: Error | null = null; - for (const configItem of configList) { - // 每个配置项重试1次,共2次尝试 - for (let attempt = 0; attempt < 2; attempt++) { - try { - const videoUrl = await generateVideoWithConfig(config, configItem); - if (videoUrl) { - const response = await axios.get(videoUrl, { responseType: "stream" }); - await u.oss.writeFile(config.savePath, response.data); - return config.savePath; - } - return videoUrl; - } catch (error: any) { - lastError = error as Error; - console.warn(`配置 ${configItem.model} 第 ${attempt + 1} 次尝试失败:`, error?.response?.data || error.message); - // 如果是第一次尝试失败,继续重试 - if (attempt === 0) continue; - // 第二次也失败了,跳到下一个配置项 - break; + // for (const configItem of configList) { + // 每个配置项重试1次,共2次尝试 + for (let attempt = 0; attempt < 2; attempt++) { + try { + const videoUrl = await generateVideoWithConfig(config, configItem); + if (videoUrl) { + const response = await axios.get(videoUrl, { responseType: "stream" }); + await u.oss.writeFile(config.savePath, response.data); + return config.savePath; } + return videoUrl; + } catch (error: any) { + lastError = error as Error; + console.warn(`配置 ${configItem.model} 第 ${attempt + 1} 次尝试失败:`, error?.response?.data || error.message); + // 如果是第一次尝试失败,继续重试 + if (attempt === 0) continue; + // 第二次也失败了,跳到下一个配置项 + break; } } + // } // 所有配置都失败了 throw new Error(`所有视频配置都失败了。最后一次错误: ${lastError?.message || "未知错误"}`); }; diff --git a/src/utils/ai/image/index.ts b/src/utils/ai/image/index.ts new file mode 100644 index 0000000..3054ebd --- /dev/null +++ b/src/utils/ai/image/index.ts @@ -0,0 +1,72 @@ +import "./type"; +import u from "@/utils"; +import modelList from "./modelList"; +import axios from "axios"; + +import volcengine from "./owned/volcengine"; +import kling from "./owned/kling"; +import vidu from "./owned/vidu"; +import runninghub from "./owned/runninghub"; +import apimart from "./owned/apimart"; +import other from "./owned/other"; +import gemini from "./owned/gemini"; + +const urlToBase64 = async (url: string): Promise => { + const res = await axios.get(url, { responseType: "arraybuffer" }); + const base64 = Buffer.from(res.data).toString("base64"); + const mimeType = res.headers["content-type"] || "image/png"; + return `data:${mimeType};base64,${base64}`; +}; + +const modelInstance = { + gemini: gemini, + volcengine: volcengine, + kling: kling, + vidu: vidu, + runninghub: runninghub, + // apimart: apimart, + other, +} as const; + +export default async (input: ImageConfig, config: AIConfig) => { + console.log("%c Line:32 🥪 config", "background:#33a5ff", config); + const { model, apiKey, baseURL, manufacturer } = { ...config }; + if (!config || !config?.model || !config?.apiKey || !config?.manufacturer) throw new Error("请检查模型配置是否正确"); + + const manufacturerFn = modelInstance[manufacturer as keyof typeof modelInstance]; + if (!manufacturerFn) if (!manufacturerFn) throw new Error("不支持的图片厂商"); + if (manufacturer !== "other") { + const owned = modelList.find((m) => m.model === model); + if (!owned) throw new Error("不支持的模型"); + } + + // 补充图片的 base64 内容类型字符串 + if (input.imageBase64 && input.imageBase64.length > 0) { + input.imageBase64 = input.imageBase64.map((img) => { + if (img.startsWith("data:image/")) { + return img; + } + // 根据 base64 头部判断图片类型 + if (img.startsWith("/9j/")) { + return `data:image/jpeg;base64,${img}`; + } + if (img.startsWith("iVBORw")) { + return `data:image/png;base64,${img}`; + } + if (img.startsWith("R0lGOD")) { + return `data:image/gif;base64,${img}`; + } + if (img.startsWith("UklGR")) { + return `data:image/webp;base64,${img}`; + } + // 默认使用 png + return `data:image/png;base64,${img}`; + }); + } + + let imageUrl = await manufacturerFn(input, { model, apiKey, baseURL }); + console.log("%c Line:68 🍷 imageUrl", "background:#4fff4B", imageUrl); + if (!input.resType) input.resType = "b64"; + if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl); + return imageUrl; +}; diff --git a/src/utils/ai/image/modelList.ts b/src/utils/ai/image/modelList.ts new file mode 100644 index 0000000..1cce6d9 --- /dev/null +++ b/src/utils/ai/image/modelList.ts @@ -0,0 +1,71 @@ +interface Owned { + manufacturer: string; + model: string; + grid: boolean; + type: "t2i" | "ti2i" | "i2i"; +} + +const modelList: Owned[] = [ + // 火山引擎 + { + manufacturer: "volcengine", + model: "doubao-seedream-4-5-251128", + grid: false, + type: "ti2i", + }, + { + manufacturer: "volcengine", + model: "doubao-seedream-4-0-250828", + grid: false, + type: "ti2i", + }, + //可灵 + { + manufacturer: "kling", + model: "kling-image-o1", + grid: false, + type: "ti2i", + }, + //gemini + { + manufacturer: "gemini", + model: "gemini-2.5-flash-image", + grid: true, + type: "ti2i", + }, + { + manufacturer: "gemini", + model: "gemini-3-pro-image-preview", + grid: true, + type: "ti2i", + }, + //Vidu + { + manufacturer: "vidu", + model: "viduq1", + grid: false, + type: "i2i", + }, + { + manufacturer: "vidu", + model: "viduq2", + grid: false, + type: "ti2i", + }, + //RunningHub + { + manufacturer: "runninghub", + model: "nanobanana", + grid: true, + type: "ti2i", + }, + //ApiMart + { + manufacturer: "apimart", + model: "nanobanana", + grid: true, + type: "ti2i", + }, +]; + +export default modelList; diff --git a/src/utils/ai/image/owned/apimart.ts b/src/utils/ai/image/owned/apimart.ts new file mode 100644 index 0000000..eab1174 --- /dev/null +++ b/src/utils/ai/image/owned/apimart.ts @@ -0,0 +1,25 @@ +import axios from "axios"; +import { pollTask } from "@/utils/ai/utils"; + + +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.apiKey) throw new Error("缺少API Key"); + const apiKey = config.apiKey.replace("Bearer ", ""); + const taskRes = await axios.post( + `https://api.apimart.ai/v1/images/generations`, + { model: "gemini-3-pro-image-preview", prompt: input.prompt, size: input.aspectRatio, n: 1, resolution: input.size }, + { headers: { Authorization: apiKey } }, + ); + + if (taskRes.data.code !== 200 || !taskRes.data.data?.[0]?.task_id) throw new Error("任务创建失败: " + JSON.stringify(taskRes.data)); + + const taskId = taskRes.data.data[0].task_id; + return pollTask(async () => { + const res = await axios.get(`https://api.apimart.ai/v1/tasks/${taskId}`, { headers: { Authorization: apiKey }, params: { language: "en" } }); + if (res.data.code !== 200) return { completed: false, error: `查询失败: ${JSON.stringify(res.data)}` }; + const { status, result } = res.data.data; + if (status === "completed") return { completed: true, url: result?.images?.[0]?.url?.[0] }; + if (status === "failed" || status === "cancelled") return { completed: false, error: `任务${status}` }; + return { completed: false }; + }); +}; diff --git a/src/utils/ai/image/owned/gemini.ts b/src/utils/ai/image/owned/gemini.ts new file mode 100644 index 0000000..3f7c34b --- /dev/null +++ b/src/utils/ai/image/owned/gemini.ts @@ -0,0 +1,58 @@ +import "../type"; +import { createGoogleGenerativeAI } from "@ai-sdk/google"; +import { generateText, ModelMessage } from "ai"; + +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.model) throw new Error("缺少Model名称"); + if (!config.apiKey) throw new Error("缺少API Key"); + if (!input.prompt) throw new Error("缺少提示词"); + + const options: any = {}; + if (config.apiKey) options.apiKey = config.apiKey; + if (config?.baseURL) options.baseURL = config.baseURL; + const google = createGoogleGenerativeAI({ + ...options, + }); + + // 构建完整的提示词 + const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt; + let promptData: ModelMessage[] | string = []; + if (input.imageBase64 && input.imageBase64.length) { + promptData = [{ role: "system", content: fullPrompt + `请直接输出图片` }]; + promptData.push({ + role: "user", + content: input.imageBase64.map((i) => ({ + type: "image", + image: i, + })), + }); + } else { + promptData = fullPrompt + `\n请直接输出图片`; + } + + const result = await generateText({ + model: google.languageModel(config.model), + prompt: promptData, + providerOptions: { + google: { + imageConfig: { + ...(config.model == "gemini-2.5-flash-image" + ? { aspectRatio: input.aspectRatio } + : { aspectRatio: input.aspectRatio, imageSize: input.size }), + }, + }, + }, + timeout: 60000, + }); + + if (!result.files.length) { + console.error(JSON.stringify(result.response, null, 2)); + throw new Error("图片生成失败"); + } + let imageBase64; + for (const item of result.files) { + imageBase64 = `data:${item.mediaType};base64,${item.base64}`; + } + // 返回生成的图片 base64 + return imageBase64!; +}; diff --git a/src/utils/ai/image/owned/kling.ts b/src/utils/ai/image/owned/kling.ts new file mode 100644 index 0000000..1f930f4 --- /dev/null +++ b/src/utils/ai/image/owned/kling.ts @@ -0,0 +1,107 @@ +import "../type"; +import axios from "axios"; +import jwt from "jsonwebtoken"; +import u from "@/utils"; +import { pollTask } from "@/utils/ai/utils"; + +function generateJwtToken(ak: string, sk: string): string { + const now = Math.floor(Date.now() / 1000); + const payload = { + iss: ak, + exp: now + 1800, + nbf: now - 5, + }; + return jwt.sign(payload, sk, { + algorithm: "HS256", + header: { alg: "HS256", typ: "JWT" }, + }); +} + +function getApiToken(apiKey: string): string { + const trimmedKey = apiKey.replace(/^Bearer\s+/i, "").trim(); + + if (trimmedKey.includes("|")) { + const parts = trimmedKey.split("|"); + if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) { + throw new Error("API Key格式错误,请使用 ak|sk 格式"); + } + return generateJwtToken(parts[0].trim(), parts[1].trim()); + } + + return trimmedKey; +} + +async function processImages(imageBase64: string[]): Promise> { + let images = imageBase64.filter((img) => img?.trim()); + if (images.length === 0) return []; + + // 压缩所有图片到10MB以内 + images = await Promise.all(images.map((img) => u.imageTools.compressImage(img, "10mb"))); + + // 参考主体数量和参考图片数量之和不得超过10 + if (images.length > 10) { + const mergeImageList = images.splice(9); + const mergedImage = await u.imageTools.mergeImages(mergeImageList, "10mb"); + images.push(mergedImage); + } + + return images.map((img) => ({ + image: img.replace(/^data:image\/[a-z]+;base64,/i, ""), + })); +} + +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.apiKey) throw new Error("缺少API Key"); + if (!input.prompt) throw new Error("缺少提示词,prompt为必填项"); + + const authorization = `Bearer ${getApiToken(config.apiKey)}`; + const baseURL = (config.baseURL ?? "https://api-beijing.klingai.com/v1/images/omni-image").replace(/\/+$/, ""); + const imageList = await processImages(input.imageBase64); + + const body: Record = { + model_name: config.model || "kling-image-o1", + prompt: input.prompt, + n: 1, + ...(input.size !== "4K" && { resolution: input.size.toLowerCase() }), + ...(imageList.length > 0 && { image_list: imageList }), + }; + + const headers = { + "Content-Type": "application/json", + Authorization: authorization, + }; + + try { + const { data: createData } = await axios.post(baseURL, body, { headers }); + + if (createData.code !== 0) { + throw new Error(createData.message || "创建任务失败"); + } + + const taskId = createData.data?.task_id; + if (!taskId) throw new Error("未获取到任务ID"); + + const queryUrl = `${baseURL}/${taskId}`; + return await pollTask(async () => { + const { data: queryData } = await axios.get(queryUrl, { headers }); + + if (queryData.code !== 0) { + return { completed: false, error: queryData.message || "查询任务失败" }; + } + + const { task_status, task_status_msg, task_result } = queryData.data || {}; + + if (task_status === "failed") { + return { completed: false, error: task_status_msg || "图片生成失败" }; + } + + if (task_status === "succeed") { + return { completed: true, url: task_result?.images?.[0]?.url }; + } + + return { completed: false }; + }); + } catch (error) { + throw new Error(u.error(error).message || "可灵图片生成失败"); + } +} diff --git a/src/utils/ai/image/owned/other.ts b/src/utils/ai/image/owned/other.ts new file mode 100644 index 0000000..b0158e1 --- /dev/null +++ b/src/utils/ai/image/owned/other.ts @@ -0,0 +1,115 @@ +import "../type"; +import { generateImage, generateText, ModelMessage } from "ai"; +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; +import axios from "axios"; + +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.model) throw new Error("缺少Model名称"); + if (!config.apiKey) throw new Error("缺少API Key"); + if (!config.baseURL) throw new Error("缺少baseUrl"); + + const apiKey = config.apiKey.replace("Bearer ", ""); + + const otherProvider = createOpenAICompatible({ + name: "xixixi", + baseURL: config.baseURL, + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + // 根据 size 配置映射到具体尺寸 + const sizeMap: Record = { + "1K": "1024x1024", + "2K": "2048x2048", + "4K": "4096x4096", + }; + // 构建完整的提示词 + const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt; + const model = config.model; + if (model.includes("gemini") || model.includes("nano")) { + let promptData; + if (input.imageBase64 && input.imageBase64.length) { + promptData = [{ role: "system", content: fullPrompt + `请直接输出图片` }]; + (promptData as ModelMessage[]).push({ + role: "user", + content: input.imageBase64.map((i) => ({ + type: "image", + image: i, + })), + }); + } else { + promptData = fullPrompt + `请直接输出图片`; + } + + const result = await generateText({ + model: otherProvider.languageModel(model), + prompt: promptData as string | ModelMessage[], + providerOptions: { + google: { + imageConfig: { + ...(config.model == "gemini-2.5-flash-image" + ? { aspectRatio: input.aspectRatio } + : { aspectRatio: input.aspectRatio, imageSize: input.size }), + }, + responseModalities: ["IMAGE"], + }, + }, + }); + + if (result.files && result.files.length) { + let imageBase64; + for (const item of result.files) { + imageBase64 = `data:${item.mediaType};base64,${item.base64}`; + } + // 返回生成的图片 base64 + return imageBase64!; + } else { + if (!result.text) { + console.error(JSON.stringify(result.response, null, 2)); + throw new Error("图片生成失败"); + } + const mdMatch = result.text.match(/^!\[.*?\]\((.+?)\)$/); + if (mdMatch) { + const imgInfo = mdMatch[1]; + const base64InMd = imgInfo.match(/data:image\/[a-z]+;base64,(.+)/); + if (base64InMd) { + return imgInfo; + } else { + return await urlToBase64(imgInfo); + } + } + const base64Match = result.text.match(/base64,([A-Za-z0-9+/=]+)/); + + if (base64Match) { + return "data:image/jpeg;base64," + base64Match[1]; + } + // 检查是否为图片直链 url + if (/^https?:\/\/.*\.(png|jpg|jpeg|gif|webp|bmp)$/i.test(result.text)) { + return await urlToBase64(result.text); + } + + // 默认情况 + return result.text; + } + } else { + const { image } = await generateImage({ + model: otherProvider.imageModel(model), + prompt: + input.imageBase64 && input.imageBase64.length + ? { text: fullPrompt + `请直接输出图片`, images: input.imageBase64 } + : fullPrompt + `请直接输出图片`, + aspectRatio: input.aspectRatio as "1:1" | "3:4" | "4:3" | "9:16" | "16:9", + size: sizeMap[input.size] ?? "1024x1024", + }); + + return image.base64; + } +}; + +async function urlToBase64(url: string): Promise { + const res = await axios.get(url, { responseType: "arraybuffer" }); + const base64 = Buffer.from(res.data).toString("base64"); + const mimeType = res.headers["content-type"] || "image/png"; + return `data:${mimeType};base64,${base64}`; +} diff --git a/src/utils/ai/image/owned/runninghub.ts b/src/utils/ai/image/owned/runninghub.ts new file mode 100644 index 0000000..79a00d5 --- /dev/null +++ b/src/utils/ai/image/owned/runninghub.ts @@ -0,0 +1,89 @@ +import axios from "axios"; +import FormData from "form-data"; +import sharp from "sharp"; +import { pollTask } from "@/utils/ai/utils"; + +// 上传 base64 图片到 runninghub +const uploadBase64ToRunninghub = async (base64Image: string, apiKey: string, baseURL: string): Promise => { + try { + apiKey = apiKey.replace("Bearer ", ""); + // 移除 base64 前缀 + const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, ""); + let buffer = Buffer.from(base64Data, "base64"); + + // 压缩图片到 7MB 以下 + const MAX_SIZE = 7 * 1024 * 1024; // 7MB + if (buffer.length > MAX_SIZE) { + let quality = 90; + + while (buffer.length > MAX_SIZE && quality > 10) { + const compressed = await sharp(buffer).jpeg({ quality, mozjpeg: true }).toBuffer(); + buffer = Buffer.from(compressed); + quality -= 10; + } + + // 如果仍然超过限制,进一步调整尺寸 + if (buffer.length > MAX_SIZE) { + const metadata = await sharp(buffer).metadata(); + const scale = Math.sqrt(MAX_SIZE / buffer.length); + + const resized = await sharp(buffer) + .resize({ + width: Math.floor((metadata.width || 1920) * scale), + height: Math.floor((metadata.height || 1080) * scale), + fit: "inside", + }) + .jpeg({ quality: 80, mozjpeg: true }) + .toBuffer(); + + buffer = Buffer.from(resized); + } + } + + // 创建 FormData + const formData = new FormData(); + formData.append("file", buffer, { + filename: "image.jpg", + contentType: "image/jpeg", + }); + + // 上传图片 + const uploadRes = await axios.post(`https://www.runninghub.cn/openapi/v2/media/upload/binary`, formData, { + headers: { Authorization: `Bearer ${apiKey}` }, + }); + + if (uploadRes.data.code !== 0 || !uploadRes.data.data?.download_url) { + throw new Error(`图片上传失败: ${JSON.stringify(uploadRes.data)}`); + } + + return uploadRes.data.data.download_url; + } catch (error) { + console.error("上传图片时发生错误:", error); + throw error; + } +}; + +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.apiKey) throw new Error("缺少API Key"); + const apiKey = config.apiKey.replace("Bearer ", ""); + const baseURL = "https://www.runninghub.cn"; + const imageUrls = await Promise.all(input.imageBase64.map((base64Image) => uploadBase64ToRunninghub(base64Image, apiKey, baseURL))); + + const endpoint = input.imageBase64.length === 0 ? "/openapi/v2/rhart-image-n-pro/text-to-image" : "/openapi/v2/rhart-image-n-pro/edit"; + const taskRes = await axios.post( + `https://www.runninghub.cn${endpoint}`, + { prompt: input.prompt, resolution: input.size, aspectRatio: input.aspectRatio, ...(imageUrls.length > 0 && { imageUrls }) }, + { headers: { Authorization: "Bearer " + apiKey } }, + ); + const taskId = taskRes.data.taskId; + if (!taskId) throw new Error(`任务创建失败,${JSON.stringify(taskRes.data)}`); + + return pollTask(async () => { + const res = await axios.post(`https://www.runninghub.cn/task/openapi/outputs`, { taskId, apiKey: apiKey }); + const { code, msg, data } = res.data; + if (code === 0 && msg === "success") return { completed: true, url: data?.[0]?.fileUrl }; + if (code === 804 || code === 813) return { completed: false }; + if (code === 805) return { completed: false, error: `任务失败: ${data?.[0]?.failedReason?.exception_message || "未知原因"}` }; + return { completed: false, error: `未知状态: code=${code}, msg=${msg}` }; + }); +}; diff --git a/src/utils/ai/image/owned/vidu.ts b/src/utils/ai/image/owned/vidu.ts new file mode 100644 index 0000000..50ea704 --- /dev/null +++ b/src/utils/ai/image/owned/vidu.ts @@ -0,0 +1,88 @@ +import "../type"; +import axios from "axios"; +import u from "@/utils"; +import { pollTask } from "@/utils/ai/utils"; +function getApiUrl(apiUrl: string) { + if (apiUrl.includes("|")) { + const parts = apiUrl.split("|"); + if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) { + throw new Error("url 格式错误,请使用 url1|url2 格式"); + } + return { requestUrl: parts[0].trim(), queryUrl: parts[1].trim() }; + } + throw new Error("请填写正确的url"); +} +function template(replaceObj: Record, url: string) { + return url.replace(/\{(\w+)\}/g, (match, varName) => { + return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match; + }); +} +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.model) throw new Error("缺少Model名称"); + if (!config.apiKey) throw new Error("缺少API Key"); + const apiKey = "Token " + config.apiKey.replace(/Token\s+/g, "").trim(); + const viduq2Ratio = ["16:9", "9:16", "1:1", "3:4", "4:3", "21:9", "2:3", "3:2"]; + const viduq1Ratio = ["16:9", "9:16", "1:1", "3:4", "4:3"]; + let images: string[] = []; + const baseImages = input.imageBase64; + // 如果图片总数大于7,合并第7张及以后的图片 + if (baseImages) { + if (baseImages.length > 7) { + // 前6张原图 + images = baseImages.slice(0, 6); + // 第7张及以后的图片进行合并 + const mergeImageList = baseImages.slice(6); // 注意此处使用slice,不会改变原数组 + const mergedImage = await u.imageTools.mergeImages(mergeImageList, "10mb"); + images.push(mergedImage); + } else { + // 不足7张,直接全部加入 + images = baseImages; + } + } + + let size = "1080p"; + if (config.model == "viduq1") { + if (!images.length) throw new Error(`viduq1 进行图片生成必须传入一张图片`); + if (!viduq1Ratio.includes(input.aspectRatio)) throw new Error("不支持的图片比例:" + input.aspectRatio); + size = "1080p"; + } else { + if (input.size == "1K") size = "1080p"; + else size = input.size; + if (!viduq2Ratio.includes(input.aspectRatio)) throw new Error("不支持的图片比例:" + input.aspectRatio); + } + + const body: Record = { + model: config.model, + prompt: input.prompt, + aspect_ratio: input.aspectRatio, + resolution: size, + ...(images.length && { images: images }), + }; + + const urlObj = getApiUrl(config.baseURL! ?? "https://api.vidu.cn/ent/v2/reference2image|https://api.vidu.cn/ent/v2/tasks/{id}/creations"); + + try { + const { data } = await axios.post(urlObj.requestUrl, body, { headers: { Authorization: apiKey } }); + + const queryUrl = template({ id: data.task_id }, urlObj.queryUrl); + + return await pollTask(async () => { + const { data: queryData } = await axios.get(queryUrl, { headers: { Authorization: apiKey } }); + + const { state, err_code, creations } = queryData || {}; + + if (state === "failed") { + return { completed: false, error: err_code || "图片生成失败" }; + } + + if (state === "success") { + return { completed: true, url: creations?.[0]?.url }; + } + + return { completed: false }; + }); + } catch (error: any) { + const msg = u.error(error).message || "vidu 图片生成失败"; + throw new Error(msg); + } +}; diff --git a/src/utils/ai/image/owned/volcengine.ts b/src/utils/ai/image/owned/volcengine.ts new file mode 100644 index 0000000..2d93fdf --- /dev/null +++ b/src/utils/ai/image/owned/volcengine.ts @@ -0,0 +1,31 @@ +import "../type"; +import axios from "axios"; +import u from "@/utils"; + +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.model) throw new Error("缺少Model名称"); + if (!config.apiKey) throw new Error("缺少API Key"); + + const apiKey = "Bearer " + config.apiKey.replace(/Bearer\s+/g, "").trim(); + const size = input.size === "1K" ? "2K" : input.size; + + const body: Record = { + model: config.model, + prompt: input.prompt, + size, + response_format: "url", + sequential_image_generation: "disabled", + stream: false, + watermark: false, + ...(input.imageBase64 && { image: input.imageBase64 }), + }; + + const url = config.baseURL ?? "https://ark.cn-beijing.volces.com/api/v3/images/generations"; + try { + const { data } = await axios.post(url, body, { headers: { Authorization: apiKey } }); + return data.data[0]?.url; + } catch (error) { + const msg = u.error(error).message || "Volcengine 图片生成失败"; + throw new Error(msg); + } +} diff --git a/src/utils/ai/image/type.ts b/src/utils/ai/image/type.ts new file mode 100644 index 0000000..bbd7eec --- /dev/null +++ b/src/utils/ai/image/type.ts @@ -0,0 +1,14 @@ +interface ImageConfig { + systemPrompt?: string; + prompt: string; + imageBase64: string[]; + size: "1K" | "2K" | "4K"; + aspectRatio: string; + resType?: "url" | "b64"; +} + +interface AIConfig { + model?: string; + apiKey?: string; + baseURL?: string; +} \ No newline at end of file diff --git a/src/utils/ai/text.ts b/src/utils/ai/text/index.ts similarity index 68% rename from src/utils/ai/text.ts rename to src/utils/ai/text/index.ts index 29d4ead..bfc45d7 100644 --- a/src/utils/ai/text.ts +++ b/src/utils/ai/text/index.ts @@ -1,9 +1,11 @@ import u from "@/utils"; import { generateText, streamText, Output, stepCountIs, ModelMessage, LanguageModel, Tool, GenerateTextResult } from "ai"; +import { wrapLanguageModel } from "ai"; +import { devToolsMiddleware } from "@ai-sdk/devtools"; import { parse } from "best-effort-json-parser"; import modelList from "./modelList"; import { z } from "zod"; - +import { OpenAIProvider } from "@ai-sdk/openai"; interface AIInput | undefined = undefined> { system?: string; tools?: Record; @@ -17,20 +19,27 @@ interface AIConfig { model?: string; apiKey?: string; baseURL?: string; + manufacturer?: string; } -const buildOptions = async (input: AIInput, config: AIConfig) => { - const sqlTextModelConfig = await u.getConfig("text"); - const { model, apiKey, baseURL } = { ...sqlTextModelConfig, ...config }; - - const owned = modelList.find((m) => m.model === model); +const buildOptions = async (input: AIInput, config: AIConfig = {}) => { + if (!config || !config?.model || !config?.apiKey || !config?.manufacturer) throw new Error("请检查模型配置是否正确"); + const { model, apiKey, baseURL, manufacturer } = { ...config }; + let owned; + if (manufacturer == "other") { + owned = modelList.find((m) => m.manufacturer === manufacturer); + } else { + owned = modelList.find((m) => m.model === model); + } if (!owned) throw new Error("不支持的模型或厂商"); - const modelInstance = owned.instance({ apiKey, baseURL }); + const modelInstance = owned.instance({ apiKey, baseURL: baseURL!, name: "xixixi" }); const maxStep = input.maxStep ?? (input.tools ? Object.keys(input.tools).length * 5 : undefined); const outputBuilders: Record any> = { - schema: (s) => Output.object({ schema: z.object(s) }), + schema: (s) => { + return Output.object({ schema: z.object(s) }); + }, object: () => { const jsonSchemaPrompt = `\n请按照以下 JSON Schema 格式返回结果:\n${JSON.stringify( z.toJSONSchema(z.object(input.output)), @@ -42,11 +51,12 @@ const buildOptions = async (input: AIInput, config: AIConfig) => { }, }; - const output = input.output ? outputBuilders[owned.responseFormat]?.(input.output) ?? null : null; - + const output = input.output ? (outputBuilders[owned.responseFormat]?.(input.output) ?? null) : null; + const chatModelManufacturer = ["doubao", "other", "openai"]; + const modelFn = chatModelManufacturer.includes(owned.manufacturer) ? (modelInstance as OpenAIProvider).chat(model!) : modelInstance(model!); return { config: { - model: modelInstance(model) as LanguageModel, + model: modelFn as LanguageModel, ...(input.system && { system: input.system }), ...(input.prompt ? { prompt: input.prompt } : { messages: input.messages! }), ...(input.tools && owned.tool && { tools: input.tools }), @@ -64,7 +74,7 @@ const ai = Object.create({}) as { stream(input: AIInput, config?: AIConfig): Promise>; }; -ai.invoke = async (input: AIInput, config: AIConfig = {}) => { +ai.invoke = async (input: AIInput, config: AIConfig) => { const options = await buildOptions(input, config); const result = await generateText(options.config); if (options.responseFormat === "object" && input.output) { @@ -80,7 +90,7 @@ ai.invoke = async (input: AIInput, config: AIConfig = {}) => { return result; }; -ai.stream = async (input: AIInput, config: AIConfig = {}) => { +ai.stream = async (input: AIInput, config: AIConfig) => { const options = await buildOptions(input, config); return streamText(options.config); }; diff --git a/src/utils/ai/modelList.ts b/src/utils/ai/text/modelList.ts similarity index 87% rename from src/utils/ai/modelList.ts rename to src/utils/ai/text/modelList.ts index d56b7c5..421885a 100644 --- a/src/utils/ai/modelList.ts +++ b/src/utils/ai/text/modelList.ts @@ -4,6 +4,8 @@ import { createZhipu } from "zhipu-ai-provider"; import { createQwen } from "qwen-ai-provider"; import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { createAnthropic } from "@ai-sdk/anthropic"; +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; +import { createXai } from '@ai-sdk/xai'; interface Owned { manufacturer: string; @@ -18,7 +20,8 @@ interface Owned { | typeof createZhipu | typeof createQwen | typeof createGoogleGenerativeAI - | typeof createAnthropic; + | typeof createAnthropic + | typeof createOpenAICompatible; } const modelList: Owned[] = [ @@ -45,7 +48,7 @@ const modelList: Owned[] = [ // 豆包 { manufacturer: "doubao", - model: "doubao-seed-1-8", + model: "doubao-seed-1-8-251228", responseFormat: "schema", image: true, think: true, @@ -54,7 +57,7 @@ const modelList: Owned[] = [ }, { manufacturer: "doubao", - model: "doubao-seed-1-6", + model: "doubao-seed-1-6-251015", responseFormat: "schema", image: true, think: true, @@ -63,7 +66,7 @@ const modelList: Owned[] = [ }, { manufacturer: "doubao", - model: "doubao-seed-1-6-lite", + model: "doubao-seed-1-6-lite-251015", responseFormat: "schema", image: true, think: true, @@ -72,7 +75,7 @@ const modelList: Owned[] = [ }, { manufacturer: "doubao", - model: "doubao-seed-1-6-flash", + model: "doubao-seed-1-6-flash-250828", responseFormat: "schema", image: true, think: true, @@ -283,7 +286,7 @@ const modelList: Owned[] = [ // Gemini { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.5-pro", responseFormat: "schema", image: true, @@ -292,7 +295,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.5-flash", responseFormat: "schema", image: true, @@ -301,7 +304,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.0-flash", responseFormat: "schema", image: true, @@ -310,7 +313,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-2.0-flash-lite", responseFormat: "schema", image: true, @@ -319,7 +322,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-1.5-pro", responseFormat: "schema", image: true, @@ -328,7 +331,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "google", + manufacturer: "gemini", model: "gemini-1.5-flash", responseFormat: "schema", image: true, @@ -409,6 +412,44 @@ const modelList: Owned[] = [ instance: createAnthropic, tool: true, }, + //xai + { + manufacturer: "xai", + model: "grok-3", + responseFormat: "schema", + image: false, + think: false, + instance: createXai, + tool: true, + }, + { + manufacturer: "xai", + model: "grok-4", + responseFormat: "schema", + image: false, + think: false, + instance: createXai, + tool: true, + }, + { + manufacturer: "xai", + model: "grok-4.1", + responseFormat: "schema", + image: true, + think: false, + instance: createXai, + tool: true, + }, + //其他 + { + manufacturer: "other", + model: "gpt-4.1", + responseFormat: "schema", + image: true, + think: false, + instance: createOpenAI, + tool: true, + }, ]; export default modelList; diff --git a/src/utils/ai/utils.ts b/src/utils/ai/utils.ts new file mode 100644 index 0000000..d03aa2e --- /dev/null +++ b/src/utils/ai/utils.ts @@ -0,0 +1,82 @@ +import modelList from "./video/modelList"; + +interface ValidateResult { + owned: (typeof modelList)[number]; + images: string[]; + hasStartEndType: boolean; + hasTextType: boolean; +} + +/** + * 校验视频生成配置与模型是否匹配 + * @param input 视频配置 + * @param config AI配置 + * @param customOwned 自定义模型配置(如果传入则跳过模型查找) + */ +export const validateVideoConfig = (input: VideoConfig, config: AIConfig, customOwned?: (typeof modelList)[number]): ValidateResult => { + if (!config.model) throw new Error("缺少Model名称"); + const owned = customOwned ?? modelList.find((m) => m.model === config.model); + if (!owned) throw new Error(`不支持的模型: ${config.model}`); + const images = input.imageBase64 ?? []; + // 校验图片数量与模型类型是否匹配 + const hasTextType = owned.type.includes("text"); + const hasSingleImageType = owned.type.includes("singleImage"); + const hasStartEndType = owned.type.some((t) => ["startEndRequired", "endFrameOptional", "startFrameOptional"].includes(t)); + const hasMultiImageType = owned.type.includes("multiImage"); + const hasReferenceType = owned.type.includes("reference"); + if (images.length === 0 && !hasTextType) { + throw new Error(`模型 ${config.model} 不支持纯文本生成,需要提供图片`); + } + if (images.length === 1 && !hasSingleImageType && !hasStartEndType && !hasReferenceType) { + throw new Error(`模型 ${config.model} 不支持单图模式`); + } + if (images.length === 2 && !hasStartEndType) { + throw new Error(`模型 ${config.model} 不支持首尾帧模式`); + } + if (images.length > 2 && !hasMultiImageType) { + throw new Error(`模型 ${config.model} 不支持多图模式`); + } + // 校验duration和resolution是否在支持范围内 + const validDurationResolution = owned.durationResolutionMap.some((map) => { + const durationMatch = map.duration.includes(input.duration); + const resolutionMatch = + // 若 map.resolution 和 input.resolution 均为空,视为匹配 + (!input.resolution && map.resolution.length === 0) || + // 否则匹配 includes + map.resolution.includes(input.resolution as (typeof map.resolution)[number]); + return durationMatch && resolutionMatch; + }); + if (!validDurationResolution) { + const supportedDurations = [...new Set(owned.durationResolutionMap.flatMap((m) => m.duration))].sort((a, b) => a - b); + const supportedResolutions = [...new Set(owned.durationResolutionMap.flatMap((m) => m.resolution))]; + throw new Error( + `不支持的duration(${input.duration})或resolution(${input.resolution})组合。` + + `支持的duration: ${supportedDurations.join(", ")},支持的resolution: ${supportedResolutions.join(", ")}`, + ); + } + // 校验音频设置 + if (input.audio && !owned.audio) { + throw new Error(`模型 ${config.model} 不支持生成音频`); + } + // 校验宽高比(仅文本生视频需要) + if (hasTextType && images.length === 0 && owned.aspectRatio.length > 0) { + if (!owned.aspectRatio.includes(input.aspectRatio as `${number}:${number}`)) { + throw new Error(`模型 ${config.model} 不支持宽高比 ${input.aspectRatio},支持的宽高比: ${owned.aspectRatio.join(", ")}`); + } + } + return { owned, images, hasStartEndType, hasTextType }; +}; + +export const pollTask = async ( + queryFn: () => Promise<{ completed: boolean; url?: string; error?: string }>, + maxAttempts = 500, + interval = 2000, +): Promise => { + for (let i = 0; i < maxAttempts; i++) { + await new Promise((resolve) => setTimeout(resolve, interval)); + const { completed, url, error } = await queryFn(); + if (error) throw new Error(error); + if (completed && url) return url; + } + throw new Error(`任务轮询超时,已尝试 ${maxAttempts} 次`); +}; diff --git a/src/utils/ai/video/index.ts b/src/utils/ai/video/index.ts new file mode 100644 index 0000000..ad39c9a --- /dev/null +++ b/src/utils/ai/video/index.ts @@ -0,0 +1,66 @@ +import "./type"; +import u from "@/utils"; +import modelList from "./modelList"; +import axios from "axios"; + +import volcengine from "./owned/volcengine"; +import kling from "./owned/kling"; +import vidu from "./owned/vidu"; +import wan from "./owned/wan"; +import runninghub from "./owned/runninghub"; +import gemini from "./owned/gemini"; +import apimart from "./owned/apimart"; +import other from "./owned/other"; + +const modelInstance = { + volcengine: volcengine, + kling: kling, + vidu: vidu, + wan: wan, + gemini: gemini, + runninghub: runninghub, + apimart: apimart, + // other: other, +} as const; + +export default async (input: VideoConfig, config?: AIConfig) => { + const { model, apiKey, baseURL, manufacturer } = { ...config }; + if (!config || !config?.model || !config?.apiKey) throw new Error("请检查模型配置是否正确"); + + const manufacturerFn = modelInstance[manufacturer as keyof typeof modelInstance]; + if (!manufacturerFn) if (!manufacturerFn) throw new Error("不支持的视频厂商"); + const owned = modelList.find((m) => m.model === model); + if (!owned) throw new Error("不支持的模型"); + + // 补充图片的 base64 内容类型字符串 + if (input.imageBase64 && input.imageBase64.length > 0) { + input.imageBase64 = input.imageBase64.map((img) => { + if (img.startsWith("data:image/")) { + return img; + } + // 根据 base64 头部判断图片类型 + if (img.startsWith("/9j/")) { + return `data:image/jpeg;base64,${img}`; + } + if (img.startsWith("iVBORw")) { + return `data:image/png;base64,${img}`; + } + if (img.startsWith("R0lGOD")) { + return `data:image/gif;base64,${img}`; + } + if (img.startsWith("UklGR")) { + return `data:image/webp;base64,${img}`; + } + // 默认使用 png + return `data:image/png;base64,${img}`; + }); + } + + let videoUrl = await manufacturerFn(input, { model, apiKey, baseURL }); + if (videoUrl) { + const response = await axios.get(videoUrl, { responseType: "stream" }); + await u.oss.writeFile(input.savePath, response.data); + return input.savePath; + } + return videoUrl; +}; diff --git a/src/utils/ai/video/modelList.ts b/src/utils/ai/video/modelList.ts new file mode 100644 index 0000000..14c65aa --- /dev/null +++ b/src/utils/ai/video/modelList.ts @@ -0,0 +1,489 @@ +type VideoGenerationType = + | "singleImage" // 单图 + | "startEndRequired" // 首尾帧(两张都得有) + | "endFrameOptional" // 首尾帧(尾帧可选) + | "startFrameOptional" // 首尾帧(首帧可选) + | "multiImage" // 多图模式 + | "reference" // 参考图模式 + | "text"; // 文本生视频 + +interface DurationResolutionMap { + duration: number[]; + resolution: (`${number}p` | `${number}k`)[]; +} +interface Owned { + manufacturer: string; + model: string; + durationResolutionMap: DurationResolutionMap[]; + aspectRatio: `${number}:${number}`[]; + type: VideoGenerationType[]; + audio: boolean; +} + +const modelList: Owned[] = [ + // ================== 火山引擎/豆包系列 ================== + // doubao-seedance-1-5-pro 文生视频/图生视频 + { + manufacturer: "volcengine", + model: "doubao-seedance-1-5-pro-251215", + durationResolutionMap: [{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"], + type: ["text", "endFrameOptional"], + audio: true, + }, + // doubao-seedance-1-0-pro 文生视频/图生视频 + { + manufacturer: "volcengine", + model: "doubao-seedance-1-0-pro-250528", + durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"], + type: ["text", "endFrameOptional"], + audio: false, + }, + // doubao-seedance-1-0-pro-fast 文生视频/图生视频 + { + manufacturer: "volcengine", + model: "doubao-seedance-1-0-pro-fast-251015", + durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"], + type: ["text", "singleImage"], + audio: false, + }, + // doubao-seedance-1-0-lite-i2v 图生视频(仅支持图片模式) + { + manufacturer: "volcengine", + model: "doubao-seedance-1-0-lite-i2v-250428", + durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: [], + type: ["endFrameOptional", "reference"], + audio: false, + }, + // doubao-seedance-1-0-lite-t2v 文生视频(仅支持文本模式) + { + manufacturer: "volcengine", + model: "doubao-seedance-1-0-lite-t2v-250428", + durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"], + type: ["text"], + audio: false, + }, + // ================== 可灵系列 ================== + // kling-v1(STD) 文生视频 + { + manufacturer: "kling", + model: "kling-v1(STD)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }], + aspectRatio: ["16:9", "1:1", "9:16"], + type: ["text"], + audio: false, + }, + // kling-v1(STD) 图生视频 + { + manufacturer: "kling", + model: "kling-v1(STD)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }], + aspectRatio: [], + type: ["startEndRequired"], + audio: false, + }, + // kling-v1(PRO) 文生视频 + { + manufacturer: "kling", + model: "kling-v1(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: ["16:9", "1:1", "9:16"], + type: ["text"], + audio: false, + }, + // kling-v1(PRO) 图生视频 + { + manufacturer: "kling", + model: "kling-v1(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: [], + type: ["startEndRequired"], + audio: false, + }, + // kling-v1-6(PRO) 文生视频 + { + manufacturer: "kling", + model: "kling-v1-6(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: ["16:9", "1:1", "9:16"], + type: ["text"], + audio: false, + }, + // kling-v1-6(PRO) 图生视频 + { + manufacturer: "kling", + model: "kling-v1-6(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: [], + type: ["startEndRequired"], + audio: false, + }, + // kling-v2-5-turbo(PRO) 文生视频 + { + manufacturer: "kling", + model: "kling-v2-5-turbo(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: ["16:9", "1:1", "9:16"], + type: ["text"], + audio: false, + }, + // kling-v2-5-turbo(PRO) 图生视频 + { + manufacturer: "kling", + model: "kling-v2-5-turbo(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: [], + type: ["startEndRequired"], + audio: false, + }, + // kling-v2-6(PRO) 文生视频 + { + manufacturer: "kling", + model: "kling-v2-6(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: ["16:9", "1:1", "9:16"], + type: ["text"], + audio: false, + }, + // kling-v2-6(PRO) 图生视频 + { + manufacturer: "kling", + model: "kling-v2-6(PRO)", + durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }], + aspectRatio: [], + type: ["startEndRequired"], + audio: false, + }, + // ================== ViduQ3系列 ================== + // viduq3-pro 文生视频 + { + manufacturer: "vidu", + model: "viduq3-pro", + durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }], + aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"], + type: ["text"], + audio: true, + }, + // viduq3-pro 图生视频 + { + manufacturer: "vidu", + model: "viduq3-pro", + durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: true, + }, + // viduq2-pro-fast 图生视频 + { + manufacturer: "vidu", + model: "viduq2-pro-fast", + durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage", "startEndRequired"], + audio: false, + }, + // viduq2-pro 文生视频 + { + manufacturer: "vidu", + model: "viduq2-pro", + durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }], + aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"], + type: ["text"], + audio: false, + }, + // viduq2-pro 图生视频 + { + manufacturer: "vidu", + model: "viduq2-pro", + durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage", "reference", "startEndRequired"], + audio: false, + }, + // viduq2-turbo 文生视频 + { + manufacturer: "vidu", + model: "viduq2-turbo", + durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }], + aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"], + type: ["text"], + audio: false, + }, + // viduq2-turbo 图生视频 + { + manufacturer: "vidu", + model: "viduq2-turbo", + durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage", "reference", "startEndRequired"], + audio: false, + }, + // viduq1 文生视频 + { + manufacturer: "vidu", + model: "viduq1", + durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }], + aspectRatio: ["16:9", "9:16", "1:1"], + type: ["text"], + audio: false, + }, + // viduq1 图生视频 + { + manufacturer: "vidu", + model: "viduq1", + durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }], + aspectRatio: [], + type: ["singleImage", "reference", "startEndRequired"], + audio: false, + }, + // viduq1-classic 图生视频 + { + manufacturer: "vidu", + model: "viduq1-classic", + durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }], + aspectRatio: [], + type: ["singleImage", "startEndRequired"], + audio: false, + }, + // vidu2.0 图生视频 + { + manufacturer: "vidu", + model: "vidu2.0", + durationResolutionMap: [ + { duration: [4], resolution: ["360p", "720p", "1080p"] }, + { duration: [8], resolution: ["720p"] }, + ], + aspectRatio: [], + type: ["singleImage", "reference", "startEndRequired"], + audio: false, + }, + // ================== 万象系列 ================== + // wan2.6-t2v 文生视频(有声视频) + { + manufacturer: "wan", + model: "wan2.6-t2v", + durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p", "1080p"] }], + aspectRatio: ["16:9", "9:16", "1:1", "4:3", "3:4"], + type: ["text"], + audio: true, + }, + // wan2.5-t2v-preview 文生视频(有声视频) + { + manufacturer: "wan", + model: "wan2.5-t2v-preview", + durationResolutionMap: [{ duration: [5, 10], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: ["16:9", "9:16", "1:1", "4:3", "3:4"], + type: ["text"], + audio: true, + }, + // wan2.2-t2v-plus 文生视频(无声视频) + { + manufacturer: "wan", + model: "wan2.2-t2v-plus", + durationResolutionMap: [{ duration: [5], resolution: ["480p", "1080p"] }], + aspectRatio: ["16:9", "9:16", "1:1", "4:3", "3:4"], + type: ["text"], + audio: false, + }, + // wanx2.1-t2v-turbo 文生视频(无声视频) + { + manufacturer: "wan", + model: "wanx2.1-t2v-turbo", + durationResolutionMap: [{ duration: [5], resolution: ["480p", "720p"] }], + aspectRatio: ["16:9", "9:16", "1:1", "4:3", "3:4"], + type: ["text"], + audio: false, + }, + // wanx2.1-t2v-plus 文生视频(无声视频) + { + manufacturer: "wan", + model: "wanx2.1-t2v-plus", + durationResolutionMap: [{ duration: [5], resolution: ["720p"] }], + aspectRatio: ["16:9", "9:16", "1:1", "4:3", "3:4"], + type: ["text"], + audio: false, + }, + // wan2.6-i2v-flash 图生视频(有声视频&无声视频) + { + manufacturer: "wan", + model: "wan2.6-i2v-flash", + durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: true, + }, + // wan2.6-i2v 图生视频(有声视频) + { + manufacturer: "wan", + model: "wan2.6-i2v", + durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: true, + }, + // wan2.5-i2v-preview 图生视频(有声视频) + { + manufacturer: "wan", + model: "wan2.5-i2v-preview", + durationResolutionMap: [{ duration: [5, 10], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: true, + }, + // wan2.2-i2v-flash 图生视频(无声视频) + { + manufacturer: "wan", + model: "wan2.2-i2v-flash", + durationResolutionMap: [{ duration: [5], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: false, + }, + // wan2.2-i2v-plus 图生视频(无声视频) + { + manufacturer: "wan", + model: "wan2.2-i2v-plus", + durationResolutionMap: [{ duration: [5], resolution: ["480p", "1080p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: false, + }, + // wanx2.1-i2v-plus 图生视频(无声视频) + { + manufacturer: "wan", + model: "wanx2.1-i2v-plus", + durationResolutionMap: [{ duration: [5], resolution: ["720p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: false, + }, + // wanx2.1-i2v-turbo 图生视频(无声视频) + { + manufacturer: "wan", + model: "wanx2.1-i2v-turbo", + durationResolutionMap: [{ duration: [3, 4, 5], resolution: ["480p", "720p"] }], + aspectRatio: [], + type: ["singleImage"], + audio: false, + }, + // wan2.2-kf2v-flash 首尾帧生视频(无声视频) + { + manufacturer: "wan", + model: "wan2.2-kf2v-flash", + durationResolutionMap: [{ duration: [5], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: [], + type: ["startEndRequired"], + audio: false, + }, + // wanx2.1-kf2v-plus 首尾帧生视频(无声视频) + { + manufacturer: "wan", + model: "wanx2.1-kf2v-plus", + durationResolutionMap: [{ duration: [5], resolution: ["720p"] }], + aspectRatio: [], + type: ["startEndRequired"], + audio: false, + }, + // ================== Gemini Veo 系列 ================== + // Veo 3.1 预览版(支持音频) + { + manufacturer: "gemini", + model: "veo-3.1-generate-preview", + durationResolutionMap: [ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ], + aspectRatio: ["16:9", "9:16"], + type: ["text", "singleImage", "startEndRequired", "endFrameOptional", "reference"], + audio: true, + }, + // Veo 3.1 Fast 预览版(支持音频) + { + manufacturer: "gemini", + model: "veo-3.1-fast-generate-preview", + durationResolutionMap: [ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ], + aspectRatio: ["16:9", "9:16"], + type: ["text", "singleImage", "startEndRequired", "endFrameOptional", "reference"], + audio: true, + }, + // Veo 3 稳定版(支持音频) + { + manufacturer: "gemini", + model: "veo-3.0-generate-preview", + durationResolutionMap: [ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ], + aspectRatio: ["16:9", "9:16"], + type: ["text", "singleImage"], + audio: true, + }, + // Veo 3 Fast 稳定版(支持音频) + { + manufacturer: "gemini", + model: "veo-3.0-fast-generate-preview", + durationResolutionMap: [ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ], + aspectRatio: ["16:9", "9:16"], + type: ["text", "singleImage"], + audio: true, + }, + // Veo 2 稳定版(无音频) + { + manufacturer: "gemini", + model: "veo-2.0-generate-001", + durationResolutionMap: [{ duration: [5, 6, 7, 8], resolution: ["720p"] }], + aspectRatio: ["16:9", "9:16"], + type: ["text", "singleImage"], + audio: false, + }, + // ================== RunningHub 系列 ================== + // sora + { + manufacturer: "runninghub", + model: "sora-2", + durationResolutionMap: [{ duration: [10, 15], resolution: [] }], + aspectRatio: ["16:9", "9:16"], + type: ["singleImage", "text"], + audio: false, + }, + // sora 2 + { + manufacturer: "runninghub", + model: "sora-2-pro", + durationResolutionMap: [{ duration: [15, 25], resolution: [] }], + aspectRatio: ["16:9", "9:16"], + type: ["singleImage", "text"], + audio: false, + }, + // ================== Apimart 系列 ================== + // sora + { + manufacturer: "apimart", + model: "sora-2", + durationResolutionMap: [{ duration: [10, 15], resolution: [] }], + aspectRatio: ["16:9", "9:16"], + type: ["singleImage", "text"], + audio: false, + }, + // sora 2 + { + manufacturer: "apimart", + model: "sora-2-pro", + durationResolutionMap: [{ duration: [15, 25], resolution: [] }], + aspectRatio: ["16:9", "9:16"], + type: ["singleImage", "text"], + audio: false, + }, +]; + +export default modelList; diff --git a/src/utils/ai/video/owned/apimart.ts b/src/utils/ai/video/owned/apimart.ts new file mode 100644 index 0000000..143859a --- /dev/null +++ b/src/utils/ai/video/owned/apimart.ts @@ -0,0 +1,115 @@ +import "../type"; +import axios from "axios"; +import { pollTask } from "@/utils/ai/utils"; +import modelList from "../modelList"; + +// 上传图片到 apimart 图床 +async function uploadImageToApimart(base64Image: string): Promise { + if (base64Image.startsWith("http")) { + return base64Image; + } + + const presignRes = await axios.post( + "https://apimart.ai/api/upload/presign", + { contentType: "image/jpeg", fileExtension: "jpeg", permanent: false }, + { headers: { "Content-Type": "application/json" } }, + ); + + if (!presignRes.data.success || !presignRes.data.presignedUrl || !presignRes.data.cdnUrl) { + throw new Error(`获取预签名 URL 失败: ${JSON.stringify(presignRes.data)}`); + } + + const { presignedUrl, cdnUrl } = presignRes.data; + + const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, ""); + const buffer = Buffer.from(base64Data, "base64"); + + await axios.put(presignedUrl, buffer, { + headers: { "Content-Type": "image/jpeg" }, + }); + + return cdnUrl; +} + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.model) throw new Error("缺少 Model 名称"); + if (!config.apiKey) throw new Error("缺少 API Key"); + + const owned = modelList.find((m) => m.model === config.model); + if (!owned) throw new Error(`未找到模型: ${config.model}`); + + // 默认 baseURL 配置 + const defaultBaseUrl = "https://api.apimart.ai/v1/videos/generations|https://api.apimart.ai/v1/tasks/{taskId}"; + const [generateUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|"); + + const authorization = `Bearer ${config.apiKey}`; + + // 上传图片到图床 + let imageUrls: string[] = []; + if (input.imageBase64 && input.imageBase64.length > 0) { + for (const base64Image of input.imageBase64) { + const imageUrl = await uploadImageToApimart(base64Image); + imageUrls.push(imageUrl); + } + } + + // 构建请求体 + const requestBody: Record = { + model: config.model, + prompt: input.prompt, + duration: input.duration, + aspect_ratio: input.aspectRatio, + }; + + if (imageUrls.length > 0) { + requestBody.image_urls = imageUrls; + } + + // 创建任务 + const createRes = await axios.post(generateUrl, requestBody, { + headers: { + Authorization: authorization, + "Content-Type": "application/json", + }, + }); + + if (createRes.data.code !== 200 || !createRes.data.data?.[0]?.task_id) { + throw new Error(`创建任务失败: ${JSON.stringify(createRes.data)}`); + } + + const taskId = createRes.data.data[0].task_id; + const actualQueryUrl = queryUrl.replace("{taskId}", taskId); + + // 轮询任务状态 + return await pollTask(async () => { + const queryRes = await axios.get(actualQueryUrl, { + headers: { Authorization: authorization }, + }); + + const { code, data } = queryRes.data; + + if (code !== 200 || !data) { + return { completed: false, error: `查询失败: ${JSON.stringify(queryRes.data)}` }; + } + + const { status, result, error } = data; + + switch (status) { + case "completed": + const videoUrl = result?.videos?.[0]?.url?.[0]; + if (!videoUrl) { + return { completed: false, error: "未获取到视频 URL" }; + } + return { completed: true, url: videoUrl }; + case "failed": + return { completed: false, error: error?.message || "任务失败" }; + case "cancelled": + return { completed: false, error: "任务已取消" }; + case "pending": + case "processing": + return { completed: false }; + default: + return { completed: false, error: `未知状态: ${status}` }; + } + }); +}; diff --git a/src/utils/ai/video/owned/gemini.ts b/src/utils/ai/video/owned/gemini.ts new file mode 100644 index 0000000..ab01e03 --- /dev/null +++ b/src/utils/ai/video/owned/gemini.ts @@ -0,0 +1,73 @@ +import "../type"; +import fs from "fs"; +import path from "path"; +import axios from "axios"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; + +const buildInlineImage = (data: string) => ({ inlineData: { mimeType: "image/png", data } }); + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.model) throw new Error("缺少Model名称"); + if (!config.apiKey) throw new Error("缺少API Key"); + + const { owned, images, hasStartEndType } = validateVideoConfig(input, config); + + const defaultBaseUrl = [ + "https://generativelanguage.googleapis.com/v1beta/models/{model}:predictLongRunning", + "https://generativelanguage.googleapis.com/v1beta/{name}", + ].join("|"); + + const [submitUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|"); + + + const headers = { "x-goog-api-key": config.apiKey }; + + const instance: Record = { prompt: input.prompt }; + const parameters: Record = { + aspectRatio: input.aspectRatio, + durationSeconds: +input.duration, + ...(input.resolution !== "720p" && { resolution: input.resolution }), + }; + + // 根据图片数量和模型能力决定图片用法 + const len = images.length; + const hasRef = owned.type.includes("reference"); + const hasSingle = owned.type.includes("singleImage"); + + if (len === 2 && hasStartEndType) { + instance.image = buildInlineImage(images[0]); + parameters.lastFrame = buildInlineImage(images[1]); + } else if (len === 1 && (hasSingle || hasStartEndType)) { + instance.image = buildInlineImage(images[0]); + } else if (len >= 1 && len <= 3 && hasRef) { + parameters.referenceImages = images.map((img) => ({ image: buildInlineImage(img), referenceType: "asset" })); + } + + const { data } = await axios.post( + submitUrl.replace("{model}", config.model), + { instances: [instance], parameters }, + { headers: { ...headers, "Content-Type": "application/json" } }, + ); + + if (!data.name) throw new Error("未获取到操作名称"); + + return pollTask(async () => { + const { data: status } = await axios.get(queryUrl.replace("{name}", data.name), { headers }); + + const { done, response, error } = status; + + + if (!done) return { completed: false }; + if (error) return { completed: false, error: `任务失败: ${error.message || JSON.stringify(error)}` }; + + const videoUri = response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri; + + if (!videoUri) return { completed: false, error: "未获取到视频下载地址" }; + + const videoRes = await axios.get(videoUri, { headers, responseType: "arraybuffer", maxRedirects: 5 }); + const savePath = input.savePath.endsWith(".mp4") ? input.savePath : path.join(input.savePath, `gemini_${Date.now()}.mp4`); + fs.writeFileSync(savePath, Buffer.from(videoRes.data)); + + return { completed: true, url: savePath }; + }); +}; diff --git a/src/utils/ai/video/owned/kling.ts b/src/utils/ai/video/owned/kling.ts new file mode 100644 index 0000000..bb3c539 --- /dev/null +++ b/src/utils/ai/video/owned/kling.ts @@ -0,0 +1,90 @@ +import "../type"; +import axios from "axios"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.apiKey) throw new Error("缺少API Key"); + if (!config.baseURL) throw new Error("缺少baseURL配置"); + + const { images } = validateVideoConfig(input, config); + + // 解析URL配置:图生视频|文生视频|查询地址 + const defaultBaseUrl = + "https://api-beijing.klingai.com/v1/videos/image2video|https://api-beijing.klingai.com/v1/videos/text2video|https://api-beijing.klingai.com/v1/videos/text2video/{taskId}"; + const [image2videoUrl, text2videoUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|"); + + const headers = { + Authorization: `Bearer ${config.apiKey}`, + "Content-Type": "application/json", + }; + + // 解析模型名称和模式,例如 "kling-v2-6(PRO)" => modelName: "kling-v2-6", mode: "pro" + const modelMatch = config.model!.match(/^(.+)\((STD|PRO)\)$/i); + const modelName = modelMatch ? modelMatch[1] : config.model; + const mode = modelMatch ? (modelMatch[2].toLowerCase() as "std" | "pro") : "std"; + + // 判断是图生视频还是文生视频 + const hasImage = images.length > 0; + const createUrl = hasImage ? image2videoUrl : text2videoUrl; + + // 去除图片的内容类型前缀(kling要求纯base64) + const stripDataUrl = (str: string) => str.replace(/^data:image\/[^;]+;base64,/, ""); + + // 构建请求体 + const body: Record = { + model_name: modelName, + mode, + duration: String(input.duration), + prompt: input.prompt, + aspect_ratio: input.aspectRatio, + }; + + if (hasImage) { + // 图生视频:首帧和尾帧 + body.image = stripDataUrl(images[0]); + if (images.length > 1) { + body.image_tail = stripDataUrl(images[1]); + } + } + + // 创建任务 + const createResponse = await axios.post(createUrl, body, { headers }); + const createData = createResponse.data; + if (createData.code !== 0) { + throw new Error(`创建任务失败: ${createData.message || "未知错误"}`); + } + + const taskId = createData.data?.task_id; + if (!taskId) { + throw new Error("创建任务失败: 未返回任务ID"); + } + + // 轮询任务状态 + return await pollTask(async () => { + const queryResponse = await axios.get(`${queryUrl.replace("{taskId}", taskId)}`, { headers }); + const queryData = queryResponse.data; + if (queryData.code !== 0) { + return { completed: false, error: `查询失败: ${queryData.message || "未知错误"}` }; + } + + const task = queryData.data; + const taskStatus = task?.task_status; + + switch (taskStatus) { + case "succeed": { + const videoUrl = task?.task_result?.videos?.[0]?.url; + if (!videoUrl) { + return { completed: false, error: "任务成功但未返回视频URL" }; + } + return { completed: true, url: videoUrl }; + } + case "failed": + return { completed: false, error: `任务失败: ${task?.task_status_msg || "未知原因"}` }; + case "submitted": + case "processing": + return { completed: false }; + default: + return { completed: false, error: `未知状态: ${taskStatus}` }; + } + }); +}; diff --git a/src/utils/ai/video/owned/other.ts b/src/utils/ai/video/owned/other.ts new file mode 100644 index 0000000..3d4004d --- /dev/null +++ b/src/utils/ai/video/owned/other.ts @@ -0,0 +1,50 @@ +import "../type"; +import axios from "axios"; +import sharp from "sharp"; +import FormData from "form-data"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; +import { createOpenAI } from "@ai-sdk/openai"; + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.apiKey) throw new Error("缺少API Key"); + if (!config.baseURL) throw new Error("缺少baseURL"); + // const { owned, images, hasTextType } = validateVideoConfig(input, config); + + const [requestUrl, queryUrl] = config.baseURL.split("|"); + + const authorization = `Bearer ${config.apiKey}`; + + const formData = new FormData(); + formData.append("model", config.model); + formData.append("prompt", input.prompt); + formData.append("seconds", String(input.duration)); + + // 根据 aspectRatio 设置 size + const sizeMap: Record = { + "16:9": "1920x1080", + "9:16": "1080x1920", + }; + formData.append("size", sizeMap[input.aspectRatio] || "1920x1080"); + if (input.imageBase64 && input.imageBase64.length) { + const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, ""); + const buffer = Buffer.from(base64Data, "base64"); + formData.append("input_reference", buffer, { filename: "image.jpg", contentType: "image/jpeg" }); + } + const { data } = await axios.post(requestUrl, formData, { + headers: { "Content-Type": "application/json", Authorization: authorization, ...formData.getHeaders() }, + }); + if (data.status === "FAILED") throw new Error(`任务提交失败: ${data.errorMessage || "未知错误"}`); + const taskId = data.id; + return await pollTask(async () => { + const { data } = await axios.get(`${queryUrl.replace("{id}", taskId)}`, { + headers: { Authorization: authorization }, + }); + + if (data.status === "SUCCESS") { + return data.results?.length ? { completed: true, url: data.results[0].url } : { completed: false, error: "任务成功但未返回视频链接" }; + } + if (data.status === "FAILED") return { completed: false, error: `任务失败: ${data.errorMessage || "未知错误"}` }; + if (data.status === "QUEUED" || data.status === "RUNNING") return { completed: false }; + return { completed: false, error: `未知状态: ${data.status}` }; + }); +}; diff --git a/src/utils/ai/video/owned/runninghub.ts b/src/utils/ai/video/owned/runninghub.ts new file mode 100644 index 0000000..1a4d6c2 --- /dev/null +++ b/src/utils/ai/video/owned/runninghub.ts @@ -0,0 +1,99 @@ +import "../type"; +import axios from "axios"; +import sharp from "sharp"; +import FormData from "form-data"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.apiKey) throw new Error("缺少API Key"); + + const { owned, images, hasTextType } = validateVideoConfig(input, config); + + const defaultBaseUrl = [ + "https://www.runninghub.cn/openapi/v2/rhart-video-s/image-to-video", + "https://www.runninghub.cn/openapi/v2/rhart-video-s/image-to-video-pro", + "https://www.runninghub.cn/openapi/v2/rhart-video-s/text-to-video", + "https://www.runninghub.cn/openapi/v2/rhart-video-s/text-to-video-pro", + "https://www.runninghub.cn/openapi/v2/query", + "https://www.runninghub.cn/openapi/v2/media/upload/binary", + ].join("|"); + + const [image2videoUrl, image2videoProUrl, text2videoUrl, text2videoProUrl, queryUrl, uploadUrl] = (config.baseURL || defaultBaseUrl).split("|"); + + const isPro = owned.model === "sora-2-pro"; + const authorization = `Bearer ${config.apiKey}`; + + // 上传 base64 图片 + const uploadImage = async (base64Image: string): Promise => { + const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, ""); + let buffer: Buffer = Buffer.from(base64Data, "base64"); + const MAX_SIZE = 5 * 1024 * 1024; + + if (buffer.length > MAX_SIZE) { + for (let quality = 90; buffer.length > MAX_SIZE && quality > 10; quality -= 10) { + buffer = await sharp(buffer).jpeg({ quality, mozjpeg: true }).toBuffer(); + } + if (buffer.length > MAX_SIZE) { + const { width = 1920, height = 1080 } = await sharp(buffer).metadata(); + const scale = Math.sqrt(MAX_SIZE / buffer.length); + buffer = await sharp(buffer) + .resize({ width: Math.floor(width * scale), height: Math.floor(height * scale), fit: "inside" }) + .jpeg({ quality: 80, mozjpeg: true }) + .toBuffer(); + } + } + + const formData = new FormData(); + formData.append("file", buffer, { filename: "image.jpg", contentType: "image/jpeg" }); + + const { data } = await axios.post(uploadUrl, formData, { + headers: { Authorization: authorization }, + }); + + if (data.code !== 0 || !data.data?.download_url) { + throw new Error(`图片上传失败: ${JSON.stringify(data)}`); + } + return data.data.download_url; + }; + + // 提交任务 + const submitTask = async (url: string, body: Record) => { + const { data } = await axios.post(url, body, { + headers: { "Content-Type": "application/json", Authorization: authorization }, + }); + if (data.status === "FAILED") throw new Error(`任务提交失败: ${data.errorMessage || "未知错误"}`); + return { taskId: data.taskId, status: data.status, url: data.results?.[0]?.url }; + }; + + const isTextToVideo = images.length === 0 && hasTextType; + const submitUrl = isTextToVideo ? (isPro ? text2videoProUrl : text2videoUrl) : isPro ? image2videoProUrl : image2videoUrl; + + const requestBody: Record = { + prompt: input.prompt, + duration: String(input.duration), + aspectRatio: input.aspectRatio, + ...(isTextToVideo ? {} : { imageUrl: await uploadImage(images[0]) }), + }; + + const { taskId } = await submitTask(submitUrl, requestBody); + + return await pollTask(async () => { + + const { data } = await axios.post( + queryUrl, + { + taskId, + }, + { + headers: { Authorization: authorization }, + }, + ); + + if (data.status === "SUCCESS") { + return data.results?.length ? { completed: true, url: data.results[0].url } : { completed: false, error: "任务成功但未返回视频链接" }; + } + if (data.status === "FAILED") return { completed: false, error: `任务失败: ${data.errorMessage || "未知错误"}` }; + if (data.status === "QUEUED" || data.status === "RUNNING") return { completed: false }; + return { completed: false, error: `未知状态: ${data.status}` }; + }); +}; diff --git a/src/utils/ai/video/owned/vidu.ts b/src/utils/ai/video/owned/vidu.ts new file mode 100644 index 0000000..86cac52 --- /dev/null +++ b/src/utils/ai/video/owned/vidu.ts @@ -0,0 +1,132 @@ +import "../type"; +import axios from "axios"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; +import modelList from "../modelList"; + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.model) throw new Error("缺少Model名称"); + if (!config.apiKey) throw new Error("缺少API Key"); + if (!input.prompt && (!input.imageBase64 || input.imageBase64.length === 0)) { + throw new Error("至少需要提供prompt或图片"); + } + + const defaultBaseUrl = ["https://api.vidu.cn/ent/v2/text2video", "https://api.vidu.cn/ent/v2/img2video", "https://api.vidu.cn/ent/v2/tasks"].join( + "|", + ); + + const [text2videoUrl, image2videoUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|"); + + const authorization = `Token ${config.apiKey}`; + const hasImages = input.imageBase64 && input.imageBase64.length > 0; + + // 根据是否有图片,查找匹配的模型配置 + const customOwned = modelList.find((m) => { + if (m.manufacturer !== "vidu") return false; + if (m.model !== config.model) return false; + if (hasImages) { + return m.type.some((t) => t !== "text"); + } else { + return m.type.includes("text"); + } + }); + + if (!customOwned) { + throw new Error(`未找到匹配的模型配置: ${config.model}`); + } + + // 使用统一校验函数 + const { owned, images } = validateVideoConfig(input, config, customOwned); + + // 判断生成类型 + const genType: "text" | "image" = images.length === 0 ? "text" : "image"; + + // 校验宽高比(仅文生视频需要) + if (genType === "text" && owned.aspectRatio.length > 0 && !owned.aspectRatio.includes(input.aspectRatio as `${number}:${number}`)) { + throw new Error(`模型 ${owned.model} 不支持宽高比 ${input.aspectRatio},支持的宽高比:${owned.aspectRatio.join("、")}`); + } + + // 创建任务 + let taskId: string; + + if (genType === "text") { + // 文生视频 + const requestBody: Record = { + model: owned.model, + prompt: input.prompt, + duration: input.duration, + resolution: input.resolution, + aspect_ratio: input.aspectRatio, + }; + if (owned.audio && input.audio !== undefined) { + requestBody.audio = input.audio; + } + + const response = await axios.post(text2videoUrl, requestBody, { + headers: { + "Content-Type": "application/json", + Authorization: authorization, + }, + }); + taskId = response.data.task_id; + } else { + // 图生视频 + const requestBody: Record = { + model: owned.model, + images: images, + duration: input.duration, + resolution: input.resolution, + }; + if (input.prompt) { + requestBody.prompt = input.prompt; + } + if (owned.audio && input.audio !== undefined) { + requestBody.audio = input.audio; + } + + const response = await axios.post(image2videoUrl, requestBody, { + headers: { + "Content-Type": "application/json", + Authorization: authorization, + }, + }); + taskId = response.data.task_id; + } + + // 轮询任务状态 + return await pollTask(async () => { + const response = await axios.get(queryUrl, { + headers: { + "Content-Type": "application/json", + Authorization: authorization, + }, + params: { + task_ids: [taskId], + }, + }); + + const tasks = response.data.tasks; + if (!tasks || tasks.length === 0) { + return { completed: false, error: "任务不存在" }; + } + + const task = tasks[0]; + + switch (task.state) { + case "success": { + const creation = task.creations?.[0]; + return { + completed: true, + url: creation?.url, + }; + } + case "failed": + return { completed: false, error: "任务生成失败" }; + case "created": + case "queueing": + case "processing": + return { completed: false }; + default: + return { completed: false, error: `未知状态: ${task.state}` }; + } + }); +}; diff --git a/src/utils/ai/video/owned/volcengine.ts b/src/utils/ai/video/owned/volcengine.ts new file mode 100644 index 0000000..328e031 --- /dev/null +++ b/src/utils/ai/video/owned/volcengine.ts @@ -0,0 +1,75 @@ +import "../type"; +import axios from "axios"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.apiKey) throw new Error("缺少API Key"); + + const { owned, images, hasStartEndType } = validateVideoConfig(input, config); + + const authorization = "Bearer " + config.apiKey.replace(/^Bearer\s*/i, "").trim(); + const baseUrl = config.baseURL ?? "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks"; + + // 判断是否为首尾帧模式(需要两张图且类型支持首尾帧) + const isStartEndMode = images.length === 2 && hasStartEndType; + + // 构建图片内容 + const imageContent = images.map((base64, index) => { + const item: Record = { + type: "image_url", + image_url: { url: base64 }, + }; + if (isStartEndMode) { + item.role = index === 0 ? "first_frame" : "last_frame"; + } + return item; + }); + + // 构建请求体 + const requestBody: Record = { + model: config.model, + content: [{ type: "text", text: input.prompt }, ...imageContent], + duration: input.duration, + resolution: input.resolution, + watermark: false, + }; + + // 仅当模型支持音频时才添加 generate_audio 字段 + if (owned.audio) { + requestBody.generate_audio = input.audio ?? false; + } + // 创建视频生成任务 + const createResponse = await axios.post(baseUrl, requestBody, { + headers: { + "Content-Type": "application/json", + Authorization: authorization, + }, + }); + + const taskId = createResponse.data.id; + + if (!taskId) throw new Error("视频任务创建失败"); + + // 轮询任务状态 + return await pollTask(async () => { + const { status, content } = ( + await axios.get(`${baseUrl}/${taskId}`, { + headers: { Authorization: authorization }, + }) + ).data; + + switch (status) { + case "succeeded": + return { completed: true, url: content?.video_url }; + case "failed": + case "cancelled": + case "expired": + return { completed: false, error: `任务${status}` }; + case "queued": + case "running": + return { completed: false }; + default: + return { completed: false, error: `未知状态: ${status}` }; + } + }); +}; diff --git a/src/utils/ai/video/owned/wan.ts b/src/utils/ai/video/owned/wan.ts new file mode 100644 index 0000000..e319ad6 --- /dev/null +++ b/src/utils/ai/video/owned/wan.ts @@ -0,0 +1,168 @@ +import "../type"; +import axios from "axios"; +import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; + +// 根据分辨率档位和宽高比计算具体尺寸 +const getSizeFromConfig = (resolution: string, aspectRatio: string): string => { + const sizeMap: Record> = { + "480p": { + "16:9": "832*480", + "9:16": "480*832", + "1:1": "624*624", + }, + "720p": { + "16:9": "1280*720", + "9:16": "720*1280", + "1:1": "960*960", + "4:3": "1088*832", + "3:4": "832*1088", + }, + "1080p": { + "16:9": "1920*1080", + "9:16": "1080*1920", + "1:1": "1440*1440", + "4:3": "1632*1248", + "3:4": "1248*1632", + }, + }; + + const resolutionKey = resolution.toLowerCase(); + const size = sizeMap[resolutionKey]?.[aspectRatio]; + + if (!size) { + throw new Error(`不支持的分辨率(${resolution})和宽高比(${aspectRatio})组合`); + } + + return size; +}; + +export default async (input: VideoConfig, config: AIConfig) => { + if (!config.apiKey) throw new Error("缺少API Key"); + + const { owned, images, hasStartEndType, hasTextType } = validateVideoConfig(input, config); + + const defaultBaseUrl = [ + "https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", + "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis", + "https://dashscope.aliyuncs.com/api/v1/tasks/{taskId}", + ].join("|"); + + const [i2vUrl, kf2vUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|"); + + const types = owned.type; + const authorization = `Bearer ${config.apiKey}`; + + // 确定端点和构建请求体 + let submitUrl: string; + let body: Record; + + if (hasTextType && images.length === 0) { + // 文本生视频 + submitUrl = i2vUrl; + body = { + model: config.model, + input: { + prompt: input.prompt, + }, + parameters: { + size: getSizeFromConfig(input.resolution, input.aspectRatio), + duration: input.duration, + }, + }; + } else if (types.includes("singleImage")) { + // 图生视频 + submitUrl = i2vUrl; + body = { + model: config.model, + input: { + prompt: input.prompt, + img_url: images[0], + }, + parameters: { + resolution: input.resolution.toUpperCase(), + duration: input.duration, + }, + }; + // audio参数仅部分模型支持 + if (owned.audio && input.audio !== undefined) { + body.parameters.audio = input.audio; + } + } else if (hasStartEndType) { + // 首尾帧 + submitUrl = kf2vUrl; + const inputObj: Record = { + prompt: input.prompt, + first_frame_url: images[0], + }; + // 尾帧处理 + if (types.includes("startEndRequired")) { + inputObj.last_frame_url = images[1]; + } else if ((types.includes("endFrameOptional") || types.includes("startFrameOptional")) && images.length >= 2) { + inputObj.last_frame_url = images[1]; + } + body = { + model: config.model, + input: inputObj, + parameters: { + resolution: input.resolution.toUpperCase(), + duration: input.duration, + }, + }; + } else { + throw new Error(`不支持的视频生成类型: ${types.join(", ")}`); + } + + // 提交任务 + const submitResponse = await axios.post(submitUrl, body, { + headers: { + "Content-Type": "application/json", + Authorization: authorization, + "X-DashScope-Async": "enable", + }, + }); + + const submitData = submitResponse.data; + if (submitData.code) { + throw new Error(`任务提交失败: [${submitData.code}] ${submitData.message}`); + } + + const taskId = submitData.output?.task_id; + if (!taskId) { + throw new Error("任务提交失败: 未返回task_id"); + } + + // 轮询任务状态 + return await pollTask(async () => { + const response = await axios.get(queryUrl.replace("{taskId}", taskId), { + headers: { Authorization: authorization }, + }); + + const data = response.data; + + // 顶层错误 + if (data.code) { + return { completed: false, error: `[${data.code}] ${data.message}` }; + } + + const taskStatus = data.output?.task_status; + + switch (taskStatus) { + case "SUCCEEDED": + return { completed: true, url: data.output?.video_url }; + case "FAILED": + return { + completed: false, + error: `任务失败: [${data.output?.code || "UNKNOWN"}] ${data.output?.message || "未知错误"}`, + }; + case "CANCELED": + return { completed: false, error: "任务已取消" }; + case "UNKNOWN": + return { completed: false, error: "任务不存在或状态未知" }; + case "PENDING": + case "RUNNING": + return { completed: false }; + default: + return { completed: false, error: `未知状态: ${taskStatus}` }; + } + }); +}; diff --git a/src/utils/ai/video/type.ts b/src/utils/ai/video/type.ts new file mode 100644 index 0000000..4c38699 --- /dev/null +++ b/src/utils/ai/video/type.ts @@ -0,0 +1,16 @@ +interface VideoConfig { + duration: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + resolution: "480p" | "720p" | "1080p" | "2K" | "4K"; + aspectRatio: "16:9" | "9:16"; + prompt: string; + savePath: string; + imageBase64?: string[]; + audio?: boolean; +} + +interface AIConfig { + model?: string; + apiKey?: string; + baseURL?: string; + manufacturer?: string; +} diff --git a/src/utils/db.ts b/src/utils/db.ts index efca2e2..932fbe0 100644 --- a/src/utils/db.ts +++ b/src/utils/db.ts @@ -40,10 +40,11 @@ const db = knex({ useNullAsDefault: true, }); -initDB(db); -fixDB(db); - -if (process.env.NODE_ENV == "dev") initKnexType(db); +(async () => { + await initDB(db); + await fixDB(db); + if (process.env.NODE_ENV == "dev") initKnexType(db); +})(); const dbClient = Object.assign((table: TName) => db, RowType[]>(table), db); dbClient.schema = db.schema; diff --git a/src/utils/editImage.ts b/src/utils/editImage.ts index af058a4..1139621 100644 --- a/src/utils/editImage.ts +++ b/src/utils/editImage.ts @@ -79,13 +79,18 @@ async function convertDirectiveAndImages(images: Record, directi */ export default async (images: Record, directive: string, projectId: number) => { const { prompt, images: base64Images } = await convertDirectiveAndImages(images, directive); - const contentStr = await u.ai.generateImage({ - systemPrompt: "根据用户提供的具体修改指令,对上传的图片进行智能编辑。", - prompt: prompt, - imageBase64: base64Images, - aspectRatio: "16:9", - size: "1K", - }); + const apiConfig = await u.getPromptAi("editImage"); + + const contentStr = await u.ai.image( + { + systemPrompt: "根据用户提供的具体修改指令,对上传的图片进行智能编辑。", + prompt: prompt, + imageBase64: base64Images, + aspectRatio: "16:9", + size: "1K", + }, + apiConfig, + ); const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); const buffer = Buffer.from(match && match.length >= 1 ? match[1]! : contentStr, "base64"); const filePath = `/${projectId}/storyboard/${uuid()}.jpg`; diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100644 index 0000000..a08789b --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,68 @@ +// utils/error.ts +import { serializeError } from "serialize-error"; +import { isAxiosError } from "axios"; + +export interface NormalizedError { + name: string; + message: string; + code?: string; + status?: number; + stack?: string; + cause?: NormalizedError; + responseData?: unknown; + meta?: Record; +} + +export function normalizeError(error: unknown): NormalizedError { + // Axios 特殊处理 + if (isAxiosError(error)) { + return { + name: "AxiosError", + message: error.response?.data?.error?.message || error.response?.data?.message || error.message, + code: error.code, + status: error.response?.status, + stack: error.stack, + responseData: error.response?.data, + meta: { + url: error.config?.url, + method: error.config?.method, + }, + }; + } + + // 普通 Error,用 serialize-error 处理 + if (error instanceof Error) { + const serialized = serializeError(error); + return { + name: serialized.name || "Error", + message: serialized.message || "未知错误", + code: (serialized as any).code, + stack: serialized.stack, + cause: error.cause ? normalizeError(error.cause) : undefined, + meta: extractMeta(serialized), + }; + } + + // 非 Error + return { + name: "UnknownError", + message: String(error), + meta: { raw: serializeError(error) }, + }; +} + +// 提取自定义属性 +function extractMeta(obj: Record): Record | undefined { + const standardKeys = ["name", "message", "stack", "cause"]; + const meta: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + if (!standardKeys.includes(key) && value !== undefined) { + meta[key] = value; + } + } + + return Object.keys(meta).length > 0 ? meta : undefined; +} + +export default normalizeError; diff --git a/src/utils/generateScript.ts b/src/utils/generateScript.ts index 1323102..74e38d6 100644 --- a/src/utils/generateScript.ts +++ b/src/utils/generateScript.ts @@ -127,16 +127,18 @@ ${episodePrompt} ${novelData}`; const prompts = await u.db("t_prompts").where("code", "script").first(); - + const promptConfig = await u.getPromptAi("generateScript"); const mainPrompts = prompts?.customValue || prompts?.defaultValue || "不论用户说什么,请直接输出AI配置异常"; - const model = await u.ai.text(); - const result = await model.invoke({ - messages: [ - { role: "system", content: mainPrompts }, - { role: "user", content: userPrompt }, - ], - }); + const result = await u.ai.text.invoke( + { + messages: [ + { role: "system", content: mainPrompts }, + { role: "user", content: userPrompt }, + ], + }, + promptConfig, + ); return result.text ?? ""; } diff --git a/src/utils/getConfig.ts b/src/utils/getConfig.ts index 3bf3859..fce2884 100644 --- a/src/utils/getConfig.ts +++ b/src/utils/getConfig.ts @@ -10,11 +10,12 @@ interface BaseConfig { interface TextResData extends BaseConfig { baseURL: string; - manufacturer: "deepseek" | "openAi" | "doubao"; + manufacturer: "deepseek" | "openAi" | "doubao" | "other"; } +// 图像模型配置接口 interface ImageResData extends BaseConfig { - manufacturer: "openAi" | "gemini" | "volcengine" | "runninghub" | "apimart"; + manufacturer: "gemini" | "volcengine" | "kling" | "vidu" | "runninghub" | "apimart" | "other"; } interface VideoResData extends BaseConfig { @@ -34,10 +35,18 @@ const errorMessages: Record = { video: "视频模型配置不存在", }; -const needBaseURL: AIType[] = ["text", "video"]; +const needBaseURL: AIType[] = ["text", "video", "image"]; -export default async function getConfig(aiType: T): Promise { - const config = await u.db("t_config").where("type", aiType).first(); +export default async function getConfig(aiType: T, manufacturer?: string): Promise { + const config = await u + .db("t_config") + .where("type", aiType) + .modify((qb) => { + if (manufacturer) { + qb.where("manufacturer", manufacturer); + } + }) + .first(); if (!config) throw new Error(errorMessages[aiType]); diff --git a/src/utils/getPromptAi.ts b/src/utils/getPromptAi.ts new file mode 100644 index 0000000..9f02401 --- /dev/null +++ b/src/utils/getPromptAi.ts @@ -0,0 +1,19 @@ +import { db } from "./db"; +interface AiConfig { + model?: string; + apiKey: string; + baseURL?: string; + manufacturer: string; +} + +export default async function getPromptAi(key: string): Promise { + const aiConfigData = await db("t_aiModelMap") + .leftJoin("t_config", "t_config.id", "t_aiModelMap.configId") + .where("t_aiModelMap.key", key) + .select("t_config.model", "t_config.apiKey", "t_config.baseUrl as baseURL", "t_config.manufacturer") + .first(); + + if (aiConfigData) { + return aiConfigData as AiConfig; + } else return {}; +} diff --git a/src/utils/imageTools.ts b/src/utils/imageTools.ts new file mode 100644 index 0000000..82b519b --- /dev/null +++ b/src/utils/imageTools.ts @@ -0,0 +1,122 @@ +import sharp from "sharp"; + +/** + * 解析大小字符串为字节数 + */ +function parseSize(size: string): number { + const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(kb|mb|gb|b)?$/); + if (!match) { + throw new Error(`无效的大小格式: ${size}`); + } + const value = parseFloat(match[1]); + const unit = match[2] || "b"; + const multipliers: Record = { + b: 1, + kb: 1024, + mb: 1024 * 1024, + gb: 1024 * 1024 * 1024, + }; + return Math.floor(value * multipliers[unit]); +} + +/** + * 将base64字符串转换为Buffer + */ +function base64ToBuffer(base64: string): Buffer { + const base64Data = base64.replace(/^data:image\/\w+;base64,/, ""); + return Buffer.from(base64Data, "base64"); +} + +/** + * 压缩Buffer到指定大小以内 + */ +async function compressToSize(imageBuffer: Buffer, maxBytes: number, originalWidth: number, originalHeight: number): Promise { + let quality = 90; + let scale = 1; + + while (true) { + const targetWidth = Math.round(originalWidth * scale); + const targetHeight = Math.round(originalHeight * scale); + + const resultBuffer = await sharp(imageBuffer).resize(targetWidth, targetHeight, { fit: "fill" }).jpeg({ quality }).toBuffer(); + + if (resultBuffer.length <= maxBytes) { + return resultBuffer; + } + + if (quality > 10) { + quality -= 10; + } else { + quality = 90; + scale *= 0.8; + } + } +} + +/** + * 压缩单张图片到指定大小以内 + * @param imageBase64 - base64编码的图片 + * @param maxSize - 最大输出大小,支持格式如 "10mb", "5MB", "1024kb" 等 + * @returns 压缩后的图片base64字符串 + */ +export async function compressImage(imageBase64: string, maxSize = "10mb"): Promise { + const maxBytes = parseSize(maxSize); + const imageBuffer = base64ToBuffer(imageBase64); + const metadata = await sharp(imageBuffer).metadata(); + const resultBuffer = await compressToSize(imageBuffer, maxBytes, metadata.width || 1, metadata.height || 1); + return resultBuffer.toString("base64"); +} + +/** + * 将多张图片横向拼接为一张,并确保输出大小不超过指定限制 + * @param imageBase64List - base64编码的图片数组 + * @param maxSize - 最大输出大小,支持格式如 "10mb", "5MB", "1024kb" 等 + * @returns 拼接后的图片base64字符串 + */ +export async function mergeImages(imageBase64List: string[], maxSize = "10mb"): Promise { + if (imageBase64List.length === 0) { + throw new Error("图片列表不能为空"); + } + + const maxBytes = parseSize(maxSize); + const imageBuffers = imageBase64List.map(base64ToBuffer); + const imageMetadatas = await Promise.all(imageBuffers.map((buffer) => sharp(buffer).metadata())); + const maxHeight = Math.max(...imageMetadatas.map((m) => m.height || 0)); + + // 计算各图片调整后的宽度 + const imageWidths = imageMetadatas.map((metadata) => { + const aspectRatio = (metadata.width || 1) / (metadata.height || 1); + return Math.round(maxHeight * aspectRatio); + }); + const totalWidth = imageWidths.reduce((sum, w) => sum + w, 0); + + // 拼接图片 + const resizedImages = await Promise.all( + imageBuffers.map(async (buffer, index) => { + return sharp(buffer).resize(imageWidths[index], maxHeight, { fit: "cover" }).toBuffer(); + }), + ); + + let currentX = 0; + const compositeInputs = resizedImages.map((buffer, index) => { + const input = { input: buffer, left: currentX, top: 0 }; + currentX += imageWidths[index]; + return input; + }); + + const mergedBuffer = await sharp({ + create: { + width: totalWidth, + height: maxHeight, + channels: 4, + background: { r: 255, g: 255, b: 255, alpha: 1 }, + }, + }) + .composite(compositeInputs) + .jpeg({ quality: 90 }) + .toBuffer(); + + // 复用压缩逻辑 + const resultBuffer = await compressToSize(mergedBuffer, maxBytes, totalWidth, maxHeight); + return resultBuffer.toString("base64"); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f52c0c3..1397131 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,15 @@ "@ai-sdk/provider" "3.0.7" "@ai-sdk/provider-utils" "4.0.13" +"@ai-sdk/devtools@^0.0.11": + version "0.0.11" + resolved "https://registry.npmmirror.com/@ai-sdk/devtools/-/devtools-0.0.11.tgz#37dd8c45a7a820b7de7cab28e66d4429f46442c9" + integrity sha512-hQwVi+ZJy9aqlGG9ZDUNBum4wKtSfXP5o9Taa7nLC2kzSG+c37C8ke62p3HKpAXLNBNcTSu/hSdXqap/AnXVFQ== + dependencies: + "@ai-sdk/provider" "3.0.7" + "@hono/node-server" "^1.13.7" + hono "^4.6.14" + "@ai-sdk/gateway@3.0.32": version "3.0.32" resolved "https://registry.npmmirror.com/@ai-sdk/gateway/-/gateway-3.0.32.tgz#4738f75fc2eba7f245f77fd0dc139225a08c9c47" @@ -40,6 +49,14 @@ "@ai-sdk/provider" "3.0.7" "@ai-sdk/provider-utils" "4.0.13" +"@ai-sdk/openai-compatible@2.0.27", "@ai-sdk/openai-compatible@^2.0.27": + version "2.0.27" + resolved "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.27.tgz#55c6bf3c59d71e71d9c337dbef8b764fa69e7ccd" + integrity sha512-YpAZe7OQuMkYqcM/m1BMX0xFn4QdhuL4qGo8sNaiLq1VjEeU/pPfz51rnlpCfCvYanUL5TjIZEbdclBUwLooSQ== + dependencies: + "@ai-sdk/provider" "3.0.7" + "@ai-sdk/provider-utils" "4.0.13" + "@ai-sdk/openai@^3.0.25": version "3.0.25" resolved "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-3.0.25.tgz#452c8f8ed597468048569ec9476a0b5641888d2a" @@ -96,136 +113,14 @@ dependencies: json-schema "^0.4.0" -"@aigne/afs-history@^1.2.0": - version "1.2.0" - resolved "https://registry.npmmirror.com/@aigne/afs-history/-/afs-history-1.2.0.tgz#42086667ee83f2bbe181b247d6987cd05793d96f" - integrity sha512-ZN/JDycrYmFwX7oSbU5MZB2NHz9pLCP6vnLjPFx6bDrB0WEf1MXjnHHfKyqT94JdxJy7mOsRNUybH2iwzI/03w== +"@ai-sdk/xai@^3.0.47": + version "3.0.47" + resolved "https://registry.npmmirror.com/@ai-sdk/xai/-/xai-3.0.47.tgz#a8d3e08603865c5e401e19c801c7a80c3f31b890" + integrity sha512-JW43TqzPhc6Y9konMFlQ0kYcdFmTSCFu3yJlXP6RzaJSa3N2CKweUhMhXTzspqvA/wBCkCttTxf8hzXLMtSJ9Q== dependencies: - "@aigne/afs" "^1.4.0" - "@aigne/sqlite" "^0.4.9" - "@aigne/uuid" "^13.0.1" - radix3 "^1.1.2" - ufo "^1.6.1" - zod "^3.25.67" - -"@aigne/afs@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@aigne/afs/-/afs-1.4.0.tgz#a3537864ef145535445c17c0029911b77d5c8acf" - integrity sha512-Q3syx606c4n1vmLgGgyWTXRWdvh4RVnj39st/YrSt+b9Y+NSI/oQQJzWvBamBVG+0fNhfAy2tma+yRStYEGCzA== - dependencies: - "@aigne/platform-helpers" "^0.6.7" - "@aigne/uuid" "^13.0.1" - strict-event-emitter "^0.5.1" - ufo "^1.6.1" - yaml "^2.8.1" - zod "^3.25.67" - -"@aigne/core@^1.72.0": - version "1.72.0" - resolved "https://registry.npmmirror.com/@aigne/core/-/core-1.72.0.tgz#27e4231b935c6d83ead1b98beab1b2253386b156" - integrity sha512-v37OXo5yJHvcz0WX0VxcXGfrJivlv77MjIDYsq+Yb1wkLTFC4VYBXDwI36A650Aeeye9ED1uOZKkra2CbrBGSA== - dependencies: - "@aigne/afs" "^1.4.0" - "@aigne/afs-history" "^1.2.0" - "@aigne/json-schema-to-zod" "^1.3.3" - "@aigne/observability-api" "^0.11.14" - "@aigne/platform-helpers" "^0.6.7" - "@aigne/uuid" "^13.0.1" - "@inquirer/prompts" "^7.8.6" - "@modelcontextprotocol/sdk" "^1.18.0" - "@opentelemetry/api" "^1.9.0" - "@opentelemetry/sdk-trace-base" "^2.1.0" - "@types/debug" "^4.1.12" - "@zenoaihq/tson" "^1.0.0" - camelize-ts "^3.0.0" - content-type "^1.0.5" - debug "^4.4.3" - eventsource-parser "^3.0.6" - fast-deep-equal "^3.1.3" - front-matter "^4.0.2" - immer "^10.1.3" - is-network-error "^1.2.0" - jaison "=2.0.2" - jsonata "^2.1.0" - mime "^4.1.0" - nunjucks "^3.2.4" - p-retry "^7.0.0" - raw-body "^3.0.1" - strict-event-emitter "^0.5.1" - ufo "^1.6.1" - yaml "^2.8.1" - zod "^3.25.67" - zod-from-json-schema "^0.0.5" - zod-to-json-schema "^3.24.6" - -"@aigne/json-schema-to-zod@^1.3.3": - version "1.3.3" - resolved "https://registry.npmmirror.com/@aigne/json-schema-to-zod/-/json-schema-to-zod-1.3.3.tgz#1baf7747a18994105d0f90741842b260ba7386b8" - integrity sha512-8QsF5Vm/Znj16KAvfNIoXghSB7XzGGp+HHoI3CWyuFLX6DbI0YrzRm3EpKo87OhbpdtiWQurC0bidkH2cbsYNQ== - -"@aigne/observability-api@^0.11.14": - version "0.11.14" - resolved "https://registry.npmmirror.com/@aigne/observability-api/-/observability-api-0.11.14.tgz#8168fc86bfac67b9bd9b14c2114bf4508a1b9824" - integrity sha512-WGWO22RMnwuIzp2r8N7Olz3YQDwmBoL0TYraxeg4QqYy0NqmjFrjSKLKJkEU7qvw0Yx0FhRc24KevseamROe5g== - dependencies: - "@aigne/sqlite" "^0.4.9" - "@aigne/uuid" "^13.0.1" - "@opentelemetry/api" "^1.9.0" - "@opentelemetry/core" "^2.1.0" - "@opentelemetry/sdk-node" "^0.205.0" - "@opentelemetry/sdk-trace-base" "^2.1.0" - archiver "^7.0.1" - chalk "^5.6.2" - cookie-parser "^1.4.7" - cors "^2.8.5" - decimal.js "^10.6.0" - drizzle-orm "^0.44.5" - express "^5.1.0" - express-sse "^1.0.0" - fastq "^1.19.1" - mime "^4.1.0" - terminal-link "^5.0.0" - ufo "^1.6.1" - unzipper "^0.12.3" - yaml "^2.8.1" - zod "^3.25.67" - -"@aigne/openai@^0.16.16": - version "0.16.16" - resolved "https://registry.npmmirror.com/@aigne/openai/-/openai-0.16.16.tgz#61d4c621f49a816a9e15b0e05bfe7977577f6560" - integrity sha512-QF1imkQWKRKEasRQSkBg1FAVhXTiPvdOf6kBuXIlmOBMEMQ69hYIUcjcjfyTXm6thGkLfKL4m99VP3Q7wywZqw== - dependencies: - "@aigne/core" "^1.72.0" - "@aigne/platform-helpers" "^0.6.7" - "@aigne/uuid" "^13.0.1" - openai "^6.14.0" - zod "^3.25.67" - -"@aigne/platform-helpers@^0.6.7": - version "0.6.7" - resolved "https://registry.npmmirror.com/@aigne/platform-helpers/-/platform-helpers-0.6.7.tgz#67a25b182634e4438af2d409012dd93886f52268" - integrity sha512-Y7xjoLZ/KcYn86DbLSJs7CqNdFEchCg7PkpIiFu/cl1HyjiGEFjtvvD2yNcoqdazZV2vu+/d0LKEN0XKeyv3Kg== - dependencies: - "@modelcontextprotocol/sdk" "^1.18.0" - -"@aigne/sqlite@^0.4.9": - version "0.4.9" - resolved "https://registry.npmmirror.com/@aigne/sqlite/-/sqlite-0.4.9.tgz#0fb8bc5e890af5375a347eb0a96018bcf35c961f" - integrity sha512-1aFFbMt0qD+Rvmzc/e5lrmtLcs/iT8ncTK7jfIu/V+YfNsr5htmfDspgy9+eCy08ad/Ga+cOD+E+vWbUd+c5cQ== - dependencies: - "@libsql/client" "^0.15.15" - drizzle-orm "^0.44.5" - sqlocal "^0.14.2" - -"@aigne/uuid@^13.0.1": - version "13.0.1" - resolved "https://registry.npmmirror.com/@aigne/uuid/-/uuid-13.0.1.tgz#046c5981a1618665de8b2feb817ff89f2cea992d" - integrity sha512-28GrlAzaE1RkCQhkBn953gOd0hvCOAlZzjs4R5xPRphyNgZtDf058i2VfGDTyGvcVVkLhhoTy7s85srOM52krQ== - -"@cfworker/json-schema@^4.0.2": - version "4.1.1" - resolved "https://registry.npmmirror.com/@cfworker/json-schema/-/json-schema-4.1.1.tgz#4a2a3947ee9fa7b7c24be981422831b8674c3be6" - integrity sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og== + "@ai-sdk/openai-compatible" "2.0.27" + "@ai-sdk/provider" "3.0.7" + "@ai-sdk/provider-utils" "4.0.13" "@develar/schema-utils@~2.6.5": version "2.6.5" @@ -329,6 +224,11 @@ dependencies: tslib "^2.4.0" +"@epic-web/invariant@^1.0.0": + version "1.0.0" + resolved "https://registry.npmmirror.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813" + integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA== + "@esbuild/aix-ppc64@0.27.2": version "0.27.2" resolved "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz#521cbd968dcf362094034947f76fa1b18d2d403c" @@ -464,25 +364,7 @@ resolved "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@grpc/grpc-js@^1.7.1": - version "1.14.3" - resolved "https://registry.npmmirror.com/@grpc/grpc-js/-/grpc-js-1.14.3.tgz#4c9b817a900ae4020ddc28515ae4b52c78cfb8da" - integrity sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA== - dependencies: - "@grpc/proto-loader" "^0.8.0" - "@js-sdsl/ordered-map" "^4.4.2" - -"@grpc/proto-loader@^0.8.0": - version "0.8.0" - resolved "https://registry.npmmirror.com/@grpc/proto-loader/-/proto-loader-0.8.0.tgz#b6c324dd909c458a0e4aa9bfd3d69cf78a4b9bd8" - integrity sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.5.3" - yargs "^17.7.2" - -"@hono/node-server@^1.19.9": +"@hono/node-server@^1.13.7": version "1.19.9" resolved "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.9.tgz#8f37119b1acf283fd3f6035f3d1356fdb97a09ac" integrity sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw== @@ -634,151 +516,6 @@ resolved "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz#a81ffb00e69267cd0a1d626eaedb8a8430b2b2f8" integrity sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw== -"@inquirer/ansi@^1.0.2": - version "1.0.2" - resolved "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-1.0.2.tgz#674a4c4d81ad460695cb2a1fc69d78cd187f337e" - integrity sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ== - -"@inquirer/checkbox@^4.3.2": - version "4.3.2" - resolved "https://registry.npmmirror.com/@inquirer/checkbox/-/checkbox-4.3.2.tgz#e1483e6519d6ffef97281a54d2a5baa0d81b3f3b" - integrity sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA== - dependencies: - "@inquirer/ansi" "^1.0.2" - "@inquirer/core" "^10.3.2" - "@inquirer/figures" "^1.0.15" - "@inquirer/type" "^3.0.10" - yoctocolors-cjs "^2.1.3" - -"@inquirer/confirm@^5.1.21": - version "5.1.21" - resolved "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-5.1.21.tgz#610c4acd7797d94890a6e2dde2c98eb1e891dd12" - integrity sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ== - dependencies: - "@inquirer/core" "^10.3.2" - "@inquirer/type" "^3.0.10" - -"@inquirer/core@^10.3.2": - version "10.3.2" - resolved "https://registry.npmmirror.com/@inquirer/core/-/core-10.3.2.tgz#535979ff3ff4fe1e7cc4f83e2320504c743b7e20" - integrity sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A== - dependencies: - "@inquirer/ansi" "^1.0.2" - "@inquirer/figures" "^1.0.15" - "@inquirer/type" "^3.0.10" - cli-width "^4.1.0" - mute-stream "^2.0.0" - signal-exit "^4.1.0" - wrap-ansi "^6.2.0" - yoctocolors-cjs "^2.1.3" - -"@inquirer/editor@^4.2.23": - version "4.2.23" - resolved "https://registry.npmmirror.com/@inquirer/editor/-/editor-4.2.23.tgz#fe046a3bfdae931262de98c1052437d794322e0b" - integrity sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ== - dependencies: - "@inquirer/core" "^10.3.2" - "@inquirer/external-editor" "^1.0.3" - "@inquirer/type" "^3.0.10" - -"@inquirer/expand@^4.0.23": - version "4.0.23" - resolved "https://registry.npmmirror.com/@inquirer/expand/-/expand-4.0.23.tgz#a38b5f32226d75717c370bdfed792313b92bdc05" - integrity sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew== - dependencies: - "@inquirer/core" "^10.3.2" - "@inquirer/type" "^3.0.10" - yoctocolors-cjs "^2.1.3" - -"@inquirer/external-editor@^1.0.3": - version "1.0.3" - resolved "https://registry.npmmirror.com/@inquirer/external-editor/-/external-editor-1.0.3.tgz#c23988291ee676290fdab3fd306e64010a6d13b8" - integrity sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA== - dependencies: - chardet "^2.1.1" - iconv-lite "^0.7.0" - -"@inquirer/figures@^1.0.15": - version "1.0.15" - resolved "https://registry.npmmirror.com/@inquirer/figures/-/figures-1.0.15.tgz#dbb49ed80df11df74268023b496ac5d9acd22b3a" - integrity sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g== - -"@inquirer/input@^4.3.1": - version "4.3.1" - resolved "https://registry.npmmirror.com/@inquirer/input/-/input-4.3.1.tgz#778683b4c4c4d95d05d4b05c4a854964b73565b4" - integrity sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g== - dependencies: - "@inquirer/core" "^10.3.2" - "@inquirer/type" "^3.0.10" - -"@inquirer/number@^3.0.23": - version "3.0.23" - resolved "https://registry.npmmirror.com/@inquirer/number/-/number-3.0.23.tgz#3fdec2540d642093fd7526818fd8d4bdc7335094" - integrity sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg== - dependencies: - "@inquirer/core" "^10.3.2" - "@inquirer/type" "^3.0.10" - -"@inquirer/password@^4.0.23": - version "4.0.23" - resolved "https://registry.npmmirror.com/@inquirer/password/-/password-4.0.23.tgz#b9f5187c8c92fd7aa9eceb9d8f2ead0d7e7b000d" - integrity sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA== - dependencies: - "@inquirer/ansi" "^1.0.2" - "@inquirer/core" "^10.3.2" - "@inquirer/type" "^3.0.10" - -"@inquirer/prompts@^7.8.6": - version "7.10.1" - resolved "https://registry.npmmirror.com/@inquirer/prompts/-/prompts-7.10.1.tgz#e1436c0484cf04c22548c74e2cd239e989d5f847" - integrity sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg== - dependencies: - "@inquirer/checkbox" "^4.3.2" - "@inquirer/confirm" "^5.1.21" - "@inquirer/editor" "^4.2.23" - "@inquirer/expand" "^4.0.23" - "@inquirer/input" "^4.3.1" - "@inquirer/number" "^3.0.23" - "@inquirer/password" "^4.0.23" - "@inquirer/rawlist" "^4.1.11" - "@inquirer/search" "^3.2.2" - "@inquirer/select" "^4.4.2" - -"@inquirer/rawlist@^4.1.11": - version "4.1.11" - resolved "https://registry.npmmirror.com/@inquirer/rawlist/-/rawlist-4.1.11.tgz#313c8c3ffccb7d41e990c606465726b4a898a033" - integrity sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw== - dependencies: - "@inquirer/core" "^10.3.2" - "@inquirer/type" "^3.0.10" - yoctocolors-cjs "^2.1.3" - -"@inquirer/search@^3.2.2": - version "3.2.2" - resolved "https://registry.npmmirror.com/@inquirer/search/-/search-3.2.2.tgz#4cc6fd574dcd434e4399badc37c742c3fd534ac8" - integrity sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA== - dependencies: - "@inquirer/core" "^10.3.2" - "@inquirer/figures" "^1.0.15" - "@inquirer/type" "^3.0.10" - yoctocolors-cjs "^2.1.3" - -"@inquirer/select@^4.4.2": - version "4.4.2" - resolved "https://registry.npmmirror.com/@inquirer/select/-/select-4.4.2.tgz#2ac8fca960913f18f1d1b35323ed8fcd27d89323" - integrity sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w== - dependencies: - "@inquirer/ansi" "^1.0.2" - "@inquirer/core" "^10.3.2" - "@inquirer/figures" "^1.0.15" - "@inquirer/type" "^3.0.10" - yoctocolors-cjs "^2.1.3" - -"@inquirer/type@^3.0.10": - version "3.0.10" - resolved "https://registry.npmmirror.com/@inquirer/type/-/type-3.0.10.tgz#11ed564ec78432a200ea2601a212d24af8150d50" - integrity sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA== - "@isaacs/balanced-match@^4.0.1": version "4.0.1" resolved "https://registry.npmmirror.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" @@ -810,148 +547,6 @@ dependencies: minipass "^7.0.4" -"@js-sdsl/ordered-map@^4.4.2": - version "4.4.2" - resolved "https://registry.npmmirror.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" - integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== - -"@langchain/core@^1.1.15": - version "1.1.17" - resolved "https://registry.npmmirror.com/@langchain/core/-/core-1.1.17.tgz#b5a9a9fb51e9f1963581d2f7ce1e22867865d8a6" - integrity sha512-g7/kcKbKEwNZSyyT7aT0utxn7wTOtKErqz0cL6VjrV4v/aOb9g+dKcfj17YkSm42YQmJp/rB2IXGc17vQPEBqA== - dependencies: - "@cfworker/json-schema" "^4.0.2" - ansi-styles "^5.0.0" - camelcase "6" - decamelize "1.2.0" - js-tiktoken "^1.0.12" - langsmith ">=0.4.0 <1.0.0" - mustache "^4.2.0" - p-queue "^6.6.2" - uuid "^10.0.0" - zod "^3.25.76 || ^4" - -"@langchain/langgraph-checkpoint@^1.0.0": - version "1.0.0" - resolved "https://registry.npmmirror.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.0.tgz#ece2ede439d0d0b0b532c4be7817fd5029afe4f8" - integrity sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A== - dependencies: - uuid "^10.0.0" - -"@langchain/langgraph-sdk@~1.5.5": - version "1.5.5" - resolved "https://registry.npmmirror.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.5.5.tgz#a84fe0f27e2ed6452a83106c3759d7673789a1f0" - integrity sha512-SyiAs6TVXPWlt/8cI9pj/43nbIvclY3ytKqUFbL5MplCUnItetEyqvH87EncxyVF5D7iJKRZRfSVYBMmOZbjbQ== - dependencies: - p-queue "^9.0.1" - p-retry "^7.1.1" - uuid "^13.0.0" - -"@langchain/langgraph@^1.1.2": - version "1.1.2" - resolved "https://registry.npmmirror.com/@langchain/langgraph/-/langgraph-1.1.2.tgz#9360e01a2f23f4d25a27adddc73ea38eb3d3a976" - integrity sha512-kpZCttZ0N+jHSl5Vh/zVNElD5SxGR4sTjjLiBC00aLGf9JK+Sa/XXO6Bsk3WWXFtA1dY+4tUzUqH0mAHfN0WvA== - dependencies: - "@langchain/langgraph-checkpoint" "^1.0.0" - "@langchain/langgraph-sdk" "~1.5.5" - "@standard-schema/spec" "1.1.0" - uuid "^10.0.0" - -"@langchain/openai@^1.2.1": - version "1.2.3" - resolved "https://registry.npmmirror.com/@langchain/openai/-/openai-1.2.3.tgz#39f4b843ea72d71bf653aa21896f151311027e27" - integrity sha512-+bKR4+Obz5a/NHEw0bAm3f/s4k0cXc/g46ZRRXqjcyDYP+9wFarItvGNn6DEEk5S7pGp1QqApAQNt9IZk1Ic1Q== - dependencies: - js-tiktoken "^1.0.12" - openai "^6.16.0" - zod "^3.25.76 || ^4" - -"@libsql/client@^0.15.15": - version "0.15.15" - resolved "https://registry.npmmirror.com/@libsql/client/-/client-0.15.15.tgz#70196a0109f8cec41a32e42d9085386900e1943b" - integrity sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w== - dependencies: - "@libsql/core" "^0.15.14" - "@libsql/hrana-client" "^0.7.0" - js-base64 "^3.7.5" - libsql "^0.5.22" - promise-limit "^2.7.0" - -"@libsql/core@^0.15.14": - version "0.15.15" - resolved "https://registry.npmmirror.com/@libsql/core/-/core-0.15.15.tgz#0c5c0e4825a859b6a688d9ecc1086f9b9e46c621" - integrity sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA== - dependencies: - js-base64 "^3.7.5" - -"@libsql/darwin-arm64@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/darwin-arm64/-/darwin-arm64-0.5.22.tgz#3e61a4419e7f765117b3e36818c3728e560887c1" - integrity sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA== - -"@libsql/darwin-x64@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/darwin-x64/-/darwin-x64-0.5.22.tgz#4967188bfa15a974d9a1c987c3129565f07a1781" - integrity sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA== - -"@libsql/hrana-client@^0.7.0": - version "0.7.0" - resolved "https://registry.npmmirror.com/@libsql/hrana-client/-/hrana-client-0.7.0.tgz#c059d8106b9d40dd931217333710aff2ceb5216e" - integrity sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw== - dependencies: - "@libsql/isomorphic-fetch" "^0.3.1" - "@libsql/isomorphic-ws" "^0.1.5" - js-base64 "^3.7.5" - node-fetch "^3.3.2" - -"@libsql/isomorphic-fetch@^0.3.1": - version "0.3.1" - resolved "https://registry.npmmirror.com/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz#42023816d5645a5a3f3a78bb3899bdc5814c7b88" - integrity sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw== - -"@libsql/isomorphic-ws@^0.1.5": - version "0.1.5" - resolved "https://registry.npmmirror.com/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz#e2d1faf965ba0f3be9301fbf5640164d03c4e606" - integrity sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg== - dependencies: - "@types/ws" "^8.5.4" - ws "^8.13.0" - -"@libsql/linux-arm-gnueabihf@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.22.tgz#1bafad9bb58f550c1b35cc40e64da67610affefd" - integrity sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA== - -"@libsql/linux-arm-musleabihf@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.22.tgz#014ffeba50c275872a97e7adefb604d46c49894e" - integrity sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg== - -"@libsql/linux-arm64-gnu@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.22.tgz#53fc8ba65e5b3573f5b5b898393690d564c26513" - integrity sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA== - -"@libsql/linux-arm64-musl@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.22.tgz#2dd78be57ff410552cdd2446b2040aac88ad8e44" - integrity sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw== - -"@libsql/linux-x64-gnu@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.22.tgz#b9b534a52bb21fc1d8394f681800f0ab2304869b" - integrity sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew== - -"@libsql/linux-x64-musl@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.22.tgz#29b43d1617d22ec92e1444955a55334b04efb6fa" - integrity sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg== - -"@libsql/win32-x64-msvc@0.5.22": - version "0.5.22" - resolved "https://registry.npmmirror.com/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.22.tgz#3d8eb9d092ebe18005dea97953575d981f669621" - integrity sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA== - "@malept/cross-spawn-promise@^2.0.0": version "2.0.0" resolved "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz#d0772de1aa680a0bfb9ba2f32b4c828c7857cb9d" @@ -969,33 +564,6 @@ lodash "^4.17.15" tmp-promise "^3.0.2" -"@modelcontextprotocol/sdk@^1.18.0": - version "1.25.3" - resolved "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz#a665ae5f983a5cdfe1a1809aafb48110b04faef1" - integrity sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ== - dependencies: - "@hono/node-server" "^1.19.9" - ajv "^8.17.1" - ajv-formats "^3.0.1" - content-type "^1.0.5" - cors "^2.8.5" - cross-spawn "^7.0.5" - eventsource "^3.0.2" - eventsource-parser "^3.0.0" - express "^5.0.1" - express-rate-limit "^7.5.0" - jose "^6.1.1" - json-schema-typed "^8.0.2" - pkce-challenge "^5.0.0" - raw-body "^3.0.0" - zod "^3.25 || ^4.0" - zod-to-json-schema "^3.25.0" - -"@neon-rs/load@^0.0.4": - version "0.0.4" - resolved "https://registry.npmmirror.com/@neon-rs/load/-/load-0.0.4.tgz#2a2a3292c6f1fef043f49886712d3c96a547532e" - integrity sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw== - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1051,369 +619,16 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@opentelemetry/api-logs@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/api-logs/-/api-logs-0.205.0.tgz#7d334958c0eaa0725a1fe51aadc9b39ed7d4b6f2" - integrity sha512-wBlPk1nFB37Hsm+3Qy73yQSobVn28F4isnWIBvKpd5IUH/eat8bwcL02H9yzmHyyPmukeccSl2mbN5sDQZYnPg== - dependencies: - "@opentelemetry/api" "^1.3.0" - -"@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0": +"@opentelemetry/api@1.9.0": version "1.9.0" resolved "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/context-async-hooks@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.1.0.tgz#de1de21d9536abfe73769f822b52a59a8c97b083" - integrity sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg== - -"@opentelemetry/core@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/core/-/core-2.1.0.tgz#5539f04eb9e5245e000b0c3f77bdfaa07557e3a7" - integrity sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ== - dependencies: - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/core@2.5.0", "@opentelemetry/core@^2.1.0": - version "2.5.0" - resolved "https://registry.npmmirror.com/@opentelemetry/core/-/core-2.5.0.tgz#3b2ac6cf471ed9a85eea836048a4de77a2e549d3" - integrity sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ== - dependencies: - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/exporter-logs-otlp-grpc@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.205.0.tgz#aaede695e83949def09b099c4a55eca1f0640bb1" - integrity sha512-jQlw7OHbqZ8zPt+pOrW2KGN7T55P50e3NXBMr4ckPOF+DWDwSy4W7mkG09GpYWlQAQ5C9BXg5gfUlv5ldTgWsw== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-grpc-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/sdk-logs" "0.205.0" - -"@opentelemetry/exporter-logs-otlp-http@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.205.0.tgz#aded88ee81afa98e6e08306131180a7d350a56a0" - integrity sha512-5JteMyVWiro4ghF0tHQjfE6OJcF7UBUcoEqX3UIQ5jutKP1H+fxFdyhqjjpmeHMFxzOHaYuLlNR1Bn7FOjGyJg== - dependencies: - "@opentelemetry/api-logs" "0.205.0" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/sdk-logs" "0.205.0" - -"@opentelemetry/exporter-logs-otlp-proto@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.205.0.tgz#ea40cabfbb43433f26c9d85a37ca0e22709c4e42" - integrity sha512-q3VS9wS+lpZ01txKxiDGBtBpTNge3YhbVEFDgem9ZQR9eI3EZ68+9tVZH9zJcSxI37nZPJ6lEEZO58yEjYZsVA== - dependencies: - "@opentelemetry/api-logs" "0.205.0" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-logs" "0.205.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - -"@opentelemetry/exporter-metrics-otlp-grpc@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.205.0.tgz#7e316a0e9f4efb4ee6445bef5f9d236a7478b83e" - integrity sha512-1Vxlo4lUwqSKYX+phFkXHKYR3DolFHxCku6lVMP1H8sVE3oj4wwmwxMzDsJ7zF+sXd8M0FCr+ckK4SnNNKkV+w== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/exporter-metrics-otlp-http" "0.205.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-grpc-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-metrics" "2.1.0" - -"@opentelemetry/exporter-metrics-otlp-http@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.205.0.tgz#d67b32486bc781ad15999bd5360100637ae18ba6" - integrity sha512-fFxNQ/HbbpLmh1pgU6HUVbFD1kNIjrkoluoKJkh88+gnmpFD92kMQ8WFNjPnSbjg2mNVnEkeKXgCYEowNW+p1w== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-metrics" "2.1.0" - -"@opentelemetry/exporter-metrics-otlp-proto@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.205.0.tgz#713264028bd7d561b0d382e373456d9a4941fda4" - integrity sha512-qIbNnedw9QfFjwpx4NQvdgjK3j3R2kWH/2T+7WXAm1IfMFe9fwatYxE61i7li4CIJKf8HgUC3GS8Du0C3D+AuQ== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/exporter-metrics-otlp-http" "0.205.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-metrics" "2.1.0" - -"@opentelemetry/exporter-prometheus@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.205.0.tgz#e3862ea409cfb4557bd1ee546e9cba6a60eb3f85" - integrity sha512-xsot/Qm9VLDTag4GEwAunD1XR1U8eBHTLAgO7IZNo2JuD/c/vL7xmDP7mQIUr6Lk3gtj/yGGIR2h3vhTeVzv4w== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-metrics" "2.1.0" - -"@opentelemetry/exporter-trace-otlp-grpc@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.205.0.tgz#e6ef2249efb6ee1bc81f720f34fc1ad471c77289" - integrity sha512-ZBksUk84CcQOuDJB65yu5A4PORkC4qEsskNwCrPZxDLeWjPOFZNSWt0E0jQxKCY8PskLhjNXJYo12YaqsYvGFA== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-grpc-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - -"@opentelemetry/exporter-trace-otlp-http@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.205.0.tgz#b49407fe807b2cebcfdf4e8b121ab93fae46bb47" - integrity sha512-vr2bwwPCSc9u7rbKc74jR+DXFvyMFQo9o5zs+H/fgbK672Whw/1izUKVf+xfWOdJOvuwTnfWxy+VAY+4TSo74Q== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - -"@opentelemetry/exporter-trace-otlp-proto@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.205.0.tgz#98d6f8fa13d1dc86e2a8749548e6c01706b0e824" - integrity sha512-bGtFzqiENO2GpJk988mOBMe0MfeNpTQjbLm/LBijas6VRyEDQarUzdBHpFlu89A25k1+BCntdWGsWTa9Ai4FyA== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - -"@opentelemetry/exporter-zipkin@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.1.0.tgz#4e0a38757df09a04b2dc3194f1a9d56909d1c2d6" - integrity sha512-0mEI0VDZrrX9t5RE1FhAyGz+jAGt96HSuXu73leswtY3L5YZD11gtcpARY2KAx/s6Z2+rj5Mhj566JsI2C7mfA== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/instrumentation@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/instrumentation/-/instrumentation-0.205.0.tgz#cf936288b14311212619ba80df96b2404d1acf14" - integrity sha512-cgvm7tvQdu9Qo7VurJP84wJ7ZV9F6WqDDGZpUc6rUEXwjV7/bXWs0kaYp9v+1Vh1+3TZCD3i6j/lUBcPhu8NhA== - dependencies: - "@opentelemetry/api-logs" "0.205.0" - import-in-the-middle "^1.8.1" - require-in-the-middle "^7.1.1" - -"@opentelemetry/otlp-exporter-base@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.205.0.tgz#3a4a09382e517af88d152c2f31e47ac16a84b43c" - integrity sha512-2MN0C1IiKyo34M6NZzD6P9Nv9Dfuz3OJ3rkZwzFmF6xzjDfqqCTatc9v1EpNfaP55iDOCLHFyYNCgs61FFgtUQ== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-transformer" "0.205.0" - -"@opentelemetry/otlp-grpc-exporter-base@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.205.0.tgz#b7c83d303b7c88abfad50ee1d60cfdf1c32cb0e6" - integrity sha512-AeuLfrciGYffqsp4EUTdYYc6Ee2BQS+hr08mHZk1C524SFWx0WnfcTnV0NFXbVURUNU6DZu1DhS89zRRrcx/hg== - dependencies: - "@grpc/grpc-js" "^1.7.1" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/otlp-exporter-base" "0.205.0" - "@opentelemetry/otlp-transformer" "0.205.0" - -"@opentelemetry/otlp-transformer@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.205.0.tgz#bf53729676a3f80a701141762ed6e3c92ec82963" - integrity sha512-KmObgqPtk9k/XTlWPJHdMbGCylRAmMJNXIRh6VYJmvlRDMfe+DonH41G7eenG8t4FXn3fxOGh14o/WiMRR6vPg== - dependencies: - "@opentelemetry/api-logs" "0.205.0" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-logs" "0.205.0" - "@opentelemetry/sdk-metrics" "2.1.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - protobufjs "^7.3.0" - -"@opentelemetry/propagator-b3@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/propagator-b3/-/propagator-b3-2.1.0.tgz#7767fbb62f9c43115cb4dee48ab9acc6dba43963" - integrity sha512-yOdHmFseIChYanddMMz0mJIFQHyjwbNhoxc65fEAA8yanxcBPwoFDoh1+WBUWAO/Z0NRgk+k87d+aFIzAZhcBw== - dependencies: - "@opentelemetry/core" "2.1.0" - -"@opentelemetry/propagator-jaeger@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.1.0.tgz#4bb040a4cb49e8f04523a1ad29b3b247717ba7ae" - integrity sha512-QYo7vLyMjrBCUTpwQBF/e+rvP7oGskrSELGxhSvLj5gpM0az9oJnu/0O4l2Nm7LEhAff80ntRYKkAcSwVgvSVQ== - dependencies: - "@opentelemetry/core" "2.1.0" - -"@opentelemetry/resources@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/resources/-/resources-2.1.0.tgz#11772e732af4f27953cf55567a6630d8b4d8282d" - integrity sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/resources@2.5.0": - version "2.5.0" - resolved "https://registry.npmmirror.com/@opentelemetry/resources/-/resources-2.5.0.tgz#e7a575b2c534961a9db5153f9498931c786a607a" - integrity sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g== - dependencies: - "@opentelemetry/core" "2.5.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/sdk-logs@0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/sdk-logs/-/sdk-logs-0.205.0.tgz#4a302b1507e753d2c4d9bddb5243aecf5eb7156b" - integrity sha512-nyqhNQ6eEzPWQU60Nc7+A5LIq8fz3UeIzdEVBQYefB4+msJZ2vuVtRuk9KxPMw1uHoHDtYEwkr2Ct0iG29jU8w== - dependencies: - "@opentelemetry/api-logs" "0.205.0" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/resources" "2.1.0" - -"@opentelemetry/sdk-metrics@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz#fbb9b270ee56c29feba885062e5c0418213fccf2" - integrity sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/resources" "2.1.0" - -"@opentelemetry/sdk-node@^0.205.0": - version "0.205.0" - resolved "https://registry.npmmirror.com/@opentelemetry/sdk-node/-/sdk-node-0.205.0.tgz#ee2f414b40bf558a976537550d87ed90d0234e12" - integrity sha512-Y4Wcs8scj/Wy1u61pX1ggqPXPtCsGaqx/UnFu7BtRQE1zCQR+b0h56K7I0jz7U2bRlPUZIFdnNLtoaJSMNzz2g== - dependencies: - "@opentelemetry/api-logs" "0.205.0" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/exporter-logs-otlp-grpc" "0.205.0" - "@opentelemetry/exporter-logs-otlp-http" "0.205.0" - "@opentelemetry/exporter-logs-otlp-proto" "0.205.0" - "@opentelemetry/exporter-metrics-otlp-grpc" "0.205.0" - "@opentelemetry/exporter-metrics-otlp-http" "0.205.0" - "@opentelemetry/exporter-metrics-otlp-proto" "0.205.0" - "@opentelemetry/exporter-prometheus" "0.205.0" - "@opentelemetry/exporter-trace-otlp-grpc" "0.205.0" - "@opentelemetry/exporter-trace-otlp-http" "0.205.0" - "@opentelemetry/exporter-trace-otlp-proto" "0.205.0" - "@opentelemetry/exporter-zipkin" "2.1.0" - "@opentelemetry/instrumentation" "0.205.0" - "@opentelemetry/propagator-b3" "2.1.0" - "@opentelemetry/propagator-jaeger" "2.1.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/sdk-logs" "0.205.0" - "@opentelemetry/sdk-metrics" "2.1.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - "@opentelemetry/sdk-trace-node" "2.1.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/sdk-trace-base@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz#9d31474824e9ed215f94bf71260d5321f64d402a" - integrity sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ== - dependencies: - "@opentelemetry/core" "2.1.0" - "@opentelemetry/resources" "2.1.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/sdk-trace-base@^2.1.0": - version "2.5.0" - resolved "https://registry.npmmirror.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz#4b96ae2494a4de5e3bfb36ef7459b30a1ce3332a" - integrity sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ== - dependencies: - "@opentelemetry/core" "2.5.0" - "@opentelemetry/resources" "2.5.0" - "@opentelemetry/semantic-conventions" "^1.29.0" - -"@opentelemetry/sdk-trace-node@2.1.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.1.0.tgz#cc066fefa87dd0898e77397224501a5f116397e0" - integrity sha512-SvVlBFc/jI96u/mmlKm86n9BbTCbQ35nsPoOohqJX6DXH92K0kTe73zGY5r8xoI1QkjR9PizszVJLzMC966y9Q== - dependencies: - "@opentelemetry/context-async-hooks" "2.1.0" - "@opentelemetry/core" "2.1.0" - "@opentelemetry/sdk-trace-base" "2.1.0" - -"@opentelemetry/semantic-conventions@^1.29.0": - version "1.39.0" - resolved "https://registry.npmmirror.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz#f653b2752171411feb40310b8a8953d7e5c543b7" - integrity sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg== - "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.npmmirror.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.npmmirror.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.npmmirror.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.npmmirror.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.npmmirror.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - "@rmp135/sql-ts@^2.2.0": version "2.2.0" resolved "https://registry.npmmirror.com/@rmp135/sql-ts/-/sql-ts-2.2.0.tgz#1027956e82c44bfc59be86664bf034d3caebd45e" @@ -1432,12 +647,7 @@ resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@sqlite.org/sqlite-wasm@^3.50.1-build1": - version "3.50.1-build1" - resolved "https://registry.npmmirror.com/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.50.1-build1.tgz#67dd9944b0e37ddb0ef2c8b195baa74ece838e44" - integrity sha512-yH4M/SHN98NibniIwTVk6rwTJjy7n39l7zwWY3u+qsfZBGTi4lC1uEl2NDvIlkzsFtfCBvHBJJFJ1iuU3UzzEQ== - -"@standard-schema/spec@1.1.0", "@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0": +"@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" integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== @@ -1486,7 +696,7 @@ dependencies: "@types/node" "*" -"@types/debug@^4.1.12", "@types/debug@^4.1.6": +"@types/debug@^4.1.6": version "4.1.12" resolved "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== @@ -1570,7 +780,7 @@ resolved "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -"@types/node@*", "@types/node@>=13.7.0": +"@types/node@*": version "25.1.0" resolved "https://registry.npmmirror.com/@types/node/-/node-25.1.0.tgz#95cc584f1f478301efc86de4f1867e5875e83571" integrity sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA== @@ -1629,17 +839,12 @@ "@types/http-errors" "*" "@types/node" "*" -"@types/uuid@^10.0.0": - version "10.0.0" - resolved "https://registry.npmmirror.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" - integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== - "@types/verror@^1.10.3": version "1.10.11" 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.4": +"@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== @@ -1653,16 +858,6 @@ dependencies: "@types/node" "*" -"@ungap/structured-clone@^1.2.0": - version "1.3.0" - resolved "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - -"@ungap/with-resolvers@^0.1.0": - version "0.1.0" - resolved "https://registry.npmmirror.com/@ungap/with-resolvers/-/with-resolvers-0.1.0.tgz#63a07b13bbf10ffff074a36498cce8d82aeeecc4" - integrity sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw== - "@vercel/oidc@3.1.0": version "3.1.0" resolved "https://registry.npmmirror.com/@vercel/oidc/-/oidc-3.1.0.tgz#066caee449b84079f33c7445fc862464fe10ec32" @@ -1673,16 +868,6 @@ resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608" integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw== -"@zenoaihq/tson@^1.0.0": - version "1.1.0" - resolved "https://registry.npmmirror.com/@zenoaihq/tson/-/tson-1.1.0.tgz#3ae15f53fc88f5063fefa7a4cfe836fd5a28e5bb" - integrity sha512-P9AdkWd7nS4qiEEWL6T5tugSH7F9fGvkWl/lyZpLSqIPjZbH08pNj3AhXfUY8RyAw17HjB7kUllQc+/27r4ukg== - -a-sync-waterfall@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7" - integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA== - abbrev@1: version "1.1.1" resolved "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1693,13 +878,6 @@ abbrev@^3.0.0: resolved "https://registry.npmmirror.com/abbrev/-/abbrev-3.0.1.tgz#8ac8b3b5024d31464fe2a5feeea9f4536bf44025" integrity sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - accepts@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" @@ -1708,16 +886,6 @@ accepts@^2.0.0: mime-types "^3.0.0" negotiator "^1.0.0" -acorn-import-attributes@^1.9.5: - version "1.9.5" - resolved "https://registry.npmmirror.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" - integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== - -acorn@^8.14.0: - version "8.15.0" - resolved "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1755,13 +923,6 @@ ai@^6.0.67: "@ai-sdk/provider-utils" "4.0.13" "@opentelemetry/api" "1.9.0" -ajv-formats@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" - integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== - dependencies: - ajv "^8.0.0" - ajv-keywords@^3.4.1: version "3.5.2" resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -1777,23 +938,6 @@ ajv@^6.10.0, ajv@^6.12.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.17.1: - version "8.17.1" - resolved "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-escapes@^7.0.0: - version "7.2.0" - resolved "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-7.2.0.tgz#31b25afa3edd3efc09d98c2fee831d460ff06b49" - integrity sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw== - dependencies: - environment "^1.0.0" - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -1818,11 +962,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - ansi-styles@^6.1.0: version "6.2.3" resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" @@ -1886,32 +1025,6 @@ app-builder-lib@26.4.0: resolved "https://registry.npmmirror.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1" integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew== -archiver-utils@^5.0.0, archiver-utils@^5.0.2: - version "5.0.2" - resolved "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" - integrity sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA== - dependencies: - glob "^10.0.0" - graceful-fs "^4.2.0" - is-stream "^2.0.1" - lazystream "^1.0.0" - lodash "^4.17.15" - normalize-path "^3.0.0" - readable-stream "^4.0.0" - -archiver@^7.0.1: - version "7.0.1" - resolved "https://registry.npmmirror.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61" - integrity sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ== - dependencies: - archiver-utils "^5.0.2" - async "^3.2.4" - buffer-crc32 "^1.0.0" - readable-stream "^4.0.0" - readdir-glob "^1.1.2" - tar-stream "^3.0.0" - zip-stream "^6.0.1" - are-we-there-yet@^3.0.0: version "3.0.1" resolved "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" @@ -1920,13 +1033,6 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1937,7 +1043,7 @@ array-find-index@^1.0.2: resolved "https://registry.npmmirror.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== -asap@^2.0.0, asap@^2.0.3: +asap@^2.0.0: version "2.0.6" resolved "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== @@ -1957,7 +1063,7 @@ async-exit-hook@^2.0.1: resolved "https://registry.npmmirror.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3" integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== -async@^3.2.4, async@^3.2.6: +async@^3.2.6: version "3.2.6" resolved "https://registry.npmmirror.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== @@ -1988,21 +1094,11 @@ axios@^1.13.2: form-data "^4.0.4" proxy-from-env "^1.1.0" -b4a@^1.6.4: - version "1.7.3" - resolved "https://registry.npmmirror.com/b4a/-/b4a-1.7.3.tgz#24cf7ccda28f5465b66aec2bac69e32809bf112f" - integrity sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -bare-events@^2.7.0: - version "2.8.2" - resolved "https://registry.npmmirror.com/bare-events/-/bare-events-2.8.2.tgz#7b3e10bd8e1fc80daf38bb516921678f566ab89f" - integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ== - base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -2049,11 +1145,6 @@ bl@^4.0.3, bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" -bluebird@~3.7.2: - version "3.7.2" - resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - body-parser@^2.2.1: version "2.2.2" resolved "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c" @@ -2096,11 +1187,6 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -buffer-crc32@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" - integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== - buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -2124,14 +1210,6 @@ buffer@^5.1.0, buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - builder-util-runtime@9.5.1: version "9.5.1" resolved "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz#74125fb374d1ecbf472ae1787485485ff7619702" @@ -2243,16 +1321,6 @@ call-bound@^1.0.2: call-bind-apply-helpers "^1.0.2" get-intrinsic "^1.3.0" -camelcase@6: - version "6.3.0" - resolved "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -camelize-ts@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/camelize-ts/-/camelize-ts-3.0.0.tgz#b9a7b4ff802464dc3d6475637a64a9742ad3db09" - integrity sha512-cgRwKKavoDKLTjO4FQTs3dRBePZp/2Y9Xpud0FhuCOTE86M2cniKN4CCXgRnsyXNMmQMifVHcv6SPaMtTx6ofQ== - chalk@^2.4.1: version "2.4.2" resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -2278,21 +1346,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.6.2: - version "5.6.2" - resolved "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" - integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== - change-case@^5.4.3: version "5.4.4" resolved "https://registry.npmmirror.com/change-case/-/change-case-5.4.4.tgz#0d52b507d8fb8f204343432381d1a6d7bff97a02" integrity sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w== -chardet@^2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/chardet/-/chardet-2.1.1.tgz#5c75593704a642f71ee53717df234031e65373c8" - integrity sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ== - chokidar@^3.5.2: version "3.6.0" resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -2333,11 +1391,6 @@ ci-info@4.3.1, ci-info@^4.2.0: resolved "https://registry.npmmirror.com/ci-info/-/ci-info-4.3.1.tgz#355ad571920810b5623e11d40232f443f16f1daa" integrity sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA== -cjs-module-lexer@^1.2.2: - version "1.4.3" - resolved "https://registry.npmmirror.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" - integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== - clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -2363,11 +1416,6 @@ cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" -cli-width@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" - integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== - cliui@^8.0.1: version "8.0.1" resolved "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -2389,18 +1437,6 @@ clone@^1.0.2: resolved "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -coincident@^1.2.3: - version "1.2.3" - resolved "https://registry.npmmirror.com/coincident/-/coincident-1.2.3.tgz#7fbb393d227cf397aa9df180a1416c9517caf8ff" - integrity sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA== - dependencies: - "@ungap/structured-clone" "^1.2.0" - "@ungap/with-resolvers" "^0.1.0" - gc-hook "^0.3.1" - proxy-target "^3.0.2" - optionalDependencies: - ws "^8.16.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2447,7 +1483,7 @@ commander@^10.0.0: resolved "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -commander@^5.0.0, commander@^5.1.0: +commander@^5.0.0: version "5.1.0" resolved "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== @@ -2457,17 +1493,6 @@ compare-version@^0.1.2: resolved "https://registry.npmmirror.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A== -compress-commons@^6.0.2: - version "6.0.2" - resolved "https://registry.npmmirror.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e" - integrity sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg== - dependencies: - crc-32 "^1.2.0" - crc32-stream "^6.0.0" - is-stream "^2.0.1" - normalize-path "^3.0.0" - readable-stream "^4.0.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2478,13 +1503,6 @@ console-control-strings@^1.1.0: resolved "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -console-table-printer@^2.12.1: - version "2.15.0" - resolved "https://registry.npmmirror.com/console-table-printer/-/console-table-printer-2.15.0.tgz#5c808204640b8f024d545bde8aabe5d344dfadc1" - integrity sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw== - dependencies: - simple-wcswidth "^1.1.2" - content-disposition@^1.0.0: version "1.0.1" resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b" @@ -2495,25 +1513,12 @@ content-type@^1.0.5: resolved "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -cookie-parser@^1.4.7: - version "1.4.7" - resolved "https://registry.npmmirror.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26" - integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw== - dependencies: - cookie "0.7.2" - cookie-signature "1.0.6" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - cookie-signature@^1.2.1: version "1.2.2" resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== -cookie@0.7.2, cookie@^0.7.1: +cookie@^0.7.1: version "0.7.2" resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== @@ -2523,11 +1528,6 @@ 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== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - cors@^2.8.5: version "2.8.6" resolved "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" @@ -2536,19 +1536,6 @@ cors@^2.8.5: object-assign "^4" vary "^1" -crc-32@^1.2.0: - version "1.2.2" - resolved "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" - integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== - -crc32-stream@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430" - integrity sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g== - dependencies: - crc-32 "^1.2.0" - readable-stream "^4.0.0" - crc@^3.8.0: version "3.8.0" resolved "https://registry.npmmirror.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" @@ -2556,7 +1543,15 @@ crc@^3.8.0: dependencies: buffer "^5.1.0" -cross-spawn@^7.0.1, cross-spawn@^7.0.5, cross-spawn@^7.0.6: +cross-env@^10.1.0: + version "10.1.0" + resolved "https://registry.npmmirror.com/cross-env/-/cross-env-10.1.0.tgz#cfd2a6200df9ed75bfb9cb3d7ce609c13ea21783" + integrity sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw== + dependencies: + "@epic-web/invariant" "^1.0.0" + cross-spawn "^7.0.6" + +cross-spawn@^7.0.1, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -2565,11 +1560,6 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.5, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -data-uri-to-buffer@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" - integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== - debug@2.6.9: version "2.6.9" resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2577,7 +1567,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.3.5, debug@^4.4.0, debug@^4.4.3: +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== @@ -2603,16 +1593,6 @@ debuglog@^1.0.1: resolved "https://registry.npmmirror.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== -decamelize@1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decimal.js@^10.6.0: - version "10.6.0" - resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" - integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -2670,11 +1650,6 @@ depd@^2.0.0, depd@~2.0.0: resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -detect-libc@2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" - integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== - detect-libc@^2.0.0, detect-libc@^2.0.1, detect-libc@^2.1.2: version "2.1.2" resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" @@ -2745,11 +1720,6 @@ dotenv@^17.2.3: resolved "https://registry.npmmirror.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2" integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== -drizzle-orm@^0.44.5: - version "0.44.7" - resolved "https://registry.npmmirror.com/drizzle-orm/-/drizzle-orm-0.44.7.tgz#6f67c80c6b64d9c18a401e8e5d03dfa67b652011" - integrity sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ== - dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -2759,13 +1729,6 @@ dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" -duplexer2@~0.1.4: - version "0.1.4" - resolved "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== - dependencies: - readable-stream "^2.0.2" - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -2873,11 +1836,6 @@ env-paths@^2.2.0: resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== -environment@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" - integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== - err-code@^2.0.2: version "2.0.3" resolved "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" @@ -2972,55 +1930,16 @@ esm@^3.2.25: resolved "https://registry.npmmirror.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - etag@^1.8.1: version "1.8.1" resolved "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -eventemitter3@^5.0.1: - version "5.0.4" - resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz#a86d66170433712dde814707ac52b5271ceb1feb" - integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== - -events-universal@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/events-universal/-/events-universal-1.0.1.tgz#b56a84fd611b6610e0a2d0f09f80fdf931e2dfe6" - integrity sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw== - dependencies: - bare-events "^2.7.0" - -events@^3.3.0: - version "3.3.0" - resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -eventsource-parser@^3.0.0, eventsource-parser@^3.0.1, eventsource-parser@^3.0.6: +eventsource-parser@^3.0.6: version "3.0.6" resolved "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== -eventsource@^3.0.2: - version "3.0.7" - resolved "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989" - integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== - dependencies: - eventsource-parser "^3.0.1" - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -3031,16 +1950,6 @@ exponential-backoff@^3.1.1: resolved "https://registry.npmmirror.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6" integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA== -express-rate-limit@^7.5.0: - version "7.5.1" - resolved "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-7.5.1.tgz#8c3a42f69209a3a1c969890070ece9e20a879dec" - integrity sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw== - -express-sse@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/express-sse/-/express-sse-1.0.0.tgz#6703dd1e9b1a9f9292d6dc178d48851d5ad0b582" - integrity sha512-ny3nCXjrWwQki8edZe0DwuXjLbw0ga3pc0CZX19VOB0GKuUnlIH2fqPBwTJcGHZPtmjJ2EVNPVeCgORpX6Nsrw== - express-ws@^5.0.2: version "5.0.2" resolved "https://registry.npmmirror.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" @@ -3048,7 +1957,7 @@ express-ws@^5.0.2: dependencies: ws "^7.4.6" -express@^5.0.1, express@^5.1.0, express@^5.2.1: +express@^5.2.1: version "5.2.1" resolved "https://registry.npmmirror.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== @@ -3098,16 +2007,11 @@ extsprintf@^1.2.0: resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-fifo@^1.2.0, fast-fifo@^1.3.2: - version "1.3.2" - resolved "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" - integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== - fast-glob@^3.3.3: version "3.3.3" resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" @@ -3124,12 +2028,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-uri@^3.0.1: - version "3.1.0" - resolved "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" - integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== - -fastq@^1.19.1, fastq@^1.6.0: +fastq@^1.6.0: version "1.20.1" resolved "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== @@ -3148,14 +2047,6 @@ fdir@^6.5.0: resolved "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.2.0" - resolved "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" - integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== - dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -3211,13 +2102,6 @@ form-data@^4.0.0, form-data@^4.0.4, form-data@^4.0.5: hasown "^2.0.2" mime-types "^2.1.12" -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" - integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== - dependencies: - fetch-blob "^3.1.2" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -3228,13 +2112,6 @@ fresh@^2.0.0: resolved "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== -front-matter@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/front-matter/-/front-matter-4.0.2.tgz#b14e54dc745cfd7293484f3210d15ea4edd7f4d5" - integrity sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg== - dependencies: - js-yaml "^3.13.1" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -3249,7 +2126,7 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^11.1.1, fs-extra@^11.2.0: +fs-extra@^11.1.1: version "11.3.3" resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.3.tgz#a27da23b72524e81ac6c3815cc0179b8c74c59ee" integrity sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg== @@ -3320,11 +2197,6 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" -gc-hook@^0.3.1: - version "0.3.1" - resolved "https://registry.npmmirror.com/gc-hook/-/gc-hook-0.3.1.tgz#033e7f493e6a79682eeca8abee1a5eed61746476" - integrity sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A== - get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -3390,7 +2262,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^10.0.0, glob@^10.2.2: +glob@^10.2.2: version "10.5.0" resolved "https://registry.npmmirror.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== @@ -3456,7 +2328,7 @@ got@^11.7.0, got@^11.8.5: p-cancelable "^2.0.0" responselike "^2.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.6: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3483,11 +2355,6 @@ has-flag@^4.0.0: resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-flag@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/has-flag/-/has-flag-5.0.1.tgz#5483db2ae02a472d1d0691462fc587d1843cd940" - integrity sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA== - has-property-descriptors@^1.0.0: version "1.0.2" resolved "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" @@ -3519,6 +2386,11 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hono@^4.6.14: + version "4.11.7" + resolved "https://registry.npmmirror.com/hono/-/hono-4.11.7.tgz#f5b8d0b0b503ef0d913a246012dda52ea23dbe53" + integrity sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw== + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -3617,7 +2489,7 @@ iconv-lite@^0.7.0, iconv-lite@~0.7.0: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13, ieee754@^1.2.1: +ieee754@^1.1.13: version "1.2.1" resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3627,11 +2499,6 @@ ignore-by-default@^1.0.1: resolved "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== -immer@^10.1.3: - version "10.2.0" - resolved "https://registry.npmmirror.com/immer/-/immer-10.2.0.tgz#88a4ce06a1af64172d254b70f7cb04df51c871b1" - integrity sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw== - import-from@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" @@ -3639,16 +2506,6 @@ import-from@^3.0.0: dependencies: resolve-from "^5.0.0" -import-in-the-middle@^1.8.1: - version "1.15.0" - resolved "https://registry.npmmirror.com/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz#9e20827a322bbadaeb5e3bac49ea8f6d4685fdd8" - integrity sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA== - dependencies: - acorn "^8.14.0" - acorn-import-attributes "^1.9.5" - cjs-module-lexer "^1.2.2" - module-details-from-path "^1.0.3" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3672,7 +2529,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3738,11 +2595,6 @@ is-lambda@^1.0.1: resolved "https://registry.npmmirror.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-network-error@^1.1.0, is-network-error@^1.2.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/is-network-error/-/is-network-error-1.3.0.tgz#2ce62cbca444abd506f8a900f39d20b898d37512" - integrity sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -3763,21 +2615,11 @@ is-retry-allowed@^2.2.0: resolved "https://registry.npmmirror.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d" integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg== -is-stream@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isbinaryfile@^4.0.8: version "4.0.10" resolved "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" @@ -3807,11 +2649,6 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jaison@=2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/jaison/-/jaison-2.0.2.tgz#584bb5ae6ad22a89be7c615a823b281362da5eb3" - integrity sha512-aDko6D0+EXarmfVG736LP5VH7r/9gpFfWG5yA1/fBW0elrUVrS5K22zkEb2/7oopJN2JztSWx7vgbRsYIyOX2Q== - jake@^10.8.5: version "10.9.4" resolved "https://registry.npmmirror.com/jake/-/jake-10.9.4.tgz#d626da108c63d5cfb00ab5c25fadc7e0084af8e6" @@ -3826,36 +2663,11 @@ jiti@^2.4.2: resolved "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== -jose@^6.1.1: - version "6.1.3" - resolved "https://registry.npmmirror.com/jose/-/jose-6.1.3.tgz#8453d7be88af7bb7d64a0481d6a35a0145ba3ea5" - integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ== - -js-base64@^3.7.5: - version "3.7.8" - resolved "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.8.tgz#af44496bc09fa178ed9c4adf67eb2b46f5c6d2a4" - integrity sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow== - js-md5@^0.8.3: version "0.8.3" resolved "https://registry.npmmirror.com/js-md5/-/js-md5-0.8.3.tgz#921bab7efa95bfc9d62b87ee08a57f8fe4305b69" integrity sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ== -js-tiktoken@^1.0.12: - version "1.0.21" - resolved "https://registry.npmmirror.com/js-tiktoken/-/js-tiktoken-1.0.21.tgz#368a9957591a30a62997dd0c4cf30866f00f8221" - integrity sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g== - dependencies: - base64-js "^1.5.1" - -js-yaml@^3.13.1: - version "3.14.2" - resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" - integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@^4.1.0: version "4.1.1" resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" @@ -3878,16 +2690,6 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-schema-typed@^8.0.2: - version "8.0.2" - resolved "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz#e98ee7b1899ff4a184534d1f167c288c66bbeff4" - integrity sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA== - json-schema@^0.4.0: version "0.4.0" resolved "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" @@ -3903,11 +2705,6 @@ json5@^2.2.3: resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonata@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/jsonata/-/jsonata-2.1.0.tgz#75547a3b1d3b83a61a9c98e01ce5d1d399832f34" - integrity sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w== - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -3984,59 +2781,11 @@ knex@^3.1.0: tarn "^3.0.2" tildify "2.0.0" -langchain@^1.2.10: - version "1.2.15" - resolved "https://registry.npmmirror.com/langchain/-/langchain-1.2.15.tgz#653b4110e8fb0fbf6222cb585191ba2867da1641" - integrity sha512-4tIUBryI+JDJYnuyQKELqfjG6VgOYQq5eFE+5TX2H+gLkNDHNpdppnKWcsYWPBE/bbIdKMMNor99k+qluJur2A== - dependencies: - "@langchain/langgraph" "^1.1.2" - "@langchain/langgraph-checkpoint" "^1.0.0" - langsmith ">=0.4.0 <1.0.0" - uuid "^10.0.0" - zod "^3.25.76 || ^4" - -"langsmith@>=0.4.0 <1.0.0": - version "0.4.10" - resolved "https://registry.npmmirror.com/langsmith/-/langsmith-0.4.10.tgz#c878e37f389f4b2231a70a3b4396c362179bfc04" - integrity sha512-l9QP/a7RXBXdaoAnNx99X+TK8aul8Qe4us1oCybdMgDmYMLT5PAwlJactvSdTlT8NOeSoFThYa2N7ijznBNe9w== - dependencies: - "@types/uuid" "^10.0.0" - chalk "^4.1.2" - console-table-printer "^2.12.1" - p-queue "^6.6.2" - semver "^7.6.3" - uuid "^10.0.0" - lazy-val@^1.0.5: version "1.0.5" resolved "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== -lazystream@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" - integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== - dependencies: - readable-stream "^2.0.5" - -libsql@^0.5.22: - version "0.5.22" - resolved "https://registry.npmmirror.com/libsql/-/libsql-0.5.22.tgz#7a44ad55246b6abade60725ec1d0acabd5634c19" - integrity sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA== - dependencies: - "@neon-rs/load" "^0.0.4" - detect-libc "2.0.2" - optionalDependencies: - "@libsql/darwin-arm64" "0.5.22" - "@libsql/darwin-x64" "0.5.22" - "@libsql/linux-arm-gnueabihf" "0.5.22" - "@libsql/linux-arm-musleabihf" "0.5.22" - "@libsql/linux-arm64-gnu" "0.5.22" - "@libsql/linux-arm64-musl" "0.5.22" - "@libsql/linux-x64-gnu" "0.5.22" - "@libsql/linux-x64-musl" "0.5.22" - "@libsql/win32-x64-msvc" "0.5.22" - license-checker@^25.0.1: version "25.0.1" resolved "https://registry.npmmirror.com/license-checker/-/license-checker-25.0.1.tgz#4d14504478a5240a857bb3c21cd0491a00d761fa" @@ -4058,11 +2807,6 @@ lodash-es@^4.17.21: resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz#58c4360fd1b5d33afc6c0bbd3d1149349b1138e0" integrity sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg== -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== - lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" @@ -4116,11 +2860,6 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -long@^5.0.0: - version "5.3.2" - resolved "https://registry.npmmirror.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" - integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== - lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -4241,11 +2980,6 @@ mime@^2.5.2: resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mime@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/mime/-/mime-4.1.0.tgz#ec55df7aa21832a36d44f0bbee5c04639b27802f" - integrity sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -4275,7 +3009,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1, minimatch@^5.1.0: +minimatch@^5.0.1: version "5.1.6" resolved "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -4400,11 +3134,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -module-details-from-path@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/module-details-from-path/-/module-details-from-path-1.0.4.tgz#b662fdcd93f6c83d3f25289da0ce81c8d9685b94" - integrity sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w== - morgan@^1.10.1: version "1.10.1" resolved "https://registry.npmmirror.com/morgan/-/morgan-1.10.1.tgz#4e02e6a4465a48e26af540191593955d17f61570" @@ -4431,16 +3160,6 @@ ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mustache@^4.2.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" - integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== - -mute-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" - integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== - nanoid@^3.3.8: version "3.3.11" resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" @@ -4497,20 +3216,6 @@ node-api-version@^0.2.1: dependencies: semver "^7.3.5" -node-domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^3.3.2: - version "3.3.2" - resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" - integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - node-gyp@8.x: version "8.4.1" resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" @@ -4543,11 +3248,6 @@ node-gyp@^11.2.0: tinyglobby "^0.2.12" which "^5.0.0" -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - nodemon@^3.1.11: version "3.1.11" resolved "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.11.tgz#04a54d1e794fbec9d8f6ffd8bf1ba9ea93a756ed" @@ -4564,6 +3264,11 @@ nodemon@^3.1.11: touch "^3.1.0" undefsafe "^2.0.5" +non-error@^0.1.0: + version "0.1.0" + resolved "https://registry.npmmirror.com/non-error/-/non-error-0.1.0.tgz#b78b7d9a67ccb03ac979f9758813336ca7521cf2" + integrity sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ== + nopt@^4.0.1: version "4.0.3" resolved "https://registry.npmmirror.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" @@ -4628,15 +3333,6 @@ npmlog@^6.0.0: gauge "^4.0.3" set-blocking "^2.0.0" -nunjucks@^3.2.4: - version "3.2.4" - resolved "https://registry.npmmirror.com/nunjucks/-/nunjucks-3.2.4.tgz#f0878eef528ce7b0aa35d67cc6898635fd74649e" - integrity sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ== - dependencies: - a-sync-waterfall "^1.0.0" - asap "^2.0.3" - commander "^5.1.0" - object-assign@^4: version "4.1.1" resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4685,11 +3381,6 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -openai@^6.14.0, openai@^6.16.0: - version "6.17.0" - resolved "https://registry.npmmirror.com/openai/-/openai-6.17.0.tgz#33bb4977a48025e2ceb831a7dc0c7798bfbc394f" - integrity sha512-NHRpPEUPzAvFOAFs9+9pC6+HCw/iWsYsKCMPXH5Kw7BpMxqd8g/A07/1o7Gx2TWtCnzevVRyKMRFqyiHyAlqcA== - ora@^5.1.0: version "5.4.1" resolved "https://registry.npmmirror.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" @@ -4728,11 +3419,6 @@ p-cancelable@^2.0.0: resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - "p-limit@^3.1.0 ": version "3.1.0" resolved "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -4752,41 +3438,6 @@ p-map@^7.0.2: resolved "https://registry.npmmirror.com/p-map/-/p-map-7.0.4.tgz#b81814255f542e252d5729dca4d66e5ec14935b8" integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ== -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.npmmirror.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-queue@^9.0.1: - version "9.1.0" - resolved "https://registry.npmmirror.com/p-queue/-/p-queue-9.1.0.tgz#846e517c461fb6e3cf8fc09904e57d342ac448b2" - integrity sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw== - dependencies: - eventemitter3 "^5.0.1" - p-timeout "^7.0.0" - -p-retry@^7.0.0, p-retry@^7.1.1: - version "7.1.1" - resolved "https://registry.npmmirror.com/p-retry/-/p-retry-7.1.1.tgz#7470fdecb1152ba50f1334e48378c9e401330e24" - integrity sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w== - dependencies: - is-network-error "^1.1.0" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-timeout@^7.0.0: - version "7.0.1" - resolved "https://registry.npmmirror.com/p-timeout/-/p-timeout-7.0.1.tgz#95680a6aa693c530f14ac337b8bd32d4ec6ae4f0" - integrity sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg== - package-json-from-dist@^1.0.0: version "1.0.1" resolved "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" @@ -4860,11 +3511,6 @@ pify@^4.0.1: resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pkce-challenge@^5.0.0: - version "5.0.1" - resolved "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz#3b4446865b17b1745e9ace2016a31f48ddf6230d" - integrity sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ== - plist@3.1.0, plist@^3.0.4, plist@^3.0.5, plist@^3.1.0: version "3.1.0" resolved "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" @@ -4902,16 +3548,6 @@ proc-log@^5.0.0: resolved "https://registry.npmmirror.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8" integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - progress@^2.0.3: version "2.0.3" resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -4922,11 +3558,6 @@ promise-inflight@^1.0.1: resolved "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== -promise-limit@^2.7.0: - version "2.7.0" - resolved "https://registry.npmmirror.com/promise-limit/-/promise-limit-2.7.0.tgz#eb5737c33342a030eaeaecea9b3d3a93cb592b26" - integrity sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw== - promise-retry@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" @@ -4935,24 +3566,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -protobufjs@^7.3.0, protobufjs@^7.5.3: - version "7.5.4" - resolved "https://registry.npmmirror.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" - integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - proxy-addr@^2.0.7: version "2.0.7" resolved "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -4966,11 +3579,6 @@ proxy-from-env@^1.1.0: resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -proxy-target@^3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/proxy-target/-/proxy-target-3.0.2.tgz#c18bff54989183c2c466ac197f31d29203054267" - integrity sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ== - pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" @@ -5014,17 +3622,12 @@ qwen-ai-provider@^0.1.1: "@ai-sdk/provider" "^1.0.7" "@ai-sdk/provider-utils" "^2.1.6" -radix3@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/radix3/-/radix3-1.1.2.tgz#fd27d2af3896c6bf4bcdfab6427c69c2afc69ec0" - integrity sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA== - range-parser@^1.2.1: version "1.2.1" resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@^3.0.0, raw-body@^3.0.1: +raw-body@^3.0.1: version "3.0.2" resolved "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== @@ -5075,19 +3678,6 @@ read-package-json@^2.0.0: normalize-package-data "^2.0.0" npm-normalize-package-bin "^1.0.0" -readable-stream@^2.0.2, readable-stream@^2.0.5: - version "2.3.8" - resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -5097,24 +3687,6 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^4.0.0: - version "4.7.0" - resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" - integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - string_decoder "^1.3.0" - -readdir-glob@^1.1.2: - version "1.1.3" - resolved "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" - integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== - dependencies: - minimatch "^5.1.0" - readdir-scoped-modules@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" @@ -5149,20 +3721,6 @@ require-directory@^2.1.1: resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -require-in-the-middle@^7.1.1: - version "7.5.2" - resolved "https://registry.npmmirror.com/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz#dc25b148affad42e570cf0e41ba30dc00f1703ec" - integrity sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ== - dependencies: - debug "^4.3.5" - module-details-from-path "^1.0.3" - resolve "^1.22.8" - resedit@^1.7.0: version "1.7.2" resolved "https://registry.npmmirror.com/resedit/-/resedit-1.7.2.tgz#b1041170b99811710c13f949c7d225871de4cc78" @@ -5185,7 +3743,7 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.8: +resolve@^1.10.0, resolve@^1.20.0: version "1.22.11" resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== @@ -5261,7 +3819,7 @@ runtime-required@^1.1.0: resolved "https://registry.npmmirror.com/runtime-required/-/runtime-required-1.1.0.tgz#a000a50c2748dba123f4dac5105e66d4599519c4" integrity sha512-yX97f5E0WfNpcQnfVjap6vzQcvErkYYCx6eTK4siqGEdC8lglwypUFgZVTX7ShvIlgfkC4XGFl9O1KTYcff0pw== -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -5330,6 +3888,14 @@ send@^1.1.0, send@^1.2.0: range-parser "^1.2.1" statuses "^2.0.2" +serialize-error@^13.0.1: + version "13.0.1" + resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-13.0.1.tgz#dd1e1bf6d3e3d01037d126bd95e919f48b0c8ec0" + integrity sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA== + dependencies: + non-error "^0.1.0" + type-fest "^5.4.1" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -5448,7 +4014,7 @@ signal-exit@^3.0.2, signal-exit@^3.0.7: resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1, signal-exit@^4.1.0: +signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -5474,11 +4040,6 @@ simple-update-notifier@2.0.0, simple-update-notifier@^2.0.0: dependencies: semver "^7.5.3" -simple-wcswidth@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz#66722f37629d5203f9b47c5477b1225b85d6525b" - integrity sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw== - slice-ansi@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" @@ -5591,11 +4152,6 @@ sprintf-js@^1.1.2: resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - sqlite3@^5.1.7: version "5.1.7" resolved "https://registry.npmmirror.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" @@ -5608,14 +4164,6 @@ sqlite3@^5.1.7: optionalDependencies: node-gyp "8.x" -sqlocal@^0.14.2: - version "0.14.2" - resolved "https://registry.npmmirror.com/sqlocal/-/sqlocal-0.14.2.tgz#66ef485f4b33bc1c67311a1ed44873f7116964c9" - integrity sha512-U2NLx7rUABfBaOc9404gljuMirpXR7Tlvza/oA2A5yFfKG1ntCNvxEe4cdNKsirjZyI8ri8uW5Ise9NJOyBrBw== - dependencies: - "@sqlite.org/sqlite-wasm" "^3.50.1-build1" - coincident "^1.2.3" - ssri@^12.0.0: version "12.0.0" resolved "https://registry.npmmirror.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832" @@ -5640,20 +4188,6 @@ statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: resolved "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== -streamx@^2.15.0: - version "2.23.0" - resolved "https://registry.npmmirror.com/streamx/-/streamx-2.23.0.tgz#7d0f3d00d4a6c5de5728aecd6422b4008d66fd0b" - integrity sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg== - dependencies: - events-universal "^1.0.0" - fast-fifo "^1.3.2" - text-decoder "^1.1.0" - -strict-event-emitter@^0.5.1: - version "0.5.1" - resolved "https://registry.npmmirror.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" - integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -5681,20 +4215,13 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@^1.1.1, string_decoder@^1.3.0: +string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -5728,11 +4255,6 @@ sumchecker@^3.0.1: dependencies: debug "^4.1.0" -supports-color@^10.2.2: - version "10.2.2" - resolved "https://registry.npmmirror.com/supports-color/-/supports-color-10.2.2.tgz#466c2978cc5cd0052d542a0b576461c2b802ebb4" - integrity sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g== - supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -5747,19 +4269,16 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^4.1.0: - version "4.4.0" - resolved "https://registry.npmmirror.com/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz#b25ed8e5ef67388d1ce1e83029c07df19d36b870" - integrity sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg== - dependencies: - has-flag "^5.0.1" - supports-color "^10.2.2" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tagged-tag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/tagged-tag/-/tagged-tag-1.0.0.tgz#a0b5917c2864cba54841495abfa3f6b13edcf4d6" + integrity sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng== + tar-fs@^2.0.0: version "2.1.4" resolved "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930" @@ -5781,15 +4300,6 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar-stream@^3.0.0: - version "3.1.7" - resolved "https://registry.npmmirror.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" - integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== - dependencies: - b4a "^1.6.4" - fast-fifo "^1.2.0" - streamx "^2.15.0" - tar@^6.0.2, tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.1.2: version "6.2.1" resolved "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" @@ -5826,21 +4336,6 @@ temp-file@^3.4.0: async-exit-hook "^2.0.1" fs-extra "^10.0.0" -terminal-link@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/terminal-link/-/terminal-link-5.0.0.tgz#f0447c8940418ab49b9b9bbc47a4ad2fa8ba81e7" - integrity sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA== - dependencies: - ansi-escapes "^7.0.0" - supports-hyperlinks "^4.1.0" - -text-decoder@^1.1.0: - version "1.2.3" - resolved "https://registry.npmmirror.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" - integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== - dependencies: - b4a "^1.6.4" - tildify@2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" @@ -5929,6 +4424,13 @@ type-fest@^0.13.1: resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== +type-fest@^5.4.1: + version "5.4.3" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-5.4.3.tgz#b4c7e028da129098911ee2162a0c30df8a1be904" + integrity sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA== + dependencies: + tagged-tag "^1.0.0" + type-is@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" @@ -5943,11 +4445,6 @@ typescript@^5.9.3: resolved "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== -ufo@^1.6.1: - version "1.6.3" - resolved "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz#799666e4e88c122a9659805e30b9dc071c3aed4f" - integrity sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q== - uglify-js@^3.1.4: version "3.19.3" resolved "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" @@ -6013,17 +4510,6 @@ unpipe@~1.0.0: resolved "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unzipper@^0.12.3: - version "0.12.3" - resolved "https://registry.npmmirror.com/unzipper/-/unzipper-0.12.3.tgz#31958f5eed7368ed8f57deae547e5a673e984f87" - integrity sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA== - dependencies: - bluebird "~3.7.2" - duplexer2 "~0.1.4" - fs-extra "^11.2.0" - graceful-fs "^4.2.2" - node-int64 "^0.4.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -6036,7 +4522,7 @@ utf8-byte-length@^1.0.1: resolved "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -6046,11 +4532,6 @@ util-extend@^1.0.1: resolved "https://registry.npmmirror.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" integrity sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA== -uuid@^10.0.0: - version "10.0.0" - resolved "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" - integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== - uuid@^13.0.0: version "13.0.0" resolved "https://registry.npmmirror.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" @@ -6095,11 +4576,6 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -web-streams-polyfill@^3.0.3: - version "3.3.3" - resolved "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" - integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== - which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -6135,15 +4611,6 @@ wordwrap@^1.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -6172,11 +4639,6 @@ ws@^7.4.6: resolved "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.13.0, ws@^8.16.0: - version "8.19.0" - resolved "https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" - integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== - xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" @@ -6197,11 +4659,6 @@ yallist@^5.0.0: resolved "https://registry.npmmirror.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== -yaml@^2.8.1: - version "2.8.2" - resolved "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5" - integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A== - yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -6233,11 +4690,6 @@ yocto-queue@^0.1.0: resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -yoctocolors-cjs@^2.1.3: - version "2.1.3" - resolved "https://registry.npmmirror.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" - integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== - zhipu-ai-provider@^0.2.2: version "0.2.2" resolved "https://registry.npmmirror.com/zhipu-ai-provider/-/zhipu-ai-provider-0.2.2.tgz#cbee428475b1c2fca446f273ac09006ef86f6f00" @@ -6246,33 +4698,7 @@ zhipu-ai-provider@^0.2.2: "@ai-sdk/provider" "^2.0.0" "@ai-sdk/provider-utils" "^3.0.0" -zip-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb" - integrity sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA== - dependencies: - archiver-utils "^5.0.0" - compress-commons "^6.0.2" - readable-stream "^4.0.0" - -zod-from-json-schema@^0.0.5: - version "0.0.5" - resolved "https://registry.npmmirror.com/zod-from-json-schema/-/zod-from-json-schema-0.0.5.tgz#b62b87da65b3c13a78913aa3643761d423d5487e" - integrity sha512-zYEoo86M1qpA1Pq6329oSyHLS785z/mTwfr9V1Xf/ZLhuuBGaMlDGu/pDVGVUe4H4oa1EFgWZT53DP0U3oT9CQ== - dependencies: - zod "^3.24.2" - -zod-to-json-schema@^3.24.6, zod-to-json-schema@^3.25.0: - version "3.25.1" - resolved "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz#7f24962101a439ddade2bf1aeab3c3bfec7d84ba" - integrity sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA== - -zod@^3.24.2, zod@^3.25.67: - version "3.25.76" - resolved "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" - integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== - -"zod@^3.25 || ^4.0", "zod@^3.25.76 || ^4", zod@^4.3.5: +zod@^4.3.5: version "4.3.6" resolved "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a" integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==