halbot 1995.1.34 → 1995.1.35
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/index.mjs +1 -0
- package/lib/hal.mjs +19 -4
- package/package.json +2 -2
- package/pipeline/080_history.mjs +2 -2
- package/pipeline/100_chat.mjs +17 -2
- package/web/turn.html +587 -0
- package/web/turn.mjs +43 -0
package/index.mjs
CHANGED
package/lib/hal.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import { alan, bot, callosum, dbio, storage, utilitas } from 'utilitas';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { parseArgs as _parseArgs } from 'node:util';
|
|
4
4
|
import { readdirSync } from 'fs';
|
|
5
|
-
import {
|
|
5
|
+
import { webjam } from 'webjam';
|
|
6
|
+
import path from 'node:path';
|
|
6
7
|
|
|
7
8
|
// 👇 https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this
|
|
8
9
|
const [ // https://limits.tginfo.me/en
|
|
@@ -15,6 +16,9 @@ const [ // https://limits.tginfo.me/en
|
|
|
15
16
|
'☑️', { log: true }, 32, 256,
|
|
16
17
|
];
|
|
17
18
|
|
|
19
|
+
const { __filename } = utilitas.__(import.meta.url);
|
|
20
|
+
const workdir = path.dirname(__filename);
|
|
21
|
+
const getPath = (subPath) => { return path.join(workdir, subPath || ''); };
|
|
18
22
|
const table = 'utilitas_hal_events';
|
|
19
23
|
const log = (c, o) => utilitas.log(c, import.meta.url, { time: 1, ...o || {} });
|
|
20
24
|
const [end] = [bot.end];
|
|
@@ -41,6 +45,7 @@ const initSql = {
|
|
|
41
45
|
\`collected\` TEXT NOT NULL,
|
|
42
46
|
\`distilled\` TEXT NOT NULL,
|
|
43
47
|
\`distilled_vector\` TEXT NOT NULL,
|
|
48
|
+
\`token\` VARCHAR(255) NOT NULL DEFAULT '',
|
|
44
49
|
\`created_at\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
45
50
|
\`updated_at\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
46
51
|
PRIMARY KEY (\`id\`),
|
|
@@ -54,6 +59,7 @@ const initSql = {
|
|
|
54
59
|
INDEX response_text (\`response_text\`(768)),
|
|
55
60
|
INDEX collected (\`collected\`(768)),
|
|
56
61
|
FULLTEXT INDEX distilled (\`distilled\`),
|
|
62
|
+
INDEX token (\`token\`),
|
|
57
63
|
INDEX created_at (\`created_at\`),
|
|
58
64
|
INDEX updated_at (\`updated_at\`)
|
|
59
65
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`), [table],
|
|
@@ -72,6 +78,7 @@ const initSql = {
|
|
|
72
78
|
collected TEXT NOT NULL,
|
|
73
79
|
distilled TEXT NOT NULL,
|
|
74
80
|
distilled_vector VECTOR(768) NOT NULL,
|
|
81
|
+
token VARCHAR(255) NOT NULL DEFAULT '',
|
|
75
82
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
76
83
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
77
84
|
)`)
|
|
@@ -97,6 +104,8 @@ const initSql = {
|
|
|
97
104
|
`CREATE INDEX IF NOT EXISTS ${table}_distilled_index ON ${table} USING GIN(to_tsvector('english', distilled))`,
|
|
98
105
|
], [
|
|
99
106
|
`CREATE INDEX IF NOT EXISTS ${table}_distilled_vector_index ON ${table} USING hnsw(distilled_vector vector_cosine_ops)`,
|
|
107
|
+
], [
|
|
108
|
+
`CREATE INDEX IF NOT EXISTS ${table}_token_index ON ${table} (token)`,
|
|
100
109
|
], [
|
|
101
110
|
`CREATE INDEX IF NOT EXISTS ${table}_created_at_index ON ${table} (created_at)`,
|
|
102
111
|
], [
|
|
@@ -149,13 +158,18 @@ const parseArgs = async (args, ctx) => {
|
|
|
149
158
|
|
|
150
159
|
const subconscious = {
|
|
151
160
|
name: 'Subconscious', run: true, priority: 0,
|
|
152
|
-
func: async (_, next) => { ignoreErrFunc(next, logOptions) }, // non-blocking
|
|
161
|
+
func: async (_, next) => { utilitas.ignoreErrFunc(next, logOptions) }, // non-blocking
|
|
153
162
|
};
|
|
154
163
|
|
|
155
164
|
const init = async (options) => {
|
|
156
165
|
if (options) {
|
|
157
|
-
const
|
|
166
|
+
const web = options?.web && await webjam.init({
|
|
167
|
+
port: options?.web?.port || 9000,
|
|
168
|
+
controllerPath: options?.web?.controllerPath || getPath('../web'),
|
|
169
|
+
cfTunnel: options?.web?.cfTunnel,
|
|
170
|
+
});
|
|
158
171
|
if (callosum.isPrimary) {
|
|
172
|
+
const { ais } = await alan.initChat({ sessions: null });
|
|
159
173
|
const cmds = options?.cmds || [];
|
|
160
174
|
// config multimodal engines
|
|
161
175
|
const supportedMimeTypes = new Set(ais.map(x => {
|
|
@@ -182,6 +196,7 @@ const init = async (options) => {
|
|
|
182
196
|
rerank: options?.rerank,
|
|
183
197
|
storage: options?.storage,
|
|
184
198
|
supportedMimeTypes,
|
|
199
|
+
web,
|
|
185
200
|
};
|
|
186
201
|
if (_.storage) {
|
|
187
202
|
assert([
|
|
@@ -218,7 +233,7 @@ const init = async (options) => {
|
|
|
218
233
|
await parseArgs(); // Validate args options.
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
|
-
assert(_, 'Bot have not been initialized.', 501);
|
|
236
|
+
assert(!callosum.isPrimary || _, 'Bot have not been initialized.', 501);
|
|
222
237
|
return _;
|
|
223
238
|
};
|
|
224
239
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halbot",
|
|
3
3
|
"description": "Just another AI powered Telegram bot, which is simple design, easy to use, extendable and fun.",
|
|
4
|
-
"version": "1995.1.
|
|
4
|
+
"version": "1995.1.35",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/Leask/halbot",
|
|
7
7
|
"type": "module",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"telegraf": "^4.16.3",
|
|
49
49
|
"tellegram": "^1.1.4",
|
|
50
50
|
"tesseract.js": "^7.0.0",
|
|
51
|
-
"
|
|
51
|
+
"webjam": "^1995.3.3",
|
|
52
52
|
"youtube-transcript": "^1.2.1"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/pipeline/080_history.mjs
CHANGED
|
@@ -118,7 +118,7 @@ const memorize = async (ctx) => {
|
|
|
118
118
|
chat_type: ctx._.chatType, message_id: ctx._.messageId,
|
|
119
119
|
received: JSON.stringify(received), received_text,
|
|
120
120
|
response: JSON.stringify(response), response_text,
|
|
121
|
-
collected: JSON.stringify(collected), distilled,
|
|
121
|
+
collected: JSON.stringify(collected), distilled, token: ctx._.token,
|
|
122
122
|
};
|
|
123
123
|
await utilitas.ignoreErrFunc(async () => {
|
|
124
124
|
event.distilled_vector = hal._.embed
|
|
@@ -173,7 +173,7 @@ const action = async (ctx, next) => {
|
|
|
173
173
|
|| ctx._.message.message_id,
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
|
-
result.length === hal.SEARCH_LIMIT ? await ctx.resp('
|
|
176
|
+
result.length === hal.SEARCH_LIMIT ? await ctx.resp('---', {
|
|
177
177
|
buttons: [{
|
|
178
178
|
label: '🔍 More',
|
|
179
179
|
text: `/search@${ctx.botInfo.username} ${keywords} `
|
package/pipeline/100_chat.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { alan } from '../index.mjs';
|
|
2
|
+
import { token } from 'webjam';
|
|
2
3
|
|
|
3
4
|
const _name = 'Chat';
|
|
4
5
|
const log = (c, o) => utilitas.log(c, _name, { time: 1, ...o || {} });
|
|
@@ -66,11 +67,25 @@ const action = async (ctx, next) => {
|
|
|
66
67
|
await ctx.timeout();
|
|
67
68
|
}
|
|
68
69
|
// console.log('Finished');
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
let lastMsg;
|
|
71
|
+
if (resp.text.trim()) {
|
|
72
|
+
lastMsg = await ok({ processing: false });
|
|
73
|
+
} else {
|
|
74
|
+
await ctx.deleteMessage(ctx._.done[0].message_id);
|
|
75
|
+
}
|
|
71
76
|
ctx._.request = resp.request;
|
|
72
77
|
ctx._.response = resp.response;
|
|
78
|
+
ctx._.token = token.newId();
|
|
73
79
|
ctx.memorize && await ctx.memorize();
|
|
80
|
+
// @todo:
|
|
81
|
+
// 1: reduce safe page range
|
|
82
|
+
// 2: adding ctx.append feature
|
|
83
|
+
// 3: Consider the layout for small screens, such as the footer and other elements.
|
|
84
|
+
// 4: using webjam config instead of hardcoding the url
|
|
85
|
+
await ctx.edit(lastMsg.message_id,
|
|
86
|
+
lastMsg.raw
|
|
87
|
+
+ `\n\n\-\-\-\n\n✨ [View in well-formatted page](https://hal.leaskh.com/turns/${ctx._.token}).`
|
|
88
|
+
);
|
|
74
89
|
await next();
|
|
75
90
|
};
|
|
76
91
|
|
package/web/turn.html
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>HAL9000 Chat</title>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link
|
|
12
|
+
href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Inter:wght@300;400;600&display=swap"
|
|
13
|
+
rel="stylesheet">
|
|
14
|
+
<style>
|
|
15
|
+
:root {
|
|
16
|
+
--bg-color: #0a0a0c;
|
|
17
|
+
--header-bg: rgba(255, 255, 255, 0.4);
|
|
18
|
+
--footer-bg: rgba(255, 255, 255, 0.4);
|
|
19
|
+
--border-color: rgba(0, 0, 0, 0.1);
|
|
20
|
+
/* Lighter border */
|
|
21
|
+
--primary-color: #00f0ff;
|
|
22
|
+
/* Sci-fi Cyan */
|
|
23
|
+
--text-color: #333;
|
|
24
|
+
/* Dark text for light background */
|
|
25
|
+
--robot-bg: #1a1a1f;
|
|
26
|
+
--human-bg: #003340;
|
|
27
|
+
--robot-border: #00f0ff;
|
|
28
|
+
--human-border: #ff0055;
|
|
29
|
+
--font-display: 'Orbitron', sans-serif;
|
|
30
|
+
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Abstract Fluid Background */
|
|
34
|
+
#neural-canvas {
|
|
35
|
+
position: fixed;
|
|
36
|
+
top: 0;
|
|
37
|
+
left: 0;
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
z-index: -2;
|
|
41
|
+
background: #f0f0f0;
|
|
42
|
+
/* Key to the liquid effect: heavy blur and high saturation */
|
|
43
|
+
filter: blur(80px) saturate(150%) contrast(120%);
|
|
44
|
+
opacity: 0.8;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
body,
|
|
49
|
+
html {
|
|
50
|
+
margin: 0;
|
|
51
|
+
padding: 0;
|
|
52
|
+
background-color: transparent;
|
|
53
|
+
color: var(--text-color);
|
|
54
|
+
font-family: var(--font-body);
|
|
55
|
+
height: 100%;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
/* Header */
|
|
61
|
+
header {
|
|
62
|
+
position: fixed;
|
|
63
|
+
top: 0;
|
|
64
|
+
left: 0;
|
|
65
|
+
right: 0;
|
|
66
|
+
height: 70px;
|
|
67
|
+
background-color: var(--header-bg);
|
|
68
|
+
border-bottom: 1px solid var(--border-color);
|
|
69
|
+
display: block;
|
|
70
|
+
/* Changed from flex */
|
|
71
|
+
padding: 0;
|
|
72
|
+
/* Removed padding */
|
|
73
|
+
z-index: 1000;
|
|
74
|
+
backdrop-filter: blur(5px);
|
|
75
|
+
box-shadow: 0 2px 20px rgba(0, 240, 255, 0.1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.header-content {
|
|
79
|
+
width: 100%;
|
|
80
|
+
max-width: 900px;
|
|
81
|
+
margin: 0 auto;
|
|
82
|
+
padding: 0 20px;
|
|
83
|
+
height: 100%;
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: space-between;
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.brand {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 1rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.brand-link {
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
gap: 1rem;
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
color: inherit;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.logo {
|
|
105
|
+
width: 32px;
|
|
106
|
+
height: 32px;
|
|
107
|
+
fill: var(--primary-color);
|
|
108
|
+
/* Removed drop-shadow */
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.brand-name {
|
|
112
|
+
font-family: var(--font-body);
|
|
113
|
+
font-size: 1.5rem;
|
|
114
|
+
font-weight: 700;
|
|
115
|
+
letter-spacing: 2px;
|
|
116
|
+
color: #333;
|
|
117
|
+
/* Dark gray */
|
|
118
|
+
/* Removed text-shadow */
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.session-info {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
align-items: flex-end;
|
|
125
|
+
gap: 2px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.chat-id {
|
|
129
|
+
font-family: var(--font-body);
|
|
130
|
+
font-size: 0.9rem;
|
|
131
|
+
color: #888;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Main Content */
|
|
135
|
+
main {
|
|
136
|
+
height: 100vh;
|
|
137
|
+
/* Fallback */
|
|
138
|
+
height: 100dvh;
|
|
139
|
+
/* Mobile viewport fix */
|
|
140
|
+
padding-top: calc(70px + 1rem);
|
|
141
|
+
/* Header height + Gap (same as gap between bubbles) */
|
|
142
|
+
padding-bottom: 80px;
|
|
143
|
+
/* Footer height + gap */
|
|
144
|
+
padding-left: 20px;
|
|
145
|
+
padding-right: 20px;
|
|
146
|
+
box-sizing: border-box;
|
|
147
|
+
overflow-y: auto;
|
|
148
|
+
-webkit-overflow-scrolling: touch;
|
|
149
|
+
/* iOS Momentum Scrolling */
|
|
150
|
+
scroll-behavior: smooth;
|
|
151
|
+
overscroll-behavior-y: contain;
|
|
152
|
+
/* Prevent pull-to-refresh on body */
|
|
153
|
+
max-width: 900px;
|
|
154
|
+
margin: 0 auto;
|
|
155
|
+
width: 100%;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.chat-container {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 1rem;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.message-row {
|
|
165
|
+
display: flex;
|
|
166
|
+
width: 100%;
|
|
167
|
+
opacity: 0;
|
|
168
|
+
animation: fadeIn 0.3s forwards;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@keyframes fadeIn {
|
|
172
|
+
to {
|
|
173
|
+
opacity: 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.message-row.robot {
|
|
178
|
+
justify-content: flex-start;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.message-row.human {
|
|
182
|
+
justify-content: flex-start;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.message-group {
|
|
186
|
+
width: 100%;
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
gap: 0.5rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.sender-name-inside {
|
|
193
|
+
font-family: var(--font-body);
|
|
194
|
+
font-size: 13px;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
padding: 8px 16px;
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: space-between;
|
|
200
|
+
/* Push time to right */
|
|
201
|
+
gap: 6px;
|
|
202
|
+
/* Header background darker than bubble */
|
|
203
|
+
background: rgba(0, 0, 0, 0.05);
|
|
204
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
205
|
+
border-radius: 8px 8px 0 0;
|
|
206
|
+
color: #333;
|
|
207
|
+
/* Dark gray for all */
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.message-time {
|
|
211
|
+
font-weight: 400;
|
|
212
|
+
opacity: 0.6;
|
|
213
|
+
font-size: 0.85em;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
.message-bubble {
|
|
218
|
+
padding: 0;
|
|
219
|
+
/* Removed padding from container */
|
|
220
|
+
border-radius: 8px;
|
|
221
|
+
font-size: 16px;
|
|
222
|
+
line-height: 1.5;
|
|
223
|
+
position: relative;
|
|
224
|
+
background: rgba(255, 255, 255, 0.6);
|
|
225
|
+
/* Removed border-left */
|
|
226
|
+
color: #333;
|
|
227
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
|
228
|
+
backdrop-filter: blur(5px);
|
|
229
|
+
display: flex;
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.message-content {
|
|
234
|
+
padding: 1rem;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.human .message-bubble {
|
|
238
|
+
border-left: none;
|
|
239
|
+
border-right: none;
|
|
240
|
+
color: #333;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Markdown Styles */
|
|
244
|
+
.message-bubble h1,
|
|
245
|
+
.message-bubble h2,
|
|
246
|
+
.message-bubble h3 {
|
|
247
|
+
margin-top: 0;
|
|
248
|
+
font-family: var(--font-display);
|
|
249
|
+
color: #333;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.message-bubble code {
|
|
253
|
+
background: rgba(0, 0, 0, 0.05);
|
|
254
|
+
/* Light gray */
|
|
255
|
+
padding: 2px 6px;
|
|
256
|
+
border-radius: 4px;
|
|
257
|
+
font-family: 'Courier New', Courier, monospace;
|
|
258
|
+
font-size: 0.9em;
|
|
259
|
+
color: #d63384;
|
|
260
|
+
/* Pinkish code color */
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.message-bubble pre {
|
|
264
|
+
background: #f4f4f4;
|
|
265
|
+
/* Light background */
|
|
266
|
+
padding: 1rem;
|
|
267
|
+
border-radius: 4px;
|
|
268
|
+
overflow-x: auto;
|
|
269
|
+
border: 1px solid #ddd;
|
|
270
|
+
color: #333;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.message-bubble pre code {
|
|
274
|
+
background: transparent;
|
|
275
|
+
padding: 0;
|
|
276
|
+
color: inherit;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Table Styles */
|
|
280
|
+
.message-bubble table {
|
|
281
|
+
width: 100%;
|
|
282
|
+
border-collapse: collapse;
|
|
283
|
+
margin: 1rem 0;
|
|
284
|
+
font-size: 0.95em;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.message-bubble th,
|
|
288
|
+
.message-bubble td {
|
|
289
|
+
padding: 8px 12px;
|
|
290
|
+
border: 1px solid #ddd;
|
|
291
|
+
text-align: left;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.message-bubble th {
|
|
295
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
296
|
+
font-weight: 600;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.message-bubble p {
|
|
300
|
+
margin: 0 0 1rem 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.message-bubble p:last-child {
|
|
304
|
+
margin: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* Footer */
|
|
308
|
+
footer {
|
|
309
|
+
position: fixed;
|
|
310
|
+
bottom: 0;
|
|
311
|
+
left: 0;
|
|
312
|
+
right: 0;
|
|
313
|
+
height: 60px;
|
|
314
|
+
background-color: var(--footer-bg);
|
|
315
|
+
border-top: 1px solid var(--border-color);
|
|
316
|
+
display: block;
|
|
317
|
+
padding: 0;
|
|
318
|
+
color: #444;
|
|
319
|
+
font-family: var(--font-body);
|
|
320
|
+
font-size: 0.8rem;
|
|
321
|
+
z-index: 1000;
|
|
322
|
+
backdrop-filter: blur(5px);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
footer a {
|
|
326
|
+
color: #666;
|
|
327
|
+
text-decoration: none;
|
|
328
|
+
transition: color 0.2s;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
footer a:hover {
|
|
332
|
+
color: var(--primary-color);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.footer-content {
|
|
336
|
+
width: 100%;
|
|
337
|
+
max-width: 900px;
|
|
338
|
+
margin: 0 auto;
|
|
339
|
+
padding: 0 20px;
|
|
340
|
+
height: 100%;
|
|
341
|
+
display: flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
justify-content: space-between;
|
|
344
|
+
box-sizing: border-box;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
</style>
|
|
348
|
+
</head>
|
|
349
|
+
|
|
350
|
+
<body>
|
|
351
|
+
|
|
352
|
+
<canvas id="neural-canvas"></canvas>
|
|
353
|
+
|
|
354
|
+
<header>
|
|
355
|
+
<div class="header-content">
|
|
356
|
+
<div class="brand">
|
|
357
|
+
<a href="https://github.com/Leask/halbot" target="_blank" class="brand-link">
|
|
358
|
+
<!-- HAL9000 Eye Logo -->
|
|
359
|
+
<svg class="logo" viewBox="0 0 100 100">
|
|
360
|
+
<defs>
|
|
361
|
+
<radialGradient id="lens-glow" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
|
|
362
|
+
<stop offset="0%" style="stop-color:#ffeb3b;stop-opacity:1" />
|
|
363
|
+
<stop offset="20%" style="stop-color:#ff9800;stop-opacity:1" />
|
|
364
|
+
<stop offset="60%" style="stop-color:#f44336;stop-opacity:1" />
|
|
365
|
+
<stop offset="100%" style="stop-color:#b71c1c;stop-opacity:1" />
|
|
366
|
+
</radialGradient>
|
|
367
|
+
<linearGradient id="metal-rim" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
368
|
+
<stop offset="0%" style="stop-color:#e0e0e0;stop-opacity:1" />
|
|
369
|
+
<stop offset="50%" style="stop-color:#9e9e9e;stop-opacity:1" />
|
|
370
|
+
<stop offset="100%" style="stop-color:#616161;stop-opacity:1" />
|
|
371
|
+
</linearGradient>
|
|
372
|
+
</defs>
|
|
373
|
+
<!-- Outer Rim -->
|
|
374
|
+
<circle cx="50" cy="50" r="48" style="fill:url(#metal-rim);stroke:#333;stroke-width:2" />
|
|
375
|
+
<!-- Inner Housing -->
|
|
376
|
+
<circle cx="50" cy="50" r="42" style="fill:#111" />
|
|
377
|
+
<!-- The Lens -->
|
|
378
|
+
<circle cx="50" cy="50" r="28" style="fill:url(#lens-glow);stroke:#800000;stroke-width:1" />
|
|
379
|
+
<!-- Reflection highlight for realism -->
|
|
380
|
+
<circle cx="35" cy="35" r="5" style="fill:#fff;opacity:0.6;filter:blur(2px)" />
|
|
381
|
+
</svg>
|
|
382
|
+
<div class="brand-name">HAL9000</div>
|
|
383
|
+
</a>
|
|
384
|
+
</div>
|
|
385
|
+
<div class="session-info">
|
|
386
|
+
<div class="chat-id" id="chatId">Chat ID: XXXXXX</div>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
</header>
|
|
390
|
+
|
|
391
|
+
<main id="chatMain">
|
|
392
|
+
<div class="chat-container" id="chatContainer">
|
|
393
|
+
<!-- Messages will be injected here -->
|
|
394
|
+
</div>
|
|
395
|
+
</main>
|
|
396
|
+
|
|
397
|
+
<footer>
|
|
398
|
+
<div class="footer-content">
|
|
399
|
+
<span id="servedTurns">Served ... turns of conversations.</span>
|
|
400
|
+
<span id="copyright">
|
|
401
|
+
© 2023-<span id="currentYear"></span> <a href="https://LeaskH.com" target="_blank">LeaskH.com</a> |
|
|
402
|
+
<a href="https://twitter.com/LeaskH" target="_blank">@LeaskH</a>
|
|
403
|
+
</span>
|
|
404
|
+
</div>
|
|
405
|
+
</footer>
|
|
406
|
+
|
|
407
|
+
<script>
|
|
408
|
+
// Injected Data
|
|
409
|
+
const chatData = '{{data}}';
|
|
410
|
+
|
|
411
|
+
const chatId = document.getElementById('chatId');
|
|
412
|
+
const currentYear = document.getElementById('currentYear');
|
|
413
|
+
|
|
414
|
+
// Set session time and ID
|
|
415
|
+
const now = new Date();
|
|
416
|
+
currentYear.textContent = now.getFullYear(); // Copyright year
|
|
417
|
+
|
|
418
|
+
// Set Chat ID from data
|
|
419
|
+
if (chatData.chat_id) {
|
|
420
|
+
chatId.textContent = `Chat ID: ${chatData.chat_id}`;
|
|
421
|
+
} else {
|
|
422
|
+
chatId.textContent = `Chat ID: UNKNOWN`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Served Turns (Mock logic or could be from data if available)
|
|
426
|
+
const servedTurns = document.getElementById('servedTurns');
|
|
427
|
+
let totalTurns = 0;
|
|
428
|
+
if (chatData.prompt_count) {
|
|
429
|
+
totalTurns = chatData.prompt_count;
|
|
430
|
+
} else {
|
|
431
|
+
// If the server provides a total count, use it. Otherwise mock.
|
|
432
|
+
totalTurns = Math.floor(Math.random() * 5000) + 1000;
|
|
433
|
+
}
|
|
434
|
+
servedTurns.textContent = `Processed ${Number(totalTurns).toLocaleString()} prompts.`;
|
|
435
|
+
|
|
436
|
+
function renderMessage(msg) {
|
|
437
|
+
const row = document.createElement('div');
|
|
438
|
+
// Determine role class based on specific role string
|
|
439
|
+
// "HAL9000" -> robot
|
|
440
|
+
// Anything else -> human
|
|
441
|
+
const roleClass = msg.role === 'HAL9000' ? 'robot' : 'human';
|
|
442
|
+
row.className = `message-row ${roleClass}`;
|
|
443
|
+
|
|
444
|
+
const group = document.createElement('div');
|
|
445
|
+
group.className = 'message-group';
|
|
446
|
+
|
|
447
|
+
// Mapping sender name
|
|
448
|
+
const senderName = msg.role === 'HAL9000' ? '🤖 HAL9000' : `😺 ${msg.role}`;
|
|
449
|
+
|
|
450
|
+
const bubble = document.createElement('div');
|
|
451
|
+
bubble.className = 'message-bubble';
|
|
452
|
+
|
|
453
|
+
// Name inside the bubble
|
|
454
|
+
const nameDiv = document.createElement('div');
|
|
455
|
+
nameDiv.className = 'sender-name-inside';
|
|
456
|
+
|
|
457
|
+
const nameSpan = document.createElement('span');
|
|
458
|
+
nameSpan.textContent = senderName;
|
|
459
|
+
|
|
460
|
+
const timeSpan = document.createElement('span');
|
|
461
|
+
timeSpan.className = 'message-time';
|
|
462
|
+
|
|
463
|
+
// Parse time from msg.time (ISO string)
|
|
464
|
+
let msgTime = '';
|
|
465
|
+
if (msg.time) {
|
|
466
|
+
msgTime = new Date(msg.time).toLocaleString();
|
|
467
|
+
} else {
|
|
468
|
+
msgTime = new Date().toLocaleString();
|
|
469
|
+
}
|
|
470
|
+
timeSpan.textContent = msgTime;
|
|
471
|
+
|
|
472
|
+
nameDiv.appendChild(nameSpan);
|
|
473
|
+
nameDiv.appendChild(timeSpan);
|
|
474
|
+
|
|
475
|
+
bubble.appendChild(nameDiv);
|
|
476
|
+
|
|
477
|
+
// Content
|
|
478
|
+
const contentDiv = document.createElement('div');
|
|
479
|
+
contentDiv.className = 'message-content';
|
|
480
|
+
contentDiv.innerHTML = marked.parse(msg.text || ''); // Use msg.text from JSON
|
|
481
|
+
bubble.appendChild(contentDiv);
|
|
482
|
+
|
|
483
|
+
group.appendChild(bubble);
|
|
484
|
+
row.appendChild(group);
|
|
485
|
+
chatContainer.appendChild(row);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Render loop
|
|
489
|
+
let delay = 0;
|
|
490
|
+
if (chatData.messages && Array.isArray(chatData.messages)) {
|
|
491
|
+
chatData.messages.forEach(msg => {
|
|
492
|
+
setTimeout(() => {
|
|
493
|
+
renderMessage(msg);
|
|
494
|
+
// Scroll to bottom
|
|
495
|
+
const main = document.getElementById('chatMain');
|
|
496
|
+
main.scrollTop = main.scrollHeight;
|
|
497
|
+
}, delay);
|
|
498
|
+
delay += 800; // Keep delay effect
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Abstract Fluid Background Animation
|
|
503
|
+
const canvas = document.getElementById('neural-canvas');
|
|
504
|
+
if (canvas) {
|
|
505
|
+
const ctx = canvas.getContext('2d');
|
|
506
|
+
let width, height;
|
|
507
|
+
let blobs = [];
|
|
508
|
+
|
|
509
|
+
// Configuration
|
|
510
|
+
const blobCount = 12;
|
|
511
|
+
const baseRadius = 250; // Large blobs
|
|
512
|
+
|
|
513
|
+
function resize() {
|
|
514
|
+
width = canvas.width = window.innerWidth;
|
|
515
|
+
height = canvas.height = window.innerHeight;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
class Blob {
|
|
519
|
+
constructor() {
|
|
520
|
+
this.x = Math.random() * width;
|
|
521
|
+
this.y = Math.random() * height;
|
|
522
|
+
this.vx = (Math.random() - 0.5) * 1.5; // Smooth slow movement
|
|
523
|
+
this.vy = (Math.random() - 0.5) * 1.5;
|
|
524
|
+
this.radius = baseRadius + Math.random() * 150;
|
|
525
|
+
// Vibrant colors: High Saturation, Medium Lightness
|
|
526
|
+
this.hue = Math.random() * 360;
|
|
527
|
+
this.hueSpeed = 0.2 + Math.random() * 0.3;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
update() {
|
|
531
|
+
this.x += this.vx;
|
|
532
|
+
this.y += this.vy;
|
|
533
|
+
this.hue += this.hueSpeed; // Color shift
|
|
534
|
+
|
|
535
|
+
// Soft bounce off edges
|
|
536
|
+
if (this.x < -this.radius || this.x > width + this.radius) this.vx *= -1;
|
|
537
|
+
if (this.y < -this.radius || this.y > height + this.radius) this.vy *= -1;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
draw() {
|
|
541
|
+
ctx.beginPath();
|
|
542
|
+
// Radial gradient for soft edges
|
|
543
|
+
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius);
|
|
544
|
+
gradient.addColorStop(0, `hsla(${this.hue}, 85%, 60%, 0.8)`);
|
|
545
|
+
gradient.addColorStop(0.6, `hsla(${this.hue}, 85%, 60%, 0.4)`);
|
|
546
|
+
gradient.addColorStop(1, `hsla(${this.hue}, 85%, 60%, 0)`);
|
|
547
|
+
|
|
548
|
+
ctx.fillStyle = gradient;
|
|
549
|
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
550
|
+
ctx.fill();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function initBlobs() {
|
|
555
|
+
blobs = [];
|
|
556
|
+
for (let i = 0; i < blobCount; i++) {
|
|
557
|
+
blobs.push(new Blob());
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function animate() {
|
|
562
|
+
ctx.clearRect(0, 0, width, height);
|
|
563
|
+
|
|
564
|
+
// 'screen' blend mode makes colors vibrant and additive where they overlap
|
|
565
|
+
ctx.globalCompositeOperation = 'screen';
|
|
566
|
+
|
|
567
|
+
for (let i = 0; i < blobs.length; i++) {
|
|
568
|
+
blobs[i].update();
|
|
569
|
+
blobs[i].draw();
|
|
570
|
+
}
|
|
571
|
+
requestAnimationFrame(animate);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
window.addEventListener('resize', () => {
|
|
575
|
+
resize();
|
|
576
|
+
initBlobs();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
resize();
|
|
580
|
+
initBlobs();
|
|
581
|
+
animate();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
</script>
|
|
585
|
+
</body>
|
|
586
|
+
|
|
587
|
+
</html>
|
package/web/turn.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { dbio, hal, utilitas } from '../index.mjs';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
const getPath = (subPath) => utilitas.__(import.meta.url, subPath);
|
|
5
|
+
const getHtml = async () => await readFile(getPath('turn.html'), 'utf-8');
|
|
6
|
+
const renderHtml = async (data) => await getHtml().then((html) => html.replace("'{{data}}'", data));
|
|
7
|
+
|
|
8
|
+
const process = async (ctx, next) => {
|
|
9
|
+
const result = await dbio.queryOne(
|
|
10
|
+
`SELECT * FROM ${hal.table} WHERE token = $1`,
|
|
11
|
+
[ctx.params.token]
|
|
12
|
+
);
|
|
13
|
+
const prompt_count = await dbio.countAll(hal.table);
|
|
14
|
+
result.received = JSON.parse(result.received);
|
|
15
|
+
result.response = JSON.parse(result.response);
|
|
16
|
+
// print(result);
|
|
17
|
+
const messages = [{
|
|
18
|
+
role: `${result.received.message.from.username} (${result.received.message.from.first_name} ${result.received.message.from.last_name})`,
|
|
19
|
+
text: result.received_text,
|
|
20
|
+
time: new Date(result.received.message.date * 1000),
|
|
21
|
+
}];
|
|
22
|
+
result.response.forEach(r => {
|
|
23
|
+
messages.push({
|
|
24
|
+
role: 'HAL9000',
|
|
25
|
+
text: r.raw,
|
|
26
|
+
time: new Date((r.edit_date || r.date) * 1000),
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
ctx.body = await renderHtml(JSON.stringify({
|
|
30
|
+
bot_id: result.bot_id, chat_id: result.chat_id,
|
|
31
|
+
chat_type: result.chat_type, messages, prompt_count,
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const { actions } = {
|
|
36
|
+
actions: [
|
|
37
|
+
{
|
|
38
|
+
path: 'turns/:token',
|
|
39
|
+
method: 'GET',
|
|
40
|
+
process,
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
};
|