Merge branch 'main' into sno-enable-treeshake

This commit is contained in:
sno
2025-10-17 09:49:24 +09:00
committed by GitHub
28 changed files with 166 additions and 56 deletions

21
.github/workflows/README.md vendored Normal file
View File

@@ -0,0 +1,21 @@
# GitHub Workflows
## Naming Convention
Workflow files follow a consistent naming pattern: `<prefix>-<descriptive-name>.yaml`
### Category Prefixes
| Prefix | Purpose | Example |
| ---------- | ----------------------------------- | ------------------------------------ |
| `ci-` | Testing, linting, validation | `ci-tests-e2e.yaml` |
| `release-` | Version management, publishing | `release-version-bump.yaml` |
| `pr-` | PR automation (triggered by labels) | `pr-claude-review.yaml` |
| `api-` | External Api type generation | `api-update-registry-api-types.yaml` |
| `i18n-` | Internationalization updates | `i18n-update-core.yaml` |
## Documentation
Each workflow file contains comments explaining its purpose, triggers, and behavior. For specific details about what each workflow does, refer to the comments at the top of each `.yaml` file.
For GitHub Actions documentation, see [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows).

View File

@@ -1,4 +1,5 @@
name: Update Electron Types name: 'Api: Update Electron API Types'
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
on: on:
workflow_dispatch: workflow_dispatch:

View File

@@ -1,4 +1,5 @@
name: Update ComfyUI-Manager API Types name: 'Api: Update Manager API Types'
description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo'
on: on:
# Manual trigger # Manual trigger

View File

@@ -1,4 +1,5 @@
name: Update Comfy Registry API Types name: 'Api: Update Registry API Types'
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
on: on:
# Manual trigger # Manual trigger

View File

@@ -1,4 +1,5 @@
name: Validate JSON name: "CI: JSON Validation"
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
on: on:
push: push:

View File

@@ -1,4 +1,5 @@
name: Lint and Format name: "CI: Lint Format"
description: "Linting and code formatting validation for pull requests"
on: on:
pull_request: pull_request:

View File

@@ -1,4 +1,5 @@
name: Devtools Python Check name: "CI: Python Validation"
description: "Validates Python code in tools/devtools directory"
on: on:
pull_request: pull_request:

View File

@@ -1,8 +1,9 @@
name: PR Playwright Deploy (Forks) name: "CI: Tests E2E (Deploy for Forks)"
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
on: on:
workflow_run: workflow_run:
workflows: ["Tests CI"] workflows: ["CI: Tests E2E"]
types: [requested, completed] types: [requested, completed]
env: env:

View File

@@ -1,4 +1,5 @@
name: Tests CI name: "CI: Tests E2E"
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
on: on:
push: push:

View File

@@ -1,8 +1,9 @@
name: PR Storybook Deploy (Forks) name: "CI: Tests Storybook (Deploy for Forks)"
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
on: on:
workflow_run: workflow_run:
workflows: ['Storybook and Chromatic CI'] workflows: ["CI: Tests Storybook"]
types: [requested, completed] types: [requested, completed]
env: env:

View File

@@ -1,6 +1,5 @@
name: Storybook and Chromatic CI name: "CI: Tests Storybook"
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ )
on: on:
workflow_dispatch: # Allow manual triggering workflow_dispatch: # Allow manual triggering

View File

@@ -1,4 +1,5 @@
name: Vitest Tests name: "CI: Tests Unit"
description: "Unit and component testing with Vitest"
on: on:
push: push:

View File

@@ -1,4 +1,5 @@
name: Update Locales name: "i18n: Update Core"
description: "Generates and updates translations for core ComfyUI components using OpenAI"
on: on:
# Manual dispatch for urgent translation updates # Manual dispatch for urgent translation updates
@@ -54,5 +55,5 @@ jobs:
# Apply the stashed changes if any # Apply the stashed changes if any
git stash pop || true git stash pop || true
git add src/locales/ git add src/locales/
git diff --staged --quiet || git commit -m "Update locales [skip ci]" git diff --staged --quiet || git commit -m "Update locales"
git push origin HEAD:${{ github.head_ref }} git push origin HEAD:${{ github.head_ref }}

View File

@@ -1,4 +1,4 @@
name: Update Locales for given custom node repository name: i18n Update Custom Nodes
on: on:
workflow_dispatch: workflow_dispatch:

View File

@@ -1,4 +1,4 @@
name: Update Node Definitions Locales name: i18n Update Nodes
on: on:
workflow_dispatch: workflow_dispatch:

View File

@@ -1,4 +1,4 @@
name: Auto Backport name: PR Backport
on: on:
pull_request_target: pull_request_target:

View File

@@ -1,4 +1,5 @@
name: Claude PR Review name: "PR: Claude Review"
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
permissions: permissions:
contents: read contents: read

View File

@@ -1,5 +1,5 @@
# Setting test expectation screenshots for Playwright # Setting test expectation screenshots for Playwright
name: Update Playwright Expectations name: "PR: Update Playwright Expectations"
on: on:
pull_request: pull_request:

View File

@@ -1,4 +1,4 @@
name: Create Release Branch name: Release Branch Create
on: on:
pull_request: pull_request:

View File

@@ -1,4 +1,4 @@
name: Create Release Draft name: Release Draft Create
on: on:
pull_request: pull_request:
@@ -126,7 +126,7 @@ jobs:
publish_types: publish_types:
needs: build needs: build
uses: ./.github/workflows/publish-frontend-types.yaml uses: ./.github/workflows/release-npm-types.yaml
with: with:
version: ${{ needs.build.outputs.version }} version: ${{ needs.build.outputs.version }}
ref: ${{ github.event.pull_request.merge_commit_sha }} ref: ${{ github.event.pull_request.merge_commit_sha }}

View File

@@ -1,4 +1,4 @@
name: Publish Frontend Types name: Release NPM Types
on: on:
workflow_dispatch: workflow_dispatch:

View File

@@ -1,4 +1,4 @@
name: Create Dev PyPI Package name: Release PyPI Dev
on: on:
workflow_dispatch: workflow_dispatch:

View File

@@ -1,4 +1,5 @@
name: Version Bump name: "Release: Version Bump"
description: "Manual workflow to increment package version with semantic versioning support"
on: on:
workflow_dispatch: workflow_dispatch:

View File

@@ -119,6 +119,7 @@ const onConfigure = function (
this.properties.proxyWidgets = serialisedNode.properties.proxyWidgets this.properties.proxyWidgets = serialisedNode.properties.proxyWidgets
const parsed = parseProxyWidgets(serialisedNode.properties.proxyWidgets) const parsed = parseProxyWidgets(serialisedNode.properties.proxyWidgets)
serialisedNode.widgets_values?.forEach((v, index) => { serialisedNode.widgets_values?.forEach((v, index) => {
if (parsed[index]?.[0] !== '-1') return
const widget = this.widgets.find((w) => w.name == parsed[index][1]) const widget = this.widgets.find((w) => w.name == parsed[index][1])
if (v !== null && widget) widget.value = v if (v !== null && widget) widget.value = v
}) })

View File

@@ -84,6 +84,51 @@ export const NonInteractive: Story = {
} }
} }
export const WithPreviewImage: Story = {
args: {
asset: createAssetData({
preview_url: '/assets/images/comfy-logo-single.svg'
}),
interactive: true
},
decorators: [
() => ({
template:
'<div class="p-8 bg-gray-50 dark-theme:bg-gray-900 max-w-96"><story /></div>'
})
],
parameters: {
docs: {
description: {
story: 'AssetCard with a preview image displayed.'
}
}
}
}
export const FallbackGradient: Story = {
args: {
asset: createAssetData({
preview_url: undefined
}),
interactive: true
},
decorators: [
() => ({
template:
'<div class="p-8 bg-gray-50 dark-theme:bg-gray-900 max-w-96"><story /></div>'
})
],
parameters: {
docs: {
description: {
story:
'AssetCard showing fallback gradient when no preview image is available.'
}
}
}
}
export const EdgeCases: Story = { export const EdgeCases: Story = {
render: () => ({ render: () => ({
components: { AssetCard }, components: { AssetCard },

View File

@@ -4,38 +4,29 @@
data-component-id="AssetCard" data-component-id="AssetCard"
:data-asset-id="asset.id" :data-asset-id="asset.id"
v-bind="elementProps" v-bind="elementProps"
:class=" :class="cardClasses"
cn(
// Base layout and container styles (always applied)
'rounded-xl overflow-hidden transition-all duration-200',
interactive && 'group',
// Button-specific styles
interactive && [
'appearance-none bg-transparent p-0 m-0 font-inherit text-inherit outline-none cursor-pointer text-left',
'bg-gray-100 dark-theme:bg-charcoal-800',
'hover:bg-gray-200 dark-theme:hover:bg-charcoal-600',
'border-none',
'focus:outline-solid outline-blue-100 outline-4'
],
// Div-specific styles
!interactive && 'bg-gray-100 dark-theme:bg-charcoal-800'
)
"
@click="interactive && $emit('select', asset)" @click="interactive && $emit('select', asset)"
@keydown.enter="interactive && $emit('select', asset)" @keydown.enter="interactive && $emit('select', asset)"
> >
<div class="relative aspect-square w-full overflow-hidden"> <div class="relative aspect-square w-full overflow-hidden rounded-xl">
<img
v-if="shouldShowImage"
:src="asset.preview_url"
class="h-full w-full object-contain"
/>
<div <div
class="flex h-full w-full items-center justify-center bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-600" v-else
class="flex h-full w-full items-center justify-center bg-gradient-to-br from-gray-400 via-gray-800 to-charcoal-400"
></div> ></div>
<AssetBadgeGroup :badges="asset.badges" /> <AssetBadgeGroup :badges="asset.badges" />
</div> </div>
<div :class="cn('p-4 h-32 flex flex-col justify-between')"> <div :class="cn('p-4 h-32 flex flex-col justify-between')">
<div> <div>
<h3 <h3
:id="titleId"
:class=" :class="
cn( cn(
'mb-2 m-0 text-base font-semibold overflow-hidden text-ellipsis whitespace-nowrap', 'mb-2 m-0 text-base font-semibold line-clamp-2 wrap-anywhere',
'text-slate-800', 'text-slate-800',
'dark-theme:text-white' 'dark-theme:text-white'
) )
@@ -44,6 +35,7 @@
{{ asset.name }} {{ asset.name }}
</h3> </h3>
<p <p
:id="descId"
:class=" :class="
cn( cn(
'm-0 text-sm leading-6 overflow-hidden [-webkit-box-orient:vertical] [-webkit-line-clamp:2] [display:-webkit-box]', 'm-0 text-sm leading-6 overflow-hidden [-webkit-box-orient:vertical] [-webkit-line-clamp:2] [display:-webkit-box]',
@@ -83,7 +75,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { useImage } from '@vueuse/core'
import { computed, useId } from 'vue'
import AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue' import AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue'
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser' import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
@@ -94,13 +87,51 @@ const props = defineProps<{
interactive?: boolean interactive?: boolean
}>() }>()
const titleId = useId()
const descId = useId()
const { error } = useImage({
src: props.asset.preview_url ?? '',
alt: props.asset.name
})
const shouldShowImage = computed(() => props.asset.preview_url && !error.value)
const cardClasses = computed(() => {
const base = [
'rounded-xl',
'overflow-hidden',
'transition-all',
'duration-200'
]
if (!props.interactive) {
return cn(...base, 'bg-gray-100 dark-theme:bg-charcoal-800')
}
return cn(
...base,
'group',
'appearance-none bg-transparent p-0 m-0',
'font-inherit text-inherit outline-none cursor-pointer text-left',
'bg-gray-100 dark-theme:bg-charcoal-800',
'hover:bg-gray-200 dark-theme:hover:bg-charcoal-600',
'border-none',
'focus:outline-solid outline-blue-100 outline-4'
)
})
const elementProps = computed(() => const elementProps = computed(() =>
props.interactive props.interactive
? { ? {
type: 'button', type: 'button',
'aria-label': `Select asset ${props.asset.name}` 'aria-labelledby': titleId,
'aria-describedby': descId
}
: {
'aria-labelledby': titleId,
'aria-describedby': descId
} }
: {}
) )
defineEmits<{ defineEmits<{

View File

@@ -12,7 +12,7 @@ import { useModelToNodeStore } from '@/stores/modelToNodeStore'
const ASSETS_ENDPOINT = '/assets' const ASSETS_ENDPOINT = '/assets'
const EXPERIMENTAL_WARNING = `EXPERIMENTAL: If you are seeing this please make sure "Comfy.Assets.UseAssetAPI" is set to "false" in your ComfyUI Settings.\n` const EXPERIMENTAL_WARNING = `EXPERIMENTAL: If you are seeing this please make sure "Comfy.Assets.UseAssetAPI" is set to "false" in your ComfyUI Settings.\n`
const DEFAULT_LIMIT = 300 const DEFAULT_LIMIT = 500
export const MODELS_TAG = 'models' export const MODELS_TAG = 'models'
export const MISSING_TAG = 'missing' export const MISSING_TAG = 'missing'

View File

@@ -117,7 +117,7 @@ describe('assetService', () => {
const result = await assetService.getAssetModelFolders() const result = await assetService.getAssetModelFolders()
expect(api.fetchApi).toHaveBeenCalledWith( expect(api.fetchApi).toHaveBeenCalledWith(
'/assets?include_tags=models&limit=300' '/assets?include_tags=models&limit=500'
) )
expect(result).toHaveLength(2) expect(result).toHaveLength(2)
@@ -163,7 +163,7 @@ describe('assetService', () => {
const result = await assetService.getAssetModels('checkpoints') const result = await assetService.getAssetModels('checkpoints')
expect(api.fetchApi).toHaveBeenCalledWith( expect(api.fetchApi).toHaveBeenCalledWith(
'/assets?include_tags=models,checkpoints&limit=300' '/assets?include_tags=models,checkpoints&limit=500'
) )
expect(result).toEqual([ expect(result).toEqual([
expect.objectContaining({ name: 'valid.safetensors', pathIndex: 0 }) expect.objectContaining({ name: 'valid.safetensors', pathIndex: 0 })
@@ -236,7 +236,7 @@ describe('assetService', () => {
// Verify API call includes correct category // Verify API call includes correct category
expect(api.fetchApi).toHaveBeenCalledWith( expect(api.fetchApi).toHaveBeenCalledWith(
'/assets?include_tags=models,checkpoints&limit=300' '/assets?include_tags=models,checkpoints&limit=500'
) )
}) })
@@ -297,7 +297,7 @@ describe('assetService', () => {
const result = await assetService.getAssetsByTag('models') const result = await assetService.getAssetsByTag('models')
expect(api.fetchApi).toHaveBeenCalledWith( expect(api.fetchApi).toHaveBeenCalledWith(
'/assets?include_tags=models&limit=300' '/assets?include_tags=models&limit=500'
) )
expect(result).toEqual(testAssets) expect(result).toEqual(testAssets)
}) })