chore: format llm c files
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
JS/TS / lint (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 16s
C++ / clang-tidy (pull_request) Failing after 43s
Python / static (pull_request) Failing after 57s
Rust / fmt (pull_request) Successful in 1m30s
Rust / build (pull_request) Successful in 2m5s
Rust / clippy (pull_request) Successful in 1m55s
Python / verify (pull_request) Successful in 2m23s

This commit is contained in:
2026-08-31 19:02:26 +02:00
parent dbb51ceadd
commit 52feb6006a
27 changed files with 741 additions and 1190 deletions
+44 -76
View File
@@ -5,22 +5,19 @@
namespace ZShell::llm {
ChatGeneration::ChatGeneration(qint64 timestamp, QObject* parent)
: QObject(parent), m_timestamp(timestamp) {
: QObject(parent), m_timestamp(timestamp) {
m_timer.setParent(this);
m_timer.setInterval(500);
m_timer.setTimerType(Qt::CoarseTimer);
connect(&m_timer, &QTimer::timeout, this, [this]() {
bool anyRunning = false;
for (auto* segment : m_segments) {
if (!segment->running())
continue;
if (!segment->running()) continue;
anyRunning = true;
segment->elapsedMsChanged();
}
if (anyRunning)
Q_EMIT elapsedMsChanged();
if (!anyRunning && !m_streaming)
m_timer.stop();
if (anyRunning) Q_EMIT elapsedMsChanged();
if (!anyRunning && !m_streaming) m_timer.stop();
});
}
@@ -28,7 +25,7 @@ QString ChatGeneration::content() const {
QStringList parts;
for (const auto* segment : m_segments) {
if (segment->type() != LlmSegment::Type::Content ||
segment->text().isEmpty())
segment->text().isEmpty())
continue;
parts.append(segment->text());
}
@@ -39,7 +36,7 @@ QString ChatGeneration::reasoning() const {
QStringList parts;
for (const auto* segment : m_segments) {
if (segment->type() != LlmSegment::Type::Reasoning ||
segment->text().isEmpty())
segment->text().isEmpty())
continue;
parts.append(segment->text());
}
@@ -47,10 +44,8 @@ QString ChatGeneration::reasoning() const {
}
bool ChatGeneration::reasoningActive() const {
if (!m_streaming)
return false;
if (!content().isEmpty())
return false;
if (!m_streaming) return false;
if (!content().isEmpty()) return false;
return !hasRunningTool();
}
@@ -81,44 +76,35 @@ qint64 ChatGeneration::toolsElapsedMs() const {
int ChatGeneration::toolCallCount() const {
int count = 0;
for (const auto* segment : m_segments)
if (segment->type() == LlmSegment::Type::ToolCall)
++count;
if (segment->type() == LlmSegment::Type::ToolCall) ++count;
return count;
}
bool ChatGeneration::hasRunningTool() const {
for (const auto* segment : m_segments)
if (segment->type() == LlmSegment::Type::ToolCall &&
segment->running())
if (segment->type() == LlmSegment::Type::ToolCall && segment->running())
return true;
return false;
}
void ChatGeneration::updateReasoningActive() {
const bool active = reasoningActive();
if (m_reasoningActive == active)
return;
if (m_reasoningActive == active) return;
m_reasoningActive = active;
Q_EMIT reasoningActiveChanged();
}
void ChatGeneration::setContent(const QString& value) {
// Replaces the entire answer: the first content segment takes the
// new text, any later content bursts are cleared.
LlmSegment* first = nullptr;
for (auto* segment : m_segments) {
if (segment->type() != LlmSegment::Type::Content)
continue;
if (segment->type() != LlmSegment::Type::Content) continue;
if (!first)
first = segment;
else
segment->setText(QString());
}
if (!first) {
// Do not materialize an empty content segment (e.g. the assistant
// placeholder created before the stream starts).
if (value.isEmpty())
return;
if (value.isEmpty()) return;
first = new LlmSegment(
LlmSegment::Type::Content,
QDateTime::currentMSecsSinceEpoch(),
@@ -129,35 +115,30 @@ void ChatGeneration::setContent(const QString& value) {
}
void ChatGeneration::appendContent(const QString& piece) {
if (piece.isEmpty())
return;
if (piece.isEmpty()) return;
for (auto* segment : m_segments) {
if (segment->type() == LlmSegment::Type::Reasoning &&
segment->running())
segment->running())
segment->close();
}
openContentSegment()->appendText(piece);
}
void ChatGeneration::appendReasoning(const QString& piece) {
if (piece.isEmpty())
return;
if (piece.isEmpty()) return;
for (auto* segment : m_segments) {
if (segment->type() == LlmSegment::Type::Content &&
segment->running())
if (segment->type() == LlmSegment::Type::Content && segment->running())
segment->close();
}
openReasoningSegment()->appendText(piece);
}
void ChatGeneration::setStreaming(bool value) {
if (m_streaming == value)
return;
if (m_streaming == value) return;
m_streaming = value;
Q_EMIT streamingChanged();
if (value) {
if (!m_timer.isActive())
m_timer.start();
if (!m_timer.isActive()) m_timer.start();
} else {
closeOpenSegments();
m_timer.stop();
@@ -168,13 +149,10 @@ void ChatGeneration::setStreaming(bool value) {
LlmSegment* ChatGeneration::openContentSegment() {
for (auto* segment : m_segments)
if (segment->type() == LlmSegment::Type::Content &&
segment->running())
if (segment->type() == LlmSegment::Type::Content && segment->running())
return segment;
auto* segment = new LlmSegment(
LlmSegment::Type::Content,
QDateTime::currentMSecsSinceEpoch(),
this);
LlmSegment::Type::Content, QDateTime::currentMSecsSinceEpoch(), this);
segment->begin();
addSegment(segment);
return segment;
@@ -183,12 +161,10 @@ LlmSegment* ChatGeneration::openContentSegment() {
LlmSegment* ChatGeneration::openReasoningSegment() {
for (auto* segment : m_segments)
if (segment->type() == LlmSegment::Type::Reasoning &&
segment->running())
segment->running())
return segment;
auto* segment = new LlmSegment(
LlmSegment::Type::Reasoning,
QDateTime::currentMSecsSinceEpoch(),
this);
LlmSegment::Type::Reasoning, QDateTime::currentMSecsSinceEpoch(), this);
segment->begin();
addSegment(segment);
return segment;
@@ -198,9 +174,7 @@ LlmSegment* ChatGeneration::beginToolCall(
const QString& name, const QString& toolCallId) {
closeOpenSegments();
auto* segment = new LlmSegment(
LlmSegment::Type::ToolCall,
QDateTime::currentMSecsSinceEpoch(),
this);
LlmSegment::Type::ToolCall, QDateTime::currentMSecsSinceEpoch(), this);
segment->setName(name);
segment->setToolCallId(toolCallId);
segment->setStatus(LlmSegment::Status::Running);
@@ -211,39 +185,33 @@ LlmSegment* ChatGeneration::beginToolCall(
}
void ChatGeneration::addSegment(LlmSegment* segment) {
if (!segment || m_segments.contains(segment))
return;
if (!segment || m_segments.contains(segment)) return;
segment->setParent(this);
connect(
segment, &LlmSegment::textChanged, this, [this, segment]() {
if (segment->type() == LlmSegment::Type::Reasoning)
Q_EMIT reasoningChanged();
else if (segment->type() == LlmSegment::Type::Content)
Q_EMIT contentChanged();
updateReasoningActive();
});
connect(
segment, &LlmSegment::statusChanged, this, [this]() {
Q_EMIT toolStateChanged();
});
connect(
segment, &LlmSegment::resultChanged, this, [this]() {
Q_EMIT toolStateChanged();
});
connect(
segment, &LlmSegment::runningChanged, this, [this]() {
Q_EMIT elapsedMsChanged();
Q_EMIT toolStateChanged();
updateReasoningActive();
});
connect(segment, &LlmSegment::textChanged, this, [this, segment]() {
if (segment->type() == LlmSegment::Type::Reasoning)
Q_EMIT reasoningChanged();
else if (segment->type() == LlmSegment::Type::Content)
Q_EMIT contentChanged();
updateReasoningActive();
});
connect(segment, &LlmSegment::statusChanged, this, [this]() {
Q_EMIT toolStateChanged();
});
connect(segment, &LlmSegment::resultChanged, this, [this]() {
Q_EMIT toolStateChanged();
});
connect(segment, &LlmSegment::runningChanged, this, [this]() {
Q_EMIT elapsedMsChanged();
Q_EMIT toolStateChanged();
updateReasoningActive();
});
m_segments.append(segment);
Q_EMIT segmentsChanged();
}
void ChatGeneration::closeOpenSegments() {
for (auto* segment : m_segments)
if (segment->running())
segment->close();
if (segment->running()) segment->close();
updateReasoningActive();
}