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