@krmxd/onegpt 0.1.5-beta → 0.1.7-beta
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/README.md +16 -1
- package/bin/ogpt.js +54 -3
- package/package.json +4 -2
- package/scripts/postinstall.js +18 -0
- package/src/bootstrap.js +41 -5
- package/src/cli.js +4 -2
- package/src/static.js +1 -1
- package/src/web.js +2 -2
package/README.md
CHANGED
|
@@ -7,10 +7,25 @@ commands, tools and session format as the Python version.
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
+
# universal launcher-installer (repo folder):
|
|
11
|
+
sh ../install.sh # or: sh install.sh at the repo root
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# zero-install (no PATH changes needed):
|
|
16
|
+
npx @krmxd/onegpt
|
|
17
|
+
|
|
18
|
+
# permanent `ogpt` command:
|
|
10
19
|
npm i -g @krmxd/onegpt
|
|
11
|
-
ogpt
|
|
20
|
+
ogpt # alias: ogpt-node
|
|
12
21
|
```
|
|
13
22
|
|
|
23
|
+
> Used `npm i @krmxd/onegpt` without `-g`? npm doesn't put local bins on
|
|
24
|
+
> your PATH - launch with `npx @krmxd/onegpt` (OGPT prints this tip too).
|
|
25
|
+
>
|
|
26
|
+
> First launch auto-installs anything missing - including **Ollama** -
|
|
27
|
+
> hidden behind one line: `Installing packages...`
|
|
28
|
+
|
|
14
29
|
## Troubleshooting
|
|
15
30
|
|
|
16
31
|
**`ModuleNotFoundError: No module named 'ogpt'` from a path like
|
package/bin/ogpt.js
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { spawn, spawnSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Cross-build hand-off: if this Node build can't run here (old Node, broken
|
|
10
|
+
// files) but the Python build can, exec it transparently - `ogpt` just works.
|
|
11
|
+
function pythonFallback(reason) {
|
|
12
|
+
const candidates = process.platform === "win32"
|
|
13
|
+
? ["python", "python3"] : ["python3", "python"];
|
|
14
|
+
for (const bin of candidates) {
|
|
15
|
+
const probe = spawnSync(bin, ["-c", "import ogpt"],
|
|
16
|
+
{ encoding: "utf-8", timeout: 60000 });
|
|
17
|
+
if (probe.error || probe.status !== 0) continue;
|
|
18
|
+
console.error(`(node build unavailable: ${reason} - using the Python build)`);
|
|
19
|
+
const child = spawn(bin, ["-m", "ogpt", ...process.argv.slice(2)],
|
|
20
|
+
{ stdio: "inherit" });
|
|
21
|
+
child.on("exit", (c) => process.exit(c || 0));
|
|
22
|
+
child.on("error", () => process.exit(1));
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
4
28
|
const [maj] = process.versions.node.split(".").map(Number);
|
|
5
29
|
if (maj < 18) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
30
|
+
if (!pythonFallback(`Node.js ${process.versions.node} is too old (need 18+)`)) {
|
|
31
|
+
console.error(`OGPT needs Node.js 18+ - you are running ${process.versions.node}.`);
|
|
32
|
+
console.error("Update Node, then try again: https://nodejs.org");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
9
35
|
}
|
|
10
36
|
|
|
11
37
|
let CLI, getConfig, VERSION_FULL;
|
|
@@ -13,6 +39,7 @@ try {
|
|
|
13
39
|
({ CLI, VERSION_FULL } = require("../src/cli"));
|
|
14
40
|
({ getConfig } = require("../src/config"));
|
|
15
41
|
} catch (e) {
|
|
42
|
+
if (pythonFallback(e.message)) process.exit(0);
|
|
16
43
|
console.error(`OGPT failed to start: ${e.message}`);
|
|
17
44
|
if (e.code === "MODULE_NOT_FOUND") { console.error(`
|
|
18
45
|
The installation looks broken or out of date. Fix it with:
|
|
@@ -36,7 +63,31 @@ Or run straight from the repo without installing:
|
|
|
36
63
|
// 'Installing packages...' line; no-op when everything is already present.
|
|
37
64
|
try { require("../src/bootstrap").ensureBoot(); } catch {}
|
|
38
65
|
|
|
66
|
+
// Local installs (npm i without -g) put no `ogpt` shortcut on PATH, so users
|
|
67
|
+
// end up launching by full file path. Detect that once and teach the fix.
|
|
68
|
+
function maybeHintLocalInstall() {
|
|
69
|
+
try {
|
|
70
|
+
const p = __dirname.replace(/\\/g, "/");
|
|
71
|
+
if (!p.includes("node_modules") || p.includes("_npx")) return;
|
|
72
|
+
const dirs = (process.env.PATH || "").split(path.delimiter);
|
|
73
|
+
const onPath = dirs.some((d) => {
|
|
74
|
+
try { return d && (fs.existsSync(path.join(d, "ogpt")) ||
|
|
75
|
+
fs.existsSync(path.join(d, "ogpt.cmd"))); } catch { return false; }
|
|
76
|
+
});
|
|
77
|
+
if (onPath) return;
|
|
78
|
+
const flagDir = require("os").homedir() + "/.config/ogpt";
|
|
79
|
+
fs.mkdirSync(flagDir, { recursive: true });
|
|
80
|
+
const flag = path.join(flagDir, ".local-install-hint");
|
|
81
|
+
if (fs.existsSync(flag)) return;
|
|
82
|
+
fs.writeFileSync(flag, new Date().toISOString());
|
|
83
|
+
console.log("\nTip: OGPT was launched by file path - no 'ogpt' command is on your PATH.");
|
|
84
|
+
console.log(" Run it anywhere: npx @krmxd/onegpt");
|
|
85
|
+
console.log(" Install shortcut: npm i -g @krmxd/onegpt\n");
|
|
86
|
+
} catch {}
|
|
87
|
+
}
|
|
88
|
+
|
|
39
89
|
async function main() {
|
|
90
|
+
maybeHintLocalInstall();
|
|
40
91
|
const args = process.argv.slice(2);
|
|
41
92
|
const cfg = getConfig();
|
|
42
93
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krmxd/onegpt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7-beta",
|
|
4
4
|
"description": "OGPT - AI coding assistant for the terminal with a built-in local engine",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node bin/ogpt.js",
|
|
12
|
+
"postinstall": "node scripts/postinstall.js",
|
|
12
13
|
"test": "node --test src/__tests__/*.test.js"
|
|
13
14
|
},
|
|
14
15
|
"keywords": [
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"files": [
|
|
29
30
|
"src/",
|
|
30
31
|
"bin/",
|
|
31
|
-
"README.md"
|
|
32
|
+
"README.md",
|
|
33
|
+
"scripts/"
|
|
32
34
|
]
|
|
33
35
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Runs after `npm install`. Teaches local installs how to launch OGPT -
|
|
4
|
+
// npm only links bins onto PATH for GLOBAL installs, which is why
|
|
5
|
+
// `ogpt` "doesn't exist" after a plain `npm i @krmxd/onegpt`.
|
|
6
|
+
try {
|
|
7
|
+
const isGlobal = process.env.npm_config_global === "true";
|
|
8
|
+
if (isGlobal) {
|
|
9
|
+
console.log("OGPT installed. Launch it with: ogpt");
|
|
10
|
+
} else {
|
|
11
|
+
console.log("");
|
|
12
|
+
console.log("OGPT was installed locally (no 'ogpt' command on your PATH).");
|
|
13
|
+
console.log(" Run it right now: npx ogpt");
|
|
14
|
+
console.log(" Or make it global: npm i -g @krmxd/onegpt");
|
|
15
|
+
console.log(" From inside this folder: node node_modules/@krmxd/onegpt/bin/ogpt.js");
|
|
16
|
+
console.log("");
|
|
17
|
+
}
|
|
18
|
+
} catch {}
|
package/src/bootstrap.js
CHANGED
|
@@ -54,7 +54,19 @@ function npmInstall(pkgs, cwd, timeout = 600000) {
|
|
|
54
54
|
} catch { return false; }
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function isTermux() {
|
|
58
|
+
return (process.env.PREFIX || "").includes("/com.termux/")
|
|
59
|
+
|| fs.existsSync("/data/data/com.termux");
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
function installOllama(timeout = 1200000) {
|
|
63
|
+
if (isTermux()) {
|
|
64
|
+
try {
|
|
65
|
+
spawnSync("pkg", ["install", "-y", "ollama"],
|
|
66
|
+
{ stdio: "ignore", timeout });
|
|
67
|
+
} catch {}
|
|
68
|
+
if (haveOllama()) return true;
|
|
69
|
+
}
|
|
58
70
|
if (process.platform === "win32") {
|
|
59
71
|
try {
|
|
60
72
|
spawnSync("winget", ["install", "--silent",
|
|
@@ -67,6 +79,21 @@ function installOllama(timeout = 1200000) {
|
|
|
67
79
|
try { return spawnSync(c, ["--version"], { stdio: "ignore" }).status === 0; }
|
|
68
80
|
catch { return false; }
|
|
69
81
|
};
|
|
82
|
+
// No fetcher at all? Try to get curl via the system package manager.
|
|
83
|
+
if (!has("curl") && !has("wget")) {
|
|
84
|
+
for (const [bin, args] of [
|
|
85
|
+
["apt-get", ["install", "-y", "curl"]],
|
|
86
|
+
["apk", ["add", "curl"]],
|
|
87
|
+
["dnf", ["install", "-y", "curl"]],
|
|
88
|
+
]) {
|
|
89
|
+
try {
|
|
90
|
+
if (spawnSync(bin, ["--version"], { stdio: "ignore" }).status === 0) {
|
|
91
|
+
spawnSync(bin, args, { stdio: "ignore", timeout });
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
70
97
|
let script = null;
|
|
71
98
|
if (has("curl")) script = "curl -fsSL https://ollama.com/install.sh | sh";
|
|
72
99
|
else if (has("wget")) script = "wget -qO- https://ollama.com/install.sh | sh";
|
|
@@ -77,15 +104,24 @@ function installOllama(timeout = 1200000) {
|
|
|
77
104
|
return haveOllama();
|
|
78
105
|
}
|
|
79
106
|
|
|
80
|
-
function ensureBoot() {
|
|
107
|
+
function ensureBoot({ have = haveOllama, install = installOllama } = {}) {
|
|
81
108
|
if (process.env.OGPT_NO_AUTO_INSTALL) return [];
|
|
82
109
|
const need = missingPackages();
|
|
83
|
-
const wantOllama = !
|
|
110
|
+
const wantOllama = !have();
|
|
84
111
|
if (!need.length && !wantOllama) return [];
|
|
85
112
|
console.log("Installing packages...");
|
|
86
113
|
if (need.length) npmInstall(need.map(([, pkg]) => pkg));
|
|
87
|
-
if (wantOllama)
|
|
88
|
-
|
|
114
|
+
if (wantOllama) install();
|
|
115
|
+
const still = missingPackages().map(([, pkg]) => pkg);
|
|
116
|
+
if (still.length) {
|
|
117
|
+
console.log("Some packages could not be installed automatically.");
|
|
118
|
+
console.log("Try manually: npm install " + still.join(" "));
|
|
119
|
+
}
|
|
120
|
+
if (wantOllama && !have()) {
|
|
121
|
+
console.log("(Ollama could not be installed automatically -");
|
|
122
|
+
console.log(" grab it from https://ollama.com/download to use local models)");
|
|
123
|
+
}
|
|
124
|
+
return still;
|
|
89
125
|
}
|
|
90
126
|
|
|
91
127
|
// Back-compat shim (deps-only, no Ollama check).
|
|
@@ -99,4 +135,4 @@ function ensurePackages() {
|
|
|
99
135
|
}
|
|
100
136
|
|
|
101
137
|
module.exports = { ensureBoot, ensurePackages, installOllama, haveOllama,
|
|
102
|
-
npmInstall, PACKAGES };
|
|
138
|
+
npmInstall, isTermux, PACKAGES };
|
package/src/cli.js
CHANGED
|
@@ -12,9 +12,11 @@ const Platform = require("./platform");
|
|
|
12
12
|
const catalog = require("./catalog");
|
|
13
13
|
const web = require("./web");
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Single source of truth: package.json version + device-build tag.
|
|
16
|
+
const PKG = require("../package.json");
|
|
16
17
|
const VERSION_TAG = "devices-all";
|
|
17
|
-
const
|
|
18
|
+
const VERSION = PKG.version;
|
|
19
|
+
const VERSION_FULL = `${PKG.version}(${VERSION_TAG})`;
|
|
18
20
|
|
|
19
21
|
class Command {
|
|
20
22
|
constructor(name, handler, description, usage, category, ai, aliases) {
|
package/src/static.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Single-file token dashboard UI - identical to the Python build.
|
|
2
|
-
module.exports = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>OGPT \u00b7 Token Dashboard</title>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js\"></script>\n<style>\n:root{\n --bg:#0d0f14; --panel:#141822; --panel2:#181d2a; --line:#232a3a;\n --text:#e8ecf4; --dim:#8b93a7; --mag:#e34fd0; --cyan:#39c6e3;\n --green:#3ddc84; --red:#ff5c69; --amber:#ffc857; --radius:14px;\n --mono:\"SFMono-Regular\",Consolas,\"Liberation Mono\",Menlo,monospace;\n}\n*{box-sizing:border-box}\nbody{margin:0;background:\n radial-gradient(1200px 500px at 80% -10%, #1a1030 0%, transparent 60%),\n radial-gradient(900px 400px at -10% 110%, #06202b 0%, transparent 55%),\n var(--bg);\n color:var(--text);font:15px/1.5 -apple-system,\"Segoe UI\",Roboto,sans-serif;}\n.wrap{max-width:1180px;margin:0 auto;padding:18px 20px 40px}\n\n/* topbar */\n.topbar{display:flex;align-items:center;gap:12px;padding:14px 4px;border-bottom:1px solid var(--line)}\n.logo{font-weight:800;font-size:19px;letter-spacing:.3px;color:var(--mag)}\n.logo .dot{color:var(--cyan)}\n.chip{background:var(--panel);border:1px solid var(--line);border-radius:999px;\n padding:4px 12px;font-size:12.5px;color:var(--dim)}\n.chip b{color:var(--text)}\n.live{margin-left:auto;display:flex;align-items:center;gap:7px;font-size:12px;color:var(--green)}\n.live i{width:9px;height:9px;border-radius:50%;background:var(--green);\n box-shadow:0 0 0 0 rgba(61,220,132,.6);animation:pulse 1.6s infinite}\n@keyframes pulse{70%{box-shadow:0 0 0 8px rgba(61,220,132,0)}100%{box-shadow:0 0 0 0 rgba(61,220,132,0)}}\n\n/* cards */\n.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:18px 0}\n.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:14px 16px}\n.card .k{font-size:11.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--dim)}\n.card .v{font-family:var(--mono);font-size:24px;font-weight:700;margin-top:4px}\n.card .s{font-size:11.5px;color:var(--dim);margin-top:2px}\n.v.mag{color:var(--mag)} .v.cyan{color:var(--cyan)} .v.green{color:var(--green)} .v.amber{color:var(--amber)}\n\n/* panels & charts */\n.grid2{display:grid;grid-template-columns:2fr 1fr;gap:12px;margin-bottom:12px}\n.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:14px 16px}\n.panel h3{margin:0 0 10px;font-size:13px;color:var(--dim);text-transform:uppercase;letter-spacing:.07em}\ncanvas{width:100%!important;height:240px!important}\n\n/* main columns */\n.main{display:grid;grid-template-columns:1fr 380px;gap:12px}\n@media(max-width:900px){.main,.grid2{grid-template-columns:1fr}}\n#feed{height:430px;overflow-y:auto;display:flex;flex-direction:column;gap:10px;padding-right:6px}\n.msg{border:1px solid var(--line);border-left:3px solid var(--cyan);border-radius:10px;\n background:var(--panel2);padding:10px 12px}\n.msg.user{border-left-color:var(--mag)}\n.msg .who{font-size:11.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--dim);margin-bottom:3px}\n.msg.user .who{color:var(--mag)} .msg.bot .who{color:var(--cyan)}\n.msg .txt{white-space:pre-wrap;word-wrap:break-word;font-size:14px}\n.meta{font-size:11px;color:var(--dim);font-family:var(--mono);margin-top:6px}\n.empty{color:var(--dim);text-align:center;padding:30px 0}\n\n/* chat composer */\n.composer{display:flex;flex-direction:column;gap:8px}\n#chat-in{width:100%;min-height:74px;resize:vertical;background:var(--panel2);color:var(--text);\n border:1px solid var(--line);border-radius:10px;padding:10px 12px;font:inherit}\n#chat-in:focus{outline:none;border-color:var(--mag)}\n.rowbtn{display:flex;gap:8px;align-items:center}\nbutton.send{background:linear-gradient(135deg,var(--mag),#8a4fe3);border:0;color:#fff;font-weight:700;\n padding:9px 20px;border-radius:999px;cursor:pointer;font-size:14px}\nbutton.send:disabled{opacity:.45;cursor:not-allowed}\n.hint{font-size:11.5px;color:var(--dim)}\n\n/* bottom tables */\n.bottom{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}\n@media(max-width:900px){.bottom{grid-template-columns:1fr}}\ntable{width:100%;border-collapse:collapse;font-size:13px}\ntd,th{padding:7px 8px;border-bottom:1px solid var(--line);text-align:left}\nth{color:var(--dim);font-weight:600;font-size:11.5px;text-transform:uppercase;letter-spacing:.06em}\n.tools{display:flex;flex-wrap:wrap;gap:7px}\n.toolchip{background:var(--panel2);border:1px solid var(--line);border-radius:999px;\n padding:4px 11px;font-size:12px;color:var(--cyan);font-family:var(--mono)}\nfooter{margin-top:22px;color:var(--dim);font-size:12px;display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px}\nfooter code{font-family:var(--mono);color:var(--cyan)}\n</style>\n</head>\n<body>\n<div class=\"wrap\">\n\n <div class=\"topbar\">\n <span class=\"logo\">\u25c6 OGPT<span class=\"dot\"> \u00b7 </span>Token Dashboard</span>\n <span class=\"chip\" id=\"chip-model\"><b>\u2014</b></span>\n <span class=\"chip\" id=\"chip-provider\">provider \u2014</span>\n <span class=\"live\"><i></i> LIVE</span>\n </div>\n\n <div class=\"cards\">\n <div class=\"card\"><div class=\"k\">Total tokens</div><div class=\"v mag\" id=\"c-total\">0</div><div class=\"s\" id=\"c-total-s\">prompt + completion</div></div>\n <div class=\"card\"><div class=\"k\">Prompt tokens</div><div class=\"v\" id=\"c-prompt\">0</div></div>\n <div class=\"card\"><div class=\"k\">Completion tokens</div><div class=\"v cyan\" id=\"c-completion\">0</div></div>\n <div class=\"card\"><div class=\"k\">Avg speed</div><div class=\"v green\" id=\"c-avgtps\">\u2013</div><div class=\"s\">tokens/sec</div></div>\n <div class=\"card\"><div class=\"k\">Best speed</div><div class=\"v green\" id=\"c-besttps\">\u2013</div></div>\n <div class=\"card\"><div class=\"k\">Last TTFT</div><div class=\"v amber\" id=\"c-ttft\">\u2013</div><div class=\"s\">time to first token</div></div>\n <div class=\"card\"><div class=\"k\">Messages</div><div class=\"v\" id=\"c-msgs\">0</div><div class=\"s\" id=\"c-runs\">0 runs tracked</div></div>\n <div class=\"card\"><div class=\"k\">Uptime</div><div class=\"v\" id=\"c-uptime\">0:00</div><div class=\"s\" id=\"c-session\">no session</div></div>\n </div>\n\n <div class=\"grid2\">\n <div class=\"panel\"><h3>Tokens per run</h3><canvas id=\"ch-runs\"></canvas></div>\n <div class=\"panel\"><h3>Token split</h3><canvas id=\"ch-split\"></canvas></div>\n </div>\n\n <div class=\"main\">\n <div class=\"panel\"><h3>Conversation</h3><div id=\"feed\"><div class=\"empty\">No messages yet.</div></div></div>\n <div class=\"panel composer-panel\">\n <h3>Chat with this agent</h3>\n <div class=\"composer\">\n <textarea id=\"chat-in\" placeholder=\"Ask anything\u2026 (shares the same brain as the terminal session)\"></textarea>\n <div class=\"rowbtn\">\n <button class=\"send\" id=\"chat-send\">Send \u23ce</button>\n <span class=\"hint\" id=\"chat-hint\">Streams from the live model.</span>\n </div>\n <div class=\"hint\">Enter to send \u00b7 Shift+Enter newline</div>\n </div>\n </div>\n </div>\n\n <div class=\"bottom\">\n <div class=\"panel\"><h3>Sessions</h3>\n <table><thead><tr><th>ID</th><th>Title</th><th>Msgs</th><th>Updated</th></tr></thead>\n <tbody id=\"sess-rows\"><tr><td colspan=\"4\" style=\"color:var(--dim)\">\u2014</td></tr></tbody></table>\n </div>\n <div class=\"panel\"><h3>Registered tools (<span id=\"tool-count\">0</span>)</h3>\n <div class=\"tools\" id=\"tools-list\"><span style=\"color:var(--dim)\">\u2014</span></div>\n </div>\n </div>\n\n <footer>\n <span>OGPT v0.1.5-beta(devices-all) \u00b7 dashboard bound to <code>127.0.0.1</code> only</span>\n <span>polling every 1.5s \u00b7 <code>/api/stats</code></span>\n </footer>\n</div>\n\n<script>\nconst $=id=>document.getElementById(id);\nconst fmt=n=>Number(n||0).toLocaleString();\nlet runsChart=null, splitChart=null;\nlet lastHistoryKey=\"\", busy=false;\n\nfunction fmtDur(s){s=Math.floor(s||0);const h=Math.floor(s/3600),m=Math.floor(s%3600/60),\n x=s%60;return (h?h+\":\":\"\")+m+\":\"+String(x).padStart(2,\"0\");}\nfunction setText(id,v){$(id).textContent=v;}\n\nasync function poll(){\n try{\n const d=await (await fetch(\"/api/stats\")).json();\n setText(\"chip-model\",\"\");$(\"chip-model\").innerHTML=\"<b>\"+d.model+\"</b>\";\n $(\"chip-provider\").textContent=\"provider \"+d.provider+\" \u00b7 \"+d.cwd;\n setText(\"c-total\",fmt(d.tokens.total));\n setText(\"c-total-s\",fmt(d.tokens.prompt)+\" prompt + \"+fmt(d.tokens.completion)+\" completion\");\n setText(\"c-prompt\",fmt(d.tokens.prompt));\n setText(\"c-completion\",fmt(d.tokens.completion));\n setText(\"c-avgtps\",d.avg_tps?d.avg_tps.toFixed(1):\"\u2013\");\n setText(\"c-besttps\",d.best_tps?d.best_tps.toFixed(1):\"\u2013\");\n const lr=d.last_run||{};\n setText(\"c-ttft\",(lr.ttft!=null)?lr.ttft.toFixed(2)+\"s\":\"\u2013\");\n setText(\"c-msgs\",fmt(d.messages));\n setText(\"c-runs\",fmt(d.runs_count)+\" runs tracked\");\n setText(\"c-uptime\",fmtDur(d.uptime_s));\n setText(\"c-session\",d.session?(\"session \"+d.session):\"no session\");\n updateCharts(d);\n }catch(e){/* server gone quiet */}\n}\n\nfunction ensureCharts(){\n if(!window.Chart||runsChart)return;\n Chart.defaults.color=\"#8b93a7\";Chart.defaults.borderColor=\"#232a3a\";\n runsChart=new Chart($(\"ch-runs\"),{type:\"line\",\n data:{labels:[],datasets:[{label:\"tokens\",data:[],borderColor:\"#e34fd0\",\n backgroundColor:\"rgba(227,79,208,.15)\",fill:true,tension:.35,pointRadius:2}]},\n options:{animation:false,maintainAspectRatio:false,\n scales:{y:{beginAtZero:true},x:{ticks:{maxTicksLimit:10}}}}});\n splitChart=new Chart($(\"ch-split\"),{type:\"doughnut\",\n data:{labels:[\"prompt\",\"completion\"],\n datasets:[{data:[0,0],backgroundColor:[\"#39c6e3\",\"#e34fd0\"],borderWidth:0}]},\n options:{animation:false,maintainAspectRatio:false,cutout:\"62%\",\n plugins:{legend:{position:\"bottom\"}}}});\n}\nfunction updateCharts(d){\n ensureCharts();if(!runsChart)return;\n const rs=d.runs||[];\n runsChart.data.labels=rs.map(r=>new Date(r.ts*1000).toLocaleTimeString());\n runsChart.data.datasets[0].data=rs.map(r=>r.tokens);\n runsChart.update(\"none\");\n splitChart.data.datasets[0].data=[d.tokens.prompt,d.tokens.completion];\n splitChart.update(\"none\");\n}\n\nasync function loadHistory(force){\n try{\n const msgs=await (await fetch(\"/api/history\")).json();\n const key=msgs.length+\":\"+((msgs[msgs.length-1]||{}).ts||0);\n if(!force&&key===lastHistoryKey)return;\n lastHistoryKey=key;\n const feed=$(\"feed\");feed.textContent=\"\";\n if(!msgs.length){feed.innerHTML='<div class=\"empty\">No messages yet.</div>';return;}\n for(const m of msgs){\n const div=document.createElement(\"div\");\n div.className=\"msg \"+(m.role===\"user\"?\"user\":\"bot\");\n const who=document.createElement(\"div\");who.className=\"who\";\n who.textContent=m.role===\"user\"?\"\u276f You\":\"\u25cf oGPT\";\n const txt=document.createElement(\"div\");txt.className=\"txt\";\n txt.textContent=m.content;\n div.append(who,txt);\n if(m.ts){const meta=document.createElement(\"div\");meta.className=\"meta\";\n meta.textContent=new Date(m.ts*1000).toLocaleString();div.append(meta);}\n feed.appendChild(div);\n }\n feed.scrollTop=feed.scrollHeight;\n }catch(e){}\n}\n\nfunction addBubble(kind){\n const div=document.createElement(\"div\");\n div.className=\"msg \"+kind;\n const who=document.createElement(\"div\");who.className=\"who\";\n who.textContent=kind===\"user\"?\"\u276f You\":\"\u25cf oGPT\";\n const txt=document.createElement(\"div\");txt.className=\"txt\";\n if(kind!==\"user\"){const m=document.createElement(\"div\");m.className=\"meta\";\n m.textContent=\"streaming\u2026\";txt.dataset.meta=m;txt.append(m);}\n div.append(who,txt);$(\"feed\").appendChild(div);\n $(\"feed\").scrollTop=$(\"feed\").scrollHeight;\n return txt;\n}\nfunction sendChat(){\n if(busy)return;\n const inp=$(\"chat-in\"),msg=inp.value.trim();\n if(!msg)return;\n inp.value=\"\";busy=true;$(\"chat-send\").disabled=true;\n addBubble(\"user\");\n const bot=addBubble(\"bot\");\n let acc=\"\";\n const es=new EventSource(\"/api/chat/stream?msg=\"+encodeURIComponent(msg));\n es.onmessage=e=>{\n let d;try{d=JSON.parse(e.data);}catch(_){return;}\n if(d.t){acc+=d.t;bot.textContent=acc;\n $(\"feed\").scrollTop=$(\"feed\").scrollHeight;}\n if(d.error){acc+=\"\\n[error] \"+d.error;bot.textContent=acc;}\n if(d.done){es.close();\n const s=d.stats||{};const bits=[];\n if(s.tokens)bits.push(s.tokens+\" tok\");\n if(s.tps)bits.push(s.tps+\" tok/s\");\n bot.dataset.meta&&(bot.querySelector(\".meta\")||{}).remove;\n const meta=document.createElement(\"div\");meta.className=\"meta\";\n meta.textContent=bits.length?bits.join(\" \u00b7 \"):\"done\";\n bot.parentElement.appendChild(meta);\n busy=false;$(\"chat-send\").disabled=false;loadHistory(true);}\n };\n es.onerror=()=>{es.close();busy=false;$(\"chat-send\").disabled=false;};\n}\n$(\"chat-send\").addEventListener(\"click\",sendChat);\n$(\"chat-in\").addEventListener(\"keydown\",ev=>{\n if(ev.key===\"Enter\"&&!ev.shiftKey){ev.preventDefault();sendChat();}\n});\n\nasync function loadBottom(){\n try{\n const rows=await (await fetch(\"/api/sessions\")).json();\n const tb=$(\"sess-rows\");tb.textContent=\"\";\n if(!rows.length){tb.innerHTML='<tr><td colspan=\"4\" style=\"color:var(--dim)\">\u2014</td></tr>';return;}\n for(const s of rows){\n const tr=document.createElement(\"tr\");\n [s.id,s.title,s.msgs,new Date((s.updated||0)*1000).toLocaleString()].forEach(v=>{\n const td=document.createElement(\"td\");td.textContent=v??\"\";tr.appendChild(td);});\n tb.appendChild(tr);\n }\n }catch(e){}\n try{\n const names=await (await fetch(\"/api/tools\")).json();\n $(\"tool-count\").textContent=names.length;\n const list=$(\"tools-list\");list.textContent=\"\";\n for(const n of names){const c=document.createElement(\"span\");\n c.className=\"toolchip\";c.textContent=n;list.appendChild(c);}\n }catch(e){}\n}\n\npoll();loadHistory();loadBottom();\nsetInterval(poll,1500);\nsetInterval(()=>loadHistory(false),5000);\nsetInterval(loadBottom,20000);\nensureCharts();\n</script>\n</body>\n</html>\n";
|
|
2
|
+
module.exports = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>OGPT \u00b7 Token Dashboard</title>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js\"></script>\n<style>\n:root{\n --bg:#0d0f14; --panel:#141822; --panel2:#181d2a; --line:#232a3a;\n --text:#e8ecf4; --dim:#8b93a7; --mag:#e34fd0; --cyan:#39c6e3;\n --green:#3ddc84; --red:#ff5c69; --amber:#ffc857; --radius:14px;\n --mono:\"SFMono-Regular\",Consolas,\"Liberation Mono\",Menlo,monospace;\n}\n*{box-sizing:border-box}\nbody{margin:0;background:\n radial-gradient(1200px 500px at 80% -10%, #1a1030 0%, transparent 60%),\n radial-gradient(900px 400px at -10% 110%, #06202b 0%, transparent 55%),\n var(--bg);\n color:var(--text);font:15px/1.5 -apple-system,\"Segoe UI\",Roboto,sans-serif;}\n.wrap{max-width:1180px;margin:0 auto;padding:18px 20px 40px}\n\n/* topbar */\n.topbar{display:flex;align-items:center;gap:12px;padding:14px 4px;border-bottom:1px solid var(--line)}\n.logo{font-weight:800;font-size:19px;letter-spacing:.3px;color:var(--mag)}\n.logo .dot{color:var(--cyan)}\n.chip{background:var(--panel);border:1px solid var(--line);border-radius:999px;\n padding:4px 12px;font-size:12.5px;color:var(--dim)}\n.chip b{color:var(--text)}\n.live{margin-left:auto;display:flex;align-items:center;gap:7px;font-size:12px;color:var(--green)}\n.live i{width:9px;height:9px;border-radius:50%;background:var(--green);\n box-shadow:0 0 0 0 rgba(61,220,132,.6);animation:pulse 1.6s infinite}\n@keyframes pulse{70%{box-shadow:0 0 0 8px rgba(61,220,132,0)}100%{box-shadow:0 0 0 0 rgba(61,220,132,0)}}\n\n/* cards */\n.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:18px 0}\n.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:14px 16px}\n.card .k{font-size:11.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--dim)}\n.card .v{font-family:var(--mono);font-size:24px;font-weight:700;margin-top:4px}\n.card .s{font-size:11.5px;color:var(--dim);margin-top:2px}\n.v.mag{color:var(--mag)} .v.cyan{color:var(--cyan)} .v.green{color:var(--green)} .v.amber{color:var(--amber)}\n\n/* panels & charts */\n.grid2{display:grid;grid-template-columns:2fr 1fr;gap:12px;margin-bottom:12px}\n.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:14px 16px}\n.panel h3{margin:0 0 10px;font-size:13px;color:var(--dim);text-transform:uppercase;letter-spacing:.07em}\ncanvas{width:100%!important;height:240px!important}\n\n/* main columns */\n.main{display:grid;grid-template-columns:1fr 380px;gap:12px}\n@media(max-width:900px){.main,.grid2{grid-template-columns:1fr}}\n#feed{height:430px;overflow-y:auto;display:flex;flex-direction:column;gap:10px;padding-right:6px}\n.msg{border:1px solid var(--line);border-left:3px solid var(--cyan);border-radius:10px;\n background:var(--panel2);padding:10px 12px}\n.msg.user{border-left-color:var(--mag)}\n.msg .who{font-size:11.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--dim);margin-bottom:3px}\n.msg.user .who{color:var(--mag)} .msg.bot .who{color:var(--cyan)}\n.msg .txt{white-space:pre-wrap;word-wrap:break-word;font-size:14px}\n.meta{font-size:11px;color:var(--dim);font-family:var(--mono);margin-top:6px}\n.empty{color:var(--dim);text-align:center;padding:30px 0}\n\n/* chat composer */\n.composer{display:flex;flex-direction:column;gap:8px}\n#chat-in{width:100%;min-height:74px;resize:vertical;background:var(--panel2);color:var(--text);\n border:1px solid var(--line);border-radius:10px;padding:10px 12px;font:inherit}\n#chat-in:focus{outline:none;border-color:var(--mag)}\n.rowbtn{display:flex;gap:8px;align-items:center}\nbutton.send{background:linear-gradient(135deg,var(--mag),#8a4fe3);border:0;color:#fff;font-weight:700;\n padding:9px 20px;border-radius:999px;cursor:pointer;font-size:14px}\nbutton.send:disabled{opacity:.45;cursor:not-allowed}\n.hint{font-size:11.5px;color:var(--dim)}\n\n/* bottom tables */\n.bottom{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}\n@media(max-width:900px){.bottom{grid-template-columns:1fr}}\ntable{width:100%;border-collapse:collapse;font-size:13px}\ntd,th{padding:7px 8px;border-bottom:1px solid var(--line);text-align:left}\nth{color:var(--dim);font-weight:600;font-size:11.5px;text-transform:uppercase;letter-spacing:.06em}\n.tools{display:flex;flex-wrap:wrap;gap:7px}\n.toolchip{background:var(--panel2);border:1px solid var(--line);border-radius:999px;\n padding:4px 11px;font-size:12px;color:var(--cyan);font-family:var(--mono)}\nfooter{margin-top:22px;color:var(--dim);font-size:12px;display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px}\nfooter code{font-family:var(--mono);color:var(--cyan)}\n</style>\n</head>\n<body>\n<div class=\"wrap\">\n\n <div class=\"topbar\">\n <span class=\"logo\">\u25c6 OGPT<span class=\"dot\"> \u00b7 </span>Token Dashboard</span>\n <span class=\"chip\" id=\"chip-model\"><b>\u2014</b></span>\n <span class=\"chip\" id=\"chip-provider\">provider \u2014</span>\n <span class=\"live\"><i></i> LIVE</span>\n </div>\n\n <div class=\"cards\">\n <div class=\"card\"><div class=\"k\">Total tokens</div><div class=\"v mag\" id=\"c-total\">0</div><div class=\"s\" id=\"c-total-s\">prompt + completion</div></div>\n <div class=\"card\"><div class=\"k\">Prompt tokens</div><div class=\"v\" id=\"c-prompt\">0</div></div>\n <div class=\"card\"><div class=\"k\">Completion tokens</div><div class=\"v cyan\" id=\"c-completion\">0</div></div>\n <div class=\"card\"><div class=\"k\">Avg speed</div><div class=\"v green\" id=\"c-avgtps\">\u2013</div><div class=\"s\">tokens/sec</div></div>\n <div class=\"card\"><div class=\"k\">Best speed</div><div class=\"v green\" id=\"c-besttps\">\u2013</div></div>\n <div class=\"card\"><div class=\"k\">Last TTFT</div><div class=\"v amber\" id=\"c-ttft\">\u2013</div><div class=\"s\">time to first token</div></div>\n <div class=\"card\"><div class=\"k\">Messages</div><div class=\"v\" id=\"c-msgs\">0</div><div class=\"s\" id=\"c-runs\">0 runs tracked</div></div>\n <div class=\"card\"><div class=\"k\">Uptime</div><div class=\"v\" id=\"c-uptime\">0:00</div><div class=\"s\" id=\"c-session\">no session</div></div>\n </div>\n\n <div class=\"grid2\">\n <div class=\"panel\"><h3>Tokens per run</h3><canvas id=\"ch-runs\"></canvas></div>\n <div class=\"panel\"><h3>Token split</h3><canvas id=\"ch-split\"></canvas></div>\n </div>\n\n <div class=\"main\">\n <div class=\"panel\"><h3>Conversation</h3><div id=\"feed\"><div class=\"empty\">No messages yet.</div></div></div>\n <div class=\"panel composer-panel\">\n <h3>Chat with this agent</h3>\n <div class=\"composer\">\n <textarea id=\"chat-in\" placeholder=\"Ask anything\u2026 (shares the same brain as the terminal session)\"></textarea>\n <div class=\"rowbtn\">\n <button class=\"send\" id=\"chat-send\">Send \u23ce</button>\n <span class=\"hint\" id=\"chat-hint\">Streams from the live model.</span>\n </div>\n <div class=\"hint\">Enter to send \u00b7 Shift+Enter newline</div>\n </div>\n </div>\n </div>\n\n <div class=\"bottom\">\n <div class=\"panel\"><h3>Sessions</h3>\n <table><thead><tr><th>ID</th><th>Title</th><th>Msgs</th><th>Updated</th></tr></thead>\n <tbody id=\"sess-rows\"><tr><td colspan=\"4\" style=\"color:var(--dim)\">\u2014</td></tr></tbody></table>\n </div>\n <div class=\"panel\"><h3>Registered tools (<span id=\"tool-count\">0</span>)</h3>\n <div class=\"tools\" id=\"tools-list\"><span style=\"color:var(--dim)\">\u2014</span></div>\n </div>\n </div>\n\n <footer>\n <span>OGPT v0.1.7-beta(devices-all) \u00b7 dashboard bound to <code>127.0.0.1</code> only</span>\n <span>polling every 1.5s \u00b7 <code>/api/stats</code></span>\n </footer>\n</div>\n\n<script>\nconst $=id=>document.getElementById(id);\nconst fmt=n=>Number(n||0).toLocaleString();\nlet runsChart=null, splitChart=null;\nlet lastHistoryKey=\"\", busy=false;\n\nfunction fmtDur(s){s=Math.floor(s||0);const h=Math.floor(s/3600),m=Math.floor(s%3600/60),\n x=s%60;return (h?h+\":\":\"\")+m+\":\"+String(x).padStart(2,\"0\");}\nfunction setText(id,v){$(id).textContent=v;}\n\nasync function poll(){\n try{\n const d=await (await fetch(\"/api/stats\")).json();\n setText(\"chip-model\",\"\");$(\"chip-model\").innerHTML=\"<b>\"+d.model+\"</b>\";\n $(\"chip-provider\").textContent=\"provider \"+d.provider+\" \u00b7 \"+d.cwd;\n setText(\"c-total\",fmt(d.tokens.total));\n setText(\"c-total-s\",fmt(d.tokens.prompt)+\" prompt + \"+fmt(d.tokens.completion)+\" completion\");\n setText(\"c-prompt\",fmt(d.tokens.prompt));\n setText(\"c-completion\",fmt(d.tokens.completion));\n setText(\"c-avgtps\",d.avg_tps?d.avg_tps.toFixed(1):\"\u2013\");\n setText(\"c-besttps\",d.best_tps?d.best_tps.toFixed(1):\"\u2013\");\n const lr=d.last_run||{};\n setText(\"c-ttft\",(lr.ttft!=null)?lr.ttft.toFixed(2)+\"s\":\"\u2013\");\n setText(\"c-msgs\",fmt(d.messages));\n setText(\"c-runs\",fmt(d.runs_count)+\" runs tracked\");\n setText(\"c-uptime\",fmtDur(d.uptime_s));\n setText(\"c-session\",d.session?(\"session \"+d.session):\"no session\");\n updateCharts(d);\n }catch(e){/* server gone quiet */}\n}\n\nfunction ensureCharts(){\n if(!window.Chart||runsChart)return;\n Chart.defaults.color=\"#8b93a7\";Chart.defaults.borderColor=\"#232a3a\";\n runsChart=new Chart($(\"ch-runs\"),{type:\"line\",\n data:{labels:[],datasets:[{label:\"tokens\",data:[],borderColor:\"#e34fd0\",\n backgroundColor:\"rgba(227,79,208,.15)\",fill:true,tension:.35,pointRadius:2}]},\n options:{animation:false,maintainAspectRatio:false,\n scales:{y:{beginAtZero:true},x:{ticks:{maxTicksLimit:10}}}}});\n splitChart=new Chart($(\"ch-split\"),{type:\"doughnut\",\n data:{labels:[\"prompt\",\"completion\"],\n datasets:[{data:[0,0],backgroundColor:[\"#39c6e3\",\"#e34fd0\"],borderWidth:0}]},\n options:{animation:false,maintainAspectRatio:false,cutout:\"62%\",\n plugins:{legend:{position:\"bottom\"}}}});\n}\nfunction updateCharts(d){\n ensureCharts();if(!runsChart)return;\n const rs=d.runs||[];\n runsChart.data.labels=rs.map(r=>new Date(r.ts*1000).toLocaleTimeString());\n runsChart.data.datasets[0].data=rs.map(r=>r.tokens);\n runsChart.update(\"none\");\n splitChart.data.datasets[0].data=[d.tokens.prompt,d.tokens.completion];\n splitChart.update(\"none\");\n}\n\nasync function loadHistory(force){\n try{\n const msgs=await (await fetch(\"/api/history\")).json();\n const key=msgs.length+\":\"+((msgs[msgs.length-1]||{}).ts||0);\n if(!force&&key===lastHistoryKey)return;\n lastHistoryKey=key;\n const feed=$(\"feed\");feed.textContent=\"\";\n if(!msgs.length){feed.innerHTML='<div class=\"empty\">No messages yet.</div>';return;}\n for(const m of msgs){\n const div=document.createElement(\"div\");\n div.className=\"msg \"+(m.role===\"user\"?\"user\":\"bot\");\n const who=document.createElement(\"div\");who.className=\"who\";\n who.textContent=m.role===\"user\"?\"\u276f You\":\"\u25cf oGPT\";\n const txt=document.createElement(\"div\");txt.className=\"txt\";\n txt.textContent=m.content;\n div.append(who,txt);\n if(m.ts){const meta=document.createElement(\"div\");meta.className=\"meta\";\n meta.textContent=new Date(m.ts*1000).toLocaleString();div.append(meta);}\n feed.appendChild(div);\n }\n feed.scrollTop=feed.scrollHeight;\n }catch(e){}\n}\n\nfunction addBubble(kind){\n const div=document.createElement(\"div\");\n div.className=\"msg \"+kind;\n const who=document.createElement(\"div\");who.className=\"who\";\n who.textContent=kind===\"user\"?\"\u276f You\":\"\u25cf oGPT\";\n const txt=document.createElement(\"div\");txt.className=\"txt\";\n if(kind!==\"user\"){const m=document.createElement(\"div\");m.className=\"meta\";\n m.textContent=\"streaming\u2026\";txt.dataset.meta=m;txt.append(m);}\n div.append(who,txt);$(\"feed\").appendChild(div);\n $(\"feed\").scrollTop=$(\"feed\").scrollHeight;\n return txt;\n}\nfunction sendChat(){\n if(busy)return;\n const inp=$(\"chat-in\"),msg=inp.value.trim();\n if(!msg)return;\n inp.value=\"\";busy=true;$(\"chat-send\").disabled=true;\n addBubble(\"user\");\n const bot=addBubble(\"bot\");\n let acc=\"\";\n const es=new EventSource(\"/api/chat/stream?msg=\"+encodeURIComponent(msg));\n es.onmessage=e=>{\n let d;try{d=JSON.parse(e.data);}catch(_){return;}\n if(d.t){acc+=d.t;bot.textContent=acc;\n $(\"feed\").scrollTop=$(\"feed\").scrollHeight;}\n if(d.error){acc+=\"\\n[error] \"+d.error;bot.textContent=acc;}\n if(d.done){es.close();\n const s=d.stats||{};const bits=[];\n if(s.tokens)bits.push(s.tokens+\" tok\");\n if(s.tps)bits.push(s.tps+\" tok/s\");\n bot.dataset.meta&&(bot.querySelector(\".meta\")||{}).remove;\n const meta=document.createElement(\"div\");meta.className=\"meta\";\n meta.textContent=bits.length?bits.join(\" \u00b7 \"):\"done\";\n bot.parentElement.appendChild(meta);\n busy=false;$(\"chat-send\").disabled=false;loadHistory(true);}\n };\n es.onerror=()=>{es.close();busy=false;$(\"chat-send\").disabled=false;};\n}\n$(\"chat-send\").addEventListener(\"click\",sendChat);\n$(\"chat-in\").addEventListener(\"keydown\",ev=>{\n if(ev.key===\"Enter\"&&!ev.shiftKey){ev.preventDefault();sendChat();}\n});\n\nasync function loadBottom(){\n try{\n const rows=await (await fetch(\"/api/sessions\")).json();\n const tb=$(\"sess-rows\");tb.textContent=\"\";\n if(!rows.length){tb.innerHTML='<tr><td colspan=\"4\" style=\"color:var(--dim)\">\u2014</td></tr>';return;}\n for(const s of rows){\n const tr=document.createElement(\"tr\");\n [s.id,s.title,s.msgs,new Date((s.updated||0)*1000).toLocaleString()].forEach(v=>{\n const td=document.createElement(\"td\");td.textContent=v??\"\";tr.appendChild(td);});\n tb.appendChild(tr);\n }\n }catch(e){}\n try{\n const names=await (await fetch(\"/api/tools\")).json();\n $(\"tool-count\").textContent=names.length;\n const list=$(\"tools-list\");list.textContent=\"\";\n for(const n of names){const c=document.createElement(\"span\");\n c.className=\"toolchip\";c.textContent=n;list.appendChild(c);}\n }catch(e){}\n}\n\npoll();loadHistory();loadBottom();\nsetInterval(poll,1500);\nsetInterval(()=>loadHistory(false),5000);\nsetInterval(loadBottom,20000);\nensureCharts();\n</script>\n</body>\n</html>\n";
|
package/src/web.js
CHANGED
|
@@ -111,7 +111,7 @@ function createDashboard(cli) {
|
|
|
111
111
|
return json(res, 200, {
|
|
112
112
|
ok: true,
|
|
113
113
|
app: "OGPT",
|
|
114
|
-
version: "0.1.
|
|
114
|
+
version: "0.1.7-beta(devices-all)",
|
|
115
115
|
model: name,
|
|
116
116
|
provider: "Ollama",
|
|
117
117
|
uptime_s: Math.round((Date.now() - STARTED_AT) / 100) / 10,
|
|
@@ -140,7 +140,7 @@ function createDashboard(cli) {
|
|
|
140
140
|
} catch {}
|
|
141
141
|
return json(res, 200, {
|
|
142
142
|
ok: true,
|
|
143
|
-
version: "0.1.
|
|
143
|
+
version: "0.1.7-beta(devices-all)",
|
|
144
144
|
model: name,
|
|
145
145
|
model_id: name,
|
|
146
146
|
provider: "Ollama",
|