diff --git a/packages/admin-client/vite.config.ts b/packages/admin-client/vite.config.ts index ea5f77e..5e16806 100644 --- a/packages/admin-client/vite.config.ts +++ b/packages/admin-client/vite.config.ts @@ -16,5 +16,20 @@ export default defineConfig({ build: { outDir: 'dist', sourcemap: true, + chunkSizeWarningLimit: 1100, // antd is ~1MB, this is expected + rollupOptions: { + output: { + manualChunks: { + // React core + 'vendor-react': ['react', 'react-dom', 'react-router-dom'], + // Ant Design UI library + 'vendor-antd': ['antd', '@ant-design/icons'], + // Charts + 'vendor-charts': ['recharts'], + // Data fetching & state + 'vendor-data': ['@tanstack/react-query', 'axios', 'zustand'], + }, + }, + }, }, }); diff --git a/packages/services/conversation-service/src/adapters/inbound/conversation.controller.ts b/packages/services/conversation-service/src/adapters/inbound/conversation.controller.ts index 8081fdf..50ff3c9 100644 --- a/packages/services/conversation-service/src/adapters/inbound/conversation.controller.ts +++ b/packages/services/conversation-service/src/adapters/inbound/conversation.controller.ts @@ -16,6 +16,21 @@ import { CreateConversationDto, SendMessageDto } from '../../application/dtos/co export class ConversationController { constructor(private conversationService: ConversationService) {} + /** + * Extract client IP from headers (supports proxies) + */ + private extractClientIp( + xForwardedFor?: string, + xRealIp?: string, + ): string | undefined { + // X-Forwarded-For can contain multiple IPs, take the first one (original client) + if (xForwardedFor) { + const ips = xForwardedFor.split(',').map((ip) => ip.trim()); + return ips[0] || undefined; + } + return xRealIp || undefined; + } + /** * Create a new conversation */ @@ -23,11 +38,24 @@ export class ConversationController { @HttpCode(HttpStatus.CREATED) async createConversation( @Headers('x-user-id') userId: string, + @Headers('x-forwarded-for') xForwardedFor: string, + @Headers('x-real-ip') xRealIp: string, + @Headers('user-agent') userAgent: string, @Body() dto: CreateConversationDto, ) { + // Build deviceInfo from headers and body + const clientIp = this.extractClientIp(xForwardedFor, xRealIp); + const deviceInfo = { + ip: clientIp || dto.deviceInfo?.ip, + userAgent: userAgent || dto.deviceInfo?.userAgent, + fingerprint: dto.deviceInfo?.fingerprint, + region: dto.deviceInfo?.region, + }; + const conversation = await this.conversationService.createConversation({ userId, title: dto.title, + deviceInfo, }); return { diff --git a/packages/services/conversation-service/src/application/dtos/conversation.dto.ts b/packages/services/conversation-service/src/application/dtos/conversation.dto.ts index de91308..9116cbd 100644 --- a/packages/services/conversation-service/src/application/dtos/conversation.dto.ts +++ b/packages/services/conversation-service/src/application/dtos/conversation.dto.ts @@ -1,10 +1,33 @@ import { IsOptional, IsString, IsNotEmpty, IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; +export class DeviceInfoDto { + @IsOptional() + @IsString() + ip?: string; + + @IsOptional() + @IsString() + userAgent?: string; + + @IsOptional() + @IsString() + fingerprint?: string; + + @IsOptional() + @IsString() + region?: string; +} + export class CreateConversationDto { @IsOptional() @IsString() title?: string; + + @IsOptional() + @ValidateNested() + @Type(() => DeviceInfoDto) + deviceInfo?: DeviceInfoDto; } export class FileAttachmentDto { diff --git a/packages/services/conversation-service/src/application/services/conversation.service.ts b/packages/services/conversation-service/src/application/services/conversation.service.ts index d391843..6d490f5 100644 --- a/packages/services/conversation-service/src/application/services/conversation.service.ts +++ b/packages/services/conversation-service/src/application/services/conversation.service.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid'; import { ConversationEntity, ConversationStatus, + DeviceInfo, } from '../../domain/entities/conversation.entity'; import { MessageEntity, @@ -26,6 +27,7 @@ import { export interface CreateConversationParams { userId: string; title?: string; + deviceInfo?: DeviceInfo; } export interface FileAttachment { @@ -63,6 +65,7 @@ export class ConversationService { id: uuidv4(), userId: params.userId, title: params.title || '新对话', + deviceInfo: params.deviceInfo, }); return this.conversationRepo.save(conversation);