Merge branch 'staging' into continue-from-reasoning
| @@ -1962,7 +1962,7 @@ | ||
| 1962 | 1962 | </span> |
| 1963 | 1963 | </div> |
| 1964 | 1964 | </div> |
| 1965 | 1965 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21"> |
| 1966 | 1966 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 1967 | 1967 | <input id="openai_function_calling" type="checkbox" /> |
| 1968 | 1968 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -3078,7 +3078,15 @@ | ||
| 3078 | 3078 | <div> |
| 3079 | 3079 | <h4 data-i18n="AI21 Model">AI21 Model</h4> |
| 3080 | 3080 | <select id="model_ai21_select"> |
| 3081 | 3081 | <optgroup label="Jamba 1.5(Latest)"> |
| 3082 | + <option value="jamba-mini">jamba-mini</option> | |
| 3083 | + <option value="jamba-large">jamba-large</option> | |
| 3084 | + </optgroup> | |
| 3085 | + <optgroup label="Jamba 1.6"> | |
| 3086 | + <option value="jamba-1.6-mini">jamba-1.6-mini</option> | |
| 3087 | + <option value="jamba-1.6-large">jamba-1.6-large</option> | |
| 3088 | + </optgroup> | |
| 3089 | + <optgroup label="Jamba 1.5 (Deprecated)"> | |
| 3082 | 3090 | <option value="jamba-1.5-mini">jamba-1.5-mini</option> |
| 3083 | 3091 | <option value="jamba-1.5-large">jamba-1.5-large</option> |
| 3084 | 3092 | </optgroup> |
| @@ -271,7 +271,7 @@ import { initSettingsSearch } from './scripts/setting-search.js'; | ||
| 271 | 271 | import { initBulkEdit } from './scripts/bulk-edit.js'; |
| 272 | 272 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; |
| 273 | 273 | import { getContext } from './scripts/st-context.js'; |
| 274 | 274 | import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js'; |
| 275 | 275 | import { accountStorage } from './scripts/util/AccountStorage.js'; |
| 276 | 276 | |
| 277 | 277 | // API OBJECT FOR EXTERNAL WIRING |
| @@ -3346,15 +3346,18 @@ class StreamingProcessor { | ||
| 3346 | 3346 | |
| 3347 | 3347 | if (Array.isArray(this.swipes) && this.swipes.length > 0) { |
| 3348 | 3348 | const message = chat[messageId]; |
| 3349 | + const swipeInfoExtra = structuredClone(message.extra ?? {}); | |
| 3350 | + delete swipeInfoExtra.token_count; | |
| 3351 | + delete swipeInfoExtra.reasoning; | |
| 3352 | + delete swipeInfoExtra.reasoning_duration; | |
| 3349 | 3353 | const swipeInfo = { |
| 3350 | 3354 | send_date: message.send_date, |
| 3351 | 3355 | gen_started: message.gen_started, |
| 3352 | 3356 | gen_finished: message.gen_finished, |
| 3353 | 3357 | extra: structuredClone(message.extra)swipeInfoExtra, |
| 3354 | 3358 | }; |
| 3355 | - const swipeInfoArray = []; | |
| 3359 | + const swipeInfoArray = Array(this.swipes.length).fill().map(() => structuredClone(swipeInfo)); | |
| 3356 | - swipeInfoArray.length = this.swipes.length; | |
| 3360 | + parseReasoningInSwipes(this.swipes, swipeInfoArray, message.extra?.reasoning_duration); | |
| 3357 | - swipeInfoArray.fill(swipeInfo); | |
| 3358 | 3361 | chat[messageId].swipes.push(...this.swipes); |
| 3359 | 3362 | chat[messageId].swipe_info.push(...swipeInfoArray); |
| 3360 | 3363 | } |
| @@ -3366,6 +3369,7 @@ class StreamingProcessor { | ||
| 3366 | 3369 | await eventSource.emit(event_types.IMPERSONATE_READY, text); |
| 3367 | 3370 | } |
| 3368 | 3371 | |
| 3372 | + syncMesToSwipe(messageId); | |
| 3369 | 3373 | saveLogprobsForActiveMessage(this.messageLogprobs.filter(Boolean), this.continueMessage); |
| 3370 | 3374 | await saveChatConditional(); |
| 3371 | 3375 | unblockGeneration(); |
| @@ -6117,15 +6121,18 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes, | ||
| 6117 | 6121 | } |
| 6118 | 6122 | |
| 6119 | 6123 | if (Array.isArray(swipes) && swipes.length > 0) { |
| 6124 | + const swipeInfoExtra = structuredClone(item.extra ?? {}); | |
| 6125 | + delete swipeInfoExtra.token_count; | |
| 6126 | + delete swipeInfoExtra.reasoning; | |
| 6127 | + delete swipeInfoExtra.reasoning_duration; | |
| 6120 | 6128 | const swipeInfo = { |
| 6121 | 6129 | send_date: item.send_date, |
| 6122 | 6130 | gen_started: item.gen_started, |
| 6123 | 6131 | gen_finished: item.gen_finished, |
| 6124 | 6132 | extra: structuredClone(item.extra)swipeInfoExtra, |
| 6125 | 6133 | }; |
| 6126 | - const swipeInfoArray = []; | |
| 6134 | + const swipeInfoArray = Array(swipes.length).fill().map(() => structuredClone(swipeInfo)); | |
| 6127 | - swipeInfoArray.length = swipes.length; | |
| 6135 | + parseReasoningInSwipes(swipes, swipeInfoArray, item.extra?.reasoning_duration); | |
| 6128 | - swipeInfoArray.fill(swipeInfo, 0, swipes.length); | |
| 6129 | 6136 | item.swipes.push(...swipes); |
| 6130 | 6137 | item.swipe_info.push(...swipeInfoArray); |
| 6131 | 6138 | } |
| @@ -1070,7 +1070,7 @@ export async function installExtension(url, global) { | ||
| 1070 | 1070 | toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`); |
| 1071 | 1071 | console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`); |
| 1072 | 1072 | await loadExtensionSettings({}, false, false); |
| 1073 | 1073 | await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response); |
| 1074 | 1074 | } |
| 1075 | 1075 | |
| 1076 | 1076 | /** |
| @@ -19,6 +19,7 @@ import { | ||
| 19 | 19 | modules, |
| 20 | 20 | renderExtensionTemplateAsync, |
| 21 | 21 | doExtrasFetch, getApiUrl, |
| 22 | + openThirdPartyExtensionMenu, | |
| 22 | 23 | } from '../../extensions.js'; |
| 23 | 24 | import { collapseNewlines, registerDebugFunction } from '../../power-user.js'; |
| 24 | 25 | import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js'; |
| @@ -34,6 +35,7 @@ import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashComm | ||
| 34 | 35 | import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js'; |
| 35 | 36 | import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; |
| 36 | 37 | import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js'; |
| 38 | +import { WebLlmVectorProvider } from './webllm.js'; | |
| 37 | 39 | |
| 38 | 40 | /** |
| 39 | 41 | * @typedef {object} HashedMessage |
| @@ -60,6 +62,7 @@ const settings = { | ||
| 60 | 62 | ollama_model: 'mxbai-embed-large', |
| 61 | 63 | ollama_keep: false, |
| 62 | 64 | vllm_model: '', |
| 65 | + webllm_model: '', | |
| 63 | 66 | summarize: false, |
| 64 | 67 | summarize_sent: false, |
| 65 | 68 | summary_source: 'main', |
| @@ -103,7 +106,7 @@ const settings = { | ||
| 103 | 106 | }; |
| 104 | 107 | |
| 105 | 108 | const moduleWorker = new ModuleWorkerWrapper(synchronizeChat); |
| 106 | - | |
| 109 | +const webllmProvider = new WebLlmVectorProvider(); | |
| 107 | 110 | const cachedSummaries = new Map(); |
| 108 | 111 | |
| 109 | 112 | /** |
| @@ -373,6 +376,8 @@ async function synchronizeChat(batchSize = 5) { | ||
| 373 | 376 | return 'Vectorization Source Model is required, but not set.'; |
| 374 | 377 | case 'extras_module_missing': |
| 375 | 378 | return 'Extras API must provide an "embeddings" module.'; |
| 379 | + case 'webllm_not_supported': | |
| 380 | + return 'WebLLM extension is not installed or the model is not set.'; | |
| 376 | 381 | default: |
| 377 | 382 | return 'Check server console for more details'; |
| 378 | 383 | } |
| @@ -747,14 +752,15 @@ async function getQueryText(chat, initiator) { | ||
| 747 | 752 | |
| 748 | 753 | /** |
| 749 | 754 | * Gets common body parameters for vector requests. |
| 750 | 755 | * @returnsparam {object} args Additional arguments |
| 756 | + * @returns {object} Request body | |
| 751 | 757 | */ |
| 752 | 758 | function getVectorsRequestBody(args = {}) { |
| 753 | 759 | const body = Object.assign({}, args); |
| 754 | 760 | switch (settings.source) { |
| 755 | 761 | case 'extras': |
| 756 | 762 | body.extrasUrl = extension_settings.apiUrl; |
| 757 | 763 | body.extrasKey = extension_settings.apiKey; |
| 758 | 764 | break; |
| 759 | 765 | case 'togetherai': |
| 760 | 766 | body.model = extension_settings.vectors.togetherai_model; |
| @@ -777,6 +783,9 @@ function getVectorsRequestBody() { | ||
| 777 | 783 | body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.VLLM]; |
| 778 | 784 | body.model = extension_settings.vectors.vllm_model; |
| 779 | 785 | break; |
| 786 | + case 'webllm': | |
| 787 | + body.model = extension_settings.vectors.webllm_model; | |
| 788 | + break; | |
| 780 | 789 | default: |
| 781 | 790 | break; |
| 782 | 791 | } |
| @@ -784,6 +793,21 @@ function getVectorsRequestBody() { | ||
| 784 | 793 | } |
| 785 | 794 | |
| 786 | 795 | /** |
| 796 | + * Gets additional arguments for vector requests. | |
| 797 | + * @param {string[]} items Items to embed | |
| 798 | + * @returns {Promise<object>} Additional arguments | |
| 799 | + */ | |
| 800 | +async function getAdditionalArgs(items) { | |
| 801 | + const args = {}; | |
| 802 | + switch (settings.source) { | |
| 803 | + case 'webllm': | |
| 804 | + args.embeddings = await createWebLlmEmbeddings(items); | |
| 805 | + break; | |
| 806 | + } | |
| 807 | + return args; | |
| 808 | +} | |
| 809 | + | |
| 810 | +/** | |
| 787 | 811 | * Gets the saved hashes for a collection |
| 788 | 812 | * @param {string} collectionId |
| 789 | 813 | * @returns {Promise<number[]>} Saved hashes |
| @@ -816,11 +840,12 @@ async function getSavedHashes(collectionId) { | ||
| 816 | 840 | async function insertVectorItems(collectionId, items) { |
| 817 | 841 | throwIfSourceInvalid(); |
| 818 | 842 | |
| 843 | + const args = await getAdditionalArgs(items.map(x => x.text)); | |
| 819 | 844 | const response = await fetch('/api/vector/insert', { |
| 820 | 845 | method: 'POST', |
| 821 | 846 | headers: getRequestHeaders(), |
| 822 | 847 | body: JSON.stringify({ |
| 823 | 848 | ...getVectorsRequestBody(args), |
| 824 | 849 | collectionId: collectionId, |
| 825 | 850 | items: items, |
| 826 | 851 | source: settings.source, |
| @@ -858,6 +883,10 @@ function throwIfSourceInvalid() { | ||
| 858 | 883 | if (settings.source === 'extras' && !modules.includes('embeddings')) { |
| 859 | 884 | throw new Error('Vectors: Embeddings module missing', { cause: 'extras_module_missing' }); |
| 860 | 885 | } |
| 886 | + | |
| 887 | + if (settings.source === 'webllm' && (!isWebLlmSupported() || !settings.webllm_model)) { | |
| 888 | + throw new Error('Vectors: WebLLM is not supported', { cause: 'webllm_not_supported' }); | |
| 889 | + } | |
| 861 | 890 | } |
| 862 | 891 | |
| 863 | 892 | /** |
| @@ -890,11 +919,12 @@ async function deleteVectorItems(collectionId, hashes) { | ||
| 890 | 919 | * @returns {Promise<{ hashes: number[], metadata: object[]}>} - Hashes of the results |
| 891 | 920 | */ |
| 892 | 921 | async function queryCollection(collectionId, searchText, topK) { |
| 922 | + const args = await getAdditionalArgs([searchText]); | |
| 893 | 923 | const response = await fetch('/api/vector/query', { |
| 894 | 924 | method: 'POST', |
| 895 | 925 | headers: getRequestHeaders(), |
| 896 | 926 | body: JSON.stringify({ |
| 897 | 927 | ...getVectorsRequestBody(args), |
| 898 | 928 | collectionId: collectionId, |
| 899 | 929 | searchText: searchText, |
| 900 | 930 | topK: topK, |
| @@ -919,11 +949,12 @@ async function queryCollection(collectionId, searchText, topK) { | ||
| 919 | 949 | * @returns {Promise<Record<string, { hashes: number[], metadata: object[] }>>} - Results mapped to collection IDs |
| 920 | 950 | */ |
| 921 | 951 | async function queryMultipleCollections(collectionIds, searchText, topK, threshold) { |
| 952 | + const args = await getAdditionalArgs([searchText]); | |
| 922 | 953 | const response = await fetch('/api/vector/query-multi', { |
| 923 | 954 | method: 'POST', |
| 924 | 955 | headers: getRequestHeaders(), |
| 925 | 956 | body: JSON.stringify({ |
| 926 | 957 | ...getVectorsRequestBody(args), |
| 927 | 958 | collectionIds: collectionIds, |
| 928 | 959 | searchText: searchText, |
| 929 | 960 | topK: topK, |
| @@ -1039,6 +1070,72 @@ function toggleSettings() { | ||
| 1039 | 1070 | $('#llamacpp_vectorsModel').toggle(settings.source === 'llamacpp'); |
| 1040 | 1071 | $('#vllm_vectorsModel').toggle(settings.source === 'vllm'); |
| 1041 | 1072 | $('#nomicai_apiKey').toggle(settings.source === 'nomicai'); |
| 1073 | + $('#webllm_vectorsModel').toggle(settings.source === 'webllm'); | |
| 1074 | + if (settings.source === 'webllm') { | |
| 1075 | + loadWebLlmModels(); | |
| 1076 | + } | |
| 1077 | +} | |
| 1078 | + | |
| 1079 | +/** | |
| 1080 | + * Executes a function with WebLLM error handling. | |
| 1081 | + * @param {function(): Promise<T>} func Function to execute | |
| 1082 | + * @returns {Promise<T>} | |
| 1083 | + * @template T | |
| 1084 | + */ | |
| 1085 | +async function executeWithWebLlmErrorHandling(func) { | |
| 1086 | + try { | |
| 1087 | + return await func(); | |
| 1088 | + } catch (error) { | |
| 1089 | + console.log('Vectors: Failed to load WebLLM models', error); | |
| 1090 | + if (!(error instanceof Error)) { | |
| 1091 | + return; | |
| 1092 | + } | |
| 1093 | + switch (error.cause) { | |
| 1094 | + case 'webllm-not-available': | |
| 1095 | + toastr.warning('WebLLM is not available. Please install the extension.', 'WebLLM not installed'); | |
| 1096 | + break; | |
| 1097 | + case 'webllm-not-updated': | |
| 1098 | + toastr.warning('The installed extension version does not support embeddings.', 'WebLLM update required'); | |
| 1099 | + break; | |
| 1100 | + } | |
| 1101 | + } | |
| 1102 | +} | |
| 1103 | + | |
| 1104 | +/** | |
| 1105 | + * Loads and displays WebLLM models in the settings. | |
| 1106 | + * @returns {Promise<void>} | |
| 1107 | + */ | |
| 1108 | +function loadWebLlmModels() { | |
| 1109 | + return executeWithWebLlmErrorHandling(() => { | |
| 1110 | + const models = webllmProvider.getModels(); | |
| 1111 | + $('#vectors_webllm_model').empty(); | |
| 1112 | + for (const model of models) { | |
| 1113 | + $('#vectors_webllm_model').append($('<option>', { value: model.id, text: model.toString() })); | |
| 1114 | + } | |
| 1115 | + if (!settings.webllm_model || !models.some(x => x.id === settings.webllm_model)) { | |
| 1116 | + if (models.length) { | |
| 1117 | + settings.webllm_model = models[0].id; | |
| 1118 | + } | |
| 1119 | + } | |
| 1120 | + $('#vectors_webllm_model').val(settings.webllm_model); | |
| 1121 | + return Promise.resolve(); | |
| 1122 | + }); | |
| 1123 | +} | |
| 1124 | + | |
| 1125 | +/** | |
| 1126 | + * Creates WebLLM embeddings for a list of items. | |
| 1127 | + * @param {string[]} items Items to embed | |
| 1128 | + * @returns {Promise<Record<string, number[]>>} Calculated embeddings | |
| 1129 | + */ | |
| 1130 | +async function createWebLlmEmbeddings(items) { | |
| 1131 | + return executeWithWebLlmErrorHandling(async () => { | |
| 1132 | + const embeddings = await webllmProvider.embedTexts(items, settings.webllm_model); | |
| 1133 | + const result = /** @type {Record<string, number[]>} */ ({}); | |
| 1134 | + for (let i = 0; i < items.length; i++) { | |
| 1135 | + result[items[i]] = embeddings[i]; | |
| 1136 | + } | |
| 1137 | + return result; | |
| 1138 | + }); | |
| 1042 | 1139 | } |
| 1043 | 1140 | |
| 1044 | 1141 | async function onPurgeClick() { |
| @@ -1567,6 +1664,30 @@ jQuery(async () => { | ||
| 1567 | 1664 | $('#dialogue_popup_input').val(presetModel); |
| 1568 | 1665 | }); |
| 1569 | 1666 | |
| 1667 | + $('#vectors_webllm_install').on('click', (e) => { | |
| 1668 | + e.preventDefault(); | |
| 1669 | + e.stopPropagation(); | |
| 1670 | + | |
| 1671 | + if (Object.hasOwn(SillyTavern, 'llm')) { | |
| 1672 | + toastr.info('WebLLM is already installed'); | |
| 1673 | + return; | |
| 1674 | + } | |
| 1675 | + | |
| 1676 | + openThirdPartyExtensionMenu('https://github.com/SillyTavern/Extension-WebLLM'); | |
| 1677 | + }); | |
| 1678 | + | |
| 1679 | + $('#vectors_webllm_model').on('input', () => { | |
| 1680 | + settings.webllm_model = String($('#vectors_webllm_model').val()); | |
| 1681 | + Object.assign(extension_settings.vectors, settings); | |
| 1682 | + saveSettingsDebounced(); | |
| 1683 | + }); | |
| 1684 | + | |
| 1685 | + $('#vectors_webllm_load').on('click', async () => { | |
| 1686 | + if (!settings.webllm_model) return; | |
| 1687 | + await webllmProvider.loadModel(settings.webllm_model); | |
| 1688 | + toastr.success('WebLLM model loaded'); | |
| 1689 | + }); | |
| 1690 | + | |
| 1570 | 1691 | $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]); |
| 1571 | 1692 | |
| 1572 | 1693 | toggleSettings(); |
| @@ -1578,6 +1699,11 @@ jQuery(async () => { | ||
| 1578 | 1699 | eventSource.on(event_types.CHAT_DELETED, purgeVectorIndex); |
| 1579 | 1700 | eventSource.on(event_types.GROUP_CHAT_DELETED, purgeVectorIndex); |
| 1580 | 1701 | eventSource.on(event_types.FILE_ATTACHMENT_DELETED, purgeFileVectorIndex); |
| 1702 | + eventSource.on(event_types.EXTENSION_SETTINGS_LOADED, async (manifest) => { | |
| 1703 | + if (settings.source === 'webllm' && manifest?.display_name === 'WebLLM') { | |
| 1704 | + await loadWebLlmModels(); | |
| 1705 | + } | |
| 1706 | + }); | |
| 1581 | 1707 | |
| 1582 | 1708 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1583 | 1709 | name: 'db-ingest', |
| @@ -21,8 +21,24 @@ | ||
| 21 | 21 | <option value="openai">OpenAI</option> |
| 22 | 22 | <option value="togetherai">TogetherAI</option> |
| 23 | 23 | <option value="vllm">vLLM</option> |
| 24 | + <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option> | |
| 24 | 25 | </select> |
| 25 | 26 | </div> |
| 27 | + <div class="flex-container flexFlowColumn" id="webllm_vectorsModel"> | |
| 28 | + <label for="vectors_webllm_model" data-i18n="Vectorization Model"> | |
| 29 | + Vectorization Model | |
| 30 | + </label> | |
| 31 | + <div class="flex-container"> | |
| 32 | + <select id="vectors_webllm_model" class="text_pole flex1"> | |
| 33 | + </select> | |
| 34 | + <div id="vectors_webllm_load" class="menu_button menu_button_icon" title="Verify and load the selected model."> | |
| 35 | + <i class="fa-solid fa-check-to-slot"></i> | |
| 36 | + </div> | |
| 37 | + </div> | |
| 38 | + <div> | |
| 39 | + Requires the WebLLM extension to be installed. Click <a href="#" id="vectors_webllm_install">here</a> to install. | |
| 40 | + </div> | |
| 41 | + </div> | |
| 26 | 42 | <div class="flex-container flexFlowColumn" id="ollama_vectorsModel"> |
| 27 | 43 | <label for="vectors_ollama_model" data-i18n="Vectorization Model"> |
| 28 | 44 | Vectorization Model |
| @@ -0,0 +1,64 @@ | ||
| 1 | +export class WebLlmVectorProvider { | |
| 2 | + /** @type {object?} WebLLM engine */ | |
| 3 | + #engine = null; | |
| 4 | + | |
| 5 | + constructor() { | |
| 6 | + this.#engine = null; | |
| 7 | + } | |
| 8 | + | |
| 9 | + /** | |
| 10 | + * Check if WebLLM is available and up-to-date | |
| 11 | + * @throws {Error} If WebLLM is not available or not up-to-date | |
| 12 | + */ | |
| 13 | + #checkWebLlm() { | |
| 14 | + if (!Object.hasOwn(SillyTavern, 'llm')) { | |
| 15 | + throw new Error('WebLLM is not available', { cause: 'webllm-not-available' }); | |
| 16 | + } | |
| 17 | + | |
| 18 | + if (typeof SillyTavern.llm.generateEmbedding !== 'function') { | |
| 19 | + throw new Error('WebLLM is not updated', { cause: 'webllm-not-updated' }); | |
| 20 | + } | |
| 21 | + } | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * Initialize the engine with a model. | |
| 25 | + * @param {string} modelId Model ID to initialize the engine with | |
| 26 | + * @returns {Promise<void>} Promise that resolves when the engine is initialized | |
| 27 | + */ | |
| 28 | + #initEngine(modelId) { | |
| 29 | + this.#checkWebLlm(); | |
| 30 | + if (!this.#engine) { | |
| 31 | + this.#engine = SillyTavern.llm.getEngine(); | |
| 32 | + } | |
| 33 | + | |
| 34 | + return this.#engine.loadModel(modelId); | |
| 35 | + } | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * Get available models. | |
| 39 | + * @returns {{id:string, toString: function(): string}[]} Array of available models | |
| 40 | + */ | |
| 41 | + getModels() { | |
| 42 | + this.#checkWebLlm(); | |
| 43 | + return SillyTavern.llm.getEmbeddingModels(); | |
| 44 | + } | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * Generate embeddings for a list of texts. | |
| 48 | + * @param {string[]} texts Array of texts to generate embeddings for | |
| 49 | + * @param {string} modelId Model to use for generating embeddings | |
| 50 | + * @returns {Promise<number[][]>} Array of embeddings for each text | |
| 51 | + */ | |
| 52 | + async embedTexts(texts, modelId) { | |
| 53 | + await this.#initEngine(modelId); | |
| 54 | + return this.#engine.generateEmbedding(texts); | |
| 55 | + } | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * Loads a model into the engine. | |
| 59 | + * @param {string} modelId Model ID to load | |
| 60 | + */ | |
| 61 | + async loadModel(modelId) { | |
| 62 | + await this.#initEngine(modelId); | |
| 63 | + } | |
| 64 | +} | |
| @@ -337,7 +337,7 @@ const default_settings = { | ||
| 337 | 337 | openai_model: 'gpt-4-turbo', |
| 338 | 338 | claude_model: 'claude-3-5-sonnet-20240620', |
| 339 | 339 | google_model: 'gemini-1.5-pro', |
| 340 | 340 | ai21_model: 'jamba-1.56-large', |
| 341 | 341 | mistralai_model: 'mistral-large-latest', |
| 342 | 342 | cohere_model: 'command-r-plus', |
| 343 | 343 | perplexity_model: 'sonar-pro', |
| @@ -417,7 +417,7 @@ const oai_settings = { | ||
| 417 | 417 | openai_model: 'gpt-4-turbo', |
| 418 | 418 | claude_model: 'claude-3-5-sonnet-20240620', |
| 419 | 419 | google_model: 'gemini-1.5-pro', |
| 420 | 420 | ai21_model: 'jamba-1.56-large', |
| 421 | 421 | mistralai_model: 'mistral-large-latest', |
| 422 | 422 | cohere_model: 'command-r-plus', |
| 423 | 423 | perplexity_model: 'sonar-pro', |
| @@ -2027,12 +2027,16 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2027 | 2027 | generate_data['logprobs'] = 5; |
| 2028 | 2028 | } |
| 2029 | 2029 | |
| 2030 | 2030 | // Remove logit bias, /logprobs and /stop -strings if it's not supported by the model |
| 2031 | - if (isOAI && oai_settings.openai_model.includes('vision') || isOpenRouter && oai_settings.openrouter_model.includes('vision') || isOAI && oai_settings.openai_model.includes('gpt-4.5-preview')) { | |
| 2031 | + const isVision = (m) => ['gpt', 'vision'].every(x => m.includes(x)); | |
| 2032 | + if (isOAI && isVision(oai_settings.openai_model) || isOpenRouter && isVision(oai_settings.openrouter_model)) { | |
| 2032 | 2033 | delete generate_data.logit_bias; |
| 2033 | 2034 | delete generate_data.stop; |
| 2034 | 2035 | delete generate_data.logprobs; |
| 2035 | 2036 | } |
| 2037 | + if (isOAI && oai_settings.openai_model.includes('gpt-4.5-preview') || isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5-preview')) { | |
| 2038 | + delete generate_data.logprobs; | |
| 2039 | + } | |
| 2036 | 2040 | |
| 2037 | 2041 | if (isClaude) { |
| 2038 | 2042 | generate_data['top_k'] = Number(oai_settings.top_k_openai); |
| @@ -3251,7 +3255,7 @@ function loadOpenAISettings(data, settings) { | ||
| 3251 | 3255 | } |
| 3252 | 3256 | |
| 3253 | 3257 | if (oai_settings.ai21_model.startsWith('j2-')) { |
| 3254 | 3258 | oai_settings.ai21_model = 'jamba-1.56-large'; |
| 3255 | 3259 | } |
| 3256 | 3260 | |
| 3257 | 3261 | if (settings.wrap_in_quotes !== undefined) oai_settings.wrap_in_quotes = !!settings.wrap_in_quotes; |
| @@ -4208,7 +4212,7 @@ async function onModelChange() { | ||
| 4208 | 4212 | |
| 4209 | 4213 | if ($(this).is('#model_ai21_select')) { |
| 4210 | 4214 | if (value === '' || value.startsWith('j2-')) { |
| 4211 | 4215 | value = 'jamba-1.56-large'; |
| 4212 | 4216 | $('#model_ai21_select').val(value); |
| 4213 | 4217 | } |
| 4214 | 4218 | |
| @@ -4485,7 +4489,7 @@ async function onModelChange() { | ||
| 4485 | 4489 | if (oai_settings.chat_completion_source == chat_completion_sources.AI21) { |
| 4486 | 4490 | if (oai_settings.max_context_unlocked) { |
| 4487 | 4491 | $('#openai_max_context').attr('max', unlocked_max); |
| 4488 | 4492 | } else if (oai_settings.ai21_model.includes('jamba-1.5') || oai_settings.ai21_model.includesstartsWith('jamba-instruct')) { |
| 4489 | 4493 | $('#openai_max_context').attr('max', max_256k); |
| 4490 | 4494 | } |
| 4491 | 4495 | |
| @@ -1104,6 +1104,32 @@ function parseReasoningFromString(str, { strict = true } = {}) { | ||
| 1104 | 1104 | } |
| 1105 | 1105 | } |
| 1106 | 1106 | |
| 1107 | +/** | |
| 1108 | + * Parse reasoning in an array of swipe strings if auto-parsing is enabled. | |
| 1109 | + * @param {string[]} swipes Array of swipe strings | |
| 1110 | + * @param {{extra: {reasoning: string, reasoning_duration: number}}[]} swipeInfoArray Array of swipe info objects | |
| 1111 | + * @param {number?} duration Duration of the reasoning | |
| 1112 | + */ | |
| 1113 | +export function parseReasoningInSwipes(swipes, swipeInfoArray, duration) { | |
| 1114 | + if (!power_user.reasoning.auto_parse) { | |
| 1115 | + return; | |
| 1116 | + } | |
| 1117 | + | |
| 1118 | + // Something ain't right, don't parse | |
| 1119 | + if (!Array.isArray(swipes) || !Array.isArray(swipeInfoArray) || swipes.length !== swipeInfoArray.length) { | |
| 1120 | + return; | |
| 1121 | + } | |
| 1122 | + | |
| 1123 | + for (let index = 0; index < swipes.length; index++) { | |
| 1124 | + const parsedReasoning = parseReasoningFromString(swipes[index]); | |
| 1125 | + if (parsedReasoning) { | |
| 1126 | + swipes[index] = parsedReasoning.content; | |
| 1127 | + swipeInfoArray[index].extra.reasoning = parsedReasoning.reasoning; | |
| 1128 | + swipeInfoArray[index].extra.reasoning_duration = duration; | |
| 1129 | + } | |
| 1130 | + } | |
| 1131 | +} | |
| 1132 | + | |
| 1107 | 1133 | function registerReasoningAppEvents() { |
| 1108 | 1134 | const eventHandler = (/** @type {string} */ type, /** @type {number} */ idx) => { |
| 1109 | 1135 | if (!power_user.reasoning.auto_parse) { |
| @@ -585,6 +585,7 @@ export class ToolManager { | ||
| 585 | 585 | chat_completion_sources.COHERE, |
| 586 | 586 | chat_completion_sources.DEEPSEEK, |
| 587 | 587 | chat_completion_sources.MAKERSUITE, |
| 588 | + chat_completion_sources.AI21, | |
| 588 | 589 | ]; |
| 589 | 590 | return supportedSources.includes(oai_settings.chat_completion_source); |
| 590 | 591 | } |
| @@ -499,6 +499,12 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 499 | 499 | async function sendAI21Request(request, response) { |
| 500 | 500 | if (!request.body) return response.sendStatus(400); |
| 501 | 501 | |
| 502 | + const apiKey = readSecret(request.user.directories, SECRET_KEYS.AI21); | |
| 503 | + if (!apiKey) { | |
| 504 | + console.warn('AI21 API key is missing.'); | |
| 505 | + return response.status(400).send({ error: true }); | |
| 506 | + } | |
| 507 | + | |
| 502 | 508 | const controller = new AbortController(); |
| 503 | 509 | console.debug(request.body.messages); |
| 504 | 510 | request.socket.removeAllListeners('close'); |
| @@ -514,13 +520,14 @@ async function sendAI21Request(request, response) { | ||
| 514 | 520 | top_p: request.body.top_p, |
| 515 | 521 | stop: request.body.stop, |
| 516 | 522 | stream: request.body.stream, |
| 523 | + tools: request.body.tools, | |
| 517 | 524 | }; |
| 518 | 525 | const options = { |
| 519 | 526 | method: 'POST', |
| 520 | 527 | headers: { |
| 521 | 528 | accept: 'application/json', |
| 522 | 529 | 'content-type': 'application/json', |
| 523 | 530 | Authorization: `Bearer ${readSecret(request.user.directories, SECRET_KEYS.AI21)apiKey}`, |
| 524 | 531 | }, |
| 525 | 532 | body: JSON.stringify(body), |
| 526 | 533 | signal: controller.signal, |
| @@ -218,11 +218,13 @@ const toShallow = (character) => { | ||
| 218 | 218 | date_last_chat: character.date_last_chat, |
| 219 | 219 | chat_size: character.chat_size, |
| 220 | 220 | data_size: character.data_size, |
| 221 | + tags: character.tags, | |
| 221 | 222 | data: { |
| 222 | 223 | name: _.get(character, 'data.name', ''), |
| 223 | 224 | character_version: _.get(character, 'data.character_version', ''), |
| 224 | 225 | creator: _.get(character, 'data.creator', ''), |
| 225 | 226 | creator_notes: _.get(character, 'data.creator_notes', ''), |
| 227 | + tags: _.get(character, 'data.tags', []), | |
| 226 | 228 | extensions: { |
| 227 | 229 | fav: _.get(character, 'data.extensions.fav', false), |
| 228 | 230 | }, |
| @@ -4,7 +4,7 @@ import storage from 'node-persist'; | ||
| 4 | 4 | import express from 'express'; |
| 5 | 5 | import lodash from 'lodash'; |
| 6 | 6 | import { jsonParser } from '../express-common.js'; |
| 7 | 7 | import { checkForNewContent, CONTENT_TYPES } from './content-manager.js'; |
| 8 | 8 | import { |
| 9 | 9 | KEY_PREFIX, |
| 10 | 10 | toKey, |
| @@ -195,7 +195,7 @@ router.post('/create', requireAdminMiddleware, jsonParser, async (request, respo | ||
| 195 | 195 | console.info('Creating data directories for', newUser.handle); |
| 196 | 196 | await ensurePublicDirectoriesExist(); |
| 197 | 197 | const directories = getUserDirectories(newUser.handle); |
| 198 | 198 | await checkForNewContent([directories], [CONTENT_TYPES.SETTINGS]); |
| 199 | 199 | return response.json({ handle: newUser.handle }); |
| 200 | 200 | } catch (error) { |
| 201 | 201 | console.error('User create failed:', error); |
| @@ -31,6 +31,7 @@ const SOURCES = [ | ||
| 31 | 31 | 'ollama', |
| 32 | 32 | 'llamacpp', |
| 33 | 33 | 'vllm', |
| 34 | + 'webllm', | |
| 34 | 35 | ]; |
| 35 | 36 | |
| 36 | 37 | /** |
| @@ -64,6 +65,8 @@ async function getVector(source, sourceSettings, text, isQuery, directories) { | ||
| 64 | 65 | return getVllmVector(text, sourceSettings.apiUrl, sourceSettings.model, directories); |
| 65 | 66 | case 'ollama': |
| 66 | 67 | return getOllamaVector(text, sourceSettings.apiUrl, sourceSettings.model, sourceSettings.keep, directories); |
| 68 | + case 'webllm': | |
| 69 | + return sourceSettings.embeddings[text]; | |
| 67 | 70 | } |
| 68 | 71 | |
| 69 | 72 | throw new Error(`Unknown vector source ${source}`); |
| @@ -114,6 +117,9 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie | ||
| 114 | 117 | case 'ollama': |
| 115 | 118 | results.push(...await getOllamaBatchVector(batch, sourceSettings.apiUrl, sourceSettings.model, sourceSettings.keep, directories)); |
| 116 | 119 | break; |
| 120 | + case 'webllm': | |
| 121 | + results.push(...texts.map(x => sourceSettings.embeddings[x])); | |
| 122 | + break; | |
| 117 | 123 | default: |
| 118 | 124 | throw new Error(`Unknown vector source ${source}`); |
| 119 | 125 | } |
| @@ -179,6 +185,11 @@ function getSourceSettings(source, request) { | ||
| 179 | 185 | return { |
| 180 | 186 | model: 'nomic-embed-text-v1.5', |
| 181 | 187 | }; |
| 188 | + case 'webllm': | |
| 189 | + return { | |
| 190 | + model: String(request.body.model), | |
| 191 | + embeddings: request.body.embeddings ?? {}, | |
| 192 | + }; | |
| 182 | 193 | default: |
| 183 | 194 | return {}; |
| 184 | 195 | } |