Parse assistant messages as Markdown (marked.js)
This commit is contained in:
1 parent
6556d62f17
commit
18b2feef4a
7 files changed
+157
-45
No files matched your search
-17
@@ -1,17 +0,0 @@
|
||||
# Gott – Zusammenfassung
|
||||
|
||||
Self-hosted AI-Coding-Agent auf Basis der Anthropic API.
|
||||
|
||||
Web-UI für Chat-Sessions mit konfigurierbaren Agenten, die Tool-Zugriff (Dateien lesen/schreiben/bearbeiten, Shell-Befehle ausführen) auf definierte Workspace-Verzeichnisse haben.
|
||||
|
||||
## Stack
|
||||
|
||||
- .NET 10, SQLite, Vanilla-Frontend
|
||||
- Port 19000
|
||||
|
||||
## Struktur
|
||||
|
||||
- **Endpoints/** – API-Routen (Chat, Sessions, Agents, History, Workspace)
|
||||
- **Services/** – Anthropic-API-Anbindung, Tool-Ausführung, Datenbankzugriff (SQLite)
|
||||
- **Models/** – Datenmodelle (Sessions, Messages, Agents, Chains, Archives)
|
||||
- **wwwroot/** – Frontend (statische Dateien)
|
||||
@@ -1,27 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
_hostname=$(hostname)
|
||||
|
||||
if [ "$_hostname" = "stein2" ]; then
|
||||
cd /home/node/.openclaw/workspace/projects/gott
|
||||
systemctl stop gott
|
||||
elif [ "$_hostname" = "meleki-container" ]; then
|
||||
cd ~/.openclaw/workspace/projects/gott
|
||||
else
|
||||
echo "$_hostname: deployment not allowed"
|
||||
exit
|
||||
fi
|
||||
|
||||
cd /app/gott
|
||||
export PATH="$HOME/.dotnet:$PATH"
|
||||
|
||||
systemctl stop gott
|
||||
dotnet publish -c Release -o publish
|
||||
|
||||
if [ "$_hostname" = "stein2" ]; then
|
||||
systemctl start gott
|
||||
elif [ "$_hostname" = "meleki-container" ]; then
|
||||
ssh -o ConnectTimeout=5 meleki@192.168.100.200 "nohup sudo systemctl restart gott > /dev/null 2>&1 &"
|
||||
fi
|
||||
|
||||
systemctl start gott
|
||||
echo "OK"
|
||||
@@ -0,0 +1,125 @@
|
||||
/* Markdown in Chat Bubbles */
|
||||
|
||||
.msg.markdown {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.msg.markdown > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.msg.markdown > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.msg.markdown p {
|
||||
margin: 0 0 var(--s3) 0;
|
||||
}
|
||||
|
||||
.msg.markdown p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.msg.markdown h1,
|
||||
.msg.markdown h2,
|
||||
.msg.markdown h3,
|
||||
.msg.markdown h4,
|
||||
.msg.markdown h5,
|
||||
.msg.markdown h6 {
|
||||
margin: var(--s4) 0 var(--s2) 0;
|
||||
}
|
||||
|
||||
.msg.markdown h1:first-child,
|
||||
.msg.markdown h2:first-child,
|
||||
.msg.markdown h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.msg.markdown ul,
|
||||
.msg.markdown ol {
|
||||
margin: 0 0 var(--s3) 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.msg.markdown li {
|
||||
margin-bottom: var(--s1);
|
||||
}
|
||||
|
||||
.msg.markdown li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.msg.markdown code {
|
||||
background: var(--f3-bg);
|
||||
padding: .1em .3em;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.msg.markdown pre {
|
||||
background: var(--f3-bg);
|
||||
border: var(--border-width) solid var(--f3-border);
|
||||
padding: var(--s3);
|
||||
margin: 0 0 var(--s3) 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.msg.markdown pre:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.msg.markdown pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.msg.markdown blockquote {
|
||||
border-left: 3px solid var(--f3-border);
|
||||
margin: 0 0 var(--s3) 0;
|
||||
padding: var(--s2) var(--s3);
|
||||
opacity: var(--semi-transparent);
|
||||
}
|
||||
|
||||
.msg.markdown blockquote:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.msg.markdown hr {
|
||||
border: none;
|
||||
border-top: var(--border-width) solid var(--f3-border);
|
||||
margin: var(--s3) 0;
|
||||
}
|
||||
|
||||
.msg.markdown table {
|
||||
border-collapse: collapse;
|
||||
margin: 0 0 var(--s3) 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.msg.markdown table:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.msg.markdown th,
|
||||
.msg.markdown td {
|
||||
border: var(--border-width) solid var(--f3-border);
|
||||
padding: var(--s2) var(--s3);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.msg.markdown th {
|
||||
background: var(--f3-bg);
|
||||
}
|
||||
|
||||
.msg.markdown a {
|
||||
color: var(--primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.msg.markdown img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.msg.markdown input[type="checkbox"] {
|
||||
margin-right: var(--s2);
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
<link rel="stylesheet" href="css/chat.css">
|
||||
<link rel="stylesheet" href="css/placeholder.css">
|
||||
<link rel="stylesheet" href="css/file-explorer.css">
|
||||
<link rel="stylesheet" href="css/markdown.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -162,6 +163,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="js/lib/marked.min.js"></script>
|
||||
<script src="js/ui.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/session.js"></script>
|
||||
|
||||
@@ -109,8 +109,13 @@ async function loadLatestArchive() {
|
||||
const div = document.createElement('div');
|
||||
switch (msg.type) {
|
||||
case 'text':
|
||||
div.className = msg.role === 'user' ? 'msg user archived' : 'msg assistant archived';
|
||||
div.textContent = (msg.content || '').trim();
|
||||
if (msg.role === 'user') {
|
||||
div.className = 'msg user archived';
|
||||
div.textContent = (msg.content || '').trim();
|
||||
} else {
|
||||
div.className = 'msg assistant markdown archived';
|
||||
div.innerHTML = renderMarkdown(msg.content);
|
||||
}
|
||||
if (msg.role === 'user' && msg.attachmentCount > 0) {
|
||||
let names = [];
|
||||
try { if (msg.attachmentNames) names = JSON.parse(msg.attachmentNames); } catch {}
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* marked v15.0.7 - a markdown parser
|
||||
* Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/markedjs/marked
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s={exec:()=>null};function r(e,t=""){let n="string"==typeof e?e:e.source;const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(i.caret,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}const i={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[\t ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},l=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,o=/(?:[*+-]|\d{1,9}[.)])/,a=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,c=r(a).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),h=r(a).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),p=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,u=/(?!\s*\])(?:\\.|[^\[\]\\])+/,g=r(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",u).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),k=r(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,o).getRegex(),d="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",f=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,x=r("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",f).replace("tag",d).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegexLine truncated
|
||||
+14
-4
@@ -4,6 +4,15 @@ const $ = (sel) => document.querySelector(sel);
|
||||
const chatEl = () => $('#chat');
|
||||
const metaEl = () => $('#meta');
|
||||
|
||||
// ── Markdown ──
|
||||
|
||||
marked.setOptions({ breaks: true, gfm: true });
|
||||
|
||||
function renderMarkdown(text) {
|
||||
if (!text) return '';
|
||||
return marked.parse(text.trim());
|
||||
}
|
||||
|
||||
function scrollDown() {
|
||||
const chat = chatEl();
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
@@ -32,8 +41,8 @@ function addUserBubble(text, streamed = false) {
|
||||
|
||||
function addAssistantBubble(text) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'msg assistant';
|
||||
div.textContent = (text || '').trim();
|
||||
div.className = 'msg assistant markdown';
|
||||
div.innerHTML = renderMarkdown(text);
|
||||
chatEl().appendChild(div);
|
||||
scrollDown();
|
||||
return div;
|
||||
@@ -56,7 +65,7 @@ let _streamText = '';
|
||||
|
||||
function startStreamBubble() {
|
||||
_streamDiv = document.createElement('div');
|
||||
_streamDiv.className = 'msg assistant streamed';
|
||||
_streamDiv.className = 'msg assistant markdown streamed';
|
||||
_streamDiv.innerHTML = '<span class="cursor"></span>';
|
||||
_streamText = '';
|
||||
chatEl().appendChild(_streamDiv);
|
||||
@@ -66,7 +75,7 @@ function startStreamBubble() {
|
||||
function appendToStream(text) {
|
||||
if (!_streamDiv) startStreamBubble();
|
||||
_streamText += text;
|
||||
_streamDiv.textContent = _streamText;
|
||||
_streamDiv.innerHTML = renderMarkdown(_streamText);
|
||||
const cursor = document.createElement('span');
|
||||
cursor.className = 'cursor';
|
||||
_streamDiv.appendChild(cursor);
|
||||
@@ -78,6 +87,7 @@ function endStreamBubble() {
|
||||
const cursor = _streamDiv.querySelector('.cursor');
|
||||
if (cursor) cursor.remove();
|
||||
if (!_streamText.trim()) _streamDiv.remove();
|
||||
else _streamDiv.innerHTML = renderMarkdown(_streamText);
|
||||
}
|
||||
_streamDiv = null;
|
||||
_streamText = '';
|
||||
|
||||
Reference in new issue
Block a user