Merge branch 'staging' into group-join-examples
| @@ -849,11 +849,9 @@ export let is_send_press = false; //Send generation | ||
| 849 | 849 | |
| 850 | 850 | let this_del_mes = -1; |
| 851 | 851 | |
| 852 | 852 | //message editing and chat scroll position persistence |
| 853 | 853 | var this_edit_mes_chname = ''; |
| 854 | 854 | var this_edit_mes_id; |
| 855 | -var scroll_holder = 0; | |
| 856 | -var is_use_scroll_holder = false; | |
| 857 | 855 | |
| 858 | 856 | //settings |
| 859 | 857 | export let settings; |
| @@ -9727,16 +9725,16 @@ jQuery(async function () { | ||
| 9727 | 9725 | chooseBogusFolder($(this), tagId); |
| 9728 | 9726 | }); |
| 9729 | 9727 | |
| 9728 | + const cssAutofit = CSS.supports('field-sizing', 'content'); | |
| 9729 | + if (!cssAutofit) { | |
| 9730 | 9730 | /** |
| 9731 | 9731 | * Sets the scroll height of the edit textarea to fit the content. |
| 9732 | 9732 | * @param {HTMLTextAreaElement} e Textarea element to auto-fit |
| 9733 | 9733 | */ |
| 9734 | 9734 | function autoFitEditTextArea(e) { |
| 9735 | - scroll_holder = chatElement[0].scrollTop; | |
| 9736 | 9735 | e.style.height = '0px'; |
| 9737 | 9736 | const newHeight = e.scrollHeight + 4; |
| 9738 | 9737 | e.style.height = `${newHeight}px`; |
| 9739 | - is_use_scroll_holder = true; | |
| 9740 | 9738 | } |
| 9741 | 9739 | const autoFitEditTextAreaDebounced = debounce(autoFitEditTextArea, debounce_timeout.short); |
| 9742 | 9740 | document.addEventListener('input', e => { |
| @@ -9746,6 +9744,8 @@ jQuery(async function () { | ||
| 9746 | 9744 | immediately ? autoFitEditTextArea(e.target) : autoFitEditTextAreaDebounced(e.target); |
| 9747 | 9745 | } |
| 9748 | 9746 | }); |
| 9747 | + } | |
| 9748 | + | |
| 9749 | 9749 | const chatElementScroll = document.getElementById('chat'); |
| 9750 | 9750 | const chatScrollHandler = function () { |
| 9751 | 9751 | if (power_user.waifuMode) { |
| @@ -9767,12 +9767,6 @@ jQuery(async function () { | ||
| 9767 | 9767 | }; |
| 9768 | 9768 | chatElementScroll.addEventListener('wheel', chatScrollHandler, { passive: true }); |
| 9769 | 9769 | chatElementScroll.addEventListener('touchmove', chatScrollHandler, { passive: true }); |
| 9770 | - chatElementScroll.addEventListener('scroll', function () { | |
| 9771 | - if (is_use_scroll_holder) { | |
| 9772 | - this.scrollTop = scroll_holder; | |
| 9773 | - is_use_scroll_holder = false; | |
| 9774 | - } | |
| 9775 | - }, { passive: true }); | |
| 9776 | 9770 | |
| 9777 | 9771 | $(document).on('click', '.mes', function () { |
| 9778 | 9772 | //when a 'delete message' parent div is clicked |
| @@ -10511,14 +10505,16 @@ jQuery(async function () { | ||
| 10511 | 10505 | .closest('.mes_block') |
| 10512 | 10506 | .find('.mes_text') |
| 10513 | 10507 | .append( |
| 10514 | 10508 | '<textarea id=\'curEditTextarea\' class=\'edit_textarea mdHotkeys\' style=\'max-width:auto;\'></textarea>', |
| 10515 | 10509 | ); |
| 10516 | 10510 | $('#curEditTextarea').val(text); |
| 10517 | 10511 | let edit_textarea = $(this) |
| 10518 | 10512 | .closest('.mes_block') |
| 10519 | 10513 | .find('.edit_textarea'); |
| 10514 | + if (!cssAutofit) { | |
| 10520 | 10515 | edit_textarea.height(0); |
| 10521 | 10516 | edit_textarea.height(edit_textarea[0].scrollHeight); |
| 10517 | + } | |
| 10522 | 10518 | edit_textarea.focus(); |
| 10523 | 10519 | edit_textarea[0].setSelectionRange( //this sets the cursor at the end of the text |
| 10524 | 10520 | String(edit_textarea.val()).length, |
| @@ -887,7 +887,40 @@ export function initRossMods() { | ||
| 887 | 887 | saveSettingsDebounced(); |
| 888 | 888 | }); |
| 889 | 889 | |
| 890 | + const cssAutofit = CSS.supports('field-sizing', 'content'); | |
| 891 | + | |
| 892 | + if (cssAutofit) { | |
| 893 | + let lastHeight = chatBlock.offsetHeight; | |
| 894 | + const chatBlockResizeObserver = new ResizeObserver((entries) => { | |
| 895 | + for (const entry of entries) { | |
| 896 | + if (entry.target !== chatBlock) { | |
| 897 | + continue; | |
| 898 | + } | |
| 899 | + | |
| 900 | + const threshold = 1; | |
| 901 | + const newHeight = chatBlock.offsetHeight; | |
| 902 | + const deltaHeight = newHeight - lastHeight; | |
| 903 | + const isScrollAtBottom = Math.abs(chatBlock.scrollHeight - chatBlock.scrollTop - newHeight) <= threshold; | |
| 904 | + | |
| 905 | + if (!isScrollAtBottom && Math.abs(deltaHeight) > threshold) { | |
| 906 | + chatBlock.scrollTop -= deltaHeight; | |
| 907 | + } | |
| 908 | + lastHeight = newHeight; | |
| 909 | + } | |
| 910 | + }); | |
| 911 | + | |
| 912 | + chatBlockResizeObserver.observe(chatBlock); | |
| 913 | + } | |
| 914 | + | |
| 890 | 915 | sendTextArea.addEventListener('input', () => { |
| 916 | + saveUserInputDebounced(); | |
| 917 | + | |
| 918 | + if (cssAutofit) { | |
| 919 | + // Unset modifications made with a manual resize | |
| 920 | + sendTextArea.style.height = 'auto'; | |
| 921 | + return; | |
| 922 | + } | |
| 923 | + | |
| 891 | 924 | const hasContent = sendTextArea.value !== ''; |
| 892 | 925 | const fitsCurrentSize = sendTextArea.scrollHeight <= sendTextArea.offsetHeight; |
| 893 | 926 | const isScrollbarShown = sendTextArea.clientWidth < sendTextArea.offsetWidth; |
| @@ -895,7 +928,6 @@ export function initRossMods() { | ||
| 895 | 928 | const needsDebounce = hasContent && (fitsCurrentSize || (isScrollbarShown && isHalfScreenHeight)); |
| 896 | 929 | if (needsDebounce) autoFitSendTextAreaDebounced(); |
| 897 | 930 | else autoFitSendTextArea(); |
| 898 | - saveUserInputDebounced(); | |
| 899 | 931 | }); |
| 900 | 932 | |
| 901 | 933 | restoreUserInput(); |
| @@ -15,6 +15,7 @@ import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashComm | ||
| 15 | 15 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 16 | 16 | import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js'; |
| 17 | 17 | import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js'; |
| 18 | +import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js'; | |
| 18 | 19 | export { MODULE_NAME }; |
| 19 | 20 | |
| 20 | 21 | const MODULE_NAME = 'expressions'; |
| @@ -59,6 +60,7 @@ const EXPRESSION_API = { | ||
| 59 | 60 | local: 0, |
| 60 | 61 | extras: 1, |
| 61 | 62 | llm: 2, |
| 63 | + webllm: 3, | |
| 62 | 64 | }; |
| 63 | 65 | |
| 64 | 66 | let expressionsList = null; |
| @@ -852,7 +854,7 @@ function setTalkingHeadState(newState) { | ||
| 852 | 854 | extension_settings.expressions.talkinghead = newState; // Store setting |
| 853 | 855 | saveSettingsDebounced(); |
| 854 | 856 | |
| 855 | 857 | if (extension_settings.expressions.api == [EXPRESSION_API.local, ||EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api == EXPRESSION_API.llm)) { |
| 856 | 858 | return; |
| 857 | 859 | } |
| 858 | 860 | |
| @@ -1057,21 +1059,25 @@ function parseLlmResponse(emotionResponse, labels) { | ||
| 1057 | 1059 | console.debug(`fuzzy search found: ${result[0].item} as closest for the LLM response:`, emotionResponse); |
| 1058 | 1060 | return result[0].item; |
| 1059 | 1061 | } |
| 1062 | + const lowerCaseResponse = String(emotionResponse || '').toLowerCase(); | |
| 1063 | + for (const label of labels) { | |
| 1064 | + if (lowerCaseResponse.includes(label.toLowerCase())) { | |
| 1065 | + console.debug(`Found label ${label} in the LLM response:`, emotionResponse); | |
| 1066 | + return label; | |
| 1067 | + } | |
| 1068 | + } | |
| 1060 | 1069 | } |
| 1061 | 1070 | |
| 1062 | 1071 | throw new Error('Could not parse emotion response ' + emotionResponse); |
| 1063 | 1072 | } |
| 1064 | 1073 | |
| 1065 | -function onTextGenSettingsReady(args) { | |
| 1074 | +/** | |
| 1066 | - // Only call if inside an API call | |
| 1075 | + * Gets the JSON schema for the LLM API. | |
| 1067 | - if (inApiCall && extension_settings.expressions.api === EXPRESSION_API.llm && isJsonSchemaSupported()) { | |
| 1076 | + * @param {string[]} emotions A list of emotions to search for. | |
| 1068 | - const emotions = DEFAULT_EXPRESSIONS.filter((e) => e != 'talkinghead'); | |
| 1077 | + * @returns {object} The JSON schema for the LLM API. | |
| 1069 | - Object.assign(args, { | |
| 1078 | + */ | |
| 1070 | - top_k: 1, | |
| 1079 | +function getJsonSchema(emotions) { | |
| 1071 | - stop: [], | |
| 1080 | + return { | |
| 1072 | - stopping_strings: [], | |
| 1073 | - custom_token_bans: [], | |
| 1074 | - json_schema: { | |
| 1075 | 1081 | $schema: 'http://json-schema.org/draft-04/schema#', |
| 1076 | 1082 | type: 'object', |
| 1077 | 1083 | properties: { |
| @@ -1083,7 +1089,19 @@ function onTextGenSettingsReady(args) { | ||
| 1083 | 1089 | required: [ |
| 1084 | 1090 | 'emotion', |
| 1085 | 1091 | ], |
| 1086 | - }, | |
| 1092 | + }; | |
| 1093 | +} | |
| 1094 | + | |
| 1095 | +function onTextGenSettingsReady(args) { | |
| 1096 | + // Only call if inside an API call | |
| 1097 | + if (inApiCall && extension_settings.expressions.api === EXPRESSION_API.llm && isJsonSchemaSupported()) { | |
| 1098 | + const emotions = DEFAULT_EXPRESSIONS.filter((e) => e != 'talkinghead'); | |
| 1099 | + Object.assign(args, { | |
| 1100 | + top_k: 1, | |
| 1101 | + stop: [], | |
| 1102 | + stopping_strings: [], | |
| 1103 | + custom_token_bans: [], | |
| 1104 | + json_schema: getJsonSchema(emotions), | |
| 1087 | 1105 | }); |
| 1088 | 1106 | } |
| 1089 | 1107 | } |
| @@ -1139,6 +1157,22 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin | ||
| 1139 | 1157 | const emotionResponse = await generateRaw(text, main_api, false, false, prompt); |
| 1140 | 1158 | return parseLlmResponse(emotionResponse, expressionsList); |
| 1141 | 1159 | } |
| 1160 | + // Using WebLLM | |
| 1161 | + case EXPRESSION_API.webllm: { | |
| 1162 | + if (!isWebLlmSupported()) { | |
| 1163 | + console.warn('WebLLM is not supported. Using fallback expression'); | |
| 1164 | + return getFallbackExpression(); | |
| 1165 | + } | |
| 1166 | + | |
| 1167 | + const expressionsList = await getExpressionsList(); | |
| 1168 | + const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList); | |
| 1169 | + const messages = [ | |
| 1170 | + { role: 'user', content: text + '\n\n' + prompt }, | |
| 1171 | + ]; | |
| 1172 | + | |
| 1173 | + const emotionResponse = await generateWebLlmChatPrompt(messages); | |
| 1174 | + return parseLlmResponse(emotionResponse, expressionsList); | |
| 1175 | + } | |
| 1142 | 1176 | // Extras |
| 1143 | 1177 | default: { |
| 1144 | 1178 | const url = new URL(getApiUrl()); |
| @@ -1603,7 +1637,7 @@ function onExpressionApiChanged() { | ||
| 1603 | 1637 | const tempApi = this.value; |
| 1604 | 1638 | if (tempApi) { |
| 1605 | 1639 | extension_settings.expressions.api = Number(tempApi); |
| 1606 | 1640 | $('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api === EXPRESSION_API.llm)); |
| 1607 | 1641 | expressionsList = null; |
| 1608 | 1642 | spriteCache = {}; |
| 1609 | 1643 | moduleWorker(); |
| @@ -1940,7 +1974,7 @@ function migrateSettings() { | ||
| 1940 | 1974 | |
| 1941 | 1975 | await renderAdditionalExpressionSettings(); |
| 1942 | 1976 | $('#expression_api').val(extension_settings.expressions.api ?? EXPRESSION_API.extras); |
| 1943 | 1977 | $('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api === EXPRESSION_API.llm)); |
| 1944 | 1978 | $('#expression_llm_prompt').val(extension_settings.expressions.llmPrompt ?? ''); |
| 1945 | 1979 | $('#expression_llm_prompt').on('input', function () { |
| 1946 | 1980 | extension_settings.expressions.llmPrompt = $(this).val(); |
| @@ -24,7 +24,8 @@ | ||
| 24 | 24 | <select id="expression_api" class="flex1 margin0"> |
| 25 | 25 | <option value="0" data-i18n="Local">Local</option> |
| 26 | 26 | <option value="1" data-i18n="Extras">Extras</option> |
| 27 | 27 | <option value="2" data-i18n="LLMMain API">LLMMain API</option> |
| 28 | + <option value="3" data-i18n="WebLLM Extension">WebLLM Extension</option> | |
| 28 | 29 | </select> |
| 29 | 30 | </div> |
| 30 | 31 | <div class="expression_llm_prompt_block m-b-1 m-t-1"> |
| @@ -25,6 +25,7 @@ const OPENROUTER_PROVIDERS = [ | ||
| 25 | 25 | 'Anthropic', |
| 26 | 26 | 'Google', |
| 27 | 27 | 'Google AI Studio', |
| 28 | + 'Amazon Bedrock', | |
| 28 | 29 | 'Groq', |
| 29 | 30 | 'SambaNova', |
| 30 | 31 | 'Cohere', |
| @@ -50,6 +51,8 @@ const OPENROUTER_PROVIDERS = [ | ||
| 50 | 51 | 'Featherless', |
| 51 | 52 | 'Inflection', |
| 52 | 53 | 'xAI', |
| 54 | + 'Cloudflare', | |
| 55 | + 'SF Compute', | |
| 53 | 56 | '01.AI', |
| 54 | 57 | 'HuggingFace', |
| 55 | 58 | 'Mancer', |
| @@ -1264,6 +1264,7 @@ button { | ||
| 1264 | 1264 | text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor); |
| 1265 | 1265 | flex: 1; |
| 1266 | 1266 | order: 3; |
| 1267 | + field-sizing: content; | |
| 1267 | 1268 | |
| 1268 | 1269 | --progColor: rgb(146, 190, 252); |
| 1269 | 1270 | --progFlashColor: rgb(215, 136, 114); |
| @@ -4111,6 +4112,7 @@ input[type="range"]::-webkit-slider-thumb { | ||
| 4111 | 4112 | line-height: calc(var(--mainFontSize) + .25rem); |
| 4112 | 4113 | max-height: 75vh; |
| 4113 | 4114 | max-height: 75dvh; |
| 4115 | + field-sizing: content; | |
| 4114 | 4116 | } |
| 4115 | 4117 | |
| 4116 | 4118 | #anchor_order { |