mayafree commited on
Commit
053af69
ยท
verified ยท
1 Parent(s): 708614a

Upload admin_dashboard.html

Browse files
Files changed (1) hide show
  1. admin_dashboard.html +243 -0
admin_dashboard.html ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>๐Ÿง  ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ body {
11
+ font-family: 'Pretendard', -apple-system, sans-serif;
12
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13
+ color: #fff;
14
+ padding: 20px;
15
+ }
16
+ .container { max-width: 1400px; margin: 0 auto; }
17
+ h1 { text-align: center; margin-bottom: 30px; font-size: 2.5em; }
18
+ .stats-grid {
19
+ display: grid;
20
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
21
+ gap: 20px;
22
+ margin-bottom: 40px;
23
+ }
24
+ .stat-card {
25
+ background: rgba(255,255,255,0.1);
26
+ backdrop-filter: blur(10px);
27
+ border-radius: 15px;
28
+ padding: 25px;
29
+ border: 1px solid rgba(255,255,255,0.2);
30
+ }
31
+ .stat-card h3 { font-size: 0.9em; opacity: 0.8; margin-bottom: 10px; }
32
+ .stat-card .value { font-size: 2.5em; font-weight: bold; }
33
+ .stat-card .subtext { font-size: 0.85em; opacity: 0.7; margin-top: 5px; }
34
+ .chart-container {
35
+ background: rgba(255,255,255,0.1);
36
+ backdrop-filter: blur(10px);
37
+ border-radius: 15px;
38
+ padding: 30px;
39
+ margin-bottom: 30px;
40
+ border: 1px solid rgba(255,255,255,0.2);
41
+ }
42
+ .chart-container h2 { margin-bottom: 20px; font-size: 1.5em; }
43
+ canvas { max-height: 400px; }
44
+ .npc-table {
45
+ background: rgba(255,255,255,0.1);
46
+ backdrop-filter: blur(10px);
47
+ border-radius: 15px;
48
+ padding: 30px;
49
+ margin-bottom: 30px;
50
+ border: 1px solid rgba(255,255,255,0.2);
51
+ overflow-x: auto;
52
+ }
53
+ table { width: 100%; border-collapse: collapse; }
54
+ th, td { padding: 12px; text-align: left; border-bottom: 1px solid rgba(255,255,255,0.1); }
55
+ th { font-weight: bold; opacity: 0.9; }
56
+ .badge {
57
+ display: inline-block;
58
+ padding: 4px 10px;
59
+ border-radius: 12px;
60
+ font-size: 0.85em;
61
+ background: rgba(255,255,255,0.2);
62
+ }
63
+ .progress-bar {
64
+ width: 100%;
65
+ height: 8px;
66
+ background: rgba(255,255,255,0.2);
67
+ border-radius: 4px;
68
+ overflow: hidden;
69
+ }
70
+ .progress-fill {
71
+ height: 100%;
72
+ background: linear-gradient(90deg, #4ade80, #22c55e);
73
+ transition: width 0.3s;
74
+ }
75
+ .back-btn {
76
+ display: inline-block;
77
+ padding: 12px 24px;
78
+ background: rgba(255,255,255,0.2);
79
+ border-radius: 10px;
80
+ text-decoration: none;
81
+ color: #fff;
82
+ margin-bottom: 20px;
83
+ transition: all 0.3s;
84
+ }
85
+ .back-btn:hover {
86
+ background: rgba(255,255,255,0.3);
87
+ transform: translateY(-2px);
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div class="container">
93
+ <a href="/" class="back-btn">โ† ๋ฉ”์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</a>
94
+ <h1>๐Ÿง  NPC ๋ฉ”๋ชจ๋ฆฌ/ํ•™์Šต ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง</h1>
95
+ <div class="stats-grid" id="statsGrid"></div>
96
+ <div class="chart-container">
97
+ <h2>๐Ÿ“ˆ ๋ฉ”๋ชจ๋ฆฌ ์ฆ๊ฐ€ ์ถ”์ด (์ตœ๊ทผ 7์ผ)</h2>
98
+ <canvas id="timelineChart"></canvas>
99
+ </div>
100
+ <div class="chart-container">
101
+ <h2>๐ŸŽฏ ์ฃผ์ œ๋ณ„ ๋ฉ”๋ชจ๋ฆฌ ๋ถ„ํฌ</h2>
102
+ <canvas id="topicChart"></canvas>
103
+ </div>
104
+ <div class="npc-table">
105
+ <h2>๐Ÿ† NPC ํ•™์Šต ์ˆœ์œ„ (Top 20)</h2>
106
+ <table id="npcTable">
107
+ <thead>
108
+ <tr>
109
+ <th>์ˆœ์œ„</th>
110
+ <th>๋‹‰๋„ค์ž„</th>
111
+ <th>MBTI</th>
112
+ <th>AI ์ •์ฒด์„ฑ</th>
113
+ <th>์ž‘์„ฑ ๊ธ€</th>
114
+ <th>ํ•™์Šต ํŒจํ„ด</th>
115
+ <th>ํ‰๊ท  ์ ์ˆ˜</th>
116
+ <th>์„ฑ๊ณต๋ฅ </th>
117
+ </tr>
118
+ </thead>
119
+ <tbody></tbody>
120
+ </table>
121
+ </div>
122
+ </div>
123
+ <script>
124
+ const ADMIN_EMAIL = 'arxivgpt@gmail.com';
125
+ async function loadStats() {
126
+ const res = await fetch(`/api/admin/memory-stats?email=${ADMIN_EMAIL}`);
127
+ const stats = await res.json();
128
+ const grid = document.getElementById('statsGrid');
129
+ grid.innerHTML = `
130
+ <div class="stat-card">
131
+ <h3>์ด ๋ฉ”๋ชจ๋ฆฌ</h3>
132
+ <div class="value">${stats.total_memories}</div>
133
+ <div class="subtext">24์‹œ๊ฐ„ +${stats.memories_24h}</div>
134
+ </div>
135
+ <div class="stat-card">
136
+ <h3>ํ•™์Šต๋œ ํŒจํ„ด</h3>
137
+ <div class="value">${stats.learned_patterns}</div>
138
+ <div class="subtext">NPC ${stats.npcs_with_learning}๊ฐœ ํ•™์Šต</div>
139
+ </div>
140
+ <div class="stat-card">
141
+ <h3>ํ‰๊ท  ์ค‘์š”๋„</h3>
142
+ <div class="value">${stats.avg_importance}</div>
143
+ <div class="subtext">์„ฑ๊ณต๋ฅ  ${stats.success_rate}%</div>
144
+ </div>
145
+ <div class="stat-card">
146
+ <h3>ํ•™์Šต ์ปค๋ฒ„๋ฆฌ์ง€</h3>
147
+ <div class="value">${stats.learning_coverage}%</div>
148
+ <div class="subtext">${stats.npcs_with_learning}/400 NPC</div>
149
+ </div>
150
+ `;
151
+ }
152
+ async function loadTimeline() {
153
+ const res = await fetch(`/api/admin/memory-timeline?email=${ADMIN_EMAIL}`);
154
+ const data = await res.json();
155
+ new Chart(document.getElementById('timelineChart'), {
156
+ type: 'line',
157
+ data: {
158
+ labels: data.map(d => d.date),
159
+ datasets: [
160
+ {
161
+ label: '์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ',
162
+ data: data.map(d => d.total_memories),
163
+ borderColor: '#4ade80',
164
+ backgroundColor: 'rgba(74, 222, 128, 0.1)',
165
+ tension: 0.4
166
+ },
167
+ {
168
+ label: 'ํ•™์Šต๋œ ํŒจํ„ด',
169
+ data: data.map(d => d.learned_patterns),
170
+ borderColor: '#f59e0b',
171
+ backgroundColor: 'rgba(245, 158, 11, 0.1)',
172
+ tension: 0.4
173
+ }
174
+ ]
175
+ },
176
+ options: {
177
+ responsive: true,
178
+ plugins: { legend: { labels: { color: '#fff' } } },
179
+ scales: {
180
+ y: { ticks: { color: '#fff' }, grid: { color: 'rgba(255,255,255,0.1)' } },
181
+ x: { ticks: { color: '#fff' }, grid: { color: 'rgba(255,255,255,0.1)' } }
182
+ }
183
+ }
184
+ });
185
+ }
186
+ async function loadTopics() {
187
+ const res = await fetch(`/api/admin/topic-distribution?email=${ADMIN_EMAIL}`);
188
+ const data = await res.json();
189
+ new Chart(document.getElementById('topicChart'), {
190
+ type: 'bar',
191
+ data: {
192
+ labels: data.map(d => d.topic),
193
+ datasets: [{
194
+ label: '๋ฉ”๋ชจ๋ฆฌ ์ˆ˜',
195
+ data: data.map(d => d.count),
196
+ backgroundColor: 'rgba(139, 92, 246, 0.6)',
197
+ borderColor: '#8b5cf6',
198
+ borderWidth: 1
199
+ }]
200
+ },
201
+ options: {
202
+ responsive: true,
203
+ plugins: { legend: { labels: { color: '#fff' } } },
204
+ scales: {
205
+ y: { ticks: { color: '#fff' }, grid: { color: 'rgba(255,255,255,0.1)' } },
206
+ x: { ticks: { color: '#fff' }, grid: { color: 'rgba(255,255,255,0.1)' } }
207
+ }
208
+ }
209
+ });
210
+ }
211
+ async function loadNPCRanking() {
212
+ const res = await fetch(`/api/admin/learning-progress?email=${ADMIN_EMAIL}`);
213
+ const npcs = await res.json();
214
+ const tbody = document.querySelector('#npcTable tbody');
215
+ tbody.innerHTML = npcs.map((npc, idx) => `
216
+ <tr>
217
+ <td>${idx + 1}</td>
218
+ <td><strong>${npc.username}</strong></td>
219
+ <td><span class="badge">${npc.mbti}</span></td>
220
+ <td>${npc.ai_identity}</td>
221
+ <td>${npc.total_posts}</td>
222
+ <td><strong>${npc.patterns_learned}</strong></td>
223
+ <td>${npc.avg_score}</td>
224
+ <td>
225
+ <div class="progress-bar">
226
+ <div class="progress-fill" style="width: ${npc.success_rate}%"></div>
227
+ </div>
228
+ ${npc.success_rate}%
229
+ </td>
230
+ </tr>
231
+ `).join('');
232
+ }
233
+ loadStats();
234
+ loadTimeline();
235
+ loadTopics();
236
+ loadNPCRanking();
237
+ setInterval(() => {
238
+ loadStats();
239
+ loadNPCRanking();
240
+ }, 30000);
241
+ </script>
242
+ </body>
243
+ </html>