Skip to content

Commit

Permalink
Support folding/unfolding output, plus a few UI improvements (#68)
Browse files Browse the repository at this point in the history
* update

* fix #65“

* update

* update

* add running indicator

* format
  • Loading branch information
LittleLittleCloud authored Sep 29, 2024
1 parent bc9572c commit 14ec104
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 81 deletions.
11 changes: 10 additions & 1 deletion example/HelloWorld/BasicSteps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace Gallery;
public class BasicSteps
{
[Step]
public async Task<string> DisplayA([FromStep(nameof(SetA))] string a)
[DependOn(nameof(ConvertAToInteger))]
public async Task<string> Display([FromStep(nameof(ConvertAToInteger))] int a)
{
await Task.Delay(1000);
var currentDate = DateTime.Now;
Expand All @@ -20,4 +21,12 @@ public async Task<string> DisplayA([FromStep(nameof(SetA))] string a)
{
return null;
}

[Step(description: "a step that converts a string to an integer")]
[DependOn(nameof(SetA))]
public async Task<int> ConvertAToInteger(
[FromStep(nameof(SetA))] string a)
{
return int.Parse(a);
}
}
2 changes: 2 additions & 0 deletions example/HelloWorld/ReleaseMaster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public async Task<IssueDTO[]> RetrieveCompletedIssues(
return issueDTOs.ToArray();
}

[DependOn(nameof(WriteReleaseNote))]
[StepWiseUITextInput(description: "Please review the release note and provide feedback, if you are happy with the release note, please type 'approve' to terminate the workflow")]
public async Task<string?> ReviewReleaseNote(
[FromStep(nameof(WriteReleaseNote))] string releaseNote)
Expand All @@ -83,6 +84,7 @@ public async Task<IssueDTO[]> RetrieveCompletedIssues(
}

[Step]
[DependOn(nameof(ReviewReleaseNote))]
public async Task<FeedBack?> Feedback(
[FromStep(nameof(ReviewReleaseNote))] string review,
[FromStep(nameof(WriteReleaseNote))] string releaseNote)
Expand Down
2 changes: 1 addition & 1 deletion src/StepWise.WebAPI/StepWiseControllerV1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ public async IAsyncEnumerable<StepRunDTO> ExecuteStep(
{
var returnType = s.OutputType;
// return type will always be Task<MyReturnType>
if (returnType.GetGenericTypeDefinition() == typeof(Task<>))
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
var actualReturnType = returnType.GenericTypeArguments[0]; // MyReturnType
Expand Down
3 changes: 2 additions & 1 deletion stepwise-studio/.prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"tabWidth": 4,
"useTabs": true,
"trailingComma": "all",
"bracketSameLine": false
"bracketSameLine": false,
"endOfLine": "auto"
}
23 changes: 19 additions & 4 deletions stepwise-studio/components/control-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// | <autolayout> | <run> | <clean> | <max_parallel>: <input> | <max_steps>: <input> |

import { ChangeEvent, FC, useState } from "react";
import { ChangeEvent, FC, useEffect, useState } from "react";
import { buttonVariants } from "./ui/button";
import { cn } from "@/lib/utils";
import {
GithubIcon,
icons,
Layout,
LayoutGrid,
Loader2,
Play,
RotateCcw,
} from "lucide-react";
Expand All @@ -21,10 +22,16 @@ interface ControlBarProps {
maxSteps: number;
onAutoLayoutClick: () => void;
onMaxStepsChange: (value: number) => void;
isRunning: boolean;
}

export const ControlBar: FC<ControlBarProps> = (props) => {
const [maxSteps, setMaxSteps] = useState<number>(props.maxSteps);
const [isRunning, setIsRunning] = useState<boolean>(props.isRunning);

useEffect(() => {
setIsRunning(props.isRunning);
}, [props.isRunning]);

const iconSize = 14;

Expand All @@ -40,13 +47,21 @@ export const ControlBar: FC<ControlBarProps> = (props) => {
<button
className={cn(
buttonVariants({
variant: "ghost",
variant: isRunning ? "disabled" : "ghost",
size: "tinyIcon",
}),
)}
onClick={props.onRunClick}
onClick={() => {
if (!isRunning) {
props.onRunClick();
}
}}
>
<Play size={iconSize} />
{isRunning ? (
<Loader2 size={iconSize} className="animate-spin" />
) : (
<Play size={iconSize} />
)}
</button>
<button
className={cn(
Expand Down
36 changes: 20 additions & 16 deletions stepwise-studio/components/parameter-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,30 @@ export const ParameterCard: React.FC<ParameterCardProps> = (props) => {

return (
<div
style={{ userSelect: "text" }}
className={cn(
"w-full flex flex-col gap-1 bg-accent rounded px-1 py-0.5 ",
"hover:bg-accent/80 hover:cursor-pointer",
"w-full flex flex-col gap-1 rounded cursor-default px-1 py-0.5 nodrag nopan ",
)}
onClick={() => setCollapsed(!collapsed)}
>
<div className="flex gap-5 justify-between">
<div className="flex gap-2 px-4 items-center">
<div
className="flex flex-wrap gap-x-5 justify-between hover:cursor-pointer"
onClick={() => setCollapsed(!collapsed)}
>
<div className="flex gap-2 items-center">
<div className="text-xs">{name}</div>
<div
className={cn(
badgeVariants({
variant: "green",
size: "tiny",
}),
"text-xs px-1 border-none truncate",
)}
>
{getDisplayType(parameterType)}
</div>
{parameterType != "" && (
<div
className={cn(
badgeVariants({
variant: "green",
size: "tiny",
}),
"text-xs px-1 border-none truncate",
)}
>
{getDisplayType(parameterType)}
</div>
)}
</div>

{/* the brief display of variable if available */}
Expand Down
108 changes: 66 additions & 42 deletions stepwise-studio/components/step-node.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ExceptionDTO,
ParameterDTO,
StepDTO,
StepRunDTO,
Expand All @@ -19,6 +20,7 @@ import {
AlertCircle,
AlertOctagon,
Badge,
Brackets,
CheckCircle,
CheckCircle2,
CircleUserRound,
Expand Down Expand Up @@ -220,6 +222,9 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {
const [width, setWidth] = useState<number | undefined>(prop.data.width);
const [height, setHeight] = useState<number | undefined>(prop.data.height);

const [exceptionDTO, setExceptionDTO] = useState<ExceptionDTO | undefined>(
prop.data.exception,
);
const shouldWaitForInput = (
status: StepNodeStatus,
stepType?: StepType,
Expand All @@ -242,7 +247,7 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {
);
setWidth(prop.data.width);
setHeight(prop.data.height);

setExceptionDTO(prop.data.exception);
// set resize observer
const resizeObserver = new ResizeObserver((entries) => {
resizeCallback();
Expand All @@ -259,6 +264,10 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {
setVariables(prop.data.variables ?? []);
}, [prop.data.variables]);

useEffect(() => {
setExceptionDTO(prop.data.exception);
}, [prop.data.exception]);

useEffect(() => {
setOutput(prop.data.result ?? undefined);
}, [prop.data.result]);
Expand Down Expand Up @@ -292,7 +301,14 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {

useEffect(() => {
updateNodeInternals(prop.id);
}, [step, sourceHandleTopOffset, targetHandleTopOffsets, status]);
}, [
step,
sourceHandleTopOffset,
targetHandleTopOffsets,
status,
height,
width,
]);

useEffect(() => {
if (titleRef.current) {
Expand Down Expand Up @@ -325,23 +341,31 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {

useEffect(() => {
if (stepNodeRef.current) {
setWidth(stepNodeRef.current.offsetWidth);
setHeight(stepNodeRef.current.offsetHeight);
console.log("Setting width and height: ", width, height);
// prop.data.onResize(stepNodeRef.current.offsetHeight, stepNodeRef.current.offsetWidth);
if (
height !== stepNodeRef.current.offsetHeight ||
width !== stepNodeRef.current.offsetWidth
) {
prop.data.onResize(
stepNodeRef.current.offsetHeight ?? height,
width ?? stepNodeRef.current.offsetWidth,
);
}
}
}, [
stepNodeRef.current,
stepNodeRef.current?.offsetWidth,
stepNodeRef.current?.offsetHeight,
stepNodeRef.current?.offsetWidth,
width,
height,
]);

return (
<div
className={cn(
"border-2 max-w-96 rounded-md shadow-md p-1 bg-background/50 group min-w-32",
"border-2 rounded-md shadow-md p-1 bg-background/50 group",
// set weight and height
isSelected ? "border-primary/40" : "border-transparent",
width ?? "max-w-48",
shouldWaitForInput(status, stepType)
? "border-primary p-2"
: "",
Expand All @@ -356,17 +380,13 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {
border: "none",
}}
onResize={(event, param) => {
console.log("Resizing: ", param);
setWidth(param.width);
setHeight(param.height);
}}
onResizeEnd={(event, param) => {
prop.data.onResize(
stepNodeRef.current!.offsetHeight,
stepNodeRef.current!.offsetWidth,
);
setHeight(stepNodeRef.current!.offsetHeight);
setWidth(stepNodeRef.current!.offsetWidth);
}}
maxWidth={384}
minWidth={128}
minHeight={height}
maxHeight={height}
Expand Down Expand Up @@ -443,13 +463,14 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {
size={"xxsIcon"}
className="w-4 h-4 m-0 p-0"
>
<VariableIcon size={12} />
<Brackets size={12} />
</Button>
<h3 className="text-xs font-semibold">Parameter</h3>
</div>
<div className="flex flex-col gap-1">
{parameters.map((param, index) => (
<div
className="pl-4 bg-accent rounded-md hover:bg-accent/50"
key={index}
ref={(el) =>
el &&
Expand Down Expand Up @@ -484,34 +505,37 @@ const StepNode: React.FC<NodeProps<StepNodeProps>> = (prop) => {
</div>
</div>
)}

{/* output */}
{output && <div className="border border-md m-1" />}
{output && (
<div>
<div className="flex gap-1 items-center">
<Button
variant={"outline"}
size={"xxsIcon"}
className="w-4 h-4 m-0 p-0"
>
<VariableIcon size={12} />
</Button>
<h3 className="text-xs font-semibold">Output</h3>
{output.type && (
<div
className={cn(
badgeVariants({
variant: "green",
size: "tiny",
}),
"text-xs px-1 border-none",
)}
>
{getDisplayType(output.type)}
</div>
)}
</div>
{output && <VariableCard variable={output} />}
<div className="flex flex-col items-center mt-1 bg-accent rounded-md hover:bg-accent/50">
<ParameterCard
name="Result"
parameter_type={getDisplayType(output.type)}
variable_name={output.name}
variable={output}
/>
</div>
)}

{/* error */}
{exceptionDTO && <div className="border border-destructive m-1" />}
{exceptionDTO && (
<div className="flex flex-col items-center mt-1 bg-destructive/60 rounded-md hover:bg-destructive/40">
<ParameterCard
name="Error"
parameter_type=""
variable_name="error"
variable={{
name: "error",
type: "",
displayValue:
exceptionDTO.message +
"\n" +
exceptionDTO.stackTrace,
value: exceptionDTO,
generation: 0,
}}
/>
</div>
)}

Expand Down
1 change: 1 addition & 0 deletions stepwise-studio/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const buttonVariants = cva(
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
disabled: "bg-background text-foreground/50 shadow-none",
},
size: {
default: "h-9 px-4 py-2",
Expand Down
2 changes: 1 addition & 1 deletion stepwise-studio/components/variable-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const VariableCard: React.FC<VariableCardProps> = (props) => {
return (
<div>
{showAsMarkdown(getDisplayType(variable.type ?? "")) && (
<div className="flex flex-col gap-1 bg-background/50 rounded px-1 overflow-x-auto">
<div className="flex flex-col gap-1 bg-background/50 rounded p-1">
<Markdown className="text-xs w-full overflow-x-auto">
{variable.displayValue!}
</Markdown>
Expand Down
Loading

0 comments on commit 14ec104

Please sign in to comment.