Update index.html
Browse files- 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-
|
| 72 |
-
.memory-score{position:absolute;top:5px;right:
|
| 73 |
-
.memory-
|
| 74 |
-
.memory-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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')">×</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')">×</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')">×</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>
|