@asgard-js/react 0.0.41 → 0.0.42-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/chatbot/api-key-input/api-key-input.d.ts +11 -0
- package/dist/components/chatbot/api-key-input/api-key-input.d.ts.map +1 -0
- package/dist/components/chatbot/api-key-input/index.d.ts +2 -0
- package/dist/components/chatbot/api-key-input/index.d.ts.map +1 -0
- package/dist/components/chatbot/chatbot.d.ts +8 -0
- package/dist/components/chatbot/chatbot.d.ts.map +1 -1
- package/dist/context/asgard-service-context.d.ts +5 -0
- package/dist/context/asgard-service-context.d.ts.map +1 -1
- package/dist/hooks/use-channel.d.ts +5 -0
- package/dist/hooks/use-channel.d.ts.map +1 -1
- package/dist/index.js +15076 -14898
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/components/.DS_Store +0 -0
- package/src/components/chatbot/api-key-input/api-key-input.module.scss +192 -0
- package/src/components/chatbot/api-key-input/api-key-input.tsx +119 -0
- package/src/components/chatbot/api-key-input/index.ts +1 -0
- package/src/components/chatbot/chatbot.tsx +133 -33
- package/src/context/asgard-service-context.tsx +3 -0
- package/src/hooks/use-channel.ts +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asgard-js/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.42-canary.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"vitest": "^1.6.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@asgard-js/core": "^0.0.
|
|
57
|
+
"@asgard-js/core": "^0.0.42-canary.1",
|
|
58
58
|
"react": "^18.0.0",
|
|
59
59
|
"react-dom": "^18.0.0"
|
|
60
60
|
},
|
|
Binary file
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
width: 220px;
|
|
3
|
+
background: rgba(45, 45, 45, 0.95);
|
|
4
|
+
border-radius: 12px;
|
|
5
|
+
padding: 24px 20px 20px;
|
|
6
|
+
backdrop-filter: blur(10px);
|
|
7
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
8
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
9
|
+
color: white;
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.header {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
align-items: center;
|
|
17
|
+
margin-bottom: 24px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.icon {
|
|
21
|
+
width: 48px;
|
|
22
|
+
height: 48px;
|
|
23
|
+
margin-bottom: 12px;
|
|
24
|
+
opacity: 0.7;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.title {
|
|
28
|
+
margin: 0;
|
|
29
|
+
font-size: 18px;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
color: white;
|
|
32
|
+
text-align: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.form {
|
|
36
|
+
width: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.inputGroup {
|
|
40
|
+
margin-bottom: 20px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.label {
|
|
44
|
+
display: block;
|
|
45
|
+
font-size: 14px;
|
|
46
|
+
font-weight: 400;
|
|
47
|
+
color: rgba(255, 255, 255, 0.7);
|
|
48
|
+
margin-bottom: 8px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.inputWrapper {
|
|
52
|
+
position: relative;
|
|
53
|
+
width: 100%;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.input {
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 42px;
|
|
59
|
+
padding: 0 40px 0 12px;
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
font-weight: 400;
|
|
62
|
+
color: rgba(255, 255, 255, 0.8);
|
|
63
|
+
background: rgba(255, 255, 255, 0.05);
|
|
64
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
outline: none;
|
|
67
|
+
transition: all 0.2s ease;
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
|
|
70
|
+
&::placeholder {
|
|
71
|
+
color: rgba(255, 255, 255, 0.4);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&:focus {
|
|
75
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
76
|
+
background: rgba(255, 255, 255, 0.08);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&.error {
|
|
80
|
+
border-color: rgba(255, 69, 58, 0.6);
|
|
81
|
+
background: rgba(255, 69, 58, 0.05);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&.disabled {
|
|
85
|
+
opacity: 0.5;
|
|
86
|
+
cursor: not-allowed;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.toggleButton {
|
|
91
|
+
position: absolute;
|
|
92
|
+
right: 8px;
|
|
93
|
+
top: 50%;
|
|
94
|
+
transform: translateY(-50%);
|
|
95
|
+
background: transparent;
|
|
96
|
+
border: none;
|
|
97
|
+
color: rgba(255, 255, 255, 0.5);
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
padding: 4px;
|
|
100
|
+
border-radius: 4px;
|
|
101
|
+
transition: all 0.2s ease;
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
|
|
106
|
+
&:hover:not(:disabled) {
|
|
107
|
+
color: rgba(255, 255, 255, 0.7);
|
|
108
|
+
background: rgba(255, 255, 255, 0.05);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&:disabled {
|
|
112
|
+
opacity: 0.3;
|
|
113
|
+
cursor: not-allowed;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.toggleIcon {
|
|
118
|
+
width: 16px;
|
|
119
|
+
height: 16px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.errorMessage {
|
|
123
|
+
margin-top: 6px;
|
|
124
|
+
font-size: 12px;
|
|
125
|
+
color: rgba(255, 69, 58, 0.8);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.submitButton {
|
|
129
|
+
width: 100%;
|
|
130
|
+
height: 42px;
|
|
131
|
+
background: #5856D6;
|
|
132
|
+
border: none;
|
|
133
|
+
border-radius: 6px;
|
|
134
|
+
color: white;
|
|
135
|
+
font-size: 14px;
|
|
136
|
+
font-weight: 500;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
transition: all 0.2s ease;
|
|
139
|
+
outline: none;
|
|
140
|
+
|
|
141
|
+
&:hover:not(:disabled) {
|
|
142
|
+
background: #4845C7;
|
|
143
|
+
transform: translateY(-1px);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&:active:not(:disabled) {
|
|
147
|
+
transform: translateY(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
&:disabled {
|
|
151
|
+
opacity: 0.5;
|
|
152
|
+
cursor: not-allowed;
|
|
153
|
+
transform: none;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
&.loading {
|
|
157
|
+
position: relative;
|
|
158
|
+
color: transparent;
|
|
159
|
+
|
|
160
|
+
&::after {
|
|
161
|
+
content: '';
|
|
162
|
+
position: absolute;
|
|
163
|
+
top: 50%;
|
|
164
|
+
left: 50%;
|
|
165
|
+
width: 16px;
|
|
166
|
+
height: 16px;
|
|
167
|
+
margin-left: -8px;
|
|
168
|
+
margin-top: -8px;
|
|
169
|
+
border: 2px solid transparent;
|
|
170
|
+
border-top: 2px solid white;
|
|
171
|
+
border-radius: 50%;
|
|
172
|
+
animation: spin 1s linear infinite;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@keyframes spin {
|
|
178
|
+
0% {
|
|
179
|
+
transform: rotate(0deg);
|
|
180
|
+
}
|
|
181
|
+
100% {
|
|
182
|
+
transform: rotate(360deg);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 響應式設計
|
|
187
|
+
@media (max-width: 280px) {
|
|
188
|
+
.container {
|
|
189
|
+
width: 100%;
|
|
190
|
+
min-width: 200px;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useState, FormEvent, ChangeEvent } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import ProfileSvg from '../../../icons/profile.svg?react';
|
|
4
|
+
import styles from './api-key-input.module.scss';
|
|
5
|
+
|
|
6
|
+
export interface ApiKeyInputProps {
|
|
7
|
+
onSubmit: (apiKey: string) => void | Promise<void>;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
showToggle?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ApiKeyInput({
|
|
17
|
+
onSubmit,
|
|
18
|
+
loading = false,
|
|
19
|
+
error,
|
|
20
|
+
placeholder = 'Enter your key',
|
|
21
|
+
title = 'Preview',
|
|
22
|
+
showToggle = true,
|
|
23
|
+
className,
|
|
24
|
+
}: ApiKeyInputProps) {
|
|
25
|
+
const [apiKey, setApiKey] = useState('');
|
|
26
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
27
|
+
|
|
28
|
+
const handleSubmit = (e: FormEvent) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
if (apiKey.trim() && !loading) {
|
|
31
|
+
onSubmit(apiKey.trim());
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
36
|
+
setApiKey(e.target.value);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const togglePasswordVisibility = () => {
|
|
40
|
+
setShowPassword(!showPassword);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className={clsx(styles.container, className)}>
|
|
45
|
+
<div className={styles.header}>
|
|
46
|
+
<ProfileSvg className={styles.icon} />
|
|
47
|
+
<h2 className={styles.title}>{title}</h2>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<form onSubmit={handleSubmit} className={styles.form}>
|
|
51
|
+
<div className={styles.inputGroup}>
|
|
52
|
+
<label className={styles.label}>Key</label>
|
|
53
|
+
<div className={styles.inputWrapper}>
|
|
54
|
+
<input
|
|
55
|
+
type={showPassword ? 'text' : 'password'}
|
|
56
|
+
value={apiKey}
|
|
57
|
+
onChange={handleInputChange}
|
|
58
|
+
placeholder={placeholder}
|
|
59
|
+
className={clsx(styles.input, {
|
|
60
|
+
[styles.error]: error,
|
|
61
|
+
[styles.disabled]: loading,
|
|
62
|
+
})}
|
|
63
|
+
disabled={loading}
|
|
64
|
+
autoComplete="off"
|
|
65
|
+
/>
|
|
66
|
+
{showToggle && (
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={togglePasswordVisibility}
|
|
70
|
+
className={styles.toggleButton}
|
|
71
|
+
disabled={loading}
|
|
72
|
+
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
|
73
|
+
>
|
|
74
|
+
<svg
|
|
75
|
+
className={styles.toggleIcon}
|
|
76
|
+
width="16"
|
|
77
|
+
height="16"
|
|
78
|
+
viewBox="0 0 24 24"
|
|
79
|
+
fill="none"
|
|
80
|
+
stroke="currentColor"
|
|
81
|
+
strokeWidth="2"
|
|
82
|
+
strokeLinecap="round"
|
|
83
|
+
strokeLinejoin="round"
|
|
84
|
+
>
|
|
85
|
+
{showPassword ? (
|
|
86
|
+
<>
|
|
87
|
+
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
|
|
88
|
+
<line x1="1" y1="1" x2="23" y2="23"/>
|
|
89
|
+
</>
|
|
90
|
+
) : (
|
|
91
|
+
<>
|
|
92
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
|
93
|
+
<circle cx="12" cy="12" r="3"/>
|
|
94
|
+
</>
|
|
95
|
+
)}
|
|
96
|
+
</svg>
|
|
97
|
+
</button>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
{error && (
|
|
101
|
+
<div className={styles.errorMessage}>
|
|
102
|
+
{error}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<button
|
|
108
|
+
type="submit"
|
|
109
|
+
disabled={!apiKey.trim() || loading}
|
|
110
|
+
className={clsx(styles.submitButton, {
|
|
111
|
+
[styles.loading]: loading,
|
|
112
|
+
})}
|
|
113
|
+
>
|
|
114
|
+
{loading ? 'Loading...' : 'Continue'}
|
|
115
|
+
</button>
|
|
116
|
+
</form>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './api-key-input';
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
ForwardedRef,
|
|
4
|
+
ReactNode,
|
|
5
|
+
CSSProperties,
|
|
6
|
+
} from 'react';
|
|
2
7
|
import { ClientConfig, ConversationMessage } from '@asgard-js/core';
|
|
3
8
|
import {
|
|
4
9
|
AsgardThemeContextProvider,
|
|
@@ -12,11 +17,14 @@ import {
|
|
|
12
17
|
AsgardAppInitializationContextProvider,
|
|
13
18
|
AsgardServiceContextProviderProps,
|
|
14
19
|
} from '../../context';
|
|
20
|
+
import { ApiKeyInput } from './api-key-input';
|
|
15
21
|
import { ChatbotHeader } from './chatbot-header';
|
|
16
22
|
import { ChatbotBody } from './chatbot-body';
|
|
17
23
|
import { ChatbotFooter } from './chatbot-footer';
|
|
18
24
|
import { ChatbotContainer } from './chatbot-container/chatbot-container';
|
|
19
25
|
|
|
26
|
+
type AuthState = 'loading' | 'needApiKey' | 'authenticated' | 'error';
|
|
27
|
+
|
|
20
28
|
interface ChatbotProps extends AsgardTemplateContextValue {
|
|
21
29
|
className?: string;
|
|
22
30
|
style?: CSSProperties;
|
|
@@ -38,6 +46,11 @@ interface ChatbotProps extends AsgardTemplateContextValue {
|
|
|
38
46
|
onClose?: () => void;
|
|
39
47
|
loadingComponent?: ReactNode;
|
|
40
48
|
defaultLinkTarget?: '_blank' | '_self' | '_parent' | '_top';
|
|
49
|
+
|
|
50
|
+
// Auth state props
|
|
51
|
+
authState?: AuthState;
|
|
52
|
+
onApiKeySubmit?: (apiKey: string) => Promise<void>;
|
|
53
|
+
onAuthError?: (error: { isAuthError: boolean; isBotProviderError: boolean; errorDetail?: any }) => void;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
export interface ChatbotRef {
|
|
@@ -72,38 +85,65 @@ export const Chatbot = forwardRef(function Chatbot(
|
|
|
72
85
|
className,
|
|
73
86
|
style,
|
|
74
87
|
defaultLinkTarget,
|
|
88
|
+
authState = 'authenticated',
|
|
89
|
+
onApiKeySubmit,
|
|
90
|
+
onAuthError,
|
|
75
91
|
} = props;
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
// Render different content based on authState
|
|
94
|
+
const renderContent = () => {
|
|
95
|
+
switch (authState) {
|
|
96
|
+
case 'loading':
|
|
97
|
+
return (
|
|
98
|
+
<div style={{
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
flex: 1,
|
|
103
|
+
padding: '20px'
|
|
104
|
+
}}>
|
|
105
|
+
{loadingComponent || <div>Loading...</div>}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
case 'needApiKey':
|
|
110
|
+
return (
|
|
111
|
+
<div style={{
|
|
112
|
+
display: 'flex',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
justifyContent: 'center',
|
|
115
|
+
flex: 1,
|
|
116
|
+
padding: '20px'
|
|
117
|
+
}}>
|
|
118
|
+
<ApiKeyInput
|
|
101
119
|
title={title}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
customActions={customActions}
|
|
105
|
-
maintainConnectionWhenClosed={maintainConnectionWhenClosed}
|
|
120
|
+
onSubmit={onApiKeySubmit || (() => {})}
|
|
121
|
+
placeholder="Enter your key"
|
|
106
122
|
/>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
case 'error':
|
|
127
|
+
return (
|
|
128
|
+
<div style={{
|
|
129
|
+
display: 'flex',
|
|
130
|
+
alignItems: 'center',
|
|
131
|
+
justifyContent: 'center',
|
|
132
|
+
flex: 1,
|
|
133
|
+
padding: '20px',
|
|
134
|
+
color: '#ff6b6b'
|
|
135
|
+
}}>
|
|
136
|
+
<div style={{ textAlign: 'center' }}>
|
|
137
|
+
<div style={{ fontSize: '16px', marginBottom: '8px' }}>⚠️</div>
|
|
138
|
+
<div>Something went wrong. Please try again later.</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
case 'authenticated':
|
|
144
|
+
default:
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
107
147
|
<AsgardTemplateContextProvider
|
|
108
148
|
onErrorClick={onErrorClick}
|
|
109
149
|
errorMessageRenderer={errorMessageRenderer}
|
|
@@ -113,9 +153,69 @@ export const Chatbot = forwardRef(function Chatbot(
|
|
|
113
153
|
<ChatbotBody />
|
|
114
154
|
</AsgardTemplateContextProvider>
|
|
115
155
|
<ChatbotFooter />
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Don't initialize SSE connection when explicitly needing API key or in error state
|
|
162
|
+
if (authState !== 'needApiKey' && authState !== 'error') {
|
|
163
|
+
return (
|
|
164
|
+
<AsgardAppInitializationContextProvider
|
|
165
|
+
enabled={enableLoadConfigFromService}
|
|
166
|
+
config={config}
|
|
167
|
+
asyncInitializers={asyncInitializers}
|
|
168
|
+
loadingComponent={loadingComponent}
|
|
169
|
+
>
|
|
170
|
+
<AsgardThemeContextProvider theme={theme}>
|
|
171
|
+
<AsgardServiceContextProvider
|
|
172
|
+
parentRef={ref}
|
|
173
|
+
avatar={avatar}
|
|
174
|
+
config={config}
|
|
175
|
+
customChannelId={customChannelId}
|
|
176
|
+
initMessages={initMessages}
|
|
177
|
+
onSseMessage={onSseMessage}
|
|
178
|
+
onAuthError={onAuthError}
|
|
179
|
+
botTypingPlaceholder={botTypingPlaceholder}
|
|
180
|
+
inputPlaceholder={inputPlaceholder}
|
|
181
|
+
>
|
|
182
|
+
<ChatbotContainer
|
|
183
|
+
fullScreen={fullScreen}
|
|
184
|
+
className={className}
|
|
185
|
+
style={style}
|
|
186
|
+
>
|
|
187
|
+
<ChatbotHeader
|
|
188
|
+
title={title}
|
|
189
|
+
onReset={onReset}
|
|
190
|
+
onClose={onClose}
|
|
191
|
+
customActions={customActions}
|
|
192
|
+
maintainConnectionWhenClosed={maintainConnectionWhenClosed}
|
|
193
|
+
/>
|
|
194
|
+
{renderContent()}
|
|
195
|
+
</ChatbotContainer>
|
|
196
|
+
</AsgardServiceContextProvider>
|
|
197
|
+
</AsgardThemeContextProvider>
|
|
198
|
+
</AsgardAppInitializationContextProvider>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// For non-authenticated states, render without AsgardServiceContextProvider
|
|
203
|
+
return (
|
|
204
|
+
<AsgardThemeContextProvider theme={theme}>
|
|
205
|
+
<ChatbotContainer
|
|
206
|
+
fullScreen={fullScreen}
|
|
207
|
+
className={className}
|
|
208
|
+
style={style}
|
|
209
|
+
>
|
|
210
|
+
<ChatbotHeader
|
|
211
|
+
title={title}
|
|
212
|
+
onReset={onReset}
|
|
213
|
+
onClose={onClose}
|
|
214
|
+
customActions={customActions}
|
|
215
|
+
maintainConnectionWhenClosed={maintainConnectionWhenClosed}
|
|
216
|
+
/>
|
|
217
|
+
{renderContent()}
|
|
218
|
+
</ChatbotContainer>
|
|
219
|
+
</AsgardThemeContextProvider>
|
|
120
220
|
);
|
|
121
221
|
});
|
|
@@ -61,6 +61,7 @@ export interface AsgardServiceContextProviderProps {
|
|
|
61
61
|
delayTime?: number;
|
|
62
62
|
initMessages?: ConversationMessage[];
|
|
63
63
|
onSseMessage?: UseChannelProps['onSseMessage'];
|
|
64
|
+
onAuthError?: (error: { isAuthError: boolean; isBotProviderError: boolean; errorDetail?: any }) => void;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export function AsgardServiceContextProvider(
|
|
@@ -76,6 +77,7 @@ export function AsgardServiceContextProvider(
|
|
|
76
77
|
customChannelId,
|
|
77
78
|
initMessages,
|
|
78
79
|
onSseMessage,
|
|
80
|
+
onAuthError,
|
|
79
81
|
} = props;
|
|
80
82
|
|
|
81
83
|
const messageBoxBottomRef = useRef<HTMLDivElement>(null);
|
|
@@ -95,6 +97,7 @@ export function AsgardServiceContextProvider(
|
|
|
95
97
|
customChannelId,
|
|
96
98
|
initMessages,
|
|
97
99
|
onSseMessage,
|
|
100
|
+
onAuthError,
|
|
98
101
|
});
|
|
99
102
|
|
|
100
103
|
const contextValue = useMemo(
|
package/src/hooks/use-channel.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface UseChannelProps {
|
|
|
23
23
|
conversation: Conversation | null;
|
|
24
24
|
}
|
|
25
25
|
) => void;
|
|
26
|
+
onAuthError?: (error: { isAuthError: boolean; isBotProviderError: boolean; errorDetail?: any }) => void;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface UseChannelReturn {
|
|
@@ -44,6 +45,7 @@ export function useChannel(props: UseChannelProps): UseChannelReturn {
|
|
|
44
45
|
customMessageId,
|
|
45
46
|
initMessages,
|
|
46
47
|
onSseMessage,
|
|
48
|
+
onAuthError,
|
|
47
49
|
} = props;
|
|
48
50
|
|
|
49
51
|
if (!client) {
|
|
@@ -88,8 +90,12 @@ export function useChannel(props: UseChannelProps): UseChannelReturn {
|
|
|
88
90
|
onSseCompleted() {
|
|
89
91
|
setIsResetting(false);
|
|
90
92
|
},
|
|
91
|
-
onSseError() {
|
|
93
|
+
onSseError(error) {
|
|
92
94
|
setIsResetting(false);
|
|
95
|
+
// Handle authentication and bot provider errors
|
|
96
|
+
if (error && typeof error === 'object' && ('isAuthError' in error || 'isBotProviderError' in error)) {
|
|
97
|
+
onAuthError?.(error as any);
|
|
98
|
+
}
|
|
93
99
|
},
|
|
94
100
|
onSseMessage(response: SseResponse<EventType>) {
|
|
95
101
|
onSseMessage?.(response, {
|
|
@@ -102,7 +108,7 @@ export function useChannel(props: UseChannelProps): UseChannelReturn {
|
|
|
102
108
|
setIsOpen(true);
|
|
103
109
|
setChannel(channel);
|
|
104
110
|
},
|
|
105
|
-
[client, customChannelId, customMessageId, initMessages, onSseMessage]
|
|
111
|
+
[client, customChannelId, customMessageId, initMessages, onSseMessage, onAuthError]
|
|
106
112
|
);
|
|
107
113
|
|
|
108
114
|
const closeChannel = useCallback(() => {
|