mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-07-23 19:51:05 -04:00
refactor: Decoupling chat architecture (#367)
* refactor: Migrate tests to Qt Creator plugin framework and update llmqore transports * refactor: Move conversation history into IDE-free session library * refactor: Extract turn context assembly behind injected ports * build: Add RunQodeAssistTests target for the plugin test suite * refactor: Route the chat through a Session and ChatBackend seam
This commit is contained in:
@@ -7,25 +7,27 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
#include "ChatModel.hpp"
|
||||
#include "GeneralSettings.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "providers/ProvidersManager.hpp"
|
||||
#include "context/ContextManager.hpp"
|
||||
#include "context/TokenUtils.hpp"
|
||||
#include "session/Session.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
InputTokenCounter::InputTokenCounter(
|
||||
ChatModel *chatModel, Context::ContextManager *contextManager, QObject *parent)
|
||||
Session::Session *session, Context::ContextManager *contextManager, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_chatModel(chatModel)
|
||||
, m_session(session)
|
||||
, m_contextManager(contextManager)
|
||||
{
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
@@ -47,10 +49,19 @@ InputTokenCounter::InputTokenCounter(
|
||||
recompute();
|
||||
});
|
||||
|
||||
m_recomputeTimer.setSingleShot(true);
|
||||
m_recomputeTimer.setInterval(150);
|
||||
connect(&m_recomputeTimer, &QTimer::timeout, this, &InputTokenCounter::recompute);
|
||||
|
||||
rewireToolsChangedConnection();
|
||||
recompute();
|
||||
}
|
||||
|
||||
void InputTokenCounter::recomputeSoon()
|
||||
{
|
||||
m_recomputeTimer.start();
|
||||
}
|
||||
|
||||
int InputTokenCounter::inputTokens() const
|
||||
{
|
||||
return m_inputTokens;
|
||||
@@ -59,7 +70,7 @@ int InputTokenCounter::inputTokens() const
|
||||
void InputTokenCounter::setMessage(const QString &message)
|
||||
{
|
||||
m_messageTokens = Context::TokenUtils::estimateTokens(message);
|
||||
recompute();
|
||||
recomputeSoon();
|
||||
}
|
||||
|
||||
void InputTokenCounter::setAttachments(const QStringList &attachments)
|
||||
@@ -92,6 +103,37 @@ void InputTokenCounter::rewireToolsChangedConnection()
|
||||
tm, &::LLMQore::ToolRegistry::toolsChanged, this, &InputTokenCounter::recompute);
|
||||
}
|
||||
|
||||
int InputTokenCounter::estimateFileTokens(const QStringList &paths)
|
||||
{
|
||||
if (paths.isEmpty())
|
||||
return 0;
|
||||
|
||||
int total = 0;
|
||||
QStringList uncached;
|
||||
|
||||
for (const QString &path : paths) {
|
||||
const QDateTime modified = QFileInfo(path).lastModified();
|
||||
const auto cached = m_fileTokens.constFind(path);
|
||||
if (cached != m_fileTokens.constEnd() && cached->modified == modified) {
|
||||
total += cached->tokens;
|
||||
continue;
|
||||
}
|
||||
uncached.append(path);
|
||||
}
|
||||
|
||||
if (m_fileTokens.size() > 256)
|
||||
m_fileTokens.clear();
|
||||
|
||||
for (const QString &path : std::as_const(uncached)) {
|
||||
const int tokens = Context::TokenUtils::estimateFilesTokens(
|
||||
m_contextManager->getContentFiles({path}));
|
||||
total += tokens;
|
||||
m_fileTokens.insert(path, {QFileInfo(path).lastModified(), tokens});
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void InputTokenCounter::recompute()
|
||||
{
|
||||
int inputTokens = m_messageTokens;
|
||||
@@ -115,25 +157,20 @@ void InputTokenCounter::recompute()
|
||||
if (!m_attachments.isEmpty()) {
|
||||
QStringList textPaths;
|
||||
inputTokens += splitImageEstimate(m_attachments, textPaths);
|
||||
if (!textPaths.isEmpty()) {
|
||||
auto attachFiles = m_contextManager->getContentFiles(textPaths);
|
||||
inputTokens += Context::TokenUtils::estimateFilesTokens(attachFiles);
|
||||
}
|
||||
inputTokens += estimateFileTokens(textPaths);
|
||||
}
|
||||
|
||||
if (!m_linkedFiles.isEmpty()) {
|
||||
QStringList textPaths;
|
||||
inputTokens += splitImageEstimate(m_linkedFiles, textPaths);
|
||||
if (!textPaths.isEmpty()) {
|
||||
auto linkFiles = m_contextManager->getContentFiles(textPaths);
|
||||
inputTokens += Context::TokenUtils::estimateFilesTokens(linkFiles);
|
||||
}
|
||||
inputTokens += estimateFileTokens(textPaths);
|
||||
}
|
||||
|
||||
const auto &history = m_chatModel->getChatHistory();
|
||||
for (const auto &message : history) {
|
||||
inputTokens += Context::TokenUtils::estimateTokens(message.content);
|
||||
inputTokens += 4; // + role
|
||||
if (m_session) {
|
||||
for (const Session::MessageRow &row : m_session->rows()) {
|
||||
inputTokens += Context::TokenUtils::estimateTokens(row.content);
|
||||
inputTokens += 4; // + role
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.enableChatTools()) {
|
||||
@@ -157,6 +194,11 @@ void InputTokenCounter::recompute()
|
||||
|
||||
void InputTokenCounter::recordSent()
|
||||
{
|
||||
if (m_recomputeTimer.isActive()) {
|
||||
m_recomputeTimer.stop();
|
||||
recompute();
|
||||
}
|
||||
|
||||
m_lastSentEstimate = m_calibrationFactor > 0.0
|
||||
? static_cast<int>(m_inputTokens / m_calibrationFactor)
|
||||
: m_inputTokens;
|
||||
|
||||
Reference in New Issue
Block a user