From ef99f6429153867789bcbaa899f84d19b5442b5a Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Tue, 21 Oct 2025 00:47:04 +0800 Subject: [PATCH 001/210] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0=20ag?= =?UTF-8?q?ent=20=E8=BF=90=E8=A1=8C=E5=99=A8=E7=B1=BB=E5=9E=8B=E5=8F=8A?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 69 ++++++++++- .../src/components/shared/AstrBotConfigV4.vue | 110 +++++++++--------- 2 files changed, 122 insertions(+), 57 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 7977e4392..361f557fd 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -71,6 +71,9 @@ DEFAULT_CONFIG = { "streaming_response": False, "show_tool_use_status": False, "streaming_segmented": False, + "agent_runner_type": "local", + "dify_runner_provider_id": "", + "coze_runner_provider_id": "", "max_agent_step": 30, "tool_call_timeout": 60, }, @@ -1931,12 +1934,19 @@ CONFIG_METADATA_2 = { "streaming_segmented": { "type": "bool", }, + "agent_runner_type": { + "type": "string", + }, + "dify_runner_provider_id": { + "type": "string", + }, + "coze_runner_provider_id": { + "type": "string", + }, "max_agent_step": { - "description": "工具调用轮数上限", "type": "int", }, "tool_call_timeout": { - "description": "工具调用超时时间(秒)", "type": "int", }, }, @@ -2070,6 +2080,46 @@ CONFIG_METADATA_3 = { "ai_group": { "name": "AI 配置", "metadata": { + "agent_runner": { + "description": "Agent", + "type": "object", + "items": { + "provider_settings.agent_runner_type": { + "description": "执行器", + "type": "string", + "options": ["local", "dify", "coze"], + "labels": ["内置 Agent", "Dify", "Coze"], + }, + }, + }, + "dify_runner": { + "description": "Dify", + "type": "object", + "items": { + "provider_settings.dify_runner_provider_id": { + "description": "Dify 执行器提供商 ID", + "type": "string", + "_special": "select_dify_runner_provider", + }, + }, + "condition": { + "provider_settings.agent_runner_type": "dify", + }, + }, + "coze_runner": { + "description": "Coze", + "type": "object", + "items": { + "provider_settings.coze_runner_provider_id": { + "description": "Coze 执行器提供商 ID", + "type": "string", + "_special": "select_coze_runner_provider", + }, + }, + "condition": { + "provider_settings.agent_runner_type": "coze", + }, + }, "ai": { "description": "模型", "type": "object", @@ -2123,6 +2173,9 @@ CONFIG_METADATA_3 = { "type": "text", }, }, + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "persona": { "description": "人格", @@ -2134,6 +2187,9 @@ CONFIG_METADATA_3 = { "_special": "select_persona", }, }, + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "knowledgebase": { "description": "知识库", @@ -2145,6 +2201,9 @@ CONFIG_METADATA_3 = { "_special": "select_knowledgebase", }, }, + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "websearch": { "description": "网页搜索", @@ -2181,6 +2240,9 @@ CONFIG_METADATA_3 = { "type": "bool", }, }, + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "others": { "description": "其他配置", @@ -2248,6 +2310,9 @@ CONFIG_METADATA_3 = { "type": "bool", }, }, + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, }, }, diff --git a/dashboard/src/components/shared/AstrBotConfigV4.vue b/dashboard/src/components/shared/AstrBotConfigV4.vue index 6ae758dfb..08373595f 100644 --- a/dashboard/src/components/shared/AstrBotConfigV4.vue +++ b/dashboard/src/components/shared/AstrBotConfigV4.vue @@ -101,6 +101,21 @@ function shouldShowItem(itemMeta, itemKey) { return true } +// 检查最外层的 object 是否应该显示 +function shouldShowSection() { + const sectionMeta = props.metadata[props.metadataKey] + if (!sectionMeta?.condition) { + return true + } + for (const [conditionKey, expectedValue] of Object.entries(sectionMeta.condition)) { + const actualValue = getValueBySelector(props.iterable, conditionKey) + if (actualValue !== expectedValue) { + return false + } + } + return true +} + function hasVisibleItemsAfter(items, currentIndex) { const itemEntries = Object.entries(items) @@ -114,12 +129,27 @@ function hasVisibleItemsAfter(items, currentIndex) { return false } + +// 将 options 和 labels 转换为 v-select 的 items 格式 +function getSelectItems(itemMeta) { + if (!itemMeta?.options) { + return [] + } + if (itemMeta?.labels && itemMeta.labels.length === itemMeta.options.length) { + return itemMeta.options.map((value, index) => ({ + title: itemMeta.labels[index], + value: value + })) + } + return itemMeta.options +} - + From e74f6263837970a78ed78b25800431e33d6fc7c4 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Tue, 21 Oct 2025 09:55:14 +0800 Subject: [PATCH 002/210] stage --- .../core/agent/runners/dify_agent_runner.py | 326 ++++++++++++++++++ astrbot/core/config/default.py | 8 +- .../process_stage/method/llm_request.py | 17 +- astrbot/core/platform/astr_message_event.py | 2 +- .../components/provider/AddNewProvider.vue | 28 +- .../src/components/shared/AstrBotConfigV4.vue | 6 + .../src/i18n/locales/en-US/core/common.json | 1 + .../i18n/locales/en-US/features/provider.json | 4 +- .../i18n/locales/zh-CN/features/provider.json | 8 +- dashboard/src/views/ProviderPage.vue | 9 +- 10 files changed, 372 insertions(+), 37 deletions(-) create mode 100644 astrbot/core/agent/runners/dify_agent_runner.py diff --git a/astrbot/core/agent/runners/dify_agent_runner.py b/astrbot/core/agent/runners/dify_agent_runner.py new file mode 100644 index 000000000..145e4a627 --- /dev/null +++ b/astrbot/core/agent/runners/dify_agent_runner.py @@ -0,0 +1,326 @@ +import sys +import os +import typing as T +from .base import BaseAgentRunner, AgentResponse, AgentState +from ..hooks import BaseAgentRunHooks +from ..tool_executor import BaseFunctionToolExecutor +from ..run_context import ContextWrapper, TContext +from ..response import AgentResponseData +from astrbot.core.provider.provider import Provider +from astrbot.core.message.message_event_result import MessageChain +from astrbot.core.provider.entities import ( + ProviderRequest, + LLMResponse, +) +from astrbot.core.utils.dify_api_client import DifyAPIClient +from astrbot.core.utils.io import download_image_by_url, download_file +from astrbot.core.utils.astrbot_path import get_astrbot_data_path +from astrbot.core import logger, sp +import astrbot.core.message.components as Comp + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + + +class DifyAgentRunner(BaseAgentRunner[TContext]): + """Dify Agent Runner""" + + @override + async def reset( + self, + provider: Provider, + request: ProviderRequest, + run_context: ContextWrapper[TContext], + tool_executor: BaseFunctionToolExecutor[TContext], + agent_hooks: BaseAgentRunHooks[TContext], + **kwargs: T.Any, + ) -> None: + self.req = request + self.streaming = kwargs.get("streaming", False) + self.provider = provider + self.final_llm_resp = None + self._state = AgentState.IDLE + self.tool_executor = tool_executor + self.agent_hooks = agent_hooks + self.run_context = run_context + + # Dify 特定配置 - 从 provider 或 kwargs 中获取 + self.api_key = kwargs.get("dify_api_key", "") + api_base = kwargs.get("dify_api_base", "https://api.dify.ai/v1") + self.api_type = kwargs.get("dify_api_type", "") + + self.workflow_output_key = kwargs.get( + "dify_workflow_output_key", "astrbot_wf_output" + ) + self.dify_query_input_key = kwargs.get( + "dify_query_input_key", "astrbot_text_query" + ) + if not self.dify_query_input_key: + self.dify_query_input_key = "astrbot_text_query" + if not self.workflow_output_key: + self.workflow_output_key = "astrbot_wf_output" + + self.variables: dict = kwargs.get("variables", {}) + self.timeout = kwargs.get("timeout", 120) + if isinstance(self.timeout, str): + self.timeout = int(self.timeout) + + self.conversation_ids = {} + """记录当前 session id 的对话 ID""" + + self.api_client = DifyAPIClient(self.api_key, api_base) + + def _transition_state(self, new_state: AgentState) -> None: + """转换 Agent 状态""" + if self._state != new_state: + logger.debug(f"Dify Agent state transition: {self._state} -> {new_state}") + self._state = new_state + + @override + async def step(self): + """ + 执行 Dify Agent 的一个步骤 + """ + if not self.req: + raise ValueError("Request is not set. Please call reset() first.") + + if self._state == AgentState.IDLE: + try: + await self.agent_hooks.on_agent_begin(self.run_context) + except Exception as e: + logger.error(f"Error in on_agent_begin hook: {e}", exc_info=True) + + # 开始处理,转换到运行状态 + self._transition_state(AgentState.RUNNING) + + try: + # 执行 Dify 请求并处理结果 + async for response in self._execute_dify_request(): + yield response + except Exception as e: + logger.error(f"Dify 请求失败:{str(e)}") + self._transition_state(AgentState.ERROR) + self.final_llm_resp = LLMResponse( + role="err", completion_text=f"Dify 请求失败:{str(e)}" + ) + yield AgentResponse( + type="err", + data=AgentResponseData( + chain=MessageChain().message(f"Dify 请求失败:{str(e)}") + ), + ) + + async def _execute_dify_request(self): + """执行 Dify 请求的核心逻辑""" + prompt = self.req.prompt or "" + session_id = self.req.session_id or "unknown" + image_urls = self.req.image_urls or [] + system_prompt = self.req.system_prompt + + conversation_id = self.conversation_ids.get(session_id, "") + result = "" + + # 处理图片上传 + files_payload = [] + for image_url in image_urls: + try: + image_path = ( + await download_image_by_url(image_url) + if image_url.startswith("http") + else image_url + ) + file_response = await self.api_client.file_upload( + image_path, user=session_id + ) + logger.debug(f"Dify 上传图片响应:{file_response}") + if "id" not in file_response: + logger.warning( + f"上传图片后得到未知的 Dify 响应:{file_response},图片将忽略。" + ) + continue + files_payload.append( + { + "type": "image", + "transfer_method": "local_file", + "upload_file_id": file_response["id"], + } + ) + except Exception as e: + logger.warning(f"上传图片失败:{e}") + continue + + # 获得会话变量 + payload_vars = self.variables.copy() + # 动态变量 + session_var = await sp.session_get(session_id, "session_variables", default={}) + payload_vars.update(session_var) + payload_vars["system_prompt"] = system_prompt + + # 处理不同的 API 类型 + match self.api_type: + case "chat" | "agent" | "chatflow": + if not prompt: + prompt = "请描述这张图片。" + + async for chunk in self.api_client.chat_messages( + inputs={ + **payload_vars, + }, + query=prompt, + user=session_id, + conversation_id=conversation_id, + files=files_payload, + timeout=self.timeout, + ): + logger.debug(f"dify resp chunk: {chunk}") + if chunk["event"] == "message" or chunk["event"] == "agent_message": + result += chunk["answer"] + if not conversation_id: + self.conversation_ids[session_id] = chunk["conversation_id"] + conversation_id = chunk["conversation_id"] + + # 如果是流式响应,发送增量数据 + if self.streaming and chunk["answer"]: + yield AgentResponse( + type="streaming_delta", + data=AgentResponseData( + chain=MessageChain().message(chunk["answer"]) + ), + ) + elif chunk["event"] == "message_end": + logger.debug("Dify message end") + break + elif chunk["event"] == "error": + logger.error(f"Dify 出现错误:{chunk}") + raise Exception( + f"Dify 出现错误 status: {chunk['status']} message: {chunk['message']}" + ) + + case "workflow": + async for chunk in self.api_client.workflow_run( + inputs={ + self.dify_query_input_key: prompt, + "astrbot_session_id": session_id, + **payload_vars, + }, + user=session_id, + files=files_payload, + timeout=self.timeout, + ): + match chunk["event"]: + case "workflow_started": + logger.info( + f"Dify 工作流(ID: {chunk['workflow_run_id']})开始运行。" + ) + case "node_finished": + logger.debug( + f"Dify 工作流节点(ID: {chunk['data']['node_id']} Title: {chunk['data'].get('title', '')})运行结束。" + ) + case "workflow_finished": + logger.info( + f"Dify 工作流(ID: {chunk['workflow_run_id']})运行结束" + ) + logger.debug(f"Dify 工作流结果:{chunk}") + if chunk["data"]["error"]: + logger.error( + f"Dify 工作流出现错误:{chunk['data']['error']}" + ) + raise Exception( + f"Dify 工作流出现错误:{chunk['data']['error']}" + ) + if self.workflow_output_key not in chunk["data"]["outputs"]: + raise Exception( + f"Dify 工作流的输出不包含指定的键名:{self.workflow_output_key}" + ) + result = chunk + case _: + raise Exception(f"未知的 Dify API 类型:{self.api_type}") + + if not result: + logger.warning("Dify 请求结果为空,请查看 Debug 日志。") + + # 解析结果 + chain = await self.parse_dify_result(result) + + # 创建最终响应 + self.final_llm_resp = LLMResponse(role="assistant", result_chain=chain) + self._transition_state(AgentState.DONE) + + try: + await self.agent_hooks.on_agent_done(self.run_context, self.final_llm_resp) + except Exception as e: + logger.error(f"Error in on_agent_done hook: {e}", exc_info=True) + + # 返回最终结果 + yield AgentResponse( + type="llm_result", + data=AgentResponseData(chain=chain), + ) + + async def parse_dify_result(self, chunk: dict | str) -> MessageChain: + """解析 Dify 的响应结果""" + if isinstance(chunk, str): + # Chat + return MessageChain(chain=[Comp.Plain(chunk)]) + + async def parse_file(item: dict): + match item["type"]: + case "image": + return Comp.Image(file=item["url"], url=item["url"]) + case "audio": + # 仅支持 wav + temp_dir = os.path.join(get_astrbot_data_path(), "temp") + path = os.path.join(temp_dir, f"{item['filename']}.wav") + await download_file(item["url"], path) + return Comp.Image(file=item["url"], url=item["url"]) + case "video": + return Comp.Video(file=item["url"]) + case _: + return Comp.File(name=item["filename"], file=item["url"]) + + output = chunk["data"]["outputs"][self.workflow_output_key] + chains = [] + if isinstance(output, str): + # 纯文本输出 + chains.append(Comp.Plain(output)) + elif isinstance(output, list): + # 主要适配 Dify 的 HTTP 请求结点的多模态输出 + for item in output: + # handle Array[File] + if ( + not isinstance(item, dict) + or item.get("dify_model_identity", "") != "__dify__file__" + ): + chains.append(Comp.Plain(str(output))) + break + else: + chains.append(Comp.Plain(str(output))) + + # scan file + files = chunk["data"].get("files", []) + for item in files: + comp = await parse_file(item) + chains.append(comp) + + return MessageChain(chain=chains) + + @override + def done(self) -> bool: + """检查 Agent 是否已完成工作""" + return self._state in (AgentState.DONE, AgentState.ERROR) + + @override + def get_final_llm_resp(self) -> LLMResponse | None: + return self.final_llm_resp + + async def forget(self, session_id): + """忘记会话上下文""" + self.conversation_ids[session_id] = "" + return True + + async def terminate(self): + """终止并清理资源""" + if hasattr(self, "api_client"): + await self.api_client.close() diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 361f557fd..c4d2793ee 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -975,7 +975,7 @@ CONFIG_METADATA_2 = { "id": "dify_app_default", "provider": "dify", "type": "dify", - "provider_type": "chat_completion", + "provider_type": "agent_runner", "enable": True, "dify_api_type": "chat", "dify_api_key": "", @@ -989,7 +989,7 @@ CONFIG_METADATA_2 = { "Coze": { "id": "coze", "provider": "coze", - "provider_type": "chat_completion", + "provider_type": "agent_runner", "type": "coze", "enable": True, "coze_api_key": "", @@ -2099,7 +2099,7 @@ CONFIG_METADATA_3 = { "provider_settings.dify_runner_provider_id": { "description": "Dify 执行器提供商 ID", "type": "string", - "_special": "select_dify_runner_provider", + "_special": "select_provider_dify_runner", }, }, "condition": { @@ -2113,7 +2113,7 @@ CONFIG_METADATA_3 = { "provider_settings.coze_runner_provider_id": { "description": "Coze 执行器提供商 ID", "type": "string", - "_special": "select_coze_runner_provider", + "_special": "select_provider_coze_runner", }, }, "condition": { diff --git a/astrbot/core/pipeline/process_stage/method/llm_request.py b/astrbot/core/pipeline/process_stage/method/llm_request.py index b2245e4da..85eaf9ffa 100644 --- a/astrbot/core/pipeline/process_stage/method/llm_request.py +++ b/astrbot/core/pipeline/process_stage/method/llm_request.py @@ -44,7 +44,7 @@ except (ModuleNotFoundError, ImportError): AgentContextWrapper = ContextWrapper[AstrAgentContext] -AgentRunner = ToolLoopAgentRunner[AgentContextWrapper] +AgentRunner = ToolLoopAgentRunner[AstrAgentContext] class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]): @@ -102,7 +102,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]): request = ProviderRequest( prompt=input_, - system_prompt=tool.description, + system_prompt=tool.description or "", image_urls=[], # 暂时不传递原始 agent 的上下文 contexts=[], # 暂时不传递原始 agent 的上下文 func_tool=toolset, @@ -239,7 +239,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]): yield res -class MainAgentHooks(BaseAgentRunHooks[AgentContextWrapper]): +class MainAgentHooks(BaseAgentRunHooks[AstrAgentContext]): async def on_agent_done(self, run_context, llm_response): # 执行事件钩子 await call_event_hook( @@ -337,7 +337,7 @@ class LLMRequestSubStage(Stage): self.conv_manager = ctx.plugin_manager.context.conversation_manager - def _select_provider(self, event: AstrMessageEvent) -> Provider | None: + def _select_provider(self, event: AstrMessageEvent): """选择使用的 LLM 提供商""" sel_provider = event.get_extra("selected_provider") _ctx = self.ctx.plugin_manager.context @@ -382,6 +382,9 @@ class LLMRequestSubStage(Stage): provider = self._select_provider(event) if provider is None: return + if not isinstance(provider, Provider): + logger.error(f"选择的提供商类型无效({type(provider)}),跳过 LLM 请求处理。") + return if event.get_extra("provider_request"): req = event.get_extra("provider_request") @@ -520,8 +523,10 @@ class LLMRequestSubStage(Stage): chain = ( MessageChain().message(final_llm_resp.completion_text).chain ) - else: + elif final_llm_resp.result_chain: chain = final_llm_resp.result_chain.chain + else: + chain = MessageChain().chain event.set_result( MessageEventResult( chain=chain, @@ -553,6 +558,8 @@ class LLMRequestSubStage(Stage): self, event: AstrMessageEvent, req: ProviderRequest, prov: Provider ): """处理 WebChat 平台的特殊情况,包括第一次 LLM 对话时总结对话内容生成 title""" + if not req.conversation: + return conversation = await self.conv_manager.get_conversation( event.unified_msg_origin, req.conversation.cid ) diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py index 05169c4fe..e948ed5bc 100644 --- a/astrbot/core/platform/astr_message_event.py +++ b/astrbot/core/platform/astr_message_event.py @@ -179,7 +179,7 @@ class AstrMessageEvent(abc.ABC): def get_extra( self, key: str | None = None, default: _VT = None - ) -> dict[str, Any] | _VT: + ) -> Any: """ 获取额外的信息。 """ diff --git a/dashboard/src/components/provider/AddNewProvider.vue b/dashboard/src/components/provider/AddNewProvider.vue index b4cd1eb92..f876779fb 100644 --- a/dashboard/src/components/provider/AddNewProvider.vue +++ b/dashboard/src/components/provider/AddNewProvider.vue @@ -7,6 +7,10 @@ mdi-message-text {{ tm('dialogs.addProvider.tabs.basic') }} + + mdi-cogs + {{ tm('dialogs.addProvider.tabs.agentRunner') }} + mdi-microphone-message {{ tm('dialogs.addProvider.tabs.speechToText') }} @@ -27,7 +31,7 @@
- 接入 {{ name }} + {{ name }} {{ getProviderDescription(template, name) }} @@ -54,7 +58,7 @@ - {{ tm('dialogs.addProvider.noTemplates', { type: getTabTypeName(tabType) }) }} + {{ t('dialogs.addProvider.noTemplates') }} @@ -104,19 +108,6 @@ export default { this.$emit('update:show', value); } }, - - // 翻译消息的计算属性 - messages() { - return { - tabTypes: { - 'chat_completion': this.tm('providers.tabs.chatCompletion'), - 'speech_to_text': this.tm('providers.tabs.speechToText'), - 'text_to_speech': this.tm('providers.tabs.textToSpeech'), - 'embedding': this.tm('providers.tabs.embedding'), - 'rerank': this.tm('providers.tabs.rerank') - } - }; - } }, methods: { closeDialog() { @@ -140,11 +131,6 @@ export default { // 从工具函数导入 getProviderIcon, - // 获取Tab类型的中文名称 - getTabTypeName(tabType) { - return this.messages.tabTypes[tabType] || tabType; - }, - // 获取提供商简介 getProviderDescription(template, name) { return getProviderDescription(template, name, this.tm); diff --git a/dashboard/src/components/shared/AstrBotConfigV4.vue b/dashboard/src/components/shared/AstrBotConfigV4.vue index 08373595f..52f793b96 100644 --- a/dashboard/src/components/shared/AstrBotConfigV4.vue +++ b/dashboard/src/components/shared/AstrBotConfigV4.vue @@ -242,6 +242,12 @@ function getSelectItems(itemMeta) {
+
+ +
+
+ +
diff --git a/dashboard/src/i18n/locales/en-US/core/common.json b/dashboard/src/i18n/locales/en-US/core/common.json index 4aff41001..ff23b255a 100644 --- a/dashboard/src/i18n/locales/en-US/core/common.json +++ b/dashboard/src/i18n/locales/en-US/core/common.json @@ -73,6 +73,7 @@ "disabled": "Disabled", "delete": "Delete", "edit": "Edit", + "copy": "Copy", "noData": "No data available" } } \ No newline at end of file diff --git a/dashboard/src/i18n/locales/en-US/features/provider.json b/dashboard/src/i18n/locales/en-US/features/provider.json index 7888c9e0b..e08177d3d 100644 --- a/dashboard/src/i18n/locales/en-US/features/provider.json +++ b/dashboard/src/i18n/locales/en-US/features/provider.json @@ -9,6 +9,7 @@ "tabs": { "all": "All", "chatCompletion": "Chat Completion", + "agentRunner": "Agent Runner", "speechToText": "Speech to Text", "textToSpeech": "Text to Speech", "embedding": "Embedding", @@ -44,12 +45,13 @@ "title": "Service Provider", "tabs": { "basic": "Basic", + "agentRunner": "Agent Runner", "speechToText": "Speech to Text", "textToSpeech": "Text to Speech", "embedding": "Embedding", "rerank": "Rerank" }, - "noTemplates": "No {type} type provider templates available" + "noTemplates": "No this type provider templates available" }, "config": { "addTitle": "Add", diff --git a/dashboard/src/i18n/locales/zh-CN/features/provider.json b/dashboard/src/i18n/locales/zh-CN/features/provider.json index 913d74c30..234018829 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/provider.json +++ b/dashboard/src/i18n/locales/zh-CN/features/provider.json @@ -8,7 +8,8 @@ "providerType": "提供商类型", "tabs": { "all": "全部", - "chatCompletion": "基本对话", + "chatCompletion": "对话", + "agentRunner": "Agent 执行器", "speechToText": "语音转文字", "textToSpeech": "文字转语音", "embedding": "嵌入(Embedding)", @@ -44,13 +45,14 @@ "addProvider": { "title": "模型提供商", "tabs": { - "basic": "基本", + "basic": "对话", + "agentRunner": "Agent 执行器", "speechToText": "语音转文字", "textToSpeech": "文字转语音", "embedding": "嵌入(Embedding)", "rerank": "重排序(Rerank)" }, - "noTemplates": "暂无{type}类型的提供商模板" + "noTemplates": "暂无该类型的提供商模板" }, "config": { "addTitle": "新增", diff --git a/dashboard/src/views/ProviderPage.vue b/dashboard/src/views/ProviderPage.vue index 7264bd35e..811e294f2 100644 --- a/dashboard/src/views/ProviderPage.vue +++ b/dashboard/src/views/ProviderPage.vue @@ -29,6 +29,10 @@ mdi-message-text {{ tm('providers.tabs.chatCompletion') }} + + mdi-message-text + {{ tm('providers.tabs.agentRunner') }} + mdi-microphone-message {{ tm('providers.tabs.speechToText') }} @@ -312,8 +316,8 @@ export default { "anthropic_chat_completion": "chat_completion", "googlegenai_chat_completion": "chat_completion", "zhipu_chat_completion": "chat_completion", - "dify": "chat_completion", - "coze": "chat_completion", + "dify": "agent_runner", + "coze": "agent_runner", "dashscope": "chat_completion", "openai_whisper_api": "speech_to_text", "openai_whisper_selfhost": "speech_to_text", @@ -357,6 +361,7 @@ export default { }, tabTypes: { 'chat_completion': this.tm('providers.tabs.chatCompletion'), + 'agent_runner': this.tm('providers.tabs.agentRunner'), 'speech_to_text': this.tm('providers.tabs.speechToText'), 'text_to_speech': this.tm('providers.tabs.textToSpeech'), 'embedding': this.tm('providers.tabs.embedding'), From 61a68477d07dedca029c9b6962ff87ea0736251e Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Tue, 21 Oct 2025 14:19:38 +0800 Subject: [PATCH 003/210] stage --- astrbot/core/config/default.py | 68 ++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index c4d2793ee..de0190405 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -1804,7 +1804,6 @@ CONFIG_METADATA_2 = { "enable": { "description": "启用", "type": "bool", - "hint": "是否启用。", }, "key": { "description": "API Key", @@ -2081,14 +2080,22 @@ CONFIG_METADATA_3 = { "name": "AI 配置", "metadata": { "agent_runner": { - "description": "Agent", + "description": "Agent 执行方式", "type": "object", "items": { + "provider_settings.enable": { + "description": "启用", + "type": "bool", + "hint": "AI 对话总开关", + }, "provider_settings.agent_runner_type": { "description": "执行器", "type": "string", "options": ["local", "dify", "coze"], "labels": ["内置 Agent", "Dify", "Coze"], + "condition": { + "provider_settings.enable": True, + }, }, }, }, @@ -2104,6 +2111,7 @@ CONFIG_METADATA_3 = { }, "condition": { "provider_settings.agent_runner_type": "dify", + "provider_settings.enable": True, }, }, "coze_runner": { @@ -2118,32 +2126,32 @@ CONFIG_METADATA_3 = { }, "condition": { "provider_settings.agent_runner_type": "coze", + "provider_settings.enable": True, }, }, "ai": { "description": "模型", "type": "object", "items": { - "provider_settings.enable": { - "description": "启用大语言模型聊天", - "type": "bool", - }, "provider_settings.default_provider_id": { "description": "默认聊天模型", "type": "string", "_special": "select_provider", - "hint": "留空时使用第一个模型。", + "hint": "留空时使用第一个模型", + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "provider_settings.default_image_caption_provider_id": { "description": "默认图片转述模型", "type": "string", "_special": "select_provider", - "hint": "留空代表不使用。可用于不支持视觉模态的聊天模型。", + "hint": "留空代表不使用,可用于非多模态模型", }, "provider_stt_settings.enable": { "description": "启用语音转文本", "type": "bool", - "hint": "STT 总开关。", + "hint": "STT 总开关", }, "provider_stt_settings.provider_id": { "description": "默认语音转文本模型", @@ -2157,12 +2165,11 @@ CONFIG_METADATA_3 = { "provider_tts_settings.enable": { "description": "启用文本转语音", "type": "bool", - "hint": "TTS 总开关。当关闭时,会话启用 TTS 也不会生效。", + "hint": "TTS 总开关", }, "provider_tts_settings.provider_id": { "description": "默认文本转语音模型", "type": "string", - "hint": "用户也可使用 /provider 单独选择会话的 TTS 模型。", "_special": "select_provider_tts", "condition": { "provider_tts_settings.enable": True, @@ -2174,7 +2181,7 @@ CONFIG_METADATA_3 = { }, }, "condition": { - "provider_settings.agent_runner_type": "local", + "provider_settings.enable": True, }, }, "persona": { @@ -2189,6 +2196,7 @@ CONFIG_METADATA_3 = { }, "condition": { "provider_settings.agent_runner_type": "local", + "provider_settings.enable": True, }, }, "knowledgebase": { @@ -2203,6 +2211,7 @@ CONFIG_METADATA_3 = { }, "condition": { "provider_settings.agent_runner_type": "local", + "provider_settings.enable": True, }, }, "websearch": { @@ -2242,6 +2251,7 @@ CONFIG_METADATA_3 = { }, "condition": { "provider_settings.agent_runner_type": "local", + "provider_settings.enable": True, }, }, "others": { @@ -2255,50 +2265,70 @@ CONFIG_METADATA_3 = { "provider_settings.identifier": { "description": "用户识别", "type": "bool", + "hint": "启用后,会在提示词前包含用户 ID 信息。", }, "provider_settings.group_name_display": { "description": "显示群名称", "type": "bool", - "hint": "启用后,在支持的平台(aiocqhttp)上会在 prompt 中包含群名称信息。", + "hint": "启用后,在支持的平台(OneBot v11)上会在提示词前包含群名称信息。", }, "provider_settings.datetime_system_prompt": { "description": "现实世界时间感知", "type": "bool", + "hint": "启用后,会在系统提示词中附带当前时间信息。", + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "provider_settings.show_tool_use_status": { "description": "输出函数调用状态", "type": "bool", + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "provider_settings.max_agent_step": { "description": "工具调用轮数上限", "type": "int", + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "provider_settings.tool_call_timeout": { "description": "工具调用超时时间(秒)", "type": "int", + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "provider_settings.streaming_response": { - "description": "流式回复", + "description": "流式输出", "type": "bool", }, "provider_settings.streaming_segmented": { - "description": "不支持流式回复的平台采取分段输出", + "description": "不支持流式输出的平台采取分段输出", "type": "bool", }, "provider_settings.max_context_length": { "description": "最多携带对话轮数", "type": "int", - "hint": "超出这个数量时丢弃最旧的部分,一轮聊天记为 1 条。-1 为不限制。", + "hint": "超出这个数量时丢弃最旧的部分,一轮聊天记为 1 条,-1 为不限制", + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "provider_settings.dequeue_context_length": { "description": "丢弃对话轮数", "type": "int", - "hint": "超出最多携带对话轮数时, 一次丢弃的聊天轮数。", + "hint": "超出最多携带对话轮数时, 一次丢弃的聊天轮数", + "condition": { + "provider_settings.agent_runner_type": "local", + }, }, "provider_settings.wake_prefix": { "description": "LLM 聊天额外唤醒前缀 ", "type": "string", - "hint": "如果唤醒前缀为 `/`, 额外聊天唤醒前缀为 `chat`,则需要 `/chat` 才会触发 LLM 请求。默认为空。", + "hint": "如果唤醒前缀为 /, 额外聊天唤醒前缀为 chat,则需要 /chat 才会触发 LLM 请求", }, "provider_settings.prompt_prefix": { "description": "用户提示词", @@ -2311,7 +2341,7 @@ CONFIG_METADATA_3 = { }, }, "condition": { - "provider_settings.agent_runner_type": "local", + "provider_settings.enable": True, }, }, }, From 27af9ebb6ba6b1366bb5b2b0460361f7d1abb9d2 Mon Sep 17 00:00:00 2001 From: RC-CHN <67079377+RC-CHN@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:54:03 +0800 Subject: [PATCH 004/210] feat: changelog display improvement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 添加旧版本changelog的modal * style: 调整发布说明对话框的样式,移除背景颜色 --- .../src/i18n/locales/en-US/core/header.json | 3 ++ .../src/i18n/locales/zh-CN/core/header.json | 3 ++ .../full/vertical-header/VerticalHeader.vue | 41 +++++++++++++++---- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/dashboard/src/i18n/locales/en-US/core/header.json b/dashboard/src/i18n/locales/en-US/core/header.json index cfc76fe02..41c5ac0dc 100644 --- a/dashboard/src/i18n/locales/en-US/core/header.json +++ b/dashboard/src/i18n/locales/en-US/core/header.json @@ -56,6 +56,9 @@ "linkText": "View master branch commit history (click copy on the right to copy)", "confirm": "Confirm Switch" }, + "releaseNotes": { + "title": "Release Notes" + }, "dashboardUpdate": { "title": "Update Dashboard to Latest Version Only", "currentVersion": "Current Version", diff --git a/dashboard/src/i18n/locales/zh-CN/core/header.json b/dashboard/src/i18n/locales/zh-CN/core/header.json index 54738b8d6..5f828176b 100644 --- a/dashboard/src/i18n/locales/zh-CN/core/header.json +++ b/dashboard/src/i18n/locales/zh-CN/core/header.json @@ -55,6 +55,9 @@ "linkText": "查看 master 分支提交记录(点击右边的 copy 即可复制)", "confirm": "确定切换" }, + "releaseNotes": { + "title": "更新日志" + }, "dashboardUpdate": { "title": "单独更新管理面板到最新版本", "currentVersion": "当前版本", diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index 20340976f..b1d076fca 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -43,6 +43,11 @@ let devCommits = ref<{ sha: string; date: string; message: string }[]>([]); let updatingDashboardLoading = ref(false); let installLoading = ref(false); +// Release Notes Modal +let releaseNotesDialog = ref(false); +let selectedReleaseNotes = ref(''); +let selectedReleaseTag = ref(''); + let tab = ref(0); const releasesHeader = computed(() => [ @@ -283,6 +288,12 @@ function toggleDarkMode() { theme.global.name.value = newTheme; } +function openReleaseNotesDialog(body: string, tag: string) { + selectedReleaseNotes.value = body; + selectedReleaseTag.value = tag; + releaseNotesDialog.value = true; +} + getVersion(); checkUpdate(); @@ -417,13 +428,10 @@ commonStore.getStartTime();
-