mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-22 15:03:19 -05:00
feat: Add Claude extended thinking (#254)
* feat: Add Claude extended thinking * fix: Set 1.0 temperature for thinking mode
This commit is contained in:
@ -18,6 +18,7 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
qml/parts/AttachedFilesPlace.qml
|
qml/parts/AttachedFilesPlace.qml
|
||||||
qml/parts/Toast.qml
|
qml/parts/Toast.qml
|
||||||
qml/ToolStatusItem.qml
|
qml/ToolStatusItem.qml
|
||||||
|
qml/ThinkingStatusItem.qml
|
||||||
qml/FileEditItem.qml
|
qml/FileEditItem.qml
|
||||||
qml/parts/RulesViewer.qml
|
qml/parts/RulesViewer.qml
|
||||||
qml/parts/FileEditsActionBar.qml
|
qml/parts/FileEditsActionBar.qml
|
||||||
@ -42,6 +43,8 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
icons/apply-changes-button.svg
|
icons/apply-changes-button.svg
|
||||||
icons/undo-changes-button.svg
|
icons/undo-changes-button.svg
|
||||||
icons/reject-changes-button.svg
|
icons/reject-changes-button.svg
|
||||||
|
icons/thinking-icon-on.svg
|
||||||
|
icons/thinking-icon-off.svg
|
||||||
|
|
||||||
SOURCES
|
SOURCES
|
||||||
ChatWidget.hpp ChatWidget.cpp
|
ChatWidget.hpp ChatWidget.cpp
|
||||||
|
|||||||
@ -81,6 +81,9 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
return filenames;
|
return filenames;
|
||||||
}
|
}
|
||||||
|
case Roles::IsRedacted: {
|
||||||
|
return message.isRedacted;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@ -92,6 +95,7 @@ QHash<int, QByteArray> ChatModel::roleNames() const
|
|||||||
roles[Roles::RoleType] = "roleType";
|
roles[Roles::RoleType] = "roleType";
|
||||||
roles[Roles::Content] = "content";
|
roles[Roles::Content] = "content";
|
||||||
roles[Roles::Attachments] = "attachments";
|
roles[Roles::Attachments] = "attachments";
|
||||||
|
roles[Roles::IsRedacted] = "isRedacted";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,6 +406,57 @@ void ChatModel::updateToolResult(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatModel::addThinkingBlock(
|
||||||
|
const QString &requestId, const QString &thinking, const QString &signature)
|
||||||
|
{
|
||||||
|
LOG_MESSAGE(QString("Adding thinking block: requestId=%1, thinking length=%2, signature length=%3")
|
||||||
|
.arg(requestId)
|
||||||
|
.arg(thinking.length())
|
||||||
|
.arg(signature.length()));
|
||||||
|
|
||||||
|
QString displayContent = thinking;
|
||||||
|
if (!signature.isEmpty()) {
|
||||||
|
displayContent += "\n[Signature: " + signature.left(40) + "...]";
|
||||||
|
}
|
||||||
|
|
||||||
|
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
||||||
|
Message thinkingMessage;
|
||||||
|
thinkingMessage.role = ChatRole::Thinking;
|
||||||
|
thinkingMessage.content = displayContent;
|
||||||
|
thinkingMessage.id = requestId;
|
||||||
|
thinkingMessage.isRedacted = false;
|
||||||
|
thinkingMessage.signature = signature;
|
||||||
|
m_messages.append(thinkingMessage);
|
||||||
|
endInsertRows();
|
||||||
|
LOG_MESSAGE(QString("Added thinking message at index %1 with signature length=%2")
|
||||||
|
.arg(m_messages.size() - 1).arg(signature.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatModel::addRedactedThinkingBlock(const QString &requestId, const QString &signature)
|
||||||
|
{
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("Adding redacted thinking block: requestId=%1, signature length=%2")
|
||||||
|
.arg(requestId)
|
||||||
|
.arg(signature.length()));
|
||||||
|
|
||||||
|
QString displayContent = "[Thinking content redacted by safety systems]";
|
||||||
|
if (!signature.isEmpty()) {
|
||||||
|
displayContent += "\n[Signature: " + signature.left(40) + "...]";
|
||||||
|
}
|
||||||
|
|
||||||
|
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
||||||
|
Message thinkingMessage;
|
||||||
|
thinkingMessage.role = ChatRole::Thinking;
|
||||||
|
thinkingMessage.content = displayContent;
|
||||||
|
thinkingMessage.id = requestId;
|
||||||
|
thinkingMessage.isRedacted = true;
|
||||||
|
thinkingMessage.signature = signature;
|
||||||
|
m_messages.append(thinkingMessage);
|
||||||
|
endInsertRows();
|
||||||
|
LOG_MESSAGE(QString("Added redacted thinking message at index %1 with signature length=%2")
|
||||||
|
.arg(m_messages.size() - 1).arg(signature.length()));
|
||||||
|
}
|
||||||
|
|
||||||
void ChatModel::updateMessageContent(const QString &messageId, const QString &newContent)
|
void ChatModel::updateMessageContent(const QString &messageId, const QString &newContent)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_messages.size(); ++i) {
|
for (int i = 0; i < m_messages.size(); ++i) {
|
||||||
|
|||||||
@ -37,10 +37,10 @@ class ChatModel : public QAbstractListModel
|
|||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ChatRole { System, User, Assistant, Tool, FileEdit };
|
enum ChatRole { System, User, Assistant, Tool, FileEdit, Thinking };
|
||||||
Q_ENUM(ChatRole)
|
Q_ENUM(ChatRole)
|
||||||
|
|
||||||
enum Roles { RoleType = Qt::UserRole, Content, Attachments };
|
enum Roles { RoleType = Qt::UserRole, Content, Attachments, IsRedacted };
|
||||||
Q_ENUM(Roles)
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
struct Message
|
struct Message
|
||||||
@ -48,6 +48,8 @@ public:
|
|||||||
ChatRole role;
|
ChatRole role;
|
||||||
QString content;
|
QString content;
|
||||||
QString id;
|
QString id;
|
||||||
|
bool isRedacted = false;
|
||||||
|
QString signature = QString();
|
||||||
|
|
||||||
QList<Context::ContentFile> attachments;
|
QList<Context::ContentFile> attachments;
|
||||||
};
|
};
|
||||||
@ -83,6 +85,9 @@ public:
|
|||||||
const QString &toolId,
|
const QString &toolId,
|
||||||
const QString &toolName,
|
const QString &toolName,
|
||||||
const QString &result);
|
const QString &result);
|
||||||
|
void addThinkingBlock(
|
||||||
|
const QString &requestId, const QString &thinking, const QString &signature);
|
||||||
|
void addRedactedThinkingBlock(const QString &requestId, const QString &signature);
|
||||||
void updateMessageContent(const QString &messageId, const QString &newContent);
|
void updateMessageContent(const QString &messageId, const QString &newContent);
|
||||||
|
|
||||||
void setLoadingFromHistory(bool loading);
|
void setLoadingFromHistory(bool loading);
|
||||||
|
|||||||
@ -43,6 +43,8 @@
|
|||||||
#include "context/ContextManager.hpp"
|
#include "context/ContextManager.hpp"
|
||||||
#include "context/TokenUtils.hpp"
|
#include "context/TokenUtils.hpp"
|
||||||
#include "llmcore/RulesLoader.hpp"
|
#include "llmcore/RulesLoader.hpp"
|
||||||
|
#include "ProvidersManager.hpp"
|
||||||
|
#include "GeneralSettings.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@ -197,12 +199,24 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
|
|
||||||
QSettings appSettings;
|
QSettings appSettings;
|
||||||
m_isAgentMode = appSettings.value("QodeAssist/Chat/AgentMode", false).toBool();
|
m_isAgentMode = appSettings.value("QodeAssist/Chat/AgentMode", false).toBool();
|
||||||
|
m_isThinkingMode = Settings::chatAssistantSettings().enableThinkingMode();
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&Settings::chatAssistantSettings().enableThinkingMode,
|
||||||
|
&Utils::BaseAspect::changed,
|
||||||
|
this,
|
||||||
|
[this]() { setIsThinkingMode(Settings::chatAssistantSettings().enableThinkingMode()); });
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
&Settings::toolsSettings().useTools,
|
&Settings::toolsSettings().useTools,
|
||||||
&Utils::BaseAspect::changed,
|
&Utils::BaseAspect::changed,
|
||||||
this,
|
this,
|
||||||
&ChatRootView::toolsSupportEnabledChanged);
|
&ChatRootView::toolsSupportEnabledChanged);
|
||||||
|
connect(
|
||||||
|
&Settings::generalSettings().caProvider,
|
||||||
|
&Utils::BaseAspect::changed,
|
||||||
|
this,
|
||||||
|
&ChatRootView::isThinkingSupportChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatModel *ChatRootView::chatModel() const
|
ChatModel *ChatRootView::chatModel() const
|
||||||
@ -779,6 +793,22 @@ void ChatRootView::setIsAgentMode(bool newIsAgentMode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatRootView::isThinkingMode() const
|
||||||
|
{
|
||||||
|
return m_isThinkingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::setIsThinkingMode(bool newIsThinkingMode)
|
||||||
|
{
|
||||||
|
if (m_isThinkingMode != newIsThinkingMode) {
|
||||||
|
m_isThinkingMode = newIsThinkingMode;
|
||||||
|
|
||||||
|
Settings::chatAssistantSettings().enableThinkingMode.setValue(newIsThinkingMode);
|
||||||
|
|
||||||
|
emit isThinkingModeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ChatRootView::toolsSupportEnabled() const
|
bool ChatRootView::toolsSupportEnabled() const
|
||||||
{
|
{
|
||||||
return Settings::toolsSettings().useTools();
|
return Settings::toolsSettings().useTools();
|
||||||
@ -1087,4 +1117,12 @@ QString ChatRootView::lastInfoMessage() const
|
|||||||
return m_lastInfoMessage;
|
return m_lastInfoMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatRootView::isThinkingSupport() const
|
||||||
|
{
|
||||||
|
auto providerName = Settings::generalSettings().caProvider();
|
||||||
|
auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
||||||
|
|
||||||
|
return provider && provider->supportThinking();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@ -49,6 +49,7 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(QVariantList activeRules READ activeRules NOTIFY activeRulesChanged FINAL)
|
Q_PROPERTY(QVariantList activeRules READ activeRules NOTIFY activeRulesChanged FINAL)
|
||||||
Q_PROPERTY(int activeRulesCount READ activeRulesCount NOTIFY activeRulesCountChanged FINAL)
|
Q_PROPERTY(int activeRulesCount READ activeRulesCount NOTIFY activeRulesCountChanged FINAL)
|
||||||
Q_PROPERTY(bool isAgentMode READ isAgentMode WRITE setIsAgentMode NOTIFY isAgentModeChanged FINAL)
|
Q_PROPERTY(bool isAgentMode READ isAgentMode WRITE setIsAgentMode NOTIFY isAgentModeChanged FINAL)
|
||||||
|
Q_PROPERTY(bool isThinkingMode READ isThinkingMode WRITE setIsThinkingMode NOTIFY isThinkingModeChanged FINAL)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
bool toolsSupportEnabled READ toolsSupportEnabled NOTIFY toolsSupportEnabledChanged FINAL)
|
bool toolsSupportEnabled READ toolsSupportEnabled NOTIFY toolsSupportEnabledChanged FINAL)
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(int currentMessageAppliedEdits READ currentMessageAppliedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
Q_PROPERTY(int currentMessageAppliedEdits READ currentMessageAppliedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
||||||
Q_PROPERTY(int currentMessagePendingEdits READ currentMessagePendingEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
Q_PROPERTY(int currentMessagePendingEdits READ currentMessagePendingEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
||||||
Q_PROPERTY(int currentMessageRejectedEdits READ currentMessageRejectedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
Q_PROPERTY(int currentMessageRejectedEdits READ currentMessageRejectedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
||||||
|
Q_PROPERTY(bool isThinkingSupport READ isThinkingSupport NOTIFY isThinkingSupportChanged FINAL)
|
||||||
|
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
@ -118,6 +120,8 @@ public:
|
|||||||
|
|
||||||
bool isAgentMode() const;
|
bool isAgentMode() const;
|
||||||
void setIsAgentMode(bool newIsAgentMode);
|
void setIsAgentMode(bool newIsAgentMode);
|
||||||
|
bool isThinkingMode() const;
|
||||||
|
void setIsThinkingMode(bool newIsThinkingMode);
|
||||||
bool toolsSupportEnabled() const;
|
bool toolsSupportEnabled() const;
|
||||||
|
|
||||||
Q_INVOKABLE void applyFileEdit(const QString &editId);
|
Q_INVOKABLE void applyFileEdit(const QString &editId);
|
||||||
@ -125,7 +129,6 @@ public:
|
|||||||
Q_INVOKABLE void undoFileEdit(const QString &editId);
|
Q_INVOKABLE void undoFileEdit(const QString &editId);
|
||||||
Q_INVOKABLE void openFileEditInEditor(const QString &editId);
|
Q_INVOKABLE void openFileEditInEditor(const QString &editId);
|
||||||
|
|
||||||
// Mass file edit operations for current message
|
|
||||||
Q_INVOKABLE void applyAllFileEditsForCurrentMessage();
|
Q_INVOKABLE void applyAllFileEditsForCurrentMessage();
|
||||||
Q_INVOKABLE void undoAllFileEditsForCurrentMessage();
|
Q_INVOKABLE void undoAllFileEditsForCurrentMessage();
|
||||||
Q_INVOKABLE void updateCurrentMessageEditsStats();
|
Q_INVOKABLE void updateCurrentMessageEditsStats();
|
||||||
@ -137,6 +140,8 @@ public:
|
|||||||
|
|
||||||
QString lastInfoMessage() const;
|
QString lastInfoMessage() const;
|
||||||
|
|
||||||
|
bool isThinkingSupport() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sendMessage(const QString &message);
|
void sendMessage(const QString &message);
|
||||||
void copyToClipboard(const QString &text);
|
void copyToClipboard(const QString &text);
|
||||||
@ -166,9 +171,12 @@ signals:
|
|||||||
void activeRulesCountChanged();
|
void activeRulesCountChanged();
|
||||||
|
|
||||||
void isAgentModeChanged();
|
void isAgentModeChanged();
|
||||||
|
void isThinkingModeChanged();
|
||||||
void toolsSupportEnabledChanged();
|
void toolsSupportEnabledChanged();
|
||||||
void currentMessageEditsStatsChanged();
|
void currentMessageEditsStatsChanged();
|
||||||
|
|
||||||
|
void isThinkingSupportChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateFileEditStatus(const QString &editId, const QString &status);
|
void updateFileEditStatus(const QString &editId, const QString &status);
|
||||||
QString getChatsHistoryDir() const;
|
QString getChatsHistoryDir() const;
|
||||||
@ -189,6 +197,7 @@ private:
|
|||||||
QString m_lastErrorMessage;
|
QString m_lastErrorMessage;
|
||||||
QVariantList m_activeRules;
|
QVariantList m_activeRules;
|
||||||
bool m_isAgentMode;
|
bool m_isAgentMode;
|
||||||
|
bool m_isThinkingMode;
|
||||||
|
|
||||||
QString m_currentMessageRequestId;
|
QString m_currentMessageRequestId;
|
||||||
int m_currentMessageTotalEdits{0};
|
int m_currentMessageTotalEdits{0};
|
||||||
|
|||||||
@ -83,6 +83,10 @@ QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message)
|
|||||||
messageObj["role"] = static_cast<int>(message.role);
|
messageObj["role"] = static_cast<int>(message.role);
|
||||||
messageObj["content"] = message.content;
|
messageObj["content"] = message.content;
|
||||||
messageObj["id"] = message.id;
|
messageObj["id"] = message.id;
|
||||||
|
messageObj["isRedacted"] = message.isRedacted;
|
||||||
|
if (!message.signature.isEmpty()) {
|
||||||
|
messageObj["signature"] = message.signature;
|
||||||
|
}
|
||||||
return messageObj;
|
return messageObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +96,8 @@ ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json)
|
|||||||
message.role = static_cast<ChatModel::ChatRole>(json["role"].toInt());
|
message.role = static_cast<ChatModel::ChatRole>(json["role"].toInt());
|
||||||
message.content = json["content"].toString();
|
message.content = json["content"].toString();
|
||||||
message.id = json["id"].toString();
|
message.id = json["id"].toString();
|
||||||
|
message.isRedacted = json["isRedacted"].toBool(false);
|
||||||
|
message.signature = json["signature"].toString();
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -119,7 +119,15 @@ void ClientInterface::sendMessage(
|
|||||||
if (msg.role == ChatModel::ChatRole::Tool || msg.role == ChatModel::ChatRole::FileEdit) {
|
if (msg.role == ChatModel::ChatRole::Tool || msg.role == ChatModel::ChatRole::FileEdit) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
messages.append({msg.role == ChatModel::ChatRole::User ? "user" : "assistant", msg.content});
|
|
||||||
|
LLMCore::Message apiMessage;
|
||||||
|
apiMessage.role = msg.role == ChatModel::ChatRole::User ? "user" : "assistant";
|
||||||
|
apiMessage.content = msg.content;
|
||||||
|
apiMessage.isThinking = (msg.role == ChatModel::ChatRole::Thinking);
|
||||||
|
apiMessage.isRedacted = msg.isRedacted;
|
||||||
|
apiMessage.signature = msg.signature;
|
||||||
|
|
||||||
|
messages.append(apiMessage);
|
||||||
}
|
}
|
||||||
context.history = messages;
|
context.history = messages;
|
||||||
|
|
||||||
@ -189,6 +197,18 @@ void ClientInterface::sendMessage(
|
|||||||
this,
|
this,
|
||||||
&ClientInterface::handleCleanAccumulatedData,
|
&ClientInterface::handleCleanAccumulatedData,
|
||||||
Qt::UniqueConnection);
|
Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
provider,
|
||||||
|
&LLMCore::Provider::thinkingBlockReceived,
|
||||||
|
m_chatModel,
|
||||||
|
&ChatModel::addThinkingBlock,
|
||||||
|
Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
provider,
|
||||||
|
&LLMCore::Provider::redactedThinkingBlockReceived,
|
||||||
|
m_chatModel,
|
||||||
|
&ChatModel::addRedactedThinkingBlock,
|
||||||
|
Qt::UniqueConnection);
|
||||||
|
|
||||||
provider->sendRequest(requestId, config.url, config.providerRequest);
|
provider->sendRequest(requestId, config.url, config.providerRequest);
|
||||||
}
|
}
|
||||||
|
|||||||
4
ChatView/icons/thinking-icon-off.svg
Normal file
4
ChatView/icons/thinking-icon-off.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.4445 9.32233C17.7036 7.28556 21.8559 7.75441 25.8713 9.68854C27.428 9.4057 30.1744 8.91006 31.6477 9.47565C34.5351 10.5309 36.6339 12.7385 37.0285 14.9805C37.81 15.3756 38.4502 15.9932 38.8635 16.751C39.7282 18.3354 39.8498 19.9232 39.2678 21.2061C39.8159 22.277 39.9974 23.4913 39.7844 24.67C39.663 25.4561 39.3556 26.2047 38.8869 26.8555C38.4183 27.5062 37.8013 28.0419 37.0842 28.42C36.8857 28.5274 34.5887 28.6167 34.3713 28.6885C34.6443 32.2168 30.9868 33.5005 27.8889 32.6602L29.0403 36.586L26.0803 36.6885L23.8713 31.6885L21.8713 29.6885C20.125 30.1697 17.0919 30.168 15.76 28.0831C15.639 27.8916 15.5299 27.693 15.4319 27.4893C15.0931 27.5567 14.7474 27.5909 14.4016 27.5919C13.415 27.5918 11.771 27.3037 10.9358 26.7393C10.2736 26.3112 9.74862 25.7095 9.42014 25.004C7.64097 25.2413 6.13134 24.8334 5.14474 23.8262C3.8951 22.5721 3.72021 18.9738 4.37131 16.751C5.22965 13.7841 7.6818 12.9427 12.8713 11.6885C13.3214 11.1426 13.8387 9.69851 14.4445 9.32233ZM21.2551 15.0001L20.9358 16.1114L19.8723 16.4444L19.3401 15.5557L18.4895 16.3331L19.0217 17.2217L18.383 18.2217L17.2131 18.0001L17.0002 18.8887L18.0637 19.4444V20.5557L17.0002 21.1114L17.2131 22.0001L18.383 21.7774L19.0217 22.7774L18.4895 23.6671L19.3401 24.4444L19.8723 23.5557L20.9358 23.8887L21.2551 25.0001H22.7444L23.0637 23.8887L24.1272 23.5557L24.6594 24.4444L25.511 23.6671L24.9787 22.7774L25.6174 21.7774L26.7873 22.0001L27.0002 21.1114L25.9358 20.5557V19.4444L27.0002 18.8887L26.7873 18.0001L25.6174 18.2217L24.9787 17.2217L25.6174 16.4444L24.6594 15.5557L24.1272 16.4444L23.0637 16.1114L22.7444 15.0001H21.2551Z" fill="black"/>
|
||||||
|
<path d="M6 35L38 6" stroke="black" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
3
ChatView/icons/thinking-icon-on.svg
Normal file
3
ChatView/icons/thinking-icon-on.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.4445 9.32233C17.7036 7.28556 21.8559 7.75441 25.8713 9.68854C27.428 9.4057 30.1744 8.91006 31.6477 9.47565C34.5351 10.5309 36.6339 12.7385 37.0285 14.9805C37.81 15.3756 38.4502 15.9932 38.8635 16.751C39.7282 18.3354 39.8498 19.9232 39.2678 21.2061C39.8159 22.277 39.9974 23.4913 39.7844 24.67C39.663 25.4561 39.3556 26.2047 38.8869 26.8555C38.4183 27.5062 37.8013 28.0419 37.0842 28.42C36.8857 28.5274 34.5887 28.6167 34.3713 28.6885C34.6443 32.2168 30.9868 33.5005 27.8889 32.6602L29.0403 36.586L26.0803 36.6885L23.8713 31.6885L21.8713 29.6885C20.125 30.1697 17.0919 30.168 15.76 28.0831C15.639 27.8916 15.5299 27.693 15.4319 27.4893C15.0931 27.5567 14.7474 27.5909 14.4016 27.5919C13.415 27.5918 11.771 27.3037 10.9358 26.7393C10.2736 26.3112 9.74862 25.7095 9.42014 25.004C7.64097 25.2413 6.13134 24.8334 5.14474 23.8262C3.8951 22.5721 3.72021 18.9738 4.37131 16.751C5.22965 13.7841 7.6818 12.9427 12.8713 11.6885C13.3214 11.1426 13.8387 9.69851 14.4445 9.32233ZM21.2551 15.0001L20.9358 16.1114L19.8723 16.4444L19.3401 15.5557L18.4895 16.3331L19.0217 17.2217L18.383 18.2217L17.2131 18.0001L17.0002 18.8887L18.0637 19.4444V20.5557L17.0002 21.1114L17.2131 22.0001L18.383 21.7774L19.0217 22.7774L18.4895 23.6671L19.3401 24.4444L19.8723 23.5557L20.9358 23.8887L21.2551 25.0001H22.7444L23.0637 23.8887L24.1272 23.5557L24.6594 24.4444L25.511 23.6671L24.9787 22.7774L25.6174 21.7774L26.7873 22.0001L27.0002 21.1114L25.9358 20.5557V19.4444L27.0002 18.8887L26.7873 18.0001L25.6174 18.2217L24.9787 17.2217L25.6174 16.4444L24.6594 15.5557L24.1272 16.4444L23.0637 16.1114L22.7444 15.0001H21.2551Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@ -91,6 +91,13 @@ ChatRootView {
|
|||||||
root.isAgentMode = agentModeSwitch.checked
|
root.isAgentMode = agentModeSwitch.checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
thinkingMode {
|
||||||
|
checked: root.isThinkingMode
|
||||||
|
enabled: root.isThinkingSupport
|
||||||
|
onCheckedChanged: {
|
||||||
|
root.isThinkingMode = thinkingMode.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
@ -116,6 +123,8 @@ ChatRootView {
|
|||||||
return toolMessageComponent
|
return toolMessageComponent
|
||||||
} else if (model.roleType === ChatModel.FileEdit) {
|
} else if (model.roleType === ChatModel.FileEdit) {
|
||||||
return fileEditMessageComponent
|
return fileEditMessageComponent
|
||||||
|
} else if (model.roleType === ChatModel.Thinking) {
|
||||||
|
return thinkingMessageComponent
|
||||||
} else {
|
} else {
|
||||||
return chatItemComponent
|
return chatItemComponent
|
||||||
}
|
}
|
||||||
@ -199,6 +208,35 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: thinkingMessageComponent
|
||||||
|
|
||||||
|
ThinkingStatusItem {
|
||||||
|
width: parent.width
|
||||||
|
thinkingContent: {
|
||||||
|
// Extract thinking content and signature
|
||||||
|
let content = model.content
|
||||||
|
let signatureStart = content.indexOf("\n[Signature:")
|
||||||
|
if (signatureStart >= 0) {
|
||||||
|
return content.substring(0, signatureStart)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
signature: {
|
||||||
|
let content = model.content
|
||||||
|
let signatureStart = content.indexOf("\n[Signature: ")
|
||||||
|
if (signatureStart >= 0) {
|
||||||
|
let signatureEnd = content.indexOf("...]", signatureStart)
|
||||||
|
if (signatureEnd >= 0) {
|
||||||
|
return content.substring(signatureStart + 13, signatureEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
isRedacted: model.isRedacted !== undefined ? model.isRedacted : false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|||||||
181
ChatView/qml/ThinkingStatusItem.qml
Normal file
181
ChatView/qml/ThinkingStatusItem.qml
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Qt.labs.platform as Platform
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string thinkingContent: ""
|
||||||
|
property string signature: ""
|
||||||
|
property bool isRedacted: false
|
||||||
|
property bool expanded: false
|
||||||
|
|
||||||
|
radius: 6
|
||||||
|
color: palette.base
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: header
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: headerRow.height + 10
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.expanded = !root.expanded
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: headerRow
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: 10
|
||||||
|
}
|
||||||
|
width: parent.width
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: root.isRedacted ? qsTr("Thinking (Redacted)")
|
||||||
|
: qsTr("Thinking")
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.bold: true
|
||||||
|
color: palette.text
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.expanded ? "▼" : "▶"
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: palette.mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: contentColumn
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: header.bottom
|
||||||
|
margins: 10
|
||||||
|
}
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: root.isRedacted
|
||||||
|
width: parent.width
|
||||||
|
text: qsTr("Thinking content was redacted by safety systems")
|
||||||
|
font.pixelSize: 11
|
||||||
|
font.italic: true
|
||||||
|
color: Qt.rgba(0.8, 0.4, 0.4, 1.0)
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEdit {
|
||||||
|
id: thinkingText
|
||||||
|
|
||||||
|
visible: !root.isRedacted
|
||||||
|
width: parent.width
|
||||||
|
text: root.thinkingContent
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
color: palette.text
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: 11
|
||||||
|
selectionColor: palette.highlight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rectangle {
|
||||||
|
// visible: root.signature.length > 0 && root.expanded
|
||||||
|
// width: parent.width
|
||||||
|
// height: signatureText.height + 10
|
||||||
|
// color: palette.alternateBase
|
||||||
|
// radius: 4
|
||||||
|
|
||||||
|
// Text {
|
||||||
|
// id: signatureText
|
||||||
|
|
||||||
|
// anchors {
|
||||||
|
// left: parent.left
|
||||||
|
// right: parent.right
|
||||||
|
// verticalCenter: parent.verticalCenter
|
||||||
|
// margins: 5
|
||||||
|
// }
|
||||||
|
// text: qsTr("Signature: %1").arg(root.signature.substring(0, Math.min(40, root.signature.length)) + "...")
|
||||||
|
// font.pixelSize: 9
|
||||||
|
// font.family: "monospace"
|
||||||
|
// color: palette.mid
|
||||||
|
// elide: Text.ElideRight
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onClicked: contextMenu.open()
|
||||||
|
propagateComposedEvents: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.Menu {
|
||||||
|
id: contextMenu
|
||||||
|
|
||||||
|
Platform.MenuItem {
|
||||||
|
text: root.expanded ? qsTr("Collapse") : qsTr("Expand")
|
||||||
|
onTriggered: root.expanded = !root.expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: thinkingMarker
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 3
|
||||||
|
height: root.height - root.radius
|
||||||
|
color: root.isRedacted ? Qt.rgba(0.8, 0.3, 0.3, 0.9)
|
||||||
|
: (root.color.hslLightness > 0.5 ? Qt.darker(palette.alternateBase, 1.3)
|
||||||
|
: Qt.lighter(palette.alternateBase, 1.3))
|
||||||
|
radius: root.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
when: !root.expanded
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
implicitHeight: header.height
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
when: root.expanded
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
implicitHeight: header.height + contentColumn.height + 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@ -35,6 +35,7 @@ Rectangle {
|
|||||||
property alias pinButton: pinButtonId
|
property alias pinButton: pinButtonId
|
||||||
property alias rulesButton: rulesButtonId
|
property alias rulesButton: rulesButtonId
|
||||||
property alias agentModeSwitch: agentModeSwitchId
|
property alias agentModeSwitch: agentModeSwitchId
|
||||||
|
property alias thinkingMode: thinkingModeId
|
||||||
property alias activeRulesCount: activeRulesCountId.text
|
property alias activeRulesCount: activeRulesCountId.text
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
@ -50,39 +51,70 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
QoAButton {
|
Row {
|
||||||
id: pinButtonId
|
height: agentModeSwitchId.height
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
checkable: true
|
QoAButton {
|
||||||
|
id: pinButtonId
|
||||||
|
|
||||||
icon {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
source: checked ? "qrc:/qt/qml/ChatView/icons/window-lock.svg"
|
checkable: true
|
||||||
: "qrc:/qt/qml/ChatView/icons/window-unlock.svg"
|
|
||||||
color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF"
|
|
||||||
height: 15
|
|
||||||
width: 15
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: checked ? qsTr("Unpin chat window")
|
|
||||||
: qsTr("Pin chat window to the top")
|
|
||||||
}
|
|
||||||
|
|
||||||
QoATextSlider {
|
icon {
|
||||||
id: agentModeSwitchId
|
source: checked ? "qrc:/qt/qml/ChatView/icons/window-lock.svg"
|
||||||
|
: "qrc:/qt/qml/ChatView/icons/window-unlock.svg"
|
||||||
leftText: "chat"
|
color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF"
|
||||||
rightText: "AI Agent"
|
height: 15
|
||||||
|
width: 15
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: {
|
|
||||||
if (!agentModeSwitchId.enabled) {
|
|
||||||
return qsTr("Tools are disabled in General Settings")
|
|
||||||
}
|
}
|
||||||
return checked
|
ToolTip.visible: hovered
|
||||||
? qsTr("Agent Mode: AI can use tools to read files, search project, and build code")
|
ToolTip.delay: 250
|
||||||
: qsTr("Chat Mode: Simple conversation without tool access")
|
ToolTip.text: checked ? qsTr("Unpin chat window")
|
||||||
|
: qsTr("Pin chat window to the top")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoATextSlider {
|
||||||
|
id: agentModeSwitchId
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
leftText: "chat"
|
||||||
|
rightText: "AI Agent"
|
||||||
|
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: 250
|
||||||
|
ToolTip.text: {
|
||||||
|
if (!agentModeSwitchId.enabled) {
|
||||||
|
return qsTr("Tools are disabled in General Settings")
|
||||||
|
}
|
||||||
|
return checked
|
||||||
|
? qsTr("Agent Mode: AI can use tools to read files, search project, and build code")
|
||||||
|
: qsTr("Chat Mode: Simple conversation without tool access")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: thinkingModeId
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
checkable: true
|
||||||
|
opacity: enabled ? 1.0 : 0.2
|
||||||
|
|
||||||
|
icon {
|
||||||
|
source: checked ? "qrc:/qt/qml/ChatView/icons/thinking-icon-on.svg"
|
||||||
|
: "qrc:/qt/qml/ChatView/icons/thinking-icon-off.svg"
|
||||||
|
color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF"
|
||||||
|
height: 15
|
||||||
|
width: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: 250
|
||||||
|
ToolTip.text: enabled ? (checked ? qsTr("Thinking Mode enabled (Check model list support it)")
|
||||||
|
: qsTr("Thinking Mode disabled"))
|
||||||
|
: qsTr("Thinking Mode is not available for this provider")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -137,4 +137,68 @@ private:
|
|||||||
QString m_result;
|
QString m_result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ThinkingContent : public ContentBlock
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ThinkingContent(const QString &thinking = QString(), const QString &signature = QString())
|
||||||
|
: ContentBlock()
|
||||||
|
, m_thinking(thinking)
|
||||||
|
, m_signature(signature)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString type() const override { return "thinking"; }
|
||||||
|
QString thinking() const { return m_thinking; }
|
||||||
|
QString signature() const { return m_signature; }
|
||||||
|
void appendThinking(const QString &text) { m_thinking += text; }
|
||||||
|
void setThinking(const QString &text) { m_thinking = text; }
|
||||||
|
void setSignature(const QString &signature) { m_signature = signature; }
|
||||||
|
|
||||||
|
QJsonValue toJson(ProviderFormat format) const override
|
||||||
|
{
|
||||||
|
Q_UNUSED(format);
|
||||||
|
// Only include signature field if it's not empty
|
||||||
|
// Empty signature is rejected by API with "Invalid signature" error
|
||||||
|
// In streaming mode, signature is not provided, so we omit the field entirely
|
||||||
|
QJsonObject obj{{"type", "thinking"}, {"thinking", m_thinking}};
|
||||||
|
if (!m_signature.isEmpty()) {
|
||||||
|
obj["signature"] = m_signature;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_thinking;
|
||||||
|
QString m_signature;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RedactedThinkingContent : public ContentBlock
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit RedactedThinkingContent(const QString &signature = QString())
|
||||||
|
: ContentBlock()
|
||||||
|
, m_signature(signature)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString type() const override { return "redacted_thinking"; }
|
||||||
|
QString signature() const { return m_signature; }
|
||||||
|
void setSignature(const QString &signature) { m_signature = signature; }
|
||||||
|
|
||||||
|
QJsonValue toJson(ProviderFormat format) const override
|
||||||
|
{
|
||||||
|
Q_UNUSED(format);
|
||||||
|
// Only include signature field if it's not empty
|
||||||
|
// Empty signature is rejected by API with "Invalid signature" error
|
||||||
|
QJsonObject obj{{"type", "redacted_thinking"}};
|
||||||
|
if (!m_signature.isEmpty()) {
|
||||||
|
obj["signature"] = m_signature;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_signature;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@ -28,6 +28,9 @@ struct Message
|
|||||||
{
|
{
|
||||||
QString role;
|
QString role;
|
||||||
QString content;
|
QString content;
|
||||||
|
QString signature;
|
||||||
|
bool isThinking = false;
|
||||||
|
bool isRedacted = false;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
bool operator==(const Message&) const = default;
|
bool operator==(const Message&) const = default;
|
||||||
|
|||||||
@ -65,6 +65,7 @@ public:
|
|||||||
= 0;
|
= 0;
|
||||||
|
|
||||||
virtual bool supportsTools() const { return false; };
|
virtual bool supportsTools() const { return false; };
|
||||||
|
virtual bool supportThinking() const { return false; };
|
||||||
|
|
||||||
virtual void cancelRequest(const RequestID &requestId);
|
virtual void cancelRequest(const RequestID &requestId);
|
||||||
|
|
||||||
@ -92,6 +93,9 @@ signals:
|
|||||||
const QString &toolName,
|
const QString &toolName,
|
||||||
const QString &result);
|
const QString &result);
|
||||||
void continuationStarted(const QodeAssist::LLMCore::RequestID &requestId);
|
void continuationStarted(const QodeAssist::LLMCore::RequestID &requestId);
|
||||||
|
void thinkingBlockReceived(
|
||||||
|
const QString &requestId, const QString &thinking, const QString &signature);
|
||||||
|
void redactedThinkingBlockReceived(const QString &requestId, const QString &signature);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QJsonObject parseEventLine(const QString &line);
|
QJsonObject parseEventLine(const QString &line);
|
||||||
|
|||||||
@ -46,6 +46,19 @@ void ClaudeMessage::handleContentBlockStart(
|
|||||||
|
|
||||||
addCurrentContent<LLMCore::ToolUseContent>(toolId, toolName, toolInput);
|
addCurrentContent<LLMCore::ToolUseContent>(toolId, toolName, toolInput);
|
||||||
m_pendingToolInputs[index] = "";
|
m_pendingToolInputs[index] = "";
|
||||||
|
|
||||||
|
} else if (blockType == "thinking") {
|
||||||
|
QString thinking = data["thinking"].toString();
|
||||||
|
QString signature = data["signature"].toString();
|
||||||
|
LOG_MESSAGE(QString("ClaudeMessage: Creating thinking block with signature length=%1")
|
||||||
|
.arg(signature.length()));
|
||||||
|
addCurrentContent<LLMCore::ThinkingContent>(thinking, signature);
|
||||||
|
|
||||||
|
} else if (blockType == "redacted_thinking") {
|
||||||
|
QString signature = data["signature"].toString();
|
||||||
|
LOG_MESSAGE(QString("ClaudeMessage: Creating redacted_thinking block with signature length=%1")
|
||||||
|
.arg(signature.length()));
|
||||||
|
addCurrentContent<LLMCore::RedactedThinkingContent>(signature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +79,24 @@ void ClaudeMessage::handleContentBlockDelta(
|
|||||||
if (m_pendingToolInputs.contains(index)) {
|
if (m_pendingToolInputs.contains(index)) {
|
||||||
m_pendingToolInputs[index] += partialJson;
|
m_pendingToolInputs[index] += partialJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (deltaType == "thinking_delta") {
|
||||||
|
if (auto thinkingContent = qobject_cast<LLMCore::ThinkingContent *>(m_currentBlocks[index])) {
|
||||||
|
thinkingContent->appendThinking(delta["thinking"].toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (deltaType == "signature_delta") {
|
||||||
|
if (auto thinkingContent = qobject_cast<LLMCore::ThinkingContent *>(m_currentBlocks[index])) {
|
||||||
|
QString signature = delta["signature"].toString();
|
||||||
|
thinkingContent->setSignature(signature);
|
||||||
|
LOG_MESSAGE(QString("Set signature for thinking block %1: length=%2")
|
||||||
|
.arg(index).arg(signature.length()));
|
||||||
|
} else if (auto redactedContent = qobject_cast<LLMCore::RedactedThinkingContent *>(m_currentBlocks[index])) {
|
||||||
|
QString signature = delta["signature"].toString();
|
||||||
|
redactedContent->setSignature(signature);
|
||||||
|
LOG_MESSAGE(QString("Set signature for redacted_thinking block %1: length=%2")
|
||||||
|
.arg(index).arg(signature.length()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,11 +135,17 @@ QJsonObject ClaudeMessage::toProviderFormat() const
|
|||||||
message["role"] = "assistant";
|
message["role"] = "assistant";
|
||||||
|
|
||||||
QJsonArray content;
|
QJsonArray content;
|
||||||
|
|
||||||
for (auto block : m_currentBlocks) {
|
for (auto block : m_currentBlocks) {
|
||||||
content.append(block->toJson(LLMCore::ProviderFormat::Claude));
|
QJsonValue blockJson = block->toJson(LLMCore::ProviderFormat::Claude);
|
||||||
|
content.append(blockJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
message["content"] = content;
|
message["content"] = content;
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("ClaudeMessage::toProviderFormat - message with %1 content block(s)")
|
||||||
|
.arg(m_currentBlocks.size()));
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +175,28 @@ QList<LLMCore::ToolUseContent *> ClaudeMessage::getCurrentToolUseContent() const
|
|||||||
return toolBlocks;
|
return toolBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<LLMCore::ThinkingContent *> ClaudeMessage::getCurrentThinkingContent() const
|
||||||
|
{
|
||||||
|
QList<LLMCore::ThinkingContent *> thinkingBlocks;
|
||||||
|
for (auto block : m_currentBlocks) {
|
||||||
|
if (auto thinkingContent = qobject_cast<LLMCore::ThinkingContent *>(block)) {
|
||||||
|
thinkingBlocks.append(thinkingContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return thinkingBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<LLMCore::RedactedThinkingContent *> ClaudeMessage::getCurrentRedactedThinkingContent() const
|
||||||
|
{
|
||||||
|
QList<LLMCore::RedactedThinkingContent *> redactedBlocks;
|
||||||
|
for (auto block : m_currentBlocks) {
|
||||||
|
if (auto redactedContent = qobject_cast<LLMCore::RedactedThinkingContent *>(block)) {
|
||||||
|
redactedBlocks.append(redactedContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redactedBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
void ClaudeMessage::startNewContinuation()
|
void ClaudeMessage::startNewContinuation()
|
||||||
{
|
{
|
||||||
LOG_MESSAGE(QString("ClaudeMessage: Starting new continuation"));
|
LOG_MESSAGE(QString("ClaudeMessage: Starting new continuation"));
|
||||||
|
|||||||
@ -39,6 +39,9 @@ public:
|
|||||||
|
|
||||||
LLMCore::MessageState state() const { return m_state; }
|
LLMCore::MessageState state() const { return m_state; }
|
||||||
QList<LLMCore::ToolUseContent *> getCurrentToolUseContent() const;
|
QList<LLMCore::ToolUseContent *> getCurrentToolUseContent() const;
|
||||||
|
QList<LLMCore::ThinkingContent *> getCurrentThinkingContent() const;
|
||||||
|
QList<LLMCore::RedactedThinkingContent *> getCurrentRedactedThinkingContent() const;
|
||||||
|
const QList<LLMCore::ContentBlock *> &getCurrentBlocks() const { return m_currentBlocks; }
|
||||||
|
|
||||||
void startNewContinuation();
|
void startNewContinuation();
|
||||||
|
|
||||||
|
|||||||
@ -86,7 +86,6 @@ void ClaudeProvider::prepareRequest(
|
|||||||
|
|
||||||
auto applyModelParams = [&request](const auto &settings) {
|
auto applyModelParams = [&request](const auto &settings) {
|
||||||
request["max_tokens"] = settings.maxTokens();
|
request["max_tokens"] = settings.maxTokens();
|
||||||
request["temperature"] = settings.temperature();
|
|
||||||
if (settings.useTopP())
|
if (settings.useTopP())
|
||||||
request["top_p"] = settings.topP();
|
request["top_p"] = settings.topP();
|
||||||
if (settings.useTopK())
|
if (settings.useTopK())
|
||||||
@ -96,8 +95,21 @@ void ClaudeProvider::prepareRequest(
|
|||||||
|
|
||||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
applyModelParams(Settings::codeCompletionSettings());
|
||||||
|
request["temperature"] = Settings::codeCompletionSettings().temperature();
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||||
|
applyModelParams(chatSettings);
|
||||||
|
|
||||||
|
if (chatSettings.enableThinkingMode()) {
|
||||||
|
QJsonObject thinkingObj;
|
||||||
|
thinkingObj["type"] = "enabled";
|
||||||
|
thinkingObj["budget_tokens"] = chatSettings.thinkingBudgetTokens();
|
||||||
|
request["thinking"] = thinkingObj;
|
||||||
|
request["max_tokens"] = chatSettings.thinkingMaxTokens();
|
||||||
|
request["temperature"] = 1.0;
|
||||||
|
} else {
|
||||||
|
request["temperature"] = chatSettings.temperature();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isToolsEnabled) {
|
if (isToolsEnabled) {
|
||||||
@ -169,7 +181,8 @@ QList<QString> ClaudeProvider::validateRequest(const QJsonObject &request, LLMCo
|
|||||||
{"top_k", {}},
|
{"top_k", {}},
|
||||||
{"stop", QJsonArray{}},
|
{"stop", QJsonArray{}},
|
||||||
{"stream", {}},
|
{"stream", {}},
|
||||||
{"tools", {}}};
|
{"tools", {}},
|
||||||
|
{"thinking", QJsonObject{{"type", {}}, {"budget_tokens", {}}}}};
|
||||||
|
|
||||||
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
||||||
}
|
}
|
||||||
@ -220,6 +233,10 @@ bool ClaudeProvider::supportsTools() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ClaudeProvider::supportThinking() const {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
void ClaudeProvider::cancelRequest(const LLMCore::RequestID &requestId)
|
void ClaudeProvider::cancelRequest(const LLMCore::RequestID &requestId)
|
||||||
{
|
{
|
||||||
LOG_MESSAGE(QString("ClaudeProvider: Cancelling request %1").arg(requestId));
|
LOG_MESSAGE(QString("ClaudeProvider: Cancelling request %1").arg(requestId));
|
||||||
@ -308,7 +325,14 @@ void ClaudeProvider::onToolExecutionComplete(
|
|||||||
messages.append(userMessage);
|
messages.append(userMessage);
|
||||||
|
|
||||||
continuationRequest["messages"] = messages;
|
continuationRequest["messages"] = messages;
|
||||||
|
|
||||||
|
if (continuationRequest.contains("thinking")) {
|
||||||
|
QJsonObject thinkingObj = continuationRequest["thinking"].toObject();
|
||||||
|
LOG_MESSAGE(QString("Thinking mode preserved for continuation: type=%1, budget=%2 tokens")
|
||||||
|
.arg(thinkingObj["type"].toString())
|
||||||
|
.arg(thinkingObj["budget_tokens"].toInt()));
|
||||||
|
}
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Sending continuation request for %1 with %2 tool results")
|
LOG_MESSAGE(QString("Sending continuation request for %1 with %2 tool results")
|
||||||
.arg(requestId)
|
.arg(requestId)
|
||||||
.arg(toolResults.size()));
|
.arg(toolResults.size()));
|
||||||
@ -347,6 +371,13 @@ void ClaudeProvider::processStreamEvent(const QString &requestId, const QJsonObj
|
|||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
QString("Adding new content block: type=%1, index=%2").arg(blockType).arg(index));
|
QString("Adding new content block: type=%1, index=%2").arg(blockType).arg(index));
|
||||||
|
|
||||||
|
if (blockType == "thinking" || blockType == "redacted_thinking") {
|
||||||
|
QJsonDocument eventDoc(event);
|
||||||
|
LOG_MESSAGE(QString("content_block_start event for %1: %2")
|
||||||
|
.arg(blockType)
|
||||||
|
.arg(QString::fromUtf8(eventDoc.toJson(QJsonDocument::Compact))));
|
||||||
|
}
|
||||||
|
|
||||||
message->handleContentBlockStart(index, blockType, contentBlock);
|
message->handleContentBlockStart(index, blockType, contentBlock);
|
||||||
|
|
||||||
@ -362,12 +393,90 @@ void ClaudeProvider::processStreamEvent(const QString &requestId, const QJsonObj
|
|||||||
LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
|
LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
|
||||||
buffers.responseContent += text;
|
buffers.responseContent += text;
|
||||||
emit partialResponseReceived(requestId, text);
|
emit partialResponseReceived(requestId, text);
|
||||||
|
} else if (deltaType == "signature_delta") {
|
||||||
|
QString signature = delta["signature"].toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (eventType == "content_block_stop") {
|
} else if (eventType == "content_block_stop") {
|
||||||
int index = event["index"].toInt();
|
int index = event["index"].toInt();
|
||||||
|
|
||||||
|
auto allBlocks = message->getCurrentBlocks();
|
||||||
|
if (index < allBlocks.size()) {
|
||||||
|
QString blockType = allBlocks[index]->type();
|
||||||
|
if (blockType == "thinking" || blockType == "redacted_thinking") {
|
||||||
|
QJsonDocument eventDoc(event);
|
||||||
|
LOG_MESSAGE(QString("content_block_stop event for %1 at index %2: %3")
|
||||||
|
.arg(blockType)
|
||||||
|
.arg(index)
|
||||||
|
.arg(QString::fromUtf8(eventDoc.toJson(QJsonDocument::Compact))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.contains("content_block")) {
|
||||||
|
QJsonObject contentBlock = event["content_block"].toObject();
|
||||||
|
QString blockType = contentBlock["type"].toString();
|
||||||
|
|
||||||
|
if (blockType == "thinking") {
|
||||||
|
QString signature = contentBlock["signature"].toString();
|
||||||
|
if (!signature.isEmpty()) {
|
||||||
|
auto allBlocks = message->getCurrentBlocks();
|
||||||
|
if (index < allBlocks.size()) {
|
||||||
|
if (auto thinkingContent = qobject_cast<LLMCore::ThinkingContent *>(allBlocks[index])) {
|
||||||
|
thinkingContent->setSignature(signature);
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("Updated thinking block signature from content_block_stop, "
|
||||||
|
"signature length=%1")
|
||||||
|
.arg(signature.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (blockType == "redacted_thinking") {
|
||||||
|
QString signature = contentBlock["signature"].toString();
|
||||||
|
if (!signature.isEmpty()) {
|
||||||
|
auto allBlocks = message->getCurrentBlocks();
|
||||||
|
if (index < allBlocks.size()) {
|
||||||
|
if (auto redactedContent = qobject_cast<LLMCore::RedactedThinkingContent *>(allBlocks[index])) {
|
||||||
|
redactedContent->setSignature(signature);
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("Updated redacted_thinking block signature from content_block_stop, "
|
||||||
|
"signature length=%1")
|
||||||
|
.arg(signature.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message->handleContentBlockStop(index);
|
message->handleContentBlockStop(index);
|
||||||
|
|
||||||
|
auto thinkingBlocks = message->getCurrentThinkingContent();
|
||||||
|
for (auto thinkingContent : thinkingBlocks) {
|
||||||
|
auto allBlocks = message->getCurrentBlocks();
|
||||||
|
if (index < allBlocks.size() && allBlocks[index] == thinkingContent) {
|
||||||
|
emit thinkingBlockReceived(
|
||||||
|
requestId, thinkingContent->thinking(), thinkingContent->signature());
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("Emitted thinking block for request %1, thinking length=%2, signature length=%3")
|
||||||
|
.arg(requestId)
|
||||||
|
.arg(thinkingContent->thinking().length())
|
||||||
|
.arg(thinkingContent->signature().length()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto redactedBlocks = message->getCurrentRedactedThinkingContent();
|
||||||
|
for (auto redactedContent : redactedBlocks) {
|
||||||
|
auto allBlocks = message->getCurrentBlocks();
|
||||||
|
if (index < allBlocks.size() && allBlocks[index] == redactedContent) {
|
||||||
|
emit redactedThinkingBlockReceived(requestId, redactedContent->signature());
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("Emitted redacted thinking block for request %1, signature length=%2")
|
||||||
|
.arg(requestId)
|
||||||
|
.arg(redactedContent->signature().length()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (eventType == "message_delta") {
|
} else if (eventType == "message_delta") {
|
||||||
QJsonObject delta = event["delta"].toObject();
|
QJsonObject delta = event["delta"].toObject();
|
||||||
if (delta.contains("stop_reason")) {
|
if (delta.contains("stop_reason")) {
|
||||||
|
|||||||
@ -53,6 +53,7 @@ public:
|
|||||||
const LLMCore::RequestID &requestId, const QUrl &url, const QJsonObject &payload) override;
|
const LLMCore::RequestID &requestId, const QUrl &url, const QJsonObject &payload) override;
|
||||||
|
|
||||||
bool supportsTools() const override;
|
bool supportsTools() const override;
|
||||||
|
bool supportThinking() const override;
|
||||||
void cancelRequest(const LLMCore::RequestID &requestId) override;
|
void cancelRequest(const LLMCore::RequestID &requestId) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|||||||
@ -78,7 +78,7 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
|
|
||||||
maxTokens.setSettingsKey(Constants::CA_MAX_TOKENS);
|
maxTokens.setSettingsKey(Constants::CA_MAX_TOKENS);
|
||||||
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
||||||
maxTokens.setRange(-1, 10000);
|
maxTokens.setRange(-1, 200000); // -1 for unlimited, 200k max for extended output
|
||||||
maxTokens.setDefaultValue(2000);
|
maxTokens.setDefaultValue(2000);
|
||||||
|
|
||||||
// Advanced Parameters
|
// Advanced Parameters
|
||||||
@ -144,6 +144,30 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
contextWindow.setRange(-1, 10000);
|
contextWindow.setRange(-1, 10000);
|
||||||
contextWindow.setDefaultValue(2048);
|
contextWindow.setDefaultValue(2048);
|
||||||
|
|
||||||
|
// Extended Thinking Settings
|
||||||
|
enableThinkingMode.setSettingsKey(Constants::CA_ENABLE_THINKING_MODE);
|
||||||
|
enableThinkingMode.setLabelText(Tr::tr("Enable extended thinking mode (Claude only).\n Temperature is 1.0 accordingly API requerement"));
|
||||||
|
enableThinkingMode.setToolTip(
|
||||||
|
Tr::tr("Enable Claude's extended thinking mode for complex reasoning tasks. "
|
||||||
|
"This provides step-by-step reasoning before the final answer."));
|
||||||
|
enableThinkingMode.setDefaultValue(false);
|
||||||
|
|
||||||
|
thinkingBudgetTokens.setSettingsKey(Constants::CA_THINKING_BUDGET_TOKENS);
|
||||||
|
thinkingBudgetTokens.setLabelText(Tr::tr("Thinking budget tokens:"));
|
||||||
|
thinkingBudgetTokens.setToolTip(
|
||||||
|
Tr::tr("Maximum number of tokens Claude can use for internal reasoning. "
|
||||||
|
"Larger budgets improve quality but increase latency. Minimum: 1024, Recommended: 10000-16000."));
|
||||||
|
thinkingBudgetTokens.setRange(1024, 100000);
|
||||||
|
thinkingBudgetTokens.setDefaultValue(10000);
|
||||||
|
|
||||||
|
thinkingMaxTokens.setSettingsKey(Constants::CA_THINKING_MAX_TOKENS);
|
||||||
|
thinkingMaxTokens.setLabelText(Tr::tr("Thinking mode max output tokens:"));
|
||||||
|
thinkingMaxTokens.setToolTip(
|
||||||
|
Tr::tr("Maximum number of tokens for the final response when thinking mode is enabled. "
|
||||||
|
"Set to -1 to use the default max tokens setting. Recommended: 4096-16000."));
|
||||||
|
thinkingMaxTokens.setRange(-1, 200000);
|
||||||
|
thinkingMaxTokens.setDefaultValue(16000);
|
||||||
|
|
||||||
autosave.setDefaultValue(true);
|
autosave.setDefaultValue(true);
|
||||||
autosave.setLabelText(Tr::tr("Enable autosave when message received"));
|
autosave.setLabelText(Tr::tr("Enable autosave when message received"));
|
||||||
|
|
||||||
@ -237,6 +261,10 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
ollamaGrid.addRow({ollamaLivetime});
|
ollamaGrid.addRow({ollamaLivetime});
|
||||||
ollamaGrid.addRow({contextWindow});
|
ollamaGrid.addRow({contextWindow});
|
||||||
|
|
||||||
|
auto thinkingGrid = Grid{};
|
||||||
|
thinkingGrid.addRow({thinkingBudgetTokens});
|
||||||
|
thinkingGrid.addRow({thinkingMaxTokens});
|
||||||
|
|
||||||
auto chatViewSettingsGrid = Grid{};
|
auto chatViewSettingsGrid = Grid{};
|
||||||
chatViewSettingsGrid.addRow({textFontFamily, textFontSize});
|
chatViewSettingsGrid.addRow({textFontFamily, textFontSize});
|
||||||
chatViewSettingsGrid.addRow({codeFontFamily, codeFontSize});
|
chatViewSettingsGrid.addRow({codeFontFamily, codeFontSize});
|
||||||
@ -269,6 +297,9 @@ ChatAssistantSettings::ChatAssistantSettings()
|
|||||||
systemPrompt,
|
systemPrompt,
|
||||||
}},
|
}},
|
||||||
Group{title(Tr::tr("Ollama Settings")), Column{Row{ollamaGrid, Stretch{1}}}},
|
Group{title(Tr::tr("Ollama Settings")), Column{Row{ollamaGrid, Stretch{1}}}},
|
||||||
|
Group{
|
||||||
|
title(Tr::tr("Extended Thinking (Claude Only)")),
|
||||||
|
Column{enableThinkingMode, Row{thinkingGrid, Stretch{1}}}},
|
||||||
Group{title(Tr::tr("Chat Settings")), Row{chatViewSettingsGrid, Stretch{1}}},
|
Group{title(Tr::tr("Chat Settings")), Row{chatViewSettingsGrid, Stretch{1}}},
|
||||||
Stretch{1}};
|
Stretch{1}};
|
||||||
});
|
});
|
||||||
@ -308,6 +339,9 @@ void ChatAssistantSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(systemPrompt);
|
resetAspect(systemPrompt);
|
||||||
resetAspect(ollamaLivetime);
|
resetAspect(ollamaLivetime);
|
||||||
resetAspect(contextWindow);
|
resetAspect(contextWindow);
|
||||||
|
resetAspect(enableThinkingMode);
|
||||||
|
resetAspect(thinkingBudgetTokens);
|
||||||
|
resetAspect(thinkingMaxTokens);
|
||||||
resetAspect(linkOpenFiles);
|
resetAspect(linkOpenFiles);
|
||||||
resetAspect(textFontFamily);
|
resetAspect(textFontFamily);
|
||||||
resetAspect(codeFontFamily);
|
resetAspect(codeFontFamily);
|
||||||
|
|||||||
@ -64,6 +64,11 @@ public:
|
|||||||
Utils::StringAspect ollamaLivetime{this};
|
Utils::StringAspect ollamaLivetime{this};
|
||||||
Utils::IntegerAspect contextWindow{this};
|
Utils::IntegerAspect contextWindow{this};
|
||||||
|
|
||||||
|
// Extended Thinking Settings (Claude only)
|
||||||
|
Utils::BoolAspect enableThinkingMode{this};
|
||||||
|
Utils::IntegerAspect thinkingBudgetTokens{this};
|
||||||
|
Utils::IntegerAspect thinkingMaxTokens{this};
|
||||||
|
|
||||||
// Visuals settings
|
// Visuals settings
|
||||||
Utils::SelectionAspect textFontFamily{this};
|
Utils::SelectionAspect textFontFamily{this};
|
||||||
Utils::IntegerAspect textFontSize{this};
|
Utils::IntegerAspect textFontSize{this};
|
||||||
|
|||||||
@ -168,6 +168,9 @@ const char CA_USE_FREQUENCY_PENALTY[] = "QodeAssist.chatUseFrequencyPenalty";
|
|||||||
const char CA_FREQUENCY_PENALTY[] = "QodeAssist.chatFrequencyPenalty";
|
const char CA_FREQUENCY_PENALTY[] = "QodeAssist.chatFrequencyPenalty";
|
||||||
const char CA_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime";
|
const char CA_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime";
|
||||||
const char CA_OLLAMA_CONTEXT_WINDOW[] = "QodeAssist.caOllamaContextWindow";
|
const char CA_OLLAMA_CONTEXT_WINDOW[] = "QodeAssist.caOllamaContextWindow";
|
||||||
|
const char CA_ENABLE_THINKING_MODE[] = "QodeAssist.caEnableThinkingMode";
|
||||||
|
const char CA_THINKING_BUDGET_TOKENS[] = "QodeAssist.caThinkingBudgetTokens";
|
||||||
|
const char CA_THINKING_MAX_TOKENS[] = "QodeAssist.caThinkingMaxTokens";
|
||||||
const char CA_TEXT_FONT_FAMILY[] = "QodeAssist.caTextFontFamily";
|
const char CA_TEXT_FONT_FAMILY[] = "QodeAssist.caTextFontFamily";
|
||||||
const char CA_TEXT_FONT_SIZE[] = "QodeAssist.caTextFontSize";
|
const char CA_TEXT_FONT_SIZE[] = "QodeAssist.caTextFontSize";
|
||||||
const char CA_CODE_FONT_FAMILY[] = "QodeAssist.caCodeFontFamily";
|
const char CA_CODE_FONT_FAMILY[] = "QodeAssist.caCodeFontFamily";
|
||||||
|
|||||||
@ -42,7 +42,33 @@ public:
|
|||||||
if (context.history) {
|
if (context.history) {
|
||||||
for (const auto &msg : context.history.value()) {
|
for (const auto &msg : context.history.value()) {
|
||||||
if (msg.role != "system") {
|
if (msg.role != "system") {
|
||||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
// Handle thinking blocks with structured content
|
||||||
|
if (msg.isThinking) {
|
||||||
|
// Create content array with thinking block
|
||||||
|
QJsonArray content;
|
||||||
|
QJsonObject thinkingBlock;
|
||||||
|
thinkingBlock["type"] = msg.isRedacted ? "redacted_thinking" : "thinking";
|
||||||
|
|
||||||
|
// Extract actual thinking text (remove display signature)
|
||||||
|
QString thinkingText = msg.content;
|
||||||
|
int signaturePos = thinkingText.indexOf("\n[Signature: ");
|
||||||
|
if (signaturePos != -1) {
|
||||||
|
thinkingText = thinkingText.left(signaturePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!msg.isRedacted) {
|
||||||
|
thinkingBlock["thinking"] = thinkingText;
|
||||||
|
}
|
||||||
|
if (!msg.signature.isEmpty()) {
|
||||||
|
thinkingBlock["signature"] = msg.signature;
|
||||||
|
}
|
||||||
|
content.append(thinkingBlock);
|
||||||
|
|
||||||
|
messages.append(QJsonObject{{"role", "assistant"}, {"content", content}});
|
||||||
|
} else {
|
||||||
|
// Normal message
|
||||||
|
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user