v4.3.39: 법률 앱 사건 자동 이름짓기

메모를 처음 작성하고 포커스를 벗어나면(blur), 제목이 아직 기본값 "새 사건"인
경우에 한해 AI가 메모 내용을 보고 8자 이내 사건명을 지어 채워줌. 채팅창에는
안 보이는 조용한 호출(quietChatCall)로 처리 — 이미 만든 generateChecklist용
callChatRaw(화면에 타이핑 표시)와 달리 UI에 아무 흔적을 안 남김. 사용자가 이미
직접 이름을 바꿨거나 다른 사건으로 넘어간 경우는 덮어쓰지 않음.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kim
2026-07-30 17:41:58 +09:00
co-authored by Claude Opus 5
parent 1c2c5e23bc
commit 2be697aff8
+57 -1
View File
@@ -475,7 +475,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
- 의뢰인: 홍길동 (임차인)
- 쟁점: 전세보증금 반환 지연
- 관련 법령: 주택임대차보호법
- 상담 내용: 계약 만료일 경과 후 2개월째 미반환" oninput="onNotesChange()" onblur="saveCases()" onkeydown="handleNotesTab(event)"></textarea>
- 상담 내용: 계약 만료일 경과 후 2개월째 미반환" oninput="onNotesChange()" onblur="saveCases();autoNameCaseIfNeeded()" onkeydown="handleNotesTab(event)"></textarea>
</div>
</div>
@@ -865,6 +865,62 @@ function handleNotesTab(e){
onNotesChange();
}
// A no-UI chat call (no typing indicator/bubble in the visible panel) for
// small background AI tasks like auto-naming — callChatRaw is the visible-
// bubble version used by generateChecklist, this is the silent counterpart.
async function quietChatCall(message){
try{
const body={message,sessionId:'lw_tmp_'+Math.random().toString(36).slice(2,8),useTools:false,
...(selectedModel?{model:selectedModel}:{}),
...(skillContext?{skillContext}:{})};
const r=await fetch('/api/chat',{method:'POST',headers:authH({'Content-Type':'application/json'}),body:JSON.stringify(body)});
if(!r.ok)return null;
const reader=r.body.getReader();const dec=new TextDecoder();let buf='',textBuf='';
while(true){
const{done,value}=await reader.read();if(done)break;
buf+=dec.decode(value,{stream:true});
const parts=buf.split('\n\n');buf=parts.pop()||'';
for(const part of parts){
if(!part.startsWith('data: '))continue;
let ev;try{ev=JSON.parse(part.slice(6));}catch{continue;}
if(ev.type==='token'||ev.type==='text_delta')textBuf+=(ev.text||'');
}
}
return textBuf.trim();
}catch{return null;}
}
// Auto-names a new case from its notes the first time they're written, so
// "새 사건" doesn't have to be manually retitled — only fires while the title
// is still the untouched default, and never overrides a name the user typed
// in themselves (checked again after the AI call returns, in case they
// switched case or renamed it while waiting).
let lwAutoNaming=false;
async function autoNameCaseIfNeeded(){
const c=currentCase();
if(!c||lwAutoNaming)return;
const title=(document.getElementById('case-name-input').value||'').trim();
const notes=document.getElementById('case-notes').value||'';
if(title!=='새 사건'||notes.trim().length<15)return;
lwAutoNaming=true;
const targetId=c.id;
try{
const prompt=`다음은 법률 상담 메모입니다. 8자 이내의 간결한 사건명을 지어주세요. 사건명만 출력하고 다른 설명·따옴표·마침표는 넣지 마세요.\n\n${notes.slice(0,500)}`;
const result=await quietChatCall(prompt);
if(!result||currentCaseId!==targetId)return; // case switched away while waiting
const cur=cases.find(x=>x.id===targetId);
if(!cur||(document.getElementById('case-name-input').value||'').trim()!=='새 사건')return; // user already renamed it
const name=result.split('\n')[0].replace(/^["'“”]+|["'“”]+$/g,'').trim().slice(0,30);
if(!name)return;
cur.title=name;
document.getElementById('case-name-input').value=name;
renderCaseList();
const label=document.getElementById('lw-chat-ctx-label');
if(label)label.textContent=name;
saveCases();
}finally{lwAutoNaming=false;}
}
function switchCaseTab(tab){
activeCaseTab=tab;
['notes','checklist','timeline','files','docs'].forEach(t=>{