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 边界时 只提交了稳定部分,最后几个词被丢掉,已修。
This commit is contained in:
1 parent
4ed4e23282
commit
736ec14d5b
32 files changed
+1502
-655
No files matched your search
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "iasr.h"
|
||||
|
||||
// 可脚本化的假推理后端:让流式策略与实时管线可以脱离真模型测试。
|
||||
//
|
||||
// dropLastTokens 用"空格分词"模拟词表:丢掉最后 N 个词,并保留原有格式
|
||||
// (这点很重要 —— 续写开头那个空格是有意义的)。
|
||||
class FakeAsr : public lc::IAsr {
|
||||
public:
|
||||
// 每次 streamAppend 依次返回这里的文本;用完后返回空串
|
||||
std::vector<std::string> scripted;
|
||||
|
||||
// 记录(供断言用)
|
||||
std::vector<std::string> prefixesSeen;
|
||||
std::vector<size_t> samplesSeen;
|
||||
int resets = 0;
|
||||
|
||||
int sampleRate() const override { return 16000; }
|
||||
|
||||
lc::AsrResult transcribeWithPrefix(const float *, size_t,
|
||||
const std::string &) override
|
||||
{
|
||||
lc::AsrResult r;
|
||||
r.ok = true;
|
||||
if (call_ < scripted.size()) {
|
||||
r.raw = scripted[call_];
|
||||
r.transcript = lc::parseAsrOutput(r.raw);
|
||||
}
|
||||
++call_;
|
||||
return r;
|
||||
}
|
||||
|
||||
lc::AsrStreamStep streamAppend(const float *, size_t nSamples,
|
||||
const std::string & prefix, int) override
|
||||
{
|
||||
prefixesSeen.push_back(prefix);
|
||||
samplesSeen.push_back(nSamples);
|
||||
|
||||
lc::AsrStreamStep s;
|
||||
s.ok = true;
|
||||
if (call_ < scripted.size()) s.newText = scripted[call_];
|
||||
++call_;
|
||||
s.newTokens = 1;
|
||||
s.encodeSeconds = 0.001;
|
||||
s.inferSeconds = 0.001;
|
||||
return s;
|
||||
}
|
||||
|
||||
void streamReset() override { ++resets; }
|
||||
|
||||
std::string dropLastTokens(const std::string & text, int dropLast) const override
|
||||
{
|
||||
if (dropLast <= 0 || text.empty()) return text;
|
||||
|
||||
const auto isSpace = [](char c) { return std::isspace((unsigned char) c) != 0; };
|
||||
|
||||
size_t pos = text.size();
|
||||
for (int i = 0; i < dropLast; ++i) {
|
||||
while (pos > 0 && isSpace(text[pos - 1])) --pos; // 跳过尾部空白
|
||||
while (pos > 0 && !isSpace(text[pos - 1])) --pos; // 跳过一个词
|
||||
if (pos == 0) return {};
|
||||
}
|
||||
while (pos > 0 && isSpace(text[pos - 1])) --pos;
|
||||
return text.substr(0, pos);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t call_ = 0;
|
||||
};
|
||||
Reference in new issue
Block a user