@@ -125,12 +249,24 @@ export default function HomePage() {
{/* CTA Section */}
@@ -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 (
-
-
-
-
- {isExpanded ? : }
-
-
Execution
-
-
-
-
- Clear
-
-
- {isLoading ? (
-
- ) : (
- <>
-
- Run
- >
- )}
-
-
-
- {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 (
-
-
setIsOpen(!isOpen)}>
- {title}
-
-
- {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('select onMouseDown');
- e.stopPropagation();
- }}
- onClick={(e) => {
- devLog.log('select onClick');
- e.stopPropagation();
- }}
- onFocus={(e) => {
- devLog.log('select onFocus');
- e.stopPropagation();
- // ํ๋ผ๋ฏธํฐ ํธ์ง ์ ๋
ธ๋ ์ ํ ํด์
- if (onClearSelection) {
- onClearSelection();
- }
- }}
- onBlur={(e) => {
- devLog.log('select onBlur');
- e.stopPropagation();
- }}
- onKeyDown={(e) => {
- // ํค๋ณด๋ ์ด๋ฒคํธ ์ ํ ๋ฐฉ์ง
- e.stopPropagation();
- }}
- className={`${styles.paramSelect} paramSelect`}
- >
- {param.options.map((option, index) => (
-
- {option.label || option.value}
-
- ))}
-
- ) : (
- 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 (
- <>
-
- onNavigate('addNodes')}>
-
- Add Node
-
- onNavigate('chat')}>
-
- Chat
-
- onNavigate('workflow')}>
-
- Workflow
-
- onNavigate('template')}>
-
- Template
-
-
-
- Settings
-
-
-
- Help
-
-
- >
- );
-};
-
-// SideMenu์ ์ ์ฒด ์ปจํ
์ด๋ ๋ฐ ๋ทฐ ์ ํ ๋ก์ง
-const SideMenu = ({ menuRef, onLoad, onExport, onLoadWorkflow }) => {
- const [view, setView] = useState('main');
-
- return (
- // menuRef๋ฅผ ๋ฐ์ ์ธ๋ถ ํด๋ฆญ ๊ฐ์ง์ ์ฌ์ฉ
-
- {view === 'main' && }
- {view === 'addNodes' && setView('main')} />}
- {view === 'chat' && setView('main')} />}
- {view === 'workflow' && (
- setView('main')}
- onLoad={onLoad}
- onExport={onExport}
- onLoadWorkflow={onLoadWorkflow}
- />
- )}
- {view === 'template' && (
- setView('main')}
- onLoadWorkflow={onLoadWorkflow}
- />
- )}
-
- );
-};
-
-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 => (
- setActiveTab(tab.categoryId)}
- >
- {iconMap[tab.icon]}
- {/* [์์ ] name -> categoryName */}
- {tab.categoryName}
-
- ))}
-
-
-
- {/* [์์ ] 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' && (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default ChatPanel;
diff --git a/temporary/(canvas_js)/components/SideMenuPanel/MiniCanvas.jsx b/temporary/(canvas_js)/components/SideMenuPanel/MiniCanvas.jsx
deleted file mode 100644
index e5c8087b..00000000
--- a/temporary/(canvas_js)/components/SideMenuPanel/MiniCanvas.jsx
+++ /dev/null
@@ -1,206 +0,0 @@
-"use client";
-import React, { useState, useRef, useEffect } from 'react';
-import Node from '@/app/canvas/components/Node';
-import Edge from '@/app/canvas/components/Edge';
-import { devLog } from '@/app/utils/logger';
-import styles from '@/app/canvas/assets/MiniCanvas.module.scss';
-
-const MiniCanvas = ({ template }) => {
- const canvasRef = useRef(null);
- const [scale, setScale] = useState(0.6);
- const [offset, setOffset] = useState({ x: 0, y: 0 });
- const [isDragging, setIsDragging] = useState(false);
- const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
-
- // ์ํฌํ๋ก์ฐ ๋ฐ์ดํฐ
- const nodes = template.data?.nodes || [];
- const edges = template.data?.edges || [];
-
- // ๋
ธ๋ ์์น๋ฅผ ๋ฏธ๋ ์บ๋ฒ์ค์ ๋ง๊ฒ ์กฐ์
- const adjustedNodes = nodes.map(node => ({
- ...node,
- position: {
- x: (node.position.x - 8800) * 0.8, // ์ขํ ์กฐ์
- y: (node.position.y - 4300) * 0.8
- }
- }));
-
- // ๋ง์ฐ์ค ํ ์ค
- const handleWheel = (e) => {
- e.preventDefault();
- const delta = e.deltaY > 0 ? -0.1 : 0.1;
- setScale(prev => Math.max(0.2, Math.min(2, prev + delta)));
- };
-
- // ๋๋๊ทธ ์์
- const handleMouseDown = (e) => {
- e.preventDefault();
- e.stopPropagation();
- setIsDragging(true);
- const rect = canvasRef.current.getBoundingClientRect();
- setDragStart({
- x: e.clientX - rect.left - offset.x,
- y: e.clientY - rect.top - offset.y
- });
- };
-
- // ๋๋๊ทธ ์ค
- const handleMouseMove = (e) => {
- if (!isDragging) return;
- e.preventDefault();
- e.stopPropagation();
- const rect = canvasRef.current?.getBoundingClientRect();
- if (!rect) return;
-
- setOffset({
- x: e.clientX - rect.left - dragStart.x,
- y: e.clientY - rect.top - dragStart.y
- });
- };
-
- // ๋๋๊ทธ ์ข
๋ฃ
- const handleMouseUp = (e) => {
- e.preventDefault();
- e.stopPropagation();
- setIsDragging(false);
- };
-
- // ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
- useEffect(() => {
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- canvas.addEventListener('wheel', handleWheel, { passive: false });
- canvas.addEventListener('mousedown', handleMouseDown);
-
- return () => {
- canvas.removeEventListener('wheel', handleWheel);
- canvas.removeEventListener('mousedown', handleMouseDown);
- };
- }, []);
-
- // ๋๋๊ทธ ์ด๋ฒคํธ๋ ์ ์ญ์ผ๋ก ๋ฑ๋ก
- useEffect(() => {
- if (isDragging) {
- document.addEventListener('mousemove', handleMouseMove);
- document.addEventListener('mouseup', handleMouseUp);
- }
-
- return () => {
- document.removeEventListener('mousemove', handleMouseMove);
- document.removeEventListener('mouseup', handleMouseUp);
- };
- }, [isDragging, dragStart]);
-
- // ๋๋ฏธ ํจ์๋ค (์ค์ ๊ธฐ๋ฅ์ ์์ง๋ง Node ์ปดํฌ๋ํธ์ ํ์)
- const dummyHandlers = {
- onNodeClick: () => {},
- onNodeDrag: () => {},
- onPortClick: () => {},
- onNodeDelete: () => {},
- onNodeDuplicate: () => {},
- updateNodeData: () => {}
- };
-
- return (
-
-
- {/* ๋ฐฐ๊ฒฝ ๊ทธ๋ฆฌ๋ */}
-
-
- {/* ์ฃ์ง ๋ ๋๋ง */}
-
- {edges.map(edge => {
- const sourceNode = adjustedNodes.find(n => n.id === edge.source.nodeId);
- const targetNode = adjustedNodes.find(n => n.id === edge.target.nodeId);
-
- if (!sourceNode || !targetNode) {
- devLog.warn('Missing node for edge:', edge.id);
- return null;
- }
-
- // ๋ฏธ๋ ์บ๋ฒ์ค ์ค์ผ์ผ์ ๋ง๋ ๊ฐ๋จํ ํฌํธ ์์น ๊ณ์ฐ
- const nodeWidth = 350 * 0.8; // 280px
- const nodeHeight = 120 * 0.8; // ๋๋ต์ ์ธ ๋
ธ๋ ๋์ด
-
- // SVG ์ขํ ์คํ์
์ ์ฉ
- const sourcePos = {
- x: sourceNode.position.x + nodeWidth + 500, // SVG ์คํ์
+ ๋
ธ๋ ์ค๋ฅธ์ชฝ ๋
- y: sourceNode.position.y + nodeHeight / 2 + 500 // SVG ์คํ์
+ ๋
ธ๋ ์ค์ ๋์ด
- };
- const targetPos = {
- x: targetNode.position.x + 500, // SVG ์คํ์
+ ๋
ธ๋ ์ผ์ชฝ ์์
- y: targetNode.position.y + nodeHeight / 2 + 500 // SVG ์คํ์
+ ๋
ธ๋ ์ค์ ๋์ด
- };
-
- return (
- {}}
- isSelected={false}
- />
- );
- })}
-
-
- {/* ๋
ธ๋ ๋ ๋๋ง */}
-
- {adjustedNodes.map(node => (
-
- ))}
-
-
-
- {/* ์ค ์ปจํธ๋กค */}
-
- setScale(prev => Math.min(2, prev + 0.1))}
- >
- +
-
- {Math.round(scale * 100)}%
- setScale(prev => Math.max(0.2, prev - 0.1))}
- >
- -
-
-
-
- );
-};
-
-export default MiniCanvas;
diff --git a/temporary/(canvas_js)/components/SideMenuPanel/TemplatePanel.jsx b/temporary/(canvas_js)/components/SideMenuPanel/TemplatePanel.jsx
deleted file mode 100644
index 688dbf6c..00000000
--- a/temporary/(canvas_js)/components/SideMenuPanel/TemplatePanel.jsx
+++ /dev/null
@@ -1,257 +0,0 @@
-"use client";
-import React, { useState, useEffect } from 'react';
-import toast from 'react-hot-toast';
-import styles from '@/app/canvas/assets/WorkflowPanel.module.scss';
-import sideMenuStyles from '@/app/canvas/assets/SideMenu.module.scss';
-import { LuArrowLeft, LuLayoutTemplate, LuPlay, LuCopy } from "react-icons/lu";
-import TemplatePreview from '@/app/canvas/components/SideMenuPanel/TemplatePreview';
-import { getWorkflowState } from '@/app/_common/components/workflowStorage';
-import { devLog } from '@/app/utils/logger';
-
-import Basic_Chatbot from '@/app/canvas/constants/workflow/Basic_Chatbot.json';
-import Data_Processing from '@/app/canvas/constants/workflow/Data_Processing.json';
-
-const templateList = [Basic_Chatbot, Data_Processing];
-
-const TemplatePanel = ({ onBack, onLoadWorkflow }) => {
- const [templates, setTemplates] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [previewTemplate, setPreviewTemplate] = useState(null);
-
- useEffect(() => {
- const loadTemplates = async () => {
- try {
- setIsLoading(true);
-
- devLog.log('templateList:', templateList);
-
- const formattedTemplates = templateList.map(template => ({
- id: template.id,
- name: template.name,
- description: template.description || 'No description available',
- tags: template.tags || [],
- nodes: template.contents?.nodes?.length || 0,
- data: template.contents
- }));
-
- setTemplates(formattedTemplates);
- setIsLoading(false);
- } catch (error) {
- devLog.error('Failed to load templates:', error);
- setTemplates([]);
- setIsLoading(false);
- }
- };
-
- loadTemplates();
- }, []);
-
- const handleUseTemplate = (template) => {
- const currentState = getWorkflowState();
- const hasCurrentWorkflow = currentState && (currentState.nodes?.length > 0 || currentState.edges?.length > 0);
-
- if (hasCurrentWorkflow) {
- const confirmToast = toast(
- (t) => (
-
-
- Use Template
-
-
- You have an existing workflow with unsaved changes.
-
- Using "{template.name} " template will replace your current work.
-
-
- {
- toast.dismiss(t.id);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#ffffff',
- border: '2px solid #6b7280',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- color: '#374151',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
- }}
- >
- Cancel
-
- {
- toast.dismiss(t.id);
- performUseTemplate(template);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#f59e0b',
- color: 'white',
- border: '2px solid #d97706',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
- }}
- >
- Use Template
-
-
-
- ),
- {
- duration: Infinity,
- style: {
- maxWidth: '420px',
- padding: '20px',
- backgroundColor: '#f9fafb',
- border: '2px solid #374151',
- borderRadius: '12px',
- boxShadow: '0 8px 25px rgba(0, 0, 0, 0.15), 0 4px 10px rgba(0, 0, 0, 0.1)',
- color: '#374151',
- fontFamily: 'system-ui, -apple-system, sans-serif'
- }
- }
- );
- } else {
- performUseTemplate(template);
- }
- };
-
- const performUseTemplate = (template) => {
- devLog.log('=== TemplatePanel performUseTemplate called ===');
- devLog.log('Template:', template);
- devLog.log('onLoadWorkflow exists:', !!onLoadWorkflow);
- devLog.log('Template data exists:', !!template?.data);
-
- if (onLoadWorkflow && template.data) {
- devLog.log('Calling onLoadWorkflow with:', template.data, template.name);
- onLoadWorkflow(template.data, template.name);
- devLog.log('onLoadWorkflow call completed');
- toast.success(`Template "${template.name}" loaded successfully!`);
- } else {
- devLog.error('Cannot call onLoadWorkflow:', {
- hasOnLoadWorkflow: !!onLoadWorkflow,
- hasTemplateData: !!template?.data
- });
- toast.error('Failed to load template');
- }
- };
-
- const handlePreviewTemplate = (template) => {
- devLog.log('Previewing template:', template);
- devLog.log('Setting previewTemplate state to:', template);
- setPreviewTemplate(template); // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ
ํ๋ฆฟ ์ค์
- };
-
- const handleClosePreview = () => {
- devLog.log('Closing preview');
- setPreviewTemplate(null); // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ซ๊ธฐ
- };
-
- if (isLoading) {
- return (
-
-
-
-
-
-
Templates
-
-
-
- Loading templates...
-
-
- );
- }
-
- devLog.log('TemplatePanel render - previewTemplate:', previewTemplate);
-
- return (
-
-
-
-
-
-
Templates
-
-
-
-
-
๐ Available Templates
- {templates.length}
-
-
-
- {templates.map(template => (
-
-
-
-
-
-
-
{template.name}
-
- {template.description && template.description.length > 20
- ? `${template.description.substring(0, 20)}...`
- : template.description
- }
-
-
-
- {template.tags && template.tags.slice(0, 2).map(tag => (
-
- {tag}
-
- ))}
- {template.tags && template.tags.length > 2 && (
-
- +{template.tags.length - 2}
-
- )}
-
-
{template.nodes} nodes
-
-
-
-
- handlePreviewTemplate(template)}
- title="Preview Template"
- >
-
-
- handleUseTemplate(template)}
- title="Use Template"
- >
-
-
-
-
- ))}
-
-
-
- {/* ํ
ํ๋ฆฟ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ์
*/}
- {previewTemplate && (
-
- )}
-
- );
-};
-
-export default TemplatePanel;
diff --git a/temporary/(canvas_js)/components/SideMenuPanel/TemplatePreview.jsx b/temporary/(canvas_js)/components/SideMenuPanel/TemplatePreview.jsx
deleted file mode 100644
index 22799ded..00000000
--- a/temporary/(canvas_js)/components/SideMenuPanel/TemplatePreview.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-"use client";
-import React, { useRef, useEffect } from 'react';
-import { createPortal } from 'react-dom';
-import toast from 'react-hot-toast';
-import styles from '@/app/canvas/assets/TemplatePreview.module.scss';
-import MiniCanvas from '@/app/canvas/components/SideMenuPanel/MiniCanvas';
-import { LuX, LuCopy } from "react-icons/lu";
-import { devLog } from '@/app/utils/logger';
-
-const TemplatePreview = ({ template, onClose, onUseTemplate }) => {
- const previewRef = useRef(null);
- const canvasRef = useRef(null);
-
- useEffect(() => {
- const handleEscapeKey = (event) => {
- if (event.key === 'Escape') {
- onClose();
- }
- };
-
- document.addEventListener('keydown', handleEscapeKey);
- return () => {
- document.removeEventListener('keydown', handleEscapeKey);
- };
- }, [onClose]);
-
- const handleUseTemplate = (template) => {
- devLog.log('=== TemplatePreview Use Template clicked ===');
- devLog.log('Template:', template);
- devLog.log('onUseTemplate function:', onUseTemplate);
-
- try {
- // TemplatePanel์ ํ์ธ ๋ก์ง์ ์ฌ์ฉํ๊ธฐ ์ํด ์ง์ ํธ์ถ
- onUseTemplate(template);
- devLog.log('onUseTemplate called successfully');
- onClose();
- devLog.log('onClose called successfully');
- } catch (error) {
- devLog.error('Error in Use Template:', error);
- toast.error('Failed to load template');
- }
- };
-
- if (!template) {
- return null;
- }
-
- const modalContent = (
-
{
- if (e.target === e.currentTarget) {
- onClose();
- }
- }}>
-
e.stopPropagation()}
- onMouseDown={(e) => e.stopPropagation()}
- >
-
-
-
{template.name}
-
- {template.tags && template.tags.map(tag => (
-
- {tag}
-
- ))}
-
-
-
- {
- devLog.log('=== TemplatePreview Use Template clicked ===');
- e.preventDefault();
- e.stopPropagation();
- handleUseTemplate(template);
- }}
- onMouseDown={(e) => {
- devLog.log('Use Template mousedown');
- e.stopPropagation();
- }}
- title="Use This Template"
- >
-
- Use Template
-
- {
- devLog.log('Close button clicked in TemplatePreview');
- e.preventDefault();
- e.stopPropagation();
- onClose();
- }}
- onMouseDown={(e) => {
- e.stopPropagation();
- }}
- title="Close Preview"
- >
-
-
-
-
-
-
-
-
-
-
-
-
{template.description}
-
-
- Nodes:
- {template.nodes}
-
-
-
-
-
-
- );
-
- return typeof document !== 'undefined' ? createPortal(modalContent, document.body) : null;
-};
-
-export default TemplatePreview;
diff --git a/temporary/(canvas_js)/components/SideMenuPanel/WorkflowPanel.jsx b/temporary/(canvas_js)/components/SideMenuPanel/WorkflowPanel.jsx
deleted file mode 100644
index 9a08a47a..00000000
--- a/temporary/(canvas_js)/components/SideMenuPanel/WorkflowPanel.jsx
+++ /dev/null
@@ -1,322 +0,0 @@
-"use client";
-import React, { useState, useEffect } from 'react';
-import toast from 'react-hot-toast';
-import styles from '@/app/canvas/assets/WorkflowPanel.module.scss';
-import sideMenuStyles from '@/app/canvas/assets/SideMenu.module.scss';
-import { LuArrowLeft, LuFolderOpen, LuDownload, LuRefreshCw, LuCalendar, LuTrash2 } from "react-icons/lu";
-import { listWorkflows, loadWorkflow, deleteWorkflow } from '@/app/api/workflowAPI';
-import { getWorkflowState } from '@/app/_common/components/workflowStorage';
-import { devLog } from '@/app/utils/logger';
-
-const WorkflowPanel = ({ onBack, onLoad, onExport, onLoadWorkflow }) => {
- const [workflows, setWorkflows] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
-
- const fetchWorkflows = async () => {
- setIsLoading(true);
- setError(null);
- try {
- const workflowList = await listWorkflows();
- setWorkflows(workflowList);
- } catch (err) {
- setError(err.message);
- } finally {
- setIsLoading(false);
- }
- };
-
- useEffect(() => {
- fetchWorkflows();
- }, []);
-
- const handleRefresh = () => {
- fetchWorkflows();
- toast.success('์ํฌํ๋ก์ฐ ์๋ก๊ณ ์นจ ์๋ฃ!');
- };
-
- const handleLoadWorkflow = async (filename) => {
- const currentState = getWorkflowState();
- const hasCurrentWorkflow = currentState && (currentState.nodes?.length > 0 || currentState.edges?.length > 0);
-
- if (hasCurrentWorkflow) {
- const workflowName = getWorkflowDisplayName(filename);
-
- const confirmToast = toast(
- (t) => (
-
-
- Load Workflow
-
-
- You have an existing workflow with unsaved changes.
-
- Loading "{workflowName} " will replace your current work.
-
-
- {
- toast.dismiss(t.id);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#ffffff',
- border: '2px solid #6b7280',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- color: '#374151',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
- }}
- >
- Cancel
-
- {
- toast.dismiss(t.id);
- await performLoadWorkflow(filename);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#f59e0b',
- color: 'white',
- border: '2px solid #d97706',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
- }}
- >
- Load Anyway
-
-
-
- ),
- {
- duration: Infinity,
- style: {
- maxWidth: '420px',
- padding: '20px',
- backgroundColor: '#f9fafb',
- border: '2px solid #374151',
- borderRadius: '12px',
- boxShadow: '0 8px 25px rgba(0, 0, 0, 0.15), 0 4px 10px rgba(0, 0, 0, 0.1)',
- color: '#374151',
- fontFamily: 'system-ui, -apple-system, sans-serif'
- }
- }
- );
- } else {
- await performLoadWorkflow(filename);
- }
- };
-
- const performLoadWorkflow = async (filename) => {
- try {
- const workflowId = filename.replace('.json', '');
- const workflowData = await loadWorkflow(workflowId);
-
- if (onLoadWorkflow) {
- // ์ํฌํ๋ก์ฐ ๋ฐ์ดํฐ์ ํจ๊ป ์ํฌํ๋ก์ฐ ์ด๋ฆ๋ ์ ๋ฌ
- onLoadWorkflow(workflowData, workflowId);
- }
- } catch (error) {
- devLog.error("Failed to load workflow:", error);
- toast.error(`Failed to load workflow: ${error.message}`);
- }
- };
-
- const handleDeleteWorkflow = async (filename) => {
- const workflowName = getWorkflowDisplayName(filename);
-
- const confirmToast = toast(
- (t) => (
-
-
- Delete Workflow
-
-
- Are you sure you want to delete "{workflowName} "?
-
- This action cannot be undone.
-
-
- {
- toast.dismiss(t.id);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#ffffff',
- border: '2px solid #6b7280',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- color: '#374151',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
- }}
- >
- Cancel
-
- {
- toast.dismiss(t.id);
- await performDelete(filename, workflowName);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#dc3545',
- color: 'white',
- border: '2px solid #b02a37',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
- }}
- >
- Delete
-
-
-
- ),
- {
- duration: Infinity,
- style: {
- maxWidth: '420px',
- padding: '20px',
- backgroundColor: '#f9fafb',
- border: '2px solid #374151',
- borderRadius: '12px',
- boxShadow: '0 8px 25px rgba(0, 0, 0, 0.15), 0 4px 10px rgba(0, 0, 0, 0.1)',
- color: '#374151',
- fontFamily: 'system-ui, -apple-system, sans-serif'
- }
- }
- );
- };
-
- const performDelete = async (filename, workflowName) => {
- const toastId = toast.loading(`Deleting "${workflowName}"...`);
-
- try {
- const workflowId = filename.replace('.json', '');
- await deleteWorkflow(workflowId);
-
- await fetchWorkflows();
-
- toast.success(`Workflow "${workflowName}" deleted successfully!`, { id: toastId });
- } catch (error) {
- devLog.error("Failed to delete workflow:", error);
- toast.error(`Failed to delete workflow: ${error.message}`, { id: toastId });
- }
- };
-
- const getWorkflowDisplayName = (filename) => {
- return filename.replace('.json', '');
- };
-
- const getFileSize = (filename) => {
- return "Unknown";
- };
-
- return (
-
-
-
-
-
-
Workflow
-
-
-
-
-
-
-
-
- Load from Local
-
-
-
- Export to Local
-
-
-
-
-
-
๐ Saved Workflows
- {workflows.length}
-
-
- {isLoading && (
-
-
- Loading workflows...
-
- )}
-
- {error && (
-
- Error: {error}
-
- Try Again
-
-
- )}
-
- {!isLoading && !error && workflows.length === 0 && (
-
-
-
No workflows found
-
Save a workflow to see it here
-
- )}
-
- {!isLoading && !error && workflows.length > 0 && (
-
- {workflows.map((filename, index) => (
-
-
-
- {getWorkflowDisplayName(filename)}
-
-
-
- handleLoadWorkflow(filename)}
- >
- Load
-
- handleDeleteWorkflow(filename)}
- >
-
-
-
-
- ))}
-
- )}
-
-
- );
-};
-
-export default WorkflowPanel;
diff --git a/temporary/(canvas_js)/constants/nodes.js b/temporary/(canvas_js)/constants/nodes.js
deleted file mode 100644
index e5e9947f..00000000
--- a/temporary/(canvas_js)/constants/nodes.js
+++ /dev/null
@@ -1,160 +0,0 @@
-export const NODE_DATA = [
- {
- categoryId: 'langchain',
- categoryName: 'LangChain',
- icon: 'LuBrainCircuit',
- functions: [
- {
- functionId: 'chat_models',
- functionName: 'Chat Models',
- nodes: [
- {
- id: 'chat-openai',
- nodeName: 'ChatOpenAI',
- inputs: [
- {
- id: 'in-msg',
- name: 'Messages',
- multi: true,
- type: 'STR',
- },
- {
- id: 'in-stop',
- name: 'Stop Sequence',
- multi: false,
- type: 'STR',
- },
- ],
- parameters: [
- {
- id: 'p-model',
- name: 'Model',
- value: 'gpt-4o',
- optional: false,
- options: [
- { value: 'gpt-4o', label: 'GPT-4o' },
- { value: 'gpt-4', label: 'GPT-4' },
- {
- value: 'gpt-3.5-turbo',
- label: 'GPT-3.5 Turbo',
- },
- ],
- },
- {
- id: 'p-temp',
- name: 'Temperature',
- value: 0.7,
- step: 0.1,
- optional: false,
- },
- {
- id: 'p-max-tokens',
- name: 'Max Tokens',
- value: 1000,
- optional: true,
- },
- {
- id: 'p-top-p',
- name: 'Top P',
- value: 1.0,
- step: 0.1,
- min: 0,
- max: 1,
- optional: true,
- },
- {
- id: 'p-frequency-penalty',
- name: 'Frequency Penalty',
- value: 0,
- step: 0.1,
- min: -2,
- max: 2,
- optional: true,
- },
- {
- id: 'p-presence-penalty',
- name: 'Presence Penalty',
- value: 0,
- step: 0.1,
- min: -2,
- max: 2,
- optional: true,
- },
- ],
- outputs: [
- {
- id: 'out-1',
- name: 'Output1',
- multi: false,
- type: 'STR',
- },
- {
- id: 'out-2',
- name: 'Output2',
- multi: false,
- type: 'STR',
- },
- {
- id: 'out-3',
- name: 'Output3',
- multi: false,
- type: 'STR',
- },
- {
- id: 'out-4',
- name: 'Output4',
- multi: false,
- type: 'STR',
- },
- ],
- },
- ],
- },
- ],
- },
- {
- categoryId: 'utilities',
- categoryName: 'Utilities',
- icon: 'LuWrench',
- functions: [
- {
- functionId: 'util_types',
- functionName: 'Type Generators',
- nodes: [
- {
- id: 'util-gen-str',
- nodeName: 'Generate String',
- parameters: [
- {
- id: 'p-text',
- name: 'Text',
- value: 'Hello World',
- optional: false,
- },
- {
- id: 'p-length',
- name: 'Max Length',
- value: 100,
- optional: true,
- },
- {
- id: 'p-uppercase',
- name: 'Uppercase',
- value: false,
- optional: true,
- options: [
- { value: false, label: 'False' },
- { value: true, label: 'True' },
- ],
- },
- ],
- outputs: [
- { id: 'out-str', name: 'String', type: 'STR' },
- ],
- },
- // ... ๋ค๋ฅธ ๋
ธ๋๋ค
- ],
- },
- ],
- },
-];
diff --git a/temporary/(canvas_js)/constants/workflow/Basic_Chatbot.json b/temporary/(canvas_js)/constants/workflow/Basic_Chatbot.json
deleted file mode 100644
index 5178001d..00000000
--- a/temporary/(canvas_js)/constants/workflow/Basic_Chatbot.json
+++ /dev/null
@@ -1,147 +0,0 @@
-{
- "id": "Basic_Chatbot",
- "name": "Basic Chatbot",
- "description": "OpenAI GPT๋ฅผ ์ฌ์ฉํ ๊ธฐ๋ณธ์ ์ธ ์ฑ๋ด ์ํฌํ๋ก์ฐ์
๋๋ค. ์ฌ์ฉ์์ ์
๋ ฅ์ ๋ฐ์ GPT ๋ชจ๋ธ๋ก ์ฒ๋ฆฌํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํฉ๋๋ค.",
- "tags": ["AI", "Chat", "OpenAI", "GPT", "Basic"],
- "contents": {
- "view": {
- "x": -8739,
- "y": -4067.5,
- "scale": 1
- },
- "nodes": [
- {
- "id": "chat/openai-1752035468492",
- "data": {
- "functionId": "chat_models",
- "id": "chat/openai",
- "nodeName": "Chat OpenAI",
- "inputs": [
- {
- "id": "text",
- "name": "Text",
- "type": "STR",
- "multi": false,
- "required": true
- }
- ],
- "outputs": [
- {
- "id": "result",
- "name": "Result",
- "type": "STR"
- }
- ],
- "parameters": [
- {
- "id": "model",
- "name": "Model",
- "type": "STR",
- "value": "gpt-4o",
- "required": true,
- "options": [
- {
- "value": "gpt-3.5-turbo",
- "label": "GPT-3.5 Turbo"
- },
- {
- "value": "gpt-4",
- "label": "GPT-4"
- },
- {
- "value": "gpt-4o",
- "label": "GPT-4o"
- }
- ]
- }
- ]
- },
- "position": {
- "x": 9438,
- "y": 4446.5
- }
- },
- {
- "id": "tools/print_any-1752035471107",
- "data": {
- "functionId": "endnode",
- "id": "tools/print_any",
- "nodeName": "Print Any",
- "inputs": [
- {
- "id": "input_print",
- "name": "Print",
- "type": "ANY",
- "multi": false,
- "required": true
- }
- ],
- "outputs": [],
- "parameters": []
- },
- "position": {
- "x": 9916,
- "y": 4446.5
- }
- },
- {
- "id": "math/input_str-1752035472920",
- "data": {
- "functionId": "tools",
- "id": "math/input_str",
- "nodeName": "Input String",
- "inputs": [],
- "outputs": [
- {
- "id": "result",
- "name": "Result",
- "type": "STR"
- }
- ],
- "parameters": [
- {
- "id": "input_str",
- "name": "String",
- "type": "STR",
- "value": "์๋
ํ์ธ์?"
- }
- ]
- },
- "position": {
- "x": 8872,
- "y": 4390.5
- }
- }
- ],
- "edges": [
- {
- "id": "edge-math/input_str-1752035472920:result-chat/openai-1752035468492:text-1752035474177",
- "source": {
- "nodeId": "math/input_str-1752035472920",
- "portId": "result",
- "portType": "output",
- "type": "STR"
- },
- "target": {
- "nodeId": "chat/openai-1752035468492",
- "portId": "text",
- "portType": "input"
- }
- },
- {
- "id": "edge-chat/openai-1752035468492:result-tools/print_any-1752035471107:input_print-1752035475062",
- "source": {
- "nodeId": "chat/openai-1752035468492",
- "portId": "result",
- "portType": "output",
- "type": "STR"
- },
- "target": {
- "nodeId": "tools/print_any-1752035471107",
- "portId": "input_print",
- "portType": "input"
- }
- }
- ]
- }
-}
\ No newline at end of file
diff --git a/temporary/(canvas_js)/constants/workflow/Data_Processing.json b/temporary/(canvas_js)/constants/workflow/Data_Processing.json
deleted file mode 100644
index ed7373ab..00000000
--- a/temporary/(canvas_js)/constants/workflow/Data_Processing.json
+++ /dev/null
@@ -1,225 +0,0 @@
-{
- "id": "Data_Processing",
- "name": "Data Processing",
- "description": "๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์ํ ๊ธฐ๋ณธ ์ํฌํ๋ก์ฐ์
๋๋ค. ์ซ์ ์
๋ ฅ์ ๋ฐ์ ์ํ์ ๊ณ์ฐ์ ์ํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํฉ๋๋ค.",
- "tags": [
- "Math",
- "Data",
- "Processing",
- "Calculation",
- "Basic"
- ],
- "contents": {
- "view": {
- "x": -4928.91640650537,
- "y": -2300.4784856565625,
- "scale": 0.6
- },
- "nodes": [
- {
- "id": "math/add_integers-1752122682772",
- "data": {
- "functionId": "arithmetic",
- "id": "math/add_integers",
- "nodeName": "Add Integers",
- "description": "๋ ๊ฐ์ ์ ์๋ฅผ ์
๋ ฅ๋ฐ์ ๋ํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ธ ์ํ ์ฐ์ฐ ๋
ธ๋์
๋๋ค.",
- "tags": [
- "math",
- "arithmetic",
- "addition",
- "integer",
- "calculation",
- "basic_operation"
- ],
- "inputs": [
- {
- "id": "a",
- "name": "A",
- "type": "INT",
- "multi": false,
- "required": true
- },
- {
- "id": "b",
- "name": "B",
- "type": "INT",
- "multi": false,
- "required": true
- }
- ],
- "outputs": [
- {
- "id": "result",
- "name": "Result",
- "type": "INT"
- }
- ],
- "parameters": []
- },
- "position": {
- "x": 9559,
- "y": 4385.5
- }
- },
- {
- "id": "tools/print_any-1752122685976",
- "data": {
- "functionId": "endnode",
- "id": "tools/print_any",
- "nodeName": "Print Any",
- "description": "์์์ ํ์
์ ๋ฐ์ดํฐ๋ฅผ ์
๋ ฅ๋ฐ์ ๊ทธ๋๋ก ๋ฐํํ๋ ์ถ๋ ฅ ๋
ธ๋์
๋๋ค. ์ํฌํ๋ก์ฐ์ ์ต์ข
๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.",
- "tags": [
- "output",
- "print",
- "display",
- "debug",
- "end_node",
- "utility",
- "any_type"
- ],
- "inputs": [
- {
- "id": "input_print",
- "name": "Print",
- "type": "ANY",
- "multi": false,
- "required": true
- }
- ],
- "outputs": [],
- "parameters": []
- },
- "position": {
- "x": 10326,
- "y": 4406.5
- }
- },
- {
- "id": "math/input_int-1752122688103",
- "data": {
- "functionId": "tools",
- "id": "math/input_int",
- "nodeName": "Input Integer",
- "description": "์ฌ์ฉ์๊ฐ ์ค์ ํ ์ ์ ๊ฐ์ ์ถ๋ ฅํ๋ ์
๋ ฅ ๋
ธ๋์
๋๋ค. ์ํฌํ๋ก์ฐ์์ ์ซ์ ๋ฐ์ดํฐ์ ์์์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.",
- "tags": [
- "input",
- "integer",
- "number",
- "parameter",
- "source",
- "start_node",
- "user_input"
- ],
- "inputs": [],
- "outputs": [
- {
- "id": "result",
- "name": "Result",
- "type": "INT"
- }
- ],
- "parameters": [
- {
- "id": "input_int",
- "name": "Integer",
- "type": "INT",
- "value": 100,
- "step": 1,
- "min": -2147483648,
- "max": 2147483647
- }
- ]
- },
- "position": {
- "x": 8741.666666666666,
- "y": 4157.5
- }
- },
- {
- "id": "math/input_int-1752122689356",
- "data": {
- "functionId": "tools",
- "id": "math/input_int",
- "nodeName": "Input Integer",
- "description": "์ฌ์ฉ์๊ฐ ์ค์ ํ ์ ์ ๊ฐ์ ์ถ๋ ฅํ๋ ์
๋ ฅ ๋
ธ๋์
๋๋ค. ์ํฌํ๋ก์ฐ์์ ์ซ์ ๋ฐ์ดํฐ์ ์์์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.",
- "tags": [
- "input",
- "integer",
- "number",
- "parameter",
- "source",
- "start_node",
- "user_input"
- ],
- "inputs": [],
- "outputs": [
- {
- "id": "result",
- "name": "Result",
- "type": "INT"
- }
- ],
- "parameters": [
- {
- "id": "input_int",
- "name": "Integer",
- "type": "INT",
- "value": 200,
- "step": 1,
- "min": -2147483648,
- "max": 2147483647
- }
- ]
- },
- "position": {
- "x": 8757.333333333332,
- "y": 4628.166666666666
- }
- }
- ],
- "edges": [
- {
- "id": "edge-math/input_int-1752122688103:result-math/add_integers-1752122682772:a-1752122694564",
- "source": {
- "nodeId": "math/input_int-1752122688103",
- "portId": "result",
- "portType": "output",
- "type": "INT"
- },
- "target": {
- "nodeId": "math/add_integers-1752122682772",
- "portId": "a",
- "portType": "input"
- }
- },
- {
- "id": "edge-math/input_int-1752122689356:result-math/add_integers-1752122682772:b-1752122695803",
- "source": {
- "nodeId": "math/input_int-1752122689356",
- "portId": "result",
- "portType": "output",
- "type": "INT"
- },
- "target": {
- "nodeId": "math/add_integers-1752122682772",
- "portId": "b",
- "portType": "input"
- }
- },
- {
- "id": "edge-math/add_integers-1752122682772:result-tools/print_any-1752122685976:input_print-1752122701660",
- "source": {
- "nodeId": "math/add_integers-1752122682772",
- "portId": "result",
- "portType": "output",
- "type": "INT"
- },
- "target": {
- "nodeId": "tools/print_any-1752122685976",
- "portId": "input_print",
- "portType": "input"
- }
- }
- ]
- }
-}
\ No newline at end of file
diff --git a/temporary/(canvas_js)/nodeHook.js b/temporary/(canvas_js)/nodeHook.js
deleted file mode 100644
index 7ea754ce..00000000
--- a/temporary/(canvas_js)/nodeHook.js
+++ /dev/null
@@ -1,59 +0,0 @@
-'use client';
-
-import { useState, useEffect, useCallback } from 'react';
-import {
- getNodes as apiGetNodes,
- exportNodes as apiExportNodes,
-} from '@/app/api/nodeAPI';
-import { toast } from 'react-hot-toast';
-
-/**
- * ๋
ธ๋ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ Custom Hook์
๋๋ค.
- * @returns {{
- * nodes: Array
,
- * isLoading: boolean,
- * error: string|null,
- * refreshNodes: () => Promise,
- * exportAndRefreshNodes: () => Promise
- * }}
- */
-export const useNodes = () => {
- const [nodes, setNodes] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
-
- const refreshNodes = useCallback(async () => {
- setIsLoading(true);
- setError(null);
- try {
- const data = await apiGetNodes();
- setNodes(data);
- } catch (err) {
- setError(err.message);
- toast.error(err.message || '๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค.');
- } finally {
- setIsLoading(false);
- }
- }, []);
-
- const exportAndRefreshNodes = useCallback(async () => {
- setIsLoading(true);
- setError(null);
- try {
- const data = await apiExportNodes();
- setNodes(data);
- toast.success('๋
ธ๋ ๋ชฉ๋ก ์๋ก๊ณ ์นจ ์๋ฃ!');
- } catch (err) {
- setError(err.message);
- toast.error(err.message || '์๋ก๊ณ ์นจ์ ์คํจํ์ต๋๋ค.');
- } finally {
- setIsLoading(false);
- }
- }, []);
-
- useEffect(() => {
- refreshNodes();
- }, [refreshNodes]);
-
- return { nodes, isLoading, error, refreshNodes, exportAndRefreshNodes };
-};
diff --git a/temporary/(canvas_js)/page.tsx b/temporary/(canvas_js)/page.tsx
deleted file mode 100644
index 6ad485f0..00000000
--- a/temporary/(canvas_js)/page.tsx
+++ /dev/null
@@ -1,754 +0,0 @@
-'use client';
-import { useState, useEffect, useRef } from 'react';
-import toast from 'react-hot-toast';
-import Canvas from '@/app/canvas/components/Canvas';
-import Header from '@/app/canvas/components/Header';
-import SideMenu from '@/app/canvas/components/SideMenu';
-import ExecutionPanel from '@/app/canvas/components/ExecutionPanel';
-import styles from '@/app/canvas/assets/PlateeRAG.module.scss';
-import {
- executeWorkflow,
- saveWorkflow,
- listWorkflows,
-} from '@/app/api/workflowAPI';
-import {
- getWorkflowName,
- getWorkflowState,
- saveWorkflowState,
- ensureValidWorkflowState,
- saveWorkflowName,
- startNewWorkflow,
-} from '@/app/_common/components/workflowStorage';
-import { devLog } from '@/app/utils/logger';
-import { generateWorkflowHash } from '@/app/utils/generateSha1Hash';
-
-export default function CanvasPage() {
- const [isMenuOpen, setIsMenuOpen] = useState(false);
- const menuRef = useRef(null);
- const canvasRef = useRef(null);
- const fileInputRef = useRef(null);
- const [hasError, setHasError] = useState(false);
-
- // Error boundary๋ฅผ ์ํ useEffect
- useEffect(() => {
- const handleError = (error: any) => {
- devLog.error('Global error caught:', error);
- setHasError(true);
- };
-
- window.addEventListener('error', handleError);
- window.addEventListener('unhandledrejection', handleError);
-
- return () => {
- window.removeEventListener('error', handleError);
- window.removeEventListener('unhandledrejection', handleError);
- };
- }, []);
-
- if (hasError) {
- return (
-
-
Something went wrong!
- setHasError(false)}>Reset
-
- );
- }
-
- const [executionOutput, setExecutionOutput] = useState(null);
- const [isExecuting, setIsExecuting] = useState(false);
- const [currentWorkflowName, setCurrentWorkflowName] = useState('Workflow');
-
- // ์ปดํฌ๋ํธ ๋ง์ดํธ ์ ์ํฌํ๋ก์ฐ ์ด๋ฆ๊ณผ ์ํ ๋ณต์
- useEffect(() => {
- devLog.log('=== Page useEffect: Restoring workflow state ===');
-
- // ์ ์ฅ๋ ์ํฌํ๋ก์ฐ ์ด๋ฆ ๋ณต์
- const savedName = getWorkflowName();
- devLog.log('Restored workflow name:', savedName);
- setCurrentWorkflowName(savedName);
- }, []);
-
- // Canvas๊ฐ ๋ง์ดํธ๋ ํ ์ํ ๋ณต์์ ์ํ ๋ณ๋ useEffect
- useEffect(() => {
- const restoreWorkflowState = () => {
- devLog.log(
- 'restoreWorkflowState called, canvasRef.current:',
- !!canvasRef.current,
- );
- const savedState = getWorkflowState();
-
- if (savedState && canvasRef.current) {
- try {
- const validState = ensureValidWorkflowState(savedState);
- if (validState) {
- devLog.log(
- 'Loading workflow state to Canvas:',
- validState,
- );
- (canvasRef.current as any).loadWorkflowState(
- validState,
- );
- devLog.log(
- 'Workflow state restored from localStorage successfully',
- );
- } else {
- devLog.log('No valid state to restore');
- }
- } catch (error) {
- devLog.warn('Failed to restore workflow state:', error);
- }
- } else {
- devLog.log('No saved state found or Canvas not ready:', {
- hasSavedState: !!savedState,
- hasCanvasRef: !!canvasRef.current,
- });
-
- // Canvas๊ฐ ์์ง ์ค๋น๋์ง ์์๋ค๋ฉด ๋ค์ ์๋
- if (!canvasRef.current && savedState) {
- devLog.log('Canvas not ready, retrying in 200ms...');
- setTimeout(restoreWorkflowState, 200);
- }
- }
- };
-
- // ์ต์ด ์๋
- const timer = setTimeout(restoreWorkflowState, 100);
- return () => clearTimeout(timer);
- }, [canvasRef.current]); // canvasRef.current ๋ณ๊ฒฝ ์ ์ฌ์คํ
-
- // ์ํฌํ๋ก์ฐ ์ํ ๋ณ๊ฒฝ ์ ์๋ ์ ์ฅ
- const handleCanvasStateChange = (state: any) => {
- devLog.log('handleCanvasStateChange called with:', {
- hasState: !!state,
- nodesCount: state?.nodes?.length || 0,
- edgesCount: state?.edges?.length || 0,
- view: state?.view,
- });
-
- try {
- // ์ํ๊ฐ ์๊ณ ๋น์ด์์ง ์์ผ๋ฉด ์ ์ฅ (๋น ์ํ๋ก ๋ฎ์ด์ฐ๊ธฐ ๋ฐฉ์ง)
- if (state && (state.nodes?.length > 0 || state.edges?.length > 0)) {
- devLog.log('Saving non-empty state to localStorage');
- saveWorkflowState(state);
- devLog.log('Workflow state saved to localStorage');
- } else {
- devLog.log(
- 'Skipping save of empty state to preserve existing localStorage data',
- );
- }
- } catch (error) {
- devLog.warn('Failed to auto-save workflow state:', error);
- }
- };
-
- // ์ํฌํ๋ก์ฐ ์ด๋ฆ ์
๋ฐ์ดํธ ํฌํผ ํจ์
- const updateWorkflowName = (newName: string) => {
- setCurrentWorkflowName(newName);
- saveWorkflowName(newName);
- };
-
- // Header์์ ์ํฌํ๋ก์ฐ ์ด๋ฆ ์ง์ ํธ์ง ์ ํธ์ถ๋ ํธ๋ค๋ฌ
- const handleWorkflowNameChange = (newName: string) => {
- setCurrentWorkflowName(newName);
- // localStorage ์ ์ฅ์ Header์์ ์ด๋ฏธ ์ฒ๋ฆฌํ๋ฏ๋ก ์ค๋ณต ์ ์ฅ ๋ฐฉ์ง
- };
-
- // ์๋ก์ด ์ํฌํ๋ก์ฐ ์์ ํธ๋ค๋ฌ
- const handleNewWorkflow = () => {
- // ํ์ฌ ์์
์ด ์๋์ง ํ์ธ
- const hasCurrentWork =
- canvasRef.current &&
- ((canvasRef.current as any).getCanvasState?.()?.nodes?.length > 0 ||
- (canvasRef.current as any).getCanvasState?.()?.edges?.length >
- 0);
-
- if (hasCurrentWork) {
- // ํ์ธ ํ ์คํธ ํ์
- const confirmToast = toast(
- (t) => (
-
-
- Start New Workflow?
-
-
- This will clear all current nodes and edges.
-
- Make sure to save your current work if needed.
-
-
- {
- toast.dismiss(t.id);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#ffffff',
- border: '2px solid #6b7280',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- color: '#374151',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
- }}
- onMouseOver={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#f9fafb';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#4b5563';
- }}
- onMouseOut={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#ffffff';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#6b7280';
- }}
- >
- Cancel
-
- {
- toast.dismiss(t.id);
- performNewWorkflow();
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#dc2626',
- color: 'white',
- border: '2px solid #b91c1c',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
- }}
- onMouseOver={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#b91c1c';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#991b1b';
- }}
- onMouseOut={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#dc2626';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#b91c1c';
- }}
- >
- Start New
-
-
-
- ),
- {
- duration: Infinity,
- style: {
- maxWidth: '420px',
- padding: '20px',
- backgroundColor: '#f9fafb',
- border: '2px solid #374151',
- borderRadius: '12px',
- boxShadow:
- '0 8px 25px rgba(0, 0, 0, 0.15), 0 4px 10px rgba(0, 0, 0, 0.1)',
- color: '#374151',
- fontFamily: 'system-ui, -apple-system, sans-serif',
- },
- },
- );
- } else {
- // ์์
์ด ์์ผ๋ฉด ๋ฐ๋ก ์์
- performNewWorkflow();
- }
- };
-
- // ์ค์ ์๋ก์ด ์ํฌํ๋ก์ฐ ์์ ๋ก์ง
- const performNewWorkflow = () => {
- try {
- devLog.log('Starting new workflow...');
-
- // localStorage ๋ฐ์ดํฐ ์ด๊ธฐํ
- startNewWorkflow();
-
- // Canvas ์ํ ์ด๊ธฐํ (๊ธฐ์กด Canvas ์ด๊ธฐํ ๋ก์ง๊ณผ ๋์ผํ๊ฒ)
- if (canvasRef.current) {
- const canvas = canvasRef.current;
- const centeredView = (canvas as any).getCenteredView();
-
- // ๋จผ์ ๊ธฐ๋ณธ ์ํ๋ก ์ด๊ธฐํ
- const initialState = {
- nodes: [],
- edges: [],
- view: centeredView,
- };
- (canvas as any).loadWorkflowState(initialState);
- devLog.log('Canvas state reset to initial values');
- }
-
- // ํ์ฌ ์ํฌํ๋ก์ฐ ์ด๋ฆ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ค์
- setCurrentWorkflowName('Workflow');
-
- devLog.log('New workflow started successfully');
- toast.success('New workflow started');
- } catch (error: any) {
- devLog.error('Failed to start new workflow:', error);
- toast.error(`Failed to start new workflow: ${error.message}`);
- }
- };
-
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- // TemplatePreview ์ค๋ฒ๋ ์ด ํด๋ฆญ์ธ์ง ํ์ธ (CSS ํด๋์ค๋ก)
- const target = event.target as HTMLElement;
- if (target.closest('[data-template-preview]')) {
- return; // TemplatePreview ๋ด๋ถ ํด๋ฆญ ์ ๋ฉ๋ด ๋ซ์ง ์์
- }
-
- if (
- menuRef.current &&
- !(menuRef.current as any).contains(event.target)
- ) {
- setIsMenuOpen(false);
- }
- };
-
- if (isMenuOpen) {
- document.addEventListener('mousedown', handleClickOutside);
- }
-
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, [isMenuOpen]);
-
- const handleSave = async () => {
- if (!canvasRef.current) {
- toast.error('Canvas is not ready.');
- return;
- }
-
- let canvasState = (canvasRef.current as any).getCanvasState();
- const workflowName = getWorkflowName();
-
- const workflowId = `workflow_${generateWorkflowHash(canvasState)}`;
- canvasState = { ...canvasState, workflow_id: workflowId };
- canvasState = { ...canvasState, workflow_name: workflowName };
-
- devLog.log('Canvas state before save:', canvasState);
- devLog.log('Workflow ID set:', workflowId);
- devLog.log('Canvas state id field:', canvasState.id);
-
- if (!canvasState.nodes || canvasState.nodes.length === 0) {
- toast.error('Cannot save an empty workflow. Please add nodes.');
- return;
- }
-
- try {
- // ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ค๋ณต ํ์ธ (๋ก๋ฉ ๋ฉ์์ง ์์ด)
- const existingWorkflows = await listWorkflows();
- const targetFilename = `${workflowName}.json`;
- const isDuplicate = existingWorkflows.includes(targetFilename);
-
- if (isDuplicate) {
- // ์ค๋ณต ๋ฐ๊ฒฌ ์ ์ฌ์ฉ์์๊ฒ ํ์ธ ์์ฒญ
- const confirmToast = toast(
- (t) => (
-
-
- Workflow Already Exists
-
-
- A workflow named "
- {workflowName} " already exists.
-
- Do you want to overwrite it?
-
-
- {
- toast.dismiss(t.id);
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#ffffff',
- border: '2px solid #6b7280',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- color: '#374151',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
- }}
- onMouseOver={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#f9fafb';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#4b5563';
- }}
- onMouseOut={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#ffffff';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#6b7280';
- }}
- >
- Cancel
-
- {
- toast.dismiss(t.id);
- await performSave(
- workflowName,
- canvasState,
- );
- }}
- style={{
- padding: '8px 16px',
- backgroundColor: '#f59e0b',
- color: 'white',
- border: '2px solid #d97706',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '0.85rem',
- fontWeight: '500',
- transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
- }}
- onMouseOver={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#d97706';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#b45309';
- }}
- onMouseOut={(e) => {
- (
- e.target as HTMLButtonElement
- ).style.backgroundColor = '#f59e0b';
- (
- e.target as HTMLButtonElement
- ).style.borderColor = '#d97706';
- }}
- >
- Overwrite
-
-
-
- ),
- {
- duration: Infinity,
- style: {
- maxWidth: '420px',
- padding: '20px',
- backgroundColor: '#f9fafb',
- border: '2px solid #374151',
- borderRadius: '12px',
- boxShadow:
- '0 8px 25px rgba(0, 0, 0, 0.15), 0 4px 10px rgba(0, 0, 0, 0.1)',
- color: '#374151',
- fontFamily: 'system-ui, -apple-system, sans-serif',
- },
- },
- );
- } else {
- // ์ค๋ณต์ด ์์ผ๋ฉด ๋ฐ๋ก ์ ์ฅ
- await performSave(workflowName, canvasState);
- }
- } catch (error: any) {
- devLog.error('Error checking existing workflows:', error);
- // ์ค๋ณต ํ์ธ ์คํจ ์์๋ ์ ์ฅ ์๋ (graceful fallback)
- toast.error(
- `Warning: Could not check for duplicates. Proceeding with save...`,
- );
- setTimeout(async () => {
- await performSave(workflowName, canvasState);
- }, 1000);
- }
- };
-
- const performSave = async (workflowName: string, canvasState: any) => {
- const toastId = toast.loading('Saving workflow...');
-
- try {
- const result = await saveWorkflow(workflowName, canvasState);
- toast.success(`Workflow '${workflowName}' saved successfully!`, {
- id: toastId,
- });
- } catch (error: any) {
- devLog.error('Save failed:', error);
- toast.error(`Save failed: ${error.message}`, { id: toastId });
- }
- };
-
- const handleExport = () => {
- if (canvasRef.current) {
- const canvasState = (canvasRef.current as any).getCanvasState();
- const workflowName = getWorkflowName();
- const jsonString = JSON.stringify(canvasState, null, 2);
- const blob = new Blob([jsonString], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `${workflowName}.json`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- }
- };
-
- const handleLoadClick = () => {
- fileInputRef.current?.click();
- };
-
- const handleLoadWorkflow = async (
- workflowData: any,
- workflowName?: string,
- ) => {
- try {
- if (canvasRef.current) {
- const validState = ensureValidWorkflowState(workflowData);
- (canvasRef.current as any).loadCanvasState(validState);
- // ์๋ก์ด ์ํฌํ๋ก์ฐ ๋ก๋ ์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ํ ์
๋ฐ์ดํธ
- saveWorkflowState(validState);
-
- // ์ํฌํ๋ก์ฐ ์ด๋ฆ์ด ์ ๊ณต๋ ๊ฒฝ์ฐ ์
๋ฐ์ดํธ
- if (workflowName) {
- updateWorkflowName(workflowName);
- }
-
- toast.success('Workflow loaded successfully!');
- }
- } catch (error: any) {
- devLog.error('Error loading workflow:', error);
- toast.error(`Failed to load workflow: ${error.message}`);
- }
- };
-
- const handleFileChange = (e: React.ChangeEvent) => {
- const file = e.target.files?.[0];
- if (!file) return;
-
- const reader = new FileReader();
- reader.onload = (event) => {
- try {
- const json = event.target?.result as string;
- const savedState = JSON.parse(json);
- if (canvasRef.current) {
- const validState = ensureValidWorkflowState(savedState);
- (canvasRef.current as any).loadCanvasState(validState);
- // ํ์ผ์์ ๋ก๋ ์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ํ ์
๋ฐ์ดํธ
- saveWorkflowState(validState);
-
- // ํ์ผ๋ช
์์ ์ํฌํ๋ก์ฐ ์ด๋ฆ ์ถ์ถ (.json ํ์ฅ์ ์ ๊ฑฐ)
- const workflowName = file.name.replace(/\.json$/i, '');
- updateWorkflowName(workflowName);
- }
- } catch (error) {
- devLog.error('Error parsing JSON file:', error);
- alert('์ ํจํ์ง ์์ ํ์ผ ํ์์
๋๋ค.');
- }
- };
- reader.readAsText(file);
- e.target.value = '';
- };
-
- const handleDragOver = (e: React.DragEvent) => {
- e.preventDefault();
- };
-
- const handleDrop = (e: React.DragEvent) => {
- e.preventDefault();
- if (canvasRef.current) {
- const nodeData = JSON.parse(
- e.dataTransfer.getData('application/json'),
- );
- if (nodeData) {
- (canvasRef.current as any).addNode(
- nodeData,
- e.clientX,
- e.clientY,
- );
- }
- }
- };
-
- const handleExecute = async () => {
- if (!canvasRef.current) {
- toast.error('Canvas is not ready.');
- return;
- }
-
- const validationResult = (
- canvasRef.current as any
- ).validateAndPrepareExecution();
- if (validationResult.error) {
- toast.error(validationResult.error);
- return;
- }
-
- setIsExecuting(true);
- setExecutionOutput(null);
- const toastId = toast.loading('Executing workflow...');
-
- try {
- let workflowData = (canvasRef.current as any).getCanvasState();
- const workflowName = getWorkflowName();
-
- if (!workflowData.nodes || workflowData.nodes.length === 0) {
- throw new Error(
- 'Cannot execute an empty workflow. Please add nodes.',
- );
- }
-
- const workflowId = `workflow_${generateWorkflowHash(workflowData)}`;
- workflowData = { ...workflowData, workflow_id: workflowId };
- workflowData = { ...workflowData, workflow_name: workflowName };
-
- const result = await executeWorkflow(workflowData);
- setExecutionOutput(result);
- toast.success('Workflow executed successfully!', { id: toastId });
- } catch (error: any) {
- devLog.error('Execution failed:', error);
- setExecutionOutput({ error: error.message });
- toast.error(`Execution failed: ${error.message}`, { id: toastId });
- } finally {
- setIsExecuting(false);
- }
- };
-
- const handleClearOutput = () => {
- setExecutionOutput(null);
- };
-
- // ๋ธ๋ผ์ฐ์ ๋ค๋ก๊ฐ๊ธฐ ๋ฐฉ์ง
- useEffect(() => {
- const preventBackspace = (e: KeyboardEvent) => {
- // ์
๋ ฅ ํ๋๊ฐ ์๋ ๊ณณ์์ ๋ฐฑ์คํ์ด์ค ํค๋ฅผ ๋๋ ์ ๋ ๋ค๋ก๊ฐ๊ธฐ ๋ฐฉ์ง
- if (
- e.key === 'Backspace' &&
- e.target instanceof HTMLElement &&
- e.target.tagName !== 'INPUT' &&
- e.target.tagName !== 'SELECT' &&
- e.target.tagName !== 'TEXTAREA' &&
- !e.target.isContentEditable
- ) {
- e.preventDefault();
- }
- };
-
- window.addEventListener('keydown', preventBackspace);
- return () => window.removeEventListener('keydown', preventBackspace);
- }, []);
-
- return (
-
-
- );
-}