21 ImGui::PushID(
"AutomationPanel");
29 state.pulse_animation += ImGui::GetIO().DeltaTime * 2.0f;
30 state.scanline_offset += ImGui::GetIO().DeltaTime * 0.5f;
31 if (state.scanline_offset > 1.0f) {
32 state.scanline_offset -= 1.0f;
36 if (ImGui::BeginChild(
"Automation_Panel", ImVec2(0, 240),
true)) {
38 float pulse = 0.5f + 0.5f * std::sin(state.pulse_animation);
39 ImVec4 header_glow = ImVec4(theme.provider_ollama.x + 0.3f * pulse,
40 theme.provider_ollama.y + 0.2f * pulse,
41 theme.provider_ollama.z + 0.4f * pulse, 1.0f);
43 ImGui::PushStyleColor(ImGuiCol_Text, header_glow);
45 ImGui::PopStyleColor();
48 ImGui::TextDisabled(
"[v0.4.x]");
51 bool connected = state.harness_connected;
53 const char* status_text;
54 const char* status_icon;
59 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";
66 0.6f + 0.4f * std::sin(state.pulse_animation * 1.5f);
67 status_color = ImVec4(red_pulse, 0.2f, 0.2f, 1.0f);
68 status_text =
"OFFLINE";
73 ImGui::TextColored(status_color,
"%s %s", status_icon, status_text);
75 ImGui::TextDisabled(
"| %s", state.grpc_server_address.c_str());
82 state.auto_refresh_enabled &&
83 (
static_cast<int>(state.pulse_animation * 2.0f) % 2 == 0);
85 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.7f, 0.8f));
98 ImGui::PopStyleColor();
101 if (ImGui::IsItemHovered()) {
102 ImGui::SetTooltip(
"Refresh automation status\nAuto-refresh: %s (%.1fs)",
103 state.auto_refresh_enabled ?
"ON" :
"OFF",
104 state.refresh_interval_seconds);
109 ImGui::Checkbox(
"##auto_refresh", &state.auto_refresh_enabled);
110 if (ImGui::IsItemHovered()) {
111 ImGui::SetTooltip(
"Auto-refresh connection status");
121 if (ImGui::IsItemHovered()) {
122 ImGui::SetTooltip(
"Open automation dashboard");
131 if (ImGui::IsItemHovered()) {
132 ImGui::SetTooltip(
"Replay last automation plan");
137 ImGui::SetNextItemWidth(80.0f);
138 ImGui::SliderFloat(
"##refresh_interval",
139 &state.refresh_interval_seconds, 0.5f, 10.0f,
141 if (ImGui::IsItemHovered()) {
142 ImGui::SetTooltip(
"Auto-refresh interval");
147 ImGui::TextDisabled(
"Automation Hooks");
148 ImGui::Checkbox(
"Auto-run harness plan", &state.auto_run_plan);
149 ImGui::Checkbox(
"Auto-sync ROM context", &state.auto_sync_rom);
150 ImGui::Checkbox(
"Auto-focus proposal drawer",
151 &state.auto_focus_proposals);
158 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
"%s RECENT ACTIONS",
161 ImGui::TextDisabled(
"[%zu]", state.recent_tests.size());
163 if (state.recent_tests.empty()) {
165 ImGui::TextDisabled(
" > No recent actions");
166 ImGui::TextDisabled(
" > Waiting for automation tasks...");
169 int dots =
static_cast<int>(state.pulse_animation) % 4;
170 std::string dot_string(dots,
'.');
171 ImGui::TextDisabled(
" > %s", dot_string.c_str());
174 ImGui::BeginChild(
"ActionQueue", ImVec2(0, 100),
true,
175 ImGuiWindowFlags_AlwaysVerticalScrollbar);
178 ImDrawList* draw_list = ImGui::GetWindowDrawList();
179 ImVec2 win_pos = ImGui::GetWindowPos();
180 ImVec2 win_size = ImGui::GetWindowSize();
183 for (
float y = 0; y < win_size.y; y += 4.0f) {
184 float offset_y = y + state.scanline_offset * 4.0f;
185 if (offset_y < win_size.y) {
187 ImVec2(win_pos.x, win_pos.y + offset_y),
188 ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y),
189 IM_COL32(0, 0, 0, 20));
193 for (
const auto& test : state.recent_tests) {
194 ImGui::PushID(test.test_id.c_str());
198 const char* status_icon;
200 if (test.status ==
"success" || test.status ==
"completed" ||
201 test.status ==
"passed") {
202 action_color = theme.status_success;
204 }
else if (test.status ==
"running" || test.status ==
"in_progress") {
205 float running_pulse =
206 0.5f + 0.5f * std::sin(state.pulse_animation * 3.0f);
208 ImVec4(theme.provider_ollama.x * running_pulse,
209 theme.provider_ollama.y * (0.8f + 0.2f * running_pulse),
210 theme.provider_ollama.z * running_pulse, 1.0f);
212 }
else if (test.status ==
"failed" || test.status ==
"error") {
213 action_color = theme.status_error;
216 action_color = theme.text_secondary_color;
221 ImGui::TextColored(action_color,
"%s", status_icon);
225 ImGui::Text(
"> %s", test.name.c_str());
228 if (test.updated_at != absl::InfinitePast()) {
230 auto elapsed = absl::Now() - test.updated_at;
231 if (elapsed < absl::Seconds(60)) {
232 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%ds]",
233 static_cast<int>(absl::ToInt64Seconds(elapsed)));
234 }
else if (elapsed < absl::Minutes(60)) {
235 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%dm]",
236 static_cast<int>(absl::ToInt64Minutes(elapsed)));
238 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%dh]",
239 static_cast<int>(absl::ToInt64Hours(elapsed)));
244 if (!test.message.empty()) {
245 ImGui::Indent(20.0f);
246 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
248 ImGui::PopStyleColor();
249 ImGui::Unindent(20.0f);