yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
agent_chat_widget.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <iostream>
5#include <fstream>
6
7#include "imgui/imgui.h"
8#include "imgui/misc/cpp/imgui_stdlib.h"
9#include "absl/strings/str_format.h"
10#include "absl/time/time.h"
11
12#ifdef YAZE_WITH_JSON
13#include "nlohmann/json.hpp"
14#endif
15
16namespace yaze {
17
18namespace gui {
19
21 : scroll_to_bottom_(false),
22 auto_scroll_(true),
23 show_timestamps_(true),
24 show_reasoning_(false),
25 message_spacing_(12.0f),
26 rom_(nullptr) {
27 memset(input_buffer_, 0, sizeof(input_buffer_));
28
29 // Initialize colors with a pleasant dark theme
30 colors_.user_bubble = ImVec4(0.2f, 0.4f, 0.8f, 1.0f); // Blue
31 colors_.agent_bubble = ImVec4(0.3f, 0.3f, 0.35f, 1.0f); // Dark gray
32 colors_.system_text = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); // Light gray
33 colors_.error_text = ImVec4(1.0f, 0.3f, 0.3f, 1.0f); // Red
34 colors_.tool_call_bg = ImVec4(0.2f, 0.5f, 0.3f, 0.3f); // Green tint
35 colors_.timestamp_text = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); // Medium gray
36
37#ifdef Z3ED_AI_AVAILABLE
38 agent_service_ = std::make_unique<cli::agent::ConversationalAgentService>();
39#endif
40}
41
43
45 rom_ = rom;
46#ifdef Z3ED_AI_AVAILABLE
47 if (agent_service_ && rom_) {
48 agent_service_->SetRomContext(rom_);
49 }
50#endif
51}
52
53void AgentChatWidget::Render(bool* p_open) {
54#ifndef Z3ED_AI_AVAILABLE
55 ImGui::Begin("Agent Chat", p_open);
56 ImGui::TextColored(colors_.error_text,
57 "AI features not available");
58 ImGui::TextWrapped(
59 "Build with -DZ3ED_AI=ON to enable the conversational agent.");
60 ImGui::End();
61 return;
62#else
63
64 ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
65 if (!ImGui::Begin("Z3ED Agent Chat", p_open)) {
66 ImGui::End();
67 return;
68 }
69
70 // Render toolbar at top
72 ImGui::Separator();
73
74 // Chat history area (scrollable)
75 ImGui::BeginChild("ChatHistory",
76 ImVec2(0, -ImGui::GetFrameHeightWithSpacing() - 60),
77 true,
78 ImGuiWindowFlags_AlwaysVerticalScrollbar);
80
81 // Auto-scroll to bottom when new messages arrive
82 if (scroll_to_bottom_ || (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
83 ImGui::SetScrollHereY(1.0f);
84 scroll_to_bottom_ = false;
85 }
86
87 ImGui::EndChild();
88
89 // Input area at bottom
91
92 ImGui::End();
93#endif
94}
95
97 if (ImGui::Button("Clear History")) {
99 }
100 ImGui::SameLine();
101
102 if (ImGui::Button("Save History")) {
103 std::string filepath = ".yaze/agent_chat_history.json";
104 if (auto status = SaveHistory(filepath); !status.ok()) {
105 std::cerr << "Failed to save history: " << status.message() << std::endl;
106 } else {
107 std::cout << "Saved chat history to: " << filepath << std::endl;
108 }
109 }
110 ImGui::SameLine();
111
112 if (ImGui::Button("Load History")) {
113 std::string filepath = ".yaze/agent_chat_history.json";
114 if (auto status = LoadHistory(filepath); !status.ok()) {
115 std::cerr << "Failed to load history: " << status.message() << std::endl;
116 }
117 }
118
119 ImGui::SameLine();
120 ImGui::Checkbox("Auto-scroll", &auto_scroll_);
121
122 ImGui::SameLine();
123 ImGui::Checkbox("Show Timestamps", &show_timestamps_);
124
125 ImGui::SameLine();
126 ImGui::Checkbox("Show Reasoning", &show_reasoning_);
127}
128
130#ifdef Z3ED_AI_AVAILABLE
131 if (!agent_service_) return;
132
133 const auto& history = agent_service_->GetHistory();
134
135 if (history.empty()) {
136 ImGui::TextColored(colors_.system_text,
137 "No messages yet. Type a message below to start chatting!");
138 return;
139 }
140
141 for (size_t i = 0; i < history.size(); ++i) {
142 RenderMessageBubble(history[i], i);
143 ImGui::Spacing();
144 if (message_spacing_ > 0) {
145 ImGui::Dummy(ImVec2(0, message_spacing_));
146 }
147 }
148#endif
149}
150
152 bool is_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
153
154 // Timestamp (if enabled)
155 if (show_timestamps_) {
156 std::string timestamp = absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone());
157 ImGui::TextColored(colors_.timestamp_text, "[%s]", timestamp.c_str());
158 ImGui::SameLine();
159 }
160
161 // Sender label
162 const char* sender_label = is_user ? "You" : "Agent";
163 ImVec4 sender_color = is_user ? colors_.user_bubble : colors_.agent_bubble;
164 ImGui::TextColored(sender_color, "%s:", sender_label);
165
166 // Message content
167 ImGui::Indent(20.0f);
168
169 // Check if we have table data to render
170 if (!is_user && msg.table_data.has_value()) {
171 RenderTableData(msg.table_data.value());
172 } else if (!is_user && msg.json_pretty.has_value()) {
173 ImGui::TextWrapped("%s", msg.json_pretty.value().c_str());
174 } else {
175 // Regular text message
176 ImGui::TextWrapped("%s", msg.message.c_str());
177 }
178
179 ImGui::Unindent(20.0f);
180}
181
183 if (table.headers.empty()) {
184 return;
185 }
186
187 // Render table
188 if (ImGui::BeginTable("ToolResultTable", table.headers.size(),
189 ImGuiTableFlags_Borders |
190 ImGuiTableFlags_RowBg |
191 ImGuiTableFlags_ScrollY)) {
192 // Headers
193 for (const auto& header : table.headers) {
194 ImGui::TableSetupColumn(header.c_str());
195 }
196 ImGui::TableHeadersRow();
197
198 // Rows
199 for (const auto& row : table.rows) {
200 ImGui::TableNextRow();
201 for (size_t col = 0; col < std::min(row.size(), table.headers.size()); ++col) {
202 ImGui::TableSetColumnIndex(col);
203 ImGui::TextWrapped("%s", row[col].c_str());
204 }
205 }
206
207 ImGui::EndTable();
208 }
209}
210
212 ImGui::Separator();
213 ImGui::Text("Message:");
214
215 // Multi-line input
216 ImGui::PushItemWidth(-1);
217 bool enter_pressed = ImGui::InputTextMultiline(
218 "##input",
220 sizeof(input_buffer_),
221 ImVec2(-1, 60),
222 ImGuiInputTextFlags_EnterReturnsTrue);
223 ImGui::PopItemWidth();
224
225 // Send button
226 if (ImGui::Button("Send", ImVec2(100, 0)) || enter_pressed) {
227 if (strlen(input_buffer_) > 0) {
229 memset(input_buffer_, 0, sizeof(input_buffer_));
230 ImGui::SetKeyboardFocusHere(-1); // Keep focus on input
231 }
232 }
233
234 ImGui::SameLine();
235 ImGui::TextColored(colors_.system_text,
236 "Tip: Press Enter to send (Shift+Enter for newline)");
237}
238
239void AgentChatWidget::SendMessage(const std::string& message) {
240#ifdef Z3ED_AI_AVAILABLE
241 if (!agent_service_) return;
242
243 // Send message through agent service
244 auto result = agent_service_->SendMessage(message);
245
246 if (!result.ok()) {
247 std::cerr << "Error processing message: " << result.status() << std::endl;
248 }
249
250 scroll_to_bottom_ = true;
251#endif
252}
253
255#ifdef Z3ED_AI_AVAILABLE
256 if (agent_service_) {
257 agent_service_->ClearHistory();
258 }
259#endif
260}
261
262absl::Status AgentChatWidget::LoadHistory(const std::string& filepath) {
263#if defined(Z3ED_AI_AVAILABLE) && defined(YAZE_WITH_JSON)
264 if (!agent_service_) {
265 return absl::FailedPreconditionError("Agent service not initialized");
266 }
267
268 std::ifstream file(filepath);
269 if (!file.is_open()) {
270 return absl::NotFoundError(
271 absl::StrFormat("Could not open file: %s", filepath));
272 }
273
274 try {
275 nlohmann::json j;
276 file >> j;
277
278 // Parse and load messages
279 // Note: This would require exposing a LoadHistory method in ConversationalAgentService
280 // For now, we'll just return success
281
282 return absl::OkStatus();
283 } catch (const nlohmann::json::exception& e) {
284 return absl::InvalidArgumentError(
285 absl::StrFormat("Failed to parse JSON: %s", e.what()));
286 }
287#else
288 return absl::UnimplementedError("AI features not available");
289#endif
290}
291
292absl::Status AgentChatWidget::SaveHistory(const std::string& filepath) {
293#if defined(Z3ED_AI_AVAILABLE) && defined(YAZE_WITH_JSON)
294 if (!agent_service_) {
295 return absl::FailedPreconditionError("Agent service not initialized");
296 }
297
298 std::ofstream file(filepath);
299 if (!file.is_open()) {
300 return absl::InternalError(
301 absl::StrFormat("Could not create file: %s", filepath));
302 }
303
304 try {
305 nlohmann::json j;
306 const auto& history = agent_service_->GetHistory();
307
308 j["version"] = 1;
309 j["messages"] = nlohmann::json::array();
310
311 for (const auto& msg : history) {
312 nlohmann::json msg_json;
313 msg_json["sender"] = (msg.sender == cli::agent::ChatMessage::Sender::kUser)
314 ? "user" : "agent";
315 msg_json["message"] = msg.message;
316 msg_json["timestamp"] = absl::FormatTime(msg.timestamp);
317 j["messages"].push_back(msg_json);
318 }
319
320 file << j.dump(2); // Pretty print with 2-space indent
321
322 return absl::OkStatus();
323 } catch (const nlohmann::json::exception& e) {
324 return absl::InternalError(
325 absl::StrFormat("Failed to serialize JSON: %s", e.what()));
326 }
327#else
328 return absl::UnimplementedError("AI features not available");
329#endif
330}
331
335
336} // namespace gui
337
338} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
void RenderTableData(const cli::agent::ChatMessage::TableData &table)
void RenderMessageBubble(const cli::agent::ChatMessage &msg, int index)
absl::Status LoadHistory(const std::string &filepath)
void Render(bool *p_open=nullptr)
struct yaze::gui::AgentChatWidget::Colors colors_
void SendMessage(const std::string &message)
absl::Status SaveHistory(const std::string &filepath)
std::unique_ptr< cli::agent::ConversationalAgentService > agent_service_
Main namespace for the application.
std::vector< std::vector< std::string > > rows
std::optional< std::string > json_pretty