移除了mcp会话级的启停,增加了批量设置的选项,对相关问题进行了修复

This commit is contained in:
advent259141
2025-07-13 00:15:21 +08:00
parent 25b75e05e4
commit a15444ee8c
9 changed files with 201 additions and 191 deletions
@@ -100,8 +100,7 @@ class LLMRequestSubStage(Stage):
if not event.message_str.startswith(self.provider_wake_prefix):
return
req.prompt = event.message_str[len(self.provider_wake_prefix) :]
if SessionServiceManager.should_process_mcp_request(event):
req.func_tool = self.ctx.plugin_manager.context.get_llm_tool_manager()
req.func_tool = self.ctx.plugin_manager.context.get_llm_tool_manager()
for comp in event.message_obj.message:
if isinstance(comp, Image):
image_path = await comp.convert_to_file_path()
@@ -179,13 +178,6 @@ class LLMRequestSubStage(Stage):
async def requesting():
step_idx = 0
while step_idx < self.max_step:
# 在每次实际请求 LLM 前检查会话级别的启停状态,这可以防止插件或函数工具调用时绕过会话级别的限制
if not SessionServiceManager.should_process_llm_request(event):
logger.debug(
f"会话 {event.unified_msg_origin} 禁用了 LLM,终止 LLM 请求。"
)
return
step_idx += 1
try:
async for resp in tool_loop_agent.step():
@@ -284,13 +276,6 @@ class LLMRequestSubStage(Stage):
self, event: AstrMessageEvent, req: ProviderRequest, prov: Provider
):
"""处理 WebChat 平台的特殊情况,包括第一次 LLM 对话时总结对话内容生成 title"""
# 检查会话级别的LLM启停状态,防止标题生成功能绕过会话级别限制
if not SessionServiceManager.should_process_llm_request(event):
logger.debug(
f"会话 {event.unified_msg_origin} 禁用了 LLM,跳过 WebChat 标题生成。"
)
return
conversation = await self.conv_manager.get_conversation(
event.unified_msg_origin, req.conversation.cid
)
@@ -1 +0,0 @@
# Session Status Check Stage
+1 -64
View File
@@ -135,68 +135,6 @@ class SessionServiceManager:
session_id = event.unified_msg_origin
return SessionServiceManager.is_tts_enabled_for_session(session_id)
# =============================================================================
# MCP 相关方法
# =============================================================================
@staticmethod
def is_mcp_enabled_for_session(session_id: str) -> bool:
"""检查MCP是否在指定会话中启用
Args:
session_id: 会话ID (unified_msg_origin)
Returns:
bool: True表示启用,False表示禁用
"""
# 获取会话服务配置
session_config = sp.get("session_service_config", {}) or {}
session_services = session_config.get(session_id, {})
# 如果配置了该会话的MCP状态,返回该状态
mcp_enabled = session_services.get("mcp_enabled")
if mcp_enabled is not None:
return mcp_enabled
# 如果没有配置,默认为启用(兼容性考虑)
return True
@staticmethod
def set_mcp_status_for_session(session_id: str, enabled: bool) -> None:
"""设置MCP在指定会话中的启停状态
Args:
session_id: 会话ID (unified_msg_origin)
enabled: True表示启用,False表示禁用
"""
# 获取当前配置
session_config = sp.get("session_service_config", {}) or {}
if session_id not in session_config:
session_config[session_id] = {}
# 设置MCP状态
session_config[session_id]["mcp_enabled"] = enabled
# 保存配置
sp.put("session_service_config", session_config)
logger.info(
f"会话 {session_id} 的MCP状态已更新为: {'启用' if enabled else '禁用'}"
)
@staticmethod
def should_process_mcp_request(event: AstrMessageEvent) -> bool:
"""检查是否应该处理MCP请求
Args:
event: 消息事件
Returns:
bool: True表示应该处理,False表示跳过
"""
session_id = event.unified_msg_origin
return SessionServiceManager.is_mcp_enabled_for_session(session_id)
# =============================================================================
# 会话整体启停相关方法
# =============================================================================
@@ -333,7 +271,7 @@ class SessionServiceManager:
session_id: 会话ID (unified_msg_origin)
Returns:
Dict[str, bool]: 包含session_enabled、llm_enabled、tts_enabled、mcp_enabled的字典
Dict[str, bool]: 包含session_enabled、llm_enabled、tts_enabled的字典
"""
session_config = sp.get("session_service_config", {}) or {}
return session_config.get(
@@ -342,7 +280,6 @@ class SessionServiceManager:
"session_enabled": True, # 默认启用
"llm_enabled": True, # 默认启用
"tts_enabled": True, # 默认启用
"mcp_enabled": True, # 默认启用
},
)
@@ -29,7 +29,6 @@ class SessionManagementRoute(Route):
"/session/update_plugin": ("POST", self.update_session_plugin),
"/session/update_llm": ("POST", self.update_session_llm),
"/session/update_tts": ("POST", self.update_session_tts),
"/session/update_mcp": ("POST", self.update_session_mcp),
"/session/update_name": ("POST", self.update_session_name),
"/session/update_status": ("POST", self.update_session_status),
}
@@ -76,9 +75,6 @@ class SessionManagementRoute(Route):
"tts_enabled": SessionServiceManager.is_tts_enabled_for_session(
session_id
),
"mcp_enabled": SessionServiceManager.is_mcp_enabled_for_session(
session_id
),
"platform": session_id.split(":")[0]
if ":" in session_id
else "unknown",
@@ -345,9 +341,6 @@ class SessionManagementRoute(Route):
session_id
),
"tts_enabled": None, # 将在下面设置
"mcp_enabled": SessionServiceManager.is_mcp_enabled_for_session(
session_id
),
"platform": session_id.split(":")[0]
if ":" in session_id
else "unknown",
@@ -613,39 +606,6 @@ class SessionManagementRoute(Route):
logger.error(error_msg)
return Response().error(f"更新会话TTS状态失败: {str(e)}").__dict__
async def update_session_mcp(self):
"""更新指定会话的MCP启停状态"""
try:
data = await request.get_json()
session_id = data.get("session_id")
enabled = data.get("enabled")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
if enabled is None:
return Response().error("缺少必要参数: enabled").__dict__
# 使用 SessionServiceManager 更新MCP状态
SessionServiceManager.set_mcp_status_for_session(session_id, enabled)
return (
Response()
.ok(
{
"message": f"MCP工具调用已{'启用' if enabled else '禁用'}",
"session_id": session_id,
"mcp_enabled": enabled,
}
)
.__dict__
)
except Exception as e:
error_msg = f"更新会话MCP状态失败: {str(e)}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"更新会话MCP状态失败: {str(e)}").__dict__
async def update_session_name(self):
"""更新指定会话的自定义名称"""
try:
@@ -29,7 +29,6 @@
"ttsProvider": "TTS Provider",
"llmStatus": "LLM Status",
"ttsStatus": "TTS Status",
"mcpStatus": "MCP Status",
"pluginManagement": "Plugin Management"
}
},
@@ -43,7 +42,13 @@
"batchOperations": {
"title": "Batch Operations",
"setPersona": "Batch Set Persona",
"setChatProvider": "Batch Set Chat Provider"
"setChatProvider": "Batch Set Chat Provider",
"setSttProvider": "Batch Set STT Provider",
"setTtsProvider": "Batch Set TTS Provider",
"setLlmStatus": "Batch Set LLM Status",
"setTtsStatus": "Batch Set TTS Status",
"noSttProvider": "No STT Provider Available",
"noTtsProvider": "No TTS Provider Available"
},
"pluginManagement": {
"title": "Plugin Management",
@@ -69,7 +74,6 @@
"sessionStatusSuccess": "Session {status}",
"llmStatusSuccess": "LLM {status}",
"ttsStatusSuccess": "TTS {status}",
"mcpStatusSuccess": "MCP {status}",
"statusUpdateError": "Failed to update status",
"loadSessionsError": "Failed to load session list",
"batchUpdateSuccess": "Successfully batch updated {count} settings",
@@ -29,7 +29,6 @@
"ttsProvider": "TTS Provider",
"llmStatus": "LLM启停",
"ttsStatus": "TTS启停",
"mcpStatus": "MCP启停",
"pluginManagement": "插件管理"
}
},
@@ -43,7 +42,13 @@
"batchOperations": {
"title": "批量操作",
"setPersona": "批量设置人格",
"setChatProvider": "批量设置 Chat Provider"
"setChatProvider": "批量设置 Chat Provider",
"setSttProvider": "批量设置 STT Provider",
"setTtsProvider": "批量设置 TTS Provider",
"setLlmStatus": "批量设置 LLM 状态",
"setTtsStatus": "批量设置 TTS 状态",
"noSttProvider": "暂无可用 STT Provider",
"noTtsProvider": "暂无可用 TTS Provider"
},
"pluginManagement": {
"title": "插件管理",
@@ -69,7 +74,6 @@
"sessionStatusSuccess": "会话 {status}",
"llmStatusSuccess": "LLM {status}",
"ttsStatusSuccess": "TTS {status}",
"mcpStatusSuccess": "MCP {status}",
"statusUpdateError": "状态更新失败",
"loadSessionsError": "加载会话列表失败",
"batchUpdateSuccess": "成功批量更新 {count} 项设置",
+185 -47
View File
@@ -52,9 +52,13 @@
<!-- 会话列表 -->
<v-data-table
:headers="headers"
:items="filteredSessions"
:items="paginatedSessions"
:loading="loading"
:items-per-page="20"
:items-per-page="itemsPerPage"
:server-items-length="totalFilteredItems"
:page="currentPage"
@update:page="updatePage"
@update:items-per-page="updateItemsPerPage"
class="elevation-0"
>
<!-- 会话信息 -->
@@ -242,21 +246,6 @@
</v-switch>
</template>
<!-- MCP启停 -->
<template v-slot:item.mcp_enabled="{ item }">
<v-switch
:model-value="item.mcp_enabled"
@update:model-value="(value) => updateMCP(item, value)"
:loading="item.updating"
:disabled="!item.session_enabled"
hide-details
density="compact"
color="info"
inset
>
</v-switch>
</template>
<!-- 插件管理 -->
<template v-slot:item.plugins="{ item }">
<v-btn
@@ -284,15 +273,15 @@
</v-card>
<!-- 批量操作面板 -->
<v-card class="mt-4" v-if="availablePersonas.length > 0 || availableChatProviders.length > 0">
<v-card class="mt-4">
<v-card-title class="bg-secondary text-white py-3 px-4">
<v-icon color="white" class="me-2">mdi-cog-outline</v-icon>
<span>{{ tm('batchOperations.title') }}</span>
</v-card-title>
<v-card-text class="pa-4">
<v-row>
<v-col cols="12" md="4" v-if="availablePersonas.length > 0">
<v-row class="justify-start align-center">
<v-col cols="2" v-if="availablePersonas.length > 0">
<v-select
v-model="batchPersona"
:items="personaOptions"
@@ -302,10 +291,11 @@
hide-details
clearable
variant="outlined"
density="compact"
></v-select>
</v-col>
<v-col cols="12" md="4" v-if="availableChatProviders.length > 0">
<v-col cols="2" v-if="availableChatProviders.length > 0">
<v-select
v-model="batchChatProvider"
:items="chatProviderOptions"
@@ -315,15 +305,75 @@
hide-details
clearable
variant="outlined"
density="compact"
></v-select>
</v-col>
<v-col cols="2">
<v-select
v-model="batchSttProvider"
:items="sttProviderOptions"
item-title="label"
item-value="value"
:label="tm('batchOperations.setSttProvider')"
hide-details
clearable
variant="outlined"
density="compact"
:disabled="availableSttProviders.length === 0"
:placeholder="availableSttProviders.length === 0 ? tm('batchOperations.noSttProvider') : ''"
></v-select>
</v-col>
<v-col cols="2">
<v-select
v-model="batchTtsProvider"
:items="ttsProviderOptions"
item-title="label"
item-value="value"
:label="tm('batchOperations.setTtsProvider')"
hide-details
clearable
variant="outlined"
density="compact"
:disabled="availableTtsProviders.length === 0"
:placeholder="availableTtsProviders.length === 0 ? tm('batchOperations.noTtsProvider') : ''"
></v-select>
</v-col>
<v-col cols="1.5">
<v-select
v-model="batchLlmStatus"
:items="[{label: tm('status.enabled'), value: true}, {label: tm('status.disabled'), value: false}]"
item-title="label"
item-value="value"
:label="tm('batchOperations.setLlmStatus')"
hide-details
clearable
variant="outlined"
density="compact"
></v-select>
</v-col>
<v-col cols="1.5">
<v-select
v-model="batchTtsStatus"
:items="[{label: tm('status.enabled'), value: true}, {label: tm('status.disabled'), value: false}]"
item-title="label"
item-value="value"
:label="tm('batchOperations.setTtsStatus')"
hide-details
clearable
variant="outlined"
density="compact"
></v-select>
</v-col>
<v-col cols="12" md="4">
<v-col cols="1">
<v-btn
color="primary"
block
@click="applyBatchChanges"
:disabled="!batchPersona && !batchChatProvider"
:disabled="!batchPersona && !batchChatProvider && !batchSttProvider && !batchTtsProvider && batchLlmStatus === null && batchTtsStatus === null"
:loading="batchUpdating"
>
{{ tm('buttons.apply') }}
@@ -484,6 +534,10 @@ export default {
searchQuery: '',
filterPlatform: null,
// 分页相关
currentPage: 1,
itemsPerPage: 20,
// 可用选项
availablePersonas: [],
availableChatProviders: [],
@@ -493,6 +547,10 @@ export default {
// 批量操作
batchPersona: null,
batchChatProvider: null,
batchSttProvider: null,
batchTtsProvider: null,
batchLlmStatus: null,
batchTtsStatus: null,
batchUpdating: false,
// 插件管理
@@ -525,10 +583,60 @@ export default {
{ title: this.tm('table.headers.ttsProvider'), key: 'tts_provider', sortable: false, width: '150px' },
{ title: this.tm('table.headers.llmStatus'), key: 'llm_enabled', sortable: false, width: '120px' },
{ title: this.tm('table.headers.ttsStatus'), key: 'tts_enabled', sortable: false, width: '120px' },
{ title: this.tm('table.headers.mcpStatus'), key: 'mcp_enabled', sortable: false, width: '120px' },
{ title: this.tm('table.headers.pluginManagement'), key: 'plugins', sortable: false, width: '120px' },
]
},
// 懒加载过滤会话 - 只处理当前页面数据
paginatedSessions() {
// 先进行过滤
let filtered = this.sessions;
// 搜索筛选
if (this.searchQuery) {
const query = this.searchQuery.toLowerCase();
filtered = filtered.filter(session =>
session.session_name.toLowerCase().includes(query) ||
session.platform.toLowerCase().includes(query) ||
session.persona_name?.toLowerCase().includes(query) ||
session.chat_provider_name?.toLowerCase().includes(query)
);
}
// 平台筛选
if (this.filterPlatform) {
filtered = filtered.filter(session => session.platform === this.filterPlatform);
}
// 计算分页
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
return filtered.slice(startIndex, endIndex);
},
// 计算过滤后的总数用于分页
totalFilteredItems() {
let filtered = this.sessions;
// 搜索筛选
if (this.searchQuery) {
const query = this.searchQuery.toLowerCase();
filtered = filtered.filter(session =>
session.session_name.toLowerCase().includes(query) ||
session.platform.toLowerCase().includes(query) ||
session.persona_name?.toLowerCase().includes(query) ||
session.chat_provider_name?.toLowerCase().includes(query)
);
}
// 平台筛选
if (this.filterPlatform) {
filtered = filtered.filter(session => session.platform === this.filterPlatform);
}
return filtered.length;
},
filteredSessions() {
let filtered = this.sessions;
@@ -738,28 +846,8 @@ export default {
session.updating = false;
},
async updateMCP(session, enabled) {
session.updating = true;
try {
const response = await axios.post('/api/session/update_mcp', {
session_id: session.session_id,
enabled: enabled
});
if (response.data.status === 'ok') {
session.mcp_enabled = enabled;
this.showSuccess(this.tm('messages.mcpStatusSuccess', { status: enabled ? this.tm('status.enabled') : this.tm('status.disabled') }));
} else {
this.showError(response.data.message || this.tm('messages.statusUpdateError'));
}
} catch (error) {
this.showError(error.response?.data?.message || this.tm('messages.statusUpdateError'));
}
session.updating = false;
},
async applyBatchChanges() {
if (!this.batchPersona && !this.batchChatProvider) {
if (!this.batchPersona && !this.batchChatProvider && !this.batchSttProvider && !this.batchTtsProvider && this.batchLlmStatus === null && this.batchTtsStatus === null) {
return;
}
@@ -767,7 +855,8 @@ export default {
let successCount = 0;
let errorCount = 0;
for (const session of this.filteredSessions) {
// 使用当前页面的会话数据而不是全部过滤后的会话
for (const session of this.paginatedSessions) {
try {
// 批量更新人格
if (this.batchPersona) {
@@ -780,6 +869,30 @@ export default {
await this.updateProvider(session, this.batchChatProvider, 'chat_completion');
successCount++;
}
// 批量更新 STT Provider
if (this.batchSttProvider) {
await this.updateProvider(session, this.batchSttProvider, 'speech_to_text');
successCount++;
}
// 批量更新 TTS Provider
if (this.batchTtsProvider) {
await this.updateProvider(session, this.batchTtsProvider, 'text_to_speech');
successCount++;
}
// 批量更新 LLM 状态
if (this.batchLlmStatus !== null) {
await this.updateLLM(session, this.batchLlmStatus);
successCount++;
}
// 批量更新 TTS 状态
if (this.batchTtsStatus !== null) {
await this.updateTTS(session, this.batchTtsStatus);
successCount++;
}
} catch (error) {
errorCount++;
}
@@ -796,6 +909,10 @@ export default {
// 清空批量设置
this.batchPersona = null;
this.batchChatProvider = null;
this.batchSttProvider = null;
this.batchTtsProvider = null;
this.batchLlmStatus = null;
this.batchTtsStatus = null;
},
async openPluginManager(session) {
@@ -905,6 +1022,27 @@ export default {
this.snackbarColor = 'error';
this.snackbar = true;
},
// 分页相关方法
updatePage(page) {
this.currentPage = page;
},
updateItemsPerPage(itemsPerPage) {
this.itemsPerPage = itemsPerPage;
this.currentPage = 1; // 重置到第一页
},
},
watch: {
// 监听搜索和筛选变化,重置页码
searchQuery() {
this.currentPage = 1;
},
filterPlatform() {
this.currentPage = 1;
},
}
}
</script>
-17
View File
@@ -351,23 +351,6 @@ class Main(star.Star):
MessageEventResult().message(f"{status_text}当前会话的文本转语音。")
)
@filter.command("mcp")
async def mcp(self, event: AstrMessageEvent):
"""开关MCP工具调用(会话级别)"""
from astrbot.core.star.session_llm_manager import SessionServiceManager
session_id = event.unified_msg_origin
current_status = SessionServiceManager.is_mcp_enabled_for_session(session_id)
# 切换状态
new_status = not current_status
SessionServiceManager.set_mcp_status_for_session(session_id, new_status)
status_text = "已开启" if new_status else "已关闭"
event.set_result(
MessageEventResult().message(f"{status_text}当前会话的MCP工具调用。")
)
@filter.command("sid")
async def sid(self, event: AstrMessageEvent):
"""获取会话 ID 和 管理员 ID"""
View File