diff --git a/public/css/select2-overrides.css b/public/css/select2-overrides.css index 52918be45..1e394c035 100644 --- a/public/css/select2-overrides.css +++ b/public/css/select2-overrides.css @@ -19,11 +19,24 @@ color: var(--SmartThemeBodyColor); } +.select2-container .select2-search__field { + opacity: 0.8; +} + +.select2-container .select2-selection--single .select2-selection__rendered { + color: var(--SmartThemeBodyColor); + line-height: revert; + padding-left: unset; +} + +.select2-container .select2-results>.select2-results__options { + max-height: 300px; +} + .select2-container .select2-selection--multiple .select2-selection__choice__remove { padding: revert; border-right: 1px solid var(--white30a); font-size: 1.1em; - } .select2-container .select2-selection--multiple .select2-selection__choice__display { @@ -58,7 +71,8 @@ background-color: var(--SmartThemeBodyColor); } -.select2-container .select2-selection--multiple { +.select2-container .select2-selection--multiple, +.select2-container .select2-selection--single { background-color: var(--black30a); color: var(--SmartThemeBodyColor); border: 1px solid var(--white30a); @@ -67,11 +81,13 @@ padding: 3px 5px; } -.select2-container.select2-container--focus .select2-selection--multiple { +.select2-container.select2-container--focus .select2-selection--multiple, +.select2-container.select2-container--focus .select2-selection--single { border: 1px solid var(--white30a); } -.select2-container .select2-selection--multiple .select2-selection__choice { +.select2-container .select2-selection--multiple .select2-selection__choice, +.select2-container .select2-selection--single .select2-selection__choice { border-radius: 5px; border-style: solid; border-width: 1px; @@ -119,7 +135,8 @@ border-radius: 2px; } -.select2-container .select2-selection--multiple .select2-selection__choice__remove { +.select2-container .select2-selection--multiple .select2-selection__choice__remove, +.select2-container .select2-selection--single .select2-selection__choice__remove { color: var(--SmartThemeBodyColor); } @@ -131,4 +148,4 @@ background-color: var(--SmartThemeBlurTintColor); text-align: center; line-height: 14px; -} \ No newline at end of file +} diff --git a/public/index.html b/public/index.html index 2ca5ebac5..26d9382ed 100644 --- a/public/index.html +++ b/public/index.html @@ -39,6 +39,7 @@ + @@ -72,6 +73,7 @@ + @@ -1849,19 +1851,21 @@ For privacy reasons, your API key will be hidden after you reload the page.
-

Mancer API url

- Example: https://neuro.mancer.tech/webui/MODEL/api +

Mancer Model

+ +

Mancer API URL

+ Example: https://neuro.mancer.tech/webui/MODEL/api
-

Blocking API url

+

Blocking API URL

Example: http://127.0.0.1:5000/api
-

Streaming API url

+

Streaming API URL

Example: ws://127.0.0.1:5005/api/v1/stream
diff --git a/public/lib/select2-search-placeholder.js b/public/lib/select2-search-placeholder.js new file mode 100644 index 000000000..88ac6e397 --- /dev/null +++ b/public/lib/select2-search-placeholder.js @@ -0,0 +1,25 @@ +(function($) { + + var Defaults = $.fn.select2.amd.require('select2/defaults'); + + $.extend(Defaults.defaults, { + searchInputPlaceholder: '', + searchInputCssClass: '', + }); + + var SearchDropdown = $.fn.select2.amd.require('select2/dropdown/search'); + + var _renderSearchDropdown = SearchDropdown.prototype.render; + + SearchDropdown.prototype.render = function(decorated) { + + // invoke parent method + var $rendered = _renderSearchDropdown.apply(this, Array.prototype.slice.apply(arguments)); + + this.$search.attr('placeholder', this.options.get('searchInputPlaceholder')); + this.$search.addClass(this.options.get('searchInputCssClass')); + + return $rendered; + }; + +})(window.jQuery); diff --git a/public/script.js b/public/script.js index dadfc2998..ac3a62236 100644 --- a/public/script.js +++ b/public/script.js @@ -177,6 +177,7 @@ import { import { applyLocale } from "./scripts/i18n.js"; import { getTokenCount, getTokenizerModel, saveTokenCache } from "./scripts/tokenizers.js"; import { initPersonas, selectCurrentPersona, setPersonaDescription } from "./scripts/personas.js"; +import { loadMancerModels } from "./scripts/mancer-settings.js"; //exporting functions and vars for mods export { @@ -5039,10 +5040,9 @@ export function setGenerationParamsFromPreset(preset) { } if (preset.max_length !== undefined) { - max_context = preset.max_length; - - const needsUnlock = max_context > MAX_CONTEXT_DEFAULT; + const needsUnlock = preset.max_length > MAX_CONTEXT_DEFAULT; $('#max_context_unlocked').prop('checked', needsUnlock).trigger('change'); + max_context = preset.max_length; $("#max_context").val(max_context); $("#max_context_counter").text(`${max_context}`); @@ -7574,6 +7574,10 @@ jQuery(async function () { api_use_mancer_webui = enabled; saveSettingsDebounced(); getStatus(); + + if (enabled) { + loadMancerModels(); + } }); $("#api_button_textgenerationwebui").click(async function (e) { @@ -8479,6 +8483,7 @@ jQuery(async function () { '.ui-widget', '.text_pole', '#toast-container', + '.select2-results', ]; for (const id of forbiddenTargets) { if (clickTarget.closest(id).length > 0) { diff --git a/public/scripts/mancer-settings.js b/public/scripts/mancer-settings.js new file mode 100644 index 000000000..a6cfd9ca0 --- /dev/null +++ b/public/scripts/mancer-settings.js @@ -0,0 +1,79 @@ +import { api_server_textgenerationwebui, getRequestHeaders, setGenerationParamsFromPreset } from "../script.js"; +import { getDeviceInfo } from "./RossAscends-mods.js"; + +let models = []; + +/** + * @param {string} modelId + */ +export function getMancerModelURL(modelId) { + return `https://neuro.mancer.tech/webui/${modelId}/api`; +} + +export async function loadMancerModels() { + try { + const response = await fetch('/api/mancer/models', { + method: 'POST', + headers: getRequestHeaders(), + }); + + if (!response.ok) { + return; + } + + const data = await response.json(); + models = data; + + $('#mancer_model').empty(); + for (const model of data) { + const option = document.createElement('option'); + option.value = model.id; + option.text = model.name; + option.selected = api_server_textgenerationwebui === getMancerModelURL(model.id); + $('#mancer_model').append(option); + } + + } catch { + console.warn('Failed to load Mancer models'); + } +} + +function onMancerModelSelect() { + const modelId = String($('#mancer_model').val()); + const url = getMancerModelURL(modelId); + $('#mancer_api_url_text').val(url); + $('#api_button_textgenerationwebui').trigger('click'); + + const context = models.find(x => x.id === modelId)?.context; + setGenerationParamsFromPreset({ max_length: context }); +} + +function getMancerModelTemplate(option) { + const model = models.find(x => x.id === option?.element?.value); + + if (!option.id || !model) { + return option.text; + } + + return $((` +
+
${DOMPurify.sanitize(model.name)} | ${model.context} ctx
+ ${DOMPurify.sanitize(model.description)} +
+ `)); +} + +jQuery(function () { + $('#mancer_model').on('change', onMancerModelSelect); + + const deviceInfo = getDeviceInfo(); + if (deviceInfo && deviceInfo.device.type === 'desktop') { + $('#mancer_model').select2({ + placeholder: 'Select a model', + searchInputPlaceholder: 'Search models...', + searchInputCssClass: 'text_pole', + width: '100%', + templateResult: getMancerModelTemplate, + }); + } +}); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index d6c27f546..4eb50f6b4 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -1153,15 +1153,15 @@ const compareFunc = (first, second) => { return Math.random() > 0.5 ? 1 : -1; } + const a = first[power_user.sort_field]; + const b = second[power_user.sort_field]; + + if (power_user.sort_field === 'create_date') { + return sortMoments(timestampToMoment(b), timestampToMoment(a)); + } + switch (power_user.sort_rule) { case 'boolean': - const a = first[power_user.sort_field]; - const b = second[power_user.sort_field]; - - if (power_user.sort_field === 'create_date') { - return sortMoments(timestampToMoment(a), timestampToMoment(b)); - } - if (a === true || a === 'true') return 1; // Prioritize 'true' or true if (b === true || b === 'true') return -1; // Prioritize 'true' or true if (a && !b) return -1; // Move truthy values to the end @@ -1169,9 +1169,9 @@ const compareFunc = (first, second) => { if (a === b) return 0; // Sort equal values normally return a < b ? -1 : 1; // Sort non-boolean values normally default: - return typeof first[power_user.sort_field] == "string" - ? first[power_user.sort_field].localeCompare(second[power_user.sort_field]) - : first[power_user.sort_field] - second[power_user.sort_field]; + return typeof a == "string" + ? a.localeCompare(b) + : a - b; } }; diff --git a/server.js b/server.js index f7badb75a..c566422b3 100644 --- a/server.js +++ b/server.js @@ -58,6 +58,10 @@ const { Tokenizer } = require('@agnai/web-tokenizers'); const _ = require('lodash'); const { generateRequestUrl, normaliseResponse } = require('google-translate-api-browser'); +// Unrestrict console logs display limit +util.inspect.defaultOptions.maxArrayLength = null; +util.inspect.defaultOptions.maxStringLength = null; + // Create files before running anything else createDefaultFiles(); @@ -812,6 +816,31 @@ app.post("/getchat", jsonParser, function (request, response) { } }); +app.post("/api/mancer/models", jsonParser, async function (_req, res) { + try { + const response = await fetch('https://mancer.tech/internal/api/models'); + const data = await response.json(); + + if (!response.ok) { + console.log('Mancer models endpoint is offline.'); + return res.json([]); + } + + if (!Array.isArray(data.models)) { + console.log('Mancer models response is not an array.') + return res.json([]); + } + + const modelIds = data.models.map(x => x.id); + console.log('Mancer models available:', modelIds); + + return res.json(data.models); + } catch (error) { + console.error(error); + return res.json([]); + } +}); + // Only called for kobold and ooba/mancer app.post("/getstatus", jsonParser, async function (request, response) { if (!request.body) return response.sendStatus(400); @@ -2151,6 +2180,7 @@ app.post("/importcharacter", urlencodedParser, async function (request, response importRisuSprites(jsonData); unsetFavFlag(jsonData); jsonData = readFromV2(jsonData); + jsonData["create_date"] = humanizedISO8601DateTime(); png_name = getPngName(jsonData.data?.name || jsonData.name); let char = JSON.stringify(jsonData); charaWrite(defaultAvatarPath, char, png_name, response, { file_name: png_name }); @@ -4260,7 +4290,7 @@ app.post('/generate_horde', jsonParser, async (request, response) => { }; if (request.header('Client-Agent') !== undefined) args.headers['Client-Agent'] = request.header('Client-Agent'); - console.log(args.body); + console.log(request.body); try { const data = await postAsync(url, args); return response.send(data);