Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/bottomPanel/BottomPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
class="bg-transparent"
>
<div class="flex w-full justify-between">
<div class="tabs-container">
<div class="tabs-container font-inter">
<Tab
v-for="tab in bottomPanelStore.bottomPanelTabs"
:key="tab.id"
:value="tab.id"
class="m-1 mx-2 border-none"
class="m-1 mx-2 border-none font-inter"
:class="{
'tab-list-single-item':
bottomPanelStore.bottomPanelTabs.length === 1
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/TreeExplorer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Tree
v-model:expanded-keys="expandedKeys"
v-model:selection-keys="selectionKeys"
class="tree-explorer px-2 py-0 2xl:px-4"
class="tree-explorer px-2 py-0 2xl:px-4 bg-transparent"
:class="props.class"
:value="renderedRoot.children"
selection-mode="single"
Expand Down
2 changes: 1 addition & 1 deletion src/components/rightSidePanel/RightSidePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function handleTitleCancel() {
<Tab
v-for="tab in tabs"
:key="tab.value"
class="text-sm py-1 px-2"
class="text-sm py-1 px-2 font-inter"
:value="tab.value"
>
{{ tab.label() }}
Expand Down
36 changes: 0 additions & 36 deletions src/components/sidebar/tabs/AssetSidebarTemplate.vue

This file was deleted.

36 changes: 21 additions & 15 deletions src/components/sidebar/tabs/AssetsSidebarTab.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
<template>
<AssetsSidebarTemplate>
<template #top>
<span v-if="!isInFolderView" class="font-bold">
{{ $t('sideToolbar.mediaAssets.title') }}
</span>
<div v-else class="flex w-full items-center justify-between gap-2">
<SidebarTabTemplate
:title="isInFolderView ? '' : $t('sideToolbar.mediaAssets.title')"
>
<template #alt-title>
<div
v-if="isInFolderView"
class="flex w-full items-center justify-between gap-2"
>
<div class="flex items-center gap-2">
<span class="font-bold">{{ $t('Job ID') }}:</span>
<span class="font-bold">{{ $t('assetBrowser.jobId') }}:</span>
<span class="text-sm">{{ folderPromptId?.substring(0, 8) }}</span>
<button
class="m-0 cursor-pointer border-0 bg-transparent p-0 outline-0"
role="button"
@click="copyJobId"
>
<i class="mb-1 icon-[lucide--copy] text-sm"></i>
<i class="icon-[lucide--copy] text-sm"></i>
</button>
</div>
<div>
Expand All @@ -23,7 +25,7 @@
</template>
<template #header>
<!-- Job Detail View Header -->
<div v-if="isInFolderView" class="pt-4 pb-2">
<div v-if="isInFolderView" class="px-2 2xl:px-4">
<IconTextButton
:label="$t('sideToolbar.backToAssets')"
type="secondary"
Expand All @@ -35,15 +37,20 @@
</IconTextButton>
</div>
<!-- Normal Tab View -->
<TabList v-else v-model="activeTab" class="pt-4 pb-1">
<Tab value="output">{{ $t('sideToolbar.labels.generated') }}</Tab>
<Tab value="input">{{ $t('sideToolbar.labels.imported') }}</Tab>
<TabList v-else v-model="activeTab" class="font-inter px-2 2xl:px-4">
<Tab class="font-inter" value="output">{{
$t('sideToolbar.labels.generated')
}}</Tab>
<Tab class="font-inter" value="input">{{
$t('sideToolbar.labels.imported')
}}</Tab>
</TabList>
<!-- Filter Bar -->
<MediaAssetFilterBar
v-model:search-query="searchQuery"
v-model:sort-by="sortBy"
v-model:media-type-filters="mediaTypeFilters"
class="pb-1 px-2 2xl:px-4"
:show-generation-time-sort="activeTab === 'output'"
/>
</template>
Expand Down Expand Up @@ -158,7 +165,7 @@
</div>
</div>
</template>
</AssetsSidebarTemplate>
</SidebarTabTemplate>
<ResultGallery
v-model:active-index="galleryActiveIndex"
:all-gallery-items="galleryItems"
Expand All @@ -177,6 +184,7 @@ import TextButton from '@/components/button/TextButton.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import VirtualGrid from '@/components/common/VirtualGrid.vue'
import Load3dViewerContent from '@/components/load3d/Load3dViewerContent.vue'
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
import Tab from '@/components/tab/Tab.vue'
import TabList from '@/components/tab/TabList.vue'
Expand All @@ -194,8 +202,6 @@ import { useDialogStore } from '@/stores/dialogStore'
import { ResultItemImpl } from '@/stores/queueStore'
import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil'

import AssetsSidebarTemplate from './AssetSidebarTemplate.vue'

const activeTab = ref<'input' | 'output'>('output')
const folderPromptId = ref<string | null>(null)
const folderExecutionTime = ref<number | undefined>(undefined)
Expand Down
5 changes: 1 addition & 4 deletions src/components/sidebar/tabs/ModelLibrarySidebarTab.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
<template>
<SidebarTabTemplate
:title="$t('sideToolbar.modelLibrary')"
class="bg-(--p-tree-background)"
>
<SidebarTabTemplate :title="$t('sideToolbar.modelLibrary')">
<template #tool-buttons>
<Button
v-tooltip.bottom="$t('g.refresh')"
Expand Down
1 change: 0 additions & 1 deletion src/components/sidebar/tabs/NodeLibrarySidebarTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<SidebarTabTemplate
v-if="!isHelpOpen"
:title="$t('sideToolbar.nodeLibrary')"
class="bg-(--p-tree-background)"
>
<template #tool-buttons>
<Button
Expand Down
11 changes: 7 additions & 4 deletions src/components/sidebar/tabs/SidebarTabTemplate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
class="comfy-vue-side-bar-container group/sidebar-tab flex h-full flex-col"
:class="props.class"
>
<div class="comfy-vue-side-bar-header">
<Toolbar class="min-h-8 rounded-none border-x-0 border-t-0 px-2 py-1">
<div class="comfy-vue-side-bar-header flex flex-col gap-2">
<Toolbar
class="min-h-15.5 bg-transparent rounded-none border-x-0 border-t-0 px-2 2xl:px-4"
>
<template #start>
<span class="truncate text-xs 2xl:text-sm" :title="props.title">
{{ props.title.toUpperCase() }}
<span class="truncate font-bold" :title="props.title">
{{ props.title }}
</span>
<slot name="alt-title" />
</template>
<template #end>
<div
Expand Down
2 changes: 1 addition & 1 deletion src/components/sidebar/tabs/WorkflowsSidebarTab.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<SidebarTabTemplate
:title="$t('sideToolbar.workflows')"
class="workflows-sidebar-tab bg-(--p-tree-background)"
class="workflows-sidebar-tab"
>
<template #tool-buttons>
<Button
Expand Down
2 changes: 1 addition & 1 deletion src/components/sidebar/tabs/nodeLibrary/NodeHelpPage.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="flex h-full flex-col overflow-auto bg-(--p-tree-background)">
<div class="flex h-full flex-col overflow-auto">
<div
class="flex items-center border-b border-(--p-divider-color) px-3 py-2"
>
Expand Down
103 changes: 52 additions & 51 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2082,71 +2082,72 @@
"cloudSurvey_steps_industry": "What's your primary industry?",
"cloudSurvey_steps_making": "What do you plan on making?",
"assetBrowser": {
"assets": "Assets",
"allCategory": "All {category}",
"allModels": "All Models",
"assetCollection": "Asset collection",
"checkpoints": "Checkpoints",
"assets": "Assets",
"baseModels": "Base models",
"browseAssets": "Browse Assets",
"noAssetsFound": "No assets found",
"tryAdjustingFilters": "Try adjusting your search or filters",
"loadingModels": "Loading {type}...",
"connectionError": "Please check your connection and try again",
"failedToCreateNode": "Failed to create node. Please try again or check console for details.",
"noModelsInFolder": "No {type} available in this folder",
"uploadModel": "Import",
"uploadModelFromCivitai": "Import a model from Civitai",
"uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.",
"onlyCivitaiUrlsSupported": "Only Civitai URLs are supported",
"uploadModelDescription1": "Paste a Civitai model download link to add it to your library.",
"uploadModelDescription2": "Only links from <a href=\"https://civitai.com\" target=\"_blank\" class=\"text-muted-foreground\">https://civitai.com</a> are supported at the moment",
"uploadModelDescription3": "Max file size: <strong>1 GB</strong>",
"checkpoints": "Checkpoints",
"civitaiLinkExample": "<strong>Example:</strong> <a href=\"https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor\" target=\"_blank\" class=\"text-muted-foreground\">https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor</a>",
"civitaiLinkLabel": "Civitai model <span class=\"font-bold italic\">download</span> link",
"civitaiLinkPlaceholder": "Paste link here",
"civitaiLinkExample": "<strong>Example:</strong> <a href=\"https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor\" target=\"_blank\" class=\"text-muted-foreground\">https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor</a>",
"confirmModelDetails": "Confirm Model Details",
"connectionError": "Please check your connection and try again",
"errorFileTooLarge": "File exceeds the maximum allowed size limit",
"errorFormatNotAllowed": "Only SafeTensor format is allowed",
"errorModelTypeNotSupported": "This model type is not supported",
"errorUnknown": "An unexpected error occurred",
"errorUnsafePickleScan": "CivitAI detected potentially unsafe code in this file",
"errorUnsafeVirusScan": "CivitAI detected malware or suspicious content in this file",
"errorUploadFailed": "Failed to import asset. Please try again.",
"failedToCreateNode": "Failed to create node. Please try again or check console for details.",
"fileFormats": "File formats",
"fileName": "File Name",
"fileSize": "File Size",
"filterBy": "Filter by",
"findInLibrary": "Find it in the {type} section of the models library.",
"finish": "Finish",
"jobId": "Job ID",
"loadingModels": "Loading {type}...",
"modelAssociatedWithLink": "The model associated with the link you provided:",
"modelName": "Model Name",
"modelNamePlaceholder": "Enter a name for this model",
"tags": "Tags",
"tagsPlaceholder": "e.g., models, checkpoint",
"tagsHelp": "Separate tags with commas",
"upload": "Import",
"uploadingModel": "Importing model...",
"uploadSuccess": "Model imported successfully!",
"uploadFailed": "Import failed",
"uploadModelHelpVideo": "Upload Model Help Video",
"uploadModelHowDoIFindThis": "How do I find this?",
"modelAssociatedWithLink": "The model associated with the link you provided:",
"modelTypeSelectorLabel": "What type of model is this?",
"modelTypeSelectorPlaceholder": "Select model type",
"selectModelType": "Select model type",
"notSureLeaveAsIs": "Not sure? Just leave this as is",
"modelUploaded": "Model imported! 🎉",
"findInLibrary": "Find it in the {type} section of the models library.",
"finish": "Finish",
"upgradeToUnlockFeature": "Upgrade to unlock this feature",
"upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.",
"allModels": "All Models",
"allCategory": "All {category}",
"unknown": "Unknown",
"fileFormats": "File formats",
"baseModels": "Base models",
"filterBy": "Filter by",
"sortBy": "Sort by",
"sortAZ": "A-Z",
"sortZA": "Z-A",
"sortRecent": "Recent",
"sortPopular": "Popular",
"noAssetsFound": "No assets found",
"noModelsInFolder": "No {type} available in this folder",
"notSureLeaveAsIs": "Not sure? Just leave this as is",
"onlyCivitaiUrlsSupported": "Only Civitai URLs are supported",
"selectFrameworks": "Select Frameworks",
"selectModelType": "Select model type",
"selectProjects": "Select Projects",
"sortAZ": "A-Z",
"sortBy": "Sort by",
"sortingType": "Sorting Type",
"errorFileTooLarge": "File exceeds the maximum allowed size limit",
"errorFormatNotAllowed": "Only SafeTensor format is allowed",
"errorUnsafePickleScan": "CivitAI detected potentially unsafe code in this file",
"errorUnsafeVirusScan": "CivitAI detected malware or suspicious content in this file",
"errorModelTypeNotSupported": "This model type is not supported",
"errorUnknown": "An unexpected error occurred",
"errorUploadFailed": "Failed to import asset. Please try again.",
"sortPopular": "Popular",
"sortRecent": "Recent",
"sortZA": "Z-A",
"tags": "Tags",
"tagsHelp": "Separate tags with commas",
"tagsPlaceholder": "e.g., models, checkpoint",
"tryAdjustingFilters": "Try adjusting your search or filters",
"unknown": "Unknown",
"upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.",
"upgradeToUnlockFeature": "Upgrade to unlock this feature",
"upload": "Import",
"uploadFailed": "Import failed",
"uploadingModel": "Importing model...",
"uploadModel": "Import",
"uploadModelDescription1": "Paste a Civitai model download link to add it to your library.",
"uploadModelDescription2": "Only links from <a href=\"https://civitai.com\" target=\"_blank\" class=\"text-muted-foreground\">https://civitai.com</a> are supported at the moment",
"uploadModelDescription3": "Max file size: <strong>1 GB</strong>",
"uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.",
"uploadModelFromCivitai": "Import a model from Civitai",
"uploadModelHelpVideo": "Upload Model Help Video",
"uploadModelHowDoIFindThis": "How do I find this?",
"uploadSuccess": "Model imported successfully!",
"ariaLabel": {
Comment on lines +2085 to 2151
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

assetBrowser i18n additions look consistent; ensure HTML strings are sanitized on render

The new assetBrowser keys (sorting/filtering, upload/import, Civitai link helpers, jobId, etc.) are well-namespaced and match existing naming patterns, and assetBrowser.jobId cleanly supports the updated Assets sidebar UI.

One caution: civitaiLinkExample embeds HTML (including an <a> tag). Wherever this key is rendered, make sure it's passed through your existing DOMPurify-based HTML sanitization helper rather than bound directly with raw v-html, to stay aligned with the XSS protection guidelines.

🤖 Prompt for AI Agents
In src/locales/en/main.json around lines 2085-2151 the civitaiLinkExample value
contains inline HTML; locate all places this key is rendered in the UI and
replace any direct raw HTML binding (e.g., v-html or innerHTML) with the
project's HTML-sanitization helper that uses DOMPurify (or wrap the string
through DOMPurify.sanitize) before injecting into the DOM; ensure the sanitizer
is configured to allow the minimal tags/attributes needed (anchor, strong,
class, target) and add/update a quick test or assertion that the rendered output
comes from the sanitized string rather than raw user input.

"assetCard": "{name} - {type} asset",
"loadingAsset": "Loading asset"
Expand Down Expand Up @@ -2273,4 +2274,4 @@
"inputsNoneTooltip": "Node has no inputs",
"nodeState": "Node state"
}
}
}
2 changes: 1 addition & 1 deletion src/platform/assets/components/MediaAssetFilterBar.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="flex gap-3 pt-2">
<div class="flex gap-3">
<SearchBox
:model-value="searchQuery"
:placeholder="$t('sideToolbar.searchAssets')"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
<div class="overflow-hidden">
<Tabs :value="activeTab">
<TabList class="scrollbar-hide overflow-x-auto">
<Tab v-if="hasCompatibilityIssues" value="warning" class="mr-6 p-2">
<Tab
v-if="hasCompatibilityIssues"
value="warning"
class="mr-6 p-2 font-inter"
>
<div class="flex items-center gap-1">
<span>⚠️</span>
{{ importFailed ? $t('g.error') : $t('g.warning') }}
</div>
</Tab>
<Tab value="description" class="mr-6 p-2">
<Tab value="description" class="mr-6 p-2 font-inter">
{{ $t('g.description') }}
</Tab>
<Tab value="nodes" class="p-2">
<Tab value="nodes" class="p-2 font-inter">
{{ $t('g.nodes') }}
</Tab>
</TabList>
Expand Down
Loading