refactor: un-hardcode file urls

This commit is contained in:
Zoe
2026-05-12 10:28:33 -05:00
parent 9550d44220
commit 47009b1f0a
11 changed files with 2790 additions and 15 deletions
+7 -4
View File
@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import ImageViewer from '~/components/ImageViewer.vue'; import ImageViewer from '~/components/ImageViewer.vue';
import { resolveFileUrl } from '~~/utils/url';
const props = defineProps<{ const props = defineProps<{
file: { file: {
@@ -17,6 +18,8 @@ const isVideo = computed(() => props.file.mimeType.startsWith('video/'));
const isAudio = computed(() => props.file.mimeType.startsWith('audio/')); const isAudio = computed(() => props.file.mimeType.startsWith('audio/'));
const isPdf = computed(() => props.file.mimeType === 'application/pdf'); const isPdf = computed(() => props.file.mimeType === 'application/pdf');
const resolvedUrl = computed(() => resolveFileUrl(props.file.url));
const imageViewerOpen = ref(false); const imageViewerOpen = ref(false);
const imageRef = ref<HTMLImageElement | null>(null); const imageRef = ref<HTMLImageElement | null>(null);
const imageViewerOriginRect = ref<DOMRect | null>(null); const imageViewerOriginRect = ref<DOMRect | null>(null);
@@ -45,16 +48,16 @@ function openImageViewer() {
<template> <template>
<div class="relative h-full w-fit flex"> <div class="relative h-full w-fit flex">
<div v-if="isImage"> <div v-if="isImage">
<img ref="imageRef" :src="props.file.url" <img ref="imageRef" :src="resolvedUrl"
class="rounded-lg h-full w-full max-h-36 max-w-64 object-cover cursor-zoom-in hover:opacity-90 transition-opacity" class="rounded-lg h-full w-full max-h-36 max-w-64 object-cover cursor-zoom-in hover:opacity-90 transition-opacity"
@click="openImageViewer" /> @click="openImageViewer" />
<ImageViewer v-if="imageViewerOpen" :src="props.file.url" :alt="props.file.name" <ImageViewer v-if="imageViewerOpen" :src="resolvedUrl" :alt="props.file.name"
:origin-rect="imageViewerOriginRect" :origin-element="imageRef" :origin-rect="imageViewerOriginRect" :origin-element="imageRef"
@close="imageViewerOpen = false" /> @close="imageViewerOpen = false" />
</div> </div>
<video v-else-if="isVideo" controls :src="props.file.url" <video v-else-if="isVideo" controls :src="resolvedUrl"
class="rounded-lg h-full w-full max-h-36 max-w-64 object-cover" /> class="rounded-lg h-full w-full max-h-36 max-w-64 object-cover" />
<audio v-else-if="isAudio" controls :src="props.file.url" class="rounded-lg h-full w-full max-h-36 max-w-64" /> <audio v-else-if="isAudio" controls :src="resolvedUrl" class="rounded-lg h-full w-full max-h-36 max-w-64" />
<div v-else class="flex items-center gap-2 p-2 rounded-lg bg-[var(--color-hover)] min-w-40 max-w-48"> <div v-else class="flex items-center gap-2 p-2 rounded-lg bg-[var(--color-hover)] min-w-40 max-w-48">
<span :class="[fileIcon, 'text-6 text-[var(--color-accent)]']"></span> <span :class="[fileIcon, 'text-6 text-[var(--color-accent)]']"></span>
+4 -4
View File
@@ -42,7 +42,7 @@ watch(() => message.children.length, (newCount, oldCount) => {
} }
if (oldCount > newCount) { if (oldCount > newCount) {
const activeChild = message.children.find(c => c.id === message.activeChildId); const activeChild = message.children.find(c => c?.id === message.activeChildId);
if (!activeChild) { if (!activeChild) {
emit('patch', { activeChildId: message.children[newCount - 1]?.id ?? null }); emit('patch', { activeChildId: message.children[newCount - 1]?.id ?? null });
} }
@@ -57,7 +57,7 @@ watch(() => message.children.length, (newCount, oldCount) => {
const activeMessage = computed(() => { const activeMessage = computed(() => {
if (message.activeChildId && message.children.length > 0) { if (message.activeChildId && message.children.length > 0) {
const child = message.children.find(c => c.id === message.activeChildId); const child = message.children.find(c => c?.id === message.activeChildId);
if (child) return child; if (child) return child;
} }
@@ -140,7 +140,7 @@ const messageCount = computed(() => {
const currentChildIndex = computed(() => { const currentChildIndex = computed(() => {
if (!message.activeChildId) return 0; if (!message.activeChildId) return 0;
const idx = message.children.findIndex(c => c.id === message.activeChildId); const idx = message.children.findIndex(c => c?.id === message.activeChildId);
return idx >= 0 ? idx + 1 : 0; return idx >= 0 ? idx + 1 : 0;
}); });
@@ -150,7 +150,7 @@ const navigateChild = (direction: -1 | 1) => {
: [null, ...message.children]; : [null, ...message.children];
const currentIdx = allItems.findIndex(item => const currentIdx = allItems.findIndex(item =>
item === null ? !message.activeChildId : item.id === message.activeChildId !item ? !message.activeChildId : item.id === message.activeChildId
); );
const newIdx = Math.max(0, Math.min(allItems.length - 1, currentIdx + direction)); const newIdx = Math.max(0, Math.min(allItems.length - 1, currentIdx + direction));
+3 -2
View File
@@ -1,5 +1,6 @@
import { attempt } from "~~/types/result"; import { attempt } from "~~/types/result";
import * as schema from '~~/drizzle/schema'; import * as schema from '~~/drizzle/schema';
import { resolveFileUrl } from '~~/utils/url';
export type ResourceFile = typeof schema.files.$inferSelect; export type ResourceFile = typeof schema.files.$inferSelect;
@@ -118,12 +119,12 @@ export const useResources = async () => {
}; };
const copyLink = async (file: ResourceFile) => { const copyLink = async (file: ResourceFile) => {
const url = file.url; const url = resolveFileUrl(file.url);
await navigator.clipboard.writeText(url); await navigator.clipboard.writeText(url);
}; };
const downloadFile = (file: ResourceFile) => { const downloadFile = (file: ResourceFile) => {
const url = file.url; const url = resolveFileUrl(file.url);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = file.name; a.download = file.name;
+2 -1
View File
@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useFloating, offset, flip, shift, autoUpdate, hide } from '@floating-ui/vue'; import { useFloating, offset, flip, shift, autoUpdate, hide } from '@floating-ui/vue';
import { resolveFileUrl } from '~~/utils/url';
const { open: sidebarOpen, openSidebar } = useSidebar(); const { open: sidebarOpen, openSidebar } = useSidebar();
const { const {
@@ -88,7 +89,7 @@ const handleRowClick = (e: MouseEvent) => {
if (file && isImage(file.mimeType)) { if (file && isImage(file.mimeType)) {
const row = trigger.closest('[data-file-id]') as HTMLElement; const row = trigger.closest('[data-file-id]') as HTMLElement;
previewOrigin.value = row?.getBoundingClientRect() ?? null; previewOrigin.value = row?.getBoundingClientRect() ?? null;
previewSrc.value = file.url; previewSrc.value = resolveFileUrl(file.url);
} }
break; break;
} }
-1
View File
@@ -1,6 +1,5 @@
{ {
"lockfileVersion": 1, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "veridian", "name": "veridian",
@@ -0,0 +1,4 @@
-- Custom SQL migration file, put your code below! --
UPDATE files
SET url = regexp_replace(url, '^https?://[^/]+', '')
WHERE url ~ '^https?://[^/]+';
File diff suppressed because it is too large Load Diff
+1
View File
@@ -169,6 +169,7 @@ export default defineNuxtConfig({
public: { public: {
disableSignup: process.env.DISABLE_SIGNUP === 'true' || process.env.DISABLE_SIGNUP === '1', disableSignup: process.env.DISABLE_SIGNUP === 'true' || process.env.DISABLE_SIGNUP === '1',
publicUrl: process.env.NUXT_PUBLIC_URL ?? 'http://localhost:3000',
} }
}, },
+1 -1
View File
@@ -56,7 +56,7 @@ export default defineEventHandler(async (event) => {
return { return {
url, url,
assetUrl: `${process.env.NUXT_PUBLIC_URL}/api/files/${key}`, assetUrl: `/api/files/${key}`,
}; };
} catch (error) { } catch (error) {
console.error('Failed to generate presigned URL:', error); console.error('Failed to generate presigned URL:', error);
+5 -2
View File
@@ -3,6 +3,7 @@ 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";
import type { Agent } from "~/composables/useAgents"; import type { Agent } from "~/composables/useAgents";
import { resolveFileUrl } from "~~/utils/url";
export const buildMessageTree = (flatMessages: MessageEntity[]) => { export const buildMessageTree = (flatMessages: MessageEntity[]) => {
const messagesMap = new Map(flatMessages.map(m => [m.id, { ...m, children: [] as MessageEntity[] }])); const messagesMap = new Map(flatMessages.map(m => [m.id, { ...m, children: [] as MessageEntity[] }]));
@@ -50,16 +51,18 @@ 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);
if (attachment.file.mimeType.startsWith('image/')) { if (attachment.file.mimeType.startsWith('image/')) {
return { return {
type: 'image', type: 'image',
image: attachment.file.url, image: url,
}; };
} }
return { return {
type: 'file', type: 'file',
data: attachment.file.url, data: url,
filename: attachment.file.name, filename: attachment.file.name,
mediaType: attachment.file.mimeType, mediaType: attachment.file.mimeType,
}; };
+7
View File
@@ -0,0 +1,7 @@
export const resolveFileUrl = (path: string): string => {
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('blob:') || path.startsWith('data:')) {
return path;
}
const config = useRuntimeConfig();
return `${config.public.publicUrl}${path}`;
};