clay-server 2.38.0-beta.3 → 2.38.0-beta.4
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.
|
@@ -1,35 +1,102 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
|
|
2
|
+
Clay FAB + popover — phablet-style chat reachable from anywhere
|
|
3
3
|
==========================================================================
|
|
4
|
-
The
|
|
5
|
-
|
|
6
|
-
(rounded card with
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
The FAB (#clay-fab) sits at the bottom-right corner over all page
|
|
5
|
+
content. Clicking it toggles #clay-popover, which contains the
|
|
6
|
+
.home-chat-frame (rounded card with header / messages / input).
|
|
7
|
+
Inspired by Vercel's persistent toolbar pattern. */
|
|
8
|
+
|
|
9
|
+
/* --- FAB --- */
|
|
10
|
+
|
|
11
|
+
.clay-fab {
|
|
12
|
+
position: fixed;
|
|
13
|
+
right: 20px;
|
|
14
|
+
bottom: 20px;
|
|
15
|
+
width: 56px;
|
|
16
|
+
height: 56px;
|
|
17
|
+
border-radius: 50%;
|
|
18
|
+
border: none;
|
|
19
|
+
background: var(--bg, #fff);
|
|
20
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18), 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
z-index: 9000;
|
|
14
23
|
display: flex;
|
|
15
|
-
align-items:
|
|
24
|
+
align-items: center;
|
|
16
25
|
justify-content: center;
|
|
17
|
-
padding:
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
padding: 0;
|
|
27
|
+
transition: transform 0.18s cubic-bezier(.2,.7,.3,1.3), box-shadow 0.18s, opacity 0.18s;
|
|
28
|
+
}
|
|
29
|
+
.clay-fab:hover {
|
|
30
|
+
transform: translateY(-1px) scale(1.04);
|
|
31
|
+
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22), 0 3px 6px rgba(0, 0, 0, 0.10);
|
|
32
|
+
}
|
|
33
|
+
.clay-fab:active {
|
|
34
|
+
transform: scale(0.96);
|
|
35
|
+
}
|
|
36
|
+
.clay-fab.open {
|
|
37
|
+
/* Hide while popover is open — the popover X button is the close path */
|
|
38
|
+
transform: scale(0.6);
|
|
39
|
+
opacity: 0;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.clay-fab-icon {
|
|
44
|
+
width: 36px;
|
|
45
|
+
height: 36px;
|
|
46
|
+
border-radius: 50%;
|
|
47
|
+
object-fit: cover;
|
|
48
|
+
pointer-events: none;
|
|
20
49
|
}
|
|
21
50
|
|
|
22
|
-
|
|
51
|
+
/* Subtle pulse ring to draw the eye on first paint */
|
|
52
|
+
.clay-fab-pulse {
|
|
53
|
+
position: absolute;
|
|
54
|
+
inset: -2px;
|
|
55
|
+
border-radius: 50%;
|
|
56
|
+
border: 2px solid var(--accent);
|
|
57
|
+
opacity: 0;
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
animation: clay-fab-pulse 2.4s ease-out 1s 2;
|
|
60
|
+
}
|
|
61
|
+
@keyframes clay-fab-pulse {
|
|
62
|
+
0% { opacity: 0; transform: scale(1); }
|
|
63
|
+
20% { opacity: 0.55; }
|
|
64
|
+
100% { opacity: 0; transform: scale(1.6); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* --- Popover --- */
|
|
68
|
+
|
|
69
|
+
.clay-popover {
|
|
70
|
+
position: fixed;
|
|
71
|
+
right: 20px;
|
|
72
|
+
bottom: 20px;
|
|
73
|
+
width: 380px;
|
|
74
|
+
height: 560px;
|
|
75
|
+
max-width: calc(100vw - 32px);
|
|
76
|
+
max-height: calc(100vh - 40px);
|
|
77
|
+
z-index: 9001;
|
|
78
|
+
display: flex;
|
|
79
|
+
transform-origin: bottom right;
|
|
80
|
+
animation: clay-popover-in 0.22s cubic-bezier(.2,.7,.3,1.05);
|
|
81
|
+
}
|
|
82
|
+
.clay-popover.hidden {
|
|
83
|
+
display: none;
|
|
84
|
+
}
|
|
85
|
+
@keyframes clay-popover-in {
|
|
86
|
+
from { transform: translateY(8px) scale(0.96); opacity: 0; }
|
|
87
|
+
to { transform: translateY(0) scale(1); opacity: 1; }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.clay-popover .home-chat-frame {
|
|
23
91
|
width: 100%;
|
|
24
|
-
|
|
92
|
+
height: 100%;
|
|
25
93
|
display: flex;
|
|
26
94
|
flex-direction: column;
|
|
27
95
|
background: var(--bg-alt, var(--bg));
|
|
28
96
|
border: 1px solid var(--border);
|
|
29
|
-
border-radius:
|
|
30
|
-
box-shadow: 0
|
|
97
|
+
border-radius: 18px;
|
|
98
|
+
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.22), 0 4px 10px rgba(0, 0, 0, 0.10);
|
|
31
99
|
overflow: hidden;
|
|
32
|
-
height: 100%;
|
|
33
100
|
}
|
|
34
101
|
|
|
35
102
|
/* --- Header --- */
|
|
@@ -37,15 +104,15 @@
|
|
|
37
104
|
.home-chat-header {
|
|
38
105
|
display: flex;
|
|
39
106
|
align-items: center;
|
|
40
|
-
gap:
|
|
41
|
-
padding: 14px
|
|
107
|
+
gap: 10px;
|
|
108
|
+
padding: 12px 12px 12px 14px;
|
|
42
109
|
border-bottom: 1px solid var(--border);
|
|
43
110
|
background: var(--bg);
|
|
44
111
|
}
|
|
45
112
|
|
|
46
113
|
.home-chat-avatar-wrap {
|
|
47
|
-
width:
|
|
48
|
-
height:
|
|
114
|
+
width: 34px;
|
|
115
|
+
height: 34px;
|
|
49
116
|
border-radius: 50%;
|
|
50
117
|
overflow: hidden;
|
|
51
118
|
flex-shrink: 0;
|
|
@@ -68,14 +135,14 @@
|
|
|
68
135
|
}
|
|
69
136
|
|
|
70
137
|
.home-chat-title {
|
|
71
|
-
font-size:
|
|
138
|
+
font-size: 14px;
|
|
72
139
|
font-weight: 600;
|
|
73
140
|
color: var(--text);
|
|
74
141
|
line-height: 1.2;
|
|
75
142
|
}
|
|
76
143
|
|
|
77
144
|
.home-chat-subtitle {
|
|
78
|
-
font-size:
|
|
145
|
+
font-size: 11px;
|
|
79
146
|
color: var(--text-muted, var(--text-dimmer));
|
|
80
147
|
line-height: 1.2;
|
|
81
148
|
margin-top: 2px;
|
|
@@ -87,8 +154,8 @@
|
|
|
87
154
|
.home-chat-icon-btn {
|
|
88
155
|
background: none;
|
|
89
156
|
border: none;
|
|
90
|
-
width:
|
|
91
|
-
height:
|
|
157
|
+
width: 30px;
|
|
158
|
+
height: 30px;
|
|
92
159
|
border-radius: 8px;
|
|
93
160
|
display: flex;
|
|
94
161
|
align-items: center;
|
|
@@ -102,8 +169,8 @@
|
|
|
102
169
|
color: var(--text);
|
|
103
170
|
}
|
|
104
171
|
.home-chat-icon-btn .lucide {
|
|
105
|
-
width:
|
|
106
|
-
height:
|
|
172
|
+
width: 16px;
|
|
173
|
+
height: 16px;
|
|
107
174
|
}
|
|
108
175
|
|
|
109
176
|
/* --- Messages list --- */
|
|
@@ -111,19 +178,19 @@
|
|
|
111
178
|
.home-chat-messages {
|
|
112
179
|
flex: 1;
|
|
113
180
|
overflow-y: auto;
|
|
114
|
-
padding:
|
|
181
|
+
padding: 14px 12px;
|
|
115
182
|
display: flex;
|
|
116
183
|
flex-direction: column;
|
|
117
|
-
gap:
|
|
184
|
+
gap: 8px;
|
|
118
185
|
background: var(--input-bg, var(--bg));
|
|
119
186
|
scroll-behavior: smooth;
|
|
120
187
|
}
|
|
121
188
|
|
|
122
189
|
.home-chat-bubble {
|
|
123
190
|
max-width: 88%;
|
|
124
|
-
padding:
|
|
125
|
-
border-radius:
|
|
126
|
-
font-size:
|
|
191
|
+
padding: 9px 12px;
|
|
192
|
+
border-radius: 16px;
|
|
193
|
+
font-size: 13px;
|
|
127
194
|
line-height: 1.5;
|
|
128
195
|
word-wrap: break-word;
|
|
129
196
|
white-space: pre-wrap;
|
|
@@ -147,15 +214,15 @@
|
|
|
147
214
|
|
|
148
215
|
.home-chat-bubble-system {
|
|
149
216
|
align-self: center;
|
|
150
|
-
font-size:
|
|
217
|
+
font-size: 11px;
|
|
151
218
|
color: var(--text-muted, var(--text-dimmer));
|
|
152
219
|
background: transparent;
|
|
153
220
|
padding: 4px 10px;
|
|
154
221
|
font-style: italic;
|
|
222
|
+
text-align: center;
|
|
223
|
+
max-width: 100%;
|
|
155
224
|
}
|
|
156
225
|
|
|
157
|
-
/* Inline session reference: [project/sess_id — 2026-04-22]. Rendered as
|
|
158
|
-
a subtle chip when the home-chat renderer detects the pattern. */
|
|
159
226
|
.home-chat-ref {
|
|
160
227
|
display: inline-block;
|
|
161
228
|
background: rgba(124, 58, 237, 0.08);
|
|
@@ -163,7 +230,7 @@
|
|
|
163
230
|
padding: 1px 8px;
|
|
164
231
|
border-radius: 10px;
|
|
165
232
|
font-family: "Roboto Mono", "Courier New", monospace;
|
|
166
|
-
font-size:
|
|
233
|
+
font-size: 11px;
|
|
167
234
|
cursor: pointer;
|
|
168
235
|
margin: 0 2px;
|
|
169
236
|
border: 1px solid rgba(124, 58, 237, 0.18);
|
|
@@ -177,14 +244,14 @@
|
|
|
177
244
|
.home-chat-typing {
|
|
178
245
|
display: flex;
|
|
179
246
|
gap: 4px;
|
|
180
|
-
padding:
|
|
247
|
+
padding: 4px 16px 0;
|
|
181
248
|
align-items: center;
|
|
182
249
|
}
|
|
183
250
|
.home-chat-typing.hidden { display: none; }
|
|
184
251
|
|
|
185
252
|
.home-chat-typing-dot {
|
|
186
|
-
width:
|
|
187
|
-
height:
|
|
253
|
+
width: 5px;
|
|
254
|
+
height: 5px;
|
|
188
255
|
border-radius: 50%;
|
|
189
256
|
background: var(--text-dimmer);
|
|
190
257
|
animation: home-chat-bounce 1.2s infinite ease-in-out;
|
|
@@ -204,7 +271,7 @@
|
|
|
204
271
|
display: flex;
|
|
205
272
|
align-items: flex-end;
|
|
206
273
|
gap: 8px;
|
|
207
|
-
padding: 10px
|
|
274
|
+
padding: 10px 10px 12px;
|
|
208
275
|
border-top: 1px solid var(--border);
|
|
209
276
|
background: var(--bg);
|
|
210
277
|
}
|
|
@@ -212,15 +279,15 @@
|
|
|
212
279
|
.home-chat-input {
|
|
213
280
|
flex: 1;
|
|
214
281
|
border: 1px solid var(--border);
|
|
215
|
-
border-radius:
|
|
216
|
-
padding:
|
|
282
|
+
border-radius: 16px;
|
|
283
|
+
padding: 9px 12px;
|
|
217
284
|
background: var(--input-bg, var(--bg));
|
|
218
285
|
color: var(--text);
|
|
219
|
-
font-size:
|
|
286
|
+
font-size: 13px;
|
|
220
287
|
line-height: 1.5;
|
|
221
288
|
font-family: inherit;
|
|
222
289
|
resize: none;
|
|
223
|
-
max-height:
|
|
290
|
+
max-height: 120px;
|
|
224
291
|
outline: none;
|
|
225
292
|
transition: border-color 0.15s, box-shadow 0.15s;
|
|
226
293
|
}
|
|
@@ -231,8 +298,8 @@
|
|
|
231
298
|
|
|
232
299
|
.home-chat-send-btn {
|
|
233
300
|
flex-shrink: 0;
|
|
234
|
-
width:
|
|
235
|
-
height:
|
|
301
|
+
width: 34px;
|
|
302
|
+
height: 34px;
|
|
236
303
|
border-radius: 50%;
|
|
237
304
|
border: none;
|
|
238
305
|
background: var(--accent);
|
|
@@ -247,47 +314,57 @@
|
|
|
247
314
|
opacity: 0.4;
|
|
248
315
|
cursor: default;
|
|
249
316
|
}
|
|
250
|
-
.home-chat-send-btn:not(:disabled):hover {
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
.home-chat-send-btn:not(:disabled):active {
|
|
254
|
-
transform: scale(0.95);
|
|
255
|
-
}
|
|
317
|
+
.home-chat-send-btn:not(:disabled):hover { opacity: 0.9; }
|
|
318
|
+
.home-chat-send-btn:not(:disabled):active { transform: scale(0.95); }
|
|
256
319
|
.home-chat-send-btn .lucide {
|
|
257
|
-
width:
|
|
258
|
-
height:
|
|
320
|
+
width: 16px;
|
|
321
|
+
height: 16px;
|
|
259
322
|
}
|
|
260
323
|
|
|
261
|
-
/* ---
|
|
324
|
+
/* --- Mobile / narrow ---
|
|
325
|
+
On phones the popover fills the screen edge-to-edge with a small
|
|
326
|
+
inset, and the FAB shrinks slightly. */
|
|
262
327
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
328
|
+
@media (max-width: 768px) {
|
|
329
|
+
/* Lift the FAB above the mobile tab bar (56px + safe-bottom). */
|
|
330
|
+
.clay-fab {
|
|
331
|
+
right: 14px;
|
|
332
|
+
bottom: calc(56px + var(--safe-bottom, 0px) + 14px);
|
|
333
|
+
width: 50px;
|
|
334
|
+
height: 50px;
|
|
335
|
+
}
|
|
336
|
+
.clay-fab-icon {
|
|
337
|
+
width: 32px;
|
|
338
|
+
height: 32px;
|
|
339
|
+
}
|
|
271
340
|
}
|
|
272
341
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
.home-chat-pane {
|
|
342
|
+
@media (max-width: 600px) {
|
|
343
|
+
.clay-popover {
|
|
344
|
+
right: 8px;
|
|
345
|
+
bottom: calc(56px + var(--safe-bottom, 0px) + 8px);
|
|
346
|
+
left: 8px;
|
|
280
347
|
width: auto;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
height: 60vh;
|
|
287
|
-
padding: 16px 12px;
|
|
348
|
+
height: 70vh;
|
|
349
|
+
max-height: calc(100vh - 80px - var(--safe-bottom, 0px));
|
|
350
|
+
}
|
|
351
|
+
.clay-popover .home-chat-frame {
|
|
352
|
+
border-radius: 16px;
|
|
288
353
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* When the mobile tab bar is hidden by the keyboard, drop the FAB back
|
|
357
|
+
down so it doesn't float in dead space. */
|
|
358
|
+
@media (max-width: 768px) {
|
|
359
|
+
#mobile-tab-bar.keyboard-hidden ~ .clay-fab,
|
|
360
|
+
body:has(#mobile-tab-bar.keyboard-hidden) .clay-fab {
|
|
361
|
+
bottom: 14px;
|
|
292
362
|
}
|
|
293
363
|
}
|
|
364
|
+
|
|
365
|
+
/* Hide FAB while user is on auth/setup pages or any modal-heavy screens.
|
|
366
|
+
Add the class .clay-fab-suppressed on <body> when needed. */
|
|
367
|
+
body.clay-fab-suppressed .clay-fab,
|
|
368
|
+
body.clay-fab-suppressed .clay-popover {
|
|
369
|
+
display: none !important;
|
|
370
|
+
}
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
========================================================================== */
|
|
4
4
|
|
|
5
5
|
/* Hub covers #main-area (sidebar + chat), sits inside it with absolute.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
panel styling. */
|
|
6
|
+
Pure widget surface — Clay chat is reachable via the global FAB
|
|
7
|
+
instead of being embedded here. */
|
|
9
8
|
#home-hub {
|
|
10
9
|
position: absolute;
|
|
11
10
|
inset: 0;
|
|
12
11
|
display: flex;
|
|
13
|
-
flex-direction:
|
|
14
|
-
align-items:
|
|
15
|
-
overflow:
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
align-items: center;
|
|
14
|
+
overflow-y: auto;
|
|
16
15
|
background: var(--bg);
|
|
17
16
|
z-index: 200;
|
|
17
|
+
padding: 48px 24px 40px;
|
|
18
18
|
border-top-left-radius: 8px;
|
|
19
19
|
animation: hubFadeIn 0.35s ease;
|
|
20
20
|
}
|
package/lib/public/index.html
CHANGED
|
@@ -115,39 +115,6 @@
|
|
|
115
115
|
<!-- === Main Area (sidebar + resize-handle + main-column) === -->
|
|
116
116
|
<div id="main-area">
|
|
117
117
|
<div id="home-hub" class="hidden">
|
|
118
|
-
<!-- Clay chat panel: phablet-style, self-contained. Talks to the
|
|
119
|
-
user's Clay mate via dedicated WS messages (home_clay_*) and
|
|
120
|
-
does not interfere with the active project session. -->
|
|
121
|
-
<div id="home-chat-pane" class="home-chat-pane">
|
|
122
|
-
<div class="home-chat-frame">
|
|
123
|
-
<div class="home-chat-header">
|
|
124
|
-
<div class="home-chat-avatar-wrap">
|
|
125
|
-
<img class="home-chat-avatar" src="/icon-banded-76.png" alt="Clay">
|
|
126
|
-
</div>
|
|
127
|
-
<div class="home-chat-title-block">
|
|
128
|
-
<div class="home-chat-title">Clay</div>
|
|
129
|
-
<div class="home-chat-subtitle">Your workspace memory</div>
|
|
130
|
-
</div>
|
|
131
|
-
<button id="home-chat-new-btn" class="home-chat-icon-btn" title="Start a new conversation" aria-label="New conversation">
|
|
132
|
-
<i data-lucide="plus"></i>
|
|
133
|
-
</button>
|
|
134
|
-
</div>
|
|
135
|
-
<div id="home-chat-messages" class="home-chat-messages">
|
|
136
|
-
<!-- messages rendered by home-chat.js -->
|
|
137
|
-
</div>
|
|
138
|
-
<div class="home-chat-typing hidden" id="home-chat-typing">
|
|
139
|
-
<span class="home-chat-typing-dot"></span>
|
|
140
|
-
<span class="home-chat-typing-dot"></span>
|
|
141
|
-
<span class="home-chat-typing-dot"></span>
|
|
142
|
-
</div>
|
|
143
|
-
<div class="home-chat-input-row">
|
|
144
|
-
<textarea id="home-chat-input" class="home-chat-input" rows="1" placeholder="Ask Clay…" autocomplete="off"></textarea>
|
|
145
|
-
<button id="home-chat-send-btn" class="home-chat-send-btn" disabled aria-label="Send">
|
|
146
|
-
<i data-lucide="arrow-up"></i>
|
|
147
|
-
</button>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
118
|
<button id="home-hub-close" class="home-hub-close-btn hidden">
|
|
152
119
|
<i data-lucide="x"></i>
|
|
153
120
|
<span>ESC</span>
|
|
@@ -2326,5 +2293,49 @@
|
|
|
2326
2293
|
</div>
|
|
2327
2294
|
</div>
|
|
2328
2295
|
</div>
|
|
2296
|
+
|
|
2297
|
+
<!-- === Clay FAB + Popover ===
|
|
2298
|
+
Persistent floating-action button (bottom-right) that opens a
|
|
2299
|
+
phablet-style chat with Clay (the host agent). The popover overlays
|
|
2300
|
+
the page content and does NOT interfere with the active project
|
|
2301
|
+
session — talks via dedicated home_clay_* WS messages. -->
|
|
2302
|
+
<button id="clay-fab" class="clay-fab" type="button" aria-label="Open Clay" title="Ask Clay">
|
|
2303
|
+
<img class="clay-fab-icon" src="/icon-banded-76.png" alt="Clay">
|
|
2304
|
+
<span class="clay-fab-pulse"></span>
|
|
2305
|
+
</button>
|
|
2306
|
+
|
|
2307
|
+
<div id="clay-popover" class="clay-popover hidden" role="dialog" aria-modal="false" aria-labelledby="home-chat-title-text">
|
|
2308
|
+
<div class="home-chat-frame">
|
|
2309
|
+
<div class="home-chat-header">
|
|
2310
|
+
<div class="home-chat-avatar-wrap">
|
|
2311
|
+
<img class="home-chat-avatar" src="/icon-banded-76.png" alt="Clay">
|
|
2312
|
+
</div>
|
|
2313
|
+
<div class="home-chat-title-block">
|
|
2314
|
+
<div class="home-chat-title" id="home-chat-title-text">Clay</div>
|
|
2315
|
+
<div class="home-chat-subtitle">Your workspace memory</div>
|
|
2316
|
+
</div>
|
|
2317
|
+
<button id="home-chat-new-btn" class="home-chat-icon-btn" title="Start a new conversation" aria-label="New conversation">
|
|
2318
|
+
<i data-lucide="plus"></i>
|
|
2319
|
+
</button>
|
|
2320
|
+
<button id="home-chat-close-btn" class="home-chat-icon-btn" title="Close" aria-label="Close">
|
|
2321
|
+
<i data-lucide="x"></i>
|
|
2322
|
+
</button>
|
|
2323
|
+
</div>
|
|
2324
|
+
<div id="home-chat-messages" class="home-chat-messages">
|
|
2325
|
+
<!-- messages rendered by home-chat.js -->
|
|
2326
|
+
</div>
|
|
2327
|
+
<div class="home-chat-typing hidden" id="home-chat-typing">
|
|
2328
|
+
<span class="home-chat-typing-dot"></span>
|
|
2329
|
+
<span class="home-chat-typing-dot"></span>
|
|
2330
|
+
<span class="home-chat-typing-dot"></span>
|
|
2331
|
+
</div>
|
|
2332
|
+
<div class="home-chat-input-row">
|
|
2333
|
+
<textarea id="home-chat-input" class="home-chat-input" rows="1" placeholder="Ask Clay…" autocomplete="off"></textarea>
|
|
2334
|
+
<button id="home-chat-send-btn" class="home-chat-send-btn" disabled aria-label="Send">
|
|
2335
|
+
<i data-lucide="arrow-up"></i>
|
|
2336
|
+
</button>
|
|
2337
|
+
</div>
|
|
2338
|
+
</div>
|
|
2339
|
+
</div>
|
|
2329
2340
|
</body>
|
|
2330
2341
|
</html>
|
|
@@ -574,18 +574,11 @@ function renderHomeHubMates() {
|
|
|
574
574
|
}
|
|
575
575
|
|
|
576
576
|
export function showHomeHub() {
|
|
577
|
-
// Home hub
|
|
578
|
-
//
|
|
579
|
-
// surface with its own renderer and its own WS protocol — it does NOT
|
|
580
|
-
// hijack the user's main project session. Any active DM stays open
|
|
581
|
-
// underneath; we just exit it visually so the hub layer is clean.
|
|
577
|
+
// Home hub is a pure widget surface. Clay chat is reachable from
|
|
578
|
+
// anywhere via the persistent FAB (#clay-fab), not embedded here.
|
|
582
579
|
if (store.get('dmMode')) exitDmMode();
|
|
583
580
|
homeHubVisible = true;
|
|
584
581
|
homeHub.classList.remove("hidden");
|
|
585
|
-
// Mount/refresh the in-hub Clay chat panel.
|
|
586
|
-
try {
|
|
587
|
-
if (typeof window.__initHomeChat === "function") window.__initHomeChat();
|
|
588
|
-
} catch (e) {}
|
|
589
582
|
// Show close button only if there's a project to return to
|
|
590
583
|
if (hubCloseBtn) {
|
|
591
584
|
if (store.get('currentSlug')) hubCloseBtn.classList.remove("hidden");
|
|
@@ -1,46 +1,55 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Self-contained: own DOM, own renderer, own WS protocol.
|
|
3
|
-
// Talks to the user's Clay mate session via home_clay_* messages.
|
|
1
|
+
// Clay FAB + popover chat — phablet-style, persistent across the app.
|
|
2
|
+
// Self-contained: own DOM, own renderer, own WS protocol (home_clay_*).
|
|
4
3
|
// Does not interfere with the active project session.
|
|
5
4
|
|
|
6
5
|
import { escapeHtml } from './utils.js';
|
|
7
6
|
import { getWs } from './ws-ref.js';
|
|
8
7
|
import { renderMarkdown } from './markdown.js';
|
|
9
|
-
import { refreshIcons } from './icons.js';
|
|
10
8
|
import { switchProject } from './app-projects.js';
|
|
11
9
|
|
|
12
10
|
var initialized = false;
|
|
11
|
+
var openState = false;
|
|
12
|
+
var fabBtn = null;
|
|
13
|
+
var popoverEl = null;
|
|
13
14
|
var messagesEl = null;
|
|
14
15
|
var inputEl = null;
|
|
15
16
|
var sendBtn = null;
|
|
16
17
|
var typingEl = null;
|
|
17
18
|
var newBtnEl = null;
|
|
19
|
+
var closeBtnEl = null;
|
|
18
20
|
|
|
19
21
|
// Per-turn assembly state. Server may emit many delta events for a single
|
|
20
22
|
// assistant turn; we accumulate text and render incrementally into the
|
|
21
23
|
// last bubble.
|
|
22
24
|
var currentAssistantBubble = null;
|
|
23
25
|
var currentAssistantText = "";
|
|
24
|
-
var
|
|
26
|
+
var openedOnce = false; // gate the initial home_clay_open request
|
|
25
27
|
|
|
26
|
-
// Initialize on first showHomeHub. The init function is exposed on
|
|
27
|
-
// window.__initHomeChat so app-home-hub.js (which already imports too
|
|
28
|
-
// many things) can call it without adding another import edge.
|
|
29
28
|
export function initHomeChat() {
|
|
30
|
-
if (initialized)
|
|
31
|
-
// Re-mount idempotent: just ensure the WS subscription is open.
|
|
32
|
-
requestSession();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
29
|
+
if (initialized) return;
|
|
35
30
|
initialized = true;
|
|
36
31
|
|
|
32
|
+
fabBtn = document.getElementById("clay-fab");
|
|
33
|
+
popoverEl = document.getElementById("clay-popover");
|
|
37
34
|
messagesEl = document.getElementById("home-chat-messages");
|
|
38
35
|
inputEl = document.getElementById("home-chat-input");
|
|
39
36
|
sendBtn = document.getElementById("home-chat-send-btn");
|
|
40
37
|
typingEl = document.getElementById("home-chat-typing");
|
|
41
38
|
newBtnEl = document.getElementById("home-chat-new-btn");
|
|
39
|
+
closeBtnEl = document.getElementById("home-chat-close-btn");
|
|
40
|
+
|
|
41
|
+
if (!fabBtn || !popoverEl || !messagesEl || !inputEl || !sendBtn) return;
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// --- FAB toggle ---
|
|
44
|
+
fabBtn.addEventListener("click", toggleOpen);
|
|
45
|
+
if (closeBtnEl) closeBtnEl.addEventListener("click", closePopover);
|
|
46
|
+
|
|
47
|
+
// ESC closes the popover.
|
|
48
|
+
document.addEventListener("keydown", function (e) {
|
|
49
|
+
if (e.key === "Escape" && openState) {
|
|
50
|
+
closePopover();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
44
53
|
|
|
45
54
|
// --- Input handling ---
|
|
46
55
|
inputEl.addEventListener("input", function () {
|
|
@@ -62,19 +71,49 @@ export function initHomeChat() {
|
|
|
62
71
|
messagesEl.innerHTML = "";
|
|
63
72
|
currentAssistantBubble = null;
|
|
64
73
|
currentAssistantText = "";
|
|
65
|
-
lastSenderWasUser = false;
|
|
66
74
|
hideTyping();
|
|
67
75
|
addSystemBubble("New conversation started.");
|
|
68
76
|
});
|
|
69
77
|
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function openPopover() {
|
|
81
|
+
if (!popoverEl || openState) return;
|
|
82
|
+
openState = true;
|
|
83
|
+
popoverEl.classList.remove("hidden");
|
|
84
|
+
if (fabBtn) fabBtn.classList.add("open");
|
|
85
|
+
// Pull session history on first open. If WS isn't ready yet, leave
|
|
86
|
+
// openedOnce false so the next open retries.
|
|
87
|
+
if (!openedOnce) {
|
|
88
|
+
var ws = getWs();
|
|
89
|
+
if (ws && ws.readyState === 1) {
|
|
90
|
+
openedOnce = true;
|
|
91
|
+
requestSession();
|
|
92
|
+
} else {
|
|
93
|
+
addSystemBubble("Connecting…");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Focus the input so the user can start typing immediately.
|
|
97
|
+
setTimeout(function () { if (inputEl) inputEl.focus(); }, 60);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function closePopover() {
|
|
101
|
+
if (!openState) return;
|
|
102
|
+
openState = false;
|
|
103
|
+
if (popoverEl) popoverEl.classList.add("hidden");
|
|
104
|
+
if (fabBtn) {
|
|
105
|
+
fabBtn.classList.remove("open");
|
|
106
|
+
fabBtn.focus();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
70
109
|
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
function toggleOpen() {
|
|
111
|
+
if (openState) closePopover(); else openPopover();
|
|
73
112
|
}
|
|
74
113
|
|
|
75
114
|
function autoResize() {
|
|
76
115
|
inputEl.style.height = "auto";
|
|
77
|
-
inputEl.style.height = Math.min(
|
|
116
|
+
inputEl.style.height = Math.min(120, inputEl.scrollHeight) + "px";
|
|
78
117
|
}
|
|
79
118
|
|
|
80
119
|
function requestSession() {
|
|
@@ -102,14 +141,12 @@ function doSend() {
|
|
|
102
141
|
// --- Rendering ---
|
|
103
142
|
|
|
104
143
|
function addUserBubble(text) {
|
|
105
|
-
// Finalize any open assistant bubble before adding the next user turn.
|
|
106
144
|
finalizeAssistant();
|
|
107
145
|
var bubble = document.createElement("div");
|
|
108
146
|
bubble.className = "home-chat-bubble home-chat-bubble-user";
|
|
109
147
|
bubble.textContent = text;
|
|
110
148
|
messagesEl.appendChild(bubble);
|
|
111
149
|
scrollToBottom();
|
|
112
|
-
lastSenderWasUser = true;
|
|
113
150
|
}
|
|
114
151
|
|
|
115
152
|
function ensureAssistantBubble() {
|
|
@@ -119,21 +156,18 @@ function ensureAssistantBubble() {
|
|
|
119
156
|
messagesEl.appendChild(bubble);
|
|
120
157
|
currentAssistantBubble = bubble;
|
|
121
158
|
currentAssistantText = "";
|
|
122
|
-
lastSenderWasUser = false;
|
|
123
159
|
return bubble;
|
|
124
160
|
}
|
|
125
161
|
|
|
126
162
|
function appendAssistantText(text) {
|
|
127
163
|
var bubble = ensureAssistantBubble();
|
|
128
164
|
currentAssistantText += text;
|
|
129
|
-
// Render markdown + linkify session refs after sanitization.
|
|
130
165
|
bubble.innerHTML = linkifyRefs(renderMarkdown(currentAssistantText));
|
|
131
166
|
scrollToBottom();
|
|
132
167
|
}
|
|
133
168
|
|
|
134
169
|
function finalizeAssistant() {
|
|
135
170
|
if (currentAssistantBubble && !currentAssistantText) {
|
|
136
|
-
// Empty assistant turn (no text produced). Drop the empty bubble.
|
|
137
171
|
currentAssistantBubble.remove();
|
|
138
172
|
}
|
|
139
173
|
currentAssistantBubble = null;
|
|
@@ -148,11 +182,9 @@ function addSystemBubble(text) {
|
|
|
148
182
|
scrollToBottom();
|
|
149
183
|
}
|
|
150
184
|
|
|
151
|
-
// Convert [project-slug/sess_xxx — date] tokens in the rendered HTML
|
|
152
|
-
// into clickable chips. Server-side Clay is instructed to emit these.
|
|
153
185
|
function linkifyRefs(html) {
|
|
154
|
-
// Match [slug/sess_id - date]
|
|
155
|
-
//
|
|
186
|
+
// Match [slug/sess_id - date]. Conservative: slug is alphanumeric/-/_,
|
|
187
|
+
// sess id starts with sess_.
|
|
156
188
|
var re = /\[([a-zA-Z0-9_\-]+)\/(sess_[a-zA-Z0-9_\-]+)(?:\s+[—-]\s+([0-9]{4}-[0-9]{2}-[0-9]{2}))?\]/g;
|
|
157
189
|
return html.replace(re, function (_full, slug, sessId, date) {
|
|
158
190
|
var label = slug + "/" + sessId.substring(0, 14) + (date ? " · " + date : "");
|
|
@@ -162,18 +194,13 @@ function linkifyRefs(html) {
|
|
|
162
194
|
|
|
163
195
|
function scrollToBottom() {
|
|
164
196
|
if (!messagesEl) return;
|
|
165
|
-
// Always pin: home chat is short, no need for scroll-up detection.
|
|
166
197
|
requestAnimationFrame(function () {
|
|
167
198
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
168
199
|
});
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
function showTyping() {
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
function hideTyping() {
|
|
175
|
-
if (typingEl) typingEl.classList.add("hidden");
|
|
176
|
-
}
|
|
202
|
+
function showTyping() { if (typingEl) typingEl.classList.remove("hidden"); }
|
|
203
|
+
function hideTyping() { if (typingEl) typingEl.classList.add("hidden"); }
|
|
177
204
|
|
|
178
205
|
// --- Server message handlers (called from app-messages.js dispatcher) ---
|
|
179
206
|
|
|
@@ -193,7 +220,6 @@ export function handleHomeClayHistory(msg) {
|
|
|
193
220
|
if (e.role === "user") {
|
|
194
221
|
addUserBubble(e.text || "");
|
|
195
222
|
} else if (e.role === "assistant") {
|
|
196
|
-
// Replay finalized assistant text in one shot.
|
|
197
223
|
appendAssistantText(e.text || "");
|
|
198
224
|
finalizeAssistant();
|
|
199
225
|
}
|
|
@@ -223,17 +249,18 @@ document.addEventListener("click", function (e) {
|
|
|
223
249
|
if (!chip) return;
|
|
224
250
|
var slug = chip.dataset.slug;
|
|
225
251
|
if (!slug) return;
|
|
226
|
-
|
|
227
|
-
// project. Session selection inside that project is up to the existing
|
|
228
|
-
// session restore mechanism.
|
|
252
|
+
closePopover();
|
|
229
253
|
if (typeof switchProject === "function") {
|
|
230
|
-
var hubBtn = document.getElementById("home-hub-close");
|
|
231
|
-
if (hubBtn) hubBtn.click();
|
|
232
254
|
switchProject(slug);
|
|
233
255
|
}
|
|
234
256
|
});
|
|
235
257
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
258
|
+
// --- Initialize on DOM ready ---
|
|
259
|
+
|
|
260
|
+
if (typeof document !== "undefined") {
|
|
261
|
+
if (document.readyState === "loading") {
|
|
262
|
+
document.addEventListener("DOMContentLoaded", initHomeChat);
|
|
263
|
+
} else {
|
|
264
|
+
initHomeChat();
|
|
265
|
+
}
|
|
239
266
|
}
|
package/package.json
CHANGED