').addClass(`acc-message ${role}`);
@@ -320,7 +592,9 @@ function addMessage(role, content) {
if (role === 'user') {
avatarDiv.html('
');
} else if (role === 'system') {
@@ -329,103 +603,435 @@ function addMessage(role, content) {
const contentDiv = $('
').addClass('acc-message-content');
+ if (role === 'thought') {
+ msgDiv.addClass('acc-thought-message');
+ contentDiv.css({
+ 'font-style': 'italic',
+ 'color': '#aaa',
+ 'font-size': '0.9em'
+ });
+ }
+
msgDiv.append(avatarDiv);
msgDiv.append(contentDiv);
stream.append(msgDiv);
- if (role === 'assistant') {
- let i = 0;
- const speed = 2;
- const chunkSize = 5;
+ contentDiv.html(formattedContent);
+ stream.scrollTop(stream[0].scrollHeight);
+}
+
+function parseMarkdown(text) {
+ if (!text) return '';
+
+ // 1. Escape HTML (basic)
+ let html = text
+ .replace(/&/g, "&")
+ .replace(//g, ">");
+
+ // 2. Code Blocks (```...```)
+ html = html.replace(/```([\s\S]*?)```/g, '
$1
');
+
+ // 3. Inline Code (`...`)
+ html = html.replace(/`([^`]+)`/g, '
$1');
+
+ // 4. Headers
+ html = html.replace(/^#### (.*$)/gm, '
$1
');
+ html = html.replace(/^### (.*$)/gm, '
$1
');
+ html = html.replace(/^## (.*$)/gm, '
$1
');
+ html = html.replace(/^# (.*$)/gm, '
$1
');
+
+ // 5. Bold & Italic
+ html = html.replace(/\*\*(.*?)\*\*/g, '
$1');
+ html = html.replace(/\*(.*?)\*/g, '
$1');
+
+ // 6. Horizontal Rules
+ html = html.replace(/^[\*\-]{3,}$/gm, '
');
+
+ // 7. Lists
+ // Replace * or - at start of line with a bullet point
+ html = html.replace(/^\s*[\-\*]\s+(.*$)/gm, '
$1');
+
+ // 8. Line breaks
+ // We want to preserve line breaks, but block elements (h1-h6, pre, li) handle their own spacing.
+ // We can replace newlines with
ONLY if they are not around block tags.
+ // A simpler approach for chat: just replace all \n with
, and let CSS handle the extra spacing or remove it.
+ // But
content shouldn't have
.
+ // Let's just replace \n with
globally, but we need to protect blocks.
+ // Actually, the code block regex above already consumed the newlines inside it.
+ // So we can just replace remaining \n.
+
+ html = html.replace(/\n/g, '
');
+
+ return html;
+}
+
+function renderEditor() {
+ const container = $('#acc-preview-container');
+ const tabsContainer = $('.acc-preview-tabs');
+
+ // Don't fully empty if we want to preserve state, but for simplicity we re-render tabs
+ // To preserve scroll position or focus, we might need more complex logic.
+ // For now, re-rendering is acceptable as long as data is in openedFiles.
+
+ container.empty();
+ tabsContainer.empty();
+
+ if (openedFiles.size === 0) {
+ container.html('');
+ return;
+ }
+
+ // Ensure activeFileId is valid
+ if (!activeFileId || !openedFiles.has(activeFileId)) {
+ activeFileId = openedFiles.keys().next().value;
+ }
+
+ openedFiles.forEach((file, id) => {
+ const isActive = id === activeFileId;
- function typeWriter() {
- if (i < formattedContent.length) {
- let chunk = "";
- let count = 0;
-
- while (count < chunkSize && i < formattedContent.length) {
- if (formattedContent.charAt(i) === '<') {
- const tagEnd = formattedContent.indexOf('>', i);
- if (tagEnd !== -1) {
- chunk += formattedContent.substring(i, tagEnd + 1);
- i = tagEnd + 1;
- } else {
- chunk += formattedContent.charAt(i);
- i++;
- }
- } else {
- chunk += formattedContent.charAt(i);
- i++;
- }
- count++;
+ const tabBtn = $('