dbhavery commited on
Commit
e101077
·
verified ·
1 Parent(s): 139aab9

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +834 -19
index.html CHANGED
@@ -1,19 +1,834 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Vaultwise</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg-primary: #0d1117;
12
+ --bg-secondary: #161b22;
13
+ --bg-tertiary: #21262d;
14
+ --bg-card: #1c2128;
15
+ --border: #30363d;
16
+ --text-primary: #e6edf3;
17
+ --text-secondary: #8b949e;
18
+ --text-muted: #6e7681;
19
+ --accent: #58a6ff;
20
+ --accent-hover: #79c0ff;
21
+ --green: #3fb950;
22
+ --yellow: #d29922;
23
+ --red: #f85149;
24
+ --purple: #bc8cff;
25
+ --radius: 8px;
26
+ --shadow: 0 2px 8px rgba(0,0,0,0.3);
27
+ }
28
+
29
+ body {
30
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif;
31
+ background: var(--bg-primary);
32
+ color: var(--text-primary);
33
+ line-height: 1.6;
34
+ min-height: 100vh;
35
+ }
36
+
37
+ /* Layout */
38
+ .app { display: flex; min-height: 100vh; }
39
+ .sidebar {
40
+ width: 240px;
41
+ background: var(--bg-secondary);
42
+ border-right: 1px solid var(--border);
43
+ padding: 20px 0;
44
+ flex-shrink: 0;
45
+ position: fixed;
46
+ top: 0; bottom: 0; left: 0;
47
+ overflow-y: auto;
48
+ z-index: 100;
49
+ }
50
+ .main { margin-left: 240px; flex: 1; padding: 24px 32px; min-width: 0; }
51
+
52
+ /* Sidebar */
53
+ .logo { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 12px; }
54
+ .logo h1 { font-size: 20px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; }
55
+ .logo small { font-size: 11px; color: var(--text-muted); }
56
+ .nav-item {
57
+ display: flex; align-items: center; gap: 10px;
58
+ padding: 10px 20px; cursor: pointer;
59
+ color: var(--text-secondary); font-size: 14px; font-weight: 500;
60
+ transition: all 0.15s;
61
+ border-left: 3px solid transparent;
62
+ }
63
+ .nav-item:hover { background: var(--bg-tertiary); color: var(--text-primary); }
64
+ .nav-item.active { color: var(--accent); border-left-color: var(--accent); background: rgba(88,166,255,0.08); }
65
+ .nav-icon { font-size: 16px; width: 20px; text-align: center; }
66
+
67
+ /* Page header */
68
+ .page-header { margin-bottom: 24px; }
69
+ .page-header h2 { font-size: 24px; font-weight: 600; }
70
+ .page-header p { color: var(--text-secondary); font-size: 14px; margin-top: 4px; }
71
+
72
+ /* Cards */
73
+ .stat-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
74
+ .stat-card {
75
+ background: var(--bg-card);
76
+ border: 1px solid var(--border);
77
+ border-radius: var(--radius);
78
+ padding: 20px;
79
+ box-shadow: var(--shadow);
80
+ }
81
+ .stat-card .label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 8px; }
82
+ .stat-card .value { font-size: 28px; font-weight: 700; }
83
+ .stat-card .value.blue { color: var(--accent); }
84
+ .stat-card .value.green { color: var(--green); }
85
+ .stat-card .value.yellow { color: var(--yellow); }
86
+ .stat-card .value.purple { color: var(--purple); }
87
+
88
+ /* Panels */
89
+ .panel {
90
+ background: var(--bg-card);
91
+ border: 1px solid var(--border);
92
+ border-radius: var(--radius);
93
+ box-shadow: var(--shadow);
94
+ margin-bottom: 24px;
95
+ overflow: hidden;
96
+ }
97
+ .panel-header {
98
+ padding: 16px 20px;
99
+ border-bottom: 1px solid var(--border);
100
+ display: flex; align-items: center; justify-content: space-between;
101
+ }
102
+ .panel-header h3 { font-size: 16px; font-weight: 600; }
103
+ .panel-body { padding: 20px; }
104
+
105
+ /* Tables */
106
+ table { width: 100%; border-collapse: collapse; }
107
+ th { text-align: left; padding: 10px 16px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); border-bottom: 1px solid var(--border); }
108
+ td { padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 14px; color: var(--text-secondary); }
109
+ tr:last-child td { border-bottom: none; }
110
+ tr:hover td { background: rgba(255,255,255,0.02); }
111
+
112
+ /* Buttons */
113
+ .btn {
114
+ display: inline-flex; align-items: center; gap: 6px;
115
+ padding: 8px 16px; border-radius: 6px;
116
+ font-size: 13px; font-weight: 500;
117
+ cursor: pointer; border: 1px solid var(--border);
118
+ background: var(--bg-tertiary); color: var(--text-primary);
119
+ transition: all 0.15s;
120
+ }
121
+ .btn:hover { border-color: var(--text-muted); }
122
+ .btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); }
123
+ .btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
124
+ .btn-sm { padding: 5px 10px; font-size: 12px; }
125
+ .btn-green { background: rgba(63,185,80,0.15); color: var(--green); border-color: rgba(63,185,80,0.3); }
126
+ .btn-red { background: rgba(248,81,73,0.15); color: var(--red); border-color: rgba(248,81,73,0.3); }
127
+
128
+ /* Inputs */
129
+ input[type="text"], textarea, select {
130
+ width: 100%;
131
+ padding: 10px 14px;
132
+ background: var(--bg-primary);
133
+ border: 1px solid var(--border);
134
+ border-radius: 6px;
135
+ color: var(--text-primary);
136
+ font-size: 14px;
137
+ font-family: inherit;
138
+ transition: border-color 0.15s;
139
+ }
140
+ input[type="text"]:focus, textarea:focus { outline: none; border-color: var(--accent); }
141
+ textarea { resize: vertical; min-height: 120px; }
142
+
143
+ /* Badge */
144
+ .badge {
145
+ display: inline-block; padding: 2px 8px; border-radius: 12px;
146
+ font-size: 11px; font-weight: 600; text-transform: uppercase;
147
+ }
148
+ .badge-green { background: rgba(63,185,80,0.15); color: var(--green); }
149
+ .badge-yellow { background: rgba(210,153,34,0.15); color: var(--yellow); }
150
+ .badge-blue { background: rgba(88,166,255,0.15); color: var(--accent); }
151
+ .badge-red { background: rgba(248,81,73,0.15); color: var(--red); }
152
+
153
+ /* Modal */
154
+ .modal-overlay {
155
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
156
+ background: rgba(0,0,0,0.6); z-index: 200;
157
+ display: flex; align-items: center; justify-content: center;
158
+ }
159
+ .modal {
160
+ background: var(--bg-secondary); border: 1px solid var(--border);
161
+ border-radius: var(--radius); width: 560px; max-width: 90vw;
162
+ max-height: 85vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,0.5);
163
+ }
164
+ .modal-header {
165
+ padding: 16px 20px; border-bottom: 1px solid var(--border);
166
+ display: flex; align-items: center; justify-content: space-between;
167
+ }
168
+ .modal-header h3 { font-size: 16px; font-weight: 600; }
169
+ .modal-close { cursor: pointer; color: var(--text-muted); font-size: 20px; background: none; border: none; padding: 4px 8px; }
170
+ .modal-close:hover { color: var(--text-primary); }
171
+ .modal-body { padding: 20px; }
172
+ .modal-body .field { margin-bottom: 16px; }
173
+ .modal-body .field label { display: block; font-size: 13px; font-weight: 500; color: var(--text-secondary); margin-bottom: 6px; }
174
+ .modal-footer { padding: 12px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
175
+
176
+ /* Chat */
177
+ .chat-container { max-width: 800px; }
178
+ .chat-messages { min-height: 200px; max-height: 500px; overflow-y: auto; margin-bottom: 16px; }
179
+ .chat-msg { margin-bottom: 16px; padding: 14px 18px; border-radius: var(--radius); }
180
+ .chat-msg.user { background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.2); margin-left: 40px; }
181
+ .chat-msg.assistant { background: var(--bg-tertiary); border: 1px solid var(--border); margin-right: 40px; }
182
+ .chat-msg .msg-label { font-size: 11px; font-weight: 600; text-transform: uppercase; color: var(--text-muted); margin-bottom: 6px; }
183
+ .chat-msg .msg-text { font-size: 14px; line-height: 1.7; white-space: pre-wrap; }
184
+ .chat-msg .sources { margin-top: 10px; font-size: 12px; color: var(--text-muted); }
185
+ .chat-msg .sources a { color: var(--accent); text-decoration: none; }
186
+ .chat-msg .confidence { margin-top: 6px; font-size: 12px; }
187
+ .chat-input-row { display: flex; gap: 8px; }
188
+ .chat-input-row input { flex: 1; }
189
+
190
+ /* Search results */
191
+ .search-result { padding: 16px; border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 12px; background: var(--bg-tertiary); }
192
+ .search-result .sr-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
193
+ .search-result .sr-title { font-weight: 600; font-size: 14px; color: var(--accent); }
194
+ .search-result .sr-score { font-size: 12px; color: var(--text-muted); }
195
+ .search-result .sr-content { font-size: 13px; color: var(--text-secondary); line-height: 1.6; }
196
+
197
+ /* Chart */
198
+ .chart-container { width: 100%; height: 200px; display: flex; align-items: flex-end; gap: 8px; padding: 10px 0; }
199
+ .chart-bar-wrapper { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; }
200
+ .chart-bar { width: 100%; background: var(--accent); border-radius: 4px 4px 0 0; min-height: 4px; transition: height 0.3s; }
201
+ .chart-label { font-size: 10px; color: var(--text-muted); }
202
+ .chart-value { font-size: 11px; color: var(--text-secondary); font-weight: 600; }
203
+
204
+ /* Quiz */
205
+ .quiz-question { margin-bottom: 24px; }
206
+ .quiz-question h4 { font-size: 15px; margin-bottom: 12px; }
207
+ .quiz-option {
208
+ display: block; width: 100%; text-align: left;
209
+ padding: 10px 14px; margin-bottom: 6px; border-radius: 6px;
210
+ background: var(--bg-primary); border: 1px solid var(--border);
211
+ color: var(--text-primary); cursor: pointer; font-size: 14px;
212
+ transition: all 0.15s;
213
+ }
214
+ .quiz-option:hover { border-color: var(--accent); }
215
+ .quiz-option.correct { border-color: var(--green); background: rgba(63,185,80,0.1); }
216
+ .quiz-option.wrong { border-color: var(--red); background: rgba(248,81,73,0.1); }
217
+ .quiz-option.disabled { pointer-events: none; opacity: 0.7; }
218
+ .quiz-explanation { margin-top: 8px; padding: 10px 14px; background: rgba(88,166,255,0.05); border-radius: 6px; font-size: 13px; color: var(--text-secondary); display: none; }
219
+ .quiz-explanation.show { display: block; }
220
+
221
+ /* Utilities */
222
+ .hidden { display: none !important; }
223
+ .mb-16 { margin-bottom: 16px; }
224
+ .flex { display: flex; }
225
+ .gap-8 { gap: 8px; }
226
+ .text-right { text-align: right; }
227
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 300px; }
228
+ .loading { text-align: center; padding: 40px; color: var(--text-muted); }
229
+
230
+ /* Scrollbar */
231
+ ::-webkit-scrollbar { width: 8px; }
232
+ ::-webkit-scrollbar-track { background: var(--bg-primary); }
233
+ ::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 4px; }
234
+ ::-webkit-scrollbar-thumb:hover { background: var(--border); }
235
+
236
+ /* Responsive */
237
+ @media (max-width: 768px) {
238
+ .sidebar { width: 60px; }
239
+ .sidebar .logo small, .sidebar .nav-label { display: none; }
240
+ .sidebar .logo h1 { font-size: 14px; }
241
+ .main { margin-left: 60px; padding: 16px; }
242
+ .stat-cards { grid-template-columns: repeat(2, 1fr); }
243
+ }
244
+ </style>
245
+ </head>
246
+ <body>
247
+ <div class="app">
248
+ <!-- Sidebar -->
249
+ <nav class="sidebar">
250
+ <div class="logo">
251
+ <h1>Vaultwise</h1>
252
+ <small>Knowledge Management</small>
253
+ </div>
254
+ <div class="nav-item active" data-page="overview" onclick="navigate('overview')">
255
+ <span class="nav-icon">&#9632;</span><span class="nav-label">Overview</span>
256
+ </div>
257
+ <div class="nav-item" data-page="documents" onclick="navigate('documents')">
258
+ <span class="nav-icon">&#9776;</span><span class="nav-label">Documents</span>
259
+ </div>
260
+ <div class="nav-item" data-page="ask" onclick="navigate('ask')">
261
+ <span class="nav-icon">&#10067;</span><span class="nav-label">Ask</span>
262
+ </div>
263
+ <div class="nav-item" data-page="search" onclick="navigate('search')">
264
+ <span class="nav-icon">&#128269;</span><span class="nav-label">Search</span>
265
+ </div>
266
+ <div class="nav-item" data-page="training" onclick="navigate('training')">
267
+ <span class="nav-icon">&#9998;</span><span class="nav-label">Training</span>
268
+ </div>
269
+ <div class="nav-item" data-page="analytics" onclick="navigate('analytics')">
270
+ <span class="nav-icon">&#128200;</span><span class="nav-label">Analytics</span>
271
+ </div>
272
+ </nav>
273
+
274
+ <!-- Main content -->
275
+ <div class="main">
276
+ <!-- OVERVIEW PAGE -->
277
+ <div id="page-overview" class="page">
278
+ <div class="page-header">
279
+ <h2>Overview</h2>
280
+ <p>Knowledge base health and activity at a glance</p>
281
+ </div>
282
+ <div class="stat-cards" id="overview-stats"></div>
283
+ <div class="panel">
284
+ <div class="panel-header"><h3>Queries Per Day (Last 7 Days)</h3></div>
285
+ <div class="panel-body"><div id="usage-chart" class="chart-container"></div></div>
286
+ </div>
287
+ <div class="panel">
288
+ <div class="panel-header"><h3>Recent Questions</h3></div>
289
+ <div class="panel-body"><table><thead><tr><th>Question</th><th>Confidence</th><th>Date</th></tr></thead><tbody id="recent-questions"></tbody></table></div>
290
+ </div>
291
+ </div>
292
+
293
+ <!-- DOCUMENTS PAGE -->
294
+ <div id="page-documents" class="page hidden">
295
+ <div class="page-header" style="display:flex;justify-content:space-between;align-items:flex-start;">
296
+ <div><h2>Documents</h2><p>Manage your knowledge base documents</p></div>
297
+ <button class="btn btn-primary" onclick="showUploadModal()">+ Upload Document</button>
298
+ </div>
299
+ <div class="panel">
300
+ <div class="panel-body"><table><thead><tr><th>Title</th><th>Type</th><th>Words</th><th>Source</th><th>Created</th><th></th></tr></thead><tbody id="doc-list"></tbody></table></div>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- DOCUMENT DETAIL (shown in-place) -->
305
+ <div id="page-doc-detail" class="page hidden">
306
+ <div class="page-header">
307
+ <p><a href="#" onclick="navigate('documents');return false;" style="color:var(--accent);text-decoration:none;">&larr; Back to Documents</a></p>
308
+ <h2 id="doc-detail-title"></h2>
309
+ </div>
310
+ <div class="panel">
311
+ <div class="panel-header"><h3>Content</h3></div>
312
+ <div class="panel-body"><pre id="doc-detail-content" style="white-space:pre-wrap;font-size:14px;line-height:1.7;color:var(--text-secondary);"></pre></div>
313
+ </div>
314
+ <div class="panel">
315
+ <div class="panel-header"><h3>Chunks</h3></div>
316
+ <div class="panel-body"><div id="doc-detail-chunks"></div></div>
317
+ </div>
318
+ </div>
319
+
320
+ <!-- ASK PAGE -->
321
+ <div id="page-ask" class="page hidden">
322
+ <div class="page-header">
323
+ <h2>Ask a Question</h2>
324
+ <p>Get AI-powered answers from your knowledge base</p>
325
+ </div>
326
+ <div class="chat-container">
327
+ <div class="chat-messages" id="chat-messages"></div>
328
+ <div class="chat-input-row">
329
+ <input type="text" id="ask-input" placeholder="Type your question..." onkeydown="if(event.key==='Enter')askQuestion()">
330
+ <button class="btn btn-primary" onclick="askQuestion()">Ask</button>
331
+ </div>
332
+ </div>
333
+ </div>
334
+
335
+ <!-- SEARCH PAGE -->
336
+ <div id="page-search" class="page hidden">
337
+ <div class="page-header">
338
+ <h2>Search</h2>
339
+ <p>Search across all documents using semantic similarity</p>
340
+ </div>
341
+ <div class="chat-input-row mb-16">
342
+ <input type="text" id="search-input" placeholder="Search the knowledge base..." onkeydown="if(event.key==='Enter')doSearch()">
343
+ <button class="btn btn-primary" onclick="doSearch()">Search</button>
344
+ </div>
345
+ <div id="search-results"></div>
346
+ </div>
347
+
348
+ <!-- TRAINING PAGE -->
349
+ <div id="page-training" class="page hidden">
350
+ <div class="page-header">
351
+ <h2>Training Materials</h2>
352
+ <p>Auto-generated articles and quizzes from your knowledge base</p>
353
+ </div>
354
+ <div class="panel">
355
+ <div class="panel-header">
356
+ <h3>Articles</h3>
357
+ <button class="btn btn-sm" onclick="showGenerateArticleModal()">+ Generate Article</button>
358
+ </div>
359
+ <div class="panel-body"><table><thead><tr><th>Title</th><th>Status</th><th>Created</th><th></th></tr></thead><tbody id="article-list"></tbody></table></div>
360
+ </div>
361
+ <div class="panel">
362
+ <div class="panel-header"><h3>Quizzes</h3></div>
363
+ <div class="panel-body"><table><thead><tr><th>Title</th><th>Created</th><th></th></tr></thead><tbody id="quiz-list"></tbody></table></div>
364
+ </div>
365
+ </div>
366
+
367
+ <!-- ARTICLE DETAIL -->
368
+ <div id="page-article-detail" class="page hidden">
369
+ <div class="page-header">
370
+ <p><a href="#" onclick="navigate('training');return false;" style="color:var(--accent);text-decoration:none;">&larr; Back to Training</a></p>
371
+ <h2 id="article-detail-title"></h2>
372
+ <div id="article-detail-status" style="margin-top:8px;"></div>
373
+ </div>
374
+ <div class="panel">
375
+ <div class="panel-body"><pre id="article-detail-content" style="white-space:pre-wrap;font-size:14px;line-height:1.7;color:var(--text-secondary);"></pre></div>
376
+ </div>
377
+ <div style="display:flex;gap:8px;">
378
+ <button class="btn btn-green btn-sm" onclick="updateArticleStatus(currentArticleId,'published')">Publish</button>
379
+ <button class="btn btn-sm" onclick="updateArticleStatus(currentArticleId,'archived')">Archive</button>
380
+ <button class="btn btn-sm" onclick="genQuizFromArticle(currentArticleId)">Generate Quiz</button>
381
+ </div>
382
+ </div>
383
+
384
+ <!-- QUIZ DETAIL -->
385
+ <div id="page-quiz-detail" class="page hidden">
386
+ <div class="page-header">
387
+ <p><a href="#" onclick="navigate('training');return false;" style="color:var(--accent);text-decoration:none;">&larr; Back to Training</a></p>
388
+ <h2 id="quiz-detail-title"></h2>
389
+ </div>
390
+ <div id="quiz-questions-container"></div>
391
+ <div id="quiz-score" class="panel hidden" style="margin-top:16px;">
392
+ <div class="panel-body" style="text-align:center;">
393
+ <h3 id="quiz-score-text"></h3>
394
+ </div>
395
+ </div>
396
+ </div>
397
+
398
+ <!-- ANALYTICS PAGE -->
399
+ <div id="page-analytics" class="page hidden">
400
+ <div class="page-header">
401
+ <h2>Analytics</h2>
402
+ <p>Knowledge gaps and usage trends</p>
403
+ </div>
404
+ <div class="panel">
405
+ <div class="panel-header"><h3>Knowledge Gaps</h3></div>
406
+ <div class="panel-body"><table><thead><tr><th>Topic</th><th>Frequency</th><th>Status</th><th>Last Asked</th><th>Actions</th></tr></thead><tbody id="gaps-list"></tbody></table></div>
407
+ </div>
408
+ <div class="panel">
409
+ <div class="panel-header"><h3>Usage Trends (Last 7 Days)</h3></div>
410
+ <div class="panel-body"><div id="analytics-chart" class="chart-container"></div></div>
411
+ </div>
412
+ </div>
413
+ </div>
414
+ </div>
415
+
416
+ <!-- Upload Modal -->
417
+ <div id="upload-modal" class="modal-overlay hidden" onclick="if(event.target===this)closeModal('upload-modal')">
418
+ <div class="modal">
419
+ <div class="modal-header"><h3>Upload Document</h3><button class="modal-close" onclick="closeModal('upload-modal')">&times;</button></div>
420
+ <div class="modal-body">
421
+ <div class="field"><label>Title</label><input type="text" id="upload-title" placeholder="Document title"></div>
422
+ <div class="field"><label>Content</label><textarea id="upload-content" placeholder="Paste document content here..." rows="10"></textarea></div>
423
+ <div class="field"><label>Type</label>
424
+ <select id="upload-type"><option value="text">Text</option><option value="markdown">Markdown</option><option value="python">Python</option></select>
425
+ </div>
426
+ </div>
427
+ <div class="modal-footer">
428
+ <button class="btn" onclick="closeModal('upload-modal')">Cancel</button>
429
+ <button class="btn btn-primary" onclick="uploadDocument()">Upload</button>
430
+ </div>
431
+ </div>
432
+ </div>
433
+
434
+ <!-- Generate Article Modal -->
435
+ <div id="gen-article-modal" class="modal-overlay hidden" onclick="if(event.target===this)closeModal('gen-article-modal')">
436
+ <div class="modal">
437
+ <div class="modal-header"><h3>Generate Article</h3><button class="modal-close" onclick="closeModal('gen-article-modal')">&times;</button></div>
438
+ <div class="modal-body">
439
+ <div class="field"><label>Select source documents:</label><div id="gen-article-docs"></div></div>
440
+ </div>
441
+ <div class="modal-footer">
442
+ <button class="btn" onclick="closeModal('gen-article-modal')">Cancel</button>
443
+ <button class="btn btn-primary" onclick="generateArticle()">Generate</button>
444
+ </div>
445
+ </div>
446
+ </div>
447
+
448
+ <script>
449
+ const API = window.location.origin;
450
+ let currentArticleId = null;
451
+ let quizState = {};
452
+
453
+ // ---- Navigation ----
454
+ function navigate(page) {
455
+ document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
456
+ document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
457
+ const el = document.getElementById('page-' + page);
458
+ if (el) el.classList.remove('hidden');
459
+ const nav = document.querySelector(`.nav-item[data-page="${page}"]`);
460
+ if (nav) nav.classList.add('active');
461
+ // Load data for the page
462
+ if (page === 'overview') loadOverview();
463
+ else if (page === 'documents') loadDocuments();
464
+ else if (page === 'training') loadTraining();
465
+ else if (page === 'analytics') loadAnalytics();
466
+ }
467
+
468
+ // ---- API helpers ----
469
+ async function api(path, options = {}) {
470
+ const url = API + path;
471
+ const res = await fetch(url, {
472
+ headers: { 'Content-Type': 'application/json', ...options.headers },
473
+ ...options,
474
+ });
475
+ if (!res.ok) {
476
+ const err = await res.json().catch(() => ({ detail: res.statusText }));
477
+ throw new Error(err.detail || 'Request failed');
478
+ }
479
+ return res.json();
480
+ }
481
+
482
+ function formatDate(iso) {
483
+ if (!iso) return '-';
484
+ const d = new Date(iso);
485
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
486
+ }
487
+
488
+ function shortDate(iso) {
489
+ if (!iso) return '-';
490
+ return iso.substring(5, 10);
491
+ }
492
+
493
+ function statusBadge(status) {
494
+ const cls = status === 'published' ? 'badge-green' : status === 'draft' ? 'badge-yellow' : status === 'open' ? 'badge-red' : 'badge-blue';
495
+ return `<span class="badge ${cls}">${status}</span>`;
496
+ }
497
+
498
+ function escapeHtml(text) {
499
+ const div = document.createElement('div');
500
+ div.textContent = text;
501
+ return div.innerHTML;
502
+ }
503
+
504
+ // ---- Overview ----
505
+ async function loadOverview() {
506
+ try {
507
+ const [stats, usage, questions] = await Promise.all([
508
+ api('/api/analytics/overview'),
509
+ api('/api/analytics/usage?days=7'),
510
+ api('/api/questions?limit=10'),
511
+ ]);
512
+ // Stat cards
513
+ document.getElementById('overview-stats').innerHTML = `
514
+ <div class="stat-card"><div class="label">Documents</div><div class="value blue">${stats.total_docs}</div></div>
515
+ <div class="stat-card"><div class="label">Questions Asked</div><div class="value green">${stats.total_questions}</div></div>
516
+ <div class="stat-card"><div class="label">Knowledge Gaps</div><div class="value yellow">${stats.gaps_count}</div></div>
517
+ <div class="stat-card"><div class="label">Avg Confidence</div><div class="value purple">${(stats.avg_confidence * 100).toFixed(0)}%</div></div>
518
+ `;
519
+ // Chart
520
+ renderChart('usage-chart', usage.queries_per_day);
521
+ // Recent questions
522
+ const tbody = document.getElementById('recent-questions');
523
+ if (questions.length === 0) {
524
+ tbody.innerHTML = '<tr><td colspan="3" style="text-align:center;color:var(--text-muted);">No questions yet</td></tr>';
525
+ } else {
526
+ tbody.innerHTML = questions.map(q => `
527
+ <tr>
528
+ <td>${escapeHtml(q.query)}</td>
529
+ <td>${(q.confidence * 100).toFixed(0)}%</td>
530
+ <td>${formatDate(q.created_at)}</td>
531
+ </tr>
532
+ `).join('');
533
+ }
534
+ } catch (e) { console.error('Failed to load overview:', e); }
535
+ }
536
+
537
+ function renderChart(containerId, data) {
538
+ const container = document.getElementById(containerId);
539
+ if (!data || data.length === 0) { container.innerHTML = '<p style="color:var(--text-muted)">No data</p>'; return; }
540
+ const maxVal = Math.max(...data.map(d => d.count), 1);
541
+ container.innerHTML = data.map(d => {
542
+ const height = Math.max((d.count / maxVal) * 160, 4);
543
+ return `<div class="chart-bar-wrapper">
544
+ <div class="chart-value">${d.count}</div>
545
+ <div class="chart-bar" style="height:${height}px"></div>
546
+ <div class="chart-label">${shortDate(d.date)}</div>
547
+ </div>`;
548
+ }).join('');
549
+ }
550
+
551
+ // ---- Documents ----
552
+ async function loadDocuments() {
553
+ try {
554
+ const data = await api('/api/documents?limit=100');
555
+ const tbody = document.getElementById('doc-list');
556
+ if (data.documents.length === 0) {
557
+ tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:var(--text-muted);">No documents yet</td></tr>';
558
+ } else {
559
+ tbody.innerHTML = data.documents.map(d => `
560
+ <tr style="cursor:pointer" onclick="viewDocument('${d.id}')">
561
+ <td style="color:var(--text-primary);font-weight:500;">${escapeHtml(d.title)}</td>
562
+ <td>${statusBadge(d.doc_type)}</td>
563
+ <td>${d.word_count.toLocaleString()}</td>
564
+ <td>${d.source}</td>
565
+ <td>${formatDate(d.created_at)}</td>
566
+ <td><button class="btn btn-red btn-sm" onclick="event.stopPropagation();deleteDoc('${d.id}')">Delete</button></td>
567
+ </tr>
568
+ `).join('');
569
+ }
570
+ } catch (e) { console.error('Failed to load documents:', e); }
571
+ }
572
+
573
+ async function viewDocument(id) {
574
+ try {
575
+ const doc = await api('/api/documents/' + id);
576
+ document.getElementById('doc-detail-title').textContent = doc.title;
577
+ document.getElementById('doc-detail-content').textContent = doc.content;
578
+ const chunksDiv = document.getElementById('doc-detail-chunks');
579
+ if (doc.chunks && doc.chunks.length > 0) {
580
+ chunksDiv.innerHTML = doc.chunks.map((c, i) =>
581
+ `<div class="search-result"><div class="sr-header"><span class="sr-title">Chunk ${i + 1}</span></div><div class="sr-content">${escapeHtml(c.content.substring(0, 300))}${c.content.length > 300 ? '...' : ''}</div></div>`
582
+ ).join('');
583
+ } else {
584
+ chunksDiv.innerHTML = '<p style="color:var(--text-muted)">No chunks</p>';
585
+ }
586
+ document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
587
+ document.getElementById('page-doc-detail').classList.remove('hidden');
588
+ } catch (e) { console.error('Failed to load document:', e); }
589
+ }
590
+
591
+ async function deleteDoc(id) {
592
+ if (!confirm('Delete this document?')) return;
593
+ try {
594
+ await api('/api/documents/' + id, { method: 'DELETE' });
595
+ loadDocuments();
596
+ } catch (e) { alert('Failed to delete: ' + e.message); }
597
+ }
598
+
599
+ function showUploadModal() { document.getElementById('upload-modal').classList.remove('hidden'); }
600
+ function closeModal(id) { document.getElementById(id).classList.add('hidden'); }
601
+
602
+ async function uploadDocument() {
603
+ const title = document.getElementById('upload-title').value.trim();
604
+ const content = document.getElementById('upload-content').value.trim();
605
+ const docType = document.getElementById('upload-type').value;
606
+ if (!title || !content) { alert('Title and content are required'); return; }
607
+ try {
608
+ await api('/api/documents/json', {
609
+ method: 'POST',
610
+ body: JSON.stringify({ title, content, doc_type: docType, source: 'upload' }),
611
+ });
612
+ closeModal('upload-modal');
613
+ document.getElementById('upload-title').value = '';
614
+ document.getElementById('upload-content').value = '';
615
+ loadDocuments();
616
+ } catch (e) { alert('Upload failed: ' + e.message); }
617
+ }
618
+
619
+ // ---- Ask ----
620
+ async function askQuestion() {
621
+ const input = document.getElementById('ask-input');
622
+ const query = input.value.trim();
623
+ if (!query) return;
624
+ input.value = '';
625
+ const messagesDiv = document.getElementById('chat-messages');
626
+ // Add user message
627
+ messagesDiv.innerHTML += `<div class="chat-msg user"><div class="msg-label">You</div><div class="msg-text">${escapeHtml(query)}</div></div>`;
628
+ messagesDiv.innerHTML += `<div class="chat-msg assistant" id="loading-msg"><div class="msg-label">Vaultwise</div><div class="msg-text" style="color:var(--text-muted)">Thinking...</div></div>`;
629
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
630
+ try {
631
+ const data = await api('/api/ask', { method: 'POST', body: JSON.stringify({ query }) });
632
+ const loadingEl = document.getElementById('loading-msg');
633
+ if (loadingEl) loadingEl.remove();
634
+ let sourcesHtml = '';
635
+ if (data.sources && data.sources.length > 0) {
636
+ sourcesHtml = '<div class="sources">Sources: ' + data.sources.map(s => `<a href="#" onclick="viewDocument(\'${s.doc_id}\');return false;">${escapeHtml(s.title)}</a>`).join(', ') + '</div>';
637
+ }
638
+ const confColor = data.confidence >= 0.7 ? 'var(--green)' : data.confidence >= 0.4 ? 'var(--yellow)' : 'var(--red)';
639
+ messagesDiv.innerHTML += `<div class="chat-msg assistant">
640
+ <div class="msg-label">Vaultwise</div>
641
+ <div class="msg-text">${escapeHtml(data.answer)}</div>
642
+ ${sourcesHtml}
643
+ <div class="confidence" style="color:${confColor}">Confidence: ${(data.confidence * 100).toFixed(0)}%</div>
644
+ </div>`;
645
+ } catch (e) {
646
+ const loadingEl = document.getElementById('loading-msg');
647
+ if (loadingEl) loadingEl.remove();
648
+ messagesDiv.innerHTML += `<div class="chat-msg assistant"><div class="msg-label">Error</div><div class="msg-text" style="color:var(--red)">${escapeHtml(e.message)}</div></div>`;
649
+ }
650
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
651
+ }
652
+
653
+ // ---- Search ----
654
+ async function doSearch() {
655
+ const query = document.getElementById('search-input').value.trim();
656
+ if (!query) return;
657
+ const resultsDiv = document.getElementById('search-results');
658
+ resultsDiv.innerHTML = '<div class="loading">Searching...</div>';
659
+ try {
660
+ const data = await api('/api/search', { method: 'POST', body: JSON.stringify({ query, limit: 10 }) });
661
+ if (data.results.length === 0) {
662
+ resultsDiv.innerHTML = '<p style="color:var(--text-muted);text-align:center;padding:20px;">No results found</p>';
663
+ } else {
664
+ resultsDiv.innerHTML = data.results.map(r => `
665
+ <div class="search-result">
666
+ <div class="sr-header">
667
+ <span class="sr-title" style="cursor:pointer" onclick="viewDocument('${r.doc_id}')">${escapeHtml(r.doc_title)}</span>
668
+ <span class="sr-score">Score: ${(r.score * 100).toFixed(1)}%</span>
669
+ </div>
670
+ <div class="sr-content">${escapeHtml(r.content.substring(0, 400))}${r.content.length > 400 ? '...' : ''}</div>
671
+ </div>
672
+ `).join('');
673
+ }
674
+ } catch (e) { resultsDiv.innerHTML = `<p style="color:var(--red)">${escapeHtml(e.message)}</p>`; }
675
+ }
676
+
677
+ // ---- Training ----
678
+ async function loadTraining() {
679
+ try {
680
+ const [articles, quizzes] = await Promise.all([api('/api/articles'), api('/api/quizzes')]);
681
+ const artBody = document.getElementById('article-list');
682
+ if (articles.length === 0) {
683
+ artBody.innerHTML = '<tr><td colspan="4" style="text-align:center;color:var(--text-muted);">No articles yet</td></tr>';
684
+ } else {
685
+ artBody.innerHTML = articles.map(a => `
686
+ <tr>
687
+ <td style="cursor:pointer;color:var(--accent);" onclick="viewArticle('${a.id}')">${escapeHtml(a.title)}</td>
688
+ <td>${statusBadge(a.status)}</td>
689
+ <td>${formatDate(a.created_at)}</td>
690
+ <td><button class="btn btn-sm" onclick="genQuizFromArticle('${a.id}')">Quiz</button></td>
691
+ </tr>
692
+ `).join('');
693
+ }
694
+ const quizBody = document.getElementById('quiz-list');
695
+ if (quizzes.length === 0) {
696
+ quizBody.innerHTML = '<tr><td colspan="3" style="text-align:center;color:var(--text-muted);">No quizzes yet</td></tr>';
697
+ } else {
698
+ quizBody.innerHTML = quizzes.map(q => `
699
+ <tr>
700
+ <td>${escapeHtml(q.title)}</td>
701
+ <td>${formatDate(q.created_at)}</td>
702
+ <td><button class="btn btn-primary btn-sm" onclick="takeQuiz('${q.id}')">Take Quiz</button></td>
703
+ </tr>
704
+ `).join('');
705
+ }
706
+ } catch (e) { console.error('Failed to load training:', e); }
707
+ }
708
+
709
+ async function viewArticle(id) {
710
+ try {
711
+ const art = await api('/api/articles/' + id);
712
+ currentArticleId = id;
713
+ document.getElementById('article-detail-title').textContent = art.title;
714
+ document.getElementById('article-detail-content').textContent = art.content;
715
+ document.getElementById('article-detail-status').innerHTML = statusBadge(art.status);
716
+ document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
717
+ document.getElementById('page-article-detail').classList.remove('hidden');
718
+ } catch (e) { console.error('Failed to load article:', e); }
719
+ }
720
+
721
+ async function updateArticleStatus(id, status) {
722
+ try {
723
+ await api('/api/articles/' + id, { method: 'PATCH', body: JSON.stringify({ status }) });
724
+ viewArticle(id);
725
+ } catch (e) { alert('Failed to update: ' + e.message); }
726
+ }
727
+
728
+ async function showGenerateArticleModal() {
729
+ try {
730
+ const data = await api('/api/documents?limit=100');
731
+ const container = document.getElementById('gen-article-docs');
732
+ container.innerHTML = data.documents.map(d =>
733
+ `<label style="display:block;padding:6px 0;cursor:pointer;font-size:14px;"><input type="checkbox" value="${d.id}" style="margin-right:8px;">${escapeHtml(d.title)}</label>`
734
+ ).join('');
735
+ document.getElementById('gen-article-modal').classList.remove('hidden');
736
+ } catch (e) { alert('Failed to load documents: ' + e.message); }
737
+ }
738
+
739
+ async function generateArticle() {
740
+ const checkboxes = document.querySelectorAll('#gen-article-docs input:checked');
741
+ const docIds = Array.from(checkboxes).map(c => c.value);
742
+ if (docIds.length === 0) { alert('Select at least one document'); return; }
743
+ try {
744
+ await api('/api/articles/generate', { method: 'POST', body: JSON.stringify({ doc_ids: docIds }) });
745
+ closeModal('gen-article-modal');
746
+ loadTraining();
747
+ } catch (e) { alert('Generation failed: ' + e.message); }
748
+ }
749
+
750
+ async function genQuizFromArticle(articleId) {
751
+ try {
752
+ await api('/api/quizzes/generate', { method: 'POST', body: JSON.stringify({ article_id: articleId }) });
753
+ navigate('training');
754
+ } catch (e) { alert('Quiz generation failed: ' + e.message); }
755
+ }
756
+
757
+ async function takeQuiz(quizId) {
758
+ try {
759
+ const quiz = await api('/api/quizzes/' + quizId);
760
+ const questions = JSON.parse(quiz.questions_json);
761
+ quizState = { total: questions.length, correct: 0, answered: 0 };
762
+ document.getElementById('quiz-detail-title').textContent = quiz.title;
763
+ document.getElementById('quiz-score').classList.add('hidden');
764
+ const container = document.getElementById('quiz-questions-container');
765
+ container.innerHTML = questions.map((q, qi) => {
766
+ const optionsHtml = q.options.map((o, oi) =>
767
+ `<button class="quiz-option" data-qi="${qi}" data-oi="${oi}" data-correct="${q.correct_index}" onclick="selectAnswer(this, ${qi}, ${oi}, ${q.correct_index})">${escapeHtml(o)}</button>`
768
+ ).join('');
769
+ return `<div class="quiz-question panel"><div class="panel-body">
770
+ <h4>Q${qi + 1}. ${escapeHtml(q.question)}</h4>
771
+ ${optionsHtml}
772
+ <div class="quiz-explanation" id="explanation-${qi}">${escapeHtml(q.explanation)}</div>
773
+ </div></div>`;
774
+ }).join('');
775
+ document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
776
+ document.getElementById('page-quiz-detail').classList.remove('hidden');
777
+ } catch (e) { console.error('Failed to load quiz:', e); }
778
+ }
779
+
780
+ function selectAnswer(btn, qi, oi, correctIdx) {
781
+ // Disable all options for this question
782
+ document.querySelectorAll(`[data-qi="${qi}"]`).forEach(b => {
783
+ b.classList.add('disabled');
784
+ if (parseInt(b.dataset.oi) === correctIdx) b.classList.add('correct');
785
+ });
786
+ if (oi !== correctIdx) btn.classList.add('wrong');
787
+ else quizState.correct++;
788
+ quizState.answered++;
789
+ document.getElementById('explanation-' + qi).classList.add('show');
790
+ // Check if quiz is complete
791
+ if (quizState.answered === quizState.total) {
792
+ const scoreDiv = document.getElementById('quiz-score');
793
+ scoreDiv.classList.remove('hidden');
794
+ document.getElementById('quiz-score-text').textContent = `Score: ${quizState.correct} / ${quizState.total} (${Math.round(quizState.correct/quizState.total*100)}%)`;
795
+ }
796
+ }
797
+
798
+ // ---- Analytics ----
799
+ async function loadAnalytics() {
800
+ try {
801
+ const [gaps, usage] = await Promise.all([api('/api/analytics/gaps'), api('/api/analytics/usage?days=7')]);
802
+ const gapsBody = document.getElementById('gaps-list');
803
+ if (gaps.length === 0) {
804
+ gapsBody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-muted);">No knowledge gaps detected</td></tr>';
805
+ } else {
806
+ gapsBody.innerHTML = gaps.map(g => `
807
+ <tr>
808
+ <td style="color:var(--text-primary);font-weight:500;">${escapeHtml(g.topic)}</td>
809
+ <td>${g.frequency}</td>
810
+ <td>${statusBadge(g.status)}</td>
811
+ <td>${formatDate(g.last_asked)}</td>
812
+ <td>
813
+ <button class="btn btn-green btn-sm" onclick="updateGap('${g.id}','addressed')">Addressed</button>
814
+ <button class="btn btn-sm" onclick="updateGap('${g.id}','dismissed')" style="margin-left:4px;">Dismiss</button>
815
+ </td>
816
+ </tr>
817
+ `).join('');
818
+ }
819
+ renderChart('analytics-chart', usage.queries_per_day);
820
+ } catch (e) { console.error('Failed to load analytics:', e); }
821
+ }
822
+
823
+ async function updateGap(id, status) {
824
+ try {
825
+ await api('/api/analytics/gaps/' + id + '?status=' + status, { method: 'PATCH' });
826
+ loadAnalytics();
827
+ } catch (e) { alert('Failed to update: ' + e.message); }
828
+ }
829
+
830
+ // ---- Init ----
831
+ document.addEventListener('DOMContentLoaded', () => { loadOverview(); });
832
+ </script>
833
+ </body>
834
+ </html>