diff --git a/README.md b/README.md index 3454fea..3824b84 100644 --- a/README.md +++ b/README.md @@ -46,22 +46,22 @@ ```mermaid flowchart LR - subgraph Frontend[Frontend (Vercel)] + subgraph Frontend["Frontend (Vercel)"] N[Next.js 14.2.15] React[React 18.3.1] TypeScript[TypeScript 5.5.4] end - subgraph Backend[Backend (Railway)] + subgraph Backend["Backend (Railway)"] FastAPI[FastAPI 0.112.0] PyEnv[Python 3.12] Uvicorn[Uvicorn 0.30.6] - SQLite[SQLite (파일 DB)] + SQLite["SQLite (파일 DB)"] end User[브라우저] --> N --> FastAPI FastAPI --> SQLite - FastAPI --> OpenAI[Embedding/Chat API] + FastAPI --> OpenAI["Embedding/Chat API"] ``` ### 스택 정리 @@ -86,14 +86,14 @@ flowchart TD end subgraph API[API Layer] FAST[FastAPI] - ROUTES[REST endpoints /api/v1/*] + ROUTES["REST endpoints /api/v1/*"] DB[(SQLite)] end UI -->|HTTPS| ROUTES ROUTES --> FAST FAST --> DB - FAST --> OpenAI[(OpenAI API)] + FAST --> OpenAI[("OpenAI API")] ``` ### API 엔드포인트 diff --git a/app/app/globals.css b/app/app/globals.css index 1b662ef..59ef8fa 100644 --- a/app/app/globals.css +++ b/app/app/globals.css @@ -47,6 +47,15 @@ select { border: 1px solid #b9c6db; border-radius: 8px; padding: 0.65rem; + transition: border-color 0.15s, outline-color 0.15s; +} + +input:focus, +textarea:focus, +select:focus { + border-color: #3b82f6; + outline: 2px solid #3b82f6; + outline-offset: -1px; } button { @@ -56,6 +65,25 @@ button { border-radius: 8px; padding: 0.65rem 1rem; cursor: pointer; + transition: background 0.15s; +} + +button:hover { + background: #1e40af; +} + +button:active { + background: #1e3a8a; +} + +button:focus-visible { + outline: 2px solid #3b82f6; + outline-offset: 2px; +} + +button:disabled { + background: #93c5fd; + cursor: not-allowed; } pre { @@ -65,3 +93,11 @@ pre { padding: 0.75rem; border-radius: 10px; } + +.message { + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 8px; + padding: 0.75rem 1rem; + margin-top: 1rem; +} diff --git a/app/app/page.tsx b/app/app/page.tsx index 36cd5af..1c1e5b2 100644 --- a/app/app/page.tsx +++ b/app/app/page.tsx @@ -45,6 +45,7 @@ export default function HomePage() { const [queryResult, setQueryResult] = useState(null); const [actionResult, setActionResult] = useState(""); const [message, setMessage] = useState(""); + const [loading, setLoading] = useState(false); useEffect(() => { void refreshAll(); @@ -67,7 +68,12 @@ export default function HomePage() { async function upload(event: FormEvent) { event.preventDefault(); + if (!sourceText.trim()) { + setMessage("텍스트를 입력하세요"); + return; + } setMessage(""); + setLoading(true); const form = new FormData(); form.set("project_id", projectId); form.set("source_text", sourceText); @@ -85,6 +91,8 @@ export default function HomePage() { await refreshAll(); } catch { setMessage("문서 업로드 실패"); + } finally { + setLoading(false); } } @@ -94,97 +102,126 @@ export default function HomePage() { setMessage("질문을 입력하세요"); return; } + setMessage(""); + setLoading(true); - const response = await fetch(ENDPOINT.queries, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - project_id: projectId, - question, - }), - }); - if (!response.ok) { - setMessage("질의 처리 실패"); - return; + try { + const response = await fetch(ENDPOINT.queries, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + project_id: projectId, + question, + }), + }); + if (!response.ok) { + setMessage("질의 처리 실패"); + return; + } + const payload: QueryResponse = await response.json(); + setQueryResult(payload); + setQuestion(""); + await refreshAll(); + } catch { + setMessage("네트워크 오류가 발생했습니다. 잠시 후 다시 시도하세요."); + } finally { + setLoading(false); } - const payload: QueryResponse = await response.json(); - setQueryResult(payload); - setQuestion(""); - await refreshAll(); } async function runAction() { - const response = await fetch(ENDPOINT.actions, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - project_id: projectId, - type: "summary", - payload: { - documents: documents.map((doc) => doc.id), + setLoading(true); + setActionResult(""); + + try { + const response = await fetch(ENDPOINT.actions, { + method: "POST", + headers: { + "Content-Type": "application/json", }, - }), - }); - if (!response.ok) { - setActionResult("액션 실행 실패"); - return; + body: JSON.stringify({ + project_id: projectId, + type: "summary", + payload: { + documents: documents.map((doc) => doc.id), + }, + }), + }); + if (!response.ok) { + setActionResult("액션 실행 실패"); + return; + } + const payload = await response.json(); + setActionResult(payload.result); + } catch { + setActionResult("네트워크 오류가 발생했습니다. 잠시 후 다시 시도하세요."); + } finally { + setLoading(false); } - const payload = await response.json(); - setActionResult(payload.result); } return (
-

Knowledge Copilot

+

Knowledge Copilot

RAG, 액션형 AI, API 로그 추적까지 포함한 포트폴리오 프로젝트

-
-

1) 문서 업로드

-
- -