differing mobile layout

This commit is contained in:
Aidan Bleser 2025-06-18 06:02:42 -05:00
parent 7c080575dd
commit 5de0a961ac
11 changed files with 117 additions and 64 deletions

View file

@ -89,11 +89,15 @@ export const createAndAddMessage = mutation({
content: v.string(), content: v.string(),
role: messageRoleValidator, role: messageRoleValidator,
session_token: v.string(), session_token: v.string(),
images: v.optional(v.array(v.object({ images: v.optional(
url: v.string(), v.array(
storage_id: v.string(), v.object({
fileName: v.optional(v.string()), url: v.string(),
}))), storage_id: v.string(),
fileName: v.optional(v.string()),
})
)
),
}, },
handler: async ( handler: async (
ctx, ctx,

View file

@ -41,11 +41,15 @@ export const create = mutation({
provider: v.optional(providerValidator), provider: v.optional(providerValidator),
token_count: v.optional(v.number()), token_count: v.optional(v.number()),
// Optional image attachments // Optional image attachments
images: v.optional(v.array(v.object({ images: v.optional(
url: v.string(), v.array(
storage_id: v.string(), v.object({
fileName: v.optional(v.string()), url: v.string(),
}))), storage_id: v.string(),
fileName: v.optional(v.string()),
})
)
),
}, },
handler: async (ctx, args): Promise<Id<'messages'>> => { handler: async (ctx, args): Promise<Id<'messages'>> => {
const session = await ctx.runQuery(api.betterAuth.publicGetSession, { const session = await ctx.runQuery(api.betterAuth.publicGetSession, {

View file

@ -55,4 +55,3 @@ export const deleteFile = mutation({
await ctx.storage.delete(args.storage_id); await ctx.storage.delete(args.storage_id);
}, },
}); });

View file

@ -1 +1 @@
export { default as ImageModal } from './image-modal.svelte'; export { default as ImageModal } from './image-modal.svelte';

View file

@ -70,4 +70,4 @@ export function compressImage(file: File, maxSizeBytes: number = 1024 * 1024): P
img.src = URL.createObjectURL(file); img.src = URL.createObjectURL(file);
}); });
} }

View file

@ -6,4 +6,4 @@ export function supportsImages(model: OpenRouterModel): boolean {
export function getImageSupportedModels(models: OpenRouterModel[]): OpenRouterModel[] { export function getImageSupportedModels(models: OpenRouterModel[]): OpenRouterModel[] {
return models.filter(supportsImages); return models.filter(supportsImages);
} }

View file

@ -52,7 +52,7 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex place-items-center gap-2"> <div class="flex place-items-center gap-2">
<Card.Title>{model.name}</Card.Title> <Card.Title>{model.name}</Card.Title>
<span class="text-muted-foreground text-xs hidden xl:block">{model.id}</span> <span class="text-muted-foreground hidden text-xs xl:block">{model.id}</span>
</div> </div>
<Switch bind:value={() => enabled, toggleEnabled} {disabled} /> <Switch bind:value={() => enabled, toggleEnabled} {disabled} />
</div> </div>

View file

@ -85,4 +85,4 @@ export const POST: RequestHandler = async ({ request }) => {
} }
return response({ ok: true, cancelled }); return response({ ok: true, cancelled });
}; };

View file

@ -14,4 +14,4 @@ export async function callCancelGeneration(args: CancelGenerationRequestBody) {
).map((r) => r.json() as Promise<CancelGenerationResponse>); ).map((r) => r.json() as Promise<CancelGenerationResponse>);
return res; return res;
} }

View file

@ -1,2 +1,2 @@
// Global cache for AbortControllers keyed by conversation ID // Global cache for AbortControllers keyed by conversation ID
export const generationAbortControllers = new Map<string, AbortController>(); export const generationAbortControllers = new Map<string, AbortController>();

View file

@ -111,7 +111,7 @@
return 'other'; return 'other';
} }
let search = $state('') let search = $state('');
const filteredModels = $derived( const filteredModels = $derived(
fuzzysearch({ fuzzysearch({
@ -197,7 +197,7 @@
}; };
} }
const isMobile = new IsMobile() const isMobile = new IsMobile();
</script> </script>
{#if enabledArr.length === 0} {#if enabledArr.length === 0}
@ -230,8 +230,14 @@
{...popover.content} {...popover.content}
class="border-border bg-popover mt-1 max-h-200 min-w-80 flex-col overflow-hidden rounded-xl border p-0 backdrop-blur-sm data-[open]:flex" class="border-border bg-popover mt-1 max-h-200 min-w-80 flex-col overflow-hidden rounded-xl border p-0 backdrop-blur-sm data-[open]:flex"
> >
<Command.Root shouldFilter={false} class="flex h-full flex-col overflow-hidden w-[572px]" columns={4}> <Command.Root
<label class="group/label relative flex items-center border-b border-border gap-2 px-4 py-3 text-sm"> shouldFilter={false}
class="flex h-full flex-col overflow-hidden md:w-[572px]"
columns={isMobile.current ? undefined : 4}
>
<label
class="group/label border-border relative flex items-center gap-2 border-b px-4 py-3 text-sm"
>
<SearchIcon class="text-muted-foreground" /> <SearchIcon class="text-muted-foreground" />
<Command.Input <Command.Input
class="w-full outline-none" class="w-full outline-none"
@ -247,61 +253,101 @@
}} }}
/> />
</label> </label>
<Command.List class="overflow-y-auto h-[430px]"> <Command.List class="h-[300px] overflow-y-auto md:h-[430px]">
<Command.Viewport> <Command.Viewport>
<Command.Empty class="text-muted-foreground p-4 text-sm h-[120px] flex items-center justify-center"> <Command.Empty
class="text-muted-foreground flex items-center justify-center p-4 text-sm md:h-[120px]"
>
No models available. Enable some models in the account settings. No models available. Enable some models in the account settings.
</Command.Empty> </Command.Empty>
{#each groupedModels as [company, models] (company)} {#each groupedModels as [company, models] (company)}
<Command.Group class="space-y-2"> <Command.Group class="space-y-2">
<Command.GroupHeading <Command.GroupHeading
class="text-heading/75 scroll-m-[180px] flex items-center gap-2 px-3 pt-3 pb-1 text-xs font-semibold tracking-wide capitalize" class="text-heading/75 flex items-center gap-2 px-3 pt-3 pb-1 text-xs font-semibold tracking-wide capitalize md:scroll-m-[180px]"
> >
{company} {company}
</Command.GroupHeading> </Command.GroupHeading>
<Command.GroupItems class="grid grid-cols-4 gap-3 px-3 pb-3"> <Command.GroupItems
class="flex flex-col gap-2 px-3 pb-3 md:grid md:grid-cols-4 md:gap-3"
>
{#each models as model (model._id)} {#each models as model (model._id)}
{@const isSelected = settings.modelId === model.model_id} {@const isSelected = settings.modelId === model.model_id}
{@const formatted = formatModelName(model.model_id)} {@const formatted = formatModelName(model.model_id)}
<Command.Item {#if isMobile.current}
value={model.model_id} <Command.Item
onSelect={() => selectModel(model.model_id)} value={model.model_id}
class={cn( onSelect={() => selectModel(model.model_id)}
'border-border flex h-40 w-32 flex-col items-center justify-center rounded-lg border p-2', class={cn(
'relative select-none', 'border-border flex h-10 items-center justify-between rounded-lg border p-2',
'data-selected:bg-accent/50 data-selected:text-accent-foreground', 'relative scroll-m-2 select-none',
isSelected && 'border-reflect border-none', 'data-selected:bg-accent/50 data-selected:text-accent-foreground',
isSelected && 'border-reflect border-none'
)} )}
> >
{#if getModelIcon(model.model_id)} <div class="flex items-center gap-2">
{@const ModelIcon = getModelIcon(model.model_id)} {#if getModelIcon(model.model_id)}
<ModelIcon class="size-6 shrink-0" /> {@const ModelIcon = getModelIcon(model.model_id)}
{/if} <ModelIcon class="size-6 shrink-0" />
<p class="font-fake-proxima mt-2 text-center leading-tight font-bold"> {/if}
{formatted.primary} <p class="font-fake-proxima text-center leading-tight font-bold">
</p> {formatted.full}
<p class="mt-0 text-center text-xs leading-tight font-medium"> </p>
{formatted.secondary} </div>
</p>
{@const openRouterModel = modelsState {@const openRouterModel = modelsState
.from(Provider.OpenRouter) .from(Provider.OpenRouter)
.find((m) => m.id === model.model_id)} .find((m) => m.id === model.model_id)}
{#if openRouterModel && supportsImages(openRouterModel)} {#if openRouterModel && supportsImages(openRouterModel)}
<Tooltip> <Tooltip>
{#snippet trigger(tooltip)} {#snippet trigger(tooltip)}
<div <div class="" {...tooltip.trigger}>
class="abs-x-center text-muted-foreground absolute bottom-3 flex items-center gap-1 text-xs" <EyeIcon class="size-3" />
{...tooltip.trigger} </div>
> {/snippet}
<EyeIcon class="size-3" /> Supports image anaylsis
</div> </Tooltip>
{/snippet} {/if}
Supports image anaylsis </Command.Item>
</Tooltip> {:else}
{/if} <Command.Item
</Command.Item> value={model.model_id}
onSelect={() => selectModel(model.model_id)}
class={cn(
'border-border flex h-40 w-32 flex-col items-center justify-center rounded-lg border p-2',
'relative select-none',
'data-selected:bg-accent/50 data-selected:text-accent-foreground',
isSelected && 'border-reflect border-none'
)}
>
{#if getModelIcon(model.model_id)}
{@const ModelIcon = getModelIcon(model.model_id)}
<ModelIcon class="size-6 shrink-0" />
{/if}
<p class="font-fake-proxima mt-2 text-center leading-tight font-bold">
{formatted.primary}
</p>
<p class="mt-0 text-center text-xs leading-tight font-medium">
{formatted.secondary}
</p>
{@const openRouterModel = modelsState
.from(Provider.OpenRouter)
.find((m) => m.id === model.model_id)}
{#if openRouterModel && supportsImages(openRouterModel)}
<Tooltip>
{#snippet trigger(tooltip)}
<div
class="abs-x-center text-muted-foreground absolute bottom-3 flex items-center gap-1 text-xs"
{...tooltip.trigger}
>
<EyeIcon class="size-3" />
</div>
{/snippet}
Supports image anaylsis
</Tooltip>
{/if}
</Command.Item>
{/if}
{/each} {/each}
</Command.GroupItems> </Command.GroupItems>
</Command.Group> </Command.Group>