import type { Env, KeyboardData } from '../types'; import { addToBuffer, processAndSummarize, generateAIResponse, } from '../summary-service'; import { sendChatAction } from '../telegram'; import { createLogger } from '../utils/logger'; import { saveConversationMessage } from './conversation-storage'; const logger = createLogger('conversation'); export interface ConversationResult { responseText: string; isProfileUpdated: boolean; keyboardData?: KeyboardData | null; } export class ConversationService { constructor(private env: Env) {} /** * 사용자 메시지를 처리하고 AI 응답을 생성합니다. * 버퍼링, AI 생성, 요약 프로세스를 포함합니다. */ async processUserMessage( userId: number, chatId: string, text: string, telegramUserId: string ): Promise { // 1. 타이핑 액션 전송 (비동기로 실행, 기다리지 않음) sendChatAction(this.env.BOT_TOKEN, Number(chatId), 'typing').catch(err => logger.error('타이핑 액션 전송 실패', err as Error) ); // 2. 새 시스템에 사용자 메시지 저장 await saveConversationMessage(this.env.DB, telegramUserId, { role: 'user', content: text, }); // 3. 기존 버퍼에도 저장 (호환성 - 나중에 제거) await addToBuffer(this.env.DB, userId, chatId, 'user', text); // 4. AI 응답 생성 let responseText = await generateAIResponse( this.env, userId, chatId, text, telegramUserId ); // __DIRECT__ 마커 제거 (AI가 그대로 전달한 경우 대비) if (responseText.includes('__DIRECT__')) { const directIndex = responseText.indexOf('__DIRECT__'); responseText = responseText.slice(directIndex + '__DIRECT__'.length).trim(); } // 5. 새 시스템에 봇 응답 저장 await saveConversationMessage(this.env.DB, telegramUserId, { role: 'assistant', content: responseText, }); // 6. 기존 버퍼에도 저장 (호환성 - 나중에 제거) // 봇 응답 버퍼에 추가 (키보드 데이터 마커 등은 그대로 저장) // 실제 사용자에게 보여질 텍스트만 저장하는 것이 좋으나, // 현재 구조상 전체를 저장하고 나중에 컨텍스트로 활용 시 정제될 수 있음 await addToBuffer(this.env.DB, userId, chatId, 'bot', responseText); // 7. 임계값 도달 시 프로필 업데이트 (요약) const { summarized } = await processAndSummarize( this.env, userId, chatId ); // 키보드 데이터 파싱 let keyboardData: KeyboardData | null = null; // s 플래그로 .이 줄바꿈도 매칭하도록 (멀티라인 JSON 대응) const keyboardMatch = responseText.match(/__KEYBOARD__(.+?)__END__\n?/s); if (keyboardMatch) { logger.debug('키보드 마커 감지', { preview: keyboardMatch[1].substring(0, 100) }); responseText = responseText.replace(/__KEYBOARD__.+?__END__\n?/s, ''); try { keyboardData = JSON.parse(keyboardMatch[1]) as KeyboardData; logger.debug('키보드 파싱 성공', { type: keyboardData.type }); } catch (e) { logger.error('키보드 파싱 오류', e as Error, { rawData: keyboardMatch[1] }); } } else if (responseText.includes('__KEYBOARD__')) { logger.warn('키보드 마커 발견했으나 정규식 매칭 실패', { preview: responseText.substring(0, 200) }); } return { responseText, isProfileUpdated: summarized, keyboardData }; } }