diff --git a/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml b/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml
index 56f958d30..c24bcf6d9 100644
--- a/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml
+++ b/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml
@@ -16,7 +16,7 @@ body:
请将插件信息填写到下方的 JSON 代码块中。其中 `tags`(插件标签)和 `social_link`(社交链接)选填。
- 不熟悉 JSON ?可以从 [此处](https://plugins.astrbot.app/submit) 生成 JSON ,生成后记得复制粘贴过来.
+ 不熟悉 JSON ?可以从 [此站](https://plugins.astrbot.app) 右下角提交。
- type: textarea
id: plugin-info
diff --git a/README.md b/README.md
index f408d6b44..5083f0264 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py
index a9d9e5e96..1021d81b5 100644
--- a/astrbot/core/config/default.py
+++ b/astrbot/core/config/default.py
@@ -740,6 +740,7 @@ CONFIG_METADATA_2 = {
"api_base": "https://api.openai.com/v1",
"timeout": 120,
"model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
"hint": "也兼容所有与 OpenAI API 兼容的服务。",
@@ -755,6 +756,7 @@ CONFIG_METADATA_2 = {
"api_base": "",
"timeout": 120,
"model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -768,6 +770,7 @@ CONFIG_METADATA_2 = {
"api_base": "https://api.x.ai/v1",
"timeout": 120,
"model_config": {"model": "grok-2-latest", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"xai_native_search": False,
"modalities": ["text", "image", "tool_use"],
@@ -799,6 +802,7 @@ CONFIG_METADATA_2 = {
"key": ["ollama"], # ollama 的 key 默认是 ollama
"api_base": "http://localhost:11434/v1",
"model_config": {"model": "llama3.1-8b", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -813,6 +817,7 @@ CONFIG_METADATA_2 = {
"model_config": {
"model": "llama-3.1-8b",
},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -829,6 +834,7 @@ CONFIG_METADATA_2 = {
"model": "gemini-1.5-flash",
"temperature": 0.4,
},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -870,6 +876,7 @@ CONFIG_METADATA_2 = {
"api_base": "https://api.deepseek.com/v1",
"timeout": 120,
"model_config": {"model": "deepseek-chat", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "tool_use"],
},
@@ -883,6 +890,7 @@ CONFIG_METADATA_2 = {
"api_base": "https://api.302.ai/v1",
"timeout": 120,
"model_config": {"model": "gpt-4.1-mini", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -899,6 +907,7 @@ CONFIG_METADATA_2 = {
"model": "deepseek-ai/DeepSeek-V3",
"temperature": 0.4,
},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -915,6 +924,7 @@ CONFIG_METADATA_2 = {
"model": "deepseek/deepseek-r1",
"temperature": 0.4,
},
+ "custom_headers": {},
"custom_extra_body": {},
},
"小马算力": {
@@ -930,6 +940,7 @@ CONFIG_METADATA_2 = {
"model": "kimi-k2-instruct-0905",
"temperature": 0.7,
},
+ "custom_headers": {},
"custom_extra_body": {},
},
"优云智算": {
@@ -944,6 +955,7 @@ CONFIG_METADATA_2 = {
"model_config": {
"model": "moonshotai/Kimi-K2-Instruct",
},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -957,6 +969,7 @@ CONFIG_METADATA_2 = {
"timeout": 120,
"api_base": "https://api.moonshot.cn/v1",
"model_config": {"model": "moonshot-v1-8k", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -972,6 +985,8 @@ CONFIG_METADATA_2 = {
"model_config": {
"model": "glm-4-flash",
},
+ "custom_headers": {},
+ "custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
"Dify": {
@@ -1028,6 +1043,7 @@ CONFIG_METADATA_2 = {
"timeout": 120,
"api_base": "https://api-inference.modelscope.cn/v1",
"model_config": {"model": "Qwen/Qwen3-32B", "temperature": 0.4},
+ "custom_headers": {},
"custom_extra_body": {},
"modalities": ["text", "image", "tool_use"],
},
@@ -1040,6 +1056,7 @@ CONFIG_METADATA_2 = {
"key": [],
"api_base": "https://api.fastgpt.in/api/v1",
"timeout": 60,
+ "custom_headers": {},
"custom_extra_body": {},
},
"Whisper(API)": {
@@ -1321,6 +1338,12 @@ CONFIG_METADATA_2 = {
"render_type": "checkbox",
"hint": "模型支持的模态。如所填写的模型不支持图像,请取消勾选图像。",
},
+ "custom_headers": {
+ "description": "自定义添加请求头",
+ "type": "dict",
+ "items": {},
+ "hint": "此处添加的键值对将被合并到 OpenAI SDK 的 default_headers 中,用于自定义 HTTP 请求头。值必须为字符串。",
+ },
"custom_extra_body": {
"description": "自定义请求体参数",
"type": "dict",
diff --git a/astrbot/core/platform/sources/discord/discord_platform_event.py b/astrbot/core/platform/sources/discord/discord_platform_event.py
index 04c95f639..6fba5e70c 100644
--- a/astrbot/core/platform/sources/discord/discord_platform_event.py
+++ b/astrbot/core/platform/sources/discord/discord_platform_event.py
@@ -1,7 +1,7 @@
import asyncio
import base64
import binascii
-import sys
+from collections.abc import AsyncGenerator
from io import BytesIO
from pathlib import Path
from typing import cast
@@ -23,11 +23,6 @@ from astrbot.api.platform import AstrBotMessage, At, PlatformMetadata
from .client import DiscordBotClient
from .components import DiscordEmbed, DiscordView
-if sys.version_info >= (3, 12):
- from typing import override
-else:
- from typing_extensions import override
-
# 自定义Discord视图组件(兼容旧版本)
class DiscordViewComponent(BaseMessageComponent):
@@ -51,7 +46,6 @@ class DiscordPlatformEvent(AstrMessageEvent):
self.client = client
self.interaction_followup_webhook = interaction_followup_webhook
- @override
async def send(self, message: MessageChain):
"""发送消息到Discord平台"""
# 解析消息链为 Discord 所需的对象
@@ -103,6 +97,21 @@ class DiscordPlatformEvent(AstrMessageEvent):
await super().send(message)
+ async def send_streaming(
+ self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False
+ ):
+ buffer = None
+ async for chain in generator:
+ if not buffer:
+ buffer = chain
+ else:
+ buffer.chain.extend(chain.chain)
+ if not buffer:
+ return None
+ buffer.squash_plain()
+ await self.send(buffer)
+ return await super().send_streaming(generator, use_fallback)
+
async def _get_channel(
self,
) -> (
diff --git a/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py b/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py
index e6d83d8ea..08ab27013 100644
--- a/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py
+++ b/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py
@@ -1,6 +1,7 @@
import asyncio
import base64
import io
+from collections.abc import AsyncGenerator
from typing import TYPE_CHECKING
import aiohttp
@@ -50,6 +51,21 @@ class WeChatPadProMessageEvent(AstrMessageEvent):
await self._send_voice(session, comp)
await super().send(message)
+ async def send_streaming(
+ self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False
+ ):
+ buffer = None
+ async for chain in generator:
+ if not buffer:
+ buffer = chain
+ else:
+ buffer.chain.extend(chain.chain)
+ if not buffer:
+ return None
+ buffer.squash_plain()
+ await self.send(buffer)
+ return await super().send_streaming(generator, use_fallback)
+
async def _send_image(self, session: aiohttp.ClientSession, comp: Image):
b64 = await comp.convert_to_base64()
raw = self._validate_base64(b64)
diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py
index 21d832eec..fde80c59f 100644
--- a/astrbot/core/provider/sources/openai_source.py
+++ b/astrbot/core/provider/sources/openai_source.py
@@ -43,14 +43,23 @@ class ProviderOpenAIOfficial(Provider):
self.api_keys: list = super().get_keys()
self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None
self.timeout = provider_config.get("timeout", 120)
+ self.custom_headers = provider_config.get("custom_headers", {})
if isinstance(self.timeout, str):
self.timeout = int(self.timeout)
+
+ if not isinstance(self.custom_headers, dict) or not self.custom_headers:
+ self.custom_headers = None
+ else:
+ for key in self.custom_headers:
+ self.custom_headers[key] = str(self.custom_headers[key])
+
# 适配 azure openai #332
if "api_version" in provider_config:
# 使用 azure api
self.client = AsyncAzureOpenAI(
api_key=self.chosen_api_key,
api_version=provider_config.get("api_version", None),
+ default_headers=self.custom_headers,
base_url=provider_config.get("api_base", ""),
timeout=self.timeout,
)
@@ -59,6 +68,7 @@ class ProviderOpenAIOfficial(Provider):
self.client = AsyncOpenAI(
api_key=self.chosen_api_key,
base_url=provider_config.get("api_base", None),
+ default_headers=self.custom_headers,
timeout=self.timeout,
)
diff --git a/dashboard/src/components/shared/KnowledgeBaseSelector.vue b/dashboard/src/components/shared/KnowledgeBaseSelector.vue
index a90e6f5e2..e959b948a 100644
--- a/dashboard/src/components/shared/KnowledgeBaseSelector.vue
+++ b/dashboard/src/components/shared/KnowledgeBaseSelector.vue
@@ -1,22 +1,25 @@
-
-
- 未选择
-
-
-
- {{ name }}
-
+
+
+
+ 未选择
+
+
+
+ {{ name }}
+
+
-
+
{{ buttonText }}
@@ -220,4 +223,11 @@ function goToKnowledgeBasePage() {
.gap-1 {
gap: 4px;
}
+
+.text-truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: inline-block;
+}