AstrBot 4.0.0b5__py3-none-any.whl → 4.1.1__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.
Files changed (33) hide show
  1. astrbot/api/event/filter/__init__.py +2 -0
  2. astrbot/core/config/default.py +73 -3
  3. astrbot/core/initial_loader.py +4 -1
  4. astrbot/core/message/components.py +59 -50
  5. astrbot/core/pipeline/result_decorate/stage.py +5 -1
  6. astrbot/core/platform/manager.py +25 -3
  7. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +11 -4
  8. astrbot/core/platform/sources/satori/satori_adapter.py +482 -0
  9. astrbot/core/platform/sources/satori/satori_event.py +221 -0
  10. astrbot/core/platform/sources/telegram/tg_adapter.py +0 -1
  11. astrbot/core/provider/sources/openai_source.py +14 -5
  12. astrbot/core/provider/sources/vllm_rerank_source.py +6 -0
  13. astrbot/core/star/__init__.py +7 -5
  14. astrbot/core/star/filter/command.py +9 -3
  15. astrbot/core/star/filter/platform_adapter_type.py +3 -0
  16. astrbot/core/star/register/__init__.py +2 -0
  17. astrbot/core/star/register/star_handler.py +18 -4
  18. astrbot/core/star/star_handler.py +9 -1
  19. astrbot/core/star/star_tools.py +116 -21
  20. astrbot/core/utils/t2i/network_strategy.py +11 -18
  21. astrbot/core/utils/t2i/renderer.py +8 -2
  22. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  23. astrbot/core/utils/t2i/template_manager.py +112 -0
  24. astrbot/dashboard/routes/chat.py +6 -1
  25. astrbot/dashboard/routes/config.py +10 -49
  26. astrbot/dashboard/routes/route.py +19 -2
  27. astrbot/dashboard/routes/t2i.py +230 -0
  28. astrbot/dashboard/server.py +13 -4
  29. {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/METADATA +39 -52
  30. {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/RECORD +33 -28
  31. {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/WHEEL +0 -0
  32. {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/entry_points.txt +0 -0
  33. {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -7,6 +7,7 @@ from astrbot.core.star.register import (
7
7
  register_permission_type as permission_type,
8
8
  register_custom_filter as custom_filter,
9
9
  register_on_astrbot_loaded as on_astrbot_loaded,
10
+ register_on_platform_loaded as on_platform_loaded,
10
11
  register_on_llm_request as on_llm_request,
11
12
  register_on_llm_response as on_llm_response,
12
13
  register_llm_tool as llm_tool,
@@ -41,6 +42,7 @@ __all__ = [
41
42
  "custom_filter",
42
43
  "PermissionType",
43
44
  "on_astrbot_loaded",
45
+ "on_platform_loaded",
44
46
  "on_llm_request",
45
47
  "llm_tool",
46
48
  "on_decorating_result",
@@ -6,7 +6,7 @@ import os
6
6
 
7
7
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
8
8
 
9
- VERSION = "4.0.0-beta.5"
9
+ VERSION = "4.1.1"
10
10
  DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
11
11
 
12
12
  # 默认配置
@@ -56,7 +56,7 @@ DEFAULT_CONFIG = {
56
56
  "wake_prefix": "",
57
57
  "web_search": False,
58
58
  "websearch_provider": "default",
59
- "websearch_tavily_key": "",
59
+ "websearch_tavily_key": [],
60
60
  "web_search_link": False,
61
61
  "display_reasoning_text": False,
62
62
  "identifier": False,
@@ -103,6 +103,7 @@ DEFAULT_CONFIG = {
103
103
  "t2i_strategy": "remote",
104
104
  "t2i_endpoint": "",
105
105
  "t2i_use_file_service": False,
106
+ "t2i_active_template": "base",
106
107
  "http_proxy": "",
107
108
  "no_proxy": ["localhost", "127.0.0.1", "::1"],
108
109
  "dashboard": {
@@ -246,8 +247,49 @@ CONFIG_METADATA_2 = {
246
247
  "slack_webhook_port": 6197,
247
248
  "slack_webhook_path": "/astrbot-slack-webhook/callback",
248
249
  },
250
+ "Satori": {
251
+ "id": "satori",
252
+ "type": "satori",
253
+ "enable": False,
254
+ "satori_api_base_url": "http://localhost:5140/satori/v1",
255
+ "satori_endpoint": "ws://127.0.0.1:5140/satori/v1/events",
256
+ "satori_token": "",
257
+ "satori_auto_reconnect": True,
258
+ "satori_heartbeat_interval": 10,
259
+ "satori_reconnect_delay": 5,
260
+ },
249
261
  },
250
262
  "items": {
263
+ "satori_api_base_url": {
264
+ "description": "Satori API Base URL",
265
+ "type": "string",
266
+ "hint": "The base URL for the Satori API.",
267
+ },
268
+ "satori_endpoint": {
269
+ "description": "Satori WebSocket Endpoint",
270
+ "type": "string",
271
+ "hint": "The WebSocket endpoint for Satori events.",
272
+ },
273
+ "satori_token": {
274
+ "description": "Satori Token",
275
+ "type": "string",
276
+ "hint": "The token used for authenticating with the Satori API.",
277
+ },
278
+ "satori_auto_reconnect": {
279
+ "description": "Enable Auto Reconnect",
280
+ "type": "bool",
281
+ "hint": "Whether to automatically reconnect the WebSocket on disconnection.",
282
+ },
283
+ "satori_heartbeat_interval": {
284
+ "description": "Satori Heartbeat Interval",
285
+ "type": "int",
286
+ "hint": "The interval (in seconds) for sending heartbeat messages.",
287
+ },
288
+ "satori_reconnect_delay": {
289
+ "description": "Satori Reconnect Delay",
290
+ "type": "int",
291
+ "hint": "The delay (in seconds) before attempting to reconnect.",
292
+ },
251
293
  "slack_connection_mode": {
252
294
  "description": "Slack Connection Mode",
253
295
  "type": "string",
@@ -557,6 +599,7 @@ CONFIG_METADATA_2 = {
557
599
  "api_base": "https://api.openai.com/v1",
558
600
  "timeout": 120,
559
601
  "model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
602
+ "custom_extra_body": {},
560
603
  "modalities": ["text", "image", "tool_use"],
561
604
  "hint": "也兼容所有与 OpenAI API 兼容的服务。",
562
605
  },
@@ -571,6 +614,7 @@ CONFIG_METADATA_2 = {
571
614
  "api_base": "",
572
615
  "timeout": 120,
573
616
  "model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
617
+ "custom_extra_body": {},
574
618
  "modalities": ["text", "image", "tool_use"],
575
619
  },
576
620
  "xAI": {
@@ -583,6 +627,7 @@ CONFIG_METADATA_2 = {
583
627
  "api_base": "https://api.x.ai/v1",
584
628
  "timeout": 120,
585
629
  "model_config": {"model": "grok-2-latest", "temperature": 0.4},
630
+ "custom_extra_body": {},
586
631
  "modalities": ["text", "image", "tool_use"],
587
632
  },
588
633
  "Anthropic": {
@@ -612,6 +657,7 @@ CONFIG_METADATA_2 = {
612
657
  "key": ["ollama"], # ollama 的 key 默认是 ollama
613
658
  "api_base": "http://localhost:11434/v1",
614
659
  "model_config": {"model": "llama3.1-8b", "temperature": 0.4},
660
+ "custom_extra_body": {},
615
661
  "modalities": ["text", "image", "tool_use"],
616
662
  },
617
663
  "LM Studio": {
@@ -625,6 +671,7 @@ CONFIG_METADATA_2 = {
625
671
  "model_config": {
626
672
  "model": "llama-3.1-8b",
627
673
  },
674
+ "custom_extra_body": {},
628
675
  "modalities": ["text", "image", "tool_use"],
629
676
  },
630
677
  "Gemini(OpenAI兼容)": {
@@ -640,6 +687,7 @@ CONFIG_METADATA_2 = {
640
687
  "model": "gemini-1.5-flash",
641
688
  "temperature": 0.4,
642
689
  },
690
+ "custom_extra_body": {},
643
691
  "modalities": ["text", "image", "tool_use"],
644
692
  },
645
693
  "Gemini": {
@@ -680,6 +728,7 @@ CONFIG_METADATA_2 = {
680
728
  "api_base": "https://api.deepseek.com/v1",
681
729
  "timeout": 120,
682
730
  "model_config": {"model": "deepseek-chat", "temperature": 0.4},
731
+ "custom_extra_body": {},
683
732
  "modalities": ["text", "image", "tool_use"],
684
733
  },
685
734
  "302.AI": {
@@ -692,6 +741,7 @@ CONFIG_METADATA_2 = {
692
741
  "api_base": "https://api.302.ai/v1",
693
742
  "timeout": 120,
694
743
  "model_config": {"model": "gpt-4.1-mini", "temperature": 0.4},
744
+ "custom_extra_body": {},
695
745
  "modalities": ["text", "image", "tool_use"],
696
746
  },
697
747
  "硅基流动": {
@@ -707,6 +757,7 @@ CONFIG_METADATA_2 = {
707
757
  "model": "deepseek-ai/DeepSeek-V3",
708
758
  "temperature": 0.4,
709
759
  },
760
+ "custom_extra_body": {},
710
761
  "modalities": ["text", "image", "tool_use"],
711
762
  },
712
763
  "PPIO派欧云": {
@@ -722,6 +773,7 @@ CONFIG_METADATA_2 = {
722
773
  "model": "deepseek/deepseek-r1",
723
774
  "temperature": 0.4,
724
775
  },
776
+ "custom_extra_body": {},
725
777
  },
726
778
  "优云智算": {
727
779
  "id": "compshare",
@@ -735,6 +787,7 @@ CONFIG_METADATA_2 = {
735
787
  "model_config": {
736
788
  "model": "moonshotai/Kimi-K2-Instruct",
737
789
  },
790
+ "custom_extra_body": {},
738
791
  "modalities": ["text", "image", "tool_use"],
739
792
  },
740
793
  "Kimi": {
@@ -747,6 +800,7 @@ CONFIG_METADATA_2 = {
747
800
  "timeout": 120,
748
801
  "api_base": "https://api.moonshot.cn/v1",
749
802
  "model_config": {"model": "moonshot-v1-8k", "temperature": 0.4},
803
+ "custom_extra_body": {},
750
804
  "modalities": ["text", "image", "tool_use"],
751
805
  },
752
806
  "智谱 AI": {
@@ -805,6 +859,7 @@ CONFIG_METADATA_2 = {
805
859
  "timeout": 120,
806
860
  "api_base": "https://api-inference.modelscope.cn/v1",
807
861
  "model_config": {"model": "Qwen/Qwen3-32B", "temperature": 0.4},
862
+ "custom_extra_body": {},
808
863
  "modalities": ["text", "image", "tool_use"],
809
864
  },
810
865
  "FastGPT": {
@@ -816,6 +871,7 @@ CONFIG_METADATA_2 = {
816
871
  "key": [],
817
872
  "api_base": "https://api.fastgpt.in/api/v1",
818
873
  "timeout": 60,
874
+ "custom_extra_body": {},
819
875
  },
820
876
  "Whisper(API)": {
821
877
  "id": "whisper",
@@ -1060,6 +1116,12 @@ CONFIG_METADATA_2 = {
1060
1116
  "render_type": "checkbox",
1061
1117
  "hint": "模型支持的模态。如所填写的模型不支持图像,请取消勾选图像。",
1062
1118
  },
1119
+ "custom_extra_body": {
1120
+ "description": "自定义请求体参数",
1121
+ "type": "dict",
1122
+ "items": {},
1123
+ "hint": "此处添加的键值对将被合并到发送给 API 的 extra_body 中。值可以是字符串、数字或布尔值。",
1124
+ },
1063
1125
  "provider": {
1064
1126
  "type": "string",
1065
1127
  "invisible": True,
@@ -1896,7 +1958,9 @@ CONFIG_METADATA_3 = {
1896
1958
  },
1897
1959
  "provider_settings.websearch_tavily_key": {
1898
1960
  "description": "Tavily API Key",
1899
- "type": "string",
1961
+ "type": "list",
1962
+ "items": {"type": "string"},
1963
+ "hint": "可添加多个 Key 进行轮询。",
1900
1964
  "condition": {
1901
1965
  "provider_settings.websearch_provider": "tavily",
1902
1966
  },
@@ -2293,6 +2357,12 @@ CONFIG_METADATA_3_SYSTEM = {
2293
2357
  },
2294
2358
  "_special": "t2i_template",
2295
2359
  },
2360
+ "t2i_active_template": {
2361
+ "description": "当前应用的文转图渲染模板",
2362
+ "type": "string",
2363
+ "hint": "此处的值由文转图模板管理页面进行维护。",
2364
+ "invisible": True,
2365
+ },
2296
2366
  "log_level": {
2297
2367
  "description": "控制台日志级别",
2298
2368
  "type": "string",
@@ -22,6 +22,7 @@ class InitialLoader:
22
22
  self.db = db
23
23
  self.logger = logger
24
24
  self.log_broker = log_broker
25
+ self.webui_dir: str | None = None
25
26
 
26
27
  async def start(self):
27
28
  core_lifecycle = AstrBotCoreLifecycle(self.log_broker, self.db)
@@ -35,8 +36,10 @@ class InitialLoader:
35
36
 
36
37
  core_task = core_lifecycle.start()
37
38
 
39
+ webui_dir = self.webui_dir
40
+
38
41
  self.dashboard_server = AstrBotDashboard(
39
- core_lifecycle, self.db, core_lifecycle.dashboard_shutdown_event
42
+ core_lifecycle, self.db, core_lifecycle.dashboard_shutdown_event, webui_dir
40
43
  )
41
44
  task = asyncio.gather(
42
45
  core_task, self.dashboard_server.run()
@@ -37,7 +37,7 @@ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
37
37
  from astrbot.core.utils.io import download_file, download_image_by_url, file_to_base64
38
38
 
39
39
 
40
- class ComponentType(Enum):
40
+ class ComponentType(str, Enum):
41
41
  Plain = "Plain" # 纯文本消息
42
42
  Face = "Face" # QQ表情
43
43
  Record = "Record" # 语音
@@ -108,7 +108,7 @@ class BaseMessageComponent(BaseModel):
108
108
 
109
109
 
110
110
  class Plain(BaseMessageComponent):
111
- type: ComponentType = "Plain"
111
+ type = ComponentType.Plain
112
112
  text: str
113
113
  convert: T.Optional[bool] = True # 若为 False 则直接发送未转换 CQ 码的消息
114
114
 
@@ -128,8 +128,9 @@ class Plain(BaseMessageComponent):
128
128
  async def to_dict(self):
129
129
  return {"type": "text", "data": {"text": self.text}}
130
130
 
131
+
131
132
  class Face(BaseMessageComponent):
132
- type: ComponentType = "Face"
133
+ type = ComponentType.Face
133
134
  id: int
134
135
 
135
136
  def __init__(self, **_):
@@ -137,7 +138,7 @@ class Face(BaseMessageComponent):
137
138
 
138
139
 
139
140
  class Record(BaseMessageComponent):
140
- type: ComponentType = "Record"
141
+ type = ComponentType.Record
141
142
  file: T.Optional[str] = ""
142
143
  magic: T.Optional[bool] = False
143
144
  url: T.Optional[str] = ""
@@ -164,19 +165,24 @@ class Record(BaseMessageComponent):
164
165
  return Record(file=url, **_)
165
166
  raise Exception("not a valid url")
166
167
 
168
+ @staticmethod
169
+ def fromBase64(bs64_data: str, **_):
170
+ return Record(file=f"base64://{bs64_data}", **_)
171
+
167
172
  async def convert_to_file_path(self) -> str:
168
173
  """将这个语音统一转换为本地文件路径。这个方法避免了手动判断语音数据类型,直接返回语音数据的本地路径(如果是网络 URL, 则会自动进行下载)。
169
174
 
170
175
  Returns:
171
176
  str: 语音的本地路径,以绝对路径表示。
172
177
  """
173
- if self.file and self.file.startswith("file:///"):
174
- file_path = self.file[8:]
175
- return file_path
176
- elif self.file and self.file.startswith("http"):
178
+ if not self.file:
179
+ raise Exception(f"not a valid file: {self.file}")
180
+ if self.file.startswith("file:///"):
181
+ return self.file[8:]
182
+ elif self.file.startswith("http"):
177
183
  file_path = await download_image_by_url(self.file)
178
184
  return os.path.abspath(file_path)
179
- elif self.file and self.file.startswith("base64://"):
185
+ elif self.file.startswith("base64://"):
180
186
  bs64_data = self.file.removeprefix("base64://")
181
187
  image_bytes = base64.b64decode(bs64_data)
182
188
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
@@ -185,8 +191,7 @@ class Record(BaseMessageComponent):
185
191
  f.write(image_bytes)
186
192
  return os.path.abspath(file_path)
187
193
  elif os.path.exists(self.file):
188
- file_path = self.file
189
- return os.path.abspath(file_path)
194
+ return os.path.abspath(self.file)
190
195
  else:
191
196
  raise Exception(f"not a valid file: {self.file}")
192
197
 
@@ -197,12 +202,14 @@ class Record(BaseMessageComponent):
197
202
  str: 语音的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
198
203
  """
199
204
  # convert to base64
200
- if self.file and self.file.startswith("file:///"):
205
+ if not self.file:
206
+ raise Exception(f"not a valid file: {self.file}")
207
+ if self.file.startswith("file:///"):
201
208
  bs64_data = file_to_base64(self.file[8:])
202
- elif self.file and self.file.startswith("http"):
209
+ elif self.file.startswith("http"):
203
210
  file_path = await download_image_by_url(self.file)
204
211
  bs64_data = file_to_base64(file_path)
205
- elif self.file and self.file.startswith("base64://"):
212
+ elif self.file.startswith("base64://"):
206
213
  bs64_data = self.file
207
214
  elif os.path.exists(self.file):
208
215
  bs64_data = file_to_base64(self.file)
@@ -236,7 +243,7 @@ class Record(BaseMessageComponent):
236
243
 
237
244
 
238
245
  class Video(BaseMessageComponent):
239
- type: ComponentType = "Video"
246
+ type = ComponentType.Video
240
247
  file: str
241
248
  cover: T.Optional[str] = ""
242
249
  c: T.Optional[int] = 2
@@ -322,7 +329,7 @@ class Video(BaseMessageComponent):
322
329
 
323
330
 
324
331
  class At(BaseMessageComponent):
325
- type: ComponentType = "At"
332
+ type = ComponentType.At
326
333
  qq: T.Union[int, str] # 此处str为all时代表所有人
327
334
  name: T.Optional[str] = ""
328
335
 
@@ -344,28 +351,28 @@ class AtAll(At):
344
351
 
345
352
 
346
353
  class RPS(BaseMessageComponent): # TODO
347
- type: ComponentType = "RPS"
354
+ type = ComponentType.RPS
348
355
 
349
356
  def __init__(self, **_):
350
357
  super().__init__(**_)
351
358
 
352
359
 
353
360
  class Dice(BaseMessageComponent): # TODO
354
- type: ComponentType = "Dice"
361
+ type = ComponentType.Dice
355
362
 
356
363
  def __init__(self, **_):
357
364
  super().__init__(**_)
358
365
 
359
366
 
360
367
  class Shake(BaseMessageComponent): # TODO
361
- type: ComponentType = "Shake"
368
+ type = ComponentType.Shake
362
369
 
363
370
  def __init__(self, **_):
364
371
  super().__init__(**_)
365
372
 
366
373
 
367
374
  class Anonymous(BaseMessageComponent): # TODO
368
- type: ComponentType = "Anonymous"
375
+ type = ComponentType.Anonymous
369
376
  ignore: T.Optional[bool] = False
370
377
 
371
378
  def __init__(self, **_):
@@ -373,7 +380,7 @@ class Anonymous(BaseMessageComponent): # TODO
373
380
 
374
381
 
375
382
  class Share(BaseMessageComponent):
376
- type: ComponentType = "Share"
383
+ type = ComponentType.Share
377
384
  url: str
378
385
  title: str
379
386
  content: T.Optional[str] = ""
@@ -384,7 +391,7 @@ class Share(BaseMessageComponent):
384
391
 
385
392
 
386
393
  class Contact(BaseMessageComponent): # TODO
387
- type: ComponentType = "Contact"
394
+ type = ComponentType.Contact
388
395
  _type: str # type 字段冲突
389
396
  id: T.Optional[int] = 0
390
397
 
@@ -393,7 +400,7 @@ class Contact(BaseMessageComponent): # TODO
393
400
 
394
401
 
395
402
  class Location(BaseMessageComponent): # TODO
396
- type: ComponentType = "Location"
403
+ type = ComponentType.Location
397
404
  lat: float
398
405
  lon: float
399
406
  title: T.Optional[str] = ""
@@ -404,7 +411,7 @@ class Location(BaseMessageComponent): # TODO
404
411
 
405
412
 
406
413
  class Music(BaseMessageComponent):
407
- type: ComponentType = "Music"
414
+ type = ComponentType.Music
408
415
  _type: str
409
416
  id: T.Optional[int] = 0
410
417
  url: T.Optional[str] = ""
@@ -421,7 +428,7 @@ class Music(BaseMessageComponent):
421
428
 
422
429
 
423
430
  class Image(BaseMessageComponent):
424
- type: ComponentType = "Image"
431
+ type = ComponentType.Image
425
432
  file: T.Optional[str] = ""
426
433
  _type: T.Optional[str] = ""
427
434
  subType: T.Optional[int] = 0
@@ -464,14 +471,15 @@ class Image(BaseMessageComponent):
464
471
  Returns:
465
472
  str: 图片的本地路径,以绝对路径表示。
466
473
  """
467
- url = self.url if self.url else self.file
468
- if url and url.startswith("file:///"):
469
- image_file_path = url[8:]
470
- return image_file_path
471
- elif url and url.startswith("http"):
474
+ url = self.url or self.file
475
+ if not url:
476
+ raise ValueError("No valid file or URL provided")
477
+ if url.startswith("file:///"):
478
+ return url[8:]
479
+ elif url.startswith("http"):
472
480
  image_file_path = await download_image_by_url(url)
473
481
  return os.path.abspath(image_file_path)
474
- elif url and url.startswith("base64://"):
482
+ elif url.startswith("base64://"):
475
483
  bs64_data = url.removeprefix("base64://")
476
484
  image_bytes = base64.b64decode(bs64_data)
477
485
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
@@ -480,8 +488,7 @@ class Image(BaseMessageComponent):
480
488
  f.write(image_bytes)
481
489
  return os.path.abspath(image_file_path)
482
490
  elif os.path.exists(url):
483
- image_file_path = url
484
- return os.path.abspath(image_file_path)
491
+ return os.path.abspath(url)
485
492
  else:
486
493
  raise Exception(f"not a valid file: {url}")
487
494
 
@@ -492,13 +499,15 @@ class Image(BaseMessageComponent):
492
499
  str: 图片的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
493
500
  """
494
501
  # convert to base64
495
- url = self.url if self.url else self.file
496
- if url and url.startswith("file:///"):
502
+ url = self.url or self.file
503
+ if not url:
504
+ raise ValueError("No valid file or URL provided")
505
+ if url.startswith("file:///"):
497
506
  bs64_data = file_to_base64(url[8:])
498
- elif url and url.startswith("http"):
507
+ elif url.startswith("http"):
499
508
  image_file_path = await download_image_by_url(url)
500
509
  bs64_data = file_to_base64(image_file_path)
501
- elif url and url.startswith("base64://"):
510
+ elif url.startswith("base64://"):
502
511
  bs64_data = url
503
512
  elif os.path.exists(url):
504
513
  bs64_data = file_to_base64(url)
@@ -532,7 +541,7 @@ class Image(BaseMessageComponent):
532
541
 
533
542
 
534
543
  class Reply(BaseMessageComponent):
535
- type: ComponentType = "Reply"
544
+ type = ComponentType.Reply
536
545
  id: T.Union[str, int]
537
546
  """所引用的消息 ID"""
538
547
  chain: T.Optional[T.List["BaseMessageComponent"]] = []
@@ -558,7 +567,7 @@ class Reply(BaseMessageComponent):
558
567
 
559
568
 
560
569
  class RedBag(BaseMessageComponent):
561
- type: ComponentType = "RedBag"
570
+ type = ComponentType.RedBag
562
571
  title: str
563
572
 
564
573
  def __init__(self, **_):
@@ -566,7 +575,7 @@ class RedBag(BaseMessageComponent):
566
575
 
567
576
 
568
577
  class Poke(BaseMessageComponent):
569
- type: str = ""
578
+ type: str = ComponentType.Poke
570
579
  id: T.Optional[int] = 0
571
580
  qq: T.Optional[int] = 0
572
581
 
@@ -576,7 +585,7 @@ class Poke(BaseMessageComponent):
576
585
 
577
586
 
578
587
  class Forward(BaseMessageComponent):
579
- type: ComponentType = "Forward"
588
+ type = ComponentType.Forward
580
589
  id: str
581
590
 
582
591
  def __init__(self, **_):
@@ -586,7 +595,7 @@ class Forward(BaseMessageComponent):
586
595
  class Node(BaseMessageComponent):
587
596
  """群合并转发消息"""
588
597
 
589
- type: ComponentType = "Node"
598
+ type = ComponentType.Node
590
599
  id: T.Optional[int] = 0 # 忽略
591
600
  name: T.Optional[str] = "" # qq昵称
592
601
  uin: T.Optional[str] = "0" # qq号
@@ -638,7 +647,7 @@ class Node(BaseMessageComponent):
638
647
 
639
648
 
640
649
  class Nodes(BaseMessageComponent):
641
- type: ComponentType = "Nodes"
650
+ type = ComponentType.Nodes
642
651
  nodes: T.List[Node]
643
652
 
644
653
  def __init__(self, nodes: T.List[Node], **_):
@@ -664,7 +673,7 @@ class Nodes(BaseMessageComponent):
664
673
 
665
674
 
666
675
  class Xml(BaseMessageComponent):
667
- type: ComponentType = "Xml"
676
+ type = ComponentType.Xml
668
677
  data: str
669
678
  resid: T.Optional[int] = 0
670
679
 
@@ -673,7 +682,7 @@ class Xml(BaseMessageComponent):
673
682
 
674
683
 
675
684
  class Json(BaseMessageComponent):
676
- type: ComponentType = "Json"
685
+ type = ComponentType.Json
677
686
  data: T.Union[str, dict]
678
687
  resid: T.Optional[int] = 0
679
688
 
@@ -684,7 +693,7 @@ class Json(BaseMessageComponent):
684
693
 
685
694
 
686
695
  class CardImage(BaseMessageComponent):
687
- type: ComponentType = "CardImage"
696
+ type = ComponentType.CardImage
688
697
  file: str
689
698
  cache: T.Optional[bool] = True
690
699
  minwidth: T.Optional[int] = 400
@@ -703,7 +712,7 @@ class CardImage(BaseMessageComponent):
703
712
 
704
713
 
705
714
  class TTS(BaseMessageComponent):
706
- type: ComponentType = "TTS"
715
+ type = ComponentType.TTS
707
716
  text: str
708
717
 
709
718
  def __init__(self, **_):
@@ -711,7 +720,7 @@ class TTS(BaseMessageComponent):
711
720
 
712
721
 
713
722
  class Unknown(BaseMessageComponent):
714
- type: ComponentType = "Unknown"
723
+ type = ComponentType.Unknown
715
724
  text: str
716
725
 
717
726
  def toString(self):
@@ -723,7 +732,7 @@ class File(BaseMessageComponent):
723
732
  文件消息段
724
733
  """
725
734
 
726
- type: ComponentType = "File"
735
+ type = ComponentType.File
727
736
  name: T.Optional[str] = "" # 名字
728
737
  file_: T.Optional[str] = "" # 本地路径
729
738
  url: T.Optional[str] = "" # url
@@ -853,7 +862,7 @@ class File(BaseMessageComponent):
853
862
 
854
863
 
855
864
  class WechatEmoji(BaseMessageComponent):
856
- type: ComponentType = "WechatEmoji"
865
+ type = ComponentType.WechatEmoji
857
866
  md5: T.Optional[str] = ""
858
867
  md5_len: T.Optional[int] = 0
859
868
  cdnurl: T.Optional[str] = ""
@@ -36,6 +36,7 @@ class ResultDecorateStage(Stage):
36
36
  self.t2i_word_threshold = 150
37
37
  self.t2i_strategy = ctx.astrbot_config["t2i_strategy"]
38
38
  self.t2i_use_network = self.t2i_strategy == "remote"
39
+ self.t2i_active_template = ctx.astrbot_config["t2i_active_template"]
39
40
 
40
41
  self.forward_threshold = ctx.astrbot_config["platform_settings"][
41
42
  "forward_threshold"
@@ -247,7 +248,10 @@ class ResultDecorateStage(Stage):
247
248
  render_start = time.time()
248
249
  try:
249
250
  url = await html_renderer.render_t2i(
250
- plain_str, return_url=True, use_network=self.t2i_use_network
251
+ plain_str,
252
+ return_url=True,
253
+ use_network=self.t2i_use_network,
254
+ template_name=self.t2i_active_template,
251
255
  )
252
256
  except BaseException:
253
257
  logger.error("文本转图片失败,使用文本发送。")