mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 16:56:22 +02:00
feat: integrate Streamdown for markdown rendering and enhance citation handling
This commit is contained in:
parent
947087452f
commit
3906ba52e0
8 changed files with 1807 additions and 224 deletions
|
|
@ -2,22 +2,22 @@
|
|||
|
||||
import type { FC } from "react";
|
||||
import { useState } from "react";
|
||||
import { SheetTrigger } from "@/components/ui/sheet";
|
||||
import { SourceDetailSheet } from "@/components/chat/SourceDetailSheet";
|
||||
import { SourceDetailPanel } from "@/components/new-chat/source-detail-panel";
|
||||
|
||||
interface InlineCitationProps {
|
||||
chunkId: number;
|
||||
citationNumber: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline citation component for the new chat.
|
||||
* Renders a clickable badge that opens the SourceDetailSheet with document chunk details.
|
||||
* Renders a clickable numbered badge that opens the SourceDetailPanel with document chunk details.
|
||||
*/
|
||||
export const InlineCitation: FC<InlineCitationProps> = ({ chunkId }) => {
|
||||
export const InlineCitation: FC<InlineCitationProps> = ({ chunkId, citationNumber }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<SourceDetailSheet
|
||||
<SourceDetailPanel
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
chunkId={chunkId}
|
||||
|
|
@ -26,22 +26,17 @@ export const InlineCitation: FC<InlineCitationProps> = ({ chunkId }) => {
|
|||
description=""
|
||||
url=""
|
||||
>
|
||||
<SheetTrigger asChild>
|
||||
<span
|
||||
className="text-[10px] font-bold bg-primary/80 hover:bg-primary text-primary-foreground rounded-full w-4 h-4 inline-flex items-center justify-center align-super cursor-pointer transition-colors ml-0.5"
|
||||
title={`View source (chunk ${chunkId})`}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
className="w-2.5 h-2.5"
|
||||
>
|
||||
<path d="M6.22 8.72a.75.75 0 0 0 1.06 1.06l5.22-5.22v1.69a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-.75-.75h-3.5a.75.75 0 0 0 0 1.5h1.69L6.22 8.72Z" />
|
||||
<path d="M3.5 6.75c0-.69.56-1.25 1.25-1.25H7A.75.75 0 0 0 7 4H4.75A2.75 2.75 0 0 0 2 6.75v4.5A2.75 2.75 0 0 0 4.75 14h4.5A2.75 2.75 0 0 0 12 11.25V9a.75.75 0 0 0-1.5 0v2.25c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25v-4.5Z" />
|
||||
</svg>
|
||||
</span>
|
||||
</SheetTrigger>
|
||||
</SourceDetailSheet>
|
||||
<span
|
||||
onClick={() => setIsOpen(true)}
|
||||
onKeyDown={(e) => e.key === "Enter" && setIsOpen(true)}
|
||||
className="text-[10px] font-bold bg-primary/80 hover:bg-primary text-primary-foreground rounded-full min-w-4 h-4 px-1 inline-flex items-center justify-center align-super cursor-pointer transition-colors ml-0.5"
|
||||
title={`View source #${citationNumber}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{citationNumber}
|
||||
</span>
|
||||
</SourceDetailPanel>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,29 @@ import { cn } from "@/lib/utils";
|
|||
// Citation pattern: [citation:CHUNK_ID]
|
||||
const CITATION_REGEX = /\[citation:(\d+)\]/g;
|
||||
|
||||
// Track chunk IDs to citation numbers mapping for consistent numbering
|
||||
// This map is reset when a new message starts rendering
|
||||
let chunkIdToCitationNumber: Map<number, number> = new Map();
|
||||
let nextCitationNumber = 1;
|
||||
|
||||
/**
|
||||
* Resets the citation counter - should be called at the start of each message
|
||||
*/
|
||||
export function resetCitationCounter() {
|
||||
chunkIdToCitationNumber = new Map();
|
||||
nextCitationNumber = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or assigns a citation number for a chunk ID
|
||||
*/
|
||||
function getCitationNumber(chunkId: number): number {
|
||||
if (!chunkIdToCitationNumber.has(chunkId)) {
|
||||
chunkIdToCitationNumber.set(chunkId, nextCitationNumber++);
|
||||
}
|
||||
return chunkIdToCitationNumber.get(chunkId)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses text and replaces [citation:XXX] patterns with InlineCitation components
|
||||
*/
|
||||
|
|
@ -26,7 +49,7 @@ function parseTextWithCitations(text: string): ReactNode[] {
|
|||
const parts: ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
let citationIndex = 0;
|
||||
let instanceIndex = 0;
|
||||
|
||||
// Reset regex state
|
||||
CITATION_REGEX.lastIndex = 0;
|
||||
|
|
@ -39,12 +62,17 @@ function parseTextWithCitations(text: string): ReactNode[] {
|
|||
|
||||
// Add the citation component
|
||||
const chunkId = Number.parseInt(match[1], 10);
|
||||
const citationNumber = getCitationNumber(chunkId);
|
||||
parts.push(
|
||||
<InlineCitation key={`citation-${chunkId}-${citationIndex}`} chunkId={chunkId} />
|
||||
<InlineCitation
|
||||
key={`citation-${chunkId}-${instanceIndex}`}
|
||||
chunkId={chunkId}
|
||||
citationNumber={citationNumber}
|
||||
/>
|
||||
);
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
citationIndex++;
|
||||
instanceIndex++;
|
||||
}
|
||||
|
||||
// Add any remaining text after the last citation
|
||||
|
|
@ -56,6 +84,10 @@ function parseTextWithCitations(text: string): ReactNode[] {
|
|||
}
|
||||
|
||||
const MarkdownTextImpl = () => {
|
||||
// Reset citation counter at the start of each render
|
||||
// This ensures consistent numbering as the message streams in
|
||||
resetCitationCounter();
|
||||
|
||||
return (
|
||||
<MarkdownTextPrimitive
|
||||
remarkPlugins={[remarkGfm]}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue