diff --git a/README.md b/README.md index 1bc0ea53..a9d06f20 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ + [**文档中心**](https://xerrors.github.io/Yuxi-Know/) | [**视频演示**](https://www.bilibili.com/video/BV1DF14BTETq/) @@ -107,9 +108,7 @@ - 更多智能体开发套件 中间件、子智能体,更简洁,更易上手。 - - - + ## 快速开始 diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index b98737f3..f5926306 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -741,6 +741,11 @@ async def update_thread( ) + +# ================================ +# > === 附件管理分组 === +# ================================ + @chat.post("/thread/{thread_id}/attachments", response_model=AttachmentResponse) async def upload_thread_attachment( thread_id: str, diff --git a/src/agents/common/toolkits/mysql/tools.py b/src/agents/common/toolkits/mysql/tools.py index 17a681d0..f163b24d 100644 --- a/src/agents/common/toolkits/mysql/tools.py +++ b/src/agents/common/toolkits/mysql/tools.py @@ -52,9 +52,9 @@ class TableListModel(BaseModel): pass -@tool(name_or_callable="查询表名及说明", args_schema=TableListModel) +@tool(name_or_callable="mysql_list_tables", args_schema=TableListModel) def mysql_list_tables() -> str: - """获取数据库中的所有表名 + """【查询表名及说明】获取数据库中的所有表名 这个工具用来列出当前数据库中所有的表名,帮助你了解数据库的结构。 """ @@ -107,9 +107,9 @@ class TableDescribeModel(BaseModel): table_name: str = Field(description="要查询的表名", example="users") -@tool(name_or_callable="描述表", args_schema=TableDescribeModel) +@tool(name_or_callable="mysql_describe_table", args_schema=TableDescribeModel) def mysql_describe_table(table_name: Annotated[str, "要查询结构的表名"]) -> str: - """获取指定表的详细结构信息 + """【描述表】获取指定表的详细结构信息 这个工具用来查看表的字段信息、数据类型、是否允许NULL、默认值、键类型等。 帮助你了解表的结构,以便编写正确的SQL查询。 @@ -203,12 +203,12 @@ class QueryModel(BaseModel): timeout: int | None = Field(default=60, description="查询超时时间(秒),默认60秒,最大600秒", ge=1, le=600) -@tool(name_or_callable="执行 SQL 查询", args_schema=QueryModel) +@tool(name_or_callable="mysql_query", args_schema=QueryModel) def mysql_query( sql: Annotated[str, "要执行的SQL查询语句(只能是SELECT语句)"], timeout: Annotated[int | None, "查询超时时间(秒),默认60秒,最大600秒"] = 60, ) -> str: - """执行只读的SQL查询语句 + """【执行 SQL 查询】执行只读的SQL查询语句 这个工具用来执行SQL查询并返回结果。支持复杂的SELECT查询,包括JOIN、GROUP BY等。 注意:只能执行查询操作,不能修改数据。 diff --git a/src/agents/reporter/graph.py b/src/agents/reporter/graph.py index 0aadf343..5d1731b7 100644 --- a/src/agents/reporter/graph.py +++ b/src/agents/reporter/graph.py @@ -1,16 +1,25 @@ from dataclasses import dataclass, field from typing import Annotated +from deepagents.backends import StateBackend +from deepagents.middleware.filesystem import FilesystemMiddleware from langchain.agents import create_agent from src.agents.common import BaseAgent, BaseContext, load_chat_model from src.agents.common.middlewares import ( RuntimeConfigMiddleware, + save_attachments_to_fs, ) from src.agents.common.toolkits.mysql import get_mysql_tools from src.services.mcp_service import get_mcp_server_names, get_tools_from_all_servers from src.utils import logger + +def _create_fs_backend(rt): + """创建文件存储后端""" + return StateBackend(rt) + + PROMPT = """你的任务是根据用户的指令,使用数据库工具和图表绘制工具,构建 SQL 查询报告。 你需要根据用户的指令,生成相应的 SQL 查询,并将查询结果以报表的形式返回给用户。 在生成报表时,你可以调用工具生成图表,以更直观地展示数据。 @@ -52,6 +61,10 @@ class SqlReporterAgent(BaseAgent): "MySQL 工具默认启用,无法选择,mcp 默认启用 Charts MCPs。" ) context_schema = ReporterContext + capabilities = [ + "file_upload", + "files", + ] def __init__(self, **kwargs): super().__init__(**kwargs) @@ -66,7 +79,9 @@ class SqlReporterAgent(BaseAgent): system_prompt=context.system_prompt, tools=get_mysql_tools(), # MySQL 工具默认启用,这里添加的 tools,不会在工具选择框中出现 middleware=[ + FilesystemMiddleware(backend=_create_fs_backend), # 文件系统后端 RuntimeConfigMiddleware(extra_tools=all_mcp_tools), + save_attachments_to_fs, # 附件保存到文件系统 ], checkpointer=await self._get_checkpointer(), ) diff --git a/src/config/static/models.py b/src/config/static/models.py index d1b83b8d..75d6856e 100644 --- a/src/config/static/models.py +++ b/src/config/static/models.py @@ -49,9 +49,9 @@ DEFAULT_CHAT_MODEL_PROVIDERS: dict[str, ChatModelProvider] = { name="OpenAI", url="https://platform.openai.com/docs/models", base_url="https://api.openai.com/v1", - default="gpt-4o-mini", + default="gpt-5-mini", env="OPENAI_API_KEY", - models=["gpt-4", "gpt-4o", "gpt-4o-mini"], + models=["gpt-5.2", "gpt-5-mini", "gpt-5.2-pro"], ), "deepseek": ChatModelProvider( name="DeepSeek", @@ -65,22 +65,21 @@ DEFAULT_CHAT_MODEL_PROVIDERS: dict[str, ChatModelProvider] = { name="智谱AI (Zhipu)", url="https://open.bigmodel.cn/dev/api", base_url="https://open.bigmodel.cn/api/paas/v4/", - default="glm-4.5-flash", + default="glm-4.7-flash", env="ZHIPUAI_API_KEY", - models=["glm-4.6", "glm-4.5-air", "glm-4.5-flash"], + models=["glm-5", "glm-4.5-air", "glm-4.7-flash"], ), "siliconflow": ChatModelProvider( name="SiliconFlow", url="https://cloud.siliconflow.cn/models", base_url="https://api.siliconflow.cn/v1", - default="deepseek-ai/DeepSeek-V3.2", + default="Pro/deepseek-ai/DeepSeek-V3.2", env="SILICONFLOW_API_KEY", models=[ - "deepseek-ai/DeepSeek-V3.2", - "Qwen/Qwen3-235B-A22B-Thinking-2507", - "Qwen/Qwen3-235B-A22B-Instruct-2507", - "moonshotai/Kimi-K2-Instruct-0905", - "zai-org/GLM-4.6", + "Pro/deepseek-ai/DeepSeek-V3.2", + "Pro/MiniMaxAI/MiniMax-M2.5", + "Pro/zai-org/GLM-5", + "Pro/moonshotai/Kimi-K2.5", ], ), # "together": ChatModelProvider( @@ -101,33 +100,31 @@ DEFAULT_CHAT_MODEL_PROVIDERS: dict[str, ChatModelProvider] = { "qwen-max-latest", "qwen-plus-latest", "qwen-turbo-latest", - "qwen3-235b-a22b-thinking-2507", - "qwen3-235b-a22b-instruct-2507", ], ), "ark": ChatModelProvider( name="豆包(Ark)", url="https://console.volcengine.com/ark/region:ark+cn-beijing/model", base_url="https://ark.cn-beijing.volces.com/api/v3", - default="doubao-seed-1-6-250615", + default="doubao-seed-2-0-lite-260215", env="ARK_API_KEY", models=[ - "doubao-seed-1-6-250615", - "doubao-seed-1-6-thinking-250715", - "doubao-seed-1-6-flash-250715", + "doubao-seed-2-0-pro-260215", + "doubao-seed-2-0-lite-260215", + "doubao-seed-2-0-mini-260215", ], ), "openrouter": ChatModelProvider( name="OpenRouter", url="https://openrouter.ai/models", base_url="https://openrouter.ai/api/v1", - default="openai/gpt-4o", + default="x-ai/grok-4.1-fast", env="OPENROUTER_API_KEY", models=[ - "openai/gpt-4o", + "anthropic/claude-opus-4.6", + "anthropic/claude-sonnet-4.5", + "x-ai/grok-4.1-fast", "x-ai/grok-4", - "google/gemini-2.5-pro", - "anthropic/claude-sonnet-4", ], ), # "moonshot": ChatModelProvider( @@ -148,7 +145,7 @@ DEFAULT_CHAT_MODEL_PROVIDERS: dict[str, ChatModelProvider] = { base_url="https://api-inference.modelscope.cn/v1/", default="deepseek-ai/DeepSeek-V3.2", env="MODELSCOPE_ACCESS_TOKEN", - models=["Qwen/Qwen3-32B", "deepseek-ai/DeepSeek-V3.2"], + models=["ZhipuAI/GLM-5", "ZhipuAI/GLM-4.7-Flash", "MiniMax/MiniMax-M2.5", "moonshotai/Kimi-K2.5", ""], ), } diff --git a/src/knowledge/base.py b/src/knowledge/base.py index f773bfe6..ebfc09c6 100644 --- a/src/knowledge/base.py +++ b/src/knowledge/base.py @@ -225,7 +225,7 @@ class KnowledgeBase(ABC): # Save to metadata self.files_meta[file_id] = metadata - await self._save_metadata() + await self._persist_file(file_id) return metadata @@ -273,7 +273,7 @@ class KnowledgeBase(ABC): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) # Add to processing queue self._add_to_processing_queue(file_id) @@ -310,7 +310,7 @@ class KnowledgeBase(ABC): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) return self.files_meta[file_id] @@ -323,7 +323,7 @@ class KnowledgeBase(ABC): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) raise @@ -358,7 +358,7 @@ class KnowledgeBase(ABC): logger.debug(f"[update_file_params] file_id={file_id}, updated_params={current_params}") - await self._save_metadata() + await self._persist_file(file_id) async def _save_markdown_to_minio(self, db_id: str, file_id: str, content: str) -> str: """Save markdown content to MinIO and return HTTP URL""" @@ -453,7 +453,7 @@ class KnowledgeBase(ABC): "metadata": kwargs, "created_at": utc_isoformat(), } - await self._save_metadata() + await self._persist_kb(db_id) # 创建工作目录 working_dir = os.path.join(self.work_dir, db_id) @@ -518,7 +518,7 @@ class KnowledgeBase(ABC): "path": folder_name, "file_type": "folder", } - await self._save_metadata() + await self._persist_file(folder_id) return self.files_meta[folder_id] @abstractmethod @@ -860,7 +860,7 @@ class KnowledgeBase(ABC): current = parent_meta.get("parent_id") meta["parent_id"] = new_parent_id - await self._save_metadata() + await self._persist_file(file_id) return meta @abstractmethod @@ -1146,3 +1146,78 @@ class KnowledgeBase(ABC): } if existing is None: await eval_repo.create_benchmark(payload) + + async def _persist_file(self, file_id: str) -> None: + """只保存单个文件到数据库,避免全量遍历""" + from src.repositories.knowledge_file_repository import KnowledgeFileRepository + + file_repo = KnowledgeFileRepository() + + if file_id not in self.files_meta: + return + + meta = self.files_meta[file_id] + db_id = meta.get("database_id") + if not db_id: + return + + await file_repo.upsert( + file_id=file_id, + data={ + "db_id": db_id, + "parent_id": meta.get("parent_id"), + "filename": meta.get("filename") or "", + "original_filename": meta.get("original_filename"), + "file_type": meta.get("file_type"), + "path": meta.get("path"), + "minio_url": meta.get("minio_url"), + "markdown_file": meta.get("markdown_file"), + "status": meta.get("status"), + "content_hash": meta.get("content_hash"), + "file_size": meta.get("size"), + "content_type": meta.get("content_type"), + "processing_params": meta.get("processing_params"), + "is_folder": meta.get("is_folder", False), + "error_message": meta.get("error"), + "created_by": str(meta.get("created_by")) if meta.get("created_by") else None, + "updated_by": str(meta.get("updated_by")) if meta.get("updated_by") else None, + }, + ) + + async def _persist_kb(self, db_id: str) -> None: + """只保存单个知识库到数据库,避免全量遍历""" + from src.repositories.knowledge_base_repository import KnowledgeBaseRepository + + kb_repo = KnowledgeBaseRepository() + + if db_id not in self.databases_meta: + return + + meta = self.databases_meta[db_id] + existing = await kb_repo.get_by_id(db_id) + payload = { + "db_id": db_id, + "name": meta.get("name") or db_id, + "description": meta.get("description"), + "kb_type": meta.get("kb_type") or self.kb_type, + "embed_info": meta.get("embed_info"), + "llm_info": meta.get("llm_info"), + "query_params": meta.get("query_params"), + "additional_params": meta.get("metadata") or {}, + } + + if existing is None: + await kb_repo.create(payload) + else: + await kb_repo.update( + db_id, + { + "name": payload["name"], + "description": payload["description"], + "kb_type": payload["kb_type"], + "embed_info": payload["embed_info"], + "llm_info": payload["llm_info"], + "query_params": payload["query_params"], + "additional_params": payload["additional_params"], + }, + ) diff --git a/src/knowledge/implementations/lightrag.py b/src/knowledge/implementations/lightrag.py index e88e3ea1..2744bcf2 100644 --- a/src/knowledge/implementations/lightrag.py +++ b/src/knowledge/implementations/lightrag.py @@ -313,7 +313,7 @@ class LightRagKB(KnowledgeBase): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) # Add to processing queue self._add_to_processing_queue(file_id) @@ -358,7 +358,7 @@ class LightRagKB(KnowledgeBase): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) return self.files_meta[file_id] @@ -369,7 +369,7 @@ class LightRagKB(KnowledgeBase): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) raise finally: @@ -416,7 +416,7 @@ class LightRagKB(KnowledgeBase): ) self.files_meta[file_id]["processing_params"] = resolved_params self.files_meta[file_id]["status"] = "processing" - await self._save_metadata() + await self._persist_file(file_id) # 重新解析文件为 markdown if content_type != "file": @@ -447,7 +447,7 @@ class LightRagKB(KnowledgeBase): # 更新元数据状态 self.files_meta[file_id]["status"] = "done" - await self._save_metadata() + await self._persist_file(file_id) # 从处理队列中移除 self._remove_from_processing_queue(file_id) @@ -463,7 +463,7 @@ class LightRagKB(KnowledgeBase): logger.error(f"更新{content_type} {file_path} 失败: {error_msg}, {traceback.format_exc()}") self.files_meta[file_id]["status"] = "failed" self.files_meta[file_id]["error"] = error_msg - await self._save_metadata() + await self._persist_file(file_id) # 从处理队列中移除 self._remove_from_processing_queue(file_id) @@ -576,7 +576,6 @@ class LightRagKB(KnowledgeBase): from src.repositories.knowledge_file_repository import KnowledgeFileRepository await KnowledgeFileRepository().delete(file_id) - await self._save_metadata() async def get_file_basic_info(self, db_id: str, file_id: str) -> dict: """获取文件基本信息(仅元数据)""" diff --git a/src/knowledge/implementations/milvus.py b/src/knowledge/implementations/milvus.py index 5f97ba40..862f72b9 100644 --- a/src/knowledge/implementations/milvus.py +++ b/src/knowledge/implementations/milvus.py @@ -338,7 +338,7 @@ class MilvusKB(KnowledgeBase): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) return self.files_meta[file_id] except Exception as e: @@ -349,7 +349,7 @@ class MilvusKB(KnowledgeBase): self.files_meta[file_id]["updated_at"] = utc_isoformat() if operator_id: self.files_meta[file_id]["updated_by"] = operator_id - await self._save_metadata() + await self._persist_file(file_id) raise finally: @@ -402,7 +402,7 @@ class MilvusKB(KnowledgeBase): ) self.files_meta[file_id]["processing_params"] = resolved_params self.files_meta[file_id]["status"] = "processing" - await self._save_metadata() + await self._persist_file(file_id) # 重新解析文件为 markdown if content_type != "file": @@ -440,7 +440,7 @@ class MilvusKB(KnowledgeBase): # 更新元数据状态 async with self._metadata_lock: self.files_meta[file_id]["status"] = "done" - await self._save_metadata() + await self._persist_file(file_id) # 从处理队列中移除 self._remove_from_processing_queue(file_id) @@ -455,7 +455,7 @@ class MilvusKB(KnowledgeBase): logger.error(f"更新{content_type} {file_path} 失败: {e}, {traceback.format_exc()}") async with self._metadata_lock: self.files_meta[file_id]["status"] = "failed" - await self._save_metadata() + await self._persist_file(file_id) # 从处理队列中移除 self._remove_from_processing_queue(file_id) @@ -712,7 +712,6 @@ class MilvusKB(KnowledgeBase): from src.repositories.knowledge_file_repository import KnowledgeFileRepository await KnowledgeFileRepository().delete(file_id) - await self._save_metadata() async def get_file_basic_info(self, db_id: str, file_id: str) -> dict: """获取文件基本信息(仅元数据)""" diff --git a/src/services/chat_stream_service.py b/src/services/chat_stream_service.py index 0d91708b..1ac94927 100644 --- a/src/services/chat_stream_service.py +++ b/src/services/chat_stream_service.py @@ -452,8 +452,7 @@ async def stream_agent_chat( if agent_state: yield make_chunk(status="agent_state", agent_state=agent_state, meta=meta) - yield make_chunk(status="finished", meta=meta) - + # 先存储数据库,再返回 finished,避免前端查询时数据未落库 await save_messages_from_langgraph_state( agent_instance=agent, thread_id=thread_id, @@ -461,6 +460,8 @@ async def stream_agent_chat( config_dict=langgraph_config, ) + yield make_chunk(status="finished", meta=meta) + except (asyncio.CancelledError, ConnectionError) as e: logger.warning(f"Client disconnected, cancelling stream: {e}") @@ -593,8 +594,8 @@ async def stream_agent_resume( yield chunk meta["time_cost"] = asyncio.get_event_loop().time() - start_time - yield make_resume_chunk(status="finished", meta=meta) + # 先存储数据库,再返回 finished,避免前端查询时数据未落库 conv_repo = ConversationRepository(db) await save_messages_from_langgraph_state( agent_instance=agent, @@ -603,6 +604,8 @@ async def stream_agent_resume( config_dict=langgraph_config, ) + yield make_resume_chunk(status="finished", meta=meta) + except (asyncio.CancelledError, ConnectionError) as e: logger.warning(f"Client disconnected during resume: {e}") diff --git a/src/services/conversation_service.py b/src/services/conversation_service.py index e0ef33f1..684f180c 100644 --- a/src/services/conversation_service.py +++ b/src/services/conversation_service.py @@ -2,6 +2,7 @@ import uuid from datetime import UTC, datetime from fastapi import HTTPException, UploadFile +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession from src.agents import agent_manager @@ -78,6 +79,8 @@ async def _sync_thread_attachment_state( graph = await agent.get_graph() config = {"configurable": {"thread_id": thread_id, "user_id": str(user_id)}} + + # 先获取现有 state,保留非附件文件 state = await graph.aget_state(config) state_values = getattr(state, "values", {}) if state else {} existing_files = state_values.get("files", {}) if isinstance(state_values, dict) else {} @@ -97,6 +100,7 @@ async def _sync_thread_attachment_state( for removed_path in prev_attachment_paths - next_attachment_paths: file_updates[removed_path] = None + # 使用 Command 确保 reducer 被正确应用 await graph.aupdate_state( config=config, values={ @@ -265,7 +269,7 @@ async def upload_thread_attachment_view( "uploaded_at": utc_isoformat(), "truncated": conversion.truncated, "file_path": file_path, # 用于 StateBackend,前端不返回此字段 - "minio_url": minio_url, + "minio_url": minio_url, # 暂未使用 } await conv_repo.add_attachment(conversation.id, attachment_record) all_attachments = await conv_repo.get_attachments(conversation.id) diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 4b5f7275..514dd2c0 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -1431,7 +1431,7 @@ const handleSendMessage = async ({ image } = {}) => { } finally { threadState.streamAbortController = null // 异步加载历史记录,保持当前消息显示直到历史记录加载完成 - fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally( + fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }).finally( () => { // 历史记录加载完成后,安全地清空当前进行中的对话 resetOnGoingConv(threadId) @@ -1515,7 +1515,7 @@ const handleApprovalWithStream = async (approved) => { } // 异步加载历史记录,保持当前消息显示直到历史记录加载完成 - fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally( + fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }).finally( () => { resetOnGoingConv(threadId) scrollController.scrollToBottom() @@ -1815,7 +1815,7 @@ watch( background: var(--gray-0); border-radius: 12px; box-shadow: 0 4px 20px var(--shadow-1); - border: 1px solid var(--gray-200); + border: 1px solid var(--gray-150); min-width: 0; will-change: flex-basis; } diff --git a/web/src/components/AgentInputArea.vue b/web/src/components/AgentInputArea.vue index a7e91b56..4fea3789 100644 --- a/web/src/components/AgentInputArea.vue +++ b/web/src/components/AgentInputArea.vue @@ -1,6 +1,7 @@ + + diff --git a/web/src/components/ToolCallingResult/tools/MysqlListTablesTool.vue b/web/src/components/ToolCallingResult/tools/MysqlListTablesTool.vue new file mode 100644 index 00000000..104e3f49 --- /dev/null +++ b/web/src/components/ToolCallingResult/tools/MysqlListTablesTool.vue @@ -0,0 +1,67 @@ + + + + + 列出数据库表 + + + + + + {{ formatResult(resultContent) }} + + + + + + + + diff --git a/web/src/components/ToolCallingResult/tools/MysqlQueryTool.vue b/web/src/components/ToolCallingResult/tools/MysqlQueryTool.vue new file mode 100644 index 00000000..11389523 --- /dev/null +++ b/web/src/components/ToolCallingResult/tools/MysqlQueryTool.vue @@ -0,0 +1,121 @@ + + + + + 执行SQL查询: + {{ + truncateSql(extractSql(toolCall.args || toolCall.function?.arguments)) + }} + + + + + + {{ extractSql(args) }} + + + + + + {{ formatResult(resultContent) }} + + + + + + + + diff --git a/web/src/views/DataBaseInfoView.vue b/web/src/views/DataBaseInfoView.vue index 512929f5..e90d027c 100644 --- a/web/src/views/DataBaseInfoView.vue +++ b/web/src/views/DataBaseInfoView.vue @@ -596,20 +596,21 @@ const handleMouseUp = () => { display: flex; align-items: center; gap: 6px; - padding: 4px 8px; - background: var(--color-warning-50); - border-radius: 4px; + padding: 6px 12px; + background: var(--color-info-50); + border-left: 3px solid var(--color-info-500); + border-radius: 2px; font-size: 13px; - color: var(--color-warning-700); + color: var(--color-info-700); cursor: pointer; transition: all 0.2s; &:hover { - background: var(--color-warning-100); + background: var(--color-info-100); } svg { - color: var(--color-warning-700); + color: var(--color-info-500); } } } @@ -660,7 +661,6 @@ const handleMouseUp = () => { :deep(.ant-tabs-nav) { margin-bottom: 0; // background-color: var(--gray-0); - border-bottom: 1px solid var(--gray-200); } :deep(.ant-tabs-extra-content) {
{{ formatResult(resultContent) }}
{{ extractSql(args) }}