v3.2.0: 카카오톡 연동 + 탐정앱 업그레이드 + 모바일 대응 강화
- 카카오톡 채널 챗봇 (오픈빌더 비동기 콜백 방식, 5초 제한 우회) - 카카오 "나에게 보내기" OAuth 알림 (kakao-notify.ts) - 채널 설정 UI + userMap으로 다중 사용자 계정 매핑 - 탐정앱: 탭(메모/체크리스트/타임라인), 우선순위, 검색, 보고서/고소장/접수안내 생성 - 모바일 브레이크포인트 640px → 1024px (갤럭시 폴드 대응) - 프롬프트 파일 제한 상향 (USER.md 3000, SOUL.md/AGENTS.md 4000) - 교통앱 마지막 선택 지역 localStorage 저장/복원 - docs/kakao-setup.md 작성 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+39
-7
@@ -156,7 +156,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
.mn-resize:hover,.mn-resize.dragging{background:#8b5cf6;}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media(max-width:640px){
|
||||
@media(max-width:1024px){
|
||||
.mn-hdr{flex-wrap:wrap;padding:5px 8px;gap:5px;}
|
||||
.mn-hdr h1{font-size:12px;}
|
||||
.mode-toggle{order:10;flex:0 0 100%;}
|
||||
@@ -198,7 +198,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
/* font-size ≥16px to prevent iOS auto-zoom */
|
||||
.mn-textarea,input,textarea{font-size:16px !important;}
|
||||
}
|
||||
@media(min-width:641px){
|
||||
@media(min-width:1025px){
|
||||
#mob-tabbar{display:none !important;}
|
||||
#mob-backdrop{display:none !important;}
|
||||
}
|
||||
@@ -375,7 +375,7 @@ function getToken(){try{return sessionStorage.getItem(TOKEN_KEY)||localStorage.g
|
||||
function authH(extra){const t=getToken();const h={...(extra||{})};if(t)h['Authorization']='Bearer '+t;return h;}
|
||||
async function checkAuth(){
|
||||
if(!getToken()){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}
|
||||
try{const r=await fetch('/api/auth/status',{headers:authH()});if(!r.ok){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}return true;}
|
||||
try{const r=await fetch('/api/auth/status',{headers:authH()});if(!r.ok){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}const d=await r.json();if(!d.authenticated){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}return true;}
|
||||
catch{location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}
|
||||
}
|
||||
|
||||
@@ -472,8 +472,37 @@ async function loadModels(){
|
||||
function saveModel(){selectedModel=document.getElementById('model-sel').value;try{localStorage.setItem(MODEL_KEY,selectedModel);}catch{}}
|
||||
|
||||
// ── Session ───────────────────────────────────────────────────────────────────
|
||||
const SID_KEY='mn_app_session';
|
||||
function getSessionId(){try{let s=sessionStorage.getItem(SID_KEY);if(!s){s='mn_'+Math.random().toString(36).slice(2,12);sessionStorage.setItem(SID_KEY,s);}return s;}catch{return 'mn_main';}}
|
||||
const APP_KEY='mn';
|
||||
let _sessionId=null;
|
||||
function getSessionId(){return _sessionId||'mn_main';}
|
||||
async function initSessionId(){
|
||||
try{const r=await fetch('/api/user/app-sessions',{headers:authH()});if(r.ok){const d=await r.json();if(d[APP_KEY]){_sessionId=d[APP_KEY];return;}}}catch{}
|
||||
_sessionId='mn_'+Math.random().toString(36).slice(2,12);
|
||||
try{await fetch('/api/user/app-sessions',{method:'PUT',headers:authH({'Content-Type':'application/json'}),body:JSON.stringify({appKey:APP_KEY,sessionId:_sessionId})});}catch{}
|
||||
}
|
||||
|
||||
async function loadHistory(){
|
||||
const sid=getSessionId();
|
||||
if(!sid||sid.endsWith('_main'))return;
|
||||
try{
|
||||
const r=await fetch('/api/chat/sessions/'+encodeURIComponent(sid),{headers:authH()});
|
||||
if(!r.ok)return;
|
||||
const d=await r.json();
|
||||
const msgs=(d.history||[]).filter(m=>m.role==='user'||m.role==='assistant');
|
||||
if(!msgs.length)return;
|
||||
const recent=msgs.slice(-30);
|
||||
const el=document.getElementById('chat-msgs');
|
||||
const sep=document.createElement('div');sep.style.cssText='text-align:center;color:var(--muted,#888);font-size:11px;padding:6px 0;';sep.textContent='─ 이전 대화 ─';el.appendChild(sep);
|
||||
for(const m of recent){
|
||||
if(m.role==='user')addMsg('user',escHtml(m.content||''));
|
||||
else addMsg('assistant',renderMd(m.content||''));
|
||||
}
|
||||
const sep2=document.createElement('div');sep2.style.cssText='text-align:center;color:var(--muted,#888);font-size:11px;padding:6px 0;';sep2.textContent='─ 이어서 ─';el.appendChild(sep2);
|
||||
el.scrollTop=el.scrollHeight;
|
||||
}catch{}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── Tools: show / hide ────────────────────────────────────────────────────────
|
||||
let activeTool=null;
|
||||
@@ -1067,10 +1096,11 @@ async function callChat(message){
|
||||
}finally{btn.disabled=false;btn.textContent='전송';}
|
||||
}
|
||||
|
||||
function clearChat(){
|
||||
async function clearChat(){
|
||||
if(!confirm('대화를 지울까요?'))return;
|
||||
document.getElementById('chat-msgs').innerHTML='';
|
||||
try{sessionStorage.removeItem(SID_KEY);}catch{}
|
||||
_sessionId='mn_'+Math.random().toString(36).slice(2,12);
|
||||
try{await fetch('/api/user/app-sessions',{method:'PUT',headers:authH({'Content-Type':'application/json'}),body:JSON.stringify({appKey:APP_KEY,sessionId:_sessionId})});}catch{}
|
||||
addMsg('system','새 대화가 시작되었습니다. 편하게 이야기해주세요.');
|
||||
}
|
||||
function handleKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMessage();}}
|
||||
@@ -1079,6 +1109,8 @@ function autoResize(el){el.style.height='auto';el.style.height=Math.min(el.scrol
|
||||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||||
(async()=>{
|
||||
const ok=await checkAuth();if(!ok)return;
|
||||
await initSessionId();
|
||||
await loadHistory();
|
||||
setMode(currentMode);
|
||||
await Promise.all([loadSkills(),loadModels()]);
|
||||
addMsg('system','🧠 마음 상담실에 오신 걸 환영합니다. 왼쪽 도구를 활용하거나 편하게 이야기해주세요.');
|
||||
|
||||
Reference in New Issue
Block a user