Self-hosted knowledge management for SMEs: a split-screen Markdown editor whose sections an LLM refines while you write, and RAG question answering over the documents that result. FastAPI + Postgres/pgvector on the back, SvelteKit on the front, everything OpenAI-compatible and self-hostable. Squashed into a single commit; the development history stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
45 lines
1.3 KiB
Svelte
45 lines
1.3 KiB
Svelte
<script lang="ts">
|
|
import { Tabs } from 'bits-ui';
|
|
import type { Snippet } from 'svelte';
|
|
|
|
type Tab = { value: string; label: string };
|
|
|
|
type Props = {
|
|
tabs: Tab[];
|
|
value?: string;
|
|
/** Rendered once per tab; receives that tab's value. */
|
|
panel: Snippet<[string]>;
|
|
class?: string;
|
|
};
|
|
|
|
let {
|
|
tabs,
|
|
value = $bindable(tabs[0]?.value ?? ''),
|
|
panel,
|
|
class: className = ''
|
|
}: Props = $props();
|
|
</script>
|
|
|
|
<Tabs.Root bind:value class="flex min-h-0 flex-col {className}">
|
|
<Tabs.List class="flex gap-1 rounded-md bg-surface-sunken p-1">
|
|
{#each tabs as tab (tab.value)}
|
|
<Tabs.Trigger
|
|
value={tab.value}
|
|
data-testid="tab-{tab.value}"
|
|
class="flex-1 cursor-pointer rounded-md px-3 py-1.5 text-sm font-medium text-ink-muted transition-colors data-[state=active]:bg-surface-raised data-[state=active]:text-ink"
|
|
>
|
|
{tab.label}
|
|
</Tabs.Trigger>
|
|
{/each}
|
|
</Tabs.List>
|
|
{#each tabs as tab (tab.value)}
|
|
<Tabs.Content value={tab.value} class="mt-2 min-h-0 flex-1">
|
|
<!-- Only the open tab is rendered. Bits keeps inactive panels mounted
|
|
(just hidden), which for a page of independent panels means every
|
|
one of them fetches its data on arrival and hidden controls sit in
|
|
the DOM where a click can never reach them. -->
|
|
{#if tab.value === value}{@render panel(tab.value)}{/if}
|
|
</Tabs.Content>
|
|
{/each}
|
|
</Tabs.Root>
|