Spaces:
Sleeping
Sleeping
Upload index.html with huggingface_hub
Browse files- index.html +834 -19
index.html
CHANGED
|
@@ -1,19 +1,834 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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">■</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">☰</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">❓</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">🔍</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">✎</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">📈</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;">← 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;">← 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;">← 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')">×</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')">×</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>
|