open-webui/src/lib/components/chat/Messages/Citations.svelte
Timothy Jaeryang Baek 3b3e12b43a
Some checks are pending
Deploy to HuggingFace Spaces / check-secret (push) Waiting to run
Deploy to HuggingFace Spaces / deploy (push) Blocked by required conditions
Create and publish Docker images with specific build args / build-main-image (linux/amd64, ubuntu-latest) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64, ubuntu-24.04-arm) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64, ubuntu-latest) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64, ubuntu-24.04-arm) (push) Waiting to run
Create and publish Docker images with specific build args / build-slim-image (linux/amd64, ubuntu-latest) (push) Waiting to run
Create and publish Docker images with specific build args / merge-slim-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64, ubuntu-latest) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64, ubuntu-24.04-arm) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda126-image (linux/amd64, ubuntu-latest) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda126-image (linux/arm64, ubuntu-24.04-arm) (push) Waiting to run
Create and publish Docker images with specific build args / build-slim-image (linux/arm64, ubuntu-24.04-arm) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda126-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
Python CI / Format Backend (push) Waiting to run
Frontend Build / Format & Build Frontend (push) Waiting to run
Frontend Build / Frontend Unit Tests (push) Waiting to run
refac
2025-12-11 01:09:14 -05:00

203 lines
5.4 KiB
Svelte

<script lang="ts">
import { getContext } from 'svelte';
import { embed, showControls, showEmbeds } from '$lib/stores';
import CitationModal from './Citations/CitationModal.svelte';
const i18n = getContext('i18n');
export let id = '';
export let chatId = '';
export let sources = [];
export let readOnly = false;
let citations = [];
let showPercentage = false;
let showRelevance = true;
let citationModal = null;
let showCitations = false;
let showCitationModal = false;
let selectedCitation: any = null;
export const showSourceModal = (sourceIdx) => {
if (citations[sourceIdx]) {
console.log('Showing citation modal for:', citations[sourceIdx]);
if (citations[sourceIdx]?.source?.embed_url) {
const embedUrl = citations[sourceIdx].source.embed_url;
if (embedUrl) {
if (readOnly) {
// Open in new tab if readOnly
window.open(embedUrl, '_blank');
return;
} else {
showControls.set(true);
showEmbeds.set(true);
embed.set({
url: embedUrl,
title: citations[sourceIdx]?.source?.name || 'Embedded Content',
source: citations[sourceIdx],
chatId: chatId,
messageId: id
});
}
} else {
selectedCitation = citations[sourceIdx];
showCitationModal = true;
}
} else {
selectedCitation = 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] : []
});
}
});
return acc;
}, []);
console.log('citations', citations);
showRelevance = calculateShowRelevance(citations);
showPercentage = shouldShowPercentage(citations);
}
const decodeString = (str: string) => {
try {
return decodeURIComponent(str);
} catch (e) {
return str;
}
};
</script>
<CitationModal
bind:show={showCitationModal}
citation={selectedCitation}
{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/30"
on:click={() => {
showCitations = !showCitations;
}}
>
{#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}
{#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}`}
class="no-toggle outline-hidden flex dark:text-gray-300 bg-transparent text-gray-600 rounded-xl gap-1.5 items-center"
on:click={() => {
showCitationModal = true;
selectedCitation = citation;
}}
>
<div class=" font-medium bg-gray-50 dark:bg-gray-850 rounded-md px-1">
{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}