diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/web/src/app/dashboard/settings/embedding-settings.tsx | 64 | ||||
| -rw-r--r-- | packages/web/src/app/dashboard/settings/page.tsx | 9 | ||||
| -rw-r--r-- | packages/web/src/server/api/routers/memory.ts | 44 |
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 }; + }), }); |