open-webui/src/lib/components/chat/Messages/Citations.svelte
Timothy Jaeryang Baek 63ca0b8cba refac
2025-09-09 17:36:18 +04:00

139 lines
3.8 KiB
Svelte

<script lang="ts">
import { getContext } from 'svelte';
import CitationsModal from '$lib/components/chat/Messages/Citations/CitationsModal.svelte';
const i18n = getContext('i18n');
export let id = '';
export let sources = [];
let citations = [];
let showPercentage = false;
let showRelevance = true;
let citationModal = null;
let showCitationModal = false;
let selectedCitation: any = null;
let isCollapsibleOpen = false;
export const showSourceModal = (sourceIdx) => {
if (citations[sourceIdx]) {
console.log('Showing citation modal for:', citations[sourceIdx]);
citationModal?.showCitation(citations[sourceIdx]);
// showCitationModal = true;
}
};
function calculateShowRelevance(sources: any[]) {
const distances = sources.flatMap((citation) => citation.distances ?? []);
const inRange = distances.filter((d) => d !== undefined && d >= -1 && d <= 1).length;
const outOfRange = distances.filter((d) => d !== undefined && (d < -1 || d > 1)).length;
if (distances.length === 0) {
return false;
}
if (
(inRange === distances.length - 1 && outOfRange === 1) ||
(outOfRange === distances.length - 1 && inRange === 1)
) {
return false;
}
return true;
}
function shouldShowPercentage(sources: any[]) {
const distances = sources.flatMap((citation) => citation.distances ?? []);
return distances.every((d) => d !== undefined && d >= -1 && d <= 1);
}
$: {
citations = sources.reduce((acc, source) => {
if (Object.keys(source).length === 0) {
return acc;
}
source?.document?.forEach((document, index) => {
const metadata = source?.metadata?.[index];
const distance = source?.distances?.[index];
// Within the same citation there could be multiple documents
const id = metadata?.source ?? source?.source?.id ?? 'N/A';
let _source = source?.source;
if (metadata?.name) {
_source = { ..._source, name: metadata.name };
}
if (id.startsWith('http://') || id.startsWith('https://')) {
_source = { ..._source, name: id, url: id };
}
const existingSource = acc.find((item) => item.id === id);
if (existingSource) {
existingSource.document.push(document);
existingSource.metadata.push(metadata);
if (distance !== undefined) existingSource.distances.push(distance);
} else {
acc.push({
id: id,
source: _source,
document: [document],
metadata: metadata ? [metadata] : [],
distances: distance !== undefined ? [distance] : undefined
});
}
});
return acc;
}, []);
console.log('citations', citations);
showRelevance = calculateShowRelevance(citations);
showPercentage = shouldShowPercentage(citations);
}
</script>
<CitationsModal
bind:this={citationModal}
bind:show={showCitationModal}
{id}
{citations}
{showPercentage}
{showRelevance}
/>
{#if citations.length > 0}
{@const urlCitations = citations.filter((c) => c?.source?.name?.startsWith('http'))}
<div class=" py-1 -mx-0.5 w-full flex gap-1 items-center flex-wrap">
<button
class="text-xs font-medium text-gray-600 dark:text-gray-300 px-3.5 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-1 border border-gray-50 dark:border-gray-850"
on:click={() => {
showCitationModal = true;
}}
>
{#if urlCitations.length > 0}
<div class="flex -space-x-1 items-center">
{#each urlCitations.slice(0, 3) as citation, idx}
<img
src="https://www.google.com/s2/favicons?sz=32&domain={citation.source.name}"
alt="favicon"
class="size-4 rounded-full shrink-0 border border-white dark:border-gray-850 bg-white dark:bg-gray-900"
/>
{/each}
</div>
{/if}
<div>
{#if citations.length === 1}
{$i18n.t('1 Source')}
{:else}
{$i18n.t('{{COUNT}} Sources', {
COUNT: citations.length
})}
{/if}
</div>
</button>
</div>
{/if}