From 46c32e28a5dbfac5a289d8838db234cf38e43ff4 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 02:07:54 +0800 Subject: [PATCH 01/70] Initial commit --- LICENSE | 21 +++++++++++++++++++++ README.md | 1 + 2 files changed, 22 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0c86d7c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 LangGraph-GUI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d3897c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# reactflow-ts \ No newline at end of file From 915fc150c4b142c2f4a4e6a797f6da60e6844383 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Fri, 10 Jan 2025 18:34:05 +0000 Subject: [PATCH 02/70] vite eslint --- .gitignore | 26 ++++++++++++++++++ LICENSE | 2 +- README.default.md | 50 ++++++++++++++++++++++++++++++++++ README.md | 9 +++++- eslint.config.js | 29 ++++++++++++++++++++ index.html | 13 +++++++++ package.json | 29 ++++++++++++++++++++ src/App.css | 42 ++++++++++++++++++++++++++++ src/App.tsx | 33 ++++++++++++++++++++++ src/index.css | 68 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.tsx | 10 +++++++ src/vite-env.d.ts | 1 + tsconfig.app.json | 26 ++++++++++++++++++ tsconfig.json | 7 +++++ tsconfig.node.json | 24 ++++++++++++++++ vite.config.ts | 11 ++++++++ 16 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 README.default.md create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package.json create mode 100644 src/App.css create mode 100644 src/App.tsx create mode 100644 src/index.css create mode 100644 src/main.tsx create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4294bde --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +package-lock.json + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/LICENSE b/LICENSE index 0c86d7c..870a1b4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 LangGraph-GUI +Copyright (c) 2025 HomunMage Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.default.md b/README.default.md new file mode 100644 index 0000000..74872fd --- /dev/null +++ b/README.default.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/README.md b/README.md index 9d3897c..a4dfa15 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ -# reactflow-ts \ No newline at end of file +# reactflow-ts + +TODO list: +* install reactflow +* install redux +* use redux as SSOT +* porting ```src/Graph``` to ts +* hold at ```yourdomain.com``` \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..b9b93af --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + 'indent': ['error', 4], // added this line to enforce 4 space indent + }, + }, +) \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..fb865d5 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "langgraph-gui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.18.2", + "vite": "^6.0.5" + } +} diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..738aad8 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> +
+ + Vite logo + + + React logo + +
+

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..6119ad9 --- /dev/null +++ b/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..3b08b76 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..358ca9b --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..f0df571 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + host: '0.0.0.0', + port: 3000, + }, +}) From 12d070ea353d558767118d12d2ae5c3c24c9f391 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Fri, 10 Jan 2025 18:42:45 +0000 Subject: [PATCH 03/70] reactflow helloworld --- package.json | 1 + src/App.tsx | 50 +++++++++++++++++--------------------------------- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index fb865d5..b2c3dc6 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@xyflow/react": "^12.3.6", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/src/App.tsx b/src/App.tsx index 738aad8..da93b7b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,33 +1,17 @@ -import { useState } from 'react' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} - -export default App +import { ReactFlow } from '@xyflow/react'; + +import '@xyflow/react/dist/style.css'; + +const initialNodes = [ + { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, + { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, +]; +const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; + +export default function App() { + return ( +
+ +
+ ); +} \ No newline at end of file From 07c05267437653cb9c5f9db5e72b7c5eb89408ad Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 06:46:12 +0000 Subject: [PATCH 04/70] TODO: router --- package.json | 3 ++ postcss.config.js | 6 +++ src/App.css | 42 -------------------- src/App.tsx | 16 ++++---- src/index.css | 4 ++ src/src/ConfigManager.ts | 82 ++++++++++++++++++++++++++++++++++++++++ src/src/Graph | 0 tailwind.config.js | 19 ++++++++++ tsconfig.app.json | 10 +++-- tsconfig.json | 2 +- 10 files changed, 131 insertions(+), 53 deletions(-) create mode 100644 postcss.config.js delete mode 100644 src/App.css create mode 100644 src/src/ConfigManager.ts create mode 100644 src/src/Graph create mode 100644 tailwind.config.js diff --git a/package.json b/package.json index b2c3dc6..206a1b0 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,13 @@ "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", "eslint": "^9.17.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", "vite": "^6.0.5" diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx index da93b7b..5229bb2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,19 @@ +// App.tsx + import { ReactFlow } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; const initialNodes = [ - { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, - { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, + { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, + { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, ]; const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; export default function App() { - return ( -
- -
- ); + return ( +
+ +
+ ); } \ No newline at end of file diff --git a/src/index.css b/src/index.css index 6119ad9..e7d4bb2 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; diff --git a/src/src/ConfigManager.ts b/src/src/ConfigManager.ts new file mode 100644 index 0000000..3a55736 --- /dev/null +++ b/src/src/ConfigManager.ts @@ -0,0 +1,82 @@ +// ConfigManager.ts + +interface ConfigSettings { + username?: string | undefined; // Explicitly allow undefined + llmModel: string; + apiKey: string; +} + +class ConfigManager { + private static instance: ConfigManager; + private llmModel: string = 'gpt'; // Provide default values + private apiKey: string = ''; // Provide default values + private username?: string; // Optional because it's fetched asynchronously + + constructor() { + if (ConfigManager.instance) { + return ConfigManager.instance; + } + + // If the local storage has data use it, otherwise set to a default. + const storedLlmModel = localStorage.getItem('llmModel'); + if(storedLlmModel) { + this.llmModel = storedLlmModel; + } + + const storedApiKey = localStorage.getItem('apiKey'); + if(storedApiKey) { + this.apiKey = storedApiKey; + } + + this.fetchUsername(); // Initiate username fetch + + ConfigManager.instance = this; + } + + // Method to fetch username from Nginx API + private async fetchUsername() { + try { + const response = await fetch('/api/username', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + const data = await response.json() as {username: string}; + this.username = data.username; + } else { + console.error('Failed to fetch username:', response.status); + } + } catch (error) { + if(error instanceof Error) { + console.error('Error fetching username:', error.message); + } else { + console.error('Error fetching username:', error) + } + } + } + + + // Method to get the current settings + getSettings(): ConfigSettings { + return { + username: this.username, + llmModel: this.llmModel, + apiKey: this.apiKey, + }; + } + + // Method to update settings + setSettings(newLlmModel: string, newapiKey: string): void { + this.llmModel = newLlmModel; + this.apiKey = newapiKey; + + localStorage.setItem('llmModel', newLlmModel); + localStorage.setItem('apiKey', newapiKey); + } +} + +const instance = new ConfigManager(); +export default instance; \ No newline at end of file diff --git a/src/src/Graph b/src/src/Graph new file mode 100644 index 0000000..e69de29 diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..680cf18 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,19 @@ +// tailwind.config.js + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + ], + theme: { + extend: { + colors: { + primary: '#007bff', + secondary: '#333', + lightGray: '#ddd', + background: '#f9f9f9', + }, + }, + }, + plugins: [], +}; \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 358ca9b..024594d 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -20,7 +20,11 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + "exactOptionalPropertyTypes": true, + "noImplicitOverride": true }, - "include": ["src"] -} + "include": ["./**/*.ts", "./**/*.tsx"], + "exclude": ["node_modules"] + +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 1ffef60..c452f43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,4 +4,4 @@ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } ] -} +} \ No newline at end of file From 61143700d0d32fd10b53cd5a2e5761d0ca4d2ba5 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 07:18:54 +0000 Subject: [PATCH 05/70] env preparing --- README.md | 23 +++++++++-- package.json | 5 ++- src/App.tsx | 23 ++++------- src/src/ConfigManager.ts | 16 ++++---- src/src/Graph | 0 src/src/Graph/GraphApp.tsx | 20 ++++++++++ src/src/routes/index.tsx | 27 +++++++++++++ src/src/store/slices/subGraphSlice.ts | 55 +++++++++++++++++++++++++++ src/src/store/store.ts | 13 +++++++ 9 files changed, 155 insertions(+), 27 deletions(-) delete mode 100644 src/src/Graph create mode 100644 src/src/Graph/GraphApp.tsx create mode 100644 src/src/routes/index.tsx create mode 100644 src/src/store/slices/subGraphSlice.ts create mode 100644 src/src/store/store.ts diff --git a/README.md b/README.md index a4dfa15..7a073ed 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,25 @@ # reactflow-ts TODO list: -* install reactflow -* install redux * use redux as SSOT * porting ```src/Graph``` to ts -* hold at ```yourdomain.com``` \ No newline at end of file +* hold at ```yourdomain.com``` + + +## Run + +compile + +``` +tsc -p tsconfig.app.json +``` + +lint +``` +npx eslint . +``` + +hold +``` +npm run dev +``` \ No newline at end of file diff --git a/package.json b/package.json index 206a1b0..51448d6 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "@reduxjs/toolkit": "^2.5.0", "@xyflow/react": "^12.3.6", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-redux": "^9.2.0", + "react-router-dom": "^7.1.1" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/src/App.tsx b/src/App.tsx index 5229bb2..8861b3e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,12 @@ // App.tsx -import { ReactFlow } from '@xyflow/react'; - -import '@xyflow/react/dist/style.css'; - -const initialNodes = [ - { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, - { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, -]; -const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; - -export default function App() { +import React from 'react'; +import AppRoutes from './src/routes'; + +const App: React.FC = () => { return ( -
- -
+ ); -} \ No newline at end of file +}; + +export default App; \ No newline at end of file diff --git a/src/src/ConfigManager.ts b/src/src/ConfigManager.ts index 3a55736..4518be0 100644 --- a/src/src/ConfigManager.ts +++ b/src/src/ConfigManager.ts @@ -17,10 +17,10 @@ class ConfigManager { return ConfigManager.instance; } - // If the local storage has data use it, otherwise set to a default. + // If the local storage has data use it, otherwise set to a default. const storedLlmModel = localStorage.getItem('llmModel'); if(storedLlmModel) { - this.llmModel = storedLlmModel; + this.llmModel = storedLlmModel; } const storedApiKey = localStorage.getItem('apiKey'); @@ -33,7 +33,7 @@ class ConfigManager { ConfigManager.instance = this; } - // Method to fetch username from Nginx API + // Method to fetch username from Nginx API private async fetchUsername() { try { const response = await fetch('/api/username', { @@ -50,11 +50,11 @@ class ConfigManager { console.error('Failed to fetch username:', response.status); } } catch (error) { - if(error instanceof Error) { - console.error('Error fetching username:', error.message); - } else { - console.error('Error fetching username:', error) - } + if(error instanceof Error) { + console.error('Error fetching username:', error.message); + } else { + console.error('Error fetching username:', error) + } } } diff --git a/src/src/Graph b/src/src/Graph deleted file mode 100644 index e69de29..0000000 diff --git a/src/src/Graph/GraphApp.tsx b/src/src/Graph/GraphApp.tsx new file mode 100644 index 0000000..84ce61b --- /dev/null +++ b/src/src/Graph/GraphApp.tsx @@ -0,0 +1,20 @@ +// src/Graph/GraphApp.tsx + +import { ReactFlow } from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; + +const initialNodes = [ + { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, + { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, +]; +const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; + +const GraphApp: React.FC = () => { + return ( +
+ +
+ ); +}; + +export default GraphApp; \ No newline at end of file diff --git a/src/src/routes/index.tsx b/src/src/routes/index.tsx new file mode 100644 index 0000000..57672d1 --- /dev/null +++ b/src/src/routes/index.tsx @@ -0,0 +1,27 @@ +// routes/index.tsx + +import React from 'react'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; + +import GraphApp from '../Graph/GraphApp'; + + +// Example Components +const HomePage = () =>

Home Page

; +const AboutPage = () =>

About Page

; +const NotFoundPage = () =>

404 Not Found

+ +const AppRoutes: React.FC = () => { + return ( + + + } /> + } /> + } /> + } /> {/* Catch-all for 404 */} + + + ); +}; + +export default AppRoutes; \ No newline at end of file diff --git a/src/src/store/slices/subGraphSlice.ts b/src/src/store/slices/subGraphSlice.ts new file mode 100644 index 0000000..43d8403 --- /dev/null +++ b/src/src/store/slices/subGraphSlice.ts @@ -0,0 +1,55 @@ +// redux/slices/subGraphSlice.ts + +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + subGraphs: [{ + graphName: "root", + nodes: [], + serial_number: 0, + }], +}; + +const subGraphSlice = createSlice({ + name: 'subGraphs', + initialState, + reducers: { + + addSubGraph: (state, action) => { + const { graphName, nodes, serial_number } = action.payload; + + // Check if a subgraph with the same name already exists + const graphExists = state.subGraphs.some(graph => graph.graphName === graphName); + if (graphExists) { + console.error(`Subgraph with name '${graphName}' already exists. Cannot add.`); + return; // Prevent adding if name exists + } + state.subGraphs.push({graphName, nodes, serial_number}); + }, + updateSubGraph: (state, action) => { + const { graphName, nodes, serial_number } = action.payload; + const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); + + if(existingGraphIndex !== -1){ + state.subGraphs[existingGraphIndex] = { + graphName: graphName, + nodes: nodes, + serial_number: serial_number, + } + } else { + // If graph doesnt exist, add it + state.subGraphs.push({graphName: graphName, nodes: nodes, serial_number: serial_number}) + } + }, + removeSubGraph: (state, action) => { + state.subGraphs = state.subGraphs.filter(graph => graph.graphName !== action.payload); + }, + initSubGraphs: () => initialState, // Add reset reducer + setSubGraphs: (state, action) => { // New reducer to replace all subGraphs + state.subGraphs = action.payload; + }, + }, +}); + +export const { addSubGraph, updateSubGraph, removeSubGraph, initSubGraphs, setSubGraphs } = subGraphSlice.actions; +export default subGraphSlice.reducer; \ No newline at end of file diff --git a/src/src/store/store.ts b/src/src/store/store.ts new file mode 100644 index 0000000..ae815a3 --- /dev/null +++ b/src/src/store/store.ts @@ -0,0 +1,13 @@ +// redux/store.ts + +import { configureStore } from '@reduxjs/toolkit'; +import subGraphReducer from './slices/subGraphSlice'; + + +export const store = configureStore( + { + reducer: { + subGraphs: subGraphReducer, + }, + }, +); \ No newline at end of file From 87cfca8a4a23763f094fc0da0a6a5f1a6c9542ce Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 07:58:03 +0000 Subject: [PATCH 06/70] todo: use redux to render reactflow --- README.md | 12 +++- package.json | 3 +- src/App.tsx | 2 +- src/{src => }/ConfigManager.ts | 0 src/Graph/GraphApp.tsx | 66 +++++++++++++++++++++ src/main.tsx | 8 ++- src/redux/slices/subGraphSlice.ts | 85 +++++++++++++++++++++++++++ src/redux/store.ts | 21 +++++++ src/{src => }/routes/index.tsx | 4 +- src/src/Graph/GraphApp.tsx | 20 ------- src/src/store/slices/subGraphSlice.ts | 55 ----------------- src/src/store/store.ts | 13 ---- 12 files changed, 195 insertions(+), 94 deletions(-) rename src/{src => }/ConfigManager.ts (100%) create mode 100644 src/Graph/GraphApp.tsx create mode 100644 src/redux/slices/subGraphSlice.ts create mode 100644 src/redux/store.ts rename src/{src => }/routes/index.tsx (84%) delete mode 100644 src/src/Graph/GraphApp.tsx delete mode 100644 src/src/store/slices/subGraphSlice.ts delete mode 100644 src/src/store/store.ts diff --git a/README.md b/README.md index 7a073ed..6f478da 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,14 @@ npx eslint . hold ``` npm run dev -``` \ No newline at end of file +``` + + + +## Debug Redux + +in browser, F12 and type + +``` +window.store.getState().subGraphs +``` diff --git a/package.json b/package.json index 51448d6..1906667 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,13 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "NODE_ENV=DEBUG vite", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { + "@redux-devtools/extension": "^3.3.0", "@reduxjs/toolkit": "^2.5.0", "@xyflow/react": "^12.3.6", "react": "^18.3.1", diff --git a/src/App.tsx b/src/App.tsx index 8861b3e..9bb2a3b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ // App.tsx import React from 'react'; -import AppRoutes from './src/routes'; +import AppRoutes from './routes'; const App: React.FC = () => { return ( diff --git a/src/src/ConfigManager.ts b/src/ConfigManager.ts similarity index 100% rename from src/src/ConfigManager.ts rename to src/ConfigManager.ts diff --git a/src/Graph/GraphApp.tsx b/src/Graph/GraphApp.tsx new file mode 100644 index 0000000..9be54a5 --- /dev/null +++ b/src/Graph/GraphApp.tsx @@ -0,0 +1,66 @@ +// src/Graph/GraphApp.tsx + +import React, { useCallback } from 'react'; +import ReactFlow, { + addEdge, + useNodesState, + useEdgesState, + Connection, + OnNodesChange, + OnEdgesChange, + applyNodeChanges, + applyEdgeChanges, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '../redux/store'; +import { updateNodesInSubGraph, updateEdgesInSubGraph } from '../redux/slices/subGraphSlice'; + + +const GraphApp: React.FC = () => { + const dispatch = useDispatch(); + const subGraphs = useSelector((state: RootState) => state.subGraphs.subGraphs); + + // Assuming we are only using "root" subGraph for the moment + const currentSubGraph = subGraphs.find(graph => graph.graphName === "root"); + const initialNodes = currentSubGraph?.nodes || []; + const initialEdges = currentSubGraph?.edges || []; + + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + + + // Use callbacks to persist changes in Redux + const onNodesChangeHandler: OnNodesChange = useCallback((changes) => { + setNodes(nodes => applyNodeChanges(changes, nodes)) + dispatch(updateNodesInSubGraph({ graphName: "root", nodes: applyNodeChanges(changes, nodes) })); + }, [dispatch, setNodes]); + + const onEdgesChangeHandler: OnEdgesChange = useCallback((changes) => { + setEdges(edges => applyEdgeChanges(changes, edges)); + dispatch(updateEdgesInSubGraph({graphName: "root", edges: applyEdgeChanges(changes, edges)})) + }, [dispatch, setEdges]); + + const onConnect = useCallback( + (connection: Connection) => { + setEdges(edges => addEdge(connection, edges)) + dispatch(updateEdgesInSubGraph({ graphName: "root", edges: addEdge(connection, edges) })) + }, + [setEdges, dispatch] + ); + + + return ( +
+ +
+ ); +}; + +export default GraphApp; \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index 3b08b76..70c6410 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,8 +3,14 @@ import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' +import { Provider } from 'react-redux'; +import {store} from "./redux/store.ts" + + createRoot(document.getElementById('root')!).render( - + + + , ) diff --git a/src/redux/slices/subGraphSlice.ts b/src/redux/slices/subGraphSlice.ts new file mode 100644 index 0000000..4ef94c1 --- /dev/null +++ b/src/redux/slices/subGraphSlice.ts @@ -0,0 +1,85 @@ +// redux/slices/subGraphSlice.ts + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Node, Edge } from '@xyflow/react'; + +interface SubGraphState { + graphName: string; + nodes: Node[]; + edges: Edge[]; + serial_number: number; +} + + +const initialState: { subGraphs: SubGraphState[] } = { + subGraphs: [{ + graphName: "root", + nodes: [ + { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, + { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, + ], + edges: [{ id: 'e1-2', source: '1', target: '2' }], + serial_number: 0, + }], +}; + + + +const subGraphSlice = createSlice({ + name: 'subGraphs', + initialState, + reducers: { + addSubGraph: (state, action: PayloadAction<{ graphName: string, nodes: Node[], edges: Edge[], serial_number: number }>) => { + const { graphName, nodes, edges, serial_number } = action.payload; + + const graphExists = state.subGraphs.some(graph => graph.graphName === graphName); + if (graphExists) { + console.error(`Subgraph with name '${graphName}' already exists. Cannot add.`); + return; + } + state.subGraphs.push({ graphName, nodes, edges, serial_number }); + }, + updateSubGraph: (state, action: PayloadAction<{ graphName: string, nodes: Node[], edges: Edge[], serial_number: number }>) => { + const { graphName, nodes, edges, serial_number } = action.payload; + const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); + + if (existingGraphIndex !== -1) { + state.subGraphs[existingGraphIndex] = { + graphName: graphName, + nodes: nodes, + edges: edges, + serial_number: serial_number, + }; + } else { + state.subGraphs.push({ graphName: graphName, nodes: nodes, edges: edges, serial_number: serial_number }); + } + }, + removeSubGraph: (state, action: PayloadAction) => { + state.subGraphs = state.subGraphs.filter(graph => graph.graphName !== action.payload); + }, + initSubGraphs: () => initialState, + setSubGraphs: (state, action: PayloadAction) => { + state.subGraphs = action.payload; + }, + updateNodesInSubGraph: (state, action: PayloadAction<{ graphName: string, nodes: Node[] }>) => { + const { graphName, nodes } = action.payload; + const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); + + if (existingGraphIndex !== -1) { + state.subGraphs[existingGraphIndex].nodes = nodes; + } + }, + updateEdgesInSubGraph: (state, action: PayloadAction<{ graphName: string, edges: Edge[] }>) => { + const { graphName, edges } = action.payload; + const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); + + if(existingGraphIndex !== -1) { + state.subGraphs[existingGraphIndex].edges = edges; + } + } + }, +}); + + +export const { addSubGraph, updateSubGraph, removeSubGraph, initSubGraphs, setSubGraphs, updateNodesInSubGraph, updateEdgesInSubGraph } = subGraphSlice.actions; +export default subGraphSlice.reducer; \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts new file mode 100644 index 0000000..9524373 --- /dev/null +++ b/src/redux/store.ts @@ -0,0 +1,21 @@ +// redux/store.ts + +import { configureStore, } from '@reduxjs/toolkit'; +import subGraphReducer from './slices/subGraphSlice'; + +export const store = configureStore( + { + reducer: { + subGraphs: subGraphReducer, + }, + + }, + +); + +// Optional: Attach store to the window object for debugging (use conditionally) +if (process.env.NODE_ENV === 'DEBUG') { + (window as any).store = store; +} + +export type AppStore = typeof store; \ No newline at end of file diff --git a/src/src/routes/index.tsx b/src/routes/index.tsx similarity index 84% rename from src/src/routes/index.tsx rename to src/routes/index.tsx index 57672d1..1f99e8a 100644 --- a/src/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -15,8 +15,8 @@ const AppRoutes: React.FC = () => { return ( - } /> - } /> + } /> + } /> } /> } /> {/* Catch-all for 404 */} diff --git a/src/src/Graph/GraphApp.tsx b/src/src/Graph/GraphApp.tsx deleted file mode 100644 index 84ce61b..0000000 --- a/src/src/Graph/GraphApp.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// src/Graph/GraphApp.tsx - -import { ReactFlow } from '@xyflow/react'; -import '@xyflow/react/dist/style.css'; - -const initialNodes = [ - { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, - { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, -]; -const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; - -const GraphApp: React.FC = () => { - return ( -
- -
- ); -}; - -export default GraphApp; \ No newline at end of file diff --git a/src/src/store/slices/subGraphSlice.ts b/src/src/store/slices/subGraphSlice.ts deleted file mode 100644 index 43d8403..0000000 --- a/src/src/store/slices/subGraphSlice.ts +++ /dev/null @@ -1,55 +0,0 @@ -// redux/slices/subGraphSlice.ts - -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - subGraphs: [{ - graphName: "root", - nodes: [], - serial_number: 0, - }], -}; - -const subGraphSlice = createSlice({ - name: 'subGraphs', - initialState, - reducers: { - - addSubGraph: (state, action) => { - const { graphName, nodes, serial_number } = action.payload; - - // Check if a subgraph with the same name already exists - const graphExists = state.subGraphs.some(graph => graph.graphName === graphName); - if (graphExists) { - console.error(`Subgraph with name '${graphName}' already exists. Cannot add.`); - return; // Prevent adding if name exists - } - state.subGraphs.push({graphName, nodes, serial_number}); - }, - updateSubGraph: (state, action) => { - const { graphName, nodes, serial_number } = action.payload; - const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); - - if(existingGraphIndex !== -1){ - state.subGraphs[existingGraphIndex] = { - graphName: graphName, - nodes: nodes, - serial_number: serial_number, - } - } else { - // If graph doesnt exist, add it - state.subGraphs.push({graphName: graphName, nodes: nodes, serial_number: serial_number}) - } - }, - removeSubGraph: (state, action) => { - state.subGraphs = state.subGraphs.filter(graph => graph.graphName !== action.payload); - }, - initSubGraphs: () => initialState, // Add reset reducer - setSubGraphs: (state, action) => { // New reducer to replace all subGraphs - state.subGraphs = action.payload; - }, - }, -}); - -export const { addSubGraph, updateSubGraph, removeSubGraph, initSubGraphs, setSubGraphs } = subGraphSlice.actions; -export default subGraphSlice.reducer; \ No newline at end of file diff --git a/src/src/store/store.ts b/src/src/store/store.ts deleted file mode 100644 index ae815a3..0000000 --- a/src/src/store/store.ts +++ /dev/null @@ -1,13 +0,0 @@ -// redux/store.ts - -import { configureStore } from '@reduxjs/toolkit'; -import subGraphReducer from './slices/subGraphSlice'; - - -export const store = configureStore( - { - reducer: { - subGraphs: subGraphReducer, - }, - }, -); \ No newline at end of file From 5d17bcb9b6dffdeb037d21fb89fa32d6629708a4 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 08:12:42 +0000 Subject: [PATCH 07/70] use redux update good --- README.md | 5 +++ src/Graph/GraphApp.tsx | 60 ++++---------------------- src/redux/slices/subGraphSlice.ts | 72 +++++++++---------------------- src/redux/store.ts | 2 + 4 files changed, 36 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 6f478da..af89a87 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,11 @@ npm run dev ## Debug Redux +at ```package.json``` +``` +"dev": "NODE_ENV=DEBUG vite", +``` + in browser, F12 and type ``` diff --git a/src/Graph/GraphApp.tsx b/src/Graph/GraphApp.tsx index 9be54a5..dcdd5d2 100644 --- a/src/Graph/GraphApp.tsx +++ b/src/Graph/GraphApp.tsx @@ -1,64 +1,22 @@ // src/Graph/GraphApp.tsx -import React, { useCallback } from 'react'; -import ReactFlow, { - addEdge, - useNodesState, - useEdgesState, - Connection, - OnNodesChange, - OnEdgesChange, - applyNodeChanges, - applyEdgeChanges, -} from '@xyflow/react'; +import { ReactFlow } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from '../redux/store'; -import { updateNodesInSubGraph, updateEdgesInSubGraph } from '../redux/slices/subGraphSlice'; - +import { useSelector } from 'react-redux'; +import { RootState } from '../redux/store'; // Assuming your store is in 'redux/store.ts' const GraphApp: React.FC = () => { - const dispatch = useDispatch(); const subGraphs = useSelector((state: RootState) => state.subGraphs.subGraphs); + // For simplicity, using the first graph; You might want to select a graph based on some criteria + const currentGraph = subGraphs[0]; - // Assuming we are only using "root" subGraph for the moment - const currentSubGraph = subGraphs.find(graph => graph.graphName === "root"); - const initialNodes = currentSubGraph?.nodes || []; - const initialEdges = currentSubGraph?.edges || []; - - const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); - const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); - - - // Use callbacks to persist changes in Redux - const onNodesChangeHandler: OnNodesChange = useCallback((changes) => { - setNodes(nodes => applyNodeChanges(changes, nodes)) - dispatch(updateNodesInSubGraph({ graphName: "root", nodes: applyNodeChanges(changes, nodes) })); - }, [dispatch, setNodes]); - - const onEdgesChangeHandler: OnEdgesChange = useCallback((changes) => { - setEdges(edges => applyEdgeChanges(changes, edges)); - dispatch(updateEdgesInSubGraph({graphName: "root", edges: applyEdgeChanges(changes, edges)})) - }, [dispatch, setEdges]); - - const onConnect = useCallback( - (connection: Connection) => { - setEdges(edges => addEdge(connection, edges)) - dispatch(updateEdgesInSubGraph({ graphName: "root", edges: addEdge(connection, edges) })) - }, - [setEdges, dispatch] - ); - + if (!currentGraph) { + return
No Graph Data Found
; + } return (
- +
); }; diff --git a/src/redux/slices/subGraphSlice.ts b/src/redux/slices/subGraphSlice.ts index 4ef94c1..c0afb22 100644 --- a/src/redux/slices/subGraphSlice.ts +++ b/src/redux/slices/subGraphSlice.ts @@ -4,14 +4,17 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { Node, Edge } from '@xyflow/react'; interface SubGraphState { - graphName: string; - nodes: Node[]; - edges: Edge[]; - serial_number: number; + graphName: string; + nodes: Node[]; + edges: Edge[]; + serial_number: number; } +interface SubGraphSliceState { + subGraphs: SubGraphState[]; +} -const initialState: { subGraphs: SubGraphState[] } = { +const initialState: SubGraphSliceState = { subGraphs: [{ graphName: "root", nodes: [ @@ -23,63 +26,28 @@ const initialState: { subGraphs: SubGraphState[] } = { }], }; - - const subGraphSlice = createSlice({ name: 'subGraphs', initialState, reducers: { - addSubGraph: (state, action: PayloadAction<{ graphName: string, nodes: Node[], edges: Edge[], serial_number: number }>) => { - const { graphName, nodes, edges, serial_number } = action.payload; - - const graphExists = state.subGraphs.some(graph => graph.graphName === graphName); - if (graphExists) { - console.error(`Subgraph with name '${graphName}' already exists. Cannot add.`); - return; - } - state.subGraphs.push({ graphName, nodes, edges, serial_number }); + addSubGraph: (state, action: PayloadAction) => { + state.subGraphs.push(action.payload); }, - updateSubGraph: (state, action: PayloadAction<{ graphName: string, nodes: Node[], edges: Edge[], serial_number: number }>) => { - const { graphName, nodes, edges, serial_number } = action.payload; - const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); - - if (existingGraphIndex !== -1) { - state.subGraphs[existingGraphIndex] = { - graphName: graphName, - nodes: nodes, - edges: edges, - serial_number: serial_number, - }; - } else { - state.subGraphs.push({ graphName: graphName, nodes: nodes, edges: edges, serial_number: serial_number }); + updateSubGraph: (state, action: PayloadAction<{ index: number; updatedGraph: SubGraphState }>) => { + const { index, updatedGraph } = action.payload; + if(index >=0 && index < state.subGraphs.length){ + state.subGraphs[index] = updatedGraph; } }, - removeSubGraph: (state, action: PayloadAction) => { - state.subGraphs = state.subGraphs.filter(graph => graph.graphName !== action.payload); + removeSubGraph: (state, action: PayloadAction) => { + state.subGraphs = state.subGraphs.filter((_, index) => index !== action.payload); }, initSubGraphs: () => initialState, - setSubGraphs: (state, action: PayloadAction) => { - state.subGraphs = action.payload; - }, - updateNodesInSubGraph: (state, action: PayloadAction<{ graphName: string, nodes: Node[] }>) => { - const { graphName, nodes } = action.payload; - const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); - - if (existingGraphIndex !== -1) { - state.subGraphs[existingGraphIndex].nodes = nodes; - } - }, - updateEdgesInSubGraph: (state, action: PayloadAction<{ graphName: string, edges: Edge[] }>) => { - const { graphName, edges } = action.payload; - const existingGraphIndex = state.subGraphs.findIndex(graph => graph.graphName === graphName); - - if(existingGraphIndex !== -1) { - state.subGraphs[existingGraphIndex].edges = edges; - } - } + setSubGraphs: (state, action: PayloadAction) =>{ + state.subGraphs = action.payload + } }, }); - -export const { addSubGraph, updateSubGraph, removeSubGraph, initSubGraphs, setSubGraphs, updateNodesInSubGraph, updateEdgesInSubGraph } = subGraphSlice.actions; +export const { addSubGraph, updateSubGraph, removeSubGraph, initSubGraphs, setSubGraphs } = subGraphSlice.actions; export default subGraphSlice.reducer; \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index 9524373..8f3029f 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -18,4 +18,6 @@ if (process.env.NODE_ENV === 'DEBUG') { (window as any).store = store; } +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; export type AppStore = typeof store; \ No newline at end of file From 7ea9b7e1978f7d22aa981ac89b3005f37beab64b Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 15:18:18 +0000 Subject: [PATCH 08/70] good --- src/Graph/GraphApp.tsx | 68 +++++++++++++++++++++++++++++-- src/redux/slices/subGraphSlice.ts | 43 ++++++++++--------- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/Graph/GraphApp.tsx b/src/Graph/GraphApp.tsx index dcdd5d2..b9dfc75 100644 --- a/src/Graph/GraphApp.tsx +++ b/src/Graph/GraphApp.tsx @@ -2,13 +2,70 @@ import { ReactFlow } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -import { useSelector } from 'react-redux'; -import { RootState } from '../redux/store'; // Assuming your store is in 'redux/store.ts' +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '../redux/store'; +import { addSubGraph, updateSubGraph, removeSubGraph } from '../redux/slices/subGraphSlice'; +import { Node, Edge } from '@xyflow/react'; +import { useState, useMemo, useCallback, useEffect } from 'react'; const GraphApp: React.FC = () => { const subGraphs = useSelector((state: RootState) => state.subGraphs.subGraphs); - // For simplicity, using the first graph; You might want to select a graph based on some criteria - const currentGraph = subGraphs[0]; + const dispatch = useDispatch(); + const [currentGraphName] = useState("root"); //default graph + + + const getGraph = useCallback((graphName: string) => { + return subGraphs.find((graph) => graph.graphName === graphName); + }, [subGraphs]); + + + const initialGraphData = useMemo(() => { + return { + nodes: [ + { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, + { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, + ], + edges: [{ id: 'e1-2', source: '1', target: '2' }], + serial_number: 0, + }; + }, []); + + + + useEffect(() => { + const rootGraph = getGraph("root"); + if (rootGraph && rootGraph.nodes.length === 0) { + dispatch(updateSubGraph({ graphName: "root", updatedGraph:initialGraphData})); //add root graph + } + }, [dispatch, getGraph, initialGraphData]) + + + + const currentGraph = getGraph(currentGraphName); + + const handleAddGraph = () => { + const newGraphName = prompt("Enter a new graph name:"); + if (newGraphName) { + dispatch(addSubGraph(newGraphName)); + } + }; + + + const handleUpdateGraph = () => { + const newNodes: Node[] = [ + { id: '3', position: { x: 200, y: 0 }, data: { label: '3' } }, + { id: '4', position: { x: 200, y: 100 }, data: { label: '4' } }, + ] + const newEdges: Edge[] = [{ id: 'e3-4', source: '3', target: '4' }] + dispatch(updateSubGraph({ graphName: currentGraphName, updatedGraph: {nodes: newNodes, edges: newEdges, serial_number: 1} })); + }; + + const handleRemoveGraph = () => { + const graphName = prompt("Enter the graph name to delete:"); + if (graphName) { + dispatch(removeSubGraph(graphName)); + } + }; if (!currentGraph) { return
No Graph Data Found
; @@ -16,6 +73,9 @@ const GraphApp: React.FC = () => { return (
+ + +
); diff --git a/src/redux/slices/subGraphSlice.ts b/src/redux/slices/subGraphSlice.ts index c0afb22..54fd6d8 100644 --- a/src/redux/slices/subGraphSlice.ts +++ b/src/redux/slices/subGraphSlice.ts @@ -4,46 +4,51 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { Node, Edge } from '@xyflow/react'; interface SubGraphState { - graphName: string; - nodes: Node[]; - edges: Edge[]; - serial_number: number; + graphName: string; + nodes: Node[]; + edges: Edge[]; + serial_number: number; } interface SubGraphSliceState { - subGraphs: SubGraphState[]; + subGraphs: SubGraphState[]; } const initialState: SubGraphSliceState = { subGraphs: [{ graphName: "root", - nodes: [ - { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, - { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, - ], - edges: [{ id: 'e1-2', source: '1', target: '2' }], + nodes: [], + edges: [], serial_number: 0, }], }; + const subGraphSlice = createSlice({ name: 'subGraphs', initialState, reducers: { - addSubGraph: (state, action: PayloadAction) => { - state.subGraphs.push(action.payload); + addSubGraph: (state, action: PayloadAction) => { + const newGraph:SubGraphState = { + graphName: action.payload, + nodes: [], + edges: [], + serial_number: 0 + }; + state.subGraphs.push(newGraph); }, - updateSubGraph: (state, action: PayloadAction<{ index: number; updatedGraph: SubGraphState }>) => { - const { index, updatedGraph } = action.payload; - if(index >=0 && index < state.subGraphs.length){ - state.subGraphs[index] = updatedGraph; + updateSubGraph: (state, action: PayloadAction<{ graphName: string; updatedGraph: Omit }>) => { + const { graphName, updatedGraph } = action.payload; + const graphIndex = state.subGraphs.findIndex((graph) => graph.graphName === graphName); + if (graphIndex !== -1) { + state.subGraphs[graphIndex] = { ...state.subGraphs[graphIndex], ...updatedGraph }; } }, - removeSubGraph: (state, action: PayloadAction) => { - state.subGraphs = state.subGraphs.filter((_, index) => index !== action.payload); + removeSubGraph: (state, action: PayloadAction) => { + state.subGraphs = state.subGraphs.filter((graph) => graph.graphName !== action.payload); }, initSubGraphs: () => initialState, - setSubGraphs: (state, action: PayloadAction) =>{ + setSubGraphs: (state, action: PayloadAction) => { state.subGraphs = action.payload } }, From 882f046e42668385dc42a07ecccfffb102d7ab41 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 16:49:29 +0000 Subject: [PATCH 09/70] redux SSOT better --- src/Graph/GraphApp.tsx | 44 +++++++++++++++++--------- src/redux/slices/subGraphSlice.ts | 51 +++++++++++++++++-------------- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/Graph/GraphApp.tsx b/src/Graph/GraphApp.tsx index b9dfc75..aff997b 100644 --- a/src/Graph/GraphApp.tsx +++ b/src/Graph/GraphApp.tsx @@ -4,23 +4,24 @@ import { ReactFlow } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '../redux/store'; -import { addSubGraph, updateSubGraph, removeSubGraph } from '../redux/slices/subGraphSlice'; +import { addSubGraph, updateSubGraph, removeSubGraph, setCurrentGraphName } from '../redux/slices/subGraphSlice'; import { Node, Edge } from '@xyflow/react'; -import { useState, useMemo, useCallback, useEffect } from 'react'; +import { useMemo, useCallback, useEffect } from 'react'; + const GraphApp: React.FC = () => { const subGraphs = useSelector((state: RootState) => state.subGraphs.subGraphs); + const currentGraphName = useSelector((state: RootState) => state.subGraphs.currentGraphName); const dispatch = useDispatch(); - const [currentGraphName] = useState("root"); //default graph const getGraph = useCallback((graphName: string) => { return subGraphs.find((graph) => graph.graphName === graphName); }, [subGraphs]); - const initialGraphData = useMemo(() => { return { + graphName: "root", nodes: [ { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, @@ -31,17 +32,17 @@ const GraphApp: React.FC = () => { }, []); - useEffect(() => { const rootGraph = getGraph("root"); - if (rootGraph && rootGraph.nodes.length === 0) { - dispatch(updateSubGraph({ graphName: "root", updatedGraph:initialGraphData})); //add root graph + if (!rootGraph) { // Check if rootGraph exists, if not then initialize it. + dispatch(updateSubGraph({ graphName: "root", updatedGraph:initialGraphData})); } - }, [dispatch, getGraph, initialGraphData]) + }, [dispatch, getGraph, initialGraphData]); - const currentGraph = getGraph(currentGraphName); + // Always get the current graph, use initial graph data when current graph is not loaded + const currentGraph = getGraph(currentGraphName) || initialGraphData; const handleAddGraph = () => { const newGraphName = prompt("Enter a new graph name:"); @@ -50,14 +51,21 @@ const GraphApp: React.FC = () => { } }; - const handleUpdateGraph = () => { const newNodes: Node[] = [ { id: '3', position: { x: 200, y: 0 }, data: { label: '3' } }, { id: '4', position: { x: 200, y: 100 }, data: { label: '4' } }, ] const newEdges: Edge[] = [{ id: 'e3-4', source: '3', target: '4' }] - dispatch(updateSubGraph({ graphName: currentGraphName, updatedGraph: {nodes: newNodes, edges: newEdges, serial_number: 1} })); + dispatch(updateSubGraph({ + graphName: currentGraphName, + updatedGraph: { + graphName: currentGraphName, + nodes: newNodes, + edges: newEdges, + serial_number: 1 + } + })); }; const handleRemoveGraph = () => { @@ -67,15 +75,23 @@ const GraphApp: React.FC = () => { } }; - if (!currentGraph) { - return
No Graph Data Found
; - } + + const handleSelectGraph = (graphName: string) => { + dispatch(setCurrentGraphName(graphName)); + }; + + return (
+
+ {subGraphs.map((graph) => ( + + ))} +
); diff --git a/src/redux/slices/subGraphSlice.ts b/src/redux/slices/subGraphSlice.ts index 54fd6d8..15de1b0 100644 --- a/src/redux/slices/subGraphSlice.ts +++ b/src/redux/slices/subGraphSlice.ts @@ -3,56 +3,61 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { Node, Edge } from '@xyflow/react'; -interface SubGraphState { +interface SubGraph { graphName: string; nodes: Node[]; edges: Edge[]; serial_number: number; } -interface SubGraphSliceState { - subGraphs: SubGraphState[]; +interface SubGraphState { + subGraphs: SubGraph[]; + currentGraphName: string; // Changed to string } -const initialState: SubGraphSliceState = { - subGraphs: [{ - graphName: "root", - nodes: [], - edges: [], - serial_number: 0, - }], +const initialState: SubGraphState = { + subGraphs: [], + currentGraphName: "root", // Default to 'root' }; - const subGraphSlice = createSlice({ name: 'subGraphs', initialState, reducers: { addSubGraph: (state, action: PayloadAction) => { - const newGraph:SubGraphState = { + state.subGraphs.push({ graphName: action.payload, nodes: [], edges: [], - serial_number: 0 - }; - state.subGraphs.push(newGraph); + serial_number: 0, + }); + if (!state.currentGraphName) state.currentGraphName = action.payload; }, - updateSubGraph: (state, action: PayloadAction<{ graphName: string; updatedGraph: Omit }>) => { + updateSubGraph: ( + state, + action: PayloadAction<{ graphName: string; updatedGraph: SubGraph }> + ) => { const { graphName, updatedGraph } = action.payload; const graphIndex = state.subGraphs.findIndex((graph) => graph.graphName === graphName); if (graphIndex !== -1) { - state.subGraphs[graphIndex] = { ...state.subGraphs[graphIndex], ...updatedGraph }; + state.subGraphs[graphIndex] = { + ...updatedGraph, + }; } }, removeSubGraph: (state, action: PayloadAction) => { - state.subGraphs = state.subGraphs.filter((graph) => graph.graphName !== action.payload); + const graphName = action.payload; + state.subGraphs = state.subGraphs.filter((graph) => graph.graphName !== graphName); + if (state.currentGraphName === graphName) { + state.currentGraphName = "root"; + } + }, + setCurrentGraphName: (state, action: PayloadAction) => { // Changed to string + state.currentGraphName = action.payload; }, - initSubGraphs: () => initialState, - setSubGraphs: (state, action: PayloadAction) => { - state.subGraphs = action.payload - } }, }); -export const { addSubGraph, updateSubGraph, removeSubGraph, initSubGraphs, setSubGraphs } = subGraphSlice.actions; +export const { addSubGraph, updateSubGraph, removeSubGraph, setCurrentGraphName } = + subGraphSlice.actions; export default subGraphSlice.reducer; \ No newline at end of file From 2ff728b579182d5baaa00f66ce67d0a81db6dd79 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 18:24:14 +0000 Subject: [PATCH 10/70] Add node is ok --- package.json | 3 +- src/Graph/GraphApp.tsx | 68 +++++++++++++++++++++++++++++++++++++++--- src/main.tsx | 9 ++++-- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 1906667..b5fcfce 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-redux": "^9.2.0", - "react-router-dom": "^7.1.1" + "react-router-dom": "^7.1.1", + "uuid": "^11.0.5" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/src/Graph/GraphApp.tsx b/src/Graph/GraphApp.tsx index aff997b..a72583c 100644 --- a/src/Graph/GraphApp.tsx +++ b/src/Graph/GraphApp.tsx @@ -1,12 +1,12 @@ // src/Graph/GraphApp.tsx -import { ReactFlow } from '@xyflow/react'; +import { ReactFlow, useReactFlow, ReactFlowProps } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '../redux/store'; import { addSubGraph, updateSubGraph, removeSubGraph, setCurrentGraphName } from '../redux/slices/subGraphSlice'; import { Node, Edge } from '@xyflow/react'; -import { useMemo, useCallback, useEffect } from 'react'; +import { useMemo, useCallback, useEffect, useState } from 'react'; const GraphApp: React.FC = () => { @@ -14,6 +14,11 @@ const GraphApp: React.FC = () => { const currentGraphName = useSelector((state: RootState) => state.subGraphs.currentGraphName); const dispatch = useDispatch(); + const { screenToFlowPosition } = useReactFlow(); + + + const [contextMenu, setContextMenu] = useState<{mouseX: number, mouseY: number} | null>(null); + const getGraph = useCallback((graphName: string) => { return subGraphs.find((graph) => graph.graphName === graphName); @@ -35,7 +40,7 @@ const GraphApp: React.FC = () => { useEffect(() => { const rootGraph = getGraph("root"); if (!rootGraph) { // Check if rootGraph exists, if not then initialize it. - dispatch(updateSubGraph({ graphName: "root", updatedGraph:initialGraphData})); + dispatch(updateSubGraph({ graphName: "root", updatedGraph: initialGraphData })); } }, [dispatch, getGraph, initialGraphData]); @@ -44,6 +49,28 @@ const GraphApp: React.FC = () => { // Always get the current graph, use initial graph data when current graph is not loaded const currentGraph = getGraph(currentGraphName) || initialGraphData; + + const handleAddNode = useCallback(() => { + if (contextMenu) { + const newPosition = screenToFlowPosition({ x: contextMenu.mouseX, y: contextMenu.mouseY }); + const newNode = { + id: String(Date.now()), // Generate a unique ID, consider using UUID + position: newPosition, + data: { label: 'New Node' }, + }; + const updatedNodes = [...currentGraph.nodes, newNode] + dispatch(updateSubGraph({ + graphName: currentGraphName, + updatedGraph: { + ...currentGraph, + nodes: updatedNodes + } + })); + setContextMenu(null); + } + }, [contextMenu, screenToFlowPosition, currentGraph, dispatch, currentGraphName]); + + const handleAddGraph = () => { const newGraphName = prompt("Enter a new graph name:"); if (newGraphName) { @@ -80,6 +107,23 @@ const GraphApp: React.FC = () => { dispatch(setCurrentGraphName(graphName)); }; + const handlePaneContextMenu = useCallback((event: React.MouseEvent) => { + event.preventDefault(); + setContextMenu({ + mouseX: event.clientX, + mouseY: event.clientY + }); + }, []); + + const handleCloseContextMenu = () => { + setContextMenu(null); + }; + + + const reactFlowProps = useMemo(() => ({ + onContextMenu: handlePaneContextMenu, + onClick: handleCloseContextMenu, + }),[handlePaneContextMenu,handleCloseContextMenu]) return ( @@ -92,7 +136,23 @@ const GraphApp: React.FC = () => { ))} - + + {contextMenu && ( +
+ + +
+ )} ); }; diff --git a/src/main.tsx b/src/main.tsx index 70c6410..4bcce7a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,3 +1,5 @@ +// main.tsx + import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' @@ -5,12 +7,15 @@ import App from './App.tsx' import { Provider } from 'react-redux'; import {store} from "./redux/store.ts" +import { ReactFlowProvider } from '@xyflow/react'; createRoot(document.getElementById('root')!).render( - + + + , -) +) \ No newline at end of file From 7206f2c6670ee840ca42b11c9eb082bb85278856 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 18:30:57 +0000 Subject: [PATCH 11/70] todo: submodule --- src/Graph/GraphApp.tsx | 160 ----------------------------------------- 1 file changed, 160 deletions(-) delete mode 100644 src/Graph/GraphApp.tsx diff --git a/src/Graph/GraphApp.tsx b/src/Graph/GraphApp.tsx deleted file mode 100644 index a72583c..0000000 --- a/src/Graph/GraphApp.tsx +++ /dev/null @@ -1,160 +0,0 @@ -// src/Graph/GraphApp.tsx - -import { ReactFlow, useReactFlow, ReactFlowProps } from '@xyflow/react'; -import '@xyflow/react/dist/style.css'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from '../redux/store'; -import { addSubGraph, updateSubGraph, removeSubGraph, setCurrentGraphName } from '../redux/slices/subGraphSlice'; -import { Node, Edge } from '@xyflow/react'; -import { useMemo, useCallback, useEffect, useState } from 'react'; - - -const GraphApp: React.FC = () => { - const subGraphs = useSelector((state: RootState) => state.subGraphs.subGraphs); - const currentGraphName = useSelector((state: RootState) => state.subGraphs.currentGraphName); - const dispatch = useDispatch(); - - const { screenToFlowPosition } = useReactFlow(); - - - const [contextMenu, setContextMenu] = useState<{mouseX: number, mouseY: number} | null>(null); - - - const getGraph = useCallback((graphName: string) => { - return subGraphs.find((graph) => graph.graphName === graphName); - }, [subGraphs]); - - const initialGraphData = useMemo(() => { - return { - graphName: "root", - nodes: [ - { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } }, - { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } }, - ], - edges: [{ id: 'e1-2', source: '1', target: '2' }], - serial_number: 0, - }; - }, []); - - - useEffect(() => { - const rootGraph = getGraph("root"); - if (!rootGraph) { // Check if rootGraph exists, if not then initialize it. - dispatch(updateSubGraph({ graphName: "root", updatedGraph: initialGraphData })); - } - }, [dispatch, getGraph, initialGraphData]); - - - - // Always get the current graph, use initial graph data when current graph is not loaded - const currentGraph = getGraph(currentGraphName) || initialGraphData; - - - const handleAddNode = useCallback(() => { - if (contextMenu) { - const newPosition = screenToFlowPosition({ x: contextMenu.mouseX, y: contextMenu.mouseY }); - const newNode = { - id: String(Date.now()), // Generate a unique ID, consider using UUID - position: newPosition, - data: { label: 'New Node' }, - }; - const updatedNodes = [...currentGraph.nodes, newNode] - dispatch(updateSubGraph({ - graphName: currentGraphName, - updatedGraph: { - ...currentGraph, - nodes: updatedNodes - } - })); - setContextMenu(null); - } - }, [contextMenu, screenToFlowPosition, currentGraph, dispatch, currentGraphName]); - - - const handleAddGraph = () => { - const newGraphName = prompt("Enter a new graph name:"); - if (newGraphName) { - dispatch(addSubGraph(newGraphName)); - } - }; - - const handleUpdateGraph = () => { - const newNodes: Node[] = [ - { id: '3', position: { x: 200, y: 0 }, data: { label: '3' } }, - { id: '4', position: { x: 200, y: 100 }, data: { label: '4' } }, - ] - const newEdges: Edge[] = [{ id: 'e3-4', source: '3', target: '4' }] - dispatch(updateSubGraph({ - graphName: currentGraphName, - updatedGraph: { - graphName: currentGraphName, - nodes: newNodes, - edges: newEdges, - serial_number: 1 - } - })); - }; - - const handleRemoveGraph = () => { - const graphName = prompt("Enter the graph name to delete:"); - if (graphName) { - dispatch(removeSubGraph(graphName)); - } - }; - - - const handleSelectGraph = (graphName: string) => { - dispatch(setCurrentGraphName(graphName)); - }; - - const handlePaneContextMenu = useCallback((event: React.MouseEvent) => { - event.preventDefault(); - setContextMenu({ - mouseX: event.clientX, - mouseY: event.clientY - }); - }, []); - - const handleCloseContextMenu = () => { - setContextMenu(null); - }; - - - const reactFlowProps = useMemo(() => ({ - onContextMenu: handlePaneContextMenu, - onClick: handleCloseContextMenu, - }),[handlePaneContextMenu,handleCloseContextMenu]) - - - return ( -
- - - -
- {subGraphs.map((graph) => ( - - ))} -
- - {contextMenu && ( -
- - -
- )} -
- ); -}; - -export default GraphApp; \ No newline at end of file From 074c9c24eccaf28e85f9f12626d01b40fd6eee77 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 18:35:48 +0000 Subject: [PATCH 12/70] refractor. TODO: "root" as default --- .gitmodules | 3 ++ src/redux/slices/subGraphSlice.ts | 63 ------------------------------- src/redux/store.ts | 2 +- 3 files changed, 4 insertions(+), 64 deletions(-) create mode 100644 .gitmodules delete mode 100644 src/redux/slices/subGraphSlice.ts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dd05226 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/Graph"] + path = src/Graph + url = ../reactflow-ts diff --git a/src/redux/slices/subGraphSlice.ts b/src/redux/slices/subGraphSlice.ts deleted file mode 100644 index 15de1b0..0000000 --- a/src/redux/slices/subGraphSlice.ts +++ /dev/null @@ -1,63 +0,0 @@ -// redux/slices/subGraphSlice.ts - -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { Node, Edge } from '@xyflow/react'; - -interface SubGraph { - graphName: string; - nodes: Node[]; - edges: Edge[]; - serial_number: number; -} - -interface SubGraphState { - subGraphs: SubGraph[]; - currentGraphName: string; // Changed to string -} - -const initialState: SubGraphState = { - subGraphs: [], - currentGraphName: "root", // Default to 'root' -}; - -const subGraphSlice = createSlice({ - name: 'subGraphs', - initialState, - reducers: { - addSubGraph: (state, action: PayloadAction) => { - state.subGraphs.push({ - graphName: action.payload, - nodes: [], - edges: [], - serial_number: 0, - }); - if (!state.currentGraphName) state.currentGraphName = action.payload; - }, - updateSubGraph: ( - state, - action: PayloadAction<{ graphName: string; updatedGraph: SubGraph }> - ) => { - const { graphName, updatedGraph } = action.payload; - const graphIndex = state.subGraphs.findIndex((graph) => graph.graphName === graphName); - if (graphIndex !== -1) { - state.subGraphs[graphIndex] = { - ...updatedGraph, - }; - } - }, - removeSubGraph: (state, action: PayloadAction) => { - const graphName = action.payload; - state.subGraphs = state.subGraphs.filter((graph) => graph.graphName !== graphName); - if (state.currentGraphName === graphName) { - state.currentGraphName = "root"; - } - }, - setCurrentGraphName: (state, action: PayloadAction) => { // Changed to string - state.currentGraphName = action.payload; - }, - }, -}); - -export const { addSubGraph, updateSubGraph, removeSubGraph, setCurrentGraphName } = - subGraphSlice.actions; -export default subGraphSlice.reducer; \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index 8f3029f..4c94d99 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,7 +1,7 @@ // redux/store.ts import { configureStore, } from '@reduxjs/toolkit'; -import subGraphReducer from './slices/subGraphSlice'; +import subGraphReducer from '../Graph/subGraphSlice.store'; export const store = configureStore( { From 3f59392ff71cc1f7d9b6223a5b31ceaa9aaa7260 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 18:37:46 +0000 Subject: [PATCH 13/70] subgraph --- src/Graph | 1 + 1 file changed, 1 insertion(+) create mode 160000 src/Graph diff --git a/src/Graph b/src/Graph new file mode 160000 index 0000000..f74af06 --- /dev/null +++ b/src/Graph @@ -0,0 +1 @@ +Subproject commit f74af064a4e5b1606727b33a1e57b11ec0445aa0 From a915026f0570639f78d76b1f445b56667caf774c Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 18:51:32 +0000 Subject: [PATCH 14/70] with "root" --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index f74af06..cd4a547 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit f74af064a4e5b1606727b33a1e57b11ec0445aa0 +Subproject commit cd4a5479697d4261db707ca68038273847e8b5fb From 2b17d1e253ccdcf22b5e0ab216a31ca001d18137 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 19:18:24 +0000 Subject: [PATCH 15/70] refractor to panel --- src/Graph | 2 +- src/redux/store.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Graph b/src/Graph index cd4a547..defec60 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit cd4a5479697d4261db707ca68038273847e8b5fb +Subproject commit defec604c24b975f5d9718a3175cfe36356fe37a diff --git a/src/redux/store.ts b/src/redux/store.ts index 4c94d99..4dcd542 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,7 +1,7 @@ // redux/store.ts import { configureStore, } from '@reduxjs/toolkit'; -import subGraphReducer from '../Graph/subGraphSlice.store'; +import subGraphReducer from '../Graph/subGraphs.store'; export const store = configureStore( { From 57e76348467909254e0caff90fed091f36c82ebb Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 19:38:01 +0000 Subject: [PATCH 16/70] better render canvas --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index defec60..5160621 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit defec604c24b975f5d9718a3175cfe36356fe37a +Subproject commit 516062178fb47c683a2f3b387ca475acea8be6d7 From c99631ba9983e1d9e35b9fc8224b64effdb1edd7 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 11 Jan 2025 20:04:26 +0000 Subject: [PATCH 17/70] utils --- src/redux/store.ts | 4 +-- src/redux/userInfo.store.ts | 39 ++++++++++++++++++++++ src/utils/JsonIO.ts | 64 +++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/redux/userInfo.store.ts create mode 100644 src/utils/JsonIO.ts diff --git a/src/redux/store.ts b/src/redux/store.ts index 4dcd542..2f42be9 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -2,15 +2,15 @@ import { configureStore, } from '@reduxjs/toolkit'; import subGraphReducer from '../Graph/subGraphs.store'; +import userInfoReducer from './userInfo.store'; export const store = configureStore( { reducer: { subGraphs: subGraphReducer, + userInfo: userInfoReducer, }, - }, - ); // Optional: Attach store to the window object for debugging (use conditionally) diff --git a/src/redux/userInfo.store.ts b/src/redux/userInfo.store.ts new file mode 100644 index 0000000..4eee535 --- /dev/null +++ b/src/redux/userInfo.store.ts @@ -0,0 +1,39 @@ +// redux/userInfo.store.ts + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface UserInfoState { + user_id: string; + llmModel: string; + apiKey: string; +} + +const initialState: UserInfoState = { + user_id: localStorage.getItem('user_id') || '', + llmModel: localStorage.getItem('llmModel') || '', + apiKey: localStorage.getItem('apiKey') || '', +}; + +const userInfoSlice = createSlice({ + name: 'userInfo', + initialState, + reducers: { + setSettings: (state, action: PayloadAction<{ newLlmModel: string; newapiKey: string; newUserId: string }>) => { + const { newLlmModel, newapiKey, newUserId } = action.payload; + state.llmModel = newLlmModel; + state.apiKey = newapiKey; + state.user_id = newUserId; + + localStorage.setItem('llmModel', newLlmModel); + localStorage.setItem('apiKey', newapiKey); + localStorage.setItem('user_id', newUserId); + }, + setUserId: (state, action: PayloadAction) => { + state.user_id = action.payload; + localStorage.setItem('user_id', action.payload); + } + }, +}); + +export const { setSettings, setUserId } = userInfoSlice.actions; +export default userInfoSlice.reducer; \ No newline at end of file diff --git a/src/utils/JsonIO.ts b/src/utils/JsonIO.ts new file mode 100644 index 0000000..a4a5e9f --- /dev/null +++ b/src/utils/JsonIO.ts @@ -0,0 +1,64 @@ +// utils/JsonIO.ts + +export const saveJsonToFile = (JsonData: any): void => { + try { + const blob = new Blob([JSON.stringify(JsonData, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'Save.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + alert('File saved!'); + } catch (error) { + console.error('Error saving JSON:', error); + alert('Failed to save file.'); + } +}; + +export const loadJsonFromFile = (): Promise => { + return new Promise((resolve, reject) => { + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.json'; + fileInput.style.display = 'none'; + document.body.appendChild(fileInput); + + fileInput.addEventListener('change', async (event) => { + try { + const file = (event.target as HTMLInputElement).files?.[0]; + if (!file) { + reject(new Error('No file selected.')); + return; + } + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const contents = e.target?.result; + if (typeof contents === 'string') { + const parsedData = JSON.parse(contents); + resolve(parsedData); + } else { + reject(new Error('File contents are not a string.')); + } + + } catch (error) { + reject(new Error('Error parsing JSON.' + error)); + } + }; + reader.onerror = () => reject(new Error('Error reading file.')); + reader.readAsText(file); + } catch (error) { + console.error("Error during file handling:", error); + reject(new Error('Error loading JSON:' + error)); + } finally { + document.body.removeChild(fileInput); + } + }); + fileInput.click(); + }); +}; \ No newline at end of file From e502d4c46d0b6af18f00ffde9a7804ef7ddc22d8 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 12 Jan 2025 04:26:32 +0000 Subject: [PATCH 18/70] panel control good, TODO: Canvas can delete node --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 5160621..9712e41 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 516062178fb47c683a2f3b387ca475acea8be6d7 +Subproject commit 9712e4184554be0052bfdaa4e7d75dcdeddbdc0a From a3729ced5a4926c9d7b668d049b9ba47bc6f1a78 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 12 Jan 2025 04:50:39 +0000 Subject: [PATCH 19/70] able to delete node --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 9712e41..10dd2cb 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 9712e4184554be0052bfdaa4e7d75dcdeddbdc0a +Subproject commit 10dd2cbe1c5b8eab40ce9317bf334f0e53c34dda From fb4eac080f69d99c5d5f2ca95c3fd91b02a9c025 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 12 Jan 2025 04:56:52 +0000 Subject: [PATCH 20/70] able to drag, todo next: custmozed nodes --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 10dd2cb..a54df12 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 10dd2cbe1c5b8eab40ce9317bf334f0e53c34dda +Subproject commit a54df12ba06c5bd8e657c2a7f5b25e7c530539f0 From 5e9c5dc186e07784702fdbeb3c7c770819299904 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 12 Jan 2025 06:32:08 +0000 Subject: [PATCH 21/70] CustomNode, ok todo: refractor --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index a54df12..6014aa7 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit a54df12ba06c5bd8e657c2a7f5b25e7c530539f0 +Subproject commit 6014aa759bc2f1c020d9896bb73f5d6377258096 From 6057f59048f88f171ee853c016ccbda157697a89 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 12 Jan 2025 07:05:39 +0000 Subject: [PATCH 22/70] nodelayout port ok, todo: * resize * edge * new type: subgraph --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 6014aa7..f1eeaff 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 6014aa759bc2f1c020d9896bb73f5d6377258096 +Subproject commit f1eeaff6deecf4eaab6f4e9298d0d7c532827c80 From f0c02f62d1956e2d46b12e531c11b822046bae1b Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 12 Jan 2025 11:20:37 +0000 Subject: [PATCH 23/70] resize icon right, todo: real resize --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index f1eeaff..318ccda 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit f1eeaff6deecf4eaab6f4e9298d0d7c532827c80 +Subproject commit 318ccdac8ac9fc4034f0c01754720ca49fc53b06 From 2814ededc058eb925271314af9068998dc88ce4a Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 12 Jan 2025 13:13:18 +0000 Subject: [PATCH 24/70] clean warning, TODO: use native node, edge in reactflow --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 318ccda..9d56fc7 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 318ccdac8ac9fc4034f0c01754720ca49fc53b06 +Subproject commit 9d56fc759499c243d1debe0194ea853d59e6adb5 From 13ddf51d1da67a0cbcbb9a8c71d2dad473e1bef8 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Mon, 13 Jan 2025 05:08:21 +0000 Subject: [PATCH 25/70] give up use redux --- src/Graph | 2 +- src/main.tsx | 9 ++++++--- src/redux/store.ts | 2 -- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Graph b/src/Graph index 9d56fc7..9514828 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 9d56fc759499c243d1debe0194ea853d59e6adb5 +Subproject commit 9514828d3b0a40f4abec6b1ff13c621dcf05e8da diff --git a/src/main.tsx b/src/main.tsx index 4bcce7a..9733a89 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,14 +8,17 @@ import App from './App.tsx' import { Provider } from 'react-redux'; import {store} from "./redux/store.ts" import { ReactFlowProvider } from '@xyflow/react'; +import { GraphProvider } from './Graph/GraphContext'; createRoot(document.getElementById('root')!).render( - - - + + + + + , ) \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index 2f42be9..b1747bb 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,13 +1,11 @@ // redux/store.ts import { configureStore, } from '@reduxjs/toolkit'; -import subGraphReducer from '../Graph/subGraphs.store'; import userInfoReducer from './userInfo.store'; export const store = configureStore( { reducer: { - subGraphs: subGraphReducer, userInfo: userInfoReducer, }, }, From 6a4d206fa7a892bc78b93e5afd7574463da54ff1 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Thu, 16 Jan 2025 11:57:58 +0000 Subject: [PATCH 26/70] TODO: edge --- README.md | 2 +- src/Graph | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af89a87..14702c7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ tsc -p tsconfig.app.json lint ``` -npx eslint . +npx eslint --fix . ``` hold diff --git a/src/Graph b/src/Graph index 9514828..dd1ed24 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 9514828d3b0a40f4abec6b1ff13c621dcf05e8da +Subproject commit dd1ed24fc402177111d2f686bded6a2094164930 From 9b4e5738563c4cbec615c84fee6d8d5e9188cd50 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Fri, 17 Jan 2025 11:39:29 +0000 Subject: [PATCH 27/70] edge create ok, TODO: customized edge logic --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index dd1ed24..9e4e90e 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit dd1ed24fc402177111d2f686bded6a2094164930 +Subproject commit 9e4e90ead54ea95a1d462aec17ab2afce4b50c61 From fb449af7451ec82522f2fcd0e005898707671a66 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 18 Jan 2025 06:28:05 +0000 Subject: [PATCH 28/70] edge color ok, TODO: add/remove logic todo later: json util --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 9e4e90e..f4243b0 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 9e4e90ead54ea95a1d462aec17ab2afce4b50c61 +Subproject commit f4243b00eb854f7b674c196e5150771d37a0f3dc From 1bf04a591c74f2cef8ea77782d21fdee5aa89a7e Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 18 Jan 2025 06:35:01 +0000 Subject: [PATCH 29/70] todo: graphcontrol --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index f4243b0..d20e2ce 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit f4243b00eb854f7b674c196e5150771d37a0f3dc +Subproject commit d20e2ce62a280e806149a37c013002b0c3db4e20 From 78b9433e9b91fc4ab2ee3018ead102ccdf9d8db0 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sun, 19 Jan 2025 12:49:52 +0000 Subject: [PATCH 30/70] todo: cust node/edge del/add logic --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index d20e2ce..4ee6b2d 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit d20e2ce62a280e806149a37c013002b0c3db4e20 +Subproject commit 4ee6b2d17efbf15c68d13f106b07aea3b545080b From 719a0a13d4a24c665275472492dc0914804eeae3 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Mon, 20 Jan 2025 06:20:46 +0000 Subject: [PATCH 31/70] todo: save as file to verify --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 4ee6b2d..e1ebabf 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 4ee6b2d17efbf15c68d13f106b07aea3b545080b +Subproject commit e1ebabf84283dd55bc76af0912b9527f89278cff From cabf9417d46a730f1a8ac011af848066f3a38f73 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Tue, 21 Jan 2025 06:55:01 +0000 Subject: [PATCH 32/70] TODO: to debug save json fail --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index e1ebabf..ed36d4f 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit e1ebabf84283dd55bc76af0912b9527f89278cff +Subproject commit ed36d4f020a342765577b64fb1ac45fa0757ec4e From e7cb774dcb5065b10803a39e3f6432d9c4e687cd Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Tue, 21 Jan 2025 08:04:25 +0000 Subject: [PATCH 33/70] vite test framework ok --- __tests__/main.test.tsx | 13 +++++++++++++ __tests__/setupTests.ts | 1 + package.json | 12 +++++++++--- tsconfig.app.json | 3 +-- vite.config.ts | 10 ++++++++-- 5 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 __tests__/main.test.tsx create mode 100644 __tests__/setupTests.ts diff --git a/__tests__/main.test.tsx b/__tests__/main.test.tsx new file mode 100644 index 0000000..cfd7577 --- /dev/null +++ b/__tests__/main.test.tsx @@ -0,0 +1,13 @@ +// __test__/main.test.tsx + +import { render, screen } from '@testing-library/react'; +import { expect, describe, it } from 'vitest'; + +describe('Basic React rendering', () => { + it('renders Hello, World!', () => { + render(
Hello, World!
); + const helloWorldText = screen.getByText(/Hello, World!/i); + expect(helloWorldText).toBeInTheDocument(); + }); +}); + diff --git a/__tests__/setupTests.ts b/__tests__/setupTests.ts new file mode 100644 index 0000000..c44951a --- /dev/null +++ b/__tests__/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom' diff --git a/package.json b/package.json index b5fcfce..b2e3168 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "dev": "NODE_ENV=DEBUG vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "typecheck": "tsc -p tsconfig.app.json", + "test": "vitest" }, "dependencies": { "@redux-devtools/extension": "^3.3.0", @@ -21,6 +23,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@vitejs/plugin-react": "^4.3.4", @@ -29,10 +33,12 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", + "jsdom": "^26.0.0", "postcss": "^8.4.49", "tailwindcss": "^3.4.17", "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", - "vite": "^6.0.5" + "vite": "^6.0.5", + "vitest": "^3.0.2" } -} +} \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 024594d..a51ac72 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -24,7 +24,6 @@ "exactOptionalPropertyTypes": true, "noImplicitOverride": true }, - "include": ["./**/*.ts", "./**/*.tsx"], + "include": ["src", "__tests__"], "exclude": ["node_modules"] - } \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index f0df571..e80b22c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,6 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +/// +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ @@ -8,4 +9,9 @@ export default defineConfig({ host: '0.0.0.0', port: 3000, }, + test: { + globals: true, + environment: "jsdom", + setupFiles: "./__tests__/setupTests.ts", + }, }) From bdc6a86bb465d27ac790bd49834084869ba281ee Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Tue, 21 Jan 2025 08:24:58 +0000 Subject: [PATCH 34/70] main.test.tsx, todo: run test graph context --- __tests__/main.test.tsx | 13 ------------- __tests__/setupTests.ts | 1 - setupTests.ts | 17 +++++++++++++++++ src/main.test.tsx | 34 ++++++++++++++++++++++++++++++++++ tsconfig.app.json | 2 +- vite.config.ts | 2 +- 6 files changed, 53 insertions(+), 16 deletions(-) delete mode 100644 __tests__/main.test.tsx delete mode 100644 __tests__/setupTests.ts create mode 100644 setupTests.ts create mode 100644 src/main.test.tsx diff --git a/__tests__/main.test.tsx b/__tests__/main.test.tsx deleted file mode 100644 index cfd7577..0000000 --- a/__tests__/main.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -// __test__/main.test.tsx - -import { render, screen } from '@testing-library/react'; -import { expect, describe, it } from 'vitest'; - -describe('Basic React rendering', () => { - it('renders Hello, World!', () => { - render(
Hello, World!
); - const helloWorldText = screen.getByText(/Hello, World!/i); - expect(helloWorldText).toBeInTheDocument(); - }); -}); - diff --git a/__tests__/setupTests.ts b/__tests__/setupTests.ts deleted file mode 100644 index c44951a..0000000 --- a/__tests__/setupTests.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom' diff --git a/setupTests.ts b/setupTests.ts new file mode 100644 index 0000000..e7d9de1 --- /dev/null +++ b/setupTests.ts @@ -0,0 +1,17 @@ +// setupTests.ts + +import "@testing-library/jest-dom"; // important to import this for rendering react components + +if (typeof ResizeObserver === 'undefined') { + global.ResizeObserver = class ResizeObserver { + observe() { + // do nothing + } + unobserve() { + // do nothing + } + disconnect() { + // do nothing + } + }; +} \ No newline at end of file diff --git a/src/main.test.tsx b/src/main.test.tsx new file mode 100644 index 0000000..0417ed5 --- /dev/null +++ b/src/main.test.tsx @@ -0,0 +1,34 @@ +// src/main.test.tsx +import { render, screen } from '@testing-library/react'; +import { expect, describe, it } from 'vitest'; + +import { Provider } from 'react-redux'; +import { store } from "./redux/store"; +import { ReactFlowProvider } from '@xyflow/react'; +import { GraphProvider } from './Graph/GraphContext'; +import { StrictMode } from 'react'; +import App from './App'; + +describe('Application Rendering', () => { + it('renders the main application with providers', () => { + render( +
+ + + + + + + + + +
+ ); + + // Now you can assert that elements from your App or child components are rendered. + // Example: Replace this with your actual test assertion. + // This example assumes you have an element with the text "My App" inside App component or child. + const appElement = screen.getByRole("main"); // assumes you have a main element + expect(appElement).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index a51ac72..47d9ee1 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -24,6 +24,6 @@ "exactOptionalPropertyTypes": true, "noImplicitOverride": true }, - "include": ["src", "__tests__"], + "include": ["src", "setupTests.ts"], "exclude": ["node_modules"] } \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index e80b22c..364f68f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,6 +12,6 @@ export default defineConfig({ test: { globals: true, environment: "jsdom", - setupFiles: "./__tests__/setupTests.ts", + setupFiles: "./setupTests.ts", }, }) From de811234d278cbea6dc980d8836913744aefc162 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Tue, 21 Jan 2025 08:49:55 +0000 Subject: [PATCH 35/70] GraphContext.test ok, todo: test json util --- package.json | 4 ++-- setupTests.ts | 22 +++++++++++----------- src/Graph | 2 +- src/main.test.tsx | 26 +++++++++++++------------- vite.config.ts | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index b2e3168..5b1fa55 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "scripts": { "dev": "NODE_ENV=DEBUG vite", "build": "tsc -b && vite build", - "lint": "eslint .", + "lint": "eslint --fix .", "preview": "vite preview", - "typecheck": "tsc -p tsconfig.app.json", + "tsc": "tsc -p tsconfig.app.json", "test": "vitest" }, "dependencies": { diff --git a/setupTests.ts b/setupTests.ts index e7d9de1..2eee530 100644 --- a/setupTests.ts +++ b/setupTests.ts @@ -3,15 +3,15 @@ import "@testing-library/jest-dom"; // important to import this for rendering react components if (typeof ResizeObserver === 'undefined') { - global.ResizeObserver = class ResizeObserver { - observe() { - // do nothing - } - unobserve() { - // do nothing - } - disconnect() { - // do nothing - } - }; + global.ResizeObserver = class ResizeObserver { + observe() { + // do nothing + } + unobserve() { + // do nothing + } + disconnect() { + // do nothing + } + }; } \ No newline at end of file diff --git a/src/Graph b/src/Graph index ed36d4f..0daa60c 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit ed36d4f020a342765577b64fb1ac45fa0757ec4e +Subproject commit 0daa60c0ef1def043b8835a7edc9a7cf749f9527 diff --git a/src/main.test.tsx b/src/main.test.tsx index 0417ed5..01572ed 100644 --- a/src/main.test.tsx +++ b/src/main.test.tsx @@ -12,23 +12,23 @@ import App from './App'; describe('Application Rendering', () => { it('renders the main application with providers', () => { render( -
- - - - - - - - - -
+
+ + + + + + + + + +
); // Now you can assert that elements from your App or child components are rendered. // Example: Replace this with your actual test assertion. // This example assumes you have an element with the text "My App" inside App component or child. - const appElement = screen.getByRole("main"); // assumes you have a main element - expect(appElement).toBeInTheDocument(); + const appElement = screen.getByRole("main"); + expect(appElement).toBeInTheDocument(); }); }); \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 364f68f..b408756 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,5 +13,5 @@ export default defineConfig({ globals: true, environment: "jsdom", setupFiles: "./setupTests.ts", - }, + }, }) From a8430b504b7b0c81a08393d9edd1d8f6785498af Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Tue, 21 Jan 2025 14:31:20 +0000 Subject: [PATCH 36/70] TODO: node to json test --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 0daa60c..efc5567 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 0daa60c0ef1def043b8835a7edc9a7cf749f9527 +Subproject commit efc5567a81ffc1c0dada4fc9b70a45a74ec70ac5 From befc721c6039a5dd1c5d1edf6473a69226337a1f Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Tue, 21 Jan 2025 15:27:15 +0000 Subject: [PATCH 37/70] todo: json util --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index efc5567..969ab70 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit efc5567a81ffc1c0dada4fc9b70a45a74ec70ac5 +Subproject commit 969ab703a86e70b4ab2651911c4cc3e80e6a9edf From 7495e0219597cfb148fcb1d81b995b69ee205402 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Tue, 21 Jan 2025 17:04:48 +0000 Subject: [PATCH 38/70] good node to json, next, graphs to json --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index 969ab70..4ae7c5e 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 969ab703a86e70b4ab2651911c4cc3e80e6a9edf +Subproject commit 4ae7c5efe1126d0bf373013c67f04c286f198ef0 From 22ec2b70beb9331dfb713c8febb29f53635873a1 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Wed, 22 Jan 2025 06:08:41 +0000 Subject: [PATCH 39/70] todo: refractor saving --- src/Graph | 2 +- src/utils/JsonIO.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Graph b/src/Graph index 4ae7c5e..5b86760 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 4ae7c5efe1126d0bf373013c67f04c286f198ef0 +Subproject commit 5b867609de80ceb8a837cc60e2da162aec2fcf23 diff --git a/src/utils/JsonIO.ts b/src/utils/JsonIO.ts index a4a5e9f..e701d04 100644 --- a/src/utils/JsonIO.ts +++ b/src/utils/JsonIO.ts @@ -13,7 +13,7 @@ export const saveJsonToFile = (JsonData: any): void => { a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); - alert('File saved!'); + alert('File saving!'); } catch (error) { console.error('Error saving JSON:', error); alert('Failed to save file.'); From 664da9391d3b7a5e397b4d8c573b5a2c7672cfe4 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Wed, 22 Jan 2025 06:20:53 +0000 Subject: [PATCH 40/70] save subgraph good, todo load graph/subgraph --- src/Graph | 2 +- src/utils/JsonIO.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Graph b/src/Graph index 5b86760..a42bbf7 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 5b867609de80ceb8a837cc60e2da162aec2fcf23 +Subproject commit a42bbf7c2b06259a31d65e276bce5085516e75e7 diff --git a/src/utils/JsonIO.ts b/src/utils/JsonIO.ts index e701d04..52bf95a 100644 --- a/src/utils/JsonIO.ts +++ b/src/utils/JsonIO.ts @@ -1,6 +1,6 @@ // utils/JsonIO.ts -export const saveJsonToFile = (JsonData: any): void => { +export const saveJsonToFile = (filename: string, JsonData: any): void => { try { const blob = new Blob([JSON.stringify(JsonData, null, 2)], { type: 'application/json', @@ -8,7 +8,7 @@ export const saveJsonToFile = (JsonData: any): void => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = 'Save.json'; + a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); @@ -19,7 +19,7 @@ export const saveJsonToFile = (JsonData: any): void => { alert('Failed to save file.'); } }; - + export const loadJsonFromFile = (): Promise => { return new Promise((resolve, reject) => { const fileInput = document.createElement('input'); @@ -27,7 +27,7 @@ export const loadJsonFromFile = (): Promise => { fileInput.accept = '.json'; fileInput.style.display = 'none'; document.body.appendChild(fileInput); - + fileInput.addEventListener('change', async (event) => { try { const file = (event.target as HTMLInputElement).files?.[0]; @@ -45,7 +45,7 @@ export const loadJsonFromFile = (): Promise => { } else { reject(new Error('File contents are not a string.')); } - + } catch (error) { reject(new Error('Error parsing JSON.' + error)); } From a1e7fb3030434a72ec54d451d1c750a5031f50b5 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Wed, 22 Jan 2025 07:46:34 +0000 Subject: [PATCH 41/70] repo merge update --- .gitmodules | 2 +- README.md | 25 +++++++------------------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/.gitmodules b/.gitmodules index dd05226..0d75613 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "src/Graph"] path = src/Graph - url = ../reactflow-ts + url = ../reactflow diff --git a/README.md b/README.md index 14702c7..0e9e758 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,18 @@ # reactflow-ts TODO list: -* use redux as SSOT * porting ```src/Graph``` to ts * hold at ```yourdomain.com``` ## Run -compile - -``` -tsc -p tsconfig.app.json -``` - -lint -``` -npx eslint --fix . -``` - -hold -``` -npm run dev -``` - +* compile + * ```npm run tsc``` +* lint + * ```npm run lint``` +* hold + * ```npm run dev``` ## Debug Redux @@ -36,5 +25,5 @@ at ```package.json``` in browser, F12 and type ``` -window.store.getState().subGraphs +window.store ``` From f4c48f074e2344d715744e71b217fa5b68f01aeb Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Thu, 23 Jan 2025 11:57:50 +0000 Subject: [PATCH 42/70] s/l graph flow ok, TODO: edge --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index a42bbf7..6dad7e9 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit a42bbf7c2b06259a31d65e276bce5085516e75e7 +Subproject commit 6dad7e964deb85cac0b0dc904a2f5d7fc0887609 From fe0da8918e51bffc1549c4fbd9c9435499c4d1e2 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Thu, 23 Jan 2025 12:30:11 +0000 Subject: [PATCH 43/70] todo: debug wrong edge logic and test case fail at edge --- README.md | 17 +++-------------- src/Graph | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0e9e758..d45c291 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ TODO list: * porting ```src/Graph``` to ts + * finally, to fix edge * hold at ```yourdomain.com``` @@ -13,17 +14,5 @@ TODO list: * ```npm run lint``` * hold * ```npm run dev``` - - -## Debug Redux - -at ```package.json``` -``` -"dev": "NODE_ENV=DEBUG vite", -``` - -in browser, F12 and type - -``` -window.store -``` +* vitest + * ```npm run test``` diff --git a/src/Graph b/src/Graph index 6dad7e9..d11029c 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 6dad7e964deb85cac0b0dc904a2f5d7fc0887609 +Subproject commit d11029c09c4806af7bfab83d54b8fa4e3bb56a79 From ad7e0bd64474c80977f03e1b6a93343c25cd3f0c Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Thu, 23 Jan 2025 17:49:21 +0000 Subject: [PATCH 44/70] condition edge ok, todo: only one and delete logic --- src/Graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graph b/src/Graph index d11029c..1d7be89 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit d11029c09c4806af7bfab83d54b8fa4e3bb56a79 +Subproject commit 1d7be89b5e957b7d04ed129401e2cc964436d425 From 2b3d03a5ad35e4f71755231704947911888f4eba Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Fri, 24 Jan 2025 15:29:16 +0000 Subject: [PATCH 45/70] todo: fix edge test fail and fix layout window size --- src/Graph | 2 +- src/components/MainLayout.tsx | 49 +++++++++++++++++++++++++++++++++++ src/config.ts | 6 +++++ src/routes/index.tsx | 9 ++++--- 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 src/components/MainLayout.tsx create mode 100644 src/config.ts diff --git a/src/Graph b/src/Graph index 1d7be89..838f47a 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 1d7be89b5e957b7d04ed129401e2cc964436d425 +Subproject commit 838f47ac092f09b73bcb2414ed43c93764b504eb diff --git a/src/components/MainLayout.tsx b/src/components/MainLayout.tsx new file mode 100644 index 0000000..36af8b1 --- /dev/null +++ b/src/components/MainLayout.tsx @@ -0,0 +1,49 @@ +// components/MainLayout.tsx + +import React, { useState, useRef } from "react"; +import { Outlet, useLocation } from 'react-router-dom'; + +const MainLayout: React.FC = () => { + const location = useLocation(); + const [menuOpen, setMenuOpen] = useState(false); + const menuRef = useRef(null); + + const toggleMenu = () => { + setMenuOpen(!menuOpen); + }; + + const shouldShowSidebar = location.pathname === '/' || location.pathname === '/about'; + + return ( +
+ {shouldShowSidebar && ( + + )} +
+ +

Steps:

+ {/* Placeholder for steps list. Remove or add back later as needed */} +
    +
  • Step 1
  • +
  • Step 2
  • +
  • Step 3
  • +
+
+
+ +
+ +
+
+
+ ); +}; + +export default MainLayout; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..6bb3d7e --- /dev/null +++ b/src/config.ts @@ -0,0 +1,6 @@ +// config.ts + +// Update this with your server's IP and port +const SERVER_URL: string = 'http://localhost:5000'; + +export default SERVER_URL; \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 1f99e8a..7e7b1af 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import GraphApp from '../Graph/GraphApp'; - +import MainLayout from '../components/MainLayout'; // Example Components const HomePage = () =>

Home Page

; @@ -15,9 +15,12 @@ const AppRoutes: React.FC = () => { return ( - } /> - } /> + + }> + } /> + } /> + } /> } /> {/* Catch-all for 404 */} From af2555238c24c475841174a3accf5cf201e40ab9 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Fri, 24 Jan 2025 16:55:53 +0000 Subject: [PATCH 46/70] todo: menu --- src/Graph | 2 +- src/components/MainLayout.tsx | 49 ----------------------------- src/components/MenuLayout.tsx | 47 +++++++++++++++++++++++++++ src/components/MenuToggleButton.tsx | 37 ++++++++++++++++++++++ src/routes/index.tsx | 4 +-- 5 files changed, 87 insertions(+), 52 deletions(-) delete mode 100644 src/components/MainLayout.tsx create mode 100644 src/components/MenuLayout.tsx create mode 100644 src/components/MenuToggleButton.tsx diff --git a/src/Graph b/src/Graph index 838f47a..32158da 160000 --- a/src/Graph +++ b/src/Graph @@ -1 +1 @@ -Subproject commit 838f47ac092f09b73bcb2414ed43c93764b504eb +Subproject commit 32158dae9dab0ec93d917e5ee8a6b047972c5c3d diff --git a/src/components/MainLayout.tsx b/src/components/MainLayout.tsx deleted file mode 100644 index 36af8b1..0000000 --- a/src/components/MainLayout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// components/MainLayout.tsx - -import React, { useState, useRef } from "react"; -import { Outlet, useLocation } from 'react-router-dom'; - -const MainLayout: React.FC = () => { - const location = useLocation(); - const [menuOpen, setMenuOpen] = useState(false); - const menuRef = useRef(null); - - const toggleMenu = () => { - setMenuOpen(!menuOpen); - }; - - const shouldShowSidebar = location.pathname === '/' || location.pathname === '/about'; - - return ( -
- {shouldShowSidebar && ( - - )} -
- -

Steps:

- {/* Placeholder for steps list. Remove or add back later as needed */} -
    -
  • Step 1
  • -
  • Step 2
  • -
  • Step 3
  • -
-
-
- -
- -
-
-
- ); -}; - -export default MainLayout; \ No newline at end of file diff --git a/src/components/MenuLayout.tsx b/src/components/MenuLayout.tsx new file mode 100644 index 0000000..7da7419 --- /dev/null +++ b/src/components/MenuLayout.tsx @@ -0,0 +1,47 @@ +// components/MenuLayout.tsx + +import React, { useState, useRef } from "react"; +import { Outlet } from 'react-router-dom'; +import MenuToggleButton from './MenuToggleButton'; + +const MenuLayout: React.FC = () => { + const [menuOpen, setMenuOpen] = useState(false); + const menuRef = useRef(null); + + const toggleMenu = () => { + setMenuOpen(!menuOpen); + }; + const closeMenu = () => { + setMenuOpen(false); + }; + + + return ( +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ ); +}; + +export default MenuLayout; \ No newline at end of file diff --git a/src/components/MenuToggleButton.tsx b/src/components/MenuToggleButton.tsx new file mode 100644 index 0000000..f423ca0 --- /dev/null +++ b/src/components/MenuToggleButton.tsx @@ -0,0 +1,37 @@ +// components/MenuToggleButton.tsx + +import React from 'react'; + +const MenuToggleButton: React.FC = () => { + + const handleUploadClick = () => { + console.log('upload file'); + // Handle upload logic here (specific to this component) + }; + + const handleDownloadClick = () => { + console.log('get files'); + // Handle download logic here (specific to this component) + }; + + const handleCleanCacheClick = () => { + console.log('clean cache'); + // Handle cache clean logic here (specific to this component) + }; + + return ( +
+ + + +
+ ); +}; + +export default MenuToggleButton; \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 7e7b1af..215926a 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import GraphApp from '../Graph/GraphApp'; -import MainLayout from '../components/MainLayout'; +import MenuLayout from '../components/MenuLayout'; // Example Components const HomePage = () =>

Home Page

; @@ -16,7 +16,7 @@ const AppRoutes: React.FC = () => { - }> + }> } /> } /> From 07fd711353db3fb664ea71377c40850a95cc2e33 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Fri, 24 Jan 2025 17:14:46 +0000 Subject: [PATCH 47/70] TODO: run graph logic --- src/components/MenuToggleButton.tsx | 43 +++++-- src/components/RunWindow.tsx | 184 ++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 src/components/RunWindow.tsx diff --git a/src/components/MenuToggleButton.tsx b/src/components/MenuToggleButton.tsx index f423ca0..04f6e7e 100644 --- a/src/components/MenuToggleButton.tsx +++ b/src/components/MenuToggleButton.tsx @@ -1,35 +1,64 @@ // components/MenuToggleButton.tsx -import React from 'react'; +import React, { useState } from 'react'; +import RunWindow from './RunWindow'; +import ConfigManager from '../ConfigManager'; const MenuToggleButton: React.FC = () => { + const [isRunWindowOpen, setIsRunWindowOpen] = useState(false); + const { username } = ConfigManager.getSettings(); const handleUploadClick = () => { console.log('upload file'); - // Handle upload logic here (specific to this component) + // Handle upload logic here (specific to this component) }; const handleDownloadClick = () => { console.log('get files'); - // Handle download logic here (specific to this component) + // Handle download logic here (specific to this component) }; const handleCleanCacheClick = () => { console.log('clean cache'); - // Handle cache clean logic here (specific to this component) + // Handle cache clean logic here (specific to this component) }; + const handleRunClick = () => { + console.log('run'); + setIsRunWindowOpen(true); + // Handle run logic here (open the RunWindow) + }; + + const closeRunWindow = () => { + setIsRunWindowOpen(false); + }; + + // Check if username is valid + const isUsernameValid = username && username.length > 0; + + return (
+
+ {isUsernameValid ? `User: ${username}` : 'User: undefined'} +
+ + + + {isRunWindowOpen && }
); }; diff --git a/src/components/RunWindow.tsx b/src/components/RunWindow.tsx new file mode 100644 index 0000000..0b3b4d3 --- /dev/null +++ b/src/components/RunWindow.tsx @@ -0,0 +1,184 @@ +// components/RunWindow.tsx + +import { useState, useEffect, useRef } from 'react'; +import SERVER_URL from '../config'; +import { useGraph } from '../Graph/GraphContext'; +import { allSubGraphsToJson } from '../Graph/JsonUtil'; +import ConfigManager from '../ConfigManager'; + +interface RunWindowProps { + onClose: () => void; +} + + +function RunWindow({ onClose }: RunWindowProps) { + const [responseMessage, setResponseMessage] = useState(''); + const [isRunning, setIsRunning] = useState(false); + const { username, llmModel, apiKey } = ConfigManager.getSettings(); + const { subGraphs } = useGraph(); + const isPollingRef = useRef(false); + + + + const saveGraphData = async () => { + try { + const flowData = allSubGraphsToJson(subGraphs); + if (!username) { + throw new Error("Username not available to save graph data."); + } + const response = await fetch(`${SERVER_URL}/save-graph/${encodeURIComponent(username)}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(flowData), + }); + + + if (!response.ok) { + throw new Error('Failed to save graph data on the server.'); + } + + + console.log('Graph data successfully saved to server.\n'); + setResponseMessage(prev => prev + '\nGraph data successfully saved to server.\n'); + } catch (error: unknown) { + let errorMessage = "An unknown error occurred"; + if (error instanceof Error) { + errorMessage = error.message; + } + console.error('Error saving graph data:', errorMessage); + setResponseMessage(prev => prev + '\nError saving graph data: ' + errorMessage); + throw error; + } + }; + + + const handleRun = async () => { + if (isRunning) return; + setIsRunning(true); + setResponseMessage(''); + + + try { + await saveGraphData(); + console.log("Attempting to send request to Flask server..."); + + if (!username) { + throw new Error("Username not available to run."); + } + + const response = await fetch(`${SERVER_URL}/run/${encodeURIComponent(username)}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: username, + llm_model: llmModel, + api_key: apiKey, + }), + }); + + + if (!response.body) { + throw new Error('ReadableStream not yet supported in this browser.'); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let done = false; + + + while (!done) { + const { value, done: streamDone } = await reader.read(); + done = streamDone; + if (value) { + const chunk = decoder.decode(value, { stream: !done }); + console.log("Received chunk:", chunk); + try{ + const parsed = JSON.parse(chunk.replace("data: ", "").trim()); + if (parsed.status){ + setIsRunning(false) + } + }catch(e){ + + } + setResponseMessage(prev => prev + chunk); + } + } + } catch (error: unknown) { + let errorMessage = "An unknown error occurred"; + if (error instanceof Error) { + errorMessage = error.message; + } + console.error('Error:', errorMessage); + setResponseMessage(prev => prev + '\nError: ' + errorMessage); + alert('Error: ' + errorMessage); + setIsRunning(false); + } finally { + if(isPollingRef.current){ + setIsRunning(false); + } + } + }; + + + useEffect(() => { + isPollingRef.current = true; + const checkStatus = async () => { + try { + if (!username) { + throw new Error("Username not available to check status."); + } + + const response = await fetch(`${SERVER_URL}/status/${encodeURIComponent(username)}`, { + method: 'GET', + }); + const status = await response.json(); + setIsRunning(status.running); + } catch (error) { + console.error('Error checking status:', error); + } + }; + const interval = setInterval(checkStatus, 2000); + + + return () => { + isPollingRef.current = false; + clearInterval(interval); + }; + }, [username]); + + + const handleLeave = async () => { + onClose(); + }; + + + return ( +
+
+

Run Script

+
+ + +
+
+
{responseMessage}
+
+
+
+ ); +} + + +export default RunWindow; \ No newline at end of file From 26074f06e52df8468d172c64886767b823572caf Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Fri, 24 Jan 2025 17:36:34 +0000 Subject: [PATCH 48/70] todo: file transmit --- src/components/MenuLayout.tsx | 19 +++++++++++++++---- src/components/MenuToggleButton.tsx | 21 +++++++++++---------- src/components/RunWindow.tsx | 2 +- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/components/MenuLayout.tsx b/src/components/MenuLayout.tsx index 7da7419..3c3b0e5 100644 --- a/src/components/MenuLayout.tsx +++ b/src/components/MenuLayout.tsx @@ -3,9 +3,11 @@ import React, { useState, useRef } from "react"; import { Outlet } from 'react-router-dom'; import MenuToggleButton from './MenuToggleButton'; +import RunWindow from './RunWindow'; const MenuLayout: React.FC = () => { const [menuOpen, setMenuOpen] = useState(false); + const [isRunWindowOpen, setIsRunWindowOpen] = useState(false); const menuRef = useRef(null); const toggleMenu = () => { @@ -14,6 +16,14 @@ const MenuLayout: React.FC = () => { const closeMenu = () => { setMenuOpen(false); }; + const openRunWindow = () => { + setIsRunWindowOpen(true); + }; + + const closeRunWindow = () => { + setIsRunWindowOpen(false); + }; + return ( @@ -21,25 +31,26 @@ const MenuLayout: React.FC = () => { -
+
- +
+ {isRunWindowOpen && }
); }; diff --git a/src/components/MenuToggleButton.tsx b/src/components/MenuToggleButton.tsx index 04f6e7e..d148105 100644 --- a/src/components/MenuToggleButton.tsx +++ b/src/components/MenuToggleButton.tsx @@ -1,13 +1,17 @@ // components/MenuToggleButton.tsx -import React, { useState } from 'react'; -import RunWindow from './RunWindow'; +import React from 'react'; import ConfigManager from '../ConfigManager'; -const MenuToggleButton: React.FC = () => { - const [isRunWindowOpen, setIsRunWindowOpen] = useState(false); +interface MenuToggleButtonProps { + openRunWindow: () => void; +} + +const MenuToggleButton: React.FC = ({ openRunWindow }) => { const { username } = ConfigManager.getSettings(); + + const handleUploadClick = () => { console.log('upload file'); // Handle upload logic here (specific to this component) @@ -25,13 +29,11 @@ const MenuToggleButton: React.FC = () => { const handleRunClick = () => { console.log('run'); - setIsRunWindowOpen(true); + openRunWindow(); // Handle run logic here (open the RunWindow) }; - const closeRunWindow = () => { - setIsRunWindowOpen(false); - }; + // Check if username is valid const isUsernameValid = username && username.length > 0; @@ -56,9 +58,8 @@ const MenuToggleButton: React.FC = () => { Clean Server Cache - {isRunWindowOpen && } ); }; diff --git a/src/components/RunWindow.tsx b/src/components/RunWindow.tsx index 0b3b4d3..1b7c146 100644 --- a/src/components/RunWindow.tsx +++ b/src/components/RunWindow.tsx @@ -156,7 +156,7 @@ function RunWindow({ onClose }: RunWindowProps) {

Run Script

-
+
{/* Changed here */}
- {isRunWindowOpen && } + {isRunWindowOpen && }
); }; diff --git a/src/components/MenuToggleButton.tsx b/src/components/MenuToggleButton.tsx index d148105..d6b7358 100644 --- a/src/components/MenuToggleButton.tsx +++ b/src/components/MenuToggleButton.tsx @@ -1,7 +1,9 @@ // components/MenuToggleButton.tsx -import React from 'react'; +import React, { useRef } from 'react'; import ConfigManager from '../ConfigManager'; +import { handleUpload, handleDownload, handleCleanCache } from './FileTransmit'; + interface MenuToggleButtonProps { openRunWindow: () => void; @@ -9,33 +11,15 @@ interface MenuToggleButtonProps { const MenuToggleButton: React.FC = ({ openRunWindow }) => { const { username } = ConfigManager.getSettings(); + const fileInputRef = useRef(null); - - const handleUploadClick = () => { - console.log('upload file'); - // Handle upload logic here (specific to this component) - }; - - const handleDownloadClick = () => { - console.log('get files'); - // Handle download logic here (specific to this component) - }; - - const handleCleanCacheClick = () => { - console.log('clean cache'); - // Handle cache clean logic here (specific to this component) - }; - const handleRunClick = () => { console.log('run'); openRunWindow(); // Handle run logic here (open the RunWindow) }; - - - // Check if username is valid const isUsernameValid = username && username.length > 0; @@ -47,14 +31,35 @@ const MenuToggleButton: React.FC = ({ openRunWindow }) => {isUsernameValid ? `User: ${username}` : 'User: undefined'}
- - - - + + + + + ); +} + +export default ConfigWindow; \ No newline at end of file diff --git a/src/GraphMenu/MenuLayout.tsx b/src/GraphMenu/MenuLayout.tsx index c6df499..d8b65cb 100644 --- a/src/GraphMenu/MenuLayout.tsx +++ b/src/GraphMenu/MenuLayout.tsx @@ -1,13 +1,15 @@ // GraphMenu/MenuLayout.tsx - import React, { useState, useRef } from "react"; import { Outlet } from 'react-router-dom'; import MenuToggleButton from './MenuToggleButton'; import RunWindow from './RunWindow'; +import ConfigWindow from './ConfigWindow'; // Import ConfigWindow + const MenuLayout: React.FC = () => { const [menuOpen, setMenuOpen] = useState(false); const [isRunWindowOpen, setIsRunWindowOpen] = useState(false); + const [isConfigWindowOpen, setIsConfigWindowOpen] = useState(false); // Config window state const menuRef = useRef(null); const toggleMenu = () => { @@ -19,12 +21,17 @@ const MenuLayout: React.FC = () => { const openRunWindow = () => { setIsRunWindowOpen(true); }; - const closeRunWindow = () => { setIsRunWindowOpen(false); }; + const openConfigWindow = () => { // Open config window + setIsConfigWindowOpen(true); + }; + const closeConfigWindow = () => { // Close config window + setIsConfigWindowOpen(false); + }; return (
@@ -37,7 +44,7 @@ const MenuLayout: React.FC = () => { «
- + {/* Pass openConfigWindow */}
@@ -51,6 +58,7 @@ const MenuLayout: React.FC = () => { {isRunWindowOpen && } + {isConfigWindowOpen && } {/* Render ConfigWindow */} ); }; diff --git a/src/GraphMenu/MenuToggleButton.tsx b/src/GraphMenu/MenuToggleButton.tsx index 3dfefb3..3eea2a7 100644 --- a/src/GraphMenu/MenuToggleButton.tsx +++ b/src/GraphMenu/MenuToggleButton.tsx @@ -7,9 +7,10 @@ import { handleUpload, handleDownload, handleCleanCache } from './FileTransmit'; interface MenuToggleButtonProps { openRunWindow: () => void; + openConfigWindow: () => void; // Add this prop } -const MenuToggleButton: React.FC = ({ openRunWindow }) => { +const MenuToggleButton: React.FC = ({ openRunWindow, openConfigWindow }) => { // Update destructuring const { username } = ConfigManager.getSettings(); const fileInputRef = useRef(null); @@ -20,6 +21,10 @@ const MenuToggleButton: React.FC = ({ openRunWindow }) => // Handle run logic here (open the RunWindow) }; + const handleConfigClick = () => { + openConfigWindow(); + } + const UsernameValid = username === 'unknown'; @@ -44,6 +49,9 @@ const MenuToggleButton: React.FC = ({ openRunWindow }) => }} /> + - + ); }; From 6168a8403868f577e3e945e42f5b910f0e4b4f06 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 25 Jan 2025 19:58:38 +0800 Subject: [PATCH 55/70] update --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 0d75613..c3a0625 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "src/Graph"] path = src/Graph - url = ../reactflow + url = ../LangGraph-GUI-reactflow From d612cf911c3c25750256127f5ce1813f27b20faa Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 25 Jan 2025 20:12:15 +0800 Subject: [PATCH 56/70] update to repo name --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index e4b78ea..4c6d616 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + LangGraph-GUI
From b4afa628a03085b55caab9fb3c92aec750e87166 Mon Sep 17 00:00:00 2001 From: Homun Mage Date: Sat, 25 Jan 2025 20:57:05 +0800 Subject: [PATCH 57/70] doc --- src/Doc/DocPage.tsx | 15 +++++++++++++++ src/GraphMenu/MenuToggleButton.tsx | 18 ++++++++++++------ src/routes/AppRoutes.tsx | 20 ++++++++++++-------- 3 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 src/Doc/DocPage.tsx diff --git a/src/Doc/DocPage.tsx b/src/Doc/DocPage.tsx new file mode 100644 index 0000000..6f4bd7d --- /dev/null +++ b/src/Doc/DocPage.tsx @@ -0,0 +1,15 @@ +// Graph/Doc.tsx + +import React from 'react'; + +const DocPage: React.FC = () => { + return ( +