| ... | @@ -4,7 +4,7 @@ import { | ... | @@ -4,7 +4,7 @@ import { |
| 4 | serializeAssistantCitation, | 4 | serializeAssistantCitation, |
| 5 | } from "@t3tools/shared/assistantCitations"; | 5 | } from "@t3tools/shared/assistantCitations"; |
| 6 | import { Link, useNavigate } from "@tanstack/react-router"; | 6 | import { Link, useNavigate } from "@tanstack/react-router"; |
| 7 | import { PencilIcon, QuoteIcon } from "lucide-react"; | 7 | import { ArrowUpRightIcon, PencilIcon, QuoteIcon } from "lucide-react"; |
| 8 | import { | 8 | import { |
| 9 | useEffect, | 9 | useEffect, |
| 10 | useEffectEvent, | 10 | useEffectEvent, |
| ... | @@ -20,9 +20,12 @@ import { | ... | @@ -20,9 +20,12 @@ import { |
| 20 | assistantCitationHash, | 20 | assistantCitationHash, |
| 21 | assistantCitationNavigation, | 21 | assistantCitationNavigation, |
| 22 | } from "../../lib/assistantCitationNavigation"; | 22 | } from "../../lib/assistantCitationNavigation"; |
| | 23 | import { cn } from "~/lib/utils"; |
| 23 | import { ContextChip, ContextChipAction, ContextChipLabel } from "../ContextChip"; | 24 | import { ContextChip, ContextChipAction, ContextChipLabel } from "../ContextChip"; |
| 24 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 25 | import { ContextChipPopover } from "../contextChipParts"; |
| 25 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; | 26 | import { Button } from "../ui/button"; |
| | 27 | import { Popover, PopoverClose, PopoverPopup, PopoverTrigger } from "../ui/popover"; |
| | 28 | import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area"; |
| 26 | import { AssistantCitationCommentEditor } from "./AssistantCitationCommentEditor"; | 29 | import { AssistantCitationCommentEditor } from "./AssistantCitationCommentEditor"; |
| 27 | import { resolveAssistantCitationCommentDismissal } from "./assistantCitationCommentDismissal"; | 30 | import { resolveAssistantCitationCommentDismissal } from "./assistantCitationCommentDismissal"; |
| 28 | import { observeAssistantCitationCommentSource } from "./AssistantCitationSource"; | 31 | import { observeAssistantCitationCommentSource } from "./AssistantCitationSource"; |
| ... | @@ -122,16 +125,27 @@ export function AssistantCitationChip({ | ... | @@ -122,16 +125,27 @@ export function AssistantCitationChip({ |
| 122 | <ContextChipLabel className="max-w-[16em]">{label}</ContextChipLabel> | 125 | <ContextChipLabel className="max-w-[16em]">{label}</ContextChipLabel> |
| 123 | </Link> | 126 | </Link> |
| 124 | ); | 127 | ); |
| 125 | const chatSourceLink = ( | 128 | if (!composer) { |
| 126 | <Link | 129 | return ( |
| 127 | {...sourceLinkProps} | 130 | <ContextChipPopover |
| 128 | className="inline-flex h-full min-w-0 items-center gap-[0.33em] rounded-sm text-inherit no-underline hover:bg-(--context-chip-accent)/17 focus-visible:outline-2 focus-visible:outline-foreground" | 131 | kind="citation" |
| 129 | aria-label={`View cited assistant text: ${label}`} | 132 | icon={<QuoteIcon />} |
| 130 | > | 133 | label={label} |
| 131 | <QuoteIcon aria-hidden="true" /> | 134 | accessibleLabel={`Quoted assistant text: ${label}`} |
| 132 | <ContextChipLabel className="max-w-[16em]">{label}</ContextChipLabel> | 135 | copyMarkdown={serializeAssistantCitation(citation)} |
| 133 | </Link> | 136 | > |
| 134 | ); | 137 | <div className="flex max-h-[calc(var(--available-height)_-_1rem_-_2px)] flex-col items-start gap-3 p-1 text-sm"> |
| | 138 | <AssistantCitationQuote citation={citation} /> |
| | 139 | <PopoverClose |
| | 140 | render={<Button variant="outline" size="sm" render={<Link {...sourceLinkProps} />} />} |
| | 141 | > |
| | 142 | <ArrowUpRightIcon aria-hidden="true" /> |
| | 143 | Go to source |
| | 144 | </PopoverClose> |
| | 145 | </div> |
| | 146 | </ContextChipPopover> |
| | 147 | ); |
| | 148 | } |
| 135 | return ( | 149 | return ( |
| 136 | <ContextChip | 150 | <ContextChip |
| 137 | kind="citation" | 151 | kind="citation" |
| ... | @@ -139,14 +153,7 @@ export function AssistantCitationChip({ | ... | @@ -139,14 +153,7 @@ export function AssistantCitationChip({ |
| 139 | data-assistant-citation-chip="true" | 153 | data-assistant-citation-chip="true" |
| 140 | data-markdown-copy={serializeAssistantCitation(citation)} | 154 | data-markdown-copy={serializeAssistantCitation(citation)} |
| 141 | > | 155 | > |
| 142 | {composer ? ( | 156 | {composerSourceLink} |
| 143 | composerSourceLink | | |
| 144 | ) : ( | | |
| 145 | <Tooltip> | | |
| 146 | <TooltipTrigger render={chatSourceLink} /> | | |
| 147 | <TooltipPopup side="top">View source</TooltipPopup> | | |
| 148 | </Tooltip> | | |
| 149 | )} | | |
| 150 | {commentEditor ? ( | 157 | {commentEditor ? ( |
| 151 | <Popover | 158 | <Popover |
| 152 | open={commentEditor.open} | 159 | open={commentEditor.open} |
| ... | @@ -232,3 +239,34 @@ export function AssistantCitationChip({ | ... | @@ -232,3 +239,34 @@ export function AssistantCitationChip({ |
| 232 | </ContextChip> | 239 | </ContextChip> |
| 233 | ); | 240 | ); |
| 234 | } | 241 | } |
| | 242 | |
| | 243 | function AssistantCitationQuote({ citation }: { citation: AssistantCitation }) { |
| | 244 | const [fade, setFade] = useState({ top: false, bottom: false }); |
| | 245 | const updateFade = (element: HTMLElement) => { |
| | 246 | const top = element.scrollTop > 1; |
| | 247 | const bottom = element.scrollHeight - element.clientHeight - element.scrollTop > 1; |
| | 248 | setFade((current) => |
| | 249 | current.top === top && current.bottom === bottom ? current : { top, bottom }, |
| | 250 | ); |
| | 251 | }; |
| | 252 | return ( |
| | 253 | <div |
| | 254 | ref={(element) => { |
| | 255 | if (!element) return; |
| | 256 | const observer = new ResizeObserver(() => updateFade(element)); |
| | 257 | observer.observe(element); |
| | 258 | return () => observer.disconnect(); |
| | 259 | }} |
| | 260 | onScroll={(event) => updateFade(event.currentTarget)} |
| | 261 | className={cn( |
| | 262 | "max-h-64 min-h-0 space-y-3 self-stretch overflow-y-auto whitespace-pre-wrap wrap-break-word", |
| | 263 | getVirtualizedScrollFadeClassName(fade), |
| | 264 | )} |
| | 265 | > |
| | 266 | <blockquote className="border-l-2 border-border pl-3 text-muted-foreground"> |
| | 267 | {citation.text} |
| | 268 | </blockquote> |
| | 269 | {citation.comment ? <p>{citation.comment}</p> : null} |
| | 270 | </div> |
| | 271 | ); |
| | 272 | } |