diff --git a/inception/inception-assistant/src/main/ts/src/AssistantPanel.svelte b/inception/inception-assistant/src/main/ts/src/AssistantPanel.svelte index 2fc05db91a..ad3740bc00 100644 --- a/inception/inception-assistant/src/main/ts/src/AssistantPanel.svelte +++ b/inception/inception-assistant/src/main/ts/src/AssistantPanel.svelte @@ -78,6 +78,13 @@ let utteranceQueue: SpeechSynthesisUtterance[] = []; let isSpeaking = false; + // Our canonical reference format + const refIdReplacementPattern = /\s*{{ref::([\w-]+)}}(\.*)/g + + // Some models (deepseek-r1) can't be bothered to properly use our reference syntax + // and keep referring to documents using the "Document XXXXXXXX" syntax... + const docIdReplacementPattern = /\s*[Dd]ocument[\s,]+([0-9a-f]{8})(\.*)/g + marked.setOptions({ breaks: true, gfm: true, @@ -323,6 +330,43 @@ chatContainer.scrollHeight - threshold; } + function copyToClipboard(message: MTextMessage) { + let usedReferences = {}; + let text = message.message.replace( + refIdReplacementPattern, + (match: string, refId: string, dots: string) => { + const refSelector = (ref) => ref.id === refId; + const reference = message.references.find(refSelector); + const refNum = message.references.findIndex(refSelector) + 1; + + if (reference) { + usedReferences[refNum] = reference; + return `[^${refNum}]`; + } + + return match; + }, + ); + + if (Object.keys(usedReferences).length > 0) { + text += "\n\nReferences:"; + } + + for (let refNum in usedReferences) { + const reference = usedReferences[refNum]; + text += `\n[^${refNum}]: ${reference.documentName} (score: ${reference.score.toFixed(4)})`; + } + + navigator.clipboard.writeText(text).then( + () => { + console.log("Copied to clipboard successfully!"); + }, + (err) => { + console.error("Could not copy text: ", err); + } + ); + } + onMount(async () => { connect(); }); @@ -346,43 +390,31 @@ const rawHtml = marked(trimmedMessage) as string; var pureHtml = DOMPurify.sanitize(rawHtml, { RETURN_DOM: false }); - var refNum = 0; - - function replaceReferences(text, pattern) { - return text.replace( - pattern, - (match, refId, dots) => { - const reference = message.references.find( - (ref) => ref.id === refId, - ); - if (reference) { - refNum++; - return `${dots}${refNum}`; - } - - // If no matching reference is found, keep the original text - // console.trace( - // `Reference with id ${refId} not found in message ${message.id}` - // ); - return match; - }, - ); - } - - // Our canonical reference format - const refIdReplacementPattern = /\s*{{ref::([\w-]+)}}(\.*)/g - - // Some models (deepseek-r1) can't be bothered to properly use our reference syntax - // and keep referring to documents using the "Document XXXXXXXX" syntax... - const docIdReplacementPattern = /\s*[Dd]ocument[\s,]+([0-9a-f]{8})(\.*)/g // Replace all references with the respective reference link - pureHtml = replaceReferences(pureHtml, refIdReplacementPattern); - pureHtml = replaceReferences(pureHtml, docIdReplacementPattern); + pureHtml = replaceReferencesWithHtmlLinks(message, pureHtml, refIdReplacementPattern); + pureHtml = replaceReferencesWithHtmlLinks(message, pureHtml, docIdReplacementPattern); return pureHtml; } + function replaceReferencesWithHtmlLinks(message, text, pattern) { + return text.replace( + pattern, + (match: string, refId: string, dots: string) => { + const refSelector = (ref) => ref.id === refId; + const reference = message.references.find(refSelector); + const refNum = message.references.findIndex(refSelector) + 1; + + if (reference) { + return `${dots}${refNum}`; + } + + return match; + }, + ); + } + function escapeXML(str) { return str.replace(/[<>&'"]/g, (char) => { switch (char) { @@ -484,6 +516,14 @@ {/if} {message.actor ? message.actor : message.role} + {#if !message.internal} + + {/if} {#if message.internal} {#if message.performance} -