forked from Chatbot/v3-api
251 lines
7.8 KiB
TypeScript
251 lines
7.8 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpStatus,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Put,
|
|
Query,
|
|
UploadedFile,
|
|
UseGuards,
|
|
UseInterceptors,
|
|
} from '@nestjs/common';
|
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
import { memoryStorage } from 'multer';
|
|
import {
|
|
ApiBearerAuth,
|
|
ApiBody,
|
|
ApiConsumes,
|
|
ApiOperation,
|
|
ApiParam,
|
|
ApiQuery,
|
|
ApiTags,
|
|
} from '@nestjs/swagger';
|
|
import { AdminGuard } from 'src/auth/guards/admin.guard';
|
|
import { Permissions } from 'src/common/decorators/permission.decorator';
|
|
import { Permission } from 'src/common/types/permissions.catalog';
|
|
import { MAX_DICTIONARY_BYTES } from 'src/storage/storage.constants';
|
|
import { AiV2Client } from './ai-v2.client';
|
|
import { AiV2Exception } from './ai-v2.exception';
|
|
import { AiV2Service } from './ai-v2.service';
|
|
import { CursorPageQueryDto } from './dto/common-query.dto';
|
|
import { CreateDomainDto, ListDomainsQueryDto, UpdateDomainDto } from './dto/domains.dto';
|
|
import { ListFilesQueryDto } from './dto/files.dto';
|
|
import {
|
|
CountPointsQueryDto,
|
|
CreatePointDto,
|
|
GetPointQueryDto,
|
|
ListPointsQueryDto,
|
|
PatchPointPayloadDto,
|
|
ReplacePointDto,
|
|
SearchPointsQueryDto,
|
|
} from './dto/points.dto';
|
|
import { RetrievalQueryDto } from './dto/retrieval.dto';
|
|
|
|
@ApiTags('v2 AI')
|
|
@ApiBearerAuth()
|
|
@UseGuards(AdminGuard)
|
|
@Permissions(Permission.DictionariesRead)
|
|
@Controller('ai/v2')
|
|
export class AiV2AdminController {
|
|
constructor(
|
|
private readonly client: AiV2Client,
|
|
private readonly aiV2: AiV2Service,
|
|
) {}
|
|
|
|
@Get('domains')
|
|
@ApiOperation({ summary: 'List tenant domains (AI collections)' })
|
|
async listDomains(@Query() query: ListDomainsQueryDto) {
|
|
return this.aiV2.ok(await this.client.listDomains(query.include_disabled));
|
|
}
|
|
|
|
@Post('domains')
|
|
@Permissions(Permission.DictionariesCreate)
|
|
@ApiOperation({ summary: 'Create a tenant domain' })
|
|
@ApiBody({ type: CreateDomainDto })
|
|
async createDomain(@Body() body: CreateDomainDto) {
|
|
return this.aiV2.created(await this.client.createDomain(body));
|
|
}
|
|
|
|
@Patch('domains/:domain')
|
|
@Permissions(Permission.DictionariesWrite)
|
|
@ApiOperation({ summary: 'Update a tenant domain display name' })
|
|
@ApiParam({ name: 'domain' })
|
|
@ApiBody({ type: UpdateDomainDto })
|
|
async updateDomain(
|
|
@Param('domain') domain: string,
|
|
@Body() body: UpdateDomainDto,
|
|
) {
|
|
return this.aiV2.ok(await this.client.updateDomain(domain, body));
|
|
}
|
|
|
|
@Delete('domains/:domain')
|
|
@Permissions(Permission.DictionariesWrite)
|
|
@ApiOperation({ summary: 'Disable a tenant domain' })
|
|
@ApiParam({ name: 'domain' })
|
|
async disableDomain(@Param('domain') domain: string) {
|
|
return this.aiV2.ok(await this.client.disableDomain(domain));
|
|
}
|
|
|
|
@Post('domains/:domain/enable')
|
|
@Permissions(Permission.DictionariesWrite)
|
|
@ApiOperation({ summary: 'Enable a tenant domain' })
|
|
@ApiParam({ name: 'domain' })
|
|
async enableDomain(@Param('domain') domain: string) {
|
|
return this.aiV2.ok(await this.client.enableDomain(domain));
|
|
}
|
|
|
|
@Get('domains/:domain/points')
|
|
@ApiOperation({ summary: 'List points in a domain' })
|
|
@ApiParam({ name: 'domain' })
|
|
async listDomainPoints(
|
|
@Param('domain') domain: string,
|
|
@Query() query: CursorPageQueryDto,
|
|
) {
|
|
return this.aiV2.ok(await this.client.listDomainPoints(domain, query));
|
|
}
|
|
|
|
@Post('files')
|
|
@Permissions(Permission.DictionariesCreate)
|
|
@ApiOperation({ summary: 'Upload a file into a domain for ingestion' })
|
|
@ApiConsumes('multipart/form-data')
|
|
@ApiBody({
|
|
schema: {
|
|
type: 'object',
|
|
required: ['file', 'domain'],
|
|
properties: {
|
|
file: { type: 'string', format: 'binary' },
|
|
domain: { type: 'string', example: 'faq' },
|
|
},
|
|
},
|
|
})
|
|
@UseInterceptors(
|
|
FileInterceptor('file', {
|
|
storage: memoryStorage(),
|
|
limits: { fileSize: MAX_DICTIONARY_BYTES },
|
|
}),
|
|
)
|
|
async uploadFile(
|
|
@UploadedFile() file: Express.Multer.File,
|
|
@Body('domain') domain: string,
|
|
) {
|
|
if (!file) {
|
|
throw new AiV2Exception(HttpStatus.BAD_REQUEST, 'file_required');
|
|
}
|
|
if (!domain?.trim()) {
|
|
throw new AiV2Exception(HttpStatus.BAD_REQUEST, 'domain_required');
|
|
}
|
|
return this.aiV2.created(await this.client.uploadFile(file, domain.trim()));
|
|
}
|
|
|
|
@Get('files')
|
|
@ApiOperation({ summary: 'List uploaded files' })
|
|
async listFiles(@Query() query: ListFilesQueryDto) {
|
|
return this.aiV2.ok(await this.client.listFiles(query));
|
|
}
|
|
|
|
@Get('files/:fileId')
|
|
@ApiOperation({ summary: 'Get file status / ingestion progress' })
|
|
@ApiParam({ name: 'fileId' })
|
|
async getFile(@Param('fileId') fileId: string) {
|
|
return this.aiV2.ok(await this.client.getFile(fileId));
|
|
}
|
|
|
|
@Delete('files/:fileId')
|
|
@Permissions(Permission.DictionariesWrite)
|
|
@ApiOperation({ summary: 'Delete a file (soft-deletes its points)' })
|
|
@ApiParam({ name: 'fileId' })
|
|
async deleteFile(@Param('fileId') fileId: string) {
|
|
return this.aiV2.ok(await this.client.deleteFile(fileId));
|
|
}
|
|
|
|
@Get('files/:fileId/points')
|
|
@ApiOperation({ summary: 'List points for a file' })
|
|
@ApiParam({ name: 'fileId' })
|
|
async listFilePoints(
|
|
@Param('fileId') fileId: string,
|
|
@Query() query: CursorPageQueryDto,
|
|
) {
|
|
return this.aiV2.ok(await this.client.listFilePoints(fileId, query));
|
|
}
|
|
|
|
@Get('points/count')
|
|
@ApiOperation({ summary: 'Count points, optionally filtered by domain and/or file' })
|
|
async countPoints(@Query() query: CountPointsQueryDto) {
|
|
return this.aiV2.ok(await this.client.countPoints(query));
|
|
}
|
|
|
|
@Get('points/search')
|
|
@ApiOperation({ summary: 'Keyword search points (not semantic retrieval)' })
|
|
async searchPoints(@Query() query: SearchPointsQueryDto) {
|
|
return this.aiV2.ok(await this.client.searchPoints(query));
|
|
}
|
|
|
|
@Get('points')
|
|
@ApiOperation({ summary: 'List points for a file_id' })
|
|
@ApiQuery({ name: 'file_id', required: true })
|
|
async listPoints(@Query() query: ListPointsQueryDto) {
|
|
return this.aiV2.ok(await this.client.listPoints(query));
|
|
}
|
|
|
|
@Post('points')
|
|
@Permissions(Permission.DictionariesCreate)
|
|
@ApiOperation({ summary: 'Create a point in a file' })
|
|
@ApiBody({ type: CreatePointDto })
|
|
async createPoint(@Body() body: CreatePointDto) {
|
|
return this.aiV2.created(await this.client.createPoint(body));
|
|
}
|
|
|
|
@Get('points/:pointId')
|
|
@ApiOperation({ summary: 'Get one point' })
|
|
@ApiParam({ name: 'pointId' })
|
|
async getPoint(
|
|
@Param('pointId') pointId: string,
|
|
@Query() query: GetPointQueryDto,
|
|
) {
|
|
return this.aiV2.ok(await this.client.getPoint(pointId, query.with_vectors));
|
|
}
|
|
|
|
@Delete('points/:pointId')
|
|
@Permissions(Permission.DictionariesWrite)
|
|
@ApiOperation({ summary: 'Delete a point' })
|
|
@ApiParam({ name: 'pointId' })
|
|
async deletePoint(@Param('pointId') pointId: string) {
|
|
return this.aiV2.ok(await this.client.deletePoint(pointId));
|
|
}
|
|
|
|
@Put('points/:pointId')
|
|
@Permissions(Permission.DictionariesWrite)
|
|
@ApiOperation({ summary: 'Replace point content (version-guarded)' })
|
|
@ApiParam({ name: 'pointId' })
|
|
@ApiBody({ type: ReplacePointDto })
|
|
async replacePoint(
|
|
@Param('pointId') pointId: string,
|
|
@Body() body: ReplacePointDto,
|
|
) {
|
|
return this.aiV2.ok(await this.client.replacePoint(pointId, body));
|
|
}
|
|
|
|
@Patch('points/:pointId/payload')
|
|
@Permissions(Permission.DictionariesWrite)
|
|
@ApiOperation({ summary: 'Patch point payload (version-guarded)' })
|
|
@ApiParam({ name: 'pointId' })
|
|
@ApiBody({ type: PatchPointPayloadDto })
|
|
async patchPointPayload(
|
|
@Param('pointId') pointId: string,
|
|
@Body() body: PatchPointPayloadDto,
|
|
) {
|
|
return this.aiV2.ok(await this.client.patchPointPayload(pointId, body));
|
|
}
|
|
|
|
@Post('retrieval/query')
|
|
@ApiOperation({ summary: 'Hybrid semantic retrieval (not the ask API)' })
|
|
@ApiBody({ type: RetrievalQueryDto })
|
|
async queryRetrieval(@Body() body: RetrievalQueryDto) {
|
|
return this.aiV2.ok(await this.client.queryRetrieval(body));
|
|
}
|
|
}
|