mirror of
https://github.com/langchain-ai/langgraph-builder.git
synced 2026-07-01 19:55:58 -04:00
first commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
out
|
||||
*.css
|
||||
.*.js
|
||||
*.ts
|
||||
*.tsx
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": ["next", "next/core-web-vitals", "airbnb", "airbnb-typescript", "plugin:sonarjs/recommended", "prettier"],
|
||||
"plugins": ["@typescript-eslint", "jest", "unused-imports"],
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"browser": true,
|
||||
"jest": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"react/require-default-props": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"react/prop-types": "off",
|
||||
"no-multi-assign": "off",
|
||||
"import/imports-first": ["error", "absolute-first"],
|
||||
"react/function-component-definition": [
|
||||
1,
|
||||
{
|
||||
"namedComponents": "arrow-function",
|
||||
"unnamedComponents": "arrow-function"
|
||||
}
|
||||
],
|
||||
"react/jsx-filename-extension": [
|
||||
1,
|
||||
{
|
||||
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
||||
}
|
||||
],
|
||||
// Multiline indentation: https://stackoverflow.com/a/48906878
|
||||
"indent": ["error", 2],
|
||||
"react/jsx-indent": ["error", 2],
|
||||
"react/jsx-indent-props": ["error", 2],
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"semi": ["error", "never"],
|
||||
"constructor-super": "error",
|
||||
"no-invalid-this": "error",
|
||||
"no-restricted-syntax": ["error", "ForInStatement"],
|
||||
"use-isnan": "error",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "warn",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-use-before-define": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"@typescript-eslint/no-unnecessary-condition": [
|
||||
"error",
|
||||
{
|
||||
"allowConstantLoopConditions": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx"]
|
||||
},
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
},
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"parserOptions": {
|
||||
// Allows for the parsing of modern ECMAScript features
|
||||
"ecmaVersion": 2021,
|
||||
// Allows for the use of imports
|
||||
"sourceType": "module",
|
||||
// https://blog.geographer.fr/eslint-parser-services, https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
.next
|
||||
.vscode
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/dist
|
||||
/build
|
||||
/out
|
||||
|
||||
#cache
|
||||
/.cache
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.Spotlight-V100
|
||||
Thumbs.db
|
||||
|
||||
# Package
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Environment Variables
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Typescript
|
||||
tsconfig.tsbuildinfo
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no -- commitlint --edit "${1}"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run test
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Only run commitizen if no commit message was already provided: https://github.com/commitizen/cz-cli/issues/844#issuecomment-1035862033
|
||||
if [ -z "${2-}" ]; then
|
||||
export CZ_TYPE="${CZ_TYPE:-fix}"
|
||||
export CZ_MAX_HEADER_WIDTH=$COMMITLINT_MAX_WIDTH
|
||||
export CZ_MAX_LINE_WIDTH=$CZ_MAX_HEADER_WIDTH
|
||||
# By default git hooks are not interactive. exec < /dev/tty allows a users terminal to interact with commitizen.
|
||||
exec < /dev/tty && npx cz --hook && npx devmoji -e || true
|
||||
else
|
||||
npx devmoji -e || true
|
||||
fi
|
||||
@@ -0,0 +1,10 @@
|
||||
const path = require('path')
|
||||
|
||||
// With `next lint`: https://nextjs.org/docs/basic-features/eslint#lint-staged
|
||||
const buildEslintCommand = (filenames) =>
|
||||
`yarn lint:fix --file ${filenames.map((f) => path.relative(process.cwd(), f)).join(' --file ')}`
|
||||
|
||||
module.exports = {
|
||||
'*.{js,jsx,ts,tsx}': [buildEslintCommand],
|
||||
'*.{ts,tsx}': "bash -c 'npm run typecheck'", // running this via bash https://github.com/okonet/lint-staged/issues/825#issuecomment-727185296
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"noSemi": true,
|
||||
"semi": false,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 120,
|
||||
"importOrderSeparation": false,
|
||||
"importOrder": [
|
||||
"^react",
|
||||
"^next",
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"",
|
||||
"^@/(.*)$",
|
||||
"",
|
||||
"^~/(.*)$",
|
||||
"",
|
||||
"^[./]"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Abhishek Bhardwaj
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const nextJest = require('next/jest')
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
||||
dir: './',
|
||||
})
|
||||
|
||||
// Add any custom config to be passed to Jest
|
||||
/** @type {import('jest').Config} */
|
||||
const customJestConfig = {
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
|
||||
|
||||
// if using TypeScript with a baseUrl set to the root directory then you need the snippet below for alias' to work
|
||||
moduleDirectories: ['node_modules', '<rootDir>/'],
|
||||
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
testMatch: ['**/*.(test|spec).(js|jsx|ts|tsx)'],
|
||||
coveragePathIgnorePatterns: ['/node_modules/'],
|
||||
/**
|
||||
* Absolute imports and module path aliases
|
||||
*/
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^~/(.*)$': '<rootDir>/public/$1',
|
||||
},
|
||||
}
|
||||
|
||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
|
||||
module.exports = createJestConfig(customJestConfig)
|
||||
@@ -0,0 +1,5 @@
|
||||
import '@testing-library/jest-dom/jest-globals'
|
||||
|
||||
// Allow router mocks.
|
||||
// eslint-disable-next-line no-undef, global-require
|
||||
jest.mock('next/router', () => require('next-router-mock'))
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
@@ -0,0 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
module.exports = {
|
||||
poweredByHeader: false,
|
||||
generateEtags: false,
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
}
|
||||
Generated
+16923
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "next-boilerplate-typescript-jest",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "yarpm run build && next export",
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"update-all-dependencies": "npx npm-check-updates -u",
|
||||
"test:coverage": "yarpm run test --coverage",
|
||||
"test:debug": "yarpm run test --debug --detectOpenHandles",
|
||||
"test:ci": "yarpm run test --runInBand --no-cache --ci",
|
||||
"test": "NODE_ENV=test jest --passWithNoTests",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rimraf out/",
|
||||
"prepare": "is-ci || husky install",
|
||||
"commit": "cz"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/speed-insights": "^1.0.10",
|
||||
"@xyflow/react": "^12.2.0",
|
||||
"clsx": "^2.0.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"is-ci": "^3.0.1",
|
||||
"jszip": "^3.10.1",
|
||||
"next": "^13.4.11",
|
||||
"openai": "^4.57.0",
|
||||
"posthog-js": "^1.120.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.7.1",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.1.0",
|
||||
"@testing-library/jest-dom": "^6.1.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/node": "^20.5.7",
|
||||
"@types/react": "^18.2.21",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
||||
"@typescript-eslint/parser": "^6.5.0",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"commitizen": "^4.3.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"devmoji": "^2.3.0",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-next": "^13.4.19",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-import-resolver-typescript": "^3.6.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jest": "^27.2.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-sonarjs": "^0.21.0",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.6.4",
|
||||
"jest-environment-jsdom": "^29.6.4",
|
||||
"lint-staged": "^14.0.1",
|
||||
"next-router-mock": "^0.9.9",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.28",
|
||||
"prettier": "^3.0.2",
|
||||
"prettier-plugin-tailwindcss": "^0.5.3",
|
||||
"rimraf": "^5.0.1",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.2.2",
|
||||
"yarpm": "^1.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
cssnano: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"nodes": [],
|
||||
"edges": []
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
from langchain.llms import OpenAI
|
||||
|
||||
class LLMManager:
|
||||
def __init__(self):
|
||||
self.llm = OpenAI()
|
||||
|
||||
def generate_response(self, prompt):
|
||||
return self.llm(prompt)
|
||||
@@ -0,0 +1,11 @@
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.graph import END
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
# Function definitions will be generated based on the nodes
|
||||
# Conditional functions will be generated based on the animated edges
|
||||
# State class will be defined
|
||||
# create_workflow function will be implemented
|
||||
# Utility functions like returnGraph and run_sql_agent will be included
|
||||
|
||||
# The actual content will be generated when the user clicks the "Generate Code" button
|
||||
@@ -0,0 +1,18 @@
|
||||
from WorkFlow import create_workflow, run_sql_agent
|
||||
|
||||
def main():
|
||||
# Initialize the workflow
|
||||
workflow = create_workflow()
|
||||
|
||||
# Example input
|
||||
input_data = {
|
||||
# Add your input data here
|
||||
}
|
||||
|
||||
# Run the workflow
|
||||
result = run_sql_agent(input_data)
|
||||
|
||||
print("Workflow result:", result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,4 @@
|
||||
langgraph
|
||||
langchain
|
||||
openai
|
||||
python-dotenv
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
@@ -0,0 +1,51 @@
|
||||
# tailwind-react-next.js-typescript-eslint-jest-starter
|
||||
|
||||
Starter template for building a project using React, Typescript, Next.js, Jest, TailwindCSS and ESLint.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
1. Clone or download the project.
|
||||
2. `cd` in the project directory.
|
||||
3. If you cloned the project, make sure you remove the remote reference to this project by running `git remote rm origin`.
|
||||
4. Copy `.env.example` to `.env` as that file is used to load up all your environment variables.
|
||||
4. Run `yarn install` or `npm install` to install all dependencies.
|
||||
|
||||
## Commands
|
||||
|
||||
- `yarn dev`: To start a local development server.
|
||||
- `yarn test`: To run the entire unit test suite using `jest`.
|
||||
- `yarn test:ci`: To run tests on CI.
|
||||
- `yarn lint`: To run the ESLint based linter to find out the issues in the project.
|
||||
- `yarn lint:fix`: To autoformat all the issues.
|
||||
- `yarn export`: Run this after running `yarn analyze` to export a build copy.
|
||||
- `yarn production`: To export a production build. Use `yarn start` to serve that.
|
||||
|
||||
- `yarn upgrade --latest`: To upgrade all packages to their latest versions (could include breaking changes).
|
||||
|
||||
## Code Structure
|
||||
|
||||
All source code is located in the `src/` directory.
|
||||
|
||||
1. All Next.js entrypoints are housed in the `src/pages` directory as a default.
|
||||
|
||||
- Currently has `_app.tsx` which imports TailwindCSS.
|
||||
- There's also a sample `index.tsx`.
|
||||
|
||||
**NOTE:** Feel free to move `pages` outside of `src/` if that's what you prefer. You'll just need to restart your local development server and everything should continue working as normal.
|
||||
|
||||
2. `src/components` are all stateless reusable components.
|
||||
3. `src/css` folder is there just to house any CSS.
|
||||
|
||||
- Currently contains the TailwindCSS initialization CSS file.
|
||||
|
||||
4. All env variables are available in `.env` files (`.env` file isn't committed). Whenever you update `.env`, please update `.env.example` and `.env.test` and `next.config.js` to proxy all environment variables properly.
|
||||
|
||||
- You can access these variables in the app source code anywhere using `process.env.<VAR_NAME>`.
|
||||
|
||||
If you feel like changing the directory structure, please change the appropriate settings in the following files:
|
||||
|
||||
- `jest.config.js`
|
||||
- `postcss.config.js`
|
||||
- `tsconfig.json`
|
||||
- The `lint` and the `lint:fix` scripts in `package.json`
|
||||
# bicycle
|
||||
@@ -0,0 +1,125 @@
|
||||
import { type CustomNodeType } from '@/components/nodes'
|
||||
import { type CustomEdgeType } from '@/components/edges'
|
||||
|
||||
export function generateLanggraphCode(
|
||||
nodes: CustomNodeType[],
|
||||
edges: CustomEdgeType[],
|
||||
buttonTexts: { [key: string]: string },
|
||||
): string {
|
||||
// Helper function to get node label
|
||||
const getNodeLabel = (nodeId: string) => {
|
||||
const node = nodes.find((n) => n.id === nodeId)
|
||||
const buttonText = buttonTexts[nodeId]
|
||||
return (buttonText || (node?.data?.label as string) || nodeId).replace(/\s+/g, '_')
|
||||
}
|
||||
|
||||
// Generate imports
|
||||
const imports = [
|
||||
'from langgraph.graph import StateGraph',
|
||||
'from langgraph.graph import END',
|
||||
'from typing_extensions import TypedDict',
|
||||
'\n',
|
||||
]
|
||||
|
||||
// Generate function definitions
|
||||
const functions = nodes
|
||||
.filter((node) => node.type !== 'source' && node.type !== 'end')
|
||||
.map((node) => `def ${(node.data.label as string).replace(/\s+/g, '_')}(self, args) -> dict:\n return args\n`)
|
||||
|
||||
// Generate conditional functions
|
||||
const conditionalFunctions = new Map()
|
||||
edges
|
||||
.filter((edge) => edge.animated)
|
||||
.forEach((edge) => {
|
||||
const sourceLabel = getNodeLabel(edge.source)
|
||||
const targetLabel = getNodeLabel(edge.target)
|
||||
if (!conditionalFunctions.has(sourceLabel)) {
|
||||
conditionalFunctions.set(sourceLabel, new Set())
|
||||
}
|
||||
conditionalFunctions.get(sourceLabel).add(targetLabel)
|
||||
})
|
||||
|
||||
const conditionalFunctionStrings = Array.from(conditionalFunctions.entries()).map(([sourceLabel, targets]) => {
|
||||
const targetStrings = Array.from(targets)
|
||||
.map((target) => ` # return "${target}"`)
|
||||
.join('\n')
|
||||
return `def conditional_${sourceLabel}(self, args) -> str:\n${targetStrings}\n return END\n`
|
||||
})
|
||||
|
||||
// Generate State class
|
||||
const stateClass = ['class State(TypedDict):', ' # add state', ' sample_state_variable: str', '\n']
|
||||
|
||||
// Generate create_workflow function
|
||||
const workflowFunction = [
|
||||
'def create_workflow(self) -> StateGraph:',
|
||||
' """Create and configure the workflow graph."""',
|
||||
' workflow = StateGraph(State) # add state',
|
||||
'',
|
||||
' # Add nodes to the graph',
|
||||
]
|
||||
|
||||
nodes
|
||||
.filter((node) => node.type !== 'source' && node.type !== 'end')
|
||||
.forEach((node) => {
|
||||
workflowFunction.push(
|
||||
` workflow.add_node("${(node.data.label as string).replace(/\s+/g, '_')}", ${(
|
||||
node.data.label as string
|
||||
).replace(/\s+/g, '_')})`,
|
||||
)
|
||||
})
|
||||
|
||||
workflowFunction.push('', ' # Define edges')
|
||||
|
||||
const processedConditionalEdges = new Set()
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const sourceLabel = getNodeLabel(edge.source)
|
||||
const targetLabel = getNodeLabel(edge.target)
|
||||
if (edge.animated) {
|
||||
if (!processedConditionalEdges.has(sourceLabel)) {
|
||||
workflowFunction.push(` workflow.add_conditional_edges("${sourceLabel}", conditional_${sourceLabel})`)
|
||||
processedConditionalEdges.add(sourceLabel)
|
||||
}
|
||||
} else {
|
||||
if (targetLabel === 'end') {
|
||||
workflowFunction.push(` workflow.add_edge("${sourceLabel}", END)`)
|
||||
} else {
|
||||
if (sourceLabel != 'source') workflowFunction.push(` workflow.add_edge("${sourceLabel}", "${targetLabel}")`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const startNode = nodes.find((node) => {
|
||||
const sourceNode = nodes.find((n) => n.type === 'source')
|
||||
return edges.some((edge) => edge.source === sourceNode?.id && edge.target === node.id)
|
||||
})
|
||||
if (startNode) {
|
||||
workflowFunction.push(` workflow.set_entry_point("${getNodeLabel(startNode.id)}")`)
|
||||
}
|
||||
workflowFunction.push('\n return workflow')
|
||||
|
||||
// Generate additional utility functions
|
||||
const utilityFunctions = [
|
||||
'def returnGraph(self):',
|
||||
' """Return the graph."""',
|
||||
' return self.create_workflow().compile()',
|
||||
'',
|
||||
'def run_sql_agent(self, args) -> dict:',
|
||||
' """Run the SQL agent workflow."""',
|
||||
' app = self.create_workflow().compile()',
|
||||
' result = app.invoke(args)',
|
||||
' return result',
|
||||
]
|
||||
|
||||
// Combine all parts
|
||||
const codeParts = [
|
||||
...imports,
|
||||
...functions,
|
||||
...conditionalFunctionStrings,
|
||||
...stateClass,
|
||||
...workflowFunction,
|
||||
...utilityFunctions,
|
||||
]
|
||||
|
||||
return codeParts.join('\n')
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import { type CustomNodeType } from '@/components/nodes'
|
||||
import { type CustomEdgeType } from '@/components/edges'
|
||||
|
||||
export function generateLanggraphJS(
|
||||
nodes: CustomNodeType[],
|
||||
edges: CustomEdgeType[],
|
||||
buttonTexts: { [key: string]: string },
|
||||
): string {
|
||||
// Helper function to get node label
|
||||
const getNodeLabel = (nodeId: string) => {
|
||||
const node = nodes.find((n) => n.id === nodeId)
|
||||
const buttonText = buttonTexts[nodeId]
|
||||
return (buttonText || (node?.data?.label as string) || nodeId).replace(/\s+/g, '_')
|
||||
}
|
||||
|
||||
// JavaScript code generation
|
||||
// Generate imports
|
||||
const imports = [
|
||||
"import { StateGraph, END, Annotation } from '@langchain/langgraph';",
|
||||
"import { State, StateAnnotation } from './State';",
|
||||
'',
|
||||
]
|
||||
|
||||
// Generate class definition
|
||||
const classDefinition = [
|
||||
'export class WorkflowManager {',
|
||||
' constructor() {',
|
||||
' this.create_workflow = this.create_workflow.bind(this);',
|
||||
' this.returnGraph = this.returnGraph.bind(this);',
|
||||
' this.run_workflow = this.run_workflow.bind(this);',
|
||||
' }',
|
||||
'',
|
||||
]
|
||||
|
||||
// Generate function definitions
|
||||
const functions = nodes
|
||||
.filter((node) => node.type !== 'source' && node.type !== 'end')
|
||||
.map((node) => {
|
||||
const functionName = (node.data.label as string).replace(/\s+/g, '_')
|
||||
return ` ${functionName}(args) {\n return args;\n }\n`
|
||||
})
|
||||
|
||||
// Generate conditional functions
|
||||
const conditionalFunctions = new Map()
|
||||
edges
|
||||
.filter((edge) => edge.animated)
|
||||
.forEach((edge) => {
|
||||
const sourceLabel = getNodeLabel(edge.source)
|
||||
const targetLabel = getNodeLabel(edge.target)
|
||||
if (!conditionalFunctions.has(sourceLabel)) {
|
||||
conditionalFunctions.set(sourceLabel, new Set())
|
||||
}
|
||||
conditionalFunctions.get(sourceLabel).add(targetLabel)
|
||||
})
|
||||
|
||||
const conditionalFunctionStrings = Array.from(conditionalFunctions.entries()).map(([sourceLabel, targets]) => {
|
||||
const targetStrings = Array.from(targets)
|
||||
.map((target) => ` // return "${target}";`)
|
||||
.join('\n')
|
||||
return ` conditional_${sourceLabel}(args) {\n${targetStrings}\n return END;\n }\n`
|
||||
})
|
||||
|
||||
// Generate create_workflow function
|
||||
const workflowFunction = [' create_workflow() {', ' const workflow = new StateGraph(StateAnnotation)']
|
||||
|
||||
nodes
|
||||
.filter((node) => node.type !== 'source' && node.type !== 'end')
|
||||
.forEach((node) => {
|
||||
const nodeLabel = (node.data.label as string).replace(/\s+/g, '_')
|
||||
workflowFunction.push(` .addNode("${nodeLabel}", this.${nodeLabel})`)
|
||||
})
|
||||
|
||||
workflowFunction.push('')
|
||||
|
||||
const processedConditionalEdges = new Set()
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const sourceLabel = getNodeLabel(edge.source)
|
||||
const targetLabel = getNodeLabel(edge.target)
|
||||
if (edge.animated) {
|
||||
if (!processedConditionalEdges.has(sourceLabel)) {
|
||||
workflowFunction.push(` .addConditionalEdges("${sourceLabel}", this.conditional_${sourceLabel})`)
|
||||
processedConditionalEdges.add(sourceLabel)
|
||||
}
|
||||
} else {
|
||||
if (targetLabel === 'end') {
|
||||
workflowFunction.push(` .addEdge("${sourceLabel}", END)`)
|
||||
} else {
|
||||
if (sourceLabel !== 'source') workflowFunction.push(` .addEdge("${sourceLabel}", "${targetLabel}")`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const startNode = nodes.find((node) => {
|
||||
const sourceNode = nodes.find((n) => n.type === 'source')
|
||||
return edges.some((edge) => edge.source === sourceNode?.id && edge.target === node.id)
|
||||
})
|
||||
if (startNode) {
|
||||
workflowFunction.push(` .setEntryPoint("${getNodeLabel(startNode.id)}")`)
|
||||
}
|
||||
|
||||
workflowFunction.push('\n return workflow;', ' }')
|
||||
|
||||
// Generate additional utility functions
|
||||
const utilityFunctions = [
|
||||
' returnGraph() {',
|
||||
' return this.create_workflow().compile();',
|
||||
' }',
|
||||
'',
|
||||
' async run_workflow(args) {',
|
||||
' const app = this.create_workflow().compile();',
|
||||
' const result = await app.invoke(args);',
|
||||
' return result;',
|
||||
' }',
|
||||
]
|
||||
|
||||
// Combine all parts
|
||||
const codeParts = [
|
||||
...imports,
|
||||
'',
|
||||
...classDefinition,
|
||||
...functions,
|
||||
...conditionalFunctionStrings,
|
||||
...workflowFunction,
|
||||
'',
|
||||
...utilityFunctions,
|
||||
'}',
|
||||
]
|
||||
|
||||
return codeParts.join('\n')
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { type CustomNodeType } from '@/components/nodes'
|
||||
import { type CustomEdgeType } from '@/components/edges'
|
||||
|
||||
export interface CodeGenerationResult {
|
||||
code: string
|
||||
nodes: CustomNodeType[]
|
||||
edges: CustomEdgeType[]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
import { useCallback, useState, useRef, useEffect } from 'react'
|
||||
import {
|
||||
Background,
|
||||
Controls,
|
||||
ReactFlow,
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
OnConnectStart,
|
||||
type OnConnect,
|
||||
useOnSelectionChange,
|
||||
applyEdgeChanges,
|
||||
applyNodeChanges,
|
||||
type Node,
|
||||
type Edge,
|
||||
} from '@xyflow/react'
|
||||
import { MarkerType } from '@xyflow/react'
|
||||
import '@xyflow/react/dist/style.css'
|
||||
|
||||
import { initialNodes, nodeTypes, type CustomNodeType } from './nodes'
|
||||
import { initialEdges, edgeTypes, type CustomEdgeType } from './edges'
|
||||
import { generateLanggraphCode } from '../codeGeneration/generateLanggraph'
|
||||
import { generateLanggraphJS } from '../codeGeneration/generateLanggraphJS'
|
||||
import { CodeGenerationResult } from '../codeGeneration/types'
|
||||
import { saveAs } from 'file-saver'
|
||||
import JSZip from 'jszip'
|
||||
import { useButtonText } from '@/contexts/ButtonTextContext'
|
||||
import Modal from './Modal'
|
||||
|
||||
export default function App() {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<CustomNodeType>(initialNodes)
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState<CustomEdgeType>(initialEdges)
|
||||
const reactFlowWrapper = useRef<any>(null)
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState<any>(null)
|
||||
const [isConnecting, setIsConnecting] = useState(false)
|
||||
const [lastClickTime, setLastClickTime] = useState(0)
|
||||
const [generatedCode, setGeneratedCode] = useState<CodeGenerationResult | null>(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [codeType, setCodeType] = useState<'js' | 'python' | null>(null)
|
||||
|
||||
const onConnectStart: OnConnectStart = useCallback(
|
||||
(connection) => {
|
||||
console.log('onConnectStart', connection)
|
||||
setIsConnecting(true)
|
||||
},
|
||||
[nodes, setIsConnecting],
|
||||
)
|
||||
|
||||
const onConnect: OnConnect = useCallback(
|
||||
(connection) => {
|
||||
console.log('onConnect', connection)
|
||||
setEdges((edges) =>
|
||||
addEdge({ ...connection, markerEnd: { type: MarkerType.ArrowClosed, width: 30, height: 30 } }, edges),
|
||||
)
|
||||
},
|
||||
[setEdges, nodes],
|
||||
)
|
||||
|
||||
const onChange = useCallback(
|
||||
({ nodes, edges }: { nodes: Node[]; edges: Edge[] }) => {
|
||||
console.log('Flow changed:', nodes, edges)
|
||||
if (edges.length == 0) return
|
||||
|
||||
const currentTime = new Date().getTime()
|
||||
if (currentTime - lastClickTime < 300) {
|
||||
// Double-click detected (300ms threshold)
|
||||
setEdges((edgs) =>
|
||||
applyEdgeChanges(
|
||||
[
|
||||
{
|
||||
type: 'remove',
|
||||
id: edges[0].id,
|
||||
},
|
||||
],
|
||||
edgs,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
setEdges((edgs) =>
|
||||
applyEdgeChanges(
|
||||
[
|
||||
{
|
||||
type: 'replace',
|
||||
id: edges[0].id,
|
||||
item: {
|
||||
...edges[0],
|
||||
source: edges[0].source,
|
||||
target: edges[0].target,
|
||||
animated: !edges[0].animated,
|
||||
selected: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
edgs,
|
||||
),
|
||||
)
|
||||
}
|
||||
setLastClickTime(currentTime)
|
||||
},
|
||||
[edges, setEdges, lastClickTime],
|
||||
)
|
||||
|
||||
useOnSelectionChange({ onChange })
|
||||
|
||||
const addNode = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
if (isConnecting) {
|
||||
setIsConnecting(false)
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
if (reactFlowWrapper) {
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect()
|
||||
|
||||
const position = reactFlowInstance.screenToFlowPosition({
|
||||
x: event.clientX - reactFlowBounds.left,
|
||||
y: event.clientY - reactFlowBounds.top,
|
||||
})
|
||||
|
||||
const newNode: CustomNodeType = {
|
||||
id: `node-${nodes.length + 1}`,
|
||||
type: 'custom',
|
||||
position,
|
||||
selected: true,
|
||||
data: { label: `Node ${nodes.length + 1}` },
|
||||
}
|
||||
setNodes((nodes) =>
|
||||
applyNodeChanges(
|
||||
[
|
||||
{
|
||||
type: 'add',
|
||||
item: newNode,
|
||||
},
|
||||
],
|
||||
nodes,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
[nodes, setNodes, reactFlowInstance, reactFlowWrapper, isConnecting, applyNodeChanges],
|
||||
)
|
||||
|
||||
const { buttonTexts } = useButtonText()
|
||||
|
||||
const generateCode = useCallback(() => {
|
||||
const workflowCode = generateLanggraphJS(nodes, edges, buttonTexts)
|
||||
setGeneratedCode({ code: workflowCode, nodes, edges })
|
||||
}, [nodes, edges, buttonTexts])
|
||||
|
||||
const downloadZip = useCallback(() => {
|
||||
if (!generatedCode) return
|
||||
|
||||
const zip = new JSZip()
|
||||
const myAgent = zip.folder('my_agent')
|
||||
|
||||
myAgent!.file('WorkFlow.py', generatedCode.code)
|
||||
myAgent!.file('main.py', `# main.py content`) // Add the content for main.py
|
||||
myAgent!.file('LLMManager.py', `# LLMManager.py content`) // Add the content for LLMManager.py
|
||||
|
||||
zip.file('.env.example', `OPENAI_API_KEY=your_openai_api_key_here`)
|
||||
zip.file('langgraph.json', JSON.stringify({ nodes, edges }, null, 2))
|
||||
zip.file('requirements.txt', `langgraph\nlangchain\nopenai\npython-dotenv`)
|
||||
|
||||
zip.generateAsync({ type: 'blob' }).then((content) => {
|
||||
saveAs(content, 'project.zip')
|
||||
})
|
||||
}, [generatedCode, nodes, edges])
|
||||
|
||||
const handleGenerateCode = () => {
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const handleCodeTypeSelection = (type: 'js' | 'python') => {
|
||||
setCodeType(type)
|
||||
setShowModal(false)
|
||||
|
||||
let generatedCode
|
||||
if (type === 'js') {
|
||||
generatedCode = generateLanggraphJS(nodes, edges, buttonTexts)
|
||||
} else {
|
||||
generatedCode = generateLanggraphCode(nodes, edges, buttonTexts)
|
||||
}
|
||||
|
||||
setGeneratedCode({ code: generatedCode, nodes, edges })
|
||||
}
|
||||
|
||||
console.log(nodes)
|
||||
console.log(edges)
|
||||
|
||||
return (
|
||||
<div ref={reactFlowWrapper} className='z-10 no-scrollbar' style={{ width: '100vw', height: '100vh' }}>
|
||||
<ReactFlow<CustomNodeType, CustomEdgeType>
|
||||
nodes={nodes}
|
||||
nodeTypes={nodeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
edges={edges}
|
||||
edgeTypes={edgeTypes}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onInit={setReactFlowInstance}
|
||||
fitView
|
||||
onConnectStart={onConnectStart}
|
||||
onPaneClick={addNode}
|
||||
className='z-10 bg-[#1a1c24]'
|
||||
style={{
|
||||
backgroundColor: '#1a1c24',
|
||||
}}
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
<button
|
||||
onClick={handleGenerateCode}
|
||||
className='absolute bottom-4 right-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'
|
||||
>
|
||||
Generate Code
|
||||
</button>
|
||||
|
||||
{showModal && <Modal onClose={() => setShowModal(false)} onSelect={handleCodeTypeSelection} />}
|
||||
|
||||
{generatedCode && (
|
||||
<div className='absolute top-4 right-4 bg-white h-full overflow-y-scroll no-scrollbar p-4 rounded shadow-lg'>
|
||||
<div className='absolute top-0 right-0 p-2 flex flex-row gap-2'>
|
||||
<button
|
||||
onClick={downloadZip}
|
||||
className='bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-2 rounded'
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setGeneratedCode(null)}
|
||||
className=' bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded'
|
||||
>
|
||||
Hide
|
||||
</button>
|
||||
</div>
|
||||
<h3 className='text-lg font-bold mb-2'>Generated Code:</h3>
|
||||
<pre className='bg-gray-100 p-2 rounded'>
|
||||
<code>{generatedCode.code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
|
||||
interface ModalProps {
|
||||
onClose: () => void
|
||||
onSelect: (type: 'js' | 'python') => void
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({ onClose, onSelect }) => {
|
||||
return (
|
||||
<div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'>
|
||||
<div className='bg-white p-6 rounded-lg'>
|
||||
<h2 className='text-xl font-bold mb-4'>Choose Code Type</h2>
|
||||
<div className='flex space-x-4'>
|
||||
<button
|
||||
onClick={() => onSelect('js')}
|
||||
className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'
|
||||
>
|
||||
JavaScript
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onSelect('python')}
|
||||
className='bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded'
|
||||
>
|
||||
Python
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={onClose} className='mt-4 text-gray-600 hover:text-gray-800'>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Modal
|
||||
@@ -0,0 +1,65 @@
|
||||
import { BaseEdge, EdgeLabelRenderer, getBezierPath, useReactFlow, type EdgeProps, type Edge } from '@xyflow/react'
|
||||
|
||||
const buttonStyle = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
background: '#eee',
|
||||
border: '1px solid #fff',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '50%',
|
||||
fontSize: '12px',
|
||||
lineHeight: 1,
|
||||
}
|
||||
|
||||
type ButtonEdgeData = {}
|
||||
|
||||
export type ButtonEdge = Edge<ButtonEdgeData>
|
||||
|
||||
export default function ButtonEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
style = {},
|
||||
markerEnd,
|
||||
}: EdgeProps<ButtonEdge>) {
|
||||
const { setEdges } = useReactFlow()
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
})
|
||||
|
||||
const onEdgeClick = () => {
|
||||
setEdges((edges) => edges.filter((edge) => edge.id !== id))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
fontSize: 12,
|
||||
// everything inside EdgeLabelRenderer has no pointer events by default
|
||||
// if you have an interactive element, set pointer-events: all
|
||||
pointerEvents: 'all',
|
||||
}}
|
||||
className='nodrag nopan'
|
||||
>
|
||||
<button style={buttonStyle} onClick={onEdgeClick}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { BuiltInEdge, Edge, EdgeTypes } from '@xyflow/react'
|
||||
|
||||
import ButtonEdge, { type ButtonEdge as ButtonEdgeType } from './ButtonEdge'
|
||||
|
||||
export const initialEdges = [
|
||||
// { id: 'a->c', source: 'a', target: 'c', animated: true },
|
||||
// { id: 'b->d', source: 'b', target: 'd', type: 'button-edge' },
|
||||
// { id: 'c->d', source: 'c', target: 'd', animated: true },
|
||||
] satisfies Edge[]
|
||||
|
||||
export const edgeTypes = {
|
||||
// Add your custom edge types here!
|
||||
'button-edge': ButtonEdge,
|
||||
} satisfies EdgeTypes
|
||||
|
||||
// Append the types of you custom edges to the BuiltInEdge type
|
||||
export type CustomEdgeType = BuiltInEdge | ButtonEdgeType
|
||||
@@ -0,0 +1,13 @@
|
||||
import Flow from './Flow'
|
||||
import { ReactFlowProvider } from '@xyflow/react'
|
||||
import { ButtonTextProvider } from '@/contexts/ButtonTextContext'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ButtonTextProvider>
|
||||
<Flow />
|
||||
</ButtonTextProvider>
|
||||
</ReactFlowProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Handle, Position } from '@xyflow/react'
|
||||
import type { Node as NodeType, NodeProps } from '@xyflow/react'
|
||||
import { useCallback, useState, useMemo } from 'react'
|
||||
import Modal from '../Modal'
|
||||
import { useButtonText } from '@/contexts/ButtonTextContext'
|
||||
|
||||
export type CustomNodeData = {
|
||||
label: string
|
||||
}
|
||||
|
||||
export type CustomNode = NodeType<CustomNodeData>
|
||||
|
||||
export default function CustomNode({ data, id }: NodeProps<CustomNode>) {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const { buttonTexts, updateButtonText } = useButtonText()
|
||||
|
||||
const randomBorderColor = useMemo(() => {
|
||||
return `#${Math.floor(Math.random() * 16777215).toString(16)}`
|
||||
}, [])
|
||||
|
||||
const onClick = useCallback((event: React.MouseEvent) => {
|
||||
console.log('onClick', event)
|
||||
setShowModal(true)
|
||||
}, [])
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateButtonText(id, e.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=' rounded-md '
|
||||
style={{ border: `2px solid ${randomBorderColor}`, backgroundColor: randomBorderColor }}
|
||||
onClick={onClick}
|
||||
>
|
||||
<input
|
||||
type='text'
|
||||
className='w-full outline-none rounded-md text-center p-2 text-white'
|
||||
value={buttonTexts[id] || data.label}
|
||||
onChange={handleInputChange}
|
||||
style={{
|
||||
backgroundColor: `rgba(26,26,36,0.8)`,
|
||||
color: randomBorderColor,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Handle type='source' position={Position.Bottom} />
|
||||
<Handle type='target' position={Position.Top} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Handle, Position } from '@xyflow/react'
|
||||
import type { Node, NodeProps } from '@xyflow/react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export type EndNode = Node
|
||||
|
||||
export default function EndNode() {
|
||||
const randomBorderColor = useMemo(() => {
|
||||
return `#${Math.floor(Math.random() * 16777215).toString(16)}`
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className=' rounded-3xl p-[0.5px] '
|
||||
style={{ border: `1px solid ${randomBorderColor}`, backgroundColor: randomBorderColor }}
|
||||
>
|
||||
<div className='p-3 px-8 rounded-3xl' style={{ color: randomBorderColor, backgroundColor: `rgba(26,26,36,0.8)` }}>
|
||||
__end__
|
||||
</div>
|
||||
<Handle type='target' position={Position.Top} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Node, NodeProps } from '@xyflow/react'
|
||||
import { Handle, Position } from '@xyflow/react'
|
||||
|
||||
export type PositionLoggerNodeData = {
|
||||
label?: string
|
||||
}
|
||||
|
||||
export type PositionLoggerNode = Node<PositionLoggerNodeData>
|
||||
|
||||
export default function PositionLoggerNode({
|
||||
positionAbsoluteX,
|
||||
positionAbsoluteY,
|
||||
data,
|
||||
}: NodeProps<PositionLoggerNode>) {
|
||||
const x = `${Math.round(positionAbsoluteX)}px`
|
||||
const y = `${Math.round(positionAbsoluteY)}px`
|
||||
|
||||
return (
|
||||
// We add this class to use the same styles as React Flow's default nodes.
|
||||
<div className='react-flow__node-default'>
|
||||
{data.label && <div>{data.label}</div>}
|
||||
|
||||
<div>
|
||||
{x} {y}
|
||||
</div>
|
||||
|
||||
<Handle type='source' position={Position.Bottom} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Handle, Position } from '@xyflow/react'
|
||||
import type { Node, NodeProps } from '@xyflow/react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export type SourceNodeData = {
|
||||
label: string
|
||||
}
|
||||
|
||||
export type SourceNode = Node<SourceNodeData>
|
||||
|
||||
export default function SourceNode({ data }: NodeProps<SourceNode>) {
|
||||
const randomBorderColor = useMemo(() => {
|
||||
return `#${Math.floor(Math.random() * 16777215).toString(16)}`
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className=' rounded-3xl p-[0.5px] '
|
||||
style={{ border: `1px solid ${randomBorderColor}`, backgroundColor: randomBorderColor }}
|
||||
>
|
||||
<div className='p-3 px-8 rounded-3xl' style={{ color: randomBorderColor, backgroundColor: `rgba(26,26,36,0.8)` }}>
|
||||
__start__
|
||||
</div>
|
||||
<Handle type='source' position={Position.Bottom} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { BuiltInNode, Node, NodeTypes } from '@xyflow/react'
|
||||
import PositionLoggerNode, { type PositionLoggerNode as PositionLoggerNodeType } from './PositionLoggerNode'
|
||||
import SourceNode, { type SourceNode as SourceNodeType } from './SourceNode'
|
||||
import CustomNode, { type CustomNode as CustomNodeDataType } from './CustomNode'
|
||||
import EndNode, { type EndNode as EndNodeType } from './EndNode'
|
||||
|
||||
export const initialNodes = [
|
||||
// { id: 'a', type: 'input', position: { x: 0, y: 0 }, data: { label: 'wire' } },
|
||||
// {
|
||||
// id: 'b',
|
||||
// type: 'position-logger',
|
||||
// position: { x: -100, y: 100 },
|
||||
// data: { label: 'drag me!' },
|
||||
// },
|
||||
// { id: 'c', position: { x: 100, y: 100 }, data: { label: 'your ideas' } },
|
||||
// {
|
||||
// id: 'd',
|
||||
// type: 'output',
|
||||
// position: { x: 0, y: 200 },
|
||||
// data: { label: 'with React Flow' },
|
||||
// },
|
||||
{
|
||||
id: 'source',
|
||||
type: 'source',
|
||||
position: { x: 0, y: 0 },
|
||||
data: { label: 'source' },
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: { x: 0, y: 600 },
|
||||
data: { label: 'end' },
|
||||
},
|
||||
] satisfies Node[]
|
||||
|
||||
export const nodeTypes = {
|
||||
'position-logger': PositionLoggerNode,
|
||||
source: SourceNode,
|
||||
custom: CustomNode,
|
||||
end: EndNode,
|
||||
} satisfies NodeTypes
|
||||
|
||||
// Append the types of you custom edges to the BuiltInNode type
|
||||
export type CustomNodeType = BuiltInNode | PositionLoggerNodeType | SourceNodeType | CustomNodeDataType | EndNodeType
|
||||
@@ -0,0 +1,26 @@
|
||||
import React, { createContext, useState, useContext } from 'react'
|
||||
|
||||
type ButtonTextContextType = {
|
||||
buttonTexts: { [key: string]: string }
|
||||
updateButtonText: (id: string, text: string) => void
|
||||
}
|
||||
|
||||
const ButtonTextContext = createContext<ButtonTextContextType | undefined>(undefined)
|
||||
|
||||
export const ButtonTextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [buttonTexts, setButtonTexts] = useState<{ [key: string]: string }>({})
|
||||
|
||||
const updateButtonText = (id: string, text: string) => {
|
||||
setButtonTexts((prev) => ({ ...prev, [id]: text }))
|
||||
}
|
||||
|
||||
return <ButtonTextContext.Provider value={{ buttonTexts, updateButtonText }}>{children}</ButtonTextContext.Provider>
|
||||
}
|
||||
|
||||
export const useButtonText = () => {
|
||||
const context = useContext(ButtonTextContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useButtonText must be used within a ButtonTextProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import React from 'react'
|
||||
import { AppProps } from 'next/app'
|
||||
|
||||
import '@/styles/tailwind.css'
|
||||
import { SpeedInsights } from '@vercel/speed-insights/next'
|
||||
|
||||
const App = ({ Component, pageProps: { session, ...pageProps } }: AppProps) => <Component {...pageProps} />
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { NextPage } from 'next'
|
||||
import Head from 'next/head'
|
||||
|
||||
import Page from '@/components'
|
||||
|
||||
const Home: NextPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<meta name='description' content='Generated by create next app' />
|
||||
<link rel='icon' href='/favicon.ico' />
|
||||
</Head>
|
||||
|
||||
<Page />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
@@ -0,0 +1,48 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--black: rgb(12, 3, 3);
|
||||
--feather-grey: rgba(250, 250, 250, 1);
|
||||
--grey: rgba(224, 224, 224, 1);
|
||||
--primary: rgb(230, 224, 202);
|
||||
--primarylight: rgb(151, 119, 246);
|
||||
--shaddow: 0px 4px 4px 0px rgba(0, 0, 0, 0.09);
|
||||
--variable-collection-light-primary: rgba(155, 120, 255, 1);
|
||||
--variable-collection-primary: rgba(230, 118, 50, 1);
|
||||
--font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
@layer utilities {
|
||||
/* Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-track {
|
||||
border-radius: 100vh;
|
||||
background: #f7f4ed;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #e0cbcb;
|
||||
border-radius: 100vh;
|
||||
border: 3px solid #f6f7ed;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #c0a0b9;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
export const createproject = async (project: any) => {
|
||||
// user: {
|
||||
// type: String,
|
||||
// },
|
||||
// projectname: {
|
||||
// type: String,
|
||||
// },
|
||||
// productname: {
|
||||
// type: String,
|
||||
// },
|
||||
// productlink: {
|
||||
// type: String,
|
||||
// },
|
||||
// productdescription: {
|
||||
// type: String,
|
||||
// },
|
||||
// postfilter: {
|
||||
// type: String,
|
||||
// },
|
||||
// subreddits: {
|
||||
// type: String,
|
||||
// },
|
||||
// keywords: {
|
||||
// type: String,
|
||||
// },
|
||||
// postandreplyids: {
|
||||
// type: Array,
|
||||
// },
|
||||
// updatedat: {
|
||||
// type: Date,
|
||||
// default: Date.now,
|
||||
// },
|
||||
const proj = {
|
||||
user: project.user,
|
||||
projectname: project.projectname,
|
||||
productname: project.productname,
|
||||
productlink: project.productlink,
|
||||
productdescription: project.productdescription,
|
||||
postfilter: project.postfilter,
|
||||
subreddits: project.subreddits,
|
||||
keywords: project.keywords,
|
||||
postandreplyids: [],
|
||||
updatedat: Date.now(),
|
||||
}
|
||||
const data = await fetch(`https://buzzgenius-backend.onrender.com/createproject`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(proj),
|
||||
})
|
||||
|
||||
const res = await data.json()
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export const createuser = async (email: string) => {
|
||||
const user = {
|
||||
email: email,
|
||||
projects: [],
|
||||
history: [],
|
||||
payment: 'free',
|
||||
postsleft: 0,
|
||||
}
|
||||
if (email === 'srijanjain1207@gmail.com' || email === 'andrew@unitedwebworks.com') user.payment = 'individual'
|
||||
const response = await fetch(`https://buzzgenius-backend.onrender.com/createuser`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(user),
|
||||
})
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// app.delete("/project/:id"
|
||||
|
||||
export const deleteproject = async (id: string) => {
|
||||
const response = await fetch(`https://buzzgenius-backend.onrender.com/project/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
console.log('response', response)
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export const getuserprojects = async (userid: string) => {
|
||||
const response = await fetch(`https://buzzgenius-backend.onrender.com/userprojects/${userid}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export const postandreplies = async (projectid: any) => {
|
||||
const response = await fetch(`https://buzzgenius-backend.onrender.com/postandreplies/${projectid}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// const FeedbackSchema = new mongoose.Schema({
|
||||
// // feedback
|
||||
// feedback: {
|
||||
// type: String,
|
||||
// },
|
||||
// updatedat: {
|
||||
// type: Date,
|
||||
// default: Date.now,
|
||||
// },
|
||||
// })
|
||||
|
||||
export const savefeedback = async (feedback: string) => {
|
||||
const response = await fetch(`https://buzzgenius-backend.onrender.com/feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ feedback }),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// send email to https://buzzgenius-backend.onrender.com/verifyemail
|
||||
|
||||
//api endpoint with params token and email
|
||||
|
||||
export const signin = async (token: string) => {
|
||||
const response = await fetch(`https://buzzgenius-backend.onrender.com/signin?token=${token}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
return response.json()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export const trialreplies = async (trialproject: any) => {
|
||||
const response = await fetch(`https://buzzgenius-backend.onrender.com/trialreplies`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(trialproject),
|
||||
})
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// send email to https://buzzgenius-backend.onrender.com/verifyemail
|
||||
|
||||
export const verifyemail = async (email: string) => {
|
||||
console.log('email', email)
|
||||
const response = await fetch('https://buzzgenius-backend.onrender.com/verifyemail', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email: email }),
|
||||
})
|
||||
return response.text()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./pages/**/*.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a',
|
||||
950: '#172554',
|
||||
},
|
||||
},
|
||||
},
|
||||
primary: 'var(--primary)',
|
||||
black: 'var(--black)',
|
||||
white: 'var(--white)',
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"~/*": [
|
||||
"./public/*"
|
||||
],
|
||||
// Types error: https://stackoverflow.com/a/73019448
|
||||
"react": [
|
||||
"./node_modules/@types/react"
|
||||
]
|
||||
},
|
||||
"incremental": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user