Compare commits

...

34 Commits

Author SHA1 Message Date
bymyself
c30e1faa22 refactor 2025-11-13 10:05:29 -08:00
bymyself
8bb1d5724b move data model to workfbench 2025-11-11 11:34:37 -08:00
Christian Byrne
6c9743c1a6 fix(vite): use dynamic base URL based on cloud distribution (#6562)
## Summary
- Replace hardcoded `<base href="/">` in index.html with dynamic vite
base config
- Set `base: DISTRIBUTION === 'cloud' ? '/' : ''` in vite.config.mts
- Ensures proper asset loading across different deployment contexts

## Test plan
- [ ] Verify cloud distribution builds work correctly
- [ ] Verify localhost/desktop distributions work correctly
- [ ] Test asset loading in both contexts

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6562-fix-vite-use-dynamic-base-URL-based-on-cloud-distribution-2a06d73d365081c8b5d2e58870ebd14d)
by [Unito](https://www.unito.io)
2025-11-04 13:34:41 -07:00
Christian Byrne
4ab1e824b7 hide minimap on mobile by default (#6579)
If below large breakpoint, hide the minimap by default. If the user has
set the setting before (or closed minimap), their preference will
override this.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6579-hide-minimap-on-mobile-by-default-2a16d73d365081db9c66d209ee046097)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-04 13:11:58 -07:00
Jin Yi
adecd258b6 Fix cloud routing issues caused by incorrect api_base calculation (#6572)
## Summary
- Resolves multiple cloud environment issues when accessing
`/cloud/user-check` directly
- Fixes API routing problems that caused 304 Not Modified errors and
JSON parsing failures
- Maintains compatibility with subpath deployments for OSS users

## Root Cause
The `api_base` was incorrectly calculated as `/cloud` on cloud SPA
routes, causing API calls to use wrong paths like `/cloud/api/user`
instead of `/api/user`.

## Issues Fixed
-  `/cloud/user-check` direct access resulted in infinite loading
-  JSON parsing errors: `Unexpected token '<', "<!DOCTYPE "... is not
valid JSON`
-  304 Not Modified responses on `/cloud/api/user`,
`/cloud/api/settings/onboarding_survey`, `/cloud/api/system_stats`

## Solution
Added conditional `api_base` calculation in `ComfyApi` constructor:
- **Cloud SPA routes** (`/cloud/*`): Use empty `api_base` → API calls
use `/api/*` paths
- **Regular deployments**: Keep existing logic → Supports subpath
deployments

## Test Plan
- [x] Verify `/cloud/user-check` direct access works without infinite
loading
- [x] Verify all API calls return 200 instead of 304
- [x] Verify OSS subpath deployment compatibility unchanged
- [x] Test authentication flow completion

🤖 Generated with [Claude Code](https://claude.ai/code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6572-Fix-cloud-routing-issues-caused-by-incorrect-api_base-calculation-2a16d73d36508163aeb2cbf6347b427d)
by [Unito](https://www.unito.io)
2025-11-04 10:37:09 -08:00
Jin Yi
2c4280a28d feat: Add Open Graph and Twitter meta tags for cloud distribution (#6571)
## Summary
- Add meta tags plugin for social media previews (Twitter, Facebook,
LinkedIn, etc.)
- Include SEO meta tags (title, description, keywords)
- Only applies to cloud distribution (`DISTRIBUTION === 'cloud'`)

## Changes
- Added Open Graph meta tags for better social media link previews
- Added Twitter Card meta tags for Twitter sharing
- Added SEO meta tags (title, description, keywords)
- Added `og-image.png` for preview image
- Meta tags only inject when `DISTRIBUTION === 'cloud'` to avoid
affecting other distributions

## Test plan
- [x] Build with `DISTRIBUTION=cloud pnpm build`
- [x] Verify meta tags appear in built HTML
- [ ] Test link sharing on Twitter, Facebook, Slack
- [ ] Verify meta tags don't appear in localhost/desktop builds

🤖 Generated with Claude Code

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6571-feat-Add-Open-Graph-and-Twitter-meta-tags-for-cloud-distribution-2a16d73d365081e58c73f6009513b2bb)
by [Unito](https://www.unito.io)
2025-11-04 08:18:17 -07:00
Johnpaul Chiwetelu
c2150c6046 [fix] improve FormDropdownMenu scrolling behavior (#6570)
This pull request makes a minor UI adjustment to the
`FormDropdownMenu.vue` component. The change improves the dropdown
menu's usability by allowing its contents to scroll vertically and adds
a small top margin for better spacing.

* UI improvement: Changed the dropdown list container to use
`overflow-y-scroll` and added a top margin (`mt-2`) for improved
scrolling and spacing.
(`src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue`)-
Add top margin (mt-2) for better spacing
- Change overflow-hidden to overflow-y-scroll for scrollable list

## Current

https://github.com/user-attachments/assets/40cb4600-ff4e-41a5-89d6-a9aee48fa633

## Before


https://github.com/user-attachments/assets/fbe7a399-06d5-4cda-b850-f28b09f20d73

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6570-fix-improve-FormDropdownMenu-scrolling-behavior-2a16d73d3650815c883fcfeb7d9a3ace)
by [Unito](https://www.unito.io)
2025-11-03 23:25:05 -08:00
Benjamin Lu
cdffcfaa33 Add .pnpm-store to gitignore (#6566)
Adds .pnpm-store to gitignore. It's a cache folder that sometimes
appears in certain scenarios that would never need to be checked into
git.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6566-Add-pnpm-store-to-gitignore-2a16d73d365081319220f90516cfad3c)
by [Unito](https://www.unito.io)
2025-11-03 19:59:22 -08:00
Alexander Brown
eae93c2a79 Style: Pinned, Muted, Bypassed header indicators (#6530)
## Summary

Match (mostly) the header design for Modern Nodes' statuses.

## Changes

- **What**: Styling to match designs for more states.

## Screenshot

<img width="591" height="687" alt="image"
src="https://github.com/user-attachments/assets/bf8fe5d1-bd42-455c-ab20-668632ab5049"
/>

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6530-WIP-Pinned-and-Muted-styles-29f6d73d36508191bb89dc13674db8ba)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-03 18:14:38 -08:00
Alexander Brown
4cfa5b4b5d Feat: Doubleclick to toggle edit for Markdown (litegraph) (#6560)
## Summary

See https://github.com/Comfy-Org/ComfyUI_frontend/pull/6537, but for
litegraph widget.

Doesn't allow dragging the node through the rendered markdown yet, that
would be more complicated (DOMWidget complication)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6560-Feat-Doubleclick-to-toggle-edit-for-Markdown-litegraph-2a06d73d36508189bf6eedd7cdeba6db)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-03 16:28:35 -08:00
Christian Byrne
a10c01db4c Conditionally generate vendor import map outside cloud builds (#6559)
## Summary
- gate vendor import-map generation behind non-cloud distributions
- keep cloud bundles slim while preserving prebuilt chunks for
desktop/localhost builds
- document the conditional to guide future CDN/cache work

## Testing
- [ ] DISTRIBUTION=cloud pnpm build
- [ ] DISTRIBUTION=desktop pnpm build

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6559-Conditionally-generate-vendor-import-map-outside-cloud-builds-2a06d73d3650819d96a0e2b6c006baf1)
by [Unito](https://www.unito.io)
2025-11-03 15:36:30 -07:00
AustinMroz
f38255bff4 Fix sizing of image previews in vue mode (#6557)
#6352 apparently introduced a regression in scaling for image previews

This PR fixes image scaling and, as a more opinionated change, makes the
image dimensions stay adjacent to the image
<img width="1283" height="668" alt="image"
src="https://github.com/user-attachments/assets/56fabe30-665f-45bb-9ed5-1c1bb79d7e54"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6557-Fix-sizing-of-image-previews-in-vue-mode-2a06d73d365081e4b2a1ebff7b42e9a8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-03 14:27:58 -07:00
AustinMroz
befa84130b Fix empty combo widget for missing models in vue (#6555)
When a loaded workflow uses a model which does not exist, the combo
widget would previously display as empty. This PR
- Sets the placeholder value to the localValue, so that an invalid
selection still displays
- Sets the selection as invalid when the value is not in valid options
so that it displays in red.

<img width="817" height="200" alt="image"
src="https://github.com/user-attachments/assets/be7d6372-3957-4fd6-ac1e-778f058ec0ba"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6555-Austin-vue-combo-missing-2a06d73d36508181a96ddd5c604efba4)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-03 14:26:17 -07:00
Arjan Singh
dc8a0e04a5 fix(AssetCard): use tooltip instead of title for overflow text (#6556)
## Summary

The default `title` attribute was too small and too slow to show for
users. We were getting complaints about not being able to see the full
name of the model.

Using [Prime Vue's tooltip](https://primevue.org/tooltip) instead.

## Screenshots (if applicable)

Before:

<img width="480" height="246" alt="image"
src="https://github.com/user-attachments/assets/db4d1cc7-9e9a-40b3-bf16-57f2def93726"
/>


After:

<img width="518" height="720" alt="image"
src="https://github.com/user-attachments/assets/500389e5-c781-4d3b-b62e-1e72d2b7e2c6"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6556-fix-AssetCard-use-tooltip-instead-of-title-for-overflow-text-2a06d73d365081939dc0d863b05f1e90)
by [Unito](https://www.unito.io)
2025-11-03 19:59:42 +00:00
Christian Byrne
9cd7a39f09 load template workflow via URL query param (#6546)
## Summary

Adds support for loading templates via URL query parameters. Users can
now share direct links to templates.

To test:

1. checkout this branch
2. start dev server on port 5173
3. go to http://localhost:5173/?template=image_qwen_image_edit_2509

**Examples:**
- `/?template=default` - Loads template with default source
- `/?template=flux_simple&source=custom` - Loads from custom source

Includes error handling with toast notifications for invalid templates
and comprehensive test coverage.

---------

Co-authored-by: Christian Byrne <c.byrne@comfy.org>
2025-11-02 21:23:56 -08:00
Christian Byrne
4810b5728a [feat] Add Partner Nodes virtual category and rename license filter (#6542)
This PR adds a 'Partner Nodes' virtual category that filters templates
where OpenSource === false, and renames the 'License' filter to 'Runs
on' with values 'ComfyUI' and 'Partner API'. The implementation is
backward compatible and works like the existing 'Basics' category - it
filters templates from any category without duplication. The filter
logic now uses the explicit OpenSource field instead of heuristic
detection. This change coordinates with upcoming workflow_templates repo
updates that will move API templates to GENERATION TYPE categories and
add the OpenSource field to all API node templates.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6542-feat-Add-Partner-Nodes-virtual-category-and-rename-license-filter-29f6d73d36508111a85bdf5017f0a100)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-02 20:50:16 -08:00
Christian Byrne
6fe88dba54 fix(telemetry): remove redundant run tracking; keep click analytics + single execution event (#6518)
## Summary
Deduplicates workflow run telemetry and keeps a single source of truth
for execution while retaining click analytics and attributing initiator
source.

- Keep execution tracking in one place via `trackWorkflowExecution()`
- Keep click analytics via `trackRunButton(...)`
- Attribute initiator with `trigger_source` = 'button' | 'keybinding' |
'legacy_ui'
- Remove pre‑tracking from keybindings to avoid double/triple counting
- Update legacy UI buttons to emit both click + execution events (they
bypass commands)

## Problem
PR #6499 added tracking at multiple layers:
1) Keybindings tracked via a dedicated method and then executed a
command
2) Menu items tracked via a dedicated method and then executed a command
3) Commands also tracked execution

Because these ultimately trigger the same command path, this produced
duplicate (sometimes triplicate) events per user action and made it hard
to attribute initiator precisely.

## Solution
- Remove redundant tracking from keybindings (and previous legacy menu
handler)
- Commands now emit both:
- `trackRunButton(...)` (click analytics, includes `trigger_source` when
provided)
- `trackWorkflowExecution()` (single execution start; includes the last
`trigger_source`)
- Legacy UI buttons (which call `app.queuePrompt(...)` directly) now
also emit both events with `trigger_source = 'legacy_ui'`
- Add `ExecutionTriggerSource` type and wire `trigger_source` through
provider so `EXECUTION_START` matches the most recent click intent

### Telemetry behavior after this change
- `RUN_BUTTON_CLICKED` (click analytics)
  - Emitted when a run is initiated via:
    - Button: `trigger_source = 'button'`
    - Keybinding: `trigger_source = 'keybinding'`
    - Legacy UI: `trigger_source = 'legacy_ui'`
- `EXECUTION_START` (execution lifecycle)
- Emitted once per run at start; includes `trigger_source` matched to
the click intent above
- Paired with `EXECUTION_SUCCESS` / `EXECUTION_ERROR` from execution
handlers

## Benefits
-  Accurate counts by removing duplicated run events
-  Clear initiator attribution (button vs keybinding vs legacy UI)
-  Separation of “intent” (click) vs. “lifecycle” (execution)
-  Simpler implementation and maintenance

## Files Changed (high level)
- `src/services/keybindingService.ts`: Route run commands with
`trigger_source = 'keybinding'`
- `src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue`: Send
`trigger_source = 'button'` to commands
- `src/scripts/ui.ts`: Legacy queue buttons emit `trackRunButton({
trigger_source: 'legacy_ui' })` and `trackWorkflowExecution()`
- `src/composables/useCoreCommands.ts`: Commands emit
`trackRunButton(...)` + `trackWorkflowExecution()`; accept telemetry
metadata
- `src/platform/telemetry/types.ts`: Add `ExecutionTriggerSource` and
optional `trigger_source` in click + execution payloads
- `src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts`:
Carry `trigger_source` from click → execution and reset after use
- `src/stores/commandStore.ts`: Allow commands to receive args (for
telemetry metadata)
- `src/extensions/core/groupNode.ts`: Adjust command function signatures
to new execute signature

## Related
- Reverts the multi‑event approach from #6499
- Keeps `trackWorkflowExecution()` as the canonical execution event
while preserving click analytics and initiator attribution with
`trackRunButton(...)`

┆Issue is synchronized with this Notion page by Unito

---------

Co-authored-by: Christian Byrne <c.byrne@comfy.org>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-02 19:48:21 -08:00
Christian Byrne
8df0a3885d [feat] Rename license filter to 'Runs On' filter in template selector (#6543)
## Summary

Renamed the templates license filter to better reflect its actual
purpose - showing where a template executes (locally in ComfyUI vs
external/remote API).

The current "License" filter has been causing confusion with model
licensing terms (e.g., Apache vs flux-dev licensing). This PR clarifies
the filter's purpose by renaming it to "Runs On" and updating the
options to be more descriptive of inference location.

<img width="196" height="230" alt="image"
src="https://github.com/user-attachments/assets/8cbea263-f399-4945-82c1-357ec185f5a7"
/>

<img width="861" height="597" alt="image"
src="https://github.com/user-attachments/assets/af116876-d7a5-49c5-b791-1fda637ff3a3"
/>


## Changes

- **Filter name**: "License" → "Runs On"
- **Filter options**: 
  - "Open Source" → "ComfyUI"
  - "Closed Source (API Nodes)" → "External or Remote API"
- **Icon**: Changed from `file-text` to `server` for better visual
representation
- **Variable naming**: Updated all related variables, types, and tests
to use `runsOn` naming convention
- **Telemetry**: Updated metadata to track `selected_runs_on` instead of
`selected_licenses`

## Why "Runs On"?

- **Clear intent**: Users want to know if a template runs locally or
requires an API call
- **Avoids confusion**: Separates the concept from model licensing terms
- **Inclusive wording**: "Remote" is included alongside "API" to help
users who may not be familiar with API terminology
- **Cloud-agnostic**: "Runs On" works whether the app itself is running
locally or in the cloud

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6543-feat-Rename-license-filter-to-Runs-On-filter-in-template-selector-29f6d73d3650811f935bc1f3fce7d7ad)
by [Unito](https://www.unito.io)
2025-11-02 16:35:42 -08:00
Jin Yi
8dfdac3fc4 [UI] Redesign media asset card layout (#6541)
## Summary
- Reorganized media asset card layout for improved UX
- Moved duration/format chips to top-left (visible in default state)
- Replaced multiple action buttons with zoom + more menu pattern
- Repositioned output count to top-right for better visibility

## Changes
1. **Duration & format chips**: Moved from bottom-left to top-left,
shown when card is not hovered and media is not playing
2. **Media actions**: Simplified to zoom button + more menu (contains
delete, download options), shown on hover or during playback
3. **Output count**: Relocated from bottom-right to top-right for
consistent positioning

## Test plan
- [x] Verify duration/format chips appear in top-left when card is idle
- [x] Confirm action buttons (zoom + more) appear on hover
- [x] Check output count displays correctly in top-right
- [x] Test transitions between hover/non-hover states
- [x] Verify media playback doesn't interfere with UI elements

🤖 Generated with [Claude Code](https://claude.ai/code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6541-UI-Redesign-media-asset-card-layout-29f6d73d365081eb8614d5dc9b2dc214)
by [Unito](https://www.unito.io)
2025-11-02 14:06:55 -08:00
Comfy Org PR Bot
0692253e90 1.32.1 (#6547)
Patch version increment to 1.32.1

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6547-1-32-1-29f6d73d365081259588c91da6aa87c5)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-02 13:42:44 -08:00
Christian Byrne
3cded2c45f [fix] Remove unreliable CI wait logic from Claude review workflow (#6548)
Removes the wait-for-ci job that has been causing random workflow skips
and reliability issues due to race conditions with CI check creation.
Multiple attempts to fix timing issues have resulted in ongoing edge
cases, so this simplifies the workflow to run immediately when the
claude-review label is added. Reviews can now run in parallel with CI
checks, and while they may occasionally run on PRs with failing CI, the
review feedback is often valuable regardless of CI status.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6548-fix-Remove-unreliable-CI-wait-logic-from-Claude-review-workflow-29f6d73d36508125910ef4feba5abdf4)
by [Unito](https://www.unito.io)
2025-11-02 13:41:36 -08:00
pythongosssss
fd236b3587 UI color updates & tweaks (#6381)
## Summary

Small updates to the UI to make it more visually distinct from the graph
nodes and improving the login button

## Changes

- **What**: 
  - Improve theme support with dynamic colors
  - Standardize surface colors/borders
  - Add shadows to all floating UI elements
  - Change side toolbar to be docked by default
  - Decrease side toolbar width
  - Change login button to icon only
  - Update C button to be more distinctive

## Review Focus

- Theme tokens, I am not sure what the overall plan for how these tokens
will be supported for custom user palettes. I've implemented a method
where default variables are chosen that look nice over all existing
themes, but they can be overridden.

## Screenshots

Dark theme updated color
<img width="958" height="615" alt="image"
src="https://github.com/user-attachments/assets/a2c540cf-6c05-4ad3-996b-8b7b80df8d2d"
/>

Themed & updated menu button (active vs hover vs default)
<img width="58" height="338" alt="github"
src="https://github.com/user-attachments/assets/90244ee2-d5ee-4b26-9d99-f4b8439aa372"
/><img width="58" height="338" alt="nord"
src="https://github.com/user-attachments/assets/053e8e7b-d639-4b72-92d2-ec7e2167aab8"
/><img width="58" height="338" alt="arc"
src="https://github.com/user-attachments/assets/3caeb07b-d41b-4d88-83b4-ef8d45d4e5a6"
/><img width="58" height="338" alt="solarized"
src="https://github.com/user-attachments/assets/6ebf6afb-ec3a-436b-90eb-bda40be1c79f"
/><img width="58" height="338" alt="light"
src="https://github.com/user-attachments/assets/fbb7f96a-b722-40c5-86fa-a0732e166972"
/><img width="58" height="338" alt="dark"
src="https://github.com/user-attachments/assets/5459208b-9256-4c55-9373-169e9cd9f09a"
/>

With labels
<img width="58" height="394" alt="labels"
src="https://github.com/user-attachments/assets/f97ee354-35cd-42b8-896f-57ac39644c1d"
/>

Logged out (only visible on desktop)
<img width="323" height="48" alt="logged out"
src="https://github.com/user-attachments/assets/75b71420-0c1b-446f-8cdd-43c68674d346"
/>

Logged in
<img width="355" height="48" alt="logged in"
src="https://github.com/user-attachments/assets/324fd133-a793-49dd-844a-f93dd5d1effb"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6381-UI-color-updates-tweaks-29b6d73d3650816384d2ef5617a776a0)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-02 12:43:21 -08:00
Jin Yi
e1f707ffe2 fix: Use environment-specific log API endpoints for Cloud and OSS (#6539)
## Problem
401 authentication errors were persistently occurring when calling
log-related APIs in the Cloud environment.

## Root Cause
- Frontend was calling `/internal/logs/*` endpoints in all environments
- Cloud backend provides public APIs at `/api/logs/*` (no authentication
required)
- OSS (open source) backend uses `/internal/logs/*` paths
- This caused Cloud to call non-existent paths → resulting in 401 errors

## Solution
Modified to use appropriate API endpoints based on environment using the
`isCloud` flag:
- Cloud environment: Use `/api/logs/*`
- OSS environment: Use `/internal/logs/*`

## Changes
- `getLogs()`: Added environment-specific URL branching
- `getRawLogs()`: Added environment-specific URL branching
- `subscribeLogs()`: Added environment-specific URL branching

## Testing
- [x] Verified log functionality works correctly in local (OSS)
environment
- [x] Confirmed 401 errors are resolved in Cloud environment

## Related Issues
This resolves the 401 errors tracked in Sentry for log API endpoints.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6539-fix-Use-environment-specific-log-API-endpoints-for-Cloud-and-OSS-29f6d73d365081da9e77f8b55556ca81)
by [Unito](https://www.unito.io)
2025-11-02 09:45:45 -08:00
Alexander Brown
fddebd4a73 Feat: Nicer click behavior for the Markdown Widget (#6537)
## Summary

Double click instead of single to edit. No longer changes background
color dramatically on hover.

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6537-Feat-Nicer-click-behavior-for-the-Markdown-Widget-29f6d73d365081a49119fcc2cc86fc11)
by [Unito](https://www.unito.io)
2025-11-02 08:42:45 -08:00
Christian Byrne
704de20245 fix: remove unsafe type assertions in subscription credits (#6536)
## Summary

Removes unsafe `as any` type assertions from subscription credits
composable now that the OpenAPI spec has been updated with the missing
balance breakdown fields.

The `GetCustomerBalance` API response now includes:
- `cloud_credit_balance_micros` - Monthly subscription credits
- `prepaid_balance_micros` - Pre-paid top-up credits

These fields were previously accessed with `as any` because they weren't
in the TypeScript type definitions. With the OpenAPI spec update (PR
#6531), these fields are now properly typed.


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6536-fix-remove-unsafe-type-assertions-in-subscription-credits-29f6d73d365081ffae52cc85c01c139b)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <c.byrne@comfy.org>
2025-11-02 00:15:02 -07:00
Benjamin Lu
feda2d47b0 feat(telemetry): track settings changes (#6504)
Summary
- Add telemetry event for settings changes when the global settings
dialog is open
- Clarify variable names in settings store (`settingParameter`,
`settingType`) for readability
- Introduce `SettingChangedMetadata` and
`TelemetryEvents.SETTING_CHANGED`
- Implement `trackSettingChanged` in Mixpanel provider
- Add focused unit test to verify telemetry triggers when settings
dialog is open vs closed

Quality
- Ran `pnpm lint:fix` and `pnpm typecheck`
- Unit tests pass locally

Notes
- Event fires only when the settings dialog is open (uses
`useDialogStore().isDialogOpen('global-settings')`)
- OSS builds are unaffected (`useTelemetry()` returns null)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6504-feat-telemetry-track-settings-changes-clarify-names-add-unit-test-29e6d73d3650815ea919d832b310cc46)
by [Unito](https://www.unito.io)
2025-11-01 23:52:04 -07:00
Christian Byrne
2d22c9f7f0 Update diffusion_models display to 'Diffusion' in asset browser (#6533)
## Summary
- Update the display label for `diffusion_models` category from
"Diffusion Models" to "Diffusion" in the asset browser/gallery UI
- Added special case handling in `formatCategoryLabel` function for
cleaner display
- This is a display-only change that does not affect underlying data
structures or API calls

## Test plan
- [x] Unit tests pass
- [x] Linter passes
- [x] Formatter passes
- [x] Type checking passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6533-Update-diffusion_models-display-to-Diffusion-in-asset-browser-29f6d73d365081009fa5cb396861570b)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <chrbyrne96@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-01 23:26:02 -07:00
Comfy Org PR Bot
d808998863 [chore] Update Comfy Registry API types from comfy-api@36df059 (#6531)
## Automated API Type Update

This PR updates the Comfy Registry API types from the latest comfy-api
OpenAPI specification.

- API commit: 36df059
- Generated on: 2025-11-01T23:56:46Z

These types are automatically generated using openapi-typescript.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6531-chore-Update-Comfy-Registry-API-types-from-comfy-api-36df059-29f6d73d3650818da7e2e6802727213e)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-01 23:10:46 -07:00
Benjamin Lu
7bf8e5af38 feat(telemetry): add tracking for sidebar, run menu, dialogs, subgraphs, settings and credits (#6511)
Summary
- Add comprehensive telemetry for key UI interactions using existing
telemetry hooks (cloud-enabled, no-op in OSS):

Sidebar and top-level
- Node library button: `node_library`
- Model library button: `model_library`
- Workflows button: `workflows`
- Assets/Media button: `assets`
- Templates button: `templates`
- Keyboard Shortcuts: `keyboard_shortcuts`
- Console: `console`
- Help Center: `help_center`
- Settings (from Comfy logo menu): `settings_menu`

Floating canvas menu
- Minimap toggle: `minimap_toggle`
- Hide links toggle: `hide_links`

Run button and queue
- Run button handle (drag): `run_button_handle`
- Run mode selection: `run_instant`, `run_on_change`
- Queue multiple: `queue_multiple` fires on each run when batch count >
1 (moved from batch-count-change to run-time, per guidance)

Error dialogs
- Close (X/mask/ESC): `error_dialog_close` via dialog onClose
- Show report: `error_show_report`
- Help fix this: `error_help_fix_this`
- Find issues: `error_find_issues`

Nodes / Subgraphs
- Selection toolbox “Node info”: `node_info`
- Enter subgraph (node header enter): `open_subgraph`
- Subgraph breadcrumb navigation: `subgraph_breadcrumb_item` and
`subgraph_breadcrumb_root`

Settings / Credits / Search
- Settings menu button (under Comfy logo): `settings_menu`
- Purchase credits (Settings > Credits panel): tracked via existing
`trackAddApiCreditButtonClicked`
- Purchase credits (Avatar popover Top Up): tracked via existing
`trackAddApiCreditButtonClicked`
- Debounced search telemetry already present for node search and
template filters; left as-is

Notes and answers
- Error dialog onClose: only fires when the dialog actually closes (X,
mask, ESC, or programmatic close). “Show report” and “Help fix this” do
not close the dialog; they each emit their own events.
- Telemetry is behind the cloud provider; calls are optional
(`useTelemetry()?.…`). OSS builds send nothing.

Open questions / follow-ups
- Primary Run button click: today cloud-only `trackRunButton` exists; we
can also emit a UI-level `run` click (`UI_BUTTON_CLICKED` style)
alongside it if desired. Confirm preference and I can add it.
- Subgraph usage richness: if we want structured analytics (e.g.,
action, depth, subgraph id, node count), I can add a dedicated provider
method and include richer metadata at enter/breadcrumb.
- Optional parity: track the Comfy menu’s “Browse Templates” item in
addition to the sidebar Templates button.

Quality
- Ran `pnpm lint:fix` and `pnpm typecheck`; both pass locally.

Implementation details
- All handlers refactored to named functions where needed; used `void`
for intentionally unawaited async calls per lint rules.
- Event names kept consistent in `button_id` strings; happy to align to
a different naming scheme if you prefer.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6511-feat-telemetry-add-tracking-for-sidebar-run-menu-dialogs-subgraphs-settings-and-cre-29e6d73d365081a1b8b4fdfbbf40e18b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-01 23:09:41 -07:00
Alexander Brown
31e405abc8 Feat: Loading state while loading dropped workflows (#6464)
## Summary

Indicate to the user that we're hard at work parsing their JSON behind
the scenes.

## Changes

- **What**: Turn on the loading spinner while processing a workflow
- **What else**: Refactored the code around figuring out how to grab the
data from the file to make this easier

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6464-WIP-Loading-state-for-dropped-workflows-29c6d73d3650812dba66f2a7d27a777c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2025-11-01 22:50:29 -07:00
AustinMroz
0e9c29e7b7 Fix node header height for subgraphs (#6525)
Node headers are slightly too tall on subgraph nodes due to the height
of the "enter subgraph" button. This is fixed by explicitly setting the
height of the enter subgraph button.
<img width="991" height="171" alt="image"
src="https://github.com/user-attachments/assets/34d049a7-1dee-46c2-ab28-169c3f546347"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6525-Fix-node-header-height-for-subgraphs-29f6d73d3650810eaf95e7a6d107c2bd)
by [Unito](https://www.unito.io)
2025-11-01 22:43:38 -07:00
Jin Yi
8d27c4fb1b fix: Change h-screen to h-svh in BaseViewTemplate (#6529)
## Summary
- Fixed height issue in BaseViewTemplate by changing `h-screen` to
`h-svh` for better viewport handling

## Changes
- Updated `BaseViewTemplate.vue` to use `h-svh` (small viewport height)
instead of `h-screen`
- This ensures proper height calculation on mobile devices with dynamic
browser UI

🤖 Generated with [Claude Code](https://claude.ai/code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6529-fix-Change-h-screen-to-h-svh-in-BaseViewTemplate-29f6d73d36508117871cd4ff165c2109)
by [Unito](https://www.unito.io)
2025-11-01 22:42:37 -07:00
AustinMroz
02aaf577ec Fix inability to select image from batch in vue (#6521)
Selecting a new image from a batch sets isLoading to true, but
handleImageLoad is never triggered so the image never displays.

Swapping to a different image from a batch is currently the only place
isLoading is set to true. This change, even if temporary, results in a
good chunk of dead code.

To my understanding, ImagePreviews are always object URLs and should
never need to load, so I don't foresee the loading placeholder being
needed here.

Resolves #6458

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6521-Fix-inability-to-select-image-from-batch-in-vue-29e6d73d36508162abeaeece7c5e0eed)
by [Unito](https://www.unito.io)
2025-11-01 16:56:41 -07:00
AustinMroz
431594a6fc Fix partial execution inside subgraphs (#6487)
`getExecutionIdsForSelectedNodes` is only used for partial execution.
The prior implementation solved the wrong problem. Given a list of
nodes, it would explore into subgraphs and return a list of partial
ExecutionIds for all contained nodes. Because this does not resolve the
partial execution path to the current subgraph, this is incorrect when
the current graph is not the root graph. Woefully, this incorrect
functionality is never useful because the recursive exploration only
applies to subgraph nodes which never satisfy the outputNode filter
applied by the parent function.

An extra function is used to correctly append the parent execution path,
but the existing, probably never useful code for recursively collecting
children is otherwise left in place.

Resolves #6480

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6487-Fix-partial-execution-inside-subgraphs-29d6d73d36508197924bfb3a0fb6699e)
by [Unito](https://www.unito.io)
2025-11-01 16:08:57 -07:00
158 changed files with 22793 additions and 20423 deletions

View File

@@ -25,3 +25,6 @@ e3bb29ceb8174b8bbca9e48ec7d42cd540f40efa
# [refactor] Improve updates/notifications domain organization (#5590)
27ab355f9c73415dc39f4d3f512b02308f847801
# Migrate Tailwind styles to design-system package
9f19d8fb4bd22518879343b49c05634dca777df0

View File

@@ -17,40 +17,9 @@ concurrency:
cancel-in-progress: true
jobs:
wait-for-ci:
claude-review:
runs-on: ubuntu-latest
if: github.event.label.name == 'claude-review'
outputs:
should-proceed: ${{ steps.check-status.outputs.proceed }}
steps:
- name: Wait for other CI checks
uses: lewagon/wait-on-check-action@e106e5c43e8ca1edea6383a39a01c5ca495fd812
with:
ref: ${{ github.event.pull_request.head.sha }}
check-regexp: '^(lint-and-format|test|playwright-tests)'
allowed-conclusions: success,skipped,failure,cancelled,neutral,action_required,timed_out,stale
wait-interval: 30
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check if we should proceed
id: check-status
run: |
CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format")) | {name, conclusion}')
if echo "$CHECK_RUNS" | grep -Eq '"conclusion": "(failure|cancelled|timed_out|action_required)"'; then
echo "Some CI checks failed - skipping Claude review"
echo "proceed=false" >> $GITHUB_OUTPUT
else
echo "All CI checks passed - proceeding with Claude review"
echo "proceed=true" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
claude-review:
needs: wait-for-ci
if: needs.wait-for-ci.outputs.should-proceed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository

1
.gitignore vendored
View File

@@ -18,6 +18,7 @@ yarn.lock
.stylelintcache
node_modules
.pnpm-store
dist
dist-ssr
*.local

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.32.0",
"version": "1.32.1",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",

View File

@@ -241,6 +241,18 @@
--border-subtle: var(--color-smoke-400);
--muted-background: var(--color-smoke-700);
--accent-background: var(--color-smoke-800);
/* Default UI element color palette variables */
--palette-contrast-mix-color: #fff;
--palette-interface-panel-surface: var(--comfy-menu-bg);
--palette-interface-stroke: color-mix(in srgb, var(--interface-panel-surface) 75.5%, var(--contrast-mix-color));
--palette-interface-panel-box-shadow: 1px 1px 8px 0 rgb(0 0 0 / 0.4);
--palette-interface-panel-drop-shadow: 1px 1px 4px rgb(0 0 0 / 0.4);
--palette-interface-panel-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 92.5%, var(--contrast-mix-color));
--palette-interface-panel-selected-surface: color-mix(in srgb, var(--interface-panel-surface) 87.5%, var(--contrast-mix-color));
--palette-interface-button-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 82%, var(--contrast-mix-color));
}
.dark-theme {
@@ -497,36 +509,40 @@ body {
.comfy-markdown {
/* We assign the textarea and the Tiptap editor to the same CSS grid area to stack them on top of one another. */
display: grid;
}
& > textarea,
.tiptap {
grid-area: 1 / 1 / 2 / 2;
}
.comfy-markdown > textarea {
grid-area: 1 / 1 / 2 / 2;
}
& > textarea {
opacity: 0;
pointer-events: none;
}
.comfy-markdown .tiptap {
grid-area: 1 / 1 / 2 / 2;
background-color: var(--comfy-input-bg);
color: var(--input-text);
overflow: hidden;
overflow-y: auto;
resize: none;
border: none;
box-sizing: border-box;
font-size: var(--comfy-textarea-font-size);
height: 100%;
padding: 0.5em;
}
&.editing {
& > textarea {
opacity: 1;
pointer-events: all;
}
.comfy-markdown.editing .tiptap {
display: none;
}
.tiptap {
opacity: 0;
pointer-events: none;
}
}
.comfy-markdown .tiptap :first-child {
margin-top: 0;
}
.tiptap {
overflow-y: auto;
font-size: var(--comfy-textarea-font-size);
.comfy-markdown .tiptap :last-child {
margin-bottom: 0;
:first-child {
margin-top: 0;
}
:last-child {
margin-bottom: 0;
}
}
}
.comfy-markdown .tiptap blockquote {

View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.13727 11.2201L6.27454 14.4398" stroke="var(--pin-color, var(--color-smoke-800, #8a8a8a))" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.28085 6.68385C6.21652 6.92342 6.08664 7.1403 5.9058 7.31009C5.72497 7.47989 5.50035 7.59587 5.25721 7.645L3.95569 7.91742C3.71254 7.96655 3.48793 8.08253 3.30709 8.25233C3.12626 8.42212 2.99637 8.639 2.93204 8.87857L2.80091 9.36797C2.75515 9.53876 2.7791 9.72073 2.86751 9.87385C2.95591 10.027 3.10153 10.1387 3.27231 10.1845L10.9997 12.255C11.1705 12.3008 11.3525 12.2768 11.5056 12.1884C11.6587 12.1 11.7705 11.9544 11.8162 11.7836L11.9474 11.2942C12.0114 11.0546 12.0074 10.8018 11.9357 10.5643C11.864 10.3269 11.7274 10.1141 11.5414 9.95001L10.5505 9.06333C10.3645 8.89921 10.2279 8.68646 10.1562 8.44899C10.0845 8.21153 10.0805 7.95877 10.1446 7.71913L10.7933 5.29787C10.8391 5.12709 10.9508 4.98148 11.1039 4.89307C11.2571 4.80466 11.439 4.78071 11.6098 4.82647C11.9514 4.91799 12.3153 4.87008 12.6216 4.69327C12.9278 4.51646 13.1513 4.22523 13.2428 3.88366C13.3343 3.54209 13.2864 3.17815 13.1096 2.8719C12.9328 2.56566 12.6416 2.34219 12.3 2.25067L7.1484 0.870299C6.80683 0.778775 6.44289 0.826689 6.13665 1.0035C5.8304 1.18031 5.60694 1.47154 5.51541 1.81311C5.42389 2.15468 5.4718 2.51862 5.64861 2.82487C5.82542 3.13111 6.11665 3.35458 6.45822 3.4461C6.62901 3.49186 6.77462 3.6036 6.86302 3.75672C6.95143 3.90984 6.97539 4.09181 6.92962 4.2626L6.28085 6.68385Z" fill="var(--handle-color, var(--color-gold-600, #fd9903))" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -4,6 +4,13 @@ import { addDynamicIconSelectors } from '@iconify/tailwind'
import { iconCollection } from './src/iconCollection'
export default {
theme: {
extend: {
boxShadow: {
interface: 'var(--interface-panel-box-shadow)'
}
}
},
plugins: [
addDynamicIconSelectors({
iconSets: {

File diff suppressed because it is too large Load Diff

6
pnpm-lock.yaml generated
View File

@@ -12,15 +12,9 @@ catalogs:
'@eslint/js':
specifier: ^9.35.0
version: 9.35.0
'@iconify-json/lucide':
specifier: ^1.1.178
version: 1.2.66
'@iconify/json':
specifier: ^2.2.380
version: 2.2.380
'@iconify/tailwind':
specifier: ^1.1.3
version: 1.2.0
'@intlify/eslint-plugin-vue-i18n':
specifier: ^4.1.0
version: 4.1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

View File

@@ -52,7 +52,7 @@
"comfy_base": {
"fg-color": "#fff",
"bg-color": "#202020",
"comfy-menu-bg": "#353535",
"comfy-menu-bg": "#11141a",
"comfy-menu-secondary-bg": "#303030",
"comfy-input-bg": "#222",
"input-text": "#ddd",

View File

@@ -68,7 +68,12 @@
"content-fg": "#222",
"content-hover-bg": "#adadad",
"content-hover-fg": "#222",
"bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem"
"bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem",
"interface-panel-box-shadow": "1px 1px 8px 0 rgba(0, 0, 0, 0.2)",
"interface-panel-drop-shadow": "1px 1px 4px rgba(0, 0, 0, 0.4)",
"interface-panel-hover-surface": "var(--color-gray-200)",
"interface-panel-selected-surface": "color-mix(in srgb, var(--interface-panel-surface) 78%, var(--contrast-mix-color))",
"contrast-mix-color": "#000"
}
}
}

View File

@@ -5,7 +5,7 @@
</div>
<div
class="actionbar-container pointer-events-auto mx-1 flex h-12 items-center rounded-lg px-2 shadow-md"
class="actionbar-container pointer-events-auto mx-1 flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] px-2 shadow-interface"
>
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div
@@ -48,6 +48,5 @@ onMounted(() => {
<style scoped>
.actionbar-container {
background-color: var(--comfy-menu-bg);
border: 1px solid var(--p-panel-border-color);
}
</style>

View File

@@ -48,6 +48,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { t } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { cn } from '@/utils/tailwindUtil'
import ComfyRunButton from './ComfyRunButton'
@@ -132,6 +133,15 @@ watch(visible, async (newVisible) => {
}
})
/**
* Track run button handle drag start using mousedown on the drag handle.
*/
useEventListener(dragHandleRef, 'mousedown', () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'actionbar_run_handle_drag_start'
})
})
const lastDragState = ref({
x: x.value,
y: y.value,
@@ -258,7 +268,9 @@ const panelClass = computed(() =>
cn(
'actionbar pointer-events-auto z1000',
isDragging.value && 'select-none pointer-events-none',
isDocked.value ? 'p-0 static mr-2 border-none bg-transparent' : 'fixed'
isDocked.value
? 'p-0 static mr-2 border-none bg-transparent'
: 'fixed shadow-interface'
)
)
</script>

View File

@@ -100,7 +100,7 @@ import BatchCountEdit from '../BatchCountEdit.vue'
const workspaceStore = useWorkspaceStore()
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
const { mode: queueMode } = storeToRefs(useQueueSettingsStore())
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
const { t } = useI18n()
const queueModeMenuItemLookup = computed(() => {
@@ -118,6 +118,9 @@ const queueModeMenuItemLookup = computed(() => {
label: `${t('menu.run')} (${t('menu.onChange')})`,
tooltip: t('menu.onChangeTooltip'),
command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_mode_option_run_on_change_selected'
})
queueMode.value = 'change'
}
}
@@ -128,6 +131,9 @@ const queueModeMenuItemLookup = computed(() => {
label: `${t('menu.run')} (${t('menu.instant')})`,
tooltip: t('menu.instantTooltip'),
command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_mode_option_run_instant_selected'
})
queueMode.value = 'instant'
}
}
@@ -158,9 +164,18 @@ const queuePrompt = async (e: Event) => {
? 'Comfy.QueuePromptFront'
: 'Comfy.QueuePrompt'
useTelemetry()?.trackRunButton({ subscribe_to_run: false })
if (batchCount.value > 1) {
useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_run_multiple_batches_submitted'
})
}
await commandStore.execute(commandId)
await commandStore.execute(commandId, {
metadata: {
subscribe_to_run: false,
trigger_source: 'button'
}
})
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="subgraph-breadcrumb w-auto drop-shadow-md"
class="subgraph-breadcrumb w-auto drop-shadow-[var(--interface-panel-drop-shadow)]"
:class="{
'subgraph-breadcrumb-collapse': collapseTabs,
'subgraph-breadcrumb-overflow': overflowingTabs
@@ -40,6 +40,7 @@ import { computed, onUpdated, ref, watch } from 'vue'
import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbItem.vue'
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useTelemetry } from '@/platform/telemetry'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
@@ -73,6 +74,9 @@ const items = computed(() => {
const items = navigationStore.navigationStack.map<MenuItem>((subgraph) => ({
label: subgraph.name,
command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'breadcrumb_subgraph_item_selected'
})
const canvas = useCanvasStore().getCanvas()
if (!canvas.graph) throw new TypeError('Canvas has no graph')
@@ -97,6 +101,9 @@ const home = computed(() => ({
key: 'root',
isBlueprint: isBlueprint.value,
command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'breadcrumb_subgraph_root_selected'
})
const canvas = useCanvasStore().getCanvas()
if (!canvas.graph) throw new TypeError('Canvas has no graph')
@@ -201,8 +208,8 @@ onUpdated(() => {
:deep(.p-breadcrumb-separator),
:deep(.p-breadcrumb-item) {
@apply h-12;
border-top: 1px solid var(--p-panel-border-color);
border-bottom: 1px solid var(--p-panel-border-color);
border-top: 1px solid var(--interface-stroke);
border-bottom: 1px solid var(--interface-stroke);
background-color: var(--comfy-menu-bg);
}
@@ -214,7 +221,7 @@ onUpdated(() => {
@apply rounded-l-lg;
/* Then collapse the root workflow */
flex-shrink: 5000;
border-left: 1px solid var(--p-panel-border-color);
border-left: 1px solid var(--interface-stroke);
.p-breadcrumb-item-link {
padding-left: var(--p-breadcrumb-item-padding);
@@ -225,7 +232,7 @@ onUpdated(() => {
@apply rounded-r-lg;
/* Then collapse the active item */
flex-shrink: 1;
border-right: 1px solid var(--p-panel-border-color);
border-right: 1px solid var(--interface-stroke);
}
:deep(.p-breadcrumb-item-link:hover),

View File

@@ -56,7 +56,7 @@ describe('UserAvatar', () => {
const avatar = wrapper.findComponent(Avatar)
expect(avatar.exists()).toBe(true)
expect(avatar.props('image')).toBeNull()
expect(avatar.props('icon')).toBe('pi pi-user')
expect(avatar.props('icon')).toBe('icon-[lucide--user]')
})
it('renders with default icon when provided photo Url is null', () => {
@@ -67,7 +67,7 @@ describe('UserAvatar', () => {
const avatar = wrapper.findComponent(Avatar)
expect(avatar.exists()).toBe(true)
expect(avatar.props('image')).toBeNull()
expect(avatar.props('icon')).toBe('pi pi-user')
expect(avatar.props('icon')).toBe('icon-[lucide--user]')
})
it('falls back to icon when image fails to load', async () => {
@@ -82,7 +82,7 @@ describe('UserAvatar', () => {
avatar.vm.$emit('error')
await nextTick()
expect(avatar.props('icon')).toBe('pi pi-user')
expect(avatar.props('icon')).toBe('icon-[lucide--user]')
})
it('uses provided ariaLabel', () => {

View File

@@ -1,7 +1,9 @@
<template>
<Avatar
class="bg-gray-200 dark-theme:bg-[var(--interface-panel-selected-surface)]"
:image="photoUrl ?? undefined"
:icon="hasAvatar ? undefined : 'pi pi-user'"
:icon="hasAvatar ? undefined : 'icon-[lucide--user]'"
:pt:icon:class="{ 'size-4': !hasAvatar }"
shape="circle"
:aria-label="ariaLabel ?? $t('auth.login.userAvatar')"
@error="handleImageError"

View File

@@ -68,17 +68,17 @@
</template>
</MultiSelect>
<!-- License Filter -->
<!-- Runs On Filter -->
<MultiSelect
v-model="selectedLicenseObjects"
:label="licenseFilterLabel"
:options="licenseOptions"
v-model="selectedRunsOnObjects"
:label="runsOnFilterLabel"
:options="runsOnOptions"
:show-search-box="true"
:show-selected-count="true"
:show-clear-button="true"
>
<template #icon>
<i class="icon-[lucide--file-text]" />
<i class="icon-[lucide--server]" />
</template>
</MultiSelect>
</div>
@@ -528,12 +528,12 @@ const {
searchQuery,
selectedModels,
selectedUseCases,
selectedLicenses,
selectedRunsOn,
sortBy,
filteredTemplates,
availableModels,
availableUseCases,
availableLicenses,
availableRunsOn,
filteredCount,
totalCount,
resetFilters
@@ -561,15 +561,15 @@ const selectedUseCaseObjects = computed({
}
})
const selectedLicenseObjects = computed({
const selectedRunsOnObjects = computed({
get() {
return selectedLicenses.value.map((license) => ({
name: license,
value: license
return selectedRunsOn.value.map((runsOn) => ({
name: runsOn,
value: runsOn
}))
},
set(value: { name: string; value: string }[]) {
selectedLicenses.value = value.map((item) => item.value)
selectedRunsOn.value = value.map((item) => item.value)
}
})
@@ -602,10 +602,10 @@ const useCaseOptions = computed(() =>
}))
)
const licenseOptions = computed(() =>
availableLicenses.value.map((license) => ({
name: license,
value: license
const runsOnOptions = computed(() =>
availableRunsOn.value.map((runsOn) => ({
name: runsOn,
value: runsOn
}))
)
@@ -634,14 +634,14 @@ const useCaseFilterLabel = computed(() => {
}
})
const licenseFilterLabel = computed(() => {
if (selectedLicenseObjects.value.length === 0) {
return t('templateWorkflows.licenseFilter', 'License')
} else if (selectedLicenseObjects.value.length === 1) {
return selectedLicenseObjects.value[0].name
const runsOnFilterLabel = computed(() => {
if (selectedRunsOnObjects.value.length === 0) {
return t('templateWorkflows.runsOnFilter', 'Runs On')
} else if (selectedRunsOnObjects.value.length === 1) {
return selectedRunsOnObjects.value[0].name
} else {
return t('templateWorkflows.licensesSelected', {
count: selectedLicenseObjects.value.length
return t('templateWorkflows.runsOnSelected', {
count: selectedRunsOnObjects.value.length
})
}
})
@@ -708,7 +708,7 @@ watch(
sortBy,
selectedModels,
selectedUseCases,
selectedLicenses
selectedRunsOn
],
() => {
resetPagination()

View File

@@ -87,7 +87,13 @@ const repoOwner = 'comfyanonymous'
const repoName = 'ComfyUI'
const reportContent = ref('')
const reportOpen = ref(false)
/**
* Open the error report content and track telemetry.
*/
const showReport = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'error_dialog_show_report_clicked'
})
reportOpen.value = true
}
const toast = useToast()
@@ -99,6 +105,9 @@ const title = computed<string>(
() => error.nodeType ?? error.exceptionType ?? t('errorDialog.defaultTitle')
)
/**
* Open contact support flow from error dialog and track telemetry.
*/
const showContactSupport = async () => {
telemetry?.trackHelpResourceClicked({
resource_type: 'help_feedback',

View File

@@ -11,6 +11,8 @@
import Button from 'primevue/button'
import { computed } from 'vue'
import { useTelemetry } from '@/platform/telemetry'
const props = defineProps<{
errorMessage: string
repoOwner: string
@@ -19,7 +21,13 @@ const props = defineProps<{
const queryString = computed(() => props.errorMessage + ' is:issue')
/**
* Open GitHub issues search and track telemetry.
*/
const openGitHubIssues = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'error_dialog_find_existing_issues_clicked'
})
const query = encodeURIComponent(queryString.value)
const url = `https://github.com/${props.repoOwner}/${props.repoName}/issues?q=${query}`
window.open(url, '_blank')

View File

@@ -164,6 +164,8 @@ watch(
)
const handlePurchaseCreditsClick = () => {
// Track purchase credits entry from Settings > Credits panel
useTelemetry()?.trackAddApiCreditButtonClicked()
dialogService.showTopUpCreditsDialog()
}

View File

@@ -10,8 +10,10 @@
></div>
<ButtonGroup
class="absolute right-0 bottom-0 z-[1200] flex-row gap-1 border-[1px] border-node-border bg-interface-panel-surface p-2"
:style="stringifiedMinimapStyles.buttonGroupStyles"
class="absolute right-0 bottom-0 z-[1200] flex-row gap-1 border-[1px] border-[var(--interface-stroke)] bg-interface-panel-surface p-2"
:style="{
...stringifiedMinimapStyles.buttonGroupStyles
}"
@wheel="canvasInteractions.handleWheel"
>
<CanvasModeSelector
@@ -61,7 +63,7 @@
data-testid="toggle-minimap-button"
:style="stringifiedMinimapStyles.buttonStyles"
:class="minimapButtonClass"
@click="() => commandStore.execute('Comfy.Canvas.ToggleMinimap')"
@click="onMinimapToggleClick"
>
<template #icon>
<i class="icon-[lucide--map] h-4 w-4" />
@@ -82,7 +84,7 @@
:aria-label="linkVisibilityAriaLabel"
data-testid="toggle-link-visibility-button"
:style="stringifiedMinimapStyles.buttonStyles"
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
@click="onLinkVisibilityToggleClick"
>
<template #icon>
<i class="icon-[lucide--route-off] h-4 w-4" />
@@ -101,6 +103,7 @@ import { useI18n } from 'vue-i18n'
import { useZoomControls } from '@/composables/useZoomControls'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'
@@ -218,6 +221,26 @@ onMounted(() => {
canvasStore.initScaleSync()
})
/**
* Track minimap toggle button click and execute the command.
*/
const onMinimapToggleClick = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'graph_menu_minimap_toggle_clicked'
})
void commandStore.execute('Comfy.Canvas.ToggleMinimap')
}
/**
* Track hide/show links button click and execute the command.
*/
const onLinkVisibilityToggleClick = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'graph_menu_hide_links_toggle_clicked'
})
void commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')
}
onBeforeUnmount(() => {
canvasStore.cleanupScaleSync()
})

View File

@@ -11,7 +11,7 @@
@click="toggleBypass"
>
<template #icon>
<i class="icon-[lucide--ban] h-4 w-4" />
<i class="icon-[lucide--redo-dot] h-4 w-4" />
</template>
</Button>
</template>

View File

@@ -13,5 +13,5 @@
<script setup lang="ts">
import Button from 'primevue/button'
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
import { showSubgraphNodeDialog } from '@/workbench/graph/subgraph/useSubgraphNodeDialog'
</script>

View File

@@ -7,7 +7,7 @@
data-testid="info-button"
text
severity="secondary"
@click="toggleHelp"
@click="onInfoClick"
>
<i class="icon-[lucide--info] h-4 w-4" />
</Button>
@@ -17,6 +17,17 @@
import Button from 'primevue/button'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import { useTelemetry } from '@/platform/telemetry'
const { showNodeHelp: toggleHelp } = useSelectionState()
/**
* Track node info button click and toggle node help.
*/
const onInfoClick = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'selection_toolbox_node_info_opened'
})
toggleHelp()
}
</script>

View File

@@ -4,13 +4,11 @@
:width="size"
:height="size"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.8193 0.600586C15.1248 0.600586 15.3296 0.70893 15.459 0.881836C15.5914 1.05888 15.6471 1.33774 15.5527 1.66895L14.8037 4.30176C14.7063 4.64386 14.4729 4.97024 14.1641 5.21191C13.8544 5.45415 13.496 5.58984 13.1699 5.58984H13.1689L9.5791 5.59668H7.90625C7.52654 5.59668 7.19496 5.84986 7.09082 6.21289L5.69434 11.0889C5.63007 11.3133 5.66134 11.5534 5.77734 11.7529L5.83203 11.8359C5.99177 12.0491 6.24252 12.1758 6.50977 12.1758H6.51074L8.88281 12.1709H11.4971C11.7643 12.171 11.9541 12.254 12.084 12.3906L12.1357 12.4521C12.2685 12.6295 12.3249 12.9089 12.2305 13.2402L11.4805 15.8721C11.383 16.2144 11.1498 16.5415 10.8408 16.7832C10.5314 17.0252 10.1736 17.161 9.84766 17.1611H9.84668L6.25684 17.168H3.64258C3.33762 17.1679 3.13349 17.0588 3.00391 16.8857C2.87135 16.7087 2.81482 16.43 2.90918 16.0986L3.39551 14.3887C3.46841 14.1327 3.41794 13.8576 3.25879 13.6445V13.6436C3.09901 13.4303 2.84745 13.3037 2.58008 13.3037H1.18066C0.875088 13.3037 0.670398 13.1953 0.541016 13.0225C0.408483 12.8451 0.351891 12.5655 0.446289 12.2344L2.11914 6.38965L2.30371 5.74707V5.74609C2.40139 5.40341 2.63456 5.07671 2.94336 4.83496C3.25302 4.59258 3.61143 4.45705 3.9375 4.45703H5.6123C5.94484 4.45703 6.24083 4.26316 6.37891 3.9707L6.42773 3.83984L6.98145 1.89551C7.07894 1.55317 7.31212 1.22614 7.62109 0.984375C7.93074 0.742127 8.2892 0.606445 8.61523 0.606445H8.61621L12.1982 0.600586H14.8193Z"
:stroke="color"
stroke-width="1"
v-bind="attributes"
/>
</svg>
</template>
@@ -22,11 +20,18 @@ interface Props {
size?: number | string
color?: string
class?: string
mode?: 'outline' | 'fill'
}
const {
size = 16,
color = 'currentColor',
mode = 'outline',
class: className
} = defineProps<Props>()
const iconClass = computed(() => className || '')
const attributes = computed(() => ({
stroke: mode === 'outline' ? color : undefined,
strokeWidth: mode === 'outline' ? 1 : undefined,
fill: mode === 'fill' ? color : 'none'
}))
</script>

View File

@@ -1,21 +1,23 @@
<template>
<div
class="comfy-menu-button-wrapper flex shrink-0 cursor-pointer flex-col items-center justify-center rounded-t-md p-2 transition-colors"
v-tooltip="{
value: t('sideToolbar.labels.menu'),
showDelay: 300,
hideDelay: 300
}"
class="comfy-menu-button-wrapper flex shrink-0 cursor-pointer flex-col items-center justify-center p-2 transition-colors"
:class="{
'comfy-menu-button-active': menuRef?.visible
}"
@click="menuRef?.toggle($event)"
@click="onLogoMenuClick($event)"
>
<ComfyLogoTransparent
alt="ComfyUI Logo"
class="comfyui-logo h-[18px] w-[18px]"
/>
<span
v-if="!isSmall"
class="side-bar-button-label mt-1 text-center text-[10px]"
>{{ t('sideToolbar.labels.menu') }}</span
>
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-black">
<ComfyLogo
alt="ComfyUI Logo"
class="comfyui-logo h-[18px] w-[18px] text-white"
mode="fill"
/>
</div>
</div>
<TieredMenu
@@ -75,9 +77,10 @@ import { computed, nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
import ComfyLogoTransparent from '@/components/icons/ComfyLogoTransparent.vue'
import ComfyLogo from '@/components/icons/ComfyLogo.vue'
import { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'
import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'
import { useTelemetry } from '@/platform/telemetry'
import { useColorPaletteService } from '@/services/colorPaletteService'
import { useCommandStore } from '@/stores/commandStore'
import { useDialogStore } from '@/stores/dialogStore'
@@ -96,14 +99,19 @@ const colorPaletteService = useColorPaletteService()
const dialogStore = useDialogStore()
const managerState = useManagerState()
const { isSmall = false } = defineProps<{
isSmall?: boolean
}>()
const menuRef = ref<
({ dirty: boolean } & TieredMenuMethods & TieredMenuState) | null
>(null)
const telemetry = useTelemetry()
function onLogoMenuClick(event: MouseEvent) {
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_comfy_menu_opened'
})
menuRef.value?.toggle(event)
}
const translateMenuItem = (item: MenuItem): MenuItem => {
const label = typeof item.label === 'function' ? item.label() : item.label
const translatedLabel = label
@@ -167,7 +175,12 @@ const extraMenuItems = computed(() => [
key: 'settings',
label: t('g.settings'),
icon: 'mdi mdi-cog-outline',
command: () => showSettings()
command: () => {
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_settings_menu_opened'
})
showSettings()
}
},
{
key: 'manage-extensions',
@@ -277,12 +290,12 @@ const hasActiveStateSiblings = (item: MenuItem): boolean => {
}
.comfy-menu-button-wrapper:hover {
background: var(--p-button-text-secondary-hover-background);
background: var(--interface-panel-hover-surface);
}
.comfy-menu-button-active,
.comfy-menu-button-active:hover {
background-color: var(--content-hover-bg);
background: var(--interface-panel-selected-surface);
}
.keybinding-tag {

View File

@@ -6,7 +6,8 @@
'small-sidebar': isSmall,
'connected-sidebar': isConnected,
'floating-sidebar': !isConnected,
'overflowing-sidebar': isOverflowing
'overflowing-sidebar': isOverflowing,
'border-r border-[var(--interface-stroke)] shadow-interface': isConnected
}"
>
<div
@@ -18,7 +19,7 @@
"
>
<div ref="topToolbarRef" :class="groupClasses">
<ComfyMenuButton :is-small="isSmall" />
<ComfyMenuButton />
<SidebarIcon
v-for="tab in tabs"
:key="tab.id"
@@ -57,6 +58,7 @@ import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'
import { useKeybindingStore } from '@/stores/keybindingStore'
@@ -97,10 +99,40 @@ const isConnected = computed(
const tabs = computed(() => workspaceStore.getSidebarTabs())
const selectedTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)
const onTabClick = async (item: SidebarTabExtension) =>
/**
* Handle sidebar tab icon click.
* - Emits UI button telemetry for known tabs
* - Delegates to the corresponding toggle command
*/
const onTabClick = async (item: SidebarTabExtension) => {
const telemetry = useTelemetry()
const isNodeLibraryTab = item.id === 'node-library'
const isModelLibraryTab = item.id === 'model-library'
const isWorkflowsTab = item.id === 'workflows'
const isAssetsTab = item.id === 'assets'
if (isNodeLibraryTab)
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_tab_node_library_selected'
})
else if (isModelLibraryTab)
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_tab_model_library_selected'
})
else if (isWorkflowsTab)
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_tab_workflows_selected'
})
else if (isAssetsTab)
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_tab_assets_media_selected'
})
await commandStore.commands
.find((cmd) => cmd.id === `Workspace.ToggleSidebarTab.${item.id}`)
?.function?.()
}
const keybindingStore = useKeybindingStore()
const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
@@ -114,7 +146,7 @@ const isOverflowing = ref(false)
const groupClasses = computed(() =>
cn(
'sidebar-item-group pointer-events-auto flex flex-col items-center overflow-hidden flex-shrink-0' +
(isConnected.value ? '' : ' rounded-lg shadow-md')
(isConnected.value ? '' : ' rounded-lg shadow-interface')
)
)
@@ -183,7 +215,7 @@ onMounted(() => {
--sidebar-padding: 4px;
--sidebar-icon-size: 1rem;
--sidebar-default-floating-width: 56px;
--sidebar-default-floating-width: 48px;
--sidebar-default-connected-width: calc(
var(--sidebar-default-floating-width) + var(--sidebar-padding) * 2
);

View File

@@ -3,7 +3,7 @@
:label="$t('sideToolbar.labels.console')"
:tooltip="$t('menu.toggleBottomPanel')"
:selected="bottomPanelStore.activePanel == 'terminal'"
@click="bottomPanelStore.toggleBottomPanel"
@click="toggleConsole"
>
<template #icon>
<i-ph:terminal-bold />
@@ -12,9 +12,20 @@
</template>
<script setup lang="ts">
import { useTelemetry } from '@/platform/telemetry'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import SidebarIcon from './SidebarIcon.vue'
const bottomPanelStore = useBottomPanelStore()
/**
* Toggle console bottom panel and track UI button click.
*/
const toggleConsole = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'sidebar_bottom_panel_console_toggled'
})
bottomPanelStore.toggleBottomPanel()
}
</script>

View File

@@ -65,6 +65,7 @@ import { computed, onMounted, toRefs } from 'vue'
import HelpCenterMenuContent from '@/components/helpcenter/HelpCenterMenuContent.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import ReleaseNotificationToast from '@/platform/updates/components/ReleaseNotificationToast.vue'
import WhatsNewPopup from '@/platform/updates/components/WhatsNewPopup.vue'
@@ -104,7 +105,13 @@ const sidebarLocation = computed(() =>
settingStore.get('Comfy.Sidebar.Location')
)
/**
* Toggle Help Center and track UI button click.
*/
const toggleHelpCenter = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'sidebar_help_center_toggled'
})
helpCenterStore.toggle()
}

View File

@@ -86,7 +86,11 @@ const computedTooltip = computed(() => t(tooltip) + tooltipSuffix)
}
.side-bar-button-selected {
background-color: var(--content-hover-bg);
background-color: var(--interface-panel-selected-surface);
color: var(--content-hover-fg);
}
.side-bar-button:hover {
background-color: var(--interface-panel-hover-surface);
color: var(--content-hover-fg);
}

View File

@@ -15,6 +15,7 @@
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useTelemetry } from '@/platform/telemetry'
import { useCommandStore } from '@/stores/commandStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
@@ -34,7 +35,13 @@ const tooltipText = computed(
() => `${t('shortcuts.keyboardShortcuts')} (${formatKeySequence(command)})`
)
/**
* Toggle keyboard shortcuts panel and track UI button click.
*/
const toggleShortcutsPanel = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'sidebar_shortcuts_panel_toggled'
})
bottomPanelStore.togglePanel('shortcuts')
}
</script>

View File

@@ -14,6 +14,7 @@ import { computed } from 'vue'
import { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import SidebarIcon from './SidebarIcon.vue'
@@ -23,7 +24,13 @@ const isSmall = computed(
() => settingStore.get('Comfy.Sidebar.Size') === 'small'
)
/**
* Open templates dialog from sidebar and track UI button click.
*/
const openTemplates = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'sidebar_templates_dialog_opened'
})
useWorkflowTemplateSelectorDialog().show('sidebar')
}
</script>

View File

@@ -170,7 +170,7 @@ import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil'
import AssetsSidebarTemplate from './AssetSidebarTemplate.vue'
const activeTab = ref<'input' | 'output'>('input')
const activeTab = ref<'input' | 'output'>('output')
const folderPromptId = ref<string | null>(null)
const folderExecutionTime = ref<number | undefined>(undefined)
const isInFolderView = computed(() => folderPromptId.value !== null)

View File

@@ -3,16 +3,18 @@
<div>
<Button
v-if="isLoggedIn"
class="user-profile-button p-1"
class="user-profile-button p-1 hover:bg-transparent"
severity="secondary"
text
:aria-label="$t('g.currentUser')"
@click="popover?.toggle($event)"
>
<div class="flex items-center rounded-full bg-(--p-content-background)">
<div
class="flex items-center gap-1 rounded-full hover:bg-[var(--interface-button-hover-surface)]"
>
<UserAvatar :photo-url="photoURL" />
<i class="pi pi-chevron-down px-1" :style="{ fontSize: '0.5rem' }" />
<i class="pi pi-chevron-down px-1" :style="{ fontSize: '0.6rem' }" />
</div>
</Button>

View File

@@ -104,6 +104,7 @@ import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthAction
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService'
const emit = defineEmits<{
@@ -136,6 +137,8 @@ const handleOpenPlanAndCreditsSettings = () => {
}
const handleTopUp = () => {
// Track purchase credits entry from avatar popover
useTelemetry()?.trackAddApiCreditButtonClicked()
dialogService.showTopUpCreditsDialog()
emit('close')
}

View File

@@ -1,15 +1,18 @@
<template>
<Button
v-if="!isLoggedIn"
:label="t('auth.login.loginButton')"
outlined
rounded
severity="secondary"
class="text-neutral border-black/50 px-4 capitalize dark-theme:border-white/50 dark-theme:text-white"
class="size-8 border-black/50 bg-transparent text-black hover:bg-[var(--interface-panel-hover-surface)] dark-theme:border-white/50 dark-theme:text-white"
@click="handleSignIn()"
@mouseenter="showPopover"
@mouseleave="hidePopover"
/>
>
<template #icon>
<i class="icon-[lucide--user] size-4" />
</template>
</Button>
<Popover
ref="popoverRef"
class="p-2"

View File

@@ -1,7 +1,7 @@
<template>
<div
ref="containerRef"
class="workflow-tabs-container flex h-full max-w-full flex-auto flex-row overflow-hidden"
class="workflow-tabs-container flex h-full max-w-full flex-auto flex-row overflow-hidden border-b border-[var(--interface-stroke)] shadow-interface"
:class="{ 'workflow-tabs-container-desktop': isDesktop }"
>
<Button

View File

@@ -5,8 +5,7 @@ import {
DEFAULT_DARK_COLOR_PALETTE,
DEFAULT_LIGHT_COLOR_PALETTE
} from '@/constants/coreColorPalettes'
import { tryToggleWidgetPromotion } from '@/core/graph/subgraph/proxyWidgetUtils'
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
import { showSubgraphNodeDialog } from '@/workbench/graph/subgraph/useSubgraphNodeDialog'
import { t } from '@/i18n'
import {
LGraphEventMode,
@@ -22,6 +21,7 @@ import { useSubscription } from '@/platform/cloud/subscription/composables/useSu
import { useSettingStore } from '@/platform/settings/settingStore'
import { SUPPORT_URL } from '@/platform/support/config'
import { useTelemetry } from '@/platform/telemetry'
import type { ExecutionTriggerSource } from '@/platform/telemetry/types'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
@@ -36,6 +36,7 @@ import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
import { useLitegraphService } from '@/services/litegraphService'
import { invokeToggleWidgetPromotion } from '@/services/widgetPromotionHandlers'
import type { ComfyCommand } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useHelpCenterStore } from '@/stores/helpCenterStore'
@@ -466,7 +467,11 @@ export function useCoreCommands(): ComfyCommand[] {
label: 'Queue Prompt',
versionAdded: '1.3.7',
category: 'essentials' as const,
function: async () => {
function: async (metadata?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
@@ -485,7 +490,11 @@ export function useCoreCommands(): ComfyCommand[] {
label: 'Queue Prompt (Front)',
versionAdded: '1.3.7',
category: 'essentials' as const,
function: async () => {
function: async (metadata?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
@@ -503,7 +512,11 @@ export function useCoreCommands(): ComfyCommand[] {
icon: 'pi pi-play',
label: 'Queue Selected Output Nodes',
versionAdded: '1.19.6',
function: async () => {
function: async (metadata?: {
subscribe_to_run?: boolean
trigger_source?: ExecutionTriggerSource
}) => {
useTelemetry()?.trackRunButton(metadata)
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
@@ -526,6 +539,17 @@ export function useCoreCommands(): ComfyCommand[] {
// Get execution IDs for all selected output nodes and their descendants
const executionIds =
getExecutionIdsForSelectedNodes(selectedOutputNodes)
if (executionIds.length === 0) {
toastStore.add({
severity: 'error',
summary: t('toastMessages.failedToQueue'),
detail: t('toastMessages.failedExecutionPathResolution'),
life: 3000
})
return
}
useTelemetry()?.trackWorkflowExecution()
await app.queuePrompt(0, batchCount, executionIds)
}
},
@@ -1003,7 +1027,7 @@ export function useCoreCommands(): ComfyCommand[] {
icon: 'icon-[lucide--arrow-left-right]',
label: 'Toggle promotion of hovered widget',
versionAdded: '1.30.1',
function: tryToggleWidgetPromotion
function: () => invokeToggleWidgetPromotion()
},
{
id: 'Comfy.OpenManagerDialog',

View File

@@ -13,7 +13,7 @@ export function useTemplateFiltering(
const searchQuery = ref('')
const selectedModels = ref<string[]>([])
const selectedUseCases = ref<string[]>([])
const selectedLicenses = ref<string[]>([])
const selectedRunsOn = ref<string[]>([])
const sortBy = ref<
| 'default'
| 'alphabetical'
@@ -63,8 +63,8 @@ export function useTemplateFiltering(
return Array.from(tagSet).sort()
})
const availableLicenses = computed(() => {
return ['Open Source', 'Closed Source (API Nodes)']
const availableRunsOn = computed(() => {
return ['ComfyUI', 'External or Remote API']
})
const debouncedSearchQuery = refDebounced(searchQuery, 50)
@@ -108,22 +108,23 @@ export function useTemplateFiltering(
})
})
const filteredByLicenses = computed(() => {
if (selectedLicenses.value.length === 0) {
const filteredByRunsOn = computed(() => {
if (selectedRunsOn.value.length === 0) {
return filteredByUseCases.value
}
return filteredByUseCases.value.filter((template) => {
// Check if template has API in its tags or name (indicating it's a closed source API node)
const isApiTemplate =
template.tags?.includes('API') ||
template.name?.toLowerCase().includes('api_')
// Use openSource field to determine where template runs
// openSource === false -> External/Remote API
// openSource !== false -> ComfyUI (includes true and undefined)
const isExternalAPI = template.openSource === false
const isComfyUI = template.openSource !== false
return selectedLicenses.value.some((selectedLicense) => {
if (selectedLicense === 'Closed Source (API Nodes)') {
return isApiTemplate
} else if (selectedLicense === 'Open Source') {
return !isApiTemplate
return selectedRunsOn.value.some((selectedRunsOn) => {
if (selectedRunsOn === 'External or Remote API') {
return isExternalAPI
} else if (selectedRunsOn === 'ComfyUI') {
return isComfyUI
}
return false
})
@@ -142,7 +143,7 @@ export function useTemplateFiltering(
}
const sortedTemplates = computed(() => {
const templates = [...filteredByLicenses.value]
const templates = [...filteredByRunsOn.value]
switch (sortBy.value) {
case 'alphabetical':
@@ -195,7 +196,7 @@ export function useTemplateFiltering(
searchQuery.value = ''
selectedModels.value = []
selectedUseCases.value = []
selectedLicenses.value = []
selectedRunsOn.value = []
sortBy.value = 'default'
}
@@ -207,8 +208,8 @@ export function useTemplateFiltering(
selectedUseCases.value = selectedUseCases.value.filter((t) => t !== tag)
}
const removeLicenseFilter = (license: string) => {
selectedLicenses.value = selectedLicenses.value.filter((l) => l !== license)
const removeRunsOnFilter = (runsOn: string) => {
selectedRunsOn.value = selectedRunsOn.value.filter((r) => r !== runsOn)
}
const filteredCount = computed(() => filteredTemplates.value.length)
@@ -220,7 +221,7 @@ export function useTemplateFiltering(
search_query: searchQuery.value || undefined,
selected_models: selectedModels.value,
selected_use_cases: selectedUseCases.value,
selected_licenses: selectedLicenses.value,
selected_runs_on: selectedRunsOn.value,
sort_by: sortBy.value,
filtered_count: filteredCount.value,
total_count: totalCount.value
@@ -229,14 +230,14 @@ export function useTemplateFiltering(
// Watch for filter changes and track them
watch(
[searchQuery, selectedModels, selectedUseCases, selectedLicenses, sortBy],
[searchQuery, selectedModels, selectedUseCases, selectedRunsOn, sortBy],
() => {
// Only track if at least one filter is active (to avoid tracking initial state)
const hasActiveFilters =
searchQuery.value.trim() !== '' ||
selectedModels.value.length > 0 ||
selectedUseCases.value.length > 0 ||
selectedLicenses.value.length > 0 ||
selectedRunsOn.value.length > 0 ||
sortBy.value !== 'default'
if (hasActiveFilters) {
@@ -251,14 +252,14 @@ export function useTemplateFiltering(
searchQuery,
selectedModels,
selectedUseCases,
selectedLicenses,
selectedRunsOn,
sortBy,
// Computed
filteredTemplates,
availableModels,
availableUseCases,
availableLicenses,
availableRunsOn,
filteredCount,
totalCount,
@@ -266,6 +267,6 @@ export function useTemplateFiltering(
resetFilters,
removeModelFilter,
removeUseCaseFilter,
removeLicenseFilter
removeRunsOnFilter
}
}

View File

@@ -1694,21 +1694,22 @@ const ext: ComfyExtension = {
label: 'Convert selected nodes to group node',
icon: 'pi pi-sitemap',
versionAdded: '1.3.17',
function: convertSelectedNodesToGroupNode
function: () => convertSelectedNodesToGroupNode()
},
{
id: 'Comfy.GroupNode.UngroupSelectedGroupNodes',
label: 'Ungroup selected group nodes',
icon: 'pi pi-sitemap',
versionAdded: '1.3.17',
function: ungroupSelectedGroupNodes
function: () => ungroupSelectedGroupNodes()
},
{
id: 'Comfy.GroupNode.ManageGroupNodes',
label: 'Manage group nodes',
icon: 'pi pi-cog',
versionAdded: '1.3.17',
function: manageGroupNodes
function: (...args: unknown[]) =>
manageGroupNodes(args[0] as string | undefined)
}
],
keybindings: [

View File

@@ -2169,7 +2169,7 @@ export class LGraphCanvas
}
}
processMouseDown(e: PointerEvent): void {
processMouseDown(e: MouseEvent): void {
if (
this.dragZoomEnabled &&
e.ctrlKey &&

View File

@@ -693,7 +693,6 @@
"perspective": "منظور"
},
"clearRecording": "مسح التسجيل",
"edgeThreshold": "عتبة الحواف",
"export": "تصدير",
"exportModel": "تصدير النموذج",
"exportRecording": "تصدير التسجيل",
@@ -706,7 +705,6 @@
"materialMode": "وضع المادة",
"materialModes": {
"depth": "العمق",
"lineart": "رسم الخطوط",
"normal": "عادي",
"original": "أصلي",
"wireframe": "إطار سلكي"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "تحميل ثلاثي الأبعاد - حركة",
"inputs": {
"clear": {
},
"height": {
"name": "الارتفاع"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "ملف النموذج"
},
"upload 3d model": {
},
"width": {
"name": "العرض"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "معلومات الكاميرا"
},
"image": {
"name": "الصورة"
},
"model_file": {
"name": "ملف النموذج"
}

View File

@@ -155,10 +155,6 @@
"name": "رؤية الشبكة الابتدائية",
"tooltip": "يتحكم في ظهور الشبكة بشكل افتراضي عند إنشاء عنصر ثلاثي الأبعاد جديد."
},
"Comfy_Load3D_ShowPreview": {
"name": "رؤية المعاينة الابتدائية",
"tooltip": "يتحكم في ظهور شاشة المعاينة بشكل افتراضي عند إنشاء عنصر ثلاثي الأبعاد جديد."
},
"Comfy_Locale": {
"name": "اللغة"
},

View File

@@ -754,7 +754,10 @@
"Image API": "Image API",
"Video API": "Video API",
"LLM API": "LLM API",
"All": "All Templates"
"All": "All Templates",
"Extensions": "Extensions",
"Partner Nodes": "Partner Nodes",
"Generation Type": "Generation Type"
},
"categories": "Categories",
"resetFilters": "Clear Filters",
@@ -766,7 +769,8 @@
"modelFilter": "Model Filter",
"modelsSelected": "{count} Models",
"useCasesSelected": "{count} Use Cases",
"licensesSelected": "{count} Licenses",
"runsOnSelected": "{count} Runs On",
"runsOnFilter": "Runs on",
"resultsCount": "Showing {count} of {total} templates",
"sort": {
"recommended": "Recommended",
@@ -776,6 +780,9 @@
"vramLowToHigh": "VRAM Usage (Low to High)",
"modelSizeLowToHigh": "Model Size (Low to High)",
"default": "Default"
},
"error": {
"templateNotFound": "Template \"{templateName}\" not found"
}
},
"graphCanvasMenu": {
@@ -1472,6 +1479,8 @@
"toastMessages": {
"nothingToQueue": "Nothing to queue",
"pleaseSelectOutputNodes": "Please select output nodes",
"failedToQueue": "Failed to queue",
"failedExecutionPathResolution": "Could not resolve path to selected nodes",
"no3dScene": "No 3D scene to apply texture",
"failedToApplyTexture": "Failed to apply texture",
"no3dSceneToExport": "No 3D scene to export",

View File

@@ -4640,10 +4640,7 @@
},
"height": {
"name": "height"
},
"clear": {},
"upload 3d model": {},
"upload extra resources": {}
}
},
"outputs": {
"0": {
@@ -8805,9 +8802,6 @@
},
"camera_info": {
"name": "camera_info"
},
"image": {
"name": "image"
}
}
},

View File

@@ -170,10 +170,6 @@
"name": "Initial Grid Visibility",
"tooltip": "Controls whether the grid is visible by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation."
},
"Comfy_Load3D_ShowPreview": {
"name": "Initial Preview Visibility",
"tooltip": "Controls whether the preview screen is visible by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation."
},
"Comfy_Locale": {
"name": "Language"
},

View File

@@ -693,7 +693,6 @@
"perspective": "Perspectiva"
},
"clearRecording": "Borrar grabación",
"edgeThreshold": "Umbral de borde",
"export": "Exportar",
"exportModel": "Exportar modelo",
"exportRecording": "Exportar grabación",
@@ -706,7 +705,6 @@
"materialMode": "Modo de material",
"materialModes": {
"depth": "Profundidad",
"lineart": "Dibujo lineal",
"normal": "Normal",
"original": "Original",
"wireframe": "Malla"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "Cargar 3D - Animación",
"inputs": {
"clear": {
},
"height": {
"name": "alto"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "archivo_modelo"
},
"upload 3d model": {
},
"width": {
"name": "ancho"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "camera_info"
},
"image": {
"name": "imagen"
},
"model_file": {
"name": "archivo_modelo"
}

View File

@@ -155,10 +155,6 @@
"name": "Mostrar Cuadrícula",
"tooltip": "Cambiar para mostrar cuadrícula por defecto"
},
"Comfy_Load3D_ShowPreview": {
"name": "Mostrar Previsualización",
"tooltip": "Cambiar para mostrar previsualización por defecto"
},
"Comfy_Locale": {
"name": "Idioma"
},

View File

@@ -693,7 +693,6 @@
"perspective": "Perspective"
},
"clearRecording": "Effacer l'enregistrement",
"edgeThreshold": "Seuil de Bordure",
"export": "Exportation",
"exportModel": "Exportation du modèle",
"exportRecording": "Exporter l'enregistrement",
@@ -706,7 +705,6 @@
"materialMode": "Mode Matériel",
"materialModes": {
"depth": "Profondeur",
"lineart": "Dessin au trait",
"normal": "Normal",
"original": "Original",
"wireframe": "Fil de fer"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "Charger 3D - Animation",
"inputs": {
"clear": {
},
"height": {
"name": "hauteur"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "fichier_modèle"
},
"upload 3d model": {
},
"width": {
"name": "largeur"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "informations_camera"
},
"image": {
"name": "image"
},
"model_file": {
"name": "fichier_modèle"
}

View File

@@ -155,10 +155,6 @@
"name": "Afficher la Grille",
"tooltip": "Basculer pour afficher la grille par défaut"
},
"Comfy_Load3D_ShowPreview": {
"name": "Afficher l'Aperçu",
"tooltip": "Basculer pour afficher l'aperçu par défaut"
},
"Comfy_Locale": {
"name": "Langue"
},

View File

@@ -693,7 +693,6 @@
"perspective": "パースペクティブ"
},
"clearRecording": "録画をクリア",
"edgeThreshold": "エッジ閾値",
"export": "エクスポート",
"exportModel": "モデルをエクスポート",
"exportRecording": "録画をエクスポート",
@@ -706,7 +705,6 @@
"materialMode": "マテリアルモード",
"materialModes": {
"depth": "深度",
"lineart": "線画",
"normal": "ノーマル",
"original": "オリジナル",
"wireframe": "ワイヤーフレーム"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "3D読み込み - アニメーション",
"inputs": {
"clear": {
},
"height": {
"name": "高さ"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "モデルファイル"
},
"upload 3d model": {
},
"width": {
"name": "幅"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "カメラ情報"
},
"image": {
"name": "画像"
},
"model_file": {
"name": "モデルファイル"
}

View File

@@ -155,10 +155,6 @@
"name": "グリッドを表示",
"tooltip": "デフォルトでグリッドを表示するには切り替えます"
},
"Comfy_Load3D_ShowPreview": {
"name": "プレビューを表示",
"tooltip": "デフォルトでプレビューを表示するには切り替えます"
},
"Comfy_Locale": {
"name": "言語"
},

View File

@@ -693,7 +693,6 @@
"perspective": "원근법"
},
"clearRecording": "녹화 지우기",
"edgeThreshold": "엣지 임계값",
"export": "내보내기",
"exportModel": "모델 내보내기",
"exportRecording": "녹화 내보내기",
@@ -706,7 +705,6 @@
"materialMode": "재질 모드",
"materialModes": {
"depth": "깊이",
"lineart": "라인아트",
"normal": "노멀(normal)",
"original": "원본",
"wireframe": "와이어프레임"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "3D 불러오기 - 애니메이션",
"inputs": {
"clear": {
},
"height": {
"name": "높이"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "모델 파일"
},
"upload 3d model": {
},
"width": {
"name": "너비"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "카메라 정보"
},
"image": {
"name": "이미지"
},
"model_file": {
"name": "모델 파일"
}

View File

@@ -155,10 +155,6 @@
"name": "그리드 표시",
"tooltip": "기본적으로 그리드를 표시하도록 전환"
},
"Comfy_Load3D_ShowPreview": {
"name": "미리보기 표시",
"tooltip": "기본적으로 미리보기를 표시하도록 전환"
},
"Comfy_Locale": {
"name": "언어"
},

View File

@@ -693,7 +693,6 @@
"perspective": "Перспектива"
},
"clearRecording": "Очистить запись",
"edgeThreshold": "Пороговое значение края",
"export": "Экспорт",
"exportModel": "Экспорт модели",
"exportRecording": "Экспортировать запись",
@@ -706,7 +705,6 @@
"materialMode": "Режим Материала",
"materialModes": {
"depth": "Глубина",
"lineart": "Лайнарт",
"normal": "Нормальный",
"original": "Оригинал",
"wireframe": "Каркас"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "Загрузить 3D - Анимация",
"inputs": {
"clear": {
},
"height": {
"name": "высота"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": айл_модели"
},
"upload 3d model": {
},
"width": {
"name": "ширина"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "camera_info"
},
"image": {
"name": "изображение"
},
"model_file": {
"name": айл_модели"
}

View File

@@ -155,10 +155,6 @@
"name": "Показать сетку",
"tooltip": "Переключиться, чтобы показывать сетку по умолчанию"
},
"Comfy_Load3D_ShowPreview": {
"name": "Показать предварительный просмотр",
"tooltip": "Переключиться, чтобы показывать предварительный просмотр по умолчанию"
},
"Comfy_Locale": {
"name": "Язык"
},

View File

@@ -693,7 +693,6 @@
"perspective": "Perspektif"
},
"clearRecording": "Kaydı Temizle",
"edgeThreshold": "Kenar Eşiği",
"export": "Dışa Aktar",
"exportModel": "Modeli Dışa Aktar",
"exportRecording": "Kaydı Dışa Aktar",
@@ -706,7 +705,6 @@
"materialMode": "Malzeme Modu",
"materialModes": {
"depth": "Derinlik",
"lineart": "Çizgi Sanatı",
"normal": "Normal",
"original": "Orijinal",
"wireframe": "Tel Kafes"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "3D Yükle - Animasyon",
"inputs": {
"clear": {
},
"height": {
"name": "yükseklik"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "model_dosyası"
},
"upload 3d model": {
},
"width": {
"name": "genişlik"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "kamera_bilgisi"
},
"image": {
"name": "görüntü"
},
"model_file": {
"name": "model_dosyası"
}

View File

@@ -155,10 +155,6 @@
"name": "Başlangıç Izgara Görünürlüğü",
"tooltip": "Yeni bir 3D widget oluşturulduğunda ızgaranın varsayılan olarak görünür olup olmadığını kontrol eder. Bu varsayılan, oluşturulduktan sonra her widget için ayrı ayrı değiştirilebilir."
},
"Comfy_Load3D_ShowPreview": {
"name": "Başlangıç Önizleme Görünürlüğü",
"tooltip": "Yeni bir 3D widget oluşturulduğunda önizleme ekranının varsayılan olarak görünür olup olmadığını kontrol eder. Bu varsayılan, oluşturulduktan sonra her widget için ayrı ayrı değiştirilebilir."
},
"Comfy_Locale": {
"name": "Dil"
},

View File

@@ -693,7 +693,6 @@
"perspective": "透視"
},
"clearRecording": "清除錄影",
"edgeThreshold": "邊緣閾值",
"export": "匯出",
"exportModel": "匯出模型",
"exportRecording": "匯出錄影",
@@ -706,7 +705,6 @@
"materialMode": "材質模式",
"materialModes": {
"depth": "深度",
"lineart": "線條藝術",
"normal": "一般",
"original": "原始",
"wireframe": "線框"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "載入 3D - 動畫",
"inputs": {
"clear": {
},
"height": {
"name": "高度"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "模型檔案"
},
"upload 3d model": {
},
"width": {
"name": "寬度"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "camera_info"
},
"image": {
"name": "影像"
},
"model_file": {
"name": "model_file"
}

View File

@@ -155,10 +155,6 @@
"name": "初始網格可見性",
"tooltip": "控制在建立新的 3D 元件時,網格是否預設可見。此預設值在建立後仍可針對每個元件單獨切換。"
},
"Comfy_Load3D_ShowPreview": {
"name": "初始預覽可見性",
"tooltip": "控制當新建 3D 元件時,預覽畫面預設是否顯示。此預設值在元件建立後仍可針對每個元件單獨切換。"
},
"Comfy_Locale": {
"name": "語言"
},

View File

@@ -693,7 +693,6 @@
"perspective": "透视"
},
"clearRecording": "清除录制",
"edgeThreshold": "边缘阈值",
"export": "导出",
"exportModel": "导出模型",
"exportRecording": "导出录制",
@@ -706,7 +705,6 @@
"materialMode": "材质模式",
"materialModes": {
"depth": "深度",
"lineart": "线稿",
"normal": "法线",
"original": "原始",
"wireframe": "线框"

View File

@@ -3361,8 +3361,6 @@
"Load3DAnimation": {
"display_name": "加载3D动画",
"inputs": {
"clear": {
},
"height": {
"name": "高度"
},
@@ -3372,8 +3370,6 @@
"model_file": {
"name": "模型文件"
},
"upload 3d model": {
},
"width": {
"name": "宽度"
}
@@ -6352,9 +6348,6 @@
"camera_info": {
"name": "相机信息"
},
"image": {
"name": "图像"
},
"model_file": {
"name": "模型文件"
}

View File

@@ -155,10 +155,6 @@
"name": "显示网格",
"tooltip": "默认显示网格开关"
},
"Comfy_Load3D_ShowPreview": {
"name": "显示预览",
"tooltip": "默认显示预览开关"
},
"Comfy_Locale": {
"name": "语言"
},

View File

@@ -24,18 +24,19 @@
<div>
<h3
:id="titleId"
v-tooltip.top="{ value: asset.name, showDelay: tooltipDelay }"
:class="
cn(
'mb-2 m-0 text-base font-semibold line-clamp-2 wrap-anywhere',
'text-base-foreground'
)
"
:title="asset.name"
>
{{ asset.name }}
</h3>
<p
:id="descId"
v-tooltip.top="{ value: asset.description, showDelay: tooltipDelay }"
:class="
cn(
'm-0 text-sm leading-6 overflow-hidden [-webkit-box-orient:vertical] [-webkit-line-clamp:2] [display:-webkit-box]',
@@ -43,7 +44,6 @@
'dark-theme:text-slate-100'
)
"
:title="asset.description"
>
{{ asset.description }}
</p>
@@ -76,6 +76,7 @@ import { computed, useId } from 'vue'
import AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue'
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
import { useSettingStore } from '@/platform/settings/settingStore'
import { cn } from '@/utils/tailwindUtil'
const props = defineProps<{
@@ -83,9 +84,15 @@ const props = defineProps<{
interactive?: boolean
}>()
const settingStore = useSettingStore()
const titleId = useId()
const descId = useId()
const tooltipDelay = computed<number>(() =>
settingStore.get('LiteGraph.Node.TooltipDelay')
)
const { error } = useImage({
src: props.asset.preview_url ?? '',
alt: props.asset.name

Some files were not shown because too many files have changed in this diff Show More