feat: enhance onboarding steps to include computer access configuration and related help information

This commit is contained in:
Soulter
2026-04-10 19:15:07 +08:00
parent 157749504a
commit 31846cbba3
4 changed files with 219 additions and 34 deletions

View File

@@ -12,11 +12,21 @@
"onboard": {
"title": "Quick Onboarding",
"subtitle": "Complete initialization directly on the welcome page.",
"step1Title": "Configure Platform Bot",
"step1Desc": "Connect AstrBot to IM platforms like QQ, Lark, Slack, Telegram, etc.",
"step2Title": "Configure AI Model",
"step2Desc": "Configure AI models for AstrBot.",
"step1Title": "Configure AI Model",
"step1Desc": "Configure AI models for AstrBot.",
"step2Title": "Configure Platform Bot",
"step2Desc": "Connect AstrBot to IM platforms like QQ, Lark, Slack, Telegram, etc.",
"step3Title": "Allow Agent to Use the Computer",
"step3Desc": "Set whether the Agent can access and use this computer.",
"step3HelpTitle": "Access Details",
"step3HelpItem1": "The Agent can access and use the workspace directory, which is isolated for each user, as well as Skills to handle more complex tasks.",
"step3HelpItem2": "For AstrBot administrators, the Agent is additionally allowed to execute shell commands, run Python code, and access all local directories on this machine.",
"step3HelpClose": "Close",
"step3SelectLabel": "Computer Access",
"step3Allow": "Allow",
"step3Deny": "Disallow",
"configure": "Configure",
"save": "Save",
"skip": "Skip",
"pending": "Pending",
"completed": "Completed",
@@ -24,7 +34,10 @@
"platformLoadFailed": "Failed to load platform configuration",
"providerLoadFailed": "Failed to load provider configuration",
"providerUpdateFailed": "Failed to update default chat provider in config file \"default\"",
"providerDefaultUpdated": "Default chat provider in config file \"default\" has been set to {id}"
"providerDefaultUpdated": "Default chat provider in config file \"default\" has been set to {id}",
"computerAccessUpdateFailed": "Failed to update Agent computer access",
"computerAccessAllowed": "Agent is now allowed to access and use the computer",
"computerAccessDenied": "Agent is no longer allowed to access and use the computer"
},
"resources": {
"title": "Resources",

View File

@@ -12,11 +12,21 @@
"onboard": {
"title": "Быстрый старт",
"subtitle": "Вы можете выполнить первичную настройку прямо здесь.",
"step1Title": "Настройка платформ",
"step1Desc": "Подключите AstrBot к QQ, Lark, WeChat, Telegram и другим мессенджерам.",
"step2Title": "Настройка AI моделей",
"step2Desc": "Выберите и настройте AI провайдеров для AstrBot.",
"step1Title": "Настройка AI моделей",
"step1Desc": "Выберите и настройте AI провайдеров для AstrBot.",
"step2Title": "Настройка платформ",
"step2Desc": "Подключите AstrBot к QQ, Lark, WeChat, Telegram и другим мессенджерам.",
"step3Title": "Разрешить Agent использовать компьютер",
"step3Desc": "Укажите, может ли Agent получать доступ к этому компьютеру и использовать его.",
"step3HelpTitle": "Сведения о доступе",
"step3HelpItem1": "Agent сможет получать доступ к каталогу рабочего пространства и использовать его, при этом рабочие каталоги разных пользователей изолированы друг от друга, а также использовать Skills для выполнения более сложных задач.",
"step3HelpItem2": "Для администраторов AstrBot Agent дополнительно сможет выполнять shell-команды, запускать Python-код и получать доступ ко всем локальным каталогам на этом компьютере.",
"step3HelpClose": "Закрыть",
"step3SelectLabel": "Доступ к компьютеру",
"step3Allow": "Разрешить",
"step3Deny": "Запретить",
"configure": "Настроить",
"save": "Сохранить",
"skip": "Пропустить",
"pending": "Ожидает",
"completed": "Готово",
@@ -24,7 +34,10 @@
"platformLoadFailed": "Ошибка загрузки конфигурации платформ",
"providerLoadFailed": "Ошибка загрузки конфигурации провайдеров",
"providerUpdateFailed": "Ошибка обновления провайдера по умолчанию в файле default",
"providerDefaultUpdated": "Провайдер {id} установлен по умолчанию в файле default"
"providerDefaultUpdated": "Провайдер {id} установлен по умолчанию в файле default",
"computerAccessUpdateFailed": "Не удалось обновить доступ Agent к компьютеру",
"computerAccessAllowed": "Agent теперь может получать доступ к компьютеру и использовать его",
"computerAccessDenied": "Agent больше не может получать доступ к компьютеру и использовать его"
},
"resources": {
"title": "Ресурсы",
@@ -34,4 +47,4 @@
"afdianTitle": "Afdian",
"afdianDesc": "Поддержите команду AstrBot через Afdian."
}
}
}

View File

@@ -12,11 +12,21 @@
"onboard": {
"title": "快速引导",
"subtitle": "欢迎页可直接完成初始化。",
"step1Title": "配置平台机器人",
"step1Desc": " AstrBot 连接到 QQ、飞书、企业微信、Telegram 等 IM 平台。",
"step2Title": "配置 AI 模型",
"step2Desc": " AstrBot 配置 AI 模型。",
"step1Title": "配置 AI 模型",
"step1Desc": " AstrBot 配置 AI 模型。",
"step2Title": "配置平台机器人",
"step2Desc": " AstrBot 连接到 QQ、飞书、企业微信、Telegram 等 IM 平台。",
"step3Title": "允许 Agent 使用电脑",
"step3Desc": "设置 Agent 是否可以访问和使用当前电脑。",
"step3HelpTitle": "允许 Agent 使用电脑",
"step3HelpItem1": "Agent 将可以访问和使用工作区目录(每个用户的工作区目录相互独立)以及 Skills 来执行更多复杂任务。",
"step3HelpItem2": "对于 AstrBot 管理员,会额外允许 Agent 执行 Shell 命令、Python 代码,以及访问本机的所有目录。",
"step3HelpClose": "关闭",
"step3SelectLabel": "电脑访问权限",
"step3Allow": "允许",
"step3Deny": "不允许",
"configure": "去配置",
"save": "保存",
"skip": "跳过",
"pending": "待处理",
"completed": "已完成",
@@ -24,7 +34,10 @@
"platformLoadFailed": "加载平台配置失败",
"providerLoadFailed": "加载提供商配置失败",
"providerUpdateFailed": "更新 default 配置文件默认对话提供商失败",
"providerDefaultUpdated": "已将 default 配置文件的默认对话提供商设置为 {id}"
"providerDefaultUpdated": "已将 default 配置文件的默认对话提供商设置为 {id}",
"computerAccessUpdateFailed": "更新 Agent 电脑访问权限失败",
"computerAccessAllowed": "已允许 Agent 访问和使用电脑",
"computerAccessDenied": "已禁止 Agent 访问和使用电脑"
},
"resources": {
"title": "相关资源",

View File

@@ -20,11 +20,28 @@
</div>
<v-timeline align="start" side="end" density="compact" class="welcome-timeline" truncate-line="both">
<v-timeline-item :dot-color="platformStepState === 'completed' ? 'success' : 'primary'"
:icon="platformStepState === 'completed' ? 'mdi-check' : 'mdi-numeric-1'" fill-dot size="small">
<v-timeline-item :dot-color="providerStepState === 'completed' ? 'success' : 'primary'"
icon="mdi-numeric-1" fill-dot size="small">
<div class="pl-2">
<div class="text-h6 font-weight-bold mb-1">{{ tm('onboard.step1Title') }}</div>
<p class="text-body-2 text-medium-emphasis mb-3">{{ tm('onboard.step1Desc') }}</p>
<div class="d-flex align-center">
<v-btn color="primary" variant="flat" rounded="pill" class="px-6" @click="openProviderDialog">
{{ tm('onboard.configure') }}
</v-btn>
<div v-if="providerStepState === 'completed'"
class="text-success d-flex align-center text-body-2 font-weight-medium ml-3">
{{ tm('onboard.completed') }}
</div>
</div>
</div>
</v-timeline-item>
<v-timeline-item :dot-color="platformStepState === 'completed' ? 'success' : 'primary'"
icon="mdi-numeric-2" fill-dot size="small">
<div class="pl-2">
<div class="text-h6 font-weight-bold mb-1">{{ tm('onboard.step2Title') }}</div>
<p class="text-body-2 text-medium-emphasis mb-3">{{ tm('onboard.step2Desc') }}</p>
<div class="d-flex align-center">
<v-btn color="primary" variant="flat" rounded="pill" class="px-6" :loading="loadingPlatformDialog"
@click="openPlatformDialog">
@@ -38,22 +55,37 @@
</div>
</v-timeline-item>
<v-timeline-item :dot-color="providerStepState === 'completed' ? 'success' : 'primary'"
:icon="providerStepState === 'completed' ? 'mdi-check' : 'mdi-numeric-2'" fill-dot size="small">
<v-timeline-item :dot-color="computerAccessStepState === 'completed' ? 'success' : 'primary'"
icon="mdi-numeric-3" fill-dot size="small">
<div class="pl-2">
<div class="text-h6 font-weight-bold mb-1"
:class="{ 'text-medium-emphasis': platformStepState !== 'completed' }">{{ tm('onboard.step2Title')
}}
</div>
<p class="text-body-2 text-medium-emphasis mb-3">{{ tm('onboard.step2Desc') }}</p>
<div class="d-flex align-center">
<v-btn color="primary" variant="flat" rounded="pill" class="px-6" @click="openProviderDialog">
{{ tm('onboard.configure') }}
<div class="d-flex align-center mb-1">
<div class="text-h6 font-weight-bold">{{ tm('onboard.step3Title') }}</div>
<v-btn
icon
variant="text"
density="comfortable"
size="small"
class="ml-1"
@click="showComputerAccessHelpDialog = true"
>
<span class="text-body-2 font-weight-bold">?</span>
</v-btn>
<div v-if="providerStepState === 'completed'"
class="text-success d-flex align-center text-body-2 font-weight-medium ml-3">
{{ tm('onboard.completed') }}
</div>
</div>
<p class="text-body-2 text-medium-emphasis mb-3">{{ tm('onboard.step3Desc') }}</p>
<div class="d-flex flex-wrap align-center ga-3">
<v-select
v-model="computerAccessRuntime"
:items="computerAccessOptions"
item-title="title"
item-value="value"
:label="tm('onboard.step3SelectLabel')"
:loading="savingComputerAccess"
:disabled="savingComputerAccess"
hide-details
density="comfortable"
variant="outlined"
class="computer-access-select"
/>
</div>
</div>
</v-timeline-item>
@@ -136,6 +168,25 @@
<AddNewPlatform v-model:show="showAddPlatformDialog" :metadata="platformMetadata" :config_data="platformConfigData"
@refresh-config="loadPlatformConfigBase" />
<ProviderConfigDialog v-model="showProviderDialog" />
<v-dialog v-model="showComputerAccessHelpDialog" max-width="640">
<v-card>
<v-card-title class="text-h3 font-weight-bold pa-4">
{{ tm('onboard.step3HelpTitle') }}
</v-card-title>
<v-card-text>
<ol class="computer-access-help-list">
<li>{{ tm('onboard.step3HelpItem1') }}</li>
<li>{{ tm('onboard.step3HelpItem2') }}</li>
</ol>
</v-card-text>
<v-card-actions class="px-6 pb-4">
<v-spacer />
<v-btn color="primary" variant="text" @click="showComputerAccessHelpDialog = false">
{{ tm('onboard.step3HelpClose') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
@@ -151,6 +202,7 @@ import 'markstream-vue/index.css';
import 'highlight.js/styles/github.css';
type StepState = 'pending' | 'completed' | 'skipped';
type ComputerAccessRuntime = 'local' | 'none';
const { tm } = useModuleI18n('features/welcome');
const { locale } = useI18n();
@@ -158,6 +210,7 @@ const { success: showSuccess, error: showError } = useToast();
const showAddPlatformDialog = ref(false);
const showProviderDialog = ref(false);
const showComputerAccessHelpDialog = ref(false);
const loadingPlatformDialog = ref(false);
const platformMetadata = ref<Record<string, any>>({});
@@ -167,6 +220,10 @@ const providerCountBeforeOpen = ref(0);
const platformStepState = ref<StepState>('pending');
const providerStepState = ref<StepState>('pending');
const computerAccessStepState = ref<StepState>('pending');
const computerAccessRuntime = ref<ComputerAccessRuntime>('none');
const savedComputerAccessRuntime = ref<ComputerAccessRuntime>('none');
const savingComputerAccess = ref(false);
const welcomeAnnouncementRaw = ref<unknown>(null);
function resolveWelcomeAnnouncement(raw: unknown, currentLocale: string) {
@@ -276,6 +333,11 @@ async function loadPlatformConfigBase() {
platformConfigData.value = res.data.data.config || {};
}
async function fetchDefaultConfig() {
const res = await axios.get('/api/config/abconf', { params: { id: 'default' } });
return res.data?.data?.config || {};
}
function getChatProvidersFromTemplatePayload(payload: any) {
const providers = payload?.providers || [];
const sources = payload?.provider_sources || [];
@@ -315,8 +377,7 @@ async function syncDefaultConfigProviderIfNeeded() {
const targetProviderId = pickDefaultProviderId(providers);
if (!targetProviderId) return;
const configRes = await axios.get('/api/config/abconf', { params: { id: 'default' } });
const configData = configRes.data?.data?.config || {};
const configData = await fetchDefaultConfig();
if (!configData.provider_settings) {
configData.provider_settings = {};
}
@@ -336,6 +397,62 @@ async function syncDefaultConfigProviderIfNeeded() {
showSuccess(tm('onboard.providerDefaultUpdated', { id: targetProviderId }));
}
function normalizeComputerAccessRuntime(runtime: unknown): ComputerAccessRuntime {
return runtime === 'local' || runtime === 'sandbox' ? 'local' : 'none';
}
function syncComputerAccessRuntime(configData: any) {
const providerSettings = configData?.provider_settings || {};
const currentRuntime = providerSettings?.computer_use_runtime;
const normalizedRuntime = normalizeComputerAccessRuntime(currentRuntime);
computerAccessRuntime.value = normalizedRuntime;
savedComputerAccessRuntime.value = normalizedRuntime;
computerAccessStepState.value =
currentRuntime === 'local' || currentRuntime === 'none' || currentRuntime === 'sandbox'
? 'completed'
: 'pending';
}
const computerAccessOptions = computed(() => [
{ title: tm('onboard.step3Allow'), value: 'local' },
{ title: tm('onboard.step3Deny'), value: 'none' }
]);
async function saveComputerAccessRuntime() {
savingComputerAccess.value = true;
try {
const configData = await fetchDefaultConfig();
if (!configData.provider_settings) {
configData.provider_settings = {};
}
configData.provider_settings.computer_use_runtime = computerAccessRuntime.value;
const updateRes = await axios.post('/api/config/astrbot/update', {
conf_id: 'default',
config: configData
});
if (updateRes.data.status !== 'ok') {
throw new Error(updateRes.data.message || tm('onboard.computerAccessUpdateFailed'));
}
savedComputerAccessRuntime.value = computerAccessRuntime.value;
computerAccessStepState.value = 'completed';
showSuccess(
tm(
computerAccessRuntime.value === 'local'
? 'onboard.computerAccessAllowed'
: 'onboard.computerAccessDenied'
)
);
} catch (err: any) {
showError(err?.response?.data?.message || err?.message || tm('onboard.computerAccessUpdateFailed'));
} finally {
savingComputerAccess.value = false;
}
}
async function loadWelcomeAnnouncement() {
try {
const res = await axios.get('https://cloud.astrbot.app/api/v1/announcement');
@@ -366,6 +483,13 @@ onMounted(async () => {
} catch (e) {
console.error(e);
}
try {
const defaultConfig = await fetchDefaultConfig();
syncComputerAccessRuntime(defaultConfig);
} catch (e) {
console.error(e);
}
});
async function openPlatformDialog() {
@@ -416,6 +540,18 @@ watch(showProviderDialog, async (visible, wasVisible) => {
showError(err?.response?.data?.message || err?.message || tm('onboard.providerUpdateFailed'));
}
});
watch(computerAccessRuntime, async (value, oldValue) => {
if (value === oldValue) return;
if (value === savedComputerAccessRuntime.value) return;
if (savingComputerAccess.value) return;
try {
await saveComputerAccessRuntime();
} catch {
computerAccessRuntime.value = savedComputerAccessRuntime.value;
}
});
</script>
<style scoped>
@@ -430,4 +566,14 @@ watch(showProviderDialog, async (visible, wasVisible) => {
.welcome-announcement-markdown {
line-height: 1.7;
}
.computer-access-select {
max-width: 240px;
min-width: 220px;
}
.computer-access-help-list {
margin: 0;
padding-left: 1.25rem;
}
</style>