diff --git a/src/apis/OpenAI.js b/src/apis/OpenAI.js new file mode 100644 index 0000000..365f88e --- /dev/null +++ b/src/apis/OpenAI.js @@ -0,0 +1,21 @@ +//AI api 연결 + +import { Axios } from '../utils'; + + +export const AnalyzeAI = async (token, params) => { + try { + const res = await Axios.post('/api/v1/ai/analyze', params, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + }); + + return res.data; + } catch (e) { + if (e instanceof Error) { + throw new Error('analyzeAI Error', e); + } + } +}; diff --git a/src/components/common/input/MessageInput.js b/src/components/common/input/MessageInput.js new file mode 100644 index 0000000..6c79b71 --- /dev/null +++ b/src/components/common/input/MessageInput.js @@ -0,0 +1,231 @@ +import { useEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; + +const AIMessageInput = ({ onSendMessage }) => { + const [isOpen, setIsOpen] = useState(false); + const [message, setMessage] = useState(''); + const [isSending, setIsSending] = useState(false); + const textareaRef = useRef(null); + const modalRef = useRef(null); + + // 텍스트 영역 높이 자동 조절 + useEffect(() => { + if (textareaRef.current && isOpen) { + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`; + } + }, [message, isOpen]); + + // 모달 외부 클릭시 닫기 + useEffect(() => { + const handleClickOutside = (event) => { + if (modalRef.current && !modalRef.current.contains(event.target)) { + closeModal(); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); + + // 모달 열기 + const openModal = () => { + setIsOpen(true); + // 모달이 열린 후 텍스트 영역에 포커스 + setTimeout(() => { + if (textareaRef.current) { + textareaRef.current.focus(); + } + }, 100); + }; + + // 모달 닫기 + const closeModal = () => { + setIsOpen(false); + setMessage(''); + }; + + // 메시지 전송 처리 + const handleSendMessage = () => { + if (message.trim() && !isSending) { + setIsSending(true); + + // 메시지 전송 처리 + if (onSendMessage) { + onSendMessage(message); + } + + // 입력 초기화 및 상태 업데이트 + setMessage(''); + setIsSending(false); + + // 모달 닫기 + closeModal(); + } + }; + + // 엔터 키 처리 (Shift+Enter 줄바꿈, Enter 전송) + const handleKeyDown = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + return ( + <> + {/* 메뉴 버튼 */} + +
+
+
+
+ + {/* 모달 오버레이 */} + + + setMessage(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="메시지를 입력하세요..." + rows={1} + /> + + + + + + + + + + ); +}; + +export default AIMessageInput; + +const ModalOverlay = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: ${props => props.isOpen ? 'flex' : 'none'}; + justify-content: center; + align-items: center; + z-index: 1000; +`; + +// 메인 컨테이너 +const InputContainer = styled.div` + width: 90%; + max-width: 600px; + border: 1px solid #e0e0e0; + border-radius: 12px; + background-color: #ffffff; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + overflow: hidden; + position: relative; + animation: ${props => props.isOpen ? 'slideUp 0.3s ease-out' : 'none'}; + + @keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } +`; + +// 메시지 입력 영역 +const MessageInput = styled.textarea` + width: 100%; + min-height: 60px; + max-height: 200px; + padding: 16px 60px 16px 16px; + border: none; + outline: none; + resize: none; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + font-size: 16px; + line-height: 1.5; + background: transparent; + + &::placeholder { + color: #9e9ea7; + } +`; + +// 전송 버튼 +const SendButton = styled.button` + position: absolute; + bottom: 12px; + right: 12px; + width: 38px; + height: 38px; + border-radius: 50%; + background-color: #5436DA; + color: white; + border: none; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: #4527D0; + } + + &:disabled { + background-color: #DADCE0; + cursor: not-allowed; + } + + svg { + width: 18px; + height: 18px; + fill: white; + } +`; + +// 메뉴 버튼 (세로 점 세개) +const MenuButton = styled.button` + width: 40px; + height: 40px; + border-radius: 50%; + background-color: #f0f0f0; + border: none; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: #e0e0e0; + } + + .dot { + width: 4px; + height: 4px; + border-radius: 50%; + background-color: #666; + margin: 2px 0; + } +`; \ No newline at end of file