Files
live-caption/apps/common/appcli.h
T
blumia 736ec14d5b refactor: split core/ui/apps targets and add unit tests
Move all business logic into liblivecaption_core (no Qt dependency) and
the widgets into liblivecaption_ui, leaving two executables: the caption
window and the CLI, which no longer links Qt Widgets. Abstract the ASR
backend behind IAsr so the streaming policy and the live pipeline can be
tested with a scripted fake backend instead of a real model.

把全部业务逻辑下沉到不依赖 Qt 的 liblivecaption_core,界面独立成
liblivecaption_ui,拆出悬浮窗与命令行两个可执行程序(CLI 不再链接 Qt
Widgets);并用 IAsr 抽象推理后端,使流式策略与实时管线可脱离真模型
用假实现做单元测试。测试还抓到一个真 bug:段恰好结束在 chunk 边界时
只提交了稳定部分,最后几个词被丢掉,已修。
2026-10-12 13:43:50 +08:00

74 lines
2.7 KiB
C++
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.
#pragma once
#include <cstdio>
#include <string>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include "qwen3asr.h"
#include "streamingasr.h"
// 两个可执行程序(GUI / CLI)共用的命令行选项定义与解析。
namespace lc::app {
inline void addModelOptions(QCommandLineParser & p)
{
p.addOption(QCommandLineOption("model", "主模型 GGUF 路径(架构须为 qwen3vl)", "path"));
p.addOption(QCommandLineOption("mmproj", "mmproj GGUF 路径(clip + qwen3a)", "path"));
p.addOption(QCommandLineOption("threads", "推理线程数", "n", "8"));
p.addOption(QCommandLineOption("max-tokens", "单段最多生成 token 数", "n", "256"));
p.addOption(QCommandLineOption("verbose", "输出 llama.cpp/mtmd 的详细日志"));
}
inline void addStreamOptions(QCommandLineParser & p)
{
p.addOption(QCommandLineOption("stream-chunk", "流式 chunk 秒数", "sec", "2.0"));
p.addOption(QCommandLineOption("stream-window", "滑窗上限秒数(仅 window 模式)", "sec", "16"));
p.addOption(QCommandLineOption("stream-segment", "单段最长秒数(0=不限)", "sec", "30"));
p.addOption(QCommandLineOption("unfixed-tokens", "前缀回滚丢弃的 token 数", "n", "5"));
p.addOption(QCommandLineOption("unfixed-chunks", "冷启动不带前缀的 chunk 数", "n", "2"));
p.addOption(QCommandLineOption("stream-mode", "流式后端: incremental 或 window", "mode",
"incremental"));
}
inline AsrOptions asrOptions(const QCommandLineParser & p)
{
AsrOptions o;
o.modelPath = p.value("model").toStdString();
o.mmprojPath = p.value("mmproj").toStdString();
o.threads = p.value("threads").toInt();
o.maxTokens = p.value("max-tokens").toInt();
o.verbose = p.isSet("verbose");
return o;
}
inline StreamOptions streamOptions(const QCommandLineParser & p)
{
StreamOptions o;
o.chunkSec = p.value("stream-chunk").toDouble();
o.windowSec = p.value("stream-window").toDouble();
o.maxUtteranceSec = p.value("stream-segment").toDouble();
o.unfixedTokenNum = p.value("unfixed-tokens").toInt();
o.unfixedChunkNum = p.value("unfixed-chunks").toInt();
o.maxTokensPerStep = p.value("max-tokens").toInt();
o.incremental = p.value("stream-mode") != QLatin1String("window");
return o;
}
inline bool loadModel(Qwen3Asr & asr, const AsrOptions & opt)
{
if (opt.modelPath.empty() || opt.mmprojPath.empty()) {
std::fprintf(stderr, "必须提供 --model 和 --mmproj\n");
return false;
}
std::string err;
if (!asr.load(opt, &err)) {
std::fprintf(stderr, "加载失败: %s\n", err.c_str());
return false;
}
return true;
}
} // namespace lc::app