Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54340cca18 | |||
| 7191d28ada | |||
| e6b5e3d282 | |||
| 1413d6b5fe | |||
| dcd8a1094c | |||
| e64b31b9ba | |||
| 080f347511 | |||
| eaaff4298d | |||
| dd5a02e8ef | |||
| 3211ec57ee | |||
| 6796afdaee | |||
| cc6fe57773 | |||
| 1dfc831938 | |||
| cafeda4abf | |||
| d951b99718 |
@@ -198,6 +198,17 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|||||||
func_tool = req.func_tool.get_func(func_tool_name)
|
func_tool = req.func_tool.get_func(func_tool_name)
|
||||||
logger.info(f"使用工具:{func_tool_name},参数:{func_tool_args}")
|
logger.info(f"使用工具:{func_tool_name},参数:{func_tool_args}")
|
||||||
|
|
||||||
|
if not func_tool:
|
||||||
|
logger.warning(f"未找到指定的工具: {func_tool_name},将跳过。")
|
||||||
|
tool_call_result_blocks.append(
|
||||||
|
ToolCallMessageSegment(
|
||||||
|
role="tool",
|
||||||
|
tool_call_id=func_tool_id,
|
||||||
|
content=f"error: 未找到工具 {func_tool_name}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.agent_hooks.on_tool_start(
|
await self.agent_hooks.on_tool_start(
|
||||||
self.run_context, func_tool, func_tool_args
|
self.run_context, func_tool, func_tool_args
|
||||||
@@ -210,9 +221,12 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|||||||
run_context=self.run_context,
|
run_context=self.run_context,
|
||||||
**func_tool_args,
|
**func_tool_args,
|
||||||
)
|
)
|
||||||
async for resp in executor:
|
|
||||||
|
_final_resp: CallToolResult | None = None
|
||||||
|
async for resp in executor: # type: ignore
|
||||||
if isinstance(resp, CallToolResult):
|
if isinstance(resp, CallToolResult):
|
||||||
res = resp
|
res = resp
|
||||||
|
_final_resp = resp
|
||||||
if isinstance(res.content[0], TextContent):
|
if isinstance(res.content[0], TextContent):
|
||||||
tool_call_result_blocks.append(
|
tool_call_result_blocks.append(
|
||||||
ToolCallMessageSegment(
|
ToolCallMessageSegment(
|
||||||
@@ -279,13 +293,14 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|||||||
chain=res.chain, type="tool_direct_result"
|
chain=res.chain, type="tool_direct_result"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# 不应该出现其他类型
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Tool 返回了不支持的类型: {type(resp)},将忽略。"
|
f"Tool 返回了不支持的类型: {type(resp)},将忽略。"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.agent_hooks.on_tool_end(
|
await self.agent_hooks.on_tool_end(
|
||||||
self.run_context, func_tool, func_tool_args, None
|
self.run_context, func_tool, func_tool_args, _final_resp
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in on_tool_end hook: {e}", exc_info=True)
|
logger.error(f"Error in on_tool_end hook: {e}", exc_info=True)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import os
|
|||||||
|
|
||||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
||||||
|
|
||||||
VERSION = "4.3.1"
|
VERSION = "4.3.2"
|
||||||
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
||||||
|
|
||||||
# 默认配置
|
# 默认配置
|
||||||
@@ -775,7 +775,7 @@ CONFIG_METADATA_2 = {
|
|||||||
"timeout": 120,
|
"timeout": 120,
|
||||||
"model_config": {"model": "deepseek-chat", "temperature": 0.4},
|
"model_config": {"model": "deepseek-chat", "temperature": 0.4},
|
||||||
"custom_extra_body": {},
|
"custom_extra_body": {},
|
||||||
"modalities": ["text", "image", "tool_use"],
|
"modalities": ["text", "tool_use"],
|
||||||
},
|
},
|
||||||
"302.AI": {
|
"302.AI": {
|
||||||
"id": "302ai",
|
"id": "302ai",
|
||||||
@@ -821,6 +821,21 @@ CONFIG_METADATA_2 = {
|
|||||||
},
|
},
|
||||||
"custom_extra_body": {},
|
"custom_extra_body": {},
|
||||||
},
|
},
|
||||||
|
"小马算力": {
|
||||||
|
"id": "tokenpony",
|
||||||
|
"provider": "tokenpony",
|
||||||
|
"type": "openai_chat_completion",
|
||||||
|
"provider_type": "chat_completion",
|
||||||
|
"enable": True,
|
||||||
|
"key": [],
|
||||||
|
"api_base": "https://api.tokenpony.cn/v1",
|
||||||
|
"timeout": 120,
|
||||||
|
"model_config": {
|
||||||
|
"model": "kimi-k2-instruct-0905",
|
||||||
|
"temperature": 0.7,
|
||||||
|
},
|
||||||
|
"custom_extra_body": {},
|
||||||
|
},
|
||||||
"优云智算": {
|
"优云智算": {
|
||||||
"id": "compshare",
|
"id": "compshare",
|
||||||
"provider": "compshare",
|
"provider": "compshare",
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ class SQLiteDatabase(BaseDatabase):
|
|||||||
"""Initialize the database by creating tables if they do not exist."""
|
"""Initialize the database by creating tables if they do not exist."""
|
||||||
async with self.engine.begin() as conn:
|
async with self.engine.begin() as conn:
|
||||||
await conn.run_sync(SQLModel.metadata.create_all)
|
await conn.run_sync(SQLModel.metadata.create_all)
|
||||||
|
await conn.execute(text("PRAGMA journal_mode=WAL"))
|
||||||
|
await conn.execute(text("PRAGMA synchronous=NORMAL"))
|
||||||
|
await conn.execute(text("PRAGMA cache_size=20000"))
|
||||||
|
await conn.execute(text("PRAGMA temp_store=MEMORY"))
|
||||||
|
await conn.execute(text("PRAGMA mmap_size=134217728"))
|
||||||
|
await conn.execute(text("PRAGMA optimize"))
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
|
|
||||||
# ====
|
# ====
|
||||||
@@ -160,6 +166,7 @@ class SQLiteDatabase(BaseDatabase):
|
|||||||
col(ConversationV2.title).ilike(f"%{search_query}%"),
|
col(ConversationV2.title).ilike(f"%{search_query}%"),
|
||||||
col(ConversationV2.content).ilike(f"%{search_query}%"),
|
col(ConversationV2.content).ilike(f"%{search_query}%"),
|
||||||
col(ConversationV2.user_id).ilike(f"%{search_query}%"),
|
col(ConversationV2.user_id).ilike(f"%{search_query}%"),
|
||||||
|
col(ConversationV2.conversation_id).ilike(f"%{search_query}%"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if "message_types" in kwargs and len(kwargs["message_types"]) > 0:
|
if "message_types" in kwargs and len(kwargs["message_types"]) > 0:
|
||||||
|
|||||||
@@ -97,5 +97,6 @@ async def call_event_hook(
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。"
|
f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。"
|
||||||
)
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
return event.is_stopped()
|
return event.is_stopped()
|
||||||
|
|||||||
@@ -190,6 +190,16 @@ class RespondStage(Stage):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"空内容检查异常: {e}")
|
logger.warning(f"空内容检查异常: {e}")
|
||||||
|
|
||||||
|
# 将 Plain 为空的消息段移除
|
||||||
|
result.chain = [
|
||||||
|
comp
|
||||||
|
for comp in result.chain
|
||||||
|
if not (
|
||||||
|
isinstance(comp, Comp.Plain)
|
||||||
|
and (not comp.text or not comp.text.strip())
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# 发送消息链
|
# 发送消息链
|
||||||
# Record 需要强制单独发送
|
# Record 需要强制单独发送
|
||||||
need_separately = {ComponentType.Record}
|
need_separately = {ComponentType.Record}
|
||||||
|
|||||||
@@ -189,54 +189,54 @@ class ResultDecorateStage(Stage):
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
f"会话 {event.unified_msg_origin} 未配置文本转语音模型。"
|
f"会话 {event.unified_msg_origin} 未配置文本转语音模型。"
|
||||||
)
|
)
|
||||||
return
|
else:
|
||||||
new_chain = []
|
new_chain = []
|
||||||
for comp in result.chain:
|
for comp in result.chain:
|
||||||
if isinstance(comp, Plain) and len(comp.text) > 1:
|
if isinstance(comp, Plain) and len(comp.text) > 1:
|
||||||
try:
|
try:
|
||||||
logger.info(f"TTS 请求: {comp.text}")
|
logger.info(f"TTS 请求: {comp.text}")
|
||||||
audio_path = await tts_provider.get_audio(comp.text)
|
audio_path = await tts_provider.get_audio(comp.text)
|
||||||
logger.info(f"TTS 结果: {audio_path}")
|
logger.info(f"TTS 结果: {audio_path}")
|
||||||
if not audio_path:
|
if not audio_path:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"由于 TTS 音频文件未找到,消息段转语音失败: {comp.text}"
|
f"由于 TTS 音频文件未找到,消息段转语音失败: {comp.text}"
|
||||||
|
)
|
||||||
|
new_chain.append(comp)
|
||||||
|
continue
|
||||||
|
|
||||||
|
use_file_service = self.ctx.astrbot_config[
|
||||||
|
"provider_tts_settings"
|
||||||
|
]["use_file_service"]
|
||||||
|
callback_api_base = self.ctx.astrbot_config[
|
||||||
|
"callback_api_base"
|
||||||
|
]
|
||||||
|
dual_output = self.ctx.astrbot_config[
|
||||||
|
"provider_tts_settings"
|
||||||
|
]["dual_output"]
|
||||||
|
|
||||||
|
url = None
|
||||||
|
if use_file_service and callback_api_base:
|
||||||
|
token = await file_token_service.register_file(
|
||||||
|
audio_path
|
||||||
|
)
|
||||||
|
url = f"{callback_api_base}/api/file/{token}"
|
||||||
|
logger.debug(f"已注册:{url}")
|
||||||
|
|
||||||
|
new_chain.append(
|
||||||
|
Record(
|
||||||
|
file=url or audio_path,
|
||||||
|
url=url or audio_path,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
if dual_output:
|
||||||
|
new_chain.append(comp)
|
||||||
|
except Exception:
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
logger.error("TTS 失败,使用文本发送。")
|
||||||
new_chain.append(comp)
|
new_chain.append(comp)
|
||||||
continue
|
else:
|
||||||
|
|
||||||
use_file_service = self.ctx.astrbot_config[
|
|
||||||
"provider_tts_settings"
|
|
||||||
]["use_file_service"]
|
|
||||||
callback_api_base = self.ctx.astrbot_config[
|
|
||||||
"callback_api_base"
|
|
||||||
]
|
|
||||||
dual_output = self.ctx.astrbot_config[
|
|
||||||
"provider_tts_settings"
|
|
||||||
]["dual_output"]
|
|
||||||
|
|
||||||
url = None
|
|
||||||
if use_file_service and callback_api_base:
|
|
||||||
token = await file_token_service.register_file(
|
|
||||||
audio_path
|
|
||||||
)
|
|
||||||
url = f"{callback_api_base}/api/file/{token}"
|
|
||||||
logger.debug(f"已注册:{url}")
|
|
||||||
|
|
||||||
new_chain.append(
|
|
||||||
Record(
|
|
||||||
file=url or audio_path,
|
|
||||||
url=url or audio_path,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if dual_output:
|
|
||||||
new_chain.append(comp)
|
|
||||||
except Exception:
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
logger.error("TTS 失败,使用文本发送。")
|
|
||||||
new_chain.append(comp)
|
new_chain.append(comp)
|
||||||
else:
|
result.chain = new_chain
|
||||||
new_chain.append(comp)
|
|
||||||
result.chain = new_chain
|
|
||||||
|
|
||||||
# 文本转图片
|
# 文本转图片
|
||||||
elif (
|
elif (
|
||||||
@@ -279,7 +279,6 @@ class ResultDecorateStage(Stage):
|
|||||||
result.chain = [Image.fromFileSystem(url)]
|
result.chain = [Image.fromFileSystem(url)]
|
||||||
|
|
||||||
# 触发转发消息
|
# 触发转发消息
|
||||||
has_forwarded = False
|
|
||||||
if event.get_platform_name() == "aiocqhttp":
|
if event.get_platform_name() == "aiocqhttp":
|
||||||
word_cnt = 0
|
word_cnt = 0
|
||||||
for comp in result.chain:
|
for comp in result.chain:
|
||||||
@@ -290,9 +289,9 @@ class ResultDecorateStage(Stage):
|
|||||||
uin=event.get_self_id(), name="AstrBot", content=[*result.chain]
|
uin=event.get_self_id(), name="AstrBot", content=[*result.chain]
|
||||||
)
|
)
|
||||||
result.chain = [node]
|
result.chain = [node]
|
||||||
has_forwarded = True
|
|
||||||
|
|
||||||
if not has_forwarded:
|
has_plain = any(isinstance(item, Plain) for item in result.chain)
|
||||||
|
if has_plain:
|
||||||
# at 回复
|
# at 回复
|
||||||
if (
|
if (
|
||||||
self.reply_with_mention
|
self.reply_with_mention
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from astrbot.core.config.default import VERSION
|
|||||||
from astrbot.core import DEMO_MODE
|
from astrbot.core import DEMO_MODE
|
||||||
from astrbot.core.db.migration.helper import do_migration_v4, check_migration_needed_v4
|
from astrbot.core.db.migration.helper import do_migration_v4, check_migration_needed_v4
|
||||||
|
|
||||||
|
CLEAR_SITE_DATA_HEADERS = {"Clear-Site-Data": '"cache"'}
|
||||||
|
|
||||||
|
|
||||||
class UpdateRoute(Route):
|
class UpdateRoute(Route):
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -113,17 +115,19 @@ class UpdateRoute(Route):
|
|||||||
|
|
||||||
if reboot:
|
if reboot:
|
||||||
await self.core_lifecycle.restart()
|
await self.core_lifecycle.restart()
|
||||||
return (
|
ret = (
|
||||||
Response()
|
Response()
|
||||||
.ok(None, "更新成功,AstrBot 将在 2 秒内全量重启以应用新的代码。")
|
.ok(None, "更新成功,AstrBot 将在 2 秒内全量重启以应用新的代码。")
|
||||||
.__dict__
|
.__dict__
|
||||||
)
|
)
|
||||||
|
return ret, 200, CLEAR_SITE_DATA_HEADERS
|
||||||
else:
|
else:
|
||||||
return (
|
ret = (
|
||||||
Response()
|
Response()
|
||||||
.ok(None, "更新成功,AstrBot 将在下次启动时应用新的代码。")
|
.ok(None, "更新成功,AstrBot 将在下次启动时应用新的代码。")
|
||||||
.__dict__
|
.__dict__
|
||||||
)
|
)
|
||||||
|
return ret, 200, CLEAR_SITE_DATA_HEADERS
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"/api/update_project: {traceback.format_exc()}")
|
logger.error(f"/api/update_project: {traceback.format_exc()}")
|
||||||
return Response().error(e.__str__()).__dict__
|
return Response().error(e.__str__()).__dict__
|
||||||
@@ -135,9 +139,8 @@ class UpdateRoute(Route):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"下载管理面板文件失败: {e}。")
|
logger.error(f"下载管理面板文件失败: {e}。")
|
||||||
return Response().error(f"下载管理面板文件失败: {e}").__dict__
|
return Response().error(f"下载管理面板文件失败: {e}").__dict__
|
||||||
return (
|
ret = Response().ok(None, "更新成功。刷新页面即可应用新版本面板。").__dict__
|
||||||
Response().ok(None, "更新成功。刷新页面即可应用新版本面板。").__dict__
|
return ret, 200, CLEAR_SITE_DATA_HEADERS
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"/api/update_dashboard: {traceback.format_exc()}")
|
logger.error(f"/api/update_dashboard: {traceback.format_exc()}")
|
||||||
return Response().error(e.__str__()).__dict__
|
return Response().error(e.__str__()).__dict__
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# What's Changed
|
||||||
|
|
||||||
|
1. fix: 修复 /reset 指令没有清除群聊上下文感知数据的问题 ([#2954](https://github.com/AstrBotDevs/AstrBot/issues/2954))
|
||||||
|
2. fix: 修复自带的 WebSearch 插件可能在部分场景下无法使用的问题
|
||||||
|
3. fix: 发送阶段强行将 Plain 为空的消息段移除
|
||||||
|
4. fix: on_tool_end无法获得工具返回的结果 ([#2956](https://github.com/AstrBotDevs/AstrBot/issues/2956))
|
||||||
|
5. feat: 为插件市场的搜索增加拼音与首字母搜索功能 ([#2936](https://github.com/AstrBotDevs/AstrBot/issues/2936))
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"marked": "^15.0.7",
|
"marked": "^15.0.7",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
|
"pinyin-pro": "^3.26.0",
|
||||||
"pinia": "2.1.6",
|
"pinia": "2.1.6",
|
||||||
"remixicon": "3.5.0",
|
"remixicon": "3.5.0",
|
||||||
"vee-validate": "4.11.3",
|
"vee-validate": "4.11.3",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-combobox v-model="platformFilter" :label="tm('filters.platform')"
|
<v-combobox v-model="platformFilter" :label="tm('filters.platform')"
|
||||||
:items="availablePlatforms" chips multiple clearable variant="solo-filled" flat
|
:items="availablePlatforms" chips multiple clearable variant="solo-filled" flat
|
||||||
density="compact" hide-details :disabled="loading">
|
density="compact" hide-details>
|
||||||
<template v-slot:selection="{ item }">
|
<template v-slot:selection="{ item }">
|
||||||
<v-chip size="small" label>
|
<v-chip size="small" label>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
@@ -21,8 +21,7 @@
|
|||||||
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select v-model="messageTypeFilter" :label="tm('filters.type')" :items="messageTypeItems"
|
<v-select v-model="messageTypeFilter" :label="tm('filters.type')" :items="messageTypeItems"
|
||||||
chips multiple clearable variant="solo-filled" density="compact" hide-details flat
|
chips multiple clearable variant="solo-filled" density="compact" hide-details flat>
|
||||||
:disabled="loading">
|
|
||||||
<template v-slot:selection="{ item }">
|
<template v-slot:selection="{ item }">
|
||||||
<v-chip size="small" variant="solo-filled" label>
|
<v-chip size="small" variant="solo-filled" label>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
<v-col cols="12" sm="12" md="4">
|
<v-col cols="12" sm="12" md="4">
|
||||||
<v-text-field v-model="search" prepend-inner-icon="mdi-magnify"
|
<v-text-field v-model="search" prepend-inner-icon="mdi-magnify"
|
||||||
:label="tm('filters.search')" hide-details density="compact" variant="solo-filled" flat
|
:label="tm('filters.search')" hide-details density="compact" variant="solo-filled" flat
|
||||||
clearable :disabled="loading"></v-text-field>
|
clearable></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-btn color="primary" prepend-icon="mdi-refresh" variant="tonal" @click="fetchConversations"
|
<v-btn color="primary" prepend-icon="mdi-refresh" variant="tonal" @click="fetchConversations"
|
||||||
@@ -79,6 +78,10 @@
|
|||||||
</v-chip>
|
</v-chip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.cid="{ item }">
|
||||||
|
<span class="text-truncate">{{ item.cid || tm('status.unknown') }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-slot:item.sessionId="{ item }">
|
<template v-slot:item.sessionId="{ item }">
|
||||||
<span>{{ item.sessionInfo.sessionId || tm('status.unknown') }}</span>
|
<span>{{ item.sessionInfo.sessionId || tm('status.unknown') }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -313,6 +316,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
|
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
|
||||||
import MarkdownIt from 'markdown-it';
|
import MarkdownIt from 'markdown-it';
|
||||||
import { useCommonStore } from '@/stores/common';
|
import { useCommonStore } from '@/stores/common';
|
||||||
@@ -417,8 +421,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
// 创建一个防抖函数,避免频繁请求
|
this.debouncedApplyFilters = debounce(() => {
|
||||||
this.debouncedApplyFilters = this.debounce(() => {
|
|
||||||
// 重置到第一页
|
// 重置到第一页
|
||||||
this.pagination.page = 1;
|
this.pagination.page = 1;
|
||||||
this.fetchConversations();
|
this.fetchConversations();
|
||||||
@@ -430,13 +433,14 @@ export default {
|
|||||||
tableHeaders() {
|
tableHeaders() {
|
||||||
return [
|
return [
|
||||||
{ title: this.tm('table.headers.title'), key: 'title', sortable: true },
|
{ title: this.tm('table.headers.title'), key: 'title', sortable: true },
|
||||||
|
{ title: '会话 ID', key: 'cid', sortable: true, width: '100px' },
|
||||||
{
|
{
|
||||||
title: this.tm('table.headers.sessionId'),
|
title: this.tm('table.headers.sessionId'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
children: [
|
children: [
|
||||||
{ title: this.tm('table.headers.platform'), key: 'platform', sortable: true, width: '120px' },
|
{ title: this.tm('table.headers.platform'), key: 'platform', sortable: true, width: '120px' },
|
||||||
{ title: this.tm('table.headers.type'), key: 'messageType', sortable: true, width: '100px' },
|
{ title: this.tm('table.headers.type'), key: 'messageType', sortable: true, width: '100px' },
|
||||||
{ title: '会话 ID', key: 'sessionId', sortable: true, width: '100px' },
|
{ title: '用户 ID', key: 'sessionId', sortable: true, width: '100px' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ title: this.tm('table.headers.createdAt'), key: 'created_at', sortable: true, width: '180px' },
|
{ title: this.tm('table.headers.createdAt'), key: 'created_at', sortable: true, width: '180px' },
|
||||||
@@ -526,19 +530,6 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 添加防抖函数
|
|
||||||
debounce(func, wait) {
|
|
||||||
let timeout;
|
|
||||||
return function () {
|
|
||||||
const context = this;
|
|
||||||
const args = arguments;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
func.apply(context, args);
|
|
||||||
}, wait);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// 处理表格选项变更(页面大小等)
|
// 处理表格选项变更(页面大小等)
|
||||||
handleTableOptions(options) {
|
handleTableOptions(options) {
|
||||||
// 处理页面大小变更
|
// 处理页面大小变更
|
||||||
@@ -579,83 +570,93 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 获取对话列表
|
// 获取对话列表
|
||||||
async fetchConversations() {
|
fetchConversations: (() => {
|
||||||
this.loading = true;
|
let controller = new AbortController();
|
||||||
try {
|
|
||||||
// 准备请求参数,包含分页和筛选条件
|
|
||||||
const params = {
|
|
||||||
page: this.pagination.page,
|
|
||||||
page_size: this.pagination.page_size
|
|
||||||
};
|
|
||||||
|
|
||||||
// 添加筛选条件 - 处理combobox的混合数据格式
|
return async function () {
|
||||||
if (this.platformFilter.length > 0) {
|
// 新请求前停止之前的请求
|
||||||
const platforms = this.platformFilter.map(item =>
|
controller?.abort()
|
||||||
typeof item === 'object' ? item.value : item
|
controller = new AbortController();
|
||||||
);
|
|
||||||
params.platforms = platforms.join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.messageTypeFilter.length > 0) {
|
this.loading = true;
|
||||||
params.message_types = this.messageTypeFilter.join(',');
|
try {
|
||||||
}
|
// 准备请求参数,包含分页和筛选条件
|
||||||
|
const params = {
|
||||||
|
page: this.pagination.page,
|
||||||
|
page_size: this.pagination.page_size
|
||||||
|
};
|
||||||
|
|
||||||
if (this.search) {
|
// 添加筛选条件 - 处理combobox的混合数据格式
|
||||||
params.search = this.search.trim();
|
if (this.platformFilter.length > 0) {
|
||||||
}
|
const platforms = this.platformFilter.map(item =>
|
||||||
|
typeof item === 'object' ? item.value : item
|
||||||
// 添加排除条件
|
);
|
||||||
params.exclude_ids = 'astrbot';
|
params.platforms = platforms.join(',');
|
||||||
params.exclude_platforms = 'webchat';
|
|
||||||
|
|
||||||
const response = await axios.get('/api/conversation/list', { params });
|
|
||||||
|
|
||||||
this.lastAppliedFilters = { ...this.currentFilters }; // 记录已应用的筛选条件
|
|
||||||
|
|
||||||
if (response.data.status === "ok") {
|
|
||||||
const data = response.data.data;
|
|
||||||
|
|
||||||
if (!data || !data.conversations) {
|
|
||||||
console.error('API 返回数据格式不符合预期:', data);
|
|
||||||
this.showErrorMessage(this.tm('messages.fetchError'));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理会话数据,解析sessionId
|
if (this.messageTypeFilter.length > 0) {
|
||||||
this.conversations = (data.conversations || []).map(conv => {
|
params.message_types = this.messageTypeFilter.join(',');
|
||||||
// 为每个会话添加会话信息
|
}
|
||||||
conv.sessionInfo = this.parseSessionId(conv.user_id);
|
|
||||||
return conv;
|
if (this.search) {
|
||||||
|
params.search = this.search.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加排除条件
|
||||||
|
params.exclude_ids = 'astrbot';
|
||||||
|
params.exclude_platforms = 'webchat';
|
||||||
|
|
||||||
|
const response = await axios.get('/api/conversation/list', {
|
||||||
|
signal: controller.signal,
|
||||||
|
params
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新分页信息
|
this.lastAppliedFilters = { ...this.currentFilters }; // 记录已应用的筛选条件
|
||||||
if (data.pagination) {
|
|
||||||
this.pagination = {
|
if (response.data.status === "ok") {
|
||||||
page: data.pagination.page || 1,
|
const data = response.data.data;
|
||||||
page_size: data.pagination.page_size || 20,
|
|
||||||
total: data.pagination.total || 0,
|
if (!data || !data.conversations) {
|
||||||
total_pages: data.pagination.total_pages || 1
|
console.error('API 返回数据格式不符合预期:', data);
|
||||||
};
|
this.showErrorMessage(this.tm('messages.fetchError'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理会话数据,解析sessionId
|
||||||
|
this.conversations = (data.conversations || []).map(conv => {
|
||||||
|
// 为每个会话添加会话信息
|
||||||
|
conv.sessionInfo = this.parseSessionId(conv.user_id);
|
||||||
|
return conv;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新分页信息
|
||||||
|
if (data.pagination) {
|
||||||
|
this.pagination = {
|
||||||
|
page: data.pagination.page || 1,
|
||||||
|
page_size: data.pagination.page_size || 20,
|
||||||
|
total: data.pagination.total || 0,
|
||||||
|
total_pages: data.pagination.total_pages || 1
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.warn('API 响应中没有分页信息');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('API 响应中没有分页信息');
|
this.showErrorMessage(response.data.message || this.tm('messages.fetchError'));
|
||||||
}
|
}
|
||||||
} else {
|
} catch (error) {
|
||||||
this.showErrorMessage(response.data.message || this.tm('messages.fetchError'));
|
if (axios.isCancel(error)) return;
|
||||||
}
|
|
||||||
} catch (error) {
|
console.error('获取对话列表出错:', error);
|
||||||
console.error('获取对话列表出错:', error);
|
if (error.response) {
|
||||||
if (error.response) {
|
console.error('错误响应数据:', error.response.data);
|
||||||
console.error('错误响应数据:', error.response.data);
|
console.error('错误状态码:', error.response.status);
|
||||||
console.error('错误状态码:', error.response.status);
|
}
|
||||||
}
|
this.showErrorMessage(error.response?.data?.message || error.message || this.tm('messages.fetchError'));
|
||||||
this.showErrorMessage(error.response?.data?.message || error.message || this.tm('messages.fetchError'));
|
} finally {
|
||||||
} finally {
|
|
||||||
// this.loading = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}, 200);
|
}
|
||||||
}
|
}
|
||||||
},
|
})(),
|
||||||
|
|
||||||
// 查看对话详情
|
// 查看对话详情
|
||||||
async viewConversation(item) {
|
async viewConversation(item) {
|
||||||
@@ -993,6 +994,14 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-truncate {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
/* 动画 */
|
/* 动画 */
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue';
|
|||||||
import ReadmeDialog from '@/components/shared/ReadmeDialog.vue';
|
import ReadmeDialog from '@/components/shared/ReadmeDialog.vue';
|
||||||
import ProxySelector from '@/components/shared/ProxySelector.vue';
|
import ProxySelector from '@/components/shared/ProxySelector.vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { pinyin } from 'pinyin-pro';
|
||||||
import { useCommonStore } from '@/stores/common';
|
import { useCommonStore } from '@/stores/common';
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||||
|
|
||||||
@@ -65,6 +66,32 @@ const marketSearch = ref("");
|
|||||||
const filterKeys = ['name', 'desc', 'author'];
|
const filterKeys = ['name', 'desc', 'author'];
|
||||||
const refreshingMarket = ref(false);
|
const refreshingMarket = ref(false);
|
||||||
|
|
||||||
|
// 插件市场拼音搜索
|
||||||
|
const normalizeStr = (s) => (s ?? '').toString().toLowerCase().trim();
|
||||||
|
const toPinyinText = (s) => pinyin(s ?? '', { toneType: 'none' }).toLowerCase().replace(/\s+/g, '');
|
||||||
|
const toInitials = (s) => pinyin(s ?? '', { pattern: 'first', toneType: 'none' }).toLowerCase().replace(/\s+/g, '');
|
||||||
|
const marketCustomFilter = (value, query, item) => {
|
||||||
|
const q = normalizeStr(query);
|
||||||
|
if (!q) return true;
|
||||||
|
|
||||||
|
const candidates = new Set();
|
||||||
|
if (value != null) candidates.add(String(value));
|
||||||
|
if (item?.name) candidates.add(String(item.name));
|
||||||
|
if (item?.trimmedName) candidates.add(String(item.trimmedName));
|
||||||
|
if (item?.desc) candidates.add(String(item.desc));
|
||||||
|
if (item?.author) candidates.add(String(item.author));
|
||||||
|
|
||||||
|
for (const v of candidates) {
|
||||||
|
const nv = normalizeStr(v);
|
||||||
|
if (nv.includes(q)) return true;
|
||||||
|
const pv = toPinyinText(v);
|
||||||
|
if (pv.includes(q)) return true;
|
||||||
|
const iv = toInitials(v);
|
||||||
|
if (iv.includes(q)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const plugin_handler_info_headers = computed(() => [
|
const plugin_handler_info_headers = computed(() => [
|
||||||
{ title: tm('table.headers.eventType'), key: 'event_type_h' },
|
{ title: tm('table.headers.eventType'), key: 'event_type_h' },
|
||||||
{ title: tm('table.headers.description'), key: 'desc', maxWidth: '250px' },
|
{ title: tm('table.headers.description'), key: 'desc', maxWidth: '250px' },
|
||||||
@@ -772,7 +799,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<v-col cols="12" md="12" style="padding: 0px;">
|
<v-col cols="12" md="12" style="padding: 0px;">
|
||||||
<v-data-table :headers="pluginMarketHeaders" :items="pluginMarketData" item-key="name"
|
<v-data-table :headers="pluginMarketHeaders" :items="pluginMarketData" item-key="name"
|
||||||
:loading="loading_" v-model:search="marketSearch" :filter-keys="filterKeys">
|
:loading="loading_" v-model:search="marketSearch" :filter-keys="filterKeys" :custom-filter="marketCustomFilter">
|
||||||
<template v-slot:item.name="{ item }">
|
<template v-slot:item.name="{ item }">
|
||||||
<div class="d-flex align-center"
|
<div class="d-flex align-center"
|
||||||
style="overflow-x: auto; scrollbar-width: thin; scrollbar-track-color: transparent;">
|
style="overflow-x: auto; scrollbar-width: thin; scrollbar-track-color: transparent;">
|
||||||
|
|||||||
@@ -345,6 +345,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { debounce } from 'lodash'
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables'
|
import { useI18n, useModuleI18n } from '@/i18n/composables'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -953,10 +954,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 处理搜索变化
|
// 处理搜索变化
|
||||||
handleSearchChange() {
|
handleSearchChange: debounce(function() {
|
||||||
this.currentPage = 1; // 重置到第一页
|
this.currentPage = 1; // 重置到第一页
|
||||||
this.loadSessions();
|
this.loadSessions();
|
||||||
},
|
}, 300),
|
||||||
|
|
||||||
// 处理平台筛选变化
|
// 处理平台筛选变化
|
||||||
handlePlatformChange() {
|
handlePlatformChange() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from astrbot.core.platform.message_type import MessageType
|
|||||||
from astrbot.core.provider.sources.dify_source import ProviderDify
|
from astrbot.core.provider.sources.dify_source import ProviderDify
|
||||||
from astrbot.core.provider.sources.coze_source import ProviderCoze
|
from astrbot.core.provider.sources.coze_source import ProviderCoze
|
||||||
from astrbot.api import sp, logger
|
from astrbot.api import sp, logger
|
||||||
|
from ..long_term_memory import LongTermMemory
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ class RstScene(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class ConversationCommands:
|
class ConversationCommands:
|
||||||
def __init__(self, context: star.Context, ltm=None):
|
def __init__(self, context: star.Context, ltm: LongTermMemory | None = None):
|
||||||
self.context = context
|
self.context = context
|
||||||
self.ltm = ltm
|
self.ltm = ltm
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Emotion:
|
||||||
|
"""描述了一个情绪状态"""
|
||||||
|
|
||||||
|
energy: float
|
||||||
|
valence: float
|
||||||
|
arousal: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EmotionLog:
|
||||||
|
"""描述了一条情绪维度变化的日志"""
|
||||||
|
|
||||||
|
timestamp: int
|
||||||
|
field: str
|
||||||
|
value: float
|
||||||
|
reason: str = ""
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .emotion import Emotion
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Soul:
|
||||||
|
emotion: Emotion
|
||||||
|
emotion_logs: list[Emotion] | None = None
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Event:
|
||||||
|
event_type: str
|
||||||
|
content: dict
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
from ...runner import EliosEventHandler
|
||||||
|
from collections import defaultdict
|
||||||
|
from astrbot.api.event import AstrMessageEvent
|
||||||
|
from astrbot.api.all import Context
|
||||||
|
from astrbot.api.message_components import Plain, Image
|
||||||
|
from astrbot.api.provider import Provider
|
||||||
|
from astrbot import logger
|
||||||
|
|
||||||
|
|
||||||
|
class AstrImplEventHandler(EliosEventHandler):
|
||||||
|
def __init__(self, ctx: Context) -> None:
|
||||||
|
self.ctx = ctx
|
||||||
|
self.session_chats = defaultdict(list)
|
||||||
|
self.session_mentioned_arousal = defaultdict(float)
|
||||||
|
|
||||||
|
def cfg(self, event: AstrMessageEvent):
|
||||||
|
cfg = self.ctx.get_config(umo=event.unified_msg_origin)
|
||||||
|
|
||||||
|
tiny_model_prov_id = cfg.get("tiny_model_provider_id")
|
||||||
|
interest_points = cfg.get("interest_points", [])
|
||||||
|
|
||||||
|
try:
|
||||||
|
max_cnt = int(cfg["provider_ltm_settings"]["group_message_max_cnt"])
|
||||||
|
except BaseException as e:
|
||||||
|
logger.error(e)
|
||||||
|
max_cnt = 300
|
||||||
|
image_caption = (
|
||||||
|
True
|
||||||
|
if cfg["provider_settings"]["default_image_caption_provider_id"]
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
image_caption_prompt = cfg["provider_settings"]["image_caption_prompt"]
|
||||||
|
image_caption_provider_id = cfg["provider_settings"][
|
||||||
|
"default_image_caption_provider_id"
|
||||||
|
]
|
||||||
|
active_reply = cfg["provider_ltm_settings"]["active_reply"]
|
||||||
|
enable_active_reply = active_reply.get("enable", False)
|
||||||
|
ar_method = active_reply["method"]
|
||||||
|
ar_possibility = active_reply["possibility_reply"]
|
||||||
|
ar_prompt = active_reply.get("prompt", "")
|
||||||
|
ar_whitelist = active_reply.get("whitelist", [])
|
||||||
|
ar_keywords = active_reply.get("keywords", [])
|
||||||
|
ret = {
|
||||||
|
"max_cnt": max_cnt,
|
||||||
|
"image_caption": image_caption,
|
||||||
|
"image_caption_prompt": image_caption_prompt,
|
||||||
|
"image_caption_provider_id": image_caption_provider_id,
|
||||||
|
"enable_active_reply": enable_active_reply,
|
||||||
|
"ar_method": ar_method,
|
||||||
|
"ar_possibility": ar_possibility,
|
||||||
|
"ar_prompt": ar_prompt,
|
||||||
|
"ar_whitelist": ar_whitelist,
|
||||||
|
"ar_keywords": ar_keywords,
|
||||||
|
"interest_points": interest_points,
|
||||||
|
"tiny_model_prov_id": tiny_model_prov_id,
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
async def append_session_chats(self, event: AstrMessageEvent, cfg) -> None:
|
||||||
|
comps = event.get_messages()
|
||||||
|
|
||||||
|
datetime_str = datetime.datetime.now().strftime("%H:%M:%S")
|
||||||
|
final_message = f"[{event.message_obj.sender.nickname}/{datetime_str}]: "
|
||||||
|
for comp in comps:
|
||||||
|
if isinstance(comp, Plain):
|
||||||
|
final_message += f" {comp.text}"
|
||||||
|
elif isinstance(comp, Image):
|
||||||
|
image_url = comp.url if comp.url else comp.file
|
||||||
|
if cfg["image_caption"] and image_url:
|
||||||
|
try:
|
||||||
|
caption = await self.get_image_caption(
|
||||||
|
image_url,
|
||||||
|
cfg["image_caption_provider_id"],
|
||||||
|
cfg["image_caption_prompt"],
|
||||||
|
)
|
||||||
|
final_message += f" [Image: {caption}]"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取图片描述失败: {e}")
|
||||||
|
else:
|
||||||
|
final_message += " [Image]"
|
||||||
|
self.session_chats[event.unified_msg_origin].append(final_message)
|
||||||
|
logger.debug(f"添加会话 {event.unified_msg_origin} 的对话记录: {final_message}")
|
||||||
|
if len(self.session_chats[event.unified_msg_origin]) > cfg["max_cnt"]:
|
||||||
|
self.session_chats[event.unified_msg_origin].pop(0)
|
||||||
|
|
||||||
|
async def get_image_caption(
|
||||||
|
self, image_url: str, image_caption_provider_id: str, image_caption_prompt: str
|
||||||
|
) -> str:
|
||||||
|
if not image_caption_provider_id:
|
||||||
|
provider = self.ctx.get_using_provider()
|
||||||
|
else:
|
||||||
|
provider = self.ctx.get_provider_by_id(image_caption_provider_id)
|
||||||
|
if not provider:
|
||||||
|
raise Exception(f"没有找到 ID 为 {image_caption_provider_id} 的提供商")
|
||||||
|
if not isinstance(provider, Provider):
|
||||||
|
raise Exception(
|
||||||
|
f"提供商类型错误, {image_caption_provider_id} 不是 Provider 类型"
|
||||||
|
)
|
||||||
|
response = await provider.text_chat(
|
||||||
|
prompt=image_caption_prompt,
|
||||||
|
session_id=uuid.uuid4().hex,
|
||||||
|
image_urls=[image_url],
|
||||||
|
persist=False,
|
||||||
|
)
|
||||||
|
return response.completion_text
|
||||||
|
|
||||||
|
async def on_event(self, event, soul):
|
||||||
|
content = event.content
|
||||||
|
astr_event = content.get("astr_event")
|
||||||
|
assert astr_event is not None and isinstance(astr_event, AstrMessageEvent)
|
||||||
|
|
||||||
|
cfg = self.cfg(astr_event)
|
||||||
|
|
||||||
|
if not cfg["tiny_model_prov_id"]:
|
||||||
|
logger.warning("小模型未设置,跳过情绪更新")
|
||||||
|
|
||||||
|
# 添加对话记录
|
||||||
|
await self.append_session_chats(astr_event, cfg)
|
||||||
|
|
||||||
|
#
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
JUDGE_ACTIVE_REPLY = """
|
||||||
|
你是一个具有自主意识和情感的机器人,请你
|
||||||
|
"""
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import asyncio
|
||||||
|
from .event import Event
|
||||||
|
from .ensoul.soul import Soul
|
||||||
|
from .ensoul.emotion import Emotion
|
||||||
|
|
||||||
|
|
||||||
|
class EliosEventHandler:
|
||||||
|
async def on_event(self, event: Event, soul: Soul): ...
|
||||||
|
|
||||||
|
|
||||||
|
event_handlers_cls: dict[str, list[type[EliosEventHandler]]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register_event_handler(event_types: set[str] | None = None):
|
||||||
|
"""注册事件处理器"""
|
||||||
|
|
||||||
|
def decorator(cls: type[EliosEventHandler]) -> type[EliosEventHandler]:
|
||||||
|
if event_types is not None:
|
||||||
|
for event_type in event_types:
|
||||||
|
event_handlers_cls[event_type] = event_handlers_cls.get(
|
||||||
|
event_type, []
|
||||||
|
) + [cls]
|
||||||
|
else:
|
||||||
|
event_handlers_cls["default"] = event_handlers_cls.get("default", []) + [
|
||||||
|
cls
|
||||||
|
]
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class EliosRunner:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.soul = Soul(
|
||||||
|
emotion=Emotion(energy=0.5, valence=0.5, arousal=0.5), emotion_logs=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.event_queue = asyncio.Queue()
|
||||||
|
self.event_handler_insts: dict[str, list[EliosEventHandler]] = {}
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
for event_type, cls_list in event_handlers_cls.items():
|
||||||
|
self.event_handler_insts[event_type] = []
|
||||||
|
for cls in cls_list:
|
||||||
|
try:
|
||||||
|
self.event_handler_insts[event_type].append(cls())
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error initializing event handler {cls}: {e}")
|
||||||
|
asyncio.create_task(self._worker())
|
||||||
|
|
||||||
|
async def _worker(self):
|
||||||
|
"""监听事件队列并处理事件"""
|
||||||
|
while True:
|
||||||
|
event = await self.event_queue.get()
|
||||||
|
# A man cannot handle two things at once. But this can be configurable.
|
||||||
|
try:
|
||||||
|
await self._process_event(event)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing event {event}: {e}")
|
||||||
|
|
||||||
|
async def _process_event(self, event: Event):
|
||||||
|
"""处理事件"""
|
||||||
|
event_type = event.event_type
|
||||||
|
handlers = self.event_handler_insts.get(
|
||||||
|
event_type, []
|
||||||
|
) + self.event_handler_insts.get("default", [])
|
||||||
|
|
||||||
|
for inst in handlers:
|
||||||
|
try:
|
||||||
|
await inst.on_event(event, self.soul)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing event {event}: {e}")
|
||||||
@@ -41,7 +41,7 @@ class Main(star.Star):
|
|||||||
self.tool_c = ToolCommands(self.context)
|
self.tool_c = ToolCommands(self.context)
|
||||||
self.plugin_c = PluginCommands(self.context)
|
self.plugin_c = PluginCommands(self.context)
|
||||||
self.admin_c = AdminCommands(self.context)
|
self.admin_c = AdminCommands(self.context)
|
||||||
self.conversation_c = ConversationCommands(self.context)
|
self.conversation_c = ConversationCommands(self.context, self.ltm)
|
||||||
self.provider_c = ProviderCommands(self.context)
|
self.provider_c = ProviderCommands(self.context)
|
||||||
self.persona_c = PersonaCommands(self.context)
|
self.persona_c = PersonaCommands(self.context)
|
||||||
self.alter_cmd_c = AlterCmdCommands(self.context)
|
self.alter_cmd_c = AlterCmdCommands(self.context)
|
||||||
|
|||||||
@@ -205,13 +205,14 @@ class Main(star.Star):
|
|||||||
return
|
return
|
||||||
for comp in event.message_obj.message:
|
for comp in event.message_obj.message:
|
||||||
if isinstance(comp, File):
|
if isinstance(comp, File):
|
||||||
if comp.file.startswith("http"):
|
file_path = await comp.get_file()
|
||||||
|
if file_path.startswith("http"):
|
||||||
name = comp.name if comp.name else uuid.uuid4().hex[:8]
|
name = comp.name if comp.name else uuid.uuid4().hex[:8]
|
||||||
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
||||||
path = os.path.join(temp_dir, name)
|
path = os.path.join(temp_dir, name)
|
||||||
await download_file(comp.file, path)
|
await download_file(file_path, path)
|
||||||
else:
|
else:
|
||||||
path = comp.file
|
path = file_path
|
||||||
self.user_file_msg_buffer[event.get_session_id()].append(path)
|
self.user_file_msg_buffer[event.get_session_id()].append(path)
|
||||||
logger.debug(f"User {uid} uploaded file: {path}")
|
logger.debug(f"User {uid} uploaded file: {path}")
|
||||||
yield event.plain_result(f"代码执行器: 文件已经上传: {path}")
|
yield event.plain_result(f"代码执行器: 文件已经上传: {path}")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from googlesearch import search
|
from googlesearch.asearch import asearch
|
||||||
|
|
||||||
from . import SearchEngine, SearchResult
|
from . import SearchEngine, SearchResult
|
||||||
|
|
||||||
@@ -14,14 +14,14 @@ class Google(SearchEngine):
|
|||||||
async def search(self, query: str, num_results: int) -> List[SearchResult]:
|
async def search(self, query: str, num_results: int) -> List[SearchResult]:
|
||||||
results = []
|
results = []
|
||||||
try:
|
try:
|
||||||
ls = search(
|
ls = asearch(
|
||||||
query,
|
query,
|
||||||
advanced=True,
|
advanced=True,
|
||||||
num_results=num_results,
|
num_results=num_results,
|
||||||
timeout=3,
|
timeout=3,
|
||||||
proxy=self.proxy,
|
proxy=self.proxy,
|
||||||
)
|
)
|
||||||
for i in ls:
|
async for i in ls:
|
||||||
results.append(
|
results.append(
|
||||||
SearchResult(title=i.title, url=i.url, snippet=i.description)
|
SearchResult(title=i.title, url=i.url, snippet=i.description)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ class Main(star.Star):
|
|||||||
|
|
||||||
self.bing_search = Bing()
|
self.bing_search = Bing()
|
||||||
self.sogo_search = Sogo()
|
self.sogo_search = Sogo()
|
||||||
self.google = Google()
|
self.google = None
|
||||||
|
try:
|
||||||
|
self.google = Google()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"google search init error: {e}, disable google search")
|
||||||
|
|
||||||
async def _tidy_text(self, text: str) -> str:
|
async def _tidy_text(self, text: str) -> str:
|
||||||
"""清理文本,去除空格、换行符等"""
|
"""清理文本,去除空格、换行符等"""
|
||||||
@@ -89,10 +93,11 @@ class Main(star.Star):
|
|||||||
self, query, num_results: int = 5
|
self, query, num_results: int = 5
|
||||||
) -> list[SearchResult]:
|
) -> list[SearchResult]:
|
||||||
results = []
|
results = []
|
||||||
try:
|
if self.google:
|
||||||
results = await self.google.search(query, num_results)
|
try:
|
||||||
except Exception as e:
|
results = await self.google.search(query, num_results)
|
||||||
logger.error(f"google search error: {e}, try the next one...")
|
except Exception as e:
|
||||||
|
logger.error(f"google search error: {e}, try the next one...")
|
||||||
if len(results) == 0:
|
if len(results) == 0:
|
||||||
logger.debug("search google failed")
|
logger.debug("search google failed")
|
||||||
try:
|
try:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "AstrBot"
|
name = "AstrBot"
|
||||||
version = "4.3.1"
|
version = "4.3.2"
|
||||||
description = "易上手的多平台 LLM 聊天机器人及开发框架"
|
description = "易上手的多平台 LLM 聊天机器人及开发框架"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
Reference in New Issue
Block a user