add markdown parsing + latex + tree-sitter highlighting for codeblocks in llm responses
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user