diff --git a/locales/de.json b/locales/de.json index 36eeee76..9e683df9 100644 --- a/locales/de.json +++ b/locales/de.json @@ -188,6 +188,9 @@ "title": "Modelle filtern", "baseModel": "Basis-Modell", "modelTags": "Tags (Top 20)", + "license": "Lizenz", + "noCreditRequired": "Kein Credit erforderlich", + "allowSellingGeneratedContent": "Verkauf erlaubt", "clearAll": "Alle Filter löschen" }, "theme": { diff --git a/locales/en.json b/locales/en.json index c455efa5..c53e6b3d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -188,6 +188,9 @@ "title": "Filter Models", "baseModel": "Base Model", "modelTags": "Tags (Top 20)", + "license": "License", + "noCreditRequired": "No Credit Required", + "allowSellingGeneratedContent": "Allow Selling", "clearAll": "Clear All Filters" }, "theme": { diff --git a/locales/es.json b/locales/es.json index 39670bdf..29f28544 100644 --- a/locales/es.json +++ b/locales/es.json @@ -188,6 +188,9 @@ "title": "Filtrar modelos", "baseModel": "Modelo base", "modelTags": "Etiquetas (Top 20)", + "license": "Licencia", + "noCreditRequired": "Sin crédito requerido", + "allowSellingGeneratedContent": "Venta permitida", "clearAll": "Limpiar todos los filtros" }, "theme": { diff --git a/locales/fr.json b/locales/fr.json index 9ddddeaa..7af2d2a2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -188,6 +188,9 @@ "title": "Filtrer les modèles", "baseModel": "Modèle de base", "modelTags": "Tags (Top 20)", + "license": "Licence", + "noCreditRequired": "Crédit non requis", + "allowSellingGeneratedContent": "Vente autorisée", "clearAll": "Effacer tous les filtres" }, "theme": { diff --git a/locales/he.json b/locales/he.json index 9215c949..e9a6f43d 100644 --- a/locales/he.json +++ b/locales/he.json @@ -188,6 +188,9 @@ "title": "סנן מודלים", "baseModel": "מודל בסיס", "modelTags": "תגיות (20 המובילות)", + "license": "רישיון", + "noCreditRequired": "ללא קרדיט נדרש", + "allowSellingGeneratedContent": "אפשר מכירה", "clearAll": "נקה את כל המסננים" }, "theme": { diff --git a/locales/ja.json b/locales/ja.json index 5a07f6cb..182ec174 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -188,6 +188,9 @@ "title": "モデルをフィルタ", "baseModel": "ベースモデル", "modelTags": "タグ(上位20)", + "license": "ライセンス", + "noCreditRequired": "クレジット不要", + "allowSellingGeneratedContent": "販売許可", "clearAll": "すべてのフィルタをクリア" }, "theme": { diff --git a/locales/ko.json b/locales/ko.json index 65929423..24b8ba45 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -188,6 +188,9 @@ "title": "모델 필터", "baseModel": "베이스 모델", "modelTags": "태그 (상위 20개)", + "license": "라이선스", + "noCreditRequired": "크레딧 표기 없음", + "allowSellingGeneratedContent": "판매 허용", "clearAll": "모든 필터 지우기" }, "theme": { diff --git a/locales/ru.json b/locales/ru.json index e0589cc5..9630fc2b 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -188,6 +188,9 @@ "title": "Фильтр моделей", "baseModel": "Базовая модель", "modelTags": "Теги (Топ 20)", + "license": "Лицензия", + "noCreditRequired": "Без указания авторства", + "allowSellingGeneratedContent": "Продажа разрешена", "clearAll": "Очистить все фильтры" }, "theme": { diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 6cd90f37..cea7096f 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -188,6 +188,9 @@ "title": "筛选模型", "baseModel": "基础模型", "modelTags": "标签(前20)", + "license": "许可证", + "noCreditRequired": "无需署名", + "allowSellingGeneratedContent": "允许销售", "clearAll": "清除所有筛选" }, "theme": { diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 270cedde..97820dcd 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -188,6 +188,9 @@ "title": "篩選模型", "baseModel": "基礎模型", "modelTags": "標籤(前 20)", + "license": "授權", + "noCreditRequired": "無需署名", + "allowSellingGeneratedContent": "允許銷售", "clearAll": "清除所有篩選" }, "theme": { diff --git a/static/css/base.css b/static/css/base.css index d79bc29b..25be44ce 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -51,6 +51,8 @@ html, body { --lora-border: oklch(72% 0.03 256 / 0.45); --lora-text: oklch(95% 0.02 256); --lora-error: oklch(75% 0.32 29); + --lora-error-bg: color-mix(in oklch, var(--lora-error) 20%, transparent); + --lora-error-border: color-mix(in oklch, var(--lora-error) 50%, transparent); --lora-warning: oklch(var(--lora-warning-l) var(--lora-warning-c) var(--lora-warning-h)); --lora-success: oklch(var(--lora-success-l) var(--lora-success-c) var(--lora-success-h)); --badge-update-bg: oklch(72% 0.2 220); @@ -103,6 +105,8 @@ html[data-theme="light"] { --lora-border: oklch(90% 0.02 256 / 0.15); --lora-text: oklch(98% 0.02 256); --lora-warning: oklch(75% 0.25 80); /* Modified to be used with oklch() */ + --lora-error-bg: color-mix(in oklch, var(--lora-error) 15%, transparent); + --lora-error-border: color-mix(in oklch, var(--lora-error) 40%, transparent); --badge-update-bg: oklch(62% 0.18 220); --badge-update-text: oklch(98% 0.02 240); --badge-update-glow: oklch(62% 0.18 220 / 0.4); diff --git a/static/css/components/search-filter.css b/static/css/components/search-filter.css index f5e91cf4..8feb9709 100644 --- a/static/css/components/search-filter.css +++ b/static/css/components/search-filter.css @@ -235,6 +235,13 @@ border-color: var(--lora-accent); } +/* Exclude state styling for filter tags */ +.filter-tag.exclude { + background-color: var(--lora-error-bg); + color: var(--lora-error); + border-color: var(--lora-error-border); +} + /* Tag filter styles */ .tag-filter { display: flex; diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 13c9c469..9034a465 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -817,6 +817,33 @@ export class BaseModelApiClient { params.append('base_model', model); }); } + + // Add license filters + if (pageState.filters.license) { + const licenseFilters = pageState.filters.license; + + if (licenseFilters.noCredit) { + // For noCredit filter: + // - 'include' means credit_required=False (no credit required) + // - 'exclude' means credit_required=True (credit required) + if (licenseFilters.noCredit === 'include') { + params.append('credit_required', 'false'); + } else if (licenseFilters.noCredit === 'exclude') { + params.append('credit_required', 'true'); + } + } + + if (licenseFilters.allowSelling) { + // For allowSelling filter: + // - 'include' means allow_selling_generated_content=True + // - 'exclude' means allow_selling_generated_content=False + if (licenseFilters.allowSelling === 'include') { + params.append('allow_selling_generated_content', 'true'); + } else if (licenseFilters.allowSelling === 'exclude') { + params.append('allow_selling_generated_content', 'false'); + } + } + } } this._addModelSpecificParams(params, pageState); diff --git a/static/js/managers/FilterManager.js b/static/js/managers/FilterManager.js index 2a05d52b..31d416ad 100644 --- a/static/js/managers/FilterManager.js +++ b/static/js/managers/FilterManager.js @@ -14,7 +14,8 @@ export class FilterManager { this.filters = pageState.filters || { baseModel: [], - tags: [] + tags: [], + license: {} }; this.filterPanel = document.getElementById('filterPanel'); @@ -36,6 +37,9 @@ export class FilterManager { this.createBaseModelTags(); } + // Add click handlers for license filter tags + this.initializeLicenseFilters(); + // Add click handler for filter button if (this.filterButton) { this.filterButton.addEventListener('click', () => { @@ -129,6 +133,85 @@ export class FilterManager { }); } + initializeLicenseFilters() { + const licenseTags = document.querySelectorAll('.license-tag'); + licenseTags.forEach(tag => { + tag.addEventListener('click', async () => { + const licenseType = tag.dataset.license; + + // Ensure license object exists + if (!this.filters.license) { + this.filters.license = {}; + } + + // Get current state + let currentState = this.filters.license[licenseType] || 'none'; // none, include, exclude + + // Cycle through states: none -> include -> exclude -> none + let newState; + switch (currentState) { + case 'none': + newState = 'include'; + tag.classList.remove('exclude'); + tag.classList.add('active'); + break; + case 'include': + newState = 'exclude'; + tag.classList.remove('active'); + tag.classList.add('exclude'); + break; + case 'exclude': + newState = 'none'; + tag.classList.remove('active', 'exclude'); + break; + } + + // Update filter state + if (newState === 'none') { + delete this.filters.license[licenseType]; + // Clean up empty license object + if (Object.keys(this.filters.license).length === 0) { + delete this.filters.license; + } + } else { + this.filters.license[licenseType] = newState; + } + + this.updateActiveFiltersCount(); + + // Auto-apply filter when tag is clicked + await this.applyFilters(false); + }); + }); + + // Update selections based on stored filters + this.updateLicenseSelections(); + } + + updateLicenseSelections() { + const licenseTags = document.querySelectorAll('.license-tag'); + licenseTags.forEach(tag => { + const licenseType = tag.dataset.license; + const state = (this.filters.license && this.filters.license[licenseType]) || 'none'; + + // Reset classes + tag.classList.remove('active', 'exclude'); + + // Apply appropriate class based on state + switch (state) { + case 'include': + tag.classList.add('active'); + break; + case 'exclude': + tag.classList.add('exclude'); + break; + default: + // none state - no classes needed + break; + } + }); + } + createBaseModelTags() { const baseModelTagsContainer = document.getElementById('baseModelTags'); if (!baseModelTagsContainer) return; @@ -233,10 +316,15 @@ export class FilterManager { tag.classList.remove('active'); } }); + + // Update license tags + this.updateLicenseSelections(); } updateActiveFiltersCount() { - const totalActiveFilters = this.filters.baseModel.length + this.filters.tags.length; + const totalActiveFilters = this.filters.baseModel.length + + this.filters.tags.length + + (this.filters.license ? Object.keys(this.filters.license).length : 0); if (this.activeFiltersCount) { if (totalActiveFilters > 0) { @@ -296,7 +384,8 @@ export class FilterManager { // Clear all filters this.filters = { baseModel: [], - tags: [] + tags: [], + license: {} // Initialize with empty object instead of deleting }; // Update state @@ -337,7 +426,8 @@ export class FilterManager { // Ensure backward compatibility with older filter format this.filters = { baseModel: savedFilters.baseModel || [], - tags: savedFilters.tags || [] + tags: savedFilters.tags || [], + license: savedFilters.license || {} }; // Update state with loaded filters @@ -357,6 +447,8 @@ export class FilterManager { } hasActiveFilters() { - return this.filters.baseModel.length > 0 || this.filters.tags.length > 0; + return this.filters.baseModel.length > 0 || + this.filters.tags.length > 0 || + (this.filters.license && Object.keys(this.filters.license).length > 0); } } diff --git a/templates/components/header.html b/templates/components/header.html index ac0d8ef7..bbf4b8d1 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -139,6 +139,17 @@
+