mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat: AI Workflow Builder backend (no-changelog) (#14837)
This commit is contained in:
@@ -100,6 +100,7 @@
|
||||
"@n8n/n8n-nodes-langchain": "workspace:*",
|
||||
"@n8n/permissions": "workspace:*",
|
||||
"@n8n/task-runner": "workspace:*",
|
||||
"@n8n/ai-workflow-builder": "workspace:*",
|
||||
"@n8n/typeorm": "0.3.20-12",
|
||||
"@n8n_io/ai-assistant-sdk": "1.13.0",
|
||||
"@n8n_io/license-sdk": "2.20.0",
|
||||
|
||||
@@ -8,14 +8,15 @@ import { mock } from 'jest-mock-extended';
|
||||
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
import type { AuthenticatedRequest } from '@/requests';
|
||||
import type { WorkflowBuilderService } from '@/services/ai-workflow-builder.service';
|
||||
import type { AiService } from '@/services/ai.service';
|
||||
|
||||
import { AiController, type FlushableResponse } from '../ai.controller';
|
||||
|
||||
describe('AiController', () => {
|
||||
const aiService = mock<AiService>();
|
||||
|
||||
const controller = new AiController(aiService, mock(), mock());
|
||||
const workflowBuilderService = mock<WorkflowBuilderService>();
|
||||
const controller = new AiController(aiService, workflowBuilderService, mock(), mock());
|
||||
|
||||
const request = mock<AuthenticatedRequest>({
|
||||
user: { id: 'user123' },
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
AiApplySuggestionRequestDto,
|
||||
AiAskRequestDto,
|
||||
AiFreeCreditsRequestDto,
|
||||
AiBuilderChatRequestDto,
|
||||
} from '@n8n/api-types';
|
||||
import { Body, Post, RestController } from '@n8n/decorators';
|
||||
import type { AiAssistantSDK } from '@n8n_io/ai-assistant-sdk';
|
||||
@@ -16,6 +17,7 @@ import { FREE_AI_CREDITS_CREDENTIAL_NAME } from '@/constants';
|
||||
import { CredentialsService } from '@/credentials/credentials.service';
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
import { AuthenticatedRequest } from '@/requests';
|
||||
import { WorkflowBuilderService } from '@/services/ai-workflow-builder.service';
|
||||
import { AiService } from '@/services/ai.service';
|
||||
import { UserService } from '@/services/user.service';
|
||||
|
||||
@@ -25,10 +27,40 @@ export type FlushableResponse = Response & { flush: () => void };
|
||||
export class AiController {
|
||||
constructor(
|
||||
private readonly aiService: AiService,
|
||||
private readonly workflowBuilderService: WorkflowBuilderService,
|
||||
private readonly credentialsService: CredentialsService,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
@Post('/build', { rateLimit: { limit: 100 } })
|
||||
async build(
|
||||
req: AuthenticatedRequest,
|
||||
res: FlushableResponse,
|
||||
@Body payload: AiBuilderChatRequestDto,
|
||||
) {
|
||||
try {
|
||||
const aiResponse = this.workflowBuilderService.chat(
|
||||
{
|
||||
question: payload.payload.question ?? '',
|
||||
},
|
||||
req.user,
|
||||
);
|
||||
|
||||
res.header('Content-type', 'application/json-lines').flush();
|
||||
|
||||
// Handle the stream
|
||||
for await (const chunk of aiResponse) {
|
||||
res.flush();
|
||||
res.write(JSON.stringify(chunk) + '⧉⇋⇋➽⌑⧉§§\n');
|
||||
}
|
||||
|
||||
res.end();
|
||||
} catch (e) {
|
||||
assert(e instanceof Error);
|
||||
throw new InternalServerError(e.message, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/chat', { rateLimit: { limit: 100 } })
|
||||
async chat(req: AuthenticatedRequest, res: FlushableResponse, @Body payload: AiChatRequestDto) {
|
||||
try {
|
||||
|
||||
40
packages/cli/src/services/ai-workflow-builder.service.ts
Normal file
40
packages/cli/src/services/ai-workflow-builder.service.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AiWorkflowBuilderService } from '@n8n/ai-workflow-builder';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { Service } from '@n8n/di';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import { License } from '@/license';
|
||||
import { NodeTypes } from '@/node-types';
|
||||
|
||||
/**
|
||||
* This service wraps the actual AiWorkflowBuilderService to avoid circular dependencies.
|
||||
* Instead of extending, we're delegating to the real service which is created on-demand.
|
||||
*/
|
||||
@Service()
|
||||
export class WorkflowBuilderService {
|
||||
private service: AiWorkflowBuilderService | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly nodeTypes: NodeTypes,
|
||||
private readonly license: License,
|
||||
private readonly config: GlobalConfig,
|
||||
) {}
|
||||
|
||||
private getService(): AiWorkflowBuilderService {
|
||||
if (!this.service) {
|
||||
this.service = new AiWorkflowBuilderService(
|
||||
this.license,
|
||||
this.nodeTypes,
|
||||
this.config,
|
||||
N8N_VERSION,
|
||||
);
|
||||
}
|
||||
return this.service;
|
||||
}
|
||||
|
||||
async *chat(payload: { question: string }, user: IUser) {
|
||||
const service = this.getService();
|
||||
yield* service.chat(payload, user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user