import { Injectable, NotFoundException } from '@nestjs/common'; import { I18nService } from '../i18n/i18n.service'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { SearchHistory } from './search-history.entity'; import { ChatMessage } from './chat-message.entity'; export interface SearchHistoryItem { id: string; title: string; selectedGroups: string[] | null; messageCount: number; lastMessageAt: string; createdAt: string; } export interface SearchHistoryDetail { id: string; title: string; selectedGroups: string[] | null; messages: Array<{ id: string; role: 'user' | 'assistant'; content: string; sources?: any[]; createdAt: string; }>; } export interface PaginatedSearchHistory { histories: SearchHistoryItem[]; total: number; page: number; limit: number; } @Injectable() export class SearchHistoryService { constructor( @InjectRepository(SearchHistory) private searchHistoryRepository: Repository, @InjectRepository(ChatMessage) private chatMessageRepository: Repository, private i18nService: I18nService, ) { } async findAll( userId: string, page: number = 1, limit: number = 20, ): Promise { const [histories, total] = await this.searchHistoryRepository.findAndCount({ where: { userId }, order: { updatedAt: 'DESC' }, skip: (page - 1) * limit, take: limit, }); const items: SearchHistoryItem[] = await Promise.all( histories.map(async (history) => { const messageCount = await this.chatMessageRepository.count({ where: { searchHistoryId: history.id }, }); const lastMessage = await this.chatMessageRepository.findOne({ where: { searchHistoryId: history.id }, order: { createdAt: 'DESC' }, }); return { id: history.id, title: history.title, selectedGroups: history.selectedGroups ? JSON.parse(history.selectedGroups) : null, messageCount, lastMessageAt: lastMessage?.createdAt.toISOString() || history.createdAt.toISOString(), createdAt: history.createdAt.toISOString(), }; }), ); return { histories: items, total, page, limit, }; } async findOne(id: string, userId: string): Promise { const history = await this.searchHistoryRepository.findOne({ where: { id, userId }, relations: ['messages'], order: { messages: { createdAt: 'ASC' } }, }); if (!history) { throw new NotFoundException(this.i18nService.getMessage('conversationHistoryNotFound')); } return { id: history.id, title: history.title, selectedGroups: history.selectedGroups ? JSON.parse(history.selectedGroups) : null, messages: history.messages.map((message) => ({ id: message.id, role: message.role, content: message.content, sources: message.sources ? JSON.parse(message.sources) : undefined, createdAt: message.createdAt.toISOString(), })), }; } async create( userId: string, title: string, selectedGroups?: string[], ): Promise { const history = this.searchHistoryRepository.create({ userId, title: title.length > 50 ? title.substring(0, 50) + '...' : title, selectedGroups: selectedGroups ? JSON.stringify(selectedGroups) : undefined, }); return await this.searchHistoryRepository.save(history); } async addMessage( historyId: string, role: 'user' | 'assistant', content: string, sources?: any[], ): Promise { const message = this.chatMessageRepository.create({ searchHistoryId: historyId, role, content, sources: sources ? JSON.stringify(sources) : undefined, }); const savedMessage = await this.chatMessageRepository.save(message); // 履歴レコードの更新時間を更新 await this.searchHistoryRepository.update(historyId, { updatedAt: new Date(), }); return savedMessage; } async remove(id: string, userId: string): Promise { const history = await this.searchHistoryRepository.findOne({ where: { id, userId }, }); if (!history) { throw new NotFoundException(this.i18nService.getMessage('conversationHistoryNotFound')); } await this.searchHistoryRepository.remove(history); } async updateTitle(id: string, title: string): Promise { await this.searchHistoryRepository.update(id, { title }); } }