diff --git a/app/Http/Controllers/Api/Admin/MeetingLogController.php b/app/Http/Controllers/Api/Admin/MeetingLogController.php index a1c44cc2..c8c6550c 100644 --- a/app/Http/Controllers/Api/Admin/MeetingLogController.php +++ b/app/Http/Controllers/Api/Admin/MeetingLogController.php @@ -214,4 +214,37 @@ public function summary(int $id): View|JsonResponse ], ]); } + + /** + * 오디오 파일 업로드 및 처리 (회의록 AI 요약) + */ + public function uploadFile(Request $request): JsonResponse + { + $validated = $request->validate([ + 'audio_file' => 'required|file|mimes:webm,wav,mp3,ogg,m4a,mp4|max:102400', + 'title' => 'nullable|string|max:200', + ]); + + $meeting = $this->meetingLogService->create([ + 'title' => $validated['title'] ?? '업로드된 회의록', + ]); + + $result = $this->meetingLogService->processUploadedFile( + $meeting, + $request->file('audio_file') + ); + + if (! $result['ok']) { + return response()->json([ + 'success' => false, + 'message' => $result['error'] ?? '처리 중 오류가 발생했습니다.', + ], 500); + } + + return response()->json([ + 'success' => true, + 'message' => '회의록이 생성되었습니다.', + 'data' => $result['meeting'], + ]); + } } diff --git a/app/Services/MeetingLogService.php b/app/Services/MeetingLogService.php index 5459c252..ed41011d 100644 --- a/app/Services/MeetingLogService.php +++ b/app/Services/MeetingLogService.php @@ -246,4 +246,82 @@ public function cleanupExpiredFiles(): int return $count; } + + /** + * 업로드된 오디오 파일 처리 (회의록 AI 요약) + */ + public function processUploadedFile(MeetingLog $meeting, \Illuminate\Http\UploadedFile $file): array + { + try { + $meeting->update(['status' => MeetingLog::STATUS_PROCESSING]); + + // 임시 저장 + $tempPath = $file->store('temp', 'local'); + $fullPath = storage_path('app/'.$tempPath); + + // 파일 크기로 대략적인 재생 시간 추정 (12KB/초 기준) + $fileSize = $file->getSize(); + $estimatedDuration = max(1, intval($fileSize / 12000)); + $meeting->update(['duration_seconds' => $estimatedDuration]); + + // 1. GCS에 오디오 업로드 + $extension = $file->getClientOriginalExtension() ?: 'webm'; + $objectName = sprintf( + 'meetings/%d/%d/%s.%s', + $meeting->tenant_id, + $meeting->id, + now()->format('YmdHis'), + $extension + ); + + $gcsUri = $this->googleCloudService->uploadToStorage($fullPath, $objectName); + + if (! $gcsUri) { + @unlink($fullPath); + throw new \Exception('오디오 파일 업로드 실패'); + } + + $meeting->update([ + 'audio_file_path' => $objectName, + 'audio_gcs_uri' => $gcsUri, + ]); + + // 2. Speech-to-Text 변환 + $transcript = $this->googleCloudService->speechToText($gcsUri); + + // 임시 파일 삭제 + @unlink($fullPath); + + if (! $transcript) { + throw new \Exception('음성 인식 실패'); + } + + $meeting->update(['transcript_text' => $transcript]); + + // 3. AI 요약 생성 + $summary = $this->generateSummary($transcript); + + $meeting->update([ + 'summary_text' => $summary, + 'status' => MeetingLog::STATUS_COMPLETED, + ]); + + return [ + 'ok' => true, + 'meeting' => $meeting->fresh(), + ]; + } catch (\Exception $e) { + Log::error('MeetingLog 파일 처리 실패', [ + 'meeting_id' => $meeting->id, + 'error' => $e->getMessage(), + ]); + + $meeting->update(['status' => MeetingLog::STATUS_FAILED]); + + return [ + 'ok' => false, + 'error' => $e->getMessage(), + ]; + } + } } diff --git a/resources/views/lab/ai/meeting-summary.blade.php b/resources/views/lab/ai/meeting-summary.blade.php index 2730c7b7..1d0a43ca 100644 --- a/resources/views/lab/ai/meeting-summary.blade.php +++ b/resources/views/lab/ai/meeting-summary.blade.php @@ -4,59 +4,429 @@ @push('styles') @endpush @section('content') -
- 회의 녹음 파일을 업로드하면 AI가 자동으로 - 전사하고 핵심 내용을 구조화된 회의록으로 정리합니다. -
-회의 녹음 파일을 업로드하면 AI가 자동으로 회의록을 작성합니다
+