aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-04 01:11:12 -0800
committerFuwn <[email protected]>2026-02-04 01:29:40 -0800
commite0beeab2bcf6edd4681912e5cddf2d9ee7eb041d (patch)
tree588b8abb71b91aef5e6e7691319468b513929a7d
parentfeat(web): Enhance memory dashboard with projects and search (diff)
downloadarchived-imemio-e0beeab2bcf6edd4681912e5cddf2d9ee7eb041d.tar.xz
archived-imemio-e0beeab2bcf6edd4681912e5cddf2d9ee7eb041d.zip
feat(web): Add embedding settings with re-embed functionality
-rw-r--r--packages/web/src/app/dashboard/settings/embedding-settings.tsx64
-rw-r--r--packages/web/src/app/dashboard/settings/page.tsx9
-rw-r--r--packages/web/src/server/api/routers/memory.ts44
3 files changed, 115 insertions, 2 deletions
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 (
+ <div className="border border-[#2a2a2a] bg-[#0f0f0f] p-4">
+ <p className="mb-2 text-white">search indexing</p>
+ <p className="mb-4 text-sm text-[#666666]">
+ re-index all memories to improve search results.
+ </p>
+
+ {result && (
+ <p className="mb-4 text-sm text-[#66b366]">
+ indexed {result.updated} of {result.total} memories
+ </p>
+ )}
+
+ {reembedMutation.error && (
+ <p className="mb-4 text-sm text-[#b36666]">
+ {reembedMutation.error.message}
+ </p>
+ )}
+
+ {!isAvailable && !embeddingInfoQuery.isLoading && (
+ <p className="mb-4 text-sm text-[#996666]">
+ search requires server configuration.
+ </p>
+ )}
+
+ <button
+ className="border border-[#2a2a2a] bg-[#0f0f0f] px-4 py-2 text-white transition hover:border-[#666666] disabled:text-[#666666] disabled:hover:border-[#2a2a2a]"
+ disabled={isReembedding || !isAvailable}
+ onClick={handleReembed}
+ type="button"
+ >
+ {isReembedding ? "indexing ..." : "re-index all"}
+ </button>
+ </div>
+ );
+}
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() {
</p>
<ApiKeySettings />
</section>
+
+ <section className="w-full">
+ <h2 className="mb-3 text-sm text-[#666666]">embeddings</h2>
+ <p className="mb-4 text-sm text-[#666666]">
+ manage memory embeddings for semantic search.
+ </p>
+ <EmbeddingSettings />
+ </section>
</div>
</main>
);
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 };
+ }),
});