AstrBot 4.0.0b5__py3-none-any.whl → 4.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/api/event/filter/__init__.py +2 -0
- astrbot/core/config/default.py +73 -3
- astrbot/core/initial_loader.py +4 -1
- astrbot/core/message/components.py +59 -50
- astrbot/core/pipeline/result_decorate/stage.py +5 -1
- astrbot/core/platform/manager.py +25 -3
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +11 -4
- astrbot/core/platform/sources/satori/satori_adapter.py +482 -0
- astrbot/core/platform/sources/satori/satori_event.py +221 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +0 -1
- astrbot/core/provider/sources/openai_source.py +12 -5
- astrbot/core/provider/sources/vllm_rerank_source.py +6 -0
- astrbot/core/star/__init__.py +7 -5
- astrbot/core/star/filter/command.py +9 -3
- astrbot/core/star/filter/platform_adapter_type.py +3 -0
- astrbot/core/star/register/__init__.py +2 -0
- astrbot/core/star/register/star_handler.py +18 -4
- astrbot/core/star/star_handler.py +9 -1
- astrbot/core/star/star_tools.py +116 -21
- astrbot/core/utils/t2i/network_strategy.py +11 -18
- astrbot/core/utils/t2i/renderer.py +8 -2
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +112 -0
- astrbot/dashboard/routes/chat.py +6 -1
- astrbot/dashboard/routes/config.py +10 -49
- astrbot/dashboard/routes/route.py +19 -2
- astrbot/dashboard/routes/t2i.py +230 -0
- astrbot/dashboard/server.py +13 -4
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.0.dist-info}/METADATA +39 -52
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.0.dist-info}/RECORD +33 -28
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.0.dist-info}/WHEEL +0 -0
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.0.dist-info}/licenses/LICENSE +0 -0
astrbot/core/star/star_tools.py
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
插件开发工具集
|
|
3
|
+
封装了许多常用的操作,方便插件开发者使用
|
|
4
|
+
|
|
5
|
+
说明:
|
|
6
|
+
|
|
7
|
+
主动发送消息: send_message(session, message_chain)
|
|
8
|
+
根据 session (unified_msg_origin) 主动发送消息, 前提是需要提前获得或构造 session
|
|
9
|
+
|
|
10
|
+
根据id直接主动发送消息: send_message_by_id(type, id, message_chain, platform="aiocqhttp")
|
|
11
|
+
根据 id (例如 qq 号, 群号等) 直接, 主动地发送消息
|
|
12
|
+
|
|
13
|
+
以上两种方式需要构造消息链, 也就是消息组件的列表
|
|
14
|
+
|
|
15
|
+
构造事件:
|
|
16
|
+
|
|
17
|
+
首先需要构造一个 AstrBotMessage 对象, 使用 create_message 方法
|
|
18
|
+
然后使用 create_event 方法提交事件到指定平台
|
|
19
|
+
"""
|
|
20
|
+
|
|
1
21
|
import inspect
|
|
2
22
|
import os
|
|
23
|
+
import uuid
|
|
3
24
|
from pathlib import Path
|
|
4
25
|
from typing import Union, Awaitable, List, Optional, ClassVar
|
|
5
26
|
from astrbot.core.message.components import BaseMessageComponent
|
|
6
27
|
from astrbot.core.message.message_event_result import MessageChain
|
|
7
|
-
from astrbot.api.platform import MessageMember, AstrBotMessage
|
|
28
|
+
from astrbot.api.platform import MessageMember, AstrBotMessage, MessageType
|
|
8
29
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
9
30
|
from astrbot.core.star.context import Context
|
|
10
31
|
from astrbot.core.star.star import star_map
|
|
11
32
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
12
|
-
|
|
33
|
+
from astrbot.core.platform.sources.aiocqhttp.aiocqhttp_message_event import AiocqhttpMessageEvent
|
|
34
|
+
from astrbot.core.platform.sources.aiocqhttp.aiocqhttp_platform_adapter import AiocqhttpAdapter
|
|
13
35
|
|
|
14
36
|
class StarTools:
|
|
15
37
|
"""
|
|
@@ -49,42 +71,76 @@ class StarTools:
|
|
|
49
71
|
Note:
|
|
50
72
|
qq_official(QQ官方API平台)不支持此方法
|
|
51
73
|
"""
|
|
74
|
+
if cls._context is None:
|
|
75
|
+
raise ValueError("StarTools not initialized")
|
|
52
76
|
return await cls._context.send_message(session, message_chain)
|
|
53
77
|
|
|
78
|
+
@classmethod
|
|
79
|
+
async def send_message_by_id(
|
|
80
|
+
cls, type: str, id: str, message_chain: MessageChain, platform: str = "aiocqhttp"
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
根据 id(例如qq号, 群号等) 直接, 主动地发送消息
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
type (str): 消息类型, 可选: PrivateMessage, GroupMessage
|
|
87
|
+
id (str): 目标ID, 例如QQ号, 群号等
|
|
88
|
+
message_chain (MessageChain): 消息链
|
|
89
|
+
platform (str): 可选的平台名称,默认平台(aiocqhttp), 目前只支持 aiocqhttp
|
|
90
|
+
"""
|
|
91
|
+
if cls._context is None:
|
|
92
|
+
raise ValueError("StarTools not initialized")
|
|
93
|
+
platforms = cls._context.platform_manager.get_insts()
|
|
94
|
+
if platform == "aiocqhttp":
|
|
95
|
+
adapter = next((p for p in platforms if isinstance(p, AiocqhttpAdapter)), None)
|
|
96
|
+
if adapter is None:
|
|
97
|
+
raise ValueError("未找到适配器: AiocqhttpAdapter")
|
|
98
|
+
await AiocqhttpMessageEvent.send_message(
|
|
99
|
+
bot=adapter.bot,
|
|
100
|
+
message_chain=message_chain,
|
|
101
|
+
is_group=(type == "GroupMessage"),
|
|
102
|
+
session_id=id,
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
raise ValueError(f"不支持的平台: {platform}")
|
|
106
|
+
|
|
54
107
|
@classmethod
|
|
55
108
|
async def create_message(
|
|
56
109
|
cls,
|
|
57
110
|
type: str,
|
|
58
111
|
self_id: str,
|
|
59
112
|
session_id: str,
|
|
60
|
-
message_id: str,
|
|
61
113
|
sender: MessageMember,
|
|
62
114
|
message: List[BaseMessageComponent],
|
|
63
115
|
message_str: str,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
116
|
+
message_id: str = "",
|
|
117
|
+
raw_message: object = None,
|
|
118
|
+
group_id: str = ""
|
|
119
|
+
) -> AstrBotMessage:
|
|
67
120
|
"""
|
|
68
121
|
创建一个AstrBot消息对象
|
|
69
122
|
|
|
70
123
|
Args:
|
|
71
|
-
type (str):
|
|
124
|
+
type (str): 消息类型, 例如 "GroupMessage" "FriendMessage" "OtherMessage"
|
|
72
125
|
self_id (str): 机器人自身ID
|
|
73
126
|
session_id (str): 会话ID(通常为用户ID)(QQ号, 群号等)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
127
|
+
sender (MessageMember): 发送者信息, 例如 MessageMember(user_id="123456", nickname="昵称")
|
|
128
|
+
message (List[BaseMessageComponent]): 消息组件列表, 也就是消息链, 这个不会发给 llm, 但是会经过其他处理
|
|
129
|
+
message_str (str): 消息字符串, 也就是纯文本消息, 也就是发送给 llm 的消息, 与消息链一致
|
|
130
|
+
|
|
131
|
+
message_id (str): 消息ID, 构造消息时可以随意填写也可不填
|
|
132
|
+
raw_message (object): 原始消息对象, 可以随意填写也可不填
|
|
79
133
|
group_id (str, optional): 群组ID, 如果为私聊则为空. Defaults to "".
|
|
80
134
|
|
|
81
135
|
Returns:
|
|
82
136
|
AstrBotMessage: 创建的消息对象
|
|
83
137
|
"""
|
|
84
138
|
abm = AstrBotMessage()
|
|
85
|
-
abm.type = type
|
|
139
|
+
abm.type = MessageType(type)
|
|
86
140
|
abm.self_id = self_id
|
|
87
141
|
abm.session_id = session_id
|
|
142
|
+
if message_id == "":
|
|
143
|
+
message_id = uuid.uuid4().hex
|
|
88
144
|
abm.message_id = message_id
|
|
89
145
|
abm.sender = sender
|
|
90
146
|
abm.message = message
|
|
@@ -93,13 +149,38 @@ class StarTools:
|
|
|
93
149
|
abm.group_id = group_id
|
|
94
150
|
return abm
|
|
95
151
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
152
|
+
@classmethod
|
|
153
|
+
async def create_event(
|
|
154
|
+
cls, abm: AstrBotMessage, platform: str = "aiocqhttp", is_wake: bool = True
|
|
155
|
+
|
|
156
|
+
) -> None:
|
|
157
|
+
"""
|
|
158
|
+
创建并提交事件到指定平台
|
|
159
|
+
当有需要创建一个事件, 触发某些处理流程时, 使用该方法
|
|
101
160
|
|
|
102
|
-
|
|
161
|
+
Args:
|
|
162
|
+
abm (AstrBotMessage): 要提交的消息对象, 请先使用 create_message 创建
|
|
163
|
+
platform (str): 可选的平台名称,默认平台(aiocqhttp), 目前只支持 aiocqhttp
|
|
164
|
+
is_wake (bool): 是否标记为唤醒事件, 默认为 True, 只有唤醒事件才会被 llm 响应
|
|
165
|
+
"""
|
|
166
|
+
if cls._context is None:
|
|
167
|
+
raise ValueError("StarTools not initialized")
|
|
168
|
+
platforms = cls._context.platform_manager.get_insts()
|
|
169
|
+
if platform == "aiocqhttp":
|
|
170
|
+
adapter = next((p for p in platforms if isinstance(p, AiocqhttpAdapter)), None)
|
|
171
|
+
if adapter is None:
|
|
172
|
+
raise ValueError("未找到适配器: AiocqhttpAdapter")
|
|
173
|
+
event = AiocqhttpMessageEvent(
|
|
174
|
+
message_str=abm.message_str,
|
|
175
|
+
message_obj=abm,
|
|
176
|
+
platform_meta=adapter.metadata,
|
|
177
|
+
session_id=abm.session_id,
|
|
178
|
+
bot=adapter.bot,
|
|
179
|
+
)
|
|
180
|
+
event.is_wake = is_wake
|
|
181
|
+
adapter.commit_event(event)
|
|
182
|
+
else:
|
|
183
|
+
raise ValueError(f"不支持的平台: {platform}")
|
|
103
184
|
|
|
104
185
|
@classmethod
|
|
105
186
|
def activate_llm_tool(cls, name: str) -> bool:
|
|
@@ -110,6 +191,8 @@ class StarTools:
|
|
|
110
191
|
Args:
|
|
111
192
|
name (str): 工具名称
|
|
112
193
|
"""
|
|
194
|
+
if cls._context is None:
|
|
195
|
+
raise ValueError("StarTools not initialized")
|
|
113
196
|
return cls._context.activate_llm_tool(name)
|
|
114
197
|
|
|
115
198
|
@classmethod
|
|
@@ -120,6 +203,8 @@ class StarTools:
|
|
|
120
203
|
Args:
|
|
121
204
|
name (str): 工具名称
|
|
122
205
|
"""
|
|
206
|
+
if cls._context is None:
|
|
207
|
+
raise ValueError("StarTools not initialized")
|
|
123
208
|
return cls._context.deactivate_llm_tool(name)
|
|
124
209
|
|
|
125
210
|
@classmethod
|
|
@@ -135,6 +220,8 @@ class StarTools:
|
|
|
135
220
|
desc (str): 工具描述
|
|
136
221
|
func_obj (Awaitable): 函数对象,必须是异步函数
|
|
137
222
|
"""
|
|
223
|
+
if cls._context is None:
|
|
224
|
+
raise ValueError("StarTools not initialized")
|
|
138
225
|
cls._context.register_llm_tool(name, func_args, desc, func_obj)
|
|
139
226
|
|
|
140
227
|
@classmethod
|
|
@@ -146,6 +233,8 @@ class StarTools:
|
|
|
146
233
|
Args:
|
|
147
234
|
name (str): 工具名称
|
|
148
235
|
"""
|
|
236
|
+
if cls._context is None:
|
|
237
|
+
raise ValueError("StarTools not initialized")
|
|
149
238
|
cls._context.unregister_llm_tool(name)
|
|
150
239
|
|
|
151
240
|
@classmethod
|
|
@@ -169,8 +258,11 @@ class StarTools:
|
|
|
169
258
|
- 创建目录失败(权限不足或其他IO错误)
|
|
170
259
|
"""
|
|
171
260
|
if not plugin_name:
|
|
172
|
-
frame = inspect.currentframe()
|
|
173
|
-
module =
|
|
261
|
+
frame = inspect.currentframe()
|
|
262
|
+
module = None
|
|
263
|
+
if frame:
|
|
264
|
+
frame = frame.f_back
|
|
265
|
+
module = inspect.getmodule(frame)
|
|
174
266
|
|
|
175
267
|
if not module:
|
|
176
268
|
raise RuntimeError("无法获取调用者模块信息")
|
|
@@ -182,6 +274,9 @@ class StarTools:
|
|
|
182
274
|
|
|
183
275
|
plugin_name = metadata.name
|
|
184
276
|
|
|
277
|
+
if not plugin_name:
|
|
278
|
+
raise ValueError("无法获取插件名称")
|
|
279
|
+
|
|
185
280
|
data_dir = Path(os.path.join(get_astrbot_data_path(), "plugin_data", plugin_name))
|
|
186
281
|
|
|
187
282
|
try:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import aiohttp
|
|
2
2
|
import asyncio
|
|
3
|
-
import os
|
|
4
3
|
import ssl
|
|
5
4
|
import certifi
|
|
6
5
|
import logging
|
|
@@ -8,10 +7,9 @@ import random
|
|
|
8
7
|
from . import RenderStrategy
|
|
9
8
|
from astrbot.core.config import VERSION
|
|
10
9
|
from astrbot.core.utils.io import download_image_by_url
|
|
11
|
-
from astrbot.core.utils.
|
|
10
|
+
from astrbot.core.utils.t2i.template_manager import TemplateManager
|
|
12
11
|
|
|
13
12
|
ASTRBOT_T2I_DEFAULT_ENDPOINT = "https://t2i.soulter.top/text2img"
|
|
14
|
-
CUSTOM_T2I_TEMPLATE_PATH = os.path.join(get_astrbot_data_path(), "t2i_template.html")
|
|
15
13
|
|
|
16
14
|
logger = logging.getLogger("astrbot")
|
|
17
15
|
|
|
@@ -23,26 +21,17 @@ class NetworkRenderStrategy(RenderStrategy):
|
|
|
23
21
|
self.BASE_RENDER_URL = ASTRBOT_T2I_DEFAULT_ENDPOINT
|
|
24
22
|
else:
|
|
25
23
|
self.BASE_RENDER_URL = self._clean_url(base_url)
|
|
26
|
-
self.TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "template", "base.html")
|
|
27
|
-
with open(self.TEMPLATE_PATH, "r", encoding="utf-8") as f:
|
|
28
|
-
self.DEFAULT_TEMPLATE = f.read()
|
|
29
24
|
|
|
30
25
|
self.endpoints = [self.BASE_RENDER_URL]
|
|
26
|
+
self.template_manager = TemplateManager()
|
|
31
27
|
|
|
32
28
|
async def initialize(self):
|
|
33
29
|
if self.BASE_RENDER_URL == ASTRBOT_T2I_DEFAULT_ENDPOINT:
|
|
34
30
|
asyncio.create_task(self.get_official_endpoints())
|
|
35
31
|
|
|
36
|
-
async def get_template(self) -> str:
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
str: 文转图 HTML 模板字符串
|
|
41
|
-
"""
|
|
42
|
-
if os.path.exists(CUSTOM_T2I_TEMPLATE_PATH):
|
|
43
|
-
with open(CUSTOM_T2I_TEMPLATE_PATH, "r", encoding="utf-8") as f:
|
|
44
|
-
return f.read()
|
|
45
|
-
return self.DEFAULT_TEMPLATE
|
|
32
|
+
async def get_template(self, name: str = "base") -> str:
|
|
33
|
+
"""通过名称获取文转图 HTML 模板"""
|
|
34
|
+
return self.template_manager.get_template(name)
|
|
46
35
|
|
|
47
36
|
async def get_official_endpoints(self):
|
|
48
37
|
"""获取官方的 t2i 端点列表。"""
|
|
@@ -124,11 +113,15 @@ class NetworkRenderStrategy(RenderStrategy):
|
|
|
124
113
|
logger.error(f"All endpoints failed: {last_exception}")
|
|
125
114
|
raise RuntimeError(f"All endpoints failed: {last_exception}")
|
|
126
115
|
|
|
127
|
-
async def render(
|
|
116
|
+
async def render(
|
|
117
|
+
self, text: str, return_url: bool = False, template_name: str | None = "base"
|
|
118
|
+
) -> str:
|
|
128
119
|
"""
|
|
129
120
|
返回图像的文件路径
|
|
130
121
|
"""
|
|
131
|
-
|
|
122
|
+
if not template_name:
|
|
123
|
+
template_name = "base"
|
|
124
|
+
tmpl_str = await self.get_template(name=template_name)
|
|
132
125
|
text = text.replace("`", "\\`")
|
|
133
126
|
return await self.render_custom_template(
|
|
134
127
|
tmpl_str, {"text": text, "version": f"v{VERSION}"}, return_url
|
|
@@ -34,12 +34,18 @@ class HtmlRenderer:
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
async def render_t2i(
|
|
37
|
-
self,
|
|
37
|
+
self,
|
|
38
|
+
text: str,
|
|
39
|
+
use_network: bool = True,
|
|
40
|
+
return_url: bool = False,
|
|
41
|
+
template_name: str | None = None,
|
|
38
42
|
):
|
|
39
43
|
"""使用默认文转图模板。"""
|
|
40
44
|
if use_network:
|
|
41
45
|
try:
|
|
42
|
-
return await self.network_strategy.render(
|
|
46
|
+
return await self.network_strategy.render(
|
|
47
|
+
text, return_url=return_url, template_name=template_name
|
|
48
|
+
)
|
|
43
49
|
except BaseException as e:
|
|
44
50
|
logger.error(
|
|
45
51
|
f"Failed to render image via AstrBot API: {e}. Falling back to local rendering."
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<title>Astrbot PowerShell {{ version }} </title>
|
|
6
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/common.min.js"></script>
|
|
8
|
+
<script>hljs.highlightAll();</script>
|
|
9
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
|
|
10
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"
|
|
11
|
+
onload="renderMathInElement(document.getElementById('content'),{delimiters: [{left: '$$', right: '$$', display: true},{left: '$', right: '$', display: false}]});"></script>
|
|
12
|
+
<style>
|
|
13
|
+
:root {
|
|
14
|
+
--bg-color: #010409;
|
|
15
|
+
--text-color: #e6edf3;
|
|
16
|
+
--title-bar-color: #161b22;
|
|
17
|
+
--title-text-color: #e6edf3;
|
|
18
|
+
--font-family: 'Consolas', 'Microsoft YaHei Mono', 'Dengxian Mono', 'Courier New', monospace;
|
|
19
|
+
--glow-color: rgba(200, 220, 255, 0.7);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes scanline {
|
|
23
|
+
0% {
|
|
24
|
+
background-position: 0 0;
|
|
25
|
+
}
|
|
26
|
+
100% {
|
|
27
|
+
background-position: 0 100%;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
background-color: var(--bg-color);
|
|
33
|
+
color: var(--text-color);
|
|
34
|
+
font-family: var(--font-family);
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: 0;
|
|
37
|
+
line-height: 1.6;
|
|
38
|
+
font-size: 18px;
|
|
39
|
+
/* The CRT glow effect from the image */
|
|
40
|
+
text-shadow: 0 0 15px var(--glow-color), 0 0 7px rgba(255, 255, 255, 1);
|
|
41
|
+
position: relative;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
body::after {
|
|
46
|
+
content: " ";
|
|
47
|
+
display: block;
|
|
48
|
+
position: absolute;
|
|
49
|
+
top: 0;
|
|
50
|
+
left: 0;
|
|
51
|
+
right: 0;
|
|
52
|
+
bottom: 0;
|
|
53
|
+
background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.3) 50%);
|
|
54
|
+
background-size: 100% 4px;
|
|
55
|
+
z-index: 2;
|
|
56
|
+
pointer-events: none;
|
|
57
|
+
animation: scanline 8s linear infinite;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.header {
|
|
61
|
+
background-color: var(--title-bar-color);
|
|
62
|
+
padding: 12px 18px;
|
|
63
|
+
color: var(--title-text-color);
|
|
64
|
+
font-size: 16px;
|
|
65
|
+
border-bottom: 1px solid #30363d;
|
|
66
|
+
text-shadow: none; /* No glow for title bar */
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.header .title {
|
|
70
|
+
font-weight: bold;
|
|
71
|
+
font-size: 28px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.header .version {
|
|
75
|
+
opacity: 0.8;
|
|
76
|
+
margin-left: 1rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main {
|
|
80
|
+
padding: 1rem 1.5rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#content {
|
|
84
|
+
/* min-width and max-width removed as per request */
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* --- Markdown Styles adjusted for terminal look --- */
|
|
88
|
+
h1, h2, h3, h4, h5, h6 {
|
|
89
|
+
line-height: 1.4;
|
|
90
|
+
margin-top: 20px;
|
|
91
|
+
margin-bottom: 10px;
|
|
92
|
+
padding-bottom: 5px;
|
|
93
|
+
border-bottom: 1px solid #30363d;
|
|
94
|
+
color: var(--text-color);
|
|
95
|
+
}
|
|
96
|
+
h1 { font-size: 2rem; }
|
|
97
|
+
h2 { font-size: 1.7rem; }
|
|
98
|
+
h3 { font-size: 1.4rem; }
|
|
99
|
+
|
|
100
|
+
p {
|
|
101
|
+
margin-top: 1rem;
|
|
102
|
+
margin-bottom: 1rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
strong {
|
|
106
|
+
color: var(--text-color);
|
|
107
|
+
font-weight: bold;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
img {
|
|
111
|
+
max-width: 100%;
|
|
112
|
+
border: 1px solid #30363d;
|
|
113
|
+
display: block;
|
|
114
|
+
margin: 1rem auto;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
hr {
|
|
118
|
+
border: 0;
|
|
119
|
+
border-top: 1px dashed #30363d;
|
|
120
|
+
margin: 2rem 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
code {
|
|
124
|
+
font-family: var(--font-family);
|
|
125
|
+
padding: 0.2em 0.4em;
|
|
126
|
+
margin: 0;
|
|
127
|
+
font-size: 90%;
|
|
128
|
+
background-color: #161b22;
|
|
129
|
+
border-radius: 4px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
pre {
|
|
133
|
+
font-family: var(--font-family);
|
|
134
|
+
border-radius: 4px;
|
|
135
|
+
background: #0d1117;
|
|
136
|
+
padding: 1rem;
|
|
137
|
+
overflow-x: auto;
|
|
138
|
+
border: 1px solid #30363d;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pre > code {
|
|
142
|
+
padding: 0;
|
|
143
|
+
margin: 0;
|
|
144
|
+
font-size: 100%;
|
|
145
|
+
background-color: transparent;
|
|
146
|
+
border-radius: 0;
|
|
147
|
+
text-shadow: none; /* Disable glow inside code blocks for clarity */
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
a {
|
|
151
|
+
color: #58a6ff;
|
|
152
|
+
text-decoration: underline;
|
|
153
|
+
}
|
|
154
|
+
a:hover {
|
|
155
|
+
text-decoration: underline;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
blockquote {
|
|
159
|
+
border-left: 4px solid #30363d;
|
|
160
|
+
padding: 0.5rem 1rem;
|
|
161
|
+
margin: 1.5rem 0;
|
|
162
|
+
color: #8b949e;
|
|
163
|
+
background-color: #161b22;
|
|
164
|
+
}
|
|
165
|
+
</style>
|
|
166
|
+
</head>
|
|
167
|
+
<body>
|
|
168
|
+
|
|
169
|
+
<div class="header">
|
|
170
|
+
<span class="title">> Astrbot PowerShell</span>
|
|
171
|
+
<span class="version">{{ version }}</span>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<main>
|
|
175
|
+
<div id="content"></div>
|
|
176
|
+
</main>
|
|
177
|
+
|
|
178
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
179
|
+
<script>
|
|
180
|
+
document.getElementById('content').innerHTML = marked.parse(`{{ text | safe }}`);
|
|
181
|
+
</script>
|
|
182
|
+
|
|
183
|
+
</body>
|
|
184
|
+
</html>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# astrbot/core/utils/t2i/template_manager.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
from astrbot.core.utils.astrbot_path import get_astrbot_data_path, get_astrbot_path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TemplateManager:
|
|
9
|
+
"""
|
|
10
|
+
负责管理 t2i HTML 模板的 CRUD 和重置操作。
|
|
11
|
+
采用“用户覆盖内置”策略:用户模板存储在 data 目录中,并优先于内置模板加载。
|
|
12
|
+
所有创建、更新、删除操作仅影响用户目录,以确保更新框架时用户数据安全。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
CORE_TEMPLATES = ["base.html", "astrbot_powershell.html"]
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.builtin_template_dir = os.path.join(
|
|
19
|
+
get_astrbot_path(), "astrbot", "core", "utils", "t2i", "template"
|
|
20
|
+
)
|
|
21
|
+
self.user_template_dir = os.path.join(get_astrbot_data_path(), "t2i_templates")
|
|
22
|
+
|
|
23
|
+
os.makedirs(self.user_template_dir, exist_ok=True)
|
|
24
|
+
self._initialize_user_templates()
|
|
25
|
+
|
|
26
|
+
def _copy_core_templates(self, overwrite: bool = False):
|
|
27
|
+
"""从内置目录复制核心模板到用户目录。"""
|
|
28
|
+
for filename in self.CORE_TEMPLATES:
|
|
29
|
+
src = os.path.join(self.builtin_template_dir, filename)
|
|
30
|
+
dst = os.path.join(self.user_template_dir, filename)
|
|
31
|
+
if os.path.exists(src) and (overwrite or not os.path.exists(dst)):
|
|
32
|
+
shutil.copyfile(src, dst)
|
|
33
|
+
|
|
34
|
+
def _initialize_user_templates(self):
|
|
35
|
+
"""如果用户目录下缺少核心模板,则进行复制。"""
|
|
36
|
+
self._copy_core_templates(overwrite=False)
|
|
37
|
+
|
|
38
|
+
def _get_user_template_path(self, name: str) -> str:
|
|
39
|
+
"""获取用户模板的完整路径,防止路径遍历漏洞。"""
|
|
40
|
+
if ".." in name or "/" in name or "\\" in name:
|
|
41
|
+
raise ValueError("模板名称包含非法字符。")
|
|
42
|
+
return os.path.join(self.user_template_dir, f"{name}.html")
|
|
43
|
+
|
|
44
|
+
def _read_file(self, path: str) -> str:
|
|
45
|
+
"""读取文件内容。"""
|
|
46
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
47
|
+
return f.read()
|
|
48
|
+
|
|
49
|
+
def list_templates(self) -> list[dict]:
|
|
50
|
+
"""
|
|
51
|
+
列出所有可用模板。
|
|
52
|
+
该列表是内置模板和用户模板的合并视图,用户模板将覆盖同名的内置模板。
|
|
53
|
+
"""
|
|
54
|
+
dirs_to_scan = [self.builtin_template_dir, self.user_template_dir]
|
|
55
|
+
all_names = {
|
|
56
|
+
os.path.splitext(f)[0]
|
|
57
|
+
for d in dirs_to_scan
|
|
58
|
+
for f in os.listdir(d)
|
|
59
|
+
if f.endswith(".html")
|
|
60
|
+
}
|
|
61
|
+
return [
|
|
62
|
+
{"name": name, "is_default": name == "base"} for name in sorted(all_names)
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def get_template(self, name: str) -> str:
|
|
66
|
+
"""
|
|
67
|
+
获取指定模板的内容。
|
|
68
|
+
优先从用户目录加载,如果不存在则回退到内置目录。
|
|
69
|
+
"""
|
|
70
|
+
user_path = self._get_user_template_path(name)
|
|
71
|
+
if os.path.exists(user_path):
|
|
72
|
+
return self._read_file(user_path)
|
|
73
|
+
|
|
74
|
+
builtin_path = os.path.join(self.builtin_template_dir, f"{name}.html")
|
|
75
|
+
if os.path.exists(builtin_path):
|
|
76
|
+
return self._read_file(builtin_path)
|
|
77
|
+
|
|
78
|
+
raise FileNotFoundError("模板不存在。")
|
|
79
|
+
|
|
80
|
+
def create_template(self, name: str, content: str):
|
|
81
|
+
"""在用户目录中创建一个新的模板文件。"""
|
|
82
|
+
path = self._get_user_template_path(name)
|
|
83
|
+
if os.path.exists(path):
|
|
84
|
+
raise FileExistsError("同名模板已存在。")
|
|
85
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
86
|
+
f.write(content)
|
|
87
|
+
|
|
88
|
+
def update_template(self, name: str, content: str):
|
|
89
|
+
"""
|
|
90
|
+
更新一个模板。此操作始终写入用户目录。
|
|
91
|
+
如果更新的是一个内置模板,此操作实际上会在用户目录中创建一个修改后的副本,
|
|
92
|
+
从而实现对内置模板的“覆盖”。
|
|
93
|
+
"""
|
|
94
|
+
path = self._get_user_template_path(name)
|
|
95
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
96
|
+
f.write(content)
|
|
97
|
+
|
|
98
|
+
def delete_template(self, name: str):
|
|
99
|
+
"""
|
|
100
|
+
仅删除用户目录中的模板文件。
|
|
101
|
+
如果删除的是一个覆盖了内置模板的用户模板,这将有效地“恢复”到内置版本。
|
|
102
|
+
"""
|
|
103
|
+
path = self._get_user_template_path(name)
|
|
104
|
+
if not os.path.exists(path):
|
|
105
|
+
raise FileNotFoundError("用户模板不存在,无法删除。")
|
|
106
|
+
os.remove(path)
|
|
107
|
+
|
|
108
|
+
def reset_default_template(self):
|
|
109
|
+
"""
|
|
110
|
+
将核心模板从内置目录强制重置到用户目录。
|
|
111
|
+
"""
|
|
112
|
+
self._copy_core_templates(overwrite=True)
|
astrbot/dashboard/routes/chat.py
CHANGED
|
@@ -157,7 +157,11 @@ class ChatRoute(Route):
|
|
|
157
157
|
|
|
158
158
|
if type == "end":
|
|
159
159
|
break
|
|
160
|
-
elif (
|
|
160
|
+
elif (
|
|
161
|
+
(streaming and type == "complete")
|
|
162
|
+
or not streaming
|
|
163
|
+
or type == "break"
|
|
164
|
+
):
|
|
161
165
|
# append bot message
|
|
162
166
|
new_his = {"type": "bot", "message": result_text}
|
|
163
167
|
await self.platform_history_mgr.insert(
|
|
@@ -197,6 +201,7 @@ class ChatRoute(Route):
|
|
|
197
201
|
"Connection": "keep-alive",
|
|
198
202
|
},
|
|
199
203
|
)
|
|
204
|
+
response.timeout = None # fix SSE auto disconnect issue
|
|
200
205
|
return response
|
|
201
206
|
|
|
202
207
|
async def _get_webchat_conv_id_from_conv_id(self, conversation_id: str) -> str:
|