Add Electron Hub as Chat Completions Provider (#4458)
* fixed merge conflicts * Supported max tokens + fixed wrong image model mapping * fixed merge conflicts * fixed merge conflicts * updated the logic * updated the logic * replaced hard coded reasoning_effort mode list with a dynamic function * replaced hard coded reasoning_effort model list with a dynamic function * Fix eslint * Adjust reasoning effort logic * Code clean-up * Add logo * Add inline image quality * Fix multimodal models list * Fix seed not passed * Add "detail" error parser --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="118.66667" height="177.33333" viewBox="0 0 89 133"><path d="M14.5 1.8c-6.3 3-11.3 8.8-13.2 15.1C-.1 21.8-.2 27.6.6 70.6l.9 48.2 3.1 4.4c1.9 2.6 5.3 5.4 8.4 7l5.2 2.8h26.7c14.8 0 28.2-.5 30.2-1 5.4-1.5 11.6-8.6 12.4-14.3 1-6.5-.2-10.4-4.7-15.3-5.1-5.5-8.3-6.4-23.3-6.4C46.2 96 43 94.9 43 90.5c0-4.3 3.3-5.5 15.4-5.5 13 0 18-1.8 22.7-8.3 5.4-7.5 5-14.4-1.3-21.3-5.1-5.5-11-7.4-22.8-7.4-9.2 0-13-1.5-13-5 0-3.9 3.6-5 16.9-5 14.5 0 19.4-1.6 23.7-7.9 5.4-7.9 5.2-15.9-.6-22.5-5.4-6.1-6.7-6.4-37.5-7.1-26.3-.6-28.2-.5-32 1.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 589 B |
+28
-13
@@ -691,7 +691,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
@@ -704,7 +704,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block-title" data-i18n="Frequency Penalty">
|
||||
Frequency Penalty
|
||||
</div>
|
||||
@@ -717,7 +717,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block-title" data-i18n="Presence Penalty">
|
||||
Presence Penalty
|
||||
</div>
|
||||
@@ -730,7 +730,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity">
|
||||
<div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub">
|
||||
<div class="range-block-title" data-i18n="Top K">
|
||||
Top K
|
||||
</div>
|
||||
@@ -743,7 +743,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block-title" data-i18n="Top P">
|
||||
Top P
|
||||
</div>
|
||||
@@ -980,7 +980,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai">
|
||||
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai">
|
||||
<div class="range-block-title justifyLeft" data-i18n="Seed">
|
||||
Seed
|
||||
</div>
|
||||
@@ -1970,7 +1970,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai,nanogpt">
|
||||
<div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai,electronhub,nanogpt">
|
||||
<label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">
|
||||
<input id="openai_enable_web_search" type="checkbox" />
|
||||
<span data-i18n="Enable web search">Enable web search</span>
|
||||
@@ -1984,7 +1984,7 @@
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi">
|
||||
<div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub">
|
||||
<label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
|
||||
<input id="openai_function_calling" type="checkbox" />
|
||||
<span data-i18n="Enable function calling">Enable function calling</span>
|
||||
@@ -1999,7 +1999,7 @@
|
||||
<strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt">
|
||||
<div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub">
|
||||
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
|
||||
<input id="openai_image_inlining" type="checkbox" />
|
||||
<span data-i18n="Send inline images">Send inline images</span>
|
||||
@@ -2015,7 +2015,7 @@
|
||||
<code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
|
||||
<span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai">
|
||||
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub">
|
||||
<div class="flex-container oneline-dropdown">
|
||||
<label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
|
||||
Inline Image Quality
|
||||
@@ -2077,7 +2077,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi">
|
||||
<div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub">
|
||||
<label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
|
||||
<input id="openai_show_thoughts" type="checkbox" />
|
||||
<span data-i18n="Request model reasoning">Request model reasoning</span>
|
||||
@@ -2091,7 +2091,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi">
|
||||
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub">
|
||||
<div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
|
||||
<label for="openai_reasoning_effort">
|
||||
<span data-i18n="Reasoning Effort">Reasoning Effort</span>
|
||||
@@ -2105,7 +2105,7 @@
|
||||
<option data-i18n="openai_reasoning_effort_high" value="high">High</option>
|
||||
<option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>
|
||||
</select>
|
||||
<div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter,perplexity" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.">
|
||||
<div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter,perplexity,electronhub" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.">
|
||||
OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.
|
||||
</div>
|
||||
<div class="toggle-description justifyLeft marginBot5" data-source="claude" data-i18n="Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.">
|
||||
@@ -2807,6 +2807,7 @@
|
||||
<!-- Temporarily disabled. -->
|
||||
<!-- <option value="cometapi">CometAPI</option> -->
|
||||
<option value="deepseek">DeepSeek</option>
|
||||
<option value="electronhub">Electron Hub</option>
|
||||
<option value="fireworks">Fireworks AI</option>
|
||||
<option value="groq">Groq</option>
|
||||
<option value="makersuite">Google AI Studio</option>
|
||||
@@ -3469,6 +3470,20 @@
|
||||
<option value="mistral-saba-24b">mistral-saba-24b</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="electronhub_form" data-source="electronhub">
|
||||
<h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_electronhub" name="api_key_electronhub" class="text_pole flex1" value="" type="text" autocomplete="off">
|
||||
<div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_electronhub"></div>
|
||||
</div>
|
||||
<div data-for="api_key_electronhub" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
|
||||
For privacy reasons, your API key will be hidden after you click 'Connect'.
|
||||
</div>
|
||||
<h4 data-i18n="Electron Hub Model">Electron Hub Model</h4>
|
||||
<select id="model_electronhub_select">
|
||||
<option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="nanogpt_form" data-source="nanogpt">
|
||||
<h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>
|
||||
<div class="flex-container">
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "وضع علامة",
|
||||
"API key (optional)": "مفتاح API (اختياري)",
|
||||
"Server url": "رابط الخادم",
|
||||
"Electron Hub API Key": "مفتاح API لـ Electron Hub",
|
||||
"Electron Hub Model": "نموذج Electron Hub",
|
||||
"Example: http://127.0.0.1:5000": "مثال: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "نموذج مخصص (اختياري)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (وضع غلاف OpenAI API)",
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "Flagge",
|
||||
"API key (optional)": "API-Schlüssel (optional)",
|
||||
"Server url": "Server-URL",
|
||||
"Electron Hub API Key": "Electron Hub API-Schlüssel",
|
||||
"Electron Hub Model": "Electron Hub-Modell",
|
||||
"Example: http://127.0.0.1:5000": "Beispiel: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "Benutzerdefiniertes Modell (optional)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (OpenAI API-Wrappermodus)",
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "bandera",
|
||||
"API key (optional)": "Clave API (opcional)",
|
||||
"Server url": "URL del servidor",
|
||||
"Electron Hub API Key": "Clave API de Electron Hub",
|
||||
"Electron Hub Model": "Modelo de Electron Hub",
|
||||
"Example: http://127.0.0.1:5000": "Ejemplo: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "Modelo personalizado (opcional)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (modo contenedor de API OpenAI)",
|
||||
|
||||
@@ -1411,6 +1411,8 @@
|
||||
"Do not proceed if you do not agree to this!": "Ne continuez pas si vous n'êtes pas d'accord avec cela !",
|
||||
"Claude API Key": "Clé API Claude",
|
||||
"Allow fallback models": "Autoriser les modèles de secours",
|
||||
"Electron Hub API Key": "Clé API Electron Hub",
|
||||
"Electron Hub Model": "Modèle Electron Hub",
|
||||
"NanoGPT API Key": "Clé API NanoGPT",
|
||||
"NanoGPT Model": "Modèle NanoGPT",
|
||||
"DeepSeek API Key": "Clé API DeepSeek",
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "bandiera",
|
||||
"API key (optional)": "Chiave API (opzionale)",
|
||||
"Server url": "URL del server",
|
||||
"Electron Hub API Key": "Chiave API di Electron Hub",
|
||||
"Electron Hub Model": "Modello di Electron Hub",
|
||||
"Example: http://127.0.0.1:5000": "Esempio: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "Modello personalizzato (opzionale)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (modalità wrapper API OpenAI)",
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "フラグ",
|
||||
"API key (optional)": "APIキー(オプション)",
|
||||
"Server url": "サーバーURL",
|
||||
"Electron Hub API Key": "Electron Hub API キー",
|
||||
"Electron Hub Model": "Electron Hub モデル",
|
||||
"Example: http://127.0.0.1:5000": "例: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "カスタムモデル(オプション)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (OpenAI API ラッパーモード)",
|
||||
|
||||
@@ -319,6 +319,8 @@
|
||||
"flag": "깃발",
|
||||
"API key (optional)": "API 키 (선택 사항)",
|
||||
"Server url": "서버 URL",
|
||||
"Electron Hub API Key": "Electron Hub API 키",
|
||||
"Electron Hub Model": "Electron Hub 모델",
|
||||
"Example: http://127.0.0.1:5000": "예시: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "사용자 정의 모델 (선택 사항)",
|
||||
"vllm-project/vllm": "vllm-project/vllm(OpenAI API 래퍼 모드)",
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "bandeira",
|
||||
"API key (optional)": "Chave da API (opcional)",
|
||||
"Server url": "URL do servidor",
|
||||
"Electron Hub API Key": "Chave API Electron Hub",
|
||||
"Electron Hub Model": "Modelo Electron Hub",
|
||||
"Example: http://127.0.0.1:5000": "Exemplo: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "Modelo personalizado (opcional)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (modo wrapper da API OpenAI)",
|
||||
|
||||
@@ -1994,6 +1994,8 @@
|
||||
"Click on the setting name to omit it from the profile.": "Нажмите на название настройки, чтобы исключить её из профиля",
|
||||
"Included settings:": "Сохранённые параметры:",
|
||||
"Server URL": "Адрес сервера",
|
||||
"Electron Hub API Key": "Ключ от API Electron Hub",
|
||||
"Electron Hub Model": "Модель Electron Hub",
|
||||
"NanoGPT API Key": "Ключ от API NanoGPT",
|
||||
"NanoGPT Model": "Модель NanoGPT",
|
||||
"Use extension settings": "Использовать настройки из расширения",
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "прапорцем",
|
||||
"API key (optional)": "Ключ API (необов'язково)",
|
||||
"Server url": "URL-адреса сервера",
|
||||
"Electron Hub API Key": "Ключ API для Electron Hub",
|
||||
"Electron Hub Model": "Модель Electron Hub",
|
||||
"Example: http://127.0.0.1:5000": "Приклад: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "Власна модель (необов'язково)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (режим оболонки OpenAI API)",
|
||||
|
||||
@@ -317,6 +317,8 @@
|
||||
"flag": "cờ",
|
||||
"API key (optional)": "Key API (tùy chọn)",
|
||||
"Server url": "URL máy chủ",
|
||||
"Electron Hub API Key": "Key API Electron Hub",
|
||||
"Electron Hub Model": "Model Electron Hub",
|
||||
"Example: http://127.0.0.1:5000": "Ví dụ: http://127.0.0.1:5000",
|
||||
"Custom model (optional)": "Model tùy chỉnh (tùy chọn)",
|
||||
"vllm-project/vllm": "vllm-project/vllm (Chế độ trình bao bọc API OpenAI)",
|
||||
|
||||
@@ -481,6 +481,8 @@
|
||||
"MistralAI Model": "MistralAI 模型",
|
||||
"Groq API Key": "Groq API 密钥",
|
||||
"Groq Model": "Groq 模型",
|
||||
"Electron Hub API Key": "Electron Hub API 密钥",
|
||||
"Electron Hub Model": "Electron Hub 模型",
|
||||
"NanoGPT API Key": "NanoGPT API 密钥",
|
||||
"NanoGPT Model": "NanoGPT 模型",
|
||||
"DeepSeek API Key": "DeepSeek API 密钥",
|
||||
|
||||
@@ -1791,6 +1791,8 @@
|
||||
"Derive context size from backend": "從後端推導上下文大小",
|
||||
"Using a proxy that you're not running yourself is a risk to your data privacy.": "使用非自行管理的代理服務可能導致您的資料隱私外洩。",
|
||||
"Claude API Key": "Claude API 金鑰",
|
||||
"Electron Hub API Key": "Electron Hub API 金鑰",
|
||||
"Electron Hub Model": "Electron Hub 模型",
|
||||
"NanoGPT API Key": "NanoGPT API 金鑰",
|
||||
"NanoGPT Model": "NanoGPT 模型",
|
||||
"context_derived": "若可能,根據模型後設資料推導。",
|
||||
|
||||
@@ -5369,6 +5369,7 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
|
||||
case chat_completion_sources.CUSTOM:
|
||||
case chat_completion_sources.COHERE:
|
||||
case chat_completion_sources.XAI:
|
||||
case chat_completion_sources.ELECTRONHUB:
|
||||
default:
|
||||
result = tryParse(text);
|
||||
break;
|
||||
|
||||
@@ -402,6 +402,7 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|
||||
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|
||||
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
|
||||
|| (secret_state[SECRET_KEYS.ELECTRONHUB] && oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB)
|
||||
|| (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
|
||||
|| (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
|
||||
|| (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
|
||||
|
||||
@@ -440,6 +440,7 @@ jQuery(async function () {
|
||||
'aimlapi': SECRET_KEYS.AIMLAPI,
|
||||
'moonshot': SECRET_KEYS.MOONSHOT,
|
||||
'nanogpt': SECRET_KEYS.NANOGPT,
|
||||
'electronhub': SECRET_KEYS.ELECTRONHUB,
|
||||
};
|
||||
|
||||
if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
|
||||
@@ -547,6 +548,7 @@ jQuery(async function () {
|
||||
await processEndpoint('aimlapi', '/api/backends/chat-completions/multimodal-models/aimlapi');
|
||||
await processEndpoint('pollinations', '/api/backends/chat-completions/multimodal-models/pollinations');
|
||||
await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');
|
||||
await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub');
|
||||
}
|
||||
|
||||
await addSettings();
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<option value="anthropic">Anthropic</option>
|
||||
<option value="cohere">Cohere</option>
|
||||
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
|
||||
<option value="electronhub">Electron Hub</option>
|
||||
<option value="google">Google AI Studio</option>
|
||||
<option value="vertexai">Google Vertex AI</option>
|
||||
<option value="groq">Groq</option>
|
||||
|
||||
@@ -251,6 +251,10 @@ function throwIfInvalidModel(useReverseProxy) {
|
||||
if (multimodalApi === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT]) {
|
||||
throw new Error('NanoGPT API key is not set.');
|
||||
}
|
||||
|
||||
if (multimodalApi === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB]) {
|
||||
throw new Error('Electron Hub API key is not set.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,6 +82,7 @@ const sources = {
|
||||
pollinations: 'pollinations',
|
||||
stability: 'stability',
|
||||
huggingface: 'huggingface',
|
||||
electronhub: 'electronhub',
|
||||
nanogpt: 'nanogpt',
|
||||
bfl: 'bfl',
|
||||
falai: 'falai',
|
||||
@@ -1289,6 +1290,7 @@ async function onModelChange() {
|
||||
sources.pollinations,
|
||||
sources.stability,
|
||||
sources.huggingface,
|
||||
sources.electronhub,
|
||||
sources.nanogpt,
|
||||
sources.bfl,
|
||||
sources.falai,
|
||||
@@ -1506,6 +1508,9 @@ async function loadSamplers() {
|
||||
case sources.huggingface:
|
||||
samplers = ['N/A'];
|
||||
break;
|
||||
case sources.electronhub:
|
||||
samplers = ['N/A'];
|
||||
break;
|
||||
case sources.nanogpt:
|
||||
samplers = ['N/A'];
|
||||
break;
|
||||
@@ -1702,6 +1707,9 @@ async function loadModels() {
|
||||
case sources.huggingface:
|
||||
models = [{ value: '', text: '<Enter Model ID above>' }];
|
||||
break;
|
||||
case sources.electronhub:
|
||||
models = await loadElectronHubModels();
|
||||
break;
|
||||
case sources.nanogpt:
|
||||
models = await loadNanoGPTModels();
|
||||
break;
|
||||
@@ -1806,6 +1814,24 @@ async function loadTogetherAIModels() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadElectronHubModels() {
|
||||
if (!secret_state[SECRET_KEYS.ELECTRONHUB]) {
|
||||
console.debug('Electron Hub API key is not set.');
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await fetch('/api/sd/electronhub/models', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
return await result.json();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadNanoGPTModels() {
|
||||
if (!secret_state[SECRET_KEYS.NANOGPT]) {
|
||||
console.debug('NanoGPT API key is not set.');
|
||||
@@ -2131,6 +2157,9 @@ async function loadSchedulers() {
|
||||
case sources.huggingface:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
case sources.electronhub:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
case sources.nanogpt:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
@@ -2228,6 +2257,9 @@ async function loadVaes() {
|
||||
case sources.huggingface:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
case sources.electronhub:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
case sources.nanogpt:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
@@ -2811,6 +2843,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
|
||||
case sources.huggingface:
|
||||
result = await generateHuggingFaceImage(prefixedPrompt, signal);
|
||||
break;
|
||||
case sources.electronhub:
|
||||
result = await generateElectronHubImage(prefixedPrompt, signal);
|
||||
break;
|
||||
case sources.nanogpt:
|
||||
result = await generateNanoGPTImage(prefixedPrompt, negativePrompt, signal);
|
||||
break;
|
||||
@@ -3013,6 +3048,56 @@ function getClosestAspectRatio(width, height, source) {
|
||||
return closestAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get closest size for Electron Hub
|
||||
* @param {number} width - The width of the image
|
||||
* @param {number} height - The height of the image
|
||||
* @returns {Promise<string>} - The closest size
|
||||
*/
|
||||
async function getClosestSize(width, height) {
|
||||
const response = await fetch('/api/sd/electronhub/sizes', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
model: extension_settings.sd.model,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
const result = await response.json();
|
||||
const sizesData = result.sizes;
|
||||
|
||||
const closestSize = sizesData.reduce((closest, size) => {
|
||||
if (!size || typeof size !== 'string') {
|
||||
return closest;
|
||||
}
|
||||
const sizeParts = size.split('x');
|
||||
if (sizeParts.length !== 2) {
|
||||
return closest;
|
||||
}
|
||||
|
||||
const sizeWidth = Number(sizeParts[0]);
|
||||
const sizeHeight = Number(sizeParts[1]);
|
||||
const targetWidth = Number(width);
|
||||
const targetHeight = Number(height);
|
||||
|
||||
if (isNaN(sizeWidth) || isNaN(sizeHeight) || isNaN(targetWidth) || isNaN(targetHeight)) {
|
||||
return closest;
|
||||
}
|
||||
|
||||
const sizeArea = sizeWidth * sizeHeight;
|
||||
const targetArea = targetWidth * targetHeight;
|
||||
const diff = Math.abs(sizeArea - targetArea);
|
||||
|
||||
return diff < closest.diff ? { size, diff } : closest;
|
||||
}, { size: null, diff: Infinity });
|
||||
|
||||
const size = closestSize.size;
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image using Stability AI.
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
@@ -3564,6 +3649,35 @@ async function generateHuggingFaceImage(prompt, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image using the Electron Hub API.
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateElectronHubImage(prompt, signal) {
|
||||
const size = await getClosestSize(extension_settings.sd.width, extension_settings.sd.height);
|
||||
|
||||
const result = await fetch('/api/sd/electronhub/generate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
signal: signal,
|
||||
body: JSON.stringify({
|
||||
model: extension_settings.sd.model,
|
||||
prompt: prompt,
|
||||
size: size,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
return { format: 'jpg', data: data.image };
|
||||
} else {
|
||||
const text = await result.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image using the NanoGPT API.
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
@@ -4014,6 +4128,8 @@ function isValidState() {
|
||||
return secret_state[SECRET_KEYS.STABILITY];
|
||||
case sources.huggingface:
|
||||
return secret_state[SECRET_KEYS.HUGGINGFACE];
|
||||
case sources.electronhub:
|
||||
return secret_state[SECRET_KEYS.ELECTRONHUB];
|
||||
case sources.nanogpt:
|
||||
return secret_state[SECRET_KEYS.NANOGPT];
|
||||
case sources.bfl:
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<option value="bfl">BFL (Black Forest Labs)</option>
|
||||
<option value="comfy">ComfyUI</option>
|
||||
<option value="drawthings">DrawThings HTTP API</option>
|
||||
<option value="electronhub">Electron Hub</option>
|
||||
<option value="extras">Extras API (deprecated)</option>
|
||||
<option value="falai">FAL.AI</option>
|
||||
<option value="google">Google AI</option>
|
||||
@@ -93,6 +94,9 @@
|
||||
<label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label>
|
||||
<input id="sd_huggingface_model_id" type="text" class="text_pole" data-i18n="[placeholder]e.g. black-forest-labs/FLUX.1-dev" placeholder="e.g. black-forest-labs/FLUX.1-dev" value="" />
|
||||
</div>
|
||||
<div data-sd-source="electronhub">
|
||||
<i>Hint: Save an API key in the Electron Hub (Chat Completion) API settings to use it here.</i>
|
||||
</div>
|
||||
<div data-sd-source="nanogpt">
|
||||
<i>Hint: Save an API key in the NanoGPT (Chat Completion) API settings to use it here.</i>
|
||||
</div>
|
||||
|
||||
+130
-12
@@ -179,6 +179,7 @@ export const chat_completion_sources = {
|
||||
COHERE: 'cohere',
|
||||
PERPLEXITY: 'perplexity',
|
||||
GROQ: 'groq',
|
||||
ELECTRONHUB: 'electronhub',
|
||||
NANOGPT: 'nanogpt',
|
||||
DEEPSEEK: 'deepseek',
|
||||
AIMLAPI: 'aimlapi',
|
||||
@@ -271,6 +272,7 @@ export const settingsToUpdate = {
|
||||
cohere_model: ['#model_cohere_select', 'cohere_model', false, true],
|
||||
perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],
|
||||
groq_model: ['#model_groq_select', 'groq_model', false, true],
|
||||
electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],
|
||||
nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],
|
||||
deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],
|
||||
aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],
|
||||
@@ -369,6 +371,7 @@ const default_settings = {
|
||||
cohere_model: 'command-r-plus',
|
||||
perplexity_model: 'sonar-pro',
|
||||
groq_model: 'llama-3.3-70b-versatile',
|
||||
electronhub_model: 'gpt-4o-mini',
|
||||
nanogpt_model: 'gpt-4o-mini',
|
||||
deepseek_model: 'deepseek-chat',
|
||||
aimlapi_model: 'gpt-4o-mini-2024-07-18',
|
||||
@@ -458,6 +461,7 @@ const oai_settings = {
|
||||
cohere_model: 'command-r-plus',
|
||||
perplexity_model: 'sonar-pro',
|
||||
groq_model: 'llama-3.1-70b-versatile',
|
||||
electronhub_model: 'gpt-4o-mini',
|
||||
nanogpt_model: 'gpt-4o-mini',
|
||||
deepseek_model: 'deepseek-chat',
|
||||
aimlapi_model: 'gpt-4-turbo',
|
||||
@@ -1542,6 +1546,11 @@ export function tryParseStreamingError(response, decoded, { quiet = false } = {}
|
||||
!quiet && toastr.error(data.message, 'Chat Completion API');
|
||||
throw new Error(data);
|
||||
}
|
||||
|
||||
if (data.detail) {
|
||||
!quiet && toastr.error(data.detail?.error?.message || response.statusText, 'Chat Completion API');
|
||||
throw new Error(data);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// No JSON. Do nothing.
|
||||
@@ -1614,6 +1623,8 @@ export function getChatCompletionModel(source = null) {
|
||||
return oai_settings.perplexity_model;
|
||||
case chat_completion_sources.GROQ:
|
||||
return oai_settings.groq_model;
|
||||
case chat_completion_sources.ELECTRONHUB:
|
||||
return oai_settings.electronhub_model;
|
||||
case chat_completion_sources.NANOGPT:
|
||||
return oai_settings.nanogpt_model;
|
||||
case chat_completion_sources.DEEPSEEK:
|
||||
@@ -1774,6 +1785,26 @@ function saveModelList(data) {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
|
||||
$('#model_electronhub_select').empty();
|
||||
model_list.forEach((model) => {
|
||||
if (model?.endpoints?.includes('/v1/chat/completions')) {
|
||||
$('#model_electronhub_select').append(
|
||||
$('<option>', {
|
||||
value: model.id,
|
||||
text: model.name,
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model);
|
||||
if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) {
|
||||
oai_settings.electronhub_model = model_list[0].id;
|
||||
}
|
||||
|
||||
$('#model_electronhub_select').val(oai_settings.electronhub_model).trigger('change');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
|
||||
$('#model_nanogpt_select').empty();
|
||||
model_list.forEach((model) => {
|
||||
@@ -2044,24 +2075,43 @@ function getReasoningEffort() {
|
||||
chat_completion_sources.POLLINATIONS,
|
||||
chat_completion_sources.PERPLEXITY,
|
||||
chat_completion_sources.COMETAPI,
|
||||
chat_completion_sources.ELECTRONHUB,
|
||||
];
|
||||
|
||||
if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {
|
||||
return oai_settings.reasoning_effort;
|
||||
}
|
||||
|
||||
switch (oai_settings.reasoning_effort) {
|
||||
case reasoning_effort_types.auto:
|
||||
return undefined;
|
||||
case reasoning_effort_types.min:
|
||||
return chat_completion_sources.OPENAI === oai_settings.chat_completion_source && /^gpt-5/.test(oai_settings.openai_model)
|
||||
? reasoning_effort_types.min
|
||||
: reasoning_effort_types.low;
|
||||
case reasoning_effort_types.max:
|
||||
return reasoning_effort_types.high;
|
||||
default:
|
||||
return oai_settings.reasoning_effort;
|
||||
function resolveReasoningEffort() {
|
||||
switch (oai_settings.reasoning_effort) {
|
||||
case reasoning_effort_types.auto:
|
||||
return undefined;
|
||||
case reasoning_effort_types.min:
|
||||
return chat_completion_sources.OPENAI === oai_settings.chat_completion_source && /^gpt-5/.test(oai_settings.openai_model)
|
||||
? reasoning_effort_types.min
|
||||
: reasoning_effort_types.low;
|
||||
case reasoning_effort_types.max:
|
||||
return reasoning_effort_types.high;
|
||||
default:
|
||||
return oai_settings.reasoning_effort;
|
||||
}
|
||||
}
|
||||
|
||||
const reasoningEffort = resolveReasoningEffort();
|
||||
|
||||
// Check if the resolved effort supported by the model
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {
|
||||
if (Array.isArray(model_list) && reasoningEffort) {
|
||||
const currentModel = model_list.find(m => m.id === oai_settings.electronhub_model);
|
||||
const supportedEfforts = currentModel?.metadata?.supported_reasoning_efforts;
|
||||
if (Array.isArray(supportedEfforts) && supportedEfforts.includes(reasoningEffort)) {
|
||||
return reasoningEffort;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return reasoningEffort;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2100,6 +2150,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
|
||||
const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
|
||||
const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
|
||||
const isAimlapi = oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI;
|
||||
const isElectronHub = oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB;
|
||||
const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
|
||||
const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;
|
||||
const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;
|
||||
@@ -2283,6 +2334,11 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
|
||||
delete generate_data.max_tokens;
|
||||
}
|
||||
|
||||
// https://docs.electronhub.ai/api-reference/chat/completions
|
||||
if (isElectronHub) {
|
||||
generate_data['top_k'] = Number(oai_settings.top_k_openai);
|
||||
}
|
||||
|
||||
const seedSupportedSources = [
|
||||
chat_completion_sources.OPENAI,
|
||||
chat_completion_sources.OPENROUTER,
|
||||
@@ -2290,6 +2346,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
|
||||
chat_completion_sources.CUSTOM,
|
||||
chat_completion_sources.COHERE,
|
||||
chat_completion_sources.GROQ,
|
||||
chat_completion_sources.ELECTRONHUB,
|
||||
chat_completion_sources.NANOGPT,
|
||||
chat_completion_sources.XAI,
|
||||
chat_completion_sources.POLLINATIONS,
|
||||
@@ -2462,7 +2519,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
|
||||
state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
|
||||
}
|
||||
return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
|
||||
} else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI].includes(chat_completion_source)) {
|
||||
} else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB].includes(chat_completion_source)) {
|
||||
if (show_thoughts) {
|
||||
state.reasoning +=
|
||||
data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
|
||||
@@ -3424,6 +3481,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.cohere_model = settings.cohere_model ?? default_settings.cohere_model;
|
||||
oai_settings.perplexity_model = settings.perplexity_model ?? default_settings.perplexity_model;
|
||||
oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
|
||||
oai_settings.electronhub_model = settings.electronhub_model ?? default_settings.electronhub_model;
|
||||
oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
|
||||
oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
|
||||
oai_settings.aimlapi_model = settings.aimlapi_model ?? default_settings.aimlapi_model;
|
||||
@@ -3519,6 +3577,8 @@ function loadOpenAISettings(data, settings) {
|
||||
$(`#model_perplexity_select option[value="${oai_settings.perplexity_model}"`).prop('selected', true);
|
||||
$('#model_groq_select').val(oai_settings.groq_model);
|
||||
$(`#model_groq_select option[value="${oai_settings.groq_model}"`).prop('selected', true);
|
||||
$('#model_electronhub_select').val(oai_settings.electronhub_model);
|
||||
$(`#model_electronhub_select option[value="${oai_settings.electronhub_model}"`).prop('selected', true);
|
||||
$('#model_nanogpt_select').val(oai_settings.nanogpt_model);
|
||||
$(`#model_nanogpt_select option[value="${oai_settings.nanogpt_model}"`).prop('selected', true);
|
||||
$('#model_deepseek_select').val(oai_settings.deepseek_model);
|
||||
@@ -3805,6 +3865,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
xai_model: settings.xai_model,
|
||||
pollinations_model: settings.pollinations_model,
|
||||
aimlapi_model: settings.aimlapi_model,
|
||||
electronhub_model: settings.electronhub_model,
|
||||
moonshot_model: settings.moonshot_model,
|
||||
fireworks_model: settings.fireworks_model,
|
||||
cometapi_model: settings.cometapi_model,
|
||||
@@ -4570,6 +4631,26 @@ function getFireworksMaxContext(model, isUnlocked) {
|
||||
return max_32k;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum context size for the ElectronHub model
|
||||
* @param {string} model Model identifier
|
||||
* @param {boolean} isUnlocked Whether context limits are unlocked
|
||||
* @returns {number} Maximum context size in tokens
|
||||
*/
|
||||
function getElectronHubMaxContext(model, isUnlocked) {
|
||||
if (isUnlocked) {
|
||||
return unlocked_max;
|
||||
}
|
||||
|
||||
if (Array.isArray(model_list)) {
|
||||
const modelInfo = model_list.find(m => m.id === model);
|
||||
if (modelInfo?.tokens) {
|
||||
return modelInfo.tokens;
|
||||
}
|
||||
}
|
||||
return max_8k;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum context size for the NanoGPT model
|
||||
* @param {string} model Model identifier
|
||||
@@ -4681,6 +4762,15 @@ async function onModelChange() {
|
||||
oai_settings.groq_model = value;
|
||||
}
|
||||
|
||||
if ($(this).is('#model_electronhub_select')) {
|
||||
if (!value) {
|
||||
console.debug('Null ElectronHub model selected. Ignoring.');
|
||||
return;
|
||||
}
|
||||
console.log('ElectronHub model changed to', value);
|
||||
oai_settings.electronhub_model = value;
|
||||
}
|
||||
|
||||
if ($(this).is('#model_nanogpt_select')) {
|
||||
if (!value) {
|
||||
console.debug('Null NanoGPT model selected. Ignoring.');
|
||||
@@ -4922,6 +5012,15 @@ async function onModelChange() {
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
|
||||
const maxContext = getElectronHubMaxContext(oai_settings.electronhub_model, oai_settings.max_context_unlocked);
|
||||
$('#openai_max_context').attr('max', maxContext);
|
||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.NANOGPT) {
|
||||
const maxContext = getNanoGptMaxContext(oai_settings.nanogpt_model, oai_settings.max_context_unlocked);
|
||||
$('#openai_max_context').attr('max', maxContext);
|
||||
@@ -5216,6 +5315,19 @@ async function onConnectButtonClick(e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
|
||||
const api_key_electronhub = String($('#api_key_electronhub').val()).trim();
|
||||
|
||||
if (api_key_electronhub.length) {
|
||||
await writeSecret(SECRET_KEYS.ELECTRONHUB, api_key_electronhub);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.ELECTRONHUB]) {
|
||||
console.log('No secret key saved for Electron Hub');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
|
||||
const api_key_nanogpt = String($('#api_key_nanogpt').val()).trim();
|
||||
|
||||
@@ -5350,6 +5462,9 @@ function toggleChatCompletionForms() {
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {
|
||||
$('#model_groq_select').trigger('change');
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
|
||||
$('#model_electronhub_select').trigger('change');
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
|
||||
$('#model_nanogpt_select').trigger('change');
|
||||
}
|
||||
@@ -5518,6 +5633,8 @@ export function isImageInliningSupported() {
|
||||
return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
|
||||
case chat_completion_sources.AIMLAPI:
|
||||
return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision'));
|
||||
case chat_completion_sources.ELECTRONHUB:
|
||||
return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.electronhub_model)?.metadata?.vision);
|
||||
case chat_completion_sources.POLLINATIONS:
|
||||
return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.pollinations_model)?.vision);
|
||||
case chat_completion_sources.COMETAPI:
|
||||
@@ -6326,6 +6443,7 @@ export function initOpenAI() {
|
||||
$('#model_cohere_select').on('change', onModelChange);
|
||||
$('#model_perplexity_select').on('change', onModelChange);
|
||||
$('#model_groq_select').on('change', onModelChange);
|
||||
$('#model_electronhub_select').on('change', onModelChange);
|
||||
$('#model_nanogpt_select').on('change', onModelChange);
|
||||
$('#model_deepseek_select').on('change', onModelChange);
|
||||
$('#model_aimlapi_select').on('change', onModelChange);
|
||||
|
||||
@@ -51,6 +51,7 @@ export const SECRET_KEYS = {
|
||||
HUGGINGFACE: 'api_key_huggingface',
|
||||
STABILITY: 'api_key_stability',
|
||||
CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
|
||||
ELECTRONHUB: 'api_key_electronhub',
|
||||
NANOGPT: 'api_key_nanogpt',
|
||||
TAVILY: 'api_key_tavily',
|
||||
BFL: 'api_key_bfl',
|
||||
@@ -95,6 +96,7 @@ const FRIENDLY_NAMES = {
|
||||
[SECRET_KEYS.GROQ]: 'Groq',
|
||||
[SECRET_KEYS.FEATHERLESS]: 'Featherless',
|
||||
[SECRET_KEYS.HUGGINGFACE]: 'HuggingFace',
|
||||
[SECRET_KEYS.ELECTRONHUB]: 'Electron Hub',
|
||||
[SECRET_KEYS.NANOGPT]: 'NanoGPT',
|
||||
[SECRET_KEYS.GENERIC]: 'Generic (OpenAI-compatible)',
|
||||
[SECRET_KEYS.DEEPSEEK]: 'DeepSeek',
|
||||
@@ -148,6 +150,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.GROQ]: '#api_key_groq',
|
||||
[SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
|
||||
[SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
|
||||
[SECRET_KEYS.ELECTRONHUB]: '#api_key_electronhub',
|
||||
[SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
|
||||
[SECRET_KEYS.GENERIC]: '#api_key_generic',
|
||||
[SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
|
||||
|
||||
@@ -4841,6 +4841,7 @@ function getModelOptions(quiet) {
|
||||
{ id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },
|
||||
{ id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },
|
||||
{ id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
|
||||
{ id: 'model_electronhub_select', api: 'openai', type: chat_completion_sources.ELECTRONHUB },
|
||||
{ id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
|
||||
{ id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
|
||||
{ id: 'model_aimlapi_select', api: 'openai', type: chat_completion_sources.AIMLAPI },
|
||||
|
||||
@@ -633,6 +633,13 @@ export class ToolManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB && Array.isArray(model_list)) {
|
||||
const currentModel = model_list.find(model => model.id === oai_settings.electronhub_model);
|
||||
if (currentModel && currentModel.metadata?.function_call) {
|
||||
return currentModel.metadata.function_call;
|
||||
}
|
||||
}
|
||||
|
||||
const supportedSources = [
|
||||
chat_completion_sources.OPENAI,
|
||||
chat_completion_sources.CUSTOM,
|
||||
@@ -651,6 +658,7 @@ export class ToolManager {
|
||||
chat_completion_sources.MOONSHOT,
|
||||
chat_completion_sources.FIREWORKS,
|
||||
chat_completion_sources.COMETAPI,
|
||||
chat_completion_sources.ELECTRONHUB,
|
||||
];
|
||||
return supportedSources.includes(oai_settings.chat_completion_source);
|
||||
}
|
||||
|
||||
@@ -173,6 +173,7 @@ export const CHAT_COMPLETION_SOURCES = {
|
||||
COHERE: 'cohere',
|
||||
PERPLEXITY: 'perplexity',
|
||||
GROQ: 'groq',
|
||||
ELECTRONHUB: 'electronhub',
|
||||
NANOGPT: 'nanogpt',
|
||||
DEEPSEEK: 'deepseek',
|
||||
AIMLAPI: 'aimlapi',
|
||||
|
||||
@@ -61,6 +61,7 @@ const API_GROQ = 'https://api.groq.com/openai/v1';
|
||||
const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
|
||||
const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com';
|
||||
const API_AI21 = 'https://api.ai21.com/studio/v1';
|
||||
const API_ELECTRONHUB = 'https://api.electronhub.ai/v1';
|
||||
const API_NANOGPT = 'https://nano-gpt.com/api/v1';
|
||||
const API_DEEPSEEK = 'https://api.deepseek.com/beta';
|
||||
const API_XAI = 'https://api.x.ai/v1';
|
||||
@@ -1193,6 +1194,106 @@ async function sendAimlapiRequest(request, response) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to Electron Hub.
|
||||
* @param {express.Request} request Express request
|
||||
* @param {express.Response} response Express response
|
||||
*/
|
||||
async function sendElectronHubRequest(request, response) {
|
||||
const apiUrl = API_ELECTRONHUB;
|
||||
const apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
|
||||
|
||||
if (!apiKey) {
|
||||
console.warn('Electron Hub key is missing.');
|
||||
return response.status(400).send({ error: true });
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
request.socket.removeAllListeners('close');
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
try {
|
||||
let bodyParams = {};
|
||||
|
||||
if (request.body.enable_web_search) {
|
||||
bodyParams['web_search'] = true;
|
||||
}
|
||||
|
||||
if (Array.isArray(request.body.tools) && request.body.tools.length > 0) {
|
||||
bodyParams['tools'] = request.body.tools;
|
||||
bodyParams['tool_choice'] = request.body.tool_choice;
|
||||
}
|
||||
|
||||
if (request.body.reasoning_effort) {
|
||||
bodyParams['reasoning_effort'] = request.body.reasoning_effort;
|
||||
}
|
||||
|
||||
if (request.body.json_schema) {
|
||||
bodyParams['response_format'] = {
|
||||
type: 'json_schema',
|
||||
json_schema: {
|
||||
name: request.body.json_schema.name,
|
||||
description: request.body.json_schema.description,
|
||||
schema: request.body.json_schema.value,
|
||||
strict: request.body.json_schema.strict ?? true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const requestBody = {
|
||||
'messages': request.body.messages,
|
||||
'model': request.body.model,
|
||||
'temperature': request.body.temperature,
|
||||
'max_tokens': request.body.max_tokens,
|
||||
'stream': request.body.stream,
|
||||
'presence_penalty': request.body.presence_penalty,
|
||||
'frequency_penalty': request.body.frequency_penalty,
|
||||
'top_p': request.body.top_p,
|
||||
'top_k': request.body.top_k,
|
||||
'seed': request.body.seed,
|
||||
...bodyParams,
|
||||
};
|
||||
|
||||
const config = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + apiKey,
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
console.debug('Electron Hub request:', requestBody);
|
||||
|
||||
const generateResponse = await fetch(apiUrl + '/chat/completions', config);
|
||||
|
||||
if (request.body.stream) {
|
||||
forwardFetchResponse(generateResponse, response);
|
||||
} else {
|
||||
if (!generateResponse.ok) {
|
||||
const errorText = await generateResponse.text();
|
||||
console.warn('Electron Hub returned error: ', errorText);
|
||||
const errorJson = tryParse(errorText) ?? { error: true };
|
||||
return response.status(500).send(errorJson);
|
||||
}
|
||||
const generateResponseJson = await generateResponse.json();
|
||||
console.debug('Electron Hub response:', generateResponseJson);
|
||||
return response.send(generateResponseJson);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error communicating with Electron Hub: ', error);
|
||||
if (!response.headersSent) {
|
||||
response.send({ error: true });
|
||||
} else {
|
||||
response.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
router.post('/status', async function (request, statusResponse) {
|
||||
@@ -1225,6 +1326,10 @@ router.post('/status', async function (request, statusResponse) {
|
||||
apiUrl = API_COHERE_V1;
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE);
|
||||
headers = {};
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ELECTRONHUB) {
|
||||
apiUrl = API_ELECTRONHUB;
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
|
||||
headers = {};
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.NANOGPT) {
|
||||
apiUrl = API_NANOGPT;
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
|
||||
@@ -1490,6 +1595,7 @@ router.post('/generate', function (request, response) {
|
||||
case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);
|
||||
case CHAT_COMPLETION_SOURCES.AIMLAPI: return sendAimlapiRequest(request, response);
|
||||
case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response);
|
||||
case CHAT_COMPLETION_SOURCES.ELECTRONHUB: return sendElectronHubRequest(request, response);
|
||||
}
|
||||
|
||||
let apiUrl;
|
||||
@@ -1927,4 +2033,22 @@ multimodalModels.post('/nanogpt', async (_req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
multimodalModels.post('/electronhub', async (_req, res) => {
|
||||
try {
|
||||
const response = await fetch('https://api.electronhub.ai/v1/models');
|
||||
|
||||
if (!response.ok) {
|
||||
return res.json([]);
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const data = await response.json();
|
||||
const multimodalModels = data.data.filter(m => m.metadata?.vision).map(m => m.id);
|
||||
return res.json(multimodalModels);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
router.use('/multimodal-models', multimodalModels);
|
||||
|
||||
@@ -81,6 +81,10 @@ router.post('/caption-image', async (request, response) => {
|
||||
key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
|
||||
}
|
||||
|
||||
if (request.body.api === 'electronhub') {
|
||||
key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
|
||||
}
|
||||
|
||||
const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations'];
|
||||
if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) {
|
||||
console.warn('No key found for API', request.body.api);
|
||||
@@ -169,6 +173,10 @@ router.post('/caption-image', async (request, response) => {
|
||||
apiUrl = 'https://nano-gpt.com/api/v1/chat/completions';
|
||||
}
|
||||
|
||||
if (request.body.api === 'electronhub') {
|
||||
apiUrl = 'https://api.electronhub.ai/v1/chat/completions';
|
||||
}
|
||||
|
||||
if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {
|
||||
apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export const SECRET_KEYS = {
|
||||
STABILITY: 'api_key_stability',
|
||||
CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
|
||||
TAVILY: 'api_key_tavily',
|
||||
ELECTRONHUB: 'api_key_electronhub',
|
||||
NANOGPT: 'api_key_nanogpt',
|
||||
BFL: 'api_key_bfl',
|
||||
FALAI: 'api_key_falai',
|
||||
|
||||
@@ -957,6 +957,118 @@ huggingface.post('/generate', async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
const electronhub = express.Router();
|
||||
|
||||
electronhub.post('/models', async (request, response) => {
|
||||
try {
|
||||
const key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
|
||||
|
||||
if (!key) {
|
||||
console.warn('Electron Hub key not found.');
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const modelsResponse = await fetch('https://api.electronhub.ai/v1/models', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${key}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!modelsResponse.ok) {
|
||||
console.warn('Electron Hub returned an error.');
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const data = await modelsResponse.json();
|
||||
const models = data.data.filter(x => x.endpoints.includes('/v1/images/generations')).map(x => ({ value: x.id, text: x.name }));
|
||||
return response.send(models);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
electronhub.post('/generate', async (request, response) => {
|
||||
try {
|
||||
const key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
|
||||
|
||||
if (!key) {
|
||||
console.warn('Electron Hub key not found.');
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
let bodyParams = {
|
||||
model: request.body.model,
|
||||
prompt: request.body.prompt,
|
||||
response_format: 'b64_json',
|
||||
};
|
||||
|
||||
if (request.body.size) {
|
||||
bodyParams.size = request.body.size;
|
||||
}
|
||||
|
||||
const result = await fetch('https://api.electronhub.ai/v1/images/generations', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${key}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...bodyParams,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const errorText = await result.text();
|
||||
console.warn('Electron Hub returned an error.', result.status, result.statusText, errorText);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const data = await result.json();
|
||||
const image = data?.data?.[0]?.b64_json;
|
||||
|
||||
if (!image) {
|
||||
console.warn('Electron Hub returned invalid data.');
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
return response.send({ image });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
electronhub.post('/sizes', async (request, response) => {
|
||||
const result = await fetch(`https://api.electronhub.ai/v1/models/${request.body.model}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
console.warn('Electron Hub returned an error.');
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const data = await result.json();
|
||||
|
||||
const sizes = data.sizes;
|
||||
|
||||
if (!sizes) {
|
||||
console.warn('Electron Hub returned invalid data.');
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
return response.send({ sizes });
|
||||
});
|
||||
|
||||
const nanogpt = express.Router();
|
||||
|
||||
nanogpt.post('/models', async (request, response) => {
|
||||
@@ -1439,6 +1551,7 @@ router.use('/drawthings', drawthings);
|
||||
router.use('/pollinations', pollinations);
|
||||
router.use('/stability', stability);
|
||||
router.use('/huggingface', huggingface);
|
||||
router.use('/electronhub', electronhub);
|
||||
router.use('/nanogpt', nanogpt);
|
||||
router.use('/bfl', bfl);
|
||||
router.use('/falai', falai);
|
||||
|
||||
Reference in New Issue
Block a user