+
+
+ No files in session directory
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+ {{ fileMeta(item) }}
+
+
+
+
+
+
@@ -249,6 +306,17 @@
}
return out
})
+
+const filesFlat = computed(() => chat.files)
+const fileTree = computed(() => {
+ const list = filesFlat.value
+ return list.map(item => {
+ const parts = item.path.split('/')
+ const name = parts[parts.length - 1]
+ const depth = parts.length - 1
+ return { ...item, name, depth }
+ })
+})
const selectedSourceLanguage = computed(() => selected.value ? sourceLanguage(selected.value) : 'plaintext')
const highlightedSource = computed(() => highlightSource(sourceText.value, selectedSourceLanguage.value))
const isDesktop = computed(() => viewportWidth.value > DRAWER_DESKTOP_BREAKPOINT)
@@ -494,6 +562,63 @@
window.open(l.url, '_blank', 'noopener,noreferrer')
}
}
+
+function fileIcon(item) {
+ if (item.is_dir) return 'ph ph-folder'
+ const ext = item.name.split('.').pop()?.toLowerCase() || ''
+ const map = {
+ js: 'ph ph-file-js',
+ ts: 'ph ph-file-ts',
+ py: 'ph ph-file-py',
+ html: 'ph ph-file-html',
+ css: 'ph ph-file-css',
+ json: 'ph ph-file-json',
+ md: 'ph ph-file-text',
+ txt: 'ph ph-file-text',
+ jpg: 'ph ph-file-image',
+ jpeg: 'ph ph-file-image',
+ png: 'ph ph-file-image',
+ gif: 'ph ph-file-image',
+ svg: 'ph ph-file-image',
+ webp: 'ph ph-file-image',
+ mp4: 'ph ph-file-video',
+ webm: 'ph ph-file-video',
+ mov: 'ph ph-file-video',
+ mp3: 'ph ph-file-audio',
+ wav: 'ph ph-file-audio',
+ ogg: 'ph ph-file-audio',
+ pdf: 'ph ph-file-pdf',
+ zip: 'ph ph-file-zip',
+ tar: 'ph ph-file-zip',
+ gz: 'ph ph-file-zip',
+ rar: 'ph ph-file-zip',
+ }
+ return map[ext] || 'ph ph-file'
+}
+
+function fileMeta(item) {
+ if (item.is_dir) return 'Directory'
+ const size = item.size
+ if (size < 1024) return `${size} B`
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`
+}
+
+function fileIndent(item) {
+ return { paddingLeft: `${12 + item.depth * 16}px` }
+}
+
+function fileDownloadUrl(path) {
+ const sessionId = chat.currentId
+ if (!sessionId) return '#'
+ return `/api/sessions/${sessionId}/files/${encodeURIComponent(path)}?download=1`
+}
+
+function isInlineFile(path) {
+ const ext = path.split('.').pop()?.toLowerCase() || ''
+ const inlineExts = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'html', 'txt', 'md', 'pdf']
+ return inlineExts.includes(ext)
+}