diff --git a/api/app/controllers/model_controller.py b/api/app/controllers/model_controller.py index 0de3d4fe..6204a745 100644 --- a/api/app/controllers/model_controller.py +++ b/api/app/controllers/model_controller.py @@ -371,6 +371,11 @@ def update_model( if model_data.type is not None or model_data.provider is not None: raise BusinessException("不允许更改模型类型和供应商", BizCode.INVALID_PARAMETER) + + if model_data.is_active: + active_keys = ModelApiKeyService.get_api_keys_by_model(db=db, model_config_id=model_id, is_active=model_data.is_active) + if not active_keys: + raise BusinessException("请先为该模型配置可用的 API Key", BizCode.INVALID_PARAMETER) try: api_logger.debug(f"开始更新模型配置: model_id={model_id}") diff --git a/api/app/core/config.py b/api/app/core/config.py index ba17da93..bbe327b6 100644 --- a/api/app/core/config.py +++ b/api/app/core/config.py @@ -192,8 +192,10 @@ class Settings: # Celery configuration (internal) # NOTE: 变量名不以 CELERY_ 开头,避免被 Celery CLI 的前缀匹配机制劫持 # 详见 docs/celery-env-bug-report.md - REDIS_DB_CELERY_BROKER: int = int(os.getenv("REDIS_DB_CELERY_BROKER", "1")) - REDIS_DB_CELERY_BACKEND: int = int(os.getenv("REDIS_DB_CELERY_BACKEND", "2")) + # 默认使用 Redis DB 3 (broker) 和 DB 4 (backend),与业务缓存 (DB 1/2) 隔离 + # 多人共用同一 Redis 时,每位开发者应在 .env 中配置不同的 DB 编号避免任务互相干扰 + REDIS_DB_CELERY_BROKER: int = int(os.getenv("REDIS_DB_CELERY_BROKER", "3")) + REDIS_DB_CELERY_BACKEND: int = int(os.getenv("REDIS_DB_CELERY_BACKEND", "4")) # SMTP Email Configuration SMTP_SERVER: str = os.getenv("SMTP_SERVER", "smtp.gmail.com") diff --git a/api/app/schemas/model_schema.py b/api/app/schemas/model_schema.py index ea4183a5..4f3878ce 100644 --- a/api/app/schemas/model_schema.py +++ b/api/app/schemas/model_schema.py @@ -23,6 +23,7 @@ class ModelConfigBase(BaseModel): load_balance_strategy: Optional[str] = Field(LoadBalanceStrategy.NONE.value, description="负载均衡策略") capability: List[str] = Field(default_factory=list, description="模型能力列表") is_omni: bool = Field(False, description="是否为Omni模型") + model_id: Optional[uuid.UUID] = Field(None, description="基础模型ID") class ApiKeyCreateNested(BaseModel): diff --git a/api/app/services/app_service.py b/api/app/services/app_service.py index a248f869..5a799937 100644 --- a/api/app/services/app_service.py +++ b/api/app/services/app_service.py @@ -703,7 +703,7 @@ class AppService: self.db.flush() # 如果是 agent 类型,复制 AgentConfig - if source_app.type == "agent": + if source_app.type == AppType.AGENT: source_config = self.db.query(AgentConfig).filter( AgentConfig.app_id == source_app.id ).first() @@ -725,6 +725,50 @@ class AppService: ) self.db.add(new_config) + elif source_app.type == AppType.WORKFLOW: + source_config = self.db.query(WorkflowConfig).filter( + WorkflowConfig.app_id == source_app.id + ).first() + + if source_config: + new_config = WorkflowConfig( + id=uuid.uuid4(), + app_id=new_app.id, + nodes=source_config.nodes.copy() if source_config.nodes else [], + edges=source_config.edges.copy() if source_config.edges else [], + variables=source_config.variables.copy() if source_config.variables else [], + execution_config=source_config.execution_config.copy() if source_config.execution_config else {}, + triggers=source_config.triggers.copy() if source_config.triggers else [], + is_active=True, + created_at=now, + updated_at=now, + ) + self.db.add(new_config) + + elif source_app.type == AppType.MULTI_AGENT: + source_config = self.db.query(MultiAgentConfig).filter( + MultiAgentConfig.app_id == source_app.id + ).first() + + if source_config: + new_config = MultiAgentConfig( + id=uuid.uuid4(), + app_id=new_app.id, + master_agent_id=source_config.master_agent_id, + master_agent_name=source_config.master_agent_name, + default_model_config_id=source_config.default_model_config_id, + model_parameters=source_config.model_parameters, + orchestration_mode=source_config.orchestration_mode, + sub_agents=source_config.sub_agents.copy() if source_config.sub_agents else [], + routing_rules=source_config.routing_rules.copy() if source_config.routing_rules else None, + execution_config=source_config.execution_config.copy() if source_config.execution_config else {}, + aggregation_strategy=source_config.aggregation_strategy, + is_active=True, + created_at=now, + updated_at=now, + ) + self.db.add(new_config) + self.db.commit() self.db.refresh(new_app) diff --git a/api/app/services/model_service.py b/api/app/services/model_service.py index cba25f32..a7398504 100644 --- a/api/app/services/model_service.py +++ b/api/app/services/model_service.py @@ -780,6 +780,7 @@ class ModelBaseService: "description": model_base.description, "capability": model_base.capability, "is_omni": model_base.is_omni, + "is_active": False, "is_composite": False } model_config = ModelConfigRepository.create(db, model_config_data) diff --git a/web/src/components/Chat/ChatInput.tsx b/web/src/components/Chat/ChatInput.tsx index 49fb65d2..8c8dce1a 100644 --- a/web/src/components/Chat/ChatInput.tsx +++ b/web/src/components/Chat/ChatInput.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-10 16:46:14 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-04 18:42:49 + * @Last Modified time: 2026-03-06 13:36:20 */ import { type FC, useEffect, useMemo } from 'react' import { Flex, Input, Form } from 'antd' @@ -50,13 +50,13 @@ const ChatInput: FC = ({ const handleDelete = (file: any) => { - fileChange?.(fileList?.filter(item => item.uid !== file.uid) || []) + fileChange?.(fileList?.filter(item => file.url ? item.url !== file.url : item.uid !== file.uid) || []) } // Convert file object to preview URL const previewFileList = useMemo(() => { return fileList?.map(file => ({ ...file, - url: file.url || (file.originFileObj ? URL.createObjectURL(file.originFileObj) : file.thumbUrl) + url: file.thumbUrl || file.url || (file.originFileObj ? URL.createObjectURL(file.originFileObj) : undefined) })) || [] }, [fileList]) @@ -72,7 +72,7 @@ const ChatInput: FC = ({ {previewFileList.map((file) => { if (file.type.includes('image')) { return ( -
+
{file.name}
= ({ } if (file.type.includes('video')) { return ( -
+