Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ Everything below is built and working in the codebase:

### Frontend
- React 18 + TypeScript + Vite + Tailwind CSS + `@tailwindcss/typography`
- Font: Inter Variable (Google Fonts) with Linear's full fallback stack
- Font: Inter Variable (Google Fonts) with a full system fallback stack
- Routes: `/`, `/chat/:sessionId`, `/dashboard`, `/monitoring`, `/monitoring/:alertId`, `/history`, `/settings`, `/users`, `/login`
- Chat page: SSE streaming, tool call inspector, cost/latency card, stop button, suggestion chips on empty state, `?prompt=` deeplink support
- Sidebar: paginated session list (15 at a time), three-dot menu with delete, real `<a>` links for native right-click
- Sidebar: paginated session list (15 at a time), three-dot menu with rename + delete (portal-based, no overflow clipping), real `<a>` links for native right-click
- Settings: Environment, Agent Config, Integrations (UI stubs), AWS Configuration (editable, admin only), Preferences (dark mode)

### Auth & MCP
Expand Down Expand Up @@ -168,7 +168,7 @@ Incomplete items from the README roadmap (do not mark complete here — update R
| Linting | `ruff>=0.4.0` (line-length 100, Python 3.11 target, `asyncio_mode = "auto"`) |
| Package manager | **uv** — always `uv run` and `uv add`, never bare `pip` |
| Frontend | React 18, TypeScript, Vite, Tailwind CSS 3, `@tailwindcss/typography` |
| Font | Inter Variable (Google Fonts) — Linear-style fallback stack in `tailwind.config.js` |
| Font | Inter Variable (Google Fonts) — full system fallback stack in `tailwind.config.js` |

---

Expand Down
14 changes: 11 additions & 3 deletions docs/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Tool calls appear as collapsible cards as they complete, showing the tool name,
arguments, and result. A contextual status label ("Digging through CloudTrail…",
"Checking Lambda metrics…") appears while each tool runs.

**Follow-up suggestions** — after an investigation completes, up to 3 drill-down
question chips appear above the input box. Clicking one sends it immediately as the
next message. The chips are generated by the agent in the same final tool call at
no extra cost and disappear once you start a new message.

**Tool call inspector** — each tool call card is collapsed by default; click to expand
and see the full arguments and result JSON.

Expand All @@ -45,8 +50,9 @@ Lists all past sessions sorted by last activity. Each entry shows:
**Actions:**
- Click any session to load it in the chat view with full history restored
- **New chat** button starts a fresh session with a new UUID
- **Delete** (trash icon) soft-deletes the session — it disappears from the list
immediately but data is preserved in the database
- **⋯ menu** on each session row:
- **Rename** — shows an inline input pre-filled with the current title; Enter or blur saves, Escape cancels
- **Delete** — soft-deletes the session; it disappears from the list immediately but data is preserved in the database

---

Expand All @@ -64,6 +70,8 @@ Click any row to open the session in the chat page.

## Dashboard page

Stats are automatically refreshed every 30 seconds — no manual page reload needed.

See [`dashboard.md`](dashboard.md) for full details on each panel.

---
Expand Down Expand Up @@ -132,7 +140,7 @@ The UI respects the system dark/light mode preference automatically. No toggle i

## Theme and design

- Font: system monospace stack
- Font: Inter Variable (Google Fonts) with a full system fallback stack
- Framework: React 18 + Tailwind CSS
- Icons: Lucide React
- Build tool: Vite with HMR in development
27 changes: 22 additions & 5 deletions frontend/src/pages/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ interface Props {
export default function ChatPage({ onSessionsChange, onNew }: Props) {
const { sessionId } = useParams<{ sessionId: string }>();
const [searchParams, setSearchParams] = useSearchParams();
const [messages, setMessages] = useState<Message[]>([]);
const [busy, setBusy] = useState(false);
const abortRef = useRef<AbortController | null>(null);
const bottomRef = useRef<HTMLDivElement>(null);
const autoPromptFired = useRef(false);
const [messages, setMessages] = useState<Message[]>([]);
const [busy, setBusy] = useState(false);
const [followUpQuestions, setFollowUpQuestions] = useState<string[]>([]);
const abortRef = useRef<AbortController | null>(null);
const bottomRef = useRef<HTMLDivElement>(null);
const autoPromptFired = useRef(false);

useEffect(() => {
if (sessionId) localStorage.setItem('devops-session-id', sessionId);
Expand Down Expand Up @@ -74,6 +75,7 @@ export default function ChatPage({ onSessionsChange, onNew }: Props) {

const send = async (text: string) => {
if (!sessionId || busy || !text.trim()) return;
setFollowUpQuestions([]);

const agentId = crypto.randomUUID();
setMessages(prev => [
Expand Down Expand Up @@ -135,6 +137,8 @@ export default function ChatPage({ onSessionsChange, onNew }: Props) {
}));
} else if (payload.type === 'done') {
patch(agentId, { streaming: false, usage: payload.usage as AgentMsg['usage'] });
const fq = payload.follow_up_questions;
if (Array.isArray(fq) && fq.length > 0) setFollowUpQuestions(fq as string[]);
} else if (payload.type === 'error') {
const msg = payload.message as string;
patch(agentId, { streaming: false, error: msg });
Expand Down Expand Up @@ -208,6 +212,19 @@ export default function ChatPage({ onSessionsChange, onNew }: Props) {
<div ref={bottomRef} />
</div>

{followUpQuestions.length > 0 && !busy && (
<div className="px-6 pb-2 flex flex-wrap gap-2">
{followUpQuestions.map((q, i) => (
<button
key={i}
onClick={() => send(q)}
className="text-[13px] px-3 py-1.5 rounded-full border border-indigo-200 dark:border-[#3730A3] bg-indigo-50 dark:bg-[#1E1B4B] text-indigo-600 dark:text-[#818CF8] hover:bg-indigo-100 dark:hover:bg-[#2D2B5C] transition-colors"
>
{q}
</button>
))}
</div>
)}
<InputArea busy={busy} onSend={send} onStop={stop} />
</div>
);
Expand Down
15 changes: 10 additions & 5 deletions frontend/src/pages/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,16 @@ export default function DashboardPage() {
const [error, setError] = useState<string | null>(null);

useEffect(() => {
fetch('/stats')
.then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); })
.then(setStats)
.catch(e => setError(e.message))
.finally(() => setLoading(false));
const load = () =>
fetch('/stats')
.then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); })
.then(setStats)
.catch(e => setError(e.message))
.finally(() => setLoading(false));

load();
const interval = setInterval(load, 30_000);
return () => clearInterval(interval);
}, []);

if (loading) {
Expand Down
10 changes: 9 additions & 1 deletion src/api/routers/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,15 @@ def _flush_text_buf():
await save_turn(session_id, user_message, response_text, tool_calls_log, usage)
await notify_slack(session_id, tool_calls_log)

yield f"data: {json.dumps({'type': 'done', 'usage': usage})}\n\n"
follow_up_questions: list[str] = []
for tc in tool_calls_log:
if tc["tool"] == "submit_investigation":
fq = tc["args"].get("follow_up_questions")
if isinstance(fq, list):
follow_up_questions = [q for q in fq if isinstance(q, str)]
break

yield f"data: {json.dumps({'type': 'done', 'usage': usage, 'follow_up_questions': follow_up_questions})}\n\n"


@router.post("/chat")
Expand Down
8 changes: 7 additions & 1 deletion src/tools/final_answer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ def submit_investigation(
confidence: VALID_CONFIDENCE,
services_affected: list[str],
recommended_follow_up: str,
follow_up_questions: list[str],
) -> str:
"""Submit the final structured investigation result. Call this exactly once when you have
gathered sufficient evidence and reached a conclusion. Do not output a JSON block in
free text — call this tool instead."""
free text — call this tool instead.

follow_up_questions: 3 short drill-down questions the user might want to ask next,
e.g. ["What caused the spike at 14:32?", "Are retries configured on the Lambda?",
"Has this happened before this week?"].
"""
return "Investigation result recorded."


Expand Down
Loading