hyperclaw 5.3.4 → 5.4.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/dist/banner-3jmKGq6t.js +143 -0
- package/dist/banner-B2Qo91lq.js +143 -0
- package/dist/banner-BhOid_rp.js +7 -0
- package/dist/banner-C5D09Afq.js +7 -0
- package/dist/banner-CmNA9NKE.js +7 -0
- package/dist/banner-D3-Ik5d2.js +143 -0
- package/dist/banner-D3X8tQ-0.js +7 -0
- package/dist/banner-DG3v43xA.js +143 -0
- package/dist/chat-BGD9AWhr.js +523 -0
- package/dist/chat-D-i8bqTf.js +523 -0
- package/dist/chat-D8wFNN4z.js +523 -0
- package/dist/daemon-BXfbyPBf.js +7 -0
- package/dist/daemon-C4j0wqxz.js +421 -0
- package/dist/daemon-CUNaq2Ls.js +421 -0
- package/dist/daemon-D-klsy3i.js +7 -0
- package/dist/daemon-D_bYfHgn.js +421 -0
- package/dist/daemon-DcE6vcjH.js +421 -0
- package/dist/daemon-IU5UIMJo.js +7 -0
- package/dist/daemon-NFwpuo3L.js +7 -0
- package/dist/engine-2Tc-z0e4.js +7 -0
- package/dist/engine-B7Q4Vx-C.js +7 -0
- package/dist/engine-C-G3G-U7.js +7 -0
- package/dist/engine-DQ3hT5JD.js +327 -0
- package/dist/engine-DRpy9Y5O.js +327 -0
- package/dist/engine-DetHi9LP.js +327 -0
- package/dist/hyperclawbot-BqyZr2GM.js +516 -0
- package/dist/hyperclawbot-DTzP20Up.js +516 -0
- package/dist/hyperclawbot-DcQt62PL.js +516 -0
- package/dist/mcp-loader-BAGRKfJz.js +93 -0
- package/dist/mcp-loader-CPlsbcja.js +93 -0
- package/dist/mcp-loader-Dt64EewT.js +93 -0
- package/dist/onboard-32d2Gsuy.js +14 -0
- package/dist/onboard-BF29mNbC.js +14 -0
- package/dist/onboard-C7vJegQc.js +3812 -0
- package/dist/onboard-CJKxKPBH.js +3812 -0
- package/dist/onboard-Cgj_CIrq.js +3812 -0
- package/dist/onboard-ChnsqU5z.js +14 -0
- package/dist/onboard-CjzqTYq3.js +3812 -0
- package/dist/onboard-CytL65jh.js +14 -0
- package/dist/orchestrator-Bcj7uc1F.js +189 -0
- package/dist/orchestrator-C7oLWNFW.js +6 -0
- package/dist/orchestrator-Cuyqncql.js +189 -0
- package/dist/orchestrator-CxlEm5li.js +6 -0
- package/dist/orchestrator-DcEHTInR.js +6 -0
- package/dist/orchestrator-PQY07fH7.js +189 -0
- package/dist/osint-ADgw_Gkd.js +283 -0
- package/dist/osint-B5j3IuJK.js +283 -0
- package/dist/osint-DsUwGzgP.js +283 -0
- package/dist/osint-DtRd_A_O.js +283 -0
- package/dist/osint-chat-BbH6eW8z.js +789 -0
- package/dist/osint-chat-CfexqWop.js +789 -0
- package/dist/osint-chat-DdclrVa6.js +789 -0
- package/dist/osint-chat-xRP672b-.js +789 -0
- package/dist/run-main.js +103 -44
- package/dist/server-6TIyu0mb.js +4 -0
- package/dist/server-C9f6qtEW.js +1366 -0
- package/dist/server-CdlilrcS.js +4 -0
- package/dist/server-Da_jSrfS.js +4 -0
- package/dist/server-DfrmKeE6.js +1356 -0
- package/dist/server-gai_RW1u.js +1356 -0
- package/dist/server-rHap51o6.js +4 -0
- package/dist/server-vbYKep6r.js +1356 -0
- package/dist/skill-runtime-249xckq3.js +5 -0
- package/dist/skill-runtime-Bv59FUVu.js +104 -0
- package/dist/skill-runtime-CHQT7c1k.js +5 -0
- package/dist/skill-runtime-Cfd0OLkP.js +104 -0
- package/dist/skill-runtime-Dr0fimDf.js +5 -0
- package/dist/skill-runtime-DtO4i9vK.js +104 -0
- package/dist/src-CDLnbsLp.js +458 -0
- package/dist/src-ClYgvXAj.js +63 -0
- package/dist/src-D4VRanxB.js +458 -0
- package/dist/src-DK-ayUx7.js +63 -0
- package/dist/src-DiYkDVnn.js +458 -0
- package/dist/src-H1I7JC2E.js +63 -0
- package/dist/sub-agent-tools-8-Imp1B5.js +39 -0
- package/dist/sub-agent-tools-BoMCZ6jz.js +39 -0
- package/dist/sub-agent-tools-Dla3ZB5N.js +39 -0
- package/package.json +145 -143
- package/scripts/fix-mojibake.mjs +166 -0
- package/static/chat.html +764 -764
- package/static/web/assets/index-B5N1LHGR.css +1 -0
- package/static/web/assets/{index-BhCDgT5S.js → index-Bf8pf7Wg.js} +1 -1
- package/static/web/index.html +2 -2
- package/static/web/assets/index-D2RfO0dG.css +0 -1
package/static/chat.html
CHANGED
|
@@ -1,764 +1,764 @@
|
|
|
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>HyperClaw Chat</title>
|
|
7
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
9
|
-
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
-
<style>
|
|
11
|
-
/* ── ACCENT THEME VARS ── */
|
|
12
|
-
:root {
|
|
13
|
-
--accent: #22d3ee;
|
|
14
|
-
--accent2: #06b6d4;
|
|
15
|
-
--accent-glow: rgba(34,211,238,0.3);
|
|
16
|
-
--accent-bg: rgba(34,211,238,0.08);
|
|
17
|
-
}
|
|
18
|
-
[data-theme="light"] {
|
|
19
|
-
--accent: #0891b2;
|
|
20
|
-
--accent2: #0e7490;
|
|
21
|
-
--accent-glow: rgba(8,145,178,0.25);
|
|
22
|
-
--accent-bg: rgba(8,145,178,0.06);
|
|
23
|
-
}
|
|
24
|
-
[data-daemon="true"] {
|
|
25
|
-
--accent: #f87171;
|
|
26
|
-
--accent2: #ef4444;
|
|
27
|
-
--accent-glow: rgba(248,113,113,0.3);
|
|
28
|
-
--accent-bg: rgba(248,113,113,0.08);
|
|
29
|
-
}
|
|
30
|
-
[data-theme="light"][data-daemon="true"] {
|
|
31
|
-
--accent: #dc2626;
|
|
32
|
-
--accent2: #b91c1c;
|
|
33
|
-
--accent-glow: rgba(220,38,38,0.25);
|
|
34
|
-
--accent-bg: rgba(220,38,38,0.06);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
38
|
-
body { background: #0a0a0a; color: #e5e5e5; font-family: 'Inter', system-ui, sans-serif; height: 100vh; display: flex; overflow: hidden; }
|
|
39
|
-
|
|
40
|
-
/* ── SCROLLBAR ── */
|
|
41
|
-
::-webkit-scrollbar { width: 4px; }
|
|
42
|
-
::-webkit-scrollbar-track { background: transparent; }
|
|
43
|
-
::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 2px; }
|
|
44
|
-
|
|
45
|
-
/* ── SIDEBAR ── */
|
|
46
|
-
#sidebar { width: 240px; flex-shrink: 0; background: #111111; border-right: 1px solid #1e1e1e; display: flex; flex-direction: column; height: 100vh; overflow: hidden; transition: width 0.2s; }
|
|
47
|
-
#sidebar.collapsed { width: 0; border: none; }
|
|
48
|
-
|
|
49
|
-
.sidebar-top { padding: 14px 12px 8px; flex-shrink: 0; }
|
|
50
|
-
.sidebar-logo { display: flex; align-items: center; gap: 9px; padding: 6px 8px; margin-bottom: 6px; }
|
|
51
|
-
.sidebar-logo img { width: 26px; height: 26px; border-radius: 6px; }
|
|
52
|
-
.sidebar-logo-text { font-size: 13px; font-weight: 600; color: #e5e5e5; }
|
|
53
|
-
|
|
54
|
-
.sidebar-btn { width: 100%; display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 8px; border: none; background: transparent; color: #a3a3a3; font-size: 13px; cursor: pointer; transition: background .15s, color .15s; text-align: left; white-space: nowrap; overflow: hidden; }
|
|
55
|
-
.sidebar-btn:hover { background: #1e1e1e; color: #e5e5e5; }
|
|
56
|
-
.sidebar-btn.active { background: #1e1e1e; color: #e5e5e5; }
|
|
57
|
-
.sidebar-btn svg { flex-shrink: 0; }
|
|
58
|
-
|
|
59
|
-
.sidebar-new-chat { width: calc(100% - 24px); margin: 0 12px 4px; display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 8px; border: 1px solid #2a2a2a; background: transparent; color: #e5e5e5; font-size: 13px; cursor: pointer; transition: background .15s, border-color .15s; font-family: inherit; }
|
|
60
|
-
.sidebar-new-chat:hover { background: #1e1e1e; border-color: #3a3a3a; }
|
|
61
|
-
|
|
62
|
-
.sidebar-search { margin: 4px 12px; position: relative; }
|
|
63
|
-
.sidebar-search input { width: 100%; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; padding: 7px 10px 7px 32px; font-size: 12px; color: #e5e5e5; outline: none; font-family: inherit; transition: border-color .15s; }
|
|
64
|
-
.sidebar-search input:focus { border-color: #3a3a3a; }
|
|
65
|
-
.sidebar-search input::placeholder { color: #555; }
|
|
66
|
-
.sidebar-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: #555; pointer-events: none; }
|
|
67
|
-
|
|
68
|
-
.sidebar-section { padding: 16px 12px 4px; font-size: 11px; font-weight: 500; color: #555; letter-spacing: 0.5px; text-transform: uppercase; }
|
|
69
|
-
|
|
70
|
-
.sidebar-project { width: calc(100% - 24px); margin: 0 12px 2px; display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px; border: none; background: transparent; color: #a3a3a3; font-size: 12px; cursor: pointer; transition: background .15s, color .15s; text-align: left; font-family: inherit; overflow: hidden; }
|
|
71
|
-
.sidebar-project:hover { background: #1e1e1e; color: #e5e5e5; }
|
|
72
|
-
.sidebar-project.active { background: #1e1e1e; color: #e5e5e5; }
|
|
73
|
-
.sidebar-project-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
|
|
74
|
-
|
|
75
|
-
.sidebar-chat-item { width: calc(100% - 24px); margin: 0 12px 1px; display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 8px; border: none; background: transparent; color: #777; font-size: 12px; cursor: pointer; transition: background .15s, color .15s; text-align: left; font-family: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
76
|
-
.sidebar-chat-item:hover { background: #1a1a1a; color: #ccc; }
|
|
77
|
-
|
|
78
|
-
.sidebar-history { flex: 1; overflow-y: auto; padding-bottom: 8px; }
|
|
79
|
-
|
|
80
|
-
.sidebar-bottom { padding: 8px 12px 12px; border-top: 1px solid #1e1e1e; flex-shrink: 0; }
|
|
81
|
-
|
|
82
|
-
/* ── MAIN ── */
|
|
83
|
-
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; height: 100vh; background: #0a0a0a; }
|
|
84
|
-
|
|
85
|
-
/* ── HEADER ── */
|
|
86
|
-
.chat-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid #1e1e1e; background: #0a0a0a; flex-shrink: 0; gap: 8px; }
|
|
87
|
-
.chat-header-left { display: flex; align-items: center; gap: 8px; }
|
|
88
|
-
.sidebar-toggle { background: transparent; border: none; color: #555; cursor: pointer; padding: 4px; border-radius: 6px; display: flex; transition: color .15s, background .15s; }
|
|
89
|
-
.sidebar-toggle:hover { color: #ccc; background: #1e1e1e; }
|
|
90
|
-
.chat-header-right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
|
|
91
|
-
.header-status { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #555; }
|
|
92
|
-
.dot { width: 6px; height: 6px; border-radius: 50%; background: #ef4444; flex-shrink: 0; }
|
|
93
|
-
.dot.connected { background: #22c55e; box-shadow: 0 0 5px #22c55e; animation: pulse-g 2s infinite; }
|
|
94
|
-
@keyframes pulse-g { 0%,100%{opacity:1} 50%{opacity:.6} }
|
|
95
|
-
.hdr-btn { background: transparent; border: 1px solid #2a2a2a; color: #777; padding: 5px 10px; border-radius: 7px; font-size: 11px; cursor: pointer; font-family: inherit; transition: background .15s, color .15s, border-color .15s; white-space: nowrap; }
|
|
96
|
-
.hdr-btn:hover { background: #1e1e1e; color: #ccc; border-color: #3a3a3a; }
|
|
97
|
-
.hdr-btn.active { background: #2a1a1a; border-color: #5a2a2a; color: #f87171; }
|
|
98
|
-
.hdr-select { background: #111; border: 1px solid #2a2a2a; color: #ccc; padding: 5px 8px; border-radius: 7px; font-size: 11px; cursor: pointer; font-family: inherit; outline: none; }
|
|
99
|
-
|
|
100
|
-
/* ── MESSAGES ── */
|
|
101
|
-
#messages { flex: 1; overflow-y: auto; padding: 24px 20px; display: flex; flex-direction: column; gap: 16px; scroll-behavior: smooth; min-height: 0; }
|
|
102
|
-
.msg-row { display: flex; gap: 10px; max-width: 720px; animation: fadeIn .2s ease; }
|
|
103
|
-
@keyframes fadeIn { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:none} }
|
|
104
|
-
.msg-row.user { flex-direction: row-reverse; align-self: flex-end; }
|
|
105
|
-
.msg-row.assistant { align-self: flex-start; }
|
|
106
|
-
.msg-avatar { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; background: #1a1a1a; border: 1px solid #2a2a2a; margin-top: 2px; }
|
|
107
|
-
.msg-bubble { max-width: min(65vw, 560px); padding: 11px 15px; border-radius: 16px; font-size: 13.5px; line-height: 1.65; }
|
|
108
|
-
.msg-row.user .msg-bubble { background: #c0392b; color: #fff; border-bottom-right-radius: 4px; }
|
|
109
|
-
.msg-row.assistant .msg-bubble { background: #141414; border: 1px solid #222; border-bottom-left-radius: 4px; color: #ddd; }
|
|
110
|
-
.msg-bubble .prose h1,.msg-bubble .prose h2,.msg-bubble .prose h3 { color: #f87171; margin: 8px 0 4px; font-weight: 600; }
|
|
111
|
-
.msg-bubble .prose p { margin: 4px 0; }
|
|
112
|
-
.msg-bubble .prose pre { background: #0d0d0d; border: 1px solid #222; padding: 10px 12px; border-radius: 8px; overflow-x: auto; margin: 8px 0; font-size: 12px; }
|
|
113
|
-
.msg-bubble .prose code { background: #0d0d0d; padding: 1px 5px; border-radius: 4px; font-size: 12px; font-family: 'JetBrains Mono', monospace; color: #f87171; }
|
|
114
|
-
.msg-bubble .prose pre code { background: none; padding: 0; color: #ccc; }
|
|
115
|
-
.msg-bubble .prose ul,.msg-bubble .prose ol { padding-left: 18px; margin: 4px 0; }
|
|
116
|
-
.msg-bubble .prose a { color: #60a5fa; text-decoration: underline; }
|
|
117
|
-
.msg-time { font-size: 10px; color: #444; margin-top: 4px; }
|
|
118
|
-
.msg-row.user .msg-time { text-align: right; }
|
|
119
|
-
|
|
120
|
-
/* ── TYPING ── */
|
|
121
|
-
.typing-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: #f87171; animation: blink 1.2s infinite; }
|
|
122
|
-
.typing-dot:nth-child(2){animation-delay:.18s}.typing-dot:nth-child(3){animation-delay:.36s}
|
|
123
|
-
@keyframes blink{0%,80%,100%{opacity:.2}40%{opacity:1}}
|
|
124
|
-
|
|
125
|
-
/* ── BANNER ── */
|
|
126
|
-
.hc-banner { display: flex; flex-direction: column; align-items: center; padding: 24px 0 16px; flex-shrink: 0; pointer-events: none; user-select: none; opacity: 0.45; }
|
|
127
|
-
.hc-banner-text { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 2.4rem; letter-spacing: 0.3em; color: var(--accent); text-shadow: 0 0 32px var(--accent), 0 0 64px var(--accent-bg); line-height: 1; }
|
|
128
|
-
.hc-banner-sub { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--accent); letter-spacing: 0.25em; margin-top: 8px; opacity: 0.75; }
|
|
129
|
-
|
|
130
|
-
/* ── WELCOME ── */
|
|
131
|
-
.welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; gap: 14px; text-align: center; padding: 40px; }
|
|
132
|
-
.welcome-eagle { font-size: 48px; margin-bottom: 4px; filter: drop-shadow(0 0 12px rgba(239,68,68,0.3)); }
|
|
133
|
-
.welcome-title { font-size: 26px; font-weight: 600; color: #e5e5e5; }
|
|
134
|
-
.welcome-sub { font-size: 13px; color: #666; max-width: 360px; line-height: 1.6; }
|
|
135
|
-
.welcome-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 4px; }
|
|
136
|
-
.chip { background: #141414; border: 1px solid #2a2a2a; color: #888; padding: 7px 14px; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all .2s; font-family: inherit; }
|
|
137
|
-
.chip:hover { border-color: #555; color: #ccc; background: #1e1e1e; }
|
|
138
|
-
|
|
139
|
-
/* ── INPUT ── */
|
|
140
|
-
.input-wrap { padding: 12px 16px 14px; border-top: 1px solid #1a1a1a; background: #0a0a0a; flex-shrink: 0; }
|
|
141
|
-
.input-row { display: flex; gap: 8px; align-items: flex-end; max-width: 720px; margin: 0 auto; }
|
|
142
|
-
#input { flex: 1; background: #141414; border: 1px solid #2a2a2a; color: #e5e5e5; border-radius: 12px; padding: 11px 14px; font-size: 13.5px; font-family: 'Inter', sans-serif; resize: none; min-height: 44px; max-height: 130px; outline: none; transition: border-color .2s; line-height: 1.5; }
|
|
143
|
-
#input:focus { border-color: #3a3a3a; }
|
|
144
|
-
#input::placeholder { color: #444; }
|
|
145
|
-
#send-btn { width: 40px; height: 40px; border-radius: 10px; background: #c0392b; border: none; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all .2s; flex-shrink: 0; }
|
|
146
|
-
#send-btn:hover:not(:disabled) { background: #e74c3c; }
|
|
147
|
-
#send-btn:disabled { opacity: 0.3; cursor: default; }
|
|
148
|
-
.input-hint { font-size: 11px; color: #333; text-align: center; margin-top: 6px; max-width: 720px; margin-left: auto; margin-right: auto; }
|
|
149
|
-
|
|
150
|
-
/* ── TERMINAL PANEL ── */
|
|
151
|
-
#terminal-panel { border-top: 1px solid #1e1e1e; background: #0d0d0d; flex-shrink: 0; height: 220px; display: none; flex-direction: column; }
|
|
152
|
-
#terminal-panel.open { display: flex; }
|
|
153
|
-
.term-titlebar { display: flex; align-items: center; justify-content: space-between; padding: 6px 14px; background: #141414; border-bottom: 1px solid #1e1e1e; flex-shrink: 0; }
|
|
154
|
-
.term-dots { display: flex; gap: 5px; }
|
|
155
|
-
.term-dot { width: 11px; height: 11px; border-radius: 50%; }
|
|
156
|
-
.term-info { font-size: 11px; color: #444; font-family: 'JetBrains Mono', monospace; flex: 1; text-align: center; }
|
|
157
|
-
.term-titlebar-right { display: flex; align-items: center; gap: 10px; }
|
|
158
|
-
.term-clear-btn { font-size: 11px; color: #444; background: none; border: none; cursor: pointer; font-family: inherit; transition: color .15s; }
|
|
159
|
-
.term-clear-btn:hover { color: #888; }
|
|
160
|
-
.term-close-btn { background: none; border: none; color: #444; cursor: pointer; display: flex; transition: color .15s; }
|
|
161
|
-
.term-close-btn:hover { color: #ccc; }
|
|
162
|
-
.term-quick { display: flex; gap: 6px; padding: 6px 12px; border-bottom: 1px solid #191919; flex-shrink: 0; overflow-x: auto; }
|
|
163
|
-
.term-quick::-webkit-scrollbar { height: 0; }
|
|
164
|
-
.term-quick-btn { padding: 4px 10px; border-radius: 6px; background: #1a1a1a; border: 1px solid #2a2a2a; color: #777; font-size: 11px; cursor: pointer; font-family: 'JetBrains Mono', monospace; transition: all .15s; white-space: nowrap; flex-shrink: 0; }
|
|
165
|
-
.term-quick-btn:hover:not(:disabled) { background: #222; color: #ccc; border-color: #3a3a3a; }
|
|
166
|
-
.term-quick-btn:disabled { opacity: 0.4; cursor: default; }
|
|
167
|
-
#terminal-log { flex: 1; overflow-y: auto; padding: 8px 14px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #888; white-space: pre-wrap; line-height: 1.5; min-height: 0; }
|
|
168
|
-
.term-log-cmd { color: #67e8f9; }
|
|
169
|
-
.term-log-err { color: #f87171; }
|
|
170
|
-
.term-input-row { display: flex; align-items: center; gap: 8px; padding: 6px 12px 8px; border-top: 1px solid #1a1a1a; background: #141414; flex-shrink: 0; }
|
|
171
|
-
.term-prompt { color: #67e8f9; font-family: 'JetBrains Mono', monospace; font-size: 12px; flex-shrink: 0; }
|
|
172
|
-
#terminal-input { flex: 1; background: transparent; border: none; color: #e5e5e5; font-family: 'JetBrains Mono', monospace; font-size: 12px; outline: none; }
|
|
173
|
-
#terminal-input::placeholder { color: #333; }
|
|
174
|
-
.term-spinner { width: 12px; height: 12px; border: 1.5px solid #d97706; border-top-color: transparent; border-radius: 50%; animation: spin .7s linear infinite; flex-shrink: 0; display: none; }
|
|
175
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
176
|
-
|
|
177
|
-
/* ── CUSTOMIZE MODAL ── */
|
|
178
|
-
#customize-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 999; align-items: center; justify-content: center; backdrop-filter: blur(4px); }
|
|
179
|
-
#customize-modal.open { display: flex; }
|
|
180
|
-
.modal-box { background: #141414; border: 1px solid #2a2a2a; border-radius: 14px; padding: 24px; width: 400px; max-width: 90vw; }
|
|
181
|
-
.modal-title { font-size: 15px; font-weight: 600; color: #e5e5e5; margin-bottom: 16px; }
|
|
182
|
-
.modal-label { font-size: 12px; color: #777; margin-bottom: 4px; display: block; }
|
|
183
|
-
.modal-input { width: 100%; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; padding: 8px 12px; color: #e5e5e5; font-size: 13px; font-family: inherit; outline: none; margin-bottom: 12px; transition: border-color .15s; }
|
|
184
|
-
.modal-input:focus { border-color: #3a3a3a; }
|
|
185
|
-
.modal-textarea { min-height: 80px; resize: vertical; }
|
|
186
|
-
.modal-row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }
|
|
187
|
-
.modal-btn { padding: 7px 16px; border-radius: 8px; font-size: 13px; cursor: pointer; font-family: inherit; border: 1px solid #2a2a2a; transition: all .15s; }
|
|
188
|
-
.modal-btn-cancel { background: transparent; color: #777; }
|
|
189
|
-
.modal-btn-cancel:hover { background: #1e1e1e; color: #ccc; }
|
|
190
|
-
.modal-btn-save { background: #c0392b; border-color: #c0392b; color: #fff; }
|
|
191
|
-
.modal-btn-save:hover { background: #e74c3c; border-color: #e74c3c; }
|
|
192
|
-
</style>
|
|
193
|
-
</head>
|
|
194
|
-
<body>
|
|
195
|
-
|
|
196
|
-
<!-- ── SIDEBAR ── -->
|
|
197
|
-
<aside id="sidebar">
|
|
198
|
-
<div class="sidebar-top">
|
|
199
|
-
<!-- Logo -->
|
|
200
|
-
<div class="sidebar-logo">
|
|
201
|
-
<span style="font-size:22px">🦅</span>
|
|
202
|
-
<span class="sidebar-logo-text">HyperClaw</span>
|
|
203
|
-
</div>
|
|
204
|
-
|
|
205
|
-
<!-- New chat -->
|
|
206
|
-
<button class="sidebar-new-chat" id="new-chat-btn">
|
|
207
|
-
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1v12M1 7h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
|
208
|
-
New chat
|
|
209
|
-
</button>
|
|
210
|
-
|
|
211
|
-
<!-- Search -->
|
|
212
|
-
<div class="sidebar-search" style="margin-top:6px">
|
|
213
|
-
<svg class="sidebar-search-icon" width="13" height="13" viewBox="0 0 13 13" fill="none"><circle cx="5.5" cy="5.5" r="4" stroke="currentColor" stroke-width="1.3"/><path d="M9 9l3 3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
|
214
|
-
<input type="text" id="search-input" placeholder="Search chats…" autocomplete="off">
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
<!-- Customize -->
|
|
219
|
-
<button class="sidebar-btn" id="customize-btn" style="margin: 2px 12px; width: calc(100% - 24px)">
|
|
220
|
-
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="2.5" stroke="currentColor" stroke-width="1.3"/><path d="M7 1v1.5M7 11.5V13M1 7h1.5M11.5 7H13M2.5 2.5l1.1 1.1M10.4 10.4l1.1 1.1M2.5 11.5l1.1-1.1M10.4 3.6l1.1-1.1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
|
221
|
-
Customize
|
|
222
|
-
</button>
|
|
223
|
-
|
|
224
|
-
<!-- Projects section -->
|
|
225
|
-
<div class="sidebar-section">Projects</div>
|
|
226
|
-
<div id="projects-list">
|
|
227
|
-
<button class="sidebar-project active" data-preset="" id="proj-general">
|
|
228
|
-
<span class="sidebar-project-dot" style="background:#6b7280"></span>
|
|
229
|
-
General
|
|
230
|
-
</button>
|
|
231
|
-
<button class="sidebar-project" data-preset="ethical-hacker" id="proj-hacker">
|
|
232
|
-
<span class="sidebar-project-dot" style="background:#ef4444"></span>
|
|
233
|
-
Ethical Hacker
|
|
234
|
-
</button>
|
|
235
|
-
<button class="sidebar-project" data-preset="hyperclaw" id="proj-hcdev">
|
|
236
|
-
<span class="sidebar-project-dot" style="background:#f97316"></span>
|
|
237
|
-
HyperClaw Dev
|
|
238
|
-
</button>
|
|
239
|
-
<button class="sidebar-project" data-preset="osint" id="proj-osint">
|
|
240
|
-
<span class="sidebar-project-dot" style="background:#a855f7"></span>
|
|
241
|
-
OSINT
|
|
242
|
-
</button>
|
|
243
|
-
<button class="sidebar-project" id="new-project-btn" style="color:#555">
|
|
244
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
|
245
|
-
New project…
|
|
246
|
-
</button>
|
|
247
|
-
</div>
|
|
248
|
-
|
|
249
|
-
<!-- Chats history -->
|
|
250
|
-
<div class="sidebar-section">Chats</div>
|
|
251
|
-
<div class="sidebar-history" id="chat-history">
|
|
252
|
-
<div style="padding: 6px 22px; font-size:11px; color:#3a3a3a">No recent chats</div>
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
<!-- Bottom: status -->
|
|
256
|
-
<div class="sidebar-bottom">
|
|
257
|
-
<div style="display:flex;align-items:center;gap:6px">
|
|
258
|
-
<span class="dot" id="ws-dot-side"></span>
|
|
259
|
-
<span id="ws-label-side" style="font-size:11px;color:#444">Offline</span>
|
|
260
|
-
</div>
|
|
261
|
-
<div id="model-label" style="font-size:11px;color:#333;margin-top:2px;font-family:'JetBrains Mono',monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></div>
|
|
262
|
-
</div>
|
|
263
|
-
</aside>
|
|
264
|
-
|
|
265
|
-
<!-- ── MAIN ── -->
|
|
266
|
-
<div id="main">
|
|
267
|
-
|
|
268
|
-
<!-- Header -->
|
|
269
|
-
<header class="chat-header">
|
|
270
|
-
<div class="chat-header-left">
|
|
271
|
-
<button class="sidebar-toggle" id="sidebar-toggle" title="Toggle sidebar">
|
|
272
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="4" width="12" height="1.3" rx=".65" fill="currentColor"/><rect x="2" y="7.35" width="12" height="1.3" rx=".65" fill="currentColor"/><rect x="2" y="10.7" width="12" height="1.3" rx=".65" fill="currentColor"/></svg>
|
|
273
|
-
</button>
|
|
274
|
-
<span style="font-size:13px;color:#555;font-weight:500" id="active-project-label">General</span>
|
|
275
|
-
</div>
|
|
276
|
-
<div class="chat-header-right">
|
|
277
|
-
<div class="header-status">
|
|
278
|
-
<span class="dot" id="ws-dot"></span>
|
|
279
|
-
<span id="ws-label" style="font-size:12px;color:#444">Offline</span>
|
|
280
|
-
</div>
|
|
281
|
-
<button class="hdr-btn" id="clear-btn">Clear</button>
|
|
282
|
-
<button class="hdr-btn" id="terminal-btn">
|
|
283
|
-
<span style="display:inline-flex;align-items:center;gap:5px">
|
|
284
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M1.5 2.5L5 6L1.5 9.5M6.5 9.5H10.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
285
|
-
Terminal
|
|
286
|
-
</span>
|
|
287
|
-
</button>
|
|
288
|
-
</div>
|
|
289
|
-
</header>
|
|
290
|
-
|
|
291
|
-
<!-- Messages -->
|
|
292
|
-
<main id="messages">
|
|
293
|
-
<!-- Banner -->
|
|
294
|
-
<div class="hc-banner">
|
|
295
|
-
<div class="hc-banner-text">HYPERCLAW</div>
|
|
296
|
-
<div class="hc-banner-sub" id="banner-sub">HyperClaw Bot · AI Gateway v5.3.
|
|
297
|
-
</div>
|
|
298
|
-
<div class="welcome" id="welcome">
|
|
299
|
-
<div class="welcome-eagle">🦅</div>
|
|
300
|
-
<div class="welcome-title">What can I help with?</div>
|
|
301
|
-
<p class="welcome-sub">Your personal AI agent — running on your hardware. Pick a suggestion or type anything.</p>
|
|
302
|
-
<div class="welcome-chips">
|
|
303
|
-
<button class="chip" onclick="fillInput(this)">What can you do?</button>
|
|
304
|
-
<button class="chip" onclick="fillInput(this)">Search the web for AI news</button>
|
|
305
|
-
<button class="chip" onclick="fillInput(this)">Help me write a script</button>
|
|
306
|
-
<button class="chip" onclick="fillInput(this)">Show system status</button>
|
|
307
|
-
<button class="chip" onclick="fillInput(this)">Run a security audit</button>
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
</main>
|
|
311
|
-
|
|
312
|
-
<!-- Input -->
|
|
313
|
-
<div class="input-wrap">
|
|
314
|
-
<form class="input-row" id="form">
|
|
315
|
-
<textarea id="input" rows="1" placeholder="Ask HyperClaw anything… Enter to send, Shift+Enter for new line" autocomplete="off"></textarea>
|
|
316
|
-
<button type="submit" id="send-btn">
|
|
317
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 12V4M4 8l4-4 4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
318
|
-
</button>
|
|
319
|
-
</form>
|
|
320
|
-
<div class="input-hint">🦅 HyperClaw · <span id="model-hint">—</span> · Shift+Enter for new line</div>
|
|
321
|
-
</div>
|
|
322
|
-
|
|
323
|
-
<!-- Terminal panel -->
|
|
324
|
-
<div id="terminal-panel">
|
|
325
|
-
<div class="term-titlebar">
|
|
326
|
-
<div class="term-dots">
|
|
327
|
-
<div class="term-dot" style="background:#ff5f57"></div>
|
|
328
|
-
<div class="term-dot" style="background:#febc2e"></div>
|
|
329
|
-
<div class="term-dot" style="background:#28c840"></div>
|
|
330
|
-
</div>
|
|
331
|
-
<span class="term-info" id="term-prompt-info">Terminal — HyperClaw gateway</span>
|
|
332
|
-
<div class="term-titlebar-right">
|
|
333
|
-
<button class="term-clear-btn" id="term-clear-btn">clear</button>
|
|
334
|
-
<button class="term-close-btn" id="term-close-btn" title="Close terminal">
|
|
335
|
-
<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M1.5 1.5l10 10M11.5 1.5l-10 10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
|
336
|
-
</button>
|
|
337
|
-
</div>
|
|
338
|
-
</div>
|
|
339
|
-
<div class="term-quick">
|
|
340
|
-
<button class="term-quick-btn" data-cmd="npm install">npm install</button>
|
|
341
|
-
<button class="term-quick-btn" data-cmd="npm run build">npm run build</button>
|
|
342
|
-
<button class="term-quick-btn" data-cmd="npm test">npm test</button>
|
|
343
|
-
<button class="term-quick-btn" data-cmd="npx hyperclaw doctor --fix">doctor --fix</button>
|
|
344
|
-
<button class="term-quick-btn" data-cmd="npx hyperclaw gateway status">gateway status</button>
|
|
345
|
-
</div>
|
|
346
|
-
<div id="terminal-log"><span style="color:#333">Use the quick buttons above or type a command below.</span></div>
|
|
347
|
-
<div class="term-input-row">
|
|
348
|
-
<span class="term-prompt">›</span>
|
|
349
|
-
<input type="text" id="terminal-input" placeholder="Run a command…">
|
|
350
|
-
<div class="term-spinner" id="term-spinner"></div>
|
|
351
|
-
</div>
|
|
352
|
-
</div>
|
|
353
|
-
|
|
354
|
-
</div>
|
|
355
|
-
|
|
356
|
-
<!-- ── CUSTOMIZE MODAL ── -->
|
|
357
|
-
<div id="customize-modal">
|
|
358
|
-
<div class="modal-box">
|
|
359
|
-
<div class="modal-title">✦ New Project / Agent</div>
|
|
360
|
-
<label class="modal-label">Project name</label>
|
|
361
|
-
<input type="text" class="modal-input" id="modal-name" placeholder="e.g. Bug Hunter">
|
|
362
|
-
<label class="modal-label">System prompt</label>
|
|
363
|
-
<textarea class="modal-input modal-textarea" id="modal-prompt" placeholder="e.g. You are a security researcher. Focus on vulnerabilities and exploits."></textarea>
|
|
364
|
-
<label class="modal-label">Color</label>
|
|
365
|
-
<input type="color" class="modal-input" id="modal-color" value="#6b7280" style="height:36px;padding:2px 6px;cursor:pointer">
|
|
366
|
-
<div class="modal-row">
|
|
367
|
-
<button class="modal-btn modal-btn-cancel" id="modal-cancel">Cancel</button>
|
|
368
|
-
<button class="modal-btn modal-btn-save" id="modal-save">Create project</button>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
</div>
|
|
372
|
-
|
|
373
|
-
<script>
|
|
374
|
-
const port = location.port || 18789;
|
|
375
|
-
const wsUrl = `ws://${location.hostname}:${port}`;
|
|
376
|
-
const apiUrl = `http://${location.hostname}:${port}`;
|
|
377
|
-
|
|
378
|
-
let ws = null, isStreaming = false, currentBubble = null;
|
|
379
|
-
let activePreset = '';
|
|
380
|
-
let customProjects = JSON.parse(localStorage.getItem('hc_projects') || '[]');
|
|
381
|
-
let chatHistory = JSON.parse(localStorage.getItem('hc_history') || '[]');
|
|
382
|
-
let currentChatId = Date.now().toString();
|
|
383
|
-
let currentChatMessages = [];
|
|
384
|
-
|
|
385
|
-
const messagesEl = document.getElementById('messages');
|
|
386
|
-
const welcomeEl = document.getElementById('welcome');
|
|
387
|
-
const input = document.getElementById('input');
|
|
388
|
-
const sendBtn = document.getElementById('send-btn');
|
|
389
|
-
const form = document.getElementById('form');
|
|
390
|
-
|
|
391
|
-
const PROMPT_PREFIX = {
|
|
392
|
-
'ethical-hacker': 'Act as an ethical hacker / security researcher. Authorized testing only. ',
|
|
393
|
-
'hyperclaw': 'You are helping with HyperClaw development. Be concise and code-focused. ',
|
|
394
|
-
'osint': 'Act as an OSINT analyst. Passive reconnaissance, open-source research. '
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
// ── Sidebar toggle ───────────────────────────────────
|
|
398
|
-
document.getElementById('sidebar-toggle').onclick = () => {
|
|
399
|
-
document.getElementById('sidebar').classList.toggle('collapsed');
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
// ── Project switching ────────────────────────────────
|
|
403
|
-
function setActiveProject(preset, name, dotColor) {
|
|
404
|
-
activePreset = preset;
|
|
405
|
-
document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
|
|
406
|
-
document.getElementById('active-project-label').textContent = name || 'General';
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
document.querySelectorAll('.sidebar-project[data-preset]').forEach(btn => {
|
|
410
|
-
btn.onclick = () => {
|
|
411
|
-
const names = { '': 'General', 'ethical-hacker': 'Ethical Hacker', 'hyperclaw': 'HyperClaw Dev', 'osint': 'OSINT' };
|
|
412
|
-
document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
|
|
413
|
-
btn.classList.add('active');
|
|
414
|
-
setActiveProject(btn.dataset.preset, names[btn.dataset.preset]);
|
|
415
|
-
};
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// ── Chat history ─────────────────────────────────────
|
|
419
|
-
function saveCurrentChat(firstMsg) {
|
|
420
|
-
if (!firstMsg) return;
|
|
421
|
-
const existing = chatHistory.find(c => c.id === currentChatId);
|
|
422
|
-
if (existing) { existing.title = firstMsg.slice(0, 40); }
|
|
423
|
-
else { chatHistory.unshift({ id: currentChatId, title: firstMsg.slice(0, 40), ts: Date.now() }); }
|
|
424
|
-
if (chatHistory.length > 30) chatHistory = chatHistory.slice(0, 30);
|
|
425
|
-
localStorage.setItem('hc_history', JSON.stringify(chatHistory));
|
|
426
|
-
renderHistory();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function renderHistory(filter) {
|
|
430
|
-
const el = document.getElementById('chat-history');
|
|
431
|
-
const items = filter
|
|
432
|
-
? chatHistory.filter(c => c.title.toLowerCase().includes(filter.toLowerCase()))
|
|
433
|
-
: chatHistory;
|
|
434
|
-
if (!items.length) { el.innerHTML = '<div style="padding:6px 22px;font-size:11px;color:#3a3a3a">' + (filter ? 'No results' : 'No recent chats') + '</div>'; return; }
|
|
435
|
-
el.innerHTML = items.map(c => `<button class="sidebar-chat-item" onclick="loadChat('${c.id}')" title="${escHtml(c.title)}">💬 ${escHtml(c.title)}</button>`).join('');
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function loadChat(id) {
|
|
439
|
-
// For now just label it — full persistence would require storing messages
|
|
440
|
-
const chat = chatHistory.find(c => c.id === id);
|
|
441
|
-
if (chat) document.getElementById('active-project-label').textContent = chat.title.slice(0,20) + '…';
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
renderHistory();
|
|
445
|
-
|
|
446
|
-
// Search chats
|
|
447
|
-
document.getElementById('search-input').oninput = function() { renderHistory(this.value); };
|
|
448
|
-
|
|
449
|
-
// ── New chat ─────────────────────────────────────────
|
|
450
|
-
document.getElementById('new-chat-btn').onclick = startNewChat;
|
|
451
|
-
document.getElementById('clear-btn').onclick = () => { clearMessages(); };
|
|
452
|
-
|
|
453
|
-
function startNewChat() {
|
|
454
|
-
currentChatId = Date.now().toString();
|
|
455
|
-
currentChatMessages = [];
|
|
456
|
-
clearMessages();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function clearMessages() {
|
|
460
|
-
messagesEl.querySelectorAll('.msg-row').forEach(e => e.remove());
|
|
461
|
-
welcomeEl.style.display = 'flex';
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// ── Customize modal ──────────────────────────────────
|
|
465
|
-
document.getElementById('customize-btn').onclick = () => {
|
|
466
|
-
document.getElementById('customize-modal').classList.add('open');
|
|
467
|
-
document.getElementById('modal-name').focus();
|
|
468
|
-
};
|
|
469
|
-
document.getElementById('new-project-btn').onclick = () => {
|
|
470
|
-
document.getElementById('customize-modal').classList.add('open');
|
|
471
|
-
document.getElementById('modal-name').focus();
|
|
472
|
-
};
|
|
473
|
-
document.getElementById('modal-cancel').onclick = () => document.getElementById('customize-modal').classList.remove('open');
|
|
474
|
-
document.getElementById('customize-modal').onclick = (e) => { if (e.target === e.currentTarget) e.currentTarget.classList.remove('open'); };
|
|
475
|
-
|
|
476
|
-
document.getElementById('modal-save').onclick = () => {
|
|
477
|
-
const name = document.getElementById('modal-name').value.trim();
|
|
478
|
-
const prompt = document.getElementById('modal-prompt').value.trim();
|
|
479
|
-
const color = document.getElementById('modal-color').value;
|
|
480
|
-
if (!name) return;
|
|
481
|
-
const id = 'custom-' + Date.now();
|
|
482
|
-
customProjects.push({ id, name, prompt, color });
|
|
483
|
-
localStorage.setItem('hc_projects', JSON.stringify(customProjects));
|
|
484
|
-
PROMPT_PREFIX[id] = prompt + ' ';
|
|
485
|
-
addProjectToSidebar({ id, name, color });
|
|
486
|
-
document.getElementById('customize-modal').classList.remove('open');
|
|
487
|
-
document.getElementById('modal-name').value = '';
|
|
488
|
-
document.getElementById('modal-prompt').value = '';
|
|
489
|
-
document.getElementById('modal-color').value = '#6b7280';
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
function addProjectToSidebar(proj) {
|
|
493
|
-
const btn = document.createElement('button');
|
|
494
|
-
btn.className = 'sidebar-project';
|
|
495
|
-
btn.dataset.preset = proj.id;
|
|
496
|
-
btn.innerHTML = `<span class="sidebar-project-dot" style="background:${proj.color}"></span>${escHtml(proj.name)}`;
|
|
497
|
-
btn.onclick = () => {
|
|
498
|
-
document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
|
|
499
|
-
btn.classList.add('active');
|
|
500
|
-
setActiveProject(proj.id, proj.name);
|
|
501
|
-
};
|
|
502
|
-
document.getElementById('new-project-btn').before(btn);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Restore custom projects
|
|
506
|
-
customProjects.forEach(proj => {
|
|
507
|
-
PROMPT_PREFIX[proj.id] = proj.prompt + ' ';
|
|
508
|
-
addProjectToSidebar(proj);
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
// ── Terminal ─────────────────────────────────────────
|
|
512
|
-
const terminalPanel = document.getElementById('terminal-panel');
|
|
513
|
-
const terminalBtn = document.getElementById('terminal-btn');
|
|
514
|
-
const terminalLog = document.getElementById('terminal-log');
|
|
515
|
-
const terminalInput = document.getElementById('terminal-input');
|
|
516
|
-
const termSpinner = document.getElementById('term-spinner');
|
|
517
|
-
let termRunning = false;
|
|
518
|
-
|
|
519
|
-
terminalBtn.onclick = toggleTerminal;
|
|
520
|
-
document.getElementById('term-close-btn').onclick = () => {
|
|
521
|
-
terminalPanel.classList.remove('open');
|
|
522
|
-
terminalBtn.classList.remove('active');
|
|
523
|
-
};
|
|
524
|
-
document.getElementById('term-clear-btn').onclick = () => {
|
|
525
|
-
terminalLog.innerHTML = '<span style="color:#333">Terminal cleared.</span>';
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
function toggleTerminal() {
|
|
529
|
-
const isOpen = terminalPanel.classList.toggle('open');
|
|
530
|
-
terminalBtn.classList.toggle('active', isOpen);
|
|
531
|
-
if (isOpen) setTimeout(() => terminalInput.focus(), 50);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
async function runTerminalCmd(cmd) {
|
|
535
|
-
cmd = (cmd || '').trim();
|
|
536
|
-
if (!cmd || termRunning) return;
|
|
537
|
-
termRunning = true;
|
|
538
|
-
termSpinner.style.display = 'block';
|
|
539
|
-
document.querySelectorAll('.term-quick-btn').forEach(b => b.disabled = true);
|
|
540
|
-
|
|
541
|
-
const cmdLine = document.createElement('div');
|
|
542
|
-
cmdLine.className = 'term-log-cmd';
|
|
543
|
-
cmdLine.textContent = '$ ' + cmd;
|
|
544
|
-
if (terminalLog.querySelector('span')) terminalLog.innerHTML = '';
|
|
545
|
-
terminalLog.appendChild(cmdLine);
|
|
546
|
-
terminalLog.scrollTop = terminalLog.scrollHeight;
|
|
547
|
-
|
|
548
|
-
try {
|
|
549
|
-
const res = await fetch(apiUrl + '/api/terminal', {
|
|
550
|
-
method: 'POST', headers: {'Content-Type': 'application/json'},
|
|
551
|
-
body: JSON.stringify({command: cmd})
|
|
552
|
-
});
|
|
553
|
-
const data = await res.json();
|
|
554
|
-
if (data.user || data.cwd) {
|
|
555
|
-
document.getElementById('term-prompt-info').textContent = `${data.user || 'user'}@${data.hostname || 'local'}:${data.cwd || ''}`;
|
|
556
|
-
}
|
|
557
|
-
if (data.stdout) {
|
|
558
|
-
const out = document.createElement('div');
|
|
559
|
-
out.textContent = data.stdout.trimEnd();
|
|
560
|
-
terminalLog.appendChild(out);
|
|
561
|
-
}
|
|
562
|
-
if (data.stderr) {
|
|
563
|
-
const err = document.createElement('div');
|
|
564
|
-
err.className = 'term-log-err';
|
|
565
|
-
err.textContent = data.stderr.trimEnd();
|
|
566
|
-
terminalLog.appendChild(err);
|
|
567
|
-
}
|
|
568
|
-
if (!data.stdout && !data.stderr) {
|
|
569
|
-
const ex = document.createElement('div');
|
|
570
|
-
ex.style.color = '#444';
|
|
571
|
-
ex.textContent = '(exit ' + (data.code ?? 0) + ')';
|
|
572
|
-
terminalLog.appendChild(ex);
|
|
573
|
-
}
|
|
574
|
-
} catch (e) {
|
|
575
|
-
const err = document.createElement('div');
|
|
576
|
-
err.className = 'term-log-err';
|
|
577
|
-
err.textContent = 'Error: ' + (e.message || String(e));
|
|
578
|
-
terminalLog.appendChild(err);
|
|
579
|
-
} finally {
|
|
580
|
-
termRunning = false;
|
|
581
|
-
termSpinner.style.display = 'none';
|
|
582
|
-
document.querySelectorAll('.term-quick-btn').forEach(b => b.disabled = false);
|
|
583
|
-
terminalLog.scrollTop = terminalLog.scrollHeight;
|
|
584
|
-
terminalInput.focus();
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
document.querySelectorAll('.term-quick-btn').forEach(btn => {
|
|
589
|
-
btn.onclick = () => runTerminalCmd(btn.dataset.cmd);
|
|
590
|
-
});
|
|
591
|
-
terminalInput.onkeydown = (e) => {
|
|
592
|
-
if (e.key === 'Enter') { runTerminalCmd(terminalInput.value); terminalInput.value = ''; }
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
// ── WebSocket ────────────────────────────────────────
|
|
596
|
-
function setConnected(ok) {
|
|
597
|
-
[document.getElementById('ws-dot'), document.getElementById('ws-dot-side')].forEach(d => {
|
|
598
|
-
if (d) d.className = 'dot' + (ok ? ' connected' : '');
|
|
599
|
-
});
|
|
600
|
-
const label = ok ? 'Connected' : 'Reconnecting…';
|
|
601
|
-
const color = ok ? '#22c55e' : '#444';
|
|
602
|
-
[document.getElementById('ws-label'), document.getElementById('ws-label-side')].forEach(el => {
|
|
603
|
-
if (el) { el.textContent = label; el.style.color = color; }
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function connectWs() {
|
|
608
|
-
ws = new WebSocket(wsUrl);
|
|
609
|
-
ws.onopen = () => setConnected(true);
|
|
610
|
-
ws.onclose = () => { setConnected(false); setTimeout(connectWs, 3000); };
|
|
611
|
-
ws.onerror = () => setConnected(false);
|
|
612
|
-
ws.onmessage = (e) => {
|
|
613
|
-
try {
|
|
614
|
-
const msg = JSON.parse(e.data);
|
|
615
|
-
if (msg.type === 'chat:chunk') {
|
|
616
|
-
if (!currentBubble) { removeTyping(); currentBubble = addMsg('assistant', '', true); }
|
|
617
|
-
currentBubble._raw = (currentBubble._raw || '') + (msg.content || '');
|
|
618
|
-
currentBubble.textContent = currentBubble._raw;
|
|
619
|
-
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
620
|
-
} else if (msg.type === 'chat:response') {
|
|
621
|
-
removeTyping();
|
|
622
|
-
if (currentBubble) {
|
|
623
|
-
currentBubble.innerHTML = '<div class="prose">' + marked.parse(currentBubble._raw || msg.content || '') + '</div>';
|
|
624
|
-
currentBubble = null;
|
|
625
|
-
} else {
|
|
626
|
-
addMsg('assistant', msg.content || '');
|
|
627
|
-
}
|
|
628
|
-
addTimestamp();
|
|
629
|
-
finishStreaming();
|
|
630
|
-
} else if (msg.type === 'error') {
|
|
631
|
-
removeTyping();
|
|
632
|
-
addMsg('assistant', '⚠️ ' + (msg.message || 'Unknown error'));
|
|
633
|
-
currentBubble = null;
|
|
634
|
-
finishStreaming();
|
|
635
|
-
}
|
|
636
|
-
} catch (_) {}
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function finishStreaming() {
|
|
641
|
-
isStreaming = false;
|
|
642
|
-
sendBtn.disabled = false;
|
|
643
|
-
currentBubble = null;
|
|
644
|
-
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// ── Messages ─────────────────────────────────────────
|
|
648
|
-
function now() { return new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}); }
|
|
649
|
-
|
|
650
|
-
function addMsg(role, content, streaming = false) {
|
|
651
|
-
welcomeEl.style.display = 'none';
|
|
652
|
-
const row = document.createElement('div');
|
|
653
|
-
row.className = 'msg-row ' + role;
|
|
654
|
-
const avatar = document.createElement('div');
|
|
655
|
-
avatar.className = 'msg-avatar';
|
|
656
|
-
avatar.textContent = role === 'user' ? '👤' : '🦅';
|
|
657
|
-
const bubble = document.createElement('div');
|
|
658
|
-
bubble.className = 'msg-bubble';
|
|
659
|
-
if (!streaming && content) {
|
|
660
|
-
bubble.innerHTML = '<div class="prose">' + (role === 'assistant' ? marked.parse(content) : escHtml(content)) + '</div>';
|
|
661
|
-
} else {
|
|
662
|
-
bubble.textContent = content || '';
|
|
663
|
-
}
|
|
664
|
-
row.appendChild(avatar);
|
|
665
|
-
row.appendChild(bubble);
|
|
666
|
-
messagesEl.appendChild(row);
|
|
667
|
-
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
668
|
-
return bubble;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function addTimestamp() {
|
|
672
|
-
const last = messagesEl.querySelector('.msg-row.assistant:last-child');
|
|
673
|
-
if (last) {
|
|
674
|
-
const t = document.createElement('div');
|
|
675
|
-
t.className = 'msg-time';
|
|
676
|
-
t.textContent = now();
|
|
677
|
-
last.appendChild(t);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
let workingTicker = null;
|
|
682
|
-
function addTyping() {
|
|
683
|
-
welcomeEl.style.display = 'none';
|
|
684
|
-
const row = document.createElement('div');
|
|
685
|
-
row.className = 'msg-row assistant';
|
|
686
|
-
row.id = 'typing-row';
|
|
687
|
-
let sec = 0;
|
|
688
|
-
row.innerHTML = '<div class="msg-avatar">🦅</div><div class="msg-bubble" style="padding:12px 16px"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span><span id="wt" style="margin-left:8px;color:#444;font-size:11px">Thinking…</span></div>';
|
|
689
|
-
messagesEl.appendChild(row);
|
|
690
|
-
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
691
|
-
workingTicker = setInterval(() => { sec++; const el = document.getElementById('wt'); if (el) el.textContent = sec + 's'; }, 1000);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
function removeTyping() {
|
|
695
|
-
if (workingTicker) { clearInterval(workingTicker); workingTicker = null; }
|
|
696
|
-
document.getElementById('typing-row')?.remove();
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
700
|
-
|
|
701
|
-
// ── Send ─────────────────────────────────────────────
|
|
702
|
-
let msgCount = 0;
|
|
703
|
-
form.onsubmit = async (e) => {
|
|
704
|
-
e.preventDefault();
|
|
705
|
-
const msg = input.value.trim();
|
|
706
|
-
if (!msg || isStreaming) return;
|
|
707
|
-
const prefix = PROMPT_PREFIX[activePreset] || '';
|
|
708
|
-
const fullMsg = prefix ? prefix + msg : msg;
|
|
709
|
-
if (msgCount === 0) saveCurrentChat(msg);
|
|
710
|
-
msgCount++;
|
|
711
|
-
addMsg('user', msg);
|
|
712
|
-
input.value = '';
|
|
713
|
-
input.style.height = 'auto';
|
|
714
|
-
sendBtn.disabled = true;
|
|
715
|
-
isStreaming = true;
|
|
716
|
-
addTyping();
|
|
717
|
-
|
|
718
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
719
|
-
ws.send(JSON.stringify({ type: 'chat:message', content: fullMsg, source: 'webchat' }));
|
|
720
|
-
} else {
|
|
721
|
-
try {
|
|
722
|
-
const res = await fetch(apiUrl + '/api/chat', {
|
|
723
|
-
method: 'POST', headers: {'Content-Type':'application/json'},
|
|
724
|
-
body: JSON.stringify({message: fullMsg})
|
|
725
|
-
});
|
|
726
|
-
const data = await res.json();
|
|
727
|
-
removeTyping();
|
|
728
|
-
addMsg('assistant', data.response || data.error || '(no response)');
|
|
729
|
-
addTimestamp();
|
|
730
|
-
} catch (err) {
|
|
731
|
-
removeTyping();
|
|
732
|
-
addMsg('assistant', '⚠️ Could not reach gateway: ' + err.message);
|
|
733
|
-
}
|
|
734
|
-
finishStreaming();
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
// ── Chips ────────────────────────────────────────────
|
|
739
|
-
function fillInput(el) { input.value = el.textContent; input.focus(); autoResize(); }
|
|
740
|
-
|
|
741
|
-
// ── Textarea auto-resize ──────────────────────────────
|
|
742
|
-
function autoResize() {
|
|
743
|
-
input.style.height = 'auto';
|
|
744
|
-
input.style.height = Math.min(input.scrollHeight, 130) + 'px';
|
|
745
|
-
}
|
|
746
|
-
input.addEventListener('input', autoResize);
|
|
747
|
-
input.addEventListener('keydown', (e) => {
|
|
748
|
-
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.dispatchEvent(new Event('submit')); }
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
// ── Init ─────────────────────────────────────────────
|
|
752
|
-
connectWs();
|
|
753
|
-
fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
|
|
754
|
-
if (d.model) {
|
|
755
|
-
document.getElementById('model-hint').textContent = d.model;
|
|
756
|
-
document.getElementById('model-label').textContent = d.model;
|
|
757
|
-
const ver = d.version || '5.3.
|
|
758
|
-
document.getElementById('banner-sub').textContent = `HyperClaw Bot \u00b7 AI Gateway v${ver}`;
|
|
759
|
-
}
|
|
760
|
-
document.documentElement.setAttribute('data-daemon', d.daemonMode ? 'true' : 'false');
|
|
761
|
-
}).catch(() => {});
|
|
762
|
-
</script>
|
|
763
|
-
</body>
|
|
764
|
-
</html>
|
|
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>HyperClaw Chat</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
/* ── ACCENT THEME VARS ── */
|
|
12
|
+
:root {
|
|
13
|
+
--accent: #22d3ee;
|
|
14
|
+
--accent2: #06b6d4;
|
|
15
|
+
--accent-glow: rgba(34,211,238,0.3);
|
|
16
|
+
--accent-bg: rgba(34,211,238,0.08);
|
|
17
|
+
}
|
|
18
|
+
[data-theme="light"] {
|
|
19
|
+
--accent: #0891b2;
|
|
20
|
+
--accent2: #0e7490;
|
|
21
|
+
--accent-glow: rgba(8,145,178,0.25);
|
|
22
|
+
--accent-bg: rgba(8,145,178,0.06);
|
|
23
|
+
}
|
|
24
|
+
[data-daemon="true"] {
|
|
25
|
+
--accent: #f87171;
|
|
26
|
+
--accent2: #ef4444;
|
|
27
|
+
--accent-glow: rgba(248,113,113,0.3);
|
|
28
|
+
--accent-bg: rgba(248,113,113,0.08);
|
|
29
|
+
}
|
|
30
|
+
[data-theme="light"][data-daemon="true"] {
|
|
31
|
+
--accent: #dc2626;
|
|
32
|
+
--accent2: #b91c1c;
|
|
33
|
+
--accent-glow: rgba(220,38,38,0.25);
|
|
34
|
+
--accent-bg: rgba(220,38,38,0.06);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
38
|
+
body { background: #0a0a0a; color: #e5e5e5; font-family: 'Inter', system-ui, sans-serif; height: 100vh; display: flex; overflow: hidden; }
|
|
39
|
+
|
|
40
|
+
/* ── SCROLLBAR ── */
|
|
41
|
+
::-webkit-scrollbar { width: 4px; }
|
|
42
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
43
|
+
::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 2px; }
|
|
44
|
+
|
|
45
|
+
/* ── SIDEBAR ── */
|
|
46
|
+
#sidebar { width: 240px; flex-shrink: 0; background: #111111; border-right: 1px solid #1e1e1e; display: flex; flex-direction: column; height: 100vh; overflow: hidden; transition: width 0.2s; }
|
|
47
|
+
#sidebar.collapsed { width: 0; border: none; }
|
|
48
|
+
|
|
49
|
+
.sidebar-top { padding: 14px 12px 8px; flex-shrink: 0; }
|
|
50
|
+
.sidebar-logo { display: flex; align-items: center; gap: 9px; padding: 6px 8px; margin-bottom: 6px; }
|
|
51
|
+
.sidebar-logo img { width: 26px; height: 26px; border-radius: 6px; }
|
|
52
|
+
.sidebar-logo-text { font-size: 13px; font-weight: 600; color: #e5e5e5; }
|
|
53
|
+
|
|
54
|
+
.sidebar-btn { width: 100%; display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 8px; border: none; background: transparent; color: #a3a3a3; font-size: 13px; cursor: pointer; transition: background .15s, color .15s; text-align: left; white-space: nowrap; overflow: hidden; }
|
|
55
|
+
.sidebar-btn:hover { background: #1e1e1e; color: #e5e5e5; }
|
|
56
|
+
.sidebar-btn.active { background: #1e1e1e; color: #e5e5e5; }
|
|
57
|
+
.sidebar-btn svg { flex-shrink: 0; }
|
|
58
|
+
|
|
59
|
+
.sidebar-new-chat { width: calc(100% - 24px); margin: 0 12px 4px; display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 8px; border: 1px solid #2a2a2a; background: transparent; color: #e5e5e5; font-size: 13px; cursor: pointer; transition: background .15s, border-color .15s; font-family: inherit; }
|
|
60
|
+
.sidebar-new-chat:hover { background: #1e1e1e; border-color: #3a3a3a; }
|
|
61
|
+
|
|
62
|
+
.sidebar-search { margin: 4px 12px; position: relative; }
|
|
63
|
+
.sidebar-search input { width: 100%; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; padding: 7px 10px 7px 32px; font-size: 12px; color: #e5e5e5; outline: none; font-family: inherit; transition: border-color .15s; }
|
|
64
|
+
.sidebar-search input:focus { border-color: #3a3a3a; }
|
|
65
|
+
.sidebar-search input::placeholder { color: #555; }
|
|
66
|
+
.sidebar-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: #555; pointer-events: none; }
|
|
67
|
+
|
|
68
|
+
.sidebar-section { padding: 16px 12px 4px; font-size: 11px; font-weight: 500; color: #555; letter-spacing: 0.5px; text-transform: uppercase; }
|
|
69
|
+
|
|
70
|
+
.sidebar-project { width: calc(100% - 24px); margin: 0 12px 2px; display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px; border: none; background: transparent; color: #a3a3a3; font-size: 12px; cursor: pointer; transition: background .15s, color .15s; text-align: left; font-family: inherit; overflow: hidden; }
|
|
71
|
+
.sidebar-project:hover { background: #1e1e1e; color: #e5e5e5; }
|
|
72
|
+
.sidebar-project.active { background: #1e1e1e; color: #e5e5e5; }
|
|
73
|
+
.sidebar-project-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
|
|
74
|
+
|
|
75
|
+
.sidebar-chat-item { width: calc(100% - 24px); margin: 0 12px 1px; display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 8px; border: none; background: transparent; color: #777; font-size: 12px; cursor: pointer; transition: background .15s, color .15s; text-align: left; font-family: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
76
|
+
.sidebar-chat-item:hover { background: #1a1a1a; color: #ccc; }
|
|
77
|
+
|
|
78
|
+
.sidebar-history { flex: 1; overflow-y: auto; padding-bottom: 8px; }
|
|
79
|
+
|
|
80
|
+
.sidebar-bottom { padding: 8px 12px 12px; border-top: 1px solid #1e1e1e; flex-shrink: 0; }
|
|
81
|
+
|
|
82
|
+
/* ── MAIN ── */
|
|
83
|
+
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; height: 100vh; background: #0a0a0a; }
|
|
84
|
+
|
|
85
|
+
/* ── HEADER ── */
|
|
86
|
+
.chat-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid #1e1e1e; background: #0a0a0a; flex-shrink: 0; gap: 8px; }
|
|
87
|
+
.chat-header-left { display: flex; align-items: center; gap: 8px; }
|
|
88
|
+
.sidebar-toggle { background: transparent; border: none; color: #555; cursor: pointer; padding: 4px; border-radius: 6px; display: flex; transition: color .15s, background .15s; }
|
|
89
|
+
.sidebar-toggle:hover { color: #ccc; background: #1e1e1e; }
|
|
90
|
+
.chat-header-right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
|
|
91
|
+
.header-status { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #555; }
|
|
92
|
+
.dot { width: 6px; height: 6px; border-radius: 50%; background: #ef4444; flex-shrink: 0; }
|
|
93
|
+
.dot.connected { background: #22c55e; box-shadow: 0 0 5px #22c55e; animation: pulse-g 2s infinite; }
|
|
94
|
+
@keyframes pulse-g { 0%,100%{opacity:1} 50%{opacity:.6} }
|
|
95
|
+
.hdr-btn { background: transparent; border: 1px solid #2a2a2a; color: #777; padding: 5px 10px; border-radius: 7px; font-size: 11px; cursor: pointer; font-family: inherit; transition: background .15s, color .15s, border-color .15s; white-space: nowrap; }
|
|
96
|
+
.hdr-btn:hover { background: #1e1e1e; color: #ccc; border-color: #3a3a3a; }
|
|
97
|
+
.hdr-btn.active { background: #2a1a1a; border-color: #5a2a2a; color: #f87171; }
|
|
98
|
+
.hdr-select { background: #111; border: 1px solid #2a2a2a; color: #ccc; padding: 5px 8px; border-radius: 7px; font-size: 11px; cursor: pointer; font-family: inherit; outline: none; }
|
|
99
|
+
|
|
100
|
+
/* ── MESSAGES ── */
|
|
101
|
+
#messages { flex: 1; overflow-y: auto; padding: 24px 20px; display: flex; flex-direction: column; gap: 16px; scroll-behavior: smooth; min-height: 0; }
|
|
102
|
+
.msg-row { display: flex; gap: 10px; max-width: 720px; animation: fadeIn .2s ease; }
|
|
103
|
+
@keyframes fadeIn { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:none} }
|
|
104
|
+
.msg-row.user { flex-direction: row-reverse; align-self: flex-end; }
|
|
105
|
+
.msg-row.assistant { align-self: flex-start; }
|
|
106
|
+
.msg-avatar { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; background: #1a1a1a; border: 1px solid #2a2a2a; margin-top: 2px; }
|
|
107
|
+
.msg-bubble { max-width: min(65vw, 560px); padding: 11px 15px; border-radius: 16px; font-size: 13.5px; line-height: 1.65; }
|
|
108
|
+
.msg-row.user .msg-bubble { background: #c0392b; color: #fff; border-bottom-right-radius: 4px; }
|
|
109
|
+
.msg-row.assistant .msg-bubble { background: #141414; border: 1px solid #222; border-bottom-left-radius: 4px; color: #ddd; }
|
|
110
|
+
.msg-bubble .prose h1,.msg-bubble .prose h2,.msg-bubble .prose h3 { color: #f87171; margin: 8px 0 4px; font-weight: 600; }
|
|
111
|
+
.msg-bubble .prose p { margin: 4px 0; }
|
|
112
|
+
.msg-bubble .prose pre { background: #0d0d0d; border: 1px solid #222; padding: 10px 12px; border-radius: 8px; overflow-x: auto; margin: 8px 0; font-size: 12px; }
|
|
113
|
+
.msg-bubble .prose code { background: #0d0d0d; padding: 1px 5px; border-radius: 4px; font-size: 12px; font-family: 'JetBrains Mono', monospace; color: #f87171; }
|
|
114
|
+
.msg-bubble .prose pre code { background: none; padding: 0; color: #ccc; }
|
|
115
|
+
.msg-bubble .prose ul,.msg-bubble .prose ol { padding-left: 18px; margin: 4px 0; }
|
|
116
|
+
.msg-bubble .prose a { color: #60a5fa; text-decoration: underline; }
|
|
117
|
+
.msg-time { font-size: 10px; color: #444; margin-top: 4px; }
|
|
118
|
+
.msg-row.user .msg-time { text-align: right; }
|
|
119
|
+
|
|
120
|
+
/* ── TYPING ── */
|
|
121
|
+
.typing-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: #f87171; animation: blink 1.2s infinite; }
|
|
122
|
+
.typing-dot:nth-child(2){animation-delay:.18s}.typing-dot:nth-child(3){animation-delay:.36s}
|
|
123
|
+
@keyframes blink{0%,80%,100%{opacity:.2}40%{opacity:1}}
|
|
124
|
+
|
|
125
|
+
/* ── BANNER ── */
|
|
126
|
+
.hc-banner { display: flex; flex-direction: column; align-items: center; padding: 24px 0 16px; flex-shrink: 0; pointer-events: none; user-select: none; opacity: 0.45; }
|
|
127
|
+
.hc-banner-text { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 2.4rem; letter-spacing: 0.3em; color: var(--accent); text-shadow: 0 0 32px var(--accent), 0 0 64px var(--accent-bg); line-height: 1; }
|
|
128
|
+
.hc-banner-sub { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--accent); letter-spacing: 0.25em; margin-top: 8px; opacity: 0.75; }
|
|
129
|
+
|
|
130
|
+
/* ── WELCOME ── */
|
|
131
|
+
.welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; gap: 14px; text-align: center; padding: 40px; }
|
|
132
|
+
.welcome-eagle { font-size: 48px; margin-bottom: 4px; filter: drop-shadow(0 0 12px rgba(239,68,68,0.3)); }
|
|
133
|
+
.welcome-title { font-size: 26px; font-weight: 600; color: #e5e5e5; }
|
|
134
|
+
.welcome-sub { font-size: 13px; color: #666; max-width: 360px; line-height: 1.6; }
|
|
135
|
+
.welcome-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 4px; }
|
|
136
|
+
.chip { background: #141414; border: 1px solid #2a2a2a; color: #888; padding: 7px 14px; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all .2s; font-family: inherit; }
|
|
137
|
+
.chip:hover { border-color: #555; color: #ccc; background: #1e1e1e; }
|
|
138
|
+
|
|
139
|
+
/* ── INPUT ── */
|
|
140
|
+
.input-wrap { padding: 12px 16px 14px; border-top: 1px solid #1a1a1a; background: #0a0a0a; flex-shrink: 0; }
|
|
141
|
+
.input-row { display: flex; gap: 8px; align-items: flex-end; max-width: 720px; margin: 0 auto; }
|
|
142
|
+
#input { flex: 1; background: #141414; border: 1px solid #2a2a2a; color: #e5e5e5; border-radius: 12px; padding: 11px 14px; font-size: 13.5px; font-family: 'Inter', sans-serif; resize: none; min-height: 44px; max-height: 130px; outline: none; transition: border-color .2s; line-height: 1.5; }
|
|
143
|
+
#input:focus { border-color: #3a3a3a; }
|
|
144
|
+
#input::placeholder { color: #444; }
|
|
145
|
+
#send-btn { width: 40px; height: 40px; border-radius: 10px; background: #c0392b; border: none; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all .2s; flex-shrink: 0; }
|
|
146
|
+
#send-btn:hover:not(:disabled) { background: #e74c3c; }
|
|
147
|
+
#send-btn:disabled { opacity: 0.3; cursor: default; }
|
|
148
|
+
.input-hint { font-size: 11px; color: #333; text-align: center; margin-top: 6px; max-width: 720px; margin-left: auto; margin-right: auto; }
|
|
149
|
+
|
|
150
|
+
/* ── TERMINAL PANEL ── */
|
|
151
|
+
#terminal-panel { border-top: 1px solid #1e1e1e; background: #0d0d0d; flex-shrink: 0; height: 220px; display: none; flex-direction: column; }
|
|
152
|
+
#terminal-panel.open { display: flex; }
|
|
153
|
+
.term-titlebar { display: flex; align-items: center; justify-content: space-between; padding: 6px 14px; background: #141414; border-bottom: 1px solid #1e1e1e; flex-shrink: 0; }
|
|
154
|
+
.term-dots { display: flex; gap: 5px; }
|
|
155
|
+
.term-dot { width: 11px; height: 11px; border-radius: 50%; }
|
|
156
|
+
.term-info { font-size: 11px; color: #444; font-family: 'JetBrains Mono', monospace; flex: 1; text-align: center; }
|
|
157
|
+
.term-titlebar-right { display: flex; align-items: center; gap: 10px; }
|
|
158
|
+
.term-clear-btn { font-size: 11px; color: #444; background: none; border: none; cursor: pointer; font-family: inherit; transition: color .15s; }
|
|
159
|
+
.term-clear-btn:hover { color: #888; }
|
|
160
|
+
.term-close-btn { background: none; border: none; color: #444; cursor: pointer; display: flex; transition: color .15s; }
|
|
161
|
+
.term-close-btn:hover { color: #ccc; }
|
|
162
|
+
.term-quick { display: flex; gap: 6px; padding: 6px 12px; border-bottom: 1px solid #191919; flex-shrink: 0; overflow-x: auto; }
|
|
163
|
+
.term-quick::-webkit-scrollbar { height: 0; }
|
|
164
|
+
.term-quick-btn { padding: 4px 10px; border-radius: 6px; background: #1a1a1a; border: 1px solid #2a2a2a; color: #777; font-size: 11px; cursor: pointer; font-family: 'JetBrains Mono', monospace; transition: all .15s; white-space: nowrap; flex-shrink: 0; }
|
|
165
|
+
.term-quick-btn:hover:not(:disabled) { background: #222; color: #ccc; border-color: #3a3a3a; }
|
|
166
|
+
.term-quick-btn:disabled { opacity: 0.4; cursor: default; }
|
|
167
|
+
#terminal-log { flex: 1; overflow-y: auto; padding: 8px 14px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #888; white-space: pre-wrap; line-height: 1.5; min-height: 0; }
|
|
168
|
+
.term-log-cmd { color: #67e8f9; }
|
|
169
|
+
.term-log-err { color: #f87171; }
|
|
170
|
+
.term-input-row { display: flex; align-items: center; gap: 8px; padding: 6px 12px 8px; border-top: 1px solid #1a1a1a; background: #141414; flex-shrink: 0; }
|
|
171
|
+
.term-prompt { color: #67e8f9; font-family: 'JetBrains Mono', monospace; font-size: 12px; flex-shrink: 0; }
|
|
172
|
+
#terminal-input { flex: 1; background: transparent; border: none; color: #e5e5e5; font-family: 'JetBrains Mono', monospace; font-size: 12px; outline: none; }
|
|
173
|
+
#terminal-input::placeholder { color: #333; }
|
|
174
|
+
.term-spinner { width: 12px; height: 12px; border: 1.5px solid #d97706; border-top-color: transparent; border-radius: 50%; animation: spin .7s linear infinite; flex-shrink: 0; display: none; }
|
|
175
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
176
|
+
|
|
177
|
+
/* ── CUSTOMIZE MODAL ── */
|
|
178
|
+
#customize-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 999; align-items: center; justify-content: center; backdrop-filter: blur(4px); }
|
|
179
|
+
#customize-modal.open { display: flex; }
|
|
180
|
+
.modal-box { background: #141414; border: 1px solid #2a2a2a; border-radius: 14px; padding: 24px; width: 400px; max-width: 90vw; }
|
|
181
|
+
.modal-title { font-size: 15px; font-weight: 600; color: #e5e5e5; margin-bottom: 16px; }
|
|
182
|
+
.modal-label { font-size: 12px; color: #777; margin-bottom: 4px; display: block; }
|
|
183
|
+
.modal-input { width: 100%; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; padding: 8px 12px; color: #e5e5e5; font-size: 13px; font-family: inherit; outline: none; margin-bottom: 12px; transition: border-color .15s; }
|
|
184
|
+
.modal-input:focus { border-color: #3a3a3a; }
|
|
185
|
+
.modal-textarea { min-height: 80px; resize: vertical; }
|
|
186
|
+
.modal-row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }
|
|
187
|
+
.modal-btn { padding: 7px 16px; border-radius: 8px; font-size: 13px; cursor: pointer; font-family: inherit; border: 1px solid #2a2a2a; transition: all .15s; }
|
|
188
|
+
.modal-btn-cancel { background: transparent; color: #777; }
|
|
189
|
+
.modal-btn-cancel:hover { background: #1e1e1e; color: #ccc; }
|
|
190
|
+
.modal-btn-save { background: #c0392b; border-color: #c0392b; color: #fff; }
|
|
191
|
+
.modal-btn-save:hover { background: #e74c3c; border-color: #e74c3c; }
|
|
192
|
+
</style>
|
|
193
|
+
</head>
|
|
194
|
+
<body>
|
|
195
|
+
|
|
196
|
+
<!-- ── SIDEBAR ── -->
|
|
197
|
+
<aside id="sidebar">
|
|
198
|
+
<div class="sidebar-top">
|
|
199
|
+
<!-- Logo -->
|
|
200
|
+
<div class="sidebar-logo">
|
|
201
|
+
<span style="font-size:22px">🦅</span>
|
|
202
|
+
<span class="sidebar-logo-text">HyperClaw</span>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<!-- New chat -->
|
|
206
|
+
<button class="sidebar-new-chat" id="new-chat-btn">
|
|
207
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1v12M1 7h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
|
208
|
+
New chat
|
|
209
|
+
</button>
|
|
210
|
+
|
|
211
|
+
<!-- Search -->
|
|
212
|
+
<div class="sidebar-search" style="margin-top:6px">
|
|
213
|
+
<svg class="sidebar-search-icon" width="13" height="13" viewBox="0 0 13 13" fill="none"><circle cx="5.5" cy="5.5" r="4" stroke="currentColor" stroke-width="1.3"/><path d="M9 9l3 3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
|
214
|
+
<input type="text" id="search-input" placeholder="Search chats…" autocomplete="off">
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<!-- Customize -->
|
|
219
|
+
<button class="sidebar-btn" id="customize-btn" style="margin: 2px 12px; width: calc(100% - 24px)">
|
|
220
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="2.5" stroke="currentColor" stroke-width="1.3"/><path d="M7 1v1.5M7 11.5V13M1 7h1.5M11.5 7H13M2.5 2.5l1.1 1.1M10.4 10.4l1.1 1.1M2.5 11.5l1.1-1.1M10.4 3.6l1.1-1.1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
|
221
|
+
Customize
|
|
222
|
+
</button>
|
|
223
|
+
|
|
224
|
+
<!-- Projects section -->
|
|
225
|
+
<div class="sidebar-section">Projects</div>
|
|
226
|
+
<div id="projects-list">
|
|
227
|
+
<button class="sidebar-project active" data-preset="" id="proj-general">
|
|
228
|
+
<span class="sidebar-project-dot" style="background:#6b7280"></span>
|
|
229
|
+
General
|
|
230
|
+
</button>
|
|
231
|
+
<button class="sidebar-project" data-preset="ethical-hacker" id="proj-hacker">
|
|
232
|
+
<span class="sidebar-project-dot" style="background:#ef4444"></span>
|
|
233
|
+
Ethical Hacker
|
|
234
|
+
</button>
|
|
235
|
+
<button class="sidebar-project" data-preset="hyperclaw" id="proj-hcdev">
|
|
236
|
+
<span class="sidebar-project-dot" style="background:#f97316"></span>
|
|
237
|
+
HyperClaw Dev
|
|
238
|
+
</button>
|
|
239
|
+
<button class="sidebar-project" data-preset="osint" id="proj-osint">
|
|
240
|
+
<span class="sidebar-project-dot" style="background:#a855f7"></span>
|
|
241
|
+
OSINT
|
|
242
|
+
</button>
|
|
243
|
+
<button class="sidebar-project" id="new-project-btn" style="color:#555">
|
|
244
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
|
245
|
+
New project…
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<!-- Chats history -->
|
|
250
|
+
<div class="sidebar-section">Chats</div>
|
|
251
|
+
<div class="sidebar-history" id="chat-history">
|
|
252
|
+
<div style="padding: 6px 22px; font-size:11px; color:#3a3a3a">No recent chats</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<!-- Bottom: status -->
|
|
256
|
+
<div class="sidebar-bottom">
|
|
257
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
258
|
+
<span class="dot" id="ws-dot-side"></span>
|
|
259
|
+
<span id="ws-label-side" style="font-size:11px;color:#444">Offline</span>
|
|
260
|
+
</div>
|
|
261
|
+
<div id="model-label" style="font-size:11px;color:#333;margin-top:2px;font-family:'JetBrains Mono',monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></div>
|
|
262
|
+
</div>
|
|
263
|
+
</aside>
|
|
264
|
+
|
|
265
|
+
<!-- ── MAIN ── -->
|
|
266
|
+
<div id="main">
|
|
267
|
+
|
|
268
|
+
<!-- Header -->
|
|
269
|
+
<header class="chat-header">
|
|
270
|
+
<div class="chat-header-left">
|
|
271
|
+
<button class="sidebar-toggle" id="sidebar-toggle" title="Toggle sidebar">
|
|
272
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="4" width="12" height="1.3" rx=".65" fill="currentColor"/><rect x="2" y="7.35" width="12" height="1.3" rx=".65" fill="currentColor"/><rect x="2" y="10.7" width="12" height="1.3" rx=".65" fill="currentColor"/></svg>
|
|
273
|
+
</button>
|
|
274
|
+
<span style="font-size:13px;color:#555;font-weight:500" id="active-project-label">General</span>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="chat-header-right">
|
|
277
|
+
<div class="header-status">
|
|
278
|
+
<span class="dot" id="ws-dot"></span>
|
|
279
|
+
<span id="ws-label" style="font-size:12px;color:#444">Offline</span>
|
|
280
|
+
</div>
|
|
281
|
+
<button class="hdr-btn" id="clear-btn">Clear</button>
|
|
282
|
+
<button class="hdr-btn" id="terminal-btn">
|
|
283
|
+
<span style="display:inline-flex;align-items:center;gap:5px">
|
|
284
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M1.5 2.5L5 6L1.5 9.5M6.5 9.5H10.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
285
|
+
Terminal
|
|
286
|
+
</span>
|
|
287
|
+
</button>
|
|
288
|
+
</div>
|
|
289
|
+
</header>
|
|
290
|
+
|
|
291
|
+
<!-- Messages -->
|
|
292
|
+
<main id="messages">
|
|
293
|
+
<!-- Banner -->
|
|
294
|
+
<div class="hc-banner">
|
|
295
|
+
<div class="hc-banner-text">HYPERCLAW</div>
|
|
296
|
+
<div class="hc-banner-sub" id="banner-sub">HyperClaw Bot · AI Gateway v5.3.45</div>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="welcome" id="welcome">
|
|
299
|
+
<div class="welcome-eagle">🦅</div>
|
|
300
|
+
<div class="welcome-title">What can I help with?</div>
|
|
301
|
+
<p class="welcome-sub">Your personal AI agent — running on your hardware. Pick a suggestion or type anything.</p>
|
|
302
|
+
<div class="welcome-chips">
|
|
303
|
+
<button class="chip" onclick="fillInput(this)">What can you do?</button>
|
|
304
|
+
<button class="chip" onclick="fillInput(this)">Search the web for AI news</button>
|
|
305
|
+
<button class="chip" onclick="fillInput(this)">Help me write a script</button>
|
|
306
|
+
<button class="chip" onclick="fillInput(this)">Show system status</button>
|
|
307
|
+
<button class="chip" onclick="fillInput(this)">Run a security audit</button>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</main>
|
|
311
|
+
|
|
312
|
+
<!-- Input -->
|
|
313
|
+
<div class="input-wrap">
|
|
314
|
+
<form class="input-row" id="form">
|
|
315
|
+
<textarea id="input" rows="1" placeholder="Ask HyperClaw anything… Enter to send, Shift+Enter for new line" autocomplete="off"></textarea>
|
|
316
|
+
<button type="submit" id="send-btn">
|
|
317
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 12V4M4 8l4-4 4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
318
|
+
</button>
|
|
319
|
+
</form>
|
|
320
|
+
<div class="input-hint">🦅 HyperClaw · <span id="model-hint">—</span> · Shift+Enter for new line</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<!-- Terminal panel -->
|
|
324
|
+
<div id="terminal-panel">
|
|
325
|
+
<div class="term-titlebar">
|
|
326
|
+
<div class="term-dots">
|
|
327
|
+
<div class="term-dot" style="background:#ff5f57"></div>
|
|
328
|
+
<div class="term-dot" style="background:#febc2e"></div>
|
|
329
|
+
<div class="term-dot" style="background:#28c840"></div>
|
|
330
|
+
</div>
|
|
331
|
+
<span class="term-info" id="term-prompt-info">Terminal — HyperClaw gateway</span>
|
|
332
|
+
<div class="term-titlebar-right">
|
|
333
|
+
<button class="term-clear-btn" id="term-clear-btn">clear</button>
|
|
334
|
+
<button class="term-close-btn" id="term-close-btn" title="Close terminal">
|
|
335
|
+
<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M1.5 1.5l10 10M11.5 1.5l-10 10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
|
336
|
+
</button>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="term-quick">
|
|
340
|
+
<button class="term-quick-btn" data-cmd="npm install">npm install</button>
|
|
341
|
+
<button class="term-quick-btn" data-cmd="npm run build">npm run build</button>
|
|
342
|
+
<button class="term-quick-btn" data-cmd="npm test">npm test</button>
|
|
343
|
+
<button class="term-quick-btn" data-cmd="npx hyperclaw doctor --fix">doctor --fix</button>
|
|
344
|
+
<button class="term-quick-btn" data-cmd="npx hyperclaw gateway status">gateway status</button>
|
|
345
|
+
</div>
|
|
346
|
+
<div id="terminal-log"><span style="color:#333">Use the quick buttons above or type a command below.</span></div>
|
|
347
|
+
<div class="term-input-row">
|
|
348
|
+
<span class="term-prompt">›</span>
|
|
349
|
+
<input type="text" id="terminal-input" placeholder="Run a command…">
|
|
350
|
+
<div class="term-spinner" id="term-spinner"></div>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<!-- ── CUSTOMIZE MODAL ── -->
|
|
357
|
+
<div id="customize-modal">
|
|
358
|
+
<div class="modal-box">
|
|
359
|
+
<div class="modal-title">✦ New Project / Agent</div>
|
|
360
|
+
<label class="modal-label">Project name</label>
|
|
361
|
+
<input type="text" class="modal-input" id="modal-name" placeholder="e.g. Bug Hunter">
|
|
362
|
+
<label class="modal-label">System prompt</label>
|
|
363
|
+
<textarea class="modal-input modal-textarea" id="modal-prompt" placeholder="e.g. You are a security researcher. Focus on vulnerabilities and exploits."></textarea>
|
|
364
|
+
<label class="modal-label">Color</label>
|
|
365
|
+
<input type="color" class="modal-input" id="modal-color" value="#6b7280" style="height:36px;padding:2px 6px;cursor:pointer">
|
|
366
|
+
<div class="modal-row">
|
|
367
|
+
<button class="modal-btn modal-btn-cancel" id="modal-cancel">Cancel</button>
|
|
368
|
+
<button class="modal-btn modal-btn-save" id="modal-save">Create project</button>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<script>
|
|
374
|
+
const port = location.port || 18789;
|
|
375
|
+
const wsUrl = `ws://${location.hostname}:${port}`;
|
|
376
|
+
const apiUrl = `http://${location.hostname}:${port}`;
|
|
377
|
+
|
|
378
|
+
let ws = null, isStreaming = false, currentBubble = null;
|
|
379
|
+
let activePreset = '';
|
|
380
|
+
let customProjects = JSON.parse(localStorage.getItem('hc_projects') || '[]');
|
|
381
|
+
let chatHistory = JSON.parse(localStorage.getItem('hc_history') || '[]');
|
|
382
|
+
let currentChatId = Date.now().toString();
|
|
383
|
+
let currentChatMessages = [];
|
|
384
|
+
|
|
385
|
+
const messagesEl = document.getElementById('messages');
|
|
386
|
+
const welcomeEl = document.getElementById('welcome');
|
|
387
|
+
const input = document.getElementById('input');
|
|
388
|
+
const sendBtn = document.getElementById('send-btn');
|
|
389
|
+
const form = document.getElementById('form');
|
|
390
|
+
|
|
391
|
+
const PROMPT_PREFIX = {
|
|
392
|
+
'ethical-hacker': 'Act as an ethical hacker / security researcher. Authorized testing only. ',
|
|
393
|
+
'hyperclaw': 'You are helping with HyperClaw development. Be concise and code-focused. ',
|
|
394
|
+
'osint': 'Act as an OSINT analyst. Passive reconnaissance, open-source research. '
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// ── Sidebar toggle ───────────────────────────────────
|
|
398
|
+
document.getElementById('sidebar-toggle').onclick = () => {
|
|
399
|
+
document.getElementById('sidebar').classList.toggle('collapsed');
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// ── Project switching ────────────────────────────────
|
|
403
|
+
function setActiveProject(preset, name, dotColor) {
|
|
404
|
+
activePreset = preset;
|
|
405
|
+
document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
|
|
406
|
+
document.getElementById('active-project-label').textContent = name || 'General';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
document.querySelectorAll('.sidebar-project[data-preset]').forEach(btn => {
|
|
410
|
+
btn.onclick = () => {
|
|
411
|
+
const names = { '': 'General', 'ethical-hacker': 'Ethical Hacker', 'hyperclaw': 'HyperClaw Dev', 'osint': 'OSINT' };
|
|
412
|
+
document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
|
|
413
|
+
btn.classList.add('active');
|
|
414
|
+
setActiveProject(btn.dataset.preset, names[btn.dataset.preset]);
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// ── Chat history ─────────────────────────────────────
|
|
419
|
+
function saveCurrentChat(firstMsg) {
|
|
420
|
+
if (!firstMsg) return;
|
|
421
|
+
const existing = chatHistory.find(c => c.id === currentChatId);
|
|
422
|
+
if (existing) { existing.title = firstMsg.slice(0, 40); }
|
|
423
|
+
else { chatHistory.unshift({ id: currentChatId, title: firstMsg.slice(0, 40), ts: Date.now() }); }
|
|
424
|
+
if (chatHistory.length > 30) chatHistory = chatHistory.slice(0, 30);
|
|
425
|
+
localStorage.setItem('hc_history', JSON.stringify(chatHistory));
|
|
426
|
+
renderHistory();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function renderHistory(filter) {
|
|
430
|
+
const el = document.getElementById('chat-history');
|
|
431
|
+
const items = filter
|
|
432
|
+
? chatHistory.filter(c => c.title.toLowerCase().includes(filter.toLowerCase()))
|
|
433
|
+
: chatHistory;
|
|
434
|
+
if (!items.length) { el.innerHTML = '<div style="padding:6px 22px;font-size:11px;color:#3a3a3a">' + (filter ? 'No results' : 'No recent chats') + '</div>'; return; }
|
|
435
|
+
el.innerHTML = items.map(c => `<button class="sidebar-chat-item" onclick="loadChat('${c.id}')" title="${escHtml(c.title)}">💬 ${escHtml(c.title)}</button>`).join('');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function loadChat(id) {
|
|
439
|
+
// For now just label it — full persistence would require storing messages
|
|
440
|
+
const chat = chatHistory.find(c => c.id === id);
|
|
441
|
+
if (chat) document.getElementById('active-project-label').textContent = chat.title.slice(0,20) + '…';
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
renderHistory();
|
|
445
|
+
|
|
446
|
+
// Search chats
|
|
447
|
+
document.getElementById('search-input').oninput = function() { renderHistory(this.value); };
|
|
448
|
+
|
|
449
|
+
// ── New chat ─────────────────────────────────────────
|
|
450
|
+
document.getElementById('new-chat-btn').onclick = startNewChat;
|
|
451
|
+
document.getElementById('clear-btn').onclick = () => { clearMessages(); };
|
|
452
|
+
|
|
453
|
+
function startNewChat() {
|
|
454
|
+
currentChatId = Date.now().toString();
|
|
455
|
+
currentChatMessages = [];
|
|
456
|
+
clearMessages();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function clearMessages() {
|
|
460
|
+
messagesEl.querySelectorAll('.msg-row').forEach(e => e.remove());
|
|
461
|
+
welcomeEl.style.display = 'flex';
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ── Customize modal ──────────────────────────────────
|
|
465
|
+
document.getElementById('customize-btn').onclick = () => {
|
|
466
|
+
document.getElementById('customize-modal').classList.add('open');
|
|
467
|
+
document.getElementById('modal-name').focus();
|
|
468
|
+
};
|
|
469
|
+
document.getElementById('new-project-btn').onclick = () => {
|
|
470
|
+
document.getElementById('customize-modal').classList.add('open');
|
|
471
|
+
document.getElementById('modal-name').focus();
|
|
472
|
+
};
|
|
473
|
+
document.getElementById('modal-cancel').onclick = () => document.getElementById('customize-modal').classList.remove('open');
|
|
474
|
+
document.getElementById('customize-modal').onclick = (e) => { if (e.target === e.currentTarget) e.currentTarget.classList.remove('open'); };
|
|
475
|
+
|
|
476
|
+
document.getElementById('modal-save').onclick = () => {
|
|
477
|
+
const name = document.getElementById('modal-name').value.trim();
|
|
478
|
+
const prompt = document.getElementById('modal-prompt').value.trim();
|
|
479
|
+
const color = document.getElementById('modal-color').value;
|
|
480
|
+
if (!name) return;
|
|
481
|
+
const id = 'custom-' + Date.now();
|
|
482
|
+
customProjects.push({ id, name, prompt, color });
|
|
483
|
+
localStorage.setItem('hc_projects', JSON.stringify(customProjects));
|
|
484
|
+
PROMPT_PREFIX[id] = prompt + ' ';
|
|
485
|
+
addProjectToSidebar({ id, name, color });
|
|
486
|
+
document.getElementById('customize-modal').classList.remove('open');
|
|
487
|
+
document.getElementById('modal-name').value = '';
|
|
488
|
+
document.getElementById('modal-prompt').value = '';
|
|
489
|
+
document.getElementById('modal-color').value = '#6b7280';
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
function addProjectToSidebar(proj) {
|
|
493
|
+
const btn = document.createElement('button');
|
|
494
|
+
btn.className = 'sidebar-project';
|
|
495
|
+
btn.dataset.preset = proj.id;
|
|
496
|
+
btn.innerHTML = `<span class="sidebar-project-dot" style="background:${proj.color}"></span>${escHtml(proj.name)}`;
|
|
497
|
+
btn.onclick = () => {
|
|
498
|
+
document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
|
|
499
|
+
btn.classList.add('active');
|
|
500
|
+
setActiveProject(proj.id, proj.name);
|
|
501
|
+
};
|
|
502
|
+
document.getElementById('new-project-btn').before(btn);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Restore custom projects
|
|
506
|
+
customProjects.forEach(proj => {
|
|
507
|
+
PROMPT_PREFIX[proj.id] = proj.prompt + ' ';
|
|
508
|
+
addProjectToSidebar(proj);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// ── Terminal ─────────────────────────────────────────
|
|
512
|
+
const terminalPanel = document.getElementById('terminal-panel');
|
|
513
|
+
const terminalBtn = document.getElementById('terminal-btn');
|
|
514
|
+
const terminalLog = document.getElementById('terminal-log');
|
|
515
|
+
const terminalInput = document.getElementById('terminal-input');
|
|
516
|
+
const termSpinner = document.getElementById('term-spinner');
|
|
517
|
+
let termRunning = false;
|
|
518
|
+
|
|
519
|
+
terminalBtn.onclick = toggleTerminal;
|
|
520
|
+
document.getElementById('term-close-btn').onclick = () => {
|
|
521
|
+
terminalPanel.classList.remove('open');
|
|
522
|
+
terminalBtn.classList.remove('active');
|
|
523
|
+
};
|
|
524
|
+
document.getElementById('term-clear-btn').onclick = () => {
|
|
525
|
+
terminalLog.innerHTML = '<span style="color:#333">Terminal cleared.</span>';
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
function toggleTerminal() {
|
|
529
|
+
const isOpen = terminalPanel.classList.toggle('open');
|
|
530
|
+
terminalBtn.classList.toggle('active', isOpen);
|
|
531
|
+
if (isOpen) setTimeout(() => terminalInput.focus(), 50);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async function runTerminalCmd(cmd) {
|
|
535
|
+
cmd = (cmd || '').trim();
|
|
536
|
+
if (!cmd || termRunning) return;
|
|
537
|
+
termRunning = true;
|
|
538
|
+
termSpinner.style.display = 'block';
|
|
539
|
+
document.querySelectorAll('.term-quick-btn').forEach(b => b.disabled = true);
|
|
540
|
+
|
|
541
|
+
const cmdLine = document.createElement('div');
|
|
542
|
+
cmdLine.className = 'term-log-cmd';
|
|
543
|
+
cmdLine.textContent = '$ ' + cmd;
|
|
544
|
+
if (terminalLog.querySelector('span')) terminalLog.innerHTML = '';
|
|
545
|
+
terminalLog.appendChild(cmdLine);
|
|
546
|
+
terminalLog.scrollTop = terminalLog.scrollHeight;
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const res = await fetch(apiUrl + '/api/terminal', {
|
|
550
|
+
method: 'POST', headers: {'Content-Type': 'application/json'},
|
|
551
|
+
body: JSON.stringify({command: cmd})
|
|
552
|
+
});
|
|
553
|
+
const data = await res.json();
|
|
554
|
+
if (data.user || data.cwd) {
|
|
555
|
+
document.getElementById('term-prompt-info').textContent = `${data.user || 'user'}@${data.hostname || 'local'}:${data.cwd || ''}`;
|
|
556
|
+
}
|
|
557
|
+
if (data.stdout) {
|
|
558
|
+
const out = document.createElement('div');
|
|
559
|
+
out.textContent = data.stdout.trimEnd();
|
|
560
|
+
terminalLog.appendChild(out);
|
|
561
|
+
}
|
|
562
|
+
if (data.stderr) {
|
|
563
|
+
const err = document.createElement('div');
|
|
564
|
+
err.className = 'term-log-err';
|
|
565
|
+
err.textContent = data.stderr.trimEnd();
|
|
566
|
+
terminalLog.appendChild(err);
|
|
567
|
+
}
|
|
568
|
+
if (!data.stdout && !data.stderr) {
|
|
569
|
+
const ex = document.createElement('div');
|
|
570
|
+
ex.style.color = '#444';
|
|
571
|
+
ex.textContent = '(exit ' + (data.code ?? 0) + ')';
|
|
572
|
+
terminalLog.appendChild(ex);
|
|
573
|
+
}
|
|
574
|
+
} catch (e) {
|
|
575
|
+
const err = document.createElement('div');
|
|
576
|
+
err.className = 'term-log-err';
|
|
577
|
+
err.textContent = 'Error: ' + (e.message || String(e));
|
|
578
|
+
terminalLog.appendChild(err);
|
|
579
|
+
} finally {
|
|
580
|
+
termRunning = false;
|
|
581
|
+
termSpinner.style.display = 'none';
|
|
582
|
+
document.querySelectorAll('.term-quick-btn').forEach(b => b.disabled = false);
|
|
583
|
+
terminalLog.scrollTop = terminalLog.scrollHeight;
|
|
584
|
+
terminalInput.focus();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
document.querySelectorAll('.term-quick-btn').forEach(btn => {
|
|
589
|
+
btn.onclick = () => runTerminalCmd(btn.dataset.cmd);
|
|
590
|
+
});
|
|
591
|
+
terminalInput.onkeydown = (e) => {
|
|
592
|
+
if (e.key === 'Enter') { runTerminalCmd(terminalInput.value); terminalInput.value = ''; }
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// ── WebSocket ────────────────────────────────────────
|
|
596
|
+
function setConnected(ok) {
|
|
597
|
+
[document.getElementById('ws-dot'), document.getElementById('ws-dot-side')].forEach(d => {
|
|
598
|
+
if (d) d.className = 'dot' + (ok ? ' connected' : '');
|
|
599
|
+
});
|
|
600
|
+
const label = ok ? 'Connected' : 'Reconnecting…';
|
|
601
|
+
const color = ok ? '#22c55e' : '#444';
|
|
602
|
+
[document.getElementById('ws-label'), document.getElementById('ws-label-side')].forEach(el => {
|
|
603
|
+
if (el) { el.textContent = label; el.style.color = color; }
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function connectWs() {
|
|
608
|
+
ws = new WebSocket(wsUrl);
|
|
609
|
+
ws.onopen = () => setConnected(true);
|
|
610
|
+
ws.onclose = () => { setConnected(false); setTimeout(connectWs, 3000); };
|
|
611
|
+
ws.onerror = () => setConnected(false);
|
|
612
|
+
ws.onmessage = (e) => {
|
|
613
|
+
try {
|
|
614
|
+
const msg = JSON.parse(e.data);
|
|
615
|
+
if (msg.type === 'chat:chunk') {
|
|
616
|
+
if (!currentBubble) { removeTyping(); currentBubble = addMsg('assistant', '', true); }
|
|
617
|
+
currentBubble._raw = (currentBubble._raw || '') + (msg.content || '');
|
|
618
|
+
currentBubble.textContent = currentBubble._raw;
|
|
619
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
620
|
+
} else if (msg.type === 'chat:response') {
|
|
621
|
+
removeTyping();
|
|
622
|
+
if (currentBubble) {
|
|
623
|
+
currentBubble.innerHTML = '<div class="prose">' + marked.parse(currentBubble._raw || msg.content || '') + '</div>';
|
|
624
|
+
currentBubble = null;
|
|
625
|
+
} else {
|
|
626
|
+
addMsg('assistant', msg.content || '');
|
|
627
|
+
}
|
|
628
|
+
addTimestamp();
|
|
629
|
+
finishStreaming();
|
|
630
|
+
} else if (msg.type === 'error') {
|
|
631
|
+
removeTyping();
|
|
632
|
+
addMsg('assistant', '⚠️ ' + (msg.message || 'Unknown error'));
|
|
633
|
+
currentBubble = null;
|
|
634
|
+
finishStreaming();
|
|
635
|
+
}
|
|
636
|
+
} catch (_) {}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function finishStreaming() {
|
|
641
|
+
isStreaming = false;
|
|
642
|
+
sendBtn.disabled = false;
|
|
643
|
+
currentBubble = null;
|
|
644
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ── Messages ─────────────────────────────────────────
|
|
648
|
+
function now() { return new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}); }
|
|
649
|
+
|
|
650
|
+
function addMsg(role, content, streaming = false) {
|
|
651
|
+
welcomeEl.style.display = 'none';
|
|
652
|
+
const row = document.createElement('div');
|
|
653
|
+
row.className = 'msg-row ' + role;
|
|
654
|
+
const avatar = document.createElement('div');
|
|
655
|
+
avatar.className = 'msg-avatar';
|
|
656
|
+
avatar.textContent = role === 'user' ? '👤' : '🦅';
|
|
657
|
+
const bubble = document.createElement('div');
|
|
658
|
+
bubble.className = 'msg-bubble';
|
|
659
|
+
if (!streaming && content) {
|
|
660
|
+
bubble.innerHTML = '<div class="prose">' + (role === 'assistant' ? marked.parse(content) : escHtml(content)) + '</div>';
|
|
661
|
+
} else {
|
|
662
|
+
bubble.textContent = content || '';
|
|
663
|
+
}
|
|
664
|
+
row.appendChild(avatar);
|
|
665
|
+
row.appendChild(bubble);
|
|
666
|
+
messagesEl.appendChild(row);
|
|
667
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
668
|
+
return bubble;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function addTimestamp() {
|
|
672
|
+
const last = messagesEl.querySelector('.msg-row.assistant:last-child');
|
|
673
|
+
if (last) {
|
|
674
|
+
const t = document.createElement('div');
|
|
675
|
+
t.className = 'msg-time';
|
|
676
|
+
t.textContent = now();
|
|
677
|
+
last.appendChild(t);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
let workingTicker = null;
|
|
682
|
+
function addTyping() {
|
|
683
|
+
welcomeEl.style.display = 'none';
|
|
684
|
+
const row = document.createElement('div');
|
|
685
|
+
row.className = 'msg-row assistant';
|
|
686
|
+
row.id = 'typing-row';
|
|
687
|
+
let sec = 0;
|
|
688
|
+
row.innerHTML = '<div class="msg-avatar">🦅</div><div class="msg-bubble" style="padding:12px 16px"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span><span id="wt" style="margin-left:8px;color:#444;font-size:11px">Thinking…</span></div>';
|
|
689
|
+
messagesEl.appendChild(row);
|
|
690
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
691
|
+
workingTicker = setInterval(() => { sec++; const el = document.getElementById('wt'); if (el) el.textContent = sec + 's'; }, 1000);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function removeTyping() {
|
|
695
|
+
if (workingTicker) { clearInterval(workingTicker); workingTicker = null; }
|
|
696
|
+
document.getElementById('typing-row')?.remove();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
700
|
+
|
|
701
|
+
// ── Send ─────────────────────────────────────────────
|
|
702
|
+
let msgCount = 0;
|
|
703
|
+
form.onsubmit = async (e) => {
|
|
704
|
+
e.preventDefault();
|
|
705
|
+
const msg = input.value.trim();
|
|
706
|
+
if (!msg || isStreaming) return;
|
|
707
|
+
const prefix = PROMPT_PREFIX[activePreset] || '';
|
|
708
|
+
const fullMsg = prefix ? prefix + msg : msg;
|
|
709
|
+
if (msgCount === 0) saveCurrentChat(msg);
|
|
710
|
+
msgCount++;
|
|
711
|
+
addMsg('user', msg);
|
|
712
|
+
input.value = '';
|
|
713
|
+
input.style.height = 'auto';
|
|
714
|
+
sendBtn.disabled = true;
|
|
715
|
+
isStreaming = true;
|
|
716
|
+
addTyping();
|
|
717
|
+
|
|
718
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
719
|
+
ws.send(JSON.stringify({ type: 'chat:message', content: fullMsg, source: 'webchat' }));
|
|
720
|
+
} else {
|
|
721
|
+
try {
|
|
722
|
+
const res = await fetch(apiUrl + '/api/chat', {
|
|
723
|
+
method: 'POST', headers: {'Content-Type':'application/json'},
|
|
724
|
+
body: JSON.stringify({message: fullMsg})
|
|
725
|
+
});
|
|
726
|
+
const data = await res.json();
|
|
727
|
+
removeTyping();
|
|
728
|
+
addMsg('assistant', data.response || data.error || '(no response)');
|
|
729
|
+
addTimestamp();
|
|
730
|
+
} catch (err) {
|
|
731
|
+
removeTyping();
|
|
732
|
+
addMsg('assistant', '⚠️ Could not reach gateway: ' + err.message);
|
|
733
|
+
}
|
|
734
|
+
finishStreaming();
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// ── Chips ────────────────────────────────────────────
|
|
739
|
+
function fillInput(el) { input.value = el.textContent; input.focus(); autoResize(); }
|
|
740
|
+
|
|
741
|
+
// ── Textarea auto-resize ──────────────────────────────
|
|
742
|
+
function autoResize() {
|
|
743
|
+
input.style.height = 'auto';
|
|
744
|
+
input.style.height = Math.min(input.scrollHeight, 130) + 'px';
|
|
745
|
+
}
|
|
746
|
+
input.addEventListener('input', autoResize);
|
|
747
|
+
input.addEventListener('keydown', (e) => {
|
|
748
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.dispatchEvent(new Event('submit')); }
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// ── Init ─────────────────────────────────────────────
|
|
752
|
+
connectWs();
|
|
753
|
+
fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
|
|
754
|
+
if (d.model) {
|
|
755
|
+
document.getElementById('model-hint').textContent = d.model;
|
|
756
|
+
document.getElementById('model-label').textContent = d.model;
|
|
757
|
+
const ver = d.version || '5.3.45';
|
|
758
|
+
document.getElementById('banner-sub').textContent = `HyperClaw Bot \u00b7 AI Gateway v${ver}`;
|
|
759
|
+
}
|
|
760
|
+
document.documentElement.setAttribute('data-daemon', d.daemonMode ? 'true' : 'false');
|
|
761
|
+
}).catch(() => {});
|
|
762
|
+
</script>
|
|
763
|
+
</body>
|
|
764
|
+
</html>
|