fix(maturin-hook): handle broken dashboard dist symlink gracefully

The dev branch has astrbot/dashboard/dist as a symlink to
../../dashboard/dist, which is valid in the dev workspace but
becomes a broken symlink when cloned to /opt/astrbot for installation.

Fix the maturin build hook to:
- Remove broken symlinks before creating placeholder directories
- Handle symlink vs directory removal in copy_dashboard_dist()
- Always generate placeholder when dashboard build is skipped or fails
This commit is contained in:
LIghtJUNction
2026-03-25 19:01:56 +08:00
14 changed files with 458 additions and 1009 deletions

View File

@@ -39,6 +39,16 @@ const emit = defineEmits([
const handlePinnedImgError = (e) => {
e.target.src = defaultPluginIcon;
};
const authorDisplay = computed(() => {
const p = props.plugin || {};
if (typeof p.author === 'string' && p.author.trim()) return p.author;
if (Array.isArray(p.authors) && p.authors.length) return p.authors.join(', ');
if (typeof p.author_name === 'string' && p.author_name.trim()) return p.author_name;
if (typeof p.owner === 'string' && p.owner.trim()) return p.owner;
if (p.author && typeof p.author === 'object' && p.author.name) return p.author.name;
return '';
});
</script>
<template>
@@ -65,119 +75,71 @@ const handlePinnedImgError = (e) => {
:src="(typeof plugin.logo === 'string' && plugin.logo.trim()) ? plugin.logo : defaultPluginIcon"
:alt="plugin.name"
@error="handlePinnedImgError"
>
/>
</v-avatar>
</template>
<v-card>
<v-card-text
class="d-flex"
style="gap:8px; padding:12px;"
>
<v-tooltip
location="top"
:text="tm('buttons.viewDocs')"
>
<v-card-title class="d-flex" style="gap:8px; padding:12px; align-items:center;">
<div style="display:flex; align-items:center; gap:8px; min-width:0;">
<v-avatar size="40" class="pinned-avatar" style="width:40px; height:40px;">
<img
:src="(typeof plugin.logo === 'string' && plugin.logo.trim()) ? plugin.logo : defaultPluginIcon"
:alt="plugin.name"
@error="handlePinnedImgError"
/>
</v-avatar>
<div style="min-width:0; overflow:hidden;">
<div style="font-weight:600; font-size:0.95rem; white-space:nowrap; text-overflow:ellipsis; overflow:hidden;">{{ plugin.display_name || plugin.name }}</div>
<div style="font-size:0.8rem; color:var(--v-theme-on-surface); opacity:0.8; white-space:nowrap; text-overflow:ellipsis; overflow:hidden;">{{ authorDisplay || (plugin.author || '') }}</div>
</div>
</div>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="d-flex" style="gap:8px; padding:12px;">
<v-tooltip location="top" :text="tm('buttons.viewDocs')">
<template #activator="{ props: a }">
<v-btn
v-bind="a"
icon
size="small"
variant="tonal"
color="info"
@click.stop="$emit('view-readme', plugin)"
>
<v-btn v-bind="a" icon size="small" variant="tonal" color="info" @click.stop="$emit('view-readme', plugin)">
<v-icon>mdi-book-open-page-variant</v-icon>
</v-btn>
</template>
</v-tooltip>
<v-tooltip
location="top"
:text="tm('card.actions.pluginConfig')"
>
<v-tooltip location="top" :text="tm('card.actions.pluginConfig')">
<template #activator="{ props: a }">
<v-btn
v-bind="a"
icon
size="small"
variant="tonal"
color="primary"
@click.stop="$emit('open-config', plugin.name)"
>
<v-btn v-bind="a" icon size="small" variant="tonal" color="primary" @click.stop="$emit('open-config', plugin.name)">
<v-icon>mdi-cog</v-icon>
</v-btn>
</template>
</v-tooltip>
<v-tooltip
location="top"
:text="tm('card.actions.reloadPlugin')"
>
<v-tooltip location="top" :text="tm('card.actions.reloadPlugin')">
<template #activator="{ props: a }">
<v-btn
v-bind="a"
icon
size="small"
variant="tonal"
color="primary"
@click.stop="$emit('reload', plugin.name)"
>
<v-btn v-bind="a" icon size="small" variant="tonal" color="primary" @click.stop="$emit('reload', plugin.name)">
<v-icon>mdi-refresh</v-icon>
</v-btn>
</template>
</v-tooltip>
<v-tooltip
location="top"
:text="tm('buttons.update')"
>
<v-tooltip location="top" :text="tm('buttons.update')">
<template #activator="{ props: a }">
<v-btn
v-bind="a"
icon
size="small"
variant="tonal"
color="warning"
@click.stop="$emit('update', plugin.name)"
>
<v-btn v-bind="a" icon size="small" variant="tonal" color="warning" @click.stop="$emit('update', plugin.name)">
<v-icon>mdi-update</v-icon>
</v-btn>
</template>
</v-tooltip>
<v-tooltip
location="top"
:text="tm('buttons.viewInfo')"
>
<v-tooltip location="top" :text="tm('buttons.viewInfo')">
<template #activator="{ props: a }">
<v-btn
v-bind="a"
icon
size="small"
variant="tonal"
color="secondary"
@click.stop="$emit('show-info', plugin)"
>
<v-btn v-bind="a" icon size="small" variant="tonal" color="secondary" @click.stop="$emit('show-info', plugin)">
<v-icon>mdi-information</v-icon>
</v-btn>
</template>
</v-tooltip>
<v-tooltip
location="top"
:text="tm('buttons.uninstall')"
>
<v-tooltip location="top" :text="tm('buttons.uninstall')">
<template #activator="{ props: a }">
<v-btn
v-if="!plugin.reserved"
v-bind="a"
icon
size="small"
variant="tonal"
color="error"
@click.stop="$emit('uninstall', plugin.name)"
>
<v-btn v-bind="a" icon size="small" variant="tonal" color="error" @click.stop="$emit('uninstall', plugin.name)" v-if="!plugin.reserved">
<v-icon>mdi-delete</v-icon>
</v-btn>
</template>
@@ -191,13 +153,11 @@ const handlePinnedImgError = (e) => {
size="small"
class="pinned-pin-btn"
:color="isPinned ? 'primary' : 'secondary'"
@click.stop="$emit('toggle-pin', plugin)"
:title="isPinned ? tm('buttons.unpin') : tm('buttons.pin')"
style="position:absolute; top:6px; right:6px; min-width:22px; width:22px; height:22px;"
@click.stop="$emit('toggle-pin', plugin)"
>
<v-icon size="14">
{{ isPinned ? 'mdi-pin' : 'mdi-pin-outline' }}
</v-icon>
<v-icon size="14">{{ isPinned ? 'mdi-pin' : 'mdi-pin-outline' }}</v-icon>
</v-btn>
</div>
</template>

View File

@@ -68,6 +68,17 @@ const astrbotVersionRequirement = computed(() => {
: "";
});
// 作者显示(兼容多种字段名)
const authorDisplay = computed(() => {
const ext = props.extension || {};
if (typeof ext.author === 'string' && ext.author.trim()) return ext.author;
if (Array.isArray(ext.authors) && ext.authors.length) return ext.authors.join(', ');
if (typeof ext.author_name === 'string' && ext.author_name.trim()) return ext.author_name;
if (typeof ext.owner === 'string' && ext.owner.trim()) return ext.owner;
if (ext.author && typeof ext.author === 'object' && ext.author.name) return ext.author.name;
return '';
});
const logoLoadFailed = ref(false);
const logoSrc = computed(() => {
@@ -377,6 +388,10 @@ const viewChangelog = () => {
{{ tag === "danger" ? tm("tags.danger") : tag }}
</v-chip>
<PluginPlatformChip :platforms="supportPlatforms" />
<v-chip v-if="authorDisplay" color="info" label size="small">
<v-icon icon="mdi-account" start></v-icon>
{{ authorDisplay }}
</v-chip>
<v-chip
v-if="astrbotVersionRequirement"
color="secondary"

View File

@@ -1249,7 +1249,7 @@
"description": "API Base URL"
},
"openai_embedding": {
"hint": "OpenAI Embedding automatically appends /v1 at request time."
"hint": "If testing fails, try adding /v1 at the end to be compatible with some OpenAI API versions."
},
"gemini_embedding": {
"hint": "Gemini Embedding does not require manually adding /v1beta."

View File

@@ -1246,7 +1246,7 @@
"description": "Адрес прокси-сервера"
},
"openai_embedding": {
"hint": "OpenAI Embedding автоматически добавляет /v1 при запросе."
"hint": "Если тест не проходит, попробуйте добавить /v1 в конец embedding_api_base для совместимости с некоторыми версиями OpenAI API."
},
"gemini_embedding": {
"hint": "Gemini Embedding не требует ручного добавления /v1beta."

View File

@@ -1251,7 +1251,7 @@
"description": "API Base URL"
},
"openai_embedding": {
"hint": "OpenAI Embedding 会在请求时自动补上 /v1。"
"hint": "如果测试不通过,可以尝试添加 /v1 在末尾以兼容部分 OpenAI API 版本。"
},
"gemini_embedding": {
"hint": "Gemini Embedding 无需手动添加 /v1beta。"

File diff suppressed because it is too large Load Diff