open-webui/src/lib/components/chat/Messages/Citations.svelte

198 lines
5.2 KiB
Svelte
Raw Normal View History

2024-08-18 18:59:59 +00:00
<script lang="ts">
import { getContext } from 'svelte';
2025-09-12 09:49:53 +00:00
import CitationModal from './Citations/CitationModal.svelte';
2025-09-29 03:15:47 +00:00
import { embed, showControls, showEmbeds } from '$lib/stores';
const i18n = getContext('i18n');
2024-08-18 18:59:59 +00:00
2025-02-19 08:35:01 +00:00
export let id = '';
2024-11-22 03:46:09 +00:00
export let sources = [];
2025-10-02 16:48:56 +00:00
export let readOnly = false;
2024-08-18 18:59:59 +00:00
2024-11-22 03:46:09 +00:00
let citations = [];
2024-10-12 13:18:56 +00:00
let showPercentage = false;
let showRelevance = true;
2024-08-18 18:59:59 +00:00
2025-09-09 13:36:18 +00:00
let citationModal = null;
2025-09-12 09:49:53 +00:00
let showCitations = false;
let showCitationModal = false;
2025-09-12 09:49:53 +00:00
2024-10-12 13:18:56 +00:00
let selectedCitation: any = null;
2025-08-14 10:33:49 +00:00
export const showSourceModal = (sourceIdx) => {
if (citations[sourceIdx]) {
2025-09-09 13:36:18 +00:00
console.log('Showing citation modal for:', citations[sourceIdx]);
2025-09-29 03:15:47 +00:00
if (citations[sourceIdx]?.source?.embed_url) {
const embedUrl = citations[sourceIdx].source.embed_url;
if (embedUrl) {
2025-10-02 16:48:56 +00:00
if (readOnly) {
// Open in new tab if readOnly
window.open(embedUrl, '_blank');
return;
} else {
showControls.set(true);
showEmbeds.set(true);
embed.set({
title: citations[sourceIdx]?.source?.name || 'Embedded Content',
url: embedUrl
});
}
2025-09-29 03:15:47 +00:00
} else {
selectedCitation = citations[sourceIdx];
showCitationModal = true;
}
} else {
selectedCitation = citations[sourceIdx];
showCitationModal = true;
}
2025-08-14 10:33:49 +00:00
}
};
2024-11-22 03:46:09 +00:00
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;
}
2024-11-22 03:46:09 +00:00
function shouldShowPercentage(sources: any[]) {
const distances = sources.flatMap((citation) => citation.distances ?? []);
return distances.every((d) => d !== undefined && d >= -1 && d <= 1);
2024-10-12 13:18:56 +00:00
}
2024-08-18 18:59:59 +00:00
2024-10-12 13:18:56 +00:00
$: {
2024-11-22 03:46:09 +00:00
citations = sources.reduce((acc, source) => {
if (Object.keys(source).length === 0) {
2024-11-22 02:26:38 +00:00
return acc;
}
2025-08-27 23:41:25 +00:00
source?.document?.forEach((document, index) => {
const metadata = source?.metadata?.[index];
const distance = source?.distances?.[index];
2024-11-22 03:46:09 +00:00
// Within the same citation there could be multiple documents
2025-02-26 23:42:19 +00:00
const id = metadata?.source ?? source?.source?.id ?? 'N/A';
2024-11-22 03:46:09 +00:00
let _source = source?.source;
2024-08-18 18:59:59 +00:00
2024-10-12 13:18:56 +00:00
if (metadata?.name) {
2024-11-22 03:46:09 +00:00
_source = { ..._source, name: metadata.name };
2024-10-12 13:18:56 +00:00
}
2024-08-18 18:59:59 +00:00
2024-10-12 13:18:56 +00:00
if (id.startsWith('http://') || id.startsWith('https://')) {
2024-11-22 03:46:09 +00:00
_source = { ..._source, name: id, url: id };
2024-10-12 13:18:56 +00:00
}
2024-08-18 18:59:59 +00:00
2024-10-12 13:18:56 +00:00
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,
2024-11-22 03:46:09 +00:00
source: _source,
2024-10-12 13:18:56 +00:00
document: [document],
metadata: metadata ? [metadata] : [],
distances: distance !== undefined ? [distance] : []
2024-10-12 13:18:56 +00:00
});
}
});
2025-08-27 23:41:25 +00:00
2024-10-12 13:18:56 +00:00
return acc;
}, []);
console.log('citations', citations);
2024-10-12 13:18:56 +00:00
2024-11-22 03:46:09 +00:00
showRelevance = calculateShowRelevance(citations);
showPercentage = shouldShowPercentage(citations);
2024-10-12 13:18:56 +00:00
}
2025-09-12 09:49:53 +00:00
const decodeString = (str: string) => {
try {
return decodeURIComponent(str);
} catch (e) {
return str;
}
};
2024-09-12 06:06:02 +00:00
</script>
2025-09-12 09:49:53 +00:00
<CitationModal
2025-09-09 13:36:18 +00:00
bind:show={showCitationModal}
2025-09-12 09:49:53 +00:00
citation={selectedCitation}
2025-09-09 13:36:18 +00:00
{showPercentage}
{showRelevance}
/>
2024-09-12 06:06:02 +00:00
2024-11-22 03:46:09 +00:00
{#if citations.length > 0}
2025-09-09 13:27:38 +00:00
{@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
2025-11-30 08:56:12 +00:00
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/30"
2025-09-09 13:27:38 +00:00
on:click={() => {
2025-09-12 09:49:53 +00:00
showCitations = !showCitations;
2025-09-09 13:27:38 +00:00
}}
>
{#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>
2025-09-09 13:27:38 +00:00
{/if}
<div>
{#if citations.length === 1}
{$i18n.t('1 Source')}
{:else}
{$i18n.t('{{COUNT}} Sources', {
COUNT: citations.length
})}
{/if}
</div>
</button>
2024-09-12 06:06:02 +00:00
</div>
{/if}
2025-09-12 09:49:53 +00:00
{#if showCitations}
<div class="py-1.5">
<div class="text-xs gap-2 flex flex-col">
{#each citations as citation, idx}
<button
id={`source-${id}-${idx + 1}`}
2025-09-15 21:39:09 +00:00
class="no-toggle outline-hidden flex dark:text-gray-300 bg-transparent text-gray-600 rounded-xl gap-1.5 items-center"
2025-09-12 09:49:53 +00:00
on:click={() => {
showCitationModal = true;
selectedCitation = citation;
}}
>
2025-09-12 22:10:41 +00:00
<div class=" font-medium bg-gray-50 dark:bg-gray-850 rounded-md px-1">
2025-09-12 09:49:53 +00:00
{idx + 1}
</div>
<div
class="flex-1 truncate hover:text-black dark:text-white/60 dark:hover:text-white transition text-left"
>
{decodeString(citation.source.name)}
</div>
</button>
{/each}
</div>
</div>
{/if}