@kilnai/widget 0.1.5

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 (2) hide show
  1. package/dist/widget.js +398 -0
  2. package/package.json +25 -0
package/dist/widget.js ADDED
@@ -0,0 +1,398 @@
1
+ (()=>{var{defineProperty:b,getOwnPropertyNames:m,getOwnPropertyDescriptor:v}=Object,y=Object.prototype.hasOwnProperty;function w(e){return this[e]}var S=(e)=>{var n=(x??=new WeakMap).get(e),t;if(n)return n;if(n=b({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function"){for(var i of m(e))if(!y.call(n,i))b(n,i,{get:w.bind(e,i),enumerable:!(t=v(e,i))||t.enumerable})}return x.set(e,n),n},x;var W=(e)=>e;function C(e,n){this[e]=W.bind(null,n)}var $=(e,n)=>{for(var t in n)b(e,t,{get:n[t],enumerable:!0,configurable:!0,set:C.bind(n,t)})};var F={};$(F,{KilnWidget:()=>k});class h{ws=null;reconnectTimer=null;reconnectDelay=1000;maxReconnectDelay=30000;intentionalClose=!1;messageHandler=null;statusHandler=null;url;userId;constructor(e,n,t){let i=e.startsWith("https")?"wss":"ws",s=e.replace(/^https?:\/\//,"").replace(/^wss?:\/\//,""),o=`kiln_widget_${t}`;this.userId=sessionStorage.getItem(o)??crypto.randomUUID(),sessionStorage.setItem(o,this.userId),this.url=`${i}://${s}/apps/${n}/ws?widgetId=${t}&userId=${encodeURIComponent(this.userId)}`}connect(){this.intentionalClose=!1,this.setStatus("connecting");let e=new WebSocket(this.url);this.ws=e,e.onopen=()=>{this.reconnectDelay=1000,this.setStatus("connected")},e.onmessage=(n)=>{try{let t=JSON.parse(n.data);this.messageHandler?.(t)}catch{}},e.onerror=()=>this.setStatus("error"),e.onclose=()=>{if(this.ws=null,!this.intentionalClose)this.setStatus("disconnected"),this.scheduleReconnect()}}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;let n={type:"message",content:e};this.ws.send(JSON.stringify(n))}onMessage(e){this.messageHandler=e}onStatusChange(e){this.statusHandler=e}disconnect(){if(this.intentionalClose=!0,this.reconnectTimer)clearTimeout(this.reconnectTimer),this.reconnectTimer=null;this.ws?.close(),this.ws=null,this.setStatus("disconnected")}get connected(){return this.ws?.readyState===WebSocket.OPEN}setStatus(e){this.statusHandler?.(e)}scheduleReconnect(){this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},this.reconnectDelay),this.reconnectDelay=Math.min(this.reconnectDelay*2,this.maxReconnectDelay)}}function g(e){let n=e==="dark";return`
2
+ :host {
3
+ --kiln-bg: ${n?"#1a1a2e":"#ffffff"};
4
+ --kiln-bg-secondary: ${n?"#2a2a3e":"#f0f0f0"};
5
+ --kiln-text: ${n?"#e8e8f0":"#1a1a1a"};
6
+ --kiln-text-secondary: ${n?"#a0a0b8":"#6b7280"};
7
+ --kiln-border: ${n?"#3a3a5c":"#e5e7eb"};
8
+ --kiln-accent: #1a1a2e;
9
+ --kiln-accent-text: #ffffff;
10
+ --kiln-user-bubble: #1a1a2e;
11
+ --kiln-user-text: #ffffff;
12
+ --kiln-assistant-bubble: ${n?"#2a2a3e":"#f0f0f0"};
13
+ --kiln-assistant-text: ${n?"#e8e8f0":"#1a1a1a"};
14
+ --kiln-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
15
+ --kiln-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
16
+ --kiln-radius: 16px;
17
+ --kiln-status-connected: #22c55e;
18
+ --kiln-status-connecting: #f59e0b;
19
+ --kiln-status-disconnected: #6b7280;
20
+ --kiln-status-error: #ef4444;
21
+ }
22
+
23
+ ${e==="auto"?`
24
+ @media (prefers-color-scheme: dark) {
25
+ :host {
26
+ --kiln-bg: #1a1a2e;
27
+ --kiln-bg-secondary: #2a2a3e;
28
+ --kiln-text: #e8e8f0;
29
+ --kiln-text-secondary: #a0a0b8;
30
+ --kiln-border: #3a3a5c;
31
+ --kiln-assistant-bubble: #2a2a3e;
32
+ --kiln-assistant-text: #e8e8f0;
33
+ }
34
+ }`:""}
35
+
36
+ *, *::before, *::after {
37
+ box-sizing: border-box;
38
+ margin: 0;
39
+ padding: 0;
40
+ }
41
+
42
+ /* Launcher button */
43
+ #kiln-launcher {
44
+ position: fixed;
45
+ bottom: 24px;
46
+ width: 56px;
47
+ height: 56px;
48
+ border-radius: 50%;
49
+ background: var(--kiln-accent);
50
+ color: var(--kiln-accent-text);
51
+ border: none;
52
+ cursor: pointer;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ box-shadow: var(--kiln-shadow);
57
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
58
+ z-index: 999998;
59
+ font-family: var(--kiln-font);
60
+ }
61
+
62
+ #kiln-launcher.position-bottom-right {
63
+ right: 24px;
64
+ }
65
+
66
+ #kiln-launcher.position-bottom-left {
67
+ left: 24px;
68
+ }
69
+
70
+ #kiln-launcher:hover {
71
+ transform: scale(1.08);
72
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
73
+ }
74
+
75
+ #kiln-launcher:active {
76
+ transform: scale(0.96);
77
+ }
78
+
79
+ #kiln-launcher svg {
80
+ width: 24px;
81
+ height: 24px;
82
+ flex-shrink: 0;
83
+ }
84
+
85
+ /* Chat panel */
86
+ #kiln-panel {
87
+ position: fixed;
88
+ bottom: 92px;
89
+ width: 380px;
90
+ height: min(520px, calc(100vh - 100px));
91
+ border-radius: var(--kiln-radius);
92
+ background: var(--kiln-bg);
93
+ box-shadow: var(--kiln-shadow);
94
+ display: flex;
95
+ flex-direction: column;
96
+ z-index: 999997;
97
+ font-family: var(--kiln-font);
98
+ border: 1px solid var(--kiln-border);
99
+ overflow: hidden;
100
+ transform-origin: bottom center;
101
+ animation: kiln-slide-up 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
102
+ }
103
+
104
+ #kiln-panel.position-bottom-right {
105
+ right: 24px;
106
+ }
107
+
108
+ #kiln-panel.position-bottom-left {
109
+ left: 24px;
110
+ }
111
+
112
+ #kiln-panel.hidden {
113
+ display: none;
114
+ }
115
+
116
+ @keyframes kiln-slide-up {
117
+ from {
118
+ opacity: 0;
119
+ transform: translateY(16px) scale(0.96);
120
+ }
121
+ to {
122
+ opacity: 1;
123
+ transform: translateY(0) scale(1);
124
+ }
125
+ }
126
+
127
+ @media (max-width: 639px) {
128
+ #kiln-panel {
129
+ width: 100vw;
130
+ height: 100vh;
131
+ bottom: 0;
132
+ right: 0 !important;
133
+ left: 0 !important;
134
+ border-radius: 0;
135
+ animation: kiln-slide-up-mobile 0.22s ease;
136
+ }
137
+
138
+ @keyframes kiln-slide-up-mobile {
139
+ from {
140
+ opacity: 0;
141
+ transform: translateY(24px);
142
+ }
143
+ to {
144
+ opacity: 1;
145
+ transform: translateY(0);
146
+ }
147
+ }
148
+ }
149
+
150
+ /* Header */
151
+ #kiln-header {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 10px;
155
+ padding: 14px 16px;
156
+ border-bottom: 1px solid var(--kiln-border);
157
+ flex-shrink: 0;
158
+ }
159
+
160
+ #kiln-status-dot {
161
+ width: 8px;
162
+ height: 8px;
163
+ border-radius: 50%;
164
+ flex-shrink: 0;
165
+ background: var(--kiln-status-disconnected);
166
+ transition: background 0.3s ease;
167
+ }
168
+
169
+ #kiln-status-dot.connected { background: var(--kiln-status-connected); }
170
+ #kiln-status-dot.connecting { background: var(--kiln-status-connecting); }
171
+ #kiln-status-dot.disconnected { background: var(--kiln-status-disconnected); }
172
+ #kiln-status-dot.error { background: var(--kiln-status-error); }
173
+
174
+ #kiln-title {
175
+ flex: 1;
176
+ font-size: 15px;
177
+ font-weight: 600;
178
+ color: var(--kiln-text);
179
+ }
180
+
181
+ #kiln-close {
182
+ background: none;
183
+ border: none;
184
+ cursor: pointer;
185
+ color: var(--kiln-text-secondary);
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: center;
189
+ width: 28px;
190
+ height: 28px;
191
+ border-radius: 6px;
192
+ transition: background 0.15s ease, color 0.15s ease;
193
+ }
194
+
195
+ #kiln-close:hover {
196
+ background: var(--kiln-bg-secondary);
197
+ color: var(--kiln-text);
198
+ }
199
+
200
+ #kiln-close svg {
201
+ width: 16px;
202
+ height: 16px;
203
+ }
204
+
205
+ /* Messages area */
206
+ #kiln-messages {
207
+ flex: 1;
208
+ overflow-y: auto;
209
+ padding: 16px;
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: 12px;
213
+ scroll-behavior: smooth;
214
+ }
215
+
216
+ #kiln-messages::-webkit-scrollbar {
217
+ width: 4px;
218
+ }
219
+
220
+ #kiln-messages::-webkit-scrollbar-track {
221
+ background: transparent;
222
+ }
223
+
224
+ #kiln-messages::-webkit-scrollbar-thumb {
225
+ background: var(--kiln-border);
226
+ border-radius: 2px;
227
+ }
228
+
229
+ /* Message bubbles */
230
+ .kiln-msg {
231
+ display: flex;
232
+ flex-direction: column;
233
+ max-width: 80%;
234
+ animation: kiln-msg-in 0.18s ease;
235
+ }
236
+
237
+ @keyframes kiln-msg-in {
238
+ from {
239
+ opacity: 0;
240
+ transform: translateY(6px);
241
+ }
242
+ to {
243
+ opacity: 1;
244
+ transform: translateY(0);
245
+ }
246
+ }
247
+
248
+ .kiln-msg.user {
249
+ align-self: flex-end;
250
+ align-items: flex-end;
251
+ }
252
+
253
+ .kiln-msg.assistant {
254
+ align-self: flex-start;
255
+ align-items: flex-start;
256
+ }
257
+
258
+ .kiln-bubble {
259
+ padding: 10px 14px;
260
+ border-radius: 14px;
261
+ font-size: 14px;
262
+ line-height: 1.5;
263
+ word-break: break-word;
264
+ }
265
+
266
+ .kiln-msg.user .kiln-bubble {
267
+ background: var(--kiln-user-bubble);
268
+ color: var(--kiln-user-text);
269
+ border-bottom-right-radius: 4px;
270
+ }
271
+
272
+ .kiln-msg.assistant .kiln-bubble {
273
+ background: var(--kiln-assistant-bubble);
274
+ color: var(--kiln-assistant-text);
275
+ border-bottom-left-radius: 4px;
276
+ }
277
+
278
+ .kiln-bubble code {
279
+ font-family: "Courier New", Courier, monospace;
280
+ font-size: 13px;
281
+ background: rgba(0, 0, 0, 0.08);
282
+ padding: 1px 5px;
283
+ border-radius: 4px;
284
+ }
285
+
286
+ .kiln-msg.user .kiln-bubble code {
287
+ background: rgba(255, 255, 255, 0.15);
288
+ }
289
+
290
+ /* Error message */
291
+ .kiln-msg.error .kiln-bubble {
292
+ background: #fee2e2;
293
+ color: #dc2626;
294
+ border: 1px solid #fca5a5;
295
+ }
296
+
297
+ /* Typing indicator */
298
+ #kiln-typing {
299
+ display: flex;
300
+ align-self: flex-start;
301
+ padding: 10px 14px;
302
+ background: var(--kiln-assistant-bubble);
303
+ border-radius: 14px;
304
+ border-bottom-left-radius: 4px;
305
+ gap: 4px;
306
+ animation: kiln-msg-in 0.18s ease;
307
+ }
308
+
309
+ #kiln-typing.hidden {
310
+ display: none;
311
+ }
312
+
313
+ .kiln-typing-dot {
314
+ width: 7px;
315
+ height: 7px;
316
+ border-radius: 50%;
317
+ background: var(--kiln-text-secondary);
318
+ animation: kiln-bounce 1.2s infinite;
319
+ }
320
+
321
+ .kiln-typing-dot:nth-child(2) { animation-delay: 0.2s; }
322
+ .kiln-typing-dot:nth-child(3) { animation-delay: 0.4s; }
323
+
324
+ @keyframes kiln-bounce {
325
+ 0%, 60%, 100% { transform: translateY(0); }
326
+ 30% { transform: translateY(-5px); }
327
+ }
328
+
329
+ /* Input area */
330
+ #kiln-input-area {
331
+ display: flex;
332
+ align-items: flex-end;
333
+ gap: 8px;
334
+ padding: 12px 16px;
335
+ border-top: 1px solid var(--kiln-border);
336
+ flex-shrink: 0;
337
+ }
338
+
339
+ #kiln-input {
340
+ flex: 1;
341
+ padding: 9px 12px;
342
+ border: 1px solid var(--kiln-border);
343
+ border-radius: 10px;
344
+ background: var(--kiln-bg);
345
+ color: var(--kiln-text);
346
+ font-family: var(--kiln-font);
347
+ font-size: 14px;
348
+ line-height: 1.5;
349
+ resize: none;
350
+ outline: none;
351
+ max-height: 120px;
352
+ min-height: 40px;
353
+ transition: border-color 0.15s ease;
354
+ overflow-y: auto;
355
+ }
356
+
357
+ #kiln-input::placeholder {
358
+ color: var(--kiln-text-secondary);
359
+ }
360
+
361
+ #kiln-input:focus {
362
+ border-color: var(--kiln-accent);
363
+ }
364
+
365
+ #kiln-send {
366
+ background: var(--kiln-accent);
367
+ color: var(--kiln-accent-text);
368
+ border: none;
369
+ border-radius: 10px;
370
+ width: 38px;
371
+ height: 38px;
372
+ flex-shrink: 0;
373
+ cursor: pointer;
374
+ display: flex;
375
+ align-items: center;
376
+ justify-content: center;
377
+ transition: opacity 0.15s ease, transform 0.1s ease;
378
+ }
379
+
380
+ #kiln-send:hover {
381
+ opacity: 0.88;
382
+ }
383
+
384
+ #kiln-send:active {
385
+ transform: scale(0.94);
386
+ }
387
+
388
+ #kiln-send:disabled {
389
+ opacity: 0.4;
390
+ cursor: not-allowed;
391
+ }
392
+
393
+ #kiln-send svg {
394
+ width: 18px;
395
+ height: 18px;
396
+ }
397
+ `}var T='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',z='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',I='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>';function Y(e){let n=document.createDocumentFragment(),t=/(\*\*[^*]+\*\*|`[^`]+`|\n)/g,i=0,s;while((s=t.exec(e))!==null){if(s.index>i)n.appendChild(document.createTextNode(e.slice(i,s.index)));let o=s[0];if(o===`
398
+ `)n.appendChild(document.createElement("br"));else if(o.startsWith("**")){let r=document.createElement("strong");r.textContent=o.slice(2,-2),n.appendChild(r)}else if(o.startsWith("`")){let r=document.createElement("code");r.textContent=o.slice(1,-1),n.appendChild(r)}i=s.index+o.length}if(i<e.length)n.appendChild(document.createTextNode(e.slice(i)));return n}class k{config;client;container;shadow;messages=[];isOpen=!1;isLoading=!1;idCounter=0;panelEl;messagesEl;typingEl;inputEl;sendEl;statusDotEl;launcherEl;constructor(e){this.config=e,this.client=new h(e.gatewayUrl,e.appName,e.widgetId),this.container=document.createElement("div"),this.container.id="kiln-widget-root",this.shadow=this.container.attachShadow({mode:"closed"}),document.body.appendChild(this.container);let n=document.createElement("style");if(n.textContent=g(this.resolveTheme()),this.shadow.appendChild(n),this.render(),this.client.onMessage((t)=>this.handleFrame(t)),this.client.onStatusChange((t)=>this.updateStatus(t)),this.client.connect(),e.greeting)this.addMessage({id:String(++this.idCounter),role:"assistant",content:e.greeting,timestamp:Date.now()})}resolveTheme(){let e=this.config.theme??"auto";if(e==="auto")return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";return e}render(){let e=this.config.position??"bottom-right",n=document.createElement("button");n.id="kiln-launcher",n.className=`position-${e}`,n.setAttribute("aria-label","Open chat"),n.innerHTML=T,n.addEventListener("click",()=>this.toggle()),this.launcherEl=n;let t=document.createElement("div");t.id="kiln-panel",t.className=`position-${e} hidden`,t.setAttribute("role","dialog"),t.setAttribute("aria-label","Chat");let i=document.createElement("div");i.id="kiln-header";let s=document.createElement("span");s.id="kiln-status-dot",s.className="disconnected",this.statusDotEl=s;let o=document.createElement("span");o.id="kiln-title",o.textContent=this.config.appName;let r=document.createElement("button");r.id="kiln-close",r.setAttribute("aria-label","Close chat"),r.innerHTML=z,r.addEventListener("click",()=>this.close()),i.appendChild(s),i.appendChild(o),i.appendChild(r);let p=document.createElement("div");p.id="kiln-messages",this.messagesEl=p;let c=document.createElement("div");c.id="kiln-typing",c.className="hidden";for(let d=0;d<3;d++){let f=document.createElement("span");f.className="kiln-typing-dot",c.appendChild(f)}this.typingEl=c,p.appendChild(c);let u=document.createElement("div");u.id="kiln-input-area";let a=document.createElement("textarea");a.id="kiln-input",a.rows=1,a.placeholder=this.config.placeholder??"Type a message...",a.setAttribute("aria-label","Message input"),a.addEventListener("keydown",(d)=>{if(d.key==="Enter"&&!d.shiftKey)d.preventDefault(),this.sendMessage()}),a.addEventListener("input",()=>this.autoResizeTextarea()),this.inputEl=a;let l=document.createElement("button");l.id="kiln-send",l.setAttribute("aria-label","Send message"),l.innerHTML=I,l.addEventListener("click",()=>this.sendMessage()),this.sendEl=l,u.appendChild(a),u.appendChild(l),t.appendChild(i),t.appendChild(p),t.appendChild(u),this.panelEl=t,this.shadow.appendChild(n),this.shadow.appendChild(t)}autoResizeTextarea(){this.inputEl.style.height="auto",this.inputEl.style.height=`${Math.min(this.inputEl.scrollHeight,120)}px`}open(){this.isOpen=!0,this.panelEl.classList.remove("hidden"),this.launcherEl.setAttribute("aria-expanded","true"),this.inputEl.focus(),this.scrollToBottom()}close(){this.isOpen=!1,this.panelEl.classList.add("hidden"),this.launcherEl.setAttribute("aria-expanded","false")}toggle(){if(this.isOpen)this.close();else this.open()}sendMessage(){let e=this.inputEl.value.trim();if(!e||this.isLoading)return;this.addMessage({id:String(++this.idCounter),role:"user",content:e,timestamp:Date.now()}),this.inputEl.value="",this.inputEl.style.height="auto",this.setLoading(!0),this.client.send(e)}addMessage(e){this.messages.push(e),this.renderMessage(e),this.scrollToBottom()}renderMessage(e){let n=document.createElement("div");n.className=`kiln-msg ${e.role}`,n.dataset.msgId=e.id;let t=document.createElement("div");if(t.className="kiln-bubble",e.role==="assistant")t.appendChild(Y(e.content));else t.textContent=e.content;n.appendChild(t),this.messagesEl.insertBefore(n,this.typingEl)}renderErrorMessage(e){let n=document.createElement("div");n.className="kiln-msg error";let t=document.createElement("div");t.className="kiln-bubble",t.textContent=e,n.appendChild(t),this.messagesEl.insertBefore(n,this.typingEl),this.scrollToBottom()}setLoading(e){if(this.isLoading=e,this.sendEl.disabled=e,this.inputEl.disabled=e,e)this.typingEl.classList.remove("hidden"),this.scrollToBottom();else this.typingEl.classList.add("hidden")}scrollToBottom(){requestAnimationFrame(()=>{this.messagesEl.scrollTop=this.messagesEl.scrollHeight})}handleFrame(e){if(e.type==="done")this.setLoading(!1),this.addMessage({id:String(++this.idCounter),role:"assistant",content:e.content,timestamp:Date.now()});else if(e.type==="error")this.setLoading(!1),this.renderErrorMessage(e.message)}updateStatus(e){this.statusDotEl.className=e}destroy(){this.client.disconnect(),this.container.remove()}}(function(){let e=document.currentScript;if(!e)return;let n=e.dataset.gateway,t=e.dataset.app,i=e.dataset.widgetId;if(!n||!t||!i)return;let s={gatewayUrl:n,appName:t,widgetId:i,position:e.dataset.position??"bottom-right",theme:e.dataset.theme??"auto",greeting:e.dataset.greeting,placeholder:e.dataset.placeholder},o=()=>new k(s);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",o);else o()})();})();
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@kilnai/widget",
3
+ "version": "0.1.5",
4
+ "description": "Embeddable chat widget for Kiln AI — add AI chat to any website with one script tag",
5
+ "type": "module",
6
+ "main": "dist/widget.js",
7
+ "files": ["dist"],
8
+ "scripts": {
9
+ "build": "bun run build.ts",
10
+ "test": "vitest run"
11
+ },
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sequelcore/kiln.git",
16
+ "directory": "packages/widget"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "devDependencies": {
22
+ "jsdom": "^25.0.0",
23
+ "vitest": "^4.0.18"
24
+ }
25
+ }