mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-05 20:54:56 +00:00
Compare commits
4 Commits
ExposeExec
...
cloud/mode
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49bc21c764 | ||
|
|
5223f27de6 | ||
|
|
8f3bafdf31 | ||
|
|
1c898b729a |
@@ -391,7 +391,7 @@ echo "Last stable release: $LAST_STABLE"
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Trigger the workflow
|
# Trigger the workflow
|
||||||
gh workflow run release-version-bump.yaml -f version_type=${VERSION_TYPE}
|
gh workflow run version-bump.yaml -f version_type=${VERSION_TYPE}
|
||||||
|
|
||||||
# Workflow runs quickly - usually creates PR within 30 seconds
|
# Workflow runs quickly - usually creates PR within 30 seconds
|
||||||
echo "Workflow triggered. Waiting for PR creation..."
|
echo "Workflow triggered. Waiting for PR creation..."
|
||||||
@@ -443,21 +443,28 @@ echo "Workflow triggered. Waiting for PR creation..."
|
|||||||
gh pr view ${PR_NUMBER} --json labels | jq -r '.labels[].name' | grep -q "Release" || \
|
gh pr view ${PR_NUMBER} --json labels | jq -r '.labels[].name' | grep -q "Release" || \
|
||||||
echo "ERROR: Release label missing! Add it immediately!"
|
echo "ERROR: Release label missing! Add it immediately!"
|
||||||
```
|
```
|
||||||
2. Verify version number in package.json
|
2. Check for update-locales commits:
|
||||||
3. Review all changed files
|
```bash
|
||||||
4. Ensure no unintended changes included
|
# WARNING: update-locales may add [skip ci] which blocks release workflow!
|
||||||
5. Wait for required PR checks:
|
gh pr view ${PR_NUMBER} --json commits | grep -q "skip ci" && \
|
||||||
|
echo "WARNING: [skip ci] detected - release workflow may not trigger!"
|
||||||
|
```
|
||||||
|
3. Verify version number in package.json
|
||||||
|
4. Review all changed files
|
||||||
|
5. Ensure no unintended changes included
|
||||||
|
6. Wait for required PR checks:
|
||||||
```bash
|
```bash
|
||||||
gh pr checks ${PR_NUMBER} --watch
|
gh pr checks ${PR_NUMBER} --watch
|
||||||
```
|
```
|
||||||
6. **FINAL CODE REVIEW**: Release label present and no [skip ci]?
|
7. **FINAL CODE REVIEW**: Release label present and no [skip ci]?
|
||||||
|
|
||||||
### Step 12: Pre-Merge Validation
|
### Step 12: Pre-Merge Validation
|
||||||
|
|
||||||
1. **Review Requirements**: Release PRs require approval
|
1. **Review Requirements**: Release PRs require approval
|
||||||
2. Monitor CI checks
|
2. Monitor CI checks - watch for update-locales
|
||||||
3. Check no new commits to main since PR creation
|
3. **CRITICAL WARNING**: If update-locales adds [skip ci], the release workflow won't trigger!
|
||||||
4. **DEPLOYMENT READINESS**: Ready to merge?
|
4. Check no new commits to main since PR creation
|
||||||
|
5. **DEPLOYMENT READINESS**: Ready to merge?
|
||||||
|
|
||||||
### Step 13: Execute Release
|
### Step 13: Execute Release
|
||||||
|
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
---
|
|
||||||
name: writing-playwright-tests
|
|
||||||
description: 'Writes Playwright e2e tests for ComfyUI_frontend. Use when creating, modifying, or debugging browser tests. Triggers on: playwright, e2e test, browser test, spec file.'
|
|
||||||
---
|
|
||||||
|
|
||||||
# Writing Playwright Tests for ComfyUI_frontend
|
|
||||||
|
|
||||||
## Golden Rules
|
|
||||||
|
|
||||||
1. **ALWAYS look at existing tests first.** Search `browser_tests/tests/` for similar patterns before writing new tests.
|
|
||||||
|
|
||||||
2. **ALWAYS read the fixture code.** The APIs are in `browser_tests/fixtures/` - read them directly instead of guessing.
|
|
||||||
|
|
||||||
3. **Use premade JSON workflow assets** instead of building workflows programmatically.
|
|
||||||
- Assets live in `browser_tests/assets/`
|
|
||||||
- Load with `await comfyPage.workflow.loadWorkflow('feature/my_workflow')`
|
|
||||||
- Create new assets by starting with `browser_tests/assets/default.json` and manually editing the JSON to match your desired graph state
|
|
||||||
|
|
||||||
## Vue Nodes vs LiteGraph: Decision Guide
|
|
||||||
|
|
||||||
Choose based on **what you're testing**, not personal preference:
|
|
||||||
|
|
||||||
| Testing... | Use | Why |
|
|
||||||
| ---------------------------------------------- | -------------------------------- | ---------------------------------------- |
|
|
||||||
| Vue-rendered node UI, DOM widgets, CSS states | `comfyPage.vueNodes.*` | Nodes are DOM elements, use locators |
|
|
||||||
| Canvas interactions, connections, legacy nodes | `comfyPage.nodeOps.*` | Canvas-based, use coordinates/references |
|
|
||||||
| Both in same test | Pick primary, minimize switching | Avoid confusion |
|
|
||||||
|
|
||||||
**Vue Nodes requires explicit opt-in:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
|
|
||||||
await comfyPage.vueNodes.waitForNodes()
|
|
||||||
```
|
|
||||||
|
|
||||||
**Vue Node state uses CSS classes:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const BYPASS_CLASS = /before:bg-bypass\/60/
|
|
||||||
await expect(node).toHaveClass(BYPASS_CLASS)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Issues
|
|
||||||
|
|
||||||
These are frequent causes of flaky tests - check them first, but investigate if they don't apply:
|
|
||||||
|
|
||||||
| Symptom | Common Cause | Typical Fix |
|
|
||||||
| ---------------------------------- | ------------------------- | -------------------------------------------------------------------------------------- |
|
|
||||||
| Test passes locally, fails in CI | Missing nextFrame() | Add `await comfyPage.nextFrame()` after canvas ops (not needed after `loadWorkflow()`) |
|
|
||||||
| Keyboard shortcuts don't work | Missing focus | Add `await comfyPage.canvas.click()` first |
|
|
||||||
| Double-click doesn't trigger | Timing too fast | Add `{ delay: 5 }` option |
|
|
||||||
| Elements end up in wrong position | Drag animation incomplete | Use `{ steps: 10 }` not `{ steps: 1 }` |
|
|
||||||
| Widget value wrong after drag-drop | Upload incomplete | Add `{ waitForUpload: true }` |
|
|
||||||
| Test fails when run with others | Test pollution | Add `afterEach` with `resetView()` |
|
|
||||||
| Local screenshots don't match CI | Platform differences | Screenshots are Linux-only, use PR label |
|
|
||||||
|
|
||||||
## Test Tags
|
|
||||||
|
|
||||||
Add appropriate tags to every test:
|
|
||||||
|
|
||||||
| Tag | When to Use |
|
|
||||||
| ------------- | ----------------------------------------- |
|
|
||||||
| `@smoke` | Quick essential tests |
|
|
||||||
| `@slow` | Tests > 10 seconds |
|
|
||||||
| `@screenshot` | Visual regression tests |
|
|
||||||
| `@canvas` | Canvas interactions |
|
|
||||||
| `@node` | Node-related |
|
|
||||||
| `@widget` | Widget-related |
|
|
||||||
| `@mobile` | Mobile viewport (runs on Pixel 5 project) |
|
|
||||||
| `@2x` | HiDPI tests (runs on 2x scale project) |
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
test.describe('Feature', { tag: ['@screenshot', '@canvas'] }, () => {
|
|
||||||
```
|
|
||||||
|
|
||||||
## Retry Patterns
|
|
||||||
|
|
||||||
**Never use `waitForTimeout`** - it's always wrong.
|
|
||||||
|
|
||||||
| Pattern | Use Case |
|
|
||||||
| ------------------------ | ---------------------------------------------------- |
|
|
||||||
| Auto-retrying assertions | `toBeVisible()`, `toHaveText()`, etc. (prefer these) |
|
|
||||||
| `expect.poll()` | Single value polling |
|
|
||||||
| `expect().toPass()` | Multiple assertions that must all pass |
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Prefer auto-retrying assertions when possible
|
|
||||||
await expect(node).toBeVisible()
|
|
||||||
|
|
||||||
// Single value polling
|
|
||||||
await expect.poll(() => widget.getValue(), { timeout: 2000 }).toBe(100)
|
|
||||||
|
|
||||||
// Multiple conditions
|
|
||||||
await expect(async () => {
|
|
||||||
expect(await node1.getValue()).toBe('foo')
|
|
||||||
expect(await node2.getValue()).toBe('bar')
|
|
||||||
}).toPass({ timeout: 2000 })
|
|
||||||
```
|
|
||||||
|
|
||||||
## Screenshot Baselines
|
|
||||||
|
|
||||||
- **Screenshots are Linux-only.** Don't commit local screenshots.
|
|
||||||
- **To update baselines:** Add PR label `New Browser Test Expectations`
|
|
||||||
- **Mask dynamic content:**
|
|
||||||
```typescript
|
|
||||||
await expect(comfyPage.canvas).toHaveScreenshot('page.png', {
|
|
||||||
mask: [page.locator('.timestamp')]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## CI Debugging
|
|
||||||
|
|
||||||
1. Download artifacts from failed CI run
|
|
||||||
2. Extract and view trace: `npx playwright show-trace trace.zip`
|
|
||||||
3. CI deploys HTML report to Cloudflare Pages (link in PR comment)
|
|
||||||
4. Reproduce CI: `CI=true pnpm test:browser`
|
|
||||||
5. Local runs: `pnpm test:browser:local`
|
|
||||||
|
|
||||||
## Anti-Patterns
|
|
||||||
|
|
||||||
Avoid these common mistakes:
|
|
||||||
|
|
||||||
1. **Arbitrary waits** - Use retrying assertions instead
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ await page.waitForTimeout(500)
|
|
||||||
// ✅ await expect(element).toBeVisible()
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementation-tied selectors** - Use test IDs or semantic selectors
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ page.locator('div.container > button.btn-primary')
|
|
||||||
// ✅ page.getByTestId('submit-button')
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Missing nextFrame after canvas ops** - Canvas needs sync time
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
await node.drag({ x: 50, y: 50 })
|
|
||||||
await comfyPage.nextFrame() // Required
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Shared state between tests** - Tests must be independent
|
|
||||||
```typescript
|
|
||||||
// ❌ let sharedData // Outside test
|
|
||||||
// ✅ Define state inside each test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Start Template
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Path depends on test file location - adjust '../' segments accordingly
|
|
||||||
import {
|
|
||||||
comfyPageFixture as test,
|
|
||||||
comfyExpect as expect
|
|
||||||
} from '../fixtures/ComfyPage'
|
|
||||||
|
|
||||||
test.describe('FeatureName', { tag: ['@canvas'] }, () => {
|
|
||||||
test.afterEach(async ({ comfyPage }) => {
|
|
||||||
await comfyPage.canvasOps.resetView()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should do something', async ({ comfyPage }) => {
|
|
||||||
await comfyPage.workflow.loadWorkflow('myWorkflow')
|
|
||||||
|
|
||||||
const node = (await comfyPage.nodeOps.getNodeRefsByTitle('KSampler'))[0]
|
|
||||||
// ... test logic
|
|
||||||
|
|
||||||
await expect(comfyPage.canvas).toHaveScreenshot('expected.png')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Finding Patterns
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Find similar tests
|
|
||||||
grep -r "KSampler" browser_tests/tests/
|
|
||||||
|
|
||||||
# Find usage of a fixture method
|
|
||||||
grep -r "loadWorkflow" browser_tests/tests/
|
|
||||||
|
|
||||||
# Find tests with specific tag
|
|
||||||
grep -r '@screenshot' browser_tests/tests/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Files to Read
|
|
||||||
|
|
||||||
| Purpose | Path |
|
|
||||||
| ----------------- | ------------------------------------------ |
|
|
||||||
| Main fixture | `browser_tests/fixtures/ComfyPage.ts` |
|
|
||||||
| Helper classes | `browser_tests/fixtures/helpers/` |
|
|
||||||
| Component objects | `browser_tests/fixtures/components/` |
|
|
||||||
| Test selectors | `browser_tests/fixtures/selectors.ts` |
|
|
||||||
| Vue Node helpers | `browser_tests/fixtures/VueNodeHelpers.ts` |
|
|
||||||
| Test assets | `browser_tests/assets/` |
|
|
||||||
| Existing tests | `browser_tests/tests/` |
|
|
||||||
|
|
||||||
**Read the fixture code directly** - it's the source of truth for available methods.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
issue_enrichment:
|
|
||||||
auto_enrich:
|
|
||||||
enabled: true
|
|
||||||
reviews:
|
|
||||||
high_level_summary: false
|
|
||||||
auto_review:
|
|
||||||
drafts: true
|
|
||||||
15
.gitattributes
vendored
15
.gitattributes
vendored
@@ -1,5 +1,16 @@
|
|||||||
# Force all text files to use LF line endings
|
# Default
|
||||||
* text=auto eol=lf
|
* text=auto
|
||||||
|
|
||||||
|
# Force TS to LF to make the unixy scripts not break on Windows
|
||||||
|
*.cjs text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.mjs text eol=lf
|
||||||
|
*.mts text eol=lf
|
||||||
|
*.snap text eol=lf
|
||||||
|
*.ts text eol=lf
|
||||||
|
*.vue text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
packages/registry-types/src/comfyRegistryTypes.ts linguist-generated=true
|
packages/registry-types/src/comfyRegistryTypes.ts linguist-generated=true
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
5
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@@ -10,7 +10,10 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I am running the latest version of ComfyUI
|
- label: I am running the latest version of ComfyUI
|
||||||
required: true
|
required: true
|
||||||
- label: I have custom nodes enabled
|
- label: I have searched existing issues to make sure this isn't a duplicate
|
||||||
|
required: true
|
||||||
|
- label: I have tested with all custom nodes disabled ([see how](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled))
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
|
|||||||
7
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
7
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
@@ -4,6 +4,13 @@ labels: []
|
|||||||
type: Feature
|
type: Feature
|
||||||
|
|
||||||
body:
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Is there an existing issue for this?
|
||||||
|
description: Please search to see if an issue already exists for the problem you're experiencing, and that it's not addressed in a recent build/commit.
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues and checked the recent builds/commits
|
||||||
|
required: true
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
|||||||
@@ -104,14 +104,14 @@ runs:
|
|||||||
|
|
||||||
- name: Find existing comment
|
- name: Find existing comment
|
||||||
id: find
|
id: find
|
||||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
||||||
comment-author: github-actions[bot]
|
comment-author: github-actions[bot]
|
||||||
body-includes: ${{ steps.build.outputs.marker_search }}
|
body-includes: ${{ steps.build.outputs.marker_search }}
|
||||||
|
|
||||||
- name: Post or update comment
|
- name: Post or update comment
|
||||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
||||||
comment-id: ${{ steps.find.outputs.comment-id }}
|
comment-id: ${{ steps.find.outputs.comment-id }}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ runs:
|
|||||||
|
|
||||||
# Checkout ComfyUI repo, install the dev_tools node and start server
|
# Checkout ComfyUI repo, install the dev_tools node and start server
|
||||||
- name: Checkout ComfyUI
|
- name: Checkout ComfyUI
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: 'comfyanonymous/ComfyUI'
|
repository: 'comfyanonymous/ComfyUI'
|
||||||
path: 'ComfyUI'
|
path: 'ComfyUI'
|
||||||
@@ -33,7 +33,7 @@ runs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
|
||||||
@@ -12,17 +12,29 @@ runs:
|
|||||||
|
|
||||||
# Install pnpm, Node.js, build frontend
|
# Install pnpm, Node.js, build frontend
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: './pnpm-lock.yaml'
|
cache-dependency-path: './pnpm-lock.yaml'
|
||||||
|
|
||||||
|
# Restore tool caches before running any build/lint operations
|
||||||
|
- name: Restore tool output cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./.cache
|
||||||
|
./tsconfig.tsbuildinfo
|
||||||
|
key: tool-cache-${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }}-${{ hashFiles('./src/**/*.{ts,vue,js,mts}', './*.config.*') }}
|
||||||
|
restore-keys: |
|
||||||
|
tool-cache-${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }}-
|
||||||
|
tool-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -11,7 +11,7 @@ runs:
|
|||||||
echo "playwright-version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
|
echo "playwright-version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache Playwright Browsers
|
- name: Cache Playwright Browsers
|
||||||
uses: actions/cache@v5 # v5.0.2
|
uses: actions/cache@v4
|
||||||
id: cache-playwright-browsers
|
id: cache-playwright-browsers
|
||||||
with:
|
with:
|
||||||
path: '~/.cache/ms-playwright'
|
path: '~/.cache/ms-playwright'
|
||||||
@@ -13,15 +13,15 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
commit-message: '[chore] Update electron-types to ${{ steps.get-version.outputs.NEW_VERSION }}'
|
commit-message: '[chore] Update electron-types to ${{ steps.get-version.outputs.NEW_VERSION }}'
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Checkout ComfyUI-Manager repository
|
- name: Checkout ComfyUI-Manager repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/ComfyUI-Manager
|
repository: Comfy-Org/ComfyUI-Manager
|
||||||
path: ComfyUI-Manager
|
path: ComfyUI-Manager
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: steps.check-changes.outputs.changed == 'true'
|
if: steps.check-changes.outputs.changed == 'true'
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
commit-message: '[chore] Update ComfyUI-Manager API types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}'
|
commit-message: '[chore] Update ComfyUI-Manager API types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}'
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Checkout comfy-api repository
|
- name: Checkout comfy-api repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/comfy-api
|
repository: Comfy-Org/comfy-api
|
||||||
path: comfy-api
|
path: comfy-api
|
||||||
@@ -87,7 +87,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: steps.check-changes.outputs.changed == 'true'
|
if: steps.check-changes.outputs.changed == 'true'
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
commit-message: '[chore] Update Comfy Registry API types from comfy-api@${{ steps.api-info.outputs.commit }}'
|
commit-message: '[chore] Update Comfy Registry API types from comfy-api@${{ steps.api-info.outputs.commit }}'
|
||||||
|
|||||||
81
.github/workflows/ci-dist-telemetry-scan.yaml
vendored
81
.github/workflows/ci-dist-telemetry-scan.yaml
vendored
@@ -1,81 +0,0 @@
|
|||||||
name: 'CI: Dist Telemetry Scan'
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches-ignore: [wip/*, draft/*, temp/*]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
scan:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
run: pnpm build
|
|
||||||
env:
|
|
||||||
DISTRIBUTION: localhost
|
|
||||||
|
|
||||||
- name: Scan dist for GTM telemetry references
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo '🔍 Scanning for Google Tag Manager references...'
|
|
||||||
if rg --no-ignore -n \
|
|
||||||
-g '*.html' \
|
|
||||||
-g '*.js' \
|
|
||||||
-e 'Google Tag Manager' \
|
|
||||||
-e '(?i)\bgtm\.js\b' \
|
|
||||||
-e '(?i)googletagmanager\.com/gtm\.js\\?id=' \
|
|
||||||
-e '(?i)googletagmanager\.com/ns\.html\\?id=' \
|
|
||||||
dist; then
|
|
||||||
echo '❌ ERROR: Google Tag Manager references found in dist assets!'
|
|
||||||
echo 'GTM must be properly tree-shaken from OSS builds.'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo '✅ No GTM references found'
|
|
||||||
|
|
||||||
- name: Scan dist for Mixpanel telemetry references
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo '🔍 Scanning for Mixpanel references...'
|
|
||||||
if rg --no-ignore -n \
|
|
||||||
-g '*.html' \
|
|
||||||
-g '*.js' \
|
|
||||||
-e '(?i)mixpanel\.init' \
|
|
||||||
-e '(?i)mixpanel\.identify' \
|
|
||||||
-e 'MixpanelTelemetryProvider' \
|
|
||||||
-e 'mp\.comfy\.org' \
|
|
||||||
-e 'mixpanel-browser' \
|
|
||||||
-e '(?i)mixpanel\.track\(' \
|
|
||||||
dist; then
|
|
||||||
echo '❌ ERROR: Mixpanel references found in dist assets!'
|
|
||||||
echo 'Mixpanel must be properly tree-shaken from OSS builds.'
|
|
||||||
echo ''
|
|
||||||
echo 'To fix this:'
|
|
||||||
echo '1. Use the TelemetryProvider pattern (see src/platform/telemetry/)'
|
|
||||||
echo '2. Call telemetry via useTelemetry() hook'
|
|
||||||
echo '3. Use conditional dynamic imports behind isCloud checks'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo '✅ No Mixpanel references found'
|
|
||||||
2
.github/workflows/ci-json-validation.yaml
vendored
2
.github/workflows/ci-json-validation.yaml
vendored
@@ -13,6 +13,6 @@ jobs:
|
|||||||
json-lint:
|
json-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
- name: Validate JSON syntax
|
- name: Validate JSON syntax
|
||||||
run: ./scripts/cicd/check-json.sh
|
run: ./scripts/cicd/check-json.sh
|
||||||
|
|||||||
32
.github/workflows/ci-lint-format.yaml
vendored
32
.github/workflows/ci-lint-format.yaml
vendored
@@ -18,21 +18,23 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout PR
|
- name: Checkout PR
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }}
|
ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }}
|
||||||
token: ${{ !github.event.pull_request.head.repo.fork && secrets.PR_GH_TOKEN || github.token }}
|
|
||||||
|
|
||||||
- name: Setup frontend
|
- name: Install pnpm
|
||||||
uses: ./.github/actions/setup-frontend
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Detect browser_tests changes
|
|
||||||
id: changed-paths
|
|
||||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
|
||||||
with:
|
with:
|
||||||
filters: |
|
version: 10
|
||||||
browser_tests:
|
|
||||||
- 'browser_tests/**'
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 'lts/*'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run ESLint with auto-fix
|
- name: Run ESLint with auto-fix
|
||||||
run: pnpm lint:fix
|
run: pnpm lint:fix
|
||||||
@@ -68,14 +70,10 @@ jobs:
|
|||||||
pnpm format:check
|
pnpm format:check
|
||||||
pnpm knip
|
pnpm knip
|
||||||
|
|
||||||
- name: Typecheck browser tests
|
|
||||||
if: steps.changed-paths.outputs.browser_tests == 'true'
|
|
||||||
run: pnpm typecheck:browser
|
|
||||||
|
|
||||||
- name: Comment on PR about auto-fix
|
- name: Comment on PR about auto-fix
|
||||||
if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name == github.repository
|
if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name == github.repository
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
@@ -88,7 +86,7 @@ jobs:
|
|||||||
- name: Comment on PR about manual fix needed
|
- name: Comment on PR about manual fix needed
|
||||||
if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name != github.repository
|
if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name != github.repository
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
|
|||||||
118
.github/workflows/ci-oss-assets-validation.yaml
vendored
118
.github/workflows/ci-oss-assets-validation.yaml
vendored
@@ -1,118 +0,0 @@
|
|||||||
name: 'CI: OSS Assets Validation'
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches-ignore: [wip/*, draft/*, temp/*]
|
|
||||||
push:
|
|
||||||
branches: [main, dev*]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate-fonts:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
run: pnpm build
|
|
||||||
env:
|
|
||||||
DISTRIBUTION: localhost
|
|
||||||
|
|
||||||
- name: Check for proprietary fonts in dist
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo '🔍 Checking dist for proprietary ABCROM fonts...'
|
|
||||||
|
|
||||||
if [ ! -d "dist" ] || [ -z "$(ls -A dist)" ]; then
|
|
||||||
echo '❌ ERROR: dist/ directory missing or empty!'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for ABCROM font files
|
|
||||||
if find dist/ -type f -iname '*abcrom*' \
|
|
||||||
\( -name '*.woff' -o -name '*.woff2' -o -name '*.ttf' -o -name '*.otf' \) \
|
|
||||||
-print -quit | grep -q .; then
|
|
||||||
echo ''
|
|
||||||
echo '❌ ERROR: Found proprietary ABCROM font files in dist!'
|
|
||||||
echo ''
|
|
||||||
find dist/ -type f -iname '*abcrom*' \
|
|
||||||
\( -name '*.woff' -o -name '*.woff2' -o -name '*.ttf' -o -name '*.otf' \)
|
|
||||||
echo ''
|
|
||||||
echo 'ABCROM fonts are proprietary and should not ship to OSS builds.'
|
|
||||||
echo ''
|
|
||||||
echo 'To fix this:'
|
|
||||||
echo '1. Use conditional font loading based on isCloud'
|
|
||||||
echo '2. Ensure fonts are dynamically imported, not bundled'
|
|
||||||
echo '3. Check vite config for font handling'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo '✅ No proprietary fonts found in dist'
|
|
||||||
|
|
||||||
validate-licenses:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Validate production dependency licenses
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo '🔍 Checking production dependency licenses...'
|
|
||||||
|
|
||||||
# Use license-checker-rseidelsohn (actively maintained fork, handles monorepos)
|
|
||||||
# Exclude internal @comfyorg packages from license check
|
|
||||||
# Run in if condition to capture exit code
|
|
||||||
if npx license-checker-rseidelsohn@4 \
|
|
||||||
--production \
|
|
||||||
--summary \
|
|
||||||
--excludePackages '@comfyorg/comfyui-frontend;@comfyorg/design-system;@comfyorg/registry-types;@comfyorg/shared-frontend-utils;@comfyorg/tailwind-utils;@comfyorg/comfyui-electron-types' \
|
|
||||||
--onlyAllow 'MIT;MIT*;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;0BSD;BlueOak-1.0.0;Python-2.0;CC0-1.0;Unlicense;(MIT OR Apache-2.0);(MIT OR GPL-3.0);(Apache-2.0 OR MIT);(MPL-2.0 OR Apache-2.0);CC-BY-4.0;CC-BY-3.0;GPL-3.0-only'; then
|
|
||||||
echo ''
|
|
||||||
echo '✅ All production dependency licenses are approved!'
|
|
||||||
else
|
|
||||||
echo ''
|
|
||||||
echo '❌ ERROR: Found dependencies with non-approved licenses!'
|
|
||||||
echo ''
|
|
||||||
echo 'To fix this:'
|
|
||||||
echo '1. Check the license of the problematic package'
|
|
||||||
echo '2. Find an alternative package with an approved license'
|
|
||||||
echo '3. If the license is safe and OSI-approved, add it to the --onlyAllow list'
|
|
||||||
echo ''
|
|
||||||
echo 'For more info on OSI-approved licenses:'
|
|
||||||
echo 'https://opensource.org/licenses'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
110
.github/workflows/ci-perf-report.yaml
vendored
110
.github/workflows/ci-perf-report.yaml
vendored
@@ -1,110 +0,0 @@
|
|||||||
name: 'CI: Performance Report'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, core/*]
|
|
||||||
paths-ignore: ['**/*.md']
|
|
||||||
pull_request:
|
|
||||||
branches-ignore: [wip/*, draft/*, temp/*]
|
|
||||||
paths-ignore: ['**/*.md']
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: perf-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
perf-tests:
|
|
||||||
if: github.repository == 'Comfy-Org/ComfyUI_frontend'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
container:
|
|
||||||
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.12
|
|
||||||
credentials:
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Setup frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
with:
|
|
||||||
include_build_step: true
|
|
||||||
|
|
||||||
- name: Start ComfyUI server
|
|
||||||
uses: ./.github/actions/start-comfyui-server
|
|
||||||
|
|
||||||
- name: Run performance tests
|
|
||||||
id: perf
|
|
||||||
continue-on-error: true
|
|
||||||
run: pnpm exec playwright test --project=performance --workers=1
|
|
||||||
|
|
||||||
- name: Upload perf metrics
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: perf-metrics
|
|
||||||
path: test-results/perf-metrics.json
|
|
||||||
retention-days: 30
|
|
||||||
if-no-files-found: warn
|
|
||||||
|
|
||||||
report:
|
|
||||||
needs: perf-tests
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Download PR perf metrics
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: perf-metrics
|
|
||||||
path: test-results/
|
|
||||||
|
|
||||||
- name: Download baseline perf metrics
|
|
||||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
|
||||||
with:
|
|
||||||
branch: ${{ github.event.pull_request.base.ref }}
|
|
||||||
workflow: ci-perf-report.yaml
|
|
||||||
event: push
|
|
||||||
name: perf-metrics
|
|
||||||
path: temp/perf-baseline/
|
|
||||||
if_no_artifact_found: warn
|
|
||||||
|
|
||||||
- name: Generate perf report
|
|
||||||
run: npx --yes tsx scripts/perf-report.ts > perf-report.md
|
|
||||||
|
|
||||||
- name: Read perf report
|
|
||||||
id: perf-report
|
|
||||||
uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58 # v1.1.7
|
|
||||||
with:
|
|
||||||
path: ./perf-report.md
|
|
||||||
|
|
||||||
- name: Create or update PR comment
|
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
number: ${{ github.event.pull_request.number }}
|
|
||||||
body: |
|
|
||||||
${{ steps.perf-report.outputs.content }}
|
|
||||||
<!-- COMFYUI_FRONTEND_PERF -->
|
|
||||||
body-include: '<!-- COMFYUI_FRONTEND_PERF -->'
|
|
||||||
4
.github/workflows/ci-python-validation.yaml
vendored
4
.github/workflows/ci-python-validation.yaml
vendored
@@ -16,10 +16,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
|
|
||||||
|
|||||||
19
.github/workflows/ci-size-data.yaml
vendored
19
.github/workflows/ci-size-data.yaml
vendored
@@ -17,10 +17,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup frontend
|
- name: Install pnpm
|
||||||
uses: ./.github/actions/setup-frontend
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: '24.x'
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
@@ -35,7 +46,7 @@ jobs:
|
|||||||
echo ${{ github.base_ref }} > ./temp/size/base.txt
|
echo ${{ github.base_ref }} > ./temp/size/base.txt
|
||||||
|
|
||||||
- name: Upload size data
|
- name: Upload size data
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: size-data
|
name: size-data
|
||||||
path: temp/size
|
path: temp/size
|
||||||
|
|||||||
12
.github/workflows/ci-tests-e2e-forks.yaml
vendored
12
.github/workflows/ci-tests-e2e-forks.yaml
vendored
@@ -6,6 +6,9 @@ on:
|
|||||||
workflows: ['CI: Tests E2E']
|
workflows: ['CI: Tests E2E']
|
||||||
types: [requested, completed]
|
types: [requested, completed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-and-comment-forked-pr:
|
deploy-and-comment-forked-pr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -28,11 +31,11 @@ jobs:
|
|||||||
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get PR Number
|
- name: Get PR Number
|
||||||
id: pr
|
id: pr
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { data: prs } = await github.rest.pulls.list({
|
const { data: prs } = await github.rest.pulls.list({
|
||||||
@@ -60,11 +63,12 @@ jobs:
|
|||||||
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||||
"${{ steps.pr.outputs.result }}" \
|
"${{ steps.pr.outputs.result }}" \
|
||||||
"${{ github.event.workflow_run.head_branch }}" \
|
"${{ github.event.workflow_run.head_branch }}" \
|
||||||
"starting"
|
"starting" \
|
||||||
|
"$(date -u '${{ env.DATE_FORMAT }}')"
|
||||||
|
|
||||||
- name: Download and Deploy Reports
|
- name: Download and Deploy Reports
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
48
.github/workflows/ci-tests-e2e.yaml
vendored
48
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -4,11 +4,9 @@ name: 'CI: Tests E2E'
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, master, core/*, desktop/*]
|
branches: [main, master, core/*, desktop/*]
|
||||||
paths-ignore: ['**/*.md']
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches-ignore: [wip/*, draft/*, temp/*]
|
branches-ignore:
|
||||||
paths-ignore: ['**/*.md']
|
[wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -19,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Setup frontend
|
- name: Setup frontend
|
||||||
uses: ./.github/actions/setup-frontend
|
uses: ./.github/actions/setup-frontend
|
||||||
with:
|
with:
|
||||||
@@ -27,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload only built dist/ (containerized test jobs will pnpm install without cache)
|
# Upload only built dist/ (containerized test jobs will pnpm install without cache)
|
||||||
- name: Upload built frontend
|
- name: Upload built frontend
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: frontend-dist
|
name: frontend-dist
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -39,7 +37,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.12
|
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -53,9 +51,9 @@ jobs:
|
|||||||
shardTotal: [8]
|
shardTotal: [8]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Download built frontend
|
- name: Download built frontend
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: frontend-dist
|
name: frontend-dist
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -74,7 +72,7 @@ jobs:
|
|||||||
PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report
|
PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report
|
||||||
|
|
||||||
- name: Upload blob report
|
- name: Upload blob report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: blob-report-chromium-${{ matrix.shardIndex }}
|
name: blob-report-chromium-${{ matrix.shardIndex }}
|
||||||
@@ -87,7 +85,7 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.12
|
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -100,9 +98,9 @@ jobs:
|
|||||||
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
|
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Download built frontend
|
- name: Download built frontend
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: frontend-dist
|
name: frontend-dist
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -130,7 +128,7 @@ jobs:
|
|||||||
pnpm exec playwright merge-reports --reporter=json ./blob-report
|
pnpm exec playwright merge-reports --reporter=json ./blob-report
|
||||||
|
|
||||||
- name: Upload Playwright report
|
- name: Upload Playwright report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report-${{ matrix.browser }}
|
name: playwright-report-${{ matrix.browser }}
|
||||||
@@ -143,13 +141,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Download blob reports
|
- name: Download blob reports
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./all-blob-reports
|
path: ./all-blob-reports
|
||||||
pattern: blob-report-chromium-*
|
pattern: blob-report-chromium-*
|
||||||
@@ -164,7 +165,7 @@ jobs:
|
|||||||
pnpm dlx @playwright/test merge-reports --reporter=json ./all-blob-reports
|
pnpm dlx @playwright/test merge-reports --reporter=json ./all-blob-reports
|
||||||
|
|
||||||
- name: Upload HTML report
|
- name: Upload HTML report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: playwright-report-chromium
|
name: playwright-report-chromium
|
||||||
path: ./playwright-report/
|
path: ./playwright-report/
|
||||||
@@ -182,7 +183,11 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Get start time
|
||||||
|
id: start-time
|
||||||
|
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Post starting comment
|
- name: Post starting comment
|
||||||
env:
|
env:
|
||||||
@@ -192,7 +197,8 @@ jobs:
|
|||||||
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||||
"${{ github.event.pull_request.number }}" \
|
"${{ github.event.pull_request.number }}" \
|
||||||
"${{ github.head_ref }}" \
|
"${{ github.head_ref }}" \
|
||||||
"starting"
|
"starting" \
|
||||||
|
"${{ steps.start-time.outputs.time }}"
|
||||||
|
|
||||||
# Deploy and comment for non-forked PRs only
|
# Deploy and comment for non-forked PRs only
|
||||||
deploy-and-comment:
|
deploy-and-comment:
|
||||||
@@ -204,10 +210,10 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Download all playwright reports
|
- name: Download all playwright reports
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: playwright-report-*
|
pattern: playwright-report-*
|
||||||
path: reports
|
path: reports
|
||||||
|
|||||||
12
.github/workflows/ci-tests-storybook-forks.yaml
vendored
12
.github/workflows/ci-tests-storybook-forks.yaml
vendored
@@ -6,6 +6,9 @@ on:
|
|||||||
workflows: ['CI: Tests Storybook']
|
workflows: ['CI: Tests Storybook']
|
||||||
types: [requested, completed]
|
types: [requested, completed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-and-comment-forked-pr:
|
deploy-and-comment-forked-pr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -28,11 +31,11 @@ jobs:
|
|||||||
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get PR Number
|
- name: Get PR Number
|
||||||
id: pr
|
id: pr
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { data: prs } = await github.rest.pulls.list({
|
const { data: prs } = await github.rest.pulls.list({
|
||||||
@@ -60,11 +63,12 @@ jobs:
|
|||||||
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
||||||
"${{ steps.pr.outputs.result }}" \
|
"${{ steps.pr.outputs.result }}" \
|
||||||
"${{ github.event.workflow_run.head_branch }}" \
|
"${{ github.event.workflow_run.head_branch }}" \
|
||||||
"starting"
|
"starting" \
|
||||||
|
"$(date -u '${{ env.DATE_FORMAT }}')"
|
||||||
|
|
||||||
- name: Download and Deploy Storybook
|
- name: Download and Deploy Storybook
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success'
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success'
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
49
.github/workflows/ci-tests-storybook.yaml
vendored
49
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Post starting comment
|
- name: Post starting comment
|
||||||
env:
|
env:
|
||||||
@@ -24,7 +24,8 @@ jobs:
|
|||||||
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
||||||
"${{ github.event.pull_request.number }}" \
|
"${{ github.event.pull_request.number }}" \
|
||||||
"${{ github.head_ref }}" \
|
"${{ github.head_ref }}" \
|
||||||
"starting"
|
"starting" \
|
||||||
|
"$(date -u '+%m/%d/%Y, %I:%M:%S %p')"
|
||||||
|
|
||||||
# Build Storybook for all PRs (free Cloudflare deployment)
|
# Build Storybook for all PRs (free Cloudflare deployment)
|
||||||
storybook-build:
|
storybook-build:
|
||||||
@@ -35,10 +36,21 @@ jobs:
|
|||||||
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup frontend
|
- name: Install pnpm
|
||||||
uses: ./.github/actions/setup-frontend
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build Storybook
|
- name: Build Storybook
|
||||||
run: pnpm build-storybook
|
run: pnpm build-storybook
|
||||||
@@ -57,7 +69,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Storybook build
|
- name: Upload Storybook build
|
||||||
if: success() && github.event.pull_request.head.repo.fork == false
|
if: success() && github.event.pull_request.head.repo.fork == false
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: storybook-static
|
name: storybook-static
|
||||||
path: storybook-static/
|
path: storybook-static/
|
||||||
@@ -74,16 +86,27 @@ jobs:
|
|||||||
chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }}
|
chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required for Chromatic baseline
|
fetch-depth: 0 # Required for Chromatic baseline
|
||||||
|
|
||||||
- name: Setup frontend
|
- name: Install pnpm
|
||||||
uses: ./.github/actions/setup-frontend
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build Storybook and run Chromatic
|
- name: Build Storybook and run Chromatic
|
||||||
id: chromatic
|
id: chromatic
|
||||||
uses: chromaui/action@07791f8243f4cb2698bf4d00426baf4b2d1cb7e0 # v13.3.5
|
uses: chromaui/action@latest
|
||||||
with:
|
with:
|
||||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
buildScriptName: build-storybook
|
buildScriptName: build-storybook
|
||||||
@@ -113,11 +136,11 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Download Storybook build
|
- name: Download Storybook build
|
||||||
if: needs.storybook-build.outputs.conclusion == 'success'
|
if: needs.storybook-build.outputs.conclusion == 'success'
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: storybook-static
|
name: storybook-static
|
||||||
path: storybook-static
|
path: storybook-static
|
||||||
@@ -147,7 +170,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Update comment with Chromatic URLs
|
- name: Update comment with Chromatic URLs
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
||||||
|
|||||||
19
.github/workflows/ci-tests-unit.yaml
vendored
19
.github/workflows/ci-tests-unit.yaml
vendored
@@ -4,10 +4,8 @@ name: 'CI: Tests Unit'
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, master, dev*, core/*, desktop/*]
|
branches: [main, master, dev*, core/*, desktop/*]
|
||||||
paths-ignore: ['**/*.md']
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches-ignore: [wip/*, draft/*, temp/*]
|
branches-ignore: [wip/*, draft/*, temp/*]
|
||||||
paths-ignore: ['**/*.md']
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -18,10 +16,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup frontend
|
- name: Install pnpm
|
||||||
uses: ./.github/actions/setup-frontend
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 'lts/*'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run Vitest tests
|
- name: Run Vitest tests
|
||||||
run: pnpm test:unit
|
run: pnpm test:unit
|
||||||
|
|||||||
21
.github/workflows/ci-validate-action-pins.yaml
vendored
21
.github/workflows/ci-validate-action-pins.yaml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: Validate Action SHA Pins
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/**'
|
|
||||||
- '.github/actions/**'
|
|
||||||
- '.pinact.yaml'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate-pins:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- uses: suzuki-shunsuke/pinact-action@3d49c6412901042473ffa78becddab1aea46bbea # v1.3.1
|
|
||||||
with:
|
|
||||||
skip_push: 'true'
|
|
||||||
4
.github/workflows/ci-yaml-validation.yaml
vendored
4
.github/workflows/ci-yaml-validation.yaml
vendored
@@ -17,10 +17,10 @@ jobs:
|
|||||||
yaml-lint:
|
yaml-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/cloud-backport-tag.yaml
vendored
4
.github/workflows/cloud-backport-tag.yaml
vendored
@@ -18,12 +18,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout merge commit
|
- name: Checkout merge commit
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/i18n-update-core.yaml
vendored
4
.github/workflows/i18n-update-core.yaml
vendored
@@ -16,9 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
|
|
||||||
# Setup playwright environment
|
# Setup playwright environment
|
||||||
- name: Setup ComfyUI Frontend
|
- name: Setup ComfyUI Frontend
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Setup playwright environment with custom node repository
|
# Setup playwright environment with custom node repository
|
||||||
- name: Setup ComfyUI Server (without launching)
|
- name: Setup ComfyUI Server (without launching)
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
# Install the custom node repository
|
# Install the custom node repository
|
||||||
- name: Checkout custom node repository
|
- name: Checkout custom node repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
||||||
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
git commit -m "Update locales"
|
git commit -m "Update locales"
|
||||||
|
|
||||||
- name: Install SSH key For PUSH
|
- name: Install SSH key For PUSH
|
||||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0
|
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
|
||||||
with:
|
with:
|
||||||
# PR private key from action server
|
# PR private key from action server
|
||||||
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
||||||
|
|||||||
4
.github/workflows/i18n-update-nodes.yaml
vendored
4
.github/workflows/i18n-update-nodes.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
# Setup playwright environment
|
# Setup playwright environment
|
||||||
- name: Setup ComfyUI Server (and start)
|
- name: Setup ComfyUI Server (and start)
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
uses: ./.github/actions/setup-comfyui-server
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
commit-message: 'Update locales for node definitions'
|
commit-message: 'Update locales for node definitions'
|
||||||
|
|||||||
2
.github/workflows/pr-backport.yaml
vendored
2
.github/workflows/pr-backport.yaml
vendored
@@ -64,7 +64,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/pr-claude-review.yaml
vendored
8
.github/workflows/pr-claude-review.yaml
vendored
@@ -23,18 +23,18 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: refs/pull/${{ github.event.pull_request.number }}/head
|
ref: refs/pull/${{ github.event.pull_request.number }}/head
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
pnpm install -g typescript @vue/compiler-sfc
|
pnpm install -g typescript @vue/compiler-sfc
|
||||||
|
|
||||||
- name: Run Claude PR Review
|
- name: Run Claude PR Review
|
||||||
uses: anthropics/claude-code-action@ff34ce0ff04a470bd3fa56c1ef391c8f1c19f8e9 # v1.0.38
|
uses: anthropics/claude-code-action@v1.0.6
|
||||||
with:
|
with:
|
||||||
label_trigger: 'claude-review'
|
label_trigger: 'claude-review'
|
||||||
prompt: |
|
prompt: |
|
||||||
|
|||||||
25
.github/workflows/pr-size-report.yaml
vendored
25
.github/workflows/pr-size-report.yaml
vendored
@@ -33,13 +33,24 @@ jobs:
|
|||||||
github.event_name == 'workflow_dispatch'
|
github.event_name == 'workflow_dispatch'
|
||||||
)
|
)
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup frontend
|
- name: Install pnpm
|
||||||
uses: ./.github/actions/setup-frontend
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: '24.x'
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download size data
|
- name: Download size data
|
||||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
uses: dawidd6/action-download-artifact@v11
|
||||||
with:
|
with:
|
||||||
name: size-data
|
name: size-data
|
||||||
run_id: ${{ github.event_name == 'workflow_dispatch' && inputs.run_id || github.event.workflow_run.id }}
|
run_id: ${{ github.event_name == 'workflow_dispatch' && inputs.run_id || github.event.workflow_run.id }}
|
||||||
@@ -64,7 +75,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Download previous size data
|
- name: Download previous size data
|
||||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
uses: dawidd6/action-download-artifact@v11
|
||||||
with:
|
with:
|
||||||
branch: ${{ steps.pr-base.outputs.content }}
|
branch: ${{ steps.pr-base.outputs.content }}
|
||||||
workflow: ci-size-data.yaml
|
workflow: ci-size-data.yaml
|
||||||
@@ -78,12 +89,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Read size report
|
- name: Read size report
|
||||||
id: size-report
|
id: size-report
|
||||||
uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58 # v1.1.7
|
uses: juliangruber/read-file-action@v1
|
||||||
with:
|
with:
|
||||||
path: ./size-report.md
|
path: ./size-report.md
|
||||||
|
|
||||||
- name: Create or update PR comment
|
- name: Create or update PR comment
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
uses: actions-cool/maintain-one-comment@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
number: ${{ steps.pr-number.outputs.content }}
|
number: ${{ steps.pr-number.outputs.content }}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Find Update Comment
|
- name: Find Update Comment
|
||||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad
|
||||||
id: 'find-update-comment'
|
id: 'find-update-comment'
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
body-includes: 'Updating Playwright Expectations'
|
body-includes: 'Updating Playwright Expectations'
|
||||||
|
|
||||||
- name: Add Starting Reaction
|
- name: Add Starting Reaction
|
||||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
||||||
with:
|
with:
|
||||||
comment-id: ${{ steps.find-update-comment.outputs.comment-id }}
|
comment-id: ${{ steps.find-update-comment.outputs.comment-id }}
|
||||||
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
reactions: eyes
|
reactions: eyes
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.pr-info.outputs.branch }}
|
ref: ${{ steps.pr-info.outputs.branch }}
|
||||||
- name: Setup frontend
|
- name: Setup frontend
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload built dist/ (containerized test jobs will pnpm install without cache)
|
# Upload built dist/ (containerized test jobs will pnpm install without cache)
|
||||||
- name: Upload built frontend
|
- name: Upload built frontend
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: frontend-dist
|
name: frontend-dist
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.12
|
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -91,11 +91,11 @@ jobs:
|
|||||||
shardTotal: [4]
|
shardTotal: [4]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.setup.outputs.branch }}
|
ref: ${{ needs.setup.outputs.branch }}
|
||||||
- name: Download built frontend
|
- name: Download built frontend
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: frontend-dist
|
name: frontend-dist
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -109,7 +109,7 @@ jobs:
|
|||||||
# Run sharded tests with snapshot updates (browsers pre-installed in container)
|
# Run sharded tests with snapshot updates (browsers pre-installed in container)
|
||||||
- name: Update snapshots (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
|
- name: Update snapshots (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
|
||||||
id: playwright-tests
|
id: playwright-tests
|
||||||
run: pnpm exec playwright test --update-snapshots --grep @screenshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
run: pnpm exec playwright test --update-snapshots --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Stage changed snapshot files
|
- name: Stage changed snapshot files
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload ONLY the changed files from this shard
|
# Upload ONLY the changed files from this shard
|
||||||
- name: Upload changed snapshots
|
- name: Upload changed snapshots
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
if: steps.changed-snapshots.outputs.has-changes == 'true'
|
if: steps.changed-snapshots.outputs.has-changes == 'true'
|
||||||
with:
|
with:
|
||||||
name: snapshots-shard-${{ matrix.shardIndex }}
|
name: snapshots-shard-${{ matrix.shardIndex }}
|
||||||
@@ -157,7 +157,7 @@ jobs:
|
|||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
- name: Upload test report
|
- name: Upload test report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report-shard-${{ matrix.shardIndex }}
|
name: playwright-report-shard-${{ matrix.shardIndex }}
|
||||||
@@ -170,18 +170,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.setup.outputs.branch }}
|
ref: ${{ needs.setup.outputs.branch }}
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
|
|
||||||
# Download all changed snapshot files from shards
|
# Download all changed snapshot files from shards
|
||||||
- name: Download snapshot artifacts
|
- name: Download snapshot artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: snapshots-shard-*
|
pattern: snapshots-shard-*
|
||||||
path: ./downloaded-snapshots
|
path: ./downloaded-snapshots
|
||||||
merge-multiple: true
|
merge-multiple: false
|
||||||
|
|
||||||
- name: List downloaded files
|
- name: List downloaded files
|
||||||
run: |
|
run: |
|
||||||
@@ -207,13 +206,13 @@ jobs:
|
|||||||
echo "MERGING CHANGED SNAPSHOTS"
|
echo "MERGING CHANGED SNAPSHOTS"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|
||||||
# Check if any artifacts were downloaded (merge-multiple puts files directly in path)
|
# Check if any artifacts were downloaded
|
||||||
if [ ! -d "./downloaded-snapshots" ]; then
|
if [ ! -d "./downloaded-snapshots" ]; then
|
||||||
echo "No snapshot artifacts to merge"
|
echo "No snapshot artifacts to merge"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "MERGE COMPLETE"
|
echo "MERGE COMPLETE"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "Files merged: 0"
|
echo "Shards merged: 0"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -223,29 +222,37 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Count files to merge
|
merged_count=0
|
||||||
file_count=$(find ./downloaded-snapshots -type f | wc -l)
|
|
||||||
|
|
||||||
if [ "$file_count" -eq 0 ]; then
|
# For each shard's changed files, copy them directly
|
||||||
echo "No snapshot files found in downloaded artifacts"
|
for shard_dir in ./downloaded-snapshots/snapshots-shard-*/; do
|
||||||
echo "=========================================="
|
if [ ! -d "$shard_dir" ]; then
|
||||||
echo "MERGE COMPLETE"
|
continue
|
||||||
echo "=========================================="
|
fi
|
||||||
echo "Files merged: 0"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Merging $file_count snapshot file(s)..."
|
shard_name=$(basename "$shard_dir")
|
||||||
|
file_count=$(find "$shard_dir" -type f | wc -l)
|
||||||
|
|
||||||
# Copy all files directly, preserving directory structure
|
if [ "$file_count" -eq 0 ]; then
|
||||||
# With merge-multiple: true, files are directly in ./downloaded-snapshots/ without shard subdirs
|
echo " $shard_name: no files"
|
||||||
cp -v -r ./downloaded-snapshots/* browser_tests/ 2>&1 | sed 's/^/ /'
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Processing $shard_name ($file_count file(s))..."
|
||||||
|
|
||||||
|
# Copy files directly, preserving directory structure
|
||||||
|
# Since files are already in correct structure (no browser_tests/ prefix), just copy them all
|
||||||
|
cp -v -r "$shard_dir"* browser_tests/ 2>&1 | sed 's/^/ /'
|
||||||
|
|
||||||
|
merged_count=$((merged_count + 1))
|
||||||
|
echo " ✓ Merged"
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "MERGE COMPLETE"
|
echo "MERGE COMPLETE"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "Files merged: $file_count"
|
echo "Shards merged: $merged_count"
|
||||||
|
|
||||||
- name: Show changes
|
- name: Show changes
|
||||||
run: |
|
run: |
|
||||||
@@ -294,7 +301,7 @@ jobs:
|
|||||||
echo "✓ Commit and push successful"
|
echo "✓ Commit and push successful"
|
||||||
|
|
||||||
- name: Add Done Reaction
|
- name: Add Done Reaction
|
||||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
||||||
if: github.event_name == 'issue_comment' && steps.commit.outputs.has-changes == 'true'
|
if: github.event_name == 'issue_comment' && steps.commit.outputs.has-changes == 'true'
|
||||||
with:
|
with:
|
||||||
comment-id: ${{ needs.setup.outputs.comment-id }}
|
comment-id: ${{ needs.setup.outputs.comment-id }}
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ jobs:
|
|||||||
dist_tag: ${{ steps.dist.outputs.dist_tag }}
|
dist_tag: ${{ steps.dist.outputs.dist_tag }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: '24.x'
|
node-version: '24.x'
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout merge commit
|
- name: Checkout merge commit
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|||||||
6
.github/workflows/publish-desktop-ui.yaml
vendored
6
.github/workflows/publish-desktop-ui.yaml
vendored
@@ -77,19 +77,19 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.resolve_ref.outputs.ref }}
|
ref: ${{ steps.resolve_ref.outputs.ref }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: '24.x'
|
node-version: '24.x'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
10
.github/workflows/release-biweekly-comfyui.yaml
vendored
10
.github/workflows/release-biweekly-comfyui.yaml
vendored
@@ -61,13 +61,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ComfyUI_frontend
|
- name: Checkout ComfyUI_frontend
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
path: frontend
|
path: frontend
|
||||||
|
|
||||||
- name: Checkout ComfyUI (sparse)
|
- name: Checkout ComfyUI (sparse)
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/ComfyUI
|
repository: Comfy-Org/ComfyUI
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
@@ -75,12 +75,12 @@ jobs:
|
|||||||
path: comfyui
|
path: comfyui
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ComfyUI fork
|
- name: Checkout ComfyUI fork
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
|
repository: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
|||||||
4
.github/workflows/release-branch-create.yaml
vendored
4
.github/workflows/release-branch-create.yaml
vendored
@@ -18,13 +18,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
|
|
||||||
|
|||||||
37
.github/workflows/release-draft-create.yaml
vendored
37
.github/workflows/release-draft-create.yaml
vendored
@@ -19,12 +19,12 @@ jobs:
|
|||||||
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
|
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -50,43 +50,37 @@ jobs:
|
|||||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||||
ENABLE_MINIFY: 'true'
|
ENABLE_MINIFY: 'true'
|
||||||
USE_PROD_CONFIG: 'true'
|
USE_PROD_CONFIG: 'true'
|
||||||
IS_NIGHTLY: ${{ case(github.ref == 'refs/heads/main', 'true', 'false') }}
|
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm build
|
||||||
# Desktop-specific release artifact with desktop distribution flags.
|
|
||||||
DISTRIBUTION=desktop pnpm build
|
|
||||||
pnpm zipdist ./dist ./dist-desktop.zip
|
|
||||||
|
|
||||||
# Default release artifact for core/PyPI.
|
|
||||||
NX_SKIP_NX_CACHE=true pnpm build
|
|
||||||
pnpm zipdist
|
pnpm zipdist
|
||||||
- name: Upload dist artifact
|
- name: Upload dist artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-files
|
name: dist-files
|
||||||
path: |
|
path: |
|
||||||
dist/
|
dist/
|
||||||
dist.zip
|
dist.zip
|
||||||
dist-desktop.zip
|
|
||||||
|
|
||||||
draft_release:
|
draft_release:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
- name: Download dist artifact
|
- name: Download dist artifact
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-files
|
name: dist-files
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
uses: >-
|
||||||
|
softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist.zip
|
dist.zip
|
||||||
dist-desktop.zip
|
|
||||||
tag_name: v${{ needs.build.outputs.version }}
|
tag_name: v${{ needs.build.outputs.version }}
|
||||||
target_commitish: ${{ github.event.pull_request.base.ref }}
|
target_commitish: ${{ github.event.pull_request.base.ref }}
|
||||||
make_latest: >-
|
make_latest: >-
|
||||||
@@ -104,13 +98,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Download dist artifact
|
- name: Download dist artifact
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-files
|
name: dist-files
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
@@ -125,7 +119,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
COMFYUI_FRONTEND_VERSION: ${{ needs.build.outputs.version }}
|
COMFYUI_FRONTEND_VERSION: ${{ needs.build.outputs.version }}
|
||||||
- name: Publish pypi package
|
- name: Publish pypi package
|
||||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
uses: >-
|
||||||
|
pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||||
with:
|
with:
|
||||||
password: ${{ secrets.PYPI_TOKEN }}
|
password: ${{ secrets.PYPI_TOKEN }}
|
||||||
packages-dir: comfyui_frontend_package/dist
|
packages-dir: comfyui_frontend_package/dist
|
||||||
@@ -152,7 +147,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout merge commit
|
- name: Checkout merge commit
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|||||||
6
.github/workflows/release-npm-types.yaml
vendored
6
.github/workflows/release-npm-types.yaml
vendored
@@ -69,18 +69,18 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.resolve_ref.outputs.ref }}
|
ref: ${{ steps.resolve_ref.outputs.ref }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
16
.github/workflows/release-pypi-dev.yaml
vendored
16
.github/workflows/release-pypi-dev.yaml
vendored
@@ -15,12 +15,12 @@ jobs:
|
|||||||
version: ${{ steps.current_version.outputs.version }}
|
version: ${{ steps.current_version.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
pnpm build
|
pnpm build
|
||||||
pnpm zipdist
|
pnpm zipdist
|
||||||
- name: Upload dist artifact
|
- name: Upload dist artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-files
|
name: dist-files
|
||||||
path: |
|
path: |
|
||||||
@@ -52,13 +52,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Download dist artifact
|
- name: Download dist artifact
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-files
|
name: dist-files
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
COMFYUI_FRONTEND_VERSION: ${{ format('{0}.dev{1}', needs.build.outputs.version, inputs.devVersion) }}
|
COMFYUI_FRONTEND_VERSION: ${{ format('{0}.dev{1}', needs.build.outputs.version, inputs.devVersion) }}
|
||||||
- name: Publish pypi package
|
- name: Publish pypi package
|
||||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||||
with:
|
with:
|
||||||
password: ${{ secrets.PYPI_TOKEN }}
|
password: ${{ secrets.PYPI_TOKEN }}
|
||||||
packages-dir: comfyui_frontend_package/dist
|
packages-dir: comfyui_frontend_package/dist
|
||||||
|
|||||||
10
.github/workflows/release-version-bump.yaml
vendored
10
.github/workflows/release-version-bump.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Close stale nightly version bump PRs
|
- name: Close stale nightly version bump PRs
|
||||||
if: github.event_name == 'schedule'
|
if: github.event_name == 'schedule'
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
script: |
|
script: |
|
||||||
@@ -118,7 +118,7 @@ jobs:
|
|||||||
core.info(`Closed ${closed.length} stale PR(s).`)
|
core.info(`Closed ${closed.length} stale PR(s).`)
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.prepared-inputs.outputs.branch }}
|
ref: ${{ steps.prepared-inputs.outputs.branch }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -142,12 +142,12 @@ jobs:
|
|||||||
echo "✅ Branch '$BRANCH' exists"
|
echo "✅ Branch '$BRANCH' exists"
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ jobs:
|
|||||||
echo "capitalised=${CAPITALISED_TYPE@u}" >> "$GITHUB_OUTPUT"
|
echo "capitalised=${CAPITALISED_TYPE@u}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
commit-message: '[release] Increment version to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
commit-message: '[release] Increment version to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.branch }}
|
ref: ${{ github.event.inputs.branch }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -51,12 +51,12 @@ jobs:
|
|||||||
echo "✅ Branch '$BRANCH' exists"
|
echo "✅ Branch '$BRANCH' exists"
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: '24.x'
|
node-version: '24.x'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
echo "capitalised=${VERSION_TYPE@u}" >> $GITHUB_OUTPUT
|
echo "capitalised=${VERSION_TYPE@u}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
commit-message: '[release] Increment desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
commit-message: '[release] Increment desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
||||||
|
|||||||
12
.github/workflows/weekly-docs-check.yaml
vendored
12
.github/workflows/weekly-docs-check.yaml
vendored
@@ -22,18 +22,18 @@ jobs:
|
|||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 50
|
fetch-depth: 0
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Run Claude Documentation Review
|
- name: Run Claude Documentation Review
|
||||||
uses: anthropics/claude-code-action@ff34ce0ff04a470bd3fa56c1ef391c8f1c19f8e9 # v1.0.38
|
uses: anthropics/claude-code-action@v1.0.6
|
||||||
with:
|
with:
|
||||||
prompt: |
|
prompt: |
|
||||||
Is all documentation still 100% accurate?
|
Is all documentation still 100% accurate?
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create or Update Pull Request
|
- name: Create or Update Pull Request
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
if: steps.check_changes.outputs.has_changes == 'true'
|
||||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
commit-message: 'docs: weekly documentation accuracy update'
|
commit-message: 'docs: weekly documentation accuracy update'
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -64,7 +64,6 @@ browser_tests/local/
|
|||||||
dist.zip
|
dist.zip
|
||||||
|
|
||||||
/temp/
|
/temp/
|
||||||
/tmp/
|
|
||||||
|
|
||||||
# Generated JSON Schemas
|
# Generated JSON Schemas
|
||||||
/schemas/
|
/schemas/
|
||||||
@@ -97,5 +96,3 @@ vitest.config.*.timestamp*
|
|||||||
|
|
||||||
# Weekly docs check output
|
# Weekly docs check output
|
||||||
/output.txt
|
/output.txt
|
||||||
|
|
||||||
.amp
|
|
||||||
@@ -60,6 +60,11 @@
|
|||||||
{
|
{
|
||||||
"name": "primevue/sidebar",
|
"name": "primevue/sidebar",
|
||||||
"message": "Sidebar is deprecated in PrimeVue 4+. Use Drawer instead: import Drawer from 'primevue/drawer'"
|
"message": "Sidebar is deprecated in PrimeVue 4+. Use Drawer instead: import Drawer from 'primevue/drawer'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@/i18n--to-enable",
|
||||||
|
"importNames": ["st", "t", "te", "d"],
|
||||||
|
"message": "Don't import `@/i18n` directly, prefer `useI18n()`"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -96,7 +101,6 @@
|
|||||||
"typescript/restrict-template-expressions": "off",
|
"typescript/restrict-template-expressions": "off",
|
||||||
"typescript/unbound-method": "off",
|
"typescript/unbound-method": "off",
|
||||||
"typescript/no-floating-promises": "error",
|
"typescript/no-floating-promises": "error",
|
||||||
"typescript/no-explicit-any": "error",
|
|
||||||
"vue/no-import-compiler-macros": "error",
|
"vue/no-import-compiler-macros": "error",
|
||||||
"vue/no-dupe-keys": "error"
|
"vue/no-dupe-keys": "error"
|
||||||
},
|
},
|
||||||
@@ -106,17 +110,6 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "allow"
|
"no-console": "allow"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["browser_tests/**/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"typescript/no-explicit-any": "error",
|
|
||||||
"no-async-promise-executor": "error",
|
|
||||||
"no-control-regex": "error",
|
|
||||||
"no-useless-rename": "error",
|
|
||||||
"no-unused-private-class-members": "error",
|
|
||||||
"unicorn/no-empty-file": "error"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
24
.pinact.yaml
24
.pinact.yaml
@@ -1,24 +0,0 @@
|
|||||||
# pinact configuration
|
|
||||||
# https://github.com/suzuki-shunsuke/pinact
|
|
||||||
version: 3
|
|
||||||
|
|
||||||
files:
|
|
||||||
- pattern: .github/workflows/*.yaml
|
|
||||||
- pattern: .github/actions/**/*.yaml
|
|
||||||
|
|
||||||
# Actions that don't need SHA pinning (official GitHub actions are trusted)
|
|
||||||
ignore_actions:
|
|
||||||
- name: actions/cache
|
|
||||||
ref: v5
|
|
||||||
- name: actions/checkout
|
|
||||||
ref: v6
|
|
||||||
- name: actions/setup-node
|
|
||||||
ref: v6
|
|
||||||
- name: actions/setup-python
|
|
||||||
ref: v6
|
|
||||||
- name: actions/upload-artifact
|
|
||||||
ref: v6
|
|
||||||
- name: actions/download-artifact
|
|
||||||
ref: v7
|
|
||||||
- name: actions/github-script
|
|
||||||
ref: v8
|
|
||||||
@@ -98,10 +98,12 @@ const config: StorybookConfig = {
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
rolldownOptions: {
|
rolldownOptions: {
|
||||||
|
experimental: {
|
||||||
|
strictExecutionOrder: true
|
||||||
|
},
|
||||||
treeshake: false,
|
treeshake: false,
|
||||||
output: {
|
output: {
|
||||||
keepNames: true,
|
keepNames: true
|
||||||
strictExecutionOrder: true
|
|
||||||
},
|
},
|
||||||
onwarn: (warning, warn) => {
|
onwarn: (warning, warn) => {
|
||||||
// Suppress specific warnings
|
// Suppress specific warnings
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ const preview: Preview = {
|
|||||||
{ value: 'light', icon: 'sun', title: 'Light' },
|
{ value: 'light', icon: 'sun', title: 'Light' },
|
||||||
{ value: 'dark', icon: 'moon', title: 'Dark' }
|
{ value: 'dark', icon: 'moon', title: 'Dark' }
|
||||||
],
|
],
|
||||||
|
showName: true,
|
||||||
dynamicTitle: true
|
dynamicTitle: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,27 +40,26 @@
|
|||||||
"block-no-empty": true,
|
"block-no-empty": true,
|
||||||
"no-descending-specificity": null,
|
"no-descending-specificity": null,
|
||||||
"no-duplicate-at-import-rules": true,
|
"no-duplicate-at-import-rules": true,
|
||||||
"at-rule-disallowed-list": ["apply"],
|
|
||||||
"at-rule-no-unknown": [
|
"at-rule-no-unknown": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
"ignoreAtRules": [
|
"ignoreAtRules": [
|
||||||
"tailwind",
|
"tailwind",
|
||||||
|
"apply",
|
||||||
"layer",
|
"layer",
|
||||||
"config",
|
"config",
|
||||||
"theme",
|
"theme",
|
||||||
"reference",
|
"reference",
|
||||||
"plugin",
|
"plugin",
|
||||||
"custom-variant",
|
"custom-variant",
|
||||||
"utility",
|
"utility"
|
||||||
"source"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"function-no-unknown": [
|
"function-no-unknown": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
"ignoreFunctions": ["theme", "v-bind", "from-folder", "from-json"]
|
"ignoreFunctions": ["theme", "v-bind"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,3 @@ rules:
|
|||||||
line-length: disable
|
line-length: disable
|
||||||
document-start: disable
|
document-start: disable
|
||||||
truthy: disable
|
truthy: disable
|
||||||
comments:
|
|
||||||
min-spaces-from-content: 1
|
|
||||||
|
|
||||||
10
AGENTS.md
10
AGENTS.md
@@ -21,7 +21,7 @@ See @docs/guidance/\*.md for file-type-specific conventions (auto-loaded by glob
|
|||||||
- i18n: `src/i18n.ts`,
|
- i18n: `src/i18n.ts`,
|
||||||
- Entry Point: `src/main.ts`.
|
- Entry Point: `src/main.ts`.
|
||||||
- Tests:
|
- Tests:
|
||||||
- unit/component in `src/**/*.test.ts`
|
- unit/component in `tests-ui/` and `src/**/*.test.ts`
|
||||||
- E2E (Playwright) in `browser_tests/**/*.spec.ts`
|
- E2E (Playwright) in `browser_tests/**/*.spec.ts`
|
||||||
- Public assets: `public/`
|
- Public assets: `public/`
|
||||||
- Build output: `dist/`
|
- Build output: `dist/`
|
||||||
@@ -37,10 +37,6 @@ See @docs/guidance/\*.md for file-type-specific conventions (auto-loaded by glob
|
|||||||
|
|
||||||
The project uses **Nx** for build orchestration and task management
|
The project uses **Nx** for build orchestration and task management
|
||||||
|
|
||||||
## Package Manager
|
|
||||||
|
|
||||||
This project uses **pnpm**. Always prefer scripts defined in `package.json` (e.g., `pnpm test:unit`, `pnpm lint`). To run arbitrary packages not in scripts, use `pnpx` or `pnpm dlx` — never `npx`.
|
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
## Build, Test, and Development Commands
|
||||||
|
|
||||||
- `pnpm dev`: Start Vite dev server.
|
- `pnpm dev`: Start Vite dev server.
|
||||||
@@ -48,7 +44,7 @@ This project uses **pnpm**. Always prefer scripts defined in `package.json` (e.g
|
|||||||
- `pnpm build`: Type-check then production build to `dist/`
|
- `pnpm build`: Type-check then production build to `dist/`
|
||||||
- `pnpm preview`: Preview the production build locally
|
- `pnpm preview`: Preview the production build locally
|
||||||
- `pnpm test:unit`: Run Vitest unit tests
|
- `pnpm test:unit`: Run Vitest unit tests
|
||||||
- `pnpm test:browser:local`: Run Playwright E2E tests (`browser_tests/`)
|
- `pnpm test:browser`: Run Playwright E2E tests (`browser_tests/`)
|
||||||
- `pnpm lint` / `pnpm lint:fix`: Lint (ESLint)
|
- `pnpm lint` / `pnpm lint:fix`: Lint (ESLint)
|
||||||
- `pnpm format` / `pnpm format:check`: oxfmt
|
- `pnpm format` / `pnpm format:check`: oxfmt
|
||||||
- `pnpm typecheck`: Vue TSC type checking
|
- `pnpm typecheck`: Vue TSC type checking
|
||||||
@@ -268,7 +264,7 @@ A particular type of complexity is over-engineering, where developers have made
|
|||||||
|
|
||||||
## Repository Navigation
|
## Repository Navigation
|
||||||
|
|
||||||
- Check README files in key folders (browser_tests, composables, etc.)
|
- Check README files in key folders (tests-ui, browser_tests, composables, etc.)
|
||||||
- Prefer running single tests for performance
|
- Prefer running single tests for performance
|
||||||
- Use --help for unfamiliar CLI tools
|
- Use --help for unfamiliar CLI tools
|
||||||
|
|
||||||
|
|||||||
50
CODEOWNERS
50
CODEOWNERS
@@ -2,57 +2,57 @@
|
|||||||
* @Comfy-org/comfy_frontend_devs
|
* @Comfy-org/comfy_frontend_devs
|
||||||
|
|
||||||
# Desktop/Electron
|
# Desktop/Electron
|
||||||
/apps/desktop-ui/ @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/apps/desktop-ui/ @benceruleanlu
|
||||||
/src/stores/electronDownloadStore.ts @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/src/stores/electronDownloadStore.ts @benceruleanlu
|
||||||
/src/extensions/core/electronAdapter.ts @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/src/extensions/core/electronAdapter.ts @benceruleanlu
|
||||||
/vite.electron.config.mts @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/vite.electron.config.mts @benceruleanlu
|
||||||
|
|
||||||
# Common UI Components
|
# Common UI Components
|
||||||
/src/components/chip/ @viva-jinyi @Comfy-org/comfy_frontend_devs
|
/src/components/chip/ @viva-jinyi
|
||||||
/src/components/card/ @viva-jinyi @Comfy-org/comfy_frontend_devs
|
/src/components/card/ @viva-jinyi
|
||||||
/src/components/button/ @viva-jinyi @Comfy-org/comfy_frontend_devs
|
/src/components/button/ @viva-jinyi
|
||||||
/src/components/input/ @viva-jinyi @Comfy-org/comfy_frontend_devs
|
/src/components/input/ @viva-jinyi
|
||||||
|
|
||||||
# Topbar
|
# Topbar
|
||||||
/src/components/topbar/ @pythongosssss @Comfy-org/comfy_frontend_devs
|
/src/components/topbar/ @pythongosssss
|
||||||
|
|
||||||
# Thumbnail
|
# Thumbnail
|
||||||
/src/renderer/core/thumbnail/ @pythongosssss @Comfy-org/comfy_frontend_devs
|
/src/renderer/core/thumbnail/ @pythongosssss
|
||||||
|
|
||||||
# Legacy UI
|
# Legacy UI
|
||||||
/scripts/ui/ @pythongosssss @Comfy-org/comfy_frontend_devs
|
/scripts/ui/ @pythongosssss
|
||||||
|
|
||||||
# Link rendering
|
# Link rendering
|
||||||
/src/renderer/core/canvas/links/ @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/src/renderer/core/canvas/links/ @benceruleanlu
|
||||||
|
|
||||||
# Partner Nodes
|
# Partner Nodes
|
||||||
/src/composables/node/useNodePricing.ts @jojodecayz @bigcat88 @Comfy-org/comfy_frontend_devs
|
/src/composables/node/useNodePricing.ts @jojodecayz @bigcat88
|
||||||
|
|
||||||
# Node help system
|
# Node help system
|
||||||
/src/utils/nodeHelpUtil.ts @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/src/utils/nodeHelpUtil.ts @benceruleanlu
|
||||||
/src/stores/workspace/nodeHelpStore.ts @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/src/stores/workspace/nodeHelpStore.ts @benceruleanlu
|
||||||
/src/services/nodeHelpService.ts @benceruleanlu @Comfy-org/comfy_frontend_devs
|
/src/services/nodeHelpService.ts @benceruleanlu
|
||||||
|
|
||||||
# Selection toolbox
|
# Selection toolbox
|
||||||
/src/components/graph/selectionToolbox/ @Myestery @Comfy-org/comfy_frontend_devs
|
/src/components/graph/selectionToolbox/ @Myestery
|
||||||
|
|
||||||
# Minimap
|
# Minimap
|
||||||
/src/renderer/extensions/minimap/ @jtydhr88 @Myestery @Comfy-org/comfy_frontend_devs
|
/src/renderer/extensions/minimap/ @jtydhr88 @Myestery
|
||||||
|
|
||||||
# Workflow Templates
|
# Workflow Templates
|
||||||
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki @Comfy-org/comfy_frontend_devs
|
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||||
/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki @Comfy-org/comfy_frontend_devs
|
/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||||
|
|
||||||
# Mask Editor
|
# Mask Editor
|
||||||
/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp @Comfy-org/comfy_frontend_devs
|
/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp
|
||||||
/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp @Comfy-org/comfy_frontend_devs
|
/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp
|
||||||
|
|
||||||
# 3D
|
# 3D
|
||||||
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-org/comfy_frontend_devs
|
/src/extensions/core/load3d.ts @jtydhr88
|
||||||
/src/components/load3d/ @jtydhr88 @Comfy-org/comfy_frontend_devs
|
/src/components/load3d/ @jtydhr88
|
||||||
|
|
||||||
# Manager
|
# Manager
|
||||||
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata @Comfy-org/comfy_frontend_devs
|
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
/src/locales/ @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs
|
/src/locales/ @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ The project supports three types of icons, all with automatic imports (no manual
|
|||||||
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i class="icon-[lucide--settings]" />`, `<i class="icon-[mdi--folder]" />`
|
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i class="icon-[lucide--settings]" />`, `<i class="icon-[mdi--folder]" />`
|
||||||
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`
|
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`
|
||||||
|
|
||||||
Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Tailwind CSS icon classes (`icon-[comfy--template]`) are provided by `@iconify/tailwind4`, configured in `packages/design-system/src/css/style.css`. Custom icons are stored in `packages/design-system/src/icons/` and loaded via `from-folder` at build time.
|
Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `packages/design-system/src/icons/` and processed by `packages/design-system/src/iconCollection.ts` with automatic validation.
|
||||||
|
|
||||||
For detailed instructions and code examples, see [packages/design-system/src/icons/README.md](packages/design-system/src/icons/README.md).
|
For detailed instructions and code examples, see [packages/design-system/src/icons/README.md](packages/design-system/src/icons/README.md).
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,8 @@
|
|||||||
"^build"
|
"^build"
|
||||||
],
|
],
|
||||||
"options": {
|
"options": {
|
||||||
"command": "vite build --config apps/desktop-ui/vite.config.mts"
|
"cwd": "apps/desktop-ui",
|
||||||
|
"command": "vite build --config vite.config.mts"
|
||||||
},
|
},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
"{projectRoot}/dist"
|
"{projectRoot}/dist"
|
||||||
|
|||||||
@@ -4,39 +4,3 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-button-secondary {
|
|
||||||
border: none;
|
|
||||||
background-color: var(--color-neutral-600);
|
|
||||||
color: var(--color-white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-button-secondary:hover {
|
|
||||||
background-color: var(--color-neutral-550);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-button-secondary:active {
|
|
||||||
background-color: var(--color-neutral-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-button-danger {
|
|
||||||
background-color: var(--color-coral-red-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-button-danger:hover {
|
|
||||||
background-color: var(--color-coral-red-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-button-danger:active {
|
|
||||||
background-color: var(--color-coral-red-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-div .p-card {
|
|
||||||
transition: opacity var(--default-transition-duration);
|
|
||||||
--p-card-background: var(--p-button-secondary-background);
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-div .p-card:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -101,15 +101,13 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* xterm renders its internal DOM outside Vue templates, so :deep selectors are
|
@reference '../../../../assets/css/style.css';
|
||||||
* required to style those generated nodes.
|
|
||||||
*/
|
|
||||||
:deep(.p-terminal) .xterm {
|
:deep(.p-terminal) .xterm {
|
||||||
overflow: hidden;
|
@apply overflow-hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-terminal) .xterm-screen {
|
:deep(.p-terminal) .xterm-screen {
|
||||||
overflow: hidden;
|
@apply bg-neutral-900 overflow-hidden;
|
||||||
background-color: var(--color-neutral-900);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
option-value="value"
|
option-value="value"
|
||||||
:disabled="isSwitching"
|
:disabled="isSwitching"
|
||||||
:pt="dropdownPt"
|
:pt="dropdownPt"
|
||||||
:size="size"
|
:size="props.size"
|
||||||
class="language-selector"
|
class="language-selector"
|
||||||
@change="onLocaleChange"
|
@change="onLocaleChange"
|
||||||
>
|
>
|
||||||
@@ -36,10 +36,16 @@ import { i18n, loadLocale, st } from '@/i18n'
|
|||||||
type VariantKey = 'dark' | 'light'
|
type VariantKey = 'dark' | 'light'
|
||||||
type SizeKey = 'small' | 'large'
|
type SizeKey = 'small' | 'large'
|
||||||
|
|
||||||
const { variant = 'dark', size = 'small' } = defineProps<{
|
const props = withDefaults(
|
||||||
variant?: VariantKey
|
defineProps<{
|
||||||
size?: SizeKey
|
variant?: VariantKey
|
||||||
}>()
|
size?: SizeKey
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
variant: 'dark',
|
||||||
|
size: 'small'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const dropdownId = `language-select-${Math.random().toString(36).slice(2)}`
|
const dropdownId = `language-select-${Math.random().toString(36).slice(2)}`
|
||||||
|
|
||||||
@@ -98,8 +104,10 @@ const VARIANT_PRESETS = {
|
|||||||
const selectedLocale = ref<string>(i18n.global.locale.value)
|
const selectedLocale = ref<string>(i18n.global.locale.value)
|
||||||
const isSwitching = ref(false)
|
const isSwitching = ref(false)
|
||||||
|
|
||||||
const sizePreset = computed(() => SIZE_PRESETS[size])
|
const sizePreset = computed(() => SIZE_PRESETS[props.size as SizeKey])
|
||||||
const variantPreset = computed(() => VARIANT_PRESETS[variant])
|
const variantPreset = computed(
|
||||||
|
() => VARIANT_PRESETS[props.variant as VariantKey]
|
||||||
|
)
|
||||||
|
|
||||||
const dropdownPt = computed(() => ({
|
const dropdownPt = computed(() => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -187,17 +195,13 @@ async function onLocaleChange(event: SelectChangeEvent) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@reference '../../assets/css/style.css';
|
||||||
|
|
||||||
:deep(.p-dropdown-panel .p-dropdown-item) {
|
:deep(.p-dropdown-panel .p-dropdown-item) {
|
||||||
transition-property: color, background-color, border-color;
|
@apply transition-colors;
|
||||||
transition-duration: var(--default-transition-duration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-dropdown) {
|
:deep(.p-dropdown) {
|
||||||
&:focus-visible {
|
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-yellow/60 focus-visible:ring-offset-2;
|
||||||
outline: none;
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 2px var(--color-neutral-900),
|
|
||||||
0 0 0 4px color-mix(in srgb, var(--color-brand-yellow) 60%, transparent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -269,43 +269,26 @@ const onFocus = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@reference '../../assets/css/style.css';
|
||||||
|
|
||||||
:deep(.location-picker-accordion) {
|
:deep(.location-picker-accordion) {
|
||||||
padding-inline: calc(var(--spacing) * 12);
|
@apply px-12;
|
||||||
|
|
||||||
.p-accordionpanel {
|
.p-accordionpanel {
|
||||||
border: 0;
|
@apply border-0 bg-transparent;
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-accordionheader {
|
.p-accordionheader {
|
||||||
margin-top: calc(var(--spacing) * 2);
|
@apply bg-neutral-800/50 border-0 rounded-xl mt-2 hover:bg-neutral-700/50;
|
||||||
border: 0;
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
background-color: color-mix(
|
|
||||||
in srgb,
|
|
||||||
var(--color-neutral-800) 50%,
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
transition:
|
transition:
|
||||||
background-color 0.2s ease,
|
background-color 0.2s ease,
|
||||||
border-radius 0.5s ease;
|
border-radius 0.5s ease;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: color-mix(
|
|
||||||
in srgb,
|
|
||||||
var(--color-neutral-700) 50%,
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When panel is expanded, adjust header border radius */
|
/* When panel is expanded, adjust header border radius */
|
||||||
.p-accordionpanel-active {
|
.p-accordionpanel-active {
|
||||||
.p-accordionheader {
|
.p-accordionheader {
|
||||||
border-top-left-radius: var(--radius-xl);
|
@apply rounded-t-xl rounded-b-none;
|
||||||
border-top-right-radius: var(--radius-xl);
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-accordionheader-toggle-icon {
|
.p-accordionheader-toggle-icon {
|
||||||
@@ -316,24 +299,11 @@ const onFocus = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.p-accordioncontent {
|
.p-accordioncontent {
|
||||||
border: 0;
|
@apply bg-neutral-800/50 border-0 rounded-b-xl rounded-t-none;
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: var(--radius-xl);
|
|
||||||
border-bottom-left-radius: var(--radius-xl);
|
|
||||||
background-color: color-mix(
|
|
||||||
in srgb,
|
|
||||||
var(--color-neutral-800) 50%,
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-accordioncontent-content {
|
.p-accordioncontent-content {
|
||||||
background-color: transparent;
|
@apply bg-transparent pt-3 pr-5 pb-5 pl-5;
|
||||||
padding-top: calc(var(--spacing) * 3);
|
|
||||||
padding-right: calc(var(--spacing) * 5);
|
|
||||||
padding-bottom: calc(var(--spacing) * 5);
|
|
||||||
padding-left: calc(var(--spacing) * 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Override default chevron icons to use up/down */
|
/* Override default chevron icons to use up/down */
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="
|
class="task-div relative grid min-h-52 max-w-48"
|
||||||
cn(
|
:class="{ 'opacity-75': isLoading }"
|
||||||
'task-div group/task-card relative grid min-h-52 max-w-48',
|
|
||||||
isLoading && 'opacity-75'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
:class="
|
class="relative h-full max-w-48 overflow-hidden"
|
||||||
cn(
|
:class="{ 'opacity-65': runner.state !== 'error' }"
|
||||||
'relative h-full max-w-48 overflow-hidden',
|
|
||||||
runner.state !== 'error' && 'opacity-65'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:pt="cardPt"
|
|
||||||
v-bind="(({ onClick, ...rest }) => rest)($attrs)"
|
v-bind="(({ onClick, ...rest }) => rest)($attrs)"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
@@ -52,7 +43,7 @@
|
|||||||
|
|
||||||
<i
|
<i
|
||||||
v-if="!isLoading && runner.state === 'OK'"
|
v-if="!isLoading && runner.state === 'OK'"
|
||||||
class="pi pi-check pointer-events-none absolute -right-4 -bottom-4 col-span-full row-span-full z-10 text-[4rem] text-green-500 opacity-100 transition-opacity group-hover/task-card:opacity-20 [text-shadow:0.25rem_0_0.5rem_black]"
|
class="task-card-ok pi pi-check"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -64,7 +55,6 @@ import { computed } from 'vue'
|
|||||||
|
|
||||||
import { useMaintenanceTaskStore } from '@/stores/maintenanceTaskStore'
|
import { useMaintenanceTaskStore } from '@/stores/maintenanceTaskStore'
|
||||||
import type { MaintenanceTask } from '@/types/desktop/maintenanceTypes'
|
import type { MaintenanceTask } from '@/types/desktop/maintenanceTypes'
|
||||||
import { cn } from '@/utils/tailwindUtil'
|
|
||||||
import { useMinLoadingDurationRef } from '@/utils/refUtil'
|
import { useMinLoadingDurationRef } from '@/utils/refUtil'
|
||||||
|
|
||||||
const taskStore = useMaintenanceTaskStore()
|
const taskStore = useMaintenanceTaskStore()
|
||||||
@@ -93,9 +83,51 @@ const reactiveExecuting = computed(() => !!runner.value.executing)
|
|||||||
|
|
||||||
const isLoading = useMinLoadingDurationRef(reactiveLoading, 250)
|
const isLoading = useMinLoadingDurationRef(reactiveLoading, 250)
|
||||||
const isExecuting = useMinLoadingDurationRef(reactiveExecuting, 250)
|
const isExecuting = useMinLoadingDurationRef(reactiveExecuting, 250)
|
||||||
|
|
||||||
const cardPt = {
|
|
||||||
header: { class: 'z-0' },
|
|
||||||
body: { class: 'z-[1] grow justify-between' }
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@reference '../../assets/css/style.css';
|
||||||
|
|
||||||
|
.task-card-ok {
|
||||||
|
@apply text-green-500 absolute -right-4 -bottom-4 opacity-100 row-span-full col-span-full transition-opacity;
|
||||||
|
|
||||||
|
font-size: 4rem;
|
||||||
|
text-shadow: 0.25rem 0 0.5rem black;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-card {
|
||||||
|
@apply transition-opacity;
|
||||||
|
|
||||||
|
--p-card-background: var(--p-button-secondary-background);
|
||||||
|
opacity: 0.9;
|
||||||
|
|
||||||
|
&.opacity-65 {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.p-card-header) {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.p-card-body) {
|
||||||
|
z-index: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-div {
|
||||||
|
> i {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover > i {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full flex flex-col rounded-lg p-6 justify-between">
|
<div class="w-full h-full flex flex-col rounded-lg p-6 justify-between">
|
||||||
<h1 class="font-inter font-semibold text-xl m-0 italic">
|
<h1 class="font-inter font-semibold text-xl m-0 italic">
|
||||||
{{ $t(`desktopDialogs.${id}.title`, title) }}
|
{{ t(`desktopDialogs.${id}.title`, title) }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="whitespace-pre-wrap">
|
<p class="whitespace-pre-wrap">
|
||||||
{{ $t(`desktopDialogs.${id}.message`, message) }}
|
{{ t(`desktopDialogs.${id}.message`, message) }}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex w-full gap-2">
|
<div class="flex w-full gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
:key="button.label"
|
:key="button.label"
|
||||||
class="rounded-lg first:mr-auto"
|
class="rounded-lg first:mr-auto"
|
||||||
:label="
|
:label="
|
||||||
$t(
|
t(
|
||||||
`desktopDialogs.${id}.buttons.${normalizeI18nKey(button.label)}`,
|
`desktopDialogs.${id}.buttons.${normalizeI18nKey(button.label)}`,
|
||||||
button.label
|
button.label
|
||||||
)
|
)
|
||||||
@@ -31,6 +31,7 @@ import { useRoute } from 'vue-router'
|
|||||||
|
|
||||||
import { getDialog } from '@/constants/desktopDialogs'
|
import { getDialog } from '@/constants/desktopDialogs'
|
||||||
import type { DialogAction } from '@/constants/desktopDialogs'
|
import type { DialogAction } from '@/constants/desktopDialogs'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import { electronAPI } from '@/utils/envUtil'
|
import { electronAPI } from '@/utils/envUtil'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -40,3 +41,31 @@ const handleButtonClick = async (button: DialogAction) => {
|
|||||||
await electronAPI().Dialog.clickButton(button.returnValue)
|
await electronAPI().Dialog.clickButton(button.returnValue)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@reference '../assets/css/style.css';
|
||||||
|
|
||||||
|
.p-button-secondary {
|
||||||
|
@apply text-white border-none bg-neutral-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-button-secondary:hover {
|
||||||
|
@apply bg-neutral-550;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-button-secondary:active {
|
||||||
|
@apply bg-neutral-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-button-danger {
|
||||||
|
@apply bg-coral-red-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-button-danger:hover {
|
||||||
|
@apply bg-coral-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-button-danger:active {
|
||||||
|
@apply bg-coral-red-400;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
<div class="relative m-8 text-center">
|
<div class="relative m-8 text-center">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<h1 class="download-bg pi-download text-4xl font-bold">
|
<h1 class="download-bg pi-download text-4xl font-bold">
|
||||||
{{ $t('desktopUpdate.title') }}
|
{{ t('desktopUpdate.title') }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="m-8">
|
<div class="m-8">
|
||||||
<span>{{ $t('desktopUpdate.description') }}</span>
|
<span>{{ t('desktopUpdate.description') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProgressSpinner class="m-8 w-48 h-48" />
|
<ProgressSpinner class="m-8 w-48 h-48" />
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<Button
|
<Button
|
||||||
style="transform: translateX(-50%)"
|
style="transform: translateX(-50%)"
|
||||||
class="fixed bottom-0 left-1/2 my-8"
|
class="fixed bottom-0 left-1/2 my-8"
|
||||||
:label="$t('maintenance.consoleLogs')"
|
:label="t('maintenance.consoleLogs')"
|
||||||
icon="pi pi-desktop"
|
icon="pi pi-desktop"
|
||||||
icon-pos="left"
|
icon-pos="left"
|
||||||
severity="secondary"
|
severity="secondary"
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
|
|
||||||
<TerminalOutputDrawer
|
<TerminalOutputDrawer
|
||||||
v-model="terminalVisible"
|
v-model="terminalVisible"
|
||||||
:header="$t('g.terminal')"
|
:header="t('g.terminal')"
|
||||||
:default-message="$t('desktopUpdate.terminalDefaultMessage')"
|
:default-message="t('desktopUpdate.terminalDefaultMessage')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,6 +44,7 @@ import Toast from 'primevue/toast'
|
|||||||
import { onUnmounted, ref } from 'vue'
|
import { onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
import TerminalOutputDrawer from '@/components/maintenance/TerminalOutputDrawer.vue'
|
import TerminalOutputDrawer from '@/components/maintenance/TerminalOutputDrawer.vue'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import { electronAPI } from '@/utils/envUtil'
|
import { electronAPI } from '@/utils/envUtil'
|
||||||
|
|
||||||
import BaseViewTemplate from './templates/BaseViewTemplate.vue'
|
import BaseViewTemplate from './templates/BaseViewTemplate.vue'
|
||||||
@@ -60,10 +61,10 @@ onUnmounted(() => electron.Validation.dispose())
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@reference '../assets/css/style.css';
|
||||||
|
|
||||||
.download-bg::before {
|
.download-bg::before {
|
||||||
position: absolute;
|
@apply m-0 absolute text-muted;
|
||||||
margin: 0;
|
|
||||||
color: var(--muted-foreground);
|
|
||||||
font-family: 'primeicons', sans-serif;
|
font-family: 'primeicons', sans-serif;
|
||||||
top: -2rem;
|
top: -2rem;
|
||||||
right: 2rem;
|
right: 2rem;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// eslint-disable-next-line storybook/no-renderer-packages
|
// eslint-disable-next-line storybook/no-renderer-packages
|
||||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||||
import type { ElectronAPI } from '@comfyorg/comfyui-electron-types'
|
|
||||||
import { nextTick, provide } from 'vue'
|
import { nextTick, provide } from 'vue'
|
||||||
import type { ElectronWindow } from '@/utils/envUtil'
|
|
||||||
import { createMemoryHistory, createRouter } from 'vue-router'
|
import { createMemoryHistory, createRouter } from 'vue-router'
|
||||||
|
|
||||||
import InstallView from './InstallView.vue'
|
import InstallView from './InstallView.vue'
|
||||||
@@ -44,21 +42,16 @@ const meta: Meta<typeof InstallView> = {
|
|||||||
const router = createMockRouter()
|
const router = createMockRouter()
|
||||||
|
|
||||||
// Mock electron API
|
// Mock electron API
|
||||||
;(window as ElectronWindow).electronAPI = {
|
;(window as any).electronAPI = {
|
||||||
getPlatform: () => 'darwin',
|
getPlatform: () => 'darwin',
|
||||||
Config: {
|
Config: {
|
||||||
getDetectedGpu: () => Promise.resolve('mps')
|
getDetectedGpu: () => Promise.resolve('mps')
|
||||||
},
|
},
|
||||||
Events: {
|
Events: {
|
||||||
trackEvent: (
|
trackEvent: (_eventName: string, _data?: any) => {}
|
||||||
_eventName: string,
|
|
||||||
_data?: Record<string, unknown>
|
|
||||||
) => {}
|
|
||||||
},
|
},
|
||||||
installComfyUI: (
|
installComfyUI: (_options: any) => {},
|
||||||
_options: Parameters<ElectronAPI['installComfyUI']>[0]
|
changeTheme: (_theme: any) => {},
|
||||||
) => {},
|
|
||||||
changeTheme: (_theme: Parameters<ElectronAPI['changeTheme']>[0]) => {},
|
|
||||||
getSystemPaths: () =>
|
getSystemPaths: () =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
defaultInstallPath: '/Users/username/ComfyUI'
|
defaultInstallPath: '/Users/username/ComfyUI'
|
||||||
@@ -247,8 +240,8 @@ export const DesktopSettings: Story = {
|
|||||||
export const WindowsPlatform: Story = {
|
export const WindowsPlatform: Story = {
|
||||||
render: () => {
|
render: () => {
|
||||||
// Override the platform to Windows
|
// Override the platform to Windows
|
||||||
;(window as ElectronWindow).electronAPI.getPlatform = () => 'win32'
|
;(window as any).electronAPI.getPlatform = () => 'win32'
|
||||||
;(window as ElectronWindow).electronAPI.Config.getDetectedGpu = () =>
|
;(window as any).electronAPI.Config.getDetectedGpu = () =>
|
||||||
Promise.resolve('nvidia')
|
Promise.resolve('nvidia')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -266,8 +259,8 @@ export const MacOSPlatform: Story = {
|
|||||||
name: 'macOS Platform',
|
name: 'macOS Platform',
|
||||||
render: () => {
|
render: () => {
|
||||||
// Override the platform to macOS
|
// Override the platform to macOS
|
||||||
;(window as ElectronWindow).electronAPI.getPlatform = () => 'darwin'
|
;(window as any).electronAPI.getPlatform = () => 'darwin'
|
||||||
;(window as ElectronWindow).electronAPI.Config.getDetectedGpu = () =>
|
;(window as any).electronAPI.Config.getDetectedGpu = () =>
|
||||||
Promise.resolve('mps')
|
Promise.resolve('mps')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -334,7 +327,7 @@ export const ManualInstall: Story = {
|
|||||||
export const ErrorState: Story = {
|
export const ErrorState: Story = {
|
||||||
render: () => {
|
render: () => {
|
||||||
// Override validation to return an error
|
// Override validation to return an error
|
||||||
;(window as ElectronWindow).electronAPI.validateInstallPath = () =>
|
;(window as any).electronAPI.validateInstallPath = () =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isValid: false,
|
isValid: false,
|
||||||
exists: false,
|
exists: false,
|
||||||
@@ -382,7 +375,7 @@ export const ErrorState: Story = {
|
|||||||
export const WarningState: Story = {
|
export const WarningState: Story = {
|
||||||
render: () => {
|
render: () => {
|
||||||
// Override validation to return a warning about non-default drive
|
// Override validation to return a warning about non-default drive
|
||||||
;(window as ElectronWindow).electronAPI.validateInstallPath = () =>
|
;(window as any).electronAPI.validateInstallPath = () =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isValid: true,
|
isValid: true,
|
||||||
exists: false,
|
exists: false,
|
||||||
|
|||||||
@@ -183,37 +183,33 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@reference '../assets/css/style.css';
|
||||||
|
|
||||||
:deep(.p-steppanel) {
|
:deep(.p-steppanel) {
|
||||||
margin-top: calc(var(--spacing) * 8);
|
@apply mt-8 flex justify-center bg-transparent;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove default padding/margin from StepPanels to make scrollbar flush */
|
/* Remove default padding/margin from StepPanels to make scrollbar flush */
|
||||||
:deep(.p-steppanels) {
|
:deep(.p-steppanels) {
|
||||||
margin: 0;
|
@apply p-0 m-0;
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure StepPanel content container has no top/bottom padding */
|
/* Ensure StepPanel content container has no top/bottom padding */
|
||||||
:deep(.p-steppanel-content) {
|
:deep(.p-steppanel-content) {
|
||||||
padding: 0;
|
@apply p-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom overlay scrollbar for WebKit browsers (Electron, Chrome) */
|
/* Custom overlay scrollbar for WebKit browsers (Electron, Chrome) */
|
||||||
:deep(.p-steppanels::-webkit-scrollbar) {
|
:deep(.p-steppanels::-webkit-scrollbar) {
|
||||||
width: calc(var(--spacing) * 4);
|
@apply w-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-steppanels::-webkit-scrollbar-track) {
|
:deep(.p-steppanels::-webkit-scrollbar-track) {
|
||||||
background-color: transparent;
|
@apply bg-transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-steppanels::-webkit-scrollbar-thumb) {
|
:deep(.p-steppanels::-webkit-scrollbar-thumb) {
|
||||||
border: 4px solid transparent;
|
@apply bg-white/20 rounded-lg border-[4px] border-transparent;
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
background-color: color-mix(in srgb, var(--color-white) 20%, transparent);
|
|
||||||
background-clip: content-box;
|
background-clip: content-box;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const createMockElectronAPI = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ensureElectronAPI = () => {
|
const ensureElectronAPI = () => {
|
||||||
const globalWindow = window as { electronAPI?: unknown }
|
const globalWindow = window as unknown as { electronAPI?: unknown }
|
||||||
if (!globalWindow.electronAPI) {
|
if (!globalWindow.electronAPI) {
|
||||||
globalWindow.electronAPI = createMockElectronAPI()
|
globalWindow.electronAPI = createMockElectronAPI()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,12 +114,12 @@ import Tag from 'primevue/tag'
|
|||||||
import Toast from 'primevue/toast'
|
import Toast from 'primevue/toast'
|
||||||
import { useToast } from 'primevue/usetoast'
|
import { useToast } from 'primevue/usetoast'
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
import RefreshButton from '@/components/common/RefreshButton.vue'
|
import RefreshButton from '@/components/common/RefreshButton.vue'
|
||||||
import StatusTag from '@/components/maintenance/StatusTag.vue'
|
import StatusTag from '@/components/maintenance/StatusTag.vue'
|
||||||
import TaskListPanel from '@/components/maintenance/TaskListPanel.vue'
|
import TaskListPanel from '@/components/maintenance/TaskListPanel.vue'
|
||||||
import TerminalOutputDrawer from '@/components/maintenance/TerminalOutputDrawer.vue'
|
import TerminalOutputDrawer from '@/components/maintenance/TerminalOutputDrawer.vue'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import { useMaintenanceTaskStore } from '@/stores/maintenanceTaskStore'
|
import { useMaintenanceTaskStore } from '@/stores/maintenanceTaskStore'
|
||||||
import type { MaintenanceFilter } from '@/types/desktop/maintenanceTypes'
|
import type { MaintenanceFilter } from '@/types/desktop/maintenanceTypes'
|
||||||
import { electronAPI } from '@/utils/envUtil'
|
import { electronAPI } from '@/utils/envUtil'
|
||||||
@@ -129,7 +129,6 @@ import BaseViewTemplate from './templates/BaseViewTemplate.vue'
|
|||||||
|
|
||||||
const electron = electronAPI()
|
const electron = electronAPI()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { t } = useI18n()
|
|
||||||
const taskStore = useMaintenanceTaskStore()
|
const taskStore = useMaintenanceTaskStore()
|
||||||
const { clearResolved, processUpdate, refreshDesktopTasks } = taskStore
|
const { clearResolved, processUpdate, refreshDesktopTasks } = taskStore
|
||||||
|
|
||||||
@@ -221,14 +220,14 @@ onUnmounted(() => electron.Validation.dispose())
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@reference '../assets/css/style.css';
|
||||||
|
|
||||||
:deep(.p-tag) {
|
:deep(.p-tag) {
|
||||||
--p-tag-gap: 0.375rem;
|
--p-tag-gap: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backspan::before {
|
.backspan::before {
|
||||||
position: absolute;
|
@apply m-0 absolute text-muted;
|
||||||
margin: 0;
|
|
||||||
color: var(--muted-foreground);
|
|
||||||
font-family: 'primeicons', sans-serif;
|
font-family: 'primeicons', sans-serif;
|
||||||
top: -2rem;
|
top: -2rem;
|
||||||
right: -2rem;
|
right: -2rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseViewTemplate>
|
<BaseViewTemplate>
|
||||||
<div class="sad-container grid items-center justify-evenly">
|
<div class="sad-container">
|
||||||
<!-- Right side image -->
|
<!-- Right side image -->
|
||||||
<img
|
<img
|
||||||
class="sad-girl"
|
class="sad-girl"
|
||||||
@@ -79,7 +79,10 @@ const continueToInstall = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@reference '../assets/css/style.css';
|
||||||
|
|
||||||
.sad-container {
|
.sad-container {
|
||||||
|
@apply grid items-center justify-evenly;
|
||||||
grid-template-columns: 25rem 1fr;
|
grid-template-columns: 25rem 1fr;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
|
|||||||
@@ -232,6 +232,8 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@reference '../assets/css/style.css';
|
||||||
|
|
||||||
/* Hide the xterm scrollbar completely */
|
/* Hide the xterm scrollbar completely */
|
||||||
:deep(.p-terminal) .xterm-viewport {
|
:deep(.p-terminal) .xterm-viewport {
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
|
|||||||
@@ -4,40 +4,5 @@ See `@docs/guidance/playwright.md` for Playwright best practices (auto-loaded fo
|
|||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
```text
|
- `assets/` - Test data (JSON workflows, fixtures)
|
||||||
browser_tests/
|
- Tests use premade JSON workflows to load desired graph state
|
||||||
├── assets/ - Test data (JSON workflows, images)
|
|
||||||
├── fixtures/
|
|
||||||
│ ├── ComfyPage.ts - Main fixture (delegates to helpers)
|
|
||||||
│ ├── ComfyMouse.ts - Mouse interaction helper
|
|
||||||
│ ├── VueNodeHelpers.ts - Vue Nodes 2.0 helpers
|
|
||||||
│ ├── selectors.ts - Centralized TestIds
|
|
||||||
│ ├── components/ - Page object components
|
|
||||||
│ │ ├── ContextMenu.ts
|
|
||||||
│ │ ├── SettingDialog.ts
|
|
||||||
│ │ ├── SidebarTab.ts
|
|
||||||
│ │ └── Topbar.ts
|
|
||||||
│ ├── helpers/ - Focused helper classes
|
|
||||||
│ │ ├── CanvasHelper.ts
|
|
||||||
│ │ ├── CommandHelper.ts
|
|
||||||
│ │ ├── KeyboardHelper.ts
|
|
||||||
│ │ ├── NodeOperationsHelper.ts
|
|
||||||
│ │ ├── SettingsHelper.ts
|
|
||||||
│ │ ├── WorkflowHelper.ts
|
|
||||||
│ │ └── ...
|
|
||||||
│ └── utils/ - Utility functions
|
|
||||||
├── helpers/ - Test-specific utilities
|
|
||||||
└── tests/ - Test files (*.spec.ts)
|
|
||||||
```
|
|
||||||
|
|
||||||
## After Making Changes
|
|
||||||
|
|
||||||
- Run `pnpm typecheck:browser` after modifying TypeScript files in this directory
|
|
||||||
- Run `pnpm exec eslint browser_tests/path/to/file.ts` to lint specific files
|
|
||||||
- Run `pnpm exec oxlint browser_tests/path/to/file.ts` to check with oxlint
|
|
||||||
|
|
||||||
## Skill Documentation
|
|
||||||
|
|
||||||
A Playwright test-writing skill exists at `.claude/skills/writing-playwright-tests/SKILL.md`.
|
|
||||||
|
|
||||||
The skill documents **meta-level guidance only** (gotchas, anti-patterns, decision guides). It does **not** duplicate fixture APIs - agents should read the fixture code directly in `browser_tests/fixtures/`.
|
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
{
|
|
||||||
"last_node_id": 7,
|
|
||||||
"last_link_id": 5,
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "T2IAdapterLoader",
|
|
||||||
"pos": [100, 100],
|
|
||||||
"size": [300, 80],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "CONTROL_NET",
|
|
||||||
"type": "CONTROL_NET",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "T2IAdapterLoader"
|
|
||||||
},
|
|
||||||
"widgets_values": ["t2iadapter_model.safetensors"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "CheckpointLoaderSimple",
|
|
||||||
"pos": [100, 300],
|
|
||||||
"size": [315, 98],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "MODEL",
|
|
||||||
"type": "MODEL",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CLIP",
|
|
||||||
"type": "CLIP",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "VAE",
|
|
||||||
"type": "VAE",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "CheckpointLoaderSimple"
|
|
||||||
},
|
|
||||||
"widgets_values": ["v1-5-pruned-emaonly-fp16.safetensors"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"type": "ResizeImagesByLongerEdge",
|
|
||||||
"pos": [500, 100],
|
|
||||||
"size": [300, 80],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [1],
|
|
||||||
"slot_index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "ResizeImagesByLongerEdge"
|
|
||||||
},
|
|
||||||
"widgets_values": [1024]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"type": "ImageScaleBy",
|
|
||||||
"pos": [500, 280],
|
|
||||||
"size": [300, 80],
|
|
||||||
"flags": {},
|
|
||||||
"order": 3,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "image",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [2, 3],
|
|
||||||
"slot_index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "ImageScaleBy"
|
|
||||||
},
|
|
||||||
"widgets_values": ["lanczos", 1.5]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "ImageBatch",
|
|
||||||
"pos": [900, 100],
|
|
||||||
"size": [300, 80],
|
|
||||||
"flags": {},
|
|
||||||
"order": 4,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "image1",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "image2",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [4],
|
|
||||||
"slot_index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "ImageBatch"
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"type": "SaveImage",
|
|
||||||
"pos": [900, 300],
|
|
||||||
"size": [300, 80],
|
|
||||||
"flags": {},
|
|
||||||
"order": 5,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "SaveImage"
|
|
||||||
},
|
|
||||||
"widgets_values": ["ComfyUI"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"type": "PreviewImage",
|
|
||||||
"pos": [1250, 100],
|
|
||||||
"size": [300, 250],
|
|
||||||
"flags": {},
|
|
||||||
"order": 6,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PreviewImage"
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
[1, 3, 0, 4, 0, "IMAGE"],
|
|
||||||
[2, 4, 0, 5, 0, "IMAGE"],
|
|
||||||
[3, 4, 0, 6, 0, "IMAGE"],
|
|
||||||
[4, 5, 0, 7, 0, "IMAGE"]
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"config": {},
|
|
||||||
"extra": {
|
|
||||||
"ds": {
|
|
||||||
"scale": 1,
|
|
||||||
"offset": [0, 0]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": 0.4
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
{
|
|
||||||
"last_node_id": 5,
|
|
||||||
"last_link_id": 2,
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "Load3DAnimation",
|
|
||||||
"pos": [100, 100],
|
|
||||||
"size": [300, 100],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "MESH",
|
|
||||||
"type": "MESH",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "Load3DAnimation"
|
|
||||||
},
|
|
||||||
"widgets_values": ["model.glb"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "Preview3DAnimation",
|
|
||||||
"pos": [450, 100],
|
|
||||||
"size": [300, 100],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "mesh",
|
|
||||||
"type": "MESH",
|
|
||||||
"link": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "Preview3DAnimation"
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"type": "ConditioningAverage ",
|
|
||||||
"pos": [100, 300],
|
|
||||||
"size": [300, 100],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "conditioning_to",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "conditioning_from",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"link": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "CONDITIONING",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"links": [1],
|
|
||||||
"slot_index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "ConditioningAverage "
|
|
||||||
},
|
|
||||||
"widgets_values": [1]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"type": "SDV_img2vid_Conditioning",
|
|
||||||
"pos": [450, 300],
|
|
||||||
"size": [300, 150],
|
|
||||||
"flags": {},
|
|
||||||
"order": 3,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "clip_vision",
|
|
||||||
"type": "CLIP_VISION",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "init_image",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vae",
|
|
||||||
"type": "VAE",
|
|
||||||
"link": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "positive",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "negative",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "latent",
|
|
||||||
"type": "LATENT",
|
|
||||||
"links": [2],
|
|
||||||
"slot_index": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "SDV_img2vid_Conditioning"
|
|
||||||
},
|
|
||||||
"widgets_values": [1024, 576, 14, 127, 25, 0.02]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "KSampler",
|
|
||||||
"pos": [800, 300],
|
|
||||||
"size": [300, 262],
|
|
||||||
"flags": {},
|
|
||||||
"order": 4,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "model",
|
|
||||||
"type": "MODEL",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "positive",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"link": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "negative",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "latent_image",
|
|
||||||
"type": "LATENT",
|
|
||||||
"link": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "LATENT",
|
|
||||||
"type": "LATENT",
|
|
||||||
"links": [],
|
|
||||||
"slot_index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "KSampler"
|
|
||||||
},
|
|
||||||
"widgets_values": [42, "fixed", 20, 8, "euler", "normal", 1]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
[1, 3, 0, 5, 1, "CONDITIONING"],
|
|
||||||
[2, 4, 2, 5, 3, "LATENT"]
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"config": {},
|
|
||||||
"extra": {
|
|
||||||
"ds": {
|
|
||||||
"scale": 1,
|
|
||||||
"offset": [0, 0]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": 0.4
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
||||||
"revision": 0,
|
|
||||||
"last_node_id": 2,
|
|
||||||
"last_link_id": 0,
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "e5fb1765-aaaa-bbbb-cccc-ddddeeee0001",
|
|
||||||
"pos": [600, 400],
|
|
||||||
"size": [200, 100],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "LATENT",
|
|
||||||
"type": "LATENT",
|
|
||||||
"links": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {},
|
|
||||||
"widgets_values": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [],
|
|
||||||
"groups": [],
|
|
||||||
"definitions": {
|
|
||||||
"subgraphs": [
|
|
||||||
{
|
|
||||||
"id": "e5fb1765-aaaa-bbbb-cccc-ddddeeee0001",
|
|
||||||
"version": 1,
|
|
||||||
"state": {
|
|
||||||
"lastGroupId": 0,
|
|
||||||
"lastNodeId": 2,
|
|
||||||
"lastLinkId": 5,
|
|
||||||
"lastRerouteId": 0
|
|
||||||
},
|
|
||||||
"revision": 0,
|
|
||||||
"config": {},
|
|
||||||
"name": "Subgraph With Duplicate Links",
|
|
||||||
"inputNode": {
|
|
||||||
"id": -10,
|
|
||||||
"bounding": [200, 400, 120, 60]
|
|
||||||
},
|
|
||||||
"outputNode": {
|
|
||||||
"id": -20,
|
|
||||||
"bounding": [900, 400, 120, 60]
|
|
||||||
},
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "out-latent-1",
|
|
||||||
"name": "LATENT",
|
|
||||||
"type": "LATENT",
|
|
||||||
"linkIds": [2],
|
|
||||||
"pos": [920, 420]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"widgets": [],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "KSampler",
|
|
||||||
"pos": [400, 100],
|
|
||||||
"size": [270, 262],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "model",
|
|
||||||
"type": "MODEL",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "positive",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "negative",
|
|
||||||
"type": "CONDITIONING",
|
|
||||||
"link": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "latent_image",
|
|
||||||
"type": "LATENT",
|
|
||||||
"link": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "LATENT",
|
|
||||||
"type": "LATENT",
|
|
||||||
"links": [2]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "KSampler"
|
|
||||||
},
|
|
||||||
"widgets_values": [0, "randomize", 20, 8, "euler", "simple", 1]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "EmptyLatentImage",
|
|
||||||
"pos": [100, 200],
|
|
||||||
"size": [200, 106],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "LATENT",
|
|
||||||
"type": "LATENT",
|
|
||||||
"links": [1, 3, 4, 5]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "EmptyLatentImage"
|
|
||||||
},
|
|
||||||
"widgets_values": [512, 512, 1]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"origin_id": 2,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 1,
|
|
||||||
"target_slot": 3,
|
|
||||||
"type": "LATENT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"origin_id": 1,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": -20,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "LATENT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"origin_id": 2,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 1,
|
|
||||||
"target_slot": 3,
|
|
||||||
"type": "LATENT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"origin_id": 2,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 1,
|
|
||||||
"target_slot": 3,
|
|
||||||
"type": "LATENT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"origin_id": 2,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 1,
|
|
||||||
"target_slot": 3,
|
|
||||||
"type": "LATENT"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extra": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"config": {},
|
|
||||||
"extra": {
|
|
||||||
"ds": {
|
|
||||||
"scale": 1,
|
|
||||||
"offset": [0, 0]
|
|
||||||
},
|
|
||||||
"frontendVersion": "1.38.14"
|
|
||||||
},
|
|
||||||
"version": 0.4
|
|
||||||
}
|
|
||||||
@@ -1,599 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "9a37f747-e96b-4304-9212-7abcaad7bdac",
|
|
||||||
"revision": 0,
|
|
||||||
"last_node_id": 5,
|
|
||||||
"last_link_id": 5,
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "1e38d8ea-45e1-48a5-aa20-966584201867",
|
|
||||||
"pos": [788, 433.5],
|
|
||||||
"size": [210, 108],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_a"
|
|
||||||
},
|
|
||||||
"link": 4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [5]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"proxyWidgets": [["-1", "string_a"]]
|
|
||||||
},
|
|
||||||
"widgets_values": [""]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "PreviewAny",
|
|
||||||
"pos": [1135, 429],
|
|
||||||
"size": [250, 145.5],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "source",
|
|
||||||
"type": "*",
|
|
||||||
"link": 5
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PreviewAny"
|
|
||||||
},
|
|
||||||
"widgets_values": [null, null, false]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "PrimitiveStringMultiline",
|
|
||||||
"pos": [456, 450],
|
|
||||||
"size": [225, 121.5],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [4]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PrimitiveStringMultiline"
|
|
||||||
},
|
|
||||||
"widgets_values": ["Outer\n"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
[4, 1, 0, 5, 0, "STRING"],
|
|
||||||
[5, 5, 0, 2, 0, "STRING"]
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"definitions": {
|
|
||||||
"subgraphs": [
|
|
||||||
{
|
|
||||||
"id": "1e38d8ea-45e1-48a5-aa20-966584201867",
|
|
||||||
"version": 1,
|
|
||||||
"state": {
|
|
||||||
"lastGroupId": 0,
|
|
||||||
"lastNodeId": 6,
|
|
||||||
"lastLinkId": 9,
|
|
||||||
"lastRerouteId": 0
|
|
||||||
},
|
|
||||||
"revision": 0,
|
|
||||||
"config": {},
|
|
||||||
"name": "New Subgraph",
|
|
||||||
"inputNode": {
|
|
||||||
"id": -10,
|
|
||||||
"bounding": [351, 432.5, 120, 60]
|
|
||||||
},
|
|
||||||
"outputNode": {
|
|
||||||
"id": -20,
|
|
||||||
"bounding": [1315, 432.5, 120, 60]
|
|
||||||
},
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "7bf3e1d4-0521-4b5c-92f5-47ca598b7eb4",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"linkIds": [1],
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"pos": [451, 452.5]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "fbe975ba-d7c2-471e-a99a-a1e2c6ab466d",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"linkIds": [9],
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"pos": [1335, 452.5]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"widgets": [],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"type": "StringConcatenate",
|
|
||||||
"pos": [815, 373],
|
|
||||||
"size": [347, 231],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_a"
|
|
||||||
},
|
|
||||||
"link": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"localized_name": "string_b",
|
|
||||||
"name": "string_b",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_b"
|
|
||||||
},
|
|
||||||
"link": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [7]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "StringConcatenate"
|
|
||||||
},
|
|
||||||
"widgets_values": ["", "", ""]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"type": "9be42452-056b-4c99-9f9f-7381d11c4454",
|
|
||||||
"pos": [955, 775],
|
|
||||||
"size": [210, 88],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_a"
|
|
||||||
},
|
|
||||||
"link": 7
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [9]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"proxyWidgets": [["-1", "string_a"]]
|
|
||||||
},
|
|
||||||
"widgets_values": [""]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"type": "PrimitiveStringMultiline",
|
|
||||||
"pos": [313, 685],
|
|
||||||
"size": [325, 109],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [2]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PrimitiveStringMultiline"
|
|
||||||
},
|
|
||||||
"widgets_values": ["Inner 1\n"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"origin_id": 4,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 3,
|
|
||||||
"target_slot": 1,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 3,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"origin_id": 3,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 6,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"origin_id": 6,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": -20,
|
|
||||||
"target_slot": 1,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"origin_id": 6,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": -20,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extra": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9be42452-056b-4c99-9f9f-7381d11c4454",
|
|
||||||
"version": 1,
|
|
||||||
"state": {
|
|
||||||
"lastGroupId": 0,
|
|
||||||
"lastNodeId": 9,
|
|
||||||
"lastLinkId": 12,
|
|
||||||
"lastRerouteId": 0
|
|
||||||
},
|
|
||||||
"revision": 0,
|
|
||||||
"config": {},
|
|
||||||
"name": "New Subgraph",
|
|
||||||
"inputNode": {
|
|
||||||
"id": -10,
|
|
||||||
"bounding": [680, 774, 120, 60]
|
|
||||||
},
|
|
||||||
"outputNode": {
|
|
||||||
"id": -20,
|
|
||||||
"bounding": [1320, 774, 120, 60]
|
|
||||||
},
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "01c05c51-86b5-4bad-b32f-9c911683a13d",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"linkIds": [4],
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"pos": [780, 794]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "a8bcf3bf-a66a-4c71-8d92-17a2a4d03686",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"linkIds": [12],
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"pos": [1340, 794]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"widgets": [],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "StringConcatenate",
|
|
||||||
"pos": [860, 719],
|
|
||||||
"size": [400, 200],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_a"
|
|
||||||
},
|
|
||||||
"link": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"localized_name": "string_b",
|
|
||||||
"name": "string_b",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_b"
|
|
||||||
},
|
|
||||||
"link": 7
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [11]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "StringConcatenate"
|
|
||||||
},
|
|
||||||
"widgets_values": ["", "", ""]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"type": "PrimitiveStringMultiline",
|
|
||||||
"pos": [401, 973],
|
|
||||||
"size": [400, 200],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [7]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PrimitiveStringMultiline"
|
|
||||||
},
|
|
||||||
"widgets_values": ["Inner 2\n"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"type": "7c2915a5-5eb8-4958-a8fd-4beb30f370ce",
|
|
||||||
"pos": [1046, 985],
|
|
||||||
"size": [210, 88],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_a"
|
|
||||||
},
|
|
||||||
"link": 11
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [12]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"proxyWidgets": [["-1", "string_a"]]
|
|
||||||
},
|
|
||||||
"widgets_values": [""]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 5,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"origin_id": 6,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 5,
|
|
||||||
"target_slot": 1,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"origin_id": 5,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 9,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"origin_id": 9,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": -20,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"origin_id": 9,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": -20,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extra": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7c2915a5-5eb8-4958-a8fd-4beb30f370ce",
|
|
||||||
"version": 1,
|
|
||||||
"state": {
|
|
||||||
"lastGroupId": 0,
|
|
||||||
"lastNodeId": 8,
|
|
||||||
"lastLinkId": 10,
|
|
||||||
"lastRerouteId": 0
|
|
||||||
},
|
|
||||||
"revision": 0,
|
|
||||||
"config": {},
|
|
||||||
"name": "New Subgraph",
|
|
||||||
"inputNode": {
|
|
||||||
"id": -10,
|
|
||||||
"bounding": [262, 1222, 120, 60]
|
|
||||||
},
|
|
||||||
"outputNode": {
|
|
||||||
"id": -20,
|
|
||||||
"bounding": [1330, 1222, 120, 60]
|
|
||||||
},
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "934a8baa-d79c-428c-8ec9-814ad437d7c7",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"linkIds": [9],
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"pos": [362, 1242]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "4c3d243b-9ff6-4dcd-9dbf-e4ec8e1fc879",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"linkIds": [10],
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"pos": [1350, 1242]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"widgets": [],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"type": "StringConcatenate",
|
|
||||||
"pos": [870, 1038],
|
|
||||||
"size": [400, 200],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "string_a",
|
|
||||||
"name": "string_a",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_a"
|
|
||||||
},
|
|
||||||
"link": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"localized_name": "string_b",
|
|
||||||
"name": "string_b",
|
|
||||||
"type": "STRING",
|
|
||||||
"widget": {
|
|
||||||
"name": "string_b"
|
|
||||||
},
|
|
||||||
"link": 8
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [10]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "StringConcatenate"
|
|
||||||
},
|
|
||||||
"widgets_values": ["", "", ""]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"type": "PrimitiveStringMultiline",
|
|
||||||
"pos": [442, 1296],
|
|
||||||
"size": [400, 200],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "STRING",
|
|
||||||
"name": "STRING",
|
|
||||||
"type": "STRING",
|
|
||||||
"links": [8]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PrimitiveStringMultiline"
|
|
||||||
},
|
|
||||||
"widgets_values": ["Inner 3\n"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"origin_id": 8,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 7,
|
|
||||||
"target_slot": 1,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 7,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"origin_id": 7,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": -20,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "STRING"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extra": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"config": {},
|
|
||||||
"extra": {
|
|
||||||
"ds": {
|
|
||||||
"scale": 1,
|
|
||||||
"offset": [-7, 144]
|
|
||||||
},
|
|
||||||
"frontendVersion": "1.38.13"
|
|
||||||
},
|
|
||||||
"version": 0.4
|
|
||||||
}
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "43e9499c-2512-43b5-a5a1-2485eb65da32",
|
|
||||||
"revision": 0,
|
|
||||||
"last_node_id": 8,
|
|
||||||
"last_link_id": 10,
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "LoadImage",
|
|
||||||
"pos": [170.55728894250745, 515.6401487466619],
|
|
||||||
"size": [282.8166809082031, 363.8333435058594],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [7, 9]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "MASK",
|
|
||||||
"type": "MASK",
|
|
||||||
"links": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "LoadImage"
|
|
||||||
},
|
|
||||||
"widgets_values": ["example.png", "image"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"type": "21dea088-e1b4-47a4-a01f-3d1bf4504001",
|
|
||||||
"pos": [500.2639113468392, 519.9960755960157],
|
|
||||||
"size": [464.95001220703125, 615.8333129882812],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 7
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [10]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"proxyWidgets": [
|
|
||||||
["2", "$$canvas-image-preview"],
|
|
||||||
["4", "$$canvas-image-preview"]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"type": "a7a0350a-af99-4d26-9391-450b4f726206",
|
|
||||||
"pos": [1000.5293620197185, 499.9253405678786],
|
|
||||||
"size": [225, 359.8333435058594],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "image1",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "image2",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 10
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {
|
|
||||||
"proxyWidgets": [["6", "$$canvas-image-preview"]]
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
[7, 1, 0, 7, 0, "IMAGE"],
|
|
||||||
[9, 1, 0, 8, 0, "IMAGE"],
|
|
||||||
[10, 7, 0, 8, 1, "IMAGE"]
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"definitions": {
|
|
||||||
"subgraphs": [
|
|
||||||
{
|
|
||||||
"id": "21dea088-e1b4-47a4-a01f-3d1bf4504001",
|
|
||||||
"version": 1,
|
|
||||||
"state": {
|
|
||||||
"lastGroupId": 0,
|
|
||||||
"lastNodeId": 8,
|
|
||||||
"lastLinkId": 10,
|
|
||||||
"lastRerouteId": 0
|
|
||||||
},
|
|
||||||
"revision": 0,
|
|
||||||
"config": {},
|
|
||||||
"name": "New Subgraph",
|
|
||||||
"inputNode": {
|
|
||||||
"id": -10,
|
|
||||||
"bounding": [297.7833638107301, 502.6302057820892, 120, 60]
|
|
||||||
},
|
|
||||||
"outputNode": {
|
|
||||||
"id": -20,
|
|
||||||
"bounding": [1052.8175480718996, 502.6302057820892, 120, 60]
|
|
||||||
},
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "afc8dbc3-12e6-4b3c-9840-9b398d06e6bd",
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"linkIds": [1, 2],
|
|
||||||
"localized_name": "images",
|
|
||||||
"pos": [397.7833638107301, 522.6302057820892]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"id": "d0a84974-5f4d-4f4b-b23a-2e7288a9689d",
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"linkIds": [5],
|
|
||||||
"localized_name": "IMAGE",
|
|
||||||
"pos": [1072.8175480718996, 522.6302057820892]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"widgets": [],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"type": "PreviewImage",
|
|
||||||
"pos": [767.8225773415076, 602.8695134060456],
|
|
||||||
"size": [225, 303.8333435058594],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "images",
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PreviewImage"
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "PreviewImage",
|
|
||||||
"pos": [754.9358989867657, 188.55375831225257],
|
|
||||||
"size": [225, 303.8333435058594],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "images",
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PreviewImage"
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"type": "ImageInvert",
|
|
||||||
"pos": [477.783932416778, 542.2440719627998],
|
|
||||||
"size": [225, 71.83333587646484],
|
|
||||||
"flags": {
|
|
||||||
"collapsed": false
|
|
||||||
},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "image",
|
|
||||||
"name": "image",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "IMAGE",
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [3, 5]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "ImageInvert"
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"origin_id": 3,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 4,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "IMAGE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 2,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "IMAGE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 3,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "IMAGE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"origin_id": 3,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": -20,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "IMAGE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extra": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a7a0350a-af99-4d26-9391-450b4f726206",
|
|
||||||
"version": 1,
|
|
||||||
"state": {
|
|
||||||
"lastGroupId": 0,
|
|
||||||
"lastNodeId": 8,
|
|
||||||
"lastLinkId": 10,
|
|
||||||
"lastRerouteId": 0
|
|
||||||
},
|
|
||||||
"revision": 0,
|
|
||||||
"config": {},
|
|
||||||
"name": "New Subgraph",
|
|
||||||
"inputNode": {
|
|
||||||
"id": -10,
|
|
||||||
"bounding": [973.7423316105073, 561.9744246746379, 120, 80]
|
|
||||||
},
|
|
||||||
"outputNode": {
|
|
||||||
"id": -20,
|
|
||||||
"bounding": [1905.487372786412, 581.9744246746379, 120, 40]
|
|
||||||
},
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "20ac4159-6814-4d40-a217-ea260152b689",
|
|
||||||
"name": "image1",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"linkIds": [4],
|
|
||||||
"localized_name": "image1",
|
|
||||||
"pos": [1073.7423316105073, 581.9744246746379]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c3759a8c-914e-4450-bc41-ca683ffce96b",
|
|
||||||
"name": "image2",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"linkIds": [8],
|
|
||||||
"localized_name": "image2",
|
|
||||||
"shape": 7,
|
|
||||||
"pos": [1073.7423316105073, 601.9744246746379]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"widgets": [],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "ImageStitch",
|
|
||||||
"pos": [1153.7423085222254, 396.2033931749105],
|
|
||||||
"size": [270, 225.1666717529297],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "image1",
|
|
||||||
"name": "image1",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"localized_name": "image2",
|
|
||||||
"name": "image2",
|
|
||||||
"shape": 7,
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 8
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "IMAGE",
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [6]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "ImageStitch"
|
|
||||||
},
|
|
||||||
"widgets_values": ["right", true, 0, "white"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"type": "PreviewImage",
|
|
||||||
"pos": [1620.4874189629757, 529.9122050216333],
|
|
||||||
"size": [225, 307.8333435058594],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "images",
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 6
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "PreviewImage"
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"origin_id": 5,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 6,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "IMAGE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 5,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "IMAGE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 1,
|
|
||||||
"target_id": 5,
|
|
||||||
"target_slot": 1,
|
|
||||||
"type": "IMAGE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extra": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"config": {},
|
|
||||||
"extra": {
|
|
||||||
"ds": {
|
|
||||||
"scale": 0.7269777827561446,
|
|
||||||
"offset": [-35.273237658266034, -55.17394203309256]
|
|
||||||
},
|
|
||||||
"frontendVersion": "1.40.8"
|
|
||||||
},
|
|
||||||
"version": 0.4
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "preview-subgraph-test-001",
|
|
||||||
"revision": 0,
|
|
||||||
"last_node_id": 11,
|
|
||||||
"last_link_id": 2,
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
||||||
"pos": [318.6320139868054, 212.9091015141833],
|
|
||||||
"size": [225, 368],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {
|
|
||||||
"proxyWidgets": [
|
|
||||||
["10", "filename_prefix"],
|
|
||||||
["10", "$$canvas-image-preview"]
|
|
||||||
],
|
|
||||||
"cnr_id": "comfy-core",
|
|
||||||
"ver": "0.13.0",
|
|
||||||
"ue_properties": {
|
|
||||||
"widget_ue_connectable": {},
|
|
||||||
"version": "7.6.2",
|
|
||||||
"input_ue_unconnectable": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"widgets_values": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"type": "LoadImage",
|
|
||||||
"pos": [-0.5080003681592018, 211.3051121416672],
|
|
||||||
"size": [282.8333435058594, 364],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [2]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "MASK",
|
|
||||||
"type": "MASK",
|
|
||||||
"links": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"ue_properties": {
|
|
||||||
"widget_ue_connectable": {},
|
|
||||||
"input_ue_unconnectable": {}
|
|
||||||
},
|
|
||||||
"cnr_id": "comfy-core",
|
|
||||||
"ver": "0.13.0",
|
|
||||||
"Node name for S&R": "LoadImage"
|
|
||||||
},
|
|
||||||
"widgets_values": ["example.png", "image"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [[2, 11, 0, 5, 0, "IMAGE"]],
|
|
||||||
"groups": [],
|
|
||||||
"definitions": {
|
|
||||||
"subgraphs": [
|
|
||||||
{
|
|
||||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
||||||
"version": 1,
|
|
||||||
"state": {
|
|
||||||
"lastGroupId": 0,
|
|
||||||
"lastNodeId": 11,
|
|
||||||
"lastLinkId": 2,
|
|
||||||
"lastRerouteId": 0
|
|
||||||
},
|
|
||||||
"revision": 0,
|
|
||||||
"config": {},
|
|
||||||
"name": "New Subgraph",
|
|
||||||
"inputNode": {
|
|
||||||
"id": -10,
|
|
||||||
"bounding": [300, 350, 120, 60]
|
|
||||||
},
|
|
||||||
"outputNode": {
|
|
||||||
"id": -20,
|
|
||||||
"bounding": [900, 350, 120, 40]
|
|
||||||
},
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "img-slot-001",
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"linkIds": [1],
|
|
||||||
"pos": [400, 370]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"widgets": [],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"type": "SaveImage",
|
|
||||||
"pos": [500.0046924937855, 300.0146992076527],
|
|
||||||
"size": [315, 340],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"localized_name": "images",
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {
|
|
||||||
"cnr_id": "comfy-core",
|
|
||||||
"ver": "0.13.0",
|
|
||||||
"Node name for S&R": "SaveImage",
|
|
||||||
"ue_properties": {
|
|
||||||
"widget_ue_connectable": {},
|
|
||||||
"version": "7.6.2",
|
|
||||||
"input_ue_unconnectable": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"widgets_values": ["ComfyUI"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"origin_id": -10,
|
|
||||||
"origin_slot": 0,
|
|
||||||
"target_id": 10,
|
|
||||||
"target_slot": 0,
|
|
||||||
"type": "IMAGE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extra": {
|
|
||||||
"ue_links": [],
|
|
||||||
"links_added_by_ue": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"config": {},
|
|
||||||
"extra": {
|
|
||||||
"ds": {
|
|
||||||
"scale": 1.1819400303977265,
|
|
||||||
"offset": [81.66005130613983, -19.028558221588725]
|
|
||||||
},
|
|
||||||
"frontendVersion": "1.40.3",
|
|
||||||
"ue_links": [],
|
|
||||||
"links_added_by_ue": [],
|
|
||||||
"VHS_latentpreview": false,
|
|
||||||
"VHS_latentpreviewrate": 0,
|
|
||||||
"VHS_MetadataImage": true,
|
|
||||||
"VHS_KeepIntermediate": true
|
|
||||||
},
|
|
||||||
"version": 0.4
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "save-image-and-webm-test",
|
|
||||||
"revision": 0,
|
|
||||||
"last_node_id": 12,
|
|
||||||
"last_link_id": 2,
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"type": "LoadImage",
|
|
||||||
"pos": [50, 100],
|
|
||||||
"size": [315, 314],
|
|
||||||
"flags": {},
|
|
||||||
"order": 0,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "IMAGE",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"links": [1, 2]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "MASK",
|
|
||||||
"type": "MASK",
|
|
||||||
"links": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Node name for S&R": "LoadImage"
|
|
||||||
},
|
|
||||||
"widgets_values": ["example.png", "image"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"type": "SaveImage",
|
|
||||||
"pos": [450, 100],
|
|
||||||
"size": [210, 270],
|
|
||||||
"flags": {},
|
|
||||||
"order": 1,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {},
|
|
||||||
"widgets_values": ["ComfyUI"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"type": "SaveWEBM",
|
|
||||||
"pos": [450, 450],
|
|
||||||
"size": [210, 368],
|
|
||||||
"flags": {},
|
|
||||||
"order": 2,
|
|
||||||
"mode": 0,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "images",
|
|
||||||
"type": "IMAGE",
|
|
||||||
"link": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [],
|
|
||||||
"properties": {},
|
|
||||||
"widgets_values": ["ComfyUI", "vp9", 6, 32]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
[1, 10, 0, 11, 0, "IMAGE"],
|
|
||||||
[2, 10, 0, 12, 0, "IMAGE"]
|
|
||||||
],
|
|
||||||
"groups": [],
|
|
||||||
"config": {},
|
|
||||||
"extra": {
|
|
||||||
"frontendVersion": "1.17.0",
|
|
||||||
"ds": {
|
|
||||||
"offset": [0, 0],
|
|
||||||
"scale": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": 0.4
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 517 B |
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
import type { Locator, Page } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
import { TestIds } from './selectors'
|
|
||||||
import { VueNodeFixture } from './utils/vueNodeFixtures'
|
import { VueNodeFixture } from './utils/vueNodeFixtures'
|
||||||
|
|
||||||
export class VueNodeHelpers {
|
export class VueNodeHelpers {
|
||||||
@@ -149,9 +148,9 @@ export class VueNodeHelpers {
|
|||||||
* Get a specific widget by node title and widget name
|
* Get a specific widget by node title and widget name
|
||||||
*/
|
*/
|
||||||
getWidgetByName(nodeTitle: string, widgetName: string): Locator {
|
getWidgetByName(nodeTitle: string, widgetName: string): Locator {
|
||||||
return this.getNodeByTitle(nodeTitle).getByLabel(widgetName, {
|
return this.getNodeByTitle(nodeTitle).locator(
|
||||||
exact: true
|
`_vue=[widget.name="${widgetName}"]`
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,8 +159,8 @@ export class VueNodeHelpers {
|
|||||||
getInputNumberControls(widget: Locator) {
|
getInputNumberControls(widget: Locator) {
|
||||||
return {
|
return {
|
||||||
input: widget.locator('input'),
|
input: widget.locator('input'),
|
||||||
decrementButton: widget.getByTestId(TestIds.widgets.decrement),
|
decrementButton: widget.getByTestId('decrement'),
|
||||||
incrementButton: widget.getByTestId(TestIds.widgets.increment)
|
incrementButton: widget.getByTestId('increment')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +170,7 @@ export class VueNodeHelpers {
|
|||||||
*/
|
*/
|
||||||
async enterSubgraph(nodeId?: string): Promise<void> {
|
async enterSubgraph(nodeId?: string): Promise<void> {
|
||||||
const locator = nodeId ? this.getNodeLocator(nodeId) : this.page
|
const locator = nodeId ? this.getNodeLocator(nodeId) : this.page
|
||||||
const editButton = locator.getByTestId(TestIds.widgets.subgraphEnterButton)
|
const editButton = locator.getByTestId('subgraph-enter-button')
|
||||||
await editButton.click()
|
await editButton.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
|
||||||
|
|
||||||
export class BaseDialog {
|
|
||||||
readonly root: Locator
|
|
||||||
readonly closeButton: Locator
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public readonly page: Page,
|
|
||||||
testId?: string
|
|
||||||
) {
|
|
||||||
this.root = testId ? page.getByTestId(testId) : page.locator('.p-dialog')
|
|
||||||
this.closeButton = this.root.getByRole('button', { name: 'Close' })
|
|
||||||
}
|
|
||||||
|
|
||||||
async isVisible(): Promise<boolean> {
|
|
||||||
return this.root.isVisible()
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForVisible(): Promise<void> {
|
|
||||||
await this.root.waitFor({ state: 'visible' })
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForHidden(): Promise<void> {
|
|
||||||
await this.root.waitFor({ state: 'hidden' })
|
|
||||||
}
|
|
||||||
|
|
||||||
async close(): Promise<void> {
|
|
||||||
await this.closeButton.click({ force: true })
|
|
||||||
await this.waitForHidden()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
|
||||||
|
|
||||||
class ShortcutsTab {
|
|
||||||
readonly essentialsTab: Locator
|
|
||||||
readonly viewControlsTab: Locator
|
|
||||||
readonly manageButton: Locator
|
|
||||||
readonly keyBadges: Locator
|
|
||||||
readonly subcategoryTitles: Locator
|
|
||||||
|
|
||||||
constructor(readonly page: Page) {
|
|
||||||
this.essentialsTab = page.getByRole('tab', { name: /Essential/i })
|
|
||||||
this.viewControlsTab = page.getByRole('tab', { name: /View Controls/i })
|
|
||||||
this.manageButton = page.getByRole('button', { name: /Manage Shortcuts/i })
|
|
||||||
this.keyBadges = page.locator('.key-badge')
|
|
||||||
this.subcategoryTitles = page.locator('.subcategory-title')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BottomPanel {
|
|
||||||
readonly root: Locator
|
|
||||||
readonly keyboardShortcutsButton: Locator
|
|
||||||
readonly toggleButton: Locator
|
|
||||||
readonly shortcuts: ShortcutsTab
|
|
||||||
|
|
||||||
constructor(readonly page: Page) {
|
|
||||||
this.root = page.locator('.bottom-panel')
|
|
||||||
this.keyboardShortcutsButton = page.getByRole('button', {
|
|
||||||
name: /Keyboard Shortcuts/i
|
|
||||||
})
|
|
||||||
this.toggleButton = page.getByRole('button', {
|
|
||||||
name: /Toggle Bottom Panel/i
|
|
||||||
})
|
|
||||||
this.shortcuts = new ShortcutsTab(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ export class ComfyNodeSearchFilterSelectionPanel {
|
|||||||
async addFilter(filterValue: string, filterType: string) {
|
async addFilter(filterValue: string, filterType: string) {
|
||||||
await this.selectFilterType(filterType)
|
await this.selectFilterType(filterType)
|
||||||
await this.selectFilterValue(filterValue)
|
await this.selectFilterValue(filterValue)
|
||||||
await this.page.getByRole('button', { name: 'Add', exact: true }).click()
|
await this.page.locator('button:has-text("Add")').click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,11 +80,4 @@ export class ComfyNodeSearchBox {
|
|||||||
async removeFilter(index: number) {
|
async removeFilter(index: number) {
|
||||||
await this.filterChips.nth(index).locator('.p-chip-remove-icon').click()
|
await this.filterChips.nth(index).locator('.p-chip-remove-icon').click()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a locator for a search result containing the specified text.
|
|
||||||
*/
|
|
||||||
findResult(text: string): Locator {
|
|
||||||
return this.dropdown.locator('li').filter({ hasText: text })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
|
||||||
|
|
||||||
import type { ComfyPage } from '../ComfyPage'
|
|
||||||
|
|
||||||
export class ComfyNodeSearchBoxV2 {
|
|
||||||
readonly dialog: Locator
|
|
||||||
readonly input: Locator
|
|
||||||
readonly results: Locator
|
|
||||||
readonly filterOptions: Locator
|
|
||||||
|
|
||||||
constructor(readonly page: Page) {
|
|
||||||
this.dialog = page.getByRole('search')
|
|
||||||
this.input = this.dialog.locator('input[type="text"]')
|
|
||||||
this.results = this.dialog.getByTestId('result-item')
|
|
||||||
this.filterOptions = this.dialog.getByTestId('filter-option')
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryButton(categoryId: string): Locator {
|
|
||||||
return this.dialog.getByTestId(`category-${categoryId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
filterBarButton(name: string): Locator {
|
|
||||||
return this.dialog.getByRole('button', { name })
|
|
||||||
}
|
|
||||||
|
|
||||||
async reload(comfyPage: ComfyPage) {
|
|
||||||
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
|
||||||
|
|
||||||
export class ContextMenu {
|
|
||||||
constructor(public readonly page: Page) {}
|
|
||||||
|
|
||||||
get primeVueMenu() {
|
|
||||||
return this.page.locator('.p-contextmenu, .p-menu')
|
|
||||||
}
|
|
||||||
|
|
||||||
get litegraphMenu() {
|
|
||||||
return this.page.locator('.litemenu')
|
|
||||||
}
|
|
||||||
|
|
||||||
get menuItems() {
|
|
||||||
return this.page.locator('.p-menuitem, .litemenu-entry')
|
|
||||||
}
|
|
||||||
|
|
||||||
async clickMenuItem(name: string): Promise<void> {
|
|
||||||
await this.page.getByRole('menuitem', { name }).click()
|
|
||||||
}
|
|
||||||
|
|
||||||
async clickLitegraphMenuItem(name: string): Promise<void> {
|
|
||||||
await this.page.locator(`.litemenu-entry:has-text("${name}")`).click()
|
|
||||||
}
|
|
||||||
|
|
||||||
async isVisible(): Promise<boolean> {
|
|
||||||
const primeVueVisible = await this.primeVueMenu
|
|
||||||
.isVisible()
|
|
||||||
.catch(() => false)
|
|
||||||
const litegraphVisible = await this.litegraphMenu
|
|
||||||
.isVisible()
|
|
||||||
.catch(() => false)
|
|
||||||
return primeVueVisible || litegraphVisible
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForHidden(): Promise<void> {
|
|
||||||
const waitIfExists = async (locator: Locator, menuName: string) => {
|
|
||||||
const count = await locator.count()
|
|
||||||
if (count > 0) {
|
|
||||||
await locator.waitFor({ state: 'hidden' }).catch((error: Error) => {
|
|
||||||
console.warn(
|
|
||||||
`[waitForHidden] ${menuName} waitFor failed:`,
|
|
||||||
error.message
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
waitIfExists(this.primeVueMenu, 'primeVueMenu'),
|
|
||||||
waitIfExists(this.litegraphMenu, 'litegraphMenu')
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import type { Page } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
|
||||||
import type { ComfyPage } from '../ComfyPage'
|
import type { ComfyPage } from '../ComfyPage'
|
||||||
import { TestIds } from '../selectors'
|
|
||||||
import { BaseDialog } from './BaseDialog'
|
|
||||||
|
|
||||||
export class SettingDialog extends BaseDialog {
|
export class SettingDialog {
|
||||||
constructor(
|
constructor(
|
||||||
page: Page,
|
public readonly page: Page,
|
||||||
public readonly comfyPage: ComfyPage
|
public readonly comfyPage: ComfyPage
|
||||||
) {
|
) {}
|
||||||
super(page, TestIds.dialogs.settings)
|
|
||||||
|
get root() {
|
||||||
|
return this.page.locator('div.settings-container')
|
||||||
}
|
}
|
||||||
|
|
||||||
async open() {
|
async open() {
|
||||||
await this.comfyPage.command.executeCommand('Comfy.ShowSettingsDialog')
|
await this.comfyPage.executeCommand('Comfy.ShowSettingsDialog')
|
||||||
await this.waitForVisible()
|
await this.page.waitForSelector('div.settings-container')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,7 +23,9 @@ export class SettingDialog extends BaseDialog {
|
|||||||
* @param value - The value to set
|
* @param value - The value to set
|
||||||
*/
|
*/
|
||||||
async setStringSetting(id: string, value: string) {
|
async setStringSetting(id: string, value: string) {
|
||||||
const settingInputDiv = this.root.locator(`div[id="${id}"]`)
|
const settingInputDiv = this.page.locator(
|
||||||
|
`div.settings-container div[id="${id}"]`
|
||||||
|
)
|
||||||
await settingInputDiv.locator('input').fill(value)
|
await settingInputDiv.locator('input').fill(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,31 +34,15 @@ export class SettingDialog extends BaseDialog {
|
|||||||
* @param id - The id of the setting
|
* @param id - The id of the setting
|
||||||
*/
|
*/
|
||||||
async toggleBooleanSetting(id: string) {
|
async toggleBooleanSetting(id: string) {
|
||||||
const settingInputDiv = this.root.locator(`div[id="${id}"]`)
|
const settingInputDiv = this.page.locator(
|
||||||
|
`div.settings-container div[id="${id}"]`
|
||||||
|
)
|
||||||
await settingInputDiv.locator('input').click()
|
await settingInputDiv.locator('input').click()
|
||||||
}
|
}
|
||||||
|
|
||||||
get searchBox() {
|
|
||||||
return this.root.getByPlaceholder(/Search/)
|
|
||||||
}
|
|
||||||
|
|
||||||
get categories() {
|
|
||||||
return this.root.locator('nav').getByRole('button')
|
|
||||||
}
|
|
||||||
|
|
||||||
category(name: string) {
|
|
||||||
return this.root.locator('nav').getByRole('button', { name })
|
|
||||||
}
|
|
||||||
|
|
||||||
get contentArea() {
|
|
||||||
return this.root.getByRole('main')
|
|
||||||
}
|
|
||||||
|
|
||||||
async goToAboutPanel() {
|
async goToAboutPanel() {
|
||||||
const aboutButton = this.root.locator('nav').getByRole('button', {
|
const aboutButton = this.page.locator('li[aria-label="About"]')
|
||||||
name: 'About'
|
|
||||||
})
|
|
||||||
await aboutButton.click()
|
await aboutButton.click()
|
||||||
await this.page.waitForSelector('.about-container')
|
await this.page.waitForSelector('div.about-container')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
import type { WorkspaceStore } from '../../types/globals'
|
|
||||||
import { TestIds } from '../selectors'
|
|
||||||
|
|
||||||
class SidebarTab {
|
class SidebarTab {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly page: Page,
|
public readonly page: Page,
|
||||||
@@ -34,16 +31,16 @@ class SidebarTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class NodeLibrarySidebarTab extends SidebarTab {
|
export class NodeLibrarySidebarTab extends SidebarTab {
|
||||||
constructor(public override readonly page: Page) {
|
constructor(public readonly page: Page) {
|
||||||
super(page, 'node-library')
|
super(page, 'node-library')
|
||||||
}
|
}
|
||||||
|
|
||||||
get nodeLibrarySearchBoxInput() {
|
get nodeLibrarySearchBoxInput() {
|
||||||
return this.page.getByPlaceholder('Search Nodes...')
|
return this.page.locator('.node-lib-search-box input[type="text"]')
|
||||||
}
|
}
|
||||||
|
|
||||||
get nodeLibraryTree() {
|
get nodeLibraryTree() {
|
||||||
return this.page.getByTestId(TestIds.sidebar.nodeLibrary)
|
return this.page.locator('.node-lib-tree-explorer')
|
||||||
}
|
}
|
||||||
|
|
||||||
get nodePreview() {
|
get nodePreview() {
|
||||||
@@ -58,12 +55,12 @@ export class NodeLibrarySidebarTab extends SidebarTab {
|
|||||||
return this.tabContainer.locator('.new-folder-button')
|
return this.tabContainer.locator('.new-folder-button')
|
||||||
}
|
}
|
||||||
|
|
||||||
override async open() {
|
async open() {
|
||||||
await super.open()
|
await super.open()
|
||||||
await this.nodeLibraryTree.waitFor({ state: 'visible' })
|
await this.nodeLibraryTree.waitFor({ state: 'visible' })
|
||||||
}
|
}
|
||||||
|
|
||||||
override async close() {
|
async close() {
|
||||||
if (!this.tabButton.isVisible()) {
|
if (!this.tabButton.isVisible()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -72,40 +69,30 @@ export class NodeLibrarySidebarTab extends SidebarTab {
|
|||||||
await this.nodeLibraryTree.waitFor({ state: 'hidden' })
|
await this.nodeLibraryTree.waitFor({ state: 'hidden' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
folderSelector(folderName: string) {
|
||||||
|
return `.p-tree-node-content:has(> .tree-explorer-node-label:has(.tree-folder .node-label:has-text("${folderName}")))`
|
||||||
|
}
|
||||||
|
|
||||||
getFolder(folderName: string) {
|
getFolder(folderName: string) {
|
||||||
return this.page.locator(
|
return this.page.locator(this.folderSelector(folderName))
|
||||||
`[data-testid="node-tree-folder"][data-folder-name="${folderName}"]`
|
}
|
||||||
)
|
|
||||||
|
nodeSelector(nodeName: string) {
|
||||||
|
return `.p-tree-node-content:has(> .tree-explorer-node-label:has(.tree-leaf .node-label:has-text("${nodeName}")))`
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode(nodeName: string) {
|
getNode(nodeName: string) {
|
||||||
return this.page.locator(
|
return this.page.locator(this.nodeSelector(nodeName))
|
||||||
`[data-testid="node-tree-leaf"][data-node-name="${nodeName}"]`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeSelector(nodeName: string): string {
|
|
||||||
return `[data-testid="node-tree-leaf"][data-node-name="${nodeName}"]`
|
|
||||||
}
|
|
||||||
|
|
||||||
folderSelector(folderName: string): string {
|
|
||||||
return `[data-testid="node-tree-folder"][data-folder-name="${folderName}"]`
|
|
||||||
}
|
|
||||||
|
|
||||||
getNodeInFolder(nodeName: string, folderName: string) {
|
|
||||||
return this.getFolder(folderName)
|
|
||||||
.locator('xpath=ancestor::li')
|
|
||||||
.locator(`[data-testid="node-tree-leaf"][data-node-name="${nodeName}"]`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkflowsSidebarTab extends SidebarTab {
|
export class WorkflowsSidebarTab extends SidebarTab {
|
||||||
constructor(public override readonly page: Page) {
|
constructor(public readonly page: Page) {
|
||||||
super(page, 'workflows')
|
super(page, 'workflows')
|
||||||
}
|
}
|
||||||
|
|
||||||
get root() {
|
get root() {
|
||||||
return this.page.getByTestId(TestIds.sidebar.workflows)
|
return this.page.locator('.workflows-sidebar-tab')
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOpenedWorkflowNames() {
|
async getOpenedWorkflowNames() {
|
||||||
@@ -153,9 +140,7 @@ export class WorkflowsSidebarTab extends SidebarTab {
|
|||||||
|
|
||||||
// Wait for workflow service to finish renaming
|
// Wait for workflow service to finish renaming
|
||||||
await this.page.waitForFunction(
|
await this.page.waitForFunction(
|
||||||
() =>
|
() => !window['app']?.extensionManager?.workflow?.isBusy,
|
||||||
!(window.app?.extensionManager as WorkspaceStore | undefined)?.workflow
|
|
||||||
?.isBusy,
|
|
||||||
undefined,
|
undefined,
|
||||||
{ timeout: 3000 }
|
{ timeout: 3000 }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
import type { WorkspaceStore } from '../../types/globals'
|
|
||||||
|
|
||||||
export class Topbar {
|
export class Topbar {
|
||||||
private readonly menuLocator: Locator
|
private readonly menuLocator: Locator
|
||||||
@@ -58,7 +57,7 @@ export class Topbar {
|
|||||||
|
|
||||||
async closeWorkflowTab(tabName: string) {
|
async closeWorkflowTab(tabName: string) {
|
||||||
const tab = this.getWorkflowTab(tabName)
|
const tab = this.getWorkflowTab(tabName)
|
||||||
await tab.getByRole('button', { name: 'Close' }).click({ force: true })
|
await tab.locator('.close-button').click({ force: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaveDialog(): Locator {
|
getSaveDialog(): Locator {
|
||||||
@@ -87,7 +86,7 @@ export class Topbar {
|
|||||||
|
|
||||||
// Wait for workflow service to finish saving
|
// Wait for workflow service to finish saving
|
||||||
await this.page.waitForFunction(
|
await this.page.waitForFunction(
|
||||||
() => !(window.app!.extensionManager as WorkspaceStore).workflow.isBusy,
|
() => !window['app'].extensionManager.workflow.isBusy,
|
||||||
undefined,
|
undefined,
|
||||||
{ timeout: 3000 }
|
{ timeout: 3000 }
|
||||||
)
|
)
|
||||||
@@ -123,7 +122,7 @@ export class Topbar {
|
|||||||
*/
|
*/
|
||||||
async closeTopbarMenu() {
|
async closeTopbarMenu() {
|
||||||
await this.page.locator('body').click({ position: { x: 300, y: 10 } })
|
await this.page.locator('body').click({ position: { x: 300, y: 10 } })
|
||||||
await this.menuLocator.waitFor({ state: 'hidden' })
|
await expect(this.menuLocator).not.toBeVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import type { Position } from './types'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hardcoded positions for the default graph loaded in tests.
|
|
||||||
* These coordinates are specific to the default workflow viewport.
|
|
||||||
*/
|
|
||||||
export const DefaultGraphPositions = {
|
|
||||||
// Node click positions
|
|
||||||
textEncodeNode1: { x: 618, y: 191 },
|
|
||||||
textEncodeNode2: { x: 622, y: 400 },
|
|
||||||
textEncodeNodeToggler: { x: 430, y: 171 },
|
|
||||||
emptySpaceClick: { x: 35, y: 31 },
|
|
||||||
|
|
||||||
// Slot positions
|
|
||||||
clipTextEncodeNode1InputSlot: { x: 427, y: 198 },
|
|
||||||
clipTextEncodeNode2InputSlot: { x: 422, y: 402 },
|
|
||||||
clipTextEncodeNode2InputLinkPath: { x: 395, y: 422 },
|
|
||||||
loadCheckpointNodeClipOutputSlot: { x: 332, y: 509 },
|
|
||||||
emptySpace: { x: 427, y: 98 },
|
|
||||||
|
|
||||||
// Widget positions
|
|
||||||
emptyLatentWidgetClick: { x: 724, y: 645 },
|
|
||||||
|
|
||||||
// Node positions and sizes for resize operations
|
|
||||||
ksampler: {
|
|
||||||
pos: { x: 863, y: 156 },
|
|
||||||
size: { width: 315, height: 292 }
|
|
||||||
},
|
|
||||||
loadCheckpoint: {
|
|
||||||
pos: { x: 26, y: 444 },
|
|
||||||
size: { width: 315, height: 127 }
|
|
||||||
},
|
|
||||||
emptyLatent: {
|
|
||||||
pos: { x: 473, y: 579 },
|
|
||||||
size: { width: 315, height: 136 }
|
|
||||||
}
|
|
||||||
} as const satisfies {
|
|
||||||
textEncodeNode1: Position
|
|
||||||
textEncodeNode2: Position
|
|
||||||
textEncodeNodeToggler: Position
|
|
||||||
emptySpaceClick: Position
|
|
||||||
clipTextEncodeNode1InputSlot: Position
|
|
||||||
clipTextEncodeNode2InputSlot: Position
|
|
||||||
clipTextEncodeNode2InputLinkPath: Position
|
|
||||||
loadCheckpointNodeClipOutputSlot: Position
|
|
||||||
emptySpace: Position
|
|
||||||
emptyLatentWidgetClick: Position
|
|
||||||
ksampler: { pos: Position; size: { width: number; height: number } }
|
|
||||||
loadCheckpoint: { pos: Position; size: { width: number; height: number } }
|
|
||||||
emptyLatent: { pos: Position; size: { width: number; height: number } }
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export interface Position {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Size {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
|
||||||
|
|
||||||
import { DefaultGraphPositions } from '../constants/defaultGraphPositions'
|
|
||||||
import type { Position } from '../types'
|
|
||||||
|
|
||||||
export class CanvasHelper {
|
|
||||||
constructor(
|
|
||||||
private page: Page,
|
|
||||||
private canvas: Locator,
|
|
||||||
private resetViewButton: Locator
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private async nextFrame(): Promise<void> {
|
|
||||||
await this.page.evaluate(() => {
|
|
||||||
return new Promise<number>(requestAnimationFrame)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetView(): Promise<void> {
|
|
||||||
if (await this.resetViewButton.isVisible()) {
|
|
||||||
await this.resetViewButton.click()
|
|
||||||
}
|
|
||||||
await this.page.mouse.move(10, 10)
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async zoom(deltaY: number, steps: number = 1): Promise<void> {
|
|
||||||
await this.page.mouse.move(10, 10)
|
|
||||||
for (let i = 0; i < steps; i++) {
|
|
||||||
await this.page.mouse.wheel(0, deltaY)
|
|
||||||
}
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async pan(offset: Position, safeSpot?: Position): Promise<void> {
|
|
||||||
safeSpot = safeSpot || { x: 10, y: 10 }
|
|
||||||
await this.page.mouse.move(safeSpot.x, safeSpot.y)
|
|
||||||
await this.page.mouse.down()
|
|
||||||
await this.page.mouse.move(offset.x + safeSpot.x, offset.y + safeSpot.y)
|
|
||||||
await this.page.mouse.up()
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async panWithTouch(offset: Position, safeSpot?: Position): Promise<void> {
|
|
||||||
safeSpot = safeSpot || { x: 10, y: 10 }
|
|
||||||
const client = await this.page.context().newCDPSession(this.page)
|
|
||||||
await client.send('Input.dispatchTouchEvent', {
|
|
||||||
type: 'touchStart',
|
|
||||||
touchPoints: [safeSpot]
|
|
||||||
})
|
|
||||||
await client.send('Input.dispatchTouchEvent', {
|
|
||||||
type: 'touchMove',
|
|
||||||
touchPoints: [{ x: offset.x + safeSpot.x, y: offset.y + safeSpot.y }]
|
|
||||||
})
|
|
||||||
await client.send('Input.dispatchTouchEvent', {
|
|
||||||
type: 'touchEnd',
|
|
||||||
touchPoints: []
|
|
||||||
})
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async rightClick(x: number = 10, y: number = 10): Promise<void> {
|
|
||||||
await this.page.mouse.click(x, y, { button: 'right' })
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async doubleClick(): Promise<void> {
|
|
||||||
await this.page.mouse.dblclick(10, 10, { delay: 5 })
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async click(position: Position): Promise<void> {
|
|
||||||
await this.canvas.click({ position })
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async clickEmptySpace(): Promise<void> {
|
|
||||||
await this.canvas.click({ position: DefaultGraphPositions.emptySpaceClick })
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async dragAndDrop(source: Position, target: Position): Promise<void> {
|
|
||||||
await this.page.mouse.move(source.x, source.y)
|
|
||||||
await this.page.mouse.down()
|
|
||||||
await this.page.mouse.move(target.x, target.y, { steps: 100 })
|
|
||||||
await this.page.mouse.up()
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async moveMouseToEmptyArea(): Promise<void> {
|
|
||||||
await this.page.mouse.move(10, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getScale(): Promise<number> {
|
|
||||||
return this.page.evaluate(() => {
|
|
||||||
return window.app!.canvas.ds.scale
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async setScale(scale: number): Promise<void> {
|
|
||||||
await this.page.evaluate((s) => {
|
|
||||||
window.app!.canvas.ds.scale = s
|
|
||||||
}, scale)
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async convertOffsetToCanvas(
|
|
||||||
pos: [number, number]
|
|
||||||
): Promise<[number, number]> {
|
|
||||||
return this.page.evaluate((pos) => {
|
|
||||||
return window.app!.canvas.ds.convertOffsetToCanvas(pos)
|
|
||||||
}, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNodeCenterByTitle(title: string): Promise<Position | null> {
|
|
||||||
return this.page.evaluate((title) => {
|
|
||||||
const app = window.app!
|
|
||||||
const node = app.graph.nodes.find(
|
|
||||||
(n: { title: string }) => n.title === title
|
|
||||||
)
|
|
||||||
if (!node) return null
|
|
||||||
|
|
||||||
const centerX = node.pos[0] + node.size[0] / 2
|
|
||||||
const centerY = node.pos[1] + node.size[1] / 2
|
|
||||||
const [clientX, clientY] = app.canvasPosToClientPos([centerX, centerY])
|
|
||||||
return { x: clientX, y: clientY }
|
|
||||||
}, title)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupPosition(title: string): Promise<Position> {
|
|
||||||
const pos = await this.page.evaluate((title) => {
|
|
||||||
const groups = window.app!.graph.groups
|
|
||||||
const group = groups.find((g: { title: string }) => g.title === title)
|
|
||||||
if (!group) return null
|
|
||||||
return { x: group.pos[0], y: group.pos[1] }
|
|
||||||
}, title)
|
|
||||||
if (!pos) throw new Error(`Group "${title}" not found`)
|
|
||||||
return pos
|
|
||||||
}
|
|
||||||
|
|
||||||
async dragGroup(options: {
|
|
||||||
name: string
|
|
||||||
deltaX: number
|
|
||||||
deltaY: number
|
|
||||||
}): Promise<void> {
|
|
||||||
const { name, deltaX, deltaY } = options
|
|
||||||
const screenPos = await this.page.evaluate((title) => {
|
|
||||||
const app = window.app!
|
|
||||||
const groups = app.graph.groups
|
|
||||||
const group = groups.find((g: { title: string }) => g.title === title)
|
|
||||||
if (!group) return null
|
|
||||||
const clientPos = app.canvasPosToClientPos([
|
|
||||||
group.pos[0] + 50,
|
|
||||||
group.pos[1] + 15
|
|
||||||
])
|
|
||||||
return { x: clientPos[0], y: clientPos[1] }
|
|
||||||
}, name)
|
|
||||||
if (!screenPos) throw new Error(`Group "${name}" not found`)
|
|
||||||
|
|
||||||
await this.dragAndDrop(screenPos, {
|
|
||||||
x: screenPos.x + deltaX,
|
|
||||||
y: screenPos.y + deltaY
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async disconnectEdge(): Promise<void> {
|
|
||||||
await this.dragAndDrop(
|
|
||||||
DefaultGraphPositions.clipTextEncodeNode1InputSlot,
|
|
||||||
DefaultGraphPositions.emptySpace
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectEdge(options: { reverse?: boolean } = {}): Promise<void> {
|
|
||||||
const { reverse = false } = options
|
|
||||||
const start = reverse
|
|
||||||
? DefaultGraphPositions.clipTextEncodeNode1InputSlot
|
|
||||||
: DefaultGraphPositions.loadCheckpointNodeClipOutputSlot
|
|
||||||
const end = reverse
|
|
||||||
? DefaultGraphPositions.loadCheckpointNodeClipOutputSlot
|
|
||||||
: DefaultGraphPositions.clipTextEncodeNode1InputSlot
|
|
||||||
|
|
||||||
await this.dragAndDrop(start, end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import type { Locator } from '@playwright/test'
|
|
||||||
|
|
||||||
import type { KeyboardHelper } from './KeyboardHelper'
|
|
||||||
|
|
||||||
export class ClipboardHelper {
|
|
||||||
constructor(private readonly keyboard: KeyboardHelper) {}
|
|
||||||
|
|
||||||
async copy(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.keyboard.ctrlSend('KeyC', locator ?? null)
|
|
||||||
}
|
|
||||||
|
|
||||||
async paste(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.keyboard.ctrlSend('KeyV', locator ?? null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import type { Page } from '@playwright/test'
|
|
||||||
|
|
||||||
import type { KeyCombo } from '../../../src/platform/keybindings/types'
|
|
||||||
|
|
||||||
export class CommandHelper {
|
|
||||||
constructor(private readonly page: Page) {}
|
|
||||||
|
|
||||||
async executeCommand(
|
|
||||||
commandId: string,
|
|
||||||
metadata?: Record<string, unknown>
|
|
||||||
): Promise<void> {
|
|
||||||
await this.page.evaluate(
|
|
||||||
({ commandId, metadata }) => {
|
|
||||||
const app = window.app
|
|
||||||
if (!app) throw new Error('window.app is not available')
|
|
||||||
|
|
||||||
return app.extensionManager.command.execute(commandId, {
|
|
||||||
metadata
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ commandId, metadata }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerCommand(
|
|
||||||
commandId: string,
|
|
||||||
command: (() => void) | (() => Promise<void>)
|
|
||||||
): Promise<void> {
|
|
||||||
// SECURITY: eval() is intentionally used here to deserialize/execute functions
|
|
||||||
// passed from controlled test code across the Node/Playwright browser boundary.
|
|
||||||
// Execution happens in isolated Playwright browser contexts with test-only data.
|
|
||||||
// This pattern is unsafe for production and must not be copied elsewhere.
|
|
||||||
await this.page.evaluate(
|
|
||||||
({ commandId, commandStr }) => {
|
|
||||||
const app = window.app!
|
|
||||||
const randomSuffix = Math.random().toString(36).substring(2, 8)
|
|
||||||
const extensionName = `TestExtension_${randomSuffix}`
|
|
||||||
|
|
||||||
app.registerExtension({
|
|
||||||
name: extensionName,
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
id: commandId,
|
|
||||||
function: eval(commandStr)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ commandId, commandStr: command.toString() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerKeybinding(
|
|
||||||
keyCombo: KeyCombo,
|
|
||||||
command: () => void
|
|
||||||
): Promise<void> {
|
|
||||||
// SECURITY: eval() is intentionally used here to deserialize/execute functions
|
|
||||||
// passed from controlled test code across the Node/Playwright browser boundary.
|
|
||||||
// Execution happens in isolated Playwright browser contexts with test-only data.
|
|
||||||
// This pattern is unsafe for production and must not be copied elsewhere.
|
|
||||||
await this.page.evaluate(
|
|
||||||
({ keyCombo, commandStr }) => {
|
|
||||||
const app = window.app!
|
|
||||||
const randomSuffix = Math.random().toString(36).substring(2, 8)
|
|
||||||
const extensionName = `TestExtension_${randomSuffix}`
|
|
||||||
const commandId = `TestCommand_${randomSuffix}`
|
|
||||||
|
|
||||||
app.registerExtension({
|
|
||||||
name: extensionName,
|
|
||||||
keybindings: [
|
|
||||||
{
|
|
||||||
combo: keyCombo,
|
|
||||||
commandId: commandId
|
|
||||||
}
|
|
||||||
],
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
id: commandId,
|
|
||||||
function: eval(commandStr)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ keyCombo, commandStr: command.toString() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import { readFileSync } from 'fs'
|
|
||||||
|
|
||||||
import type { Page } from '@playwright/test'
|
|
||||||
|
|
||||||
import type { Position } from '../types'
|
|
||||||
|
|
||||||
export class DragDropHelper {
|
|
||||||
constructor(
|
|
||||||
private readonly page: Page,
|
|
||||||
private readonly assetPath: (fileName: string) => string
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private async nextFrame(): Promise<void> {
|
|
||||||
await this.page.evaluate(() => {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
requestAnimationFrame(() => resolve())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async dragAndDropExternalResource(
|
|
||||||
options: {
|
|
||||||
fileName?: string
|
|
||||||
url?: string
|
|
||||||
dropPosition?: Position
|
|
||||||
waitForUpload?: boolean
|
|
||||||
} = {}
|
|
||||||
): Promise<void> {
|
|
||||||
const {
|
|
||||||
dropPosition = { x: 100, y: 100 },
|
|
||||||
fileName,
|
|
||||||
url,
|
|
||||||
waitForUpload = false
|
|
||||||
} = options
|
|
||||||
|
|
||||||
if (!fileName && !url)
|
|
||||||
throw new Error('Must provide either fileName or url')
|
|
||||||
|
|
||||||
const evaluateParams: {
|
|
||||||
dropPosition: Position
|
|
||||||
fileName?: string
|
|
||||||
fileType?: string
|
|
||||||
buffer?: Uint8Array | number[]
|
|
||||||
url?: string
|
|
||||||
} = { dropPosition }
|
|
||||||
|
|
||||||
if (fileName) {
|
|
||||||
const filePath = this.assetPath(fileName)
|
|
||||||
const buffer = readFileSync(filePath)
|
|
||||||
|
|
||||||
const getFileType = (fileName: string) => {
|
|
||||||
if (fileName.endsWith('.png')) return 'image/png'
|
|
||||||
if (fileName.endsWith('.svg')) return 'image/svg+xml'
|
|
||||||
if (fileName.endsWith('.webp')) return 'image/webp'
|
|
||||||
if (fileName.endsWith('.webm')) return 'video/webm'
|
|
||||||
if (fileName.endsWith('.json')) return 'application/json'
|
|
||||||
if (fileName.endsWith('.glb')) return 'model/gltf-binary'
|
|
||||||
if (fileName.endsWith('.avif')) return 'image/avif'
|
|
||||||
return 'application/octet-stream'
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluateParams.fileName = fileName
|
|
||||||
evaluateParams.fileType = getFileType(fileName)
|
|
||||||
evaluateParams.buffer = [...new Uint8Array(buffer)]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url) evaluateParams.url = url
|
|
||||||
|
|
||||||
const uploadResponsePromise = waitForUpload
|
|
||||||
? this.page.waitForResponse(
|
|
||||||
(resp) => resp.url().includes('/upload/') && resp.status() === 200,
|
|
||||||
{ timeout: 10000 }
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
|
|
||||||
await this.page.evaluate(async (params) => {
|
|
||||||
const dataTransfer = new DataTransfer()
|
|
||||||
|
|
||||||
if (params.buffer && params.fileName && params.fileType) {
|
|
||||||
const file = new File(
|
|
||||||
[new Uint8Array(params.buffer)],
|
|
||||||
params.fileName,
|
|
||||||
{
|
|
||||||
type: params.fileType
|
|
||||||
}
|
|
||||||
)
|
|
||||||
dataTransfer.items.add(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.url) {
|
|
||||||
dataTransfer.setData('text/uri-list', params.url)
|
|
||||||
dataTransfer.setData('text/x-moz-url', params.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetElement = document.elementFromPoint(
|
|
||||||
params.dropPosition.x,
|
|
||||||
params.dropPosition.y
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!targetElement) {
|
|
||||||
throw new Error(
|
|
||||||
`No element found at drop position: (${params.dropPosition.x}, ${params.dropPosition.y}). ` +
|
|
||||||
`document.elementFromPoint returned null. Ensure the target is visible and not obscured.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventOptions = {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
dataTransfer,
|
|
||||||
clientX: params.dropPosition.x,
|
|
||||||
clientY: params.dropPosition.y
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragOverEvent = new DragEvent('dragover', eventOptions)
|
|
||||||
const dropEvent = new DragEvent('drop', eventOptions)
|
|
||||||
|
|
||||||
const graphCanvasElement = document.querySelector('#graph-canvas')
|
|
||||||
|
|
||||||
// Keep Litegraph's drag-over node tracking in sync when the drop target is a
|
|
||||||
// Vue node DOM overlay outside of the graph canvas element.
|
|
||||||
if (graphCanvasElement && !graphCanvasElement.contains(targetElement)) {
|
|
||||||
graphCanvasElement.dispatchEvent(
|
|
||||||
new DragEvent('dragover', eventOptions)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(dropEvent, 'preventDefault', {
|
|
||||||
value: () => {},
|
|
||||||
writable: false
|
|
||||||
})
|
|
||||||
|
|
||||||
Object.defineProperty(dropEvent, 'stopPropagation', {
|
|
||||||
value: () => {},
|
|
||||||
writable: false
|
|
||||||
})
|
|
||||||
|
|
||||||
targetElement.dispatchEvent(dragOverEvent)
|
|
||||||
targetElement.dispatchEvent(dropEvent)
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
targetInfo: {
|
|
||||||
tagName: targetElement.tagName,
|
|
||||||
id: targetElement.id,
|
|
||||||
classList: Array.from(targetElement.classList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, evaluateParams)
|
|
||||||
|
|
||||||
if (uploadResponsePromise) {
|
|
||||||
await uploadResponsePromise
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async dragAndDropFile(
|
|
||||||
fileName: string,
|
|
||||||
options: { dropPosition?: Position; waitForUpload?: boolean } = {}
|
|
||||||
): Promise<void> {
|
|
||||||
return this.dragAndDropExternalResource({ fileName, ...options })
|
|
||||||
}
|
|
||||||
|
|
||||||
async dragAndDropURL(
|
|
||||||
url: string,
|
|
||||||
options: { dropPosition?: Position } = {}
|
|
||||||
): Promise<void> {
|
|
||||||
return this.dragAndDropExternalResource({ url, ...options })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import type { Locator, Page } from '@playwright/test'
|
|
||||||
|
|
||||||
export class KeyboardHelper {
|
|
||||||
constructor(
|
|
||||||
private readonly page: Page,
|
|
||||||
private readonly canvas: Locator
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private async nextFrame(): Promise<void> {
|
|
||||||
await this.page.evaluate(() => new Promise<number>(requestAnimationFrame))
|
|
||||||
}
|
|
||||||
|
|
||||||
async ctrlSend(
|
|
||||||
keyToPress: string,
|
|
||||||
locator: Locator | null = this.canvas
|
|
||||||
): Promise<void> {
|
|
||||||
const target = locator ?? this.page.keyboard
|
|
||||||
await target.press(`Control+${keyToPress}`)
|
|
||||||
await this.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectAll(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.ctrlSend('KeyA', locator)
|
|
||||||
}
|
|
||||||
|
|
||||||
async bypass(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.ctrlSend('KeyB', locator)
|
|
||||||
}
|
|
||||||
|
|
||||||
async undo(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.ctrlSend('KeyZ', locator)
|
|
||||||
}
|
|
||||||
|
|
||||||
async redo(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.ctrlSend('KeyY', locator)
|
|
||||||
}
|
|
||||||
|
|
||||||
async moveUp(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.ctrlSend('ArrowUp', locator)
|
|
||||||
}
|
|
||||||
|
|
||||||
async moveDown(locator?: Locator | null): Promise<void> {
|
|
||||||
await this.ctrlSend('ArrowDown', locator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
import type { Locator } from '@playwright/test'
|
|
||||||
|
|
||||||
import type {
|
|
||||||
LGraph,
|
|
||||||
LGraphNode
|
|
||||||
} from '../../../src/lib/litegraph/src/litegraph'
|
|
||||||
import type { NodeId } from '../../../src/platform/workflow/validation/schemas/workflowSchema'
|
|
||||||
import type { ComfyPage } from '../ComfyPage'
|
|
||||||
import { DefaultGraphPositions } from '../constants/defaultGraphPositions'
|
|
||||||
import type { Position, Size } from '../types'
|
|
||||||
import { NodeReference } from '../utils/litegraphUtils'
|
|
||||||
|
|
||||||
export class NodeOperationsHelper {
|
|
||||||
constructor(private comfyPage: ComfyPage) {}
|
|
||||||
|
|
||||||
private get page() {
|
|
||||||
return this.comfyPage.page
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGraphNodesCount(): Promise<number> {
|
|
||||||
return await this.page.evaluate(() => {
|
|
||||||
return window.app?.graph?.nodes?.length || 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSelectedGraphNodesCount(): Promise<number> {
|
|
||||||
return await this.page.evaluate(() => {
|
|
||||||
return (
|
|
||||||
window.app?.graph?.nodes?.filter(
|
|
||||||
(node: LGraphNode) => node.is_selected === true
|
|
||||||
).length || 0
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNodeCount(): Promise<number> {
|
|
||||||
return await this.page.evaluate(() => window.app!.graph.nodes.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNodes(): Promise<LGraphNode[]> {
|
|
||||||
return await this.page.evaluate(() => {
|
|
||||||
return window.app!.graph.nodes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForGraphNodes(count: number): Promise<void> {
|
|
||||||
await this.page.waitForFunction((count) => {
|
|
||||||
return window.app?.canvas.graph?.nodes?.length === count
|
|
||||||
}, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFirstNodeRef(): Promise<NodeReference | null> {
|
|
||||||
const id = await this.page.evaluate(() => {
|
|
||||||
return window.app!.graph.nodes[0]?.id
|
|
||||||
})
|
|
||||||
if (!id) return null
|
|
||||||
return this.getNodeRefById(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNodeRefById(id: NodeId): Promise<NodeReference> {
|
|
||||||
return new NodeReference(id, this.comfyPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNodeRefsByType(
|
|
||||||
type: string,
|
|
||||||
includeSubgraph: boolean = false
|
|
||||||
): Promise<NodeReference[]> {
|
|
||||||
return Promise.all(
|
|
||||||
(
|
|
||||||
await this.page.evaluate(
|
|
||||||
({ type, includeSubgraph }) => {
|
|
||||||
const graph = (
|
|
||||||
includeSubgraph ? window.app!.canvas.graph : window.app!.graph
|
|
||||||
) as LGraph
|
|
||||||
const nodes = graph.nodes
|
|
||||||
return nodes
|
|
||||||
.filter((n: LGraphNode) => n.type === type)
|
|
||||||
.map((n: LGraphNode) => n.id)
|
|
||||||
},
|
|
||||||
{ type, includeSubgraph }
|
|
||||||
)
|
|
||||||
).map((id: NodeId) => this.getNodeRefById(id))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNodeRefsByTitle(title: string): Promise<NodeReference[]> {
|
|
||||||
return Promise.all(
|
|
||||||
(
|
|
||||||
await this.page.evaluate((title) => {
|
|
||||||
return window
|
|
||||||
.app!.graph.nodes.filter((n: LGraphNode) => n.title === title)
|
|
||||||
.map((n: LGraphNode) => n.id)
|
|
||||||
}, title)
|
|
||||||
).map((id: NodeId) => this.getNodeRefById(id))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectNodes(nodeTitles: string[]): Promise<void> {
|
|
||||||
await this.page.keyboard.down('Control')
|
|
||||||
try {
|
|
||||||
for (const nodeTitle of nodeTitles) {
|
|
||||||
const nodes = await this.getNodeRefsByTitle(nodeTitle)
|
|
||||||
for (const node of nodes) {
|
|
||||||
await node.click('title')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await this.page.keyboard.up('Control')
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async resizeNode(
|
|
||||||
nodePos: Position,
|
|
||||||
nodeSize: Size,
|
|
||||||
ratioX: number,
|
|
||||||
ratioY: number,
|
|
||||||
revertAfter: boolean = false
|
|
||||||
): Promise<void> {
|
|
||||||
const bottomRight = {
|
|
||||||
x: nodePos.x + nodeSize.width,
|
|
||||||
y: nodePos.y + nodeSize.height
|
|
||||||
}
|
|
||||||
const target = {
|
|
||||||
x: nodePos.x + nodeSize.width * ratioX,
|
|
||||||
y: nodePos.y + nodeSize.height * ratioY
|
|
||||||
}
|
|
||||||
// -1 to be inside the node. -2 because nodes currently get an arbitrary +1 to width.
|
|
||||||
await this.comfyPage.canvasOps.dragAndDrop(
|
|
||||||
{ x: bottomRight.x - 2, y: bottomRight.y - 1 },
|
|
||||||
target
|
|
||||||
)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
if (revertAfter) {
|
|
||||||
await this.comfyPage.canvasOps.dragAndDrop(
|
|
||||||
{ x: target.x - 2, y: target.y - 1 },
|
|
||||||
bottomRight
|
|
||||||
)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async convertAllNodesToGroupNode(groupNodeName: string): Promise<void> {
|
|
||||||
await this.comfyPage.canvas.press('Control+a')
|
|
||||||
const node = await this.getFirstNodeRef()
|
|
||||||
if (!node) {
|
|
||||||
throw new Error('No nodes found to convert')
|
|
||||||
}
|
|
||||||
await node.clickContextMenuOption('Convert to Group Node')
|
|
||||||
await this.fillPromptDialog(groupNodeName)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
get promptDialogInput(): Locator {
|
|
||||||
return this.page.locator('.p-dialog-content input[type="text"]')
|
|
||||||
}
|
|
||||||
|
|
||||||
async fillPromptDialog(value: string): Promise<void> {
|
|
||||||
await this.promptDialogInput.fill(value)
|
|
||||||
await this.page.keyboard.press('Enter')
|
|
||||||
await this.promptDialogInput.waitFor({ state: 'hidden' })
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async dragTextEncodeNode2(): Promise<void> {
|
|
||||||
await this.comfyPage.canvasOps.dragAndDrop(
|
|
||||||
DefaultGraphPositions.textEncodeNode2,
|
|
||||||
{
|
|
||||||
x: DefaultGraphPositions.textEncodeNode2.x,
|
|
||||||
y: 300
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
async adjustEmptyLatentWidth(): Promise<void> {
|
|
||||||
await this.page.locator('#graph-canvas').click({
|
|
||||||
position: DefaultGraphPositions.emptyLatentWidgetClick
|
|
||||||
})
|
|
||||||
const dialogInput = this.page.locator('.graphdialog input[type="text"]')
|
|
||||||
await dialogInput.click()
|
|
||||||
await dialogInput.fill('128')
|
|
||||||
await dialogInput.press('Enter')
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import type { CDPSession, Page } from '@playwright/test'
|
|
||||||
|
|
||||||
interface PerfSnapshot {
|
|
||||||
RecalcStyleCount: number
|
|
||||||
RecalcStyleDuration: number
|
|
||||||
LayoutCount: number
|
|
||||||
LayoutDuration: number
|
|
||||||
TaskDuration: number
|
|
||||||
JSHeapUsedSize: number
|
|
||||||
Timestamp: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PerfMeasurement {
|
|
||||||
name: string
|
|
||||||
durationMs: number
|
|
||||||
styleRecalcs: number
|
|
||||||
styleRecalcDurationMs: number
|
|
||||||
layouts: number
|
|
||||||
layoutDurationMs: number
|
|
||||||
taskDurationMs: number
|
|
||||||
heapDeltaBytes: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PerformanceHelper {
|
|
||||||
private cdp: CDPSession | null = null
|
|
||||||
private snapshot: PerfSnapshot | null = null
|
|
||||||
|
|
||||||
constructor(private readonly page: Page) {}
|
|
||||||
|
|
||||||
async init(): Promise<void> {
|
|
||||||
this.cdp = await this.page.context().newCDPSession(this.page)
|
|
||||||
await this.cdp.send('Performance.enable')
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispose(): Promise<void> {
|
|
||||||
this.snapshot = null
|
|
||||||
if (this.cdp) {
|
|
||||||
try {
|
|
||||||
await this.cdp.send('Performance.disable')
|
|
||||||
} finally {
|
|
||||||
await this.cdp.detach()
|
|
||||||
this.cdp = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getSnapshot(): Promise<PerfSnapshot> {
|
|
||||||
if (!this.cdp) throw new Error('PerformanceHelper not initialized')
|
|
||||||
const { metrics } = (await this.cdp.send('Performance.getMetrics')) as {
|
|
||||||
metrics: { name: string; value: number }[]
|
|
||||||
}
|
|
||||||
function get(name: string): number {
|
|
||||||
return metrics.find((m) => m.name === name)?.value ?? 0
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
RecalcStyleCount: get('RecalcStyleCount'),
|
|
||||||
RecalcStyleDuration: get('RecalcStyleDuration'),
|
|
||||||
LayoutCount: get('LayoutCount'),
|
|
||||||
LayoutDuration: get('LayoutDuration'),
|
|
||||||
TaskDuration: get('TaskDuration'),
|
|
||||||
JSHeapUsedSize: get('JSHeapUsedSize'),
|
|
||||||
Timestamp: get('Timestamp')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async startMeasuring(): Promise<void> {
|
|
||||||
if (this.snapshot) {
|
|
||||||
throw new Error(
|
|
||||||
'Measurement already in progress — call stopMeasuring() first'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.snapshot = await this.getSnapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
async stopMeasuring(name: string): Promise<PerfMeasurement> {
|
|
||||||
if (!this.snapshot) throw new Error('Call startMeasuring() first')
|
|
||||||
const after = await this.getSnapshot()
|
|
||||||
const before = this.snapshot
|
|
||||||
this.snapshot = null
|
|
||||||
|
|
||||||
function delta(key: keyof PerfSnapshot): number {
|
|
||||||
return after[key] - before[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
durationMs: delta('Timestamp') * 1000,
|
|
||||||
styleRecalcs: delta('RecalcStyleCount'),
|
|
||||||
styleRecalcDurationMs: delta('RecalcStyleDuration') * 1000,
|
|
||||||
layouts: delta('LayoutCount'),
|
|
||||||
layoutDurationMs: delta('LayoutDuration') * 1000,
|
|
||||||
taskDurationMs: delta('TaskDuration') * 1000,
|
|
||||||
heapDeltaBytes: delta('JSHeapUsedSize')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import type { Page } from '@playwright/test'
|
|
||||||
|
|
||||||
export class SettingsHelper {
|
|
||||||
constructor(private readonly page: Page) {}
|
|
||||||
|
|
||||||
async setSetting(settingId: string, settingValue: unknown): Promise<void> {
|
|
||||||
await this.page.evaluate(
|
|
||||||
async ({ id, value }) => {
|
|
||||||
await window.app!.extensionManager.setting.set(id, value)
|
|
||||||
},
|
|
||||||
{ id: settingId, value: settingValue }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSetting<T = unknown>(settingId: string): Promise<T> {
|
|
||||||
return (await this.page.evaluate(async (id) => {
|
|
||||||
return await window.app!.extensionManager.setting.get(id)
|
|
||||||
}, settingId)) as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
import type { Page } from '@playwright/test'
|
|
||||||
|
|
||||||
import type {
|
|
||||||
CanvasPointerEvent,
|
|
||||||
Subgraph
|
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
|
||||||
|
|
||||||
import type { ComfyPage } from '../ComfyPage'
|
|
||||||
import type { NodeReference } from '../utils/litegraphUtils'
|
|
||||||
import { SubgraphSlotReference } from '../utils/litegraphUtils'
|
|
||||||
|
|
||||||
export class SubgraphHelper {
|
|
||||||
constructor(private readonly comfyPage: ComfyPage) {}
|
|
||||||
|
|
||||||
private get page(): Page {
|
|
||||||
return this.comfyPage.page
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Core helper method for interacting with subgraph I/O slots.
|
|
||||||
* Handles both input/output slots and both right-click/double-click actions.
|
|
||||||
*
|
|
||||||
* @param slotType - 'input' or 'output'
|
|
||||||
* @param action - 'rightClick' or 'doubleClick'
|
|
||||||
* @param slotName - Optional specific slot name to target
|
|
||||||
*/
|
|
||||||
private async interactWithSubgraphSlot(
|
|
||||||
slotType: 'input' | 'output',
|
|
||||||
action: 'rightClick' | 'doubleClick',
|
|
||||||
slotName?: string
|
|
||||||
): Promise<void> {
|
|
||||||
const foundSlot = await this.page.evaluate(
|
|
||||||
async (params) => {
|
|
||||||
const { slotType, action, targetSlotName } = params
|
|
||||||
const app = window.app!
|
|
||||||
const currentGraph = app.canvas!.graph!
|
|
||||||
|
|
||||||
// Check if we're in a subgraph
|
|
||||||
if (!('inputNode' in currentGraph)) {
|
|
||||||
throw new Error(
|
|
||||||
'Not in a subgraph - this method only works inside subgraphs'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const subgraph = currentGraph as Subgraph
|
|
||||||
|
|
||||||
// Get the appropriate node and slots
|
|
||||||
const node =
|
|
||||||
slotType === 'input' ? subgraph.inputNode : subgraph.outputNode
|
|
||||||
const slots = slotType === 'input' ? subgraph.inputs : subgraph.outputs
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
throw new Error(`No ${slotType} node found in subgraph`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!slots || slots.length === 0) {
|
|
||||||
throw new Error(`No ${slotType} slots found in subgraph`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter slots based on target name and action type
|
|
||||||
const slotsToTry = targetSlotName
|
|
||||||
? slots.filter((slot) => slot.name === targetSlotName)
|
|
||||||
: action === 'rightClick'
|
|
||||||
? slots
|
|
||||||
: [slots[0]] // Right-click tries all, double-click uses first
|
|
||||||
|
|
||||||
if (slotsToTry.length === 0) {
|
|
||||||
throw new Error(
|
|
||||||
targetSlotName
|
|
||||||
? `${slotType} slot '${targetSlotName}' not found`
|
|
||||||
: `No ${slotType} slots available to try`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the interaction based on action type
|
|
||||||
if (action === 'rightClick') {
|
|
||||||
// Right-click: try each slot until one works
|
|
||||||
for (const slot of slotsToTry) {
|
|
||||||
if (!slot.pos) continue
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
canvasX: slot.pos[0],
|
|
||||||
canvasY: slot.pos[1],
|
|
||||||
button: 2, // Right mouse button
|
|
||||||
preventDefault: () => {},
|
|
||||||
stopPropagation: () => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.onPointerDown) {
|
|
||||||
node.onPointerDown(
|
|
||||||
event as Partial<CanvasPointerEvent> as CanvasPointerEvent,
|
|
||||||
app.canvas.pointer,
|
|
||||||
app.canvas.linkConnector
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
slotName: slot.name,
|
|
||||||
x: slot.pos[0],
|
|
||||||
y: slot.pos[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (action === 'doubleClick') {
|
|
||||||
// Double-click: use first slot with bounding rect center
|
|
||||||
const slot = slotsToTry[0]
|
|
||||||
if (!slot.boundingRect) {
|
|
||||||
throw new Error(`${slotType} slot bounding rect not found`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = slot.boundingRect
|
|
||||||
const testX = rect[0] + rect[2] / 2 // x + width/2
|
|
||||||
const testY = rect[1] + rect[3] / 2 // y + height/2
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
canvasX: testX,
|
|
||||||
canvasY: testY,
|
|
||||||
button: 0, // Left mouse button
|
|
||||||
preventDefault: () => {},
|
|
||||||
stopPropagation: () => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.onPointerDown) {
|
|
||||||
node.onPointerDown(
|
|
||||||
event as Partial<CanvasPointerEvent> as CanvasPointerEvent,
|
|
||||||
app.canvas.pointer,
|
|
||||||
app.canvas.linkConnector
|
|
||||||
)
|
|
||||||
|
|
||||||
// Trigger double-click
|
|
||||||
if (app.canvas.pointer.onDoubleClick) {
|
|
||||||
app.canvas.pointer.onDoubleClick(
|
|
||||||
event as Partial<CanvasPointerEvent> as CanvasPointerEvent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: true, slotName: slot.name, x: testX, y: testY }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: false }
|
|
||||||
},
|
|
||||||
{ slotType, action, targetSlotName: slotName }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!foundSlot.success) {
|
|
||||||
const actionText =
|
|
||||||
action === 'rightClick' ? 'open context menu for' : 'double-click'
|
|
||||||
throw new Error(
|
|
||||||
slotName
|
|
||||||
? `Could not ${actionText} ${slotType} slot '${slotName}'`
|
|
||||||
: `Could not find any ${slotType} slot to ${actionText}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the appropriate UI element to appear
|
|
||||||
if (action === 'rightClick') {
|
|
||||||
await this.page.waitForSelector('.litemenu-entry', {
|
|
||||||
state: 'visible',
|
|
||||||
timeout: 5000
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Right-clicks on a subgraph input slot to open the context menu.
|
|
||||||
* Must be called when inside a subgraph.
|
|
||||||
*
|
|
||||||
* This method uses the actual slot positions from the subgraph.inputs array,
|
|
||||||
* which contain the correct coordinates for each input slot. These positions
|
|
||||||
* are different from the visual node positions and are specifically where
|
|
||||||
* the slots are rendered on the input node.
|
|
||||||
*
|
|
||||||
* @param inputName Optional name of the specific input slot to target (e.g., 'text').
|
|
||||||
* If not provided, tries all available input slots until one works.
|
|
||||||
* @returns Promise that resolves when the context menu appears
|
|
||||||
*/
|
|
||||||
async rightClickInputSlot(inputName?: string): Promise<void> {
|
|
||||||
return this.interactWithSubgraphSlot('input', 'rightClick', inputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Right-clicks on a subgraph output slot to open the context menu.
|
|
||||||
* Must be called when inside a subgraph.
|
|
||||||
*
|
|
||||||
* Similar to rightClickInputSlot but for output slots.
|
|
||||||
*
|
|
||||||
* @param outputName Optional name of the specific output slot to target.
|
|
||||||
* If not provided, tries all available output slots until one works.
|
|
||||||
* @returns Promise that resolves when the context menu appears
|
|
||||||
*/
|
|
||||||
async rightClickOutputSlot(outputName?: string): Promise<void> {
|
|
||||||
return this.interactWithSubgraphSlot('output', 'rightClick', outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Double-clicks on a subgraph input slot to rename it.
|
|
||||||
* Must be called when inside a subgraph.
|
|
||||||
*
|
|
||||||
* @param inputName Optional name of the specific input slot to target (e.g., 'text').
|
|
||||||
* If not provided, tries the first available input slot.
|
|
||||||
* @returns Promise that resolves when the rename dialog appears
|
|
||||||
*/
|
|
||||||
async doubleClickInputSlot(inputName?: string): Promise<void> {
|
|
||||||
return this.interactWithSubgraphSlot('input', 'doubleClick', inputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Double-clicks on a subgraph output slot to rename it.
|
|
||||||
* Must be called when inside a subgraph.
|
|
||||||
*
|
|
||||||
* @param outputName Optional name of the specific output slot to target.
|
|
||||||
* If not provided, tries the first available output slot.
|
|
||||||
* @returns Promise that resolves when the rename dialog appears
|
|
||||||
*/
|
|
||||||
async doubleClickOutputSlot(outputName?: string): Promise<void> {
|
|
||||||
return this.interactWithSubgraphSlot('output', 'doubleClick', outputName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a reference to a subgraph input slot
|
|
||||||
*/
|
|
||||||
getInputSlot(slotName?: string): SubgraphSlotReference {
|
|
||||||
return new SubgraphSlotReference('input', slotName || '', this.comfyPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a reference to a subgraph output slot
|
|
||||||
*/
|
|
||||||
getOutputSlot(slotName?: string): SubgraphSlotReference {
|
|
||||||
return new SubgraphSlotReference('output', slotName || '', this.comfyPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect a regular node output to a subgraph input.
|
|
||||||
* This creates a new input slot on the subgraph if targetInputName is not provided.
|
|
||||||
*/
|
|
||||||
async connectToInput(
|
|
||||||
sourceNode: NodeReference,
|
|
||||||
sourceSlotIndex: number,
|
|
||||||
targetInputName?: string
|
|
||||||
): Promise<void> {
|
|
||||||
const sourceSlot = await sourceNode.getOutput(sourceSlotIndex)
|
|
||||||
const targetSlot = this.getInputSlot(targetInputName)
|
|
||||||
|
|
||||||
const targetPosition = targetInputName
|
|
||||||
? await targetSlot.getPosition() // Connect to existing slot
|
|
||||||
: await targetSlot.getOpenSlotPosition() // Create new slot
|
|
||||||
|
|
||||||
await this.comfyPage.canvasOps.dragAndDrop(
|
|
||||||
await sourceSlot.getPosition(),
|
|
||||||
targetPosition
|
|
||||||
)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect a subgraph input to a regular node input.
|
|
||||||
* This creates a new input slot on the subgraph if sourceInputName is not provided.
|
|
||||||
*/
|
|
||||||
async connectFromInput(
|
|
||||||
targetNode: NodeReference,
|
|
||||||
targetSlotIndex: number,
|
|
||||||
sourceInputName?: string
|
|
||||||
): Promise<void> {
|
|
||||||
const sourceSlot = this.getInputSlot(sourceInputName)
|
|
||||||
const targetSlot = await targetNode.getInput(targetSlotIndex)
|
|
||||||
|
|
||||||
const sourcePosition = sourceInputName
|
|
||||||
? await sourceSlot.getPosition() // Connect from existing slot
|
|
||||||
: await sourceSlot.getOpenSlotPosition() // Create new slot
|
|
||||||
|
|
||||||
const targetPosition = await targetSlot.getPosition()
|
|
||||||
|
|
||||||
await this.comfyPage.canvasOps.dragAndDrop(sourcePosition, targetPosition)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect a regular node output to a subgraph output.
|
|
||||||
* This creates a new output slot on the subgraph if targetOutputName is not provided.
|
|
||||||
*/
|
|
||||||
async connectToOutput(
|
|
||||||
sourceNode: NodeReference,
|
|
||||||
sourceSlotIndex: number,
|
|
||||||
targetOutputName?: string
|
|
||||||
): Promise<void> {
|
|
||||||
const sourceSlot = await sourceNode.getOutput(sourceSlotIndex)
|
|
||||||
const targetSlot = this.getOutputSlot(targetOutputName)
|
|
||||||
|
|
||||||
const targetPosition = targetOutputName
|
|
||||||
? await targetSlot.getPosition() // Connect to existing slot
|
|
||||||
: await targetSlot.getOpenSlotPosition() // Create new slot
|
|
||||||
|
|
||||||
await this.comfyPage.canvasOps.dragAndDrop(
|
|
||||||
await sourceSlot.getPosition(),
|
|
||||||
targetPosition
|
|
||||||
)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect a subgraph output to a regular node input.
|
|
||||||
* This creates a new output slot on the subgraph if sourceOutputName is not provided.
|
|
||||||
*/
|
|
||||||
async connectFromOutput(
|
|
||||||
targetNode: NodeReference,
|
|
||||||
targetSlotIndex: number,
|
|
||||||
sourceOutputName?: string
|
|
||||||
): Promise<void> {
|
|
||||||
const sourceSlot = this.getOutputSlot(sourceOutputName)
|
|
||||||
const targetSlot = await targetNode.getInput(targetSlotIndex)
|
|
||||||
|
|
||||||
const sourcePosition = sourceOutputName
|
|
||||||
? await sourceSlot.getPosition() // Connect from existing slot
|
|
||||||
: await sourceSlot.getOpenSlotPosition() // Create new slot
|
|
||||||
|
|
||||||
await this.comfyPage.canvasOps.dragAndDrop(
|
|
||||||
sourcePosition,
|
|
||||||
await targetSlot.getPosition()
|
|
||||||
)
|
|
||||||
await this.comfyPage.nextFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user