feat: 支持配置 Whisper 模型并优化 API Key 管理

- 添加 download_model.sh 脚本,支持从 Hugging Face 下载 Whisper 语音识别模型
- 在 bot 状态和序列化接口中新增 api_key 字段,支持持久化存储
- 更新前端 i18n 文本,将“新的 API Key”改为“API Key”,明确其为必填项
- 设置 axios 默认携带凭证,确保身份验证
- 将管理面板名称从“Nanobot”更新为“Unisbot”
main
AlanPaine 2026-04-15 17:23:00 +08:00
parent 4c99826863
commit 8e6b4581cd
8 changed files with 87 additions and 8 deletions

View File

@ -424,6 +424,7 @@ def serialize_bot_detail(bot: BotInstance) -> Dict[str, Any]:
"image_tag": bot.image_tag, "image_tag": bot.image_tag,
"llm_provider": runtime["llm_provider"], "llm_provider": runtime["llm_provider"],
"llm_model": runtime["llm_model"], "llm_model": runtime["llm_model"],
"api_key": runtime["api_key"],
"api_base": runtime["api_base"], "api_base": runtime["api_base"],
"temperature": runtime["temperature"], "temperature": runtime["temperature"],
"top_p": runtime["top_p"], "top_p": runtime["top_p"],

74
download_model.sh 100755
View File

@ -0,0 +1,74 @@
#!/usr/bin/env bash
# ==========================================
# Whisper ggml 模型下载脚本
# 下载的模型将存放在 data/model 目录下
# ==========================================
# 确保在项目根目录运行
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET_DIR="${SCRIPT_DIR}/data/model"
# 创建目标目录
mkdir -p "$TARGET_DIR"
# Hugging Face 的 whisper.cpp 模型仓库地址
# 使用 hf-mirror 镜像加速国内下载
BASE_URL="https://hf-mirror.com/ggerganov/whisper.cpp/resolve/main"
echo "=========================================="
echo "请选择要下载的 Whisper 语音识别模型:"
echo "1) tiny (最小,速度最快,约 40MB)"
echo "2) base (基础,速度快,约 74MB)"
echo "3) small (当前默认,平衡,约 240MB)"
echo "4) medium (推荐,效果更好,约 770MB)"
echo "5) large-v3 (最大,效果最好,约 1.5GB)"
echo "=========================================="
read -p "请输入对应的数字 [1-5默认 4]: " choice
MODEL_NAME=""
case $choice in
1) MODEL_NAME="ggml-tiny-q8_0.bin" ;;
2) MODEL_NAME="ggml-base-q8_0.bin" ;;
3) MODEL_NAME="ggml-small-q8_0.bin" ;;
4|"") MODEL_NAME="ggml-medium-q8_0.bin" ;;
5) MODEL_NAME="ggml-large-v3-q8_0.bin" ;;
*)
echo "无效的选择,使用默认选项 4 (medium)"
MODEL_NAME="ggml-medium-q8_0.bin"
;;
esac
echo ""
echo "准备下载模型: $MODEL_NAME"
echo "保存路径: $TARGET_DIR/$MODEL_NAME"
echo ""
# 下载文件
DOWNLOAD_URL="${BASE_URL}/${MODEL_NAME}"
if command -v wget >/dev/null 2>&1; then
echo "使用 wget 下载中..."
wget -c --show-progress "$DOWNLOAD_URL" -O "$TARGET_DIR/$MODEL_NAME"
elif command -v curl >/dev/null 2>&1; then
echo "使用 curl 下载中..."
curl -L -C - -o "$TARGET_DIR/$MODEL_NAME" "$DOWNLOAD_URL"
else
echo "错误:找不到 wget 或 curl 命令,无法下载文件。"
exit 1
fi
if [ $? -eq 0 ]; then
echo ""
echo "✅ 下载成功!"
echo "模型文件已保存到: $TARGET_DIR/$MODEL_NAME"
echo ""
echo "请记得更新 .env.prod 或 backend/.env 文件中的配置:"
echo "STT_MODEL=$MODEL_NAME"
echo "修改后重启后端容器即可生效。"
else
echo ""
echo "❌ 下载失败,请检查网络连接或稍后重试。"
exit 1
fi

View File

@ -1,5 +1,5 @@
export const appZhCn = { export const appZhCn = {
title: 'Nanobot 管理面板', title: 'Unisbot 管理面板',
theme: '主题', theme: '主题',
language: '语言', language: '语言',
dark: '深色', dark: '深色',

View File

@ -69,7 +69,7 @@ export const dashboardEn = {
feedbackSaveFail: 'Failed to save feedback.', feedbackSaveFail: 'Failed to save feedback.',
feedbackMessagePending: 'Message is not synced yet. Please retry in a moment.', feedbackMessagePending: 'Message is not synced yet. Please retry in a moment.',
sendFailMsg: (msg: string) => `Command delivery failed: ${msg}`, sendFailMsg: (msg: string) => `Command delivery failed: ${msg}`,
providerRequired: 'Set provider/model/new API key before testing.', providerRequired: 'Set provider/model/API key before testing.',
connOk: (preview: string) => (preview ? `Connection passed, models: ${preview}` : 'Connection passed'), connOk: (preview: string) => (preview ? `Connection passed, models: ${preview}` : 'Connection passed'),
connFail: (msg: string) => `Failed: ${msg}`, connFail: (msg: string) => `Failed: ${msg}`,
configUpdated: 'Configuration updated (effective after bot restart).', configUpdated: 'Configuration updated (effective after bot restart).',
@ -265,8 +265,8 @@ export const dashboardEn = {
baseImageReadonly: 'Base Image', baseImageReadonly: 'Base Image',
modelName: 'Model Name', modelName: 'Model Name',
modelNamePlaceholder: 'e.g. qwen-plus', modelNamePlaceholder: 'e.g. qwen-plus',
newApiKey: 'New API Key (optional)', newApiKey: 'API Key',
newApiKeyPlaceholder: 'Only updated when filled', newApiKeyPlaceholder: 'Enter API key',
testing: 'Testing...', testing: 'Testing...',
testModelConnection: 'Test model connection', testModelConnection: 'Test model connection',
cancel: 'Cancel', cancel: 'Cancel',

View File

@ -69,7 +69,7 @@ export const dashboardZhCn = {
feedbackSaveFail: '反馈保存失败。', feedbackSaveFail: '反馈保存失败。',
feedbackMessagePending: '消息尚未同步,暂不可反馈。', feedbackMessagePending: '消息尚未同步,暂不可反馈。',
sendFailMsg: (msg: string) => `指令发送失败:${msg}`, sendFailMsg: (msg: string) => `指令发送失败:${msg}`,
providerRequired: '请填写 Provider、模型和 API Key 后再测试。', providerRequired: '请填写 Provider、模型和 API Key 后再测试。',
connOk: (preview: string) => (preview ? `连接成功,模型: ${preview}` : '连接成功'), connOk: (preview: string) => (preview ? `连接成功,模型: ${preview}` : '连接成功'),
connFail: (msg: string) => `连接失败: ${msg}`, connFail: (msg: string) => `连接失败: ${msg}`,
configUpdated: '配置已更新(重启 Bot 后生效)。', configUpdated: '配置已更新(重启 Bot 后生效)。',
@ -265,8 +265,8 @@ export const dashboardZhCn = {
baseImageReadonly: '基础镜像', baseImageReadonly: '基础镜像',
modelName: '模型名称', modelName: '模型名称',
modelNamePlaceholder: '如 qwen-plus', modelNamePlaceholder: '如 qwen-plus',
newApiKey: '新的 API Key(留空不更新)', newApiKey: 'API Key',
newApiKeyPlaceholder: '输入新 Key 才会更新', newApiKeyPlaceholder: '输入 API Key',
testing: '测试中...', testing: '测试中...',
testModelConnection: '测试模型连接', testModelConnection: '测试模型连接',
cancel: '取消', cancel: '取消',

View File

@ -1,11 +1,14 @@
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import axios from 'axios'
import './index.css' import './index.css'
import App from './App.tsx' import App from './App.tsx'
import { LucentPromptProvider } from './components/lucent/LucentPromptProvider.tsx' import { LucentPromptProvider } from './components/lucent/LucentPromptProvider.tsx'
import { setupBotAccessAuth } from './utils/botAccess.ts' import { setupBotAccessAuth } from './utils/botAccess.ts'
import { setupPanelAccessAuth } from './utils/panelAccess.ts' import { setupPanelAccessAuth } from './utils/panelAccess.ts'
axios.defaults.withCredentials = true
setupPanelAccessAuth(); setupPanelAccessAuth();
setupBotAccessAuth(); setupBotAccessAuth();

View File

@ -97,7 +97,7 @@ export function useDashboardBotEditor({
llm_provider: provider, llm_provider: provider,
llm_model: bot.llm_model || '', llm_model: bot.llm_model || '',
image_tag: bot.image_tag || '', image_tag: bot.image_tag || '',
api_key: '', api_key: bot.api_key || '',
api_base: bot.api_base || '', api_base: bot.api_base || '',
temperature: clampTemperature(bot.temperature ?? 0.2), temperature: clampTemperature(bot.temperature ?? 0.2),
top_p: bot.top_p ?? 1, top_p: bot.top_p ?? 1,

View File

@ -28,6 +28,7 @@ export interface BotState {
image_tag?: string; image_tag?: string;
llm_provider?: string; llm_provider?: string;
llm_model?: string; llm_model?: string;
api_key?: string;
api_base?: string; api_base?: string;
temperature?: number; temperature?: number;
top_p?: number; top_p?: number;