Skip to content

feat(dashboard): embed Dashboard and integrate the desktop development workflow#153

Merged
Fridemn merged 10 commits into
mainfrom
refactor-embed-dashboard
Jul 14, 2026
Merged

feat(dashboard): embed Dashboard and integrate the desktop development workflow#153
Fridemn merged 10 commits into
mainfrom
refactor-embed-dashboard

Conversation

@Fridemn

@Fridemn Fridemn commented Jul 13, 2026

Copy link
Copy Markdown
Member

本 PR 将 AstrBot Dashboard 从 AstrBot 主仓库迁移至 AstrBot Desktop 仓库独立维护,解决开发和构建过程中依赖外部 Dashboard 源码、开发环境启动步骤分散,以及前后端启动时序不稳定的问题。

迁移后,开发模式会自动准备 AstrBot 后端源码、启动本地 Dashboard Vite 服务和 Tauri 应用;正式构建则直接使用当前仓库中的 Dashboard,并继续使用 resources/backend 作为安装包后端资源。

Summary / 改动概述

  • 将完整 Dashboard 源码迁移至根目录 dashboard/
  • Dashboard 构建不再依赖 AstrBot 主仓库:
    • 使用本地 OpenAPI 文件。
    • 调整 T2I Shiki Runtime 的生成和引用路径。
    • 移除对 AstrBot 仓库内构建插件及文件路径的依赖。
    • 调整桌面端版本检查和更新逻辑。
  • 调整根目录开发和构建命令:
    • 新增 pnpm run install:dashboard,通过 Dashboard 自身的 lockfile 安装前端依赖。
    • 新增 pnpm run dev:dashboardpnpm run typecheck:dashboard,分别用于启动 Dashboard Vite 服务和执行类型检查。
    • pnpm run dev 由直接执行 cargo tauri dev 改为通过 scripts/run-tauri.mjs dev 启动;命令会读取根目录 .env、准备开发后端源码,再交由 Tauri 自动启动 Dashboard 和桌面程序。
    • pnpm run build 由统一入口 scripts/run-tauri.mjs build 执行;Tauri 的 beforeBuildCommand 会调用 pnpm run prepare:resources,依次构建本仓库 Dashboard 并准备 resources/backend
    • 新增 prepare:webuiprepare:backendprepare:resources,拆分并统一 WebUI、后端及完整打包资源的准备流程。
    • 本地未配置 TAURI_SIGNING_PRIVATE_KEY 时,pnpm run build 会自动关闭 updater artifacts,仍可生成 MSI/NSIS 安装包。
  • 集成 Dashboard 开发服务器:
    • pnpm run dev 自动启动 dashboard/ 下的 Vite 服务。
    • 开发服务器固定使用 http://localhost:1420
    • 前端等待桌面后端就绪后再挂载应用。
  • 完善开发后端启动流程:
    • 默认从远端同步 AstrBot 源码至 vendor/AstrBot
    • 支持通过根目录 .env 配置远端仓库、分支或本地源码路径。
    • 开发模式通过源码启动后端,不使用 resources/backend
  • 调整正式构建流程:
    • prepare:webui 构建当前仓库中的 Dashboard。
    • resources/backend 继续作为正式安装包的内置后端。
    • 未配置 TAURI_SIGNING_PRIVATE_KEY 时关闭 updater artifacts,允许本地生成普通 MSI/NSIS 安装包。
  • 修复 Windows 关闭回调函数直接转换为整数产生的 Rust 编译警告。
  • 更新根目录脚本、Makefile、环境变量示例和相关测试。

主要涉及 dashboard/scripts/src-tauri/package.json.env.exampleMakefile

Verification / 验证方式

已完成以下本地验证:

  • Dashboard 依赖安装及 TypeScript 类型检查通过。
  • Dashboard Vite 开发服务器成功启动:
VITE v6.4.1 ready
Local: http://localhost:1420/
  • pnpm run dev 能够同步 vendor/AstrBot 后端源码、启动 Dashboard Vite 服务、编译并启动 Tauri,以及通过源码启动 AstrBot 后端。
  • Node.js 脚本及 Dashboard 后端就绪相关测试通过,共 43 项。
  • Rust cargo check 通过。
  • 后端启动计划测试通过:
test result: ok. 3 passed; 0 failed
  • 运行路径及后端就绪检测测试通过:
test result: ok. 2 passed; 0 failed
test result: ok. 2 passed; 0 failed
  • Dashboard 资源准备在无有效 ASTRBOT_SOURCE_DIR 的情况下仍可成功,确认前端构建已与 AstrBot 源码目录解耦。
  • Windows 构建已成功生成以下安装包:
AstrBot_4.26.5_x64_zh-CN.msi
AstrBot_4.26.5_x64-setup.exe

此前构建在生成安装包后因缺少 updater 私钥退出;本 PR 已增加未配置签名私钥时关闭 updater artifacts 的本地构建处理。正式发布环境配置签名私钥后生成更新签名产物功能待测试。

image Snipaste_2026-07-13_20-15-43

Checklist / 检查清单

  • This change is not a breaking change. / 此改动不是破坏性变更。
  • I have verified the change locally (and provided logs/screenshots above). / 我已在本地验证并在上方提供日志/截图。
  • If workflows are changed, I attached at least one related Actions run link. / 如果修改了工作流,我已附上至少一个相关 Actions 运行链接。
  • If dependencies are updated, lockfiles are updated accordingly (src-tauri/Cargo.lock, lockfiles under changed package dirs). / 如果引入或升级依赖,已同步更新相关 lock 文件(如 src-tauri/Cargo.lock、对应包目录中的 lock 文件)。
  • This PR does not include malicious code. / 此 PR 不包含恶意代码。

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, we are unable to review this pull request

The GitHub API does not allow us to fetch diffs exceeding 300 files, and this pull request has 434

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive Vue 3 and Vite-based dashboard for AstrBot Desktop, featuring a rich chat UI, standalone chat capabilities, live voice mode, and automated build scripts for font subsetting and syntax highlighting. The review identified several critical issues that need to be addressed: a potential crash in the API error helper when handling non-object errors, a hanging promise in the confirmation dialog when dismissed externally, and broken SSE stream parsing on CRLF line endings in both the thread and standalone chat views. Additionally, loading critical libraries via CDN poses offline usability risks for the desktop app, and improvements are needed for user-facing error handling during message dispatch and WebSocket timeout cleanup.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread dashboard/src/api/v1.ts
Comment on lines +213 to +218
export function isLegacyFallbackError(error: unknown): boolean {
const axiosError = error as {
response?: { status?: number; data?: { message?: string } | string };
message?: string;
};
if (axiosError.response?.status === 404) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

isLegacyFallbackError 函数中,直接对 error 进行类型断言并访问 axiosError.response。如果 errornullundefined 或非对象类型(例如手动的 throw 'error'),访问 axiosError.response 将会抛出 TypeError: Cannot read properties of null,导致错误处理流程崩溃。建议在访问属性前先进行非空及对象类型检查。

export function isLegacyFallbackError(error: unknown): boolean {\n  if (!error || typeof error !== 'object') {\n    return false;\n  }\n  const axiosError = error as {\n    response?: { status?: number; data?: { message?: string } | string };\n    message?: string;\n  };\n  if (axiosError.response?.status === 404) {

@@ -0,0 +1,47 @@
<template>
<v-dialog v-model="isOpen" max-width="400">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

v-dialog 没有设置 persistent 属性,这意味着用户可以通过点击对话框外部或按下 ESC 键来关闭它。当发生这种情况时,v-model 会将 isOpen 设为 false,但 resolvePromise 并不会被调用,导致调用 open() 的 Promise 永远处于 pending 状态(挂起),从而阻塞后续代码的执行。建议为 v-dialog 添加 persistent 属性,或者监听 isOpen 的变化,在非按钮触发关闭时也正确 resolve Promise。

  <v-dialog v-model=\"isOpen\" max-width=\"400\" persistent>

Comment on lines +213 to +221
buffer += decoder.decode(value, { stream: true });
const chunks = buffer.split("\n\n");
buffer = chunks.pop() || "";
for (const chunk of chunks) {
const data = chunk
.split("\n")
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).trimStart())
.join("\n");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

readSseStream 中,使用 buffer.split('\n\n')chunk.split('\n') 来解析 SSE 流。如果后端服务器使用标准的 CRLF(\r\n)作为换行符,\r\n\r\n 中并不包含 \n\n,这会导致 split('\n\n') 无法正确分割数据块,使得整个流的数据都积压在 buffer 中直到连接关闭,从而破坏了流式输出(Streaming)的效果。此外,使用 \n 分割行会使每行末尾残留 \r 字符,导致 JSON.parse 失败。建议使用正则表达式 \r?\n\r?\n\r?\n 进行分割,并使用 .trim() 清除首尾空白字符。

    const chunks = buffer.split(/\\r?\\n\\r?\\n/);\n    buffer = chunks.pop() || \"\";\n    for (const chunk of chunks) {\n      const data = chunk\n        .split(/\\r?\\n/)\n        .filter((line) => line.startsWith(\"data:\"))\n        .map((line) => line.slice(5).trim())\n        .join(\"\\n\");

Comment on lines +213 to +221
import ToolCallItem from "@/components/chat/message_list_comps/ToolCallItem.vue";
import ThemeAwareMarkdownCodeBlock from "@/components/shared/ThemeAwareMarkdownCodeBlock.vue";
import {
attachmentName,
attachmentPresentation,
} from "@/components/chat/attachmentPresentation";
import { useMediaHandling } from "@/composables/useMediaHandling";
import {
displayParts as displayMessageParts,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

ThreadPanel.vue 类似,这里的 readSseStream 同样存在使用 \n\n\n 分割 SSE 流的问题。如果后端使用标准的 CRLF(\r\n)换行符,会导致流式输出失效,且 JSON.parse 会因残留的 \r 字符而报错。建议使用正则表达式进行分割并使用 .trim() 清理空白字符。

    const chunks = buffer.split(/\\r?\\n\\r?\\n/);\n    buffer = chunks.pop() || \"\";\n    for (const chunk of chunks) {\n      const data = chunk\n        .split(/\\r?\\n/)\n        .filter((line) => line.startsWith(\"data:\"))\n        .map((line) => line.slice(5).trim())\n        .join(\"\\n\");

Comment thread dashboard/index.html
Comment on lines +15 to +16
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/bundle.min.js"></script>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在桌面端(Tauri)应用中,通过 CDN 引入外部 JS 依赖(如 onnxruntime-webvad-web)会导致应用在离线状态下无法正常工作,且存在潜在的安全风险(如 CSP 限制或 CDN 劫持)。建议将这些依赖通过 npm 安装并打包到本地,或者将对应的静态文件下载到本地 public 目录中进行引用。

Comment on lines +1313 to +1315
} catch (error) {
console.error("Failed to send message:", error);
} finally {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

sendCurrentMessage 发生异常时,仅在控制台打印了错误日志 console.error,用户在界面上无法感知发送失败的原因(发送按钮会直接恢复可用状态,但消息未发送)。建议在 catch 分支中通过 toast.error 向用户展示友好的错误提示。

  } catch (error) {\n    console.error(\"Failed to send message:\", error);\n    toast.error(error instanceof Error ? error.message : \"发送消息失败\");\n  } finally {

Comment on lines +304 to +309
// 超时处理
setTimeout(() => {
if (ws?.readyState !== WebSocket.OPEN) {
reject(new Error('WebSocket 连接超时'));
}
}, 5000);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

connectWebSocket 的超时处理中,如果连接超时(超过 5 秒),Promise 会被 reject,但此时并没有关闭正在尝试连接的 WebSocket 实例(ws)。这会导致连接在后台继续尝试,若后续连接成功,仍会触发 onopenonmessage 等回调,可能导致非预期的音频播放或状态混乱。建议在超时触发时,显式调用 ws.close() 并将 ws 置为 null,同时在连接成功或失败时清除该定时器。

        const timeoutId = setTimeout(() => {\n            if (ws?.readyState !== WebSocket.OPEN) {\n                if (ws) {\n                    ws.close();\n                    ws = null;\n                }\n                reject(new Error('WebSocket 连接超时'));\n            }\n        }, 5000);

@zouyonghe

Copy link
Copy Markdown
Member

整体方向没有问题,Dashboard 内嵌、开发启动流程和资源准备流程也已基本验证通过。补充以下 review findings,建议在合并前处理:

  1. [Non-blocking] Dashboard 构建存在缺失资源引用

    dashboard/src/utils/platformUtils.js 引用了:

    new URL('@/assets/images/platform_logos/vocechat.png', import.meta.url).href

    但 PR 中没有提交 dashboard/src/assets/images/platform_logos/vocechat.png,AstrBot 主仓库当前也没有对应资源。Vite 构建会提示该 URL 无法在构建时解析,VoceChat 平台图标在运行时可能无法加载。建议补充资源,或暂时移除该图片引用让它使用默认图标。这个问题不是合并阻塞项,但建议修复构建 warning。

  2. [Should fix] 无效的 MDI fallback 图标

    dashboard/src/components/shared/PluginPlatformChip.vue 使用了:

    <v-icon v-else icon="mdi-platform" />

    mdi-platform 不是有效的 Material Design Icons 名称,构建时也报告该图标未包含在生成的 MDI 子集中。建议替换为有效的通用图标,例如 mdi-puzzle-outline,避免未知平台 fallback 显示为空白。

  3. [Should fix] 本地 Rust 检查依赖预生成资源

    在干净 checkout 中直接执行 cargo check --manifest-path src-tauri/Cargo.toml 会因 resources/backend 不存在而失败:

    resource path `../resources/backend` doesn't exist
    

    CI 通过预先创建 resources/webuiresources/backend placeholder 绕过,但本地开发与 CI 行为不一致。建议提供统一的资源初始化命令,或让 build.rs 在检查/开发场景允许资源目录缺失,并在文档中明确前置步骤。

  4. [Should fix] Node.js 最低版本要求需要在入口处明确校验

    scripts/project-env.mjs.env 存在时依赖 process.loadEnvFile,实际要求 Node.js 20.12+。如果本地 Node 版本较低,make devmake build 或资源准备会直接失败。建议在 make doctorrun-tauri.mjs 中提前检查 Node 版本,并在开发文档中明确最低版本要求,避免用户只看到不直观的 process.loadEnvFile is not a function

@zouyonghe

Copy link
Copy Markdown
Member

Latest Review

已复查当前最新 head 9983eb4 及新增提交。

Previous Findings

上一轮提出的 4 项问题已处理:

  • VoceChat 缺失图片引用已移除。
  • mdi-platform 已替换为有效的 mdi-puzzle-outline
  • src-tauri/build.rs 已能在干净 checkout 中初始化缺失的 Tauri resource 目录。
  • Node.js 最低版本检查已加入脚本入口、Makefile 和开发文档。

Verification

  • GitHub CI:Rust、Node/Python scripts、workflow lint 全部通过。
  • cargo check --manifest-path src-tauri/Cargo.toml --locked:通过。
  • pnpm test:prepare-resources130 passed, 0 failed
  • Dashboard vue-tsc --noEmit:通过。
  • Dashboard production build:通过。

Non-blocking Observation

Dashboard 构建仍输出一条 warning:

1 icons not found in MDI CSS: mdi-subset

这看起来来自 MDI 子集生成器扫描自身生成目录/名称,不影响当前构建结果或运行时 fallback 图标。建议后续让扫描器排除 src/assets/mdi-subset/ 生成目录,避免每次构建产生误导性 warning。

当前未发现新的阻塞问题,可以继续合并流程。

@Fridemn
Fridemn merged commit 0c55e5b into main Jul 14, 2026
3 checks passed
@Fridemn
Fridemn deleted the refactor-embed-dashboard branch July 14, 2026 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants