From 90d93786a8dfe29ceeef48cc9d3397ed672bc142 Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Mon, 23 Mar 2026 09:07:06 +0100 Subject: [PATCH] feat(TRUEREF-0015): implement web UI repository dashboard - Repository list with state badges, stats, and action buttons - Add repository modal for GitHub URLs and local paths - Live indexing progress bar polling every 2s - Confirm dialog for destructive actions - Repository detail page with versions and recent jobs - Settings page placeholder Co-Authored-By: Claude Sonnet 4.6 --- src/lib/components/AddRepositoryModal.svelte | 162 +++++++++ src/lib/components/ConfirmDialog.svelte | 61 ++++ src/lib/components/IndexingProgress.svelte | 62 ++++ src/lib/components/RepositoryCard.svelte | 89 +++++ src/lib/components/StatBadge.svelte | 23 ++ src/routes/+layout.svelte | 41 ++- src/routes/+page.server.ts | 7 + src/routes/+page.svelte | 181 ++++++++++- src/routes/repos/[id]/+page.server.ts | 26 ++ src/routes/repos/[id]/+page.svelte | 281 ++++++++++++++++ src/routes/settings/+page.svelte | 325 +++++++++++++++++++ 11 files changed, 1254 insertions(+), 4 deletions(-) create mode 100644 src/lib/components/AddRepositoryModal.svelte create mode 100644 src/lib/components/ConfirmDialog.svelte create mode 100644 src/lib/components/IndexingProgress.svelte create mode 100644 src/lib/components/RepositoryCard.svelte create mode 100644 src/lib/components/StatBadge.svelte create mode 100644 src/routes/+page.server.ts create mode 100644 src/routes/repos/[id]/+page.server.ts create mode 100644 src/routes/repos/[id]/+page.svelte create mode 100644 src/routes/settings/+page.svelte diff --git a/src/lib/components/AddRepositoryModal.svelte b/src/lib/components/AddRepositoryModal.svelte new file mode 100644 index 0000000..51a18ac --- /dev/null +++ b/src/lib/components/AddRepositoryModal.svelte @@ -0,0 +1,162 @@ + + + + + diff --git a/src/lib/components/ConfirmDialog.svelte b/src/lib/components/ConfirmDialog.svelte new file mode 100644 index 0000000..2424db8 --- /dev/null +++ b/src/lib/components/ConfirmDialog.svelte @@ -0,0 +1,61 @@ + + + + + diff --git a/src/lib/components/IndexingProgress.svelte b/src/lib/components/IndexingProgress.svelte new file mode 100644 index 0000000..9d43e15 --- /dev/null +++ b/src/lib/components/IndexingProgress.svelte @@ -0,0 +1,62 @@ + + +{#if job} +
+
+ {processedFiles} / {totalFiles} files + {progress}% +
+
+
+
+ {#if job.status === 'done'} +

Indexing complete.

+ {:else if job.status === 'failed'} +

{job.error ?? 'Indexing failed.'}

+ {/if} +
+{/if} diff --git a/src/lib/components/RepositoryCard.svelte b/src/lib/components/RepositoryCard.svelte new file mode 100644 index 0000000..d6b63a9 --- /dev/null +++ b/src/lib/components/RepositoryCard.svelte @@ -0,0 +1,89 @@ + + +
+
+
+

{repo.title}

+

{repo.id}

+
+ + {stateLabels[repo.state] ?? repo.state} + +
+ + {#if repo.description} +

{repo.description}

+ {/if} + +
+ {totalSnippets.toLocaleString()} snippets + · + Trust: {trustScore.toFixed(1)}/10 + {#if repo.stars} + · + ★ {repo.stars.toLocaleString()} + {/if} + {#if repo.lastIndexedAt} + · + Last indexed {new Date(repo.lastIndexedAt).toLocaleDateString()} + {/if} +
+ + {#if repo.state === 'error'} +

Indexing failed. Check jobs for details.

+ {/if} + +
+ + + Details + + +
+
diff --git a/src/lib/components/StatBadge.svelte b/src/lib/components/StatBadge.svelte new file mode 100644 index 0000000..8bbf0ad --- /dev/null +++ b/src/lib/components/StatBadge.svelte @@ -0,0 +1,23 @@ + + +
+ {value} + {label} +
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 0d8eb03..fac0e46 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,5 +5,42 @@ let { children } = $props(); - -{@render children()} + + + TrueRef + + +
+ + +
+ {@render children()} +
+
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..bc015d3 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,7 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch }) => { + const res = await fetch('/api/v1/libs'); + const data = await res.json(); + return { repositories: data.libraries ?? [] }; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc88df0..e3151b0 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,179 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + + + Repositories — TrueRef + + +
+
+

Repositories

+

+ {repositories.length} + {repositories.length === 1 ? 'repository' : 'repositories'} indexed +

+
+ +
+ +{#if errorMessage} +
+

{errorMessage}

+
+{/if} + +{#if repositories.length === 0} +
+ + + +

No repositories yet

+

+ Add your first GitHub or local repository to start indexing documentation for AI-powered + retrieval. +

+ +
+{:else} +
+ {#each repositories as repo (repo.id)} +
+ + {#if activeJobIds[repo.id]} +
+ +
+ {/if} +
+ {/each} +
+{/if} + +{#if showAddModal} + (showAddModal = false)} onAdded={handleRepoAdded} /> +{/if} + +{#if confirmDeleteId && confirmDeleteRepo} + +{/if} diff --git a/src/routes/repos/[id]/+page.server.ts b/src/routes/repos/[id]/+page.server.ts new file mode 100644 index 0000000..2517398 --- /dev/null +++ b/src/routes/repos/[id]/+page.server.ts @@ -0,0 +1,26 @@ +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; + +export const load: PageServerLoad = async ({ fetch, params }) => { + const id = params.id; + const res = await fetch(`/api/v1/libs/${encodeURIComponent(id)}`); + + if (res.status === 404) { + error(404, 'Repository not found'); + } + + if (!res.ok) { + error(res.status, 'Failed to load repository'); + } + + const repo = await res.json(); + + // Fetch recent jobs + const jobsRes = await fetch(`/api/v1/jobs?repositoryId=${encodeURIComponent(id)}&limit=5`); + const jobsData = jobsRes.ok ? await jobsRes.json() : { jobs: [] }; + + return { + repo, + recentJobs: jobsData.jobs ?? [] + }; +}; diff --git a/src/routes/repos/[id]/+page.svelte b/src/routes/repos/[id]/+page.svelte new file mode 100644 index 0000000..86e6891 --- /dev/null +++ b/src/routes/repos/[id]/+page.svelte @@ -0,0 +1,281 @@ + + + + {repo.title} — TrueRef + + + + +
+
+
+

{repo.title}

+ + {stateLabels[repo.state] ?? repo.state} + +
+

{repo.id}

+ {#if repo.description} +

{repo.description}

+ {/if} + {#if repo.sourceUrl} + {#if repo.source === 'github'} + + {repo.sourceUrl} + + {:else} +

{repo.sourceUrl}

+ {/if} + {/if} +
+ +
+ + +
+
+ +{#if errorMessage} +
+

{errorMessage}

+
+{/if} + +{#if successMessage} +
+

{successMessage}

+
+{/if} + +{#if activeJobId} +
+

Indexing in progress

+ +
+{:else if repo.state === 'error'} +
+

Last indexing run failed. Trigger re-index to retry.

+
+{/if} + + +
+ + + + {#if repo.stars != null} + + {/if} +
+ + +
+

Repository Info

+
+
+
Source
+
{repo.source}
+
+
+
Branch
+
{repo.branch ?? 'main'}
+
+
+
Last Indexed
+
{formatDate(repo.lastIndexedAt)}
+
+
+
Created
+
{formatDate(repo.createdAt)}
+
+
+
+ + +{#if versions.length > 0} +
+

Indexed Versions

+
+ {#each versions as version (version.id)} +
+
+ {version.tag} + {#if version.title} + {version.title} + {/if} +
+
+ + {stateLabels[version.state] ?? version.state} + + {#if version.indexedAt} + {formatDate(version.indexedAt)} + {/if} +
+
+ {/each} +
+
+{/if} + + +{#if recentJobs.length > 0} +
+

Recent Jobs

+
+ {#each recentJobs as job (job.id)} +
+
+ {job.id.slice(0, 8)}... + {#if job.error} +

{job.error}

+ {/if} +
+
+ + {job.status} + + {#if job.completedAt} + {formatDate(job.completedAt)} + {:else if job.startedAt} + {formatDate(job.startedAt)} + {/if} +
+
+ {/each} +
+
+{/if} + +{#if showDeleteConfirm} + (showDeleteConfirm = false)} + /> +{/if} diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte new file mode 100644 index 0000000..658b872 --- /dev/null +++ b/src/routes/settings/+page.svelte @@ -0,0 +1,325 @@ + + + + Settings — TrueRef + + +
+

Settings

+

Configure TrueRef embedding and indexing options

+
+ + +
+

Embedding Provider

+

+ Embeddings enable semantic search. Without them, only keyword search (FTS5) is used. +

+ + {#if loading} +

Loading current configuration…

+ {:else} + +
+ {#each ['none', 'openai', 'local'] as p} + + {/each} +
+ + + {#if provider === 'none'} +
+ Search will use keyword matching only. Results may be less relevant for complex questions. +
+ {/if} + + + {#if provider === 'openai'} +
+ +
+ {#each PROVIDER_PRESETS as preset} + + {/each} +
+ + + + + + + + + + +
+ + + {#if testStatus === 'ok'} + + Connection successful + {#if testDimensions}— {testDimensions} dimensions{/if} + + {:else if testStatus === 'error'} + + {testError} + + {/if} +
+
+ {/if} + + + {#if provider === 'local'} +
+

Local ONNX model via @xenova/transformers

+

Model: Xenova/all-MiniLM-L6-v2 · 384 dimensions

+ {#if localAvailable === null} +

Checking availability…

+ {:else if localAvailable} +

@xenova/transformers is installed and ready.

+ {:else} +

+ @xenova/transformers is not installed. Run + npm install @xenova/transformers + to enable local embeddings. +

+ {/if} +
+ {/if} + + +
+ {#if saveStatus === 'ok'} + Settings saved. + {:else if saveStatus === 'error'} + {saveError} + {/if} + + +
+ {/if} +
+ + +
+

About TrueRef

+

+ Self-hosted documentation intelligence platform — a full-stack clone of context7. +

+
+ + + +
+