openfree commited on
Commit
14670b6
Β·
verified Β·
1 Parent(s): b069274

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +559 -23
index.html CHANGED
@@ -20,13 +20,20 @@ body{font-family:"SF Pro Display",-apple-system,BlinkMacSystemFont,"Noto Sans KR
20
  .logo{display:flex;align-items:center;justify-content:center;gap:15px}
21
  .logo h1{background:linear-gradient(135deg,#ff6b9d,#c44569);-webkit-background-clip:text;background-clip:text;color:transparent;font-size:26px;letter-spacing:1px;font-weight:700}
22
  .current-time{font-size:12px;color:#888;margin-top:5px}
 
 
 
23
  .email-section{background:rgba(30,30,30,0.8);backdrop-filter:blur(10px);border-radius:10px;padding:14px;border:1px solid var(--border-color);margin-bottom:12px}
24
  .email-section label{font-size:12px;color:#888;display:block;margin-bottom:6px}
25
  .email-section input{width:100%;padding:10px;background:rgba(0,0,0,0.3);border:1px solid #333;border-radius:6px;color:white;font-size:13px}
26
  .email-section input:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 10px rgba(111,66,193,0.2)}
27
  .sidebar-section{background:rgba(30,30,30,0.8);backdrop-filter:blur(10px);border-radius:10px;padding:14px;border:1px solid var(--border-color);transition:all 0.3s ease}
28
  .sidebar-section:hover{border-color:var(--primary-color);box-shadow:0 0 15px rgba(111,66,193,0.1)}
29
- .sidebar-section h3{margin:0 0 12px 0;font-size:13px;text-transform:uppercase;letter-spacing:0.5px;color:#999;font-weight:600}
 
 
 
 
30
  .streaming-indicator{display:none;align-items:center;gap:8px;padding:8px 12px;background:linear-gradient(135deg,rgba(255,107,107,0.1),rgba(111,66,193,0.1));border-radius:6px;margin-bottom:10px;border:1px solid var(--streaming-color)}
31
  .streaming-indicator.active{display:flex}
32
  .streaming-dot{width:8px;height:8px;background:var(--streaming-color);border-radius:50%;animation:pulse 1.5s infinite}
@@ -65,13 +72,37 @@ body{font-family:"SF Pro Display",-apple-system,BlinkMacSystemFont,"Noto Sans KR
65
  .stat-item{text-align:center}
66
  .stat-value{font-size:18px;font-weight:bold;color:var(--primary-color)}
67
  .stat-label{font-size:10px;color:#888;text-transform:uppercase}
 
 
 
68
  .memory-list{max-height:150px;overflow-y:auto}
69
- .memory-item{padding:10px;margin-bottom:8px;background:linear-gradient(135deg,rgba(74,158,255,0.1),rgba(111,66,193,0.1));border-radius:6px;border-left:3px solid var(--memory-color);position:relative;font-size:12px;transition:all 0.3s}
70
  .memory-item:hover{transform:translateX(5px);box-shadow:0 2px 10px rgba(74,158,255,0.2)}
71
- .memory-item:hover .memory-delete{opacity:1}
72
- .memory-score{position:absolute;top:5px;right:30px;font-size:10px;color:var(--memory-color);background:rgba(74,158,255,0.2);padding:2px 6px;border-radius:4px}
73
- .memory-delete{position:absolute;top:5px;right:5px;font-size:14px;color:#ff6b6b;cursor:pointer;opacity:0;transition:opacity 0.3s;background:rgba(255,107,107,0.2);width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;line-height:1}
74
- .memory-delete:hover{background:rgba(255,107,107,0.4);transform:scale(1.1)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  .chat-container{border-radius:10px;background:rgba(30,30,30,0.8);backdrop-filter:blur(10px);box-shadow:0 4px 20px rgba(0,0,0,0.3);padding:15px;flex-grow:1;display:flex;flex-direction:column;border:1px solid var(--border-color);overflow:hidden;min-height:0;height:100%}
76
  .chat-messages{flex-grow:1;overflow-y:auto;padding:10px;scrollbar-width:thin;scrollbar-color:var(--primary-color) transparent;min-height:0;max-height:calc(100vh - 280px)}
77
  .message{margin-bottom:12px;padding:12px 16px;border-radius:10px;font-size:15px;line-height:1.8;position:relative;max-width:85%;animation:slideIn 0.3s ease-out;word-wrap:break-word}
@@ -97,11 +128,28 @@ button{background:linear-gradient(135deg,#c44569,#ff6b9d);color:white;border:non
97
  button:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 4px 12px rgba(255,107,157,0.5)}
98
  button:disabled{opacity:0.5;cursor:not-allowed}
99
  #send-button{background:linear-gradient(135deg,#2ecc71,#27ae60)}
 
 
 
100
  .toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);padding:14px 24px;border-radius:8px;font-size:14px;z-index:1000;display:none;box-shadow:0 4px 12px rgba(0,0,0,0.3);animation:slideDown 0.3s ease-out}
101
  @keyframes slideDown{from{transform:translateX(-50%) translateY(-20px);opacity:0}to{transform:translateX(-50%) translateY(0);opacity:1}}
102
  .toast.success{background:linear-gradient(135deg,#4caf50,#45a049);color:white}
103
  .toast.error{background:linear-gradient(135deg,#f44336,#e53935);color:white}
104
  .toast.warning{background:linear-gradient(135deg,var(--warning-color),#ff9800);color:#333}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  .api-section{background:rgba(30,30,30,0.95);border-top:1px solid var(--border-color);padding:15px;margin-top:15px}
106
  .api-section h3{text-align:center;margin-bottom:10px;color:#888;font-size:12px;text-transform:uppercase}
107
  .api-docs-container{background:rgba(0,0,0,0.3);border-radius:10px;padding:20px;border:1px solid var(--border-color);max-height:500px;overflow-y:auto;display:none}
@@ -113,19 +161,54 @@ button:disabled{opacity:0.5;cursor:not-allowed}
113
  .api-method.delete{background:#dc3545;color:white}
114
  .api-url{font-family:monospace;color:#4a9eff;font-size:13px}
115
  .api-desc{color:#aaa;font-size:12px;margin-top:8px;line-height:1.5}
116
- .api-params{margin-top:10px;font-size:12px}
117
- .api-params code{background:rgba(0,0,0,0.4);padding:2px 6px;border-radius:3px;color:#e06c75}
118
  .collapse-btn{background:rgba(0,0,0,0.3);border:1px solid var(--border-color);color:#888;padding:8px 20px;font-size:11px;margin:0 auto;display:block;margin-bottom:10px}
119
  .collapse-btn:hover{background:rgba(111,66,193,0.2);color:white}
120
- @media (max-width:768px){.main-content{flex-direction:column}.sidebar{width:100%;max-height:none}.health-stats{grid-template-columns:1fr 1fr}}
121
  </style>
122
  </head>
123
  <body>
124
  <div id="error-toast" class="toast"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  <div class="container">
126
  <div class="header">
127
  <div class="logo"><h1>μ‹œλ‹ˆμ–΄ 말벗 AI</h1></div>
128
  <div class="current-time" id="current-time"></div>
 
129
  </div>
130
  <div class="main-content">
131
  <div class="sidebar">
@@ -133,8 +216,11 @@ button:disabled{opacity:0.5;cursor:not-allowed}
133
  <label>이메일 (ν•„μˆ˜)</label>
134
  <input type="email" id="user-email" placeholder="your@email.com">
135
  </div>
 
 
136
  <div class="sidebar-section health-section">
137
- <h3>건강 μƒνƒœ 진단</h3>
 
138
  <div class="health-gauge">
139
  <div class="gauge-container">
140
  <div class="gauge-bg"></div>
@@ -166,8 +252,12 @@ button:disabled{opacity:0.5;cursor:not-allowed}
166
  <canvas id="health-chart"></canvas>
167
  </div>
168
  </div>
 
 
 
169
  <div class="sidebar-section">
170
- <h3>μ„€μ •</h3>
 
171
  <div class="settings-grid">
172
  <div class="setting-item">
173
  <span class="setting-label">μ›Ή 검색</span>
@@ -181,25 +271,73 @@ button:disabled{opacity:0.5;cursor:not-allowed}
181
  <span class="setting-label">슀트리밍</span>
182
  <div id="streaming-toggle" class="toggle-switch active"><div class="toggle-slider"></div></div>
183
  </div>
 
 
 
 
184
  </div>
185
  <div style="margin-top:12px;">
186
  <label style="font-size:12px;color:#888;">이름 (호칭)</label>
187
  <input type="text" id="user-name" placeholder="μ–΄λ₯΄μ‹  성함..." style="width:100%;margin-top:4px;padding:8px;background:rgba(0,0,0,0.3);border:1px solid #333;border-radius:6px;color:white;font-size:12px;">
188
  </div>
189
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  <div class="sidebar-section">
191
- <h3>λŒ€ν™” κΈ°μ–΅</h3>
 
192
  <div class="memory-stats">
193
  <div class="stat-item"><div class="stat-value" id="memory-count">0</div><div class="stat-label">총 κΈ°μ–΅</div></div>
194
  <div class="stat-item"><div class="stat-value" id="vector-count">0</div><div class="stat-label">μ€‘μš” κΈ°μ–΅</div></div>
195
  <div class="stat-item"><div class="stat-value" id="relation-count">0</div><div class="stat-label">μ—°κ΄€ 관계</div></div>
196
  </div>
 
 
 
 
 
197
  <div class="memory-list" id="memory-list"></div>
198
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  <div style="text-align:center;margin-top:10px;">
200
  <button id="end-session-button">λŒ€ν™” μ €μž₯ν•˜κΈ°</button>
 
201
  </div>
202
  </div>
 
203
  <div class="chat-section">
204
  <div class="chat-container">
205
  <div class="streaming-indicator" id="streaming-indicator"><div class="streaming-dot"></div><span style="font-size:12px;color:var(--streaming-color);">응닡 생성 쀑...</span></div>
@@ -221,6 +359,7 @@ button:disabled{opacity:0.5;cursor:not-allowed}
221
  </div>
222
  </div>
223
  </div>
 
224
  <div class="api-section" id="api-section">
225
  <button class="collapse-btn" id="api-collapse-btn">API λ¬Έμ„œ μ—΄κΈ°/λ‹«κΈ°</button>
226
  <h3>API μ—”λ“œν¬μΈνŠΈ κ°€μ΄λ“œ</h3>
@@ -235,7 +374,16 @@ button:disabled{opacity:0.5;cursor:not-allowed}
235
  <h4>μƒˆ μ„Έμ…˜ 생성</h4>
236
  <span class="api-method post">POST</span>
237
  <span class="api-url">/session/new</span>
238
- <div class="api-desc">μƒˆλ‘œμš΄ λŒ€ν™” μ„Έμ…˜μ„ μ‹œμž‘ν•©λ‹ˆλ‹€.</div>
 
 
 
 
 
 
 
 
 
239
  </div>
240
  <div class="api-endpoint">
241
  <h4>건강 μƒνƒœ 쑰회</h4>
@@ -243,29 +391,86 @@ button:disabled{opacity:0.5;cursor:not-allowed}
243
  <span class="api-url">/health/current?user_email={email}</span>
244
  </div>
245
  <div class="api-endpoint">
 
 
 
 
 
246
  <h4>κΈ°μ–΅ 검색</h4>
247
  <span class="api-method post">POST</span>
248
  <span class="api-url">/memory/search</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  </div>
250
  <div class="api-endpoint">
251
  <h4>λͺ¨λ“  κΈ°μ–΅ 쑰회</h4>
252
  <span class="api-method get">GET</span>
253
  <span class="api-url">/memory/all?user_email={email}</span>
254
  </div>
 
 
 
 
255
  </div>
 
 
 
 
256
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  <script>
258
- let webSearchEnabled=true,selfLearningEnabled=true,streamingEnabled=true,currentSessionId=null;
 
259
  let userName=localStorage.getItem('userName')||'';
260
  let userEmail=localStorage.getItem('userEmail')||'';
261
  let userMemories={},isStreaming=false,healthChart=null;
 
262
  marked.setOptions({breaks:true,gfm:true,headerIds:false,mangle:false});
 
263
  function scoreToLabel(score){
264
  if(score>=80)return{text:'μ–‘ν˜Έ',class:'good'};
265
  if(score>=60)return{text:'보톡',class:'normal'};
266
  if(score>=40)return{text:'μœ„ν—˜',class:'warning'};
267
  return{text:'κΈ΄κΈ‰',class:'critical'};
268
  }
 
269
  const elements={
270
  endSessionButton:document.getElementById('end-session-button'),
271
  sendButton:document.getElementById('send-button'),
@@ -273,12 +478,16 @@ chatMessages:document.getElementById('chat-messages'),
273
  searchToggle:document.getElementById('search-toggle'),
274
  learningToggle:document.getElementById('learning-toggle'),
275
  streamingToggle:document.getElementById('streaming-toggle'),
 
276
  textInput:document.getElementById('text-input'),
277
  memoryList:document.getElementById('memory-list'),
 
278
  userNameInput:document.getElementById('user-name'),
279
  userEmailInput:document.getElementById('user-email'),
280
  currentTimeDiv:document.getElementById('current-time'),
 
281
  streamingIndicator:document.getElementById('streaming-indicator'),
 
282
  memoryCount:document.getElementById('memory-count'),
283
  vectorCount:document.getElementById('vector-count'),
284
  relationCount:document.getElementById('relation-count'),
@@ -290,8 +499,29 @@ conversationValue:document.getElementById('conversation-value'),
290
  memoryValue:document.getElementById('memory-value'),
291
  emotionalValue:document.getElementById('emotional-value'),
292
  apiCollapseBtn:document.getElementById('api-collapse-btn'),
293
- apiDocsContainer:document.getElementById('api-docs-container')
 
294
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  elements.apiCollapseBtn.addEventListener('click',()=>{
296
  const container=elements.apiDocsContainer;
297
  if(container.style.display==='none'||container.style.display===''){
@@ -302,6 +532,8 @@ container.style.display='none';
302
  elements.apiCollapseBtn.textContent='API λ¬Έμ„œ μ—΄κΈ°/λ‹«κΈ°';
303
  }
304
  });
 
 
305
  function initHealthChart(){
306
  const ctx=document.getElementById('health-chart').getContext('2d');
307
  healthChart=new Chart(ctx,{
@@ -315,6 +547,7 @@ data:{labels:[],datasets:[
315
  options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:true,position:'bottom',labels:{boxWidth:8,padding:8,font:{size:9},color:'#888'}}},scales:{x:{display:true,grid:{color:'rgba(255,255,255,0.05)'},ticks:{font:{size:8},color:'#666',maxRotation:0}},y:{display:true,min:0,max:100,grid:{color:'rgba(255,255,255,0.05)'},ticks:{font:{size:8},color:'#666'}}}}
316
  });
317
  }
 
318
  function updateGauge(riskLevel,riskLabel){
319
  const angle=-90+(riskLevel/100)*180;
320
  elements.gaugeNeedle.style.transform='rotate('+angle+'deg)';
@@ -323,11 +556,41 @@ elements.gaugeValue.textContent=label.text;
323
  elements.gaugeLabel.textContent=riskLabel||label.text;
324
  elements.gaugeLabel.className='gauge-label '+label.class;
325
  }
 
326
  function updateHealthStat(element,score){
327
  const label=scoreToLabel(score);
328
  element.textContent=label.text;
329
  element.className='health-stat-value '+label.class;
330
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  async function loadHealthStatus(){
332
  if(!userEmail)return;
333
  try{
@@ -342,6 +605,7 @@ updateGauge(data.risk_level||70,data.risk_label||'μ–‘ν˜Έ');
342
  }
343
  }catch(error){console.error('Failed to load health status:',error);}
344
  }
 
345
  async function loadHealthHistory(){
346
  if(!userEmail)return;
347
  try{
@@ -362,6 +626,51 @@ healthChart.update();
362
  }
363
  }catch(error){console.error('Failed to load health history:',error);}
364
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  async function loadGraphStats(){
366
  if(!userEmail)return;
367
  try{
@@ -370,6 +679,189 @@ const stats=await response.json();
370
  if(stats.total_edges!==undefined){elements.relationCount.textContent=stats.total_edges;}
371
  }catch(error){console.error('Failed to load graph stats:',error);}
372
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  function updateCurrentTime(){
374
  const now=new Date();
375
  const kstTime=new Date(now.toLocaleString("en-US",{timeZone:"Asia/Seoul"}));
@@ -378,13 +870,22 @@ elements.currentTimeDiv.textContent=timeString;
378
  }
379
  updateCurrentTime();
380
  setInterval(updateCurrentTime,1000);
 
381
  elements.userNameInput.value=userName;
382
  elements.userEmailInput.value=userEmail;
383
  elements.userNameInput.addEventListener('input',()=>{userName=elements.userNameInput.value;localStorage.setItem('userName',userName);});
384
  elements.userEmailInput.addEventListener('input',()=>{userEmail=elements.userEmailInput.value.trim();localStorage.setItem('userEmail',userEmail);});
 
385
  elements.searchToggle.addEventListener('click',()=>{webSearchEnabled=!webSearchEnabled;elements.searchToggle.classList.toggle('active',webSearchEnabled);showToast(webSearchEnabled?'μ›Ή 검색 ν™œμ„±ν™”':'μ›Ή 검색 λΉ„ν™œμ„±ν™”','success');});
386
  elements.learningToggle.addEventListener('click',()=>{selfLearningEnabled=!selfLearningEnabled;elements.learningToggle.classList.toggle('active',selfLearningEnabled);showToast(selfLearningEnabled?'μžκ°€ ν•™μŠ΅ ν™œμ„±ν™”':'μžκ°€ ν•™μŠ΅ λΉ„ν™œμ„±ν™”','success');});
387
  elements.streamingToggle.addEventListener('click',()=>{streamingEnabled=!streamingEnabled;elements.streamingToggle.classList.toggle('active',streamingEnabled);showToast(streamingEnabled?'슀트리밍 ν™œμ„±ν™”':'슀트리밍 λΉ„ν™œμ„±ν™”','success');});
 
 
 
 
 
 
 
388
  async function deleteMemory(memoryId,element){
389
  if(!confirm('이 기얡을 μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?'))return;
390
  try{
@@ -393,6 +894,7 @@ if(response.ok){element.remove();showToast('기얡이 μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.','s
393
  else{showToast('μ‚­μ œ μ‹€νŒ¨','error');}
394
  }catch(error){console.error('Delete memory error:',error);showToast('μ‚­μ œ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.','error');}
395
  }
 
396
  async function sendMessage(){
397
  const message=elements.textInput.value.trim();
398
  userEmail=elements.userEmailInput.value.trim();
@@ -406,6 +908,7 @@ elements.textInput.value='';
406
  if(streamingEnabled){await sendStreamingMessage(message);}
407
  else{showToast('μŠ€νŠΈλ¦¬λ°μ„ ν™œμ„±ν™”ν•΄μ£Όμ„Έμš”.','warning');}
408
  }
 
409
  async function sendStreamingMessage(message){
410
  isStreaming=true;
411
  elements.sendButton.disabled=true;
@@ -437,6 +940,7 @@ catch(e){console.log('Parse error:',e,data);}
437
  }catch(error){console.error('Streaming error:',error);assistantMessage.classList.remove('streaming');assistantMessage.textContent='였λ₯˜: '+error.message;showToast('슀트리밍 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.','error');}
438
  finally{isStreaming=false;elements.sendButton.disabled=false;elements.streamingIndicator.classList.remove('active');await loadMemories();await loadHealthStatus();await loadHealthHistory();}
439
  }
 
440
  function addMessage(role,content){
441
  const messageDiv=document.createElement('div');
442
  messageDiv.classList.add('message',role);
@@ -445,27 +949,47 @@ else{messageDiv.textContent=content;}
445
  elements.chatMessages.appendChild(messageDiv);
446
  elements.chatMessages.scrollTop=elements.chatMessages.scrollHeight;
447
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  async function loadMemories(){
449
  if(!userEmail)return;
450
  try{
451
  const response=await fetch('/memory/all?user_email='+encodeURIComponent(userEmail));
452
  const memories=await response.json();
453
  userMemories={};
454
- elements.memoryList.innerHTML='';
455
  memories.forEach(memory=>{
456
  if(!userMemories[memory.category]){userMemories[memory.category]=[];}
457
  userMemories[memory.category].push(memory.content);
458
- const item=document.createElement('div');
459
- item.className='memory-item';
460
- const importance=(memory.importance*100).toFixed(0);
461
- item.innerHTML='<div class="memory-score">'+importance+'%</div><div class="memory-delete" onclick="deleteMemory('+memory.id+', this.parentElement)">X</div><div style="font-size:10px;color:#888;margin-bottom:4px;">'+memory.category+'</div><div>'+memory.content+'</div>';
462
- elements.memoryList.appendChild(item);
463
  });
 
464
  elements.memoryCount.textContent=memories.length;
465
  elements.vectorCount.textContent=memories.filter(m=>m.importance>0.7).length;
466
  await loadGraphStats();
467
  }catch(error){console.error('Failed to load memories:',error);}
468
  }
 
469
  async function startNewSession(){
470
  userEmail=elements.userEmailInput.value.trim();
471
  if(!userEmail||!userEmail.includes('@')){showToast('μ˜¬λ°”λ₯Έ 이메일을 μž…λ ₯ν•΄μ£Όμ„Έμš”.','warning');return false;}
@@ -478,19 +1002,24 @@ console.log('New session started:',currentSessionId);
478
  await loadMemories();
479
  await loadHealthStatus();
480
  await loadHealthHistory();
 
 
 
481
  const displayName=userName||userEmail.split('@')[0];
482
  addMessage('assistant','μ—¬λ³΄μ„Έμš”, '+displayName+'λ‹˜! 잘 μ§€λ‚΄μ…¨μ–΄μš”? 였늘 ν•˜λ£¨ μ–΄λ– μ…¨λŠ”μ§€ μ–˜κΈ°ν•΄ μ£Όμ„Έμš”.');
483
  return true;
484
  }catch(error){console.error('Session creation error:',error);showToast('μ„Έμ…˜ 생성 μ‹€νŒ¨','error');return false;}
485
  }
 
486
  async function endSession(){
487
  if(!currentSessionId||!userEmail){showToast('μ €μž₯ν•  μ„Έμ…˜μ΄ μ—†μŠ΅λ‹ˆλ‹€.','warning');return;}
488
  try{
489
  showToast('λŒ€ν™” λ‚΄μš©μ„ μ €μž₯ν•˜λŠ” 쀑...','success');
490
  const response=await fetch('/session/end',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({session_id:currentSessionId,user_email:userEmail})});
491
- if(response.ok){showToast('λŒ€ν™”κ°€ μ„±κ³΅μ μœΌλ‘œ μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€!','success');await loadMemories();await loadHealthStatus();await loadHealthHistory();}
492
  }catch(error){console.error('Failed to end session:',error);showToast('μ €μž₯ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.','error');}
493
  }
 
494
  function showToast(message,type){
495
  const toast=document.getElementById('error-toast');
496
  toast.textContent=message;
@@ -498,11 +1027,18 @@ toast.className='toast '+type;
498
  toast.style.display='block';
499
  setTimeout(()=>{toast.style.display='none';},3000);
500
  }
 
501
  elements.textInput.addEventListener('keypress',(e)=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMessage();}});
502
  elements.sendButton.addEventListener('click',sendMessage);
503
  document.querySelectorAll('.quick-btn').forEach(btn=>{btn.addEventListener('click',()=>{const prompt=btn.dataset.prompt;if(prompt&&!isStreaming){elements.textInput.value=prompt;sendMessage();}});});
504
  elements.endSessionButton.addEventListener('click',endSession);
505
- window.addEventListener('DOMContentLoaded',()=>{initHealthChart();if(userEmail&&userEmail.includes('@')){startNewSession();}});
 
 
 
 
 
 
506
  </script>
507
  </body>
508
  </html>
 
20
  .logo{display:flex;align-items:center;justify-content:center;gap:15px}
21
  .logo h1{background:linear-gradient(135deg,#ff6b9d,#c44569);-webkit-background-clip:text;background-clip:text;color:transparent;font-size:26px;letter-spacing:1px;font-weight:700}
22
  .current-time{font-size:12px;color:#888;margin-top:5px}
23
+ .storage-info{font-size:10px;color:#666;margin-top:3px}
24
+ .storage-info.persistent{color:#28a745}
25
+ .storage-info.local{color:#ffc107}
26
  .email-section{background:rgba(30,30,30,0.8);backdrop-filter:blur(10px);border-radius:10px;padding:14px;border:1px solid var(--border-color);margin-bottom:12px}
27
  .email-section label{font-size:12px;color:#888;display:block;margin-bottom:6px}
28
  .email-section input{width:100%;padding:10px;background:rgba(0,0,0,0.3);border:1px solid #333;border-radius:6px;color:white;font-size:13px}
29
  .email-section input:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 10px rgba(111,66,193,0.2)}
30
  .sidebar-section{background:rgba(30,30,30,0.8);backdrop-filter:blur(10px);border-radius:10px;padding:14px;border:1px solid var(--border-color);transition:all 0.3s ease}
31
  .sidebar-section:hover{border-color:var(--primary-color);box-shadow:0 0 15px rgba(111,66,193,0.1)}
32
+ .sidebar-section h3{margin:0 0 12px 0;font-size:13px;text-transform:uppercase;letter-spacing:0.5px;color:#999;font-weight:600;display:flex;align-items:center;justify-content:space-between}
33
+ .section-toggle{cursor:pointer;font-size:16px;transition:transform 0.3s}
34
+ .section-toggle.collapsed{transform:rotate(-90deg)}
35
+ .section-content{transition:max-height 0.3s ease-out;overflow:hidden}
36
+ .section-content.collapsed{max-height:0!important}
37
  .streaming-indicator{display:none;align-items:center;gap:8px;padding:8px 12px;background:linear-gradient(135deg,rgba(255,107,107,0.1),rgba(111,66,193,0.1));border-radius:6px;margin-bottom:10px;border:1px solid var(--streaming-color)}
38
  .streaming-indicator.active{display:flex}
39
  .streaming-dot{width:8px;height:8px;background:var(--streaming-color);border-radius:50%;animation:pulse 1.5s infinite}
 
72
  .stat-item{text-align:center}
73
  .stat-value{font-size:18px;font-weight:bold;color:var(--primary-color)}
74
  .stat-label{font-size:10px;color:#888;text-transform:uppercase}
75
+ .memory-search-box{display:flex;gap:8px;margin-bottom:10px}
76
+ .memory-search-box input{flex:1;padding:8px;background:rgba(0,0,0,0.3);border:1px solid #333;border-radius:6px;color:white;font-size:12px}
77
+ .memory-search-box button{padding:8px 12px;font-size:11px}
78
  .memory-list{max-height:150px;overflow-y:auto}
79
+ .memory-item{padding:10px;margin-bottom:8px;background:linear-gradient(135deg,rgba(74,158,255,0.1),rgba(111,66,193,0.1));border-radius:6px;border-left:3px solid var(--memory-color);position:relative;font-size:12px;transition:all 0.3s;cursor:pointer}
80
  .memory-item:hover{transform:translateX(5px);box-shadow:0 2px 10px rgba(74,158,255,0.2)}
81
+ .memory-item:hover .memory-actions{opacity:1}
82
+ .memory-score{position:absolute;top:5px;right:50px;font-size:10px;color:var(--memory-color);background:rgba(74,158,255,0.2);padding:2px 6px;border-radius:4px}
83
+ .memory-actions{position:absolute;top:5px;right:5px;display:flex;gap:4px;opacity:0;transition:opacity 0.3s}
84
+ .memory-action-btn{font-size:12px;cursor:pointer;background:rgba(0,0,0,0.3);width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;line-height:1;transition:all 0.2s}
85
+ .memory-action-btn.delete{color:#ff6b6b}
86
+ .memory-action-btn.delete:hover{background:rgba(255,107,107,0.4)}
87
+ .memory-action-btn.like{color:#28a745}
88
+ .memory-action-btn.like:hover{background:rgba(40,167,69,0.4)}
89
+ .memory-action-btn.dislike{color:#ffc107}
90
+ .memory-action-btn.dislike:hover{background:rgba(255,193,7,0.4)}
91
+ .memory-action-btn.link{color:#4a9eff}
92
+ .memory-action-btn.link:hover{background:rgba(74,158,255,0.4)}
93
+ .memory-relations{margin-top:8px;padding-top:8px;border-top:1px dashed rgba(255,255,255,0.1);font-size:10px;color:#888;display:none}
94
+ .memory-item.expanded .memory-relations{display:block}
95
+ .history-section{background:linear-gradient(135deg,rgba(46,204,113,0.1),rgba(39,174,96,0.1));border:1px solid rgba(46,204,113,0.3)}
96
+ .history-list{max-height:120px;overflow-y:auto}
97
+ .history-item{padding:8px 10px;margin-bottom:6px;background:rgba(0,0,0,0.3);border-radius:6px;cursor:pointer;transition:all 0.2s;font-size:11px;display:flex;justify-content:space-between;align-items:center}
98
+ .history-item:hover{background:rgba(46,204,113,0.2);transform:translateX(3px)}
99
+ .history-item .date{color:#888;font-size:10px}
100
+ .history-item .summary{flex:1;margin-left:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
101
+ .tools-section{background:linear-gradient(135deg,rgba(155,89,182,0.1),rgba(142,68,173,0.1));border:1px solid rgba(155,89,182,0.3)}
102
+ .tool-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
103
+ .tool-btn{padding:10px;background:rgba(0,0,0,0.3);border:1px solid #333;border-radius:6px;cursor:pointer;transition:all 0.2s;text-align:center;font-size:11px;color:#aaa}
104
+ .tool-btn:hover{background:rgba(155,89,182,0.2);border-color:#9b59b6;color:white}
105
+ .tool-btn i{display:block;font-size:16px;margin-bottom:4px}
106
  .chat-container{border-radius:10px;background:rgba(30,30,30,0.8);backdrop-filter:blur(10px);box-shadow:0 4px 20px rgba(0,0,0,0.3);padding:15px;flex-grow:1;display:flex;flex-direction:column;border:1px solid var(--border-color);overflow:hidden;min-height:0;height:100%}
107
  .chat-messages{flex-grow:1;overflow-y:auto;padding:10px;scrollbar-width:thin;scrollbar-color:var(--primary-color) transparent;min-height:0;max-height:calc(100vh - 280px)}
108
  .message{margin-bottom:12px;padding:12px 16px;border-radius:10px;font-size:15px;line-height:1.8;position:relative;max-width:85%;animation:slideIn 0.3s ease-out;word-wrap:break-word}
 
128
  button:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 4px 12px rgba(255,107,157,0.5)}
129
  button:disabled{opacity:0.5;cursor:not-allowed}
130
  #send-button{background:linear-gradient(135deg,#2ecc71,#27ae60)}
131
+ .auto-save-indicator{font-size:10px;color:#888;text-align:center;margin-top:8px}
132
+ .auto-save-indicator.saving{color:#ffc107}
133
+ .auto-save-indicator.saved{color:#28a745}
134
  .toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);padding:14px 24px;border-radius:8px;font-size:14px;z-index:1000;display:none;box-shadow:0 4px 12px rgba(0,0,0,0.3);animation:slideDown 0.3s ease-out}
135
  @keyframes slideDown{from{transform:translateX(-50%) translateY(-20px);opacity:0}to{transform:translateX(-50%) translateY(0);opacity:1}}
136
  .toast.success{background:linear-gradient(135deg,#4caf50,#45a049);color:white}
137
  .toast.error{background:linear-gradient(135deg,#f44336,#e53935);color:white}
138
  .toast.warning{background:linear-gradient(135deg,var(--warning-color),#ff9800);color:#333}
139
+ .modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);display:none;justify-content:center;align-items:center;z-index:1000}
140
+ .modal-overlay.active{display:flex}
141
+ .modal{background:var(--card-bg);border-radius:12px;padding:20px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto;border:1px solid var(--border-color)}
142
+ .modal h3{margin-bottom:15px;color:#ff6b9d}
143
+ .modal-close{float:right;cursor:pointer;font-size:20px;color:#888}
144
+ .modal-close:hover{color:white}
145
+ .modal input,.modal textarea{width:100%;padding:10px;margin-bottom:10px;background:rgba(0,0,0,0.3);border:1px solid #333;border-radius:6px;color:white;font-size:13px}
146
+ .modal textarea{min-height:100px;resize:vertical}
147
+ .modal-actions{display:flex;gap:10px;margin-top:15px}
148
+ .modal-actions button{flex:1}
149
+ .relation-graph{background:rgba(0,0,0,0.3);border-radius:8px;padding:10px;margin-top:10px}
150
+ .relation-item{display:flex;align-items:center;gap:8px;padding:6px;margin-bottom:4px;background:rgba(74,158,255,0.1);border-radius:4px;font-size:11px}
151
+ .relation-strength{width:50px;height:6px;background:#333;border-radius:3px;overflow:hidden}
152
+ .relation-strength-fill{height:100%;background:linear-gradient(90deg,#4a9eff,#ff6b9d);border-radius:3px}
153
  .api-section{background:rgba(30,30,30,0.95);border-top:1px solid var(--border-color);padding:15px;margin-top:15px}
154
  .api-section h3{text-align:center;margin-bottom:10px;color:#888;font-size:12px;text-transform:uppercase}
155
  .api-docs-container{background:rgba(0,0,0,0.3);border-radius:10px;padding:20px;border:1px solid var(--border-color);max-height:500px;overflow-y:auto;display:none}
 
161
  .api-method.delete{background:#dc3545;color:white}
162
  .api-url{font-family:monospace;color:#4a9eff;font-size:13px}
163
  .api-desc{color:#aaa;font-size:12px;margin-top:8px;line-height:1.5}
 
 
164
  .collapse-btn{background:rgba(0,0,0,0.3);border:1px solid var(--border-color);color:#888;padding:8px 20px;font-size:11px;margin:0 auto;display:block;margin-bottom:10px}
165
  .collapse-btn:hover{background:rgba(111,66,193,0.2);color:white}
166
+ @media (max-width:768px){.main-content{flex-direction:column}.sidebar{width:100%;max-height:none}.health-stats{grid-template-columns:1fr 1fr}.tool-grid{grid-template-columns:1fr}}
167
  </style>
168
  </head>
169
  <body>
170
  <div id="error-toast" class="toast"></div>
171
+
172
+ <!-- κΈ°μ–΅ 관계 λͺ¨λ‹¬ -->
173
+ <div class="modal-overlay" id="relation-modal">
174
+ <div class="modal">
175
+ <span class="modal-close" onclick="closeModal('relation-modal')">&times;</span>
176
+ <h3>πŸ”— κΈ°μ–΅ μ—°κ΄€ 관계</h3>
177
+ <div id="relation-content"></div>
178
+ </div>
179
+ </div>
180
+
181
+ <!-- URL 크둀링 λͺ¨λ‹¬ -->
182
+ <div class="modal-overlay" id="crawl-modal">
183
+ <div class="modal">
184
+ <span class="modal-close" onclick="closeModal('crawl-modal')">&times;</span>
185
+ <h3>🌐 URL 크둀링</h3>
186
+ <input type="url" id="crawl-url-input" placeholder="https://example.com">
187
+ <div class="modal-actions">
188
+ <button onclick="crawlUrl()">크둀링 μ‹œμž‘</button>
189
+ </div>
190
+ <div id="crawl-result" style="margin-top:15px;font-size:12px;max-height:300px;overflow-y:auto;"></div>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- μ›Ή 검색 λͺ¨λ‹¬ -->
195
+ <div class="modal-overlay" id="search-modal">
196
+ <div class="modal">
197
+ <span class="modal-close" onclick="closeModal('search-modal')">&times;</span>
198
+ <h3>πŸ” μ›Ή 검색 ν…ŒμŠ€νŠΈ</h3>
199
+ <input type="text" id="search-query-input" placeholder="검색어 μž…λ ₯...">
200
+ <div class="modal-actions">
201
+ <button onclick="testWebSearch()">검색</button>
202
+ </div>
203
+ <div id="search-result" style="margin-top:15px;font-size:12px;max-height:300px;overflow-y:auto;"></div>
204
+ </div>
205
+ </div>
206
+
207
  <div class="container">
208
  <div class="header">
209
  <div class="logo"><h1>μ‹œλ‹ˆμ–΄ 말벗 AI</h1></div>
210
  <div class="current-time" id="current-time"></div>
211
+ <div class="storage-info" id="storage-info">μŠ€ν† λ¦¬μ§€ 확인 쀑...</div>
212
  </div>
213
  <div class="main-content">
214
  <div class="sidebar">
 
216
  <label>이메일 (ν•„μˆ˜)</label>
217
  <input type="email" id="user-email" placeholder="your@email.com">
218
  </div>
219
+
220
+ <!-- 건강 μƒνƒœ μ„Ήμ…˜ -->
221
  <div class="sidebar-section health-section">
222
+ <h3>건강 μƒνƒœ 진단 <span class="section-toggle" data-target="health-content">β–Ό</span></h3>
223
+ <div class="section-content" id="health-content">
224
  <div class="health-gauge">
225
  <div class="gauge-container">
226
  <div class="gauge-bg"></div>
 
252
  <canvas id="health-chart"></canvas>
253
  </div>
254
  </div>
255
+ </div>
256
+
257
+ <!-- μ„€μ • μ„Ήμ…˜ -->
258
  <div class="sidebar-section">
259
+ <h3>μ„€μ • <span class="section-toggle" data-target="settings-content">β–Ό</span></h3>
260
+ <div class="section-content" id="settings-content">
261
  <div class="settings-grid">
262
  <div class="setting-item">
263
  <span class="setting-label">μ›Ή 검색</span>
 
271
  <span class="setting-label">슀트리밍</span>
272
  <div id="streaming-toggle" class="toggle-switch active"><div class="toggle-slider"></div></div>
273
  </div>
274
+ <div class="setting-item">
275
+ <span class="setting-label">μžλ™ μ €μž₯ (3λΆ„)</span>
276
+ <div id="autosave-toggle" class="toggle-switch active"><div class="toggle-slider"></div></div>
277
+ </div>
278
  </div>
279
  <div style="margin-top:12px;">
280
  <label style="font-size:12px;color:#888;">이름 (호칭)</label>
281
  <input type="text" id="user-name" placeholder="μ–΄λ₯΄μ‹  성함..." style="width:100%;margin-top:4px;padding:8px;background:rgba(0,0,0,0.3);border:1px solid #333;border-radius:6px;color:white;font-size:12px;">
282
  </div>
283
  </div>
284
+ </div>
285
+
286
+ <!-- λŒ€ν™” νžˆμŠ€ν† λ¦¬ μ„Ήμ…˜ (μƒˆλ‘œ μΆ”κ°€) -->
287
+ <div class="sidebar-section history-section">
288
+ <h3>πŸ“œ 이전 λŒ€ν™” <span class="section-toggle" data-target="history-content">β–Ό</span></h3>
289
+ <div class="section-content" id="history-content">
290
+ <div class="history-list" id="history-list">
291
+ <div style="text-align:center;color:#888;font-size:11px;padding:20px;">이전 λŒ€ν™”κ°€ μ—†μŠ΅λ‹ˆλ‹€</div>
292
+ </div>
293
+ </div>
294
+ </div>
295
+
296
+ <!-- λŒ€ν™” κΈ°μ–΅ μ„Ήμ…˜ -->
297
  <div class="sidebar-section">
298
+ <h3>λŒ€ν™” κΈ°μ–΅ <span class="section-toggle" data-target="memory-content">β–Ό</span></h3>
299
+ <div class="section-content" id="memory-content">
300
  <div class="memory-stats">
301
  <div class="stat-item"><div class="stat-value" id="memory-count">0</div><div class="stat-label">총 κΈ°μ–΅</div></div>
302
  <div class="stat-item"><div class="stat-value" id="vector-count">0</div><div class="stat-label">μ€‘μš” κΈ°μ–΅</div></div>
303
  <div class="stat-item"><div class="stat-value" id="relation-count">0</div><div class="stat-label">μ—°κ΄€ 관계</div></div>
304
  </div>
305
+ <!-- κΈ°μ–΅ 검색 (μƒˆλ‘œ μΆ”κ°€) -->
306
+ <div class="memory-search-box">
307
+ <input type="text" id="memory-search-input" placeholder="κΈ°μ–΅ 검색...">
308
+ <button onclick="searchMemories()">검색</button>
309
+ </div>
310
  <div class="memory-list" id="memory-list"></div>
311
  </div>
312
+ </div>
313
+
314
+ <!-- 도ꡬ μ„Ήμ…˜ (μƒˆλ‘œ μΆ”κ°€) -->
315
+ <div class="sidebar-section tools-section">
316
+ <h3>πŸ›  도ꡬ <span class="section-toggle" data-target="tools-content">β–Ό</span></h3>
317
+ <div class="section-content" id="tools-content">
318
+ <div class="tool-grid">
319
+ <div class="tool-btn" onclick="openModal('crawl-modal')">
320
+ <i>🌐</i>URL 크둀링
321
+ </div>
322
+ <div class="tool-btn" onclick="openModal('search-modal')">
323
+ <i>πŸ”</i>μ›Ή 검색
324
+ </div>
325
+ <div class="tool-btn" onclick="loadStorageInfo()">
326
+ <i>πŸ’Ύ</i>μŠ€ν† λ¦¬μ§€ 정보
327
+ </div>
328
+ <div class="tool-btn" onclick="exportMemories()">
329
+ <i>πŸ“€</i>κΈ°μ–΅ 내보내기
330
+ </div>
331
+ </div>
332
+ </div>
333
+ </div>
334
+
335
  <div style="text-align:center;margin-top:10px;">
336
  <button id="end-session-button">λŒ€ν™” μ €μž₯ν•˜κΈ°</button>
337
+ <div class="auto-save-indicator" id="auto-save-indicator">μžλ™ μ €μž₯ λŒ€κΈ° 쀑</div>
338
  </div>
339
  </div>
340
+
341
  <div class="chat-section">
342
  <div class="chat-container">
343
  <div class="streaming-indicator" id="streaming-indicator"><div class="streaming-dot"></div><span style="font-size:12px;color:var(--streaming-color);">응닡 생성 쀑...</span></div>
 
359
  </div>
360
  </div>
361
  </div>
362
+
363
  <div class="api-section" id="api-section">
364
  <button class="collapse-btn" id="api-collapse-btn">API λ¬Έμ„œ μ—΄κΈ°/λ‹«κΈ°</button>
365
  <h3>API μ—”λ“œν¬μΈνŠΈ κ°€μ΄λ“œ</h3>
 
374
  <h4>μƒˆ μ„Έμ…˜ 생성</h4>
375
  <span class="api-method post">POST</span>
376
  <span class="api-url">/session/new</span>
377
+ </div>
378
+ <div class="api-endpoint">
379
+ <h4>μ„Έμ…˜ μ’…λ£Œ 및 μ €μž₯</h4>
380
+ <span class="api-method post">POST</span>
381
+ <span class="api-url">/session/end</span>
382
+ </div>
383
+ <div class="api-endpoint">
384
+ <h4>μžλ™ μ €μž₯</h4>
385
+ <span class="api-method post">POST</span>
386
+ <span class="api-url">/session/auto-save</span>
387
  </div>
388
  <div class="api-endpoint">
389
  <h4>건강 μƒνƒœ 쑰회</h4>
 
391
  <span class="api-url">/health/current?user_email={email}</span>
392
  </div>
393
  <div class="api-endpoint">
394
+ <h4>건강 νžˆμŠ€ν† λ¦¬</h4>
395
+ <span class="api-method get">GET</span>
396
+ <span class="api-url">/health/history?user_email={email}&days=30</span>
397
+ </div>
398
+ <div class="api-endpoint">
399
  <h4>κΈ°μ–΅ 검색</h4>
400
  <span class="api-method post">POST</span>
401
  <span class="api-url">/memory/search</span>
402
+ <div class="api-desc">μ‹œλ§¨ν‹± κ²€μƒ‰μœΌλ‘œ κ΄€λ ¨ 기얡을 μ°ΎμŠ΅λ‹ˆλ‹€.</div>
403
+ </div>
404
+ <div class="api-endpoint">
405
+ <h4>κΈ°μ–΅ ν”Όλ“œλ°±</h4>
406
+ <span class="api-method post">POST</span>
407
+ <span class="api-url">/memory/feedback</span>
408
+ <div class="api-desc">기얡에 λŒ€ν•œ ν”Όλ“œλ°±μœΌλ‘œ κ°•ν™”ν•™μŠ΅μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.</div>
409
+ </div>
410
+ <div class="api-endpoint">
411
+ <h4>κΈ°μ–΅ 관계 쑰회</h4>
412
+ <span class="api-method get">GET</span>
413
+ <span class="api-url">/memory/relationships/{memory_id}?user_email={email}</span>
414
+ </div>
415
+ <div class="api-endpoint">
416
+ <h4>κΈ°μ–΅ κ·Έλž˜ν”„ 톡계</h4>
417
+ <span class="api-method get">GET</span>
418
+ <span class="api-url">/memory/graph/stats?user_email={email}</span>
419
  </div>
420
  <div class="api-endpoint">
421
  <h4>λͺ¨λ“  κΈ°μ–΅ 쑰회</h4>
422
  <span class="api-method get">GET</span>
423
  <span class="api-url">/memory/all?user_email={email}</span>
424
  </div>
425
+ <div class="api-endpoint">
426
+ <h4>κΈ°μ–΅ μ‚­μ œ</h4>
427
+ <span class="api-method delete">DELETE</span>
428
+ <span class="api-url">/memory/{memory_id}?user_email={email}</span>
429
  </div>
430
+ <div class="api-endpoint">
431
+ <h4>졜근 λŒ€ν™” λͺ©λ‘</h4>
432
+ <span class="api-method get">GET</span>
433
+ <span class="api-url">/history/recent?user_email={email}</span>
434
  </div>
435
+ <div class="api-endpoint">
436
+ <h4>λŒ€ν™” λ‚΄μš© 쑰회</h4>
437
+ <span class="api-method get">GET</span>
438
+ <span class="api-url">/history/{session_id}?user_email={email}</span>
439
+ </div>
440
+ <div class="api-endpoint">
441
+ <h4>μ›Ή 검색</h4>
442
+ <span class="api-method post">POST</span>
443
+ <span class="api-url">/search/web</span>
444
+ </div>
445
+ <div class="api-endpoint">
446
+ <h4>URL 크둀링</h4>
447
+ <span class="api-method post">POST</span>
448
+ <span class="api-url">/crawl/url</span>
449
+ </div>
450
+ <div class="api-endpoint">
451
+ <h4>μŠ€ν† λ¦¬μ§€ 정보</h4>
452
+ <span class="api-method get">GET</span>
453
+ <span class="api-url">/storage/info</span>
454
+ </div>
455
+ </div>
456
+ </div>
457
+
458
  <script>
459
+ let webSearchEnabled=true,selfLearningEnabled=true,streamingEnabled=true,autoSaveEnabled=true;
460
+ let currentSessionId=null,autoSaveInterval=null;
461
  let userName=localStorage.getItem('userName')||'';
462
  let userEmail=localStorage.getItem('userEmail')||'';
463
  let userMemories={},isStreaming=false,healthChart=null;
464
+
465
  marked.setOptions({breaks:true,gfm:true,headerIds:false,mangle:false});
466
+
467
  function scoreToLabel(score){
468
  if(score>=80)return{text:'μ–‘ν˜Έ',class:'good'};
469
  if(score>=60)return{text:'보톡',class:'normal'};
470
  if(score>=40)return{text:'μœ„ν—˜',class:'warning'};
471
  return{text:'κΈ΄κΈ‰',class:'critical'};
472
  }
473
+
474
  const elements={
475
  endSessionButton:document.getElementById('end-session-button'),
476
  sendButton:document.getElementById('send-button'),
 
478
  searchToggle:document.getElementById('search-toggle'),
479
  learningToggle:document.getElementById('learning-toggle'),
480
  streamingToggle:document.getElementById('streaming-toggle'),
481
+ autosaveToggle:document.getElementById('autosave-toggle'),
482
  textInput:document.getElementById('text-input'),
483
  memoryList:document.getElementById('memory-list'),
484
+ historyList:document.getElementById('history-list'),
485
  userNameInput:document.getElementById('user-name'),
486
  userEmailInput:document.getElementById('user-email'),
487
  currentTimeDiv:document.getElementById('current-time'),
488
+ storageInfo:document.getElementById('storage-info'),
489
  streamingIndicator:document.getElementById('streaming-indicator'),
490
+ autoSaveIndicator:document.getElementById('auto-save-indicator'),
491
  memoryCount:document.getElementById('memory-count'),
492
  vectorCount:document.getElementById('vector-count'),
493
  relationCount:document.getElementById('relation-count'),
 
499
  memoryValue:document.getElementById('memory-value'),
500
  emotionalValue:document.getElementById('emotional-value'),
501
  apiCollapseBtn:document.getElementById('api-collapse-btn'),
502
+ apiDocsContainer:document.getElementById('api-docs-container'),
503
+ memorySearchInput:document.getElementById('memory-search-input')
504
  };
505
+
506
+ // μ„Ήμ…˜ μ ‘κΈ°/펼치기
507
+ document.querySelectorAll('.section-toggle').forEach(toggle=>{
508
+ toggle.addEventListener('click',()=>{
509
+ const targetId=toggle.dataset.target;
510
+ const content=document.getElementById(targetId);
511
+ content.classList.toggle('collapsed');
512
+ toggle.classList.toggle('collapsed');
513
+ });
514
+ });
515
+
516
+ // λͺ¨λ‹¬ 관리
517
+ function openModal(modalId){
518
+ document.getElementById(modalId).classList.add('active');
519
+ }
520
+ function closeModal(modalId){
521
+ document.getElementById(modalId).classList.remove('active');
522
+ }
523
+
524
+ // API λ¬Έμ„œ ν† κΈ€
525
  elements.apiCollapseBtn.addEventListener('click',()=>{
526
  const container=elements.apiDocsContainer;
527
  if(container.style.display==='none'||container.style.display===''){
 
532
  elements.apiCollapseBtn.textContent='API λ¬Έμ„œ μ—΄κΈ°/λ‹«κΈ°';
533
  }
534
  });
535
+
536
+ // 건강 차트 μ΄ˆκΈ°ν™”
537
  function initHealthChart(){
538
  const ctx=document.getElementById('health-chart').getContext('2d');
539
  healthChart=new Chart(ctx,{
 
547
  options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:true,position:'bottom',labels:{boxWidth:8,padding:8,font:{size:9},color:'#888'}}},scales:{x:{display:true,grid:{color:'rgba(255,255,255,0.05)'},ticks:{font:{size:8},color:'#666',maxRotation:0}},y:{display:true,min:0,max:100,grid:{color:'rgba(255,255,255,0.05)'},ticks:{font:{size:8},color:'#666'}}}}
548
  });
549
  }
550
+
551
  function updateGauge(riskLevel,riskLabel){
552
  const angle=-90+(riskLevel/100)*180;
553
  elements.gaugeNeedle.style.transform='rotate('+angle+'deg)';
 
556
  elements.gaugeLabel.textContent=riskLabel||label.text;
557
  elements.gaugeLabel.className='gauge-label '+label.class;
558
  }
559
+
560
  function updateHealthStat(element,score){
561
  const label=scoreToLabel(score);
562
  element.textContent=label.text;
563
  element.className='health-stat-value '+label.class;
564
  }
565
+
566
+ // μŠ€ν† λ¦¬μ§€ 정보 λ‘œλ“œ (μƒˆλ‘œ μΆ”κ°€)
567
+ async function loadStorageInfo(){
568
+ try{
569
+ const response=await fetch('/storage/info');
570
+ const data=await response.json();
571
+ if(data.is_persistent){
572
+ elements.storageInfo.textContent='πŸ’Ύ 영ꡬ μŠ€ν† λ¦¬μ§€ ν™œμ„±ν™” ('+formatBytes(data.db_size)+')';
573
+ elements.storageInfo.className='storage-info persistent';
574
+ }else{
575
+ elements.storageInfo.textContent='⚠️ 둜컬 μŠ€ν† λ¦¬μ§€ (μž„μ‹œ)';
576
+ elements.storageInfo.className='storage-info local';
577
+ }
578
+ showToast('μŠ€ν† λ¦¬μ§€: '+(data.is_persistent?'영ꡬ':'μž„μ‹œ')+' / DB: '+formatBytes(data.db_size),'success');
579
+ }catch(error){
580
+ elements.storageInfo.textContent='μŠ€ν† λ¦¬μ§€ 정보 μ—†μŒ';
581
+ console.error('Storage info error:',error);
582
+ }
583
+ }
584
+
585
+ function formatBytes(bytes){
586
+ if(bytes===0)return'0 Bytes';
587
+ const k=1024;
588
+ const sizes=['Bytes','KB','MB','GB'];
589
+ const i=Math.floor(Math.log(bytes)/Math.log(k));
590
+ return parseFloat((bytes/Math.pow(k,i)).toFixed(2))+' '+sizes[i];
591
+ }
592
+
593
+ // 건강 μƒνƒœ λ‘œλ“œ
594
  async function loadHealthStatus(){
595
  if(!userEmail)return;
596
  try{
 
605
  }
606
  }catch(error){console.error('Failed to load health status:',error);}
607
  }
608
+
609
  async function loadHealthHistory(){
610
  if(!userEmail)return;
611
  try{
 
626
  }
627
  }catch(error){console.error('Failed to load health history:',error);}
628
  }
629
+
630
+ // 졜근 λŒ€ν™” νžˆμŠ€ν† λ¦¬ λ‘œλ“œ (μƒˆλ‘œ μΆ”κ°€)
631
+ async function loadRecentHistory(){
632
+ if(!userEmail)return;
633
+ try{
634
+ const response=await fetch('/history/recent?user_email='+encodeURIComponent(userEmail));
635
+ const history=await response.json();
636
+ if(Array.isArray(history)&&history.length>0){
637
+ elements.historyList.innerHTML='';
638
+ history.forEach(h=>{
639
+ const item=document.createElement('div');
640
+ item.className='history-item';
641
+ const date=new Date(h.created_at);
642
+ const dateStr=(date.getMonth()+1)+'/'+date.getDate()+' '+date.getHours()+':'+String(date.getMinutes()).padStart(2,'0');
643
+ item.innerHTML='<span class="date">'+dateStr+'</span><span class="summary">'+(h.summary||'λŒ€ν™” λ‚΄μš©')+'</span>';
644
+ item.onclick=()=>loadConversation(h.id);
645
+ elements.historyList.appendChild(item);
646
+ });
647
+ }else{
648
+ elements.historyList.innerHTML='<div style="text-align:center;color:#888;font-size:11px;padding:20px;">이전 λŒ€ν™”κ°€ μ—†μŠ΅λ‹ˆλ‹€</div>';
649
+ }
650
+ }catch(error){console.error('Failed to load history:',error);}
651
+ }
652
+
653
+ // 이전 λŒ€ν™” 뢈러였기 (μƒˆλ‘œ μΆ”κ°€)
654
+ async function loadConversation(sessionId){
655
+ if(!userEmail)return;
656
+ try{
657
+ const response=await fetch('/history/'+sessionId+'?user_email='+encodeURIComponent(userEmail));
658
+ const messages=await response.json();
659
+ if(Array.isArray(messages)&&messages.length>0){
660
+ elements.chatMessages.innerHTML='';
661
+ messages.forEach(msg=>{
662
+ addMessage(msg.role,msg.content);
663
+ });
664
+ currentSessionId=sessionId;
665
+ showToast('이전 λŒ€ν™”λ₯Ό λΆˆλŸ¬μ™”μŠ΅λ‹ˆλ‹€.','success');
666
+ }
667
+ }catch(error){
668
+ console.error('Failed to load conversation:',error);
669
+ showToast('λŒ€ν™” 뢈러였기 μ‹€νŒ¨','error');
670
+ }
671
+ }
672
+
673
+ // κ·Έλž˜ν”„ 톡계 λ‘œλ“œ
674
  async function loadGraphStats(){
675
  if(!userEmail)return;
676
  try{
 
679
  if(stats.total_edges!==undefined){elements.relationCount.textContent=stats.total_edges;}
680
  }catch(error){console.error('Failed to load graph stats:',error);}
681
  }
682
+
683
+ // κΈ°μ–΅ 검색 (μƒˆλ‘œ μΆ”κ°€)
684
+ async function searchMemories(){
685
+ const query=elements.memorySearchInput.value.trim();
686
+ if(!query||!userEmail){
687
+ showToast('검색어λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.','warning');
688
+ return;
689
+ }
690
+ try{
691
+ const response=await fetch('/memory/search',{
692
+ method:'POST',
693
+ headers:{'Content-Type':'application/json'},
694
+ body:JSON.stringify({query:query,user_email:userEmail,k:20,threshold:0.5})
695
+ });
696
+ const data=await response.json();
697
+ if(data.results&&data.results.length>0){
698
+ displayMemories(data.results);
699
+ showToast(data.results.length+'개의 κ΄€λ ¨ 기얡을 μ°Ύμ•˜μŠ΅λ‹ˆλ‹€.','success');
700
+ }else{
701
+ showToast('κ΄€λ ¨ 기얡이 μ—†μŠ΅λ‹ˆλ‹€.','warning');
702
+ }
703
+ }catch(error){
704
+ console.error('Memory search error:',error);
705
+ showToast('검색 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.','error');
706
+ }
707
+ }
708
+
709
+ // κΈ°μ–΅ 관계 쑰회 (μƒˆλ‘œ μΆ”κ°€)
710
+ async function showMemoryRelations(memoryId){
711
+ if(!userEmail)return;
712
+ try{
713
+ const response=await fetch('/memory/relationships/'+memoryId+'?user_email='+encodeURIComponent(userEmail));
714
+ const data=await response.json();
715
+ const container=document.getElementById('relation-content');
716
+ if(data.direct_relationships&&data.direct_relationships.length>0){
717
+ let html='<div class="relation-graph">';
718
+ data.direct_relationships.forEach(rel=>{
719
+ const strengthPercent=(rel.strength*100).toFixed(0);
720
+ html+='<div class="relation-item">';
721
+ html+='<div class="relation-strength"><div class="relation-strength-fill" style="width:'+strengthPercent+'%"></div></div>';
722
+ html+='<span style="flex:1">'+rel.content_preview+'</span>';
723
+ html+='</div>';
724
+ });
725
+ html+='</div>';
726
+ html+='<p style="margin-top:10px;font-size:11px;color:#888;">총 '+data.total_connections+'개의 μ—°κ²°</p>';
727
+ container.innerHTML=html;
728
+ }else{
729
+ container.innerHTML='<p style="color:#888;">μ—°κ΄€λœ 기얡이 μ—†μŠ΅λ‹ˆλ‹€.</p>';
730
+ }
731
+ openModal('relation-modal');
732
+ }catch(error){
733
+ console.error('Relations error:',error);
734
+ showToast('관계 쑰회 μ‹€νŒ¨','error');
735
+ }
736
+ }
737
+
738
+ // κΈ°μ–΅ ν”Όλ“œλ°± (μƒˆλ‘œ μΆ”κ°€)
739
+ async function sendMemoryFeedback(memoryId,feedback){
740
+ if(!userEmail)return;
741
+ try{
742
+ await fetch('/memory/feedback',{
743
+ method:'POST',
744
+ headers:{'Content-Type':'application/json'},
745
+ body:JSON.stringify({
746
+ memory_ids:[memoryId],
747
+ feedback:feedback,
748
+ context:{},
749
+ user_email:userEmail
750
+ })
751
+ });
752
+ showToast('ν”Όλ“œλ°±μ΄ λ°˜μ˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€.','success');
753
+ }catch(error){
754
+ console.error('Feedback error:',error);
755
+ }
756
+ }
757
+
758
+ // URL 크둀링 (μƒˆλ‘œ μΆ”κ°€)
759
+ async function crawlUrl(){
760
+ const url=document.getElementById('crawl-url-input').value.trim();
761
+ if(!url){
762
+ showToast('URL을 μž…λ ₯ν•΄μ£Όμ„Έμš”.','warning');
763
+ return;
764
+ }
765
+ const resultDiv=document.getElementById('crawl-result');
766
+ resultDiv.innerHTML='<p style="color:#888;">크둀링 쀑...</p>';
767
+ try{
768
+ const response=await fetch('/crawl/url',{
769
+ method:'POST',
770
+ headers:{'Content-Type':'application/json'},
771
+ body:JSON.stringify({url:url})
772
+ });
773
+ const data=await response.json();
774
+ if(data.success){
775
+ resultDiv.innerHTML='<h4 style="color:#28a745;">βœ“ 성곡</h4>'+
776
+ '<p><strong>제λͺ©:</strong> '+data.title+'</p>'+
777
+ '<p><strong>길이:</strong> '+data.content_length+'자</p>'+
778
+ '<div style="max-height:200px;overflow-y:auto;background:rgba(0,0,0,0.3);padding:10px;border-radius:6px;margin-top:10px;">'+
779
+ data.content.substring(0,2000)+'...</div>';
780
+ }else{
781
+ resultDiv.innerHTML='<p style="color:#dc3545;">였λ₯˜: '+data.error+'</p>';
782
+ }
783
+ }catch(error){
784
+ resultDiv.innerHTML='<p style="color:#dc3545;">크둀링 μ‹€νŒ¨: '+error.message+'</p>';
785
+ }
786
+ }
787
+
788
+ // μ›Ή 검색 ν…ŒμŠ€νŠΈ (μƒˆλ‘œ μΆ”κ°€)
789
+ async function testWebSearch(){
790
+ const query=document.getElementById('search-query-input').value.trim();
791
+ if(!query){
792
+ showToast('검색어λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.','warning');
793
+ return;
794
+ }
795
+ const resultDiv=document.getElementById('search-result');
796
+ resultDiv.innerHTML='<p style="color:#888;">검색 쀑...</p>';
797
+ try{
798
+ const response=await fetch('/search/web',{
799
+ method:'POST',
800
+ headers:{'Content-Type':'application/json'},
801
+ body:JSON.stringify({query:query,count:5})
802
+ });
803
+ const data=await response.json();
804
+ if(data.results&&data.results.length>0){
805
+ let html='<p style="color:#28a745;">'+data.count+'개 결과</p>';
806
+ data.results.forEach((r,i)=>{
807
+ html+='<div style="margin:10px 0;padding:10px;background:rgba(0,0,0,0.3);border-radius:6px;">';
808
+ html+='<a href="'+r.url+'" target="_blank" style="color:#4a9eff;text-decoration:none;">'+(i+1)+'. '+r.title+'</a>';
809
+ html+='<p style="color:#aaa;font-size:11px;margin-top:5px;">'+r.description+'</p>';
810
+ html+='</div>';
811
+ });
812
+ resultDiv.innerHTML=html;
813
+ }else{
814
+ resultDiv.innerHTML='<p style="color:#ffc107;">검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€.</p>';
815
+ }
816
+ }catch(error){
817
+ resultDiv.innerHTML='<p style="color:#dc3545;">검색 μ‹€νŒ¨: '+error.message+'</p>';
818
+ }
819
+ }
820
+
821
+ // κΈ°μ–΅ 내보내기 (μƒˆλ‘œ μΆ”κ°€)
822
+ async function exportMemories(){
823
+ if(!userEmail)return;
824
+ try{
825
+ const response=await fetch('/memory/all?user_email='+encodeURIComponent(userEmail));
826
+ const memories=await response.json();
827
+ const dataStr=JSON.stringify(memories,null,2);
828
+ const blob=new Blob([dataStr],{type:'application/json'});
829
+ const url=URL.createObjectURL(blob);
830
+ const a=document.createElement('a');
831
+ a.href=url;
832
+ a.download='memories_'+userEmail.split('@')[0]+'_'+new Date().toISOString().split('T')[0]+'.json';
833
+ a.click();
834
+ URL.revokeObjectURL(url);
835
+ showToast('기얡이 λ‚΄λ³΄λ‚΄μ‘ŒμŠ΅λ‹ˆλ‹€.','success');
836
+ }catch(error){
837
+ showToast('내보내기 μ‹€νŒ¨','error');
838
+ }
839
+ }
840
+
841
+ // μžλ™ μ €μž₯ (μƒˆλ‘œ μΆ”κ°€)
842
+ function startAutoSave(){
843
+ if(autoSaveInterval)clearInterval(autoSaveInterval);
844
+ if(!autoSaveEnabled)return;
845
+ autoSaveInterval=setInterval(async()=>{
846
+ if(currentSessionId&&userEmail){
847
+ elements.autoSaveIndicator.textContent='μžλ™ μ €μž₯ 쀑...';
848
+ elements.autoSaveIndicator.className='auto-save-indicator saving';
849
+ try{
850
+ await fetch('/session/auto-save',{
851
+ method:'POST',
852
+ headers:{'Content-Type':'application/json'},
853
+ body:JSON.stringify({session_id:currentSessionId,user_email:userEmail})
854
+ });
855
+ elements.autoSaveIndicator.textContent='μžλ™ μ €μž₯ μ™„λ£Œ ('+new Date().toLocaleTimeString()+')';
856
+ elements.autoSaveIndicator.className='auto-save-indicator saved';
857
+ }catch(error){
858
+ elements.autoSaveIndicator.textContent='μžλ™ μ €μž₯ μ‹€νŒ¨';
859
+ console.error('Auto-save error:',error);
860
+ }
861
+ }
862
+ },180000); // 3λΆ„
863
+ }
864
+
865
  function updateCurrentTime(){
866
  const now=new Date();
867
  const kstTime=new Date(now.toLocaleString("en-US",{timeZone:"Asia/Seoul"}));
 
870
  }
871
  updateCurrentTime();
872
  setInterval(updateCurrentTime,1000);
873
+
874
  elements.userNameInput.value=userName;
875
  elements.userEmailInput.value=userEmail;
876
  elements.userNameInput.addEventListener('input',()=>{userName=elements.userNameInput.value;localStorage.setItem('userName',userName);});
877
  elements.userEmailInput.addEventListener('input',()=>{userEmail=elements.userEmailInput.value.trim();localStorage.setItem('userEmail',userEmail);});
878
+
879
  elements.searchToggle.addEventListener('click',()=>{webSearchEnabled=!webSearchEnabled;elements.searchToggle.classList.toggle('active',webSearchEnabled);showToast(webSearchEnabled?'μ›Ή 검색 ν™œμ„±ν™”':'μ›Ή 검색 λΉ„ν™œμ„±ν™”','success');});
880
  elements.learningToggle.addEventListener('click',()=>{selfLearningEnabled=!selfLearningEnabled;elements.learningToggle.classList.toggle('active',selfLearningEnabled);showToast(selfLearningEnabled?'μžκ°€ ν•™μŠ΅ ν™œμ„±ν™”':'μžκ°€ ν•™μŠ΅ λΉ„ν™œμ„±ν™”','success');});
881
  elements.streamingToggle.addEventListener('click',()=>{streamingEnabled=!streamingEnabled;elements.streamingToggle.classList.toggle('active',streamingEnabled);showToast(streamingEnabled?'슀트리밍 ν™œμ„±ν™”':'슀트리밍 λΉ„ν™œμ„±ν™”','success');});
882
+ elements.autosaveToggle.addEventListener('click',()=>{
883
+ autoSaveEnabled=!autoSaveEnabled;
884
+ elements.autosaveToggle.classList.toggle('active',autoSaveEnabled);
885
+ if(autoSaveEnabled){startAutoSave();showToast('μžλ™ μ €μž₯ ν™œμ„±ν™”','success');}
886
+ else{if(autoSaveInterval)clearInterval(autoSaveInterval);showToast('μžλ™ μ €μž₯ λΉ„ν™œμ„±ν™”','success');}
887
+ });
888
+
889
  async function deleteMemory(memoryId,element){
890
  if(!confirm('이 기얡을 μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?'))return;
891
  try{
 
894
  else{showToast('μ‚­μ œ μ‹€νŒ¨','error');}
895
  }catch(error){console.error('Delete memory error:',error);showToast('μ‚­μ œ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.','error');}
896
  }
897
+
898
  async function sendMessage(){
899
  const message=elements.textInput.value.trim();
900
  userEmail=elements.userEmailInput.value.trim();
 
908
  if(streamingEnabled){await sendStreamingMessage(message);}
909
  else{showToast('μŠ€νŠΈλ¦¬λ°μ„ ν™œμ„±ν™”ν•΄μ£Όμ„Έμš”.','warning');}
910
  }
911
+
912
  async function sendStreamingMessage(message){
913
  isStreaming=true;
914
  elements.sendButton.disabled=true;
 
940
  }catch(error){console.error('Streaming error:',error);assistantMessage.classList.remove('streaming');assistantMessage.textContent='였λ₯˜: '+error.message;showToast('슀트리밍 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.','error');}
941
  finally{isStreaming=false;elements.sendButton.disabled=false;elements.streamingIndicator.classList.remove('active');await loadMemories();await loadHealthStatus();await loadHealthHistory();}
942
  }
943
+
944
  function addMessage(role,content){
945
  const messageDiv=document.createElement('div');
946
  messageDiv.classList.add('message',role);
 
949
  elements.chatMessages.appendChild(messageDiv);
950
  elements.chatMessages.scrollTop=elements.chatMessages.scrollHeight;
951
  }
952
+
953
+ function displayMemories(memories){
954
+ elements.memoryList.innerHTML='';
955
+ memories.forEach(memory=>{
956
+ const item=document.createElement('div');
957
+ item.className='memory-item';
958
+ item.dataset.id=memory.id;
959
+ const importance=(memory.importance*100).toFixed(0);
960
+ item.innerHTML=`
961
+ <div class="memory-score">${importance}%</div>
962
+ <div class="memory-actions">
963
+ <span class="memory-action-btn like" onclick="event.stopPropagation();sendMemoryFeedback(${memory.id},'μ’‹μ•„μš”')" title="μ’‹μ•„μš”">πŸ‘</span>
964
+ <span class="memory-action-btn dislike" onclick="event.stopPropagation();sendMemoryFeedback(${memory.id},'λ³„λ‘œ')" title="λ³„λ‘œ">πŸ‘Ž</span>
965
+ <span class="memory-action-btn link" onclick="event.stopPropagation();showMemoryRelations(${memory.id})" title="μ—°κ΄€ 관계">πŸ”—</span>
966
+ <span class="memory-action-btn delete" onclick="event.stopPropagation();deleteMemory(${memory.id},this.closest('.memory-item'))" title="μ‚­μ œ">βœ•</span>
967
+ </div>
968
+ <div style="font-size:10px;color:#888;margin-bottom:4px;">${memory.category}</div>
969
+ <div>${memory.content}</div>
970
+ `;
971
+ item.onclick=()=>item.classList.toggle('expanded');
972
+ elements.memoryList.appendChild(item);
973
+ });
974
+ }
975
+
976
  async function loadMemories(){
977
  if(!userEmail)return;
978
  try{
979
  const response=await fetch('/memory/all?user_email='+encodeURIComponent(userEmail));
980
  const memories=await response.json();
981
  userMemories={};
 
982
  memories.forEach(memory=>{
983
  if(!userMemories[memory.category]){userMemories[memory.category]=[];}
984
  userMemories[memory.category].push(memory.content);
 
 
 
 
 
985
  });
986
+ displayMemories(memories);
987
  elements.memoryCount.textContent=memories.length;
988
  elements.vectorCount.textContent=memories.filter(m=>m.importance>0.7).length;
989
  await loadGraphStats();
990
  }catch(error){console.error('Failed to load memories:',error);}
991
  }
992
+
993
  async function startNewSession(){
994
  userEmail=elements.userEmailInput.value.trim();
995
  if(!userEmail||!userEmail.includes('@')){showToast('μ˜¬λ°”λ₯Έ 이메일을 μž…λ ₯ν•΄μ£Όμ„Έμš”.','warning');return false;}
 
1002
  await loadMemories();
1003
  await loadHealthStatus();
1004
  await loadHealthHistory();
1005
+ await loadRecentHistory();
1006
+ await loadStorageInfo();
1007
+ startAutoSave();
1008
  const displayName=userName||userEmail.split('@')[0];
1009
  addMessage('assistant','μ—¬λ³΄μ„Έμš”, '+displayName+'λ‹˜! 잘 μ§€λ‚΄μ…¨μ–΄μš”? 였늘 ν•˜λ£¨ μ–΄λ– μ…¨λŠ”μ§€ μ–˜κΈ°ν•΄ μ£Όμ„Έμš”.');
1010
  return true;
1011
  }catch(error){console.error('Session creation error:',error);showToast('μ„Έμ…˜ 생성 μ‹€νŒ¨','error');return false;}
1012
  }
1013
+
1014
  async function endSession(){
1015
  if(!currentSessionId||!userEmail){showToast('μ €μž₯ν•  μ„Έμ…˜μ΄ μ—†μŠ΅λ‹ˆλ‹€.','warning');return;}
1016
  try{
1017
  showToast('λŒ€ν™” λ‚΄μš©μ„ μ €μž₯ν•˜λŠ” 쀑...','success');
1018
  const response=await fetch('/session/end',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({session_id:currentSessionId,user_email:userEmail})});
1019
+ if(response.ok){showToast('λŒ€ν™”κ°€ μ„±κ³΅μ μœΌλ‘œ μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€!','success');await loadMemories();await loadHealthStatus();await loadHealthHistory();await loadRecentHistory();}
1020
  }catch(error){console.error('Failed to end session:',error);showToast('μ €μž₯ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.','error');}
1021
  }
1022
+
1023
  function showToast(message,type){
1024
  const toast=document.getElementById('error-toast');
1025
  toast.textContent=message;
 
1027
  toast.style.display='block';
1028
  setTimeout(()=>{toast.style.display='none';},3000);
1029
  }
1030
+
1031
  elements.textInput.addEventListener('keypress',(e)=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMessage();}});
1032
  elements.sendButton.addEventListener('click',sendMessage);
1033
  document.querySelectorAll('.quick-btn').forEach(btn=>{btn.addEventListener('click',()=>{const prompt=btn.dataset.prompt;if(prompt&&!isStreaming){elements.textInput.value=prompt;sendMessage();}});});
1034
  elements.endSessionButton.addEventListener('click',endSession);
1035
+ elements.memorySearchInput.addEventListener('keypress',(e)=>{if(e.key==='Enter'){searchMemories();}});
1036
+
1037
+ window.addEventListener('DOMContentLoaded',()=>{
1038
+ initHealthChart();
1039
+ loadStorageInfo();
1040
+ if(userEmail&&userEmail.includes('@')){startNewSession();}
1041
+ });
1042
  </script>
1043
  </body>
1044
  </html>