hermes-web-ui 0.1.2 → 0.1.3
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 +117 -408
- package/bin/hermes-web-ui.mjs +111 -97
- package/dist/assets/{ChatView-WnNeYrS7.js → ChatView-BBqtEbUW.js} +20 -20
- package/dist/assets/ChatView-DC6_7Uwg.css +1 -0
- package/dist/assets/JobsView-sQ8sqrxF.js +356 -0
- package/dist/assets/LogsView-BN_TkDPi.css +1 -0
- package/dist/assets/LogsView-DukKyFJt.js +1 -0
- package/dist/assets/Modal-oIDM0xXN.js +232 -0
- package/dist/assets/Spin-zWt--szS.js +476 -0
- package/dist/assets/Tooltip-BGvPCNBt.js +1 -0
- package/dist/assets/Warning-B9_T2nKK.js +1 -0
- package/dist/assets/_plugin-vue_export-helper-V8xgnEJh.js +231 -0
- package/dist/assets/chat-CGF6ipPC.js +5 -0
- package/dist/assets/fade-in-scale-up.cssr-DQYNrBys.js +45 -0
- package/dist/assets/index-BsLYVWlc.js +307 -0
- package/dist/assets/index-DDAe8BhJ.css +1 -0
- package/dist/assets/{jobs-Cuol6Mqb.js → jobs-QHXENDTQ.js} +1 -1
- package/dist/assets/use-message-42wmA96q.js +117 -0
- package/dist/index.html +8 -6
- package/dist/server/config.d.ts +7 -0
- package/dist/server/config.js +12 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +154 -0
- package/dist/server/routes/logs.d.ts +2 -0
- package/dist/server/routes/logs.js +87 -0
- package/dist/server/routes/proxy-handler.d.ts +2 -0
- package/dist/server/routes/proxy-handler.js +82 -0
- package/dist/server/routes/proxy.d.ts +2 -0
- package/dist/server/routes/proxy.js +12 -0
- package/dist/server/routes/sessions.d.ts +2 -0
- package/dist/server/routes/sessions.js +69 -0
- package/dist/server/routes/upload.d.ts +2 -0
- package/dist/server/routes/upload.js +50 -0
- package/dist/server/routes/webhook.d.ts +2 -0
- package/dist/server/routes/webhook.js +33 -0
- package/dist/server/services/hermes-cli.d.ts +50 -0
- package/dist/server/services/hermes-cli.js +185 -0
- package/dist/server/services/hermes.d.ts +40 -0
- package/dist/server/services/hermes.js +118 -0
- package/package.json +19 -4
- package/dist/assets/ChatView-DrzZz5ex.css +0 -1
- package/dist/assets/JobsView-BF9wWdwU.js +0 -831
- package/dist/assets/Modal-CQVLL_Oc.js +0 -276
- package/dist/assets/_plugin-vue_export-helper-D5N3Hfca.js +0 -231
- package/dist/assets/chat-640V6r6N.js +0 -1
- package/dist/assets/index-4iFlrAYJ.js +0 -307
- package/dist/assets/index-CMJKLUKk.css +0 -1
- package/dist/assets/use-message-B8ClBznx.js +0 -117
- /package/dist/assets/{client-BxIiwrqC.js → client-ZYGCrm9m.js} +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.sessionRoutes = void 0;
|
|
40
|
+
const router_1 = __importDefault(require("@koa/router"));
|
|
41
|
+
const hermesCli = __importStar(require("../services/hermes-cli"));
|
|
42
|
+
exports.sessionRoutes = new router_1.default();
|
|
43
|
+
// List sessions from Hermes
|
|
44
|
+
exports.sessionRoutes.get('/api/sessions', async (ctx) => {
|
|
45
|
+
const source = ctx.query.source || undefined;
|
|
46
|
+
const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : undefined;
|
|
47
|
+
const sessions = await hermesCli.listSessions(source, limit);
|
|
48
|
+
ctx.body = { sessions };
|
|
49
|
+
});
|
|
50
|
+
// Get single session with messages
|
|
51
|
+
exports.sessionRoutes.get('/api/sessions/:id', async (ctx) => {
|
|
52
|
+
const session = await hermesCli.getSession(ctx.params.id);
|
|
53
|
+
if (!session) {
|
|
54
|
+
ctx.status = 404;
|
|
55
|
+
ctx.body = { error: 'Session not found' };
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
ctx.body = { session };
|
|
59
|
+
});
|
|
60
|
+
// Delete session from Hermes
|
|
61
|
+
exports.sessionRoutes.delete('/api/sessions/:id', async (ctx) => {
|
|
62
|
+
const ok = await hermesCli.deleteSession(ctx.params.id);
|
|
63
|
+
if (!ok) {
|
|
64
|
+
ctx.status = 500;
|
|
65
|
+
ctx.body = { error: 'Failed to delete session' };
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
ctx.body = { ok: true };
|
|
69
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadRoutes = void 0;
|
|
7
|
+
const router_1 = __importDefault(require("@koa/router"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const promises_1 = require("fs/promises");
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
exports.uploadRoutes = new router_1.default();
|
|
12
|
+
exports.uploadRoutes.post('/upload', async (ctx) => {
|
|
13
|
+
const contentType = ctx.get('content-type') || '';
|
|
14
|
+
if (!contentType.startsWith('multipart/form-data')) {
|
|
15
|
+
ctx.status = 400;
|
|
16
|
+
ctx.body = { error: 'Expected multipart/form-data' };
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const boundary = '--' + contentType.split('boundary=')[1];
|
|
20
|
+
if (!boundary || boundary === '--undefined') {
|
|
21
|
+
ctx.status = 400;
|
|
22
|
+
ctx.body = { error: 'Missing boundary' };
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await (0, promises_1.mkdir)(config_1.config.uploadDir, { recursive: true });
|
|
26
|
+
// Read raw body
|
|
27
|
+
const chunks = [];
|
|
28
|
+
for await (const chunk of ctx.req)
|
|
29
|
+
chunks.push(chunk);
|
|
30
|
+
const body = Buffer.concat(chunks).toString('latin1');
|
|
31
|
+
const parts = body.split(boundary).slice(1, -1);
|
|
32
|
+
const results = [];
|
|
33
|
+
for (const part of parts) {
|
|
34
|
+
const headerEnd = part.indexOf('\r\n\r\n');
|
|
35
|
+
if (headerEnd === -1)
|
|
36
|
+
continue;
|
|
37
|
+
const header = part.substring(0, headerEnd);
|
|
38
|
+
const data = part.substring(headerEnd + 4, part.length - 2);
|
|
39
|
+
const filenameMatch = header.match(/filename="([^"]+)"/);
|
|
40
|
+
if (!filenameMatch)
|
|
41
|
+
continue;
|
|
42
|
+
const filename = filenameMatch[1];
|
|
43
|
+
const ext = filename.includes('.') ? '.' + filename.split('.').pop() : '';
|
|
44
|
+
const savedName = (0, crypto_1.randomBytes)(8).toString('hex') + ext;
|
|
45
|
+
const savedPath = `${config_1.config.uploadDir}/${savedName}`;
|
|
46
|
+
await (0, promises_1.writeFile)(savedPath, Buffer.from(data, 'binary'));
|
|
47
|
+
results.push({ name: filename, path: savedPath });
|
|
48
|
+
}
|
|
49
|
+
ctx.body = { files: results };
|
|
50
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.webhookRoutes = void 0;
|
|
7
|
+
const router_1 = __importDefault(require("@koa/router"));
|
|
8
|
+
const hermes_1 = require("../services/hermes");
|
|
9
|
+
exports.webhookRoutes = new router_1.default();
|
|
10
|
+
/**
|
|
11
|
+
* POST /webhook — receive callbacks from Hermes Agent
|
|
12
|
+
*
|
|
13
|
+
* Expected body:
|
|
14
|
+
* {
|
|
15
|
+
* "event": "run.completed" | "job.completed" | ...,
|
|
16
|
+
* "run_id": "...",
|
|
17
|
+
* "data": { ... }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* TODO: Add signature verification when Hermes supports webhook signing
|
|
21
|
+
*/
|
|
22
|
+
exports.webhookRoutes.post('/webhook', async (ctx) => {
|
|
23
|
+
const payload = ctx.request.body;
|
|
24
|
+
if (!payload || !payload.event) {
|
|
25
|
+
ctx.status = 400;
|
|
26
|
+
ctx.body = { error: 'Missing event field' };
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`[Webhook] Received event: ${payload.event}`);
|
|
30
|
+
// Emit to registered callbacks
|
|
31
|
+
(0, hermes_1.emitWebhook)(payload);
|
|
32
|
+
ctx.body = { ok: true };
|
|
33
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface HermesSession {
|
|
2
|
+
id: string;
|
|
3
|
+
source: string;
|
|
4
|
+
user_id: string | null;
|
|
5
|
+
model: string;
|
|
6
|
+
title: string | null;
|
|
7
|
+
started_at: number;
|
|
8
|
+
ended_at: number | null;
|
|
9
|
+
end_reason: string | null;
|
|
10
|
+
message_count: number;
|
|
11
|
+
tool_call_count: number;
|
|
12
|
+
input_tokens: number;
|
|
13
|
+
output_tokens: number;
|
|
14
|
+
billing_provider: string | null;
|
|
15
|
+
estimated_cost_usd: number;
|
|
16
|
+
messages?: any[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* List sessions from Hermes CLI (without messages)
|
|
20
|
+
*/
|
|
21
|
+
export declare function listSessions(source?: string, limit?: number): Promise<HermesSession[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Get a single session with messages from Hermes CLI
|
|
24
|
+
*/
|
|
25
|
+
export declare function getSession(id: string): Promise<HermesSession | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Delete a session from Hermes CLI
|
|
28
|
+
*/
|
|
29
|
+
export declare function deleteSession(id: string): Promise<boolean>;
|
|
30
|
+
export interface LogFileInfo {
|
|
31
|
+
name: string;
|
|
32
|
+
size: string;
|
|
33
|
+
modified: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get Hermes version
|
|
37
|
+
*/
|
|
38
|
+
export declare function getVersion(): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Update Hermes Agent
|
|
41
|
+
*/
|
|
42
|
+
export declare function restartGateway(): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* List available log files
|
|
45
|
+
*/
|
|
46
|
+
export declare function listLogFiles(): Promise<LogFileInfo[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Read log lines
|
|
49
|
+
*/
|
|
50
|
+
export declare function readLogs(logName?: string, lines?: number, level?: string, session?: string, since?: string): Promise<string>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listSessions = listSessions;
|
|
4
|
+
exports.getSession = getSession;
|
|
5
|
+
exports.deleteSession = deleteSession;
|
|
6
|
+
exports.getVersion = getVersion;
|
|
7
|
+
exports.restartGateway = restartGateway;
|
|
8
|
+
exports.listLogFiles = listLogFiles;
|
|
9
|
+
exports.readLogs = readLogs;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const util_1 = require("util");
|
|
12
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
13
|
+
/**
|
|
14
|
+
* List sessions from Hermes CLI (without messages)
|
|
15
|
+
*/
|
|
16
|
+
async function listSessions(source, limit) {
|
|
17
|
+
const args = ['sessions', 'export', '-'];
|
|
18
|
+
if (source)
|
|
19
|
+
args.push('--source', source);
|
|
20
|
+
try {
|
|
21
|
+
const { stdout } = await execFileAsync('hermes', args, {
|
|
22
|
+
maxBuffer: 50 * 1024 * 1024, // 50MB
|
|
23
|
+
timeout: 30000,
|
|
24
|
+
});
|
|
25
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
26
|
+
const sessions = [];
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
try {
|
|
29
|
+
const raw = JSON.parse(line);
|
|
30
|
+
sessions.push({
|
|
31
|
+
id: raw.id,
|
|
32
|
+
source: raw.source,
|
|
33
|
+
user_id: raw.user_id,
|
|
34
|
+
model: raw.model,
|
|
35
|
+
title: raw.title,
|
|
36
|
+
started_at: raw.started_at,
|
|
37
|
+
ended_at: raw.ended_at,
|
|
38
|
+
end_reason: raw.end_reason,
|
|
39
|
+
message_count: raw.message_count,
|
|
40
|
+
tool_call_count: raw.tool_call_count,
|
|
41
|
+
input_tokens: raw.input_tokens,
|
|
42
|
+
output_tokens: raw.output_tokens,
|
|
43
|
+
billing_provider: raw.billing_provider,
|
|
44
|
+
estimated_cost_usd: raw.estimated_cost_usd,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch { /* skip malformed lines */ }
|
|
48
|
+
}
|
|
49
|
+
// Sort by started_at descending
|
|
50
|
+
sessions.sort((a, b) => b.started_at - a.started_at);
|
|
51
|
+
if (limit && limit > 0) {
|
|
52
|
+
return sessions.slice(0, limit);
|
|
53
|
+
}
|
|
54
|
+
return sessions;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error('[Hermes CLI] sessions export failed:', err.message);
|
|
58
|
+
throw new Error(`Failed to list sessions: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get a single session with messages from Hermes CLI
|
|
63
|
+
*/
|
|
64
|
+
async function getSession(id) {
|
|
65
|
+
const args = ['sessions', 'export', '-', '--session-id', id];
|
|
66
|
+
try {
|
|
67
|
+
const { stdout } = await execFileAsync('hermes', args, {
|
|
68
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
69
|
+
timeout: 30000,
|
|
70
|
+
});
|
|
71
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
72
|
+
if (lines.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
const raw = JSON.parse(lines[0]);
|
|
75
|
+
return {
|
|
76
|
+
id: raw.id,
|
|
77
|
+
source: raw.source,
|
|
78
|
+
user_id: raw.user_id,
|
|
79
|
+
model: raw.model,
|
|
80
|
+
title: raw.title,
|
|
81
|
+
started_at: raw.started_at,
|
|
82
|
+
ended_at: raw.ended_at,
|
|
83
|
+
end_reason: raw.end_reason,
|
|
84
|
+
message_count: raw.message_count,
|
|
85
|
+
tool_call_count: raw.tool_call_count,
|
|
86
|
+
input_tokens: raw.input_tokens,
|
|
87
|
+
output_tokens: raw.output_tokens,
|
|
88
|
+
billing_provider: raw.billing_provider,
|
|
89
|
+
estimated_cost_usd: raw.estimated_cost_usd,
|
|
90
|
+
messages: raw.messages,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err.code === 1 || err.status === 1)
|
|
95
|
+
return null;
|
|
96
|
+
console.error('[Hermes CLI] session export failed:', err.message);
|
|
97
|
+
throw new Error(`Failed to get session: ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Delete a session from Hermes CLI
|
|
102
|
+
*/
|
|
103
|
+
async function deleteSession(id) {
|
|
104
|
+
try {
|
|
105
|
+
await execFileAsync('hermes', ['sessions', 'delete', id, '--yes'], {
|
|
106
|
+
timeout: 10000,
|
|
107
|
+
});
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.error('[Hermes CLI] session delete failed:', err.message);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get Hermes version
|
|
117
|
+
*/
|
|
118
|
+
async function getVersion() {
|
|
119
|
+
try {
|
|
120
|
+
const { stdout } = await execFileAsync('hermes', ['--version'], { timeout: 5000 });
|
|
121
|
+
return stdout.trim();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Update Hermes Agent
|
|
129
|
+
*/
|
|
130
|
+
async function restartGateway() {
|
|
131
|
+
const { stdout, stderr } = await execFileAsync('hermes', ['gateway', 'restart'], {
|
|
132
|
+
timeout: 30000,
|
|
133
|
+
});
|
|
134
|
+
return stdout || stderr;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* List available log files
|
|
138
|
+
*/
|
|
139
|
+
async function listLogFiles() {
|
|
140
|
+
try {
|
|
141
|
+
const { stdout } = await execFileAsync('hermes', ['logs', 'list'], {
|
|
142
|
+
timeout: 10000,
|
|
143
|
+
});
|
|
144
|
+
const files = [];
|
|
145
|
+
const lines = stdout.trim().split('\n').filter(l => l.includes('.log'));
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
const match = line.match(/^\s+(\S+)\s+([\d.]+\w+)\s+(.+)$/);
|
|
148
|
+
if (match) {
|
|
149
|
+
const rawName = match[1];
|
|
150
|
+
const name = rawName.replace(/\.log$/, '');
|
|
151
|
+
if (['agent', 'errors', 'gateway'].includes(name)) {
|
|
152
|
+
files.push({ name, size: match[2], modified: match[3].trim() });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return files;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
console.error('[Hermes CLI] logs list failed:', err.message);
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Read log lines
|
|
165
|
+
*/
|
|
166
|
+
async function readLogs(logName = 'agent', lines = 100, level, session, since) {
|
|
167
|
+
const args = ['logs', logName, '-n', String(lines)];
|
|
168
|
+
if (level)
|
|
169
|
+
args.push('--level', level);
|
|
170
|
+
if (session)
|
|
171
|
+
args.push('--session', session);
|
|
172
|
+
if (since)
|
|
173
|
+
args.push('--since', since);
|
|
174
|
+
try {
|
|
175
|
+
const { stdout } = await execFileAsync('hermes', args, {
|
|
176
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
177
|
+
timeout: 15000,
|
|
178
|
+
});
|
|
179
|
+
return stdout;
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
console.error('[Hermes CLI] logs read failed:', err.message);
|
|
183
|
+
throw new Error(`Failed to read logs: ${err.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send an instruction to Hermes Agent via /v1/runs
|
|
3
|
+
*/
|
|
4
|
+
export declare function sendInstruction(params: {
|
|
5
|
+
input: string | any[];
|
|
6
|
+
instructions?: string;
|
|
7
|
+
conversationHistory?: any[];
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
authToken?: string;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
run_id: string;
|
|
12
|
+
status: string;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Get run status (poll /v1/runs/:id if supported)
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRunStatus(runId: string): Promise<any>;
|
|
18
|
+
/**
|
|
19
|
+
* Subscribe to SSE events for a run
|
|
20
|
+
*/
|
|
21
|
+
export declare function streamRunEvents(runId: string, authToken?: string): AsyncGenerator<any>;
|
|
22
|
+
/**
|
|
23
|
+
* Health check
|
|
24
|
+
*/
|
|
25
|
+
export declare function healthCheck(): Promise<{
|
|
26
|
+
status: string;
|
|
27
|
+
version?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Fetch available models
|
|
31
|
+
*/
|
|
32
|
+
export declare function fetchModels(): Promise<{
|
|
33
|
+
data: Array<{
|
|
34
|
+
id: string;
|
|
35
|
+
}>;
|
|
36
|
+
}>;
|
|
37
|
+
type WebhookCallback = (payload: any) => void | Promise<void>;
|
|
38
|
+
export declare function onWebhook(callback: WebhookCallback): void;
|
|
39
|
+
export declare function emitWebhook(payload: any): void;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendInstruction = sendInstruction;
|
|
4
|
+
exports.getRunStatus = getRunStatus;
|
|
5
|
+
exports.streamRunEvents = streamRunEvents;
|
|
6
|
+
exports.healthCheck = healthCheck;
|
|
7
|
+
exports.fetchModels = fetchModels;
|
|
8
|
+
exports.onWebhook = onWebhook;
|
|
9
|
+
exports.emitWebhook = emitWebhook;
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const UPSTREAM = config_1.config.upstream.replace(/\/$/, '');
|
|
12
|
+
/**
|
|
13
|
+
* Send an instruction to Hermes Agent via /v1/runs
|
|
14
|
+
*/
|
|
15
|
+
async function sendInstruction(params) {
|
|
16
|
+
const headers = {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
};
|
|
19
|
+
if (params.authToken) {
|
|
20
|
+
headers['Authorization'] = `Bearer ${params.authToken}`;
|
|
21
|
+
}
|
|
22
|
+
const body = { input: params.input };
|
|
23
|
+
if (params.instructions)
|
|
24
|
+
body.instructions = params.instructions;
|
|
25
|
+
if (params.conversationHistory)
|
|
26
|
+
body.conversation_history = params.conversationHistory;
|
|
27
|
+
if (params.sessionId)
|
|
28
|
+
body.session_id = params.sessionId;
|
|
29
|
+
const res = await fetch(`${UPSTREAM}/v1/runs`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers,
|
|
32
|
+
body: JSON.stringify(body),
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
throw new Error(`Hermes API error ${res.status}: ${text}`);
|
|
37
|
+
}
|
|
38
|
+
return res.json();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get run status (poll /v1/runs/:id if supported)
|
|
42
|
+
*/
|
|
43
|
+
async function getRunStatus(runId) {
|
|
44
|
+
const res = await fetch(`${UPSTREAM}/v1/runs/${runId}`);
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(`Failed to get run status: ${res.status}`);
|
|
47
|
+
}
|
|
48
|
+
return res.json();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Subscribe to SSE events for a run
|
|
52
|
+
*/
|
|
53
|
+
async function* streamRunEvents(runId, authToken) {
|
|
54
|
+
const headers = {};
|
|
55
|
+
if (authToken) {
|
|
56
|
+
headers['Authorization'] = `Bearer ${authToken}`;
|
|
57
|
+
}
|
|
58
|
+
const res = await fetch(`${UPSTREAM}/v1/runs/${runId}/events`, { headers });
|
|
59
|
+
if (!res.ok || !res.body) {
|
|
60
|
+
throw new Error(`Failed to stream run events: ${res.status}`);
|
|
61
|
+
}
|
|
62
|
+
const reader = res.body.getReader();
|
|
63
|
+
const decoder = new TextDecoder();
|
|
64
|
+
let buffer = '';
|
|
65
|
+
try {
|
|
66
|
+
while (true) {
|
|
67
|
+
const { done, value } = await reader.read();
|
|
68
|
+
if (done)
|
|
69
|
+
break;
|
|
70
|
+
buffer += decoder.decode(value, { stream: true });
|
|
71
|
+
const lines = buffer.split('\n');
|
|
72
|
+
buffer = lines.pop() || '';
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
if (line.startsWith('data: ')) {
|
|
75
|
+
const data = line.slice(6).trim();
|
|
76
|
+
if (data === '[DONE]')
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
const event = JSON.parse(data);
|
|
80
|
+
yield event;
|
|
81
|
+
if (event.event === 'run.completed' || event.event === 'run.failed')
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
catch { /* skip malformed lines */ }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
reader.releaseLock();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Health check
|
|
95
|
+
*/
|
|
96
|
+
async function healthCheck() {
|
|
97
|
+
const res = await fetch(`${UPSTREAM}/health`);
|
|
98
|
+
return res.json();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fetch available models
|
|
102
|
+
*/
|
|
103
|
+
async function fetchModels() {
|
|
104
|
+
const res = await fetch(`${UPSTREAM}/v1/models`);
|
|
105
|
+
return res.json();
|
|
106
|
+
}
|
|
107
|
+
const webhookCallbacks = [];
|
|
108
|
+
function onWebhook(callback) {
|
|
109
|
+
webhookCallbacks.push(callback);
|
|
110
|
+
}
|
|
111
|
+
function emitWebhook(payload) {
|
|
112
|
+
for (const cb of webhookCallbacks) {
|
|
113
|
+
const result = cb(payload);
|
|
114
|
+
if (result && typeof result.catch === 'function') {
|
|
115
|
+
result.catch((err) => console.error('Webhook callback error:', err));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermes-web-ui",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.1.3",
|
|
5
4
|
"description": "Hermes Agent Web UI - Chat and Job Management Dashboard",
|
|
6
5
|
"repository": {
|
|
7
6
|
"type": "git",
|
|
@@ -14,8 +13,10 @@
|
|
|
14
13
|
},
|
|
15
14
|
"scripts": {
|
|
16
15
|
"start": "vite --host --port 8648",
|
|
17
|
-
"dev": "
|
|
18
|
-
"
|
|
16
|
+
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
|
|
17
|
+
"dev:client": "vite --host",
|
|
18
|
+
"dev:server": "nodemon --watch server/src --ext ts --exec node -r ts-node/register server/src/index.ts",
|
|
19
|
+
"build": "vue-tsc -b && vite build && tsc -p server/tsconfig.json",
|
|
19
20
|
"preview": "vite preview"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
@@ -23,7 +24,13 @@
|
|
|
23
24
|
"dist/"
|
|
24
25
|
],
|
|
25
26
|
"dependencies": {
|
|
27
|
+
"@koa/bodyparser": "^5.0.0",
|
|
28
|
+
"@koa/cors": "^5.0.0",
|
|
29
|
+
"@koa/router": "^13.1.0",
|
|
26
30
|
"highlight.js": "^11.11.1",
|
|
31
|
+
"koa": "^2.15.3",
|
|
32
|
+
"koa-send": "^5.0.1",
|
|
33
|
+
"koa-static": "^5.0.0",
|
|
27
34
|
"markdown-it": "^14.1.1",
|
|
28
35
|
"naive-ui": "^2.44.1",
|
|
29
36
|
"pinia": "^3.0.4",
|
|
@@ -31,11 +38,19 @@
|
|
|
31
38
|
"vue-router": "^4.6.4"
|
|
32
39
|
},
|
|
33
40
|
"devDependencies": {
|
|
41
|
+
"@types/koa": "^2.15.0",
|
|
42
|
+
"@types/koa__cors": "^5.0.0",
|
|
43
|
+
"@types/koa__router": "^12.0.4",
|
|
44
|
+
"@types/koa-send": "^4.1.6",
|
|
45
|
+
"@types/koa-static": "^4.0.4",
|
|
34
46
|
"@types/markdown-it": "^14.1.2",
|
|
35
47
|
"@types/node": "^24.12.2",
|
|
36
48
|
"@vitejs/plugin-vue": "^6.0.5",
|
|
37
49
|
"@vue/tsconfig": "^0.9.1",
|
|
50
|
+
"concurrently": "^9.2.1",
|
|
51
|
+
"nodemon": "^3.1.14",
|
|
38
52
|
"sass": "^1.99.0",
|
|
53
|
+
"ts-node": "^10.9.2",
|
|
39
54
|
"typescript": "~6.0.2",
|
|
40
55
|
"vite": "^8.0.4",
|
|
41
56
|
"vue-tsc": "^3.2.6"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.markdown-body{font-size:14px;line-height:1.65}.markdown-body p{margin:0 0 8px}.markdown-body p:last-child{margin-bottom:0}.markdown-body ul,.markdown-body ol{margin:4px 0 8px;padding-left:20px}.markdown-body li{margin:2px 0}.markdown-body strong{color:#1a1a1a;font-weight:600}.markdown-body em{color:#666}.markdown-body a{color:#333;text-underline-offset:2px;text-decoration:underline}.markdown-body a:hover{color:#1a1a1a}.markdown-body blockquote{color:#666;border-left:3px solid #e0e0e0;margin:8px 0;padding:4px 12px}.markdown-body code:not(.hljs){color:#333;background:#f4f4f4;border-radius:4px;padding:2px 6px;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:13px}.markdown-body table{border-collapse:collapse;width:100%;margin:8px 0}.markdown-body table th,.markdown-body table td{text-align:left;border:1px solid #e0e0e0;padding:6px 12px;font-size:13px}.markdown-body table th{color:#1a1a1a;background:#33333314;font-weight:600}.markdown-body table td{color:#666}.markdown-body hr{border:none;border-top:1px solid #e0e0e0;margin:12px 0}.hljs-code-block{background:#f4f4f4;border:1px solid #e0e0e0;border-radius:6px;margin:8px 0;overflow:hidden}.hljs-code-block .code-header{background:#00000008;border-bottom:1px solid #e0e0e0;justify-content:space-between;align-items:center;padding:6px 12px;display:flex}.hljs-code-block .code-header .code-lang{color:#999;text-transform:uppercase;font-size:11px}.hljs-code-block .code-header .copy-btn{color:#999;cursor:pointer;background:0 0;border:none;border-radius:3px;padding:2px 6px;font-size:11px;transition:all .15s}.hljs-code-block .code-header .copy-btn:hover{color:#1a1a1a;background:#0000000d}.hljs-code-block code.hljs{padding:12px;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:13px;line-height:1.5;display:block;overflow-x:auto}.hljs{color:#2a2a2a;background:0 0}.hljs-keyword,.hljs-selector-tag{color:#1a1a1a;font-weight:600}.hljs-string,.hljs-attr{color:#555}.hljs-number{color:#333}.hljs-comment{color:#999;font-style:italic}.hljs-built_in{color:#444}.hljs-type{color:#3a3a3a}.hljs-variable,.hljs-title,.hljs-title\.function_{color:#1a1a1a}.hljs-params{color:#2a2a2a}.hljs-meta{color:#999}.message[data-v-865df6f5]{flex-direction:column;display:flex}.message.user[data-v-865df6f5]{align-items:flex-end}.message.user .msg-body[data-v-865df6f5]{max-width:75%}.message.user .msg-content.user[data-v-865df6f5]{align-items:flex-end}.message.user .message-bubble[data-v-865df6f5]{background-color:#e8e8e8;border-radius:10px 10px 4px}.message.assistant[data-v-865df6f5]{flex-direction:row;align-items:flex-start;gap:8px}.message.assistant .msg-body[data-v-865df6f5]{max-width:80%}.message.assistant .msg-avatar[data-v-865df6f5]{border-radius:4px;flex-shrink:0;width:28px;height:28px;margin-top:2px}.message.assistant .message-bubble[data-v-865df6f5]{background-color:#f5f5f5;border-radius:10px 10px 10px 4px}.message.tool[data-v-865df6f5],.message.system[data-v-865df6f5]{align-items:flex-start}.message.system .message-bubble.system[data-v-865df6f5]{background-color:#f57f170f;border-left:3px solid #f57f17;border-radius:6px;max-width:80%}.msg-body[data-v-865df6f5]{align-items:flex-start;gap:8px;max-width:85%;display:flex}.msg-content[data-v-865df6f5]{flex-direction:column;min-width:0;display:flex}.message-bubble[data-v-865df6f5]{word-break:break-word;padding:10px 14px;font-size:14px;line-height:1.65}.message-time[data-v-865df6f5]{color:#999;margin-top:4px;padding:0 4px;font-size:11px}.tool-line[data-v-865df6f5]{color:#999;align-items:center;gap:6px;padding:0 4px;font-size:11px;display:flex}.tool-line .tool-name[data-v-865df6f5]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.tool-line .tool-preview[data-v-865df6f5]{text-overflow:ellipsis;white-space:nowrap;max-width:400px;overflow:hidden}.streaming-cursor[data-v-865df6f5]{vertical-align:text-bottom;background-color:#999;width:2px;height:1em;margin-left:2px;animation:.8s infinite blink-865df6f5;display:inline-block}.streaming-dots[data-v-865df6f5]{gap:4px;padding:4px 0;display:flex}.streaming-dots span[data-v-865df6f5]{background-color:#999;border-radius:50%;width:6px;height:6px;animation:1.4s ease-in-out infinite pulse-865df6f5}.streaming-dots span[data-v-865df6f5]:nth-child(2){animation-delay:.2s}.streaming-dots span[data-v-865df6f5]:nth-child(3){animation-delay:.4s}@keyframes blink-865df6f5{0%,50%{opacity:1}51%,to{opacity:0}}@keyframes pulse-865df6f5{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}.message-list[data-v-c48f8f02]{flex-direction:column;flex:1;gap:16px;padding:20px;display:flex;overflow-y:auto}.empty-state[data-v-c48f8f02]{color:#999;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:12px;display:flex}.empty-state .empty-logo[data-v-c48f8f02]{opacity:.25;width:48px;height:48px}.empty-state p[data-v-c48f8f02]{font-size:14px}.streaming-indicator[data-v-c48f8f02]{color:#999;align-items:center;gap:4px;padding:4px;display:flex}.streaming-indicator span[data-v-c48f8f02]{background-color:#999;border-radius:50%;width:5px;height:5px;animation:1.4s ease-in-out infinite stream-pulse-c48f8f02}.streaming-indicator span[data-v-c48f8f02]:nth-child(2){animation-delay:.2s}.streaming-indicator span[data-v-c48f8f02]:nth-child(3){animation-delay:.4s}@keyframes stream-pulse-c48f8f02{0%,80%,to{opacity:.2;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}.chat-input-area[data-v-7668b7e3]{border-top:1px solid #e0e0e0;flex-shrink:0;padding:12px 20px 16px}.input-wrapper[data-v-7668b7e3]{background-color:#fff;border:1px solid #e0e0e0;border-radius:10px;align-items:center;gap:10px;padding:10px 12px;transition:border-color .15s;display:flex}.input-wrapper[data-v-7668b7e3]:focus-within{border-color:#333}.input-textarea[data-v-7668b7e3]{color:#1a1a1a;resize:none;background:0 0;border:none;outline:none;flex:1;min-height:20px;max-height:100px;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:14px;line-height:1.5;overflow-y:auto}.input-textarea[data-v-7668b7e3]::placeholder{color:#999}.input-actions[data-v-7668b7e3]{flex-shrink:0;align-items:center;gap:6px;display:flex}.chat-panel[data-v-24185ee8]{height:100%;display:flex}.session-list[data-v-24185ee8]{border-right:1px solid #e0e0e0;flex-direction:column;flex-shrink:0;width:220px;transition:width .25s,opacity .25s;display:flex;overflow:hidden}.session-list.collapsed[data-v-24185ee8]{opacity:0;pointer-events:none;border-right:none;width:0}.session-list-header[data-v-24185ee8]{flex-shrink:0;justify-content:space-between;align-items:center;padding:12px;display:flex}.session-list-title[data-v-24185ee8]{color:#999;text-transform:uppercase;letter-spacing:.5px;font-size:12px;font-weight:600}.session-items[data-v-24185ee8]{flex:1;padding:0 6px 12px;overflow-y:auto}.session-item[data-v-24185ee8]{cursor:pointer;text-align:left;color:#666;background:0 0;border:none;border-radius:6px;justify-content:space-between;align-items:center;width:100%;margin-bottom:2px;padding:8px 10px;transition:all .15s;display:flex}.session-item[data-v-24185ee8]:hover{color:#1a1a1a;background:#3333330f}.session-item:hover .session-item-delete[data-v-24185ee8]{opacity:1}.session-item.active[data-v-24185ee8]{color:#1a1a1a;background:#3333331a;font-weight:500}.session-item-content[data-v-24185ee8]{flex:1;overflow:hidden}.session-item-title[data-v-24185ee8]{white-space:nowrap;text-overflow:ellipsis;font-size:13px;display:block;overflow:hidden}.session-item-time[data-v-24185ee8]{color:#999;margin-top:2px;font-size:11px;display:block}.session-item-delete[data-v-24185ee8]{opacity:0;color:#999;cursor:pointer;background:0 0;border:none;border-radius:3px;flex-shrink:0;padding:2px;transition:all .15s}.session-item-delete[data-v-24185ee8]:hover{color:#c62828;background:#c628281a}.chat-main[data-v-24185ee8]{flex-direction:column;flex:1;min-width:0;display:flex;overflow:hidden}.chat-header[data-v-24185ee8]{border-bottom:1px solid #e0e0e0;flex-shrink:0;justify-content:space-between;align-items:center;padding:12px 16px;display:flex}.header-left[data-v-24185ee8]{align-items:center;gap:8px;display:flex;overflow:hidden}.header-session-title[data-v-24185ee8]{color:#1a1a1a;white-space:nowrap;text-overflow:ellipsis;font-size:14px;font-weight:500;overflow:hidden}.model-badge[data-v-24185ee8]{color:#999;background:#3333331a;border:1px solid #e0e0e0;border-radius:10px;flex-shrink:0;padding:2px 8px;font-size:11px}.header-actions[data-v-24185ee8]{flex-shrink:0;align-items:center;gap:4px;display:flex}.chat-view[data-v-e67fc0ba]{flex-direction:column;height:100vh;display:flex}
|