139 lines
3.1 KiB
C++
139 lines
3.1 KiB
C++
#include "segment.hpp"
|
|
|
|
#include "markdownparser.hpp"
|
|
|
|
#include <QDateTime>
|
|
|
|
namespace ZShell::llm {
|
|
|
|
namespace {
|
|
// Re-parse cadence while a segment streams; content between refreshes is
|
|
// at most this stale.
|
|
constexpr int kMarkdownRefreshMs = 150;
|
|
} // namespace
|
|
|
|
LlmSegment::LlmSegment(Type type, qint64 timestamp, QObject* parent)
|
|
: QObject(parent), m_type(type), m_timestamp(timestamp) {
|
|
m_markdownTimer.setInterval(kMarkdownRefreshMs);
|
|
connect(
|
|
&m_markdownTimer, &QTimer::timeout, this, [this]() {
|
|
if (!m_markdownDirty)
|
|
return;
|
|
m_markdownDirty = false;
|
|
parseMarkdown();
|
|
});
|
|
}
|
|
|
|
qint64 LlmSegment::elapsedMs() const {
|
|
if (m_startedAt <= 0)
|
|
return 0;
|
|
const qint64 end = m_endedAt > 0 ? m_endedAt
|
|
: QDateTime::currentMSecsSinceEpoch();
|
|
return end - m_startedAt;
|
|
}
|
|
|
|
void LlmSegment::begin() {
|
|
if (m_running)
|
|
return;
|
|
m_running = true;
|
|
m_startedAt = QDateTime::currentMSecsSinceEpoch();
|
|
m_endedAt = 0;
|
|
Q_EMIT runningChanged();
|
|
Q_EMIT elapsedMsChanged();
|
|
}
|
|
|
|
void LlmSegment::close() {
|
|
if (m_running) {
|
|
m_running = false;
|
|
m_endedAt = QDateTime::currentMSecsSinceEpoch();
|
|
Q_EMIT runningChanged();
|
|
Q_EMIT elapsedMsChanged();
|
|
}
|
|
// The segment is final; parse immediately so the UI does not wait
|
|
// out the debounce.
|
|
if (m_type == Type::Content && m_markdownDirty) {
|
|
m_markdownTimer.stop();
|
|
parseMarkdown();
|
|
}
|
|
}
|
|
|
|
void LlmSegment::appendText(const QString& piece) {
|
|
if (piece.isEmpty())
|
|
return;
|
|
m_text += piece;
|
|
Q_EMIT textChanged();
|
|
scheduleMarkdown();
|
|
}
|
|
|
|
void LlmSegment::setText(const QString& value) {
|
|
if (m_text == value)
|
|
return;
|
|
m_text = value;
|
|
Q_EMIT textChanged();
|
|
scheduleMarkdown();
|
|
}
|
|
|
|
void LlmSegment::setName(const QString& value) {
|
|
if (m_name == value)
|
|
return;
|
|
m_name = value;
|
|
Q_EMIT nameChanged();
|
|
}
|
|
|
|
void LlmSegment::setToolCallId(const QString& value) {
|
|
if (m_toolCallId == value)
|
|
return;
|
|
m_toolCallId = value;
|
|
Q_EMIT toolCallIdChanged();
|
|
}
|
|
|
|
void LlmSegment::appendArguments(const QString& piece) {
|
|
if (piece.isEmpty())
|
|
return;
|
|
m_arguments += piece;
|
|
Q_EMIT argumentsChanged();
|
|
}
|
|
|
|
void LlmSegment::setResult(const QString& value) {
|
|
if (m_result == value)
|
|
return;
|
|
m_result = value;
|
|
Q_EMIT resultChanged();
|
|
}
|
|
|
|
void LlmSegment::setStatus(Status value) {
|
|
if (m_status == value)
|
|
return;
|
|
m_status = value;
|
|
Q_EMIT statusChanged();
|
|
}
|
|
|
|
void LlmSegment::finishTool(const QString& resultText, bool success) {
|
|
setResult(resultText);
|
|
setStatus(success ? Status::Success : Status::Error);
|
|
close();
|
|
}
|
|
|
|
void LlmSegment::restore(qint64 elapsedMs) {
|
|
m_startedAt = m_timestamp;
|
|
m_endedAt = m_timestamp + qMax<qint64>(0, elapsedMs);
|
|
}
|
|
|
|
void LlmSegment::scheduleMarkdown() {
|
|
// Content segments only; reasoning/tool output is never parsed.
|
|
// (User content is parsed too, so it can later be rendered as blocks
|
|
// as well; the QML currently only does that for assistant messages.)
|
|
if (m_type != Type::Content)
|
|
return;
|
|
m_markdownDirty = true;
|
|
if (!m_markdownTimer.isActive())
|
|
m_markdownTimer.start();
|
|
}
|
|
|
|
void LlmSegment::parseMarkdown() {
|
|
m_markdown = MarkdownParser::parse(m_text);
|
|
Q_EMIT markdownChanged();
|
|
}
|
|
|
|
} // namespace ZShell::llm
|