mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-13 12:55:19 +00:00
refac/enh: mention token rendering
This commit is contained in:
parent
22e11760a1
commit
098f34f400
7 changed files with 108 additions and 6 deletions
|
|
@ -409,14 +409,14 @@ input[type='number'] {
|
|||
}
|
||||
}
|
||||
|
||||
.tiptap .mention {
|
||||
.mention {
|
||||
border-radius: 0.4rem;
|
||||
box-decoration-break: clone;
|
||||
padding: 0.1rem 0.3rem;
|
||||
@apply text-blue-900 dark:text-blue-100 bg-blue-300/20 dark:bg-blue-500/20;
|
||||
}
|
||||
|
||||
.tiptap .mention::after {
|
||||
.mention::after {
|
||||
content: '\200B';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
|
||||
const select = (index: number) => {
|
||||
const item = filteredItems[index];
|
||||
if (item) command({ id: item.id, label: item.name });
|
||||
// Add the "A:" prefix to the id to indicate it's an assistant model
|
||||
if (item) command({ id: `A:${item.id}|${item.name}`, label: item.name });
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import markedExtension from '$lib/utils/marked/extension';
|
||||
import markedKatexExtension from '$lib/utils/marked/katex-extension';
|
||||
import { mentionExtension } from '$lib/utils/marked/mention-extension';
|
||||
|
||||
import MarkdownTokens from './Markdown/MarkdownTokens.svelte';
|
||||
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
|
||||
marked.use(markedKatexExtension(options));
|
||||
marked.use(markedExtension(options));
|
||||
marked.use({ extensions: [mentionExtension({ triggerChar: '@' })] });
|
||||
|
||||
$: (async () => {
|
||||
if (content) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
import HtmlToken from './HTMLToken.svelte';
|
||||
import TextToken from './MarkdownInlineTokens/TextToken.svelte';
|
||||
import CodespanToken from './MarkdownInlineTokens/CodespanToken.svelte';
|
||||
import MentionToken from './MarkdownInlineTokens/MentionToken.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let done = true;
|
||||
|
|
@ -60,6 +61,8 @@
|
|||
frameborder="0"
|
||||
onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"
|
||||
></iframe>
|
||||
{:else if token.type === 'mention'}
|
||||
<MentionToken {token} />
|
||||
{:else if token.type === 'text'}
|
||||
<TextToken {token} {done} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<script lang="ts">
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import type { Token } from 'marked';
|
||||
|
||||
export let token: Token;
|
||||
</script>
|
||||
|
||||
<Tooltip as="span" className="mention" content={token.id} placement="top">
|
||||
{token.triggerChar}{token.label}
|
||||
</Tooltip>
|
||||
|
|
@ -7,10 +7,12 @@
|
|||
|
||||
export let elementId = '';
|
||||
|
||||
export let as = 'div';
|
||||
export let className = 'flex';
|
||||
|
||||
export let placement = 'top';
|
||||
export let content = `I'm a tooltip!`;
|
||||
export let touch = true;
|
||||
export let className = 'flex';
|
||||
export let theme = '';
|
||||
export let offset = [0, 4];
|
||||
export let allowHTML = true;
|
||||
|
|
@ -59,8 +61,8 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={tooltipElement} class={className}>
|
||||
<svelte:element this={as} bind:this={tooltipElement} class={className}>
|
||||
<slot />
|
||||
</div>
|
||||
</svelte:element>
|
||||
|
||||
<slot name="tooltip"></slot>
|
||||
|
|
|
|||
84
src/lib/utils/marked/mention-extension.ts
Normal file
84
src/lib/utils/marked/mention-extension.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// mention-extension.ts
|
||||
type MentionOptions = {
|
||||
triggerChar?: string; // default "@"
|
||||
className?: string; // default "mention"
|
||||
extraAttrs?: Record<string, string>; // additional HTML attrs
|
||||
};
|
||||
|
||||
function escapeHtml(s: string) {
|
||||
return s.replace(
|
||||
/[&<>"']/g,
|
||||
(c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]!
|
||||
);
|
||||
}
|
||||
|
||||
function mentionStart(src: string) {
|
||||
// Find the first "<" followed by trigger char
|
||||
// We'll refine inside tokenizer
|
||||
return src.indexOf('<');
|
||||
}
|
||||
|
||||
function mentionTokenizer(this: any, src: string, options: MentionOptions = {}) {
|
||||
const trigger = options.triggerChar ?? '@';
|
||||
|
||||
// Build dynamic regex for `<@id>`, `<@id|label>`, `<@id|>`
|
||||
const re = new RegExp(`^<\\${trigger}([\\w.\\-:]+)(?:\\|([^>]*))?>`);
|
||||
const m = re.exec(src);
|
||||
if (!m) return;
|
||||
|
||||
const [, id, label] = m;
|
||||
return {
|
||||
type: 'mention',
|
||||
raw: m[0],
|
||||
triggerChar: trigger,
|
||||
id,
|
||||
label: label && label.length > 0 ? label : id
|
||||
};
|
||||
}
|
||||
|
||||
function mentionRenderer(token: any, options: MentionOptions = {}) {
|
||||
const trigger = options.triggerChar ?? '@';
|
||||
const cls = options.className ?? 'mention';
|
||||
const extra = options.extraAttrs ?? {};
|
||||
|
||||
const attrs = Object.entries({
|
||||
class: cls,
|
||||
'data-type': 'mention',
|
||||
'data-id': token.id,
|
||||
'data-mention-suggestion-char': trigger,
|
||||
...extra
|
||||
})
|
||||
.map(([k, v]) => `${k}="${escapeHtml(String(v))}"`)
|
||||
.join(' ');
|
||||
|
||||
return `<span ${attrs}>${escapeHtml(trigger + token.label)}</span>`;
|
||||
}
|
||||
|
||||
export function mentionExtension(opts: MentionOptions = {}) {
|
||||
return {
|
||||
name: 'mention',
|
||||
level: 'inline' as const,
|
||||
start: mentionStart,
|
||||
tokenizer(src: string) {
|
||||
return mentionTokenizer.call(this, src, opts);
|
||||
},
|
||||
renderer(token: any) {
|
||||
return mentionRenderer(token, opts);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Usage:
|
||||
// import { marked } from 'marked';
|
||||
// marked.use({ extensions: [mentionExtension({ triggerChar: '@' })] });
|
||||
//
|
||||
// "<@llama3.2:latest>" →
|
||||
// <span class="mention" data-type="mention" data-id="llama3.2:latest" data-mention-suggestion-char="@">@llama3.2:latest</span>
|
||||
//
|
||||
// "<@llama3.2:latest|friendly>" →
|
||||
// <span class="mention" ...>@friendly</span>
|
||||
//
|
||||
// "<@llama3.2:latest|>" →
|
||||
// <span class="mention" ...>@llama3.2:latest</span>
|
||||
//
|
||||
// If triggerChar = "#"
|
||||
Loading…
Reference in a new issue