diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 403160c..022bf42 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -26,6 +26,10 @@ import { DialogService } from 'primeng/dynamicdialog'; import { PanelModule } from 'primeng/panel'; import { ChipModule } from 'primeng/chip'; import { DialogModule } from 'primeng/dialog'; +import { InputSwitchModule } from 'primeng/inputswitch'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { ChipsModule } from 'primeng/chips'; +import { MultiSelectModule } from 'primeng/multiselect'; const routes: Routes = [ @@ -56,6 +60,10 @@ const routes: Routes = [ ChipModule, ButtonModule, DialogModule, + InputSwitchModule, + SelectButtonModule, + ChipsModule, + MultiSelectModule, FileUploadModule, AccordionModule, ImageModule, diff --git a/src/app/home/options/options.component.css b/src/app/home/options/options.component.css index d45fcdf..8f15ba8 100644 --- a/src/app/home/options/options.component.css +++ b/src/app/home/options/options.component.css @@ -6,14 +6,16 @@ flex-wrap: wrap; align-items: center; justify-content: start; - gap: 10px; /* Space between items */ + gap: 10px; + /* Space between items */ } /* Includes prompt input box and generate button */ #prompt-input-box { padding-left: 10px; padding-bottom: 10px; - flex-grow: 1; /* Allow the input box to grow and take available space */ + flex-grow: 1; + /* Allow the input box to grow and take available space */ } /* The input box itself where text is */ @@ -44,7 +46,8 @@ .search-bar { width: 100%; - margin-bottom: 15px; /* Add space below the search bar */ + margin-bottom: 10px; + /* Add space below the search bar */ } .search-bar input { @@ -54,12 +57,16 @@ .history-grid { display: grid; - grid-template-columns: repeat(2, 1fr); /* Enforce 2 columns */ + grid-template-columns: repeat(2, 1fr); + /* Enforce 2 columns */ grid-gap: 10px; margin-top: 10px; - max-width: 400px; /* Set a maximum width for the grid container */ - margin-left: auto; /* Center the grid horizontally */ - margin-right: auto; /* Center the grid horizontally */ + max-width: 400px; + /* Set a maximum width for the grid container */ + margin-left: auto; + /* Center the grid horizontally */ + margin-right: auto; + /* Center the grid horizontally */ } .history-item { @@ -69,9 +76,12 @@ .history-item img { width: 100%; height: auto; - border-radius: 6px; /* Slightly larger border radius for a smoother look */ - aspect-ratio: 1 / 1; /* This will make the image container square */ - object-fit: cover; /* Ensure the image covers its container */ + border-radius: 6px; + /* Slightly larger border radius for a smoother look */ + aspect-ratio: 1 / 1; + /* This will make the image container square */ + object-fit: cover; + /* Ensure the image covers its container */ } .history-details { @@ -105,9 +115,11 @@ } .card-body { - min-height: 75vh; /* Start with 50% of the viewport height */ - max-height: fit-content ; - overflow-y: auto; /* Allow scrolling if content exceeds max-height */ + min-height: 75vh; + /* Start with 50% of the viewport height */ + max-height: fit-content; + overflow-y: auto; + /* Allow scrolling if content exceeds max-height */ transition: height 0.5s ease-in-out; max-width: 600px; overflow: hidden @@ -115,36 +127,51 @@ .pagination { display: flex; - justify-content: space-between; /* Space buttons evenly */ + justify-content: space-between; + /* Space buttons evenly */ margin-top: 20px; } .pagination .btn { - width: 48%; /* Make pagination buttons larger */ - padding: 10px 0; /* Increase padding for easier tapping */ + width: 48%; + /* Make pagination buttons larger */ + padding: 10px 0; + /* Increase padding for easier tapping */ } /* loras */ +.lora-filters { + display: flex; + flex-wrap: wrap; +} + .loras-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: 1rem; - overflow-y: auto; /* Enable scrolling if the content exceeds the height */ + display: flex; + flex-wrap: wrap; + gap: 15px; + /* Space between items */ + overflow-y: auto; + /* Enable scrolling if the content exceeds the height */ + overflow-x: hidden; + /* Hide horizontal scrollbar */ } .lora-card { border: 1px solid #ddd; border-radius: 8px; overflow: visible; - min-height: 190px; /* Set a minimum height to prevent layout shifts */ - max-width: 600px; /* Enforce a consistent card width */ + min-height: 190px; + /* Set a minimum height to prevent layout shifts */ + max-width: 600px; + /* Enforce a consistent card width */ } .lora-image { width: 100%; - height: 100px; + height: 150px; object-fit: cover; - object-position: center top; /* Align the image towards the top */ + object-position: center top; + /* Align the image towards the top */ } .lora-info { @@ -167,7 +194,8 @@ } .selected-lora-item-container { - max-height: 150px; /* Adjust this value as needed */ + max-height: 150px; + /* Adjust this value as needed */ overflow-y: auto; padding: 10px; } @@ -178,7 +206,8 @@ align-items: center; margin-bottom: 10px; /* flex-wrap: nowrap; Prevent wrapping */ - max-width: 30%; /* Ensure it doesn't exceed the container width */ + max-width: 30%; + /* Ensure it doesn't exceed the container width */ } .selected-lora-image { @@ -192,24 +221,26 @@ margin-right: 10px; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; /* Prevent wrapping */ - max-width: 100%; /* Adjust based on image and button width */ + white-space: nowrap; + /* Prevent wrapping */ + max-width: 100%; + /* Adjust based on image and button width */ } /* Menu dropdowns */ -#collapseExample{ +#collapseExample { margin-left: 20px; } -#historyCollapse{ +#historyCollapse { margin-left: 20px; } -#lorasCollapse{ +#lorasCollapse { margin-left: 20px; } -#lorasDropdown{ +#lorasDropdown { max-height: 75vh; } @@ -218,13 +249,16 @@ } .history-item img:hover { - transform: scale(1.05); /* Slight zoom */ - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); /* Add a shadow on hover */ + transform: scale(1.05); + /* Slight zoom */ + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); + /* Add a shadow on hover */ } /* Increase spacing between images */ .history-grid { - grid-gap: 20px; /* Increase space between items */ + grid-gap: 20px; + /* Increase space between items */ } .image-container { @@ -252,6 +286,16 @@ border-radius: 50%; } +.lora-card { + width: calc(33% - 15px); + border: 1px solid #ddd; + box-sizing: border-box; +} + +.btn.btn-primary.lora-request-btn { + width: 100%; +} + @media (max-width: 768px) { .button-group { flex-direction: row; @@ -261,8 +305,8 @@ .button-group .btn { width: 95%; margin-bottom: 10px; - padding: 12px 0; - font-size: 16px; + padding: 12px 0; + font-size: 16px; margin-left: 0; } @@ -307,7 +351,8 @@ #advanced-options { width: 97vw; - padding-left: 20px; /* Increased padding for consistent spacing */ + padding-left: 20px; + /* Increased padding for consistent spacing */ } .p-inputtext { @@ -315,13 +360,17 @@ } .history-grid { - grid-template-columns: repeat(2, 1fr); /* Ensure a 2x2 grid on mobile */ + grid-template-columns: repeat(2, 1fr); + /* Ensure a 2x2 grid on mobile */ } .card-body { - min-height: 70vh; /* Start with 50% of the viewport height */ - max-height: 80vh; /* Don't exceed 80% of the viewport height */ - overflow-y: auto; /* Allow scrolling if content exceeds max-height */ + min-height: 70vh; + /* Start with 50% of the viewport height */ + max-height: 80vh; + /* Don't exceed 80% of the viewport height */ + overflow-y: auto; + /* Allow scrolling if content exceeds max-height */ transition: height 0.5s ease-in-out; } @@ -332,25 +381,44 @@ } .pagination .btn { - width: 48%; /* Ensure pagination buttons are easy to tap */ + width: 48%; + /* Ensure pagination buttons are easy to tap */ padding: 10px 0; } /* Menu dropdowns */ - #collapseExample{ + #collapseExample { margin-left: 0; } - - #historyCollapse{ + + #historyCollapse { margin-left: 0; } - - #lorasCollapse{ + + #lorasCollapse { margin-left: 0; } - #lorasDropdown{ + #lorasDropdown { min-height: 60vh; max-height: 70vh; } -} + + .lora-card { + width: calc(50% - 15px); + } + + p { + font-size: 0.9rem; /* Slightly smaller text */ + margin-bottom: 5px; + } + + .d-flex.justify-content-between.mb-3 { + flex-direction: column; + align-items: stretch; /* Make them full width */ + } + + .d-flex.justify-content-between.mb-3 input { + margin-bottom: 0px; + } +} \ No newline at end of file diff --git a/src/app/home/options/options.component.html b/src/app/home/options/options.component.html index 8c48e72..9b614d0 100644 --- a/src/app/home/options/options.component.html +++ b/src/app/home/options/options.component.html @@ -88,74 +88,6 @@ - -
-
-

- Select and apply LoRAs to your image generation - - ? - -

- - -
- - -
- - - - -
Full Lora Preview
-
- - Full Lora Preview - -
- - -
-
- -
-
{{ lora.name }}
-

{{ lora.version }}

-
- -
-
- - -
-
Selected LoRAs:
-
-
- -
-
{{ loraItem.name }}
- - Strength: {{ loraItem.strength }} -
- -
-
-
-
-
-
@@ -291,7 +223,6 @@
{{ loraItem.name }}
-
@@ -310,12 +241,8 @@
{{ loraItem.name }}
Generated Image
- +
@@ -354,12 +281,8 @@
{{ loraItem.name }}
Generated Image
- +
@@ -387,5 +310,84 @@
{{ loraItem.name }}
+ +
+
+

+ + View and apply LoRAs to your image generation + + ? + + + + Show NSFW Loras? + + +

+ + + + + + +
+ +
+ + + + +
Full Lora Preview
+
+ + Full Lora Preview + +
+ + +
+ +
+ +
+
{{ lora.name }}
+

{{ lora.version }}

+
+ +
+
+ + +
+
Selected LoRAs:
+
+
+ +
+
{{ loraItem.name }}
+ + Strength: {{ loraItem.strength }} +
+ +
+
+
+
+
- + \ No newline at end of file diff --git a/src/app/home/options/options.component.ts b/src/app/home/options/options.component.ts index 2c006fc..f8e3d27 100644 --- a/src/app/home/options/options.component.ts +++ b/src/app/home/options/options.component.ts @@ -112,6 +112,9 @@ export class OptionsComponent implements OnInit { availableLoras: string[] = ['Loras1', 'Loras2', 'Loras3']; // Example Loras names // loras info + showNSFWLoras: boolean = false; + loraTagOptions: { optionLabel: string, optionValue: string, count: number }[] = []; + selectedTags: string[] = []; loras: any[] = []; loraSearchQuery: string = ''; filteredLoras: any[] = []; @@ -493,6 +496,7 @@ export class OptionsComponent implements OnInit { } this.filterLoras(); + this.refreshLoraFiltersList(); // Remove any selected loras that are not available for the selected model this.selectedLoras = this.selectedLoras.filter(lora => this.filteredLoras.includes(lora)); @@ -586,6 +590,9 @@ export class OptionsComponent implements OnInit { // Save model localStorage.setItem("model", this.generationRequest.model); + + // Save showNSFWLoras + localStorage.setItem("showNSFWLoras", this.showNSFWLoras.toString()); } // Load session storage info of changed settings @@ -618,6 +625,9 @@ export class OptionsComponent implements OnInit { if (localStorage.getItem("lossy-images") != null) { this.generationRequest.lossy_images = localStorage.getItem("lossy-images") == 'true'; } + if (localStorage.getItem("showNSFWLoras") != null) { + this.showNSFWLoras = localStorage.getItem("showNSFWLoras") == 'true'; + } try { // Use the new queryImages function to load the initial set of images @@ -639,6 +649,7 @@ export class OptionsComponent implements OnInit { localStorage.removeItem("aspect-ratio"); localStorage.removeItem("fast-pass-code"); localStorage.removeItem('discordUserData'); + localStorage.removeItem('showNSFWLoras'); this.generationRequest.prompt = ""; this.generationRequest.negative_prompt = this.defaultNegativePrompt; this.generationRequest.strength = 0.8; @@ -646,6 +657,7 @@ export class OptionsComponent implements OnInit { this.generationRequest.guidance_scale = 7; this.generationRequest.model = "sonicDiffusionV4"; this.loginInfo = null; + this.showNSFWLoras = false; this.sharedService.setUserData(null); this.changeAspectRatio("square"); @@ -1437,6 +1449,7 @@ export class OptionsComponent implements OnInit { next: (response: any[]) => { this.loras = response; this.filterLoras(); + this.refreshLoraFiltersList(); }, error: (error) => { console.error('Error loading Loras:', error); @@ -1448,6 +1461,11 @@ export class OptionsComponent implements OnInit { // First filter by model type this.filteredLoras = this.loras.filter(lora => lora.base_model === this.models_types[this.generationRequest.model]); + // Filter by nsfw (ie. Hide nsfw if showNSFWLoras is false) + if (!this.showNSFWLoras) { + this.filteredLoras = this.filteredLoras.filter(lora => !lora.is_nsfw); + } + // Then filter by search query this.filteredLoras = this.filteredLoras.filter(lora => (lora.name.toLowerCase().includes(this.loraSearchQuery.toLowerCase()) || lora.version.toLowerCase().includes(this.loraSearchQuery.toLowerCase())) @@ -1455,6 +1473,11 @@ export class OptionsComponent implements OnInit { // Sort by most uses this.filteredLoras = this.filteredLoras.sort((a, b) => b.uses - a.uses); + + // Filter by selected filters + if (this.selectedTags.length > 0) { + this.filteredLoras = this.filteredLoras.filter(lora => lora.tags.some((tag: string) => this.selectedTags.includes(tag))); + } } // Function to select a LoRA @@ -1617,7 +1640,7 @@ export class OptionsComponent implements OnInit { } updateFavoriteImages() { - if (this.favoriteSearchQuery){ + if (this.favoriteSearchQuery) { const filteredImages = this.imageHistoryMetadata; this.favoriteImageHistoryMetadata = this.imageHistoryMetadata.filter(image => image.favorite); @@ -1862,4 +1885,46 @@ export class OptionsComponent implements OnInit { } } //#endregion + + toggleFilter(filter: string) { + const index = this.selectedTags.indexOf(filter); + if (index > -1) { + // If filter is already active, remove it + this.selectedTags.splice(index, 1); + } else { + // Otherwise, add it + this.selectedTags.push(filter); + } + this.filterLoras(); + } + + refreshLoraFiltersList() { + // Start fresh + this.loraTagOptions = []; + + // First, just count occurrences + this.filteredLoras.forEach((lora: any) => { + lora.tags.forEach((tag: string) => { + // Try to find existing option by optionValue (which we won't alter later) + let existing = this.loraTagOptions.find(option => option.optionValue === tag); + if (!existing) { + this.loraTagOptions.push({ + optionLabel: tag, + optionValue: tag, // keep original tag here for lookups + count: 1 + }); + } else { + existing.count++; + } + }); + }); + + // Sort by count now that counting is complete + this.loraTagOptions.sort((a, b) => b.count - a.count); + + // Now, after sorting and finalizing counts, modify the displayed label + this.loraTagOptions.forEach(option => { + option.optionLabel = `${option.optionValue} (${option.count})`; + }); + } } diff --git a/src/app/notification.service.ts b/src/app/notification.service.ts index a500cd5..4db97ff 100644 --- a/src/app/notification.service.ts +++ b/src/app/notification.service.ts @@ -12,7 +12,7 @@ export class NotificationService { public userId: string = uuidv4(); // Generate a new UUID for the user //private apiBaseUrl = 'http://76.157.184.213:9000'; - private apiBaseUrl = 'https://mobians.azurewebsites.net' + private apiBaseUrl = 'https://api.mobians.ai'; constructor(private http: HttpClient, private swPush: SwPush) { } diff --git a/src/modules/home.module.ts b/src/modules/home.module.ts index c91f51c..1059dcf 100644 --- a/src/modules/home.module.ts +++ b/src/modules/home.module.ts @@ -31,6 +31,10 @@ import { ChipModule } from 'primeng/chip'; import { TabViewModule } from 'primeng/tabview'; import { RouterModule } from '@angular/router'; import { MessagesModule } from 'primeng/messages'; +import { InputSwitchModule } from 'primeng/inputswitch'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { ChipsModule } from 'primeng/chips'; +import { MultiSelectModule } from 'primeng/multiselect'; @NgModule({ @@ -65,6 +69,10 @@ import { MessagesModule } from 'primeng/messages'; TabViewModule, RouterModule, MessagesModule, + InputSwitchModule, + SelectButtonModule, + MultiSelectModule, + ChipsModule, ], exports: [ ImageGridComponent,