hoolix 0.0.1-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +337 -0
- package/STABILITY.md +109 -0
- package/bin/hoolix.js +50 -0
- package/bin/mcp-portal.js +12 -0
- package/bin/postinstall.js +61 -0
- package/dist/app/contracts.d.ts +105 -0
- package/dist/app/contracts.d.ts.map +1 -0
- package/dist/app/contracts.js +2 -0
- package/dist/app/contracts.js.map +1 -0
- package/dist/app/events.d.ts +13 -0
- package/dist/app/events.d.ts.map +1 -0
- package/dist/app/events.js +13 -0
- package/dist/app/events.js.map +1 -0
- package/dist/app/services/analytics.d.ts +42 -0
- package/dist/app/services/analytics.d.ts.map +1 -0
- package/dist/app/services/analytics.js +106 -0
- package/dist/app/services/analytics.js.map +1 -0
- package/dist/app/services/catalog.d.ts +14 -0
- package/dist/app/services/catalog.d.ts.map +1 -0
- package/dist/app/services/catalog.js +26 -0
- package/dist/app/services/catalog.js.map +1 -0
- package/dist/app/services/credentials.d.ts +42 -0
- package/dist/app/services/credentials.d.ts.map +1 -0
- package/dist/app/services/credentials.js +143 -0
- package/dist/app/services/credentials.js.map +1 -0
- package/dist/app/services/servers.d.ts +15 -0
- package/dist/app/services/servers.d.ts.map +1 -0
- package/dist/app/services/servers.js +445 -0
- package/dist/app/services/servers.js.map +1 -0
- package/dist/catalog/community.d.ts +19 -0
- package/dist/catalog/community.d.ts.map +1 -0
- package/dist/catalog/community.js +53 -0
- package/dist/catalog/community.js.map +1 -0
- package/dist/catalog/templates.d.ts +436 -0
- package/dist/catalog/templates.d.ts.map +1 -0
- package/dist/catalog/templates.js +489 -0
- package/dist/catalog/templates.js.map +1 -0
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +122 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/bundle.d.ts +11 -0
- package/dist/commands/bundle.d.ts.map +1 -0
- package/dist/commands/bundle.js +299 -0
- package/dist/commands/bundle.js.map +1 -0
- package/dist/commands/clients.d.ts +6 -0
- package/dist/commands/clients.d.ts.map +1 -0
- package/dist/commands/clients.js +242 -0
- package/dist/commands/clients.js.map +1 -0
- package/dist/commands/completion.d.ts +18 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +495 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/connect.d.ts +10 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +463 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +607 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/delete.d.ts +2 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +44 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +259 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +93 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/gui.d.ts +2 -0
- package/dist/commands/gui.d.ts.map +1 -0
- package/dist/commands/gui.js +19 -0
- package/dist/commands/gui.js.map +1 -0
- package/dist/commands/import.d.ts +2 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +102 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/info.d.ts +2 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +151 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +90 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/reindex.d.ts +2 -0
- package/dist/commands/reindex.d.ts.map +1 -0
- package/dist/commands/reindex.js +186 -0
- package/dist/commands/reindex.js.map +1 -0
- package/dist/commands/rotate.d.ts +2 -0
- package/dist/commands/rotate.d.ts.map +1 -0
- package/dist/commands/rotate.js +67 -0
- package/dist/commands/rotate.js.map +1 -0
- package/dist/commands/secrets.d.ts +10 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +293 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +234 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stats.d.ts +2 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +220 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +24 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/templates.d.ts +2 -0
- package/dist/commands/templates.d.ts.map +1 -0
- package/dist/commands/templates.js +168 -0
- package/dist/commands/templates.js.map +1 -0
- package/dist/commands/trial.d.ts +2 -0
- package/dist/commands/trial.d.ts.map +1 -0
- package/dist/commands/trial.js +61 -0
- package/dist/commands/trial.js.map +1 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +114 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +104 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +301 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/core/config.d.ts +25 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +54 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/errors.d.ts +25 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +53 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/logger.d.ts +3 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +12 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/paths.d.ts +15 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +51 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/registry.d.ts +474 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +186 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/updater.d.ts +16 -0
- package/dist/core/updater.d.ts.map +1 -0
- package/dist/core/updater.js +317 -0
- package/dist/core/updater.js.map +1 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +3 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion/chunker.d.ts +13 -0
- package/dist/ingestion/chunker.d.ts.map +1 -0
- package/dist/ingestion/chunker.js +107 -0
- package/dist/ingestion/chunker.js.map +1 -0
- package/dist/ingestion/cleaners.d.ts +10 -0
- package/dist/ingestion/cleaners.d.ts.map +1 -0
- package/dist/ingestion/cleaners.js +61 -0
- package/dist/ingestion/cleaners.js.map +1 -0
- package/dist/ingestion/detectors.d.ts +5 -0
- package/dist/ingestion/detectors.d.ts.map +1 -0
- package/dist/ingestion/detectors.js +25 -0
- package/dist/ingestion/detectors.js.map +1 -0
- package/dist/ingestion/fetchers.d.ts +38 -0
- package/dist/ingestion/fetchers.d.ts.map +1 -0
- package/dist/ingestion/fetchers.js +296 -0
- package/dist/ingestion/fetchers.js.map +1 -0
- package/dist/ingestion/github.d.ts +60 -0
- package/dist/ingestion/github.d.ts.map +1 -0
- package/dist/ingestion/github.js +314 -0
- package/dist/ingestion/github.js.map +1 -0
- package/dist/ingestion/pipeline.d.ts +3 -0
- package/dist/ingestion/pipeline.d.ts.map +1 -0
- package/dist/ingestion/pipeline.js +160 -0
- package/dist/ingestion/pipeline.js.map +1 -0
- package/dist/ingestion/types.d.ts +51 -0
- package/dist/ingestion/types.d.ts.map +1 -0
- package/dist/ingestion/types.js +2 -0
- package/dist/ingestion/types.js.map +1 -0
- package/dist/lib/auth.d.ts +2 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +6 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/embedding.d.ts +10 -0
- package/dist/lib/embedding.d.ts.map +1 -0
- package/dist/lib/embedding.js +21 -0
- package/dist/lib/embedding.js.map +1 -0
- package/dist/mcp/host.d.ts +16 -0
- package/dist/mcp/host.d.ts.map +1 -0
- package/dist/mcp/host.js +307 -0
- package/dist/mcp/host.js.map +1 -0
- package/dist/mcp/proxy-host.d.ts +25 -0
- package/dist/mcp/proxy-host.d.ts.map +1 -0
- package/dist/mcp/proxy-host.js +393 -0
- package/dist/mcp/proxy-host.js.map +1 -0
- package/dist/mcp/stdio-host.d.ts +19 -0
- package/dist/mcp/stdio-host.d.ts.map +1 -0
- package/dist/mcp/stdio-host.js +175 -0
- package/dist/mcp/stdio-host.js.map +1 -0
- package/dist/process/manager.d.ts +74 -0
- package/dist/process/manager.d.ts.map +1 -0
- package/dist/process/manager.js +322 -0
- package/dist/process/manager.js.map +1 -0
- package/dist/rag/models.d.ts +30 -0
- package/dist/rag/models.d.ts.map +1 -0
- package/dist/rag/models.js +30 -0
- package/dist/rag/models.js.map +1 -0
- package/dist/rag/store.d.ts +63 -0
- package/dist/rag/store.d.ts.map +1 -0
- package/dist/rag/store.js +505 -0
- package/dist/rag/store.js.map +1 -0
- package/dist/rag/types.d.ts +56 -0
- package/dist/rag/types.d.ts.map +1 -0
- package/dist/rag/types.js +2 -0
- package/dist/rag/types.js.map +1 -0
- package/dist/sources/plugins.d.ts +25 -0
- package/dist/sources/plugins.d.ts.map +1 -0
- package/dist/sources/plugins.js +55 -0
- package/dist/sources/plugins.js.map +1 -0
- package/dist/sources/registry.d.ts +19 -0
- package/dist/sources/registry.d.ts.map +1 -0
- package/dist/sources/registry.js +183 -0
- package/dist/sources/registry.js.map +1 -0
- package/dist/sources/types.d.ts +361 -0
- package/dist/sources/types.d.ts.map +1 -0
- package/dist/sources/types.js +59 -0
- package/dist/sources/types.js.map +1 -0
- package/dist/tui/index.d.ts +22 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +711 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/ui/format.d.ts +27 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +80 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/help.d.ts +2 -0
- package/dist/ui/help.d.ts.map +1 -0
- package/dist/ui/help.js +106 -0
- package/dist/ui/help.js.map +1 -0
- package/dist/web/assets.d.ts +2 -0
- package/dist/web/assets.d.ts.map +1 -0
- package/dist/web/assets.js +659 -0
- package/dist/web/assets.js.map +1 -0
- package/dist/web/server.d.ts +9 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +349 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
export function buildDashboardHtml(_initialToken) {
|
|
2
|
+
// Self-contained local dashboard; CSS and client JS are bundled into this module.
|
|
3
|
+
// Functional dashboard: list, create, templates, trial, stats, start/stop, reindex, verify, playground, delete, logs tail.
|
|
4
|
+
return `<!DOCTYPE html>
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="UTF-8">
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
9
|
+
<title>Hoolix • Local Dashboard</title>
|
|
10
|
+
<style>
|
|
11
|
+
:root { --accent: #7dd3fc; color-scheme: dark; }
|
|
12
|
+
* { box-sizing: border-box; }
|
|
13
|
+
body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
14
|
+
button, input, select { font: inherit; }
|
|
15
|
+
button { border: 0; cursor: pointer; }
|
|
16
|
+
a { color: inherit; text-decoration: none; }
|
|
17
|
+
.font-display { font-family: "Segoe UI", Inter, ui-sans-serif, system-ui, sans-serif; }
|
|
18
|
+
.card { transition: all 0.1s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
19
|
+
.card:hover { transform: translateY(-1px); box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1); }
|
|
20
|
+
.status-dot { width: 10px; height: 10px; border-radius: 9999px; }
|
|
21
|
+
.monospace { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
22
|
+
.result-card { border-left: 3px solid #7dd3fc; }
|
|
23
|
+
.nav-active { background-color: #27272a; color: white; border-radius: 6px; }
|
|
24
|
+
.section-title { letter-spacing: -.025em; }
|
|
25
|
+
pre { white-space: pre-wrap; }
|
|
26
|
+
.hidden { display: none !important; }
|
|
27
|
+
.fixed { position: fixed; } .inset-0 { inset: 0; } .z-50 { z-index: 50; }
|
|
28
|
+
.flex { display: flex; } .grid { display: grid; } .block { display: block; }
|
|
29
|
+
.flex-1 { flex: 1 1 0%; } .flex-col { flex-direction: column; } .flex-wrap { flex-wrap: wrap; }
|
|
30
|
+
.items-center { align-items: center; } .items-start { align-items: flex-start; } .justify-center { justify-content: center; } .justify-between { justify-content: space-between; }
|
|
31
|
+
.overflow-hidden { overflow: hidden; } .overflow-auto { overflow: auto; }
|
|
32
|
+
.h-screen { height: 100vh; } .h-14 { height: 3.5rem; } .w-64 { width: 16rem; } .w-9 { width: 2.25rem; } .h-9 { height: 2.25rem; } .w-4 { width: 1rem; }
|
|
33
|
+
.w-full { width: 100%; } .max-w-lg { max-width: 32rem; } .max-w-3xl { max-width: 48rem; } .max-w-sm { max-width: 24rem; } .mx-4 { margin-left: 1rem; margin-right: 1rem; } .mx-auto { margin-left: auto; margin-right: auto; }
|
|
34
|
+
.mt-1 { margin-top: .25rem; } .mt-3 { margin-top: .75rem; } .mt-4 { margin-top: 1rem; } .mt-12 { margin-top: 3rem; } .mb-1 { margin-bottom: .25rem; } .mb-2 { margin-bottom: .5rem; } .mb-3 { margin-bottom: .75rem; } .mb-4 { margin-bottom: 1rem; } .ml-auto { margin-left: auto; } .-mt-1 { margin-top: -.25rem; }
|
|
35
|
+
.p-3 { padding: .75rem; } .p-4 { padding: 1rem; } .p-5 { padding: 1.25rem; } .p-6 { padding: 1.5rem; } .px-1\\.5 { padding-left: .375rem; padding-right: .375rem; } .px-3 { padding-left: .75rem; padding-right: .75rem; } .px-4 { padding-left: 1rem; padding-right: 1rem; } .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; } .py-px { padding-top: 1px; padding-bottom: 1px; } .py-1 { padding-top: .25rem; padding-bottom: .25rem; } .py-1\\.5 { padding-top: .375rem; padding-bottom: .375rem; } .py-2 { padding-top: .5rem; padding-bottom: .5rem; } .py-2\\.5 { padding-top: .625rem; padding-bottom: .625rem; } .py-3 { padding-top: .75rem; padding-bottom: .75rem; } .py-4 { padding-top: 1rem; padding-bottom: 1rem; } .pt-1 { padding-top: .25rem; } .py-12 { padding-top: 3rem; padding-bottom: 3rem; }
|
|
36
|
+
.gap-2 { gap: .5rem; } .gap-3 { gap: .75rem; } .gap-x-1\\.5 { column-gap: .375rem; } .gap-x-2 { column-gap: .5rem; } .gap-x-3 { column-gap: .75rem; } .gap-x-4 { column-gap: 1rem; } .space-y-1 > * + * { margin-top: .25rem; } .space-y-3 > * + * { margin-top: .75rem; } .space-y-4 > * + * { margin-top: 1rem; }
|
|
37
|
+
.rounded { border-radius: .25rem; } .rounded-lg { border-radius: .5rem; } .rounded-xl { border-radius: .75rem; } .rounded-2xl { border-radius: 1rem; } .rounded-3xl { border-radius: 1.5rem; } .rounded-full { border-radius: 9999px; }
|
|
38
|
+
.border { border: 1px solid; } .border-r { border-right: 1px solid; } .border-b { border-bottom: 1px solid; } .border-t { border-top: 1px solid; }
|
|
39
|
+
.border-zinc-700 { border-color: #3f3f46; } .border-zinc-800 { border-color: #27272a; }
|
|
40
|
+
.bg-black\\/60 { background-color: rgb(0 0 0 / .6); } .bg-zinc-950 { background-color: #09090b; } .bg-zinc-900 { background-color: #18181b; } .bg-zinc-800 { background-color: #27272a; } .bg-zinc-700 { background-color: #3f3f46; } .bg-white { background-color: #fff; } .bg-sky-400 { background-color: #38bdf8; } .bg-sky-300 { background-color: #7dd3fc; } .bg-emerald-400 { background-color: #34d399; } .bg-zinc-500 { background-color: #71717a; } .bg-red-900\\/40 { background-color: rgb(127 29 29 / .4); } .bg-red-900\\/60 { background-color: rgb(127 29 29 / .6); } .bg-emerald-900\\/60 { background-color: rgb(6 78 59 / .6); }
|
|
41
|
+
.text-black { color: #000; } .text-white { color: #fff; } .text-zinc-950 { color: #09090b; } .text-zinc-900 { color: #18181b; } .text-zinc-500 { color: #71717a; } .text-zinc-400 { color: #a1a1aa; } .text-zinc-300 { color: #d4d4d8; } .text-zinc-200 { color: #e4e4e7; } .text-sky-400 { color: #38bdf8; } .text-emerald-400 { color: #34d399; } .text-red-400 { color: #f87171; }
|
|
42
|
+
.text-center { text-align: center; } .text-\\[10px\\] { font-size: 10px; } .text-xs { font-size: .75rem; line-height: 1rem; } .text-sm { font-size: .875rem; line-height: 1.25rem; } .text-base { font-size: 1rem; } .text-lg { font-size: 1.125rem; } .text-xl { font-size: 1.25rem; } .text-2xl { font-size: 1.5rem; } .text-4xl { font-size: 2.25rem; }
|
|
43
|
+
.font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; } .font-medium { font-weight: 500; } .font-semibold { font-weight: 600; } .tracking-tight { letter-spacing: 0; } .tracking-tighter { letter-spacing: 0; } .tracking-widest { letter-spacing: .1em; } .uppercase { text-transform: uppercase; }
|
|
44
|
+
.shadow-xl { box-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1); } .backdrop-blur { backdrop-filter: blur(8px); }
|
|
45
|
+
.transition { transition-property: color, background-color, border-color, transform, opacity; transition-duration: 150ms; }
|
|
46
|
+
.hover\\:bg-zinc-800:hover { background-color: #27272a; } .hover\\:bg-zinc-700:hover { background-color: #3f3f46; } .hover\\:bg-zinc-100:hover { background-color: #f4f4f5; } .hover\\:bg-sky-300:hover { background-color: #7dd3fc; } .hover\\:bg-red-900:hover { background-color: #7f1d1d; } .hover\\:bg-emerald-900:hover { background-color: #064e3b; } .hover\\:text-white:hover { color: #fff; } .hover\\:underline:hover { text-decoration: underline; } .focus\\:border-sky-400:focus { border-color: #38bdf8; outline: none; }
|
|
47
|
+
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
48
|
+
.line-clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
|
49
|
+
.fa-solid::before { display: inline-block; width: 1em; text-align: center; font-style: normal; }
|
|
50
|
+
.fa-server::before { content: "H"; font-weight: 700; } .fa-list::before { content: "☰"; } .fa-plus::before { content: "+"; } .fa-search::before { content: "⌕"; } .fa-sync::before { content: "↻"; } .fa-times::before { content: "×"; }
|
|
51
|
+
@media (min-width: 768px) { .md\\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } .md\\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } .md\\:col-span-2 { grid-column: span 2 / span 2; } }
|
|
52
|
+
@media (min-width: 1024px) { .lg\\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body class="bg-zinc-950 text-zinc-200">
|
|
56
|
+
<div class="flex h-screen overflow-hidden">
|
|
57
|
+
<!-- Sidebar -->
|
|
58
|
+
<div class="w-64 border-r border-zinc-800 bg-zinc-900 flex flex-col">
|
|
59
|
+
<div class="p-6 border-b border-zinc-800">
|
|
60
|
+
<div class="flex items-center gap-x-3">
|
|
61
|
+
<div class="w-9 h-9 rounded-xl bg-sky-400 flex items-center justify-center">
|
|
62
|
+
<i class="fa-solid fa-server text-zinc-950 text-xl"></i>
|
|
63
|
+
</div>
|
|
64
|
+
<div>
|
|
65
|
+
<div class="font-display text-2xl font-semibold tracking-tighter">Hoolix</div>
|
|
66
|
+
<div class="text-[10px] text-zinc-500 -mt-1">LOCAL <span class="text-emerald-400">MCP DASHBOARD</span></div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="p-3 flex-1">
|
|
72
|
+
<div class="space-y-1 text-sm">
|
|
73
|
+
<a href="#" onclick="showView('servers'); return false;" class="nav-link nav-active flex items-center gap-x-3 px-3 py-2 text-sm hover:bg-zinc-800 rounded-lg" data-view="servers">
|
|
74
|
+
<i class="fa-solid fa-list w-4"></i> <span>Servers</span>
|
|
75
|
+
</a>
|
|
76
|
+
<a href="#" onclick="showCreateModal(); return false;" class="nav-link flex items-center gap-x-3 px-3 py-2 text-sm hover:bg-zinc-800 rounded-lg">
|
|
77
|
+
<i class="fa-solid fa-plus w-4"></i> <span>Create Server</span>
|
|
78
|
+
</a>
|
|
79
|
+
<a href="#" onclick="createTrial(); return false;" class="nav-link flex items-center gap-x-3 px-3 py-2 text-sm hover:bg-zinc-800 rounded-lg">
|
|
80
|
+
<i class="fa-solid fa-plus w-4"></i> <span>Trial Server</span>
|
|
81
|
+
</a>
|
|
82
|
+
<a href="#" onclick="showView('templates'); return false;" class="nav-link flex items-center gap-x-3 px-3 py-2 text-sm hover:bg-zinc-800 rounded-lg" data-view="templates">
|
|
83
|
+
<i class="fa-solid fa-list w-4"></i> <span>Templates</span>
|
|
84
|
+
</a>
|
|
85
|
+
<a href="#" onclick="showView('playground'); return false;" class="nav-link flex items-center gap-x-3 px-3 py-2 text-sm hover:bg-zinc-800 rounded-lg" data-view="playground">
|
|
86
|
+
<i class="fa-solid fa-search w-4"></i> <span>Playground</span>
|
|
87
|
+
</a>
|
|
88
|
+
<a href="#" onclick="refreshAll(); return false;" class="flex items-center gap-x-3 px-3 py-2 text-sm hover:bg-zinc-800 rounded-lg text-zinc-400">
|
|
89
|
+
<i class="fa-solid fa-sync w-4"></i> <span>Refresh</span>
|
|
90
|
+
</a>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="p-4 border-t border-zinc-800 text-xs text-zinc-500">
|
|
95
|
+
<div class="flex items-center justify-between">
|
|
96
|
+
<div>Local only</div>
|
|
97
|
+
<div id="token-hint" class="font-mono text-[10px] bg-zinc-800 px-1.5 py-px rounded cursor-pointer" onclick="copyToken()">token</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="mt-1 text-[10px]">Port: <span id="port-display"></span></div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- Main Content -->
|
|
104
|
+
<div class="flex-1 flex flex-col overflow-hidden">
|
|
105
|
+
<!-- Top bar -->
|
|
106
|
+
<div class="h-14 border-b border-zinc-800 bg-zinc-900 px-6 flex items-center justify-between">
|
|
107
|
+
<div class="flex items-center gap-x-3">
|
|
108
|
+
<div id="view-title" class="font-semibold text-lg">Servers</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="flex items-center gap-x-2 text-sm">
|
|
111
|
+
<div class="px-3 py-1 bg-zinc-800 rounded-full text-xs flex items-center gap-x-1.5">
|
|
112
|
+
<div class="w-2 h-2 bg-emerald-400 rounded-full animate-pulse"></div>
|
|
113
|
+
<span>Connected</span>
|
|
114
|
+
</div>
|
|
115
|
+
<button onclick="refreshAll()" class="px-3 py-1.5 text-xs rounded-lg bg-zinc-800 hover:bg-zinc-700 flex items-center gap-x-2">
|
|
116
|
+
<i class="fa-solid fa-sync fa-sm"></i> <span>Refresh</span>
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<!-- Content area -->
|
|
122
|
+
<div class="flex-1 overflow-auto p-6" id="main-content">
|
|
123
|
+
<!-- Servers view (default) -->
|
|
124
|
+
<div id="view-servers">
|
|
125
|
+
<div class="flex items-center justify-between mb-4">
|
|
126
|
+
<div>
|
|
127
|
+
<div class="text-2xl font-semibold tracking-tight">Your MCP Servers</div>
|
|
128
|
+
<div class="text-zinc-500 text-sm">Manage sources, templates, transports, stats, and grounded MCP tools</div>
|
|
129
|
+
</div>
|
|
130
|
+
<button onclick="showCreateModal()" class="px-4 py-2 bg-white text-zinc-900 rounded-xl text-sm font-medium flex items-center gap-x-2 hover:bg-zinc-100">
|
|
131
|
+
<i class="fa-solid fa-plus"></i>
|
|
132
|
+
<span>New Server</span>
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div id="servers-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
137
|
+
<!-- Populated by JS -->
|
|
138
|
+
</div>
|
|
139
|
+
<div id="servers-empty" class="hidden text-center py-12">
|
|
140
|
+
<i class="fa-solid fa-server text-4xl text-zinc-700 mb-3"></i>
|
|
141
|
+
<div class="text-zinc-400">No servers yet. Create one, launch a trial, or start from a template.</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- Playground view -->
|
|
146
|
+
<div id="view-templates" class="hidden">
|
|
147
|
+
<div class="flex items-center justify-between mb-4">
|
|
148
|
+
<div>
|
|
149
|
+
<div class="text-2xl font-semibold tracking-tight">Official Templates</div>
|
|
150
|
+
<div class="text-zinc-500 text-sm">Start from curated server definitions with known-good source presets</div>
|
|
151
|
+
</div>
|
|
152
|
+
<button onclick="loadTemplates()" class="px-3 py-1.5 text-xs rounded-lg bg-zinc-800 hover:bg-zinc-700">Refresh</button>
|
|
153
|
+
</div>
|
|
154
|
+
<div id="templates-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<!-- Playground view -->
|
|
158
|
+
<div id="view-playground" class="hidden">
|
|
159
|
+
<div class="max-w-3xl">
|
|
160
|
+
<div class="mb-4">
|
|
161
|
+
<div class="text-2xl font-semibold tracking-tight">RAG Playground</div>
|
|
162
|
+
<div class="text-sm text-zinc-400">Test searches exactly as your agents will see them. Grounding URLs and source labels are always included.</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div class="bg-zinc-900 border border-zinc-800 rounded-2xl p-5">
|
|
166
|
+
<div class="grid grid-cols-1 md:grid-cols-5 gap-3 mb-4">
|
|
167
|
+
<div class="md:col-span-2">
|
|
168
|
+
<label class="text-xs uppercase tracking-widest text-zinc-500">Server</label>
|
|
169
|
+
<select id="playground-slug" class="w-full bg-zinc-950 border border-zinc-700 rounded-lg px-3 py-2 text-sm mt-1"></select>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="md:col-span-2">
|
|
172
|
+
<label class="text-xs uppercase tracking-widest text-zinc-500">Query</label>
|
|
173
|
+
<input id="playground-query" value="authentication" class="w-full bg-zinc-950 border border-zinc-700 rounded-lg px-3 py-2 text-sm mt-1" placeholder="Search query...">
|
|
174
|
+
</div>
|
|
175
|
+
<div>
|
|
176
|
+
<label class="text-xs uppercase tracking-widest text-zinc-500">Mode</label>
|
|
177
|
+
<select id="playground-mode" class="w-full bg-zinc-950 border border-zinc-700 rounded-lg px-3 py-2 text-sm mt-1">
|
|
178
|
+
<option value="hybrid">hybrid</option>
|
|
179
|
+
<option value="keyword">keyword</option>
|
|
180
|
+
<option value="semantic">semantic</option>
|
|
181
|
+
</select>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="flex gap-2">
|
|
186
|
+
<button onclick="runPlaygroundSearch()" class="flex-1 bg-sky-400 hover:bg-sky-300 transition text-zinc-950 font-medium rounded-xl py-2 text-sm flex items-center justify-center gap-x-2">
|
|
187
|
+
<i class="fa-solid fa-search"></i> <span>Search</span>
|
|
188
|
+
</button>
|
|
189
|
+
<button onclick="runPlaygroundSearch(true)" class="px-4 bg-zinc-800 hover:bg-zinc-700 rounded-xl text-sm">RRF</button>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div id="playground-results" class="mt-4 space-y-3 text-sm"></div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Create Modal -->
|
|
201
|
+
<div id="create-modal" onclick="if (event.target.id === 'create-modal') hideCreateModal()" class="hidden fixed inset-0 bg-black/60 backdrop-blur flex items-center justify-center z-50">
|
|
202
|
+
<div onclick="event.stopImmediatePropagation()" class="bg-zinc-900 border border-zinc-700 rounded-3xl w-full max-w-lg mx-4 overflow-hidden">
|
|
203
|
+
<div class="px-6 py-4 border-b border-zinc-700 flex items-center justify-between">
|
|
204
|
+
<div class="font-semibold">Create MCP Server</div>
|
|
205
|
+
<button onclick="hideCreateModal()" class="text-zinc-400 hover:text-white"><i class="fa-solid fa-times"></i></button>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="p-6 space-y-4">
|
|
208
|
+
<div>
|
|
209
|
+
<label class="text-xs font-medium text-zinc-400">Name</label>
|
|
210
|
+
<input id="create-name" class="mt-1 w-full bg-zinc-950 border border-zinc-700 focus:border-sky-400 rounded-xl px-4 py-2.5 text-sm" placeholder="My Project Docs" value="Test Docs">
|
|
211
|
+
</div>
|
|
212
|
+
<div>
|
|
213
|
+
<label class="text-xs font-medium text-zinc-400">Source URL</label>
|
|
214
|
+
<input id="create-url" class="mt-1 w-full bg-zinc-950 border border-zinc-700 focus:border-sky-400 rounded-xl px-4 py-2.5 text-sm monospace" placeholder="https://example.com/llms.txt or https://github.com/owner/repo" value="https://raw.githubusercontent.com/modelcontextprotocol/servers/main/README.md">
|
|
215
|
+
<div class="text-[10px] text-zinc-500 mt-1">Supports llms.txt, llms-full.txt, GitHub repos, and regular docs pages. Use the CLI for repeated --source, private headers, cookies, and custom plugins.</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="flex items-center gap-3 pt-1">
|
|
218
|
+
<input type="checkbox" id="create-hybrid" class="accent-sky-400" checked>
|
|
219
|
+
<label for="create-hybrid" class="text-sm">Enable hybrid RAG (BGE semantic + keyword)</label>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="px-6 py-4 bg-zinc-950 flex gap-3 border-t border-zinc-700">
|
|
223
|
+
<button onclick="hideCreateModal()" class="flex-1 py-2 rounded-2xl text-sm bg-zinc-800 hover:bg-zinc-700">Cancel</button>
|
|
224
|
+
<button onclick="submitCreate()" class="flex-1 py-2 rounded-2xl text-sm bg-white text-zinc-900 font-medium">Create Server</button>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<script>
|
|
230
|
+
let CURRENT_TOKEN = new URLSearchParams(location.search).get('token') || '';
|
|
231
|
+
let SERVERS = [];
|
|
232
|
+
let TEMPLATES = [];
|
|
233
|
+
let POLL_INTERVAL = null;
|
|
234
|
+
|
|
235
|
+
function tailwindInit() {
|
|
236
|
+
document.documentElement.style.setProperty('--accent', '#7dd3fc');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function setToken(t) {
|
|
240
|
+
CURRENT_TOKEN = t;
|
|
241
|
+
if (t && !location.search.includes('token=')) {
|
|
242
|
+
const u = new URL(location.href);
|
|
243
|
+
u.searchParams.set('token', t);
|
|
244
|
+
history.replaceState({}, '', u.toString());
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function copyToken() {
|
|
249
|
+
if (!CURRENT_TOKEN) return;
|
|
250
|
+
navigator.clipboard.writeText(CURRENT_TOKEN).then(() => {
|
|
251
|
+
const el = document.getElementById('token-hint');
|
|
252
|
+
const old = el.innerHTML;
|
|
253
|
+
el.innerHTML = 'copied!';
|
|
254
|
+
setTimeout(() => el.innerHTML = old, 1200);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function showView(view) {
|
|
259
|
+
document.querySelectorAll('[id^="view-"]').forEach(el => el.classList.add('hidden'));
|
|
260
|
+
const target = document.getElementById('view-' + view);
|
|
261
|
+
if (target) target.classList.remove('hidden');
|
|
262
|
+
|
|
263
|
+
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('nav-active'));
|
|
264
|
+
const active = document.querySelector('.nav-link[data-view="' + view + '"]');
|
|
265
|
+
if (active) active.classList.add('nav-active');
|
|
266
|
+
|
|
267
|
+
if (view === 'servers') refreshServers();
|
|
268
|
+
if (view === 'templates') loadTemplates();
|
|
269
|
+
if (view === 'playground') loadPlaygroundServers();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function api(path, opts = {}) {
|
|
273
|
+
const url = path + (path.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(CURRENT_TOKEN);
|
|
274
|
+
const res = await fetch(url, {
|
|
275
|
+
...opts,
|
|
276
|
+
headers: {
|
|
277
|
+
'Content-Type': 'application/json',
|
|
278
|
+
...(opts.headers || {})
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
if (!res.ok) {
|
|
282
|
+
const err = await res.json().catch(() => ({}));
|
|
283
|
+
throw new Error(err.error || 'Request failed: ' + res.status);
|
|
284
|
+
}
|
|
285
|
+
return res.json();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function refreshServers() {
|
|
289
|
+
try {
|
|
290
|
+
const servers = await api('/api/servers');
|
|
291
|
+
SERVERS = servers;
|
|
292
|
+
renderServers(servers);
|
|
293
|
+
} catch (e) {
|
|
294
|
+
console.error(e);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function renderServers(servers) {
|
|
299
|
+
const grid = document.getElementById('servers-grid');
|
|
300
|
+
const empty = document.getElementById('servers-empty');
|
|
301
|
+
grid.innerHTML = '';
|
|
302
|
+
if (!servers.length) {
|
|
303
|
+
empty.classList.remove('hidden');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
empty.classList.add('hidden');
|
|
307
|
+
|
|
308
|
+
servers.forEach(s => {
|
|
309
|
+
const isRunning = !!s.running;
|
|
310
|
+
const statusColor = isRunning ? 'bg-emerald-400' : 'bg-zinc-500';
|
|
311
|
+
const statusText = isRunning ? 'RUNNING' : 'STOPPED';
|
|
312
|
+
const modelLabel = s.embeddingModel && s.embeddingModel.startsWith('hybrid') ? s.embeddingModel : 'fuse';
|
|
313
|
+
const sourceCount = s.sourceCount || (s.definition && s.definition.sources ? s.definition.sources.length : 1);
|
|
314
|
+
const sourceLabel = s.sourceLabel || s.sourceUrl || '';
|
|
315
|
+
const templateLabel = s.templateLabel ? '<div>' + s.templateLabel + '</div>' : '';
|
|
316
|
+
|
|
317
|
+
const card = document.createElement('div');
|
|
318
|
+
card.className = 'card bg-zinc-900 border border-zinc-800 rounded-2xl p-4';
|
|
319
|
+
card.innerHTML = \`
|
|
320
|
+
<div class="flex justify-between items-start">
|
|
321
|
+
<div>
|
|
322
|
+
<div class="font-semibold text-base">\${s.name}</div>
|
|
323
|
+
<div class="text-xs text-zinc-500 font-mono">\${s.slug}</div>
|
|
324
|
+
</div>
|
|
325
|
+
<div class="flex items-center gap-x-1.5 text-xs">
|
|
326
|
+
<div class="status-dot \${statusColor}"></div>
|
|
327
|
+
<span class="font-medium \${isRunning ? 'text-emerald-400' : 'text-zinc-400'}">\${statusText}</span>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<div class="mt-3 text-xs flex gap-x-4 text-zinc-400">
|
|
332
|
+
<div><span class="font-mono">\${(s.chunkCount || 0).toLocaleString()}</span> chunks</div>
|
|
333
|
+
<div>\${modelLabel}</div>
|
|
334
|
+
<div>\${sourceCount} source\${sourceCount === 1 ? '' : 's'}</div>
|
|
335
|
+
\${templateLabel}
|
|
336
|
+
\${s.port ? \`<div>:\${s.port}</div>\` : ''}
|
|
337
|
+
</div>
|
|
338
|
+
<div class="mt-1 text-[10px] text-zinc-500 line-clamp-3">\${sourceLabel}</div>
|
|
339
|
+
|
|
340
|
+
<div class="mt-4 flex flex-wrap gap-2">
|
|
341
|
+
<button data-action="toggle" data-slug="\${s.slug}" data-running="\${isRunning}" class="text-xs px-3 py-1 rounded-lg \${isRunning ? 'bg-red-900/60 hover:bg-red-900 text-red-400' : 'bg-emerald-900/60 hover:bg-emerald-900 text-emerald-400'}">
|
|
342
|
+
\${isRunning ? 'Stop' : 'Start'}
|
|
343
|
+
</button>
|
|
344
|
+
<button data-action="reindex" data-slug="\${s.slug}" class="text-xs px-3 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700">Reindex</button>
|
|
345
|
+
<button data-action="verify" data-slug="\${s.slug}" class="text-xs px-3 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700">Verify</button>
|
|
346
|
+
<button data-action="stats" data-slug="\${s.slug}" class="text-xs px-3 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700">Stats</button>
|
|
347
|
+
<button data-action="play" data-slug="\${s.slug}" class="text-xs px-3 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700">Playground</button>
|
|
348
|
+
<button data-action="delete" data-slug="\${s.slug}" class="text-xs px-3 py-1 rounded-lg bg-red-900/40 hover:bg-red-900 text-red-400 ml-auto">Delete</button>
|
|
349
|
+
</div>
|
|
350
|
+
\`;
|
|
351
|
+
|
|
352
|
+
// attach handlers
|
|
353
|
+
card.querySelectorAll('button').forEach(btn => {
|
|
354
|
+
btn.addEventListener('click', async (ev) => {
|
|
355
|
+
ev.stopPropagation();
|
|
356
|
+
const action = btn.dataset.action;
|
|
357
|
+
const slug = btn.dataset.slug;
|
|
358
|
+
if (action === 'toggle') {
|
|
359
|
+
const running = btn.dataset.running === 'true';
|
|
360
|
+
await (running ? stopServer(slug) : startServer(slug));
|
|
361
|
+
refreshServers();
|
|
362
|
+
} else if (action === 'reindex') {
|
|
363
|
+
await reindexServer(slug);
|
|
364
|
+
refreshServers();
|
|
365
|
+
} else if (action === 'verify') {
|
|
366
|
+
await verifyServer(slug);
|
|
367
|
+
} else if (action === 'stats') {
|
|
368
|
+
await showStats(slug);
|
|
369
|
+
} else if (action === 'play') {
|
|
370
|
+
showView('playground');
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
const sel = document.getElementById('playground-slug');
|
|
373
|
+
if (sel) sel.value = slug;
|
|
374
|
+
}, 50);
|
|
375
|
+
} else if (action === 'delete') {
|
|
376
|
+
if (confirm('Delete server ' + slug + '? This removes all data.')) {
|
|
377
|
+
await deleteServer(slug);
|
|
378
|
+
refreshServers();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
grid.appendChild(card);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function startServer(slug) {
|
|
389
|
+
try {
|
|
390
|
+
await api('/api/servers/' + slug + '/start', { method: 'POST' });
|
|
391
|
+
showToast('Server started');
|
|
392
|
+
} catch (e) { alert(e.message); }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function stopServer(slug) {
|
|
396
|
+
try {
|
|
397
|
+
await api('/api/servers/' + slug + '/stop', { method: 'POST' });
|
|
398
|
+
showToast('Server stopped');
|
|
399
|
+
} catch (e) { alert(e.message); }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function reindexServer(slug) {
|
|
403
|
+
const btns = document.querySelectorAll('button');
|
|
404
|
+
try {
|
|
405
|
+
await api('/api/servers/' + slug + '/reindex', { method: 'POST' });
|
|
406
|
+
showToast('Reindex complete');
|
|
407
|
+
} catch (e) { alert('Reindex failed: ' + e.message); }
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function verifyServer(slug) {
|
|
411
|
+
try {
|
|
412
|
+
const data = await api('/api/servers/' + slug + '/verify');
|
|
413
|
+
const msg = data.samples.map(s => s.query + ': ' + (s.hits.length ? s.hits[0].metadata.url : 'no hits')).join('\\n');
|
|
414
|
+
alert('Verify results for ' + slug + ':\\n\\n' + msg);
|
|
415
|
+
} catch (e) { alert(e.message); }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function showStats(slug) {
|
|
419
|
+
try {
|
|
420
|
+
const data = await api('/api/servers/' + slug + '/stats?days=30');
|
|
421
|
+
const tools = data.byTool ? Object.entries(data.byTool).map(([k, v]) => k + ': ' + v).join('\\n') : 'No tool calls yet';
|
|
422
|
+
const health = data.health ? '\\n\\nAvg hits/search: ' + data.health.avgHitsPerSearch + '\\nZero-hit searches: ' + data.health.zeroHitSearches + '\\nRead success: ' + data.health.readSuccessRate + '%' : '';
|
|
423
|
+
alert('Stats for ' + slug + '\\n\\nTotal calls: ' + (data.total || 0) + '\\n\\n' + tools + health);
|
|
424
|
+
} catch (e) { alert(e.message); }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function deleteServer(slug) {
|
|
428
|
+
await api('/api/servers/' + slug, { method: 'DELETE' });
|
|
429
|
+
showToast('Deleted');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function showCreateModal() {
|
|
433
|
+
document.getElementById('create-modal').classList.remove('hidden');
|
|
434
|
+
document.getElementById('create-modal').classList.add('flex');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function loadTemplates() {
|
|
438
|
+
const grid = document.getElementById('templates-grid');
|
|
439
|
+
if (!grid) return;
|
|
440
|
+
grid.innerHTML = '<div class="text-xs text-zinc-500">Loading templates...</div>';
|
|
441
|
+
try {
|
|
442
|
+
TEMPLATES = await api('/api/templates');
|
|
443
|
+
grid.innerHTML = '';
|
|
444
|
+
TEMPLATES.forEach(t => {
|
|
445
|
+
const card = document.createElement('div');
|
|
446
|
+
card.className = 'card bg-zinc-900 border border-zinc-800 rounded-2xl p-4';
|
|
447
|
+
const required = (t.inputs || []).filter(i => i.required).map(i => i.name).join(', ') || 'none';
|
|
448
|
+
card.innerHTML = \`
|
|
449
|
+
<div class="flex justify-between items-start gap-3">
|
|
450
|
+
<div>
|
|
451
|
+
<div class="font-semibold text-base">\${t.name}</div>
|
|
452
|
+
<div class="text-xs text-zinc-500 font-mono">\${t.id}</div>
|
|
453
|
+
</div>
|
|
454
|
+
<div class="text-[10px] uppercase tracking-widest text-sky-400">\${t.category}</div>
|
|
455
|
+
</div>
|
|
456
|
+
<div class="mt-3 text-sm text-zinc-300 line-clamp-3">\${t.description}</div>
|
|
457
|
+
<div class="mt-3 text-xs text-zinc-500">Required: \${required}</div>
|
|
458
|
+
<div class="mt-4 flex gap-2">
|
|
459
|
+
<button data-template="\${t.id}" class="text-xs px-3 py-1 rounded-lg bg-sky-400 text-black font-medium">Use Template</button>
|
|
460
|
+
<button data-info="\${t.id}" class="text-xs px-3 py-1 rounded-lg bg-zinc-800 hover:bg-zinc-700">Info</button>
|
|
461
|
+
</div>
|
|
462
|
+
\`;
|
|
463
|
+
card.querySelector('[data-template]').addEventListener('click', () => useTemplate(t.id));
|
|
464
|
+
card.querySelector('[data-info]').addEventListener('click', () => {
|
|
465
|
+
alert(t.name + '\\n\\n' + t.description + '\\n\\nInputs: ' + required);
|
|
466
|
+
});
|
|
467
|
+
grid.appendChild(card);
|
|
468
|
+
});
|
|
469
|
+
} catch (e) {
|
|
470
|
+
grid.innerHTML = '<div class="text-red-400 text-sm">Could not load templates: ' + e.message + '</div>';
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function useTemplate(id) {
|
|
475
|
+
const t = TEMPLATES.find(x => x.id === id);
|
|
476
|
+
showCreateModal();
|
|
477
|
+
const name = document.getElementById('create-name');
|
|
478
|
+
const url = document.getElementById('create-url');
|
|
479
|
+
name.value = t ? t.name.replace(/ MCP$/, '') : 'Template Server';
|
|
480
|
+
url.dataset.templateId = id;
|
|
481
|
+
url.placeholder = id === 'github-docs' ? 'owner/repo' : (id === 'docs-rag' ? 'https://example.com/llms.txt' : '(no input needed)');
|
|
482
|
+
url.value = '';
|
|
483
|
+
showToast('Template selected: ' + id);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function hideCreateModal() {
|
|
487
|
+
const m = document.getElementById('create-modal');
|
|
488
|
+
m.classList.add('hidden');
|
|
489
|
+
m.classList.remove('flex');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function submitCreate() {
|
|
493
|
+
const name = document.getElementById('create-name').value.trim();
|
|
494
|
+
const urlEl = document.getElementById('create-url');
|
|
495
|
+
const url = urlEl.value.trim();
|
|
496
|
+
const hybrid = document.getElementById('create-hybrid').checked;
|
|
497
|
+
const templateId = urlEl.dataset.templateId || '';
|
|
498
|
+
|
|
499
|
+
if (!name || (!url && !templateId)) return alert('Name and URL required');
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
const body = { name, url, hybrid };
|
|
503
|
+
if (templateId) {
|
|
504
|
+
body.templateId = templateId;
|
|
505
|
+
if (templateId === 'github-docs') body.repo = url;
|
|
506
|
+
}
|
|
507
|
+
const res = await api('/api/servers', {
|
|
508
|
+
method: 'POST',
|
|
509
|
+
body: JSON.stringify(body)
|
|
510
|
+
});
|
|
511
|
+
urlEl.dataset.templateId = '';
|
|
512
|
+
hideCreateModal();
|
|
513
|
+
showToast('Server created: ' + res.slug);
|
|
514
|
+
showView('servers');
|
|
515
|
+
setTimeout(refreshServers, 400);
|
|
516
|
+
} catch (e) {
|
|
517
|
+
alert('Create failed: ' + e.message);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function createTrial() {
|
|
522
|
+
try {
|
|
523
|
+
const res = await api('/api/trial', { method: 'POST' });
|
|
524
|
+
showToast(res.existed ? 'Trial server already exists' : 'Trial server created');
|
|
525
|
+
showView('servers');
|
|
526
|
+
setTimeout(refreshServers, 400);
|
|
527
|
+
} catch (e) {
|
|
528
|
+
alert('Trial failed: ' + e.message);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async function loadPlaygroundServers() {
|
|
533
|
+
const sel = document.getElementById('playground-slug');
|
|
534
|
+
sel.innerHTML = '';
|
|
535
|
+
try {
|
|
536
|
+
const servers = await api('/api/servers');
|
|
537
|
+
servers.forEach(s => {
|
|
538
|
+
const opt = document.createElement('option');
|
|
539
|
+
opt.value = s.slug;
|
|
540
|
+
opt.textContent = s.name + ' (' + s.slug + ')';
|
|
541
|
+
sel.appendChild(opt);
|
|
542
|
+
});
|
|
543
|
+
if (servers.length) sel.value = servers[0].slug;
|
|
544
|
+
} catch (e) {}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function runPlaygroundSearch(useRrf = false) {
|
|
548
|
+
const slug = document.getElementById('playground-slug').value;
|
|
549
|
+
const query = document.getElementById('playground-query').value.trim() || 'overview';
|
|
550
|
+
const mode = document.getElementById('playground-mode').value;
|
|
551
|
+
|
|
552
|
+
const container = document.getElementById('playground-results');
|
|
553
|
+
container.innerHTML = '<div class="text-xs text-zinc-500">Searching...</div>';
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const body = { query, mode, limit: 5 };
|
|
557
|
+
if (useRrf && mode === 'hybrid') body.reranker = 'rrf';
|
|
558
|
+
|
|
559
|
+
const data = await api('/api/servers/' + slug + '/search', {
|
|
560
|
+
method: 'POST',
|
|
561
|
+
body: JSON.stringify(body)
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
container.innerHTML = '';
|
|
565
|
+
if (!data.results || !data.results.length) {
|
|
566
|
+
container.innerHTML = '<div class="text-sm text-zinc-400">No results.</div>';
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
data.results.forEach(r => {
|
|
571
|
+
const div = document.createElement('div');
|
|
572
|
+
div.className = 'result-card bg-zinc-900 border border-zinc-700 rounded-2xl p-4 text-sm';
|
|
573
|
+
div.innerHTML = \`
|
|
574
|
+
<div class="flex justify-between text-xs mb-1 text-zinc-400">
|
|
575
|
+
<div>score: \${(r.score || 0).toFixed(2)}</div>
|
|
576
|
+
<a href="\${r.metadata.url}" target="_blank" class="text-sky-400 hover:underline">Source →</a>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="font-medium mb-1">\${r.metadata.title || r.metadata.sectionPath || r.metadata.url}</div>
|
|
579
|
+
<div class="text-zinc-300 line-clamp-3">\${r.content.substring(0, 280)}...</div>
|
|
580
|
+
\`;
|
|
581
|
+
container.appendChild(div);
|
|
582
|
+
});
|
|
583
|
+
} catch (e) {
|
|
584
|
+
container.innerHTML = '<div class="text-red-400 text-sm">Search error: ' + e.message + '</div>';
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function showToast(msg) {
|
|
589
|
+
const t = document.createElement('div');
|
|
590
|
+
t.className = 'fixed bottom-4 right-4 bg-zinc-800 border border-zinc-700 text-sm px-4 py-2 rounded-2xl shadow-xl';
|
|
591
|
+
t.textContent = msg;
|
|
592
|
+
document.body.appendChild(t);
|
|
593
|
+
setTimeout(() => t.remove(), 2200);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function refreshAll() {
|
|
597
|
+
await refreshServers();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function init() {
|
|
601
|
+
tailwindInit();
|
|
602
|
+
|
|
603
|
+
const portEl = document.getElementById('port-display');
|
|
604
|
+
if (portEl) portEl.textContent = location.port || '8080';
|
|
605
|
+
|
|
606
|
+
const tokenHint = document.getElementById('token-hint');
|
|
607
|
+
if (tokenHint && CURRENT_TOKEN) {
|
|
608
|
+
tokenHint.textContent = CURRENT_TOKEN.slice(0, 8) + '…';
|
|
609
|
+
tokenHint.title = 'Click to copy full token';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// If no token, show simple unlock
|
|
613
|
+
if (!CURRENT_TOKEN) {
|
|
614
|
+
const main = document.getElementById('main-content');
|
|
615
|
+
main.innerHTML = \`
|
|
616
|
+
<div class="max-w-sm mx-auto mt-12 text-center">
|
|
617
|
+
<div class="text-xl font-semibold mb-2">Unlock Hoolix Dashboard</div>
|
|
618
|
+
<div class="text-sm text-zinc-400 mb-4">Enter the local dashboard token printed when you ran <span class="font-mono">hoolix gui</span></div>
|
|
619
|
+
<input id="unlock-token" class="w-full bg-zinc-900 border border-zinc-700 rounded-2xl px-4 py-3 text-sm monospace" placeholder="gui_xxxxxxxxxxxxxxxxxxxxxxxx">
|
|
620
|
+
<button onclick="unlock()" class="mt-3 w-full bg-white text-black font-medium py-2.5 rounded-2xl">Unlock Dashboard</button>
|
|
621
|
+
</div>
|
|
622
|
+
\`;
|
|
623
|
+
window.unlock = () => {
|
|
624
|
+
const t = document.getElementById('unlock-token').value.trim();
|
|
625
|
+
if (t) {
|
|
626
|
+
const u = new URL(location.href);
|
|
627
|
+
u.searchParams.set('token', t);
|
|
628
|
+
location.href = u.toString();
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
setToken(CURRENT_TOKEN);
|
|
635
|
+
|
|
636
|
+
// initial load servers
|
|
637
|
+
await refreshServers();
|
|
638
|
+
showView('servers');
|
|
639
|
+
|
|
640
|
+
// auto refresh every 3s (good enough for beta)
|
|
641
|
+
if (POLL_INTERVAL) clearInterval(POLL_INTERVAL);
|
|
642
|
+
POLL_INTERVAL = setInterval(() => {
|
|
643
|
+
if (document.getElementById('view-servers') && !document.getElementById('view-servers').classList.contains('hidden')) {
|
|
644
|
+
refreshServers();
|
|
645
|
+
}
|
|
646
|
+
}, 3000);
|
|
647
|
+
|
|
648
|
+
// preload playground servers
|
|
649
|
+
loadPlaygroundServers();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function unlock() { /* defined inline above */ }
|
|
653
|
+
|
|
654
|
+
window.onload = init;
|
|
655
|
+
</script>
|
|
656
|
+
</body>
|
|
657
|
+
</html>`;
|
|
658
|
+
}
|
|
659
|
+
//# sourceMappingURL=assets.js.map
|