@@ -48,7 +48,20 @@ import { Disk, ListArtifactsResp, Artifact as FileInfo } from "@/types";
4848import ReactCodeMirror from "@uiw/react-codemirror" ;
4949import { okaidia } from "@uiw/codemirror-theme-okaidia" ;
5050import { json } from "@codemirror/lang-json" ;
51+ import { javascript } from "@codemirror/lang-javascript" ;
52+ import { python } from "@codemirror/lang-python" ;
53+ import { html } from "@codemirror/lang-html" ;
54+ import { css } from "@codemirror/lang-css" ;
55+ import { markdown } from "@codemirror/lang-markdown" ;
56+ import { xml } from "@codemirror/lang-xml" ;
57+ import { sql } from "@codemirror/lang-sql" ;
5158import { EditorView } from "@codemirror/view" ;
59+ import { StreamLanguage } from "@codemirror/language" ;
60+ import { go } from "@codemirror/legacy-modes/mode/go" ;
61+ import { yaml } from "@codemirror/legacy-modes/mode/yaml" ;
62+ import { shell } from "@codemirror/legacy-modes/mode/shell" ;
63+ import { rust } from "@codemirror/legacy-modes/mode/rust" ;
64+ import { ruby } from "@codemirror/legacy-modes/mode/ruby" ;
5265
5366interface TreeNode {
5467 id : string ;
@@ -82,6 +95,79 @@ function truncateMiddle(str: string, maxLength: number = 30): string {
8295 ) ;
8396}
8497
98+ // Get language extension based on file type or filename
99+ function getLanguageExtension ( contentType : string | null , filename ?: string ) {
100+ // First try to determine by content type
101+ if ( contentType ) {
102+ const type = contentType . toLowerCase ( ) ;
103+ if ( type . includes ( "json" ) ) return json ( ) ;
104+ if ( type . includes ( "javascript" ) || type . includes ( "js" ) ) return javascript ( ) ;
105+ if ( type . includes ( "typescript" ) || type . includes ( "ts" ) )
106+ return javascript ( { typescript : true } ) ;
107+ if ( type . includes ( "python" ) || type . includes ( "py" ) ) return python ( ) ;
108+ if ( type . includes ( "html" ) ) return html ( ) ;
109+ if ( type . includes ( "css" ) ) return css ( ) ;
110+ if ( type . includes ( "markdown" ) || type . includes ( "md" ) ) return markdown ( ) ;
111+ if ( type . includes ( "xml" ) ) return xml ( ) ;
112+ if ( type . includes ( "sql" ) ) return sql ( ) ;
113+ if ( type . includes ( "yaml" ) || type . includes ( "yml" ) )
114+ return StreamLanguage . define ( yaml ) ;
115+ if ( type . includes ( "shell" ) || type . includes ( "bash" ) || type . includes ( "sh" ) )
116+ return StreamLanguage . define ( shell ) ;
117+ if ( type . includes ( "go" ) ) return StreamLanguage . define ( go ) ;
118+ if ( type . includes ( "rust" ) || type . includes ( "rs" ) )
119+ return StreamLanguage . define ( rust ) ;
120+ if ( type . includes ( "ruby" ) || type . includes ( "rb" ) )
121+ return StreamLanguage . define ( ruby ) ;
122+ }
123+
124+ // Then try to determine by filename extension
125+ if ( filename ) {
126+ const ext = filename . split ( "." ) . pop ( ) ?. toLowerCase ( ) ;
127+ switch ( ext ) {
128+ case "json" :
129+ return json ( ) ;
130+ case "js" :
131+ case "jsx" :
132+ case "mjs" :
133+ return javascript ( { jsx : true } ) ;
134+ case "ts" :
135+ case "tsx" :
136+ return javascript ( { typescript : true , jsx : ext === "tsx" } ) ;
137+ case "py" :
138+ return python ( ) ;
139+ case "html" :
140+ case "htm" :
141+ return html ( ) ;
142+ case "css" :
143+ return css ( ) ;
144+ case "md" :
145+ case "markdown" :
146+ return markdown ( ) ;
147+ case "xml" :
148+ return xml ( ) ;
149+ case "sql" :
150+ return sql ( ) ;
151+ case "yaml" :
152+ case "yml" :
153+ return StreamLanguage . define ( yaml ) ;
154+ case "sh" :
155+ case "bash" :
156+ case "zsh" :
157+ return StreamLanguage . define ( shell ) ;
158+ case "go" :
159+ return StreamLanguage . define ( go ) ;
160+ case "rs" :
161+ return StreamLanguage . define ( rust ) ;
162+ case "rb" :
163+ return StreamLanguage . define ( ruby ) ;
164+ }
165+ }
166+
167+ // Return empty array as fallback
168+ return [ ] ;
169+ }
170+
85171function Node ( {
86172 node,
87173 style,
@@ -233,26 +319,27 @@ export default function DiskPage() {
233319
234320 // Disk related states
235321 const [ disks , setDisks ] = useState < Disk [ ] > ( [ ] ) ;
236- const [ selectedDisk , setSelectedDisk ] = useState < Disk | null > (
237- null
238- ) ;
322+ const [ selectedDisk , setSelectedDisk ] = useState < Disk | null > ( null ) ;
239323 const [ isLoadingDisks , setIsLoadingDisks ] = useState ( true ) ;
240324
241325 // File preview states
242326 const [ imageUrl , setImageUrl ] = useState < string | null > ( null ) ;
327+ const [ fileContent , setFileContent ] = useState < string | null > ( null ) ;
328+ const [ fileContentType , setFileContentType ] = useState < string | null > ( null ) ;
243329 const [ isLoadingPreview , setIsLoadingPreview ] = useState ( false ) ;
244330 const [ isLoadingDownload , setIsLoadingDownload ] = useState ( false ) ;
245331
246332 // Delete confirmation dialog states
247333 const [ deleteDialogOpen , setDeleteDialogOpen ] = useState ( false ) ;
248- const [ diskToDelete , setDiskToDelete ] = useState < Disk | null > (
249- null
250- ) ;
334+ const [ diskToDelete , setDiskToDelete ] = useState < Disk | null > ( null ) ;
251335 const [ isDeleting , setIsDeleting ] = useState ( false ) ;
252336
253337 // Delete artifact confirmation dialog states
254- const [ deleteArtifactDialogOpen , setDeleteArtifactDialogOpen ] = useState ( false ) ;
255- const [ artifactToDelete , setArtifactToDelete ] = useState < TreeNode | null > ( null ) ;
338+ const [ deleteArtifactDialogOpen , setDeleteArtifactDialogOpen ] =
339+ useState ( false ) ;
340+ const [ artifactToDelete , setArtifactToDelete ] = useState < TreeNode | null > (
341+ null
342+ ) ;
256343 const [ isDeletingArtifact , setIsDeletingArtifact ] = useState ( false ) ;
257344
258345 // Upload artifact states
@@ -632,7 +719,8 @@ export default function DiskPage() {
632719
633720 // Handle delete file confirmation
634721 const handleDeleteArtifact = async ( ) => {
635- if ( ! artifactToDelete || ! selectedDisk || ! artifactToDelete . fileInfo ) return ;
722+ if ( ! artifactToDelete || ! selectedDisk || ! artifactToDelete . fileInfo )
723+ return ;
636724
637725 try {
638726 setIsDeletingArtifact ( true ) ;
@@ -724,6 +812,8 @@ export default function DiskPage() {
724812 // Reset preview states when file selection changes
725813 useEffect ( ( ) => {
726814 setImageUrl ( null ) ;
815+ setFileContent ( null ) ;
816+ setFileContentType ( null ) ;
727817 } , [ selectedFile ] ) ;
728818
729819 // Handle preview button click
@@ -734,13 +824,22 @@ export default function DiskPage() {
734824 setIsLoadingPreview ( true ) ;
735825 const res = await getArtifact (
736826 selectedDisk . id ,
737- `${ selectedFile . path } ${ selectedFile . fileInfo . filename } `
827+ `${ selectedFile . path } ${ selectedFile . fileInfo . filename } ` ,
828+ true // with_content
738829 ) ;
739- if ( res . code !== 0 ) {
830+ if ( res . code !== 0 || ! res . data ) {
740831 console . error ( res . message ) ;
741832 return ;
742833 }
743- setImageUrl ( res . data ?. public_url || null ) ;
834+
835+ // Set image URL for image files
836+ setImageUrl ( res . data . public_url || null ) ;
837+
838+ // Set file content for text-based files
839+ if ( res . data . content ) {
840+ setFileContent ( res . data . content . raw ) ;
841+ setFileContentType ( res . data . content . type ) ;
842+ }
744843 } catch ( error ) {
745844 console . error ( "Failed to load preview:" , error ) ;
746845 } finally {
@@ -756,7 +855,8 @@ export default function DiskPage() {
756855 setIsLoadingDownload ( true ) ;
757856 const res = await getArtifact (
758857 selectedDisk . id ,
759- `${ selectedFile . path } ${ selectedFile . fileInfo . filename } `
858+ `${ selectedFile . path } ${ selectedFile . fileInfo . filename } ` ,
859+ false // with_content = false for download
760860 ) ;
761861 if ( res . code !== 0 ) {
762862 console . error ( res . message ) ;
@@ -1102,50 +1202,75 @@ export default function DiskPage() {
11021202 </ div >
11031203 </ div >
11041204
1105- { /* Preview section for images */ }
1106- { selectedFile . fileInfo . meta . __artifact_info__ . mime . startsWith (
1107- "image/"
1108- ) && (
1109- < div className = "border-t pt-6" >
1110- < p className = "text-sm font-medium text-muted-foreground mb-3" >
1111- { t ( "preview" ) }
1112- </ p >
1113- { isLoadingPreview ? (
1114- < div className = "flex items-center justify-center h-64 bg-muted rounded-md" >
1115- < div className = "flex flex-col items-center gap-2" >
1116- < Loader2 className = "h-8 w-8 animate-spin text-muted-foreground" />
1117- < p className = "text-sm text-muted-foreground" >
1118- { t ( "loadingImage" ) }
1119- </ p >
1120- </ div >
1205+ { /* Preview section */ }
1206+ < div className = "border-t pt-6" >
1207+ < p className = "text-sm font-medium text-muted-foreground mb-3" >
1208+ { t ( "preview" ) }
1209+ </ p >
1210+ { isLoadingPreview ? (
1211+ < div className = "flex items-center justify-center h-64 bg-muted rounded-md" >
1212+ < div className = "flex flex-col items-center gap-2" >
1213+ < Loader2 className = "h-8 w-8 animate-spin text-muted-foreground" />
1214+ < p className = "text-sm text-muted-foreground" >
1215+ { t ( "loadingPreview" ) }
1216+ </ p >
11211217 </ div >
1122- ) : imageUrl ? (
1123- < div className = "rounded-md border bg-muted p-4" >
1124- < div className = "relative w-full min-h-[200px]" >
1125- < Image
1126- src = { imageUrl }
1127- alt = { selectedFile . fileInfo . filename }
1128- width = { 800 }
1129- height = { 600 }
1130- className = "max-w-full h-auto rounded-md shadow-sm"
1131- style = { { objectFit : "contain" } }
1132- unoptimized
1218+ </ div >
1219+ ) : imageUrl || fileContent ? (
1220+ < >
1221+ { /* Image preview */ }
1222+ { imageUrl &&
1223+ selectedFile . fileInfo . meta . __artifact_info__ . mime . startsWith (
1224+ "image/"
1225+ ) && (
1226+ < div className = "rounded-md border bg-muted p-4 mb-4" >
1227+ < div className = "relative w-full min-h-[200px]" >
1228+ < Image
1229+ src = { imageUrl }
1230+ alt = { selectedFile . fileInfo . filename }
1231+ width = { 800 }
1232+ height = { 600 }
1233+ className = "max-w-full h-auto rounded-md shadow-sm"
1234+ style = { { objectFit : "contain" } }
1235+ unoptimized
1236+ />
1237+ </ div >
1238+ </ div >
1239+ ) }
1240+
1241+ { /* Text content preview */ }
1242+ { fileContent && (
1243+ < div >
1244+ < ReactCodeMirror
1245+ value = { fileContent }
1246+ height = "400px"
1247+ theme = { resolvedTheme === "dark" ? okaidia : "light" }
1248+ extensions = { [
1249+ getLanguageExtension (
1250+ fileContentType ,
1251+ selectedFile . fileInfo ?. filename
1252+ ) ,
1253+ EditorView . lineWrapping ,
1254+ ] . flat ( ) }
1255+ editable = { false }
1256+ readOnly
1257+ className = "border rounded-md overflow-hidden"
11331258 />
11341259 </ div >
1135- </ div >
1136- ) : (
1137- < div className = "flex items-center justify-center h-64 bg-muted rounded-md" >
1138- < Button
1139- variant = "outline"
1140- onClick = { handlePreviewClick }
1141- disabled = { isLoadingPreview }
1142- >
1143- { t ( "loadPreview" ) }
1144- </ Button >
1145- </ div >
1146- ) }
1147- </ div >
1148- ) }
1260+ ) }
1261+ </ >
1262+ ) : (
1263+ < div className = "flex items-center justify-center h-64 bg-muted rounded-md" >
1264+ < Button
1265+ variant = "outline"
1266+ onClick = { handlePreviewClick }
1267+ disabled = { isLoadingPreview }
1268+ >
1269+ { t ( "loadPreview" ) }
1270+ </ Button >
1271+ </ div >
1272+ ) }
1273+ </ div >
11491274 </ div >
11501275 ) : (
11511276 < p className = "text-sm text-muted-foreground" >
@@ -1285,7 +1410,9 @@ export default function DiskPage() {
12851410 className = "border rounded-md overflow-hidden"
12861411 />
12871412 { uploadMetaError && (
1288- < p className = "mt-2 text-sm text-destructive" > { uploadMetaError } </ p >
1413+ < p className = "mt-2 text-sm text-destructive" >
1414+ { uploadMetaError }
1415+ </ p >
12891416 ) }
12901417 < p className = "text-xs text-muted-foreground mt-1" >
12911418 { t ( "metaJsonHelp" ) }
@@ -1302,7 +1429,9 @@ export default function DiskPage() {
13021429 </ AlertDialogCancel >
13031430 < AlertDialogAction
13041431 onClick = { handleUploadConfirm }
1305- disabled = { isUploading || ! selectedUploadFile || ! isUploadMetaValid }
1432+ disabled = {
1433+ isUploading || ! selectedUploadFile || ! isUploadMetaValid
1434+ }
13061435 >
13071436 { isUploading ? (
13081437 < >
0 commit comments