@asgard-js/react 0.0.43-canary.2 → 0.0.43-canary.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asgard-js/react",
3
- "version": "0.0.43-canary.2",
3
+ "version": "0.0.43-canary.20",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
Binary file
@@ -1,52 +1,51 @@
1
- .container {
1
+ .api_key_input {
2
2
  width: 220px;
3
3
  background-color: var(--asg-color-bg);
4
4
  border-radius: 12px;
5
- padding: 20px;
5
+ padding: 12px 20px;
6
6
  border: 0.5px solid var(--asg-color-border);
7
7
  }
8
8
 
9
- // Header styles removed as header is now handled by ChatbotHeader
10
- // .header {
11
- // display: flex;
12
- // flex-direction: column;
13
- // gap: 4px;
14
- // align-items: center;
15
- // margin-bottom: 12px;
16
- // }
17
- //
18
- // .icon {
19
- // width: 24px;
20
- // height: 24px;
21
- // }
22
- //
23
- // .title {
24
- // margin: 0;
25
- // font-size: 18px;
26
- // font-weight: 500;
27
- // color: white;
28
- // }
29
-
30
- .form {
9
+ .api_key_input__header {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 4px;
13
+ align-items: center;
14
+ margin-bottom: 12px;
15
+ }
16
+
17
+ .api_key_input__icon {
18
+ width: 24px;
19
+ height: 24px;
20
+ }
21
+
22
+ .api_key_input__title {
23
+ margin: 0;
24
+ font-size: 18px;
25
+ font-weight: 500;
26
+ color: white;
27
+ }
28
+
29
+ .api_key_input__form {
31
30
  width: 100%;
32
31
  display: flex;
33
32
  flex-direction: column;
34
33
  gap: 20px;
35
34
  }
36
35
 
37
- .label {
36
+ .api_key_input__label {
38
37
  display: block;
39
38
  font-size: 14px;
40
39
  color: rgba(255, 255, 255, 0.7);
41
40
  margin-bottom: 8px;
42
41
  }
43
42
 
44
- .inputWrapper {
43
+ .api_key_input__input_wrapper {
45
44
  position: relative;
46
45
  width: 100%;
47
46
  }
48
47
 
49
- .input {
48
+ .api_key_input__input {
50
49
  width: 100%;
51
50
  height: 42px;
52
51
  padding: 0 40px 0 12px;
@@ -69,18 +68,18 @@
69
68
  background: rgba(255, 255, 255, 0.08);
70
69
  }
71
70
 
72
- &.error {
71
+ &--error {
73
72
  border-color: rgba(255, 69, 58, 0.6);
74
73
  background: rgba(255, 69, 58, 0.05);
75
74
  }
76
75
 
77
- &.disabled {
76
+ &--disabled {
78
77
  opacity: 0.5;
79
78
  cursor: not-allowed;
80
79
  }
81
80
  }
82
81
 
83
- .toggleButton {
82
+ .api_key_input__toggle_button {
84
83
  position: absolute;
85
84
  right: 8px;
86
85
  top: 50%;
@@ -107,23 +106,23 @@
107
106
  }
108
107
  }
109
108
 
110
- .toggleIcon {
109
+ .api_key_input__toggle_icon {
111
110
  width: 16px;
112
111
  height: 16px;
113
112
  }
114
113
 
115
- .errorMessage {
114
+ .api_key_input__error_message {
116
115
  margin-top: 6px;
117
116
  font-size: 12px;
118
117
  color: rgba(255, 69, 58, 0.8);
119
118
  }
120
119
 
121
- .submitButton {
120
+ .api_key_input__submit_button {
122
121
  width: 100%;
123
122
  height: 42px;
124
- background: #5856d6;
125
123
  border: none;
126
124
  border-radius: 6px;
125
+ background-color: #5856d6;
127
126
  color: white;
128
127
  font-size: 14px;
129
128
  font-weight: 500;
@@ -131,14 +130,6 @@
131
130
  transition: all 0.2s ease;
132
131
  outline: none;
133
132
 
134
- &:hover:not(:disabled) {
135
- background: #4845c7;
136
- transform: translateY(-1px);
137
- }
138
-
139
- &:active:not(:disabled) {
140
- transform: translateY(0);
141
- }
142
133
 
143
134
  &:disabled {
144
135
  opacity: 0.5;
@@ -146,7 +137,7 @@
146
137
  transform: none;
147
138
  }
148
139
 
149
- &.loading {
140
+ &--loading {
150
141
  position: relative;
151
142
  color: transparent;
152
143
 
@@ -178,7 +169,7 @@
178
169
 
179
170
  // 響應式設計
180
171
  @media (max-width: 280px) {
181
- .container {
172
+ .api_key_input {
182
173
  width: 100%;
183
174
  min-width: 200px;
184
175
  }
@@ -1,6 +1,10 @@
1
1
  import { useState, FormEvent, ChangeEvent } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { useAsgardThemeContext } from '../../../context/asgard-theme-context';
4
+ import { useAsgardContext } from '../../../context/asgard-service-context';
5
+ import { ProfileIcon } from '../profile-icon';
6
+ import EyeSvg from '../../../icons/eye.svg?react';
7
+ import EyeOffSvg from '../../../icons/eye-off.svg?react';
4
8
  import styles from './api-key-input.module.scss';
5
9
 
6
10
  export interface ApiKeyInputProps {
@@ -8,6 +12,7 @@ export interface ApiKeyInputProps {
8
12
  loading?: boolean;
9
13
  error?: string;
10
14
  placeholder?: string;
15
+ title?: string;
11
16
  showToggle?: boolean;
12
17
  className?: string;
13
18
  }
@@ -17,12 +22,14 @@ export function ApiKeyInput({
17
22
  loading = false,
18
23
  error,
19
24
  placeholder = 'Enter your key',
25
+ title = 'Preview',
20
26
  showToggle = true,
21
27
  className,
22
28
  }: ApiKeyInputProps): JSX.Element {
23
29
  const [apiKey, setApiKey] = useState('');
24
30
  const [showPassword, setShowPassword] = useState(false);
25
31
  const { chatbot } = useAsgardThemeContext();
32
+ const { avatar } = useAsgardContext();
26
33
 
27
34
  const handleSubmit = (e: FormEvent): void => {
28
35
  e.preventDefault();
@@ -41,24 +48,29 @@ export function ApiKeyInput({
41
48
 
42
49
  return (
43
50
  <div
44
- className={clsx(styles.container, className)}
51
+ className={clsx(styles.api_key_input, className)}
45
52
  style={{
46
53
  backgroundColor: chatbot.backgroundColor,
47
54
  borderColor: chatbot.borderColor,
48
55
  }}
49
56
  >
50
- <form onSubmit={handleSubmit} className={styles.form}>
57
+ <div className={styles.api_key_input__header}>
58
+ <ProfileIcon avatar={avatar} />
59
+ <h2 className={styles.api_key_input__title} style={chatbot?.header?.title?.style}>{title}</h2>
60
+ </div>
61
+
62
+ <form onSubmit={handleSubmit} className={styles.api_key_input__form}>
51
63
  <div className={styles.inputGroup}>
52
- <label className={styles.label}>Key</label>
53
- <div className={styles.inputWrapper}>
64
+ <label className={styles.api_key_input__label}>Key</label>
65
+ <div className={styles.api_key_input__input_wrapper}>
54
66
  <input
55
67
  type={showPassword ? 'text' : 'password'}
56
68
  value={apiKey}
57
69
  onChange={handleInputChange}
58
70
  placeholder={placeholder}
59
- className={clsx(styles.input, {
60
- [styles.error]: error,
61
- [styles.disabled]: loading,
71
+ className={clsx(styles.api_key_input__input, {
72
+ [styles['api_key_input__input--error']]: error,
73
+ [styles['api_key_input__input--disabled']]: loading,
62
74
  })}
63
75
  disabled={loading}
64
76
  autoComplete="off"
@@ -67,45 +79,31 @@ export function ApiKeyInput({
67
79
  <button
68
80
  type="button"
69
81
  onClick={togglePasswordVisibility}
70
- className={styles.toggleButton}
82
+ className={styles.api_key_input__toggle_button}
71
83
  disabled={loading}
72
84
  aria-label={showPassword ? 'Hide password' : 'Show password'}
73
85
  >
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>
86
+ {showPassword ? (
87
+ <EyeOffSvg className={styles.api_key_input__toggle_icon} />
88
+ ) : (
89
+ <EyeSvg className={styles.api_key_input__toggle_icon} />
90
+ )}
97
91
  </button>
98
92
  )}
99
93
  </div>
100
- {error && <div className={styles.errorMessage}>{error}</div>}
94
+ {error && <div className={styles.api_key_input__error_message}>{error}</div>}
101
95
  </div>
102
96
 
103
97
  <button
104
98
  type="submit"
105
99
  disabled={!apiKey.trim() || loading}
106
- className={clsx(styles.submitButton, {
107
- [styles.loading]: loading,
100
+ className={clsx(styles.api_key_input__submit_button, {
101
+ [styles['api_key_input__submit_button--loading']]: loading,
108
102
  })}
103
+ style={{
104
+ backgroundColor: chatbot?.mainColor,
105
+ color: chatbot?.secondaryColor,
106
+ }}
109
107
  >
110
108
  {loading ? 'Loading...' : 'Continue'}
111
109
  </button>
@@ -23,7 +23,7 @@ import { ChatbotBody } from './chatbot-body';
23
23
  import { ChatbotFooter } from './chatbot-footer';
24
24
  import { ChatbotContainer } from './chatbot-container/chatbot-container';
25
25
 
26
- type AuthState = 'loading' | 'needApiKey' | 'authenticated' | 'error';
26
+ type AuthState = 'loading' | 'needApiKey' | 'authenticated' | 'error' | 'invalidApiKey';
27
27
 
28
28
  interface ChatbotProps extends AsgardTemplateContextValue {
29
29
  className?: string;
@@ -116,12 +116,31 @@ export const Chatbot = forwardRef(function Chatbot(
116
116
  padding: '20px'
117
117
  }}>
118
118
  <ApiKeyInput
119
+ title={title}
119
120
  onSubmit={onApiKeySubmit || (() => {})}
120
121
  placeholder="Enter your key"
121
122
  />
122
123
  </div>
123
124
  );
124
125
 
126
+ case 'invalidApiKey':
127
+ return (
128
+ <div style={{
129
+ display: 'flex',
130
+ alignItems: 'center',
131
+ justifyContent: 'center',
132
+ flex: 1,
133
+ padding: '20px'
134
+ }}>
135
+ <ApiKeyInput
136
+ title={title}
137
+ onSubmit={onApiKeySubmit || (() => {})}
138
+ placeholder="Enter your key"
139
+ error="Please check if the key is correct."
140
+ />
141
+ </div>
142
+ );
143
+
125
144
  case 'error':
126
145
  return (
127
146
  <div style={{
@@ -158,7 +177,7 @@ export const Chatbot = forwardRef(function Chatbot(
158
177
  };
159
178
 
160
179
  // Don't initialize SSE connection when explicitly needing API key or in error state
161
- if (authState !== 'needApiKey' && authState !== 'error') {
180
+ if (authState !== 'needApiKey' && authState !== 'error' && authState !== 'invalidApiKey') {
162
181
  return (
163
182
  <AsgardAppInitializationContextProvider
164
183
  enabled={enableLoadConfigFromService}
@@ -31,6 +31,8 @@ export interface AsgardThemeContextValue {
31
31
  backgroundColor?: CSSProperties['backgroundColor'];
32
32
  borderColor?: CSSProperties['borderColor'];
33
33
  inactiveColor?: CSSProperties['color'];
34
+ mainColor?: CSSProperties['color'];
35
+ secondaryColor?: CSSProperties['color'];
34
36
  primaryComponent?: {
35
37
  mainColor?: CSSProperties['color'];
36
38
  secondaryColor?: CSSProperties['color'];
@@ -267,6 +269,9 @@ export function AsgardThemeContextProvider(
267
269
  chatbot: {
268
270
  backgroundColor: themeFromAnnotations.chatbot?.backgroundColor,
269
271
  borderColor: themeFromAnnotations.chatbot?.borderColor,
272
+ mainColor: themeFromAnnotations.chatbot?.primaryComponent?.mainColor,
273
+ secondaryColor: themeFromAnnotations.chatbot?.primaryComponent?.secondaryColor,
274
+
270
275
  header: {
271
276
  style: {
272
277
  borderBottomColor: themeFromAnnotations.chatbot?.borderColor,
@@ -407,6 +412,8 @@ export function AsgardThemeContextProvider(
407
412
  chatbot: {
408
413
  backgroundColor: propsTheme.chatbot?.backgroundColor,
409
414
  borderColor: propsTheme.chatbot?.borderColor,
415
+ mainColor: propsTheme.chatbot?.primaryComponent?.mainColor,
416
+ secondaryColor: propsTheme.chatbot?.primaryComponent?.secondaryColor,
410
417
  header: {
411
418
  style: {
412
419
  borderBottomColor: propsTheme.chatbot?.borderColor,
@@ -0,0 +1,4 @@
1
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <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"/>
3
+ <line x1="1" y1="1" x2="23" y2="23"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
3
+ <circle cx="12" cy="12" r="3"/>
4
+ </svg>