diff --git a/apps/web/app/(dashboard)/dashboard/documents/page.tsx b/apps/web/app/(dashboard)/dashboard/documents/page.tsx index 26e0abb..fee0337 100644 --- a/apps/web/app/(dashboard)/dashboard/documents/page.tsx +++ b/apps/web/app/(dashboard)/dashboard/documents/page.tsx @@ -319,7 +319,13 @@ export default function DocumentsPage() { handleDelete(doc.doc_id)} + onSelect={(e) => { + // Defer past Radix's menu-close/focus-restore + // so window.confirm() isn't suppressed (the + // reason delete appeared to do nothing). + e.preventDefault(); + setTimeout(() => handleDelete(doc.doc_id), 0); + }} disabled={deletingId === doc.doc_id} className="text-destructive focus:text-destructive" > diff --git a/apps/web/app/api/dashboard/playground/grounding/route.ts b/apps/web/app/api/dashboard/playground/grounding/route.ts index 768aafe..19e4166 100644 --- a/apps/web/app/api/dashboard/playground/grounding/route.ts +++ b/apps/web/app/api/dashboard/playground/grounding/route.ts @@ -15,11 +15,29 @@ interface GroundingBody { citations: CitationIn[]; } -/** Collapse whitespace + lowercase for a tolerant substring match. */ +/** Collapse whitespace + lowercase for a tolerant match. */ function norm(s: string): string { return s.replace(/\s+/g, " ").trim().toLowerCase(); } +/** + * Is the quote grounded in the source? LLM-extracted spans are rarely + * byte-exact (smart quotes, ellipses, trimmed edges), so a strict + * substring check yields false negatives. We accept an exact substring + * OR a high token-overlap: ≥80% of the quote's words (len>2) appear in + * the source. + */ +function isGrounded(quote: string, source: string): boolean { + const hay = norm(source); + if (!hay) return false; + const needle = norm(quote); + if (hay.includes(needle)) return true; + const words = needle.split(" ").filter((w) => w.length > 2); + if (words.length === 0) return false; + const hit = words.filter((w) => hay.includes(w)).length; + return hit / words.length >= 0.8; +} + /** * POST /api/dashboard/playground/grounding * For each citation, fetch its cited section(s) and check the verbatim @@ -53,10 +71,8 @@ export async function POST(request: NextRequest) { citations.map(async (c) => { const quote = c.quote?.trim(); if (!quote || !c.section_ids?.length) return { grounded: null }; - const needle = norm(quote); for (const id of c.section_ids) { - const hay = norm(await getSection(id)); - if (hay && hay.includes(needle)) return { grounded: true }; + if (isGrounded(quote, await getSection(id))) return { grounded: true }; } return { grounded: false }; }), diff --git a/apps/web/components/dashboard/AppSidebar.tsx b/apps/web/components/dashboard/AppSidebar.tsx index b5e0bd2..e969b4f 100644 --- a/apps/web/components/dashboard/AppSidebar.tsx +++ b/apps/web/components/dashboard/AppSidebar.tsx @@ -24,7 +24,7 @@ import { } from "lucide-react"; import { signOut, useSession } from "@/lib/auth-client"; -import { VectorlessDot } from "@/components/VectorlessIcon"; +import { VectorlessIcon } from "@/components/VectorlessIcon"; import { Sidebar, SidebarContent, @@ -119,9 +119,7 @@ export function AppSidebar() { - - - + vectorless diff --git a/apps/web/components/playground/ChatThread.tsx b/apps/web/components/playground/ChatThread.tsx index 2af5efd..c6f794b 100644 --- a/apps/web/components/playground/ChatThread.tsx +++ b/apps/web/components/playground/ChatThread.tsx @@ -29,16 +29,17 @@ function CitationItem({ c }: { c: Citation }) { {pages} )} + {c.sectionIds && c.sectionIds.length > 0 && ( + + {c.sectionIds.length} section{c.sectionIds.length === 1 ? "" : "s"} + + )} - {c.quote ? ( + {c.quote && (

“{c.quote}”

- ) : ( -

- Cited {c.sectionIds?.length ?? 0} section(s). -

)} ); @@ -59,7 +60,12 @@ function AssistantBubble({ m }: { m: AssistantMessage }) { )} - {m.answer && } + {m.answer && ( + + )} {m.status === "error" && (