Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
517 changes: 495 additions & 22 deletions .github/ISSUE_TEMPLATE/model-verification.yml

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions .github/workflows/chore-update-model-verification-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: 🔄 Update Model Verification Template

# Description: Keeps the model dropdown in the model-verification issue template
# in sync with the *ProviderModels.cs source files. Runs the
# Update-ModelVerificationTemplate.ps1 script whenever provider model
# definitions change on the default branch, and opens a PR with the result.
#
# Triggers:
# - push to main touching any *ProviderModels.cs file
# - workflow_dispatch for manual runs

on:
push:
branches: [main]
paths:
- 'src/SmartHopper.Providers.*/[A-Z]*ProviderModels.cs'
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
update-template:
name: Update issue template dropdown
runs-on: windows-latest
concurrency:
group: update-model-verification-template
cancel-in-progress: true
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 1

- name: Run Update-ModelVerificationTemplate.ps1
id: update
shell: pwsh
run: |
pwsh -ExecutionPolicy Bypass -File .\tools\Update-ModelVerificationTemplate.ps1
$code = $LASTEXITCODE
if ($code -eq 0) {
"changed=true" | Out-File -Append -FilePath $env:GITHUB_OUTPUT
} elseif ($code -eq 1) {
"changed=false" | Out-File -Append -FilePath $env:GITHUB_OUTPUT
} else {
throw "Update-ModelVerificationTemplate.ps1 failed with exit code $code"
}

- name: Emit step summary
if: always()
shell: pwsh
run: |
$changed = '${{ steps.update.outputs.changed }}'
if ($changed -eq 'true') {
Write-Output "## Model verification template updated" >> $env:GITHUB_STEP_SUMMARY
Write-Output "The dropdown options in ``model-verification.yml`` were refreshed from ``*ProviderModels.cs``." >> $env:GITHUB_STEP_SUMMARY
} else {
Write-Output "## No changes needed" >> $env:GITHUB_STEP_SUMMARY
Write-Output "The dropdown options in ``model-verification.yml`` are already up to date." >> $env:GITHUB_STEP_SUMMARY
}

- name: Create Pull Request
if: steps.update.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore(templates): update model-verification dropdown options [ci skip]"
title: "chore(templates): sync model-verification template with ProviderModels"
body: |
Auto-generated PR from `.github/workflows/chore-update-model-verification-template.yml`.

The model dropdown in `.github/ISSUE_TEMPLATE/model-verification.yml` has been
refreshed to reflect the current non-deprecated models declared in each
`*ProviderModels.cs` file.
branch: "chore/update-model-verification-template"
base: main
delete-branch: true
labels: |
automated
28 changes: 20 additions & 8 deletions .github/workflows/model-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,29 @@ jobs:

const body = issue.body || '';

// Parse provider (dropdown rendered as plain text under "### Provider")
const providerMatch = body.match(/###\s*Provider\s*\r?\n\s*([A-Za-z0-9_\-]+)/);
const modelMatch = body.match(/###\s*Model name\s*\r?\n\s*([^\r\n]+?)\s*$/m);
if (!providerMatch || !modelMatch) {
core.warning('Could not parse provider or model from issue body.');
// Parse Provider / Model (dropdown rendered as plain text under "### Provider / Model")
const providerModelMatch = body.match(/###\s*Provider \/ Model\s*\r?\n\s*([A-Za-z0-9_\-]+)\s*\/\s*([^\r\n]+?)\s*$/m);
const versionMatch = body.match(/###\s*SmartHopper Version\s*\r?\n\s*([^\r\n]+?)\s*$/m);
if (!providerModelMatch) {
core.warning('Could not parse provider and model from issue body.');
core.setOutput('should_promote', 'false');
return;
}
const provider = providerMatch[1].trim();
const model = modelMatch[1].trim();
core.info(`Parsed provider='${provider}', model='${model}'`);
const provider = providerModelMatch[1].trim();
const model = providerModelMatch[2].trim();
const version = versionMatch ? versionMatch[1].trim() : 'unknown';
core.info(`Parsed provider='${provider}', model='${model}', version='${version}'`);

// Check if title needs updating (it starts as just "[model verification]")
if (issue.title === "[model verification]") {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
title: `[model verification] ${provider} / ${model} (v${version})`
});
core.info(`Updated issue title to include model and version`);
}

// The "I confirm" checkbox must be checked for the author to count.
const authorConfirms = /-\s*\[x\][^\n]*I confirm that I personally ran/i.test(body);
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/release-3-pr-to-main-closed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,37 @@ jobs:
draft: true
prerelease: ${{ env.IS_PRERELEASE }}
target_commitish: ${{ steps.target-branch.outputs.branch }}

- name: Update Issue Template Version Dropdown
id: update_template
shell: pwsh
run: |
# The softprops action above doesn't fetch tags immediately into the local workspace,
# but the new tag exists remotely. We fetch it first.
git fetch --tags
pwsh -ExecutionPolicy Bypass -File .\tools\Update-VersionVerificationTemplate.ps1 -MinMajor 1
$code = $LASTEXITCODE
if ($code -eq 0) {
"changed=true" | Out-File -Append -FilePath $env:GITHUB_OUTPUT
} elseif ($code -eq 1) {
"changed=false" | Out-File -Append -FilePath $env:GITHUB_OUTPUT
} else {
throw "Update-VersionVerificationTemplate.ps1 failed with exit code $code"
}

- name: Create Pull Request for Template Update
if: steps.update_template.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore(templates): add ${{ steps.release_info.outputs.version }} to model-verification template [ci skip]"
title: "chore(templates): add ${{ steps.release_info.outputs.version }} to model-verification dropdown"
body: |
Auto-generated PR from `.github/workflows/release-3-pr-to-main-closed.yml`.

Adds the newly released version `${{ steps.release_info.outputs.version }}` to the version dropdown in the model verification issue template.
branch: "chore/update-version-dropdown-${{ steps.release_info.outputs.version }}"
base: ${{ steps.target-branch.outputs.branch }}
delete-branch: true
labels: |
automated
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 Tools and Assistant for Grasshopper3D

[![Version](https://img.shields.io/badge/version-2.0.0--dev.260503-brown?style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/releases)
[![Version](https://img.shields.io/badge/version-2.0.0--dev.260504-brown?style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/releases)
[![Status](https://img.shields.io/badge/status-Unstable%20Development-brown?style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/releases)
[![.NET CI](https://img.shields.io/github/actions/workflow/status/architects-toolkit/SmartHopper/.github/workflows/ci-dotnet-tests.yml?label=tests&logo=dotnet&style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/actions/workflows/ci-dotnet-tests.yml)
[![Ready to use](https://img.shields.io/badge/ready_to_use-NO-brown?style=for-the-badge)](https://smarthopper.xyz/#installation)
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>2.0.0-dev.260503</SolutionVersion>
<SolutionVersion>2.0.0-dev.260504</SolutionVersion>
</PropertyGroup>
</Project>
172 changes: 172 additions & 0 deletions tools/Update-ModelVerificationTemplate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<#
.SYNOPSIS
Updates the model-verification issue template dropdown with all non-deprecated
models from each *ProviderModels.cs file.

.DESCRIPTION
Scans every src/SmartHopper.Providers.<Provider>/<Provider>ProviderModels.cs file,
extracts non-deprecated model names together with their provider, and rewrites
the options list in .github/ISSUE_TEMPLATE/model-verification.yml between the
AUTO-GENERATED-MODEL-OPTIONS markers.

Models are sorted by provider (alphabetical), then by Rank (descending, so the
most relevant models appear first within each provider group).

Exits with code 0 when the template was modified, 1 when no change was needed,
and 2 on error.

.PARAMETER TemplateFile
Optional. Path to the issue template YAML file.
Defaults to .github/ISSUE_TEMPLATE/model-verification.yml relative to the repo root.

.EXAMPLE
pwsh -File tools/Update-ModelVerificationTemplate.ps1
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)][string] $TemplateFile = ""
)

$ErrorActionPreference = 'Stop'

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..')

if ([string]::IsNullOrWhiteSpace($TemplateFile)) {
$TemplateFile = Join-Path $repoRoot '.github/ISSUE_TEMPLATE/model-verification.yml'
}

$startMarker = '# AUTO-GENERATED-MODEL-OPTIONS-START'
$endMarker = '# AUTO-GENERATED-MODEL-OPTIONS-END'

# ---------------------------------------------------------------------------
# Helper: Parse model blocks from a ProviderModels.cs file
# ---------------------------------------------------------------------------
function Get-ProviderModelsFromFile($filePath, $providerName) {
$content = [System.IO.File]::ReadAllText($filePath, [System.Text.Encoding]::UTF8)
$content = $content -replace '^[\uFEFF]', ''

$blockRx = [regex]::new(
'new\s+AIModelCapabilities\s*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\},?',
[System.Text.RegularExpressions.RegexOptions]::Singleline
)
$blocks = $blockRx.Matches($content)
$results = [System.Collections.Generic.List[psobject]]::new()

foreach ($block in $blocks) {
$text = $block.Value

# Skip deprecated models
if ([regex]::IsMatch($text, 'Deprecated\s*=\s*true')) { continue }

# Extract model name
$modelMatch = [regex]::Match($text, 'Model\s*=\s*"([^"]*)"')
if (-not $modelMatch.Success) { continue }
$model = $modelMatch.Groups[1].Value

# Extract rank (default to 0 if not present)
$rankMatch = [regex]::Match($text, 'Rank\s*=\s*(-?\d+)')
$rank = if ($rankMatch.Success) { [int]$rankMatch.Groups[1].Value } else { 0 }

$results.Add([pscustomobject]@{
Provider = $providerName
Model = $model
Rank = $rank
})
}

return $results
}

# ---------------------------------------------------------------------------
# 1. Discover all *ProviderModels.cs files
# ---------------------------------------------------------------------------
$srcDir = Join-Path $repoRoot 'src'
$providerFiles = Get-ChildItem -Path $srcDir -Filter '*ProviderModels.cs' -Recurse |
Where-Object {
$_.FullName -match 'SmartHopper\.Providers\.(\w+)[/\\]\1ProviderModels\.cs$'
}

if ($providerFiles.Count -eq 0) {
Write-Error "No *ProviderModels.cs files found under $srcDir"
exit 2
}

# ---------------------------------------------------------------------------
# 2. Extract non-deprecated models from each file
# ---------------------------------------------------------------------------
$allModels = [System.Collections.Generic.List[psobject]]::new()

foreach ($file in $providerFiles) {
$providerMatch = [regex]::Match($file.DirectoryName, 'SmartHopper\.Providers\.(\w+)$')
if (-not $providerMatch.Success) { continue }
$provider = $providerMatch.Groups[1].Value

$models = Get-ProviderModelsFromFile -filePath $file.FullName -providerName $provider
Write-Host "[$provider] Found $($models.Count) non-deprecated model(s)."

foreach ($m in $models) {
$allModels.Add($m)
}
}

Write-Host "Total non-deprecated models: $($allModels.Count)"

# ---------------------------------------------------------------------------
# 3. Sort: provider (alpha ascending), then rank (descending)
# ---------------------------------------------------------------------------
$sorted = $allModels | Sort-Object -Property `
@{ Expression = { $_.Provider }; Ascending = $true },
@{ Expression = { $_.Rank }; Descending = $true }

# Build option lines (8-space indent + "- Provider / Model")
$optionLines = [System.Collections.Generic.List[string]]::new()
foreach ($m in $sorted) {
$optionLines.Add(" - $($m.Provider) / $($m.Model)")
}

# ---------------------------------------------------------------------------
# 4. Read template and replace between markers
# ---------------------------------------------------------------------------
if (-not (Test-Path $TemplateFile)) {
Write-Error "Template file not found: $TemplateFile"
exit 2
}

$templateLines = [System.IO.File]::ReadAllLines($TemplateFile, [System.Text.Encoding]::UTF8)

$startIdx = -1
$endIdx = -1

for ($i = 0; $i -lt $templateLines.Count; $i++) {
if ($templateLines[$i].TrimEnd() -match [regex]::Escape($startMarker)) {
$startIdx = $i
}
if ($templateLines[$i].TrimEnd() -match [regex]::Escape($endMarker)) {
$endIdx = $i
}
}

if ($startIdx -lt 0 -or $endIdx -lt 0 -or $endIdx -le $startIdx) {
Write-Error "Could not find AUTO-GENERATED-MODEL-OPTIONS markers in $TemplateFile"
exit 2
}

# Preserve the marker lines themselves, replace everything between
$before = $templateLines[0..$startIdx]
$after = $templateLines[$endIdx..($templateLines.Count - 1)]
$newLines = @($before) + @($optionLines) + @($after)

$newContent = ($newLines -join "`n") + "`n"
$oldContent = [System.IO.File]::ReadAllText($TemplateFile, [System.Text.Encoding]::UTF8)

if ($newContent -eq $oldContent) {
Write-Host "No changes needed in $TemplateFile"
exit 1
}

[System.IO.File]::WriteAllText($TemplateFile, $newContent, [System.Text.Encoding]::UTF8)
Write-Host "Updated $TemplateFile with $($optionLines.Count) model option(s)."
exit 0
Loading
Loading