passline-ai 2.0.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.
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/api/passlineApi.d.ts +31 -0
- package/dist/component/PasslineChat.d.ts +4 -0
- package/dist/component/types.d.ts +88 -0
- package/dist/hooks/usePasslineClient.d.ts +24 -0
- package/dist/hooks/usePasslineSession.d.ts +13 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Himexa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Passline
|
|
2
|
+
|
|
3
|
+
React chat widget for the Passline website chatbot API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install passline-ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies: `react` and `react-dom` >= 18.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { PasslineChat } from "passline-ai";
|
|
17
|
+
|
|
18
|
+
export function App() {
|
|
19
|
+
return (
|
|
20
|
+
<PasslineChat
|
|
21
|
+
token={process.env.NEXT_PUBLIC_PASSLINE_TOKEN!}
|
|
22
|
+
origin="https://api.passline.ai"
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`origin` should be the API host that serves `POST /api/chatbot/session`. Styles ship inside the bundle.
|
|
29
|
+
|
|
30
|
+
## Scripts
|
|
31
|
+
|
|
32
|
+
| Command | Purpose |
|
|
33
|
+
| ----------------------------- | ----------------------------------- |
|
|
34
|
+
| `npm run build` | Produce `dist/` for npm |
|
|
35
|
+
| `npm run pack:check` | Show what `npm publish` will upload |
|
|
36
|
+
| `npm run dev` | Local demo at http://127.0.0.1:5173 |
|
|
37
|
+
| `npm publish --access public` | Publish `passline-ai` |
|
|
38
|
+
|
|
39
|
+
Copy `.env.example` to `.env` for the demo. Publishing a GitHub Release on `main` runs `.github/workflows/publish.yml` when `NPM_TOKEN` is set.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PasslineSession } from "../component/types";
|
|
2
|
+
export type ChatbotSessionResponse = {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
greeting: string;
|
|
5
|
+
assistantName: string;
|
|
6
|
+
companyName: string | null;
|
|
7
|
+
logoUrl: string | null;
|
|
8
|
+
};
|
|
9
|
+
export type ChatbotMessageResponse = {
|
|
10
|
+
reply: string;
|
|
11
|
+
};
|
|
12
|
+
export declare class PasslineApiError extends Error {
|
|
13
|
+
readonly code?: string;
|
|
14
|
+
readonly status?: number;
|
|
15
|
+
constructor(message: string, options?: {
|
|
16
|
+
code?: string;
|
|
17
|
+
status?: number;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export declare function isOfflineError(err: unknown): boolean;
|
|
21
|
+
export declare function isSessionInvalidError(err: unknown): boolean;
|
|
22
|
+
export declare class PasslineApi {
|
|
23
|
+
private token;
|
|
24
|
+
private origin;
|
|
25
|
+
constructor(token: string, origin?: string);
|
|
26
|
+
private getHeaders;
|
|
27
|
+
private request;
|
|
28
|
+
createSession(): Promise<ChatbotSessionResponse>;
|
|
29
|
+
sendMessage(sessionId: string, message: string): Promise<ChatbotMessageResponse>;
|
|
30
|
+
}
|
|
31
|
+
export declare function toPasslineSession(response: ChatbotSessionResponse, messages?: PasslineSession["messages"]): PasslineSession;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface PasslineMessage {
|
|
3
|
+
id?: string;
|
|
4
|
+
role: "user" | "assistant";
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PasslineSession {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
assistantName: string;
|
|
11
|
+
companyName: string | null;
|
|
12
|
+
logoUrl: string | null;
|
|
13
|
+
greeting?: string;
|
|
14
|
+
messages: PasslineMessage[];
|
|
15
|
+
}
|
|
16
|
+
export interface PasslineTheme {
|
|
17
|
+
primaryColor?: string;
|
|
18
|
+
primaryHoverColor?: string;
|
|
19
|
+
backgroundColor?: string;
|
|
20
|
+
textColor?: string;
|
|
21
|
+
userBubbleColor?: string;
|
|
22
|
+
userBubbleTextColor?: string;
|
|
23
|
+
assistantBubbleColor?: string;
|
|
24
|
+
assistantBubbleTextColor?: string;
|
|
25
|
+
borderRadius?: string | number;
|
|
26
|
+
borderWidth?: string | number;
|
|
27
|
+
borderColor?: string;
|
|
28
|
+
buttonSize?: "small" | "medium" | "large";
|
|
29
|
+
buttonBorderRadius?: string | number;
|
|
30
|
+
buttonBackgroundColor?: string;
|
|
31
|
+
buttonHoverColor?: string;
|
|
32
|
+
buttonIconColor?: string;
|
|
33
|
+
panelWidth?: string | number;
|
|
34
|
+
panelHeight?: string | number;
|
|
35
|
+
panelShadow?: string;
|
|
36
|
+
fontFamily?: string;
|
|
37
|
+
fontSize?: string;
|
|
38
|
+
spacing?: string | number;
|
|
39
|
+
padding?: string | number;
|
|
40
|
+
className?: string;
|
|
41
|
+
buttonClassName?: string;
|
|
42
|
+
panelClassName?: string;
|
|
43
|
+
headerClassName?: string;
|
|
44
|
+
messagesClassName?: string;
|
|
45
|
+
inputClassName?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface PasslineChatProps {
|
|
48
|
+
token: string;
|
|
49
|
+
origin?: string;
|
|
50
|
+
theme?: PasslineTheme;
|
|
51
|
+
className?: string;
|
|
52
|
+
buttonClassName?: string;
|
|
53
|
+
panelClassName?: string;
|
|
54
|
+
renderButton?: (props: {
|
|
55
|
+
onClick: () => void;
|
|
56
|
+
isOpen: boolean;
|
|
57
|
+
className: string;
|
|
58
|
+
}) => ReactNode;
|
|
59
|
+
renderHeader?: (props: {
|
|
60
|
+
title: string;
|
|
61
|
+
subtitle: string;
|
|
62
|
+
avatar: string;
|
|
63
|
+
onNewChat: () => void;
|
|
64
|
+
}) => ReactNode;
|
|
65
|
+
renderMessage?: (props: {
|
|
66
|
+
message: PasslineMessage;
|
|
67
|
+
className: string;
|
|
68
|
+
}) => ReactNode;
|
|
69
|
+
renderInput?: (props: {
|
|
70
|
+
value: string;
|
|
71
|
+
onChange: (value: string) => void;
|
|
72
|
+
onSend: () => void;
|
|
73
|
+
disabled: boolean;
|
|
74
|
+
placeholder: string;
|
|
75
|
+
className: string;
|
|
76
|
+
}) => ReactNode;
|
|
77
|
+
autoOpen?: boolean;
|
|
78
|
+
persistSession?: boolean;
|
|
79
|
+
maxSessionAge?: number;
|
|
80
|
+
maxStoredMessages?: number;
|
|
81
|
+
onOpen?: () => void;
|
|
82
|
+
onClose?: () => void;
|
|
83
|
+
onMessageSend?: (message: string) => void;
|
|
84
|
+
onMessageReceive?: (message: PasslineMessage) => void;
|
|
85
|
+
onError?: (error: Error) => void;
|
|
86
|
+
onSessionStart?: (session: PasslineSession) => void;
|
|
87
|
+
onNewChat?: () => void;
|
|
88
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PasslineMessage, PasslineSession } from "../component/types";
|
|
2
|
+
export declare function usePasslineChat(token: string, options?: {
|
|
3
|
+
origin?: string;
|
|
4
|
+
persistSession?: boolean;
|
|
5
|
+
maxSessionAge?: number;
|
|
6
|
+
maxStoredMessages?: number;
|
|
7
|
+
autoOpen?: boolean;
|
|
8
|
+
onMessageSend?: (message: string) => void;
|
|
9
|
+
onMessageReceive?: (message: PasslineMessage) => void;
|
|
10
|
+
onError?: (error: Error) => void;
|
|
11
|
+
onSessionStart?: (session: PasslineSession) => void;
|
|
12
|
+
}): {
|
|
13
|
+
isOpen: boolean;
|
|
14
|
+
setIsOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
15
|
+
toggleOpen: () => void;
|
|
16
|
+
isSending: boolean;
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
offline: boolean;
|
|
19
|
+
messages: PasslineMessage[];
|
|
20
|
+
session: PasslineSession | null;
|
|
21
|
+
error: string | null;
|
|
22
|
+
sendMessage: (content: string) => Promise<void>;
|
|
23
|
+
newChat: () => Promise<void>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PasslineMessage, PasslineSession } from "../component/types";
|
|
2
|
+
export declare function usePasslineSession(token: string, origin?: string, persistSession?: boolean, maxSessionAge?: number, maxStoredMessages?: number): {
|
|
3
|
+
session: PasslineSession | null;
|
|
4
|
+
messages: PasslineMessage[];
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
offline: boolean;
|
|
7
|
+
error: string | null;
|
|
8
|
+
sendMessage: (content: string) => Promise<PasslineMessage>;
|
|
9
|
+
newChat: () => Promise<PasslineSession>;
|
|
10
|
+
createSession: (forceNew?: boolean) => Promise<PasslineSession>;
|
|
11
|
+
ensureSession: () => Promise<PasslineSession | null>;
|
|
12
|
+
clearStoredSession: () => void;
|
|
13
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import react__default, { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface PasslineMessage {
|
|
5
|
+
id?: string;
|
|
6
|
+
role: "user" | "assistant";
|
|
7
|
+
content: string;
|
|
8
|
+
timestamp?: number;
|
|
9
|
+
}
|
|
10
|
+
interface PasslineSession {
|
|
11
|
+
sessionId: string;
|
|
12
|
+
assistantName: string;
|
|
13
|
+
companyName: string | null;
|
|
14
|
+
logoUrl: string | null;
|
|
15
|
+
greeting?: string;
|
|
16
|
+
messages: PasslineMessage[];
|
|
17
|
+
}
|
|
18
|
+
interface PasslineTheme {
|
|
19
|
+
primaryColor?: string;
|
|
20
|
+
primaryHoverColor?: string;
|
|
21
|
+
backgroundColor?: string;
|
|
22
|
+
textColor?: string;
|
|
23
|
+
userBubbleColor?: string;
|
|
24
|
+
userBubbleTextColor?: string;
|
|
25
|
+
assistantBubbleColor?: string;
|
|
26
|
+
assistantBubbleTextColor?: string;
|
|
27
|
+
borderRadius?: string | number;
|
|
28
|
+
borderWidth?: string | number;
|
|
29
|
+
borderColor?: string;
|
|
30
|
+
buttonSize?: "small" | "medium" | "large";
|
|
31
|
+
buttonBorderRadius?: string | number;
|
|
32
|
+
buttonBackgroundColor?: string;
|
|
33
|
+
buttonHoverColor?: string;
|
|
34
|
+
buttonIconColor?: string;
|
|
35
|
+
panelWidth?: string | number;
|
|
36
|
+
panelHeight?: string | number;
|
|
37
|
+
panelShadow?: string;
|
|
38
|
+
fontFamily?: string;
|
|
39
|
+
fontSize?: string;
|
|
40
|
+
spacing?: string | number;
|
|
41
|
+
padding?: string | number;
|
|
42
|
+
className?: string;
|
|
43
|
+
buttonClassName?: string;
|
|
44
|
+
panelClassName?: string;
|
|
45
|
+
headerClassName?: string;
|
|
46
|
+
messagesClassName?: string;
|
|
47
|
+
inputClassName?: string;
|
|
48
|
+
}
|
|
49
|
+
interface PasslineChatProps {
|
|
50
|
+
token: string;
|
|
51
|
+
origin?: string;
|
|
52
|
+
theme?: PasslineTheme;
|
|
53
|
+
className?: string;
|
|
54
|
+
buttonClassName?: string;
|
|
55
|
+
panelClassName?: string;
|
|
56
|
+
renderButton?: (props: {
|
|
57
|
+
onClick: () => void;
|
|
58
|
+
isOpen: boolean;
|
|
59
|
+
className: string;
|
|
60
|
+
}) => ReactNode;
|
|
61
|
+
renderHeader?: (props: {
|
|
62
|
+
title: string;
|
|
63
|
+
subtitle: string;
|
|
64
|
+
avatar: string;
|
|
65
|
+
onNewChat: () => void;
|
|
66
|
+
}) => ReactNode;
|
|
67
|
+
renderMessage?: (props: {
|
|
68
|
+
message: PasslineMessage;
|
|
69
|
+
className: string;
|
|
70
|
+
}) => ReactNode;
|
|
71
|
+
renderInput?: (props: {
|
|
72
|
+
value: string;
|
|
73
|
+
onChange: (value: string) => void;
|
|
74
|
+
onSend: () => void;
|
|
75
|
+
disabled: boolean;
|
|
76
|
+
placeholder: string;
|
|
77
|
+
className: string;
|
|
78
|
+
}) => ReactNode;
|
|
79
|
+
autoOpen?: boolean;
|
|
80
|
+
persistSession?: boolean;
|
|
81
|
+
maxSessionAge?: number;
|
|
82
|
+
maxStoredMessages?: number;
|
|
83
|
+
onOpen?: () => void;
|
|
84
|
+
onClose?: () => void;
|
|
85
|
+
onMessageSend?: (message: string) => void;
|
|
86
|
+
onMessageReceive?: (message: PasslineMessage) => void;
|
|
87
|
+
onError?: (error: Error) => void;
|
|
88
|
+
onSessionStart?: (session: PasslineSession) => void;
|
|
89
|
+
onNewChat?: () => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
declare const PasslineChat: react__default.FC<PasslineChatProps>;
|
|
93
|
+
|
|
94
|
+
declare function usePasslineChat(token: string, options?: {
|
|
95
|
+
origin?: string;
|
|
96
|
+
persistSession?: boolean;
|
|
97
|
+
maxSessionAge?: number;
|
|
98
|
+
maxStoredMessages?: number;
|
|
99
|
+
autoOpen?: boolean;
|
|
100
|
+
onMessageSend?: (message: string) => void;
|
|
101
|
+
onMessageReceive?: (message: PasslineMessage) => void;
|
|
102
|
+
onError?: (error: Error) => void;
|
|
103
|
+
onSessionStart?: (session: PasslineSession) => void;
|
|
104
|
+
}): {
|
|
105
|
+
isOpen: boolean;
|
|
106
|
+
setIsOpen: react.Dispatch<react.SetStateAction<boolean>>;
|
|
107
|
+
toggleOpen: () => void;
|
|
108
|
+
isSending: boolean;
|
|
109
|
+
isLoading: boolean;
|
|
110
|
+
offline: boolean;
|
|
111
|
+
messages: PasslineMessage[];
|
|
112
|
+
session: PasslineSession | null;
|
|
113
|
+
error: string | null;
|
|
114
|
+
sendMessage: (content: string) => Promise<void>;
|
|
115
|
+
newChat: () => Promise<void>;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
declare function usePasslineSession(token: string, origin?: string, persistSession?: boolean, maxSessionAge?: number, maxStoredMessages?: number): {
|
|
119
|
+
session: PasslineSession | null;
|
|
120
|
+
messages: PasslineMessage[];
|
|
121
|
+
isLoading: boolean;
|
|
122
|
+
offline: boolean;
|
|
123
|
+
error: string | null;
|
|
124
|
+
sendMessage: (content: string) => Promise<PasslineMessage>;
|
|
125
|
+
newChat: () => Promise<PasslineSession>;
|
|
126
|
+
createSession: (forceNew?: boolean) => Promise<PasslineSession>;
|
|
127
|
+
ensureSession: () => Promise<PasslineSession | null>;
|
|
128
|
+
clearStoredSession: () => void;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
type ChatbotSessionResponse = {
|
|
132
|
+
sessionId: string;
|
|
133
|
+
greeting: string;
|
|
134
|
+
assistantName: string;
|
|
135
|
+
companyName: string | null;
|
|
136
|
+
logoUrl: string | null;
|
|
137
|
+
};
|
|
138
|
+
type ChatbotMessageResponse = {
|
|
139
|
+
reply: string;
|
|
140
|
+
};
|
|
141
|
+
declare class PasslineApiError extends Error {
|
|
142
|
+
readonly code?: string;
|
|
143
|
+
readonly status?: number;
|
|
144
|
+
constructor(message: string, options?: {
|
|
145
|
+
code?: string;
|
|
146
|
+
status?: number;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
declare function isOfflineError(err: unknown): boolean;
|
|
150
|
+
declare function isSessionInvalidError(err: unknown): boolean;
|
|
151
|
+
declare class PasslineApi {
|
|
152
|
+
private token;
|
|
153
|
+
private origin;
|
|
154
|
+
constructor(token: string, origin?: string);
|
|
155
|
+
private getHeaders;
|
|
156
|
+
private request;
|
|
157
|
+
createSession(): Promise<ChatbotSessionResponse>;
|
|
158
|
+
sendMessage(sessionId: string, message: string): Promise<ChatbotMessageResponse>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export { PasslineApi, PasslineApiError, PasslineChat, isOfflineError, isSessionInvalidError, usePasslineChat, usePasslineSession };
|
|
162
|
+
export type { ChatbotMessageResponse, ChatbotSessionResponse, PasslineChatProps, PasslineMessage, PasslineSession, PasslineTheme };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{jsx as e,jsxs as s}from"react/jsx-runtime";import{useState as n,useRef as a,useEffect as t,useCallback as r}from"react";class o extends Error{constructor(e,s){super(e),this.name="PasslineApiError",this.code=s?.code,this.status=s?.status}}function l(e){if(!e)return!1;if(e instanceof o&&"OFFLINE"===e.code)return!0;const s=e instanceof Error?e.message:"";return/offline|failed to fetch|networkerror|load failed|request failed \(5\d\d\)/i.test(s)}function i(e){const s=e instanceof Error?e.message.toLowerCase():"";return/session/.test(s)&&/(not found|expired|invalid)/.test(s)}class c{constructor(e,s){this.token=e,this.origin=(s||"https://api.passline.ai").replace(/\/$/,"")}getHeaders(){return{Accept:"application/json","Content-Type":"application/json","X-Passline-Embed-Token":this.token}}async request(e,s={}){const n=`${this.origin}/api/chatbot/${e.replace(/^\//,"")}`;let a;try{a=await fetch(n,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(s),cache:"no-store"})}catch{throw new o("Offline",{code:"OFFLINE"})}const t=await a.json().catch(()=>null);if(!a.ok){if(a.status>=500||0===a.status)throw new o("Offline",{code:"OFFLINE",status:a.status});throw new o(t?.message||`Request failed (${a.status})`,{status:a.status})}return t}async createSession(){return this.request("session",{})}async sendMessage(e,s){return this.request(`session/${encodeURIComponent(e)}/messages`,{message:s})}}function u(e){return`${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function d(e,s,o=!0,d=6048e5,m=200){const[g,p]=n(null),[f,h]=n(!1),[b,C]=n(!1),[w,y]=n(null),N=a(new c(e,s)),S=a(null),v=a(0),x=`passline:chat:${e}`;S.current=g,t(()=>{N.current=new c(e,s)},[e,s]);const I=r(()=>{if(!o||"undefined"==typeof window)return null;try{const e=window.localStorage.getItem(x);if(!e)return null;const s=JSON.parse(e);return s?.sessionId&&Array.isArray(s.messages)?Date.now()-(s.updatedAt||0)>d?(window.localStorage.removeItem(x),null):s:null}catch{return null}},[d,o,x]),O=r(e=>{if(o&&"undefined"!=typeof window)try{const s=e.messages.length>m?e.messages.slice(e.messages.length-m):e.messages;window.localStorage.setItem(x,JSON.stringify({...e,messages:s}))}catch{}},[m,o,x]),k=r(()=>{if(o&&"undefined"!=typeof window)try{window.localStorage.removeItem(x)}catch{}},[o,x]),E=r(e=>{O({sessionId:e.sessionId,assistantName:e.assistantName,companyName:e.companyName,logoUrl:e.logoUrl,messages:e.messages,updatedAt:Date.now()})},[O]),$=r(()=>{C(!0),p(null),S.current=null,y("Offline"),h(!1)},[]),A=r(()=>{const e=I();if(!e)return null;const s={sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,messages:e.messages};return p(s),S.current=s,C(!1),y(null),h(!1),s},[I]),M=r(async(e=!1)=>{const s=++v.current;if(y(null),!e){const e=A();if(e)return e}try{h(!0);const e=await N.current.createSession();if(v.current!==s){const e=S.current;if(e)return e}const n=function(e,s=[]){return{sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,greeting:e.greeting,messages:s}}(e,[{id:"greeting",role:"assistant",content:e.greeting||"Hi! How can I help?",timestamp:Date.now()}]);return p(n),S.current=n,C(!1),E(n),n}catch(e){if(v.current!==s){const e=S.current;if(e)return e}if(l(e))throw $(),new Error("Offline");const n=e instanceof Error?e.message:"Unable to start chat";throw y(n),p(null),S.current=null,new Error(n)}finally{v.current===s&&h(!1)}},[$,E,A]),B=r(async()=>{if(b)return null;if(S.current?.sessionId)return S.current;try{return await M(!1)}catch{return null}},[M,b]),U=r(async e=>{const s=S.current;if(!s?.sessionId)throw new Error("No active session");const n={id:u("u"),role:"user",content:e,timestamp:Date.now()},a={...s,messages:[...s.messages,n]};p(a),S.current=a,E(a);try{const n=await N.current.sendMessage(s.sessionId,e),t={id:u("a"),role:"assistant",content:n.reply||"",timestamp:Date.now()},r=S.current??a,o={...r,messages:[...r.messages,t]};return p(o),S.current=o,E(o),t}catch(e){if(l(e))throw $(),new Error("Offline");if(i(e))throw k(),p(null),S.current=null,await M(!0),new Error("Session expired. Please try your message again.");throw e}},[k,M,$,E]),R=r(async()=>(k(),p(null),S.current=null,C(!1),M(!0)),[k,M]);return t(()=>{v.current+=1,p(null),S.current=null,C(!1),y(null),h(!1),A()},[A,e]),{session:g,messages:g?.messages||[],isLoading:f,offline:b,error:w,sendMessage:U,newChat:R,createSession:M,ensureSession:B,clearStoredSession:k}}function m(e,s){const[o,l]=n(Boolean(s?.autoOpen)),[i,c]=n(!1),u=a(null),{session:m,messages:g,isLoading:p,offline:f,error:h,sendMessage:b,newChat:C,ensureSession:w}=d(e,s?.origin,s?.persistSession,s?.maxSessionAge,s?.maxStoredMessages);t(()=>{s?.autoOpen&&l(!0)},[s?.autoOpen]),t(()=>{u.current=null},[e]),t(()=>{o&&!f&&w()},[w,o,f,e]),t(()=>{m?.sessionId&&m.sessionId!==u.current&&(u.current=m.sessionId,s?.onSessionStart?.(m))},[s,m]),t(()=>{h&&s?.onError&&s.onError(new Error(h))},[h,s]);const y=r(async e=>{if(e.trim()&&!i&&!p&&!f)try{c(!0),s?.onMessageSend?.(e);const n=await b(e);s?.onMessageReceive?.(n)}catch(e){s?.onError?.(e instanceof Error?e:new Error("Failed to send message"))}finally{c(!1)}},[p,i,f,s,b]),N=r(async()=>{try{await C()}catch(e){s?.onError?.(e instanceof Error?e:new Error("Failed to start new chat"))}},[C,s]),S=r(()=>{l(e=>!e)},[]);return{isOpen:o,setIsOpen:l,toggleOpen:S,isSending:i,isLoading:p,offline:f,messages:g,session:m,error:h,sendMessage:y,newChat:N}}const g={primaryColor:"#0f766e",primaryHoverColor:"#0d9488",backgroundColor:"#f8fafc",textColor:"#0f172a",userBubbleColor:"#0f766e",userBubbleTextColor:"#ffffff",assistantBubbleColor:"#ffffff",assistantBubbleTextColor:"#1e293b",borderRadius:"16px",borderWidth:"1px",borderColor:"#e2e8f0",buttonSize:"medium",buttonBorderRadius:"999px",buttonBackgroundColor:"#0f766e",buttonHoverColor:"#0d9488",buttonIconColor:"#ffffff",panelWidth:"380px",panelHeight:"560px",panelShadow:"0 12px 40px rgba(0,0,0,.22)",fontFamily:"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",fontSize:"14px",spacing:"12px",padding:"12px",className:"",buttonClassName:"",panelClassName:"",headerClassName:"",messagesClassName:"",inputClassName:""};function p(e){const s=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===s.length?"A":1===s.length?s[0].slice(0,2).toUpperCase():(s[0].charAt(0)+s[1].charAt(0)).toUpperCase()}function f({logoUrl:s,label:a,fallback:r="initials",className:o,backgroundColor:l}){const[i,c]=n(!1);t(()=>{c(!1)},[s]);const u=Boolean(s)&&!i;return e("div",{className:`${o}${u?" passline-has-logo":""}`,style:u?void 0:{backgroundColor:l},children:u?e("img",{src:s??"",alt:a||"Organization logo",className:"passline-chat-avatar-img",onError:()=>c(!0)}):"icon"===r?e("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e("path",{d:"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"})}):p(a)})}const h=({token:o,origin:l,theme:i={},className:c="",buttonClassName:u="",panelClassName:d="",renderButton:h,renderHeader:b,renderMessage:C,renderInput:w,autoOpen:y=!1,persistSession:N=!0,maxSessionAge:S,maxStoredMessages:v,onOpen:x,onClose:I,onMessageSend:O,onMessageReceive:k,onError:E,onSessionStart:$,onNewChat:A})=>{const M={...g,...i},[B,U]=n(""),R=a(null),F=a(null),{isOpen:H,setIsOpen:z,toggleOpen:L,isSending:T,isLoading:D,offline:q,messages:P,session:j,error:W,sendMessage:J,newChat:V}=m(o,{origin:l,persistSession:N,maxSessionAge:S,maxStoredMessages:v,autoOpen:y,onMessageSend:O,onMessageReceive:k,onError:E,onSessionStart:$}),K=j?.companyName||j?.assistantName||"Assistant",X=D||q||!j?.sessionId,G=q?"Offline":W&&!j?"Unavailable":j?.companyName||(D?"Connecting…":"Online"),Q=q?"Offline":D?"Connecting…":j?.sessionId?"Type a message…":"Unavailable";t(()=>{y&&z(!0)},[y,z]),t(()=>{H?(x?.(),q||setTimeout(()=>{F.current?.focus()},100)):I?.()},[H,q,x,I]),t(()=>{R.current?.scrollIntoView({behavior:"smooth"})},[P,T,D]);const Y=r(()=>{!B.trim()||T||X||(J(B),U(""))},[X,B,T,J]),Z=r(e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),Y())},[Y]),_=r(()=>{V(),A?.()},[V,A]),ee="small"===M.buttonSize?"44px":"large"===M.buttonSize?"64px":"56px",se=e("button",{className:`passline-chat-button ${M.buttonClassName} ${u}`,onClick:L,style:{backgroundColor:j?.logoUrl?void 0:H?M.primaryHoverColor:M.buttonBackgroundColor,color:M.buttonIconColor,borderRadius:M.buttonBorderRadius,width:ee,height:ee},"aria-label":H?"Close chat":"Open chat",children:e(f,{logoUrl:j?.logoUrl,label:K,fallback:"icon",className:"passline-chat-button-mark"})}),ne=s("div",{className:`passline-chat-header ${M.headerClassName}`,children:[e(f,{logoUrl:j?.logoUrl,label:K,className:"passline-header-avatar",backgroundColor:M.primaryColor}),s("div",{className:"passline-header-info",children:[e("div",{className:"passline-header-title",children:j?.assistantName||"Assistant"}),e("div",{className:"passline-header-subtitle",children:G})]}),e("button",{className:"passline-header-newchat",onClick:_,title:"Start new chat","aria-label":"Start new chat",style:{color:M.textColor},children:e("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e("path",{d:"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z"})})})]}),ae=s("div",{className:`passline-chat-input-container ${M.inputClassName}`,children:[e("input",{ref:F,type:"text",className:"passline-chat-input",value:B,onChange:e=>U(e.target.value),onKeyDown:Z,placeholder:Q,disabled:X,autoComplete:"off",style:{borderColor:M.borderColor,borderRadius:M.borderRadius,backgroundColor:M.backgroundColor,color:M.textColor,fontFamily:M.fontFamily,fontSize:M.fontSize}}),e("button",{className:"passline-chat-send",onClick:Y,disabled:X||T||!B.trim(),style:{backgroundColor:M.primaryColor,borderRadius:M.borderRadius,color:"#ffffff"},children:"Send"})]}),te=s("div",{className:`passline-chat-panel ${M.panelClassName} ${d}`,role:"dialog","aria-label":"Passline chat",style:{width:M.panelWidth,height:M.panelHeight,boxShadow:M.panelShadow,backgroundColor:M.backgroundColor,borderRadius:M.borderRadius,fontFamily:M.fontFamily,fontSize:M.fontSize,color:M.textColor},children:[b?b({title:j?.assistantName||"Assistant",subtitle:G,avatar:p(K),onNewChat:_}):ne,s("div",{className:`passline-chat-messages ${M.messagesClassName}`,children:[D&&0===P.length?e("p",{className:"passline-chat-meta",children:"Connecting…"}):null,P.map((s,n)=>C?C({message:s,className:""}):((s,n)=>e("div",{className:"passline-message "+("user"===s.role?"message-user":"message-assistant"),style:{backgroundColor:"user"===s.role?M.userBubbleColor:M.assistantBubbleColor,color:"user"===s.role?M.userBubbleTextColor:M.assistantBubbleTextColor,borderRadius:M.borderRadius,border:"assistant"===s.role?`${M.borderWidth} solid ${M.borderColor}`:"none"},children:s.content},s.id||n))(s,n)),T?e("p",{className:"passline-chat-typing",children:"Assistant is typing…"}):null,W?e("p",{className:"passline-chat-error",children:W}):null,e("div",{ref:R})]}),w?w({value:B,onChange:U,onSend:Y,disabled:X||T,placeholder:Q,className:""}):ae]});return s("div",{className:`passline-chat-wrapper ${M.className} ${c}`,children:[H&&te,h?h({onClick:L,isOpen:H,className:""}):se]})};export{c as PasslineApi,o as PasslineApiError,h as PasslineChat,l as isOfflineError,i as isSessionInvalidError,m as usePasslineChat,d as usePasslineSession};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/api/passlineApi.ts","../src/hooks/usePasslineSession.ts","../src/hooks/usePasslineClient.ts","../src/component/PasslineChat.tsx"],"sourcesContent":["import { PasslineSession } from \"../component/types\";\n\nexport type ChatbotSessionResponse = {\n sessionId: string;\n greeting: string;\n assistantName: string;\n companyName: string | null;\n logoUrl: string | null;\n};\n\nexport type ChatbotMessageResponse = {\n reply: string;\n};\n\nexport class PasslineApiError extends Error {\n readonly code?: string;\n readonly status?: number;\n\n constructor(message: string, options?: { code?: string; status?: number }) {\n super(message);\n this.name = \"PasslineApiError\";\n this.code = options?.code;\n this.status = options?.status;\n }\n}\n\nexport function isOfflineError(err: unknown): boolean {\n if (!err) return false;\n if (err instanceof PasslineApiError && err.code === \"OFFLINE\") return true;\n const message = err instanceof Error ? err.message : \"\";\n return /offline|failed to fetch|networkerror|load failed|request failed \\(5\\d\\d\\)/i.test(\n message,\n );\n}\n\nexport function isSessionInvalidError(err: unknown): boolean {\n const message = err instanceof Error ? err.message.toLowerCase() : \"\";\n // Booking/calendar 404s (\"credentials not found\") must not wipe the chat.\n return /session/.test(message) && /(not found|expired|invalid)/.test(message);\n}\n\nexport class PasslineApi {\n private token: string;\n private origin: string;\n\n constructor(token: string, origin?: string) {\n this.token = token;\n this.origin = (origin || \"https://api.passline.ai\").replace(/\\/$/, \"\");\n }\n\n private getHeaders(): HeadersInit {\n return {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"X-Passline-Embed-Token\": this.token,\n };\n }\n\n private async request<T>(\n path: string,\n body: Record<string, unknown> = {},\n ): Promise<T> {\n const url = `${this.origin}/api/chatbot/${path.replace(/^\\//, \"\")}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: this.getHeaders(),\n body: JSON.stringify(body),\n cache: \"no-store\",\n });\n } catch {\n throw new PasslineApiError(\"Offline\", { code: \"OFFLINE\" });\n }\n\n const payload = (await response.json().catch(() => null)) as\n | (T & { message?: string })\n | null;\n\n if (!response.ok) {\n if (response.status >= 500 || response.status === 0) {\n throw new PasslineApiError(\"Offline\", {\n code: \"OFFLINE\",\n status: response.status,\n });\n }\n throw new PasslineApiError(\n payload?.message || `Request failed (${response.status})`,\n { status: response.status },\n );\n }\n\n return payload as T;\n }\n\n async createSession(): Promise<ChatbotSessionResponse> {\n return this.request<ChatbotSessionResponse>(\"session\", {});\n }\n\n async sendMessage(\n sessionId: string,\n message: string,\n ): Promise<ChatbotMessageResponse> {\n return this.request<ChatbotMessageResponse>(\n `session/${encodeURIComponent(sessionId)}/messages`,\n { message },\n );\n }\n}\n\nexport function toPasslineSession(\n response: ChatbotSessionResponse,\n messages: PasslineSession[\"messages\"] = [],\n): PasslineSession {\n return {\n sessionId: response.sessionId,\n assistantName: response.assistantName || \"Assistant\",\n companyName: response.companyName ?? null,\n logoUrl: response.logoUrl ?? null,\n greeting: response.greeting,\n messages,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\nimport {\n isOfflineError,\n isSessionInvalidError,\n PasslineApi,\n toPasslineSession,\n} from \"../api/passlineApi\";\n\nconst DEFAULT_GREETING = \"Hi! How can I help?\";\n\ninterface StoredSession {\n sessionId: string;\n assistantName?: string;\n companyName?: string | null;\n logoUrl?: string | null;\n messages: PasslineMessage[];\n updatedAt: number;\n}\n\nfunction nextMessageId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport function usePasslineSession(\n token: string,\n origin?: string,\n persistSession: boolean = true,\n maxSessionAge: number = 7 * 24 * 60 * 60 * 1000,\n maxStoredMessages: number = 200,\n) {\n const [session, setSession] = useState<PasslineSession | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [offline, setOffline] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const apiRef = useRef(new PasslineApi(token, origin));\n const sessionRef = useRef<PasslineSession | null>(null);\n const bootIdRef = useRef(0);\n const storageKey = `passline:chat:${token}`;\n\n sessionRef.current = session;\n\n useEffect(() => {\n apiRef.current = new PasslineApi(token, origin);\n }, [token, origin]);\n\n const readStoredSession = useCallback((): StoredSession | null => {\n if (!persistSession || typeof window === \"undefined\") return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = JSON.parse(raw) as StoredSession;\n if (!parsed?.sessionId || !Array.isArray(parsed.messages)) {\n return null;\n }\n\n if (Date.now() - (parsed.updatedAt || 0) > maxSessionAge) {\n window.localStorage.removeItem(storageKey);\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n }, [maxSessionAge, persistSession, storageKey]);\n\n const writeStoredSession = useCallback(\n (data: StoredSession) => {\n if (!persistSession || typeof window === \"undefined\") return;\n\n try {\n const messages =\n data.messages.length > maxStoredMessages\n ? data.messages.slice(data.messages.length - maxStoredMessages)\n : data.messages;\n window.localStorage.setItem(\n storageKey,\n JSON.stringify({ ...data, messages }),\n );\n } catch {\n // Private browsing or quota exceeded — degrade to in-memory only.\n }\n },\n [maxStoredMessages, persistSession, storageKey],\n );\n\n const clearStoredSession = useCallback(() => {\n if (!persistSession || typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(storageKey);\n } catch {\n // ignore\n }\n }, [persistSession, storageKey]);\n\n const persistFromSession = useCallback(\n (next: PasslineSession) => {\n writeStoredSession({\n sessionId: next.sessionId,\n assistantName: next.assistantName,\n companyName: next.companyName,\n logoUrl: next.logoUrl,\n messages: next.messages,\n updatedAt: Date.now(),\n });\n },\n [writeStoredSession],\n );\n\n const goOffline = useCallback(() => {\n setOffline(true);\n setSession(null);\n sessionRef.current = null;\n setError(\"Offline\");\n setIsLoading(false);\n }, []);\n\n const restoreCachedSession = useCallback((): PasslineSession | null => {\n const stored = readStoredSession();\n if (!stored) return null;\n\n const restored: PasslineSession = {\n sessionId: stored.sessionId,\n assistantName: stored.assistantName || \"Assistant\",\n companyName: stored.companyName ?? null,\n logoUrl: stored.logoUrl ?? null,\n messages: stored.messages,\n };\n setSession(restored);\n sessionRef.current = restored;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n return restored;\n }, [readStoredSession]);\n\n const createSession = useCallback(\n async (forceNew: boolean = false): Promise<PasslineSession> => {\n const bootId = ++bootIdRef.current;\n setError(null);\n\n if (!forceNew) {\n const restored = restoreCachedSession();\n if (restored) return restored;\n }\n\n try {\n setIsLoading(true);\n const response = await apiRef.current.createSession();\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n\n const greeting = response.greeting || DEFAULT_GREETING;\n const next = toPasslineSession(response, [\n {\n id: \"greeting\",\n role: \"assistant\",\n content: greeting,\n timestamp: Date.now(),\n },\n ]);\n\n setSession(next);\n sessionRef.current = next;\n setOffline(false);\n persistFromSession(next);\n return next;\n } catch (err) {\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n const errorMessage =\n err instanceof Error ? err.message : \"Unable to start chat\";\n setError(errorMessage);\n setSession(null);\n sessionRef.current = null;\n throw new Error(errorMessage);\n } finally {\n if (bootIdRef.current === bootId) {\n setIsLoading(false);\n }\n }\n },\n [goOffline, persistFromSession, restoreCachedSession],\n );\n\n const ensureSession = useCallback(async (): Promise<PasslineSession | null> => {\n if (offline) return null;\n if (sessionRef.current?.sessionId) return sessionRef.current;\n try {\n return await createSession(false);\n } catch {\n return null;\n }\n }, [createSession, offline]);\n\n const sendMessage = useCallback(\n async (content: string): Promise<PasslineMessage> => {\n const active = sessionRef.current;\n if (!active?.sessionId) {\n throw new Error(\"No active session\");\n }\n\n const userMessage: PasslineMessage = {\n id: nextMessageId(\"u\"),\n role: \"user\",\n content,\n timestamp: Date.now(),\n };\n\n const withUser: PasslineSession = {\n ...active,\n messages: [...active.messages, userMessage],\n };\n setSession(withUser);\n sessionRef.current = withUser;\n persistFromSession(withUser);\n\n try {\n const response = await apiRef.current.sendMessage(\n active.sessionId,\n content,\n );\n\n const assistantMessage: PasslineMessage = {\n id: nextMessageId(\"a\"),\n role: \"assistant\",\n content: response.reply || \"\",\n timestamp: Date.now(),\n };\n\n const latest = sessionRef.current ?? withUser;\n const withAssistant: PasslineSession = {\n ...latest,\n messages: [...latest.messages, assistantMessage],\n };\n setSession(withAssistant);\n sessionRef.current = withAssistant;\n persistFromSession(withAssistant);\n return assistantMessage;\n } catch (err) {\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n if (isSessionInvalidError(err)) {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n await createSession(true);\n throw new Error(\"Session expired. Please try your message again.\");\n }\n throw err;\n }\n },\n [clearStoredSession, createSession, goOffline, persistFromSession],\n );\n\n const newChat = useCallback(async () => {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n return createSession(true);\n }, [clearStoredSession, createSession]);\n\n useEffect(() => {\n bootIdRef.current += 1;\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n restoreCachedSession();\n }, [restoreCachedSession, token]);\n\n return {\n session,\n messages: session?.messages || [],\n isLoading,\n offline,\n error,\n sendMessage,\n newChat,\n createSession,\n ensureSession,\n clearStoredSession,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { usePasslineSession } from \"./usePasslineSession\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\n\nexport function usePasslineChat(\n token: string,\n options?: {\n origin?: string;\n persistSession?: boolean;\n maxSessionAge?: number;\n maxStoredMessages?: number;\n autoOpen?: boolean;\n onMessageSend?: (message: string) => void;\n onMessageReceive?: (message: PasslineMessage) => void;\n onError?: (error: Error) => void;\n onSessionStart?: (session: PasslineSession) => void;\n },\n) {\n const [isOpen, setIsOpen] = useState(Boolean(options?.autoOpen));\n const [isSending, setIsSending] = useState(false);\n const lastSessionIdRef = useRef<string | null>(null);\n\n const {\n session,\n messages,\n isLoading,\n offline,\n error,\n sendMessage: sendMessageApi,\n newChat: newChatApi,\n ensureSession,\n } = usePasslineSession(\n token,\n options?.origin,\n options?.persistSession,\n options?.maxSessionAge,\n options?.maxStoredMessages,\n );\n\n useEffect(() => {\n if (options?.autoOpen) {\n setIsOpen(true);\n }\n }, [options?.autoOpen]);\n\n useEffect(() => {\n lastSessionIdRef.current = null;\n }, [token]);\n\n useEffect(() => {\n if (!isOpen || offline) {\n return;\n }\n void ensureSession();\n }, [ensureSession, isOpen, offline, token]);\n\n useEffect(() => {\n if (!session?.sessionId || session.sessionId === lastSessionIdRef.current) {\n return;\n }\n lastSessionIdRef.current = session.sessionId;\n options?.onSessionStart?.(session);\n }, [options, session]);\n\n useEffect(() => {\n if (error && options?.onError) {\n options.onError(new Error(error));\n }\n }, [error, options]);\n\n const sendMessage = useCallback(\n async (content: string) => {\n if (!content.trim() || isSending || isLoading || offline) return;\n\n try {\n setIsSending(true);\n options?.onMessageSend?.(content);\n const response = await sendMessageApi(content);\n options?.onMessageReceive?.(response);\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to send message\"),\n );\n } finally {\n setIsSending(false);\n }\n },\n [isLoading, isSending, offline, options, sendMessageApi],\n );\n\n const newChat = useCallback(async () => {\n try {\n await newChatApi();\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to start new chat\"),\n );\n }\n }, [newChatApi, options]);\n\n const toggleOpen = useCallback(() => {\n setIsOpen((prev) => !prev);\n }, []);\n\n return {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n };\n}\n","import React, { useState, useRef, useEffect, useCallback } from \"react\";\nimport { usePasslineChat } from \"../hooks/usePasslineClient\";\nimport { PasslineChatProps, PasslineMessage, PasslineTheme } from \"./types\";\nimport \"./PasslineChat.css\";\n\nconst CHAT_ICON =\n \"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z\";\n\nconst defaultTheme: Required<PasslineTheme> = {\n primaryColor: \"#0f766e\",\n primaryHoverColor: \"#0d9488\",\n backgroundColor: \"#f8fafc\",\n textColor: \"#0f172a\",\n userBubbleColor: \"#0f766e\",\n userBubbleTextColor: \"#ffffff\",\n assistantBubbleColor: \"#ffffff\",\n assistantBubbleTextColor: \"#1e293b\",\n borderRadius: \"16px\",\n borderWidth: \"1px\",\n borderColor: \"#e2e8f0\",\n buttonSize: \"medium\",\n buttonBorderRadius: \"999px\",\n buttonBackgroundColor: \"#0f766e\",\n buttonHoverColor: \"#0d9488\",\n buttonIconColor: \"#ffffff\",\n panelWidth: \"380px\",\n panelHeight: \"560px\",\n panelShadow: \"0 12px 40px rgba(0,0,0,.22)\",\n fontFamily:\n \"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n fontSize: \"14px\",\n spacing: \"12px\",\n padding: \"12px\",\n className: \"\",\n buttonClassName: \"\",\n panelClassName: \"\",\n headerClassName: \"\",\n messagesClassName: \"\",\n inputClassName: \"\",\n};\n\nfunction brandingInitials(label?: string | null): string {\n const parts = String(label || \"\")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n if (parts.length === 0) return \"A\";\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0].charAt(0) + parts[1].charAt(0)).toUpperCase();\n}\n\nfunction BrandMark({\n logoUrl,\n label,\n fallback = \"initials\",\n className,\n backgroundColor,\n}: {\n logoUrl?: string | null;\n label: string;\n fallback?: \"initials\" | \"icon\";\n className: string;\n backgroundColor?: string;\n}) {\n const [broken, setBroken] = useState(false);\n\n useEffect(() => {\n setBroken(false);\n }, [logoUrl]);\n\n const showLogo = Boolean(logoUrl) && !broken;\n\n return (\n <div\n className={`${className}${showLogo ? \" passline-has-logo\" : \"\"}`}\n style={showLogo ? undefined : { backgroundColor }}\n >\n {showLogo ? (\n <img\n src={logoUrl ?? \"\"}\n alt={label || \"Organization logo\"}\n className=\"passline-chat-avatar-img\"\n onError={() => setBroken(true)}\n />\n ) : fallback === \"icon\" ? (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d={CHAT_ICON} />\n </svg>\n ) : (\n brandingInitials(label)\n )}\n </div>\n );\n}\n\nexport const PasslineChat: React.FC<PasslineChatProps> = ({\n token,\n origin,\n theme = {},\n className = \"\",\n buttonClassName = \"\",\n panelClassName = \"\",\n renderButton,\n renderHeader,\n renderMessage,\n renderInput,\n autoOpen = false,\n persistSession = true,\n maxSessionAge,\n maxStoredMessages,\n onOpen,\n onClose,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n onNewChat,\n}) => {\n const mergedTheme = { ...defaultTheme, ...theme };\n const [inputValue, setInputValue] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n } = usePasslineChat(token, {\n origin,\n persistSession,\n maxSessionAge,\n maxStoredMessages,\n autoOpen,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n });\n\n const brandLabel =\n session?.companyName || session?.assistantName || \"Assistant\";\n const inputLocked = isLoading || offline || !session?.sessionId;\n const subtitle = offline\n ? \"Offline\"\n : error && !session\n ? \"Unavailable\"\n : session?.companyName || (isLoading ? \"Connecting…\" : \"Online\");\n const placeholder = offline\n ? \"Offline\"\n : isLoading\n ? \"Connecting…\"\n : session?.sessionId\n ? \"Type a message…\"\n : \"Unavailable\";\n\n useEffect(() => {\n if (autoOpen) {\n setIsOpen(true);\n }\n }, [autoOpen, setIsOpen]);\n\n useEffect(() => {\n if (isOpen) {\n onOpen?.();\n if (!offline) {\n setTimeout(() => {\n inputRef.current?.focus();\n }, 100);\n }\n } else {\n onClose?.();\n }\n }, [isOpen, offline, onOpen, onClose]);\n\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages, isSending, isLoading]);\n\n const handleSend = useCallback(() => {\n if (inputValue.trim() && !isSending && !inputLocked) {\n sendMessage(inputValue);\n setInputValue(\"\");\n }\n }, [inputLocked, inputValue, isSending, sendMessage]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n const handleNewChat = useCallback(() => {\n newChat();\n onNewChat?.();\n }, [newChat, onNewChat]);\n\n const buttonSizePx =\n mergedTheme.buttonSize === \"small\"\n ? \"44px\"\n : mergedTheme.buttonSize === \"large\"\n ? \"64px\"\n : \"56px\";\n\n const defaultButton = (\n <button\n className={`passline-chat-button ${mergedTheme.buttonClassName} ${buttonClassName}`}\n onClick={toggleOpen}\n style={{\n backgroundColor: session?.logoUrl\n ? undefined\n : isOpen\n ? mergedTheme.primaryHoverColor\n : mergedTheme.buttonBackgroundColor,\n color: mergedTheme.buttonIconColor,\n borderRadius: mergedTheme.buttonBorderRadius,\n width: buttonSizePx,\n height: buttonSizePx,\n }}\n aria-label={isOpen ? \"Close chat\" : \"Open chat\"}\n >\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n fallback=\"icon\"\n className=\"passline-chat-button-mark\"\n />\n </button>\n );\n\n const defaultMessage = (message: PasslineMessage, index: number) => (\n <div\n key={message.id || index}\n className={`passline-message ${message.role === \"user\" ? \"message-user\" : \"message-assistant\"}`}\n style={{\n backgroundColor:\n message.role === \"user\"\n ? mergedTheme.userBubbleColor\n : mergedTheme.assistantBubbleColor,\n color:\n message.role === \"user\"\n ? mergedTheme.userBubbleTextColor\n : mergedTheme.assistantBubbleTextColor,\n borderRadius: mergedTheme.borderRadius,\n border:\n message.role === \"assistant\"\n ? `${mergedTheme.borderWidth} solid ${mergedTheme.borderColor}`\n : \"none\",\n }}\n >\n {message.content}\n </div>\n );\n\n const defaultHeader = (\n <div className={`passline-chat-header ${mergedTheme.headerClassName}`}>\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n className=\"passline-header-avatar\"\n backgroundColor={mergedTheme.primaryColor}\n />\n <div className=\"passline-header-info\">\n <div className=\"passline-header-title\">\n {session?.assistantName || \"Assistant\"}\n </div>\n <div className=\"passline-header-subtitle\">{subtitle}</div>\n </div>\n <button\n className=\"passline-header-newchat\"\n onClick={handleNewChat}\n title=\"Start new chat\"\n aria-label=\"Start new chat\"\n style={{\n color: mergedTheme.textColor,\n }}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z\" />\n </svg>\n </button>\n </div>\n );\n\n const defaultInput = (\n <div\n className={`passline-chat-input-container ${mergedTheme.inputClassName}`}\n >\n <input\n ref={inputRef}\n type=\"text\"\n className=\"passline-chat-input\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={inputLocked}\n autoComplete=\"off\"\n style={{\n borderColor: mergedTheme.borderColor,\n borderRadius: mergedTheme.borderRadius,\n backgroundColor: mergedTheme.backgroundColor,\n color: mergedTheme.textColor,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n }}\n />\n <button\n className=\"passline-chat-send\"\n onClick={handleSend}\n disabled={inputLocked || isSending || !inputValue.trim()}\n style={{\n backgroundColor: mergedTheme.primaryColor,\n borderRadius: mergedTheme.borderRadius,\n color: \"#ffffff\",\n }}\n >\n Send\n </button>\n </div>\n );\n\n const defaultPanel = (\n <div\n className={`passline-chat-panel ${mergedTheme.panelClassName} ${panelClassName}`}\n role=\"dialog\"\n aria-label=\"Passline chat\"\n style={{\n width: mergedTheme.panelWidth,\n height: mergedTheme.panelHeight,\n boxShadow: mergedTheme.panelShadow,\n backgroundColor: mergedTheme.backgroundColor,\n borderRadius: mergedTheme.borderRadius,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n color: mergedTheme.textColor,\n }}\n >\n {renderHeader\n ? renderHeader({\n title: session?.assistantName || \"Assistant\",\n subtitle,\n avatar: brandingInitials(brandLabel),\n onNewChat: handleNewChat,\n })\n : defaultHeader}\n\n <div className={`passline-chat-messages ${mergedTheme.messagesClassName}`}>\n {isLoading && messages.length === 0 ? (\n <p className=\"passline-chat-meta\">Connecting…</p>\n ) : null}\n\n {messages.map((msg, index) =>\n renderMessage\n ? renderMessage({ message: msg, className: \"\" })\n : defaultMessage(msg, index),\n )}\n\n {isSending ? (\n <p className=\"passline-chat-typing\">Assistant is typing…</p>\n ) : null}\n\n {error ? <p className=\"passline-chat-error\">{error}</p> : null}\n <div ref={messagesEndRef} />\n </div>\n\n {renderInput\n ? renderInput({\n value: inputValue,\n onChange: setInputValue,\n onSend: handleSend,\n disabled: inputLocked || isSending,\n placeholder,\n className: \"\",\n })\n : defaultInput}\n </div>\n );\n\n return (\n <div\n className={`passline-chat-wrapper ${mergedTheme.className} ${className}`}\n >\n {isOpen && defaultPanel}\n {renderButton\n ? renderButton({ onClick: toggleOpen, isOpen, className: \"\" })\n : defaultButton}\n </div>\n );\n};\n"],"names":["PasslineApiError","Error","constructor","message","options","super","this","name","code","status","isOfflineError","err","test","isSessionInvalidError","toLowerCase","PasslineApi","token","origin","replace","getHeaders","Accept","request","path","body","url","response","fetch","method","headers","JSON","stringify","cache","payload","json","catch","ok","createSession","sendMessage","sessionId","encodeURIComponent","nextMessageId","prefix","Date","now","Math","random","toString","slice","usePasslineSession","persistSession","maxSessionAge","maxStoredMessages","session","setSession","useState","isLoading","setIsLoading","offline","setOffline","error","setError","apiRef","useRef","sessionRef","bootIdRef","storageKey","current","useEffect","readStoredSession","useCallback","window","raw","localStorage","getItem","parsed","parse","Array","isArray","messages","updatedAt","removeItem","writeStoredSession","data","length","setItem","clearStoredSession","persistFromSession","next","assistantName","companyName","logoUrl","goOffline","restoreCachedSession","stored","restored","async","forceNew","bootId","greeting","toPasslineSession","id","role","content","timestamp","errorMessage","ensureSession","active","userMessage","withUser","assistantMessage","reply","latest","withAssistant","newChat","usePasslineChat","isOpen","setIsOpen","Boolean","autoOpen","isSending","setIsSending","lastSessionIdRef","sendMessageApi","newChatApi","onSessionStart","onError","trim","onMessageSend","onMessageReceive","toggleOpen","prev","defaultTheme","primaryColor","primaryHoverColor","backgroundColor","textColor","userBubbleColor","userBubbleTextColor","assistantBubbleColor","assistantBubbleTextColor","borderRadius","borderWidth","borderColor","buttonSize","buttonBorderRadius","buttonBackgroundColor","buttonHoverColor","buttonIconColor","panelWidth","panelHeight","panelShadow","fontFamily","fontSize","spacing","padding","className","buttonClassName","panelClassName","headerClassName","messagesClassName","inputClassName","brandingInitials","label","parts","String","split","filter","toUpperCase","charAt","BrandMark","fallback","broken","setBroken","showLogo","_jsx","style","undefined","src","alt","viewBox","fill","d","PasslineChat","theme","renderButton","renderHeader","renderMessage","renderInput","onOpen","onClose","onNewChat","mergedTheme","inputValue","setInputValue","messagesEndRef","inputRef","brandLabel","inputLocked","subtitle","placeholder","setTimeout","focus","scrollIntoView","behavior","handleSend","handleKeyDown","e","key","shiftKey","preventDefault","handleNewChat","buttonSizePx","defaultButton","onClick","color","width","height","children","defaultHeader","_jsxs","title","defaultInput","ref","type","value","onChange","target","onKeyDown","disabled","autoComplete","defaultPanel","boxShadow","avatar","map","msg","index","border","defaultMessage","onSend"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var e=require("react/jsx-runtime"),s=require("react");class a extends Error{constructor(e,s){super(e),this.name="PasslineApiError",this.code=s?.code,this.status=s?.status}}function n(e){if(!e)return!1;if(e instanceof a&&"OFFLINE"===e.code)return!0;const s=e instanceof Error?e.message:"";return/offline|failed to fetch|networkerror|load failed|request failed \(5\d\d\)/i.test(s)}function t(e){const s=e instanceof Error?e.message.toLowerCase():"";return/session/.test(s)&&/(not found|expired|invalid)/.test(s)}class r{constructor(e,s){this.token=e,this.origin=(s||"https://api.passline.ai").replace(/\/$/,"")}getHeaders(){return{Accept:"application/json","Content-Type":"application/json","X-Passline-Embed-Token":this.token}}async request(e,s={}){const n=`${this.origin}/api/chatbot/${e.replace(/^\//,"")}`;let t;try{t=await fetch(n,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(s),cache:"no-store"})}catch{throw new a("Offline",{code:"OFFLINE"})}const r=await t.json().catch(()=>null);if(!t.ok){if(t.status>=500||0===t.status)throw new a("Offline",{code:"OFFLINE",status:t.status});throw new a(r?.message||`Request failed (${t.status})`,{status:t.status})}return r}async createSession(){return this.request("session",{})}async sendMessage(e,s){return this.request(`session/${encodeURIComponent(e)}/messages`,{message:s})}}function o(e){return`${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function l(e,a,l=!0,i=6048e5,c=200){const[u,d]=s.useState(null),[f,m]=s.useState(!1),[g,p]=s.useState(!1),[h,b]=s.useState(null),C=s.useRef(new r(e,a)),w=s.useRef(null),x=s.useRef(0),y=`passline:chat:${e}`;w.current=u,s.useEffect(()=>{C.current=new r(e,a)},[e,a]);const N=s.useCallback(()=>{if(!l||"undefined"==typeof window)return null;try{const e=window.localStorage.getItem(y);if(!e)return null;const s=JSON.parse(e);return s?.sessionId&&Array.isArray(s.messages)?Date.now()-(s.updatedAt||0)>i?(window.localStorage.removeItem(y),null):s:null}catch{return null}},[i,l,y]),S=s.useCallback(e=>{if(l&&"undefined"!=typeof window)try{const s=e.messages.length>c?e.messages.slice(e.messages.length-c):e.messages;window.localStorage.setItem(y,JSON.stringify({...e,messages:s}))}catch{}},[c,l,y]),k=s.useCallback(()=>{if(l&&"undefined"!=typeof window)try{window.localStorage.removeItem(y)}catch{}},[l,y]),v=s.useCallback(e=>{S({sessionId:e.sessionId,assistantName:e.assistantName,companyName:e.companyName,logoUrl:e.logoUrl,messages:e.messages,updatedAt:Date.now()})},[S]),E=s.useCallback(()=>{p(!0),d(null),w.current=null,b("Offline"),m(!1)},[]),I=s.useCallback(()=>{const e=N();if(!e)return null;const s={sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,messages:e.messages};return d(s),w.current=s,p(!1),b(null),m(!1),s},[N]),O=s.useCallback(async(e=!1)=>{const s=++x.current;if(b(null),!e){const e=I();if(e)return e}try{m(!0);const e=await C.current.createSession();if(x.current!==s){const e=w.current;if(e)return e}const a=function(e,s=[]){return{sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,greeting:e.greeting,messages:s}}(e,[{id:"greeting",role:"assistant",content:e.greeting||"Hi! How can I help?",timestamp:Date.now()}]);return d(a),w.current=a,p(!1),v(a),a}catch(e){if(x.current!==s){const e=w.current;if(e)return e}if(n(e))throw E(),new Error("Offline");const a=e instanceof Error?e.message:"Unable to start chat";throw b(a),d(null),w.current=null,new Error(a)}finally{x.current===s&&m(!1)}},[E,v,I]),j=s.useCallback(async()=>{if(g)return null;if(w.current?.sessionId)return w.current;try{return await O(!1)}catch{return null}},[O,g]),R=s.useCallback(async e=>{const s=w.current;if(!s?.sessionId)throw new Error("No active session");const a={id:o("u"),role:"user",content:e,timestamp:Date.now()},r={...s,messages:[...s.messages,a]};d(r),w.current=r,v(r);try{const a=await C.current.sendMessage(s.sessionId,e),n={id:o("a"),role:"assistant",content:a.reply||"",timestamp:Date.now()},t=w.current??r,l={...t,messages:[...t.messages,n]};return d(l),w.current=l,v(l),n}catch(e){if(n(e))throw E(),new Error("Offline");if(t(e))throw k(),d(null),w.current=null,await O(!0),new Error("Session expired. Please try your message again.");throw e}},[k,O,E,v]),A=s.useCallback(async()=>(k(),d(null),w.current=null,p(!1),O(!0)),[k,O]);return s.useEffect(()=>{x.current+=1,d(null),w.current=null,p(!1),b(null),m(!1),I()},[I,e]),{session:u,messages:u?.messages||[],isLoading:f,offline:g,error:h,sendMessage:R,newChat:A,createSession:O,ensureSession:j,clearStoredSession:k}}function i(e,a){const[n,t]=s.useState(Boolean(a?.autoOpen)),[r,o]=s.useState(!1),i=s.useRef(null),{session:c,messages:u,isLoading:d,offline:f,error:m,sendMessage:g,newChat:p,ensureSession:h}=l(e,a?.origin,a?.persistSession,a?.maxSessionAge,a?.maxStoredMessages);s.useEffect(()=>{a?.autoOpen&&t(!0)},[a?.autoOpen]),s.useEffect(()=>{i.current=null},[e]),s.useEffect(()=>{n&&!f&&h()},[h,n,f,e]),s.useEffect(()=>{c?.sessionId&&c.sessionId!==i.current&&(i.current=c.sessionId,a?.onSessionStart?.(c))},[a,c]),s.useEffect(()=>{m&&a?.onError&&a.onError(new Error(m))},[m,a]);const b=s.useCallback(async e=>{if(e.trim()&&!r&&!d&&!f)try{o(!0),a?.onMessageSend?.(e);const s=await g(e);a?.onMessageReceive?.(s)}catch(e){a?.onError?.(e instanceof Error?e:new Error("Failed to send message"))}finally{o(!1)}},[d,r,f,a,g]),C=s.useCallback(async()=>{try{await p()}catch(e){a?.onError?.(e instanceof Error?e:new Error("Failed to start new chat"))}},[p,a]),w=s.useCallback(()=>{t(e=>!e)},[]);return{isOpen:n,setIsOpen:t,toggleOpen:w,isSending:r,isLoading:d,offline:f,messages:u,session:c,error:m,sendMessage:b,newChat:C}}const c={primaryColor:"#0f766e",primaryHoverColor:"#0d9488",backgroundColor:"#f8fafc",textColor:"#0f172a",userBubbleColor:"#0f766e",userBubbleTextColor:"#ffffff",assistantBubbleColor:"#ffffff",assistantBubbleTextColor:"#1e293b",borderRadius:"16px",borderWidth:"1px",borderColor:"#e2e8f0",buttonSize:"medium",buttonBorderRadius:"999px",buttonBackgroundColor:"#0f766e",buttonHoverColor:"#0d9488",buttonIconColor:"#ffffff",panelWidth:"380px",panelHeight:"560px",panelShadow:"0 12px 40px rgba(0,0,0,.22)",fontFamily:"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",fontSize:"14px",spacing:"12px",padding:"12px",className:"",buttonClassName:"",panelClassName:"",headerClassName:"",messagesClassName:"",inputClassName:""};function u(e){const s=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===s.length?"A":1===s.length?s[0].slice(0,2).toUpperCase():(s[0].charAt(0)+s[1].charAt(0)).toUpperCase()}function d({logoUrl:a,label:n,fallback:t="initials",className:r,backgroundColor:o}){const[l,i]=s.useState(!1);s.useEffect(()=>{i(!1)},[a]);const c=Boolean(a)&&!l;return e.jsx("div",{className:`${r}${c?" passline-has-logo":""}`,style:c?void 0:{backgroundColor:o},children:c?e.jsx("img",{src:a??"",alt:n||"Organization logo",className:"passline-chat-avatar-img",onError:()=>i(!0)}):"icon"===t?e.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"})}):u(n)})}exports.PasslineApi=r,exports.PasslineApiError=a,exports.PasslineChat=({token:a,origin:n,theme:t={},className:r="",buttonClassName:o="",panelClassName:l="",renderButton:f,renderHeader:m,renderMessage:g,renderInput:p,autoOpen:h=!1,persistSession:b=!0,maxSessionAge:C,maxStoredMessages:w,onOpen:x,onClose:y,onMessageSend:N,onMessageReceive:S,onError:k,onSessionStart:v,onNewChat:E})=>{const I={...c,...t},[O,j]=s.useState(""),R=s.useRef(null),A=s.useRef(null),{isOpen:$,setIsOpen:M,toggleOpen:B,isSending:U,isLoading:F,offline:H,messages:z,session:P,error:L,sendMessage:T,newChat:D}=i(a,{origin:n,persistSession:b,maxSessionAge:C,maxStoredMessages:w,autoOpen:h,onMessageSend:N,onMessageReceive:S,onError:k,onSessionStart:v}),q=P?.companyName||P?.assistantName||"Assistant",W=F||H||!P?.sessionId,J=H?"Offline":L&&!P?"Unavailable":P?.companyName||(F?"Connecting…":"Online"),V=H?"Offline":F?"Connecting…":P?.sessionId?"Type a message…":"Unavailable";s.useEffect(()=>{h&&M(!0)},[h,M]),s.useEffect(()=>{$?(x?.(),H||setTimeout(()=>{A.current?.focus()},100)):y?.()},[$,H,x,y]),s.useEffect(()=>{R.current?.scrollIntoView({behavior:"smooth"})},[z,U,F]);const K=s.useCallback(()=>{!O.trim()||U||W||(T(O),j(""))},[W,O,U,T]),X=s.useCallback(e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),K())},[K]),G=s.useCallback(()=>{D(),E?.()},[D,E]),Q="small"===I.buttonSize?"44px":"large"===I.buttonSize?"64px":"56px",Y=e.jsx("button",{className:`passline-chat-button ${I.buttonClassName} ${o}`,onClick:B,style:{backgroundColor:P?.logoUrl?void 0:$?I.primaryHoverColor:I.buttonBackgroundColor,color:I.buttonIconColor,borderRadius:I.buttonBorderRadius,width:Q,height:Q},"aria-label":$?"Close chat":"Open chat",children:e.jsx(d,{logoUrl:P?.logoUrl,label:q,fallback:"icon",className:"passline-chat-button-mark"})}),Z=e.jsxs("div",{className:`passline-chat-header ${I.headerClassName}`,children:[e.jsx(d,{logoUrl:P?.logoUrl,label:q,className:"passline-header-avatar",backgroundColor:I.primaryColor}),e.jsxs("div",{className:"passline-header-info",children:[e.jsx("div",{className:"passline-header-title",children:P?.assistantName||"Assistant"}),e.jsx("div",{className:"passline-header-subtitle",children:J})]}),e.jsx("button",{className:"passline-header-newchat",onClick:G,title:"Start new chat","aria-label":"Start new chat",style:{color:I.textColor},children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z"})})})]}),_=e.jsxs("div",{className:`passline-chat-input-container ${I.inputClassName}`,children:[e.jsx("input",{ref:A,type:"text",className:"passline-chat-input",value:O,onChange:e=>j(e.target.value),onKeyDown:X,placeholder:V,disabled:W,autoComplete:"off",style:{borderColor:I.borderColor,borderRadius:I.borderRadius,backgroundColor:I.backgroundColor,color:I.textColor,fontFamily:I.fontFamily,fontSize:I.fontSize}}),e.jsx("button",{className:"passline-chat-send",onClick:K,disabled:W||U||!O.trim(),style:{backgroundColor:I.primaryColor,borderRadius:I.borderRadius,color:"#ffffff"},children:"Send"})]}),ee=e.jsxs("div",{className:`passline-chat-panel ${I.panelClassName} ${l}`,role:"dialog","aria-label":"Passline chat",style:{width:I.panelWidth,height:I.panelHeight,boxShadow:I.panelShadow,backgroundColor:I.backgroundColor,borderRadius:I.borderRadius,fontFamily:I.fontFamily,fontSize:I.fontSize,color:I.textColor},children:[m?m({title:P?.assistantName||"Assistant",subtitle:J,avatar:u(q),onNewChat:G}):Z,e.jsxs("div",{className:`passline-chat-messages ${I.messagesClassName}`,children:[F&&0===z.length?e.jsx("p",{className:"passline-chat-meta",children:"Connecting…"}):null,z.map((s,a)=>g?g({message:s,className:""}):((s,a)=>e.jsx("div",{className:"passline-message "+("user"===s.role?"message-user":"message-assistant"),style:{backgroundColor:"user"===s.role?I.userBubbleColor:I.assistantBubbleColor,color:"user"===s.role?I.userBubbleTextColor:I.assistantBubbleTextColor,borderRadius:I.borderRadius,border:"assistant"===s.role?`${I.borderWidth} solid ${I.borderColor}`:"none"},children:s.content},s.id||a))(s,a)),U?e.jsx("p",{className:"passline-chat-typing",children:"Assistant is typing…"}):null,L?e.jsx("p",{className:"passline-chat-error",children:L}):null,e.jsx("div",{ref:R})]}),p?p({value:O,onChange:j,onSend:K,disabled:W||U,placeholder:V,className:""}):_]});return e.jsxs("div",{className:`passline-chat-wrapper ${I.className} ${r}`,children:[$&&ee,f?f({onClick:B,isOpen:$,className:""}):Y]})},exports.isOfflineError=n,exports.isSessionInvalidError=t,exports.usePasslineChat=i,exports.usePasslineSession=l;
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/api/passlineApi.ts","../src/hooks/usePasslineSession.ts","../src/hooks/usePasslineClient.ts","../src/component/PasslineChat.tsx"],"sourcesContent":["import { PasslineSession } from \"../component/types\";\n\nexport type ChatbotSessionResponse = {\n sessionId: string;\n greeting: string;\n assistantName: string;\n companyName: string | null;\n logoUrl: string | null;\n};\n\nexport type ChatbotMessageResponse = {\n reply: string;\n};\n\nexport class PasslineApiError extends Error {\n readonly code?: string;\n readonly status?: number;\n\n constructor(message: string, options?: { code?: string; status?: number }) {\n super(message);\n this.name = \"PasslineApiError\";\n this.code = options?.code;\n this.status = options?.status;\n }\n}\n\nexport function isOfflineError(err: unknown): boolean {\n if (!err) return false;\n if (err instanceof PasslineApiError && err.code === \"OFFLINE\") return true;\n const message = err instanceof Error ? err.message : \"\";\n return /offline|failed to fetch|networkerror|load failed|request failed \\(5\\d\\d\\)/i.test(\n message,\n );\n}\n\nexport function isSessionInvalidError(err: unknown): boolean {\n const message = err instanceof Error ? err.message.toLowerCase() : \"\";\n // Booking/calendar 404s (\"credentials not found\") must not wipe the chat.\n return /session/.test(message) && /(not found|expired|invalid)/.test(message);\n}\n\nexport class PasslineApi {\n private token: string;\n private origin: string;\n\n constructor(token: string, origin?: string) {\n this.token = token;\n this.origin = (origin || \"https://api.passline.ai\").replace(/\\/$/, \"\");\n }\n\n private getHeaders(): HeadersInit {\n return {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"X-Passline-Embed-Token\": this.token,\n };\n }\n\n private async request<T>(\n path: string,\n body: Record<string, unknown> = {},\n ): Promise<T> {\n const url = `${this.origin}/api/chatbot/${path.replace(/^\\//, \"\")}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: this.getHeaders(),\n body: JSON.stringify(body),\n cache: \"no-store\",\n });\n } catch {\n throw new PasslineApiError(\"Offline\", { code: \"OFFLINE\" });\n }\n\n const payload = (await response.json().catch(() => null)) as\n | (T & { message?: string })\n | null;\n\n if (!response.ok) {\n if (response.status >= 500 || response.status === 0) {\n throw new PasslineApiError(\"Offline\", {\n code: \"OFFLINE\",\n status: response.status,\n });\n }\n throw new PasslineApiError(\n payload?.message || `Request failed (${response.status})`,\n { status: response.status },\n );\n }\n\n return payload as T;\n }\n\n async createSession(): Promise<ChatbotSessionResponse> {\n return this.request<ChatbotSessionResponse>(\"session\", {});\n }\n\n async sendMessage(\n sessionId: string,\n message: string,\n ): Promise<ChatbotMessageResponse> {\n return this.request<ChatbotMessageResponse>(\n `session/${encodeURIComponent(sessionId)}/messages`,\n { message },\n );\n }\n}\n\nexport function toPasslineSession(\n response: ChatbotSessionResponse,\n messages: PasslineSession[\"messages\"] = [],\n): PasslineSession {\n return {\n sessionId: response.sessionId,\n assistantName: response.assistantName || \"Assistant\",\n companyName: response.companyName ?? null,\n logoUrl: response.logoUrl ?? null,\n greeting: response.greeting,\n messages,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\nimport {\n isOfflineError,\n isSessionInvalidError,\n PasslineApi,\n toPasslineSession,\n} from \"../api/passlineApi\";\n\nconst DEFAULT_GREETING = \"Hi! How can I help?\";\n\ninterface StoredSession {\n sessionId: string;\n assistantName?: string;\n companyName?: string | null;\n logoUrl?: string | null;\n messages: PasslineMessage[];\n updatedAt: number;\n}\n\nfunction nextMessageId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport function usePasslineSession(\n token: string,\n origin?: string,\n persistSession: boolean = true,\n maxSessionAge: number = 7 * 24 * 60 * 60 * 1000,\n maxStoredMessages: number = 200,\n) {\n const [session, setSession] = useState<PasslineSession | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [offline, setOffline] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const apiRef = useRef(new PasslineApi(token, origin));\n const sessionRef = useRef<PasslineSession | null>(null);\n const bootIdRef = useRef(0);\n const storageKey = `passline:chat:${token}`;\n\n sessionRef.current = session;\n\n useEffect(() => {\n apiRef.current = new PasslineApi(token, origin);\n }, [token, origin]);\n\n const readStoredSession = useCallback((): StoredSession | null => {\n if (!persistSession || typeof window === \"undefined\") return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = JSON.parse(raw) as StoredSession;\n if (!parsed?.sessionId || !Array.isArray(parsed.messages)) {\n return null;\n }\n\n if (Date.now() - (parsed.updatedAt || 0) > maxSessionAge) {\n window.localStorage.removeItem(storageKey);\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n }, [maxSessionAge, persistSession, storageKey]);\n\n const writeStoredSession = useCallback(\n (data: StoredSession) => {\n if (!persistSession || typeof window === \"undefined\") return;\n\n try {\n const messages =\n data.messages.length > maxStoredMessages\n ? data.messages.slice(data.messages.length - maxStoredMessages)\n : data.messages;\n window.localStorage.setItem(\n storageKey,\n JSON.stringify({ ...data, messages }),\n );\n } catch {\n // Private browsing or quota exceeded — degrade to in-memory only.\n }\n },\n [maxStoredMessages, persistSession, storageKey],\n );\n\n const clearStoredSession = useCallback(() => {\n if (!persistSession || typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(storageKey);\n } catch {\n // ignore\n }\n }, [persistSession, storageKey]);\n\n const persistFromSession = useCallback(\n (next: PasslineSession) => {\n writeStoredSession({\n sessionId: next.sessionId,\n assistantName: next.assistantName,\n companyName: next.companyName,\n logoUrl: next.logoUrl,\n messages: next.messages,\n updatedAt: Date.now(),\n });\n },\n [writeStoredSession],\n );\n\n const goOffline = useCallback(() => {\n setOffline(true);\n setSession(null);\n sessionRef.current = null;\n setError(\"Offline\");\n setIsLoading(false);\n }, []);\n\n const restoreCachedSession = useCallback((): PasslineSession | null => {\n const stored = readStoredSession();\n if (!stored) return null;\n\n const restored: PasslineSession = {\n sessionId: stored.sessionId,\n assistantName: stored.assistantName || \"Assistant\",\n companyName: stored.companyName ?? null,\n logoUrl: stored.logoUrl ?? null,\n messages: stored.messages,\n };\n setSession(restored);\n sessionRef.current = restored;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n return restored;\n }, [readStoredSession]);\n\n const createSession = useCallback(\n async (forceNew: boolean = false): Promise<PasslineSession> => {\n const bootId = ++bootIdRef.current;\n setError(null);\n\n if (!forceNew) {\n const restored = restoreCachedSession();\n if (restored) return restored;\n }\n\n try {\n setIsLoading(true);\n const response = await apiRef.current.createSession();\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n\n const greeting = response.greeting || DEFAULT_GREETING;\n const next = toPasslineSession(response, [\n {\n id: \"greeting\",\n role: \"assistant\",\n content: greeting,\n timestamp: Date.now(),\n },\n ]);\n\n setSession(next);\n sessionRef.current = next;\n setOffline(false);\n persistFromSession(next);\n return next;\n } catch (err) {\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n const errorMessage =\n err instanceof Error ? err.message : \"Unable to start chat\";\n setError(errorMessage);\n setSession(null);\n sessionRef.current = null;\n throw new Error(errorMessage);\n } finally {\n if (bootIdRef.current === bootId) {\n setIsLoading(false);\n }\n }\n },\n [goOffline, persistFromSession, restoreCachedSession],\n );\n\n const ensureSession = useCallback(async (): Promise<PasslineSession | null> => {\n if (offline) return null;\n if (sessionRef.current?.sessionId) return sessionRef.current;\n try {\n return await createSession(false);\n } catch {\n return null;\n }\n }, [createSession, offline]);\n\n const sendMessage = useCallback(\n async (content: string): Promise<PasslineMessage> => {\n const active = sessionRef.current;\n if (!active?.sessionId) {\n throw new Error(\"No active session\");\n }\n\n const userMessage: PasslineMessage = {\n id: nextMessageId(\"u\"),\n role: \"user\",\n content,\n timestamp: Date.now(),\n };\n\n const withUser: PasslineSession = {\n ...active,\n messages: [...active.messages, userMessage],\n };\n setSession(withUser);\n sessionRef.current = withUser;\n persistFromSession(withUser);\n\n try {\n const response = await apiRef.current.sendMessage(\n active.sessionId,\n content,\n );\n\n const assistantMessage: PasslineMessage = {\n id: nextMessageId(\"a\"),\n role: \"assistant\",\n content: response.reply || \"\",\n timestamp: Date.now(),\n };\n\n const latest = sessionRef.current ?? withUser;\n const withAssistant: PasslineSession = {\n ...latest,\n messages: [...latest.messages, assistantMessage],\n };\n setSession(withAssistant);\n sessionRef.current = withAssistant;\n persistFromSession(withAssistant);\n return assistantMessage;\n } catch (err) {\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n if (isSessionInvalidError(err)) {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n await createSession(true);\n throw new Error(\"Session expired. Please try your message again.\");\n }\n throw err;\n }\n },\n [clearStoredSession, createSession, goOffline, persistFromSession],\n );\n\n const newChat = useCallback(async () => {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n return createSession(true);\n }, [clearStoredSession, createSession]);\n\n useEffect(() => {\n bootIdRef.current += 1;\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n restoreCachedSession();\n }, [restoreCachedSession, token]);\n\n return {\n session,\n messages: session?.messages || [],\n isLoading,\n offline,\n error,\n sendMessage,\n newChat,\n createSession,\n ensureSession,\n clearStoredSession,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { usePasslineSession } from \"./usePasslineSession\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\n\nexport function usePasslineChat(\n token: string,\n options?: {\n origin?: string;\n persistSession?: boolean;\n maxSessionAge?: number;\n maxStoredMessages?: number;\n autoOpen?: boolean;\n onMessageSend?: (message: string) => void;\n onMessageReceive?: (message: PasslineMessage) => void;\n onError?: (error: Error) => void;\n onSessionStart?: (session: PasslineSession) => void;\n },\n) {\n const [isOpen, setIsOpen] = useState(Boolean(options?.autoOpen));\n const [isSending, setIsSending] = useState(false);\n const lastSessionIdRef = useRef<string | null>(null);\n\n const {\n session,\n messages,\n isLoading,\n offline,\n error,\n sendMessage: sendMessageApi,\n newChat: newChatApi,\n ensureSession,\n } = usePasslineSession(\n token,\n options?.origin,\n options?.persistSession,\n options?.maxSessionAge,\n options?.maxStoredMessages,\n );\n\n useEffect(() => {\n if (options?.autoOpen) {\n setIsOpen(true);\n }\n }, [options?.autoOpen]);\n\n useEffect(() => {\n lastSessionIdRef.current = null;\n }, [token]);\n\n useEffect(() => {\n if (!isOpen || offline) {\n return;\n }\n void ensureSession();\n }, [ensureSession, isOpen, offline, token]);\n\n useEffect(() => {\n if (!session?.sessionId || session.sessionId === lastSessionIdRef.current) {\n return;\n }\n lastSessionIdRef.current = session.sessionId;\n options?.onSessionStart?.(session);\n }, [options, session]);\n\n useEffect(() => {\n if (error && options?.onError) {\n options.onError(new Error(error));\n }\n }, [error, options]);\n\n const sendMessage = useCallback(\n async (content: string) => {\n if (!content.trim() || isSending || isLoading || offline) return;\n\n try {\n setIsSending(true);\n options?.onMessageSend?.(content);\n const response = await sendMessageApi(content);\n options?.onMessageReceive?.(response);\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to send message\"),\n );\n } finally {\n setIsSending(false);\n }\n },\n [isLoading, isSending, offline, options, sendMessageApi],\n );\n\n const newChat = useCallback(async () => {\n try {\n await newChatApi();\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to start new chat\"),\n );\n }\n }, [newChatApi, options]);\n\n const toggleOpen = useCallback(() => {\n setIsOpen((prev) => !prev);\n }, []);\n\n return {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n };\n}\n","import React, { useState, useRef, useEffect, useCallback } from \"react\";\nimport { usePasslineChat } from \"../hooks/usePasslineClient\";\nimport { PasslineChatProps, PasslineMessage, PasslineTheme } from \"./types\";\nimport \"./PasslineChat.css\";\n\nconst CHAT_ICON =\n \"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z\";\n\nconst defaultTheme: Required<PasslineTheme> = {\n primaryColor: \"#0f766e\",\n primaryHoverColor: \"#0d9488\",\n backgroundColor: \"#f8fafc\",\n textColor: \"#0f172a\",\n userBubbleColor: \"#0f766e\",\n userBubbleTextColor: \"#ffffff\",\n assistantBubbleColor: \"#ffffff\",\n assistantBubbleTextColor: \"#1e293b\",\n borderRadius: \"16px\",\n borderWidth: \"1px\",\n borderColor: \"#e2e8f0\",\n buttonSize: \"medium\",\n buttonBorderRadius: \"999px\",\n buttonBackgroundColor: \"#0f766e\",\n buttonHoverColor: \"#0d9488\",\n buttonIconColor: \"#ffffff\",\n panelWidth: \"380px\",\n panelHeight: \"560px\",\n panelShadow: \"0 12px 40px rgba(0,0,0,.22)\",\n fontFamily:\n \"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n fontSize: \"14px\",\n spacing: \"12px\",\n padding: \"12px\",\n className: \"\",\n buttonClassName: \"\",\n panelClassName: \"\",\n headerClassName: \"\",\n messagesClassName: \"\",\n inputClassName: \"\",\n};\n\nfunction brandingInitials(label?: string | null): string {\n const parts = String(label || \"\")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n if (parts.length === 0) return \"A\";\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0].charAt(0) + parts[1].charAt(0)).toUpperCase();\n}\n\nfunction BrandMark({\n logoUrl,\n label,\n fallback = \"initials\",\n className,\n backgroundColor,\n}: {\n logoUrl?: string | null;\n label: string;\n fallback?: \"initials\" | \"icon\";\n className: string;\n backgroundColor?: string;\n}) {\n const [broken, setBroken] = useState(false);\n\n useEffect(() => {\n setBroken(false);\n }, [logoUrl]);\n\n const showLogo = Boolean(logoUrl) && !broken;\n\n return (\n <div\n className={`${className}${showLogo ? \" passline-has-logo\" : \"\"}`}\n style={showLogo ? undefined : { backgroundColor }}\n >\n {showLogo ? (\n <img\n src={logoUrl ?? \"\"}\n alt={label || \"Organization logo\"}\n className=\"passline-chat-avatar-img\"\n onError={() => setBroken(true)}\n />\n ) : fallback === \"icon\" ? (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d={CHAT_ICON} />\n </svg>\n ) : (\n brandingInitials(label)\n )}\n </div>\n );\n}\n\nexport const PasslineChat: React.FC<PasslineChatProps> = ({\n token,\n origin,\n theme = {},\n className = \"\",\n buttonClassName = \"\",\n panelClassName = \"\",\n renderButton,\n renderHeader,\n renderMessage,\n renderInput,\n autoOpen = false,\n persistSession = true,\n maxSessionAge,\n maxStoredMessages,\n onOpen,\n onClose,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n onNewChat,\n}) => {\n const mergedTheme = { ...defaultTheme, ...theme };\n const [inputValue, setInputValue] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n } = usePasslineChat(token, {\n origin,\n persistSession,\n maxSessionAge,\n maxStoredMessages,\n autoOpen,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n });\n\n const brandLabel =\n session?.companyName || session?.assistantName || \"Assistant\";\n const inputLocked = isLoading || offline || !session?.sessionId;\n const subtitle = offline\n ? \"Offline\"\n : error && !session\n ? \"Unavailable\"\n : session?.companyName || (isLoading ? \"Connecting…\" : \"Online\");\n const placeholder = offline\n ? \"Offline\"\n : isLoading\n ? \"Connecting…\"\n : session?.sessionId\n ? \"Type a message…\"\n : \"Unavailable\";\n\n useEffect(() => {\n if (autoOpen) {\n setIsOpen(true);\n }\n }, [autoOpen, setIsOpen]);\n\n useEffect(() => {\n if (isOpen) {\n onOpen?.();\n if (!offline) {\n setTimeout(() => {\n inputRef.current?.focus();\n }, 100);\n }\n } else {\n onClose?.();\n }\n }, [isOpen, offline, onOpen, onClose]);\n\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages, isSending, isLoading]);\n\n const handleSend = useCallback(() => {\n if (inputValue.trim() && !isSending && !inputLocked) {\n sendMessage(inputValue);\n setInputValue(\"\");\n }\n }, [inputLocked, inputValue, isSending, sendMessage]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n const handleNewChat = useCallback(() => {\n newChat();\n onNewChat?.();\n }, [newChat, onNewChat]);\n\n const buttonSizePx =\n mergedTheme.buttonSize === \"small\"\n ? \"44px\"\n : mergedTheme.buttonSize === \"large\"\n ? \"64px\"\n : \"56px\";\n\n const defaultButton = (\n <button\n className={`passline-chat-button ${mergedTheme.buttonClassName} ${buttonClassName}`}\n onClick={toggleOpen}\n style={{\n backgroundColor: session?.logoUrl\n ? undefined\n : isOpen\n ? mergedTheme.primaryHoverColor\n : mergedTheme.buttonBackgroundColor,\n color: mergedTheme.buttonIconColor,\n borderRadius: mergedTheme.buttonBorderRadius,\n width: buttonSizePx,\n height: buttonSizePx,\n }}\n aria-label={isOpen ? \"Close chat\" : \"Open chat\"}\n >\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n fallback=\"icon\"\n className=\"passline-chat-button-mark\"\n />\n </button>\n );\n\n const defaultMessage = (message: PasslineMessage, index: number) => (\n <div\n key={message.id || index}\n className={`passline-message ${message.role === \"user\" ? \"message-user\" : \"message-assistant\"}`}\n style={{\n backgroundColor:\n message.role === \"user\"\n ? mergedTheme.userBubbleColor\n : mergedTheme.assistantBubbleColor,\n color:\n message.role === \"user\"\n ? mergedTheme.userBubbleTextColor\n : mergedTheme.assistantBubbleTextColor,\n borderRadius: mergedTheme.borderRadius,\n border:\n message.role === \"assistant\"\n ? `${mergedTheme.borderWidth} solid ${mergedTheme.borderColor}`\n : \"none\",\n }}\n >\n {message.content}\n </div>\n );\n\n const defaultHeader = (\n <div className={`passline-chat-header ${mergedTheme.headerClassName}`}>\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n className=\"passline-header-avatar\"\n backgroundColor={mergedTheme.primaryColor}\n />\n <div className=\"passline-header-info\">\n <div className=\"passline-header-title\">\n {session?.assistantName || \"Assistant\"}\n </div>\n <div className=\"passline-header-subtitle\">{subtitle}</div>\n </div>\n <button\n className=\"passline-header-newchat\"\n onClick={handleNewChat}\n title=\"Start new chat\"\n aria-label=\"Start new chat\"\n style={{\n color: mergedTheme.textColor,\n }}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z\" />\n </svg>\n </button>\n </div>\n );\n\n const defaultInput = (\n <div\n className={`passline-chat-input-container ${mergedTheme.inputClassName}`}\n >\n <input\n ref={inputRef}\n type=\"text\"\n className=\"passline-chat-input\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={inputLocked}\n autoComplete=\"off\"\n style={{\n borderColor: mergedTheme.borderColor,\n borderRadius: mergedTheme.borderRadius,\n backgroundColor: mergedTheme.backgroundColor,\n color: mergedTheme.textColor,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n }}\n />\n <button\n className=\"passline-chat-send\"\n onClick={handleSend}\n disabled={inputLocked || isSending || !inputValue.trim()}\n style={{\n backgroundColor: mergedTheme.primaryColor,\n borderRadius: mergedTheme.borderRadius,\n color: \"#ffffff\",\n }}\n >\n Send\n </button>\n </div>\n );\n\n const defaultPanel = (\n <div\n className={`passline-chat-panel ${mergedTheme.panelClassName} ${panelClassName}`}\n role=\"dialog\"\n aria-label=\"Passline chat\"\n style={{\n width: mergedTheme.panelWidth,\n height: mergedTheme.panelHeight,\n boxShadow: mergedTheme.panelShadow,\n backgroundColor: mergedTheme.backgroundColor,\n borderRadius: mergedTheme.borderRadius,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n color: mergedTheme.textColor,\n }}\n >\n {renderHeader\n ? renderHeader({\n title: session?.assistantName || \"Assistant\",\n subtitle,\n avatar: brandingInitials(brandLabel),\n onNewChat: handleNewChat,\n })\n : defaultHeader}\n\n <div className={`passline-chat-messages ${mergedTheme.messagesClassName}`}>\n {isLoading && messages.length === 0 ? (\n <p className=\"passline-chat-meta\">Connecting…</p>\n ) : null}\n\n {messages.map((msg, index) =>\n renderMessage\n ? renderMessage({ message: msg, className: \"\" })\n : defaultMessage(msg, index),\n )}\n\n {isSending ? (\n <p className=\"passline-chat-typing\">Assistant is typing…</p>\n ) : null}\n\n {error ? <p className=\"passline-chat-error\">{error}</p> : null}\n <div ref={messagesEndRef} />\n </div>\n\n {renderInput\n ? renderInput({\n value: inputValue,\n onChange: setInputValue,\n onSend: handleSend,\n disabled: inputLocked || isSending,\n placeholder,\n className: \"\",\n })\n : defaultInput}\n </div>\n );\n\n return (\n <div\n className={`passline-chat-wrapper ${mergedTheme.className} ${className}`}\n >\n {isOpen && defaultPanel}\n {renderButton\n ? renderButton({ onClick: toggleOpen, isOpen, className: \"\" })\n : defaultButton}\n </div>\n );\n};\n"],"names":["PasslineApiError","Error","constructor","message","options","super","this","name","code","status","isOfflineError","err","test","isSessionInvalidError","toLowerCase","PasslineApi","token","origin","replace","getHeaders","Accept","request","path","body","url","response","fetch","method","headers","JSON","stringify","cache","payload","json","catch","ok","createSession","sendMessage","sessionId","encodeURIComponent","nextMessageId","prefix","Date","now","Math","random","toString","slice","usePasslineSession","persistSession","maxSessionAge","maxStoredMessages","session","setSession","useState","isLoading","setIsLoading","offline","setOffline","error","setError","apiRef","useRef","sessionRef","bootIdRef","storageKey","current","useEffect","readStoredSession","useCallback","window","raw","localStorage","getItem","parsed","parse","Array","isArray","messages","updatedAt","removeItem","writeStoredSession","data","length","setItem","clearStoredSession","persistFromSession","next","assistantName","companyName","logoUrl","goOffline","restoreCachedSession","stored","restored","async","forceNew","bootId","greeting","toPasslineSession","id","role","content","timestamp","errorMessage","ensureSession","active","userMessage","withUser","assistantMessage","reply","latest","withAssistant","newChat","usePasslineChat","isOpen","setIsOpen","Boolean","autoOpen","isSending","setIsSending","lastSessionIdRef","sendMessageApi","newChatApi","onSessionStart","onError","trim","onMessageSend","onMessageReceive","toggleOpen","prev","defaultTheme","primaryColor","primaryHoverColor","backgroundColor","textColor","userBubbleColor","userBubbleTextColor","assistantBubbleColor","assistantBubbleTextColor","borderRadius","borderWidth","borderColor","buttonSize","buttonBorderRadius","buttonBackgroundColor","buttonHoverColor","buttonIconColor","panelWidth","panelHeight","panelShadow","fontFamily","fontSize","spacing","padding","className","buttonClassName","panelClassName","headerClassName","messagesClassName","inputClassName","brandingInitials","label","parts","String","split","filter","toUpperCase","charAt","BrandMark","fallback","broken","setBroken","showLogo","_jsx","style","undefined","src","alt","viewBox","fill","d","theme","renderButton","renderHeader","renderMessage","renderInput","onOpen","onClose","onNewChat","mergedTheme","inputValue","setInputValue","messagesEndRef","inputRef","brandLabel","inputLocked","subtitle","placeholder","setTimeout","focus","scrollIntoView","behavior","handleSend","handleKeyDown","e","key","shiftKey","preventDefault","handleNewChat","buttonSizePx","defaultButton","onClick","color","width","height","children","defaultHeader","_jsxs","title","defaultInput","ref","type","value","onChange","target","onKeyDown","disabled","autoComplete","defaultPanel","boxShadow","avatar","map","msg","index","border","defaultMessage","onSend"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "passline-ai",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Passline chat widget for React and Next.js applications",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "passline.ai",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.esm.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"sideEffects": [
|
|
11
|
+
"./dist/index.js",
|
|
12
|
+
"./dist/index.esm.js"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.esm.js",
|
|
18
|
+
"require": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.esm.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "rollup -c",
|
|
29
|
+
"prepack": "npm run build",
|
|
30
|
+
"pack:check": "npm pack --dry-run",
|
|
31
|
+
"dev": "vite --config demo/vite.config.mts",
|
|
32
|
+
"preview": "vite preview --config demo/vite.config.mts",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": ">=18.0.0",
|
|
43
|
+
"react-dom": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@rollup/plugin-commonjs": "^29.0.3",
|
|
47
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
48
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
49
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
50
|
+
"@types/react": "^19.2.18",
|
|
51
|
+
"@types/react-dom": "^19.2.7",
|
|
52
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
53
|
+
"react": "^19.2.8",
|
|
54
|
+
"react-dom": "^19.2.8",
|
|
55
|
+
"rollup": "^4.63.1",
|
|
56
|
+
"rollup-plugin-dts": "^6.5.1",
|
|
57
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
58
|
+
"rollup-plugin-postcss": "^4.0.2",
|
|
59
|
+
"tslib": "^2.8.1",
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"vite": "^8.2.2"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"passline",
|
|
65
|
+
"chat",
|
|
66
|
+
"react",
|
|
67
|
+
"nextjs",
|
|
68
|
+
"chatbot"
|
|
69
|
+
],
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "https://github.com/Himexa-Tech/passline-chatbot.git"
|
|
73
|
+
},
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/Himexa-Tech/passline-chatbot/issues"
|
|
76
|
+
},
|
|
77
|
+
"homepage": "https://github.com/Himexa-Tech/passline-chatbot#readme"
|
|
78
|
+
}
|