mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
Fix(WebUi): allow batch resetting provider config to "follow" (iss#6749) (#6825)
* feat(webui): use explicit 'follow' status for provider settings and improve batch operation logic * fix: allow batch resetting provider config to "follow config" * fix(#6749): use a unique constant for 'follow' status to avoid collisions with provider IDs * fix: remove config.use_reloader = True * refactor(ui): extract follow config sentinel constant --------- Co-authored-by: RC-CHN <1051989940@qq.com>
This commit is contained in:
@@ -328,37 +328,87 @@ class SessionManagementRoute(Route):
|
||||
|
||||
请求体:
|
||||
{
|
||||
"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 @@ class SessionManagementRoute(Route):
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"已删除 {deleted_count} 条规则",
|
||||
"deleted_count": deleted_count,
|
||||
"message": message,
|
||||
"success_count": success_count,
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" lg="3">
|
||||
<v-select v-model="batchChatProvider" :items="chatProviderOptions" item-title="label" item-value="value"
|
||||
<v-select v-model="batchChatProvider" :items="batchChatProviderOptions" item-title="label" item-value="value"
|
||||
:label="tm('batchOperations.chatProvider')" hide-details clearable variant="solo-filled" flat density="comfortable">
|
||||
</v-select>
|
||||
</v-col>
|
||||
@@ -527,6 +527,8 @@ import {
|
||||
useConfirmDialog
|
||||
} from '@/utils/confirmDialog'
|
||||
|
||||
const FOLLOW_CONFIG_VALUE = '__astrbot_follow_config__'
|
||||
|
||||
export default {
|
||||
name: 'SessionManagementPage',
|
||||
setup() {
|
||||
@@ -584,9 +586,9 @@ export default {
|
||||
|
||||
// Provider 配置
|
||||
providerConfig: {
|
||||
chat_completion: null,
|
||||
speech_to_text: null,
|
||||
text_to_speech: null,
|
||||
chat_completion: FOLLOW_CONFIG_VALUE,
|
||||
speech_to_text: FOLLOW_CONFIG_VALUE,
|
||||
text_to_speech: FOLLOW_CONFIG_VALUE,
|
||||
},
|
||||
|
||||
// 插件配置
|
||||
@@ -671,7 +673,7 @@ export default {
|
||||
|
||||
chatProviderOptions() {
|
||||
return [
|
||||
{ label: this.tm('provider.followConfig'), value: null },
|
||||
{ label: this.tm('provider.followConfig'), value: FOLLOW_CONFIG_VALUE },
|
||||
...this.availableChatProviders.map(p => ({
|
||||
label: `${p.name} (${p.model})`,
|
||||
value: p.id
|
||||
@@ -681,7 +683,7 @@ export default {
|
||||
|
||||
sttProviderOptions() {
|
||||
return [
|
||||
{ label: this.tm('provider.followConfig'), value: null },
|
||||
{ label: this.tm('provider.followConfig'), value: FOLLOW_CONFIG_VALUE },
|
||||
...this.availableSttProviders.map(p => ({
|
||||
label: `${p.name} (${p.model})`,
|
||||
value: p.id
|
||||
@@ -691,7 +693,27 @@ export default {
|
||||
|
||||
ttsProviderOptions() {
|
||||
return [
|
||||
{ label: this.tm('provider.followConfig'), value: null },
|
||||
{ label: this.tm('provider.followConfig'), value: FOLLOW_CONFIG_VALUE },
|
||||
...this.availableTtsProviders.map(p => ({
|
||||
label: `${p.name} (${p.model})`,
|
||||
value: p.id
|
||||
}))
|
||||
]
|
||||
},
|
||||
|
||||
batchChatProviderOptions() {
|
||||
return [
|
||||
{ label: this.tm('provider.followConfig'), value: FOLLOW_CONFIG_VALUE },
|
||||
...this.availableChatProviders.map(p => ({
|
||||
label: `${p.name} (${p.model})`,
|
||||
value: p.id
|
||||
}))
|
||||
]
|
||||
},
|
||||
|
||||
batchTtsProviderOptions() {
|
||||
return [
|
||||
{ label: this.tm('provider.followConfig'), value: FOLLOW_CONFIG_VALUE },
|
||||
...this.availableTtsProviders.map(p => ({
|
||||
label: `${p.name} (${p.model})`,
|
||||
value: p.id
|
||||
@@ -914,9 +936,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'] || FOLLOW_CONFIG_VALUE,
|
||||
speech_to_text: this.editingRules['provider_perf_speech_to_text'] || FOLLOW_CONFIG_VALUE,
|
||||
text_to_speech: this.editingRules['provider_perf_text_to_speech'] || FOLLOW_CONFIG_VALUE,
|
||||
}
|
||||
|
||||
// 初始化插件配置
|
||||
@@ -997,7 +1019,7 @@ export default {
|
||||
|
||||
for (const type of providerTypes) {
|
||||
const value = this.providerConfig[type]
|
||||
if (value) {
|
||||
if (value && value !== FOLLOW_CONFIG_VALUE) {
|
||||
// 有值时更新
|
||||
updateTasks.push(
|
||||
axios.post('/api/session/update-rule', {
|
||||
@@ -1007,7 +1029,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 +1057,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 !== FOLLOW_CONFIG_VALUE) {
|
||||
item.rules[`provider_perf_${type}`] = val
|
||||
this.editingRules[`provider_perf_${type}`] = val
|
||||
} else {
|
||||
// 删除本地数据
|
||||
delete item.rules[`provider_perf_${type}`]
|
||||
@@ -1354,23 +1377,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 === FOLLOW_CONFIG_VALUE) {
|
||||
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 === FOLLOW_CONFIG_VALUE) {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user