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);
66 ImVec4(success.x, success.y * green_pulse, success.z, 1.0f);
67 status_text =
"ONLINE";
71 float red_pulse = 0.6f + 0.4f * std::sin(state.pulse_animation * 1.5f);
73 status_color = ImVec4(error.x * red_pulse, error.y, error.z, 1.0f);
74 status_text =
"OFFLINE";
79 ImGui::TextColored(status_color,
"%s %s", status_icon, status_text);
81 ImGui::TextDisabled(
"| %s", state.grpc_server_address.c_str());
88 state.auto_refresh_enabled &&
89 (
static_cast<int>(state.pulse_animation * 2.0f) % 2 == 0);
91 std::optional<gui::StyleColorGuard> pulse_guard;
94 pulse_guard.emplace(ImGuiCol_Button,
95 ImVec4(info.x, info.y, info.z, 0.8f));
108 if (ImGui::IsItemHovered()) {
109 ImGui::SetTooltip(
"Refresh automation status\nAuto-refresh: %s (%.1fs)",
110 state.auto_refresh_enabled ?
"ON" :
"OFF",
111 state.refresh_interval_seconds);
116 ImGui::Checkbox(
"##auto_refresh", &state.auto_refresh_enabled);
117 if (ImGui::IsItemHovered()) {
118 ImGui::SetTooltip(
"Auto-refresh connection status");
128 if (ImGui::IsItemHovered()) {
129 ImGui::SetTooltip(
"Open automation dashboard");
138 if (ImGui::IsItemHovered()) {
139 ImGui::SetTooltip(
"Replay last automation plan");
144 ImGui::SetNextItemWidth(80.0f);
145 ImGui::SliderFloat(
"##refresh_interval", &state.refresh_interval_seconds,
146 0.5f, 10.0f,
"%.1fs");
147 if (ImGui::IsItemHovered()) {
148 ImGui::SetTooltip(
"Auto-refresh interval");
153 ImGui::TextDisabled(
"Automation Hooks");
154 ImGui::Checkbox(
"Auto-run harness plan", &state.auto_run_plan);
155 ImGui::Checkbox(
"Auto-sync ROM context", &state.auto_sync_rom);
156 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) {
190 draw_list->AddLineH(win_pos.x, win_pos.x + win_size.x,
191 win_pos.y + offset_y, IM_COL32(0, 0, 0, 20));
195 for (
const auto& test : state.recent_tests) {
196 ImGui::PushID(test.test_id.c_str());
200 const char* status_icon;
202 if (test.status ==
"success" || test.status ==
"completed" ||
203 test.status ==
"passed") {
204 action_color = theme.status_success;
206 }
else if (test.status ==
"running" || test.status ==
"in_progress") {
207 float running_pulse =
208 0.5f + 0.5f * std::sin(state.pulse_animation * 3.0f);
210 ImVec4(theme.provider_ollama.x * running_pulse,
211 theme.provider_ollama.y * (0.8f + 0.2f * running_pulse),
212 theme.provider_ollama.z * running_pulse, 1.0f);
214 }
else if (test.status ==
"failed" || test.status ==
"error") {
215 action_color = theme.status_error;
218 action_color = theme.text_secondary_color;
223 ImGui::TextColored(action_color,
"%s", status_icon);
227 ImGui::Text(
"> %s", test.name.c_str());
230 if (test.updated_at != absl::InfinitePast()) {
232 auto elapsed = absl::Now() - test.updated_at;
233 if (elapsed < absl::Seconds(60)) {
235 static_cast<int>(absl::ToInt64Seconds(elapsed)));
236 }
else if (elapsed < absl::Minutes(60)) {
238 static_cast<int>(absl::ToInt64Minutes(elapsed)));
241 static_cast<int>(absl::ToInt64Hours(elapsed)));
246 if (!test.message.empty()) {
247 ImGui::Indent(20.0f);
252 test.message.c_str());
254 ImGui::Unindent(20.0f);