浏览代码

finalizing fixes & additions

Jannik Streidl 6 月之前
父节点
当前提交
0bebc898c8

+ 84 - 63
src/lib/components/chat/Messages/Citations.svelte

@@ -10,48 +10,61 @@
 	export let citations = [];
 
 	let _citations = [];
+	let showPercentage = false;
 
 	let showCitationModal = false;
-	let selectedCitation = null;
+	let selectedCitation: any = null;
 	let isCollapsibleOpen = false;
 
-	$: _citations = citations.reduce((acc, citation) => {
-		citation.document.forEach((document, index) => {
-			const metadata = citation.metadata?.[index];
-			const distance = citation.distances?.[index];
-			const id = metadata?.source ?? 'N/A';
-			let source = citation?.source;
+	function shouldShowPercentage(citations: any[]) {
+		return citations.every(
+			(citation) =>
+				citation.distances &&
+				citation.distances.length > 0 &&
+				citation.distances.every((d: number) => d !== undefined && d >= -1 && d <= 1)
+		);
+	}
 
-			if (metadata?.name) {
-				source = { ...source, name: metadata.name };
-			}
+	$: {
+		_citations = citations.reduce((acc, citation) => {
+			citation.document.forEach((document, index) => {
+				const metadata = citation.metadata?.[index];
+				const distance = citation.distances?.[index];
+				const id = metadata?.source ?? 'N/A';
+				let source = citation?.source;
 
-			// Check if ID looks like a URL
-			if (id.startsWith('http://') || id.startsWith('https://')) {
-				source = { name: id };
-			}
+				if (metadata?.name) {
+					source = { ...source, name: metadata.name };
+				}
 
-			const existingSource = acc.find((item) => item.id === id);
+				if (id.startsWith('http://') || id.startsWith('https://')) {
+					source = { name: 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;
-	}, []);
+				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;
+		}, []);
+
+		showPercentage = shouldShowPercentage(_citations);
+	}
 </script>
 
-<CitationsModal bind:show={showCitationModal} citation={selectedCitation} />
+<CitationsModal bind:show={showCitationModal} citation={selectedCitation} {showPercentage} />
 
 {#if _citations.length > 0}
 	<div class="mt-1 mb-2 w-full flex gap-1 items-center flex-wrap">
@@ -79,42 +92,50 @@
 		{:else}
 			<Collapsible bind:open={isCollapsibleOpen} className="w-full">
 				<div
-					class="flex items-center gap-2 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition cursor-pointer"
+					class="flex items-center gap-1 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition cursor-pointer"
 				>
-					<span>{$i18n.t('References from')}</span>
-					{#each _citations.slice(0, 2) as citation, idx}
-						<div class="flex gap-1 text-xs font-semibold">
-							<button
-								class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96"
-								on:click={() => {
-									showCitationModal = true;
-									selectedCitation = citation;
-								}}
-							>
-								{#if _citations.every((c) => c.distances !== undefined)}
-									<div class="bg-white dark:bg-gray-700 rounded-full size-4">
-										{idx + 1}
-									</div>
-								{/if}
-								<div class="flex-1 mx-2 line-clamp-1">
-									{citation.source.name}
+					<div class="flex-grow flex flex-wrap items-center gap-1">
+						<span class="whitespace-nowrap hidden sm:inline">{$i18n.t('References from')}</span>
+						<div class="flex flex-wrap items-center">
+							{#each _citations.slice(0, 2) as citation, idx}
+								<div class="flex items-center">
+									<button
+										class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96 text-xs font-semibold"
+										on:click={() => {
+											showCitationModal = true;
+											selectedCitation = citation;
+										}}
+									>
+										{#if _citations.every((c) => c.distances !== undefined)}
+											<div class="bg-white dark:bg-gray-700 rounded-full size-4">
+												{idx + 1}
+											</div>
+										{/if}
+										<div class="flex-1 mx-2 line-clamp-1">
+											{citation.source.name}
+										</div>
+									</button>
+									{#if idx === 0}
+										<span class="mr-1">,</span>
+									{/if}
 								</div>
-							</button>
+							{/each}
+						</div>
+						<div class="flex items-center gap-1 whitespace-nowrap">
+							<span class="hidden sm:inline">{$i18n.t('and')}</span>
+							<span class="text-gray-600 dark:text-gray-400">
+								{_citations.length - 2}
+							</span>
+							<span>{$i18n.t('more')}</span>
 						</div>
-						{#if idx === 0}
-							<span class="-ml-2">,</span>
+					</div>
+					<div class="flex-shrink-0">
+						{#if isCollapsibleOpen}
+							<ChevronUp strokeWidth="3.5" className="size-3.5" />
+						{:else}
+							<ChevronDown strokeWidth="3.5" className="size-3.5" />
 						{/if}
-					{/each}
-					<span>{$i18n.t('and')}</span>
-					<div class="text-gray-600 dark:text-gray-400">
-						{_citations.length - 2}
 					</div>
-					<span>{$i18n.t('more')}</span>
-					{#if isCollapsibleOpen}
-						<ChevronUp strokeWidth="3.5" className="size-3.5" />
-					{:else}
-						<ChevronDown strokeWidth="3.5" className="size-3.5" />
-					{/if}
 				</div>
 				<div slot="content" class="mt-2">
 					<div class="flex flex-wrap gap-2">

+ 35 - 25
src/lib/components/chat/Messages/CitationsModal.svelte

@@ -7,24 +7,26 @@
 
 	export let show = false;
 	export let citation;
+	export let showPercentage = false;
 
 	let mergedDocuments = [];
 
-	function calculatePercentage(distance) {
+	function calculatePercentage(distance: number) {
 		if (distance < 0) return 100;
 		if (distance > 1) return 0;
-		return Math.round((1 - distance) * 100);
+		return Math.round((1 - distance) * 10000) / 100;
 	}
 
-	function shouldShowPercentage(documents) {
-		const validDistances = documents.filter(
-			(d) => d.distance !== undefined && d.distance >= 0 && d.distance <= 1
-		);
-		return validDistances.length >= 2;
+	function getRelevanceColor(percentage: number) {
+		if (percentage >= 80)
+			return 'bg-green-200 dark:bg-green-800 text-green-800 dark:text-green-200';
+		if (percentage >= 60)
+			return 'bg-yellow-200 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200';
+		if (percentage >= 40)
+			return 'bg-orange-200 dark:bg-orange-800 text-orange-800 dark:text-orange-200';
+		return 'bg-red-200 dark:bg-red-800 text-red-800 dark:text-red-200';
 	}
 
-	$: showPercentage = shouldShowPercentage(mergedDocuments);
-
 	$: if (citation) {
 		mergedDocuments = citation.document?.map((c, i) => {
 			return {
@@ -79,7 +81,7 @@
 							<Tooltip
 								content={$i18n.t('Open file')}
 								placement="left"
-								tippyOptions={{ duration: [500, 0], animation: 'perspective' }}
+								tippyOptions={{ duration: [500, 0] }}
 							>
 								<div class="text-sm dark:text-gray-400 flex items-center gap-2">
 									<a
@@ -105,23 +107,31 @@
 								<div class="text-sm font-medium dark:text-gray-300 mt-2">
 									{$i18n.t('Relevance')}
 								</div>
-								{#if showPercentage}
-									{@const percentage = calculatePercentage(document.distance)}
-									<div class="text-sm my-1 dark:text-gray-400 flex items-center gap-2">
-										<span class={`px-1 rounded font-medium ${getRelevanceColor(percentage)}`}>
-											{percentage}%
-										</span>
-										<span class="text-gray-500 dark:text-gray-500"
-											>({document.distance.toFixed(4)})</span
-										>
-									</div>
-								{:else}
+								<Tooltip
+									content={$i18n.t('Semantic distance to query from vector store')}
+									placement="left"
+									tippyOptions={{ duration: [500, 0] }}
+								>
 									<div class="text-sm my-1 dark:text-gray-400 flex items-center gap-2">
-										<span class="text-gray-500 dark:text-gray-500">
-											{document.distance.toFixed(4)}
-										</span>
+										{#if showPercentage}
+											{@const percentage = calculatePercentage(document.distance)}
+											<span class={`px-1 rounded font-medium ${getRelevanceColor(percentage)}`}>
+												{percentage.toFixed(2)}%
+											</span>
+											<span class="text-gray-500 dark:text-gray-500">
+												({document.distance.toFixed(4)})
+											</span>
+										{:else}
+											<span class="text-gray-500 dark:text-gray-500">
+												{document.distance.toFixed(4)}
+											</span>
+										{/if}
 									</div>
-								{/if}
+								</Tooltip>
+							{:else}
+								<div class="text-sm dark:text-gray-400">
+									{$i18n.t('No source available')}
+								</div>
 							{/if}
 						{:else}
 							<div class="text-sm dark:text-gray-400">

+ 14 - 3
src/lib/components/common/Collapsible.svelte

@@ -9,11 +9,18 @@
 	export let className = '';
 	export let title = null;
 
+	let contentHeight = 0;
+	let contentElement: HTMLElement;
+
 	function handleClick(event) {
 		if (!event.target.closest('.no-toggle')) {
 			open = !open;
 		}
 	}
+
+	$: if (contentElement) {
+		contentHeight = open ? contentElement.scrollHeight : 0;
+	}
 </script>
 
 <div class={className}>
@@ -43,9 +50,13 @@
 		</button>
 	{/if}
 
-	{#if open}
-		<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
+	<div
+		bind:this={contentElement}
+		class="overflow-hidden transition-all duration-300 ease-in-out"
+		style="max-height: {contentHeight}px;"
+	>
+		<div>
 			<slot name="content" />
 		</div>
-	{/if}
+	</div>
 </div>