hyperclaw 5.3.2 → 5.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/banner-BT9llPVP.js +7 -0
- package/dist/banner-DNSYi9N-.js +143 -0
- package/dist/chat-CKLXvJjd.js +545 -0
- package/dist/daemon-Bi0rcsh5.js +421 -0
- package/dist/daemon-DnWIgRUn.js +7 -0
- package/dist/engine-BZ44Vuhh.js +7 -0
- package/dist/engine-C1osvhmq.js +327 -0
- package/dist/hyperclawbot-DC3jGsoA.js +516 -0
- package/dist/mcp-loader-BBlqPFQl.js +93 -0
- package/dist/onboard-DRgSPOuY.js +3812 -0
- package/dist/onboard-qPUG8PBU.js +14 -0
- package/dist/orchestrator-BURpEF0U.js +6 -0
- package/dist/orchestrator-CnEDItE1.js +189 -0
- package/dist/osint-BiZZ_d-g.js +283 -0
- package/dist/osint-chat-DaRWDl3X.js +789 -0
- package/dist/run-main.js +87 -72
- package/dist/server-DBXhcGiq.js +4 -0
- package/dist/server-O4Zv8s74.js +1366 -0
- package/dist/skill-runtime-IFk6whV0.js +104 -0
- package/dist/skill-runtime-vylCVHmD.js +5 -0
- package/dist/src-BA04v4CG.js +458 -0
- package/dist/src-BWpeRe8J.js +63 -0
- package/dist/sub-agent-tools-Dzh0nVzy.js +39 -0
- package/package.json +1 -1
- package/static/chat.html +570 -259
package/static/chat.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en"
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
@@ -8,184 +8,364 @@
|
|
|
8
8
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
9
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
10
|
<style>
|
|
11
|
+
/* ── ACCENT THEME VARS ── */
|
|
11
12
|
:root {
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--accent: #00c2ff;
|
|
17
|
-
--accent2: #0077b6;
|
|
18
|
-
--text: #e2eaf8;
|
|
19
|
-
--text2: #8ba0c0;
|
|
20
|
-
--user-bg: linear-gradient(135deg, #0077b6, #00c2ff);
|
|
21
|
-
--asst-bg: #111d33;
|
|
22
|
-
--header-bg: rgba(9,14,26,0.95);
|
|
23
|
-
--shadow: 0 4px 24px rgba(0,194,255,0.08);
|
|
13
|
+
--accent: #22d3ee;
|
|
14
|
+
--accent2: #06b6d4;
|
|
15
|
+
--accent-glow: rgba(34,211,238,0.3);
|
|
16
|
+
--accent-bg: rgba(34,211,238,0.08);
|
|
24
17
|
}
|
|
25
18
|
[data-theme="light"] {
|
|
26
|
-
--
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
--
|
|
30
|
-
--accent: #0077b6;
|
|
31
|
-
--accent2: #005f99;
|
|
32
|
-
--text: #0d1d3a;
|
|
33
|
-
--text2: #3a5580;
|
|
34
|
-
--user-bg: linear-gradient(135deg, #0077b6, #00aee0);
|
|
35
|
-
--asst-bg: #ffffff;
|
|
36
|
-
--header-bg: rgba(238,244,255,0.97);
|
|
37
|
-
--shadow: 0 4px 24px rgba(0,119,182,0.10);
|
|
19
|
+
--accent: #0891b2;
|
|
20
|
+
--accent2: #0e7490;
|
|
21
|
+
--accent-glow: rgba(8,145,178,0.25);
|
|
22
|
+
--accent-bg: rgba(8,145,178,0.06);
|
|
38
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
|
+
|
|
39
37
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
40
|
-
body { background:
|
|
41
|
-
|
|
42
|
-
/* ──
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.
|
|
62
|
-
|
|
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} }
|
|
63
104
|
.msg-row.user { flex-direction: row-reverse; align-self: flex-end; }
|
|
64
105
|
.msg-row.assistant { align-self: flex-start; }
|
|
65
|
-
.msg-avatar { width:
|
|
66
|
-
.msg-bubble { max-width: min(
|
|
67
|
-
.msg-row.user .msg-bubble { background:
|
|
68
|
-
.msg-row.assistant .msg-bubble { background:
|
|
69
|
-
.msg-bubble .prose h1,.msg-bubble .prose h2,.msg-bubble .prose h3 { color:
|
|
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; }
|
|
70
111
|
.msg-bubble .prose p { margin: 4px 0; }
|
|
71
|
-
.msg-bubble .prose pre { background:
|
|
72
|
-
.msg-bubble .prose code { background:
|
|
73
|
-
.msg-bubble .prose pre code { background: none; padding: 0; color:
|
|
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; }
|
|
74
115
|
.msg-bubble .prose ul,.msg-bubble .prose ol { padding-left: 18px; margin: 4px 0; }
|
|
75
|
-
.msg-bubble .prose a { color:
|
|
76
|
-
.msg-time { font-size: 10px; color:
|
|
77
|
-
.msg-row.
|
|
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; }
|
|
78
119
|
|
|
79
|
-
/* ── TYPING
|
|
80
|
-
.typing-dot { display: inline-block; width:
|
|
81
|
-
.typing-dot:nth-child(2){animation-delay:.
|
|
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}
|
|
82
123
|
@keyframes blink{0%,80%,100%{opacity:.2}40%{opacity:1}}
|
|
83
124
|
|
|
84
|
-
/* ──
|
|
85
|
-
.
|
|
86
|
-
.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
/* ── WELCOME ─── */
|
|
96
|
-
.welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; gap: 12px; text-align: center; padding: 32px; }
|
|
97
|
-
.welcome-logo { font-family: 'JetBrains Mono', monospace; font-size: clamp(32px, 8vw, 64px); font-weight: 700; letter-spacing: 4px; background: linear-gradient(135deg, #7dd4fc, var(--accent), #0077b6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; }
|
|
98
|
-
.welcome-sub { font-size: 14px; color: var(--text2); max-width: 380px; line-height: 1.6; }
|
|
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; }
|
|
99
135
|
.welcome-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 4px; }
|
|
100
|
-
.chip { background:
|
|
101
|
-
.chip:hover { border-color:
|
|
102
|
-
|
|
103
|
-
/* ──
|
|
104
|
-
.
|
|
105
|
-
.
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
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; }
|
|
118
192
|
</style>
|
|
119
193
|
</head>
|
|
120
194
|
<body>
|
|
121
195
|
|
|
122
|
-
<!--
|
|
123
|
-
<
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
<div class="
|
|
127
|
-
|
|
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>
|
|
128
216
|
</div>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<
|
|
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>
|
|
133
247
|
</div>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<option value="osint">OSINT</option>
|
|
140
|
-
</select>
|
|
141
|
-
<button class="btn-icon" id="new-chat-btn" title="New chat">💬</button>
|
|
142
|
-
<button class="btn-icon" id="clear-btn" title="Clear messages">🗑️</button>
|
|
143
|
-
<button class="btn-icon" id="theme-btn" title="Toggle theme">🌙</button>
|
|
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>
|
|
144
253
|
</div>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<p class="welcome-sub">Your personal AI assistant — running on your hardware. Ask anything or pick a suggestion:</p>
|
|
152
|
-
<div class="welcome-chips">
|
|
153
|
-
<span class="chip" onclick="fillInput(this)">What can you do?</span>
|
|
154
|
-
<span class="chip" onclick="fillInput(this)">What's the weather today?</span>
|
|
155
|
-
<span class="chip" onclick="fillInput(this)">Search the web for AI news</span>
|
|
156
|
-
<span class="chip" onclick="fillInput(this)">Help me write a script</span>
|
|
157
|
-
<span class="chip" onclick="fillInput(this)">Show system status</span>
|
|
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>
|
|
158
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.3</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>
|
|
159
321
|
</div>
|
|
160
|
-
</main>
|
|
161
|
-
|
|
162
|
-
<!-- INPUT -->
|
|
163
|
-
<div class="input-wrap">
|
|
164
|
-
<form class="input-row" id="form">
|
|
165
|
-
<textarea id="input" rows="1" placeholder="Your AI assistant awaits — type a message and press Enter to send · HyperClaw" autocomplete="off"></textarea>
|
|
166
|
-
<button type="submit" id="send-btn">↑</button>
|
|
167
|
-
</form>
|
|
168
|
-
<div class="input-hint">🦅 HyperClaw · <span id="model-hint">—</span></div>
|
|
169
|
-
</div>
|
|
170
322
|
|
|
171
|
-
<!--
|
|
172
|
-
<div
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
<
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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>
|
|
184
338
|
</div>
|
|
185
|
-
<div class="
|
|
186
|
-
|
|
187
|
-
<
|
|
188
|
-
<button
|
|
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>
|
|
189
369
|
</div>
|
|
190
370
|
</div>
|
|
191
371
|
</div>
|
|
@@ -196,70 +376,244 @@
|
|
|
196
376
|
const apiUrl = `http://${location.hostname}:${port}`;
|
|
197
377
|
|
|
198
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
|
+
|
|
199
385
|
const messagesEl = document.getElementById('messages');
|
|
200
386
|
const welcomeEl = document.getElementById('welcome');
|
|
201
|
-
const wsDot = document.getElementById('ws-dot');
|
|
202
|
-
const wsLabel = document.getElementById('ws-label');
|
|
203
|
-
const agentSub = document.getElementById('agent-sub');
|
|
204
|
-
const modelHint = document.getElementById('model-hint');
|
|
205
387
|
const input = document.getElementById('input');
|
|
206
388
|
const sendBtn = document.getElementById('send-btn');
|
|
207
389
|
const form = document.getElementById('form');
|
|
208
390
|
|
|
209
|
-
// ── Theme ────────────────────────────────────────────
|
|
210
|
-
const themeBtn = document.getElementById('theme-btn');
|
|
211
|
-
let dark = true;
|
|
212
|
-
themeBtn.onclick = () => {
|
|
213
|
-
dark = !dark;
|
|
214
|
-
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
|
215
|
-
themeBtn.textContent = dark ? '🌙' : '☀️';
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// ── New chat / Clear ─────────────────────────────────
|
|
219
|
-
const promptSelect = document.getElementById('prompt-select');
|
|
220
391
|
const PROMPT_PREFIX = {
|
|
221
392
|
'ethical-hacker': 'Act as an ethical hacker / security researcher. Authorized testing only. ',
|
|
222
393
|
'hyperclaw': 'You are helping with HyperClaw development. Be concise and code-focused. ',
|
|
223
394
|
'osint': 'Act as an OSINT analyst. Passive reconnaissance, open-source research. '
|
|
224
395
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
396
|
+
|
|
397
|
+
// ── Sidebar toggle ───────────────────────────────────
|
|
398
|
+
document.getElementById('sidebar-toggle').onclick = () => {
|
|
399
|
+
document.getElementById('sidebar').classList.toggle('collapsed');
|
|
228
400
|
};
|
|
229
|
-
|
|
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() {
|
|
230
460
|
messagesEl.querySelectorAll('.msg-row').forEach(e => e.remove());
|
|
231
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';
|
|
232
490
|
};
|
|
233
491
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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);
|
|
239
503
|
}
|
|
240
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
|
+
|
|
241
595
|
// ── WebSocket ────────────────────────────────────────
|
|
242
596
|
function setConnected(ok) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
});
|
|
246
605
|
}
|
|
247
606
|
|
|
248
607
|
function connectWs() {
|
|
249
608
|
ws = new WebSocket(wsUrl);
|
|
250
|
-
ws.onopen = () =>
|
|
609
|
+
ws.onopen = () => setConnected(true);
|
|
251
610
|
ws.onclose = () => { setConnected(false); setTimeout(connectWs, 3000); };
|
|
252
611
|
ws.onerror = () => setConnected(false);
|
|
253
612
|
ws.onmessage = (e) => {
|
|
254
613
|
try {
|
|
255
614
|
const msg = JSON.parse(e.data);
|
|
256
|
-
if (msg.type === '
|
|
257
|
-
|
|
258
|
-
} else if (msg.type === 'chat:chunk') {
|
|
259
|
-
if (!currentBubble) {
|
|
260
|
-
removeTyping();
|
|
261
|
-
currentBubble = addMsg('assistant', '', true);
|
|
262
|
-
}
|
|
615
|
+
if (msg.type === 'chat:chunk') {
|
|
616
|
+
if (!currentBubble) { removeTyping(); currentBubble = addMsg('assistant', '', true); }
|
|
263
617
|
currentBubble._raw = (currentBubble._raw || '') + (msg.content || '');
|
|
264
618
|
currentBubble.textContent = currentBubble._raw;
|
|
265
619
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
@@ -290,11 +644,8 @@
|
|
|
290
644
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
291
645
|
}
|
|
292
646
|
|
|
293
|
-
// ──
|
|
294
|
-
function now() {
|
|
295
|
-
return new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
|
|
296
|
-
}
|
|
297
|
-
let lastTimestampEl = null;
|
|
647
|
+
// ── Messages ─────────────────────────────────────────
|
|
648
|
+
function now() { return new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}); }
|
|
298
649
|
|
|
299
650
|
function addMsg(role, content, streaming = false) {
|
|
300
651
|
welcomeEl.style.display = 'none';
|
|
@@ -318,11 +669,13 @@
|
|
|
318
669
|
}
|
|
319
670
|
|
|
320
671
|
function addTimestamp() {
|
|
321
|
-
const t = document.createElement('div');
|
|
322
|
-
t.className = 'msg-time';
|
|
323
|
-
t.textContent = now();
|
|
324
672
|
const last = messagesEl.querySelector('.msg-row.assistant:last-child');
|
|
325
|
-
if (last)
|
|
673
|
+
if (last) {
|
|
674
|
+
const t = document.createElement('div');
|
|
675
|
+
t.className = 'msg-time';
|
|
676
|
+
t.textContent = now();
|
|
677
|
+
last.appendChild(t);
|
|
678
|
+
}
|
|
326
679
|
}
|
|
327
680
|
|
|
328
681
|
let workingTicker = null;
|
|
@@ -332,12 +685,10 @@
|
|
|
332
685
|
row.className = 'msg-row assistant';
|
|
333
686
|
row.id = 'typing-row';
|
|
334
687
|
let sec = 0;
|
|
335
|
-
row.innerHTML = '<div class="msg-avatar">🦅</div><div class="msg-bubble" style="padding:
|
|
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>';
|
|
336
689
|
messagesEl.appendChild(row);
|
|
337
690
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
338
|
-
workingTicker = setInterval(() => {
|
|
339
|
-
sec++; const el = document.getElementById('working-text'); if (el) el.textContent = 'Working (' + sec + 's)';
|
|
340
|
-
}, 1000);
|
|
691
|
+
workingTicker = setInterval(() => { sec++; const el = document.getElementById('wt'); if (el) el.textContent = sec + 's'; }, 1000);
|
|
341
692
|
}
|
|
342
693
|
|
|
343
694
|
function removeTyping() {
|
|
@@ -345,16 +696,18 @@
|
|
|
345
696
|
document.getElementById('typing-row')?.remove();
|
|
346
697
|
}
|
|
347
698
|
|
|
348
|
-
function escHtml(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
699
|
+
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
349
700
|
|
|
350
701
|
// ── Send ─────────────────────────────────────────────
|
|
702
|
+
let msgCount = 0;
|
|
351
703
|
form.onsubmit = async (e) => {
|
|
352
704
|
e.preventDefault();
|
|
353
705
|
const msg = input.value.trim();
|
|
354
706
|
if (!msg || isStreaming) return;
|
|
355
|
-
const
|
|
356
|
-
const prefix = PROMPT_PREFIX[preset] || '';
|
|
707
|
+
const prefix = PROMPT_PREFIX[activePreset] || '';
|
|
357
708
|
const fullMsg = prefix ? prefix + msg : msg;
|
|
709
|
+
if (msgCount === 0) saveCurrentChat(msg);
|
|
710
|
+
msgCount++;
|
|
358
711
|
addMsg('user', msg);
|
|
359
712
|
input.value = '';
|
|
360
713
|
input.style.height = 'auto';
|
|
@@ -365,7 +718,6 @@
|
|
|
365
718
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
366
719
|
ws.send(JSON.stringify({ type: 'chat:message', content: fullMsg, source: 'webchat' }));
|
|
367
720
|
} else {
|
|
368
|
-
// REST fallback
|
|
369
721
|
try {
|
|
370
722
|
const res = await fetch(apiUrl + '/api/chat', {
|
|
371
723
|
method: 'POST', headers: {'Content-Type':'application/json'},
|
|
@@ -383,70 +735,29 @@
|
|
|
383
735
|
}
|
|
384
736
|
};
|
|
385
737
|
|
|
738
|
+
// ── Chips ────────────────────────────────────────────
|
|
739
|
+
function fillInput(el) { input.value = el.textContent; input.focus(); autoResize(); }
|
|
740
|
+
|
|
386
741
|
// ── Textarea auto-resize ──────────────────────────────
|
|
387
742
|
function autoResize() {
|
|
388
743
|
input.style.height = 'auto';
|
|
389
|
-
input.style.height = Math.min(input.scrollHeight,
|
|
744
|
+
input.style.height = Math.min(input.scrollHeight, 130) + 'px';
|
|
390
745
|
}
|
|
391
746
|
input.addEventListener('input', autoResize);
|
|
392
747
|
input.addEventListener('keydown', (e) => {
|
|
393
748
|
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.dispatchEvent(new Event('submit')); }
|
|
394
749
|
});
|
|
395
750
|
|
|
396
|
-
// ── Local Terminal ───────────────────────────────────
|
|
397
|
-
const terminalToggle = document.getElementById('terminal-toggle');
|
|
398
|
-
const terminalBody = document.getElementById('terminal-body');
|
|
399
|
-
const terminalLog = document.getElementById('terminal-log');
|
|
400
|
-
const terminalInput = document.getElementById('terminal-input');
|
|
401
|
-
const terminalRun = document.getElementById('terminal-run');
|
|
402
|
-
const toggleLabel = document.getElementById('terminal-toggle-label');
|
|
403
|
-
let terminalRunning = false;
|
|
404
|
-
|
|
405
|
-
terminalToggle.onclick = () => {
|
|
406
|
-
terminalBody.classList.toggle('open', !terminalBody.classList.contains('open'));
|
|
407
|
-
toggleLabel.textContent = terminalBody.classList.contains('open') ? 'Hide' : 'Show';
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
async function runTerminalCmd(cmd) {
|
|
411
|
-
cmd = (cmd || '').trim();
|
|
412
|
-
if (!cmd || terminalRunning) return;
|
|
413
|
-
terminalLog.append('$ ' + cmd + '\n');
|
|
414
|
-
terminalLog.scrollTop = terminalLog.scrollHeight;
|
|
415
|
-
terminalRunning = true;
|
|
416
|
-
terminalRun.disabled = true;
|
|
417
|
-
document.querySelectorAll('.terminal-btn[data-cmd]').forEach(b => b.disabled = true);
|
|
418
|
-
try {
|
|
419
|
-
const res = await fetch(apiUrl + '/api/terminal', {
|
|
420
|
-
method: 'POST', headers: {'Content-Type': 'application/json'},
|
|
421
|
-
body: JSON.stringify({command: cmd})
|
|
422
|
-
});
|
|
423
|
-
const data = await res.json();
|
|
424
|
-
if (data.stdout) terminalLog.append(data.stdout.trimEnd() + '\n');
|
|
425
|
-
if (data.stderr) terminalLog.append('! ' + data.stderr.trimEnd() + '\n');
|
|
426
|
-
terminalLog.append('(exit code ' + (data.code ?? 0) + ')\n');
|
|
427
|
-
} catch (e) {
|
|
428
|
-
terminalLog.append('Error: ' + (e.message || String(e)) + '\n');
|
|
429
|
-
} finally {
|
|
430
|
-
terminalRunning = false;
|
|
431
|
-
terminalRun.disabled = false;
|
|
432
|
-
document.querySelectorAll('.terminal-btn[data-cmd]').forEach(b => b.disabled = false);
|
|
433
|
-
terminalLog.scrollTop = terminalLog.scrollHeight;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
document.querySelectorAll('.terminal-btn[data-cmd]').forEach(btn => {
|
|
438
|
-
btn.onclick = () => runTerminalCmd(btn.dataset.cmd);
|
|
439
|
-
});
|
|
440
|
-
terminalRun.onclick = () => { runTerminalCmd(terminalInput.value); terminalInput.value = ''; };
|
|
441
|
-
terminalInput.onkeydown = (e) => {
|
|
442
|
-
if (e.key === 'Enter') { runTerminalCmd(terminalInput.value); terminalInput.value = ''; }
|
|
443
|
-
};
|
|
444
|
-
|
|
445
751
|
// ── Init ─────────────────────────────────────────────
|
|
446
752
|
connectWs();
|
|
447
753
|
fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
|
|
448
|
-
if (d.
|
|
449
|
-
|
|
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.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');
|
|
450
761
|
}).catch(() => {});
|
|
451
762
|
</script>
|
|
452
763
|
</body>
|