feat: complete phase 5 -- error hardening, frontend, Docker, demo, docs

Backend:
- ConversationTracker: Protocol + PostgresConversationTracker for lifecycle tracking
- Error handler: ErrorCategory enum, classify_error(), with_retry() exponential backoff
- Wire PostgresAnalyticsRecorder + ConversationTracker into ws_handler
- Rate limiting (10 msg/10s per thread), edge case hardening
- Health endpoint GET /api/health, version 0.5.0
- Demo seed data script + sample OpenAPI spec

Frontend (all new):
- React Router with NavBar (Chat / Replay / Dashboard / Review)
- ReplayListPage + ReplayPage with ReplayTimeline component
- DashboardPage with MetricCard, range selector, zero-state
- ReviewPage for OpenAPI classification review
- ErrorBanner for WebSocket disconnect handling
- API client (api.ts) with typed fetch wrappers

Infrastructure:
- Frontend Dockerfile (multi-stage node -> nginx)
- nginx.conf with SPA routing + API/WS proxy
- docker-compose.yml with frontend service + healthchecks
- .env.example files (root + backend)

Documentation:
- README.md with quick start and architecture
- Agent configuration guide
- OpenAPI import guide
- Deployment guide
- Demo script

48 new tests, 449 total passing, 92.87% coverage
This commit is contained in:
Yaojia Wang
2026-03-31 21:20:06 +02:00
parent 38644594d2
commit 0e78e5b06b
44 changed files with 3397 additions and 169 deletions

View File

@@ -0,0 +1,64 @@
import { NavLink } from "react-router-dom";
const navLinks = [
{ to: "/", label: "Chat", exact: true },
{ to: "/replay", label: "Replay" },
{ to: "/dashboard", label: "Dashboard" },
{ to: "/review", label: "API Review" },
];
const styles: Record<string, React.CSSProperties> = {
nav: {
display: "flex",
alignItems: "center",
gap: "0",
padding: "0 16px",
borderBottom: "1px solid #e0e0e0",
background: "#fff",
height: "48px",
boxShadow: "0 1px 4px rgba(0,0,0,0.06)",
},
brand: {
fontWeight: 700,
fontSize: "16px",
color: "#1a1a1a",
marginRight: "24px",
textDecoration: "none",
},
link: {
padding: "0 14px",
height: "48px",
display: "flex",
alignItems: "center",
fontSize: "14px",
color: "#555",
textDecoration: "none",
borderBottom: "2px solid transparent",
transition: "color 0.15s, border-color 0.15s",
},
activeLink: {
color: "#1976d2",
borderBottom: "2px solid #1976d2",
},
};
export function NavBar() {
return (
<nav style={styles.nav}>
<span style={styles.brand}>Smart Support</span>
{navLinks.map(({ to, label }) => (
<NavLink
key={to}
to={to}
end={to === "/"}
style={({ isActive }) => ({
...styles.link,
...(isActive ? styles.activeLink : {}),
})}
>
{label}
</NavLink>
))}
</nav>
);
}