diff --git a/README.md b/README.md index e215bc4c..a091cd11 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,424 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# ๐Ÿš€ PlateerRAG -## Getting Started +
+ PlateerRAG ๋ฉ”์ธ ํ™”๋ฉด +
-First, run the development server: +
+ +> **์ฐจ์„ธ๋Œ€ AI ์›Œํฌํ”Œ๋กœ์šฐ ํ”Œ๋žซํผ** - ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ AI ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•˜๊ณ  ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ƒํ˜ธ์ž‘์šฉํ•˜์„ธ์š” + +[![Next.js](https://img.shields.io/badge/Next.js-15.3.2-black?style=flat-square&logo=next.js)](https://nextjs.org/) +[![React](https://img.shields.io/badge/React-19.0.0-blue?style=flat-square&logo=react)](https://reactjs.org/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/) +[![SCSS](https://img.shields.io/badge/SCSS-Styling-pink?style=flat-square&logo=sass)](https://sass-lang.com/) + +PlateerRAG๋Š” **๋น„์ฃผ์–ผ ์›Œํฌํ”Œ๋กœ์šฐ ์—๋””ํ„ฐ**๋กœ AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋Š” ํ˜์‹ ์ ์ธ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ์ฝ”๋”ฉ ์—†์ด **๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ**๋งŒ์œผ๋กœ LangChain ๊ธฐ๋ฐ˜์˜ AI ํŒŒ์ดํ”„๋ผ์ธ์„ ์„ค๊ณ„ํ•˜๊ณ , **์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…**์œผ๋กœ AI์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์†Œํ†ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## โœจ ์ฃผ์š” ๊ธฐ๋Šฅ + +### ๐ŸŽจ **๋น„์ฃผ์–ผ ์บ”๋ฒ„์Šค ์—๋””ํ„ฐ** + +
+ PlateerRAG ์บ”๋ฒ„์Šค ์—๋””ํ„ฐ +

๐Ÿ–ฑ๏ธ ์ง๊ด€์ ์ธ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ์ธํ„ฐํŽ˜์ด์Šค๋กœ AI ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ๊ตฌ์„ฑ

+
+ +- **LangChain ๋…ธ๋“œ ์ง€์›**: ChatOpenAI, ChatAnthropic, VectorStore ๋“ฑ ํ’๋ถ€ํ•œ AI ๋…ธ๋“œ +- **๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ**: ์ง๊ด€์ ์ธ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์„ฑ +- **์‹ค์‹œ๊ฐ„ ์—ฐ๊ฒฐ**: ๋…ธ๋“œ ๊ฐ„ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„ +- **์ž๋™ ์ €์žฅ**: LocalStorage ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์—… ๋‚ด์šฉ ๋ณด์กด +- **ํ…œํ”Œ๋ฆฟ ์‹œ์Šคํ…œ**: ๋ฏธ๋ฆฌ ์ œ์ž‘๋œ ์›Œํฌํ”Œ๋กœ์šฐ ํ…œํ”Œ๋ฆฟ ์ œ๊ณต + +
+ ์›Œํฌํ”Œ๋กœ์šฐ ํ…œํ”Œ๋ฆฟ +

โšก ๋‹ค์–‘ํ•œ ํ…œํ”Œ๋ฆฟ์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์›Œํฌํ”Œ๋กœ์šฐ ์‹œ์ž‘ํ•˜๊ธฐ

+
+ +### ๐Ÿ’ฌ **AI ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค** + +- **์›Œํฌํ”Œ๋กœ์šฐ ์—ฐ๋™**: ๊ตฌ์ถ•ํ•œ ์›Œํฌํ”Œ๋กœ์šฐ์™€ ์‹ค์‹œ๊ฐ„ ๋Œ€ํ™” +- **ํƒ€์ดํ•‘ ์• ๋‹ˆ๋ฉ”์ด์…˜**: ์„ธ๋ จ๋œ UI๋กœ AI ์‘๋‹ต ํ‘œ์‹œ +- **๋‹ค์ค‘ ์›Œํฌํ”Œ๋กœ์šฐ**: ์—ฌ๋Ÿฌ ์›Œํฌํ”Œ๋กœ์šฐ ์ค‘ ์„ ํƒํ•˜์—ฌ ๋Œ€ํ™” +- **๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ**: ์ฑ„ํŒ… ๊ธฐ๋ก ์ €์žฅ ๋ฐ ๊ด€๋ฆฌ +- **React Hot Toast**: ์•Œ๋ฆผ ์‹œ์Šคํ…œ์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ + +### ๐Ÿ“Š **ํ†ตํ•ฉ ๊ด€๋ฆฌ์„ผํ„ฐ** + +
+ ๊ด€๋ฆฌ์„ผํ„ฐ ์ฑ„ํŒ… ๋ฐ๋ชจ +

๐Ÿ’ฌ ๊ด€๋ฆฌ์„ผํ„ฐ์—์„œ ๋ฐ”๋กœ ์›Œํฌํ”Œ๋กœ์šฐ ํ…Œ์ŠคํŠธ ๋ฐ ์ฑ„ํŒ… ํ™•์ธ

+
+ +- **์‹คํ–‰ ๋ชจ๋‹ˆํ„ฐ๋ง**: ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰ ์ƒํƒœ ๋ฐ ์„ฑ๋Šฅ ์ถ”์  +- **๋””๋ฒ„๊ทธ ๋„๊ตฌ**: ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ์˜ ์ƒ์„ธ ๋กœ๊ทธ ์‹œ์Šคํ…œ +- **์„ค์ • ๊ด€๋ฆฌ**: ๊ธ€๋กœ๋ฒŒ ์„ค์ • ๋ฐ API ํ‚ค ๊ด€๋ฆฌ +- **์›Œํฌํ”Œ๋กœ์šฐ ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ**: ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์ œ๊ณต +- **์™„๋ฃŒ๋œ ์›Œํฌํ”Œ๋กœ์šฐ**: ์‹คํ–‰ ์™„๋ฃŒ๋œ ์ž‘์—…๋“ค์˜ ์ด๋ ฅ ๊ด€๋ฆฌ + +
+ ๋ฆฌ์†Œ์Šค ๋ชจ๋‹ˆํ„ฐ๋ง +

๐Ÿ“ˆ ์‹ค์‹œ๊ฐ„ ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰ ์ƒํƒœ ๋ฐ ๋ฆฌ์†Œ์Šค ๋ชจ๋‹ˆํ„ฐ๋ง

+
+ +### โšก **๊ณ ์„ฑ๋Šฅ ์•„ํ‚คํ…์ฒ˜** + +- **FastAPI ๋ฐฑ์—”๋“œ**: Python ๊ธฐ๋ฐ˜์˜ ๊ณ ์„ฑ๋Šฅ API ์„œ๋ฒ„ +- **Turbopack**: Next.js 15์˜ ๋น ๋ฅธ ๋ฒˆ๋“ค๋Ÿฌ ํ™œ์šฉ +- **๋ชจ๋“ˆํ™” ์„ค๊ณ„**: ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜์˜ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ +- **ํƒ€์ž… ์•ˆ์ „์„ฑ**: TypeScript๋กœ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ ๋ฐ ์•ˆ์ •์„ฑ ํ™•๋ณด + +## ๐Ÿ—๏ธ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ + +``` +plateerag/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ app/ +โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx # ๋ฉ”์ธ ๋žœ๋”ฉ ํŽ˜์ด์ง€ +โ”‚ โ”‚ โ”œโ”€โ”€ layout.tsx # ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ +โ”‚ โ”‚ โ”œโ”€โ”€ globals.css # ๊ธ€๋กœ๋ฒŒ ์Šคํƒ€์ผ +โ”‚ โ”‚ โ”œโ”€โ”€ HomePage.module.scss # ํ™ˆํŽ˜์ด์ง€ ์ „์šฉ ์Šคํƒ€์ผ +โ”‚ โ”‚ โ”œโ”€โ”€ config.js # ์„ค์ • ํŒŒ์ผ (API_BASE_URL ๋“ฑ) +โ”‚ โ”‚ โ”œโ”€โ”€ canvas/ # ๐ŸŽจ ๋น„์ฃผ์–ผ ์›Œํฌํ”Œ๋กœ์šฐ ์—๋””ํ„ฐ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx # ์บ”๋ฒ„์Šค ๋ฉ”์ธ ํŽ˜์ด์ง€ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ types.ts # ์บ”๋ฒ„์Šค ํƒ€์ž… ์ •์˜ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ # ์บ”๋ฒ„์Šค ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Canvas.tsx # ๋ฉ”์ธ ์บ”๋ฒ„์Šค (ReactFlow ๊ธฐ๋ฐ˜) +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Node.tsx # AI ๋…ธ๋“œ ์ปดํฌ๋„ŒํŠธ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Edge.tsx # ์—ฐ๊ฒฐ์„  ์ปดํฌ๋„ŒํŠธ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Header.tsx # ์บ”๋ฒ„์Šค ํ—ค๋” (์ €์žฅ/๋ถˆ๋Ÿฌ์˜ค๊ธฐ) +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ SideMenu.tsx # ์‚ฌ์ด๋“œ ๋ฉ”๋‰ด ์ปจํ…Œ์ด๋„ˆ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ ExecutionPanel.tsx # ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰ ํŒจ๋„ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ constants/ # ๋…ธ๋“œ ์ •์˜ ๋ฐ ์ƒ์ˆ˜ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ nodes.js # LangChain ๋…ธ๋“œ ๋ฐ์ดํ„ฐ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ workflow/ # ์›Œํฌํ”Œ๋กœ์šฐ ํ…œํ”Œ๋ฆฟ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ assets/ # ์บ”๋ฒ„์Šค ์Šคํƒ€์ผ (SCSS Modules) +โ”‚ โ”‚ โ”œโ”€โ”€ chat/ # ๐Ÿ’ฌ AI ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx # ์ฑ„ํŒ… ๋ฉ”์ธ ํŽ˜์ด์ง€ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ # ์ฑ„ํŒ… ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ChatInterface.tsx # ๋ฉ”์ธ ์ฑ„ํŒ… UI +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ChatContent.tsx # ์ฑ„ํŒ… ๋‚ด์šฉ ํ‘œ์‹œ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ WorkflowSelection.tsx # ์›Œํฌํ”Œ๋กœ์šฐ ์„ ํƒ๊ธฐ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ assets/ # ์ฑ„ํŒ… ์Šคํƒ€์ผ +โ”‚ โ”‚ โ”œโ”€โ”€ main/ # ๐Ÿ“Š ํ†ตํ•ฉ ๊ด€๋ฆฌ์„ผํ„ฐ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx # ๊ด€๋ฆฌ์„ผํ„ฐ ๋ฉ”์ธ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ # ๊ด€๋ฆฌ ๋„๊ตฌ ์ปดํฌ๋„ŒํŠธ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ MainPageContent.tsx # ๋ฉ”์ธ ๋Œ€์‹œ๋ณด๋“œ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Sidebar.tsx # ์‚ฌ์ด๋“œ๋ฐ” ๋„ค๋น„๊ฒŒ์ด์…˜ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ContentArea.tsx # ์ฝ˜ํ…์ธ  ์˜์—ญ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Executor.tsx # ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰๊ธฐ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Monitor.tsx # ์‹คํ–‰ ๋ชจ๋‹ˆํ„ฐ๋ง +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Settings.tsx # ์„ค์ • ๊ด€๋ฆฌ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Playground.tsx # ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ConfigViewer.tsx # ์„ค์ • ๋ทฐ์–ด +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CompletedWorkflows.tsx # ์™„๋ฃŒ ์›Œํฌํ”Œ๋กœ์šฐ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ CanvasIntroduction.tsx # ์บ”๋ฒ„์Šค ์†Œ๊ฐœ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ assets/ # ๊ด€๋ฆฌ์„ผํ„ฐ ์Šคํƒ€์ผ +โ”‚ โ”‚ โ”œโ”€โ”€ api/ # ๐Ÿ”— API ํด๋ผ์ด์–ธํŠธ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ workflowAPI.js # ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰ API +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ chatAPI.js # ์ฑ„ํŒ… API +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ nodeAPI.js # ๋…ธ๋“œ ๊ด€๋ฆฌ API +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ configAPI.js # ์„ค์ • API +โ”‚ โ”‚ โ”œโ”€โ”€ data/ # ๐Ÿ“Š ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ chatData.js # ์ฑ„ํŒ… ๋ฐ์ดํ„ฐ ๋ชจ๋ธ +โ”‚ โ”‚ โ”œโ”€โ”€ utils/ # ๐Ÿ› ๏ธ ๊ณตํ†ต ์œ ํ‹ธ๋ฆฌํ‹ฐ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ logger.ts # ๋””๋ฒ„๊ทธ ๋กœ๊ฑฐ ์‹œ์Šคํ…œ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ generateSha1Hash.ts # ํ•ด์‹œ ์ƒ์„ฑ๊ธฐ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ debug-guide.js # ๋””๋ฒ„๊ทธ ๊ฐ€์ด๋“œ +โ”‚ โ”‚ โ””โ”€โ”€ _common/ # ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ +โ”‚ โ”‚ โ””โ”€โ”€ components/ +โ”‚ โ”‚ โ”œโ”€โ”€ ToastProvider.jsx # ์•Œ๋ฆผ ์‹œ์Šคํ…œ +โ”‚ โ”‚ โ”œโ”€โ”€ nodeHook.ts # ๋…ธ๋“œ ๊ด€๋ฆฌ ํ›… +โ”‚ โ”‚ โ”œโ”€โ”€ sidebarConfig.ts # ์‚ฌ์ด๋“œ๋ฐ” ์„ค์ • +โ”‚ โ”‚ โ””โ”€โ”€ workflowStorage.js # ์›Œํฌํ”Œ๋กœ์šฐ ์ €์žฅ์†Œ +โ”‚ โ””โ”€โ”€ public/ # ์ •์  ํŒŒ์ผ (์•„์ด์ฝ˜, ์ด๋ฏธ์ง€) +โ”œโ”€โ”€ package.json # ํ”„๋กœ์ ํŠธ ์„ค์ • ๋ฐ ์˜์กด์„ฑ +โ”œโ”€โ”€ next.config.ts # Next.js ์„ค์ • +โ”œโ”€โ”€ tsconfig.json # TypeScript ์„ค์ • +โ”œโ”€โ”€ eslint.config.mjs # ESLint ์„ค์ • +โ”œโ”€โ”€ postcss.config.mjs # PostCSS ์„ค์ • +โ”œโ”€โ”€ DEBUG_GUIDE.md # ๋””๋ฒ„๊ทธ ์‹œ์Šคํ…œ ์‚ฌ์šฉ๋ฒ• +โ””โ”€โ”€ README.md # ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ +``` + +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ assets/ # ์ฑ„ํŒ… ์Šคํƒ€์ผ +โ”‚ โ”‚ โ”œโ”€โ”€ main/ # ๊ด€๋ฆฌ์„ผํ„ฐ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ # ๊ด€๋ฆฌ ๋„๊ตฌ ์ปดํฌ๋„ŒํŠธ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ assets/ # ๊ด€๋ฆฌ์„ผํ„ฐ ์Šคํƒ€์ผ +โ”‚ โ”‚ โ”œโ”€โ”€ api/ # API ๊ด€๋ จ ์œ ํ‹ธ๋ฆฌํ‹ฐ +โ”‚ โ”‚ โ”œโ”€โ”€ data/ # ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ +โ”‚ โ”‚ โ””โ”€โ”€ utils/ # ๊ณตํ†ต ์œ ํ‹ธ๋ฆฌํ‹ฐ +โ”‚ โ””โ”€โ”€ public/ # ์ •์  ํŒŒ์ผ +โ”œโ”€โ”€ package.json # ํ”„๋กœ์ ํŠธ ์„ค์ • +โ”œโ”€โ”€ next.config.ts # Next.js ์„ค์ • +โ”œโ”€โ”€ tsconfig.json # TypeScript ์„ค์ • +โ””โ”€โ”€ README.md # ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ + +```` + +## ๐Ÿค– ์ง€์›ํ•˜๋Š” AI ๋…ธ๋“œ + +PlateerRAG๋Š” **LangChain** ์ƒํƒœ๊ณ„์˜ ๋‹ค์–‘ํ•œ AI ๋…ธ๋“œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: + +### ๐Ÿ’ฌ **Chat Models** +- **ChatOpenAI**: GPT-4o, GPT-4, GPT-3.5 Turbo ์ง€์› +- **ChatAnthropic**: Claude ๋ชจ๋ธ ์‹œ๋ฆฌ์ฆˆ +- **Temperature ์กฐ์ ˆ**: ์ฐฝ์˜์„ฑ๊ณผ ์ผ๊ด€์„ฑ ์ œ์–ด +- **Stop Sequence**: ์ถœ๋ ฅ ์ œ์–ด ์˜ต์…˜ + +### ๐Ÿ”— **Chains & Agents** +- **LLMChain**: ๊ธฐ๋ณธ ์–ธ์–ด๋ชจ๋ธ ์ฒด์ธ +- **ConversationChain**: ๋Œ€ํ™”ํ˜• ์ฒด์ธ +- **Agent**: ์ž์œจ์  AI ์—์ด์ „ํŠธ +- **Tools**: ์™ธ๋ถ€ ๋„๊ตฌ ์—ฐ๋™ + +### ๐Ÿ“š **Memory & Storage** +- **VectorStore**: ๋ฒกํ„ฐ ๊ธฐ๋ฐ˜ ๋ฌธ์„œ ์ €์žฅ์†Œ +- **Memory**: ๋Œ€ํ™” ๊ธฐ์–ต ๊ด€๋ฆฌ +- **Document Loaders**: ๋‹ค์–‘ํ•œ ๋ฌธ์„œ ํ˜•์‹ ๋กœ๋“œ + +### ๐Ÿ”„ **Utility Nodes** +- **Input/Output**: ๋ฐ์ดํ„ฐ ์ž…์ถœ๋ ฅ ๋…ธ๋“œ +- **Transform**: ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋…ธ๋“œ +- **Conditional**: ์กฐ๊ฑด๋ถ€ ๋ถ„๊ธฐ ๋…ธ๋“œ + +## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ + +### 1. ํ™˜๊ฒฝ ์š”๊ตฌ์‚ฌํ•ญ + +- **Node.js** 18.17 ์ด์ƒ +- **npm**, **yarn**, **pnpm** ๋˜๋Š” **bun** + +### 2. ์„ค์น˜ ๋ฐ ์‹คํ–‰ ```bash +# ์ €์žฅ์†Œ ํด๋ก  +git clone https://github.com/your-org/plateerag.git +cd plateerag + +# ์˜์กด์„ฑ ์„ค์น˜ +npm install +# ๋˜๋Š” +yarn install + +# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ npm run dev -# or +# ๋˜๋Š” yarn dev -# or -pnpm dev -# or -bun dev +```` + +### 3. ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธ + +[http://localhost:3000](http://localhost:3000)์—์„œ PlateerRAG๋ฅผ ๋งŒ๋‚˜๋ณด์„ธ์š”! ๐ŸŽ‰ + +## ๐Ÿ“– ์‚ฌ์šฉ ๊ฐ€์ด๋“œ + +### ๐ŸŽฏ ์›Œํฌํ”Œ๋กœ์šฐ ์ƒ์„ฑํ•˜๊ธฐ + +1. **์บ”๋ฒ„์Šค ์—๋””ํ„ฐ** ์ ‘์† (`/canvas`) +2. ์ขŒ์ธก ๋…ธ๋“œ ํŒจ๋„์—์„œ **AI ๋…ธ๋“œ ์„ ํƒ** +3. **๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ**์œผ๋กœ ์บ”๋ฒ„์Šค์— ๋ฐฐ์น˜ +4. ๋…ธ๋“œ ๊ฐ„ **์—ฐ๊ฒฐ์„  ์ƒ์„ฑ**์œผ๋กœ ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์„ฑ +5. **์ €์žฅ** ํ›„ **์‹คํ–‰ ํ…Œ์ŠคํŠธ** + +### ๐Ÿ’ฌ AI์™€ ์ฑ„ํŒ…ํ•˜๊ธฐ + +1. **์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค** ์ ‘์† (`/chat`) +2. **์›Œํฌํ”Œ๋กœ์šฐ ์„ ํƒ** ๋ฒ„ํŠผ ํด๋ฆญ +3. ์›ํ•˜๋Š” ์›Œํฌํ”Œ๋กœ์šฐ ์„ ํƒ +4. **์ž์—ฐ์–ด๋กœ ๋Œ€ํ™”** ์‹œ์ž‘ +5. ์‹ค์‹œ๊ฐ„์œผ๋กœ AI ์‘๋‹ต ํ™•์ธ + +### ๐Ÿ“Š ์›Œํฌํ”Œ๋กœ์šฐ ๊ด€๋ฆฌํ•˜๊ธฐ + +
+ ์›Œํฌํ”Œ๋กœ์šฐ ์ œ์–ด ๋ฐ ๊ด€๋ฆฌ +

๐ŸŽ›๏ธ ์ง๊ด€์ ์ธ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์†์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•˜๊ณ  ์ œ์–ด

+
+ +1. **๊ด€๋ฆฌ์„ผํ„ฐ** ์ ‘์† (`/main`) +2. **์„ฑ๋Šฅ ๋Œ€์‹œ๋ณด๋“œ**์—์„œ ์‹คํ–‰ ํ˜„ํ™ฉ ํ™•์ธ +3. **์‹คํ–‰ ๋กœ๊ทธ**์—์„œ ๋””๋ฒ„๊น… ์ •๋ณด ๋ถ„์„ +4. **์„ค์ •**์—์„œ ์›Œํฌํ”Œ๋กœ์šฐ ์ตœ์ ํ™” + +
+ ์„ค์ • ๊ด€๋ฆฌ +

โš™๏ธ ํ•„์š”ํ•œ ๋ชจ๋“  ์„ค์ •์„ ํ•œ ๊ณณ์—์„œ ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ด€๋ฆฌ

+
+ +## ๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ + +### ์Šคํฌ๋ฆฝํŠธ ๋ช…๋ น์–ด + +```bash +# ๊ฐœ๋ฐœ ์„œ๋ฒ„ (Turbopack ์‚ฌ์šฉ) +npm run dev + +# ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ +npm run build + +# ํ”„๋กœ๋•์…˜ ์„œ๋ฒ„ ์‹คํ–‰ +npm run start + +# ์ฝ”๋“œ ๋ฆฐํŒ… +npm run lint + +# ์ฝ”๋“œ ์ž๋™ ์ˆ˜์ • +npm run lint:fix + +# ์ฝ”๋“œ ํฌ๋งคํŒ… +npm run format ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +### ๊ธฐ์ˆ  ์Šคํƒ + +#### ๐ŸŽจ **ํ”„๋ก ํŠธ์—”๋“œ** + +- **Next.js 15.3.2**: App Router ๊ธฐ๋ฐ˜์˜ ํ’€์Šคํƒ React ํ”„๋ ˆ์ž„์›Œํฌ +- **React 19**: ์ตœ์‹  React ๊ธฐ๋Šฅ ํ™œ์šฉ (Concurrent Features, Server Components) +- **TypeScript**: ํƒ€์ž… ์•ˆ์ „์„ฑ๊ณผ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ +- **SCSS Modules**: ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ์Šคํƒ€์ผ ์บก์Аํ™” +- **React Icons**: Feather Icons ์„ธํŠธ ํ™œ์šฉ +- **React Hot Toast**: ์„ธ๋ จ๋œ ์•Œ๋ฆผ ์‹œ์Šคํ…œ + +#### ๐Ÿ”ง **๊ฐœ๋ฐœ ๋„๊ตฌ** + +- **Turbopack**: Next.js 15์˜ ๊ณ ์† ๋ฒˆ๋“ค๋Ÿฌ (dev ๋ชจ๋“œ) +- **ESLint**: ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ์ผ๊ด€์„ฑ ๊ด€๋ฆฌ +- **Prettier**: ์ž๋™ ์ฝ”๋“œ ํฌ๋งคํŒ… +- **Husky**: Git ํ›…์„ ํ†ตํ•œ ํ’ˆ์งˆ ๊ด€๋ฆฌ + +#### ๐ŸŒ **๋ฐฑ์—”๋“œ ์—ฐ๋™** + +- **FastAPI**: Python ๊ธฐ๋ฐ˜ ๊ณ ์„ฑ๋Šฅ API ์„œ๋ฒ„ +- **LangChain**: AI ์ฒด์ธ ๊ตฌ์„ฑ์„ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ +- **RESTful API**: ํ‘œ์ค€ HTTP API ํ†ต์‹  + +#### ๐Ÿ“Š **๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ** + +- **LocalStorage**: ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ฐ์ดํ„ฐ ์ €์žฅ +- **React State**: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ ๊ด€๋ฆฌ +- **JSON**: ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” ๋ฐ API ํ†ต์‹  + +### ์ฝ”๋”ฉ ์Šคํƒ€์ผ + +- **TypeScript** ์—„๊ฒฉ ๋ชจ๋“œ ์‚ฌ์šฉ +- **ESLint + Prettier** ์ž๋™ ํฌ๋งคํŒ… +- **SCSS Modules**๋กœ ์Šคํƒ€์ผ ์บก์Аํ™” +- **์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜** ์•„ํ‚คํ…์ฒ˜ + +### ๐Ÿ” ๋””๋ฒ„๊ทธ ์‹œ์Šคํ…œ + +PlateerRAG๋Š” **์Šค๋งˆํŠธ ๋””๋ฒ„๊ทธ ๋กœ๊ฑฐ**๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: + +```javascript +import { devLog, prodLog } from '@/app/utils/logger'; + +// ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์ถœ๋ ฅ +devLog.log('๋””๋ฒ„๊ทธ ์ •๋ณด'); +devLog.error('๊ฐœ๋ฐœ์šฉ ์—๋Ÿฌ'); + +// ํ•ญ์ƒ ์ถœ๋ ฅ (์ค‘์š”ํ•œ ์—๋Ÿฌ์šฉ) +prodLog.error('์‹ฌ๊ฐํ•œ ์—๋Ÿฌ'); +``` + +#### ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†” ์ œ์–ด + +```javascript +// ๋””๋ฒ„๊ทธ ๋กœ๊ทธ ๊ฐ•์ œ ํ™œ์„ฑํ™” +enableDebugLogs(); + +// ๋””๋ฒ„๊ทธ ๋กœ๊ทธ ๋น„ํ™œ์„ฑํ™” +disableDebugLogs(); + +// ํ™˜๊ฒฝ ์„ค์ •์œผ๋กœ ๋ฆฌ์…‹ +resetDebugLogs(); + +// ํ˜„์žฌ ํ™˜๊ฒฝ ์ •๋ณด ํ™•์ธ +checkEnvironment(); +``` + +์ƒ์„ธํ•œ ์‚ฌ์šฉ๋ฒ•์€ [`DEBUG_GUIDE.md`](DEBUG_GUIDE.md)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. + +## ๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท ๊ฐค๋Ÿฌ๋ฆฌ + +
+ +### ๐ŸŽจ ์บ”๋ฒ„์Šค ์—๋””ํ„ฐ +์บ”๋ฒ„์Šค ์—๋””ํ„ฐ +ํ…œํ”Œ๋ฆฟ ์„ ํƒ + +### ๐Ÿ“Š ๊ด€๋ฆฌ์„ผํ„ฐ + +์ฑ„ํŒ… ๋ฐ๋ชจ +๋ฆฌ์†Œ์Šค ๋ชจ๋‹ˆํ„ฐ๋ง + +### โš™๏ธ ์›Œํฌํ”Œ๋กœ์šฐ ๊ด€๋ฆฌ + +์›Œํฌํ”Œ๋กœ์šฐ ์ œ์–ด +์„ค์ • ๊ด€๋ฆฌ + +
+ +## ๐ŸŒŸ ์ฃผ์š” ํŽ˜์ด์ง€ + +| ํŽ˜์ด์ง€ | ๊ฒฝ๋กœ | ์„ค๋ช… | +| ------------ | --------- | ----------------------------- | +| **ํ™ˆ** | `/` | ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ๋ฐ ๋ฉ”์ธ ๋žœ๋”ฉ | +| **์บ”๋ฒ„์Šค** | `/canvas` | ๋น„์ฃผ์–ผ ์›Œํฌํ”Œ๋กœ์šฐ ์—๋””ํ„ฐ | +| **์ฑ„ํŒ…** | `/chat` | AI ์›Œํฌํ”Œ๋กœ์šฐ ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค | +| **๊ด€๋ฆฌ์„ผํ„ฐ** | `/main` | ์›Œํฌํ”Œ๋กœ์šฐ ๊ด€๋ฆฌ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง | + +## ๐Ÿค ๊ธฐ์—ฌํ•˜๊ธฐ + +PlateerRAG ํ”„๋กœ์ ํŠธ์— ๊ธฐ์—ฌํ•ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! + +### ๊ธฐ์—ฌ ๋ฐฉ๋ฒ• + +1. **Fork** ์ด ์ €์žฅ์†Œ +2. **Feature ๋ธŒ๋žœ์น˜** ์ƒ์„ฑ (`git checkout -b feature/amazing-feature`) +3. **๋ณ€๊ฒฝ์‚ฌํ•ญ ์ปค๋ฐ‹** (`git commit -m 'Add amazing feature'`) +4. **๋ธŒ๋žœ์น˜์— Push** (`git push origin feature/amazing-feature`) +5. **Pull Request** ์ƒ์„ฑ + +### ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ • + +```bash +# ๊ฐœ๋ฐœ ์˜์กด์„ฑ ์„ค์น˜ +npm install + +# pre-commit ํ›… ์„ค์ • +npm run prepare + +# ์ฝ”๋“œ ํ’ˆ์งˆ ํ™•์ธ +npm run lint +npm run format +``` + +## ๐Ÿ“„ ๋ผ์ด์„ ์Šค + +์ด ํ”„๋กœ์ ํŠธ๋Š” [MIT ๋ผ์ด์„ ์Šค](LICENSE)๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +## ๐Ÿ‘ฅ ๊ฐœ๋ฐœํŒ€ -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +- **Plateer AI-LAB** - ํ”„๋กœ์ ํŠธ ๊ธฐํš ๋ฐ AI ์—”์ง„ +- **CocoRoF** - ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ ๋ฐ UI/UX +- **haesookimDev** - ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ ๋ฐ ์•„ํ‚คํ…์ฒ˜ -## Learn More +## ๐Ÿ”— ๊ด€๋ จ ๋งํฌ -To learn more about Next.js, take a look at the following resources: +- **GitHub ์ €์žฅ์†Œ**: [PlateerRAG Repository](https://github.com/plateer/plateerag) +- **๊ฐœ๋ฐœ ๋ฌธ์„œ**: [`DEBUG_GUIDE.md`](DEBUG_GUIDE.md) - ๋””๋ฒ„๊ทธ ์‹œ์Šคํ…œ ์‚ฌ์šฉ๋ฒ• +- **์ด์Šˆ ๋ฆฌํฌํŠธ**: GitHub Issues๋ฅผ ํ†ตํ•œ ๋ฒ„๊ทธ ์‹ ๊ณ  ๋ฐ ๊ธฐ๋Šฅ ์š”์ฒญ +- **๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ**: [Plateer ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ](https://tech.plateer.com) -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +--- -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +
-## Deploy on Vercel +**Made with โค๏ธ by Plateer AI-LAB** -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +[โญ Star this repo](https://github.com/plateer/plateerag) โ€ข [๐Ÿ› Report Bug](https://github.com/plateer/plateerag/issues) โ€ข [๐Ÿ’ก Request Feature](https://github.com/plateer/plateerag/issues) -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +
diff --git a/img/canvas.png b/img/canvas.png new file mode 100644 index 00000000..933028d9 Binary files /dev/null and b/img/canvas.png differ diff --git a/img/chatdemo.png b/img/chatdemo.png new file mode 100644 index 00000000..b3804b52 Binary files /dev/null and b/img/chatdemo.png differ diff --git a/img/main.png b/img/main.png new file mode 100644 index 00000000..b466a74d Binary files /dev/null and b/img/main.png differ diff --git a/img/resource_monitoring.png b/img/resource_monitoring.png new file mode 100644 index 00000000..eb7d1c38 Binary files /dev/null and b/img/resource_monitoring.png differ diff --git a/img/setting.png b/img/setting.png new file mode 100644 index 00000000..b34e5965 Binary files /dev/null and b/img/setting.png differ diff --git a/img/template.png b/img/template.png new file mode 100644 index 00000000..fcf6a134 Binary files /dev/null and b/img/template.png differ diff --git a/img/workflow_control.png b/img/workflow_control.png new file mode 100644 index 00000000..b70862e1 Binary files /dev/null and b/img/workflow_control.png differ diff --git a/src/app/HomePage.module.scss b/src/app/HomePage.module.scss index cf8cae4f..ac1e3ce3 100644 --- a/src/app/HomePage.module.scss +++ b/src/app/HomePage.module.scss @@ -4,6 +4,8 @@ $primary-blue: #2563eb; $primary-purple: #7c3aed; $primary-green: #059669; $primary-pink: #db2777; +$primary-orange: #ea580c; +$primary-indigo: #4f46e5; $gray-50: #f9fafb; $gray-100: #f3f4f6; $gray-200: #e5e7eb; @@ -47,32 +49,54 @@ $white: #ffffff; // Main Container .container { min-height: 100vh; - background: linear-gradient(135deg, $gray-50 0%, $white 100%); + background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 50%, #cbd5e1 100%); + position: relative; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 20% 30%, rgba(148, 163, 184, 0.08) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, rgba(100, 116, 139, 0.06) 0%, transparent 50%); + pointer-events: none; + } } // Header Styles .header { position: relative; - z-index: 10; + z-index: 100; + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(20px); + border-bottom: 1px solid rgba(148, 163, 184, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); } .nav { - max-width: 1280px; + max-width: 1400px; margin: 0 auto; - padding: 0 1rem; + padding: 0 2rem; @media (min-width: 640px) { - padding: 0 1.5rem; + padding: 0 3rem; } @media (min-width: 1024px) { - padding: 0 2rem; + padding: 0 4rem; + } + + @media (min-width: 1440px) { + padding: 0 5rem; } } .navContent { display: flex; - height: 4rem; + height: 4.5rem; align-items: center; justify-content: space-between; } @@ -83,10 +107,14 @@ $white: #ffffff; flex-shrink: 0; h1 { - font-size: 1.5rem; + font-size: 1.75rem; font-weight: 700; margin: 0; - @include gradient-text($primary-blue, $primary-purple); + background: linear-gradient(135deg, $gray-900, $gray-700); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + letter-spacing: -0.025em; } } @@ -98,102 +126,383 @@ $white: #ffffff; .getStartedBtn { @include button-primary; - background-color: $primary-blue; + background: linear-gradient(135deg, $primary-blue, $primary-purple); color: $white; + box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3); + font-weight: 600; &:hover { - background-color: color.adjust($primary-blue, $lightness: -10%); + background: linear-gradient(135deg, #1d4ed8, #6d28d9); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4); } svg { margin-left: 0.5rem; width: 1rem; height: 1rem; + transition: transform 0.2s ease; + } + + &:hover svg { + transform: translateX(2px); } } .secondaryBtn { @include button-primary; - background-color: $white; + background-color: rgba(255, 255, 255, 0.8); color: $gray-700; - border: 1px solid $gray-300; + border: 1px solid rgba(255, 255, 255, 0.3); + backdrop-filter: blur(10px); + font-weight: 500; &:hover { - background-color: $gray-50; + background-color: $white; color: $gray-900; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + svg { + margin-right: 0.5rem; + width: 1rem; + height: 1rem; } } // Main Content .main { position: relative; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + max-width: 100vw; + overflow-x: hidden; } .heroSection { - max-width: 1280px; - margin: 0 auto; - padding: 5rem 1rem 4rem; - text-align: center; + width: 90vw; + max-width: 1400px; + margin: 2rem auto; + padding: 4rem 2rem; + position: relative; + z-index: 1; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 24px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.08); + border: 1px solid rgba(255, 255, 255, 0.8); + + @media (min-width: 1024px) { + width: 85vw; + padding: 5rem 4rem; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + align-items: center; + text-align: left; + margin: 3rem auto; + } @media (min-width: 640px) { - padding-left: 1.5rem; - padding-right: 1.5rem; + width: 88vw; + padding: 4rem 3rem; } - @media (min-width: 1024px) { - padding-left: 2rem; - padding-right: 2rem; + @media (min-width: 1440px) { + width: 80vw; + padding: 6rem 5rem; + } +} + +.heroContent { + @media (max-width: 1023px) { + text-align: center; + } +} + +.heroLabel { + display: inline-block; + margin-bottom: 1.5rem; + + span { + background: linear-gradient(135deg, $gray-100, $gray-50); + border: 1px solid $gray-200; + border-radius: 50px; + padding: 0.5rem 1.5rem; + color: $gray-700; + font-size: 0.875rem; + font-weight: 500; + letter-spacing: 0.3px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } } .heroTitle { - font-size: 2.25rem; + font-size: 2.5rem; font-weight: 700; - line-height: 1.1; + line-height: 1.2; color: $gray-900; margin-bottom: 1.5rem; @media (min-width: 640px) { - font-size: 3.75rem; + font-size: 3.2rem; + } + + @media (min-width: 1024px) { + font-size: 3.8rem; } .highlight { display: block; - color: $primary-blue; + background: linear-gradient(135deg, $primary-blue, $primary-purple); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-top: 0.5rem; + font-weight: 600; } } .heroDescription { font-size: 1.125rem; - line-height: 1.75; + line-height: 1.7; color: $gray-600; - max-width: 48rem; - margin: 0 auto 2.5rem; + margin-bottom: 2rem; + max-width: 600px; + + @media (min-width: 1024px) { + margin-bottom: 2.5rem; + } +} + +.heroStats { + display: flex; + gap: 2rem; + margin-bottom: 2.5rem; + justify-content: center; + flex-wrap: wrap; + + @media (min-width: 1024px) { + justify-content: flex-start; + } +} + +.statItem { + text-align: center; + padding: 1rem; + background: $gray-50; + border-radius: 12px; + border: 1px solid $gray-100; + + strong { + display: block; + font-size: 1.5rem; + font-weight: 700; + color: $gray-900; + margin-bottom: 0.25rem; + } + + span { + font-size: 0.875rem; + color: $gray-600; + } } .heroActions { display: flex; align-items: center; - justify-content: center; - gap: 1.5rem; + gap: 1rem; flex-wrap: wrap; + justify-content: center; + + @media (min-width: 1024px) { + justify-content: flex-start; + } +} + +.heroVisual { + display: none; + + @media (min-width: 1024px) { + display: block; + position: relative; + } +} + +.heroImage { + position: relative; + perspective: 1000px; + + &::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: + radial-gradient(circle at center, rgba(255, 255, 255, 0.1) 0%, transparent 70%); + animation: float 6s ease-in-out infinite; + pointer-events: none; + } +} + +.mockupScreen { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 16px; + overflow: hidden; + transform: rotateY(-5deg) rotateX(2deg); + box-shadow: + 0 25px 50px rgba(0, 0, 0, 0.25), + 0 0 0 1px rgba(255, 255, 255, 0.1); + transition: transform 0.3s ease; + + &:hover { + transform: rotateY(-2deg) rotateX(1deg) scale(1.02); + } +} + +.mockupHeader { + padding: 1rem 1.5rem; + background: rgba(255, 255, 255, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + gap: 1rem; + + span { + color: $white; + font-size: 0.875rem; + font-weight: 500; + } +} + +.mockupDots { + display: flex; + gap: 0.5rem; + + span { + width: 12px; + height: 12px; + border-radius: 50%; + + &:nth-child(1) { background: #ff5f57; } + &:nth-child(2) { background: #ffbd2e; } + &:nth-child(3) { background: #28ca42; } + } +} + +.mockupContent { + padding: 2rem; + min-height: 200px; +} + +.mockupNodes { + display: flex; + align-items: center; + gap: 1rem; + justify-content: center; +} + +.mockupNode { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + padding: 1rem; + border-radius: 12px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + transition: all 0.3s ease; + + svg { + width: 24px; + height: 24px; + } + + span { + font-size: 0.75rem; + color: $white; + font-weight: 500; + } + + &.input { + background: rgba(37, 99, 235, 0.2); + color: #60a5fa; + + &:hover { + background: rgba(37, 99, 235, 0.3); + transform: translateY(-2px); + } + } + + &.ai { + background: rgba(124, 58, 237, 0.2); + color: #a78bfa; + + &:hover { + background: rgba(124, 58, 237, 0.3); + transform: translateY(-2px); + } + } + + &.output { + background: rgba(5, 150, 105, 0.2); + color: #34d399; + + &:hover { + background: rgba(5, 150, 105, 0.3); + transform: translateY(-2px); + } + } +} + +.mockupFlow { + width: 40px; + height: 2px; + background: linear-gradient(90deg, transparent, rgba(37, 99, 235, 0.8), transparent); + position: relative; + + &::after { + content: ''; + position: absolute; + right: -4px; + top: -2px; + width: 0; + height: 0; + border-left: 6px solid rgba(37, 99, 235, 0.8); + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; + } +} + +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-20px); } } .primaryBtn { @include button-primary; - background-color: $primary-blue; + background: linear-gradient(135deg, $primary-blue, $primary-purple); color: $white; - padding: 0.75rem 1.5rem; - box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); + padding: 1rem 2rem; + box-shadow: 0 10px 25px rgba(37, 99, 235, 0.3); + font-weight: 600; + border-radius: 12px; &:hover { - background-color: color.adjust($primary-blue, $lightness: -10%); + background: linear-gradient(135deg, #1d4ed8, #6d28d9); transform: translateY(-2px); - box-shadow: 0 20px 25px rgba(0, 0, 0, 0.15); + box-shadow: 0 15px 35px rgba(37, 99, 235, 0.4); } svg { - margin-right: 0.5rem; + margin-right: 0.75rem; width: 1.25rem; height: 1.25rem; } @@ -212,18 +521,32 @@ $white: #ffffff; // Features Section .featuresSection { - max-width: 1280px; - margin: 0 auto; - padding: 6rem 1rem; - + width: 90vw; + max-width: 1400px; + margin: 4rem auto; + padding: 6rem 2rem; + position: relative; + z-index: 1; + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(20px); + border-radius: 24px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.08); + border: 1px solid rgba(255, 255, 255, 0.8); + @media (min-width: 640px) { - padding-left: 1.5rem; - padding-right: 1.5rem; + width: 88vw; + padding: 6rem 3rem; } @media (min-width: 1024px) { - padding-left: 2rem; - padding-right: 2rem; + width: 85vw; + margin: 6rem auto; + padding: 8rem 4rem; + } + + @media (min-width: 1440px) { + width: 80vw; + padding: 10rem 5rem; } } @@ -232,20 +555,25 @@ $white: #ffffff; margin-bottom: 4rem; h2 { - font-size: 1.875rem; - font-weight: 700; - color: $gray-900; + font-size: 2.5rem; + font-weight: 800; + background: linear-gradient(135deg, $gray-900, $primary-blue); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; margin-bottom: 1rem; @media (min-width: 640px) { - font-size: 2.25rem; + font-size: 3rem; } } p { - font-size: 1.125rem; + font-size: 1.25rem; color: $gray-600; margin: 0; + max-width: 600px; + margin: 0 auto; } } @@ -256,135 +584,270 @@ $white: #ffffff; @media (min-width: 640px) { grid-template-columns: repeat(2, 1fr); + gap: 2.5rem; } @media (min-width: 1024px) { grid-template-columns: repeat(3, 1fr); + gap: 3rem; + } + + @media (min-width: 1440px) { + grid-template-columns: repeat(3, 1fr); + gap: 4rem; + } + + @media (min-width: 1600px) { + grid-template-columns: repeat(3, 1fr); + gap: 5rem; } } .featureCard { position: relative; + height: 100%; .cardBackground { position: absolute; inset: 0; - border-radius: 0.5rem; - opacity: 0.25; - filter: blur(4px); - transition: opacity 0.2s ease-in-out; + border-radius: 1rem; + opacity: 0.1; + transition: all 0.3s ease; &.blue { - background: linear-gradient(to right, $primary-blue, $primary-purple); + background: linear-gradient(135deg, $primary-blue, $primary-purple); } &.purple { - background: linear-gradient(to right, $primary-purple, $primary-pink); + background: linear-gradient(135deg, $primary-purple, $primary-pink); } &.green { - background: linear-gradient(to right, $primary-green, $primary-blue); + background: linear-gradient(135deg, $primary-green, $primary-blue); + } + + &.orange { + background: linear-gradient(135deg, $primary-orange, #f59e0b); + } + + &.pink { + background: linear-gradient(135deg, $primary-pink, $primary-purple); + } + + &.indigo { + background: linear-gradient(135deg, $primary-indigo, $primary-blue); } } - &:hover .cardBackground { - opacity: 0.4; + &:hover { + .cardBackground { + opacity: 0.15; + transform: scale(1.02); + } + + .cardContent { + transform: translateY(-4px); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + } } .cardContent { position: relative; background-color: $white; - padding: 2rem; - border-radius: 0.5rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); - @include card-hover; + padding: 2.5rem 2rem; + border-radius: 1rem; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); + border: 1px solid rgba(255, 255, 255, 0.2); + transition: all 0.3s ease; + height: 100%; + display: flex; + flex-direction: column; } .cardHeader { display: flex; - align-items: center; - margin-bottom: 1rem; + align-items: flex-start; + margin-bottom: 1.5rem; svg { - width: 2rem; - height: 2rem; + width: 2.5rem; + height: 2.5rem; flex-shrink: 0; - - &.blue { - color: $primary-blue; - } - - &.purple { - color: $primary-purple; - } - - &.green { - color: $primary-green; - } + margin-top: 0.25rem; + + &.blue { color: $primary-blue; } + &.purple { color: $primary-purple; } + &.green { color: $primary-green; } + &.orange { color: $primary-orange; } + &.pink { color: $primary-pink; } + &.indigo { color: $primary-indigo; } } h3 { - font-size: 1.125rem; - font-weight: 500; + font-size: 1.375rem; + font-weight: 600; color: $gray-900; margin: 0 0 0 1rem; + line-height: 1.3; } } .cardDescription { color: $gray-600; - margin: 0; - line-height: 1.6; + margin: 0 0 1.5rem 0; + line-height: 1.7; + font-size: 1rem; + flex-grow: 1; + } + + .cardFeatures { + display: flex; + flex-direction: column; + gap: 0.5rem; + + span { + font-size: 0.875rem; + color: $gray-500; + display: flex; + align-items: center; + + &::before { + content: 'โœ“'; + color: $primary-green; + font-weight: 600; + margin-right: 0.5rem; + } + } } } // CTA Section .ctaSection { - background-color: $primary-blue; - color: $white; + width: 90vw; + max-width: 1400px; + margin: 4rem auto; + position: relative; + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(20px); + border-radius: 24px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.08); + border: 1px solid rgba(255, 255, 255, 0.8); + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + linear-gradient(135deg, $gray-50 0%, $white 100%), + radial-gradient(circle at 30% 20%, rgba(37, 99, 235, 0.03) 0%, transparent 50%), + radial-gradient(circle at 70% 80%, rgba(124, 58, 237, 0.03) 0%, transparent 50%); + pointer-events: none; + } + + @media (min-width: 640px) { + width: 88vw; + } + + @media (min-width: 1024px) { + width: 85vw; + margin: 6rem auto; + } + + @media (min-width: 1440px) { + width: 80vw; + } .ctaContent { - max-width: 1280px; + max-width: 900px; margin: 0 auto; - padding: 4rem 1rem; + padding: 6rem 2rem; text-align: center; + position: relative; + z-index: 1; @media (min-width: 640px) { - padding-left: 1.5rem; - padding-right: 1.5rem; + padding: 7rem 3rem; } @media (min-width: 1024px) { - padding-left: 2rem; - padding-right: 2rem; + padding: 8rem 4rem; } - h2 { - font-size: 1.875rem; - font-weight: 700; - margin-bottom: 1rem; + @media (min-width: 1440px) { + padding: 10rem 5rem; + } + } + + .ctaLabel { + margin-bottom: 1.5rem; + + span { + background: linear-gradient(135deg, $gray-100, $gray-50); + border: 1px solid $gray-200; + border-radius: 50px; + padding: 0.5rem 1.5rem; + color: $gray-700; + font-size: 0.875rem; + font-weight: 500; + letter-spacing: 0.3px; + display: inline-block; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + } + } - @media (min-width: 640px) { - font-size: 2.25rem; - } + h2 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 1.5rem; + color: $gray-900; + + @media (min-width: 640px) { + font-size: 3.2rem; } + } - p { - font-size: 1.125rem; - color: rgba(255, 255, 255, 0.8); - margin-bottom: 2rem; + p { + font-size: 1.25rem; + color: $gray-600; + margin-bottom: 3rem; + line-height: 1.6; + } + + .ctaActions { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 2rem; + } + + .ctaNote { + span { + font-size: 0.875rem; + color: $gray-500; + font-style: italic; } } } .ctaBtn { @include button-primary; - background-color: $white; - color: $primary-blue; - box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); - + background: linear-gradient(135deg, $primary-blue, $primary-purple); + color: $white; + box-shadow: 0 8px 20px rgba(37, 99, 235, 0.25); + border: none; + font-weight: 600; + padding: 1rem 2rem; + border-radius: 12px; + &:hover { - background-color: $gray-50; + background: linear-gradient(135deg, #1d4ed8, #6d28d9); + transform: translateY(-2px); + box-shadow: 0 12px 30px rgba(37, 99, 235, 0.35); } svg { @@ -394,23 +857,67 @@ $white: #ffffff; } } +.ctaSecondaryBtn { + @include button-primary; + background: $gray-50; + color: $gray-700; + border: 1px solid $gray-200; + font-weight: 600; + padding: 1rem 2rem; + border-radius: 12px; + + &:hover { + background: $white; + color: $gray-900; + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); + } + + svg { + margin-right: 0.5rem; + width: 1.25rem; + height: 1.25rem; + } +} + // Footer .footer { - background-color: $gray-50; + background: linear-gradient(135deg, #1f2937, #111827); + color: $white; + position: relative; + width: 100vw; + margin-left: calc(-50vw + 50%); + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 20% 50%, rgba(37, 99, 235, 0.1) 0%, transparent 50%), + radial-gradient(circle at 80% 50%, rgba(124, 58, 237, 0.1) 0%, transparent 50%); + pointer-events: none; + } .footerContent { - max-width: 1280px; + max-width: 1400px; margin: 0 auto; - padding: 3rem 1rem; + padding: 5rem 2rem 3rem; + position: relative; + z-index: 1; @media (min-width: 640px) { - padding-left: 1.5rem; - padding-right: 1.5rem; + padding: 5rem 3rem 3rem; } @media (min-width: 1024px) { - padding-left: 2rem; - padding-right: 2rem; + padding: 6rem 4rem 3rem; + } + + @media (min-width: 1440px) { + padding: 8rem 5rem 4rem; } } @@ -422,20 +929,19 @@ $white: #ffffff; gap: 1rem; .footerBrand { - display: flex; - align-items: center; - gap: 1rem; - h3 { - font-size: 1.125rem; - font-weight: 600; - color: $gray-900; - margin: 0; + font-size: 1.5rem; + font-weight: 700; + background: linear-gradient(135deg, $white, #e2e8f0); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin: 0 0 0.5rem 0; } p { - font-size: 0.875rem; - color: $gray-600; + font-size: 1rem; + color: rgba(255, 255, 255, 0.8); margin: 0; } } @@ -443,33 +949,44 @@ $white: #ffffff; .footerSocial { display: flex; align-items: center; - gap: 1.5rem; + gap: 1rem; a { - color: $gray-400; - transition: color 0.2s ease-in-out; + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 12px; + color: rgba(255, 255, 255, 0.8); + transition: all 0.3s ease; + backdrop-filter: blur(10px); &:hover { - color: $gray-500; + background: rgba(255, 255, 255, 0.2); + color: $white; + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); } svg { - width: 1.5rem; - height: 1.5rem; + width: 1.25rem; + height: 1.25rem; } } } } .footerBottom { - margin-top: 2rem; - border-top: 1px solid $gray-200; + border-top: 1px solid rgba(255, 255, 255, 0.1); padding-top: 2rem; text-align: center; p { font-size: 0.875rem; - color: $gray-500; + color: rgba(255, 255, 255, 0.6); margin: 0; } } diff --git a/src/app/chat/assets/ChatContent.module.scss b/src/app/chat/assets/ChatContent.module.scss index de94e921..c43bdfd9 100644 --- a/src/app/chat/assets/ChatContent.module.scss +++ b/src/app/chat/assets/ChatContent.module.scss @@ -41,11 +41,14 @@ $white: #ffffff; padding: 2rem; transition: all 0.5s ease; overflow: hidden; + height: 100vh; + max-height: 100vh; .container { width: 100%; max-width: 1200px; margin: 0 auto; + height: 100%; } } diff --git a/src/app/chat/assets/ChatInterface.module.scss b/src/app/chat/assets/ChatInterface.module.scss new file mode 100644 index 00000000..33df9d2c --- /dev/null +++ b/src/app/chat/assets/ChatInterface.module.scss @@ -0,0 +1,405 @@ +// Color Variables +$primary-blue: #2563eb; +$primary-green: #059669; +$primary-red: #dc2626; +$gray-50: #f9fafb; +$gray-100: #f3f4f6; +$gray-200: #e5e7eb; +$gray-300: #d1d5db; +$gray-400: #9ca3af; +$gray-500: #6b7280; +$gray-600: #4b5563; +$gray-700: #374151; +$gray-800: #1f2937; +$gray-900: #111827; +$white: #ffffff; + +.container { + width: 100%; + height: 85vh; + max-height: 85vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +// Header +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + border-bottom: 1px solid $gray-200; + flex-shrink: 0; + background: $white; +} + +.headerInfo { + display: flex; + align-items: center; + gap: 1rem; + + h2 { + font-size: 1.5rem; + font-weight: 700; + color: $gray-900; + margin: 0; + } + + p { + color: $gray-600; + margin: 0; + font-size: 0.9rem; + } +} + +.backButton { + background: $white; + border: 2px solid $gray-200; + border-radius: 0.5rem; + padding: 0.75rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + + svg { + font-size: 1.25rem; + color: $gray-600; + } + + &:hover { + border-color: $primary-blue; + background: $gray-50; + + svg { + color: $primary-blue; + } + } +} + +.chatCount { + display: flex; + align-items: center; + gap: 0.5rem; + color: $gray-600; + font-size: 0.9rem; + font-weight: 500; + + svg { + width: 16px; + height: 16px; + color: $primary-blue; + } +} + +// Chat Container +.chatContainer { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + +.loadingState { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: $gray-500; + gap: 1rem; + + p { + font-size: 1rem; + margin: 0; + } +} + +.loadingSpinner { + width: 32px; + height: 32px; + border: 3px solid $gray-200; + border-top: 3px solid $primary-blue; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +// Messages Area +.messagesArea { + flex: 1; + overflow-y: auto; + padding: 1.5rem 0; + display: flex; + flex-direction: column; + gap: 1.5rem; + min-height: 0; + + /* ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ๋ง */ + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background: $gray-100; + border-radius: 3px; + } + + &::-webkit-scrollbar-thumb { + background: $gray-300; + border-radius: 3px; + + &:hover { + background: $gray-400; + } + } +} + +.emptyState { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + text-align: center; + color: $gray-500; + + .emptyIcon { + width: 3rem; + height: 3rem; + margin-bottom: 1rem; + opacity: 0.5; + } + + h3 { + font-size: 1.25rem; + font-weight: 600; + margin: 0 0 0.5rem 0; + color: $gray-700; + } + + p { + margin: 0; + line-height: 1.6; + } +} + +// Message Exchange +.messageExchange { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.userMessage { + display: flex; + flex-direction: column; + align-items: flex-end; + padding-right: 1rem; + + .messageContent { + background: linear-gradient(135deg, $primary-blue, #3b82f6); + color: $white; + padding: 0.75rem 1rem; + border-radius: 1rem 1rem 0.25rem 1rem; + max-width: 70%; + word-wrap: break-word; + box-shadow: 0 2px 8px rgba($primary-blue, 0.25); + } + + .messageTime { + font-size: 0.75rem; + color: $gray-500; + margin-top: 0.25rem; + } +} + +.botMessage { + display: flex; + flex-direction: column; + align-items: flex-start; + + .messageContent { + background: $white; + border: 1px solid $gray-200; + color: $gray-900; + padding: 0.75rem 1rem; + border-radius: 1rem 1rem 1rem 0.25rem; + max-width: 70%; + word-wrap: break-word; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + } +} + +// Typing Indicator +.typingIndicator { + display: flex; + align-items: center; + gap: 0.25rem; + + span { + width: 6px; + height: 6px; + background-color: $gray-400; + border-radius: 50%; + animation: typing 1.4s infinite ease-in-out; + + &:nth-child(1) { + animation-delay: -0.32s; + } + + &:nth-child(2) { + animation-delay: -0.16s; + } + } +} + +@keyframes typing { + + 0%, + 80%, + 100% { + transform: scale(0.8); + opacity: 0.5; + } + + 40% { + transform: scale(1); + opacity: 1; + } +} + +// Input Area +.inputArea { + flex-shrink: 0; + border-top: 1px solid $gray-200; + padding: 1rem 0 0 0; + background: $white; +} + +.inputContainer { + display: flex; + gap: 0.75rem; + align-items: flex-end; +} + +.messageInput { + flex: 1; + padding: 0.75rem 1rem; + border: 2px solid $gray-200; + border-radius: 1rem; + font-size: 0.95rem; + resize: none; + outline: none; + transition: all 0.2s ease; + background: $white; + + &:focus { + border-color: $primary-blue; + box-shadow: 0 0 0 3px rgba($primary-blue, 0.1); + } + + &:disabled { + background: $gray-50; + cursor: not-allowed; + } + + &::placeholder { + color: $gray-400; + } +} + +.sendButton { + background: $primary-blue; + color: $white; + border: none; + border-radius: 1rem; + padding: 0.75rem 1rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + min-width: 48px; + height: 48px; + + &:hover:not(.disabled) { + background: #1d4ed8; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba($primary-blue, 0.3); + } + + &:active:not(.disabled) { + transform: translateY(0); + } + + &.disabled { + background: $gray-300; + cursor: not-allowed; + } + + svg { + font-size: 1.1rem; + } +} + +.miniSpinner { + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top: 2px solid currentColor; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +.executingNote { + margin: 0.5rem 0 0 0; + font-size: 0.85rem; + color: $primary-blue; + font-weight: 500; +} + +.errorNote { + margin: 0.5rem 0 0 0; + font-size: 0.85rem; + color: $primary-red; + font-weight: 500; +} + +// Responsive Design +@media (max-width: 768px) { + .header { + flex-direction: column; + align-items: stretch; + gap: 1rem; + } + + .headerInfo { + justify-content: space-between; + } + + .userMessage .messageContent, + .botMessage .messageContent { + max-width: 85%; + } + + .inputContainer { + gap: 0.5rem; + } + + .messageInput { + font-size: 16px; // iOS zoom ๋ฐฉ์ง€ + } +} \ No newline at end of file diff --git a/src/app/chat/assets/WorkflowSelection.module.scss b/src/app/chat/assets/WorkflowSelection.module.scss index 3e63f442..953361af 100644 --- a/src/app/chat/assets/WorkflowSelection.module.scss +++ b/src/app/chat/assets/WorkflowSelection.module.scss @@ -64,7 +64,7 @@ $white: #ffffff; display: flex; align-items: center; justify-content: center; - + svg { font-size: 1.25rem; color: $gray-600; @@ -73,7 +73,7 @@ $white: #ffffff; &:hover { border-color: $primary-blue; background: $gray-50; - + svg { color: $primary-blue; } @@ -133,8 +133,13 @@ $white: #ffffff; } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } } .filterButton { @@ -167,23 +172,23 @@ $white: #ffffff; padding-top: 1rem; gap: 1rem; overflow-y: auto; - max-height: 380px; + max-height: 670px; padding-right: 0.5rem; - + /* ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ๋ง */ &::-webkit-scrollbar { width: 6px; } - + &::-webkit-scrollbar-track { background: $gray-100; border-radius: 3px; } - + &::-webkit-scrollbar-thumb { background: $gray-300; border-radius: 3px; - + &:hover { background: $gray-400; } @@ -192,36 +197,63 @@ $white: #ffffff; .workflowCard { background: $white; - border: 2px solid $gray-200; - border-radius: 0.75rem; - padding: 1rem; - transition: all 0.2s ease-in-out; + border: 1px solid $gray-200; + border-radius: 1rem; + padding: 1.25rem; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; position: relative; - min-height: 160px; - max-height: 160px; + min-height: 205px; display: flex; flex-direction: column; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 1rem; + background: linear-gradient(135deg, rgba($primary-blue, 0.02), rgba($primary-blue, 0.05)); + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; + } &:hover:not(.disabled) { - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); - border-color: $primary-blue; + transform: translateY(-3px); + box-shadow: + 0 10px 30px rgba(0, 0, 0, 0.1), + 0 4px 15px rgba($primary-blue, 0.15); + border-color: rgba($primary-blue, 0.3); + + &::before { + opacity: 1; + } } &:active:not(.disabled) { transform: translateY(-1px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + box-shadow: + 0 5px 20px rgba(0, 0, 0, 0.08), + 0 2px 8px rgba($primary-blue, 0.12); } &.disabled { - opacity: 0.6; + opacity: 0.5; cursor: not-allowed; - + background: $gray-50; + &:hover { transform: none; - box-shadow: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); border-color: $gray-200; + + &::before { + opacity: 0; + } } } } @@ -229,47 +261,52 @@ $white: #ffffff; .cardHeader { display: flex; justify-content: space-between; - align-items: center; - margin-bottom: 0.75rem; + align-items: flex-start; + margin-bottom: 1rem; } .workflowIcon { - width: 2rem; - height: 2rem; - background: rgba($primary-blue, 0.1); - border-radius: 0.5rem; + width: 2.25rem; + height: 2.25rem; + background: linear-gradient(135deg, rgba($primary-blue, 0.1), rgba($primary-blue, 0.15)); + border-radius: 0.75rem; display: flex; align-items: center; justify-content: center; + border: 1px solid rgba($primary-blue, 0.1); svg { - width: 1rem; - height: 1rem; + width: 1.125rem; + height: 1.125rem; color: $primary-blue; } } .status { - padding: 0.2rem 0.5rem; - border-radius: 0.75rem; - font-size: 0.65rem; + padding: 0.3rem 0.7rem; + border-radius: 0.5rem; + font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; + border: 1px solid transparent; &.statusActive { - background: rgba($primary-green, 0.1); + background: linear-gradient(135deg, rgba($primary-green, 0.1), rgba($primary-green, 0.15)); color: $primary-green; + border-color: rgba($primary-green, 0.2); } &.statusDraft { - background: rgba($primary-yellow, 0.1); + background: linear-gradient(135deg, rgba($primary-yellow, 0.1), rgba($primary-yellow, 0.15)); color: $primary-yellow; + border-color: rgba($primary-yellow, 0.2); } &.statusArchived { - background: rgba($gray-500, 0.1); + background: linear-gradient(135deg, rgba($gray-500, 0.1), rgba($gray-500, 0.15)); color: $gray-500; + border-color: rgba($gray-500, 0.2); } } @@ -282,14 +319,15 @@ $white: #ffffff; } .workflowName { - font-size: 0.95rem; - font-weight: 600; + font-size: 1rem; + font-weight: 700; color: $gray-900; margin: 0 0 0.5rem 0; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + letter-spacing: -0.01em; } .workflowDescription { @@ -309,34 +347,43 @@ $white: #ffffff; line-height: 1.4; margin: 0 0 0.75rem 0; font-size: 0.75rem; - padding: 0.4rem; - background: rgba($primary-red, 0.1); - border-radius: 0.375rem; - border-left: 2px solid $primary-red; + padding: 0.5rem; + background: linear-gradient(135deg, rgba($primary-red, 0.05), rgba($primary-red, 0.1)); + border-radius: 0.5rem; + border-left: 3px solid $primary-red; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; + border: 1px solid rgba($primary-red, 0.15); } .workflowMeta { display: flex; flex-direction: column; - gap: 0.3rem; + gap: 0.4rem; margin-top: auto; + padding-top: 0.5rem; + border-top: 1px solid $gray-100; } .metaItem { display: flex; align-items: center; - gap: 0.3rem; + gap: 0.4rem; color: $gray-500; font-size: 0.75rem; + font-weight: 500; svg { - width: 0.75rem; - height: 0.75rem; + width: 0.8rem; + height: 0.8rem; + color: $gray-400; + } + + span { + color: $gray-600; } } diff --git a/src/app/chat/components/ChatContent.tsx b/src/app/chat/components/ChatContent.tsx index 1a9f4602..b504727d 100644 --- a/src/app/chat/components/ChatContent.tsx +++ b/src/app/chat/components/ChatContent.tsx @@ -3,15 +3,32 @@ import styles from '@/app/chat/assets/ChatContent.module.scss'; import { LuWorkflow } from "react-icons/lu"; import { IoChatbubblesOutline } from "react-icons/io5"; import WorkflowSelection from './WorkflowSelection'; +import ChatInterface from './ChatInterface'; const ChatContent: React.FC = () => { - const [currentView, setCurrentView] = useState<'welcome' | 'workflow'>('welcome'); + const [currentView, setCurrentView] = useState<'welcome' | 'workflow' | 'chat'>('welcome'); + const [selectedWorkflow, setSelectedWorkflow] = useState(null); const handleWorkflowSelect = (workflow: any) => { - // ์›Œํฌํ”Œ๋กœ์šฐ ์„ ํƒ ํ›„ ๋กœ์ง (๋‚˜์ค‘์— ๊ตฌํ˜„) - console.log('Selected workflow:', workflow); + setSelectedWorkflow(workflow); + setCurrentView('chat'); }; + // ์ฑ„ํŒ… ํ™”๋ฉด + if (currentView === 'chat' && selectedWorkflow) { + return ( +
+
+ setCurrentView('workflow')} + /> +
+
+ ); + } + + // ์›Œํฌํ”Œ๋กœ์šฐ ์„ ํƒ ํ™”๋ฉด if (currentView === 'workflow') { return (
@@ -25,6 +42,7 @@ const ChatContent: React.FC = () => { ); } + // ์›ฐ์ปด ํ™”๋ฉด return (
diff --git a/src/app/chat/components/ChatInterface.tsx b/src/app/chat/components/ChatInterface.tsx new file mode 100644 index 00000000..2b120c7f --- /dev/null +++ b/src/app/chat/components/ChatInterface.tsx @@ -0,0 +1,270 @@ +'use client'; +import React, { useState, useEffect, useRef } from 'react'; +import { + FiSend, + FiArrowLeft, + FiMessageSquare, + FiClock, +} from 'react-icons/fi'; +import styles from '@/app/chat/assets/ChatInterface.module.scss'; +import { getWorkflowIOLogs, executeWorkflowById } from '@/app/api/workflowAPI'; +import toast from 'react-hot-toast'; + +interface Workflow { + id: string; + name: string; + description?: string; + createdAt?: string; + lastModified?: string; + author: string; + nodeCount: number; + status: 'active' | 'draft' | 'archived'; + filename?: string; + error?: string; +} + +interface IOLog { + log_id: number | string; + workflow_name: string; + workflow_id: string; + input_data: string; + output_data: string; + updated_at: string; +} + +interface ChatInterfaceProps { + workflow: Workflow; + onBack: () => void; +} + +const ChatInterface: React.FC = ({ workflow, onBack }) => { + const [ioLogs, setIOLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [executing, setExecuting] = useState(false); + const [error, setError] = useState(null); + const [inputMessage, setInputMessage] = useState(''); + const [pendingLogId, setPendingLogId] = useState(null); + + const messagesRef = useRef(null); + + useEffect(() => { + loadChatLogs(); + }, [workflow]); + + useEffect(() => { + scrollToBottom(); + }, [ioLogs]); + + const scrollToBottom = () => { + if (messagesRef.current) { + messagesRef.current.scrollTop = messagesRef.current.scrollHeight; + } + }; + + const loadChatLogs = async () => { + try { + setLoading(true); + setError(null); + const logs = await getWorkflowIOLogs(workflow.name, workflow.id); + setIOLogs((logs as any).in_out_logs || []); + setPendingLogId(null); + } catch (err) { + setError('์ฑ„ํŒ… ๊ธฐ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); + setIOLogs([]); + } finally { + setLoading(false); + } + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleString('ko-KR', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + }; + + const executeWorkflow = async () => { + if (!inputMessage.trim()) { + return; + } + + setError(null); + setExecuting(true); + const tempId = `pending-${Date.now()}`; + setPendingLogId(tempId); + + // ์ž„์‹œ ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€ + setIOLogs((prev) => [ + ...prev, + { + log_id: tempId, + workflow_name: workflow.name, + workflow_id: workflow.id, + input_data: inputMessage, + output_data: '', + updated_at: new Date().toISOString(), + }, + ]); + + const currentMessage = inputMessage; + setInputMessage(''); + + try { + const result: any = await executeWorkflowById( + workflow.name, + workflow.id, + currentMessage, + ); + + // ๊ฒฐ๊ณผ๋กœ ์ž„์‹œ ๋ฉ”์‹œ์ง€ ์—…๋ฐ์ดํŠธ + setIOLogs((prev) => + prev.map((log) => + String(log.log_id) === tempId + ? { + ...log, + output_data: result.outputs + ? JSON.stringify(result.outputs) + : result.message || '์ฒ˜๋ฆฌ ์™„๋ฃŒ', + updated_at: new Date().toISOString(), + } + : log, + ), + ); + setPendingLogId(null); + } catch (err) { + // ์—๋Ÿฌ๋กœ ์ž„์‹œ ๋ฉ”์‹œ์ง€ ์—…๋ฐ์ดํŠธ + setIOLogs((prev) => + prev.map((log) => + String(log.log_id) === tempId + ? { + ...log, + output_data: err instanceof Error ? err.message : '์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', + updated_at: new Date().toISOString(), + } + : log, + ), + ); + setPendingLogId(null); + toast.error('๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'); + } finally { + setExecuting(false); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey && !executing) { + e.preventDefault(); + executeWorkflow(); + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+

{workflow.name}

+

AI ์›Œํฌํ”Œ๋กœ์šฐ์™€ ๋Œ€ํ™”ํ•˜์„ธ์š”

+
+
+
+ + {ioLogs.length}๊ฐœ์˜ ๋Œ€ํ™” +
+
+ + {/* Chat Area */} +
+ {loading ? ( +
+
+

์ฑ„ํŒ… ๊ธฐ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

+
+ ) : ( + <> +
+ {ioLogs.length === 0 ? ( +
+ +

์ฒซ ๋Œ€ํ™”๋ฅผ ์‹œ์ž‘ํ•ด๋ณด์„ธ์š”!

+

"{workflow.name}" ์›Œํฌํ”Œ๋กœ์šฐ๊ฐ€ ์ค€๋น„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

+
+ ) : ( + ioLogs.map((log) => ( +
+ {/* User Message */} +
+
+ {log.input_data} +
+
+ {formatDate(log.updated_at)} +
+
+ + {/* Bot Message */} +
+
+ {String(log.log_id) === pendingLogId && executing && !log.output_data ? ( +
+ + + +
+ ) : ( + log.output_data + )} +
+
+
+ )) + )} +
+ + {/* Input Area */} +
+
+ setInputMessage(e.target.value)} + onKeyPress={handleKeyPress} + disabled={executing} + className={styles.messageInput} + /> + +
+ {executing && ( +

+ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค... +

+ )} + {error && ( +

{error}

+ )} +
+ + )} +
+
+ ); +}; + +export default ChatInterface; diff --git a/src/app/main/assets/Executor.module.scss b/src/app/main/assets/Executor.module.scss index cdbd7592..d657f4fd 100644 --- a/src/app/main/assets/Executor.module.scss +++ b/src/app/main/assets/Executor.module.scss @@ -190,6 +190,41 @@ } } +// Typing Indicator +.typingIndicator { + display: flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0; + + span { + width: 6px; + height: 6px; + background-color: #6c757d; + border-radius: 50%; + animation: typing 1.4s infinite ease-in-out; + + &:nth-child(1) { + animation-delay: -0.32s; + } + + &:nth-child(2) { + animation-delay: -0.16s; + } + } +} + +@keyframes typing { + 0%, 80%, 100% { + transform: scale(0.8); + opacity: 0.5; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + .executorInputArea { border-top: 1px solid #e9ecef; padding: 1rem 1.5rem; @@ -260,15 +295,6 @@ width: 16px; height: 16px; } - - .miniSpinner { - width: 16px; - height: 16px; - border: 2px solid #e9ecef; - border-top: 2px solid #007bff; - border-radius: 50%; - animation: spin 1s linear infinite; - } } } diff --git a/src/app/main/components/Executor.tsx b/src/app/main/components/Executor.tsx index e7c13781..d8748d70 100644 --- a/src/app/main/components/Executor.tsx +++ b/src/app/main/components/Executor.tsx @@ -232,11 +232,11 @@ const Executor: React.FC = ({ workflow }) => { pendingLogId && executing && !log.output_data ? ( - +
+ + + +
) : ( log.output_data )} @@ -269,7 +269,7 @@ const Executor: React.FC = ({ workflow }) => { > {executing ? (
) : ( diff --git a/src/app/page.tsx b/src/app/page.tsx index 319de423..4c38f515 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,6 +7,12 @@ import { FiZap, FiGrid, FiUsers, + FiMessageCircle, + FiCpu, + FiLayers, + FiTrendingUp, + FiShield, + FiGlobe, } from 'react-icons/fi'; import styles from '@/app/HomePage.module.scss'; @@ -39,84 +45,202 @@ export default function HomePage() { {/* Hero Section */}
-

- Build AI Workflows - - Visually & Intuitively - -

-

- PlateerRAG๋Š” ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ AI ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌ์ถ•ํ•  - ์ˆ˜ ์žˆ๋Š” ๋น„์ฃผ์–ผ ์—๋””ํ„ฐ์ž…๋‹ˆ๋‹ค.

- ๋ณต์žกํ•œ AI ํŒŒ์ดํ”„๋ผ์ธ์„ ์ง๊ด€์ ์ธ ๋…ธ๋“œ ๊ธฐ๋ฐ˜ ์ธํ„ฐํŽ˜์ด์Šค๋กœ - ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. -

-
- - - ์ง€๊ธˆ ์‹œ์ž‘ํ•˜๊ธฐ - - - ๊ด€๋ฆฌ์„ผํ„ฐ ๋‘˜๋Ÿฌ๋ณด๊ธฐ - +
+
+ ๐Ÿš€ Next-Gen AI Workflow Platform +
+

+ Build Intelligent AI Workflows + + with Visual Simplicity + +

+

+ PlateerRAG๋Š” ์ฐจ์„ธ๋Œ€ AI ์›Œํฌํ”Œ๋กœ์šฐ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. + ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ๋ณต์žกํ•œ AI ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•˜๊ณ , + ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…์œผ๋กœ AI์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ฉฐ, + ๊ฐ•๋ ฅํ•œ ๊ด€๋ฆฌ๋„๊ตฌ๋กœ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์šด์˜ํ•˜์„ธ์š”. +

+
+
+ 5+ + AI ๋…ธ๋“œ ํƒ€์ž… +
+
+ ์‹ค์‹œ๊ฐ„ + ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค +
+
+ ๋ฌด์ œํ•œ + ์›Œํฌํ”Œ๋กœ์šฐ ์ƒ์„ฑ +
+
+
+ + + ์›Œํฌํ”Œ๋กœ์šฐ ๋งŒ๋“ค๊ธฐ + + + + AI ์ฑ„ํŒ… ์ฒดํ—˜ + +
+
+
+
+
+
+
+ + + +
+ PlateerRAG Canvas +
+
+
+
+ + Input +
+
+
+ + AI Process +
+
+
+ + Output +
+
+
+
+
{/* Feature Cards */}
-

ํ•ต์‹ฌ ๊ธฐ๋Šฅ

+

์™œ PlateerRAG์ธ๊ฐ€์š”?

- PlateerRAG๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋“ค์„ ํ™•์ธํ•ด๋ณด์„ธ์š” + AI ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์ถ•์˜ ์ƒˆ๋กœ์šด ํ‘œ์ค€์„ ์ œ์‹œํ•˜๋Š” ํ˜์‹ ์ ์ธ ๊ธฐ๋Šฅ๋“ค

-
+
-

๋น„์ฃผ์–ผ ์›Œํฌํ”Œ๋กœ์šฐ ์—๋””ํ„ฐ

+

๋น„์ฃผ์–ผ ์บ”๋ฒ„์Šค ์—๋””ํ„ฐ

+
+

+ ์ง๊ด€์ ์ธ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ๋ณต์žกํ•œ AI ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ + ์‹œ๊ฐ์ ์œผ๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฏธ๋ฆฌ๋ณด๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +

+
+ โ€ข 5+ AI ๋…ธ๋“œ ํƒ€์ž… + โ€ข ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ + โ€ข ์ž๋™ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ +
+
+
+ +
+
+
+
+ +

์‹ค์‹œ๊ฐ„ AI ์ฑ„ํŒ…

+
+

+ ์™„์„ฑ๋œ ์›Œํฌํ”Œ๋กœ์šฐ์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋Œ€ํ™”ํ•˜๋ฉฐ + AI์˜ ์‘๋‹ต์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +

+
+ โ€ข ์‹ค์‹œ๊ฐ„ ์‘๋‹ต + โ€ข ๋Œ€ํ™” ๊ธฐ๋ก ์ €์žฅ + โ€ข ๋‹ค์ค‘ ์›Œํฌํ”Œ๋กœ์šฐ ์„ ํƒ +
+
+
+ +
+
+
+
+ +

์Šค๋งˆํŠธ ๊ด€๋ฆฌ์„ผํ„ฐ

+
+

+ ์›Œํฌํ”Œ๋กœ์šฐ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง, ์‹คํ–‰ ๋กœ๊ทธ ๋ถ„์„, + ๊ทธ๋ฆฌ๊ณ  ํŒ€ ํ˜‘์—…์„ ์œ„ํ•œ ํ†ตํ•ฉ ๊ด€๋ฆฌ ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +

+
+ โ€ข ์„ฑ๋Šฅ ๋Œ€์‹œ๋ณด๋“œ + โ€ข ์‹คํ–‰ ๋กœ๊ทธ ๋ถ„์„ + โ€ข ํŒ€ ํ˜‘์—… ๋„๊ตฌ +
+
+
+ +
+
+
+
+ +

๊ณ ์„ฑ๋Šฅ ์‹คํ–‰ ์—”์ง„

- ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ๋…ธ๋“œ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ๋ณต์žกํ•œ AI - ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + ์ตœ์ ํ™”๋œ ์‹คํ–‰ ์—”์ง„์œผ๋กœ ๋Œ€๊ทœ๋ชจ ์›Œํฌํ”Œ๋กœ์šฐ๋„ + ๋น ๋ฅด๊ณ  ์•ˆ์ •์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

+
+ โ€ข ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์ง€์› + โ€ข ์ž๋™ ์Šค์ผ€์ผ๋ง + โ€ข ์—๋Ÿฌ ๋ณต๊ตฌ ์‹œ์Šคํ…œ +
-
+
- -

์‹ค์‹œ๊ฐ„ ์‹คํ–‰

+ +

์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ๋ณด์•ˆ

- ๊ตฌ์„ฑํ•œ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์‹คํ–‰ํ•˜๊ณ  - ๊ฒฐ๊ณผ๋ฅผ ์ฆ‰์‹œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + ๊ธฐ์—…๊ธ‰ ๋ณด์•ˆ๊ณผ ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ ๊ธฐ๋Šฅ์œผ๋กœ + ์•ˆ์ „ํ•œ AI ์›Œํฌํ”Œ๋กœ์šฐ ํ™˜๊ฒฝ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

+
+ โ€ข ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™” + โ€ข ์ ‘๊ทผ ๊ถŒํ•œ ์ œ์–ด + โ€ข ๊ฐ์‚ฌ ๋กœ๊ทธ +
-
+
- -

ํ˜‘์—… ํ™˜๊ฒฝ

+ +

๊ฐœ๋ฐฉํ˜• ์ƒํƒœ๊ณ„

- ํŒ€์›๋“ค๊ณผ ํ•จ๊ป˜ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ณต์œ ํ•˜๊ณ  ํ˜‘์—…ํ•  - ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + ๋‹ค์–‘ํ•œ AI ๋ชจ๋ธ๊ณผ ์„œ๋น„์Šค๋ฅผ ์‰ฝ๊ฒŒ ์—ฐ๋™ํ•˜๊ณ  + ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

+
+ โ€ข API ํ†ตํ•ฉ + โ€ข ํ”Œ๋Ÿฌ๊ทธ์ธ ์ง€์› + โ€ข ์ปค๋ฎค๋‹ˆํ‹ฐ ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค +
@@ -125,12 +249,24 @@ export default function HomePage() { {/* CTA Section */}
+
+ ๐ŸŽฏ Ready to Transform Your AI Workflow? +

์ง€๊ธˆ ๋ฐ”๋กœ ์‹œ์ž‘ํ•ด๋ณด์„ธ์š”

-

๋ช‡ ๋ถ„ ์•ˆ์— ์ฒซ ๋ฒˆ์งธ AI ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”

- - - ์›Œํฌํ”Œ๋กœ์šฐ ๋งŒ๋“ค๊ธฐ - +

๋ช‡ ๋ถ„ ์•ˆ์— ์ฒซ ๋ฒˆ์งธ AI ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹คํ–‰ํ•ด๋ณด์„ธ์š”

+
+ + + ๋ฌด๋ฃŒ๋กœ ์‹œ์ž‘ํ•˜๊ธฐ + + + + ๊ด€๋ฆฌ์„ผํ„ฐ ๋‘˜๋Ÿฌ๋ณด๊ธฐ + +
+
+ Let's Start +
@@ -141,10 +277,10 @@ export default function HomePage() {

PlateerRAG

-

AI ์›Œํฌํ”Œ๋กœ์šฐ์˜ ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„

+

Next Generation AI Workflow

diff --git a/temporary/(canvas_js)/assets/Canvas.module.scss b/temporary/(canvas_js)/assets/Canvas.module.scss deleted file mode 100644 index 53d51557..00000000 --- a/temporary/(canvas_js)/assets/Canvas.module.scss +++ /dev/null @@ -1,46 +0,0 @@ -.canvasContainer { - width: 100vw; - height: 100vh; - overflow: hidden; - background-color: #f0f2f5; - position: relative; - cursor: grab; -} - -.canvasGrid { - width: 5000vw; - height: 5000vh; - position: relative; - transform-origin: 0 0; - - $background-color: #ffffff; - $grid-color-minor: rgba(0, 0, 0, 0.08); - $grid-color-major: rgba(0, 0, 0, 0.15); - $grid-size-minor: 20px; - $grid-size-major: 100px; - - background-color: $background-color; - background-image: - linear-gradient($grid-color-minor 1px, transparent 1px), - linear-gradient(90deg, $grid-color-minor 1px, transparent 1px), - linear-gradient($grid-color-major 1px, transparent 1px), - linear-gradient(90deg, $grid-color-major 1px, transparent 1px); - - background-size: - $grid-size-minor $grid-size-minor, - $grid-size-minor $grid-size-minor, - $grid-size-major $grid-size-major, - $grid-size-major $grid-size-major; -} - -.svgLayer { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - overflow: visible; - z-index: 10; - -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/Chat.module.scss b/temporary/(canvas_js)/assets/Chat.module.scss deleted file mode 100644 index 54d495d3..00000000 --- a/temporary/(canvas_js)/assets/Chat.module.scss +++ /dev/null @@ -1,179 +0,0 @@ -/* Chat.module.scss */ - -.chatContainer { - display: flex; - flex-direction: column; - height: 100%; /* Make chat container take full height of its parent */ - width: 360px; /* Or a width that suits your layout */ - background-color: #ffffff; - border-radius: 12px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); - border: 1px solid rgba(0, 0, 0, 0.07); - overflow: hidden; /* Important for keeping children within rounded borders */ -} - -.chatHeader { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 16px; - flex-shrink: 0; - border-bottom: 1px solid #f0f0f0; - background-color: #f9f9f9; /* Slightly different background for header */ -} - -.chatHeader h3 { - margin: 0; - font-size: 1rem; /* 16px */ - font-weight: 600; - color: #333; -} - -.backButton { - background: none; - border: none; - font-size: 1.25rem; /* Consistent with SideMenu */ - color: #555; - cursor: pointer; - padding: 4px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; -} - -.backButton:hover { - background-color: #f0f0f0; /* Consistent hover effect */ -} - -.messageList { - flex-grow: 1; - padding: 16px; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 12px; - scrollbar-width: thin; - scrollbar-color: #cccccc transparent; -} - -.messageList::-webkit-scrollbar { - width: 6px; -} - -.messageList::-webkit-scrollbar-track { - background: transparent; -} - -.messageList::-webkit-scrollbar-thumb { - background-color: #d1d1d1; - border-radius: 10px; - border: 1px solid transparent; - background-clip: content-box; -} - -.messageList::-webkit-scrollbar-thumb:hover { - background-color: #a8a8a8; -} - -.message { - padding: 10px 14px; - border-radius: 18px; /* More rounded for messages */ - max-width: 80%; - word-wrap: break-word; - font-size: 0.9rem; - line-height: 1.4; -} - -.userMessage { - background-color: #007bff; /* Blue for user messages */ - color: white; - align-self: flex-end; /* Align user messages to the right */ - border-bottom-right-radius: 6px; /* Slightly different rounding for tail effect */ -} - -.botMessage { - background-color: #e9ecef; /* Light grey for bot messages */ - color: #333; - align-self: flex-start; /* Align bot messages to the left */ - border-bottom-left-radius: 6px; /* Slightly different rounding for tail effect */ -} - -.inputArea { - display: flex; - padding: 12px; - border-top: 1px solid #f0f0f0; - background-color: #f9f9f9; /* Match header background */ - gap: 10px; -} - -.textInput { - flex-grow: 1; - padding: 10px 16px; - border-radius: 20px; /* Pill shape input */ - border: 1px solid #e0e0e0; - font-size: 0.95rem; - outline: none; - transition: border-color 0.2s ease, box-shadow 0.2s ease; -} - -.textInput:focus { - border-color: #007bff; - box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1); -} - -.sendButton { - padding: 10px 18px; - background-color: #007bff; - color: white; - border: none; - border-radius: 20px; /* Pill shape button */ - cursor: pointer; - font-size: 0.95rem; - font-weight: 500; - transition: background-color 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.sendButton:hover { - background-color: #0056b3; -} - -.sendButton:disabled { - background-color: #a0a0a0; - cursor: not-allowed; -} - -/* Loading dots animation for bot messages */ -.loadingDots span { - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - background-color: currentColor; /* Use the text color of the bot message */ - margin: 0 2px; - animation: loadingBounce 1.4s infinite ease-in-out both; -} - -.loadingDots span:nth-child(1) { - animation-delay: -0.32s; -} - -.loadingDots span:nth-child(2) { - animation-delay: -0.16s; -} - -@keyframes loadingBounce { - - 0%, - 80%, - 100% { - transform: scale(0); - } - - 40% { - transform: scale(1.0); - } -} diff --git a/temporary/(canvas_js)/assets/Edge.module.scss b/temporary/(canvas_js)/assets/Edge.module.scss deleted file mode 100644 index ac5546e2..00000000 --- a/temporary/(canvas_js)/assets/Edge.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -.edgeGroup { - // cursor: pointer; - pointer-events: auto; - &:hover .edgePath, - &.selected .edgePath { - stroke: #3b82f6; - } -} - -.edgePath { - stroke: #adb5bd; - stroke-width: 2.5; - fill: none; - transition: stroke 0.2s ease; - pointer-events: none; -} - -.edgeHitbox { - fill: none; - stroke: transparent; - stroke-width: 20px; -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/ExecutionPanel.module.scss b/temporary/(canvas_js)/assets/ExecutionPanel.module.scss deleted file mode 100644 index e6142e78..00000000 --- a/temporary/(canvas_js)/assets/ExecutionPanel.module.scss +++ /dev/null @@ -1,154 +0,0 @@ -.executionPanel { - position: absolute; - top: 10px; - width: 450px; - left: 10px; - max-height: 40vh; - background-color: rgba(255, 255, 255, 0.9); - backdrop-filter: blur(10px); - border-radius: 12px; - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); - border: 1px solid rgba(0, 0, 0, 0.1); - z-index: 1000; - display: flex; - flex-direction: column; - overflow: hidden; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - - &.collapsed { - max-height: auto; - height: auto; - width: 200px; - } -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 16px; - border-bottom: 1px solid #e0e0e0; - flex-shrink: 0; - transition: border-bottom 0.3s ease; - - h4 { - margin: 0; - font-weight: 600; - font-size: 0.95rem; - } - - .executionPanel.collapsed & { - border-bottom: none; - } -} - -.titleSection { - display: flex; - align-items: center; - gap: 8px; -} - -.actions { - display: flex; - align-items: center; - gap: 8px; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.actionButton { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 12px; - border: none; - border-radius: 6px; - font-size: 0.85rem; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s ease; - - &:disabled { - opacity: 0.6; - cursor: not-allowed; - } -} - -.toggleButton { - display: flex; - align-items: center; - justify-content: center; - padding: 4px; - border: none; - border-radius: 6px; - background-color: #f8f9fa; - color: #495057; - font-size: 0.85rem; - cursor: pointer; - transition: all 0.2s ease; - min-width: 24px; - height: 24px; - - &:hover { - background-color: #e9ecef; - color: #343a40; - transform: scale(1.05); - } - - svg { - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - } -} - -.runButton { - background-color: #28a745; - color: white; - - &:hover:not(:disabled) { - background-color: #218838; - } -} - -.outputContainer { - flex-grow: 1; - padding: 16px; - overflow-y: auto; - font-family: 'Courier New', Courier, monospace; - font-size: 0.85rem; - background-color: #f8f9fa; - animation: slideDown 0.4s cubic-bezier(0.4, 0, 0.2, 1); - - pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; - color: #333; - } -} - -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-10px); - max-height: 0; - } - to { - opacity: 1; - transform: translateY(0); - max-height: 500px; - } -} - -/* --- Loader Animation --- */ -.loader { - border: 2px solid #f3f3f3; - border-top: 2px solid #3498db; - border-radius: 50%; - width: 14px; - height: 14px; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/Header.module.scss b/temporary/(canvas_js)/assets/Header.module.scss deleted file mode 100644 index e270e01a..00000000 --- a/temporary/(canvas_js)/assets/Header.module.scss +++ /dev/null @@ -1,190 +0,0 @@ -.header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 24px; - height: 60px; // ํ—ค๋” ๋†’์ด - background-color: #ffffff; // ํ—ค๋” ๋ฐฐ๊ฒฝ์ƒ‰ - border-bottom: 1px solid #e0e0e0; // ๊ตฌ๋ถ„์„  - flex-shrink: 0; // ํ—ค๋”๊ฐ€ ์ค„์–ด๋“ค์ง€ ์•Š๋„๋ก ์„ค์ • - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); -} - -.leftSection { - display: flex; - align-items: center; - gap: 24px; -} - -.logoLink { - text-decoration: none; - transition: all 0.2s ease; - border-radius: 8px; - padding: 8px 12px; - - &:hover { - background-color: #f8f9fa; - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - } - - &:active { - transform: translateY(0); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); - } -} - -.logo { - font-size: 1.5rem; // 24px - font-weight: 700; - color: #212529; - background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%); - background-clip: text; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - transition: all 0.2s ease; - - .logoLink:hover & { - background: linear-gradient(135deg, #1d4ed8 0%, #6d28d9 100%); - background-clip: text; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - } -} - -.workflowNameSection { - min-width: 200px; -} - -.displayMode { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 4px 8px; - border-radius: 6px; - transition: background-color 0.2s ease; - min-width: 200px; - - &:hover { - background-color: #f8f9fa; - } -} - -.workflowName { - font-size: 1rem; - font-weight: 600; - color: #495057; - cursor: pointer; - flex-grow: 1; - text-align: left; -} - -.editMode { - display: flex; - align-items: center; - gap: 4px; -} - -.workflowInput { - font-size: 1rem; - font-weight: 600; - color: #495057; - border: 2px solid #3b82f6; - border-radius: 6px; - padding: 6px 10px; - background-color: #ffffff; - outline: none; - min-width: 150px; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); - - &:focus { - border-color: #2563eb; - } -} - -.editButton { - background: none; - border: none; - padding: 4px; - cursor: pointer; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.9rem; - transition: background-color 0.2s ease; - min-width: 24px; - min-height: 24px; - flex-shrink: 0; - - &:hover { - background-color: #e9ecef; - } -} - -.saveButton { - color: #28a745; - - &:hover { - background-color: #d4edda; - } -} - -.cancelButton { - color: #dc3545; - - &:hover { - background-color: #f8d7da; - } -} - -.nav { - ul { - display: flex; - align-items: center; - list-style: none; - margin: 0; - padding: 0; - gap: 8px; // ๋ฉ”๋‰ด ์‚ฌ์ด ๊ฐ„๊ฒฉ - } - - li button { - background: none; - border: none; - padding: 8px 16px; - font-size: 0.95rem; // 15px - font-weight: 500; - color: #495057; - cursor: pointer; - border-radius: 6px; - transition: background-color 0.2s ease; - - &:hover { - background-color: #f1f3f5; - } - } -} - -.rightSection { - display: flex; - align-items: center; - gap: 16px; -} - -.menuButton { - background: none; - border: none; - font-size: 1.5rem; // ์•„์ด์ฝ˜ ํฌ๊ธฐ - color: #555; - cursor: pointer; - padding: 8px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - - &:hover { - background-color: #f0f0f0; - } -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/MiniCanvas.module.scss b/temporary/(canvas_js)/assets/MiniCanvas.module.scss deleted file mode 100644 index 5cc15762..00000000 --- a/temporary/(canvas_js)/assets/MiniCanvas.module.scss +++ /dev/null @@ -1,108 +0,0 @@ -// MiniCanvas ์Šคํƒ€์ผ -.miniCanvas { - width: 100%; - height: 100%; - position: relative; - overflow: hidden; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - border-radius: 12px; - border: 2px solid #e2e8f0; - user-select: none; - pointer-events: auto; /* ์บ”๋ฒ„์Šค ๋‚ด๋ถ€ ์ด๋ฒคํŠธ ํ™œ์„ฑํ™” */ -} - -.canvasContent { - width: 100%; - height: 100%; - position: relative; - transition: transform 0.1s ease-out; -} - -.edgesSvg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - z-index: 1; -} - -.nodesContainer { - position: relative; - z-index: 2; - pointer-events: none; /* ๋…ธ๋“œ ์ž์ฒด๋Š” ํด๋ฆญ ๋ถˆ๊ฐ€ */ -} - -.grid { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: - radial-gradient(circle at 1px 1px, rgba(255,255,255,0.15) 1px, transparent 0); - background-size: 20px 20px; - pointer-events: none; -} - -.zoomControls { - position: absolute; - bottom: 10px; - right: 10px; - display: flex; - align-items: center; - background: rgba(255, 255, 255, 0.9); - border-radius: 20px; - padding: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(4px); -} - -.zoomButton { - width: 24px; - height: 24px; - border: none; - background: transparent; - cursor: pointer; - font-size: 14px; - font-weight: bold; - color: #374151; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - transition: background-color 0.2s; - - &:hover { - background-color: rgba(0, 0, 0, 0.1); - } -} - -.zoomLevel { - margin: 0 8px; - font-size: 11px; - color: #6b7280; - min-width: 35px; - text-align: center; -} - -// Node preview ์Šคํƒ€์ผ ์กฐ์ • -.miniCanvas :global(.nodeContainer.preview) { - transform: scale(0.8); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - - :global(.nodeHeader) { - font-size: 11px; - } - - :global(.nodeInputs), :global(.nodeOutputs) { - font-size: 10px; - } -} - -// Edge preview ์Šคํƒ€์ผ ์กฐ์ • -.miniCanvas :global(.edge.preview) { - stroke-width: 2; - opacity: 0.8; -} diff --git a/temporary/(canvas_js)/assets/Node.module.scss b/temporary/(canvas_js)/assets/Node.module.scss deleted file mode 100644 index 8599d6e4..00000000 --- a/temporary/(canvas_js)/assets/Node.module.scss +++ /dev/null @@ -1,314 +0,0 @@ -.node { - position: absolute; - min-width: 350px; - background-color: #ffffff; - border-radius: 12px; - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); - border: 2px solid #95979c; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - user-select: none; - -webkit-user-select: none; - z-index: 20; - - &.selected { - border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4), 0 8px 24px rgba(0, 0, 0, 0.2); - z-index: 30; - } -} - -.header { - padding: 12px 16px; - font-weight: 600; - font-size: 1rem; // 16px - color: #111827; - border-top-left-radius: 12px; - border-top-right-radius: 12px; - border-bottom: 1px solid #f3f4f6; // ๋งค์šฐ ์˜…์€ ๊ตฌ๋ถ„์„  - cursor: grab; // [์ถ”๊ฐ€] ํ—ค๋”์—์„œ๋งŒ ๋“œ๋ž˜๊ทธ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ปค์„œ ๋ณ€๊ฒฝ - display: flex; - align-items: center; - gap: 8px; -} - -.functionId { - font-size: 0.75rem; - color: #6b7280; - font-weight: normal; -} - -// --- Node Name Editing Styles --- -.nodeName { - cursor: pointer; - user-select: none; - transition: background-color 0.2s ease; - padding: 2px 4px; - border-radius: 4px; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - &:hover { - background-color: rgba(59, 130, 246, 0.1); - } -} - -.nameInput { - background: #fff; - border: 2px solid #3b82f6; - border-radius: 4px; - padding: 2px 6px; - font-size: 1rem; - font-weight: 600; - color: #111827; - outline: none; - min-width: 100px; - max-width: 200px; - font-family: inherit; - - &:focus { - box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); - } -} - -.body { - padding: 12px 0; -} - -// --- IO ์„น์…˜ ์Šคํƒ€์ผ --- -.ioContainer { - display: flex; - justify-content: space-between; - padding: 0 16px; -} - -.column { - display: flex; - flex-direction: column; - gap: 14px; - flex-basis: 48%; // [์ˆ˜์ •] ๋„ˆ๋น„ ๋ฏธ์„ธ ์กฐ์ • - - // [์ถ”๊ฐ€] Output๋งŒ ์žˆ์„ ๋•Œ ์ปฌ๋Ÿผ ๋„ˆ๋น„๋ฅผ 100%๋กœ - &.fullWidth { - flex-basis: 100%; - } -} - -.outputColumn { - align-items: flex-end; - - .portRow { - justify-content: flex-end; - } -} - -.sectionHeader { - font-size: 0.8rem; // 11.2px - font-weight: 700; - color: #838a94; - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 4px; -} - -.portRow { - display: flex; - align-items: center; - gap: 12px; // [์ˆ˜์ •] ๊ฐ„๊ฒฉ ์กฐ์ • -} - -.portLabel { - flex: 1; - font-size: 0.875rem; - color: #4b5563; - - &.required::after { - content: " *"; - color: #ef4444; - font-weight: bold; - } -} - - -.port { - position: relative; - z-index: 5; - - padding: 3px 12px; - border-radius: 6px; - min-width: 50px; - - // ํ…์ŠคํŠธ ์Šคํƒ€์ผ - font-size: 0.8rem; - font-weight: 600; - text-align: center; - color: #374151; - - // ํ…Œ๋‘๋ฆฌ ๋ฐ ๊ธฐ๋ณธ ์ƒ‰์ƒ - background-color: #fff; - border: 2px solid #adb5bd; - transition: all 0.2s ease; - cursor: crosshair; - - // ํƒ€์ž…๋ณ„ ์ƒ‰์ƒ (ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ์œผ๋กœ ๊ตฌ๋ถ„) - &.type-STR { border-color: #5D9CF4; } - &.type-INT { border-color: #7AD37A; } - &.type-FLOAT { border-color: #F7C966; } - &.type-ANY { border-color: #9C5BF4; } // ๋ณด๋ผ์ƒ‰ ๊ณ„์—ด๋กœ ANY ํƒ€์ž… ํ‘œ์‹œ - - // ๋‹ค์ค‘ ์—ฐ๊ฒฐ ํฌํŠธ ์Šคํƒ€์ผ - &.multi { - background-color: #dcf5f7; - } - - // ์ƒํ˜ธ์ž‘์šฉ ์Šคํƒ€์ผ - &:hover { - transform: scale(1); - filter: brightness(0.95); - } - - &.snapping { - // border-width: 3px; - transform: scale(1.05); - } - - &.invalid-snap { - border-color: #e53e3e !important; - background-color: #fed7d7; - cursor: not-allowed; - } -} - -// .port.multi { -// border-radius: 4px; -// background-color: #d1d5db; -// } - -.divider { - height: 2px; - background-color: #a8abb388; - margin: 16px 0; -} - -.paramSection { - padding: 0 16px; -} - -.param { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.875rem; - margin-top: 10px; - margin-bottom: 6px; - - &:last-child { - margin-bottom: 0; - } -} - -.paramKey { - color: #6b7280; - margin-right: 8px; - - &.required::after { - content: " *"; - color: #ef4444; - font-weight: bold; - } -} - -.paramValue { - color: #111827; - font-weight: 500; - background-color: #f3f4f6; - padding: 4px 8px; - border-radius: 6px; - font-size: 0.8rem; -} - -// --- [์ƒˆ๋กœ์šด ์Šคํƒ€์ผ] ํŒŒ๋ผ๋ฏธํ„ฐ ์ž…๋ ฅ ํ•„๋“œ --- -.paramInput { - flex-grow: 1; - max-width: 60%; - background-color: #f3f4f6; - border: 1px solid transparent; - border-radius: 6px; - padding: 4px 8px; - font-size: 0.8rem; - font-weight: 500; - color: #111827; - outline: none; - transition: border-color 0.2s, box-shadow 0.2s; - text-align: right; - - &:focus { - background-color: #fff; - border-color: #3b82f6; - box-shadow: 0 0 0 1px #3b82f6; - } -} - -// --- [์ƒˆ๋กœ์šด ์Šคํƒ€์ผ] ํŒŒ๋ผ๋ฏธํ„ฐ ์„ ํƒ ํ•„๋“œ (์˜ต์…˜์ด ์žˆ๋Š” ๊ฒฝ์šฐ) --- -.paramSelect { - flex-grow: 1; - max-width: 60%; - background-color: #f3f4f6; - border: 1px solid transparent; - border-radius: 6px; - padding: 4px 8px; - font-size: 0.8rem; - font-weight: 500; - color: #111827; - outline: none; - transition: border-color 0.2s, box-shadow 0.2s; - text-align: right; - cursor: pointer; - position: relative; // z-index ๋ฌธ์ œ ๋ฐฉ์ง€ - z-index: 1; // ๊ธฐ๋ณธ z-index ์„ค์ • - - &:focus { - background-color: #fff; - border-color: #3b82f6; - box-shadow: 0 0 0 1px #3b82f6; - z-index: 1000; // focus ์‹œ ์ตœ์ƒ์œ„๋กœ - } - - &:hover { - background-color: #e5e7eb; - } -} - -// --- Advanced Parameters Styles --- -.advancedParams { - margin-top: 8px; - border-top: 1px solid #e5e7eb; - padding-top: 8px; -} - -.advancedHeader { - display: flex; - align-items: center; - justify-content: center; - padding: 6px 12px; - background-color: #f8fafc; - border: 1px solid #e2e8f0; - border-radius: 6px; - cursor: pointer; - font-size: 0.75rem; - font-weight: 500; - color: #64748b; - transition: all 0.2s ease; - margin-bottom: 8px; - - &:hover { - background-color: #f1f5f9; - border-color: #cbd5e1; - color: #475569; - } - - span { - user-select: none; - } -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/NodeList.module.scss b/temporary/(canvas_js)/assets/NodeList.module.scss deleted file mode 100644 index 606e069f..00000000 --- a/temporary/(canvas_js)/assets/NodeList.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -.nodeList { - width: 100%; - border-bottom: 1px solid #e0e0e0; -} - -.header { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - padding: 16px; - background-color: #fff; - border: none; - cursor: pointer; - font-size: 1rem; - font-weight: 500; - text-align: left; - - &:hover { - background-color: #f8f9fa; - } -} - -.icon { - transition: transform 0.2s ease-in-out; -} - -.content { - padding: 8px 16px 16px; - background-color: #f8f9fa; - // ๋‚˜์ค‘์— ๋…ธ๋“œ ์•„์ดํ…œ๋“ค์ด ๋“ค์–ด๊ฐˆ ๊ณต๊ฐ„ -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/PlateeRAG.module.scss b/temporary/(canvas_js)/assets/PlateeRAG.module.scss deleted file mode 100644 index 2562cf4c..00000000 --- a/temporary/(canvas_js)/assets/PlateeRAG.module.scss +++ /dev/null @@ -1,14 +0,0 @@ -.pageContainer { - display: flex; - flex-direction: column; - width: 100vw; - height: 100vh; - overflow: hidden; - background-color: #f8f9fa; -} - -.mainContent { - flex-grow: 1; - position: relative; - display: flex; -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/SideMenu.module.scss b/temporary/(canvas_js)/assets/SideMenu.module.scss deleted file mode 100644 index fd998e94..00000000 --- a/temporary/(canvas_js)/assets/SideMenu.module.scss +++ /dev/null @@ -1,285 +0,0 @@ -@keyframes pop-in { - from { - opacity: 0; - transform: scale(0.95) translateY(-5px); - } - - to { - opacity: 1; - transform: scale(1) translateY(0); - } -} - -.sideMenuContainer { - position: absolute; - top: 10px; - right: 10px; - min-width: 320px; // ์ตœ์†Œ ๋„ˆ๋น„ ์„ค์ • - width: auto; // ๋™์  ๋„ˆ๋น„ - max-width: 600px; // ์ตœ๋Œ€ ๋„ˆ๋น„ ์ œํ•œ - height: auto; // ๊ธฐ๋ณธ ๋ฉ”๋‰ด ๋†’์ด - background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%); - border-radius: 14px; // ๋‘ฅ๊ทผ ํ…Œ๋‘๋ฆฌ ์ฆ๊ฐ€ - box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06); // ๋” ๊นŠ์€ ๊ทธ๋ฆผ์ž - border: 1px solid rgba(0, 0, 0, 0.05); - z-index: 1000; // ๋‹ค๋ฅธ ์š”์†Œ๋“ค ์œ„์— ๋ณด์ด๋„๋ก z-index ์„ค์ • - overflow: hidden; // ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ ๊ฐ€ ํ…Œ๋‘๋ฆฌ๋ฅผ ๋„˜์ง€ ์•Š๋„๋ก ํ•จ - - animation: pop-in 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; - transform-origin: top right; // ์˜ค๋ฅธ์ชฝ ์œ„๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ฐœ์ƒ - transition: width 0.3s ease-in-out, height 0.3s ease-in-out; - - display: flex; - flex-direction: column; - - // AddNodePanel ๋ทฐ์ผ ๋•Œ ํฌ๊ธฐ ํ™•์žฅ - &[data-view="addNodes"] { - min-width: 400px; // ํ™•์žฅ ์‹œ ์ตœ์†Œ ๋„ˆ๋น„ - width: auto; // ๋™์  ๋„ˆ๋น„ - max-width: 600px; // ์ตœ๋Œ€ ๋„ˆ๋น„ ์ œํ•œ - height: 85vh; - max-height: 700px; - } - - // WorkflowPanel ๋ทฐ์ผ ๋•Œ ํฌ๊ธฐ ํ™•์žฅ - &[data-view="workflow"] { - min-width: 400px; // ํ™•์žฅ ์‹œ ์ตœ์†Œ ๋„ˆ๋น„ - width: auto; // ๋™์  ๋„ˆ๋น„ - max-width: 600px; // ์ตœ๋Œ€ ๋„ˆ๋น„ ์ œํ•œ - height: 85vh; - max-height: 700px; - } - - // TemplatePanel ๋ทฐ์ผ ๋•Œ ํฌ๊ธฐ ํ™•์žฅ - &[data-view="template"] { - min-width: 400px; // WorkflowPanel๊ณผ ๋™์ผํ•œ ์ตœ์†Œ ๋„ˆ๋น„ - width: auto; // ๋™์  ๋„ˆ๋น„ - max-width: 600px; // ์ตœ๋Œ€ ๋„ˆ๋น„ ์ œํ•œ - height: 85vh; - max-height: 700px; - } -} - -// --- ํŒจ๋„ ๊ณตํ†ต ํ—ค๋” --- -.header { - display: flex; - align-items: center; - gap: 12px; - padding: 16px 20px; - flex-shrink: 0; - border-bottom: 1px solid #e2e8f0; - background: linear-gradient(90deg, #f8fafc 0%, #f1f5f9 100%); - - h3 { - margin: 0; - font-size: 1.05rem; // 16px โ†’ 17px๋กœ ์•ฝ๊ฐ„ ์ฆ๊ฐ€ - font-weight: 600; - color: #374151; - letter-spacing: 0.2px; - } -} - -.backButton { - background: none; - border: none; - font-size: 1.25rem; - color: #6b7280; - cursor: pointer; - padding: 6px; - display: flex; - border-radius: 8px; - transition: all 0.2s ease; - - &:hover { - background-color: rgba(59, 130, 246, 0.08); - color: #3b82f6; - transform: translateX(-1px); - } -} - -.refreshButton { - background: none; - border: none; - font-size: 1.1rem; - color: #6b7280; - cursor: pointer; - padding: 6px; - display: flex; - border-radius: 8px; - margin-left: auto; - transition: all 0.2s ease; - - &:hover { - background-color: rgba(34, 197, 94, 0.08); - color: #22c55e; - transform: rotate(90deg); - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - &.loading { - animation: spin 1s linear infinite; - } -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -// --- ๋ฉ”์ธ ๋ฉ”๋‰ด ์Šคํƒ€์ผ --- -.mainMenu { - padding: 12px 8px; // ์ขŒ์šฐ ํŒจ๋”ฉ์„ ์ค„์ž„ (16px โ†’ 8px) - display: flex; - flex-direction: column; - gap: 4px; -} - -.menuItem { - display: flex; - align-items: center; - gap: 14px; - width: 100%; - padding: 12px 16px; - font-size: 0.95rem; - font-weight: 500; - text-align: left; - background-color: transparent; - border: none; - border-radius: 10px; - cursor: pointer; - color: #374151; - transition: all 0.2s ease; - position: relative; - margin: 0; // ๋งˆ์ง„ ์ œ๊ฑฐ - box-sizing: border-box; // ๋ฐ•์Šค ์‚ฌ์ด์ง• ๋ช…์‹œ - - svg { - font-size: 1.2rem; - color: #6b7280; - transition: all 0.2s ease; - } - - &:hover { - background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(147, 51, 234, 0.06) 100%); - color: #1e40af; - transform: translateX(1px); // ์ด๋™ ๊ฑฐ๋ฆฌ๋ฅผ ์ค„์ž„ (2px โ†’ 1px) - - svg { - color: #3b82f6; - transform: scale(1.05); - } - } - - &:active { - transform: translateX(0px) scale(0.98); // ์•กํ‹ฐ๋ธŒ ์‹œ ์ด๋™ ์ œ๊ฑฐ - } -} - -// --- AddNodePanel ์Šคํƒ€์ผ --- -.searchBar { - position: relative; - padding: 16px; - flex-shrink: 0; - - input { - width: 100%; - padding: 10px 16px 10px 40px; - border-radius: 8px; - border: 1px solid #e0e0e0; - font-size: 0.95rem; - outline: none; - transition: all 0.2s ease; - - &:focus { - border-color: #007bff; - box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.15); - } - } -} - -.searchIcon, -.clearIcon { - position: absolute; - top: 50%; - transform: translateY(-50%); - color: #aaa; -} - -.searchIcon { - left: 30px; -} - -.clearIcon { - right: 30px; - cursor: pointer; -} - -.tabs { - display: flex; - padding: 0 16px; - border-bottom: 1px solid #e0e0e0; - flex-shrink: 0; - - overflow-x: auto; - - &::-webkit-scrollbar { - display: none; - } - - -ms-overflow-style: none; - scrollbar-width: none; -} - -.tab { - display: flex; - align-items: center; - gap: 8px; - padding: 12px 16px; - font-weight: 500; - color: #555; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - flex-shrink: 0; - border: none; - background-color: transparent; - cursor: pointer; - - &.active { - color: #007bff; - border-bottom-color: #007bff; - } -} - -.nodeList { - flex-grow: 1; - overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: #cccccc transparent; - - &::-webkit-scrollbar { - width: 8px; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background-color: #d1d1d1; - border-radius: 10px; - border: 2px solid transparent; - background-clip: content-box; - } - - &::-webkit-scrollbar-thumb:hover { - background-color: #a8a8a8; - } -} \ No newline at end of file diff --git a/temporary/(canvas_js)/assets/TemplatePreview.module.scss b/temporary/(canvas_js)/assets/TemplatePreview.module.scss deleted file mode 100644 index 2a07a6f6..00000000 --- a/temporary/(canvas_js)/assets/TemplatePreview.module.scss +++ /dev/null @@ -1,213 +0,0 @@ -// TemplatePreview ์Šคํƒ€์ผ -.overlay { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(4px); - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; - animation: fadeIn 0.2s ease-out; -} - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.previewContainer { - width: 90vw; - max-width: 1000px; - height: 80vh; - max-height: 700px; - background: #ffffff; - border-radius: 16px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); - display: flex; - flex-direction: column; - overflow: hidden; - animation: slideUp 0.3s ease-out; -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(20px) scale(0.95); - } - to { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -.header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 24px; - border-bottom: 1px solid #e2e8f0; - background: linear-gradient(90deg, #f8fafc 0%, #f1f5f9 100%); -} - -.titleSection { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; - - h3 { - margin: 0; - font-size: 1.25rem; - font-weight: 700; - color: #374151; - } -} - -.tagsContainer { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.category { - font-size: 0.75rem; - font-weight: 600; - color: #3b82f6; - background-color: rgba(59, 130, 246, 0.1); - padding: 4px 8px; - border-radius: 6px; - border: 1px solid rgba(59, 130, 246, 0.2); -} - -.actions { - display: flex; - align-items: center; - gap: 12px; -} - -.useButton { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 16px; - background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); - color: white; - border: none; - border-radius: 8px; - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: all 0.2s ease; - - &:hover { - background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); - } - - &:active { - transform: translateY(0); - } -} - -.closeButton { - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - background: none; - border: 1px solid #e2e8f0; - border-radius: 8px; - color: #6b7280; - cursor: pointer; - transition: all 0.2s ease; - - &:hover { - background-color: #fee2e2; - border-color: #fca5a5; - color: #dc2626; - } -} - -.previewContent { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; -} - -.canvasContainer { - flex: 1; - background: #f8fafc; - border-bottom: 1px solid #e2e8f0; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - padding: 20px; - position: relative; /* MiniCanvas ์ด๋ฒคํŠธ๋ฅผ ์œ„ํ•ด ์ถ”๊ฐ€ */ -} - -.previewCanvas { - width: 100%; - height: 100%; - border: 2px solid #e2e8f0; - border-radius: 12px; - background: #ffffff; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); -} - -.edges { - pointer-events: none; -} - -.nodes { - pointer-events: none; -} - -.templateInfo { - padding: 20px 24px; - background: #ffffff; -} - -.description { - margin: 0 0 16px 0; - font-size: 0.95rem; - color: #6b7280; - line-height: 1.5; -} - -.stats { - display: flex; - gap: 24px; -} - -.stat { - display: flex; - align-items: center; - gap: 8px; -} - -.statLabel { - font-size: 0.85rem; - font-weight: 600; - color: #374151; -} - -.statValue { - font-size: 0.85rem; - font-weight: 700; - color: #3b82f6; - background-color: rgba(59, 130, 246, 0.1); - padding: 2px 6px; - border-radius: 4px; -} diff --git a/temporary/(canvas_js)/assets/WorkflowPanel.module.scss b/temporary/(canvas_js)/assets/WorkflowPanel.module.scss deleted file mode 100644 index ebfd5890..00000000 --- a/temporary/(canvas_js)/assets/WorkflowPanel.module.scss +++ /dev/null @@ -1,399 +0,0 @@ -.workflowPanel { - display: flex; - flex-direction: column; - height: 100%; - background-color: #ffffff; -} - -.actionButtons { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px; - flex-shrink: 0; -} - -.actionButton { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - padding: 10px; - font-size: 0.9rem; - font-weight: 500; - text-align: left; - background-color: transparent; - border: none; - border-radius: 6px; - cursor: pointer; - color: #333; - transition: background-color 0.2s ease; - - svg { - font-size: 1.1rem; - color: #555; - } - - &:hover { - background-color: #f5f5f5; - } - - span { - flex-grow: 1; - text-align: left; - } -} - -.workflowList { - flex: 1; - overflow-y: auto; - padding: 16px; - scrollbar-width: thin; - scrollbar-color: #cccccc transparent; - - &::-webkit-scrollbar { - width: 8px; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background-color: #d1d1d1; - border-radius: 10px; - border: 2px solid transparent; - background-clip: content-box; - } - - &::-webkit-scrollbar-thumb:hover { - background-color: #a8a8a8; - } -} - -.listHeader { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 16px; - padding: 10px 14px; - background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); - border: 1px solid #cbd5e1; - border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); - - h3 { - font-size: 0.95rem; - font-weight: 600; - color: #374151; - margin: 0; - letter-spacing: 0.1px; - } - - .count { - font-size: 0.8rem; - font-weight: 500; - color: #6b7280; - background-color: rgba(59, 130, 246, 0.08); - padding: 3px 8px; - border-radius: 12px; - border: 1px solid rgba(59, 130, 246, 0.15); - } -} - -.loadingState, .errorState, .emptyState { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 30px 16px; - text-align: center; - color: #666; - - .spinIcon { - font-size: 1.5rem; - margin-bottom: 8px; - animation: spin 1s linear infinite; - } - - span { - font-size: 0.9rem; - font-weight: 500; - margin-bottom: 4px; - } - - p { - font-size: 0.8rem; - color: #888; - margin: 0; - } -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -.errorState { - .retryButton { - margin-top: 8px; - padding: 6px 12px; - background-color: #007bff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 0.8rem; - font-weight: 500; - transition: background-color 0.2s ease; - - &:hover { - background-color: #0056b3; - } - } -} - -.workflowItems { - display: flex; - flex-direction: column; - gap: 6px; -} - -.workflowItem { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px; - background-color: #ffffff; - border: 1px solid #e0e0e0; - border-radius: 6px; - transition: all 0.2s ease; - - &:hover { - border-color: #ccc; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - } -} - -.workflowInfo { - flex: 1; - min-width: 0; -} - -.workflowName { - font-size: 0.9rem; - font-weight: 600; - color: #333; - word-break: break-word; -} - -.workflowMeta { - display: flex; - align-items: center; - gap: 8px; -} - -.filename { - font-size: 0.75rem; - color: #666; - font-family: monospace; - background-color: #f0f0f0; - padding: 1px 4px; - border-radius: 3px; -} - -.workflowActions { - display: flex; - gap: 6px; - align-items: center; -} - -.loadButton { - padding: 6px 12px; - background-color: #007bff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 0.8rem; - font-weight: 500; - transition: background-color 0.2s ease; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - background-color: #0056b3; - } - - &:active { - background-color: #004085; - } -} - -.deleteButton { - padding: 6px; - background-color: #dc3545; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 0.8rem; - transition: background-color 0.2s ease; - display: flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 28px; - - &:hover { - background-color: #c82333; - } - - &:active { - background-color: #bd2130; - } -} - -// --- TemplatePanel ์Šคํƒ€์ผ --- -.templateList { - display: flex; - flex-direction: column; - gap: 12px; -} - -.templateItem { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; // templateHeader์™€ templateActions ์‚ฌ์ด์— ๊ฐ„๊ฒฉ ์ถ”๊ฐ€ - padding: 16px; - background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); - border: 1px solid #e2e8f0; - border-radius: 12px; - transition: all 0.2s ease; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); - - &:hover { - border-color: #cbd5e1; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - transform: translateY(-1px); - } -} - -.templateHeader { - display: flex; - align-items: flex-start; - gap: 12px; - flex: 1; -} - -.templateIcon { - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%); - border-radius: 10px; - color: white; - font-size: 1.2rem; - flex-shrink: 0; -} - -.templateInfo { - flex: 1; - min-width: 0; -} - -.templateName { - font-size: 0.95rem; - font-weight: 600; - color: #374151; - margin: 0 0 4px 0; - line-height: 1.2; -} - -.templateDescription { - font-size: 0.8rem; - color: #6b7280; - margin: 0 0 8px 0; - line-height: 1.3; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.templateMeta { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; -} - -.templateTags { - display: flex; - gap: 4px; - align-items: center; - flex-wrap: nowrap; // ํ•œ ์ค„์— ์œ ์ง€ - overflow: hidden; // ๋„˜์น˜๋Š” ๋ถ€๋ถ„ ์ˆจ๊น€ -} - -.templateCategory { - font-size: 0.7rem; - font-weight: 500; - color: #3b82f6; - background-color: rgba(59, 130, 246, 0.08); - padding: 2px 6px; - border-radius: 6px; - border: 1px solid rgba(59, 130, 246, 0.15); - white-space: nowrap; // ํ…์ŠคํŠธ ์ค„๋ฐ”๊ฟˆ ๋ฐฉ์ง€ - flex-shrink: 0; // ํƒœ๊ทธ๊ฐ€ ์ถ•์†Œ๋˜์ง€ ์•Š๋„๋ก -} - -.templateNodes { - font-size: 0.7rem; - color: #6b7280; - background-color: #f3f4f6; - padding: 2px 6px; - border-radius: 6px; - white-space: nowrap; - flex-shrink: 0; -} - -.templateActions { - display: flex; - gap: 6px; - flex-shrink: 0; -} - -.templateActionButton { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: none; - border: 1px solid #e2e8f0; - border-radius: 8px; - cursor: pointer; - color: #6b7280; - font-size: 0.9rem; - transition: all 0.2s ease; - - &:hover { - background-color: #f3f4f6; - border-color: #cbd5e1; - color: #374151; - transform: scale(1.05); - } - - &:active { - transform: scale(0.95); - } -} diff --git a/temporary/(canvas_js)/components/Canvas.jsx b/temporary/(canvas_js)/components/Canvas.jsx deleted file mode 100644 index 85f9ade1..00000000 --- a/temporary/(canvas_js)/components/Canvas.jsx +++ /dev/null @@ -1,790 +0,0 @@ -"use client"; - -import React, { useRef, useEffect, useState, forwardRef, useImperativeHandle, useCallback, memo, useLayoutEffect } from 'react'; -import styles from '@/app/canvas/assets/Canvas.module.scss'; -import Node from '@/app/canvas/components/Node'; -import Edge from '@/app/canvas/components/Edge'; -import { devLog } from '@/app/utils/logger'; - -const MIN_SCALE = 0.6; -const MAX_SCALE = 20; -const ZOOM_SENSITIVITY = 0.05; -const SNAP_DISTANCE = 40; - -const areTypesCompatible = (sourceType, targetType) => { - if (!sourceType || !targetType) return true; - if (sourceType === targetType) return true; - if (targetType === 'ANY') return true; - if (sourceType === 'INT' && targetType === 'FLOAT') return true; - return false; -}; - -const validateRequiredInputs = (nodes, edges) => { - for (const node of nodes) { - if (!node.data.inputs || node.data.inputs.length === 0) continue; - for (const input of node.data.inputs) { - if (input.required) { - const hasConnection = edges.some(edge => - edge.target.nodeId === node.id && - edge.target.portId === input.id - ); - - if (!hasConnection) { - return { - isValid: false, - nodeId: node.id, - nodeName: node.data.nodeName, - inputName: input.name - }; - } - } - } - } - return { isValid: true }; -}; - -const Canvas = forwardRef(({ onStateChange, ...otherProps }, ref) => { - const contentRef = useRef(null); - const containerRef = useRef(null); - - const [view, setView] = useState({ x: 0, y: 0, scale: 1 }); - const [nodes, setNodes] = useState([]); - const [edges, setEdges] = useState([]); - const [selectedNodeId, setSelectedNodeId] = useState(null); - const [selectedEdgeId, setSelectedEdgeId] = useState(null); - const [dragState, setDragState] = useState({ type: 'none', startX: 0, startY: 0 }); - const [edgePreview, setEdgePreview] = useState(null); - const [portPositions, setPortPositions] = useState({}); - const [snappedPortKey, setSnappedPortKey] = useState(null); - const [isSnapTargetValid, setIsSnapTargetValid] = useState(true); - const [copiedNode, setCopiedNode] = useState(null); - const [lastDeleted, setLastDeleted] = useState(null); // ์‚ญ์ œ ๋ณต์›์„ ์œ„ํ•œ ์ƒํƒœ - - const nodesRef = useRef(nodes); - const edgePreviewRef = useRef(edgePreview); - const portRefs = useRef(new Map()); - const snappedPortKeyRef = useRef(snappedPortKey); - const isSnapTargetValidRef = useRef(isSnapTargetValid); - - useLayoutEffect(() => { - const newPortPositions = {}; - const contentEl = contentRef.current; - if (!contentEl) return; - - const contentRect = contentEl.getBoundingClientRect(); - - portRefs.current.forEach((portEl, key) => { - if (portEl) { - const portRect = portEl.getBoundingClientRect(); - const x = (portRect.left + portRect.width / 2 - contentRect.left) / view.scale; - const y = (portRect.top + portRect.height / 2 - contentRect.top) / view.scale; - newPortPositions[key] = { x, y }; - } - }); - setPortPositions(newPortPositions); - }, [nodes, view.scale]); - - - - useEffect(() => { - if (onStateChange) { - const currentState = { view, nodes, edges }; - if (nodes.length > 0 || edges.length > 0) { - devLog.log('Canvas state changed, calling onStateChange:', { - nodesCount: nodes.length, - edgesCount: edges.length, - view: view - }); - onStateChange(currentState); - } else { - devLog.log('Canvas state is empty, skipping onStateChange to preserve localStorage'); - } - } else { - devLog.warn('onStateChange callback is not provided to Canvas'); - } - }, [nodes, edges, view, onStateChange]); - - const registerPortRef = useCallback((nodeId, portId, portType, el) => { - const key = `${nodeId}__PORTKEYDELIM__${portId}__PORTKEYDELIM__${portType}`; - if (el) { - portRefs.current.set(key, el); - } else { - portRefs.current.delete(key); - } - }, []); - - const getCenteredView = useCallback(() => { - const container = containerRef.current; - const content = contentRef.current; - - if (container && content) { - const containerWidth = container.clientWidth; - const containerHeight = container.clientHeight; - const contentWidth = content.offsetWidth; - const contentHeight = content.offsetHeight; - - // ์ปจํ…Œ์ด๋„ˆ๋‚˜ ์ฝ˜ํ…์ธ  ํฌ๊ธฐ๊ฐ€ 0์ด๋ฉด ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜ - if (containerWidth <= 0 || containerHeight <= 0) { - devLog.log('Container not ready for centered view calculation, using default'); - return { x: 0, y: 0, scale: 1 }; - } - - const centeredView = { - x: (containerWidth - contentWidth) / 2, - y: (containerHeight - contentHeight) / 2, - scale: 1 - }; - - devLog.log('Calculated centered view:', centeredView, 'container:', { containerWidth, containerHeight }, 'content:', { contentWidth, contentHeight }); - return centeredView; - } - - devLog.log('Container or content not ready for centered view calculation'); - return { x: 0, y: 0, scale: 1 }; - }, []); - - - useImperativeHandle(ref, () => ({ - getCanvasState: () => ({ view, nodes, edges }), - addNode: (nodeData, clientX, clientY) => { - const container = containerRef.current; - if (!container) return; - const rect = container.getBoundingClientRect(); - const worldX = (clientX - rect.left - view.x) / view.scale; - const worldY = (clientY - rect.top - view.y) / view.scale; - - const newNode = { - id: `${nodeData.id}-${Date.now()}`, - data: nodeData, - position: { x: worldX, y: worldY }, - }; - setNodes(prev => [...prev, newNode]); - }, - loadCanvasState: (state) => { - if (state.nodes) setNodes(state.nodes); - if (state.edges) setEdges(state.edges); - if (state.view) setView(state.view); - }, - loadWorkflowState: (state) => { - devLog.log('Canvas loadWorkflowState called with:', { - hasNodes: !!state.nodes, - nodesCount: state.nodes?.length || 0, - hasEdges: !!state.edges, - edgesCount: state.edges?.length || 0, - hasView: !!state.view, - view: state.view - }); - - if (state.nodes) { - devLog.log('Setting nodes:', state.nodes.length); - setNodes(state.nodes); - } - if (state.edges) { - devLog.log('Setting edges:', state.edges.length); - setEdges(state.edges); - } - if (state.view) { - devLog.log('Setting view:', state.view); - setView(state.view); - } - - devLog.log('Canvas loadWorkflowState completed'); - }, - getCenteredView, - clearSelectedNode: () => { - setSelectedNodeId(null); - setSelectedEdgeId(null); - }, - validateAndPrepareExecution: () => { - const validationResult = validateRequiredInputs(nodes, edges); - if (!validationResult.isValid) { - setSelectedNodeId(validationResult.nodeId); - setSelectedEdgeId(null); - return { - error: `Required input "${validationResult.inputName}" is missing in node "${validationResult.nodeName}"`, - nodeId: validationResult.nodeId - }; - } - setSelectedNodeId(null); - setSelectedEdgeId(null); - return { success: true }; - } - })); - - const calculateDistance = (pos1, pos2) => { - if (!pos1 || !pos2) return Infinity; - return Math.sqrt(Math.pow(pos1.x - pos2.x, 2) + Math.pow(pos1.y - pos2.y, 2)); - }; - - const copySelectedNode = () => { - if (selectedNodeId) { - const nodeToCopy = nodes.find(node => node.id === selectedNodeId); - if (nodeToCopy) { - setCopiedNode(nodeToCopy); - devLog.log('Node copied:', nodeToCopy.data.nodeName); - } - } - }; - - const pasteNode = () => { - if (copiedNode) { - const newNode = { - ...copiedNode, - id: `${copiedNode.data.id}-${Date.now()}`, - position: { - x: copiedNode.position.x + 50, - y: copiedNode.position.y + 50 - } - }; - - setNodes(prev => [...prev, newNode]); - setSelectedNodeId(newNode.id); - devLog.log('Node pasted:', newNode.data.nodeName); - } - }; - - - const handleParameterChange = useCallback((nodeId, paramId, value) => { - devLog.log('=== Canvas Parameter Change ==='); - devLog.log('Received:', { nodeId, paramId, value }); - - setNodes(prevNodes => { - devLog.log('Previous nodes count:', prevNodes.length); - - const targetNodeIndex = prevNodes.findIndex(node => node.id === nodeId); - if (targetNodeIndex === -1) { - devLog.warn('Target node not found:', nodeId); - return prevNodes; - } - - const targetNode = prevNodes[targetNodeIndex]; - devLog.log('Found target node:', targetNode.data.nodeName); - - if (!targetNode.data.parameters || !Array.isArray(targetNode.data.parameters)) { - devLog.warn('No parameters found in target node'); - return prevNodes; - } - - const targetParamIndex = targetNode.data.parameters.findIndex(param => param.id === paramId); - if (targetParamIndex === -1) { - devLog.warn('Target parameter not found:', paramId); - return prevNodes; - } - - const targetParam = targetNode.data.parameters[targetParamIndex]; - const newValue = typeof targetParam.value === 'number' ? Number(value) : value; - - if (targetParam.value === newValue) { - devLog.log('Parameter value unchanged, skipping update'); - return prevNodes; - } - - devLog.log('Updating parameter:', { - paramName: targetParam.name, - paramId, - oldValue: targetParam.value, - newValue - }); - - const newNodes = [...prevNodes]; - newNodes[targetNodeIndex] = { - ...targetNode, - data: { - ...targetNode.data, - parameters: [ - ...targetNode.data.parameters.slice(0, targetParamIndex), - { ...targetParam, value: newValue }, - ...targetNode.data.parameters.slice(targetParamIndex + 1) - ] - } - }; - - devLog.log('Parameter update completed successfully'); - devLog.log('=== End Canvas Parameter Change ==='); - return newNodes; - }); - }, []); - - const handleNodeNameChange = useCallback((nodeId, newName) => { - devLog.log('=== Canvas Node Name Change ==='); - devLog.log('Received:', { nodeId, newName }); - - setNodes(prevNodes => { - const targetNodeIndex = prevNodes.findIndex(node => node.id === nodeId); - if (targetNodeIndex === -1) { - devLog.warn('Target node not found:', nodeId); - return prevNodes; - } - - const targetNode = prevNodes[targetNodeIndex]; - if (targetNode.data.nodeName === newName) { - devLog.log('Node name unchanged, skipping update'); - return prevNodes; - } - - devLog.log('Updating node name:', { - nodeId, - oldName: targetNode.data.nodeName, - newName - }); - - const newNodes = [ - ...prevNodes.slice(0, targetNodeIndex), - { - ...targetNode, - data: { - ...targetNode.data, - nodeName: newName - } - }, - ...prevNodes.slice(targetNodeIndex + 1) - ]; - - devLog.log('Node name update completed successfully'); - devLog.log('=== End Canvas Node Name Change ==='); - return newNodes; - }); - }, []); - - const findPortData = (nodeId, portId, portType) => { - const node = nodes.find(n => n.id === nodeId); - if (!node) return null; - const portList = portType === 'input' ? node.data.inputs : node.data.outputs; - return portList?.find(p => p.id === portId) || null; - }; - - const handleCanvasMouseDown = (e) => { - const target = e.target; - const isParameterInput = target.matches('input, select, option') || - target.classList.contains('paramInput') || - target.classList.contains('paramSelect') || - target.closest('.param') || - target.closest('[class*="param"]'); - - if (isParameterInput) { - devLog.log('Canvas mousedown blocked for parameter input:', target); - return; - } - - if (e.button !== 0) return; - setSelectedNodeId(null); - setSelectedEdgeId(null); - setDragState({ type: 'canvas', startX: e.clientX - view.x, startY: e.clientY - view.y }); - }; - - const handleMouseMove = (e) => { - if (dragState.type === 'none') return; - - if (dragState.type === 'canvas') { - setView(prev => ({ ...prev, x: e.clientX - dragState.startX, y: e.clientY - dragState.startY })); - } else if (dragState.type === 'node') { - const newX = (e.clientX / view.scale) - dragState.offsetX; - const newY = (e.clientY / view.scale) - dragState.offsetY; - setNodes(prevNodes => - prevNodes.map(node => - node.id === dragState.nodeId ? { ...node, position: { x: newX, y: newY } } : node - ) - ); - } else if (dragState.type === 'edge') { - const container = containerRef.current; - if (!container || !edgePreviewRef.current) return; - - const rect = container.getBoundingClientRect(); - const mousePos = { - x: (e.clientX - rect.left - view.x) / view.scale, - y: (e.clientY - rect.top - view.y) / view.scale, - }; - - setEdgePreview(prev => ({ ...prev, targetPos: mousePos })); - - let closestPortKey = null; - let minSnapDistance = SNAP_DISTANCE; - const edgeSource = edgePreviewRef.current.source; - - if (edgeSource) { - portRefs.current.forEach((portEl, key) => { - const parts = key.split('__PORTKEYDELIM__'); - if (parts.length !== 3) return; - const [targetNodeId, targetPortId, targetPortType] = parts; - if (targetPortType === 'input' && edgeSource.nodeId !== targetNodeId) { - const targetPortWorldPos = portPositions[key]; - if (targetPortWorldPos) { - const distance = calculateDistance(mousePos, targetPortWorldPos); - if (distance < minSnapDistance) { - minSnapDistance = distance; - closestPortKey = key; - } - } - } - }); - - if (closestPortKey) { - const parts = closestPortKey.split('__PORTKEYDELIM__'); - const targetPort = findPortData(parts[0], parts[1], parts[2]); - const isValid = areTypesCompatible(edgeSource.type, targetPort.type); - setIsSnapTargetValid(isValid); - } else { - setIsSnapTargetValid(true); - } - } - setSnappedPortKey(closestPortKey); - } - }; - - const handleKeyDown = useCallback((e) => { - // ์ž…๋ ฅ ํ•„๋“œ์—์„œ์˜ ํ‚ค ์ด๋ฒคํŠธ๋Š” ๋ฌด์‹œ - if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA') { - return; - } - - const isCtrlOrCmd = e.ctrlKey || e.metaKey; - - if (isCtrlOrCmd && e.key === 'c') { - e.preventDefault(); - copySelectedNode(); - } - else if (isCtrlOrCmd && e.key === 'v') { - e.preventDefault(); - pasteNode(); - } - else if (isCtrlOrCmd && e.key === 'z') { - e.preventDefault(); - if (lastDeleted) { - setNodes(prev => [...prev, lastDeleted.node]); - setEdges(prev => [...prev, ...lastDeleted.edges]); - setLastDeleted(null); - devLog.log('Node restored:', lastDeleted.node.data.nodeName); - } - } - else if (e.key === 'Delete' && selectedNodeId) { - e.preventDefault(); - const nodeToDelete = nodes.find(node => node.id === selectedNodeId); - if (nodeToDelete) { - const connectedEdges = edges.filter(edge => edge.source.nodeId === selectedNodeId || edge.target.nodeId === selectedNodeId); - setLastDeleted({ node: nodeToDelete, edges: connectedEdges }); - - setNodes(prev => prev.filter(node => node.id !== selectedNodeId)); - setEdges(prev => prev.filter(edge => edge.source.nodeId !== selectedNodeId && edge.target.nodeId !== selectedNodeId)); - setSelectedNodeId(null); - devLog.log('Node deleted and saved for undo:', nodeToDelete.data.nodeName); - } - } - }, [selectedNodeId, copiedNode, nodes, edges, lastDeleted]); - - const handleNodeMouseDown = useCallback((e, nodeId) => { - if (e.button !== 0) return; - setSelectedNodeId(nodeId); - setSelectedEdgeId(null); - const node = nodes.find(n => n.id === nodeId); - if (node) { - setDragState({ - type: 'node', - nodeId, - offsetX: (e.clientX / view.scale) - node.position.x, - offsetY: (e.clientY / view.scale) - node.position.y, - }); - } - }, [nodes, view.scale]); - - const handleEdgeClick = useCallback((edgeId) => { - setSelectedEdgeId(edgeId); - setSelectedNodeId(null); - }, []); - - const handlePortMouseUp = useCallback(({ nodeId, portId, portType, type }) => { - const currentEdgePreview = edgePreviewRef.current; - if (!currentEdgePreview) return; - - if (currentEdgePreview && !areTypesCompatible(currentEdgePreview.source.type, type)) { - setSnappedPortKey(null); - setIsSnapTargetValid(true); - setEdgePreview(null); - return; - } - - if (!currentEdgePreview || currentEdgePreview.source.portType === portType) { - setEdgePreview(null); - return; - }; - - const sourceNodeId = currentEdgePreview.source.nodeId; - if (sourceNodeId === nodeId) { - setEdgePreview(null); - return; - } - - const newEdgeSignature = `${currentEdgePreview.source.nodeId}:${currentEdgePreview.source.portId}-${nodeId}:${portId}`; - const isDuplicate = edges.some(edge => - `${edge.source.nodeId}:${edge.source.portId}-${edge.target.nodeId}:${edge.target.portId}` === newEdgeSignature - ); - - if (isDuplicate) { - setEdgePreview(null); - return; - } - - let newEdges = [...edges]; - if (portType === 'input') { - const targetPort = findPortData(nodeId, portId, 'input'); - if (targetPort && !targetPort.multi) { - newEdges = newEdges.filter(edge => !(edge.target.nodeId === nodeId && edge.target.portId === portId)); - } - } - - const newEdge = { - id: `edge-${newEdgeSignature}-${Date.now()}`, - source: currentEdgePreview.source, - target: { nodeId, portId, portType } - }; - setEdges([...newEdges, newEdge]); - setEdgePreview(null); - setSnappedPortKey(null); - setIsSnapTargetValid(true); - }, [edges, nodes]); - - const handleMouseUp = useCallback(() => { - setDragState({ type: 'none' }); - - if (dragState.type === 'edge') { - const snappedKey = snappedPortKeyRef.current; - if (snappedKey) { - const source = edgePreviewRef.current.source; - const parts = snappedKey.split('__PORTKEYDELIM__'); - const [targetNodeId, targetPortId, targetPortType] = parts; - - const targetPortData = findPortData(targetNodeId, targetPortId, targetPortType); - - if (targetPortData && areTypesCompatible(source.type, targetPortData.type)) { - handlePortMouseUp({ - nodeId: targetNodeId, - portId: targetPortId, - portType: targetPortType, - type: targetPortData.type - }); - } - } - } - - setEdgePreview(null); - setSnappedPortKey(null); - setIsSnapTargetValid(true); - - }, [dragState.type, handlePortMouseUp]); - - const handlePortMouseDown = useCallback(({ nodeId, portId, portType, isMulti, type }) => { - if (portType === 'input') { - let existingEdge - if (!isMulti) { - existingEdge = edges.find(e => e.target.nodeId === nodeId && e.target.portId === portId); - } else{ - existingEdge = edges.findLast(e => e.target.nodeId === nodeId && e.target.portId === portId); - } - if (existingEdge) { - setDragState({ type: 'edge' }); - devLog.log(existingEdge) - const sourcePosKey = `${existingEdge.source.nodeId}__PORTKEYDELIM__${existingEdge.source.portId}__PORTKEYDELIM__${existingEdge.source.portType}`; - const sourcePos = portPositions[sourcePosKey]; - const targetPosKey = `${existingEdge.target.nodeId}__PORTKEYDELIM__${existingEdge.target.portId}__PORTKEYDELIM__${existingEdge.target.portType}`; - const targetPos = portPositions[targetPosKey]; - - const sourcePortData = findPortData(existingEdge.source.nodeId, existingEdge.source.portId, existingEdge.source.portType); - - if (sourcePos && sourcePortData) { - setEdgePreview({ - source: { ...existingEdge.source, type: sourcePortData.type }, - startPos: sourcePos, - targetPos: targetPos - }); - } - - setEdges(prevEdges => prevEdges.filter(e => e.id !== existingEdge.id)); - return; - } - - } - - if (portType === 'output') { - setDragState({ type: 'edge' }); - const startPosKey = `${nodeId}__PORTKEYDELIM__${portId}__PORTKEYDELIM__${portType}`; - const startPos = portPositions[startPosKey]; - if (startPos) { - setEdgePreview({ source: { nodeId, portId, portType, type }, startPos, targetPos: startPos }); - } - return; - } - }, [edges, portPositions, nodes]); - - - useEffect(() => { - nodesRef.current = nodes; - edgePreviewRef.current = edgePreview; - snappedPortKeyRef.current = snappedPortKey; - isSnapTargetValidRef.current = isSnapTargetValid; - }, [nodes, edgePreview, snappedPortKey, isSnapTargetValid]); - - useEffect(() => { - const container = containerRef.current; - if (!container) return; - const handleWheel = (e) => { - e.preventDefault(); - setView(prevView => { - const delta = e.deltaY > 0 ? -1 : 1; - const newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, prevView.scale + delta * ZOOM_SENSITIVITY * prevView.scale)); - if (newScale === prevView.scale) return prevView; - const rect = container.getBoundingClientRect(); - const mouseX = e.clientX - rect.left; - const mouseY = e.clientY - rect.top; - const worldX = (mouseX - prevView.x) / prevView.scale; - const worldY = (mouseY - prevView.y) / prevView.scale; - const newX = mouseX - worldX * newScale; - const newY = mouseY - worldY * newScale; - return { x: newX, y: newY, scale: newScale }; - }); - }; - container.addEventListener('wheel', handleWheel, { passive: false }); - return () => container.removeEventListener('wheel', handleWheel); - }, []); - - useEffect(() => { - const container = containerRef.current; - if (container) { - container.addEventListener('keydown', handleKeyDown); - container.setAttribute('tabindex', '0'); - - return () => { - container.removeEventListener('keydown', handleKeyDown); - }; - } - }, [handleKeyDown]); - - useEffect(() => { - const container = containerRef.current; - const content = contentRef.current; - if (container && content) { - const centeredView = getCenteredView(); - setView(centeredView); - } - }, []); - - useEffect(() => { - devLog.log('Canvas mounted, checking initial state'); - if (onStateChange && (nodes.length > 0 || edges.length > 0)) { - devLog.log('Canvas has content, sending initial state'); - const initialState = { view, nodes, edges }; - onStateChange(initialState); - } else { - devLog.log('Canvas is empty, not sending initial state to avoid overwriting localStorage'); - } - }, []); - - useEffect(() => { - const handleKeyDown = (e) => { - // ์ž…๋ ฅ ํ•„๋“œ์—์„œ์˜ ํ‚ค ์ด๋ฒคํŠธ๋Š” ๋ฌด์‹œ - if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA') { - return; - } - - if (e.key === 'Delete' || e.key === 'Backspace') { - e.preventDefault(); // ํŽ˜์ด์ง€ ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฐฉ์ง€ - if (selectedNodeId) { - setNodes(prev => prev.filter(node => node.id !== selectedNodeId)); - setEdges(prev => prev.filter(edge => edge.source.nodeId !== selectedNodeId && edge.target.nodeId !== selectedNodeId)); - setSelectedNodeId(null); - } else if (selectedEdgeId) { - setEdges(prev => prev.filter(edge => edge.id !== selectedEdgeId)); - setSelectedEdgeId(null); - } - } - }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [selectedNodeId, selectedEdgeId]); - - return ( -
containerRef.current?.focus()} - tabIndex={0} - style={{ outline: 'none' }} - > -
- {nodes.map(node => ( - setSelectedNodeId(null)} - /> - ))} - - - {edges - .filter(edge => edge.id !== selectedEdgeId) - .map(edge => { - const sourceKey = `${edge.source.nodeId}__PORTKEYDELIM__${edge.source.portId}__PORTKEYDELIM__${edge.source.portType}`; - const targetKey = `${edge.target.nodeId}__PORTKEYDELIM__${edge.target.portId}__PORTKEYDELIM__${edge.target.portType}`; - const sourcePos = portPositions[sourceKey]; - const targetPos = portPositions[targetKey]; - return ; - })} - - {edges - .filter(edge => edge.id === selectedEdgeId) - .map(edge => { - const sourceKey = `${edge.source.nodeId}__PORTKEYDELIM__${edge.source.portId}__PORTKEYDELIM__${edge.source.portType}`; - const targetKey = `${edge.target.nodeId}__PORTKEYDELIM__${edge.target.portId}__PORTKEYDELIM__${edge.target.portType}`; - const sourcePos = portPositions[sourceKey]; - const targetPos = portPositions[targetKey]; - return ; - })} - {edgePreview?.targetPos && ( - - )} - - -
-
- ); -}); - -Canvas.displayName = 'Canvas'; -export default memo(Canvas); \ No newline at end of file diff --git a/temporary/(canvas_js)/components/Edge.jsx b/temporary/(canvas_js)/components/Edge.jsx deleted file mode 100644 index 3368f883..00000000 --- a/temporary/(canvas_js)/components/Edge.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { memo } from 'react'; -import styles from '@/app/canvas/assets/Edge.module.scss'; - -const getBezierPath = (x1, y1, x2, y2) => { - const controlPointX1 = x1 + Math.abs(x2 - x1) * 0.5; - const controlPointX2 = x2 - Math.abs(x2 - x1) * 0.5; - return `M ${x1},${y1} C ${controlPointX1},${y1} ${controlPointX2},${y2} ${x2},${y2}`; -}; - -const Edge = ({ id, sourcePos, targetPos, onEdgeClick, isSelected, isPreview = false }) => { - if (!sourcePos || !targetPos) return null; - - const d = getBezierPath(sourcePos.x, sourcePos.y, targetPos.x, targetPos.y); - - const handleEdgeClick = (e) => { - if (isPreview) return; // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ์—์„œ๋Š” ํด๋ฆญ ๋น„ํ™œ์„ฑํ™” - e.stopPropagation(); - onEdgeClick(id); - }; - - return ( - - - - - - ); -}; - -export default memo(Edge); \ No newline at end of file diff --git a/temporary/(canvas_js)/components/ExecutionPanel.jsx b/temporary/(canvas_js)/components/ExecutionPanel.jsx deleted file mode 100644 index cca85978..00000000 --- a/temporary/(canvas_js)/components/ExecutionPanel.jsx +++ /dev/null @@ -1,94 +0,0 @@ -"use client"; -import React, { useState } from 'react'; -import styles from '@/app/canvas/assets/ExecutionPanel.module.scss'; -import { LuPlay, LuTrash2, LuCircleX, LuChevronUp, LuChevronDown } from 'react-icons/lu'; - -const OutputRenderer = ({ output }) => { - if (!output) { - return
Click 'Run' to execute the workflow.
; - } - - if (output.error) { - return ( -
-
- - Execution Failed -
-
{output.error}
-
- ); - } - - const { outputs } = output; - return ( -
-
-
-                    {JSON.stringify(outputs, null, 2)}
-                
-
-
- ); -}; - - -const ExecutionPanel = ({ onExecute, onClear, output, isLoading }) => { - const [isExpanded, setIsExpanded] = useState(true); - - const toggleExpanded = () => { - setIsExpanded(!isExpanded); - }; - - return ( -
-
-
- -

Execution

-
-
- - -
-
- {isExpanded && ( -
-
-                        
-                    
-
- )} -
- ); -}; - -export default ExecutionPanel; \ No newline at end of file diff --git a/temporary/(canvas_js)/components/Header.jsx b/temporary/(canvas_js)/components/Header.jsx deleted file mode 100644 index 411389ce..00000000 --- a/temporary/(canvas_js)/components/Header.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import Link from 'next/link'; -import styles from '@/app/canvas/assets/Header.module.scss'; -import { LuPanelRightOpen, LuSave, LuCheck, LuX, LuPencil, LuFileText } from "react-icons/lu"; -import { getWorkflowName, saveWorkflowName } from '@/app/_common/components/workflowStorage'; - -const Header = ({ onMenuClick, onSave, onLoad, onExport, onNewWorkflow, workflowName: externalWorkflowName, onWorkflowNameChange }) => { - const [workflowName, setWorkflowName] = useState('Workflow'); - const [isEditing, setIsEditing] = useState(false); - const [editValue, setEditValue] = useState(''); - const inputRef = useRef(null); - - useEffect(() => { - if (externalWorkflowName) { - setWorkflowName(externalWorkflowName); - } else { - const savedName = getWorkflowName(); - setWorkflowName(savedName); - } - }, [externalWorkflowName]); - - useEffect(() => { - if (isEditing && inputRef.current) { - inputRef.current.focus(); - inputRef.current.select(); - } - }, [isEditing]); - - const handleEditClick = () => { - setEditValue(workflowName); - setIsEditing(true); - }; - - const handleSaveClick = () => { - const trimmedValue = editValue.trim(); - const finalValue = trimmedValue || 'Workflow'; - setWorkflowName(finalValue); - saveWorkflowName(finalValue); - - // ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ๋ณ€๊ฒฝ์‚ฌํ•ญ ์•Œ๋ฆผ - if (onWorkflowNameChange) { - onWorkflowNameChange(finalValue); - } - - setIsEditing(false); - }; - - const handleCancelClick = () => { - setEditValue(workflowName); - setIsEditing(false); - }; - - const handleKeyDown = (e) => { - if (e.key === 'Enter') { - handleSaveClick(); - } else if (e.key === 'Escape') { - handleCancelClick(); - } - }; - - return ( -
-
- -
- PlateeRAG -
- -
- {isEditing ? ( -
- setEditValue(e.target.value)} - onKeyDown={handleKeyDown} - className={styles.workflowInput} - placeholder="Workflow name..." - /> - - -
- ) : ( -
- {workflowName} - -
- )} -
-
-
- - - -
-
- ); -}; - -export default Header; \ No newline at end of file diff --git a/temporary/(canvas_js)/components/Helper/DraggableNodeItem.jsx b/temporary/(canvas_js)/components/Helper/DraggableNodeItem.jsx deleted file mode 100644 index bab6b389..00000000 --- a/temporary/(canvas_js)/components/Helper/DraggableNodeItem.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import styles from '@/app/canvas/assets/SideMenu.module.scss'; - -const DraggableNodeItem = ({ nodeData }) => { - const onDragStart = (event) => { - event.dataTransfer.setData('application/json', JSON.stringify(nodeData)); - event.dataTransfer.effectAllowed = 'move'; - }; - - return ( -
- {nodeData.nodeName} -
- ); -}; - -export default DraggableNodeItem; \ No newline at end of file diff --git a/temporary/(canvas_js)/components/Helper/NodeList.jsx b/temporary/(canvas_js)/components/Helper/NodeList.jsx deleted file mode 100644 index 0a555fee..00000000 --- a/temporary/(canvas_js)/components/Helper/NodeList.jsx +++ /dev/null @@ -1,27 +0,0 @@ -"use client"; -import React, { useState } from 'react'; -import styles from '@/app/canvas/assets/NodeList.module.scss'; -import { LuChevronDown } from 'react-icons/lu'; - -const NodeList = ({ title, children }) => { - const [isOpen, setIsOpen] = useState(false); - - return ( -
- - {isOpen && ( -
- {children} -
- )} -
- ); -}; - -export default NodeList; \ No newline at end of file diff --git a/temporary/(canvas_js)/components/Node.jsx b/temporary/(canvas_js)/components/Node.jsx deleted file mode 100644 index 00b8b7b9..00000000 --- a/temporary/(canvas_js)/components/Node.jsx +++ /dev/null @@ -1,298 +0,0 @@ -// src/app/components/Node.jsx -import React, { memo, useState, useEffect } from 'react'; -import styles from '@/app/canvas/assets/Node.module.scss'; -import { devLog } from '@/app/utils/logger'; - -const Node = ({ id, data, position, onNodeMouseDown, isSelected, onPortMouseDown, onPortMouseUp, registerPortRef, snappedPortKey, onParameterChange, isSnapTargetInvalid, isPreview = false, onNodeNameChange, onClearSelection }) => { - const { nodeName, inputs, parameters, outputs, functionId } = data; - const [showAdvanced, setShowAdvanced] = useState(false); - const [isEditingName, setIsEditingName] = useState(false); - const [editingName, setEditingName] = useState(nodeName); - - // nodeName์ด ๋ณ€๊ฒฝ๋  ๋•Œ editingName ๋™๊ธฐํ™” - useEffect(() => { - setEditingName(nodeName); - }, [nodeName]); - - // ๋…ธ๋“œ ์ด๋ฆ„ ํŽธ์ง‘ ๊ด€๋ จ ํ•จ์ˆ˜๋“ค - const handleNameDoubleClick = (e) => { - if (isPreview) return; - e.stopPropagation(); - setIsEditingName(true); - setEditingName(nodeName); - }; - - const handleNameChange = (e) => { - setEditingName(e.target.value); - }; - - const handleNameKeyDown = (e) => { - if (e.key === 'Enter') { - handleNameSubmit(); - } else if (e.key === 'Escape') { - handleNameCancel(); - } - e.stopPropagation(); - }; - - const handleNameSubmit = () => { - const trimmedName = editingName.trim(); - if (trimmedName && trimmedName !== nodeName && onNodeNameChange) { - onNodeNameChange(id, trimmedName); - } else { - // ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†๊ฑฐ๋‚˜ ๋นˆ ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ ์›๋ž˜ ๊ฐ’์œผ๋กœ ๋ณต์› - setEditingName(nodeName); - } - setIsEditingName(false); - }; - - const handleNameCancel = () => { - setEditingName(nodeName); - setIsEditingName(false); - }; - - const handleNameBlur = () => { - handleNameSubmit(); - }; - - // ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ธฐ๋ณธ/๊ณ ๊ธ‰์œผ๋กœ ๋ถ„๋ฆฌ - const basicParameters = parameters?.filter(param => !param.optional) || []; - const advancedParameters = parameters?.filter(param => param.optional) || []; - const hasAdvancedParams = advancedParameters.length > 0; - - const toggleAdvanced = (e) => { - e.stopPropagation(); - setShowAdvanced(prev => !prev); - }; - - // ํŒŒ๋ผ๋ฏธํ„ฐ ๋ Œ๋”๋ง ํ•จ์ˆ˜ - const renderParameter = (param) => ( -
- - {param.name} - - {param.options && param.options.length > 0 ? ( - - ) : ( - handleParamValueChange(e, param.id)} - onMouseDown={(e) => { - devLog.log('input onMouseDown'); - e.stopPropagation(); - }} - onClick={(e) => { - devLog.log('input onClick'); - e.stopPropagation(); - }} - onFocus={(e) => { - devLog.log('input onFocus'); - e.stopPropagation(); - // ํŒŒ๋ผ๋ฏธํ„ฐ ํŽธ์ง‘ ์‹œ ๋…ธ๋“œ ์„ ํƒ ํ•ด์ œ - if (onClearSelection) { - onClearSelection(); - } - }} - onKeyDown={(e) => { - // ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ ์ „ํŒŒ ๋ฐฉ์ง€ (๋ฐฑ์ŠคํŽ˜์ด์Šค, ์‚ญ์ œ ๋“ฑ) - e.stopPropagation(); - }} - className={`${styles.paramInput} paramInput`} - step={param.step} - min={param.min} - max={param.max} - /> - )} -
- ); - - const handleMouseDown = (e) => { - if (isPreview) return; // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ์—์„œ๋Š” ๋“œ๋ž˜๊ทธ ๋น„ํ™œ์„ฑํ™” - e.stopPropagation(); - onNodeMouseDown(e, id); - }; - - const handleParamValueChange = (e, paramId) => { - devLog.log('=== Parameter Change Event ==='); - devLog.log('nodeId:', id, 'paramId:', paramId, 'value:', e.target.value); - - // ์ด๋ฒคํŠธ ์ „ํŒŒ ์ค‘๋‹จ - e.preventDefault(); - e.stopPropagation(); - - try { - // ๊ฐ’ ๊ฒ€์ฆ - const value = e.target.value; - if (value === undefined || value === null) { - devLog.warn('Invalid parameter value:', value); - return; - } - - devLog.log('Calling onParameterChange...'); - // ์•ˆ์ „ํ•œ ์ฝœ๋ฐฑ ํ˜ธ์ถœ - if (typeof onParameterChange === 'function') { - onParameterChange(id, paramId, value); - devLog.log('onParameterChange completed successfully'); - } else { - devLog.error('onParameterChange is not a function'); - } - } catch (error) { - devLog.error('Error in handleParamValueChange:', error); - } - devLog.log('=== End Parameter Change ==='); - }; - - const hasInputs = inputs?.length > 0; - const hasOutputs = outputs?.length > 0; - const hasIO = hasInputs || hasOutputs; - const hasParams = parameters?.length > 0; - const hasOnlyOutputs = hasOutputs && !hasInputs; - - // ๋…ธ๋“œ ์ด๋ฆ„ ํ‘œ์‹œ์šฉ (10์ž ๋„˜์œผ๋ฉด ... ์ฒ˜๋ฆฌ) - const displayName = nodeName.length > 20 ? nodeName.substring(0, 20) + '...' : nodeName; - - return ( -
-
- {isEditingName ? ( - { - e.stopPropagation(); - }} - onClick={(e) => { - e.stopPropagation(); - }} - onFocus={(e) => { - e.stopPropagation(); - }} - className={styles.nameInput} - autoFocus - /> - ) : ( - - {displayName} - - )} - {functionId && ({functionId})} -
-
- {hasIO && ( -
- {hasInputs && ( -
-
INPUT
- {inputs.map(portData => { - const portKey = `${id}__PORTKEYDELIM__${portData.id}__PORTKEYDELIM__input`; - const isSnapping = snappedPortKey === portKey; - - const portClasses = [ styles.port, styles.inputPort, portData.multi ? styles.multi : '', styles[`type-${portData.type}`], isSnapping ? styles.snapping : '', isSnapping && isSnapTargetInvalid ? styles['invalid-snap'] : '' ].filter(Boolean).join(' '); - - return ( -
-
registerPortRef && registerPortRef(id, portData.id, 'input', el)} - className={portClasses} - onMouseDown={isPreview ? undefined : (e) => { e.stopPropagation(); onPortMouseDown({ nodeId: id, portId: portData.id, portType: 'input', isMulti: portData.multi, type: portData.type }) }} - onMouseUp={isPreview ? undefined : (e) => { e.stopPropagation(); onPortMouseUp({ nodeId: id, portId: portData.id, portType: 'input', type: portData.type }) }} - > - {portData.type} -
- - {portData.name} - -
- ) - })} -
- )} - {hasOutputs && ( -
-
OUTPUT
- {outputs.map(portData => { - const portClasses = [ styles.port, styles.outputPort, portData.multi ? styles.multi : '', styles[`type-${portData.type}`] ].filter(Boolean).join(' '); - - return ( -
- {portData.name} -
registerPortRef && registerPortRef(id, portData.id, 'output', el)} - className={portClasses} - onMouseDown={isPreview ? undefined : (e) => { e.stopPropagation(); onPortMouseDown({ nodeId: id, portId: portData.id, portType: 'output', isMulti: portData.multi, type: portData.type }) }} - onMouseUp={isPreview ? undefined : (e) => { e.stopPropagation(); onPortMouseUp({ nodeId: id, portId: portData.id, portType: 'output', type: portData.type }) }} - > - {portData.type} -
-
- ) - })} -
- )} -
- )} - {hasParams && ( - <> - {hasIO &&
} -
-
PARAMETER
- {basicParameters.map(param => renderParameter(param))} - {hasAdvancedParams && ( -
-
- Advanced {showAdvanced ? 'โ–ฒ' : 'โ–ผ'} -
- {showAdvanced && advancedParameters.map(param => renderParameter(param))} -
- )} -
- - )} -
-
- ); -}; - -export default memo(Node); \ No newline at end of file diff --git a/temporary/(canvas_js)/components/SideMenu.jsx b/temporary/(canvas_js)/components/SideMenu.jsx deleted file mode 100644 index cb923ab2..00000000 --- a/temporary/(canvas_js)/components/SideMenu.jsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; -import React, { useState } from 'react'; -import styles from '@/app/canvas/assets/SideMenu.module.scss'; -import AddNodePanel from '@/app/canvas/components/SideMenuPanel/AddNodePanel'; -import ChatPanel from '@/app/canvas/components/SideMenuPanel/ChatPanel'; -import WorkflowPanel from '@/app/canvas/components/SideMenuPanel/WorkflowPanel'; -import TemplatePanel from '@/app/canvas/components/SideMenuPanel/TemplatePanel'; -import { LuCirclePlus, LuCircleHelp, LuSettings, LuLayoutGrid, LuMessageSquare, LuLayoutTemplate } from "react-icons/lu"; - -// ๋ฉ”์ธ ๋ฉ”๋‰ด UI -const MainMenu = ({ onNavigate }) => { - return ( - <> -
- - - - - - -
- - ); -}; - -// SideMenu์˜ ์ „์ฒด ์ปจํ…Œ์ด๋„ˆ ๋ฐ ๋ทฐ ์ „ํ™˜ ๋กœ์ง -const SideMenu = ({ menuRef, onLoad, onExport, onLoadWorkflow }) => { - const [view, setView] = useState('main'); - - return ( - // menuRef๋ฅผ ๋ฐ›์•„ ์™ธ๋ถ€ ํด๋ฆญ ๊ฐ์ง€์— ์‚ฌ์šฉ - - ); -}; - -export default SideMenu; \ No newline at end of file diff --git a/temporary/(canvas_js)/components/SideMenuPanel/AddNodePanel.jsx b/temporary/(canvas_js)/components/SideMenuPanel/AddNodePanel.jsx deleted file mode 100644 index 082c4a74..00000000 --- a/temporary/(canvas_js)/components/SideMenuPanel/AddNodePanel.jsx +++ /dev/null @@ -1,104 +0,0 @@ -"use client"; -import React, { useState, useEffect } from 'react'; -import styles from '@/app/canvas/assets/SideMenu.module.scss'; -import NodeList from '@/app/canvas/components/Helper/NodeList'; -import DraggableNodeItem from '@/app/canvas/components/Helper/DraggableNodeItem'; -import { LuSearch, LuArrowLeft, LuBrainCircuit, LuShare2, LuWrench, LuX, LuRefreshCw } from 'react-icons/lu'; -import { SiLangchain } from "react-icons/si"; -import { useNodes } from '@/app/_common/components/nodeHook'; - -const iconMap = { - LuBrainCircuit: , - LuShare2: , - LuWrench: , - SiLangchain: , -}; - -const AddNodePanel = ({ onBack }) => { - const { nodes: nodeSpecs, isLoading, error, exportAndRefreshNodes } = useNodes(); - const [activeTab, setActiveTab] = useState(null); - - useEffect(() => { - if (nodeSpecs && nodeSpecs.length > 0) { - setActiveTab(nodeSpecs[0].categoryId); - } - }, [nodeSpecs]); - - const activeTabData = nodeSpecs.find(tab => tab.categoryId === activeTab); - if (isLoading) { - return ( - <> -
- -

Add Nodes

-
-
Loading nodes...
- - ) - } - - if (error) { - return ( - <> -
- -

Add Nodes

-
-
Error: {error}
- - ) - } - - return ( - <> -
- -

Add Nodes

- -
- -
- - - -
- -
- {nodeSpecs.map(tab => ( - - ))} -
- -
- {/* [์ˆ˜์ •] categories -> functions, ๋‚ด๋ถ€ ํ‚ค ์ด๋ฆ„๋“ค๋„ ๋ณ€๊ฒฝ */} - {activeTabData?.functions?.map(func => ( - - {func.nodes?.map(node => ( - - ))} - - ))} -
- - ); -}; - -export default AddNodePanel; \ No newline at end of file diff --git a/temporary/(canvas_js)/components/SideMenuPanel/ChatPanel.jsx b/temporary/(canvas_js)/components/SideMenuPanel/ChatPanel.jsx deleted file mode 100644 index a94d22fe..00000000 --- a/temporary/(canvas_js)/components/SideMenuPanel/ChatPanel.jsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { sendMessage } from '@/app/api/chatAPI'; -import styles from '@/app/canvas/assets/Chat.module.scss'; -import sideMenuStyles from '@/app/canvas/assets/SideMenu.module.scss'; -import { LuArrowLeft, LuSend } from "react-icons/lu"; -import { devLog } from '@/app/utils/logger'; - -const ChatPanel = ({ onBack }) => { - const [messages, setMessages] = useState([]); - const [inputValue, setInputValue] = useState(''); - const [isLoading, setIsLoading] = useState(false); - const messageListRef = useRef(null); - - useEffect(() => { - if (messageListRef.current) { - messageListRef.current.scrollTop = messageListRef.current.scrollHeight; - } - }, [messages]); - - const handleInputChange = (e) => { - setInputValue(e.target.value); - }; - - const handleSendMessage = async () => { - if (inputValue.trim() === '' || isLoading) return; - - const userMessage = { - id: Date.now(), - text: inputValue, - sender: 'user', - timestamp: new Date(), - }; - setMessages(prevMessages => [...prevMessages, userMessage]); - setIsLoading(true); - setInputValue(''); - - try { - const response = await sendMessage(userMessage.text); - const botMessage = { - id: Date.now() + 1, - text: response.text, - sender: 'bot', - timestamp: new Date(), - }; - setMessages(prevMessages => [...prevMessages, botMessage]); - } catch (error) { - devLog.error("Error sending message:", error); - const errorMessage = { - id: Date.now() + 1, - text: "Sorry, I couldn't get a response. Please try again.", - sender: 'bot', - timestamp: new Date(), - }; - setMessages(prevMessages => [...prevMessages, errorMessage]); - } finally { - setIsLoading(false); - } - }; - - const handleKeyPress = (e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSendMessage(); - } - }; - - return ( -
-
- -

Chat

-
- -
- {messages.map(msg => ( -
- {msg.text} -
- ))} - {isLoading && messages.length > 0 && messages[messages.length -1].sender === 'user' && ( -
- -
- )} -
- -
-