search-history.service.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { Injectable, NotFoundException } from '@nestjs/common';
  2. import { I18nService } from '../i18n/i18n.service';
  3. import { InjectRepository } from '@nestjs/typeorm';
  4. import { Repository } from 'typeorm';
  5. import { SearchHistory } from './search-history.entity';
  6. import { ChatMessage } from './chat-message.entity';
  7. export interface SearchHistoryItem {
  8. id: string;
  9. title: string;
  10. selectedGroups: string[] | null;
  11. messageCount: number;
  12. lastMessageAt: string;
  13. createdAt: string;
  14. }
  15. export interface SearchHistoryDetail {
  16. id: string;
  17. title: string;
  18. selectedGroups: string[] | null;
  19. messages: Array<{
  20. id: string;
  21. role: 'user' | 'assistant';
  22. content: string;
  23. sources?: any[];
  24. createdAt: string;
  25. }>;
  26. }
  27. export interface PaginatedSearchHistory {
  28. histories: SearchHistoryItem[];
  29. total: number;
  30. page: number;
  31. limit: number;
  32. }
  33. @Injectable()
  34. export class SearchHistoryService {
  35. constructor(
  36. @InjectRepository(SearchHistory)
  37. private searchHistoryRepository: Repository<SearchHistory>,
  38. @InjectRepository(ChatMessage)
  39. private chatMessageRepository: Repository<ChatMessage>,
  40. private i18nService: I18nService,
  41. ) { }
  42. async findAll(
  43. userId: string,
  44. page: number = 1,
  45. limit: number = 20,
  46. ): Promise<PaginatedSearchHistory> {
  47. const [histories, total] = await this.searchHistoryRepository.findAndCount({
  48. where: { userId },
  49. order: { updatedAt: 'DESC' },
  50. skip: (page - 1) * limit,
  51. take: limit,
  52. });
  53. const items: SearchHistoryItem[] = await Promise.all(
  54. histories.map(async (history) => {
  55. const messageCount = await this.chatMessageRepository.count({
  56. where: { searchHistoryId: history.id },
  57. });
  58. const lastMessage = await this.chatMessageRepository.findOne({
  59. where: { searchHistoryId: history.id },
  60. order: { createdAt: 'DESC' },
  61. });
  62. return {
  63. id: history.id,
  64. title: history.title,
  65. selectedGroups: history.selectedGroups ? JSON.parse(history.selectedGroups) : null,
  66. messageCount,
  67. lastMessageAt: lastMessage?.createdAt.toISOString() || history.createdAt.toISOString(),
  68. createdAt: history.createdAt.toISOString(),
  69. };
  70. }),
  71. );
  72. return {
  73. histories: items,
  74. total,
  75. page,
  76. limit,
  77. };
  78. }
  79. async findOne(id: string, userId: string): Promise<SearchHistoryDetail> {
  80. const history = await this.searchHistoryRepository.findOne({
  81. where: { id, userId },
  82. relations: ['messages'],
  83. order: { messages: { createdAt: 'ASC' } },
  84. });
  85. if (!history) {
  86. throw new NotFoundException(this.i18nService.getMessage('conversationHistoryNotFound'));
  87. }
  88. return {
  89. id: history.id,
  90. title: history.title,
  91. selectedGroups: history.selectedGroups ? JSON.parse(history.selectedGroups) : null,
  92. messages: history.messages.map((message) => ({
  93. id: message.id,
  94. role: message.role,
  95. content: message.content,
  96. sources: message.sources ? JSON.parse(message.sources) : undefined,
  97. createdAt: message.createdAt.toISOString(),
  98. })),
  99. };
  100. }
  101. async create(
  102. userId: string,
  103. title: string,
  104. selectedGroups?: string[],
  105. ): Promise<SearchHistory> {
  106. const history = this.searchHistoryRepository.create({
  107. userId,
  108. title: title.length > 50 ? title.substring(0, 50) + '...' : title,
  109. selectedGroups: selectedGroups ? JSON.stringify(selectedGroups) : undefined,
  110. });
  111. return await this.searchHistoryRepository.save(history);
  112. }
  113. async addMessage(
  114. historyId: string,
  115. role: 'user' | 'assistant',
  116. content: string,
  117. sources?: any[],
  118. ): Promise<ChatMessage> {
  119. const message = this.chatMessageRepository.create({
  120. searchHistoryId: historyId,
  121. role,
  122. content,
  123. sources: sources ? JSON.stringify(sources) : undefined,
  124. });
  125. const savedMessage = await this.chatMessageRepository.save(message);
  126. // 履歴レコードの更新時間を更新
  127. await this.searchHistoryRepository.update(historyId, {
  128. updatedAt: new Date(),
  129. });
  130. return savedMessage;
  131. }
  132. async remove(id: string, userId: string): Promise<void> {
  133. const history = await this.searchHistoryRepository.findOne({
  134. where: { id, userId },
  135. });
  136. if (!history) {
  137. throw new NotFoundException(this.i18nService.getMessage('conversationHistoryNotFound'));
  138. }
  139. await this.searchHistoryRepository.remove(history);
  140. }
  141. async updateTitle(id: string, title: string): Promise<void> {
  142. await this.searchHistoryRepository.update(id, { title });
  143. }
  144. }