LibreChat/client/src/components/Web/Context.tsx
Danny Avila 52e59e40be
📚 feat: Add Source Citations for File Search in Agents (#8652)
* feat: Source Citations for file_search in Agents

* Fix: Added citation limits and relevance score to app service. Removed duplicate tests

*  feat: implement Role-level toggle to optionally disable file Source Citation in Agents

* 🐛 fix: update mock for librechat-data-provider to include PermissionTypes and SystemRoles

---------

Co-authored-by: “Praneeth <praneeth.goparaju@slalom.com>
2025-08-13 16:24:16 -04:00

106 lines
2.7 KiB
TypeScript

import { createContext, useContext } from 'react';
import type { SearchRefType, ValidSource, ResultReference } from 'librechat-data-provider';
import type * as t from './types';
import { useSearchContext } from '~/Providers';
export interface CitationContextType {
hoveredCitationId: string | null;
setHoveredCitationId: (id: string | null) => void;
}
export const CitationContext = createContext<CitationContextType>({
hoveredCitationId: null,
setHoveredCitationId: () => {},
});
export function useHighlightState(citationId: string | undefined) {
const { hoveredCitationId } = useContext(CitationContext);
return citationId && hoveredCitationId === citationId;
}
export type CitationSource = (ValidSource | ResultReference) & {
turn: number;
refType: string | SearchRefType;
index: number;
};
const refTypeMap: Record<string | SearchRefType, string> = {
search: 'organic',
ref: 'references',
news: 'topStories',
file: 'references',
};
export function useCitation({
turn,
index,
refType: _refType,
}: {
turn: number;
index: number;
refType?: SearchRefType | string;
}): (t.Citation & t.Reference) | undefined {
const { searchResults } = useSearchContext();
if (!_refType) {
return undefined;
}
const refType = refTypeMap[_refType.toLowerCase()]
? refTypeMap[_refType.toLowerCase()]
: _refType;
if (!searchResults || !searchResults[turn] || !searchResults[turn][refType]) {
return undefined;
}
const source: CitationSource = searchResults[turn][refType][index];
if (!source) {
return undefined;
}
return {
...source,
turn,
refType: _refType.toLowerCase(),
index,
link: source.link ?? '',
title: source.title ?? '',
snippet: source['snippet'] ?? '',
attribution: source.attribution ?? '',
};
}
export function useCompositeCitations(
citations: Array<{ turn: number; refType: SearchRefType | string; index: number }>,
): Array<t.Citation & t.Reference> {
const { searchResults } = useSearchContext();
const result: Array<t.Citation & t.Reference> = [];
for (const { turn, refType: _refType, index } of citations) {
const refType = refTypeMap[_refType.toLowerCase()]
? refTypeMap[_refType.toLowerCase()]
: _refType;
if (!searchResults || !searchResults[turn] || !searchResults[turn][refType]) {
continue;
}
const source: CitationSource = searchResults[turn][refType][index];
if (!source) {
continue;
}
result.push({
...source,
turn,
refType: _refType.toLowerCase(),
index,
link: source.link ?? '',
title: source.title ?? '',
snippet: source['snippet'] ?? '',
attribution: source.attribution ?? '',
});
}
return result;
}