feat: file upload retry, secure file tokens, UI polish
- Add HMAC-based file token auth for secure AI model file access - Add file upload retry with exponential backoff (max 3 retries) - File endpoint now requires session auth or signed token - Support assistant role messages in chat input - Optimistic UI for attachments on message send - Verify topic ownership before allowing messages - Switch web scraping to Firecrawl API - Agent profile page layout fixes (proper flex overflow) - Add quick switcher (Ctrl+K) to sidenav - Clean up longcat.ts and stale comments
This commit is contained in:
@@ -10,9 +10,14 @@ const props = defineProps<{
|
|||||||
status?: 'uploading' | 'uploaded' | 'error';
|
status?: 'uploading' | 'uploaded' | 'error';
|
||||||
url: string;
|
url: string;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
|
retryCount?: number;
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
retry: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
const isImage = computed(() => props.file.mimeType.startsWith('image/'));
|
const isImage = computed(() => props.file.mimeType.startsWith('image/'));
|
||||||
const isVideo = computed(() => props.file.mimeType.startsWith('video/'));
|
const isVideo = computed(() => props.file.mimeType.startsWith('video/'));
|
||||||
const isAudio = computed(() => props.file.mimeType.startsWith('audio/'));
|
const isAudio = computed(() => props.file.mimeType.startsWith('audio/'));
|
||||||
@@ -71,8 +76,20 @@ function openImageViewer() {
|
|||||||
class="absolute inset-0 bg-black/50 rounded-lg flex items-center justify-center">
|
class="absolute inset-0 bg-black/50 rounded-lg flex items-center justify-center">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="i-svg-spinners-90-ring-with-bg text-6 text-white block mb-1"></span>
|
<span class="i-svg-spinners-90-ring-with-bg text-6 text-white block mb-1"></span>
|
||||||
<span class="text-xs text-white">{{ file.progress || 0 }}%</span>
|
<span class="text-xs text-white">
|
||||||
|
{{ file.retryCount ? `Retry ${file.retryCount}/3` : `${file.progress || 0}%` }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="file.status === 'error'"
|
||||||
|
class="absolute inset-0 bg-black/60 rounded-lg flex flex-col items-center justify-center gap-1">
|
||||||
|
<span class="i-mynaui-danger-triangle text-6 text-red-400"></span>
|
||||||
|
<span class="text-xs text-red-300">Upload failed</span>
|
||||||
|
<button @click.stop="emit('retry')"
|
||||||
|
class="mt-1 flex items-center gap-1 px-2 py-0.5 rounded-md bg-white/10 hover:bg-white/20 text-xs text-white transition-colors">
|
||||||
|
<span class="i-mynaui-refresh text-3"></span>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ const props = defineProps<{
|
|||||||
mimeType: string;
|
mimeType: string;
|
||||||
status: 'uploading' | 'uploaded' | 'error';
|
status: 'uploading' | 'uploaded' | 'error';
|
||||||
url: string;
|
url: string;
|
||||||
|
retryCount?: number;
|
||||||
}
|
}
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
delete: [];
|
delete: [];
|
||||||
|
retry: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
@@ -21,12 +23,16 @@ const handleDelete = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative h-full w-fit flex">
|
<div class="relative h-full w-fit flex flex-shrink-0">
|
||||||
<Display :file="props.file" />
|
<Display :file="props.file" @retry="emit('retry')" />
|
||||||
|
|
||||||
<button @click="handleDelete"
|
<button @click="handleDelete"
|
||||||
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-4 h-4 rounded-full bg-[var(--bg-base)] border border-[var(--color-border)] text-xs text-[#ff3b3b]">
|
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-4 h-4 rounded-full bg-[var(--bg-base)] border border-[var(--color-border)] text-xs text-[#ff3b3b]">
|
||||||
<span class="i-mynaui-x text-3"></span>
|
<span class="i-mynaui-x text-3"></span>
|
||||||
</button>
|
</button>
|
||||||
|
<div v-if="file.status === 'error'"
|
||||||
|
class="absolute bottom-0 right-0 translate-x-1/4 translate-y-1/4 flex items-center justify-center w-4 h-4 rounded-full bg-red-500">
|
||||||
|
<span class="i-mynaui-danger-triangle text-2.5 text-white"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BaseMessage } from '~/composables/useChat';
|
import type { BaseMessage } from '~/composables/useChat';
|
||||||
import { onMounted, ref, watch, onUnmounted, nextTick, type Ref } from 'vue';
|
import { computed, onMounted, ref, watch, onUnmounted, nextTick, type Ref } from 'vue';
|
||||||
import type { ModelWithProvider, ProviderWithModels } from '~/composables/useModels';
|
import type { ModelWithProvider, ProviderWithModels } from '~/composables/useModels';
|
||||||
import type { Agent } from '~/composables/useAgents';
|
import type { Agent } from '~/composables/useAgents';
|
||||||
import type FileSelector from './FileSelector.vue';
|
import type FileSelector from './FileSelector.vue';
|
||||||
@@ -19,6 +19,7 @@ const files = ref<{
|
|||||||
status: 'uploading' | 'uploaded' | 'error';
|
status: 'uploading' | 'uploaded' | 'error';
|
||||||
url: string;
|
url: string;
|
||||||
progress: number;
|
progress: number;
|
||||||
|
retryCount: number;
|
||||||
}[]>([]);
|
}[]>([]);
|
||||||
const inputValue = defineModel<BaseMessage>({ required: false, default: { content: '', fileIds: [] } });
|
const inputValue = defineModel<BaseMessage>({ required: false, default: { content: '', fileIds: [] } });
|
||||||
const textAreaValue = ref('');
|
const textAreaValue = ref('');
|
||||||
@@ -31,12 +32,14 @@ watch(files, (newFiles) => {
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [value: BaseMessage, model: ModelWithProvider | null];
|
submit: [value: BaseMessage, model: ModelWithProvider | null];
|
||||||
|
addMessage: [value: BaseMessage, role: 'user' | 'assistant', model: ModelWithProvider | null];
|
||||||
cancel: [];
|
cancel: [];
|
||||||
resize: [];
|
resize: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
allowManualRole?: boolean;
|
||||||
agent: Readonly<Agent> | null;
|
agent: Readonly<Agent> | null;
|
||||||
providers?: ProviderWithModels[];
|
providers?: ProviderWithModels[];
|
||||||
}>();
|
}>();
|
||||||
@@ -70,6 +73,9 @@ const saveSearchConfig = async () => {
|
|||||||
updateAgent(props.agent.id, { config: newConfig });
|
updateAgent(props.agent.id, { config: newConfig });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isUploading = computed(() => files.value.some((f) => f.status === 'uploading'));
|
||||||
|
const hasFailedUploads = computed(() => files.value.some((f) => f.status === 'error'));
|
||||||
|
|
||||||
const selectedModel = ref<ModelWithProvider | null>(null);
|
const selectedModel = ref<ModelWithProvider | null>(null);
|
||||||
|
|
||||||
const handlePaste = async (event: ClipboardEvent) => {
|
const handlePaste = async (event: ClipboardEvent) => {
|
||||||
@@ -109,11 +115,18 @@ const initializeModel = () => {
|
|||||||
const updateAgentDefaultModel = async (modelId: string) => {
|
const updateAgentDefaultModel = async (modelId: string) => {
|
||||||
if (!props.agent) return;
|
if (!props.agent) return;
|
||||||
try {
|
try {
|
||||||
|
const currentModelId = props.agent.defaultModelId;
|
||||||
await $fetch(`/api/agent/${props.agent.id}`, {
|
await $fetch(`/api/agent/${props.agent.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: {
|
body: {
|
||||||
defaultModelId: modelId,
|
defaultModelId: modelId,
|
||||||
},
|
},
|
||||||
|
onRequest: () => {
|
||||||
|
patchAgentLocally(props.agent!.id, { defaultModelId: modelId });
|
||||||
|
},
|
||||||
|
onResponseError: () => {
|
||||||
|
patchAgentLocally(props.agent!.id, { defaultModelId: currentModelId });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update agent default model:', error);
|
console.error('Failed to update agent default model:', error);
|
||||||
@@ -144,7 +157,35 @@ const handleSubmit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inputValue.value.content.trim() || inputValue.value.fileIds.length > 0) {
|
if (inputValue.value.content.trim() || inputValue.value.fileIds.length > 0) {
|
||||||
emit('submit', structuredClone(toRaw(inputValue.value)), selectedModel.value);
|
const message: BaseMessage = {
|
||||||
|
content: inputValue.value.content,
|
||||||
|
fileIds: inputValue.value.fileIds,
|
||||||
|
files: files.value.map(f => ({
|
||||||
|
id: f.id,
|
||||||
|
name: f.name,
|
||||||
|
mimeType: f.mimeType,
|
||||||
|
url: f.url,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
emit('submit', message, selectedModel.value);
|
||||||
|
files.value = [];
|
||||||
|
textAreaValue.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddMessage = (role: 'user' | 'assistant') => {
|
||||||
|
if (inputValue.value.content.trim() || inputValue.value.fileIds.length > 0) {
|
||||||
|
const message: BaseMessage = {
|
||||||
|
content: inputValue.value.content,
|
||||||
|
fileIds: inputValue.value.fileIds,
|
||||||
|
files: files.value.map(f => ({
|
||||||
|
id: f.id,
|
||||||
|
name: f.name,
|
||||||
|
mimeType: f.mimeType,
|
||||||
|
url: f.url,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
emit('addMessage', message, role, selectedModel.value);
|
||||||
files.value = [];
|
files.value = [];
|
||||||
textAreaValue.value = '';
|
textAreaValue.value = '';
|
||||||
}
|
}
|
||||||
@@ -320,12 +361,13 @@ onUnmounted(() => {
|
|||||||
class="absolute top-0 left-0 pointer-events-none invisible overflow-hidden h-0"></div>
|
class="absolute top-0 left-0 pointer-events-none invisible overflow-hidden h-0"></div>
|
||||||
<div class="relative w-full flex flex-shrink-1 flex-col gap-3 p-2 rounded-2xl border transition-border ease-in-out duration-300 bg-[var(--bg-container)]
|
<div class="relative w-full flex flex-shrink-1 flex-col gap-3 p-2 rounded-2xl border transition-border ease-in-out duration-300 bg-[var(--bg-container)]
|
||||||
border-[var(--color-border)] focus-within:border-[var(--color-border-active)]">
|
border-[var(--color-border)] focus-within:border-[var(--color-border-active)]">
|
||||||
<div v-if="files.length > 0" class="flex-1 flex gap-2 pt-2 px-2 pb-1 overflow-x-auto flex-wrap">
|
<div v-if="files.length > 0"
|
||||||
|
class="flex-shrink-0 flex flex-nowrap gap-2 pt-2 px-2 pb-1 overflow-x-auto overflow-y-hidden [scrollbar-width:thin]">
|
||||||
<!-- TODO: show attachment previews -->
|
<!-- TODO: show attachment previews -->
|
||||||
<AttachmentPreview v-for="file in files" @delete="files = files.filter((f) => f.id !== file.id)"
|
<AttachmentPreview v-for="file in files" @delete="files = files.filter((f) => f.id !== file.id)"
|
||||||
:key="file.id" :file="file" />
|
@retry="fileSelectorRef?.retryFile(file.id)" :key="file.id" :file="file" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0 max-h-full">
|
<div class="min-w-0 max-h-full">
|
||||||
<!-- Grammarly literally breaks everything, go fuck yourself -->
|
<!-- Grammarly literally breaks everything, go fuck yourself -->
|
||||||
<!-- It is absolutely paramount that the closing tag for the textare has ZERO whitespace between the end of the textarea opening tag, otherwise there will be hydration errors -->
|
<!-- It is absolutely paramount that the closing tag for the textare has ZERO whitespace between the end of the textarea opening tag, otherwise there will be hydration errors -->
|
||||||
<textarea data-gramm="false" id="chat" v-model="textAreaValue" ref="inputRef"
|
<textarea data-gramm="false" id="chat" v-model="textAreaValue" ref="inputRef"
|
||||||
@@ -346,10 +388,54 @@ onUnmounted(() => {
|
|||||||
@update:rerank="(v: boolean) => { searchConfig.rerank = v; saveSearchConfig() }" />
|
@update:rerank="(v: boolean) => { searchConfig.rerank = v; saveSearchConfig() }" />
|
||||||
<FileSelector ref="fileSelectorRef" :selected-model="selectedModel" v-model="files" />
|
<FileSelector ref="fileSelectorRef" :selected-model="selectedModel" v-model="files" />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="allowManualRole" class="flex items-center">
|
||||||
<button aria-label="Send message" @click="handleSubmit"
|
<button aria-label="Send message" @click="handleSubmit"
|
||||||
:disabled="(!inputValue.content.trim() && files.length === 0) && !loading" :class="[
|
:disabled="((!inputValue.content.trim() && files.length === 0) && !loading) || isUploading || hasFailedUploads"
|
||||||
|
:class="[
|
||||||
|
'h-8 w-8 rounded-l-lg rounded-r-none transition-all duration-200 flex items-center justify-center disabled:cursor-not-allowed disabled:bg-transparent',
|
||||||
|
(inputValue.content.trim() || files.length > 0) && !loading && !isUploading && !hasFailedUploads
|
||||||
|
? 'bg-[var(--color-accent)] text-[var(--color-accent-text)] @hover:bg-[var(--color-accent-hover)]'
|
||||||
|
: 'text-[var(--text-dim)]',
|
||||||
|
loading && 'bg-[var(--color-hover)] @hover:bg-[var(--color-active)]',
|
||||||
|
]">
|
||||||
|
<span v-if="loading" class="text-6.5 i-mynaui-stop-solid"></span>
|
||||||
|
<span v-else class="text-5 i-mynaui-send-solid"></span>
|
||||||
|
</button>
|
||||||
|
<Dropdown placement="bottom-end">
|
||||||
|
<template #default="{ setRef, isOpen, toggle }">
|
||||||
|
<button :ref="setRef" aria-label="Message options" @click="toggle"
|
||||||
|
:disabled="(!inputValue.content.trim() && files.length === 0) || loading || isUploading || hasFailedUploads"
|
||||||
|
:class="[
|
||||||
|
'h-8 w-6 rounded-r-lg rounded-l-none transition-all duration-200 flex items-center justify-center disabled:cursor-not-allowed border-l border-[var(--text-dim)] disabled:border-transparent',
|
||||||
|
(inputValue.content.trim() || files.length > 0) && !loading && !isUploading && !hasFailedUploads
|
||||||
|
? 'bg-[var(--color-accent)] text-[var(--color-accent-text)] @hover:bg-[var(--color-accent-hover)] disabled:bg-transparent'
|
||||||
|
: 'text-[var(--text-dim)]',
|
||||||
|
loading && 'bg-[var(--color-hover)] @hover:bg-[var(--color-active)]',
|
||||||
|
]">
|
||||||
|
<span class="text-4.5 i-mynaui-chevron-down" :class="{ 'rotate-180': isOpen }"></span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template #dropdown="{ close }">
|
||||||
|
<button
|
||||||
|
class="flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm text-[var(--text-primary)] @hover:bg-[var(--color-hover)] transition-colors whitespace-nowrap"
|
||||||
|
@click="handleAddMessage('user'); close()">
|
||||||
|
<span class="text-4 i-mynaui-user"></span>
|
||||||
|
Add as User
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm text-[var(--text-primary)] @hover:bg-[var(--color-hover)] transition-colors whitespace-nowrap"
|
||||||
|
@click="handleAddMessage('assistant'); close()">
|
||||||
|
<span class="text-4 i-mynaui-sparkles"></span>
|
||||||
|
Add as Assistant
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
<button v-else aria-label="Send message" @click="handleSubmit"
|
||||||
|
:disabled="((!inputValue.content.trim() && files.length === 0) && !loading) || isUploading || hasFailedUploads"
|
||||||
|
:class="[
|
||||||
'h-8 w-8 rounded-xl transition-all duration-200 flex items-center justify-center disabled:cursor-not-allowed disabled:bg-transparent',
|
'h-8 w-8 rounded-xl transition-all duration-200 flex items-center justify-center disabled:cursor-not-allowed disabled:bg-transparent',
|
||||||
(inputValue.content.trim() || files.length > 0) && !loading
|
(inputValue.content.trim() || files.length > 0) && !loading && !isUploading && !hasFailedUploads
|
||||||
? 'bg-[var(--color-accent)] text-[var(--color-accent-text)] @hover:bg-[var(--color-accent-hover)]'
|
? 'bg-[var(--color-accent)] text-[var(--color-accent-text)] @hover:bg-[var(--color-accent-hover)]'
|
||||||
: 'text-[var(--text-dim)]',
|
: 'text-[var(--text-dim)]',
|
||||||
loading && 'bg-[var(--color-hover)] @hover:bg-[var(--color-active)]',
|
loading && 'bg-[var(--color-hover)] @hover:bg-[var(--color-active)]',
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
const RETRY_BASE_DELAY_MS = 1000;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
selectedModel?: ModelWithProvider | null;
|
selectedModel?: ModelWithProvider | null;
|
||||||
}>();
|
}>();
|
||||||
@@ -15,10 +18,12 @@ const files = defineModel<{
|
|||||||
status: 'uploading' | 'uploaded' | 'error';
|
status: 'uploading' | 'uploaded' | 'error';
|
||||||
url: string;
|
url: string;
|
||||||
progress: number;
|
progress: number;
|
||||||
|
retryCount: number;
|
||||||
}[]>({ required: false, default: [] });
|
}[]>({ required: false, default: [] });
|
||||||
const rawFiles = ref<File[]>([]);
|
const rawFiles = ref<File[]>([]);
|
||||||
|
|
||||||
const activeUploads = ref<Map<string, XMLHttpRequest>>(new Map());
|
const activeUploads = ref<Map<string, XMLHttpRequest>>(new Map());
|
||||||
|
const pendingFiles = ref<Map<string, File>>(new Map());
|
||||||
|
|
||||||
const uploadWithProgress = (file: File, url: string, id: string) => {
|
const uploadWithProgress = (file: File, url: string, id: string) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -39,12 +44,16 @@ const uploadWithProgress = (file: File, url: string, id: string) => {
|
|||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
activeUploads.value.delete(id);
|
activeUploads.value.delete(id);
|
||||||
|
if (xhr.status >= 400) {
|
||||||
|
reject(new Error(`Upload failed with status ${xhr.status}`));
|
||||||
|
} else {
|
||||||
resolve(xhr);
|
resolve(xhr);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = () => {
|
xhr.onerror = () => {
|
||||||
activeUploads.value.delete(id);
|
activeUploads.value.delete(id);
|
||||||
reject(xhr);
|
reject(new Error('Network error during upload'));
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onabort = () => {
|
xhr.onabort = () => {
|
||||||
@@ -57,21 +66,9 @@ const uploadWithProgress = (file: File, url: string, id: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFile = async (file: File) => {
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
try {
|
|
||||||
const id = nanoid();
|
|
||||||
const fileName = file.name || `${id}.png`;
|
|
||||||
const fileType = file.type || 'application/octet-stream';
|
|
||||||
|
|
||||||
files.value.push({
|
|
||||||
id,
|
|
||||||
name: fileName,
|
|
||||||
mimeType: fileType,
|
|
||||||
status: 'uploading',
|
|
||||||
url: URL.createObjectURL(file),
|
|
||||||
progress: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const uploadFileAttempt = async (file: File, id: string, fileName: string, fileType: string) => {
|
||||||
const { url: uploadUrl, assetUrl } = await $fetch('/api/upload/presigned', {
|
const { url: uploadUrl, assetUrl } = await $fetch('/api/upload/presigned', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
@@ -93,32 +90,98 @@ const uploadFile = async (file: File) => {
|
|||||||
size: file.size,
|
size: file.size,
|
||||||
url: assetUrl,
|
url: assetUrl,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFile = async (file: File) => {
|
||||||
|
const id = nanoid();
|
||||||
|
const fileName = file.name || `${id}.png`;
|
||||||
|
const fileType = file.type || 'application/octet-stream';
|
||||||
|
|
||||||
|
files.value.push({
|
||||||
|
id,
|
||||||
|
name: fileName,
|
||||||
|
mimeType: fileType,
|
||||||
|
status: 'uploading',
|
||||||
|
url: URL.createObjectURL(file),
|
||||||
|
progress: 0,
|
||||||
|
retryCount: 0,
|
||||||
|
});
|
||||||
|
pendingFiles.value.set(id, file);
|
||||||
|
|
||||||
|
await attemptUpload(file, id, fileName, fileType);
|
||||||
|
};
|
||||||
|
|
||||||
|
const attemptUpload = async (file: File, id: string, fileName: string, fileType: string, attempt = 0) => {
|
||||||
|
try {
|
||||||
|
await uploadFileAttempt(file, id, fileName, fileType);
|
||||||
|
|
||||||
files.value = files.value.map(f => {
|
files.value = files.value.map(f => {
|
||||||
if (f.name === file.name) {
|
if (f.id === id) {
|
||||||
return {
|
return { ...f, status: 'uploaded' as const, progress: 100 };
|
||||||
...f,
|
|
||||||
status: 'uploaded',
|
|
||||||
progress: 100
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
});
|
});
|
||||||
|
pendingFiles.value.delete(id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof Error && error.message === 'Upload aborted') {
|
||||||
|
pendingFiles.value.delete(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextAttempt = attempt + 1;
|
||||||
|
|
||||||
|
if (nextAttempt < MAX_RETRIES) {
|
||||||
files.value = files.value.map(f => {
|
files.value = files.value.map(f => {
|
||||||
if (f.name === file.name) {
|
if (f.id === id) {
|
||||||
return {
|
return { ...f, retryCount: nextAttempt, progress: 0 };
|
||||||
...f,
|
}
|
||||||
status: 'error'
|
return f;
|
||||||
};
|
});
|
||||||
|
|
||||||
|
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
|
||||||
|
await sleep(delay);
|
||||||
|
|
||||||
|
const stillExists = files.value.some(f => f.id === id);
|
||||||
|
if (!stillExists) return;
|
||||||
|
|
||||||
|
files.value = files.value.map(f => {
|
||||||
|
if (f.id === id) {
|
||||||
|
return { ...f, status: 'uploading' as const };
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
|
||||||
|
await attemptUpload(file, id, fileName, fileType, nextAttempt);
|
||||||
|
} else {
|
||||||
|
files.value = files.value.map(f => {
|
||||||
|
if (f.id === id) {
|
||||||
|
return { ...f, status: 'error' as const };
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
defineExpose({ uploadFile });
|
const retryFile = async (id: string) => {
|
||||||
|
const file = pendingFiles.value.get(id);
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const entry = files.value.find(f => f.id === id);
|
||||||
|
if (!entry) return;
|
||||||
|
|
||||||
|
files.value = files.value.map(f => {
|
||||||
|
if (f.id === id) {
|
||||||
|
return { ...f, status: 'uploading' as const, progress: 0, retryCount: 0 };
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
|
||||||
|
await attemptUpload(file, id, entry.name, entry.mimeType);
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ uploadFile, retryFile });
|
||||||
|
|
||||||
watch(rawFiles, async (newFiles, oldFiles) => {
|
watch(rawFiles, async (newFiles, oldFiles) => {
|
||||||
const diff = newFiles.filter(f =>
|
const diff = newFiles.filter(f =>
|
||||||
@@ -146,6 +209,8 @@ watch(files, async (newFiles, oldFiles) => {
|
|||||||
activeUploads.value.delete(removedFile.id);
|
activeUploads.value.delete(removedFile.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingFiles.value.delete(removedFile.id);
|
||||||
|
|
||||||
if (removedFile.url.startsWith('blob:')) {
|
if (removedFile.url.startsWith('blob:')) {
|
||||||
URL.revokeObjectURL(removedFile.url);
|
URL.revokeObjectURL(removedFile.url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import RowVirtualizerFixed from '~/components/RowVirtualizerFixed.vue';
|
import RowVirtualizerFixed from '~/components/RowVirtualizerFixed.vue';
|
||||||
import { useFloating, offset, flip, shift, autoUpdate, hide } from '@floating-ui/vue';
|
import { useFloating, offset, flip, shift, autoUpdate, hide } from '@floating-ui/vue';
|
||||||
|
import { DialogType } from '~/composables/useDialog';
|
||||||
|
|
||||||
|
const { openDialog } = useDialog();
|
||||||
const dropdownOpen = ref(false);
|
const dropdownOpen = ref(false);
|
||||||
const dropdownTrigger = ref<HTMLElement | null>(null);
|
const dropdownTrigger = ref<HTMLElement | null>(null);
|
||||||
const dropdownContent = ref(null);
|
const dropdownContent = ref(null);
|
||||||
@@ -151,6 +153,17 @@ onMounted(() => {
|
|||||||
<SidenavItem :to="`/agent/${agentId}/profile`" name="Agent Info" icon="i-mynaui-info-square"
|
<SidenavItem :to="`/agent/${agentId}/profile`" name="Agent Info" icon="i-mynaui-info-square"
|
||||||
:active="route.path.endsWith('/profile')" />
|
:active="route.path.endsWith('/profile')" />
|
||||||
|
|
||||||
|
<SidenavItem @click="openDialog(DialogType.QuickSwitcher)" name="Search" icon="i-mynaui-search">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="flex bg-[var(--bg-container)] px-1 rounded border border-[var(--color-border)]">
|
||||||
|
<kbd class="font-mono text-[10px] case-capital">ctrl</kbd>
|
||||||
|
</span>
|
||||||
|
<span class="flex bg-[var(--bg-container)] px-1 rounded border border-[var(--color-border)]">
|
||||||
|
<kbd class="font-mono text-[10px] case-capital">k</kbd>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</SidenavItem>
|
||||||
|
|
||||||
<!-- Topics Section -->
|
<!-- Topics Section -->
|
||||||
<button @click="topicsOpen = !topicsOpen"
|
<button @click="topicsOpen = !topicsOpen"
|
||||||
class="flex items-center justify-between gap-2 px-2 py-2 rounded-lg bg-transparent @hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] transition-colors w-full text-left">
|
class="flex items-center justify-between gap-2 px-2 py-2 rounded-lg bg-transparent @hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] transition-colors w-full text-left">
|
||||||
|
|||||||
+77
-15
@@ -6,6 +6,13 @@ import { buildMessageTree } from '~~/utils/message';
|
|||||||
export type BaseMessage = {
|
export type BaseMessage = {
|
||||||
content: string;
|
content: string;
|
||||||
fileIds: string[];
|
fileIds: string[];
|
||||||
|
files?: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
mimeType: string;
|
||||||
|
url: string;
|
||||||
|
size?: number;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolCall = typeof schema.toolCalls.$inferSelect
|
export type ToolCall = typeof schema.toolCalls.$inferSelect
|
||||||
@@ -263,12 +270,6 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
const { data: ssrData } = await useFetch<Topic & { messages: Message[] }>(`/api/topic/${id}`);
|
const { data: ssrData } = await useFetch<Topic & { messages: Message[] }>(`/api/topic/${id}`);
|
||||||
data.value = ssrData.value;
|
data.value = ssrData.value;
|
||||||
} else {
|
} else {
|
||||||
// if (data.value === undefined) {
|
|
||||||
// const id = unref(topicId);
|
|
||||||
// const { data: ssrData } = await useFetch<Topic & { messages: Message[] }>(`/api/topic/${id}`);
|
|
||||||
// data.value = ssrData.value;
|
|
||||||
// }
|
|
||||||
|
|
||||||
await connectSSE();
|
await connectSSE();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,6 +306,7 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
const sendMessage = async (
|
const sendMessage = async (
|
||||||
baseMessage: BaseMessage,
|
baseMessage: BaseMessage,
|
||||||
onRequest?: () => void,
|
onRequest?: () => void,
|
||||||
|
role: 'user' | 'assistant' = 'user',
|
||||||
): Promise<Result<void, ChatErrorType>> => {
|
): Promise<Result<void, ChatErrorType>> => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
if (!user.value) return Err(ChatErrorType.NoUser);
|
if (!user.value) return Err(ChatErrorType.NoUser);
|
||||||
@@ -312,34 +314,94 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
try {
|
try {
|
||||||
const message = {
|
const message = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
role: 'user',
|
role,
|
||||||
content: baseMessage.content,
|
|
||||||
fileIds: baseMessage.fileIds,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await $fetch(`/api/topic/${unref(topicId)}/message`, {
|
await $fetch(`/api/topic/${unref(topicId)}/message`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
message
|
message: {
|
||||||
|
...message,
|
||||||
|
content: baseMessage.content,
|
||||||
|
fileIds: baseMessage.fileIds,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onRequest() {
|
onRequest() {
|
||||||
if (data.value) {
|
if (data.value) {
|
||||||
|
switch (role) {
|
||||||
|
case 'user': {
|
||||||
|
const optimisticAttachments = baseMessage.files?.map(file => ({
|
||||||
|
id: nanoid(),
|
||||||
|
userId: user.value!.id,
|
||||||
|
topicId: unref(topicId),
|
||||||
|
messageId: message.id,
|
||||||
|
fileId: file.id,
|
||||||
|
createdAt: new Date(),
|
||||||
|
file: {
|
||||||
|
id: file.id,
|
||||||
|
userId: user.value!.id,
|
||||||
|
name: file.name,
|
||||||
|
mimeType: file.mimeType,
|
||||||
|
size: file.size ?? 0,
|
||||||
|
url: file.url,
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
data.value!.messages.push({
|
data.value!.messages.push({
|
||||||
topicId: unref(topicId),
|
topicId: unref(topicId),
|
||||||
userId: user.value!.id,
|
userId: user.value!.id,
|
||||||
// TODO
|
attachments: optimisticAttachments,
|
||||||
attachments: [],
|
|
||||||
parts: undefined,
|
parts: undefined,
|
||||||
generation: null,
|
generation: null,
|
||||||
parentMessageId: null,
|
parentMessageId: null,
|
||||||
generationId: null,
|
generationId: null,
|
||||||
activeChildId: null,
|
activeChildId: null,
|
||||||
deleted: null,
|
children: [],
|
||||||
|
deleted: false,
|
||||||
|
content: baseMessage.content,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
children: [],
|
|
||||||
...message
|
...message
|
||||||
} as Message);
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'assistant': {
|
||||||
|
data.value!.messages.push({
|
||||||
|
topicId: unref(topicId),
|
||||||
|
userId: user.value!.id,
|
||||||
|
// TODO
|
||||||
|
attachments: [],
|
||||||
|
generation: null,
|
||||||
|
parentMessageId: null,
|
||||||
|
generationId: null,
|
||||||
|
activeChildId: null,
|
||||||
|
children: [],
|
||||||
|
deleted: false,
|
||||||
|
content: null,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: 'bogus-id',
|
||||||
|
userId: user.value!.id,
|
||||||
|
topicId: unref(topicId),
|
||||||
|
messageId: message.id,
|
||||||
|
type: 'text',
|
||||||
|
content: baseMessage.content,
|
||||||
|
finished: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
lastUpdatedAt: new Date(),
|
||||||
|
providerOptions: null,
|
||||||
|
toolCallId: null,
|
||||||
|
toolCall: null,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
...message
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onRequest?.();
|
onRequest?.();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
import type { BaseMessage } from '~/composables/useChat';
|
import type { BaseMessage } from '~/composables/useChat';
|
||||||
import type { ModelWithProvider } from '~/composables/useModels';
|
import type { ModelWithProvider } from '~/composables/useModels';
|
||||||
|
|
||||||
@@ -43,7 +44,23 @@ const handleSubmit = async (message: BaseMessage, model: ModelWithProvider | nul
|
|||||||
children: [],
|
children: [],
|
||||||
generationId: null,
|
generationId: null,
|
||||||
activeChildId: null,
|
activeChildId: null,
|
||||||
attachments: [],
|
attachments: message.files?.map(file => ({
|
||||||
|
id: nanoid(),
|
||||||
|
userId: user.value!.id,
|
||||||
|
topicId: '',
|
||||||
|
messageId: '',
|
||||||
|
fileId: file.id,
|
||||||
|
createdAt: new Date(),
|
||||||
|
file: {
|
||||||
|
id: file.id,
|
||||||
|
userId: user.value!.id,
|
||||||
|
name: file.name,
|
||||||
|
mimeType: file.mimeType,
|
||||||
|
size: file.size ?? 0,
|
||||||
|
url: file.url,
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
})) ?? [],
|
||||||
deleted: false,
|
deleted: false,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
@@ -75,6 +92,27 @@ const handleSubmit = async (message: BaseMessage, model: ModelWithProvider | nul
|
|||||||
|
|
||||||
return startGeneration(model, undefined, route.params.id as string);
|
return startGeneration(model, undefined, route.params.id as string);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddMessage = async (message: BaseMessage, role: 'user' | 'assistant', _model: ModelWithProvider | null) => {
|
||||||
|
const user = useAuth().user;
|
||||||
|
if (!user) {
|
||||||
|
console.error('No user');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const topic = await createTopic(agent.value!.id);
|
||||||
|
if (!topic) throw new Error('Failed to create topic');
|
||||||
|
|
||||||
|
const { sendMessage } = await useChat(topic.id, false);
|
||||||
|
|
||||||
|
const res = await sendMessage(message, undefined, role);
|
||||||
|
if (res.ok === false) {
|
||||||
|
console.error('Failed to send message:', res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await navigateTo(`/agent/${route.params.id}/topic/${topic.id}`);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@@ -108,7 +146,8 @@ const handleSubmit = async (message: BaseMessage, model: ModelWithProvider | nul
|
|||||||
|
|
||||||
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
|
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
|
||||||
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
|
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
|
||||||
:agent="agent" :providers="providers?.filter(p => p.enabled)" @submit="handleSubmit" />
|
:allow-manual-role="true" :agent="agent" :providers="providers?.filter(p => p.enabled)"
|
||||||
|
@submit="handleSubmit" @add-message="handleAddMessage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ const changeSystemPrompt = async (e: Event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-14 flex items-center justify-between px-4">
|
<div class="h-full flex flex-col overflow-hidden pb-4">
|
||||||
|
<div class="h-14 flex items-center justify-between px-4 shrink-0">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button v-if="!sidebarOpen" @click="openSidebar"
|
<button v-if="!sidebarOpen" @click="openSidebar"
|
||||||
class="flex p-2 rounded-lg text-[var(--text-primary)] bg-transparent @hover:bg-[var(--color-hover)] transition-colors">
|
class="flex p-2 rounded-lg text-[var(--text-primary)] bg-transparent @hover:bg-[var(--color-hover)] transition-colors">
|
||||||
@@ -64,7 +65,7 @@ const changeSystemPrompt = async (e: Event) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 px-14 w-full h-full">
|
<div class="flex flex-col gap-4 px-4 md:px-14 w-full flex-1 min-h-0">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div>
|
<div>
|
||||||
<img v-if="agent?.imageUrl" :src="agent.imageUrl" class="w-16 h-16 rounded-full object-cover" />
|
<img v-if="agent?.imageUrl" :src="agent.imageUrl" class="w-16 h-16 rounded-full object-cover" />
|
||||||
@@ -74,11 +75,12 @@ const changeSystemPrompt = async (e: Event) => {
|
|||||||
class="placeholder:text-[var(--text-tertiary)] w-full bg-transparent rounded-none border-b-4 border-b-[var(--color-border)] text-12 p-0"
|
class="placeholder:text-[var(--text-tertiary)] w-full bg-transparent rounded-none border-b-4 border-b-[var(--color-border)] text-12 p-0"
|
||||||
type="text" />
|
type="text" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 w-full h-full mb-14">
|
<div class="flex flex-col gap-2 w-full flex-1 min-h-0">
|
||||||
<label class="text-sm text-[var(--text-secondary)]">System Message</label>
|
<label class="text-sm text-[var(--text-secondary)]">System Message</label>
|
||||||
<textarea v-model="systemPrompt" placeholder="You are a helpful assistant."
|
<textarea v-model="systemPrompt" placeholder="You are a helpful assistant."
|
||||||
class="p-4 w-full h-full resize-none bg-transparent rounded-lg border border-[var(--color-border)]"
|
class="p-4 w-full flex-1 min-h-0 resize-none bg-transparent rounded-lg border border-[var(--color-border)]"
|
||||||
@input="changeSystemPrompt"></textarea>
|
@input="changeSystemPrompt"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -54,6 +54,27 @@ const submitMessage = async (message: BaseMessage, model: ModelWithProvider | nu
|
|||||||
startGeneration(model);
|
startGeneration(model);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddMessage = async (message: BaseMessage, role: 'user' | 'assistant', _model: ModelWithProvider | null) => {
|
||||||
|
inputValue.value = { content: '', fileIds: [] };
|
||||||
|
|
||||||
|
const res = await sendMessage(message, async () => {
|
||||||
|
await nextTick();
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToBottom('instant');
|
||||||
|
});
|
||||||
|
}, role);
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Failed to add message:', res.error);
|
||||||
|
const chatInput = document.getElementById('chat') as HTMLInputElement | null;
|
||||||
|
if (chatInput) {
|
||||||
|
inputValue.value = message;
|
||||||
|
nextTick(() => {
|
||||||
|
chatInput.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleRegenerate = async (message: Message) => {
|
const handleRegenerate = async (message: Message) => {
|
||||||
if (!agent.value!.defaultModelId) {
|
if (!agent.value!.defaultModelId) {
|
||||||
console.error('No model selected');
|
console.error('No model selected');
|
||||||
@@ -292,8 +313,8 @@ console.log("full page render took", Date.now() - rootStart);
|
|||||||
|
|
||||||
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
|
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
|
||||||
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
|
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
|
||||||
:loading="activeGeneration !== null" :agent="agent"
|
:loading="activeGeneration !== null" :allow-manual-role="true" :agent="agent"
|
||||||
:providers="providers?.filter(p => p.enabled)" @submit="submitMessage" @cancel="handleCancel"
|
:providers="providers?.filter(p => p.enabled)" @submit="submitMessage" @add-message="handleAddMessage" @cancel="handleCancel"
|
||||||
@resize="handleResize" />
|
@resize="handleResize" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "veridian",
|
"name": "veridian",
|
||||||
@@ -14,17 +15,17 @@
|
|||||||
"@ai-sdk/openai-compatible": "^2.0.41",
|
"@ai-sdk/openai-compatible": "^2.0.41",
|
||||||
"@aws-sdk/client-s3": "^3.1028.0",
|
"@aws-sdk/client-s3": "^3.1028.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.1028.0",
|
"@aws-sdk/s3-request-presigner": "^3.1028.0",
|
||||||
"@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@6913",
|
"@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@9489",
|
||||||
"@floating-ui/vue": "^1.1.11",
|
"@floating-ui/vue": "^1.1.11",
|
||||||
"@iconify-json/mynaui": "^1.2.17",
|
"@iconify-json/mynaui": "^1.2.17",
|
||||||
"@nuxt/fonts": "0.14.0",
|
"@nuxt/fonts": "0.14.0",
|
||||||
"@nuxt/hints": "1.0.0-alpha.5",
|
"@nuxt/hints": "1.0.0-alpha.5",
|
||||||
"@nuxt/icon": "2.2.0",
|
"@nuxt/icon": "2.2.0",
|
||||||
"@openrouter/ai-sdk-provider": "^2.5.1",
|
"@openrouter/ai-sdk-provider": "^2.9.0",
|
||||||
"@pydantic/monty": "^0.0.17",
|
"@pydantic/monty": "^0.0.17",
|
||||||
"@sentry/nuxt": "^10.48.0",
|
"@sentry/nuxt": "^10.48.0",
|
||||||
"@tanstack/vue-virtual": "^3.13.23",
|
"@tanstack/vue-virtual": "^3.13.23",
|
||||||
"ai": "^6.0.156",
|
"ai": "^6.0.182",
|
||||||
"ai-sdk-ollama": "^3.8.3",
|
"ai-sdk-ollama": "^3.8.3",
|
||||||
"better-auth": "^1.6.2",
|
"better-auth": "^1.6.2",
|
||||||
"comlink": "^4.4.2",
|
"comlink": "^4.4.2",
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
|
|
||||||
"@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.30", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-j3fe/6lUUkHPD/51OgMXN9UD7p1QSQEAlroIinmb3MhJ1s+O0MnqdRa30IM7dRHafNp0FQ9X4YpobY85iMknUQ=="],
|
"@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.30", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-j3fe/6lUUkHPD/51OgMXN9UD7p1QSQEAlroIinmb3MhJ1s+O0MnqdRa30IM7dRHafNp0FQ9X4YpobY85iMknUQ=="],
|
||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.114", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MqkZ5sd+qiq6RgIxELkoFQXg2/JwK+WCMaot7U+rtrZpWJl3fSyYvc28SC03b256o4F7OXjQtdjTqs81B2w+dA=="],
|
||||||
|
|
||||||
"@ai-sdk/google": ["@ai-sdk/google@3.0.61", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jEKU1Mjcy5CoicejdJQIzM0ntYwyXR8vtYgAZYriKaOuLAiAhiiU538++fGU3CC9HJH/mL1OfsCwMM3gFiCNsw=="],
|
"@ai-sdk/google": ["@ai-sdk/google@3.0.61", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jEKU1Mjcy5CoicejdJQIzM0ntYwyXR8vtYgAZYriKaOuLAiAhiiU538++fGU3CC9HJH/mL1OfsCwMM3gFiCNsw=="],
|
||||||
|
|
||||||
@@ -264,9 +265,9 @@
|
|||||||
|
|
||||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||||
|
|
||||||
"@better-auth/core": ["@better-auth/core@1.4.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.3.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.8", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-q+awYgC7nkLEBdx2sW0iJjkzgSHlIxGnOpsN1r/O1+a4m7osJNHtfK2mKJSL1I+GfNyIlxJF8WvD/NLuYMpmcg=="],
|
"@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
|
||||||
|
|
||||||
"@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@6913", { "peerDependencies": { "@better-auth/core": "1.5.0-beta.13", "@better-auth/utils": "^0.3.0", "drizzle-orm": ">=0.41.0", "prettier": "^3.7.4" } }],
|
"@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@9489", { "peerDependencies": { "@better-auth/core": "^1.7.0-beta.3", "@better-auth/utils": "0.4.0", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }],
|
||||||
|
|
||||||
"@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "kysely": "^0.27.0 || ^0.28.0" }, "optionalPeers": ["kysely"] }, "sha512-YMMm75jek/MNCAFWTAaq/U3VPmFnrwZW4NhBjjAwruHQJEIrSZZaOaUEXuUpFRRBhWqg7OOltQcHMwU/45CkuA=="],
|
"@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "kysely": "^0.27.0 || ^0.28.0" }, "optionalPeers": ["kysely"] }, "sha512-YMMm75jek/MNCAFWTAaq/U3VPmFnrwZW4NhBjjAwruHQJEIrSZZaOaUEXuUpFRRBhWqg7OOltQcHMwU/45CkuA=="],
|
||||||
|
|
||||||
@@ -278,7 +279,7 @@
|
|||||||
|
|
||||||
"@better-auth/telemetry": ["@better-auth/telemetry@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21" } }, "sha512-o4gHKXqizUxVUUYChZZTowLEzdsz3ViBE/fKFzfHqNFUnF+aVt8QsbLSfipq1WpTIXyJVT/SnH0hgSdWxdssbQ=="],
|
"@better-auth/telemetry": ["@better-auth/telemetry@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21" } }, "sha512-o4gHKXqizUxVUUYChZZTowLEzdsz3ViBE/fKFzfHqNFUnF+aVt8QsbLSfipq1WpTIXyJVT/SnH0hgSdWxdssbQ=="],
|
||||||
|
|
||||||
"@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
|
"@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
||||||
|
|
||||||
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
|
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
|
||||||
|
|
||||||
@@ -456,9 +457,9 @@
|
|||||||
|
|
||||||
"@nuxt/vite-builder": ["@nuxt/vite-builder@4.4.2", "", { "dependencies": { "@nuxt/kit": "4.4.2", "@rollup/plugin-replace": "^6.0.3", "@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue-jsx": "^5.1.4", "autoprefixer": "^10.4.27", "consola": "^3.4.2", "cssnano": "^7.1.3", "defu": "^6.1.4", "escape-string-regexp": "^5.0.0", "exsolve": "^1.0.8", "get-port-please": "^3.2.0", "jiti": "^2.6.1", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.1", "mocked-exports": "^0.1.1", "nypm": "^0.6.5", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "postcss": "^8.5.8", "seroval": "^1.5.1", "std-env": "^4.0.0", "ufo": "^1.6.3", "unenv": "^2.0.0-rc.24", "vite": "^7.3.1", "vite-node": "^5.3.0", "vite-plugin-checker": "^0.12.0", "vue-bundle-renderer": "^2.2.0" }, "peerDependencies": { "@babel/plugin-proposal-decorators": "^7.25.0", "@babel/plugin-syntax-jsx": "^7.25.0", "nuxt": "4.4.2", "rolldown": "^1.0.0-beta.38", "rollup-plugin-visualizer": "^6.0.0 || ^7.0.1", "vue": "^3.3.4" }, "optionalPeers": ["@babel/plugin-proposal-decorators", "@babel/plugin-syntax-jsx", "rolldown", "rollup-plugin-visualizer"] }, "sha512-fJaIwMA8ID6BU5EqmoDvnhq4qYDJeWjdHk4jfqy8D3Nm7CoUW0BvX7Ee92XoO05rtUiClGlk/NQ1Ii8hs3ZIbw=="],
|
"@nuxt/vite-builder": ["@nuxt/vite-builder@4.4.2", "", { "dependencies": { "@nuxt/kit": "4.4.2", "@rollup/plugin-replace": "^6.0.3", "@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue-jsx": "^5.1.4", "autoprefixer": "^10.4.27", "consola": "^3.4.2", "cssnano": "^7.1.3", "defu": "^6.1.4", "escape-string-regexp": "^5.0.0", "exsolve": "^1.0.8", "get-port-please": "^3.2.0", "jiti": "^2.6.1", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.1", "mocked-exports": "^0.1.1", "nypm": "^0.6.5", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "postcss": "^8.5.8", "seroval": "^1.5.1", "std-env": "^4.0.0", "ufo": "^1.6.3", "unenv": "^2.0.0-rc.24", "vite": "^7.3.1", "vite-node": "^5.3.0", "vite-plugin-checker": "^0.12.0", "vue-bundle-renderer": "^2.2.0" }, "peerDependencies": { "@babel/plugin-proposal-decorators": "^7.25.0", "@babel/plugin-syntax-jsx": "^7.25.0", "nuxt": "4.4.2", "rolldown": "^1.0.0-beta.38", "rollup-plugin-visualizer": "^6.0.0 || ^7.0.1", "vue": "^3.3.4" }, "optionalPeers": ["@babel/plugin-proposal-decorators", "@babel/plugin-syntax-jsx", "rolldown", "rollup-plugin-visualizer"] }, "sha512-fJaIwMA8ID6BU5EqmoDvnhq4qYDJeWjdHk4jfqy8D3Nm7CoUW0BvX7Ee92XoO05rtUiClGlk/NQ1Ii8hs3ZIbw=="],
|
||||||
|
|
||||||
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="],
|
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.9.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Seva+NCa0WUQnJIUE5GzHsUv1WTIeyqwz0ELl2VtS6NP+eF+77yCXGFVOMbvoCM7QMjlnhv7931e89R+8pJdcQ=="],
|
||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
||||||
|
|
||||||
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="],
|
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="],
|
||||||
|
|
||||||
@@ -1076,7 +1077,7 @@
|
|||||||
|
|
||||||
"@vercel/nft": ["@vercel/nft@0.27.10", "", { "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0-rc.0", "@rollup/pluginutils": "^5.1.3", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", "resolve-from": "^5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-zbaF9Wp/NsZtKLE4uVmL3FyfFwlpDyuymQM1kPbeT0mVOHKDQQNjnnfslB3REg3oZprmNFJuh3pkHBk2qAaizg=="],
|
"@vercel/nft": ["@vercel/nft@0.27.10", "", { "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0-rc.0", "@rollup/pluginutils": "^5.1.3", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", "resolve-from": "^5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-zbaF9Wp/NsZtKLE4uVmL3FyfFwlpDyuymQM1kPbeT0mVOHKDQQNjnnfslB3REg3oZprmNFJuh3pkHBk2qAaizg=="],
|
||||||
|
|
||||||
"@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
|
"@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
||||||
|
|
||||||
@@ -1170,7 +1171,7 @@
|
|||||||
|
|
||||||
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
|
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
|
||||||
|
|
||||||
"ai": ["ai@6.0.156", "", { "dependencies": { "@ai-sdk/gateway": "3.0.95", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uyi/5LYbugHQxZsR2PeAFOZEL4WqKkzZw4pv0nQvvdgxgVOsM7snOmGrYkp5fShxH/vnd08SXvHCVTX7oUW7xQ=="],
|
"ai": ["ai@6.0.182", "", { "dependencies": { "@ai-sdk/gateway": "3.0.114", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ooJdziFjYrYRcsCx107roqA8gDTI3P82nUfroNWIhVvwrkYzEN3W1l50YK+XNqkUew8AiimaW0/SLBewRXMuHQ=="],
|
||||||
|
|
||||||
"ai-sdk-ollama": ["ai-sdk-ollama@3.8.3", "", { "dependencies": { "@ai-sdk/provider": "^3.0.8", "@ai-sdk/provider-utils": "^4.0.23", "jsonrepair": "^3.13.3", "ollama": "^0.6.3" }, "peerDependencies": { "ai": "^6.0.154" } }, "sha512-KId/S++eb0CgTPFTtHzCGCrO73kXZLK+hyyZx5k8LVqU2XOEHYKVbIwDiQ+hm3okHjnsGehn4zR4QNm14SUM3Q=="],
|
"ai-sdk-ollama": ["ai-sdk-ollama@3.8.3", "", { "dependencies": { "@ai-sdk/provider": "^3.0.8", "@ai-sdk/provider-utils": "^4.0.23", "jsonrepair": "^3.13.3", "ollama": "^0.6.3" }, "peerDependencies": { "ai": "^6.0.154" } }, "sha512-KId/S++eb0CgTPFTtHzCGCrO73kXZLK+hyyZx5k8LVqU2XOEHYKVbIwDiQ+hm3okHjnsGehn4zR4QNm14SUM3Q=="],
|
||||||
|
|
||||||
@@ -2140,8 +2141,6 @@
|
|||||||
|
|
||||||
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
||||||
|
|
||||||
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
|
||||||
|
|
||||||
"pretty-bytes": ["pretty-bytes@7.1.0", "", {}, "sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw=="],
|
"pretty-bytes": ["pretty-bytes@7.1.0", "", {}, "sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw=="],
|
||||||
|
|
||||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||||
@@ -2566,6 +2565,10 @@
|
|||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
|
"@ai-sdk/gateway/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
|
"@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
|
"@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
|
||||||
@@ -2602,28 +2605,6 @@
|
|||||||
|
|
||||||
"@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
"@better-auth/core/better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="],
|
|
||||||
|
|
||||||
"@better-auth/kysely-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
|
|
||||||
|
|
||||||
"@better-auth/kysely-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
|
||||||
|
|
||||||
"@better-auth/memory-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
|
|
||||||
|
|
||||||
"@better-auth/memory-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
|
||||||
|
|
||||||
"@better-auth/mongo-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
|
|
||||||
|
|
||||||
"@better-auth/mongo-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
|
||||||
|
|
||||||
"@better-auth/prisma-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
|
|
||||||
|
|
||||||
"@better-auth/prisma-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
|
||||||
|
|
||||||
"@better-auth/telemetry/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
|
|
||||||
|
|
||||||
"@better-auth/telemetry/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
|
||||||
|
|
||||||
"@dxup/nuxt/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
"@dxup/nuxt/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||||
|
|
||||||
"@fastify/otel/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg=="],
|
"@fastify/otel/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg=="],
|
||||||
@@ -2680,8 +2661,6 @@
|
|||||||
|
|
||||||
"@nuxt/vite-builder/std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
"@nuxt/vite-builder/std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
||||||
|
|
||||||
"@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
|
||||||
|
|
||||||
"@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="],
|
"@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="],
|
||||||
|
|
||||||
"@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="],
|
"@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="],
|
||||||
@@ -2712,10 +2691,6 @@
|
|||||||
|
|
||||||
"@sentry/cli/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"@sentry/cli/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
"@sentry/cloudflare/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
|
||||||
|
|
||||||
"@sentry/node/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
|
||||||
|
|
||||||
"@sentry/nuxt/@nuxt/kit": ["@nuxt/kit@3.21.1", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg=="],
|
"@sentry/nuxt/@nuxt/kit": ["@nuxt/kit@3.21.1", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg=="],
|
||||||
|
|
||||||
"@types/connect/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
|
"@types/connect/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
|
||||||
@@ -2786,6 +2761,10 @@
|
|||||||
|
|
||||||
"@vue/server-renderer/@vue/shared": ["@vue/shared@3.6.0-beta.10", "", {}, "sha512-13JUfIAd06F+IBnObE8mExDAMOknPIBjPBUN2JeemmuQwj5i20GduCLbHLVbxSkpFD0RGH4z2mOxUUdD+8M/Aw=="],
|
"@vue/server-renderer/@vue/shared": ["@vue/shared@3.6.0-beta.10", "", {}, "sha512-13JUfIAd06F+IBnObE8mExDAMOknPIBjPBUN2JeemmuQwj5i20GduCLbHLVbxSkpFD0RGH4z2mOxUUdD+8M/Aw=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||||
@@ -2798,14 +2777,8 @@
|
|||||||
|
|
||||||
"ast-walker-scope/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"ast-walker-scope/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
"better-auth/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
|
|
||||||
|
|
||||||
"better-auth/@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "drizzle-orm": ">=0.41.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-KawrNNuhgmpcc5PgLs6HesMckxCscz5J+BQ99iRmU1cLzG/A87IcydrmYtep+K8WHPN0HmZ/i4z/nOBCtxE2qA=="],
|
"better-auth/@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "drizzle-orm": ">=0.41.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-KawrNNuhgmpcc5PgLs6HesMckxCscz5J+BQ99iRmU1cLzG/A87IcydrmYtep+K8WHPN0HmZ/i4z/nOBCtxE2qA=="],
|
||||||
|
|
||||||
"better-auth/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
|
||||||
|
|
||||||
"better-call/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
|
||||||
|
|
||||||
"better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
"better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
||||||
|
|
||||||
"browserslist/caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
|
"browserslist/caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
|
||||||
@@ -2992,6 +2965,8 @@
|
|||||||
|
|
||||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"@ai-sdk/gateway/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
|
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
|
"@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
|
||||||
@@ -3012,10 +2987,6 @@
|
|||||||
|
|
||||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"@better-auth/core/better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
|
||||||
|
|
||||||
"@better-auth/core/better-call/set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
|
||||||
|
|
||||||
"@dxup/nuxt/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
"@dxup/nuxt/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||||
|
|
||||||
"@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.212.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg=="],
|
"@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.212.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg=="],
|
||||||
@@ -3168,6 +3139,8 @@
|
|||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
|
||||||
|
|
||||||
"archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
"archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||||
@@ -3496,8 +3469,6 @@
|
|||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||||
|
|
||||||
"@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
|
||||||
|
|
||||||
"@nuxt/kit/mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
"@nuxt/kit/mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||||
|
|
||||||
"@nuxt/kit/mlly/pkg-types/mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
|
"@nuxt/kit/mlly/pkg-types/mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
|
||||||
@@ -3522,6 +3493,8 @@
|
|||||||
|
|
||||||
"@opentelemetry/instrumentation-pg/@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
"@opentelemetry/instrumentation-pg/@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||||
|
|
||||||
|
"@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@types/pg-pool/@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
"@types/pg-pool/@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx/oxc-parser/@oxc-parser/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
|
"@unocss/transformer-attributify-jsx/oxc-parser/@oxc-parser/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
|
||||||
|
|||||||
+3
-3
@@ -20,17 +20,17 @@
|
|||||||
"@ai-sdk/openai-compatible": "^2.0.41",
|
"@ai-sdk/openai-compatible": "^2.0.41",
|
||||||
"@aws-sdk/client-s3": "^3.1028.0",
|
"@aws-sdk/client-s3": "^3.1028.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.1028.0",
|
"@aws-sdk/s3-request-presigner": "^3.1028.0",
|
||||||
"@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@6913",
|
"@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@9489",
|
||||||
"@floating-ui/vue": "^1.1.11",
|
"@floating-ui/vue": "^1.1.11",
|
||||||
"@iconify-json/mynaui": "^1.2.17",
|
"@iconify-json/mynaui": "^1.2.17",
|
||||||
"@nuxt/fonts": "0.14.0",
|
"@nuxt/fonts": "0.14.0",
|
||||||
"@nuxt/hints": "1.0.0-alpha.5",
|
"@nuxt/hints": "1.0.0-alpha.5",
|
||||||
"@nuxt/icon": "2.2.0",
|
"@nuxt/icon": "2.2.0",
|
||||||
"@openrouter/ai-sdk-provider": "^2.5.1",
|
"@openrouter/ai-sdk-provider": "^2.9.0",
|
||||||
"@pydantic/monty": "^0.0.17",
|
"@pydantic/monty": "^0.0.17",
|
||||||
"@sentry/nuxt": "^10.48.0",
|
"@sentry/nuxt": "^10.48.0",
|
||||||
"@tanstack/vue-virtual": "^3.13.23",
|
"@tanstack/vue-virtual": "^3.13.23",
|
||||||
"ai": "^6.0.156",
|
"ai": "^6.0.182",
|
||||||
"ai-sdk-ollama": "^3.8.3",
|
"ai-sdk-ollama": "^3.8.3",
|
||||||
"better-auth": "^1.6.2",
|
"better-auth": "^1.6.2",
|
||||||
"comlink": "^4.4.2",
|
"comlink": "^4.4.2",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
import { auth } from "~~/lib/auth";
|
||||||
|
import { verifyFileToken } from "~~/server/utils/file-token";
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const key = getRouterParam(event, 'key');
|
const key = getRouterParam(event, 'key');
|
||||||
@@ -6,6 +8,34 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 400, statusMessage: 'Missing file key' });
|
throw createError({ statusCode: 400, statusMessage: 'Missing file key' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const query = getQuery(event);
|
||||||
|
const exp = query.exp ? Number(query.exp) : undefined;
|
||||||
|
const sig = query.sig as string | undefined;
|
||||||
|
|
||||||
|
let authorized = false;
|
||||||
|
|
||||||
|
// Path 1: HMAC token (for AI model access)
|
||||||
|
if (exp && sig && process.env.BETTER_AUTH_SECRET) {
|
||||||
|
authorized = verifyFileToken(key, exp, sig, process.env.BETTER_AUTH_SECRET);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path 2: Session auth (for client-side access)
|
||||||
|
if (!authorized) {
|
||||||
|
try {
|
||||||
|
const sessionData = await auth.api.getSession(event);
|
||||||
|
if (sessionData) {
|
||||||
|
event.context.user = sessionData.user;
|
||||||
|
authorized = true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// No valid session
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authorized) {
|
||||||
|
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
const s3 = new S3Client({
|
const s3 = new S3Client({
|
||||||
@@ -24,14 +54,12 @@ export default defineEventHandler(async (event) => {
|
|||||||
Key: key,
|
Key: key,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 3. Set the correct headers so the browser knows what it's receiving
|
|
||||||
setHeaders(event, {
|
setHeaders(event, {
|
||||||
'Content-Type': response.ContentType || 'application/octet-stream',
|
'Content-Type': response.ContentType || 'application/octet-stream',
|
||||||
'Content-Length': response.ContentLength?.toString() || '',
|
'Content-Length': response.ContentLength?.toString() || '',
|
||||||
'Cache-Control': 'public, max-age=3600', // Optional: cache for 1 hour
|
'Cache-Control': 'public, max-age=3600',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. Return the body as a stream directly to the client
|
|
||||||
return response.Body;
|
return response.Body;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.name === 'NoSuchKey') {
|
if (error.name === 'NoSuchKey') {
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ export default defineEventHandler(async (event) => {
|
|||||||
},
|
},
|
||||||
with: {
|
with: {
|
||||||
parts: true,
|
parts: true,
|
||||||
|
attachments: {
|
||||||
|
with: {
|
||||||
|
file: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -160,8 +165,18 @@ export default defineEventHandler(async (event) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prompt = firstMessage.content!;
|
||||||
|
|
||||||
|
if (firstMessage.attachments.length > 0) {
|
||||||
|
for (const attachment of firstMessage.attachments) {
|
||||||
|
if (attachment.file.mimeType.startsWith('image/')) {
|
||||||
|
prompt += `\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const abortController = addPendingRename(topicId);
|
const abortController = addPendingRename(topicId);
|
||||||
event.waitUntil(autoRename(topicId, abortController, { gateway: gateway.gateway, model }, gateway.textTransformer, firstMessage.content!, userId));
|
event.waitUntil(autoRename(topicId, abortController, { gateway: gateway.gateway, model }, gateway.textTransformer, prompt, userId));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as z from 'zod';
|
|||||||
import { type MessageEntity } from '~/composables/useChat';
|
import { type MessageEntity } from '~/composables/useChat';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { glob } from 'glob';
|
import { glob } from 'glob';
|
||||||
import { isLoopFinished, type ModelMessage, rerank, type RerankingModel, streamText, type StreamTextTransform, Tool, tool } from "ai";
|
import { isLoopFinished, type ModelMessage, rerank, type RerankingModel, streamText, type StreamTextTransform, type Tool, tool } from "ai";
|
||||||
import { generations, messageParts, messages, toolCalls, ToolCallType } from "~~/drizzle/schema";
|
import { generations, messageParts, messages, toolCalls, ToolCallType } from "~~/drizzle/schema";
|
||||||
import { topicEvents } from "~~/server/utils/events";
|
import { topicEvents } from "~~/server/utils/events";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
@@ -13,6 +13,7 @@ import { eq } from "drizzle-orm";
|
|||||||
import { buildFocusedMessageTree, buildMessageTree, marshallMessages } from "~~/utils/message";
|
import { buildFocusedMessageTree, buildMessageTree, marshallMessages } from "~~/utils/message";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { isRerankingProvider } from "~~/server/utils/ai-provider";
|
import { isRerankingProvider } from "~~/server/utils/ai-provider";
|
||||||
|
import { generateFileToken } from "~~/server/utils/file-token";
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
await protectRoute(event);
|
await protectRoute(event);
|
||||||
@@ -237,7 +238,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
agentmessage.generationId = generation.id;
|
agentmessage.generationId = generation.id;
|
||||||
// @ts-ignore
|
// @ts-ignore - doesnt exist on the type but yeah it does now
|
||||||
agentmessage.generation = generation;
|
agentmessage.generation = generation;
|
||||||
|
|
||||||
return agentmessage;
|
return agentmessage;
|
||||||
@@ -311,7 +312,13 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const topicMessages = marshallMessages(topic.agent, buildFocusedMessageTree(topicMessageTree));
|
const fileTokenSecret = process.env.BETTER_AUTH_SECRET!;
|
||||||
|
const topicMessages = marshallMessages(topic.agent, buildFocusedMessageTree(topicMessageTree), {
|
||||||
|
signFileUrl: (fileKey) => {
|
||||||
|
const { exp, sig } = generateFileToken(fileKey, fileTokenSecret);
|
||||||
|
return `exp=${exp}&sig=${sig}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
if (topicMessages.ok === false) {
|
if (topicMessages.ok === false) {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
@@ -353,7 +360,7 @@ const formatPartId = (partType: string, existingId: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatToolCallId = (nativeId: string) => {
|
const formatToolCallId = (nativeId: string) => {
|
||||||
return `veridian__tool-${nativeId}-${nanoid()}`;
|
return `veridian__tool-${nativeId.slice(0, 16)}-${nanoid()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const evalPython = async (code: string) => {
|
const evalPython = async (code: string) => {
|
||||||
@@ -528,10 +535,20 @@ const { listDirectoryTool, globTool, readFileTool, readFilesTool, fetchUrlTool,
|
|||||||
content: z.string(),
|
content: z.string(),
|
||||||
}),
|
}),
|
||||||
execute: async ({ url }) => {
|
execute: async ({ url }) => {
|
||||||
const response = await fetch(url);
|
const response = await fetch('https://api.firecrawl.dev/v2/scrape', {
|
||||||
const content = await response.text();
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + process.env.FIRECRAWL_API_KEY,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const content = await response.json();
|
||||||
|
console.log(content);
|
||||||
return {
|
return {
|
||||||
content,
|
content: content.data.markdown,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -629,7 +646,7 @@ async function generateResponse(
|
|||||||
glob: globTool,
|
glob: globTool,
|
||||||
readFile: readFileTool,
|
readFile: readFileTool,
|
||||||
readFiles: readFilesTool,
|
readFiles: readFilesTool,
|
||||||
// fetchUrl: fetchUrlTool,
|
fetchUrl: fetchUrlTool,
|
||||||
python: pythonTool,
|
python: pythonTool,
|
||||||
bash: bashTool,
|
bash: bashTool,
|
||||||
};
|
};
|
||||||
@@ -657,13 +674,14 @@ async function generateResponse(
|
|||||||
const response = streamText({
|
const response = streamText({
|
||||||
model: model.gateway(model.model.externalId),
|
model: model.gateway(model.model.externalId),
|
||||||
messages,
|
messages,
|
||||||
|
allowSystemInMessages: true,
|
||||||
providerOptions: {
|
providerOptions: {
|
||||||
openrouter: {
|
openrouter: {
|
||||||
debug: {
|
debug: {
|
||||||
echo_upstream_body: true,
|
echo_upstream_body: true,
|
||||||
},
|
},
|
||||||
user: userId,
|
user: userId,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
experimental_transform: streamTransoforms,
|
experimental_transform: streamTransoforms,
|
||||||
stopWhen: isLoopFinished(),
|
stopWhen: isLoopFinished(),
|
||||||
@@ -959,7 +977,7 @@ async function generateResponse(
|
|||||||
throw new Error('Failed to insert message part');
|
throw new Error('Failed to insert message part');
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore - doesnt exist on the type but yeah it does now
|
||||||
part.toolCall = toolCall;
|
part.toolCall = toolCall;
|
||||||
|
|
||||||
await topicEvents.emit(topicId, {
|
await topicEvents.emit(topicId, {
|
||||||
@@ -1005,6 +1023,11 @@ async function generateResponse(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.where(eq(toolCalls.id, dbToolCallId));
|
.where(eq(toolCalls.id, dbToolCallId));
|
||||||
|
if (token.providerMetadata) await db.update(messageParts)
|
||||||
|
.set({
|
||||||
|
providerOptions: token.providerMetadata,
|
||||||
|
})
|
||||||
|
.where(eq(messageParts.toolCallId, dbToolCallId));
|
||||||
|
|
||||||
await topicEvents.emit(topicId, {
|
await topicEvents.emit(topicId, {
|
||||||
type: 'tool-call-delta',
|
type: 'tool-call-delta',
|
||||||
@@ -1049,6 +1072,7 @@ async function generateResponse(
|
|||||||
topicId,
|
topicId,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
toolCallId: dbToolCallId,
|
toolCallId: dbToolCallId,
|
||||||
|
providerOptions: token.providerMetadata,
|
||||||
type: 'tool-call',
|
type: 'tool-call',
|
||||||
content: null,
|
content: null,
|
||||||
finished: false,
|
finished: false,
|
||||||
@@ -1060,7 +1084,7 @@ async function generateResponse(
|
|||||||
throw new Error('Failed to insert message part');
|
throw new Error('Failed to insert message part');
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore - doesnt exist on the type but yeah it does now
|
||||||
part.toolCall = toolCall;
|
part.toolCall = toolCall;
|
||||||
|
|
||||||
await topicEvents.emit(topicId, {
|
await topicEvents.emit(topicId, {
|
||||||
@@ -1097,6 +1121,11 @@ async function generateResponse(
|
|||||||
status: 'failed',
|
status: 'failed',
|
||||||
error: { type: ToolCallType.Text, value: 'Tool returned invalid output' }
|
error: { type: ToolCallType.Text, value: 'Tool returned invalid output' }
|
||||||
}).where(eq(toolCalls.id, dbToolCallId));
|
}).where(eq(toolCalls.id, dbToolCallId));
|
||||||
|
if (token.providerMetadata) await db.update(messageParts)
|
||||||
|
.set({
|
||||||
|
providerOptions: token.providerMetadata,
|
||||||
|
})
|
||||||
|
.where(eq(messageParts.toolCallId, dbToolCallId));
|
||||||
|
|
||||||
await topicEvents.emit(topicId, {
|
await topicEvents.emit(topicId, {
|
||||||
type: 'tool-call-delta',
|
type: 'tool-call-delta',
|
||||||
@@ -1125,6 +1154,11 @@ async function generateResponse(
|
|||||||
value: outputValue,
|
value: outputValue,
|
||||||
},
|
},
|
||||||
}).where(eq(toolCalls.id, dbToolCallId));
|
}).where(eq(toolCalls.id, dbToolCallId));
|
||||||
|
if (token.providerMetadata) await db.update(messageParts)
|
||||||
|
.set({
|
||||||
|
providerOptions: token.providerMetadata,
|
||||||
|
})
|
||||||
|
.where(eq(messageParts.toolCallId, dbToolCallId));
|
||||||
|
|
||||||
await topicEvents.emit(topicId, {
|
await topicEvents.emit(topicId, {
|
||||||
type: 'tool-call-delta',
|
type: 'tool-call-delta',
|
||||||
@@ -1176,6 +1210,11 @@ async function generateResponse(
|
|||||||
value: outputValue as string,
|
value: outputValue as string,
|
||||||
}
|
}
|
||||||
}).where(eq(toolCalls.id, existingDbToolCallId));
|
}).where(eq(toolCalls.id, existingDbToolCallId));
|
||||||
|
if (token.providerMetadata) await db.update(messageParts)
|
||||||
|
.set({
|
||||||
|
providerOptions: token.providerMetadata,
|
||||||
|
})
|
||||||
|
.where(eq(messageParts.toolCallId, existingDbToolCallId));
|
||||||
|
|
||||||
await topicEvents.emit(topicId, {
|
await topicEvents.emit(topicId, {
|
||||||
type: 'tool-call-delta',
|
type: 'tool-call-delta',
|
||||||
@@ -1220,6 +1259,7 @@ async function generateResponse(
|
|||||||
topicId,
|
topicId,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
toolCallId: dbToolCallId,
|
toolCallId: dbToolCallId,
|
||||||
|
providerOptions: token.providerMetadata,
|
||||||
type: 'tool-call',
|
type: 'tool-call',
|
||||||
content: null,
|
content: null,
|
||||||
finished: false,
|
finished: false,
|
||||||
@@ -1250,12 +1290,8 @@ async function generateResponse(
|
|||||||
|
|
||||||
case 'finish': {
|
case 'finish': {
|
||||||
let tps;
|
let tps;
|
||||||
if (ttft !== undefined && token.totalUsage.outputTokens !== undefined) {
|
if (requestStart !== undefined && token.totalUsage.outputTokens !== undefined) {
|
||||||
const tokenStreamStart = requestStart! + ttft;
|
const requestDuration = performance.now() - requestStart;
|
||||||
// this is the *real* request duration, excluding the
|
|
||||||
// TTFT
|
|
||||||
const requestDuration = performance.now() - tokenStreamStart;
|
|
||||||
|
|
||||||
tps = token.totalUsage.outputTokens / (requestDuration / 1000);
|
tps = token.totalUsage.outputTokens / (requestDuration / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { modelMessageSchema } from 'ai';
|
import { modelMessageSchema } from 'ai';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
import { attachments, messages } from '~~/drizzle/schema';
|
import { attachments, messageParts, messages } from '~~/drizzle/schema';
|
||||||
import { db } from '~~/server/lib/db';
|
import { db } from '~~/server/lib/db';
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
@@ -9,6 +9,21 @@ export default defineEventHandler(async (event) => {
|
|||||||
const topicId = getRouterParam(event, 'topicId')!;
|
const topicId = getRouterParam(event, 'topicId')!;
|
||||||
const userId = event.context.user!.id;
|
const userId = event.context.user!.id;
|
||||||
|
|
||||||
|
const topic = await db.query.topics.findFirst({
|
||||||
|
where: {
|
||||||
|
id: topicId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!topic) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 404,
|
||||||
|
statusMessage: 'Not Found',
|
||||||
|
message: 'Topic not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const result = await readValidatedBody(event, z.object({
|
const result = await readValidatedBody(event, z.object({
|
||||||
message: z.intersection(
|
message: z.intersection(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -19,6 +34,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
),
|
),
|
||||||
}).safeParse);
|
}).safeParse);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
console.log(result.error);
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: 'Bad Request',
|
statusMessage: 'Bad Request',
|
||||||
@@ -28,6 +44,16 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
const { message } = result.data;
|
const { message } = result.data;
|
||||||
|
|
||||||
|
if (['user', 'assistant'].includes(message.role) === false) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: 'Bad Request',
|
||||||
|
message: 'Invalid role',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message.role) {
|
||||||
|
case 'user': {
|
||||||
await db.insert(messages).values({
|
await db.insert(messages).values({
|
||||||
// @ts-ignore - drizzle bug
|
// @ts-ignore - drizzle bug
|
||||||
id: message.id,
|
id: message.id,
|
||||||
@@ -73,4 +99,55 @@ export default defineEventHandler(async (event) => {
|
|||||||
return {
|
return {
|
||||||
ok: true
|
ok: true
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
case 'assistant': {
|
||||||
|
const [dbmessage] = await db.insert(messages).values({
|
||||||
|
// @ts-ignore - drizzle bug
|
||||||
|
id: message.id,
|
||||||
|
userId,
|
||||||
|
topicId,
|
||||||
|
parentMessageId: null,
|
||||||
|
generationId: null,
|
||||||
|
role: message.role,
|
||||||
|
content: message.content,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
if (!dbmessage) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to insert message',
|
||||||
|
message: 'Failed to insert message',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [part] = await db.insert(messageParts).values({
|
||||||
|
userId,
|
||||||
|
topicId,
|
||||||
|
messageId: dbmessage.id,
|
||||||
|
type: 'text',
|
||||||
|
content: message.content,
|
||||||
|
providerOptions: null,
|
||||||
|
finished: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
lastUpdatedAt: new Date(),
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
// TODO: transaction
|
||||||
|
if (!part) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to insert message part',
|
||||||
|
message: 'Failed to insert message part',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
topicEvents.emit(topicId, { type: 'MESSAGE_CREATED', payload: dbmessage });
|
||||||
|
topicEvents.emit(topicId, { type: 'text-start', payload: { messageId: dbmessage.id, part } });
|
||||||
|
topicEvents.emit(topicId, { type: 'text-end', payload: { messageId: dbmessage.id, partId: part.id, lastUpdatedAt: new Date(), content: message.content } });
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
@@ -39,7 +39,22 @@ export default defineEventHandler(async (event) => {
|
|||||||
responseChecksumValidation: 'WHEN_REQUIRED',
|
responseChecksumValidation: 'WHEN_REQUIRED',
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = `veridian__uploads/${Date.now()}-${file.name.replace(/[^a-zA-Z0-9.]/g, '_')}-${event.context.user!.id}`
|
const fileParts = file.name.split('.');
|
||||||
|
if (fileParts.length < 2) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: 'Invalid file name',
|
||||||
|
data: {
|
||||||
|
code: 'INVALID_FILE_NAME',
|
||||||
|
ok: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileExt = fileParts.pop()!;
|
||||||
|
const fileName = fileParts.join('.').replace(/[^a-zA-Z0-9.]/g, '_');
|
||||||
|
|
||||||
|
const key = `veridian__uploads/${Date.now()}-${fileName}-${event.context.user!.id}.${fileExt}`;
|
||||||
|
|
||||||
const command = new PutObjectCommand({
|
const command = new PutObjectCommand({
|
||||||
ACL: 'public-read',
|
ACL: 'public-read',
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { createHmac, timingSafeEqual } from 'crypto';
|
||||||
|
|
||||||
|
const DEFAULT_EXPIRY_MS = 30 * 60 * 1000; // 30 minutes
|
||||||
|
|
||||||
|
export function generateFileToken(
|
||||||
|
fileKey: string,
|
||||||
|
secret: string,
|
||||||
|
expiresInMs = DEFAULT_EXPIRY_MS,
|
||||||
|
): { exp: number; sig: string } {
|
||||||
|
const exp = Date.now() + expiresInMs;
|
||||||
|
const payload = `${fileKey}:${exp}`;
|
||||||
|
const sig = createHmac('sha256', secret).update(payload).digest('hex');
|
||||||
|
return { exp, sig };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyFileToken(
|
||||||
|
fileKey: string,
|
||||||
|
exp: number,
|
||||||
|
sig: string,
|
||||||
|
secret: string,
|
||||||
|
): boolean {
|
||||||
|
if (Date.now() > exp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = `${fileKey}:${exp}`;
|
||||||
|
const expected = createHmac('sha256', secret).update(payload).digest('hex');
|
||||||
|
|
||||||
|
const sigBuffer = Buffer.from(sig, 'hex');
|
||||||
|
const expectedBuffer = Buffer.from(expected, 'hex');
|
||||||
|
|
||||||
|
if (sigBuffer.length !== expectedBuffer.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timingSafeEqual(sigBuffer, expectedBuffer);
|
||||||
|
}
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { type ToolSet, type TextStreamPart, type ToolCallPart } from 'ai';
|
|
||||||
|
|
||||||
export function createLongcatTransformer<TOOLS extends ToolSet>(): (options: {
|
|
||||||
tools: TOOLS;
|
|
||||||
stopStream: () => void;
|
|
||||||
}) => TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>> {
|
|
||||||
let buffer = '';
|
|
||||||
let hasToolCallInStep = false;
|
|
||||||
let lastChunkId: string | undefined;
|
|
||||||
let lastChunkType: 'text' | 'reasoning' | undefined;
|
|
||||||
let step = 0;
|
|
||||||
|
|
||||||
return (_opts) => {
|
|
||||||
return new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({
|
|
||||||
transform(chunk, controller) {
|
|
||||||
if (chunk.type === 'finish-step' || chunk.type === 'finish') {
|
|
||||||
step++;
|
|
||||||
|
|
||||||
if (hasToolCallInStep) {
|
|
||||||
// We clone the chunk and overwrite the finishReason.
|
|
||||||
// This tricks the SDK into thinking the model requested a tool natively.
|
|
||||||
const modifiedChunk = {
|
|
||||||
...chunk,
|
|
||||||
finishReason: 'tool-calls' as const,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reset for the next potential step
|
|
||||||
if (chunk.type === 'finish-step') {
|
|
||||||
hasToolCallInStep = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.enqueue(modifiedChunk);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunk.type === 'text-start' || chunk.type === 'reasoning-start') {
|
|
||||||
lastChunkId = chunk.id;
|
|
||||||
lastChunkType = chunk.type.split('-')[1] as 'text' | 'reasoning';
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only care about text chunks
|
|
||||||
if (chunk.type !== 'text-delta' && chunk.type !== 'reasoning-delta') {
|
|
||||||
controller.enqueue(chunk);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += chunk.text;
|
|
||||||
|
|
||||||
// Check if we have a full tool call in the buffer
|
|
||||||
const pattern = /<longcat_tool_call>([\s\S]*?)<\/longcat_tool_call>/g;
|
|
||||||
let lastIndex = 0;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = pattern.exec(buffer)) !== null) {
|
|
||||||
console.log("longcat tool call found at index", match.index);
|
|
||||||
|
|
||||||
// 1. Enqueue any text that appeared BEFORE the tool call
|
|
||||||
const textBefore = buffer.substring(lastIndex, match.index);
|
|
||||||
if (textBefore) {
|
|
||||||
controller.enqueue({ type: chunk.type, text: textBefore, id: lastChunkId ?? chunk.type.includes('reasoning') ? `reasoning-${step}` : `text-${step}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Parse the XML content
|
|
||||||
const content = match[1]!.trim();
|
|
||||||
const toolNameMatch = content.match(/^([^\s<]+)/);
|
|
||||||
|
|
||||||
if (toolNameMatch) {
|
|
||||||
hasToolCallInStep = true;
|
|
||||||
|
|
||||||
const toolName = toolNameMatch[1];
|
|
||||||
const args: Record<string, any> = {};
|
|
||||||
const argRegex = /<longcat_arg_key>(.*?)<\/longcat_arg_key>\s*<longcat_arg_value>(.*?)<\/longcat_arg_value>/gs;
|
|
||||||
|
|
||||||
let argMatch;
|
|
||||||
while ((argMatch = argRegex.exec(content)) !== null) {
|
|
||||||
args[argMatch[1]!.trim()] = argMatch[2]!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. EMIT A TOOL CALL PART
|
|
||||||
// This is the "magic" - the SDK will see this and act as if the LLM
|
|
||||||
// called a native tool.
|
|
||||||
const toolCallId = `lc-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
|
|
||||||
controller.enqueue({
|
|
||||||
type: 'tool-call',
|
|
||||||
// @ts-ignore
|
|
||||||
id: toolCallId,
|
|
||||||
toolCallId,
|
|
||||||
toolName,
|
|
||||||
input: args,
|
|
||||||
dynamic: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = pattern.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep the remaining buffer (unclosed tags) for the next chunk
|
|
||||||
buffer = buffer.substring(lastIndex);
|
|
||||||
|
|
||||||
// If there's no open tag starting, we can flush the buffer as text
|
|
||||||
if (!buffer.includes('<longcat_tool_call>')) {
|
|
||||||
if (buffer) {
|
|
||||||
controller.enqueue({ type: chunk.type, text: buffer, id: lastChunkId ?? chunk.type.includes('reasoning') ? `reasoning-${step}` : `text-${step}` });
|
|
||||||
buffer = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
flush(controller) {
|
|
||||||
if (buffer && lastChunkId && lastChunkType) {
|
|
||||||
controller.enqueue({ type: `${lastChunkType}-delta`, text: buffer, id: lastChunkId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Ok({
|
return Ok({
|
||||||
gateway: createOpenAI({ name: 'ClosedRouter', apiKey, baseURL }),
|
gateway: createOpenAI({
|
||||||
|
name: 'ClosedRouter',
|
||||||
|
apiKey,
|
||||||
|
baseURL
|
||||||
|
}),
|
||||||
streamTransformer: undefined,
|
streamTransformer: undefined,
|
||||||
textTransformer: undefined,
|
textTransformer: undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
+80
-39
@@ -1,4 +1,4 @@
|
|||||||
import type { FilePart, ImagePart, ModelMessage } from "ai";
|
import type { AssistantContent, FilePart, ImagePart, JSONValue, ModelMessage, ToolContent } from "ai";
|
||||||
import { ToolCallType } from "~~/drizzle/schema";
|
import { ToolCallType } from "~~/drizzle/schema";
|
||||||
import { Err, Ok, type Result } from "~~/types/result";
|
import { Err, Ok, type Result } from "~~/types/result";
|
||||||
import type { Message, MessageEntity } from "~/composables/useChat";
|
import type { Message, MessageEntity } from "~/composables/useChat";
|
||||||
@@ -37,7 +37,11 @@ export const buildFocusedMessageTree = (messages: Readonly<Message[]>): MessageE
|
|||||||
return focusedMessageTree;
|
return focusedMessageTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const marshallMessages = (agent: Agent, messages: Readonly<MessageEntity[]>): Result<ModelMessage[], string> => {
|
export interface MarshallOptions {
|
||||||
|
signFileUrl?: (fileKey: string) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const marshallMessages = (agent: Agent, messages: Readonly<MessageEntity[]>, opts?: MarshallOptions): Result<ModelMessage[], string> => {
|
||||||
const marshalledMessages: ModelMessage[] = [];
|
const marshalledMessages: ModelMessage[] = [];
|
||||||
|
|
||||||
if (agent && agent.systemPrompt) {
|
if (agent && agent.systemPrompt) {
|
||||||
@@ -51,47 +55,72 @@ export const marshallMessages = (agent: Agent, messages: Readonly<MessageEntity[
|
|||||||
switch (message.role) {
|
switch (message.role) {
|
||||||
case 'user': {
|
case 'user': {
|
||||||
const attachments = message.attachments.map(attachment => {
|
const attachments = message.attachments.map(attachment => {
|
||||||
const url = resolveFileUrl(attachment.file.url);
|
let url = resolveFileUrl(attachment.file.url);
|
||||||
|
|
||||||
|
if (opts?.signFileUrl && attachment.file.url.startsWith('/api/files/')) {
|
||||||
|
const fileKey = attachment.file.url.slice('/api/files/'.length);
|
||||||
|
const token = opts.signFileUrl(fileKey);
|
||||||
|
url = `${url}?${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (attachment.file.mimeType.startsWith('image/')) {
|
if (attachment.file.mimeType.startsWith('image/')) {
|
||||||
return {
|
return {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
image: url,
|
image: new URL(url),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
data: url,
|
data: new URL(url),
|
||||||
filename: attachment.file.name,
|
filename: attachment.file.name,
|
||||||
mediaType: attachment.file.mimeType,
|
mediaType: attachment.file.mimeType,
|
||||||
};
|
};
|
||||||
}) as (FilePart | ImagePart)[];
|
}) as (FilePart | ImagePart)[];
|
||||||
|
|
||||||
let messageDate = new Date(message.createdAt);
|
let messageDate = new Date(message.createdAt);
|
||||||
|
const prompt = `[${messageDate.toDateString()} ${messageDate.toLocaleTimeString()}]: ${message.content!}`;
|
||||||
|
|
||||||
marshalledMessages.push({
|
marshalledMessages.push({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: [
|
content: attachments ? [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: `[${messageDate.toDateString()} ${messageDate.toLocaleTimeString()}]: ${message.content!}`
|
text: `[${messageDate.toDateString()} ${messageDate.toLocaleTimeString()}]: ${message.content!}`
|
||||||
},
|
},
|
||||||
...attachments,
|
...attachments,
|
||||||
],
|
] : prompt,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'assistant':
|
case 'assistant':
|
||||||
|
let assistantPart: AssistantContent = [];
|
||||||
|
let toolParts: ToolContent = [];
|
||||||
|
|
||||||
for (const part of (message.parts || [])) {
|
for (const part of (message.parts || [])) {
|
||||||
if (!part) return Err('Part is undefined');
|
if (!part) return Err('Part is undefined');
|
||||||
|
|
||||||
switch (part.type) {
|
switch (part.type) {
|
||||||
case 'text':
|
case 'text':
|
||||||
case 'reasoning': {
|
case 'reasoning': {
|
||||||
|
if (toolParts.length > 0) {
|
||||||
marshalledMessages.push({
|
marshalledMessages.push({
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: part.content!,
|
content: assistantPart,
|
||||||
});
|
});
|
||||||
|
marshalledMessages.push({
|
||||||
|
role: 'tool',
|
||||||
|
content: toolParts,
|
||||||
|
});
|
||||||
|
toolParts = [];
|
||||||
|
assistantPart = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.providerOptions || part.content) assistantPart.push({
|
||||||
|
type: part.type,
|
||||||
|
text: part.content || '',
|
||||||
|
providerOptions: part.providerOptions ? part.providerOptions as Record<string, any> : undefined,
|
||||||
|
})
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'tool-call': {
|
case 'tool-call': {
|
||||||
@@ -101,33 +130,32 @@ export const marshallMessages = (agent: Agent, messages: Readonly<MessageEntity[
|
|||||||
return Err('Marshalling tool call that is still pending. This is likely a UI bug if this happens.');
|
return Err('Marshalling tool call that is still pending. This is likely a UI bug if this happens.');
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputValue: string = '';
|
let inputValue: string | object = '';
|
||||||
|
|
||||||
switch (part.toolCall.input!.type) {
|
switch (part.toolCall.input!.type) {
|
||||||
case ToolCallType.Text:
|
case ToolCallType.Text:
|
||||||
inputValue = part.toolCall.input!.value;
|
inputValue = part.toolCall.input!.value;
|
||||||
break;
|
break;
|
||||||
case ToolCallType.Json:
|
case ToolCallType.Json:
|
||||||
inputValue = JSON.stringify(part.toolCall.input!.value);
|
if (typeof part.toolCall.input!.value === 'string') {
|
||||||
|
inputValue = JSON.parse(part.toolCall.input!.value);
|
||||||
|
} else {
|
||||||
|
inputValue = part.toolCall.input!.value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
marshalledMessages.push({
|
assistantPart.push({
|
||||||
role: 'assistant',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'tool-call',
|
type: 'tool-call',
|
||||||
toolCallId: part.toolCall.id,
|
toolCallId: part.toolCall.id!,
|
||||||
toolName: part.toolCall.toolName,
|
toolName: part.toolCall.toolName!,
|
||||||
input: inputValue,
|
input: inputValue,
|
||||||
},
|
|
||||||
],
|
|
||||||
providerOptions: part.providerOptions ? part.providerOptions as Record<string, any> : undefined,
|
providerOptions: part.providerOptions ? part.providerOptions as Record<string, any> : undefined,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (part.toolCall.status === 'failed') {
|
if (part.toolCall.status === 'failed') {
|
||||||
let failureType: 'error-text' | 'error-json';
|
let failureType: 'error-text' | 'error-json';
|
||||||
let failureValue: string;
|
let failureValue: string | JSONValue;
|
||||||
|
|
||||||
if (part.toolCall.error === null || part.toolCall.error === undefined) {
|
if (part.toolCall.error === null || part.toolCall.error === undefined) {
|
||||||
failureType = 'error-text';
|
failureType = 'error-text';
|
||||||
@@ -140,27 +168,32 @@ export const marshallMessages = (agent: Agent, messages: Readonly<MessageEntity[
|
|||||||
break;
|
break;
|
||||||
case ToolCallType.Json:
|
case ToolCallType.Json:
|
||||||
failureType = 'error-json';
|
failureType = 'error-json';
|
||||||
failureValue = JSON.stringify(part.toolCall.error!.value);
|
if (typeof part.toolCall.error!.value === 'string') {
|
||||||
|
failureValue = JSON.parse(part.toolCall.error!.value);
|
||||||
|
} else {
|
||||||
|
failureValue = part.toolCall.error!.value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
failureType = 'error-json';
|
failureType = 'error-json';
|
||||||
failureValue = JSON.stringify(part.toolCall.error!.value);
|
// failureValue = JSON.stringify(part.toolCall.error!.value);
|
||||||
|
if (typeof part.toolCall.error!.value === 'string') {
|
||||||
|
failureValue = JSON.parse(part.toolCall.error!.value);
|
||||||
|
} else {
|
||||||
|
failureValue = part.toolCall.error!.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
marshalledMessages.push({
|
toolParts.push({
|
||||||
role: 'tool',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'tool-result',
|
type: 'tool-result',
|
||||||
toolCallId: part.toolCall.id,
|
toolCallId: part.toolCall.id!,
|
||||||
toolName: part.toolCall.toolName,
|
toolName: part.toolCall.toolName!,
|
||||||
|
// @ts-expect-error - This is a type error, because typescript cant provie that the value must be a string when the type is error-text
|
||||||
output: {
|
output: {
|
||||||
type: failureType,
|
type: failureType,
|
||||||
value: failureValue,
|
value: failureValue,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
providerOptions: part.providerOptions ? part.providerOptions as Record<string, any> : undefined,
|
providerOptions: part.providerOptions ? part.providerOptions as Record<string, any> : undefined,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -177,23 +210,22 @@ export const marshallMessages = (agent: Agent, messages: Readonly<MessageEntity[
|
|||||||
break;
|
break;
|
||||||
case ToolCallType.Json:
|
case ToolCallType.Json:
|
||||||
outputType = 'json';
|
outputType = 'json';
|
||||||
outputValue = JSON.stringify(part.toolCall.output!.value);
|
if (typeof part.toolCall.output!.value === 'string') {
|
||||||
|
outputValue = JSON.parse(part.toolCall.output!.value);
|
||||||
|
} else {
|
||||||
|
outputValue = part.toolCall.output!.value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
marshalledMessages.push({
|
toolParts.push({
|
||||||
role: 'tool',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'tool-result',
|
type: 'tool-result',
|
||||||
toolCallId: part.toolCall.id,
|
toolCallId: part.toolCall.id!,
|
||||||
toolName: part.toolCall.toolName,
|
toolName: part.toolCall.toolName!,
|
||||||
output: {
|
output: {
|
||||||
type: outputType,
|
type: outputType,
|
||||||
value: outputValue,
|
value: outputValue,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
providerOptions: part.providerOptions ? part.providerOptions as Record<string, any> : undefined,
|
providerOptions: part.providerOptions ? part.providerOptions as Record<string, any> : undefined,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -203,6 +235,15 @@ export const marshallMessages = (agent: Agent, messages: Readonly<MessageEntity[
|
|||||||
return Err(`Unknown part type: ${part.type}`);
|
return Err(`Unknown part type: ${part.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (assistantPart.length > 0) marshalledMessages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: assistantPart,
|
||||||
|
});
|
||||||
|
if (toolParts.length > 0) marshalledMessages.push({
|
||||||
|
role: 'tool',
|
||||||
|
content: toolParts,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return Err(`Unknown message role: ${message.role}`);
|
return Err(`Unknown message role: ${message.role}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user