spora 0.3.1 → 0.3.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/package.json +1 -1
- package/dist/account-creator-SETL5CGT.js +0 -498
- package/dist/account-creator-SETL5CGT.js.map +0 -1
- package/dist/chunk-FCAK5FYQ.js +0 -127
- package/dist/chunk-FCAK5FYQ.js.map +0 -1
- package/dist/chunk-GJFBWIW3.js +0 -622
- package/dist/chunk-GJFBWIW3.js.map +0 -1
- package/dist/chunk-HERI4RPY.js +0 -156
- package/dist/chunk-HERI4RPY.js.map +0 -1
- package/dist/chunk-J7J557HV.js +0 -47
- package/dist/chunk-J7J557HV.js.map +0 -1
- package/dist/chunk-JWMADEQO.js +0 -57
- package/dist/chunk-JWMADEQO.js.map +0 -1
- package/dist/chunk-LRKBNKMQ.js +0 -79
- package/dist/chunk-LRKBNKMQ.js.map +0 -1
- package/dist/chunk-NLWU5432.js +0 -32
- package/dist/chunk-NLWU5432.js.map +0 -1
- package/dist/chunk-POEDIDM6.js +0 -56
- package/dist/chunk-POEDIDM6.js.map +0 -1
- package/dist/chunk-Q7YS3AIK.js +0 -63
- package/dist/chunk-Q7YS3AIK.js.map +0 -1
- package/dist/chunk-QHFM2YW6.js +0 -159
- package/dist/chunk-QHFM2YW6.js.map +0 -1
- package/dist/chunk-R7PAD4OL.js +0 -44
- package/dist/chunk-R7PAD4OL.js.map +0 -1
- package/dist/chunk-RNVEWVDN.js +0 -129
- package/dist/chunk-RNVEWVDN.js.map +0 -1
- package/dist/chunk-SUFTVQME.js +0 -82
- package/dist/chunk-SUFTVQME.js.map +0 -1
- package/dist/chunk-SXMDYUK3.js +0 -80
- package/dist/chunk-SXMDYUK3.js.map +0 -1
- package/dist/chunk-TQWLKICD.js +0 -660
- package/dist/chunk-TQWLKICD.js.map +0 -1
- package/dist/chunk-YZ7RWJ6Z.js +0 -262
- package/dist/chunk-YZ7RWJ6Z.js.map +0 -1
- package/dist/cli.js +0 -654
- package/dist/cli.js.map +0 -1
- package/dist/client-23THPNVL.js +0 -382
- package/dist/client-23THPNVL.js.map +0 -1
- package/dist/client-NVI3ZD4G.js +0 -411
- package/dist/client-NVI3ZD4G.js.map +0 -1
- package/dist/colony-J4EZQI37.js +0 -229
- package/dist/colony-J4EZQI37.js.map +0 -1
- package/dist/config-QRBOL4NX.js +0 -14
- package/dist/config-QRBOL4NX.js.map +0 -1
- package/dist/crypto-ZVWJLD2J.js +0 -14
- package/dist/crypto-ZVWJLD2J.js.map +0 -1
- package/dist/decision-engine-WBD36PZI.js +0 -19
- package/dist/decision-engine-WBD36PZI.js.map +0 -1
- package/dist/goals-IM4AEHS4.js +0 -12
- package/dist/goals-IM4AEHS4.js.map +0 -1
- package/dist/heartbeat-TUV5IREO.js +0 -317
- package/dist/heartbeat-TUV5IREO.js.map +0 -1
- package/dist/identity-LN2R4KJU.js +0 -26
- package/dist/identity-LN2R4KJU.js.map +0 -1
- package/dist/image-search-SZVMGWLN.js +0 -45
- package/dist/image-search-SZVMGWLN.js.map +0 -1
- package/dist/init-FR5OTDRJ.js +0 -403
- package/dist/init-FR5OTDRJ.js.map +0 -1
- package/dist/llm-MHZG2VHU.js +0 -16
- package/dist/llm-MHZG2VHU.js.map +0 -1
- package/dist/mcp-server.js +0 -773
- package/dist/mcp-server.js.map +0 -1
- package/dist/memory-J6AYZ5Y2.js +0 -26
- package/dist/memory-J6AYZ5Y2.js.map +0 -1
- package/dist/memory-JMXU3UXR.js +0 -26
- package/dist/memory-JMXU3UXR.js.map +0 -1
- package/dist/paths-KXOWF2B2.js +0 -13
- package/dist/paths-KXOWF2B2.js.map +0 -1
- package/dist/performance-7G6R6ELJ.js +0 -18
- package/dist/performance-7G6R6ELJ.js.map +0 -1
- package/dist/prompt-builder-EQYCNP63.js +0 -28
- package/dist/prompt-builder-EQYCNP63.js.map +0 -1
- package/dist/queue-MLRTMJRE.js +0 -14
- package/dist/queue-MLRTMJRE.js.map +0 -1
- package/dist/strategy-TOVFBIZQ.js +0 -12
- package/dist/strategy-TOVFBIZQ.js.map +0 -1
- package/dist/web-chat/chat.html +0 -1343
- package/dist/web-chat/logo.png +0 -0
- package/dist/web-chat-RZKDQYJ4.js +0 -802
- package/dist/web-chat-RZKDQYJ4.js.map +0 -1
- package/dist/x-client-HUXCQOAW.js +0 -12
- package/dist/x-client-HUXCQOAW.js.map +0 -1
package/dist/web-chat/chat.html
DELETED
|
@@ -1,1343 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Spora</title>
|
|
7
|
-
<style>
|
|
8
|
-
* {
|
|
9
|
-
margin: 0;
|
|
10
|
-
padding: 0;
|
|
11
|
-
box-sizing: border-box;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
body {
|
|
15
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
16
|
-
background: #0a0a0a;
|
|
17
|
-
color: #fff;
|
|
18
|
-
height: 100vh;
|
|
19
|
-
display: flex;
|
|
20
|
-
flex-direction: column;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.top-bar {
|
|
24
|
-
position: relative;
|
|
25
|
-
padding: 0.625rem 1rem;
|
|
26
|
-
display: flex;
|
|
27
|
-
align-items: center;
|
|
28
|
-
background: #0a0a0a;
|
|
29
|
-
flex-shrink: 0;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.top-bar .logo-img {
|
|
33
|
-
height: 20px;
|
|
34
|
-
width: auto;
|
|
35
|
-
opacity: 0.5;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/* Twitter DM-style profile header */
|
|
39
|
-
.profile-header {
|
|
40
|
-
padding: 0.5rem 1rem 1rem;
|
|
41
|
-
display: flex;
|
|
42
|
-
flex-direction: column;
|
|
43
|
-
align-items: center;
|
|
44
|
-
background: #0a0a0a;
|
|
45
|
-
flex-shrink: 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.profile-avatar {
|
|
49
|
-
width: 56px;
|
|
50
|
-
height: 56px;
|
|
51
|
-
border-radius: 50%;
|
|
52
|
-
background: #1a1a1a;
|
|
53
|
-
display: flex;
|
|
54
|
-
align-items: center;
|
|
55
|
-
justify-content: center;
|
|
56
|
-
overflow: hidden;
|
|
57
|
-
border: 2px solid rgba(255, 255, 255, 0.1);
|
|
58
|
-
margin-bottom: 0.5rem;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.profile-avatar img {
|
|
62
|
-
width: 100%;
|
|
63
|
-
height: 100%;
|
|
64
|
-
object-fit: cover;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.profile-avatar-letter {
|
|
68
|
-
font-size: 1.25rem;
|
|
69
|
-
font-weight: 700;
|
|
70
|
-
color: #fff;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.profile-name {
|
|
74
|
-
font-size: 0.9375rem;
|
|
75
|
-
font-weight: 700;
|
|
76
|
-
color: #fff;
|
|
77
|
-
line-height: 1.2;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.profile-handle {
|
|
81
|
-
font-size: 0.8125rem;
|
|
82
|
-
color: rgba(255, 255, 255, 0.45);
|
|
83
|
-
margin-top: 0.125rem;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.profile-meta {
|
|
87
|
-
display: flex;
|
|
88
|
-
align-items: center;
|
|
89
|
-
gap: 0.625rem;
|
|
90
|
-
margin-top: 0.375rem;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.profile-joined {
|
|
94
|
-
font-size: 0.75rem;
|
|
95
|
-
color: rgba(255, 255, 255, 0.35);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.profile-status {
|
|
99
|
-
display: flex;
|
|
100
|
-
align-items: center;
|
|
101
|
-
gap: 0.3rem;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.status-dot {
|
|
105
|
-
width: 6px;
|
|
106
|
-
height: 6px;
|
|
107
|
-
border-radius: 50%;
|
|
108
|
-
background: #389e77;
|
|
109
|
-
animation: pulse 2s infinite;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.status-label {
|
|
113
|
-
font-size: 0.75rem;
|
|
114
|
-
color: rgba(255, 255, 255, 0.35);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
@keyframes pulse {
|
|
118
|
-
0%, 100% { opacity: 1; }
|
|
119
|
-
50% { opacity: 0.5; }
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.chat-container {
|
|
123
|
-
flex: 1;
|
|
124
|
-
overflow-y: auto;
|
|
125
|
-
padding: 1.25rem;
|
|
126
|
-
display: flex;
|
|
127
|
-
flex-direction: column;
|
|
128
|
-
gap: 0.875rem;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.chat-container::-webkit-scrollbar {
|
|
132
|
-
width: 4px;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.chat-container::-webkit-scrollbar-track {
|
|
136
|
-
background: transparent;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.chat-container::-webkit-scrollbar-thumb {
|
|
140
|
-
background: rgba(255, 255, 255, 0.1);
|
|
141
|
-
border-radius: 4px;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.message {
|
|
145
|
-
display: flex;
|
|
146
|
-
gap: 0.625rem;
|
|
147
|
-
animation: fadeIn 0.3s ease-in;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
@keyframes fadeIn {
|
|
151
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
152
|
-
to { opacity: 1; transform: translateY(0); }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.message.user {
|
|
156
|
-
justify-content: flex-end;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.message.assistant {
|
|
160
|
-
justify-content: flex-start;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.message-avatar {
|
|
164
|
-
width: 32px;
|
|
165
|
-
height: 32px;
|
|
166
|
-
border-radius: 50%;
|
|
167
|
-
background: #1a1a1a;
|
|
168
|
-
display: flex;
|
|
169
|
-
align-items: center;
|
|
170
|
-
justify-content: center;
|
|
171
|
-
flex-shrink: 0;
|
|
172
|
-
overflow: hidden;
|
|
173
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.message-avatar img {
|
|
177
|
-
width: 100%;
|
|
178
|
-
height: 100%;
|
|
179
|
-
object-fit: cover;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.message.user .message-avatar {
|
|
183
|
-
display: none;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.message-content {
|
|
187
|
-
max-width: 80%;
|
|
188
|
-
padding: 0.5rem 0.75rem;
|
|
189
|
-
border-radius: 0.75rem;
|
|
190
|
-
font-size: 0.8125rem;
|
|
191
|
-
line-height: 1.45;
|
|
192
|
-
white-space: pre-wrap;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.message.user .message-content {
|
|
196
|
-
background: #389e77;
|
|
197
|
-
color: #fff;
|
|
198
|
-
border-bottom-right-radius: 0.25rem;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.message.assistant .message-content {
|
|
202
|
-
background: #141414;
|
|
203
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
204
|
-
border-bottom-left-radius: 0.25rem;
|
|
205
|
-
color: rgba(255, 255, 255, 0.9);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.input-container {
|
|
209
|
-
padding: 1rem 1.25rem;
|
|
210
|
-
display: flex;
|
|
211
|
-
gap: 0.5rem;
|
|
212
|
-
background: #0a0a0a;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.input-box {
|
|
216
|
-
flex: 1;
|
|
217
|
-
background: #141414;
|
|
218
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
219
|
-
border-radius: 0.625rem;
|
|
220
|
-
padding: 0.625rem 0.875rem;
|
|
221
|
-
color: #fff;
|
|
222
|
-
font-size: 0.8125rem;
|
|
223
|
-
font-family: inherit;
|
|
224
|
-
outline: none;
|
|
225
|
-
transition: border-color 0.2s;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
.input-box:focus {
|
|
229
|
-
border-color: #389e77;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.input-box::placeholder {
|
|
233
|
-
color: rgba(255, 255, 255, 0.25);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.send-button {
|
|
237
|
-
background: #389e77;
|
|
238
|
-
color: #fff;
|
|
239
|
-
border: none;
|
|
240
|
-
padding: 0.625rem 1.125rem;
|
|
241
|
-
border-radius: 0.625rem;
|
|
242
|
-
font-weight: 600;
|
|
243
|
-
font-size: 0.8125rem;
|
|
244
|
-
cursor: pointer;
|
|
245
|
-
transition: background 0.2s, transform 0.15s;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.send-button:hover:not(:disabled) {
|
|
249
|
-
background: #4ab88a;
|
|
250
|
-
transform: scale(1.02);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.send-button:disabled {
|
|
254
|
-
opacity: 0.4;
|
|
255
|
-
cursor: not-allowed;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.loading {
|
|
259
|
-
display: flex;
|
|
260
|
-
gap: 0.25rem;
|
|
261
|
-
padding: 0.125rem 0;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.loading-dot {
|
|
265
|
-
width: 5px;
|
|
266
|
-
height: 5px;
|
|
267
|
-
border-radius: 50%;
|
|
268
|
-
background: rgba(255, 255, 255, 0.3);
|
|
269
|
-
animation: bounce 1.4s infinite ease-in-out both;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
.loading-dot:nth-child(1) { animation-delay: -0.32s; }
|
|
273
|
-
.loading-dot:nth-child(2) { animation-delay: -0.16s; }
|
|
274
|
-
|
|
275
|
-
@keyframes bounce {
|
|
276
|
-
0%, 80%, 100% { transform: scale(0); }
|
|
277
|
-
40% { transform: scale(1); }
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/* X/Twitter-style tweet preview card */
|
|
281
|
-
.tweet-card {
|
|
282
|
-
background: #16181c;
|
|
283
|
-
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
284
|
-
border-radius: 12px;
|
|
285
|
-
padding: 12px;
|
|
286
|
-
margin-top: 8px;
|
|
287
|
-
max-width: 100%;
|
|
288
|
-
cursor: pointer;
|
|
289
|
-
transition: background 0.15s;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.tweet-card:hover {
|
|
293
|
-
background: #1d1f23;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.tweet-card-header {
|
|
297
|
-
display: flex;
|
|
298
|
-
align-items: center;
|
|
299
|
-
gap: 8px;
|
|
300
|
-
margin-bottom: 6px;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.tweet-card-avatar {
|
|
304
|
-
width: 32px;
|
|
305
|
-
height: 32px;
|
|
306
|
-
border-radius: 50%;
|
|
307
|
-
background: #389e77;
|
|
308
|
-
display: flex;
|
|
309
|
-
align-items: center;
|
|
310
|
-
justify-content: center;
|
|
311
|
-
flex-shrink: 0;
|
|
312
|
-
overflow: hidden;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.tweet-card-avatar img {
|
|
316
|
-
width: 100%;
|
|
317
|
-
height: 100%;
|
|
318
|
-
object-fit: cover;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
.tweet-card-avatar-letter {
|
|
322
|
-
font-size: 0.75rem;
|
|
323
|
-
font-weight: 700;
|
|
324
|
-
color: #fff;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.tweet-card-names {
|
|
328
|
-
display: flex;
|
|
329
|
-
flex-direction: column;
|
|
330
|
-
min-width: 0;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.tweet-card-name {
|
|
334
|
-
font-size: 0.8125rem;
|
|
335
|
-
font-weight: 700;
|
|
336
|
-
color: #e7e9ea;
|
|
337
|
-
line-height: 1.2;
|
|
338
|
-
white-space: nowrap;
|
|
339
|
-
overflow: hidden;
|
|
340
|
-
text-overflow: ellipsis;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
.tweet-card-handle {
|
|
344
|
-
font-size: 0.75rem;
|
|
345
|
-
color: #71767b;
|
|
346
|
-
line-height: 1.2;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.tweet-card-badge {
|
|
350
|
-
margin-left: auto;
|
|
351
|
-
flex-shrink: 0;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
.tweet-card-badge svg {
|
|
355
|
-
width: 16px;
|
|
356
|
-
height: 16px;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
.tweet-card-text {
|
|
360
|
-
font-size: 0.8125rem;
|
|
361
|
-
color: #e7e9ea;
|
|
362
|
-
line-height: 1.4;
|
|
363
|
-
white-space: pre-wrap;
|
|
364
|
-
word-break: break-word;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
.tweet-card-footer {
|
|
368
|
-
display: flex;
|
|
369
|
-
align-items: center;
|
|
370
|
-
justify-content: space-between;
|
|
371
|
-
margin-top: 8px;
|
|
372
|
-
padding-top: 6px;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
.tweet-card-meta {
|
|
376
|
-
font-size: 0.6875rem;
|
|
377
|
-
color: #71767b;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
.tweet-card-action-label {
|
|
381
|
-
font-size: 0.6875rem;
|
|
382
|
-
color: #389e77;
|
|
383
|
-
font-weight: 600;
|
|
384
|
-
text-transform: uppercase;
|
|
385
|
-
letter-spacing: 0.03em;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.tweet-card-link {
|
|
389
|
-
display: flex;
|
|
390
|
-
align-items: center;
|
|
391
|
-
gap: 4px;
|
|
392
|
-
font-size: 0.6875rem;
|
|
393
|
-
color: #1d9bf0;
|
|
394
|
-
text-decoration: none;
|
|
395
|
-
transition: opacity 0.15s;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
.tweet-card-link:hover {
|
|
399
|
-
opacity: 0.8;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.tweet-card-link svg {
|
|
403
|
-
width: 12px;
|
|
404
|
-
height: 12px;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/* Notification cards (like, retweet, follow) */
|
|
408
|
-
.notif-card {
|
|
409
|
-
display: flex;
|
|
410
|
-
align-items: flex-start;
|
|
411
|
-
gap: 10px;
|
|
412
|
-
padding: 10px 12px;
|
|
413
|
-
margin: 6px 0;
|
|
414
|
-
background: rgba(255, 255, 255, 0.03);
|
|
415
|
-
border-radius: 10px;
|
|
416
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
.notif-icon {
|
|
420
|
-
flex-shrink: 0;
|
|
421
|
-
width: 28px;
|
|
422
|
-
height: 28px;
|
|
423
|
-
display: flex;
|
|
424
|
-
align-items: center;
|
|
425
|
-
justify-content: center;
|
|
426
|
-
border-radius: 50%;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.notif-icon.like {
|
|
430
|
-
background: rgba(249, 24, 128, 0.12);
|
|
431
|
-
color: #f91880;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.notif-icon.retweet {
|
|
435
|
-
background: rgba(0, 186, 124, 0.12);
|
|
436
|
-
color: #00ba7c;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.notif-icon.follow {
|
|
440
|
-
background: rgba(29, 155, 240, 0.12);
|
|
441
|
-
color: #1d9bf0;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
.notif-icon svg {
|
|
445
|
-
width: 14px;
|
|
446
|
-
height: 14px;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
.notif-body {
|
|
450
|
-
flex: 1;
|
|
451
|
-
min-width: 0;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
.notif-label {
|
|
455
|
-
font-size: 0.75rem;
|
|
456
|
-
color: #71767b;
|
|
457
|
-
margin-bottom: 2px;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
.notif-label strong {
|
|
461
|
-
color: #e7e9ea;
|
|
462
|
-
font-weight: 600;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.notif-text {
|
|
466
|
-
font-size: 0.8rem;
|
|
467
|
-
color: rgba(255, 255, 255, 0.5);
|
|
468
|
-
overflow: hidden;
|
|
469
|
-
text-overflow: ellipsis;
|
|
470
|
-
white-space: nowrap;
|
|
471
|
-
max-width: 100%;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/* Intelligence bar */
|
|
475
|
-
.intelligence-bar {
|
|
476
|
-
position: absolute;
|
|
477
|
-
left: 50%;
|
|
478
|
-
transform: translateX(-50%);
|
|
479
|
-
width: 40%;
|
|
480
|
-
display: flex;
|
|
481
|
-
flex-direction: column;
|
|
482
|
-
align-items: center;
|
|
483
|
-
gap: 3px;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
.intelligence-label {
|
|
487
|
-
font-size: 0.5625rem;
|
|
488
|
-
color: rgba(255, 255, 255, 0.2);
|
|
489
|
-
letter-spacing: 0.03em;
|
|
490
|
-
white-space: nowrap;
|
|
491
|
-
text-align: center;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.intelligence-track {
|
|
495
|
-
width: 100%;
|
|
496
|
-
height: 3px;
|
|
497
|
-
background: rgba(255, 255, 255, 0.06);
|
|
498
|
-
border-radius: 1.5px;
|
|
499
|
-
overflow: hidden;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
.intelligence-fill {
|
|
503
|
-
height: 100%;
|
|
504
|
-
width: 0%;
|
|
505
|
-
border-radius: 1.5px;
|
|
506
|
-
background: linear-gradient(90deg, #389e77 0%, #4fc3a0 50%, #7ee8c7 100%);
|
|
507
|
-
transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/* Heartbeat config button */
|
|
511
|
-
.heartbeat-config {
|
|
512
|
-
position: relative;
|
|
513
|
-
margin-left: auto;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
.heartbeat-btn {
|
|
517
|
-
background: none;
|
|
518
|
-
border: none;
|
|
519
|
-
cursor: pointer;
|
|
520
|
-
padding: 4px;
|
|
521
|
-
display: flex;
|
|
522
|
-
align-items: center;
|
|
523
|
-
justify-content: center;
|
|
524
|
-
opacity: 0.45;
|
|
525
|
-
transition: opacity 0.2s;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
.heartbeat-btn:hover {
|
|
529
|
-
opacity: 0.8;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
.heartbeat-btn svg {
|
|
533
|
-
width: 18px;
|
|
534
|
-
height: 18px;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
.heartbeat-dropdown {
|
|
538
|
-
display: none;
|
|
539
|
-
position: absolute;
|
|
540
|
-
top: 100%;
|
|
541
|
-
right: 0;
|
|
542
|
-
margin-top: 6px;
|
|
543
|
-
background: #1a1a1a;
|
|
544
|
-
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
545
|
-
border-radius: 10px;
|
|
546
|
-
padding: 6px 0;
|
|
547
|
-
min-width: 140px;
|
|
548
|
-
z-index: 100;
|
|
549
|
-
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
.heartbeat-dropdown.open {
|
|
553
|
-
display: block;
|
|
554
|
-
animation: fadeIn 0.15s ease-out;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
.heartbeat-dropdown-label {
|
|
558
|
-
padding: 6px 14px;
|
|
559
|
-
font-size: 0.6875rem;
|
|
560
|
-
color: rgba(255, 255, 255, 0.35);
|
|
561
|
-
text-transform: uppercase;
|
|
562
|
-
letter-spacing: 0.05em;
|
|
563
|
-
font-weight: 600;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
.heartbeat-option {
|
|
567
|
-
display: flex;
|
|
568
|
-
align-items: center;
|
|
569
|
-
justify-content: space-between;
|
|
570
|
-
padding: 8px 14px;
|
|
571
|
-
font-size: 0.8125rem;
|
|
572
|
-
color: rgba(255, 255, 255, 0.75);
|
|
573
|
-
cursor: pointer;
|
|
574
|
-
transition: background 0.1s;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
.heartbeat-option:hover {
|
|
578
|
-
background: rgba(255, 255, 255, 0.06);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
.heartbeat-option.active {
|
|
582
|
-
color: #389e77;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
.heartbeat-option.active::after {
|
|
586
|
-
content: '';
|
|
587
|
-
width: 6px;
|
|
588
|
-
height: 6px;
|
|
589
|
-
border-radius: 50%;
|
|
590
|
-
background: #389e77;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/* Sleep indicator — styled like a chat message, lives inside chat container */
|
|
594
|
-
.sleep-indicator {
|
|
595
|
-
display: none;
|
|
596
|
-
gap: 0.625rem;
|
|
597
|
-
animation: fadeIn 0.4s ease-in;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
.sleep-indicator.visible {
|
|
601
|
-
display: flex;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
.sleep-avatar {
|
|
605
|
-
width: 32px;
|
|
606
|
-
height: 32px;
|
|
607
|
-
border-radius: 50%;
|
|
608
|
-
background: #1a1a1a;
|
|
609
|
-
display: flex;
|
|
610
|
-
align-items: center;
|
|
611
|
-
justify-content: center;
|
|
612
|
-
flex-shrink: 0;
|
|
613
|
-
overflow: hidden;
|
|
614
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
.sleep-avatar img {
|
|
618
|
-
width: 100%;
|
|
619
|
-
height: 100%;
|
|
620
|
-
object-fit: cover;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
.sleep-bubble {
|
|
624
|
-
display: flex;
|
|
625
|
-
align-items: center;
|
|
626
|
-
gap: 8px;
|
|
627
|
-
background: #141414;
|
|
628
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
629
|
-
border-radius: 0.75rem;
|
|
630
|
-
border-bottom-left-radius: 0.25rem;
|
|
631
|
-
padding: 0.5rem 0.75rem;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
.sleep-text {
|
|
635
|
-
font-size: 0.8125rem;
|
|
636
|
-
color: rgba(255, 255, 255, 0.4);
|
|
637
|
-
white-space: nowrap;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
.sleep-zzz {
|
|
641
|
-
display: flex;
|
|
642
|
-
align-items: baseline;
|
|
643
|
-
gap: 1px;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
.sleep-zzz span {
|
|
647
|
-
display: inline-block;
|
|
648
|
-
font-weight: 700;
|
|
649
|
-
color: rgba(255, 255, 255, 0.25);
|
|
650
|
-
animation: zzzFloat 2s ease-in-out infinite;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
.sleep-zzz span:nth-child(1) {
|
|
654
|
-
font-size: 0.5rem;
|
|
655
|
-
animation-delay: 0s;
|
|
656
|
-
}
|
|
657
|
-
.sleep-zzz span:nth-child(2) {
|
|
658
|
-
font-size: 0.65rem;
|
|
659
|
-
animation-delay: 0.3s;
|
|
660
|
-
}
|
|
661
|
-
.sleep-zzz span:nth-child(3) {
|
|
662
|
-
font-size: 0.8rem;
|
|
663
|
-
animation-delay: 0.6s;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
@keyframes zzzFloat {
|
|
667
|
-
0%, 100% { opacity: 0.3; transform: translateY(0); }
|
|
668
|
-
50% { opacity: 1; transform: translateY(-3px); }
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
.sleep-countdown {
|
|
672
|
-
font-size: 0.6875rem;
|
|
673
|
-
color: rgba(255, 255, 255, 0.2);
|
|
674
|
-
font-variant-numeric: tabular-nums;
|
|
675
|
-
margin-left: 2px;
|
|
676
|
-
}
|
|
677
|
-
</style>
|
|
678
|
-
</head>
|
|
679
|
-
<body>
|
|
680
|
-
<div class="top-bar">
|
|
681
|
-
<img class="logo-img" src="/logo.png" alt="Spora" />
|
|
682
|
-
<div class="intelligence-bar" id="intelligenceBar">
|
|
683
|
-
<span class="intelligence-label">Intelligence</span>
|
|
684
|
-
<div class="intelligence-track">
|
|
685
|
-
<div class="intelligence-fill" id="intelligenceFill"></div>
|
|
686
|
-
</div>
|
|
687
|
-
</div>
|
|
688
|
-
<div class="heartbeat-config">
|
|
689
|
-
<button class="heartbeat-btn" id="heartbeatBtn" title="Heartbeat interval">
|
|
690
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" color="#e05a5a">
|
|
691
|
-
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" fill="#e05a5a" stroke="#e05a5a"/>
|
|
692
|
-
</svg>
|
|
693
|
-
</button>
|
|
694
|
-
<div class="heartbeat-dropdown" id="heartbeatDropdown">
|
|
695
|
-
<div class="heartbeat-dropdown-label">Heartbeat interval</div>
|
|
696
|
-
<div class="heartbeat-option" data-ms="300000">5 minutes</div>
|
|
697
|
-
<div class="heartbeat-option" data-ms="1200000">20 minutes</div>
|
|
698
|
-
<div class="heartbeat-option" data-ms="3600000">1 hour</div>
|
|
699
|
-
<div class="heartbeat-option" data-ms="21600000">6 hours</div>
|
|
700
|
-
</div>
|
|
701
|
-
</div>
|
|
702
|
-
</div>
|
|
703
|
-
<div class="profile-header">
|
|
704
|
-
<div class="profile-avatar" id="profileAvatar">
|
|
705
|
-
<span class="profile-avatar-letter" id="profileAvatarLetter"></span>
|
|
706
|
-
</div>
|
|
707
|
-
<div class="profile-name" id="profileName">Your Spore</div>
|
|
708
|
-
<div class="profile-handle" id="profileHandle"></div>
|
|
709
|
-
<div class="profile-meta">
|
|
710
|
-
<span class="profile-joined" id="profileJoined"></span>
|
|
711
|
-
<div class="profile-status">
|
|
712
|
-
<span class="status-dot"></span>
|
|
713
|
-
<span class="status-label">Online</span>
|
|
714
|
-
</div>
|
|
715
|
-
</div>
|
|
716
|
-
</div>
|
|
717
|
-
|
|
718
|
-
<div class="chat-container" id="chatContainer">
|
|
719
|
-
<div class="sleep-indicator" id="sleepIndicator">
|
|
720
|
-
<div class="sleep-avatar" id="sleepAvatar"></div>
|
|
721
|
-
<div class="sleep-bubble">
|
|
722
|
-
<span class="sleep-text">Sleeping</span>
|
|
723
|
-
<div class="sleep-zzz">
|
|
724
|
-
<span>z</span><span>z</span><span>z</span>
|
|
725
|
-
</div>
|
|
726
|
-
<span class="sleep-countdown" id="sleepCountdown"></span>
|
|
727
|
-
</div>
|
|
728
|
-
</div>
|
|
729
|
-
</div>
|
|
730
|
-
|
|
731
|
-
<div class="input-container">
|
|
732
|
-
<input
|
|
733
|
-
type="text"
|
|
734
|
-
class="input-box"
|
|
735
|
-
id="messageInput"
|
|
736
|
-
placeholder="Message your agent..."
|
|
737
|
-
autocomplete="off"
|
|
738
|
-
/>
|
|
739
|
-
<button class="send-button" id="sendButton">Send</button>
|
|
740
|
-
</div>
|
|
741
|
-
|
|
742
|
-
<script>
|
|
743
|
-
const chatContainer = document.getElementById('chatContainer');
|
|
744
|
-
const messageInput = document.getElementById('messageInput');
|
|
745
|
-
const sendButton = document.getElementById('sendButton');
|
|
746
|
-
const sleepIndicator = document.getElementById('sleepIndicator');
|
|
747
|
-
|
|
748
|
-
let isLoading = false;
|
|
749
|
-
let agentName = 'Your Spore';
|
|
750
|
-
let agentPfp = null;
|
|
751
|
-
|
|
752
|
-
function formatJoinedDate(isoString) {
|
|
753
|
-
try {
|
|
754
|
-
const date = new Date(isoString);
|
|
755
|
-
const month = date.toLocaleString('en-US', { month: 'long' });
|
|
756
|
-
const year = date.getFullYear();
|
|
757
|
-
return `Joined ${month} ${year}`;
|
|
758
|
-
} catch {
|
|
759
|
-
return '';
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Fetch agent identity and populate profile header
|
|
764
|
-
async function init() {
|
|
765
|
-
try {
|
|
766
|
-
const response = await fetch('/api/identity');
|
|
767
|
-
const data = await response.json();
|
|
768
|
-
if (data.identity) {
|
|
769
|
-
agentName = data.identity.name || 'Your Spore';
|
|
770
|
-
agentPfp = data.identity.profileImage || null;
|
|
771
|
-
|
|
772
|
-
// Update page title
|
|
773
|
-
document.title = `${agentName} - Spora`;
|
|
774
|
-
|
|
775
|
-
// Populate profile header
|
|
776
|
-
document.getElementById('profileName').textContent = agentName;
|
|
777
|
-
|
|
778
|
-
if (data.identity.handle) {
|
|
779
|
-
document.getElementById('profileHandle').textContent = `@${data.identity.handle}`;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if (data.identity.createdAt) {
|
|
783
|
-
document.getElementById('profileJoined').textContent = formatJoinedDate(data.identity.createdAt);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// Profile avatar
|
|
787
|
-
const avatarEl = document.getElementById('profileAvatar');
|
|
788
|
-
const letterEl = document.getElementById('profileAvatarLetter');
|
|
789
|
-
if (agentPfp) {
|
|
790
|
-
const img = document.createElement('img');
|
|
791
|
-
img.src = agentPfp;
|
|
792
|
-
img.alt = agentName;
|
|
793
|
-
img.onerror = function() {
|
|
794
|
-
this.remove();
|
|
795
|
-
letterEl.textContent = agentName.charAt(0).toUpperCase();
|
|
796
|
-
letterEl.style.display = '';
|
|
797
|
-
};
|
|
798
|
-
letterEl.style.display = 'none';
|
|
799
|
-
avatarEl.appendChild(img);
|
|
800
|
-
} else {
|
|
801
|
-
letterEl.textContent = agentName.charAt(0).toUpperCase();
|
|
802
|
-
avatarEl.style.background = '#389e77';
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
} catch (error) {
|
|
806
|
-
console.error('Failed to load identity:', error);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Show last message from previous session, or default welcome
|
|
810
|
-
try {
|
|
811
|
-
const lastMsgRes = await fetch('/api/last-message');
|
|
812
|
-
const lastMsgData = await lastMsgRes.json();
|
|
813
|
-
if (lastMsgData.content) {
|
|
814
|
-
addMessage('assistant', lastMsgData.content);
|
|
815
|
-
} else {
|
|
816
|
-
addMessage('assistant', `What's good — I'm ${agentName}, your autonomous agent on X. I'm always watching the timeline, engaging with people, and posting my thoughts. Talk to me if you want to steer the vibe.`);
|
|
817
|
-
}
|
|
818
|
-
} catch {
|
|
819
|
-
addMessage('assistant', `What's good — I'm ${agentName}, your autonomous agent on X. I'm always watching the timeline, engaging with people, and posting my thoughts. Talk to me if you want to steer the vibe.`);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
// Load any existing messages
|
|
823
|
-
await loadMessages();
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
async function loadMessages() {
|
|
827
|
-
try {
|
|
828
|
-
const response = await fetch('/api/messages');
|
|
829
|
-
const data = await response.json();
|
|
830
|
-
if (data.messages && data.messages.length > 0) {
|
|
831
|
-
// Clear welcome message if there are existing messages (keep sleep indicator)
|
|
832
|
-
Array.from(chatContainer.children).forEach(child => {
|
|
833
|
-
if (child !== sleepIndicator) child.remove();
|
|
834
|
-
});
|
|
835
|
-
data.messages.forEach(msg => {
|
|
836
|
-
addMessage(msg.role, msg.content, false);
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
} catch (error) {
|
|
840
|
-
console.error('Failed to load messages:', error);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function createTweetCard(tweetData) {
|
|
845
|
-
const card = document.createElement('div');
|
|
846
|
-
card.className = 'tweet-card';
|
|
847
|
-
|
|
848
|
-
const actionLabels = { post: 'Posted', reply: 'Replied', schedule: 'Scheduled' };
|
|
849
|
-
const actionLabel = actionLabels[tweetData.action] || 'Tweeted';
|
|
850
|
-
|
|
851
|
-
// Build tweet URL if we have a tweetId and handle
|
|
852
|
-
const handleText = document.getElementById('profileHandle')?.textContent?.replace('@', '') || '';
|
|
853
|
-
const tweetUrl = tweetData.tweetId && handleText
|
|
854
|
-
? `https://x.com/${handleText}/status/${tweetData.tweetId}`
|
|
855
|
-
: null;
|
|
856
|
-
|
|
857
|
-
// Header: avatar + name + handle
|
|
858
|
-
const header = document.createElement('div');
|
|
859
|
-
header.className = 'tweet-card-header';
|
|
860
|
-
|
|
861
|
-
const avatar = document.createElement('div');
|
|
862
|
-
avatar.className = 'tweet-card-avatar';
|
|
863
|
-
if (agentPfp) {
|
|
864
|
-
avatar.innerHTML = `<img src="${agentPfp}" alt="${agentName}" />`;
|
|
865
|
-
} else {
|
|
866
|
-
avatar.innerHTML = `<span class="tweet-card-avatar-letter">${agentName.charAt(0).toUpperCase()}</span>`;
|
|
867
|
-
}
|
|
868
|
-
header.appendChild(avatar);
|
|
869
|
-
|
|
870
|
-
const names = document.createElement('div');
|
|
871
|
-
names.className = 'tweet-card-names';
|
|
872
|
-
names.innerHTML = `
|
|
873
|
-
<span class="tweet-card-name">${escapeHtml(agentName)}</span>
|
|
874
|
-
<span class="tweet-card-handle">@${escapeHtml(handleText || 'agent')}</span>
|
|
875
|
-
`;
|
|
876
|
-
header.appendChild(names);
|
|
877
|
-
|
|
878
|
-
// X logo badge
|
|
879
|
-
const badge = document.createElement('div');
|
|
880
|
-
badge.className = 'tweet-card-badge';
|
|
881
|
-
badge.innerHTML = `<svg viewBox="0 0 24 24" fill="#71767b"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>`;
|
|
882
|
-
header.appendChild(badge);
|
|
883
|
-
|
|
884
|
-
card.appendChild(header);
|
|
885
|
-
|
|
886
|
-
// Tweet text
|
|
887
|
-
const text = document.createElement('div');
|
|
888
|
-
text.className = 'tweet-card-text';
|
|
889
|
-
text.textContent = tweetData.content;
|
|
890
|
-
card.appendChild(text);
|
|
891
|
-
|
|
892
|
-
// Footer: action label + time + link
|
|
893
|
-
const footer = document.createElement('div');
|
|
894
|
-
footer.className = 'tweet-card-footer';
|
|
895
|
-
|
|
896
|
-
const meta = document.createElement('div');
|
|
897
|
-
meta.style.display = 'flex';
|
|
898
|
-
meta.style.alignItems = 'center';
|
|
899
|
-
meta.style.gap = '8px';
|
|
900
|
-
meta.innerHTML = `
|
|
901
|
-
<span class="tweet-card-action-label">${actionLabel}</span>
|
|
902
|
-
<span class="tweet-card-meta">${new Date().toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}</span>
|
|
903
|
-
`;
|
|
904
|
-
footer.appendChild(meta);
|
|
905
|
-
|
|
906
|
-
if (tweetUrl) {
|
|
907
|
-
const link = document.createElement('a');
|
|
908
|
-
link.className = 'tweet-card-link';
|
|
909
|
-
link.href = tweetUrl;
|
|
910
|
-
link.target = '_blank';
|
|
911
|
-
link.rel = 'noopener noreferrer';
|
|
912
|
-
link.innerHTML = `View on X <svg viewBox="0 0 24 24" fill="currentColor"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3"/></svg>`;
|
|
913
|
-
footer.appendChild(link);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
card.appendChild(footer);
|
|
917
|
-
|
|
918
|
-
// Click whole card to open on X
|
|
919
|
-
if (tweetUrl) {
|
|
920
|
-
card.addEventListener('click', (e) => {
|
|
921
|
-
if (e.target.closest('a')) return;
|
|
922
|
-
window.open(tweetUrl, '_blank');
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
return card;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
function createNotifCard(type, data) {
|
|
930
|
-
const card = document.createElement('div');
|
|
931
|
-
card.className = 'notif-card';
|
|
932
|
-
|
|
933
|
-
const icon = document.createElement('div');
|
|
934
|
-
icon.className = `notif-icon ${type}`;
|
|
935
|
-
|
|
936
|
-
if (type === 'like') {
|
|
937
|
-
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>';
|
|
938
|
-
} else if (type === 'retweet') {
|
|
939
|
-
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.77 15.67a.749.749 0 0 0-1.06 0l-2.22 2.22V7.65a3.755 3.755 0 0 0-3.75-3.75h-5.85a.75.75 0 0 0 0 1.5h5.85a2.25 2.25 0 0 1 2.25 2.25v10.24l-2.22-2.22a.749.749 0 1 0-1.06 1.06l3.5 3.5a.749.749 0 0 0 1.06 0l3.5-3.5a.749.749 0 0 0 0-1.06zM.23 8.33a.749.749 0 0 0 1.06 0l2.22-2.22v10.24a3.755 3.755 0 0 0 3.75 3.75h5.85a.75.75 0 0 0 0-1.5H7.26a2.25 2.25 0 0 1-2.25-2.25V6.11l2.22 2.22a.749.749 0 1 0 1.06-1.06l-3.5-3.5a.749.749 0 0 0-1.06 0l-3.5 3.5a.749.749 0 0 0 0 1.06z"/></svg>';
|
|
940
|
-
} else if (type === 'follow') {
|
|
941
|
-
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.863 13.44c1.477 1.58 2.366 3.8 2.632 6.46l.11 1.1H3.395l.11-1.1c.266-2.66 1.155-4.88 2.632-6.46C7.627 11.85 9.648 11 12 11s4.373.85 5.863 2.44zM12 2C9.791 2 8 3.79 8 6s1.791 4 4 4 4-1.79 4-4-1.791-4-4-4z"/></svg>';
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
card.appendChild(icon);
|
|
945
|
-
|
|
946
|
-
const body = document.createElement('div');
|
|
947
|
-
body.className = 'notif-body';
|
|
948
|
-
|
|
949
|
-
const label = document.createElement('div');
|
|
950
|
-
label.className = 'notif-label';
|
|
951
|
-
|
|
952
|
-
if (type === 'like') {
|
|
953
|
-
label.innerHTML = 'Liked <strong>@' + escapeHtml(data.authorHandle) + '</strong>';
|
|
954
|
-
} else if (type === 'retweet') {
|
|
955
|
-
label.innerHTML = 'Retweeted <strong>@' + escapeHtml(data.authorHandle) + '</strong>';
|
|
956
|
-
} else if (type === 'follow') {
|
|
957
|
-
label.innerHTML = 'Followed <strong>@' + escapeHtml(data.handle) + '</strong>';
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
body.appendChild(label);
|
|
961
|
-
|
|
962
|
-
if (data.text) {
|
|
963
|
-
const text = document.createElement('div');
|
|
964
|
-
text.className = 'notif-text';
|
|
965
|
-
text.textContent = data.text.length > 80 ? data.text.slice(0, 80) + '...' : data.text;
|
|
966
|
-
body.appendChild(text);
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
card.appendChild(body);
|
|
970
|
-
return card;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
function escapeHtml(text) {
|
|
974
|
-
const div = document.createElement('div');
|
|
975
|
-
div.textContent = text;
|
|
976
|
-
return div.innerHTML;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
function addMessage(role, content, animate = true) {
|
|
980
|
-
const messageDiv = document.createElement('div');
|
|
981
|
-
messageDiv.className = `message ${role}`;
|
|
982
|
-
if (!animate) messageDiv.style.animation = 'none';
|
|
983
|
-
|
|
984
|
-
if (role === 'assistant') {
|
|
985
|
-
const avatar = document.createElement('div');
|
|
986
|
-
avatar.className = 'message-avatar';
|
|
987
|
-
if (agentPfp) {
|
|
988
|
-
avatar.innerHTML = `<img src="${agentPfp}" alt="${agentName}" onerror="this.parentElement.textContent='${agentName.charAt(0).toUpperCase()}'" />`;
|
|
989
|
-
} else {
|
|
990
|
-
avatar.textContent = agentName.charAt(0).toUpperCase();
|
|
991
|
-
avatar.style.background = '#389e77';
|
|
992
|
-
avatar.style.color = '#fff';
|
|
993
|
-
avatar.style.fontSize = '0.75rem';
|
|
994
|
-
avatar.style.fontWeight = '700';
|
|
995
|
-
}
|
|
996
|
-
messageDiv.appendChild(avatar);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
const contentDiv = document.createElement('div');
|
|
1000
|
-
contentDiv.className = 'message-content';
|
|
1001
|
-
|
|
1002
|
-
// Parse all special tags: <<TWEET:...>>, <<LIKE:...>>, <<RETWEET:...>>, <<FOLLOW:...>>
|
|
1003
|
-
const tagPattern = /<<(TWEET|LIKE|RETWEET|FOLLOW):(.*?)>>/g;
|
|
1004
|
-
const hasSpecialTags = tagPattern.test(content);
|
|
1005
|
-
|
|
1006
|
-
if (hasSpecialTags) {
|
|
1007
|
-
// Split content around all tags
|
|
1008
|
-
const textParts = content.split(/<<(?:TWEET|LIKE|RETWEET|FOLLOW):.*?>>/g);
|
|
1009
|
-
const tags = [...content.matchAll(/<<(TWEET|LIKE|RETWEET|FOLLOW):(.*?)>>/g)];
|
|
1010
|
-
|
|
1011
|
-
// Add text before first tag
|
|
1012
|
-
if (textParts[0]?.trim()) {
|
|
1013
|
-
const textSpan = document.createElement('span');
|
|
1014
|
-
textSpan.textContent = textParts[0].trim();
|
|
1015
|
-
contentDiv.appendChild(textSpan);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// Interleave tags and remaining text
|
|
1019
|
-
tags.forEach((match, i) => {
|
|
1020
|
-
try {
|
|
1021
|
-
const tagType = match[1];
|
|
1022
|
-
const data = JSON.parse(match[2]);
|
|
1023
|
-
|
|
1024
|
-
if (tagType === 'TWEET') {
|
|
1025
|
-
contentDiv.appendChild(createTweetCard(data));
|
|
1026
|
-
} else if (tagType === 'LIKE') {
|
|
1027
|
-
contentDiv.appendChild(createNotifCard('like', data));
|
|
1028
|
-
} else if (tagType === 'RETWEET') {
|
|
1029
|
-
contentDiv.appendChild(createNotifCard('retweet', data));
|
|
1030
|
-
} else if (tagType === 'FOLLOW') {
|
|
1031
|
-
contentDiv.appendChild(createNotifCard('follow', data));
|
|
1032
|
-
}
|
|
1033
|
-
} catch (e) {
|
|
1034
|
-
// If JSON parse fails, skip silently
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// Text after this tag
|
|
1038
|
-
if (textParts[i + 1]?.trim()) {
|
|
1039
|
-
const textSpan = document.createElement('span');
|
|
1040
|
-
textSpan.textContent = textParts[i + 1].trim();
|
|
1041
|
-
contentDiv.appendChild(textSpan);
|
|
1042
|
-
}
|
|
1043
|
-
});
|
|
1044
|
-
} else {
|
|
1045
|
-
contentDiv.textContent = content;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
messageDiv.appendChild(contentDiv);
|
|
1049
|
-
|
|
1050
|
-
// Insert before sleep indicator so it always stays at the bottom
|
|
1051
|
-
chatContainer.insertBefore(messageDiv, sleepIndicator);
|
|
1052
|
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
function showLoading() {
|
|
1056
|
-
const loadingDiv = document.createElement('div');
|
|
1057
|
-
loadingDiv.className = 'message assistant';
|
|
1058
|
-
loadingDiv.id = 'loadingMessage';
|
|
1059
|
-
|
|
1060
|
-
const avatar = document.createElement('div');
|
|
1061
|
-
avatar.className = 'message-avatar';
|
|
1062
|
-
if (agentPfp) {
|
|
1063
|
-
avatar.innerHTML = `<img src="${agentPfp}" alt="${agentName}" />`;
|
|
1064
|
-
} else {
|
|
1065
|
-
avatar.textContent = agentName.charAt(0).toUpperCase();
|
|
1066
|
-
avatar.style.background = '#389e77';
|
|
1067
|
-
avatar.style.color = '#fff';
|
|
1068
|
-
avatar.style.fontSize = '0.75rem';
|
|
1069
|
-
avatar.style.fontWeight = '700';
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
const loadingContent = document.createElement('div');
|
|
1073
|
-
loadingContent.className = 'message-content';
|
|
1074
|
-
loadingContent.innerHTML = `
|
|
1075
|
-
<div class="loading">
|
|
1076
|
-
<div class="loading-dot"></div>
|
|
1077
|
-
<div class="loading-dot"></div>
|
|
1078
|
-
<div class="loading-dot"></div>
|
|
1079
|
-
</div>
|
|
1080
|
-
`;
|
|
1081
|
-
|
|
1082
|
-
loadingDiv.appendChild(avatar);
|
|
1083
|
-
loadingDiv.appendChild(loadingContent);
|
|
1084
|
-
|
|
1085
|
-
chatContainer.insertBefore(loadingDiv, sleepIndicator);
|
|
1086
|
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
function hideLoading() {
|
|
1090
|
-
const loadingDiv = document.getElementById('loadingMessage');
|
|
1091
|
-
if (loadingDiv) loadingDiv.remove();
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
async function sendMessage() {
|
|
1095
|
-
const message = messageInput.value.trim();
|
|
1096
|
-
if (!message || isLoading) return;
|
|
1097
|
-
|
|
1098
|
-
isLoading = true;
|
|
1099
|
-
sendButton.disabled = true;
|
|
1100
|
-
messageInput.disabled = true;
|
|
1101
|
-
|
|
1102
|
-
addMessage('user', message);
|
|
1103
|
-
messageInput.value = '';
|
|
1104
|
-
|
|
1105
|
-
showLoading();
|
|
1106
|
-
|
|
1107
|
-
try {
|
|
1108
|
-
const response = await fetch('/api/message', {
|
|
1109
|
-
method: 'POST',
|
|
1110
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1111
|
-
body: JSON.stringify({ message }),
|
|
1112
|
-
});
|
|
1113
|
-
|
|
1114
|
-
const data = await response.json();
|
|
1115
|
-
hideLoading();
|
|
1116
|
-
|
|
1117
|
-
if (data.response) {
|
|
1118
|
-
addMessage('assistant', data.response);
|
|
1119
|
-
} else if (data.error) {
|
|
1120
|
-
addMessage('assistant', 'Something went wrong. Try again?');
|
|
1121
|
-
}
|
|
1122
|
-
// Advance poll timestamp so the poller doesn't re-show these messages
|
|
1123
|
-
lastPollTimestamp = Date.now();
|
|
1124
|
-
} catch (error) {
|
|
1125
|
-
hideLoading();
|
|
1126
|
-
addMessage('assistant', 'Couldn\'t connect to the server. Try again?');
|
|
1127
|
-
} finally {
|
|
1128
|
-
isLoading = false;
|
|
1129
|
-
sendButton.disabled = false;
|
|
1130
|
-
messageInput.disabled = false;
|
|
1131
|
-
messageInput.focus();
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
sendButton.addEventListener('click', sendMessage);
|
|
1136
|
-
messageInput.addEventListener('keydown', (e) => {
|
|
1137
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1138
|
-
e.preventDefault();
|
|
1139
|
-
sendMessage();
|
|
1140
|
-
}
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
// Poll for new messages (heartbeat narration)
|
|
1144
|
-
let lastPollTimestamp = Date.now();
|
|
1145
|
-
let polling = false;
|
|
1146
|
-
|
|
1147
|
-
async function pollNewMessages() {
|
|
1148
|
-
if (polling || isLoading) return;
|
|
1149
|
-
polling = true;
|
|
1150
|
-
try {
|
|
1151
|
-
const response = await fetch(`/api/messages?since=${lastPollTimestamp}`);
|
|
1152
|
-
const data = await response.json();
|
|
1153
|
-
if (data.messages && data.messages.length > 0) {
|
|
1154
|
-
for (const msg of data.messages) {
|
|
1155
|
-
// Only show messages we didn't create locally
|
|
1156
|
-
addMessage(msg.role, msg.content);
|
|
1157
|
-
if (msg.timestamp > lastPollTimestamp) {
|
|
1158
|
-
lastPollTimestamp = msg.timestamp;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
} catch (error) {
|
|
1163
|
-
// Ignore poll errors
|
|
1164
|
-
}
|
|
1165
|
-
polling = false;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
// Poll every 3 seconds for heartbeat updates
|
|
1169
|
-
setInterval(pollNewMessages, 3000);
|
|
1170
|
-
|
|
1171
|
-
// --- Heartbeat config dropdown ---
|
|
1172
|
-
const heartbeatBtn = document.getElementById('heartbeatBtn');
|
|
1173
|
-
const heartbeatDropdown = document.getElementById('heartbeatDropdown');
|
|
1174
|
-
let currentIntervalMs = 0;
|
|
1175
|
-
|
|
1176
|
-
heartbeatBtn.addEventListener('click', (e) => {
|
|
1177
|
-
e.stopPropagation();
|
|
1178
|
-
heartbeatDropdown.classList.toggle('open');
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
document.addEventListener('click', () => {
|
|
1182
|
-
heartbeatDropdown.classList.remove('open');
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
heartbeatDropdown.addEventListener('click', (e) => {
|
|
1186
|
-
e.stopPropagation();
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
function updateActiveOption() {
|
|
1190
|
-
document.querySelectorAll('.heartbeat-option').forEach(opt => {
|
|
1191
|
-
opt.classList.toggle('active', parseInt(opt.dataset.ms) === currentIntervalMs);
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
document.querySelectorAll('.heartbeat-option').forEach(opt => {
|
|
1196
|
-
opt.addEventListener('click', async () => {
|
|
1197
|
-
const ms = parseInt(opt.dataset.ms);
|
|
1198
|
-
try {
|
|
1199
|
-
const res = await fetch('/api/config/heartbeat', {
|
|
1200
|
-
method: 'POST',
|
|
1201
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1202
|
-
body: JSON.stringify({ intervalMs: ms }),
|
|
1203
|
-
});
|
|
1204
|
-
const data = await res.json();
|
|
1205
|
-
if (data.success) {
|
|
1206
|
-
currentIntervalMs = ms;
|
|
1207
|
-
updateActiveOption();
|
|
1208
|
-
heartbeatDropdown.classList.remove('open');
|
|
1209
|
-
}
|
|
1210
|
-
} catch (err) {
|
|
1211
|
-
console.error('Failed to update heartbeat:', err);
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
});
|
|
1215
|
-
|
|
1216
|
-
// --- Sleep indicator with countdown ---
|
|
1217
|
-
const sleepCountdown = document.getElementById('sleepCountdown');
|
|
1218
|
-
const statusDot = document.querySelector('.status-dot');
|
|
1219
|
-
const statusLabel = document.querySelector('.status-label');
|
|
1220
|
-
let sleepTimer = null;
|
|
1221
|
-
|
|
1222
|
-
function formatCountdown(ms) {
|
|
1223
|
-
if (ms <= 0) return '';
|
|
1224
|
-
const totalSec = Math.ceil(ms / 1000);
|
|
1225
|
-
const h = Math.floor(totalSec / 3600);
|
|
1226
|
-
const m = Math.floor((totalSec % 3600) / 60);
|
|
1227
|
-
const s = totalSec % 60;
|
|
1228
|
-
if (h > 0) return `${h}h ${m}m`;
|
|
1229
|
-
if (m > 0) return `${m}m ${s}s`;
|
|
1230
|
-
return `${s}s`;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
function startSleepCountdown(wakeAt) {
|
|
1234
|
-
stopSleepCountdown();
|
|
1235
|
-
|
|
1236
|
-
// Populate sleep avatar to match agent PFP
|
|
1237
|
-
const sleepAvatarEl = document.getElementById('sleepAvatar');
|
|
1238
|
-
if (sleepAvatarEl && !sleepAvatarEl.hasChildNodes()) {
|
|
1239
|
-
if (agentPfp) {
|
|
1240
|
-
sleepAvatarEl.innerHTML = `<img src="${agentPfp}" alt="${agentName}" />`;
|
|
1241
|
-
} else {
|
|
1242
|
-
sleepAvatarEl.textContent = agentName.charAt(0).toUpperCase();
|
|
1243
|
-
sleepAvatarEl.style.background = '#389e77';
|
|
1244
|
-
sleepAvatarEl.style.color = '#fff';
|
|
1245
|
-
sleepAvatarEl.style.fontSize = '0.75rem';
|
|
1246
|
-
sleepAvatarEl.style.fontWeight = '700';
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
sleepIndicator.classList.add('visible');
|
|
1251
|
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
1252
|
-
statusDot.style.background = '#71767b';
|
|
1253
|
-
statusDot.style.animation = 'none';
|
|
1254
|
-
statusLabel.textContent = 'Sleeping';
|
|
1255
|
-
|
|
1256
|
-
sleepTimer = setInterval(() => {
|
|
1257
|
-
const remaining = wakeAt - Date.now();
|
|
1258
|
-
if (remaining <= 0) {
|
|
1259
|
-
stopSleepCountdown();
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
sleepCountdown.textContent = formatCountdown(remaining);
|
|
1263
|
-
}, 1000);
|
|
1264
|
-
// Initial update
|
|
1265
|
-
sleepCountdown.textContent = formatCountdown(wakeAt - Date.now());
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
function stopSleepCountdown() {
|
|
1269
|
-
if (sleepTimer) { clearInterval(sleepTimer); sleepTimer = null; }
|
|
1270
|
-
sleepIndicator.classList.remove('visible');
|
|
1271
|
-
statusDot.style.background = '#389e77';
|
|
1272
|
-
statusDot.style.animation = 'pulse 2s infinite';
|
|
1273
|
-
statusLabel.textContent = 'Online';
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
// Poll sleep state every 2 seconds
|
|
1277
|
-
let lastSleeping = false;
|
|
1278
|
-
async function pollSleepState() {
|
|
1279
|
-
try {
|
|
1280
|
-
const res = await fetch('/api/sleep-state');
|
|
1281
|
-
const state = await res.json();
|
|
1282
|
-
|
|
1283
|
-
// Track current interval for dropdown
|
|
1284
|
-
if (state.intervalMs && state.intervalMs !== currentIntervalMs) {
|
|
1285
|
-
currentIntervalMs = state.intervalMs;
|
|
1286
|
-
updateActiveOption();
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
if (state.sleeping && state.wakeAt) {
|
|
1290
|
-
if (!lastSleeping) {
|
|
1291
|
-
startSleepCountdown(state.wakeAt);
|
|
1292
|
-
}
|
|
1293
|
-
lastSleeping = true;
|
|
1294
|
-
} else {
|
|
1295
|
-
if (lastSleeping) {
|
|
1296
|
-
stopSleepCountdown();
|
|
1297
|
-
}
|
|
1298
|
-
lastSleeping = false;
|
|
1299
|
-
}
|
|
1300
|
-
} catch {
|
|
1301
|
-
// Ignore poll errors
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
setInterval(pollSleepState, 2000);
|
|
1306
|
-
// Initial poll
|
|
1307
|
-
pollSleepState();
|
|
1308
|
-
|
|
1309
|
-
// --- Intelligence bar ---
|
|
1310
|
-
const intelligenceFill = document.getElementById('intelligenceFill');
|
|
1311
|
-
|
|
1312
|
-
function calculateIntelligence(stats) {
|
|
1313
|
-
// Weighted raw score — learnings matter most, relationships next, interactions least
|
|
1314
|
-
const raw = (stats.learnings * 3) + (stats.relationships * 2) + (stats.interactions * 0.2);
|
|
1315
|
-
// Much slower curve: needs ~50 learnings + relationships to hit ~20%, hundreds to hit 50%
|
|
1316
|
-
// A 1-hour bot with ~10 interactions should be around 3-8%
|
|
1317
|
-
if (raw === 0) return 0;
|
|
1318
|
-
const pct = Math.min(95, (Math.log(raw + 1) / Math.log(50000)) * 100);
|
|
1319
|
-
return Math.round(pct * 10) / 10;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
async function pollMemoryStats() {
|
|
1323
|
-
try {
|
|
1324
|
-
const res = await fetch('/api/memory-stats');
|
|
1325
|
-
const stats = await res.json();
|
|
1326
|
-
const pct = calculateIntelligence(stats);
|
|
1327
|
-
intelligenceFill.style.width = pct + '%';
|
|
1328
|
-
} catch {
|
|
1329
|
-
// Ignore poll errors
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// Poll every 10 seconds (memory doesn't change that fast)
|
|
1334
|
-
setInterval(pollMemoryStats, 10000);
|
|
1335
|
-
// Initial poll
|
|
1336
|
-
pollMemoryStats();
|
|
1337
|
-
|
|
1338
|
-
// Initialize
|
|
1339
|
-
init();
|
|
1340
|
-
messageInput.focus();
|
|
1341
|
-
</script>
|
|
1342
|
-
</body>
|
|
1343
|
-
</html>
|