n8cn / packages /cli /src /controllers /ai.controller.ts
gallyga's picture
Add n8n Chinese version
aec3094
import type { CreateCredentialDto } from '@n8n/api-types';
import {
AiChatRequestDto,
AiApplySuggestionRequestDto,
AiAskRequestDto,
AiFreeCreditsRequestDto,
AiBuilderChatRequestDto,
} from '@n8n/api-types';
import { Body, Post, RestController } from '@n8n/decorators';
import type { AiAssistantSDK } from '@n8n_io/ai-assistant-sdk';
import { Response } from 'express';
import { OPEN_AI_API_CREDENTIAL_TYPE } from 'n8n-workflow';
import { strict as assert } from 'node:assert';
import { WritableStream } from 'node:stream/web';
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';
export type FlushableResponse = Response & { flush: () => void };
@RestController('/ai')
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 {
const aiResponse = await this.aiService.chat(payload, req.user);
if (aiResponse.body) {
res.header('Content-type', 'application/json-lines').flush();
await aiResponse.body.pipeTo(
new WritableStream({
write(chunk) {
res.write(chunk);
res.flush();
},
}),
);
res.end();
}
} catch (e) {
assert(e instanceof Error);
throw new InternalServerError(e.message, e);
}
}
@Post('/chat/apply-suggestion')
async applySuggestion(
req: AuthenticatedRequest,
_: Response,
@Body payload: AiApplySuggestionRequestDto,
): Promise<AiAssistantSDK.ApplySuggestionResponse> {
try {
return await this.aiService.applySuggestion(payload, req.user);
} catch (e) {
assert(e instanceof Error);
throw new InternalServerError(e.message, e);
}
}
@Post('/ask-ai')
async askAi(
req: AuthenticatedRequest,
_: Response,
@Body payload: AiAskRequestDto,
): Promise<AiAssistantSDK.AskAiResponsePayload> {
try {
return await this.aiService.askAi(payload, req.user);
} catch (e) {
assert(e instanceof Error);
throw new InternalServerError(e.message, e);
}
}
@Post('/free-credits')
async aiCredits(req: AuthenticatedRequest, _: Response, @Body payload: AiFreeCreditsRequestDto) {
try {
const aiCredits = await this.aiService.createFreeAiCredits(req.user);
const credentialProperties: CreateCredentialDto = {
name: FREE_AI_CREDITS_CREDENTIAL_NAME,
type: OPEN_AI_API_CREDENTIAL_TYPE,
data: {
apiKey: aiCredits.apiKey,
url: aiCredits.url,
},
projectId: payload?.projectId,
};
const newCredential = await this.credentialsService.createManagedCredential(
credentialProperties,
req.user,
);
await this.userService.updateSettings(req.user.id, {
userClaimedAiCredits: true,
});
return newCredential;
} catch (e) {
assert(e instanceof Error);
throw new InternalServerError(e.message, e);
}
}
}