Skip to content

Conversation

@jtydhr88
Copy link
Collaborator

@jtydhr88 jtydhr88 commented Jan 17, 2026

Summary

add thrumbnail for 3d genations, feature requested by @PabloWiedemann

Screenshots

2026-01-17.13-47-50.mp4

┆Issue is synchronized with this Notion page by Unito

@jtydhr88 jtydhr88 requested a review from a team as a code owner January 17, 2026 18:52
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jan 17, 2026
@github-actions
Copy link

github-actions bot commented Jan 17, 2026

🎭 Playwright Tests: ⚠️ Passed with flaky tests

Results: 503 passed, 0 failed, 2 flaky, 8 skipped (Total: 513)

❌ Failed Tests

📊 Browser Reports
  • chromium: View Report (✅ 493 / ❌ 0 / ⚠️ 1 / ⏭️ 8)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 7 / ❌ 0 / ⚠️ 1 / ⏭️ 0)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

Adds automatic thumbnail capture/upload for 3D models: a new Load3d.captureThumbnail method, Load3dUtils helpers to check/generate/upload thumbnails, triggers to run generation after model load (useLoad3d and saveMesh), and UI changes to show PNG thumbnails with a fallback placeholder.

Changes

Cohort / File(s) Summary
Load3d core
src/extensions/core/load3d/Load3d.ts
Adds public async captureThumbnail(width = 256, height = 256): Promise<string> that frames the model, captures the scene, and restores camera/grid state.
Thumbnail utilities
src/extensions/core/load3d/Load3dUtils.ts
Adds generateThumbnailIfNeeded(load3d, modelPath, folderType), getThumbnailFilename(), fileExists(...) (HEAD check), and uploadThumbnail(...) (POST /upload/image with FormData).
Load lifecycle integration
src/composables/useLoad3d.ts, src/extensions/core/saveMesh.ts
Calls Load3dUtils.generateThumbnailIfNeeded(...) after modelLoadingEnd in both the composable and saveMesh flow to schedule thumbnail generation for preview/output contexts.
Thumbnail display
src/platform/assets/components/Media3DTop.vue
Renders an <img> for asset thumbnails (computed URL replacing filename with .png) with load-error handling that falls back to a placeholder block; adds asset prop.

Sequence Diagram(s)

sequenceDiagram
    participant Node as Model Node
    participant Composable as useLoad3d / saveMesh hook
    participant Load3d as Load3d Instance
    participant Utils as Load3dUtils
    participant API as Backend API

    Node->>Composable: modelLoadingEnd(modelPath, isPreview)
    Composable->>Utils: generateThumbnailIfNeeded(load3d, modelPath, folderType)
    Utils->>API: HEAD /<subfolder>/<filename>.png
    API-->>Utils: 200/404
    alt not exists
        Utils->>Load3d: captureThumbnail(256,256)
        activate Load3d
        Load3d->>Load3d: save camera/grid state
        Load3d->>Load3d: position camera & capture scene -> imageData
        Load3d->>Load3d: restore camera/grid state
        deactivate Load3d
        Utils->>API: POST /upload/image (FormData with image, subfolder, type)
        API-->>Utils: 200 OK
    else exists
        Utils-->>Composable: skip upload
    end
    Utils-->>Composable: done
Loading

Possibly related PRs

Suggested reviewers

  • shinshin86
  • PabloWiedemann
  • Yorha4D
  • KarryCharon
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 01/18/2026, 03:14:18 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

Bundle Size Report

Summary

  • Raw size: 19.1 MB baseline 19.1 MB — 🔴 +2.91 kB
  • Gzip: 4.05 MB baseline 4.05 MB — 🔴 +738 B
  • Brotli: 3.09 MB baseline 3.09 MB — 🔴 +739 B
  • Bundles: 155 current • 155 baseline • 66 added / 66 removed

Category Glance
Data & Services 🔴 +2.07 kB (2.75 MB) · Other 🔴 +838 B (5.91 MB) · Vendor & Third-Party ⚪ 0 B (9.09 MB) · Graph Workspace ⚪ 0 B (878 kB) · Panels & Settings ⚪ 0 B (385 kB) · Views & Navigation ⚪ 0 B (66.6 kB) · + 5 more

Per-category breakdown
App Entry Points — 18.1 kB (baseline 18.1 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CBpqXZjo.js (removed) 18.1 kB 🟢 -18.1 kB 🟢 -6 kB 🟢 -5.25 kB
assets/index-eMO74yBW.js (new) 18.1 kB 🔴 +18.1 kB 🔴 +6.01 kB 🔴 +5.25 kB

Status: 1 added / 1 removed

Graph Workspace — 878 kB (baseline 878 kB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-Bf-UXmz-.js (new) 878 kB 🔴 +878 kB 🔴 +183 kB 🔴 +141 kB
assets/GraphView-kMCeD4f_.js (removed) 878 kB 🟢 -878 kB 🟢 -183 kB 🟢 -141 kB

Status: 1 added / 1 removed

Views & Navigation — 66.6 kB (baseline 66.6 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BhALHUcS.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.37 kB 🟢 -2.86 kB
assets/CloudSurveyView-C_M6y9Gs.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.37 kB 🔴 +2.85 kB
assets/CloudLoginView-C2O-wnfS.js (new) 9.75 kB 🔴 +9.75 kB 🔴 +2.83 kB 🔴 +2.46 kB
assets/CloudLoginView-DMnimPfi.js (removed) 9.75 kB 🟢 -9.75 kB 🟢 -2.82 kB 🟢 -2.47 kB
assets/UserCheckView-BATgKCei.js (removed) 8.2 kB 🟢 -8.2 kB 🟢 -2.15 kB 🟢 -1.86 kB
assets/UserCheckView-dF4p3kup.js (new) 8.2 kB 🔴 +8.2 kB 🔴 +2.14 kB 🔴 +1.86 kB
assets/CloudSignupView-B04_MuJ1.js (new) 6.96 kB 🔴 +6.96 kB 🔴 +2.16 kB 🔴 +1.88 kB
assets/CloudSignupView-DGDTW6Au.js (removed) 6.96 kB 🟢 -6.96 kB 🟢 -2.16 kB 🟢 -1.89 kB
assets/CloudLayoutView-BOx_7Dji.js (removed) 6.18 kB 🟢 -6.18 kB 🟢 -2.02 kB 🟢 -1.75 kB
assets/CloudLayoutView-CqZALYBW.js (new) 6.18 kB 🔴 +6.18 kB 🔴 +2.02 kB 🔴 +1.75 kB
assets/CloudForgotPasswordView-25W9NZou.js (removed) 5.31 kB 🟢 -5.31 kB 🟢 -1.85 kB 🟢 -1.61 kB
assets/CloudForgotPasswordView-DyJ1gxOn.js (new) 5.31 kB 🔴 +5.31 kB 🔴 +1.85 kB 🔴 +1.61 kB
assets/CloudAuthTimeoutView-CmjTC4O8.js (new) 4.71 kB 🔴 +4.71 kB 🔴 +1.69 kB 🔴 +1.47 kB
assets/CloudAuthTimeoutView-Dz-cTxwK.js (removed) 4.71 kB 🟢 -4.71 kB 🟢 -1.69 kB 🟢 -1.47 kB
assets/CloudSubscriptionRedirectView-4zoAXIZz.js (new) 4.37 kB 🔴 +4.37 kB 🔴 +1.65 kB 🔴 +1.46 kB
assets/CloudSubscriptionRedirectView-DrMSPXmX.js (removed) 4.37 kB 🟢 -4.37 kB 🟢 -1.65 kB 🟢 -1.45 kB
assets/UserSelectView-BpS3aKrP.js (new) 4.34 kB 🔴 +4.34 kB 🔴 +1.56 kB 🔴 +1.4 kB
assets/UserSelectView-Hj_2gMOC.js (removed) 4.34 kB 🟢 -4.34 kB 🟢 -1.57 kB 🟢 -1.4 kB
assets/CloudSorryContactSupportView-Bq5r0bm1.js 958 B 958 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-DPXamnO6.js 296 B 296 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

Panels & Settings — 385 kB (baseline 385 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/LegacyCreditsPanel-Dy8O1wmv.js (new) 20.5 kB 🔴 +20.5 kB 🔴 +5.5 kB 🔴 +4.81 kB
assets/LegacyCreditsPanel-sYfZipCP.js (removed) 20.5 kB 🟢 -20.5 kB 🟢 -5.49 kB 🟢 -4.81 kB
assets/SubscriptionPanel-42U8ZpAC.js (new) 18.2 kB 🔴 +18.2 kB 🔴 +4.62 kB 🔴 +4.06 kB
assets/SubscriptionPanel-C_kti8kQ.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.62 kB 🟢 -4.05 kB
assets/KeybindingPanel-B6FRUD0T.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.5 kB 🔴 +3.08 kB
assets/KeybindingPanel-DdSIWbvw.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.49 kB 🟢 -3.07 kB
assets/ExtensionPanel-BP3dq7a6.js (removed) 9.18 kB 🟢 -9.18 kB 🟢 -2.57 kB 🟢 -2.28 kB
assets/ExtensionPanel-Dxt2n4Zv.js (new) 9.18 kB 🔴 +9.18 kB 🔴 +2.57 kB 🔴 +2.29 kB
assets/AboutPanel-Cs9MJQA7.js (removed) 8.26 kB 🟢 -8.26 kB 🟢 -2.31 kB 🟢 -2.08 kB
assets/AboutPanel-rWWp3XVm.js (new) 8.26 kB 🔴 +8.26 kB 🔴 +2.31 kB 🔴 +2.08 kB
assets/ServerConfigPanel-DCzBekGo.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -2.03 kB 🟢 -1.82 kB
assets/ServerConfigPanel-DZXkq_t7.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +2.03 kB 🔴 +1.81 kB
assets/UserPanel-CC91hxUs.js (new) 5.92 kB 🔴 +5.92 kB 🔴 +1.84 kB 🔴 +1.62 kB
assets/UserPanel-mp3jKC1d.js (removed) 5.92 kB 🟢 -5.92 kB 🟢 -1.84 kB 🟢 -1.62 kB
assets/cloudRemoteConfig-C5020Gja.js (removed) 1.71 kB 🟢 -1.71 kB 🟢 -817 B 🟢 -702 B
assets/cloudRemoteConfig-CICT-nVM.js (new) 1.71 kB 🔴 +1.71 kB 🔴 +818 B 🔴 +705 B
assets/remoteConfig-BUDSMhNf.js 180 B 180 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/remoteConfig-ChJ6chsD.js 842 B 842 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B4tKvhju.js 25.9 kB 25.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CaRugk0T.js 31.4 kB 31.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Cc_5ZmNN.js 22.9 kB 22.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CGqeatFl.js 25.7 kB 25.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CmdWWupL.js 26.7 kB 26.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CUG7K3Yo.js 22.3 kB 22.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-D-sRfyAY.js 28.3 kB 28.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DiaIkXsJ.js 27.5 kB 27.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DNazWeKW.js 26.6 kB 26.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Dx2hk4FX.js 35.4 kB 35.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-NGLy8ZAc.js 29.2 kB 29.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 8 added / 8 removed

User & Accounts — 4.22 kB (baseline 4.22 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-BbKwXm9a.js (removed) 3.29 kB 🟢 -3.29 kB 🟢 -1.14 kB 🟢 -955 B
assets/auth-BRmBA4eb.js (new) 3.29 kB 🔴 +3.29 kB 🔴 +1.13 kB 🔴 +960 B
assets/firebaseAuthStore-8dqZlUJw.js (removed) 638 B 🟢 -638 B 🟢 -327 B 🟢 -292 B
assets/firebaseAuthStore-CgZMPEya.js (new) 638 B 🔴 +638 B 🔴 +330 B 🔴 +293 B
assets/auth-BKE5qzXr.js (removed) 288 B 🟢 -288 B 🟢 -193 B 🟢 -179 B
assets/auth-C7i4PMqs.js (new) 288 B 🔴 +288 B 🔴 +191 B 🔴 +179 B

Status: 3 added / 3 removed

Editors & Dialogs — 2.85 kB (baseline 2.85 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-Ae05A2oS.js (removed) 2.22 kB 🟢 -2.22 kB 🟢 -1.13 kB 🟢 -1.01 kB
assets/useSubscriptionDialog-TwstDWZn.js (new) 2.22 kB 🔴 +2.22 kB 🔴 +1.14 kB 🔴 +1.02 kB
assets/useSubscriptionDialog-DYfAU5OF.js (removed) 631 B 🟢 -631 B 🟢 -328 B 🟢 -290 B
assets/useSubscriptionDialog-qZmL_7xC.js (new) 631 B 🔴 +631 B 🔴 +330 B 🔴 +291 B

Status: 2 added / 2 removed

UI Components — 26.3 kB (baseline 26.3 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SubscribeButton-BTqJ4_Vw.js (new) 10.7 kB 🔴 +10.7 kB 🔴 +2.73 kB 🔴 +2.42 kB
assets/SubscribeButton-CPioVHpm.js (removed) 10.7 kB 🟢 -10.7 kB 🟢 -2.73 kB 🟢 -2.44 kB
assets/ComfyQueueButton-26qzsB7V.js (new) 7.07 kB 🔴 +7.07 kB 🔴 +2.28 kB 🔴 +2.04 kB
assets/ComfyQueueButton-Bv6DB8vQ.js (removed) 7.07 kB 🟢 -7.07 kB 🟢 -2.28 kB 🟢 -2.04 kB
assets/CloudBadge-BycW9eNW.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -570 B 🟢 -492 B
assets/CloudBadge-D5NhsgoW.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +572 B 🔴 +495 B
assets/cloudFeedbackTopbarButton-Cq3HHlmB.js (new) 1.09 kB 🔴 +1.09 kB 🔴 +633 B 🔴 +545 B
assets/cloudFeedbackTopbarButton-DhgAhflV.js (removed) 1.09 kB 🟢 -1.09 kB 🟢 -632 B 🟢 -540 B
assets/ComfyQueueButton-C_VbpgVn.js (removed) 643 B 🟢 -643 B 🟢 -334 B 🟢 -306 B
assets/ComfyQueueButton-x1TTplQN.js (new) 643 B 🔴 +643 B 🔴 +336 B 🔴 +309 B
assets/Button-DXbPXIj1.js 2.86 kB 2.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-CRJy-LDw.js 1.11 kB 1.11 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-CKnjYR8V.js 1.71 kB 1.71 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 2.75 MB (baseline 2.75 MB) • 🔴 +2.07 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-CGaqb88m.js (new) 1.65 MB 🔴 +1.65 MB 🔴 +367 kB 🔴 +283 kB
assets/dialogService-CeI4DUvp.js (removed) 1.64 MB 🟢 -1.64 MB 🟢 -367 kB 🟢 -282 kB
assets/api-DIcVNlGV.js (removed) 1.08 MB 🟢 -1.08 MB 🟢 -228 kB 🟢 -180 kB
assets/api-DhhYx0ns.js (new) 1.08 MB 🔴 +1.08 MB 🔴 +228 kB 🔴 +180 kB
assets/releaseStore-BH2I_B1c.js (new) 7.91 kB 🔴 +7.91 kB 🔴 +2.16 kB 🔴 +1.89 kB
assets/releaseStore-yfJDR06X.js (removed) 7.91 kB 🟢 -7.91 kB 🟢 -2.16 kB 🟢 -1.9 kB
assets/keybindingService-CJIJTqvo.js (removed) 6.04 kB 🟢 -6.04 kB 🟢 -1.6 kB 🟢 -1.38 kB
assets/keybindingService-Z7_7tBYi.js (new) 6.04 kB 🔴 +6.04 kB 🔴 +1.6 kB 🔴 +1.38 kB
assets/userStore-CL74Of-Z.js (new) 1.83 kB 🔴 +1.83 kB 🔴 +704 B 🔴 +617 B
assets/userStore-Dh4DKd67.js (removed) 1.83 kB 🟢 -1.83 kB 🟢 -704 B 🟢 -658 B
assets/audioService-D72vge49.js (new) 1.66 kB 🔴 +1.66 kB 🔴 +818 B 🔴 +708 B
assets/audioService-DgijO8Ir.js (removed) 1.66 kB 🟢 -1.66 kB 🟢 -818 B 🟢 -706 B
assets/releaseStore-CcsM21C1.js (removed) 610 B 🟢 -610 B 🟢 -326 B 🟢 -280 B
assets/releaseStore-lAPpkmH_.js (new) 610 B 🔴 +610 B 🔴 +327 B 🔴 +283 B
assets/serverConfigStore-D6d48Q0y.js 2.3 kB 2.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 7 added / 7 removed

Utilities & Hooks — 16.6 kB (baseline 16.6 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useErrorHandling-BzJwiehh.js (removed) 4.51 kB 🟢 -4.51 kB 🟢 -1.32 kB 🟢 -1.15 kB
assets/useErrorHandling-DEK181F3.js (new) 4.51 kB 🔴 +4.51 kB 🔴 +1.32 kB 🔴 +1.15 kB
assets/subscriptionCheckoutUtil-Co-_oDei.js (removed) 1.63 kB 🟢 -1.63 kB 🟢 -736 B 🟢 -650 B
assets/subscriptionCheckoutUtil-DXafUnXU.js (new) 1.63 kB 🔴 +1.63 kB 🔴 +737 B 🔴 +649 B
assets/audioUtils-D3o_jYiT.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +536 B 🔴 +446 B
assets/audioUtils-Dx6EYOWJ.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -537 B 🟢 -449 B
assets/useCurrentUser-BmtpcwLz.js (removed) 572 B 🟢 -572 B 🟢 -313 B 🟢 -268 B
assets/useCurrentUser-CzAIqSYQ.js (new) 572 B 🔴 +572 B 🔴 +315 B 🔴 +271 B
assets/_plugin-vue_export-helper-DkXvIbNw.js 218 B 218 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-Cvq9baIq.js 6.89 kB 6.89 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-BgHJHHDI.js 1.49 kB 1.49 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeFilterUtil-DTOFbnOf.js 242 B 242 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 4 added / 4 removed

Vendor & Third-Party — 9.09 MB (baseline 9.09 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-chart-vHYiiHjX.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-bLc542qk.js 4.34 MB 4.34 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-Dn50BkoY.js 1.71 MB 1.71 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-Dfa--nkF.js 97.9 kB 97.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-ROpDBAgW.js 1.84 MB 1.84 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-S96Ju_Nb.js 185 kB 185 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-B8djomGx.js 131 kB 131 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-C9PB687x.js 397 kB 397 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 5.91 MB (baseline 5.91 MB) • 🔴 +838 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-CE9_-7tg.js (new) 165 kB 🔴 +165 kB 🔴 +40.5 kB 🔴 +34.2 kB
assets/core-3gNEr0P-.js (removed) 165 kB 🟢 -165 kB 🟢 -40.4 kB 🟢 -34.1 kB
assets/Load3D-BmT6iWrl.js (removed) 46.7 kB 🟢 -46.7 kB 🟢 -8.16 kB 🟢 -7.12 kB
assets/Load3D-BOo_66vZ.js (new) 46.7 kB 🔴 +46.7 kB 🔴 +8.16 kB 🔴 +7.12 kB
assets/WidgetSelect-Bjdh78C9.js (removed) 42.4 kB 🟢 -42.4 kB 🟢 -9.97 kB 🟢 -8.8 kB
assets/WidgetSelect-Bq6diHzI.js (new) 42.4 kB 🔴 +42.4 kB 🔴 +9.97 kB 🔴 +8.81 kB
assets/SubscriptionRequiredDialogContent-2ssZxFuo.js (new) 25.2 kB 🔴 +25.2 kB 🔴 +6.26 kB 🔴 +5.48 kB
assets/SubscriptionRequiredDialogContent-B_BhczUN.js (removed) 25.2 kB 🟢 -25.2 kB 🟢 -6.26 kB 🟢 -5.48 kB
assets/WidgetRecordAudio-BqRG0dzC.js (removed) 16.9 kB 🟢 -16.9 kB 🟢 -4.81 kB 🟢 -4.3 kB
assets/WidgetRecordAudio-Ct-G85nU.js (new) 16.9 kB 🔴 +16.9 kB 🔴 +4.8 kB 🔴 +4.3 kB
assets/WidgetImageCrop-Bme-hrDg.js (removed) 16.5 kB 🟢 -16.5 kB 🟢 -4.09 kB 🟢 -3.58 kB
assets/WidgetImageCrop-CqWvso7u.js (new) 16.5 kB 🔴 +16.5 kB 🔴 +4.09 kB 🔴 +3.58 kB
assets/PanelTemplate-bD1zgeUp.js (new) 14.5 kB 🔴 +14.5 kB 🔴 +5.09 kB 🔴 +4.47 kB
assets/PanelTemplate-BvD15vtb.js (removed) 14.5 kB 🟢 -14.5 kB 🟢 -5.1 kB 🟢 -4.47 kB
assets/WidgetInputNumber-DYTuaTYM.js (new) 14.2 kB 🔴 +14.2 kB 🔴 +3.86 kB 🔴 +3.43 kB
assets/WidgetInputNumber-ZLoLIFhP.js (removed) 14.2 kB 🟢 -14.2 kB 🟢 -3.86 kB 🟢 -3.44 kB
assets/AudioPreviewPlayer-B-jcizAz.js (new) 11 kB 🔴 +11 kB 🔴 +3.12 kB 🔴 +2.81 kB
assets/AudioPreviewPlayer-WHS89ZBV.js (removed) 11 kB 🟢 -11 kB 🟢 -3.12 kB 🟢 -2.81 kB
assets/ValueControlPopover-CmH61zbu.js (new) 4.87 kB 🔴 +4.87 kB 🔴 +1.72 kB 🔴 +1.53 kB
assets/ValueControlPopover-DXeI3if7.js (removed) 4.87 kB 🟢 -4.87 kB 🟢 -1.71 kB 🟢 -1.53 kB
assets/WidgetWithControl-BFJrEEvE.js (new) 3.62 kB 🔴 +3.62 kB 🔴 +1.59 kB 🔴 +1.44 kB
assets/WidgetWithControl-DnxPh4dj.js (removed) 3.62 kB 🟢 -3.62 kB 🟢 -1.59 kB 🟢 -1.44 kB
assets/WidgetAudioUI-CcsUSTmt.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -1.27 kB 🟢 -1.13 kB
assets/WidgetAudioUI-RuQrESUN.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +1.27 kB 🔴 +1.13 kB
assets/cloudSessionCookie-BzJnor1W.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -982 B 🟢 -860 B
assets/cloudSessionCookie-kJ7xB6WI.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +987 B 🔴 +860 B
assets/GlobalToast-BHV6GeIF.js (new) 2.28 kB 🔴 +2.28 kB 🔴 +939 B 🔴 +786 B
assets/GlobalToast-CF6vVGMo.js (removed) 2.28 kB 🟢 -2.28 kB 🟢 -938 B 🟢 -782 B
assets/SubscribeToRun-CegI6Exn.js (new) 2.05 kB 🔴 +2.05 kB 🔴 +952 B 🔴 +826 B
assets/SubscribeToRun-sACHUpED.js (removed) 2.05 kB 🟢 -2.05 kB 🟢 -952 B 🟢 -829 B
assets/Media3DTop-lnE1c3Rz.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +865 B 🔴 +733 B
assets/BaseViewTemplate-C6_6SWh0.js (new) 1.69 kB 🔴 +1.69 kB 🔴 +884 B 🔴 +806 B
assets/BaseViewTemplate-DKgY86Zo.js (removed) 1.69 kB 🟢 -1.69 kB 🟢 -888 B 🟢 -804 B
assets/CloudRunButtonWrapper-D3Pl5gN4.js (new) 1.42 kB 🔴 +1.42 kB 🔴 +682 B 🔴 +591 B
assets/CloudRunButtonWrapper-D4LQiEPT.js (removed) 1.42 kB 🟢 -1.42 kB 🟢 -679 B 🟢 -589 B
assets/Media3DTop-Dxtqlb1m.js (removed) 1.18 kB 🟢 -1.18 kB 🟢 -626 B 🟢 -514 B
assets/cloudBadges-B3E1Un17.js (new) 1.12 kB 🔴 +1.12 kB 🔴 +598 B 🔴 +514 B
assets/cloudBadges-cVQ5UCts.js (removed) 1.12 kB 🟢 -1.12 kB 🟢 -596 B 🟢 -513 B
assets/cloudSubscription-BFFh8dI-.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -553 B 🟢 -468 B
assets/cloudSubscription-BIonIJ2G.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +555 B 🔴 +472 B
assets/WidgetSelect-BX8ynswj.js (removed) 779 B 🟢 -779 B 🟢 -404 B 🟢 -348 B
assets/WidgetSelect-CEpqBpjc.js (new) 779 B 🔴 +779 B 🔴 +402 B 🔴 +346 B
assets/graphHasMissingNodes-B3jqA9d_.js (removed) 696 B 🟢 -696 B 🟢 -341 B 🟢 -285 B
assets/graphHasMissingNodes-BgnZgiqk.js (new) 696 B 🔴 +696 B 🔴 +338 B 🔴 +292 B
assets/AudioPreviewPlayer-DcvLUj1M.js (new) 684 B 🔴 +684 B 🔴 +353 B 🔴 +322 B
assets/AudioPreviewPlayer-rzbk3Xj3.js (removed) 684 B 🟢 -684 B 🟢 -354 B 🟢 -314 B
assets/Load3D-CFVoXpqL.js (new) 613 B 🔴 +613 B 🔴 +329 B 🔴 +291 B
assets/Load3D-DSfsxWW_.js (removed) 613 B 🟢 -613 B 🟢 -328 B 🟢 -287 B
assets/WidgetLegacy-Ceh95e-R.js (new) 595 B 🔴 +595 B 🔴 +327 B 🔴 +282 B
assets/WidgetLegacy-OxIl5HD8.js (removed) 595 B 🟢 -595 B 🟢 -324 B 🟢 -279 B
assets/WidgetInputNumber-BCD1UrEa.js (removed) 353 B 🟢 -353 B 🟢 -215 B 🟢 -196 B
assets/WidgetInputNumber-DgHGlJaz.js (new) 353 B 🔴 +353 B 🔴 +217 B 🔴 +195 B
assets/auto-C5ZGWZyn.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-7Rm2ilks.js 16 kB 16 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B2T0Tiqb.js 18 kB 18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BfbWPTP5.js 14.4 kB 14.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Bk5Hy71M.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BKiU7Msm.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C1rCoJIk.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C7GUJ-mt.js 14.6 kB 14.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CdWPfhpt.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CVH8YfJ3.js 16.8 kB 16.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DEW8TY9B.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DSsDsJlz.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/LazyImage-HSfoff1Z.js 12.1 kB 12.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BcGurlLt.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Brh7PtUL.js 130 kB 130 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-By3kkO70.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CMwYySnf.js 117 kB 117 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CukDmnQ9.js 100 kB 100 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DdC_qgWU.js 120 kB 120 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DEiGPqKr.js 115 kB 115 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DZsqHcbk.js 101 kB 101 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-sAR6lZ5U.js 157 kB 157 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-WFOApOr_.js 137 kB 137 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-WlYSOVfg.js 141 kB 141 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-D8F3P6nT.js 1.37 kB 1.37 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-C2q25E_R.js 1.64 kB 1.64 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-BekXmdBL.js 2.19 kB 2.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/mixpanel.module-CB67EPbl.js 92 B 92 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-a476Kb1a.js 339 kB 339 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BoysRuhG.js 352 kB 352 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C1DGym0y.js 310 kB 310 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CFwsX_m-.js 381 kB 381 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CiPSzZ9E.js 339 kB 339 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-COhYgldT.js 343 kB 343 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D1eJ3MWg.js 313 kB 313 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D5AbIjlp.js 336 kB 336 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Dj0hpilf.js 415 kB 415 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Dm25Ay8M.js 381 kB 381 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-h4f9Qpyk.js 333 kB 333 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/preservedQueryNamespaces-8IvUKrEM.js 2.7 kB 2.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-BVSCtPLU.js 603 B 603 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-uYyVBMFy.js 1.34 kB 1.34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-hv4kjqwF.js 3.42 kB 3.42 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-DWRIFAAP.js 191 B 191 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-B5EDHllb.js 383 B 383 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-BMqKTQRe.js 168 B 168 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-U1eyr0w3.js 3.89 kB 3.89 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-BEyqUpBQ.js 2.15 kB 2.15 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-B9aMCXIf.js 2.81 kB 2.81 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria--Zrc3dqq.js 3.55 kB 3.55 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-lotcrt8e.js 2.99 kB 2.99 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-BLNJPSNx.js 1.76 kB 1.76 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-DB5JMJv7.js 1.84 kB 1.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-kMx3IKl7.js 2.52 kB 2.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-BOgWBAuV.js 1.04 kB 1.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-DzYOr75r.js 2.7 kB 2.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-B9y-IUIh.js 2.26 kB 2.26 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 26 added / 26 removed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/extensions/core/load3d/Load3dUtils.ts`:
- Around line 144-169: The uploadThumbnail function currently assumes imageData
is a valid data URL and will quietly fail on invalid input; add a basic
validation at the start of uploadThumbnail (e.g., check imageData is a non-empty
string and matches a data URL pattern like /^data:image\/[a-zA-Z]+;base64,/) and
if validation fails, log a clear warning via console.warn or processLogger and
return false before calling fetch; keep the rest of the function (creating
blob/File, FormData, api.fetchApi) unchanged so callers get an immediate,
predictable false for bad input rather than a fetch error.

In `@src/extensions/core/saveMesh.ts`:
- Around line 21-47: The duplicate thumbnail logic in
generateThumbnailForSaveMesh should be extracted into a shared static method on
Load3dUtils (e.g., generateThumbnailIfNeeded) that uses
Load3dUtils.splitFilePath, .getThumbnailFilename, .fileExists and
.uploadThumbnail and calls load3d.captureThumbnail; replace the body of
generateThumbnailForSaveMesh with a call to
Load3dUtils.generateThumbnailIfNeeded(load3d, modelPath, folderType) and update
the other duplicate in useLoad3d.ts to call the same utility, and add the
corresponding import for Load3dUtils where needed; preserve the try/catch and
console.warn behavior inside the new utility so callers remain simple.

In `@src/platform/assets/components/Media3DTop.vue`:
- Around line 29-34: The thumbnailError ref isn't reset when the asset prop
changes, so a previous load failure keeps showing the placeholder; update the
component to watch the asset (or thumbnailSrc) and reset thumbnailError to false
when it changes (use watch or watchEffect to observe asset or thumbnailSrc and
set thumbnailError.value = false), ensuring you import the appropriate Vue
watch/watchEffect symbol; reference the thumbnailError ref, the thumbnailSrc
computed, and the asset prop when adding the watcher.

}
}

static async uploadThumbnail(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we re-use the helpers that already exist -- e.g., the one that is 100 lines above this

static async uploadFile(file: File, subfolder: string) {

Or this


Not really for this PR, but after looking, we seem to have a lot of duplication across the codebase for the upload endpoint usage:

  • src/composables/node/useNodeImageUpload.ts:20-49 (most generic implementation)
  • src/composables/maskeditor/useMaskEditorSaver.ts:266-299
  • src/components/common/BackgroundImageUpload.vue:55-74
  • src/extensions/core/webcamCapture.ts
  • src/extensions/core/uploadAudio.ts:28-73
  • src/services/audioService.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai make an Issue to centralize all the upload helpers listed above into a singlar shared util or service that these modules then import from. Allows easier refactoring later whenever we adjust the upload logic or parameters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@christian-byrne, I'll create an issue to track this refactoring work.


🏁 Script executed:

#!/bin/bash
# Create GitHub issue to centralize upload helper functions

gh issue create \
  --title "Centralize upload helper functions into shared utility" \
  --body "## Context

Multiple upload helper implementations are duplicated across the codebase, making it difficult to maintain and refactor upload logic consistently.

## Locations with duplicated upload functionality

- \`src/composables/node/useNodeImageUpload.ts:20-49\` (most generic implementation)
- \`src/composables/maskeditor/useMaskEditorSaver.ts:266-299\`
- \`src/components/common/BackgroundImageUpload.vue:55-74\`
- \`src/extensions/core/webcamCapture.ts\`
- \`src/extensions/core/uploadAudio.ts:28-73\`
- \`src/services/audioService.ts\`
- \`src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue\`

## Proposal

Centralize these implementations into a single shared utility or service that all modules can import from. This will:
- Reduce code duplication
- Make future changes to upload logic easier to maintain
- Ensure consistent behavior across all upload operations
- Simplify testing and error handling

## References

- Identified in PR `#8129`: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8129
- Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8129#discussion_r2701437891
- Requested by: `@christian-byrne`" \
  --assignee christian-byrne

Length of output: 57


@christian-byrne, I've created issue #8130 to track the centralization of upload helper functions. The issue includes all the file locations you identified, the benefits of consolidating them, and references back to this PR and comment.


🧬 Code Graph Analysis Results

File: src/scripts/api.ts (lines 1304-1304)

export const api = new ComfyApi()

File: src/scripts/app.ts (lines 1746-1746)

export const app = new ComfyApp()

File: src/platform/updates/common/toastStore.ts (lines 8-39)

export const useToastStore = defineStore('toast', () => {
  const messagesToAdd = ref<ToastMessageOptions[]>([])
  const messagesToRemove = ref<ToastMessageOptions[]>([])
  const removeAllRequested = ref(false)

  function add(message: ToastMessageOptions) {
    messagesToAdd.value = [...messagesToAdd.value, message]
  }

  function remove(message: ToastMessageOptions) {
    messagesToRemove.value = [...messagesToRemove.value, message]
  }

  function removeAll() {
    removeAllRequested.value = true
  }

  function addAlert(message: string) {
    add({ severity: 'warn', summary: 'Alert', detail: message })
  }

  return {
    messagesToAdd,
    messagesToRemove,
    removeAllRequested,

    add,
    remove,
    removeAll,
    addAlert
  }
})

File: src/stores/queueStore.ts (lines 83-85)

get url(): string {
    return api.apiURL('/view?' + this.urlParams)
  }

🧠 Learnings used
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7746
File: src/platform/assets/composables/useUploadModelWizard.ts:207-219
Timestamp: 2026-01-06T19:17:12.355Z
Learning: In `src/platform/assets/composables/useUploadModelWizard.ts`, the `uploadModel()` function should have a concurrency guard at the beginning that checks if `isUploading.value` is already true and returns false early to prevent race conditions from multiple rapid invocations.

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T21:33:34.380Z
Learning: Applies to src/**/*.{ts,tsx,js,jsx,vue} : Ask if there is a simpler way to introduce functionality; choose the simpler course when available

@jtydhr88 jtydhr88 force-pushed the thumbnail-3d-generation branch from 7dcc34f to fd143c3 Compare January 18, 2026 03:13
@jtydhr88 jtydhr88 assigned christian-byrne and unassigned jtydhr88 Jan 18, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/extensions/core/load3d/Load3d.ts`:
- Around line 772-807: Replace direct calls to cameraManager.toggleCamera with
the class-level toggle that keeps controls and view helper in sync: call
this.toggleCamera('perspective') before capturing and
this.toggleCamera(savedCameraType) in the finally block instead of
this.cameraManager.toggleCamera(...). This ensures controlsManager.controls and
any view helper bindings are updated when switching cameras during the thumbnail
flow (references: Load3d.toggleCamera, cameraManager.toggleCamera,
controlsManager.controls).

In `@src/extensions/core/load3d/Load3dUtils.ts`:
- Around line 8-30: The generateThumbnailIfNeeded flow can throw unhandled
rejections because callers use void; wrap the entire body of
Load3dUtils.generateThumbnailIfNeeded in a try/catch that logs a single error on
failure (use processLogger or existing logger), preserving context (include
modelPath and folderType) and avoid rethrowing so fire-and-forget callers don't
produce unhandled promise rejections; keep the existing logic (splitFilePath,
getThumbnailFilename, fileExists, captureThumbnail, uploadThumbnail) inside the
try block and call logger.error once in the catch with the caught error details.
♻️ Duplicate comments (1)
src/platform/assets/components/Media3DTop.vue (1)

27-34: Reset thumbnailError when the asset changes.

If the previous asset failed, the placeholder will persist for the next asset unless the error flag is reset. Based on learnings, prefer watch/watchEffect for this side effect.

🐛 Proposed fix
-import { computed, ref } from 'vue'
+import { computed, ref, watchEffect } from 'vue'
@@
 const thumbnailError = ref(false)
 
+watchEffect(() => {
+  if (asset?.src) {
+    thumbnailError.value = false
+  }
+})
+
 const thumbnailSrc = computed(() => {
   if (!asset?.src) return ''
   return asset.src.replace(/([?&]filename=)([^&]*)/, '$1$2.png')
 })

Comment on lines +772 to +807
if (savedCameraType !== 'perspective') {
this.cameraManager.toggleCamera('perspective')
}

const box = new THREE.Box3().setFromObject(this.modelManager.currentModel)
const size = box.getSize(new THREE.Vector3())
const center = box.getCenter(new THREE.Vector3())

const maxDim = Math.max(size.x, size.y, size.z)
const distance = maxDim * 1.5

const cameraPosition = new THREE.Vector3(
center.x - distance * 0.8,
center.y + distance * 0.4,
center.z + distance * 0.3
)

this.cameraManager.perspectiveCamera.position.copy(cameraPosition)
this.cameraManager.perspectiveCamera.lookAt(center)
this.cameraManager.perspectiveCamera.updateProjectionMatrix()

if (this.controlsManager.controls) {
this.controlsManager.controls.target.copy(center)
this.controlsManager.controls.update()
}

const result = await this.sceneManager.captureScene(width, height)
return result.scene
} finally {
this.sceneManager.gridHelper.visible = savedGridVisible

if (savedCameraType !== 'perspective') {
this.cameraManager.toggleCamera(savedCameraType)
}
this.cameraManager.setCameraState(savedState)
this.controlsManager.controls?.update()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep controls/view helper in sync when switching cameras.

cameraManager.toggleCamera(...) bypasses Load3d.toggleCamera, so controls and view helper may remain bound to the previous camera type after thumbnail capture. This can leave interactions broken if the user was in orthographic mode.

🐛 Proposed fix (minimal side effects)
-      if (savedCameraType !== 'perspective') {
-        this.cameraManager.toggleCamera('perspective')
-      }
+      if (savedCameraType !== 'perspective') {
+        this.cameraManager.toggleCamera('perspective')
+        this.controlsManager.updateCamera(this.cameraManager.activeCamera)
+        this.viewHelperManager.recreateViewHelper()
+      }
@@
-      if (savedCameraType !== 'perspective') {
-        this.cameraManager.toggleCamera(savedCameraType)
-      }
+      if (savedCameraType !== 'perspective') {
+        this.cameraManager.toggleCamera(savedCameraType)
+        this.controlsManager.updateCamera(this.cameraManager.activeCamera)
+        this.viewHelperManager.recreateViewHelper()
+      }
🤖 Prompt for AI Agents
In `@src/extensions/core/load3d/Load3d.ts` around lines 772 - 807, Replace direct
calls to cameraManager.toggleCamera with the class-level toggle that keeps
controls and view helper in sync: call this.toggleCamera('perspective') before
capturing and this.toggleCamera(savedCameraType) in the finally block instead of
this.cameraManager.toggleCamera(...). This ensures controlsManager.controls and
any view helper bindings are updated when switching cameras during the thumbnail
flow (references: Load3d.toggleCamera, cameraManager.toggleCamera,
controlsManager.controls).

Comment on lines +8 to +30
static async generateThumbnailIfNeeded(
load3d: Load3d,
modelPath: string,
folderType: 'input' | 'output'
): Promise<void> {
const [subfolder, filename] = this.splitFilePath(modelPath)
const thumbnailFilename = this.getThumbnailFilename(filename)

const exists = await this.fileExists(
subfolder,
thumbnailFilename,
folderType
)
if (exists) return

const imageData = await load3d.captureThumbnail(256, 256)
await this.uploadThumbnail(
imageData,
subfolder,
thumbnailFilename,
folderType
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent unhandled rejections from fire‑and‑forget thumbnail generation.

Callers invoke this with void, so any failure will surface as an unhandled promise rejection. Wrap the flow in a try/catch and log once. As per coding guidelines, ensure proper error handling.

🐛 Proposed fix
   static async generateThumbnailIfNeeded(
     load3d: Load3d,
     modelPath: string,
     folderType: 'input' | 'output'
   ): Promise<void> {
-    const [subfolder, filename] = this.splitFilePath(modelPath)
-    const thumbnailFilename = this.getThumbnailFilename(filename)
-
-    const exists = await this.fileExists(
-      subfolder,
-      thumbnailFilename,
-      folderType
-    )
-    if (exists) return
-
-    const imageData = await load3d.captureThumbnail(256, 256)
-    await this.uploadThumbnail(
-      imageData,
-      subfolder,
-      thumbnailFilename,
-      folderType
-    )
+    try {
+      const [subfolder, filename] = this.splitFilePath(modelPath)
+      const thumbnailFilename = this.getThumbnailFilename(filename)
+
+      const exists = await this.fileExists(
+        subfolder,
+        thumbnailFilename,
+        folderType
+      )
+      if (exists) return
+
+      const imageData = await load3d.captureThumbnail(256, 256)
+      await this.uploadThumbnail(
+        imageData,
+        subfolder,
+        thumbnailFilename,
+        folderType
+      )
+    } catch (error) {
+      console.warn('[Load3D] generateThumbnailIfNeeded failed:', error)
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static async generateThumbnailIfNeeded(
load3d: Load3d,
modelPath: string,
folderType: 'input' | 'output'
): Promise<void> {
const [subfolder, filename] = this.splitFilePath(modelPath)
const thumbnailFilename = this.getThumbnailFilename(filename)
const exists = await this.fileExists(
subfolder,
thumbnailFilename,
folderType
)
if (exists) return
const imageData = await load3d.captureThumbnail(256, 256)
await this.uploadThumbnail(
imageData,
subfolder,
thumbnailFilename,
folderType
)
}
static async generateThumbnailIfNeeded(
load3d: Load3d,
modelPath: string,
folderType: 'input' | 'output'
): Promise<void> {
try {
const [subfolder, filename] = this.splitFilePath(modelPath)
const thumbnailFilename = this.getThumbnailFilename(filename)
const exists = await this.fileExists(
subfolder,
thumbnailFilename,
folderType
)
if (exists) return
const imageData = await load3d.captureThumbnail(256, 256)
await this.uploadThumbnail(
imageData,
subfolder,
thumbnailFilename,
folderType
)
} catch (error) {
console.warn('[Load3D] generateThumbnailIfNeeded failed:', error)
}
}
🤖 Prompt for AI Agents
In `@src/extensions/core/load3d/Load3dUtils.ts` around lines 8 - 30, The
generateThumbnailIfNeeded flow can throw unhandled rejections because callers
use void; wrap the entire body of Load3dUtils.generateThumbnailIfNeeded in a
try/catch that logs a single error on failure (use processLogger or existing
logger), preserving context (include modelPath and folderType) and avoid
rethrowing so fire-and-forget callers don't produce unhandled promise
rejections; keep the existing logic (splitFilePath, getThumbnailFilename,
fileExists, captureThumbnail, uploadThumbnail) inside the try block and call
logger.error once in the catch with the caught error details.

@christian-byrne christian-byrne merged commit 82c3cd3 into main Jan 18, 2026
27 checks passed
@christian-byrne christian-byrne deleted the thumbnail-3d-generation branch January 18, 2026 03:32
godwiniheuwa pushed a commit to godwiniheuwa/ComfyUI_frontend that referenced this pull request Jan 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants