feat(drawer): artifacts section — files, code blocks, deploy URL

This commit is contained in:
2026-04-10 20:35:12 +09:00
parent fb8f2f3b87
commit 548279c888
2 changed files with 113 additions and 1 deletions

View File

@@ -1 +1 @@
1775812935
1775818515

View File

@@ -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<string, unknown>;
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) {
</Section>
)}
{(artifacts.mainFile || artifacts.codeFiles.length > 0 || artifacts.deployUrl) && (
<Section>
<SectionLabel></SectionLabel>
<FileList>
{artifacts.mainFile && (
<FileRow>
<FileBadge $kind="md">log</FileBadge>
<FilePath>{artifacts.mainFile}</FilePath>
</FileRow>
)}
{artifacts.codeFiles.map((f) => (
<FileRow key={f.path}>
<FileBadge $kind={f.lang || 'code'}>{f.lang || 'code'}</FileBadge>
<FilePath>files/{f.path}</FilePath>
</FileRow>
))}
{artifacts.deployUrl && (
<FileRow>
<FileBadge $kind="url">url</FileBadge>
<FilePath>
<a href={artifacts.deployUrl} target="_blank" rel="noopener noreferrer" style={{ color: '#5fafff' }}>
{artifacts.deployUrl}
</a>
</FilePath>
</FileRow>
)}
</FileList>
</Section>
)}
{llmResult && llmResult.text && (
<Section>
<SectionLabel>LLM </SectionLabel>