Skip to content

Commit

Permalink
fix(editor): Fix styling and typography in AI Assistant chat (#10895)
Browse files Browse the repository at this point in the history
  • Loading branch information
MiloradFilipovic authored Sep 23, 2024
1 parent 67fb6d6 commit 57ff3cc
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 59 deletions.
2 changes: 1 addition & 1 deletion cypress/pages/features/ai-assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class AIAssistant extends BasePage {
cy.getByTestId('node-error-view-ask-assistant-button').find('button').first(),
credentialEditAssistantButton: () =>
cy.getByTestId('credentail-edit-ask-assistant-button').find('button').first(),
codeSnippet: () => cy.getByTestId('assistant-code-snippet'),
codeSnippet: () => cy.getByTestId('assistant-code-snippet-content'),
};

actions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,107 @@ WithCodeSnippet.args = {
},
]),
};

export const RichTextMessage = Template.bind({});
RichTextMessage.args = {
user: {
firstName: 'Kobi',
lastName: 'Dog',
},
messages: getMessages([
{
id: '29083188',
role: 'user',
type: 'text',
content: 'Hey',
read: true,
},
{
id: '29083188',
type: 'text',
role: 'assistant',
content: 'Hello Kobi! How can I assist you with n8n today?',
read: true,
},
{
id: '21514129',
role: 'user',
type: 'text',
content: 'Can you show me a message example with paragraphs, lists and links?',
read: true,
},
{
id: '21514129',
type: 'text',
role: 'assistant',
content:
"Sure: \n\nTo connect your Slack account to n8n, follow these steps:\n\n1. Open your [Slack API Apps](https://api.slack.com/apps) page.\n2. Select **Create New App > From scratch**.\n3. Enter an **App Name**.\n4. Select the **Workspace** where you'll be developing your app.\n5. Select **Create App**.\n6. In **Basic Information**, open the **App Credentials** section.\n7. Copy the **Client ID** and **Client Secret**. Paste these into the corresponding fields in n8n.\n8. In **Basic Information > Building Apps for Slack**, select **Add features and functionality**.\n9. Select **Permissions**.\n10. In the **Redirect URLs** section, select **Add New Redirect URL**.\n\nFor more details, you can refer to the [Slack API Quickstart](https://api.slack.com/quickstart) and the [Installing with OAuth](https://api.slack.com/authentication/oauth-v2) documentation.",
codeSnippet: '',
read: true,
},
{
id: '86572001',
role: 'user',
type: 'text',
content: 'Can you show me an example of a table?',
read: true,
},
{
id: '86572001',
type: 'text',
role: 'assistant',
content:
'Sure, here it is:\n\n| **Scope name** | **Notes** |\n| --- | --- |\n| `channels:read` | |\n| `channels:write` | Not available as a bot token scope |\n| `stars:read`| Not available as a bot token scope |\n| `stars:write` | Not available as a bot token scope |\n| `users.profile:write` | Not available as a bot token scope |\n| `users:read` | |',
read: true,
},
{
id: '86572001',
role: 'user',
type: 'text',
content: 'Thanks, can you send me another one with more columns?',
read: true,
},
{
id: '86572001',
type: 'text',
role: 'assistant',
content:
'Yup:\n\n| **Scope name** | **Notes** | **One More Column** |\n| --- | --- | --- |\n| `channels:read` | | Something else |\n| `channels:write` | Not available as a bot token scope | Something else |\n| `stars:read`| Not available as a bot token scope |\n| `stars:write` | Not available as a bot token scope |\n| `users.profile:write` | Not available as a bot token scope |\n| `users:read` | |',
read: true,
},
{
id: '2556642',
role: 'user',
type: 'text',
content: 'Great',
read: true,
},
{
id: '2556642',
type: 'text',
role: 'assistant',
content:
"I'm glad you found the information helpful! If you have any more questions about n8n or need further assistance, feel free to ask.",
read: true,
},
{
id: '2556642',
type: 'text',
role: 'assistant',
content: 'Did this answer solve your question?',
quickReplies: [
{
text: 'Yes, thanks',
type: 'all-good',
isFeedback: true,
},
{
text: 'No, I am still stuck',
type: 'still-stuck',
isFeedback: true,
},
],
read: true,
},
]),
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ const { t } = useI18n();
const md = new Markdown({
breaks: true,
}).use(markdownLink, {
});
md.use(markdownLink, {
attrs: {
target: '_blank',
rel: 'noopener',
},
});
// Wrap tables in div
md.renderer.rules.table_open = function () {
return '<div class="table-wrapper"><table>';
};
md.renderer.rules.table_close = function () {
return '</table></div>';
};
const MAX_CHAT_INPUT_HEIGHT = 100;
Expand Down Expand Up @@ -65,6 +74,10 @@ const showPlaceholder = computed(() => {
return !props.messages?.length && !props.loadingMessage && !props.sessionId;
});
const isClipboardSupported = computed(() => {
return navigator.clipboard?.writeText;
});
function isEndOfSessionEvent(event?: ChatUI.AssistantMessage) {
return event?.type === 'event' && event?.eventName === 'end-session';
}
Expand Down Expand Up @@ -97,6 +110,15 @@ function growInput() {
const scrollHeight = chatInput.value.scrollHeight;
chatInput.value.style.height = `${Math.min(scrollHeight, MAX_CHAT_INPUT_HEIGHT)}px`;
}
async function onCopyButtonClick(content: string, e: MouseEvent) {
const button = e.target as HTMLButtonElement;
await navigator.clipboard.writeText(content);
button.innerText = t('assistantChat.copied');
setTimeout(() => {
button.innerText = t('assistantChat.copy');
}, 2000);
}
</script>

<template>
Expand Down Expand Up @@ -151,7 +173,10 @@ function growInput() {
/>
</div>
<div :class="$style.blockBody">
<span v-n8n-html="renderMarkdown(message.content)"></span>
<span
v-n8n-html="renderMarkdown(message.content)"
:class="$style['rendered-content']"
></span>
<BlinkingCursor
v-if="streaming && i === messages?.length - 1 && message.title && message.content"
/>
Expand All @@ -162,18 +187,35 @@ function growInput() {
<span
v-if="message.role === 'user'"
v-n8n-html="renderMarkdown(message.content)"
:class="$style['rendered-content']"
></span>
<div
v-else
v-n8n-html="renderMarkdown(message.content)"
:class="$style.assistantText"
:class="[$style.assistantText, $style['rendered-content']]"
></div>
<div
v-if="message?.codeSnippet"
:class="$style['code-snippet']"
data-test-id="assistant-code-snippet"
v-n8n-html="renderMarkdown(message.codeSnippet).trim()"
></div>
>
<header v-if="isClipboardSupported">
<n8n-button
type="tertiary"
text="true"
size="mini"
data-test-id="assistant-copy-snippet-button"
@click="onCopyButtonClick(message.codeSnippet, $event)"
>
{{ t('assistantChat.copy') }}
</n8n-button>
</header>
<div
v-n8n-html="renderMarkdown(message.codeSnippet).trim()"
data-test-id="assistant-code-snippet-content"
:class="[$style['snippet-content'], $style['rendered-content']]"
></div>
</div>
<BlinkingCursor
v-if="streaming && i === messages?.length - 1 && message.role === 'assistant'"
/>
Expand Down Expand Up @@ -415,14 +457,37 @@ p {
word-break: break-word;
}
code[class^='language-'] {
display: block;
padding: var(--spacing-4xs);
}
.code-snippet {
position: relative;
border: var(--border-base);
background-color: var(--color-foreground-xlight);
border-radius: var(--border-radius-base);
padding: var(--spacing-2xs);
font-family: var(--font-family-monospace);
font-size: var(--font-size-3xs);
max-height: 218px; // 12 lines
overflow: auto;
margin: var(--spacing-4s) 0;
header {
display: flex;
justify-content: flex-end;
padding: var(--spacing-4xs);
border-bottom: var(--border-base);
button:active,
button:focus {
outline: none !important;
}
}
.snippet-content {
padding: var(--spacing-2xs);
}
pre {
white-space-collapse: collapse;
Expand Down Expand Up @@ -491,17 +556,60 @@ p {
}
.assistantText {
display: inline;
display: inline-flex;
flex-direction: column;
}
.rendered-content {
p {
display: inline;
line-height: 1.7;
margin: 0;
margin: var(--spacing-4xs) 0;
}
h1,
h2,
h3 {
font-weight: var(--font-weight-bold);
font-size: var(--font-size-xs);
margin: var(--spacing-xs) 0 var(--spacing-4xs);
}
h4,
h5,
h6 {
font-weight: var(--font-weight-bold);
font-size: var(--font-size-2xs);
}
ul,
ol {
list-style-position: inside;
margin: var(--spacing-xs) 0 var(--spacing-xs) var(--spacing-xs);
margin: var(--spacing-4xs) 0 var(--spacing-4xs) var(--spacing-l);
ul,
ol {
margin-left: var(--spacing-xs);
margin-top: var(--spacing-4xs);
}
}
:global(.table-wrapper) {
overflow-x: auto;
}
table {
margin: var(--spacing-4xs) 0;
th {
white-space: nowrap;
min-width: 120px;
width: auto;
}
th,
td {
border: var(--border-base);
padding: var(--spacing-4xs);
}
}
}
Expand Down
Loading

0 comments on commit 57ff3cc

Please sign in to comment.