Skip to content

Commit 881b409

Browse files
authored
feat: improve plugin failure handling and extension list UX (#5535)
* feat: improve plugin failure handling and extension list UX * fix: address plugin review comments * fix: clear stale reload feedback on failed plugin reload * fix: refine extension i18n and uninstall flow * fix: refresh extension list after install failure * feat: add random plugin visibility controls in market * refactor: extract extension helpers and simplify uninstall flow * refactor: improve failed plugin diagnostics and uninstall flow * refactor: streamline extension uninstall flow * fix: harden failed plugin install tracking and cleanup * refactor: simplify extension flows and remove unused timed message * fix: improve failed uninstall idempotency and extension error handling * refactor: unify extension install-uninstall orchestration
1 parent 74a4646 commit 881b409

File tree

11 files changed

+841
-343
lines changed

11 files changed

+841
-343
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Shared plugin error message templates for star manager flows."""
2+
3+
PLUGIN_ERROR_TEMPLATES = {
4+
"not_found_in_failed_list": "插件不存在于失败列表中。",
5+
"reserved_plugin_cannot_uninstall": "该插件是 AstrBot 保留插件,无法卸载。",
6+
"failed_plugin_dir_remove_error": (
7+
"移除失败插件成功,但是删除插件文件夹失败: {error}。"
8+
"您可以手动删除该文件夹,位于 addons/plugins/ 下。"
9+
),
10+
}
11+
12+
13+
def format_plugin_error(key: str, **kwargs) -> str:
14+
template = PLUGIN_ERROR_TEMPLATES.get(key, key)
15+
try:
16+
return template.format(**kwargs)
17+
except Exception:
18+
return template

astrbot/core/star/star_manager.py

Lines changed: 230 additions & 70 deletions
Large diffs are not rendered by default.

astrbot/dashboard/routes/plugin.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def __init__(
5858
"/plugin/update": ("POST", self.update_plugin),
5959
"/plugin/update-all": ("POST", self.update_all_plugins),
6060
"/plugin/uninstall": ("POST", self.uninstall_plugin),
61+
"/plugin/uninstall-failed": ("POST", self.uninstall_failed_plugin),
6162
"/plugin/market_list": ("GET", self.get_online_plugins),
6263
"/plugin/off": ("POST", self.off_plugin),
6364
"/plugin/on": ("POST", self.on_plugin),
@@ -565,6 +566,34 @@ async def uninstall_plugin(self):
565566
logger.error(traceback.format_exc())
566567
return Response().error(str(e)).__dict__
567568

569+
async def uninstall_failed_plugin(self):
570+
if DEMO_MODE:
571+
return (
572+
Response()
573+
.error("You are not permitted to do this operation in demo mode")
574+
.__dict__
575+
)
576+
577+
post_data = await request.get_json()
578+
dir_name = post_data.get("dir_name", "")
579+
delete_config = post_data.get("delete_config", False)
580+
delete_data = post_data.get("delete_data", False)
581+
if not dir_name:
582+
return Response().error("缺少失败插件目录名").__dict__
583+
584+
try:
585+
logger.info(f"正在卸载失败插件 {dir_name}")
586+
await self.plugin_manager.uninstall_failed_plugin(
587+
dir_name,
588+
delete_config=delete_config,
589+
delete_data=delete_data,
590+
)
591+
logger.info(f"卸载失败插件 {dir_name} 成功")
592+
return Response().ok(None, "卸载成功").__dict__
593+
except Exception as e:
594+
logger.error(traceback.format_exc())
595+
return Response().error(str(e)).__dict__
596+
568597
async def update_plugin(self):
569598
if DEMO_MODE:
570599
return (

dashboard/src/components/shared/ExtensionCard.vue

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,28 +189,15 @@ const viewChangelog = () => {
189189
class="ml-2"
190190
icon="mdi-update"
191191
size="small"
192+
style="cursor: pointer"
193+
@click.stop="updateExtension"
192194
></v-icon>
193195
</template>
194196
<span
195197
>{{ tm("card.status.hasUpdate") }}:
196198
{{ extension.online_version }}</span
197199
>
198200
</v-tooltip>
199-
<v-tooltip
200-
location="top"
201-
v-if="!extension.activated && !marketMode"
202-
>
203-
<template v-slot:activator="{ props: tooltipProps }">
204-
<v-icon
205-
v-bind="tooltipProps"
206-
color="error"
207-
class="ml-2"
208-
icon="mdi-cancel"
209-
size="small"
210-
></v-icon>
211-
</template>
212-
<span>{{ tm("card.status.disabled") }}</span>
213-
</v-tooltip>
214201
</p>
215202

216203
<template v-if="!marketMode">
@@ -299,6 +286,8 @@ const viewChangelog = () => {
299286
color="warning"
300287
label
301288
size="small"
289+
style="cursor: pointer"
290+
@click="updateExtension"
302291
>
303292
<v-icon icon="mdi-arrow-up-bold" start></v-icon>
304293
{{ extension.online_version }}

dashboard/src/i18n/locales/en-US/features/extension.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
"titles": {
1212
"installedAstrBotPlugins": "Installed AstrBot Plugins"
1313
},
14+
"failedPlugins": {
15+
"title": "Failed to Load Plugins ({count})",
16+
"hint": "These plugins failed to load. You can try reload or uninstall them directly.",
17+
"columns": {
18+
"plugin": "Plugin",
19+
"error": "Error"
20+
}
21+
},
1422
"search": {
1523
"placeholder": "Search extensions...",
1624
"marketPlaceholder": "Search market extensions..."
@@ -109,6 +117,8 @@
109117
"sourceExists": "This source already exists",
110118
"installPlugin": "Install Plugin",
111119
"randomPlugins": "🎲 Random Plugins",
120+
"showRandomPlugins": "Show Random Plugins",
121+
"hideRandomPlugins": "Hide Random Plugins",
112122
"sourceSafetyWarning": "Even with the default source, plugin stability and security cannot be fully guaranteed. Please verify carefully before use."
113123
},
114124
"sort": {
@@ -177,7 +187,9 @@
177187
"refreshing": "Refreshing extension list...",
178188
"refreshSuccess": "Extension list refreshed!",
179189
"refreshFailed": "Error occurred while refreshing extension list",
190+
"operationFailed": "Operation failed",
180191
"reloadSuccess": "Reload successful",
192+
"reloadFailed": "Reload failed",
181193
"updateSuccess": "Update successful!",
182194
"addSuccess": "Add successful!",
183195
"saveSuccess": "Save successful!",

dashboard/src/i18n/locales/zh-CN/features/extension.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
"titles": {
1212
"installedAstrBotPlugins": "已安装的 AstrBot 插件"
1313
},
14+
"failedPlugins": {
15+
"title": "加载失败插件({count})",
16+
"hint": "这些插件加载失败,仍可尝试重载或直接卸载。",
17+
"columns": {
18+
"plugin": "插件",
19+
"error": "错误"
20+
}
21+
},
1422
"search": {
1523
"placeholder": "搜索插件...",
1624
"marketPlaceholder": "搜索市场插件..."
@@ -109,6 +117,8 @@
109117
"sourceExists": "该插件源已存在",
110118
"installPlugin": "安装插件",
111119
"randomPlugins": "🎲 随机插件",
120+
"showRandomPlugins": "显示随机插件",
121+
"hideRandomPlugins": "隐藏随机插件",
112122
"sourceSafetyWarning": "即使是默认插件源,我们也不能完全保证插件的稳定性和安全性,使用前请谨慎核查。"
113123
},
114124
"sort": {
@@ -177,7 +187,9 @@
177187
"refreshing": "正在刷新插件列表...",
178188
"refreshSuccess": "插件列表已刷新!",
179189
"refreshFailed": "刷新插件列表时发生错误",
190+
"operationFailed": "操作失败",
180191
"reloadSuccess": "重载成功",
192+
"reloadFailed": "重载失败",
181193
"updateSuccess": "更新成功!",
182194
"addSuccess": "添加成功!",
183195
"saveSuccess": "保存成功!",

dashboard/src/utils/errorUtils.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const INVALID_ERROR_STRINGS = new Set(["[object Object]", "undefined", "null", ""]);
2+
3+
const pickResponseMessage = (responseData) => {
4+
if (typeof responseData === "string") {
5+
return responseData.trim();
6+
}
7+
if (!responseData || typeof responseData !== "object") {
8+
return "";
9+
}
10+
11+
const keys = ["message", "error", "detail", "details", "msg"];
12+
for (const key of keys) {
13+
const value = responseData[key];
14+
if (typeof value === "string" && value.trim()) {
15+
return value.trim();
16+
}
17+
}
18+
return "";
19+
};
20+
21+
export const resolveErrorMessage = (err, fallbackMessage = "") => {
22+
if (typeof err === "string") {
23+
return err.trim() || fallbackMessage;
24+
}
25+
if (typeof err === "number" || typeof err === "boolean") {
26+
return String(err);
27+
}
28+
29+
const fromResponse =
30+
pickResponseMessage(err?.response?.data) ||
31+
(typeof err?.response?.statusText === "string"
32+
? err.response.statusText.trim()
33+
: "");
34+
const fromError =
35+
typeof err?.message === "string" ? err.message.trim() : "";
36+
37+
let fromString = "";
38+
if (typeof err?.toString === "function") {
39+
const value = err.toString().trim();
40+
fromString = INVALID_ERROR_STRINGS.has(value) ? "" : value;
41+
}
42+
43+
return fromResponse || fromError || fromString || fallbackMessage;
44+
};

dashboard/src/views/ExtensionPage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const {
5959
installCompat,
6060
versionCompatibilityDialog,
6161
showUninstallDialog,
62-
pluginToUninstall,
62+
uninstallTarget,
6363
showSourceDialog,
6464
showSourceManagerDialog,
6565
sourceName,

0 commit comments

Comments
 (0)