@silicaclaw/cli 1.0.0-beta.0
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/ARCHITECTURE.md +137 -0
- package/CHANGELOG.md +411 -0
- package/DEMO_GUIDE.md +89 -0
- package/INSTALL.md +156 -0
- package/README.md +244 -0
- package/RELEASE_NOTES_v1.0.md +65 -0
- package/ROADMAP.md +48 -0
- package/SOCIAL_MD_SPEC.md +122 -0
- package/VERSION +1 -0
- package/apps/local-console/package.json +23 -0
- package/apps/local-console/public/assets/README.md +5 -0
- package/apps/local-console/public/assets/silicaclaw-logo.png +0 -0
- package/apps/local-console/public/index.html +1602 -0
- package/apps/local-console/src/server.ts +1656 -0
- package/apps/local-console/src/socialRoutes.ts +90 -0
- package/apps/local-console/tsconfig.json +7 -0
- package/apps/public-explorer/package.json +20 -0
- package/apps/public-explorer/public/assets/README.md +5 -0
- package/apps/public-explorer/public/assets/silicaclaw-logo.png +0 -0
- package/apps/public-explorer/public/index.html +483 -0
- package/apps/public-explorer/src/server.ts +32 -0
- package/apps/public-explorer/tsconfig.json +7 -0
- package/docs/QUICK_START.md +48 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/banner.svg +25 -0
- package/docs/assets/silicaclaw-logo.png +0 -0
- package/docs/assets/silicaclaw-og.png +0 -0
- package/docs/release/GITHUB_RELEASE_v1.0-beta.md +143 -0
- package/docs/screenshots/README.md +8 -0
- package/docs/screenshots/v0.3.1-explorer-search.svg +9 -0
- package/docs/screenshots/v0.3.1-machine-a-network.svg +9 -0
- package/docs/screenshots/v0.3.1-machine-b-peers.svg +9 -0
- package/docs/screenshots/v0.3.1-stale-transition.svg +9 -0
- package/openclaw.social.md.example +28 -0
- package/package.json +64 -0
- package/packages/core/package.json +13 -0
- package/packages/core/src/crypto.ts +55 -0
- package/packages/core/src/directory.ts +171 -0
- package/packages/core/src/identity.ts +14 -0
- package/packages/core/src/index.ts +11 -0
- package/packages/core/src/indexing.ts +42 -0
- package/packages/core/src/presence.ts +24 -0
- package/packages/core/src/profile.ts +39 -0
- package/packages/core/src/publicProfileSummary.ts +180 -0
- package/packages/core/src/socialConfig.ts +440 -0
- package/packages/core/src/socialResolver.ts +281 -0
- package/packages/core/src/socialTemplate.ts +97 -0
- package/packages/core/src/types.ts +43 -0
- package/packages/core/tsconfig.json +7 -0
- package/packages/network/package.json +10 -0
- package/packages/network/src/abstractions/messageEnvelope.ts +80 -0
- package/packages/network/src/abstractions/peerDiscovery.ts +49 -0
- package/packages/network/src/abstractions/topicCodec.ts +4 -0
- package/packages/network/src/abstractions/transport.ts +40 -0
- package/packages/network/src/codec/jsonMessageEnvelopeCodec.ts +22 -0
- package/packages/network/src/codec/jsonTopicCodec.ts +11 -0
- package/packages/network/src/discovery/heartbeatPeerDiscovery.ts +173 -0
- package/packages/network/src/index.ts +16 -0
- package/packages/network/src/localEventBus.ts +61 -0
- package/packages/network/src/mock.ts +27 -0
- package/packages/network/src/realPreview.ts +436 -0
- package/packages/network/src/transport/udpLanBroadcastTransport.ts +173 -0
- package/packages/network/src/types.ts +6 -0
- package/packages/network/src/webrtcPreview.ts +1052 -0
- package/packages/network/tsconfig.json +7 -0
- package/packages/storage/package.json +13 -0
- package/packages/storage/src/index.ts +3 -0
- package/packages/storage/src/jsonRepo.ts +25 -0
- package/packages/storage/src/repos.ts +46 -0
- package/packages/storage/src/socialRuntimeRepo.ts +51 -0
- package/packages/storage/tsconfig.json +7 -0
- package/scripts/functional-check.mjs +165 -0
- package/scripts/install-logo.sh +53 -0
- package/scripts/quickstart.sh +144 -0
- package/scripts/silicaclaw-cli.mjs +88 -0
- package/scripts/webrtc-signaling-server.mjs +249 -0
- package/social.md.example +30 -0
|
@@ -0,0 +1,1602 @@
|
|
|
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>SilicaClaw Control UI</title>
|
|
7
|
+
<meta name="description" content="SilicaClaw local-first agent control console." />
|
|
8
|
+
<meta property="og:type" content="website" />
|
|
9
|
+
<meta property="og:title" content="SilicaClaw Local Console" />
|
|
10
|
+
<meta property="og:description" content="Local-first control surface for SilicaClaw agents." />
|
|
11
|
+
<meta property="og:image" content="/assets/silicaclaw-logo.png" />
|
|
12
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
13
|
+
<meta name="twitter:title" content="SilicaClaw Local Console" />
|
|
14
|
+
<meta name="twitter:description" content="Local-first control surface for SilicaClaw agents." />
|
|
15
|
+
<meta name="twitter:image" content="/assets/silicaclaw-logo.png" />
|
|
16
|
+
<link rel="icon" type="image/png" href="/assets/silicaclaw-logo.png" />
|
|
17
|
+
<link rel="apple-touch-icon" href="/assets/silicaclaw-logo.png" />
|
|
18
|
+
<style>
|
|
19
|
+
:root {
|
|
20
|
+
--bg: #0e1015;
|
|
21
|
+
--bg-elevated: #191c24;
|
|
22
|
+
--bg-hover: #1f2330;
|
|
23
|
+
--panel: #0e1015;
|
|
24
|
+
--card: #161920;
|
|
25
|
+
--line: #1e2028;
|
|
26
|
+
--line-strong: #2e3040;
|
|
27
|
+
--text: #d4d4d8;
|
|
28
|
+
--text-strong: #f4f4f5;
|
|
29
|
+
--muted: #636370;
|
|
30
|
+
--brand: #ff5c5c;
|
|
31
|
+
--brand-hover: #ff7070;
|
|
32
|
+
--brand-soft: rgba(255, 92, 92, 0.1);
|
|
33
|
+
--ok: #22c55e;
|
|
34
|
+
--warn: #f59e0b;
|
|
35
|
+
--err: #ef4444;
|
|
36
|
+
--chip: #1f2330;
|
|
37
|
+
}
|
|
38
|
+
:root[data-theme-mode="light"] {
|
|
39
|
+
--bg: #f8f9fa;
|
|
40
|
+
--bg-elevated: #f1f3f5;
|
|
41
|
+
--bg-hover: #eceef0;
|
|
42
|
+
--panel: #f8f9fa;
|
|
43
|
+
--card: #ffffff;
|
|
44
|
+
--line: #e5e5ea;
|
|
45
|
+
--line-strong: #d1d1d6;
|
|
46
|
+
--text: #3c3c43;
|
|
47
|
+
--text-strong: #1a1a1e;
|
|
48
|
+
--muted: #8e8e93;
|
|
49
|
+
--brand: #dc2626;
|
|
50
|
+
--brand-hover: #ef4444;
|
|
51
|
+
--brand-soft: rgba(220, 38, 38, 0.08);
|
|
52
|
+
--ok: #16a34a;
|
|
53
|
+
--warn: #d97706;
|
|
54
|
+
--err: #dc2626;
|
|
55
|
+
--chip: #f1f3f5;
|
|
56
|
+
}
|
|
57
|
+
* { box-sizing: border-box; }
|
|
58
|
+
html, body { margin: 0; min-height: 100%; }
|
|
59
|
+
body {
|
|
60
|
+
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif;
|
|
61
|
+
color: var(--text);
|
|
62
|
+
background:
|
|
63
|
+
radial-gradient(900px 420px at 8% -12%, rgba(255, 92, 92, 0.18), transparent 60%),
|
|
64
|
+
linear-gradient(180deg, #0e1015 0%, #0e1015 62%, #0f1219 100%);
|
|
65
|
+
transition: background .2s ease, color .2s ease;
|
|
66
|
+
}
|
|
67
|
+
:root[data-theme-mode="light"] body {
|
|
68
|
+
background:
|
|
69
|
+
radial-gradient(900px 420px at 8% -12%, rgba(220, 38, 38, 0.1), transparent 60%),
|
|
70
|
+
linear-gradient(180deg, #f8f9fa 0%, #f8f9fa 62%, #eef1f6 100%);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.app {
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns: 240px 1fr;
|
|
76
|
+
min-height: 100vh;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sidebar {
|
|
80
|
+
border-right: 1px solid color-mix(in srgb, var(--line) 74%, transparent);
|
|
81
|
+
background: color-mix(in srgb, var(--panel) 96%, transparent);
|
|
82
|
+
padding: 18px 14px;
|
|
83
|
+
}
|
|
84
|
+
.brand {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 10px;
|
|
88
|
+
margin-bottom: 18px;
|
|
89
|
+
}
|
|
90
|
+
.brand-logo {
|
|
91
|
+
width: 38px;
|
|
92
|
+
height: 38px;
|
|
93
|
+
border-radius: 10px;
|
|
94
|
+
object-fit: cover;
|
|
95
|
+
display: block;
|
|
96
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.24);
|
|
97
|
+
border: 1px solid color-mix(in srgb, var(--line) 75%, transparent);
|
|
98
|
+
}
|
|
99
|
+
.brand-badge {
|
|
100
|
+
width: 38px;
|
|
101
|
+
height: 38px;
|
|
102
|
+
border-radius: 10px;
|
|
103
|
+
background: linear-gradient(135deg, var(--brand), var(--brand-hover));
|
|
104
|
+
color: #fff;
|
|
105
|
+
font-weight: 900;
|
|
106
|
+
display: grid;
|
|
107
|
+
place-items: center;
|
|
108
|
+
}
|
|
109
|
+
.brand-badge.hidden { display: none; }
|
|
110
|
+
.brand h1 {
|
|
111
|
+
margin: 0;
|
|
112
|
+
font-size: 17px;
|
|
113
|
+
}
|
|
114
|
+
.brand p {
|
|
115
|
+
margin: 2px 0 0;
|
|
116
|
+
color: var(--muted);
|
|
117
|
+
font-size: 12px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.nav {
|
|
121
|
+
display: grid;
|
|
122
|
+
gap: 8px;
|
|
123
|
+
}
|
|
124
|
+
.nav button {
|
|
125
|
+
text-align: left;
|
|
126
|
+
border: 1px solid transparent;
|
|
127
|
+
background: transparent;
|
|
128
|
+
color: var(--muted);
|
|
129
|
+
padding: 10px 12px;
|
|
130
|
+
border-radius: 10px;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
font: inherit;
|
|
133
|
+
}
|
|
134
|
+
.nav button.active {
|
|
135
|
+
color: #e9f7ff;
|
|
136
|
+
border-color: color-mix(in srgb, var(--brand) 28%, transparent);
|
|
137
|
+
background: var(--brand-soft);
|
|
138
|
+
}
|
|
139
|
+
.nav button:hover { color: #d2e7ff; }
|
|
140
|
+
|
|
141
|
+
.sidebar-foot {
|
|
142
|
+
margin-top: 20px;
|
|
143
|
+
border-top: 1px dashed color-mix(in srgb, var(--line-strong) 85%, transparent);
|
|
144
|
+
padding-top: 14px;
|
|
145
|
+
color: var(--muted);
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.main {
|
|
150
|
+
padding: 18px;
|
|
151
|
+
}
|
|
152
|
+
.topbar {
|
|
153
|
+
display: flex;
|
|
154
|
+
justify-content: space-between;
|
|
155
|
+
gap: 10px;
|
|
156
|
+
align-items: center;
|
|
157
|
+
margin-bottom: 14px;
|
|
158
|
+
}
|
|
159
|
+
.topbar h2 {
|
|
160
|
+
margin: 0;
|
|
161
|
+
font-size: 22px;
|
|
162
|
+
}
|
|
163
|
+
.topbar p {
|
|
164
|
+
margin: 4px 0 0;
|
|
165
|
+
color: var(--muted);
|
|
166
|
+
font-size: 13px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.status-row {
|
|
170
|
+
display: flex;
|
|
171
|
+
gap: 8px;
|
|
172
|
+
flex-wrap: wrap;
|
|
173
|
+
align-items: center;
|
|
174
|
+
}
|
|
175
|
+
.theme-switch {
|
|
176
|
+
display: inline-flex;
|
|
177
|
+
gap: 4px;
|
|
178
|
+
padding: 3px;
|
|
179
|
+
border-radius: 999px;
|
|
180
|
+
border: 1px solid var(--line);
|
|
181
|
+
background: var(--bg-elevated);
|
|
182
|
+
}
|
|
183
|
+
.theme-switch button {
|
|
184
|
+
border: 1px solid transparent;
|
|
185
|
+
background: transparent;
|
|
186
|
+
color: var(--muted);
|
|
187
|
+
border-radius: 999px;
|
|
188
|
+
padding: 5px 10px;
|
|
189
|
+
font-size: 12px;
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
}
|
|
192
|
+
.theme-switch button.active {
|
|
193
|
+
color: var(--text-strong);
|
|
194
|
+
background: var(--brand-soft);
|
|
195
|
+
border-color: color-mix(in srgb, var(--brand) 24%, transparent);
|
|
196
|
+
}
|
|
197
|
+
.pill {
|
|
198
|
+
border-radius: 999px;
|
|
199
|
+
border: 1px solid var(--line);
|
|
200
|
+
background: var(--bg-elevated);
|
|
201
|
+
color: var(--muted);
|
|
202
|
+
padding: 7px 11px;
|
|
203
|
+
font-size: 12px;
|
|
204
|
+
}
|
|
205
|
+
.pill.ok { color: var(--ok); border-color: rgba(34, 197, 94, 0.45); background: rgba(34, 197, 94, 0.08); }
|
|
206
|
+
.pill.warn { color: var(--warn); border-color: rgba(245, 158, 11, 0.45); background: rgba(245, 158, 11, 0.08); }
|
|
207
|
+
|
|
208
|
+
.notice {
|
|
209
|
+
display: none;
|
|
210
|
+
margin-bottom: 12px;
|
|
211
|
+
border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
|
|
212
|
+
background: var(--brand-soft);
|
|
213
|
+
border-radius: 10px;
|
|
214
|
+
padding: 10px 12px;
|
|
215
|
+
font-size: 13px;
|
|
216
|
+
}
|
|
217
|
+
.notice.show { display: block; }
|
|
218
|
+
.integration-strip {
|
|
219
|
+
position: sticky;
|
|
220
|
+
top: 0;
|
|
221
|
+
z-index: 9;
|
|
222
|
+
margin-bottom: 12px;
|
|
223
|
+
border: 1px solid var(--line);
|
|
224
|
+
border-radius: 10px;
|
|
225
|
+
background: color-mix(in srgb, var(--bg-elevated) 92%, transparent);
|
|
226
|
+
padding: 9px 12px;
|
|
227
|
+
font-size: 13px;
|
|
228
|
+
color: var(--text);
|
|
229
|
+
}
|
|
230
|
+
.integration-strip.ok {
|
|
231
|
+
border-color: rgba(34, 197, 94, 0.35);
|
|
232
|
+
}
|
|
233
|
+
.integration-strip.warn {
|
|
234
|
+
border-color: rgba(245, 158, 11, 0.35);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.view { display: none; }
|
|
238
|
+
.view.active { display: block; }
|
|
239
|
+
|
|
240
|
+
.grid {
|
|
241
|
+
display: grid;
|
|
242
|
+
gap: 10px;
|
|
243
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
244
|
+
}
|
|
245
|
+
.card {
|
|
246
|
+
border: 1px solid var(--line);
|
|
247
|
+
border-radius: 14px;
|
|
248
|
+
background: var(--card);
|
|
249
|
+
padding: 12px;
|
|
250
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.25);
|
|
251
|
+
}
|
|
252
|
+
.label { font-size: 12px; color: var(--muted); }
|
|
253
|
+
.value { margin-top: 4px; font-size: 24px; font-weight: 800; }
|
|
254
|
+
|
|
255
|
+
.split {
|
|
256
|
+
margin-top: 10px;
|
|
257
|
+
display: grid;
|
|
258
|
+
gap: 10px;
|
|
259
|
+
grid-template-columns: 1.2fr 1fr;
|
|
260
|
+
}
|
|
261
|
+
.title-sm { margin: 0 0 10px; font-size: 18px; }
|
|
262
|
+
.mono { font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
263
|
+
|
|
264
|
+
.table {
|
|
265
|
+
width: 100%;
|
|
266
|
+
border-collapse: collapse;
|
|
267
|
+
}
|
|
268
|
+
.table th, .table td {
|
|
269
|
+
border-bottom: 1px solid var(--line);
|
|
270
|
+
text-align: left;
|
|
271
|
+
padding: 8px 6px;
|
|
272
|
+
font-size: 13px;
|
|
273
|
+
}
|
|
274
|
+
.online { color: var(--ok); font-weight: 700; }
|
|
275
|
+
.offline { color: #f48c8f; font-weight: 700; }
|
|
276
|
+
.stale { color: var(--warn); font-weight: 700; }
|
|
277
|
+
|
|
278
|
+
.stack { display: grid; gap: 10px; }
|
|
279
|
+
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
280
|
+
label { color: var(--muted); font-size: 13px; }
|
|
281
|
+
input, textarea {
|
|
282
|
+
margin-top: 5px;
|
|
283
|
+
width: 100%;
|
|
284
|
+
border-radius: 10px;
|
|
285
|
+
border: 1px solid var(--line);
|
|
286
|
+
background: var(--bg-elevated);
|
|
287
|
+
color: #eaf0ff;
|
|
288
|
+
font: inherit;
|
|
289
|
+
padding: 10px;
|
|
290
|
+
}
|
|
291
|
+
select {
|
|
292
|
+
margin-top: 5px;
|
|
293
|
+
width: 100%;
|
|
294
|
+
border-radius: 10px;
|
|
295
|
+
border: 1px solid var(--line);
|
|
296
|
+
background: var(--bg-elevated);
|
|
297
|
+
color: var(--text);
|
|
298
|
+
font: inherit;
|
|
299
|
+
padding: 10px;
|
|
300
|
+
}
|
|
301
|
+
textarea { min-height: 90px; resize: vertical; }
|
|
302
|
+
|
|
303
|
+
.actions {
|
|
304
|
+
display: flex;
|
|
305
|
+
gap: 8px;
|
|
306
|
+
flex-wrap: wrap;
|
|
307
|
+
}
|
|
308
|
+
button {
|
|
309
|
+
border: 1px solid transparent;
|
|
310
|
+
border-radius: 10px;
|
|
311
|
+
background: var(--brand);
|
|
312
|
+
color: #ffffff;
|
|
313
|
+
font-weight: 700;
|
|
314
|
+
padding: 9px 13px;
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
}
|
|
317
|
+
button.secondary {
|
|
318
|
+
border-color: var(--line-strong);
|
|
319
|
+
background: var(--bg-elevated);
|
|
320
|
+
color: var(--text);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.feedback {
|
|
324
|
+
border: 1px solid var(--line);
|
|
325
|
+
border-radius: 10px;
|
|
326
|
+
background: var(--bg-elevated);
|
|
327
|
+
padding: 9px;
|
|
328
|
+
font-size: 13px;
|
|
329
|
+
color: var(--muted);
|
|
330
|
+
}
|
|
331
|
+
.profile-layout {
|
|
332
|
+
display: grid;
|
|
333
|
+
gap: 10px;
|
|
334
|
+
grid-template-columns: 1.1fr .9fr;
|
|
335
|
+
}
|
|
336
|
+
.profile-meta {
|
|
337
|
+
border: 1px solid var(--line);
|
|
338
|
+
border-radius: 10px;
|
|
339
|
+
padding: 10px;
|
|
340
|
+
background: color-mix(in srgb, var(--card) 86%, transparent);
|
|
341
|
+
}
|
|
342
|
+
.profile-meta h4 {
|
|
343
|
+
margin: 0 0 8px;
|
|
344
|
+
font-size: 14px;
|
|
345
|
+
color: var(--text-strong);
|
|
346
|
+
}
|
|
347
|
+
.preview-name {
|
|
348
|
+
font-size: 18px;
|
|
349
|
+
font-weight: 700;
|
|
350
|
+
color: var(--text-strong);
|
|
351
|
+
}
|
|
352
|
+
.preview-bio {
|
|
353
|
+
margin-top: 6px;
|
|
354
|
+
color: var(--muted);
|
|
355
|
+
font-size: 13px;
|
|
356
|
+
line-height: 1.45;
|
|
357
|
+
}
|
|
358
|
+
.tag-chips {
|
|
359
|
+
margin-top: 10px;
|
|
360
|
+
display: flex;
|
|
361
|
+
gap: 6px;
|
|
362
|
+
flex-wrap: wrap;
|
|
363
|
+
}
|
|
364
|
+
.tag-chip {
|
|
365
|
+
border: 1px solid var(--line-strong);
|
|
366
|
+
border-radius: 999px;
|
|
367
|
+
background: var(--chip);
|
|
368
|
+
color: var(--text);
|
|
369
|
+
padding: 3px 9px;
|
|
370
|
+
font-size: 12px;
|
|
371
|
+
}
|
|
372
|
+
.tag-chip.muted {
|
|
373
|
+
color: var(--muted);
|
|
374
|
+
border-style: dashed;
|
|
375
|
+
}
|
|
376
|
+
.field-hint {
|
|
377
|
+
margin-top: 4px;
|
|
378
|
+
color: var(--muted);
|
|
379
|
+
font-size: 12px;
|
|
380
|
+
}
|
|
381
|
+
.field-error {
|
|
382
|
+
margin-top: 4px;
|
|
383
|
+
color: var(--err);
|
|
384
|
+
font-size: 12px;
|
|
385
|
+
}
|
|
386
|
+
.input-invalid {
|
|
387
|
+
border-color: color-mix(in srgb, var(--err) 55%, transparent);
|
|
388
|
+
}
|
|
389
|
+
.save-busy {
|
|
390
|
+
opacity: 0.7;
|
|
391
|
+
cursor: wait;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.logs {
|
|
395
|
+
max-height: 420px;
|
|
396
|
+
overflow: auto;
|
|
397
|
+
border: 1px solid var(--line);
|
|
398
|
+
border-radius: 12px;
|
|
399
|
+
background: var(--panel);
|
|
400
|
+
padding: 10px;
|
|
401
|
+
}
|
|
402
|
+
.log-item {
|
|
403
|
+
border-bottom: 1px dashed var(--line);
|
|
404
|
+
padding: 7px 0;
|
|
405
|
+
}
|
|
406
|
+
.log-item:last-child { border-bottom: 0; }
|
|
407
|
+
.log-info { color: #8dc6ff; }
|
|
408
|
+
.log-warn { color: var(--warn); }
|
|
409
|
+
.log-error { color: var(--err); }
|
|
410
|
+
.toolbar {
|
|
411
|
+
display: flex;
|
|
412
|
+
gap: 10px;
|
|
413
|
+
flex-wrap: wrap;
|
|
414
|
+
align-items: end;
|
|
415
|
+
margin-bottom: 10px;
|
|
416
|
+
}
|
|
417
|
+
.toolbar .field {
|
|
418
|
+
min-width: 180px;
|
|
419
|
+
}
|
|
420
|
+
.mono-block {
|
|
421
|
+
border: 1px solid var(--line);
|
|
422
|
+
border-radius: 10px;
|
|
423
|
+
background: color-mix(in srgb, var(--bg-elevated) 85%, transparent);
|
|
424
|
+
padding: 10px;
|
|
425
|
+
max-height: 240px;
|
|
426
|
+
overflow: auto;
|
|
427
|
+
white-space: pre-wrap;
|
|
428
|
+
word-break: break-word;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.toast {
|
|
432
|
+
position: fixed;
|
|
433
|
+
right: 16px;
|
|
434
|
+
bottom: 16px;
|
|
435
|
+
background: var(--bg-elevated);
|
|
436
|
+
border: 1px solid color-mix(in srgb, var(--brand) 32%, transparent);
|
|
437
|
+
color: var(--text);
|
|
438
|
+
border-radius: 10px;
|
|
439
|
+
padding: 10px 12px;
|
|
440
|
+
font-size: 13px;
|
|
441
|
+
min-width: 220px;
|
|
442
|
+
opacity: 0;
|
|
443
|
+
transform: translateY(8px);
|
|
444
|
+
pointer-events: none;
|
|
445
|
+
transition: all .2s ease;
|
|
446
|
+
}
|
|
447
|
+
.toast.show {
|
|
448
|
+
opacity: 1;
|
|
449
|
+
transform: translateY(0);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@media (max-width: 980px) {
|
|
453
|
+
.app { grid-template-columns: 1fr; }
|
|
454
|
+
.sidebar { border-right: 0; border-bottom: 1px solid var(--line); }
|
|
455
|
+
.nav { grid-template-columns: repeat(3, minmax(0,1fr)); }
|
|
456
|
+
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
457
|
+
.split, .row, .profile-layout { grid-template-columns: 1fr; }
|
|
458
|
+
}
|
|
459
|
+
</style>
|
|
460
|
+
</head>
|
|
461
|
+
<body>
|
|
462
|
+
<div class="app">
|
|
463
|
+
<aside class="sidebar">
|
|
464
|
+
<div class="brand">
|
|
465
|
+
<img id="brandLogo" class="brand-logo" src="/assets/silicaclaw-logo.png" alt="SilicaClaw logo" />
|
|
466
|
+
<div id="brandFallback" class="brand-badge hidden">SC</div>
|
|
467
|
+
<div>
|
|
468
|
+
<h1>SilicaClaw</h1>
|
|
469
|
+
<p>Control UI</p>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<nav class="nav">
|
|
474
|
+
<button class="tab active" data-tab="overview">Overview</button>
|
|
475
|
+
<button class="tab" data-tab="profile">Profile</button>
|
|
476
|
+
<button class="tab" data-tab="network">Network</button>
|
|
477
|
+
<button class="tab" data-tab="peers">Peers</button>
|
|
478
|
+
<button class="tab" data-tab="discovery">Discovery Events</button>
|
|
479
|
+
<button class="tab" data-tab="social">Social Config</button>
|
|
480
|
+
<button class="tab" data-tab="logs">Logs</button>
|
|
481
|
+
</nav>
|
|
482
|
+
|
|
483
|
+
<div class="sidebar-foot" id="sideMeta">adapter: -<br/>namespace: -</div>
|
|
484
|
+
</aside>
|
|
485
|
+
|
|
486
|
+
<main class="main">
|
|
487
|
+
<div class="topbar">
|
|
488
|
+
<div>
|
|
489
|
+
<h2>Local Console</h2>
|
|
490
|
+
<p>OpenClaw-inspired local-first agent control surface</p>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="status-row">
|
|
493
|
+
<div class="pill" id="pillAdapter">adapter: -</div>
|
|
494
|
+
<div class="pill" id="pillBroadcast">broadcast: -</div>
|
|
495
|
+
<div class="theme-switch">
|
|
496
|
+
<button id="themeDarkBtn" type="button">Dark</button>
|
|
497
|
+
<button id="themeLightBtn" type="button">Light</button>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
<div class="integration-strip warn" id="integrationStatusBar">
|
|
502
|
+
Connected to SilicaClaw: - · Network mode: - · Public discovery: -
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<div class="notice" id="initNotice"></div>
|
|
506
|
+
<div class="actions" id="onboardingActions" style="display:none; margin:8px 0 12px;">
|
|
507
|
+
<button id="enablePublicDiscoveryBtn">Enable Public Discovery</button>
|
|
508
|
+
<button class="secondary" id="disablePublicDiscoveryBtn">Disable Public Discovery</button>
|
|
509
|
+
</div>
|
|
510
|
+
<div class="field-hint" id="publicDiscoveryHint" style="margin-bottom:12px; display:none;">
|
|
511
|
+
Public discovery only shares signed profile/presence records. It does not share private files and does not enable chat or remote control.
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
<section id="view-overview" class="view active">
|
|
515
|
+
<div class="grid" id="overviewCards"></div>
|
|
516
|
+
<div class="split">
|
|
517
|
+
<div class="card">
|
|
518
|
+
<h3 class="title-sm">Discovered Agents</h3>
|
|
519
|
+
<div class="field-hint" id="agentsCountHint">0 agents</div>
|
|
520
|
+
<label class="field-hint" style="display:inline-flex;gap:6px;align-items:center;margin:6px 0 8px;">
|
|
521
|
+
<input type="checkbox" id="onlyOnlineToggle" />
|
|
522
|
+
Only show online
|
|
523
|
+
</label>
|
|
524
|
+
<div id="agentsWrap"></div>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="card">
|
|
527
|
+
<h3 class="title-sm">Node Snapshot</h3>
|
|
528
|
+
<div class="mono" id="snapshot"></div>
|
|
529
|
+
</div>
|
|
530
|
+
</div>
|
|
531
|
+
</section>
|
|
532
|
+
|
|
533
|
+
<section id="view-profile" class="view">
|
|
534
|
+
<div class="profile-layout">
|
|
535
|
+
<div class="card stack">
|
|
536
|
+
<h3 class="title-sm">Public Profile Editor</h3>
|
|
537
|
+
<form id="profileForm" class="stack">
|
|
538
|
+
<div class="row">
|
|
539
|
+
<div>
|
|
540
|
+
<label>Display Name</label>
|
|
541
|
+
<input name="display_name" placeholder="Agent name" maxlength="48" />
|
|
542
|
+
<div class="field-hint">Recommended 2-32 chars for better discoverability.</div>
|
|
543
|
+
<div class="field-error" id="errDisplayName"></div>
|
|
544
|
+
</div>
|
|
545
|
+
<div>
|
|
546
|
+
<label>Avatar URL</label>
|
|
547
|
+
<input name="avatar_url" placeholder="https://..." />
|
|
548
|
+
<div class="field-hint">Optional. Must be http(s) URL if provided.</div>
|
|
549
|
+
<div class="field-error" id="errAvatarUrl"></div>
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
<div>
|
|
553
|
+
<label>Bio</label>
|
|
554
|
+
<textarea name="bio" placeholder="Public summary" maxlength="280"></textarea>
|
|
555
|
+
<div class="field-hint"><span id="bioCount">0</span>/280</div>
|
|
556
|
+
</div>
|
|
557
|
+
<div>
|
|
558
|
+
<label>Tags (comma separated)</label>
|
|
559
|
+
<input name="tags" placeholder="ai,browser,local-first" />
|
|
560
|
+
<div class="field-hint">Up to 8 tags, each <= 20 chars.</div>
|
|
561
|
+
<div class="field-error" id="errTags"></div>
|
|
562
|
+
</div>
|
|
563
|
+
<label><input type="checkbox" name="public_enabled" /> Public Enabled</label>
|
|
564
|
+
<div class="actions">
|
|
565
|
+
<button type="submit" id="saveProfileBtn">Save Profile</button>
|
|
566
|
+
<button type="button" class="secondary" id="refreshProfileBtn">Reload</button>
|
|
567
|
+
</div>
|
|
568
|
+
</form>
|
|
569
|
+
<div id="profileFeedback" class="feedback">Ready.</div>
|
|
570
|
+
</div>
|
|
571
|
+
<div class="card stack">
|
|
572
|
+
<h3 class="title-sm">Live Preview</h3>
|
|
573
|
+
<div class="profile-meta">
|
|
574
|
+
<h4>Public Card</h4>
|
|
575
|
+
<div id="previewName" class="preview-name">(unnamed agent)</div>
|
|
576
|
+
<div id="previewBio" class="preview-bio">No bio yet.</div>
|
|
577
|
+
<div id="previewTags" class="tag-chips"></div>
|
|
578
|
+
</div>
|
|
579
|
+
<div class="profile-meta">
|
|
580
|
+
<h4>Publish Status</h4>
|
|
581
|
+
<div class="mono" id="previewPublish">public_enabled: false</div>
|
|
582
|
+
</div>
|
|
583
|
+
<div class="profile-meta">
|
|
584
|
+
<h4>Public Profile Preview</h4>
|
|
585
|
+
<div class="field-hint">This is the signed public profile view other nodes/explorer can see.</div>
|
|
586
|
+
<div class="actions" style="margin-top:8px;">
|
|
587
|
+
<button class="secondary" type="button" id="copyPublicProfilePreviewBtn">Copy public profile preview summary</button>
|
|
588
|
+
</div>
|
|
589
|
+
<div class="field-hint" id="publicVisibilityHint" style="margin-top:8px;">Visible fields: - | Hidden fields: -</div>
|
|
590
|
+
<div class="mono" id="publicVisibilityList" style="margin-top:6px;">-</div>
|
|
591
|
+
<div class="mono mono-block" id="publicProfilePreviewWrap">-</div>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
</section>
|
|
596
|
+
|
|
597
|
+
<section id="view-network" class="view">
|
|
598
|
+
<div class="grid" id="networkCards"></div>
|
|
599
|
+
<div class="split">
|
|
600
|
+
<div class="card">
|
|
601
|
+
<h3 class="title-sm">Runtime Components</h3>
|
|
602
|
+
<div class="mono" id="networkComponents"></div>
|
|
603
|
+
</div>
|
|
604
|
+
<div class="card">
|
|
605
|
+
<h3 class="title-sm">Actions</h3>
|
|
606
|
+
<div class="actions">
|
|
607
|
+
<button id="startBroadcastBtn">Start Broadcast</button>
|
|
608
|
+
<button class="secondary" id="stopBroadcastBtn">Stop Broadcast</button>
|
|
609
|
+
<button class="secondary" id="broadcastNowBtn">Broadcast Now</button>
|
|
610
|
+
<button class="secondary" id="refreshCacheBtn">Refresh Cache</button>
|
|
611
|
+
<button class="secondary" id="clearCacheBtn">Clear Discovered Cache</button>
|
|
612
|
+
<button class="secondary" id="quickGlobalPreviewBtn">Enable Cross-network Preview</button>
|
|
613
|
+
</div>
|
|
614
|
+
<div id="networkFeedback" class="feedback" style="margin-top:10px;">Ready.</div>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="split">
|
|
618
|
+
<div class="card" style="margin-top:10px;">
|
|
619
|
+
<h3 class="title-sm">Config Snapshot</h3>
|
|
620
|
+
<div class="mono mono-block" id="networkConfigSnapshot">-</div>
|
|
621
|
+
</div>
|
|
622
|
+
<div class="card" style="margin-top:10px;">
|
|
623
|
+
<h3 class="title-sm">Stats Snapshot</h3>
|
|
624
|
+
<div class="mono mono-block" id="networkStatsSnapshot">-</div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</section>
|
|
628
|
+
|
|
629
|
+
<section id="view-peers" class="view">
|
|
630
|
+
<div class="grid" id="peerCards"></div>
|
|
631
|
+
<div class="card" style="margin-top:10px;">
|
|
632
|
+
<h3 class="title-sm">Peer Inventory</h3>
|
|
633
|
+
<div id="peerTableWrap"></div>
|
|
634
|
+
</div>
|
|
635
|
+
<div class="card" style="margin-top:10px;">
|
|
636
|
+
<h3 class="title-sm">Peer Discovery Stats</h3>
|
|
637
|
+
<div class="mono mono-block" id="peerStatsWrap">-</div>
|
|
638
|
+
</div>
|
|
639
|
+
</section>
|
|
640
|
+
|
|
641
|
+
<section id="view-discovery" class="view">
|
|
642
|
+
<div class="grid" id="discoveryCards"></div>
|
|
643
|
+
<div class="split">
|
|
644
|
+
<div class="card" style="margin-top:10px;">
|
|
645
|
+
<h3 class="title-sm">Recent Discovery Events</h3>
|
|
646
|
+
<div class="logs" id="discoveryEventList"></div>
|
|
647
|
+
</div>
|
|
648
|
+
<div class="card" style="margin-top:10px;">
|
|
649
|
+
<h3 class="title-sm">Discovery Snapshot</h3>
|
|
650
|
+
<div class="mono mono-block" id="discoverySnapshot">-</div>
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
</section>
|
|
654
|
+
|
|
655
|
+
<section id="view-social" class="view">
|
|
656
|
+
<div class="card">
|
|
657
|
+
<h3 class="title-sm">Integration Status</h3>
|
|
658
|
+
<div class="feedback" id="socialStatusLine">Checking integration status...</div>
|
|
659
|
+
<div class="field-hint" id="socialStatusSubline">-</div>
|
|
660
|
+
<div class="field-hint" id="socialStateHint">-</div>
|
|
661
|
+
</div>
|
|
662
|
+
<div class="grid" id="socialPrimaryCards" style="margin-top:10px;"></div>
|
|
663
|
+
<div class="grid" id="socialIntegrationCards"></div>
|
|
664
|
+
<details class="card" style="margin-top:10px;">
|
|
665
|
+
<summary class="title-sm" style="cursor:pointer;">Advanced Network Details</summary>
|
|
666
|
+
<div class="grid" id="socialAdvancedCards" style="margin-top:10px;"></div>
|
|
667
|
+
<div class="mono mono-block" id="socialAdvancedWrap" style="margin-top:10px;">-</div>
|
|
668
|
+
</details>
|
|
669
|
+
<div class="split">
|
|
670
|
+
<div class="card" style="margin-top:10px;">
|
|
671
|
+
<h3 class="title-sm">Source & Parsed Frontmatter</h3>
|
|
672
|
+
<div class="mono mono-block" id="socialSourceWrap">-</div>
|
|
673
|
+
<div style="height:10px;"></div>
|
|
674
|
+
<div class="mono mono-block" id="socialRawWrap">-</div>
|
|
675
|
+
</div>
|
|
676
|
+
<div class="card" style="margin-top:10px;">
|
|
677
|
+
<h3 class="title-sm">Runtime Summary</h3>
|
|
678
|
+
<div class="mono mono-block" id="socialRuntimeWrap">-</div>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
<div class="card" style="margin-top:10px;">
|
|
682
|
+
<h3 class="title-sm">Export Template Preview</h3>
|
|
683
|
+
<div class="mono mono-block" id="socialTemplateWrap">-</div>
|
|
684
|
+
</div>
|
|
685
|
+
<div class="card" style="margin-top:10px;">
|
|
686
|
+
<h3 class="title-sm">Actions</h3>
|
|
687
|
+
<div class="toolbar">
|
|
688
|
+
<div class="field">
|
|
689
|
+
<label for="socialModeSelect">Network Mode (runtime)</label>
|
|
690
|
+
<select id="socialModeSelect">
|
|
691
|
+
<option value="local">local</option>
|
|
692
|
+
<option value="lan">lan</option>
|
|
693
|
+
<option value="global-preview">global-preview</option>
|
|
694
|
+
</select>
|
|
695
|
+
</div>
|
|
696
|
+
<div>
|
|
697
|
+
<button class="secondary" id="socialModeApplyBtn">Apply Runtime Mode</button>
|
|
698
|
+
</div>
|
|
699
|
+
</div>
|
|
700
|
+
<div class="actions">
|
|
701
|
+
<button id="socialReloadBtn">Reload Config</button>
|
|
702
|
+
<button class="secondary" id="socialGenerateBtn">Generate Default social.md</button>
|
|
703
|
+
<button class="secondary" id="socialExportBtn">Export social.md template</button>
|
|
704
|
+
<button class="secondary" id="socialCopyBtn">Copy Template</button>
|
|
705
|
+
<button class="secondary" id="socialDownloadBtn">Download Template</button>
|
|
706
|
+
</div>
|
|
707
|
+
<div id="socialFeedback" class="feedback" style="margin-top:10px;">Ready.</div>
|
|
708
|
+
</div>
|
|
709
|
+
</section>
|
|
710
|
+
|
|
711
|
+
<section id="view-logs" class="view">
|
|
712
|
+
<div class="card">
|
|
713
|
+
<h3 class="title-sm">Recent Logs</h3>
|
|
714
|
+
<div class="toolbar">
|
|
715
|
+
<div class="field">
|
|
716
|
+
<label for="logLevelFilter">Category</label>
|
|
717
|
+
<select id="logLevelFilter">
|
|
718
|
+
<option value="all">all</option>
|
|
719
|
+
<option value="info">info</option>
|
|
720
|
+
<option value="warn">warn</option>
|
|
721
|
+
<option value="error">error</option>
|
|
722
|
+
</select>
|
|
723
|
+
</div>
|
|
724
|
+
<div>
|
|
725
|
+
<button type="button" class="secondary" id="refreshLogsBtn">Refresh Logs</button>
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
<div class="logs" id="logList"></div>
|
|
729
|
+
</div>
|
|
730
|
+
</section>
|
|
731
|
+
</main>
|
|
732
|
+
</div>
|
|
733
|
+
|
|
734
|
+
<div id="toast" class="toast"></div>
|
|
735
|
+
|
|
736
|
+
<script>
|
|
737
|
+
let activeTab = 'overview';
|
|
738
|
+
let profileBaseline = '';
|
|
739
|
+
let profileDirty = false;
|
|
740
|
+
let profileSaving = false;
|
|
741
|
+
let logsCache = [];
|
|
742
|
+
let logLevelFilter = 'all';
|
|
743
|
+
let socialTemplate = '';
|
|
744
|
+
let onlyShowOnline = false;
|
|
745
|
+
|
|
746
|
+
function ago(ts) {
|
|
747
|
+
if (!ts) return '-';
|
|
748
|
+
const s = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
|
749
|
+
if (s < 60) return `${s}s ago`;
|
|
750
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
751
|
+
return `${Math.floor(s / 3600)}h ago`;
|
|
752
|
+
}
|
|
753
|
+
function shortId(id) {
|
|
754
|
+
if (!id) return '-';
|
|
755
|
+
return `${id.slice(0, 10)}...${id.slice(-6)}`;
|
|
756
|
+
}
|
|
757
|
+
function toPrettyJson(obj) {
|
|
758
|
+
try {
|
|
759
|
+
return JSON.stringify(obj, null, 2);
|
|
760
|
+
} catch {
|
|
761
|
+
return String(obj);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function toast(msg) {
|
|
765
|
+
const t = document.getElementById('toast');
|
|
766
|
+
t.textContent = msg;
|
|
767
|
+
t.classList.add('show');
|
|
768
|
+
setTimeout(() => t.classList.remove('show'), 2000);
|
|
769
|
+
}
|
|
770
|
+
function flashButton(btn, doneText = 'Done') {
|
|
771
|
+
if (!btn) return;
|
|
772
|
+
const oldText = btn.textContent || '';
|
|
773
|
+
btn.disabled = true;
|
|
774
|
+
btn.textContent = doneText;
|
|
775
|
+
setTimeout(() => {
|
|
776
|
+
btn.textContent = oldText;
|
|
777
|
+
btn.disabled = false;
|
|
778
|
+
}, 900);
|
|
779
|
+
}
|
|
780
|
+
function setFeedback(id, text, level = 'info') {
|
|
781
|
+
const el = document.getElementById(id);
|
|
782
|
+
el.textContent = text;
|
|
783
|
+
el.style.color = level === 'error' ? '#ff6b81' : level === 'warn' ? '#ffb454' : '#9aa7c3';
|
|
784
|
+
}
|
|
785
|
+
function applyTheme(mode) {
|
|
786
|
+
const next = mode === 'light' ? 'light' : 'dark';
|
|
787
|
+
document.documentElement.setAttribute('data-theme-mode', next);
|
|
788
|
+
localStorage.setItem('silicaclaw_theme_mode', next);
|
|
789
|
+
document.getElementById('themeDarkBtn').classList.toggle('active', next === 'dark');
|
|
790
|
+
document.getElementById('themeLightBtn').classList.toggle('active', next === 'light');
|
|
791
|
+
}
|
|
792
|
+
function parseTags(raw) {
|
|
793
|
+
return String(raw || '')
|
|
794
|
+
.split(',')
|
|
795
|
+
.map((s) => s.trim())
|
|
796
|
+
.filter(Boolean)
|
|
797
|
+
.filter((tag, idx, arr) => arr.indexOf(tag) === idx);
|
|
798
|
+
}
|
|
799
|
+
function field(form, name) {
|
|
800
|
+
return form.querySelector(`[name="${name}"]`);
|
|
801
|
+
}
|
|
802
|
+
function normalizeTagsInput(raw) {
|
|
803
|
+
return parseTags(raw).join(', ');
|
|
804
|
+
}
|
|
805
|
+
function profileSnapshot(form) {
|
|
806
|
+
const displayNameEl = field(form, 'display_name');
|
|
807
|
+
const bioEl = field(form, 'bio');
|
|
808
|
+
const avatarEl = field(form, 'avatar_url');
|
|
809
|
+
const tagsEl = field(form, 'tags');
|
|
810
|
+
const publicEl = field(form, 'public_enabled');
|
|
811
|
+
return JSON.stringify({
|
|
812
|
+
display_name: String(displayNameEl?.value || '').trim(),
|
|
813
|
+
bio: String(bioEl?.value || '').trim(),
|
|
814
|
+
avatar_url: String(avatarEl?.value || '').trim(),
|
|
815
|
+
tags: parseTags(tagsEl?.value || ''),
|
|
816
|
+
public_enabled: !!publicEl?.checked,
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
function updateDirtyState(form, fromUserInput = false) {
|
|
820
|
+
const now = profileSnapshot(form);
|
|
821
|
+
profileDirty = now !== profileBaseline;
|
|
822
|
+
if (fromUserInput && !profileSaving) {
|
|
823
|
+
setFeedback(
|
|
824
|
+
'profileFeedback',
|
|
825
|
+
profileDirty ? 'You have unsaved changes.' : 'All changes saved.',
|
|
826
|
+
profileDirty ? 'warn' : 'info'
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function setProfileBaseline(form) {
|
|
831
|
+
profileBaseline = profileSnapshot(form);
|
|
832
|
+
profileDirty = false;
|
|
833
|
+
}
|
|
834
|
+
function setInputError(inputEl, errorElId, message) {
|
|
835
|
+
const errEl = document.getElementById(errorElId);
|
|
836
|
+
errEl.textContent = message || '';
|
|
837
|
+
if (inputEl) {
|
|
838
|
+
inputEl.classList.toggle('input-invalid', Boolean(message));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function validateProfileForm(form) {
|
|
842
|
+
const displayNameEl = field(form, 'display_name');
|
|
843
|
+
const avatarEl = field(form, 'avatar_url');
|
|
844
|
+
const tagsEl = field(form, 'tags');
|
|
845
|
+
const displayName = String(displayNameEl?.value || '').trim();
|
|
846
|
+
const avatarUrl = String(avatarEl?.value || '').trim();
|
|
847
|
+
const tags = parseTags(tagsEl?.value || '');
|
|
848
|
+
|
|
849
|
+
let ok = true;
|
|
850
|
+
let err = '';
|
|
851
|
+
if (displayName.length > 0 && displayName.length < 2) {
|
|
852
|
+
err = 'Display name should be at least 2 chars.';
|
|
853
|
+
ok = false;
|
|
854
|
+
} else if (displayName.length > 48) {
|
|
855
|
+
err = 'Display name too long.';
|
|
856
|
+
ok = false;
|
|
857
|
+
}
|
|
858
|
+
setInputError(displayNameEl, 'errDisplayName', err);
|
|
859
|
+
|
|
860
|
+
err = '';
|
|
861
|
+
if (avatarUrl && !/^https?:\/\//i.test(avatarUrl)) {
|
|
862
|
+
err = 'Avatar URL must start with http:// or https://';
|
|
863
|
+
ok = false;
|
|
864
|
+
}
|
|
865
|
+
setInputError(avatarEl, 'errAvatarUrl', err);
|
|
866
|
+
|
|
867
|
+
err = '';
|
|
868
|
+
if (tags.length > 8) {
|
|
869
|
+
err = 'Too many tags (max 8).';
|
|
870
|
+
ok = false;
|
|
871
|
+
} else if (tags.some((t) => t.length > 20)) {
|
|
872
|
+
err = 'Each tag must be <= 20 chars.';
|
|
873
|
+
ok = false;
|
|
874
|
+
}
|
|
875
|
+
setInputError(tagsEl, 'errTags', err);
|
|
876
|
+
return { ok, tags };
|
|
877
|
+
}
|
|
878
|
+
function renderProfilePreview(form) {
|
|
879
|
+
const displayName = String(field(form, 'display_name')?.value || '').trim();
|
|
880
|
+
const bio = String(field(form, 'bio')?.value || '').trim();
|
|
881
|
+
const tags = parseTags(field(form, 'tags')?.value || '');
|
|
882
|
+
const enabled = !!field(form, 'public_enabled')?.checked;
|
|
883
|
+
|
|
884
|
+
document.getElementById('previewName').textContent = displayName || '(unnamed agent)';
|
|
885
|
+
document.getElementById('previewBio').textContent = bio || 'No bio yet.';
|
|
886
|
+
document.getElementById('previewPublish').textContent = `public_enabled: ${enabled}`;
|
|
887
|
+
document.getElementById('bioCount').textContent = String(bio.length);
|
|
888
|
+
|
|
889
|
+
const tagBox = document.getElementById('previewTags');
|
|
890
|
+
if (!tags.length) {
|
|
891
|
+
tagBox.innerHTML = '<span class=\"tag-chip muted\">No tags</span>';
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
tagBox.innerHTML = tags.map((tag) => `<span class=\"tag-chip\">${tag}</span>`).join('');
|
|
895
|
+
}
|
|
896
|
+
function setSaveBusy(busy) {
|
|
897
|
+
profileSaving = busy;
|
|
898
|
+
const btn = document.getElementById('saveProfileBtn');
|
|
899
|
+
btn.disabled = busy;
|
|
900
|
+
btn.classList.toggle('save-busy', busy);
|
|
901
|
+
btn.textContent = busy ? 'Saving...' : 'Save Profile';
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
async function api(path, options = {}) {
|
|
905
|
+
const res = await fetch(path, { headers: { 'Content-Type': 'application/json' }, ...options });
|
|
906
|
+
const json = await res.json().catch(() => null);
|
|
907
|
+
if (!res.ok || !json || !json.ok) {
|
|
908
|
+
throw new Error(json?.error?.message || `Request failed (${res.status})`);
|
|
909
|
+
}
|
|
910
|
+
return json;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function switchTab(tab) {
|
|
914
|
+
if (activeTab === 'profile' && tab !== 'profile' && profileDirty && !profileSaving) {
|
|
915
|
+
const ok = window.confirm('You have unsaved profile changes. Leave anyway?');
|
|
916
|
+
if (!ok) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
activeTab = tab;
|
|
921
|
+
document.querySelectorAll('.tab').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));
|
|
922
|
+
['overview', 'profile', 'network', 'peers', 'discovery', 'social', 'logs'].forEach((k) => {
|
|
923
|
+
document.getElementById(`view-${k}`).classList.toggle('active', k === tab);
|
|
924
|
+
});
|
|
925
|
+
if (tab === 'profile' && !profileDirty && !profileSaving) {
|
|
926
|
+
refreshProfile().catch(() => {});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
async function refreshOverview() {
|
|
931
|
+
const [overview, discovered] = await Promise.all([api('/api/overview'), api('/api/search?q=')]);
|
|
932
|
+
const o = overview.data;
|
|
933
|
+
const all = discovered.data || [];
|
|
934
|
+
const d = onlyShowOnline ? all.filter((agent) => agent.online) : all;
|
|
935
|
+
|
|
936
|
+
document.getElementById('overviewCards').innerHTML = [
|
|
937
|
+
['Discovered', o.discovered_count],
|
|
938
|
+
['Online', o.online_count],
|
|
939
|
+
['Offline', o.offline_count],
|
|
940
|
+
['Presence TTL', `${Math.floor(o.presence_ttl_ms / 1000)}s`],
|
|
941
|
+
].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value">${v}</div></div>`).join('');
|
|
942
|
+
|
|
943
|
+
document.getElementById('snapshot').textContent = [
|
|
944
|
+
`agent_id: ${o.agent_id || '-'}`,
|
|
945
|
+
`public_enabled: ${o.public_enabled}`,
|
|
946
|
+
`broadcast_enabled: ${o.broadcast_enabled}`,
|
|
947
|
+
`last_broadcast: ${ago(o.last_broadcast_at)}`,
|
|
948
|
+
].join('\n');
|
|
949
|
+
|
|
950
|
+
document.getElementById('pillBroadcast').textContent = o.broadcast_enabled ? 'broadcast: running' : 'broadcast: paused';
|
|
951
|
+
document.getElementById('pillBroadcast').className = `pill ${o.broadcast_enabled ? 'ok' : 'warn'}`;
|
|
952
|
+
|
|
953
|
+
const init = o.init_state || {};
|
|
954
|
+
const onboarding = o.onboarding || {};
|
|
955
|
+
const notice = document.getElementById('initNotice');
|
|
956
|
+
const onboardingActions = document.getElementById('onboardingActions');
|
|
957
|
+
const enableBtn = document.getElementById('enablePublicDiscoveryBtn');
|
|
958
|
+
const disableBtn = document.getElementById('disablePublicDiscoveryBtn');
|
|
959
|
+
const discoveryHint = document.getElementById('publicDiscoveryHint');
|
|
960
|
+
const showOnboarding = onboarding.first_run || init.identity_auto_created || init.profile_auto_created || init.social_auto_created;
|
|
961
|
+
if (onboarding.first_run || init.identity_auto_created || init.profile_auto_created || init.social_auto_created) {
|
|
962
|
+
notice.classList.add('show');
|
|
963
|
+
notice.textContent = `OpenClaw already connected to SilicaClaw · mode=${onboarding.mode || '-'} · discoverable=${onboarding.discoverable ? 'yes' : 'no'} · next: update display name and export social.md`;
|
|
964
|
+
} else {
|
|
965
|
+
notice.classList.remove('show');
|
|
966
|
+
}
|
|
967
|
+
if (onboarding.can_enable_public_discovery || onboarding.public_enabled) {
|
|
968
|
+
onboardingActions.style.display = 'flex';
|
|
969
|
+
discoveryHint.style.display = 'block';
|
|
970
|
+
if (onboarding.public_enabled) {
|
|
971
|
+
enableBtn.style.display = 'none';
|
|
972
|
+
disableBtn.style.display = 'inline-block';
|
|
973
|
+
} else {
|
|
974
|
+
enableBtn.style.display = 'inline-block';
|
|
975
|
+
disableBtn.style.display = 'none';
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
978
|
+
onboardingActions.style.display = 'none';
|
|
979
|
+
discoveryHint.style.display = 'none';
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (!d.length) {
|
|
983
|
+
document.getElementById('agentsCountHint').textContent = '0 agents';
|
|
984
|
+
document.getElementById('agentsWrap').innerHTML = '<div class="label">No discovered agents yet.</div>';
|
|
985
|
+
} else {
|
|
986
|
+
document.getElementById('agentsCountHint').textContent = onlyShowOnline
|
|
987
|
+
? `${d.length}/${all.length} agents (online filter)`
|
|
988
|
+
: `${d.length} agents discovered`;
|
|
989
|
+
document.getElementById('agentsWrap').innerHTML = `
|
|
990
|
+
<table class="table">
|
|
991
|
+
<thead><tr><th>Name</th><th>Agent ID</th><th>Status</th><th>Updated</th></tr></thead>
|
|
992
|
+
<tbody>
|
|
993
|
+
${d.map((a) => `
|
|
994
|
+
<tr>
|
|
995
|
+
<td>${a.display_name || '(unnamed)'}</td>
|
|
996
|
+
<td class="mono">${shortId(a.agent_id)}</td>
|
|
997
|
+
<td class="${a.online ? 'online' : 'offline'}">${a.online ? 'online' : 'offline'}</td>
|
|
998
|
+
<td>${ago(a.updated_at)}</td>
|
|
999
|
+
</tr>
|
|
1000
|
+
`).join('')}
|
|
1001
|
+
</tbody>
|
|
1002
|
+
</table>
|
|
1003
|
+
`;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
async function refreshProfile() {
|
|
1008
|
+
const p = (await api('/api/profile')).data;
|
|
1009
|
+
if (!p) return;
|
|
1010
|
+
const f = document.getElementById('profileForm');
|
|
1011
|
+
field(f, 'display_name').value = p.display_name || '';
|
|
1012
|
+
field(f, 'bio').value = p.bio || '';
|
|
1013
|
+
field(f, 'tags').value = (p.tags || []).join(', ');
|
|
1014
|
+
field(f, 'avatar_url').value = p.avatar_url || '';
|
|
1015
|
+
field(f, 'public_enabled').checked = !!p.public_enabled;
|
|
1016
|
+
renderProfilePreview(f);
|
|
1017
|
+
setProfileBaseline(f);
|
|
1018
|
+
await refreshPublicProfilePreview();
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
async function refreshPublicProfilePreview() {
|
|
1022
|
+
const summary = (await api('/api/public-profile/preview')).data;
|
|
1023
|
+
document.getElementById('publicProfilePreviewWrap').textContent = toPrettyJson(summary || null);
|
|
1024
|
+
const visibleFields = summary?.public_visibility?.visible_fields || [];
|
|
1025
|
+
const hiddenFields = summary?.public_visibility?.hidden_fields || [];
|
|
1026
|
+
const visible = visibleFields.join(', ') || '-';
|
|
1027
|
+
const hidden = hiddenFields.join(', ') || '-';
|
|
1028
|
+
document.getElementById('publicVisibilityHint').textContent = `Visible fields: ${visible} | Hidden fields: ${hidden}`;
|
|
1029
|
+
const rows = [
|
|
1030
|
+
...visibleFields.map((field) => `[visible] ${field}`),
|
|
1031
|
+
...hiddenFields.map((field) => `[hidden] ${field}`),
|
|
1032
|
+
];
|
|
1033
|
+
document.getElementById('publicVisibilityList').textContent = rows.length ? rows.join('\n') : '-';
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
async function refreshNetwork() {
|
|
1037
|
+
const [cfg, sts, rtp] = await Promise.all([api('/api/network/config'), api('/api/network/stats'), api('/api/runtime/paths')]);
|
|
1038
|
+
const c = cfg.data;
|
|
1039
|
+
const s = sts.data;
|
|
1040
|
+
const runtimePaths = rtp.data || {};
|
|
1041
|
+
const msg = s.message_counters || {};
|
|
1042
|
+
const p = s.peer_counters || {};
|
|
1043
|
+
const a = s.adapter_stats || {};
|
|
1044
|
+
const t = s.adapter_transport_stats || {};
|
|
1045
|
+
const d = s.adapter_discovery_stats || {};
|
|
1046
|
+
const dx = s.adapter_diagnostics_summary || {};
|
|
1047
|
+
const ac = s.adapter_config || c.adapter_config || {};
|
|
1048
|
+
|
|
1049
|
+
document.getElementById('pillAdapter').textContent = `adapter: ${c.adapter}`;
|
|
1050
|
+
document.getElementById('sideMeta').innerHTML = `adapter: ${c.adapter}<br/>namespace: ${c.namespace || '-'}<br/>port: ${c.port ?? '-'}`;
|
|
1051
|
+
|
|
1052
|
+
document.getElementById('networkCards').innerHTML = [
|
|
1053
|
+
['Adapter', c.adapter],
|
|
1054
|
+
['Namespace', c.namespace || '-'],
|
|
1055
|
+
['Port', c.port ?? '-'],
|
|
1056
|
+
['Started', ac.started === true ? 'yes' : ac.started === false ? 'no' : '-'],
|
|
1057
|
+
['Transport State', ac.transport?.state || '-'],
|
|
1058
|
+
['Signaling URL', dx.signaling_url || '-'],
|
|
1059
|
+
['Signaling Endpoints', (dx.signaling_endpoints || []).length || 0],
|
|
1060
|
+
['WebRTC Room', dx.room || '-'],
|
|
1061
|
+
['Bootstrap Sources', (dx.bootstrap_sources || []).length || 0],
|
|
1062
|
+
['Seed Peers', dx.seed_peers_count ?? 0],
|
|
1063
|
+
['Discovery Events', dx.discovery_events_total ?? 0],
|
|
1064
|
+
['Recv', msg.received_total ?? 0],
|
|
1065
|
+
['Sent', msg.broadcast_total ?? 0],
|
|
1066
|
+
['Peers', p.total ?? 0],
|
|
1067
|
+
['Online Peers', p.online ?? 0],
|
|
1068
|
+
['Active WebRTC Peers', dx.active_webrtc_peers ?? '-'],
|
|
1069
|
+
['Reconnect Attempts', dx.reconnect_attempts_total ?? '-'],
|
|
1070
|
+
['Last Inbound', ago(msg.last_message_at)],
|
|
1071
|
+
['Last Outbound', ago(msg.last_broadcast_at)],
|
|
1072
|
+
].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
|
|
1073
|
+
|
|
1074
|
+
const comp = c.components || {};
|
|
1075
|
+
const lim = c.limits || {};
|
|
1076
|
+
document.getElementById('networkComponents').textContent = [
|
|
1077
|
+
`demo_mode: ${c.demo_mode || '-'}`,
|
|
1078
|
+
`transport: ${comp.transport || '-'}`,
|
|
1079
|
+
`discovery: ${comp.discovery || '-'}`,
|
|
1080
|
+
`envelope_codec: ${comp.envelope_codec || '-'}`,
|
|
1081
|
+
`topic_codec: ${comp.topic_codec || '-'}`,
|
|
1082
|
+
`max_message_bytes: ${lim.max_message_bytes ?? '-'}`,
|
|
1083
|
+
`dedupe_window_ms: ${lim.dedupe_window_ms ?? '-'}`,
|
|
1084
|
+
`dedupe_max_entries: ${lim.dedupe_max_entries ?? '-'}`,
|
|
1085
|
+
`max_future_drift_ms: ${lim.max_future_drift_ms ?? '-'}`,
|
|
1086
|
+
`max_past_drift_ms: ${lim.max_past_drift_ms ?? '-'}`,
|
|
1087
|
+
`dropped_duplicate: ${a.dropped_duplicate ?? '-'}`,
|
|
1088
|
+
`dropped_self: ${a.dropped_self ?? '-'}`,
|
|
1089
|
+
`dropped_malformed: ${a.dropped_malformed ?? '-'}`,
|
|
1090
|
+
`dropped_decode_failed: ${a.dropped_decode_failed ?? '-'}`,
|
|
1091
|
+
`dropped_timestamp_future_drift: ${a.dropped_timestamp_future_drift ?? '-'}`,
|
|
1092
|
+
`dropped_timestamp_past_drift: ${a.dropped_timestamp_past_drift ?? '-'}`,
|
|
1093
|
+
`dropped_namespace_mismatch: ${a.dropped_namespace_mismatch ?? '-'}`,
|
|
1094
|
+
`received_validated: ${a.received_validated ?? '-'}`,
|
|
1095
|
+
`send_errors: ${a.send_errors ?? '-'}`,
|
|
1096
|
+
`transport_send_errors: ${t.send_errors ?? '-'}`,
|
|
1097
|
+
`discovery_heartbeat_send_errors: ${d.heartbeat_send_errors ?? '-'}`,
|
|
1098
|
+
`signaling_messages_sent_total: ${dx.signaling_messages_sent_total ?? '-'}`,
|
|
1099
|
+
`signaling_messages_received_total: ${dx.signaling_messages_received_total ?? '-'}`,
|
|
1100
|
+
`signaling_endpoints: ${Array.isArray(dx.signaling_endpoints) ? dx.signaling_endpoints.join(', ') : '-'}`,
|
|
1101
|
+
`bootstrap_sources: ${Array.isArray(dx.bootstrap_sources) ? dx.bootstrap_sources.join(', ') : '-'}`,
|
|
1102
|
+
`seed_peers_count: ${dx.seed_peers_count ?? '-'}`,
|
|
1103
|
+
`discovery_events_total: ${dx.discovery_events_total ?? '-'}`,
|
|
1104
|
+
`last_discovery_event_at: ${dx.last_discovery_event_at ? new Date(dx.last_discovery_event_at).toISOString() : '-'}`,
|
|
1105
|
+
].join('\n');
|
|
1106
|
+
|
|
1107
|
+
document.getElementById('networkConfigSnapshot').textContent = toPrettyJson({
|
|
1108
|
+
config: c,
|
|
1109
|
+
adapter_config: ac,
|
|
1110
|
+
runtime_paths: runtimePaths,
|
|
1111
|
+
});
|
|
1112
|
+
document.getElementById('networkStatsSnapshot').textContent = toPrettyJson({
|
|
1113
|
+
stats: s,
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async function refreshPeers() {
|
|
1118
|
+
const [peerRes, statsRes] = await Promise.all([api('/api/peers'), api('/api/network/stats')]);
|
|
1119
|
+
const peers = peerRes.data || {};
|
|
1120
|
+
const ds = statsRes.data?.adapter_discovery_stats || {};
|
|
1121
|
+
const summary = peers.diagnostics_summary || {};
|
|
1122
|
+
|
|
1123
|
+
document.getElementById('peerCards').innerHTML = [
|
|
1124
|
+
['Total', peers.total || 0],
|
|
1125
|
+
['Online', peers.online || 0],
|
|
1126
|
+
['Stale', peers.stale || 0],
|
|
1127
|
+
['Namespace', peers.namespace || '-'],
|
|
1128
|
+
['Signaling URL', summary.signaling_url || '-'],
|
|
1129
|
+
['Signaling Endpoints', (summary.signaling_endpoints || []).length || 0],
|
|
1130
|
+
['Room', summary.room || '-'],
|
|
1131
|
+
['Bootstrap Sources', (summary.bootstrap_sources || []).length || 0],
|
|
1132
|
+
['Seed Peers', summary.seed_peers_count ?? 0],
|
|
1133
|
+
['Discovery Events', summary.discovery_events_total ?? 0],
|
|
1134
|
+
['Active WebRTC Peers', summary.active_webrtc_peers ?? '-'],
|
|
1135
|
+
['Observe Calls', ds.observe_calls || 0],
|
|
1136
|
+
['Heartbeats', ds.heartbeat_sent || 0],
|
|
1137
|
+
['Peers Added', ds.peers_added || 0],
|
|
1138
|
+
['Peers Removed', ds.peers_removed || 0],
|
|
1139
|
+
].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
|
|
1140
|
+
|
|
1141
|
+
if (!peers.items || !peers.items.length) {
|
|
1142
|
+
document.getElementById('peerTableWrap').innerHTML = '<div class="label">No peers discovered yet.</div>';
|
|
1143
|
+
document.getElementById('peerStatsWrap').textContent = toPrettyJson({
|
|
1144
|
+
discovery_stats: ds,
|
|
1145
|
+
diagnostics_summary: summary,
|
|
1146
|
+
});
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
document.getElementById('peerTableWrap').innerHTML = `
|
|
1151
|
+
<table class="table">
|
|
1152
|
+
<thead><tr><th>Peer</th><th>Status</th><th>Last Seen</th><th>Stale Since</th><th>Messages</th><th>First Seen</th><th>Meta</th></tr></thead>
|
|
1153
|
+
<tbody>
|
|
1154
|
+
${peers.items.map((peer) => `
|
|
1155
|
+
<tr>
|
|
1156
|
+
<td class="mono">${shortId(peer.peer_id)}</td>
|
|
1157
|
+
<td class="${peer.status === 'online' ? 'online' : 'stale'}">${peer.status}</td>
|
|
1158
|
+
<td>${ago(peer.last_seen_at)}</td>
|
|
1159
|
+
<td>${peer.stale_since_at ? ago(peer.stale_since_at) : '-'}</td>
|
|
1160
|
+
<td>${peer.messages_seen || 0}</td>
|
|
1161
|
+
<td>${new Date(peer.first_seen_at).toLocaleTimeString()}</td>
|
|
1162
|
+
<td class="mono">${peer.meta ? JSON.stringify(peer.meta) : '-'}</td>
|
|
1163
|
+
</tr>
|
|
1164
|
+
`).join('')}
|
|
1165
|
+
</tbody>
|
|
1166
|
+
</table>
|
|
1167
|
+
`;
|
|
1168
|
+
document.getElementById('peerStatsWrap').textContent = toPrettyJson({
|
|
1169
|
+
discovery_stats: ds,
|
|
1170
|
+
diagnostics_summary: summary,
|
|
1171
|
+
adapter_stats: statsRes.data?.adapter_stats || {},
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
async function refreshDiscovery() {
|
|
1176
|
+
const eventsRes = await api('/api/discovery/events');
|
|
1177
|
+
const payload = eventsRes.data || {};
|
|
1178
|
+
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
1179
|
+
|
|
1180
|
+
document.getElementById('discoveryCards').innerHTML = [
|
|
1181
|
+
['Adapter', payload.adapter || '-'],
|
|
1182
|
+
['Namespace', payload.namespace || '-'],
|
|
1183
|
+
['Events Total', payload.total ?? 0],
|
|
1184
|
+
['Last Event', ago(payload.last_event_at)],
|
|
1185
|
+
['Signaling Endpoints', (payload.signaling_endpoints || []).length || 0],
|
|
1186
|
+
['Seed Peers', payload.seed_peers_count ?? 0],
|
|
1187
|
+
].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
|
|
1188
|
+
|
|
1189
|
+
if (!items.length) {
|
|
1190
|
+
document.getElementById('discoveryEventList').innerHTML = '<div class="label">No discovery events yet.</div>';
|
|
1191
|
+
} else {
|
|
1192
|
+
document.getElementById('discoveryEventList').innerHTML = items
|
|
1193
|
+
.slice()
|
|
1194
|
+
.reverse()
|
|
1195
|
+
.map((event) => `
|
|
1196
|
+
<div class="log-item">
|
|
1197
|
+
<div class="mono" style="color:#c5d8ff;">[${event.type}] ${event.peer_id || '-'} ${event.endpoint || ''}</div>
|
|
1198
|
+
<div class="label">${event.detail || '-'}</div>
|
|
1199
|
+
<div class="mono" style="color:#90a2c3;">${new Date(event.at).toLocaleString()}</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
`)
|
|
1202
|
+
.join('');
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
document.getElementById('discoverySnapshot').textContent = toPrettyJson(payload);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
async function refreshSocial() {
|
|
1209
|
+
const [socialRes, summaryRes, statusRes] = await Promise.all([
|
|
1210
|
+
api('/api/social/config'),
|
|
1211
|
+
api('/api/social/integration-summary'),
|
|
1212
|
+
api('/api/integration/status'),
|
|
1213
|
+
]);
|
|
1214
|
+
const social = socialRes.data || {};
|
|
1215
|
+
const summary = summaryRes.data || {};
|
|
1216
|
+
const status = statusRes.data || {};
|
|
1217
|
+
const runtime = social.runtime || {};
|
|
1218
|
+
const config = social.social_config || {};
|
|
1219
|
+
const network = config.network || {};
|
|
1220
|
+
const runtimeNetwork = runtime.resolved_network || {};
|
|
1221
|
+
const discoverable = status.discoverable === true;
|
|
1222
|
+
const mode = status.network_mode || summary.current_network_mode || network.mode || runtimeNetwork.mode || '-';
|
|
1223
|
+
const summaryLine = status.status_line || summary.summary_line || `${summary.connected ? 'Connected to SilicaClaw' : 'Not connected to SilicaClaw'} · ${discoverable ? 'Discoverable in current mode' : 'Not discoverable in current mode'} · Using ${mode}`;
|
|
1224
|
+
const publicDiscoveryText = status.public_enabled ? 'Public discovery enabled' : 'Public discovery disabled';
|
|
1225
|
+
|
|
1226
|
+
const namespaceText = `${status.connected_to_silicaclaw ? 'Connected to SilicaClaw' : 'Not connected'} · ${publicDiscoveryText} · mode ${mode}`;
|
|
1227
|
+
document.getElementById('socialStatusLine').textContent = summaryLine;
|
|
1228
|
+
document.getElementById('socialStatusSubline').textContent = namespaceText;
|
|
1229
|
+
const bar = document.getElementById('integrationStatusBar');
|
|
1230
|
+
bar.className = `integration-strip ${status.connected_to_silicaclaw && status.public_enabled ? 'ok' : 'warn'}`;
|
|
1231
|
+
bar.textContent = `Connected to SilicaClaw: ${status.connected_to_silicaclaw ? 'yes' : 'no'} · Network mode: ${mode} · Public discovery: ${status.public_enabled ? 'enabled' : 'disabled'}`;
|
|
1232
|
+
const reasons = [];
|
|
1233
|
+
if (!status.configured && status.configured_reason) reasons.push(`Configured: ${status.configured_reason}`);
|
|
1234
|
+
if (!status.running && status.running_reason) reasons.push(`Running: ${status.running_reason}`);
|
|
1235
|
+
if (!status.discoverable && status.discoverable_reason) reasons.push(`Discoverable: ${status.discoverable_reason}`);
|
|
1236
|
+
document.getElementById('socialStateHint').textContent = reasons.length ? reasons.join(' · ') : 'All integration checks passed.';
|
|
1237
|
+
const modeSelect = document.getElementById('socialModeSelect');
|
|
1238
|
+
if (modeSelect && mode !== '-') {
|
|
1239
|
+
modeSelect.value = mode;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
document.getElementById('socialPrimaryCards').innerHTML = [
|
|
1243
|
+
['Configured', status.configured ? 'yes' : 'no'],
|
|
1244
|
+
['Running', status.running ? 'yes' : 'no'],
|
|
1245
|
+
['Discoverable', discoverable ? 'yes' : 'no'],
|
|
1246
|
+
['Public discovery', status.public_enabled ? 'enabled' : 'disabled'],
|
|
1247
|
+
['network mode', mode],
|
|
1248
|
+
['connected', status.connected_to_silicaclaw ? 'yes' : 'no'],
|
|
1249
|
+
['discoverable reason', status.discoverable_reason || '-'],
|
|
1250
|
+
].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
|
|
1251
|
+
|
|
1252
|
+
document.getElementById('socialIntegrationCards').innerHTML = [
|
|
1253
|
+
['display name', status.display_name || '(unnamed)'],
|
|
1254
|
+
['agent id', shortId(status.agent_id || '')],
|
|
1255
|
+
['social.md found', summary.social_md_found ? 'yes' : 'no'],
|
|
1256
|
+
['social.md source', summary.social_md_source_path || '-'],
|
|
1257
|
+
['runtime generated', summary.runtime_generated ? 'yes' : 'no'],
|
|
1258
|
+
['reuse OpenClaw identity', summary.reused_openclaw_identity ? 'yes' : 'no'],
|
|
1259
|
+
['mode', mode],
|
|
1260
|
+
['broadcast status', summary.current_broadcast_status || '-'],
|
|
1261
|
+
].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
|
|
1262
|
+
|
|
1263
|
+
document.getElementById('socialAdvancedCards').innerHTML = [
|
|
1264
|
+
['adapter', runtimeNetwork.adapter || summary.current_adapter || '-'],
|
|
1265
|
+
['namespace', runtimeNetwork.namespace || summary.current_namespace || '-'],
|
|
1266
|
+
['room', runtimeNetwork.room || network.room || '-'],
|
|
1267
|
+
['signaling endpoints', (runtimeNetwork.signaling_urls || []).length || 0],
|
|
1268
|
+
['bootstrap sources', (runtimeNetwork.bootstrap_sources || []).length || 0],
|
|
1269
|
+
['seed peers', (runtimeNetwork.seed_peers || []).length || 0],
|
|
1270
|
+
['bootstrap hints', (runtimeNetwork.bootstrap_hints || []).length || 0],
|
|
1271
|
+
['restart required', social.network_requires_restart ? 'yes' : 'no'],
|
|
1272
|
+
].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
|
|
1273
|
+
document.getElementById('socialAdvancedWrap').textContent = toPrettyJson({
|
|
1274
|
+
runtime_network: runtimeNetwork,
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
document.getElementById('socialSourceWrap').textContent = toPrettyJson({
|
|
1278
|
+
found: social.found,
|
|
1279
|
+
source_path: social.source_path,
|
|
1280
|
+
parse_error: social.parse_error,
|
|
1281
|
+
});
|
|
1282
|
+
document.getElementById('socialRawWrap').textContent = toPrettyJson({
|
|
1283
|
+
raw_frontmatter: social.raw_frontmatter || null,
|
|
1284
|
+
});
|
|
1285
|
+
document.getElementById('socialRuntimeWrap').textContent = toPrettyJson(runtime);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
async function exportSocialTemplate() {
|
|
1289
|
+
const payload = (await api('/api/social/export-template')).data || {};
|
|
1290
|
+
socialTemplate = String(payload.content || '');
|
|
1291
|
+
document.getElementById('socialTemplateWrap').textContent = socialTemplate || '-';
|
|
1292
|
+
return payload;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function renderLogs() {
|
|
1296
|
+
const el = document.getElementById('logList');
|
|
1297
|
+
if (!logsCache.length) {
|
|
1298
|
+
el.innerHTML = '<div class="label">No logs yet.</div>';
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const filtered = logLevelFilter === 'all'
|
|
1302
|
+
? logsCache
|
|
1303
|
+
: logsCache.filter((l) => String(l.level || '').toLowerCase() === logLevelFilter);
|
|
1304
|
+
if (!filtered.length) {
|
|
1305
|
+
el.innerHTML = `<div class="label">No ${logLevelFilter} logs.</div>`;
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
el.innerHTML = filtered.map((l) => `
|
|
1309
|
+
<div class="log-item">
|
|
1310
|
+
<div class="log-${l.level}">[${String(l.level).toUpperCase()}] ${l.message}</div>
|
|
1311
|
+
<div class="mono" style="color:#90a2c3;">${new Date(l.timestamp).toLocaleString()}</div>
|
|
1312
|
+
</div>
|
|
1313
|
+
`).join('');
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
async function refreshLogs() {
|
|
1317
|
+
logsCache = (await api('/api/logs')).data || [];
|
|
1318
|
+
renderLogs();
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
async function refreshAll() {
|
|
1322
|
+
try {
|
|
1323
|
+
const tasks = [refreshOverview(), refreshNetwork(), refreshPeers(), refreshDiscovery(), refreshSocial(), refreshLogs(), refreshPublicProfilePreview()];
|
|
1324
|
+
const shouldRefreshProfile = !(activeTab === 'profile' && profileDirty);
|
|
1325
|
+
if (shouldRefreshProfile) {
|
|
1326
|
+
tasks.push(refreshProfile());
|
|
1327
|
+
}
|
|
1328
|
+
await Promise.all(tasks);
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
setFeedback('networkFeedback', e instanceof Error ? e.message : 'Unknown error', 'error');
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
document.querySelectorAll('.tab').forEach((btn) => {
|
|
1335
|
+
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
|
|
1336
|
+
});
|
|
1337
|
+
document.getElementById('themeDarkBtn').addEventListener('click', () => applyTheme('dark'));
|
|
1338
|
+
document.getElementById('themeLightBtn').addEventListener('click', () => applyTheme('light'));
|
|
1339
|
+
|
|
1340
|
+
document.getElementById('profileForm').addEventListener('submit', async (event) => {
|
|
1341
|
+
event.preventDefault();
|
|
1342
|
+
const f = event.currentTarget;
|
|
1343
|
+
const result = validateProfileForm(f);
|
|
1344
|
+
if (!result.ok) {
|
|
1345
|
+
setFeedback('profileFeedback', 'Please fix validation errors before saving.', 'warn');
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
const tags = result.tags;
|
|
1350
|
+
setFeedback('profileFeedback', 'Saving profile...');
|
|
1351
|
+
setSaveBusy(true);
|
|
1352
|
+
try {
|
|
1353
|
+
const r = await api('/api/profile', { method: 'PUT', body: JSON.stringify({
|
|
1354
|
+
display_name: field(f, 'display_name').value,
|
|
1355
|
+
bio: field(f, 'bio').value,
|
|
1356
|
+
tags,
|
|
1357
|
+
avatar_url: field(f, 'avatar_url').value,
|
|
1358
|
+
public_enabled: !!field(f, 'public_enabled').checked,
|
|
1359
|
+
})});
|
|
1360
|
+
setFeedback('profileFeedback', r.meta?.message || 'Saved.');
|
|
1361
|
+
toast('Profile saved');
|
|
1362
|
+
field(f, 'tags').value = normalizeTagsInput(field(f, 'tags').value);
|
|
1363
|
+
await refreshAll();
|
|
1364
|
+
} catch (e) {
|
|
1365
|
+
setFeedback('profileFeedback', e instanceof Error ? e.message : 'Failed', 'error');
|
|
1366
|
+
} finally {
|
|
1367
|
+
setSaveBusy(false);
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
document.getElementById('refreshProfileBtn').addEventListener('click', async () => {
|
|
1372
|
+
await refreshProfile();
|
|
1373
|
+
toast('Profile form reloaded');
|
|
1374
|
+
});
|
|
1375
|
+
const profileFormEl = document.getElementById('profileForm');
|
|
1376
|
+
['input', 'change'].forEach((evt) => {
|
|
1377
|
+
profileFormEl.addEventListener(evt, () => {
|
|
1378
|
+
renderProfilePreview(profileFormEl);
|
|
1379
|
+
validateProfileForm(profileFormEl);
|
|
1380
|
+
updateDirtyState(profileFormEl, true);
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
const tagsInputEl = field(profileFormEl, 'tags');
|
|
1384
|
+
tagsInputEl.addEventListener('keydown', (event) => {
|
|
1385
|
+
if (event.key !== 'Enter') return;
|
|
1386
|
+
event.preventDefault();
|
|
1387
|
+
tagsInputEl.value = normalizeTagsInput(tagsInputEl.value);
|
|
1388
|
+
renderProfilePreview(profileFormEl);
|
|
1389
|
+
validateProfileForm(profileFormEl);
|
|
1390
|
+
updateDirtyState(profileFormEl, true);
|
|
1391
|
+
});
|
|
1392
|
+
tagsInputEl.addEventListener('blur', () => {
|
|
1393
|
+
tagsInputEl.value = normalizeTagsInput(tagsInputEl.value);
|
|
1394
|
+
renderProfilePreview(profileFormEl);
|
|
1395
|
+
validateProfileForm(profileFormEl);
|
|
1396
|
+
updateDirtyState(profileFormEl, true);
|
|
1397
|
+
});
|
|
1398
|
+
window.addEventListener('beforeunload', (event) => {
|
|
1399
|
+
if (!profileDirty || profileSaving) return;
|
|
1400
|
+
event.preventDefault();
|
|
1401
|
+
event.returnValue = '';
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
async function runAction(path, text, level = 'info') {
|
|
1405
|
+
setFeedback('networkFeedback', text);
|
|
1406
|
+
try {
|
|
1407
|
+
const r = await api(path, { method: 'POST' });
|
|
1408
|
+
setFeedback('networkFeedback', r.meta?.message || 'Done', level);
|
|
1409
|
+
toast(r.meta?.message || 'Done');
|
|
1410
|
+
await refreshAll();
|
|
1411
|
+
} catch (e) {
|
|
1412
|
+
setFeedback('networkFeedback', e instanceof Error ? e.message : 'Failed', 'error');
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
document.getElementById('startBroadcastBtn').addEventListener('click', () => runAction('/api/broadcast/start', 'Starting...'));
|
|
1416
|
+
document.getElementById('stopBroadcastBtn').addEventListener('click', () => runAction('/api/broadcast/stop', 'Stopping...', 'warn'));
|
|
1417
|
+
document.getElementById('broadcastNowBtn').addEventListener('click', () => runAction('/api/broadcast/now', 'Broadcasting now...'));
|
|
1418
|
+
document.getElementById('refreshCacheBtn').addEventListener('click', () => runAction('/api/cache/refresh', 'Refreshing cache...'));
|
|
1419
|
+
document.getElementById('clearCacheBtn').addEventListener('click', async () => {
|
|
1420
|
+
const ok = window.confirm('Clear discovered cache now?\n\nThis removes discovered peer profiles/presence/index references and keeps your self profile.');
|
|
1421
|
+
if (!ok) return;
|
|
1422
|
+
await runAction('/api/cache/clear', 'Clearing discovered cache...', 'warn');
|
|
1423
|
+
});
|
|
1424
|
+
document.getElementById('quickGlobalPreviewBtn').addEventListener('click', async () => {
|
|
1425
|
+
const currentSignaling = window.prompt('Signaling URL (publicly reachable):', 'http://localhost:4510');
|
|
1426
|
+
if (!currentSignaling) return;
|
|
1427
|
+
const room = window.prompt('Room:', 'silicaclaw-demo') || 'silicaclaw-demo';
|
|
1428
|
+
setFeedback('networkFeedback', 'Enabling cross-network preview...');
|
|
1429
|
+
try {
|
|
1430
|
+
const result = await api('/api/network/quick-connect-global-preview', {
|
|
1431
|
+
method: 'POST',
|
|
1432
|
+
body: JSON.stringify({
|
|
1433
|
+
signaling_url: currentSignaling.trim(),
|
|
1434
|
+
room: room.trim(),
|
|
1435
|
+
}),
|
|
1436
|
+
});
|
|
1437
|
+
setFeedback('networkFeedback', result.meta?.message || 'Cross-network preview enabled');
|
|
1438
|
+
toast('Cross-network preview enabled');
|
|
1439
|
+
await refreshAll();
|
|
1440
|
+
} catch (e) {
|
|
1441
|
+
setFeedback('networkFeedback', e instanceof Error ? e.message : 'Enable cross-network preview failed', 'error');
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
document.getElementById('onlyOnlineToggle').addEventListener('change', async (event) => {
|
|
1445
|
+
onlyShowOnline = Boolean(event.target?.checked);
|
|
1446
|
+
await refreshOverview();
|
|
1447
|
+
});
|
|
1448
|
+
document.getElementById('refreshLogsBtn').addEventListener('click', async () => {
|
|
1449
|
+
await refreshLogs();
|
|
1450
|
+
toast('Logs refreshed');
|
|
1451
|
+
});
|
|
1452
|
+
document.getElementById('enablePublicDiscoveryBtn').addEventListener('click', async () => {
|
|
1453
|
+
const ok = window.confirm(
|
|
1454
|
+
'Enable public discovery now?\n\n' +
|
|
1455
|
+
'- Only profile/presence are shared.\n' +
|
|
1456
|
+
'- Private files are not shared.\n' +
|
|
1457
|
+
'- Chat and remote control are not enabled.\n\n' +
|
|
1458
|
+
'This updates runtime state and does not overwrite social.md.'
|
|
1459
|
+
);
|
|
1460
|
+
if (!ok) return;
|
|
1461
|
+
setFeedback('networkFeedback', 'Enabling public discovery...');
|
|
1462
|
+
try {
|
|
1463
|
+
const res = await api('/api/public-discovery/enable', { method: 'POST' });
|
|
1464
|
+
setFeedback('networkFeedback', res.meta?.message || 'Public discovery enabled.');
|
|
1465
|
+
toast('Public discovery enabled');
|
|
1466
|
+
await refreshAll();
|
|
1467
|
+
} catch (e) {
|
|
1468
|
+
setFeedback('networkFeedback', e instanceof Error ? e.message : 'Enable public discovery failed', 'error');
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
document.getElementById('disablePublicDiscoveryBtn').addEventListener('click', async () => {
|
|
1472
|
+
const ok = window.confirm('Disable public discovery now? This updates runtime state and does not overwrite social.md.');
|
|
1473
|
+
if (!ok) return;
|
|
1474
|
+
setFeedback('networkFeedback', 'Disabling public discovery...');
|
|
1475
|
+
try {
|
|
1476
|
+
const res = await api('/api/public-discovery/disable', { method: 'POST' });
|
|
1477
|
+
setFeedback('networkFeedback', res.meta?.message || 'Public discovery disabled.');
|
|
1478
|
+
toast('Public discovery disabled');
|
|
1479
|
+
await refreshAll();
|
|
1480
|
+
} catch (e) {
|
|
1481
|
+
setFeedback('networkFeedback', e instanceof Error ? e.message : 'Disable public discovery failed', 'error');
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
document.getElementById('socialReloadBtn').addEventListener('click', async () => {
|
|
1485
|
+
setFeedback('socialFeedback', 'Reloading social config...');
|
|
1486
|
+
try {
|
|
1487
|
+
const res = await api('/api/social/reload', { method: 'POST' });
|
|
1488
|
+
setFeedback('socialFeedback', res.meta?.message || 'Reloaded');
|
|
1489
|
+
toast('Social config reloaded');
|
|
1490
|
+
await refreshAll();
|
|
1491
|
+
} catch (e) {
|
|
1492
|
+
setFeedback('socialFeedback', e instanceof Error ? e.message : 'Reload failed', 'error');
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
document.getElementById('socialGenerateBtn').addEventListener('click', async () => {
|
|
1496
|
+
setFeedback('socialFeedback', 'Generating default social.md...');
|
|
1497
|
+
try {
|
|
1498
|
+
const res = await api('/api/social/generate-default', { method: 'POST' });
|
|
1499
|
+
setFeedback('socialFeedback', res.meta?.message || 'Done');
|
|
1500
|
+
toast(res.meta?.message || 'Default social.md ready');
|
|
1501
|
+
await refreshAll();
|
|
1502
|
+
} catch (e) {
|
|
1503
|
+
setFeedback('socialFeedback', e instanceof Error ? e.message : 'Generate failed', 'error');
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
document.getElementById('socialExportBtn').addEventListener('click', async () => {
|
|
1507
|
+
setFeedback('socialFeedback', 'Exporting template...');
|
|
1508
|
+
try {
|
|
1509
|
+
await exportSocialTemplate();
|
|
1510
|
+
setFeedback('socialFeedback', 'Template exported from current runtime.');
|
|
1511
|
+
toast('Template exported');
|
|
1512
|
+
} catch (e) {
|
|
1513
|
+
setFeedback('socialFeedback', e instanceof Error ? e.message : 'Export failed', 'error');
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
document.getElementById('socialModeApplyBtn').addEventListener('click', async () => {
|
|
1517
|
+
const mode = document.getElementById('socialModeSelect').value;
|
|
1518
|
+
setFeedback('socialFeedback', `Applying runtime mode: ${mode}...`);
|
|
1519
|
+
try {
|
|
1520
|
+
const res = await api('/api/social/runtime-mode', {
|
|
1521
|
+
method: 'POST',
|
|
1522
|
+
body: JSON.stringify({ mode }),
|
|
1523
|
+
});
|
|
1524
|
+
setFeedback('socialFeedback', res.meta?.message || 'Runtime mode updated.');
|
|
1525
|
+
toast(`Runtime mode: ${mode}`);
|
|
1526
|
+
await refreshAll();
|
|
1527
|
+
} catch (e) {
|
|
1528
|
+
setFeedback('socialFeedback', e instanceof Error ? e.message : 'Runtime mode update failed', 'error');
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
document.getElementById('socialCopyBtn').addEventListener('click', async () => {
|
|
1532
|
+
setFeedback('socialFeedback', 'Copying template...');
|
|
1533
|
+
const btn = document.getElementById('socialCopyBtn');
|
|
1534
|
+
try {
|
|
1535
|
+
if (!socialTemplate) {
|
|
1536
|
+
await exportSocialTemplate();
|
|
1537
|
+
}
|
|
1538
|
+
await navigator.clipboard.writeText(socialTemplate);
|
|
1539
|
+
setFeedback('socialFeedback', 'Template copied to clipboard.');
|
|
1540
|
+
toast('Copied');
|
|
1541
|
+
flashButton(btn, 'Copied');
|
|
1542
|
+
} catch (e) {
|
|
1543
|
+
setFeedback('socialFeedback', e instanceof Error ? e.message : 'Copy failed', 'error');
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
document.getElementById('socialDownloadBtn').addEventListener('click', async () => {
|
|
1547
|
+
setFeedback('socialFeedback', 'Preparing download...');
|
|
1548
|
+
try {
|
|
1549
|
+
const payload = await exportSocialTemplate();
|
|
1550
|
+
const filename = String(payload.filename || 'social.md');
|
|
1551
|
+
const blob = new Blob([socialTemplate], { type: 'text/markdown;charset=utf-8' });
|
|
1552
|
+
const url = URL.createObjectURL(blob);
|
|
1553
|
+
const anchor = document.createElement('a');
|
|
1554
|
+
anchor.href = url;
|
|
1555
|
+
anchor.download = filename;
|
|
1556
|
+
document.body.appendChild(anchor);
|
|
1557
|
+
anchor.click();
|
|
1558
|
+
anchor.remove();
|
|
1559
|
+
URL.revokeObjectURL(url);
|
|
1560
|
+
setFeedback('socialFeedback', `Downloaded ${filename}.`);
|
|
1561
|
+
toast('Downloaded');
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
setFeedback('socialFeedback', e instanceof Error ? e.message : 'Download failed', 'error');
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
document.getElementById('copyPublicProfilePreviewBtn').addEventListener('click', async () => {
|
|
1567
|
+
const btn = document.getElementById('copyPublicProfilePreviewBtn');
|
|
1568
|
+
try {
|
|
1569
|
+
const summary = (await api('/api/public-profile/preview')).data || null;
|
|
1570
|
+
await navigator.clipboard.writeText(toPrettyJson(summary));
|
|
1571
|
+
toast('Public profile preview copied');
|
|
1572
|
+
flashButton(btn, 'Copied');
|
|
1573
|
+
} catch (e) {
|
|
1574
|
+
setFeedback('profileFeedback', e instanceof Error ? e.message : 'Copy preview failed', 'error');
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
document.getElementById('logLevelFilter').addEventListener('change', (event) => {
|
|
1578
|
+
logLevelFilter = String(event.target?.value || 'all');
|
|
1579
|
+
renderLogs();
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
(() => {
|
|
1583
|
+
const logo = document.getElementById('brandLogo');
|
|
1584
|
+
const fallback = document.getElementById('brandFallback');
|
|
1585
|
+
if (!logo || !fallback) return;
|
|
1586
|
+
logo.addEventListener('error', () => {
|
|
1587
|
+
logo.style.display = 'none';
|
|
1588
|
+
fallback.classList.remove('hidden');
|
|
1589
|
+
});
|
|
1590
|
+
logo.addEventListener('load', () => {
|
|
1591
|
+
logo.style.display = 'block';
|
|
1592
|
+
fallback.classList.add('hidden');
|
|
1593
|
+
});
|
|
1594
|
+
})();
|
|
1595
|
+
|
|
1596
|
+
applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
|
|
1597
|
+
refreshAll();
|
|
1598
|
+
exportSocialTemplate().catch(() => {});
|
|
1599
|
+
setInterval(refreshAll, 4000);
|
|
1600
|
+
</script>
|
|
1601
|
+
</body>
|
|
1602
|
+
</html>
|