Compare commits

..

3 Commits

Author SHA1 Message Date
Soulter eaee98d4b8 chore: bump version to 4.10.2 2025-12-24 21:55:05 +08:00
Soulter 76c66000a7 chore: restrict psutil version <7.2.0 to avoid compatibility issues
fixes: #4176
2025-12-24 15:48:58 +08:00
Oscar Shaw 4b365143c0 feat: support for managing command aliases (#4170)
* feat(command): persist aliases on rename and apply to runtime filter

* feat(dashboard-api): support aliases in rename command endpoint

* feat(dashboard-ui): add alias editor to rename command dialog

* feat(dashboard-ui): enhance alias editor UI in rename dialog
2025-12-24 15:37:10 +08:00
13 changed files with 151 additions and 12 deletions
+1 -1
View File
@@ -1 +1 @@
__version__ = "4.10.1"
__version__ = "4.10.2"
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Any, TypedDict
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
VERSION = "4.10.1"
VERSION = "4.10.2"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
WEBHOOK_SUPPORTED_PLATFORMS = [
+45 -4
View File
@@ -90,6 +90,7 @@ async def toggle_command(handler_full_name: str, enabled: bool) -> CommandDescri
async def rename_command(
handler_full_name: str,
new_fragment: str,
aliases: list[str] | None = None,
) -> CommandDescriptor:
descriptor = _build_descriptor_by_full_name(handler_full_name)
if not descriptor:
@@ -99,9 +100,24 @@ async def rename_command(
if not new_fragment:
raise ValueError("指令名不能为空。")
# 校验主指令名
candidate_full = _compose_command(descriptor.parent_signature, new_fragment)
if _is_command_in_use(handler_full_name, candidate_full):
raise ValueError("新的指令名已被其他指令占用,请换一个名称")
raise ValueError(f"指令名 '{candidate_full}' 已被其他指令占用。")
# 校验别名
if aliases:
for alias in aliases:
alias = alias.strip()
if not alias:
continue
alias_full = _compose_command(descriptor.parent_signature, alias)
if _is_command_in_use(handler_full_name, alias_full):
raise ValueError(f"别名 '{alias_full}' 已被其他指令占用。")
existing_cfg = await db_helper.get_command_config(handler_full_name)
merged_extra = dict(existing_cfg.extra_data or {}) if existing_cfg else {}
merged_extra["resolved_aliases"] = aliases or []
config = await db_helper.upsert_command_config(
handler_full_name=handler_full_name,
@@ -114,7 +130,7 @@ async def rename_command(
conflict_key=descriptor.original_command,
resolution_strategy="manual_rename",
note=None,
extra_data=None,
extra_data=merged_extra,
auto_managed=False,
)
_bind_descriptor_with_config(descriptor, config)
@@ -363,14 +379,27 @@ def _apply_config_to_descriptor(
new_fragment,
)
extra = config.extra_data or {}
resolved_aliases = extra.get("resolved_aliases")
if isinstance(resolved_aliases, list):
descriptor.aliases = [str(x) for x in resolved_aliases if str(x).strip()]
def _apply_config_to_runtime(
descriptor: CommandDescriptor,
config: CommandConfig,
) -> None:
descriptor.handler.enabled = config.enabled
if descriptor.filter_ref and descriptor.current_fragment:
_set_filter_fragment(descriptor.filter_ref, descriptor.current_fragment)
if descriptor.filter_ref:
if descriptor.current_fragment:
_set_filter_fragment(descriptor.filter_ref, descriptor.current_fragment)
extra = config.extra_data or {}
resolved_aliases = extra.get("resolved_aliases")
if isinstance(resolved_aliases, list):
_set_filter_aliases(
descriptor.filter_ref,
[str(x) for x in resolved_aliases if str(x).strip()],
)
def _bind_configs_to_descriptors(
@@ -409,6 +438,18 @@ def _set_filter_fragment(
filter_ref._cmpl_cmd_names = None
def _set_filter_aliases(
filter_ref: CommandFilter | CommandGroupFilter,
aliases: list[str],
) -> None:
current_aliases = getattr(filter_ref, "alias", set())
if set(aliases) == current_aliases:
return
setattr(filter_ref, "alias", set(aliases))
if hasattr(filter_ref, "_cmpl_cmd_names"):
filter_ref._cmpl_cmd_names = None
def _is_command_in_use(
target_handler_full_name: str,
candidate_full_command: str,
+2 -1
View File
@@ -61,12 +61,13 @@ class CommandRoute(Route):
data = await request.get_json()
handler_full_name = data.get("handler_full_name")
new_name = data.get("new_name")
aliases = data.get("aliases")
if not handler_full_name or not new_name:
return Response().error("handler_full_name 与 new_name 均为必填。").__dict__
try:
await rename_command_service(handler_full_name, new_name)
await rename_command_service(handler_full_name, new_name, aliases=aliases)
except ValueError as exc:
return Response().error(str(exc)).__dict__
+9
View File
@@ -0,0 +1,9 @@
## What's Changed
### 修复
1. ‼️‼️ 修复了由 `psutil` 新版本导致的启动时报错的问题。
### 新增
1. 插件指令管理支持管理别名。
@@ -1,14 +1,16 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useModuleI18n } from '@/i18n/composables';
import type { CommandItem } from '../types';
const { tm } = useModuleI18n('features/command');
// Props
defineProps<{
const props = defineProps<{
show: boolean;
command: CommandItem | null;
newName: string;
aliases: string[];
loading: boolean;
}>();
@@ -16,8 +18,42 @@ defineProps<{
const emit = defineEmits<{
(e: 'update:show', value: boolean): void;
(e: 'update:newName', value: string): void;
(e: 'update:aliases', value: string[]): void;
(e: 'confirm'): void;
}>();
const addAlias = () => {
emit('update:aliases', [...props.aliases, '']);
};
const removeAlias = (index: number) => {
const newAliases = [...props.aliases];
newAliases.splice(index, 1);
emit('update:aliases', newAliases);
};
const updateAlias = (index: number, value: string) => {
const newAliases = [...props.aliases];
newAliases[index] = value;
emit('update:aliases', newAliases);
};
const hasAliases = computed(() => (props.aliases || []).some(a => (a ?? '').toString().trim()));
const showAliasEditor = ref(false);
const aliasEditorEverOpened = ref(false);
watch(
() => props.show,
(open) => {
if (!open) return;
// 如果已有别名则默认展开,否则默认收起
showAliasEditor.value = hasAliases.value;
},
);
watch(showAliasEditor, (open) => {
if (open) aliasEditorEverOpened.value = true;
});
</script>
<template>
@@ -32,7 +68,49 @@ const emit = defineEmits<{
variant="outlined"
density="compact"
autofocus
class="mb-2"
/>
<v-card variant="outlined" class="mt-2" elevation="0">
<div
class="d-flex align-center justify-space-between px-4 py-3"
role="button"
tabindex="0"
@click="showAliasEditor = !showAliasEditor"
@keydown.enter.prevent="showAliasEditor = !showAliasEditor"
@keydown.space.prevent="showAliasEditor = !showAliasEditor"
>
<div class="text-subtitle-1">{{ tm('dialogs.rename.aliases') }}</div>
<v-icon size="20">{{ showAliasEditor ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</div>
<v-divider v-if="showAliasEditor" />
<v-slide-y-transition>
<div v-if="aliasEditorEverOpened" v-show="showAliasEditor" class="px-4 py-3">
<div v-for="(alias, index) in aliases" :key="index" class="d-flex align-center mb-2">
<v-text-field
:model-value="alias"
@update:model-value="updateAlias(index, $event)"
variant="outlined"
density="compact"
hide-details
class="flex-grow-1 mr-2"
/>
<v-btn icon="mdi-delete" variant="text" color="error" density="compact" @click="removeAlias(index)" />
</div>
<v-btn
prepend-icon="mdi-plus"
variant="outlined"
color="primary"
block
size="small"
class="mt-2"
@click="addAlias"
>
{{ tm('dialogs.rename.addAlias') }}
</v-btn>
</div>
</v-slide-y-transition>
</v-card>
</v-card-text>
<v-card-actions>
<v-spacer />
@@ -14,6 +14,7 @@ export function useCommandActions(
show: false,
command: null,
newName: '',
aliases: [],
loading: false
});
@@ -53,6 +54,7 @@ export function useCommandActions(
const openRenameDialog = (cmd: CommandItem) => {
renameDialog.command = cmd;
renameDialog.newName = cmd.current_fragment || '';
renameDialog.aliases = [...(cmd.aliases || [])];
renameDialog.show = true;
};
@@ -66,7 +68,8 @@ export function useCommandActions(
try {
const res = await axios.post('/api/commands/rename', {
handler_full_name: renameDialog.command.handler_full_name,
new_name: renameDialog.newName.trim()
new_name: renameDialog.newName.trim(),
aliases: renameDialog.aliases.filter(a => a.trim())
});
if (res.data.status === 'ok') {
toast(successMessage, 'success');
@@ -288,6 +288,8 @@ watch(viewMode, async (mode) => {
@update:show="renameDialog.show = $event"
:new-name="renameDialog.newName"
@update:new-name="renameDialog.newName = $event"
:aliases="renameDialog.aliases"
@update:aliases="renameDialog.aliases = $event"
:command="renameDialog.command"
:loading="renameDialog.loading"
@confirm="handleConfirmRename"
@@ -52,6 +52,7 @@ export interface RenameDialogState {
show: boolean;
command: CommandItem | null;
newName: string;
aliases: string[];
loading: boolean;
}
@@ -45,6 +45,8 @@
"rename": {
"title": "Rename Command",
"newName": "New command name",
"aliases": "Manage aliases",
"addAlias": "Add alias",
"cancel": "Cancel",
"confirm": "Confirm"
},
@@ -45,6 +45,8 @@
"rename": {
"title": "重命名指令",
"newName": "新指令名",
"aliases": "管理别名",
"addAlias": "添加别名",
"cancel": "取消",
"confirm": "确认"
},
+2 -2
View File
@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
version = "4.10.1"
version = "4.10.2"
description = "Easy-to-use multi-platform LLM chatbot and development framework"
readme = "README.md"
requires-python = ">=3.10"
@@ -34,7 +34,7 @@ dependencies = [
"ormsgpack>=1.9.1",
"pillow>=11.2.1",
"pip>=25.1.1",
"psutil>=5.8.0",
"psutil>=5.8.0,<7.2.0",
"py-cord>=2.6.1",
"pydantic~=2.10.3",
"pydub>=0.25.1",
+1 -1
View File
@@ -27,7 +27,7 @@ openai>=1.78.0
ormsgpack>=1.9.1
pillow>=11.2.1
pip>=25.1.1
psutil>=5.8.0
psutil>=5.8.0,<7.2.0
py-cord>=2.6.1
pydantic~=2.10.3
pydub>=0.25.1