Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
18a376e
ci: add release as accepted pr titles
marc-romu Jul 22, 2025
63d80ef
fix(statefulcomponent): prevent false positive on input change calcul…
marc-romu Jul 22, 2025
e729236
fix(statefulcomponent): prevent unnecessary transitions to needsrun o…
marc-romu Jul 22, 2025
3d5fcb8
ci(tests): prevent duplicated trigger for PR to main returning fail s…
marc-romu Jul 23, 2025
00a30d2
fix(statefulcomponent): stuck components and missing metrics when usi…
marc-romu Jul 23, 2025
b53b4d3
refactor(statefulcomponent): added debug compilation conditionals
marc-romu Jul 23, 2025
7c795b9
fix(statefulcomponent): stuck components, missing metrics, and input …
marc-romu Jul 23, 2025
bb9d127
docs: update version badge for dev
actions-user Jul 23, 2025
169f949
docs: update version badge for dev to 0.4.1-dev.250723 (#269)
marc-romu Jul 23, 2025
e6ecd84
fix(gh_toggle_preview): now compatible with params too
marc-romu Jul 23, 2025
c7b40de
refactor: cleanup
marc-romu Jul 23, 2025
929cd2e
ci(pr-milestone): create missing milestones
marc-romu Jul 23, 2025
c95509b
fix(ghtogglepreview): now compatible with params too (#270)
marc-romu Jul 23, 2025
aca3542
feat(components): progress reporting
marc-romu Jul 23, 2025
fe2ebce
fix(chat): automatically close open chats on rhino close
marc-romu Jul 23, 2025
f9b813e
chore: prepare release 0.4.1-alpha with version update and code style…
actions-user Jul 23, 2025
0c00147
chore: prepare release 0.4.1-alpha with version update and code style…
marc-romu Jul 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci-dotnet-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: 🧪 .NET CI
#
# Triggers:
# - push to main branch
# - pull_request targeting main branch
# - pull_request targeting main, dev and release branches
#
# Permissions:
# - contents: read - Required to read repository content
Expand All @@ -14,7 +14,6 @@ on:
push:
branches:
- main
- dev
pull_request:
branches:
- main
Expand Down
27 changes: 23 additions & 4 deletions .github/workflows/pr-milestone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,35 @@ jobs:
state: 'all' // Include both open and closed milestones
});

const targetMilestone = milestones.find(milestone => milestone.title === processedVersion);
let targetMilestone = milestones.find(milestone => milestone.title === processedVersion);

if (!targetMilestone) {
console.log(`No milestone found with title: ${processedVersion}`);
console.log('Available milestones:', milestones.map(m => m.title));
return;

// Create the milestone if it doesn't exist
console.log(`Creating new milestone: ${processedVersion}`);
try {
const { data: newMilestone } = await github.rest.issues.createMilestone({
owner: context.repo.owner,
repo: context.repo.repo,
title: processedVersion,
description: `Milestone for version ${processedVersion}`,
state: 'open'
});

targetMilestone = newMilestone;
console.log(`Successfully created milestone: ${targetMilestone.title}`);

} catch (error) {
console.error('Error creating milestone:', error);
core.setFailed(`Failed to create milestone: ${error.message}`);
return;
}
} else {
console.log(`Found existing milestone: ${targetMilestone.title} (${targetMilestone.state})`);
}

console.log(`Found milestone: ${targetMilestone.title} (${targetMilestone.state})`);

// Assign PR to milestone
try {
await github.rest.issues.update({
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ jobs:
echo "Checking PR title: $PR_TITLE"

# Valid types based on conventional commits
VALID_TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|security"
VALID_TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|security|release"

# Check if PR title follows conventional commits format
if [[ ! $PR_TITLE =~ ^($VALID_TYPES)(\([a-z0-9-]+\))?:\ .+ ]]; then
Expand All @@ -150,6 +150,7 @@ jobs:
echo " chore: Other changes that don't modify src or test files"
echo " revert: Revert a previous commit"
echo " security: Security-related changes"
echo " release: Release PR"
echo ""
echo "Examples:"
echo " feat: add new grasshopper component"
Expand Down
3 changes: 2 additions & 1 deletion .windsurf/workflows/new-branch.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Where:
### Available Prefixes

- `feature`: New functionality
- `fix`: Bug fixes
- `bugfix`: Bug fixes
- `hotfix`: Bugs that need to be fixed urgently in production
- `docs`: Documentation changes
- `refactor`: Code changes that neither fix bugs nor add features
- `test`: Adding missing tests or correcting tests
Expand Down
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.1-alpha] - 2025-07-23

### Added

- New `ProgressInfo` class to `StatefulAsyncComponentBase` to provide progress information to the UI. It allows to display a dynamic progress reporting which branch is being processed.

### Fixed

- Multiple fixes to `StatefulAsyncComponentBase`:
- Fixed issue: Components now transition to "Done" state when opening files with existing results instead of "Run me!" ([#113](https://github.com/architects-toolkit/SmartHopper/issues/113))
- Calculate changed inputs based on actual values, not on object instances, to prevent false positives when connecting new sources with same values.
- Fixed issue: Stuck components when using Boolean toggle ([#260](https://github.com/architects-toolkit/SmartHopper/issues/260)).
- Fixed issue: Output metrics not being set when using Boolean toggle.
- Fixed issue ([#208](https://github.com/architects-toolkit/SmartHopper/issues/208)): enabled compatibility with params in `gh_toggle_preview` tool.
- Fixed WebChatDialog not automatically closing when Rhino is closed.

## [0.4.0-alpha] - 2025-07-22

### Added
Expand Down Expand Up @@ -36,7 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix incorrect model handling in `AIStatefulAsyncComponentBase`.
- Fixed certificate creation tests to handle CI environment constraints
- Updated `GhRetrieveComponents` to use the correct ai tool `gh_list_components` instead of `gh_get_available_components`
- Fixes ""Missing required parameter: ‘response_format.json_schema" in text-list-generate with OpenAI provider" ([#259](https://github.com/architects-toolkit/SmartHopper/issues/259)).
- Fixes "Missing required parameter: ‘response_format.json_schema' in text-list-generate with OpenAI provider" ([#259](https://github.com/architects-toolkit/SmartHopper/issues/259)).

## [0.3.6-alpha] - 2025-07-20

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SmartHopper - AI-Powered Grasshopper3D Plugin

[![Version](https://img.shields.io/badge/version-0%2E4%2E0--alpha-orange)](https://github.com/architects-toolkit/SmartHopper/releases)
[![Version](https://img.shields.io/badge/version-0%2E4%2E1--alpha-orange)](https://github.com/architects-toolkit/SmartHopper/releases)
[![Status](https://img.shields.io/badge/status-Alpha-orange)](https://github.com/architects-toolkit/SmartHopper/releases)
[![Test results](https://img.shields.io/github/actions/workflow/status/architects-toolkit/SmartHopper/.github/workflows/ci-dotnet-tests.yml?label=.NET%20CI&logo=dotnet)](https://github.com/architects-toolkit/SmartHopper/actions/workflows/ci-dotnet-tests.yml)
[![Grasshopper](https://img.shields.io/badge/plugin_for-Grasshopper3D-darkgreen?logo=rhinoceros)](https://www.rhino3d.com/)
Expand Down
2 changes: 1 addition & 1 deletion Solution.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<SolutionVersion>0.4.0-alpha</SolutionVersion>
<SolutionVersion>0.4.1-alpha</SolutionVersion>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/SmartHopper.Components/List/AIListEvaluate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public override async Task DoWorkAsync(CancellationToken token)
Debug.WriteLine($"[Worker] Input tree keys: {string.Join(", ", this.inputTree.Keys)}");
Debug.WriteLine($"[Worker] Input tree data counts: {string.Join(", ", this.inputTree.Select(kvp => $"{kvp.Key}: {kvp.Value.DataCount}"))}");

this.result = await DataTreeProcessor.RunFunctionAsync(
this.result = await this.parent.RunDataTreeFunctionAsync(
this.inputTree,
async (branches, reuseCount) =>
{
Expand Down
2 changes: 1 addition & 1 deletion src/SmartHopper.Components/List/AIListFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public override async Task DoWorkAsync(CancellationToken token)
Debug.WriteLine($"[Worker] Input tree keys: {string.Join(", ", this.inputTree.Keys)}");
Debug.WriteLine($"[Worker] Input tree data counts: {string.Join(", ", this.inputTree.Select(kvp => $"{kvp.Key}: {kvp.Value.DataCount}"))}");

this.result = await DataTreeProcessor.RunFunctionAsync(
this.result = await this.parent.RunDataTreeFunctionAsync(
this.inputTree,
async (branches, reuseCount) =>
{
Expand Down
2 changes: 1 addition & 1 deletion src/SmartHopper.Components/Text/AITextEvaluate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public override async Task DoWorkAsync(CancellationToken token)
Debug.WriteLine($"[Worker] Input tree keys: {string.Join(", ", this.inputTree.Keys)}");
Debug.WriteLine($"[Worker] Input tree data counts: {string.Join(", ", this.inputTree.Select(kvp => $"{kvp.Key}: {kvp.Value.DataCount}"))}");

this.result = await DataTreeProcessor.RunFunctionAsync(
this.result = await this.parent.RunDataTreeFunctionAsync(
this.inputTree,
async (branches, reuseCount) =>
{
Expand Down
2 changes: 1 addition & 1 deletion src/SmartHopper.Components/Text/AITextGenerate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public override async Task DoWorkAsync(CancellationToken token)
Debug.WriteLine($"[Worker] Input tree keys: {string.Join(", ", this.inputTree.Keys)}");
Debug.WriteLine($"[Worker] Input tree data counts: {string.Join(", ", this.inputTree.Select(kvp => $"{kvp.Key}: {kvp.Value.DataCount}"))}");

this.result = await DataTreeProcessor.RunFunctionAsync(
this.result = await this.parent.RunDataTreeFunctionAsync(
this.inputTree,
async (branches, reuseCount) =>
{
Expand Down
2 changes: 1 addition & 1 deletion src/SmartHopper.Components/Text/AITextListGenerate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public override async Task DoWorkAsync(CancellationToken token)
try
{
Debug.WriteLine($"[AITextListGenerate] Starting DoWorkAsync");
this.result = await DataTreeProcessor.RunFunctionAsync(
this.result = await this.parent.RunDataTreeFunctionAsync(
this.inputTree,
async (branches, reuseCount) =>
{
Expand Down
32 changes: 24 additions & 8 deletions src/SmartHopper.Core.Grasshopper/Utils/GHComponentUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,32 @@ public static void SetComponentPreview(Guid guid, bool previewOn, bool redraw =
Debug.WriteLine("[GHComponentUtils] Component is not preview capable");
}
}
else if (obj is IGH_PreviewObject preview)
else if (obj is IGH_Param param)
{
Debug.WriteLine($"[GHComponentUtils] IGH_PreviewObject.Hidden={preview.Hidden}");
obj.RecordUndoEvent("[SH] Set Component Preview");
preview.Hidden = !previewOn;
Debug.WriteLine($"[GHComponentUtils] New Hidden={preview.Hidden}");
if (redraw)
Debug.WriteLine($"[GHComponentUtils] IGH_Param found: {param.GetType().Name}");
// Check if the parameter implements IGH_PreviewObject for preview capabilities
if (param is IGH_PreviewObject paramPreview)
{
Instances.RedrawCanvas();
Debug.WriteLine("[GHComponentUtils] Canvas redrawn");
Debug.WriteLine($"[GHComponentUtils] IGH_Param.Hidden={paramPreview.Hidden}, IsPreviewCapable={paramPreview.IsPreviewCapable}");
if (paramPreview.IsPreviewCapable)
{
obj.RecordUndoEvent("[SH] Set Parameter Preview");
paramPreview.Hidden = !previewOn;
Debug.WriteLine($"[GHComponentUtils] New Hidden={paramPreview.Hidden}");
if (redraw)
{
Instances.RedrawCanvas();
Debug.WriteLine("[GHComponentUtils] Canvas redrawn");
}
}
else
{
Debug.WriteLine("[GHComponentUtils] IGH_Param is not preview capable");
}
}
else
{
Debug.WriteLine("[GHComponentUtils] IGH_Param does not implement IGH_PreviewObject");
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,11 @@ protected override void BeforeSolveInstance()
base.BeforeSolveInstance();

// Clear previous response metrics only when starting a new run
if (this.CurrentState == ComponentState.Processing && this.Run)
// Fix for boolean toggle issue: Only clear when Run is true AND we have no active workers
// This ensures we're truly starting a new processing run, not in the middle of one
if (this.CurrentState == ComponentState.Processing && this.Run && this.Workers.Count == 0)
{
Debug.WriteLine("[AIStatefulAsyncComponentBase] Cleaning previous response metrics");

// Clear the stored metrics on start a new run
Debug.WriteLine("[AIStatefulAsyncComponentBase] Cleaning previous response metrics for new Processing run");
_responseMetrics.Clear();
}
}
Expand Down
45 changes: 35 additions & 10 deletions src/SmartHopper.Core/ComponentBase/AsyncComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,12 @@ protected override void BeforeSolveInstance()
}

Debug.WriteLine("[AsyncComponentBase] BeforeSolveInstance - Cleaning up previous run");
foreach (var source in this._cancellationSources)
{
source.Cancel();
}

// Cancel any running tasks before reset
this.TaskCancellation();

this._cancellationSources.Clear();
this._tasks.Clear();
this.Workers.Clear();
this._state = 0;
this._setData = 0;
// Use the centralized reset method
this.ResetAsyncState();
}

protected override void SolveInstance(IGH_DataAccess DA)
Expand Down Expand Up @@ -271,14 +267,43 @@ protected override void AfterSolveInstance()
}
}

public virtual void RequestTaskCancellation()
private void TaskCancellation()
{
foreach (var source in this._cancellationSources)
{
source.Cancel();
}
}

public virtual void RequestTaskCancellation()
{
this.TaskCancellation();
}

/// <summary>
/// Resets the AsyncComponentBase state variables to allow fresh worker creation.
/// This is needed when transitioning to Processing state from other states,
/// especially for boolean toggle scenarios where async state needs to be reset.
/// </summary>
protected void ResetAsyncState()
{
Debug.WriteLine($"[{this.GetType().Name}] ResetAsyncState - Before: State={this._state}, Tasks={this._tasks?.Count ?? 0}, SetData={this._setData}");

// Clear cancellation sources from previous runs
this._cancellationSources?.Clear();

// Clear any existing tasks and workers from previous runs
this._tasks?.Clear();
this.Workers?.Clear();

// Reset the async component state variables to initial values
// This ensures that AsyncComponentBase.SolveInstance() will create new workers
this._state = 0;
this._setData = 0;

Debug.WriteLine($"[{this.GetType().Name}] ResetAsyncState - After: State={this._state}, Tasks={this._tasks?.Count ?? 0}, SetData={this._setData}");
}

public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Expand Down
79 changes: 79 additions & 0 deletions src/SmartHopper.Core/ComponentBase/ProgressInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* SmartHopper - AI-powered Grasshopper Plugin
* Copyright (C) 2025 Marc Roca Musach
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*/

namespace SmartHopper.Core.ComponentBase
{
/// <summary>
/// Represents progress information for component processing operations.
/// </summary>
public class ProgressInfo
{
/// <summary>
/// Gets or sets the current item being processed (1-based).
/// </summary>
public int Current { get; set; }

/// <summary>
/// Gets or sets the total number of items to process.
/// </summary>
public int Total { get; set; }

/// <summary>
/// Gets a value indicating whether progress tracking is active.
/// </summary>
public bool IsActive => this.Total > 0;

/// <summary>
/// Initializes a new instance of the <see cref="ProgressInfo"/> class.
/// </summary>
public ProgressInfo()
{
this.Current = 0;
this.Total = 0;
}

/// <summary>
/// Initializes a new instance of the <see cref="ProgressInfo"/> class.
/// </summary>
/// <param name="total">The total number of items to process.</param>
public ProgressInfo(int total)
{
this.Current = total > 0 ? 1 : 0;
this.Total = total;
}

/// <summary>
/// Updates the current progress.
/// </summary>
/// <param name="current">The current item being processed (1-based).</param>
public void UpdateCurrent(int current)
{
this.Current = current <= this.Total ? current : this.Total;
}

/// <summary>
/// Resets the progress information.
/// </summary>
public void Reset()
{
this.Current = 0;
this.Total = 0;
}

/// <summary>
/// Gets a formatted progress string.
/// </summary>
/// <returns>A string like "1/3" if progress is active, otherwise empty string.</returns>
public string GetProgressString()
{
return this.IsActive ? $"{this.Current}/{this.Total}" : string.Empty;
}
}
}
Loading
Loading