Your Name commited on
Commit
1448ed8
·
1 Parent(s): e9b8ed2

Add animated glowing radar, live pulse, and thinking indicators for agent

Browse files
frontend/src/components/TaskGraph.tsx CHANGED
@@ -20,7 +20,7 @@ export default function TaskGraph({
20
  return (
21
  <div className="task-graph">
22
  {tasks.map((task) => (
23
- <div className="task-node" key={task.task_key}>
24
  <div><strong>{task.task_key}</strong></div>
25
  <div>{task.task_type}</div>
26
  <span className={`status ${task.status}`}>{task.status}</span>
@@ -36,7 +36,7 @@ export default function TaskGraph({
36
  const live = taskMap.get(pt.id);
37
  const status = live?.status || "PENDING";
38
  return (
39
- <div className="task-node" key={pt.id}>
40
  <div><strong>{pt.id}</strong></div>
41
  <div>{pt.type}</div>
42
  <div style={{ fontSize: "0.8rem", color: "#7f92ab" }}>
 
20
  return (
21
  <div className="task-graph">
22
  {tasks.map((task) => (
23
+ <div className={`task-node ${task.status.toLowerCase()}`} key={task.task_key}>
24
  <div><strong>{task.task_key}</strong></div>
25
  <div>{task.task_type}</div>
26
  <span className={`status ${task.status}`}>{task.status}</span>
 
36
  const live = taskMap.get(pt.id);
37
  const status = live?.status || "PENDING";
38
  return (
39
+ <div className={`task-node ${status.toLowerCase()}`} key={pt.id}>
40
  <div><strong>{pt.id}</strong></div>
41
  <div>{pt.type}</div>
42
  <div style={{ fontSize: "0.8rem", color: "#7f92ab" }}>
frontend/src/components/WorkflowTimeline.tsx CHANGED
@@ -42,9 +42,11 @@ export default function WorkflowTimeline({ events }: { events: WorkflowEvent[] }
42
  style={{
43
  background: "var(--bg-elevated)",
44
  borderRadius: "var(--radius-md)",
45
- border: "1px solid var(--border-subtle)",
 
46
  marginBottom: "2rem",
47
- overflow: "hidden"
 
48
  }}
49
  >
50
  <summary style={{
@@ -53,16 +55,26 @@ export default function WorkflowTimeline({ events }: { events: WorkflowEvent[] }
53
  fontWeight: 600,
54
  display: "flex",
55
  alignItems: "center",
56
- gap: "0.5rem",
 
57
  listStyle: "none",
58
  borderBottom: isOpen ? "1px solid var(--border-subtle)" : "none",
59
- userSelect: "none"
 
60
  }}>
61
- <span style={{ transition: "transform 0.2s", transform: isOpen ? "rotate(90deg)" : "rotate(0deg)" }}>▶</span>
62
- {isComplete ? "Agent finished thinking" : "Agent is thinking..."}
 
 
 
 
 
 
 
 
63
  </summary>
64
 
65
- <div style={{ padding: "1.5rem", maxHeight: "400px", overflowY: "auto" }}>
66
  <ul className="timeline">
67
  {events.map((event) => (
68
  <li key={event.id} style={{ marginBottom: "1rem", display: "flex", gap: "1rem", alignItems: "flex-start" }}>
@@ -87,7 +99,27 @@ export default function WorkflowTimeline({ events }: { events: WorkflowEvent[] }
87
  </div>
88
  </li>
89
  ))}
90
- {events.length === 0 && <li>Initializing workflow engine...</li>}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  </ul>
92
  </div>
93
  </details>
 
42
  style={{
43
  background: "var(--bg-elevated)",
44
  borderRadius: "var(--radius-md)",
45
+ border: isComplete ? "1px solid var(--border-subtle)" : "1px solid rgba(99, 102, 241, 0.4)",
46
+ boxShadow: isComplete ? "none" : "0 0 20px rgba(99, 102, 241, 0.25)",
47
  marginBottom: "2rem",
48
+ overflow: "hidden",
49
+ transition: "all 0.3s ease"
50
  }}
51
  >
52
  <summary style={{
 
55
  fontWeight: 600,
56
  display: "flex",
57
  alignItems: "center",
58
+ justifyContent: "space-between",
59
+ gap: "0.75rem",
60
  listStyle: "none",
61
  borderBottom: isOpen ? "1px solid var(--border-subtle)" : "none",
62
+ userSelect: "none",
63
+ background: isComplete ? "transparent" : "linear-gradient(90deg, rgba(99, 102, 241, 0.05) 0%, transparent 100%)"
64
  }}>
65
+ <div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
66
+ <span style={{ transition: "transform 0.2s", transform: isOpen ? "rotate(90deg)" : "rotate(0deg)", display: "inline-block" }}>▶</span>
67
+ {!isComplete && <span className="radar-dot green"></span>}
68
+ <span>{isComplete ? "Agent completed execution" : "Agent is thinking & executing..."}</span>
69
+ </div>
70
+ {!isComplete && (
71
+ <span style={{ fontSize: "0.8rem", color: "#6366f1", display: "flex", alignItems: "center", gap: "0.3rem" }}>
72
+ LIVE <span className="thinking-dots"><span></span><span></span><span></span></span>
73
+ </span>
74
+ )}
75
  </summary>
76
 
77
+ <div style={{ padding: "1.5rem", maxHeight: "450px", overflowY: "auto" }}>
78
  <ul className="timeline">
79
  {events.map((event) => (
80
  <li key={event.id} style={{ marginBottom: "1rem", display: "flex", gap: "1rem", alignItems: "flex-start" }}>
 
99
  </div>
100
  </li>
101
  ))}
102
+
103
+ {!isComplete && (
104
+ <li style={{ marginTop: "1rem", listStyle: "none" }}>
105
+ <div className="live-thinking-pulse">
106
+ <span className="radar-dot"></span>
107
+ <div style={{ flex: 1 }}>
108
+ <div style={{ display: "flex", alignItems: "center", gap: "0.4rem" }}>
109
+ <span>Agent is working on the autonomous loop</span>
110
+ <span className="thinking-dots"><span></span><span></span><span></span></span>
111
+ </div>
112
+ <div style={{ fontSize: "0.8rem", color: "var(--text-muted)", marginTop: "0.2rem" }}>
113
+ Generating code, compiling DAG graph, executing tasks & verifying...
114
+ </div>
115
+ <div style={{ marginTop: "0.6rem" }}>
116
+ <div className="thinking-shimmer-progress"></div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </li>
121
+ )}
122
+ {events.length === 0 && isComplete && <li>No events recorded.</li>}
123
  </ul>
124
  </div>
125
  </details>
frontend/src/index.css CHANGED
@@ -1039,3 +1039,165 @@ button.secondary:hover,
1039
  padding: 1.5rem;
1040
  }
1041
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1039
  padding: 1.5rem;
1040
  }
1041
  }
1042
+
1043
+ /* ============================================================
1044
+ LIVE AGENT GLOWING & WORKING ANIMATIONS
1045
+ ============================================================ */
1046
+
1047
+ @keyframes pulse-ring {
1048
+ 0% {
1049
+ transform: scale(0.95);
1050
+ box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.8);
1051
+ }
1052
+ 70% {
1053
+ transform: scale(1.1);
1054
+ box-shadow: 0 0 0 12px rgba(99, 102, 241, 0);
1055
+ }
1056
+ 100% {
1057
+ transform: scale(0.95);
1058
+ box-shadow: 0 0 0 0 rgba(99, 102, 241, 0);
1059
+ }
1060
+ }
1061
+
1062
+ @keyframes pulse-ring-green {
1063
+ 0% {
1064
+ transform: scale(0.95);
1065
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.8);
1066
+ }
1067
+ 70% {
1068
+ transform: scale(1.1);
1069
+ box-shadow: 0 0 0 12px rgba(16, 185, 129, 0);
1070
+ }
1071
+ 100% {
1072
+ transform: scale(0.95);
1073
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
1074
+ }
1075
+ }
1076
+
1077
+ @keyframes live-glow {
1078
+ 0%, 100% {
1079
+ box-shadow: 0 0 16px rgba(99, 102, 241, 0.4), 0 0 32px rgba(168, 85, 247, 0.2);
1080
+ border-color: rgba(99, 102, 241, 0.7);
1081
+ }
1082
+ 50% {
1083
+ box-shadow: 0 0 30px rgba(99, 102, 241, 0.7), 0 0 60px rgba(168, 85, 247, 0.45);
1084
+ border-color: rgba(168, 85, 247, 1);
1085
+ }
1086
+ }
1087
+
1088
+ @keyframes live-neon-sweep {
1089
+ 0% {
1090
+ background-position: 0% 50%;
1091
+ }
1092
+ 50% {
1093
+ background-position: 100% 50%;
1094
+ }
1095
+ 100% {
1096
+ background-position: 0% 50%;
1097
+ }
1098
+ }
1099
+
1100
+ @keyframes shimmer-bar {
1101
+ 0% {
1102
+ background-position: -200% 0;
1103
+ }
1104
+ 100% {
1105
+ background-position: 200% 0;
1106
+ }
1107
+ }
1108
+
1109
+ @keyframes bounce-dot {
1110
+ 0%, 80%, 100% {
1111
+ transform: scale(0.4);
1112
+ opacity: 0.3;
1113
+ }
1114
+ 40% {
1115
+ transform: scale(1.1);
1116
+ opacity: 1;
1117
+ }
1118
+ }
1119
+
1120
+ .radar-dot {
1121
+ width: 10px;
1122
+ height: 10px;
1123
+ background: #6366f1;
1124
+ border-radius: 50%;
1125
+ display: inline-block;
1126
+ animation: pulse-ring 1.6s cubic-bezier(0.45, 0, 0.55, 1) infinite;
1127
+ }
1128
+
1129
+ .radar-dot.green {
1130
+ background: #10b981;
1131
+ animation: pulse-ring-green 1.6s cubic-bezier(0.45, 0, 0.55, 1) infinite;
1132
+ }
1133
+
1134
+ .agent-live-banner {
1135
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.12) 0%, rgba(168, 85, 247, 0.12) 100%);
1136
+ border: 1px solid rgba(99, 102, 241, 0.4);
1137
+ border-radius: var(--radius-lg);
1138
+ padding: 1.25rem 1.5rem;
1139
+ margin-bottom: 1.5rem;
1140
+ animation: live-glow 2.5s ease-in-out infinite;
1141
+ display: flex;
1142
+ flex-direction: column;
1143
+ gap: 0.75rem;
1144
+ }
1145
+
1146
+ .agent-live-header {
1147
+ display: flex;
1148
+ align-items: center;
1149
+ justify-content: space-between;
1150
+ font-weight: 600;
1151
+ color: var(--text-primary);
1152
+ }
1153
+
1154
+ .thinking-shimmer-progress {
1155
+ height: 6px;
1156
+ width: 100%;
1157
+ background: linear-gradient(90deg, rgba(99, 102, 241, 0.2) 0%, #6366f1 30%, #ec4899 70%, rgba(168, 85, 247, 0.2) 100%);
1158
+ background-size: 200% 100%;
1159
+ animation: shimmer-bar 1.8s linear infinite;
1160
+ border-radius: 999px;
1161
+ }
1162
+
1163
+ .thinking-dots span {
1164
+ display: inline-block;
1165
+ width: 6px;
1166
+ height: 6px;
1167
+ background: currentColor;
1168
+ border-radius: 50%;
1169
+ margin: 0 2px;
1170
+ animation: bounce-dot 1.4s infinite ease-in-out both;
1171
+ }
1172
+ .thinking-dots span:nth-child(1) { animation-delay: -0.32s; }
1173
+ .thinking-dots span:nth-child(2) { animation-delay: -0.16s; }
1174
+
1175
+ .task-node.running {
1176
+ border-color: #6366f1 !important;
1177
+ animation: live-glow 2s ease-in-out infinite;
1178
+ background: linear-gradient(145deg, var(--bg-surface), rgba(99, 102, 241, 0.1)) !important;
1179
+ }
1180
+
1181
+ .status.RUNNING {
1182
+ background: rgba(99, 102, 241, 0.18) !important;
1183
+ color: #6366f1 !important;
1184
+ box-shadow: 0 0 12px rgba(99, 102, 241, 0.5);
1185
+ font-weight: 700;
1186
+ animation: live-glow 2s infinite;
1187
+ }
1188
+
1189
+ .live-thinking-pulse {
1190
+ display: flex;
1191
+ align-items: center;
1192
+ gap: 0.75rem;
1193
+ padding: 0.85rem 1.25rem;
1194
+ background: linear-gradient(90deg, rgba(99, 102, 241, 0.08) 0%, rgba(168, 85, 247, 0.08) 100%);
1195
+ border: 1px dashed rgba(99, 102, 241, 0.5);
1196
+ border-radius: var(--radius-md);
1197
+ margin-top: 1rem;
1198
+ color: #6366f1;
1199
+ font-weight: 500;
1200
+ font-size: 0.9rem;
1201
+ animation: live-glow 2.5s ease-in-out infinite;
1202
+ }
1203
+
frontend/src/pages/WorkflowPage.tsx CHANGED
@@ -64,11 +64,29 @@ export default function WorkflowPage() {
64
  <div>
65
  <h2>Workflow {workflow.workflow_id}</h2>
66
  {error && <p className="error">{error}</p>}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  <div className="card">
68
- <p>{workflow.user_prompt}</p>
69
- <p>
70
  <span className={`status ${workflow.status}`}>{workflow.status}</span>
71
- {" · "}{workflow.llm_provider} / {workflow.llm_model}
72
  </p>
73
  </div>
74
 
 
64
  <div>
65
  <h2>Workflow {workflow.workflow_id}</h2>
66
  {error && <p className="error">{error}</p>}
67
+ {isLive && (
68
+ <div className="agent-live-banner">
69
+ <div className="agent-live-header">
70
+ <div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
71
+ <span className="radar-dot green"></span>
72
+ <span style={{ fontSize: "1.05rem", fontWeight: 600 }}>Agent is Actively Working</span>
73
+ </div>
74
+ <span style={{ fontSize: "0.85rem", color: "#6366f1", fontWeight: 600, display: "flex", alignItems: "center", gap: "0.25rem" }}>
75
+ AUTONOMOUS LOOP <span className="thinking-dots"><span></span><span></span><span></span></span>
76
+ </span>
77
+ </div>
78
+ <p style={{ margin: 0, color: "var(--text-secondary)", fontSize: "0.9rem" }}>
79
+ The AI agent is planning architecture, generating code & tests, and orchestrating verification tasks in the background.
80
+ </p>
81
+ <div className="thinking-shimmer-progress"></div>
82
+ </div>
83
+ )}
84
+
85
  <div className="card">
86
+ <p style={{ fontSize: "1.1rem", marginBottom: "0.75rem", fontWeight: 500 }}>{workflow.user_prompt}</p>
87
+ <p style={{ display: "flex", alignItems: "center", gap: "0.75rem", margin: 0 }}>
88
  <span className={`status ${workflow.status}`}>{workflow.status}</span>
89
+ <span style={{ color: "var(--text-muted)", fontSize: "0.9rem" }}>{workflow.llm_provider} / {workflow.llm_model}</span>
90
  </p>
91
  </div>
92