refac: sources and citations
This commit is contained in:
1 parent
b0491886bc
commit
ec45d77ce9
11 files changed
+201
-62
No files matched your search
@@ -169,7 +169,7 @@
|
||||
></iframe>
|
||||
{:else}
|
||||
<pre class="text-sm dark:text-gray-400 whitespace-pre-line">
|
||||
{document.document}
|
||||
{document.document.trim()}
|
||||
</pre>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import { mentionExtension } from '$lib/utils/marked/mention-extension';
|
||||
|
||||
import MarkdownTokens from './Markdown/MarkdownTokens.svelte';
|
||||
import footnoteExtension from '$lib/utils/marked/footnote-extension';
|
||||
import citationExtension from '$lib/utils/marked/citation-extension';
|
||||
|
||||
export let id = '';
|
||||
export let content;
|
||||
@@ -39,6 +41,8 @@
|
||||
|
||||
marked.use(markedKatexExtension(options));
|
||||
marked.use(markedExtension(options));
|
||||
marked.use(citationExtension(options));
|
||||
marked.use(footnoteExtension(options));
|
||||
marked.use(disableSingleTilde);
|
||||
marked.use({
|
||||
extensions: [mentionExtension({ triggerChar: '@' }), mentionExtension({ triggerChar: '#' })]
|
||||
@@ -47,7 +51,7 @@
|
||||
$: (async () => {
|
||||
if (content) {
|
||||
tokens = marked.lexer(
|
||||
replaceTokens(processResponseContent(content), sourceIds, model?.name, $user?.name)
|
||||
replaceTokens(processResponseContent(content), model?.name, $user?.name)
|
||||
);
|
||||
}
|
||||
})();
|
||||
@@ -61,6 +65,7 @@
|
||||
{save}
|
||||
{preview}
|
||||
{editCodeBlock}
|
||||
{sourceIds}
|
||||
{topPadding}
|
||||
{onTaskClick}
|
||||
{onSourceClick}
|
||||
|
||||
@@ -3,14 +3,11 @@
|
||||
import type { Token } from 'marked';
|
||||
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import Source from './Source.svelte';
|
||||
import { settings } from '$lib/stores';
|
||||
|
||||
export let id: string;
|
||||
export let token: Token;
|
||||
|
||||
export let onSourceClick: Function = () => {};
|
||||
|
||||
let html: string | null = null;
|
||||
|
||||
$: if (token.type === 'html' && token?.text) {
|
||||
@@ -129,8 +126,6 @@
|
||||
}}
|
||||
></iframe>
|
||||
{/if}
|
||||
{:else if token.text.includes(`<source_id`)}
|
||||
<Source {id} {token} onClick={onSourceClick} />
|
||||
{:else if token.text.trim().match(/^<br\s*\/?>$/i)}
|
||||
<br />
|
||||
{:else}
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
import TextToken from './MarkdownInlineTokens/TextToken.svelte';
|
||||
import CodespanToken from './MarkdownInlineTokens/CodespanToken.svelte';
|
||||
import MentionToken from './MarkdownInlineTokens/MentionToken.svelte';
|
||||
import SourceToken from './SourceToken.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let done = true;
|
||||
export let tokens: Token[];
|
||||
export let sourceIds = [];
|
||||
export let onSourceClick: Function = () => {};
|
||||
</script>
|
||||
|
||||
@@ -68,6 +70,17 @@
|
||||
></iframe>
|
||||
{:else if token.type === 'mention'}
|
||||
<MentionToken {token} />
|
||||
{:else if token.type === 'footnote'}
|
||||
{@html DOMPurify.sanitize(
|
||||
`<sup class="footnote-ref footnote-ref-text">${token.escapedText}</sup>`
|
||||
) || ''}
|
||||
{:else if token.type === 'citation'}
|
||||
<SourceToken {id} {token} {sourceIds} onClick={onSourceClick} />
|
||||
<!-- {#if token.ids && token.ids.length > 0}
|
||||
{#each token.ids as sourceId}
|
||||
<Source id={sourceId - 1} title={sourceIds[sourceId - 1]} onClick={onSourceClick} />
|
||||
{/each}
|
||||
{/if} -->
|
||||
{:else if token.type === 'text'}
|
||||
<TextToken {token} {done} />
|
||||
{/if}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Download from '$lib/components/icons/Download.svelte';
|
||||
|
||||
import Source from './Source.svelte';
|
||||
import HtmlToken from './HTMLToken.svelte';
|
||||
import Clipboard from '$lib/components/icons/Clipboard.svelte';
|
||||
|
||||
@@ -29,6 +28,7 @@
|
||||
export let tokens: Token[];
|
||||
export let top = true;
|
||||
export let attributes = {};
|
||||
export let sourceIds = [];
|
||||
|
||||
export let done = true;
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
id={`${id}-${tokenIdx}-h`}
|
||||
tokens={token.tokens}
|
||||
{done}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
/>
|
||||
</svelte:element>
|
||||
@@ -147,6 +148,7 @@
|
||||
id={`${id}-${tokenIdx}-header-${headerIdx}`}
|
||||
tokens={header.tokens}
|
||||
{done}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
/>
|
||||
</div>
|
||||
@@ -172,6 +174,7 @@
|
||||
id={`${id}-${tokenIdx}-row-${rowIdx}-${cellIdx}`}
|
||||
tokens={cell.tokens}
|
||||
{done}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
/>
|
||||
</div>
|
||||
@@ -348,6 +351,7 @@
|
||||
id={`${id}-${tokenIdx}-p`}
|
||||
tokens={token.tokens ?? []}
|
||||
{done}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
/>
|
||||
</p>
|
||||
@@ -359,6 +363,7 @@
|
||||
id={`${id}-${tokenIdx}-t`}
|
||||
tokens={token.tokens}
|
||||
{done}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
/>
|
||||
{:else}
|
||||
@@ -370,6 +375,7 @@
|
||||
id={`${id}-${tokenIdx}-p`}
|
||||
tokens={token.tokens ?? []}
|
||||
{done}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
/>
|
||||
{:else}
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let id;
|
||||
export let token;
|
||||
|
||||
export let title: string = 'N/A';
|
||||
|
||||
export let onClick: Function = () => {};
|
||||
|
||||
let attributes: Record<string, string | undefined> = {};
|
||||
|
||||
function extractAttributes(input: string): Record<string, string> {
|
||||
const regex = /(\w+)="([^"]*)"/g;
|
||||
let match;
|
||||
let attrs: Record<string, string> = {};
|
||||
|
||||
// Loop through all matches and populate the attributes object
|
||||
while ((match = regex.exec(input)) !== null) {
|
||||
attrs[match[1]] = match[2];
|
||||
}
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
// Helper function to return only the domain from a URL
|
||||
function getDomain(url: string): string {
|
||||
const domain = url.replace('http://', '').replace('https://', '').split(/[/?#]/)[0];
|
||||
@@ -44,23 +31,17 @@
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
$: attributes = extractAttributes(token.text);
|
||||
</script>
|
||||
|
||||
{#if attributes.title !== 'N/A'}
|
||||
{#if title !== 'N/A'}
|
||||
<button
|
||||
class="text-xs font-medium w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/60 dark:hover:text-white bg-gray-50 text-black/60 hover:text-black transition rounded-lg"
|
||||
class="text-[10px] w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/80 dark:hover:text-white bg-gray-50 text-black/80 hover:text-black transition rounded-xl"
|
||||
on:click={() => {
|
||||
onClick(id, attributes.data);
|
||||
onClick(id);
|
||||
}}
|
||||
>
|
||||
<span class="line-clamp-1">
|
||||
{getDisplayTitle(
|
||||
decodeURIComponent(attributes.title)
|
||||
? formattedTitle(decodeURIComponent(attributes.title))
|
||||
: ''
|
||||
)}
|
||||
{getDisplayTitle(formattedTitle(decodeURIComponent(title)))}
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { LinkPreview } from 'bits-ui';
|
||||
import Source from './Source.svelte';
|
||||
|
||||
export let id;
|
||||
export let token;
|
||||
export let sourceIds = [];
|
||||
export let onClick: Function = () => {};
|
||||
|
||||
let containerElement;
|
||||
|
||||
// Helper function to return only the domain from a URL
|
||||
function getDomain(url: string): string {
|
||||
const domain = url.replace('http://', '').replace('https://', '').split(/[/?#]/)[0];
|
||||
|
||||
if (domain.startsWith('www.')) {
|
||||
return domain.slice(4);
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
// Helper function to check if text is a URL and return the domain
|
||||
function formattedTitle(title: string): string {
|
||||
if (title.startsWith('http')) {
|
||||
return getDomain(title);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if (token?.ids ?? []).length == 1}
|
||||
<Source id={token.ids[0] - 1} title={sourceIds[token.ids[0] - 1]} {onClick} />
|
||||
{:else}
|
||||
<LinkPreview.Root openDelay={0}>
|
||||
<LinkPreview.Trigger>
|
||||
<button
|
||||
class="text-[10px] w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/80 dark:hover:text-white bg-gray-50 text-black/80 hover:text-black transition rounded-xl"
|
||||
>
|
||||
<span class="line-clamp-1">
|
||||
{getDisplayTitle(formattedTitle(decodeURIComponent(sourceIds[token.ids[0] - 1])))}
|
||||
<span class="dark:text-white/50 text-black/50">+{(token?.ids ?? []).length - 1}</span>
|
||||
</span>
|
||||
</button>
|
||||
</LinkPreview.Trigger>
|
||||
<LinkPreview.Content
|
||||
class="z-[999]"
|
||||
align="start"
|
||||
strategy="fixed"
|
||||
sideOffset={6}
|
||||
el={containerElement}
|
||||
>
|
||||
<div class="bg-gray-50 dark:bg-gray-850 rounded-xl p-1 cursor-pointer">
|
||||
{#each token.ids as sourceId}
|
||||
<div class="">
|
||||
<Source id={sourceId - 1} title={sourceIds[sourceId - 1]} {onClick} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</LinkPreview.Content>
|
||||
</LinkPreview.Root>
|
||||
{/if}
|
||||
@@ -797,11 +797,11 @@
|
||||
onTaskClick={async (e) => {
|
||||
console.log(e);
|
||||
}}
|
||||
onSourceClick={async (id, idx) => {
|
||||
console.log(id, idx);
|
||||
onSourceClick={async (id) => {
|
||||
console.log(id);
|
||||
|
||||
if (citationsElement) {
|
||||
citationsElement?.showSourceModal(idx - 1);
|
||||
citationsElement?.showSourceModal(id);
|
||||
}
|
||||
}}
|
||||
onAddMessages={({ modelId, parentId, messages }) => {
|
||||
|
||||
Reference in new issue
Block a user