diff --git a/.gitignore b/.gitignore index e204fe12..d86054dd 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ nbdist/ *.xml.versionsBackup *.swp data/ +logs/ !*/build/*.java !*/build/*.html @@ -52,3 +53,4 @@ data/ .flattened-pom.xml /.claude/settings.local.json +/docs/docker/milvus/volumes/ diff --git a/RAG_TEST_REPORT_2026-07-20.md b/RAG_TEST_REPORT_2026-07-20.md new file mode 100644 index 00000000..5fabde07 --- /dev/null +++ b/RAG_TEST_REPORT_2026-07-20.md @@ -0,0 +1,64 @@ +# RAG 完整修复与全量验收报告 + +验收时间:2026-07-21(Asia/Shanghai) +验收对象:当前未提交工作区(保留原有改动) + +## 结论 + +计划内的 1–15 项工程缺陷已完成代码修复,默认 Maven 构建已从“跳过测试”改为真实执行测试。全仓 37 个 reactor 模块测试成功,`ruoyi-chat` 49/49 通过,两个前端生产构建通过,`git diff --check` 通过。 + +本机已运行 MySQL、Redis、MinIO 和 Weaviate 1.30.0;Milvus/Qdrant 容器以及有效的 embedding/chat/rerank provider 凭证不存在,因此这三项真实 provider/存储引擎冒烟被标记为环境限制,不影响确定性代码验收。 + +## 1–15 项验收 + +| # | 状态 | 修复/证据 | +|---|---|---| +| 1 | 通过 | Markdown/Java/字符分片的边界、空文档、超长块回归通过。 | +| 2 | 通过 | Supervisor 每轮仅保留一个 RAG 入口,不再用已含 RAG 的 prompt 重复检索。 | +| 3 | 通过 | 历史消息进入 Supervisor prompt,检索 query 与最终 prompt 分离。 | +| 4 | 通过 | `fid` 稳定 ID 贯穿 DB/三种向量库/RRF,融合去重回归通过。 | +| 5 | 通过 | aiflow vector/hybrid 复用统一检索服务;graph 明确返回不支持,不再伪装为 vector。 | +| 6 | 通过 | 重解析改为先写新 fid、再清旧向量、最后替换 DB;失败补偿新向量;删片段/附件/库遇向量删除失败即中止。 | +| 7 | 通过 | embedding/rerank provider 使用 prototype 实例,工厂缓存可按模型刷新,避免跨配置污染。 | +| 8 | 通过 | `similarityThreshold` 仅用于粗召回;`rerankScoreThreshold` 仅在 rerank 真实成功后生效,回归测试通过。 | +| 9 | 通过 | 默认配置和 Compose 统一为 Weaviate 1.30.0、`28080:8080`。 | +| 10 | 通过 | 三种策略均使用 `embedAll`;Weaviate batch objects、Milvus `addAll`、Qdrant `addAll`。 | +| 11 | 通过 | upload/parse/retrieval 权限保留,parse/retrieval 增加分布式防重复提交,upload 由现有知识库+文件名唯一约束兜底。 | +| 12 | 通过 | 分隔符使用字面量语义,`|`/`.`/`*` 回归通过。 | +| 13 | 通过 | hybrid 通道失败可降级到 vector;所有可用通道都失败时抛出明确业务异常。 | +| 14 | 通过 | Weaviate client 稳定懒加载单例;schema 仅在已存在或创建成功后进入缓存。 | +| 15 | 通过 | 工厂新增严格 `getStrategy(type)`,知识库 `vectorModel` 优先,空值才回退全局,非法值直接报错。 | + +## 其他完成项 + +- 多知识库并行检索,按 `kid + docId + fid` 去重,统一上限和字符预算。 +- 5 分钟短 TTL 检索缓存,key 覆盖检索参数,知识数据变更主动失效。 +- rerank 仅保留 provider 实际返回的文档。 +- 知识库文档数改为 group-by 查询,消除该 N+1。 +- Milvus/Qdrant/Weaviate 的删 collection/doc/fid 语义对齐;Milvus 删库改为 drop collection。 +- MCP `npx` 根据操作系统解析,支持系统属性/环境变量覆盖。 +- `fid` 非空唯一、`doc_id varchar(32)`、租户/用户索引与可重复执行迁移脚本已提供。 +- 用户端聊天页已接入知识库列表和最小选择器。 + +## 测试记录 + +| 检查 | 结果 | +|---|---| +| `mvn -Pdev test` | 37/37 reactor 模块 SUCCESS;`ruoyi-chat` 49/49 | +| `mvn -Pdev -pl ruoyi-modules/ruoyi-aiflow -am -DskipTests compile` | 21/21 SUCCESS | +| `ruoyi-web: pnpm build` | SUCCESS,2621 modules transformed | +| `ruoyi-admin: pnpm build` | SUCCESS,10/10 build tasks | +| `git diff --check` | SUCCESS,无空白错误 | +| Weaviate/MySQL/Redis/MinIO | Docker 服务运行,Weaviate 1.30.0 映射 28080 | +| 三向量库 Docker 集成 | SUCCESS;Weaviate 1.30.0、Milvus 2.5.7、Qdrant 1.17.0 真实写入/检索/删除测试 3/3 通过 | +| 真实 embedding/chat/rerank | 环境限制:当前配置为无效/占位凭证 | + +本轮未创建新的 `codex_rag_verify_` 持久化数据;上一轮验收数据已清理,未动现有非测试数据。 + +## 2026-07-21 三向量库 Docker 补充验收 + +- 启动并保留 `ruoyi-rag-milvus`、`ruoyi-rag-milvus-etcd`、`ruoyi-rag-milvus-minio`、`ruoyi-rag-qdrant`,四个容器健康检查均为 `healthy`。 +- Milvus 专用 MinIO 仅在 Docker 内网可达,没有占用宿主机 9000/9001;Milvus 映射 19530/9091,Qdrant 映射 6333/6334。 +- `ThreeVectorStoresDockerIT` 使用 32 维确定性 embedding,对三库逐一验证 batch write、vector search、fid delete、docId delete 和 drop collection,3/3 通过。 +- 首轮测试发现 Milvus `autoFlush=false` 导致批量写入后不可立即检索、元数据删除不可立即见;改为写入和删除返回前 flush 后通过。 +- 清理后 Weaviate/Qdrant 的 `CodexRagVerify*` collection 计数均为 0,Milvus collection 也由测试 finally 成功 drop;本轮未写入 MySQL 或 OSS 测试数据。 diff --git a/README.md b/README.md index 4e92fb7f..1b981147 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,13 @@ ## ✨ 核心亮点 -| 模块 | 现有能力 -|:----------:|--- -| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱/MiniMax)、多模态理解、Coze/DIFY/FastGPT平台集成 -| **知识管理** | 本地RAG + 向量库(Milvus/Weaviate/Qdrant) + 文档解析 -| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态 -| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点 -| **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型 +| 模块 | 现有能力 +|:---------:|--- +| **模型管理** | 多模型接入(DeepSeek/智谱/MIMO/百炼/OpenAI)、多模态理解、Coze/DIFY/FastGPT/RAGFlow平台集成 +| **知识管理** | 本地RAG + 向量库(Milvus/Weaviate/Qdrant) + 文档解析 +| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态 +| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点 +| **智能体管理** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型,可以灵活搭配工具,skills ### 项目源码 @@ -45,6 +45,9 @@ | 🔧 后端服务 | [ruoyi-ai](https://github.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitee.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitcode.com/ageerle/ruoyi-ai) | | 🎨 用户前端 | [ruoyi-web](https://github.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitee.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitcode.com/ageerle/ruoyi-web) | | 🛠️ 管理后台 | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) | +| 🎬 短剧平台 | [ruoyi-drama](https://github.com/ageerle/ruoyi-drama) | [ruoyi-drama](https://gitee.com/ageerle/ruoyi-drama) | [ruoyi-drama](https://gitcode.com/ageerle/ruoyi-drama) | +| 🤖 编程助手 | [ruoyi-copilot](https://github.com/ageerle/ruoyi-copilot) | [ruoyi-copilot](https://gitee.com/ageerle/ruoyi-copilot) | [ruoyi-copilot](https://gitcode.com/ageerle/ruoyi-copilot) | +| 📱 小程序端 | [ruoyi-uniapp](https://github.com/ageerle/ruoyi-uniapp) | [ruoyi-uniapp](https://gitee.com/ageerle/ruoyi-uniapp) | [ruoyi-uniapp](https://gitcode.com/ageerle/ruoyi-uniapp) | ### 合作项目 | 项目名称 | GitHub 仓库 | Gitee 仓库 @@ -213,15 +216,15 @@ docker-compose -f docker-compose-all.yaml restart [服务名] Atlas Cloud -[访问Atlas Cloud官网](https://www.atlascloud.ai?ref=89F97E) · [编程计划优惠](https://www.atlascloud.ai/console/coding-plan) +[访问Atlas Cloud官网](https://www.atlascloud.ai?ref=89F97E&utm_source=github&utm_campaign=ruoyi-drama) · [编程计划优惠](https://www.atlascloud.ai/console/coding-plan) 全模态 AI 推理平台,为开发者提供统一的 AI API,支持视频生成、图像生成和大语言模型。一次接入,即可访问 **300+ 精选模型**。 火山引擎 CodingPlan -[火山引擎 CodingPlan 开发者计划](https://www.volcengine.com/activity/codingplan?utm_campaign=hw&utm_content=hw&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ageerle-ruoyi-ai) -火山引擎是字节跳动旗下的云与 AI 服务平台,火山方舟提供豆包大模型、DeepSeek 等多种模型的 API 接入,为开发者提供一站式 AI 开发与推理服务。 +[注册即领2500万Tokens,立即前往](https://www.volcengine.com/activity/ai618?utm_campaign=hw&utm_content=hw&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ageerle-ruoyi-ai) +享字节自研豆包模型+满血版开源 SOTA模型,覆盖文本、VLM、图像生成,全模态一站配齐:Seed-2.1、Seedream-5.0、GLM-5.2、DeepSeek等。不止编程、更能解决 Agent 复杂长程任务! ## 💬 社区交流 diff --git a/README_EN.md b/README_EN.md index 8865116b..5c68afa3 100644 --- a/README_EN.md +++ b/README_EN.md @@ -47,6 +47,9 @@ | 🔧 Backend | [ruoyi-ai](https://github.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitee.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitcode.com/ageerle/ruoyi-ai) | | 🎨 User Frontend | [ruoyi-web](https://github.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitee.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitcode.com/ageerle/ruoyi-web) | | 🛠️ Admin Panel | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) | +| 🎬 Drama | [ruoyi-drama](https://github.com/ageerle/ruoyi-drama) | [ruoyi-drama](https://gitee.com/ageerle/ruoyi-drama) | | +| 🤖 Copilot | [ruoyi-copilot](https://github.com/ageerle/ruoyi-copilot) | [ruoyi-copilot](https://gitee.com/ageerle/ruoyi-copilot) | [ruoyi-copilot](https://gitcode.com/ageerle/ruoyi-copilot) | +| 📱 Mini-App | [ruoyi-uniapp](https://github.com/ageerle/ruoyi-uniapp) | [ruoyi-uniapp](https://gitee.com/ageerle/ruoyi-uniapp) | [ruoyi-uniapp](https://gitcode.com/ageerle/ruoyi-uniapp) | ### Partner Projects | Project Name | GitHub Repository | Gitee Repository | @@ -175,6 +178,34 @@ docker-compose -f docker-compose-all.yaml logs -f [service-name] docker-compose -f docker-compose-all.yaml restart [service-name] ``` +### MiniMax Configuration + +The built-in MiniMax provider accepts one API Host value and selects the matching protocol adapter. Use a Base URL from this table: + +| Region | OpenAI-compatible Base URL | Anthropic-compatible Base URL | +| --- | --- | --- | +| Global | `https://api.minimax.io/v1` | `https://api.minimax.io/anthropic` | +| China | `https://api.minimaxi.com/v1` | `https://api.minimaxi.com/anthropic` | + +For Anthropic-compatible requests, configure the Base URL ending in `/anthropic`. Do not append `/v1` or `/v1/messages`; the provider adapter derives the request path internally. + +| Model ID | Total context | Input modalities | Thinking | +| --- | ---: | --- | --- | +| `MiniMax-M3` | 1,000,000 tokens | Text, image, video | Adaptive or disabled | +| `MiniMax-M2.7` | 204,800 tokens | Text | Always on | + +Current pay-as-you-go prices are in USD per million tokens: + +| Model | Service tier and input range | Input | Output | Cache read | Cache write | +| --- | --- | ---: | ---: | ---: | ---: | +| `MiniMax-M3` | Standard, up to 512,000 input tokens | $0.30 | $1.20 | $0.06 | Not listed | +| `MiniMax-M3` | Standard, over 512,000 input tokens | $0.60 | $2.40 | $0.12 | Not listed | +| `MiniMax-M3` | Priority, up to 512,000 input tokens | $0.45 | $1.80 | $0.09 | Not listed | +| `MiniMax-M3` | Priority, over 512,000 input tokens | $0.90 | $3.60 | $0.18 | Not listed | +| `MiniMax-M2.7` | Standard | $0.30 | $1.20 | $0.06 | $0.375 | + +See the [official API overview](https://platform.minimax.io/docs/api-reference/api-overview) and [pay-as-you-go pricing](https://platform.minimax.io/docs/guides/pricing-paygo) for current details. + ## 📚 Documentation Want to learn more about installation, deployment, configuration, and secondary development? diff --git a/docs/docker/milvus/docker-compose.yml b/docs/docker/milvus/docker-compose.yml index 43fe699c..a917c996 100644 --- a/docs/docker/milvus/docker-compose.yml +++ b/docs/docker/milvus/docker-compose.yml @@ -1,8 +1,6 @@ -version: '3.5' - services: etcd: - container_name: milvus-etcd + container_name: ruoyi-rag-milvus-etcd image: quay.io/coreos/etcd:v3.5.18 environment: - ETCD_AUTO_COMPACTION_MODE=revision @@ -19,14 +17,11 @@ services: retries: 3 minio: - container_name: milvus-minio + container_name: ruoyi-rag-milvus-minio image: minio/minio:RELEASE.2023-03-20T20-16-18Z environment: MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin - ports: - - "9001:9001" - - "9000:9000" volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data command: minio server /minio_data --console-address ":9001" @@ -37,7 +32,7 @@ services: retries: 3 standalone: - container_name: milvus-standalone + container_name: ruoyi-rag-milvus image: milvusdb/milvus:v2.5.7 command: ["milvus", "run", "standalone"] security_opt: @@ -61,7 +56,7 @@ services: - "minio" attu: - container_name: attu + container_name: ruoyi-rag-attu image: zilliz/attu:v2.5.7 environment: MILVUS_URL: milvus-standalone:19530 @@ -72,4 +67,4 @@ services: networks: default: - name: milvus \ No newline at end of file + name: ruoyi-rag-milvus diff --git a/docs/docker/qdrant/docker-compose.yml b/docs/docker/qdrant/docker-compose.yml index d17c9fd8..eed546f8 100644 --- a/docs/docker/qdrant/docker-compose.yml +++ b/docs/docker/qdrant/docker-compose.yml @@ -1,12 +1,20 @@ --- services: qdrant: - image: qdrant/qdrant:latest + container_name: ruoyi-rag-qdrant + image: qdrant/qdrant:v1.17.0 ports: - 6333:6333 - 6334:6334 volumes: - qdrant_data:/qdrant/storage + healthcheck: + test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/6333 && printf \"GET /healthz HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\" >&3 && grep -q \"200 OK\" <&3'"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 10s + restart: unless-stopped volumes: qdrant_data: ... diff --git a/docs/docker/ruoyi-ai/Dockerfile.backend b/docs/docker/ruoyi-ai/Dockerfile.backend index 87eb5626..3cd7ce5c 100644 --- a/docs/docker/ruoyi-ai/Dockerfile.backend +++ b/docs/docker/ruoyi-ai/Dockerfile.backend @@ -20,6 +20,22 @@ RUN mvn clean package -Pprod -DskipTests # 最终运行镜像 FROM eclipse-temurin:17-jre-alpine +RUN set -eux; \ + apk add --no-cache ffmpeg; \ + for filter in scale pad fps setsar format tpad trim settb setpts xfade \ + aresample aformat apad atrim asetpts anullsrc concat acrossfade; do \ + ffmpeg -hide_banner -filters 2>/dev/null | grep -Eq "[[:space:]]${filter}[[:space:]]"; \ + done; \ + ffmpeg -hide_banner -h filter=xfade 2>&1 | grep -q dissolve; \ + ffmpeg -hide_banner -h filter=xfade 2>&1 | grep -q fadeblack; \ + ffmpeg -hide_banner -h filter=xfade 2>&1 | grep -q slideleft; \ + ffmpeg -hide_banner -encoders 2>/dev/null | grep -Eq '[[:space:]]libx264[[:space:]]'; \ + ffmpeg -hide_banner -encoders 2>/dev/null | grep -Eq '[[:space:]]aac[[:space:]]'; \ + ffprobe -hide_banner -version >/dev/null + +ENV FFMPEG_PATH=/usr/bin/ffmpeg \ + FFPROBE_PATH=/usr/bin/ffprobe + # 设置工作目录 WORKDIR /app @@ -27,10 +43,10 @@ WORKDIR /app COPY --from=builder /build/ruoyi-admin/target/ruoyi-admin.jar ./ruoyi-admin.jar # 创建日志目录 -RUN mkdir -p /ruoyi/server/logs +RUN mkdir -p /ruoyi/server/logs /ruoyi/server/temp # 暴露端口 EXPOSE 6039 # 启动命令 -ENTRYPOINT ["java", "-jar", "ruoyi-admin.jar", "--spring.profiles.active=prod"] +ENTRYPOINT ["java", "-Djava.io.tmpdir=/ruoyi/server/temp", "-jar", "ruoyi-admin.jar", "--spring.profiles.active=prod"] diff --git a/docs/docker/ruoyi-ai/Dockerfile.mysql b/docs/docker/ruoyi-ai/Dockerfile.mysql index d5fcecb7..a7b11679 100644 --- a/docs/docker/ruoyi-ai/Dockerfile.mysql +++ b/docs/docker/ruoyi-ai/Dockerfile.mysql @@ -1,16 +1,14 @@ # 基于官方MySQL 8.0镜像构建自定义镜像 # 构建命令: docker build -t registry.cn-hangzhou.aliyuncs.com/ruoyi-ai/mysql:v3 -f Dockerfile.mysql . -FROM mysql:8.0.33 +FROM mysql:8.0 # 设置时区 ENV TZ=Asia/Shanghai -# 复制初始化脚本和SQL文件到镜像中 -COPY docs/script/docker/mysql/init/init-db.sh /docker-entrypoint-initdb.d/init-db.sh -COPY docs/script/sql/ruoyi-ai-v3_mysql8.sql /docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql - -# 设置脚本可执行权限 -RUN chmod +x /docker-entrypoint-initdb.d/init-db.sh +# MySQL 官方入口会按文件名顺序执行初始化目录中的 SQL。 +# ruoyi-ai.sql 会导入 MYSQL_DATABASE,snail_job_mysql.sql 会自行创建并切换数据库。 +COPY docs/script/sql/ruoyi-ai.sql /docker-entrypoint-initdb.d/01-ruoyi-ai.sql +COPY docs/script/sql/snail_job_mysql.sql /docker-entrypoint-initdb.d/02-snail-job.sql # MySQL启动参数 CMD ["--default-authentication-plugin=mysql_native_password", \ @@ -18,4 +16,4 @@ CMD ["--default-authentication-plugin=mysql_native_password", \ "--collation-server=utf8mb4_general_ci", \ "--explicit_defaults_for_timestamp=true", \ "--lower_case_table_names=1", \ - "--skip-ssl"] \ No newline at end of file + "--skip-ssl"] diff --git a/docs/docker/ruoyi-ai/docker-compose-all.yaml b/docs/docker/ruoyi-ai/docker-compose-all.yaml index b9f330c1..21c781a5 100644 --- a/docs/docker/ruoyi-ai/docker-compose-all.yaml +++ b/docs/docker/ruoyi-ai/docker-compose-all.yaml @@ -12,8 +12,6 @@ # # 镜像仓库地址: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai -version: '3.8' - services: # ==================== MySQL 数据库 ==================== mysql: @@ -30,7 +28,10 @@ services: volumes: - mysql-data:/var/lib/mysql healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"] + # Force TCP: during first-time initialization MySQL exposes a temporary + # socket-only server. A socket ping would mark the service healthy before + # the real network listener (used by the backend) is ready. + test: ["CMD", "mysqladmin", "ping", "--protocol=tcp", "-h", "127.0.0.1", "-u", "root", "-proot"] interval: 15s timeout: 10s retries: 10 @@ -177,4 +178,4 @@ volumes: weaviate-data: minio-data: logs-data: - upload-data: \ No newline at end of file + upload-data: diff --git a/docs/docker/ruoyi-ai/docker-compose.yaml b/docs/docker/ruoyi-ai/docker-compose.yaml index eac534d8..96c4e949 100644 --- a/docs/docker/ruoyi-ai/docker-compose.yaml +++ b/docs/docker/ruoyi-ai/docker-compose.yaml @@ -21,8 +21,8 @@ services: MYSQL_DATABASE: ruoyi-ai-agent TZ: Asia/Shanghai volumes: - - ./docs/script/docker/mysql/init/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh:ro - - ./docs/script/sql/ruoyi-ai-v3_mysql8.sql:/docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql:ro + - ../../script/sql/ruoyi-ai.sql:/docker-entrypoint-initdb.d/01-ruoyi-ai.sql:ro + - ../../script/sql/snail_job_mysql.sql:/docker-entrypoint-initdb.d/02-snail-job.sql:ro - mysql-data:/var/lib/mysql command: --default-authentication-plugin=mysql_native_password @@ -32,7 +32,9 @@ services: --lower_case_table_names=1 --skip-ssl healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"] + # Do not let the socket-only initialization server satisfy the check. + # The backend connects over TCP and must wait for that listener. + test: ["CMD", "mysqladmin", "ping", "--protocol=tcp", "-h", "127.0.0.1", "-u", "root", "-proot"] interval: 15s timeout: 10s retries: 10 diff --git a/docs/docker/weaviate/docker-compose.yml b/docs/docker/weaviate/docker-compose.yml index 4504591e..6b6acb47 100644 --- a/docs/docker/weaviate/docker-compose.yml +++ b/docs/docker/weaviate/docker-compose.yml @@ -5,12 +5,12 @@ services: - --host - 0.0.0.0 - --port - - '6038' + - '8080' - --scheme - http - image: semitechnologies/weaviate:1.19.7 + image: semitechnologies/weaviate:1.30.0 ports: - - 6038:6038 + - 28080:8080 - 50051:50051 volumes: - weaviate_data:/var/lib/weaviate diff --git a/docs/image/wx06.png b/docs/image/wx06.png index 209d3a23..58eb0144 100644 Binary files a/docs/image/wx06.png and b/docs/image/wx06.png differ diff --git a/docs/script/docker/mysql/init/init-db.sh b/docs/script/docker/mysql/init/init-db.sh deleted file mode 100644 index 46b6fc6a..00000000 --- a/docs/script/docker/mysql/init/init-db.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# 数据库初始化脚本 -# 使用 --force 参数确保即使出错也继续执行 - -echo "开始初始化数据库..." - -# 使用 --force 参数忽略错误继续执行 -mysql -uroot -proot ruoyi-ai-agent --force < /docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql - -echo "数据库初始化完成" diff --git a/docs/script/install-ffmpeg-windows.ps1 b/docs/script/install-ffmpeg-windows.ps1 new file mode 100644 index 00000000..e6b5b63f --- /dev/null +++ b/docs/script/install-ffmpeg-windows.ps1 @@ -0,0 +1,86 @@ +[CmdletBinding()] +param( + [switch]$Force +) + +$ErrorActionPreference = 'Stop' + +function Find-Executable([string]$Name) { + $command = Get-Command $Name -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + $roots = @( + (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages'), + (Join-Path $env:USERPROFILE 'scoop\apps\ffmpeg'), + 'C:\ProgramData\chocolatey\bin', + 'C:\ffmpeg' + ) + foreach ($root in $roots) { + if (-not (Test-Path $root)) { continue } + $match = Get-ChildItem -Path $root -Filter "$Name.exe" -Recurse -ErrorAction SilentlyContinue | + Select-Object -First 1 -ExpandProperty FullName + if ($match) { return $match } + } + return $null +} + +$ffmpeg = Find-Executable 'ffmpeg' +$ffprobe = Find-Executable 'ffprobe' + +if ($Force -or -not $ffmpeg -or -not $ffprobe) { + $winget = Get-Command winget -ErrorAction SilentlyContinue + if (-not $winget) { + throw '未找到 winget。请先从 Microsoft Store 安装“应用安装程序”,或使用项目 Dockerfile 运行后端。' + } + + Write-Host '正在通过 winget 安装 FFmpeg...' -ForegroundColor Cyan + & winget install --id Gyan.FFmpeg --exact --source winget ` + --accept-package-agreements --accept-source-agreements + if ($LASTEXITCODE -ne 0) { + throw "winget 安装失败,退出码: $LASTEXITCODE" + } + + $ffmpeg = Find-Executable 'ffmpeg' + $ffprobe = Find-Executable 'ffprobe' +} + +if (-not $ffmpeg -or -not $ffprobe) { + throw 'FFmpeg 已安装但未能定位 ffmpeg.exe 或 ffprobe.exe。请重新打开 PowerShell 后再次运行脚本。' +} + +$binDirectory = Split-Path -Parent $ffmpeg +[Environment]::SetEnvironmentVariable('FFMPEG_PATH', $ffmpeg, 'User') +[Environment]::SetEnvironmentVariable('FFPROBE_PATH', $ffprobe, 'User') + +$userPath = [Environment]::GetEnvironmentVariable('Path', 'User') +$pathParts = @($userPath -split ';' | Where-Object { $_ }) +if ($pathParts -notcontains $binDirectory) { + $newPath = (@($pathParts) + $binDirectory) -join ';' + [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') +} + +$env:FFMPEG_PATH = $ffmpeg +$env:FFPROBE_PATH = $ffprobe +if (($env:Path -split ';') -notcontains $binDirectory) { + $env:Path = "$binDirectory;$env:Path" +} + +Write-Host "ffmpeg: $ffmpeg" -ForegroundColor Green +Write-Host "ffprobe: $ffprobe" -ForegroundColor Green + +& $ffmpeg -hide_banner -version | Select-Object -First 1 +& $ffprobe -hide_banner -version | Select-Object -First 1 + +$encoders = & $ffmpeg -hide_banner -encoders 2>&1 | Out-String +if ($encoders -notmatch '\blibx264\b') { + throw '当前 FFmpeg 不包含 libx264 编码器,无法满足短剧合成配置。' +} +if ($encoders -notmatch '\baac\b') { + throw '当前 FFmpeg 不包含 AAC 编码器,无法满足短剧合成配置。' +} + +Write-Host '' +Write-Host '安装与配置完成。请完全重启 IntelliJ IDEA 和 ruoyi-ai 后端服务。' -ForegroundColor Yellow +Write-Host '重启后,Spring 将从 FFMPEG_PATH 和 FFPROBE_PATH 读取绝对路径。' \ No newline at end of file diff --git a/docs/script/sql/ruoyi-ai-v3_mysql8.sql b/docs/script/sql/ruoyi-ai.sql similarity index 72% rename from docs/script/sql/ruoyi-ai-v3_mysql8.sql rename to docs/script/sql/ruoyi-ai.sql index 89ea2af7..0fae0fde 100644 --- a/docs/script/sql/ruoyi-ai-v3_mysql8.sql +++ b/docs/script/sql/ruoyi-ai.sql @@ -11,12 +11,45 @@ Target Server Version : 80045 (8.0.45) File Encoding : 65001 - Date: 21/05/2026 11:03:49 + Date: 14/07/2026 21:36:39 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; +-- ---------------------------- +-- Table structure for agent_info +-- ---------------------------- +DROP TABLE IF EXISTS `agent_info`; +CREATE TABLE `agent_info` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '智能体ID', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户ID', + `agent_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '智能体名称', + `agent_describe` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '智能体描述(下拉展示用)', + `agent_show` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '展示图标/头像URL', + `model_id` bigint NOT NULL COMMENT '绑定的聊天模型ID(chat_model.id, category=chat)', + `enable_thinking` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '是否启用深度思考(ReAct多子Agent):0 否 1 是', + `system_prompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '自定义系统提示词', + `mcp_tool_ids` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '关联MCP工具ID列表(JSON数组,[Long])', + `skill_names` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '关联磁盘技能名列表(JSON数组,[String])', + `knowledge_ids` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '关联知识库ID列表(JSON数组,[Long])', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '状态:0 正常 1 停用', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_agent_tenant_id`(`tenant_id` ASC) USING BTREE, + INDEX `idx_agent_model_id`(`model_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '智能体信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of agent_info +-- ---------------------------- +INSERT INTO `agent_info` VALUES (1, 0, '对话智能体', '对话智能体', NULL, 2000585866022060033, '0', '你是一个乐于助人的通用 AI 助手,请用简洁、准确的中文回答用户的问题。', '[]', '[\"docx\"]', '[]', '0', '系统初始化的默认智能体,自动绑定首个启用的对话模型', 103, 1, '2026-07-14 16:12:21', 1, '2026-07-14 16:18:04'); + -- ---------------------------- -- Table structure for chat_message -- ---------------------------- @@ -42,6 +75,8 @@ CREATE TABLE `chat_message` ( -- ---------------------------- -- Records of chat_message -- ---------------------------- +INSERT INTO `chat_message` VALUES (2076944340894203905, 2076944338398593026, 1, '你好', 'user', 0, 'deepseek-v4-flash', 103, 1, '2026-07-14 16:18:13', 1, '2026-07-14 16:18:13', NULL, 0); +INSERT INTO `chat_message` VALUES (2076944348947267585, 2076944338398593026, 1, '你好!有什么可以帮你的吗?', 'assistant', 0, 'deepseek-v4-flash', -1, -1, '2026-07-14 16:18:15', -1, '2026-07-14 16:18:15', NULL, 0); -- ---------------------------- -- Table structure for chat_model @@ -70,9 +105,18 @@ CREATE TABLE `chat_model` ( -- ---------------------------- -- Records of chat_model -- ---------------------------- -INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_dimension`, `model_show`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2000585866022060033, 'chat', 'zai-org/glm-5.1', 'atlas', 'zai-org/glm-5.1', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', '对话模型', 0); -INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_dimension`, `model_show`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2007528268536287233, 'vector', 'embedding-3', 'zhipu', 'embedding-3', 2048, 'N', 'https://open.bigmodel.cn', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-03-15 19:18:51', '向量模型', 0); -INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_dimension`, `model_show`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2045071617578237953, 'rerank', 'rerank', 'zhipu', 'rerank', NULL, 'N', 'https://open.bigmodel.cn', 'sk_xx', 103, 1, '2026-04-17 17:27:24', 1, '2026-04-20 15:21:48', '重排序模型', 0); +INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek-v4-flash', 'deepseek', 'deepseek-v4-flash', NULL, 'Y', 'https://api.deepseek.com', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', '对话模型', 0); +INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'embedding-3', 'zhipu', 'embedding-3', 2048, 'N', 'https://open.bigmodel.cn', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-03-15 19:18:51', '向量模型', 0); +INSERT INTO `chat_model` VALUES (2045071617578237953, 'rerank', 'rerank', 'zhipu', 'rerank', NULL, 'N', 'https://open.bigmodel.cn', 'sk_xx', 103, 1, '2026-04-17 17:27:24', 1, '2026-04-20 15:21:48', '重排序模型', 0); +INSERT INTO `chat_model` VALUES (2000585866022060003, 'chat', 'deepseek-ai/deepseek-v4-flash', 'atlas', 'deepseek-v4-flash', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', '对话模型', 0); +INSERT INTO `chat_model` VALUES (2050000000000000001, 'video', 'bytedance/seedance-2.0/text-to-video', 'atlas', 'Seedance 2.0 文生视频(字节跳动)', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2026-06-17 18:39:20', 1, '2026-06-17 18:39:20', 'Atlas Cloud 视频模型 - Seedance 2.0', 0); +INSERT INTO `chat_model` VALUES (2060622000000000001, 'image', 'openai/gpt-image-2/text-to-image', 'atlas', 'GPT-IMAGE-2 文生图', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2026-06-22 14:45:54', 1, '2026-06-22 14:45:54', 'Atlas Cloud 图片模型 - 文生图,支持三视图、角色设定、场景图', 0); +INSERT INTO `chat_model` VALUES (2060622000000000002, 'image', 'openai/gpt-image-2/edit', 'atlas', 'GPT-IMAGE-2 图生图编辑', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2026-06-22 14:45:54', 1, '2026-06-22 14:45:54', 'Atlas Cloud 图片模型 - 图生图/编辑,支持基于参考图修改风格、纹理等', 0); +INSERT INTO `chat_model` VALUES (2060622000000000003, 'video', 'bytedance/seedance-2.0/image-to-video', 'atlas', 'Seedance 2.0 图生视频(字节跳动)', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2026-06-22 17:24:07', 1, '2026-06-22 17:24:07', 'Atlas Cloud 视频模型 - Seedance 2.0 图生视频,接收参考图+提示词生成视频', 0); +INSERT INTO `chat_model` VALUES (2060622000000000004, 'video', 'bytedance/seedance-2.0/reference-to-video', 'atlas', 'Seedance 2.0 多参考图生视频(字节跳动)', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2026-06-22 20:24:44', 1, '2026-06-22 20:24:44', 'Atlas Cloud 视频模型 - Seedance 2.0 多参考图生视频,接收多张参考图+带@imageN标记的提示词生成视频', 0); +INSERT INTO `chat_model` VALUES (2070700000000000002, 'chat', 'dify-chat', 'dify', 'Dify Chat App', NULL, 'Y', 'https://api.dify.ai/v1', '替换为你的DIFY_APP_API_KEY', 103, 1, '2026-07-14 11:03:44', 1, '2026-07-14 11:03:44', 'Dify 聊天应用;api_key 为 Dify App API Key,model_name 可按应用名修改', 0); +INSERT INTO `chat_model` VALUES (2070700000000000004, 'chat', '替换为你的COZE_BOT_ID', 'coze', 'Coze Bot', NULL, 'Y', 'https://api.coze.cn', '替换为你的COZE_PAT', 103, 1, '2026-07-14 11:03:38', 1, '2026-07-14 11:03:38', 'Coze 聊天 Bot;model_name 为 Coze Bot ID,api_key 为 PAT 或 OAuth access token', 0); +INSERT INTO `chat_model` VALUES (2070700000000000010, 'audio', 'bytedance/seed-audio-1.0', 'atlas', 'Seed Audio 1.0 语音生成(字节跳动)', NULL, 'Y', 'https://api.atlascloud.ai/v1', 'sk_xx', 103, 1, '2026-07-16 18:00:00', 1, '2026-07-16 18:00:00', 'Atlas Cloud 语音模型 - 支持多角色对白配音,references 指定 speaker 音色,text 中用 @audioN 引用', 0); -- ---------------------------- -- Table structure for chat_provider @@ -82,7 +126,7 @@ CREATE TABLE `chat_provider` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `provider_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '厂商名称', `provider_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '厂商编码', - `provider_icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商图标', + `provider_icon` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商图标', `provider_desc` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商描述', `api_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'API地址', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', @@ -100,19 +144,21 @@ CREATE TABLE `chat_provider` ( PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `unique_provider_code`(`provider_code` ASC, `tenant_id` ASC, `del_flag` ASC) USING BTREE, INDEX `idx_status`(`status` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2045727230803255298 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '厂商管理表' ROW_FORMAT = DYNAMIC; +) ENGINE = InnoDB AUTO_INCREMENT = 2070700000000000004 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '厂商管理表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of chat_provider -- ---------------------------- -INSERT INTO `chat_provider` VALUES (11, '深度求索', 'deepseek', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/5ba8c30f153246898a4d7dc7b846de8d.png', 'DeepSeek官方API', 'https://api.deepseek.com', '0', 0, 103, '2026-04-19 12:52:34', '1', '1', '2026-04-19 13:13:25', 'DeepSeek官方API', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (12, '智谱AI', 'zhipu', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/da071783c9284fdd9ed1ce1b57b3c75c.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 4, 103, '2025-12-14 21:48:11', '1', '1', '2026-04-19 13:14:00', '智谱AI厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (13, '小米MIMO', 'xiaomi', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/18dd39365ce244e3ae5e030da036760e.png', '小米官方API', 'https://api.xiaomimimo.com/v1', '0', 3, 103, '2026-04-19 12:48:24', '1', '1', '2026-04-19 13:14:22', '小米官方API', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (14, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (15, 'Atlas Cloud', 'atlas', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/atlascloud.png', '全模态AI推理平台', 'https://api.atlascloud.ai/v1', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'Atlas Cloud AI平台', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (16, 'MiniMax', 'minimax', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/fdc712e90e0e4d78b05862ad230884e5.png', 'MiniMax大模型服务,支持M2.7、M2.5等模型', 'https://api.minimax.io/v1', '0', 6, 103, '2026-04-19 12:50:12', '1', '1', '2026-04-19 13:14:59', 'MiniMax厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (17, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 7, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (18, '自定义厂商', 'custom_api', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/c1a8e122510f4e2f90deb36958af710b.png', 'OPENAI兼容格式', '自定义', '0', 8, 103, '2026-04-19 12:35:57', '1', '1', '2026-04-19 13:17:20', 'OPENAI兼容格式', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (11, '深度求索', 'deepseek', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/04/19/5ba8c30f153246898a4d7dc7b846de8d.png', 'DeepSeek官方API', 'https://api.deepseek.com', '0', 0, 103, '2026-04-19 12:52:34', '1', '1', '2026-04-19 13:13:25', 'DeepSeek官方API', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (12, '智谱AI', 'zhipu', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/04/19/da071783c9284fdd9ed1ce1b57b3c75c.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 4, 103, '2025-12-14 21:48:11', '1', '1', '2026-04-19 13:14:00', '智谱AI厂商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (13, '小米MIMO', 'xiaomi', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/04/19/18dd39365ce244e3ae5e030da036760e.png', '小米官方API', 'https://api.xiaomimimo.com/v1', '0', 3, 103, '2026-04-19 12:48:24', '1', '1', '2026-04-19 13:14:22', '小米官方API', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (14, '阿里云百炼', 'qianwen', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (15, 'Atlas Cloud', 'atlas', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/04/19/atlascloud.png', '全模态AI推理平台', 'https://api.atlascloud.ai/v1', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'Atlas Cloud AI平台', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (16, 'MiniMax', 'minimax', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/04/19/fdc712e90e0e4d78b05862ad230884e5.png', 'MiniMax model service supporting MiniMax-M3 and MiniMax-M2.7', 'https://api.minimax.io/v1', '0', 6, 103, '2026-04-19 12:50:12', '1', '1', '2026-04-19 13:14:59', 'MiniMax provider', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (17, 'ollama', 'ollama', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 7, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (18, '自定义厂商', 'custom_api', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/04/19/c1a8e122510f4e2f90deb36958af710b.png', 'OPENAI兼容格式', '自定义', '0', 8, 103, '2026-04-19 12:35:57', '1', '1', '2026-04-19 13:17:20', 'OPENAI兼容格式', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (2070700000000000001, 'Dify', 'dify', NULL, 'Dify 应用 API 服务', 'https://api.dify.ai/v1', '0', 9, 103, '2026-07-14 11:03:44', '1', '1', '2026-07-14 11:03:44', 'Dify 应用 API 服务商', NULL, '0', NULL, 0); +INSERT INTO `chat_provider` VALUES (2070700000000000003, 'Coze', 'coze', NULL, 'Coze Bot API 服务', 'https://api.coze.cn', '0', 10, 103, '2026-07-14 11:03:38', '1', '1', '2026-07-14 11:03:38', 'Coze Bot API 服务商', NULL, '0', NULL, 0); -- ---------------------------- -- Table structure for chat_session @@ -137,6 +183,13 @@ CREATE TABLE `chat_session` ( -- ---------------------------- -- Records of chat_session -- ---------------------------- +INSERT INTO `chat_session` VALUES (2076944338398593026, 1, '你好', '你好', '103', 1, '2026-07-14 16:18:13', 1, '2026-07-14 16:18:13', '你好', NULL, 0); +INSERT INTO `chat_session` VALUES (2076957117012561921, 1, '测试工作流', NULL, '103', 1, '2026-07-14 17:08:59', 1, '2026-07-14 17:08:59', NULL, NULL, 0); +INSERT INTO `chat_session` VALUES (2076958796772593665, 1, '测试工作流', NULL, '103', 1, '2026-07-14 17:15:40', 1, '2026-07-14 17:15:40', NULL, NULL, 0); +INSERT INTO `chat_session` VALUES (2076958822525620225, 1, '测试工作流', NULL, '103', 1, '2026-07-14 17:15:46', 1, '2026-07-14 17:15:46', NULL, NULL, 0); +INSERT INTO `chat_session` VALUES (2076964726549565441, 1, '测试工作流', NULL, '103', 1, '2026-07-14 17:39:14', 1, '2026-07-14 17:39:14', NULL, NULL, 0); +INSERT INTO `chat_session` VALUES (2076964778940616705, 1, '测试工作流', NULL, '103', 1, '2026-07-14 17:39:26', 1, '2026-07-14 17:39:26', NULL, NULL, 0); +INSERT INTO `chat_session` VALUES (2076971705284218881, 1, '你好2阿', '你好2阿', '103', 1, '2026-07-14 18:06:57', 1, '2026-07-14 18:06:57', '你好2阿', NULL, 0); -- ---------------------------- -- Table structure for flow_category @@ -1078,7 +1131,8 @@ CREATE TABLE `knowledge_attach` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `knowledge_id` bigint NOT NULL COMMENT '知识库ID', `oss_id` bigint NULL DEFAULT NULL COMMENT '对象存储ID', - `doc_id` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档ID', + `doc_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档ID', + `file_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件SHA-256摘要', `name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '附件名称', `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '附件类型', `create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '部门', @@ -1090,7 +1144,8 @@ CREATE TABLE `knowledge_attach` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', `status` tinyint NULL DEFAULT 0 COMMENT '解析状态: 0待解析, 1解析中, 2已解析, 3解析失败', PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `idx_kname`(`knowledge_id` ASC, `name` ASC) USING BTREE + UNIQUE INDEX `idx_kname`(`knowledge_id` ASC, `name` ASC) USING BTREE, + UNIQUE INDEX `uk_knowledge_file_hash`(`knowledge_id`, `file_hash`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2033199209203183619 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识库附件' ROW_FORMAT = DYNAMIC; -- ---------------------------- @@ -1103,8 +1158,9 @@ CREATE TABLE `knowledge_attach` ( DROP TABLE IF EXISTS `knowledge_fragment`; CREATE TABLE `knowledge_fragment` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `fid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '向量库片段ID', `idx` int NOT NULL COMMENT '片段索引下标', - `doc_id` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档ID', + `doc_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档ID', `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文档内容', `create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '部门', `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人', @@ -1115,6 +1171,9 @@ CREATE TABLE `knowledge_fragment` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', `knowledge_id` bigint NULL DEFAULT NULL COMMENT '知识库ID', PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_fid`(`fid`) USING BTREE, + INDEX `idx_doc_id`(`doc_id`) USING BTREE, + INDEX `idx_knowledge_id`(`knowledge_id`) USING BTREE, FULLTEXT INDEX `ft_content`(`content`) WITH PARSER `ngram` ) ENGINE = InnoDB AUTO_INCREMENT = 2033199209131880451 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识片段' ROW_FORMAT = DYNAMIC; @@ -1153,7 +1212,9 @@ CREATE TABLE `knowledge_info` ( `enable_hybrid` tinyint(1) NULL DEFAULT 0 COMMENT '是否启用混合检索', `hybrid_alpha` double NULL DEFAULT 0.5 COMMENT '混合检索权重比例 (0.0=纯向量, 1.0=纯关键词)', `system_prompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '系统提示词', - PRIMARY KEY (`id`) USING BTREE + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_tenant_user` (`tenant_id`, `user_id`) USING BTREE, + INDEX `idx_tenant_share` (`tenant_id`, `share`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2033198818050781187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识库' ROW_FORMAT = DYNAMIC; -- ---------------------------- @@ -1235,17 +1296,290 @@ CREATE TABLE `mcp_tool_info` ( INDEX `idx_type`(`type` ASC) USING BTREE, INDEX `idx_status`(`status` ASC) USING BTREE, INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP工具表' ROW_FORMAT = DYNAMIC; +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP工具表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mcp_tool_info -- ---------------------------- -INSERT INTO `mcp_tool_info` VALUES (1, 'edit_file', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-03-10 21:21:09', '0'); -INSERT INTO `mcp_tool_info` VALUES (2, 'list_directory', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-03-10 21:21:09', '0'); -INSERT INTO `mcp_tool_info` VALUES (3, 'read_file', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-03-10 21:21:09', '0'); -INSERT INTO `mcp_tool_info` VALUES (4, 'query_all_tables', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-03-10 21:21:09', '0'); -INSERT INTO `mcp_tool_info` VALUES (5, 'execute_sql_query', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-03-10 21:21:09', '0'); -INSERT INTO `mcp_tool_info` VALUES (6, 'query_table_schema', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-03-10 21:21:09', '0'); +INSERT INTO `mcp_tool_info` VALUES (1, 'edit_file', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-06-22 16:34:29', '0'); +INSERT INTO `mcp_tool_info` VALUES (2, 'list_directory', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-06-22 16:34:29', '0'); +INSERT INTO `mcp_tool_info` VALUES (3, 'read_file', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-06-22 16:34:29', '0'); +INSERT INTO `mcp_tool_info` VALUES (4, 'query_all_tables', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-06-22 16:34:29', '0'); +INSERT INTO `mcp_tool_info` VALUES (5, 'execute_sql_query', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-06-22 16:34:29', '0'); +INSERT INTO `mcp_tool_info` VALUES (6, 'query_table_schema', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-06-22 16:34:29', '0'); +INSERT INTO `mcp_tool_info` VALUES (7, 'bing-cn-mcp-server', '必应中文联网查询工具,支持实时搜索和网页内容抓取。内置工具:bing_search、fetch_webpage。', 'LOCAL', 'ENABLED', '{\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"bing-cn-mcp\"\n ]\n}\n', '000000', -1, -1, '2026-06-22 16:33:22', -1, '2026-06-22 16:33:22', '0'); + +-- ---------------------------- +-- Table structure for short_drama_character +-- ---------------------------- +DROP TABLE IF EXISTS `short_drama_character`; +CREATE TABLE `short_drama_character` ( + `id` bigint NOT NULL COMMENT '主键', + `project_id` bigint NOT NULL COMMENT '项目ID', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名', + `aliases` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '别名/称呼(逗号分隔)', + `introduction` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '角色介绍(身份、关系、称呼映射)', + `role_level` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'B' COMMENT '角色层级:S/A/B/C/D', + `gender` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别', + `age_range` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '年龄段(如:约二十五岁)', + `personality_tags` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性格标签(逗号分隔)', + `costume_tier` int NULL DEFAULT 2 COMMENT '服装华丽度1-5', + `visual_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '视觉外貌描述(详细,用于图片生成)', + `reference_image_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '角色参考图URL', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_project_id`(`project_id` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短剧角色表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of short_drama_character +-- ---------------------------- +INSERT INTO `short_drama_character` VALUES (2075609273630703616, 2075608659739787264, '队医', '', '峨眉队跟队医师,负责队员伤病的现场处理与康复监督。在钰珑脚踝旧伤复发时为其做紧急处理。无特定人际称呼,队员通常只喊「医生」或「队医」。', 'C', '女', '约三十岁', '温柔,细心', 2, '鹅蛋脸线条柔和,双眼皮杏眼明亮有神,小巧挺拔的鼻梁,薄厚适中的嘴唇微抿,柳叶眉自然舒展。乌黑过肩直发扎成利落低马尾,额前碎发自然垂落。身形修长匀称,穿简洁白色短袖Polo衫,左胸有队徽刺绣,搭配卡其色工装长裤和白色运动鞋,腰间斜挎急救小包。', NULL, -1, -1, '2026-07-10 23:53:08', -1, '2026-07-10 23:53:08', 0); +INSERT INTO `short_drama_character` VALUES (2075609329431724032, 2075608659739787264, '徐风', '徐教练', '峨眉队前任功夫教练,决赛前转投对手东亚商联,成为贯穿剧情的主要对手。曾是双双与钰珑的授业者,后利用对她们的了解反制旧部。被队员普遍称呼为「徐教练」。', 'A', '男', '约三十五岁', '高冷,腹黑,专业,野心', 3, '脸型棱角分明,下颌线条锋利。细长眼型,双眼皮,高挺精致的鼻梁,薄唇平直,眉峰上扬的剑眉浓黑有型,右眼尾点缀一颗小痣。乌黑短发以利落背头后梳,露出饱满额头,发质柔顺服帖。身形修长,高挑匀称。身穿深灰色高支羊毛西装,剪裁修身,肩线平直,内搭黑色精纺高领毛衣,下身同色西裤线条流畅,脚著黑色牛津皮鞋。鼻梁上架一副极细金属框运动墨镜,左手腕佩戴银色纤薄腕表。', NULL, -1, -1, '2026-07-10 23:53:22', -1, '2026-07-10 23:53:22', 0); +INSERT INTO `short_drama_character` VALUES (2075609380031807488, 2075608659739787264, '钰珑', '', '峨眉女足主力前锋,曾深度依赖徐风传授的功夫射门技术,后在双双引导下融入团队体系。与双双为挚友,视徐风为前恩师后对手。被双双及其他队友直呼「钰珑」。', 'A', '女', '约二十四岁', '直爽,冲动,坚韧,热情', 2, '钰珑生着一张轮廓分明的瓜子脸,浓黑剑眉斜抬,衬出几分不驯。眼形偏窄的杏眼微微上挑,单眼皮收紧眼尾,转折处利落带锋。鼻梁小巧挺直,嘴唇薄厚适中,下颌角附近有一道浅淡旧伤痕。一头乌黑长发高高束成马尾,额前碎发桀骜翘起,发尾带着自然微卷。身形修长结实,肩背紧绷,常年奔跑的线条紧致流畅。她穿着白色速干运动背心、深灰色运动短裤,脚踩抓地强劲的黑色跑鞋。右脚踝始终缠着防护绷带,步态间略带拖沓,却像随时要蹬地冲出。手腕戴一只简约的黑色运动手表。', NULL, -1, -1, '2026-07-10 23:53:34', -1, '2026-07-10 23:53:34', 0); +INSERT INTO `short_drama_character` VALUES (2075609601688190976, 2075608659739787264, '双双', '', '故事女主角,峨眉女足队长,球队战术核心与精神领袖。前锋钰珑的队友兼好友,前教练徐风曾执教的队员。被队友称呼为「队长」,被钰珑直接叫名字「双双」。', 'S', '女', '约二十五岁', '冷静,果断,温柔,坚毅', 2, '瓜子脸,线条利落分明。一双眼裂纤长的丹凤眼,眼尾如燕尾般锐利上扬,内眼角尖细,双眼皮折痕深刻清晰,注视时自带压迫感。鼻梁挺直高耸,鼻翼收窄,鼻尖精致小巧。双唇薄而轮廓分明,上唇弓形,下唇微微内收。眉骨高隆,眉形为天然剑眉,眉头略粗,眉峰微挑,眉尾收得细长利落。鼻梁中段偏左有一颗极浅小痣。一头乌黑长发在脑后高高束成利落马尾,扎紧于顶骨后方,用黑色弹力绳固定,发丝柔顺垂坠,发尾齐整落至肩胛骨下缘,额前无一丝刘海,露出光洁额头。体型修长而匀称,身姿高挑挺拔。上身穿一件白色速干圆领运动短袖T恤,面料带有微透气网眼,外罩一件深蓝色立领轻型教练夹克,拉链半开,袖口设有魔术贴收束,左臂佩戴红色弹性针织队长袖标,表面印有白色「CAPTAIN」字样。下身为黑色弹力紧身运动长裤,高腰设计,膝盖处有拼接剪裁,脚踝处有短拉链。脚蹬一双白色轻量跑步鞋,鞋面为透气网布拼接合成革,鞋带系紧,鞋底防滑纹理清晰。左手腕佩戴一块简约黑色运动电子手表。', NULL, -1, -1, '2026-07-10 23:54:27', -1, '2026-07-10 23:54:27', 0); +INSERT INTO `short_drama_character` VALUES (2077008099927412736, 2077007721974484992, '百货经理', '经理', '百货公司经理,在工商检查时出现,对小明提出的信息平台方案表示认可并推动放行。角色身份为商业系统干部,被小明称为「经理」。', 'C', '男', '约四十岁', '务实,深谙利益,精明', 3, '方形脸,浓黑剑眉,双眼皮,眼神锐利,鼻梁高挺,嘴唇线条分明。乌黑三七分背头,整齐服帖。身形匀称,身高适中。身穿藏青色修身西装外套,内搭白色衬衫,深灰色西裤,黑色系带皮鞋,手腕佩戴简约钢带手表。', NULL, -1, -1, '2026-07-14 20:31:35', -1, '2026-07-14 20:31:35', 0); +INSERT INTO `short_drama_character` VALUES (2077008101332504576, 2077007721974484992, '老陈', '陈厂长', '工厂厂长,小明的关键支持者和合作伙伴。性格谨慎但敢于冒险,信任小明的能力。被小明称为「陈厂长」,被工人和下属称为「厂长」或「老陈」。', 'A', '男', '约五十岁', '谨慎,务实,有魄力,正直', 2, '长方脸型,轮廓分明,额间有浅横纹,颧骨平直。浓眉微斜,眉尾稍疏,单眼皮细长眼,眼窝微陷,目光专注。鼻梁高直,鼻翼内收,唇形薄而线条清晰,下唇中央有浅凹痕。下巴方圆,无明显痣疤。发色花白,短发偏分,发质硬而蓬松,略见鬓角银丝。身形匀称中等,肩背挺直。上身着灰色中山装,领口紧扣齐整,左下口袋别一支钢笔。下装深灰西裤,裤线笔直,脚穿黑色系带皮鞋,鞋面光亮。无多余配饰。', NULL, -1, -1, '2026-07-14 20:31:35', -1, '2026-07-14 20:31:35', 0); +INSERT INTO `short_drama_character` VALUES (2077008104104939520, 2077007721974484992, '小明', '无', '故事核心主角,从2025年穿越回1985年的重生者。原本是50岁的失意中年,穿越后成为年轻工人。老陈的合作伙伴,被团队成员称为「小明」或「同志」,被老陈直呼「小明」。', 'S', '男', '约二十五岁(实际心理年龄五十岁)', '远见,执着,果断,坚毅', 2, '轮廓分明的瓜子脸,下颌线条利落。深邃的双眼皮眼睛眼型偏长,目光沉静;鼻梁高挺笔直;嘴唇厚薄适中,唇峰清晰;浓密的剑眉斜飞入鬓。右侧眉尾处一颗小痣。发色乌黑,及耳的长度微卷自然蓬松,额前碎发刘海随意散落。身形修长挺拔,身高约一米七八。穿着简约白色棉质衬衫,袖口卷至小臂,搭配深灰色直筒长裤与黑色帆布鞋。', NULL, -1, -1, '2026-07-14 20:31:36', -1, '2026-07-14 20:31:36', 0); + +-- ---------------------------- +-- Table structure for short_drama_character_appearance +-- ---------------------------- +DROP TABLE IF EXISTS `short_drama_character_appearance`; +CREATE TABLE `short_drama_character_appearance` ( + `id` bigint NOT NULL COMMENT '主键', + `character_id` bigint NOT NULL COMMENT '角色ID', + `appearance_index` int NOT NULL DEFAULT 0 COMMENT '形象序号(0=主形象)', + `change_reason` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '初始形象' COMMENT '变化原因', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '形象视觉描述', + `reference_image_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '形象参考图URL', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', + `image_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '生成图片URL列表(JSON数组)', + `image_descriptions` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '每张图片对应的提示词(JSON数组)', + `selected_image_index` int NULL DEFAULT 0 COMMENT '当前选中的图片索引', + `previous_image_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '上一轮图片URL列表(撤销用,JSON数组)', + `previous_descriptions` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '上一轮提示词列表(撤销用,JSON数组)', + `voice` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '音色名(如 zh_male_taocheng_uranus_bigtts),用于该形象对白配音', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_character_appearance`(`character_id` ASC, `appearance_index` ASC) USING BTREE, + INDEX `idx_character_id`(`character_id` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短剧角色子形象表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of short_drama_character_appearance +-- ---------------------------- +INSERT INTO `short_drama_character_appearance` (`id`, `character_id`, `appearance_index`, `change_reason`, `description`, `reference_image_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `image_urls`, `image_descriptions`, `selected_image_index`, `previous_image_urls`, `previous_descriptions`) VALUES (2075609273836224512, 2075609273630703616, 0, '初始形象', '鹅蛋脸线条柔和,双眼皮杏眼明亮有神,小巧挺拔的鼻梁,薄厚适中的嘴唇微抿,柳叶眉自然舒展。乌黑过肩直发扎成利落低马尾,额前碎发自然垂落。身形修长匀称,穿简洁白色短袖Polo衫,左胸有队徽刺绣,搭配卡其色工装长裤和白色运动鞋,腰间斜挎急救小包。', NULL, -1, -1, '2026-07-10 23:53:08', -1, '2026-07-10 23:53:08', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_character_appearance` (`id`, `character_id`, `appearance_index`, `change_reason`, `description`, `reference_image_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `image_urls`, `image_descriptions`, `selected_image_index`, `previous_image_urls`, `previous_descriptions`) VALUES (2075609329570136064, 2075609329431724032, 0, '初始形象', '脸型棱角分明,下颌线条锋利。细长眼型,双眼皮,高挺精致的鼻梁,薄唇平直,眉峰上扬的剑眉浓黑有型,右眼尾点缀一颗小痣。乌黑短发以利落背头后梳,露出饱满额头,发质柔顺服帖。身形修长,高挑匀称。身穿深灰色高支羊毛西装,剪裁修身,肩线平直,内搭黑色精纺高领毛衣,下身同色西裤线条流畅,脚著黑色牛津皮鞋。鼻梁上架一副极细金属框运动墨镜,左手腕佩戴银色纤薄腕表。', NULL, -1, -1, '2026-07-10 23:53:22', -1, '2026-07-10 23:53:22', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_character_appearance` (`id`, `character_id`, `appearance_index`, `change_reason`, `description`, `reference_image_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `image_urls`, `image_descriptions`, `selected_image_index`, `previous_image_urls`, `previous_descriptions`) VALUES (2075609380207968256, 2075609380031807488, 0, '初始形象', '钰珑生着一张轮廓分明的瓜子脸,浓黑剑眉斜抬,衬出几分不驯。眼形偏窄的杏眼微微上挑,单眼皮收紧眼尾,转折处利落带锋。鼻梁小巧挺直,嘴唇薄厚适中,下颌角附近有一道浅淡旧伤痕。一头乌黑长发高高束成马尾,额前碎发桀骜翘起,发尾带着自然微卷。身形修长结实,肩背紧绷,常年奔跑的线条紧致流畅。她穿着白色速干运动背心、深灰色运动短裤,脚踩抓地强劲的黑色跑鞋。右脚踝始终缠着防护绷带,步态间略带拖沓,却像随时要蹬地冲出。手腕戴一只简约的黑色运动手表。', NULL, -1, -1, '2026-07-10 23:53:34', -1, '2026-07-10 23:53:34', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_character_appearance` (`id`, `character_id`, `appearance_index`, `change_reason`, `description`, `reference_image_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `image_urls`, `image_descriptions`, `selected_image_index`, `previous_image_urls`, `previous_descriptions`) VALUES (2075609601822408704, 2075609601688190976, 0, '初始形象', '瓜子脸,线条利落分明。一双眼裂纤长的丹凤眼,眼尾如燕尾般锐利上扬,内眼角尖细,双眼皮折痕深刻清晰,注视时自带压迫感。鼻梁挺直高耸,鼻翼收窄,鼻尖精致小巧。双唇薄而轮廓分明,上唇弓形,下唇微微内收。眉骨高隆,眉形为天然剑眉,眉头略粗,眉峰微挑,眉尾收得细长利落。鼻梁中段偏左有一颗极浅小痣。一头乌黑长发在脑后高高束成利落马尾,扎紧于顶骨后方,用黑色弹力绳固定,发丝柔顺垂坠,发尾齐整落至肩胛骨下缘,额前无一丝刘海,露出光洁额头。体型修长而匀称,身姿高挑挺拔。上身穿一件白色速干圆领运动短袖T恤,面料带有微透气网眼,外罩一件深蓝色立领轻型教练夹克,拉链半开,袖口设有魔术贴收束,左臂佩戴红色弹性针织队长袖标,表面印有白色「CAPTAIN」字样。下身为黑色弹力紧身运动长裤,高腰设计,膝盖处有拼接剪裁,脚踝处有短拉链。脚蹬一双白色轻量跑步鞋,鞋面为透气网布拼接合成革,鞋带系紧,鞋底防滑纹理清晰。左手腕佩戴一块简约黑色运动电子手表。', NULL, -1, -1, '2026-07-10 23:54:27', -1, '2026-07-10 23:54:27', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_character_appearance` (`id`, `character_id`, `appearance_index`, `change_reason`, `description`, `reference_image_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `image_urls`, `image_descriptions`, `selected_image_index`, `previous_image_urls`, `previous_descriptions`) VALUES (2077008100070019072, 2077008099927412736, 0, '初始形象', '方形脸,浓黑剑眉,双眼皮,眼神锐利,鼻梁高挺,嘴唇线条分明。乌黑三七分背头,整齐服帖。身形匀称,身高适中。身穿藏青色修身西装外套,内搭白色衬衫,深灰色西裤,黑色系带皮鞋,手腕佩戴简约钢带手表。', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/dd7454cd25a6486aa5c7c3f8c7085041-3742113260bb4ee9.jpg', -1, -1, '2026-07-14 20:31:35', 1, '2026-07-14 20:38:50', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/dd7454cd25a6486aa5c7c3f8c7085041-3742113260bb4ee9.jpg\"]', '[\"真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感,character design sheet, multiple views reference sheet, front view / side view / back view full body, clean white background, no props, no text. 方形脸,浓黑剑眉,双眼皮,眼神锐利,鼻梁高挺,嘴唇线条分明。乌黑三七分背头,整齐服帖。身形匀称,身高适中。身穿藏青色修身西装外套,内搭白色衬衫,深灰色西裤,黑色系带皮鞋,手腕佩戴简约钢带手表。。角色设定图,画面分为左右两个区域:【左侧区域】占约1/3宽度,是角色的正面特写(完整正脸,最具辨识度的正面形态);【右侧区域】占约2/3宽度,是角色三视图横向排列(从左到右依次为:正面全身、侧面全身、背面全身),三视图高度一致。纯白色背景,无其他元素。\"]', 0, NULL, NULL); +INSERT INTO `short_drama_character_appearance` (`id`, `character_id`, `appearance_index`, `change_reason`, `description`, `reference_image_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `image_urls`, `image_descriptions`, `selected_image_index`, `previous_image_urls`, `previous_descriptions`) VALUES (2077008101487693824, 2077008101332504576, 0, '初始形象', '长方脸型,轮廓分明,额间有浅横纹,颧骨平直。浓眉微斜,眉尾稍疏,单眼皮细长眼,眼窝微陷,目光专注。鼻梁高直,鼻翼内收,唇形薄而线条清晰,下唇中央有浅凹痕。下巴方圆,无明显痣疤。发色花白,短发偏分,发质硬而蓬松,略见鬓角银丝。身形匀称中等,肩背挺直。上身着灰色中山装,领口紧扣齐整,左下口袋别一支钢笔。下装深灰西裤,裤线笔直,脚穿黑色系带皮鞋,鞋面光亮。无多余配饰。', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/4729daf662de42c0a3472bc05043529c-01a8aaf57a08ea21.jpg', -1, -1, '2026-07-14 20:31:35', 1, '2026-07-14 20:38:50', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/4729daf662de42c0a3472bc05043529c-01a8aaf57a08ea21.jpg\"]', '[\"真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感,character design sheet, multiple views reference sheet, front view / side view / back view full body, clean white background, no props, no text. 长方脸型,轮廓分明,额间有浅横纹,颧骨平直。浓眉微斜,眉尾稍疏,单眼皮细长眼,眼窝微陷,目光专注。鼻梁高直,鼻翼内收,唇形薄而线条清晰,下唇中央有浅凹痕。下巴方圆,无明显痣疤。发色花白,短发偏分,发质硬而蓬松,略见鬓角银丝。身形匀称中等,肩背挺直。上身着灰色中山装,领口紧扣齐整,左下口袋别一支钢笔。下装深灰西裤,裤线笔直,脚穿黑色系带皮鞋,鞋面光亮。无多余配饰。。角色设定图,画面分为左右两个区域:【左侧区域】占约1/3宽度,是角色的正面特写(完整正脸,最具辨识度的正面形态);【右侧区域】占约2/3宽度,是角色三视图横向排列(从左到右依次为:正面全身、侧面全身、背面全身),三视图高度一致。纯白色背景,无其他元素。\"]', 0, NULL, NULL); +INSERT INTO `short_drama_character_appearance` (`id`, `character_id`, `appearance_index`, `change_reason`, `description`, `reference_image_url`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `image_urls`, `image_descriptions`, `selected_image_index`, `previous_image_urls`, `previous_descriptions`) VALUES (2077008104272711680, 2077008104104939520, 0, '初始形象', '轮廓分明的瓜子脸,下颌线条利落。深邃的双眼皮眼睛眼型偏长,目光沉静;鼻梁高挺笔直;嘴唇厚薄适中,唇峰清晰;浓密的剑眉斜飞入鬓。右侧眉尾处一颗小痣。发色乌黑,及耳的长度微卷自然蓬松,额前碎发刘海随意散落。身形修长挺拔,身高约一米七八。穿着简约白色棉质衬衫,袖口卷至小臂,搭配深灰色直筒长裤与黑色帆布鞋。', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/aab75b6b-865d-4825-8eee-9e3a45ccec62.png', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:50', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/aab75b6b-865d-4825-8eee-9e3a45ccec62.png\"]', '[\"真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感,character design sheet, multiple views reference sheet, front view / side view / back view full body, clean white background, no props, no text. 轮廓分明的瓜子脸,下颌线条利落。深邃的双眼皮眼睛眼型偏长,目光沉静;鼻梁高挺笔直;嘴唇厚薄适中,唇峰清晰;浓密的剑眉斜飞入鬓。右侧眉尾处一颗小痣。发色乌黑,及耳的长度微卷自然蓬松,额前碎发刘海随意散落。身形修长挺拔,身高约一米七八。穿着简约白色棉质衬衫,袖口卷至小臂,搭配深灰色直筒长裤与黑色帆布鞋。。角色设定图,画面分为左右两个区域:【左侧区域】占约1/3宽度,是角色的正面特写(完整正脸,最具辨识度的正面形态);【右侧区域】占约2/3宽度,是角色三视图横向排列(从左到右依次为:正面全身、侧面全身、背面全身),三视图高度一致。纯白色背景,无其他元素。\"]', 0, NULL, NULL); + +-- ---------------------------- +-- Table structure for short_drama_location +-- ---------------------------- +DROP TABLE IF EXISTS `short_drama_location`; +CREATE TABLE `short_drama_location` ( + `id` bigint NOT NULL COMMENT '主键', + `project_id` bigint NOT NULL COMMENT '项目ID', + `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '场景名(如:客厅_白天)', + `summary` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '场景简要说明', + `has_crowd` tinyint(1) NULL DEFAULT 0 COMMENT '是否有背景人群', + `crowd_description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '背景人群描述', + `available_slots` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '可站位置列表(JSON数组)', + `descriptions` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '场景描述列表(JSON数组,3条差异化描述)', + `reference_image_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '场景全景参考图URL', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', + `image_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '生成图片URL列表(JSON数组)', + `image_descriptions` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '每张图片对应的提示词(JSON数组)', + `selected_image_index` int NULL DEFAULT 0 COMMENT '当前选中的图片索引', + `previous_image_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '上一轮图片URL列表(撤销用,JSON数组)', + `previous_descriptions` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '上一轮提示词列表(撤销用,JSON数组)', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_project_id`(`project_id` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短剧场景表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of short_drama_location +-- ---------------------------- +INSERT INTO `short_drama_location` VALUES (2075609601935654912, 2075608659739787264, '峨眉队训练基地_傍晚', '金色夕阳下的足球训练场,质朴的草地与球门构成核心训练区', 1, '远处有模糊的队员正在进行抢圈练习,人影被拉长', '[\"土场中央中圈开球点的位置\",\"远端球门右侧角旗区附近的位置\",\"场边简易长条凳与战术板之间的空地\"]', '[\"「峨眉队训练基地傍晚」傍晚的余晖将整片土场染成浓郁的金色,宽广的球场向远处延伸,边界由低矮的铁丝围栏框定。左侧设置一座标准尺寸的球门,白色球网微微颤动,右侧对等位置的球门轮廓在逆光中略显模糊。四周几盏简易训练灯尚未点亮,整个空间被暖调的自然光笼罩,光从西侧斜射过来,将球门阴影长长地拖在东侧草坪上。中圈弧线清晰可见,两侧边线外留有宽敞的跑动通道,场边零散放着几个训练标志盘和水瓶。\",\"「峨眉队训练基地傍晚」土质训练场开阔而硬朗,天空半边橙红半边浅紫。镜头从底线角球区拉开,可以看到两座球门、两侧边线以及远处低矮平房的轮廓。西边的斜阳穿过球门网,在泥土地上投出网格状的影子。场地中央被踩实的黄土露出些许草根,四周则还留着稀疏的绿草。场边一侧摆着一条木质长凳,旁边立着一块可移动的战术白板,为这片朴素的空间增添了几分专业气息。空旷的球场除了风声,只剩远处模糊的呼喝与足球撞击的闷响。\",\"「峨眉队训练基地傍晚」广角俯瞰下,整个训练基地被夕阳包裹,像一座独立于时间之外的金色擂台。两座标准足球门遥相对望,球门后的拦网高而完整。中圈向外辐射,中轴线上的草稀而坚实。光线从画面右侧灌入,将左侧的球门区域照得最亮,右侧则稍稍藏在阴影里。场边零散堆放着几件训练背心和几只足球,没有围观的观众,只有空间本身在等待下一场奔跑。远处的训练队员身影模糊成小小的剪影,在远处半场进行着无声的抢圈游戏。\"]', NULL, -1, -1, '2026-07-10 23:54:27', -1, '2026-07-10 23:54:27', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2075609601960820736, 2075608659739787264, '球队更衣室_夜', '赛前赛后使用的长方形更衣室,木凳、储物柜和散落的球衣构成封闭空间', 0, '', '[\"中央长条木凳前方的过道空间\",\"靠近门口储物柜与墙壁之间的角落\",\"另一侧更衣柜前空出的站立区域\"]', '[\"「球队更衣室夜」封闭的长方形房间,天花板上的日光灯管发出冷白色的光,照亮了整间屋子。三面墙壁装着浅绿色金属储物柜,柜门大多虚掩或敞开,里面挂着几件换下的球衣,左侧墙壁上挂着一块白板,上面还留着隐约的战术标记。房间中央横置一条厚重的长条木凳,凳面磨损光滑,周围散落着几双球鞋和护腿板。光从顶部均匀洒下,地面是深灰色的防滑地胶,靠门的角落扔着几件绿色训练背心。门口一方空间相对空阔,连着一条通往淋浴区的走廊。\",\"「球队更衣室夜」灯光通明,更衣室被冷色调充满。正对面的储物柜墙几乎占据了整个视野,柜子编号清晰,其中几个柜门开着,球衣搭在柜沿外边。中央的木凳长度几乎横贯房间,凳下塞着几个运动包。左侧墙角立着一面战术白板,笔迹被擦去大半,残留的弧线隐约可见。右侧是另一排储物柜,柜门反光出冷白的灯管倒影。整个空间静静封闭,只有头顶排风扇发出轻微的嗡鸣,地面清洁但带有些许草屑,门口的通道空出明显的站立空间。\",\"「球队更衣室夜」紧凑但有序的球队更衣室,三面排列着标准体育储物柜,中间一条厚木长凳将空间划为两半。日光灯从天花中央铺开,柜门的金属边框反射出明亮光线。左侧柜组上方的墙壁挂着一面简朴的电子钟,时间显示在晚间。长凳上扔着一双刚脱下的球袜,地板在灯下呈现出深灰色橡胶纹理。靠门口的区域相对整洁,空出的地面足够两人并肩站立,那一侧的门把手还带些微使用过的痕迹。整个房间弥漫着一种刚结束训练的沉默,等待被下一句话打破。\"]', NULL, -1, -1, '2026-07-10 23:54:27', -1, '2026-07-10 23:54:27', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2075609602027929600, 2075608659739787264, '训练场_日', '白天的足球训练场,阳光充足,设有球门、绳梯和战术布置区域,空旷而专注', 0, '', '[\"禁区弧顶正对球门的罚球点附近\",\"中圈开球点周围的开阔区域\",\"场边摆放战术板与训练器材的教练区域\"]', '[\"「训练场日」白日下的标准足球场,草坪修剪整齐,浓绿色向远处铺展。遥相对望的两座白色球门在午后阳光下十分醒目,近端球门的网子绑得紧实。场地中央的中圈白线清晰,禁区线、边线等标记一丝不苟。右侧边线外摆着一架绳梯和几个锥形标志桶,还有一块战术白板支在折叠支架上。阳光从偏南方向照射,将球门立柱的影子投在草皮上。整个训练场除了远处围栏外偶尔经过的人影,场内一片空旷,只剩下训练器材静待使用。\",\"「训练场日」晴朗天气下,训练场被明亮的光线填满,没有观众,只有训练的味道。从底线角度望过去,纵深极强:近处角球弧的弧线、中圈、远方另一侧球门的全景尽收眼底。场地偏左的位置安放着一组红白相间的训练桩,右侧边线附近零星散落着足球。光线柔和地从画面左上方射入,草尖泛出微光。战术白板立在阴凉一侧,上面画着交叉跑位的弧线和点位标记,旁边放着一只医疗箱和几瓶喷雾,这方空间留有足够的站立和讲解余地。\",\"「训练场日」俯瞰的训练场呈现出清晰的几何线条,白色的场地标记在阳光下异常鲜明。两座球门对称分布于两端,禁区的矩形线条和中圈圆环完整。靠近远端球门的一侧,绳梯铺在阴影与阳光的交界处,橙色的标志碟在绿色背景上跳跃。画面中没有风吹动的痕迹,只有光从南偏西的方向大面积洒下,使得左侧球门区域更亮,右侧微暗。边线外的教练区空着,战术板上的弧线图被画得用力,旁边几件荧光训练背心堆在地上,等着一场无声的预演。\"]', NULL, -1, -1, '2026-07-10 23:54:27', -1, '2026-07-10 23:54:27', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2075609602099232768, 2075608659739787264, '亚冠决赛球场_夜', '盛大的亚冠决赛夜球场,灯光璀璨,看台满座,记分牌显示着紧张的下半场比分', 1, '看台上密密麻麻的观众,挥舞着旗帜和围巾,形成人浪与声浪', '[\"中圈弧附近的开阔地带\",\"禁区外正对球门的任意球主罚点\",\"靠近客队教练区一侧的边线区域\"]', '[\"「亚冠决赛球场夜」灯火如昼的巨型体育场,草皮在夜间照明下泛出整齐的墨绿。四面看台坐满了观众,数万人的衣色和旗帜连成涌动的人海,模糊的面孔只看得见张开的嘴巴和举起的围巾。巨大的电子记分牌高悬于一侧看台上方,红色的数字显示下半场72分钟,比分暂为悬殊的落后。两座球门背后,摄影记者长枪短炮已对准禁区。光线来自顶棚密集的灯阵,无死角照亮整个场地,使场边的教练区、替补席和第四官员席都清晰可见。草地中圈弧线被踩得微脏,比赛正进入最窒息的时间。\",\"「亚冠决赛球场夜」从高空俯瞰,这座宏大的足球圣殿被灯光分割成无数块面。绿茵场居于正中,被一圈暗红色的跑道环绕,再往外是层层叠叠的看台,人群如密织的织物铺满每一块区域。左侧球门后方的看台爆发出一片摇动的旗海,右侧观众席则因紧张而微微前倾。记分牌上,时间与比分在夜色中极其刺目。教练区空空地等着下一个指令,边线旁的摄影机摇臂缓缓移动。光从顶棚巨大的灯架倾泻而下,在草皮上几乎没有影子,整个赛场明如白昼,气氛却重如铅水。\",\"「亚冠决赛球场夜」镜头从角球区低角度向上拉升,首先看到近处被蹭掉几条草痕的角旗杆,然后中景闯入密实的禁区线,接着整座球场的壮阔扑面而来。看台组成一道陡峭的人墙,无数手臂在挥舞,助威声浪似有形物质充满空气。正对面远端球门上方,记分牌的数字亮得发烫,时间在流逝,比分残酷。教练区域设置在中线旁,简易的白色画线框内放着水壶和战术纸条。球场灯光从四面八方浇筑,使得场上即将发生的一切都无从遁形,只留下宽广的草皮等待下一脚触球。\"]', NULL, -1, -1, '2026-07-10 23:54:27', -1, '2026-07-10 23:54:27', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2075609602162147328, 2075608659739787264, '球场_伤停补时阶段', '同片决赛球场,但已进入伤停补时,气氛沸腾至顶点,人墙与禁区形成焦点', 1, '全场观众几乎全部站立,紧张地注视着场内,呐喊与屏息交替', '[\"禁区弧前方任意球人墙后的区域\",\"中圈附近等待反击的空旷地带\",\"对方球门远角一侧的包抄路线区域\"]', '[\"「球场伤停补时阶段」伤停补时的灯光仿佛更加灼热,整个体育场被推至沸点。草坪上,禁区前沿排着紧密的人墙,球员们肩并着肩,手臂护住胸前,形成一道移动的屏障。远处的球门在这一刻显得格外宽阔,门柱的影子硬朗地打在草皮上。看台上所有观众都站了起来,无数的身影形成一圈颤抖的轮廓,模糊的旗帜在夜风中猎猎作响。记分牌上的数字显示着3:3的平局,时间刻度停在伤停补时。光从顶棚强力射出,将禁区弧顶的罚球点照得如同舞台中央,四周的空气几乎凝固。\",\"「球场伤停补时阶段」广角定格在球门与禁区之间,人墙严阵以待,守门员在门线前压低重心,整座体育场的视线都聚焦于罚球点。左侧看台无声地亮起一片手机灯光,右侧球迷则咬住围巾。草地被踩踏出无数重叠的足迹,尤其是禁区线附近草皮翻起几道浅痕。球门右上角网窝静止,等待撞击。教练区空无一人,但第四官员举起的电子牌清晰可辨。光从正上方与左右顶棚交织投下,把每一个远近空间填满,唯有远端的底线角旗处在微微的阴影中,为即将可能发生的反抢留出视觉余地。\",\"「球场伤停补时阶段」补时中的决赛草场弥漫着接近终局的肃杀感。中圈到禁区弧这一段区域被无形的手攥紧,人墙背后,球门远角的空间仿佛在主动收缩。看台化为巨大的黑红色背景,点缀着无数晃动的小点。球场中央地带相对空虚,只有几名中场球员的站位拉开宽度,形成随时可以冲刺的空档。远处的角旗区和边线在强烈灯光下泛白,教练区的白线边界依旧清晰,战术纸条散落在地。这个广阔的空间里,下一个呼吸就可能决定所有。顶光无遮无拦,把即将落下的射门路线照得坦坦荡荡。\"]', NULL, -1, -1, '2026-07-10 23:54:27', -1, '2026-07-10 23:54:27', 0, NULL, NULL, 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104339820544, 2077007721974484992, '现代办公室_夜', '深夜的现代办公室,电脑屏幕亮光,桌面杂乱,电水壶和插座位于角落。', 0, '', '[\"办公桌前椅子的正前方位置\",\"门口内侧靠墙的空地\",\"电水壶倾倒处的桌子左侧空地\"]', '[\"「现代办公室_夜」深夜的办公室,空间约二十平米,深灰色地毯覆盖地面,左侧是两扇玻璃窗,窗外城市霓虹灯隐约。右侧墙壁悬挂企业标语,下方是文件柜。中央一张深色办公桌,电脑屏幕为唯一光源,桌面散落纸张和水杯。天花板荧光灯未开,光线从电脑屏幕向右前方散射。前景桌子右侧有空地,左侧靠窗也有站立空间。\",\"「现代办公室_夜」一个中等规模的现代办公室,后墙整面白色,中央挂钟指针指向11点。左侧墙壁有白色百叶窗,光线从窗外微光透入与电脑屏幕蓝光混合。右侧墙壁是嵌入式书架,书本整齐排列。办公桌位于房间中央偏后,桌上一台显示器、电水壶、凌乱文件。天花板吊灯未亮,整体光线昏暗。门口处有一块可站人区域,桌子左侧也有空位。\",\"「现代办公室_夜」约二十平米的封闭办公室,三面墙壁可见:正面墙有白色防火板,左侧墙为玻璃隔断(窗外黑色夜空),右侧墙为木质护墙板。地面铺深色橡胶地板。办公桌位于房间中央稍靠右,桌上电脑亮着,电水壶倒在桌边,插座位在桌下墙角。天花板中央有方形灯槽但未开灯。门口与桌子之间有两米见方的空白区域,可以站立一人;窗户左侧也有约一米宽的通道。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/e927b16c08d64b72a0039fcc6232c626-d9b1460b47849a88.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:50', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/e927b16c08d64b72a0039fcc6232c626-d9b1460b47849a88.jpg\"]', '[\"宽广空间全景,[\\\"「现代办公室_夜」深夜的办公室,空间约二十平米,深灰色地毯覆盖地面,左侧是两扇玻璃窗,窗外城市霓虹灯隐约。右侧墙壁悬挂企业标语,下方是文件柜。中央一张深色办公桌,电脑屏幕为唯一光源,桌面散落纸张和水杯。天花板荧光灯未开,光线从电脑屏幕向右前方散射。前景桌子右侧有空地,左侧靠窗也有站立空间。\\\",\\\"「现代办公室_夜」一个中等规模的现代办公室,后墙整面白色,中央挂钟指针指向11点。左侧墙壁有白色百叶窗,光线从窗外微光透入与电脑屏幕蓝光混合。右侧墙壁是嵌入式书架,书本整齐排列。办公桌位于房间中央偏后,桌上一台显示器、电水壶、凌乱文件。天花板吊灯未亮,整体光线昏暗。门口处有一块可站人区域,桌子左侧也有空位。\\\",\\\"「现代办公室_夜」约二十平米的封闭办公室,三面墙壁可见:正面墙有白色防火板,左侧墙为玻璃隔断(窗外黑色夜空),右侧墙为木质护墙板。地面铺深色橡胶地板。办公桌位于房间中央稍靠右,桌上电脑亮着,电水壶倒在桌边,插座位在桌下墙角。天花板中央有方形灯槽但未开灯。门口与桌子之间有两米见方的空白区域,可以站立一人;窗户左侧也有约一米宽的通道。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104394346496, 2077007721974484992, '小巷_日_1985', '八十年代老式小巷,石板路面,墙上有红色标语,二八自行车靠在墙边。', 0, '', '[\"巷口地面中央位置\",\"右侧墙边二八自行车旁的空地\",\"左侧墙标语下方的台阶处\"]', '[\"「小巷_日_1985」一条狭窄的老式小巷,宽约三米,两侧是青砖老墙,墙高约四米,墙面斑驳。右侧墙上用红漆写着「时间就是金钱」的标语,字迹清晰。地面是水泥路面,有些裂缝。巷口方向有一辆黑色的二八自行车斜靠在墙边。光线从巷口一端射入,形成明暗对比。巷子中央地面可以站立,左侧墙根下也有约一米宽的通道。\",\"「小巷_日_1985」巷子长约五十米,两端可见开阔。左侧墙壁爬满半枯的爬山虎,右墙每隔几米有木质门窗(紧闭)。地面是青石板铺成,略有水渍。巷子尽头有一棵老槐树伸出墙头。阳光从头顶偏左照下,在地面投下窗格阴影。巷口方向有一辆老式二八自行车,车把挂着布包。巷子中央空旷处可站人,右侧自行车旁也有空间。\",\"「小巷_日_1985」宽度不足三米的小巷,两侧墙壁均用灰砖砌成,左墙上有模糊的「工业学大庆」标语残迹,右墙新刷了白色石灰。地面为水泥抹平,中央微微隆起。巷口处堆着几个空木箱。远处可见巷子拐角,墙上挂着一个老式电表箱。光线从正上方偏东方向照入,地面光影斑驳。巷口木箱旁、巷子中央靠近右墙位置均有可供落脚的平坦地面。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/c1ef1eeb2a64424798068602a181c0cc-e1074ab882e7517b.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:50', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/c1ef1eeb2a64424798068602a181c0cc-e1074ab882e7517b.jpg\"]', '[\"宽广空间全景,[\\\"「小巷_日_1985」一条狭窄的老式小巷,宽约三米,两侧是青砖老墙,墙高约四米,墙面斑驳。右侧墙上用红漆写着「时间就是金钱」的标语,字迹清晰。地面是水泥路面,有些裂缝。巷口方向有一辆黑色的二八自行车斜靠在墙边。光线从巷口一端射入,形成明暗对比。巷子中央地面可以站立,左侧墙根下也有约一米宽的通道。\\\",\\\"「小巷_日_1985」巷子长约五十米,两端可见开阔。左侧墙壁爬满半枯的爬山虎,右墙每隔几米有木质门窗(紧闭)。地面是青石板铺成,略有水渍。巷子尽头有一棵老槐树伸出墙头。阳光从头顶偏左照下,在地面投下窗格阴影。巷口方向有一辆老式二八自行车,车把挂着布包。巷子中央空旷处可站人,右侧自行车旁也有空间。\\\",\\\"「小巷_日_1985」宽度不足三米的小巷,两侧墙壁均用灰砖砌成,左墙上有模糊的「工业学大庆」标语残迹,右墙新刷了白色石灰。地面为水泥抹平,中央微微隆起。巷口处堆着几个空木箱。远处可见巷子拐角,墙上挂着一个老式电表箱。光线从正上方偏东方向照入,地面光影斑驳。巷口木箱旁、巷子中央靠近右墙位置均有可供落脚的平坦地面。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104432095232, 2077007721974484992, '街道办门口_日', '街道办事处门口,有传达室和铁门,门口台阶下是水泥路,对面有行道树。', 0, '', '[\"街道办门口台阶正下方的位置\",\"传达室窗户左侧墙边\",\"路对面人行道靠近电线杆处\"]', '[\"「街道办门口_日」一栋灰砖二层小楼,门口上方挂着「XX街道办事处」木牌。楼前有三级水泥台阶,台阶两侧铁质扶手。左侧是传达室,小窗户上方有遮阳棚。门前空地为水泥路面,约十平方米,对面是普通的居民楼围墙,墙脚种着冬青。光线从右上方斜照,在台阶上形成阴影。台阶正下方中央、传达室窗边、路对面冬青旁都有平坦可站区域。\",\"「街道办门口_日」政府机关式的老旧建筑,门口为两扇对开铁栅栏门,门内是院落。门外台阶较宽,约五级,两侧有花盆。右侧墙面上贴着红色通知栏。地面为水磨石,有些破损。远处街道上有电线杆和停着的二八大杠自行车。阳光从左侧高层建筑缝隙中射下,照亮铁门局部。台阶中段、传达室门口左侧、路对面树下空地均可站立。\",\"「街道办门口_日」典型八十年代街道办公点,单层门厅带歇山顶。门厅前有宽大平台,平台边缘有低矮水泥护栏。传达室位于左侧,窗口很小。平台下是水泥马路,马路对面是砖砌围墙,墙上有「为人民服务」标语。光线从东南方照射,平台右侧有树影。平台上中央区域、传达室窗口下方、马路对面围墙根处均可落位。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/ee4c0cd60f6b43f3a459937e0272cc88-1f5cd218d1905a4d.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:50', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/ee4c0cd60f6b43f3a459937e0272cc88-1f5cd218d1905a4d.jpg\"]', '[\"宽广空间全景,[\\\"「街道办门口_日」一栋灰砖二层小楼,门口上方挂着「XX街道办事处」木牌。楼前有三级水泥台阶,台阶两侧铁质扶手。左侧是传达室,小窗户上方有遮阳棚。门前空地为水泥路面,约十平方米,对面是普通的居民楼围墙,墙脚种着冬青。光线从右上方斜照,在台阶上形成阴影。台阶正下方中央、传达室窗边、路对面冬青旁都有平坦可站区域。\\\",\\\"「街道办门口_日」政府机关式的老旧建筑,门口为两扇对开铁栅栏门,门内是院落。门外台阶较宽,约五级,两侧有花盆。右侧墙面上贴着红色通知栏。地面为水磨石,有些破损。远处街道上有电线杆和停着的二八大杠自行车。阳光从左侧高层建筑缝隙中射下,照亮铁门局部。台阶中段、传达室门口左侧、路对面树下空地均可站立。\\\",\\\"「街道办门口_日」典型八十年代街道办公点,单层门厅带歇山顶。门厅前有宽大平台,平台边缘有低矮水泥护栏。传达室位于左侧,窗口很小。平台下是水泥马路,马路对面是砖砌围墙,墙上有「为人民服务」标语。光线从东南方照射,平台右侧有树影。平台上中央区域、传达室窗口下方、马路对面围墙根处均可落位。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104465649664, 2077007721974484992, '废弃仓库_日', '空旷的旧仓库内部,水泥地面,墙面有老标语,中央放置一张木桌和两把椅子。', 0, '', '[\"木桌左侧椅子前方的位置\",\"仓库大门内侧正对门的位置\",\"右侧墙「工业学大庆」标语下方\"]', '[\"「废弃仓库_日」高大空旷的老仓库,内部面积约两百平米,顶棚为弧形钢架,高约八米。地面是水泥抹平,有多处裂缝和油渍。前墙(南墙)有一排高窗,玻璃蒙尘,光线从窗中斜射入形成光柱。后墙正中写有红色大字「工业学大庆」,字迹斑驳。仓库中央放着一张老式木桌,两侧各一把木椅。左侧墙边堆着几个空铁桶。右墙下靠近标语处有一块干净地面,木桌前后也有空间。\",\"「废弃仓库_日」长方形仓库,东西走向,北侧墙面有一扇双开铁皮大门,半开着。室内光线从大门和南墙高窗进入,混合照亮。地面为灰色水泥,有推车压痕。仓库左侧靠墙有木架,架上零散工具。右侧墙有白色涂料脱落,露出红砖。中央木桌桌面放着两盒烟和几张纸。桌椅周围有约两米见方空地。大门内侧、桌子前方、右侧墙根可站人。\",\"「废弃仓库_日」仓库内部宽畅,纵深约四十米,天花板可见裸露木桁架和瓦片。墙面为抹灰白墙,部分脱落,露出砖块。南侧高窗透入冷色天光,地面有光斑。北侧大门关闭,有门缝透光。仓库中央偏左摆放一张旧木桌,桌面摆放烟盒和铅笔。左侧墙边有生锈铁架。右侧墙「工业学大庆」标语下方地面干净。桌子前侧、左侧铁架旁、右侧标语下均有可供人物站立的平整区域。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/22364ee6ea8649788330827d67015aaf-390fb3c2f4c24ee7.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:50', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/22364ee6ea8649788330827d67015aaf-390fb3c2f4c24ee7.jpg\"]', '[\"宽广空间全景,[\\\"「废弃仓库_日」高大空旷的老仓库,内部面积约两百平米,顶棚为弧形钢架,高约八米。地面是水泥抹平,有多处裂缝和油渍。前墙(南墙)有一排高窗,玻璃蒙尘,光线从窗中斜射入形成光柱。后墙正中写有红色大字「工业学大庆」,字迹斑驳。仓库中央放着一张老式木桌,两侧各一把木椅。左侧墙边堆着几个空铁桶。右墙下靠近标语处有一块干净地面,木桌前后也有空间。\\\",\\\"「废弃仓库_日」长方形仓库,东西走向,北侧墙面有一扇双开铁皮大门,半开着。室内光线从大门和南墙高窗进入,混合照亮。地面为灰色水泥,有推车压痕。仓库左侧靠墙有木架,架上零散工具。右侧墙有白色涂料脱落,露出红砖。中央木桌桌面放着两盒烟和几张纸。桌椅周围有约两米见方空地。大门内侧、桌子前方、右侧墙根可站人。\\\",\\\"「废弃仓库_日」仓库内部宽畅,纵深约四十米,天花板可见裸露木桁架和瓦片。墙面为抹灰白墙,部分脱落,露出砖块。南侧高窗透入冷色天光,地面有光斑。北侧大门关闭,有门缝透光。仓库中央偏左摆放一张旧木桌,桌面摆放烟盒和铅笔。左侧墙边有生锈铁架。右侧墙「工业学大庆」标语下方地面干净。桌子前侧、左侧铁架旁、右侧标语下均有可供人物站立的平整区域。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104541147136, 2077007721974484992, '工厂办公室_日', '八十年代工厂办公室,木制办公桌、转盘电话、文件柜,窗外可见厂区。', 0, '', '[\"办公桌前椅子正前方的位置\",\"窗台下方靠墙的空地\",\"门口内侧文件柜旁\"]', '[\"「工厂办公室_日」一间十五平米的老式办公室,水泥地面刷了绿色油漆,略有磨损。正面墙有木框玻璃窗,窗外是工厂院落,可见烟囱和车间屋顶。窗下放着一张深褐色办公桌,桌面有玻璃板压着照片,一部黑色转盘电话放在右侧。左侧墙立着铁皮文件柜,绿色漆。右墙贴着生产进度表。阳光从窗外照射进来,在桌面形成光斑。桌前空地约两平米,窗台下、门口文件柜旁也有空间。\",\"「工厂办公室_日」房间方正,南墙整面为窗户,光线充足。窗户为老式钢窗,窗外有梧桐树枝叶。屋顶为白色涂料,日光灯管一支亮着。地面铺设浅色地板革,有磨损。办公桌靠东墙放置,桌上有笔筒、茶杯和电话。西墙有木制衣架和镜子。北墙门边有铁皮柜。桌前方地板、西墙衣架旁、北墙柜前均可落位。\",\"「工厂办公室_日」约十二平米的办公空间,乳胶漆墙面局部发黄。南侧窗户透入明亮日光,照射在东侧白色墙壁上。办公桌为深棕色三抽屉木桌,居中放置,桌上电话、台历、一部老式计算机。右侧墙角有饮水机和水桶。左侧墙角放置扫帚和簸箕。门在西墙。地板为红砖铺成,铺垫草席。桌前方、门口左侧墙角、右墙饮水机旁都有站立空间。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/86f032880f2e43409a9df446490e7032-6d12c0e8763f4c3e.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:51', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/86f032880f2e43409a9df446490e7032-6d12c0e8763f4c3e.jpg\"]', '[\"宽广空间全景,[\\\"「工厂办公室_日」一间十五平米的老式办公室,水泥地面刷了绿色油漆,略有磨损。正面墙有木框玻璃窗,窗外是工厂院落,可见烟囱和车间屋顶。窗下放着一张深褐色办公桌,桌面有玻璃板压着照片,一部黑色转盘电话放在右侧。左侧墙立着铁皮文件柜,绿色漆。右墙贴着生产进度表。阳光从窗外照射进来,在桌面形成光斑。桌前空地约两平米,窗台下、门口文件柜旁也有空间。\\\",\\\"「工厂办公室_日」房间方正,南墙整面为窗户,光线充足。窗户为老式钢窗,窗外有梧桐树枝叶。屋顶为白色涂料,日光灯管一支亮着。地面铺设浅色地板革,有磨损。办公桌靠东墙放置,桌上有笔筒、茶杯和电话。西墙有木制衣架和镜子。北墙门边有铁皮柜。桌前方地板、西墙衣架旁、北墙柜前均可落位。\\\",\\\"「工厂办公室_日」约十二平米的办公空间,乳胶漆墙面局部发黄。南侧窗户透入明亮日光,照射在东侧白色墙壁上。办公桌为深棕色三抽屉木桌,居中放置,桌上电话、台历、一部老式计算机。右侧墙角有饮水机和水桶。左侧墙角放置扫帚和簸箕。门在西墙。地板为红砖铺成,铺垫草席。桌前方、门口左侧墙角、右墙饮水机旁都有站立空间。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104583090176, 2077007721974484992, '出租屋_夜', '狭小的出租屋,室内有床、桌子和黑板,黑板画着时间轴,灯光昏暗。', 0, '', '[\"黑板前方半米处地面\",\"桌子右侧靠墙位置\",\"门口内侧的空地\"]', '[\"「出租屋_夜」一间约十平米的单间,水泥地面,白灰墙面陈旧泛黄。北墙上有小窗户,挂着花布窗帘。东墙摆放一张单人木床,铺着格子床单。西墙靠着一张方桌,桌面散落纸笔和茶杯。南墙立着一块小黑板,黑板用粉笔画着时间轴和文字。屋顶悬着一盏白炽灯泡,发出昏黄光线,向下照射。床前、黑板前、桌子右侧均有可站立空地。\",\"「出租屋_夜」逼仄的出租屋,空间狭小,内有双层铁床靠左墙,下铺放行李。房间中央是一张折叠桌,桌上有电炉和锅。右墙钉着一块黑板,黑板边角有残缺,上面用粉笔写着「1985-2000」等字样。地面为砖地,不平整。灯光从屋顶一盏裸露灯泡发出,光线偏黄,在墙角形成阴影。桌子前方空地、双层床铺下方前端、黑板的左侧均可落位。\",\"「出租屋_夜」低矮的出租屋,层高约两米五,屋顶有木梁。西墙有一扇木门,门边有挂锁。南墙是大面积窗户,但被报纸糊住。北墙放置一个老旧衣柜,柜门敞开。中央一张矮方桌,桌上有手绘图纸和圆珠笔。地面铺着塑料地板革,已有破损。灯光从屋顶日光灯管发出,白色冷光,照亮全屋。黑板挂于东墙,上面时间轴清晰。桌子周围、衣柜前、门口都有站人空间。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/1c3fe372d8c34a28ad4d157e973a8342-acdc977ee2a76eab.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:51', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/1c3fe372d8c34a28ad4d157e973a8342-acdc977ee2a76eab.jpg\"]', '[\"宽广空间全景,[\\\"「出租屋_夜」一间约十平米的单间,水泥地面,白灰墙面陈旧泛黄。北墙上有小窗户,挂着花布窗帘。东墙摆放一张单人木床,铺着格子床单。西墙靠着一张方桌,桌面散落纸笔和茶杯。南墙立着一块小黑板,黑板用粉笔画着时间轴和文字。屋顶悬着一盏白炽灯泡,发出昏黄光线,向下照射。床前、黑板前、桌子右侧均有可站立空地。\\\",\\\"「出租屋_夜」逼仄的出租屋,空间狭小,内有双层铁床靠左墙,下铺放行李。房间中央是一张折叠桌,桌上有电炉和锅。右墙钉着一块黑板,黑板边角有残缺,上面用粉笔写着「1985-2000」等字样。地面为砖地,不平整。灯光从屋顶一盏裸露灯泡发出,光线偏黄,在墙角形成阴影。桌子前方空地、双层床铺下方前端、黑板的左侧均可落位。\\\",\\\"「出租屋_夜」低矮的出租屋,层高约两米五,屋顶有木梁。西墙有一扇木门,门边有挂锁。南墙是大面积窗户,但被报纸糊住。北墙放置一个老旧衣柜,柜门敞开。中央一张矮方桌,桌上有手绘图纸和圆珠笔。地面铺着塑料地板革,已有破损。灯光从屋顶日光灯管发出,白色冷光,照亮全屋。黑板挂于东墙,上面时间轴清晰。桌子周围、衣柜前、门口都有站人空间。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104604061696, 2077007721974484992, '百货大楼门口_日', '八十年代百货大楼正门,台阶宽大,门口有立柱,橱窗陈列商品,街道上有行人。', 1, '几位穿着中山装和军便装的市民站在台阶上下,有的正在看橱窗,有的在交谈。', '[\"百货大楼正门台阶最高一级中央\",\"右侧立柱旁的地面\",\"台阶下左侧报栏前\",\"对面人行道树下\"]', '[\"「百货大楼门口_日」一栋三层百货大楼,正立面贴白色瓷砖,一层为玻璃橱窗,展示布料、搪瓷盆等商品。楼顶立着「百货大楼」红色大字。门前有五级水磨石台阶,两侧有方形立柱,柱身贴着宣传画。台阶下是宽敞的马路,路边有梧桐树。光学从东南方照来,大楼正面明亮,橱窗玻璃反光。台阶上、立柱旁、报栏前、对面树荫下均有可供人物站立的空间。\",\"「百货大楼门口_日」八十年代百货商场大门,门楣上方有遮雨棚,棚下悬挂「为人民服务」标语。大门为三扇玻璃推拉门,门内可见柜台。左右两侧橱窗内陈列着自行车、缝纫机等展品。门前广场铺着六边形水泥砖,有数根电线杆。光线充足,阳光从右后方照射,左前方有阴影。大门正前方台阶、右侧电线杆附近、左侧橱窗旁均可作为人物落位点。\",\"「百货大楼门口_日」大型百货商店正门区域,建筑为现代主义风格,立面简洁。大门前有宽阔平台,平台四角有花坛,内种月季。地面为水磨石,洁净明亮。左侧有一块公告栏,贴着红色海报。远处可见公交站台,有行人等车。光线从正头顶略偏南,地面有微弱阴影。平台中央、左侧公告栏前、右侧花坛旁均有平坦地面适合站立。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/c94f6cb0c4364d5db271b9606c2bde94-101b8d0c012901fe.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:51', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/c94f6cb0c4364d5db271b9606c2bde94-101b8d0c012901fe.jpg\"]', '[\"宽广空间全景,[\\\"「百货大楼门口_日」一栋三层百货大楼,正立面贴白色瓷砖,一层为玻璃橱窗,展示布料、搪瓷盆等商品。楼顶立着「百货大楼」红色大字。门前有五级水磨石台阶,两侧有方形立柱,柱身贴着宣传画。台阶下是宽敞的马路,路边有梧桐树。光学从东南方照来,大楼正面明亮,橱窗玻璃反光。台阶上、立柱旁、报栏前、对面树荫下均有可供人物站立的空间。\\\",\\\"「百货大楼门口_日」八十年代百货商场大门,门楣上方有遮雨棚,棚下悬挂「为人民服务」标语。大门为三扇玻璃推拉门,门内可见柜台。左右两侧橱窗内陈列着自行车、缝纫机等展品。门前广场铺着六边形水泥砖,有数根电线杆。光线充足,阳光从右后方照射,左前方有阴影。大门正前方台阶、右侧电线杆附近、左侧橱窗旁均可作为人物落位点。\\\",\\\"「百货大楼门口_日」大型百货商店正门区域,建筑为现代主义风格,立面简洁。大门前有宽阔平台,平台四角有花坛,内种月季。地面为水磨石,洁净明亮。左侧有一块公告栏,贴着红色海报。远处可见公交站台,有行人等车。光线从正头顶略偏南,地面有微弱阴影。平台中央、左侧公告栏前、右侧花坛旁均有平坦地面适合站立。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104675364864, 2077007721974484992, '厂庆礼堂_夜', '工厂礼堂内部,舞台上有麦克风,台下长凳坐满工人,红灯笼和横幅装饰。', 1, '几十名穿着蓝色工装的工人整齐坐在长凳上,面向舞台,表情喜悦。', '[\"舞台中央麦克风架前方位置\",\"舞台左侧幕布旁\",\"观众席前排中间走道\",\"礼堂后门入口处\"]', '[\"「厂庆礼堂_夜」一座中型礼堂,空间约三百平米,两侧墙壁挂满红色锦旗和奖状。舞台为砖砌,高约一米,铺着红色地毯,中央立着麦克风,背景幕布为深红色绒布,上方悬挂「厂庆联欢晚会」横幅。观众席摆着二十排长木凳,坐满穿工装的工人。屋顶悬挂多盏白炽灯和串串红灯笼,灯光暖黄。舞台侧方、观众席中间走廊、后门口均有空地可站人。\",\"「厂庆礼堂_夜」礼堂内部呈长方形,前高后低,地板为水泥磨光。天花板有木横梁,垂吊着彩带和纸花。舞台左后侧有上台台阶,右侧放置锣鼓乐器。台下长凳密集排列,过道狭窄,工人拥挤而坐,有些站着。礼堂后墙有双开木门,紧闭。光线主要来自舞台上的聚光灯和两侧壁灯,明亮温暖。舞台中央地面、右侧锣鼓旁、后墙门边有可落位空间。\",\"「厂庆礼堂_夜」宽大的老式礼堂,墙面下半部刷绿色墙裙,上半部白墙。窗户用深色帘幕遮住。舞台台口有弧形边缘,台上有两张木椅和一张讲台。台前摆满花篮。观众席中央过道约一米宽,两侧长凳坐满人。天花板中央一盏大吊灯,四周小灯,整体照明均匀。舞台正前方、左侧幕布缺口处、礼堂后门旁均可站立。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/a9295d479fef45f198bffd3ef850eacd-588538d1e9d8e094.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:51', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/a9295d479fef45f198bffd3ef850eacd-588538d1e9d8e094.jpg\"]', '[\"宽广空间全景,[\\\"「厂庆礼堂_夜」一座中型礼堂,空间约三百平米,两侧墙壁挂满红色锦旗和奖状。舞台为砖砌,高约一米,铺着红色地毯,中央立着麦克风,背景幕布为深红色绒布,上方悬挂「厂庆联欢晚会」横幅。观众席摆着二十排长木凳,坐满穿工装的工人。屋顶悬挂多盏白炽灯和串串红灯笼,灯光暖黄。舞台侧方、观众席中间走廊、后门口均有空地可站人。\\\",\\\"「厂庆礼堂_夜」礼堂内部呈长方形,前高后低,地板为水泥磨光。天花板有木横梁,垂吊着彩带和纸花。舞台左后侧有上台台阶,右侧放置锣鼓乐器。台下长凳密集排列,过道狭窄,工人拥挤而坐,有些站着。礼堂后墙有双开木门,紧闭。光线主要来自舞台上的聚光灯和两侧壁灯,明亮温暖。舞台中央地面、右侧锣鼓旁、后墙门边有可落位空间。\\\",\\\"「厂庆礼堂_夜」宽大的老式礼堂,墙面下半部刷绿色墙裙,上半部白墙。窗户用深色帘幕遮住。舞台台口有弧形边缘,台上有两张木椅和一张讲台。台前摆满花篮。观众席中央过道约一米宽,两侧长凳坐满人。天花板中央一盏大吊灯,四周小灯,整体照明均匀。舞台正前方、左侧幕布缺口处、礼堂后门旁均可站立。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); +INSERT INTO `short_drama_location` VALUES (2077008104721502208, 2077007721974484992, '工厂大门_晨', '清晨工厂大门,新挂牌「华夏信息服务中心」,自行车棚,远处烟囱冒烟。', 1, '几名穿着蓝色工装的工人推着自行车或步行进入厂门,路边有早点摊的模糊身影。', '[\"厂门口新挂牌下方正中间\",\"右侧自行车棚外侧\",\"左侧传达室门口台阶\",\"马路对面老槐树旁\"]', '[\"「工厂大门_晨」工厂大门为铁制对开栅栏门,门柱为红砖砌成,左侧门柱上挂着崭新白底黑字牌子「华夏信息服务中心」。进门可见一条水泥路通向厂区。右侧有一排自行车棚,棚内停着几十辆自行车。远处工厂烟囱缓缓冒白烟,背景是朦胧的晨曦天空。阳光从东方斜照,拉长门柱影子。大门正下方、自行车棚外面、左侧传达室门口、马路对面树荫下均有平坦地面。\",\"「工厂大门_晨」晨曦中的老工厂入口,灰砖围墙高约三米,大门敞开。左侧传达室为平房,窗户透出灯光。门右侧墙上贴着红色通知。路面是水泥铺设,有些许落叶。厂内远处可见车间屋顶和冒烟烟囱。天空泛鱼肚白,东边有朝霞。光线从右前方射来,门牌上的字清晰反光。大门中央、传达室左侧空地、路边电线杆旁都可以站人。\",\"「工厂大门_晨」工厂正门区域,宽阔的厂前空地上矗立着新立的招牌。大门两侧各有一棵法国梧桐,枝叶繁茂。地面为柏油路,路中央画有白色虚线。厂门内侧两边是花坛,种着冬青。远处厂房轮廓在晨雾中若隐若现,烟囱吐烟。光线柔和,晨光从建筑物缝隙中洒落。招牌下方、左侧花坛旁、右侧梧桐树下均是可站立的空旷位置。\"]', 'https://atlas-media.oss-us-west-1.aliyuncs.com/images/50f5cc1fb8e74dac95d589c23dc2daf7-7fb4da35d3075e95.jpg', -1, -1, '2026-07-14 20:31:36', 1, '2026-07-14 20:38:51', 0, '[\"https://atlas-media.oss-us-west-1.aliyuncs.com/images/50f5cc1fb8e74dac95d589c23dc2daf7-7fb4da35d3075e95.jpg\"]', '[\"宽广空间全景,[\\\"「工厂大门_晨」工厂大门为铁制对开栅栏门,门柱为红砖砌成,左侧门柱上挂着崭新白底黑字牌子「华夏信息服务中心」。进门可见一条水泥路通向厂区。右侧有一排自行车棚,棚内停着几十辆自行车。远处工厂烟囱缓缓冒白烟,背景是朦胧的晨曦天空。阳光从东方斜照,拉长门柱影子。大门正下方、自行车棚外面、左侧传达室门口、马路对面树荫下均有平坦地面。\\\",\\\"「工厂大门_晨」晨曦中的老工厂入口,灰砖围墙高约三米,大门敞开。左侧传达室为平房,窗户透出灯光。门右侧墙上贴着红色通知。路面是水泥铺设,有些许落叶。厂内远处可见车间屋顶和冒烟烟囱。天空泛鱼肚白,东边有朝霞。光线从右前方射来,门牌上的字清晰反光。大门中央、传达室左侧空地、路边电线杆旁都可以站人。\\\",\\\"「工厂大门_晨」工厂正门区域,宽阔的厂前空地上矗立着新立的招牌。大门两侧各有一棵法国梧桐,枝叶繁茂。地面为柏油路,路中央画有白色虚线。厂门内侧两边是花坛,种着冬青。远处厂房轮廓在晨雾中若隐若现,烟囱吐烟。光线柔和,晨光从建筑物缝隙中洒落。招牌下方、左侧花坛旁、右侧梧桐树下均是可站立的空旷位置。\\\"],禁止出现任何角色,纯背景板真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感\"]', 0, NULL, NULL); + +-- ---------------------------- +-- Table structure for short_drama_audio +-- ---------------------------- +DROP TABLE IF EXISTS `short_drama_audio`; +CREATE TABLE `short_drama_audio` ( + `id` bigint NOT NULL COMMENT '主键', + `project_id` bigint NOT NULL COMMENT '项目ID', + `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '语音资产名称', + `audio_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'narration' COMMENT '语音类型:narration(旁白)/dialogue(对白)', + `text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '语音文案(生成语音用的文本)', + `voice` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '音色(如 alloy/onyx)', + `audio_oss_id` bigint NULL DEFAULT NULL COMMENT '音频文件OSS ID', + `audio_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '音频文件URL', + `linked_storyboard_id` bigint NULL DEFAULT NULL COMMENT '对白关联的分镜ID(NULL=全局旁白)', + `duration_seconds` int NULL DEFAULT NULL COMMENT '音频时长(秒)', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_project_id`(`project_id` ASC) USING BTREE, + INDEX `idx_linked_storyboard_id`(`linked_storyboard_id` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短剧语音资产表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for short_drama_project +-- ---------------------------- +DROP TABLE IF EXISTS `short_drama_project`; +CREATE TABLE `short_drama_project` ( + `id` bigint NOT NULL COMMENT '主键', + `user_id` bigint NOT NULL COMMENT '用户ID', + `project_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目名称', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '项目描述', + `status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'draft' COMMENT '状态:draft/active/archived', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', + `art_style` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'realistic' COMMENT '视觉风格:american-comic=美漫, chinese-comic=国漫, japanese-anime=日系, realistic=写实', + `composed_video_oss_id` bigint NULL DEFAULT NULL COMMENT '最新成片OSS对象ID', + `compose_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '合成状态:NULL未合成,pending/processing/done/failed', + `compose_job_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '当前合成任务标识(并发防重)', + `compose_progress` int NOT NULL DEFAULT 0 COMMENT '合成进度:0-100', + `compose_transition_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'dissolve' COMMENT '转场类型:none/dissolve/fade/slide', + `compose_transition_duration_seconds` decimal(6, 3) NOT NULL DEFAULT 0.500 COMMENT '单次转场时长(秒)', + `compose_aspect_ratio` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '9:16' COMMENT '成片画幅:9:16/16:9/1:1', + `composed_video_duration_seconds` decimal(12, 3) NULL DEFAULT NULL COMMENT '最终成片实际时长(秒,ffprobe测量)', + `compose_error_message` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '最近一次合成失败原因', + `composed_at` datetime NULL DEFAULT NULL COMMENT '最近一次合成完成时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`user_id` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短剧项目表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of short_drama_project +-- ---------------------------- +INSERT INTO `short_drama_project` VALUES (2077007721974484992, 1, '短剧项目', '50岁失意中年小明意外穿越到1985年,利用未来知识创业,在时代洪流中改写命运,最终留下并影响一代人。', 'draft', -1, -1, '2026-07-14 20:30:05', -1, '2026-07-14 20:51:14', 0, 'realistic', NULL, 'done', NULL, 100, 'fade', 0.300, '9:16', 32.267, NULL, '2026-07-14 20:51:14'); + +-- ---------------------------- +-- Table structure for short_drama_script +-- ---------------------------- +DROP TABLE IF EXISTS `short_drama_script`; +CREATE TABLE `short_drama_script` ( + `id` bigint NOT NULL COMMENT '主键', + `project_id` bigint NOT NULL COMMENT '项目ID', + `script_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '剧本名称', + `script_text` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '剧本文本', + `outline_text` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '大纲文本', + `tone` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '风格/基调', + `source_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'manual' COMMENT '来源:manual/ai', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_project_id`(`project_id` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短剧剧本表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of short_drama_script +-- ---------------------------- +INSERT INTO `short_drama_script` VALUES (2077007722058371072, 2077007721974484992, '重启1985', '1. 内景 现代办公室 夜\n\n办公室昏暗,只有电脑屏幕亮着。50岁的小明穿着皱巴巴的衬衫,盯着屏幕上的辞退邮件。他拿起手机,看到妻子发来的离婚协议截图。手抖着放下手机,碰倒桌上的电水壶,水洒在插座上。火花闪过,小明倒在椅子上。\n\n2. 外景 小巷 日 1985年\n\n小明醒来,发现自己躺在地上,身穿80年代蓝色工装。他摸着脸——没有皱纹。旁边老式自行车铃响,一个年轻人喊:“小明,上班迟到了!”小明看着路口的二八自行车,墙上的标语“时间就是金钱”,愣住。他低头看手腕上的电子表:1985年6月。他深吸一口气,眼神从迷茫变成坚定:“再来一次。”\n\n3. 外景 街道办门口 日\n\n小明拿着一叠手写方案,站在街道办门口。门卫拦住他。小明:“同志,我有商业计划,能帮工厂接全国订单!”门卫:“胡闹!”推他出去。小明捡起散落的纸,抬头看到路对面一个穿中山装的中年男人(老陈)正在看他的方案。老陈:“你这上面写的‘电商’是啥?”小明眼睛一亮:“陈厂长,我能让您仓库里的积压货三天卖掉。”\n\n4. 内景 废弃仓库 日\n\n老陈和小明对面坐着。桌上摆着两盒烟。老陈:“我信你一次,但怎么弄?”小明在纸上画:“用电话线连电脑,搞个库存信息库。我写程序。”老陈:“钱呢?”小明:“我先拉商户,您出设备。”老陈犹豫后点头。小明看着仓库墙上的“工业学大庆”,低声:“这次一定要成。”\n\n5. 内景 工厂办公室 日\n\n办公桌上电话响起。老陈接起,脸色变白。挂断后对小明:“国营百货举报我们投机倒把,工商明天来查。”小明快速翻看账本:“我们走的是信息服务,不是倒卖。”老陈:“没人认可这个。”小明:“明天我来说。”\n\n6. 内景 小明的出租屋 夜\n\n团队成员(两个年轻人)拍桌:“不干了,风险太大!”老陈沉默。小明拿出一块黑板,画出时间轴:“1985年,电脑刚进中国,我们的优势是信息差。不做商品买卖,做平台——帮工厂和供销社匹配库存。”他指着黑板:“明天工商来,我就说这是‘技术咨询’。”老陈抬头:“你哪来的这些?”小明:“梦里学的。”\n\n7. 外景 百货大楼门口 日\n\n工商人员和小明、老陈站在门口。小明拿出一份表格:“这是通过我们信息服务成交的订单,全部有记录,不碰资金,只收咨询费。”工商人员翻看,递给旁边一个戴眼镜的中年人(百货公司经理)。经理看了后低声:“这方法能帮我们清库存?”小明:“三天见效。”经理点头。工商人员收起表格:“先观察。”\n\n8. 内景 厂庆礼堂 夜\n\n工人坐满长凳。老陈上台:“感谢小明的信息平台,咱们工厂活了。”台下鼓掌。小明站在侧台,看着灯光下兴奋的面孔。他手摸到口袋里的现代身份证——早已发黄。他默默撕碎。主持人:“请小明同志讲话!”小明上前,拿着话筒停顿三秒:“各位,未来三十年,互联网会改变一切。今天,我们从这里开始。”台下掌声雷动。\n\n9. 外景 工厂大门 晨\n\n阳光照在新挂牌上:“华夏信息服务中心”。小明推着自行车出来,老陈追上:“下一步?”小明:“跑遍全国,建数据库。”老陈笑:“疯了。”小明跨上车:“1985年,疯的人才能赢。”他蹬车远去,街道两旁的工厂烟囱开始冒烟。', '1.现代办公室夜,小明被老板辞退,回家发现妻子要离婚,绝望中碰倒电水壶触电。2.1985年小巷外景清晨,小明醒来发现变成25岁身体,确认穿越,决定利用记忆创业。3.街道办外景日,小明推销‘互联网购物’概念,被当成疯子赶出,遇到工厂副厂长老陈愿意听。4.废弃仓库内景,小明说服老陈入股,用记忆设计简易电商系统,团队组建。5.工厂办公室内景,初获订单,却遭国营百货公司经理举报,面临查封危机。6.小作家中内景夜,团队内讧,老陈动摇,小明用未来管理经验分析市场,决定转型做信息中介。7.百货大楼外景日,小明带团队利用数据库帮商家库存匹配,化解危机,赢取首个大客户。8.厂庆礼堂内景夜,小明站在台上,回想现代生活,决定留下,带领工人转型。9.同场景,小明讲话,镜头拉远,工厂转型成功。', '现实与励志', 'llm', -1, -1, '2026-07-14 20:30:05', -1, '2026-07-14 20:30:05', 0); + +-- ---------------------------- +-- Table structure for short_drama_storyboard +-- ---------------------------- +DROP TABLE IF EXISTS `short_drama_storyboard`; +CREATE TABLE `short_drama_storyboard` ( + `id` bigint NOT NULL COMMENT '主键', + `project_id` bigint NOT NULL COMMENT '项目ID', + `script_id` bigint NOT NULL COMMENT '剧本ID', + `scene_no` int NOT NULL COMMENT '分镜序号', + `scene_title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分镜标题', + `scene_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '分镜说明', + `scene_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'daily' COMMENT '场景类型:daily/emotion/action/epic/suspense', + `shot_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '镜头景别:如平视中景/俯拍远景', + `camera_move` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '镜头运动:如缓推/固定/跟随/环绕', + `characters_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '镜头角色列表JSON:[{name,appearance,slot}]', + `location_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '场景名称(匹配资产库)', + `photography_rules` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '摄影规则JSON', + `acting_notes` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '表演指导JSON', + `continuity_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '镜头连续性JSON:起始状态、结束状态、动作承接、空间锚点、在场人物', + `source_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '原文片段', + `image_prompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '图片生成提示词', + `duration_seconds` int NOT NULL DEFAULT 15 COMMENT '时长秒数', + `video_prompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '视频提示词', + `video_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '视频地址', + `video_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '视频生成任务ID', + `video_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'pending' COMMENT '视频状态:pending/generating/done/failed', + `last_frame_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '上一镜末帧URL(同场景连续镜头首帧承接用)', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_script_scene`(`script_id` ASC, `scene_no` ASC) USING BTREE, + INDEX `idx_project_id`(`project_id` ASC) USING BTREE, + INDEX `idx_script_id`(`script_id` ASC) USING BTREE, + INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短剧分镜表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of short_drama_storyboard +-- ---------------------------- +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008643517599744, 2077007721974484992, 2077007722058371072, 1, '辞退邮件与触电穿越', '深夜办公室,电脑屏幕亮光映照50岁小明苍老疲惫的脸,他盯着屏幕上的辞退邮件,眼神失焦。他拿起手机看到离婚协议截图,手开始颤抖,手机滑落。他试图扶住桌子,手肘碰倒旁边电水壶,水洒向插座,火花迸溅,小明身体后仰倒在椅子上,画面渐黑。', 'suspense', '平视近景', '缓推至固定', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"办公桌前\"}]', '现代办公室_夜', '{\"panel_number\":1,\"scene_summary\":\"深夜办公室,电脑屏幕亮光映照50岁小明苍老疲惫的脸,他盯着屏幕上的辞退邮件,眼神失焦。他拿起手机看到离婚协议截图,手开始颤抖,手机滑落。他试图扶住桌子,手肘碰倒旁边电水壶,水洒向插座,火花迸溅,小明身体后仰倒在椅子上,画面渐黑。\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"低调硬光,保留阴影层次\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"办公桌前\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"中浅景深,保留环境压迫感\",\"color_tone\":\"冷暗色调\"}', '[{\"name\":\"小明\",\"acting\":\"控制呼吸与眨眼频率,用迟疑、停顿和缓慢转头制造紧张感,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:现代办公室_夜,重新交代人物位置、朝向与环境\",\"end_state\":\"小明倒在椅子上,身体后仰,火花在插座处闪烁,画面渐黑\",\"continuity_action\":\"小明身体失去平衡向后倒,火花仍在闪烁\",\"spatial_anchor\":\"小明位于画面中央偏左,办公桌桌面杂乱的纸张和电脑屏幕占据右半,电水壶翻倒在桌角\",\"present_characters\":[\"小明\"],\"scene_number\":1,\"segment_number\":1,\"segment_goal\":\"建立小明被辞退后的绝望状态并触发穿越\",\"segment_result\":\"小明触电倒下,穿越启动\",\"bridge_in\":\"开场建立办公室环境与人物状态\",\"bridge_out\":\"火花和倒下的动作形成视觉钩子,画面切黑\",\"beat_type\":\"trigger\",\"narrative_cause\":\"小明收到辞退邮件和离婚协议,情绪崩溃导致意外\",\"character_goal\":\"缓解内心痛苦,但无意识动作导致触电\",\"story_action\":\"手抖放下手机,碰倒水壶,水溅到插座引发火花\",\"story_result\":\"小明触电昏迷,穿越时空\",\"next_hook\":\"下一镜头需要展示小明醒来后的新环境\"}', '办公室昏暗,只有电脑屏幕亮着。50岁的小明穿着皱巴巴的衬衫,盯着屏幕上的辞退邮件。他拿起手机,看到妻子发来的离婚协议截图。手抖着放下手机,碰倒桌上的电水壶,水洒在插座上。火花闪过,小明倒在椅子上。', '小明坐在深夜办公室的办公桌前,电脑屏幕蓝光照亮他苍老疲惫的脸,桌上散落纸张,他手中拿着手机看着离婚协议截图,旁边的电水壶摇摇欲坠,背景黑暗,气氛压抑。', 8, '深夜办公室,中年男子坐在办公桌前,电脑屏幕光映照脸庞,他盯着屏幕,眼神失焦。他拿起手机看向屏幕,手开始颤抖,手机滑落。他试图扶住桌子,手肘碰倒旁边的电水壶,水洒向插座,火花迸溅,他身体后仰倒在椅子上。镜头从平视中景缓缓推近至脸部特写,然后固定,画面渐黑。', 'https://atlas-media.oss-us-west-1.aliyuncs.com/videos/cgt-20260714203912-pshlw-0.mp4', '472c35ef433744c8a4cbd35a438ad35e', 'done', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:41:42', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008643727314944, 2077007721974484992, 2077007722058371072, 2, '重生1985', '小明躺在地上,睁开眼,阳光刺眼。他坐起身,低头看见自己穿着蓝色工装,手背皮肤光滑无皱纹。他摸自己的脸,感觉紧致年轻。旁边老式自行车铃响,一个年轻人骑车路过喊“小明,上班迟到了!”小明看向路口停着的二八自行车和墙上红色标语“时间就是金钱”,低头看手腕电子表显示1985.6,他深吸一口气,眼神从迷茫变成坚定,低语“再来一次”。', 'emotion', '平视近景转特写', '缓推加环绕', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"地面中央\"}]', '小巷_日_1985', '{\"panel_number\":2,\"scene_summary\":\"小明躺在地上,睁开眼,阳光刺眼。他坐起身,低头看见自己穿着蓝色工装,手背皮肤光滑无皱纹。他摸自己的脸,感觉紧致年轻。旁边老式自行车铃响,一个年轻人骑车路过喊“小明,上班迟到了!”小明看向路口停着的二八自行车和墙上红色标语“时间就是金钱”,低头看手腕电子表显示1985.6,他深吸一口气,眼神从迷茫变成坚定,低语“再来一次”。\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"柔和侧光,突出面部情绪\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"地面中央\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"浅景深,突出角色表情\",\"color_tone\":\"克制柔和色调\"}', '[{\"name\":\"小明\",\"acting\":\"表情逐步变化,先克制呼吸,再通过眼神和细微肢体释放情绪,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:小巷_日_1985,重新交代人物位置、朝向与环境\",\"end_state\":\"小明站立,眼神坚定,低头看电子表,嘴唇微动说“再来一次”\",\"continuity_action\":\"小明视线从手表移向前方街道,准备行动\",\"spatial_anchor\":\"小明位于小巷中央地面,左侧墙上有红色标语,右侧有自行车和骑车年轻人,背景是老旧居民楼\",\"present_characters\":[\"小明\",\"年轻人(群众)\"],\"scene_number\":2,\"segment_number\":1,\"segment_goal\":\"展示小明穿越后醒来发现变成年轻模样\",\"segment_result\":\"小明确认自己回到1985年,眼神由迷茫变坚定\",\"bridge_in\":\"画面从黑场渐亮,小明面部特写从模糊到清晰\",\"bridge_out\":\"小明深吸一口气并说出内心决定,视线看向前方街道\",\"beat_type\":\"reaction\",\"narrative_cause\":\"小明触电昏迷,穿越时空\",\"character_goal\":\"确认自身状态和时代\",\"story_action\":\"坐起、摸脸、看周围、看电子表\",\"story_result\":\"小明确定自己重生,产生第二次人生的决心\",\"next_hook\":\"下一镜头展示小明带着方案前往街道办\"}', '小明醒来,发现自己躺在地上,身穿80年代蓝色工装。他摸着脸——没有皱纹。旁边老式自行车铃响,一个年轻人喊:“小明,上班迟到了!”小明看着路口的二八自行车,墙上的标语“时间就是金钱”,愣住。他低头看手腕上的电子表:1985年6月。他深吸一口气,眼神从迷茫变成坚定:“再来一次。”', '小明坐在小巷地面,身穿蓝色工装,阳光从巷口射入照亮他的脸,他摸着自己年轻的脸庞,眼神迷茫;背景是青砖墙和红色标语,一辆老式自行车靠在墙边。', 10, '年轻男子躺在地面上,阳光刺眼,他缓缓睁开眼,然后坐起身,低头看自己双手和蓝色工装,摸自己的脸,表情从迷茫到惊讶。旁边传来自行车铃声,他转头看向巷口的二八自行车和「时间就是金钱」标语,再低头看手腕上的电子表,眼神从迷茫变为坚定。镜头从全景缓慢推近至脸部特写,同时环绕拍摄,最后固定。', 'https://atlas-media.oss-us-west-1.aliyuncs.com/videos/cgt-20260714203913-lxtrb-0.mp4', 'b3b6843f0b78433893608675852797bd', 'done', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:47:08', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008643781840896, 2077007721974484992, 2077007722058371072, 3, '街道办受阻遇老陈', '街道办门口,小明拿着一叠手写方案快步走向大门。门卫伸出手臂挡住他。小明急切地解释“同志,我有商业计划,能帮工厂接全国订单!”门卫皱眉推他出去,纸张散落一地。小明蹲下慌乱捡纸。路对面一个穿中山装的中年男人(老陈)弯腰捡起一张,看着上面的字,抬头问:“你这上面写的‘电商’是啥?”小明抬头,眼睛一亮:“陈厂长,我能让您仓库里的积压货三天卖掉。”', 'daily', '平视中景', '轻微跟随', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"门口台阶下\"},{\"name\":\"老陈\",\"appearance\":\"初始形象\",\"slot\":\"路对面人行道\"}]', '街道办门口_日', '{\"panel_number\":3,\"scene_summary\":\"街道办门口,小明拿着一叠手写方案快步走向大门。门卫伸出手臂挡住他。小明急切地解释“同志,我有商业计划,能帮工厂接全国订单!”门卫皱眉推他出去,纸张散落一地。小明蹲下慌乱捡纸。路对面一个穿中山装的中年男人(老陈)弯腰捡起一张,看着上面的字,抬头问:“你这上面写的‘电商’是啥?”小明抬头,眼睛一亮:“陈厂长,我能让您仓库里的积压货三天卖掉。”\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"自然柔光,肤色与环境协调\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"门口台阶下\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"老陈\",\"screen_position\":\"路对面人行道\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"中等景深,兼顾角色与环境\",\"color_tone\":\"自然统一色调\"}', '[{\"name\":\"小明\",\"acting\":\"保持自然呼吸和目光交流,动作从起始状态平滑过渡到结束状态,按前段、中段、后段完成三个连续表演节拍\"},{\"name\":\"老陈\",\"acting\":\"保持自然呼吸和目光交流,动作从起始状态平滑过渡到结束状态,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:街道办门口_日,重新交代人物位置、朝向与环境\",\"end_state\":\"小明蹲在地上抬头看向老陈,老陈站在路对面手持一张纸,两人视线交汇\",\"continuity_action\":\"小明视线锁定老陈,准备进一步解释\",\"spatial_anchor\":\"街道办铁门在画面左侧,门卫站在台阶上;小明在台阶下中央;老陈在画面右侧路对面行道树旁\",\"present_characters\":[\"小明\",\"老陈\",\"门卫(群众)\"],\"scene_number\":3,\"segment_number\":1,\"segment_goal\":\"小明尝试进入街道办被门卫阻拦,吸引老陈注意\",\"segment_result\":\"老陈捡起散落的方案并开始询问\",\"bridge_in\":\"小明拿着方案走向街道办大门,眼神充满希望\",\"bridge_out\":\"老陈目光落在方案上,问题引发后续对话\",\"beat_type\":\"trigger\",\"narrative_cause\":\"小明确定自己重生,产生第二次人生的决心\",\"character_goal\":\"进入街道办寻求支持\",\"story_action\":\"小明上前解释,被推倒,捡纸时发现老陈在看方案\",\"story_result\":\"老陈对方案产生好奇并主动与小明对话\",\"next_hook\":\"下一镜头展示两人在废弃仓库深入讨论\"}', '小明拿着一叠手写方案,站在街道办门口。门卫拦住他。小明:“同志,我有商业计划,能帮工厂接全国订单!”门卫:“胡闹!”推他出去。小明捡起散落的纸,抬头看到路对面一个穿中山装的中年男人(老陈)正在看他的方案。老陈:“你这上面写的‘电商’是啥?”小明眼睛一亮:“陈厂长,我能让您仓库里的积压货三天卖掉。”', '小明站在街道办门口台阶下,手拿一叠方案,表情急切;门卫站在台阶上伸手阻拦;路对面老陈穿中山装低头看纸,阳光从右上方照射。', 8, '年轻男子拿着一叠手写方案快步走向街道办大门,门卫伸出手臂阻挡。年轻男子急切地解释,门卫皱眉推他出去,纸张散落。年轻男子蹲下慌乱捡纸。路对面一位中年男子弯腰捡起一张纸,看着上面,抬头问话。镜头平视跟随年轻男子移动,然后切换到越肩视角展示门卫的阻拦动作。', 'https://atlas-media.oss-us-west-1.aliyuncs.com/videos/cgt-20260714203920-6rw8b-0.mp4', '7173c12461574b8abd063c46fc625ebe', 'done', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:47:09', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008643823783936, 2077007721974484992, 2077007722058371072, 4, '仓库达成合作', '废弃仓库内,一张木桌两把椅子,小明和老陈对面而坐。桌上摆着两盒烟,老陈抽出一根点燃:“我信你一次,但怎么弄?”小明拿起笔在纸上画示意图:“用电话线连电脑,搞个库存信息库。我写程序。”老陈皱眉:“钱呢?”小明:“我先拉商户,您出设备。”老陈抖了抖烟灰,沉默几秒后点头。小明转头看向墙上的标语“工业学大庆”,低声:“这次一定要成。”', 'daily', '平视中景', '缓推', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"桌子一侧\"},{\"name\":\"老陈\",\"appearance\":\"初始形象\",\"slot\":\"桌子另一侧\"}]', '废弃仓库_日', '{\"panel_number\":4,\"scene_summary\":\"废弃仓库内,一张木桌两把椅子,小明和老陈对面而坐。桌上摆着两盒烟,老陈抽出一根点燃:“我信你一次,但怎么弄?”小明拿起笔在纸上画示意图:“用电话线连电脑,搞个库存信息库。我写程序。”老陈皱眉:“钱呢?”小明:“我先拉商户,您出设备。”老陈抖了抖烟灰,沉默几秒后点头。小明转头看向墙上的标语“工业学大庆”,低声:“这次一定要成。”\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"自然柔光,肤色与环境协调\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"桌子一侧\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"老陈\",\"screen_position\":\"桌子另一侧\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"中等景深,兼顾角色与环境\",\"color_tone\":\"自然统一色调\"}', '[{\"name\":\"小明\",\"acting\":\"保持自然呼吸和目光交流,动作从起始状态平滑过渡到结束状态,按前段、中段、后段完成三个连续表演节拍\"},{\"name\":\"老陈\",\"acting\":\"保持自然呼吸和目光交流,动作从起始状态平滑过渡到结束状态,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:废弃仓库_日,重新交代人物位置、朝向与环境\",\"end_state\":\"老陈点头,小明目光从墙上标语收回,表情坚定\",\"continuity_action\":\"小明视线从标语移回老陈,准备下一步计划\",\"spatial_anchor\":\"桌子位于仓库中央,小明坐画面左侧,老陈坐右侧,后面墙上有标语,水泥地面空旷\",\"present_characters\":[\"小明\",\"老陈\"],\"scene_number\":4,\"segment_number\":1,\"segment_goal\":\"小明与老陈在仓库达成合作意向\",\"segment_result\":\"老陈决定信任小明,提供设备支持\",\"bridge_in\":\"两人对面而坐,桌上两盒烟,老陈面露犹豫\",\"bridge_out\":\"老陈点头,小明看向墙上标语,低声坚定信念\",\"beat_type\":\"action\",\"narrative_cause\":\"老陈对方案产生好奇并主动与小明对话\",\"character_goal\":\"说服老陈提供设备支持\",\"story_action\":\"小明画图解释技术方案,提出资源分工\",\"story_result\":\"老陈同意合作,小明获得初步资源\",\"next_hook\":\"下一镜头展示工厂办公室内新进展与危机\"}', '老陈和小明对面坐着。桌上摆着两盒烟。老陈:“我信你一次,但怎么弄?”小明在纸上画:“用电话线连电脑,搞个库存信息库。我写程序。”老陈:“钱呢?”小明:“我先拉商户,您出设备。”老陈犹豫后点头。小明看着仓库墙上的“工业学大庆”,低声:“这次一定要成。”', '小明和老陈在废弃仓库的旧木桌对面而坐,桌上两盒烟和散落的纸张;老陈夹烟沉思,小明执笔画示意图;背景墙上红色大字「工业学大庆」,高窗透入光线。', 8, '年轻男子和中年男子对面坐在木桌两侧,桌上摆着两盒烟。中年男子抽出一根点燃,年轻男子拿起笔在纸上画图并说话。中年男子皱眉问话,年轻男子抬头回答,伸手指向纸面。中年男子抖烟灰后点头。年轻男子转头看向墙上的标语,低声自语。镜头从两人的中景缓缓推近至年轻男子的脸部,然后固定。', NULL, 'd4665ee140f34673914292855d389d38', 'generating', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:39:20', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008643874115584, 2077007721974484992, 2077007722058371072, 5, '工商检查危机', '工厂办公室内,木制办公桌上转盘电话响起。老陈拿起听筒,脸色逐渐变白,手指捏紧话筒。挂断后他转身对旁边椅子上的小明说:“国营百货举报我们投机倒把,工商明天来查。”小明快速翻看桌上的账本,抬起头:“我们走的是信息服务,不是倒卖。”老陈叹气:“没人认可这个。”小明合上账本,直视老陈:“明天我来说。”', 'suspense', '平视近景', '缓推', '[{\"name\":\"老陈\",\"appearance\":\"初始形象\",\"slot\":\"办公桌后\"},{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"办公桌旁椅子\"}]', '工厂办公室_日', '{\"panel_number\":5,\"scene_summary\":\"工厂办公室内,木制办公桌上转盘电话响起。老陈拿起听筒,脸色逐渐变白,手指捏紧话筒。挂断后他转身对旁边椅子上的小明说:“国营百货举报我们投机倒把,工商明天来查。”小明快速翻看桌上的账本,抬起头:“我们走的是信息服务,不是倒卖。”老陈叹气:“没人认可这个。”小明合上账本,直视老陈:“明天我来说。”\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"低调硬光,保留阴影层次\"},\"characters\":[{\"name\":\"老陈\",\"screen_position\":\"办公桌后\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"小明\",\"screen_position\":\"办公桌旁椅子\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"中浅景深,保留环境压迫感\",\"color_tone\":\"冷暗色调\"}', '[{\"name\":\"老陈\",\"acting\":\"控制呼吸与眨眼频率,用迟疑、停顿和缓慢转头制造紧张感\"},{\"name\":\"小明\",\"acting\":\"控制呼吸与眨眼频率,用迟疑、停顿和缓慢转头制造紧张感\"}]', '{\"start_state\":\"新场景建立:工厂办公室_日,重新交代人物位置、朝向与环境\",\"end_state\":\"小明合上账本,直视老陈,表情自信;老陈眉头紧锁但不再反驳\",\"continuity_action\":\"小明视线坚定,老陈目光仍带忧虑但转向信任\",\"spatial_anchor\":\"老陈位于画面左侧办公桌后,小明在右侧椅子,窗外可见厂区烟囱,文件柜靠墙\",\"present_characters\":[\"小明\",\"老陈\"],\"scene_number\":5,\"segment_number\":1,\"segment_goal\":\"工厂办公室内老陈接到工商查处的电话,小明提出应对\",\"segment_result\":\"小明决定亲自去面对工商检查\",\"bridge_in\":\"办公室电话铃响打破平静,老陈接听表情变化\",\"bridge_out\":\"老陈看向小明,小明坚定地说“明天我来说”\",\"beat_type\":\"trigger\",\"narrative_cause\":\"老陈同意合作,小明获得初步资源\",\"character_goal\":\"让老陈相信自己能解决危机\",\"story_action\":\"小明解释模式合法性,并主动承担交涉责任\",\"story_result\":\"老陈部分安心,小明获得应对危机的授权\",\"next_hook\":\"下一镜头展示出租屋内团队内部争吵与统一思想\"}', '办公桌上电话响起。老陈接起,脸色变白。挂断后对小明:“国营百货举报我们投机倒把,工商明天来查。”小明快速翻看账本:“我们走的是信息服务,不是倒卖。”老陈:“没人认可这个。”小明:“明天我来说。”', '工厂办公室内,老陈坐在办公桌后接电话,脸色凝重;小明坐在侧面椅子上翻看账本,窗外可见工厂烟囱;桌上转盘电话和文件散落。', 7, '办公桌上黑色转盘电话响起。中年男子坐在桌后拿起听筒,脸色逐渐变白,手指捏紧话筒。他挂断后转身对旁边的年轻男子说话。年轻男子快速翻看账本,抬起头直视对方回话,然后合上账本。镜头从电话特写缓慢推近至中年男子面部,再缓拉到两人中景。', 'https://atlas-media.oss-us-west-1.aliyuncs.com/videos/cgt-20260714203917-gnb2m-0.mp4', '61edb37e979a426f9e8272786243c243', 'done', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:41:08', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008643920252928, 2077007721974484992, 2077007722058371072, 6, '出租屋统一思想', '狭小出租屋,灯光昏暗。两个年轻团队成员拍桌站起:“不干了,风险太大!”老陈坐在床边沉默,低头看地面。小明从墙角拿起一块黑板放在桌上,画出一条时间轴,标注1985年,指向电脑图标。他转头对大家说:“1985年,电脑刚进中国,我们的优势是信息差。不做商品买卖,做平台——帮工厂和供销社匹配库存。”他指着黑板:“明天工商来,我就说这是‘技术咨询’。”老陈抬头,皱眉问:“你哪来的这些?”小明停顿一下:“梦里学的。”', 'emotion', '平视近景转特写', '缓推加环绕', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"桌边站立\"},{\"name\":\"老陈\",\"appearance\":\"初始形象\",\"slot\":\"床边坐着\"}]', '出租屋_夜', '{\"panel_number\":6,\"scene_summary\":\"狭小出租屋,灯光昏暗。两个年轻团队成员拍桌站起:“不干了,风险太大!”老陈坐在床边沉默,低头看地面。小明从墙角拿起一块黑板放在桌上,画出一条时间轴,标注1985年,指向电脑图标。他转头对大家说:“1985年,电脑刚进中国,我们的优势是信息差。不做商品买卖,做平台——帮工厂和供销社匹配库存。”他指着黑板:“明天工商来,我就说这是‘技术咨询’。”老陈抬头,皱眉问:“你哪来的这些?”小明停顿一下:“梦里学的。”\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"柔和侧光,突出面部情绪\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"桌边站立\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"老陈\",\"screen_position\":\"床边坐着\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"浅景深,突出角色表情\",\"color_tone\":\"克制柔和色调\"}', '[{\"name\":\"小明\",\"acting\":\"表情逐步变化,先克制呼吸,再通过眼神和细微肢体释放情绪,按前段、中段、后段完成三个连续表演节拍\"},{\"name\":\"老陈\",\"acting\":\"表情逐步变化,先克制呼吸,再通过眼神和细微肢体释放情绪,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:出租屋_夜,重新交代人物位置、朝向与环境\",\"end_state\":\"老陈抬头看向小明,两个年轻人停止争吵望向黑板,小黑板上画有时间轴和“电脑”字样\",\"continuity_action\":\"小明手指还指着黑板上的“技术咨询”,等待异议\",\"spatial_anchor\":\"画面中央是一张桌子,黑板靠在桌上,小明站在桌子左侧画黑板,老陈坐在右侧床边,两个年轻人站在桌子远端\",\"present_characters\":[\"小明\",\"老陈\",\"两个年轻人(群众)\"],\"scene_number\":6,\"segment_number\":1,\"segment_goal\":\"出租屋内团队成员因风险而退缩,小明用时间轴规划说服大家\",\"segment_result\":\"老陈和小明达成共识,团队重拾信心\",\"bridge_in\":\"出租屋内两个年轻人拍桌起身,气氛紧张\",\"bridge_out\":\"老陈抬头看小明,眼神中带着疑惑与信任\",\"beat_type\":\"decision\",\"narrative_cause\":\"老陈部分安心,小明获得应对危机的授权\",\"character_goal\":\"稳定团队,统一认知\",\"story_action\":\"小明画出时间轴,解释信息差优势和应对策略\",\"story_result\":\"团队情绪稳定,老陈虽疑惑但接受解释\",\"next_hook\":\"下一镜头展示百货大楼门口工商检查的结果\"}', '团队成员(两个年轻人)拍桌:“不干了,风险太大!”老陈沉默。小明拿出一块黑板,画出时间轴:“1985年,电脑刚进中国,我们的优势是信息差。不做商品买卖,做平台——帮工厂和供销社匹配库存。”他指着黑板:“明天工商来,我就说这是‘技术咨询’。”老陈抬头:“你哪来的这些?”小明:“梦里学的。”', '出租屋内,小明站在桌子左侧画黑板,黑板上粉笔写着时间轴和「电脑」字样;老陈坐在床边抬头看;两个年轻人站在桌子远端,表情从愤怒转为倾听;昏黄灯光下气氛紧张。', 10, '狭小出租屋内,两个年轻人拍桌站起情绪激动。中年男子坐在床边低头沉默。年轻男子从墙角拿起一块黑板放在桌上,用粉笔画出一条时间轴,标注1985和电脑图标,转头对大家说话并指向黑板。中年男子抬起头皱眉问话,年轻男子停顿后回答。镜头从全景缓慢推近至年轻男子面部特写,然后环绕拍摄黑板上的时间轴。', NULL, NULL, 'pending', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:33:44', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008643978973184, 2077007721974484992, 2077007722058371072, 7, '工商检查通过', '百货大楼门口台阶上,工商人员和穿藏青色西装的中年经理站在一起。小明从公文包拿出一叠表格递过去:“这是通过我们信息服务成交的订单,全部有记录,不碰资金,只收咨询费。”工商人员翻看后递给旁边的百货经理。经理推了推眼镜细看,低声问小明:“这方法能帮我们清库存?”小明点头:“三天见效。”经理转向工商人员点了点头。工商人员收起表格:“先观察。”', 'daily', '平视中景', '轻微跟随', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"台阶上右侧\"},{\"name\":\"老陈\",\"appearance\":\"初始形象\",\"slot\":\"台阶上小明身后\"},{\"name\":\"百货经理\",\"appearance\":\"初始形象\",\"slot\":\"台阶上中央\"}]', '百货大楼门口_日', '{\"panel_number\":7,\"scene_summary\":\"百货大楼门口台阶上,工商人员和穿藏青色西装的中年经理站在一起。小明从公文包拿出一叠表格递过去:“这是通过我们信息服务成交的订单,全部有记录,不碰资金,只收咨询费。”工商人员翻看后递给旁边的百货经理。经理推了推眼镜细看,低声问小明:“这方法能帮我们清库存?”小明点头:“三天见效。”经理转向工商人员点了点头。工商人员收起表格:“先观察。”\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"自然柔光,肤色与环境协调\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"台阶上右侧\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"老陈\",\"screen_position\":\"台阶上小明身后\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"百货经理\",\"screen_position\":\"台阶上中央\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"中等景深,兼顾角色与环境\",\"color_tone\":\"自然统一色调\"}', '[{\"name\":\"小明\",\"acting\":\"保持自然呼吸和目光交流,动作从起始状态平滑过渡到结束状态,按前段、中段、后段完成三个连续表演节拍\"},{\"name\":\"老陈\",\"acting\":\"保持自然呼吸和目光交流,动作从起始状态平滑过渡到结束状态,按前段、中段、后段完成三个连续表演节拍\"},{\"name\":\"百货经理\",\"acting\":\"保持自然呼吸和目光交流,动作从起始状态平滑过渡到结束状态,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:百货大楼门口_日,重新交代人物位置、朝向与环境\",\"end_state\":\"工商人员收起表格,经理向小明点头,小明露出微笑\",\"continuity_action\":\"小明视线跟随工商人员离开,准备下一步行动\",\"spatial_anchor\":\"百货大楼正门台阶为背景,工商人员在中央偏左,经理在左侧,小明在右侧,老陈在小明后方半步\",\"present_characters\":[\"小明\",\"老陈\",\"百货经理\",\"工商人员(群众)\"],\"scene_number\":7,\"segment_number\":1,\"segment_goal\":\"工商检查现场,小明用订单记录证明信息服务合法性,获得百货经理认可\",\"segment_result\":\"工商同意观察,项目得以继续\",\"bridge_in\":\"百货大楼门口,工商人员和小明、老陈站立,气氛凝重\",\"bridge_out\":\"工商人员收起表格,经理点头,危机解除\",\"beat_type\":\"result\",\"narrative_cause\":\"团队情绪稳定,老陈虽疑惑但接受解释\",\"character_goal\":\"用事实凭证打消工商疑虑\",\"story_action\":\"小明出示订单记录并解释运营模式\",\"story_result\":\"工商暂缓处理,项目获得官方默许\",\"next_hook\":\"下一镜头展示庆功礼堂的成果与小明的情感时刻\"}', '工商人员和小明、老陈站在门口。小明拿出一份表格:“这是通过我们信息服务成交的订单,全部有记录,不碰资金,只收咨询费。”工商人员翻看,递给旁边一个戴眼镜的中年人(百货公司经理)。经理看了后低声:“这方法能帮我们清库存?”小明:“三天见效。”经理点头。工商人员收起表格:“先观察。”', '小明站在百货大楼门口台阶上,从公文包中拿出表格递给工商人员;老陈站在他身后;旁边穿藏青色西装的百货经理低头查看表格;阳光照亮大楼立面。', 8, '百货大楼门口台阶上,工商人员和穿西装的中年男子站在一起。年轻男子从公文包拿出一叠表格递出并说话。工商人员翻看后递给旁边的经理。经理推了推眼镜细看,低声问年轻男子,年轻男子点头回答。经理转向工商人员点头。工商人员收起表格。镜头平视跟随年轻男子的动作,使用越肩视角展示表格内容。', NULL, NULL, 'pending', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:33:44', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008644041887744, 2077007721974484992, 2077007722058371072, 8, '厂庆宣言互联网愿景', '厂庆礼堂,红灯笼和横幅装饰,舞台上麦克风前,老陈对着台下说:“感谢小明的信息平台,咱们工厂活了。”台下工人鼓掌。小明站在侧台阴影里,手伸进口袋摸到发黄的现代身份证,他看着灯光下兴奋的面孔,默默将身份证撕碎。主持人喊:“请小明同志讲话!”小明走上舞台中央,拿起话筒,停顿三秒:“各位,未来三十年,互联网会改变一切。今天,我们从这里开始。”台下掌声雷动。', 'epic', '大远景转特写', '俯拍后缓降推近', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"舞台上中央\"},{\"name\":\"老陈\",\"appearance\":\"初始形象\",\"slot\":\"舞台侧边\"}]', '厂庆礼堂_夜', '{\"panel_number\":8,\"scene_summary\":\"厂庆礼堂,红灯笼和横幅装饰,舞台上麦克风前,老陈对着台下说:“感谢小明的信息平台,咱们工厂活了。”台下工人鼓掌。小明站在侧台阴影里,手伸进口袋摸到发黄的现代身份证,他看着灯光下兴奋的面孔,默默将身份证撕碎。主持人喊:“请小明同志讲话!”小明走上舞台中央,拿起话筒,停顿三秒:“各位,未来三十年,互联网会改变一切。今天,我们从这里开始。”台下掌声雷动。\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"大范围自然光,突出空间规模\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"舞台上中央\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"老陈\",\"screen_position\":\"舞台侧边\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"深景深,清晰展现环境规模\",\"color_tone\":\"宏大通透色调\"}', '[{\"name\":\"小明\",\"acting\":\"动作沉稳有力量,视线跟随环境或对手变化,保持画面张力,按前段、中段、后段完成三个连续表演节拍\"},{\"name\":\"老陈\",\"acting\":\"动作沉稳有力量,视线跟随环境或对手变化,保持画面张力,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:厂庆礼堂_夜,重新交代人物位置、朝向与环境\",\"end_state\":\"小明站在舞台中央举起话筒,台下工人站立鼓掌,老陈在侧台微笑\",\"continuity_action\":\"小明视线扫过全场,准备下台开始新阶段\",\"spatial_anchor\":\"舞台在画面中央靠上,台下长凳坐满工人,小明位于舞台中央,老陈在舞台左侧靠边\",\"present_characters\":[\"小明\",\"老陈\",\"工人(群众)\",\"主持人(群众)\"],\"scene_number\":8,\"segment_number\":1,\"segment_goal\":\"厂庆礼堂中老陈表扬小明,小明撕碎现代身份证并发表讲话\",\"segment_result\":\"小明公开宣告互联网未来愿景,全体工人热烈鼓掌\",\"bridge_in\":\"礼堂内工人坐满长凳,老陈在台上讲话\",\"bridge_out\":\"掌声雷动中,小明眼神坚定望向未来\",\"beat_type\":\"action\",\"narrative_cause\":\"工商暂缓处理,项目获得官方默许\",\"character_goal\":\"激励团队并宣告新方向\",\"story_action\":\"小明撕碎现代身份证,上台演讲\",\"story_result\":\"全体工人被鼓舞,小明确立新一代领导地位\",\"next_hook\":\"下一镜头展示清晨新挂牌和下一步计划\"}', '工人坐满长凳。老陈上台:“感谢小明的信息平台,咱们工厂活了。”台下鼓掌。小明站在侧台,看着灯光下兴奋的面孔。他手摸到口袋里的现代身份证——早已发黄。他默默撕碎。主持人:“请小明同志讲话!”小明上前,拿着话筒停顿三秒:“各位,未来三十年,互联网会改变一切。今天,我们从这里开始。”台下掌声雷动。', '厂庆礼堂内,舞台中央老陈握着话筒讲话,台下工人鼓掌;小明站在侧幕阴影中,手中捏着撕碎的身份证,灯光照亮舞台,红灯笼和横幅装饰,气氛热烈。', 10, '礼堂内工人坐满长凳,舞台中央中年男子在讲话,台下鼓掌。年轻男子站在侧台阴影中,手伸进口袋掏出一张发黄的身份证,默默撕碎。主持人喊话,年轻男子走上舞台中央,拿起话筒停顿三秒后说话,台下掌声雷动。镜头从礼堂大远景俯拍开始,然后缓缓下降并推近至年轻男子面部特写。', NULL, NULL, 'pending', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:33:44', 0); +INSERT INTO `short_drama_storyboard` (`id`, `project_id`, `script_id`, `scene_no`, `scene_title`, `scene_text`, `scene_type`, `shot_type`, `camera_move`, `characters_json`, `location_name`, `photography_rules`, `acting_notes`, `continuity_json`, `source_text`, `image_prompt`, `duration_seconds`, `video_prompt`, `video_url`, `video_id`, `video_status`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`) VALUES (2077008644113190912, 2077007721974484992, 2077007722058371072, 9, '新征程启程', '清晨,阳光照在工厂大门新挂牌上,金边字体“华夏信息服务中心”。小明推着自行车走出来,老陈从后面追上:“下一步?”小明跨上车:“跑遍全国,建数据库。”老陈笑着摇头:“疯了。”小明回以笑容:“1985年,疯的人才能赢。”他蹬车远去,街道两旁的工厂烟囱开始冒烟,晨光中骑车背影越来越小。', 'epic', '大远景', '缓拉升起', '[{\"name\":\"小明\",\"appearance\":\"初始形象\",\"slot\":\"工厂大门前\"},{\"name\":\"老陈\",\"appearance\":\"初始形象\",\"slot\":\"门口台阶上\"}]', '工厂大门_晨', '{\"panel_number\":9,\"scene_summary\":\"清晨,阳光照在工厂大门新挂牌上,金边字体“华夏信息服务中心”。小明推着自行车走出来,老陈从后面追上:“下一步?”小明跨上车:“跑遍全国,建数据库。”老陈笑着摇头:“疯了。”小明回以笑容:“1985年,疯的人才能赢。”他蹬车远去,街道两旁的工厂烟囱开始冒烟,晨光中骑车背影越来越小。\",\"lighting\":{\"direction\":\"根据场景主光方向保持连续\",\"quality\":\"大范围自然光,突出空间规模\"},\"characters\":[{\"name\":\"小明\",\"screen_position\":\"工厂大门前\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"},{\"name\":\"老陈\",\"screen_position\":\"门口台阶上\",\"posture\":\"保持符合当前动作的自然姿态\",\"facing\":\"面向动作目标或对话对象\"}],\"depth_of_field\":\"深景深,清晰展现环境规模\",\"color_tone\":\"宏大通透色调\"}', '[{\"name\":\"小明\",\"acting\":\"动作沉稳有力量,视线跟随环境或对手变化,保持画面张力,按前段、中段、后段完成三个连续表演节拍\"},{\"name\":\"老陈\",\"acting\":\"动作沉稳有力量,视线跟随环境或对手变化,保持画面张力,按前段、中段、后段完成三个连续表演节拍\"}]', '{\"start_state\":\"新场景建立:工厂大门_晨,重新交代人物位置、朝向与环境\",\"end_state\":\"小明骑车远去的背影在街道尽头,烟囱冒烟,晨光笼罩\",\"continuity_action\":\"小明继续蹬车,老陈目送,街道向前延伸\",\"spatial_anchor\":\"工厂大门在画面左侧,新挂牌醒目;小明骑车向右远去,街道两旁有工厂围墙和烟囱;老陈站在门口台阶上\",\"present_characters\":[\"小明\",\"老陈\"],\"scene_number\":9,\"segment_number\":1,\"segment_goal\":\"新挂牌的清晨,小明与老陈对话后骑车远去,象征新征程\",\"segment_result\":\"小明骑车远去,烟囱冒烟,新的一天开始\",\"bridge_in\":\"阳光照在新挂牌上,小明推车出现\",\"bridge_out\":\"镜头定格在远去的背影和工厂烟囱,留下开放节奏\",\"beat_type\":\"result\",\"narrative_cause\":\"全体工人被鼓舞,小明确立新一代领导地位\",\"character_goal\":\"确定下一阶段行动计划\",\"story_action\":\"小明跨上自行车说出全国数据库计划\",\"story_result\":\"小明开始实际行动,留下奋斗的背影\",\"next_hook\":\"故事结束,无下一镜头\"}', '阳光照在新挂牌上:“华夏信息服务中心”。小明推着自行车出来,老陈追上:“下一步?”小明:“跑遍全国,建数据库。”老陈笑:“疯了。”小明跨上车:“1985年,疯的人才能赢。”他蹬车远去,街道两旁的工厂烟囱开始冒烟。', '清晨阳光照射工厂大门新挂牌「华夏信息服务中心」,小明推自行车站在门口,老陈站在台阶上微笑;远处烟囱冒烟,天空泛鱼肚白,街道延伸向远方。', 10, '清晨阳光照在工厂大门新挂牌上,金色字体「华夏信息服务中心」。年轻男子推自行车从门内走出,中年男子从后面追上,两人对话。年轻男子跨上自行车,蹬车远去,街道两旁的工厂烟囱开始冒烟,晨光中骑车背影越来越小。镜头从新挂牌特写缓缓拉远并升起,展现工厂全貌和远去的背影。', NULL, NULL, 'pending', -1, -1, '2026-07-14 20:33:44', -1, '2026-07-14 20:33:44', 0); -- ---------------------------- -- Table structure for sj_distributed_lock @@ -1953,6 +2287,15 @@ INSERT INTO `sys_config` VALUES (2018858143803641859, '154726', '用户管理- INSERT INTO `sys_config` VALUES (2018858143803641860, '154726', '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '深色主题theme-dark,浅色主题theme-light'); INSERT INTO `sys_config` VALUES (2018858143803641861, '154726', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '是否开启注册用户功能(true开启,false关闭)'); INSERT INTO `sys_config` VALUES (2018858143803641862, '154726', 'OSS预览列表资源开关', 'sys.oss.previewListResource', 'true', 'Y', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', 'true:开启, false:关闭'); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027192921483309058, '000000', 'HTTP请求节点响应模板', 'node.httpRequest.template', '✅ HTTP请求节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:23:51', 1, '2026-02-27 09:31:41', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193296990957569, '000000', '文生图节点响应模板', 'node.image.template', '🎨 文生图节点:结束响应 - 图片URL: ', 'Y', 103, 1, '2026-02-27 09:25:20', 1, '2026-02-27 09:31:52', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193820393959425, '000000', '发送邮箱节点响应模板', 'node.mailsend.template', '📧 发送邮箱节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:27:25', 1, '2026-02-27 09:32:05', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027194134438277122, '000000', '结束节点响应模板', 'node.end.template', '🔚 流程已执行完毕,如果您有其他需求,请随时重新发起请求。', 'Y', 103, 1, '2026-02-27 09:28:40', 1, '2026-02-27 09:32:53', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027206492573335554, '000000', '人机交互节点响应模板', 'node.humanFeedback.template', '👤 人机交互节点:等待用户操作 - ', 'Y', 103, 1, '2026-02-27 10:17:46', 1, '2026-02-27 10:17:46', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027208880369647617, '000000', '条件分支节点响应模板', 'node.switch.template', '🔀 条件分支节点:触发 -> 跳转到节点 ', 'Y', 103, 1, '2026-02-27 10:27:15', 1, '2026-02-27 10:35:54', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027213914603995137, '000000', '大模型回答节点响应模板', 'node.llmAnswer.template', '🤖 LLM 节点 生成回答:', 'Y', 103, 1, '2026-02-27 10:47:16', 1, '2026-02-27 10:52:40', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027214387000066050, '000000', '关键词提取响应模板', 'node.keywordExtractor.template', '🔑 关键词提取节点 处理完成 : ', 'Y', 103, 1, '2026-02-27 10:49:08', 1, '2026-02-27 10:52:08', NULL); +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027217577397391361, '000000', '工作流异常响应模板', 'node.exception.template', '🛑 工作流发生异常:', 'N', 103, 1, '2026-02-27 11:01:49', 1, '2026-02-27 11:02:01', NULL); -- ---------------------------- -- Table structure for sys_dept @@ -2165,6 +2508,37 @@ CREATE TABLE `sys_logininfor` ( -- ---------------------------- -- Records of sys_logininfor -- ---------------------------- +INSERT INTO `sys_logininfor` VALUES (2067087010153709570, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-17 11:28:42'); +INSERT INTO `sys_logininfor` VALUES (2067120324839292929, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-17 13:41:05'); +INSERT INTO `sys_logininfor` VALUES (2067178210017693697, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-17 17:31:06'); +INSERT INTO `sys_logininfor` VALUES (2067439136780369921, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-18 10:47:56'); +INSERT INTO `sys_logininfor` VALUES (2067509245943230465, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-18 15:26:31'); +INSERT INTO `sys_logininfor` VALUES (2067515184486297601, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-18 15:50:07'); +INSERT INTO `sys_logininfor` VALUES (2067536325229756417, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-18 17:14:08'); +INSERT INTO `sys_logininfor` VALUES (2067599466735149057, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-18 21:25:02'); +INSERT INTO `sys_logininfor` VALUES (2068942142755569665, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-22 14:20:21'); +INSERT INTO `sys_logininfor` VALUES (2068963471252791297, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-22 15:45:06'); +INSERT INTO `sys_logininfor` VALUES (2068986934684389377, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-22 17:18:20'); +INSERT INTO `sys_logininfor` VALUES (2069012061732057090, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '1', '密码输入错误1次', '2026-06-22 18:58:11'); +INSERT INTO `sys_logininfor` VALUES (2069012086394564609, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-22 18:58:16'); +INSERT INTO `sys_logininfor` VALUES (2069034797917339649, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-06-22 20:28:31'); +INSERT INTO `sys_logininfor` VALUES (2075512593476263937, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 17:28:58'); +INSERT INTO `sys_logininfor` VALUES (2075536378350891010, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 19:03:29'); +INSERT INTO `sys_logininfor` VALUES (2075536785592643586, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 19:05:06'); +INSERT INTO `sys_logininfor` VALUES (2075539196780572674, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 19:14:41'); +INSERT INTO `sys_logininfor` VALUES (2075539270906507265, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 19:14:59'); +INSERT INTO `sys_logininfor` VALUES (2075544342642126850, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 19:35:08'); +INSERT INTO `sys_logininfor` VALUES (2075603310492020737, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 23:29:27'); +INSERT INTO `sys_logininfor` VALUES (2075608688223178753, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-10 23:50:49'); +INSERT INTO `sys_logininfor` VALUES (2076851040791674882, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 10:07:29'); +INSERT INTO `sys_logininfor` VALUES (2076867711472353281, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 11:13:43'); +INSERT INTO `sys_logininfor` VALUES (2076916180597719042, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 14:26:19'); +INSERT INTO `sys_logininfor` VALUES (2076935225945997313, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 15:42:00'); +INSERT INTO `sys_logininfor` VALUES (2076944166545375233, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 16:17:32'); +INSERT INTO `sys_logininfor` VALUES (2076955097467768833, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 17:00:58'); +INSERT INTO `sys_logininfor` VALUES (2076972033710804993, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 18:08:16'); +INSERT INTO `sys_logininfor` VALUES (2077003517935505409, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 20:13:22'); +INSERT INTO `sys_logininfor` VALUES (2077008039416188930, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-07-14 20:31:20'); -- ---------------------------- -- Table structure for sys_menu @@ -2320,6 +2694,13 @@ INSERT INTO `sys_menu` VALUES (2014, 'MCP市场删除', 2010, 4, '#', '', '', 1, INSERT INTO `sys_menu` VALUES (2015, 'MCP市场刷新', 2010, 5, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:refresh', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (2016, 'MCP工具加载', 2010, 6, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:load', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (2017, 'MCP市场导出', 2010, 7, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:export', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (3000, '智能体管理', 0, 1, 'agent', '', '', 1, 0, 'M', '0', '0', '', 'mdi:robot', 103, 1, '2026-07-07 21:37:37', NULL, NULL, '智能体管理目录'); +INSERT INTO `sys_menu` VALUES (3001, '智能体列表', 3000, 1, 'agent', 'agent/agent/index', '', 1, 0, 'C', '0', '0', 'agent:agent:list', 'mdi:robot-outline', 103, 1, '2026-07-07 21:37:37', NULL, NULL, '智能体列表菜单'); +INSERT INTO `sys_menu` VALUES (3002, '智能体查询', 3001, 1, '#', '', '', 1, 0, 'F', '0', '0', 'agent:agent:query', '#', 103, 1, '2026-07-07 21:37:37', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (3003, '智能体新增', 3001, 2, '#', '', '', 1, 0, 'F', '0', '0', 'agent:agent:add', '#', 103, 1, '2026-07-07 21:37:37', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (3004, '智能体修改', 3001, 3, '#', '', '', 1, 0, 'F', '0', '0', 'agent:agent:edit', '#', 103, 1, '2026-07-07 21:37:37', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (3005, '智能体删除', 3001, 4, '#', '', '', 1, 0, 'F', '0', '0', 'agent:agent:remove', '#', 103, 1, '2026-07-07 21:37:37', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (3006, '智能体导出', 3001, 5, '#', '', '', 1, 0, 'F', '0', '0', 'agent:agent:export', '#', 103, 1, '2026-07-07 21:37:37', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (11616, '工作流', 0, 6, 'workflow', '', '', 1, 0, 'M', '0', '0', '', 'mdi:workflow-outline', 103, 1, '2026-01-05 14:39:33', 1, '2026-01-05 14:56:07', ''); INSERT INTO `sys_menu` VALUES (11618, '我的任务', 0, 7, 'task', '', '', 1, 0, 'M', '0', '0', '', 'carbon:task-approved', 103, 1, '2026-01-05 14:39:33', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (11619, '我的待办', 11618, 2, 'taskWaiting', 'workflow/task/taskWaiting', '', 1, 1, 'C', '0', '0', '', 'ri:todo-line', 103, 1, '2026-01-05 14:39:33', NULL, NULL, ''); @@ -2429,6 +2810,15 @@ CREATE TABLE `sys_oper_log` ( -- ---------------------------- -- Records of sys_oper_log -- ---------------------------- +INSERT INTO `sys_oper_log` VALUES (2056756437140963329, '000000', 'OSS对象存储', 1, 'org.ruoyi.system.controller.system.SysOssController.upload()', 'POST', 1, 'admin', '研发部门', '/resource/oss/upload', '127.0.0.1', '内网IP', '', '', 1, '上传文件失败,请检查配置信息:[software.amazon.awssdk.services.s3.model.S3Exception: The access key Id format you provided is invalid. (Service: S3, Status Code: 403, Request ID: NmEwYzdmNTFfOGEzMTI3MGJfOGE2Y19lNDUyZDk=)]', '2026-05-19 23:18:42', 2621); +INSERT INTO `sys_oper_log` VALUES (2076944303757836289, '000000', '智能体管理', 2, 'org.ruoyi.controller.agent.AgentController.edit()', 'PUT', 1, 'admin', '研发部门', '/agent/agent', '127.0.0.1', '内网IP', '{\"id\":\"1\",\"agentName\":\"对话智能体\",\"agentDescribe\":\"对话智能体\",\"modelId\":\"2000585866022060033\",\"enableThinking\":\"0\",\"systemPrompt\":\"你是一个乐于助人的通用 AI 助手,请用简洁、准确的中文回答用户的问题。\",\"mcpToolIds\":[],\"skillNames\":[\"docx\"],\"knowledgeIds\":[],\"status\":\"0\",\"remark\":\"系统初始化的默认智能体,自动绑定首个启用的对话模型\"}', '{\"code\":200,\"msg\":\"操作成功\"}', 0, '', '2026-07-14 16:18:04', 23); +INSERT INTO `sys_oper_log` VALUES (2076944338465701889, '000000', '会话管理', 1, 'org.ruoyi.controller.chat.ChatSessionController.add()', 'POST', 1, 'admin', '研发部门', '/system/session', '127.0.0.1', '内网IP', '{\"id\":\"2076944338398593026\",\"userId\":\"1\",\"sessionTitle\":\"你好\",\"sessionContent\":\"你好\",\"remark\":\"你好\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":\"2076944338398593026\"}', 0, '', '2026-07-14 16:18:13', 19); +INSERT INTO `sys_oper_log` VALUES (2076957117130002434, '000000', '会话管理', 1, 'org.ruoyi.controller.chat.ChatSessionController.add()', 'POST', 1, 'admin', '研发部门', '/system/session', '127.0.0.1', '内网IP', '{\"id\":\"2076957117012561921\",\"userId\":\"1\",\"sessionTitle\":\"测试工作流\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":\"2076957117012561921\"}', 0, '', '2026-07-14 17:08:59', 24); +INSERT INTO `sys_oper_log` VALUES (2076958796898422785, '000000', '会话管理', 1, 'org.ruoyi.controller.chat.ChatSessionController.add()', 'POST', 1, 'admin', '研发部门', '/system/session', '127.0.0.1', '内网IP', '{\"id\":\"2076958796772593665\",\"userId\":\"1\",\"sessionTitle\":\"测试工作流\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":\"2076958796772593665\"}', 0, '', '2026-07-14 17:15:40', 18); +INSERT INTO `sys_oper_log` VALUES (2076958822575951874, '000000', '会话管理', 1, 'org.ruoyi.controller.chat.ChatSessionController.add()', 'POST', 1, 'admin', '研发部门', '/system/session', '127.0.0.1', '内网IP', '{\"id\":\"2076958822525620225\",\"userId\":\"1\",\"sessionTitle\":\"测试工作流\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":\"2076958822525620225\"}', 0, '', '2026-07-14 17:15:46', 15); +INSERT INTO `sys_oper_log` VALUES (2076964726599897090, '000000', '会话管理', 1, 'org.ruoyi.controller.chat.ChatSessionController.add()', 'POST', 1, 'admin', '研发部门', '/system/session', '127.0.0.1', '内网IP', '{\"id\":\"2076964726549565441\",\"userId\":\"1\",\"sessionTitle\":\"测试工作流\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":\"2076964726549565441\"}', 0, '', '2026-07-14 17:39:14', 9); +INSERT INTO `sys_oper_log` VALUES (2076964779045474305, '000000', '会话管理', 1, 'org.ruoyi.controller.chat.ChatSessionController.add()', 'POST', 1, 'admin', '研发部门', '/system/session', '127.0.0.1', '内网IP', '{\"id\":\"2076964778940616705\",\"userId\":\"1\",\"sessionTitle\":\"测试工作流\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":\"2076964778940616705\"}', 0, '', '2026-07-14 17:39:26', 23); +INSERT INTO `sys_oper_log` VALUES (2076971705414242305, '000000', '会话管理', 1, 'org.ruoyi.controller.chat.ChatSessionController.add()', 'POST', 1, 'admin', '研发部门', '/system/session', '127.0.0.1', '内网IP', '{\"id\":\"2076971705284218881\",\"userId\":\"1\",\"sessionTitle\":\"你好2阿\",\"sessionContent\":\"你好2阿\",\"remark\":\"你好2阿\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":\"2076971705284218881\"}', 0, '', '2026-07-14 18:06:57', 24); -- ---------------------------- -- Table structure for sys_oss @@ -2454,18 +2844,18 @@ CREATE TABLE `sys_oss` ( -- ---------------------------- -- Records of sys_oss -- ---------------------------- -INSERT INTO `sys_oss` VALUES (2026580908423340033, '000000', '2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', 'logo.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', '{\"fileSize\":\"183613\",\"contentType\":\"image/png\"}', 103, '2026-02-25 16:51:55', 1, '2026-02-25 16:51:55', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2026640059920883713, '000000', '2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'openai.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', '{\"fileSize\":\"11297\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:46:58', 1, '2026-02-25 20:46:58', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2026640515967557633, '000000', '2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', '{\"fileSize\":\"8746\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:47', 1, '2026-02-25 20:48:47', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2026640548213366785, '000000', '2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:55', 1, '2026-02-25 20:48:55', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2026640572443860993, '000000', '2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:00', 1, '2026-02-25 20:49:00', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2026640621945036802, '000000', '2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', 'bailian-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '{\"fileSize\":\"5901\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:12', 1, '2026-02-25 20:49:12', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2033120065673043969, '000000', '2026/03/15/68c4d853814e444982c2517ffabac0f3.jpg', '9b75e600b2df6160261a507055eabfdf.jpg', '.jpg', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/68c4d853814e444982c2517ffabac0f3.jpg', '{\"fileSize\":\"28027\",\"contentType\":\"image/jpeg\"}', 103, '2026-03-15 17:56:12', 1, '2026-03-15 17:56:12', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2033169884118593537, '000000', '2026/03/15/4b7e93a72bf04805ae59985cc0845ef1.png', 'logo.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/4b7e93a72bf04805ae59985cc0845ef1.png', '{\"fileSize\":\"61537\",\"contentType\":\"image/png\"}', 103, '2026-03-15 21:14:10', 1, '2026-03-15 21:14:10', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2033198191581147137, '000000', '2026/03/15/66d9e6d216c74652bb466a13d24f4440.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/66d9e6d216c74652bb466a13d24f4440.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:06:39', 1, '2026-03-15 23:06:39', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2033198447232364546, '000000', '2026/03/15/ae1b1e0d363e4bc1b3321d696453fdbf.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/ae1b1e0d363e4bc1b3321d696453fdbf.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:07:39', 1, '2026-03-15 23:07:39', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2033198841211727874, '000000', '2026/03/15/83564059b4f643b69a1e0ea727d17364.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/83564059b4f643b69a1e0ea727d17364.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:09:13', 1, '2026-03-15 23:09:13', 1, 'qcloud'); -INSERT INTO `sys_oss` VALUES (2033199209064771586, '000000', '2026/03/15/695360eb380d43d6af34e8a308c09696.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/695360eb380d43d6af34e8a308c09696.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:10:41', 1, '2026-03-15 23:10:41', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026580908423340033, '000000', '2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', 'logo.png', '.png', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', '{\"fileSize\":\"183613\",\"contentType\":\"image/png\"}', 103, '2026-02-25 16:51:55', 1, '2026-02-25 16:51:55', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640059920883713, '000000', '2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'openai.png', '.png', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', '{\"fileSize\":\"11297\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:46:58', 1, '2026-02-25 20:46:58', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640515967557633, '000000', '2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama.png', '.png', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', '{\"fileSize\":\"8746\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:47', 1, '2026-02-25 20:48:47', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640548213366785, '000000', '2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', 'ppio-color.png', '.png', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:55', 1, '2026-02-25 20:48:55', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640572443860993, '000000', '2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'ppio-color.png', '.png', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:00', 1, '2026-02-25 20:49:00', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2026640621945036802, '000000', '2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', 'bailian-color.png', '.png', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '{\"fileSize\":\"5901\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:12', 1, '2026-02-25 20:49:12', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2033120065673043969, '000000', '2026/03/15/68c4d853814e444982c2517ffabac0f3.jpg', '9b75e600b2df6160261a507055eabfdf.jpg', '.jpg', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/03/15/68c4d853814e444982c2517ffabac0f3.jpg', '{\"fileSize\":\"28027\",\"contentType\":\"image/jpeg\"}', 103, '2026-03-15 17:56:12', 1, '2026-03-15 17:56:12', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2033169884118593537, '000000', '2026/03/15/4b7e93a72bf04805ae59985cc0845ef1.png', 'logo.png', '.png', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/03/15/4b7e93a72bf04805ae59985cc0845ef1.png', '{\"fileSize\":\"61537\",\"contentType\":\"image/png\"}', 103, '2026-03-15 21:14:10', 1, '2026-03-15 21:14:10', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2033198191581147137, '000000', '2026/03/15/66d9e6d216c74652bb466a13d24f4440.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/03/15/66d9e6d216c74652bb466a13d24f4440.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:06:39', 1, '2026-03-15 23:06:39', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2033198447232364546, '000000', '2026/03/15/ae1b1e0d363e4bc1b3321d696453fdbf.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/03/15/ae1b1e0d363e4bc1b3321d696453fdbf.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:07:39', 1, '2026-03-15 23:07:39', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2033198841211727874, '000000', '2026/03/15/83564059b4f643b69a1e0ea727d17364.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/03/15/83564059b4f643b69a1e0ea727d17364.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:09:13', 1, '2026-03-15 23:09:13', 1, 'qcloud'); +INSERT INTO `sys_oss` VALUES (2033199209064771586, '000000', '2026/03/15/695360eb380d43d6af34e8a308c09696.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://example-1234567890.cos.ap-guangzhou.myqcloud.com/2026/03/15/695360eb380d43d6af34e8a308c09696.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:10:41', 1, '2026-03-15 23:10:41', 1, 'qcloud'); -- ---------------------------- -- Table structure for sys_oss_config @@ -2501,7 +2891,7 @@ CREATE TABLE `sys_oss_config` ( INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-25 15:44:13', NULL); INSERT INTO `sys_oss_config` VALUES (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL); INSERT INTO `sys_oss_config` VALUES (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL); -INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'xx', 'xx', 'ruoyiai-1254149996', '', 'cos.ap-guangzhou.myqcloud.com', '', 'Y', 'ap-guangzhou', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-03-15 17:56:06', ''); +INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'xx', 'xx', 'example-1234567890', '', 'cos.ap-guangzhou.myqcloud.com', '', 'Y', 'ap-guangzhou', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-03-15 17:56:06', ''); INSERT INTO `sys_oss_config` VALUES (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:53', 1, '2026-02-03 05:14:53', NULL); -- ---------------------------- @@ -2948,7 +3338,7 @@ CREATE TABLE `sys_user` ( -- ---------------------------- -- Records of sys_user -- ---------------------------- -INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-05-19 23:16:57', 103, 1, '2026-02-05 09:22:12', -1, '2026-05-19 23:16:57', '管理员', NULL, 0.00); +INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-07-14 20:31:20', 103, 1, '2026-02-05 09:22:12', -1, '2026-07-14 20:31:20', '管理员', NULL, 0.00); INSERT INTO `sys_user` VALUES (3, '000000', 108, 'test', '本部门及以下 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 3, '2026-02-05 09:22:12', NULL, NULL, 0.00); INSERT INTO `sys_user` VALUES (4, '000000', 102, 'test1', '仅本人 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 4, '2026-02-05 09:22:12', NULL, NULL, 0.00); diff --git a/docs/script/sql/snail_job_mysql.sql b/docs/script/sql/snail_job_mysql.sql new file mode 100644 index 00000000..322cccae --- /dev/null +++ b/docs/script/sql/snail_job_mysql.sql @@ -0,0 +1,545 @@ +SET NAMES utf8mb4; + +DROP DATABASE IF EXISTS snail_job; +CREATE DATABASE snail_job; +USE snail_job; + +CREATE TABLE `sj_namespace` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) NOT NULL COMMENT '名称', + `unique_id` varchar(64) NOT NULL COMMENT '唯一id', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`), + UNIQUE KEY `uk_unique_id` (`unique_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='命名空间'; + +INSERT INTO `sj_namespace` (`id`, `name`, `unique_id`, `create_dt`, `update_dt`, `deleted`) +VALUES (1, 'Default', '764d604ec6fc45f68cd92514c40e9e1a', now(), now(), 0); + + +CREATE TABLE `sj_group_config` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL DEFAULT '' COMMENT '组名称', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '组描述', + `token` varchar(64) NOT NULL DEFAULT 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT' COMMENT 'token', + `group_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组状态 0、未启用 1、启用', + `version` int(11) NOT NULL COMMENT '版本号', + `group_partition` int(11) NOT NULL COMMENT '分区', + `id_generator_mode` tinyint(4) NOT NULL DEFAULT 1 COMMENT '唯一id生成模式 默认号段模式', + `init_scene` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否初始化场景 0:否 1:是', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='组配置' +; + +CREATE TABLE `sj_notify_config` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `notify_name` varchar(64) NOT NULL DEFAULT '' COMMENT '通知名称', + `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '任务类型 1. 重试任务 2. 重试回调 3、JOB任务 4、WORKFLOW任务', + `notify_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '通知状态 0、未启用 1、启用', + `recipient_ids` varchar(128) NOT NULL COMMENT '接收人id列表', + `notify_threshold` int(11) NOT NULL DEFAULT 0 COMMENT '通知阈值', + `notify_scene` tinyint(4) NOT NULL DEFAULT 0 COMMENT '通知场景', + `rate_limiter_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '限流状态 0、未启用 1、启用', + `rate_limiter_threshold` int(11) NOT NULL DEFAULT 0 COMMENT '每秒限流阈值', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='通知配置' +; + +CREATE TABLE `sj_notify_recipient` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `recipient_name` varchar(64) NOT NULL COMMENT '接收人名称', + `notify_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '通知类型 1、钉钉 2、邮件 3、企业微信 4 飞书 5 webhook', + `notify_attribute` varchar(512) NOT NULL COMMENT '配置属性', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id` (`namespace_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='告警通知接收人' +; + +CREATE TABLE `sj_retry_dead_letter` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `group_id` bigint(20) NOT NULL COMMENT '组Id', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `scene_id` bigint(20) NOT NULL COMMENT '场景ID', + `idempotent_id` varchar(64) NOT NULL COMMENT '幂等id', + `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号', + `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', + -- jackson 兼容历史数据 预计1.8.0默认改为fury + `serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '执行方法参数序列化器名称', + `args_str` text NOT NULL COMMENT '执行方法参数', + `ext_attrs` text NOT NULL COMMENT '扩展字段', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`), + KEY `idx_idempotent_id` (`idempotent_id`), + KEY `idx_biz_no` (`biz_no`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='死信队列表' +; + +CREATE TABLE `sj_retry` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `group_id` bigint(20) NOT NULL COMMENT '组Id', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `scene_id` bigint(20) NOT NULL COMMENT '场景ID', + `idempotent_id` varchar(64) NOT NULL COMMENT '幂等id', + `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号', + `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', + `args_str` text NOT NULL COMMENT '执行方法参数', + `ext_attrs` text NOT NULL COMMENT '扩展字段', + -- jackson 兼容历史数据 预计1.8.0默认改为fury + `serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '执行方法参数序列化器名称', + `next_trigger_at` bigint(13) NOT NULL COMMENT '下次触发时间', + `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '重试次数', + `retry_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '重试状态 0、重试中 1、成功 2、最大重试次数', + `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、重试数据 2、回调数据', + `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', + `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '父节点id', + `deleted` bigint(20) NOT NULL DEFAULT 0 COMMENT '逻辑删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_biz_no` (`biz_no`), + KEY `idx_idempotent_id` (`idempotent_id`), + KEY `idx_retry_status_bucket_index` (`retry_status`, `bucket_index`), + KEY `idx_parent_id` (`parent_id`), + KEY `idx_create_dt` (`create_dt`), + UNIQUE KEY `uk_scene_tasktype_idempotentid_deleted` (`scene_id`, `task_type`, `idempotent_id`, `deleted`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='重试信息表' +; + +CREATE TABLE `sj_retry_task` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `retry_id` bigint(20) NOT NULL COMMENT '重试信息Id', + `ext_attrs` text NOT NULL COMMENT '扩展字段', + `task_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '重试状态', + `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、重试数据 2、回调数据', + `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '操作原因', + `client_info` varchar(128) DEFAULT NULL COMMENT '客户端地址 clientId#ip:port', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`), + KEY `task_status` (`task_status`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_retry_id` (`retry_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='重试任务表' +; + +CREATE TABLE `sj_retry_task_log_message` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `retry_id` bigint(20) NOT NULL COMMENT '重试信息Id', + `retry_task_id` bigint(20) NOT NULL COMMENT '重试任务Id', + `message` longtext NOT NULL COMMENT '异常信息', + `log_num` int(11) NOT NULL DEFAULT 1 COMMENT '日志数量', + `real_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '上报时间', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_retry_task_id` (`namespace_id`, `group_name`, `retry_task_id`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务调度日志信息记录表' +; + +CREATE TABLE `sj_retry_scene_config` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `scene_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组状态 0、未启用 1、启用', + `max_retry_count` int(11) NOT NULL DEFAULT 5 COMMENT '最大重试次数', + `back_off` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1、默认等级 2、固定间隔时间 3、CRON 表达式', + `trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '间隔时长', + `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表', + `deadline_request` bigint(20) unsigned NOT NULL DEFAULT 60000 COMMENT 'Deadline Request 调用链超时 单位毫秒', + `executor_timeout` int(11) unsigned NOT NULL DEFAULT 5 COMMENT '任务执行超时时间,单位秒', + `route_key` tinyint(4) NOT NULL DEFAULT 4 COMMENT '路由策略', + `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '阻塞策略 1、丢弃 2、覆盖 3、并行', + `cb_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '回调状态 0、不开启 1、开启', + `cb_trigger_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1、默认等级 2、固定间隔时间 3、CRON 表达式', + `cb_max_count` int(11) NOT NULL DEFAULT 16 COMMENT '回调的最大执行次数', + `cb_trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '回调的最大执行次数', + `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id', + `labels` varchar(512) NULL DEFAULT '' COMMENT '标签', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='场景配置' +; + +CREATE TABLE `sj_server_node` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `host_id` varchar(64) NOT NULL COMMENT '主机id', + `host_ip` varchar(64) NOT NULL COMMENT '机器ip', + `host_port` int(16) NOT NULL COMMENT '机器端口', + `expire_at` datetime NOT NULL COMMENT '过期时间', + `node_type` tinyint(4) NOT NULL COMMENT '节点类型 1、客户端 2、是服务端', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `labels` varchar(512) NULL DEFAULT '' COMMENT '标签', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_expire_at_node_type` (`expire_at`, `node_type`), + UNIQUE KEY `uk_host_id_host_ip` (`host_id`, `host_ip`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='服务器节点' +; + +CREATE TABLE `sj_distributed_lock` +( + `name` varchar(64) NOT NULL COMMENT '锁名称', + `lock_until` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '锁定时长', + `locked_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '锁定时间', + `locked_by` varchar(255) NOT NULL COMMENT '锁定者', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='锁定表' +; + +CREATE TABLE `sj_system_user` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `username` varchar(64) NOT NULL COMMENT '账号', + `password` varchar(128) NOT NULL COMMENT '密码', + `role` tinyint(4) NOT NULL DEFAULT 0 COMMENT '角色:1-普通用户、2-管理员', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`) USING BTREE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='系统用户表'; + +-- pwd: admin +INSERT INTO `sj_system_user` (username, password, role) +VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2); + +CREATE TABLE `sj_system_user_permission` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `system_user_id` bigint(20) NOT NULL COMMENT '系统用户id', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_namespace_id_group_name_system_user_id` (`namespace_id`, `group_name`, `system_user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='系统用户权限表'; + +-- 分布式调度DDL +CREATE TABLE `sj_job` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_name` varchar(64) NOT NULL COMMENT '名称', + `args_str` text DEFAULT NULL COMMENT '执行方法参数', + `args_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '参数类型 ', + `next_trigger_at` bigint(13) NOT NULL COMMENT '下次触发时间', + `job_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务状态 0、关闭、1、开启', + `task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、集群 2、广播 3、切片', + `route_key` tinyint(4) NOT NULL DEFAULT 4 COMMENT '路由策略', + `executor_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '执行器类型', + `executor_info` varchar(255) DEFAULT NULL COMMENT '执行器名称', + `trigger_type` tinyint(4) NOT NULL COMMENT '触发类型 1.CRON 表达式 2. 固定时间', + `trigger_interval` varchar(255) NOT NULL COMMENT '间隔时长', + `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '阻塞策略 1、丢弃 2、覆盖 3、并行 4、恢复', + `executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '任务执行超时时间,单位秒', + `max_retry_times` int(11) NOT NULL DEFAULT 0 COMMENT '最大重试次数', + `parallel_num` int(11) NOT NULL DEFAULT 1 COMMENT '并行数', + `retry_interval` int(11) NOT NULL DEFAULT 0 COMMENT '重试间隔(s)', + `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', + `resident` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否是常驻任务', + `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表', + `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id', + `labels` varchar(512) NULL DEFAULT '' COMMENT '标签', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_job_status_bucket_index` (`job_status`, `bucket_index`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务信息'; + +CREATE TABLE `sj_job_log_message` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务信息id', + `task_batch_id` bigint(20) NOT NULL COMMENT '任务批次id', + `task_id` bigint(20) NOT NULL COMMENT '调度任务id', + `message` longtext NOT NULL COMMENT '调度信息', + `log_num` int(11) NOT NULL DEFAULT 1 COMMENT '日志数量', + `real_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '上报时间', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_task_batch_id_task_id` (`task_batch_id`, `task_id`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='调度日志'; + +CREATE TABLE `sj_job_task` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务信息id', + `task_batch_id` bigint(20) NOT NULL COMMENT '调度任务id', + `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '父执行器id', + `task_status` tinyint NOT NULL DEFAULT 0 COMMENT '执行的状态 0、失败 1、成功', + `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '重试次数', + `mr_stage` tinyint DEFAULT NULL COMMENT '动态分片所处阶段 1:map 2:reduce 3:mergeReduce', + `leaf` tinyint NOT NULL DEFAULT '1' COMMENT '叶子节点', + `task_name` varchar(255) NOT NULL DEFAULT '' COMMENT '任务名称', + `client_info` varchar(128) DEFAULT NULL COMMENT '客户端地址 clientId#ip:port', + `wf_context` text DEFAULT NULL COMMENT '工作流全局上下文', + `result_message` text NOT NULL COMMENT '执行结果', + `args_str` text DEFAULT NULL COMMENT '执行方法参数', + `args_type` tinyint NOT NULL DEFAULT 1 COMMENT '参数类型 ', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_task_batch_id_task_status` (`task_batch_id`, `task_status`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务实例'; + +CREATE TABLE `sj_job_task_batch` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务id', + `workflow_node_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '工作流节点id', + `parent_workflow_node_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '工作流任务父批次id', + `workflow_task_batch_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '工作流任务批次id', + `task_batch_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '任务批次状态 0、失败 1、成功', + `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '操作原因', + `execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '任务执行时间', + `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '任务类型 3、JOB任务 4、WORKFLOW任务', + `parent_id` varchar(64) NOT NULL DEFAULT '' COMMENT '父节点', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_job_id_task_batch_status` (`job_id`, `task_batch_status`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_workflow_task_batch_id_workflow_node_id` (`workflow_task_batch_id`, `workflow_node_id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务批次'; + +CREATE TABLE `sj_job_summary` +( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` VARCHAR(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '组名称', + `business_id` bigint NOT NULL COMMENT '业务id (job_id或workflow_id)', + `system_task_type` tinyint(4) NOT NULL DEFAULT 3 COMMENT '任务类型 3、JOB任务 4、WORKFLOW任务', + `trigger_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计时间', + `success_num` int NOT NULL DEFAULT 0 COMMENT '执行成功-日志数量', + `fail_num` int NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量', + `fail_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '失败原因', + `stop_num` int NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量', + `stop_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '失败原因', + `cancel_num` int NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量', + `cancel_reason` varchar(512) NOT NULL DEFAULT '' COMMENT '失败原因', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name_business_id` (`namespace_id`, `group_name`, business_id), + UNIQUE KEY `uk_trigger_at_system_task_type_business_id` (`trigger_at`, `system_task_type`, `business_id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 COMMENT ='DashBoard_Job'; + +CREATE TABLE `sj_retry_summary` +( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` VARCHAR(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '组名称', + `scene_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '场景名称', + `trigger_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计时间', + `running_num` int NOT NULL DEFAULT 0 COMMENT '重试中-日志数量', + `finish_num` int NOT NULL DEFAULT 0 COMMENT '重试完成-日志数量', + `max_count_num` int NOT NULL DEFAULT 0 COMMENT '重试到达最大次数-日志数量', + `suspend_num` int NOT NULL DEFAULT 0 COMMENT '暂停重试-日志数量', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_trigger_at` (`trigger_at`), + UNIQUE KEY `uk_scene_name_trigger_at` (`namespace_id`, `group_name`, `scene_name`, `trigger_at`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 COMMENT ='DashBoard_Retry'; + +CREATE TABLE `sj_workflow` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `workflow_name` varchar(64) NOT NULL COMMENT '工作流名称', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `workflow_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '工作流状态 0、关闭、1、开启', + `trigger_type` tinyint(4) NOT NULL COMMENT '触发类型 1.CRON 表达式 2. 固定时间', + `trigger_interval` varchar(255) NOT NULL COMMENT '间隔时长', + `next_trigger_at` bigint NOT NULL COMMENT '下次触发时间', + `block_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '阻塞策略 1、丢弃 2、覆盖 3、并行', + `executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '任务执行超时时间,单位秒', + `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', + `flow_info` text DEFAULT NULL COMMENT '流程信息', + `wf_context` text DEFAULT NULL COMMENT '上下文', + `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表', + `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', + `version` int(11) NOT NULL COMMENT '版本号', + `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='工作流'; + +CREATE TABLE `sj_workflow_node` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `node_name` varchar(64) NOT NULL COMMENT '节点名称', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `job_id` bigint(20) NOT NULL COMMENT '任务信息id', + `workflow_id` bigint(20) NOT NULL COMMENT '工作流ID', + `node_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1、任务节点 2、条件节点', + `expression_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '1、SpEl、2、Aviator 3、QL', + `fail_strategy` tinyint(4) NOT NULL DEFAULT 1 COMMENT '失败策略 1、跳过 2、阻塞', + `workflow_node_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '工作流节点状态 0、关闭、1、开启', + `priority_level` int(11) NOT NULL DEFAULT 1 COMMENT '优先级', + `node_info` text DEFAULT NULL COMMENT '节点信息 ', + `version` int(11) NOT NULL COMMENT '版本号', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='工作流节点'; + +CREATE TABLE `sj_workflow_task_batch` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `workflow_id` bigint(20) NOT NULL COMMENT '工作流任务id', + `task_batch_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '任务批次状态 0、失败 1、成功', + `operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '操作原因', + `flow_info` text DEFAULT NULL COMMENT '流程信息', + `wf_context` text DEFAULT NULL COMMENT '全局上下文', + `execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '任务执行时间', + `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本号', + `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_job_id_task_batch_status` (`workflow_id`, `task_batch_status`), + KEY `idx_create_dt` (`create_dt`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='工作流批次'; + +CREATE TABLE `sj_job_executor` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `executor_info` varchar(256) NOT NULL COMMENT '任务执行器名称', + `executor_type` varchar(3) NOT NULL COMMENT '1:java 2:python 3:go', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务执行器信息'; diff --git a/docs/script/sql/update/2026-07-20-knowledge-fragment-fid.sql b/docs/script/sql/update/2026-07-20-knowledge-fragment-fid.sql new file mode 100644 index 00000000..0bb5c264 --- /dev/null +++ b/docs/script/sql/update/2026-07-20-knowledge-fragment-fid.sql @@ -0,0 +1,58 @@ +-- RAG metadata migration (MySQL 8). Safe to execute repeatedly. +-- 注意:MySQL 8 不支持 ALTER TABLE ... ADD COLUMN IF NOT EXISTS(仅 MariaDB 支持), +-- 因此列的增量添加统一用 information_schema 守卫 + PREPARE 实现幂等。 +SET @add_file_hash_col = IF(EXISTS( + SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() + AND table_name = 'knowledge_attach' AND column_name = 'file_hash'), + 'SELECT 1', + 'ALTER TABLE `knowledge_attach` ADD COLUMN `file_hash` varchar(64) NULL DEFAULT NULL COMMENT ''文件SHA-256摘要'' AFTER `doc_id`'); +PREPARE stmt FROM @add_file_hash_col; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +ALTER TABLE `knowledge_attach` + MODIFY COLUMN `doc_id` varchar(32) NULL DEFAULT NULL COMMENT '文档ID'; + +SET @add_file_hash = IF(EXISTS( + SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() + AND table_name = 'knowledge_attach' AND index_name = 'uk_knowledge_file_hash'), + 'SELECT 1', 'ALTER TABLE `knowledge_attach` ADD UNIQUE INDEX `uk_knowledge_file_hash` (`knowledge_id`, `file_hash`)'); +PREPARE stmt FROM @add_file_hash; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +SET @add_fid_col = IF(EXISTS( + SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() + AND table_name = 'knowledge_fragment' AND column_name = 'fid'), + 'SELECT 1', + 'ALTER TABLE `knowledge_fragment` ADD COLUMN `fid` varchar(32) NULL DEFAULT NULL COMMENT ''向量库片段ID'' AFTER `id`'); +PREPARE stmt FROM @add_fid_col; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +ALTER TABLE `knowledge_fragment` + MODIFY COLUMN `doc_id` varchar(32) NULL DEFAULT NULL COMMENT '文档ID'; + +UPDATE `knowledge_fragment` +SET `fid` = LOWER(MD5(CONCAT('knowledge_fragment:', `id`))) +WHERE `fid` IS NULL OR `fid` = ''; + +SET @drop_idx_fid = IF(EXISTS( + SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() + AND table_name = 'knowledge_fragment' AND index_name = 'idx_fid'), + 'ALTER TABLE `knowledge_fragment` DROP INDEX `idx_fid`', 'SELECT 1'); +PREPARE stmt FROM @drop_idx_fid; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +SET @add_uk_fid = IF(EXISTS( + SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() + AND table_name = 'knowledge_fragment' AND index_name = 'uk_fid'), + 'SELECT 1', 'ALTER TABLE `knowledge_fragment` ADD UNIQUE INDEX `uk_fid` (`fid`)'); +PREPARE stmt FROM @add_uk_fid; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +ALTER TABLE `knowledge_fragment` MODIFY COLUMN `fid` varchar(32) NOT NULL COMMENT '向量库片段ID'; + +SET @add_tenant_user = IF(EXISTS( + SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() + AND table_name = 'knowledge_info' AND index_name = 'idx_tenant_user'), + 'SELECT 1', 'ALTER TABLE `knowledge_info` ADD INDEX `idx_tenant_user` (`tenant_id`, `user_id`)'); +PREPARE stmt FROM @add_tenant_user; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +SET @add_tenant_share = IF(EXISTS( + SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() + AND table_name = 'knowledge_info' AND index_name = 'idx_tenant_share'), + 'SELECT 1', 'ALTER TABLE `knowledge_info` ADD INDEX `idx_tenant_share` (`tenant_id`, `share`)'); +PREPARE stmt FROM @add_tenant_share; EXECUTE stmt; DEALLOCATE PREPARE stmt; diff --git a/docs/script/sql/update/2026-07-21-chat-provider-icon-length.sql b/docs/script/sql/update/2026-07-21-chat-provider-icon-length.sql new file mode 100644 index 00000000..88f6fcb9 --- /dev/null +++ b/docs/script/sql/update/2026-07-21-chat-provider-icon-length.sql @@ -0,0 +1,7 @@ +-- 加宽 chat_provider.provider_icon 字段 (对应 issue IHPUDA) +-- 背景:文件系统使用 minio 私有桶时,厂商图标存的是带签名的临时访问 URL, +-- 长度常超过 255,导致「Data too long for column 'provider_icon'」。 +-- MODIFY COLUMN 可重复执行。 + +ALTER TABLE `chat_provider` + MODIFY COLUMN `provider_icon` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商图标'; diff --git a/docs/script/sql/update/2026-07-21-sys-config-node-template.sql b/docs/script/sql/update/2026-07-21-sys-config-node-template.sql new file mode 100644 index 00000000..ed1c5721 --- /dev/null +++ b/docs/script/sql/update/2026-07-21-sys-config-node-template.sql @@ -0,0 +1,41 @@ +-- 补充工作流节点消息模板配置 (对应 issue IJX5VV) +-- 背景:NodeMessageTemplateEnum 依赖以下 9 个 sys_config 键,缺失时 +-- WorkflowMessageUtil.getNodeMessageTemplate 会抛出「请先配置该节点的响应模板」。 +-- 这批配置在历史提交 20d531c0 中存在,SQL 脚本合并重命名时遗失,此处恢复。 +-- 幂等:按 config_key + tenant_id 判重,可重复执行。 + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027192921483309058, '000000', 'HTTP请求节点响应模板', 'node.httpRequest.template', '✅ HTTP请求节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:23:51', 1, '2026-02-27 09:31:41', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.httpRequest.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027193296990957569, '000000', '文生图节点响应模板', 'node.image.template', '🎨 文生图节点:结束响应 - 图片URL: ', 'Y', 103, 1, '2026-02-27 09:25:20', 1, '2026-02-27 09:31:52', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.image.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027193820393959425, '000000', '发送邮箱节点响应模板', 'node.mailsend.template', '📧 发送邮箱节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:27:25', 1, '2026-02-27 09:32:05', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.mailsend.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027194134438277122, '000000', '结束节点响应模板', 'node.end.template', '🔚 流程已执行完毕,如果您有其他需求,请随时重新发起请求。', 'Y', 103, 1, '2026-02-27 09:28:40', 1, '2026-02-27 09:32:53', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.end.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027206492573335554, '000000', '人机交互节点响应模板', 'node.humanFeedback.template', '👤 人机交互节点:等待用户操作 - ', 'Y', 103, 1, '2026-02-27 10:17:46', 1, '2026-02-27 10:17:46', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.humanFeedback.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027208880369647617, '000000', '条件分支节点响应模板', 'node.switch.template', '🔀 条件分支节点:触发 -> 跳转到节点 ', 'Y', 103, 1, '2026-02-27 10:27:15', 1, '2026-02-27 10:35:54', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.switch.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027213914603995137, '000000', '大模型回答节点响应模板', 'node.llmAnswer.template', '🤖 LLM 节点 生成回答:', 'Y', 103, 1, '2026-02-27 10:47:16', 1, '2026-02-27 10:52:40', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.llmAnswer.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027214387000066050, '000000', '关键词提取响应模板', 'node.keywordExtractor.template', '🔑 关键词提取节点 处理完成 : ', 'Y', 103, 1, '2026-02-27 10:49:08', 1, '2026-02-27 10:52:08', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.keywordExtractor.template' AND `tenant_id` = '000000'); + +INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +SELECT 2027217577397391361, '000000', '工作流异常响应模板', 'node.exception.template', '🛑 工作流发生异常:', 'N', 103, 1, '2026-02-27 11:01:49', 1, '2026-02-27 11:02:01', NULL +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `config_key` = 'node.exception.template' AND `tenant_id` = '000000'); diff --git a/docs/script/sql/update/updat-0419.sql b/docs/script/sql/update/updat-0419.sql deleted file mode 100644 index f744f8a5..00000000 --- a/docs/script/sql/update/updat-0419.sql +++ /dev/null @@ -1,92 +0,0 @@ -/* - Navicat Premium Dump SQL - - Source Server : localhost-mysql - Source Server Type : MySQL - Source Server Version : 80045 (8.0.45) - Source Host : localhost:3306 - Source Schema : ruoyi-ai - - Target Server Type : MySQL - Target Server Version : 80045 (8.0.45) - File Encoding : 65001 - - Date: 19/04/2026 13:36:41 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for chat_model --- ---------------------------- -DROP TABLE IF EXISTS `chat_model`; -CREATE TABLE `chat_model` ( - `id` bigint NOT NULL COMMENT '主键', - `category` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型分类', - `model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型名称', - `provider_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型供应商', - `model_describe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型描述', - `model_dimension` int NULL DEFAULT NULL COMMENT '模型维度', - `model_show` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '是否显示', - `api_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求地址', - `api_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密钥', - `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注', - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '模型管理' ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Records of chat_model --- ---------------------------- -INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'zai-org/glm-5', 'ppio', 'zai-org/glm-5', NULL, 'Y', 'https://api.ppio.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0); -INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'ppio', 'bge-m3', 1024, 'N', 'https://api.ppio.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-03-15 19:18:51', 'BGE-M3 是一款具备多维度能力的文本嵌入模型,可同时实现密集检索、多向量检索和稀疏检索三大核心功能。该模型设计上兼容超过100种语言,并支持从短句到长达8192词元的长文本等多种输入形式。在跨语言检索任务中,BGE-M3展现出显著优势,其性能在MIRACL、MKQA等国际基准测试中位居前列。此外,针对长文档检索场景,该模型在MLDR、NarritiveQA等数据集上的表现同样达到行业领先水平。', 0); -INSERT INTO `chat_model` VALUES (2045735140488847361, 'chat', 'deepseek-chat', 'custom_api', 'deepseek-chat', NULL, NULL, 'https://api.deepseek.com', 'sk_xx', 103, 1, '2026-04-19 13:24:00', 1, '2026-04-19 13:24:00', 'deepseek对话模型', 0); - --- ---------------------------- --- Table structure for chat_provider --- ---------------------------- -DROP TABLE IF EXISTS `chat_provider`; -CREATE TABLE `chat_provider` ( - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', - `provider_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '厂商名称', - `provider_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '厂商编码', - `provider_icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商图标', - `provider_desc` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商描述', - `api_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'API地址', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', - `sort_order` int NULL DEFAULT 0 COMMENT '排序', - `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注', - `version` int NULL DEFAULT NULL COMMENT '版本', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志', - `update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新IP', - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_provider_code`(`provider_code` ASC, `tenant_id` ASC, `del_flag` ASC) USING BTREE, - INDEX `idx_status`(`status` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2045727230803255298 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '厂商管理表' ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Records of chat_provider --- ---------------------------- -INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:46:59', 'OpenAI厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (11, '深度求索', 'deepseek', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/5ba8c30f153246898a4d7dc7b846de8d.png', 'DeepSeek官方API', 'https://api.deepseek.com', '0', 0, 103, '2026-04-19 12:52:34', '1', '1', '2026-04-19 13:13:25', 'DeepSeek官方API', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (12, '智谱AI', 'zhipu', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/da071783c9284fdd9ed1ce1b57b3c75c.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 4, 103, '2025-12-14 21:48:11', '1', '1', '2026-04-19 13:14:00', '智谱AI厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (13, '小米MIMO', 'xiaomi', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/18dd39365ce244e3ae5e030da036760e.png', '小米官方API', 'https://api.xiaomimimo.com/anthropic/v1/messages', '0', 3, 103, '2026-04-19 12:48:24', '1', '1', '2026-04-19 13:14:22', '小米官方API', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (14, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (15, 'PPIO', 'ppio', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'api聚合厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (16, 'MiniMax', 'minimax', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/fdc712e90e0e4d78b05862ad230884e5.png', 'MiniMax大模型服务,支持M2.7、M2.5等模型', 'https://api.minimax.io/v1', '0', 6, 103, '2026-04-19 12:50:12', '1', '1', '2026-04-19 13:14:59', 'MiniMax厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (17, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 7, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0); -INSERT INTO `chat_provider` VALUES (18, '自定义厂商', 'custom_api', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/c1a8e122510f4e2f90deb36958af710b.png', 'OPENAI兼容格式', '自定义', '0', 8, 103, '2026-04-19 12:35:57', '1', '1', '2026-04-19 13:17:20', 'OPENAI兼容格式', NULL, '0', NULL, 0); - -SET FOREIGN_KEY_CHECKS = 1; diff --git a/docs/script/sql/update/updat-0420.sql b/docs/script/sql/update/updat-0420.sql deleted file mode 100644 index 628a6b5c..00000000 --- a/docs/script/sql/update/updat-0420.sql +++ /dev/null @@ -1,46 +0,0 @@ -/* - Navicat Premium Dump SQL - - Source Server : localhost-mysql - Source Server Type : MySQL - Source Server Version : 80045 (8.0.45) - Source Host : localhost:3306 - Source Schema : ruoyi-ai - - Target Server Type : MySQL - Target Server Version : 80045 (8.0.45) - File Encoding : 65001 - - Date: 20/04/2026 15:30:00 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- 新增:重排序模型(chat_model) --- ---------------------------- -INSERT INTO `chat_model` -(id, category, model_name, provider_code, model_describe, model_dimension, model_show, api_host, api_key, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) -VALUES(2045071617578237953, 'rerank', 'rerank', 'zhipu', '智谱重排序', NULL, 'Y', 'https://open.bigmodel.cn', 'e9xx', 103, 1, '2026-04-17 17:27:24', 1, '2026-04-20 15:21:48', '智谱重排序', 0); - -INSERT INTO `chat_model` -(id, category, model_name, provider_code, model_describe, model_dimension, model_show, api_host, api_key, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) -VALUES(2046119803482902530, 'rerank', 'qwen3-rerank', 'qianwen', '千问3重排序', NULL, NULL, 'https://dashscope.aliyuncs.com', 'sk-xx', 103, 1, '2026-04-20 14:52:31', 1, '2026-04-20 15:03:13', '千问3文本重排序', 0); - --- ---------------------------- --- 新增:字典类型 - 重排序模型分类 --- ---------------------------- -INSERT INTO `sys_dict_data` -(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES(2045070879435259905, '000000', 4, '重排序', 'rerank', 'chat_model_category', NULL, '#000000', 'N', 103, 1, '2026-04-17 17:24:28', 1, '2026-04-19 01:02:20', '重排序模型'); - --- ---------------------------- --- 修改表:knowledge_info 增加重排序相关字段 --- ---------------------------- -ALTER TABLE `knowledge_info` ADD COLUMN `enable_rerank` tinyint DEFAULT 0 NULL COMMENT '是否启用重排序(0否 1是)'; -ALTER TABLE `knowledge_info` ADD COLUMN `rerank_score_threshold` double NULL COMMENT '重排序相关性分数阈值'; -ALTER TABLE `knowledge_info` ADD COLUMN `rerank_top_n` int NULL COMMENT '重排序后返回的文档数量'; -ALTER TABLE `knowledge_info` ADD COLUMN `rerank_model` varchar(100) NULL COMMENT '重排序模型名称'; - -SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/docs/script/sql/update/updat-0423.sql b/docs/script/sql/update/updat-0423.sql deleted file mode 100644 index 4ed14430..00000000 --- a/docs/script/sql/update/updat-0423.sql +++ /dev/null @@ -1,14 +0,0 @@ --- 为知识库信息表新增检索配置字段 (剔除了已存在的重排字段) -ALTER TABLE knowledge_info - ADD COLUMN similarity_threshold DOUBLE DEFAULT 0.5 COMMENT '相似度阈值' -AFTER retrieve_limit; - -ALTER TABLE knowledge_info ADD COLUMN enable_hybrid tinyint(1) DEFAULT 0 COMMENT '是否启用混合检索'; -ALTER TABLE knowledge_info ADD COLUMN hybrid_alpha double DEFAULT 0.5 COMMENT '混合检索权重比例 (0.0=纯向量, 1.0=纯关键词)'; - --- 为知识片段表增加全文索引及关联ID -ALTER TABLE knowledge_fragment ADD COLUMN knowledge_id bigint COMMENT '知识库ID'; -ALTER TABLE knowledge_fragment ADD FULLTEXT INDEX ft_content (content) WITH PARSER ngram; - --- 为知识库附件表增加解析状态字段 -ALTER TABLE `knowledge_attach` ADD COLUMN `status` TINYINT DEFAULT 0 COMMENT '解析状态: 0待解析, 1解析中, 2已解析, 3解析失败'; diff --git a/pom.xml b/pom.xml index 4441852f..c1102289 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 全栈式AI开发平台 - 3.0.0 + 3.1.0 3.5.8 UTF-8 UTF-8 @@ -54,18 +54,23 @@ 2.18.2 - 1.13.0 - 1.13.0-beta23 - 1.5.3 + + 1.17.2 + 1.17.2-beta27 + 1.17.0-beta27 + 1.8.20 + 1.19.6 - 1.0.7 + 1.2.7 + 0.4.2 + 1.62.2 1.27.1 - 1.1.0 + 3.30.2-GA 1.21.2 4.4.0 2.2.8 @@ -79,7 +84,7 @@ 3.5.3 1.3.0 - true + false @@ -499,20 +504,8 @@ src/main/resources - false - - src/main/resources - - - application* - bootstrap* - banner* - - - true - diff --git a/ruoyi-admin/Dockerfile b/ruoyi-admin/Dockerfile index 0394ccb9..af28020c 100644 --- a/ruoyi-admin/Dockerfile +++ b/ruoyi-admin/Dockerfile @@ -1,31 +1,59 @@ -# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/ +# FFmpeg-enabled variant of ruoyi-admin/Dockerfile. FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds -#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds -#FROM findepi/graalvm:java17-native LABEL maintainer="Lion Li" +# Rocky base repositories do not provide the full codec build. RPM Fusion Free +# supplies ffmpeg with libx264, while the checks below prevent a reduced build +# from reaching production unnoticed. +RUN set -eux; \ + dnf -y install dnf-plugins-core epel-release; \ + . /etc/os-release; \ + rocky_major="${VERSION_ID%%.*}"; \ + if [ "${rocky_major}" -ge 9 ]; then \ + dnf config-manager --set-enabled crb; \ + else \ + dnf config-manager --set-enabled powertools; \ + fi; \ + dnf -y install "https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-${rocky_major}.noarch.rpm"; \ + dnf -y install ffmpeg; \ + for filter in scale pad fps setsar format tpad trim settb setpts xfade \ + aresample aformat apad atrim asetpts anullsrc concat acrossfade; do \ + ffmpeg -hide_banner -filters 2>/dev/null | grep -Eq "[[:space:]]${filter}[[:space:]]"; \ + done; \ + ffmpeg -hide_banner -h filter=xfade 2>&1 | grep -q dissolve; \ + ffmpeg -hide_banner -h filter=xfade 2>&1 | grep -q fadeblack; \ + ffmpeg -hide_banner -h filter=xfade 2>&1 | grep -q slideleft; \ + ffmpeg -hide_banner -encoders 2>/dev/null | grep -Eq '[[:space:]]libx264[[:space:]]'; \ + ffmpeg -hide_banner -encoders 2>/dev/null | grep -Eq '[[:space:]]aac[[:space:]]'; \ + ffprobe -hide_banner -version >/dev/null; \ + dnf clean all; \ + rm -rf /var/cache/dnf + RUN mkdir -p /ruoyi/server/logs \ /ruoyi/server/temp \ /ruoyi/skywalking/agent WORKDIR /ruoyi/server -ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" +ENV SERVER_PORT=8080 \ + SNAIL_PORT=28080 \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + JAVA_OPTS="" \ + FFMPEG_PATH=/usr/bin/ffmpeg \ + FFPROBE_PATH=/usr/bin/ffprobe EXPOSE ${SERVER_PORT} -# 暴露 snail job 客户端端口 用于定时任务调度中心通信 EXPOSE ${SNAIL_PORT} ADD ./target/ruoyi-admin.jar ./app.jar SHELL ["/bin/bash", "-c"] -ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \ +ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \ + -Djava.io.tmpdir=/ruoyi/server/temp \ + -Dserver.port=${SERVER_PORT} \ -Dsnail-job.port=${SNAIL_PORT} \ - # 应用名称 如果想区分集群节点监控 改成不同的名称即可 - #-Dskywalking.agent.service_name=ruoyi-server \ - #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \ -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \ -jar app.jar - diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 5168f7a0..fbe7e4cc 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -8,8 +8,8 @@ spring.boot.admin.client: metadata: username: ${spring.boot.admin.client.username} userpassword: ${spring.boot.admin.client.password} - username: @monitor.username@ - password: @monitor.password@ + username: ${MONITOR_USERNAME:ruoyi} + password: ${MONITOR_PASSWORD:123456} --- # mcp配置信息 mcp: @@ -27,12 +27,12 @@ snail-job: enabled: false # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务 group: "ruoyi_group" - # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表 + # SnailJob 接入验证令牌 详见 docs/script/sql/ruoyi-ai-v3_mysql8.sql `sj_group_config` 表 token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" server: host: 127.0.0.1 port: 17888 - # 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段 + # 命名空间UUID 详见 docs/script/sql/ruoyi-ai-v3_mysql8.sql `sj_namespace`表`unique_id`字段 namespace: ${spring.profiles.active} # 随主应用端口漂移 port: 2${server.port} @@ -60,7 +60,7 @@ spring: # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root - password: root + password: 123456 # agent: # url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # # url: jdbc:mysql://localhost:3306/agent_db diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index 2708e5af..ae8372d9 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -9,8 +9,8 @@ spring.boot.admin.client: metadata: username: ${spring.boot.admin.client.username} userpassword: ${spring.boot.admin.client.password} - username: @monitor.username@ - password: @monitor.password@ + username: ${MONITOR_USERNAME:ruoyi} + password: ${MONITOR_PASSWORD:123456} --- # mcp配置信息 mcp: @@ -28,12 +28,12 @@ snail-job: enabled: false # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务 group: "ruoyi_group" - # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表 + # SnailJob 接入验证令牌 详见 docs/script/sql/ruoyi-ai-v3_mysql8.sql `sj_group_config` 表 token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" server: host: 127.0.0.1 port: 17888 - # 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段 + # 命名空间UUID 详见 docs/script/sql/ruoyi-ai-v3_mysql8.sql `sj_namespace`表`unique_id`字段 namespace: ${spring.profiles.active} # 随主应用端口漂移 port: 2${server.port} diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index a528f646..c93019e0 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -20,6 +20,10 @@ server: # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 worker: 256 +--- # 小程序对话 WebSocket 兜底默认模型(前端未传 model 且无智能体绑定时使用) +chat: + default-model: deepseek-v4-flash + captcha: # 是否启用验证码校验 enable: false @@ -35,7 +39,7 @@ captcha: # 日志配置 logging: level: - org.ruoyi: @logging.level@ + org.ruoyi: ${LOGGING_LEVEL_ORG_RUOYI:info} org.springframework: warn org.mybatis.spring.mapper: error org.apache.fury: warn @@ -75,7 +79,7 @@ spring: # 国际化资源文件路径 basename: i18n/messages profiles: - active: @profiles.active@ + active: ${SPRING_PROFILES_ACTIVE:dev} # 文件上传 servlet: multipart: @@ -125,6 +129,7 @@ security: - /*/api-docs/** - /warm-flow-ui/config - /workflow/run + - /coding/** # 多租户配置 tenant: # 是否开启 @@ -314,11 +319,12 @@ warm-flow: vector-store: # 向量存储类型 可选(weaviate/milvus/qdrant) # 如需修改向量库类型,请修改此配置值! - type: milvus + # 注意:需与 docker-compose 实际部署的向量库保持一致(当前 compose 内置 weaviate,映射端口 28080) + type: weaviate # Weaviate配置 weaviate: protocol: http - host: 127.0.0.1:6038 + host: 127.0.0.1:28080 classname: LocalKnowledge # Milvus配置 milvus: @@ -331,3 +337,38 @@ vector-store: collectionname: LocalKnowledge api-key: use-tls: false + +# 短剧成片合成 +short-drama: + composition: + ffmpeg-path: ${FFMPEG_PATH:ffmpeg} + ffprobe-path: ${FFPROBE_PATH:ffprobe} + fps: ${SHORT_DRAMA_COMPOSITION_FPS:30} + audio-sample-rate: ${SHORT_DRAMA_COMPOSITION_AUDIO_SAMPLE_RATE:48000} + video-codec: ${SHORT_DRAMA_COMPOSITION_VIDEO_CODEC:libx264} + audio-codec: ${SHORT_DRAMA_COMPOSITION_AUDIO_CODEC:aac} + preset: ${SHORT_DRAMA_COMPOSITION_PRESET:medium} + crf: ${SHORT_DRAMA_COMPOSITION_CRF:20} + audio-bitrate: ${SHORT_DRAMA_COMPOSITION_AUDIO_BITRATE:192k} + max-clips: ${SHORT_DRAMA_COMPOSITION_MAX_CLIPS:100} + max-transition-seconds: ${SHORT_DRAMA_COMPOSITION_MAX_TRANSITION_SECONDS:2.0} + minimum-output-bytes: ${SHORT_DRAMA_COMPOSITION_MINIMUM_OUTPUT_BYTES:1024} + max-process-output-bytes: ${SHORT_DRAMA_COMPOSITION_MAX_PROCESS_OUTPUT_BYTES:1048576} + max-source-bytes: ${SHORT_DRAMA_COMPOSITION_MAX_SOURCE_BYTES:536870912} + max-total-source-bytes: ${SHORT_DRAMA_COMPOSITION_MAX_TOTAL_SOURCE_BYTES:2147483648} + probe-timeout: ${SHORT_DRAMA_COMPOSITION_PROBE_TIMEOUT:30s} + process-timeout: ${SHORT_DRAMA_COMPOSITION_PROCESS_TIMEOUT:30m} + job-stale-after: ${SHORT_DRAMA_COMPOSITION_JOB_STALE_AFTER:45m} + watermark-font-file: ${SHORT_DRAMA_COMPOSITION_WATERMARK_FONT_FILE:} + worker-core-size: ${SHORT_DRAMA_COMPOSITION_WORKER_CORE_SIZE:1} + worker-max-size: ${SHORT_DRAMA_COMPOSITION_WORKER_MAX_SIZE:2} + worker-queue-capacity: ${SHORT_DRAMA_COMPOSITION_WORKER_QUEUE_CAPACITY:8} + storage-mode: ${SHORT_DRAMA_COMPOSITION_STORAGE_MODE:local} + local-output-directory: ${SHORT_DRAMA_COMPOSITION_LOCAL_OUTPUT_DIRECTORY:logs/short-drama-compositions} + download: + connect-timeout: ${SHORT_DRAMA_DOWNLOAD_CONNECT_TIMEOUT:30s} + call-timeout: ${SHORT_DRAMA_DOWNLOAD_CALL_TIMEOUT:10m} + max-redirects: ${SHORT_DRAMA_DOWNLOAD_MAX_REDIRECTS:5} + # 生产环境建议配置为视频供应商或 OSS/CDN 域名,多个值用逗号分隔。 + allowed-hosts: ${SHORT_DRAMA_DOWNLOAD_ALLOWED_HOSTS:} + fake-ip-allowed-hosts: ${SHORT_DRAMA_DOWNLOAD_FAKE_IP_ALLOWED_HOSTS:atlas-media.oss-us-west-1.aliyuncs.com} diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index 9b0acbdd..3841be4e 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -14,7 +14,7 @@ - 3.0.0 + 3.1.0 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java index e8640887..a9c7c1e5 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java @@ -1,10 +1,12 @@ package org.ruoyi.common.chat.domain.bo.chat; import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; import org.ruoyi.common.chat.entity.chat.ChatModel; +import org.ruoyi.common.core.validate.AddGroup; import org.ruoyi.common.core.validate.EditGroup; import org.ruoyi.common.mybatis.core.domain.BaseEntity; @@ -28,16 +30,19 @@ public class ChatModelBo extends BaseEntity { /** * 模型分类 */ + @NotBlank(message = "模型分类不能为空", groups = { AddGroup.class, EditGroup.class }) private String category; /** * 模型名称 */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) private String modelName; /** * 模型供应商 */ + @NotBlank(message = "模型供应商不能为空", groups = { AddGroup.class, EditGroup.class }) private String providerCode; /** diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ModelBatchKeyBo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ModelBatchKeyBo.java new file mode 100644 index 00000000..683292f0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ModelBatchKeyBo.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.chat.domain.bo.chat; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +/** + * 按厂商批量更新密钥业务对象 + * + * @author ageerle + * @date 2025-12-14 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ModelBatchKeyBo extends BaseEntity { + + /** + * 模型供应商 + */ + @NotBlank(message = "厂商编码不能为空") + private String providerCode; + + /** + * 密钥 + */ + @NotBlank(message = "密钥不能为空") + private String apiKey; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java index 23e42375..ac106ab3 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ChatRequest.java @@ -21,9 +21,19 @@ import java.util.List; @Data public class ChatRequest { - @NotEmpty(message = "传入的模型不能为空") + /** + * 模型名称。 + * 智能体模式下可缺省:传 agentId 时后端按智能体绑定的模型解析,此字段仅作回退。 + */ private String model; + /** + * 智能体ID。传入时后端按智能体配置解析模型/工具/技能/知识库/提示词/是否深度思考。 + */ + @JsonSerialize(using = ToStringSerializer.class) + @JSONField(serializeUsing = String.class) + private Long agentId; + /** * 对话消息 */ @@ -35,16 +45,6 @@ public class ChatRequest { */ private WorkFlowRunner workFlowRunner; - /** - * 人机交互信息体 - */ - private ReSumeRunner reSumeRunner; - - /** - * 是否为人机交互用户继续输入 - */ - private Boolean isResume = false; - /** * 是否启用工作流 */ diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ReSumeRunner.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ReSumeRunner.java deleted file mode 100644 index 65b39fd0..00000000 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/dto/request/ReSumeRunner.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.ruoyi.common.chat.domain.dto.request; - -import lombok.Data; - -/** - * 人机交互输入信息 - */ -@Data -public class ReSumeRunner { - /** - * 运行节点UUID - */ - private String runtimeUuid; - - /** - * 人机交互用户输入信息 - */ - private String feedbackContent; -} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/audio/AudioContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/audio/AudioContext.java new file mode 100644 index 00000000..a096fb54 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/audio/AudioContext.java @@ -0,0 +1,44 @@ +package org.ruoyi.common.chat.entity.audio; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; + +/** + * Text-to-speech context. + */ +@Data +@Builder +public class AudioContext { + + @NotNull(message = "模型配置不能为空") + private ChatModelVo chatModelVo; + + @NotBlank(message = "输入文本不能为空") + private String input; + + private String voice; + + private String responseFormat; + + private Double speed; + + private String instructions; + + /** Atlas seed-audio 参考资源:[{speaker, audioUrl, audioData, imageData}],speaker 为音色名 */ + private java.util.List> references; + + /** Atlas seed-audio 采样率 */ + private Integer sampleRate; + + /** Atlas seed-audio 音调调整 (-12~12) */ + private Integer pitchRate; + + /** Atlas seed-audio 语速调整 (-50~100) */ + private Integer speechRate; + + /** Atlas seed-audio 响度调整 (-50~100) */ + private Integer loudnessRate; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/image/ImageContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/image/ImageContext.java index 60be2739..ce6302c0 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/image/ImageContext.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/image/ImageContext.java @@ -42,4 +42,9 @@ public class ImageContext { @Min(value = 0, message = "随机数种子不能小于0") @Max(value = 2147483647, message = "随机数种子不能大于2147483647") private Integer seed; + + /** + * 参考图 URL(图生图用) + */ + private String image; } diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/media/MediaGenerationResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/media/MediaGenerationResponse.java new file mode 100644 index 00000000..870b109f --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/media/MediaGenerationResponse.java @@ -0,0 +1,31 @@ +package org.ruoyi.common.chat.entity.media; + +import lombok.Builder; +import lombok.Data; + +/** + * Unified media generation response. + */ +@Data +@Builder +public class MediaGenerationResponse { + + private String type; + + private String mimeType; + + private String url; + + private String b64Json; + + private String dataUrl; + + private String id; + + private String status; + + private String rawResponse; + + /** 末帧图片 URL(Atlas return_last_frame=true 时返回,用于下一镜首帧承接) */ + private String lastFrameUrl; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/video/VideoContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/video/VideoContext.java new file mode 100644 index 00000000..c602d4b7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/video/VideoContext.java @@ -0,0 +1,47 @@ +package org.ruoyi.common.chat.entity.video; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; + +/** + * Text-to-video context. + */ +@Data +@Builder +public class VideoContext { + + @NotNull(message = "模型配置不能为空") + private ChatModelVo chatModelVo; + + @NotBlank(message = "提示词不能为空") + private String prompt; + + private String size; + + private Integer seconds; + + private String quality; + + /** 参考图 URL(图生视频模式,单图) */ + private String imageUrl; + + /** 多参考图 URL(多参考图生视频模式,配合 @imageN prompt 使用) */ + private java.util.List referenceImages; + + /** 参考音频 URL 列表(对白口型对齐模式) */ + private java.util.List referenceAudios; + + /** 是否让模型生成同步音频(环境音/动效) */ + private Boolean generateAudio; + + /** 是否要求返回末帧,用于下一镜首帧承接 */ + private Boolean returnLastFrame; + + /** 上一镜末帧 URL(同场景连续镜头首帧承接用,作为额外参考图传入) */ + private String lastFrameUrl; + + private String videoId; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/AudioServiceFactory.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/AudioServiceFactory.java new file mode 100644 index 00000000..7199ced4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/AudioServiceFactory.java @@ -0,0 +1,40 @@ +package org.ruoyi.common.chat.factory; + +import org.ruoyi.common.chat.service.audio.IAudioGenerationService; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Text-to-speech service factory. + */ +@Component +public class AudioServiceFactory implements ApplicationContextAware { + + private final Map audioServiceMap = new ConcurrentHashMap<>(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Map serviceMap = applicationContext.getBeansOfType(IAudioGenerationService.class); + for (IAudioGenerationService service : serviceMap.values()) { + if (service != null) { + audioServiceMap.put(service.getProviderName(), service); + } + } + } + + public IAudioGenerationService getOriginalService(String providerCode) { + IAudioGenerationService service = audioServiceMap.get(providerCode); + if (service == null && "custom_api".equals(providerCode)) { + service = audioServiceMap.get("openai"); + } + if (service == null) { + throw new IllegalArgumentException("不支持的语音模型供应商: " + providerCode); + } + return service; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java index 52d3731b..89c641db 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/ImageServiceFactory.java @@ -37,6 +37,9 @@ public class ImageServiceFactory implements ApplicationContextAware { */ public IImageGenerationService getOriginalService(String category) { IImageGenerationService service = imageSerivceMap.get(category); + if (service == null && "custom_api".equals(category)) { + service = imageSerivceMap.get("openai"); + } if (service == null) { throw new IllegalArgumentException("不支持的模型类别: " + category); } diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/VideoServiceFactory.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/VideoServiceFactory.java new file mode 100644 index 00000000..8cdd9e4e --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/factory/VideoServiceFactory.java @@ -0,0 +1,40 @@ +package org.ruoyi.common.chat.factory; + +import org.ruoyi.common.chat.service.video.IVideoGenerationService; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Text-to-video service factory. + */ +@Component +public class VideoServiceFactory implements ApplicationContextAware { + + private final Map videoServiceMap = new ConcurrentHashMap<>(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Map serviceMap = applicationContext.getBeansOfType(IVideoGenerationService.class); + for (IVideoGenerationService service : serviceMap.values()) { + if (service != null) { + videoServiceMap.put(service.getProviderName(), service); + } + } + } + + public IVideoGenerationService getOriginalService(String providerCode) { + IVideoGenerationService service = videoServiceMap.get(providerCode); + if (service == null && "custom_api".equals(providerCode)) { + service = videoServiceMap.get("openai"); + } + if (service == null) { + throw new IllegalArgumentException("不支持的视频模型供应商: " + providerCode); + } + return service; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/audio/IAudioGenerationService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/audio/IAudioGenerationService.java new file mode 100644 index 00000000..68cb383e --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/audio/IAudioGenerationService.java @@ -0,0 +1,15 @@ +package org.ruoyi.common.chat.service.audio; + +import jakarta.validation.Valid; +import org.ruoyi.common.chat.entity.audio.AudioContext; +import org.ruoyi.common.chat.entity.media.MediaGenerationResponse; + +/** + * Public text-to-speech service. + */ +public interface IAudioGenerationService { + + MediaGenerationResponse generateSpeech(@Valid AudioContext audioContext); + + String getProviderName(); +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatModelService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatModelService.java index 3c362a52..7f802a30 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatModelService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chat/IChatModelService.java @@ -73,4 +73,13 @@ public interface IChatModelService { * @return 是否删除成功 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 按厂商批量更新密钥 + * + * @param providerCode 厂商编码 + * @param apiKey 密钥 + * @return 是否更新成功 + */ + Boolean updateApiKeyByProvider(String providerCode, String apiKey); } diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/image/IImageGenerationService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/image/IImageGenerationService.java index 3be0a84f..aa845dbd 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/image/IImageGenerationService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/image/IImageGenerationService.java @@ -2,6 +2,8 @@ package org.ruoyi.common.chat.service.image; import jakarta.validation.Valid; import org.ruoyi.common.chat.entity.image.ImageContext; +import org.ruoyi.common.chat.entity.media.MediaGenerationResponse; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; /** * 公共文生图接口 @@ -9,10 +11,31 @@ import org.ruoyi.common.chat.entity.image.ImageContext; public interface IImageGenerationService { /** - * 根据文字生成图片 + * 根据文字生成图片(同步阻塞,返回 URL) */ String generateImage(@Valid ImageContext imageContext); + /** + * 异步启动图片生成(返回 prediction 信息,用于轮询进度) + * 默认回退到同步模式 + */ + default MediaGenerationResponse startImageGeneration(@Valid ImageContext imageContext) { + String url = generateImage(imageContext); + return MediaGenerationResponse.builder() + .type("image") + .url(url) + .status("completed") + .build(); + } + + /** + * 上传图生图/图生视频使用的临时媒体文件。 + * 不支持上传的供应商保持默认异常,避免静默回退到错误存储。 + */ + default String uploadMedia(ChatModelVo model, byte[] content, String fileName, String contentType) { + throw new UnsupportedOperationException("当前图片供应商不支持临时媒体上传"); + } + /** * 获取服务提供商名称 */ diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/video/IVideoGenerationService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/video/IVideoGenerationService.java new file mode 100644 index 00000000..5a1aa035 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/video/IVideoGenerationService.java @@ -0,0 +1,17 @@ +package org.ruoyi.common.chat.service.video; + +import jakarta.validation.Valid; +import org.ruoyi.common.chat.entity.media.MediaGenerationResponse; +import org.ruoyi.common.chat.entity.video.VideoContext; + +/** + * Public text-to-video service. + */ +public interface IVideoGenerationService { + + MediaGenerationResponse generateVideo(@Valid VideoContext videoContext); + + MediaGenerationResponse retrieveVideo(@Valid VideoContext videoContext); + + String getProviderName(); +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/OssService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/OssService.java index 884f44e2..d3debd43 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/OssService.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/OssService.java @@ -1,8 +1,11 @@ package org.ruoyi.common.core.service; +import jakarta.servlet.http.HttpServletResponse; import org.ruoyi.common.core.domain.dto.OssDTO; import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.IOException; import java.util.List; /** @@ -33,5 +36,19 @@ public interface OssService { */ OssDTO uploadFile(MultipartFile file); + /** + * Upload a server-side file without buffering the complete payload in memory. + */ + OssDTO uploadFile(File file); + + /** + * Stream an object as an authenticated attachment response. + */ + void downloadFile(Long ossId, HttpServletResponse response) throws IOException; + + /** + * Delete one object and its sys_oss metadata. + */ + Boolean deleteFile(Long ossId); } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/core/OssClient.java index 87a9ca8f..0fbab047 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/core/OssClient.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/core/OssClient.java @@ -310,7 +310,7 @@ public class OssClient { client.deleteObject( x -> x.bucket(properties.getBucketName()) .key(removeBaseUrl(path)) - .build()); + .build()).join(); } catch (Exception e) { throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); } diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml index 72447e95..b2723802 100644 --- a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml +++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml @@ -4,7 +4,7 @@ spring: application: name: ruoyi-monitor-admin profiles: - active: @profiles.active@ + active: ${SPRING_PROFILES_ACTIVE:dev} logging: config: classpath:logback-plus.xml @@ -13,8 +13,8 @@ logging: spring: security: user: - name: @monitor.username@ - password: @monitor.password@ + name: ${MONITOR_USERNAME:ruoyi} + password: ${MONITOR_PASSWORD:123456} boot: admin: ui: @@ -47,5 +47,5 @@ spring.boot.admin.client: metadata: username: ${spring.boot.admin.client.username} userpassword: ${spring.boot.admin.client.password} - username: @monitor.username@ - password: @monitor.password@ + username: ${MONITOR_USERNAME:ruoyi} + password: ${MONITOR_PASSWORD:123456} diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml index 18a851c7..67b4d4e2 100644 --- a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml @@ -2,7 +2,7 @@ spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/snail_job?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: root hikari: @@ -47,5 +47,5 @@ spring.boot.admin.client: metadata: username: ${spring.boot.admin.client.username} userpassword: ${spring.boot.admin.client.password} - username: @monitor.username@ - password: @monitor.password@ + username: ${MONITOR_USERNAME:ruoyi} + password: ${MONITOR_PASSWORD:123456} diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml index 18a851c7..4510d899 100644 --- a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml @@ -47,5 +47,5 @@ spring.boot.admin.client: metadata: username: ${spring.boot.admin.client.username} userpassword: ${spring.boot.admin.client.password} - username: @monitor.username@ - password: @monitor.password@ + username: ${MONITOR_USERNAME:ruoyi} + password: ${MONITOR_PASSWORD:123456} diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml index ba6df45f..bdefb472 100644 --- a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application.yml @@ -7,7 +7,7 @@ spring: application: name: ruoyi-snailjob-server profiles: - active: @profiles.active@ + active: ${SPRING_PROFILES_ACTIVE:dev} web: resources: static-locations: classpath:admin/ diff --git a/ruoyi-modules/ruoyi-aiflow/pom.xml b/ruoyi-modules/ruoyi-aiflow/pom.xml index d7a147c8..e2419ca9 100644 --- a/ruoyi-modules/ruoyi-aiflow/pom.xml +++ b/ruoyi-modules/ruoyi-aiflow/pom.xml @@ -29,6 +29,12 @@ ruoyi-common-chat + + + org.ruoyi + ruoyi-chat + + org.ruoyi ruoyi-common-web @@ -72,6 +78,21 @@ com.talanlabs avatar-generator-cat ${avatar-generator.version} + + + org.javassist + javassist + + + + + + + org.javassist + javassist + ${javassist.version} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java index dada5dea..bde74b30 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntime.java @@ -27,13 +27,13 @@ public class WorkflowRuntime extends BaseEntity { @TableField("workflow_id") private Long workflowId; - @TableField(value = "input") + @TableField(value = "`input`") private String input; - @TableField(value = "output") + @TableField(value = "`output`") private String output; - @TableField("status") + @TableField("`status`") private Integer status; @TableField("status_remark") diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java index a472b5a1..00550a73 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/entity/WorkflowRuntimeNode.java @@ -29,13 +29,13 @@ public class WorkflowRuntimeNode extends BaseEntity { @TableField("node_id") private Long nodeId; - @TableField(value = "input") + @TableField(value = "`input`") private String input; - @TableField(value = "output") + @TableField(value = "`output`") private String output; - @TableField("status") + @TableField("`status`") private Integer status; @TableField("status_remark") diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/CompileNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/CompileNode.java index 20960113..1e6ad419 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/CompileNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/CompileNode.java @@ -16,6 +16,7 @@ import java.util.List; @AllArgsConstructor public class CompileNode { protected String id; + @Builder.Default protected Boolean conditional = false; /** @@ -24,5 +25,6 @@ public class CompileNode { * 2. 当前节点为条件分支节点,下游节点为多个节点,实际执行时只会执行一条 * 两种节点根据是否GraphCompileNode来区分 */ + @Builder.Default protected List nextNodes = new ArrayList<>(); } diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java index a62d4218..290245a7 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java @@ -15,6 +15,7 @@ import java.util.List; @NoArgsConstructor public class NodeProcessResult { + @Builder.Default private List content = new ArrayList<>(); /** @@ -25,6 +26,7 @@ public class NodeProcessResult { /** * 是否发生错误 */ + @Builder.Default private boolean error = false; /** diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java index 87ab1baf..24d0110f 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowEngine.java @@ -265,10 +265,6 @@ public class WorkflowEngine { String node = streamingOutput.node(); String chunk = streamingOutput.chunk(); log.info("node:{},chunk:{}", node, chunk); - Map strMap = new HashMap<>(); - strMap.put("ck", chunk); -// SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_CHUNK_" + node + "]", strMap.toString()); - SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_CHUNK_" + node + "]", chunk); } else { AbstractWfNode abstractWfNode = wfState.getCompletedNodes().stream() diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java index b7507f4d..9244d025 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/node/knowledgeRetrieval/KnowledgeRetrievalNode.java @@ -80,9 +80,10 @@ public class KnowledgeRetrievalNode extends AbstractWfNode { String retrievalResult; String mode = config.getRetrievalMode() != null ? config.getRetrievalMode().toLowerCase() : "vector"; - // 目前只支持向量检索,图谱检索需要依赖graph模块 - if ("graph".equals(mode) || "hybrid".equals(mode)) { - log.warn("Graph retrieval mode is not supported in workflow-api module, falling back to vector retrieval"); + // 图谱检索需要依赖 graph 模块,暂不支持;vector/hybrid 由统一检索服务处理 + if ("graph".equals(mode)) { + log.warn("Graph retrieval mode is not supported"); + throw new UnsupportedOperationException("GraphRAG retrieval is not supported"); } retrievalResult = retrieveFromVector(config, finalQuery); @@ -203,18 +204,75 @@ public class KnowledgeRetrievalNode extends AbstractWfNode { } /** - * 从向量库检索 + * 从向量库检索(复用聊天模块的统一检索服务:向量 + 可选混合检索 + 可选重排) */ private String retrieveFromVector(KnowledgeRetrievalNodeConfig config, String query) { try { - - // 获取知识库信息以获取embedding模型配置 Long knowledgeId = Long.parseLong(config.getKnowledgeId()); - // 合并结果 - String mergedResult = "根据知识库id + query 查询知识库内容"; + org.ruoyi.service.knowledge.IKnowledgeInfoService knowledgeInfoService = + SpringUtil.getBean(org.ruoyi.service.knowledge.IKnowledgeInfoService.class); + org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo kb = knowledgeInfoService.queryById(knowledgeId); + if (kb == null) { + log.error("Knowledge base not found: {}", knowledgeId); + return "错误:知识库不存在, id=" + knowledgeId; + } - return mergedResult; + org.ruoyi.common.chat.service.chat.IChatModelService chatModelService = + SpringUtil.getBean(org.ruoyi.common.chat.service.chat.IChatModelService.class); + org.ruoyi.common.chat.domain.vo.chat.ChatModelVo embModel = + chatModelService.selectModelByName(kb.getEmbeddingModel()); + if (embModel == null) { + log.error("Embedding model not found: {}", kb.getEmbeddingModel()); + return "错误:知识库未配置有效的向量模型"; + } + + // 组装检索参数:节点配置优先,混合检索/重排继承知识库配置 + org.ruoyi.domain.bo.vector.QueryVectorBo bo = new org.ruoyi.domain.bo.vector.QueryVectorBo(); + bo.setQuery(query); + bo.setKid(String.valueOf(knowledgeId)); + bo.setMaxResults(config.getTopK() != null ? config.getTopK() : kb.getRetrieveLimit()); + bo.setSimilarityThreshold(config.getSimilarityThreshold() != null + ? config.getSimilarityThreshold() : kb.getSimilarityThreshold()); + bo.setEmbeddingModelName(kb.getEmbeddingModel()); + bo.setVectorModelName(kb.getVectorModel()); + bo.setApiKey(embModel.getApiKey()); + bo.setBaseUrl(embModel.getApiHost()); + + String mode = config.getRetrievalMode() != null ? config.getRetrievalMode().toLowerCase() : "vector"; + boolean enableHybrid = "hybrid".equals(mode) + || (kb.getEnableHybrid() != null && kb.getEnableHybrid() == 1); + bo.setEnableHybrid(enableHybrid); + bo.setHybridAlpha(kb.getHybridAlpha()); + bo.setEnableRerank(kb.getEnableRerank() != null && kb.getEnableRerank() == 1); + bo.setRerankModelName(kb.getRerankModel()); + bo.setRerankTopN(kb.getRerankTopN()); + bo.setRerankScoreThreshold(kb.getRerankScoreThreshold()); + + org.ruoyi.service.retrieval.KnowledgeRetrievalService retrievalService = + SpringUtil.getBean(org.ruoyi.service.retrieval.KnowledgeRetrievalService.class); + java.util.List results = retrievalService.retrieve(bo); + if (results == null || results.isEmpty()) { + log.info("Knowledge retrieval returned no results, kid={}, query={}", knowledgeId, query); + return ""; + } + + // 合并结果 + boolean returnSource = config.getReturnSource() == null || config.getReturnSource(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < results.size(); i++) { + org.ruoyi.domain.vo.knowledge.KnowledgeRetrievalVo vo = results.get(i); + sb.append(i + 1).append(". ").append(vo.getContent()); + if (returnSource && StringUtils.isNotBlank(vo.getSourceName())) { + sb.append("(来源: ").append(vo.getSourceName()); + if (vo.getScore() != null) { + sb.append(String.format(", 相关度: %.3f", vo.getScore())); + } + sb.append(")"); + } + sb.append("\n"); + } + return sb.toString().trim(); } catch (NumberFormatException e) { log.error("Invalid knowledge base ID format: {}", config.getKnowledgeId(), e); return "错误:知识库ID格式无效"; diff --git a/ruoyi-modules/ruoyi-chat/docs/MCP工具模块接口文档.md b/ruoyi-modules/ruoyi-chat/docs/MCP工具模块接口文档.md deleted file mode 100644 index 9d8460b7..00000000 --- a/ruoyi-modules/ruoyi-chat/docs/MCP工具模块接口文档.md +++ /dev/null @@ -1,336 +0,0 @@ -# MCP工具管理模块 - API接口文档 - -## 概述 - -本文档描述了MCP工具管理模块的REST API接口,供前端开发人员参考。 - -## 基础信息 - -- **Base URL**: `/api/mcp` -- **认证方式**: Bearer Token (SaToken) -- **响应格式**: JSON - ---- - -## 1. MCP工具管理 - -### 1.1 查询工具列表(分页) - -**接口**: `GET /tool/list` - -**权限**: `mcp:tool:list` - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| name | String | 否 | 工具名称(模糊查询) | -| description | String | 否 | 工具描述(模糊查询) | -| type | String | 否 | 工具类型:LOCAL/REMOTE/BUILTIN | -| status | String | 否 | 状态:0-启用, 1-禁用 | -| pageNum | Integer | 是 | 页码,默认1 | -| pageSize | Integer | 是 | 每页数量,默认10 | - -**响应示例**: -```json -{ - "rows": [ - { - "id": 1, - "name": "ReadFileTool", - "description": "读取文件内容工具", - "type": "BUILTIN", - "status": "0", - "configJson": null, - "createTime": "2026-03-08 10:00:00", - "updateTime": "2026-03-08 10:00:00" - } - ], - "total": 1 -} -``` - -### 1.2 查询工具列表(不分页) - -**接口**: `GET /tool/all` - -**权限**: `mcp:tool:list` - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| keyword | String | 否 | 关键词 | -| type | String | 否 | 工具类型 | -| status | String | 否 | 状态 | - -**响应示例**: -```json -{ - "tools": [ - { - "id": 1, - "name": "ReadFileTool", - "description": "读取文件内容工具", - "type": "BUILTIN", - "status": "0" - } - ], - "total": 1 -} -``` - -### 1.3 获取工具详情 - -**接口**: `GET /tool/{id}` - -**权限**: `mcp:tool:query` - -**路径参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| id | Long | 是 | 工具ID | - -### 1.4 新增工具 - -**接口**: `POST /tool` - -**权限**: `mcp:tool:add` - -**请求体**: -```json -{ - "name": "MyMcpTool", - "description": "我的MCP工具", - "type": "REMOTE", - "status": "0", - "configJson": "{\"baseUrl\": \"http://localhost:8080/mcp\"}" -} -``` - -### 1.5 修改工具 - -**接口**: `PUT /tool` - -**权限**: `mcp:tool:edit` - -**请求体**: 同新增工具 - -### 1.6 删除工具 - -**接口**: `DELETE /tool/{ids}` - -**权限**: `mcp:tool:remove` - -**路径参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| ids | String | 是 | 工具ID,多个用逗号分隔 | - -### 1.7 更新工具状态 - -**接口**: `PUT /tool/{id}/status` - -**权限**: `mcp:tool:edit` - -**路径参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| id | Long | 是 | 工具ID | - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| status | String | 是 | 状态:0-启用, 1-禁用 | - -### 1.8 测试工具连接 - -**接口**: `POST /tool/{id}/test` - -**权限**: `mcp:tool:query` - -**响应示例**: -```json -{ - "success": true, - "message": "连接测试成功", - "toolCount": 5, - "tools": ["tool1", "tool2", "tool3", "tool4", "tool5"] -} -``` - ---- - -## 2. MCP市场管理 - -### 2.1 查询市场列表 - -**接口**: `GET /market/list` - -**权限**: `mcp:market:list` - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| name | String | 否 | 市场名称 | -| description | String | 否 | 市场描述 | -| status | String | 否 | 状态 | -| pageNum | Integer | 是 | 页码 | -| pageSize | Integer | 是 | 每页数量 | - -### 2.2 获取市场工具列表 - -**接口**: `GET /market/{marketId}/tools` - -**权限**: `mcp:market:query` - -**路径参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| marketId | Long | 是 | 市场ID | - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| page | Integer | 否 | 页码,默认1 | -| size | Integer | 否 | 每页数量,默认10 | - -### 2.3 刷新市场工具 - -**接口**: `POST /market/{marketId}/refresh` - -**权限**: `mcp:market:edit` - -**响应示例**: -```json -{ - "success": true, - "message": "刷新成功", - "addedCount": 3, - "updatedCount": 5 -} -``` - -### 2.4 加载工具到本地 - -**接口**: `POST /market/tool/{toolId}/load` - -**权限**: `mcp:market:edit` - -**路径参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| toolId | Long | 是 | 市场工具ID | - -### 2.5 批量加载工具 - -**接口**: `POST /market/tools/batchLoad` - -**权限**: `mcp:market:edit` - -**请求体**: -```json -{ - "toolIds": [1, 2, 3] -} -``` - ---- - -## 3. 工具调用日志 - -### 3.1 查询调用日志 - -**接口**: `GET /tool/callLog` - -**权限**: `mcp:tool:query` - -**请求参数**: -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| toolId | Long | 否 | 工具ID | -| sessionId | Long | 否 | 会话ID | -| startDate | Date | 否 | 开始日期 | -| endDate | Date | 否 | 结束日期 | -| pageNum | Integer | 是 | 页码 | -| pageSize | Integer | 是 | 每页数量 | - -### 3.2 获取工具统计 - -**接口**: `GET /tool/{toolId}/metrics` - -**权限**: `mcp:tool:query` - -**响应示例**: -```json -{ - "toolId": 1, - "toolName": "ReadFileTool", - "today": { - "callCount": 100, - "successCount": 95, - "failureCount": 5, - "avgDurationMs": 150, - "successRate": 95.0 - }, - "week": { - "callCount": 500, - "successCount": 475, - "failureCount": 25, - "avgDurationMs": 160, - "successRate": 95.0 - } -} -``` - ---- - -## 4. 状态码说明 - -| 状态码 | 说明 | -|--------|------| -| 200 | 请求成功 | -| 401 | 未认证 | -| 403 | 无权限 | -| 404 | 资源不存在 | -| 500 | 服务器错误 | - ---- - -## 5. 前端页面需求 - -### 5.1 MCP工具管理页面 (`/mcp/tool`) - -**功能**: -- 工具列表展示(分页) -- 工具搜索和筛选 -- 新增/编辑/删除工具 -- 工具状态切换 -- 工具连接测试 - -**表格列**: -- 工具名称 -- 工具描述 -- 工具类型(标签显示) -- 状态(开关) -- 创建时间 -- 操作(编辑、删除、测试) - -### 5.2 MCP市场管理页面 (`/mcp/market`) - -**功能**: -- 市场列表展示 -- 市场工具浏览 -- 刷新市场工具 -- 加载工具到本地 - -### 5.3 工具调用日志页面 (`/mcp/log`) - -**功能**: -- 调用日志列表 -- 按工具/日期筛选 -- 成功率统计 -- 响应时间统计 - -**图表**: -- 每日调用次数趋势图 -- 工具调用成功率饼图 -- 平均响应时间柱状图 diff --git a/ruoyi-modules/ruoyi-chat/docs/数据库智能体实现总结.md b/ruoyi-modules/ruoyi-chat/docs/数据库智能体实现总结.md deleted file mode 100644 index 72cd4257..00000000 --- a/ruoyi-modules/ruoyi-chat/docs/数据库智能体实现总结.md +++ /dev/null @@ -1,152 +0,0 @@ -# 数据库操作智能体实现总结 - -## 概述 -基于 LangChain4j 的 **Pure agentic AI** 模式,完成了一个智能数据库查询系统。该系统能够根据用户的自然语言问题,自动分析数据库结构、生成查询计划并执行相应的数据库操作。 - -## 架构设计 - -### 1. 整体框架 -``` -用户请求 → SupervisorAgent(协调器) → SqlAgent(数据库专家) → 数据库工具 → 数据库 - ↓ - 结果处理与响应 -``` - -### 2. 核心组件 - -#### A. SqlAgent (数据库查询专家) -- **文件**: `org.ruoyi.agent.SqlAgent` -- **职责**: 根据用户的自然语言问题,调用相应的工具查询数据库 -- **使用的工具**: - - `QueryAllTablesTool`: 查询所有表名和注释 - - `QueryTableSchemaTool`: 查询表的DDL(CREATE TABLE语句) - - `ExecuteSqlQueryTool`: 执行SELECT查询 - -```java -public interface SqlAgent { - @SystemMessage("...") // 详细的系统提示 - @UserMessage("请回答以下问题:{{query}}") - @Agent("一个智能数据库查询助手...") - String getData(@V("query") String query); -} -``` - -#### B. SupervisorAgent (总体协调器) -- 在 `OpenAIServiceImpl.doAgent()` 中创建 -- 作用:协调 SqlAgent 的执行,管理任务流程 -- 响应策略:`SUMMARY` - 返回所有操作的摘要 - -```java -SupervisorAgent supervisor = AgenticServices - .supervisorBuilder() - .chatModel(PLANNER_MODEL) - .subAgents(sqlAgent) - .responseStrategy(SupervisorResponseStrategy.SUMMARY) - .build(); -``` - -#### C. 数据库工具 (Tools) - -##### 1. QueryAllTablesTool -```java -@Tool("Query all tables in the database and return table names and basic information") -public String queryAllTables() -``` -- 返回数据库中所有表的名称和注释 -- 使用注入的 `agentDataSource` DataSource - -##### 2. QueryTableSchemaTool -```java -@Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name") -public String queryTableSchema(String tableName) -``` -- 返回指定表的建表SQL语句 -- 包含SQL注入防护(表名有效性验证) - -##### 3. ExecuteSqlQueryTool -```java -@Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user") -public String executeSql(String sql) -``` -- 执行SELECT查询(安全性考虑,不允许执行其他操作) -- 格式化查询结果,最多显示前20行 - -### 3. 配置体系 - -#### AgentMysqlProperties -配置文件前缀:`agent.mysql` -```yaml -agent: - mysql: - enabled: true - url: jdbc:mysql://localhost:3306/your_database - username: your_username - password: your_password - max-pool-size: 10 - min-idle: 2 -``` - -#### AgentMysqlConfig -- 创建独立的 DataSource Bean (`agentDataSource`) -- 使用 HikariCP 连接池管理 -- 与项目主数据源隔离 - -#### TableSchemaManager -- 在应用启动时初始化表结构缓存 -- 使用 `ConcurrentHashMap` 存储结构信息 -- 支持按需刷新单个表的结构 - -## 工作流程示例 - -### 用户查询: "数据库有哪些表?" - -``` -1. SupervisorAgent 接收请求 - ↓ -2. SupervisorAgent 分析请求,决定调用 SqlAgent - ↓ -3. SqlAgent 理解需求,调用 QueryAllTablesTool - ↓ -4. QueryAllTablesTool 连接数据库,获取所有表 - ↓ -5. 结果返回给 SqlAgent - ↓ -6. SqlAgent 格式化结果 - ↓ -7. SupervisorAgent 生成最终摘要 - ↓ -8. 结果通过流式处理器返回给用户 -``` - -### 用户查询: "查询 sys_user 表中有多少条记录" - -``` -1. SqlAgent 接收请求 - ↓ -2. SqlAgent 分析需求,可能先调用 QueryTableSchemaTool 了解表结构 - ↓ -3. 然后调用 ExecuteSqlQueryTool 执行 "SELECT COUNT(*) FROM sys_user" - ↓ -4. 获取查询结果并返回 -``` - -## Agentic AI 特性 - -### 自适应决策 -- Agent 能根据上下文和之前的结果决定下一步操作 -- 不是预定义的固定流程,而是动态适应 - -### 例子 -当询问"查询部门表的字段信息时": -- SqlAgent 可能先调用 `QueryTableSchemaTool` 获取建表SQL -- 如果发现需要具体的数据示例,会继续调用 `ExecuteSqlQueryTool` -- 整个决策过程由 LLM 驱动,非硬编码 - -## 安全考虑 - -1. **数据源隔离**: Agent 使用独立的数据源(agentDataSource),与主应用隔离 -2. **SQL验证**: 只允许执行 SELECT 查询 -3. **表名验证**: 表名必须通过正则表达式验证(防止SQL注入) -4. **权限限制**: 可通过 AGENT_ALLOWED_TABLES 环境变量限制可访问的表 -5. **凭证管理**: 数据库凭证通过配置文件管理,不硬编码 - diff --git a/ruoyi-modules/ruoyi-chat/pom.xml b/ruoyi-modules/ruoyi-chat/pom.xml index 716e697e..143ceb24 100644 --- a/ruoyi-modules/ruoyi-chat/pom.xml +++ b/ruoyi-modules/ruoyi-chat/pom.xml @@ -24,6 +24,14 @@ ruoyi-common-sse + + + org.ruoyi + ruoyi-common-websocket + + org.ruoyi ruoyi-common-sensitive @@ -40,6 +48,12 @@ ${langchain4j.version} + + dev.langchain4j + langchain4j-anthropic + ${langchain4j.version} + + dev.langchain4j langchain4j-ollama @@ -49,7 +63,7 @@ dev.langchain4j langchain4j-agentic - ${langchain4j.community.version} + ${langchain4j.beta.version} @@ -74,7 +88,7 @@ dev.langchain4j langchain4j-document-parser-apache-tika - ${langchain4j.community.version} + ${langchain4j.beta.version} org.apache.commons @@ -87,39 +101,39 @@ dev.langchain4j langchain4j-milvus - ${langchain4j.community.version} + ${langchain4j.beta.version} dev.langchain4j langchain4j-weaviate - ${langchain4j.community.version} + ${langchain4j.beta.version} dev.langchain4j langchain4j-qdrant - ${langchain4j.community.version} + ${langchain4j.beta.version} dev.langchain4j langchain4j-mcp - ${langchain4j.community.version} + ${langchain4j.beta.version} dev.langchain4j langchain4j-skills - ${langchain4j.community.version} + ${langchain4j.beta.version} dev.langchain4j langchain4j-experimental-skills-shell - ${langchain4j.community.version} + ${langchain4j.beta.version} @@ -160,10 +174,24 @@ ${dify.version} - - com.zaxxer - HikariCP + com.coze + coze-api + ${coze.version} + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + + + io.jsonwebtoken + jjwt-jackson + + diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChitChatAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChitChatAgent.java new file mode 100644 index 00000000..7ec1e29e --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChitChatAgent.java @@ -0,0 +1,28 @@ +package org.ruoyi.agent; + +import dev.langchain4j.agentic.Agent; +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import dev.langchain4j.service.V; + +/** + * 闲聊兜底 Agent + * 负责问候、日常闲聊和常识性问答,不挂任何工具。 + * 作为 supervisor 的默认落脚点,避免简单任务无子 Agent 可用导致输出为空。 + * + * @author ageerle@163.com + */ +public interface ChitChatAgent { + + @SystemMessage(""" + 你是一个友好、自然的对话助手,负责问候、闲聊和常识性问答。 + 要求: + - 用与用户相同的语言回答,简洁自然 + - 不要编造需要实时数据或专业工具才能得到的事实 + - 如果用户的问题实际需要联网搜索、查数据库、执行技能或生成图表,直接说明这超出你的职责, + 让用户重新描述需求 + """) + @UserMessage("{{query}}") + @Agent("闲聊兜底助手:仅用于问候、日常闲聊和不需要联网搜索、数据库、技能或图表的通用问题") + String chat(@V("query") String query); +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ShortDramaScriptAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ShortDramaScriptAgent.java new file mode 100644 index 00000000..7601feeb --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ShortDramaScriptAgent.java @@ -0,0 +1,51 @@ +package org.ruoyi.agent; + +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import dev.langchain4j.service.V; +import org.ruoyi.domain.bo.shortdrama.ShortDramaScriptResult; + +/** + * 短剧剧本打磨 Agent —— 使用 langchain4j AiServices 结构化输出 + *

+ * 框架自动生成 JSON Schema 并强制 LLM 返回符合结构的数据,无需手工 parse。 + * + * @author ageerle + */ +public interface ShortDramaScriptAgent { + + @SystemMessage(""" + 你是顶级短剧编剧和创意总监。请根据用户的一个创意想法,创作完整的短剧剧本。 + + 【核心原则 - 最高优先级】 + 1. 剧本必须完整、有张力、有画面感 + 2. 角色要有鲜明性格,不是工具人 + 3. 情节紧凑,每句台词都推动剧情 + 4. 场景描写具体,让分镜师能直接画出画面 + 5. 对话自然有力,避免废话 + + 【剧本格式】 + 使用标准剧本格式,包含以下元素: + 1. 场景头(Scene Heading):内景/外景+地点+时间,如"内景 客厅 清晨" + 2. 场景描述(Scene Description):简洁描述场景环境、布局、关键道具 + 3. 动作描述(Action):描述角色的动作、表情、行为,连续段落形式 + 4. 对话(Dialogue):角色名: 台词内容 + 5. 画外音(Voiceover):旁白、独白、回忆中的声音 + + 【剧本长度要求】 + - scriptText:1000-3000字,含完整的开场、冲突发展、高潮、结尾 + - outlineText:400-800字,概述完整故事线 + + 【角色塑造要求】 + - 每个角色要有明确的性格标签(如:霸道总裁、温柔女医、腹黑谋士) + - 角色之间要有清晰的关系和冲突 + - 对话要符合角色性格 + + 【情节要求】 + - 必须有清晰的冲突和反转 + - 情绪节奏要有起伏(紧张→舒缓→爆发) + - 结尾要有记忆点(反转/留白/情感升华) + """) + @UserMessage("用户期望项目名:{{projectName}}\n用户创意:{{idea}}") + ShortDramaScriptResult polish(@V("projectName") String projectName, @V("idea") String idea); +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java index e21b61c1..6cadc94c 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java @@ -30,6 +30,9 @@ public interface SqlAgent { - You MUST ALWAYS use queryAllTables first to query all tables in the database before executing any SQL queries - Only after understanding the database schema can you construct and execute appropriate SQL queries - This is mandatory and applies to all queries without exception + - If queryAllTables returns NO tables or an empty list, you MUST NOT call executeSql or queryTableSchema + - When no tables are available, inform the user: "当前未配置可查询的数据库表,请联系管理员配置" + - NEVER attempt to execute any SQL query (including SELECT * FROM xxx) without first confirming available tables """) @UserMessage(""" Answer the following question: {{query}} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java index 1b9079f1..a17bf545 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java @@ -8,9 +8,14 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.sql.DataSource; +import org.ruoyi.agent.manager.TableSchemaManager; import org.ruoyi.common.core.utils.SpringUtils; import org.springframework.stereotype.Component; @@ -54,6 +59,22 @@ public class ExecuteSqlQueryTool implements BuiltinToolProvider { return "Error: Only SELECT queries are allowed for security reasons"; } + // 校验表白名单:未配置表时直接拒绝,已配置则校验 SQL 中引用的表 + TableSchemaManager schemaManager = SpringUtils.getBean(TableSchemaManager.class); + List allowedTables = schemaManager.getAllowedTableNames(); + if (allowedTables.isEmpty()) { + return "Error: 当前未配置可查询的数据库表,无法执行任何SQL查询。请联系管理员配置 AGENT_ALLOWED_TABLES"; + } + Set allowedSet = allowedTables.stream() + .map(String::toLowerCase) + .collect(Collectors.toSet()); + Set referencedTables = extractTableNames(upperSql); + for (String table : referencedTables) { + if (!allowedSet.contains(table.toLowerCase())) { + return "Error: 表 " + table + " 不在允许查询的表列表中。允许查询的表: " + String.join(", ", allowedTables); + } + } + try { DataSource dataSource = getDataSource(); if (dataSource == null) { @@ -99,6 +120,21 @@ public class ExecuteSqlQueryTool implements BuiltinToolProvider { } } + /** + * 从 SQL 中提取引用的表名(FROM / JOIN 后的标识符) + * 覆盖 FROM t1, t2 / FROM t1 JOIN t2 / FROM `t1` 等常见写法 + */ + private Set extractTableNames(String upperSql) { + Set tables = new java.util.HashSet<>(); + // 匹配 FROM 或 JOIN 后面的表名(支持反引号包裹) + Pattern pattern = Pattern.compile("(?:FROM|JOIN)\\s+`?([A-Z0-9_]+)`?", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(upperSql); + while (matcher.find()) { + tables.add(matcher.group(1)); + } + return tables; + } + /** * 格式化查询结果 * 返回清晰的表格格式,展示关键数据 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/agent/SkillsPathResolver.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/agent/SkillsPathResolver.java new file mode 100644 index 00000000..83f3cf50 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/agent/SkillsPathResolver.java @@ -0,0 +1,32 @@ +package org.ruoyi.config.agent; + +import java.nio.file.Path; + +/** + * 磁盘 Skills 目录路径解析器 + *

+ * langchain4j 的 ShellSkills 通过 FileSystemSkillLoader 从磁盘加载 SKILL.md, + * 路径硬编码在 ChatServiceFacade 中。抽到此工具类供智能体管理端与聊天流程共用, + * 避免两处路径漂移。 + * + * @author ruoyi team + */ +public final class SkillsPathResolver { + + private SkillsPathResolver() { + } + + /** + * skills 目录相对项目根目录的路径 + */ + private static final String SKILLS_RELATIVE_PATH = "ruoyi-admin/src/main/resources/skills"; + + /** + * 返回磁盘 skills 目录的绝对路径 + */ + public static Path resolveSkillsPath() { + String userDir = System.getProperty("user.dir"); + return Path.of(userDir, SKILLS_RELATIVE_PATH); + } + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/constant/ShortDramaImageConstants.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/constant/ShortDramaImageConstants.java new file mode 100644 index 00000000..619b7bef --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/constant/ShortDramaImageConstants.java @@ -0,0 +1,72 @@ +package org.ruoyi.constant; + +/** + * 短剧图片资产常量 — 三视图 prompt 工程 + * + * @author ruoyi + */ +public final class ShortDramaImageConstants { + + private ShortDramaImageConstants() {} + + /** + * 角色三视图 prompt 前缀,生图时自动拼到用户 prompt 之前。 + * 把风格和构图指令放最前面,避免被角色描述稀释导致画风漂移。 + */ + public static final String CHARACTER_PROMPT_PREFIX = + "character design sheet, multiple views reference sheet, " + + "front view / side view / back view full body, " + + "clean white background, no props, no text. "; + + /** + * 角色三视图 prompt 后缀,生图时自动追加到用户 prompt 之后。 + * 左侧1/3正面特写 + 右侧2/3三视图横向排列(正面全身、侧面全身、背面全身)。 + */ + public static final String CHARACTER_PROMPT_SUFFIX = + "。角色设定图,画面分为左右两个区域:" + + "【左侧区域】占约1/3宽度,是角色的正面特写" + + "(完整正脸,最具辨识度的正面形态);" + + "【右侧区域】占约2/3宽度,是角色三视图横向排列" + + "(从左到右依次为:正面全身、侧面全身、背面全身)," + + "三视图高度一致。纯白色背景,无其他元素。"; + + /** 场景图 prompt 前缀 */ + public static final String LOCATION_PROMPT_PREFIX = "宽广空间全景,"; + + /** 场景图 prompt 后缀 */ + public static final String LOCATION_PROMPT_SUFFIX = ",禁止出现任何角色,纯背景板"; + + /** 每个资产最多保留的图片变体数量 */ + public static final int MAX_IMAGE_VARIANTS = 20; + + // ==================== 视觉风格 ==================== + + /** 项目视觉风格 → 生图 prompt 后缀映射,确保同项目所有图片风格一致 */ + public static final java.util.Map ART_STYLE_PROMPTS = java.util.Map.of( + "american-comic", "美式漫画风格,粗线条,高饱和度色彩,强烈光影对比", + "chinese-comic", "现代国漫动画风格,Chinese donghua 2D comic style,赛璐璐平涂上色,干净锐利的黑色线稿,平面化光影无真实景深,动漫人物比例(略放大双眼、修长身形),皮肤平滑无毛孔无写实肤质,国风服饰剪裁与材质细节清晰,色彩饱满通透,画面精致干净;禁止真人写实、摄影实拍、3D渲染、CGI、厚涂油画、写实皮肤纹理、景深虚化", + "japanese-anime", "现代日系动漫风格,赛璐璐上色,清晰干净的线条,视觉小说CG感,高质量2D风格", + "realistic", "真实电影级画面质感,真实现实场景,色彩饱满通透,画面干净精致,真实感" + ); + + /** 默认视觉风格 */ + public static final String DEFAULT_ART_STYLE = "realistic"; + + /** 查找 artStyle 对应的 prompt 后缀,找不到返回空字符串 */ + public static String artStylePrompt(String artStyle) { + if (artStyle == null) return ""; + return ART_STYLE_PROMPTS.getOrDefault(artStyle, ""); + } + + /** + * 由项目 id 派生一个稳定的生图随机种子。 + * 同项目内所有角色/形象共用同一颗种子,渲染基调(光影、配色、笔触)更趋一致; + * 不同项目派生不同种子,避免跨项目撞图。返回值落在 [0, 2_000_000_000),兼容各供应商。 + */ + public static Integer styleSeed(Long projectId) { + if (projectId == null) return null; + long h = projectId; + h ^= (h >>> 32); + return (int) Math.floorMod(h * 2654435761L, 2_000_000_000L); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/agent/AgentController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/agent/AgentController.java new file mode 100644 index 00000000..972b7d9d --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/agent/AgentController.java @@ -0,0 +1,130 @@ +package org.ruoyi.controller.agent; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.domain.bo.agent.AgentBo; +import org.ruoyi.domain.vo.agent.AgentVo; +import org.ruoyi.domain.vo.agent.SkillOptionVo; +import org.ruoyi.service.agent.IAgentService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 智能体管理 Controller + * + * @author ruoyi team + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/agent/agent") +public class AgentController extends BaseController { + + private final IAgentService agentService; + + /** + * 分页查询智能体列表 + */ + @SaCheckPermission("agent:agent:list") + @GetMapping("/list") + public TableDataInfo list(AgentBo bo, PageQuery pageQuery) { + return agentService.queryPageList(bo, pageQuery); + } + + /** + * 查询智能体列表(不分页,用于导出) + */ + @SaCheckPermission("agent:agent:list") + @GetMapping("/queryList") + public R> queryList(AgentBo bo) { + return R.ok(agentService.queryList(bo)); + } + + /** + * 导出智能体列表 + */ + @SaCheckPermission("agent:agent:export") + @Log(title = "智能体管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(AgentBo bo, HttpServletResponse response) { + List list = agentService.queryList(bo); + ExcelUtil.exportExcel(list, "智能体", AgentVo.class, response); + } + + /** + * 根据ID获取智能体详情 + */ + @SaCheckPermission("agent:agent:query") + @GetMapping("/{id}") + public R getInfo(@PathVariable Long id) { + return R.ok(agentService.queryById(id)); + } + + /** + * 新增智能体 + */ + @SaCheckPermission("agent:agent:add") + @Log(title = "智能体管理", businessType = BusinessType.INSERT) + @RepeatSubmit + @PostMapping + public R add(@Validated @RequestBody AgentBo bo) { + return toAjax(agentService.insertByBo(bo)); + } + + /** + * 修改智能体 + */ + @SaCheckPermission("agent:agent:edit") + @Log(title = "智能体管理", businessType = BusinessType.UPDATE) + @RepeatSubmit + @PutMapping + public R edit(@Validated @RequestBody AgentBo bo) { + return toAjax(agentService.updateByBo(bo)); + } + + /** + * 删除智能体 + */ + @SaCheckPermission("agent:agent:remove") + @Log(title = "智能体管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@PathVariable Long[] ids) { + return toAjax(agentService.deleteByIds(List.of(ids))); + } + + /** + * 用户端聊天页智能体下拉选项(启用状态,不需权限校验) + */ + @GetMapping("/agentOptions") + public R> agentOptions() { + return R.ok(agentService.queryEnabledOptions()); + } + + /** + * 列出磁盘上可用的 Skills(供管理端表单勾选) + */ + @SaCheckPermission("agent:agent:list") + @GetMapping("/skillOptions") + public R> skillOptions() { + return R.ok(agentService.listSkillOptions()); + } + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java index adf4322b..42d3b5fd 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/ChatModelController.java @@ -8,7 +8,9 @@ import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo; +import org.ruoyi.common.chat.domain.bo.chat.ModelBatchKeyBo; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.enums.ChatModeType; import org.ruoyi.enums.ModelType; import org.springframework.web.bind.annotation.*; @@ -54,7 +56,9 @@ public class ChatModelController extends BaseController { */ @GetMapping("/modelList") public R> modelList(ChatModelBo bo) { - bo.setCategory(ModelType.CHAT.getKey()); + if (StringUtils.isBlank(bo.getCategory())) { + bo.setCategory(ModelType.CHAT.getKey()); + } return R.ok(chatModelService.queryList(bo)); } @@ -118,6 +122,17 @@ public class ChatModelController extends BaseController { return toAjax(chatModelService.updateByBo(bo)); } + /** + * 按厂商批量更新密钥 + */ + @SaCheckPermission("system:model:edit") + @Log(title = "模型管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/batchKeyByProvider") + public R batchKeyByProvider(@Validated @RequestBody ModelBatchKeyBo bo) { + return toAjax(chatModelService.updateApiKeyByProvider(bo.getProviderCode(), bo.getApiKey())); + } + /** * 删除模型管理 * diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/MediaGenerationController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/MediaGenerationController.java new file mode 100644 index 00000000..b2f032ab --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/chat/MediaGenerationController.java @@ -0,0 +1,141 @@ +package org.ruoyi.controller.chat; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.entity.audio.AudioContext; +import org.ruoyi.common.chat.entity.image.ImageContext; +import org.ruoyi.common.chat.entity.media.MediaGenerationResponse; +import org.ruoyi.common.chat.entity.video.VideoContext; +import org.ruoyi.common.chat.factory.AudioServiceFactory; +import org.ruoyi.common.chat.factory.ImageServiceFactory; +import org.ruoyi.common.chat.factory.VideoServiceFactory; +import org.ruoyi.common.chat.service.chat.IChatModelService; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.domain.bo.media.ImageGenerationRequest; +import org.ruoyi.domain.bo.media.SpeechGenerationRequest; +import org.ruoyi.domain.bo.media.VideoGenerationRequest; +import org.ruoyi.enums.ModelType; +import org.ruoyi.service.media.AtlasPredictionService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/media") +public class MediaGenerationController { + + private final IChatModelService chatModelService; + private final AudioServiceFactory audioServiceFactory; + private final ImageServiceFactory imageServiceFactory; + private final VideoServiceFactory videoServiceFactory; + private final AtlasPredictionService atlasPredictionService; + + @PostMapping("/speech") + public R speech(@Valid @RequestBody SpeechGenerationRequest request) { + ChatModelVo model = loadModel(request.getModel(), ModelType.AUDIO.getKey()); + MediaGenerationResponse response = audioServiceFactory.getOriginalService(model.getProviderCode()) + .generateSpeech(AudioContext.builder() + .chatModelVo(model) + .input(request.getInput()) + .voice(request.getVoice()) + .responseFormat(request.getResponseFormat()) + .speed(request.getSpeed()) + .instructions(request.getInstructions()) + .build()); + return R.ok(response); + } + + @PostMapping("/image") + public R image(@Valid @RequestBody ImageGenerationRequest request) { + ChatModelVo model = loadModel(request.getModel(), ModelType.IMAGE.getKey()); + String result = imageServiceFactory.getOriginalService(model.getProviderCode()) + .generateImage(ImageContext.builder() + .chatModelVo(model) + .prompt(request.getPrompt()) + .size(request.getSize()) + .seed(request.getSeed()) + .build()); + return R.ok(toImageResponse(result)); + } + + @PostMapping("/video") + public R video(@Valid @RequestBody VideoGenerationRequest request) { + ChatModelVo model = loadModel(request.getModel(), ModelType.VIDEO.getKey()); + MediaGenerationResponse response = videoServiceFactory.getOriginalService(model.getProviderCode()) + .generateVideo(VideoContext.builder() + .chatModelVo(model) + .prompt(request.getPrompt()) + .size(request.getSize()) + .seconds(request.getSeconds()) + .quality(request.getQuality()) + .build()); + return R.ok(response); + } + + @GetMapping("/video") + public R videoResult(@NotBlank(message = "模型不能为空") @RequestParam String model, + @NotBlank(message = "videoId不能为空") @RequestParam String videoId) { + ChatModelVo chatModelVo = loadModel(model, ModelType.VIDEO.getKey()); + MediaGenerationResponse response = videoServiceFactory.getOriginalService(chatModelVo.getProviderCode()) + .retrieveVideo(VideoContext.builder() + .chatModelVo(chatModelVo) + .prompt("retrieve") + .videoId(videoId) + .build()); + return R.ok(response); + } + + @GetMapping("/prediction") + public R prediction(@NotBlank(message = "模型不能为空") @RequestParam String model, + @NotBlank(message = "predictionId不能为空") @RequestParam String predictionId) { + ChatModelVo chatModelVo = chatModelService.selectModelByName(model); + if (chatModelVo == null) { + throw new IllegalArgumentException("未找到模型配置: " + model); + } + return R.ok(atlasPredictionService.retrieve(chatModelVo, predictionId)); + } + + private ChatModelVo loadModel(String modelName, String category) { + ChatModelVo model = chatModelService.selectModelByName(modelName); + if (model == null) { + throw new IllegalArgumentException("未找到模型配置: " + modelName); + } + if (!category.equals(model.getCategory())) { + throw new IllegalArgumentException("模型分类不匹配,期望: " + category + ", 实际: " + model.getCategory()); + } + return model; + } + + private MediaGenerationResponse toImageResponse(String result) { + if (result != null && result.startsWith("{")) { + try { + return atlasPredictionService.toResponse(result, "image"); + } catch (Exception e) { + throw new RuntimeException("图片生成响应解析失败: " + e.getMessage(), e); + } + } + if (result != null && result.startsWith("data:")) { + String mimeType = result.substring("data:".length(), result.indexOf(";base64,")); + String b64 = result.substring(result.indexOf(";base64,") + ";base64,".length()); + return MediaGenerationResponse.builder() + .type("image") + .mimeType(mimeType) + .b64Json(b64) + .dataUrl(result) + .build(); + } + return MediaGenerationResponse.builder() + .type("image") + .mimeType("image/png") + .url(result) + .build(); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/coding/CodingController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/coding/CodingController.java new file mode 100644 index 00000000..dc7d1bdf --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/coding/CodingController.java @@ -0,0 +1,95 @@ +package org.ruoyi.controller.coding; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo; +import org.ruoyi.common.chat.service.chat.IChatModelService; +import org.ruoyi.domain.bo.coding.CodingRequestBo; +import org.ruoyi.enums.ModelType; +import org.ruoyi.service.coding.CodingWorkspaceService; +import org.ruoyi.service.coding.ICodingService; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; + +/** + * 编程能力接口(B 路径,不走 Supervisor 调度) + * + *

第一阶段 {@code /coding/**} 在 {@code application.yml} 的 security.excludes 中, + * 免鉴权直连。Controller 只做参数绑定 + 同步取 userId(Sa-Token 异步上下文丢失, + * 见 SecurityConfig 注释)+ 转发 Service。 + * + * @author ageerle + */ +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/coding") +public class CodingController { + + private final ICodingService codingService; + private final CodingWorkspaceService workspaceService; + private final IChatModelService chatModelService; + + /** + * 编程对话(SSE 流式) + * + * @param bo 请求参数(prompt / model / workspacePath) + * @return SseEmitter + */ + @PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter chat(@Valid @RequestBody CodingRequestBo bo) { + // 同步线程取 userId;第一阶段免鉴权,可能为 null + Long userId = LoginHelper.getUserId(); + return codingService.chat(bo, userId); + } + + @GetMapping("/workspace") + public R workspace( + @RequestParam(required = false) String workspacePath) throws Exception { + return R.ok(workspaceService.list(workspacePath)); + } + + @GetMapping("/models") + public R> models() { + // 编程对话只能用聊天模型,按 category=chat 过滤 + ChatModelBo bo = new ChatModelBo(); + bo.setCategory(ModelType.CHAT.getKey()); + List models = chatModelService.queryList(bo).stream() + .map(model -> new ModelOption(model.getId(), model.getModelName(), model.getProviderCode())) + .toList(); + return R.ok(models); + } + + @GetMapping("/file") + public R file( + @RequestParam(required = false) String workspacePath, + @RequestParam String path) throws Exception { + return R.ok(workspaceService.read(workspacePath, path)); + } + + @PutMapping("/file") + public R saveFile(@RequestBody FileWriteRequest request) throws Exception { + return R.ok(workspaceService.write(request.workspacePath(), request.path(), request.content())); + } + + @PostMapping("/command") + public R command(@RequestBody CommandRequest request) { + return R.ok(workspaceService.execute(request.workspacePath(), request.command())); + } + + public record FileWriteRequest(String workspacePath, String path, String content) { } + public record CommandRequest(String workspacePath, String command) { } + public record ModelOption(Long id, String name, String provider) { } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeAttachController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeAttachController.java index e2e1b4e8..ea52cdf9 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeAttachController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeAttachController.java @@ -9,6 +9,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission; import org.ruoyi.domain.bo.knowledge.KnowledgeAttachBo; import org.ruoyi.domain.bo.knowledge.KnowledgeInfoUploadBo; import org.ruoyi.domain.vo.knowledge.KnowledgeAttachVo; +import org.ruoyi.domain.vo.knowledge.KnowledgeReparseVo; import org.ruoyi.service.knowledge.IKnowledgeAttachService; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; @@ -106,7 +107,10 @@ public class KnowledgeAttachController extends BaseController { /** * 上传知识库附件 + * 注意:multipart 上传不能加 @RepeatSubmit(其参数序列化不支持 MultipartFile) */ + @SaCheckPermission("system:attach:add") + @Log(title = "知识库附件", businessType = BusinessType.INSERT) @PostMapping(value = "/upload") public R upload(KnowledgeInfoUploadBo bo){ knowledgeAttachService.upload(bo); @@ -118,9 +122,20 @@ public class KnowledgeAttachController extends BaseController { * * @param id 附件ID */ + @SaCheckPermission("system:attach:edit") + @Log(title = "知识库附件", businessType = BusinessType.UPDATE) @PostMapping("/parse/{id}") + @RepeatSubmit() public R parse(@PathVariable Long id) { knowledgeAttachService.parse(id); return R.ok(); } + + @SaCheckPermission("system:attach:edit") + @Log(title = "知识库附件批量重新解析", businessType = BusinessType.UPDATE) + @PostMapping("/reparse/knowledge/{knowledgeId}") + @RepeatSubmit() + public R reparseKnowledge(@PathVariable Long knowledgeId) { + return R.ok(knowledgeAttachService.reparseKnowledge(knowledgeId)); + } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeFragmentController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeFragmentController.java index d739a30f..c3a5a13e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeFragmentController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/knowledge/KnowledgeFragmentController.java @@ -107,7 +107,9 @@ public class KnowledgeFragmentController extends BaseController { /** * 检索测试 */ + @SaCheckPermission("system:fragment:list") @PostMapping("/retrieval") + @RepeatSubmit() public R> retrieval(@RequestBody KnowledgeFragmentBo bo) { return R.ok(knowledgeFragmentService.retrieval(bo)); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/shortdrama/ShortDramaController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/shortdrama/ShortDramaController.java new file mode 100644 index 00000000..6ecd69a3 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/shortdrama/ShortDramaController.java @@ -0,0 +1,408 @@ +package org.ruoyi.controller.shortdrama; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.chat.entity.media.MediaGenerationResponse; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.OssService; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.domain.bo.shortdrama.ShortDramaCharacterBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaCharacterAppearanceBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaComposeVideoBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaAudioBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaLocationBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaProjectBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaScriptBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaStoryboardBo; +import org.ruoyi.domain.bo.shortdrama.ShortDramaIdeaBo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaCharacterVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaCharacterAppearanceVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaComposeVideoVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaDetailVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaAudioVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaLocationVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaProjectVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaScriptVo; +import org.ruoyi.domain.vo.shortdrama.ShortDramaStoryboardVo; +import org.ruoyi.service.shortdrama.IShortDramaService; +import org.ruoyi.service.shortdrama.IShortDramaVideoComposeService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/short-drama") +public class ShortDramaController { + + private final IShortDramaService shortDramaService; + + private final IShortDramaVideoComposeService videoComposeService; + + private final OssService ossService; + + // ==================== 项目 ==================== + + @GetMapping("/projects") + public R> projects() { + return R.ok(shortDramaService.listProjects(LoginHelper.getUserId())); + } + + @GetMapping("/{projectId}") + public R detail(@PathVariable Long projectId) { + return R.ok(shortDramaService.getDetail(projectId, LoginHelper.getUserId())); + } + + @PostMapping("/create-from-idea") + public R createFromIdea(@Valid @RequestBody ShortDramaIdeaBo bo) { + return R.ok(shortDramaService.createFromIdea(bo, LoginHelper.getUserId())); + } + + /** SSE 流式创建:逐阶段推送进度,避免用户等待焦虑 */ + @PostMapping("/create-from-idea/stream") + public SseEmitter createFromIdeaStream(@Valid @RequestBody ShortDramaIdeaBo bo) { + return shortDramaService.createFromIdeaStream(bo, LoginHelper.getUserId()); + } + + @PostMapping("/project") + public R saveProject(@Valid @RequestBody ShortDramaProjectBo bo) { + return R.ok(shortDramaService.saveProject(bo, LoginHelper.getUserId())); + } + + @PutMapping("/project") + public R updateProject(@Valid @RequestBody ShortDramaProjectBo bo) { + return R.ok(shortDramaService.saveProject(bo, LoginHelper.getUserId())); + } + + @DeleteMapping("/project/{projectId}") + public R deleteProject(@NotNull @PathVariable Long projectId) { + shortDramaService.deleteProject(projectId, LoginHelper.getUserId()); + return R.ok(); + } + + // ==================== 剧本 ==================== + + @PostMapping("/script") + public R saveScript(@Valid @RequestBody ShortDramaScriptBo bo) { + return R.ok(shortDramaService.saveScript(bo, LoginHelper.getUserId())); + } + + // ==================== 分镜 ==================== + + @PostMapping("/storyboards/generate") + public R> generate(@NotNull @RequestParam Long projectId, + @NotNull @RequestParam Long scriptId, + @RequestParam(required = false) String model) { + return R.ok(shortDramaService.generateStoryboards(projectId, scriptId, model, LoginHelper.getUserId())); + } + + @PostMapping("/storyboard") + public R saveStoryboard(@Valid @RequestBody ShortDramaStoryboardBo bo) { + return R.ok(shortDramaService.saveStoryboard(bo, LoginHelper.getUserId())); + } + + @PostMapping("/storyboard/{storyboardId}/generate-video") + public R generateVideo(@NotNull @PathVariable Long storyboardId, + @NotBlank @RequestParam String model) { + return R.ok(shortDramaService.generateVideo(storyboardId, model, LoginHelper.getUserId())); + } + + @GetMapping("/storyboard/{storyboardId}/video-result") + public R videoResult(@NotNull @PathVariable Long storyboardId, + @NotBlank @RequestParam String model) { + return R.ok(shortDramaService.retrieveVideo(storyboardId, model, LoginHelper.getUserId())); + } + + @PostMapping("/{projectId}/generate-all-videos") + public R> generateAllVideos(@NotNull @PathVariable Long projectId, + @NotBlank @RequestParam String model) { + return R.ok(shortDramaService.generateAllVideos(projectId, model, LoginHelper.getUserId())); + } + + @PostMapping("/{projectId}/compose-video") + public R composeVideo(@NotNull @PathVariable Long projectId, + @Valid @RequestBody ShortDramaComposeVideoBo bo) { + return R.ok(videoComposeService.composeVideo(projectId, bo, LoginHelper.getUserId())); + } + + @GetMapping("/{projectId}/compose-video") + public R getComposedVideo(@NotNull @PathVariable Long projectId) { + return R.ok(videoComposeService.getComposedVideo(projectId, LoginHelper.getUserId())); + } + + @GetMapping("/{projectId}/compose-video/download") + public void downloadComposedVideo(@NotNull @PathVariable Long projectId, HttpServletResponse response) + throws IOException { + ShortDramaComposeVideoVo composition = videoComposeService.getComposedVideo(projectId, LoginHelper.getUserId()); + if (composition == null || !"done".equals(composition.getStatus())) { + throw new ServiceException("成片尚未生成完成"); + } + if (composition.getVideoOssId() != null) { + ossService.downloadFile(composition.getVideoOssId(), response); + return; + } + Path localVideo = videoComposeService.getLocalComposedVideo(projectId, LoginHelper.getUserId()); + response.setContentType("video/mp4"); + response.setHeader("Content-Disposition", "attachment; filename=short-drama-" + projectId + ".mp4"); + response.setContentLengthLong(Files.size(localVideo)); + Files.copy(localVideo, response.getOutputStream()); + } + + // ==================== 阶段式流水线端点 ==================== + + /** Phase 1: 剧本打磨 */ + @PostMapping("/{projectId}/polish-script") + public R polishScript(@NotNull @PathVariable Long projectId) { + return R.ok(shortDramaService.polishScript(projectId, LoginHelper.getUserId())); + } + + // ==================== 资产分析 ==================== + + /** Phase 2: 资产分析(角色+场景提取) */ + @PostMapping("/{projectId}/analyze-assets") + public R analyzeAssets(@NotNull @PathVariable Long projectId, + @NotNull @RequestParam Long scriptId) { + return R.ok(shortDramaService.analyzeAssets(projectId, scriptId, LoginHelper.getUserId())); + } + + // ==================== 分镜流水线 ==================== + + /** Phase 3-6: 分镜规划+摄影规则+表演指导+分镜细化 */ + @PostMapping("/{projectId}/plan-storyboard") + public R> planStoryboard(@NotNull @PathVariable Long projectId, + @NotNull @RequestParam Long scriptId, + @RequestParam(required = false) String model) { + return R.ok(shortDramaService.planStoryboard(projectId, scriptId, model, LoginHelper.getUserId())); + } + + /** Phase 3-6: SSE 流式生成分镜,持续推送规划和细化进度 */ + @PostMapping("/{projectId}/plan-storyboard/stream") + public SseEmitter planStoryboardStream(@NotNull @PathVariable Long projectId, + @NotNull @RequestParam Long scriptId, + @RequestParam(required = false) String model) { + return shortDramaService.planStoryboardStream(projectId, scriptId, model, LoginHelper.getUserId()); + } + + /** Phase 4: 重新生成摄影规则 */ + @PostMapping("/{projectId}/photography-rules") + public R> generatePhotographyRules(@NotNull @PathVariable Long projectId, + @NotNull @RequestParam Long scriptId) { + return R.ok(shortDramaService.generatePhotographyRules(projectId, scriptId, LoginHelper.getUserId())); + } + + /** Phase 5: 重新生成表演指导 */ + @PostMapping("/{projectId}/acting-directions") + public R> generateActingDirections(@NotNull @PathVariable Long projectId, + @NotNull @RequestParam Long scriptId) { + return R.ok(shortDramaService.generateActingDirections(projectId, scriptId, LoginHelper.getUserId())); + } + + // ==================== 角色管理 ==================== + + @PostMapping("/character") + public R saveCharacter(@Valid @RequestBody ShortDramaCharacterBo bo) { + return R.ok(shortDramaService.saveCharacter(bo, LoginHelper.getUserId())); + } + + @PutMapping("/character") + public R updateCharacter(@Valid @RequestBody ShortDramaCharacterBo bo) { + return R.ok(shortDramaService.saveCharacter(bo, LoginHelper.getUserId())); + } + + @DeleteMapping("/character/{characterId}") + public R deleteCharacter(@NotNull @PathVariable Long characterId) { + shortDramaService.deleteCharacter(characterId, LoginHelper.getUserId()); + return R.ok(); + } + + @PostMapping("/character/{characterId}/generate-image") + public R generateCharacterImage(@NotNull @PathVariable Long characterId, + @NotBlank @RequestParam String model, + @RequestParam(required = false) String referenceImageUrl) { + return R.ok(shortDramaService.generateCharacterImage(characterId, model, referenceImageUrl, LoginHelper.getUserId())); + } + + // ==================== 角色形象管理 ==================== + + @PostMapping("/character-appearance") + public R saveAppearance(@Valid @RequestBody ShortDramaCharacterAppearanceBo bo) { + return R.ok(shortDramaService.saveAppearance(bo, LoginHelper.getUserId())); + } + + @PutMapping("/character-appearance") + public R updateAppearance(@Valid @RequestBody ShortDramaCharacterAppearanceBo bo) { + return R.ok(shortDramaService.saveAppearance(bo, LoginHelper.getUserId())); + } + + @DeleteMapping("/character-appearance/{appearanceId}") + public R deleteAppearance(@NotNull @PathVariable Long appearanceId) { + shortDramaService.deleteAppearance(appearanceId, LoginHelper.getUserId()); + return R.ok(); + } + + @PostMapping("/character-appearance/{appearanceId}/generate-image") + public R generateAppearanceImage(@NotNull @PathVariable Long appearanceId, + @NotBlank @RequestParam String model, + @RequestParam(required = false) String referenceImageUrl) { + return R.ok(shortDramaService.generateAppearanceImage(appearanceId, model, referenceImageUrl, LoginHelper.getUserId())); + } + + @PostMapping("/character-appearance/{appearanceId}/regenerate") + public R regenerateAppearanceImage(@NotNull @PathVariable Long appearanceId, + @NotBlank @RequestParam String model, + @RequestParam(required = false) String referenceImageUrl) { + return R.ok(shortDramaService.regenerateAppearanceImage(appearanceId, model, referenceImageUrl, LoginHelper.getUserId())); + } + + @PostMapping("/character-appearance/{appearanceId}/select-image") + public R selectAppearanceImage(@NotNull @PathVariable Long appearanceId, + @NotNull @RequestParam Integer index) { + return R.ok(shortDramaService.selectAppearanceImage(appearanceId, index, LoginHelper.getUserId())); + } + + @DeleteMapping("/character-appearance/{appearanceId}/image") + public R deleteAppearanceImage(@NotNull @PathVariable Long appearanceId, + @NotNull @RequestParam Integer index) { + return R.ok(shortDramaService.deleteAppearanceImage(appearanceId, index, LoginHelper.getUserId())); + } + + @PostMapping("/character-appearance/{appearanceId}/undo-image") + public R undoAppearanceImage(@NotNull @PathVariable Long appearanceId) { + return R.ok(shortDramaService.undoAppearanceImage(appearanceId, LoginHelper.getUserId())); + } + + // ==================== 场景管理 ==================== + + @PostMapping("/location") + public R saveLocation(@Valid @RequestBody ShortDramaLocationBo bo) { + return R.ok(shortDramaService.saveLocation(bo, LoginHelper.getUserId())); + } + + @PutMapping("/location") + public R updateLocation(@Valid @RequestBody ShortDramaLocationBo bo) { + return R.ok(shortDramaService.saveLocation(bo, LoginHelper.getUserId())); + } + + @DeleteMapping("/location/{locationId}") + public R deleteLocation(@NotNull @PathVariable Long locationId) { + shortDramaService.deleteLocation(locationId, LoginHelper.getUserId()); + return R.ok(); + } + + @PostMapping("/location/{locationId}/generate-image") + public R generateLocationImage(@NotNull @PathVariable Long locationId, + @NotBlank @RequestParam String model, + @RequestParam(required = false) String referenceImageUrl) { + return R.ok(shortDramaService.generateLocationImage(locationId, model, referenceImageUrl, LoginHelper.getUserId())); + } + + @PostMapping("/location/{locationId}/regenerate") + public R regenerateLocationImage(@NotNull @PathVariable Long locationId, + @NotBlank @RequestParam String model, + @RequestParam(required = false) String referenceImageUrl) { + return R.ok(shortDramaService.regenerateLocationImage(locationId, model, referenceImageUrl, LoginHelper.getUserId())); + } + + @PostMapping("/location/{locationId}/select-image") + public R selectLocationImage(@NotNull @PathVariable Long locationId, + @NotNull @RequestParam Integer index) { + return R.ok(shortDramaService.selectLocationImage(locationId, index, LoginHelper.getUserId())); + } + + @DeleteMapping("/location/{locationId}/image") + public R deleteLocationImage(@NotNull @PathVariable Long locationId, + @NotNull @RequestParam Integer index) { + return R.ok(shortDramaService.deleteLocationImage(locationId, index, LoginHelper.getUserId())); + } + + @PostMapping("/location/{locationId}/undo-image") + public R undoLocationImage(@NotNull @PathVariable Long locationId) { + return R.ok(shortDramaService.undoLocationImage(locationId, LoginHelper.getUserId())); + } + + // ==================== 语音资产管理 ==================== + + @PostMapping("/audio") + public R saveAudio(@Valid @RequestBody ShortDramaAudioBo bo) { + return R.ok(shortDramaService.saveAudio(bo, LoginHelper.getUserId())); + } + + @PutMapping("/audio") + public R updateAudio(@Valid @RequestBody ShortDramaAudioBo bo) { + return R.ok(shortDramaService.saveAudio(bo, LoginHelper.getUserId())); + } + + @DeleteMapping("/audio/{audioId}") + public R deleteAudio(@NotNull @PathVariable Long audioId) { + shortDramaService.deleteAudio(audioId, LoginHelper.getUserId()); + return R.ok(); + } + + @GetMapping("/audio/list") + public R> listAudios(@NotNull @RequestParam Long projectId) { + return R.ok(shortDramaService.listAudios(projectId, LoginHelper.getUserId())); + } + + @PostMapping("/audio/{audioId}/generate-speech") + public R generateAudio(@NotNull @PathVariable Long audioId, + @NotBlank @RequestParam String model) { + return R.ok(shortDramaService.generateAudio(audioId, model, LoginHelper.getUserId())); + } + + // ==================== 异步图片生成(轮询进度) ==================== + + /** 上传本地照片到图片供应商,返回当前生成会话使用的临时 URL。 */ + @PostMapping(value = "/image/upload-reference", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R uploadReferenceImage(@RequestPart("file") MultipartFile file, + @NotBlank @RequestParam String model) { + String temporaryUrl = shortDramaService.uploadReferenceImage(file, model, LoginHelper.getUserId()); + return R.ok("上传成功", temporaryUrl); + } + + /** 异步启动图片生成,返回 predictionId 供前端轮询 */ + @PostMapping("/image/start") + public R startImage(@NotBlank @RequestParam String assetType, + @NotNull @RequestParam Long assetId, + @NotBlank @RequestParam String model, + @RequestParam(required = false) String referenceImageUrl) { + return R.ok(shortDramaService.startImageGeneration(assetType, assetId, model, referenceImageUrl, LoginHelper.getUserId())); + } + + /** 轮询确认形象图片并保存 */ + @PostMapping("/character-appearance/{id}/confirm-image") + public R confirmAppearanceImage(@NotNull @PathVariable Long id, + @NotBlank @RequestParam String predictionId, + @NotBlank @RequestParam String model) { + return R.ok(shortDramaService.confirmAppearanceImage(id, predictionId, model, LoginHelper.getUserId())); + } + + /** 轮询确认场景图片并保存 */ + @PostMapping("/location/{id}/confirm-image") + public R confirmLocationImage(@NotNull @PathVariable Long id, + @NotBlank @RequestParam String predictionId, + @NotBlank @RequestParam String model) { + return R.ok(shortDramaService.confirmLocationImage(id, predictionId, model, LoginHelper.getUserId())); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/agent/AgentBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/agent/AgentBo.java new file mode 100644 index 00000000..4146dde0 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/agent/AgentBo.java @@ -0,0 +1,95 @@ +package org.ruoyi.domain.bo.agent; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.agent.Agent; + +import java.io.Serial; +import java.util.List; + +/** + * 智能体业务对象 + * + * @author ruoyi team + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = Agent.class, reverseConvertGenerate = false) +public class AgentBo extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 智能体ID + */ + private Long id; + + /** + * 智能体名称 + */ + @NotBlank(message = "智能体名称不能为空") + @Size(min = 0, max = 200, message = "智能体名称不能超过{max}个字符") + private String agentName; + + /** + * 智能体描述 + */ + private String agentDescribe; + + /** + * 展示图标/头像URL + */ + private String agentShow; + + /** + * 绑定的聊天模型ID + */ + @NotNull(message = "绑定模型不能为空") + private Long modelId; + + /** + * 是否启用深度思考:0 否 1 是 + */ + private String enableThinking; + + /** + * 自定义系统提示词 + */ + private String systemPrompt; + + /** + * 关联MCP工具ID列表 + */ + @AutoMapping(target = "mcpToolIds", expression = "java(org.ruoyi.common.json.utils.JsonUtils.toJsonString(source.getMcpToolIds()))") + private List mcpToolIds; + + /** + * 关联磁盘技能名列表 + */ + @AutoMapping(target = "skillNames", expression = "java(org.ruoyi.common.json.utils.JsonUtils.toJsonString(source.getSkillNames()))") + private List skillNames; + + /** + * 关联知识库ID列表 + */ + @AutoMapping(target = "knowledgeIds", expression = "java(org.ruoyi.common.json.utils.JsonUtils.toJsonString(source.getKnowledgeIds()))") + private List knowledgeIds; + + /** + * 状态:0 正常 1 停用 + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/coding/CodingRequestBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/coding/CodingRequestBo.java new file mode 100644 index 00000000..4c479498 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/coding/CodingRequestBo.java @@ -0,0 +1,30 @@ +package org.ruoyi.domain.bo.coding; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 编程能力对话请求 + * + * @author ageerle + */ +@Data +public class CodingRequestBo { + + /** + * 用户指令 + */ + @NotBlank(message = "prompt 不能为空") + private String prompt; + + /** + * 模型名称(走 IChatModelService.selectModelByName) + */ + @NotBlank(message = "model 不能为空") + private String model; + + /** + * 工作目录,可选;为空时默认指向 ruoyi-copilot 前端项目 + */ + private String workspacePath; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/ImageGenerationRequest.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/ImageGenerationRequest.java new file mode 100644 index 00000000..b3c44665 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/ImageGenerationRequest.java @@ -0,0 +1,22 @@ +package org.ruoyi.domain.bo.media; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.Data; + +@Data +public class ImageGenerationRequest { + + @NotBlank(message = "模型不能为空") + private String model; + + @NotBlank(message = "提示词不能为空") + private String prompt; + + private String size; + + @Min(value = 0, message = "随机种子不能小于0") + @Max(value = 2147483647, message = "随机种子不能大于2147483647") + private Integer seed; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/SpeechGenerationRequest.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/SpeechGenerationRequest.java new file mode 100644 index 00000000..1683bb6a --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/SpeechGenerationRequest.java @@ -0,0 +1,22 @@ +package org.ruoyi.domain.bo.media; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class SpeechGenerationRequest { + + @NotBlank(message = "模型不能为空") + private String model; + + @NotBlank(message = "输入文本不能为空") + private String input; + + private String voice; + + private String responseFormat; + + private Double speed; + + private String instructions; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/VideoGenerationRequest.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/VideoGenerationRequest.java new file mode 100644 index 00000000..df2f45d5 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/media/VideoGenerationRequest.java @@ -0,0 +1,20 @@ +package org.ruoyi.domain.bo.media; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class VideoGenerationRequest { + + @NotBlank(message = "模型不能为空") + private String model; + + @NotBlank(message = "提示词不能为空") + private String prompt; + + private String size; + + private Integer seconds; + + private String quality; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaAudioBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaAudioBo.java new file mode 100644 index 00000000..fd00de0c --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaAudioBo.java @@ -0,0 +1,37 @@ +package org.ruoyi.domain.bo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.shortdrama.ShortDramaAudio; + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ShortDramaAudio.class, reverseConvertGenerate = false) +public class ShortDramaAudioBo extends BaseEntity { + + private Long id; + + @NotNull(message = "项目ID不能为空") + private Long projectId; + + @NotBlank(message = "语音资产名称不能为空") + private String name; + + @NotBlank(message = "语音类型不能为空") + @Pattern(regexp = "narration|dialogue", message = "语音类型只能是 narration 或 dialogue") + private String audioType; + + @NotBlank(message = "语音文案不能为空") + private String text; + + /** 音色(生成语音时使用,可空,空则用模型默认) */ + private String voice; + + /** 对白关联的分镜ID(旁白类型留空) */ + private Long linkedStoryboardId; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaCharacterAppearanceBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaCharacterAppearanceBo.java new file mode 100644 index 00000000..e14b7c09 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaCharacterAppearanceBo.java @@ -0,0 +1,37 @@ +package org.ruoyi.domain.bo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.shortdrama.ShortDramaCharacterAppearance; + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ShortDramaCharacterAppearance.class, reverseConvertGenerate = false) +public class ShortDramaCharacterAppearanceBo extends BaseEntity { + + private Long id; + + private Long characterId; + + private Integer appearanceIndex; + + private String changeReason; + + private String description; + + private String referenceImageUrl; + + private String imageUrls; + + private String imageDescriptions; + + private Integer selectedImageIndex; + + private String previousImageUrls; + + private String previousDescriptions; + + private String voice; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaCharacterBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaCharacterBo.java new file mode 100644 index 00000000..bc988198 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaCharacterBo.java @@ -0,0 +1,37 @@ +package org.ruoyi.domain.bo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.shortdrama.ShortDramaCharacter; + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ShortDramaCharacter.class, reverseConvertGenerate = false) +public class ShortDramaCharacterBo extends BaseEntity { + + private Long id; + + private Long projectId; + + private String name; + + private String aliases; + + private String introduction; + + private String roleLevel; + + private String gender; + + private String ageRange; + + private String personalityTags; + + private Integer costumeTier; + + private String visualDescription; + + private String referenceImageUrl; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaComposeVideoBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaComposeVideoBo.java new file mode 100644 index 00000000..d1c7e36b --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaComposeVideoBo.java @@ -0,0 +1,35 @@ +package org.ruoyi.domain.bo.shortdrama; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Data +public class ShortDramaComposeVideoBo { + + @NotBlank(message = "转场类型不能为空") + @Pattern(regexp = "none|dissolve|fade|slide", message = "不支持的转场类型") + private String transitionType = "fade"; + + @NotNull(message = "转场时长不能为空") + @DecimalMin(value = "0.0", message = "转场时长不能小于0秒") + private BigDecimal transitionDurationSeconds = new BigDecimal("0.3"); + + @NotBlank(message = "成片画幅不能为空") + @Pattern(regexp = "9:16|16:9|4:3|3:4|1:1|21:9", message = "不支持的成片画幅") + private String aspectRatio = "9:16"; + @Size(min = 2, message = "至少选择2个分镜视频") + private List storyboardIds; + + /** 旁白语音资产ID(可选,未传则不混入旁白) */ + private Long narrationAudioId; + + /** 是否加水印(null 时用后端默认配置 ruoyi-ai) */ + private Boolean watermark; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaIdeaBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaIdeaBo.java new file mode 100644 index 00000000..b6eeb4f7 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaIdeaBo.java @@ -0,0 +1,20 @@ +package org.ruoyi.domain.bo.shortdrama; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class ShortDramaIdeaBo { + + @NotBlank(message = "创意想法不能为空") + private String idea; + + @NotBlank(message = "模型不能为空") + private String model; + + private String projectName; + + private String artStyle; + + private String aspectRatio; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaLocationBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaLocationBo.java new file mode 100644 index 00000000..23f2aa77 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaLocationBo.java @@ -0,0 +1,41 @@ +package org.ruoyi.domain.bo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.shortdrama.ShortDramaLocation; + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ShortDramaLocation.class, reverseConvertGenerate = false) +public class ShortDramaLocationBo extends BaseEntity { + + private Long id; + + private Long projectId; + + private String name; + + private String summary; + + private Boolean hasCrowd; + + private String crowdDescription; + + private String availableSlots; + + private String descriptions; + + private String referenceImageUrl; + + private String imageUrls; + + private String imageDescriptions; + + private Integer selectedImageIndex; + + private String previousImageUrls; + + private String previousDescriptions; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaProjectBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaProjectBo.java new file mode 100644 index 00000000..ce73bec3 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaProjectBo.java @@ -0,0 +1,27 @@ +package org.ruoyi.domain.bo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.shortdrama.ShortDramaProject; + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ShortDramaProject.class, reverseConvertGenerate = false) +public class ShortDramaProjectBo extends BaseEntity { + + private Long id; + + private Long userId; + + private String projectName; + + private String description; + + private String status; + + private String artStyle; + + private String composeAspectRatio; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaScriptBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaScriptBo.java new file mode 100644 index 00000000..c4252441 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaScriptBo.java @@ -0,0 +1,27 @@ +package org.ruoyi.domain.bo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.shortdrama.ShortDramaScript; + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ShortDramaScript.class, reverseConvertGenerate = false) +public class ShortDramaScriptBo extends BaseEntity { + + private Long id; + + private Long projectId; + + private String scriptName; + + private String scriptText; + + private String outlineText; + + private String tone; + + private String sourceType; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaScriptResult.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaScriptResult.java new file mode 100644 index 00000000..8a61c5a5 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaScriptResult.java @@ -0,0 +1,31 @@ +package org.ruoyi.domain.bo.shortdrama; + +import dev.langchain4j.model.output.structured.Description; +import lombok.Data; + +/** + * Phase 1 剧本打磨结构化响应 —— langchain4j AiServices 自动解析用 + * + * @author ageerle + */ +@Data +public class ShortDramaScriptResult { + + @Description("项目名称(有吸引力的短剧名)") + private String projectName; + + @Description("一句话简介(20-50字)") + private String description; + + @Description("剧本名称") + private String scriptName; + + @Description("风格基调(如:都市甜宠/古装虐恋/悬疑惊悚/喜剧爽文)") + private String tone; + + @Description("剧情大纲(400-800字,完整故事线)") + private String outlineText; + + @Description("完整短剧文本(1000-3000字,标准剧本格式,含场景头、动作描述、对话)") + private String scriptText; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaStoryboardBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaStoryboardBo.java new file mode 100644 index 00000000..8eaf989f --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/shortdrama/ShortDramaStoryboardBo.java @@ -0,0 +1,49 @@ +package org.ruoyi.domain.bo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.domain.entity.shortdrama.ShortDramaStoryboard; + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ShortDramaStoryboard.class, reverseConvertGenerate = false) +public class ShortDramaStoryboardBo extends BaseEntity { + + private Long id; + + private Long projectId; + + private Long scriptId; + + private Integer sceneNo; + + private String sceneTitle; + + private String sceneText; + + private String sceneType; + + private String shotType; + + private String cameraMove; + + private String charactersJson; + + private String locationName; + + private String photographyRules; + + private String actingNotes; + + private String continuityJson; + + private String sourceText; + + private String imagePrompt; + + private Integer durationSeconds; + + private String videoPrompt; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/agent/Agent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/agent/Agent.java new file mode 100644 index 00000000..098763e4 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/agent/Agent.java @@ -0,0 +1,84 @@ +package org.ruoyi.domain.entity.agent; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +/** + * 智能体信息实体 + *

+ * 一个智能体聚合:一个聊天模型 + 一组 MCP 工具 + 一组磁盘技能 + 一组知识库 + 自定义提示词 + * 关联以 JSON 数组字符串列存储:mcp_tool_ids / skill_names / knowledge_ids + * + * @author ruoyi team + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("agent_info") +public class Agent extends TenantEntity { + + /** + * 智能体ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 智能体名称 + */ + private String agentName; + + /** + * 智能体描述(下拉展示用) + */ + private String agentDescribe; + + /** + * 展示图标/头像URL + */ + private String agentShow; + + /** + * 绑定的聊天模型ID(chat_model.id, category=chat) + */ + private Long modelId; + + /** + * 是否启用深度思考(ReAct多子Agent):0 否 1 是 + */ + private String enableThinking; + + /** + * 自定义系统提示词 + */ + private String systemPrompt; + + /** + * 关联MCP工具ID列表(JSON数组,[Long]) + */ + private String mcpToolIds; + + /** + * 关联磁盘技能名列表(JSON数组,[String]) + */ + private String skillNames; + + /** + * 关联知识库ID列表(JSON数组,[Long]) + */ + private String knowledgeIds; + + /** + * 状态:0 正常 1 停用 + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeAttach.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeAttach.java index 2d836f88..4b244efa 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeAttach.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeAttach.java @@ -37,6 +37,9 @@ public class KnowledgeAttach extends BaseEntity { */ private String docId; + /** SHA-256 content digest used for upload idempotency. */ + private String fileHash; + /** * 附件名称 */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeFragment.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeFragment.java index d184a10a..1fa84aca 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeFragment.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/knowledge/KnowledgeFragment.java @@ -27,6 +27,11 @@ public class KnowledgeFragment extends BaseEntity { @TableId(value = "id") private Long id; + /** + * 向量库片段ID(与向量库中的 fid 元数据对应,用于向量定位与混合检索融合) + */ + private String fid; + /** * 文档ID-用于关联文本块信息 */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaAudio.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaAudio.java new file mode 100644 index 00000000..dd02f16b --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaAudio.java @@ -0,0 +1,47 @@ +package org.ruoyi.domain.entity.shortdrama; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("short_drama_audio") +public class ShortDramaAudio extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private Long projectId; + + /** 语音资产名称 */ + private String name; + + /** 语音类型:narration(旁白)/dialogue(对白) */ + private String audioType; + + /** 语音文案(生成语音用的文本) */ + private String text; + + /** 音色(如 alloy/onyx) */ + private String voice; + + /** 音频文件OSS ID */ + private Long audioOssId; + + /** 音频文件URL */ + private String audioUrl; + + /** 对白关联的分镜ID(NULL=全局旁白) */ + private Long linkedStoryboardId; + + /** 音频时长(秒) */ + private Integer durationSeconds; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaCharacter.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaCharacter.java new file mode 100644 index 00000000..25472e44 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaCharacter.java @@ -0,0 +1,43 @@ +package org.ruoyi.domain.entity.shortdrama; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("short_drama_character") +public class ShortDramaCharacter extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private Long projectId; + + private String name; + + private String aliases; + + private String introduction; + + private String roleLevel; + + private String gender; + + private String ageRange; + + private String personalityTags; + + private Integer costumeTier; + + private String visualDescription; + + private String referenceImageUrl; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaCharacterAppearance.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaCharacterAppearance.java new file mode 100644 index 00000000..53360596 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaCharacterAppearance.java @@ -0,0 +1,49 @@ +package org.ruoyi.domain.entity.shortdrama; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("short_drama_character_appearance") +public class ShortDramaCharacterAppearance extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private Long characterId; + + private Integer appearanceIndex; + + private String changeReason; + + private String description; + + private String referenceImageUrl; + + /** 生成图片URL列表(JSON数组) */ + private String imageUrls; + + /** 每张图片对应的提示词(JSON数组) */ + private String imageDescriptions; + + /** 当前选中的图片索引 */ + private Integer selectedImageIndex; + + /** 上一轮图片URL列表(撤销用,JSON数组) */ + private String previousImageUrls; + + /** 上一轮提示词列表(撤销用,JSON数组) */ + private String previousDescriptions; + + /** 音色名(如 zh_male_taocheng_uranus_bigtts),用于该形象的对白配音 */ + private String voice; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaLocation.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaLocation.java new file mode 100644 index 00000000..7ae05126 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaLocation.java @@ -0,0 +1,52 @@ +package org.ruoyi.domain.entity.shortdrama; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("short_drama_location") +public class ShortDramaLocation extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private Long projectId; + + private String name; + + private String summary; + + private Boolean hasCrowd; + + private String crowdDescription; + + private String availableSlots; + + private String descriptions; + + private String referenceImageUrl; + + /** 生成图片URL列表(JSON数组) */ + private String imageUrls; + + /** 每张图片对应的提示词(JSON数组) */ + private String imageDescriptions; + + /** 当前选中的图片索引 */ + private Integer selectedImageIndex; + + /** 上一轮图片URL列表(撤销用,JSON数组) */ + private String previousImageUrls; + + /** 上一轮提示词列表(撤销用,JSON数组) */ + private String previousDescriptions; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaProject.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaProject.java new file mode 100644 index 00000000..529c3cdd --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaProject.java @@ -0,0 +1,53 @@ +package org.ruoyi.domain.entity.shortdrama; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; +import java.math.BigDecimal; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("short_drama_project") +public class ShortDramaProject extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private Long userId; + + private String projectName; + + private String description; + + private String status; + + private String artStyle; + + private Long composedVideoOssId; + + private String composeStatus; + + private String composeJobId; + + private Integer composeProgress; + + private String composeTransitionType; + + private BigDecimal composeTransitionDurationSeconds; + + private String composeAspectRatio; + + private BigDecimal composedVideoDurationSeconds; + + private String composeErrorMessage; + + private Date composedAt; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaScript.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaScript.java new file mode 100644 index 00000000..7a5b0e60 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaScript.java @@ -0,0 +1,33 @@ +package org.ruoyi.domain.entity.shortdrama; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("short_drama_script") +public class ShortDramaScript extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private Long projectId; + + private String scriptName; + + private String scriptText; + + private String outlineText; + + private String tone; + + private String sourceType; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaStoryboard.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaStoryboard.java new file mode 100644 index 00000000..5a2e2004 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/shortdrama/ShortDramaStoryboard.java @@ -0,0 +1,64 @@ +package org.ruoyi.domain.entity.shortdrama; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("short_drama_storyboard") +public class ShortDramaStoryboard extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private Long projectId; + + private Long scriptId; + + private Integer sceneNo; + + private String sceneTitle; + + private String sceneText; + + private String sceneType; + + private String shotType; + + private String cameraMove; + + private String charactersJson; + + private String locationName; + + private String photographyRules; + + private String actingNotes; + + private String continuityJson; + + private String sourceText; + + private String imagePrompt; + + private Integer durationSeconds; + + private String videoPrompt; + + private String videoUrl; + + private String videoId; + + private String videoStatus; + + /** 上一镜末帧URL(同场景连续镜头首帧承接用) */ + private String lastFrameUrl; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/agent/AgentVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/agent/AgentVo.java new file mode 100644 index 00000000..102d83a5 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/agent/AgentVo.java @@ -0,0 +1,121 @@ +package org.ruoyi.domain.vo.agent; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 智能体视图对象 + *

+ * 注意:不使用 @AutoMapper,因为 entity 的 mcpToolIds/skillNames/knowledgeIds 是 JSON 字符串列, + * 而 VO 是 List 类型,MapStruct 无法双向自动转换。由 AgentServiceImpl.toVo 手动组装。 + * + * @author ruoyi team + */ +@Data +@ExcelIgnoreUnannotated +public class AgentVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 智能体ID + */ + @ExcelProperty(value = "智能体ID") + private Long id; + + /** + * 智能体名称 + */ + @ExcelProperty(value = "智能体名称") + private String agentName; + + /** + * 智能体描述 + */ + @ExcelProperty(value = "智能体描述") + private String agentDescribe; + + /** + * 展示图标/头像URL + */ + private String agentShow; + + /** + * 绑定的聊天模型ID + */ + @ExcelProperty(value = "绑定模型ID") + private Long modelId; + + /** + * 绑定的聊天模型名称(关联展示) + */ + @ExcelProperty(value = "绑定模型") + private String modelName; + + /** + * 是否启用深度思考:0 否 1 是 + */ + @ExcelProperty(value = "深度思考") + private String enableThinking; + + /** + * 自定义系统提示词 + */ + private String systemPrompt; + + /** + * 关联MCP工具ID列表 + */ + private List mcpToolIds; + + /** + * 关联MCP工具名称列表(关联展示) + */ + private List mcpToolNames; + + /** + * 关联磁盘技能名列表 + */ + private List skillNames; + + /** + * 关联知识库ID列表 + */ + private List knowledgeIds; + + /** + * 关联知识库名称列表(关联展示) + */ + private List knowledgeNames; + + /** + * 状态:0 正常 1 停用 + */ + @ExcelProperty(value = "状态") + private String status; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** + * 更新时间 + */ + @ExcelProperty(value = "更新时间") + private Date updateTime; + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/agent/SkillOptionVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/agent/SkillOptionVo.java new file mode 100644 index 00000000..40959059 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/agent/SkillOptionVo.java @@ -0,0 +1,33 @@ +package org.ruoyi.domain.vo.agent; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 磁盘技能可选项 + * + * @author ruoyi team + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SkillOptionVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 技能名称(对应 SKILL.md front-matter name) + */ + private String name; + + /** + * 技能描述 + */ + private String description; + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/knowledge/KnowledgeFragmentVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/knowledge/KnowledgeFragmentVo.java index 45b4a9ab..a0c7bd8b 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/knowledge/KnowledgeFragmentVo.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/knowledge/KnowledgeFragmentVo.java @@ -30,6 +30,11 @@ public class KnowledgeFragmentVo implements Serializable { @ExcelProperty(value = "主键") private Long id; + /** + * 向量库片段ID + */ + private String fid; + /** * 文档ID-用于关联文本块信息 */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/knowledge/KnowledgeReparseVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/knowledge/KnowledgeReparseVo.java new file mode 100644 index 00000000..88f64bd2 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/knowledge/KnowledgeReparseVo.java @@ -0,0 +1,4 @@ +package org.ruoyi.domain.vo.knowledge; + +public record KnowledgeReparseVo(int submitted, int skipped, int total) { +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaAudioVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaAudioVo.java new file mode 100644 index 00000000..d61afa2c --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaAudioVo.java @@ -0,0 +1,41 @@ +package org.ruoyi.domain.vo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.domain.entity.shortdrama.ShortDramaAudio; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +@Data +@AutoMapper(target = ShortDramaAudio.class) +public class ShortDramaAudioVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + private Long projectId; + + private String name; + + private String audioType; + + private String text; + + private String voice; + + private Long audioOssId; + + private String audioUrl; + + private Long linkedStoryboardId; + + private Integer durationSeconds; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaCharacterAppearanceVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaCharacterAppearanceVo.java new file mode 100644 index 00000000..ac35eb97 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaCharacterAppearanceVo.java @@ -0,0 +1,45 @@ +package org.ruoyi.domain.vo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.domain.entity.shortdrama.ShortDramaCharacterAppearance; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +@Data +@AutoMapper(target = ShortDramaCharacterAppearance.class) +public class ShortDramaCharacterAppearanceVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + private Long characterId; + + private Integer appearanceIndex; + + private String changeReason; + + private String description; + + private String referenceImageUrl; + + private String imageUrls; + + private String imageDescriptions; + + private Integer selectedImageIndex; + + private String previousImageUrls; + + private String previousDescriptions; + + private String voice; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaCharacterVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaCharacterVo.java new file mode 100644 index 00000000..50f77b81 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaCharacterVo.java @@ -0,0 +1,48 @@ +package org.ruoyi.domain.vo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.domain.entity.shortdrama.ShortDramaCharacter; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +@AutoMapper(target = ShortDramaCharacter.class) +public class ShortDramaCharacterVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + private Long projectId; + + private String name; + + private String aliases; + + private String introduction; + + private String roleLevel; + + private String gender; + + private String ageRange; + + private String personalityTags; + + private Integer costumeTier; + + private String visualDescription; + + private String referenceImageUrl; + + private List appearances; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaComposeVideoVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaComposeVideoVo.java new file mode 100644 index 00000000..f3004a52 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaComposeVideoVo.java @@ -0,0 +1,45 @@ +package org.ruoyi.domain.vo.shortdrama; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShortDramaComposeVideoVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long projectId; + + private String status; + + private Integer progress; + + private String transitionType; + + private BigDecimal transitionDurationSeconds; + + private String aspectRatio; + + /** Actual duration measured from the final MP4 with ffprobe. */ + private BigDecimal outputDurationSeconds; + + private Long videoOssId; + + /** Freshly resolved URL; private-bucket URLs may be short lived. */ + private String videoUrl; + + private String errorMessage; + + private Date composedAt; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaDetailVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaDetailVo.java new file mode 100644 index 00000000..31cdc1cb --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaDetailVo.java @@ -0,0 +1,26 @@ +package org.ruoyi.domain.vo.shortdrama; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +@Data +public class ShortDramaDetailVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private ShortDramaProjectVo project; + + private ShortDramaScriptVo script; + + private List characters; + + private List locations; + + private List audios; + + private List storyboards; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaLocationVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaLocationVo.java new file mode 100644 index 00000000..9d7353e1 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaLocationVo.java @@ -0,0 +1,49 @@ +package org.ruoyi.domain.vo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.domain.entity.shortdrama.ShortDramaLocation; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +@Data +@AutoMapper(target = ShortDramaLocation.class) +public class ShortDramaLocationVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + private Long projectId; + + private String name; + + private String summary; + + private Boolean hasCrowd; + + private String crowdDescription; + + private String availableSlots; + + private String descriptions; + + private String referenceImageUrl; + + private String imageUrls; + + private String imageDescriptions; + + private Integer selectedImageIndex; + + private String previousImageUrls; + + private String previousDescriptions; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaProjectVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaProjectVo.java new file mode 100644 index 00000000..a5cde322 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaProjectVo.java @@ -0,0 +1,52 @@ +package org.ruoyi.domain.vo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.domain.entity.shortdrama.ShortDramaProject; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +@Data +@AutoMapper(target = ShortDramaProject.class) +public class ShortDramaProjectVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + private Long userId; + + private String projectName; + + private String description; + + private String status; + + private String artStyle; + + private Long composedVideoOssId; + + private String composeStatus; + + private Integer composeProgress; + + private String composeTransitionType; + + private BigDecimal composeTransitionDurationSeconds; + + private String composeAspectRatio; + + private BigDecimal composedVideoDurationSeconds; + + private String composeErrorMessage; + + private Date composedAt; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaScriptVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaScriptVo.java new file mode 100644 index 00000000..a26e6067 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaScriptVo.java @@ -0,0 +1,35 @@ +package org.ruoyi.domain.vo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.domain.entity.shortdrama.ShortDramaScript; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +@Data +@AutoMapper(target = ShortDramaScript.class) +public class ShortDramaScriptVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + private Long projectId; + + private String scriptName; + + private String scriptText; + + private String outlineText; + + private String tone; + + private String sourceType; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaStoryboardVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaStoryboardVo.java new file mode 100644 index 00000000..94680c3c --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/shortdrama/ShortDramaStoryboardVo.java @@ -0,0 +1,65 @@ +package org.ruoyi.domain.vo.shortdrama; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.domain.entity.shortdrama.ShortDramaStoryboard; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +@Data +@AutoMapper(target = ShortDramaStoryboard.class) +public class ShortDramaStoryboardVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + private Long projectId; + + private Long scriptId; + + private Integer sceneNo; + + private String sceneTitle; + + private String sceneText; + + private String sceneType; + + private String shotType; + + private String cameraMove; + + private String charactersJson; + + private String locationName; + + private String photographyRules; + + private String actingNotes; + + private String continuityJson; + + private String sourceText; + + private String imagePrompt; + + private Integer durationSeconds; + + private String videoPrompt; + + private String videoUrl; + + private String videoId; + + private String videoStatus; + + private String lastFrameUrl; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java index 6279b107..b7b545bb 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java @@ -18,6 +18,8 @@ public enum ChatModeType { ATLAS("atlas", "Atlas Cloud"), CUSTOM_API("custom_api", "自定义API"), MINIMAX("minimax", "MiniMax"), + DIFY("dify", "Dify"), + COZE("coze", "Coze"), XIAOMI("xiaomi", "小米MiMo"); private final String code; private final String description; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java index b123e6bb..0db9d859 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java @@ -25,6 +25,14 @@ import java.util.concurrent.ConcurrentHashMap; @Slf4j public class EmbeddingModelFactory { + /** + * 厂商 providerCode → Spring Bean 名称映射 + * 处理 providerCode 与 @Component 注册名不一致的情况 + */ + private static final Map PROVIDER_BEAN_MAPPING = Map.of( + "qianwen", "alibailian" + ); + private final ApplicationContext applicationContext; private final IChatModelService chatModelService; @@ -77,13 +85,22 @@ public class EmbeddingModelFactory { /** * 刷新模型缓存 - * 根据给定的嵌入模型ID从缓存中移除对应的模型 + * 根据给定的嵌入模型ID解析模型名称后,从缓存中移除对应的模型 * * @param embeddingModelId 嵌入模型的唯一标识ID */ public void refreshModel(Long embeddingModelId) { - // 从模型缓存中移除指定ID的模型 - modelCache.remove(embeddingModelId); + ChatModelVo modelConfig = chatModelService.queryById(embeddingModelId); + if (modelConfig != null) { + modelCache.remove(modelConfig.getModelName()); + } + } + + /** + * 按模型名称刷新缓存 + */ + public void refreshModelByName(String embeddingModelName) { + modelCache.remove(embeddingModelName); } /** @@ -107,8 +124,10 @@ public class EmbeddingModelFactory { */ private BaseEmbedModelService createModelInstance(String factory, ChatModelVo config) { try { + // 解析实际的 Bean 名称(处理 providerCode 与 @Component 注册名不一致的情况) + String beanName = PROVIDER_BEAN_MAPPING.getOrDefault(factory, factory); // 从Spring上下文中获取模型实例 - BaseEmbedModelService model = applicationContext.getBean(factory, BaseEmbedModelService.class); + BaseEmbedModelService model = applicationContext.getBean(beanName, BaseEmbedModelService.class); // 配置模型参数 model.configure(config); // 增加嵌入模型监听器 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/RerankModelFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/RerankModelFactory.java index 2b9ff9fe..62ede6ef 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/RerankModelFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/RerankModelFactory.java @@ -55,12 +55,22 @@ public class RerankModelFactory { /** * 刷新模型缓存 - * 根据给定的模型ID从缓存中移除对应的模型 + * 根据给定的模型ID解析模型名称后,从缓存中移除对应的模型 * * @param modelId 模型的唯一标识ID */ public void refreshModel(Long modelId) { - modelCache.remove(modelId); + ChatModelVo modelConfig = chatModelService.queryById(modelId); + if (modelConfig != null) { + modelCache.remove(modelConfig.getModelName()); + } + } + + /** + * 按模型名称刷新缓存 + */ + public void refreshModelByName(String modelName) { + modelCache.remove(modelName); } /** diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/ResourceLoaderFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/ResourceLoaderFactory.java index de293426..0e5f601c 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/ResourceLoaderFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/ResourceLoaderFactory.java @@ -17,7 +17,7 @@ public class ResourceLoaderFactory { private final ExcelTextSplitter excelTextSplitter; public ResourceLoader getLoaderByFileType(String fileType) { - fileType = StringUtils.removeStart(fileType, "."); + fileType = StringUtils.lowerCase(StringUtils.removeStart(StringUtils.trim(fileType), ".")); if (FileTypeConstants.isTextFile(fileType)) { return new TextFileLoader(characterTextSplitter); } else if (FileTypeConstants.isWord(fileType)) { diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java index a66ea66c..4c4fe536 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/VectorStoreStrategyFactory.java @@ -10,6 +10,7 @@ import org.ruoyi.service.vector.impl.MilvusVectorStoreStrategy; import org.ruoyi.service.vector.impl.QdrantVectorStoreStrategy; import org.ruoyi.service.vector.impl.WeaviateVectorStoreStrategy; import org.springframework.stereotype.Component; +import org.ruoyi.common.core.exception.ServiceException; import java.util.HashMap; import java.util.Map; @@ -45,14 +46,20 @@ public class VectorStoreStrategyFactory { * 获取当前配置的向量库策略 */ public VectorStoreService getStrategy() { - String vectorStoreType = vectorStoreProperties.getType(); + return getStrategy(null); + } + + public VectorStoreService getStrategy(String requestedType) { + String vectorStoreType = requestedType; + if (vectorStoreType == null || vectorStoreType.trim().isEmpty()) { + vectorStoreType = vectorStoreProperties.getType(); + } if (vectorStoreType == null || vectorStoreType.trim().isEmpty()) { vectorStoreType = "weaviate"; // 默认使用weaviate } VectorStoreService strategy = strategies.get(vectorStoreType.toLowerCase()); if (strategy == null) { - log.warn("未找到向量库策略: {}, 使用默认策略: weaviate", vectorStoreType); - strategy = strategies.get("weaviate"); + throw new ServiceException("不支持的向量库类型: " + vectorStoreType); } log.debug("使用向量库策略: {}", vectorStoreType); return strategy; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/agent/AgentMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/agent/AgentMapper.java new file mode 100644 index 00000000..e13ecf25 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/agent/AgentMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.mapper.agent; + +import org.apache.ibatis.annotations.Mapper; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.domain.entity.agent.Agent; +import org.ruoyi.domain.vo.agent.AgentVo; +/** + * 智能体信息 Mapper + * + * @author ruoyi team + */ +@Mapper +public interface AgentMapper extends BaseMapperPlus { +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeAttachMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeAttachMapper.java index 05b4d780..f6c7aeff 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeAttachMapper.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeAttachMapper.java @@ -6,6 +6,8 @@ import org.apache.ibatis.annotations.Select; import org.ruoyi.domain.entity.knowledge.KnowledgeAttach; import org.ruoyi.domain.vo.knowledge.KnowledgeAttachVo; import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import java.util.List; +import java.util.Map; /** * 知识库附件Mapper接口 @@ -21,4 +23,10 @@ public interface KnowledgeAttachMapper extends BaseMapperPlusSELECT knowledge_id AS knowledgeId, COUNT(*) AS documentCount " + + "FROM knowledge_attach WHERE knowledge_id IN " + + "#{id} " + + "GROUP BY knowledge_id") + List> countByKnowledgeIds(@Param("knowledgeIds") List knowledgeIds); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeFragmentMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeFragmentMapper.java index 304bb7d5..f710b9ac 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeFragmentMapper.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/knowledge/KnowledgeFragmentMapper.java @@ -34,7 +34,7 @@ public interface KnowledgeFragmentMapper extends BaseMapperPlus") List selectFragmentCountByDocIds(@Param("docIds") List docIds); @Select("