Skip to content

Commit

Permalink
Add CodeMirror to Python Node
Browse files Browse the repository at this point in the history
  • Loading branch information
proteusvacuum committed Dec 14, 2024
1 parent ae1333c commit 5985c7c
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 11 deletions.
1 change: 1 addition & 0 deletions apps/pipelines/nodes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def logger(self):

class Widgets(StrEnum):
expandable_text = "expandable_text"
code = "code"
toggle = "toggle"
select = "select"
float = "float"
Expand Down
2 changes: 1 addition & 1 deletion apps/pipelines/nodes/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
18 changes: 18 additions & 0 deletions apps/pipelines/tests/data/CodeNode.json
Original file line number Diff line number Diff line change
@@ -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"
}
140 changes: 130 additions & 10 deletions assets/javascript/apps/pipeline/nodes/widgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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":
Expand Down Expand Up @@ -140,7 +146,7 @@ function SelectWidget(props: WidgetParams) {
setLink(selectedOption?.edit_url);
props.updateParamValue(event);
};


return <InputField label={props.label} help_text={props.helpText} inputError={props.inputError}>
<div className="flex flex-row gap-2">
Expand All @@ -164,7 +170,7 @@ function SelectWidget(props: WidgetParams) {
</a>
</div>
)}
</div>
</div>
</InputField>
}

Expand Down Expand Up @@ -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);
}
);
};

Expand All @@ -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}
<div className="tooltip tooltip-left" data-tip={`Expand ${props.label}`}>
<button className="btn btn-xs btn-ghost float-right" onClick={openModal}>
<i className="fa-solid fa-expand-alt"></i>
</button>
</div >
</>
)
return (
<>
<InputField label={label} help_text={props.helpText} inputError={props.inputError}>
<div className="relative w-full">
<textarea
className="textarea textarea-bordered resize-none textarea-sm w-full "
disabled={true}
rows={3}
name={props.name}
value={props.paramValue}
></textarea>
<div
className="absolute inset-0 cursor-pointer"
onClick={openModal}
></div>
</div>
</InputField>
<CodeModal
modalId={modalId}
humanName={props.label}
value={concatenate(props.paramValue)}
onChange={onChangeCallback}
isDarkMode={isDarkMode}
/>
</>
);
}


export function CodeModal(
{ modalId, humanName, value, onChange, isDarkMode }: {
modalId: string;
humanName: string;
value: string;
onChange: (value: string) => void;
isDarkMode: boolean;
}) {
return (
<dialog
id={modalId}
className="modal nopan nodelete nodrag noflow nowheel"
>
<div className="modal-box min-w-[85vw] h-[80vh] flex flex-col">
<form method="dialog">
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
</button>
</form>
<div className="flex-grow h-full w-full flex flex-col">
<h4 className="mb-4 font-bold text-lg bottom-2 capitalize">
{humanName}
</h4>
<CodeMirror
value={value}
onChange={onChange}
className="textarea textarea-bordered h-full w-full flex-grow"
height="100%"
width="100%"
theme={isDarkMode ? githubDark : githubLight}
extensions={[
python(),
]}
/>
</div>
</div>
<form method="dialog" className="modal-backdrop">
{/* Allows closing the modal by clicking outside of it */}
<button>close</button>
</form>
</dialog>
);
}



export function TextModal(
{modalId, humanName, name, value, onChange}: {
Expand Down
Loading

0 comments on commit 5985c7c

Please sign in to comment.