only(['search', 'status', 'per_page']); $meetings = $this->meetingLogService->getList($params); if ($request->header('HX-Request')) { return view('lab.ai.web-recording.partials.list', compact('meetings')); } return response()->json([ 'success' => true, 'data' => $meetings, ]); } /** * 회의록 상세 조회 */ public function show(int $id): JsonResponse { $meeting = $this->meetingLogService->getById($id); if (! $meeting) { return response()->json([ 'success' => false, 'message' => '회의록을 찾을 수 없습니다.', ], 404); } return response()->json([ 'success' => true, 'data' => $meeting, ]); } /** * 회의록 생성 (녹음 시작) */ public function store(Request $request): JsonResponse { $validated = $request->validate([ 'title' => 'nullable|string|max:200', ]); $meeting = $this->meetingLogService->create($validated); return response()->json([ 'success' => true, 'message' => '회의록이 생성되었습니다.', 'data' => $meeting, ], 201); } /** * 오디오 업로드 및 처리 */ public function processAudio(Request $request, int $id): JsonResponse { $meeting = MeetingLog::find($id); if (! $meeting) { return response()->json([ 'success' => false, 'message' => '회의록을 찾을 수 없습니다.', ], 404); } $validated = $request->validate([ 'audio' => 'required|string', // Base64 인코딩된 오디오 'duration' => 'required|integer|min:1', // 녹음 시간(초) ]); $result = $this->meetingLogService->processAudio( $meeting, $validated['audio'], $validated['duration'] ); if (! $result['ok']) { return response()->json([ 'success' => false, 'message' => $result['error'] ?? '처리 중 오류가 발생했습니다.', ], 500); } return response()->json([ 'success' => true, 'message' => '회의록이 생성되었습니다.', 'data' => $result['meeting'], ]); } /** * 제목 업데이트 */ public function updateTitle(Request $request, int $id): JsonResponse { $meeting = MeetingLog::find($id); if (! $meeting) { return response()->json([ 'success' => false, 'message' => '회의록을 찾을 수 없습니다.', ], 404); } $validated = $request->validate([ 'title' => 'required|string|max:200', ]); $meeting = $this->meetingLogService->updateTitle($meeting, $validated['title']); return response()->json([ 'success' => true, 'message' => '제목이 업데이트되었습니다.', 'data' => $meeting, ]); } /** * 회의록 삭제 */ public function destroy(int $id): JsonResponse { $meeting = MeetingLog::find($id); if (! $meeting) { return response()->json([ 'success' => false, 'message' => '회의록을 찾을 수 없습니다.', ], 404); } $this->meetingLogService->delete($meeting); return response()->json([ 'success' => true, 'message' => '회의록이 삭제되었습니다.', ]); } /** * 처리 상태 확인 (폴링용) */ public function status(int $id): JsonResponse { $meeting = MeetingLog::find($id); if (! $meeting) { return response()->json([ 'success' => false, 'message' => '회의록을 찾을 수 없습니다.', ], 404); } return response()->json([ 'success' => true, 'data' => [ 'id' => $meeting->id, 'status' => $meeting->status, 'status_label' => $meeting->status_label, 'is_completed' => $meeting->isCompleted(), 'is_processing' => $meeting->isProcessing(), ], ]); } /** * 요약 결과 조회 (HTMX용) */ public function summary(int $id): View|JsonResponse { $meeting = $this->meetingLogService->getById($id); if (! $meeting) { return response()->json([ 'success' => false, 'message' => '회의록을 찾을 수 없습니다.', ], 404); } if (request()->header('HX-Request')) { return view('lab.ai.web-recording.partials.summary', compact('meeting')); } return response()->json([ 'success' => true, 'data' => [ 'transcript' => $meeting->transcript_text, 'summary' => $meeting->summary_text, ], ]); } /** * 오디오 파일 업로드 및 처리 (회의록/업무협의록 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', 'summary_type' => 'nullable|string|in:meeting,work-memo', ]); $summaryType = $validated['summary_type'] ?? 'meeting'; $defaultTitle = $summaryType === 'work-memo' ? '업로드된 업무협의록' : '업로드된 회의록'; $meeting = $this->meetingLogService->create([ 'title' => $validated['title'] ?? $defaultTitle, ]); $result = $this->meetingLogService->processUploadedFile( $meeting, $request->file('audio_file'), $summaryType ); if (! $result['ok']) { return response()->json([ 'success' => false, 'message' => $result['error'] ?? '처리 중 오류가 발생했습니다.', ], 500); } $message = $summaryType === 'work-memo' ? '업무협의록이 생성되었습니다.' : '회의록이 생성되었습니다.'; return response()->json([ 'success' => true, 'message' => $message, 'data' => $result['meeting'], ]); } }