From 956efe6a09405fe920882a23c07ac06bdbc61a9e Mon Sep 17 00:00:00 2001 From: wxy Date: Wed, 18 Mar 2026 12:21:59 +0800 Subject: [PATCH 01/14] fix: read new_name from request body in copy_app endpoint --- api/app/controllers/app_controller.py | 3 ++- api/app/schemas/app_schema.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index d6c71f37..85493500 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -193,7 +193,7 @@ def delete_app( @cur_workspace_access_guard() def copy_app( app_id: uuid.UUID, - new_name: Optional[str] = None, + payload: app_schema.CopyAppRequest = None, db: Session = Depends(get_db), current_user=Depends(get_current_user), ): @@ -205,6 +205,7 @@ def copy_app( - 不影响原应用 """ workspace_id = current_user.current_workspace_id + new_name = payload.new_name if payload else None logger.info( "用户请求复制应用", extra={ diff --git a/api/app/schemas/app_schema.py b/api/app/schemas/app_schema.py index b63c73f5..8bfedd5a 100644 --- a/api/app/schemas/app_schema.py +++ b/api/app/schemas/app_schema.py @@ -525,6 +525,13 @@ class AppRelease(BaseModel): return int(dt.timestamp() * 1000) if dt else None +# ---------- App Copy Schema ---------- + +class CopyAppRequest(BaseModel): + """复制应用请求""" + new_name: Optional[str] = Field(None, description="新应用名称,不填则使用原名称-副本") + + # ---------- App Share Schemas ---------- class AppShareCreate(BaseModel): From 56c98648f90755615b39d4d3339f2b480ab79fed Mon Sep 17 00:00:00 2001 From: wxy Date: Wed, 18 Mar 2026 12:30:50 +0800 Subject: [PATCH 02/14] fix: support both query param and body for new_name in copy_app for backward compatibility --- api/app/controllers/app_controller.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index 85493500..9b77fa30 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -193,6 +193,7 @@ def delete_app( @cur_workspace_access_guard() def copy_app( app_id: uuid.UUID, + new_name: Optional[str] = None, payload: app_schema.CopyAppRequest = None, db: Session = Depends(get_db), current_user=Depends(get_current_user), @@ -205,7 +206,8 @@ def copy_app( - 不影响原应用 """ workspace_id = current_user.current_workspace_id - new_name = payload.new_name if payload else None + # body takes precedence over query param for backward compatibility + new_name = (payload.new_name if payload else None) or new_name logger.info( "用户请求复制应用", extra={ From f042f445019c98474b1b7842adb4ad57c3ae31ee Mon Sep 17 00:00:00 2001 From: wxy Date: Wed, 18 Mar 2026 16:38:22 +0800 Subject: [PATCH 03/14] fix: shared app uses release snapshot config instead of draft in draft_run and get_agent_config --- api/app/controllers/app_controller.py | 46 ++++++++++++++------ api/app/services/app_service.py | 60 +++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index 9b77fa30..79ec9116 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -559,18 +559,29 @@ async def draft_run( service._check_agent_config(app_id) # 2. 获取 Agent 配置 - stmt = select(AgentConfig).where(AgentConfig.app_id == app_id) - agent_cfg = db.scalars(stmt).first() - if not agent_cfg: - raise BusinessException("Agent 配置不存在", BizCode.AGENT_CONFIG_MISSING) + # 共享应用:从最新发布版本读配置快照,而非草稿 + is_shared = app.workspace_id != workspace_id + if is_shared: + if not app.current_release_id: + raise BusinessException("该应用尚未发布,无法使用", BizCode.AGENT_CONFIG_MISSING) + release = db.get(AppRelease, app.current_release_id) + if not release: + raise BusinessException("发布版本不存在", BizCode.AGENT_CONFIG_MISSING) + agent_cfg = service._agent_config_from_release(release) + model_config = db.get(ModelConfig, release.default_model_config_id) if release.default_model_config_id else None + else: + stmt = select(AgentConfig).where(AgentConfig.app_id == app_id) + agent_cfg = db.scalars(stmt).first() + if not agent_cfg: + raise BusinessException("Agent 配置不存在", BizCode.AGENT_CONFIG_MISSING) - # 3. 获取模型配置 - model_config = None - if agent_cfg.default_model_config_id: - model_config = db.get(ModelConfig, agent_cfg.default_model_config_id) - if not model_config: - from app.core.exceptions import ResourceNotFoundException - raise ResourceNotFoundException("模型配置", str(agent_cfg.default_model_config_id)) + # 3. 获取模型配置 + model_config = None + if agent_cfg.default_model_config_id: + model_config = db.get(ModelConfig, agent_cfg.default_model_config_id) + if not model_config: + from app.core.exceptions import ResourceNotFoundException + raise ResourceNotFoundException("模型配置", str(agent_cfg.default_model_config_id)) # 流式返回 if payload.stream: @@ -726,7 +737,18 @@ async def draft_run( msg="多 Agent 任务执行成功" ) elif app.type == AppType.WORKFLOW: # 工作流 - config = workflow_service.check_config(app_id) + # 共享应用:从最新发布版本读配置快照,而非草稿 + is_shared = app.workspace_id != workspace_id + if is_shared: + if not app.current_release_id: + raise BusinessException("该应用尚未发布,无法使用", BizCode.AGENT_CONFIG_MISSING) + from app.models import AppRelease + release = db.get(AppRelease, app.current_release_id) + if not release: + raise BusinessException("发布版本不存在", BizCode.AGENT_CONFIG_MISSING) + config = service._workflow_config_from_release(release) + else: + config = workflow_service.check_config(app_id) # 3. 流式返回 if payload.stream: logger.debug( diff --git a/api/app/services/app_service.py b/api/app/services/app_service.py index 98fdf6c9..94ee606e 100644 --- a/api/app/services/app_service.py +++ b/api/app/services/app_service.py @@ -1426,6 +1426,47 @@ class AppService: logger.info("Agent 配置更新成功", extra={"app_id": str(app_id)}) return agent_cfg + def _agent_config_from_release(self, release: "AppRelease") -> "AgentConfig": + """从发布版本快照重建 AgentConfig 对象(不入库,仅用于运行)""" + cfg = release.config or {} + now = release.created_at or datetime.datetime.now() + agent_cfg = AgentConfig( + id=uuid.uuid4(), + app_id=release.app_id, + system_prompt=cfg.get("system_prompt", ""), + default_model_config_id=release.default_model_config_id, + model_parameters=cfg.get("model_parameters"), + knowledge_retrieval=cfg.get("knowledge_retrieval"), + memory=cfg.get("memory", {}), + variables=cfg.get("variables", []), + tools=cfg.get("tools", []), + skills=cfg.get("skills", {}), + features=cfg.get("features", {}), + is_active=True, + created_at=now, + updated_at=now, + ) + return agent_cfg + + def _workflow_config_from_release(self, release: "AppRelease") -> "WorkflowConfig": + """从发布版本快照重建 WorkflowConfig 对象(不入库,仅用于运行)""" + cfg = release.config or {} + now = release.created_at or datetime.datetime.now() + from app.models.workflow_model import WorkflowConfig as WorkflowConfigModel + wf_cfg = WorkflowConfigModel( + id=uuid.uuid4(), + app_id=release.app_id, + nodes=cfg.get("nodes", []), + edges=cfg.get("edges", []), + variables=cfg.get("variables", []), + execution_config=cfg.get("execution_config", {}), + triggers=cfg.get("triggers", []), + is_active=True, + created_at=now, + updated_at=now, + ) + return wf_cfg + def get_agent_config( self, *, @@ -1457,6 +1498,15 @@ class AppService: # 只读操作,允许访问共享应用 self._validate_app_accessible(app, workspace_id) + # 共享应用:返回最新发布版本的配置快照,而非草稿 + if workspace_id and app.workspace_id != workspace_id: + if not app.current_release_id: + raise BusinessException("该应用尚未发布,无法使用", BizCode.AGENT_CONFIG_MISSING) + release = self.db.get(AppRelease, app.current_release_id) + if not release: + raise BusinessException("发布版本不存在", BizCode.AGENT_CONFIG_MISSING) + return self._agent_config_from_release(release) + stmt = select(AgentConfig).where( AgentConfig.app_id == app_id, AgentConfig.is_active.is_(True) @@ -1555,6 +1605,16 @@ class AppService: # 只读操作,允许访问共享应用 self._validate_app_accessible(app, workspace_id) + + # 共享应用:返回最新发布版本的配置快照,而非草稿 + if workspace_id and app.workspace_id != workspace_id: + if not app.current_release_id: + raise BusinessException("该应用尚未发布,无法使用", BizCode.CONFIG_MISSING) + release = self.db.get(AppRelease, app.current_release_id) + if not release: + raise BusinessException("发布版本不存在", BizCode.CONFIG_MISSING) + return self._workflow_config_from_release(release) + repo = WorkflowConfigRepository(self.db) config = repo.get_by_app_id(app_id) if config: From e24217a6baabf417c72ce914f8d58681870bfe9a Mon Sep 17 00:00:00 2001 From: wxy Date: Wed, 18 Mar 2026 17:36:43 +0800 Subject: [PATCH 04/14] fix: remove redundant local AppRelease import causing NameError in draft_run --- api/app/controllers/app_controller.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index 79ec9116..e9b539df 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -520,7 +520,7 @@ async def draft_run( # 提前验证和准备(在流式响应开始前完成) from app.services.app_service import AppService from app.services.multi_agent_service import MultiAgentService - from app.models import AgentConfig, ModelConfig + from app.models import AgentConfig, ModelConfig, AppRelease from sqlalchemy import select from app.core.exceptions import BusinessException from app.services.draft_run_service import AgentRunService @@ -742,7 +742,6 @@ async def draft_run( if is_shared: if not app.current_release_id: raise BusinessException("该应用尚未发布,无法使用", BizCode.AGENT_CONFIG_MISSING) - from app.models import AppRelease release = db.get(AppRelease, app.current_release_id) if not release: raise BusinessException("发布版本不存在", BizCode.AGENT_CONFIG_MISSING) From e6c558c2a078d167788b629e50db32a2d1b4d00e Mon Sep 17 00:00:00 2001 From: wxy Date: Wed, 18 Mar 2026 18:01:17 +0800 Subject: [PATCH 05/14] fix: use real workflow_config id from db to avoid foreign key violation in workflow_executions --- api/app/services/app_service.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/app/services/app_service.py b/api/app/services/app_service.py index 94ee606e..68d255f8 100644 --- a/api/app/services/app_service.py +++ b/api/app/services/app_service.py @@ -1453,8 +1453,11 @@ class AppService: cfg = release.config or {} now = release.created_at or datetime.datetime.now() from app.models.workflow_model import WorkflowConfig as WorkflowConfigModel + # 查出源应用真实的 WorkflowConfig id,供 workflow_executions 外键使用 + real_config = WorkflowConfigRepository(self.db).get_by_app_id(release.app_id) + real_id = real_config.id if real_config else uuid.uuid4() wf_cfg = WorkflowConfigModel( - id=uuid.uuid4(), + id=real_id, app_id=release.app_id, nodes=cfg.get("nodes", []), edges=cfg.get("edges", []), From 8c5fa9c441a9d2de4c17744df9c084e13f48b2aa Mon Sep 17 00:00:00 2001 From: yujiangping Date: Wed, 18 Mar 2026 18:16:27 +0800 Subject: [PATCH 06/14] fix:cdn pdf --- web/src/components/DocumentPreview/index.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/src/components/DocumentPreview/index.tsx b/web/src/components/DocumentPreview/index.tsx index 02345d13..4ea4a741 100644 --- a/web/src/components/DocumentPreview/index.tsx +++ b/web/src/components/DocumentPreview/index.tsx @@ -4,7 +4,7 @@ * @Author: yujiangping * @Date: 2026-03-16 19:01:12 * @LastEditors: yujiangping - * @LastEditTime: 2026-03-17 16:19:45 + * @LastEditTime: 2026-03-18 18:15:29 */ import { useState, useEffect, useRef, useCallback, type FC } from 'react'; import { Spin, Alert, Button, Table, InputNumber, Image } from 'antd'; @@ -21,10 +21,9 @@ import { cookieUtils } from '@/utils/request'; import mammoth from 'mammoth'; import * as XLSX from 'xlsx'; import * as pdfjsLib from 'pdfjs-dist'; -import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?url'; -// 设置 pdf.js worker -pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker; +// 设置 pdf.js worker - 使用 CDN 避免 Vite 打包动态 import 问题 +pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/pdf.worker.min.mjs'; interface DocumentPreviewProps { fileUrl: string; From 22ffe6ef1d52048ba3d3ed14ba1d5b38de8d212e Mon Sep 17 00:00:00 2001 From: yujiangping Date: Wed, 18 Mar 2026 18:25:01 +0800 Subject: [PATCH 07/14] fix:pdf change version --- web/package.json | 2 +- web/src/components/DocumentPreview/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/package.json b/web/package.json index b6840854..db6a8408 100644 --- a/web/package.json +++ b/web/package.json @@ -46,7 +46,7 @@ "lexical": "^0.39.0", "mammoth": "^1.12.0", "mermaid": "^11.12.1", - "pdfjs-dist": "^4.4.168", + "pdfjs-dist": "4.10.38", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.0.0", diff --git a/web/src/components/DocumentPreview/index.tsx b/web/src/components/DocumentPreview/index.tsx index 4ea4a741..fd426379 100644 --- a/web/src/components/DocumentPreview/index.tsx +++ b/web/src/components/DocumentPreview/index.tsx @@ -4,7 +4,7 @@ * @Author: yujiangping * @Date: 2026-03-16 19:01:12 * @LastEditors: yujiangping - * @LastEditTime: 2026-03-18 18:15:29 + * @LastEditTime: 2026-03-18 18:24:27 */ import { useState, useEffect, useRef, useCallback, type FC } from 'react'; import { Spin, Alert, Button, Table, InputNumber, Image } from 'antd'; @@ -23,7 +23,7 @@ import * as XLSX from 'xlsx'; import * as pdfjsLib from 'pdfjs-dist'; // 设置 pdf.js worker - 使用 CDN 避免 Vite 打包动态 import 问题 -pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.4.168/pdf.worker.min.mjs'; +pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.10.38/pdf.worker.min.mjs'; interface DocumentPreviewProps { fileUrl: string; From 750dbcc7c35d66a0ef580192bc47ac848aff316a Mon Sep 17 00:00:00 2001 From: yujiangping Date: Wed, 18 Mar 2026 18:52:37 +0800 Subject: [PATCH 08/14] fix(web): improve document preview handling for .doc files and validate docx format - Add early return for .doc files with unsupported format message - Implement ZIP format validation for docx files by checking PK header bytes - Add error handling for invalid docx content with detailed error messages - Update Word preview UI to show download prompt for .doc files instead of attempting conversion - Prevent mammoth converter from processing invalid or non-docx file formats --- web/src/components/DocumentPreview/index.tsx | 37 ++++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/web/src/components/DocumentPreview/index.tsx b/web/src/components/DocumentPreview/index.tsx index fd426379..247f713e 100644 --- a/web/src/components/DocumentPreview/index.tsx +++ b/web/src/components/DocumentPreview/index.tsx @@ -4,7 +4,7 @@ * @Author: yujiangping * @Date: 2026-03-16 19:01:12 * @LastEditors: yujiangping - * @LastEditTime: 2026-03-18 18:24:27 + * @LastEditTime: 2026-03-18 18:35:53 */ import { useState, useEffect, useRef, useCallback, type FC } from 'react'; import { Spin, Alert, Button, Table, InputNumber, Image } from 'antd'; @@ -286,7 +286,20 @@ const DocumentPreview: FC = ({ setError(false); setErrorMessage(''); try { + // .doc 旧格式 mammoth 不支持,使用 Office Online Viewer + if (getFileExtension() === '.doc') { + setHtmlContent(''); + setLoading(false); + return; + } const arrayBuffer = await fetchFileBuffer(fileUrl); + // 校验是否为有效的 docx(ZIP 格式,前两字节为 PK) + const header = new Uint8Array(arrayBuffer.slice(0, 4)); + if (header[0] !== 0x50 || header[1] !== 0x4B) { + // 不是 ZIP/docx 格式,可能是 HTML 错误页或 JSON 响应 + const text = new TextDecoder().decode(arrayBuffer.slice(0, 200)); + throw new Error(`文件内容不是有效的 docx 格式: ${text.substring(0, 100)}`); + } const result = await mammoth.convertToHtml({ arrayBuffer }); setHtmlContent(result.value); setLoading(false); @@ -491,12 +504,22 @@ const DocumentPreview: FC = ({ {/* Word 预览 */} {isWordFile() && !error && !loading && ( -
-
-
+ getFileExtension() === '.doc' ? ( + /* .doc 旧格式前端无法解析,提示下载 */ +
+
+

.doc 格式暂不支持在线预览,请下载后查看

+ +
+
+ ) : ( +
+
+
+ ) )} {/* Excel 预览 */} From 2c318f6e60caa70028be308d64c02c77767a39bb Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 18 Mar 2026 19:39:12 +0800 Subject: [PATCH 09/14] fix(web): app features bugfix --- web/src/views/ApplicationConfig/Cluster.tsx | 12 ++++++------ .../FeaturesConfig/FileUploadSettingModal.tsx | 4 ++-- web/src/views/Workflow/components/Chat/Chat.tsx | 7 +++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/web/src/views/ApplicationConfig/Cluster.tsx b/web/src/views/ApplicationConfig/Cluster.tsx index 97ae939a..94023f0b 100644 --- a/web/src/views/ApplicationConfig/Cluster.tsx +++ b/web/src/views/ApplicationConfig/Cluster.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:33 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-17 14:48:57 + * @Last Modified time: 2026-03-18 19:37:15 */ import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react' import { useTranslation } from 'react-i18next' @@ -30,7 +30,7 @@ import RadioGroupCard from '@/components/RadioGroupCard' import { getModelListUrl } from '@/api/models' import ModelConfigModal from './components/ModelConfigModal' import type { Application } from '@/views/ApplicationManagement/types' -import FeaturesConfig from './components/FeaturesConfig' +// import FeaturesConfig from './components/FeaturesConfig' const tagColors = ['processing', 'warning', 'default'] const MAX_LENGTH = 5; @@ -187,15 +187,15 @@ const Cluster = forwardRef { - form.setFieldValue('features', value) - } + // const handleSaveFeaturesConfig = (value: FeaturesConfigForm) => { + // form.setFieldValue('features', value) + // } return ( - + {/* */} diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx index 3579497a..1d4e2a12 100644 --- a/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx +++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-05 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-17 18:10:47 + * @Last Modified time: 2026-03-18 19:35:57 */ import { forwardRef, useImperativeHandle, useState } from 'react'; import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd'; @@ -80,7 +80,7 @@ const FileUploadSettingModal = forwardRef { setVisible(true); if (values) { - const methods = values.allowed_transfer_methods + const methods = values.allowed_transfer_methods || ['local_file', 'remote_url'] const transferMethod = Array.isArray(methods) ? methods.length === 2 ? 'both' : methods[0] : methods diff --git a/web/src/views/Workflow/components/Chat/Chat.tsx b/web/src/views/Workflow/components/Chat/Chat.tsx index 37cb215e..1e3a1b4a 100644 --- a/web/src/views/Workflow/components/Chat/Chat.tsx +++ b/web/src/views/Workflow/components/Chat/Chat.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-06 21:10:56 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 14:34:20 + * @Last Modified time: 2026-03-18 19:31:28 */ /** * Workflow Chat Component @@ -63,9 +63,12 @@ const Chat = forwardRef { setOpen(true) - if (data?.features) setFeatures(data.features) } + useEffect(() => { + if (data?.features && open) setFeatures(data.features) + }, [open, data?.features]) + useEffect(() => { if (open && graphRef.current && toolbarRef.current) { getVariables() From 04b22057696d0a3be80a3d7bd0457d8238769efa Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 18 Mar 2026 20:01:59 +0800 Subject: [PATCH 10/14] fix(web): update app export param key --- web/src/api/application.ts | 4 ++-- web/src/views/ApplicationConfig/ReleasePage.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/api/application.ts b/web/src/api/application.ts index 6035afe2..5225115e 100644 --- a/web/src/api/application.ts +++ b/web/src/api/application.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 13:59:45 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-13 17:07:54 + * @Last Modified time: 2026-03-18 20:01:29 */ import { request } from '@/utils/request' import type { ApplicationModalData } from '@/views/ApplicationManagement/types' @@ -137,7 +137,7 @@ export const getExperienceConfig = (share_token: string) => { }) } // Export application -export const appExport = (app_id: string, appName: string, data?: { release_version: string }) => { +export const appExport = (app_id: string, appName: string, data?: { release_id: string }) => { return request.getDownloadFile(`/apps/${app_id}/export`, `${appName}.yml`, data) } // Import application diff --git a/web/src/views/ApplicationConfig/ReleasePage.tsx b/web/src/views/ApplicationConfig/ReleasePage.tsx index efa62578..ab34576a 100644 --- a/web/src/views/ApplicationConfig/ReleasePage.tsx +++ b/web/src/views/ApplicationConfig/ReleasePage.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:41 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 14:30:41 + * @Last Modified time: 2026-03-18 20:01:31 */ import { type FC, useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; @@ -71,7 +71,7 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres } const handleExport = () => { if (!selectedVersion) return - appExport(data.id, data.name, {release_version: selectedVersion.id}) + appExport(data.id, data.name, { release_id: selectedVersion.id}) } return (
From be46ed8865738f1001f7f7b5273b676e1bf39c83 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 18 Mar 2026 20:55:31 +0800 Subject: [PATCH 11/14] feat(web): chart content support files --- web/src/components/Chat/ChatContent.tsx | 54 ++++++++++++++++--- web/src/components/Chat/types.ts | 8 +-- web/src/views/ApplicationConfig/Cluster.tsx | 4 +- .../ApplicationConfig/TestChat/index.tsx | 14 +++-- .../ApplicationConfig/components/Chat.tsx | 8 +-- .../FeaturesConfig/FileUploadSettingModal.tsx | 10 ++-- .../Conversation/components/FileUpload.tsx | 15 +++++- web/src/views/Conversation/index.tsx | 8 +-- .../views/Workflow/components/Chat/Chat.tsx | 7 ++- 9 files changed, 101 insertions(+), 27 deletions(-) diff --git a/web/src/components/Chat/ChatContent.tsx b/web/src/components/Chat/ChatContent.tsx index 15dcd496..2f2552db 100644 --- a/web/src/components/Chat/ChatContent.tsx +++ b/web/src/components/Chat/ChatContent.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-10 16:46:17 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-17 14:11:24 + * @Last Modified time: 2026-03-18 20:48:03 */ import { type FC, useRef, useEffect, useState } from 'react' import clsx from 'clsx' @@ -33,7 +33,7 @@ const ChatContent: FC = ({ const audioRef = useRef(null) const [playingIndex, setPlayingIndex] = useState(null) - const handlePlay = (index: number, audioUrl: string) => { + const handlePlay = (index: number, audio_url: string) => { if (playingIndex === index) { audioRef.current?.pause() setPlayingIndex(null) @@ -42,7 +42,7 @@ const ChatContent: FC = ({ if (audioRef.current) { audioRef.current.pause() } - const audio = new Audio(audioUrl) + const audio = new Audio(audio_url) audioRef.current = audio audio.play() setPlayingIndex(index) @@ -108,6 +108,48 @@ const ChatContent: FC = ({ {labelFormat(item)}
} + {item.meta_data?.files && item.meta_data?.files.length > 0 &&
+ {item.meta_data?.files?.map((file) => { + if (file.type.includes('image')) { + return ( +
+ {file.name} +
+ ) + } + if (file.type.includes('video')) { + return ( +
+
+ ) + } + if (file.type.includes('audio')) { + return ( +
+
+ ) + } + return ( +
+ {(file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document')) &&
} + {(file.type.includes('pdf')) &&
} + {(file.type.includes('excel') || file.type.includes('spreadsheetml.sheet') || file.type.includes('csv')) &&
} +
+
{file.name}
+
{file.type} · {file.size}
+
+
+ ) + })} +
} {/* Message bubble */}
= ({ {/* Render message content using Markdown component */} - {item.audioUrl && <> + {item.meta_data?.audio_url && <> {playingIndex !== index - ? handlePlay(index, item.audioUrl!)} /> + ? handlePlay(index, item.meta_data?.audio_url!)} /> :
handlePlay(index, item.audioUrl!)} + onClick={() => handlePlay(index, item.meta_data?.audio_url!)} /> } diff --git a/web/src/components/Chat/types.ts b/web/src/components/Chat/types.ts index 9fb77ed7..a1d0a7e1 100644 --- a/web/src/components/Chat/types.ts +++ b/web/src/components/Chat/types.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-10 16:45:54 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-17 13:46:24 + * @Last Modified time: 2026-03-18 20:47:42 */ import { type ReactNode } from 'react' @@ -22,9 +22,11 @@ export interface ChatItem { created_at?: number | string; status?: string; subContent?: Record[]; - files?: any[]; error?: string; - audioUrl?: string; + meta_data?: { + audio_url?: string; + files?: any[]; + }, } /** diff --git a/web/src/views/ApplicationConfig/Cluster.tsx b/web/src/views/ApplicationConfig/Cluster.tsx index 94023f0b..f4dd5a26 100644 --- a/web/src/views/ApplicationConfig/Cluster.tsx +++ b/web/src/views/ApplicationConfig/Cluster.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:33 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 19:37:15 + * @Last Modified time: 2026-03-18 19:49:09 */ import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react' import { useTranslation } from 'react-i18next' @@ -295,6 +295,7 @@ const Cluster = forwardRef } diff --git a/web/src/views/ApplicationConfig/TestChat/index.tsx b/web/src/views/ApplicationConfig/TestChat/index.tsx index b7ce167e..ad7931e2 100644 --- a/web/src/views/ApplicationConfig/TestChat/index.tsx +++ b/web/src/views/ApplicationConfig/TestChat/index.tsx @@ -1,3 +1,9 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-03-13 17:27:52 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-18 20:54:35 + */ import { type FC, useState, useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { App } from 'antd' @@ -116,7 +122,9 @@ const TestChat: FC = ({ role: 'user', content: message, created_at: Date.now(), - files + meta_data: { + files + }, }]) } @@ -136,7 +144,7 @@ const TestChat: FC = ({ const lastMsg = newList[newList.length - 1] if (lastMsg.role === 'assistant') { lastMsg.content += content; - lastMsg.audioUrl = audio_url + lastMsg.meta_data = {audio_url} } return newList }) @@ -428,7 +436,7 @@ const TestChat: FC = ({ status, error, content: newList[lastIndex].content === '' ? null : newList[lastIndex].content, - audioUrl: audio_url + meta_data: { audio_url: audio_url } } } return newList diff --git a/web/src/views/ApplicationConfig/components/Chat.tsx b/web/src/views/ApplicationConfig/components/Chat.tsx index b8fba5a6..ee5bc53a 100644 --- a/web/src/views/ApplicationConfig/components/Chat.tsx +++ b/web/src/views/ApplicationConfig/components/Chat.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:39 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-17 15:27:57 + * @Last Modified time: 2026-03-18 20:52:33 */ /** * Chat debugging component for application testing @@ -92,7 +92,9 @@ const Chat: FC = ({ role: 'user', content: message, created_at: Date.now(), - files + meta_data: { + files + }, }; updateChatList(prev => prev.map(item => ({ ...item, @@ -142,7 +144,7 @@ const Chat: FC = ({ { ...lastMsg, content: lastMsg.content + (content || ''), - audioUrl: audio_url + meta_data: { audio_url } } ] } diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx index 1d4e2a12..eae1a5a5 100644 --- a/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx +++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-05 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 19:35:57 + * @Last Modified time: 2026-03-18 20:29:28 */ import { forwardRef, useImperativeHandle, useState } from 'react'; import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd'; @@ -27,22 +27,22 @@ const fileTypeOptions = [ { type: 'document', icon:
, - formats: 'TXT, MD, MDX, MARKDOWN, PDF, DOC, DOCX', + formats: 'TXT, PDF, DOC, DOCX, XLSX, CSV, JSON', }, { type: 'image', icon:
, - formats: 'JPG, JPEG, PNG, GIF, WEBP, SVG', + formats: 'JPG, JPEG, PNG, GIF, WEBP', }, { type: 'audio', icon:
, - formats: 'MP3, M4A, WAV, AMR, MPGA', + formats: 'MP3, M4A, WAV, OGG, FLAC', }, { type: 'video', icon:
, - formats: 'MP4, MOV, MPEG, WEBM', + formats: 'MP4, MOV, AVI, WEBM', }, ]; diff --git a/web/src/views/Conversation/components/FileUpload.tsx b/web/src/views/Conversation/components/FileUpload.tsx index 6aeb2dd5..48370fcc 100644 --- a/web/src/views/Conversation/components/FileUpload.tsx +++ b/web/src/views/Conversation/components/FileUpload.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-06 21:09:42 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-17 14:42:31 + * @Last Modified time: 2026-03-18 20:32:54 */ /** * File Upload Component @@ -71,6 +71,12 @@ const transform_file_type = { 'application/vnd.ms-powerpoint': 'document/ppt', 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'document/pptx', + + 'application/vnd.ms-excel': 'document/xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'document/xlsx', + 'text/csv': 'document/csv', + + 'application/json': 'document/json' } // Mapping of file extensions to MIME types const ALL_FILE_TYPE: { @@ -88,6 +94,13 @@ const ALL_FILE_TYPE: { ppt: 'application/vnd.ms-powerpoint', pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + xls: 'application/vnd.ms-excel', + xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + + csv: 'text/csv', + + json: 'application/json', + jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx index e120af49..9774a8df 100644 --- a/web/src/views/Conversation/index.tsx +++ b/web/src/views/Conversation/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:58:03 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 15:35:05 + * @Last Modified time: 2026-03-18 20:54:00 */ /** * Conversation Page @@ -160,7 +160,9 @@ const Conversation: FC = () => { role: 'user', content: message, created_at: Date.now(), - files + meta_data: { + files + }, }]) } @@ -185,7 +187,7 @@ const Conversation: FC = () => { { ...lastMsg, content: lastMsg.content + content, - audioUrl: audio_url + meta_data: { audio_url } } ] } diff --git a/web/src/views/Workflow/components/Chat/Chat.tsx b/web/src/views/Workflow/components/Chat/Chat.tsx index 1e3a1b4a..ad46d2dc 100644 --- a/web/src/views/Workflow/components/Chat/Chat.tsx +++ b/web/src/views/Workflow/components/Chat/Chat.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-06 21:10:56 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 19:31:28 + * @Last Modified time: 2026-03-18 20:46:35 */ /** * Workflow Chat Component @@ -151,10 +151,14 @@ const Chat = forwardRef [...prev, { role: 'user', content: message, created_at: Date.now(), + meta_data: { + files + }, }]) setChatList(prev => [...prev, { role: 'assistant', @@ -338,7 +342,6 @@ const Chat = forwardRef Date: Wed, 18 Mar 2026 21:10:41 +0800 Subject: [PATCH 12/14] feat(web): multi_agent app not support share --- web/src/views/ApplicationConfig/ReleasePage.tsx | 4 ++-- web/src/views/ApplicationManagement/index.tsx | 5 +++-- .../views/Conversation/components/UploadFileListModal.tsx | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/web/src/views/ApplicationConfig/ReleasePage.tsx b/web/src/views/ApplicationConfig/ReleasePage.tsx index ab34576a..efaedea3 100644 --- a/web/src/views/ApplicationConfig/ReleasePage.tsx +++ b/web/src/views/ApplicationConfig/ReleasePage.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:41 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 20:01:31 + * @Last Modified time: 2026-03-18 20:57:24 */ import { type FC, useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; @@ -132,7 +132,7 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres {data?.type !== 'multi_agent' && } {data.current_release_id !== selectedVersion.id && } - + {data?.type !== 'multi_agent' && } } diff --git a/web/src/views/ApplicationManagement/index.tsx b/web/src/views/ApplicationManagement/index.tsx index c9f57268..f67d9160 100644 --- a/web/src/views/ApplicationManagement/index.tsx +++ b/web/src/views/ApplicationManagement/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:34:12 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 10:50:33 + * @Last Modified time: 2026-03-18 21:00:12 */ /** * Application Management Page @@ -143,7 +143,7 @@ const ApplicationManagement: React.FC = () => { +
Date: Wed, 18 Mar 2026 21:25:59 +0800 Subject: [PATCH 13/14] fix(web): workflow header hidden operate --- .../views/ApplicationConfig/components/ConfigHeader.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx index bab3fd74..e2e1cf6f 100644 --- a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx +++ b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:52 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-18 15:40:53 + * @Last Modified time: 2026-03-18 21:25:23 */ import { type FC, useRef, useMemo, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; @@ -183,6 +183,7 @@ const ConfigHeader: FC = ({ appRef?.current?.handleSaveFeaturesConfig?.(value) onFeaturesChange?.(value) }, [appRef, onFeaturesChange]) + return ( <>
@@ -211,9 +212,9 @@ const ConfigHeader: FC = ({ className={styles.tabs} />
- {application?.type === 'workflow' + {application?.type === 'workflow' && source !== 'sharing' ?
- + From f6efa0d71135de41cb31817e36b4dff79549f7fe Mon Sep 17 00:00:00 2001 From: Timebomb2018 <18868801967@163.com> Date: Wed, 18 Mar 2026 22:29:10 +0800 Subject: [PATCH 14/14] fix(agent): Reading of docx multimodal files; Multimodal attachment history record --- api/app/services/app_chat_service.py | 104 +++++++++++++++++-------- api/app/services/draft_run_service.py | 58 ++++++++++---- api/app/services/multimodal_service.py | 5 +- 3 files changed, 116 insertions(+), 51 deletions(-) diff --git a/api/app/services/app_chat_service.py b/api/app/services/app_chat_service.py index cd9d3e81..58beea53 100644 --- a/api/app/services/app_chat_service.py +++ b/api/app/services/app_chat_service.py @@ -24,6 +24,7 @@ from app.services.model_service import ModelApiKeyService from app.services.multi_agent_orchestrator import MultiAgentOrchestrator from app.services.multimodal_service import MultimodalService from app.services.workflow_service import WorkflowService +from app.schemas import FileType logger = get_business_logger() @@ -156,20 +157,6 @@ class AppChatService: files=processed_files # 传递处理后的文件 ) - # 保存消息 - message_id = self.conversation_service.save_conversation_messages( - conversation_id=conversation_id, - user_message=message, - assistant_message=result["content"], - meta_data={ - "usage": result.get("usage", { - "prompt_tokens": 0, - "completion_tokens": 0, - "total_tokens": 0 - }) - } - ) - ModelApiKeyService.record_api_key_usage(self.db, api_key_obj.id) elapsed_time = time.time() - start_time @@ -191,6 +178,40 @@ class AppChatService: tenant_id=tenant_id, workspace_id=workspace_id ) + # 构建用户消息内容(含多模态文件) + human_meta = { + "files": [] + } + assistant_meta = { + "model": api_key_obj.model_name, + "usage": result.get("usage", {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}), + "audio_url": None + } + if files: + for f in files: + # url = await MultimodalService(self.db).get_file_url(f) + human_meta["files"].append({ + "type": FileType.IMAGE, + "url": f.url + }) + + # 保存消息 + if audio_url: + assistant_meta["audio_url"] = audio_url + self.conversation_service.add_message( + conversation_id=conversation_id, + role="user", + content=message, + meta_data=human_meta + ) + ai_message = self.conversation_service.add_message( + conversation_id=conversation_id, + role="assistant", + content=result["content"], + meta_data=assistant_meta + ) + message_id = ai_message.id + return { "conversation_id": conversation_id, "message_id": str(message_id), @@ -344,24 +365,6 @@ class AppChatService: elapsed_time = time.time() - start_time - # 保存消息 - self.conversation_service.add_message( - conversation_id=conversation_id, - role="user", - content=message - ) - - self.conversation_service.add_message( - message_id=message_id, - conversation_id=conversation_id, - role="assistant", - content=full_content, - meta_data={ - "model": api_key_obj.model_name, - "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": total_tokens} - } - ) - ModelApiKeyService.record_api_key_usage(self.db, api_key_obj.id) # 发送结束事件(包含 suggested_questions、tts、citations) @@ -373,13 +376,48 @@ class AppChatService: {"model_name": api_key_obj.model_name, "api_key": api_key_obj.api_key, "api_base": api_key_obj.api_base}, {} ) - end_data["audio_url"] = await self.agent_service._generate_tts( + stream_audio_url = await self.agent_service._generate_tts( features_config, full_content, {"model_name": api_key_obj.model_name, "api_key": api_key_obj.api_key, "api_base": api_key_obj.api_base, "provider": api_key_obj.provider}, tenant_id=tenant_id, workspace_id=workspace_id ) + end_data["audio_url"] = stream_audio_url end_data["citations"] = self.agent_service._filter_citations(features_config, []) + + # 保存消息 + human_meta = { + "files":[] + } + assistant_meta = { + "model": api_key_obj.model_name, + "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": total_tokens}, + "audio_url": None + } + + if files: + for f in files: + # url = await MultimodalService(self.db).get_file_url(f) + human_meta["files"].append({ + "type": FileType.IMAGE, + "url": f.url + }) + + if stream_audio_url: + assistant_meta["audio_url"] = stream_audio_url + self.conversation_service.add_message( + conversation_id=conversation_id, + role="user", + content=message, + meta_data=human_meta + ) + self.conversation_service.add_message( + message_id=message_id, + conversation_id=conversation_id, + role="assistant", + content=full_content, + meta_data=assistant_meta + ) yield f"event: end\ndata: {json.dumps(end_data, ensure_ascii=False)}\n\n" logger.info( diff --git a/api/app/services/draft_run_service.py b/api/app/services/draft_run_service.py index 92b13bfc..5b8bbfa7 100644 --- a/api/app/services/draft_run_service.py +++ b/api/app/services/draft_run_service.py @@ -37,6 +37,7 @@ from app.services.model_parameter_merger import ModelParameterMerger from app.services.model_service import ModelApiKeyService from app.services.multimodal_service import MultimodalService from app.services.tool_service import ToolService +from app.schemas import FileType logger = get_business_logger() @@ -636,7 +637,13 @@ class AgentRunService: ModelApiKeyService.record_api_key_usage(self.db, api_key_config.get("api_key_id")) - # 9. 保存会话消息 + # 9. 生成 TTS audio_url(在保存消息前生成,以便一并存入 meta_data) + audio_url = await self._generate_tts( + features_config, result["content"], api_key_config, + tenant_id=tenant_id, workspace_id=workspace_id + ) if not sub_agent else None + + # 10. 保存会话消息 if not sub_agent: await self._save_conversation_message( conversation_id=conversation_id, @@ -650,7 +657,9 @@ class AgentRunService: "completion_tokens": 0, "total_tokens": 0 }) - } + }, + files=files, + audio_url=audio_url ) response = { @@ -666,10 +675,7 @@ class AgentRunService: features_config, result["content"], api_key_config, effective_params ) if not sub_agent else [], "citations": self._filter_citations(features_config, result.get("citations", [])), - "audio_url": await self._generate_tts( - features_config, result["content"], api_key_config, - tenant_id=tenant_id, workspace_id=workspace_id - ) if not sub_agent else None, + "audio_url": audio_url, } logger.info( @@ -878,7 +884,13 @@ class AgentRunService: "total_tokens": total_tokens }) - # 10. 保存会话消息 + # 10. 生成 audio_url(在保存消息前生成,以便一并存入 meta_data) + stream_audio_url = await self._generate_tts( + features_config, full_content, api_key_config, + tenant_id=tenant_id, workspace_id=workspace_id + ) if not sub_agent else None + + # 11. 保存会话消息 if not sub_agent: await self._save_conversation_message( conversation_id=conversation_id, @@ -888,10 +900,12 @@ class AgentRunService: user_id=user_id, meta_data={ "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": total_tokens} - } + }, + files=files, + audio_url=stream_audio_url ) - # 11. 发送结束事件(包含 suggested_questions 和 tts) + # 12. 发送结束事件(包含 suggested_questions 和 tts) end_data: Dict[str, Any] = { "conversation_id": conversation_id, "elapsed_time": elapsed_time, @@ -901,10 +915,7 @@ class AgentRunService: end_data["suggested_questions"] = await self._generate_suggested_questions( features_config, full_content, api_key_config, effective_params ) - end_data["audio_url"] = await self._generate_tts( - features_config, full_content, api_key_config, - tenant_id=tenant_id, workspace_id=workspace_id - ) + end_data["audio_url"] = stream_audio_url end_data["citations"] = self._filter_citations(features_config, []) yield self._format_sse_event("end", end_data) @@ -1143,7 +1154,9 @@ class AgentRunService: assistant_message: str, meta_data: dict, app_id: Optional[uuid.UUID] = None, - user_id: Optional[str] = None + user_id: Optional[str] = None, + files: Optional[List[FileInput]] = None, + audio_url: Optional[str] = None ) -> None: """保存会话消息(会话已通过 _ensure_conversation 确保存在) @@ -1162,13 +1175,26 @@ class AgentRunService: conv_uuid = uuid.UUID(conversation_id) # 保存消息(会话已经存在) + human_meta = { + "files": [] + } + if files: + for f in files: + # url = await MultimodalService(self.db).get_file_url(f) + human_meta["files"].append({ + "type": FileType.IMAGE, + "url": f.url + }) # 保存用户消息 conversation_service.add_message( conversation_id=conv_uuid, role="user", - content=user_message + content=user_message, + meta_data=human_meta ) - # 保存助手消息 + # 保存助手消息(含 audio_url) + if audio_url: + meta_data["audio_url"] = audio_url conversation_service.add_message( conversation_id=conv_uuid, role="assistant", diff --git a/api/app/services/multimodal_service.py b/api/app/services/multimodal_service.py index 1f0e1cc2..f0c7cee2 100644 --- a/api/app/services/multimodal_service.py +++ b/api/app/services/multimodal_service.py @@ -41,7 +41,8 @@ TEXT_MIME = ['text/plain', 'text/x-markdown'] PDF_MIME = ['application/pdf'] DOC_MIME = [ 'application/msword', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/zip' ] XLSX_MIME = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', @@ -590,7 +591,7 @@ class MultimodalService: return file_content.decode("utf-8") elif file_mime_type in PDF_MIME: return await self._extract_pdf_text(file_content) - elif file_mime_type in DOC_MIME: + elif file_mime_type in DOC_MIME and file.file_type.endswith(('docx', 'doc')): return await self._extract_word_text(file_content) elif file_mime_type in XLSX_MIME and file.file_type.endswith(("xlsx", "xls")): return await self._extract_xlsx_text(file_content)