- AiConfig 모델: AI API 설정 관리 - BusinessCardOcrService: Gemini Vision API 호출 - BusinessCardOcrController: OCR API 엔드포인트 - AiConfigController: AI 설정 CRUD - create.blade.php: 드래그앤드롭 명함 인식 UI - AI 설정 관리 페이지 추가
201 lines
5.8 KiB
PHP
201 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\System;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\System\AiConfig;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
|
|
class AiConfigController extends Controller
|
|
{
|
|
/**
|
|
* AI 설정 목록
|
|
*/
|
|
public function index(Request $request): View
|
|
{
|
|
if ($request->header('HX-Request')) {
|
|
return response('', 200)->header('HX-Redirect', route('system.ai-config.index'));
|
|
}
|
|
|
|
$configs = AiConfig::orderBy('provider')
|
|
->orderByDesc('is_active')
|
|
->orderBy('name')
|
|
->get();
|
|
|
|
return view('system.ai-config.index', compact('configs'));
|
|
}
|
|
|
|
/**
|
|
* AI 설정 저장
|
|
*/
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:50',
|
|
'provider' => 'required|string|in:gemini,claude,openai',
|
|
'api_key' => 'required|string|max:255',
|
|
'model' => 'required|string|max:100',
|
|
'base_url' => 'nullable|string|max:255|url',
|
|
'description' => 'nullable|string',
|
|
'is_active' => 'boolean',
|
|
]);
|
|
|
|
// 활성화 시 동일 provider의 다른 설정 비활성화
|
|
if ($validated['is_active'] ?? false) {
|
|
AiConfig::where('provider', $validated['provider'])
|
|
->update(['is_active' => false]);
|
|
}
|
|
|
|
$config = AiConfig::create($validated);
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'message' => '저장되었습니다.',
|
|
'data' => $config,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* AI 설정 수정
|
|
*/
|
|
public function update(Request $request, int $id): JsonResponse
|
|
{
|
|
$config = AiConfig::findOrFail($id);
|
|
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:50',
|
|
'provider' => 'required|string|in:gemini,claude,openai',
|
|
'api_key' => 'required|string|max:255',
|
|
'model' => 'required|string|max:100',
|
|
'base_url' => 'nullable|string|max:255',
|
|
'description' => 'nullable|string',
|
|
'is_active' => 'boolean',
|
|
]);
|
|
|
|
// 활성화 시 동일 provider의 다른 설정 비활성화
|
|
if ($validated['is_active'] ?? false) {
|
|
AiConfig::where('provider', $validated['provider'])
|
|
->where('id', '!=', $id)
|
|
->update(['is_active' => false]);
|
|
}
|
|
|
|
$config->update($validated);
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'message' => '수정되었습니다.',
|
|
'data' => $config->fresh(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* AI 설정 삭제
|
|
*/
|
|
public function destroy(int $id): JsonResponse
|
|
{
|
|
$config = AiConfig::findOrFail($id);
|
|
$config->delete();
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'message' => '삭제되었습니다.',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* AI 설정 활성화/비활성화 토글
|
|
*/
|
|
public function toggle(int $id): JsonResponse
|
|
{
|
|
$config = AiConfig::findOrFail($id);
|
|
|
|
if (! $config->is_active) {
|
|
// 활성화 시 동일 provider의 다른 설정 비활성화
|
|
AiConfig::where('provider', $config->provider)
|
|
->where('id', '!=', $id)
|
|
->update(['is_active' => false]);
|
|
}
|
|
|
|
$config->update(['is_active' => ! $config->is_active]);
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'message' => $config->is_active ? '활성화되었습니다.' : '비활성화되었습니다.',
|
|
'data' => $config->fresh(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* API 연결 테스트
|
|
*/
|
|
public function test(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'provider' => 'required|string|in:gemini,claude,openai',
|
|
'api_key' => 'required|string',
|
|
'model' => 'required|string',
|
|
'base_url' => 'nullable|string',
|
|
]);
|
|
|
|
try {
|
|
$provider = $validated['provider'];
|
|
$apiKey = $validated['api_key'];
|
|
$model = $validated['model'];
|
|
$baseUrl = $validated['base_url'] ?? AiConfig::DEFAULT_BASE_URLS[$provider];
|
|
|
|
if ($provider === 'gemini') {
|
|
$result = $this->testGemini($baseUrl, $model, $apiKey);
|
|
} else {
|
|
return response()->json([
|
|
'ok' => false,
|
|
'error' => '아직 지원하지 않는 provider입니다.',
|
|
]);
|
|
}
|
|
|
|
return response()->json($result);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'ok' => false,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gemini API 테스트
|
|
*/
|
|
private function testGemini(string $baseUrl, string $model, string $apiKey): array
|
|
{
|
|
$url = "{$baseUrl}/models/{$model}:generateContent?key={$apiKey}";
|
|
|
|
$response = \Illuminate\Support\Facades\Http::timeout(10)->post($url, [
|
|
'contents' => [
|
|
[
|
|
'parts' => [
|
|
['text' => '안녕하세요. 테스트입니다. "OK"라고만 응답해주세요.'],
|
|
],
|
|
],
|
|
],
|
|
'generationConfig' => [
|
|
'temperature' => 0,
|
|
'maxOutputTokens' => 10,
|
|
],
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
return [
|
|
'ok' => true,
|
|
'message' => '연결 테스트 성공',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => false,
|
|
'error' => 'API 응답 오류: ' . $response->status(),
|
|
];
|
|
}
|
|
}
|