feat(gui): expose the streaming and debug options in the advanced page
These were command-line only. Add them to the settings dialog so the whole feature set is reachable without arguments: stream mode, window limit, segment limit, prefix-rollback token count, cold-start chunk count, max tokens per step, thread count, verbose logging and the model directory. The streaming group is grouped and greyed out for streaming-ONNX models, which only use the commit granularity. The backend fingerprint now includes them, so changing one restarts the recogniser; appearance changes still apply live. Also add a test-only --settings-tab so a specific page can be screenshotted. 设置「高级」页补上原来只有命令行才有的选项:流式策略/滑窗上限/单段最长/前缀回滚 token 数/冷启动 chunk 数/单段最多生成 token/线程数/详细日志/模型目录。流式那一组 单独成组,选了流式 ONNX 模型时置灰(它们只用「更新粒度」)。这些值纳入「后端指纹」, 改动会重启识别后端;外观类改动仍然即时生效。另加测试用 --settings-tab 以便给单个 页签截图。
This commit is contained in:
1 parent
d234b9658d
commit
6c04071bb5
8 files changed
+252
-25
No files matched your search
@@ -109,7 +109,7 @@ inline void addBackendOptions(QCommandLineParser & p)
|
||||
// 按注册表里的模型直接构造后端(GUI 用;不依赖任何命令行参数)
|
||||
inline std::unique_ptr<IAsr> makeAsrForModel(const models::ModelInfo & m,
|
||||
const QString & language,
|
||||
int threads, bool verbose,
|
||||
int threads, int maxTokens, bool verbose,
|
||||
std::string * err)
|
||||
{
|
||||
QStringList missing;
|
||||
@@ -125,6 +125,7 @@ inline std::unique_ptr<IAsr> makeAsrForModel(const models::ModelInfo & m,
|
||||
o.modelPath = m.filePath(QStringLiteral("model")).toStdString();
|
||||
o.mmprojPath = m.filePath(QStringLiteral("mmproj")).toStdString();
|
||||
o.threads = threads;
|
||||
if (maxTokens > 0) o.maxTokens = maxTokens;
|
||||
o.verbose = verbose;
|
||||
auto a = std::make_unique<Qwen3Asr>();
|
||||
if (!a->load(o, err)) return nullptr;
|
||||
|
||||
+59
-12
@@ -13,7 +13,9 @@
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QStringList>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QTabWidget>
|
||||
#include <QTimer>
|
||||
|
||||
#include "appcli.h"
|
||||
@@ -66,6 +68,25 @@ bool hasUsableModel()
|
||||
return m && ModelStore::isInstalled(*m);
|
||||
}
|
||||
|
||||
// 后端相关设置的「指纹」:这些值变了就必须重建后端(外观类改动不需要)。
|
||||
QString backendKeyOf(const lc::AppSettings & c)
|
||||
{
|
||||
return QStringList{ c.modelId,
|
||||
c.language,
|
||||
c.captureDevice,
|
||||
c.modelDir,
|
||||
c.streamMode,
|
||||
QString::number(c.chunkSec),
|
||||
QString::number(c.streamWindowSec),
|
||||
QString::number(c.streamSegmentSec),
|
||||
QString::number(c.unfixedTokens),
|
||||
QString::number(c.unfixedChunks),
|
||||
QString::number(c.maxTokens),
|
||||
QString::number(c.threads),
|
||||
QString::number(c.verbose ? 1 : 0) }
|
||||
.join(QLatin1Char('|'));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
@@ -95,6 +116,7 @@ int main(int argc, char ** argv)
|
||||
parser.addOption(QCommandLineOption("screenshot-delay", "截图前等待秒数", "sec", "6"));
|
||||
parser.addOption(QCommandLineOption("no-tray", "不创建托盘图标(测试用)"));
|
||||
parser.addOption(QCommandLineOption("screenshot-settings", "打开设置对话框截图后退出", "path"));
|
||||
parser.addOption(QCommandLineOption("settings-tab", "截图设置对话框时先切到某个页签(0-2 或 模型/显示/高级)", "tab"));
|
||||
parser.addOption(QCommandLineOption("settings-autoclose", "复现用:自动操作设置对话框(reject|close|accept|ok|apply)", "how"));
|
||||
parser.addOption(QCommandLineOption("quit-after", "运行若干秒后优雅退出(测试退出路径用)", "sec"));
|
||||
parser.process(app);
|
||||
@@ -102,12 +124,28 @@ int main(int argc, char ** argv)
|
||||
if (parser.isSet("asr-model")) cfg.modelId = parser.value("asr-model");
|
||||
if (parser.isSet("language")) cfg.language = parser.value("language");
|
||||
if (parser.isSet("opacity")) cfg.opacityPercent = parser.value("opacity").toInt();
|
||||
if (parser.isSet("model-dir")) lc::models::ModelStore::setRootOverride(parser.value("model-dir"));
|
||||
if (parser.isSet("model-dir")) cfg.modelDir = parser.value("model-dir");
|
||||
if (!cfg.modelDir.isEmpty()) lc::models::ModelStore::setRootOverride(cfg.modelDir);
|
||||
|
||||
// 仅用于验证渲染:打开设置对话框截图后退出
|
||||
if (parser.isSet("screenshot-settings")) {
|
||||
const QString path = parser.value("screenshot-settings");
|
||||
lc::SettingsDialog dlg(!hasUsableModel());
|
||||
// 可选:先切页签,便于给某个页单独截图
|
||||
const QString tab = parser.value("settings-tab");
|
||||
if (!tab.isEmpty()) {
|
||||
if (auto * tw = dlg.findChild<QTabWidget *>()) {
|
||||
bool numeric = false;
|
||||
const int n = tab.toInt(&numeric);
|
||||
if (numeric) {
|
||||
tw->setCurrentIndex(qBound(0, n, tw->count() - 1));
|
||||
} else {
|
||||
for (int i = 0; i < tw->count(); ++i) {
|
||||
if (tw->tabText(i) == tab) { tw->setCurrentIndex(i); break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dlg.show();
|
||||
QTimer::singleShot(1200, &dlg, [&dlg, path] {
|
||||
dlg.grab().save(path);
|
||||
@@ -173,14 +211,24 @@ int main(int argc, char ** argv)
|
||||
return false;
|
||||
}
|
||||
std::string err;
|
||||
asr = lc::app::makeAsrForModel(*m, cfg.language, cfg.threads,
|
||||
parser.isSet("verbose"), &err);
|
||||
asr = lc::app::makeAsrForModel(*m, cfg.language, cfg.threads, cfg.maxTokens,
|
||||
cfg.verbose || parser.isSet("verbose"), &err);
|
||||
if (!asr) {
|
||||
qCritical("加载模型失败: %s", err.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 流式参数:只有 llama 后端(Qwen3-ASR)用得到大部分参数,
|
||||
// 流式 ONNX 只关心「更新粒度」。
|
||||
lc::StreamOptions sop;
|
||||
sop.windowSec = cfg.streamWindowSec;
|
||||
sop.maxUtteranceSec = cfg.streamSegmentSec;
|
||||
sop.unfixedTokenNum = cfg.unfixedTokens;
|
||||
sop.unfixedChunkNum = cfg.unfixedChunks;
|
||||
sop.maxTokensPerStep = cfg.maxTokens;
|
||||
sop.incremental = (cfg.streamMode != QLatin1String("append")
|
||||
&& cfg.streamMode != QLatin1String("window"));
|
||||
sop.appendOnly = (cfg.streamMode == QLatin1String("append"));
|
||||
if (asr->kind() == lc::AsrKind::NativeStreaming) {
|
||||
sop.chunkSec = (cfg.chunkSec > 0.0) ? cfg.chunkSec : 0.32;
|
||||
} else if (cfg.chunkSec > 0.0) {
|
||||
@@ -207,28 +255,27 @@ int main(int argc, char ** argv)
|
||||
qWarning("采集失败: %s", qUtf8Printable(m));
|
||||
});
|
||||
|
||||
// 后端相关项(模型/语言/采集设备)变化才需要重启;外观类改动即时生效
|
||||
const auto backendKey = [&cfg] {
|
||||
return cfg.modelId + QLatin1Char('|') + cfg.language + QLatin1Char('|') + cfg.captureDevice;
|
||||
};
|
||||
QString activeKey = backendKey();
|
||||
// 后端相关项(模型/语言/采集设备/流式参数)变化才需要重启;外观类改动即时生效
|
||||
QString activeKey = backendKeyOf(cfg);
|
||||
|
||||
const auto openSettings = [&] {
|
||||
lc::SettingsDialog dlg(false);
|
||||
QObject::connect(&dlg, &lc::SettingsDialog::settingsApplied, &window, [&] {
|
||||
lc::models::ModelStore::setRootOverride(cfg.modelDir);
|
||||
applyAppearance(window, cfg); // 透明度/字号等立刻可见
|
||||
if (backendKey() != activeKey) {
|
||||
if (startTranscriber()) activeKey = backendKey();
|
||||
if (backendKeyOf(cfg) != activeKey) {
|
||||
if (startTranscriber()) activeKey = backendKeyOf(cfg);
|
||||
}
|
||||
});
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
lc::models::ModelStore::setRootOverride(cfg.modelDir);
|
||||
applyAppearance(window, cfg);
|
||||
if (backendKey() != activeKey) {
|
||||
if (backendKeyOf(cfg) != activeKey) {
|
||||
if (!startTranscriber()) {
|
||||
QMessageBox::warning(&window, QObject::tr("启动失败"),
|
||||
QObject::tr("模型加载失败,请检查设置。"));
|
||||
} else {
|
||||
activeKey = backendKey();
|
||||
activeKey = backendKeyOf(cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
参数的选择与实测见 [docs/phase2-streaming.md](phase2-streaming.md)。
|
||||
**这些参数只影响 `llama` 后端**(Qwen3-ASR);`sherpa` 的流式模型只用到「提交粒度」一个参数。
|
||||
同一组参数在 GUI 的「高级 → 流式」里也有(选了流式 ONNX 模型时那一组会置灰)。
|
||||
|
||||
| 选项 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
@@ -84,6 +85,7 @@
|
||||
| `--screenshot <path>` | — | 启动后自动截图并退出;同时截折叠态与展开态(`-expanded.png`) |
|
||||
| `--screenshot-delay <sec>` | `6` | 截图前等待秒数 |
|
||||
| `--screenshot-settings <path>` | — | 打开设置对话框、截图后退出 |
|
||||
| `--settings-tab <tab>` | — | 截图设置对话框时先切到某个页签(`0`–`2` 或 `模型`/`显示`/`高级`) |
|
||||
| `--settings-autoclose <how>` | — | 自动操作设置对话框:`reject` / `close` / `accept` / `ok` / `apply`(`ok`/`apply` 是真的去点按钮) |
|
||||
| `--quit-after <sec>` | — | 运行若干秒后优雅退出(测退出路径) |
|
||||
|
||||
|
||||
+29
-3
@@ -47,8 +47,9 @@
|
||||
| 取消 | 放弃本次改动(已经用「应用」生效过的改动不会被回滚) |
|
||||
| 确定 | 应用并关闭 |
|
||||
|
||||
**哪些改动要重启识别后端?** 只有**模型 / 识别语言 / 采集设备**这三项变化时才会重建后端并重启转写;
|
||||
透明度、字号、行数、灰色尾部这类外观改动是即时生效的,不会打断正在进行的转写。
|
||||
**哪些改动要重启识别后端?** 只有**模型 / 识别语言 / 采集设备**,以及高级页里的**流式参数、
|
||||
线程数、模型目录**变化时才会重建后端并重启转写;透明度、字号、行数、灰色尾部这类外观改动是
|
||||
即时生效的,不会打断正在进行的转写。
|
||||
|
||||
### 模型页
|
||||
|
||||
@@ -80,10 +81,31 @@
|
||||
|
||||
### 高级页
|
||||
|
||||
**通用**
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 更新粒度(秒) | 累积多少秒音频跑一步;「自动」= 由后端决定(流式模型 0.32s) |
|
||||
| 线程数 | 推理线程数;**只影响 Qwen3-ASR**,流式模型固定单线程(多线程反而更慢) |
|
||||
|
||||
**流式(只影响 Qwen3-ASR)** —— 这一组对应 llama 后端的流式参数,改动会重启识别后端。
|
||||
选了流式 ONNX 模型(zipformer / X-ASR / Nemotron)时整组会**置灰**,因为那些模型只用「更新粒度」。
|
||||
|
||||
| 项 | 对应命令行 | 说明 |
|
||||
|---|---|---|
|
||||
| 策略 | `--stream-mode` | `incremental`(增量复用,1~2 秒就出字,推荐)/ `append`(追加式,成本平坦但需要大 chunk)/ `window`(早期的对比实现) |
|
||||
| 滑窗上限(秒) | `--stream-window` | 仅 window 模式;「不限」= 不滑窗 |
|
||||
| 单段最长时长(秒) | `--stream-segment` | 超过就提交并重置上下文;「不限」= 不设上限 |
|
||||
| 前缀回滚 token 数 | `--unfixed-tokens` | 回滚丢弃最后 N 个 token —— 它们会显示成灰色的不稳定尾部 |
|
||||
| 冷启动 chunk 数 | `--unfixed-chunks` | 前 N 个 chunk 不带前缀,避免开头重复 |
|
||||
| 单段最多生成 token | `--max-tokens` | 单段生成的 token 上限 |
|
||||
| 线程数 | `--threads` | 只影响 Qwen3-ASR;流式模型固定单线程(多线程反而更慢) |
|
||||
|
||||
**调试**
|
||||
|
||||
| 项 | 对应命令行 | 说明 |
|
||||
|---|---|---|
|
||||
| 输出详细日志 | `--verbose` | 打印 llama.cpp / mtmd 的详细日志(排查加载/推理问题时用) |
|
||||
| 模型目录 | `--model-dir` | 留空 = 默认目录(占位符会显示实际路径);一般不需要改 |
|
||||
|
||||
## 配置文件
|
||||
|
||||
@@ -101,6 +123,10 @@ windowX=179 windowY=725 ; 拖动后记录的位置
|
||||
mirrorBase=https://hf-mirror.com
|
||||
proxyMode=0 ; 0=不使用 1=跟随系统 2=手动
|
||||
chunkSec=0 threads=8 ; 高级
|
||||
streamMode=incremental ; 高级:流式参数(只影响 Qwen3-ASR)
|
||||
streamWindowSec=16 streamSegmentSec=30
|
||||
unfixedTokens=5 unfixedChunks=2 maxTokens=256
|
||||
verbose=false modelDir= ; 高级:调试
|
||||
```
|
||||
|
||||
删掉这个文件(或其中某个键)就回到默认值。模型文件在
|
||||
|
||||
@@ -42,6 +42,15 @@ void AppSettings::load()
|
||||
|
||||
chunkSec = s.value(QStringLiteral("chunkSec"), 0.0).toDouble();
|
||||
threads = s.value(QStringLiteral("threads"), 8).toInt();
|
||||
|
||||
streamMode = s.value(QStringLiteral("streamMode"), QStringLiteral("incremental")).toString();
|
||||
streamWindowSec = s.value(QStringLiteral("streamWindowSec"), 16.0).toDouble();
|
||||
streamSegmentSec = s.value(QStringLiteral("streamSegmentSec"), 30.0).toDouble();
|
||||
unfixedTokens = s.value(QStringLiteral("unfixedTokens"), 5).toInt();
|
||||
unfixedChunks = s.value(QStringLiteral("unfixedChunks"), 2).toInt();
|
||||
maxTokens = s.value(QStringLiteral("maxTokens"), 256).toInt();
|
||||
verbose = s.value(QStringLiteral("verbose"), false).toBool();
|
||||
modelDir = s.value(QStringLiteral("modelDir")).toString();
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
@@ -74,6 +83,14 @@ void AppSettings::save()
|
||||
|
||||
s.setValue(QStringLiteral("chunkSec"), chunkSec);
|
||||
s.setValue(QStringLiteral("threads"), threads);
|
||||
s.setValue(QStringLiteral("streamMode"), streamMode);
|
||||
s.setValue(QStringLiteral("streamWindowSec"), streamWindowSec);
|
||||
s.setValue(QStringLiteral("streamSegmentSec"), streamSegmentSec);
|
||||
s.setValue(QStringLiteral("unfixedTokens"), unfixedTokens);
|
||||
s.setValue(QStringLiteral("unfixedChunks"), unfixedChunks);
|
||||
s.setValue(QStringLiteral("maxTokens"), maxTokens);
|
||||
s.setValue(QStringLiteral("verbose"), verbose);
|
||||
s.setValue(QStringLiteral("modelDir"), modelDir);
|
||||
s.endGroup();
|
||||
s.sync();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,18 @@ public:
|
||||
double chunkSec = 0.0; // 0 = 由后端决定
|
||||
int threads = 8;
|
||||
|
||||
// 高级:流式参数(只影响 llama 后端的 Qwen3-ASR;流式 ONNX 只用 chunkSec)
|
||||
QString streamMode = QStringLiteral("incremental"); // incremental | append | window
|
||||
double streamWindowSec = 16.0; // 仅 window 模式:滑窗上限;0 = 不限
|
||||
double streamSegmentSec = 30.0; // 单段最长时长;0 = 不限
|
||||
int unfixedTokens = 5; // 前缀回滚丢弃的 token 数(算作不稳定尾部)
|
||||
int unfixedChunks = 2; // 冷启动不带前缀的 chunk 数
|
||||
int maxTokens = 256; // 单段最多生成 token 数
|
||||
|
||||
// 高级:调试
|
||||
bool verbose = false; // 输出 llama.cpp / mtmd 的详细日志
|
||||
QString modelDir; // 空 = 默认模型存储目录
|
||||
|
||||
void load();
|
||||
void save();
|
||||
|
||||
|
||||
+115
-9
@@ -77,6 +77,7 @@ SettingsDialog::SettingsDialog(bool firstRun, QWidget * parent)
|
||||
|
||||
refreshModelTable();
|
||||
refreshActiveCombo();
|
||||
updateStreamGroupEnabled();
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog() = default;
|
||||
@@ -208,6 +209,7 @@ QWidget * SettingsDialog::buildModelPage()
|
||||
AppSettings::instance().modelId = activeCombo_->itemData(idx).toString();
|
||||
AppSettings::instance().save();
|
||||
}
|
||||
updateStreamGroupEnabled();
|
||||
});
|
||||
|
||||
connect(downloader_, &ModelDownloader::status, this, [this](const QString & m) {
|
||||
@@ -282,30 +284,125 @@ QWidget * SettingsDialog::buildDisplayPage()
|
||||
QWidget * SettingsDialog::buildAdvancedPage()
|
||||
{
|
||||
auto * page = new QWidget(this);
|
||||
auto * form = new QFormLayout(page);
|
||||
auto * root = new QVBoxLayout(page);
|
||||
AppSettings & cfg = AppSettings::instance();
|
||||
|
||||
// ---- 通用 ----
|
||||
auto * generalForm = new QFormLayout;
|
||||
|
||||
chunkSpin_ = new QDoubleSpinBox(page);
|
||||
chunkSpin_->setRange(0.0, 5.0);
|
||||
chunkSpin_->setSingleStep(0.1);
|
||||
chunkSpin_->setDecimals(2);
|
||||
chunkSpin_->setSpecialValueText(tr("自动"));
|
||||
chunkSpin_->setValue(cfg.chunkSec);
|
||||
form->addRow(tr("更新粒度(秒)"), chunkSpin_);
|
||||
chunkSpin_->setToolTip(tr("累积多少秒音频跑一步。流式模型默认 0.32 秒。"));
|
||||
generalForm->addRow(tr("更新粒度(秒)"), chunkSpin_);
|
||||
|
||||
threadsSpin_ = new QSpinBox(page);
|
||||
root->addLayout(generalForm);
|
||||
|
||||
// ---- 流式:只影响 llama 后端的 Qwen3-ASR ----
|
||||
streamGroup_ = new QGroupBox(tr("流式(只影响 Qwen3-ASR)"), page);
|
||||
auto * streamForm = new QFormLayout(streamGroup_);
|
||||
|
||||
streamModeCombo_ = new QComboBox(streamGroup_);
|
||||
streamModeCombo_->addItem(tr("incremental — 增量复用,1~2 秒就出字(推荐)"),
|
||||
QStringLiteral("incremental"));
|
||||
streamModeCombo_->addItem(tr("append — 追加式,成本平坦但需要大 chunk"),
|
||||
QStringLiteral("append"));
|
||||
streamModeCombo_->addItem(tr("window — 每次重编码整个窗口(早期的对比实现)"),
|
||||
QStringLiteral("window"));
|
||||
{
|
||||
const int i = streamModeCombo_->findData(cfg.streamMode);
|
||||
if (i >= 0) streamModeCombo_->setCurrentIndex(i);
|
||||
}
|
||||
streamForm->addRow(tr("策略"), streamModeCombo_);
|
||||
|
||||
windowSpin_ = new QDoubleSpinBox(streamGroup_);
|
||||
windowSpin_->setRange(0.0, 120.0);
|
||||
windowSpin_->setDecimals(1);
|
||||
windowSpin_->setSpecialValueText(tr("不限"));
|
||||
windowSpin_->setValue(cfg.streamWindowSec);
|
||||
windowSpin_->setToolTip(tr("仅 window 模式使用:滑窗上限。"));
|
||||
streamForm->addRow(tr("滑窗上限(秒)"), windowSpin_);
|
||||
|
||||
segmentSpin_ = new QDoubleSpinBox(streamGroup_);
|
||||
segmentSpin_->setRange(0.0, 300.0);
|
||||
segmentSpin_->setDecimals(1);
|
||||
segmentSpin_->setSpecialValueText(tr("不限"));
|
||||
segmentSpin_->setValue(cfg.streamSegmentSec);
|
||||
segmentSpin_->setToolTip(tr("超过就提交并重置上下文。真实场景由静音检测分段,这是兜底上界。"));
|
||||
streamForm->addRow(tr("单段最长时长(秒)"), segmentSpin_);
|
||||
|
||||
unfixedTokenSpin_ = new QSpinBox(streamGroup_);
|
||||
unfixedTokenSpin_->setRange(0, 64);
|
||||
unfixedTokenSpin_->setValue(cfg.unfixedTokens);
|
||||
unfixedTokenSpin_->setToolTip(tr("回滚丢弃最后 N 个 token —— 它们会显示成灰色的不稳定尾部。"));
|
||||
streamForm->addRow(tr("前缀回滚 token 数"), unfixedTokenSpin_);
|
||||
|
||||
unfixedChunkSpin_ = new QSpinBox(streamGroup_);
|
||||
unfixedChunkSpin_->setRange(0, 32);
|
||||
unfixedChunkSpin_->setValue(cfg.unfixedChunks);
|
||||
unfixedChunkSpin_->setToolTip(tr("前 N 个 chunk 不带前缀(冷启动),用于避免开头重复。"));
|
||||
streamForm->addRow(tr("冷启动 chunk 数"), unfixedChunkSpin_);
|
||||
|
||||
maxTokenSpin_ = new QSpinBox(streamGroup_);
|
||||
maxTokenSpin_->setRange(16, 4096);
|
||||
maxTokenSpin_->setValue(cfg.maxTokens);
|
||||
maxTokenSpin_->setToolTip(tr("单段最多生成的 token 数。"));
|
||||
streamForm->addRow(tr("单段最多生成 token"), maxTokenSpin_);
|
||||
|
||||
threadsSpin_ = new QSpinBox(streamGroup_);
|
||||
threadsSpin_->setRange(1, 32);
|
||||
threadsSpin_->setValue(cfg.threads);
|
||||
form->addRow(tr("线程数"), threadsSpin_);
|
||||
threadsSpin_->setToolTip(tr("只影响 Qwen3-ASR;流式模型固定单线程,因为这类模型多线程反而更慢。"));
|
||||
streamForm->addRow(tr("线程数"), threadsSpin_);
|
||||
|
||||
auto * note = new QLabel(
|
||||
tr("说明:流式模型(zipformer / X-ASR / Nemotron)会自动使用单线程,"
|
||||
"因为这类模型多线程反而更慢;线程数只影响 Qwen3-ASR。"), page);
|
||||
note->setWordWrap(true);
|
||||
form->addRow(QString(), note);
|
||||
streamNote_ = new QLabel(streamGroup_);
|
||||
streamNote_->setWordWrap(true);
|
||||
streamForm->addRow(QString(), streamNote_);
|
||||
|
||||
root->addWidget(streamGroup_);
|
||||
|
||||
// ---- 调试 ----
|
||||
auto * debugForm = new QFormLayout;
|
||||
|
||||
verboseCheck_ = new QCheckBox(tr("输出详细日志(llama.cpp / mtmd)"), page);
|
||||
verboseCheck_->setChecked(cfg.verbose);
|
||||
debugForm->addRow(QString(), verboseCheck_);
|
||||
|
||||
modelDirEdit_ = new QLineEdit(page);
|
||||
modelDirEdit_->setText(cfg.modelDir);
|
||||
modelDirEdit_->setPlaceholderText(ModelStore::root());
|
||||
modelDirEdit_->setToolTip(tr("留空 = 默认目录(占位符所示)。一般不需要改。"));
|
||||
debugForm->addRow(tr("模型目录"), modelDirEdit_);
|
||||
|
||||
root->addLayout(debugForm);
|
||||
root->addStretch(1);
|
||||
|
||||
updateStreamGroupEnabled();
|
||||
return page;
|
||||
}
|
||||
|
||||
// 流式那一组参数只对 llama 后端(Qwen3-ASR)有意义;
|
||||
// 选了流式 ONNX 模型时置灰,免得改了没效果。
|
||||
void SettingsDialog::updateStreamGroupEnabled()
|
||||
{
|
||||
if (!streamGroup_) return;
|
||||
const QString id = (activeCombo_ && activeCombo_->currentIndex() >= 0)
|
||||
? activeCombo_->currentData().toString()
|
||||
: QString();
|
||||
const ModelInfo * m = id.isEmpty() ? nullptr : ModelStore::find(id);
|
||||
const bool llamaBackend = m ? (m->backend == models::Backend::LlamaCpp) : true;
|
||||
|
||||
streamGroup_->setEnabled(llamaBackend);
|
||||
streamNote_->setText(llamaBackend
|
||||
? tr("这些参数对应命令行里的 --stream-mode / --stream-window / --stream-segment / "
|
||||
"--unfixed-tokens / --unfixed-chunks / --max-tokens / --threads。改动会重启识别后端。")
|
||||
: tr("当前模型是流式 ONNX(zipformer / X-ASR / Nemotron),这些参数不适用;"
|
||||
"它只用上面那个「更新粒度」。"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
void SettingsDialog::refreshModelTable()
|
||||
{
|
||||
@@ -480,6 +577,15 @@ void SettingsDialog::applySettings()
|
||||
cfg.chunkSec = chunkSpin_->value();
|
||||
cfg.threads = threadsSpin_->value();
|
||||
|
||||
cfg.streamMode = streamModeCombo_->currentData().toString();
|
||||
cfg.streamWindowSec = windowSpin_->value();
|
||||
cfg.streamSegmentSec = segmentSpin_->value();
|
||||
cfg.unfixedTokens = unfixedTokenSpin_->value();
|
||||
cfg.unfixedChunks = unfixedChunkSpin_->value();
|
||||
cfg.maxTokens = maxTokenSpin_->value();
|
||||
cfg.verbose = verboseCheck_->isChecked();
|
||||
cfg.modelDir = modelDirEdit_->text().trimmed();
|
||||
|
||||
if (activeCombo_->currentIndex() >= 0) {
|
||||
cfg.modelId = activeCombo_->currentData().toString();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
class QComboBox;
|
||||
class QCheckBox;
|
||||
class QDoubleSpinBox;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QProgressBar;
|
||||
@@ -37,6 +38,7 @@ private:
|
||||
|
||||
void refreshModelTable();
|
||||
void refreshActiveCombo();
|
||||
void updateStreamGroupEnabled(); // 流式参数只在 llama 后端下可用
|
||||
void onDownload();
|
||||
void onVerify();
|
||||
void onDelete();
|
||||
@@ -73,6 +75,20 @@ private:
|
||||
QDoubleSpinBox * chunkSpin_ = nullptr;
|
||||
QSpinBox * threadsSpin_ = nullptr;
|
||||
|
||||
// 高级:流式参数(只影响 llama 后端)
|
||||
QGroupBox * streamGroup_ = nullptr;
|
||||
QLabel * streamNote_ = nullptr;
|
||||
QComboBox * streamModeCombo_ = nullptr;
|
||||
QDoubleSpinBox * windowSpin_ = nullptr;
|
||||
QDoubleSpinBox * segmentSpin_ = nullptr;
|
||||
QSpinBox * unfixedTokenSpin_ = nullptr;
|
||||
QSpinBox * unfixedChunkSpin_ = nullptr;
|
||||
QSpinBox * maxTokenSpin_ = nullptr;
|
||||
|
||||
// 高级:调试
|
||||
QCheckBox * verboseCheck_ = nullptr;
|
||||
QLineEdit * modelDirEdit_ = nullptr;
|
||||
|
||||
models::ModelDownloader * downloader_ = nullptr;
|
||||
bool firstRun_ = false;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user