feat:사용자 매뉴얼 영상 자동 생성 기능 구현
- TutorialVideo 모델 (상태 관리, TenantScope) - GeminiScriptService에 callGeminiWithParts() 멀티모달 지원 추가 - ScreenAnalysisService: Gemini Vision 스크린샷 AI 분석 - SlideAnnotationService: PHP GD 이미지 어노테이션 (마커, 캡션) - TutorialAssemblyService: FFmpeg 이미지→영상 합성 (crossfade) - TutorialVideoJob: 분석→슬라이드→TTS→BGM→합성 파이프라인 - TutorialVideoController: 업로드/분석/생성/상태/다운로드/이력 API - React-in-Blade UI: 3단계 (업로드→분석확인→생성모니터링) + 이력 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -331,9 +331,19 @@ public function generateScenario(string $title, string $keyword = ''): array
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini API 호출 (AiConfig 기반 - API Key / Vertex AI 자동 분기)
|
||||
* Gemini API 호출 (텍스트 전용 - 기존 호환)
|
||||
*/
|
||||
private function callGemini(string $prompt): ?string
|
||||
{
|
||||
return $this->callGeminiWithParts([['text' => $prompt]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini API 호출 (멀티모달 지원 - 텍스트 + 이미지)
|
||||
*
|
||||
* @param array $parts [['text' => '...'], ['inlineData' => ['mimeType' => '...', 'data' => '...']]]
|
||||
*/
|
||||
public function callGeminiWithParts(array $parts, float $temperature = 0.9, int $maxTokens = 4096): ?string
|
||||
{
|
||||
if (! $this->config) {
|
||||
Log::error('GeminiScriptService: 활성화된 Gemini 설정이 없습니다. (시스템 > AI 설정 확인)');
|
||||
@@ -346,20 +356,17 @@ private function callGemini(string $prompt): ?string
|
||||
$body = [
|
||||
'contents' => [
|
||||
[
|
||||
'parts' => [
|
||||
['text' => $prompt],
|
||||
],
|
||||
'parts' => $parts,
|
||||
],
|
||||
],
|
||||
'generationConfig' => [
|
||||
'temperature' => 0.9,
|
||||
'maxOutputTokens' => 4096,
|
||||
'temperature' => $temperature,
|
||||
'maxOutputTokens' => $maxTokens,
|
||||
'responseMimeType' => 'application/json',
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->config->isVertexAi()) {
|
||||
// Vertex AI 방식 (서비스 계정 OAuth)
|
||||
$accessToken = $this->googleCloud->getAccessToken();
|
||||
if (! $accessToken) {
|
||||
Log::error('GeminiScriptService: Vertex AI 액세스 토큰 획득 실패');
|
||||
@@ -371,19 +378,17 @@ private function callGemini(string $prompt): ?string
|
||||
$region = $this->config->getRegion();
|
||||
$url = "https://{$region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$region}/publishers/google/models/{$model}:generateContent";
|
||||
|
||||
// Vertex AI에서는 role 필수
|
||||
$body['contents'][0]['role'] = 'user';
|
||||
|
||||
$response = Http::withToken($accessToken)
|
||||
->timeout(60)
|
||||
->timeout(120)
|
||||
->post($url, $body);
|
||||
} else {
|
||||
// API Key 방식
|
||||
$apiKey = $this->config->api_key;
|
||||
$baseUrl = $this->config->base_url ?? 'https://generativelanguage.googleapis.com/v1beta';
|
||||
$url = "{$baseUrl}/models/{$model}:generateContent?key={$apiKey}";
|
||||
|
||||
$response = Http::timeout(60)->post($url, $body);
|
||||
$response = Http::timeout(120)->post($url, $body);
|
||||
}
|
||||
|
||||
if (! $response->successful()) {
|
||||
@@ -407,6 +412,14 @@ private function callGemini(string $prompt): ?string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 응답 파싱 (public - 외부 서비스에서도 사용)
|
||||
*/
|
||||
public function parseJson(string $text): ?array
|
||||
{
|
||||
return $this->parseJsonResponse($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 응답 파싱 (코드블록 제거 포함)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user