25 ImGui::PushID(
"AutomationPanel");
33 state.pulse_animation += ImGui::GetIO().DeltaTime * 2.0f;
34 state.scanline_offset += ImGui::GetIO().DeltaTime * 0.5f;
35 if (state.scanline_offset > 1.0f) {
36 state.scanline_offset -= 1.0f;
40 if (ImGui::BeginChild(
"Automation_Panel", ImVec2(0, 240),
true)) {
42 float pulse = 0.5f + 0.5f * std::sin(state.pulse_animation);
43 ImVec4 header_glow = ImVec4(theme.provider_ollama.x + 0.3f * pulse,
44 theme.provider_ollama.y + 0.2f * pulse,
45 theme.provider_ollama.z + 0.4f * pulse, 1.0f);
56 bool connected = state.harness_connected;
58 const char* status_text;
59 const char* status_icon;
63 float green_pulse = 0.7f + 0.3f * std::sin(state.pulse_animation * 0.5f);
65 status_color = ImVec4(success.x, success.y * green_pulse, success.z, 1.0f);
66 status_text =
"ONLINE";
70 float red_pulse = 0.6f + 0.4f * std::sin(state.pulse_animation * 1.5f);
72 status_color = ImVec4(error.x * red_pulse, error.y, error.z, 1.0f);
73 status_text =
"OFFLINE";
78 ImGui::TextColored(status_color,
"%s %s", status_icon, status_text);
80 ImGui::TextDisabled(
"| %s", state.grpc_server_address.c_str());
87 state.auto_refresh_enabled &&
88 (
static_cast<int>(state.pulse_animation * 2.0f) % 2 == 0);
90 std::optional<gui::StyleColorGuard> pulse_guard;
93 pulse_guard.emplace(ImGuiCol_Button,
94 ImVec4(info.x, info.y, info.z, 0.8f));
107 if (ImGui::IsItemHovered()) {
108 ImGui::SetTooltip(
"Refresh automation status\nAuto-refresh: %s (%.1fs)",
109 state.auto_refresh_enabled ?
"ON" :
"OFF",
110 state.refresh_interval_seconds);
115 ImGui::Checkbox(
"##auto_refresh", &state.auto_refresh_enabled);
116 if (ImGui::IsItemHovered()) {
117 ImGui::SetTooltip(
"Auto-refresh connection status");
127 if (ImGui::IsItemHovered()) {
128 ImGui::SetTooltip(
"Open automation dashboard");
137 if (ImGui::IsItemHovered()) {
138 ImGui::SetTooltip(
"Replay last automation plan");
143 ImGui::SetNextItemWidth(80.0f);
144 ImGui::SliderFloat(
"##refresh_interval", &state.refresh_interval_seconds,
145 0.5f, 10.0f,
"%.1fs");
146 if (ImGui::IsItemHovered()) {
147 ImGui::SetTooltip(
"Auto-refresh interval");
152 ImGui::TextDisabled(
"Automation Hooks");
153 ImGui::Checkbox(
"Auto-run harness plan", &state.auto_run_plan);
154 ImGui::Checkbox(
"Auto-sync ROM context", &state.auto_sync_rom);
155 ImGui::Checkbox(
"Auto-focus proposal drawer", &state.auto_focus_proposals);
165 ImGui::TextDisabled(
"[%zu]", state.recent_tests.size());
167 if (state.recent_tests.empty()) {
169 ImGui::TextDisabled(
" > No recent actions");
170 ImGui::TextDisabled(
" > Waiting for automation tasks...");
173 int dots =
static_cast<int>(state.pulse_animation) % 4;
174 std::string dot_string(dots,
'.');
175 ImGui::TextDisabled(
" > %s", dot_string.c_str());
178 ImGui::BeginChild(
"ActionQueue", ImVec2(0, 100),
true,
179 ImGuiWindowFlags_AlwaysVerticalScrollbar);
182 ImDrawList* draw_list = ImGui::GetWindowDrawList();
183 ImVec2 win_pos = ImGui::GetWindowPos();
184 ImVec2 win_size = ImGui::GetWindowSize();
187 for (
float y = 0; y < win_size.y; y += 4.0f) {
188 float offset_y = y + state.scanline_offset * 4.0f;
189 if (offset_y < win_size.y) {
191 ImVec2(win_pos.x, win_pos.y + offset_y),
192 ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y),
193 IM_COL32(0, 0, 0, 20));
197 for (
const auto& test : state.recent_tests) {
198 ImGui::PushID(test.test_id.c_str());
202 const char* status_icon;
204 if (test.status ==
"success" || test.status ==
"completed" ||
205 test.status ==
"passed") {
206 action_color = theme.status_success;
208 }
else if (test.status ==
"running" || test.status ==
"in_progress") {
209 float running_pulse =
210 0.5f + 0.5f * std::sin(state.pulse_animation * 3.0f);
212 ImVec4(theme.provider_ollama.x * running_pulse,
213 theme.provider_ollama.y * (0.8f + 0.2f * running_pulse),
214 theme.provider_ollama.z * running_pulse, 1.0f);
216 }
else if (test.status ==
"failed" || test.status ==
"error") {
217 action_color = theme.status_error;
220 action_color = theme.text_secondary_color;
225 ImGui::TextColored(action_color,
"%s", status_icon);
229 ImGui::Text(
"> %s", test.name.c_str());
232 if (test.updated_at != absl::InfinitePast()) {
234 auto elapsed = absl::Now() - test.updated_at;
235 if (elapsed < absl::Seconds(60)) {
237 static_cast<int>(absl::ToInt64Seconds(elapsed)));
238 }
else if (elapsed < absl::Minutes(60)) {
240 static_cast<int>(absl::ToInt64Minutes(elapsed)));
243 static_cast<int>(absl::ToInt64Hours(elapsed)));
248 if (!test.message.empty()) {
249 ImGui::Indent(20.0f);
254 test.message.c_str());
256 ImGui::Unindent(20.0f);