From 6105522ad1a6dae4405524744fb8758caf919586 Mon Sep 17 00:00:00 2001 From: Ian Arawjo Date: Tue, 2 May 2023 17:36:22 -0400 Subject: [PATCH 1/8] LLM list WIP --- chain-forge/package-lock.json | 234 +++++++++++++++++- chain-forge/package.json | 3 + chain-forge/src/LLMListComponent.js | 70 ++++++ chain-forge/src/ListItemComponent.js | 71 ++++++ chain-forge/src/PromptNode.js | 52 +++- chain-forge/src/SettingsButton.js | 20 ++ chain-forge/src/SettingsIcon.js | 0 chain-forge/src/StrictModeDroppable.js | 19 ++ chain-forge/src/TemperatureSliderComponent.js | 31 +++ chain-forge/src/text-fields-node.css | 28 +++ 10 files changed, 511 insertions(+), 17 deletions(-) create mode 100644 chain-forge/src/LLMListComponent.js create mode 100644 chain-forge/src/ListItemComponent.js create mode 100644 chain-forge/src/SettingsButton.js create mode 100644 chain-forge/src/SettingsIcon.js create mode 100644 chain-forge/src/StrictModeDroppable.js create mode 100644 chain-forge/src/TemperatureSliderComponent.js diff --git a/chain-forge/package-lock.json b/chain-forge/package-lock.json index aa182ea..f08c8db 100644 --- a/chain-forge/package-lock.json +++ b/chain-forge/package-lock.json @@ -14,6 +14,7 @@ "@reactflow/background": "^11.2.0", "@reactflow/controls": "^11.1.11", "@reactflow/core": "^11.7.0", + "@tabler/icons-react": "^2.17.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -31,11 +32,13 @@ "plotly.js": "^2.21.0", "react": "^18.2.0", "react-ace": "^10.1.0", + "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", "react-edit-text": "^5.1.0", "react-flow-renderer": "^10.3.17", "react-plotly.js": "^2.6.0", "react-scripts": "5.0.1", + "styled-components": "^5.3.10", "web-vitals": "^2.1.4", "zustand": "^4.3.7" } @@ -2328,11 +2331,18 @@ "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==", "peer": true }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, "node_modules/@emotion/memoize": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==", - "peer": true + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" }, "node_modules/@emotion/react": { "version": "11.10.8", @@ -2377,6 +2387,11 @@ "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==", "peer": true }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, "node_modules/@emotion/unitless": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", @@ -4115,6 +4130,31 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tabler/icons": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.17.0.tgz", + "integrity": "sha512-UeJaylOGNRhQKyDlgZfrQ3UPSGlfVQuXcmCsTYeXioKKepibW6VZ3H36Lo1jvBTBkQD2e9m+k2NxwkztOTXwrA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.17.0.tgz", + "integrity": "sha512-kuEW+qNwRqcK5iMl7qTapzX2NiMOwPg4Az01d+IZ1DIMwaZ7iKPJaIor2ihKFLPYrT9D5BZHXB8R5mSkw0FETg==", + "dependencies": { + "@tabler/icons": "2.17.0", + "prop-types": "^15.7.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz", @@ -4826,6 +4866,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -5164,6 +5213,17 @@ "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/resize-observer-browser": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz", @@ -6446,6 +6506,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.1.tgz", + "integrity": "sha512-c8lJlszObVQPguHkI+akXv8+Jgb9Ccujx0EetL7oIvwU100LxO6XAGe45qry37wUL40a5U9f23SYrivro2XKhA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.21", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -6815,6 +6895,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -7446,6 +7534,22 @@ "postcss": "^8.4" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", @@ -7673,6 +7777,16 @@ "resolved": "https://registry.npmjs.org/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz", "integrity": "sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -10902,7 +11016,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "peer": true, "dependencies": { "react-is": "^16.7.0" } @@ -10910,8 +11023,7 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "peer": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/hoopy": { "version": "0.1.4", @@ -14339,6 +14451,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -16814,6 +16931,11 @@ "performance-now": "^2.1.0" } }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -16912,6 +17034,24 @@ "node": ">=14" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -17118,6 +17258,30 @@ "react": ">0.13.0" } }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -17336,6 +17500,14 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -18092,6 +18264,11 @@ "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -18653,6 +18830,40 @@ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.2.tgz", "integrity": "sha512-C4myMmRTO8iaC5Gg+N1ftK2WT4eXUTMAa+HEFPPrfVeO/NtqLTtAmV1HbqnuGtLwCek44Ra76fdGUkSqjiMPcQ==" }, + "node_modules/styled-components": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.10.tgz", + "integrity": "sha512-3kSzSBN0TiCnGJM04UwO1HklIQQSXW7rCARUk+VyMR7clz8XVlA3jijtf5ypqoDIdNMKx3la4VvaPFR855SFcg==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -19167,6 +19378,11 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", @@ -19654,6 +19870,14 @@ } } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/chain-forge/package.json b/chain-forge/package.json index c6a8d39..2fa5c9b 100644 --- a/chain-forge/package.json +++ b/chain-forge/package.json @@ -9,6 +9,7 @@ "@reactflow/background": "^11.2.0", "@reactflow/controls": "^11.1.11", "@reactflow/core": "^11.7.0", + "@tabler/icons-react": "^2.17.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -26,11 +27,13 @@ "plotly.js": "^2.21.0", "react": "^18.2.0", "react-ace": "^10.1.0", + "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", "react-edit-text": "^5.1.0", "react-flow-renderer": "^10.3.17", "react-plotly.js": "^2.6.0", "react-scripts": "5.0.1", + "styled-components": "^5.3.10", "web-vitals": "^2.1.4", "zustand": "^4.3.7" }, diff --git a/chain-forge/src/LLMListComponent.js b/chain-forge/src/LLMListComponent.js new file mode 100644 index 0000000..3b825c1 --- /dev/null +++ b/chain-forge/src/LLMListComponent.js @@ -0,0 +1,70 @@ +import { useState } from "react"; +import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; +import ListItem, { DragItem, ListItemClone } from "./ListItemComponent"; +import { StrictModeDroppable } from './StrictModeDroppable' + +const llmItems = [ + { model: "🙂 GPT3.5", temp: 1.0 }, + { model: "🥵 GPT4", temp: 1.0 }, + { model: "🦙 Alpaca 7B", temp: 0.5 }, + { model: "📚 Claude v1", temp: 0.5 } +]; + +export default function LLMList() { + const [items, setItems] = useState(llmItems); + + const onDragEnd = (result) => { + const { destination, source } = result; + if (!destination) return; + if ( + destination.droppableId === source.droppableId && + destination.index === source.index + ) { + return; + } + const newItems = Array.from(items); + const [removed] = newItems.splice(result.source.index, 1); + newItems.splice(result.destination.index, 0, removed); + setItems(newItems); + }; + + return ( +
+ + ( + // + + // + )} + > + {(provided) => ( +
+ {items.map((item, index) => ( + + {(provided, snapshot) => ( + + )} + + ))} + {provided.placeholder} +
+ )} +
+
+
+ ); +} diff --git a/chain-forge/src/ListItemComponent.js b/chain-forge/src/ListItemComponent.js new file mode 100644 index 0000000..ad18b06 --- /dev/null +++ b/chain-forge/src/ListItemComponent.js @@ -0,0 +1,71 @@ +/** Thanks to Kabir Haruna: https://codesandbox.io/s/i0rxsj */ +import React from "react"; +import styled from "styled-components"; +import SettingsButton from "./SettingsButton" + +const CardHeader = styled.div` + font-weight: 500; + font-size: 10pt; + font-family: -apple-system, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + text-align: start; + float: left; + margin-top: 1px; +`; + +const Author = styled.div` + display: flex; + align-items: center; +`; + +const CardFooter = styled.div` + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; +`; + +export const DragItem = styled.div` + padding: 10px; + border-radius: 6px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + background: white; + margin: 0 0 8px 0; + display: grid; + grid-gap: 20px; + flex-direction: column; +`; + +const ListItem = ({ item, provided, snapshot }) => { + return ( + +
+ {item.model} + +
+ +
+ ); +}; + +export const ListItemClone = ({ item, provided, snapshot }) => { + return ( + +
+ {item.model} + +
+
+ ); +}; + +export default ListItem; \ No newline at end of file diff --git a/chain-forge/src/PromptNode.js b/chain-forge/src/PromptNode.js index d6b23d3..6d2cefc 100644 --- a/chain-forge/src/PromptNode.js +++ b/chain-forge/src/PromptNode.js @@ -4,6 +4,7 @@ import useStore from './store'; import StatusIndicator from './StatusIndicatorComponent' import NodeLabel from './NodeLabelComponent' import TemplateHooks from './TemplateHooksComponent' +import LLMList from './LLMListComponent' // Helper funcs const truncStr = (s, maxLen) => { @@ -251,6 +252,27 @@ const PromptNode = ({ data, id }) => { ? '1px solid #222' : '1px solid #999'; + const llm_list_data = [ + { + "position": 6, + "mass": 12.011, + "symbol": "C", + "name": "Carbon" + }, + { + "position": 7, + "mass": 14.007, + "symbol": "N", + "name": "Nitrogen" + }, + { + "position": 39, + "mass": 88.906, + "symbol": "Y", + "name": "Yttrium" + } + ]; + return (
{
-
-
+
-
-

LLMs:

-
- - - - - - +
+
+ Models to query: +
+ +
+
+ +
+ + {/* + + + + + */} +
-
{responsePreviews}
diff --git a/chain-forge/src/SettingsButton.js b/chain-forge/src/SettingsButton.js new file mode 100644 index 0000000..7cc2ec9 --- /dev/null +++ b/chain-forge/src/SettingsButton.js @@ -0,0 +1,20 @@ +import { useDisclosure } from '@mantine/hooks'; +import { Modal, Button, Group } from '@mantine/core'; +import { IconSettings, IconTrash } from '@tabler/icons-react'; + +export default function SettingsButton() { + const [opened, { open, close }] = useDisclosure(false); + + return ( +
+ + {/* Modal content */} + + + + + + +
+ ); +} \ No newline at end of file diff --git a/chain-forge/src/SettingsIcon.js b/chain-forge/src/SettingsIcon.js new file mode 100644 index 0000000..e69de29 diff --git a/chain-forge/src/StrictModeDroppable.js b/chain-forge/src/StrictModeDroppable.js new file mode 100644 index 0000000..6e52970 --- /dev/null +++ b/chain-forge/src/StrictModeDroppable.js @@ -0,0 +1,19 @@ +// StrictModeDroppable.tsx +// Credits to https://github.com/GiovanniACamacho and https://github.com/Meligy for the TypeScript version +// Original post: https://github.com/atlassian/react-beautiful-dnd/issues/2399#issuecomment-1175638194 +import { useEffect, useState } from "react"; +import { Droppable } from "react-beautiful-dnd"; +export const StrictModeDroppable = ({ children, ...props }) => { + const [enabled, setEnabled] = useState(false); + useEffect(() => { + const animation = requestAnimationFrame(() => setEnabled(true)); + return () => { + cancelAnimationFrame(animation); + setEnabled(false); + }; + }, []); + if (!enabled) { + return null; + } + return {children}; +}; \ No newline at end of file diff --git a/chain-forge/src/TemperatureSliderComponent.js b/chain-forge/src/TemperatureSliderComponent.js new file mode 100644 index 0000000..eecd6fe --- /dev/null +++ b/chain-forge/src/TemperatureSliderComponent.js @@ -0,0 +1,31 @@ +import { Slider, RangeSlider } from '@mantine/core'; +import { IconTemperature, IconTemperatureOff } from '@tabler/icons-react'; + +const styles = { thumb: { borderWidth: 2, height: 24, width: 24, padding: 3 } }; + +export default function TemperatureSlider({style}) { + return ( +
+ } + color="red" + label={null} + defaultValue={40} + styles={styles} + onPointerDown={e => {e.preventDefault();}} + /> + + {/* , + , + ]} + /> */} +
+ ); +} \ No newline at end of file diff --git a/chain-forge/src/text-fields-node.css b/chain-forge/src/text-fields-node.css index 9b74b7b..4a6fce6 100644 --- a/chain-forge/src/text-fields-node.css +++ b/chain-forge/src/text-fields-node.css @@ -5,6 +5,11 @@ border-radius: 5px; } + .small-standard-font { + font-size: 10pt; + font-family: -apple-system, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + } + .status-icon { display: inline-block; font-size: 14pt; @@ -214,6 +219,29 @@ justify-content: center; } + .add-llm-model-btn { + display: flex; + align-items: right; + justify-content: right; + float: right; + height: 20px; + } + .add-llm-model-btn button { + border-style: solid; + border-color: #777; + border-width: 1pt; + border-radius: 3px; + color: #777; + margin-right: 2px; + } + .add-llm-model-btn:hover button { + background-color: white; + } + .add-llm-model-btn:active button { + background-color: black; + color: white; + } + /** CSS Button style from https://css-buttons-hover.netlify.app/ */ .AmitSahoo45-button-3 { position: relative; From 187be40b3f26b66d59df3f76f58eb215f1878f6d Mon Sep 17 00:00:00 2001 From: Ian Arawjo Date: Tue, 2 May 2023 18:51:37 -0400 Subject: [PATCH 2/8] Add LLM button --- chain-forge/src/LLMListComponent.js | 11 +----- chain-forge/src/ListItemComponent.js | 4 +- chain-forge/src/PromptNode.js | 58 +++++++++++++--------------- 3 files changed, 30 insertions(+), 43 deletions(-) diff --git a/chain-forge/src/LLMListComponent.js b/chain-forge/src/LLMListComponent.js index 3b825c1..13125e7 100644 --- a/chain-forge/src/LLMListComponent.js +++ b/chain-forge/src/LLMListComponent.js @@ -3,15 +3,8 @@ import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import ListItem, { DragItem, ListItemClone } from "./ListItemComponent"; import { StrictModeDroppable } from './StrictModeDroppable' -const llmItems = [ - { model: "🙂 GPT3.5", temp: 1.0 }, - { model: "🥵 GPT4", temp: 1.0 }, - { model: "🦙 Alpaca 7B", temp: 0.5 }, - { model: "📚 Claude v1", temp: 0.5 } -]; - -export default function LLMList() { - const [items, setItems] = useState(llmItems); +export default function LLMList({llms}) { + const [items, setItems] = useState(llms); const onDragEnd = (result) => { const { destination, source } = result; diff --git a/chain-forge/src/ListItemComponent.js b/chain-forge/src/ListItemComponent.js index ad18b06..c04a32c 100644 --- a/chain-forge/src/ListItemComponent.js +++ b/chain-forge/src/ListItemComponent.js @@ -44,7 +44,7 @@ const ListItem = ({ item, provided, snapshot }) => { {...provided.dragHandleProps} >
- {item.model} + {item.emoji} {item.model}
@@ -61,7 +61,7 @@ export const ListItemClone = ({ item, provided, snapshot }) => { snapshot={snapshot} >
- {item.model} + {item.emoji} {item.model}
diff --git a/chain-forge/src/PromptNode.js b/chain-forge/src/PromptNode.js index 6d2cefc..2ff8e19 100644 --- a/chain-forge/src/PromptNode.js +++ b/chain-forge/src/PromptNode.js @@ -1,11 +1,20 @@ import React, { useEffect, useState, useCallback } from 'react'; import { Handle } from 'react-flow-renderer'; +import { Menu, Button, Text, useMantineTheme } from '@mantine/core'; import useStore from './store'; import StatusIndicator from './StatusIndicatorComponent' import NodeLabel from './NodeLabelComponent' import TemplateHooks from './TemplateHooksComponent' import LLMList from './LLMListComponent' +// Available LLMs +const llmItems = [ + { model: "GPT3.5", emoji: "🙂", temp: 1.0 }, + { model: "GPT4", emoji: "🥵", temp: 1.0 }, + { model: "Alpaca 7B", emoji: "🦙", temp: 0.5 }, + { model: "Claude v1", emoji: "📚", temp: 0.5 } +]; + // Helper funcs const truncStr = (s, maxLen) => { if (s.length > maxLen) // Cut the name short if it's long @@ -32,6 +41,7 @@ const bucketResponsesByLLM = (responses) => { }; const PromptNode = ({ data, id }) => { + const theme = useMantineTheme(); // Get state from the Zustand store: const edges = useStore((state) => state.edges); @@ -234,16 +244,6 @@ const PromptNode = ({ data, id }) => { } }; -// const nodeLabelRef = React.useRef(null); -// const makeEditable = () => { -// if (nodeLabelRef.current) { -// for (const child of nodeLabelRef.current.children) { -// console.log(child.children); -// child.contentEditable = 'true'; -// } -// } -// }; - const hideStatusIndicator = () => { if (status !== 'none') { setStatus('none'); } }; @@ -252,26 +252,9 @@ const PromptNode = ({ data, id }) => { ? '1px solid #222' : '1px solid #999'; - const llm_list_data = [ - { - "position": 6, - "mass": 12.011, - "symbol": "C", - "name": "Carbon" - }, - { - "position": 7, - "mass": 14.007, - "symbol": "N", - "name": "Nitrogen" - }, - { - "position": 39, - "mass": 88.906, - "symbol": "Y", - "name": "Yttrium" - } - ]; + const selectedModel = (name) => { + console.log(name); + }; return (
{
Models to query:
- + + + + + + {llmItems.map(item => ( selectedModel(item.model)} icon={item.emoji}>{item.model}))} + +
- + {/* From 1a1533530ceab2af5ce2fb5a709a61dede4bea2b Mon Sep 17 00:00:00 2001 From: Ian Arawjo Date: Tue, 2 May 2023 19:10:24 -0400 Subject: [PATCH 3/8] Use Mantine badges for prompt var labels --- chain-forge/src/PromptNode.js | 6 ++++-- chain-forge/src/TemplateHooksComponent.js | 5 +++-- chain-forge/src/text-fields-node.css | 5 +++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/chain-forge/src/PromptNode.js b/chain-forge/src/PromptNode.js index 2ff8e19..07aa717 100644 --- a/chain-forge/src/PromptNode.js +++ b/chain-forge/src/PromptNode.js @@ -12,7 +12,8 @@ const llmItems = [ { model: "GPT3.5", emoji: "🙂", temp: 1.0 }, { model: "GPT4", emoji: "🥵", temp: 1.0 }, { model: "Alpaca 7B", emoji: "🦙", temp: 0.5 }, - { model: "Claude v1", emoji: "📚", temp: 0.5 } + { model: "Claude v1", emoji: "📚", temp: 0.5 }, + { model: "Ian Chatbot", emoji: "💩", temp: 0.5 } ]; // Helper funcs @@ -286,12 +287,13 @@ const PromptNode = ({ data, id }) => { />
+
-
+
Models to query:
diff --git a/chain-forge/src/TemplateHooksComponent.js b/chain-forge/src/TemplateHooksComponent.js index 8589f47..d3f19dc 100644 --- a/chain-forge/src/TemplateHooksComponent.js +++ b/chain-forge/src/TemplateHooksComponent.js @@ -1,16 +1,17 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Handle } from 'react-flow-renderer'; +import { Badge } from '@mantine/core'; import useStore from './store' export default function TemplateHooks({ vars, nodeId, startY }) { const genTemplateHooks = useCallback((temp_var_names, names_to_blink) => { return temp_var_names.map((name, idx) => { - const className = (names_to_blink.includes(name)) ? 'text-blink' : ''; + const className = (names_to_blink.includes(name)) ? 'hook-tag text-blink' : 'hook-tag'; const pos = (idx * 35) + startY + 'px'; const style = { top: pos, background: '#555' }; return (
-

{name}

+ {name}
) }); diff --git a/chain-forge/src/text-fields-node.css b/chain-forge/src/text-fields-node.css index 4a6fce6..2d428f6 100644 --- a/chain-forge/src/text-fields-node.css +++ b/chain-forge/src/text-fields-node.css @@ -203,6 +203,11 @@ font-family: 'Courier New', Courier, monospace; } + .hook-tag { + padding-top: 11px;; + padding-bottom: 3px; + } + .code-mirror-field-header { font-size: 10pt; color: #333; From eb1ce03b484ad78ed1fa9ad6e0ba2fa6473e51c4 Mon Sep 17 00:00:00 2001 From: Ian Arawjo Date: Tue, 2 May 2023 21:33:12 -0400 Subject: [PATCH 4/8] Modularize alert modals --- chain-forge/src/AlertModal.js | 28 ++++++++++++++++++ chain-forge/src/EvaluatorNode.js | 51 +++++--------------------------- chain-forge/src/PromptNode.js | 2 +- 3 files changed, 37 insertions(+), 44 deletions(-) create mode 100644 chain-forge/src/AlertModal.js diff --git a/chain-forge/src/AlertModal.js b/chain-forge/src/AlertModal.js new file mode 100644 index 0000000..53fa4d0 --- /dev/null +++ b/chain-forge/src/AlertModal.js @@ -0,0 +1,28 @@ +/** An alert popup for displaying errors */ +import React, { useState, forwardRef, useImperativeHandle } from 'react'; +import { useDisclosure } from '@mantine/hooks'; +import { Modal } from '@mantine/core'; + +const AlertModal = forwardRef((props, ref) => { + // Mantine modal popover for alerts + const [opened, { open, close }] = useDisclosure(false); + const [alertMsg, setAlertMsg] = useState(''); + + // This gives the parent access to triggering the modal alert + const trigger = (msg) => { + console.error(msg); + setAlertMsg(msg); + open(); + }; + useImperativeHandle(ref, () => ({ + trigger, + })); + + return ( + +

{alertMsg}

+
+ ); +}); + +export default AlertModal; \ No newline at end of file diff --git a/chain-forge/src/EvaluatorNode.js b/chain-forge/src/EvaluatorNode.js index 7265b6f..a7841e8 100644 --- a/chain-forge/src/EvaluatorNode.js +++ b/chain-forge/src/EvaluatorNode.js @@ -1,12 +1,9 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Handle } from 'react-flow-renderer'; import useStore from './store'; import StatusIndicator from './StatusIndicatorComponent' import NodeLabel from './NodeLabelComponent' - -// Mantine modal -import { useDisclosure } from '@mantine/hooks'; -import { Modal } from '@mantine/core'; +import AlertModal from './AlertModal' // Ace code editor import AceEditor from "react-ace"; @@ -14,28 +11,6 @@ import "ace-builds/src-noconflict/mode-python"; import "ace-builds/src-noconflict/theme-xcode"; import "ace-builds/src-noconflict/ext-language_tools"; -// CodeMirror text editor -// import CodeMirror from '@uiw/react-codemirror'; -// import { globalCompletion, python } from '@codemirror/lang-python'; -// // import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate} from '@codemirror/view'; -// import { indentUnit } from '@codemirror/language'; -// import { okaidia } from '@uiw/codemirror-theme-okaidia'; // dark theme -// import { solarizedDark } from '@uiw/codemirror-theme-solarized'; // dark theme; warm -// import { noctisLilac } from '@uiw/codemirror-theme-noctis-lilac'; // light theme NOTE: Unfortunately this does not show selected text, no idea why. -// import { materialLight } from '@uiw/codemirror-theme-material'; // light theme, material -// import { xcodeDark, xcodeLight } from '@uiw/codemirror-theme-xcode'; // light theme, xcode -// import { sublime } from '@uiw/codemirror-theme-sublime'; - -// Experimenting with making the 'def evaluator' line read-only -// import readOnlyRangesExtension from 'codemirror-readonly-ranges' -// const getReadOnlyRanges = (editorState) => { -// return [{ -// from: editorState.doc.line(2).from, //same as: editorState.doc.line(0).from or 0 -// to: editorState.doc.line(2).to -// }] -// } -// // Add readOnlyRangesExtension(getReadOnlyRanges) to extensions prop of CodeMirror component - const EvaluatorNode = ({ data, id }) => { const inputEdgesForNode = useStore((state) => state.inputEdgesForNode); @@ -44,9 +19,8 @@ const EvaluatorNode = ({ data, id }) => { const setDataPropsForNode = useStore((state) => state.setDataPropsForNode); const [status, setStatus] = useState('none'); - // Mantine modal popover for alerts - const [opened, { open, close }] = useDisclosure(false); - const [alertMsg, setAlertMsg] = useState(''); + // For displaying error messages to user + const alertModal = useRef(null); const [hovered, setHovered] = useState(false); const [codeText, setCodeText] = useState(data.code); @@ -74,13 +48,6 @@ const EvaluatorNode = ({ data, id }) => { setDataPropsForNode(id, {code: code}); }; - // Trigger an alert modal popover with Mantine: - const triggerErrorAlert = (msg) => { - console.error(msg); - setAlertMsg(msg); - open(); - }; - const handleRunClick = (event) => { // Get the ids from the connected input nodes: const input_node_ids = inputEdgesForNode(id).map(e => e.source); @@ -94,7 +61,7 @@ const EvaluatorNode = ({ data, id }) => { if (codeText.search(/def\s+evaluate\s*(.*):/) === -1) { const err_msg = `Could not find required function 'evaluate'. Make sure you have defined an 'evaluate' function.`; setStatus('error'); - triggerErrorAlert(err_msg); + alertModal.current.trigger(err_msg); return; } @@ -102,7 +69,7 @@ const EvaluatorNode = ({ data, id }) => { const rejected = (err_msg) => { setStatus('error'); - triggerErrorAlert(err_msg); + alertModal.current.trigger(err_msg); }; // Run evaluator in backend @@ -126,7 +93,7 @@ const EvaluatorNode = ({ data, id }) => { // Check if there's an error; if so, bubble it up to user and exit: if (json.error) { setStatus('error'); - triggerErrorAlert(json.error); + alertModal.current.trigger(json.error); return; } @@ -210,9 +177,7 @@ const EvaluatorNode = ({ data, id }) => { id="output" style={{ top: '50%', background: '#555' }} /> - -

{alertMsg}

-
+
Function to map over each  
-
+
Models to query:
From ddade145f8089ed6219129bc2acbb8278f4088db Mon Sep 17 00:00:00 2001 From: Ian Arawjo Date: Tue, 2 May 2023 22:20:21 -0400 Subject: [PATCH 5/8] Slightly better response preview styling --- chain-forge/src/PromptNode.js | 47 ++++++++++++++++++++-------- chain-forge/src/text-fields-node.css | 14 ++++----- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/chain-forge/src/PromptNode.js b/chain-forge/src/PromptNode.js index 5a55a56..e0fcde0 100644 --- a/chain-forge/src/PromptNode.js +++ b/chain-forge/src/PromptNode.js @@ -1,11 +1,12 @@ -import React, { useEffect, useState, useCallback } from 'react'; +import React, { useEffect, useState, useRef, useCallback } from 'react'; import { Handle } from 'react-flow-renderer'; -import { Menu, Button, Text, useMantineTheme } from '@mantine/core'; +import { Menu, Badge, useMantineTheme } from '@mantine/core'; import useStore from './store'; import StatusIndicator from './StatusIndicatorComponent' import NodeLabel from './NodeLabelComponent' import TemplateHooks from './TemplateHooksComponent' import LLMList from './LLMListComponent' +import AlertModal from './AlertModal' // Available LLMs const llmItems = [ @@ -28,7 +29,7 @@ const vars_to_str = (vars) => { const s = truncStr(vars[varname].trim(), 12); return `${varname} = '${s}'`; }); - return pairs.join('; '); + return pairs; }; const bucketResponsesByLLM = (responses) => { let responses_by_llm = {}; @@ -58,6 +59,9 @@ const PromptNode = ({ data, id }) => { const [status, setStatus] = useState('none'); const [responsePreviews, setReponsePreviews] = useState([]); const [numGenerations, setNumGenerations] = useState(data.n || 1); + + // For displaying error messages to user + const alertModal = useRef(null); const handleMouseEnter = () => { setHovered(true); @@ -116,7 +120,7 @@ const PromptNode = ({ data, id }) => { // Set status indicator setStatus('loading'); - // Pull data from each source: + // Pull data from each source, recursively: const pulled_data = {}; const get_outputs = (varnames, nodeId) => { console.log(varnames); @@ -152,6 +156,11 @@ const PromptNode = ({ data, id }) => { pulled_data[varname] = pulled_data[varname].map(val => to_py_template_format(val)); }); + const rejected = (err) => { + setStatus('error'); + alertModal.current.trigger(err.message); + }; + // Run all prompt permutations through the LLM to generate + cache responses: fetch('http://localhost:5000/queryllm', { method: 'POST', @@ -167,10 +176,14 @@ const PromptNode = ({ data, id }) => { }, no_cache: false, }), - }).then(function(response) { + }, rejected).then(function(response) { return response.json(); - }).then(function(json) { - if (json.responses) { + }, rejected).then(function(json) { + if (!json) { + setStatus('error'); + alertModal.current.trigger('Request was sent and received by backend server, but there was no response.'); + } + else if (json.responses) { // Success! Change status to 'ready': setStatus('ready'); @@ -181,7 +194,8 @@ const PromptNode = ({ data, id }) => { // Save preview strings of responses, for quick glance // Bucket responses by LLM: const responses_by_llm = bucketResponsesByLLM(json.responses); - const colors = ['#cbf078', '#f1b963', '#e46161', '#f8f398', '#defcf9', '#cadefc', '#c3bef0', '#cca8e9']; + // const colors = ['#cbf078', '#f1b963', '#e46161', '#f8f398', '#defcf9', '#cadefc', '#c3bef0', '#cca8e9']; + // const colors = ['green', 'yellow', 'orange', 'red', 'pink', 'grape', 'violet', 'indigo', 'blue', 'gray', 'cyan', 'lime']; setReponsePreviews(Object.keys(responses_by_llm).map((llm, llm_idx) => { const resp_boxes = responses_by_llm[llm].map((res_obj, idx) => { const num_resp = res_obj['responses'].length; @@ -189,9 +203,13 @@ const PromptNode = ({ data, id }) => { (
{truncStr(r, 40)}({i+1} of {num_resp})
) ); const vars = vars_to_str(res_obj.vars); + const var_tags = vars.map((v, i) => ( + {v} + )); return ( -
-

{vars}

+
+ {var_tags} + {/*

{vars}

*/} {resp_prevs}
); @@ -207,13 +225,15 @@ const PromptNode = ({ data, id }) => { // Log responses for debugging: console.log(json.responses); } else { - console.error(json.error || 'Unknown error when querying LLM'); + setStatus('error'); + alertModal.current.trigger(json.error || 'Unknown error when querying LLM'); } - }); + }, rejected); console.log(pulled_data); } else { console.log('Not connected! :('); + alertModal.current.trigger('Missing inputs to one or more template variables.') // TODO: Blink the names of unconnected params } @@ -269,6 +289,7 @@ const PromptNode = ({ data, id }) => { nodeId={id} onEdit={hideStatusIndicator} /> +
@@ -293,7 +314,7 @@ const PromptNode = ({ data, id }) => {
-
+
Models to query:
diff --git a/chain-forge/src/text-fields-node.css b/chain-forge/src/text-fields-node.css index 2d428f6..0694cca 100644 --- a/chain-forge/src/text-fields-node.css +++ b/chain-forge/src/text-fields-node.css @@ -152,25 +152,26 @@ border-color: #aaa; padding: 2px; margin: 0px; - background-color: #fff; + background-color: rgba(255, 255, 255, 0.4); white-space: pre-wrap; } .llm-response-container { max-width: 450px; - background-color: #fff; } .llm-response-container h1 { + font-weight: 400; font-size: 10pt; - font-weight: bold; - margin: 4px; + margin: 6px 8px 0px 8px; padding-top: 2px; - padding-bottom: 2px; + padding-bottom: 0px; color: #222; } .response-preview-container { - max-height: 180px; + margin: 10px -9px -9px -9px; + max-height: 100px; overflow-y: auto; + background-color: #ace1aeb1; /* ACE1AF */ } .response-box { padding: 2px; @@ -179,7 +180,6 @@ .response-tag { font-size: 9pt; color: #555; - font-weight: 700; margin: 0px 0px 0px 2px; } From 3c085819212839a0e0178e101d5f2dde86c939a7 Mon Sep 17 00:00:00 2001 From: ianarawjo Date: Tue, 2 May 2023 22:33:10 -0400 Subject: [PATCH 6/8] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ca6fc4..d497bf2 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ ChainForge is built on [ReactFlow](https://reactflow.dev) and is in active devel # Features A key goal of ChainForge is facilitating **comparison** and **evaluation** of prompts and models, and (in the near future) prompt chains. Basic features are: -- **Prompt permutations**: Setup a prompt template and feed it variations of input variables. ChainForge will prompt all selected LLMs with all possible permutations of the input prompt, so that you can get a better sense of prompt quality. -- **Evaluation nodes**: Probe LLM responses in a chain and test them for some desired behavior. Initially, Python script based. +- **Prompt permutations**: Setup a prompt template and feed it variations of input variables. ChainForge will prompt all selected LLMs with all possible permutations of the input prompt, so that you can get a better sense of prompt quality. You can also chain prompt templates at arbitrary depth (e.g., to compare templates). +- **Evaluation nodes**: Probe LLM responses in a chain and test them (classically) for some desired behavior. At a basic level, this is Python script based. We plan to add preset evaluator nodes for common use cases in the near future (e.g., name-entity recognition). Note that you can also chain LLM responses into prompt templates to help evaluate outputs cheaply before more extensive evaluation methods. - **Visualization nodes**: Visualize evaluation results on plots like box-and-whisker and 3D scatterplots. Taken together, these three features let you easily: @@ -40,6 +40,10 @@ This spins up two local servers: a React server through npm, and a Python backen All ChainForge node graphs are importable/exportable as JSON specs. You can freely share prompt chains you develop (alongside any custom analysis code), whether to the public or within your organization. +## Example: Test LLM robustness to prompt injection + +... + # Development ChainForge is developed by research scientists at Harvard University in the [Harvard HCI](https://hci.seas.harvard.edu) group: From bf72a6f8a8e55d385a497d4d48ae7cc0a6a32fb0 Mon Sep 17 00:00:00 2001 From: Ian Arawjo Date: Wed, 3 May 2023 10:10:31 -0400 Subject: [PATCH 7/8] Adding and removing LLM list items --- chain-forge/package-lock.json | 15 ++++ chain-forge/package.json | 1 + ...ettingsButton.js => LLMItemButtonGroup.js} | 6 +- chain-forge/src/LLMListComponent.js | 58 +++++++++------ .../{ListItemComponent.js => LLMListItem.js} | 28 +++----- chain-forge/src/PromptNode.js | 72 ++++++++++--------- 6 files changed, 101 insertions(+), 79 deletions(-) rename chain-forge/src/{SettingsButton.js => LLMItemButtonGroup.js} (57%) rename chain-forge/src/{ListItemComponent.js => LLMListItem.js} (65%) diff --git a/chain-forge/package-lock.json b/chain-forge/package-lock.json index f08c8db..eabd204 100644 --- a/chain-forge/package-lock.json +++ b/chain-forge/package-lock.json @@ -39,6 +39,7 @@ "react-plotly.js": "^2.6.0", "react-scripts": "5.0.1", "styled-components": "^5.3.10", + "uuidv4": "^6.2.13", "web-vitals": "^2.1.4", "zustand": "^4.3.7" } @@ -5295,6 +5296,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" + }, "node_modules/@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -19990,6 +19996,15 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uuidv4": { + "version": "6.2.13", + "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz", + "integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==", + "dependencies": { + "@types/uuid": "8.3.4", + "uuid": "8.3.2" + } + }, "node_modules/v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", diff --git a/chain-forge/package.json b/chain-forge/package.json index 2fa5c9b..e799d6e 100644 --- a/chain-forge/package.json +++ b/chain-forge/package.json @@ -34,6 +34,7 @@ "react-plotly.js": "^2.6.0", "react-scripts": "5.0.1", "styled-components": "^5.3.10", + "uuidv4": "^6.2.13", "web-vitals": "^2.1.4", "zustand": "^4.3.7" }, diff --git a/chain-forge/src/SettingsButton.js b/chain-forge/src/LLMItemButtonGroup.js similarity index 57% rename from chain-forge/src/SettingsButton.js rename to chain-forge/src/LLMItemButtonGroup.js index 7cc2ec9..efcc5d8 100644 --- a/chain-forge/src/SettingsButton.js +++ b/chain-forge/src/LLMItemButtonGroup.js @@ -2,7 +2,7 @@ import { useDisclosure } from '@mantine/hooks'; import { Modal, Button, Group } from '@mantine/core'; import { IconSettings, IconTrash } from '@tabler/icons-react'; -export default function SettingsButton() { +export default function LLMItemButtonGroup( {onClickTrash, onClickSettings} ) { const [opened, { open, close }] = useDisclosure(false); return ( @@ -12,8 +12,8 @@ export default function SettingsButton() { - - + +
); diff --git a/chain-forge/src/LLMListComponent.js b/chain-forge/src/LLMListComponent.js index 13125e7..bd72f32 100644 --- a/chain-forge/src/LLMListComponent.js +++ b/chain-forge/src/LLMListComponent.js @@ -1,11 +1,16 @@ -import { useState } from "react"; +import { useState, useEffect, useCallback } from "react"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; -import ListItem, { DragItem, ListItemClone } from "./ListItemComponent"; +import LLMListItem, { DragItem, LLMListItemClone } from "./LLMListItem"; import { StrictModeDroppable } from './StrictModeDroppable' -export default function LLMList({llms}) { +export default function LLMList({llms, onItemsChange}) { const [items, setItems] = useState(llms); + const updateItems = useCallback((new_items) => { + setItems(new_items); + onItemsChange(new_items); + }, [onItemsChange]); + const onDragEnd = (result) => { const { destination, source } = result; if (!destination) return; @@ -21,35 +26,44 @@ export default function LLMList({llms}) { setItems(newItems); }; + const removeItem = useCallback((item_key) => { + // Double-check that the item we want to remove is in the list of items... + if (!items.find(i => i.key === item_key)) { + console.error(`Could not remove model from LLM list: Could not find item with key ${item_key}.`); + return; + } + // Remove it + updateItems(items.filter(i => i.key !== item_key)); + }, [items, updateItems]); + + useEffect(() => { + // When LLMs list changes, we need to add new items + // while preserving the current order of 'items'. + // Check for new items and for each, add to end: + let new_items = Array.from(items); + llms.forEach(item => { + if (!items.find(i => i.key === item.key)) + new_items.push(item); + }); + + updateItems(new_items); + }, [llms, updateItems]); + return ( -
+
( - // - - // + )} > {(provided) => (
{items.map((item, index) => ( - + {(provided, snapshot) => ( - + )} ))} diff --git a/chain-forge/src/ListItemComponent.js b/chain-forge/src/LLMListItem.js similarity index 65% rename from chain-forge/src/ListItemComponent.js rename to chain-forge/src/LLMListItem.js index c04a32c..2d948c8 100644 --- a/chain-forge/src/ListItemComponent.js +++ b/chain-forge/src/LLMListItem.js @@ -1,7 +1,7 @@ /** Thanks to Kabir Haruna: https://codesandbox.io/s/i0rxsj */ import React from "react"; import styled from "styled-components"; -import SettingsButton from "./SettingsButton" +import LLMItemButtonGroup from "./LLMItemButtonGroup" const CardHeader = styled.div` font-weight: 500; @@ -12,18 +12,6 @@ const CardHeader = styled.div` margin-top: 1px; `; -const Author = styled.div` - display: flex; - align-items: center; -`; - -const CardFooter = styled.div` - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; -`; - export const DragItem = styled.div` padding: 10px; border-radius: 6px; @@ -35,7 +23,7 @@ export const DragItem = styled.div` flex-direction: column; `; -const ListItem = ({ item, provided, snapshot }) => { +const LLMListItem = ({ item, provided, snapshot, removeCallback }) => { return ( { {...provided.dragHandleProps} >
- {item.emoji} {item.model} - + {item.emoji} {item.name} + removeCallback(item.key)} />
); }; -export const ListItemClone = ({ item, provided, snapshot }) => { +export const LLMListItemClone = ({ item, provided, snapshot }) => { return ( { snapshot={snapshot} >
- {item.emoji} {item.model} - + {item.emoji} {item.name} +
); }; -export default ListItem; \ No newline at end of file +export default LLMListItem; \ No newline at end of file diff --git a/chain-forge/src/PromptNode.js b/chain-forge/src/PromptNode.js index e0fcde0..f0bbc4f 100644 --- a/chain-forge/src/PromptNode.js +++ b/chain-forge/src/PromptNode.js @@ -1,6 +1,7 @@ import React, { useEffect, useState, useRef, useCallback } from 'react'; import { Handle } from 'react-flow-renderer'; -import { Menu, Badge, useMantineTheme } from '@mantine/core'; +import { Menu, Badge } from '@mantine/core'; +import { v4 as uuid } from 'uuid'; import useStore from './store'; import StatusIndicator from './StatusIndicatorComponent' import NodeLabel from './NodeLabelComponent' @@ -9,13 +10,14 @@ import LLMList from './LLMListComponent' import AlertModal from './AlertModal' // Available LLMs -const llmItems = [ - { model: "GPT3.5", emoji: "🙂", temp: 1.0 }, - { model: "GPT4", emoji: "🥵", temp: 1.0 }, - { model: "Alpaca 7B", emoji: "🦙", temp: 0.5 }, - { model: "Claude v1", emoji: "📚", temp: 0.5 }, - { model: "Ian Chatbot", emoji: "💩", temp: 0.5 } +const allLLMs = [ + { name: "GPT3.5", emoji: "🙂", model: "gpt3.5", temp: 1.0 }, + { name: "GPT4", emoji: "🥵", model: "gpt4", temp: 1.0 }, + { name: "Alpaca 7B", emoji: "🦙", model: "alpaca.7B", temp: 0.5 }, + { name: "Claude v1", emoji: "📚", model: "claude.v1", temp: 0.5 }, + { name: "Ian Chatbot", emoji: "💩", model: "test", temp: 0.5 } ]; +const initLLMs = [allLLMs[0]]; // Helper funcs const truncStr = (s, maxLen) => { @@ -43,7 +45,6 @@ const bucketResponsesByLLM = (responses) => { }; const PromptNode = ({ data, id }) => { - const theme = useMantineTheme(); // Get state from the Zustand store: const edges = useStore((state) => state.edges); @@ -55,13 +56,36 @@ const PromptNode = ({ data, id }) => { const [templateVars, setTemplateVars] = useState(data.vars || []); const [promptText, setPromptText] = useState(data.prompt); const [promptTextOnLastRun, setPromptTextOnLastRun] = useState(null); - const [selectedLLMs, setSelectedLLMs] = useState(['gpt3.5']); const [status, setStatus] = useState('none'); const [responsePreviews, setReponsePreviews] = useState([]); const [numGenerations, setNumGenerations] = useState(data.n || 1); // For displaying error messages to user const alertModal = useRef(null); + + // Selecting LLM models to prompt + const [llmItems, setLLMItems] = useState(initLLMs.map((i, idx) => ({key: uuid(), ...i}))); + const [llmItemsCurrState, setLLMItemsCurrState] = useState([]); + + const addModel = useCallback((model) => { + // Get the item for that model + let item = allLLMs.find(llm => llm.model === model); + + if (!item) { // This should never trigger, but in case it does: + alertModal.current.trigger(`Could not find model named '${model}' in list of available LLMs.`); + return; + } + + // Give it a uid as a unique key (this is needed for the draggable list to support multiple same-model items; keys must be unique) + item = {key: uuid(), ...item}; + + // Add model to LLM list (regardless of it's present already or not). + setLLMItems(llmItemsCurrState.concat([item])) + }, [llmItemsCurrState]); + + const onLLMListItemsChange = useCallback((new_items) => { + setLLMItemsCurrState(new_items); + }, [setLLMItemsCurrState]); const handleMouseEnter = () => { setHovered(true); @@ -112,7 +136,7 @@ const PromptNode = ({ data, id }) => { console.log('Connected!'); // Check that there is at least one LLM selected: - if (selectedLLMs.length === 0) { + if (llmItems.length === 0) { alert('Please select at least one LLM to prompt.') return; } @@ -167,7 +191,7 @@ const PromptNode = ({ data, id }) => { headers: {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'}, body: JSON.stringify({ id: id, - llm: selectedLLMs, + llm: llmItems.map(item => item.model), prompt: py_prompt_template, vars: pulled_data, params: { @@ -239,22 +263,6 @@ const PromptNode = ({ data, id }) => { } } - const handleLLMChecked = (event) => { - console.log(event.target.value, event.target.checked); - if (event.target.checked) { - if (!selectedLLMs.includes(event.target.value)) { - // Add the selected LLM to the list: - setSelectedLLMs(selectedLLMs.concat([event.target.value])) - } - } else { - if (selectedLLMs.includes(event.target.value)) { - // Remove the LLM from the selected list: - const removeByIndex = (array, index) => array.filter((_, i) => i !== index); - setSelectedLLMs(removeByIndex(selectedLLMs, selectedLLMs.indexOf(event.target.value))); - } - } - } - const handleNumGenChange = (event) => { let n = event.target.value; if (!isNaN(n) && n.length > 0 && /^\d+$/.test(n)) { @@ -273,10 +281,6 @@ const PromptNode = ({ data, id }) => { ? '1px solid #222' : '1px solid #999'; - const selectedModel = (name) => { - console.log(name); - }; - return (
{
-
+
Models to query:
@@ -327,14 +331,14 @@ const PromptNode = ({ data, id }) => { - {llmItems.map(item => ( selectedModel(item.model)} icon={item.emoji}>{item.model}))} + {allLLMs.map(item => ( addModel(item.model)} icon={item.emoji}>{item.name}))}
- + {/* From 0d13ca2060122a8b696f9d9df837141fb7a31b20 Mon Sep 17 00:00:00 2001 From: Ian Arawjo Date: Wed, 3 May 2023 10:13:35 -0400 Subject: [PATCH 8/8] Pull from llmItemsCurrState when sending responses --- chain-forge/src/PromptNode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chain-forge/src/PromptNode.js b/chain-forge/src/PromptNode.js index f0bbc4f..e7290f5 100644 --- a/chain-forge/src/PromptNode.js +++ b/chain-forge/src/PromptNode.js @@ -136,7 +136,7 @@ const PromptNode = ({ data, id }) => { console.log('Connected!'); // Check that there is at least one LLM selected: - if (llmItems.length === 0) { + if (llmItemsCurrState.length === 0) { alert('Please select at least one LLM to prompt.') return; } @@ -191,7 +191,7 @@ const PromptNode = ({ data, id }) => { headers: {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'}, body: JSON.stringify({ id: id, - llm: llmItems.map(item => item.model), + llm: llmItemsCurrState.map(item => item.model), prompt: py_prompt_template, vars: pulled_data, params: {