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" ;
54import { Button } from "@/components/ui/button" ;
65import { Badge } from "@/components/ui/badge" ;
7- import { ExternalLink , Edit3 , Copy , Trash2 } from "lucide-react" ;
6+ import { Copy , Expand , X } from "lucide-react" ;
87import { api } from "@/trpc/react" ;
98import { 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
1115interface 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