feat(my-deepagent): v0.3 PR #8 — conversation-centric Web GUI (/conversation.html)

Workflow run 페이지를 archive 로 격하시키고, 사용자가 처음 보는 화면을
chat-style 대화 thread 로 전환.  Claude Code 의 Web GUI 와 동일한 UX.

핵심 동작:
- 새 페이지 `/conversation.html` 에서 세션을 picker 로 고르거나 "새 대화"
  버튼으로 만들고 메시지 입력.  Cmd/Ctrl+Enter 로 전송.
- POST /api/sessions/{id}/messages 가 user MessageRow 를 영속한 즉시 200 응답
  후 `asyncio.create_task(invoke_session_agent(...))` 로 백그라운드 invoke 발사.
- 백그라운드 task 는 lifespan 에서 1회 열어둔 LangGraph saver 를 재사용하고
  agent.ainvoke → assistant MessageRow 영속 → 자동 compaction 까지 처리.
- 기존 SSE 스트림 (`/api/sessions/{id}/stream`) 이 새 메시지를 push,
  프론트엔드의 `EventSource` 가 받아 thread 에 렌더.

신규 / 수정 파일:
- `static/conversation.html` (신규): chat UI 마크업.  data-page="conversation".
- `static/app.js`: 새 페이지 핸들러 `bootstrapConversationPage` +
  세션 picker + 메시지 thread 렌더 + SSE 구독 + Cmd/Ctrl+Enter 단축키.
  XSS 정책 동일: 모든 사용자 콘텐츠는 `textContent` 만 사용.
- `static/style.css`: `.messages-thread`, `.msg-bubble`, `.conv-topbar`,
  `.conv-input-bar` 등 chat UI 스타일.
- `api/app.py`: lifespan 에서 LangGraph saver 를 1회 열어 `app.state.saver`
  에 보관 (Postgres 일 때만).
- `api/agent_runner.py` (신규): `invoke_session_agent(...)` — REPL 의
  `InteractiveSession + _invoke_and_stream` 와 동일한 stack 을 HTTP background
  context 용으로 재구성.  실패는 로깅 + return.
- `api/routes/sessions.py`: POST /messages 가 background task 발사 + ref 를
  `app.state.pending_invocations` set 에 보관 (RUF006 / GC drop 방지).

테스트 (`tests/integration/test_conversation_gui.py`, 4 케이스):
- GET /conversation.html → 200 + 필수 마크업
- POST /messages → 200 + user row 영속 + 스텁 runner 호출 확인
- 백그라운드 task ref 가 `pending_invocations` 에 잡혀있고 완료 후 자동 discard
- 스텁 runner 가 assistant row 영속 → user + assistant 시퀀스 검증

게이트:
- ruff check / format --check / mypy: PASS
- pytest -q --ignore=tests/integration/test_e2e_workflow.py
  --ignore=tests/integration/test_openrouter_smoke.py: 675 passed (4 신규 포함)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chungyeong
2026-05-17 21:03:09 +09:00
parent 61b34af0e4
commit e326c07dcb
8 changed files with 1067 additions and 1 deletions

View File

@@ -778,3 +778,187 @@ select {
.event-line { grid-template-columns: 1fr; gap: 2px; }
.chips { grid-template-columns: 1fr; gap: 6px; }
}
/* =================================================================
v0.3 PR #8 — Conversation page
================================================================= */
.conversation-main {
display: flex;
flex-direction: column;
min-height: calc(100vh - 80px);
padding-bottom: 0;
}
.conv-topbar {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.conv-label {
font-size: 13px;
color: var(--text-muted);
font-weight: 600;
}
.conv-picker {
flex: 1;
min-width: 240px;
padding: 6px 10px;
font-family: var(--font-mono);
font-size: 13px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
}
.conv-action-btn {
padding: 6px 14px;
font-size: 13px;
background: var(--accent);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.conv-action-btn:hover { filter: brightness(1.08); }
.conv-session-state {
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
text-transform: lowercase;
letter-spacing: 0.04em;
}
.conv-session-state.state-active {
background: rgba(34,197,94,0.12);
color: rgb(22,163,74);
}
.conv-session-state.state-ended {
background: rgba(100,116,139,0.12);
color: rgb(71,85,105);
}
.messages-thread {
flex: 1;
overflow-y: auto;
padding: 16px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
margin-bottom: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.conv-empty {
color: var(--text-muted);
text-align: center;
padding: 40px 16px;
font-size: 13px;
}
.msg-bubble {
max-width: 80%;
padding: 10px 14px;
border-radius: 12px;
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
}
.msg-bubble.role-user {
align-self: flex-end;
background: var(--accent);
color: white;
}
.msg-bubble.role-assistant {
align-self: flex-start;
background: var(--bg-card);
border: 1px solid var(--border);
}
.msg-bubble.role-system {
align-self: center;
max-width: 90%;
font-style: italic;
font-size: 12.5px;
background: rgba(245,158,11,0.08);
border: 1px dashed rgba(245,158,11,0.4);
color: rgb(120,53,15);
}
.msg-bubble.pending {
opacity: 0.6;
font-size: 20px;
padding: 6px 14px;
}
.msg-meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
opacity: 0.6;
margin-bottom: 4px;
}
.msg-role {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.conv-input-bar {
display: flex;
gap: 8px;
padding: 12px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
}
.conv-input-bar textarea {
flex: 1;
font-family: var(--font-body);
font-size: 14px;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 6px;
resize: vertical;
min-height: 44px;
}
.conv-input-bar textarea:disabled {
background: var(--bg);
opacity: 0.5;
}
.conv-input-bar button {
padding: 0 18px;
font-size: 13px;
background: var(--accent);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.conv-input-bar button:disabled {
opacity: 0.4;
cursor: not-allowed;
}