feat: add resources page with file management

- Add /resources page with file listing, filtering, and bulk operations
- Add useResources composable for file state management
- Add sidenav header and nav components for resources section
- Add files list API endpoint (GET /api/files)
- Track file size during upload
- Add resources link to home navigation
This commit is contained in:
Zoe
2026-05-11 17:42:28 -05:00
parent 103dedd9cc
commit a0f2e89154
9 changed files with 533 additions and 12 deletions
+1 -8
View File
@@ -90,6 +90,7 @@ const uploadFile = async (file: File) => {
id,
name: fileName,
mimeType: fileType,
size: file.size,
url: assetUrl,
},
})
@@ -146,14 +147,6 @@ watch(files, async (newFiles, oldFiles) => {
}
if (removedFile.url.startsWith('blob:')) {
// we knpw that if the url starts with blob: it was the first time we uploaded it
// so we can just delete it
if (removedFile.status === 'uploaded') {
await $fetch(`/api/file/${removedFile.id}`, {
method: 'DELETE',
})
}
URL.revokeObjectURL(removedFile.url);
}
}
@@ -0,0 +1,22 @@
<script setup lang="ts">
const { isHovered } = useSidenavContext();
</script>
<template>
<header class="flex items-center rounded-lg overflow-hidden flex-1 min-w-0">
<div class="w-10 md:w-8 flex flex-shrink-0 items-center">
<NuxtLink to="/"
class="flex @hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] rounded-lg decoration-none transition-inherit text-[var(--text-secondary)] p-1.5">
<span class="i-mynaui-home text-5.5 md:text-4.5"></span>
</NuxtLink>
</div>
<div class="flex items-center gap-1 pr-1">
<span class="i-mynaui-chevron-right inline-block text-5 md:text-4"></span>
<span
class="md:text-sm font-medium text-ellipsis overflow-hidden text-[var(--text-secondary)] whitespace-nowrap">
Resources
</span>
</div>
</header>
</template>
+4 -3
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useFloating, offset, flip, shift, autoUpdate, hide } from '@floating-ui/vue';
import type { DialogType } from '~/composables/useDialog';
import { DialogType } from '~/composables/useDialog';
const { openDialog } = useDialog();
const { agents, createAgent, deleteAgent, updateAgent } = await useAgents();
@@ -115,7 +115,8 @@ onMounted(() => {
</span>
</div>
</SidenavItem>
<SidenavItem to="/" name="Home" icon="i-mynaui-home" />
<SidenavItem to="/" name="Home" icon="i-mynaui-home" active />
<SidenavItem to="/resources" name="Resources" icon="i-tabler-books" />
<!-- Header Toggle -->
<button
@@ -134,7 +135,7 @@ onMounted(() => {
class="disabled:cursor-wait flex items-center gap-2 px-1 h-9 shrink-0 rounded-lg md:text-sm text-[var(--text-secondary)] @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50 w-full">
<div class="h-9 w-9 md:h-7 md:w-7 flex items-center justify-center">
<span v-if="creatingAgent" class="i-svg-spinners-ring-resize text-5 md:text-4"></span>
<span v-else class="i-mynaui-plus text-5 md:text-4"></span>
<span v-else class="i-mynaui-plus-solid text-5 md:text-4"></span>
</div>
<span>New Agent</span>
</button>
+33
View File
@@ -0,0 +1,33 @@
<script setup lang="ts">
import type { ResourceCategory } from '~/composables/useResources';
const { category, setCategory, categoryCount } = await useResources();
const categories: { key: ResourceCategory; label: string; icon: string }[] = [
{ key: 'all', label: 'All', icon: 'i-mynaui-folder' },
{ key: 'documents', label: 'Documents', icon: 'i-mynaui-file-text' },
{ key: 'images', label: 'Images', icon: 'i-mynaui-image' },
{ key: 'audio', label: 'Audio', icon: 'i-mynaui-music' },
{ key: 'videos', label: 'Videos', icon: 'i-mynaui-video' },
];
</script>
<template>
<nav class="flex flex-col gap-1 overflow-auto">
<button v-for="cat in categories" :key="cat.key" @click="setCategory(cat.key)"
class="flex items-center justify-between gap-2 px-2 h-9 shrink-0 rounded-lg transition-colors w-full"
:class="category === cat.key
? 'text-[var(--text-primary)] bg-[var(--color-hover)]'
: 'text-[var(--text-secondary)] @hover:bg-[var(--color-hover)]'">
<div class="flex items-center gap-2">
<div class="h-9 w-9 md:h-7 md:w-7 flex items-center justify-center">
<span class="text-5 md:text-4.5" :class="cat.icon"></span>
</div>
<span class="md:text-sm font-medium">{{ cat.label }}</span>
</div>
<span class="text-xs tabular-nums text-[var(--text-tertiary)]">
{{ categoryCount(cat.key) }}
</span>
</button>
</nav>
</template>
+3
View File
@@ -96,6 +96,7 @@ onUnmounted(() => {
const navKind = computed(() => {
if (route.path === '/') return 'home';
if (route.path.startsWith('/agent/')) return 'agent';
if (route.path === '/resources') return 'resources';
return null;
});
</script>
@@ -123,6 +124,7 @@ const navKind = computed(() => {
class="z-25 bg-[var(--bg-base)] relative flex flex-row gap-2 justify-between items-center shrink-0 pb-1.5 h-13 md:h-11 pt-2">
<SidenavHeader v-if="navKind === 'home'" />
<SidenavHeaderResources v-else-if="navKind === 'resources'" />
<SidenavHeaderAgent v-else-if="navKind === 'agent'" />
<div class="flex items-center justify-end text-[var(--text-secondary)] gap-0.5">
@@ -155,6 +157,7 @@ const navKind = computed(() => {
<!-- Main Menu -->
<SidenavNavHome v-if="navKind === 'home'" />
<SidenavNavAgent v-else-if="navKind === 'agent'" />
<SidenavNavResources v-else-if="navKind === 'resources'" />
</div>
<div class="flex justify-between pt-2 z-25 bg-[var(--bg-base)] pb-2">