improve mobile
This commit is contained in:
parent
c2cc437061
commit
b468afdc8c
10 changed files with 78 additions and 31 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently -n \"convex,vite\" -c \"blue.bold,green.bold\" \"convex dev\" \"vite dev\"",
|
"dev": "concurrently -n \"convex,vite\" -c \"blue.bold,green.bold\" \"convex dev\" \"vite dev\"",
|
||||||
|
"dev:host": "concurrently -n \"convex,vite\" -c \"blue.bold,green.bold\" \"convex dev\" \"vite dev --host\"",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"prepare": "svelte-kit sync || echo ''",
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
|
|
||||||
29
src/app.css
29
src/app.css
|
|
@ -54,7 +54,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.2409 0.0201 307.5346);
|
--background: oklch(0.2409 0.0201 267.5346);
|
||||||
--foreground: oklch(0.8398 0.0387 309.5391);
|
--foreground: oklch(0.8398 0.0387 309.5391);
|
||||||
--card: oklch(0.2803 0.0232 307.5413);
|
--card: oklch(0.2803 0.0232 307.5413);
|
||||||
--card-foreground: oklch(0.8456 0.0302 341.4597);
|
--card-foreground: oklch(0.8456 0.0302 341.4597);
|
||||||
|
|
@ -211,13 +211,38 @@
|
||||||
--shadow-2xl: var(--shadow-2xl);
|
--shadow-2xl: var(--shadow-2xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@utility fill-device {
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh; /* Dynamic viewport height - newer browsers */
|
||||||
|
|
||||||
|
/* Alternative: Use env() for safe areas */
|
||||||
|
min-height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom));
|
||||||
|
|
||||||
|
/* Ensure full width */
|
||||||
|
width: 100vw;
|
||||||
|
width: 100dvw; /* Dynamic viewport width */
|
||||||
|
|
||||||
|
/* Remove default margins/padding */
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility fill-device-height {
|
||||||
|
height: 100svh;
|
||||||
|
|
||||||
|
/* min-height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom)); */
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground bg-noise;
|
@apply bg-background text-foreground bg-noise fill-device;
|
||||||
|
position: fixed;
|
||||||
|
overflow: clip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content"
|
||||||
|
/>
|
||||||
<title>thom.chat</title>
|
<title>thom.chat</title>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,6 @@
|
||||||
let { class: className, children, ...rest }: HTMLAttributes<HTMLDivElement> = $props();
|
let { class: className, children, ...rest }: HTMLAttributes<HTMLDivElement> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div {...rest} class={cn('bg-background bg-noise col-start-2 h-screen', className)}>
|
<div {...rest} class={cn('bg-background bg-noise fill-device-height col-start-2', className)}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<div
|
<div
|
||||||
{...rest}
|
{...rest}
|
||||||
class={cn(
|
class={cn(
|
||||||
'bg-sidebar border-sidebar-border col-start-1 h-screen w-[--sidebar-width] border-r',
|
'bg-sidebar border-sidebar-border fill-device-height col-start-1 w-[--sidebar-width] border-r',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@
|
||||||
import { callGenerateMessage } from '../api/generate-message/call.js';
|
import { callGenerateMessage } from '../api/generate-message/call.js';
|
||||||
import ModelPicker from './model-picker.svelte';
|
import ModelPicker from './model-picker.svelte';
|
||||||
import SearchModal from './search-modal.svelte';
|
import SearchModal from './search-modal.svelte';
|
||||||
|
import { shortcut } from '$lib/actions/shortcut.svelte.js';
|
||||||
|
import { mergeAttrs } from 'melt';
|
||||||
|
|
||||||
const client = useConvexClient();
|
const client = useConvexClient();
|
||||||
|
|
||||||
|
|
@ -378,9 +380,13 @@
|
||||||
<title>Chat | thom.chat</title>
|
<title>Chat | thom.chat</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
<svelte:window
|
||||||
|
use:shortcut={[{ ctrl: true, key: 'd', callback: () => scrollState.scrollToBottom() }]}
|
||||||
|
/>
|
||||||
|
|
||||||
<Sidebar.Root
|
<Sidebar.Root
|
||||||
bind:open={sidebarOpen}
|
bind:open={sidebarOpen}
|
||||||
class="h-screen overflow-clip"
|
class="fill-device-height overflow-clip"
|
||||||
{...currentModelSupportsImages ? omit(fileUpload.dropzone, ['onclick']) : {}}
|
{...currentModelSupportsImages ? omit(fileUpload.dropzone, ['onclick']) : {}}
|
||||||
>
|
>
|
||||||
<AppSidebar bind:searchModalOpen />
|
<AppSidebar bind:searchModalOpen />
|
||||||
|
|
@ -443,7 +449,7 @@
|
||||||
<LightSwitch variant="ghost" class="size-8" />
|
<LightSwitch variant="ghost" class="size-8" />
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div bind:this={conversationList} class="h-screen overflow-y-auto">
|
<div bind:this={conversationList} class="fill-device-height overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
class={cn('mx-auto flex max-w-3xl flex-col', {
|
class={cn('mx-auto flex max-w-3xl flex-col', {
|
||||||
'pt-10': page.url.pathname !== '/chat',
|
'pt-10': page.url.pathname !== '/chat',
|
||||||
|
|
@ -452,19 +458,26 @@
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Tooltip placement="top">
|
||||||
onclick={() => scrollState.scrollToBottom()}
|
{#snippet trigger(tooltip)}
|
||||||
variant="secondary"
|
<Button
|
||||||
size="sm"
|
onclick={() => scrollState.scrollToBottom()}
|
||||||
class={[
|
variant="secondary"
|
||||||
'text-muted-foreground !border-border absolute bottom-0 left-1/2 z-10 -translate-x-1/2 rounded-full !border !pl-3 text-xs transition',
|
size="sm"
|
||||||
notAtBottom.current ? 'opacity-100' : 'pointer-events-none scale-95 opacity-0',
|
class={[
|
||||||
]}
|
'text-muted-foreground !border-border absolute bottom-0 left-1/2 z-10 -translate-x-1/2 rounded-full !border !pl-3 text-xs transition',
|
||||||
style="bottom: {wrapperSize.height + 5}px;"
|
notAtBottom.current ? 'opacity-100' : 'pointer-events-none scale-95 opacity-0',
|
||||||
>
|
]}
|
||||||
Scroll to bottom
|
{...mergeAttrs(tooltip.trigger, {
|
||||||
<ChevronDownIcon class="inline" />
|
style: `bottom: ${wrapperSize.height + 5}px;`,
|
||||||
</Button>
|
})}
|
||||||
|
>
|
||||||
|
Scroll to bottom
|
||||||
|
<ChevronDownIcon class="inline" />
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
{cmdOrCtrl} + D
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -572,10 +585,10 @@
|
||||||
{...pick(popover.trigger, ['id', 'style', 'onfocusout', 'onfocus'])}
|
{...pick(popover.trigger, ['id', 'style', 'onfocusout', 'onfocus'])}
|
||||||
bind:this={textarea}
|
bind:this={textarea}
|
||||||
disabled={textareaDisabled}
|
disabled={textareaDisabled}
|
||||||
class="text-foreground placeholder:text-muted-foreground/60 max-h-64 min-h-[80px] w-full resize-none !overflow-y-auto bg-transparent px-3 text-base leading-6 outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
class="text-foreground placeholder:text-muted-foreground/60 max-h-64 min-h-[60px] w-full resize-none !overflow-y-auto bg-transparent px-3 text-base leading-6 outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-[80px]"
|
||||||
placeholder={isGenerating
|
placeholder={isGenerating
|
||||||
? 'Generating response...'
|
? 'Generating response...'
|
||||||
: 'Type your message here... Tag rules with @'}
|
: 'Type your message here, tag rules with @'}
|
||||||
name="message"
|
name="message"
|
||||||
onkeydown={(e) => {
|
onkeydown={(e) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey && !popover.open) {
|
if (e.key === 'Enter' && !e.shiftKey && !popover.open) {
|
||||||
|
|
@ -634,23 +647,23 @@
|
||||||
{isGenerating ? 'Stop generation' : 'Send message'}
|
{isGenerating ? 'Stop generation' : 'Send message'}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-2 pr-2 sm:flex-row sm:items-center">
|
<div class="flex flex-row flex-wrap items-center gap-2 pr-2">
|
||||||
<ModelPicker onlyImageModels={selectedImages.length > 0} />
|
<ModelPicker onlyImageModels={selectedImages.length > 0} />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={cn(
|
class={cn(
|
||||||
'border-border flex items-center gap-1 rounded-full border px-2 py-1 text-xs transition-colors',
|
'border-border flex items-center gap-1 rounded-full border px-1 py-1 text-xs transition-colors sm:px-2',
|
||||||
settings.webSearchEnabled ? 'bg-accent/50' : 'hover:bg-accent/20'
|
settings.webSearchEnabled ? 'bg-accent/50' : 'hover:bg-accent/20'
|
||||||
)}
|
)}
|
||||||
onclick={() => (settings.webSearchEnabled = !settings.webSearchEnabled)}
|
onclick={() => (settings.webSearchEnabled = !settings.webSearchEnabled)}
|
||||||
>
|
>
|
||||||
<SearchIcon class="!size-3" />
|
<SearchIcon class="!size-3" />
|
||||||
<span class="whitespace-nowrap">Web search</span>
|
<span class="hidden whitespace-nowrap sm:inline">Web search</span>
|
||||||
</button>
|
</button>
|
||||||
{#if currentModelSupportsImages}
|
{#if currentModelSupportsImages}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="border-border hover:bg-accent/20 flex items-center gap-1 rounded-full border px-2 py-1 text-xs transition-colors disabled:opacity-50"
|
class="border-border hover:bg-accent/20 flex items-center gap-1 rounded-full border px-1 py-1 text-xs transition-colors disabled:opacity-50 sm:px-2"
|
||||||
onclick={() => fileInput?.click()}
|
onclick={() => fileInput?.click()}
|
||||||
disabled={isUploading}
|
disabled={isUploading}
|
||||||
>
|
>
|
||||||
|
|
@ -661,7 +674,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<ImageIcon class="!size-3" />
|
<ImageIcon class="!size-3" />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="whitespace-nowrap">Attach image</span>
|
<span class="hidden whitespace-nowrap sm:inline">Attach image</span>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@
|
||||||
<button
|
<button
|
||||||
{...popover.trigger}
|
{...popover.trigger}
|
||||||
class={cn(
|
class={cn(
|
||||||
'ring-offset-background focus:ring-ring flex w-full items-center justify-between rounded-lg px-2 py-1 text-xs transition hover:text-white focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
'ring-offset-background focus:ring-ring flex items-center justify-between rounded-lg px-2 py-1 text-xs transition hover:text-white focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window use:shortcut={{ ctrl: true, key: 'k', callback: () => (open = true) }} />
|
<svelte:window use:shortcut={[{ ctrl: true, key: 'k', callback: () => (open = true) }]} />
|
||||||
|
|
||||||
<Modal bind:open>
|
<Modal bind:open>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else if search.data?.length}
|
{:else if search.data?.length}
|
||||||
<div class="max-h-96 space-y-2 overflow-y-auto">
|
<div class="max-h-96 space-y-2 overflow-y-auto">
|
||||||
{#each search.data as { conversation, messages, score, titleMatch }, index}
|
{#each search.data as { conversation, messages, titleMatch }, index}
|
||||||
<div
|
<div
|
||||||
data-result-index={index}
|
data-result-index={index}
|
||||||
class="border-border flex cursor-pointer items-center justify-between gap-2 rounded-lg border px-3 py-2 transition-colors {index ===
|
class="border-border flex cursor-pointer items-center justify-between gap-2 rounded-lg border px-3 py-2 transition-colors {index ===
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
<meta name="description" content="A shared conversation from thom.chat" />
|
<meta name="description" content="A shared conversation from thom.chat" />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="min-h-screen">
|
<div class="fill-device-height">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header
|
<header
|
||||||
class="border-border bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 border-b backdrop-blur"
|
class="border-border bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 border-b backdrop-blur"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import Icons from 'unplugin-icons/vite';
|
import Icons from 'unplugin-icons/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
|
|
@ -37,4 +39,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
allowedHosts: isDev ? true : undefined,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue