-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Ask assistant behind feature flag (#9995)
Co-authored-by: Ricardo Espinoza <[email protected]> Co-authored-by: Milorad Filipovic <[email protected]>
- Loading branch information
1 parent
e4c88e7
commit 5ed2a77
Showing
70 changed files
with
3,414 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { Post, RestController } from '@/decorators'; | ||
import { AiAssistantService } from '@/services/aiAsisstant.service'; | ||
import { AiAssistantRequest } from '@/requests'; | ||
import { Response } from 'express'; | ||
import type { AiAssistantSDK } from '@n8n_io/ai-assistant-sdk'; | ||
import { Readable, promises } from 'node:stream'; | ||
import { InternalServerError } from 'express-openapi-validator/dist/openapi.validator'; | ||
import { strict as assert } from 'node:assert'; | ||
import { ErrorReporterProxy } from 'n8n-workflow'; | ||
|
||
@RestController('/ai-assistant') | ||
export class AiAssistantController { | ||
constructor(private readonly aiAssistantService: AiAssistantService) {} | ||
|
||
@Post('/chat', { rateLimit: { limit: 100 } }) | ||
async chat(req: AiAssistantRequest.Chat, res: Response) { | ||
try { | ||
const stream = await this.aiAssistantService.chat(req.body, req.user); | ||
|
||
if (stream.body) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
await promises.pipeline(Readable.fromWeb(stream.body), res); | ||
} | ||
} catch (e) { | ||
// todo add sentry reporting | ||
assert(e instanceof Error); | ||
ErrorReporterProxy.error(e); | ||
throw new InternalServerError({ message: `Something went wrong: ${e.message}` }); | ||
} | ||
} | ||
|
||
@Post('/chat/apply-suggestion') | ||
async applySuggestion( | ||
req: AiAssistantRequest.ApplySuggestion, | ||
): Promise<AiAssistantSDK.ApplySuggestionResponse> { | ||
try { | ||
return await this.aiAssistantService.applySuggestion(req.body, req.user); | ||
} catch (e) { | ||
assert(e instanceof Error); | ||
ErrorReporterProxy.error(e); | ||
throw new InternalServerError({ message: `Something went wrong: ${e.message}` }); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { Service } from 'typedi'; | ||
import type { AiAssistantSDK } from '@n8n_io/ai-assistant-sdk'; | ||
import { AiAssistantClient } from '@n8n_io/ai-assistant-sdk'; | ||
import { assert, type IUser } from 'n8n-workflow'; | ||
import { License } from '../License'; | ||
import { N8N_VERSION } from '../constants'; | ||
import config from '@/config'; | ||
import type { AiAssistantRequest } from '@/requests'; | ||
import type { Response } from 'undici'; | ||
|
||
@Service() | ||
export class AiAssistantService { | ||
private client: AiAssistantClient | undefined; | ||
|
||
constructor(private readonly licenseService: License) {} | ||
|
||
async init() { | ||
const aiAssistantEnabled = this.licenseService.isAiAssistantEnabled(); | ||
if (!aiAssistantEnabled) { | ||
return; | ||
} | ||
|
||
const licenseCert = await this.licenseService.loadCertStr(); | ||
const consumerId = this.licenseService.getConsumerId(); | ||
const baseUrl = config.get('aiAssistant.baseUrl'); | ||
const logLevel = config.getEnv('logs.level'); | ||
|
||
this.client = new AiAssistantClient({ | ||
licenseCert, | ||
consumerId, | ||
n8nVersion: N8N_VERSION, | ||
baseUrl, | ||
logLevel, | ||
}); | ||
} | ||
|
||
async chat(payload: AiAssistantSDK.ChatRequestPayload, user: IUser): Promise<Response> { | ||
if (!this.client) { | ||
await this.init(); | ||
} | ||
assert(this.client, 'Assistant client not setup'); | ||
|
||
return await this.client.chat(payload, { id: user.id }); | ||
} | ||
|
||
async applySuggestion(payload: AiAssistantRequest.SuggestionPayload, user: IUser) { | ||
if (!this.client) { | ||
await this.init(); | ||
} | ||
assert(this.client, 'Assistant client not setup'); | ||
|
||
return await this.client.applySuggestion(payload, { id: user.id }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
packages/design-system/src/components/AskAssistantAvatar/AssistantAvatar.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import AssistantAvatar from './AssistantAvatar.vue'; | ||
import type { StoryFn } from '@storybook/vue3'; | ||
|
||
export default { | ||
title: 'Assistant/AssistantAvatar', | ||
component: AssistantAvatar, | ||
argTypes: {}, | ||
}; | ||
|
||
const Template: StoryFn = (args, { argTypes }) => ({ | ||
setup: () => ({ args }), | ||
props: Object.keys(argTypes), | ||
components: { | ||
AssistantAvatar, | ||
}, | ||
template: '<AssistantAvatar v-bind="args" />', | ||
}); | ||
|
||
export const Default = Template.bind({}); | ||
Default.args = {}; | ||
|
||
export const Mini = Template.bind({}); | ||
Mini.args = { | ||
size: 'mini', | ||
}; |
33 changes: 33 additions & 0 deletions
33
packages/design-system/src/components/AskAssistantAvatar/AssistantAvatar.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<script setup lang="ts"> | ||
import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue'; | ||
withDefaults(defineProps<{ size: 'small' | 'mini' }>(), { | ||
size: 'small', | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div :class="[$style.container, $style[size]]"> | ||
<AssistantIcon :size="size" theme="blank" /> | ||
</div> | ||
</template> | ||
|
||
<style lang="scss" module> | ||
.container { | ||
background: var(--color-assistant-highlight-gradient); | ||
border-radius: 50%; | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
.small { | ||
height: var(--spacing-m); | ||
width: var(--spacing-m); | ||
} | ||
.mini { | ||
height: var(--spacing-s); | ||
width: var(--spacing-s); | ||
} | ||
</style> |
31 changes: 31 additions & 0 deletions
31
packages/design-system/src/components/AskAssistantButton/AskAssistantButton.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import AskAssistantButton from './AskAssistantButton.vue'; | ||
import { action } from '@storybook/addon-actions'; | ||
import type { StoryFn } from '@storybook/vue3'; | ||
|
||
export default { | ||
title: 'Assistant/AskAssistantButton', | ||
component: AskAssistantButton, | ||
argTypes: {}, | ||
}; | ||
|
||
const methods = { | ||
onClick: action('click'), | ||
}; | ||
|
||
const Template: StoryFn = (args, { argTypes }) => ({ | ||
setup: () => ({ args }), | ||
props: Object.keys(argTypes), | ||
components: { | ||
AskAssistantButton, | ||
}, | ||
template: | ||
'<div style="display: flex; height: 50px; width: 300px; align-items: center; justify-content: center"><AskAssistantButton v-bind="args" @click="onClick" /></div>', | ||
methods, | ||
}); | ||
|
||
export const Button = Template.bind({}); | ||
|
||
export const Notifications = Template.bind({}); | ||
Notifications.args = { | ||
unreadCount: 1, | ||
}; |
Oops, something went wrong.