diff --git a/components/chat-page/chatMain.js b/components/chat-page/chatMain.js
index 3daf522..78a2d49 100644
--- a/components/chat-page/chatMain.js
+++ b/components/chat-page/chatMain.js
@@ -1,7 +1,7 @@
import useConversation from "../../global/useConversation.js";
import useHistory from "../../global/useHistory.js";
import useModelSettings from "../../global/useModelSettings.js";
-import { formatJSON } from "../../tools/conversationFormat.js";
+import { formatJSON, formatMarkdown } from "../../tools/conversationFormat.js";
import showMessage from "../../tools/message.js";
import request from "../../tools/request.js";
import getSVG from "../../tools/svgs.js";
@@ -168,7 +168,7 @@ async function sendMessage(message, send) {
top: main_elem.scrollHeight,
behavior: 'smooth'
})
- const [bot_answer, updateMessage] = createBlock('assistant');
+ const [bot_answer, elements] = createBlock('assistant');
main_elem.appendChild(bot_answer);
let content = ''
@@ -183,15 +183,12 @@ async function sendMessage(message, send) {
}
}, true)
- await send(response, msg=>{
- content = msg;
- updateMessage(msg);
- });
+ await send(response, elements, c=>content = c);
} catch(error) {
error;
if(content) content+=' ...'
content += '(Message Abroted)'
- updateMessage(content)
+ elements.main = content;
} finally {
appendConversationMessage([
{ role: 'user', message },
@@ -210,28 +207,43 @@ function sendMessageWaiting(msg) {
}
async function sendMessageStream(msg) {
- return sendMessage(msg, async (response, updateMessage) => {
- let resp_content = ''
+ return sendMessage(msg, async (response, elements, updateContent) => {
+ const { main, pending, started } = elements;
+ let msg_started = false;
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
- let pending_content = ''
+ let resp_content = '', pending_content = '', html_content = '', end_block = null
while(true) {
const {value, done} = await reader.read();
if(done) break;
pending_content += value;
- if(pending_content.includes('\n\n')) {
- const splitted_content = pending_content.split('\n\n')
+ if(pending_content.includes('}\n\n')) {
+ const splitted_content = pending_content.split('}\n\n')
try {
- const json = JSON.parse(splitted_content.shift().replace('data: ', ''))
+ if(!msg_started) {
+ msg_started = true;
+ started();
+ }
+ const json = JSON.parse(splitted_content.shift().replace('data: ', '')+'}')
+ pending_content = splitted_content.join('}\n\n')
resp_content += json.content;
- updateMessage(resp_content);
- pending_content = splitted_content.join('')
+ html_content += json.content;
+ const parsed_md = formatMarkdown(html_content, main, pending, end_block);
+ main_elem.scrollTo({
+ top: main_elem.scrollHeight,
+ behavior: 'smooth'
+ })
+ if(parsed_md) {
+ html_content = parsed_md[0];
+ end_block = parsed_md[1];
+ }
+ updateContent(resp_content);
if(json.stop) break;
- } catch(error) {
+ } catch(error) {
console.error(error);
}
}
}
- return resp_content;
+ formatMarkdown(html_content, main, pending, end_block, true);
})
}
@@ -262,28 +274,28 @@ function createBlock(role, msg = '') {
block.appendChild(message);
if(role === 'assistant') {
- message.innerHTML = `
- ${getSVG('circle-fill', 'dot-animation dot-1')}
- ${getSVG('circle-fill', 'dot-animation dot-2')}
- ${getSVG('circle-fill', 'dot-animation dot-3')}`
-
block.insertAdjacentHTML("afterbegin", `
`)
+ if(!msg) {
+ message.innerHTML = `
+ ${getSVG('circle-fill', 'dot-animation dot-1')}
+ ${getSVG('circle-fill', 'dot-animation dot-2')}
+ ${getSVG('circle-fill', 'dot-animation dot-3')}`
+ }
}
+ const pending_elem = document.createElement('div');
+
if(msg) {
- message.textContent = msg;
+ message.appendChild(pending_elem);
+ formatMarkdown(msg, message, pending_elem, null, true);
}
return [
block,
- (msg) => {
- if(msg) {
- message.textContent = msg;
- main_elem.scrollTo({
- top: main_elem.scrollHeight,
- behavior: 'smooth'
- })
- }
+ {
+ main: message,
+ pending: pending_elem,
+ started: ()=>{ message.innerHTML = ''; message.appendChild(pending_elem); }
}
]
}
\ No newline at end of file
diff --git a/styles/chat_page.css b/styles/chat_page.css
index 9252484..369b654 100644
--- a/styles/chat_page.css
+++ b/styles/chat_page.css
@@ -247,6 +247,20 @@
animation-delay: .6s;
}
+.message .code-block {
+ background-color: #1f1f1f;
+ color: white;
+ padding: 20px 15px;
+ border-radius: 7px;
+ margin-top: 5px;
+}
+
+.message .inline-code {
+ background-color: var(--gray-bg);
+ padding: 3px 7px;
+ border-radius: 6px;
+}
+
#chat-page #chat-main #submit-chat {
position: absolute;
width: calc(100% - var(--conversation-main-side-padding));
diff --git a/tools/conversationFormat.js b/tools/conversationFormat.js
index 1730b3c..b2e5df5 100644
--- a/tools/conversationFormat.js
+++ b/tools/conversationFormat.js
@@ -1,8 +1,74 @@
-// export function formatMarkdown(input) {
-// input.split('\n').map(line=>{
-
-// })
-// }
+export function formatMarkdown(str, target_elem, pending_elem, end_special_block = null, force = false) {
+ if(!str.includes('\n') && !force) {
+ pending_elem.textContent = str;
+ return null;
+ }
+ const whole_lines = str.split('\n');
+ let content_left = ''
+ if(!force) content_left = whole_lines.pop();
+ pending_elem.textContent = content_left;
+
+ function parseSingleLine(pattern_name) {
+ return (_, group_1, group_2) => {
+ switch(pattern_name) {
+ case 'header':
+ return `${group_2}`;
+ case 'bold': return `${group_1}`;
+ case 'italic': return `${group_1}`;
+ case 'bold-italic': return `${group_1}`;
+ case 'hr': return '';
+ case 'inline-code': return `${group_2||group_1}`;
+ }
+ }
+ }
+
+ function parseLine(line) {
+ // test if this line is start/end of a code block
+ const match_code = line.match(/`{3,}/)
+ if(match_code) {
+ if(end_special_block) {
+ if(match_code[0] === end_special_block) {
+ end_special_block = null;
+ target_elem.appendChild(pending_elem)
+ return;
+ }
+ } else{
+ const pattern = match_code[0]
+ const elem = document.createElement('div')
+ elem.className = 'code-block';
+ target_elem.appendChild(elem);
+
+ end_special_block = pattern;
+ elem.appendChild(pending_elem);
+ return;
+ }
+ }
+
+ // replace white spaces, no need for single white space
+ line.replaceAll(' ', ' ');
+ if(end_special_block) {
+ pending_elem.insertAdjacentHTML("beforebegin",line||"");
+ } else {
+ const parsed_line = !line ? "" : line
+ .replace(/(#{6}|#{5}|#{4}|#{3}|#{2}|#{1}) (.*$)/, parseSingleLine('header'))
+ .replaceAll(/[*_]{3,}(.+?)[*_]{3,}/g, parseSingleLine('bold-italic'))
+ .replaceAll(/\*\*(.+?)\*\*/g, parseSingleLine('bold'))
+ .replaceAll(/__(.+?)__/g, parseSingleLine('italic'))
+ .replaceAll(/^(\*|-){3,}$/g, parseSingleLine('hr'))
+ .replaceAll(/``(.+?)``|`(.+?)`/g, parseSingleLine('inline-code'))
+
+ pending_elem.insertAdjacentHTML("beforebegin", parsed_line);
+ }
+ }
+
+ whole_lines.forEach(line=>{
+ parseLine(line)
+ })
+
+ force && pending_elem.remove();
+
+ return [content_left, end_special_block]
+}
export function formatJSON(conversation, {createdAt, name}) {
const json =