Skip to content

Commit

Permalink
[WebUI] Add webui (#18)
Browse files Browse the repository at this point in the history
* update

* add ract flow

* update

* update

* update

* fix

* update

* update github workflow

* fix build pipeline

* move helloworld example

* update readme
  • Loading branch information
LittleLittleCloud authored Sep 15, 2024
1 parent 84d6c1c commit 56b772c
Show file tree
Hide file tree
Showing 58 changed files with 11,837 additions and 212 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/dotnet-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ jobs:
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '22.x'
- name: build webui
run: |
cd stepwise-studio
npm install
npm run build
- name: Restore dependencies
run: |
dotnet restore -bl
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/dotnet-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: '22.x'
- name: build webui
run: |
cd stepwise-studio
npm install
npm run build
- uses: nuget/setup-nuget@v2
with:
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
Expand Down
186 changes: 0 additions & 186 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,186 +0,0 @@
<a name="readme-top"></a>


<div align="center">

<img src="./asset/stepwise-logo-svg.svg" alt="StepWise Logo" width="100">

# StepWise
</div>

StepWise is a powerful and flexible C# library for defining and executing workflows. It allows you to break down complex workflows into manageable steps, define dependencies between them, and execute them in the correct order.

## Features

- Define workflows as a series of steps
- Automatic dependency resolution between steps
- Parallel execution of independent steps

## Quick Start

Here's a simple example of how to define a workflow to prepare dinner. The workflow consists of several steps, such as chopping vegetables, boiling water, cooking pasta, and cooking sauce. The final step is to serve dinner, which depends on all the previous steps. When executed, the workflow will automatically resolve the dependencies between steps and execute them in the parallel if possible.

```csharp
using StepWise;

public class PrepareDinner
{
[Step]
public async Task<string> ChopVegetables(string[] vegetables)
{
await Task.Delay(3000);

return $"Chopped {string.Join(", ", vegetables)}";
}

[Step]
public async Task<string> BoilWater()
{
await Task.Delay(2000);

return "Boiled water";
}

[Step]
public async Task<string> CookPasta()
{
await Task.Delay(5000);

return "Cooked pasta";
}

[Step]
public async Task<string> CookSauce()
{
await Task.Delay(4000);

return "Cooked sauce";
}

[Step]
[DependOn(nameof(ChopVegetables))]
[DependOn(nameof(BoilWater))]
[DependOn(nameof(CookPasta))]
[DependOn(nameof(CookSauce))]
public async Task<string> ServeDinner(
[FromStep(nameof(ChopVegetables))] string[] vegetables,
[FromStep(nameof(BoilWater))] string water,
[FromStep(nameof(CookPasta))] string pasta,
[FromStep(nameof(CookSauce))] string sauce)
{
return $"Dinner ready!";
}
}

// Usage
var prepareDinner = new PrepareDinner();
var workflow = Workflow.CreateFromInstance(prepareDinner);
var engine = new WorkflowEngine(workflow, maxConcurrency: 10);
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var inputVariables = new Dictionary<string, object>
{
[nameof(ChopVegetables)] = StepVariable.Create(new[] { "tomato", "onion", "garlic" }),
};

await foreach (var stepResult in engine.ExecuteAsync(nameof(ServeDinner), inputVariables))
{
// print every step result
// ChopVegetables: Chopped tomato, onion, garlic
// BoilWater: Boiled water
// CookPasta: Cooked pasta
// CookSauce: Cooked sauce
// ServeDinner: Dinner ready!
Console.WriteLine(stepResult);
}

stopwatch.Stop();

// Because the steps are executed in parallel, the total time should be less than the sum of individual step times
stopwatch.ElapsedMilliseconds.Should().BeLessThan(6000);
```

## Examples
You can find more examples in the [examples](https://github.com/LittleLittleCloud/StepWise/tree/main/example) directory.

## Dependency Management between Steps
### Step Dependency
In StepWise, you can define dependencies between steps using the `[DependsOn]` attribute. This ensures that a step is executed only after its dependencies have been satisfied.

> [!Note]
> Prevent circular dependencies between steps, otherwise, the workflow engine will remind you with an exception.
### Variable Dependency
Variable dependencies of a step means that the step requires certain variables to be available in the context before it can be executed. If all variable dependencies are met, the step can be executed in parallel with other steps that don't have dependencies on it. In StepWise, variable dependencies are the input parameters of a step.

> [!Note]
> `[FromStep]` attribute doesn't affect the step dependency. It is used to pass the output of one step as input to another step.
StepWise automatically manages dependencies between Steps:
- Use the `[DependsOn]` attribute to specify dependencies between Steps.
- The StepwiseEngine resolves these dependencies and ensures Steps are executed in the correct order.

## Parallel Execution

StepWise supports parallel execution of steps that do not have step dependencies on each other. This can significantly improve the performance of your workflows by executing independent steps concurrently.

## `StepWiseEngine`
`StepWiseEngine` is the core component of StepWise that manages the execution of workflows. It uses a consumer-producer approach to execute steps in the correct order while handling dependencies between steps and parallel execution when possible. You can visit this [documentation](./article/DeepDiveToStepWiseEngine.md) to learn more about how the `StepWiseEngine` works.

## Primitives

StepWise is built around two main primitives:

### 1. Step

A Step is the smallest unit of work in StepWise. It represents a single task or operation within a workflow.

- **Definition**: A Step is essentially a C# method decorated with the `[Step]` attribute.
- **Properties**:
- Name: A unique identifier for the step.
- Input Parameters: The data required by the step to perform its task.
- Output: The result produced by the step (if any). **Must be a Task or Task<\T>**.
- Dependencies: Other steps that must be executed before this step. This is specified using the `[DependsOn]` attribute.
- **Usage**:
```csharp
[Step]
[DependsOn(nameof(OtherStep))]
[DependsOn(nameof(AnotherStep))]
public Task<Data> GetData(int id)
{
// Implementation
}
```

### 2. Workflow

A Workflow is a collection of Steps that together accomplish a larger task.

- **Definition**: A Workflow is typically represented by a class containing multiple Step methods.
- **Usage**:
```csharp
public class DataProcessingWorkflow
{
[Step(Name = "GetData")]
public Task<Data> GetData(int id) { /* ... */ }

[Step(Name = "ProcessData")]
[DependsOn(nameof(GetData))]
public Task<Result> ProcessData([FromStep("GetData")] Data data) { /* ... */ }

[Step(Name = "SaveResult")]
[DependsOn(nameof(ProcessData))]
public Task<string> SaveResult([FromStep("ProcessData")] Result result) { /* ... */ }
}
```

## Contributing

We welcome contributions to StepWise! Please see our [Contributing Guide](https://github.com/LittleLittleCloud/StepWise/tree/main/CONTRIBUTING.md) for more details.

## License

StepWise is released under the MIT License. See the [LICENSE](https://github.com/LittleLittleCloud/StepWise/tree/main/LICENSE) file for details.

## Support

If you encounter any issues or have questions, please file an issue on the GitHub issue tracker.
28 changes: 26 additions & 2 deletions StepWise.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.Core.Tests", "test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeInterpreter", "example\CodeInterpreter\CodeInterpreter.csproj", "{9BF63586-31E6-4075-B120-C8D2B6E7296A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StepWise.WebAPI", "src\StepWise.WebAPI\StepWise.WebAPI.csproj", "{8D2B6D19-3922-4B52-BB88-B28C01737970}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.WebAPI", "src\StepWise.WebAPI\StepWise.WebAPI.csproj", "{8D2B6D19-3922-4B52-BB88-B28C01737970}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StepWise.WebAPI.Tests", "test\StepWise.WebAPI.Tests\StepWise.WebAPI.Tests.csproj", "{DAE8E54E-0A43-4FD7-9D75-6081792FA94E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.WebAPI.Tests", "test\StepWise.WebAPI.Tests\StepWise.WebAPI.Tests.csproj", "{DAE8E54E-0A43-4FD7-9D75-6081792FA94E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stepwise-studio", "stepwise-studio", "{DC2EB9EC-E500-4BC0-A36C-F3FD0245CF3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWorld", "example\HelloWorld\HelloWorld.csproj", "{8BD13BAB-263D-403F-93CC-FB628FA2E0C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StepWise.WebUI", "src\StepWise.WebUI\StepWise.WebUI.csproj", "{7DD9A65A-C68D-464A-AFDB-21E9A3C6AA0E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StepWise", "src\StepWise\StepWise.csproj", "{3D5EDF70-D0B3-4552-8185-BE5EB11C7ADA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -59,6 +67,18 @@ Global
{DAE8E54E-0A43-4FD7-9D75-6081792FA94E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE8E54E-0A43-4FD7-9D75-6081792FA94E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE8E54E-0A43-4FD7-9D75-6081792FA94E}.Release|Any CPU.Build.0 = Release|Any CPU
{8BD13BAB-263D-403F-93CC-FB628FA2E0C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BD13BAB-263D-403F-93CC-FB628FA2E0C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BD13BAB-263D-403F-93CC-FB628FA2E0C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BD13BAB-263D-403F-93CC-FB628FA2E0C2}.Release|Any CPU.Build.0 = Release|Any CPU
{7DD9A65A-C68D-464A-AFDB-21E9A3C6AA0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DD9A65A-C68D-464A-AFDB-21E9A3C6AA0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7DD9A65A-C68D-464A-AFDB-21E9A3C6AA0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DD9A65A-C68D-464A-AFDB-21E9A3C6AA0E}.Release|Any CPU.Build.0 = Release|Any CPU
{3D5EDF70-D0B3-4552-8185-BE5EB11C7ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D5EDF70-D0B3-4552-8185-BE5EB11C7ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D5EDF70-D0B3-4552-8185-BE5EB11C7ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D5EDF70-D0B3-4552-8185-BE5EB11C7ADA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -70,6 +90,10 @@ Global
{9BF63586-31E6-4075-B120-C8D2B6E7296A} = {0D861355-C022-4E1A-8C7B-7D1C3A066EE3}
{8D2B6D19-3922-4B52-BB88-B28C01737970} = {19750AFD-3091-4569-9D89-8D5735C3EBFC}
{DAE8E54E-0A43-4FD7-9D75-6081792FA94E} = {5E5C30E1-F538-430A-BE65-40C1C5B5C76A}
{DC2EB9EC-E500-4BC0-A36C-F3FD0245CF3F} = {0D861355-C022-4E1A-8C7B-7D1C3A066EE3}
{8BD13BAB-263D-403F-93CC-FB628FA2E0C2} = {DC2EB9EC-E500-4BC0-A36C-F3FD0245CF3F}
{7DD9A65A-C68D-464A-AFDB-21E9A3C6AA0E} = {19750AFD-3091-4569-9D89-8D5735C3EBFC}
{3D5EDF70-D0B3-4552-8185-BE5EB11C7ADA} = {19750AFD-3091-4569-9D89-8D5735C3EBFC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55953F2E-2283-4F22-9B79-17E81B54BDCE}
Expand Down
Binary file added asset/stepwise-ui-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions example/HelloWorld/Cumulative.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) LittleLittleCloud. All rights reserved.
// Cumulative.cs

// Create a web host running on 5123 port
using StepWise.Core;

public class Cumulative
{
[Step]
public async Task<string> A()
{
return "a";
}

[Step]
[DependOn(nameof(A))]
public async Task<string> B(
[FromStep(nameof(A))] string a)
{
return "b";
}

[Step]
[DependOn(nameof(B))]
[DependOn(nameof(A))]
public async Task<string> C(
[FromStep(nameof(A))] string a,
[FromStep(nameof(B))] string b)
{
return "c";
}

[Step]
[DependOn(nameof(C))]
[DependOn(nameof(B))]
[DependOn(nameof(A))]
public async Task<string> D(
[FromStep(nameof(A))] string a,
[FromStep(nameof(B))] string b,
[FromStep(nameof(C))] string c)
{
return "d";
}

[Step]
[DependOn(nameof(D))]
[DependOn(nameof(C))]
[DependOn(nameof(B))]
[DependOn(nameof(A))]
public async Task<string> E(
[FromStep(nameof(A))] string a,
[FromStep(nameof(B))] string b,
[FromStep(nameof(C))] string c,
[FromStep(nameof(D))] string d)
{
return "e";
}
}

31 changes: 31 additions & 0 deletions example/HelloWorld/HelloWorld.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) LittleLittleCloud. All rights reserved.
// HelloWorld.cs

// Create a web host running on 5123 port
using StepWise.Core;

public class HelloWorld
{
[Step]
public async Task<string> SayHelloAsync()
{
return $"Hello";
}

[Step]
[DependOn(nameof(SayHelloAsync))]
public async Task<string> SayHelloWorldAsync([FromStep(nameof(SayHelloAsync))] string hello)
{
return $"{hello} World!";
}

[Step]
[DependOn(nameof(SayHelloWorldAsync))]
public async Task<string> GetNameAsync(
[FromStep(nameof(SayHelloWorldAsync))] string helloWorld,
string name = "LittleLittleCloud")
{
return $"{helloWorld}, {name}";
}
}

14 changes: 14 additions & 0 deletions example/HelloWorld/HelloWorld.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\StepWise\StepWise.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit 56b772c

Please sign in to comment.