diff --git a/public/index.html b/public/index.html index 47dfc8c7a..ad010165c 100644 --- a/public/index.html +++ b/public/index.html @@ -1118,7 +1118,14 @@
diff --git a/public/notes/content.md b/public/notes/content.md index e2b64c967..8437f2781 100644 --- a/public/notes/content.md +++ b/public/notes/content.md @@ -388,6 +388,15 @@ If your subscription tier is Paper, Tablet or Scroll use only Euterpe model othe _Lost API keys can't be restored! Make sure to keep it safe!_ +### Window.ai + +You can use window.ai browser extension to access AI models with SillyTavern. + +1. Install a browser extension from: [windowai.io](https://windowai.io/) +2. Create an OpenRouter account: [openrouter.ai](https://openrouter.ai/) +3. Select OpenRouter as a provider in Window.ai extension. +4. Use OpenAI API provider and enable "Use Window.ai" option in SillyTavern + ## Poe ### API key diff --git a/public/script.js b/public/script.js index f3b0a59ba..e68dcc5a6 100644 --- a/public/script.js +++ b/public/script.js @@ -3725,6 +3725,10 @@ function changeMainAPI() { main_api = selectedVal; online_status = "no_connection"; + if (main_api == 'openai' && oai_settings.use_window_ai) { + $('#api_button_openai').trigger('click'); + } + if (main_api == "koboldhorde") { is_get_status = true; getStatus(); diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 80c93f6cb..c8c664f3a 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -104,6 +104,7 @@ const default_settings = { jailbreak_system: false, reverse_proxy: '', legacy_streaming: false, + use_window_ai: false, }; const oai_settings = { @@ -129,6 +130,7 @@ const oai_settings = { jailbreak_system: false, reverse_proxy: '', legacy_streaming: false, + use_window_ai: false, }; let openai_setting_names; @@ -550,6 +552,41 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) { "logit_bias": logit_bias, }; + if (oai_settings.use_window_ai) { + if (!('ai' in window)) { + return showWindowExtensionError(); + } + + async function* windowStreamingFunction(res) { + yield (res?.message?.content || ''); + } + + const generatePromise = window.ai.generateText( + { + messages: openai_msgs_tosend, + }, + { + temperature: parseFloat(oai_settings.temp_openai), + maxTokens: oai_settings.openai_max_tokens, + onStreamResult: windowStreamingFunction, + } + ); + + if (stream) { + return windowStreamingFunction; + } + + try { + const [{ message }] = await generatePromise; + windowStreamingFunction(message); + return message?.content; + } catch (err) { + const text = parseWindowError(err); + toastr.error(text, 'Window.ai returned an error'); + throw err; + } + } + const generate_url = '/generate_openai'; const response = await fetch(generate_url, { method: 'POST', @@ -614,6 +651,30 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) { } } +function parseWindowError(err) { + let text = 'Unknown error'; + + switch (err) { + case "NOT_AUTHENTICATED": + text = 'Incorrect API key / auth'; + break; + case "MODEL_REJECTED_REQUEST": + text = 'AI model refused to fulfill a request'; + break; + case "PERMISSION_DENIED": + text = 'User denied permission to the app'; + break; + case "REQUEST_NOT_FOUND": + text = 'Permission request popup timed out'; + break; + case "INVALID_REQUEST": + text = 'Malformed request'; + break; + } + + return text; +} + async function calculateLogitBias() { const body = JSON.stringify(oai_settings.bias_presets[oai_settings.bias_preset_selected]); let result = {}; @@ -813,10 +874,27 @@ function loadOpenAISettings(data, settings) { $('#openai_logit_bias_preset').append(option); } $('#openai_logit_bias_preset').trigger('change'); + + $('#use_window_ai').prop('checked', oai_settings.use_window_ai); + $('#openai_form').toggle(!oai_settings.use_window_ai); } async function getStatusOpen() { if (is_get_status_openai) { + if (oai_settings.use_window_ai) { + let status; + + if ('ai' in window) { + status = 'Valid'; + } + else { + showWindowExtensionError(); + status = 'no_connection'; + } + + setOnlineStatus(status); + return resultCheckStatusOpen(); + } let data = { reverse_proxy: oai_settings.reverse_proxy, @@ -851,6 +929,15 @@ async function getStatusOpen() { } } +function showWindowExtensionError() { + toastr.error('Get it here: windowai.io', 'Extension is not installed', { + escapeHtml: false, + timeOut: 0, + extendedTimeOut: 0, + preventDuplicates: true, + }); +} + function resultCheckStatusOpen() { is_api_button_press_openai = false; checkOnlineStatus(); @@ -1221,6 +1308,13 @@ function onReverseProxyInput() { async function onConnectButtonClick(e) { e.stopPropagation(); + + if (oai_settings.use_window_ai) { + is_get_status_openai = true; + is_api_button_press_openai = true; + return await getStatusOpen(); + } + const api_key_openai = $('#api_key_openai').val().trim(); if (api_key_openai.length) { @@ -1386,6 +1480,15 @@ $(document).ready(function () { saveSettingsDebounced(); }); + $('#use_window_ai').on('input', function() { + oai_settings.use_window_ai = !!$(this).prop('checked'); + $('#openai_form').toggle(!oai_settings.use_window_ai); + setOnlineStatus('no_connection'); + resultCheckStatusOpen(); + $('#api_button_openai').trigger('click'); + saveSettingsDebounced(); + }); + $("#api_button_openai").on("click", onConnectButtonClick); $("#openai_reverse_proxy").on("input", onReverseProxyInput); $("#model_openai_select").on("change", onModelChange);