Skip to content

Commit 4a8a07e

Browse files
committed
implementing the artifacts in the chat
1 parent fd116a9 commit 4a8a07e

File tree

5 files changed

+123
-440
lines changed

5 files changed

+123
-440
lines changed
Lines changed: 89 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
"use client";
22

3-
import { useState } from "react";
4-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
3+
import { useState, useEffect } from "react";
54
import { Button } from "@/components/ui/button";
65
import { Badge } from "@/components/ui/badge";
7-
import { ExternalLink, Edit3, Copy, Trash2 } from "lucide-react";
6+
import { Copy, Expand, X } from "lucide-react";
87
import { api } from "@/trpc/react";
98
import { toast } from "sonner";
9+
import { CodeBlock } from "@/components/ui/code-block";
10+
import { Markdown } from "@/components/ui/markdown";
11+
import { cn } from "@/lib/utils";
12+
import type { BundledLanguage } from "@/components/ui/code/shiki.bundle";
13+
import { LLMMarkdown } from "./utils/llm-markdown";
1014

1115
interface Props {
1216
documentId: string;
@@ -22,10 +26,11 @@ export const ArtifactPreview: React.FC<Props> = ({
2226
description,
2327
}) => {
2428
const [isExpanded, setIsExpanded] = useState(false);
25-
29+
const [isFullscreen, setIsFullscreen] = useState(false);
30+
2631
const { data: document, isLoading } = api.documents.get.useQuery(
2732
{ id: documentId },
28-
{ enabled: !!documentId }
33+
{ enabled: !!documentId },
2934
);
3035

3136
const deleteDocument = api.documents.delete.useMutation({
@@ -44,130 +49,101 @@ export const ArtifactPreview: React.FC<Props> = ({
4449
}
4550
};
4651

47-
const handleDelete = () => {
48-
if (confirm(`Are you sure you want to delete "${title}"?`)) {
49-
deleteDocument.mutate({ id: documentId });
50-
}
51-
};
52+
if (isLoading) {
53+
return (
54+
<div className="w-full rounded-lg border bg-gray-50 p-4">
55+
<div className="animate-pulse">
56+
<div className="mb-2 h-4 w-1/3 rounded bg-gray-200"></div>
57+
<div className="h-20 rounded bg-gray-200"></div>
58+
</div>
59+
</div>
60+
);
61+
}
5262

53-
const getKindColor = (kind: string) => {
54-
switch (kind) {
55-
case "text":
56-
return "bg-blue-100 text-blue-800";
57-
case "code":
58-
return "bg-green-100 text-green-800";
59-
case "custom":
60-
return "bg-purple-100 text-purple-800";
61-
default:
62-
return "bg-gray-100 text-gray-800";
63-
}
64-
};
63+
const renderContent = () => {
64+
if (!document?.content) return null;
6565

66-
const getKindIcon = (kind: string) => {
67-
switch (kind) {
68-
case "text":
69-
return "📝";
70-
case "code":
71-
return "💻";
72-
case "custom":
73-
return "✨";
74-
default:
75-
return "📄";
76-
}
77-
};
66+
const content = document.content;
7867

79-
if (isLoading) {
8068
return (
81-
<Card className="w-full max-w-2xl">
82-
<CardContent className="p-4">
83-
<div className="animate-pulse">
84-
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
85-
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
86-
</div>
87-
</CardContent>
88-
</Card>
69+
<div className="h-full w-full overflow-auto">
70+
<LLMMarkdown llmOutput={content} isStreamFinished={true} />
71+
</div>
8972
);
90-
}
73+
};
9174

9275
return (
93-
<Card className="w-full max-w-2xl border-l-4 border-l-blue-500">
94-
<CardHeader className="pb-3">
95-
<div className="flex items-start justify-between">
96-
<div className="flex items-center space-x-2">
97-
<span className="text-lg">{getKindIcon(kind)}</span>
98-
<div>
99-
<CardTitle className="text-lg">{title}</CardTitle>
100-
{description && (
101-
<CardDescription className="mt-1">{description}</CardDescription>
102-
)}
103-
</div>
104-
</div>
105-
<div className="flex items-center space-x-2">
106-
<Badge className={getKindColor(kind)}>{kind}</Badge>
107-
<div className="flex items-center space-x-1">
108-
<Button
109-
variant="ghost"
110-
size="sm"
111-
onClick={handleCopy}
112-
className="h-8 w-8 p-0"
113-
>
114-
<Copy className="w-3 h-3" />
115-
</Button>
116-
<Button
117-
variant="ghost"
118-
size="sm"
119-
onClick={() => setIsExpanded(!isExpanded)}
120-
className="h-8 w-8 p-0"
121-
>
122-
<ExternalLink className="w-3 h-3" />
123-
</Button>
124-
<Button
125-
variant="ghost"
126-
size="sm"
127-
onClick={handleDelete}
128-
className="h-8 w-8 p-0 text-red-600 hover:text-red-700"
129-
>
130-
<Trash2 className="w-3 h-3" />
131-
</Button>
132-
</div>
133-
</div>
76+
<div className="w-full space-y-2">
77+
<div className="flex items-center justify-between rounded-lg border bg-gray-50 p-3">
78+
<div className="flex items-center space-x-3">
79+
<div className="text-sm font-medium text-gray-900">{title}</div>
80+
<Badge variant="secondary" className="text-xs">
81+
{kind}
82+
</Badge>
13483
</div>
135-
</CardHeader>
13684

137-
{document?.content && (
138-
<CardContent className="pt-0">
139-
<div className="border rounded-lg bg-gray-50">
140-
{isExpanded ? (
141-
<div className="p-4 max-h-96 overflow-auto">
142-
{kind === "code" ? (
143-
<pre className="text-sm font-mono whitespace-pre-wrap">
144-
<code>{document.content}</code>
145-
</pre>
146-
) : (
147-
<div className="text-sm whitespace-pre-wrap">
148-
{document.content}
149-
</div>
150-
)}
151-
</div>
152-
) : (
153-
<div className="p-4">
154-
<div className="text-sm text-gray-600 line-clamp-3">
155-
{document.content.substring(0, 200)}
156-
{document.content.length > 200 && "..."}
85+
<div className="flex items-center space-x-1">
86+
<Button
87+
variant="ghost"
88+
size="sm"
89+
onClick={() => setIsFullscreen(true)}
90+
className="h-8 w-8 p-0"
91+
title="View artifact"
92+
>
93+
<Expand className="h-4 w-4" />
94+
</Button>
95+
</div>
96+
</div>
97+
98+
{isFullscreen && (
99+
<div
100+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
101+
onClick={(e) => {
102+
if (e.target === e.currentTarget) {
103+
setIsFullscreen(false);
104+
}
105+
}}
106+
>
107+
<div
108+
className="flex h-full max-h-[90vh] w-full max-w-6xl flex-col rounded-lg bg-white shadow-xl"
109+
onClick={(e) => e.stopPropagation()}
110+
>
111+
<div className="flex flex-shrink-0 items-center justify-between border-b p-4">
112+
<div className="flex items-center space-x-3">
113+
<div className="text-lg font-semibold text-gray-900">
114+
{title}
157115
</div>
116+
<Badge variant="secondary" className="text-xs">
117+
{kind}
118+
</Badge>
119+
</div>
120+
<div className="flex items-center space-x-2">
158121
<Button
159-
variant="link"
122+
variant="ghost"
160123
size="sm"
161-
onClick={() => setIsExpanded(true)}
162-
className="p-0 mt-2 h-auto text-blue-600"
124+
onClick={handleCopy}
125+
className="h-8 w-8 p-0"
126+
title="Copy content"
163127
>
164-
Show full content
128+
<Copy className="h-4 w-4" />
129+
</Button>
130+
<Button
131+
variant="ghost"
132+
size="sm"
133+
onClick={() => setIsFullscreen(false)}
134+
className="h-8 w-8 p-0"
135+
title="Close"
136+
>
137+
<X className="h-4 w-4" />
165138
</Button>
166139
</div>
167-
)}
140+
</div>
141+
<div className="flex-1 overflow-hidden p-4">
142+
<div className="h-full overflow-y-auto">{renderContent()}</div>
143+
</div>
168144
</div>
169-
</CardContent>
145+
</div>
170146
)}
171-
</Card>
147+
</div>
172148
);
173149
};

0 commit comments

Comments
 (0)