FileList.vue 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. <script setup lang="ts">
  2. // 会话或工作组的文件列表:点击预览或分享
  3. import { onMounted, ref, watch } from 'vue'
  4. import type { FileDto } from '@agent-server/shared'
  5. import { fileIcon, formatSize, type FileClient } from '@agent-server/chat-ui'
  6. import { openFile } from '../lib/files'
  7. import { relativeTime } from '../lib/ui'
  8. const props = defineProps<{ client: FileClient; load: () => Promise<FileDto[]>; refreshKey?: unknown }>()
  9. const files = ref<FileDto[] | null>(null)
  10. const error = ref<string | null>(null)
  11. async function refresh() {
  12. try {
  13. files.value = (await props.load()).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
  14. error.value = null
  15. } catch (err) {
  16. error.value = (err as Error).message
  17. }
  18. }
  19. onMounted(refresh)
  20. watch(() => props.refreshKey, refresh)
  21. </script>
  22. <template>
  23. <div class="m-list">
  24. <div v-if="error" class="px-4 py-3 text-error">{{ error }}</div>
  25. <div v-else-if="!files" class="px-4 py-3 text-fg-muted">加载中…</div>
  26. <div v-else-if="!files.length" class="px-4 py-3 text-fg-muted">还没有文件</div>
  27. <button
  28. v-for="f in files" v-else :key="f.id" type="button" class="m-row"
  29. @click="openFile(client, { fileId: f.id, path: f.path, mime: f.mime, size: f.size })"
  30. >
  31. <span class="codicon text-[18px] text-fg-muted" :class="fileIcon(f.mime, f.path)" />
  32. <span class="flex-1 min-w-0">
  33. <span class="block truncate">{{ f.path }}</span>
  34. <span class="block text-[12px] text-fg-muted">{{ formatSize(f.size) }} · {{ f.origin === 'upload' ? '上传' : '生成' }} · {{ relativeTime(f.updatedAt) }}</span>
  35. </span>
  36. </button>
  37. </div>
  38. </template>