diff --git a/apps/pipelines/nodes/base.py b/apps/pipelines/nodes/base.py
index c5c52ecaa..3e971da90 100644
--- a/apps/pipelines/nodes/base.py
+++ b/apps/pipelines/nodes/base.py
@@ -121,6 +121,7 @@ def logger(self):
class Widgets(StrEnum):
expandable_text = "expandable_text"
+ code = "code"
toggle = "toggle"
select = "select"
float = "float"
diff --git a/apps/pipelines/nodes/nodes.py b/apps/pipelines/nodes/nodes.py
index 9cf93e2ea..1350f68f0 100644
--- a/apps/pipelines/nodes/nodes.py
+++ b/apps/pipelines/nodes/nodes.py
@@ -633,7 +633,7 @@ class CodeNode(PipelineNode):
model_config = ConfigDict(json_schema_extra=NodeSchema(label="Python Node"))
code: str = Field(
description="The code to run",
- json_schema_extra=UiSchema(widget=Widgets.expandable_text), # TODO: add a code widget
+ json_schema_extra=UiSchema(widget=Widgets.code),
)
@field_validator("code")
diff --git a/apps/pipelines/tests/data/CodeNode.json b/apps/pipelines/tests/data/CodeNode.json
new file mode 100644
index 000000000..c58a1ed06
--- /dev/null
+++ b/apps/pipelines/tests/data/CodeNode.json
@@ -0,0 +1,18 @@
+{
+ "description": "Runs python",
+ "properties": {
+ "code": {
+ "description": "The code to run",
+ "title": "Code",
+ "type": "string",
+ "ui:widget": "code"
+ }
+ },
+ "required": [
+ "code"
+ ],
+ "title": "CodeNode",
+ "type": "object",
+ "ui:flow_node_type": "pipelineNode",
+ "ui:label": "Python Node"
+}
\ No newline at end of file
diff --git a/assets/javascript/apps/pipeline/nodes/widgets.tsx b/assets/javascript/apps/pipeline/nodes/widgets.tsx
index b86ad6bda..14eecd0a0 100644
--- a/assets/javascript/apps/pipeline/nodes/widgets.tsx
+++ b/assets/javascript/apps/pipeline/nodes/widgets.tsx
@@ -3,13 +3,17 @@ import React, {
ChangeEventHandler,
ReactNode,
useId,
+ useEffect,
} from "react";
import { useState } from "react";
-import {TypedOption} from "../types/nodeParameterValues";
+import CodeMirror from '@uiw/react-codemirror';
+import { python } from "@codemirror/lang-python";
+import { githubLight, githubDark } from "@uiw/codemirror-theme-github";
+import { TypedOption } from "../types/nodeParameterValues";
import usePipelineStore from "../stores/pipelineStore";
-import {classNames, concatenate, getCachedData, getSelectOptions} from "../utils";
-import {NodeParams, PropertySchema} from "../types/nodeParams";
-import {Node, useUpdateNodeInternals} from "reactflow";
+import { classNames, concatenate, getCachedData, getSelectOptions } from "../utils";
+import { NodeParams, PropertySchema } from "../types/nodeParams";
+import { Node, useUpdateNodeInternals } from "reactflow";
import DOMPurify from 'dompurify';
export function getWidget(name: string) {
@@ -22,10 +26,12 @@ export function getWidget(name: string) {
return RangeWidget
case "expandable_text":
return ExpandableTextWidget
+ case "code":
+ return CodeWidget
case "select":
return SelectWidget
- case "multiselect":
- return MultiSelectWidget
+ case "multiselect":
+ return MultiSelectWidget
case "llm_provider_model":
return LlmWidget
case "history":
@@ -140,7 +146,7 @@ function SelectWidget(props: WidgetParams) {
setLink(selectedOption?.edit_url);
props.updateParamValue(event);
};
-
+
return
@@ -164,7 +170,7 @@ function SelectWidget(props: WidgetParams) {
)}
-
+
}
@@ -195,8 +201,8 @@ function MultiSelectWidget(props: WidgetParams) {
selectedValues = selectedValues.filter((tool) => tool !== event.target.name)
}
setNode(props.nodeId, (old) => {
- return getNewNodeData(old, selectedValues);
- }
+ return getNewNodeData(old, selectedValues);
+ }
);
};
@@ -220,6 +226,120 @@ function MultiSelectWidget(props: WidgetParams) {
)
}
+export function CodeWidget(props: WidgetParams) {
+ const [isDarkMode, setIsDarkMode] = useState(false);
+ const setNode = usePipelineStore((state) => state.setNode);
+ const onChangeCallback = (value: string) => {
+ setNode(props.nodeId, (old) => ({
+ ...old,
+ data: {
+ ...old.data,
+ params: {
+ ...old.data.params,
+ [props.name]: value,
+ },
+ },
+ }));
+ };
+
+ useEffect(() => {
+ // Set dark / light mode
+ const mediaQuery: MediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
+ const handleChange = (event: MediaQueryListEvent): void => {
+ setIsDarkMode(event.matches);
+ };
+ setIsDarkMode(mediaQuery.matches);
+
+ mediaQuery.addEventListener("change", handleChange);
+ return () => mediaQuery.removeEventListener("change", handleChange);
+ }, []);
+
+ const modalId = useId();
+ const openModal = () => (document.getElementById(modalId) as HTMLDialogElement)?.showModal()
+ const label = (
+ <>
+ {props.label}
+
+
+
+ >
+ )
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+
+
+export function CodeModal(
+ { modalId, humanName, value, onChange, isDarkMode }: {
+ modalId: string;
+ humanName: string;
+ value: string;
+ onChange: (value: string) => void;
+ isDarkMode: boolean;
+ }) {
+ return (
+
+ );
+}
+
+
export function TextModal(
{modalId, humanName, name, value, onChange}: {
diff --git a/package-lock.json b/package-lock.json
index 64224b2be..9a7540290 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,9 @@
"name": "open-chat-studio",
"version": "1.0.0",
"dependencies": {
+ "@codemirror/lang-python": "^6.1.6",
+ "@uiw/codemirror-theme-github": "^4.23.6",
+ "@uiw/react-codemirror": "^4.23.6",
"alertifyjs": "^1.14.0",
"alpinejs": "^3.14.3",
"axios": "^1.7.7",
@@ -1776,6 +1779,108 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@codemirror/autocomplete": {
+ "version": "6.18.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz",
+ "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/commands": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz",
+ "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.4.0",
+ "@codemirror/view": "^6.27.0",
+ "@lezer/common": "^1.1.0"
+ }
+ },
+ "node_modules/@codemirror/lang-python": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.6.tgz",
+ "integrity": "sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.3.2",
+ "@codemirror/language": "^6.8.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.2.1",
+ "@lezer/python": "^1.1.4"
+ }
+ },
+ "node_modules/@codemirror/language": {
+ "version": "6.10.6",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.6.tgz",
+ "integrity": "sha512-KrsbdCnxEztLVbB5PycWXFxas4EOyk/fPAfruSOnDDppevQgid2XZ+KbJ9u+fDikP/e7MW7HPBTvTb8JlZK9vA==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.23.0",
+ "@lezer/common": "^1.1.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/lint": {
+ "version": "6.8.4",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz",
+ "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.35.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/search": {
+ "version": "6.5.8",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
+ "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/state": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
+ "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
+ "dependencies": {
+ "@marijn/find-cluster-break": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/theme-one-dark": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+ "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/highlight": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/view": {
+ "version": "6.35.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.3.tgz",
+ "integrity": "sha512-ScY7L8+EGdPl4QtoBiOzE4FELp7JmNUsBvgBcCakXWM2uiv/K89VAzU3BMDscf0DsACLvTKePbd5+cFDTcei6g==",
+ "dependencies": {
+ "@codemirror/state": "^6.5.0",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -2109,6 +2214,42 @@
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
+ "node_modules/@lezer/common": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
+ "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
+ },
+ "node_modules/@lezer/highlight": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
+ "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/lr": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+ "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/python": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.15.tgz",
+ "integrity": "sha512-aVQ43m2zk4FZYedCqL0KHPEUsqZOrmAvRhkhHlVPnDD1HODDyyQv5BRIuod4DadkgBEZd53vQOtXTonNbEgjrQ==",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@marijn/find-cluster-break": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
+ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
+ },
"node_modules/@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.3",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
@@ -3188,6 +3329,86 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@uiw/codemirror-extensions-basic-setup": {
+ "version": "4.23.6",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.6.tgz",
+ "integrity": "sha512-bvtq8IOvdkLJMhoJBRGPEzU51fMpPDwEhcAHp9xCR05MtbIokQgsnLXrmD1aZm6e7s/3q47H+qdSfAAkR5MkLA==",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@codemirror/autocomplete": ">=6.0.0",
+ "@codemirror/commands": ">=6.0.0",
+ "@codemirror/language": ">=6.0.0",
+ "@codemirror/lint": ">=6.0.0",
+ "@codemirror/search": ">=6.0.0",
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0"
+ }
+ },
+ "node_modules/@uiw/codemirror-theme-github": {
+ "version": "4.23.6",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.23.6.tgz",
+ "integrity": "sha512-p74sbyuDo7JsYlGWNtyNyJSyr9N4niCPGqYreQhoAk2C6c4JXi0sGwZRlHd8KyynsxCdSA9L727SADOuukD1Ug==",
+ "dependencies": {
+ "@uiw/codemirror-themes": "4.23.6"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ }
+ },
+ "node_modules/@uiw/codemirror-themes": {
+ "version": "4.23.6",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.6.tgz",
+ "integrity": "sha512-0dpuLQW+V6zrKvfvor/eo71V3tpr2L2Hsu8QZAdtSzksjWABxTOzH3ShaBRxCEsrz6sU9sa9o7ShwBMMDz59bQ==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@codemirror/language": ">=6.0.0",
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0"
+ }
+ },
+ "node_modules/@uiw/react-codemirror": {
+ "version": "4.23.6",
+ "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.6.tgz",
+ "integrity": "sha512-caYKGV6TfGLRV1HHD3p0G3FiVzKL1go7wes5XT2nWjB0+dTdyzyb81MKRSacptgZcotujfNO6QXn65uhETRAMw==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.6",
+ "@codemirror/commands": "^6.1.0",
+ "@codemirror/state": "^6.1.1",
+ "@codemirror/theme-one-dark": "^6.0.0",
+ "@uiw/codemirror-extensions-basic-setup": "4.23.6",
+ "codemirror": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@babel/runtime": ">=7.11.0",
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/theme-one-dark": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0",
+ "codemirror": ">=6.0.0",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"node_modules/@vue/reactivity": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
@@ -4114,6 +4335,20 @@
"node": ">=6"
}
},
+ "node_modules/codemirror": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+ "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -4223,6 +4458,11 @@
}
}
},
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -8612,6 +8852,11 @@
"webpack": "^5.27.0"
}
},
+ "node_modules/style-mod": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+ "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+ },
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
@@ -9206,6 +9451,11 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+ },
"node_modules/watchpack": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
diff --git a/package.json b/package.json
index bca33128c..aa13fba80 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,9 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
+ "@codemirror/lang-python": "^6.1.6",
+ "@uiw/codemirror-theme-github": "^4.23.6",
+ "@uiw/react-codemirror": "^4.23.6",
"alertifyjs": "^1.14.0",
"alpinejs": "^3.14.3",
"axios": "^1.7.7",