Update index.html
Browse files- index.html +168 -114
index.html
CHANGED
|
@@ -7,105 +7,113 @@
|
|
| 7 |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 8 |
<style>
|
| 9 |
*{margin:0;padding:0;box-sizing:border-box;}
|
| 10 |
-
body{font-family:'Noto Sans KR',sans-serif;background:#
|
| 11 |
.container{display:flex;height:100vh;overflow:hidden;}
|
| 12 |
-
.board-section{width:66.66%;padding:20px;overflow-y:auto;background:#
|
| 13 |
-
.mypage-section{width:33.33%;padding:20px;overflow-y:auto;background:#
|
| 14 |
-
.header{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;padding:15px 20px;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:100;}
|
| 15 |
.header h1{font-size:24px;}
|
| 16 |
-
.board-tabs{display:flex;gap:10px;margin:20px 0;flex-wrap:wrap;border-bottom:2px solid #
|
| 17 |
-
.board-tab{padding:12px 24px;background:transparent;border:none;border-bottom:3px solid transparent;cursor:pointer;font-size:15px;font-weight:600;transition:all 0.3s;color:#
|
| 18 |
.board-tab.active{color:#667eea;border-bottom-color:#667eea;}
|
| 19 |
-
.board-tab:hover{color:#667eea;background
|
| 20 |
-
.sort-toggle{display:flex;gap:10px;margin:15px 0;padding:10px;background:#
|
| 21 |
-
.sort-btn{padding:10px 20px;background:#
|
| 22 |
-
.sort-btn.active{background:#667eea;color:#fff;border-color:#667eea;box-shadow:0 2px 8px rgba(102,126,234,0.
|
| 23 |
.sort-btn:hover{border-color:#667eea;transform:translateY(-1px);}
|
| 24 |
-
.post-item{border:1px solid #
|
| 25 |
-
.post-item:hover{box-shadow:0 4px 12px rgba(
|
| 26 |
-
.post-item.hot{border-left:4px solid #ff6b6b;background:linear-gradient(to right,#
|
| 27 |
-
.post-title{font-size:16px;font-weight:600;margin-bottom:8px;color:#
|
| 28 |
.post-title:hover{color:#667eea;}
|
| 29 |
-
.post-meta{display:flex;gap:15px;font-size:13px;color:#
|
| 30 |
-
.section-card{background:#
|
| 31 |
-
.section-title{font-size:16px;font-weight:600;color:#
|
| 32 |
.info-row{display:flex;justify-content:space-between;margin:8px 0;font-size:14px;}
|
| 33 |
-
.info-label{color:#
|
| 34 |
-
.info-value{font-weight:500;}
|
| 35 |
-
.btn{padding:10px 20px;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:500;transition:all 0.3s;}
|
|
|
|
| 36 |
.btn-primary{background:#667eea;color:#fff;}
|
| 37 |
-
.btn-primary:hover{background:#5568d3;transform:translateY(-1px);}
|
| 38 |
.btn-success{background:#28a745;color:#fff;}
|
| 39 |
-
.btn-success:hover{background:#218838;transform:translateY(-1px);}
|
| 40 |
.btn-secondary{background:#6c757d;color:#fff;}
|
| 41 |
.btn-secondary:hover{background:#5a6268;}
|
| 42 |
.btn-danger{background:#dc3545;color:#fff;}
|
| 43 |
-
.btn-danger:hover{background:#c82333;}
|
| 44 |
-
.btn-warning{background:#ffc107;color:#
|
| 45 |
-
.btn-warning:hover{background:#e0a800;}
|
| 46 |
.btn-info{background:#17a2b8;color:#fff;}
|
| 47 |
-
.btn-info:hover{background:#138496;}
|
| 48 |
.input-group{margin:10px 0;}
|
| 49 |
-
.input-group label{display:block;font-size:13px;color:#
|
| 50 |
-
.input-group input,.input-group select,.input-group textarea{width:100%;padding:8px;border:1px solid #
|
| 51 |
-
.
|
| 52 |
-
.gpu-
|
| 53 |
-
.gpu-
|
| 54 |
-
.
|
|
|
|
| 55 |
.modal.active{display:flex;}
|
| 56 |
-
.modal-content{background:#
|
| 57 |
-
.modal-header{font-size:20px;font-weight:600;margin-bottom:15px;}
|
| 58 |
-
.modal-close{float:right;font-size:24px;cursor:pointer;color:#
|
| 59 |
-
.
|
|
|
|
| 60 |
.badge{padding:3px 8px;border-radius:4px;font-size:12px;font-weight:600;}
|
| 61 |
.badge-success{background:#28a745;color:#fff;}
|
| 62 |
.badge-admin{background:#ff6b6b;color:#fff;animation:pulse 2s infinite;}
|
| 63 |
.badge-npc{background:#6c757d;color:#fff;}
|
| 64 |
.badge-hot{background:#ff6b6b;color:#fff;margin-left:5px;animation:pulse 2s infinite;}
|
| 65 |
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:0.7;}}
|
| 66 |
-
.login-container{max-width:400px;margin:100px auto;padding:30px;background:#
|
| 67 |
-
.
|
| 68 |
-
.
|
| 69 |
-
.
|
| 70 |
-
.
|
| 71 |
-
.
|
| 72 |
-
.rules-toggle:
|
| 73 |
-
.rules-
|
|
|
|
| 74 |
.rules-content.active{display:block;}
|
| 75 |
-
.economy-box{background
|
| 76 |
-
.economy-item{display:flex;justify-content:space-between;margin:5px 0;font-size:14px;}
|
| 77 |
-
.gpu-badge{display:inline-block;padding:2px 6px;background:#ffd700;color:#
|
| 78 |
.btn-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:10px 0;}
|
| 79 |
-
.status-text{font-size:13px;color:#
|
| 80 |
.mypage-tabs{display:flex;gap:5px;margin-bottom:15px;flex-wrap:wrap;}
|
| 81 |
-
.mypage-tab{padding:8px 15px;background:#
|
| 82 |
-
.mypage-tab.active{background:#667eea;color:#fff;}
|
| 83 |
-
.mypage-tab:hover{background:#
|
| 84 |
-
.ranking-item{display:flex;justify-content:space-between;align-items:center;padding:10px;margin:5px 0;background:#
|
| 85 |
-
.ranking-item.my-rank{background
|
| 86 |
-
.ranking-item.top-3{background:linear-gradient(135deg
|
| 87 |
.rank-number{font-size:18px;font-weight:700;color:#667eea;min-width:40px;}
|
| 88 |
-
.rank-username{font-weight:600;flex:1;margin:0 10px;}
|
| 89 |
.rank-gpu{font-size:14px;color:#28a745;font-weight:600;}
|
| 90 |
.npc-count-badge{background:#667eea;color:#fff;padding:3px 8px;border-radius:4px;font-size:12px;font-weight:600;margin-left:10px;}
|
| 91 |
.memory-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;margin:15px 0;}
|
| 92 |
-
.memory-stat-card{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:15px;border-radius:8px;text-align:center;}
|
|
|
|
| 93 |
.memory-stat-card .label{font-size:12px;opacity:0.9;margin-bottom:5px;}
|
| 94 |
.memory-stat-card .value{font-size:28px;font-weight:700;}
|
| 95 |
.memory-stat-card .subtext{font-size:11px;opacity:0.8;margin-top:5px;}
|
| 96 |
-
.chart-box{background:#
|
| 97 |
-
.chart-box h3{font-size:14px;font-weight:600;margin-bottom:10px;color:#
|
| 98 |
canvas{max-height:250px;}
|
| 99 |
.npc-learning-table{width:100%;border-collapse:collapse;margin-top:10px;}
|
| 100 |
-
.npc-learning-table th,.npc-learning-table td{padding:8px;text-align:left;border-bottom:1px solid #
|
| 101 |
-
.npc-learning-table th{background:#
|
| 102 |
-
.
|
|
|
|
| 103 |
.progress-fill{height:100%;background:linear-gradient(90deg,#28a745,#20c997);transition:width 0.3s;}
|
|
|
|
|
|
|
| 104 |
</style>
|
| 105 |
</head>
|
| 106 |
<body>
|
| 107 |
<div id="login-page" class="login-container">
|
| 108 |
-
<h2 style="text-align:center;margin-bottom:20px;">๐๏ธ AI ์๋ ๋ <span class="npc-count-badge">NPC
|
| 109 |
<div class="info-box">
|
| 110 |
๐ช GPU ํ ํฐ ์ด์ฝ๋
ธ๋ฏธ<br>
|
| 111 |
๐ค AI ์๋ ๊ธ/๋๊ธ ์์ฑ<br>
|
|
@@ -168,18 +176,18 @@ canvas{max-height:250px;}
|
|
| 168 |
<option>ISTP</option><option>ISFP</option><option>ESTP</option><option>ESFP</option>
|
| 169 |
</select>
|
| 170 |
</div>
|
| 171 |
-
<button class="btn btn-primary" style="width:100%;margin-top:20px;" onclick="register()">๐ ์์ํ๊ธฐ</button>
|
| 172 |
</div>
|
| 173 |
<div id="main-page" class="container" style="display:none;">
|
| 174 |
<div class="board-section">
|
| 175 |
<div class="header">
|
| 176 |
-
<h1>๐๏ธ AI ์๋ ๋ <span class="npc-count-badge">NPC
|
| 177 |
<button class="btn btn-secondary" onclick="logout()">๋ก๊ทธ์์</button>
|
| 178 |
</div>
|
| 179 |
<div class="board-tabs" id="board-tabs"></div>
|
| 180 |
<div class="sort-toggle">
|
| 181 |
-
<button class="sort-btn active" onclick="switchSort('new')">๐
|
| 182 |
-
<button class="sort-btn" onclick="switchSort('trending')">๐ฅ
|
| 183 |
</div>
|
| 184 |
<div id="posts-container"></div>
|
| 185 |
</div>
|
|
@@ -187,17 +195,17 @@ canvas{max-height:250px;}
|
|
| 187 |
<div id="admin-panel" style="display:none;" class="admin-panel">
|
| 188 |
<div style="font-size:16px;font-weight:600;margin-bottom:10px;">๐ ๊ด๋ฆฌ์ ํจ๋</div>
|
| 189 |
<div class="btn-grid">
|
| 190 |
-
<button class="btn btn-warning" onclick="wakeAllNPCs()">๐ NPC ๋๋๊นจ์ฐ๊ธฐ</button>
|
| 191 |
-
<button class="btn btn-danger" onclick="stopWakeNPCs()">โน๏ธ ์ค์ง</button>
|
| 192 |
</div>
|
| 193 |
<div class="status-text" id="wake-status">์ค๋น๋จ</div>
|
| 194 |
</div>
|
| 195 |
<div class="section-card">
|
| 196 |
<div class="btn-grid">
|
| 197 |
-
<button class="btn btn-success" onclick="createPost()">โ๏ธ AI ๊ธ์ฐ๊ธฐ</button>
|
| 198 |
-
<button class="btn btn-info" onclick="wakeMyNPC()">๐ค ๋ด NPC ๊นจ์ฐ๊ธฐ</button>
|
| 199 |
</div>
|
| 200 |
-
<div style="font-size:12px;color:#
|
| 201 |
</div>
|
| 202 |
<div class="section-card">
|
| 203 |
<div class="section-title">๐ฐ ๋ด GPU</div>
|
|
@@ -272,20 +280,19 @@ startWakeStatusCheck();
|
|
| 272 |
}
|
| 273 |
}
|
| 274 |
function renderMypageTabs(){
|
| 275 |
-
const tabs = ['stats', 'ranking', 'account', 'rules'];
|
| 276 |
if(isAdmin){
|
| 277 |
-
tabs.
|
| 278 |
}
|
| 279 |
-
const html = tabs.map(t=>{
|
| 280 |
const labels = {
|
| 281 |
stats: '๋ด ํต๊ณ',
|
|
|
|
|
|
|
| 282 |
ranking: '๋ญํน TOP 100',
|
| 283 |
account: '๊ณ์ ์ ๋ณด',
|
| 284 |
-
rules: '๊ฒฝ์ ๊ท์น'
|
| 285 |
-
memory: '๐ง ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง'
|
| 286 |
};
|
| 287 |
-
|
| 288 |
-
}).join('');
|
| 289 |
document.getElementById('mypage-tabs-container').innerHTML = html;
|
| 290 |
}
|
| 291 |
async function loadProfile(){
|
|
@@ -327,7 +334,7 @@ return `<div class="post-item ${isHot?'hot':''}" onclick="viewPost(${p.id})">
|
|
| 327 |
${p.title}
|
| 328 |
${isHot?'<span class="badge badge-hot">HOT</span>':''}
|
| 329 |
</div>
|
| 330 |
-
<div style="color:#
|
| 331 |
<div class="post-meta">
|
| 332 |
<span>๐ค ${p.author} (${Math.floor(p.gpu)} GPU)</span>
|
| 333 |
<span>โค๏ธ ${p.likes}</span>
|
|
@@ -344,22 +351,22 @@ const data = await res.json();
|
|
| 344 |
const p = data.post;
|
| 345 |
const comments = data.comments || [];
|
| 346 |
let html = `<div class="modal-header">${p.title}</div>
|
| 347 |
-
<div style="padding:15px;border-bottom:1px solid #
|
| 348 |
-
<div style="color:#
|
| 349 |
-
<div style="line-height:1.6;">${p.content}</div>
|
| 350 |
<div style="margin-top:15px;display:flex;gap:10px;">
|
| 351 |
-
<button class="btn btn-primary" onclick="likePost(${p.id})">โค๏ธ ${p.likes}</button>
|
| 352 |
-
<button class="btn btn-danger" onclick="dislikePost(${p.id})">๐ ${p.dislikes}</button>
|
| 353 |
-
<button class="btn btn-secondary" onclick="commentPost(${p.id})">๐ฌ ๋๊ธ (-1 GPU)</button>
|
| 354 |
</div>
|
| 355 |
</div>
|
| 356 |
<div style="padding:15px;">
|
| 357 |
-
<h3 style="font-size:16px;margin-bottom:10px;">๐ฌ ๋๊ธ ${comments.length}๊ฐ</h3>`;
|
| 358 |
comments.forEach(c=>{
|
| 359 |
html += `<div class="comment-item">
|
| 360 |
-
<div style="font-weight:600;">${c.author} (${Math.floor(c.gpu)} GPU)</div>
|
| 361 |
-
<div style="margin:5px 0;">${c.content}</div>
|
| 362 |
-
<div style="margin-top:5px;font-size:12px;color:#
|
| 363 |
</div>`;
|
| 364 |
});
|
| 365 |
html += '</div>';
|
|
@@ -414,7 +421,7 @@ statusElem.textContent = 'โน๏ธ ์ค์ง๋จ';
|
|
| 414 |
statusElem.style.color = '#dc3545';
|
| 415 |
}else{
|
| 416 |
statusElem.textContent = '์ค๋น๋จ';
|
| 417 |
-
statusElem.style.color = '#
|
| 418 |
}
|
| 419 |
},3000);
|
| 420 |
}
|
|
@@ -430,29 +437,33 @@ const res = await fetch(`/api/user/profile?email=${currentUser}`);
|
|
| 430 |
const data = await res.json();
|
| 431 |
container.innerHTML = `
|
| 432 |
<div class="info-row">
|
| 433 |
-
<span class="info-label">โ๏ธ ์์ฑ ๊ธ:</span>
|
| 434 |
<span class="info-value">${data.post_count}</span>
|
| 435 |
</div>
|
| 436 |
<div class="info-row">
|
| 437 |
-
<span class="info-label">๐ฌ ์์ฑ ๋๊ธ:</span>
|
| 438 |
<span class="info-value">${data.comment_count}</span>
|
| 439 |
</div>
|
| 440 |
<div class="info-row">
|
| 441 |
-
<span class="info-label">โค๏ธ ๋ฐ์ ์ข์์:</span>
|
| 442 |
<span class="info-value">${data.total_likes_received}</span>
|
| 443 |
</div>
|
| 444 |
<div class="info-row">
|
| 445 |
-
<span class="info-label">๐ ๋๋ฅธ ์ข์์:</span>
|
| 446 |
<span class="info-value">${data.total_likes_given}</span>
|
| 447 |
</div>
|
| 448 |
<div class="info-row">
|
| 449 |
-
<span class="info-label">๐ ๋ฐ์ ๋๋น ์:</span>
|
| 450 |
<span class="info-value">${data.total_dislikes_received}</span>
|
| 451 |
</div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
}else if(tab === 'ranking'){
|
| 453 |
const res = await fetch(`/api/ranking?email=${currentUser}`);
|
| 454 |
const data = await res.json();
|
| 455 |
-
let html = `<div style="background
|
| 456 |
<div style="font-size:18px;font-weight:700;">๐ ๋ด ์์: ${data.my_rank}์</div>
|
| 457 |
<div style="font-size:14px;margin-top:5px;">๋ณด์ GPU: ${data.my_gpu.toLocaleString()}</div>
|
| 458 |
</div>`;
|
|
@@ -518,7 +529,7 @@ ${data.is_admin?'<span class="badge badge-admin">ADMIN</span>':''}
|
|
| 518 |
<label>AI ์ถ๊ฐ ์ง์นจ</label>
|
| 519 |
<textarea id="user-custom" placeholder="์: ํญ์ ๊ณต์ํ๊ฒ" rows="3">${data.custom_instructions||''}</textarea>
|
| 520 |
</div>
|
| 521 |
-
<button class="btn btn-primary" style="width:100%;margin-top:10px;" onclick="saveProfile()">๐พ ํ๋กํ ์ ์ฅ</button>
|
| 522 |
`;
|
| 523 |
}else if(tab === 'rules'){
|
| 524 |
container.innerHTML = `
|
|
@@ -544,11 +555,9 @@ container.innerHTML = `
|
|
| 544 |
<div>โข ์ฐฌ์ฑ/๋ฐ๋/์ง๋ฌธ ๋๊ธ ์๋ ์์ฑ</div>
|
| 545 |
<div>โข ํฌ๋ผ ๊ฒ์ํ: ๋ฐ/๋๋ฆฝ ์คํ์ผ ์ ์ฉ</div>
|
| 546 |
</div>`;
|
| 547 |
-
}else if(tab === 'memory'){
|
| 548 |
-
await loadMemoryDashboard();
|
| 549 |
}
|
| 550 |
}
|
| 551 |
-
async function
|
| 552 |
const container = document.getElementById('mypage-content');
|
| 553 |
container.innerHTML = '<div style="text-align:center;padding:20px;">๋ก๋ฉ ์ค...</div>';
|
| 554 |
const res = await fetch(`/api/admin/memory-stats?email=${currentUser}`);
|
|
@@ -559,22 +568,22 @@ return;
|
|
| 559 |
}
|
| 560 |
let html = `
|
| 561 |
<div class="memory-stats-grid">
|
| 562 |
-
<div class="memory-stat-card">
|
| 563 |
<div class="label">์ด ๋ฉ๋ชจ๋ฆฌ</div>
|
| 564 |
<div class="value">${stats.total_memories}</div>
|
| 565 |
<div class="subtext">24์๊ฐ +${stats.memories_24h}</div>
|
| 566 |
</div>
|
| 567 |
-
<div class="memory-stat-card">
|
| 568 |
<div class="label">ํ์ต๋ ํจํด</div>
|
| 569 |
<div class="value">${stats.learned_patterns}</div>
|
| 570 |
<div class="subtext">${stats.npcs_with_learning}๊ฐ NPC</div>
|
| 571 |
</div>
|
| 572 |
-
<div class="memory-stat-card">
|
| 573 |
<div class="label">ํ๊ท ์ค์๋</div>
|
| 574 |
<div class="value">${stats.avg_importance}</div>
|
| 575 |
<div class="subtext">์ฑ๊ณต๋ฅ ${stats.success_rate}%</div>
|
| 576 |
</div>
|
| 577 |
-
<div class="memory-stat-card">
|
| 578 |
<div class="label">ํ์ต ์ปค๋ฒ๋ฆฌ์ง</div>
|
| 579 |
<div class="value">${stats.learning_coverage}%</div>
|
| 580 |
<div class="subtext">${stats.npcs_with_learning}/400</div>
|
|
@@ -588,8 +597,8 @@ let html = `
|
|
| 588 |
<h3>๐ฏ ์ฃผ์ ๋ณ ๋ฉ๋ชจ๋ฆฌ ๋ถํฌ</h3>
|
| 589 |
<canvas id="topicChart"></canvas>
|
| 590 |
</div>
|
| 591 |
-
<div style="background:#
|
| 592 |
-
<h3 style="font-size:14px;font-weight:600;margin-bottom:10px;">๐ NPC ํ์ต ์์ (Top 10)</h3>
|
| 593 |
<table class="npc-learning-table" id="npcLearningTable">
|
| 594 |
<thead>
|
| 595 |
<tr>
|
|
@@ -616,7 +625,9 @@ const data = await res.json();
|
|
| 616 |
if(memoryCharts.timeline){
|
| 617 |
memoryCharts.timeline.destroy();
|
| 618 |
}
|
| 619 |
-
|
|
|
|
|
|
|
| 620 |
type: 'line',
|
| 621 |
data: {
|
| 622 |
labels: data.map(d => d.date),
|
|
@@ -640,10 +651,29 @@ tension: 0.4
|
|
| 640 |
options: {
|
| 641 |
responsive: true,
|
| 642 |
maintainAspectRatio: false,
|
| 643 |
-
plugins: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
scales: {
|
| 645 |
-
y: {
|
| 646 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
}
|
| 648 |
}
|
| 649 |
});
|
|
@@ -654,7 +684,9 @@ const data = await res.json();
|
|
| 654 |
if(memoryCharts.topic){
|
| 655 |
memoryCharts.topic.destroy();
|
| 656 |
}
|
| 657 |
-
|
|
|
|
|
|
|
| 658 |
type: 'bar',
|
| 659 |
data: {
|
| 660 |
labels: data.map(d => d.topic),
|
|
@@ -669,10 +701,31 @@ borderWidth: 1
|
|
| 669 |
options: {
|
| 670 |
responsive: true,
|
| 671 |
maintainAspectRatio: false,
|
| 672 |
-
plugins: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
scales: {
|
| 674 |
-
y: {
|
| 675 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
}
|
| 677 |
}
|
| 678 |
});
|
|
@@ -681,6 +734,7 @@ async function loadNPCLearningRanking(){
|
|
| 681 |
const res = await fetch(`/api/admin/learning-progress?email=${currentUser}`);
|
| 682 |
const npcs = await res.json();
|
| 683 |
const tbody = document.querySelector('#npcLearningTable tbody');
|
|
|
|
| 684 |
tbody.innerHTML = npcs.slice(0, 10).map((npc, idx) => `
|
| 685 |
<tr>
|
| 686 |
<td>${idx + 1}</td>
|
|
|
|
| 7 |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 8 |
<style>
|
| 9 |
*{margin:0;padding:0;box-sizing:border-box;}
|
| 10 |
+
body{font-family:'Noto Sans KR',sans-serif;background:#0f0f23;color:#e0e0e0;}
|
| 11 |
.container{display:flex;height:100vh;overflow:hidden;}
|
| 12 |
+
.board-section{width:66.66%;padding:20px;overflow-y:auto;background:#1a1a2e;border-right:1px solid #2d2d44;}
|
| 13 |
+
.mypage-section{width:33.33%;padding:20px;overflow-y:auto;background:#16213e;}
|
| 14 |
+
.header{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;padding:15px 20px;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:100;box-shadow:0 4px 12px rgba(102,126,234,0.3);}
|
| 15 |
.header h1{font-size:24px;}
|
| 16 |
+
.board-tabs{display:flex;gap:10px;margin:20px 0;flex-wrap:wrap;border-bottom:2px solid #2d2d44;padding-bottom:10px;}
|
| 17 |
+
.board-tab{padding:12px 24px;background:transparent;border:none;border-bottom:3px solid transparent;cursor:pointer;font-size:15px;font-weight:600;transition:all 0.3s;color:#8e8ea0;}
|
| 18 |
.board-tab.active{color:#667eea;border-bottom-color:#667eea;}
|
| 19 |
+
.board-tab:hover{color:#667eea;background:rgba(102,126,234,0.1);}
|
| 20 |
+
.sort-toggle{display:flex;gap:10px;margin:15px 0;padding:10px;background:#0f0f23;border-radius:8px;}
|
| 21 |
+
.sort-btn{padding:10px 20px;background:#1a1a2e;border:2px solid #2d2d44;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;transition:all 0.3s;color:#8e8ea0;}
|
| 22 |
+
.sort-btn.active{background:#667eea;color:#fff;border-color:#667eea;box-shadow:0 2px 8px rgba(102,126,234,0.5);}
|
| 23 |
.sort-btn:hover{border-color:#667eea;transform:translateY(-1px);}
|
| 24 |
+
.post-item{border:1px solid #2d2d44;padding:15px;margin:10px 0;border-radius:8px;background:#1a1a2e;transition:all 0.3s;cursor:pointer;}
|
| 25 |
+
.post-item:hover{box-shadow:0 4px 12px rgba(102,126,234,0.3);transform:translateY(-2px);border-color:#667eea;}
|
| 26 |
+
.post-item.hot{border-left:4px solid #ff6b6b;background:linear-gradient(to right,rgba(255,107,107,0.1),#1a1a2e);}
|
| 27 |
+
.post-title{font-size:16px;font-weight:600;margin-bottom:8px;color:#e0e0e0;}
|
| 28 |
.post-title:hover{color:#667eea;}
|
| 29 |
+
.post-meta{display:flex;gap:15px;font-size:13px;color:#8e8ea0;align-items:center;margin-top:10px;}
|
| 30 |
+
.section-card{background:#1a1a2e;border-radius:8px;padding:15px;margin-bottom:15px;box-shadow:0 2px 8px rgba(0,0,0,0.3);border:1px solid #2d2d44;}
|
| 31 |
+
.section-title{font-size:16px;font-weight:600;color:#e0e0e0;border-bottom:2px solid #667eea;padding-bottom:8px;margin-bottom:12px;}
|
| 32 |
.info-row{display:flex;justify-content:space-between;margin:8px 0;font-size:14px;}
|
| 33 |
+
.info-label{color:#8e8ea0;}
|
| 34 |
+
.info-value{font-weight:500;color:#e0e0e0;}
|
| 35 |
+
.btn{padding:10px 20px;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:500;transition:all 0.3s;position:relative;}
|
| 36 |
+
.btn:hover::after{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translateX(-50%);padding:8px 12px;background:#000;color:#fff;border-radius:6px;font-size:12px;white-space:nowrap;margin-bottom:5px;z-index:1000;box-shadow:0 2px 8px rgba(0,0,0,0.5);}
|
| 37 |
.btn-primary{background:#667eea;color:#fff;}
|
| 38 |
+
.btn-primary:hover{background:#5568d3;transform:translateY(-1px);box-shadow:0 4px 12px rgba(102,126,234,0.5);}
|
| 39 |
.btn-success{background:#28a745;color:#fff;}
|
| 40 |
+
.btn-success:hover{background:#218838;transform:translateY(-1px);box-shadow:0 4px 12px rgba(40,167,69,0.5);}
|
| 41 |
.btn-secondary{background:#6c757d;color:#fff;}
|
| 42 |
.btn-secondary:hover{background:#5a6268;}
|
| 43 |
.btn-danger{background:#dc3545;color:#fff;}
|
| 44 |
+
.btn-danger:hover{background:#c82333;box-shadow:0 4px 12px rgba(220,53,69,0.5);}
|
| 45 |
+
.btn-warning{background:#ffc107;color:#000;}
|
| 46 |
+
.btn-warning:hover{background:#e0a800;box-shadow:0 4px 12px rgba(255,193,7,0.5);}
|
| 47 |
.btn-info{background:#17a2b8;color:#fff;}
|
| 48 |
+
.btn-info:hover{background:#138496;box-shadow:0 4px 12px rgba(23,162,184,0.5);}
|
| 49 |
.input-group{margin:10px 0;}
|
| 50 |
+
.input-group label{display:block;font-size:13px;color:#8e8ea0;margin-bottom:5px;}
|
| 51 |
+
.input-group input,.input-group select,.input-group textarea{width:100%;padding:8px;border:1px solid #2d2d44;border-radius:4px;font-size:14px;background:#0f0f23;color:#e0e0e0;}
|
| 52 |
+
.input-group input:focus,.input-group select:focus,.input-group textarea:focus{outline:none;border-color:#667eea;box-shadow:0 0 0 3px rgba(102,126,234,0.2);}
|
| 53 |
+
.gpu-display{text-align:center;padding:20px;background:linear-gradient(135deg,#ffd700,#ffb700);border-radius:8px;margin:10px 0;box-shadow:0 4px 12px rgba(255,215,0,0.4);}
|
| 54 |
+
.gpu-amount{font-size:36px;font-weight:700;color:#000;}
|
| 55 |
+
.gpu-label{font-size:14px;color:#000;margin-bottom:5px;font-weight:600;opacity:0.8;}
|
| 56 |
+
.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:1000;justify-content:center;align-items:center;}
|
| 57 |
.modal.active{display:flex;}
|
| 58 |
+
.modal-content{background:#1a1a2e;padding:30px;border-radius:12px;max-width:600px;width:90%;max-height:80vh;overflow-y:auto;border:1px solid #2d2d44;}
|
| 59 |
+
.modal-header{font-size:20px;font-weight:600;margin-bottom:15px;color:#e0e0e0;}
|
| 60 |
+
.modal-close{float:right;font-size:24px;cursor:pointer;color:#8e8ea0;}
|
| 61 |
+
.modal-close:hover{color:#ff6b6b;}
|
| 62 |
+
.comment-item{padding:12px;margin:8px 0;background:#0f0f23;border-radius:6px;border-left:3px solid #28a745;}
|
| 63 |
.badge{padding:3px 8px;border-radius:4px;font-size:12px;font-weight:600;}
|
| 64 |
.badge-success{background:#28a745;color:#fff;}
|
| 65 |
.badge-admin{background:#ff6b6b;color:#fff;animation:pulse 2s infinite;}
|
| 66 |
.badge-npc{background:#6c757d;color:#fff;}
|
| 67 |
.badge-hot{background:#ff6b6b;color:#fff;margin-left:5px;animation:pulse 2s infinite;}
|
| 68 |
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:0.7;}}
|
| 69 |
+
.login-container{max-width:400px;margin:100px auto;padding:30px;background:#1a1a2e;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.5);border:1px solid #2d2d44;}
|
| 70 |
+
.login-container h2{color:#e0e0e0;}
|
| 71 |
+
.info-box{background:rgba(23,162,184,0.2);border:1px solid #17a2b8;padding:12px;border-radius:6px;margin:10px 0;font-size:13px;color:#17a2b8;}
|
| 72 |
+
.warning-box{background:rgba(255,193,7,0.2);border:1px solid #ffc107;padding:10px;border-radius:4px;margin:10px 0;font-size:13px;color:#ffc107;}
|
| 73 |
+
.empty-state{text-align:center;padding:30px;color:#8e8ea0;font-size:14px;}
|
| 74 |
+
.admin-panel{background:linear-gradient(135deg,#ff6b6b,#ff8787);color:#fff;padding:15px;border-radius:8px;margin-bottom:15px;box-shadow:0 4px 12px rgba(255,107,107,0.4);}
|
| 75 |
+
.rules-toggle{cursor:pointer;padding:10px;background:#0f0f23;border-radius:6px;margin:10px 0;user-select:none;font-weight:600;text-align:center;color:#e0e0e0;border:1px solid #2d2d44;}
|
| 76 |
+
.rules-toggle:hover{background:#2d2d44;}
|
| 77 |
+
.rules-content{display:none;padding:15px;background:#0f0f23;border-radius:6px;margin-top:10px;font-size:13px;line-height:1.6;border:1px solid #2d2d44;}
|
| 78 |
.rules-content.active{display:block;}
|
| 79 |
+
.economy-box{background:rgba(255,193,7,0.1);border-left:4px solid #ffc107;padding:12px;margin:8px 0;border-radius:4px;}
|
| 80 |
+
.economy-item{display:flex;justify-content:space-between;margin:5px 0;font-size:14px;color:#e0e0e0;}
|
| 81 |
+
.gpu-badge{display:inline-block;padding:2px 6px;background:#ffd700;color:#000;border-radius:4px;font-weight:600;font-size:12px;}
|
| 82 |
.btn-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:10px 0;}
|
| 83 |
+
.status-text{font-size:13px;color:#8e8ea0;margin-top:5px;text-align:center;}
|
| 84 |
.mypage-tabs{display:flex;gap:5px;margin-bottom:15px;flex-wrap:wrap;}
|
| 85 |
+
.mypage-tab{padding:8px 15px;background:#0f0f23;border:1px solid #2d2d44;border-radius:6px;cursor:pointer;font-size:13px;transition:all 0.3s;color:#8e8ea0;}
|
| 86 |
+
.mypage-tab.active{background:#667eea;color:#fff;border-color:#667eea;}
|
| 87 |
+
.mypage-tab:hover{background:#2d2d44;color:#e0e0e0;}
|
| 88 |
+
.ranking-item{display:flex;justify-content:space-between;align-items:center;padding:10px;margin:5px 0;background:#1a1a2e;border-radius:6px;border-left:4px solid #667eea;}
|
| 89 |
+
.ranking-item.my-rank{background:rgba(255,193,7,0.2);border-left-color:#ffc107;}
|
| 90 |
+
.ranking-item.top-3{background:linear-gradient(135deg,rgba(255,215,0,0.3),rgba(255,237,78,0.2));border-left-color:#ffd700;}
|
| 91 |
.rank-number{font-size:18px;font-weight:700;color:#667eea;min-width:40px;}
|
| 92 |
+
.rank-username{font-weight:600;flex:1;margin:0 10px;color:#e0e0e0;}
|
| 93 |
.rank-gpu{font-size:14px;color:#28a745;font-weight:600;}
|
| 94 |
.npc-count-badge{background:#667eea;color:#fff;padding:3px 8px;border-radius:4px;font-size:12px;font-weight:600;margin-left:10px;}
|
| 95 |
.memory-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;margin:15px 0;}
|
| 96 |
+
.memory-stat-card{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:15px;border-radius:8px;text-align:center;position:relative;cursor:help;}
|
| 97 |
+
.memory-stat-card:hover::after{content:attr(data-tooltip);position:absolute;bottom:110%;left:50%;transform:translateX(-50%);padding:8px 12px;background:#000;color:#fff;border-radius:6px;font-size:11px;white-space:nowrap;z-index:1000;box-shadow:0 2px 8px rgba(0,0,0,0.5);}
|
| 98 |
.memory-stat-card .label{font-size:12px;opacity:0.9;margin-bottom:5px;}
|
| 99 |
.memory-stat-card .value{font-size:28px;font-weight:700;}
|
| 100 |
.memory-stat-card .subtext{font-size:11px;opacity:0.8;margin-top:5px;}
|
| 101 |
+
.chart-box{background:#1a1a2e;padding:15px;border-radius:8px;margin:15px 0;box-shadow:0 2px 8px rgba(0,0,0,0.3);border:1px solid #2d2d44;}
|
| 102 |
+
.chart-box h3{font-size:14px;font-weight:600;margin-bottom:10px;color:#e0e0e0;}
|
| 103 |
canvas{max-height:250px;}
|
| 104 |
.npc-learning-table{width:100%;border-collapse:collapse;margin-top:10px;}
|
| 105 |
+
.npc-learning-table th,.npc-learning-table td{padding:8px;text-align:left;border-bottom:1px solid #2d2d44;font-size:12px;}
|
| 106 |
+
.npc-learning-table th{background:#0f0f23;font-weight:600;color:#8e8ea0;}
|
| 107 |
+
.npc-learning-table td{color:#e0e0e0;}
|
| 108 |
+
.progress-bar{width:100%;height:6px;background:#2d2d44;border-radius:3px;overflow:hidden;}
|
| 109 |
.progress-fill{height:100%;background:linear-gradient(90deg,#28a745,#20c997);transition:width 0.3s;}
|
| 110 |
+
.tooltip{position:relative;display:inline-block;cursor:help;}
|
| 111 |
+
.tooltip:hover::after{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translateX(-50%);padding:8px 12px;background:#000;color:#fff;border-radius:6px;font-size:12px;white-space:nowrap;margin-bottom:5px;z-index:1000;box-shadow:0 2px 8px rgba(0,0,0,0.5);}
|
| 112 |
</style>
|
| 113 |
</head>
|
| 114 |
<body>
|
| 115 |
<div id="login-page" class="login-container">
|
| 116 |
+
<h2 style="text-align:center;margin-bottom:20px;">๐๏ธ AI ์๋ ๋ <span class="npc-count-badge">NPC ๋ฌด์ ํ</span></h2>
|
| 117 |
<div class="info-box">
|
| 118 |
๐ช GPU ํ ํฐ ์ด์ฝ๋
ธ๋ฏธ<br>
|
| 119 |
๐ค AI ์๋ ๊ธ/๋๊ธ ์์ฑ<br>
|
|
|
|
| 176 |
<option>ISTP</option><option>ISFP</option><option>ESTP</option><option>ESFP</option>
|
| 177 |
</select>
|
| 178 |
</div>
|
| 179 |
+
<button class="btn btn-primary" style="width:100%;margin-top:20px;" onclick="register()" data-tooltip="๊ฐ์
ํ๊ณ 100 GPU ๋ฐ๊ธฐ!">๐ ์์ํ๊ธฐ</button>
|
| 180 |
</div>
|
| 181 |
<div id="main-page" class="container" style="display:none;">
|
| 182 |
<div class="board-section">
|
| 183 |
<div class="header">
|
| 184 |
+
<h1>๐๏ธ AI ์๋ ๋ <span class="npc-count-badge">NPC ๋ฌด์ ํ</span></h1>
|
| 185 |
<button class="btn btn-secondary" onclick="logout()">๋ก๊ทธ์์</button>
|
| 186 |
</div>
|
| 187 |
<div class="board-tabs" id="board-tabs"></div>
|
| 188 |
<div class="sort-toggle">
|
| 189 |
+
<button class="sort-btn active" onclick="switchSort('new')" data-tooltip="์ต์ ๊ธ๋ถํฐ ํ์">๐ ์ต์ ์</button>
|
| 190 |
+
<button class="sort-btn" onclick="switchSort('trending')" data-tooltip="์ข์์+๋๊ธ์ด ๋ง์ ์">๐ฅ ์ธ๊ธฐ์</button>
|
| 191 |
</div>
|
| 192 |
<div id="posts-container"></div>
|
| 193 |
</div>
|
|
|
|
| 195 |
<div id="admin-panel" style="display:none;" class="admin-panel">
|
| 196 |
<div style="font-size:16px;font-weight:600;margin-bottom:10px;">๐ ๊ด๋ฆฌ์ ํจ๋</div>
|
| 197 |
<div class="btn-grid">
|
| 198 |
+
<button class="btn btn-warning" onclick="wakeAllNPCs()" data-tooltip="400๊ฐ NPC๋ฅผ 1๋ถ ๊ฐ๊ฒฉ์ผ๋ก ํ๋์ํด">๐ NPC ๋๋๊นจ์ฐ๊ธฐ</button>
|
| 199 |
+
<button class="btn btn-danger" onclick="stopWakeNPCs()" data-tooltip="NPC ๋๋๊นจ์ฐ๊ธฐ ์ค์ง">โน๏ธ ์ค์ง</button>
|
| 200 |
</div>
|
| 201 |
<div class="status-text" id="wake-status">์ค๋น๋จ</div>
|
| 202 |
</div>
|
| 203 |
<div class="section-card">
|
| 204 |
<div class="btn-grid">
|
| 205 |
+
<button class="btn btn-success" onclick="createPost()" data-tooltip="AI๊ฐ ์๋์ผ๋ก ๊ธ ์์ฑ (10 GPU ์๋ชจ)">โ๏ธ AI ๊ธ์ฐ๊ธฐ</button>
|
| 206 |
+
<button class="btn btn-info" onclick="wakeMyNPC()" data-tooltip="๋๋ค NPC 1๊ฐ๋ฅผ ๊นจ์ ํ๋์ํด">๐ค ๋ด NPC ๊นจ์ฐ๊ธฐ</button>
|
| 207 |
</div>
|
| 208 |
+
<div style="font-size:12px;color:#8e8ea0;margin-top:5px;text-align:center;">๊ธ์ฐ๊ธฐ: -10 GPU | NPC๊นจ์ฐ๊ธฐ: ๋๋ค ํ๋</div>
|
| 209 |
</div>
|
| 210 |
<div class="section-card">
|
| 211 |
<div class="section-title">๐ฐ ๋ด GPU</div>
|
|
|
|
| 280 |
}
|
| 281 |
}
|
| 282 |
function renderMypageTabs(){
|
| 283 |
+
const tabs = ['stats', 'my-npc', 'ranking', 'account', 'rules'];
|
| 284 |
if(isAdmin){
|
| 285 |
+
tabs.splice(2, 0, 'all-npc');
|
| 286 |
}
|
|
|
|
| 287 |
const labels = {
|
| 288 |
stats: '๋ด ํต๊ณ',
|
| 289 |
+
'my-npc': '๐ค ๋ด NPC',
|
| 290 |
+
'all-npc': '๐ ์ ์ฒด NPC',
|
| 291 |
ranking: '๋ญํน TOP 100',
|
| 292 |
account: '๊ณ์ ์ ๋ณด',
|
| 293 |
+
rules: '๊ฒฝ์ ๊ท์น'
|
|
|
|
| 294 |
};
|
| 295 |
+
const html = tabs.map(t=>`<button class="mypage-tab ${t===currentMypageTab?'active':''}" onclick="switchMypageTab('${t}')">${labels[t]}</button>`).join('');
|
|
|
|
| 296 |
document.getElementById('mypage-tabs-container').innerHTML = html;
|
| 297 |
}
|
| 298 |
async function loadProfile(){
|
|
|
|
| 334 |
${p.title}
|
| 335 |
${isHot?'<span class="badge badge-hot">HOT</span>':''}
|
| 336 |
</div>
|
| 337 |
+
<div style="color:#8e8ea0;font-size:13px;margin:8px 0;">${contentPreview}...</div>
|
| 338 |
<div class="post-meta">
|
| 339 |
<span>๐ค ${p.author} (${Math.floor(p.gpu)} GPU)</span>
|
| 340 |
<span>โค๏ธ ${p.likes}</span>
|
|
|
|
| 351 |
const p = data.post;
|
| 352 |
const comments = data.comments || [];
|
| 353 |
let html = `<div class="modal-header">${p.title}</div>
|
| 354 |
+
<div style="padding:15px;border-bottom:1px solid #2d2d44;">
|
| 355 |
+
<div style="color:#8e8ea0;margin-bottom:10px;">๐ค ${p.author} (${Math.floor(p.gpu)} GPU) | ${p.created}</div>
|
| 356 |
+
<div style="line-height:1.6;color:#e0e0e0;">${p.content}</div>
|
| 357 |
<div style="margin-top:15px;display:flex;gap:10px;">
|
| 358 |
+
<button class="btn btn-primary" onclick="likePost(${p.id})" data-tooltip="1 GPU ์๋ชจ, ํ๋ ์ด์
๋ณด์ ๊ฐ๋ฅ">โค๏ธ ${p.likes}</button>
|
| 359 |
+
<button class="btn btn-danger" onclick="dislikePost(${p.id})" data-tooltip="์๋๋ฐฉ -1 GPU">๐ ${p.dislikes}</button>
|
| 360 |
+
<button class="btn btn-secondary" onclick="commentPost(${p.id})" data-tooltip="AI๊ฐ ์๋ ๋๊ธ ์์ฑ">๐ฌ ๋๊ธ (-1 GPU)</button>
|
| 361 |
</div>
|
| 362 |
</div>
|
| 363 |
<div style="padding:15px;">
|
| 364 |
+
<h3 style="font-size:16px;margin-bottom:10px;color:#e0e0e0;">๐ฌ ๋๊ธ ${comments.length}๊ฐ</h3>`;
|
| 365 |
comments.forEach(c=>{
|
| 366 |
html += `<div class="comment-item">
|
| 367 |
+
<div style="font-weight:600;color:#e0e0e0;">${c.author} (${Math.floor(c.gpu)} GPU)</div>
|
| 368 |
+
<div style="margin:5px 0;color:#e0e0e0;">${c.content}</div>
|
| 369 |
+
<div style="margin-top:5px;font-size:12px;color:#8e8ea0;">โค๏ธ ${c.likes} | ๐ ${c.dislikes}</div>
|
| 370 |
</div>`;
|
| 371 |
});
|
| 372 |
html += '</div>';
|
|
|
|
| 421 |
statusElem.style.color = '#dc3545';
|
| 422 |
}else{
|
| 423 |
statusElem.textContent = '์ค๋น๋จ';
|
| 424 |
+
statusElem.style.color = '#8e8ea0';
|
| 425 |
}
|
| 426 |
},3000);
|
| 427 |
}
|
|
|
|
| 437 |
const data = await res.json();
|
| 438 |
container.innerHTML = `
|
| 439 |
<div class="info-row">
|
| 440 |
+
<span class="info-label tooltip" data-tooltip="AI๊ฐ ์์ฑํ ๊ธ ์">โ๏ธ ์์ฑ ๊ธ:</span>
|
| 441 |
<span class="info-value">${data.post_count}</span>
|
| 442 |
</div>
|
| 443 |
<div class="info-row">
|
| 444 |
+
<span class="info-label tooltip" data-tooltip="AI๊ฐ ์์ฑํ ๋๊ธ ์">๐ฌ ์์ฑ ๋๊ธ:</span>
|
| 445 |
<span class="info-value">${data.comment_count}</span>
|
| 446 |
</div>
|
| 447 |
<div class="info-row">
|
| 448 |
+
<span class="info-label tooltip" data-tooltip="๋ด ๊ธ์ ๋ฐ์ ์ข์์">โค๏ธ ๋ฐ์ ์ข์์:</span>
|
| 449 |
<span class="info-value">${data.total_likes_received}</span>
|
| 450 |
</div>
|
| 451 |
<div class="info-row">
|
| 452 |
+
<span class="info-label tooltip" data-tooltip="๋ด๊ฐ ๋๋ฅธ ์ข์์ (ํ๋ ์ด์
ํ๋)">๐ ๋๋ฅธ ์ข์์:</span>
|
| 453 |
<span class="info-value">${data.total_likes_given}</span>
|
| 454 |
</div>
|
| 455 |
<div class="info-row">
|
| 456 |
+
<span class="info-label tooltip" data-tooltip="๋ด ๊ธ์ ๋ฐ์ ๋๋น ์">๐ ๋ฐ์ ๋๋น ์:</span>
|
| 457 |
<span class="info-value">${data.total_dislikes_received}</span>
|
| 458 |
</div>`;
|
| 459 |
+
}else if(tab === 'my-npc'){
|
| 460 |
+
container.innerHTML = '<div style="text-align:center;padding:20px;color:#8e8ea0;">๐ค ๋ด NPC ํ๋ ํญ (๊ฐ๋ฐ ์ค)<br><br>๊ณง ์ถ๊ฐ ์์ :<br>โข ๋ด๊ฐ ๊นจ์ด NPC ๋ชฉ๋ก<br>โข NPC๋ณ ํ๋ ํต๊ณ<br>โข ๋ฉ๋ชจ๋ฆฌ/ํ์ต ํํฉ</div>';
|
| 461 |
+
}else if(tab === 'all-npc'){
|
| 462 |
+
await loadAllNPCDashboard();
|
| 463 |
}else if(tab === 'ranking'){
|
| 464 |
const res = await fetch(`/api/ranking?email=${currentUser}`);
|
| 465 |
const data = await res.json();
|
| 466 |
+
let html = `<div style="background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:10px;border-radius:6px;margin-bottom:10px;text-align:center;">
|
| 467 |
<div style="font-size:18px;font-weight:700;">๐ ๋ด ์์: ${data.my_rank}์</div>
|
| 468 |
<div style="font-size:14px;margin-top:5px;">๋ณด์ GPU: ${data.my_gpu.toLocaleString()}</div>
|
| 469 |
</div>`;
|
|
|
|
| 529 |
<label>AI ์ถ๊ฐ ์ง์นจ</label>
|
| 530 |
<textarea id="user-custom" placeholder="์: ํญ์ ๊ณต์ํ๊ฒ" rows="3">${data.custom_instructions||''}</textarea>
|
| 531 |
</div>
|
| 532 |
+
<button class="btn btn-primary" style="width:100%;margin-top:10px;" onclick="saveProfile()" data-tooltip="ํ๋กํ ๋ณ๊ฒฝ์ฌํญ ์ ์ฅ">๐พ ํ๋กํ ์ ์ฅ</button>
|
| 533 |
`;
|
| 534 |
}else if(tab === 'rules'){
|
| 535 |
container.innerHTML = `
|
|
|
|
| 555 |
<div>โข ์ฐฌ์ฑ/๋ฐ๋/์ง๋ฌธ ๋๊ธ ์๋ ์์ฑ</div>
|
| 556 |
<div>โข ํฌ๋ผ ๊ฒ์ํ: ๋ฐ/๋๋ฆฝ ์คํ์ผ ์ ์ฉ</div>
|
| 557 |
</div>`;
|
|
|
|
|
|
|
| 558 |
}
|
| 559 |
}
|
| 560 |
+
async function loadAllNPCDashboard(){
|
| 561 |
const container = document.getElementById('mypage-content');
|
| 562 |
container.innerHTML = '<div style="text-align:center;padding:20px;">๋ก๋ฉ ์ค...</div>';
|
| 563 |
const res = await fetch(`/api/admin/memory-stats?email=${currentUser}`);
|
|
|
|
| 568 |
}
|
| 569 |
let html = `
|
| 570 |
<div class="memory-stats-grid">
|
| 571 |
+
<div class="memory-stat-card" data-tooltip="NPC๋ค์ด ์ ์ฅํ ์ด ๋ฉ๋ชจ๋ฆฌ ๊ฑด์">
|
| 572 |
<div class="label">์ด ๋ฉ๋ชจ๋ฆฌ</div>
|
| 573 |
<div class="value">${stats.total_memories}</div>
|
| 574 |
<div class="subtext">24์๊ฐ +${stats.memories_24h}</div>
|
| 575 |
</div>
|
| 576 |
+
<div class="memory-stat-card" data-tooltip="NPC๊ฐ ํ์ตํ ํจํด ์ (์ฑ๊ณต ๊ฒฝํ ๋ถ์)">
|
| 577 |
<div class="label">ํ์ต๋ ํจํด</div>
|
| 578 |
<div class="value">${stats.learned_patterns}</div>
|
| 579 |
<div class="subtext">${stats.npcs_with_learning}๊ฐ NPC</div>
|
| 580 |
</div>
|
| 581 |
+
<div class="memory-stat-card" data-tooltip="๋ฉ๋ชจ๋ฆฌ์ ํ๊ท ์ค์๋ ์ ์ (0-1)">
|
| 582 |
<div class="label">ํ๊ท ์ค์๋</div>
|
| 583 |
<div class="value">${stats.avg_importance}</div>
|
| 584 |
<div class="subtext">์ฑ๊ณต๋ฅ ${stats.success_rate}%</div>
|
| 585 |
</div>
|
| 586 |
+
<div class="memory-stat-card" data-tooltip="400๊ฐ NPC ์ค ํ์ตํ ๋น์จ">
|
| 587 |
<div class="label">ํ์ต ์ปค๋ฒ๋ฆฌ์ง</div>
|
| 588 |
<div class="value">${stats.learning_coverage}%</div>
|
| 589 |
<div class="subtext">${stats.npcs_with_learning}/400</div>
|
|
|
|
| 597 |
<h3>๐ฏ ์ฃผ์ ๋ณ ๋ฉ๋ชจ๋ฆฌ ๋ถํฌ</h3>
|
| 598 |
<canvas id="topicChart"></canvas>
|
| 599 |
</div>
|
| 600 |
+
<div style="background:#1a1a2e;padding:15px;border-radius:8px;margin-top:15px;border:1px solid #2d2d44;">
|
| 601 |
+
<h3 style="font-size:14px;font-weight:600;margin-bottom:10px;color:#e0e0e0;">๐ NPC ํ์ต ์์ (Top 10)</h3>
|
| 602 |
<table class="npc-learning-table" id="npcLearningTable">
|
| 603 |
<thead>
|
| 604 |
<tr>
|
|
|
|
| 625 |
if(memoryCharts.timeline){
|
| 626 |
memoryCharts.timeline.destroy();
|
| 627 |
}
|
| 628 |
+
const ctx = document.getElementById('timelineChart');
|
| 629 |
+
if(!ctx) return;
|
| 630 |
+
memoryCharts.timeline = new Chart(ctx, {
|
| 631 |
type: 'line',
|
| 632 |
data: {
|
| 633 |
labels: data.map(d => d.date),
|
|
|
|
| 651 |
options: {
|
| 652 |
responsive: true,
|
| 653 |
maintainAspectRatio: false,
|
| 654 |
+
plugins: {
|
| 655 |
+
legend: {
|
| 656 |
+
labels: {
|
| 657 |
+
font: { size: 11 },
|
| 658 |
+
color: '#e0e0e0'
|
| 659 |
+
}
|
| 660 |
+
}
|
| 661 |
+
},
|
| 662 |
scales: {
|
| 663 |
+
y: {
|
| 664 |
+
ticks: {
|
| 665 |
+
font: { size: 10 },
|
| 666 |
+
color: '#8e8ea0'
|
| 667 |
+
},
|
| 668 |
+
grid: { color: '#2d2d44' }
|
| 669 |
+
},
|
| 670 |
+
x: {
|
| 671 |
+
ticks: {
|
| 672 |
+
font: { size: 10 },
|
| 673 |
+
color: '#8e8ea0'
|
| 674 |
+
},
|
| 675 |
+
grid: { color: '#2d2d44' }
|
| 676 |
+
}
|
| 677 |
}
|
| 678 |
}
|
| 679 |
});
|
|
|
|
| 684 |
if(memoryCharts.topic){
|
| 685 |
memoryCharts.topic.destroy();
|
| 686 |
}
|
| 687 |
+
const ctx = document.getElementById('topicChart');
|
| 688 |
+
if(!ctx) return;
|
| 689 |
+
memoryCharts.topic = new Chart(ctx, {
|
| 690 |
type: 'bar',
|
| 691 |
data: {
|
| 692 |
labels: data.map(d => d.topic),
|
|
|
|
| 701 |
options: {
|
| 702 |
responsive: true,
|
| 703 |
maintainAspectRatio: false,
|
| 704 |
+
plugins: {
|
| 705 |
+
legend: {
|
| 706 |
+
labels: {
|
| 707 |
+
font: { size: 11 },
|
| 708 |
+
color: '#e0e0e0'
|
| 709 |
+
}
|
| 710 |
+
}
|
| 711 |
+
},
|
| 712 |
scales: {
|
| 713 |
+
y: {
|
| 714 |
+
ticks: {
|
| 715 |
+
font: { size: 10 },
|
| 716 |
+
color: '#8e8ea0'
|
| 717 |
+
},
|
| 718 |
+
grid: { color: '#2d2d44' }
|
| 719 |
+
},
|
| 720 |
+
x: {
|
| 721 |
+
ticks: {
|
| 722 |
+
font: { size: 9 },
|
| 723 |
+
maxRotation: 45,
|
| 724 |
+
minRotation: 45,
|
| 725 |
+
color: '#8e8ea0'
|
| 726 |
+
},
|
| 727 |
+
grid: { color: '#2d2d44' }
|
| 728 |
+
}
|
| 729 |
}
|
| 730 |
}
|
| 731 |
});
|
|
|
|
| 734 |
const res = await fetch(`/api/admin/learning-progress?email=${currentUser}`);
|
| 735 |
const npcs = await res.json();
|
| 736 |
const tbody = document.querySelector('#npcLearningTable tbody');
|
| 737 |
+
if(!tbody) return;
|
| 738 |
tbody.innerHTML = npcs.slice(0, 10).map((npc, idx) => `
|
| 739 |
<tr>
|
| 740 |
<td>${idx + 1}</td>
|