+
+
-
+
+
跨域 CORS 未配置
+ 同源可用,跨域请求会被 403。在「设置」中配置允许的来源。
-
跨域 CORS
-
- 控制哪些前端域名能跨域调
+
+
-
- /v1/audio/speech。同源始终放行。
+
+
- {{ pageTitle }}
+{{ pageSub }}
-
-
- 勾上后下面白名单失效。仅测试用
+
+
-
-
-
-
- 当前: {{ corsCurrentLabel }}
- {{ corsErr }}
- ✓ 已保存,跨域请求从下个请求开始按新配置
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
- ⚠
-
-
-
+
- 跨域 CORS 未配置
- 同源可用,跨域会 403。
+
+ 设置 → 跨域 CORS 配。
+
-
-
+
+
+ 运行模式
+
+
+ {{ overview.installed ? '已就绪' : '待配置' }}
+
+
+ {{ overview.mode || '—' }}
+
+
+ 音色
+ {{ overview.voice_count ?? 0 }}条
+
+ {{ overview.voice_enabled_count ?? 0 }} 启用 ·
+ {{ (overview.voice_count ?? 0) - (overview.voice_enabled_count ?? 0) }} 停用
+
+
+
+ 运行时间
+ {{ formatUptime(overview.uptime_seconds) }}
+ {{ formatStart(overview.start_time) }}
+
+
数据库
+ {{ shortPath(overview.db_path) || '—' }}
+ {{ overview.db_path || '' }}
+ ⚠ {{ actionErr }}
+
+
+
+
+
+
+
+
+
+ 系统信息
+ 运行版本与资源
+ -
+
- Commit
- {{ overview.commit || 'dev' }}
- 版本
- {{ overview.version || '—' }}
- 运行模式
- {{ overview.mode || '—' }}
- 数据库路径
- {{ overview.db_path || '—' }}
- 锁文件
- {{ overview.lock_path || '—' }}
- 内存 +
- + goroutines {{ overview.memory?.goroutines ?? '—' }} · heap {{ formatBytes(overview.memory?.heap_alloc) }} + +
+
+
+
+
+
+
+
+ 操作失败
+ {{ actionErr }}
+
+
+
+
+
+
+ | 名称 | +Speaker | +资源 ID | +Model | +状态 | +操作 | +
|---|---|---|---|---|---|
|
+ {{ v.name }}
+ {{ v.description }}
+ |
+ {{ v.speaker }} | +{{ v.resource_id }} | +{{ v.model || '—' }} | ++ + + {{ v.enabled ? '启用' : '停用' }} + + | ++ + + | +
+
+
+
+ 还没有音色
+ 点击右上角「+ 新增音色」开始添加第一个火山 TTS 音色
+
+
+
+
+ 没有匹配的音色
+ 尝试调整搜索词或筛选条件
+
+
+
+
+
+
+
+
+
+
+ 敏感
+ 鉴权 Key
+ 客户端调用
+ /v1/audio/speech 与登录 /admin 共用
+
+
+ {{ settings.auth_key || '(未设置)' }}
+
+
+
+
+
+
+
+ 改了之后,你的浏览器 401 自动跳回登录页,用新 Key 重登。
+
+
+
+
+
+
+
+ 火山 TTS 上游凭证
+ 调火山 v3 上游用的凭证与默认路由
+
+
+
+ {{ settings.api_key || '(未设置)' }}
+
+
+
+
+
+
+
+
+ 独立端点 PUT /api/settings/api-key,与其他字段分开传输
+
+
+
+
+
+
+
+
+
+
+ 必须是「音色」tab 里存在的 name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ settings.cors_configured ? '已配置' : '未配置' }}
+
+ 跨域 CORS
+ 控制哪些前端域名能跨域调
+ /v1/audio/speech
+
+
+ 勾上后下面白名单失效。仅测试用
+
+
+
+
+ 一行一个。当前: {{ corsCurrentLabel }}
+
+
+
+
-
diff --git a/router/setup.html b/router/setup.html
index 957873b..b5354e2 100644
--- a/router/setup.html
+++ b/router/setup.html
@@ -8,96 +8,197 @@
-
+
+
新增音色
- - - - - - - - - - - - -{{ addErr }}
-
-
-
+
+
+
+
-
+
-
+
+
+ 新增音色
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交失败
+ {{ addErr }}
+
+
+
+
+
+
TTS
-
+
-
+
+ 火山 TTS 聚合平台 · 首次安装
填写以下信息完成初始化。token 来自启动日志或环境变量
TTS_ADMIN_KEY。
+
-
+
+
+ 1
+ 上游凭证
+
+
+
+ 2
+ 默认路由
+
+
+
+ 3
+ 音色列表
+
+
+ 4
+ 完成安装
+
-
OpenAI 鉴权 + 火山引擎 TTS 凭证
- 从控制台/面板获取。两个 Key 仅在此页面输入,不会回显,装完不用再碰 env。
+
+
+
+
+
+ {{ errorMsg }}
+
+
+
+
+
+
+
+ 第 1 步
+ OpenAI 鉴权 + 火山引擎凭证
+ 两个 Key 仅在此页面输入,不会回显,装完不用再碰 env。
+
+
-
+
+
+ 第 2 步
默认路由与格式
+ 未传 voice/format 时使用。装完可在
+ /admin → 设置 改。
@@ -144,11 +256,24 @@
-
音色列表 (至少 1 条)
- 填入火山 TTS 音色 ID(S_ 开头的为复刻音色)。可后续在 /admin 中增删。
+
+
+
+
+
+
+ 第 3 步
+ 音色列表 (至少 1 条)
+ 填入火山 TTS 音色 ID(S_ 开头的为复刻音色)。可后续在 /admin 中增删。
+
+
对外名 (voice)
+ Speaker
+ 资源 ID
+ Model
+
+
-
+
@@ -157,13 +282,53 @@
-
-
- 提交后立即进入正常模式;后续管理请访问
@@ -171,6 +336,7 @@
const { createApp, ref, onMounted } = Vue;
createApp({
setup() {
+ const step = ref(1);
const form = ref({
auth_key: '',
api_key: '',
@@ -190,9 +356,34 @@
const removeVoice = (i) => {
if (form.value.voices.length > 1) form.value.voices.splice(i, 1);
};
+ const next = () => {
+ errorMsg.value = '';
+ if (step.value === 1) {
+ if (!form.value.auth_key || !form.value.api_key) {
+ errorMsg.value = '请填写 OpenAI 鉴权 Key 和火山 API Key';
+ return;
+ }
+ } else if (step.value === 2) {
+ if (!form.value.default_resource_id || !form.value.default_speaker || !form.value.token) {
+ errorMsg.value = '请填写默认资源 ID、默认音色和初始化 Token';
+ return;
+ }
+ } else if (step.value === 3) {
+ if (!form.value.voices.length || form.value.voices.some(v => !v.name || !v.speaker || !v.resource_id)) {
+ errorMsg.value = '音色列表不完整(每条都需要 name + speaker + resource_id)';
+ return;
+ }
+ }
+ step.value = Math.min(4, step.value + 1);
+ };
+ const prev = () => { errorMsg.value = ''; step.value = Math.max(1, step.value - 1); };
+ const mask = (s) => {
+ if (!s) return '';
+ if (s.length <= 8) return '****';
+ return s.slice(0, 4) + '****' + s.slice(-4);
+ };
onMounted(async () => {
- // 预填旧 env 变量(只填非空字段;api_key 永不预填,需手动输入)
try {
const r = await axios.get('/api/setup/prefill');
const s = r.data.settings || {};
@@ -207,14 +398,6 @@
const submit = async () => {
errorMsg.value = '';
- if (!form.value.auth_key || !form.value.api_key || !form.value.default_resource_id || !form.value.default_speaker || !form.value.token) {
- errorMsg.value = '请填写所有必填项(OpenAI Key / 火山 API Key / 资源 ID / 默认音色 / Token)';
- return;
- }
- if (!form.value.voices.length || form.value.voices.some(v => !v.name || !v.speaker || !v.resource_id)) {
- errorMsg.value = '音色列表不完整(每条都需要 name + speaker + resource_id)';
- return;
- }
submitting.value = true;
try {
const r = await axios.post('/api/setup', {
@@ -229,16 +412,15 @@
},
voices: form.value.voices,
});
- // 成功:跳 /admin(M2 才会有,先跳 /dashboard)
- window.location.href = r.data.redirect || '/dashboard';
+ window.location.href = r.data.redirect || '/admin';
} catch (e) {
const msg = e.response?.data?.error?.message || e.message;
- errorMsg.value = `安装失败: ${msg}`;
+ errorMsg.value = '安装失败: ' + msg;
submitting.value = false;
}
};
- return { form, errorMsg, submitting, addVoice, removeVoice, submit };
+ return { step, form, errorMsg, submitting, addVoice, removeVoice, next, prev, mask, submit };
},
}).mount('#app');
diff --git a/store/voices.go b/store/voices.go
index 586851e..6a25d02 100644
--- a/store/voices.go
+++ b/store/voices.go
@@ -10,17 +10,21 @@ import (
)
// Voice 是一行音色记录;时间字段保持 ISO8601 字符串(SQLite TEXT 默认)。
+//
+// JSON tag 是为了前端(admin.html)能直接读取 — 之前没加 tag 时 Go 的
+// "Name" / "Speaker" 等大写字段会原样输出,前端用 v.name 拿到 undefined,
+// 整张表看起来"空"但其实有数据。补 tag 后前端能正常显示。
type Voice struct {
- ID int64
- Name string
- Speaker string
- ResourceID string
- Model string
- Language string
- Description string
- Enabled bool
- CreatedAt string
- UpdatedAt string
+ ID int64 `json:"id"`
+ Name string `json:"name"`
+ Speaker string `json:"speaker"`
+ ResourceID string `json:"resource_id"`
+ Model string `json:"model"`
+ Language string `json:"language"`
+ Description string `json:"description"`
+ Enabled bool `json:"enabled"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
}
// ErrDuplicate 表示 name 唯一冲突;controller 翻译为 409。
/admin。
+
+
+
+
+
+
+
+
+
+ 第 4 步
+ 确认安装
+ 检查以下配置,点击「完成安装」提交。提交后立即进入正常模式。
+
+
+
+
+ OpenAI Key
+ {{ form.auth_key ? mask(form.auth_key) : '(未填)' }}
+
+
+ 火山 API Key
+ {{ form.api_key ? mask(form.api_key) : '(未填)' }}
+
+
+ 默认资源 ID
+ {{ form.default_resource_id || '—' }}
+
+
+ 默认音色
+ {{ form.default_speaker || '—' }}
+
+
+ 输出格式 / 采样率
+ {{ form.default_format }} · {{ form.sample_rate }} Hz
+
+
+ 音色数
+ {{ form.voices.length }} 条 ({{ form.voices.filter(v => v.name).length }} 已填名)
+
+
+
提交后立即进入正常模式;后续管理请访问
+ /admin。
+
+
-