better chat list view + anchor to bottom. sqlite db for chats + LIFO-ordered
This commit is contained in:
+67
-100
@@ -1,123 +1,90 @@
|
||||
#include "message.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
#include "messagemodel.hpp"
|
||||
#include "session.hpp"
|
||||
|
||||
namespace ZShell {
|
||||
namespace ZShell::llm {
|
||||
|
||||
ChatMessage::ChatMessage(
|
||||
Role role,
|
||||
const QString& content,
|
||||
namespace {
|
||||
|
||||
ChatSession* sessionOf(const ChatMessage* message) {
|
||||
if (auto* model = qobject_cast<ChatMessageModel*>(message->parent()))
|
||||
return model->session();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatMessage::ChatMessage(Role role, qint64 timestamp, QObject* parent)
|
||||
: QObject(parent), m_role(role), m_timestamp(timestamp) {}
|
||||
|
||||
ChatGeneration* ChatMessage::addGeneration(
|
||||
qint64 timestamp,
|
||||
QObject* parent)
|
||||
: QObject(parent), m_role(role), m_content(content), m_timestamp(timestamp) {
|
||||
m_timer.setParent(this);
|
||||
m_timer.setInterval(500);
|
||||
m_timer.setTimerType(Qt::CoarseTimer);
|
||||
connect(&m_timer, &QTimer::timeout, this, [this]() {
|
||||
if (!reasoningInFlight() && !contentInFlight()) {
|
||||
m_timer.stop();
|
||||
return;
|
||||
}
|
||||
Q_EMIT elapsedMsChanged();
|
||||
});
|
||||
const QString& content,
|
||||
const QString& reasoning,
|
||||
qint64 reasoningElapsedMs,
|
||||
qint64 contentElapsedMs) {
|
||||
auto* generation = new ChatGeneration(timestamp, this);
|
||||
generation->setContent(content);
|
||||
generation->setReasoning(reasoning);
|
||||
generation->setElapsedMs(reasoningElapsedMs, contentElapsedMs);
|
||||
m_generations.append(generation);
|
||||
if (m_active < 0)
|
||||
m_active = static_cast<int>(m_generations.size() - 1);
|
||||
Q_EMIT generationsChanged();
|
||||
return generation;
|
||||
}
|
||||
|
||||
qint64 ChatMessage::reasoningElapsedMs() const {
|
||||
if (m_reasoningStartedAt <= 0)
|
||||
return 0;
|
||||
const qint64 end = m_reasoningEndedAt > 0
|
||||
? m_reasoningEndedAt
|
||||
: QDateTime::currentMSecsSinceEpoch();
|
||||
return end - m_reasoningStartedAt;
|
||||
ChatGeneration* ChatMessage::appendGeneration(qint64 timestamp) {
|
||||
auto* generation =
|
||||
addGeneration(timestamp, QString(), QString(), 0, 0);
|
||||
setActiveInternal(static_cast<int>(m_generations.size() - 1));
|
||||
return generation;
|
||||
}
|
||||
|
||||
qint64 ChatMessage::contentElapsedMs() const {
|
||||
if (m_contentStartedAt <= 0)
|
||||
return 0;
|
||||
const qint64 end = m_contentEndedAt > 0
|
||||
? m_contentEndedAt
|
||||
: QDateTime::currentMSecsSinceEpoch();
|
||||
return end - m_contentStartedAt;
|
||||
}
|
||||
|
||||
void ChatMessage::updateReasoningActive() {
|
||||
const bool active = m_streaming && m_content.isEmpty();
|
||||
if (m_reasoningActive == active)
|
||||
void ChatMessage::removeGeneration(ChatGeneration* generation) {
|
||||
const int index = static_cast<int>(m_generations.indexOf(generation));
|
||||
if (index < 0)
|
||||
return;
|
||||
m_reasoningActive = active;
|
||||
Q_EMIT reasoningActiveChanged();
|
||||
const bool wasActive = index == m_active;
|
||||
m_generations.removeAt(index);
|
||||
delete generation;
|
||||
if (m_generations.isEmpty()) {
|
||||
m_active = -1;
|
||||
} else if (m_active >= m_generations.size()) {
|
||||
m_active = static_cast<int>(m_generations.size() - 1);
|
||||
}
|
||||
Q_EMIT generationsChanged();
|
||||
if (wasActive)
|
||||
Q_EMIT activeGenerationChanged();
|
||||
}
|
||||
|
||||
void ChatMessage::appendContent(const QString& piece) {
|
||||
if (piece.isEmpty())
|
||||
void ChatMessage::setActiveInternal(int index) {
|
||||
if (index < 0 || index >= m_generations.size() || index == m_active)
|
||||
return;
|
||||
if (m_content.isEmpty()) {
|
||||
const qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
if (reasoningInFlight())
|
||||
m_reasoningEndedAt = now;
|
||||
m_contentStartedAt = now;
|
||||
if (!m_timer.isActive())
|
||||
m_timer.start();
|
||||
}
|
||||
m_content += piece;
|
||||
Q_EMIT contentChanged();
|
||||
updateReasoningActive();
|
||||
Q_EMIT elapsedMsChanged();
|
||||
m_active = index;
|
||||
Q_EMIT activeGenerationChanged();
|
||||
}
|
||||
|
||||
void ChatMessage::appendReasoning(const QString& piece) {
|
||||
if (piece.isEmpty())
|
||||
return;
|
||||
if (m_reasoning.isEmpty()) {
|
||||
if (m_reasoningStartedAt <= 0)
|
||||
m_reasoningStartedAt = QDateTime::currentMSecsSinceEpoch();
|
||||
if (!m_timer.isActive())
|
||||
m_timer.start();
|
||||
}
|
||||
m_reasoning += piece;
|
||||
Q_EMIT reasoningChanged();
|
||||
updateReasoningActive();
|
||||
Q_EMIT elapsedMsChanged();
|
||||
void ChatMessage::setActiveGeneration(int index) {
|
||||
setActiveInternal(index);
|
||||
}
|
||||
|
||||
void ChatMessage::setReasoning(const QString& value) {
|
||||
if (m_reasoning == value)
|
||||
return;
|
||||
m_reasoning = value;
|
||||
Q_EMIT reasoningChanged();
|
||||
void ChatMessage::edit(const QString& newContent) {
|
||||
if (auto* generation = activeGeneration())
|
||||
generation->setContent(newContent);
|
||||
if (auto* session = sessionOf(this))
|
||||
session->persist();
|
||||
}
|
||||
|
||||
void ChatMessage::setElapsedMs(qint64 reasoningMs, qint64 contentMs) {
|
||||
if (reasoningMs > 0) {
|
||||
m_reasoningStartedAt = m_timestamp;
|
||||
m_reasoningEndedAt = m_timestamp + reasoningMs;
|
||||
}
|
||||
if (contentMs > 0) {
|
||||
m_contentStartedAt = m_timestamp;
|
||||
m_contentEndedAt = m_timestamp + contentMs;
|
||||
}
|
||||
void ChatMessage::retry() {
|
||||
if (auto* session = sessionOf(this))
|
||||
session->retry(this);
|
||||
}
|
||||
|
||||
void ChatMessage::setStreaming(bool value) {
|
||||
if (m_streaming == value)
|
||||
return;
|
||||
m_streaming = value;
|
||||
Q_EMIT streamingChanged();
|
||||
if (value) {
|
||||
if (m_reasoningStartedAt <= 0)
|
||||
m_reasoningStartedAt = QDateTime::currentMSecsSinceEpoch();
|
||||
if (!m_timer.isActive())
|
||||
m_timer.start();
|
||||
} else {
|
||||
const qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
if (reasoningInFlight())
|
||||
m_reasoningEndedAt = now;
|
||||
if (contentInFlight())
|
||||
m_contentEndedAt = now;
|
||||
m_timer.stop();
|
||||
Q_EMIT elapsedMsChanged();
|
||||
}
|
||||
updateReasoningActive();
|
||||
void ChatMessage::generate() {
|
||||
if (auto* session = sessionOf(this))
|
||||
session->continueFrom(this);
|
||||
}
|
||||
|
||||
} // namespace ZShell
|
||||
} // namespace ZShell::llm
|
||||
|
||||
Reference in New Issue
Block a user