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:
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user