nothumanallowed 12.7.0 → 13.2.13
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/LICENSE +21 -0
- package/README.md +185 -707
- package/bin/nha.mjs +35 -1
- package/package.json +2 -2
- package/src/commands/ui.mjs +484 -113
- package/src/config.mjs +0 -46
- package/src/constants.mjs +1 -1
- package/src/services/google-oauth.mjs +3 -8
- package/src/services/llm.mjs +140 -6
- package/src/services/tool-executor.mjs +2 -178
- package/src/services/web-ui.mjs +1452 -474
- package/src/services/imap-email.mjs +0 -428
package/src/services/web-ui.mjs
CHANGED
|
@@ -4,263 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { VERSION } from '../constants.mjs';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// CSS as a clean block — no template literal nesting issues
|
|
11
|
-
const CSS = `
|
|
12
|
-
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
13
|
-
:root{
|
|
14
|
-
--bg:#0a0a0a;--bg2:#111;--bg3:#1a1a2e;--bg4:#222;
|
|
15
|
-
--green:#a5b4fc;--green2:#818cf8;--green3:#6366f1;--greendim:#12121e;
|
|
16
|
-
--cyan:#38bdf8;--amber:#fbbf24;--red:#ef4444;
|
|
17
|
-
--text:#e4e4e7;--dim:#9ca3af;--bright:#fff;
|
|
18
|
-
--border:#1e1e2e;--border2:#3f3f5e;
|
|
19
|
-
--font:'JetBrains Mono','Fira Code','SF Mono','Consolas',monospace;
|
|
20
|
-
--r:6px;
|
|
21
|
-
}
|
|
22
|
-
html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--font);font-size:13px;line-height:1.5}
|
|
23
|
-
a{color:var(--cyan);text-decoration:none}
|
|
24
|
-
button{font-family:var(--font);cursor:pointer;border:none;outline:none}
|
|
25
|
-
input,textarea{font-family:var(--font);background:var(--bg2);color:var(--text);border:1px solid var(--border);padding:8px 12px;border-radius:var(--r);outline:none;font-size:13px}
|
|
26
|
-
input:focus,textarea:focus{border-color:var(--green3)}
|
|
27
|
-
::-webkit-scrollbar{width:6px}
|
|
28
|
-
::-webkit-scrollbar-track{background:var(--bg)}
|
|
29
|
-
::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
|
30
|
-
|
|
31
|
-
/* ---- TASKS ---- */
|
|
32
|
-
.task-bar{display:flex;gap:8px;margin-bottom:16px}
|
|
33
|
-
.task-bar input{flex:1;font-size:13px;padding:10px 14px}
|
|
34
|
-
.task-bar select{background:var(--bg2);color:var(--text);border:1px solid var(--border);padding:8px;border-radius:var(--r);font-size:12px}
|
|
35
|
-
.task-bar button{background:var(--green3);color:var(--bg);padding:8px 20px;border-radius:var(--r);font-weight:700;font-size:13px}
|
|
36
|
-
.task{display:flex;align-items:center;gap:10px;padding:12px;cursor:pointer}
|
|
37
|
-
.task--done{opacity:0.5}
|
|
38
|
-
.task__check{width:24px;height:24px;border:2px solid var(--border2);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--green);cursor:pointer;flex-shrink:0}
|
|
39
|
-
.task__check--done{background:var(--green3);border-color:var(--green)}
|
|
40
|
-
.task__desc{flex:1;font-size:13px}
|
|
41
|
-
.task__priority{font-size:10px;padding:2px 8px;border-radius:4px;text-transform:uppercase}
|
|
42
|
-
.task__priority--critical{background:var(--red);color:var(--bright)}
|
|
43
|
-
.task__priority--high{background:#ff6d00;color:var(--bright)}
|
|
44
|
-
.task__priority--medium{background:var(--amber);color:var(--bg)}
|
|
45
|
-
.task__priority--low{background:var(--border2);color:var(--dim)}
|
|
46
|
-
|
|
47
|
-
/* ---- LAYOUT: mobile-first ---- */
|
|
48
|
-
.app{display:flex;flex-direction:column;height:100vh;height:100dvh}
|
|
49
|
-
/* header removed — info moved to sidebar brand */
|
|
50
|
-
|
|
51
|
-
.sidebar{display:none;position:fixed;top:0;left:0;width:260px;height:100vh;height:100dvh;background:var(--bg2);border-right:1px solid var(--border);z-index:200;flex-direction:column;overflow-y:auto;box-shadow:4px 0 20px rgba(0,0,0,0.8)}
|
|
52
|
-
.sidebar--open{display:flex}
|
|
53
|
-
.sidebar__overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:150}
|
|
54
|
-
.sidebar__overlay--open{display:block}
|
|
55
|
-
.sidebar__brand{padding:16px;border-bottom:1px solid var(--border)}
|
|
56
|
-
.sidebar__brand-name{font-size:16px;color:var(--green);font-weight:700;letter-spacing:2px}
|
|
57
|
-
.sidebar__brand-sub{font-size:10px;color:var(--dim);margin-top:2px}
|
|
58
|
-
.sidebar__section{padding:12px 0}
|
|
59
|
-
.sidebar__label{padding:0 16px;font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--dim);margin-bottom:4px}
|
|
60
|
-
.nav-item{display:flex;align-items:center;gap:10px;padding:8px 16px;color:var(--dim);cursor:pointer;font-size:12px;border-left:2px solid transparent}
|
|
61
|
-
.nav-item:hover,.nav-item--active{color:var(--bright);background:var(--bg3)}
|
|
62
|
-
.nav-item--active{border-left-color:var(--green);color:var(--green)}
|
|
63
|
-
.nav-item__icon{width:18px;text-align:center}
|
|
64
|
-
.nav-item__badge{background:var(--red);color:var(--bright);font-size:9px;padding:1px 5px;border-radius:8px;margin-left:auto}
|
|
65
|
-
|
|
66
|
-
.content{flex:1;overflow-y:auto;padding:16px;-webkit-overflow-scrolling:touch}
|
|
67
|
-
|
|
68
|
-
/* Mobile burger button */
|
|
69
|
-
#mobileBurger{display:block}
|
|
70
|
-
.sidebar__close{position:absolute;top:12px;right:12px;background:none;border:none;color:var(--dim);font-size:20px;cursor:pointer;padding:4px 8px;z-index:10;line-height:1}
|
|
71
|
-
.sidebar__close:hover{color:var(--bright)}
|
|
72
|
-
.sidebar__brand{position:relative}
|
|
73
|
-
|
|
74
|
-
/* ---- DESKTOP: sidebar always visible ---- */
|
|
75
|
-
@media(min-width:901px){
|
|
76
|
-
.app{flex-direction:row}
|
|
77
|
-
.header__burger{display:none}
|
|
78
|
-
#mobileBurger{display:none!important}
|
|
79
|
-
.sidebar__close{display:none}
|
|
80
|
-
.sidebar{display:flex!important;position:static;width:220px;min-width:220px;height:auto;box-shadow:none}
|
|
81
|
-
.sidebar__overlay{display:none!important}
|
|
82
|
-
.content{padding:20px}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/* ---- CARDS ---- */
|
|
86
|
-
.card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:14px;margin-bottom:10px}
|
|
87
|
-
.card__title{font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--dim);margin-bottom:6px}
|
|
88
|
-
.card__value{font-size:22px;font-weight:700;color:var(--green)}
|
|
89
|
-
.card__sub{font-size:11px;color:var(--dim);margin-top:3px}
|
|
90
|
-
.dash-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px}
|
|
91
|
-
@media(min-width:901px){.dash-grid{grid-template-columns:repeat(4,1fr)}}
|
|
92
|
-
.section-title{font-size:12px;color:var(--cyan);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px}
|
|
93
|
-
|
|
94
|
-
/* ---- CHAT ---- */
|
|
95
|
-
.content--chat{overflow:hidden!important;padding:0!important;display:flex;flex-direction:column}
|
|
96
|
-
.chat{display:flex;flex-direction:column;flex:1;min-height:0;padding:16px;padding-bottom:0}
|
|
97
|
-
@media(min-width:901px){.content--chat{padding:0!important}}
|
|
98
|
-
.chat__messages{flex:1;overflow-y:auto;padding-bottom:12px;-webkit-overflow-scrolling:touch}
|
|
99
|
-
.chat__empty{text-align:center;padding:60px 16px;color:var(--dim)}
|
|
100
|
-
.chat__empty-title{font-size:28px;color:var(--green);margin-bottom:12px}
|
|
101
|
-
.chat__empty-hint{font-size:11px;margin-top:12px}
|
|
102
|
-
.msg{margin-bottom:12px}
|
|
103
|
-
.msg--user .msg__bubble{background:var(--bg3);border:1px solid var(--border2);border-radius:8px 8px 2px 8px;padding:10px 14px;max-width:85%;margin-left:auto;color:var(--bright)}
|
|
104
|
-
.msg--assistant .msg__bubble{background:var(--greendim);border:1px solid var(--green3);border-radius:8px 8px 8px 2px;padding:10px 14px;max-width:85%;color:var(--text);white-space:pre-wrap;word-wrap:break-word;line-height:1.5}
|
|
105
|
-
.msg--assistant .msg__bubble img{max-width:100%;border-radius:8px;margin:8px 0;border:1px solid rgba(0,255,65,0.2)}
|
|
106
|
-
.msg__label{font-size:10px;color:var(--dim);margin-bottom:2px}
|
|
107
|
-
.msg__actions{display:flex;gap:6px;margin-top:4px;opacity:0.4;transition:opacity 0.2s}
|
|
108
|
-
.msg:hover .msg__actions{opacity:1}
|
|
109
|
-
.msg__actions button{background:none;border:none;color:var(--dim);cursor:pointer;font-size:10px;font-family:var(--mono);padding:2px 4px}
|
|
110
|
-
.msg__actions button:hover{color:var(--green)}
|
|
111
|
-
#canvasPanel{position:fixed;top:60px;right:12px;width:480px;max-height:calc(100vh - 80px);background:#0d0d0d;border:1px solid var(--green);border-radius:12px;z-index:1000;overflow:hidden;display:none;flex-direction:column;box-shadow:0 0 30px rgba(0,255,65,0.1)}
|
|
112
|
-
#canvasPanel.open{display:flex}
|
|
113
|
-
#canvasPanel .cvs-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--green);background:rgba(0,255,65,0.05)}
|
|
114
|
-
#canvasPanel .cvs-header span{font-family:var(--mono);color:var(--green);font-size:12px}
|
|
115
|
-
#canvasPanel .cvs-header button{background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px;margin-left:8px}
|
|
116
|
-
#canvasPanel iframe{flex:1;border:none;background:#fff;min-height:350px;width:100%}
|
|
117
|
-
.msg--thinking{color:var(--dim);font-style:italic}
|
|
118
|
-
.tool-indicator{display:inline-block;padding:2px 8px;margin:2px 0;border-radius:4px;font-size:11px;background:var(--bg3);border:1px solid var(--border)}
|
|
119
|
-
.tool-indicator--browser{border-color:#9c27b0;color:#ce93d8}
|
|
120
|
-
.tool-indicator--web{border-color:var(--cyan);color:var(--cyan)}
|
|
121
|
-
.tool-indicator--email{border-color:var(--green3);color:var(--green)}
|
|
122
|
-
.screenshot-preview{max-width:100%;border-radius:var(--r);margin:8px 0;border:1px solid var(--border)}
|
|
123
|
-
.inline-card{margin:12px 0;padding:0;border-radius:10px;border:1px solid var(--border);overflow:hidden;background:var(--bg2)}
|
|
124
|
-
.inline-card iframe{width:100%;height:280px;border:none;border-radius:0 0 10px 10px}
|
|
125
|
-
.inline-card a{color:var(--cyan);text-decoration:none}
|
|
126
|
-
.inline-card a:hover{text-decoration:underline}
|
|
127
|
-
.inline-browser{margin:12px 0;border-radius:10px;border:1px solid var(--green3);overflow:hidden;background:#000}
|
|
128
|
-
.inline-browser-bar{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--bg);border-bottom:1px solid var(--border)}
|
|
129
|
-
.inline-browser-dot{width:8px;height:8px;border-radius:50%;background:var(--border2)}
|
|
130
|
-
.inline-browser-url{font-family:var(--mono);font-size:10px;color:var(--dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
131
|
-
.inline-browser img{width:100%;display:block}
|
|
132
|
-
.browser-viewer{position:fixed;top:12px;left:12px;width:440px;background:var(--bg2);border:2px solid #9c27b0;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);z-index:300;overflow:hidden;display:none;transition:all .3s ease}
|
|
133
|
-
.browser-viewer--open{display:block}
|
|
134
|
-
.browser-viewer__header{display:flex;align-items:center;gap:6px;padding:6px 10px;background:#1a1a2e;border-bottom:1px solid #9c27b0;font-size:10px;color:#ce93d8}
|
|
135
|
-
.browser-viewer__dot{width:6px;height:6px;border-radius:50%;background:#9c27b0;animation:bvpulse 1.5s infinite}
|
|
136
|
-
@keyframes bvpulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
137
|
-
.browser-viewer__title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
138
|
-
.browser-viewer__close{background:none;border:none;color:#666;cursor:pointer;font-size:14px;padding:0 4px}
|
|
139
|
-
.browser-viewer__close:hover{color:#fff}
|
|
140
|
-
.browser-viewer__frame{width:100%;aspect-ratio:16/9;background:#000;display:flex;align-items:center;justify-content:center}
|
|
141
|
-
.browser-viewer__frame img{width:100%;height:100%;object-fit:contain}
|
|
142
|
-
.browser-viewer__status{padding:4px 10px;font-size:9px;color:var(--dim);border-top:1px solid var(--border)}
|
|
143
|
-
@media(max-width:600px){.browser-viewer{width:calc(100vw - 24px);top:8px;left:8px}}
|
|
144
|
-
@media(min-width:901px){.browser-viewer{left:232px}}
|
|
145
|
-
.chat__bar{display:flex;flex-wrap:wrap;gap:6px;padding:10px 0 12px 0;border-top:1px solid var(--border);flex-shrink:0;align-items:flex-end}
|
|
146
|
-
.chat__bar-tools{display:flex;gap:4px;align-items:center;width:100%}
|
|
147
|
-
.chat__input{flex:1;resize:none;min-height:44px;max-height:120px;padding:10px 14px;font-size:14px}
|
|
148
|
-
.chat__send{background:var(--green3);color:var(--bg);padding:10px 20px;border-radius:var(--r);font-weight:700;font-size:14px;white-space:nowrap}
|
|
149
|
-
.chat__send:disabled{opacity:.4}
|
|
150
|
-
.chat__stop{white-space:nowrap}
|
|
151
|
-
|
|
152
|
-
/* ---- MOBILE TOUCH (Termux / small screens) ---- */
|
|
153
|
-
@media(max-width:600px){
|
|
154
|
-
.msg--user .msg__bubble,.msg--assistant .msg__bubble{font-size:14px;padding:12px 14px;max-width:94%;line-height:1.55}
|
|
155
|
-
.msg__label{font-size:11px}
|
|
156
|
-
.chat__bar{flex-wrap:wrap;gap:6px;padding:8px 4px 10px 4px}
|
|
157
|
-
.chat__bar-tools{flex-wrap:wrap;gap:4px}
|
|
158
|
-
.chat__input{min-height:48px;font-size:15px;padding:12px 14px;width:100%;flex-basis:100%}
|
|
159
|
-
.chat__send{padding:12px 20px;font-size:15px;flex-grow:1}
|
|
160
|
-
.chat__stop{flex-grow:1}
|
|
161
|
-
.chat__empty-title{font-size:22px}
|
|
162
|
-
.header{padding:10px 12px}
|
|
163
|
-
.header__title{font-size:15px}
|
|
164
|
-
.content{padding:10px}
|
|
165
|
-
/* Conversation sidebar as overlay on mobile, not side panel */
|
|
166
|
-
#convSidebar{position:fixed!important;top:0!important;left:0!important;height:100dvh!important;width:260px!important;box-shadow:4px 0 20px rgba(0,0,0,0.8)!important;z-index:250!important}
|
|
167
|
-
}
|
|
168
|
-
.chat__stop{background:var(--red);color:var(--bright);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px;display:none}
|
|
169
|
-
.chat__stop--visible{display:block}
|
|
170
|
-
|
|
171
|
-
/* ---- TASKS ---- */
|
|
172
|
-
.task-bar{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
|
|
173
|
-
.task-bar input{flex:1;min-width:150px}
|
|
174
|
-
.task-bar select{background:var(--bg2);color:var(--text);border:1px solid var(--border);padding:8px;border-radius:var(--r);font-size:12px}
|
|
175
|
-
.task-bar button{background:var(--green3);color:var(--bg);padding:8px 16px;border-radius:var(--r);font-weight:700;font-size:12px}
|
|
176
|
-
.task{display:flex;align-items:center;gap:10px;padding:10px 14px}
|
|
177
|
-
.task--done{opacity:.5}
|
|
178
|
-
.task__check{width:18px;height:18px;border:2px solid var(--border2);border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:12px;color:var(--green)}
|
|
179
|
-
.task__check--done{background:var(--green3);border-color:var(--green)}
|
|
180
|
-
.task__desc{flex:1;min-width:0}
|
|
181
|
-
.task__priority{font-size:9px;padding:2px 6px;border-radius:4px;text-transform:uppercase;font-weight:700}
|
|
182
|
-
.task__priority--high,.task__priority--critical{background:var(--red);color:var(--bright)}
|
|
183
|
-
.task__priority--medium{background:var(--amber);color:var(--bg)}
|
|
184
|
-
.task__priority--low{background:var(--border2);color:var(--dim)}
|
|
185
|
-
|
|
186
|
-
/* ---- PLAN ---- */
|
|
187
|
-
.plan-summary{padding:16px;border-left:3px solid var(--green);background:var(--bg2);border-radius:0 var(--r) var(--r) 0;margin-bottom:16px;line-height:1.6}
|
|
188
|
-
.plan-action{display:flex;gap:10px;align-items:baseline;padding:6px 0}
|
|
189
|
-
.plan-action__time{color:var(--amber);min-width:50px;font-weight:600}
|
|
190
|
-
.plan-action__text{flex:1}
|
|
191
|
-
.plan-action__priority{font-size:9px;padding:1px 5px;border-radius:3px;font-weight:700}
|
|
192
|
-
|
|
193
|
-
/* ---- EMAILS ---- */
|
|
194
|
-
.email{padding:12px 14px}
|
|
195
|
-
.email__header{display:flex;justify-content:space-between;gap:8px}
|
|
196
|
-
.email__from{color:var(--cyan);font-weight:600;font-size:12px}
|
|
197
|
-
.email__date{color:var(--dim);font-size:10px;white-space:nowrap}
|
|
198
|
-
.email__subject{color:var(--bright);margin-top:3px}
|
|
199
|
-
.email__snippet{color:var(--dim);font-size:11px;margin-top:3px}
|
|
200
|
-
|
|
201
|
-
/* ---- CALENDAR ---- */
|
|
202
|
-
.event{display:flex;gap:12px;align-items:center;padding:10px 14px}
|
|
203
|
-
.event__time{color:var(--amber);font-weight:600;min-width:100px;white-space:nowrap;font-size:12px}
|
|
204
|
-
.event__title{color:var(--bright);flex:1}
|
|
205
|
-
.event__location{color:var(--dim);font-size:11px}
|
|
206
|
-
|
|
207
|
-
/* ---- AGENTS ---- */
|
|
208
|
-
.agents-grid{display:grid;grid-template-columns:1fr;gap:6px}
|
|
209
|
-
@media(min-width:500px){.agents-grid{grid-template-columns:1fr 1fr}}
|
|
210
|
-
@media(min-width:901px){.agents-grid{grid-template-columns:1fr 1fr 1fr}}
|
|
211
|
-
.agent-card{padding:8px 10px;text-align:left;cursor:pointer;transition:border-color .15s;display:flex;align-items:center;gap:10px}
|
|
212
|
-
.agent-card:hover{border-color:var(--green3)}
|
|
213
|
-
.agent-card__icon{font-size:18px;flex-shrink:0}
|
|
214
|
-
.agent-card__body{min-width:0;flex:1}
|
|
215
|
-
.agent-card__name{color:var(--green);font-weight:700;font-size:11px}
|
|
216
|
-
.agent-card__tagline{font-size:9px;color:var(--text);line-height:1.3}
|
|
217
|
-
.agent-card__cat{display:none}
|
|
218
|
-
|
|
219
|
-
/* ---- MODAL ---- */
|
|
220
|
-
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:300;align-items:center;justify-content:center}
|
|
221
|
-
.modal-overlay--open{display:flex}
|
|
222
|
-
.modal{background:var(--bg2);border:1px solid var(--border2);border-radius:8px;width:92%;max-width:540px;max-height:90vh;display:flex;flex-direction:column}
|
|
223
|
-
.modal__header{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--border)}
|
|
224
|
-
.modal__header h2{font-size:16px;color:var(--green)}
|
|
225
|
-
.modal__close{background:none;color:var(--dim);font-size:24px;padding:0 4px}
|
|
226
|
-
.modal__body{padding:16px;overflow-y:auto;flex:1}
|
|
227
|
-
.modal__body textarea{width:100%;min-height:80px;margin-bottom:10px}
|
|
228
|
-
.modal__response{background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);padding:12px;white-space:pre-wrap;word-wrap:break-word;max-height:300px;overflow-y:auto;font-size:12px}
|
|
229
|
-
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)}
|
|
230
|
-
.btn{padding:8px 16px;border-radius:var(--r);font-size:12px;font-weight:600}
|
|
231
|
-
.btn--primary{background:var(--green3);color:var(--bg)}
|
|
232
|
-
.btn--secondary{background:var(--bg3);color:var(--dim);border:1px solid var(--border)}
|
|
233
|
-
|
|
234
|
-
/* ---- SPINNER ---- */
|
|
235
|
-
.spinner{width:24px;height:24px;border:2px solid var(--border);border-top-color:var(--green);border-radius:50%;animation:spin .6s linear infinite;margin:0 auto 12px}
|
|
236
|
-
@keyframes spin{to{transform:rotate(360deg)}}
|
|
237
|
-
|
|
238
|
-
/* ---- TOASTS (real-time notifications) ---- */
|
|
239
|
-
.toast-container{position:fixed;top:16px;right:16px;z-index:500;display:flex;flex-direction:column;gap:8px;pointer-events:none}
|
|
240
|
-
.toast{background:var(--bg3);border:1px solid var(--green);border-radius:8px;padding:12px 16px;max-width:320px;animation:toastIn .3s;pointer-events:auto;cursor:pointer;box-shadow:0 4px 20px rgba(0,0,0,0.5)}
|
|
241
|
-
.toast--email{border-color:var(--cyan)}
|
|
242
|
-
.toast--meeting{border-color:var(--amber)}
|
|
243
|
-
.toast--security{border-color:var(--red)}
|
|
244
|
-
.toast--plan{border-color:var(--green)}
|
|
245
|
-
.toast__title{font-size:11px;font-weight:700;margin-bottom:3px}
|
|
246
|
-
.toast--email .toast__title{color:var(--cyan)}
|
|
247
|
-
.toast--meeting .toast__title{color:var(--amber)}
|
|
248
|
-
.toast--security .toast__title{color:var(--red)}
|
|
249
|
-
.toast--plan .toast__title{color:var(--green)}
|
|
250
|
-
.toast__body{font-size:11px;color:var(--text);line-height:1.4}
|
|
251
|
-
.toast--fadeout{animation:toastOut .3s forwards}
|
|
252
|
-
@keyframes toastIn{from{transform:translateX(100%);opacity:0}to{transform:none;opacity:1}}
|
|
253
|
-
@keyframes toastOut{from{opacity:1}to{opacity:0;transform:translateX(40px)}}
|
|
254
|
-
|
|
255
|
-
/* ---- VOICE MIC BUTTON (in chat bar) ---- */
|
|
256
|
-
.chat__mic{background:var(--bg3);color:var(--green);border:1px solid var(--border2);width:40px;height:40px;border-radius:var(--r);display:flex;align-items:center;justify-content:center;font-size:18px;cursor:pointer;flex-shrink:0;transition:all .2s}
|
|
257
|
-
.chat__mic:hover{border-color:var(--green3);background:var(--greendim)}
|
|
258
|
-
.chat__mic--recording{border-color:var(--red);color:var(--red);background:rgba(255,23,68,0.1);animation:micPulse 1.5s ease-in-out infinite}
|
|
259
|
-
@keyframes micPulse{0%,100%{box-shadow:none}50%{box-shadow:0 0 0 6px rgba(255,23,68,0.2)}}
|
|
260
|
-
`;
|
|
261
|
-
|
|
262
|
-
// JS as clean block
|
|
263
|
-
const JS = `
|
|
7
|
+
// JS bundle — served as /nha-ui.js to avoid browser SyntaxError from large inline scripts
|
|
8
|
+
const JS = `
|
|
264
9
|
var API = '';
|
|
265
10
|
var currentView = 'dashboard';
|
|
266
11
|
var chatHistory = [];
|
|
@@ -271,6 +16,117 @@ var dashLoaded = {emails:false,events:false,tasks:false,contacts:false,notes:fal
|
|
|
271
16
|
var chatStreaming = false;
|
|
272
17
|
var chatAbortController = null;
|
|
273
18
|
|
|
19
|
+
// ---- MARKDOWN RENDERER ----
|
|
20
|
+
// Minimal inline markdown → HTML, no deps, safe (no innerHTML injection)
|
|
21
|
+
function renderMd(raw) {
|
|
22
|
+
if (!raw) return '';
|
|
23
|
+
var s = raw;
|
|
24
|
+
// Escape HTML first (safe base)
|
|
25
|
+
s = s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
26
|
+
// Fenced code blocks (triple-backtick) — use RegExp() to avoid backtick in template literal
|
|
27
|
+
var BT = String.fromCharCode(96);
|
|
28
|
+
var reTriple = new RegExp(BT+BT+BT+'[a-z]*[\\s\\S]*?'+BT+BT+BT,'g');
|
|
29
|
+
var reSingle = new RegExp(BT+'([^'+BT+']{1,200})'+BT,'g');
|
|
30
|
+
s = s.replace(reTriple, function(m){
|
|
31
|
+
var inner=m.replace(new RegExp(BT+BT+BT+'[a-z]*'),'').replace(new RegExp(BT+BT+BT),'');
|
|
32
|
+
return '<pre class="md-code"><code>'+inner+'</code></pre>';
|
|
33
|
+
});
|
|
34
|
+
// Inline code (single backtick)
|
|
35
|
+
s = s.replace(reSingle,'<code class="md-inline-code">$1</code>');
|
|
36
|
+
// Bold and italic — use [*] to avoid both /* */ comment ambiguity and \* escape issues
|
|
37
|
+
s = s.replace(new RegExp('[*][*]([^*]+)[*][*]','g'),'<strong>$1</strong>');
|
|
38
|
+
s = s.replace(new RegExp('[*]([^*]+)[*]','g'),'<em>$1</em>');
|
|
39
|
+
// Strikethrough ~~text~~
|
|
40
|
+
s = s.replace(/~~([^~]+)~~/g,'<del>$1</del>');
|
|
41
|
+
// Split into lines for block-level parsing — use fromCharCode to avoid template literal newline
|
|
42
|
+
var NL = String.fromCharCode(10);
|
|
43
|
+
var lines = s.split(NL);
|
|
44
|
+
var out = [];
|
|
45
|
+
var inUl = false, inOl = false, inPre = false;
|
|
46
|
+
for (var i = 0; i < lines.length; i++) {
|
|
47
|
+
var l = lines[i];
|
|
48
|
+
// Already inside pre block
|
|
49
|
+
if (l.indexOf('<pre class="md-code">') !== -1) { inPre = true; }
|
|
50
|
+
if (inPre) { out.push(l); if (l.indexOf('</pre>') !== -1) inPre = false; continue; }
|
|
51
|
+
// H1-H3
|
|
52
|
+
var hm = l.match(/^(#{1,3}) (.+)/);
|
|
53
|
+
if (hm) {
|
|
54
|
+
if (inUl) { out.push('</ul>'); inUl = false; }
|
|
55
|
+
if (inOl) { out.push('</ol>'); inOl = false; }
|
|
56
|
+
var hl = hm[1].length;
|
|
57
|
+
out.push('<h'+hl+' class="md-h'+hl+'">'+hm[2]+'</h'+hl+'>');
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Horizontal rule ---
|
|
61
|
+
if (/^---+$/.test(l.trim())) {
|
|
62
|
+
out.push('<hr class="md-hr">');
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Unordered list: - item or * item
|
|
66
|
+
var ulm = l.match(/^(\s*)[*\-] (.+)/);
|
|
67
|
+
if (ulm) {
|
|
68
|
+
if (inOl) { out.push('</ol>'); inOl = false; }
|
|
69
|
+
if (!inUl) { out.push('<ul class="md-ul">'); inUl = true; }
|
|
70
|
+
out.push('<li>'+ulm[2]+'</li>');
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Ordered list: 1. item
|
|
74
|
+
var olm = l.match(/^(\s*)\d+\. (.+)/);
|
|
75
|
+
if (olm) {
|
|
76
|
+
if (inUl) { out.push('</ul>'); inUl = false; }
|
|
77
|
+
if (!inOl) { out.push('<ol class="md-ol">'); inOl = true; }
|
|
78
|
+
out.push('<li>'+olm[2]+'</li>');
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Close lists on blank or non-list line
|
|
82
|
+
if (inUl) { out.push('</ul>'); inUl = false; }
|
|
83
|
+
if (inOl) { out.push('</ol>'); inOl = false; }
|
|
84
|
+
// Blockquote > text
|
|
85
|
+
var bqm = l.match(/^> (.+)/);
|
|
86
|
+
if (bqm) { out.push('<blockquote class="md-bq">'+bqm[1]+'</blockquote>'); continue; }
|
|
87
|
+
// Blank line → paragraph break
|
|
88
|
+
if (l.trim() === '') { out.push('<div class="md-spacer"></div>'); continue; }
|
|
89
|
+
// Regular paragraph line
|
|
90
|
+
out.push('<p class="md-p">'+l+'</p>');
|
|
91
|
+
}
|
|
92
|
+
if (inUl) out.push('</ul>');
|
|
93
|
+
if (inOl) out.push('</ol>');
|
|
94
|
+
return out.join('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---- DRAG-TO-MOVE for floating panels ----
|
|
98
|
+
function makeDraggable(el, handleSelector) {
|
|
99
|
+
var handle = handleSelector ? el.querySelector(handleSelector) : el;
|
|
100
|
+
if (!handle) handle = el;
|
|
101
|
+
var ox=0,oy=0,sx=0,sy=0,dragging=false;
|
|
102
|
+
handle.style.cursor='grab';
|
|
103
|
+
handle.addEventListener('mousedown', function(e) {
|
|
104
|
+
if (e.button !== 0) return;
|
|
105
|
+
dragging = true;
|
|
106
|
+
sx = e.clientX; sy = e.clientY;
|
|
107
|
+
var rect = el.getBoundingClientRect();
|
|
108
|
+
// Switch from CSS right/top to explicit left/top
|
|
109
|
+
el.style.right = 'auto';
|
|
110
|
+
el.style.left = rect.left + 'px';
|
|
111
|
+
el.style.top = rect.top + 'px';
|
|
112
|
+
ox = rect.left; oy = rect.top;
|
|
113
|
+
handle.style.cursor='grabbing';
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
});
|
|
116
|
+
document.addEventListener('mousemove', function(e) {
|
|
117
|
+
if (!dragging) return;
|
|
118
|
+
var dx = e.clientX - sx, dy = e.clientY - sy;
|
|
119
|
+
var nx = ox+dx, ny = oy+dy;
|
|
120
|
+
// Clamp to viewport
|
|
121
|
+
nx = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, nx));
|
|
122
|
+
ny = Math.max(0, Math.min(window.innerHeight - 40, ny));
|
|
123
|
+
el.style.left = nx+'px'; el.style.top = ny+'px';
|
|
124
|
+
});
|
|
125
|
+
document.addEventListener('mouseup', function() {
|
|
126
|
+
if (dragging) { dragging=false; handle.style.cursor='grab'; }
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
274
130
|
function endStreaming(){
|
|
275
131
|
chatStreaming=false;chatAbortController=null;
|
|
276
132
|
var stopBtn=document.getElementById('chatStop');if(stopBtn)stopBtn.classList.remove('chat__stop--visible');
|
|
@@ -314,6 +170,19 @@ function closeBrowserViewer(){
|
|
|
314
170
|
var v=document.getElementById('browserViewer');if(v)v.classList.remove('browser-viewer--open');
|
|
315
171
|
}
|
|
316
172
|
|
|
173
|
+
function openLightbox(src){
|
|
174
|
+
var o=document.getElementById('lightboxOverlay');
|
|
175
|
+
var img=document.getElementById('lightboxImg');
|
|
176
|
+
if(!o||!img||!src)return;
|
|
177
|
+
img.src=src;
|
|
178
|
+
o.classList.add('lightbox-overlay--open');
|
|
179
|
+
document.addEventListener('keydown',function handler(e){if(e.key==='Escape'){closeLightbox();document.removeEventListener('keydown',handler);}});
|
|
180
|
+
}
|
|
181
|
+
function closeLightbox(){
|
|
182
|
+
var o=document.getElementById('lightboxOverlay');
|
|
183
|
+
if(o)o.classList.remove('lightbox-overlay--open');
|
|
184
|
+
}
|
|
185
|
+
|
|
317
186
|
function loadConvList(){return apiGet('/api/conversations').then(function(r){convList=(r&&r.conversations)||[];renderConvSidebar();})}
|
|
318
187
|
function loadConv(id){return apiGet('/api/conversations/'+id).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=r.conversation.messages||[];renderMessages();renderConvSidebar();onConversationSwitch();}})}
|
|
319
188
|
function createNewConv(){return apiPost('/api/conversations',{}).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=[];renderMessages();loadConvList();}})}
|
|
@@ -321,6 +190,7 @@ function deleteConv(id){return fetch(API+'/api/conversations/'+id,{method:'DELET
|
|
|
321
190
|
function clearChatHistory(){createNewConv()}
|
|
322
191
|
var agentsList = [];
|
|
323
192
|
var selectedAgent = null;
|
|
193
|
+
var agentChatHistory = []; // [{role:'user'|'agent', text:'...'}]
|
|
324
194
|
|
|
325
195
|
// ---- NAV ----
|
|
326
196
|
function switchView(v) {
|
|
@@ -334,13 +204,15 @@ function switchView(v) {
|
|
|
334
204
|
document.querySelectorAll('.nav-item').forEach(function(el){
|
|
335
205
|
if(el.dataset.view===v){el.classList.add('nav-item--active')}else{el.classList.remove('nav-item--active')}
|
|
336
206
|
});
|
|
337
|
-
var titles = {dashboard:'Dashboard',chat:'Chat',plan:'Daily Plan',tasks:'Tasks',emails:'Emails',calendar:'Calendar',drive:'Drive',contacts:'Contacts',notes:'Notes',onedrive:'OneDrive',mstodo:'Microsoft To Do',agents:'Agents',settings:'Settings'};
|
|
207
|
+
var titles = {dashboard:'Dashboard',chat:'Chat',plan:'Daily Plan',tasks:'Tasks',emails:'Emails',calendar:'Calendar',drive:'Drive',contacts:'Contacts',notes:'Notes',onedrive:'OneDrive',mstodo:'Microsoft To Do',agents:'Agents',studio:'Studio',settings:'Settings'};
|
|
338
208
|
var spt=document.getElementById('sidebarPageTitle');
|
|
339
209
|
if(spt)spt.textContent=titles[v]||v;
|
|
340
210
|
// Toggle content--chat class for proper chat layout (no overflow, flex column)
|
|
341
211
|
var ct=document.getElementById('content');
|
|
342
212
|
if(ct){if(v==='chat'){ct.classList.add('content--chat')}else{ct.classList.remove('content--chat')}}
|
|
343
213
|
closeSidebar();
|
|
214
|
+
// Auto-close floating panels when leaving chat/studio
|
|
215
|
+
if(v!=='chat'&&v!=='studio'){closeBrowserViewer();closeCanvas();}
|
|
344
216
|
render();
|
|
345
217
|
}
|
|
346
218
|
function openSidebar() {
|
|
@@ -371,7 +243,7 @@ function apiPatch(p){return fetch(API+p,{method:'PATCH'}).then(function(r){retur
|
|
|
371
243
|
|
|
372
244
|
// ---- LOAD DATA ----
|
|
373
245
|
function loadDash(){
|
|
374
|
-
// Load each API independently
|
|
246
|
+
// Load each API independently - render as each arrives (emails are slow)
|
|
375
247
|
apiGet('/api/status').then(function(r){dash.status=r;render()});
|
|
376
248
|
apiGet('/api/tasks').then(function(r){dash.tasks=(r&&r.tasks)||[];dashLoaded.tasks=true;updateBadges();render()});
|
|
377
249
|
apiGet('/api/calendar').then(function(r){dash.events=(r&&r.events)||[];dashLoaded.events=true;updateBadges();render()});
|
|
@@ -412,6 +284,7 @@ function render(){
|
|
|
412
284
|
case 'slack':renderSlack(el);break;
|
|
413
285
|
case 'birthdays':renderBirthdays(el);break;
|
|
414
286
|
case 'agents':renderAgents(el);break;
|
|
287
|
+
case 'studio':renderStudio(el);break;
|
|
415
288
|
case 'collab':renderCollab(el);break;
|
|
416
289
|
case 'settings':renderSettings(el);break;
|
|
417
290
|
}
|
|
@@ -504,7 +377,7 @@ function exportConvMd(){if(!activeConvId)return;window.open(API+'/api/conversati
|
|
|
504
377
|
function renderMessages(){
|
|
505
378
|
var el=document.getElementById('chatMessages');if(!el)return;
|
|
506
379
|
if(chatHistory.length===0){
|
|
507
|
-
el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant
|
|
380
|
+
el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant - Streaming + Web Search + Browser</div><div class="chat__empty-hint">Try: Show my unread emails / Search the web for React 19 / Open google.com and take a screenshot</div></div>';
|
|
508
381
|
return;
|
|
509
382
|
}
|
|
510
383
|
var h='';chatHistory.forEach(function(m,mi){
|
|
@@ -518,18 +391,19 @@ function renderMessages(){
|
|
|
518
391
|
var cm=raw.match(/\\[CANVAS_RENDER\\]([\\s\\S]*?)\\[\\/CANVAS_RENDER\\]/);
|
|
519
392
|
if(cm){try{var cd=JSON.parse(cm[1]);showCanvas(cd.html,cd.title);}catch(e){} raw=raw.replace(/\\[CANVAS_RENDER\\][\\s\\S]*?\\[\\/CANVAS_RENDER\\]/,'').trim();}
|
|
520
393
|
if(raw.indexOf('[CANVAS_CLEAR]')!==-1){closeCanvas();raw=raw.replace(/\\[CANVAS_CLEAR\\][\\s\\S]*?\\[\\/CANVAS_CLEAR\\]/,'').trim();}
|
|
521
|
-
// Inline cards
|
|
394
|
+
// Inline cards - rendered as embedded HTML inside the message
|
|
522
395
|
raw=raw.replace(/\\[INLINE_CARD\\]([\\s\\S]*?)\\[\\/INLINE_CARD\\]/g,function(_,html){return '<div class="inline-card">'+html+'</div>';});
|
|
523
|
-
// Inline browser frame
|
|
524
|
-
raw=raw.replace(/\\[INLINE_BROWSER\\]([^|]+)\\|([^\\]]+)\\[\\/INLINE_BROWSER\\]/g,function(_,file,url){return '<div class="inline-browser"><div class="inline-browser-bar"><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-url">'+esc(url)+'</span></div><img src="/api/screenshots/'+esc(file)+'" alt="'+esc(url)+'"></div>';});
|
|
396
|
+
// Inline browser frame - rendered as embedded image inside the message
|
|
397
|
+
raw=raw.replace(/\\[INLINE_BROWSER\\]([^|]+)\\|([^\\]]+)\\[\\/INLINE_BROWSER\\]/g,function(_,file,url){return '<div class="inline-browser"><div class="inline-browser-bar"><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-url">'+esc(url)+'</span></div><img src="/api/screenshots/'+esc(file)+'" alt="'+esc(url)+'" onclick="openLightbox(this.src)" style="cursor:zoom-in"></div>';});
|
|
525
398
|
// Handle screenshot file markers
|
|
526
399
|
var sm=raw.match(/\\[SCREENSHOT_FILE\\](.*?)\\[\\/SCREENSHOT_FILE\\]/);
|
|
527
400
|
if(sm){var fn=sm[1].split('/').pop();raw=raw.replace(/\\[SCREENSHOT_FILE\\].*?\\[\\/SCREENSHOT_FILE\\]/,'');raw='\\n'+raw;}
|
|
528
401
|
}
|
|
529
402
|
var imgs=[];var idx=0;
|
|
530
403
|
var safe=raw.replace(/!\\[([^\\]]*)\\]\\((\\/api\\/screenshots\\/[a-zA-Z0-9._-]+)\\)/g,function(_,alt,src){var ph='__IMG'+idx+'__';imgs.push({ph:ph,alt:alt,src:src});idx++;return ph;});
|
|
531
|
-
|
|
532
|
-
|
|
404
|
+
// Assistant messages get markdown rendering; user messages get plain escape
|
|
405
|
+
var content=isA?renderMd(safe):esc(safe);
|
|
406
|
+
for(var i=0;i<imgs.length;i++){content=content.replace(imgs[i].ph,'<img class="screenshot-preview" alt="'+esc(imgs[i].alt)+'" src="'+imgs[i].src+'" onclick="openLightbox(this.src)">');}
|
|
533
407
|
// Action buttons + fork navigation
|
|
534
408
|
var acts='<div class="msg__actions">';
|
|
535
409
|
acts+='<button onclick="copyMsg('+mi+')">Copy</button>';
|
|
@@ -540,9 +414,10 @@ function renderMessages(){
|
|
|
540
414
|
acts+='</div>';
|
|
541
415
|
var inlineBlock='';
|
|
542
416
|
if(isA&&m.inlineHtml){
|
|
543
|
-
inlineBlock=m.inlineHtml.replace(/\\[INLINE_CARD\\]([\\s\\S]*?)\\[\\/INLINE_CARD\\]/g,function(_,htm){return '<div class="inline-card">'+htm+'</div>';}).replace(/\\[INLINE_BROWSER\\]([^|]+)\\|([^\\]]+)\\[\\/INLINE_BROWSER\\]/g,function(_,file,url){return '<div class="inline-browser"><div class="inline-browser-bar"><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-url">'+esc(url)+'</span></div><img src="/api/screenshots/'+esc(file)+'" alt="'+esc(url)+'"></div>';});
|
|
417
|
+
inlineBlock=m.inlineHtml.replace(/\\[INLINE_CARD\\]([\\s\\S]*?)\\[\\/INLINE_CARD\\]/g,function(_,htm){return '<div class="inline-card">'+htm+'</div>';}).replace(/\\[INLINE_BROWSER\\]([^|]+)\\|([^\\]]+)\\[\\/INLINE_BROWSER\\]/g,function(_,file,url){return '<div class="inline-browser"><div class="inline-browser-bar"><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-url">'+esc(url)+'</span></div><img src="/api/screenshots/'+esc(file)+'" alt="'+esc(url)+'" onclick="openLightbox(this.src)" style="cursor:zoom-in"></div>';});
|
|
544
418
|
}
|
|
545
|
-
|
|
419
|
+
var bubbleCls=isA?'msg__bubble md-body':'msg__bubble';
|
|
420
|
+
h+='<div class="msg msg--'+esc(m.role)+'"><div class="msg__label">'+esc(m.role==='user'?'You':'NHA')+'</div><div class="'+bubbleCls+'">'+content+'</div>'+inlineBlock+acts+'</div>';
|
|
546
421
|
});
|
|
547
422
|
el.innerHTML=h;el.scrollTop=el.scrollHeight;
|
|
548
423
|
// Load fork info for messages that have IDs
|
|
@@ -574,7 +449,7 @@ function handleChatFile(input){
|
|
|
574
449
|
chatAttachedFile={name:file.name,size:file.size,content:null,base64:base64,mimeType:'application/pdf',isPDF:true};
|
|
575
450
|
chatAttachedImage=null;
|
|
576
451
|
document.getElementById('chatAttachInfo').style.display='';
|
|
577
|
-
document.getElementById('chatAttachName').textContent='
|
|
452
|
+
document.getElementById('chatAttachName').textContent='[file] '+file.name+' ('+Math.round(file.size/1024)+' KB)';
|
|
578
453
|
};
|
|
579
454
|
reader.readAsDataURL(file);
|
|
580
455
|
}else{
|
|
@@ -583,7 +458,7 @@ function handleChatFile(input){
|
|
|
583
458
|
chatAttachedFile={name:file.name,size:file.size,content:e.target.result};
|
|
584
459
|
chatAttachedImage=null;
|
|
585
460
|
document.getElementById('chatAttachInfo').style.display='';
|
|
586
|
-
document.getElementById('chatAttachName').textContent='
|
|
461
|
+
document.getElementById('chatAttachName').textContent='[file] '+file.name+' ('+Math.round(file.size/1024)+' KB)';
|
|
587
462
|
};
|
|
588
463
|
reader.readAsText(file);
|
|
589
464
|
}
|
|
@@ -597,7 +472,7 @@ function handleChatImage(input){
|
|
|
597
472
|
chatAttachedImage={name:file.name,size:file.size,base64:base64,mimeType:file.type||'image/jpeg'};
|
|
598
473
|
chatAttachedFile=null;
|
|
599
474
|
document.getElementById('chatAttachInfo').style.display='';
|
|
600
|
-
document.getElementById('chatAttachName').textContent='
|
|
475
|
+
document.getElementById('chatAttachName').textContent='[img] '+file.name+' ('+Math.round(file.size/1024)+' KB)';
|
|
601
476
|
};
|
|
602
477
|
reader.readAsDataURL(file);
|
|
603
478
|
}
|
|
@@ -693,7 +568,7 @@ function renderCanvasPanel(){
|
|
|
693
568
|
else if(!item){t.textContent='Empty canvas';}
|
|
694
569
|
else{t.textContent=(item.title||'Canvas')+(d.canvases.length>1?' ('+(canvasIdx+1)+'/'+d.canvases.length+')':'');}
|
|
695
570
|
}
|
|
696
|
-
// Nav arrows
|
|
571
|
+
// Nav arrows - only for canvas mode (browser uses gallery grid)
|
|
697
572
|
var navEl=document.getElementById('canvasNav');
|
|
698
573
|
if(navEl){navEl.style.display=d.canvases.length>1&&canvasMode==='canvas'?'flex':'none';}
|
|
699
574
|
// Tab highlight
|
|
@@ -753,7 +628,7 @@ function canvasShowBrowser(){var d=getConvCanvasData();browserIdx=d.browsers.len
|
|
|
753
628
|
function canvasShowCanvas(){var d=getConvCanvasData();canvasIdx=d.canvases.length-1;canvasMode='canvas';renderCanvasPanel();}
|
|
754
629
|
|
|
755
630
|
function onConversationSwitch(){
|
|
756
|
-
// Called when user switches conversation
|
|
631
|
+
// Called when user switches conversation - update canvas panel
|
|
757
632
|
var p=document.getElementById('canvasPanel');
|
|
758
633
|
if(p&&p.classList.contains('open')){
|
|
759
634
|
var d=getConvCanvasData();
|
|
@@ -852,7 +727,7 @@ function editMsg(i){
|
|
|
852
727
|
// User can press Esc to cancel or Enter to send (which creates a branch)
|
|
853
728
|
inp.value=chatHistory[i].content;
|
|
854
729
|
inp.focus();
|
|
855
|
-
// Mark that we're editing
|
|
730
|
+
// Mark that we're editing - sendChat will handle truncation
|
|
856
731
|
window._nhaEditIndex=i;
|
|
857
732
|
// Listen for Esc to cancel
|
|
858
733
|
inp.onkeydown=function(e){
|
|
@@ -898,7 +773,7 @@ function sendChat(){
|
|
|
898
773
|
var isRetry=!!window._nhaRetryMode;
|
|
899
774
|
window._nhaRetryMode=false;
|
|
900
775
|
|
|
901
|
-
// Handle edit mode
|
|
776
|
+
// Handle edit mode - truncate history to edit point before adding
|
|
902
777
|
if(window._nhaEditIndex!=null){
|
|
903
778
|
chatHistory=chatHistory.slice(0,window._nhaEditIndex);
|
|
904
779
|
window._nhaEditIndex=null;
|
|
@@ -925,11 +800,6 @@ function sendChat(){
|
|
|
925
800
|
clearChatAttach();
|
|
926
801
|
apiPost('/api/chat',payload).then(function(r){
|
|
927
802
|
chatHistory.pop();
|
|
928
|
-
// Save llmContent in the user message so file context persists across turns
|
|
929
|
-
if(r&&r.llmContent){
|
|
930
|
-
var lastUser=chatHistory[chatHistory.length-1];
|
|
931
|
-
if(lastUser&&lastUser.role==='user')lastUser.llmContent=r.llmContent;
|
|
932
|
-
}
|
|
933
803
|
if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
|
|
934
804
|
else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
|
|
935
805
|
else{chatHistory.push({role:'assistant',content:'Error: no response'})}
|
|
@@ -948,12 +818,12 @@ function sendChat(){
|
|
|
948
818
|
chatHistory.push({role:'assistant',content:''});
|
|
949
819
|
renderMessages();
|
|
950
820
|
var streamIdx=chatHistory.length-1;
|
|
951
|
-
var allHistory=chatHistory.slice(0,-1).map(function(m){
|
|
821
|
+
var allHistory=chatHistory.slice(0,-1).map(function(m){return{role:m.role,content:(m.content||'').replace(/!\\[Screenshot\\]\\(data:image\\/[^)]+\\)/g,'[Screenshot taken]')};});
|
|
952
822
|
var payload={message:msg,history:allHistory,conversationId:activeConvId,isRetry:isRetry};
|
|
953
823
|
|
|
954
824
|
fetch(API+'/api/chat/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),signal:chatAbortController.signal}).then(function(response){
|
|
955
825
|
if(!response.ok||!response.body||typeof response.body.getReader!=='function'){
|
|
956
|
-
// Fallback for browsers without ReadableStream support
|
|
826
|
+
// Fallback for browsers without ReadableStream support - use non-streaming endpoint
|
|
957
827
|
chatHistory[streamIdx].content='Thinking...';renderMessages();
|
|
958
828
|
apiPost('/api/chat',{message:msg,history:allHistory,conversationId:activeConvId,isRetry:isRetry}).then(function(r){
|
|
959
829
|
chatHistory[streamIdx].content=(r&&r.response)||(r&&r.error?'Error: '+r.error:'Error: no response');
|
|
@@ -984,7 +854,7 @@ function sendChat(){
|
|
|
984
854
|
displayContent=displayContent.replace(/<think>[\\s\\S]*?<\\/think>/g,'').trim();
|
|
985
855
|
}
|
|
986
856
|
var el=document.getElementById('chatMessages');
|
|
987
|
-
if(el){var msgs=el.querySelectorAll('.msg');var last=msgs[msgs.length-1];if(last){var bub=last.querySelector('.msg__bubble');if(bub)bub.
|
|
857
|
+
if(el){var msgs=el.querySelectorAll('.msg');var last=msgs[msgs.length-1];if(last){var bub=last.querySelector('.msg__bubble');if(bub){bub.className='msg__bubble md-body';bub.innerHTML=isThinking?displayContent:renderMd(displayContent)||'Thinking...';}}el.scrollTop=el.scrollHeight;}
|
|
988
858
|
}
|
|
989
859
|
if(currentEvent==='tool'){
|
|
990
860
|
var toolLabels={browser_open:'Opening page',browser_screenshot:'Taking screenshot',browser_click:'Clicking element',browser_type:'Typing text',browser_extract:'Extracting content',browser_js:'Running JavaScript',browser_wait:'Waiting for element',browser_scroll:'Scrolling page',browser_key:'Pressing key',browser_close:'Closing browser',web_search:'Searching the web',fetch_url:'Fetching URL',gmail_list:'Searching emails',gmail_read:'Reading email',gmail_send:'Sending email',calendar_today:'Loading calendar',calendar_create:'Creating event'};
|
|
@@ -1013,7 +883,7 @@ function sendChat(){
|
|
|
1013
883
|
if(data.url)updateBrowserStatus(data.url);
|
|
1014
884
|
}
|
|
1015
885
|
if(currentEvent==='canvas'&&data.markers){
|
|
1016
|
-
// Canvas content arrived
|
|
886
|
+
// Canvas content arrived - render it immediately
|
|
1017
887
|
var cm=data.markers.match(/\\[CANVAS_RENDER\\]([\\s\\S]*?)\\[\\/CANVAS_RENDER\\]/);
|
|
1018
888
|
if(cm){try{var cd=JSON.parse(cm[1]);showCanvas(cd.html,cd.title);}catch(e){}}
|
|
1019
889
|
if(data.markers.indexOf('[CANVAS_CLEAR]')!==-1)closeCanvas();
|
|
@@ -1055,7 +925,7 @@ function addTaskUI(){
|
|
|
1055
925
|
});
|
|
1056
926
|
}
|
|
1057
927
|
function toggleTask(id){
|
|
1058
|
-
// Optimistic UI
|
|
928
|
+
// Optimistic UI - update instantly, sync in background
|
|
1059
929
|
var t=dash.tasks.find(function(x){return x.id===id});
|
|
1060
930
|
if(t){t.status=t.status==='done'?'pending':'done';t.completedAt=t.status==='done'?new Date().toISOString():null}
|
|
1061
931
|
render();
|
|
@@ -1100,7 +970,7 @@ function refreshPlan(){
|
|
|
1100
970
|
function renderEmails(el){
|
|
1101
971
|
if(!dashLoaded.emails){el.innerHTML=loadingHTML('emails');return}
|
|
1102
972
|
var e=dash.emails;
|
|
1103
|
-
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">Inbox zero
|
|
973
|
+
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">Inbox zero - no emails</div>';return}
|
|
1104
974
|
var unreadCount=e.filter(function(x){return x.isUnread}).length;
|
|
1105
975
|
var h='<div style="display:flex;gap:8px;margin-bottom:10px;align-items:center">';
|
|
1106
976
|
h+='<span style="font-size:12px;color:var(--dim)">'+e.length+' emails'+(unreadCount>0?' ('+unreadCount+' unread)':'')+'</span>';
|
|
@@ -1222,7 +1092,7 @@ function renderCalendar(el){
|
|
|
1222
1092
|
});
|
|
1223
1093
|
h+='</div>';
|
|
1224
1094
|
|
|
1225
|
-
// Calendar grid
|
|
1095
|
+
// Calendar grid - square cells
|
|
1226
1096
|
h+='<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:3px">';
|
|
1227
1097
|
// Empty cells before first day
|
|
1228
1098
|
for(var i=0;i<startDay;i++){h+='<div style="aspect-ratio:1;background:var(--bg);border-radius:6px"></div>'}
|
|
@@ -1319,16 +1189,18 @@ function openDayDetail(dateStr){
|
|
|
1319
1189
|
});
|
|
1320
1190
|
}
|
|
1321
1191
|
|
|
1322
|
-
// Use the agent modal for day detail
|
|
1192
|
+
// Use the agent modal for day detail (read-only mode)
|
|
1193
|
+
selectedAgent=null;
|
|
1194
|
+
agentChatHistory=[];
|
|
1323
1195
|
document.getElementById('modalName').textContent=dayLabel;
|
|
1324
|
-
document.getElementById('
|
|
1325
|
-
|
|
1326
|
-
document.getElementById('
|
|
1327
|
-
|
|
1328
|
-
|
|
1196
|
+
document.getElementById('modalAgentDesc').textContent='';
|
|
1197
|
+
// Show the day events in the messages area
|
|
1198
|
+
var msgEl=document.getElementById('agentMessages');
|
|
1199
|
+
if(msgEl){msgEl.innerHTML='<div class="agent-chat__bubble agent-chat__bubble--agent md-body" style="width:100%;max-width:100%;box-sizing:border-box">'+h+'</div>';}
|
|
1200
|
+
// Hide input footer in read-only mode
|
|
1201
|
+
var footer=document.querySelector('.agent-chat__footer');
|
|
1202
|
+
if(footer)footer.style.display='none';
|
|
1329
1203
|
document.getElementById('agentModal').classList.add('modal-overlay--open');
|
|
1330
|
-
var sendBtn=document.getElementById('agentModal').querySelector('.btn--primary');
|
|
1331
|
-
if(sendBtn)sendBtn.style.display='none';
|
|
1332
1204
|
}
|
|
1333
1205
|
|
|
1334
1206
|
// ---- GITHUB ----
|
|
@@ -1422,7 +1294,7 @@ function renderBirthdays(el){
|
|
|
1422
1294
|
bdays.forEach(function(b){
|
|
1423
1295
|
var isToday=b.daysUntil===0;
|
|
1424
1296
|
var label=isToday?'<span style="color:var(--red);font-weight:700">TODAY!</span>':b.daysUntil===1?'<span style="color:var(--amber)">Tomorrow</span>':'<span style="color:var(--dim)">in '+b.daysUntil+' days</span>';
|
|
1425
|
-
h+='<div class="card" style="padding:12px 14px'+(isToday?';border-color:var(--red)':'')+'"><span style="font-size:16px">🎂</span> <span style="font-weight:700">'+esc(b.name)+'</span>
|
|
1297
|
+
h+='<div class="card" style="padding:12px 14px'+(isToday?';border-color:var(--red)':'')+'"><span style="font-size:16px">🎂</span> <span style="font-weight:700">'+esc(b.name)+'</span> - '+esc(b.date)+' '+label+'</div>';
|
|
1426
1298
|
});
|
|
1427
1299
|
el.innerHTML=h;
|
|
1428
1300
|
});
|
|
@@ -1953,7 +1825,7 @@ var collabLastMessageCount=0;
|
|
|
1953
1825
|
var collabUnreadCount=0;
|
|
1954
1826
|
var collabGlobalPolling=null;
|
|
1955
1827
|
|
|
1956
|
-
// No client-side polling needed
|
|
1828
|
+
// No client-side polling needed - server pushes via WebSocket
|
|
1957
1829
|
function startCollabGlobalPolling(){
|
|
1958
1830
|
// Just load channels list once
|
|
1959
1831
|
apiGet('/api/collab/channels').then(function(r){
|
|
@@ -1994,7 +1866,7 @@ setTimeout(startCollabGlobalPolling,2000);
|
|
|
1994
1866
|
|
|
1995
1867
|
function renderCollab(el){
|
|
1996
1868
|
var h='<div style="max-width:800px;margin:0 auto;padding:20px">';
|
|
1997
|
-
h+='<h2 style="font-family:var(--term);color:var(--amber);font-size:18px;margin-bottom:16px">AgentMessenger
|
|
1869
|
+
h+='<h2 style="font-family:var(--term);color:var(--amber);font-size:18px;margin-bottom:16px">AgentMessenger - Encrypted Communication</h2>';
|
|
1998
1870
|
|
|
1999
1871
|
// Channel list
|
|
2000
1872
|
h+='<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap">';
|
|
@@ -2015,17 +1887,17 @@ function renderCollab(el){
|
|
|
2015
1887
|
if(!collabActiveChannel){
|
|
2016
1888
|
h+='<div style="padding:20px">';
|
|
2017
1889
|
h+='<div style="text-align:center;margin-bottom:20px">';
|
|
2018
|
-
h+='<div style="font-size:32px;margin-bottom:8px"
|
|
1890
|
+
h+='<div style="font-size:32px;margin-bottom:8px">🔐</div>';
|
|
2019
1891
|
h+='<div style="font-family:var(--term);color:var(--amber);font-size:16px;margin-bottom:4px">Alexandria</div>';
|
|
2020
1892
|
h+='<div style="color:var(--dim);font-size:11px">E2E encrypted messaging for AI agents and teams</div>';
|
|
2021
1893
|
h+='</div>';
|
|
2022
1894
|
h+='<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:14px;margin-bottom:10px">';
|
|
2023
1895
|
h+='<div style="color:var(--amber);font-size:10px;font-family:var(--term);letter-spacing:1px;margin-bottom:8px">HOW TO USE</div>';
|
|
2024
|
-
h+='<div style="color:var(--fg);font-size:12px;font-family:var(--mono);margin-bottom:4px"><b>1. Create a channel</b>
|
|
1896
|
+
h+='<div style="color:var(--fg);font-size:12px;font-family:var(--mono);margin-bottom:4px"><b>1. Create a channel</b> - Click [+ Create Channel] above. Give it a name.</div>';
|
|
2025
1897
|
h+='<div style="color:var(--dim);font-size:11px;margin-left:4px;margin-bottom:6px">You get an invite code. Share it with your team or another AI session.</div>';
|
|
2026
|
-
h+='<div style="color:var(--fg);font-size:12px;font-family:var(--mono);margin-bottom:4px"><b>2. Others join</b>
|
|
1898
|
+
h+='<div style="color:var(--fg);font-size:12px;font-family:var(--mono);margin-bottom:4px"><b>2. Others join</b> - They click [Join Channel] and paste the invite code.</div>';
|
|
2027
1899
|
h+='<div style="color:var(--dim);font-size:11px;margin-left:4px;margin-bottom:6px">Works from this web UI, the Android app, or the CLI.</div>';
|
|
2028
|
-
h+='<div style="color:var(--fg);font-size:12px;font-family:var(--mono);margin-bottom:4px"><b>3. Chat encrypted</b>
|
|
1900
|
+
h+='<div style="color:var(--fg);font-size:12px;font-family:var(--mono);margin-bottom:4px"><b>3. Chat encrypted</b> - All messages are E2E encrypted. The server sees only ciphertext.</div>';
|
|
2029
1901
|
h+='</div>';
|
|
2030
1902
|
h+='<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:14px;margin-bottom:10px">';
|
|
2031
1903
|
h+='<div style="color:var(--amber);font-size:10px;font-family:var(--term);letter-spacing:1px;margin-bottom:8px">FROM CLI (same channels)</div>';
|
|
@@ -2037,10 +1909,10 @@ function renderCollab(el){
|
|
|
2037
1909
|
h+='<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:14px">';
|
|
2038
1910
|
h+='<div style="color:var(--amber);font-size:10px;font-family:var(--term);letter-spacing:1px;margin-bottom:8px">USE CASES</div>';
|
|
2039
1911
|
h+='<div style="color:var(--dim);font-size:11px;font-family:var(--mono);line-height:18px">';
|
|
2040
|
-
h+='
|
|
2041
|
-
h+='
|
|
2042
|
-
h+='
|
|
2043
|
-
h+='
|
|
1912
|
+
h+='• Two Claude Code instances sharing context in real-time<br>';
|
|
1913
|
+
h+='• Team sharing AI analysis privately (security audits, code reviews)<br>';
|
|
1914
|
+
h+='• Coordinating deployments between AI agents<br>';
|
|
1915
|
+
h+='• Security briefings with auto-delete TTL</div>';
|
|
2044
1916
|
h+='</div>';
|
|
2045
1917
|
h+='</div>';
|
|
2046
1918
|
}
|
|
@@ -2060,7 +1932,7 @@ function renderCollab(el){
|
|
|
2060
1932
|
|
|
2061
1933
|
function renderCollabChannelList(){
|
|
2062
1934
|
var el=document.getElementById('collabChannelList');if(!el)return;
|
|
2063
|
-
if(collabChannels.length===0){el.innerHTML='<div style="color:var(--dim);font-size:11px;padding:8px">No channels yet
|
|
1935
|
+
if(collabChannels.length===0){el.innerHTML='<div style="color:var(--dim);font-size:11px;padding:8px">No channels yet - click [+ Create Channel] to start, or [Join Channel] to enter an invite code.</div>';return;}
|
|
2064
1936
|
var h='';
|
|
2065
1937
|
for(var i=0;i<collabChannels.length;i++){
|
|
2066
1938
|
var ch=collabChannels[i];
|
|
@@ -2113,7 +1985,7 @@ function collabDeleteChannel(id){
|
|
|
2113
1985
|
function collabSelect(id){
|
|
2114
1986
|
collabActiveChannel=id;
|
|
2115
1987
|
collabLoadMessages();
|
|
2116
|
-
// No polling
|
|
1988
|
+
// No polling - messages arrive via WebSocket in real-time
|
|
2117
1989
|
if(collabPolling)clearInterval(collabPolling);
|
|
2118
1990
|
collabPolling=null;
|
|
2119
1991
|
}
|
|
@@ -2147,7 +2019,7 @@ function collabSend(){
|
|
|
2147
2019
|
inp.value='';
|
|
2148
2020
|
apiPost('/api/collab/send',{channelId:collabActiveChannel,message:msg}).then(function(r){
|
|
2149
2021
|
if(r.error){alert(r.error);return;}
|
|
2150
|
-
// Message will arrive via WebSocket
|
|
2022
|
+
// Message will arrive via WebSocket - no need to reload
|
|
2151
2023
|
});
|
|
2152
2024
|
}
|
|
2153
2025
|
|
|
@@ -2201,58 +2073,150 @@ function renderAgents(el){
|
|
|
2201
2073
|
}
|
|
2202
2074
|
var agentFilter=null;
|
|
2203
2075
|
|
|
2076
|
+
function openAgentEditor(mode, prefill) {
|
|
2077
|
+
// mode: 'create' | 'edit'
|
|
2078
|
+
var isEdit = mode === 'edit';
|
|
2079
|
+
var overlay = document.getElementById('agentEditorOverlay');
|
|
2080
|
+
overlay.innerHTML =
|
|
2081
|
+
'<div class="modal" style="max-width:560px">' +
|
|
2082
|
+
'<div class="modal__header">' +
|
|
2083
|
+
'<h2 style="color:var(--green)">'+(isEdit?'\u270F\uFE0F Edit Agent':'\u2795 New Agent')+'</h2>' +
|
|
2084
|
+
'<button class="modal__close" onclick="closeAgentEditor()">×</button>' +
|
|
2085
|
+
'</div>' +
|
|
2086
|
+
'<div class="modal__body" style="display:flex;flex-direction:column;gap:12px">' +
|
|
2087
|
+
(isEdit ? '' :
|
|
2088
|
+
'<div><label style="font-size:11px;color:var(--dim);display:block;margin-bottom:4px">Agent name (lowercase, no spaces)</label>' +
|
|
2089
|
+
'<input id="aeNameField" placeholder="my-agent" style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:13px"></div>'
|
|
2090
|
+
) +
|
|
2091
|
+
'<div><label style="font-size:11px;color:var(--dim);display:block;margin-bottom:4px">Tagline</label>' +
|
|
2092
|
+
'<input id="aeTaglineField" value="'+(prefill&&prefill.tagline?esc(prefill.tagline):'')+'" placeholder="Short description of what this agent does" style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:13px"></div>' +
|
|
2093
|
+
'<div><label style="font-size:11px;color:var(--dim);display:block;margin-bottom:4px">System Prompt</label>' +
|
|
2094
|
+
'<textarea id="aeSysPromptField" placeholder="You are an expert in... Your job is to..." style="width:100%;min-height:160px;padding:10px;border-radius:6px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:12px;font-family:var(--mono);resize:vertical">'+(prefill&&prefill.systemPrompt?esc(prefill.systemPrompt):'')+'</textarea></div>' +
|
|
2095
|
+
'<div id="aeError" style="color:var(--red);font-size:11px;display:none"></div>' +
|
|
2096
|
+
'</div>' +
|
|
2097
|
+
'<div class="modal__footer">' +
|
|
2098
|
+
'<button class="btn btn--secondary" onclick="closeAgentEditor()">Cancel</button>' +
|
|
2099
|
+
'<button class="btn btn--primary" id="aeSubmitBtn" onclick="submitAgentEditor(\\''+mode+'\\',\\''+esc((prefill&&prefill.name)||'')+'\\')">'+(isEdit?'Save Changes':'Create Agent')+'</button>' +
|
|
2100
|
+
'</div>' +
|
|
2101
|
+
'</div>';
|
|
2102
|
+
overlay.style.display='flex';
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
function closeAgentEditor(){
|
|
2106
|
+
var overlay=document.getElementById('agentEditorOverlay');
|
|
2107
|
+
if(overlay){overlay.style.display='none';overlay.innerHTML='';}
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
function submitAgentEditor(mode, existingName){
|
|
2111
|
+
var tagline=(document.getElementById('aeTaglineField')||{}).value||'';
|
|
2112
|
+
var sysPrompt=(document.getElementById('aeSysPromptField')||{}).value||'';
|
|
2113
|
+
var errEl=document.getElementById('aeError');
|
|
2114
|
+
var btn=document.getElementById('aeSubmitBtn');
|
|
2115
|
+
if(!tagline.trim()||!sysPrompt.trim()){if(errEl){errEl.textContent='Tagline and system prompt are required.';errEl.style.display='';}return;}
|
|
2116
|
+
|
|
2117
|
+
if(mode==='create'){
|
|
2118
|
+
var nameEl=document.getElementById('aeNameField');
|
|
2119
|
+
var name=(nameEl?nameEl.value:'').toLowerCase().replace(/[^a-z0-9_-]/g,'');
|
|
2120
|
+
if(!name){if(errEl){errEl.textContent='Agent name is required (lowercase, no spaces).';errEl.style.display='';}return;}
|
|
2121
|
+
if(btn)btn.disabled=true;
|
|
2122
|
+
apiPost('/api/agents',{name:name,tagline:tagline,systemPrompt:sysPrompt}).then(function(r){
|
|
2123
|
+
if(r&&r.ok){closeAgentEditor();showToast('success','Agent Created',name.toUpperCase()+' is ready to use');loadAgents().then(function(){renderAgents(document.getElementById('content'));});}
|
|
2124
|
+
else{if(errEl){errEl.textContent='Error: '+(r&&r.error||'Unknown');errEl.style.display='';}if(btn)btn.disabled=false;}
|
|
2125
|
+
});
|
|
2126
|
+
} else {
|
|
2127
|
+
if(btn)btn.disabled=true;
|
|
2128
|
+
fetch(API+'/api/agents/'+existingName,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({tagline:tagline,systemPrompt:sysPrompt,category:'custom'})}).then(function(r){return r.json();}).then(function(r){
|
|
2129
|
+
if(r&&r.ok){closeAgentEditor();showToast('success','Agent Updated',existingName.toUpperCase()+' updated');loadAgents().then(function(){renderAgents(document.getElementById('content'));});}
|
|
2130
|
+
else{if(errEl){errEl.textContent='Error: '+(r&&r.error||'Unknown');errEl.style.display='';}if(btn)btn.disabled=false;}
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2204
2135
|
function showCreateAgentForm(){
|
|
2205
|
-
|
|
2206
|
-
if(!name)return;
|
|
2207
|
-
name=name.toLowerCase().replace(/[^a-z0-9_-]/g,'');
|
|
2208
|
-
if(!name)return;
|
|
2209
|
-
var tagline=prompt('Tagline (short description):');
|
|
2210
|
-
if(!tagline)return;
|
|
2211
|
-
var sysPrompt=prompt('System prompt (agent personality & instructions):');
|
|
2212
|
-
if(!sysPrompt)return;
|
|
2213
|
-
apiPost('/api/agents',{name:name,tagline:tagline,systemPrompt:sysPrompt}).then(function(r){
|
|
2214
|
-
if(r&&r.ok){showToast('success','Agent Created',name.toUpperCase()+' is ready to use');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
|
|
2215
|
-
else{alert('Error: '+(r&&r.error||'Unknown'));}
|
|
2216
|
-
});
|
|
2136
|
+
openAgentEditor('create', null);
|
|
2217
2137
|
}
|
|
2218
2138
|
|
|
2219
2139
|
function editAgent(name){
|
|
2220
|
-
fetch('/api/agents/'+name).then(function(r){return r.json()}).then(function(data){
|
|
2221
|
-
|
|
2222
|
-
if(newTagline===null)return;
|
|
2223
|
-
var newPrompt=prompt('System prompt:',data.systemPrompt||'');
|
|
2224
|
-
if(newPrompt===null)return;
|
|
2225
|
-
fetch('/api/agents/'+name,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({tagline:newTagline,systemPrompt:newPrompt,category:data.category||'custom'})}).then(function(r){return r.json()}).then(function(r){
|
|
2226
|
-
if(r&&r.ok){showToast('success','Agent Updated',name.toUpperCase()+' updated');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
|
|
2227
|
-
else{alert('Error: '+(r&&r.error||'Unknown'));}
|
|
2228
|
-
});
|
|
2140
|
+
fetch(API+'/api/agents/'+name).then(function(r){return r.json();}).then(function(data){
|
|
2141
|
+
openAgentEditor('edit', {name:name, tagline:data.tagline||'', systemPrompt:data.systemPrompt||''});
|
|
2229
2142
|
});
|
|
2230
2143
|
}
|
|
2231
2144
|
|
|
2232
2145
|
function deleteAgent(name){
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2146
|
+
// Inline confirm via toast-style overlay
|
|
2147
|
+
var overlay=document.getElementById('agentEditorOverlay');
|
|
2148
|
+
overlay.innerHTML=
|
|
2149
|
+
'<div class="modal" style="max-width:380px">' +
|
|
2150
|
+
'<div class="modal__header"><h2 style="color:var(--red)">\u26A0\uFE0F Delete Agent</h2><button class="modal__close" onclick="closeAgentEditor()">×</button></div>' +
|
|
2151
|
+
'<div class="modal__body"><p style="color:var(--bright);font-size:14px">Delete <strong>'+esc(name)+'</strong>?</p><p style="color:var(--dim);font-size:12px">This cannot be undone.</p></div>' +
|
|
2152
|
+
'<div class="modal__footer">' +
|
|
2153
|
+
'<button class="btn btn--secondary" onclick="closeAgentEditor()">Cancel</button>' +
|
|
2154
|
+
'<button class="btn btn--primary" style="background:var(--red)" onclick="confirmDeleteAgent(\\''+esc(name)+'\\')">Delete</button>' +
|
|
2155
|
+
'</div>' +
|
|
2156
|
+
'</div>';
|
|
2157
|
+
overlay.style.display='flex';
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
function confirmDeleteAgent(name){
|
|
2161
|
+
closeAgentEditor();
|
|
2162
|
+
fetch(API+'/api/agents/'+name,{method:'DELETE'}).then(function(r){return r.json();}).then(function(r){
|
|
2163
|
+
if(r&&r.ok){showToast('success','Agent Deleted',name+' removed');loadAgents().then(function(){renderAgents(document.getElementById('content'));});}
|
|
2164
|
+
else{showToast('error','Delete Failed',r&&r.error||'Unknown error');}
|
|
2237
2165
|
});
|
|
2238
2166
|
}
|
|
2239
2167
|
|
|
2168
|
+
function renderAgentMessages(){
|
|
2169
|
+
var el=document.getElementById('agentMessages');
|
|
2170
|
+
if(!el)return;
|
|
2171
|
+
if(agentChatHistory.length===0){
|
|
2172
|
+
var icon=AGENT_ICONS[selectedAgent]||'\uD83E\uDD16';
|
|
2173
|
+
var desc=AGENT_DESCRIPTIONS[selectedAgent]||'';
|
|
2174
|
+
el.innerHTML='<div style="text-align:center;padding:24px 8px;color:var(--dim);font-size:12px">'+
|
|
2175
|
+
'<div style="font-size:28px;margin-bottom:6px">'+icon+'</div>'+
|
|
2176
|
+
'<div style="color:var(--fg);font-weight:600;margin-bottom:4px">'+esc(selectedAgent.toUpperCase())+'</div>'+
|
|
2177
|
+
(desc?'<div>'+esc(desc)+'</div>':'')+
|
|
2178
|
+
'</div>';
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
el.innerHTML=agentChatHistory.map(function(m){
|
|
2182
|
+
var cls='agent-chat__bubble agent-chat__bubble--'+(m.role==='user'?'user':'agent');
|
|
2183
|
+
var content;
|
|
2184
|
+
if(m.role==='user'){
|
|
2185
|
+
content=esc(m.text);
|
|
2186
|
+
} else if(m.waiting||(m.streaming&&!m.text)){
|
|
2187
|
+
content='<span class="thinking-dots"><span></span><span></span><span></span></span>';
|
|
2188
|
+
} else if(m.streaming){
|
|
2189
|
+
content=esc(m.text)+'\u258B';
|
|
2190
|
+
} else {
|
|
2191
|
+
content=renderMd(m.text);
|
|
2192
|
+
}
|
|
2193
|
+
return '<div class="'+cls+' '+(m.role==='agent'?'md-body':'')+'">'+content+'</div>';
|
|
2194
|
+
}).join('');
|
|
2195
|
+
el.scrollTop=el.scrollHeight;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2240
2198
|
function openAgent(name,display){
|
|
2241
2199
|
selectedAgent=name;
|
|
2200
|
+
agentChatHistory=[];
|
|
2242
2201
|
attachedFileContent=null;attachedFileName=null;
|
|
2243
|
-
|
|
2202
|
+
var icon=AGENT_ICONS[name.toLowerCase()]||'\u{1F916}';
|
|
2203
|
+
var desc=AGENT_DESCRIPTIONS[name.toLowerCase()]||'';
|
|
2204
|
+
var nameEl=document.getElementById('modalName');
|
|
2205
|
+
nameEl.innerHTML=icon+' <span style="color:var(--green)">'+esc(display||name)+'</span>';
|
|
2206
|
+
var subEl=document.getElementById('modalAgentDesc');
|
|
2207
|
+
if(subEl){subEl.textContent=desc;}
|
|
2244
2208
|
document.getElementById('modalPrompt').value='';
|
|
2245
|
-
document.getElementById('
|
|
2246
|
-
document.getElementById('
|
|
2247
|
-
document.getElementById('
|
|
2248
|
-
document.getElementById('
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
document.
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
if(sendBtn)sendBtn.style.display='';
|
|
2209
|
+
document.getElementById('agentFileInfo').style.display='none';
|
|
2210
|
+
document.getElementById('agentFileInput').value='';
|
|
2211
|
+
document.getElementById('agentFileDropZone').style.borderColor='';
|
|
2212
|
+
var askBtn=document.getElementById('agentAskBtn');
|
|
2213
|
+
if(askBtn){askBtn.disabled=false;askBtn.textContent='Send';}
|
|
2214
|
+
// Ensure footer is visible (may be hidden from calendar day-detail view)
|
|
2215
|
+
var footer=document.querySelector('.agent-chat__footer');
|
|
2216
|
+
if(footer)footer.style.display='';
|
|
2217
|
+
renderAgentMessages();
|
|
2255
2218
|
document.getElementById('agentModal').classList.add('modal-overlay--open');
|
|
2219
|
+
setTimeout(function(){var i=document.getElementById('modalPrompt');if(i)i.focus();},100);
|
|
2256
2220
|
}
|
|
2257
2221
|
function closeModal(){
|
|
2258
2222
|
document.getElementById('agentModal').classList.remove('modal-overlay--open');
|
|
@@ -2306,11 +2270,14 @@ function renderSettings(el) {
|
|
|
2306
2270
|
['profile-notes', 'Notes', 'Anything else agents should know about you'],
|
|
2307
2271
|
]) +
|
|
2308
2272
|
'<div style="padding:12px 16px;margin-bottom:16px;background:var(--amberdim);border:1px solid var(--amber3);border-radius:8px"><span style="font-family:var(--term);color:var(--amber);font-size:13px;font-weight:700">NHA Free (Liara)</span><div style="font-size:11px;color:var(--dim);margin:4px 0 8px">Powered by Qwen3 32B. Free, no API key needed. Slower (5-15s).</div><button onclick="apiPost(\\x27/api/config\\x27,{key:\\x27provider\\x27,value:\\x27nha\\x27}).then(function(){location.reload()})" style="padding:6px 16px;background:var(--amber3);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-family:var(--mono);font-size:11px;font-weight:700">Use NHA Free</button></div>' +
|
|
2273
|
+
settingsSection('language', 'Language / Lingua', 'Set the UI language. Applies on reload.', [
|
|
2274
|
+
['lang', 'Language', 'en / it / es / fr / de / pt / zh / ja / ar / hi / ru / nl / pl / tr / ko / sv / da / fi / no / cs'],
|
|
2275
|
+
]) +
|
|
2309
2276
|
settingsSection('llm', 'LLM Provider', 'Or use your own API key for faster, more capable responses.', [
|
|
2310
2277
|
['provider', 'Provider', 'nha (free) / anthropic / openai / gemini / deepseek / grok / mistral'],
|
|
2311
2278
|
['key', 'API Key', 'Not needed for NHA Free', true],
|
|
2312
2279
|
['model', 'Model', 'Leave empty for default'],
|
|
2313
|
-
['thinking', 'Extended Thinking', 'on / off
|
|
2280
|
+
['thinking', 'Extended Thinking', 'on / off - Qwen3 reasoning mode (NHA Free only)'],
|
|
2314
2281
|
]) +
|
|
2315
2282
|
settingsSection('responder', 'Message Responder', 'Auto-reply to Telegram and Discord messages.', [
|
|
2316
2283
|
['telegram-bot-token', 'Telegram Bot Token', 'Get from @BotFather', true],
|
|
@@ -2321,7 +2288,6 @@ function renderSettings(el) {
|
|
|
2321
2288
|
['summary-time', 'Summary Time', '18:00'],
|
|
2322
2289
|
['meeting-alert', 'Meeting Alert (minutes)', '30'],
|
|
2323
2290
|
]) +
|
|
2324
|
-
renderEmailAccountsSection() +
|
|
2325
2291
|
'<div class="card" style="margin-top:16px"><div class="card__title">Google Account</div>' +
|
|
2326
2292
|
'<div style="font-size:11px;color:var(--dim);margin-bottom:8px">Connect Gmail, Calendar, Drive, Contacts, and Tasks. Opens a browser window for Google sign-in.</div>' +
|
|
2327
2293
|
(settingsData.hasGoogle ? '<div style="color:var(--green);font-size:12px;margin-bottom:8px">\\u2705 Connected</div>' : '') +
|
|
@@ -2342,92 +2308,6 @@ function connectGoogle() {
|
|
|
2342
2308
|
});
|
|
2343
2309
|
}
|
|
2344
2310
|
|
|
2345
|
-
function renderEmailAccountsSection() {
|
|
2346
|
-
var accounts = settingsData.emailAccounts || [];
|
|
2347
|
-
var h = '<div class="card" style="margin-bottom:16px"><div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">Email Accounts (IMAP/SMTP)</div>';
|
|
2348
|
-
h += '<div style="font-size:11px;color:var(--dim);margin-bottom:12px">Add email accounts for multi-provider inbox. Read-only IMAP (never deletes from server) + SMTP for sending. Works alongside Gmail.</div>';
|
|
2349
|
-
|
|
2350
|
-
if (accounts.length > 0) {
|
|
2351
|
-
for (var i = 0; i < accounts.length; i++) {
|
|
2352
|
-
var a = accounts[i];
|
|
2353
|
-
h += '<div style="padding:8px 12px;margin-bottom:8px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;display:flex;align-items:center;justify-content:space-between">';
|
|
2354
|
-
h += '<div><span style="color:var(--green);font-weight:700;font-size:12px">' + esc(a.label || 'Email') + '</span>';
|
|
2355
|
-
h += '<span style="color:var(--dim);font-size:11px;margin-left:8px">' + esc(a.address || '') + '</span>';
|
|
2356
|
-
if (a.isDefault) h += '<span style="color:var(--amber);font-size:9px;margin-left:6px">(default)</span>';
|
|
2357
|
-
h += '</div>';
|
|
2358
|
-
h += '<div><button onclick="setDefaultEmail(' + i + ')" style="background:none;border:1px solid var(--border);color:var(--dim);padding:3px 8px;border-radius:4px;cursor:pointer;font-size:10px;margin-right:4px" title="Set as default">Default</button>';
|
|
2359
|
-
h += '<button onclick="removeEmailAccount(' + i + ')" style="background:none;border:1px solid var(--red3);color:var(--red);padding:3px 8px;border-radius:4px;cursor:pointer;font-size:10px" title="Remove">Remove</button></div></div>';
|
|
2360
|
-
}
|
|
2361
|
-
} else {
|
|
2362
|
-
h += '<div style="color:var(--dim);font-size:11px;padding:8px 0">No email accounts configured.</div>';
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
h += '<div style="margin-top:12px;border-top:1px solid var(--border);padding-top:12px">';
|
|
2366
|
-
h += '<div style="font-size:12px;color:var(--fg);font-weight:700;margin-bottom:8px">Add Email Account</div>';
|
|
2367
|
-
var fields = [
|
|
2368
|
-
['ea_label', 'Label', 'e.g. Work, Personal'],
|
|
2369
|
-
['ea_address', 'Email Address', 'e.g. you@company.com'],
|
|
2370
|
-
['ea_imap_host', 'IMAP Host', 'e.g. imap.company.com'],
|
|
2371
|
-
['ea_imap_port', 'IMAP Port', '993'],
|
|
2372
|
-
['ea_imap_user', 'IMAP User', 'Usually your email address'],
|
|
2373
|
-
['ea_imap_pass', 'IMAP Password', 'App password recommended', true],
|
|
2374
|
-
['ea_smtp_host', 'SMTP Host', 'e.g. smtp.company.com'],
|
|
2375
|
-
['ea_smtp_port', 'SMTP Port', '587'],
|
|
2376
|
-
['ea_smtp_user', 'SMTP User', 'Usually same as IMAP'],
|
|
2377
|
-
['ea_smtp_pass', 'SMTP Password', 'Usually same as IMAP', true],
|
|
2378
|
-
];
|
|
2379
|
-
for (var i = 0; i < fields.length; i++) {
|
|
2380
|
-
var f = fields[i];
|
|
2381
|
-
h += '<div style="margin-bottom:6px"><label style="display:block;font-size:10px;color:var(--dim);margin-bottom:2px">' + esc(f[1]) + '</label>';
|
|
2382
|
-
h += '<input id="' + f[0] + '" type="' + (f[3] ? 'password' : 'text') + '" placeholder="' + esc(f[2]) + '" style="width:100%;padding:6px 10px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg);font-size:12px;font-family:var(--mono)"></div>';
|
|
2383
|
-
}
|
|
2384
|
-
h += '<button onclick="addEmailAccount()" style="margin-top:8px;padding:8px 20px;background:var(--green3);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:700;font-size:12px">Add Account</button>';
|
|
2385
|
-
h += '<div id="emailAccountStatus" style="margin-top:6px;font-size:10px;color:var(--dim)"></div>';
|
|
2386
|
-
h += '</div></div>';
|
|
2387
|
-
return h;
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
function addEmailAccount() {
|
|
2391
|
-
var label = document.getElementById('ea_label').value.trim();
|
|
2392
|
-
var address = document.getElementById('ea_address').value.trim();
|
|
2393
|
-
var imapHost = document.getElementById('ea_imap_host').value.trim();
|
|
2394
|
-
var imapPort = document.getElementById('ea_imap_port').value.trim() || '993';
|
|
2395
|
-
var imapUser = document.getElementById('ea_imap_user').value.trim() || address;
|
|
2396
|
-
var imapPass = document.getElementById('ea_imap_pass').value;
|
|
2397
|
-
var smtpHost = document.getElementById('ea_smtp_host').value.trim();
|
|
2398
|
-
var smtpPort = document.getElementById('ea_smtp_port').value.trim() || '587';
|
|
2399
|
-
var smtpUser = document.getElementById('ea_smtp_user').value.trim() || imapUser;
|
|
2400
|
-
var smtpPass = document.getElementById('ea_smtp_pass').value || imapPass;
|
|
2401
|
-
|
|
2402
|
-
if (!address || !imapHost) {
|
|
2403
|
-
var s = document.getElementById('emailAccountStatus');
|
|
2404
|
-
if (s) { s.textContent = 'Email address and IMAP host are required.'; s.style.color = 'var(--red)'; }
|
|
2405
|
-
return;
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
var val = [label, address, imapHost, imapPort, imapUser, imapPass, smtpHost, smtpPort, smtpUser, smtpPass].join('|');
|
|
2409
|
-
apiPost('/api/config', { key: 'email-add', value: val }).then(function() {
|
|
2410
|
-
var s = document.getElementById('emailAccountStatus');
|
|
2411
|
-
if (s) { s.textContent = 'Account added!'; s.style.color = 'var(--green)'; }
|
|
2412
|
-
settingsLoaded = false;
|
|
2413
|
-
renderSettings(document.getElementById('mainContent'));
|
|
2414
|
-
});
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
function removeEmailAccount(idx) {
|
|
2418
|
-
apiPost('/api/config', { key: 'email-remove', value: String(idx) }).then(function() {
|
|
2419
|
-
settingsLoaded = false;
|
|
2420
|
-
renderSettings(document.getElementById('mainContent'));
|
|
2421
|
-
});
|
|
2422
|
-
}
|
|
2423
|
-
|
|
2424
|
-
function setDefaultEmail(idx) {
|
|
2425
|
-
apiPost('/api/config', { key: 'email-default', value: String(idx) }).then(function() {
|
|
2426
|
-
settingsLoaded = false;
|
|
2427
|
-
renderSettings(document.getElementById('mainContent'));
|
|
2428
|
-
});
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
2311
|
function settingsSection(id, title, desc, fields) {
|
|
2432
2312
|
var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
|
|
2433
2313
|
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +
|
|
@@ -2445,7 +2325,7 @@ function settingsSection(id, title, desc, fields) {
|
|
|
2445
2325
|
if (key === 'provider') {
|
|
2446
2326
|
// Dropdown for provider selection
|
|
2447
2327
|
var providers = [
|
|
2448
|
-
{value:'nha',label:'NHA Free (Liara)
|
|
2328
|
+
{value:'nha',label:'NHA Free (Liara) - no API key needed'},
|
|
2449
2329
|
{value:'anthropic',label:'Anthropic (Claude)'},
|
|
2450
2330
|
{value:'openai',label:'OpenAI (GPT-4)'},
|
|
2451
2331
|
{value:'gemini',label:'Google (Gemini)'},
|
|
@@ -2463,8 +2343,8 @@ function settingsSection(id, title, desc, fields) {
|
|
|
2463
2343
|
} else if (key === 'thinking') {
|
|
2464
2344
|
// Dropdown for thinking toggle
|
|
2465
2345
|
h += '<select style="width:100%;padding:8px 12px;font-size:13px;background:var(--bg);color:var(--fg);border:1px solid var(--border2);border-radius:var(--r)" data-config-key="thinking" data-section="' + esc(id) + '">' +
|
|
2466
|
-
'<option value="off"' + (currentVal !== 'on' ? ' selected' : '') + '>Off
|
|
2467
|
-
'<option value="on"' + (currentVal === 'on' ? ' selected' : '') + '>On
|
|
2346
|
+
'<option value="off"' + (currentVal !== 'on' ? ' selected' : '') + '>Off - faster responses</option>' +
|
|
2347
|
+
'<option value="on"' + (currentVal === 'on' ? ' selected' : '') + '>On - extended reasoning (NHA Free only)</option>' +
|
|
2468
2348
|
'</select>';
|
|
2469
2349
|
} else {
|
|
2470
2350
|
h += '<input type="' + (isSecret ? 'password' : 'text') + '" ' +
|
|
@@ -2542,46 +2422,102 @@ function handleFileSelect(input) {
|
|
|
2542
2422
|
if (file) readFile(file);
|
|
2543
2423
|
}
|
|
2544
2424
|
function readFile(file) {
|
|
2425
|
+
var infoId = document.getElementById('agentFileInfo') ? 'agentFileInfo' : 'fileInfo';
|
|
2426
|
+
var dropId = document.getElementById('agentFileDropZone') ? 'agentFileDropZone' : 'fileDropZone';
|
|
2427
|
+
var infoEl = document.getElementById(infoId);
|
|
2428
|
+
var dropEl = document.getElementById(dropId);
|
|
2545
2429
|
if (file.size > 500000) {
|
|
2546
|
-
|
|
2547
|
-
document.getElementById('fileInfo').textContent = 'File too large (max 500KB)';
|
|
2548
|
-
document.getElementById('fileInfo').style.color = 'var(--red)';
|
|
2430
|
+
if(infoEl){infoEl.style.display='block';infoEl.style.color='var(--red)';infoEl.textContent='File too large (max 500KB)';}
|
|
2549
2431
|
return;
|
|
2550
2432
|
}
|
|
2551
2433
|
var reader = new FileReader();
|
|
2552
2434
|
reader.onload = function(e) {
|
|
2553
2435
|
attachedFileContent = e.target.result;
|
|
2554
2436
|
attachedFileName = file.name;
|
|
2555
|
-
var
|
|
2556
|
-
|
|
2557
|
-
info.style.color = 'var(--cyan)';
|
|
2558
|
-
info.textContent = 'Attached: ' + file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)';
|
|
2559
|
-
document.getElementById('fileDropZone').style.borderColor = 'var(--green)';
|
|
2437
|
+
if(infoEl){infoEl.style.display='block';infoEl.style.color='var(--cyan)';infoEl.textContent='Attached: '+file.name+' ('+(file.size/1024).toFixed(1)+' KB)';}
|
|
2438
|
+
if(dropEl){dropEl.style.borderColor='var(--green)';}
|
|
2560
2439
|
};
|
|
2561
2440
|
reader.readAsText(file);
|
|
2562
2441
|
}
|
|
2563
2442
|
|
|
2443
|
+
var agentAbortController = null;
|
|
2444
|
+
|
|
2564
2445
|
function askAgent(){
|
|
2565
2446
|
var p=document.getElementById('modalPrompt').value.trim();if(!p||!selectedAgent)return;
|
|
2566
|
-
var resp=document.getElementById('modalResponse');
|
|
2567
|
-
resp.style.display='block';resp.textContent='Thinking...';
|
|
2568
2447
|
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2448
|
+
// Add user message to history
|
|
2449
|
+
agentChatHistory.push({role:'user',text:p});
|
|
2450
|
+
// Add waiting placeholder — shows dots until first token arrives
|
|
2451
|
+
agentChatHistory.push({role:'agent',text:'',waiting:true});
|
|
2452
|
+
renderAgentMessages();
|
|
2453
|
+
document.getElementById('modalPrompt').value='';
|
|
2454
|
+
|
|
2455
|
+
// Abort any previous stream
|
|
2456
|
+
if(agentAbortController){try{agentAbortController.abort();}catch(e){}}
|
|
2457
|
+
agentAbortController=new AbortController();
|
|
2458
|
+
|
|
2459
|
+
var payload={agent:selectedAgent,prompt:p};
|
|
2460
|
+
if(attachedFileContent){payload.fileContent=attachedFileContent;payload.fileName=attachedFileName;}
|
|
2461
|
+
|
|
2462
|
+
// Disable Send button while streaming
|
|
2463
|
+
var askBtn=document.getElementById('agentAskBtn');
|
|
2464
|
+
if(askBtn){askBtn.disabled=true;askBtn.textContent='...';}
|
|
2465
|
+
|
|
2466
|
+
var agentMsgIdx=agentChatHistory.length-1; // index of the streaming placeholder
|
|
2467
|
+
|
|
2468
|
+
function updateStreamingBubble(text,done){
|
|
2469
|
+
agentChatHistory[agentMsgIdx]={role:'agent',text:text,waiting:false,streaming:!done};
|
|
2470
|
+
renderAgentMessages();
|
|
2573
2471
|
}
|
|
2574
2472
|
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2473
|
+
fetch(API+'/api/ask/stream',{
|
|
2474
|
+
method:'POST',
|
|
2475
|
+
headers:{'Content-Type':'application/json'},
|
|
2476
|
+
body:JSON.stringify(payload),
|
|
2477
|
+
signal:agentAbortController.signal
|
|
2478
|
+
}).then(function(response){
|
|
2479
|
+
var reader=response.body.getReader();
|
|
2480
|
+
var decoder=new TextDecoder();
|
|
2481
|
+
var buf='';
|
|
2482
|
+
var accumulated='';
|
|
2483
|
+
function pump(){
|
|
2484
|
+
return reader.read().then(function(result){
|
|
2485
|
+
if(result.done){
|
|
2486
|
+
updateStreamingBubble(accumulated||'(empty response)',true);
|
|
2487
|
+
if(askBtn){askBtn.disabled=false;askBtn.textContent='Send';}
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
buf+=decoder.decode(result.value,{stream:true});
|
|
2491
|
+
var lines=buf.split(String.fromCharCode(10));
|
|
2492
|
+
buf=lines.pop();
|
|
2493
|
+
lines.forEach(function(line){
|
|
2494
|
+
if(line.indexOf('data: ')!==0)return;
|
|
2495
|
+
try{
|
|
2496
|
+
var ev=JSON.parse(line.slice(6));
|
|
2497
|
+
if(ev.token){accumulated+=ev.token;updateStreamingBubble(accumulated,false);}
|
|
2498
|
+
if(ev.done){
|
|
2499
|
+
updateStreamingBubble(accumulated||'(empty response)',true);
|
|
2500
|
+
if(askBtn){askBtn.disabled=false;askBtn.textContent='Send';}
|
|
2501
|
+
attachedFileContent=null;attachedFileName=null;
|
|
2502
|
+
document.getElementById('agentFileInfo').style.display='none';
|
|
2503
|
+
document.getElementById('agentFileInput').value='';
|
|
2504
|
+
document.getElementById('agentFileDropZone').style.borderColor='';
|
|
2505
|
+
}
|
|
2506
|
+
if(ev.error){
|
|
2507
|
+
updateStreamingBubble('\u26a0\ufe0f Error: '+ev.error,true);
|
|
2508
|
+
if(askBtn){askBtn.disabled=false;askBtn.textContent='Send';}
|
|
2509
|
+
}
|
|
2510
|
+
}catch(e){}
|
|
2511
|
+
});
|
|
2512
|
+
return pump();
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
return pump();
|
|
2516
|
+
}).catch(function(err){
|
|
2517
|
+
if(err.name!=='AbortError'){
|
|
2518
|
+
updateStreamingBubble('\u26a0\ufe0f Stream error: '+err.message,true);
|
|
2519
|
+
}
|
|
2520
|
+
if(askBtn){askBtn.disabled=false;askBtn.textContent='Send';}
|
|
2585
2521
|
});
|
|
2586
2522
|
}
|
|
2587
2523
|
|
|
@@ -2671,7 +2607,7 @@ function handleDaemonEvent(msg) {
|
|
|
2671
2607
|
break;
|
|
2672
2608
|
|
|
2673
2609
|
case 'security_alert':
|
|
2674
|
-
showToast('security', 'Security Alert', 'Suspicious: ' + (msg.data.from || '') + '
|
|
2610
|
+
showToast('security', 'Security Alert', 'Suspicious: ' + (msg.data.from || '') + ' - ' + (msg.data.subject || ''), 20000);
|
|
2675
2611
|
break;
|
|
2676
2612
|
|
|
2677
2613
|
case 'plan_ready':
|
|
@@ -2692,7 +2628,7 @@ function handleDaemonEvent(msg) {
|
|
|
2692
2628
|
renderCollabMessages();
|
|
2693
2629
|
}
|
|
2694
2630
|
} else {
|
|
2695
|
-
// Not viewing this channel
|
|
2631
|
+
// Not viewing this channel - show badge + toast
|
|
2696
2632
|
collabUnreadCount++;
|
|
2697
2633
|
updateCollabBadge();
|
|
2698
2634
|
showToast('collab', 'AgentMessenger', cm.senderName + ': ' + (cm.content || '').slice(0, 100), 5000);
|
|
@@ -2769,6 +2705,667 @@ function stopVoiceInput() {
|
|
|
2769
2705
|
}
|
|
2770
2706
|
}
|
|
2771
2707
|
|
|
2708
|
+
// ---- STUDIO ----
|
|
2709
|
+
var studioState = {
|
|
2710
|
+
task: '',
|
|
2711
|
+
nodes: [], // [{icon,agent,label,status:'waiting'|'running'|'done'|'error'}]
|
|
2712
|
+
log: [], // [{agent,icon,text,time,type:'agent'|'system'|'error'}]
|
|
2713
|
+
result: '',
|
|
2714
|
+
running: false,
|
|
2715
|
+
planned: false
|
|
2716
|
+
};
|
|
2717
|
+
|
|
2718
|
+
var STUDIO_EXAMPLES = [
|
|
2719
|
+
'Analyze my unread emails and create a priority action plan',
|
|
2720
|
+
'Search the web for AI news today and summarize it in a canvas report',
|
|
2721
|
+
'Check my calendar for this week and suggest how to optimize my schedule',
|
|
2722
|
+
'Review my GitHub notifications and draft responses to open issues',
|
|
2723
|
+
'Search for information about a topic, fact-check it, and write a report'
|
|
2724
|
+
];
|
|
2725
|
+
|
|
2726
|
+
function studioLog(agent, icon, text, type, update) {
|
|
2727
|
+
// update=true: update the last log entry for this agent instead of adding a new one
|
|
2728
|
+
var time = new Date().toLocaleTimeString('en', {hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false});
|
|
2729
|
+
if (update && studioState.log.length) {
|
|
2730
|
+
var last = studioState.log[studioState.log.length - 1];
|
|
2731
|
+
if (last.agent === agent) { last.text = text; last.type = type || last.type; renderStudioLog(); return; }
|
|
2732
|
+
}
|
|
2733
|
+
studioState.log.push({agent: agent, icon: icon, text: text, time: time, type: type||'agent'});
|
|
2734
|
+
renderStudioLog();
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
function renderStudioNodes() {
|
|
2738
|
+
var el = document.getElementById('studioNodes');
|
|
2739
|
+
if (!el) return;
|
|
2740
|
+
if (!studioState.nodes.length) {
|
|
2741
|
+
el.innerHTML = '<div class="studio-canvas__empty"><div class="studio-canvas__empty-icon">⚙</div><div>Describe a task and click Run</div></div>';
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
var html = '<div class="studio-nodes">';
|
|
2745
|
+
studioState.nodes.forEach(function(n, i) {
|
|
2746
|
+
var cls = 'studio-node';
|
|
2747
|
+
if (n.status === 'running') cls += ' studio-node--active';
|
|
2748
|
+
else if (n.status === 'done') cls += ' studio-node--done';
|
|
2749
|
+
else if (n.status === 'error') cls += ' studio-node--error';
|
|
2750
|
+
var statusLabel = {waiting:'◯ wait', running:'▶ running', done:'✓ done', error:'✕ error'}[n.status] || '';
|
|
2751
|
+
html += '<div class="' + cls + '">';
|
|
2752
|
+
html += '<div class="studio-node__circle">' + n.icon + '</div>';
|
|
2753
|
+
html += '<div class="studio-node__label">' + esc(n.label) + '</div>';
|
|
2754
|
+
html += '<div class="studio-node__status studio-node__status--' + n.status + '">' + statusLabel + '</div>';
|
|
2755
|
+
html += '</div>';
|
|
2756
|
+
if (i < studioState.nodes.length - 1) {
|
|
2757
|
+
var next = studioState.nodes[i + 1];
|
|
2758
|
+
var arrowCls = 'studio-arrow';
|
|
2759
|
+
if (n.status === 'done' && next.status === 'running') arrowCls += ' studio-arrow--active';
|
|
2760
|
+
else if (n.status === 'done') arrowCls += ' studio-arrow--done';
|
|
2761
|
+
html += '<div class="' + arrowCls + '">→</div>';
|
|
2762
|
+
}
|
|
2763
|
+
});
|
|
2764
|
+
html += '</div>';
|
|
2765
|
+
el.innerHTML = html;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
function renderStudioLog() {
|
|
2769
|
+
var el = document.getElementById('studioLog');
|
|
2770
|
+
if (!el) return;
|
|
2771
|
+
if (!studioState.log.length) { el.style.display = 'none'; return; }
|
|
2772
|
+
el.style.display = 'block';
|
|
2773
|
+
el.innerHTML = studioState.log.map(function(e) {
|
|
2774
|
+
var cls = 'studio-log-entry' + (e.type === 'system' ? ' studio-log-entry--system' : e.type === 'error' ? ' studio-log-entry--error' : '');
|
|
2775
|
+
return '<div class="' + cls + '">' +
|
|
2776
|
+
'<div class="studio-log-entry__header">' +
|
|
2777
|
+
'<span class="studio-log-entry__icon">' + e.icon + '</span>' +
|
|
2778
|
+
'<span class="studio-log-entry__agent">' + esc(e.agent) + '</span>' +
|
|
2779
|
+
'<span class="studio-log-entry__time">' + esc(e.time) + '</span>' +
|
|
2780
|
+
'</div>' +
|
|
2781
|
+
'<div class="studio-log-entry__text md-body">' + renderMd(e.text) + '</div>' +
|
|
2782
|
+
'</div>';
|
|
2783
|
+
}).join('');
|
|
2784
|
+
el.scrollTop = el.scrollHeight;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
function renderStudioResult() {
|
|
2788
|
+
var el = document.getElementById('studioResult');
|
|
2789
|
+
if (!el) return;
|
|
2790
|
+
if (!studioState.result) { el.style.display = 'none'; return; }
|
|
2791
|
+
el.style.display = 'block';
|
|
2792
|
+
el.innerHTML = '<div class="studio-result__title">✓ Final Result</div><div class="studio-result__body md-body">' + renderMd(studioState.result) + '</div>';
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
function studioSetNodeStatus(idx, status) {
|
|
2796
|
+
if (studioState.nodes[idx]) {
|
|
2797
|
+
studioState.nodes[idx].status = status;
|
|
2798
|
+
renderStudioNodes();
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
async function runStudio() {
|
|
2803
|
+
var ta = document.getElementById('studioTaskInput');
|
|
2804
|
+
var task = ta ? ta.value.trim() : '';
|
|
2805
|
+
if (!task || studioState.running) return;
|
|
2806
|
+
|
|
2807
|
+
// Reset state
|
|
2808
|
+
studioState.task = task;
|
|
2809
|
+
studioState.nodes = [];
|
|
2810
|
+
studioState.log = [];
|
|
2811
|
+
studioState.result = '';
|
|
2812
|
+
studioState.running = true;
|
|
2813
|
+
studioState.planned = false;
|
|
2814
|
+
renderStudioNodes();
|
|
2815
|
+
renderStudioLog();
|
|
2816
|
+
renderStudioResult();
|
|
2817
|
+
|
|
2818
|
+
var btn = document.getElementById('studioRunBtn');
|
|
2819
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Planning...'; }
|
|
2820
|
+
|
|
2821
|
+
studioLog('Studio', '⚙', 'Planning workflow for: "' + task + '"', 'system');
|
|
2822
|
+
// Show a temporary planning indicator in the nodes area
|
|
2823
|
+
var nodesEl = document.getElementById('studioNodes');
|
|
2824
|
+
if (nodesEl) nodesEl.innerHTML = '<div style="text-align:center;padding:20px;color:var(--dim);font-size:12px;font-family:var(--font)"><span class="thinking-dots"><span></span><span></span><span></span></span><div style="margin-top:8px">Designing workflow...</div></div>';
|
|
2825
|
+
|
|
2826
|
+
try {
|
|
2827
|
+
// Step 1: plan the workflow
|
|
2828
|
+
var planRes = await apiPost('/api/studio/plan', {task: task});
|
|
2829
|
+
if (!planRes || !planRes.steps || !planRes.steps.length) {
|
|
2830
|
+
studioLog('Studio', '⚠', 'Could not plan workflow. Check your LLM provider config.', 'error');
|
|
2831
|
+
studioState.running = false;
|
|
2832
|
+
if (btn) btn.disabled = false;
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
studioState.nodes = planRes.steps.map(function(s) {
|
|
2837
|
+
return {icon: s.icon, agent: s.agent, label: s.label, status: 'waiting'};
|
|
2838
|
+
});
|
|
2839
|
+
renderStudioNodes();
|
|
2840
|
+
studioLog('Studio', '✓', 'Workflow planned: ' + planRes.steps.map(function(s){return s.label}).join(' -> '), 'system');
|
|
2841
|
+
|
|
2842
|
+
// Step 2: execute each step via SSE
|
|
2843
|
+
var context = '';
|
|
2844
|
+
for (var i = 0; i < studioState.nodes.length; i++) {
|
|
2845
|
+
var node = studioState.nodes[i];
|
|
2846
|
+
studioSetNodeStatus(i, 'running');
|
|
2847
|
+
studioLog(node.label, node.icon, 'Starting...', 'agent');
|
|
2848
|
+
|
|
2849
|
+
var stepResult = await runStudioStep(i, node, task, context, planRes.steps[i]);
|
|
2850
|
+
if (stepResult.error) {
|
|
2851
|
+
studioSetNodeStatus(i, 'error');
|
|
2852
|
+
studioLog(node.label, node.icon, 'Error: ' + stepResult.error, 'error');
|
|
2853
|
+
break;
|
|
2854
|
+
}
|
|
2855
|
+
studioSetNodeStatus(i, 'done');
|
|
2856
|
+
studioLog(node.label, node.icon, stepResult.output || '(done)', 'agent', true);
|
|
2857
|
+
// If CanvasAgent produced HTML, open it in the canvas panel
|
|
2858
|
+
if (stepResult.canvas) {
|
|
2859
|
+
var cf = document.getElementById('canvasFrame');
|
|
2860
|
+
var cp = document.getElementById('canvasPanel');
|
|
2861
|
+
var ct = document.getElementById('canvasTitle');
|
|
2862
|
+
if (cf && cp) {
|
|
2863
|
+
cf.srcdoc = stepResult.canvas;
|
|
2864
|
+
cp.classList.add('open');
|
|
2865
|
+
if (ct) ct.textContent = node.label + ' Report';
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
context = stepResult.output || stepResult.canvas || context;
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
// Final result is the last step's output
|
|
2872
|
+
studioState.result = context;
|
|
2873
|
+
renderStudioResult();
|
|
2874
|
+
studioLog('Studio', '🎉', 'Workflow complete.', 'system');
|
|
2875
|
+
|
|
2876
|
+
// Save session to localStorage for reuse in Chat
|
|
2877
|
+
saveStudioSession(task, studioState.nodes, studioState.log, context);
|
|
2878
|
+
renderStudioSessionsBar();
|
|
2879
|
+
|
|
2880
|
+
} catch(e) {
|
|
2881
|
+
studioLog('Studio', '⚠', 'Unexpected error: ' + (e.message || String(e)), 'error');
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
studioState.running = false;
|
|
2885
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Run'; }
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// ---- STUDIO SESSIONS ----
|
|
2889
|
+
function saveStudioSession(task, nodes, log, result) {
|
|
2890
|
+
try {
|
|
2891
|
+
var sessions = JSON.parse(localStorage.getItem('nha_studio_sessions') || '[]');
|
|
2892
|
+
sessions.unshift({
|
|
2893
|
+
id: Date.now(),
|
|
2894
|
+
task: task,
|
|
2895
|
+
nodes: nodes.map(function(n){return {label:n.label,icon:n.icon,agent:n.agent};}),
|
|
2896
|
+
result: result,
|
|
2897
|
+
log: log.map(function(e){return {agent:e.agent,icon:e.icon,text:e.text,type:e.type,time:e.time};}),
|
|
2898
|
+
ts: new Date().toLocaleString()
|
|
2899
|
+
});
|
|
2900
|
+
sessions = sessions.slice(0, 20); // keep last 20
|
|
2901
|
+
localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions));
|
|
2902
|
+
} catch(e) {}
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
function loadStudioSessions() {
|
|
2906
|
+
try { return JSON.parse(localStorage.getItem('nha_studio_sessions') || '[]'); }
|
|
2907
|
+
catch(e) { return []; }
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
function renderStudioSessionsBar() {
|
|
2911
|
+
var el = document.getElementById('studioSessionsBar');
|
|
2912
|
+
if (!el) return;
|
|
2913
|
+
var sessions = loadStudioSessions();
|
|
2914
|
+
if (!sessions.length) { el.style.display = 'none'; return; }
|
|
2915
|
+
el.style.display = 'block';
|
|
2916
|
+
el.innerHTML = '<div style="font-size:10px;color:var(--dim);margin-bottom:8px;text-transform:uppercase;letter-spacing:1px">Recent sessions</div>' +
|
|
2917
|
+
sessions.slice(0,5).map(function(s,i) {
|
|
2918
|
+
return '<div class="studio-session-item">' +
|
|
2919
|
+
'<div style="display:flex;align-items:center;justify-content:space-between;gap:8px">' +
|
|
2920
|
+
'<span class="studio-session-task">' + esc(s.task.slice(0,60)) + (s.task.length>60?'...':'') + '</span>' +
|
|
2921
|
+
'<span style="font-size:9px;color:var(--dim);white-space:nowrap">' + esc(s.ts) + '</span>' +
|
|
2922
|
+
'</div>' +
|
|
2923
|
+
'<div style="display:flex;gap:6px;margin-top:6px">' +
|
|
2924
|
+
'<button onclick="restoreStudioSession('+i+')" style="font-size:10px;padding:3px 8px;background:var(--bg3);border:1px solid var(--border);border-radius:4px;color:var(--green);cursor:pointer">Restore</button>' +
|
|
2925
|
+
'<button onclick="importStudioToChat('+i+')" style="font-size:10px;padding:3px 8px;background:var(--bg3);border:1px solid var(--border2);border-radius:4px;color:var(--cyan);cursor:pointer">Send to Chat</button>' +
|
|
2926
|
+
'<button onclick="deleteStudioSession('+i+')" style="font-size:10px;padding:3px 8px;background:none;border:none;color:var(--dim);cursor:pointer">×</button>' +
|
|
2927
|
+
'</div>' +
|
|
2928
|
+
'</div>';
|
|
2929
|
+
}).join('');
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
function restoreStudioSession(idx) {
|
|
2933
|
+
var sessions = loadStudioSessions();
|
|
2934
|
+
var s = sessions[idx]; if (!s) return;
|
|
2935
|
+
studioState.task = s.task;
|
|
2936
|
+
studioState.nodes = s.nodes.map(function(n){return {icon:n.icon,agent:n.agent,label:n.label,status:'done'};});
|
|
2937
|
+
studioState.log = s.log;
|
|
2938
|
+
studioState.result = s.result;
|
|
2939
|
+
studioState.running = false;
|
|
2940
|
+
var ta = document.getElementById('studioTaskInput');
|
|
2941
|
+
if (ta) ta.value = s.task;
|
|
2942
|
+
renderStudioNodes(); renderStudioLog(); renderStudioResult();
|
|
2943
|
+
toast('Session restored');
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
function importStudioToChat(idx) {
|
|
2947
|
+
var sessions = loadStudioSessions();
|
|
2948
|
+
var s = sessions[idx]; if (!s) return;
|
|
2949
|
+
// Build a context message and inject into chat
|
|
2950
|
+
var NL2 = '\\n\\n';
|
|
2951
|
+
var summary = '**Studio Workflow Result**' + NL2 + 'Task: ' + s.task + NL2 + 'Agents: ' + s.nodes.map(function(n){return n.label;}).join(' -> ') + NL2 + '---' + NL2 + (s.result || '');
|
|
2952
|
+
// Switch to chat and pre-fill with context
|
|
2953
|
+
switchView('chat');
|
|
2954
|
+
setTimeout(function() {
|
|
2955
|
+
createNewConv().then(function() {
|
|
2956
|
+
// Add the studio result as an assistant message in history
|
|
2957
|
+
chatHistory = [{role:'assistant', content: summary}];
|
|
2958
|
+
renderMessages();
|
|
2959
|
+
var inp = document.getElementById('chatInput');
|
|
2960
|
+
if (inp) { inp.focus(); inp.placeholder = 'Ask follow-up questions about this studio result...'; }
|
|
2961
|
+
toast('Studio result imported into chat. Ask your follow-up question.');
|
|
2962
|
+
});
|
|
2963
|
+
}, 300);
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
function deleteStudioSession(idx) {
|
|
2967
|
+
try {
|
|
2968
|
+
var sessions = JSON.parse(localStorage.getItem('nha_studio_sessions') || '[]');
|
|
2969
|
+
sessions.splice(idx, 1);
|
|
2970
|
+
localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions));
|
|
2971
|
+
renderStudioSessionsBar();
|
|
2972
|
+
} catch(e) {}
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
// ---- TOKEN COUNTER for Studio ----
|
|
2976
|
+
var studioTokens = {in: 0, out: 0};
|
|
2977
|
+
function studioAddTokens(inp, out) {
|
|
2978
|
+
studioTokens.in += (inp||0);
|
|
2979
|
+
studioTokens.out += (out||0);
|
|
2980
|
+
var el = document.getElementById('studioTokenBar');
|
|
2981
|
+
if (el) el.textContent = 'Tokens: ' + studioTokens.in + ' in / ' + studioTokens.out + ' out';
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
function runStudioStep(idx, node, task, context, stepDef) {
|
|
2985
|
+
return new Promise(function(resolve) {
|
|
2986
|
+
var output = '';
|
|
2987
|
+
var canvasHtml = null;
|
|
2988
|
+
var body = JSON.stringify({stepIdx: idx, agent: node.agent, task: task, context: context, stepDef: stepDef});
|
|
2989
|
+
|
|
2990
|
+
fetch('/api/studio/run', {
|
|
2991
|
+
method: 'POST',
|
|
2992
|
+
headers: {'Content-Type': 'application/json'},
|
|
2993
|
+
body: body
|
|
2994
|
+
}).then(function(res) {
|
|
2995
|
+
if (!res.ok) { resolve({error: 'HTTP ' + res.status}); return; }
|
|
2996
|
+
var reader = res.body.getReader();
|
|
2997
|
+
var decoder = new TextDecoder();
|
|
2998
|
+
var buf = '';
|
|
2999
|
+
function pump() {
|
|
3000
|
+
reader.read().then(function(chunk) {
|
|
3001
|
+
if (chunk.done) { resolve({output: output || '(no output)', canvas: canvasHtml}); return; }
|
|
3002
|
+
buf += decoder.decode(chunk.value, {stream: true});
|
|
3003
|
+
var lines = buf.split('\\n');
|
|
3004
|
+
buf = lines.pop();
|
|
3005
|
+
lines.forEach(function(line) {
|
|
3006
|
+
if (!line.startsWith('data: ')) return;
|
|
3007
|
+
var d = line.slice(6).trim();
|
|
3008
|
+
if (d === '[DONE]') { resolve({output: output || '(no output)', canvas: canvasHtml}); return; }
|
|
3009
|
+
try {
|
|
3010
|
+
var ev = JSON.parse(d);
|
|
3011
|
+
if (ev.token) {
|
|
3012
|
+
// Tool status tokens (start with '[') shown in dim color, LLM output streamed normally
|
|
3013
|
+
var isStatus = ev.token.charAt(0) === '[' && ev.token.indexOf(']') > 0 && ev.token.length < 60;
|
|
3014
|
+
if (!isStatus) output += ev.token;
|
|
3015
|
+
// Update live log entry
|
|
3016
|
+
var entries = document.querySelectorAll('.studio-log-entry');
|
|
3017
|
+
var last = entries[entries.length - 1];
|
|
3018
|
+
if (last) {
|
|
3019
|
+
var tb = last.querySelector('.studio-log-entry__text');
|
|
3020
|
+
if (tb) tb.textContent = isStatus ? ev.token.replace(/[\\r\\n]/g, '') : output;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
if (ev.canvas) {
|
|
3024
|
+
canvasHtml = ev.canvas;
|
|
3025
|
+
// Render canvas immediately when received — don't wait for step resolution
|
|
3026
|
+
var cf2 = document.getElementById('canvasFrame');
|
|
3027
|
+
var cp2 = document.getElementById('canvasPanel');
|
|
3028
|
+
if (cf2 && cp2) {
|
|
3029
|
+
cf2.srcdoc = canvasHtml;
|
|
3030
|
+
cp2.classList.add('open');
|
|
3031
|
+
var ct2 = document.getElementById('canvasTitle');
|
|
3032
|
+
if (ct2) ct2.textContent = 'Studio Report';
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
if (ev.usage) { studioAddTokens(ev.usage.input||0, ev.usage.output||0); }
|
|
3036
|
+
if (ev.done) { resolve({output: output || '(no output)', canvas: canvasHtml}); return; }
|
|
3037
|
+
if (ev.error) { resolve({error: ev.error}); return; }
|
|
3038
|
+
} catch(e) {}
|
|
3039
|
+
});
|
|
3040
|
+
pump();
|
|
3041
|
+
}).catch(function(e) { resolve({error: e.message}); });
|
|
3042
|
+
}
|
|
3043
|
+
pump();
|
|
3044
|
+
}).catch(function(e) { resolve({error: e.message}); });
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function renderStudio(el) {
|
|
3049
|
+
var examplesHtml = STUDIO_EXAMPLES.map(function(ex) {
|
|
3050
|
+
return '<button class="studio-example-btn" onclick="document.getElementById(\\'studioTaskInput\\').value=' + JSON.stringify(ex) + '">' + esc(ex.slice(0, 52)) + (ex.length > 52 ? '...' : '') + '</button>';
|
|
3051
|
+
}).join('');
|
|
3052
|
+
|
|
3053
|
+
// Agent catalog
|
|
3054
|
+
var STUDIO_AGENTS = [
|
|
3055
|
+
{icon:'🔍',name:'WebSearchAgent',desc:'Search the web'},
|
|
3056
|
+
{icon:'🌐',name:'BrowserAgent',desc:'Navigate & screenshot pages'},
|
|
3057
|
+
{icon:'💌',name:'EmailAgent',desc:'Read & summarize emails'},
|
|
3058
|
+
{icon:'📅',name:'CalendarAgent',desc:'Read events & scheduling'},
|
|
3059
|
+
{icon:'⚙',name:'GitHubAgent',desc:'Issues, PRs, notifications'},
|
|
3060
|
+
{icon:'📖',name:'NotionAgent',desc:'Search Notion pages'},
|
|
3061
|
+
{icon:'💬',name:'SlackAgent',desc:'Read channels'},
|
|
3062
|
+
{icon:'📊',name:'DataAnalystAgent',desc:'Analyze data & patterns'},
|
|
3063
|
+
{icon:'✏',name:'WriterAgent',desc:'Write structured documents'},
|
|
3064
|
+
{icon:'📋',name:'SummaryAgent',desc:'Summarize long content'},
|
|
3065
|
+
{icon:'🔎',name:'ResearchAgent',desc:'Deep research & facts'},
|
|
3066
|
+
{icon:'📷',name:'CanvasAgent',desc:'Generate HTML visual report'},
|
|
3067
|
+
{icon:'🔒',name:'SecurityAgent',desc:'Security analysis'},
|
|
3068
|
+
{icon:'🔧',name:'DevOpsAgent',desc:'Infrastructure analysis'},
|
|
3069
|
+
];
|
|
3070
|
+
|
|
3071
|
+
var toolsHtml = STUDIO_AGENTS.map(function(t){
|
|
3072
|
+
return '<div class="studio-tool-item" onclick="addAgentToBuilder(\\x27'+t.name+'\\x27,\\x27'+t.icon+'\\x27)">' +
|
|
3073
|
+
'<span class="studio-tool-icon">'+t.icon+'</span>' +
|
|
3074
|
+
'<div><div style="font-size:11px;font-weight:600;color:var(--green)">'+t.name+'</div><div style="font-size:10px;color:var(--dim)">'+esc(t.desc)+'</div></div>' +
|
|
3075
|
+
'<span style="margin-left:auto;font-size:14px;color:var(--green3);flex-shrink:0">+</span>' +
|
|
3076
|
+
'</div>';
|
|
3077
|
+
}).join('');
|
|
3078
|
+
|
|
3079
|
+
// 38 specialist agents sidebar
|
|
3080
|
+
var SPECIALIST_AGENTS = [
|
|
3081
|
+
{icon:'\\u{1F6E1}',name:'saber',desc:'Security audits, pentest, OWASP'},
|
|
3082
|
+
{icon:'\\u{1F50D}',name:'zero',desc:'Vulnerability & dependency audit'},
|
|
3083
|
+
{icon:'\\u2713',name:'veritas',desc:'Fact-checking & hallucination detection'},
|
|
3084
|
+
{icon:'\\u{1F52C}',name:'ade',desc:'Full security review, forensics'},
|
|
3085
|
+
{icon:'\\u{1F512}',name:'heimdall',desc:'OAuth, JWT, RBAC design'},
|
|
3086
|
+
{icon:'\\u{1F4BB}',name:'jarvis',desc:'Full-stack architecture & API'},
|
|
3087
|
+
{icon:'\\u2699',name:'forge',desc:'CI/CD, deployment, infra'},
|
|
3088
|
+
{icon:'\\u{1F527}',name:'pipe',desc:'Build systems, Airflow, automation'},
|
|
3089
|
+
{icon:'\\u{1F4DF}',name:'shell',desc:'Shell scripts, CLI tools'},
|
|
3090
|
+
{icon:'\\u{1F41B}',name:'glitch',desc:'Debugging & root cause'},
|
|
3091
|
+
{icon:'\\u{1F4CA}',name:'oracle',desc:'Data analysis, stats, ML'},
|
|
3092
|
+
{icon:'\\u{1F9EE}',name:'logos',desc:'Logic, proofs, formal reasoning'},
|
|
3093
|
+
{icon:'\\u{1F5FA}',name:'atlas',desc:'Terraform, CloudFormation, IaC'},
|
|
3094
|
+
{icon:'\\u{1F30D}',name:'cartographer',desc:'Geo data, mapping, routing'},
|
|
3095
|
+
{icon:'\\u270D',name:'scheherazade',desc:'Docs, tutorials, blog posts'},
|
|
3096
|
+
{icon:'\\u{1F4DD}',name:'quill',desc:'Posts, summaries, abstracts'},
|
|
3097
|
+
{icon:'\\u{1F3A8}',name:'muse',desc:'Creative brainstorming & ideation'},
|
|
3098
|
+
{icon:'\\u{1F58C}',name:'murasaki',desc:'UI/UX design, accessibility'},
|
|
3099
|
+
{icon:'\\u{1F517}',name:'hermes',desc:'Kafka, RabbitMQ, event-driven'},
|
|
3100
|
+
{icon:'\\u{1F50C}',name:'link',desc:'Community, reputation, engagement'},
|
|
3101
|
+
{icon:'\\u{1F310}',name:'mercury',desc:'Finance, market, ROI analysis'},
|
|
3102
|
+
{icon:'\\u2638',name:'shogun',desc:'Kubernetes, Helm, pod security'},
|
|
3103
|
+
{icon:'\\u{1F504}',name:'flux',desc:'GitOps, rollback planning'},
|
|
3104
|
+
{icon:'\\u23F0',name:'cron',desc:'GitHub Actions, GitLab CI'},
|
|
3105
|
+
{icon:'\\u{1F30E}',name:'babel',desc:'API integration, microservices'},
|
|
3106
|
+
{icon:'\\u{1F5E3}',name:'polyglot',desc:'i18n, localization, translation'},
|
|
3107
|
+
{icon:'\\u{1F4E2}',name:'herald',desc:'News analysis, trend detection'},
|
|
3108
|
+
{icon:'\\u{1F4E1}',name:'echo',desc:'Content repurposing: blog→social'},
|
|
3109
|
+
{icon:'\\u26A1',name:'macro',desc:'Batch processing, data migration'},
|
|
3110
|
+
{icon:'\\u{1F525}',name:'prometheus',desc:'Strategy, architecture trade-offs'},
|
|
3111
|
+
{icon:'\\u26A0',name:'cassandra',desc:'Risk prediction, worst-case analysis'},
|
|
3112
|
+
{icon:'\\u{1F9E0}',name:'athena',desc:'Tech evaluation, benchmarks'},
|
|
3113
|
+
{icon:'\\u{1F441}',name:'sauron',desc:'Performance profiling, bottlenecks'},
|
|
3114
|
+
{icon:'\\u{1F3BC}',name:'conductor',desc:'Workflow orchestration'},
|
|
3115
|
+
{icon:'\\u{1F9ED}',name:'navi',desc:'Data profiling, schema inference'},
|
|
3116
|
+
{icon:'\\u{1F4C8}',name:'edi',desc:'A/B testing, hypothesis testing'},
|
|
3117
|
+
{icon:'\\u26C8',name:'tempest',desc:'Climate, weather, environmental'},
|
|
3118
|
+
{icon:'\\u{1F37D}',name:'epicure',desc:'Recipes, nutrition, dietary'},
|
|
3119
|
+
];
|
|
3120
|
+
var specialistHtml = SPECIALIST_AGENTS.map(function(t){
|
|
3121
|
+
var ic = t.icon;
|
|
3122
|
+
return '<div class="studio-tool-item" onclick="addAgentToBuilder(\\x27'+t.name+'\\x27,\\x27'+ic+'\\x27)">' +
|
|
3123
|
+
'<span class="studio-tool-icon">'+ic+'</span>' +
|
|
3124
|
+
'<div><div style="font-size:11px;font-weight:600;color:var(--cyan)">'+t.name+'</div><div style="font-size:10px;color:var(--dim)">'+esc(t.desc)+'</div></div>' +
|
|
3125
|
+
'<span style="margin-left:auto;font-size:14px;color:var(--green3);flex-shrink:0">+</span>' +
|
|
3126
|
+
'</div>';
|
|
3127
|
+
}).join('');
|
|
3128
|
+
|
|
3129
|
+
el.innerHTML =
|
|
3130
|
+
'<div class="studio-header">' +
|
|
3131
|
+
'<h2>⚙ NHA Studio</h2>' +
|
|
3132
|
+
'<p>Build a pipeline manually — click agents to add them in order — or describe your task in natural language and let Studio plan it automatically.</p>' +
|
|
3133
|
+
'</div>' +
|
|
3134
|
+
'<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start">' +
|
|
3135
|
+
'<div style="flex:1;min-width:0">' +
|
|
3136
|
+
|
|
3137
|
+
// ── MODE TABS ──
|
|
3138
|
+
'<div style="display:flex;gap:0;margin-bottom:14px;border:1px solid var(--border);border-radius:8px;overflow:hidden">' +
|
|
3139
|
+
'<button id="studioTabAuto" onclick="studioSetMode(\\x27auto\\x27)" style="flex:1;padding:8px;font-size:11px;font-weight:600;background:var(--green3);color:#fff;border:none;cursor:pointer">💡 Auto Plan</button>' +
|
|
3140
|
+
'<button id="studioTabManual" onclick="studioSetMode(\\x27manual\\x27)" style="flex:1;padding:8px;font-size:11px;font-weight:600;background:var(--bg3);color:var(--dim);border:none;cursor:pointer">🔧 Manual Builder</button>' +
|
|
3141
|
+
'</div>' +
|
|
3142
|
+
|
|
3143
|
+
// ── AUTO MODE ──
|
|
3144
|
+
'<div id="studioAutoMode">' +
|
|
3145
|
+
'<div style="margin-bottom:10px">' +
|
|
3146
|
+
'<div style="font-size:10px;color:var(--dim);margin-bottom:6px;text-transform:uppercase;letter-spacing:1px">Examples</div>' +
|
|
3147
|
+
examplesHtml +
|
|
3148
|
+
'</div>' +
|
|
3149
|
+
'<div class="studio-input-row">' +
|
|
3150
|
+
'<textarea id="studioTaskInput" placeholder="Describe what you want to accomplish... (Ctrl+Enter to run)" onkeydown="if(event.key===\\x27Enter\\x27&&(event.ctrlKey||event.metaKey)){runStudio();event.preventDefault()}">' + esc(studioState.task) + '</textarea>' +
|
|
3151
|
+
'<button id="studioRunBtn" class="studio-run-btn" onclick="runStudio()" ' + (studioState.running ? 'disabled' : '') + '>▶ Run</button>' +
|
|
3152
|
+
'</div>' +
|
|
3153
|
+
'</div>' +
|
|
3154
|
+
|
|
3155
|
+
// ── MANUAL BUILDER MODE ──
|
|
3156
|
+
'<div id="studioManualMode" style="display:none">' +
|
|
3157
|
+
'<div style="margin-bottom:10px">' +
|
|
3158
|
+
'<input id="studioManualTask" placeholder="Describe the overall goal (optional)..." style="width:100%;padding:10px 14px;font-size:13px;border-radius:8px;border:1px solid var(--border);background:var(--bg2);color:var(--text)">' +
|
|
3159
|
+
'</div>' +
|
|
3160
|
+
'<div id="studioBuilderPipeline" class="studio-builder-pipeline">' +
|
|
3161
|
+
'<div class="studio-builder-empty">Click agents on the right to build your pipeline</div>' +
|
|
3162
|
+
'</div>' +
|
|
3163
|
+
'<div style="display:flex;gap:8px;margin-top:10px">' +
|
|
3164
|
+
'<button onclick="runManualWorkflow()" class="studio-run-btn" style="flex:1">▶ Run Pipeline</button>' +
|
|
3165
|
+
'<button onclick="clearBuilderPipeline()" style="padding:8px 14px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);cursor:pointer;font-size:12px">Clear</button>' +
|
|
3166
|
+
'</div>' +
|
|
3167
|
+
'</div>' +
|
|
3168
|
+
|
|
3169
|
+
'<div id="studioTokenBar" style="font-size:10px;color:var(--dim);margin:8px 0;font-family:var(--mono)"></div>' +
|
|
3170
|
+
'<div class="studio-canvas" id="studioNodes"></div>' +
|
|
3171
|
+
'<div class="studio-log" id="studioLog" style="display:none"></div>' +
|
|
3172
|
+
'<div id="studioResult"></div>' +
|
|
3173
|
+
'<div id="studioSessionsBar" style="margin-top:16px;display:none"></div>' +
|
|
3174
|
+
'</div>' +
|
|
3175
|
+
|
|
3176
|
+
// ── AGENT SIDEBAR ──
|
|
3177
|
+
'<div class="studio-tools-panel">' +
|
|
3178
|
+
// Tab bar
|
|
3179
|
+
'<div style="display:flex;gap:0;margin-bottom:10px;border:1px solid var(--border);border-radius:6px;overflow:hidden">' +
|
|
3180
|
+
'<button id="sideTabTools" onclick="studioSideTab(\\x27tools\\x27)" style="flex:1;padding:5px;font-size:10px;font-weight:600;background:var(--green3);color:#fff;border:none;cursor:pointer">🔧 Tools</button>' +
|
|
3181
|
+
'<button id="sideTabAgents" onclick="studioSideTab(\\x27agents\\x27)" style="flex:1;padding:5px;font-size:10px;font-weight:600;background:var(--bg3);color:var(--dim);border:none;cursor:pointer">🤖 Agents</button>' +
|
|
3182
|
+
'</div>' +
|
|
3183
|
+
'<div style="font-size:9px;color:var(--dim);margin-bottom:8px">Click to add to pipeline</div>' +
|
|
3184
|
+
'<div id="sideToolsList">'+toolsHtml+'</div>' +
|
|
3185
|
+
'<div id="sideAgentsList" style="display:none">'+specialistHtml+'</div>' +
|
|
3186
|
+
'</div>' +
|
|
3187
|
+
'</div>';
|
|
3188
|
+
|
|
3189
|
+
renderStudioNodes();
|
|
3190
|
+
renderStudioLog();
|
|
3191
|
+
renderStudioResult();
|
|
3192
|
+
renderStudioSessionsBar();
|
|
3193
|
+
studioTokens = {in:0,out:0};
|
|
3194
|
+
// Restore pipeline from state
|
|
3195
|
+
renderBuilderPipeline();
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
// ---- STUDIO SIDEBAR TAB ----
|
|
3199
|
+
function studioSideTab(tab) {
|
|
3200
|
+
var toolsList = document.getElementById('sideToolsList');
|
|
3201
|
+
var agentsList = document.getElementById('sideAgentsList');
|
|
3202
|
+
var tabT = document.getElementById('sideTabTools');
|
|
3203
|
+
var tabA = document.getElementById('sideTabAgents');
|
|
3204
|
+
if (!toolsList || !agentsList) return;
|
|
3205
|
+
if (tab === 'tools') {
|
|
3206
|
+
toolsList.style.display = '';
|
|
3207
|
+
agentsList.style.display = 'none';
|
|
3208
|
+
if (tabT) { tabT.style.background='var(--green3)'; tabT.style.color='#fff'; }
|
|
3209
|
+
if (tabA) { tabA.style.background='var(--bg3)'; tabA.style.color='var(--dim)'; }
|
|
3210
|
+
} else {
|
|
3211
|
+
toolsList.style.display = 'none';
|
|
3212
|
+
agentsList.style.display = '';
|
|
3213
|
+
if (tabA) { tabA.style.background='var(--cyan)'; tabA.style.color='#fff'; }
|
|
3214
|
+
if (tabT) { tabT.style.background='var(--bg3)'; tabT.style.color='var(--dim)'; }
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
// ---- STUDIO MODE SWITCHING ----
|
|
3219
|
+
var studioMode = 'auto';
|
|
3220
|
+
var builderPipeline = []; // [{icon,name,agent}]
|
|
3221
|
+
|
|
3222
|
+
function studioSetMode(mode) {
|
|
3223
|
+
studioMode = mode;
|
|
3224
|
+
var autoEl = document.getElementById('studioAutoMode');
|
|
3225
|
+
var manEl = document.getElementById('studioManualMode');
|
|
3226
|
+
var tabA = document.getElementById('studioTabAuto');
|
|
3227
|
+
var tabM = document.getElementById('studioTabManual');
|
|
3228
|
+
if (!autoEl || !manEl) return;
|
|
3229
|
+
if (mode === 'auto') {
|
|
3230
|
+
autoEl.style.display = '';
|
|
3231
|
+
manEl.style.display = 'none';
|
|
3232
|
+
if (tabA) { tabA.style.background='var(--green3)'; tabA.style.color='#fff'; }
|
|
3233
|
+
if (tabM) { tabM.style.background='var(--bg3)'; tabM.style.color='var(--dim)'; }
|
|
3234
|
+
} else {
|
|
3235
|
+
autoEl.style.display = 'none';
|
|
3236
|
+
manEl.style.display = '';
|
|
3237
|
+
if (tabM) { tabM.style.background='var(--green3)'; tabM.style.color='#fff'; }
|
|
3238
|
+
if (tabA) { tabA.style.background='var(--bg3)'; tabA.style.color='var(--dim)'; }
|
|
3239
|
+
renderBuilderPipeline();
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
// ---- MANUAL BUILDER ----
|
|
3244
|
+
function addAgentToBuilder(agentName, icon) {
|
|
3245
|
+
if (builderPipeline.length >= 6) { toast('Max 6 agents per pipeline'); return; }
|
|
3246
|
+
builderPipeline.push({icon: icon||'⚙', name: agentName, agent: agentName, label: agentName.replace('Agent',''), status:'waiting'});
|
|
3247
|
+
// Auto-switch to manual mode when user clicks an agent
|
|
3248
|
+
studioSetMode('manual');
|
|
3249
|
+
renderBuilderPipeline();
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
function removeBuilderAgent(idx) {
|
|
3253
|
+
builderPipeline.splice(idx, 1);
|
|
3254
|
+
renderBuilderPipeline();
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
function moveBuilderAgent(idx, dir) {
|
|
3258
|
+
var newIdx = idx + dir;
|
|
3259
|
+
if (newIdx < 0 || newIdx >= builderPipeline.length) return;
|
|
3260
|
+
var tmp = builderPipeline[idx];
|
|
3261
|
+
builderPipeline[idx] = builderPipeline[newIdx];
|
|
3262
|
+
builderPipeline[newIdx] = tmp;
|
|
3263
|
+
renderBuilderPipeline();
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
function clearBuilderPipeline() {
|
|
3267
|
+
builderPipeline = [];
|
|
3268
|
+
renderBuilderPipeline();
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
function renderBuilderPipeline() {
|
|
3272
|
+
var el = document.getElementById('studioBuilderPipeline');
|
|
3273
|
+
if (!el) return;
|
|
3274
|
+
if (!builderPipeline.length) {
|
|
3275
|
+
el.innerHTML = '<div class="studio-builder-empty">Click agents on the right to build your pipeline</div>';
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
el.innerHTML = builderPipeline.map(function(a, i) {
|
|
3279
|
+
return '<div class="studio-builder-node" draggable="true" ondragstart="builderDragStart('+i+')" ondragover="event.preventDefault()" ondrop="builderDrop('+i+')">' +
|
|
3280
|
+
'<span style="font-size:18px;flex-shrink:0">'+a.icon+'</span>' +
|
|
3281
|
+
'<div style="flex:1;min-width:0">' +
|
|
3282
|
+
'<div style="font-size:12px;font-weight:600;color:var(--green)">'+esc(a.label||a.name)+'</div>' +
|
|
3283
|
+
'<div style="font-size:10px;color:var(--dim)">Step '+(i+1)+'</div>' +
|
|
3284
|
+
'</div>' +
|
|
3285
|
+
'<div style="display:flex;gap:2px;align-items:center">' +
|
|
3286
|
+
(i>0?'<button onclick="moveBuilderAgent('+i+',-1)" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px" title="Move up">↑</button>':'') +
|
|
3287
|
+
(i<builderPipeline.length-1?'<button onclick="moveBuilderAgent('+i+',1)" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px" title="Move down">↓</button>':'') +
|
|
3288
|
+
'<button onclick="removeBuilderAgent('+i+')" style="background:none;border:none;color:var(--red);cursor:pointer;font-size:14px;padding:2px 4px" title="Remove">×</button>' +
|
|
3289
|
+
'</div>' +
|
|
3290
|
+
(i<builderPipeline.length-1?'<div style="position:absolute;bottom:-14px;left:50%;transform:translateX(-50%);color:var(--green3);font-size:12px">↓</div>':'') +
|
|
3291
|
+
'</div>';
|
|
3292
|
+
}).join('');
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
// Drag-and-drop reorder for builder pipeline
|
|
3296
|
+
var builderDragIdx = -1;
|
|
3297
|
+
function builderDragStart(idx) { builderDragIdx = idx; }
|
|
3298
|
+
function builderDrop(toIdx) {
|
|
3299
|
+
if (builderDragIdx < 0 || builderDragIdx === toIdx) return;
|
|
3300
|
+
var item = builderPipeline.splice(builderDragIdx, 1)[0];
|
|
3301
|
+
builderPipeline.splice(toIdx, 0, item);
|
|
3302
|
+
builderDragIdx = -1;
|
|
3303
|
+
renderBuilderPipeline();
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
async function runManualWorkflow() {
|
|
3307
|
+
if (!builderPipeline.length) { toast('Add at least one agent to the pipeline'); return; }
|
|
3308
|
+
if (studioState.running) return;
|
|
3309
|
+
|
|
3310
|
+
var taskEl = document.getElementById('studioManualTask');
|
|
3311
|
+
var task = taskEl ? taskEl.value.trim() : '';
|
|
3312
|
+
if (!task) task = builderPipeline.map(function(a){return a.label||a.name;}).join(' -> ') + ' pipeline';
|
|
3313
|
+
|
|
3314
|
+
// Build steps in the same format runStudio expects, skipping the LLM planner
|
|
3315
|
+
var steps = builderPipeline.map(function(a) {
|
|
3316
|
+
return {
|
|
3317
|
+
agent: a.agent,
|
|
3318
|
+
label: a.label || a.name,
|
|
3319
|
+
icon: a.icon,
|
|
3320
|
+
prompt: task,
|
|
3321
|
+
status: 'waiting'
|
|
3322
|
+
};
|
|
3323
|
+
});
|
|
3324
|
+
|
|
3325
|
+
studioState.task = task;
|
|
3326
|
+
studioState.nodes = steps.map(function(s){return {icon:s.icon,agent:s.agent,label:s.label,status:'waiting'};});
|
|
3327
|
+
studioState.log = [];
|
|
3328
|
+
studioState.result = '';
|
|
3329
|
+
studioState.running = true;
|
|
3330
|
+
studioTokens = {in:0, out:0};
|
|
3331
|
+
var tb = document.getElementById('studioTokenBar');
|
|
3332
|
+
if(tb) tb.textContent = '';
|
|
3333
|
+
renderStudioNodes();
|
|
3334
|
+
renderStudioLog();
|
|
3335
|
+
renderStudioResult();
|
|
3336
|
+
|
|
3337
|
+
var context = '';
|
|
3338
|
+
try {
|
|
3339
|
+
for (var i = 0; i < steps.length; i++) {
|
|
3340
|
+
var s = steps[i];
|
|
3341
|
+
studioSetNodeStatus(i, 'running');
|
|
3342
|
+
studioLog(s.label, s.icon, 'Starting...', 'agent');
|
|
3343
|
+
var stepResult = await runStudioStep(i, {agent:s.agent,label:s.label,icon:s.icon}, task, context, s);
|
|
3344
|
+
if (stepResult.error) {
|
|
3345
|
+
studioSetNodeStatus(i, 'error');
|
|
3346
|
+
studioLog(s.label, s.icon, 'Error: '+stepResult.error, 'error');
|
|
3347
|
+
break;
|
|
3348
|
+
}
|
|
3349
|
+
studioSetNodeStatus(i, 'done');
|
|
3350
|
+
studioLog(s.label, s.icon, stepResult.output || '(done)', 'agent', true);
|
|
3351
|
+
if (stepResult.canvas) {
|
|
3352
|
+
var cf = document.getElementById('canvasFrame');
|
|
3353
|
+
var cp = document.getElementById('canvasPanel');
|
|
3354
|
+
if (cf && cp) { cf.srcdoc = stepResult.canvas; cp.classList.add('open'); var ct3=document.getElementById('canvasTitle'); if(ct3) ct3.textContent='Studio Report'; }
|
|
3355
|
+
}
|
|
3356
|
+
context = stepResult.output || stepResult.canvas || context;
|
|
3357
|
+
}
|
|
3358
|
+
studioState.result = context;
|
|
3359
|
+
renderStudioResult();
|
|
3360
|
+
studioLog('Studio', '🎉', 'Pipeline complete.', 'system');
|
|
3361
|
+
saveStudioSession(task, studioState.nodes, studioState.log, context);
|
|
3362
|
+
renderStudioSessionsBar();
|
|
3363
|
+
} catch(e) {
|
|
3364
|
+
studioLog('Studio', '⚠', 'Error: '+(e.message||String(e)), 'error');
|
|
3365
|
+
}
|
|
3366
|
+
studioState.running = false;
|
|
3367
|
+
}
|
|
3368
|
+
|
|
2772
3369
|
// ---- INIT ----
|
|
2773
3370
|
function init(){
|
|
2774
3371
|
var el=document.getElementById('content');
|
|
@@ -2778,8 +3375,373 @@ function init(){
|
|
|
2778
3375
|
setInterval(function(){loadDash().then(function(){if(currentView==='dashboard')render()}).catch(function(){})},120000);
|
|
2779
3376
|
// Connect to daemon WebSocket for real-time notifications
|
|
2780
3377
|
connectWebSocket();
|
|
3378
|
+
// Make floating panels draggable
|
|
3379
|
+
var bv=document.getElementById('browserViewer');
|
|
3380
|
+
if(bv)makeDraggable(bv,'.browser-viewer__header');
|
|
3381
|
+
var cp=document.getElementById('canvasPanel');
|
|
3382
|
+
if(cp)makeDraggable(cp,'.cvs-header');
|
|
2781
3383
|
}
|
|
2782
3384
|
init();
|
|
3385
|
+
`;
|
|
3386
|
+
|
|
3387
|
+
export function getHTML(port) {
|
|
3388
|
+
const ts = Date.now();
|
|
3389
|
+
|
|
3390
|
+
// CSS as a clean block — no template literal nesting issues
|
|
3391
|
+
const CSS = `
|
|
3392
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
3393
|
+
:root{
|
|
3394
|
+
--bg:#0a0a0a;--bg2:#111;--bg3:#1a1a2e;--bg4:#222;
|
|
3395
|
+
--green:#a5b4fc;--green2:#818cf8;--green3:#6366f1;--greendim:#12121e;
|
|
3396
|
+
--cyan:#38bdf8;--amber:#fbbf24;--red:#ef4444;
|
|
3397
|
+
--text:#e4e4e7;--dim:#9ca3af;--bright:#fff;
|
|
3398
|
+
--border:#1e1e2e;--border2:#3f3f5e;
|
|
3399
|
+
--font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
3400
|
+
--mono:'JetBrains Mono','Fira Code','SF Mono','Consolas',monospace;
|
|
3401
|
+
--term:'JetBrains Mono','Fira Code','SF Mono','Consolas',monospace;
|
|
3402
|
+
--amber3:#d97706;--amberdim:#1a1200;
|
|
3403
|
+
--r:6px;
|
|
3404
|
+
}
|
|
3405
|
+
html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--font);font-size:13.5px;line-height:1.6}
|
|
3406
|
+
a{color:var(--cyan);text-decoration:none}
|
|
3407
|
+
button{font-family:var(--font);cursor:pointer;border:none;outline:none}
|
|
3408
|
+
input,textarea{font-family:var(--font);background:var(--bg2);color:var(--text);border:1px solid var(--border);padding:8px 12px;border-radius:var(--r);outline:none;font-size:13px}
|
|
3409
|
+
input:focus,textarea:focus{border-color:var(--green3)}
|
|
3410
|
+
::-webkit-scrollbar{width:6px}
|
|
3411
|
+
::-webkit-scrollbar-track{background:var(--bg)}
|
|
3412
|
+
::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
|
3413
|
+
|
|
3414
|
+
/* ---- TASKS ---- */
|
|
3415
|
+
.task-bar{display:flex;gap:8px;margin-bottom:16px}
|
|
3416
|
+
.task-bar input{flex:1;font-size:13px;padding:10px 14px}
|
|
3417
|
+
.task-bar select{background:var(--bg2);color:var(--text);border:1px solid var(--border);padding:8px;border-radius:var(--r);font-size:12px}
|
|
3418
|
+
.task-bar button{background:var(--green3);color:var(--bg);padding:8px 20px;border-radius:var(--r);font-weight:700;font-size:13px}
|
|
3419
|
+
.task{display:flex;align-items:center;gap:10px;padding:12px;cursor:pointer}
|
|
3420
|
+
.task--done{opacity:0.5}
|
|
3421
|
+
.task__check{width:24px;height:24px;border:2px solid var(--border2);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--green);cursor:pointer;flex-shrink:0}
|
|
3422
|
+
.task__check--done{background:var(--green3);border-color:var(--green)}
|
|
3423
|
+
.task__desc{flex:1;font-size:13px}
|
|
3424
|
+
.task__priority{font-size:10px;padding:2px 8px;border-radius:4px;text-transform:uppercase}
|
|
3425
|
+
.task__priority--critical{background:var(--red);color:var(--bright)}
|
|
3426
|
+
.task__priority--high{background:#ff6d00;color:var(--bright)}
|
|
3427
|
+
.task__priority--medium{background:var(--amber);color:var(--bg)}
|
|
3428
|
+
.task__priority--low{background:var(--border2);color:var(--dim)}
|
|
3429
|
+
|
|
3430
|
+
/* ---- LAYOUT: mobile-first ---- */
|
|
3431
|
+
.app{display:flex;flex-direction:column;height:100vh;height:100dvh}
|
|
3432
|
+
/* header removed — info moved to sidebar brand */
|
|
3433
|
+
|
|
3434
|
+
.sidebar{display:none;position:fixed;top:0;left:0;width:260px;height:100vh;height:100dvh;background:var(--bg2);border-right:1px solid var(--border);z-index:200;flex-direction:column;overflow-y:auto;box-shadow:4px 0 20px rgba(0,0,0,0.8)}
|
|
3435
|
+
.sidebar--open{display:flex}
|
|
3436
|
+
.sidebar__overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:150}
|
|
3437
|
+
.sidebar__overlay--open{display:block}
|
|
3438
|
+
.sidebar__brand{padding:16px;border-bottom:1px solid var(--border)}
|
|
3439
|
+
.sidebar__brand-name{font-size:16px;color:var(--green);font-weight:700;letter-spacing:2px}
|
|
3440
|
+
.sidebar__brand-sub{font-size:10px;color:var(--dim);margin-top:2px}
|
|
3441
|
+
.sidebar__section{padding:12px 0}
|
|
3442
|
+
.sidebar__label{padding:0 16px;font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--dim);margin-bottom:4px}
|
|
3443
|
+
.nav-item{display:flex;align-items:center;gap:10px;padding:8px 16px;color:var(--dim);cursor:pointer;font-size:12px;border-left:2px solid transparent}
|
|
3444
|
+
.nav-item:hover,.nav-item--active{color:var(--bright);background:var(--bg3)}
|
|
3445
|
+
.nav-item--active{border-left-color:var(--green);color:var(--green)}
|
|
3446
|
+
.nav-item__icon{width:18px;text-align:center}
|
|
3447
|
+
.nav-item__badge{background:var(--red);color:var(--bright);font-size:9px;padding:1px 5px;border-radius:8px;margin-left:auto}
|
|
3448
|
+
|
|
3449
|
+
.content{flex:1;overflow-y:auto;padding:16px;-webkit-overflow-scrolling:touch}
|
|
3450
|
+
|
|
3451
|
+
/* Mobile burger button */
|
|
3452
|
+
#mobileBurger{display:block}
|
|
3453
|
+
.sidebar__close{position:absolute;top:12px;right:12px;background:none;border:none;color:var(--dim);font-size:20px;cursor:pointer;padding:4px 8px;z-index:10;line-height:1}
|
|
3454
|
+
.sidebar__close:hover{color:var(--bright)}
|
|
3455
|
+
.sidebar__brand{position:relative}
|
|
3456
|
+
|
|
3457
|
+
/* ---- DESKTOP: sidebar always visible ---- */
|
|
3458
|
+
@media(min-width:901px){
|
|
3459
|
+
.app{flex-direction:row}
|
|
3460
|
+
.header__burger{display:none}
|
|
3461
|
+
#mobileBurger{display:none!important}
|
|
3462
|
+
.sidebar__close{display:none}
|
|
3463
|
+
.sidebar{display:flex!important;position:static;width:220px;min-width:220px;height:auto;box-shadow:none}
|
|
3464
|
+
.sidebar__overlay{display:none!important}
|
|
3465
|
+
.content{padding:20px}
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
/* ---- CARDS ---- */
|
|
3469
|
+
.card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:14px;margin-bottom:10px}
|
|
3470
|
+
.card__title{font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--dim);margin-bottom:6px}
|
|
3471
|
+
.card__value{font-size:22px;font-weight:700;color:var(--green)}
|
|
3472
|
+
.card__sub{font-size:11px;color:var(--dim);margin-top:3px}
|
|
3473
|
+
.dash-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px}
|
|
3474
|
+
@media(min-width:901px){.dash-grid{grid-template-columns:repeat(4,1fr)}}
|
|
3475
|
+
.section-title{font-size:12px;color:var(--cyan);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px}
|
|
3476
|
+
|
|
3477
|
+
/* ---- CHAT ---- */
|
|
3478
|
+
.content--chat{overflow:hidden!important;padding:0!important;display:flex;flex-direction:column}
|
|
3479
|
+
.chat{display:flex;flex-direction:column;flex:1;min-height:0;padding:16px;padding-bottom:0}
|
|
3480
|
+
@media(min-width:901px){.content--chat{padding:0!important}}
|
|
3481
|
+
.chat__messages{flex:1;overflow-y:auto;padding-bottom:12px;-webkit-overflow-scrolling:touch}
|
|
3482
|
+
.chat__empty{text-align:center;padding:60px 16px;color:var(--dim)}
|
|
3483
|
+
.chat__empty-title{font-size:28px;color:var(--green);margin-bottom:12px}
|
|
3484
|
+
.chat__empty-hint{font-size:11px;margin-top:12px}
|
|
3485
|
+
.msg{margin-bottom:12px}
|
|
3486
|
+
.msg--user .msg__bubble{background:var(--bg3);border:1px solid var(--border2);border-radius:8px 8px 2px 8px;padding:10px 14px;max-width:85%;margin-left:auto;color:var(--bright)}
|
|
3487
|
+
.msg--assistant .msg__bubble{background:var(--greendim);border:1px solid var(--green3);border-radius:8px 8px 8px 2px;padding:10px 14px;max-width:85%;color:var(--text);white-space:pre-wrap;word-wrap:break-word;line-height:1.5}
|
|
3488
|
+
.msg--assistant .msg__bubble img{max-width:100%;border-radius:8px;margin:8px 0;border:1px solid rgba(0,255,65,0.2)}
|
|
3489
|
+
.msg__label{font-size:10px;color:var(--dim);margin-bottom:2px}
|
|
3490
|
+
.msg__actions{display:flex;gap:6px;margin-top:4px;opacity:0.4;transition:opacity 0.2s}
|
|
3491
|
+
.msg:hover .msg__actions{opacity:1}
|
|
3492
|
+
.msg__actions button{background:none;border:none;color:var(--dim);cursor:pointer;font-size:10px;font-family:var(--mono);padding:2px 4px}
|
|
3493
|
+
.msg__actions button:hover{color:var(--green)}
|
|
3494
|
+
#canvasPanel{position:fixed;top:60px;right:12px;width:480px;max-height:calc(100vh - 80px);background:#0d0d0d;border:1px solid var(--green);border-radius:12px;z-index:1000;overflow:hidden;display:none;flex-direction:column;box-shadow:0 0 30px rgba(0,255,65,0.1)}
|
|
3495
|
+
#canvasPanel.open{display:flex}
|
|
3496
|
+
#canvasPanel .cvs-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--green);background:rgba(0,255,65,0.05)}
|
|
3497
|
+
#canvasPanel .cvs-header span{font-family:var(--mono);color:var(--green);font-size:12px}
|
|
3498
|
+
#canvasPanel .cvs-header button{background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px;margin-left:8px}
|
|
3499
|
+
#canvasPanel iframe{flex:1;border:none;background:#fff;min-height:350px;width:100%}
|
|
3500
|
+
.msg--thinking{color:var(--dim);font-style:italic}
|
|
3501
|
+
.tool-indicator{display:inline-block;padding:2px 8px;margin:2px 0;border-radius:4px;font-size:11px;background:var(--bg3);border:1px solid var(--border)}
|
|
3502
|
+
.tool-indicator--browser{border-color:#9c27b0;color:#ce93d8}
|
|
3503
|
+
.tool-indicator--web{border-color:var(--cyan);color:var(--cyan)}
|
|
3504
|
+
.tool-indicator--email{border-color:var(--green3);color:var(--green)}
|
|
3505
|
+
.screenshot-preview{max-width:100%;border-radius:var(--r);margin:8px 0;border:1px solid var(--border);cursor:zoom-in;transition:opacity .15s}
|
|
3506
|
+
.screenshot-preview:hover{opacity:.88}
|
|
3507
|
+
.lightbox-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.88);z-index:9999;align-items:center;justify-content:center;cursor:zoom-out}
|
|
3508
|
+
.lightbox-overlay--open{display:flex}
|
|
3509
|
+
.lightbox-overlay img{max-width:92vw;max-height:88vh;border-radius:8px;object-fit:contain;box-shadow:0 8px 48px rgba(0,0,0,.8)}
|
|
3510
|
+
.lightbox-close{position:fixed;top:16px;right:20px;color:#fff;font-size:28px;cursor:pointer;line-height:1;opacity:.7}
|
|
3511
|
+
.lightbox-close:hover{opacity:1}
|
|
3512
|
+
.inline-card{margin:12px 0;padding:0;border-radius:10px;border:1px solid var(--border);overflow:hidden;background:var(--bg2)}
|
|
3513
|
+
.inline-card iframe{width:100%;height:280px;border:none;border-radius:0 0 10px 10px}
|
|
3514
|
+
.inline-card a{color:var(--cyan);text-decoration:none}
|
|
3515
|
+
.inline-card a:hover{text-decoration:underline}
|
|
3516
|
+
.inline-browser{margin:12px 0;border-radius:10px;border:1px solid var(--green3);overflow:hidden;background:#000}
|
|
3517
|
+
.inline-browser-bar{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--bg);border-bottom:1px solid var(--border)}
|
|
3518
|
+
.inline-browser-dot{width:8px;height:8px;border-radius:50%;background:var(--border2)}
|
|
3519
|
+
.inline-browser-url{font-family:var(--mono);font-size:10px;color:var(--dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3520
|
+
.inline-browser img{width:100%;display:block}
|
|
3521
|
+
.browser-viewer{position:fixed;top:12px;left:12px;width:440px;background:var(--bg2);border:2px solid #9c27b0;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);z-index:300;overflow:hidden;display:none;transition:all .3s ease}
|
|
3522
|
+
.browser-viewer--open{display:block}
|
|
3523
|
+
.browser-viewer__header{display:flex;align-items:center;gap:6px;padding:6px 10px;background:#1a1a2e;border-bottom:1px solid #9c27b0;font-size:10px;color:#ce93d8}
|
|
3524
|
+
.browser-viewer__dot{width:6px;height:6px;border-radius:50%;background:#9c27b0;animation:bvpulse 1.5s infinite}
|
|
3525
|
+
@keyframes bvpulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
3526
|
+
.browser-viewer__title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3527
|
+
.browser-viewer__close{background:none;border:none;color:#666;cursor:pointer;font-size:14px;padding:0 4px}
|
|
3528
|
+
.browser-viewer__close:hover{color:#fff}
|
|
3529
|
+
.browser-viewer__frame{width:100%;aspect-ratio:16/9;background:#000;display:flex;align-items:center;justify-content:center}
|
|
3530
|
+
.browser-viewer__frame img{width:100%;height:100%;object-fit:contain}
|
|
3531
|
+
.browser-viewer__status{padding:4px 10px;font-size:9px;color:var(--dim);border-top:1px solid var(--border)}
|
|
3532
|
+
@media(max-width:600px){.browser-viewer{width:calc(100vw - 24px);top:8px;left:8px}}
|
|
3533
|
+
@media(min-width:901px){.browser-viewer{left:232px}}
|
|
3534
|
+
.chat__bar{display:flex;flex-wrap:wrap;gap:6px;padding:10px 0 12px 0;border-top:1px solid var(--border);flex-shrink:0;align-items:flex-end}
|
|
3535
|
+
.chat__bar-tools{display:flex;gap:4px;align-items:center;width:100%}
|
|
3536
|
+
.chat__input{flex:1;resize:none;min-height:44px;max-height:120px;padding:10px 14px;font-size:14px}
|
|
3537
|
+
.chat__send{background:var(--green3);color:var(--bg);padding:10px 20px;border-radius:var(--r);font-weight:700;font-size:14px;white-space:nowrap}
|
|
3538
|
+
.chat__send:disabled{opacity:.4}
|
|
3539
|
+
.chat__stop{white-space:nowrap}
|
|
3540
|
+
|
|
3541
|
+
/* ---- MOBILE TOUCH (Termux / small screens) ---- */
|
|
3542
|
+
@media(max-width:600px){
|
|
3543
|
+
.msg--user .msg__bubble,.msg--assistant .msg__bubble{font-size:14px;padding:12px 14px;max-width:94%;line-height:1.55}
|
|
3544
|
+
.msg__label{font-size:11px}
|
|
3545
|
+
.chat__bar{flex-wrap:wrap;gap:6px;padding:8px 4px 10px 4px}
|
|
3546
|
+
.chat__bar-tools{flex-wrap:wrap;gap:4px}
|
|
3547
|
+
.chat__input{min-height:48px;font-size:15px;padding:12px 14px;width:100%;flex-basis:100%}
|
|
3548
|
+
.chat__send{padding:12px 20px;font-size:15px;flex-grow:1}
|
|
3549
|
+
.chat__stop{flex-grow:1}
|
|
3550
|
+
.chat__empty-title{font-size:22px}
|
|
3551
|
+
.header{padding:10px 12px}
|
|
3552
|
+
.header__title{font-size:15px}
|
|
3553
|
+
.content{padding:10px}
|
|
3554
|
+
/* Conversation sidebar as overlay on mobile, not side panel */
|
|
3555
|
+
#convSidebar{position:fixed!important;top:0!important;left:0!important;height:100dvh!important;width:260px!important;box-shadow:4px 0 20px rgba(0,0,0,0.8)!important;z-index:250!important}
|
|
3556
|
+
}
|
|
3557
|
+
.chat__stop{background:var(--red);color:var(--bright);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px;display:none}
|
|
3558
|
+
.chat__stop--visible{display:block}
|
|
3559
|
+
|
|
3560
|
+
/* ---- TASKS ---- */
|
|
3561
|
+
.task-bar{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
|
|
3562
|
+
.task-bar input{flex:1;min-width:150px}
|
|
3563
|
+
.task-bar select{background:var(--bg2);color:var(--text);border:1px solid var(--border);padding:8px;border-radius:var(--r);font-size:12px}
|
|
3564
|
+
.task-bar button{background:var(--green3);color:var(--bg);padding:8px 16px;border-radius:var(--r);font-weight:700;font-size:12px}
|
|
3565
|
+
.task{display:flex;align-items:center;gap:10px;padding:10px 14px}
|
|
3566
|
+
.task--done{opacity:.5}
|
|
3567
|
+
.task__check{width:18px;height:18px;border:2px solid var(--border2);border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:12px;color:var(--green)}
|
|
3568
|
+
.task__check--done{background:var(--green3);border-color:var(--green)}
|
|
3569
|
+
.task__desc{flex:1;min-width:0}
|
|
3570
|
+
.task__priority{font-size:9px;padding:2px 6px;border-radius:4px;text-transform:uppercase;font-weight:700}
|
|
3571
|
+
.task__priority--high,.task__priority--critical{background:var(--red);color:var(--bright)}
|
|
3572
|
+
.task__priority--medium{background:var(--amber);color:var(--bg)}
|
|
3573
|
+
.task__priority--low{background:var(--border2);color:var(--dim)}
|
|
3574
|
+
|
|
3575
|
+
/* ---- PLAN ---- */
|
|
3576
|
+
.plan-summary{padding:16px;border-left:3px solid var(--green);background:var(--bg2);border-radius:0 var(--r) var(--r) 0;margin-bottom:16px;line-height:1.6}
|
|
3577
|
+
.plan-action{display:flex;gap:10px;align-items:baseline;padding:6px 0}
|
|
3578
|
+
.plan-action__time{color:var(--amber);min-width:50px;font-weight:600}
|
|
3579
|
+
.plan-action__text{flex:1}
|
|
3580
|
+
.plan-action__priority{font-size:9px;padding:1px 5px;border-radius:3px;font-weight:700}
|
|
3581
|
+
|
|
3582
|
+
/* ---- EMAILS ---- */
|
|
3583
|
+
.email{padding:12px 14px}
|
|
3584
|
+
.email__header{display:flex;justify-content:space-between;gap:8px}
|
|
3585
|
+
.email__from{color:var(--cyan);font-weight:600;font-size:12px}
|
|
3586
|
+
.email__date{color:var(--dim);font-size:10px;white-space:nowrap}
|
|
3587
|
+
.email__subject{color:var(--bright);margin-top:3px}
|
|
3588
|
+
.email__snippet{color:var(--dim);font-size:11px;margin-top:3px}
|
|
3589
|
+
|
|
3590
|
+
/* ---- CALENDAR ---- */
|
|
3591
|
+
.event{display:flex;gap:12px;align-items:center;padding:10px 14px}
|
|
3592
|
+
.event__time{color:var(--amber);font-weight:600;min-width:100px;white-space:nowrap;font-size:12px}
|
|
3593
|
+
.event__title{color:var(--bright);flex:1}
|
|
3594
|
+
.event__location{color:var(--dim);font-size:11px}
|
|
3595
|
+
|
|
3596
|
+
/* ---- AGENTS ---- */
|
|
3597
|
+
.agents-grid{display:grid;grid-template-columns:1fr;gap:6px}
|
|
3598
|
+
@media(min-width:500px){.agents-grid{grid-template-columns:1fr 1fr}}
|
|
3599
|
+
@media(min-width:901px){.agents-grid{grid-template-columns:1fr 1fr 1fr}}
|
|
3600
|
+
.agent-card{padding:8px 10px;text-align:left;cursor:pointer;transition:border-color .15s;display:flex;align-items:center;gap:10px}
|
|
3601
|
+
.agent-card:hover{border-color:var(--green3)}
|
|
3602
|
+
.agent-card__icon{font-size:18px;flex-shrink:0}
|
|
3603
|
+
.agent-card__body{min-width:0;flex:1}
|
|
3604
|
+
.agent-card__name{color:var(--green);font-weight:700;font-size:11px}
|
|
3605
|
+
.agent-card__tagline{font-size:9px;color:var(--text);line-height:1.3}
|
|
3606
|
+
.agent-card__cat{display:none}
|
|
3607
|
+
|
|
3608
|
+
/* ---- MODAL ---- */
|
|
3609
|
+
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:300;align-items:center;justify-content:center}
|
|
3610
|
+
.modal-overlay--open{display:flex}
|
|
3611
|
+
.modal{background:var(--bg2);border:1px solid var(--border2);border-radius:8px;width:92%;max-width:560px;max-height:90vh;display:flex;flex-direction:column}
|
|
3612
|
+
.modal--chat{height:80vh}
|
|
3613
|
+
.modal__header{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--border)}
|
|
3614
|
+
.modal__header h2{font-size:16px;color:var(--green)}
|
|
3615
|
+
.modal__close{background:none;color:var(--dim);font-size:24px;padding:0 4px}
|
|
3616
|
+
.modal__body{padding:16px;overflow-y:auto;flex:1}
|
|
3617
|
+
.modal__body textarea{width:100%;min-height:80px;margin-bottom:10px}
|
|
3618
|
+
.modal__response{background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);padding:12px;word-wrap:break-word;max-height:400px;overflow-y:auto;font-size:13px}
|
|
3619
|
+
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)}
|
|
3620
|
+
.modal--chat .modal__body{padding:0;display:flex;flex-direction:column;overflow:hidden}
|
|
3621
|
+
.agent-chat{display:flex;flex-direction:column;height:100%;min-height:0;padding:12px 16px;box-sizing:border-box}
|
|
3622
|
+
.agent-chat__messages{flex:1;overflow-y:auto;padding:4px 0 8px;min-height:0;display:flex;flex-direction:column;gap:8px}
|
|
3623
|
+
.agent-chat__bubble{max-width:88%;border-radius:8px;padding:8px 12px;font-size:13px;line-height:1.55;word-wrap:break-word}
|
|
3624
|
+
.agent-chat__bubble--user{align-self:flex-end;background:var(--green3);color:#e8ffe8}
|
|
3625
|
+
.agent-chat__bubble--agent{align-self:flex-start;background:var(--bg3);border:1px solid var(--border)}
|
|
3626
|
+
.agent-chat__footer{display:flex;flex-direction:column;gap:6px;padding-top:8px;border-top:1px solid var(--border)}
|
|
3627
|
+
.agent-chat__drop{border:2px dashed var(--border2);border-radius:6px;padding:8px;text-align:center;color:var(--dim);font-size:11px;cursor:pointer;transition:border-color .2s}
|
|
3628
|
+
.agent-chat__drop:hover{border-color:var(--green)}
|
|
3629
|
+
.agent-chat__input-row{display:flex;gap:6px;align-items:flex-end}
|
|
3630
|
+
.agent-chat__input{flex:1;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:8px 10px;color:var(--fg);font-size:13px;font-family:var(--font);resize:none;min-height:36px;max-height:120px;overflow-y:auto;outline:none}
|
|
3631
|
+
.agent-chat__input:focus{border-color:var(--green)}
|
|
3632
|
+
.btn{padding:8px 16px;border-radius:var(--r);font-size:12px;font-weight:600}
|
|
3633
|
+
.btn--primary{background:var(--green3);color:var(--bg)}
|
|
3634
|
+
.btn--secondary{background:var(--bg3);color:var(--dim);border:1px solid var(--border)}
|
|
3635
|
+
|
|
3636
|
+
/* ---- SPINNER ---- */
|
|
3637
|
+
.spinner{width:24px;height:24px;border:2px solid var(--border);border-top-color:var(--green);border-radius:50%;animation:spin .6s linear infinite;margin:0 auto 12px}
|
|
3638
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
3639
|
+
.thinking-dots{display:inline-flex;gap:4px;align-items:center;padding:2px 0}
|
|
3640
|
+
.thinking-dots span{width:6px;height:6px;border-radius:50%;background:var(--dim);animation:tdot 1.2s ease-in-out infinite}
|
|
3641
|
+
.thinking-dots span:nth-child(2){animation-delay:.2s}
|
|
3642
|
+
.thinking-dots span:nth-child(3){animation-delay:.4s}
|
|
3643
|
+
@keyframes tdot{0%,80%,100%{opacity:.2;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}
|
|
3644
|
+
|
|
3645
|
+
/* ---- TOASTS (real-time notifications) ---- */
|
|
3646
|
+
.toast-container{position:fixed;top:16px;right:16px;z-index:500;display:flex;flex-direction:column;gap:8px;pointer-events:none}
|
|
3647
|
+
.toast{background:var(--bg3);border:1px solid var(--green);border-radius:8px;padding:12px 16px;max-width:320px;animation:toastIn .3s;pointer-events:auto;cursor:pointer;box-shadow:0 4px 20px rgba(0,0,0,0.5)}
|
|
3648
|
+
.toast--email{border-color:var(--cyan)}
|
|
3649
|
+
.toast--meeting{border-color:var(--amber)}
|
|
3650
|
+
.toast--security{border-color:var(--red)}
|
|
3651
|
+
.toast--plan{border-color:var(--green)}
|
|
3652
|
+
.toast__title{font-size:11px;font-weight:700;margin-bottom:3px}
|
|
3653
|
+
.toast--email .toast__title{color:var(--cyan)}
|
|
3654
|
+
.toast--meeting .toast__title{color:var(--amber)}
|
|
3655
|
+
.toast--security .toast__title{color:var(--red)}
|
|
3656
|
+
.toast--plan .toast__title{color:var(--green)}
|
|
3657
|
+
.toast__body{font-size:11px;color:var(--text);line-height:1.4}
|
|
3658
|
+
.toast--fadeout{animation:toastOut .3s forwards}
|
|
3659
|
+
@keyframes toastIn{from{transform:translateX(100%);opacity:0}to{transform:none;opacity:1}}
|
|
3660
|
+
@keyframes toastOut{from{opacity:1}to{opacity:0;transform:translateX(40px)}}
|
|
3661
|
+
|
|
3662
|
+
/* ---- VOICE MIC BUTTON (in chat bar) ---- */
|
|
3663
|
+
.chat__mic{background:var(--bg3);color:var(--green);border:1px solid var(--border2);width:40px;height:40px;border-radius:var(--r);display:flex;align-items:center;justify-content:center;font-size:18px;cursor:pointer;flex-shrink:0;transition:all .2s}
|
|
3664
|
+
.chat__mic:hover{border-color:var(--green3);background:var(--greendim)}
|
|
3665
|
+
.chat__mic--recording{border-color:var(--red);color:var(--red);background:rgba(255,23,68,0.1);animation:micPulse 1.5s ease-in-out infinite}
|
|
3666
|
+
@keyframes micPulse{0%,100%{box-shadow:none}50%{box-shadow:0 0 0 6px rgba(255,23,68,0.2)}}
|
|
3667
|
+
|
|
3668
|
+
/* ---- STUDIO ---- */
|
|
3669
|
+
.studio-header{margin-bottom:20px}
|
|
3670
|
+
.studio-header h2{font-size:15px;color:var(--green);margin-bottom:4px}
|
|
3671
|
+
.studio-header p{font-size:11px;color:var(--dim);line-height:1.5}
|
|
3672
|
+
.studio-input-row{display:flex;gap:8px;margin-bottom:20px}
|
|
3673
|
+
.studio-input-row textarea{flex:1;resize:none;height:60px;padding:10px 14px;font-size:13px;border-radius:var(--r);border:1px solid var(--border2)}
|
|
3674
|
+
.studio-input-row textarea:focus{border-color:var(--green3)}
|
|
3675
|
+
.studio-run-btn{background:var(--green3);color:var(--bg);padding:0 20px;border-radius:var(--r);font-weight:700;font-size:13px;white-space:nowrap;align-self:stretch;min-width:90px}
|
|
3676
|
+
.studio-run-btn:disabled{opacity:.4}
|
|
3677
|
+
.studio-canvas{position:relative;width:100%;min-height:220px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;margin-bottom:20px;overflow:hidden}
|
|
3678
|
+
.studio-canvas__empty{display:flex;align-items:center;justify-content:center;height:180px;color:var(--dim);font-size:11px;flex-direction:column;gap:8px}
|
|
3679
|
+
.studio-canvas__empty-icon{font-size:32px;opacity:.3}
|
|
3680
|
+
.studio-nodes{display:flex;align-items:center;gap:0;padding:28px 24px;overflow-x:auto;min-height:130px;background:var(--bg2);border-radius:10px;border:1px solid var(--border);margin-bottom:16px}
|
|
3681
|
+
.studio-node{position:relative;display:flex;flex-direction:column;align-items:center;gap:7px;min-width:106px;max-width:126px}
|
|
3682
|
+
.studio-node__circle{width:56px;height:56px;border-radius:14px;border:1.5px solid var(--border2);background:var(--bg3);display:flex;align-items:center;justify-content:center;font-size:22px;transition:all .35s;flex-shrink:0}
|
|
3683
|
+
.studio-node--active .studio-node__circle{border-color:var(--green3);box-shadow:0 0 0 4px rgba(99,102,241,.15);background:var(--greendim);animation:stRing 1.4s ease-out infinite}
|
|
3684
|
+
.studio-node--done .studio-node__circle{border-color:#22c55e;background:rgba(34,197,94,.08);box-shadow:0 0 0 3px rgba(34,197,94,.12)}
|
|
3685
|
+
.studio-node--error .studio-node__circle{border-color:var(--red);background:rgba(239,68,68,.07)}
|
|
3686
|
+
.studio-node__label{font-size:10px;color:var(--dim);text-align:center;line-height:1.3;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}
|
|
3687
|
+
.studio-node--active .studio-node__label{color:var(--green);font-weight:700}
|
|
3688
|
+
.studio-node--done .studio-node__label{color:#22c55e;font-weight:600}
|
|
3689
|
+
.studio-node__status{font-size:8px;padding:2px 8px;border-radius:20px;font-weight:700;text-transform:uppercase;letter-spacing:.6px}
|
|
3690
|
+
.studio-node__status--waiting{background:rgba(156,163,175,.1);color:var(--dim)}
|
|
3691
|
+
.studio-node__status--running{background:rgba(99,102,241,.15);color:var(--green);animation:stPulse .9s ease-in-out infinite}
|
|
3692
|
+
.studio-node__status--done{background:rgba(34,197,94,.12);color:#22c55e}
|
|
3693
|
+
.studio-node__status--error{background:rgba(239,68,68,.12);color:var(--red)}
|
|
3694
|
+
@keyframes stPulse{0%,100%{opacity:1}50%{opacity:.25}}
|
|
3695
|
+
@keyframes stRing{0%{box-shadow:0 0 0 4px rgba(99,102,241,.15)}70%{box-shadow:0 0 0 12px rgba(99,102,241,0)}100%{box-shadow:0 0 0 4px rgba(99,102,241,.15)}}
|
|
3696
|
+
@keyframes stFlow{0%{opacity:.4;transform:scaleX(.7)}100%{opacity:1;transform:scaleX(1)}}
|
|
3697
|
+
.studio-arrow{display:flex;align-items:center;color:var(--border2);font-size:18px;padding:0 8px;flex-shrink:0;margin-bottom:30px;transition:color .4s}
|
|
3698
|
+
.studio-arrow--active{color:var(--green3);animation:stFlow .5s ease-in-out infinite alternate}
|
|
3699
|
+
.studio-arrow--done{color:#22c55e}
|
|
3700
|
+
.studio-log{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px;max-height:380px;overflow-y:auto;font-size:11.5px;line-height:1.65}
|
|
3701
|
+
.studio-log-entry{margin-bottom:12px;padding:10px 12px;border-radius:8px;background:var(--bg3);border:1px solid var(--border)}
|
|
3702
|
+
.studio-log-entry:last-child{margin-bottom:0}
|
|
3703
|
+
.studio-log-entry__header{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
|
3704
|
+
.studio-log-entry__icon{font-size:15px}
|
|
3705
|
+
.studio-log-entry__agent{font-weight:700;color:var(--green);font-size:11px;letter-spacing:.3px}
|
|
3706
|
+
.studio-log-entry__time{color:var(--dim);font-size:9px;margin-left:auto;opacity:.7}
|
|
3707
|
+
.studio-log-entry__text{color:var(--text);word-wrap:break-word}
|
|
3708
|
+
.studio-log-entry--system .studio-log-entry__agent{color:var(--cyan)}
|
|
3709
|
+
.studio-log-entry--error .studio-log-entry__agent{color:var(--red)}
|
|
3710
|
+
.studio-result{margin-top:16px;padding:16px;background:var(--greendim);border:1px solid var(--green3);border-radius:8px}
|
|
3711
|
+
.studio-result__title{font-size:10px;color:var(--green);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}
|
|
3712
|
+
.studio-result__body{font-size:13px;color:var(--text);word-wrap:break-word;line-height:1.7}
|
|
3713
|
+
.studio-example-btn{display:inline-block;padding:5px 12px;border:1px solid var(--border2);border-radius:20px;font-size:10px;color:var(--dim);cursor:pointer;background:none;margin:0 4px 6px 0;transition:all .15s}
|
|
3714
|
+
.studio-example-btn:hover{border-color:var(--green3);color:var(--green);background:var(--greendim)}
|
|
3715
|
+
.studio-tools-panel{width:220px;flex-shrink:0;border:1px solid var(--border);border-radius:10px;padding:12px;background:var(--bg2);max-height:600px;overflow-y:auto}
|
|
3716
|
+
.studio-tool-item{display:flex;align-items:flex-start;gap:8px;padding:8px;border-radius:6px;cursor:pointer;transition:background .15s;margin-bottom:4px}
|
|
3717
|
+
.studio-tool-item:hover{background:var(--bg3);border-radius:6px}
|
|
3718
|
+
.studio-tool-icon{font-size:16px;flex-shrink:0;margin-top:1px}
|
|
3719
|
+
.studio-session-item{padding:10px 12px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;margin-bottom:8px}
|
|
3720
|
+
.studio-session-task{font-size:11px;color:var(--text);font-weight:500}
|
|
3721
|
+
|
|
3722
|
+
/* ---- MARKDOWN BODY ---- */
|
|
3723
|
+
.md-body{line-height:1.75}
|
|
3724
|
+
.md-body .md-p{margin:0 0 10px;color:var(--text);font-size:13.5px}
|
|
3725
|
+
.md-body .md-h1{font-size:18px;font-weight:700;color:var(--bright);margin:18px 0 10px;line-height:1.3}
|
|
3726
|
+
.md-body .md-h2{font-size:15px;font-weight:600;color:var(--bright);margin:16px 0 8px;line-height:1.3}
|
|
3727
|
+
.md-body .md-h3{font-size:13.5px;font-weight:600;color:var(--green);margin:12px 0 6px;line-height:1.3}
|
|
3728
|
+
.md-body .md-ul,.md-body .md-ol{margin:6px 0 12px 0;padding-left:22px}
|
|
3729
|
+
.md-body .md-ul li,.md-body .md-ol li{margin-bottom:5px;color:var(--text);font-size:13.5px;line-height:1.65}
|
|
3730
|
+
.md-body .md-ul{list-style:disc}
|
|
3731
|
+
.md-body .md-ol{list-style:decimal}
|
|
3732
|
+
.md-body .md-code{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:12px 14px;margin:10px 0;overflow-x:auto;font-family:var(--mono);font-size:12px;color:#a5b4fc;white-space:pre}
|
|
3733
|
+
.md-body .md-inline-code{background:var(--bg3);border:1px solid var(--border);border-radius:3px;padding:1px 5px;font-family:var(--mono);font-size:12px;color:#a5b4fc}
|
|
3734
|
+
.md-body .md-bq{border-left:3px solid var(--green3);padding:6px 12px;margin:8px 0;color:var(--dim);font-style:italic}
|
|
3735
|
+
.md-body .md-hr{border:none;border-top:1px solid var(--border);margin:14px 0}
|
|
3736
|
+
.md-body .md-spacer{height:6px}
|
|
3737
|
+
.md-body strong{font-weight:700;color:var(--bright)}
|
|
3738
|
+
.md-body em{font-style:italic;color:var(--dim)}
|
|
3739
|
+
.md-body del{text-decoration:line-through;color:var(--dim)}
|
|
3740
|
+
.md-body a{color:var(--cyan);text-decoration:underline}
|
|
3741
|
+
|
|
3742
|
+
/* ---- CHAT bubble markdown tweaks ---- */
|
|
3743
|
+
.msg--assistant .msg__bubble{white-space:normal}
|
|
3744
|
+
.msg--user .msg__bubble{white-space:pre-wrap}
|
|
2783
3745
|
`;
|
|
2784
3746
|
|
|
2785
3747
|
return `<!DOCTYPE html>
|
|
@@ -2793,6 +3755,9 @@ init();
|
|
|
2793
3755
|
<meta name="apple-mobile-web-app-title" content="NHA">
|
|
2794
3756
|
<meta name="theme-color" content="#0a0a0a">
|
|
2795
3757
|
<link rel="manifest" href="/manifest.json">
|
|
3758
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
3759
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
3760
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
2796
3761
|
<title>NHA</title>
|
|
2797
3762
|
<style>${CSS}</style>
|
|
2798
3763
|
</head>
|
|
@@ -2843,6 +3808,7 @@ init();
|
|
|
2843
3808
|
<div class="sidebar__section">
|
|
2844
3809
|
<div class="sidebar__label">AI</div>
|
|
2845
3810
|
<div class="nav-item" data-view="agents" onclick="switchView('agents')"><span class="nav-item__icon">🤖</span> Agents</div>
|
|
3811
|
+
<div class="nav-item" data-view="studio" onclick="switchView('studio')"><span class="nav-item__icon">⚙</span> Studio <span style="font-size:8px;padding:1px 5px;border-radius:4px;background:rgba(99,102,241,.25);color:var(--green);margin-left:4px;font-weight:700">NEW</span></div>
|
|
2846
3812
|
<div class="nav-item" data-view="collab" onclick="switchView('collab')"><span class="nav-item__icon">🔒</span> AgentMessenger <span id="collabBadge" style="display:none;background:var(--red);color:#fff;font-size:9px;padding:1px 5px;border-radius:8px;margin-left:4px;font-family:var(--mono)">0</span></div>
|
|
2847
3813
|
</div>
|
|
2848
3814
|
<div class="sidebar__section">
|
|
@@ -2876,29 +3842,41 @@ init();
|
|
|
2876
3842
|
</div>
|
|
2877
3843
|
|
|
2878
3844
|
<div class="modal-overlay" id="agentModal">
|
|
2879
|
-
<div class="modal">
|
|
3845
|
+
<div class="modal modal--chat">
|
|
2880
3846
|
<div class="modal__header">
|
|
2881
|
-
<h2 id="modalName">Agent</h2>
|
|
3847
|
+
<div><h2 id="modalName">Agent</h2><div id="modalAgentDesc" style="font-size:10px;color:var(--dim);margin-top:2px"></div></div>
|
|
2882
3848
|
<button class="modal__close" onclick="closeModal()">×</button>
|
|
2883
3849
|
</div>
|
|
2884
3850
|
<div class="modal__body">
|
|
2885
|
-
<
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
3851
|
+
<div class="agent-chat">
|
|
3852
|
+
<div class="agent-chat__messages" id="agentMessages"></div>
|
|
3853
|
+
<div class="agent-chat__footer">
|
|
3854
|
+
<div class="agent-chat__drop" id="agentFileDropZone" onclick="document.getElementById('agentFileInput').click()" ondragover="event.preventDefault();this.style.borderColor='var(--green)'" ondragleave="this.style.borderColor=''" ondrop="event.preventDefault();this.style.borderColor='';handleFileDrop(event)">
|
|
3855
|
+
Drop a file or click to attach
|
|
3856
|
+
<input type="file" id="agentFileInput" style="display:none" onchange="handleFileSelect(this)">
|
|
3857
|
+
</div>
|
|
3858
|
+
<div id="agentFileInfo" style="display:none;font-size:10px;color:var(--cyan)"></div>
|
|
3859
|
+
<div class="agent-chat__input-row">
|
|
3860
|
+
<textarea class="agent-chat__input" id="modalPrompt" rows="1" placeholder="Ask this agent... (Enter to send)" onkeydown="if(event.key==='Enter'&&!event.shiftKey){askAgent();event.preventDefault();}"></textarea>
|
|
3861
|
+
<button class="btn btn--primary" id="agentAskBtn" onclick="askAgent()" style="height:36px;padding:0 14px">Send</button>
|
|
3862
|
+
</div>
|
|
3863
|
+
</div>
|
|
2889
3864
|
</div>
|
|
2890
|
-
<div id="fileInfo" style="display:none;font-size:10px;color:var(--cyan);margin-bottom:8px"></div>
|
|
2891
|
-
<div class="modal__response" id="modalResponse" style="display:none"></div>
|
|
2892
|
-
</div>
|
|
2893
|
-
<div class="modal__footer">
|
|
2894
|
-
<button class="btn btn--secondary" onclick="closeModal()">Close</button>
|
|
2895
|
-
<button class="btn btn--primary" onclick="askAgent()">Ask</button>
|
|
2896
3865
|
</div>
|
|
2897
3866
|
</div>
|
|
2898
3867
|
</div>
|
|
2899
3868
|
|
|
2900
3869
|
<div id="canvasPanel"><div class="cvs-header"><div style="display:flex;align-items:center;gap:8px"><button id="canvasTabC" onclick="canvasShowCanvas()" style="background:none;border:none;border-bottom:2px solid var(--green);color:var(--green);cursor:pointer;font-family:var(--mono);font-size:11px;padding:2px 6px">Canvas</button><button id="canvasTabB" onclick="canvasShowBrowser()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-family:var(--mono);font-size:11px;padding:2px 6px">Browser</button><span id="canvasTitle" style="font-family:var(--mono);color:var(--green);font-size:11px;margin-left:8px">Canvas</span></div><div style="display:flex;align-items:center;gap:4px"><span id="canvasNav" style="display:none;gap:4px"><button onclick="canvasPrev()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Previous">◀</button><button onclick="canvasNext()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Next">▶</button></span><button onclick="canvasCopyText()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;font-family:var(--mono)" title="Copy text content">Copy</button><button onclick="canvasCopyHTML()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;font-family:var(--mono)" title="Copy HTML source">HTML</button><button onclick="canvasCopyImage()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;font-family:var(--mono)" title="Copy as image">IMG</button><button onclick="toggleCanvasSize()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Resize">⤢</button><button onclick="closeCanvas()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Close">×</button></div></div><iframe id="canvasFrame" sandbox="allow-scripts" srcdoc=""></iframe></div>
|
|
2901
|
-
<
|
|
3870
|
+
<div id="agentEditorOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:400;align-items:center;justify-content:center;padding:16px"></div>
|
|
3871
|
+
<div class="lightbox-overlay" id="lightboxOverlay" onclick="closeLightbox()">
|
|
3872
|
+
<span class="lightbox-close" onclick="closeLightbox()">×</span>
|
|
3873
|
+
<img id="lightboxImg" src="" alt="Preview">
|
|
3874
|
+
</div>
|
|
3875
|
+
<script src="/nha-ui.js?v=${ts}"></script>
|
|
2902
3876
|
</body>
|
|
2903
3877
|
</html>`;
|
|
2904
3878
|
}
|
|
3879
|
+
|
|
3880
|
+
export function getJS() {
|
|
3881
|
+
return JS;
|
|
3882
|
+
}
|