diff --git a/astrbot/dashboard/routes/session_management.py b/astrbot/dashboard/routes/session_management.py index ffe5372a02..fc632d1f55 100644 --- a/astrbot/dashboard/routes/session_management.py +++ b/astrbot/dashboard/routes/session_management.py @@ -328,37 +328,87 @@ async def batch_delete_session_rule(self): 请求体: { - "umos": ["平台:消息类型:会话ID", ...] // umo 列表 + "umos": ["平台:消息类型:会话ID", ...], // 可选 + "scope": "all" | "group" | "private" | "custom_group", // 可选,批量范围 + "group_id": "分组ID", // 当 scope 为 custom_group 时必填 + "rule_key": "session_service_config" | ... (可选,不传则删除所有规则) } """ + try: data = await request.get_json() umos = data.get("umos", []) + scope = data.get("scope", "") + group_id = data.get("group_id", "") + rule_key = data.get("rule_key") + + # 如果指定了 scope,获取符合条件的所有 umo + if scope and not umos: + # 如果是自定义分组 + if scope == "custom_group": + if not group_id: + return Response().error("请指定分组 ID").__dict__ + groups = self._get_groups() + if group_id not in groups: + return Response().error(f"分组 '{group_id}' 不存在").__dict__ + umos = groups[group_id].get("umos", []) + else: + async with self.db_helper.get_db() as session: + session: AsyncSession + result = await session.execute( + select(ConversationV2.user_id).distinct() + ) + all_umos = [row[0] for row in result.fetchall()] + + if scope == "group": + umos = [ + u + for u in all_umos + if ":group:" in u.lower() or ":groupmessage:" in u.lower() + ] + elif scope == "private": + umos = [ + u + for u in all_umos + if ":private:" in u.lower() or ":friend" in u.lower() + ] + elif scope == "all": + umos = all_umos if not umos: - return Response().error("缺少必要参数: umos").__dict__ + return Response().error("缺少必要参数: umos 或有效的 scope").__dict__ if not isinstance(umos, list): return Response().error("参数 umos 必须是数组").__dict__ + if rule_key and rule_key not in AVAILABLE_SESSION_RULE_KEYS: + return Response().error(f"不支持的规则键: {rule_key}").__dict__ + # 批量删除 - deleted_count = 0 + success_count = 0 failed_umos = [] for umo in umos: try: - await sp.clear_async("umo", umo) - deleted_count += 1 + if rule_key: + await sp.session_remove(umo, rule_key) + else: + await sp.clear_async("umo", umo) + success_count += 1 except Exception as e: logger.error(f"删除 umo {umo} 的规则失败: {e!s}") failed_umos.append(umo) + message = f"已删除 {success_count} 条规则" + if rule_key: + message = f"已删除 {success_count} 条 {rule_key} 规则" + if failed_umos: return ( Response() .ok( { - "message": f"已删除 {deleted_count} 条规则,{len(failed_umos)} 条删除失败", - "deleted_count": deleted_count, + "message": f"{message},{len(failed_umos)} 条删除失败", + "success_count": success_count, "failed_umos": failed_umos, } ) @@ -369,8 +419,8 @@ async def batch_delete_session_rule(self): Response() .ok( { - "message": f"已删除 {deleted_count} 条规则", - "deleted_count": deleted_count, + "message": message, + "success_count": success_count, } ) .__dict__ diff --git a/dashboard/src/views/SessionManagementPage.vue b/dashboard/src/views/SessionManagementPage.vue index 0d195cf67a..6eb6a506ab 100644 --- a/dashboard/src/views/SessionManagementPage.vue +++ b/dashboard/src/views/SessionManagementPage.vue @@ -137,7 +137,7 @@ - @@ -584,9 +584,9 @@ export default { // Provider 配置 providerConfig: { - chat_completion: null, - speech_to_text: null, - text_to_speech: null, + chat_completion: '__astrbot_follow_config__', + speech_to_text: '__astrbot_follow_config__', + text_to_speech: '__astrbot_follow_config__', }, // 插件配置 @@ -671,7 +671,7 @@ export default { chatProviderOptions() { return [ - { label: this.tm('provider.followConfig'), value: null }, + { label: this.tm('provider.followConfig'), value: '__astrbot_follow_config__' }, ...this.availableChatProviders.map(p => ({ label: `${p.name} (${p.model})`, value: p.id @@ -681,7 +681,7 @@ export default { sttProviderOptions() { return [ - { label: this.tm('provider.followConfig'), value: null }, + { label: this.tm('provider.followConfig'), value: '__astrbot_follow_config__' }, ...this.availableSttProviders.map(p => ({ label: `${p.name} (${p.model})`, value: p.id @@ -691,7 +691,27 @@ export default { ttsProviderOptions() { return [ - { label: this.tm('provider.followConfig'), value: null }, + { label: this.tm('provider.followConfig'), value: '__astrbot_follow_config__' }, + ...this.availableTtsProviders.map(p => ({ + label: `${p.name} (${p.model})`, + value: p.id + })) + ] + }, + + batchChatProviderOptions() { + return [ + { label: this.tm('provider.followConfig'), value: '__astrbot_follow_config__' }, + ...this.availableChatProviders.map(p => ({ + label: `${p.name} (${p.model})`, + value: p.id + })) + ] + }, + + batchTtsProviderOptions() { + return [ + { label: this.tm('provider.followConfig'), value: '__astrbot_follow_config__' }, ...this.availableTtsProviders.map(p => ({ label: `${p.name} (${p.model})`, value: p.id @@ -914,9 +934,9 @@ export default { // 初始化 Provider 配置 this.providerConfig = { - chat_completion: this.editingRules['provider_perf_chat_completion'] || null, - speech_to_text: this.editingRules['provider_perf_speech_to_text'] || null, - text_to_speech: this.editingRules['provider_perf_text_to_speech'] || null, + chat_completion: this.editingRules['provider_perf_chat_completion'] || '__astrbot_follow_config__', + speech_to_text: this.editingRules['provider_perf_speech_to_text'] || '__astrbot_follow_config__', + text_to_speech: this.editingRules['provider_perf_text_to_speech'] || '__astrbot_follow_config__', } // 初始化插件配置 @@ -997,7 +1017,7 @@ export default { for (const type of providerTypes) { const value = this.providerConfig[type] - if (value) { + if (value && value !== '__astrbot_follow_config__') { // 有值时更新 updateTasks.push( axios.post('/api/session/update-rule', { @@ -1007,7 +1027,7 @@ export default { }) ) } else if (this.editingRules[`provider_perf_${type}`]) { - // 选择了"跟随配置文件"(null)且之前有配置,则删除 + // 选择了"跟随配置文件" (__astrbot_follow_config__) 且之前有配置,则删除 deleteTasks.push( axios.post('/api/session/delete-rule', { umo: this.selectedUmo.umo, @@ -1035,9 +1055,10 @@ export default { this.rulesList.push(item) } for (const type of providerTypes) { - if (this.providerConfig[type]) { - item.rules[`provider_perf_${type}`] = this.providerConfig[type] - this.editingRules[`provider_perf_${type}`] = this.providerConfig[type] + const val = this.providerConfig[type] + if (val && val !== '__astrbot_follow_config__') { + item.rules[`provider_perf_${type}`] = val + this.editingRules[`provider_perf_${type}`] = val } else { // 删除本地数据 delete item.rules[`provider_perf_${type}`] @@ -1354,23 +1375,41 @@ export default { } if (this.batchChatProvider !== null) { - tasks.push(axios.post('/api/session/batch-update-provider', { - scope, - umos, - group_id: groupId, - provider_type: 'chat_completion', - provider_id: this.batchChatProvider || null - })) + if (this.batchChatProvider === '__astrbot_follow_config__') { + tasks.push(axios.post('/api/session/batch-delete-rule', { + scope, + umos, + group_id: groupId, + rule_key: 'provider_perf_chat_completion' + })) + } else { + tasks.push(axios.post('/api/session/batch-update-provider', { + scope, + umos, + group_id: groupId, + provider_type: 'chat_completion', + provider_id: this.batchChatProvider + })) + } } if (this.batchTtsProvider !== null) { - tasks.push(axios.post('/api/session/batch-update-provider', { - scope, - umos, - group_id: groupId, - provider_type: 'text_to_speech', - provider_id: this.batchTtsProvider || null - })) + if (this.batchTtsProvider === '__astrbot_follow_config__') { + tasks.push(axios.post('/api/session/batch-delete-rule', { + scope, + umos, + group_id: groupId, + rule_key: 'provider_perf_text_to_speech' + })) + } else { + tasks.push(axios.post('/api/session/batch-update-provider', { + scope, + umos, + group_id: groupId, + provider_type: 'text_to_speech', + provider_id: this.batchTtsProvider + })) + } } if (tasks.length === 0) {