@qnote/q-ai-note 1.0.11 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/app.js +169 -8
- package/dist/web/styles.css +59 -0
- package/package.json +1 -1
package/dist/web/app.js
CHANGED
|
@@ -776,8 +776,49 @@ function populateParentSelect(items, preferredParentId = null) {
|
|
|
776
776
|
select.value = hasExpectedValue ? expectedValue : '';
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
-
function
|
|
780
|
-
|
|
779
|
+
function normalizeAssistantResponseText(raw) {
|
|
780
|
+
let text = String(raw || '').trim();
|
|
781
|
+
if (!text) return '';
|
|
782
|
+
// Recover nested response payload when parser fallback returns a JSON string.
|
|
783
|
+
for (let depth = 0; depth < 2; depth += 1) {
|
|
784
|
+
const maybeJson = text.trim();
|
|
785
|
+
if (!maybeJson.startsWith('{') || !maybeJson.endsWith('}')) break;
|
|
786
|
+
try {
|
|
787
|
+
const parsed = JSON.parse(maybeJson);
|
|
788
|
+
if (parsed && typeof parsed.response === 'string' && parsed.response.trim()) {
|
|
789
|
+
text = String(parsed.response).trim();
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
} catch {
|
|
793
|
+
// Ignore invalid json and keep original text.
|
|
794
|
+
}
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
return text;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function unwrapMarkdownFence(rawText) {
|
|
801
|
+
const text = String(rawText || '').replace(/\r\n/g, '\n').trim();
|
|
802
|
+
const start = text.match(/^```(?:markdown|md)\s*\n?/i);
|
|
803
|
+
if (!start) return null;
|
|
804
|
+
let body = text.slice(start[0].length);
|
|
805
|
+
const endIdx = body.lastIndexOf('\n```');
|
|
806
|
+
if (endIdx >= 0) {
|
|
807
|
+
body = body.slice(0, endIdx);
|
|
808
|
+
} else {
|
|
809
|
+
body = body.replace(/```$/m, '');
|
|
810
|
+
}
|
|
811
|
+
return body.trim();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function renderMarkdownSnippet(text, options = {}) {
|
|
815
|
+
const enableMermaid = Boolean(options.enableMermaid);
|
|
816
|
+
const normalizedSource = normalizeAssistantResponseText(text);
|
|
817
|
+
const fencedMarkdown = unwrapMarkdownFence(normalizedSource);
|
|
818
|
+
if (fencedMarkdown !== null) {
|
|
819
|
+
return renderMarkdownSnippet(fencedMarkdown, options);
|
|
820
|
+
}
|
|
821
|
+
const source = String(normalizedSource || '').replace(/\r\n/g, '\n');
|
|
781
822
|
const lines = source.split('\n');
|
|
782
823
|
const blocks = [];
|
|
783
824
|
let listItems = [];
|
|
@@ -801,12 +842,61 @@ function renderMarkdownSnippet(text) {
|
|
|
801
842
|
return html;
|
|
802
843
|
};
|
|
803
844
|
|
|
804
|
-
|
|
845
|
+
const splitTableCells = (line) => {
|
|
846
|
+
let raw = String(line || '').trim();
|
|
847
|
+
if (raw.startsWith('|')) raw = raw.slice(1);
|
|
848
|
+
if (raw.endsWith('|')) raw = raw.slice(0, -1);
|
|
849
|
+
return raw.split('|').map((cell) => String(cell || '').trim());
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
853
|
+
const line = lines[idx];
|
|
805
854
|
const trimmed = line.trim();
|
|
806
855
|
if (!trimmed) {
|
|
807
856
|
flushList();
|
|
808
857
|
continue;
|
|
809
858
|
}
|
|
859
|
+
const fenceMatch = trimmed.match(/^```([a-zA-Z0-9_-]+)?\s*$/);
|
|
860
|
+
if (fenceMatch) {
|
|
861
|
+
flushList();
|
|
862
|
+
const fenceLang = String(fenceMatch[1] || '').toLowerCase();
|
|
863
|
+
const codeLines = [];
|
|
864
|
+
idx += 1;
|
|
865
|
+
while (idx < lines.length && !/^```/.test(String(lines[idx] || '').trim())) {
|
|
866
|
+
codeLines.push(lines[idx]);
|
|
867
|
+
idx += 1;
|
|
868
|
+
}
|
|
869
|
+
const code = codeLines.join('\n');
|
|
870
|
+
if (fenceLang === 'mermaid' && enableMermaid) {
|
|
871
|
+
blocks.push(`<div class="chat-mermaid" data-mermaid-code="${encodeURIComponent(code)}"></div>`);
|
|
872
|
+
} else {
|
|
873
|
+
blocks.push(`<pre><code>${safeText(code)}</code></pre>`);
|
|
874
|
+
}
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (trimmed.includes('|') && idx + 1 < lines.length) {
|
|
878
|
+
const nextTrimmed = String(lines[idx + 1] || '').trim();
|
|
879
|
+
const isTableDivider = /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(nextTrimmed);
|
|
880
|
+
if (isTableDivider) {
|
|
881
|
+
flushList();
|
|
882
|
+
const headerCells = splitTableCells(trimmed);
|
|
883
|
+
const bodyRows = [];
|
|
884
|
+
idx += 2;
|
|
885
|
+
while (idx < lines.length) {
|
|
886
|
+
const row = String(lines[idx] || '').trim();
|
|
887
|
+
if (!row || !row.includes('|')) {
|
|
888
|
+
idx -= 1;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
bodyRows.push(splitTableCells(row));
|
|
892
|
+
idx += 1;
|
|
893
|
+
}
|
|
894
|
+
const headerHtml = `<tr>${headerCells.map((cell) => `<th>${renderInline(cell)}</th>`).join('')}</tr>`;
|
|
895
|
+
const bodyHtml = bodyRows.map((cells) => `<tr>${cells.map((cell) => `<td>${renderInline(cell)}</td>`).join('')}</tr>`).join('');
|
|
896
|
+
blocks.push(`<table class="md-table"><thead>${headerHtml}</thead><tbody>${bodyHtml}</tbody></table>`);
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
810
900
|
if (/^[-*]\s+/.test(trimmed)) {
|
|
811
901
|
listItems.push(renderInline(trimmed.replace(/^[-*]\s+/, '')));
|
|
812
902
|
continue;
|
|
@@ -824,6 +914,73 @@ function renderMarkdownSnippet(text) {
|
|
|
824
914
|
return blocks.join('');
|
|
825
915
|
}
|
|
826
916
|
|
|
917
|
+
let mermaidLoadPromise = null;
|
|
918
|
+
|
|
919
|
+
function ensureMermaidRuntime() {
|
|
920
|
+
if (window.mermaid && typeof window.mermaid.render === 'function') {
|
|
921
|
+
return Promise.resolve(window.mermaid);
|
|
922
|
+
}
|
|
923
|
+
if (mermaidLoadPromise) return mermaidLoadPromise;
|
|
924
|
+
mermaidLoadPromise = new Promise((resolve, reject) => {
|
|
925
|
+
const existing = document.querySelector('script[data-mermaid-runtime="1"]');
|
|
926
|
+
if (existing) {
|
|
927
|
+
existing.addEventListener('load', () => {
|
|
928
|
+
if (window.mermaid && typeof window.mermaid.initialize === 'function') {
|
|
929
|
+
window.mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' });
|
|
930
|
+
}
|
|
931
|
+
resolve(window.mermaid);
|
|
932
|
+
}, { once: true });
|
|
933
|
+
existing.addEventListener('error', () => reject(new Error('Mermaid script load failed')), { once: true });
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const script = document.createElement('script');
|
|
937
|
+
script.src = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js';
|
|
938
|
+
script.async = true;
|
|
939
|
+
script.setAttribute('data-mermaid-runtime', '1');
|
|
940
|
+
script.addEventListener('load', () => {
|
|
941
|
+
if (window.mermaid && typeof window.mermaid.initialize === 'function') {
|
|
942
|
+
window.mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' });
|
|
943
|
+
}
|
|
944
|
+
resolve(window.mermaid);
|
|
945
|
+
}, { once: true });
|
|
946
|
+
script.addEventListener('error', () => reject(new Error('Mermaid script load failed')), { once: true });
|
|
947
|
+
document.head.appendChild(script);
|
|
948
|
+
});
|
|
949
|
+
return mermaidLoadPromise;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
async function renderMermaidInContainer(container) {
|
|
953
|
+
if (!(container instanceof HTMLElement)) return;
|
|
954
|
+
const blocks = Array.from(container.querySelectorAll('.chat-mermaid[data-mermaid-code]:not([data-mermaid-rendered="1"])'));
|
|
955
|
+
if (!blocks.length) return;
|
|
956
|
+
let mermaid = null;
|
|
957
|
+
try {
|
|
958
|
+
mermaid = await ensureMermaidRuntime();
|
|
959
|
+
} catch {
|
|
960
|
+
mermaid = null;
|
|
961
|
+
}
|
|
962
|
+
for (let idx = 0; idx < blocks.length; idx += 1) {
|
|
963
|
+
const block = blocks[idx];
|
|
964
|
+
const code = decodeURIComponent(String(block.getAttribute('data-mermaid-code') || ''));
|
|
965
|
+
if (!code) {
|
|
966
|
+
block.setAttribute('data-mermaid-rendered', '1');
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (mermaid && typeof mermaid.render === 'function') {
|
|
970
|
+
try {
|
|
971
|
+
const id = `chat-mermaid-${Date.now()}-${idx}`;
|
|
972
|
+
const rendered = await mermaid.render(id, code);
|
|
973
|
+
block.innerHTML = rendered?.svg || `<pre><code>${safeText(code)}</code></pre>`;
|
|
974
|
+
} catch {
|
|
975
|
+
block.innerHTML = `<pre><code>${safeText(code)}</code></pre>`;
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
978
|
+
block.innerHTML = `<pre><code>${safeText(code)}</code></pre>`;
|
|
979
|
+
}
|
|
980
|
+
block.setAttribute('data-mermaid-rendered', '1');
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
827
984
|
function getNodeById(nodeId) {
|
|
828
985
|
return state.currentSandbox?.items?.find((item) => item.id === nodeId) || null;
|
|
829
986
|
}
|
|
@@ -1601,9 +1758,10 @@ async function loadSandboxChats(sandboxId) {
|
|
|
1601
1758
|
chats.map((chat) => renderChatEntry(chat, {
|
|
1602
1759
|
safeText,
|
|
1603
1760
|
renderAIActionMessage,
|
|
1604
|
-
renderContent: (content) => renderMarkdownSnippet(content),
|
|
1761
|
+
renderContent: (content) => renderMarkdownSnippet(content, { enableMermaid: true }),
|
|
1605
1762
|
})),
|
|
1606
1763
|
);
|
|
1764
|
+
await renderMermaidInContainer(messages);
|
|
1607
1765
|
|
|
1608
1766
|
messages.scrollTop = messages.scrollHeight;
|
|
1609
1767
|
}
|
|
@@ -1635,7 +1793,7 @@ function renderAIActionMessage(action) {
|
|
|
1635
1793
|
};
|
|
1636
1794
|
|
|
1637
1795
|
if (actionType === 'response' || actionType === 'clarify') {
|
|
1638
|
-
return `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || action.observation || '')}</div>`;
|
|
1796
|
+
return `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || action.observation || '', { enableMermaid: true })}</div>`;
|
|
1639
1797
|
}
|
|
1640
1798
|
else if (actionType === 'confirm' && action.confirm_items) {
|
|
1641
1799
|
// Skip confirm, go directly to done
|
|
@@ -1662,7 +1820,7 @@ function renderAIActionMessage(action) {
|
|
|
1662
1820
|
</div>`;
|
|
1663
1821
|
}
|
|
1664
1822
|
|
|
1665
|
-
return `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || '')}</div>`;
|
|
1823
|
+
return `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || '', { enableMermaid: true })}</div>`;
|
|
1666
1824
|
}
|
|
1667
1825
|
|
|
1668
1826
|
window.undoOperation = async function(operationId, btn) {
|
|
@@ -2666,7 +2824,8 @@ async function initApp() {
|
|
|
2666
2824
|
const actionType = action.action;
|
|
2667
2825
|
|
|
2668
2826
|
if (actionType === 'response' || actionType === 'clarify') {
|
|
2669
|
-
messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || action.observation || '')}</div>`);
|
|
2827
|
+
messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || action.observation || '', { enableMermaid: true })}</div>`);
|
|
2828
|
+
await renderMermaidInContainer(messages);
|
|
2670
2829
|
messages.scrollTop = messages.scrollHeight;
|
|
2671
2830
|
|
|
2672
2831
|
if (actionType === 'clarify') {
|
|
@@ -2754,11 +2913,13 @@ async function initApp() {
|
|
|
2754
2913
|
}
|
|
2755
2914
|
else if (actionType === 'done') {
|
|
2756
2915
|
messages.insertAdjacentHTML('beforeend', renderAIActionMessage(action));
|
|
2916
|
+
await renderMermaidInContainer(messages);
|
|
2757
2917
|
messages.scrollTop = messages.scrollHeight;
|
|
2758
2918
|
state.pendingAction = null;
|
|
2759
2919
|
}
|
|
2760
2920
|
else if (actionType === 'stop') {
|
|
2761
|
-
messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${renderMarkdownSnippet(action.observation || '已取消操作')}</div>`);
|
|
2921
|
+
messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${renderMarkdownSnippet(action.observation || '已取消操作', { enableMermaid: true })}</div>`);
|
|
2922
|
+
await renderMermaidInContainer(messages);
|
|
2762
2923
|
messages.scrollTop = messages.scrollHeight;
|
|
2763
2924
|
state.pendingAction = null;
|
|
2764
2925
|
}
|
package/dist/web/styles.css
CHANGED
|
@@ -1204,6 +1204,65 @@ h2 {
|
|
|
1204
1204
|
border-bottom-left-radius: 4px;
|
|
1205
1205
|
}
|
|
1206
1206
|
|
|
1207
|
+
.chat-message.assistant h1,
|
|
1208
|
+
.chat-message.assistant h2,
|
|
1209
|
+
.chat-message.assistant h3,
|
|
1210
|
+
.chat-message.assistant h4 {
|
|
1211
|
+
margin: 2px 0 6px;
|
|
1212
|
+
line-height: 1.35;
|
|
1213
|
+
font-weight: 600;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.chat-message.assistant h1 {
|
|
1217
|
+
font-size: 16px;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
.chat-message.assistant h2 {
|
|
1221
|
+
font-size: 15px;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
.chat-message.assistant h3,
|
|
1225
|
+
.chat-message.assistant h4 {
|
|
1226
|
+
font-size: 14px;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
.chat-message.assistant p,
|
|
1230
|
+
.chat-message.assistant ul,
|
|
1231
|
+
.chat-message.assistant ol,
|
|
1232
|
+
.chat-message.assistant pre {
|
|
1233
|
+
margin: 4px 0;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
.chat-message.assistant .md-table {
|
|
1237
|
+
border-collapse: collapse;
|
|
1238
|
+
width: 100%;
|
|
1239
|
+
margin: 6px 0;
|
|
1240
|
+
font-size: 12px;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.chat-message.assistant .md-table th,
|
|
1244
|
+
.chat-message.assistant .md-table td {
|
|
1245
|
+
border: 1px solid #d5deed;
|
|
1246
|
+
padding: 4px 6px;
|
|
1247
|
+
text-align: left;
|
|
1248
|
+
vertical-align: top;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.chat-message.assistant .md-table th {
|
|
1252
|
+
background: #eef4ff;
|
|
1253
|
+
font-weight: 600;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.chat-message.assistant .chat-mermaid {
|
|
1257
|
+
width: 100%;
|
|
1258
|
+
overflow-x: auto;
|
|
1259
|
+
background: #fff;
|
|
1260
|
+
border: 1px solid #d5deed;
|
|
1261
|
+
border-radius: 8px;
|
|
1262
|
+
padding: 6px;
|
|
1263
|
+
margin: 6px 0;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1207
1266
|
.chat-message.assistant.loading {
|
|
1208
1267
|
color: var(--text-secondary);
|
|
1209
1268
|
font-style: italic;
|