| 1 | import type { AssistantCitation } from "@t3tools/contracts"; |
| 2 | import { |
| 3 | formatAssistantCitationHref, |
| 4 | parseAssistantCitationHref, |
| 5 | } from "@t3tools/shared/assistantCitations"; |
| 6 | import * as Base64Url from "effect/encoding/Base64Url"; |
| 7 | import * as Result from "effect/Result"; |
| 8 | |
| 9 | import { randomUUID } from "./utils"; |
| 10 | |
| 11 | declare module "@tanstack/react-router" { |
| 12 | interface HistoryState { |
| 13 | assistantCitationActivation?: string; |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | const CITATION_HASH_PREFIX = "assistant-citation="; |
| 18 | |
| 19 | /** Base64url keeps router hash normalization from decoding quote whitespace or source IDs. */ |
| 20 | export function assistantCitationHash(citation: AssistantCitation) { |
| 21 | return `${CITATION_HASH_PREFIX}${Base64Url.encode(formatAssistantCitationHref(citation))}`; |
| 22 | } |
| 23 | |
| 24 | export function assistantCitationFromLocation(href: string) { |
| 25 | const hashIndex = href.indexOf("#"); |
| 26 | if (hashIndex === -1) return null; |
| 27 | const hash = href.slice(hashIndex + 1); |
| 28 | if (!hash.startsWith(CITATION_HASH_PREFIX) || hash.length > 140_000) return null; |
| 29 | try { |
| 30 | return parseAssistantCitationHref( |
| 31 | Result.getOrThrow(Base64Url.decodeString(hash.slice(CITATION_HASH_PREFIX.length))), |
| 32 | ); |
| 33 | } catch { |
| 34 | return null; |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | /** A fresh activation lets the same link reveal its source again after dismissal. */ |
| 39 | export function assistantCitationNavigation(citation: AssistantCitation) { |
| 40 | return { |
| 41 | to: "/$environmentId/$threadId" as const, |
| 42 | params: { environmentId: citation.environmentId, threadId: citation.threadId }, |
| 43 | hash: assistantCitationHash(citation), |
| 44 | resetScroll: false, |
| 45 | state: { assistantCitationActivation: randomUUID() }, |
| 46 | }; |
| 47 | } |