AstrBot 4.2.1__py3-none-any.whl → 4.3.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.
- astrbot/core/config/default.py +49 -10
- astrbot/core/db/__init__.py +12 -1
- astrbot/core/db/po.py +8 -4
- astrbot/core/db/sqlite.py +144 -34
- astrbot/core/pipeline/preprocess_stage/stage.py +24 -0
- astrbot/core/pipeline/result_decorate/stage.py +5 -1
- astrbot/core/platform/astr_message_event.py +10 -0
- astrbot/core/platform/platform_metadata.py +2 -0
- astrbot/core/platform/register.py +3 -0
- astrbot/core/platform/sources/lark/lark_event.py +16 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +59 -23
- astrbot/core/platform/sources/telegram/tg_event.py +34 -0
- astrbot/core/provider/provider.py +1 -1
- astrbot/core/provider/sources/zhipu_source.py +5 -65
- astrbot/core/star/filter/command.py +32 -2
- astrbot/core/star/register/star_handler.py +6 -4
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/dashboard/routes/config.py +88 -2
- astrbot/dashboard/routes/session_management.py +37 -29
- {astrbot-4.2.1.dist-info → astrbot-4.3.1.dist-info}/METADATA +3 -3
- {astrbot-4.2.1.dist-info → astrbot-4.3.1.dist-info}/RECORD +24 -24
- {astrbot-4.2.1.dist-info → astrbot-4.3.1.dist-info}/WHEEL +0 -0
- {astrbot-4.2.1.dist-info → astrbot-4.3.1.dist-info}/entry_points.txt +0 -0
- {astrbot-4.2.1.dist-info → astrbot-4.3.1.dist-info}/licenses/LICENSE +0 -0
astrbot/core/config/default.py
CHANGED
|
@@ -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.
|
|
9
|
+
VERSION = "4.3.1"
|
|
10
10
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
11
11
|
|
|
12
12
|
# 默认配置
|
|
@@ -64,7 +64,7 @@ DEFAULT_CONFIG = {
|
|
|
64
64
|
"datetime_system_prompt": True,
|
|
65
65
|
"default_personality": "default",
|
|
66
66
|
"persona_pool": ["*"],
|
|
67
|
-
"prompt_prefix": "",
|
|
67
|
+
"prompt_prefix": "{{prompt}}",
|
|
68
68
|
"max_context_length": -1,
|
|
69
69
|
"dequeue_context_length": 1,
|
|
70
70
|
"streaming_response": False,
|
|
@@ -116,6 +116,15 @@ DEFAULT_CONFIG = {
|
|
|
116
116
|
"port": 6185,
|
|
117
117
|
},
|
|
118
118
|
"platform": [],
|
|
119
|
+
"platform_specific": {
|
|
120
|
+
# 平台特异配置:按平台分类,平台下按功能分组
|
|
121
|
+
"lark": {
|
|
122
|
+
"pre_ack_emoji": {"enable": False, "emojis": ["Typing"]},
|
|
123
|
+
},
|
|
124
|
+
"telegram": {
|
|
125
|
+
"pre_ack_emoji": {"enable": False, "emojis": ["✍️"]},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
119
128
|
"wake_prefix": ["/"],
|
|
120
129
|
"log_level": "INFO",
|
|
121
130
|
"pip_install_arg": "",
|
|
@@ -1976,26 +1985,28 @@ CONFIG_METADATA_3 = {
|
|
|
1976
1985
|
"hint": "留空代表不使用。可用于不支持视觉模态的聊天模型。",
|
|
1977
1986
|
},
|
|
1978
1987
|
"provider_stt_settings.enable": {
|
|
1979
|
-
"description": "
|
|
1988
|
+
"description": "启用语音转文本",
|
|
1980
1989
|
"type": "bool",
|
|
1990
|
+
"hint": "STT 总开关。",
|
|
1981
1991
|
},
|
|
1982
1992
|
"provider_stt_settings.provider_id": {
|
|
1983
|
-
"description": "
|
|
1993
|
+
"description": "默认语音转文本模型",
|
|
1984
1994
|
"type": "string",
|
|
1985
|
-
"hint": "
|
|
1995
|
+
"hint": "用户也可使用 /provider 指令单独选择会话的 STT 模型。",
|
|
1986
1996
|
"_special": "select_provider_stt",
|
|
1987
1997
|
"condition": {
|
|
1988
1998
|
"provider_stt_settings.enable": True,
|
|
1989
1999
|
},
|
|
1990
2000
|
},
|
|
1991
2001
|
"provider_tts_settings.enable": {
|
|
1992
|
-
"description": "
|
|
2002
|
+
"description": "启用文本转语音",
|
|
1993
2003
|
"type": "bool",
|
|
2004
|
+
"hint": "TTS 总开关。当关闭时,会话启用 TTS 也不会生效。",
|
|
1994
2005
|
},
|
|
1995
2006
|
"provider_tts_settings.provider_id": {
|
|
1996
|
-
"description": "
|
|
2007
|
+
"description": "默认文本转语音模型",
|
|
1997
2008
|
"type": "string",
|
|
1998
|
-
"hint": "
|
|
2009
|
+
"hint": "用户也可使用 /provider 单独选择会话的 TTS 模型。",
|
|
1999
2010
|
"_special": "select_provider_tts",
|
|
2000
2011
|
"condition": {
|
|
2001
2012
|
"provider_tts_settings.enable": True,
|
|
@@ -2107,12 +2118,14 @@ CONFIG_METADATA_3 = {
|
|
|
2107
2118
|
"provider_settings.wake_prefix": {
|
|
2108
2119
|
"description": "LLM 聊天额外唤醒前缀 ",
|
|
2109
2120
|
"type": "string",
|
|
2121
|
+
"hint": "例子: 如果唤醒前缀为 `/`, 额外聊天唤醒前缀为 `chat`,则需要 `/chat` 才会触发 LLM 请求。默认为空。",
|
|
2110
2122
|
},
|
|
2111
2123
|
"provider_settings.prompt_prefix": {
|
|
2112
|
-
"description": "
|
|
2124
|
+
"description": "用户提示词",
|
|
2113
2125
|
"type": "string",
|
|
2126
|
+
"hint": "可使用 {{prompt}} 作为用户输入的占位符。如果不输入占位符则代表添加在用户输入的前面。",
|
|
2114
2127
|
},
|
|
2115
|
-
"
|
|
2128
|
+
"provider_tts_settings.dual_output": {
|
|
2116
2129
|
"description": "开启 TTS 时同时输出语音和文字内容",
|
|
2117
2130
|
"type": "bool",
|
|
2118
2131
|
},
|
|
@@ -2293,6 +2306,32 @@ CONFIG_METADATA_3 = {
|
|
|
2293
2306
|
"description": "用户权限不足时是否回复",
|
|
2294
2307
|
"type": "bool",
|
|
2295
2308
|
},
|
|
2309
|
+
"platform_specific.lark.pre_ack_emoji.enable": {
|
|
2310
|
+
"description": "[飞书] 启用预回应表情",
|
|
2311
|
+
"type": "bool",
|
|
2312
|
+
},
|
|
2313
|
+
"platform_specific.lark.pre_ack_emoji.emojis": {
|
|
2314
|
+
"description": "表情列表(飞书表情枚举名)",
|
|
2315
|
+
"type": "list",
|
|
2316
|
+
"items": {"type": "string"},
|
|
2317
|
+
"hint": "表情枚举名参考:https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce",
|
|
2318
|
+
"condition": {
|
|
2319
|
+
"platform_specific.lark.pre_ack_emoji.enable": True,
|
|
2320
|
+
},
|
|
2321
|
+
},
|
|
2322
|
+
"platform_specific.telegram.pre_ack_emoji.enable": {
|
|
2323
|
+
"description": "[Telegram] 启用预回应表情",
|
|
2324
|
+
"type": "bool",
|
|
2325
|
+
},
|
|
2326
|
+
"platform_specific.telegram.pre_ack_emoji.emojis": {
|
|
2327
|
+
"description": "表情列表(Unicode)",
|
|
2328
|
+
"type": "list",
|
|
2329
|
+
"items": {"type": "string"},
|
|
2330
|
+
"hint": "Telegram 仅支持固定反应集合,参考:https://gist.github.com/Soulter/3f22c8e5f9c7e152e967e8bc28c97fc9",
|
|
2331
|
+
"condition": {
|
|
2332
|
+
"platform_specific.telegram.pre_ack_emoji.enable": True,
|
|
2333
|
+
},
|
|
2334
|
+
},
|
|
2296
2335
|
},
|
|
2297
2336
|
},
|
|
2298
2337
|
},
|
astrbot/core/db/__init__.py
CHANGED
|
@@ -164,7 +164,7 @@ class BaseDatabase(abc.ABC):
|
|
|
164
164
|
self,
|
|
165
165
|
platform_id: str,
|
|
166
166
|
user_id: str,
|
|
167
|
-
content:
|
|
167
|
+
content: dict,
|
|
168
168
|
sender_id: str | None = None,
|
|
169
169
|
sender_name: str | None = None,
|
|
170
170
|
) -> None:
|
|
@@ -287,3 +287,14 @@ class BaseDatabase(abc.ABC):
|
|
|
287
287
|
# async def get_llm_messages(self, cid: str) -> list[LLMMessage]:
|
|
288
288
|
# """Get all LLM messages for a specific conversation."""
|
|
289
289
|
# ...
|
|
290
|
+
|
|
291
|
+
@abc.abstractmethod
|
|
292
|
+
async def get_session_conversations(
|
|
293
|
+
self,
|
|
294
|
+
page: int = 1,
|
|
295
|
+
page_size: int = 20,
|
|
296
|
+
search_query: str | None = None,
|
|
297
|
+
platform: str | None = None,
|
|
298
|
+
) -> tuple[list[dict], int]:
|
|
299
|
+
"""Get paginated session conversations with joined conversation and persona details, support search and platform filter."""
|
|
300
|
+
...
|
astrbot/core/db/po.py
CHANGED
|
@@ -75,7 +75,9 @@ class Persona(SQLModel, table=True):
|
|
|
75
75
|
|
|
76
76
|
__tablename__ = "personas"
|
|
77
77
|
|
|
78
|
-
id: int = Field(
|
|
78
|
+
id: int | None = Field(
|
|
79
|
+
primary_key=True, sa_column_kwargs={"autoincrement": True}, default=None
|
|
80
|
+
)
|
|
79
81
|
persona_id: str = Field(max_length=255, nullable=False)
|
|
80
82
|
system_prompt: str = Field(sa_type=Text, nullable=False)
|
|
81
83
|
begin_dialogs: Optional[list] = Field(default=None, sa_type=JSON)
|
|
@@ -135,7 +137,9 @@ class PlatformMessageHistory(SQLModel, table=True):
|
|
|
135
137
|
|
|
136
138
|
__tablename__ = "platform_message_history"
|
|
137
139
|
|
|
138
|
-
id: int = Field(
|
|
140
|
+
id: int | None = Field(
|
|
141
|
+
primary_key=True, sa_column_kwargs={"autoincrement": True}, default=None
|
|
142
|
+
)
|
|
139
143
|
platform_id: str = Field(nullable=False)
|
|
140
144
|
user_id: str = Field(nullable=False) # An id of group, user in platform
|
|
141
145
|
sender_id: Optional[str] = Field(default=None) # ID of the sender in the platform
|
|
@@ -158,8 +162,8 @@ class Attachment(SQLModel, table=True):
|
|
|
158
162
|
|
|
159
163
|
__tablename__ = "attachments"
|
|
160
164
|
|
|
161
|
-
inner_attachment_id: int = Field(
|
|
162
|
-
primary_key=True, sa_column_kwargs={"autoincrement": True}
|
|
165
|
+
inner_attachment_id: int | None = Field(
|
|
166
|
+
primary_key=True, sa_column_kwargs={"autoincrement": True}, default=None
|
|
163
167
|
)
|
|
164
168
|
attachment_id: str = Field(
|
|
165
169
|
max_length=36,
|
astrbot/core/db/sqlite.py
CHANGED
|
@@ -15,10 +15,8 @@ from astrbot.core.db.po import (
|
|
|
15
15
|
SQLModel,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
-
from
|
|
18
|
+
from sqlmodel import select, update, delete, text, func, or_, desc, col
|
|
19
19
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
20
|
-
from sqlalchemy.sql import func
|
|
21
|
-
from sqlalchemy import or_
|
|
22
20
|
|
|
23
21
|
NOT_GIVEN = T.TypeVar("NOT_GIVEN")
|
|
24
22
|
|
|
@@ -42,10 +40,10 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
42
40
|
|
|
43
41
|
async def insert_platform_stats(
|
|
44
42
|
self,
|
|
45
|
-
platform_id
|
|
46
|
-
platform_type
|
|
47
|
-
count
|
|
48
|
-
timestamp
|
|
43
|
+
platform_id,
|
|
44
|
+
platform_type,
|
|
45
|
+
count=1,
|
|
46
|
+
timestamp=None,
|
|
49
47
|
) -> None:
|
|
50
48
|
"""Insert a new platform statistic record."""
|
|
51
49
|
async with self.get_db() as session:
|
|
@@ -76,7 +74,9 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
76
74
|
async with self.get_db() as session:
|
|
77
75
|
session: AsyncSession
|
|
78
76
|
result = await session.execute(
|
|
79
|
-
select(func.count(PlatformStat.platform_id)).select_from(
|
|
77
|
+
select(func.count(col(PlatformStat.platform_id))).select_from(
|
|
78
|
+
PlatformStat
|
|
79
|
+
)
|
|
80
80
|
)
|
|
81
81
|
count = result.scalar_one_or_none()
|
|
82
82
|
return count if count is not None else 0
|
|
@@ -96,7 +96,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
96
96
|
"""),
|
|
97
97
|
{"start_time": start_time},
|
|
98
98
|
)
|
|
99
|
-
return result.scalars().all()
|
|
99
|
+
return list(result.scalars().all())
|
|
100
100
|
|
|
101
101
|
# ====
|
|
102
102
|
# Conversation Management
|
|
@@ -112,7 +112,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
112
112
|
if platform_id:
|
|
113
113
|
query = query.where(ConversationV2.platform_id == platform_id)
|
|
114
114
|
# order by
|
|
115
|
-
query = query.order_by(ConversationV2.created_at
|
|
115
|
+
query = query.order_by(desc(ConversationV2.created_at))
|
|
116
116
|
result = await session.execute(query)
|
|
117
117
|
|
|
118
118
|
return result.scalars().all()
|
|
@@ -130,7 +130,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
130
130
|
offset = (page - 1) * page_size
|
|
131
131
|
result = await session.execute(
|
|
132
132
|
select(ConversationV2)
|
|
133
|
-
.order_by(ConversationV2.created_at
|
|
133
|
+
.order_by(desc(ConversationV2.created_at))
|
|
134
134
|
.offset(offset)
|
|
135
135
|
.limit(page_size)
|
|
136
136
|
)
|
|
@@ -151,25 +151,25 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
151
151
|
|
|
152
152
|
if platform_ids:
|
|
153
153
|
base_query = base_query.where(
|
|
154
|
-
ConversationV2.platform_id.in_(platform_ids)
|
|
154
|
+
col(ConversationV2.platform_id).in_(platform_ids)
|
|
155
155
|
)
|
|
156
156
|
if search_query:
|
|
157
157
|
search_query = search_query.encode("unicode_escape").decode("utf-8")
|
|
158
158
|
base_query = base_query.where(
|
|
159
159
|
or_(
|
|
160
|
-
ConversationV2.title.ilike(f"%{search_query}%"),
|
|
161
|
-
ConversationV2.content.ilike(f"%{search_query}%"),
|
|
162
|
-
ConversationV2.user_id.ilike(f"%{search_query}%"),
|
|
160
|
+
col(ConversationV2.title).ilike(f"%{search_query}%"),
|
|
161
|
+
col(ConversationV2.content).ilike(f"%{search_query}%"),
|
|
162
|
+
col(ConversationV2.user_id).ilike(f"%{search_query}%"),
|
|
163
163
|
)
|
|
164
164
|
)
|
|
165
165
|
if "message_types" in kwargs and len(kwargs["message_types"]) > 0:
|
|
166
166
|
for msg_type in kwargs["message_types"]:
|
|
167
167
|
base_query = base_query.where(
|
|
168
|
-
ConversationV2.user_id.ilike(f"%:{msg_type}:%")
|
|
168
|
+
col(ConversationV2.user_id).ilike(f"%:{msg_type}:%")
|
|
169
169
|
)
|
|
170
170
|
if "platforms" in kwargs and len(kwargs["platforms"]) > 0:
|
|
171
171
|
base_query = base_query.where(
|
|
172
|
-
ConversationV2.platform_id.in_(kwargs["platforms"])
|
|
172
|
+
col(ConversationV2.platform_id).in_(kwargs["platforms"])
|
|
173
173
|
)
|
|
174
174
|
|
|
175
175
|
# Get total count matching the filters
|
|
@@ -180,7 +180,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
180
180
|
# Get paginated results
|
|
181
181
|
offset = (page - 1) * page_size
|
|
182
182
|
result_query = (
|
|
183
|
-
base_query.order_by(ConversationV2.created_at
|
|
183
|
+
base_query.order_by(desc(ConversationV2.created_at))
|
|
184
184
|
.offset(offset)
|
|
185
185
|
.limit(page_size)
|
|
186
186
|
)
|
|
@@ -226,7 +226,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
226
226
|
session: AsyncSession
|
|
227
227
|
async with session.begin():
|
|
228
228
|
query = update(ConversationV2).where(
|
|
229
|
-
ConversationV2.conversation_id == cid
|
|
229
|
+
col(ConversationV2.conversation_id) == cid
|
|
230
230
|
)
|
|
231
231
|
values = {}
|
|
232
232
|
if title is not None:
|
|
@@ -246,7 +246,9 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
246
246
|
session: AsyncSession
|
|
247
247
|
async with session.begin():
|
|
248
248
|
await session.execute(
|
|
249
|
-
delete(ConversationV2).where(
|
|
249
|
+
delete(ConversationV2).where(
|
|
250
|
+
col(ConversationV2.conversation_id) == cid
|
|
251
|
+
)
|
|
250
252
|
)
|
|
251
253
|
|
|
252
254
|
async def delete_conversations_by_user_id(self, user_id: str) -> None:
|
|
@@ -254,8 +256,115 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
254
256
|
session: AsyncSession
|
|
255
257
|
async with session.begin():
|
|
256
258
|
await session.execute(
|
|
257
|
-
delete(ConversationV2).where(ConversationV2.user_id == user_id)
|
|
259
|
+
delete(ConversationV2).where(col(ConversationV2.user_id) == user_id)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
async def get_session_conversations(
|
|
263
|
+
self,
|
|
264
|
+
page=1,
|
|
265
|
+
page_size=20,
|
|
266
|
+
search_query=None,
|
|
267
|
+
platform=None,
|
|
268
|
+
) -> tuple[list[dict], int]:
|
|
269
|
+
"""Get paginated session conversations with joined conversation and persona details."""
|
|
270
|
+
async with self.get_db() as session:
|
|
271
|
+
session: AsyncSession
|
|
272
|
+
offset = (page - 1) * page_size
|
|
273
|
+
|
|
274
|
+
base_query = (
|
|
275
|
+
select(
|
|
276
|
+
col(Preference.scope_id).label("session_id"),
|
|
277
|
+
func.json_extract(Preference.value, "$.val").label(
|
|
278
|
+
"conversation_id"
|
|
279
|
+
), # type: ignore
|
|
280
|
+
col(ConversationV2.persona_id).label("persona_id"),
|
|
281
|
+
col(ConversationV2.title).label("title"),
|
|
282
|
+
col(Persona.persona_id).label("persona_name"),
|
|
283
|
+
)
|
|
284
|
+
.select_from(Preference)
|
|
285
|
+
.outerjoin(
|
|
286
|
+
ConversationV2,
|
|
287
|
+
func.json_extract(Preference.value, "$.val")
|
|
288
|
+
== ConversationV2.conversation_id,
|
|
289
|
+
)
|
|
290
|
+
.outerjoin(
|
|
291
|
+
Persona, col(ConversationV2.persona_id) == Persona.persona_id
|
|
258
292
|
)
|
|
293
|
+
.where(Preference.scope == "umo", Preference.key == "sel_conv_id")
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# 搜索筛选
|
|
297
|
+
if search_query:
|
|
298
|
+
search_pattern = f"%{search_query}%"
|
|
299
|
+
base_query = base_query.where(
|
|
300
|
+
or_(
|
|
301
|
+
col(Preference.scope_id).ilike(search_pattern),
|
|
302
|
+
col(ConversationV2.title).ilike(search_pattern),
|
|
303
|
+
col(Persona.persona_id).ilike(search_pattern),
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# 平台筛选
|
|
308
|
+
if platform:
|
|
309
|
+
platform_pattern = f"{platform}:%"
|
|
310
|
+
base_query = base_query.where(
|
|
311
|
+
col(Preference.scope_id).like(platform_pattern)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# 排序
|
|
315
|
+
base_query = base_query.order_by(Preference.scope_id)
|
|
316
|
+
|
|
317
|
+
# 分页结果
|
|
318
|
+
result_query = base_query.offset(offset).limit(page_size)
|
|
319
|
+
result = await session.execute(result_query)
|
|
320
|
+
rows = result.fetchall()
|
|
321
|
+
|
|
322
|
+
# 查询总数(应用相同的筛选条件)
|
|
323
|
+
count_base_query = (
|
|
324
|
+
select(func.count(col(Preference.scope_id)))
|
|
325
|
+
.select_from(Preference)
|
|
326
|
+
.outerjoin(
|
|
327
|
+
ConversationV2,
|
|
328
|
+
func.json_extract(Preference.value, "$.val")
|
|
329
|
+
== ConversationV2.conversation_id,
|
|
330
|
+
)
|
|
331
|
+
.outerjoin(
|
|
332
|
+
Persona, col(ConversationV2.persona_id) == Persona.persona_id
|
|
333
|
+
)
|
|
334
|
+
.where(Preference.scope == "umo", Preference.key == "sel_conv_id")
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# 应用相同的搜索和平台筛选条件到计数查询
|
|
338
|
+
if search_query:
|
|
339
|
+
search_pattern = f"%{search_query}%"
|
|
340
|
+
count_base_query = count_base_query.where(
|
|
341
|
+
or_(
|
|
342
|
+
col(Preference.scope_id).ilike(search_pattern),
|
|
343
|
+
col(ConversationV2.title).ilike(search_pattern),
|
|
344
|
+
col(Persona.persona_id).ilike(search_pattern),
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if platform:
|
|
349
|
+
platform_pattern = f"{platform}:%"
|
|
350
|
+
count_base_query = count_base_query.where(
|
|
351
|
+
col(Preference.scope_id).like(platform_pattern)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
total_result = await session.execute(count_base_query)
|
|
355
|
+
total = total_result.scalar() or 0
|
|
356
|
+
|
|
357
|
+
sessions_data = [
|
|
358
|
+
{
|
|
359
|
+
"session_id": row.session_id,
|
|
360
|
+
"conversation_id": row.conversation_id,
|
|
361
|
+
"persona_id": row.persona_id,
|
|
362
|
+
"title": row.title,
|
|
363
|
+
"persona_name": row.persona_name,
|
|
364
|
+
}
|
|
365
|
+
for row in rows
|
|
366
|
+
]
|
|
367
|
+
return sessions_data, total
|
|
259
368
|
|
|
260
369
|
async def insert_platform_message_history(
|
|
261
370
|
self,
|
|
@@ -290,9 +399,9 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
290
399
|
cutoff_time = now - timedelta(seconds=offset_sec)
|
|
291
400
|
await session.execute(
|
|
292
401
|
delete(PlatformMessageHistory).where(
|
|
293
|
-
PlatformMessageHistory.platform_id == platform_id,
|
|
294
|
-
PlatformMessageHistory.user_id == user_id,
|
|
295
|
-
PlatformMessageHistory.created_at < cutoff_time,
|
|
402
|
+
col(PlatformMessageHistory.platform_id) == platform_id,
|
|
403
|
+
col(PlatformMessageHistory.user_id) == user_id,
|
|
404
|
+
col(PlatformMessageHistory.created_at) < cutoff_time,
|
|
296
405
|
)
|
|
297
406
|
)
|
|
298
407
|
|
|
@@ -309,7 +418,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
309
418
|
PlatformMessageHistory.platform_id == platform_id,
|
|
310
419
|
PlatformMessageHistory.user_id == user_id,
|
|
311
420
|
)
|
|
312
|
-
.order_by(PlatformMessageHistory.created_at
|
|
421
|
+
.order_by(desc(PlatformMessageHistory.created_at))
|
|
313
422
|
)
|
|
314
423
|
result = await session.execute(query.offset(offset).limit(page_size))
|
|
315
424
|
return result.scalars().all()
|
|
@@ -331,7 +440,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
331
440
|
"""Get an attachment by its ID."""
|
|
332
441
|
async with self.get_db() as session:
|
|
333
442
|
session: AsyncSession
|
|
334
|
-
query = select(Attachment).where(Attachment.
|
|
443
|
+
query = select(Attachment).where(Attachment.attachment_id == attachment_id)
|
|
335
444
|
result = await session.execute(query)
|
|
336
445
|
return result.scalar_one_or_none()
|
|
337
446
|
|
|
@@ -374,7 +483,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
374
483
|
async with self.get_db() as session:
|
|
375
484
|
session: AsyncSession
|
|
376
485
|
async with session.begin():
|
|
377
|
-
query = update(Persona).where(Persona.persona_id == persona_id)
|
|
486
|
+
query = update(Persona).where(col(Persona.persona_id) == persona_id)
|
|
378
487
|
values = {}
|
|
379
488
|
if system_prompt is not None:
|
|
380
489
|
values["system_prompt"] = system_prompt
|
|
@@ -394,7 +503,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
394
503
|
session: AsyncSession
|
|
395
504
|
async with session.begin():
|
|
396
505
|
await session.execute(
|
|
397
|
-
delete(Persona).where(Persona.persona_id == persona_id)
|
|
506
|
+
delete(Persona).where(col(Persona.persona_id) == persona_id)
|
|
398
507
|
)
|
|
399
508
|
|
|
400
509
|
async def insert_preference_or_update(self, scope, scope_id, key, value):
|
|
@@ -449,9 +558,9 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
449
558
|
async with session.begin():
|
|
450
559
|
await session.execute(
|
|
451
560
|
delete(Preference).where(
|
|
452
|
-
Preference.scope == scope,
|
|
453
|
-
Preference.scope_id == scope_id,
|
|
454
|
-
Preference.key == key,
|
|
561
|
+
col(Preference.scope) == scope,
|
|
562
|
+
col(Preference.scope_id) == scope_id,
|
|
563
|
+
col(Preference.key) == key,
|
|
455
564
|
)
|
|
456
565
|
)
|
|
457
566
|
await session.commit()
|
|
@@ -463,7 +572,8 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
463
572
|
async with session.begin():
|
|
464
573
|
await session.execute(
|
|
465
574
|
delete(Preference).where(
|
|
466
|
-
Preference.scope == scope,
|
|
575
|
+
col(Preference.scope) == scope,
|
|
576
|
+
col(Preference.scope_id) == scope_id,
|
|
467
577
|
)
|
|
468
578
|
)
|
|
469
579
|
await session.commit()
|
|
@@ -490,7 +600,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
490
600
|
DeprecatedPlatformStat(
|
|
491
601
|
name=data.platform_id,
|
|
492
602
|
count=data.count,
|
|
493
|
-
timestamp=data.timestamp.timestamp(),
|
|
603
|
+
timestamp=int(data.timestamp.timestamp()),
|
|
494
604
|
)
|
|
495
605
|
)
|
|
496
606
|
return deprecated_stats
|
|
@@ -548,7 +658,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
548
658
|
DeprecatedPlatformStat(
|
|
549
659
|
name=platform_id,
|
|
550
660
|
count=count,
|
|
551
|
-
timestamp=start_time.timestamp(),
|
|
661
|
+
timestamp=int(start_time.timestamp()),
|
|
552
662
|
)
|
|
553
663
|
)
|
|
554
664
|
return deprecated_stats
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import traceback
|
|
2
2
|
import asyncio
|
|
3
|
+
import random
|
|
3
4
|
from typing import Union, AsyncGenerator
|
|
4
5
|
from ..stage import Stage, register_stage
|
|
5
6
|
from ..context import PipelineContext
|
|
@@ -22,6 +23,26 @@ class PreProcessStage(Stage):
|
|
|
22
23
|
self, event: AstrMessageEvent
|
|
23
24
|
) -> Union[None, AsyncGenerator[None, None]]:
|
|
24
25
|
"""在处理事件之前的预处理"""
|
|
26
|
+
# 平台特异配置:platform_specific.<platform>.pre_ack_emoji
|
|
27
|
+
supported = {"telegram", "lark"}
|
|
28
|
+
platform = event.get_platform_name()
|
|
29
|
+
cfg = (
|
|
30
|
+
self.config.get("platform_specific", {})
|
|
31
|
+
.get(platform, {})
|
|
32
|
+
.get("pre_ack_emoji", {})
|
|
33
|
+
) or {}
|
|
34
|
+
emojis = cfg.get("emojis") or []
|
|
35
|
+
if (
|
|
36
|
+
cfg.get("enable", False)
|
|
37
|
+
and platform in supported
|
|
38
|
+
and emojis
|
|
39
|
+
and event.is_at_or_wake_command
|
|
40
|
+
):
|
|
41
|
+
try:
|
|
42
|
+
await event.react(random.choice(emojis))
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.warning(f"{platform} 预回应表情发送失败: {e}")
|
|
45
|
+
|
|
25
46
|
# 路径映射
|
|
26
47
|
if mappings := self.platform_settings.get("path_mapping", []):
|
|
27
48
|
# 支持 Record,Image 消息段的路径映射。
|
|
@@ -46,6 +67,9 @@ class PreProcessStage(Stage):
|
|
|
46
67
|
ctx = self.plugin_manager.context
|
|
47
68
|
stt_provider = ctx.get_using_stt_provider(event.unified_msg_origin)
|
|
48
69
|
if not stt_provider:
|
|
70
|
+
logger.warning(
|
|
71
|
+
f"会话 {event.unified_msg_origin} 未配置语音转文本模型。"
|
|
72
|
+
)
|
|
49
73
|
return
|
|
50
74
|
message_chain = event.get_messages()
|
|
51
75
|
for idx, component in enumerate(message_chain):
|
|
@@ -183,9 +183,13 @@ class ResultDecorateStage(Stage):
|
|
|
183
183
|
if (
|
|
184
184
|
self.ctx.astrbot_config["provider_tts_settings"]["enable"]
|
|
185
185
|
and result.is_llm_result()
|
|
186
|
-
and tts_provider
|
|
187
186
|
and SessionServiceManager.should_process_tts_request(event)
|
|
188
187
|
):
|
|
188
|
+
if not tts_provider:
|
|
189
|
+
logger.warning(
|
|
190
|
+
f"会话 {event.unified_msg_origin} 未配置文本转语音模型。"
|
|
191
|
+
)
|
|
192
|
+
return
|
|
189
193
|
new_chain = []
|
|
190
194
|
for comp in result.chain:
|
|
191
195
|
if isinstance(comp, Plain) and len(comp.text) > 1:
|
|
@@ -416,6 +416,16 @@ class AstrMessageEvent(abc.ABC):
|
|
|
416
416
|
)
|
|
417
417
|
self._has_send_oper = True
|
|
418
418
|
|
|
419
|
+
async def react(self, emoji: str):
|
|
420
|
+
"""
|
|
421
|
+
对消息添加表情回应。
|
|
422
|
+
|
|
423
|
+
默认实现为发送一条包含该表情的消息。
|
|
424
|
+
注意:此实现并不一定符合所有平台的原生“表情回应”行为。
|
|
425
|
+
如需支持平台原生的消息反应功能,请在对应平台的子类中重写本方法。
|
|
426
|
+
"""
|
|
427
|
+
await self.send(MessageChain([Plain(emoji)]))
|
|
428
|
+
|
|
419
429
|
async def get_group(self, group_id: str = None, **kwargs) -> Optional[Group]:
|
|
420
430
|
"""获取一个群聊的数据, 如果不填写 group_id: 如果是私聊消息,返回 None。如果是群聊消息,返回当前群聊的数据。
|
|
421
431
|
|
|
@@ -13,10 +13,12 @@ def register_platform_adapter(
|
|
|
13
13
|
desc: str,
|
|
14
14
|
default_config_tmpl: dict = None,
|
|
15
15
|
adapter_display_name: str = None,
|
|
16
|
+
logo_path: str = None,
|
|
16
17
|
):
|
|
17
18
|
"""用于注册平台适配器的带参装饰器。
|
|
18
19
|
|
|
19
20
|
default_config_tmpl 指定了平台适配器的默认配置模板。用户填写好后将会作为 platform_config 传入你的 Platform 类的实现类。
|
|
21
|
+
logo_path 指定了平台适配器的 logo 文件路径,是相对于插件目录的路径。
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
24
|
def decorator(cls):
|
|
@@ -39,6 +41,7 @@ def register_platform_adapter(
|
|
|
39
41
|
description=desc,
|
|
40
42
|
default_config_tmpl=default_config_tmpl,
|
|
41
43
|
adapter_display_name=adapter_display_name,
|
|
44
|
+
logo_path=logo_path,
|
|
42
45
|
)
|
|
43
46
|
platform_registry.append(pm)
|
|
44
47
|
platform_cls_map[adapter_name] = cls
|
|
@@ -107,6 +107,22 @@ class LarkMessageEvent(AstrMessageEvent):
|
|
|
107
107
|
|
|
108
108
|
await super().send(message)
|
|
109
109
|
|
|
110
|
+
async def react(self, emoji: str):
|
|
111
|
+
request = (
|
|
112
|
+
CreateMessageReactionRequest.builder()
|
|
113
|
+
.message_id(self.message_obj.message_id)
|
|
114
|
+
.request_body(
|
|
115
|
+
CreateMessageReactionRequestBody.builder()
|
|
116
|
+
.reaction_type(Emoji.builder().emoji_type(emoji).build())
|
|
117
|
+
.build()
|
|
118
|
+
)
|
|
119
|
+
.build()
|
|
120
|
+
)
|
|
121
|
+
response = await self.bot.im.v1.message_reaction.acreate(request)
|
|
122
|
+
if not response.success():
|
|
123
|
+
logger.error(f"发送飞书表情回应失败({response.code}): {response.msg}")
|
|
124
|
+
return None
|
|
125
|
+
|
|
110
126
|
async def send_streaming(self, generator, use_fallback: bool = False):
|
|
111
127
|
buffer = None
|
|
112
128
|
async for chain in generator:
|