@jidou-ai/chat-widget 1.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.
Files changed (40) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/README.md +276 -0
  3. package/dist/index.html +236 -0
  4. package/dist/test-local.html +268 -0
  5. package/dist/test-production.html +455 -0
  6. package/dist/widget.js +2781 -0
  7. package/package.json +41 -0
  8. package/scripts/build.js +73 -0
  9. package/scripts/dev.js +183 -0
  10. package/scripts/mock-server.js +436 -0
  11. package/src/components/BaseComponent.ts +67 -0
  12. package/src/components/ChatWindow.ts +283 -0
  13. package/src/components/ConnectionStatus.ts +50 -0
  14. package/src/components/InputArea.ts +164 -0
  15. package/src/components/Launcher.ts +121 -0
  16. package/src/components/MessageList.ts +150 -0
  17. package/src/components/QuickReplies.ts +42 -0
  18. package/src/components/icons.ts +51 -0
  19. package/src/components/index.ts +9 -0
  20. package/src/core/Widget.ts +478 -0
  21. package/src/core/index.ts +1 -0
  22. package/src/i18n/index.ts +85 -0
  23. package/src/i18n/translations.ts +150 -0
  24. package/src/index.ts +76 -0
  25. package/src/services/ChatHistoryManager.ts +98 -0
  26. package/src/services/StateManager.ts +148 -0
  27. package/src/services/WebSocketClient.ts +295 -0
  28. package/src/services/index.ts +3 -0
  29. package/src/styles/base.ts +231 -0
  30. package/src/styles/chatWindow.ts +239 -0
  31. package/src/styles/index.ts +21 -0
  32. package/src/styles/launcher.ts +186 -0
  33. package/src/styles/messages.ts +329 -0
  34. package/src/types/index.ts +345 -0
  35. package/src/utils/dom.ts +130 -0
  36. package/src/utils/events.ts +52 -0
  37. package/src/utils/id.ts +41 -0
  38. package/src/utils/index.ts +129 -0
  39. package/src/utils/storage.ts +64 -0
  40. package/tsconfig.json +26 -0
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Message list and input area styles
3
+ */
4
+
5
+ export const MESSAGE_STYLES = `
6
+ /* Message List Container */
7
+ .jd-message-list {
8
+ flex: 1;
9
+ overflow-y: auto;
10
+ padding: 16px;
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 12px;
14
+ }
15
+
16
+ /* Message */
17
+ .jd-message {
18
+ display: flex;
19
+ flex-direction: column;
20
+ max-width: 85%;
21
+ animation: jd-message-in 0.3s ease;
22
+ }
23
+
24
+ @keyframes jd-message-in {
25
+ from {
26
+ opacity: 0;
27
+ transform: translateY(8px) scale(0.98);
28
+ }
29
+ to {
30
+ opacity: 1;
31
+ transform: translateY(0) scale(1);
32
+ }
33
+ }
34
+
35
+ .jd-message--user {
36
+ align-self: flex-end;
37
+ }
38
+
39
+ .jd-message--assistant {
40
+ align-self: flex-start;
41
+ }
42
+
43
+ /* Message Bubble */
44
+ .jd-message__bubble {
45
+ padding: 12px 16px;
46
+ border-radius: var(--jd-radius);
47
+ font-family: var(--jd-font);
48
+ font-size: 14px;
49
+ line-height: 1.5;
50
+ word-wrap: break-word;
51
+ white-space: pre-wrap;
52
+ }
53
+
54
+ .jd-message--user .jd-message__bubble {
55
+ background: var(--jd-user-bubble);
56
+ color: var(--jd-user-bubble-text);
57
+ border-bottom-right-radius: 4px;
58
+ }
59
+
60
+ .jd-message--assistant .jd-message__bubble {
61
+ background: var(--jd-bot-bubble);
62
+ color: var(--jd-text);
63
+ border-bottom-left-radius: 4px;
64
+ }
65
+
66
+ /* Message Time */
67
+ .jd-message__time {
68
+ font-family: var(--jd-font);
69
+ font-size: 11px;
70
+ color: var(--jd-text-secondary);
71
+ margin-top: 4px;
72
+ padding: 0 4px;
73
+ }
74
+
75
+ .jd-message--user .jd-message__time {
76
+ text-align: right;
77
+ }
78
+
79
+ /* Message Status */
80
+ .jd-message__status {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 4px;
84
+ font-size: 11px;
85
+ color: var(--jd-text-secondary);
86
+ margin-top: 4px;
87
+ padding: 0 4px;
88
+ }
89
+
90
+ .jd-message--user .jd-message__status {
91
+ justify-content: flex-end;
92
+ }
93
+
94
+ .jd-message__status svg {
95
+ width: 12px;
96
+ height: 12px;
97
+ }
98
+
99
+ .jd-message__status--sending {
100
+ color: var(--jd-text-secondary);
101
+ }
102
+
103
+ .jd-message__status--error {
104
+ color: #EF4444;
105
+ }
106
+
107
+ /* Typing Indicator */
108
+ .jd-typing-indicator {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 5px;
112
+ padding: 12px 16px;
113
+ background: var(--jd-bot-bubble);
114
+ border-radius: var(--jd-radius);
115
+ border-bottom-left-radius: 4px;
116
+ width: fit-content;
117
+ animation: jd-message-in 0.3s ease;
118
+ }
119
+
120
+ .jd-typing-indicator__dot {
121
+ width: 6px;
122
+ height: 6px;
123
+ border-radius: var(--jd-radius-full);
124
+ background: var(--jd-text-secondary);
125
+ animation: jd-typing-bounce 1.4s infinite ease-in-out;
126
+ }
127
+
128
+ .jd-typing-indicator__dot:nth-child(2) {
129
+ animation-delay: 0.2s;
130
+ }
131
+
132
+ .jd-typing-indicator__dot:nth-child(3) {
133
+ animation-delay: 0.4s;
134
+ }
135
+
136
+ @keyframes jd-typing-bounce {
137
+ 0%, 60%, 100% {
138
+ transform: translateY(0);
139
+ opacity: 0.4;
140
+ }
141
+ 30% {
142
+ transform: translateY(-4px);
143
+ opacity: 1;
144
+ }
145
+ }
146
+
147
+ /* Welcome Screen */
148
+ .jd-welcome {
149
+ flex: 1;
150
+ display: flex;
151
+ flex-direction: column;
152
+ align-items: center;
153
+ justify-content: center;
154
+ padding: 32px;
155
+ text-align: center;
156
+ }
157
+
158
+ .jd-welcome__icon {
159
+ width: 64px;
160
+ height: 64px;
161
+ border-radius: var(--jd-radius-full);
162
+ background: var(--jd-surface);
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ margin-bottom: 16px;
167
+ }
168
+
169
+ .jd-welcome__icon svg {
170
+ width: 32px;
171
+ height: 32px;
172
+ color: var(--jd-primary);
173
+ }
174
+
175
+ .jd-welcome__title {
176
+ font-family: var(--jd-font);
177
+ font-size: 18px;
178
+ font-weight: 600;
179
+ color: var(--jd-text);
180
+ margin: 0 0 8px 0;
181
+ }
182
+
183
+ .jd-welcome__subtitle {
184
+ font-family: var(--jd-font);
185
+ font-size: 14px;
186
+ color: var(--jd-text-secondary);
187
+ margin: 0 0 24px 0;
188
+ }
189
+
190
+ /* Quick Replies */
191
+ .jd-quick-replies {
192
+ display: flex;
193
+ gap: 6px;
194
+ padding: 8px 12px;
195
+ flex-shrink: 0;
196
+ border-top: 1px solid var(--jd-border);
197
+ background: var(--jd-surface);
198
+ overflow-x: auto;
199
+ -webkit-overflow-scrolling: touch;
200
+ scrollbar-width: none;
201
+ }
202
+
203
+ .jd-quick-replies::-webkit-scrollbar {
204
+ display: none;
205
+ }
206
+
207
+ .jd-quick-reply {
208
+ padding: 6px 12px;
209
+ border: 1px solid var(--jd-border);
210
+ border-radius: var(--jd-radius-full);
211
+ background: var(--jd-background);
212
+ color: var(--jd-text);
213
+ font-family: var(--jd-font);
214
+ font-size: 12px;
215
+ cursor: pointer;
216
+ transition: all var(--jd-transition);
217
+ white-space: nowrap;
218
+ flex-shrink: 0;
219
+ }
220
+
221
+ .jd-quick-reply:hover {
222
+ border-color: var(--jd-primary);
223
+ color: var(--jd-primary);
224
+ background: var(--jd-surface);
225
+ }
226
+
227
+ .jd-quick-reply:focus {
228
+ outline: 2px solid var(--jd-primary);
229
+ outline-offset: 2px;
230
+ }
231
+
232
+ /* Input Area */
233
+ .jd-input-area {
234
+ display: flex;
235
+ align-items: flex-end;
236
+ gap: 8px;
237
+ padding: 12px 16px;
238
+ border-top: 1px solid var(--jd-border);
239
+ background: var(--jd-background);
240
+ }
241
+
242
+ .jd-input-wrapper {
243
+ flex: 1;
244
+ display: flex;
245
+ align-items: flex-end;
246
+ background: var(--jd-surface);
247
+ border: 1px solid var(--jd-border);
248
+ border-radius: var(--jd-radius);
249
+ padding: 8px 12px;
250
+ transition: border-color var(--jd-transition);
251
+ }
252
+
253
+ .jd-input-wrapper:focus-within {
254
+ border-color: var(--jd-primary);
255
+ }
256
+
257
+ .jd-input {
258
+ flex: 1;
259
+ border: none;
260
+ background: transparent;
261
+ font-family: var(--jd-font);
262
+ font-size: 14px;
263
+ color: var(--jd-text);
264
+ resize: none;
265
+ max-height: 120px;
266
+ line-height: 1.5;
267
+ }
268
+
269
+ .jd-input:focus {
270
+ outline: none;
271
+ }
272
+
273
+ .jd-input::placeholder {
274
+ color: var(--jd-text-secondary);
275
+ }
276
+
277
+ .jd-send-btn {
278
+ width: 40px;
279
+ height: 40px;
280
+ border: none;
281
+ border-radius: var(--jd-radius-full);
282
+ background: var(--jd-primary);
283
+ color: white;
284
+ cursor: pointer;
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: center;
288
+ transition: all var(--jd-transition);
289
+ flex-shrink: 0;
290
+ }
291
+
292
+ .jd-send-btn:hover:not(:disabled) {
293
+ background: var(--jd-primary-hover);
294
+ }
295
+
296
+ .jd-send-btn:disabled {
297
+ opacity: 0.5;
298
+ cursor: not-allowed;
299
+ }
300
+
301
+ .jd-send-btn:focus {
302
+ outline: 2px solid var(--jd-primary);
303
+ outline-offset: 2px;
304
+ }
305
+
306
+ .jd-send-btn svg {
307
+ width: 20px;
308
+ height: 20px;
309
+ }
310
+
311
+ /* Empty State */
312
+ .jd-empty-state {
313
+ flex: 1;
314
+ display: flex;
315
+ flex-direction: column;
316
+ align-items: center;
317
+ justify-content: center;
318
+ padding: 32px;
319
+ text-align: center;
320
+ color: var(--jd-text-secondary);
321
+ }
322
+
323
+ .jd-empty-state__icon {
324
+ width: 48px;
325
+ height: 48px;
326
+ margin-bottom: 16px;
327
+ opacity: 0.5;
328
+ }
329
+ `;
@@ -0,0 +1,345 @@
1
+ // ============================================
2
+ // Display Mode Types
3
+ // ============================================
4
+
5
+ export type DisplayMode = 'floating' | 'fullscreen' | 'embedded';
6
+
7
+ export type Theme = 'light' | 'dark' | 'auto';
8
+
9
+ export type Language = 'zh-TW' | 'zh-CN' | 'en' | 'ja' | 'auto';
10
+
11
+ export type LauncherPosition = 'bottom-right' | 'bottom-left';
12
+
13
+ export type LauncherSize = 'small' | 'medium' | 'large';
14
+
15
+ export type LauncherShape = 'circle' | 'rounded' | 'square';
16
+
17
+ // ============================================
18
+ // Connection Types
19
+ // ============================================
20
+
21
+ export type ConnectionState =
22
+ | 'connecting'
23
+ | 'connected'
24
+ | 'disconnected'
25
+ | 'reconnecting'
26
+ | 'failed';
27
+
28
+ // ============================================
29
+ // Message Types
30
+ // ============================================
31
+
32
+ export interface Message {
33
+ id: string;
34
+ role: 'user' | 'assistant';
35
+ content: string;
36
+ timestamp: number;
37
+ status?: 'sending' | 'sent' | 'error';
38
+ }
39
+
40
+ export interface QuickReply {
41
+ text: string;
42
+ value?: string;
43
+ icon?: string;
44
+ }
45
+
46
+ // ============================================
47
+ // Widget Configuration
48
+ // ============================================
49
+
50
+ export interface WidgetColors {
51
+ primary?: string;
52
+ background?: string;
53
+ text?: string;
54
+ botBubble?: string;
55
+ userBubble?: string;
56
+ headerBackground?: string | { type: 'gradient'; from: string; to: string };
57
+ }
58
+
59
+ export interface LauncherConfig {
60
+ show?: boolean;
61
+ position?: LauncherPosition;
62
+ offsetX?: number;
63
+ offsetY?: number;
64
+ icon?: {
65
+ type?: 'default' | 'custom' | 'text';
66
+ url?: string;
67
+ text?: string;
68
+ };
69
+ size?: LauncherSize;
70
+ shape?: LauncherShape;
71
+ animation?: {
72
+ entrance?: string;
73
+ hover?: string;
74
+ notification?: string;
75
+ };
76
+ badge?: {
77
+ show?: boolean;
78
+ color?: string;
79
+ };
80
+ tooltip?: {
81
+ show?: boolean;
82
+ text?: string;
83
+ };
84
+ }
85
+
86
+ export interface ChatWindowConfig {
87
+ width?: number | string;
88
+ height?: number | string;
89
+ borderRadius?: number;
90
+ header?: {
91
+ show?: boolean;
92
+ logo?: {
93
+ show?: boolean;
94
+ url?: string;
95
+ size?: number;
96
+ };
97
+ title?: string;
98
+ subtitle?: string;
99
+ statusIndicator?: {
100
+ show?: boolean;
101
+ };
102
+ };
103
+ footer?: {
104
+ input?: {
105
+ placeholder?: string;
106
+ maxLength?: number;
107
+ };
108
+ branding?: {
109
+ show?: boolean;
110
+ text?: string;
111
+ };
112
+ };
113
+ welcomeScreen?: {
114
+ show?: boolean;
115
+ quickReplies?: {
116
+ items?: QuickReply[];
117
+ };
118
+ };
119
+ }
120
+
121
+ export interface FullscreenConfig {
122
+ background?: {
123
+ type?: 'solid' | 'gradient' | 'image';
124
+ color?: string;
125
+ gradient?: {
126
+ from: string;
127
+ to: string;
128
+ direction?: string;
129
+ };
130
+ image?: {
131
+ url: string;
132
+ size?: string;
133
+ position?: string;
134
+ overlay?: string;
135
+ };
136
+ };
137
+ layout?: {
138
+ maxWidth?: number | string;
139
+ position?: 'center' | 'left' | 'right';
140
+ padding?: {
141
+ top?: number;
142
+ bottom?: number;
143
+ left?: number;
144
+ right?: number;
145
+ };
146
+ };
147
+ header?: {
148
+ show?: boolean;
149
+ height?: number;
150
+ sticky?: boolean;
151
+ logo?: {
152
+ show?: boolean;
153
+ url?: string;
154
+ maxHeight?: number;
155
+ };
156
+ title?: string;
157
+ backButton?: {
158
+ show?: boolean;
159
+ text?: string;
160
+ url?: string;
161
+ };
162
+ };
163
+ footer?: {
164
+ show?: boolean;
165
+ branding?: {
166
+ show?: boolean;
167
+ text?: string;
168
+ };
169
+ links?: Array<{
170
+ text: string;
171
+ url: string;
172
+ }>;
173
+ };
174
+ sidebar?: {
175
+ show?: boolean;
176
+ position?: 'left' | 'right';
177
+ width?: number;
178
+ content?: {
179
+ type?: 'history' | 'faq' | 'custom';
180
+ customHtml?: string;
181
+ };
182
+ };
183
+ }
184
+
185
+ export interface BehaviorConfig {
186
+ autoOpen?: {
187
+ enabled?: boolean;
188
+ delay?: number;
189
+ trigger?: string;
190
+ once?: boolean;
191
+ };
192
+ proactiveMessage?: {
193
+ enabled?: boolean;
194
+ delay?: number;
195
+ message?: string;
196
+ };
197
+ sounds?: {
198
+ enabled?: boolean;
199
+ newMessage?: boolean;
200
+ volume?: number;
201
+ };
202
+ persistence?: {
203
+ enabled?: boolean;
204
+ duration?: number;
205
+ };
206
+ rating?: {
207
+ enabled?: boolean;
208
+ style?: 'emoji' | 'stars' | 'thumbs';
209
+ };
210
+ offline?: {
211
+ mode?: 'hide' | 'form' | 'message';
212
+ };
213
+ }
214
+
215
+ export interface EventHooks {
216
+ onLoad?: () => void;
217
+ onReady?: () => void;
218
+ onOpen?: () => void;
219
+ onClose?: () => void;
220
+ onDisplayModeChange?: (mode: DisplayMode) => void;
221
+ onMessageSent?: (message: string) => void;
222
+ onMessageReceived?: (message: string) => void;
223
+ onConversationStarted?: (sessionId: string) => void;
224
+ onConnectionStateChange?: (state: ConnectionState) => void;
225
+ onReconnect?: (attempt: number) => void;
226
+ onError?: (error: Error) => void;
227
+ }
228
+
229
+ export interface WidgetConfig {
230
+ // Required
231
+ clientId: string;
232
+
233
+ // Basic settings
234
+ language?: Language;
235
+ timezone?: string;
236
+
237
+ // Display mode
238
+ displayMode?: DisplayMode;
239
+ container?: string | HTMLElement;
240
+
241
+ // Appearance
242
+ theme?: Theme;
243
+ colors?: WidgetColors;
244
+ launcher?: LauncherConfig;
245
+ chatWindow?: ChatWindowConfig;
246
+ fullscreen?: FullscreenConfig;
247
+
248
+ // Behavior
249
+ behavior?: BehaviorConfig;
250
+
251
+ // Hooks
252
+ hooks?: EventHooks;
253
+
254
+ // Advanced
255
+ zIndex?: number;
256
+ customData?: Record<string, unknown>;
257
+
258
+ // WebSocket
259
+ wsUrl?: string;
260
+ }
261
+
262
+ // ============================================
263
+ // WebSocket Protocol Types
264
+ // ============================================
265
+
266
+ export type ClientMessageType =
267
+ | 'session:init'
268
+ | 'message:send'
269
+ | 'typing:start'
270
+ | 'typing:stop'
271
+ | 'ping';
272
+
273
+ export type ServerMessageType =
274
+ | 'connection:ack'
275
+ | 'session:ready'
276
+ | 'session:expired'
277
+ | 'message:receive'
278
+ | 'bot:typing'
279
+ | 'pong'
280
+ | 'error';
281
+
282
+ export interface WSMessage<T = unknown> {
283
+ type: ClientMessageType | ServerMessageType;
284
+ payload?: T;
285
+ timestamp?: number;
286
+ }
287
+
288
+ export interface ConnectionAckPayload {
289
+ sessionId: string;
290
+ isResumed: boolean;
291
+ }
292
+
293
+ export interface MessagePayload {
294
+ id: string;
295
+ content: string;
296
+ role?: 'user' | 'assistant';
297
+ timestamp?: number;
298
+ }
299
+
300
+ export interface ErrorPayload {
301
+ code: string;
302
+ message: string;
303
+ }
304
+
305
+ // ============================================
306
+ // State Types
307
+ // ============================================
308
+
309
+ export interface WidgetState {
310
+ isOpen: boolean;
311
+ displayMode: DisplayMode;
312
+ connectionState: ConnectionState;
313
+ messages: Message[];
314
+ isTyping: boolean;
315
+ unreadCount: number;
316
+ sessionId: string | null;
317
+ }
318
+
319
+ // ============================================
320
+ // Chat History Types
321
+ // ============================================
322
+
323
+ export interface LocalChatHistory {
324
+ sessionId: string;
325
+ messages: Message[];
326
+ savedAt: number;
327
+ }
328
+
329
+ // ============================================
330
+ // Public API Types
331
+ // ============================================
332
+
333
+ export interface JidouChatAPI {
334
+ open(): void;
335
+ close(): void;
336
+ toggle(): void;
337
+ setDisplayMode(mode: DisplayMode, options?: { container?: string | HTMLElement }): void;
338
+ getDisplayMode(): DisplayMode;
339
+ sendMessage(text: string): void;
340
+ on<K extends keyof EventHooks>(event: K, callback: NonNullable<EventHooks[K]>): void;
341
+ off<K extends keyof EventHooks>(event: K, callback: NonNullable<EventHooks[K]>): void;
342
+ isOpen(): boolean;
343
+ destroy(): void;
344
+ getState(): Readonly<WidgetState>;
345
+ }