anotherath commited on
Commit
521104b
·
1 Parent(s): 4c47e22

typing fix

Browse files
src/components/ChatArea.jsx CHANGED
@@ -618,7 +618,7 @@ function ChatArea({
618
  );
619
  }
620
  }}
621
- isTyping={isBotRoom || (isDM && otherTyping)}
622
  />
623
  <ChatInput
624
  isDark={isDark}
@@ -626,6 +626,8 @@ function ChatArea({
626
  onSend={handleSend}
627
  onTyping={handleTyping}
628
  onStopTyping={handleStopTyping}
 
 
629
  />
630
  </div>
631
  );
 
618
  );
619
  }
620
  }}
621
+ isTyping={isBotRoom}
622
  />
623
  <ChatInput
624
  isDark={isDark}
 
626
  onSend={handleSend}
627
  onTyping={handleTyping}
628
  onStopTyping={handleStopTyping}
629
+ typingSender={isDM && otherTyping ? dmUser?.name : null}
630
+ otherTyping={otherTyping}
631
  />
632
  </div>
633
  );
src/components/chatarea/ChatInput.jsx CHANGED
@@ -27,6 +27,7 @@ function ChatInput({
27
  onSend,
28
  onTyping,
29
  onStopTyping,
 
30
  }) {
31
  const [showMentions, setShowMentions] = useState(false);
32
  const [mentionFilter, setMentionFilter] = useState("");
@@ -41,8 +42,6 @@ function ChatInput({
41
 
42
  const placeholderText = placeholder;
43
 
44
-
45
-
46
  useEffect(() => {
47
  const handleClickOutside = (e) => {
48
  if (containerRef.current && !containerRef.current.contains(e.target)) {
@@ -352,108 +351,148 @@ function ChatInput({
352
  return (
353
  <div
354
  ref={containerRef}
355
- className="px-4 py-3 border-t shrink-0 relative"
356
  style={{
357
  borderColor: "var(--border-primary)",
358
  background: "var(--bg-surface-secondary)",
359
  }}
360
  >
361
- {/* File attachment preview */}
362
- {selectedFiles.length > 0 && (
363
- <FileAttachmentPreview
364
- files={selectedFiles}
365
- onRemove={handleRemoveFile}
366
- isDark={isDark}
367
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  )}
369
 
370
- {showMentions && (
371
- <div
372
- className="absolute bottom-full left-4 right-4 mb-2 rounded-lg shadow-lg border overflow-hidden z-50"
373
- style={{
374
- background: isDark ? "var(--bg-surface-secondary)" : "#fff",
375
- borderColor: "var(--border-primary)",
376
- }}
377
- >
378
- <MentionSuggestions
379
- onSelect={handleMentionSelect}
380
  isDark={isDark}
381
- filterText={mentionFilter}
382
- selectedIndex={selectedMentionIndex}
383
  />
384
- </div>
385
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
- <div
388
- className="chat-input-wrapper flex items-center gap-2 border rounded-lg p-1 transition-colors relative"
389
- style={{
390
- background: "var(--input-bg)",
391
- borderColor: "var(--input-border)",
392
- }}
393
- onFocus={(e) => (e.currentTarget.style.borderColor = "var(--primary)")}
394
- onBlur={(e) =>
395
- (e.currentTarget.style.borderColor = "var(--input-border)")
396
- }
397
- >
398
- <div className={`chat-input-placeholder ${isEmpty ? "" : "hidden"}`}>
399
- {placeholderText}
400
- </div>
401
  <div
402
- ref={editorRef}
403
- contentEditable
404
- className="flex-1 border-none bg-transparent px-3 py-2 text-sm outline-none font-sans min-h-9 max-h-32 overflow-y-auto relative z-10"
405
  style={{
406
- color: "var(--input-text)",
 
407
  }}
408
- onInput={handleInput}
409
- onKeyDown={handleKeyDown}
410
- suppressContentEditableWarning
411
- />
412
- {/* Hidden file input */}
413
- <input
414
- ref={fileInputRef}
415
- type="file"
416
- accept=".pdf,.jpg,.jpeg,.png,.gif,.webp,.svg"
417
- multiple
418
- className="hidden"
419
- onChange={handleFileSelect}
420
- />
421
- <button
422
- type="button"
423
- className="w-9 h-9 border-none rounded-md cursor-pointer flex items-center justify-center transition-colors"
424
- style={{
425
- background: "transparent",
426
- color: "var(--text-secondary)",
427
- }}
428
- onMouseEnter={(e) =>
429
- (e.currentTarget.style.background = "var(--hover-primary)")
430
- }
431
- onMouseLeave={(e) =>
432
- (e.currentTarget.style.background = "transparent")
433
- }
434
- onClick={handleAttachmentClick}
435
- title="Đính kèm file (PDF, hình ảnh)"
436
- >
437
- <FiPaperclip size={18} />
438
- </button>
439
- <button
440
- type="button"
441
- className="w-9 h-9 border-none rounded-md cursor-pointer flex items-center justify-center transition-colors"
442
- style={{
443
- background: "var(--primary)",
444
- color: isDark ? "var(--bg-surface)" : "#fff",
445
- }}
446
- onMouseEnter={(e) =>
447
- (e.currentTarget.style.background = "var(--primary-hover)")
448
  }
449
- onMouseLeave={(e) =>
450
- (e.currentTarget.style.background = "var(--primary)")
451
  }
452
- onClick={handleSend}
453
- title="Gửi"
454
  >
455
- <IoSend size={18} />
456
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  </div>
458
  </div>
459
  );
 
27
  onSend,
28
  onTyping,
29
  onStopTyping,
30
+ typingSender,
31
  }) {
32
  const [showMentions, setShowMentions] = useState(false);
33
  const [mentionFilter, setMentionFilter] = useState("");
 
42
 
43
  const placeholderText = placeholder;
44
 
 
 
45
  useEffect(() => {
46
  const handleClickOutside = (e) => {
47
  if (containerRef.current && !containerRef.current.contains(e.target)) {
 
351
  return (
352
  <div
353
  ref={containerRef}
354
+ className="border-t shrink-0 relative"
355
  style={{
356
  borderColor: "var(--border-primary)",
357
  background: "var(--bg-surface-secondary)",
358
  }}
359
  >
360
+ {/* Typing indicator nằm TRÊN box input, absolute position */}
361
+ {typingSender && (
362
+ <div className="absolute top-[-28px] left-0 flex items-center gap-2 px-4 pt-2 pb-1">
363
+ <span className="text-xs italic" style={{ color: "var(--primary)" }}>
364
+ {typingSender} đang nhập
365
+ </span>
366
+ <span className="flex gap-0.5">
367
+ <span
368
+ className="w-1 h-1 rounded-full animate-bounce"
369
+ style={{
370
+ background: "var(--primary)",
371
+ animationDelay: "0ms",
372
+ }}
373
+ />
374
+ <span
375
+ className="w-1 h-1 rounded-full animate-bounce"
376
+ style={{
377
+ background: "var(--primary)",
378
+ animationDelay: "150ms",
379
+ }}
380
+ />
381
+ <span
382
+ className="w-1 h-1 rounded-full animate-bounce"
383
+ style={{
384
+ background: "var(--primary)",
385
+ animationDelay: "300ms",
386
+ }}
387
+ />
388
+ </span>
389
+ </div>
390
  )}
391
 
392
+ <div className="px-4 py-3">
393
+ {/* File attachment preview */}
394
+ {selectedFiles.length > 0 && (
395
+ <FileAttachmentPreview
396
+ files={selectedFiles}
397
+ onRemove={handleRemoveFile}
 
 
 
 
398
  isDark={isDark}
 
 
399
  />
400
+ )}
401
+
402
+ {showMentions && (
403
+ <div
404
+ className="absolute bottom-full left-4 right-4 mb-2 rounded-lg shadow-lg border overflow-hidden z-50"
405
+ style={{
406
+ background: isDark ? "var(--bg-surface-secondary)" : "#fff",
407
+ borderColor: "var(--border-primary)",
408
+ }}
409
+ >
410
+ <MentionSuggestions
411
+ onSelect={handleMentionSelect}
412
+ isDark={isDark}
413
+ filterText={mentionFilter}
414
+ selectedIndex={selectedMentionIndex}
415
+ />
416
+ </div>
417
+ )}
418
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  <div
420
+ className="chat-input-wrapper border rounded-lg p-1 transition-colors relative"
 
 
421
  style={{
422
+ background: "var(--input-bg)",
423
+ borderColor: "var(--input-border)",
424
  }}
425
+ onFocus={(e) =>
426
+ (e.currentTarget.style.borderColor = "var(--primary)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  }
428
+ onBlur={(e) =>
429
+ (e.currentTarget.style.borderColor = "var(--input-border)")
430
  }
 
 
431
  >
432
+ <div className="flex items-center gap-2">
433
+ <div
434
+ className={`chat-input-placeholder ${isEmpty ? "" : "hidden"}`}
435
+ >
436
+ {placeholderText}
437
+ </div>
438
+ <div
439
+ ref={editorRef}
440
+ contentEditable
441
+ className="flex-1 border-none bg-transparent px-3 py-2 text-sm outline-none font-sans min-h-9 max-h-32 overflow-y-auto relative z-10"
442
+ style={{
443
+ color: "var(--input-text)",
444
+ }}
445
+ onInput={handleInput}
446
+ onKeyDown={handleKeyDown}
447
+ suppressContentEditableWarning
448
+ />
449
+ {/* Hidden file input */}
450
+ <input
451
+ ref={fileInputRef}
452
+ type="file"
453
+ accept=".pdf,.jpg,.jpeg,.png,.gif,.webp,.svg"
454
+ multiple
455
+ className="hidden"
456
+ onChange={handleFileSelect}
457
+ />
458
+ <button
459
+ type="button"
460
+ className="w-9 h-9 border-none rounded-md cursor-pointer flex items-center justify-center transition-colors"
461
+ style={{
462
+ background: "transparent",
463
+ color: "var(--text-secondary)",
464
+ }}
465
+ onMouseEnter={(e) =>
466
+ (e.currentTarget.style.background = "var(--hover-primary)")
467
+ }
468
+ onMouseLeave={(e) =>
469
+ (e.currentTarget.style.background = "transparent")
470
+ }
471
+ onClick={handleAttachmentClick}
472
+ title="Đính kèm file (PDF, hình ảnh)"
473
+ >
474
+ <FiPaperclip size={18} />
475
+ </button>
476
+ <button
477
+ type="button"
478
+ className="w-9 h-9 border-none rounded-md cursor-pointer flex items-center justify-center transition-colors"
479
+ style={{
480
+ background: "var(--primary)",
481
+ color: isDark ? "var(--bg-surface)" : "#fff",
482
+ }}
483
+ onMouseEnter={(e) =>
484
+ (e.currentTarget.style.background = "var(--primary-hover)")
485
+ }
486
+ onMouseLeave={(e) =>
487
+ (e.currentTarget.style.background = "var(--primary)")
488
+ }
489
+ onClick={handleSend}
490
+ title="Gửi"
491
+ >
492
+ <IoSend size={18} />
493
+ </button>
494
+ </div>
495
+ </div>
496
  </div>
497
  </div>
498
  );
src/components/chatarea/ChatMessages.jsx CHANGED
@@ -125,41 +125,38 @@ function ChatMessage({
125
  );
126
  }
127
 
128
- function TypingIndicator({ isDark }) {
129
  return (
130
- <div className="flex gap-3 px-3 py-2">
131
- <div
132
- className="w-9 h-9 rounded-lg flex items-center justify-center text-sm font-semibold shrink-0"
133
- style={{
134
- background: "var(--tertiary-active)",
135
- color: "var(--tertiary)",
136
- }}
137
  >
138
- 🤖
139
- </div>
140
- <div className="flex items-center gap-1">
141
- <div
142
- className="w-2 h-2 rounded-full animate-pulse"
143
  style={{
144
- background: "var(--tertiary)",
145
  animationDelay: "0ms",
146
  }}
147
  />
148
- <div
149
- className="w-2 h-2 rounded-full animate-pulse"
150
  style={{
151
- background: "var(--tertiary)",
152
  animationDelay: "150ms",
153
  }}
154
  />
155
- <div
156
- className="w-2 h-2 rounded-full animate-pulse"
157
  style={{
158
- background: "var(--tertiary)",
159
  animationDelay: "300ms",
160
  }}
161
  />
162
- </div>
163
  </div>
164
  );
165
  }
@@ -265,7 +262,6 @@ function ChatMessages({
265
  chatMessages,
266
  dmUser,
267
  onReply,
268
- isTyping,
269
  onEdit,
270
  onShowProfile,
271
  hasNoSelection,
@@ -357,7 +353,7 @@ function ChatMessages({
357
 
358
  const hasMessages = chatMessages.length > 0;
359
 
360
- const isEmpty = !hasMessages && !isTyping && !isLoading;
361
 
362
  return (
363
  <div
@@ -425,7 +421,7 @@ function ChatMessages({
425
  onShowProfile={onShowProfile}
426
  />
427
  ))}
428
- {isTyping && <TypingIndicator isDark={isDark} />}
429
  </div>
430
  )}
431
  </div>
 
125
  );
126
  }
127
 
128
+ function TypingIndicator({ isDark, senderName = "Đang nhập" }) {
129
  return (
130
+ <div className="flex items-center gap-2 px-4 py-1.5" style={{ background: "transparent" }}>
131
+ <span
132
+ className="text-xs italic"
133
+ style={{ color: "var(--primary)" }}
 
 
 
134
  >
135
+ {senderName} đang nhập
136
+ </span>
137
+ <span className="flex gap-0.5">
138
+ <span
139
+ className="w-1 h-1 rounded-full animate-bounce"
140
  style={{
141
+ background: "var(--primary)",
142
  animationDelay: "0ms",
143
  }}
144
  />
145
+ <span
146
+ className="w-1 h-1 rounded-full animate-bounce"
147
  style={{
148
+ background: "var(--primary)",
149
  animationDelay: "150ms",
150
  }}
151
  />
152
+ <span
153
+ className="w-1 h-1 rounded-full animate-bounce"
154
  style={{
155
+ background: "var(--primary)",
156
  animationDelay: "300ms",
157
  }}
158
  />
159
+ </span>
160
  </div>
161
  );
162
  }
 
262
  chatMessages,
263
  dmUser,
264
  onReply,
 
265
  onEdit,
266
  onShowProfile,
267
  hasNoSelection,
 
353
 
354
  const hasMessages = chatMessages.length > 0;
355
 
356
+ const isEmpty = !hasMessages && !isLoading;
357
 
358
  return (
359
  <div
 
421
  onShowProfile={onShowProfile}
422
  />
423
  ))}
424
+ {/* Typing indicator removed — now shown in ChatInput instead */}
425
  </div>
426
  )}
427
  </div>