{{ 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}>`);
+ 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('
'); + continue; + } + + const headingMatch = line.match(/^(#{1,3})\s+(.+)$/); + + if (headingMatch) { + closeList(); + const level = headingMatch[1].length + 2; + html.push( + `
- ');
+ listMode = 'ol';
+ }
+
+ html.push(`
- ${this.formatInlineMarkdown(orderedMatch[1])} `); + continue; + } + + const unorderedMatch = line.match(/^[-*]\s+(.+)$/); + + if (unorderedMatch) { + if (listMode !== 'ul') { + closeList(); + html.push('
- ${this.formatInlineMarkdown(unorderedMatch[1])} `); + continue; + } + + closeList(); + html.push(`
- ');
+ listMode = 'ul';
+ }
+
+ 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, ''');
+ }
}