diff --git a/listify-client/src/app/assistant/assistant-chat.component.html b/listify-client/src/app/assistant/assistant-chat.component.html index a6a407e..4384e8e 100644 --- a/listify-client/src/app/assistant/assistant-chat.component.html +++ b/listify-client/src/app/assistant/assistant-chat.component.html @@ -10,7 +10,10 @@
@for (message of messages(); track $index) {
-

{{ message.content }}

+
@if (message.actions?.length) {
diff --git a/listify-client/src/app/assistant/assistant-chat.component.scss b/listify-client/src/app/assistant/assistant-chat.component.scss index 90eca07..044ec6d 100644 --- a/listify-client/src/app/assistant/assistant-chat.component.scss +++ b/listify-client/src/app/assistant/assistant-chat.component.scss @@ -68,10 +68,45 @@ color: var(--mat-sys-on-surface); } -.message p { - margin: 0; +.message-content { + display: grid; + gap: 0.5rem; overflow-wrap: anywhere; - line-height: 1.4; + line-height: 1.45; +} + +.message-content :where(p, h3, h4, h5, ol, ul) { + margin: 0; +} + +.message-content :where(h3, h4, h5) { + font-size: 0.98rem; + font-weight: 600; +} + +.message-content :where(ol, ul) { + display: grid; + gap: 0.35rem; + padding-left: 1.25rem; +} + +.message-content li { + overflow-wrap: anywhere; +} + +.message-content hr { + width: 100%; + margin: 0.15rem 0; + border: 0; + border-top: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 75%, transparent); +} + +.message-content code { + padding: 0.08rem 0.25rem; + border-radius: 4px; + background: color-mix(in srgb, var(--mat-sys-outline-variant) 22%, transparent); + font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', monospace; + font-size: 0.9em; } .action-list { diff --git a/listify-client/src/app/assistant/assistant-chat.component.ts b/listify-client/src/app/assistant/assistant-chat.component.ts index 2dfd9fe..301dcbb 100644 --- a/listify-client/src/app/assistant/assistant-chat.component.ts +++ b/listify-client/src/app/assistant/assistant-chat.component.ts @@ -103,4 +103,93 @@ export class AssistantChatComponent { return `Item hinzugefuegt: ${action.itemTitle}`; } + + protected formatMessage(content: string): string { + const lines = this.escapeHtml(content).split(/\r?\n/); + const html: string[] = []; + let listMode: 'ol' | 'ul' | null = null; + + const closeList = () => { + if (!listMode) { + return; + } + + html.push(``); + listMode = null; + }; + + for (const rawLine of lines) { + const line = rawLine.trim(); + + if (!line) { + closeList(); + continue; + } + + if (/^-{3,}$/.test(line)) { + closeList(); + html.push('
'); + continue; + } + + const headingMatch = line.match(/^(#{1,3})\s+(.+)$/); + + if (headingMatch) { + closeList(); + const level = headingMatch[1].length + 2; + html.push( + `${this.formatInlineMarkdown(headingMatch[2])}`, + ); + continue; + } + + const orderedMatch = line.match(/^\d+\.\s+(.+)$/); + + if (orderedMatch) { + if (listMode !== 'ol') { + closeList(); + html.push('
    '); + listMode = 'ol'; + } + + html.push(`
  1. ${this.formatInlineMarkdown(orderedMatch[1])}
  2. `); + continue; + } + + const unorderedMatch = line.match(/^[-*]\s+(.+)$/); + + if (unorderedMatch) { + if (listMode !== 'ul') { + closeList(); + html.push('
      '); + listMode = 'ul'; + } + + html.push(`
    • ${this.formatInlineMarkdown(unorderedMatch[1])}
    • `); + continue; + } + + closeList(); + html.push(`

      ${this.formatInlineMarkdown(line)}

      `); + } + + closeList(); + return html.join(''); + } + + private formatInlineMarkdown(value: string): string { + return value + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\*(.+?)\*/g, '$1') + .replace(/`(.+?)`/g, '$1'); + } + + private escapeHtml(value: string): string { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } }