From 95fbbd4fff7ef979aa4fa9e186044b9bb25678af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Mon, 9 Feb 2026 21:41:32 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=EA=B3=B5=EC=82=AC=ED=98=84=EC=9E=A5?= =?UTF-8?q?=20=EC=82=AC=EC=A7=84=EB=8C=80=EC=A7=80=20GCS=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=8B=9C=20AI=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EB=9F=89=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AiTokenHelper::saveGcsStorageUsage 호출 추가 Co-Authored-By: Claude Opus 4.6 --- app/Services/ConstructionSitePhotoService.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Services/ConstructionSitePhotoService.php b/app/Services/ConstructionSitePhotoService.php index 9dc562b7..ac4aaf52 100644 --- a/app/Services/ConstructionSitePhotoService.php +++ b/app/Services/ConstructionSitePhotoService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\Helpers\AiTokenHelper; use App\Models\Juil\ConstructionSitePhoto; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Support\Facades\Auth; @@ -86,6 +87,8 @@ public function uploadPhoto(ConstructionSitePhoto $photo, $file, string $type): $type . '_photo_size' => $result['size'], ]); + AiTokenHelper::saveGcsStorageUsage('공사현장사진대지-GCS저장', $result['size']); + return true; } From e1a9910939801e69908d3f29bdbad1e48ddcb08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Mon, 9 Feb 2026 21:49:02 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=ED=98=84=EC=9E=A5=EB=AA=85/?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EC=9D=8C=EC=84=B1=EC=9E=85=EB=A0=A5(STT)?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web Speech API 기반 음성→텍스트 변환 신규등록/수정 모드에서만 마이크 버튼 표시 Co-Authored-By: Claude Opus 4.6 --- .../views/juil/construction-photos.blade.php | 119 ++++++++++++++---- 1 file changed, 96 insertions(+), 23 deletions(-) diff --git a/resources/views/juil/construction-photos.blade.php b/resources/views/juil/construction-photos.blade.php index dd7e0bb5..d53125b7 100644 --- a/resources/views/juil/construction-photos.blade.php +++ b/resources/views/juil/construction-photos.blade.php @@ -53,6 +53,67 @@ return res.json(); } +// --- VoiceInputButton (Web Speech API STT) --- +function VoiceInputButton({ onResult, disabled, mode = 'replace' }) { + const [recording, setRecording] = useState(false); + const recognitionRef = useRef(null); + + const isSupported = typeof window !== 'undefined' && + (window.SpeechRecognition || window.webkitSpeechRecognition); + + const toggle = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (disabled || !isSupported) return; + + if (recording) { + recognitionRef.current?.stop(); + setRecording(false); + return; + } + + const SR = window.SpeechRecognition || window.webkitSpeechRecognition; + const recognition = new SR(); + recognition.lang = 'ko-KR'; + recognition.continuous = false; + recognition.interimResults = false; + recognition.maxAlternatives = 1; + + recognition.onresult = (event) => { + const text = event.results[0][0].transcript; + if (text) onResult(text, mode); + setRecording(false); + }; + recognition.onerror = () => setRecording(false); + recognition.onend = () => setRecording(false); + + recognitionRef.current = recognition; + recognition.start(); + setRecording(true); + }; + + if (!isSupported) return null; + + return ( + + ); +} + // --- ToastNotification --- function ToastNotification({ message, type, onClose }) { useEffect(() => { @@ -234,14 +295,17 @@ function CreateModal({ show, onClose, onCreate }) {
- setSiteName(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" - placeholder="공사 현장명을 입력하세요" - autoFocus - /> +
+ setSiteName(e.target.value)} + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" + placeholder="공사 현장명을 입력하세요" + autoFocus + /> + setSiteName(prev => prev ? prev + ' ' + text : text)} /> +
@@ -254,13 +318,16 @@ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus
-