v3.2.2: 폴더블 폰 모바일 패널 토글 전면 수정 + 탐정앱 네트워크 분석 + 음성 유틸

- mobToggle 가드 640→1024: 음악·슬라이드·코드·날씨·언어·치과·ESP32·아두이노 앱
  (CSS max-width:1024px인데 JS guard가 >640으로 남아 폴드 내부화면에서 토글 불가)
- #mob-tabbar z-index 400/490→600, 패널 bottom 0→52px, backdrop inset 0 0 52px 0
  (backdrop이 탭바를 가려 열린 상태에서 다른 탭 전환 불가 문제 해결)
- 모든 innerWidth<=640 자동트리거 →<=1024 통일 (9개 앱)
- 탐정앱: 네트워크 분석 패널 (IP조회/Traceroute/Nmap/DNS/WHOIS/SSL/취약점/해시/웹스캔/배너/CVE)
- voice-utils.js 분리 — 서브앱 공통 음성입력+TTS 유틸
- app.js: 컨텍스트 토큰 게이지 (세션 목록에 tok/max 표시), usage 이벤트 처리
- routes-music.ts: Demucs 보컬분리 + YouTube 다운로드 잡 관리 (TTL 자동정리)
- server-v2.ts: 탐정 네트워크 API (traceroute/dns/whois/ssl/nmap/vuln/hash/webscan/cve)
- session.ts: resolveNumCtx export, /api/agent/session contextTokenEstimate 노출
- config.json: 카카오 채널 설정, ITS 교통 API 키 vault 참조
- styles.css: app-mic-btn / app-tts-btn 공통 스타일

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kim
2026-06-27 19:16:59 +09:00
co-authored by Claude Sonnet 4.6
parent 5a1ecfe36d
commit 75a36cdc7d
22 changed files with 1710 additions and 91 deletions
+12 -7
View File
@@ -166,7 +166,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
.mn-resize{display:none;}
/* sidebar & chat become fixed overlays */
.mn-sb{
position:fixed;top:0;left:0;bottom:0;z-index:500;
position:fixed;top:0;left:0;bottom:52px;z-index:500;
width:80vw;max-width:300px;
transform:translateX(-100%);transition:transform .25s ease;
border-right:1px solid var(--line);
@@ -174,7 +174,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
}
.mn-sb.mob-open{transform:translateX(0);}
.mn-chat{
position:fixed;top:0;right:0;bottom:0;z-index:500;
position:fixed;top:0;right:0;bottom:52px;z-index:500;
width:92vw;max-width:380px;
transform:translateX(100%);transition:transform .25s ease;
border-left:1px solid var(--line);
@@ -182,14 +182,14 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
}
.mn-chat.mob-open{transform:translateX(0);}
/* overlay backdrop */
#mob-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:499;}
#mob-backdrop{display:none;position:fixed;inset:0 0 52px 0;background:rgba(0,0,0,.5);z-index:499;}
#mob-backdrop.show{display:block;}
/* center takes full width */
.mn-center{width:100%;}
/* bottom tab bar */
#mob-tabbar{
display:flex !important;
position:fixed;bottom:0;left:0;right:0;z-index:400;
position:fixed;bottom:0;left:0;right:0;z-index:600;
background:var(--panel);border-top:1px solid var(--line);
height:52px;
}
@@ -243,6 +243,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
.mn-send:hover:not(:disabled){background:#6d28d9;}
.mn-send:disabled{opacity:.5;cursor:not-allowed;}
</style>
<script src="voice-utils.js"></script>
</head>
<body>
@@ -362,6 +363,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
<div class="mn-msgs" id="chat-msgs"></div>
<div class="mn-input-bar">
<textarea class="mn-textarea" id="chat-input" rows="1" placeholder="마음 속 이야기를 편하게 나눠보세요..." onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea>
<button class="app-mic-btn" id="mic-btn" onclick="toggleMicRecording()" title="음성 입력">🎤</button>
<button class="mn-send" id="send-btn" onclick="sendMessage()">전송</button>
</div>
</div>
@@ -523,7 +525,7 @@ const toolTitles={
function showTool(name){
// on mobile: close sidebar overlay after selecting a tool
if(window.innerWidth<=640) mobCloseAll();
if(window.innerWidth<=1024) mobCloseAll();
// stop breath timer if switching
if(breathTimer&&name!=='breath'){clearInterval(breathTimer);breathTimer=null;}
activeTool=name;
@@ -1018,7 +1020,9 @@ function addMsg(role,html){
const msgs=document.getElementById('chat-msgs');
const div=document.createElement('div');div.className='mn-msg '+role;
const b=document.createElement('div');b.className='mn-bubble';b.innerHTML=html;
div.appendChild(b);msgs.appendChild(div);msgs.scrollTop=msgs.scrollHeight;
div.appendChild(b);
if(role==='assistant'){const tb=document.createElement('button');tb.className='app-tts-btn';tb.title='음성으로 듣기';tb.textContent='🔊';tb.onclick=function(){playTTSText(tb,b.innerText);};div.appendChild(tb);}
msgs.appendChild(div);msgs.scrollTop=msgs.scrollHeight;
return b;
}
@@ -1048,7 +1052,7 @@ async function sendMessage(){
if(!msg||document.getElementById('send-btn').disabled)return;
input.value='';autoResize(input);
// on mobile: open chat panel so user can see the response
if(window.innerWidth<=640) mobToggle('chat');
if(window.innerWidth<=1024) mobToggle('chat');
addMsg('user',escHtml(msg));
await callChat(msg);
}
@@ -1114,6 +1118,7 @@ function autoResize(el){el.style.height='auto';el.style.height=Math.min(el.scrol
setMode(currentMode);
await Promise.all([loadSkills(),loadModels()]);
addMsg('system','🧠 마음 상담실에 오신 걸 환영합니다. 왼쪽 도구를 활용하거나 편하게 이야기해주세요.');
initAppVoice(sendMessage);
document.getElementById('chat-input').focus();
})();
</script>