From e0beeab2bcf6edd4681912e5cddf2d9ee7eb041d Mon Sep 17 00:00:00 2001
From: Fuwn
Date: Wed, 4 Feb 2026 01:11:12 -0800
Subject: feat(web): Add embedding settings with re-embed functionality
---
.../app/dashboard/settings/embedding-settings.tsx | 64 ++++++++++++++++++++++
packages/web/src/app/dashboard/settings/page.tsx | 9 +++
packages/web/src/server/api/routers/memory.ts | 44 ++++++++++++++-
3 files changed, 115 insertions(+), 2 deletions(-)
create mode 100644 packages/web/src/app/dashboard/settings/embedding-settings.tsx
diff --git a/packages/web/src/app/dashboard/settings/embedding-settings.tsx b/packages/web/src/app/dashboard/settings/embedding-settings.tsx
new file mode 100644
index 0000000..eefd98a
--- /dev/null
+++ b/packages/web/src/app/dashboard/settings/embedding-settings.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import { useState } from "react";
+import { api } from "~/trpc/react";
+
+export function EmbeddingSettings() {
+ const [isReembedding, setIsReembedding] = useState(false);
+ const [result, setResult] = useState<{ total: number; updated: number } | null>(null);
+ const embeddingInfoQuery = api.memory.embeddingInfo.useQuery();
+ const reembedMutation = api.memory.reembed.useMutation({
+ onMutate: () => {
+ setIsReembedding(true);
+ setResult(null);
+ },
+ onSettled: () => {
+ setIsReembedding(false);
+ },
+ onSuccess: (data) => {
+ setResult(data);
+ },
+ });
+ const handleReembed = () => {
+ if (confirm("This will re-index all your memories for search. Continue?")) {
+ reembedMutation.mutate();
+ }
+ };
+ const isAvailable = embeddingInfoQuery.data?.available ?? false;
+
+ return (
+
+
search indexing
+
+ re-index all memories to improve search results.
+
+
+ {result && (
+
+ indexed {result.updated} of {result.total} memories
+
+ )}
+
+ {reembedMutation.error && (
+
+ {reembedMutation.error.message}
+
+ )}
+
+ {!isAvailable && !embeddingInfoQuery.isLoading && (
+
+ search requires server configuration.
+
+ )}
+
+
+
+ );
+}
diff --git a/packages/web/src/app/dashboard/settings/page.tsx b/packages/web/src/app/dashboard/settings/page.tsx
index 7319a72..458c3d8 100644
--- a/packages/web/src/app/dashboard/settings/page.tsx
+++ b/packages/web/src/app/dashboard/settings/page.tsx
@@ -2,6 +2,7 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import { getUser } from "~/server/auth";
import { ApiKeySettings } from "./api-key-settings";
+import { EmbeddingSettings } from "./embedding-settings";
export default async function SettingsPage() {
const user = await getUser();
@@ -32,6 +33,14 @@ export default async function SettingsPage() {
+
+
+ embeddings
+
+ manage memory embeddings for semantic search.
+
+
+
);
diff --git a/packages/web/src/server/api/routers/memory.ts b/packages/web/src/server/api/routers/memory.ts
index eebe0f8..d523b4e 100644
--- a/packages/web/src/server/api/routers/memory.ts
+++ b/packages/web/src/server/api/routers/memory.ts
@@ -179,8 +179,7 @@ export const memoryRouter = createTRPCRouter({
if (!embeddingService) {
throw new TRPCError({
code: "PRECONDITION_FAILED",
- message:
- "Search is not available. Configure OPENAI_API_KEY to enable semantic search.",
+ message: "Search requires OPENAI_API_KEY to be configured.",
});
}
@@ -194,6 +193,7 @@ export const memoryRouter = createTRPCRouter({
const searchResults = await memoryStore.search(queryEmbedding, {
projectId: input.projectId,
limit: input.limit,
+ threshold: 0.3,
});
const projectsResult = await projectStore.list();
const projects = projectsResult.success ? projectsResult.value : [];
@@ -205,4 +205,44 @@ export const memoryRouter = createTRPCRouter({
return { results: resultsWithProjects, projects };
}),
+
+ embeddingInfo: protectedProcedure.query(() => {
+ const embeddingService = getEmbeddingService();
+
+ return { available: embeddingService !== null };
+ }),
+
+ reembed: protectedProcedure.mutation(async ({ ctx: context }) => {
+ const embeddingService = getEmbeddingService();
+
+ if (!embeddingService) {
+ throw new TRPCError({
+ code: "PRECONDITION_FAILED",
+ message: "Search is not configured on this server.",
+ });
+ }
+
+ const supabaseClient = await createClient();
+ const memoryStore = new SupabaseStore(supabaseClient, context.user.id);
+ const memories = await memoryStore.list();
+ let updated = 0;
+
+ for (const memory of memories) {
+ const embedding = await embeddingService.generate(memory.content);
+ const { error } = await supabaseClient
+ .from("memories")
+ .update({
+ embedding: JSON.stringify(embedding),
+ embedding_dimensions: embeddingService.dimensions,
+ })
+ .eq("id", memory.id)
+ .eq("user_id", context.user.id);
+
+ if (!error) {
+ updated++;
+ }
+ }
+
+ return { total: memories.length, updated };
+ }),
});
--
cgit v1.2.3