Files
BiliNote/BillNote_extension/src/options/pages/Transcriber.vue
huangjianwu 3bd8b670ca feat(extension): options 改为多 tab,搬入 web 端的全部设置项
把原来一长条的 options 拆成五个 tab,覆盖 web 端 SettingPage 的全部能力。今后新功能优先在插件里做,web 端逐步退役。

- 通用:后端地址 + 默认供应商/模型 + 默认生成选项(原 Options.vue 内容)
- 模型供应商:完整 CRUD —— 列表 / 启用切换 / 编辑 / 测试连接 / 添加 / 模型增删
- 音频转写配置:转写器引擎切换(fast-whisper / mlx-whisper / Groq / 必剪 / 快手)+ Whisper 模型大小切换 + 模型本地下载状态 + 触发下载
  · 直接修复 'MLX Whisper 不可用' 报错——非 Mac 用户现在能切到 fast-whisper / Groq
- 下载配置:每平台 cookie 显示 / 浏览器一键同步 / 手动粘贴保存
- 部署监控:后端、FFmpeg、CUDA、Whisper 模型 当前状态

logic/api.ts 补齐:provider CRUD / model CRUD / connect_test / transcriber_config / transcriber_models_status / transcriber_download / get_downloader_cookie / deploy_status / sys_health。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:53:08 +08:00

163 lines
5.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import {
downloadTranscriberModel,
getTranscriberConfig,
getTranscriberModelsStatus,
setTranscriberConfig,
} from '~/logic/api'
import type {
TranscriberConfig,
TranscriberModelsStatus,
TranscriberType,
WhisperModelSize,
WhisperModelStatus,
} from '~/logic/types'
const config = ref<TranscriberConfig | null>(null)
const status = ref<TranscriberModelsStatus | null>(null)
const selType = ref<TranscriberType>('fast-whisper')
const selSize = ref<WhisperModelSize>('medium')
const loading = ref(false)
const saving = ref(false)
const message = ref<{ kind: 'ok' | 'err' | 'idle', text: string }>({ kind: 'idle', text: '' })
const isWhisperLike = computed(() => selType.value === 'fast-whisper' || selType.value === 'mlx-whisper')
async function refresh() {
loading.value = true
message.value = { kind: 'idle', text: '' }
try {
const [cfg, st] = await Promise.all([getTranscriberConfig(), getTranscriberModelsStatus()])
config.value = cfg
status.value = st
selType.value = cfg.transcriber_type
if (cfg.whisper_model_size)
selSize.value = cfg.whisper_model_size
}
catch (e) {
message.value = { kind: 'err', text: `读取失败:${(e as Error).message}` }
}
finally {
loading.value = false
}
}
async function save() {
saving.value = true
message.value = { kind: 'idle', text: '保存中…' }
try {
const cfg = await setTranscriberConfig(selType.value, isWhisperLike.value ? selSize.value : undefined)
config.value = cfg
message.value = { kind: 'ok', text: '已保存。下一次生成笔记会用新配置。' }
}
catch (e) {
message.value = { kind: 'err', text: `保存失败:${(e as Error).message}` }
}
finally {
saving.value = false
}
}
async function triggerDownload(size: WhisperModelSize) {
try {
await downloadTranscriberModel(size, selType.value === 'mlx-whisper' ? 'mlx-whisper' : 'fast-whisper')
message.value = { kind: 'ok', text: `已开始下载 ${size}` }
await refresh()
}
catch (e) {
message.value = { kind: 'err', text: `触发下载失败:${(e as Error).message}` }
}
}
const currentSizeStatus = computed<WhisperModelStatus[]>(() => {
if (!status.value)
return []
return selType.value === 'mlx-whisper' ? status.value.mlx_whisper : status.value.whisper
})
onMounted(refresh)
</script>
<template>
<div class="p-6 max-w-3xl">
<h1 class="text-xl font-bold mb-1">音频转写配置</h1>
<p class="text-xs text-gray-500 mb-4">
选择把视频音频转成文字的引擎在线引擎Groq / 必剪 / 快手走第三方 API本地 Whisper 需要先下载模型
</p>
<div v-if="loading" class="text-sm text-gray-500">加载中</div>
<template v-else-if="config">
<section class="section-card">
<h2 class="font-semibold">引擎</h2>
<select v-model="selType" class="input">
<option v-for="opt in config.available_types" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
<p v-if="selType === 'mlx-whisper' && !config.mlx_whisper_available" class="text-xs text-red-600">
当前后端没有装 mlx_whisper macOS 可用如果不是 Mac请改用 fast-whisper / Groq / 必剪 / 快手
</p>
</section>
<section v-if="isWhisperLike" class="section-card">
<h2 class="font-semibold">Whisper 模型大小</h2>
<select v-model="selSize" class="input">
<option v-for="s in config.whisper_model_sizes" :key="s" :value="s">
{{ s }}
</option>
</select>
<h3 class="text-sm font-medium mt-2">下载状态</h3>
<table class="text-sm w-full">
<thead>
<tr class="text-left text-gray-500">
<th class="py-1 font-normal">模型</th>
<th class="py-1 font-normal">本地</th>
<th class="py-1 font-normal">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in currentSizeStatus" :key="row.model_size" class="border-t">
<td class="py-1">{{ row.model_size }}</td>
<td class="py-1">
<span v-if="row.downloaded" class="tag bg-green-100 text-green-700">已下载</span>
<span v-else-if="row.downloading" class="tag bg-yellow-100 text-yellow-700">下载中</span>
<span v-else class="tag bg-gray-100 text-gray-500">未下载</span>
</td>
<td class="py-1">
<button
v-if="!row.downloaded && !row.downloading"
class="btn-secondary"
@click="triggerDownload(row.model_size)"
>
下载
</button>
</td>
</tr>
</tbody>
</table>
</section>
<section class="flex items-center gap-3">
<button class="btn-primary" :disabled="saving" @click="save">
{{ saving ? '保存中' : '保存配置' }}
</button>
<button class="btn-secondary" @click="refresh">刷新</button>
<span
v-if="message.text"
class="text-xs"
:class="{
'text-green-700': message.kind === 'ok',
'text-red-600': message.kind === 'err',
'text-gray-500': message.kind === 'idle',
}"
>{{ message.text }}</span>
</section>
</template>
</div>
</template>