Skip to content

Commit e75b2c6

Browse files
authored
Merge pull request #355 from iceljc/features/add-js-interpreter
Features/add js interpreter
2 parents 466cbf5 + 59f665f commit e75b2c6

File tree

7 files changed

+133
-11
lines changed

7 files changed

+133
-11
lines changed

src/lib/common/markdown/Markdown.svelte

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
/** @type {boolean} */
2222
export let scrollable = false;
2323
24-
const scrollbarId = uuidv4();
25-
24+
const scrollbarId = `markdown-scrollbar-${uuidv4()}`;
2625
const options = {
2726
scrollbars: {
2827
visibility: 'auto',
@@ -42,7 +41,7 @@
4241
});
4342
4443
function initScrollbar() {
45-
const elem = document.querySelector(`#markdown-scrollbar-${scrollbarId}`);
44+
const elem = document.querySelector(`#${scrollbarId}`);
4645
if (elem) {
4746
// @ts-ignore
4847
const scrollbar = OverlayScrollbars(elem, options);
@@ -65,7 +64,7 @@
6564
</script>
6665
6766
<div
68-
id={`markdown-scrollbar-${scrollbarId}`}
67+
id={`${scrollbarId}`}
6968
class={`markdown-container markdown-lite ${containerClasses || 'text-white'}`}
7069
style={`${containerStyles}`}
7170
>

src/lib/helpers/enums.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ const richType = {
2727
Button: 'button_template',
2828
MultiSelect: 'multi-select_template',
2929
Generic: 'generic_template',
30-
Upload: 'upload_template'
30+
Upload: 'upload_template',
31+
ProgramCode: 'program_code',
3132
}
3233
export const RichType = Object.freeze(richType);
3334

src/lib/helpers/http.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export function replaceNewLine(text) {
212212
* @returns {string}
213213
*/
214214
export function replaceMarkdown(text) {
215-
let res = text.replace(/#([\s]+)/g, '\\# ').replace(/[-|=]{3,}/g, '');
215+
let res = text.replace(/#([\s]+)/g, '\\# ').replace(/[-|=]{3,}/g, '@@@');
216216

217217
let regex1 = new RegExp('\\*(.*)\\*', 'g');
218218
let regex2 = new RegExp('\\*([\\*]+)\\*', 'g');

src/lib/helpers/types/conversationTypes.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ IRichContent.prototype.elements;
105105
*/
106106
IRichContent.prototype.quick_replies;
107107

108+
/**
109+
* The language of the code rich content.
110+
*
111+
* @name language
112+
* @type {string}
113+
* @instance
114+
*/
115+
IRichContent.prototype.language;
116+
117+
108118
/**
109119
* @typedef {Object} TextMessage
110120
* @property {string} text
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<script>
2+
import { onMount } from "svelte";
3+
import { marked } from "marked";
4+
import 'overlayscrollbars/overlayscrollbars.css';
5+
import { OverlayScrollbars } from 'overlayscrollbars';
6+
import { v4 as uuidv4 } from 'uuid';
7+
8+
/** @type {import('$conversationTypes').ChatResponseModel?} */
9+
export let message;
10+
11+
/** @type {boolean} */
12+
export let scrollable = false;
13+
14+
const scrollbarId = `js-interpreter-scrollbar-${uuidv4()}`;
15+
const options = {
16+
scrollbars: {
17+
visibility: 'auto',
18+
autoHide: 'move',
19+
autoHideDelay: 100,
20+
dragScroll: true,
21+
clickScroll: false,
22+
theme: 'os-theme-dark',
23+
pointers: ['mouse', 'touch', 'pen']
24+
}
25+
};
26+
27+
onMount(() => {
28+
if (scrollable) {
29+
initScrollbar();
30+
}
31+
32+
initCode();
33+
});
34+
35+
function initScrollbar() {
36+
const elem = document.querySelector(`#${scrollbarId}`);
37+
if (elem) {
38+
// @ts-ignore
39+
const scrollbar = OverlayScrollbars(elem, options);
40+
}
41+
}
42+
43+
function initCode() {
44+
try {
45+
const text = message?.rich_content?.message?.text || message?.text || '';
46+
const parsedText = marked.lexer(text);
47+
// @ts-ignore
48+
const codeText = parsedText.filter(x => !!x.text).map(x => x.text).join('');
49+
loadScript(codeText);
50+
} catch (error) {
51+
console.error('Error parsing js code:', error);
52+
}
53+
}
54+
55+
/** @param {string} codeText */
56+
function loadScript(codeText) {
57+
const code = codeText.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, '$1');
58+
const scriptTags = [...codeText.matchAll(/<script\s+[^>]*src\s*=\s*["']([^"']+)["'][^>]*>/gi)];
59+
const matchedSrcs = scriptTags.filter(x => !!x[1]).map(x => x[1]);
60+
61+
if (matchedSrcs.length > 0) {
62+
const promises = matchedSrcs.map(x => loadScriptSrc(x));
63+
Promise.all(promises).then(() => setTimeout(() => eval(code), 0));
64+
} else {
65+
setTimeout(() => eval(code), 0);
66+
}
67+
}
68+
69+
/** @param {string} src */
70+
function loadScriptSrc(src) {
71+
return new Promise(resolve => {
72+
const curScripts = document.head.getElementsByTagName("script");
73+
const found = Array.from(curScripts).find(x => x.src === src);
74+
if (found) {
75+
found.remove();
76+
}
77+
78+
const script = document.createElement('script');
79+
script.async = false;
80+
script.src = src;
81+
script.onload = () => {
82+
setTimeout(() => {
83+
console.log(`Script loaded: ${src}`);
84+
resolve(true);
85+
}, 0);
86+
}
87+
script.onerror = () => {
88+
setTimeout(() => {
89+
console.log(`Error when loading script: ${src}`);
90+
resolve(false);
91+
}, 0);
92+
}
93+
document.head.appendChild(script);
94+
});
95+
}
96+
</script>
97+
98+
<div>
99+
{#if message?.text}
100+
<div class="mb-3">{message.text}</div>
101+
{/if}
102+
<div id={`${scrollbarId}`}>
103+
<div id={`chart-${message?.message_id}`} style="min-width: 1000px; max-height: 500px;"></div>
104+
</div>
105+
</div>

src/routes/chat/[agentId]/[conversationId]/rich-content/rc-message.svelte

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<script>
22
import Markdown from "$lib/common/markdown/Markdown.svelte";
3+
import { RichType } from "$lib/helpers/enums";
4+
import RcJsInterpreter from "./rc-js-interpreter.svelte";
35
4-
/** @type {any} */
6+
/** @type {import('$conversationTypes').ChatResponseModel?} */
57
export let message;
68
79
/** @type {string} */
@@ -22,7 +24,12 @@
2224
style={`${containerStyles}`}
2325
>
2426
<div class="flex-shrink-0 align-self-center">
25-
<Markdown containerClasses={markdownClasses} text={text} rawText />
27+
{#if message?.rich_content?.message?.rich_type === RichType.ProgramCode
28+
&& message?.rich_content?.message?.language === 'javascript'}
29+
<RcJsInterpreter message={message} scrollable />
30+
{:else}
31+
<Markdown containerClasses={markdownClasses} text={text} rawText />
32+
{/if}
2633
</div>
2734
</div>
2835
{/if}

src/routes/chat/[agentId]/[conversationId]/rich-content/rich-content.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@
4848
}
4949
</script>
5050
51+
52+
5153
{#if message?.rich_content?.editor === EditorType.File}
5254
<ChatAttachmentOptions options={options} disabled={disabled} onConfirm={(title, payload) => handleConfirm(title, payload)} />
53-
{/if}
54-
55-
{#if message?.rich_content?.editor !== EditorType.File}
55+
{:else}
5656
{#if !isComplexElement}
5757
<RcPlainOptions options={options} isMultiSelect={isMultiSelect} disabled={disabled} onConfirm={(title, payload) => handleConfirm(title, payload)} />
5858
{:else}

0 commit comments

Comments
 (0)