Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eaee98d4b8 | |||
| 76c66000a7 | |||
| 4b365143c0 |
@@ -1 +1 @@
|
||||
__version__ = "4.10.1"
|
||||
__version__ = "4.10.2"
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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__
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user