Merge pull request #2964 from DarokCx/release Featherless model search improvements.
Signed| @@ -2260,7 +2260,37 @@ | |||
| 2260 | <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page."> | 2260 | <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page."> |
| 2261 | For privacy reasons, your API key will be hidden after you reload the page. | 2261 | For privacy reasons, your API key will be hidden after you reload the page. |
| 2262 | </div> | 2262 | </div> |
| 2263 | <select id="featherless_model"> | 2263 | <hr> |
| 2264 | <h4 data-i18n="Featherless Model Selection">Featherless Model Selection</h4> | ||
| 2265 | <div class="flex-container wide100p flexGap10"> | ||
| 2266 | <div class="flex1 overflowHidden wide100p"> | ||
| 2267 | <div class="flex-container marginBot10 alignitemscenter"> | ||
| 2268 | <input id="featherless_model_search_bar" class="text_pole width100p flex1 margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search..."> | ||
| 2269 | <select id="featherless_model_sort_order" class="margin0 text_pole"> | ||
| 2270 | <option value="search" data-i18n="Search" hidden>A-Z</option> | ||
| 2271 | <option value="asc">A-Z</option> | ||
| 2272 | <option value="desc">Z-A</option> | ||
| 2273 | <option value="date_asc">Date Asc</option> | ||
| 2274 | <option value="date_desc">Date Desc</option> | ||
| 2275 | </select> | ||
| 2276 | <select id="featherless_category_selection" class="text_pole"> | ||
| 2277 | <option value="" disabled selected data-i18n="category">category</option> | ||
| 2278 | <!-- <option value="Favorite" data-i18n="Top">Favorite</option> --> | ||
| 2279 | <option value="Top" data-i18n="Top">Top</option> | ||
| 2280 | <option value="New" data-i18n="New">New</option> | ||
| 2281 | <option value="All" data-i18n="All">All</option> | ||
| 2282 | </select> | ||
| 2283 | <select id="featherless_class_selection" class="text_pole"> | ||
| 2284 | <option value="" selected data-i18n="class">All Classes</option> | ||
| 2285 | </select> | ||
| 2286 | <div id="featherless_model_pagination_container" class="flex1"></div> | ||
| 2287 | <i id="featherless_model_grid_toggle" class="fa-solid fa-table-cells-large menu_button" data-i18n="[title]Toggle grid view" title="Toggle grid view"></i> | ||
| 2288 | </div> | ||
| 2289 | <div id="featherless_model_card_block" data-i18n="[no_desc_text]No model description" no_desc_text="[No description]"></div> | ||
| 2290 | </div> | ||
| 2291 | </div> | ||
| 2292 | <!-- Do not remove. Needed for the /model command to function --> | ||
| 2293 | <select id="featherless_model" class="displayNone"> | ||
| 2264 | <option value="" data-i18n="-- Connect to the API --"> | 2294 | <option value="" data-i18n="-- Connect to the API --"> |
| 2265 | -- Connect to the API -- | 2295 | -- Connect to the API -- |
| 2266 | </option> | 2296 | </option> |
| @@ -4,6 +4,7 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from | |||
| 4 | import { tokenizers } from './tokenizers.js'; | 4 | import { tokenizers } from './tokenizers.js'; |
| 5 | import { renderTemplateAsync } from './templates.js'; | 5 | import { renderTemplateAsync } from './templates.js'; |
| 6 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; | 6 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 7 | import { PAGINATION_TEMPLATE } from './utils.js'; | ||
| 7 | 8 | ||
| 8 | let mancerModels = []; | 9 | let mancerModels = []; |
| 9 | let togetherModels = []; | 10 | let togetherModels = []; |
| @@ -265,19 +266,212 @@ export async function loadAphroditeModels(data) { | |||
| 265 | } | 266 | } |
| 266 | } | 267 | } |
| 267 | 268 | ||
| 269 | let featherlessCurrentPage = 1; | ||
| 268 | export async function loadFeatherlessModels(data) { | 270 | export async function loadFeatherlessModels(data) { |
| 271 | const searchBar = document.getElementById('featherless_model_search_bar'); | ||
| 272 | const modelCardBlock = document.getElementById('featherless_model_card_block'); | ||
| 273 | const paginationContainer = $('#featherless_model_pagination_container'); | ||
| 274 | const sortOrderSelect = document.getElementById('featherless_model_sort_order'); | ||
| 275 | const classSelect = document.getElementById('featherless_class_selection'); | ||
| 276 | const categoriesSelect = document.getElementById('featherless_category_selection'); | ||
| 277 | const storageKey = 'FeatherlessModels_PerPage'; | ||
| 278 | |||
| 279 | // Store the original models data for search and filtering | ||
| 280 | let originalModels = []; | ||
| 281 | |||
| 269 | if (!Array.isArray(data)) { | 282 | if (!Array.isArray(data)) { |
| 270 | console.error('Invalid Featherless models data', data); | 283 | console.error('Invalid Featherless models data', data); |
| 271 | return; | 284 | return; |
| 272 | } | 285 | } |
| 273 | 286 | ||
| 287 | // Sort the data by model id (default A-Z) | ||
| 274 | data.sort((a, b) => a.id.localeCompare(b.id)); | 288 | data.sort((a, b) => a.id.localeCompare(b.id)); |
| 289 | originalModels = data; // Store the original data for search | ||
| 275 | featherlessModels = data; | 290 | featherlessModels = data; |
| 276 | 291 | ||
| 277 | if (!data.find(x => x.id === textgen_settings.featherless_model)) { | 292 | // Populate class select options with unique classes |
| 278 | textgen_settings.featherless_model = data[0]?.id || ''; | 293 | populateClassSelection(data); |
| 294 | |||
| 295 | // Retrieve the stored number of items per page or default to 5 | ||
| 296 | const perPage = Number(localStorage.getItem(storageKey)) || 10; | ||
| 297 | |||
| 298 | // Initialize pagination with the full set of models | ||
| 299 | const selectedModelPage = (data.findIndex(x => x.id === textgen_settings.featherless_model) / perPage) + 1; | ||
| 300 | featherlessCurrentPage = selectedModelPage > 0 ? selectedModelPage : 1; | ||
| 301 | setupPagination(originalModels, perPage); | ||
| 302 | |||
| 303 | // Function to set up pagination (also used for filtered results) | ||
| 304 | function setupPagination(models, perPage, pageNumber = featherlessCurrentPage) { | ||
| 305 | paginationContainer.pagination({ | ||
| 306 | dataSource: models, | ||
| 307 | pageSize: perPage, | ||
| 308 | pageNumber: pageNumber, | ||
| 309 | sizeChangerOptions: [6, 10, 26, 50, 100, 250, 500, 1000], | ||
| 310 | pageRange: 1, | ||
| 311 | showPageNumbers: true, | ||
| 312 | showSizeChanger: false, | ||
| 313 | prevText: '<', | ||
| 314 | nextText: '>', | ||
| 315 | formatNavigator: function (currentPage, totalPage) { | ||
| 316 | return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage; | ||
| 317 | }, | ||
| 318 | showNavigator: true, | ||
| 319 | callback: function (modelsOnPage, pagination) { | ||
| 320 | modelCardBlock.innerHTML = ''; | ||
| 321 | |||
| 322 | modelsOnPage.forEach(model => { | ||
| 323 | const card = document.createElement('div'); | ||
| 324 | card.classList.add('model-card'); | ||
| 325 | |||
| 326 | const modelNameContainer = document.createElement('div'); | ||
| 327 | modelNameContainer.classList.add('model-name-container'); | ||
| 328 | |||
| 329 | const modelTitle = document.createElement('div'); | ||
| 330 | modelTitle.classList.add('model-title'); | ||
| 331 | modelTitle.textContent = model.id.replace(/_/g, '_\u200B'); | ||
| 332 | modelNameContainer.appendChild(modelTitle); | ||
| 333 | |||
| 334 | const detailsContainer = document.createElement('div'); | ||
| 335 | detailsContainer.classList.add('details-container'); | ||
| 336 | |||
| 337 | const modelClassDiv = document.createElement('div'); | ||
| 338 | modelClassDiv.classList.add('model-class'); | ||
| 339 | modelClassDiv.textContent = `Class: ${model.model_class || 'N/A'}`; | ||
| 340 | |||
| 341 | const contextLengthDiv = document.createElement('div'); | ||
| 342 | contextLengthDiv.classList.add('model-context-length'); | ||
| 343 | contextLengthDiv.textContent = `Context Length: ${model.context_length}`; | ||
| 344 | |||
| 345 | const dateAddedDiv = document.createElement('div'); | ||
| 346 | dateAddedDiv.classList.add('model-date-added'); | ||
| 347 | dateAddedDiv.textContent = `Added On: ${new Date(model.updated_at).toLocaleDateString()}`; | ||
| 348 | |||
| 349 | detailsContainer.appendChild(modelClassDiv); | ||
| 350 | detailsContainer.appendChild(contextLengthDiv); | ||
| 351 | detailsContainer.appendChild(dateAddedDiv); | ||
| 352 | |||
| 353 | card.appendChild(modelNameContainer); | ||
| 354 | card.appendChild(detailsContainer); | ||
| 355 | |||
| 356 | modelCardBlock.appendChild(card); | ||
| 357 | |||
| 358 | if (model.id === textgen_settings.featherless_model) { | ||
| 359 | card.classList.add('selected'); | ||
| 360 | } | ||
| 361 | |||
| 362 | card.addEventListener('click', function () { | ||
| 363 | document.querySelectorAll('.model-card').forEach(c => c.classList.remove('selected')); | ||
| 364 | card.classList.add('selected'); | ||
| 365 | onFeatherlessModelSelect(model.id); | ||
| 366 | }); | ||
| 367 | }); | ||
| 368 | |||
| 369 | // Update the current page value whenever the page changes | ||
| 370 | featherlessCurrentPage = pagination.pageNumber; | ||
| 371 | }, | ||
| 372 | afterSizeSelectorChange: function (e) { | ||
| 373 | const newPerPage = e.target.value; | ||
| 374 | localStorage.setItem('Models_PerPage', newPerPage); | ||
| 375 | setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number | ||
| 376 | }, | ||
| 377 | }); | ||
| 378 | } | ||
| 379 | |||
| 380 | // Unset previously added listeners | ||
| 381 | $(searchBar).off('input'); | ||
| 382 | $(sortOrderSelect).off('change'); | ||
| 383 | $(classSelect).off('change'); | ||
| 384 | $(categoriesSelect).off('change'); | ||
| 385 | |||
| 386 | // Add event listener for input on the search bar | ||
| 387 | searchBar.addEventListener('input', function () { | ||
| 388 | applyFiltersAndSort(); | ||
| 389 | }); | ||
| 390 | |||
| 391 | // Add event listener for the sort order select | ||
| 392 | sortOrderSelect.addEventListener('change', function () { | ||
| 393 | applyFiltersAndSort(); | ||
| 394 | }); | ||
| 395 | |||
| 396 | // Add event listener for the class select | ||
| 397 | classSelect.addEventListener('change', function () { | ||
| 398 | applyFiltersAndSort(); | ||
| 399 | }); | ||
| 400 | |||
| 401 | categoriesSelect.addEventListener('change', function () { | ||
| 402 | applyFiltersAndSort(); | ||
| 403 | }); | ||
| 404 | |||
| 405 | // Function to populate class selection dropdown | ||
| 406 | function populateClassSelection(models) { | ||
| 407 | const uniqueClasses = [...new Set(models.map(model => model.model_class).filter(Boolean))]; // Get unique class names | ||
| 408 | uniqueClasses.sort((a, b) => a.localeCompare(b)); | ||
| 409 | uniqueClasses.forEach(className => { | ||
| 410 | const option = document.createElement('option'); | ||
| 411 | option.value = className; | ||
| 412 | option.textContent = className; | ||
| 413 | classSelect.appendChild(option); | ||
| 414 | }); | ||
| 279 | } | 415 | } |
| 280 | 416 | ||
| 417 | // Function to apply sorting and filtering based on user input | ||
| 418 | async function applyFiltersAndSort() { | ||
| 419 | if (!(searchBar instanceof HTMLInputElement) || | ||
| 420 | !(sortOrderSelect instanceof HTMLSelectElement) || | ||
| 421 | !(classSelect instanceof HTMLSelectElement) || | ||
| 422 | !(categoriesSelect instanceof HTMLSelectElement)) { | ||
| 423 | return; | ||
| 424 | } | ||
| 425 | const searchQuery = searchBar.value.toLowerCase(); | ||
| 426 | const selectedSortOrder = sortOrderSelect.value; | ||
| 427 | const selectedClass = classSelect.value; | ||
| 428 | const selectedCategory = categoriesSelect.value; | ||
| 429 | let featherlessTop = []; | ||
| 430 | let featherlessNew = []; | ||
| 431 | |||
| 432 | if (selectedCategory === 'Top') { | ||
| 433 | featherlessTop = await fetchFeatherlessStats(); | ||
| 434 | } | ||
| 435 | const featherlessIds = featherlessTop.map(stat => stat.id); | ||
| 436 | if (selectedCategory === 'New') { | ||
| 437 | featherlessNew = await fetchFeatherlessNew(); | ||
| 438 | } | ||
| 439 | const featherlessNewIds = featherlessNew.map(stat => stat.id); | ||
| 440 | |||
| 441 | let filteredModels = originalModels.filter(model => { | ||
| 442 | const matchesSearch = model.id.toLowerCase().includes(searchQuery); | ||
| 443 | const matchesClass = selectedClass ? model.model_class === selectedClass : true; | ||
| 444 | const matchesTop = featherlessIds.includes(model.id); | ||
| 445 | const matchesNew = featherlessNewIds.includes(model.id); | ||
| 446 | |||
| 447 | if (selectedCategory === 'All') { | ||
| 448 | return matchesSearch && matchesClass; | ||
| 449 | } | ||
| 450 | else if (selectedCategory === 'Top') { | ||
| 451 | return matchesSearch && matchesClass && matchesTop; | ||
| 452 | } | ||
| 453 | else if (selectedCategory === 'New') { | ||
| 454 | return matchesSearch && matchesClass && matchesNew; | ||
| 455 | } | ||
| 456 | else { | ||
| 457 | return matchesSearch; | ||
| 458 | } | ||
| 459 | }); | ||
| 460 | |||
| 461 | if (selectedSortOrder === 'asc') { | ||
| 462 | filteredModels.sort((a, b) => a.id.localeCompare(b.id)); | ||
| 463 | } else if (selectedSortOrder === 'desc') { | ||
| 464 | filteredModels.sort((a, b) => b.id.localeCompare(a.id)); | ||
| 465 | } else if (selectedSortOrder === 'date_asc') { | ||
| 466 | filteredModels.sort((a, b) => a.updated_at.localeCompare(b.updated_at)); | ||
| 467 | } else if (selectedSortOrder === 'date_desc') { | ||
| 468 | filteredModels.sort((a, b) => b.updated_at.localeCompare(a.updated_at)); | ||
| 469 | } | ||
| 470 | |||
| 471 | setupPagination(filteredModels, Number(localStorage.getItem(storageKey)) || perPage, featherlessCurrentPage); | ||
| 472 | } | ||
| 473 | |||
| 474 | // Required to keep the /model command function | ||
| 281 | $('#featherless_model').empty(); | 475 | $('#featherless_model').empty(); |
| 282 | for (const model of data) { | 476 | for (const model of data) { |
| 283 | const option = document.createElement('option'); | 477 | const option = document.createElement('option'); |
| @@ -288,15 +482,49 @@ export async function loadFeatherlessModels(data) { | |||
| 288 | } | 482 | } |
| 289 | } | 483 | } |
| 290 | 484 | ||
| 291 | function onFeatherlessModelSelect() { | 485 | async function fetchFeatherlessStats() { |
| 292 | const modelId = String($('#featherless_model').val()); | 486 | const response = await fetch('https://api.featherless.ai/feather/popular'); |
| 487 | const data = await response.json(); | ||
| 488 | return data.popular; | ||
| 489 | } | ||
| 490 | |||
| 491 | async function fetchFeatherlessNew() { | ||
| 492 | const response = await fetch('https://api.featherless.ai/feather/models?sort=-created_at&perPage=10'); | ||
| 493 | const data = await response.json(); | ||
| 494 | return data.items; | ||
| 495 | } | ||
| 496 | |||
| 497 | function onFeatherlessModelSelect(modelId) { | ||
| 498 | const model = featherlessModels.find(x => x.id === modelId); | ||
| 293 | textgen_settings.featherless_model = modelId; | 499 | textgen_settings.featherless_model = modelId; |
| 500 | $('#featherless_model').val(modelId); | ||
| 294 | $('#api_button_textgenerationwebui').trigger('click'); | 501 | $('#api_button_textgenerationwebui').trigger('click'); |
| 295 | const model = featherlessModels.find(x => x.id === modelId); | ||
| 296 | setGenerationParamsFromPreset({ max_length: model.context_length }); | 502 | setGenerationParamsFromPreset({ max_length: model.context_length }); |
| 297 | } | 503 | } |
| 298 | 504 | ||
| 505 | let featherlessIsGridView = false; // Default state set to grid view | ||
| 506 | |||
| 507 | // Ensure the correct initial view is applied when the page loads | ||
| 508 | document.addEventListener('DOMContentLoaded', function () { | ||
| 509 | const modelCardBlock = document.getElementById('featherless_model_card_block'); | ||
| 510 | modelCardBlock.classList.add('list-view'); | ||
| 511 | |||
| 512 | const toggleButton = document.getElementById('featherless_model_grid_toggle'); | ||
| 513 | toggleButton.addEventListener('click', function () { | ||
| 514 | // Toggle between grid and list view | ||
| 515 | if (featherlessIsGridView) { | ||
| 516 | modelCardBlock.classList.remove('grid-view'); | ||
| 517 | modelCardBlock.classList.add('list-view'); | ||
| 518 | this.title = 'Toggle to grid view'; | ||
| 519 | } else { | ||
| 520 | modelCardBlock.classList.remove('list-view'); | ||
| 521 | modelCardBlock.classList.add('grid-view'); | ||
| 522 | this.title = 'Toggle to list view'; | ||
| 523 | } | ||
| 299 | 524 | ||
| 525 | featherlessIsGridView = !featherlessIsGridView; | ||
| 526 | }); | ||
| 527 | }); | ||
| 300 | function onMancerModelSelect() { | 528 | function onMancerModelSelect() { |
| 301 | const modelId = String($('#mancer_model').val()); | 529 | const modelId = String($('#mancer_model').val()); |
| 302 | textgen_settings.mancer_model = modelId; | 530 | textgen_settings.mancer_model = modelId; |
| @@ -469,20 +697,6 @@ function getAphroditeModelTemplate(option) { | |||
| 469 | `)); | 697 | `)); |
| 470 | } | 698 | } |
| 471 | 699 | ||
| 472 | function getFeatherlessModelTemplate(option) { | ||
| 473 | const model = featherlessModels.find(x => x.id === option?.element?.value); | ||
| 474 | |||
| 475 | if (!option.id || !model) { | ||
| 476 | return option.text; | ||
| 477 | } | ||
| 478 | |||
| 479 | return $((` | ||
| 480 | <div class="flex-container flexFlowColumn"> | ||
| 481 | <div><strong>${DOMPurify.sanitize(model.name)}</strong> | <span>${model.context_length || '???'} tokens</span></div> | ||
| 482 | </div> | ||
| 483 | `)); | ||
| 484 | } | ||
| 485 | |||
| 486 | async function downloadOllamaModel() { | 700 | async function downloadOllamaModel() { |
| 487 | try { | 701 | try { |
| 488 | const serverUrl = textgen_settings.server_urls[textgen_types.OLLAMA]; | 702 | const serverUrl = textgen_settings.server_urls[textgen_types.OLLAMA]; |
| @@ -670,9 +884,9 @@ export function initTextGenModels() { | |||
| 670 | $('#ollama_download_model').on('click', downloadOllamaModel); | 884 | $('#ollama_download_model').on('click', downloadOllamaModel); |
| 671 | $('#vllm_model').on('change', onVllmModelSelect); | 885 | $('#vllm_model').on('change', onVllmModelSelect); |
| 672 | $('#aphrodite_model').on('change', onAphroditeModelSelect); | 886 | $('#aphrodite_model').on('change', onAphroditeModelSelect); |
| 673 | $('#featherless_model').on('change', onFeatherlessModelSelect); | ||
| 674 | $('#tabby_download_model').on('click', downloadTabbyModel); | 887 | $('#tabby_download_model').on('click', downloadTabbyModel); |
| 675 | $('#tabby_model').on('change', onTabbyModelSelect); | 888 | $('#tabby_model').on('change', onTabbyModelSelect); |
| 889 | $('#featherless_model').on('change', () => onFeatherlessModelSelect(String($('#featherless_model').val()))); | ||
| 676 | 890 | ||
| 677 | const providersSelect = $('.openrouter_providers'); | 891 | const providersSelect = $('.openrouter_providers'); |
| 678 | for (const provider of OPENROUTER_PROVIDERS) { | 892 | for (const provider of OPENROUTER_PROVIDERS) { |
| @@ -745,13 +959,6 @@ export function initTextGenModels() { | |||
| 745 | width: '100%', | 959 | width: '100%', |
| 746 | templateResult: getAphroditeModelTemplate, | 960 | templateResult: getAphroditeModelTemplate, |
| 747 | }); | 961 | }); |
| 748 | $('#featherless_model').select2({ | ||
| 749 | placeholder: 'Select a model', | ||
| 750 | searchInputPlaceholder: 'Search models...', | ||
| 751 | searchInputCssClass: 'text_pole', | ||
| 752 | width: '100%', | ||
| 753 | templateResult: getFeatherlessModelTemplate, | ||
| 754 | }); | ||
| 755 | providersSelect.select2({ | 962 | providersSelect.select2({ |
| 756 | sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)), | 963 | sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)), |
| 757 | placeholder: 'Select providers. No selection = all providers.', | 964 | placeholder: 'Select providers. No selection = all providers.', |
| @@ -193,6 +193,7 @@ const settings = { | |||
| 193 | openrouter_allow_fallbacks: true, | 193 | openrouter_allow_fallbacks: true, |
| 194 | xtc_threshold: 0.1, | 194 | xtc_threshold: 0.1, |
| 195 | xtc_probability: 0, | 195 | xtc_probability: 0, |
| 196 | featherless_model: '', | ||
| 196 | }; | 197 | }; |
| 197 | 198 | ||
| 198 | export let textgenerationwebui_banned_in_macros = []; | 199 | export let textgenerationwebui_banned_in_macros = []; |
| @@ -5541,3 +5541,129 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5541 | #InstructSequencesColumn details:not(:last-of-type) { | 5541 | #InstructSequencesColumn details:not(:last-of-type) { |
| 5542 | margin-bottom: 5px; | 5542 | margin-bottom: 5px; |
| 5543 | } | 5543 | } |
| 5544 | |||
| 5545 | #user_avatar_block { | ||
| 5546 | display: flex; | ||
| 5547 | flex-wrap: wrap; | ||
| 5548 | gap: 10px; | ||
| 5549 | } | ||
| 5550 | |||
| 5551 | /* Main structure for the model cards */ | ||
| 5552 | .model-card { | ||
| 5553 | display: flex; | ||
| 5554 | justify-content: space-between; | ||
| 5555 | align-items: center; | ||
| 5556 | padding: 5px; | ||
| 5557 | border: 1px solid #333; | ||
| 5558 | border-radius: 8px; | ||
| 5559 | background-color: #222; | ||
| 5560 | color: #fff; | ||
| 5561 | margin: 7px; | ||
| 5562 | width: calc(100% - 7px); | ||
| 5563 | box-sizing: border-box; | ||
| 5564 | transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out, border 0.2s ease-in-out; | ||
| 5565 | } | ||
| 5566 | |||
| 5567 | .model-card .details-container { | ||
| 5568 | text-align: right; | ||
| 5569 | } | ||
| 5570 | |||
| 5571 | .model-card:hover { | ||
| 5572 | transform: scale(1.01); | ||
| 5573 | background-color: #444; | ||
| 5574 | transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out; /* Smooth transition */ | ||
| 5575 | } | ||
| 5576 | |||
| 5577 | .model-card.selected { | ||
| 5578 | border: 2px solid var(--okGreen70a); | ||
| 5579 | background-color: var(--okGreen70a); | ||
| 5580 | } | ||
| 5581 | |||
| 5582 | .model-info { | ||
| 5583 | flex: 1; | ||
| 5584 | white-space: nowrap; | ||
| 5585 | overflow: hidden; | ||
| 5586 | text-overflow: ellipsis; | ||
| 5587 | } | ||
| 5588 | |||
| 5589 | .model-title { | ||
| 5590 | font-size: 13px; | ||
| 5591 | font-weight: bold; | ||
| 5592 | overflow: hidden; | ||
| 5593 | } | ||
| 5594 | |||
| 5595 | .model-details { | ||
| 5596 | display: flex; | ||
| 5597 | flex-direction: column; | ||
| 5598 | align-items: flex-end; | ||
| 5599 | text-align: right; | ||
| 5600 | min-width: 120px; | ||
| 5601 | } | ||
| 5602 | |||
| 5603 | .model-class, .model-context-length, .model-date-added { | ||
| 5604 | font-size: 10px; | ||
| 5605 | } | ||
| 5606 | |||
| 5607 | .model-class, .model-context-length { | ||
| 5608 | margin-bottom: 5px; | ||
| 5609 | } | ||
| 5610 | |||
| 5611 | #featherless_model_pagination_container .paginationjs-nav { | ||
| 5612 | min-width: max-content; | ||
| 5613 | } | ||
| 5614 | |||
| 5615 | #featherless_model_card_block.grid-view { | ||
| 5616 | grid-template-columns: repeat(2, 1fr); | ||
| 5617 | display: flex; | ||
| 5618 | flex-wrap: wrap; | ||
| 5619 | /* gap: 3px; */ | ||
| 5620 | justify-content: flex-start; | ||
| 5621 | } | ||
| 5622 | |||
| 5623 | /* Grid-view card */ | ||
| 5624 | #featherless_model_card_block.grid-view .model-card { | ||
| 5625 | flex-direction: column; | ||
| 5626 | flex: 1 1 calc(50% - 30px); | ||
| 5627 | } | ||
| 5628 | |||
| 5629 | #featherless_model_search_bar { | ||
| 5630 | width: 15ch; | ||
| 5631 | flex-grow: 0; | ||
| 5632 | align-self: center; | ||
| 5633 | } | ||
| 5634 | |||
| 5635 | #featherless_model_sort_order { | ||
| 5636 | width: auto; | ||
| 5637 | flex-shrink: 0; | ||
| 5638 | align-self: center; | ||
| 5639 | } | ||
| 5640 | |||
| 5641 | #featherless_model_grid_toggle { | ||
| 5642 | flex-shrink: 0; | ||
| 5643 | width: auto; | ||
| 5644 | cursor: pointer; | ||
| 5645 | } | ||
| 5646 | |||
| 5647 | #featherless_category_selection, | ||
| 5648 | #featherless_class_selection { | ||
| 5649 | display: flex; | ||
| 5650 | width: auto; | ||
| 5651 | align-self: center; | ||
| 5652 | } | ||
| 5653 | |||
| 5654 | @media (max-width: 768px) { | ||
| 5655 | .model-card { | ||
| 5656 | flex-direction: column; | ||
| 5657 | align-items: stretch; | ||
| 5658 | } | ||
| 5659 | |||
| 5660 | .model-info, .model-details, .model-date-added { | ||
| 5661 | width: 100%; | ||
| 5662 | text-align: left; | ||
| 5663 | } | ||
| 5664 | |||
| 5665 | #featherless_model_search_bar { | ||
| 5666 | width: 100%; | ||
| 5667 | } | ||
| 5668 | |||
| 5669 | } | ||