Files
sam-manage/app/Http/Controllers/Sales/AiInterviewController.php
김보곤 89c6d6fc43 feat: [interview] AI 대화형 인터뷰 초고도화 시스템 구현
Phase 2: AI 대화형 인터뷰 엔진
- AiInterviewController: 세션 시작/메시지/히스토리/진행률/지식추출 API
- AiInterviewService: Gemini API 연동 멀티턴 대화, 구조화 데이터 추출
- InterviewAiConversation: 대화 기록 모델 + 마이그레이션
- ai-interview.blade.php: React 채팅 UI (음성녹음/파일업로드/진행률)

Phase 3: 현장 데이터 수집 도구
- InterviewFileAnalysisService: 엑셀/PDF/이미지 AI 분석 (PhpSpreadsheet + Gemini Vision)
- 음성 녹음 + Gemini STT 변환
- 파일 자동 파싱 → interview_knowledge 자동 저장

Phase 4: 결과물 자동화
- TenantConfigGeneratorService: 인터뷰 지식 → SAM 테넌트 설정 자동 생성
- 12개 도메인별 설정 생성기 (품목/BOM/단가/공식/견적/공정 등)
- AI 정합성 검증
2026-03-22 22:42:25 +09:00

197 lines
6.2 KiB
PHP

<?php
namespace App\Http\Controllers\Sales;
use App\Http\Controllers\Controller;
use App\Services\Sales\AiInterviewService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class AiInterviewController extends Controller
{
public function __construct(
private AiInterviewService $aiInterviewService
) {}
/**
* AI 인터뷰 메인 페이지
*/
public function index(Request $request): View|\Illuminate\Http\Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('sales.interviews.ai.index'));
}
return view('sales.interviews.ai-interview');
}
/**
* AI 인터뷰 세션 시작
*/
public function startSession(Request $request): JsonResponse
{
$validated = $request->validate([
'project_id' => 'required|integer|exists:interview_projects,id',
'domain' => 'required|string|max:50',
'interviewee_name' => 'nullable|string|max:100',
'interviewee_company' => 'nullable|string|max:100',
]);
try {
$result = $this->aiInterviewService->startAiSession(
$validated['project_id'],
$validated['domain'],
$validated['interviewee_name'] ?? null,
$validated['interviewee_company'] ?? null,
);
return response()->json(['success' => true, 'data' => $result]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* AI에게 메시지 전송
*/
public function sendMessage(Request $request): JsonResponse
{
$validated = $request->validate([
'session_id' => 'required|integer|exists:interview_sessions,id',
'message' => 'required|string|max:10000',
'domain' => 'nullable|string|max:50',
]);
try {
$result = $this->aiInterviewService->sendMessage(
$validated['session_id'],
$validated['message'],
$validated['domain'] ?? null,
);
return response()->json(['success' => true, 'data' => $result]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* 대화 히스토리 조회
*/
public function getHistory(int $sessionId): JsonResponse
{
try {
$history = $this->aiInterviewService->getConversationHistory($sessionId);
return response()->json(['success' => true, 'data' => $history]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* 진행률 분석
*/
public function analyzeProgress(int $sessionId): JsonResponse
{
try {
$progress = $this->aiInterviewService->analyzeProgress($sessionId);
return response()->json(['success' => true, 'data' => $progress]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* 지식 자동 추출
*/
public function extractKnowledge(int $sessionId): JsonResponse
{
try {
$knowledge = $this->aiInterviewService->extractKnowledge($sessionId);
return response()->json(['success' => true, 'data' => $knowledge]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* 음성 파일 업로드 + STT 변환
*/
public function uploadVoice(Request $request): JsonResponse
{
$request->validate([
'session_id' => 'required|integer|exists:interview_sessions,id',
'audio' => 'required|file|mimes:webm,wav,mp3,ogg,m4a|max:51200',
]);
try {
$result = $this->aiInterviewService->processVoiceUpload(
$request->input('session_id'),
$request->file('audio'),
);
return response()->json(['success' => true, 'data' => $result]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* 파일 업로드 + AI 분석
*/
public function uploadFile(Request $request): JsonResponse
{
$request->validate([
'project_id' => 'required|integer|exists:interview_projects,id',
'session_id' => 'nullable|integer|exists:interview_sessions,id',
'file' => 'required|file|max:51200',
'file_type' => 'nullable|string|in:excel_template,pdf_quote,sample_bom,price_list,photo,other',
]);
try {
$result = $this->aiInterviewService->processFileUpload(
$request->input('project_id'),
$request->file('file'),
$request->input('file_type', 'other'),
$request->input('session_id'),
);
return response()->json(['success' => true, 'data' => $result]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* 프로젝트별 도메인 목록 + 진행 현황
*/
public function getDomains(int $projectId): JsonResponse
{
try {
$domains = $this->aiInterviewService->getProjectDomains($projectId);
return response()->json(['success' => true, 'data' => $domains]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
/**
* 프로젝트 목록 조회
*/
public function getProjects(): JsonResponse
{
try {
$projects = $this->aiInterviewService->getProjects();
return response()->json(['success' => true, 'data' => $projects]);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
}
}