From 548279c88869cad30e31ef8fc9f7337ede6bdce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=9E=91=EC=9D=B4?= Date: Fri, 10 Apr 2026 20:35:12 +0900 Subject: [PATCH] =?UTF-8?q?feat(drawer):=20artifacts=20section=20=E2=80=94?= =?UTF-8?q?=20files,=20code=20blocks,=20deploy=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/sessions/.last_inbox_check | 2 +- .../components/rails/SubTaskDetailDrawer.tsx | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/.claude/sessions/.last_inbox_check b/.claude/sessions/.last_inbox_check index 8e41f47..2111a81 100644 --- a/.claude/sessions/.last_inbox_check +++ b/.claude/sessions/.last_inbox_check @@ -1 +1 @@ -1775812935 +1775818515 diff --git a/frontend/components/rails/SubTaskDetailDrawer.tsx b/frontend/components/rails/SubTaskDetailDrawer.tsx index 2603367..58fe566 100644 --- a/frontend/components/rails/SubTaskDetailDrawer.tsx +++ b/frontend/components/rails/SubTaskDetailDrawer.tsx @@ -380,6 +380,67 @@ const EventPayload = styled.pre` opacity: 0.8; `; +const FileList = styled.div` + display: flex; + flex-direction: column; + gap: 6px; +`; + +const FileRow = styled.div` + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 8px; + font-family: var(--font-mono); + font-size: 12px; +`; + +const FileBadge = styled.span<{ $kind: string }>` + padding: 2px 8px; + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + border-radius: 10px; + color: #fff; + background: ${({ $kind }) => { + switch ($kind) { + case 'html': + case 'htm': + return '#f97316'; + case 'js': + case 'javascript': + case 'jsx': + return '#eab308'; + case 'ts': + case 'typescript': + case 'tsx': + return '#3b82f6'; + case 'css': + case 'scss': + return '#ec4899'; + case 'json': + case 'yaml': + return '#8b5cf6'; + case 'md': + case 'markdown': + return '#6b7280'; + case 'url': + return '#22c55e'; + default: + return '#525252'; + } + }}; +`; + +const FilePath = styled.code` + flex: 1; + word-break: break-all; + color: var(--text-primary); +`; + const Loading = styled.div` padding: 60px 20px; text-align: center; @@ -460,6 +521,27 @@ export default function SubTaskDetailDrawer({ subTaskId, onClose }: Props) { [detail], ); + const artifacts = useMemo(() => { + if (!detail) return { mainFile: '', codeFiles: [] as Array<{ path: string; lang: string }>, deployUrl: '' }; + let mainFile = ''; + let codeFiles: Array<{ path: string; lang: string }> = []; + let deployUrl = ''; + for (const ev of detail.events) { + if (ev.eventType === 'completed' && ev.payload && typeof ev.payload === 'object') { + const p = ev.payload as Record; + if (typeof p.file === 'string' && p.file) mainFile = p.file; + if (Array.isArray(p.extractedFiles)) { + codeFiles = (p.extractedFiles as Array<{ path?: string; lang?: string }>) + .filter((f): f is { path: string; lang: string } => !!f?.path) + .map((f) => ({ path: f.path, lang: f.lang ?? '' })); + } + if (typeof p.deployUrl === 'string') deployUrl = p.deployUrl; + if (typeof p.repoUrl === 'string' && !deployUrl) deployUrl = p.repoUrl; + } + } + return { mainFile, codeFiles, deployUrl }; + }, [detail]); + useEffect(() => { setLoading(true); fetch(`${API_URL}/api/rails/sub-tasks/${subTaskId}`, { @@ -565,6 +647,36 @@ export default function SubTaskDetailDrawer({ subTaskId, onClose }: Props) { )} + {(artifacts.mainFile || artifacts.codeFiles.length > 0 || artifacts.deployUrl) && ( +
+ 산출물 + + {artifacts.mainFile && ( + + log + {artifacts.mainFile} + + )} + {artifacts.codeFiles.map((f) => ( + + {f.lang || 'code'} + files/{f.path} + + ))} + {artifacts.deployUrl && ( + + url + + + {artifacts.deployUrl} + + + + )} + +
+ )} + {llmResult && llmResult.text && (
LLM 응답