Add Contact standard object, related lists, meilisearch, pagination, search, AI assistant

This commit is contained in:
Francisco Gaona
2026-01-16 18:01:26 +01:00
parent 51c82d3d95
commit 20fc90a3fb
62 changed files with 6613 additions and 278 deletions

View File

@@ -0,0 +1,41 @@
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { CurrentUser } from '../auth/current-user.decorator';
import { TenantId } from '../tenant/tenant.decorator';
import { AiAssistantService } from './ai-assistant.service';
import { AiChatRequestDto } from './dto/ai-chat.dto';
import { AiSearchRequestDto } from './dto/ai-search.dto';
@Controller('ai')
@UseGuards(JwtAuthGuard)
export class AiAssistantController {
constructor(private readonly aiAssistantService: AiAssistantService) {}
@Post('chat')
async chat(
@TenantId() tenantId: string,
@CurrentUser() user: any,
@Body() payload: AiChatRequestDto,
) {
return this.aiAssistantService.handleChat(
tenantId,
user.userId,
payload.message,
payload.history,
payload.context,
);
}
@Post('search')
async search(
@TenantId() tenantId: string,
@CurrentUser() user: any,
@Body() payload: AiSearchRequestDto,
) {
return this.aiAssistantService.searchRecords(
tenantId,
user.userId,
payload,
);
}
}

View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { AiAssistantController } from './ai-assistant.controller';
import { AiAssistantService } from './ai-assistant.service';
import { ObjectModule } from '../object/object.module';
import { PageLayoutModule } from '../page-layout/page-layout.module';
import { TenantModule } from '../tenant/tenant.module';
import { MeilisearchModule } from '../search/meilisearch.module';
@Module({
imports: [ObjectModule, PageLayoutModule, TenantModule, MeilisearchModule],
controllers: [AiAssistantController],
providers: [AiAssistantService],
})
export class AiAssistantModule {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
export interface AiChatMessage {
role: 'user' | 'assistant';
text: string;
}
export interface AiChatContext {
objectApiName?: string;
view?: string;
recordId?: string;
route?: string;
}
export interface AiAssistantReply {
reply: string;
action?: 'create_record' | 'collect_fields' | 'clarify';
missingFields?: string[];
record?: any;
}
export interface AiAssistantState {
message: string;
history?: AiChatMessage[];
context: AiChatContext;
objectDefinition?: any;
pageLayout?: any;
extractedFields?: Record<string, any>;
requiredFields?: string[];
missingFields?: string[];
action?: AiAssistantReply['action'];
record?: any;
reply?: string;
}

View File

@@ -0,0 +1,36 @@
import { Type } from 'class-transformer';
import { IsNotEmpty, IsObject, IsOptional, IsString, ValidateNested } from 'class-validator';
import { AiChatMessageDto } from './ai-chat.message.dto';
export class AiChatContextDto {
@IsOptional()
@IsString()
objectApiName?: string;
@IsOptional()
@IsString()
view?: string;
@IsOptional()
@IsString()
recordId?: string;
@IsOptional()
@IsString()
route?: string;
}
export class AiChatRequestDto {
@IsString()
@IsNotEmpty()
message: string;
@IsOptional()
@IsObject()
context?: AiChatContextDto;
@IsOptional()
@ValidateNested({ each: true })
@Type(() => AiChatMessageDto)
history?: AiChatMessageDto[];
}

View File

@@ -0,0 +1,10 @@
import { IsIn, IsNotEmpty, IsString } from 'class-validator';
export class AiChatMessageDto {
@IsIn(['user', 'assistant'])
role: 'user' | 'assistant';
@IsString()
@IsNotEmpty()
text: string;
}

View File

@@ -0,0 +1,22 @@
import { Type } from 'class-transformer';
import { IsNotEmpty, IsOptional, IsString, IsNumber } from 'class-validator';
export class AiSearchRequestDto {
@IsString()
@IsNotEmpty()
objectApiName: string;
@IsString()
@IsNotEmpty()
query: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
page?: number;
@IsOptional()
@Type(() => Number)
@IsNumber()
pageSize?: number;
}