@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
113 lines (110 loc) • 9.13 kB
JavaScript
import { jsxs, jsx } from 'react/jsx-runtime';
import { useState, useRef, useEffect, useCallback } from 'react';
import { Button } from '../../../../components/ui/Button.js';
import { SuggestedQuestions } from './suggested-questions.js';
import { useI18n, RTLText } from './i18n-context.js';
function ChatDock({ isOpen, onClose, onMinimize, onSendMessage, messages, isTyping = false, title, placeholder, className = "", maxHeight = "600px", position = "bottom-right", showHeader = true, allowMinimize = true, disabled = false, showSuggestedQuestions = true, suggestedQuestionsContext = "welcome", }) {
// Use i18n context
const { t, isRTL, currentLanguage } = useI18n();
// Use default values from translations if not provided
const displayTitle = title || t("chatbot");
const displayPlaceholder = placeholder || t("placeholder");
const [inputValue, setInputValue] = useState("");
const [isMinimized, setIsMinimized] = useState(false);
const messagesEndRef = useRef(null);
const inputRef = useRef(null);
const messagesContainerRef = useRef(null);
// Auto-scroll to bottom when new messages arrive
useEffect(() => {
if (messagesEndRef.current && !isMinimized) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
}
}, [messages, isMinimized]);
// Focus input when dock opens
useEffect(() => {
if (isOpen && !isMinimized && inputRef.current) {
const timer = setTimeout(() => {
var _a;
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
}, 100);
return () => clearTimeout(timer);
}
}, [isOpen, isMinimized]);
const handleSendMessage = useCallback(() => {
const trimmedValue = inputValue.trim();
if (trimmedValue && !disabled) {
onSendMessage(trimmedValue);
setInputValue("");
}
}, [inputValue, onSendMessage, disabled]);
const handleKeyPress = useCallback((event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
handleSendMessage();
}
}, [handleSendMessage]);
const handleMinimize = useCallback(() => {
setIsMinimized(!isMinimized);
if (onMinimize) {
onMinimize();
}
}, [isMinimized, onMinimize]);
const getPositionClasses = () => {
switch (position) {
case "bottom-left":
return "bottom-24 left-6";
case "center":
return "top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2";
default:
return "bottom-24 right-6";
}
};
const formatTimestamp = (timestamp) => {
return timestamp.toLocaleTimeString("ko-KR", {
hour: "2-digit",
minute: "2-digit",
});
};
const TypingIndicator = () => (jsxs("div", { className: "flex items-center space-x-2 px-4 py-3", children: [jsxs("div", { className: "flex space-x-1", children: [jsx("div", { className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.3s]" }), jsx("div", { className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.15s]" }), jsx("div", { className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce" })] }), jsx(RTLText, { className: "text-sm text-gray-500", children: t("typing") })] }));
const MessageBubble = ({ message }) => {
const isUser = message.sender === "user";
const isError = message.type === "error";
const isSystem = message.type === "system";
return (jsx("div", { className: `flex ${isUser ? "justify-end" : "justify-start"} mb-4 animate-in slide-in-from-bottom-1 duration-300`, children: jsxs("div", { className: `
max-w-[85%] px-4 py-3 rounded-2xl shadow-sm
${isUser
? "bg-blue-600 text-white rounded-br-md"
: isError
? "bg-red-50 text-red-800 border border-red-200 rounded-bl-md"
: isSystem
? "bg-gray-50 text-gray-600 border border-gray-200 rounded-bl-md"
: "bg-gray-100 text-gray-800 rounded-bl-md"}
${message.isStreaming ? "animate-pulse" : ""}
`, children: [jsx("div", { className: "text-sm leading-relaxed whitespace-pre-wrap break-words", children: message.content }), jsx("div", { className: `text-xs mt-1 opacity-70 ${isUser ? "text-blue-100" : "text-gray-500"}`, children: formatTimestamp(message.timestamp) })] }) }));
};
if (!isOpen)
return null;
return (jsxs("div", { className: `
fixed z-40
${getPositionClasses()}
w-96
bg-white
rounded-2xl
shadow-2xl
border
border-gray-200
transition-all
duration-300
ease-in-out
${isMinimized ? "h-16" : ""}
${className}
`, style: { maxHeight: isMinimized ? "64px" : maxHeight }, role: "dialog", "aria-labelledby": "chat-title", "aria-describedby": "chat-description", children: [showHeader && (jsxs("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 bg-gray-50 rounded-t-2xl", children: [jsxs("div", { className: "flex items-center space-x-3", children: [jsx("div", { className: "w-3 h-3 bg-green-500 rounded-full animate-pulse" }), jsx("h3", { id: "chat-title", className: `font-semibold text-gray-800 ${isRTL ? "text-right" : "text-left"}`, dir: isRTL ? "rtl" : "ltr", children: displayTitle })] }), jsxs("div", { className: "flex items-center space-x-2", children: [allowMinimize && (jsx(Button, { onClick: handleMinimize, variant: "outline", className: "w-8 h-8 p-0 rounded-full border-gray-300", "aria-label": isMinimized ? t("maximize") : t("minimize"), children: jsx("svg", { className: `w-4 h-4 transition-transform duration-200 ${isMinimized ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }) })), jsx(Button, { onClick: onClose, variant: "outline", className: "w-8 h-8 p-0 rounded-full border-gray-300", "aria-label": t("close"), children: jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] })] })), !isMinimized && (jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto p-4 space-y-4", style: { height: "calc(100% - 140px)" }, id: "chat-description", "aria-label": t("messageFromBot"), children: [messages.length === 0 ? (jsxs("div", { className: "text-center text-gray-500 py-8", children: [jsx("div", { className: "text-4xl mb-4", children: "\uD83D\uDCAC" }), jsx(RTLText, { className: "text-sm mb-6", children: t("welcomeMessage") }), showSuggestedQuestions && (jsx("div", { className: "mt-6", children: jsx(SuggestedQuestions, { onQuestionSelect: onSendMessage, context: suggestedQuestionsContext, variant: "chips", maxQuestions: 3, className: "justify-center" }) }))] })) : (messages.map((message) => (jsx(MessageBubble, { message: message }, message.id)))), isTyping && jsx(TypingIndicator, {}), jsx("div", { ref: messagesEndRef })] })), !isMinimized && (jsxs("div", { className: "p-4 border-t border-gray-200 bg-gray-50 rounded-b-2xl", children: [jsxs("div", { className: "flex items-center space-x-3", children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyDown: handleKeyPress, placeholder: displayPlaceholder, disabled: disabled || isTyping, className: "flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500", "aria-label": t("sendMessage"), dir: isRTL ? "rtl" : "ltr" }), jsx(Button, { onClick: handleSendMessage, disabled: !inputValue.trim() || disabled || isTyping, className: "px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors duration-200", "aria-label": t("send"), children: jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8" }) }) })] }), jsx(RTLText, { className: "mt-2 text-xs text-gray-500 text-center", children: currentLanguage === "ko"
? "Enter로 전송 • Shift+Enter로 줄바꿈"
: currentLanguage === "ja"
? "Enterで送信 • Shift+Enterで改行"
: currentLanguage === "ar"
? "اضغط Enter للإرسال • Shift+Enter للسطر الجديد"
: "Press Enter to send • Shift+Enter for new line" })] }))] }));
}
export { ChatDock, ChatDock as default };
//# sourceMappingURL=chat-dock.js.map