@extrachill/chat 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +154 -0
  3. package/css/chat.css +552 -0
  4. package/dist/Chat.d.ts +73 -0
  5. package/dist/Chat.d.ts.map +1 -0
  6. package/dist/Chat.js +50 -0
  7. package/dist/api.d.ts +68 -0
  8. package/dist/api.d.ts.map +1 -0
  9. package/dist/api.js +93 -0
  10. package/dist/components/AvailabilityGate.d.ts +19 -0
  11. package/dist/components/AvailabilityGate.d.ts.map +1 -0
  12. package/dist/components/AvailabilityGate.js +32 -0
  13. package/dist/components/ChatInput.d.ts +21 -0
  14. package/dist/components/ChatInput.d.ts.map +1 -0
  15. package/dist/components/ChatInput.js +52 -0
  16. package/dist/components/ChatMessage.d.ts +23 -0
  17. package/dist/components/ChatMessage.d.ts.map +1 -0
  18. package/dist/components/ChatMessage.js +34 -0
  19. package/dist/components/ChatMessages.d.ts +28 -0
  20. package/dist/components/ChatMessages.d.ts.map +1 -0
  21. package/dist/components/ChatMessages.js +121 -0
  22. package/dist/components/ErrorBoundary.d.ts +27 -0
  23. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  24. package/dist/components/ErrorBoundary.js +34 -0
  25. package/dist/components/SessionSwitcher.d.ts +25 -0
  26. package/dist/components/SessionSwitcher.d.ts.map +1 -0
  27. package/dist/components/SessionSwitcher.js +44 -0
  28. package/dist/components/ToolMessage.d.ts +34 -0
  29. package/dist/components/ToolMessage.d.ts.map +1 -0
  30. package/dist/components/ToolMessage.js +39 -0
  31. package/dist/components/TypingIndicator.d.ts +16 -0
  32. package/dist/components/TypingIndicator.d.ts.map +1 -0
  33. package/dist/components/TypingIndicator.js +14 -0
  34. package/dist/components/index.d.ts +9 -0
  35. package/dist/components/index.d.ts.map +1 -0
  36. package/dist/components/index.js +8 -0
  37. package/dist/hooks/index.d.ts +2 -0
  38. package/dist/hooks/index.d.ts.map +1 -0
  39. package/dist/hooks/index.js +1 -0
  40. package/dist/hooks/useChat.d.ts +102 -0
  41. package/dist/hooks/useChat.d.ts.map +1 -0
  42. package/dist/hooks/useChat.js +192 -0
  43. package/dist/index.d.ts +15 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +16 -0
  46. package/dist/normalizer.d.ts +24 -0
  47. package/dist/normalizer.d.ts.map +1 -0
  48. package/dist/normalizer.js +96 -0
  49. package/dist/types/adapter.d.ts +151 -0
  50. package/dist/types/adapter.d.ts.map +1 -0
  51. package/dist/types/adapter.js +11 -0
  52. package/dist/types/api.d.ts +137 -0
  53. package/dist/types/api.d.ts.map +1 -0
  54. package/dist/types/api.js +8 -0
  55. package/dist/types/index.d.ts +4 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/dist/types/index.js +1 -0
  58. package/dist/types/message.d.ts +62 -0
  59. package/dist/types/message.d.ts.map +1 -0
  60. package/dist/types/message.js +7 -0
  61. package/dist/types/session.d.ts +59 -0
  62. package/dist/types/session.d.ts.map +1 -0
  63. package/dist/types/session.js +7 -0
  64. package/package.json +61 -0
  65. package/src/Chat.tsx +157 -0
  66. package/src/api.ts +173 -0
  67. package/src/components/AvailabilityGate.tsx +85 -0
  68. package/src/components/ChatInput.tsx +114 -0
  69. package/src/components/ChatMessage.tsx +85 -0
  70. package/src/components/ChatMessages.tsx +193 -0
  71. package/src/components/ErrorBoundary.tsx +66 -0
  72. package/src/components/SessionSwitcher.tsx +129 -0
  73. package/src/components/ToolMessage.tsx +112 -0
  74. package/src/components/TypingIndicator.tsx +36 -0
  75. package/src/components/index.ts +8 -0
  76. package/src/hooks/index.ts +1 -0
  77. package/src/hooks/useChat.ts +310 -0
  78. package/src/index.ts +79 -0
  79. package/src/normalizer.ts +112 -0
  80. package/src/types/api.ts +146 -0
  81. package/src/types/index.ts +26 -0
  82. package/src/types/message.ts +66 -0
  83. package/src/types/session.ts +50 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ - **BREAKING:** Remove `ChatAdapter` interface and adapter pattern
6
+ - Package now speaks the standard chat REST API contract natively
7
+ - Add `api.ts` — built-in REST client (`sendMessage`, `continueResponse`, `listSessions`, `loadSession`, `deleteSession`)
8
+ - Add `normalizer.ts` — maps raw backend messages to `ChatMessage` format
9
+ - Add `types/api.ts` — typed REST request/response shapes
10
+ - `useChat` hook now takes `basePath`, `fetchFn`, and `agentId` instead of an adapter
11
+ - `Chat` component props updated: `basePath` + `fetchFn` + `agentId` replace `adapter`
12
+ - Add `showSessions` prop to `Chat` for optional session switcher
13
+ - `showTools` defaults to `true` (was `false`)
14
+ - Export API functions and normalizer for advanced use cases
15
+ - Remove `ChatCapabilities`, `SendMessageInput`, `SendMessageResult`, `ContinueResult`, `StreamChunk` types
16
+
17
+ ## 0.1.0
18
+
19
+ - Initial release
20
+ - Adapter contract (`ChatAdapter` interface) with capability flags
21
+ - Normalized message model (`ChatMessage`, `ChatSession`, `ChatAvailability`)
22
+ - Core components: `ChatMessages`, `ChatMessage`, `ChatInput`, `TypingIndicator`, `ToolMessage`, `SessionSwitcher`, `ErrorBoundary`
23
+ - `useChat` hook — core state orchestrator with adapter integration
24
+ - Base CSS styles
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # Extra Chill Chat
2
+
3
+ Generic frontend Gutenberg chat block for WordPress.
4
+
5
+ ## Vision
6
+
7
+ `extra-chill/chat` is a shared chat UI primitive for WordPress.
8
+
9
+ The goal is to provide a **standard, frontend-first, dumb chat block** that can power multiple chat experiences cleanly across different plugins and products.
10
+
11
+ This repo should focus on the **best chat experience possible** while leaving backend-specific concerns to adapters and consuming plugins.
12
+
13
+ ## Why This Exists
14
+
15
+ There are already multiple chat block implementations across different repos, including:
16
+
17
+ - `Extra-Chill/extrachill-chat`
18
+ - `Sarai-Chinwag/spawn`
19
+
20
+ Those implementations overlap heavily in UI and runtime behavior, but differ in backend details like:
21
+
22
+ - authentication
23
+ - session management
24
+ - billing / credits
25
+ - agent transport
26
+ - provisioning / availability states
27
+ - branding
28
+
29
+ This repo exists to separate the **shared chat experience** from the **product-specific business logic**.
30
+
31
+ ## Goals
32
+
33
+ - Build a reusable Gutenberg chat block
34
+ - Keep the block frontend-focused and presentation-first
35
+ - Support multiple backend implementations through adapters
36
+ - Standardize chat UX across Extra Chill projects
37
+ - Make it easy for WordPress plugins to become AI-native without rebuilding chat UI every time
38
+
39
+ ## Non-Goals
40
+
41
+ - Owning backend AI orchestration
42
+ - Owning product-specific business logic
43
+ - Owning billing, provisioning, or auth policy
44
+ - Becoming a monolithic app plugin
45
+
46
+ ## Proposed Architecture
47
+
48
+ ```text
49
+ WordPress Plugin
50
+ |
51
+ | server-rendered wrapper + config
52
+ v
53
+ Shared Chat Block UI
54
+ |
55
+ | adapter contract
56
+ v
57
+ Backend-specific implementation
58
+ ```
59
+
60
+ ### Core idea
61
+
62
+ The shared block should own:
63
+
64
+ - message list rendering
65
+ - composer UX
66
+ - loading / error / retry states
67
+ - scrolling behavior
68
+ - streaming display
69
+ - session list UI (when enabled)
70
+ - accessibility and keyboard interactions
71
+ - mobile and responsive behavior
72
+
73
+ The consuming plugin should own:
74
+
75
+ - REST endpoints
76
+ - authentication
77
+ - pricing / credit logic
78
+ - agent lifecycle
79
+ - account state
80
+ - server availability logic
81
+ - branding and product-specific rules
82
+
83
+ ## Adapter Model
84
+
85
+ The main abstraction in this repo should be a narrow adapter contract.
86
+
87
+ Example shape:
88
+
89
+ ```ts
90
+ interface ChatAdapter {
91
+ capabilities: {
92
+ sessions: boolean
93
+ history: boolean
94
+ streaming: boolean
95
+ tools: boolean
96
+ availabilityStates: boolean
97
+ }
98
+
99
+ loadInitialState?(): Promise<InitialState>
100
+ listSessions?(): Promise<Session[]>
101
+ loadMessages?(sessionId: string): Promise<Message[]>
102
+ createSession?(): Promise<Session>
103
+ sendMessage(input: SendMessageInput): Promise<SendMessageResult>
104
+ clearSession?(sessionId: string): Promise<void>
105
+ }
106
+ ```
107
+
108
+ This keeps the UI generic while allowing:
109
+
110
+ - `extrachill-chat` to use WordPress REST endpoints
111
+ - `spawn` to use its own gateway / session model
112
+ - future implementations to plug in without forking the UI
113
+
114
+ ## Initial Priorities
115
+
116
+ 1. Define the adapter API and normalized state model
117
+ 2. Build a minimal frontend-only chat block shell
118
+ 3. Support a basic message flow end-to-end
119
+ 4. Add optional session sidebar support
120
+ 5. Port one real consumer first (`extrachill-chat` is likely the easiest)
121
+ 6. Port `spawn` after the core boundaries are proven
122
+
123
+ ## Migration Strategy
124
+
125
+ ### Phase 1
126
+ - create core shared block
127
+ - define types and adapter interfaces
128
+ - implement minimal mount + send + render loop
129
+
130
+ ### Phase 2
131
+ - adapt `extrachill-chat` to use shared UI
132
+ - validate API shape and message model
133
+
134
+ ### Phase 3
135
+ - adapt `spawn`
136
+ - support richer session and availability states
137
+
138
+ ### Phase 4
139
+ - refine styling, streaming, markdown, and extensibility
140
+
141
+ ## Repo Status
142
+
143
+ This repo is currently in the **concept / planning** stage.
144
+
145
+ Implementation should start only after the core abstractions are clear enough that we do not just move duplication into a new place.
146
+
147
+ ## Roadmap
148
+
149
+ See GitHub issues for the initial roadmap.
150
+
151
+ ## Related Repositories
152
+
153
+ - https://github.com/Extra-Chill/extrachill-chat
154
+ - https://github.com/Sarai-Chinwag/spawn
package/css/chat.css ADDED
@@ -0,0 +1,552 @@
1
+ /**
2
+ * @extrachill/chat — Base styles
3
+ *
4
+ * These styles use CSS custom properties so consumers can theme
5
+ * the chat without overriding selectors. Override these variables
6
+ * at any level to customize appearance.
7
+ *
8
+ * All classes use the `ec-chat` prefix to avoid collisions.
9
+ */
10
+
11
+ /* ============================================
12
+ CSS Custom Properties (Theme API)
13
+ ============================================ */
14
+
15
+ .ec-chat {
16
+ --ec-chat-font-family: inherit;
17
+ --ec-chat-font-size: 14px;
18
+ --ec-chat-line-height: 1.5;
19
+
20
+ /* Layout */
21
+ --ec-chat-max-width: 100%;
22
+ --ec-chat-padding: 16px;
23
+ --ec-chat-gap: 8px;
24
+ --ec-chat-border-radius: 12px;
25
+
26
+ /* Colors — light defaults */
27
+ --ec-chat-bg: #ffffff;
28
+ --ec-chat-text: #1a1a1a;
29
+ --ec-chat-text-muted: #6b7280;
30
+ --ec-chat-border: #e5e7eb;
31
+
32
+ /* User bubble */
33
+ --ec-chat-user-bg: #2563eb;
34
+ --ec-chat-user-text: #ffffff;
35
+
36
+ /* Assistant bubble */
37
+ --ec-chat-assistant-bg: #f3f4f6;
38
+ --ec-chat-assistant-text: #1a1a1a;
39
+
40
+ /* Input */
41
+ --ec-chat-input-bg: #ffffff;
42
+ --ec-chat-input-border: #d1d5db;
43
+ --ec-chat-input-focus-border: #2563eb;
44
+
45
+ /* Send button */
46
+ --ec-chat-send-bg: #2563eb;
47
+ --ec-chat-send-text: #ffffff;
48
+ --ec-chat-send-disabled-opacity: 0.4;
49
+
50
+ /* Tool messages */
51
+ --ec-chat-tool-bg: #f9fafb;
52
+ --ec-chat-tool-border: #e5e7eb;
53
+ --ec-chat-tool-success: #10b981;
54
+ --ec-chat-tool-error: #ef4444;
55
+ --ec-chat-tool-pending: #6b7280;
56
+
57
+ /* Sessions */
58
+ --ec-chat-session-active-bg: #eff6ff;
59
+ --ec-chat-session-hover-bg: #f9fafb;
60
+
61
+ /* Typing indicator */
62
+ --ec-chat-typing-dot: #9ca3af;
63
+
64
+ /* Error */
65
+ --ec-chat-error-bg: #fef2f2;
66
+ --ec-chat-error-text: #991b1b;
67
+
68
+ /* Availability */
69
+ --ec-chat-availability-bg: #f9fafb;
70
+ --ec-chat-availability-text: #374151;
71
+
72
+ font-family: var(--ec-chat-font-family);
73
+ font-size: var(--ec-chat-font-size);
74
+ line-height: var(--ec-chat-line-height);
75
+ color: var(--ec-chat-text);
76
+ display: flex;
77
+ flex-direction: column;
78
+ max-width: var(--ec-chat-max-width);
79
+ height: 100%;
80
+ }
81
+
82
+ /* ============================================
83
+ Chat Messages Container
84
+ ============================================ */
85
+
86
+ .ec-chat-messages {
87
+ flex: 1;
88
+ overflow-y: auto;
89
+ padding: var(--ec-chat-padding);
90
+ display: flex;
91
+ flex-direction: column;
92
+ gap: var(--ec-chat-gap);
93
+ }
94
+
95
+ .ec-chat-messages__empty {
96
+ flex: 1;
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+ color: var(--ec-chat-text-muted);
101
+ text-align: center;
102
+ padding: 32px;
103
+ }
104
+
105
+ /* ============================================
106
+ Individual Message
107
+ ============================================ */
108
+
109
+ .ec-chat-message {
110
+ display: flex;
111
+ flex-direction: column;
112
+ max-width: 80%;
113
+ }
114
+
115
+ .ec-chat-message--user {
116
+ align-self: flex-end;
117
+ align-items: flex-end;
118
+ }
119
+
120
+ .ec-chat-message--assistant {
121
+ align-self: flex-start;
122
+ align-items: flex-start;
123
+ }
124
+
125
+ .ec-chat-message__bubble {
126
+ padding: 10px 14px;
127
+ border-radius: var(--ec-chat-border-radius);
128
+ word-wrap: break-word;
129
+ overflow-wrap: break-word;
130
+ }
131
+
132
+ .ec-chat-message--user .ec-chat-message__bubble {
133
+ background: var(--ec-chat-user-bg);
134
+ color: var(--ec-chat-user-text);
135
+ border-bottom-right-radius: 4px;
136
+ }
137
+
138
+ .ec-chat-message--assistant .ec-chat-message__bubble {
139
+ background: var(--ec-chat-assistant-bg);
140
+ color: var(--ec-chat-assistant-text);
141
+ border-bottom-left-radius: 4px;
142
+ }
143
+
144
+ .ec-chat-message__bubble p {
145
+ margin: 0;
146
+ }
147
+
148
+ .ec-chat-message__bubble p + p {
149
+ margin-top: 8px;
150
+ }
151
+
152
+ .ec-chat-message__timestamp {
153
+ font-size: 11px;
154
+ color: var(--ec-chat-text-muted);
155
+ margin-top: 2px;
156
+ opacity: 0;
157
+ transition: opacity 0.15s;
158
+ }
159
+
160
+ .ec-chat-message:hover .ec-chat-message__timestamp {
161
+ opacity: 1;
162
+ }
163
+
164
+ /* ============================================
165
+ Chat Input
166
+ ============================================ */
167
+
168
+ .ec-chat-input {
169
+ display: flex;
170
+ align-items: flex-end;
171
+ gap: 8px;
172
+ padding: var(--ec-chat-padding);
173
+ border-top: 1px solid var(--ec-chat-border);
174
+ background: var(--ec-chat-input-bg);
175
+ }
176
+
177
+ .ec-chat-input__textarea {
178
+ flex: 1;
179
+ resize: none;
180
+ border: 1px solid var(--ec-chat-input-border);
181
+ border-radius: var(--ec-chat-border-radius);
182
+ padding: 10px 14px;
183
+ font-family: inherit;
184
+ font-size: inherit;
185
+ line-height: inherit;
186
+ background: transparent;
187
+ color: inherit;
188
+ outline: none;
189
+ min-height: 40px;
190
+ max-height: 160px;
191
+ transition: border-color 0.15s;
192
+ }
193
+
194
+ .ec-chat-input__textarea:focus {
195
+ border-color: var(--ec-chat-input-focus-border);
196
+ }
197
+
198
+ .ec-chat-input__textarea::placeholder {
199
+ color: var(--ec-chat-text-muted);
200
+ }
201
+
202
+ .ec-chat-input__send {
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ width: 40px;
207
+ height: 40px;
208
+ border: none;
209
+ border-radius: 50%;
210
+ background: var(--ec-chat-send-bg);
211
+ color: var(--ec-chat-send-text);
212
+ cursor: pointer;
213
+ flex-shrink: 0;
214
+ transition: opacity 0.15s;
215
+ }
216
+
217
+ .ec-chat-input__send:disabled {
218
+ opacity: var(--ec-chat-send-disabled-opacity);
219
+ cursor: not-allowed;
220
+ }
221
+
222
+ .ec-chat-input__send:not(:disabled):hover {
223
+ opacity: 0.9;
224
+ }
225
+
226
+ .ec-chat-input__send-icon {
227
+ display: block;
228
+ }
229
+
230
+ /* ============================================
231
+ Typing Indicator
232
+ ============================================ */
233
+
234
+ .ec-chat-typing {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 8px;
238
+ padding: 4px var(--ec-chat-padding);
239
+ }
240
+
241
+ .ec-chat-typing__dots {
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 4px;
245
+ padding: 10px 14px;
246
+ background: var(--ec-chat-assistant-bg);
247
+ border-radius: var(--ec-chat-border-radius);
248
+ border-bottom-left-radius: 4px;
249
+ }
250
+
251
+ .ec-chat-typing__dot {
252
+ width: 6px;
253
+ height: 6px;
254
+ border-radius: 50%;
255
+ background: var(--ec-chat-typing-dot);
256
+ animation: ec-chat-typing-bounce 1.4s infinite ease-in-out both;
257
+ }
258
+
259
+ .ec-chat-typing__dot:nth-child(1) { animation-delay: -0.32s; }
260
+ .ec-chat-typing__dot:nth-child(2) { animation-delay: -0.16s; }
261
+ .ec-chat-typing__dot:nth-child(3) { animation-delay: 0s; }
262
+
263
+ @keyframes ec-chat-typing-bounce {
264
+ 0%, 80%, 100% {
265
+ transform: scale(0.6);
266
+ opacity: 0.4;
267
+ }
268
+ 40% {
269
+ transform: scale(1);
270
+ opacity: 1;
271
+ }
272
+ }
273
+
274
+ .ec-chat-typing__label {
275
+ font-size: 12px;
276
+ color: var(--ec-chat-text-muted);
277
+ }
278
+
279
+ /* ============================================
280
+ Tool Message
281
+ ============================================ */
282
+
283
+ .ec-chat-tool {
284
+ margin: 4px var(--ec-chat-padding);
285
+ border: 1px solid var(--ec-chat-tool-border);
286
+ border-radius: 8px;
287
+ background: var(--ec-chat-tool-bg);
288
+ overflow: hidden;
289
+ font-size: 13px;
290
+ }
291
+
292
+ .ec-chat-tool__header {
293
+ display: flex;
294
+ align-items: center;
295
+ gap: 8px;
296
+ width: 100%;
297
+ padding: 8px 12px;
298
+ border: none;
299
+ background: transparent;
300
+ cursor: pointer;
301
+ font-family: inherit;
302
+ font-size: inherit;
303
+ color: inherit;
304
+ text-align: left;
305
+ }
306
+
307
+ .ec-chat-tool__header:hover {
308
+ background: var(--ec-chat-session-hover-bg);
309
+ }
310
+
311
+ .ec-chat-tool__icon {
312
+ flex-shrink: 0;
313
+ font-size: 14px;
314
+ }
315
+
316
+ .ec-chat-tool--success .ec-chat-tool__icon { color: var(--ec-chat-tool-success); }
317
+ .ec-chat-tool--error .ec-chat-tool__icon { color: var(--ec-chat-tool-error); }
318
+ .ec-chat-tool--pending .ec-chat-tool__icon { color: var(--ec-chat-tool-pending); }
319
+
320
+ .ec-chat-tool__name {
321
+ flex: 1;
322
+ }
323
+
324
+ .ec-chat-tool__chevron {
325
+ flex-shrink: 0;
326
+ font-size: 10px;
327
+ color: var(--ec-chat-text-muted);
328
+ }
329
+
330
+ .ec-chat-tool__details {
331
+ padding: 0 12px 12px;
332
+ }
333
+
334
+ .ec-chat-tool__section {
335
+ margin-top: 8px;
336
+ }
337
+
338
+ .ec-chat-tool__section-label {
339
+ font-size: 11px;
340
+ font-weight: 600;
341
+ text-transform: uppercase;
342
+ letter-spacing: 0.05em;
343
+ color: var(--ec-chat-text-muted);
344
+ margin-bottom: 4px;
345
+ }
346
+
347
+ .ec-chat-tool__json {
348
+ margin: 0;
349
+ padding: 8px;
350
+ background: var(--ec-chat-bg);
351
+ border: 1px solid var(--ec-chat-tool-border);
352
+ border-radius: 4px;
353
+ font-family: monospace;
354
+ font-size: 12px;
355
+ white-space: pre-wrap;
356
+ word-break: break-all;
357
+ overflow-x: auto;
358
+ max-height: 200px;
359
+ overflow-y: auto;
360
+ }
361
+
362
+ /* ============================================
363
+ Session Switcher
364
+ ============================================ */
365
+
366
+ .ec-chat-sessions {
367
+ border-bottom: 1px solid var(--ec-chat-border);
368
+ }
369
+
370
+ .ec-chat-sessions__header {
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: space-between;
374
+ padding: 8px var(--ec-chat-padding);
375
+ }
376
+
377
+ .ec-chat-sessions__title {
378
+ font-size: 12px;
379
+ font-weight: 600;
380
+ text-transform: uppercase;
381
+ letter-spacing: 0.05em;
382
+ color: var(--ec-chat-text-muted);
383
+ }
384
+
385
+ .ec-chat-sessions__new {
386
+ display: flex;
387
+ align-items: center;
388
+ justify-content: center;
389
+ width: 24px;
390
+ height: 24px;
391
+ border: 1px solid var(--ec-chat-border);
392
+ border-radius: 4px;
393
+ background: transparent;
394
+ color: var(--ec-chat-text-muted);
395
+ cursor: pointer;
396
+ font-size: 16px;
397
+ line-height: 1;
398
+ }
399
+
400
+ .ec-chat-sessions__new:hover {
401
+ background: var(--ec-chat-session-hover-bg);
402
+ color: var(--ec-chat-text);
403
+ }
404
+
405
+ .ec-chat-sessions__loading {
406
+ padding: 8px var(--ec-chat-padding);
407
+ color: var(--ec-chat-text-muted);
408
+ font-size: 13px;
409
+ }
410
+
411
+ .ec-chat-sessions__list {
412
+ list-style: none;
413
+ margin: 0;
414
+ padding: 0;
415
+ max-height: 200px;
416
+ overflow-y: auto;
417
+ }
418
+
419
+ .ec-chat-sessions__item {
420
+ display: flex;
421
+ align-items: center;
422
+ }
423
+
424
+ .ec-chat-sessions__item--active {
425
+ background: var(--ec-chat-session-active-bg);
426
+ }
427
+
428
+ .ec-chat-sessions__item-button {
429
+ flex: 1;
430
+ display: flex;
431
+ flex-direction: column;
432
+ gap: 2px;
433
+ padding: 8px var(--ec-chat-padding);
434
+ border: none;
435
+ background: transparent;
436
+ cursor: pointer;
437
+ text-align: left;
438
+ font-family: inherit;
439
+ color: inherit;
440
+ min-width: 0;
441
+ }
442
+
443
+ .ec-chat-sessions__item-button:hover {
444
+ background: var(--ec-chat-session-hover-bg);
445
+ }
446
+
447
+ .ec-chat-sessions__item-title {
448
+ font-size: 13px;
449
+ white-space: nowrap;
450
+ overflow: hidden;
451
+ text-overflow: ellipsis;
452
+ }
453
+
454
+ .ec-chat-sessions__item-date {
455
+ font-size: 11px;
456
+ color: var(--ec-chat-text-muted);
457
+ }
458
+
459
+ .ec-chat-sessions__item-delete {
460
+ padding: 4px 8px;
461
+ border: none;
462
+ background: transparent;
463
+ color: var(--ec-chat-text-muted);
464
+ cursor: pointer;
465
+ font-size: 16px;
466
+ line-height: 1;
467
+ opacity: 0;
468
+ transition: opacity 0.15s;
469
+ }
470
+
471
+ .ec-chat-sessions__item:hover .ec-chat-sessions__item-delete {
472
+ opacity: 1;
473
+ }
474
+
475
+ .ec-chat-sessions__item-delete:hover {
476
+ color: var(--ec-chat-tool-error);
477
+ }
478
+
479
+ /* ============================================
480
+ Error Boundary
481
+ ============================================ */
482
+
483
+ .ec-chat-error {
484
+ display: flex;
485
+ flex-direction: column;
486
+ align-items: center;
487
+ justify-content: center;
488
+ gap: 12px;
489
+ padding: 32px;
490
+ text-align: center;
491
+ }
492
+
493
+ .ec-chat-error__message {
494
+ color: var(--ec-chat-error-text);
495
+ margin: 0;
496
+ }
497
+
498
+ .ec-chat-error__retry {
499
+ padding: 8px 16px;
500
+ border: 1px solid var(--ec-chat-border);
501
+ border-radius: 6px;
502
+ background: transparent;
503
+ color: var(--ec-chat-text);
504
+ cursor: pointer;
505
+ font-family: inherit;
506
+ font-size: inherit;
507
+ }
508
+
509
+ .ec-chat-error__retry:hover {
510
+ background: var(--ec-chat-session-hover-bg);
511
+ }
512
+
513
+ /* ============================================
514
+ Availability Gate
515
+ ============================================ */
516
+
517
+ .ec-chat-availability {
518
+ display: flex;
519
+ flex-direction: column;
520
+ align-items: center;
521
+ justify-content: center;
522
+ gap: 12px;
523
+ padding: 32px;
524
+ text-align: center;
525
+ background: var(--ec-chat-availability-bg);
526
+ color: var(--ec-chat-availability-text);
527
+ border-radius: var(--ec-chat-border-radius);
528
+ margin: var(--ec-chat-padding);
529
+ }
530
+
531
+ .ec-chat-availability p {
532
+ margin: 0;
533
+ }
534
+
535
+ .ec-chat-availability__action {
536
+ display: inline-block;
537
+ padding: 8px 16px;
538
+ border: 1px solid var(--ec-chat-border);
539
+ border-radius: 6px;
540
+ color: var(--ec-chat-send-bg);
541
+ text-decoration: none;
542
+ font-weight: 500;
543
+ }
544
+
545
+ .ec-chat-availability__action:hover {
546
+ background: var(--ec-chat-session-hover-bg);
547
+ }
548
+
549
+ .ec-chat-availability--error {
550
+ background: var(--ec-chat-error-bg);
551
+ color: var(--ec-chat-error-text);
552
+ }