mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 04:15:25 +00:00
refac: citations display
This commit is contained in:
parent
0214c1e66c
commit
d0b20df46c
4 changed files with 111 additions and 125 deletions
|
|
@ -1,10 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import CitationsModal from './CitationsModal.svelte';
|
||||
import Collapsible from '$lib/components/common/Collapsible.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import { mobile } from '$lib/stores';
|
||||
import CitationsModal from '$lib/components/chat/Messages/Citations/CitationsModal.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
|
@ -96,127 +92,39 @@
|
|||
showRelevance = calculateShowRelevance(citations);
|
||||
showPercentage = shouldShowPercentage(citations);
|
||||
}
|
||||
|
||||
const decodeString = (str: string) => {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<CitationsModal
|
||||
bind:show={showCitationModal}
|
||||
citation={selectedCitation}
|
||||
{showPercentage}
|
||||
{showRelevance}
|
||||
/>
|
||||
<CitationsModal bind:show={showCitationModal} {id} {citations} {showPercentage} {showRelevance} />
|
||||
|
||||
{#if citations.length > 0}
|
||||
<div class=" py-0.5 -mx-0.5 w-full flex gap-1 items-center flex-wrap">
|
||||
{#if citations.length <= 3}
|
||||
<div class="flex text-xs font-medium flex-wrap">
|
||||
{#each citations as citation, idx}
|
||||
<button
|
||||
id={`source-${id}-${idx + 1}`}
|
||||
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-white dark:bg-gray-900 rounded-xl max-w-96"
|
||||
on:click={() => {
|
||||
showCitationModal = true;
|
||||
selectedCitation = citation;
|
||||
}}
|
||||
>
|
||||
{#if citations.every((c) => c.distances !== undefined)}
|
||||
<div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
|
||||
{idx + 1}
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="flex-1 mx-1 truncate text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white transition"
|
||||
>
|
||||
{decodeString(citation.source.name)}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{@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>
|
||||
{:else}
|
||||
<Collapsible
|
||||
id={`collapsible-${id}`}
|
||||
bind:open={isCollapsibleOpen}
|
||||
className="w-full max-w-full "
|
||||
buttonClassName="w-fit max-w-full"
|
||||
>
|
||||
<div
|
||||
class="flex w-full overflow-auto items-center gap-2 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition cursor-pointer"
|
||||
>
|
||||
<div
|
||||
class="flex-1 flex items-center gap-1 overflow-auto scrollbar-none w-full max-w-full"
|
||||
>
|
||||
<span class="whitespace-nowrap hidden sm:inline shrink-0"
|
||||
>{$i18n.t('References from')}</span
|
||||
>
|
||||
<div class="flex items-center overflow-auto scrollbar-none w-full max-w-full flex-1">
|
||||
<div class="flex text-xs font-medium items-center">
|
||||
{#each citations.slice(0, $mobile ? 1 : 2) as citation, idx}
|
||||
<button
|
||||
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
|
||||
on:click={() => {
|
||||
showCitationModal = true;
|
||||
selectedCitation = citation;
|
||||
}}
|
||||
on:pointerup={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{#if citations.every((c) => c.distances !== undefined)}
|
||||
<div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
|
||||
{idx + 1}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-1 mx-1 truncate">
|
||||
{decodeString(citation.source.name)}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 whitespace-nowrap shrink-0">
|
||||
<span class="hidden sm:inline">{$i18n.t('and')}</span>
|
||||
{citations.length - ($mobile ? 1 : 2)}
|
||||
<span>{$i18n.t('more')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
{#if isCollapsibleOpen}
|
||||
<ChevronUp strokeWidth="3.5" className="size-3.5" />
|
||||
{:else}
|
||||
<ChevronDown strokeWidth="3.5" className="size-3.5" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div slot="content">
|
||||
<div class="flex text-xs font-medium flex-wrap">
|
||||
{#each citations.slice($mobile ? 1 : 2) as citation, idx}
|
||||
<button
|
||||
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
|
||||
on:click={() => {
|
||||
showCitationModal = true;
|
||||
selectedCitation = citation;
|
||||
}}
|
||||
>
|
||||
{#if citations.every((c) => c.distances !== undefined)}
|
||||
<div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
|
||||
{idx + 3}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-1 mx-1 truncate">
|
||||
{decodeString(citation.source.name)}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onMount, tick } from 'svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
|
||||
export let id = '';
|
||||
export let show = false;
|
||||
export let citations = [];
|
||||
export let showPercentage = false;
|
||||
export let showRelevance = true;
|
||||
|
||||
const decodeString = (str: string) => {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal size="lg" bind:show>
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
|
||||
<div class=" text-lg font-medium self-center capitalize">
|
||||
{$i18n.t('Citations')}
|
||||
</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<XMark className={'size-5'} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
|
||||
<div
|
||||
class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-hidden text-left text-sm gap-2"
|
||||
>
|
||||
{#each citations as citation, idx}
|
||||
<button
|
||||
id={`source-${id}-${idx + 1}`}
|
||||
class="no-toggle outline-hidden flex dark:text-gray-300 bg-white dark:bg-gray-900 rounded-xl gap-2 items-center"
|
||||
on:click={() => {
|
||||
showCitationModal = true;
|
||||
selectedCitation = citation;
|
||||
}}
|
||||
>
|
||||
{#if citations.every((c) => c.distances !== undefined)}
|
||||
<div class="bg-gray-50 dark:bg-gray-800 rounded-full size-5">
|
||||
{idx + 1}
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="flex-1 truncate text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white transition text-left"
|
||||
>
|
||||
{decodeString(citation.source.name)}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
@ -37,6 +37,14 @@
|
|||
return title;
|
||||
}
|
||||
|
||||
const getDisplayTitle = (title: string) => {
|
||||
if (!title) return 'N/A';
|
||||
if (title.length > 30) {
|
||||
return title.slice(0, 15) + '...' + title.slice(-10);
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
$: attributes = extractAttributes(token.text);
|
||||
</script>
|
||||
|
||||
|
|
@ -48,9 +56,11 @@
|
|||
}}
|
||||
>
|
||||
<span class="line-clamp-1">
|
||||
{decodeURIComponent(attributes.title)
|
||||
? formattedTitle(decodeURIComponent(attributes.title))
|
||||
: ''}
|
||||
{getDisplayTitle(
|
||||
decodeURIComponent(attributes.title)
|
||||
? formattedTitle(decodeURIComponent(attributes.title))
|
||||
: ''
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Reference in a new issue