22 ImGui::PushID(
"AutomationPanel");
30 state.pulse_animation += ImGui::GetIO().DeltaTime * 2.0f;
31 state.scanline_offset += ImGui::GetIO().DeltaTime * 0.5f;
32 if (state.scanline_offset > 1.0f) {
33 state.scanline_offset -= 1.0f;
37 if (ImGui::BeginChild(
"Automation_Panel", ImVec2(0, 240),
true)) {
39 float pulse = 0.5f + 0.5f * std::sin(state.pulse_animation);
40 ImVec4 header_glow = ImVec4(theme.provider_ollama.x + 0.3f * pulse,
41 theme.provider_ollama.y + 0.2f * pulse,
42 theme.provider_ollama.z + 0.4f * pulse, 1.0f);
44 ImGui::PushStyleColor(ImGuiCol_Text, header_glow);
46 ImGui::PopStyleColor();
49 ImGui::TextDisabled(
"[v0.5.x]");
52 bool connected = state.harness_connected;
54 const char* status_text;
55 const char* status_icon;
59 float green_pulse = 0.7f + 0.3f * std::sin(state.pulse_animation * 0.5f);
60 status_color = ImVec4(0.1f, green_pulse, 0.3f, 1.0f);
61 status_text =
"ONLINE";
65 float red_pulse = 0.6f + 0.4f * std::sin(state.pulse_animation * 1.5f);
66 status_color = ImVec4(red_pulse, 0.2f, 0.2f, 1.0f);
67 status_text =
"OFFLINE";
72 ImGui::TextColored(status_color,
"%s %s", status_icon, status_text);
74 ImGui::TextDisabled(
"| %s", state.grpc_server_address.c_str());
81 state.auto_refresh_enabled &&
82 (
static_cast<int>(state.pulse_animation * 2.0f) % 2 == 0);
84 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.7f, 0.8f));
97 ImGui::PopStyleColor();
100 if (ImGui::IsItemHovered()) {
101 ImGui::SetTooltip(
"Refresh automation status\nAuto-refresh: %s (%.1fs)",
102 state.auto_refresh_enabled ?
"ON" :
"OFF",
103 state.refresh_interval_seconds);
108 ImGui::Checkbox(
"##auto_refresh", &state.auto_refresh_enabled);
109 if (ImGui::IsItemHovered()) {
110 ImGui::SetTooltip(
"Auto-refresh connection status");
120 if (ImGui::IsItemHovered()) {
121 ImGui::SetTooltip(
"Open automation dashboard");
130 if (ImGui::IsItemHovered()) {
131 ImGui::SetTooltip(
"Replay last automation plan");
136 ImGui::SetNextItemWidth(80.0f);
137 ImGui::SliderFloat(
"##refresh_interval", &state.refresh_interval_seconds,
138 0.5f, 10.0f,
"%.1fs");
139 if (ImGui::IsItemHovered()) {
140 ImGui::SetTooltip(
"Auto-refresh interval");
145 ImGui::TextDisabled(
"Automation Hooks");
146 ImGui::Checkbox(
"Auto-run harness plan", &state.auto_run_plan);
147 ImGui::Checkbox(
"Auto-sync ROM context", &state.auto_sync_rom);
148 ImGui::Checkbox(
"Auto-focus proposal drawer", &state.auto_focus_proposals);
155 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
"%s RECENT ACTIONS",
158 ImGui::TextDisabled(
"[%zu]", state.recent_tests.size());
160 if (state.recent_tests.empty()) {
162 ImGui::TextDisabled(
" > No recent actions");
163 ImGui::TextDisabled(
" > Waiting for automation tasks...");
166 int dots =
static_cast<int>(state.pulse_animation) % 4;
167 std::string dot_string(dots,
'.');
168 ImGui::TextDisabled(
" > %s", dot_string.c_str());
171 ImGui::BeginChild(
"ActionQueue", ImVec2(0, 100),
true,
172 ImGuiWindowFlags_AlwaysVerticalScrollbar);
175 ImDrawList* draw_list = ImGui::GetWindowDrawList();
176 ImVec2 win_pos = ImGui::GetWindowPos();
177 ImVec2 win_size = ImGui::GetWindowSize();
180 for (
float y = 0; y < win_size.y; y += 4.0f) {
181 float offset_y = y + state.scanline_offset * 4.0f;
182 if (offset_y < win_size.y) {
184 ImVec2(win_pos.x, win_pos.y + offset_y),
185 ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y),
186 IM_COL32(0, 0, 0, 20));
190 for (
const auto& test : state.recent_tests) {
191 ImGui::PushID(test.test_id.c_str());
195 const char* status_icon;
197 if (test.status ==
"success" || test.status ==
"completed" ||
198 test.status ==
"passed") {
199 action_color = theme.status_success;
201 }
else if (test.status ==
"running" || test.status ==
"in_progress") {
202 float running_pulse =
203 0.5f + 0.5f * std::sin(state.pulse_animation * 3.0f);
205 ImVec4(theme.provider_ollama.x * running_pulse,
206 theme.provider_ollama.y * (0.8f + 0.2f * running_pulse),
207 theme.provider_ollama.z * running_pulse, 1.0f);
209 }
else if (test.status ==
"failed" || test.status ==
"error") {
210 action_color = theme.status_error;
213 action_color = theme.text_secondary_color;
218 ImGui::TextColored(action_color,
"%s", status_icon);
222 ImGui::Text(
"> %s", test.name.c_str());
225 if (test.updated_at != absl::InfinitePast()) {
227 auto elapsed = absl::Now() - test.updated_at;
228 if (elapsed < absl::Seconds(60)) {
229 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%ds]",
230 static_cast<int>(absl::ToInt64Seconds(elapsed)));
231 }
else if (elapsed < absl::Minutes(60)) {
232 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%dm]",
233 static_cast<int>(absl::ToInt64Minutes(elapsed)));
235 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%dh]",
236 static_cast<int>(absl::ToInt64Hours(elapsed)));
241 if (!test.message.empty()) {
242 ImGui::Indent(20.0f);
243 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
245 ImGui::PopStyleColor();
246 ImGui::Unindent(20.0f);