Skip to content

Commit bbc26a5

Browse files
committed
Refactor Chat History to Store Full Conversations Instead of Prompts
Replace the simple prompt history with a full conversation history that preserves the entire chat context including both user and assistant messages. This enables restoring previous chat sessions with their complete message history. The configuration now serializes conversations as structured JSON arrays with role and content fields under the "chat_history" key, replacing the flat "history" string array. The ChatHistoryDialog has been updated to work with Conversation objects instead of raw prompt strings. When starting a new session or closing the window, the current conversation is automatically saved. Selecting a history entry now restores the full conversation to both the display and the LLM client context. * Plugin/ai/Config - Add Conversation/ChatHistory types and new serialization format * Plugin/ai/LLMManager - Add GetConversation/LoadConversation methods * Plugin/ai/ChatHistoryDialog - Update to handle Conversation objects * Plugin/ai/ChatAIWindow - Save conversations on close and session change, restore full history on selection * submodules/assistant - Update submodule reference ** Generated by CodeLite. ** Signed-off-by: Eran Ifrah <eran@codelite.org>
1 parent bca0a52 commit bbc26a5

File tree

9 files changed

+213
-127
lines changed

9 files changed

+213
-127
lines changed

Plugin/ai/ChatAIWindow.cpp

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ ChatAIWindow::ChatAIWindow(wxWindow* parent, ChatAI* plugin)
4747
clAuiToolBarArt::AddTool(m_toolbar, wxID_STOP, _("Stop"), images->LoadBitmap("execute_stop"));
4848
m_toolbar->AddSeparator();
4949
clAuiToolBarArt::AddTool(m_toolbar, wxID_REFRESH, _("Restart the client"), images->LoadBitmap("debugger_restart"));
50-
clAuiToolBarArt::AddTool(
51-
m_toolbar, XRCID("prompt_history"), _("Show prompt history"), images->LoadBitmap("history"));
50+
clAuiToolBarArt::AddTool(m_toolbar, XRCID("chat_history"), _("Show chat history"), images->LoadBitmap("history"));
5251
clAuiToolBarArt::AddTool(m_toolbar,
5352
XRCID("auto_scroll"),
5453
_("Enable auto scrolling"),
@@ -96,15 +95,15 @@ ChatAIWindow::ChatAIWindow(wxWindow* parent, ChatAI* plugin)
9695
Bind(wxEVT_MENU, &ChatAIWindow::OnSend, this, wxID_EXECUTE);
9796
Bind(wxEVT_MENU, &ChatAIWindow::OnStop, this, wxID_STOP);
9897
Bind(wxEVT_MENU, &ChatAIWindow::OnAutoScroll, this, XRCID("auto_scroll"));
99-
Bind(wxEVT_MENU, &ChatAIWindow::OnHistory, this, XRCID("prompt_history"));
98+
Bind(wxEVT_MENU, &ChatAIWindow::OnHistory, this, XRCID("chat_history"));
10099
Bind(wxEVT_MENU, &ChatAIWindow::OnDetachView, this, XRCID("detach_view"));
101100

102101
Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnDetachViewUI, this, XRCID("detach_view"));
103102
Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnSendUI, this, wxID_EXECUTE);
104103
Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnStopUI, this, wxID_STOP);
105104
Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnClearOutputViewUI, this, wxID_CLEAR);
106105
Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnAutoScrollUI, this, XRCID("auto_scroll"));
107-
Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnHistoryUI, this, XRCID("prompt_history"));
106+
Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnHistoryUI, this, XRCID("chat_history"));
108107
m_choiceEndpoints->Bind(wxEVT_UPDATE_UI, &ChatAIWindow::OnBusyUI, this);
109108
UpdateChoices();
110109

@@ -142,6 +141,11 @@ ChatAIWindow::~ChatAIWindow()
142141

143142
clConfig::Get().Write("chat-ai/sash-position", m_mainSplitter->GetSashPosition());
144143
clConfig::Get().Write("chat-ai/enable-tools", m_checkboxEnableTools->IsChecked());
144+
145+
// Store the current session
146+
auto conversation = llm::Manager::GetInstance().GetConversation();
147+
llm::Manager::GetInstance().GetConfig().AddConversation(conversation);
148+
llm::Manager::GetInstance().GetConfig().Save();
145149
}
146150

147151
void ChatAIWindow::OnSend(wxCommandEvent& event)
@@ -171,12 +175,6 @@ void ChatAIWindow::DoSendPrompt()
171175
}
172176
llm::Manager::GetInstance().Chat(this, prompt, m_cancel_token, chat_options);
173177

174-
// Remember this prompt in the history.
175-
if (!prompt.empty()) {
176-
llm::Manager::GetInstance().GetConfig().AddHistory(prompt);
177-
llm::Manager::GetInstance().GetConfig().Save();
178-
}
179-
180178
prompt.Prepend(wxString() << "\n**" << ::wxGetUserId() << "**:\n");
181179
AppendOutput(prompt + "\n\n");
182180
m_stcInput->ClearAll();
@@ -304,6 +302,10 @@ void ChatAIWindow::DoClearOutputView()
304302
void ChatAIWindow::OnNewSession(wxCommandEvent& event)
305303
{
306304
wxUnusedVar(event);
305+
// Store the current conversation
306+
auto conversation = llm::Manager::GetInstance().GetConversation();
307+
llm::Manager::GetInstance().GetConfig().AddConversation(conversation);
308+
llm::Manager::GetInstance().GetConfig().Save();
307309
DoClearOutputView();
308310
}
309311

@@ -481,15 +483,26 @@ void ChatAIWindow::OnHistory(wxCommandEvent& event)
481483
return;
482484
}
483485

484-
// Update the prompt field.
485-
m_stcInput->SetText(dlg.GetSelectedPrompt());
486+
const auto& conversation = dlg.GetSelectedConversation();
487+
// Restore the chat text
488+
for (const auto& msg : conversation) {
489+
if (msg.role == "assistant") {
490+
AppendOutput("**assistant**:\n");
491+
AppendOutput(wxString() << wxString::FromUTF8(msg.text) << "\n\n");
492+
} else if (msg.role == "user") {
493+
AppendOutput(wxString() << "**" << ::wxGetUserId() << "**:\n");
494+
AppendOutput(wxString() << wxString::FromUTF8(msg.text) << "\n\n");
495+
}
496+
}
497+
StyleOutput();
498+
llm::Manager::GetInstance().LoadConversation(conversation);
486499
m_stcInput->CallAfter(&wxStyledTextCtrl::SetFocus);
487500
}
488501

489502
void ChatAIWindow::OnHistoryUI(wxUpdateUIEvent& event)
490503
{
491504
const auto& config = llm::Manager::GetInstance().GetConfig();
492-
event.Enable(!config.GetHistory().IsEmpty() && m_state == ChatState::kReady);
505+
event.Enable(!config.GetHistory().empty() && m_state == ChatState::kReady);
493506
}
494507

495508
void ChatAIWindow::OnStop(wxCommandEvent& event)

Plugin/ai/ChatHistoryDialog.cpp

Lines changed: 19 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,25 @@
22

33
#include "ai/LLMManager.hpp"
44

5-
namespace
6-
{
7-
/**
8-
* @brief Creates a truncated label string with ellipsis if the input text exceeds a specified size.
9-
*
10-
* If the input text is longer than or equal to the specified size, this function
11-
* truncates it and appends "..." to indicate truncation. Otherwise, the original
12-
* text is returned unchanged.
13-
*
14-
* @param text The input string to be processed into a label.
15-
* @param size The maximum length of the resulting label (default is 100). Must be
16-
* at least 3 to accommodate the ellipsis when truncation occurs.
17-
*
18-
* @return wxString The resulting label, either truncated with "..." or the original text.
19-
*/
20-
wxString CreateLabel(const wxString& text, size_t size = 100)
21-
{
22-
wxString label = text.BeforeFirst('\n');
23-
label.Trim().Trim(false);
24-
if (label.size() >= size) {
25-
return label.Mid(0, size - 3) << "...";
26-
}
27-
return label;
28-
}
29-
} // namespace
30-
315
ChatHistoryDialog::ChatHistoryDialog(wxWindow* parent)
326
: ChatHistoryDialogBase(parent)
337
{
34-
auto prompts = llm::Manager::GetInstance().GetConfig().GetHistory();
35-
for (auto& prompt : prompts) {
36-
prompt.Trim().Trim(false);
37-
if (prompt.empty()) {
8+
auto history = llm::Manager::GetInstance().GetConfig().GetHistory();
9+
for (const auto& conversation : history) {
10+
if (conversation.empty()) {
11+
continue;
12+
}
13+
auto res = llm::Config::GetConversationLabel(conversation);
14+
if (!res.has_value()) {
3815
continue;
3916
}
17+
18+
auto label = wxString::FromUTF8(res.value());
4019
wxVector<wxVariant> cols;
41-
// Ensure that each line in the table contains a single line (not multi-line)
42-
// as multi-line entries may not render correctly on some platforms.
43-
auto label = CreateLabel(prompt);
4420
cols.push_back(label);
45-
auto p = std::make_shared<wxString>(prompt);
21+
auto p = std::make_shared<llm::Conversation>(conversation);
4622
m_dvListCtrlPrompts->AppendItem(cols, reinterpret_cast<wxUIntPtr>(p.get()));
47-
m_prompts.push_back(p);
23+
m_coversations.push_back(p); // Keep a copy to keep the `p` pointer valid.
4824
}
4925
m_dvListCtrlPrompts->CallAfter(&wxDataViewListCtrl::SetFocus);
5026
}
@@ -83,7 +59,7 @@ void ChatHistoryDialog::OnDelete(wxCommandEvent& event)
8359
}
8460

8561
// Update the history.
86-
llm::Manager::GetInstance().GetConfig().SetHistory(GetPrompts());
62+
llm::Manager::GetInstance().GetConfig().SetHistory(GetHistory());
8763
llm::Manager::GetInstance().GetConfig().Save();
8864
}
8965

@@ -117,7 +93,7 @@ void ChatHistoryDialog::SetSelectionAndEndModal(const wxDataViewItem& item)
11793
return;
11894
}
11995

120-
auto cd = reinterpret_cast<wxString*>(m_dvListCtrlPrompts->GetItemData(item));
96+
auto cd = reinterpret_cast<llm::Conversation*>(m_dvListCtrlPrompts->GetItemData(item));
12197
if (!cd) {
12298
clERROR() << "History entry does not have client data associated with it." << endl;
12399
EndModal(wxID_CANCEL);
@@ -128,13 +104,13 @@ void ChatHistoryDialog::SetSelectionAndEndModal(const wxDataViewItem& item)
128104
EndModal(wxID_OK);
129105
}
130106

131-
wxArrayString ChatHistoryDialog::GetPrompts() const
107+
llm::ChatHistory ChatHistoryDialog::GetHistory() const
132108
{
133-
wxArrayString prompts;
134-
prompts.reserve(m_dvListCtrlPrompts->GetItemCount());
109+
llm::ChatHistory v;
110+
v.reserve(m_dvListCtrlPrompts->GetItemCount());
135111
for (auto i = 0; i < m_dvListCtrlPrompts->GetItemCount(); ++i) {
136-
prompts.push_back(
137-
*reinterpret_cast<wxString*>(m_dvListCtrlPrompts->GetItemData(m_dvListCtrlPrompts->RowToItem(i))));
112+
v.push_back(
113+
*reinterpret_cast<llm::Conversation*>(m_dvListCtrlPrompts->GetItemData(m_dvListCtrlPrompts->RowToItem(i))));
138114
}
139-
return prompts;
115+
return v;
140116
}

Plugin/ai/ChatHistoryDialog.hpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#pragma once
22

33
#include "UI.hpp"
4+
#include "ai/Config.hpp"
45
#include "codelite_exports.h"
56

6-
#include <memory>
77
#include <vector>
88

99
class WXDLLIMPEXP_SDK ChatHistoryDialog : public ChatHistoryDialogBase
@@ -12,8 +12,11 @@ class WXDLLIMPEXP_SDK ChatHistoryDialog : public ChatHistoryDialogBase
1212
ChatHistoryDialog(wxWindow* parent);
1313
~ChatHistoryDialog() override;
1414

15-
void SetSelectedPrompt(const wxString& selectedPrompt) { this->m_selectedPrompt = selectedPrompt; }
16-
const wxString& GetSelectedPrompt() const { return m_selectedPrompt; }
15+
inline void SetSelectedPrompt(const llm::Conversation& conversation)
16+
{
17+
this->m_selectedConversation = conversation;
18+
}
19+
inline const llm::Conversation& GetSelectedConversation() const { return m_selectedConversation; }
1720

1821
protected:
1922
void OnInsert(wxCommandEvent& event) override;
@@ -25,9 +28,9 @@ class WXDLLIMPEXP_SDK ChatHistoryDialog : public ChatHistoryDialogBase
2528
void OnDeleteUI(wxUpdateUIEvent& event) override;
2629

2730
void SetSelectionAndEndModal(const wxDataViewItem& item);
28-
wxArrayString GetPrompts() const;
31+
llm::ChatHistory GetHistory() const;
2932

3033
private:
31-
wxString m_selectedPrompt;
32-
std::vector<std::shared_ptr<wxString>> m_prompts;
34+
llm::Conversation m_selectedConversation;
35+
std::vector<std::shared_ptr<llm::Conversation>> m_coversations;
3336
};

Plugin/ai/Config.cpp

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,23 @@ void Config::Load()
4848
try {
4949
auto json = nlohmann::json::parse(cstr_content);
5050
m_prompts = ReadValue(json, "prompts", kDefaultPromptTable);
51-
m_history = ReadValue(json, "history", std::vector<std::string>{});
51+
52+
if (json.contains("chat_history") && json["chat_history"].is_array()) {
53+
auto chat_history = json["chat_history"];
54+
for (const auto& history : chat_history) {
55+
Conversation ch;
56+
for (const auto& msg : history) {
57+
assistant::Message history_message;
58+
history_message.text = msg["content"].get<std::string>();
59+
history_message.role = msg["role"].get<std::string>();
60+
ch.push_back(history_message);
61+
}
62+
if (!ch.empty()) {
63+
m_history.push_back(ch);
64+
}
65+
}
66+
}
67+
5268
} catch (const std::exception& e) {
5369
clERROR() << "Failed to parse JSON file:" << GetFullPath() << "." << e.what() << endl;
5470
return;
@@ -72,24 +88,45 @@ void Config::Save()
7288
}
7389

7490
j["prompts"] = m_prompts;
75-
j["history"] = m_history;
91+
92+
nlohmann::json history_array = nlohmann::json::array();
93+
for (const auto& hst : m_history) {
94+
nlohmann::json chat = nlohmann::json::array();
95+
for (const auto& msg : hst) {
96+
nlohmann::json o = {{"role", msg.role}, {"content", msg.text}};
97+
chat.push_back(o);
98+
}
99+
if (!hst.empty()) {
100+
history_array.push_back(chat);
101+
}
102+
}
103+
j["chat_history"] = history_array;
76104

77105
wxString content = wxString::FromUTF8(j.dump(2));
78106
FileUtils::WriteFileContent(GetFullPath(), content, wxConvUTF8);
79107
}
80108

81-
void Config::AddHistory(const wxString& prompt)
109+
void Config::AddConversation(const Conversation& conversation)
82110
{
83111
std::scoped_lock lk{m_mutex};
84-
std::string new_prompt = prompt.ToStdString(wxConvUTF8);
85-
auto iter = std::find_if(
86-
m_history.begin(), m_history.end(), [&new_prompt](const std::string& p) { return p == new_prompt; });
112+
auto res = GetConversationLabel(conversation);
113+
if (!res.has_value()) {
114+
return;
115+
}
116+
117+
auto new_label = res.value();
118+
auto iter = std::find_if(m_history.begin(), m_history.end(), [&new_label](const Conversation& h) {
119+
auto l = GetConversationLabel(h);
120+
return l.has_value() && l.value() == new_label;
121+
});
122+
123+
// Delete the old chat history
87124
if (iter != m_history.end()) {
88125
m_history.erase(iter);
89126
}
90127

91128
// Insert at the top
92-
m_history.insert(m_history.begin(), new_prompt);
129+
m_history.insert(m_history.begin(), conversation);
93130
}
94131

95132
wxString Config::GetPrompt(PromptKind kind) const

0 commit comments

Comments
 (0)