diff --git a/package.json b/package.json index ea1967b..b8c05b2 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "eslint" }, "dependencies": { + "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@tanstack/react-query": "^5.90.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6b5a5e..1827de9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@emotion/cache': + specifier: ^11.14.0 + version: 11.14.0 '@emotion/react': specifier: ^11.14.0 version: 11.14.0(@types/react@19.2.7)(react@19.2.0) diff --git a/public/icons/sidebar/chat.svg b/public/icons/sidebar/chat.svg new file mode 100644 index 0000000..d0e4046 --- /dev/null +++ b/public/icons/sidebar/chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/agent/page.tsx b/src/app/agent/page.tsx new file mode 100644 index 0000000..f8e73ee --- /dev/null +++ b/src/app/agent/page.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Image from 'next/image'; +import * as S from './style'; +import ChatSection from '@/components/ui/Agent/ChatSection/ui'; +import SearchSection from '@/components/ui/Agent/SearchSection/ui'; + +// 현재 특별한 백엔드 개발 진행이 없어 멋대로 고정된 입출력을 사용함 +const USER_MESSAGE = "최애의 사인 프로젝트의 CPU 사용량을 알고 싶어"; +const AI_MESSAGE = "좋습니다, 다음은 최애의 사인 프로젝트의 CPU 사용량 입니다."; + +export default function AgentPage() { + const [isChatStarted, setIsChatStarted] = useState(false); + const [streamedText, setStreamedText] = useState(""); + const [inputValue, setInputValue] = useState(""); + + useEffect(() => { + if (isChatStarted) { + let currentIndex = 0; + const interval = setInterval(() => { + if (currentIndex <= AI_MESSAGE.length) { + setStreamedText(AI_MESSAGE.slice(0, currentIndex)); + currentIndex++; + } else { + clearInterval(interval); + } + }, 50); + + return () => clearInterval(interval); + } + }, [isChatStarted]); + + const handleSearch = (e?: React.FormEvent) => { + e?.preventDefault(); + if (!inputValue.trim()) return; + if (!isChatStarted) { + setIsChatStarted(true); + } + setInputValue(""); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.nativeEvent.isComposing) { + handleSearch(); + } + }; + + return ( + + {!isChatStarted && ( + + M-ADP Logo + M-ADP + + )} + + {isChatStarted && ( + + )} + + + + ); +} diff --git a/src/app/agent/style.ts b/src/app/agent/style.ts new file mode 100644 index 0000000..09d5562 --- /dev/null +++ b/src/app/agent/style.ts @@ -0,0 +1,32 @@ +import styled from '@emotion/styled'; +import { colors } from '@/styles/colors'; + +export const Container = styled.div<{ isChat?: boolean }>` + display: flex; + flex-direction: column; + align-items: center; + justify-content: ${({ isChat }) => (isChat ? 'space-between' : 'center')}; + width: 100%; + height: 100%; + background-color: #ffffff; + padding: ${({ isChat }) => (isChat ? '40px 0' : '0')}; + box-sizing: border-box; +`; + +export const LogoSection = styled.div` + display: flex; + align-items: center; + gap: 28px; + margin-bottom: 60px; +`; + +export const LogoTitle = styled.span` + font-family: 'IBM Plex Sans KR', sans-serif; + font-weight: 700; + font-size: 80px; + line-height: normal; + color: ${colors.black[300]}; + margin: 0; +`; + +// Moved to components/ui/Agent/... diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b783888..595c4a0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import Sidebar from "@/components/ui/Sidebar/ui"; +import EmotionRegistry from './registry'; export const metadata: Metadata = { title: "M-ADP", @@ -14,10 +15,12 @@ export default function RootLayout({ return ( - -
- {children} -
+ + +
+ {children} +
+
); diff --git a/src/app/registry.tsx b/src/app/registry.tsx new file mode 100644 index 0000000..6c5c15d --- /dev/null +++ b/src/app/registry.tsx @@ -0,0 +1,27 @@ +'use client'; + +import React, { useState } from 'react'; +import { useServerInsertedHTML } from 'next/navigation'; +import { CacheProvider } from '@emotion/react'; +import createCache from '@emotion/cache'; + +export default function EmotionRegistry({ children }: { children: React.ReactNode }) { + const [cache] = useState(() => { + const cache = createCache({ key: 'css' }); + cache.compat = true; + return cache; + }); + + useServerInsertedHTML(() => { + return ( +