yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
agent_chat_history_popup.cc
Go to the documentation of this file.
2
3#include <cstring>
4
5#include "absl/strings/str_format.h"
6#include "absl/time/time.h"
9#include "app/gui/icons.h"
10#include "app/gui/style.h"
11#include "imgui/imgui.h"
12#include "imgui/misc/cpp/imgui_stdlib.h"
13
14namespace yaze {
15namespace editor {
16
20
22 if (!visible_) return;
23
24 const auto& theme = AgentUI::GetTheme();
25
26 // Animate retro effects
27 ImGuiIO& io = ImGui::GetIO();
28 pulse_animation_ += io.DeltaTime * 2.0f;
29 scanline_offset_ += io.DeltaTime * 0.3f;
30 if (scanline_offset_ > 1.0f) scanline_offset_ -= 1.0f;
31 glitch_animation_ += io.DeltaTime * 5.0f;
32 blink_counter_ = static_cast<int>(pulse_animation_ * 2.0f) % 2;
33
34 // Set drawer position on the LEFT side (full height)
35 ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
36 ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always);
37
38 ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove |
39 ImGuiWindowFlags_NoResize |
40 ImGuiWindowFlags_NoCollapse |
41 ImGuiWindowFlags_NoTitleBar;
42
43 // Use current theme colors with slight glow
44 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
45 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10));
46
47 // Pulsing border color
48 float border_pulse = 0.7f + 0.3f * std::sin(pulse_animation_ * 0.5f);
49 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(
50 theme.provider_ollama.x * border_pulse,
51 theme.provider_ollama.y * border_pulse,
52 theme.provider_ollama.z * border_pulse + 0.2f,
53 0.8f
54 ));
55
56 if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) {
57 DrawHeader();
58
59 ImGui::Separator();
60 ImGui::Spacing();
61
62 // Calculate proper list height
63 float list_height = ImGui::GetContentRegionAvail().y - 220.0f;
64
65 // Dark terminal background
66 ImVec4 terminal_bg = theme.code_bg_color;
67 terminal_bg.x *= 0.9f;
68 terminal_bg.y *= 0.9f;
69 terminal_bg.z *= 0.95f;
70
71 ImGui::PushStyleColor(ImGuiCol_ChildBg, terminal_bg);
72 ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
73
74 // Draw scanline effect
75 ImDrawList* draw_list = ImGui::GetWindowDrawList();
76 ImVec2 win_pos = ImGui::GetWindowPos();
77 ImVec2 win_size = ImGui::GetWindowSize();
78
79 for (float y = 0; y < win_size.y; y += 3.0f) {
80 float offset_y = y + scanline_offset_ * 3.0f;
81 if (offset_y < win_size.y) {
82 draw_list->AddLine(
83 ImVec2(win_pos.x, win_pos.y + offset_y),
84 ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y),
85 IM_COL32(0, 0, 0, 15));
86 }
87 }
88
90
91 if (needs_scroll_) {
92 ImGui::SetScrollHereY(1.0f);
93 needs_scroll_ = false;
94 }
95
96 ImGui::EndChild();
97 ImGui::PopStyleColor();
98
99 ImGui::Spacing();
100
101 // Quick actions bar
104 ImGui::Spacing();
105 }
106
107 // Input section at bottom
109 }
110 ImGui::End();
111
112 ImGui::PopStyleColor(); // Border color
113 ImGui::PopStyleVar(2);
114}
115
117 if (messages_.empty()) {
118 ImGui::TextDisabled("No messages yet. Start a conversation in the chat window.");
119 return;
120 }
121
122 // Calculate starting index for display limit
123 int start_index = messages_.size() > display_limit_ ?
124 messages_.size() - display_limit_ : 0;
125
126 for (int i = start_index; i < messages_.size(); ++i) {
127 const auto& msg = messages_[i];
128
129 // Skip internal messages
130 if (msg.is_internal) continue;
131
132 // Apply filter
134 msg.sender != cli::agent::ChatMessage::Sender::kUser) continue;
136 msg.sender != cli::agent::ChatMessage::Sender::kAgent) continue;
137
138 DrawMessage(msg, i);
139 }
140}
141
143 ImGui::PushID(index);
144
145 bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
146
147 // Retro terminal colors
148 ImVec4 header_color = from_user
149 ? ImVec4(1.0f, 0.85f, 0.0f, 1.0f) // Amber/Gold for user
150 : ImVec4(0.0f, 1.0f, 0.7f, 1.0f); // Cyan/Green for agent
151
152 const char* sender_label = from_user ? "> USER:" : "> AGENT:";
153
154 // Message header with terminal prefix
155 ImGui::TextColored(header_color, "%s", sender_label);
156
157 ImGui::SameLine();
158 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
159 "[%s]", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str());
160
161 // Message content with terminal styling
162 ImGui::Indent(15.0f);
163
164 if (msg.table_data.has_value()) {
165 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f),
166 " %s [Table Data]", ICON_MD_TABLE_CHART);
167 } else if (msg.json_pretty.has_value()) {
168 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f),
169 " %s [Structured Response]", ICON_MD_DATA_OBJECT);
170 } else {
171 // Truncate long messages with ellipsis
172 std::string content = msg.message;
173 if (content.length() > 200) {
174 content = content.substr(0, 197) + "...";
175 }
176 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.85f, 0.85f, 1.0f));
177 ImGui::TextWrapped(" %s", content.c_str());
178 ImGui::PopStyleColor();
179 }
180
181 // Show proposal indicator with pulse
182 if (msg.proposal.has_value()) {
183 float proposal_pulse = 0.7f + 0.3f * std::sin(pulse_animation_ * 2.0f);
184 ImGui::TextColored(ImVec4(0.2f, proposal_pulse, 0.4f, 1.0f),
185 " %s Proposal: [%s]", ICON_MD_PREVIEW, msg.proposal->id.c_str());
186 }
187
188 ImGui::Unindent(15.0f);
189 ImGui::Spacing();
190
191 // Retro separator line
192 ImDrawList* draw_list = ImGui::GetWindowDrawList();
193 ImVec2 line_start = ImGui::GetCursorScreenPos();
194 float line_width = ImGui::GetContentRegionAvail().x;
195 draw_list->AddLine(
196 line_start,
197 ImVec2(line_start.x + line_width, line_start.y),
198 IM_COL32(60, 60, 70, 100),
199 1.0f
200 );
201
202 ImGui::Dummy(ImVec2(0, 2));
203
204 ImGui::PopID();
205}
206
208 const auto& theme = AgentUI::GetTheme();
209 ImDrawList* draw_list = ImGui::GetWindowDrawList();
210 ImVec2 header_start = ImGui::GetCursorScreenPos();
211 ImVec2 header_size(ImGui::GetContentRegionAvail().x, 55);
212
213 // Retro gradient with pulse
214 float pulse = 0.5f + 0.5f * std::sin(pulse_animation_);
215 ImVec4 bg_top = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
216 ImVec4 bg_bottom = ImGui::GetStyleColorVec4(ImGuiCol_ChildBg);
217 bg_top.x += 0.1f * pulse;
218 bg_top.y += 0.1f * pulse;
219 bg_top.z += 0.15f * pulse;
220
221 ImU32 color_top = ImGui::GetColorU32(bg_top);
222 ImU32 color_bottom = ImGui::GetColorU32(bg_bottom);
223 draw_list->AddRectFilledMultiColor(
224 header_start,
225 ImVec2(header_start.x + header_size.x, header_start.y + header_size.y),
226 color_top, color_top, color_bottom, color_bottom);
227
228 // Pulsing accent line with glow
229 float line_pulse = 0.6f + 0.4f * std::sin(pulse_animation_ * 0.7f);
230 ImU32 accent_color = IM_COL32(
231 static_cast<int>(theme.provider_ollama.x * 255 * line_pulse),
232 static_cast<int>(theme.provider_ollama.y * 255 * line_pulse),
233 static_cast<int>(theme.provider_ollama.z * 255 * line_pulse + 50),
234 200
235 );
236 draw_list->AddLine(
237 ImVec2(header_start.x, header_start.y + header_size.y),
238 ImVec2(header_start.x + header_size.x, header_start.y + header_size.y),
239 accent_color, 2.0f);
240
241 ImGui::Dummy(ImVec2(0, 8));
242
243 // Title with pulsing glow
244 ImVec4 title_color = ImVec4(
245 0.4f + 0.3f * pulse,
246 0.8f + 0.2f * pulse,
247 1.0f,
248 1.0f
249 );
250 ImGui::PushStyleColor(ImGuiCol_Text, title_color);
251 ImGui::Text("%s CHAT HISTORY", ICON_MD_CHAT);
252 ImGui::PopStyleColor();
253
254 ImGui::SameLine();
255 ImGui::TextDisabled("[v0.4.x]");
256
257 // Buttons properly spaced from right edge
258 ImGui::SameLine(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 75.0f);
259
260 // Compact mode toggle with pulse
261 if (blink_counter_ == 0 && compact_mode_) {
262 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.4f, 0.6f, 0.7f));
263 }
264 if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) {
266 }
267 if (blink_counter_ == 0 && compact_mode_) {
268 ImGui::PopStyleColor();
269 }
270 if (ImGui::IsItemHovered()) {
271 ImGui::SetTooltip(compact_mode_ ? "Expand view" : "Compact view");
272 }
273
274 ImGui::SameLine();
275
276 // Full chat button
277 if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) {
280 visible_ = false;
281 }
282 }
283 if (ImGui::IsItemHovered()) {
284 ImGui::SetTooltip("Open full chat");
285 }
286
287 ImGui::SameLine();
288
289 // Close button
290 if (ImGui::SmallButton(ICON_MD_CLOSE)) {
291 visible_ = false;
292 }
293 if (ImGui::IsItemHovered()) {
294 ImGui::SetTooltip("Close (Ctrl+H)");
295 }
296
297 // Message count with retro styling
298 int visible_count = 0;
299 for (const auto& msg : messages_) {
300 if (msg.is_internal) continue;
302 msg.sender != cli::agent::ChatMessage::Sender::kUser) continue;
304 msg.sender != cli::agent::ChatMessage::Sender::kAgent) continue;
305 visible_count++;
306 }
307
308 ImGui::Spacing();
309 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
310 "> MESSAGES: [%d]", visible_count);
311
312 // Animated status indicator
313 if (unread_count_ > 0) {
314 ImGui::SameLine();
315 float unread_pulse = 0.5f + 0.5f * std::sin(pulse_animation_ * 3.0f);
316 ImGui::TextColored(ImVec4(1.0f, unread_pulse * 0.5f, 0.0f, 1.0f),
318 }
319
320 ImGui::Dummy(ImVec2(0, 5));
321}
322
324 // 4 buttons with narrower width
325 float button_width = (ImGui::GetContentRegionAvail().x - 15) / 4.0f;
326
327 // Multimodal snapshot button
328 if (ImGui::Button(ICON_MD_CAMERA, ImVec2(button_width, 30))) {
331 }
332 }
333 if (ImGui::IsItemHovered()) {
334 ImGui::SetTooltip("Capture screenshot");
335 }
336
337 ImGui::SameLine();
338
339 // Filter button with icon indicator
340 const char* filter_icons[] = {ICON_MD_FILTER_LIST, ICON_MD_PERSON, ICON_MD_SMART_TOY};
341 int filter_idx = static_cast<int>(message_filter_);
342 if (ImGui::Button(filter_icons[filter_idx], ImVec2(button_width, 30))) {
343 ImGui::OpenPopup("FilterPopup");
344 }
345 if (ImGui::IsItemHovered()) {
346 const char* filter_names[] = {"All", "User only", "Agent only"};
347 ImGui::SetTooltip("Filter: %s", filter_names[filter_idx]);
348 }
349
350 // Filter popup
351 if (ImGui::BeginPopup("FilterPopup")) {
352 if (ImGui::Selectable(ICON_MD_FILTER_LIST " All Messages", message_filter_ == MessageFilter::kAll)) {
354 }
355 if (ImGui::Selectable(ICON_MD_PERSON " User Only", message_filter_ == MessageFilter::kUserOnly)) {
357 }
358 if (ImGui::Selectable(ICON_MD_SMART_TOY " Agent Only", message_filter_ == MessageFilter::kAgentOnly)) {
360 }
361 ImGui::EndPopup();
362 }
363
364 ImGui::SameLine();
365
366 // Save session button
367 if (ImGui::Button(ICON_MD_SAVE, ImVec2(button_width, 30))) {
368 if (toast_manager_) {
369 toast_manager_->Show(ICON_MD_SAVE " Session auto-saved", ToastType::kSuccess, 1.5f);
370 }
371 }
372 if (ImGui::IsItemHovered()) {
373 ImGui::SetTooltip("Save chat session");
374 }
375
376 ImGui::SameLine();
377
378 // Clear button
379 if (ImGui::Button(ICON_MD_DELETE, ImVec2(button_width, 30))) {
380 ClearHistory();
381 }
382 if (ImGui::IsItemHovered()) {
383 ImGui::SetTooltip("Clear popup view");
384 }
385}
386
388 ImGui::Separator();
389 ImGui::Spacing();
390
391 // Input field using theme colors
392 bool send_message = false;
393 if (ImGui::InputTextMultiline("##popup_input", input_buffer_, sizeof(input_buffer_),
394 ImVec2(-1, 60),
395 ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CtrlEnterForNewLine)) {
396 send_message = true;
397 }
398
399 // Focus input on first show
400 if (focus_input_) {
401 ImGui::SetKeyboardFocusHere(-1);
402 focus_input_ = false;
403 }
404
405 // Send button (proper width)
406 ImGui::Spacing();
407 float send_button_width = ImGui::GetContentRegionAvail().x;
408 if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(send_button_width, 32)) || send_message) {
409 if (std::strlen(input_buffer_) > 0) {
411 std::memset(input_buffer_, 0, sizeof(input_buffer_));
412 }
413 }
414
415 if (ImGui::IsItemHovered()) {
416 ImGui::SetTooltip("Send message (Enter) • Ctrl+Enter for newline");
417 }
418
419 // Info text
420 ImGui::Spacing();
421 ImGui::TextDisabled(ICON_MD_INFO " Enter: send • Ctrl+Enter: newline");
422}
423
424void AgentChatHistoryPopup::SendMessage(const std::string& message) {
426 send_message_callback_(message);
427
428 if (toast_manager_) {
429 toast_manager_->Show(ICON_MD_SEND " Message sent", ToastType::kSuccess, 1.5f);
430 }
431
432 // Auto-scroll to see response
433 needs_scroll_ = true;
434 }
435}
436
437void AgentChatHistoryPopup::UpdateHistory(const std::vector<cli::agent::ChatMessage>& history) {
438 bool had_messages = !messages_.empty();
439 int old_size = messages_.size();
440
441 messages_ = history;
442
443 // Auto-scroll if new messages arrived
444 if (auto_scroll_ && messages_.size() > old_size) {
445 needs_scroll_ = true;
446 }
447}
448
450 if (auto_scroll_) {
451 needs_scroll_ = true;
452 }
453
454 // Flash the window to draw attention
455 if (toast_manager_ && !visible_) {
456 toast_manager_->Show(ICON_MD_CHAT " New message received", ToastType::kInfo, 2.0f);
457 }
458}
459
461 messages_.clear();
462
463 if (toast_manager_) {
464 toast_manager_->Show("Chat history popup cleared", ToastType::kInfo, 2.0f);
465 }
466}
467
469 // TODO: Implement export functionality
470 if (toast_manager_) {
471 toast_manager_->Show("Export feature coming soon", ToastType::kInfo, 2.0f);
472 }
473}
474
478
479} // namespace editor
480} // namespace yaze
void SendMessage(const std::string &message)
void UpdateHistory(const std::vector< cli::agent::ChatMessage > &history)
void DrawMessage(const cli::agent::ChatMessage &msg, int index)
std::vector< cli::agent::ChatMessage > messages_
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
#define ICON_MD_DATA_OBJECT
Definition icons.h:519
#define ICON_MD_CAMERA
Definition icons.h:352
#define ICON_MD_INFO
Definition icons.h:991
#define ICON_MD_UNFOLD_MORE
Definition icons.h:2039
#define ICON_MD_CHAT
Definition icons.h:392
#define ICON_MD_TABLE_CHART
Definition icons.h:1931
#define ICON_MD_NOTIFICATION_IMPORTANT
Definition icons.h:1332
#define ICON_MD_SEND
Definition icons.h:1681
#define ICON_MD_PERSON
Definition icons.h:1413
#define ICON_MD_PREVIEW
Definition icons.h:1510
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_FILTER_LIST
Definition icons.h:766
#define ICON_MD_DELETE
Definition icons.h:528
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1352
#define ICON_MD_CLOSE
Definition icons.h:416
#define ICON_MD_UNFOLD_LESS
Definition icons.h:2038
#define ICON_MD_SMART_TOY
Definition icons.h:1779
const AgentUITheme & GetTheme()
Main namespace for the application.
std::optional< std::string > json_pretty
std::optional< ProposalSummary > proposal