Commit Β·
cfb0d65
1
Parent(s): 9fcca57
fix: restore frontend-backend compatibility with dual response keys
Browse files- server/app.py: return both 'state' and 'observation' for broad frontend support
- frontend/src/app/page.tsx: clean rewrite with robust boot logic and multi-field support
- Verified local connectivity and dual-key compliance
- frontend/src/app/page.tsx +70 -183
- server/app.py +10 -3
frontend/src/app/page.tsx
CHANGED
|
@@ -2,7 +2,11 @@
|
|
| 2 |
|
| 3 |
import { useState, useEffect } from 'react';
|
| 4 |
|
| 5 |
-
const API_URL = typeof window !== 'undefined'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
export default function Home() {
|
| 8 |
const [state, setState] = useState<any>(null);
|
|
@@ -28,8 +32,8 @@ export default function Home() {
|
|
| 28 |
const res = await fetch(`${API_URL}/state`);
|
| 29 |
if (res.ok) {
|
| 30 |
const data = await res.json();
|
| 31 |
-
const obs = data.observation || data;
|
| 32 |
-
if (obs && obs.
|
| 33 |
setState(obs);
|
| 34 |
setBooting(false);
|
| 35 |
return true;
|
|
@@ -47,7 +51,7 @@ export default function Home() {
|
|
| 47 |
try {
|
| 48 |
const res = await fetch(`${API_URL}/reset`, { method: 'POST' });
|
| 49 |
const data = await res.json();
|
| 50 |
-
const obs = data.observation || data;
|
| 51 |
setState(obs);
|
| 52 |
setLogs([{ role: 'system', message: 'Environment Reset Successfully' }]);
|
| 53 |
showStatus("Enterprise session initialized.", "success");
|
|
@@ -68,15 +72,15 @@ export default function Home() {
|
|
| 68 |
const res = await fetch(`${API_URL}/reset`, { method: 'POST' });
|
| 69 |
if (res.ok) {
|
| 70 |
const data = await res.json();
|
| 71 |
-
const obs = data.observation || data;
|
| 72 |
setState(obs);
|
| 73 |
setBooting(false);
|
| 74 |
addLog('Backend connected β session started', 'system');
|
| 75 |
} else {
|
| 76 |
-
setTimeout(boot,
|
| 77 |
}
|
| 78 |
} catch {
|
| 79 |
-
setTimeout(boot,
|
| 80 |
}
|
| 81 |
}
|
| 82 |
};
|
|
@@ -88,7 +92,7 @@ export default function Home() {
|
|
| 88 |
try {
|
| 89 |
actionObj = JSON.parse(actionInput.trim());
|
| 90 |
} catch (e) {
|
| 91 |
-
throw new Error("Invalid JSON
|
| 92 |
}
|
| 93 |
|
| 94 |
const res = await fetch(`${API_URL}/step`, {
|
|
@@ -97,24 +101,20 @@ export default function Home() {
|
|
| 97 |
body: JSON.stringify(actionObj)
|
| 98 |
});
|
| 99 |
|
| 100 |
-
if (!res.ok) {
|
| 101 |
-
const errorData = await res.json().catch(() => ({ detail: "Server error" }));
|
| 102 |
-
throw new Error(errorData.detail || "Step failed.");
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
const data = await res.json();
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
| 108 |
if (obs.status === "session_complete") {
|
| 109 |
addLog('π Session Complete!', 'system');
|
| 110 |
-
|
| 111 |
-
showStatus("Session finished successfully!", "success");
|
| 112 |
} else {
|
| 113 |
-
setState(obs);
|
| 114 |
const reward = data.reward ?? 0;
|
| 115 |
const msg = data.info?.message || "Action processed";
|
| 116 |
addLog(`Action: ${actionObj.action_type}`, 'agent', `${msg} | Reward: ${reward.toFixed(3)}`, reward >= 0 ? "success" : "failed");
|
| 117 |
-
showStatus(`Action
|
| 118 |
}
|
| 119 |
} catch (e: any) {
|
| 120 |
showStatus(e.message || "Network Error.", "error");
|
|
@@ -127,13 +127,12 @@ export default function Home() {
|
|
| 127 |
setScore(null);
|
| 128 |
try {
|
| 129 |
const res = await fetch(`${API_URL}/grader?task_id=task_hard_1`);
|
| 130 |
-
if (!res.ok) throw new Error("Evaluation engine unreachable.");
|
| 131 |
const data = await res.json();
|
| 132 |
setScore(data.score ?? 0);
|
| 133 |
-
addLog(`Evaluation: ${(data.score * 100).toFixed(0)}%`, 'system');
|
| 134 |
-
showStatus("
|
| 135 |
} catch (e: any) {
|
| 136 |
-
showStatus(
|
| 137 |
}
|
| 138 |
setLoading(false);
|
| 139 |
};
|
|
@@ -152,221 +151,109 @@ export default function Home() {
|
|
| 152 |
<p style={{ color: 'var(--muted)', fontSize: '1.1rem', margin: '0.25rem 0 0 0' }}>AI Decision Monitoring System</p>
|
| 153 |
</div>
|
| 154 |
<div style={{ textAlign: 'right' }}>
|
| 155 |
-
<button className="btn btn-outline" onClick={resetEnv} disabled={loading}>
|
| 156 |
{loading ? 'Processing...' : 'New Session'}
|
| 157 |
</button>
|
| 158 |
</div>
|
| 159 |
</header>
|
| 160 |
|
| 161 |
{statusMsg && (
|
| 162 |
-
<div
|
| 163 |
-
position: 'fixed',
|
| 164 |
-
|
| 165 |
-
right: '20px',
|
| 166 |
-
zIndex: 1000,
|
| 167 |
-
padding: '1rem 2rem',
|
| 168 |
-
borderRadius: '12px',
|
| 169 |
background: statusMsg.type === 'success' ? '#10b981' : '#ef4444',
|
| 170 |
-
color: 'white',
|
| 171 |
-
|
| 172 |
-
boxShadow: '0 20px 25px -5px rgba(0,0,0,0.1)',
|
| 173 |
-
display: 'flex',
|
| 174 |
-
alignItems: 'center',
|
| 175 |
-
gap: '10px'
|
| 176 |
}}>
|
| 177 |
{statusMsg.type === 'success' ? 'β
' : 'β'} {statusMsg.text}
|
| 178 |
</div>
|
| 179 |
)}
|
| 180 |
|
| 181 |
-
<div className="layout-grid"
|
| 182 |
<section style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
| 183 |
-
|
| 184 |
<div className="glass-card">
|
| 185 |
{booting ? (
|
| 186 |
<div style={{ textAlign: 'center', padding: '5rem' }}>
|
| 187 |
<div style={{ fontSize: '3rem', marginBottom: '1.5rem', animation: 'spin 2s linear infinite', display: 'inline-block' }}>βοΈ</div>
|
| 188 |
-
<h2 style={{ fontSize: '1.5rem', fontWeight: 800
|
| 189 |
<p style={{ color: 'var(--muted)' }}>Connecting to backend... Attempt {bootAttempt}</p>
|
| 190 |
-
<div style={{ marginTop: '1rem', fontSize: '0.8rem', opacity: 0.6 }}>Waiting for high-performance simulation environment</div>
|
| 191 |
</div>
|
| 192 |
) : state && state.status !== "session_complete" ? (
|
| 193 |
<div style={{ display: 'grid', gap: '1.5rem' }}>
|
| 194 |
-
<div style={{ display: 'flex', justifyContent: 'space-between'
|
| 195 |
<div style={{ flex: 1 }}>
|
| 196 |
-
<span style={{ fontSize: '0.7rem', fontWeight: 800, color: 'var(--primary)', textTransform: 'uppercase'
|
| 197 |
-
<p style={{ marginTop: '0.5rem', fontSize: '1.
|
| 198 |
</div>
|
| 199 |
-
<div style={{
|
| 200 |
-
<div style={{ fontSize: '0.7rem', fontWeight: 800, color:
|
| 201 |
-
<div style={{ fontSize: '1.5rem', fontWeight: 800
|
| 202 |
-
{state.steps_taken || 0} / {state.sla_limit || 10}
|
| 203 |
-
</div>
|
| 204 |
</div>
|
| 205 |
</div>
|
| 206 |
|
| 207 |
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '1rem' }}>
|
| 208 |
-
|
| 209 |
-
<div style={{
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
<div style={{ fontSize: '0.65rem', fontWeight: 700, color: 'var(--muted)', marginBottom: '0.25rem', textTransform: 'uppercase' }}>Status</div>
|
| 218 |
-
<div className={`badge badge-${state.status}`} style={{ fontSize: '0.75rem', width: '100%', textAlign: 'center' }}>{state.status}</div>
|
| 219 |
-
</div>
|
| 220 |
-
<div className="glass-card" style={{ padding: '0.75rem' }}>
|
| 221 |
-
<div style={{ fontSize: '0.65rem', fontWeight: 700, color: 'var(--muted)', marginBottom: '0.25rem', textTransform: 'uppercase' }}>Reward</div>
|
| 222 |
-
<div style={{ fontSize: '0.8rem', fontWeight: 900, textAlign: 'center', color: 'var(--primary)' }}>+{(state.total_reward || 0).toFixed(2)}</div>
|
| 223 |
</div>
|
| 224 |
</div>
|
| 225 |
</div>
|
| 226 |
) : state?.status === "session_complete" ? (
|
| 227 |
<div style={{ textAlign: 'center', padding: '4rem' }}>
|
| 228 |
-
<div style={{ fontSize: '4rem'
|
| 229 |
-
<h2 style={{ fontSize: '2rem', fontWeight: 800 }}>
|
| 230 |
<div style={{ display: 'flex', justifyContent: 'center', gap: '3rem', marginTop: '2rem' }}>
|
| 231 |
-
<div>
|
| 232 |
-
|
| 233 |
-
<div style={{ fontSize: '2rem', fontWeight: 900 }}>{state.resolved}</div>
|
| 234 |
-
</div>
|
| 235 |
-
<div>
|
| 236 |
-
<div style={{ color: 'var(--muted)', fontSize: '0.8rem', fontWeight: 700 }}>TOTAL REWARD</div>
|
| 237 |
-
<div style={{ fontSize: '2rem', fontWeight: 900, color: 'var(--primary)' }}>{state.total_reward?.toFixed(2) || '0.00'}</div>
|
| 238 |
-
</div>
|
| 239 |
</div>
|
| 240 |
<button className="btn" style={{ marginTop: '2rem' }} onClick={resetEnv}>Start New Session</button>
|
| 241 |
</div>
|
| 242 |
) : (
|
| 243 |
-
<div style={{ textAlign: 'center', padding: '5rem', color: 'var(--muted)' }}>
|
| 244 |
-
Waiting for engine state...
|
| 245 |
-
</div>
|
| 246 |
)}
|
| 247 |
</div>
|
| 248 |
|
| 249 |
<div className="glass-card">
|
| 250 |
-
<
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
<button className="btn btn-outline" style={{ fontSize: '0.7rem', padding: '0.25rem 0.5rem' }} onClick={() => setActionInput('{\n "action_type": "generate_response",\n "payload": { "response": "I apologize for the delay, we are fixing this now." }\n}')}>βοΈ Reply</button>
|
| 256 |
-
<button className="btn btn-outline" style={{ fontSize: '0.7rem', padding: '0.25rem 0.5rem' }} onClick={() => setActionInput('{\n "action_type": "resolve",\n "payload": {}\n}')}>β
Resolve</button>
|
| 257 |
-
</div>
|
| 258 |
-
</div>
|
| 259 |
-
|
| 260 |
-
<div style={{ marginBottom: '1.5rem' }}>
|
| 261 |
-
<textarea
|
| 262 |
-
value={actionInput}
|
| 263 |
-
onChange={(e) => setActionInput(e.target.value)}
|
| 264 |
-
rows={5}
|
| 265 |
-
style={{ fontSize: '0.9rem', fontFamily: 'monospace', padding: '1rem', background: '#f8fafc', borderRadius: '12px' }}
|
| 266 |
-
placeholder="Enter AI Action JSON..."
|
| 267 |
-
/>
|
| 268 |
-
</div>
|
| 269 |
<div style={{ display: 'flex', gap: '1rem' }}>
|
| 270 |
-
<button className="btn" onClick={sendAction} disabled={loading || booting || !state
|
| 271 |
{loading ? 'Executing...' : 'Execute Action'}
|
| 272 |
</button>
|
| 273 |
-
<button className="btn btn-outline" onClick={runHardGrader} disabled={loading || booting || !state} style={{ flex: 1 }}>
|
| 274 |
-
Grade Model
|
| 275 |
-
</button>
|
| 276 |
</div>
|
| 277 |
</div>
|
| 278 |
-
|
| 279 |
-
{score !== null && (
|
| 280 |
-
<div className="glass-card animate-slide" style={{ border: '2px solid #10b981', background: '#ecfdf5' }}>
|
| 281 |
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
| 282 |
-
<div>
|
| 283 |
-
<h3 style={{ margin: 0, fontSize: '0.9rem', color: '#065f46', fontWeight: 800, textTransform: 'uppercase' }}>Benchmark Result</h3>
|
| 284 |
-
<p style={{ margin: '0.25rem 0 0 0', color: '#047857', fontSize: '0.8rem' }}>Hard Task Evaluation Suite</p>
|
| 285 |
-
</div>
|
| 286 |
-
<div style={{ fontSize: '3rem', fontWeight: 950, color: '#059669', letterSpacing: '-0.05em' }}>
|
| 287 |
-
{(score * 100).toFixed(0)}%
|
| 288 |
-
</div>
|
| 289 |
-
</div>
|
| 290 |
-
</div>
|
| 291 |
-
)}
|
| 292 |
-
|
| 293 |
</section>
|
| 294 |
|
| 295 |
<section style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
| 296 |
-
<div className="glass-card" style={{ flex: 1, maxHeight: '
|
| 297 |
-
<
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
{/* Progress Tracker */}
|
| 303 |
-
<div style={{ marginBottom: '1.5rem' }}>
|
| 304 |
-
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.7rem', fontWeight: 700, marginBottom: '0.4rem', color: 'var(--muted)' }}>
|
| 305 |
-
<span>SESSION PROGRESS</span>
|
| 306 |
-
<span>{((state?.info?.resolved || 0) / 3 * 100).toFixed(0)}%</span>
|
| 307 |
-
</div>
|
| 308 |
-
<div style={{ width: '100%', height: '6px', background: '#f1f5f9', borderRadius: '10px', overflow: 'hidden' }}>
|
| 309 |
-
<div style={{
|
| 310 |
-
width: `${((state?.info?.resolved || 0) / 3 * 100)}%`,
|
| 311 |
-
height: '100%',
|
| 312 |
-
background: 'var(--primary)',
|
| 313 |
-
transition: 'width 0.5s ease-out'
|
| 314 |
-
}}></div>
|
| 315 |
-
</div>
|
| 316 |
-
</div>
|
| 317 |
-
|
| 318 |
-
<div style={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
| 319 |
-
{state?.info?.queue && state.info.queue.length > 0 ? state.info.queue.map((q: string, i: number) => {
|
| 320 |
-
const isFirst = i === 0;
|
| 321 |
-
const difficulty = q.length > 25 ? "HARD" : q.length > 20 ? "MEDIUM" : "EASY";
|
| 322 |
-
return (
|
| 323 |
-
<div key={i} className="glass-card" style={{
|
| 324 |
-
padding: '0.75rem',
|
| 325 |
-
fontSize: '0.85rem',
|
| 326 |
-
background: isFirst ? '#f0f9ff' : 'white',
|
| 327 |
-
borderLeft: isFirst ? '4px solid var(--primary)' : '1px solid var(--card-border)',
|
| 328 |
-
position: 'relative',
|
| 329 |
-
opacity: isFirst ? 1 : 0.8,
|
| 330 |
-
animation: isFirst ? 'pulse 2s infinite' : 'none'
|
| 331 |
-
}}>
|
| 332 |
-
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '0.4rem' }}>
|
| 333 |
-
<span style={{ fontSize: '0.6rem', fontWeight: 800, color: isFirst ? 'var(--primary)' : 'var(--muted)' }}>
|
| 334 |
-
{isFirst ? 'β ACTIVE NOW' : `UPCOMING #${i + 1}`}
|
| 335 |
-
</span>
|
| 336 |
-
<span style={{ fontSize: '0.55rem', fontWeight: 900, color: difficulty === 'HARD' ? 'var(--error)' : 'var(--muted)' }}>
|
| 337 |
-
{difficulty}
|
| 338 |
-
</span>
|
| 339 |
-
</div>
|
| 340 |
-
<div style={{ fontWeight: 600, color: 'var(--foreground)' }}>{q}</div>
|
| 341 |
-
<div style={{ marginTop: '0.5rem', fontSize: '0.65rem', color: 'var(--muted)', display: 'flex', gap: '10px' }}>
|
| 342 |
-
<span>β±οΈ {isFirst ? 'Est. 2m' : `${(i+1)*3}m wait`}</span>
|
| 343 |
-
<span>π’ Tier 1</span>
|
| 344 |
-
</div>
|
| 345 |
-
</div>
|
| 346 |
-
);
|
| 347 |
-
}) : (
|
| 348 |
-
<div style={{ textAlign: 'center', padding: '2rem', color: 'var(--muted)' }}>
|
| 349 |
-
<div style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>π―</div>
|
| 350 |
-
<p style={{ fontSize: '0.8rem' }}>Queue Cleared</p>
|
| 351 |
</div>
|
| 352 |
-
)}
|
| 353 |
</div>
|
| 354 |
</div>
|
| 355 |
|
| 356 |
<div className="glass-card" style={{ height: '350px', display: 'flex', flexDirection: 'column' }}>
|
| 357 |
-
<h2 style={{ fontSize: '
|
| 358 |
-
<div style={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column-reverse', gap: '0.
|
| 359 |
{logs.map((log, i) => (
|
| 360 |
-
<div key={i} className={`log-entry ${log.role === 'agent' ? 'log-agent' : 'log-customer'}`}
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
<div style={{ fontSize: '0.65rem', fontWeight: 900, textTransform: 'uppercase', marginBottom: '0.25rem', opacity: 0.6, display: 'flex', justifyContent: 'space-between' }}>
|
| 365 |
-
<span>{log.role}</span>
|
| 366 |
-
{log.status && <span style={{ color: log.status === 'success' ? '#10b981' : '#ef4444' }}>{log.status.toUpperCase()}</span>}
|
| 367 |
-
</div>
|
| 368 |
-
<div style={{ fontSize: '0.9rem', fontWeight: 600 }}>{log.message}</div>
|
| 369 |
-
{log.info && <div style={{ fontSize: '0.7rem', color: 'var(--primary)', marginTop: '0.25rem', fontWeight: 700 }}>{log.info}</div>}
|
| 370 |
</div>
|
| 371 |
))}
|
| 372 |
</div>
|
|
|
|
| 2 |
|
| 3 |
import { useState, useEffect } from 'react';
|
| 4 |
|
| 5 |
+
const API_URL = typeof window !== 'undefined'
|
| 6 |
+
? (window.location.origin.includes('localhost') || window.location.origin.includes('127.0.0.1')
|
| 7 |
+
? 'http://127.0.0.1:7860'
|
| 8 |
+
: window.location.origin)
|
| 9 |
+
: "http://127.0.0.1:7860";
|
| 10 |
|
| 11 |
export default function Home() {
|
| 12 |
const [state, setState] = useState<any>(null);
|
|
|
|
| 32 |
const res = await fetch(`${API_URL}/state`);
|
| 33 |
if (res.ok) {
|
| 34 |
const data = await res.json();
|
| 35 |
+
const obs = data.observation || data.state || data;
|
| 36 |
+
if (obs && obs.status !== "session_complete") {
|
| 37 |
setState(obs);
|
| 38 |
setBooting(false);
|
| 39 |
return true;
|
|
|
|
| 51 |
try {
|
| 52 |
const res = await fetch(`${API_URL}/reset`, { method: 'POST' });
|
| 53 |
const data = await res.json();
|
| 54 |
+
const obs = data.observation || data.state || data;
|
| 55 |
setState(obs);
|
| 56 |
setLogs([{ role: 'system', message: 'Environment Reset Successfully' }]);
|
| 57 |
showStatus("Enterprise session initialized.", "success");
|
|
|
|
| 72 |
const res = await fetch(`${API_URL}/reset`, { method: 'POST' });
|
| 73 |
if (res.ok) {
|
| 74 |
const data = await res.json();
|
| 75 |
+
const obs = data.observation || data.state || data;
|
| 76 |
setState(obs);
|
| 77 |
setBooting(false);
|
| 78 |
addLog('Backend connected β session started', 'system');
|
| 79 |
} else {
|
| 80 |
+
setTimeout(boot, 2000);
|
| 81 |
}
|
| 82 |
} catch {
|
| 83 |
+
setTimeout(boot, 2000);
|
| 84 |
}
|
| 85 |
}
|
| 86 |
};
|
|
|
|
| 92 |
try {
|
| 93 |
actionObj = JSON.parse(actionInput.trim());
|
| 94 |
} catch (e) {
|
| 95 |
+
throw new Error("Invalid JSON format.");
|
| 96 |
}
|
| 97 |
|
| 98 |
const res = await fetch(`${API_URL}/step`, {
|
|
|
|
| 101 |
body: JSON.stringify(actionObj)
|
| 102 |
});
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
const data = await res.json();
|
| 105 |
+
if (!res.ok) throw new Error(data.detail || "Step failed.");
|
| 106 |
+
|
| 107 |
+
const obs = data.observation || data.state;
|
| 108 |
+
setState(obs);
|
| 109 |
+
|
| 110 |
if (obs.status === "session_complete") {
|
| 111 |
addLog('π Session Complete!', 'system');
|
| 112 |
+
showStatus("Session finished!", "success");
|
|
|
|
| 113 |
} else {
|
|
|
|
| 114 |
const reward = data.reward ?? 0;
|
| 115 |
const msg = data.info?.message || "Action processed";
|
| 116 |
addLog(`Action: ${actionObj.action_type}`, 'agent', `${msg} | Reward: ${reward.toFixed(3)}`, reward >= 0 ? "success" : "failed");
|
| 117 |
+
showStatus(`Action: ${reward >= 0 ? 'SUCCESS' : 'DEDUCTION'}`, reward >= 0 ? "success" : "error");
|
| 118 |
}
|
| 119 |
} catch (e: any) {
|
| 120 |
showStatus(e.message || "Network Error.", "error");
|
|
|
|
| 127 |
setScore(null);
|
| 128 |
try {
|
| 129 |
const res = await fetch(`${API_URL}/grader?task_id=task_hard_1`);
|
|
|
|
| 130 |
const data = await res.json();
|
| 131 |
setScore(data.score ?? 0);
|
| 132 |
+
addLog(`Evaluation Score: ${(data.score * 100).toFixed(0)}%`, 'system');
|
| 133 |
+
showStatus("Evaluation complete.", "success");
|
| 134 |
} catch (e: any) {
|
| 135 |
+
showStatus("Grader unavailable.", "error");
|
| 136 |
}
|
| 137 |
setLoading(false);
|
| 138 |
};
|
|
|
|
| 151 |
<p style={{ color: 'var(--muted)', fontSize: '1.1rem', margin: '0.25rem 0 0 0' }}>AI Decision Monitoring System</p>
|
| 152 |
</div>
|
| 153 |
<div style={{ textAlign: 'right' }}>
|
| 154 |
+
<button className="btn btn-outline" onClick={resetEnv} disabled={loading || booting}>
|
| 155 |
{loading ? 'Processing...' : 'New Session'}
|
| 156 |
</button>
|
| 157 |
</div>
|
| 158 |
</header>
|
| 159 |
|
| 160 |
{statusMsg && (
|
| 161 |
+
<div style={{
|
| 162 |
+
position: 'fixed', top: '20px', right: '20px', zIndex: 1000,
|
| 163 |
+
padding: '1rem 2rem', borderRadius: '12px',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
background: statusMsg.type === 'success' ? '#10b981' : '#ef4444',
|
| 165 |
+
color: 'white', fontWeight: 700, boxShadow: '0 20px 25px -5px rgba(0,0,0,0.1)',
|
| 166 |
+
display: 'flex', alignItems: 'center', gap: '10px'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
}}>
|
| 168 |
{statusMsg.type === 'success' ? 'β
' : 'β'} {statusMsg.text}
|
| 169 |
</div>
|
| 170 |
)}
|
| 171 |
|
| 172 |
+
<div className="layout-grid">
|
| 173 |
<section style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
|
|
|
| 174 |
<div className="glass-card">
|
| 175 |
{booting ? (
|
| 176 |
<div style={{ textAlign: 'center', padding: '5rem' }}>
|
| 177 |
<div style={{ fontSize: '3rem', marginBottom: '1.5rem', animation: 'spin 2s linear infinite', display: 'inline-block' }}>βοΈ</div>
|
| 178 |
+
<h2 style={{ fontSize: '1.5rem', fontWeight: 800 }}>Starting Engine</h2>
|
| 179 |
<p style={{ color: 'var(--muted)' }}>Connecting to backend... Attempt {bootAttempt}</p>
|
|
|
|
| 180 |
</div>
|
| 181 |
) : state && state.status !== "session_complete" ? (
|
| 182 |
<div style={{ display: 'grid', gap: '1.5rem' }}>
|
| 183 |
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
| 184 |
<div style={{ flex: 1 }}>
|
| 185 |
+
<span style={{ fontSize: '0.7rem', fontWeight: 800, color: 'var(--primary)', textTransform: 'uppercase' }}>Current Ticket</span>
|
| 186 |
+
<p style={{ marginTop: '0.5rem', fontSize: '1.4rem', fontWeight: 600 }}>"{state.ticket_text}"</p>
|
| 187 |
</div>
|
| 188 |
+
<div style={{ textAlign: 'right', minWidth: '100px' }}>
|
| 189 |
+
<div style={{ fontSize: '0.7rem', fontWeight: 800, color: 'var(--muted)' }}>SLA</div>
|
| 190 |
+
<div style={{ fontSize: '1.5rem', fontWeight: 800 }}>{state.steps_taken || 0} / {state.sla_limit || 10}</div>
|
|
|
|
|
|
|
| 191 |
</div>
|
| 192 |
</div>
|
| 193 |
|
| 194 |
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '1rem' }}>
|
| 195 |
+
{['sentiment', 'priority', 'status'].map(key => (
|
| 196 |
+
<div key={key} className="glass-card" style={{ padding: '0.75rem', textAlign: 'center' }}>
|
| 197 |
+
<div style={{ fontSize: '0.6rem', fontWeight: 700, color: 'var(--muted)', textTransform: 'uppercase' }}>{key}</div>
|
| 198 |
+
<div className={`badge badge-${state[key] || 'neutral'}`} style={{ fontSize: '0.7rem', marginTop: '0.25rem' }}>{state[key] || 'OPEN'}</div>
|
| 199 |
+
</div>
|
| 200 |
+
))}
|
| 201 |
+
<div className="glass-card" style={{ padding: '0.75rem', textAlign: 'center' }}>
|
| 202 |
+
<div style={{ fontSize: '0.6rem', fontWeight: 700, color: 'var(--muted)', textTransform: 'uppercase' }}>Reward</div>
|
| 203 |
+
<div style={{ fontSize: '0.8rem', fontWeight: 900, color: 'var(--primary)' }}>+{(state.total_reward || 0).toFixed(2)}</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
</div>
|
| 205 |
</div>
|
| 206 |
</div>
|
| 207 |
) : state?.status === "session_complete" ? (
|
| 208 |
<div style={{ textAlign: 'center', padding: '4rem' }}>
|
| 209 |
+
<div style={{ fontSize: '4rem' }}>π</div>
|
| 210 |
+
<h2 style={{ fontSize: '2rem', fontWeight: 800 }}>Queue Completed</h2>
|
| 211 |
<div style={{ display: 'flex', justifyContent: 'center', gap: '3rem', marginTop: '2rem' }}>
|
| 212 |
+
<div><div style={{ color: 'var(--muted)', fontWeight: 700 }}>RESOLVED</div><div style={{ fontSize: '2rem', fontWeight: 900 }}>{state.resolved}</div></div>
|
| 213 |
+
<div><div style={{ color: 'var(--muted)', fontWeight: 700 }}>TOTAL REWARD</div><div style={{ fontSize: '2rem', fontWeight: 900, color: 'var(--primary)' }}>{state.total_reward?.toFixed(2)}</div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
</div>
|
| 215 |
<button className="btn" style={{ marginTop: '2rem' }} onClick={resetEnv}>Start New Session</button>
|
| 216 |
</div>
|
| 217 |
) : (
|
| 218 |
+
<div style={{ textAlign: 'center', padding: '5rem', color: 'var(--muted)' }}>Initializing Enterprise Service...</div>
|
|
|
|
|
|
|
| 219 |
)}
|
| 220 |
</div>
|
| 221 |
|
| 222 |
<div className="glass-card">
|
| 223 |
+
<h2 style={{ fontSize: '1.2rem', fontWeight: 700, marginBottom: '1rem' }}>Control Center</h2>
|
| 224 |
+
<textarea
|
| 225 |
+
value={actionInput} onChange={(e) => setActionInput(e.target.value)}
|
| 226 |
+
rows={5} style={{ fontSize: '0.9rem', fontFamily: 'monospace', marginBottom: '1.5rem' }}
|
| 227 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
<div style={{ display: 'flex', gap: '1rem' }}>
|
| 229 |
+
<button className="btn" onClick={sendAction} disabled={loading || booting || !state} style={{ flex: 2 }}>
|
| 230 |
{loading ? 'Executing...' : 'Execute Action'}
|
| 231 |
</button>
|
| 232 |
+
<button className="btn btn-outline" onClick={runHardGrader} disabled={loading || booting || !state} style={{ flex: 1 }}>Grade</button>
|
|
|
|
|
|
|
| 233 |
</div>
|
| 234 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
</section>
|
| 236 |
|
| 237 |
<section style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
| 238 |
+
<div className="glass-card" style={{ flex: 1, maxHeight: '400px', display: 'flex', flexDirection: 'column' }}>
|
| 239 |
+
<h2 style={{ fontSize: '1rem', fontWeight: 700, marginBottom: '1rem' }}>Support Queue</h2>
|
| 240 |
+
<div style={{ flex: 1, overflowY: 'auto' }}>
|
| 241 |
+
{state?.info?.queue?.map((q: string, i: number) => (
|
| 242 |
+
<div key={i} style={{ padding: '0.75rem', borderBottom: '1px solid var(--card-border)', fontSize: '0.85rem', opacity: i === 0 ? 1 : 0.6 }}>
|
| 243 |
+
#{i+1}: {q}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
</div>
|
| 245 |
+
)) || <div style={{ color: 'var(--muted)' }}>Queue is empty</div>}
|
| 246 |
</div>
|
| 247 |
</div>
|
| 248 |
|
| 249 |
<div className="glass-card" style={{ height: '350px', display: 'flex', flexDirection: 'column' }}>
|
| 250 |
+
<h2 style={{ fontSize: '1rem', fontWeight: 700, marginBottom: '1rem' }}>Activity Logs</h2>
|
| 251 |
+
<div style={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column-reverse', gap: '0.5rem' }}>
|
| 252 |
{logs.map((log, i) => (
|
| 253 |
+
<div key={i} className={`log-entry ${log.role === 'agent' ? 'log-agent' : 'log-customer'}`}>
|
| 254 |
+
<div style={{ fontSize: '0.6rem', fontWeight: 900, opacity: 0.5 }}>{log.role.toUpperCase()}</div>
|
| 255 |
+
<div style={{ fontSize: '0.85rem', fontWeight: 600 }}>{log.message}</div>
|
| 256 |
+
{log.info && <div style={{ fontSize: '0.7rem', color: 'var(--primary)' }}>{log.info}</div>}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
</div>
|
| 258 |
))}
|
| 259 |
</div>
|
server/app.py
CHANGED
|
@@ -112,8 +112,10 @@ def get_schema():
|
|
| 112 |
def reset_env():
|
| 113 |
"""Reset the environment and yield the initial observation (GET or POST)."""
|
| 114 |
obs = env_instance.reset()
|
|
|
|
| 115 |
return {
|
| 116 |
-
"observation":
|
|
|
|
| 117 |
"reward": 0.0,
|
| 118 |
"done": False
|
| 119 |
}
|
|
@@ -127,8 +129,10 @@ def step_env(action: Action):
|
|
| 127 |
env_instance.reset()
|
| 128 |
|
| 129 |
obs, reward, done, info = env_instance.step(action)
|
|
|
|
| 130 |
return {
|
| 131 |
-
"observation":
|
|
|
|
| 132 |
"reward": float(reward.value),
|
| 133 |
"done": bool(done),
|
| 134 |
"info": info
|
|
@@ -144,7 +148,10 @@ def get_state():
|
|
| 144 |
if state.get("status") == "session_complete":
|
| 145 |
obs = env_instance.reset()
|
| 146 |
state = obs.state
|
| 147 |
-
return {
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
|
| 150 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 112 |
def reset_env():
|
| 113 |
"""Reset the environment and yield the initial observation (GET or POST)."""
|
| 114 |
obs = env_instance.reset()
|
| 115 |
+
state = obs.state
|
| 116 |
return {
|
| 117 |
+
"observation": state,
|
| 118 |
+
"state": state, # Legacy compatibility
|
| 119 |
"reward": 0.0,
|
| 120 |
"done": False
|
| 121 |
}
|
|
|
|
| 129 |
env_instance.reset()
|
| 130 |
|
| 131 |
obs, reward, done, info = env_instance.step(action)
|
| 132 |
+
state = obs.state
|
| 133 |
return {
|
| 134 |
+
"observation": state,
|
| 135 |
+
"state": state, # Legacy compatibility
|
| 136 |
"reward": float(reward.value),
|
| 137 |
"done": bool(done),
|
| 138 |
"info": info
|
|
|
|
| 148 |
if state.get("status") == "session_complete":
|
| 149 |
obs = env_instance.reset()
|
| 150 |
state = obs.state
|
| 151 |
+
return {
|
| 152 |
+
"observation": state,
|
| 153 |
+
"state": state # Legacy compatibility
|
| 154 |
+
}
|
| 155 |
|
| 156 |
|
| 157 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|