feat: 1-to-1 message render + web data-lake backend

This commit is contained in:
hh
2026-05-31 01:45:05 +02:00
parent f0afb7ec5b
commit 75425d1bee
110 changed files with 10199 additions and 54 deletions
+40 -2
View File
@@ -1,9 +1,47 @@
<script lang="ts">
import "./layout.css";
import favicon from "$lib/assets/favicon.svg";
import "$lib/styles/reboot.css";
import "$lib/styles/icons.css";
import "$lib/styles/global.scss";
import { goto } from "$app/navigation";
import { page } from "$app/state";
import ToastHost from "$lib/components/ui/ToastHost.svelte";
import { auth } from "$lib/stores/auth.svelte";
import { theme } from "$lib/stores/theme.svelte";
let { children } = $props();
let previousTheme: string | null = null;
let themeTimer: ReturnType<typeof setTimeout> | null = null;
$effect(() => theme.watchSystem());
$effect(() => {
const next = theme.resolved;
const root = document.documentElement;
if (previousTheme !== null && previousTheme !== next) {
root.classList.add("theme-transition");
if (themeTimer) {
clearTimeout(themeTimer);
}
themeTimer = setTimeout(
() => root.classList.remove("theme-transition"),
300
);
}
previousTheme = next;
root.classList.toggle("theme-dark", next === "dark");
});
$effect(() => {
const onLogin = page.url.pathname === "/login";
if (!(auth.isAuthenticated || onLogin)) {
goto("/login");
} else if (auth.isAuthenticated && onLogin) {
goto("/app");
}
});
</script>
<svelte:head><link rel="icon" href={favicon}></svelte:head>
{@render children()}
<ToastHost />
+2
View File
@@ -0,0 +1,2 @@
export const ssr = false;
export const prerender = false;
+8 -5
View File
@@ -1,5 +1,8 @@
<h1>Welcome to SvelteKit</h1>
<p>
Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read
the documentation
</p>
<script lang="ts">
import { goto } from "$app/navigation";
import { auth } from "$lib/stores/auth.svelte";
$effect(() => {
goto(auth.isAuthenticated ? "/app" : "/login", { replaceState: true });
});
</script>
+86
View File
@@ -0,0 +1,86 @@
<script lang="ts">
import AccountSwitcher from "$lib/components/AccountSwitcher.svelte";
import ChatList from "$lib/components/ChatList.svelte";
import FolderTabs from "$lib/components/FolderTabs.svelte";
import Button from "$lib/components/ui/Button.svelte";
import Icon from "$lib/components/ui/Icon.svelte";
import { accounts } from "$lib/stores/accounts.svelte";
import { theme } from "$lib/stores/theme.svelte";
import { toasts } from "$lib/stores/toasts.svelte";
let { children } = $props();
$effect(() => {
if (!accounts.loaded) {
accounts.load().catch(() => toasts.error("Failed to load accounts"));
}
});
</script>
<div id="Main">
<div id="LeftColumn">
<header class="left-header">
<AccountSwitcher />
<Button
variant="translucent"
round
smaller
onclick={() => theme.toggle()}
aria-label="Toggle theme"
>
<Icon name={theme.resolved === "dark" ? "sun" : "moon"} />
</Button>
</header>
<div class="folder-tabs">
<FolderTabs />
</div>
<ChatList />
</div>
<div id="MiddleColumn">
{@render children()}
</div>
</div>
<style lang="scss">
#Main {
display: grid;
grid-template-columns: minmax(16rem, 26.5rem) 1fr;
grid-template-rows: 100%;
overflow: hidden;
height: 100%;
}
#LeftColumn {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
border-right: 1px solid var(--color-borders);
background-color: var(--color-background);
}
.left-header {
display: flex;
align-items: center;
gap: 0.5rem;
height: var(--header-height);
padding: 0 0.625rem;
border-bottom: 1px solid var(--color-borders);
}
.folder-tabs {
flex-shrink: 0;
padding: 0.25rem 0.5rem 0;
}
#MiddleColumn {
position: relative;
overflow: hidden;
height: 100%;
background-color: var(--color-background-secondary);
}
</style>
+23
View File
@@ -0,0 +1,23 @@
<div class="placeholder">
<span class="pill">Select a chat to view its archive</span>
</div>
<style lang="scss">
.placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.pill {
padding: 0.4375rem 1rem;
border-radius: 1rem;
font-size: 0.875rem;
color: var(--color-white);
background-color: var(--color-default-shadow);
backdrop-filter: blur(8px);
}
</style>
@@ -0,0 +1,38 @@
<script lang="ts">
import { page } from "$app/state";
import ChatHeader from "$lib/components/ChatHeader.svelte";
import MessageList from "$lib/components/MessageList.svelte";
import { accounts } from "$lib/stores/accounts.svelte";
import { chats } from "$lib/stores/chats.svelte";
const chatId = $derived(Number(page.params.chatId));
$effect(() => {
if (accounts.selectedId === null) {
return;
}
chats.enrich(chatId);
});
</script>
<div class="chat-view">
{#key chatId}
<ChatHeader {chatId} />
<div class="chat-body">
<MessageList {chatId} />
</div>
{/key}
</div>
<style lang="scss">
.chat-view {
display: flex;
flex-direction: column;
height: 100%;
}
.chat-body {
flex: 1;
min-height: 0;
}
</style>
+5
View File
@@ -0,0 +1,5 @@
<script lang="ts">
import LoginScreen from "$lib/components/LoginScreen.svelte";
</script>
<LoginScreen />