@happiest-team/ai-chat-elements 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +247 -0
- package/dist/index.mjs +247 -0
- package/package.json +31 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as lit_html from 'lit-html';
|
|
2
|
+
import * as lit from 'lit';
|
|
3
|
+
import { LitElement } from 'lit';
|
|
4
|
+
|
|
5
|
+
declare class TypingIndicator extends LitElement {
|
|
6
|
+
static styles: lit.CSSResult;
|
|
7
|
+
render(): lit_html.TemplateResult<1>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare class ChatPanel extends LitElement {
|
|
11
|
+
publishableKey: string;
|
|
12
|
+
gatewayUrl: string;
|
|
13
|
+
private _messages;
|
|
14
|
+
private _isTyping;
|
|
15
|
+
private _inputValue;
|
|
16
|
+
private _client;
|
|
17
|
+
private _unsubscribe?;
|
|
18
|
+
static styles: lit.CSSResult;
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
disconnectedCallback(): void;
|
|
21
|
+
private scrollToBottom;
|
|
22
|
+
private handleInput;
|
|
23
|
+
private handleKeydown;
|
|
24
|
+
private sendMessage;
|
|
25
|
+
render(): lit_html.TemplateResult<1>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { ChatPanel, TypingIndicator };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as lit_html from 'lit-html';
|
|
2
|
+
import * as lit from 'lit';
|
|
3
|
+
import { LitElement } from 'lit';
|
|
4
|
+
|
|
5
|
+
declare class TypingIndicator extends LitElement {
|
|
6
|
+
static styles: lit.CSSResult;
|
|
7
|
+
render(): lit_html.TemplateResult<1>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare class ChatPanel extends LitElement {
|
|
11
|
+
publishableKey: string;
|
|
12
|
+
gatewayUrl: string;
|
|
13
|
+
private _messages;
|
|
14
|
+
private _isTyping;
|
|
15
|
+
private _inputValue;
|
|
16
|
+
private _client;
|
|
17
|
+
private _unsubscribe?;
|
|
18
|
+
static styles: lit.CSSResult;
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
disconnectedCallback(): void;
|
|
21
|
+
private scrollToBottom;
|
|
22
|
+
private handleInput;
|
|
23
|
+
private handleKeydown;
|
|
24
|
+
private sendMessage;
|
|
25
|
+
render(): lit_html.TemplateResult<1>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { ChatPanel, TypingIndicator };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";var u=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var x=(t,s)=>{for(var e in s)u(t,e,{get:s[e],enumerable:!0})},w=(t,s,e,i)=>{if(s&&typeof s=="object"||typeof s=="function")for(let a of y(s))!m.call(t,a)&&a!==e&&u(t,a,{get:()=>s[a],enumerable:!(i=g(s,a))||i.enumerable});return t};var v=t=>w(u({},"__esModule",{value:!0}),t),n=(t,s,e,i)=>{for(var a=i>1?void 0:i?g(s,e):s,o=t.length-1,p;o>=0;o--)(p=t[o])&&(a=(i?p(s,e,a):p(a))||a);return i&&a&&u(s,e,a),a};var k={};x(k,{ChatPanel:()=>r,TypingIndicator:()=>h});module.exports=v(k);var c=require("lit"),f=require("lit/decorators.js");var h=class extends c.LitElement{render(){return c.html`
|
|
2
|
+
<div class="typing-indicator">
|
|
3
|
+
<div class="dot"></div>
|
|
4
|
+
<div class="dot"></div>
|
|
5
|
+
<div class="dot"></div>
|
|
6
|
+
</div>
|
|
7
|
+
`}};h.styles=c.css`
|
|
8
|
+
.typing-indicator {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
padding: 12px 16px;
|
|
12
|
+
background-color: #f1f5f9;
|
|
13
|
+
border-radius: 16px;
|
|
14
|
+
gap: 4px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dot {
|
|
18
|
+
width: 6px;
|
|
19
|
+
height: 6px;
|
|
20
|
+
background-color: #64748b;
|
|
21
|
+
border-radius: 50%;
|
|
22
|
+
animation: bounce 1.4s infinite ease-in-out both;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dot:nth-child(1) {
|
|
26
|
+
animation-delay: -0.32s;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dot:nth-child(2) {
|
|
30
|
+
animation-delay: -0.16s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@keyframes bounce {
|
|
34
|
+
0%, 80%, 100% {
|
|
35
|
+
transform: translateY(0);
|
|
36
|
+
}
|
|
37
|
+
40% {
|
|
38
|
+
transform: translateY(-6px);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`,h=n([(0,f.customElement)("happiest-typing-indicator")],h);var l=require("lit"),d=require("lit/decorators.js");var b=class{config;state;listeners=new Set;apiUrl;conversationId;constructor(t){this.config=t,this.apiUrl=t.gatewayUrl||"https://www.happiest.team/api";let s=this.getCache("token"),e=this.getCache("messages")||[];this.state={status:s?"connected":"idle",isTyping:!1,messages:e,sessionToken:s,widgetConfig:{},error:null}}updateState(t){this.state={...this.state,...t},this.notifyListeners()}subscribe(t){return this.listeners.add(t),t(this.state),()=>this.listeners.delete(t)}notifyListeners(){this.listeners.forEach(t=>t(this.state))}getState(){return this.state}getCacheKey(t){return`happiest_${t}_${this.config.publishableKey}`}getCache(t){if(typeof window>"u")return null;try{let s=localStorage.getItem(this.getCacheKey(t));return s?JSON.parse(s):null}catch{return null}}setCache(t,s){if(!(typeof window>"u"))try{localStorage.setItem(this.getCacheKey(t),JSON.stringify(s))}catch(e){console.warn("[Happiest] Failed to persist state",e)}}clearSession(){typeof window<"u"&&(localStorage.removeItem(this.getCacheKey("token")),localStorage.removeItem(this.getCacheKey("messages"))),this.updateState({status:"idle",sessionToken:null,messages:[],error:null}),this.conversationId=void 0}async initSession(){if(!(this.state.status==="connected"&&this.state.sessionToken)){this.updateState({status:"connecting",error:null});try{let t=await fetch(`${this.apiUrl}/widget/v1/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({publishableKey:this.config.publishableKey})}),s=await t.json();if(!t.ok)throw new Error(s.error||"Failed to initialize session");let e=s.sessionToken||s.embedToken,i=s.widgetConfig||s.config||{};this.setCache("token",e),this.updateState({status:"connected",sessionToken:e,widgetConfig:i})}catch(t){let s=t instanceof Error?t.message:"Network error";this.updateState({status:"error",error:s})}}}async sendMessage(t){let s=t.trim();if(!s||!this.state.sessionToken)return;let e={id:Date.now().toString(),text:s,isUser:!0,createdAt:Date.now(),status:"sending"},i=[...this.state.messages,e];this.setCache("messages",i),this.updateState({messages:i,isTyping:!0,error:null}),await this._sendToNetwork(e.id,s)}async retryMessage(t){let s=this.state.messages.find(i=>i.id===t);if(!s||s.status!=="failed")return;let e=this.state.messages.map(i=>i.id===t?{...i,status:"sending"}:i);this.setCache("messages",e),this.updateState({messages:e,isTyping:!0,error:null}),await this._sendToNetwork(t,s.text)}async _sendToNetwork(t,s){try{let e=await fetch(`${this.apiUrl}/widget/v1/chat`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.state.sessionToken}`},body:JSON.stringify({message:s,conversationId:this.conversationId})}),i=await e.json();if(!e.ok)throw new Error(i.error||"Failed to send message");i.conversationId&&(this.conversationId=i.conversationId);let a={id:(Date.now()+1).toString(),text:i.message||i.reply||"Thanks for your message.",isUser:!1,createdAt:Date.now()},o=[...this.state.messages.map(p=>p.id===t?{...p,status:"delivered"}:p),a];this.setCache("messages",o),this.updateState({messages:o,isTyping:!1})}catch(e){let i=e instanceof Error?e.message:"Message failed",a=this.state.messages.map(o=>o.id===t?{...o,status:"failed"}:o);this.setCache("messages",a),this.updateState({messages:a,isTyping:!1,error:i})}}};var r=class extends l.LitElement{constructor(){super(...arguments);this.publishableKey="";this.gatewayUrl="https://www.happiest.team/api";this._messages=[];this._isTyping=!1;this._inputValue=""}connectedCallback(){if(super.connectedCallback(),!this.publishableKey){console.error("[Happiest] publishable-key attribute is missing!");return}this._client=new b({publishableKey:this.publishableKey,gatewayUrl:this.gatewayUrl}),this._client.initSession(),this._unsubscribe=this._client.subscribe(e=>{this._messages=[...e.messages],this._isTyping=e.isTyping,this.scrollToBottom()})}disconnectedCallback(){super.disconnectedCallback(),this._unsubscribe&&this._unsubscribe()}async scrollToBottom(){await this.updateComplete;let e=this.shadowRoot?.querySelector(".messages");e&&(e.scrollTop=e.scrollHeight)}handleInput(e){this._inputValue=e.target.value}handleKeydown(e){e.key==="Enter"&&this.sendMessage()}sendMessage(){let e=this._inputValue.trim();e&&(this._client.sendMessage(e),this._inputValue="")}render(){return l.html`
|
|
42
|
+
<div class="header">
|
|
43
|
+
<div class="avatar">
|
|
44
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
45
|
+
<path d="M12 2a2 2 0 0 1 2 2v2.5a2.5 2.5 0 0 1-5 0V4a2 2 0 0 1 2-2Z"/>
|
|
46
|
+
<path d="M6 13a6 6 0 0 0 12 0v-2a6 6 0 0 0-12 0v2Z"/>
|
|
47
|
+
<path d="M15 13a3 3 0 0 1-6 0"/>
|
|
48
|
+
<path d="M12 22v-3"/>
|
|
49
|
+
<path d="M8 22h8"/>
|
|
50
|
+
</svg>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="header-text">
|
|
53
|
+
<h3>Happiest Support</h3>
|
|
54
|
+
<p>We typically reply in minutes</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="messages">
|
|
59
|
+
${this._messages.map(e=>l.html`
|
|
60
|
+
<div style="display: flex; flex-direction: column; align-items: ${e.isUser?"flex-end":"flex-start"}; width: 100%;">
|
|
61
|
+
<div class="message ${e.isUser?"user":"ai"} ${e.status==="failed"?"failed":""}">
|
|
62
|
+
${e.text}
|
|
63
|
+
</div>
|
|
64
|
+
${e.status==="failed"?l.html`
|
|
65
|
+
<button class="retry-btn" @click=${()=>this._client.retryMessage(e.id)}>
|
|
66
|
+
⚠️ Failed to send. Tap to retry
|
|
67
|
+
</button>
|
|
68
|
+
`:""}
|
|
69
|
+
</div>
|
|
70
|
+
`)}
|
|
71
|
+
|
|
72
|
+
${this._isTyping?l.html`
|
|
73
|
+
<div class="message ai" style="background: transparent; padding: 0;">
|
|
74
|
+
<happiest-typing-indicator></happiest-typing-indicator>
|
|
75
|
+
</div>
|
|
76
|
+
`:""}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="input-area">
|
|
80
|
+
<input
|
|
81
|
+
type="text"
|
|
82
|
+
placeholder="Type your message..."
|
|
83
|
+
.value=${this._inputValue}
|
|
84
|
+
@input=${this.handleInput}
|
|
85
|
+
@keydown=${this.handleKeydown}
|
|
86
|
+
>
|
|
87
|
+
<button
|
|
88
|
+
@click=${this.sendMessage}
|
|
89
|
+
?disabled=${!this._inputValue.trim()}
|
|
90
|
+
>
|
|
91
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
92
|
+
<line x1="22" y1="2" x2="11" y2="13"></line>
|
|
93
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
94
|
+
</svg>
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
`}};r.styles=l.css`
|
|
98
|
+
:host {
|
|
99
|
+
display: block;
|
|
100
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
height: 100%;
|
|
103
|
+
width: 100%;
|
|
104
|
+
background: white;
|
|
105
|
+
border-radius: 24px 24px 0 0;
|
|
106
|
+
box-shadow: 0 -5px 20px rgba(0,0,0,0.1);
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
* { box-sizing: inherit; }
|
|
113
|
+
|
|
114
|
+
.header {
|
|
115
|
+
padding: 16px;
|
|
116
|
+
border-bottom: 1px solid #e2e8f0;
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 12px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.avatar {
|
|
123
|
+
width: 40px;
|
|
124
|
+
height: 40px;
|
|
125
|
+
background: #3b82f6;
|
|
126
|
+
border-radius: 50%;
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
color: white;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.header-text h3 {
|
|
134
|
+
margin: 0;
|
|
135
|
+
font-size: 16px;
|
|
136
|
+
color: #0f172a;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.header-text p {
|
|
140
|
+
margin: 2px 0 0;
|
|
141
|
+
font-size: 12px;
|
|
142
|
+
color: #64748b;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.messages {
|
|
146
|
+
flex: 1;
|
|
147
|
+
overflow-y: auto;
|
|
148
|
+
padding: 16px;
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: 12px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.message {
|
|
155
|
+
max-width: 80%;
|
|
156
|
+
padding: 12px 16px;
|
|
157
|
+
border-radius: 16px;
|
|
158
|
+
font-size: 14px;
|
|
159
|
+
line-height: 1.4;
|
|
160
|
+
word-break: break-word;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.message.user {
|
|
164
|
+
align-self: flex-end;
|
|
165
|
+
background: #3b82f6;
|
|
166
|
+
color: white;
|
|
167
|
+
border-bottom-right-radius: 4px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.message.failed {
|
|
171
|
+
opacity: 0.9;
|
|
172
|
+
background: #ef4444;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.retry-btn {
|
|
176
|
+
align-self: flex-end;
|
|
177
|
+
background: transparent;
|
|
178
|
+
border: none;
|
|
179
|
+
color: #ef4444;
|
|
180
|
+
font-size: 11px;
|
|
181
|
+
font-weight: 500;
|
|
182
|
+
margin-top: 4px;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
padding: 0;
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
gap: 4px;
|
|
188
|
+
width: auto;
|
|
189
|
+
height: auto;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.retry-btn:hover {
|
|
193
|
+
text-decoration: underline;
|
|
194
|
+
opacity: 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.message.ai {
|
|
198
|
+
align-self: flex-start;
|
|
199
|
+
background: #f1f5f9;
|
|
200
|
+
color: #0f172a;
|
|
201
|
+
border-bottom-left-radius: 4px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.input-area {
|
|
205
|
+
padding: 16px;
|
|
206
|
+
border-top: 1px solid #e2e8f0;
|
|
207
|
+
display: flex;
|
|
208
|
+
gap: 8px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
input {
|
|
212
|
+
flex: 1;
|
|
213
|
+
padding: 12px 20px;
|
|
214
|
+
border-radius: 24px;
|
|
215
|
+
border: none;
|
|
216
|
+
background: #f1f5f9;
|
|
217
|
+
font-size: 14px;
|
|
218
|
+
outline: none;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
input:focus {
|
|
222
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
button {
|
|
226
|
+
width: 44px;
|
|
227
|
+
height: 44px;
|
|
228
|
+
border-radius: 50%;
|
|
229
|
+
border: none;
|
|
230
|
+
background: #3b82f6;
|
|
231
|
+
color: white;
|
|
232
|
+
cursor: pointer;
|
|
233
|
+
display: flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
justify-content: center;
|
|
236
|
+
transition: opacity 0.2s;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
button:hover {
|
|
240
|
+
opacity: 0.9;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
button:disabled {
|
|
244
|
+
background: #94a3b8;
|
|
245
|
+
cursor: not-allowed;
|
|
246
|
+
}
|
|
247
|
+
`,n([(0,d.property)({type:String,attribute:"publishable-key"})],r.prototype,"publishableKey",2),n([(0,d.property)({type:String,attribute:"gateway-url"})],r.prototype,"gatewayUrl",2),n([(0,d.state)()],r.prototype,"_messages",2),n([(0,d.state)()],r.prototype,"_isTyping",2),n([(0,d.state)()],r.prototype,"_inputValue",2),r=n([(0,d.customElement)("happiest-chat-panel")],r);0&&(module.exports={ChatPanel,TypingIndicator});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
var g=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var n=(t,s,e,i)=>{for(var a=i>1?void 0:i?f(s,e):s,r=t.length-1,l;r>=0;r--)(l=t[r])&&(a=(i?l(s,e,a):l(a))||a);return i&&a&&g(s,e,a),a};import{LitElement as b,html as y,css as m}from"lit";import{customElement as x}from"lit/decorators.js";var d=class extends b{render(){return y`
|
|
2
|
+
<div class="typing-indicator">
|
|
3
|
+
<div class="dot"></div>
|
|
4
|
+
<div class="dot"></div>
|
|
5
|
+
<div class="dot"></div>
|
|
6
|
+
</div>
|
|
7
|
+
`}};d.styles=m`
|
|
8
|
+
.typing-indicator {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
padding: 12px 16px;
|
|
12
|
+
background-color: #f1f5f9;
|
|
13
|
+
border-radius: 16px;
|
|
14
|
+
gap: 4px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dot {
|
|
18
|
+
width: 6px;
|
|
19
|
+
height: 6px;
|
|
20
|
+
background-color: #64748b;
|
|
21
|
+
border-radius: 50%;
|
|
22
|
+
animation: bounce 1.4s infinite ease-in-out both;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dot:nth-child(1) {
|
|
26
|
+
animation-delay: -0.32s;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dot:nth-child(2) {
|
|
30
|
+
animation-delay: -0.16s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@keyframes bounce {
|
|
34
|
+
0%, 80%, 100% {
|
|
35
|
+
transform: translateY(0);
|
|
36
|
+
}
|
|
37
|
+
40% {
|
|
38
|
+
transform: translateY(-6px);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`,d=n([x("happiest-typing-indicator")],d);import{LitElement as w,html as p,css as v}from"lit";import{customElement as k,property as u,state as h}from"lit/decorators.js";var c=class{config;state;listeners=new Set;apiUrl;conversationId;constructor(t){this.config=t,this.apiUrl=t.gatewayUrl||"https://www.happiest.team/api";let s=this.getCache("token"),e=this.getCache("messages")||[];this.state={status:s?"connected":"idle",isTyping:!1,messages:e,sessionToken:s,widgetConfig:{},error:null}}updateState(t){this.state={...this.state,...t},this.notifyListeners()}subscribe(t){return this.listeners.add(t),t(this.state),()=>this.listeners.delete(t)}notifyListeners(){this.listeners.forEach(t=>t(this.state))}getState(){return this.state}getCacheKey(t){return`happiest_${t}_${this.config.publishableKey}`}getCache(t){if(typeof window>"u")return null;try{let s=localStorage.getItem(this.getCacheKey(t));return s?JSON.parse(s):null}catch{return null}}setCache(t,s){if(!(typeof window>"u"))try{localStorage.setItem(this.getCacheKey(t),JSON.stringify(s))}catch(e){console.warn("[Happiest] Failed to persist state",e)}}clearSession(){typeof window<"u"&&(localStorage.removeItem(this.getCacheKey("token")),localStorage.removeItem(this.getCacheKey("messages"))),this.updateState({status:"idle",sessionToken:null,messages:[],error:null}),this.conversationId=void 0}async initSession(){if(!(this.state.status==="connected"&&this.state.sessionToken)){this.updateState({status:"connecting",error:null});try{let t=await fetch(`${this.apiUrl}/widget/v1/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({publishableKey:this.config.publishableKey})}),s=await t.json();if(!t.ok)throw new Error(s.error||"Failed to initialize session");let e=s.sessionToken||s.embedToken,i=s.widgetConfig||s.config||{};this.setCache("token",e),this.updateState({status:"connected",sessionToken:e,widgetConfig:i})}catch(t){let s=t instanceof Error?t.message:"Network error";this.updateState({status:"error",error:s})}}}async sendMessage(t){let s=t.trim();if(!s||!this.state.sessionToken)return;let e={id:Date.now().toString(),text:s,isUser:!0,createdAt:Date.now(),status:"sending"},i=[...this.state.messages,e];this.setCache("messages",i),this.updateState({messages:i,isTyping:!0,error:null}),await this._sendToNetwork(e.id,s)}async retryMessage(t){let s=this.state.messages.find(i=>i.id===t);if(!s||s.status!=="failed")return;let e=this.state.messages.map(i=>i.id===t?{...i,status:"sending"}:i);this.setCache("messages",e),this.updateState({messages:e,isTyping:!0,error:null}),await this._sendToNetwork(t,s.text)}async _sendToNetwork(t,s){try{let e=await fetch(`${this.apiUrl}/widget/v1/chat`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.state.sessionToken}`},body:JSON.stringify({message:s,conversationId:this.conversationId})}),i=await e.json();if(!e.ok)throw new Error(i.error||"Failed to send message");i.conversationId&&(this.conversationId=i.conversationId);let a={id:(Date.now()+1).toString(),text:i.message||i.reply||"Thanks for your message.",isUser:!1,createdAt:Date.now()},r=[...this.state.messages.map(l=>l.id===t?{...l,status:"delivered"}:l),a];this.setCache("messages",r),this.updateState({messages:r,isTyping:!1})}catch(e){let i=e instanceof Error?e.message:"Message failed",a=this.state.messages.map(r=>r.id===t?{...r,status:"failed"}:r);this.setCache("messages",a),this.updateState({messages:a,isTyping:!1,error:i})}}};var o=class extends w{constructor(){super(...arguments);this.publishableKey="";this.gatewayUrl="https://www.happiest.team/api";this._messages=[];this._isTyping=!1;this._inputValue=""}connectedCallback(){if(super.connectedCallback(),!this.publishableKey){console.error("[Happiest] publishable-key attribute is missing!");return}this._client=new c({publishableKey:this.publishableKey,gatewayUrl:this.gatewayUrl}),this._client.initSession(),this._unsubscribe=this._client.subscribe(e=>{this._messages=[...e.messages],this._isTyping=e.isTyping,this.scrollToBottom()})}disconnectedCallback(){super.disconnectedCallback(),this._unsubscribe&&this._unsubscribe()}async scrollToBottom(){await this.updateComplete;let e=this.shadowRoot?.querySelector(".messages");e&&(e.scrollTop=e.scrollHeight)}handleInput(e){this._inputValue=e.target.value}handleKeydown(e){e.key==="Enter"&&this.sendMessage()}sendMessage(){let e=this._inputValue.trim();e&&(this._client.sendMessage(e),this._inputValue="")}render(){return p`
|
|
42
|
+
<div class="header">
|
|
43
|
+
<div class="avatar">
|
|
44
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
45
|
+
<path d="M12 2a2 2 0 0 1 2 2v2.5a2.5 2.5 0 0 1-5 0V4a2 2 0 0 1 2-2Z"/>
|
|
46
|
+
<path d="M6 13a6 6 0 0 0 12 0v-2a6 6 0 0 0-12 0v2Z"/>
|
|
47
|
+
<path d="M15 13a3 3 0 0 1-6 0"/>
|
|
48
|
+
<path d="M12 22v-3"/>
|
|
49
|
+
<path d="M8 22h8"/>
|
|
50
|
+
</svg>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="header-text">
|
|
53
|
+
<h3>Happiest Support</h3>
|
|
54
|
+
<p>We typically reply in minutes</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="messages">
|
|
59
|
+
${this._messages.map(e=>p`
|
|
60
|
+
<div style="display: flex; flex-direction: column; align-items: ${e.isUser?"flex-end":"flex-start"}; width: 100%;">
|
|
61
|
+
<div class="message ${e.isUser?"user":"ai"} ${e.status==="failed"?"failed":""}">
|
|
62
|
+
${e.text}
|
|
63
|
+
</div>
|
|
64
|
+
${e.status==="failed"?p`
|
|
65
|
+
<button class="retry-btn" @click=${()=>this._client.retryMessage(e.id)}>
|
|
66
|
+
⚠️ Failed to send. Tap to retry
|
|
67
|
+
</button>
|
|
68
|
+
`:""}
|
|
69
|
+
</div>
|
|
70
|
+
`)}
|
|
71
|
+
|
|
72
|
+
${this._isTyping?p`
|
|
73
|
+
<div class="message ai" style="background: transparent; padding: 0;">
|
|
74
|
+
<happiest-typing-indicator></happiest-typing-indicator>
|
|
75
|
+
</div>
|
|
76
|
+
`:""}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="input-area">
|
|
80
|
+
<input
|
|
81
|
+
type="text"
|
|
82
|
+
placeholder="Type your message..."
|
|
83
|
+
.value=${this._inputValue}
|
|
84
|
+
@input=${this.handleInput}
|
|
85
|
+
@keydown=${this.handleKeydown}
|
|
86
|
+
>
|
|
87
|
+
<button
|
|
88
|
+
@click=${this.sendMessage}
|
|
89
|
+
?disabled=${!this._inputValue.trim()}
|
|
90
|
+
>
|
|
91
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
92
|
+
<line x1="22" y1="2" x2="11" y2="13"></line>
|
|
93
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
94
|
+
</svg>
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
`}};o.styles=v`
|
|
98
|
+
:host {
|
|
99
|
+
display: block;
|
|
100
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
height: 100%;
|
|
103
|
+
width: 100%;
|
|
104
|
+
background: white;
|
|
105
|
+
border-radius: 24px 24px 0 0;
|
|
106
|
+
box-shadow: 0 -5px 20px rgba(0,0,0,0.1);
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
* { box-sizing: inherit; }
|
|
113
|
+
|
|
114
|
+
.header {
|
|
115
|
+
padding: 16px;
|
|
116
|
+
border-bottom: 1px solid #e2e8f0;
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 12px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.avatar {
|
|
123
|
+
width: 40px;
|
|
124
|
+
height: 40px;
|
|
125
|
+
background: #3b82f6;
|
|
126
|
+
border-radius: 50%;
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
color: white;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.header-text h3 {
|
|
134
|
+
margin: 0;
|
|
135
|
+
font-size: 16px;
|
|
136
|
+
color: #0f172a;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.header-text p {
|
|
140
|
+
margin: 2px 0 0;
|
|
141
|
+
font-size: 12px;
|
|
142
|
+
color: #64748b;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.messages {
|
|
146
|
+
flex: 1;
|
|
147
|
+
overflow-y: auto;
|
|
148
|
+
padding: 16px;
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: 12px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.message {
|
|
155
|
+
max-width: 80%;
|
|
156
|
+
padding: 12px 16px;
|
|
157
|
+
border-radius: 16px;
|
|
158
|
+
font-size: 14px;
|
|
159
|
+
line-height: 1.4;
|
|
160
|
+
word-break: break-word;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.message.user {
|
|
164
|
+
align-self: flex-end;
|
|
165
|
+
background: #3b82f6;
|
|
166
|
+
color: white;
|
|
167
|
+
border-bottom-right-radius: 4px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.message.failed {
|
|
171
|
+
opacity: 0.9;
|
|
172
|
+
background: #ef4444;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.retry-btn {
|
|
176
|
+
align-self: flex-end;
|
|
177
|
+
background: transparent;
|
|
178
|
+
border: none;
|
|
179
|
+
color: #ef4444;
|
|
180
|
+
font-size: 11px;
|
|
181
|
+
font-weight: 500;
|
|
182
|
+
margin-top: 4px;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
padding: 0;
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
gap: 4px;
|
|
188
|
+
width: auto;
|
|
189
|
+
height: auto;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.retry-btn:hover {
|
|
193
|
+
text-decoration: underline;
|
|
194
|
+
opacity: 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.message.ai {
|
|
198
|
+
align-self: flex-start;
|
|
199
|
+
background: #f1f5f9;
|
|
200
|
+
color: #0f172a;
|
|
201
|
+
border-bottom-left-radius: 4px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.input-area {
|
|
205
|
+
padding: 16px;
|
|
206
|
+
border-top: 1px solid #e2e8f0;
|
|
207
|
+
display: flex;
|
|
208
|
+
gap: 8px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
input {
|
|
212
|
+
flex: 1;
|
|
213
|
+
padding: 12px 20px;
|
|
214
|
+
border-radius: 24px;
|
|
215
|
+
border: none;
|
|
216
|
+
background: #f1f5f9;
|
|
217
|
+
font-size: 14px;
|
|
218
|
+
outline: none;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
input:focus {
|
|
222
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
button {
|
|
226
|
+
width: 44px;
|
|
227
|
+
height: 44px;
|
|
228
|
+
border-radius: 50%;
|
|
229
|
+
border: none;
|
|
230
|
+
background: #3b82f6;
|
|
231
|
+
color: white;
|
|
232
|
+
cursor: pointer;
|
|
233
|
+
display: flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
justify-content: center;
|
|
236
|
+
transition: opacity 0.2s;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
button:hover {
|
|
240
|
+
opacity: 0.9;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
button:disabled {
|
|
244
|
+
background: #94a3b8;
|
|
245
|
+
cursor: not-allowed;
|
|
246
|
+
}
|
|
247
|
+
`,n([u({type:String,attribute:"publishable-key"})],o.prototype,"publishableKey",2),n([u({type:String,attribute:"gateway-url"})],o.prototype,"gatewayUrl",2),n([h()],o.prototype,"_messages",2),n([h()],o.prototype,"_isTyping",2),n([h()],o.prototype,"_inputValue",2),o=n([k("happiest-chat-panel")],o);export{o as ChatPanel,d as TypingIndicator};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happiest-team/ai-chat-elements",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Universal Web Components for Happiest Chat (Lit framework)",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@happiest-team/ai-chat-core": "*",
|
|
24
|
+
"lit": "^3.2.1"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"author": "Happiest",
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|