// ── 채팅 라이브 카메라 ─────────────────────────────────────────────────── // 📷 버튼으로 카메라 패널(미리보기)을 켜 두면, 메시지를 보낼 때마다(타이핑·음성 통화 STT 공통) // 현재 프레임을 찍어 이미지로 올리고 메시지에 자동 첨부한다. 서버는 기존 이미지 첨부와 같은 // 경로(/api/upload/image → ![alt](/api/files/..) → resolveImageContent)로 모델에 시각 입력을 넘긴다. // 프레임 alt는 CAMERA_ALT("📷…")로 시작 — 서버가 이전 턴의 카메라 프레임을 텍스트로 접어 비용 누적을 막는다. (function () { var CAMERA_ALT = '📷 카메라'; var MAX_SIDE = 768; // 긴 변 기준 다운스케일 (모델 입력 비용·업로드 크기 절감) var JPEG_QUALITY = 0.72; var stream = null; var facing = 'environment'; // 폰은 후면 우선, 후면이 없으면 브라우저가 알아서 대체 function $(id) { return document.getElementById(id); } // 메인 채팅은 authHeaders(), 작업실 페이지는 authH() — 둘 다 지원 function authHdr() { if (typeof window.authHeaders === 'function') return window.authHeaders(); if (typeof window.authH === 'function') return window.authH(); return {}; } function insertText(md) { if (typeof window.insertIntoChatInput === 'function') { window.insertIntoChatInput(md); return; } var el = $('chat-input'); if (!el) return; el.value = el.value ? el.value + '\n' + md : md; el.dispatchEvent(new Event('input')); el.focus(); } function setOpenUI(open) { var panel = $('camera-panel'), btn = $('camera-btn'); if (panel) panel.style.display = open ? 'flex' : 'none'; if (btn) { btn.classList.toggle('recording', open); btn.classList.toggle('on', open); btn.title = open ? '카메라 끄기' : '카메라 켜기 (메시지마다 현재 화면 첨부)'; } } async function openCamera() { var video = $('camera-video'); try { stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: facing }, width: { ideal: 1280 }, height: { ideal: 720 } }, audio: false }); } catch (e) { alert('카메라 권한이 필요합니다: ' + e.message); stream = null; return; } video.srcObject = stream; try { await video.play(); } catch (e) { /* autoplay 정책 — muted+playsinline이면 보통 통과 */ } setOpenUI(true); } function closeCamera() { if (stream) { try { stream.getTracks().forEach(function (t) { t.stop(); }); } catch (e) {} } stream = null; var video = $('camera-video'); if (video) video.srcObject = null; setOpenUI(false); } window.toggleCamera = function () { if (stream) closeCamera(); else openCamera(); }; window.cameraFlip = async function () { facing = facing === 'environment' ? 'user' : 'environment'; if (stream) { closeCamera(); await openCamera(); } }; window.cameraClose = closeCamera; window.cameraIsOn = function () { return !!stream; }; function captureBlob() { var video = $('camera-video'); if (!stream || !video || !video.videoWidth) return Promise.resolve(null); var w = video.videoWidth, h = video.videoHeight; var scale = Math.min(1, MAX_SIDE / Math.max(w, h)); var cw = Math.round(w * scale), ch = Math.round(h * scale); var canvas = document.createElement('canvas'); canvas.width = cw; canvas.height = ch; canvas.getContext('2d').drawImage(video, 0, 0, cw, ch); return new Promise(function (resolve) { canvas.toBlob(resolve, 'image/jpeg', JPEG_QUALITY); }); } window.cameraCaptureBlob = function () { return captureBlob(); }; // 현재 프레임을 서버에 올리고 채팅용 마크다운을 돌려준다. 실패하면 '' (메시지는 그대로 전송된다). async function uploadFrame() { var blob = await captureBlob(); if (!blob) return ''; var form = new FormData(); form.append('image', blob, 'camera-' + Date.now() + '.jpg'); var res = await fetch('/api/upload/image', { method: 'POST', headers: authHdr(), body: form }); var data = await res.json(); if (data && data.success && data.path) return '![' + CAMERA_ALT + '](/api/files/' + data.path + ')'; return ''; } // 📸 버튼: 지금 화면을 입력창에 이미지로 넣는다 (자동 첨부와 별개로 수동 첨부) window.cameraSnapAttach = async function () { try { var md = await uploadFrame(); if (md) insertText(md); else alert('카메라 화면을 아직 읽을 수 없습니다. 잠시 후 다시 시도하세요.'); } catch (e) { alert('촬영 오류: ' + e.message); } }; // sendChat이 전송 직전에 호출 — 라이브 자동 첨부가 켜져 있으면 프레임 마크다운, 아니면 '' window.cameraFrameForSend = async function () { var chk = $('camera-auto'); if (!stream || (chk && !chk.checked)) return ''; try { return await uploadFrame(); } catch (e) { console.warn('[camera] frame upload failed:', e.message); return ''; } }; // 탭을 떠나면 카메라 스트림 정리 (모바일에서 카메라 표시등이 계속 켜진 채 남는 것 방지) window.addEventListener('pagehide', closeCamera); })();