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>

8687bb99f3dd7a20555f82173420c70f34398c95

Ngo Dinh Gia Bao <112873281+snowby666@users.noreply.github.com>

Signed
31 files changed, +562 -14Showing whitespace changes
public/img/electronhub.svg+1 -0
@@ -0,0 +1 @@
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>
\ No newline at end of file1 \ No newline at end of file
public/index.html+28 -13
@@ -691,7 +691,7 @@
691 </span>691 </span>
692 </div>692 </div>
693 </div>693 </div>
694 <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">694 <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">
695 <div class="range-block-title" data-i18n="Temperature">695 <div class="range-block-title" data-i18n="Temperature">
696 Temperature696 Temperature
697 </div>697 </div>
@@ -704,7 +704,7 @@
704 </div>704 </div>
705 </div>705 </div>
706 </div>706 </div>
707 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">707 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
708 <div class="range-block-title" data-i18n="Frequency Penalty">708 <div class="range-block-title" data-i18n="Frequency Penalty">
709 Frequency Penalty709 Frequency Penalty
710 </div>710 </div>
@@ -717,7 +717,7 @@
717 </div>717 </div>
718 </div>718 </div>
719 </div>719 </div>
720 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">720 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi">
721 <div class="range-block-title" data-i18n="Presence Penalty">721 <div class="range-block-title" data-i18n="Presence Penalty">
722 Presence Penalty722 Presence Penalty
723 </div>723 </div>
@@ -730,7 +730,7 @@
730 </div>730 </div>
731 </div>731 </div>
732 </div>732 </div>
733 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity">733 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub">
734 <div class="range-block-title" data-i18n="Top K">734 <div class="range-block-title" data-i18n="Top K">
735 Top K735 Top K
736 </div>736 </div>
@@ -743,7 +743,7 @@
743 </div>743 </div>
744 </div>744 </div>
745 </div>745 </div>
746 <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">746 <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">
747 <div class="range-block-title" data-i18n="Top P">747 <div class="range-block-title" data-i18n="Top P">
748 Top P748 Top P
749 </div>749 </div>
@@ -980,7 +980,7 @@
980 </div>980 </div>
981 </div>981 </div>
982 </div>982 </div>
983 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai">983 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai">
984 <div class="range-block-title justifyLeft" data-i18n="Seed">984 <div class="range-block-title justifyLeft" data-i18n="Seed">
985 Seed985 Seed
986 </div>986 </div>
@@ -1970,7 +1970,7 @@
1970 </span>1970 </span>
1971 </div>1971 </div>
1972 </div>1972 </div>
1973 <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai,nanogpt">1973 <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai,electronhub,nanogpt">
1974 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">1974 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">
1975 <input id="openai_enable_web_search" type="checkbox" />1975 <input id="openai_enable_web_search" type="checkbox" />
1976 <span data-i18n="Enable web search">Enable web search</span>1976 <span data-i18n="Enable web search">Enable web search</span>
@@ -1984,7 +1984,7 @@
1984 </b>1984 </b>
1985 </div>1985 </div>
1986 </div>1986 </div>
1987 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi">1987 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub">
1988 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">1988 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
1989 <input id="openai_function_calling" type="checkbox" />1989 <input id="openai_function_calling" type="checkbox" />
1990 <span data-i18n="Enable function calling">Enable function calling</span>1990 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1999,7 +1999,7 @@
1999 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>1999 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
2000 </div>2000 </div>
2001 </div>2001 </div>
2002 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt">2002 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub">
2003 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">2003 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
2004 <input id="openai_image_inlining" type="checkbox" />2004 <input id="openai_image_inlining" type="checkbox" />
2005 <span data-i18n="Send inline images">Send inline images</span>2005 <span data-i18n="Send inline images">Send inline images</span>
@@ -2015,7 +2015,7 @@
2015 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>2015 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
2016 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>2016 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
2017 </div>2017 </div>
2018 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai">2018 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub">
2019 <div class="flex-container oneline-dropdown">2019 <div class="flex-container oneline-dropdown">
2020 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">2020 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
2021 Inline Image Quality2021 Inline Image Quality
@@ -2077,7 +2077,7 @@
2077 </span>2077 </span>
2078 </div>2078 </div>
2079 </div>2079 </div>
2080 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi">2080 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub">
2081 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">2081 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
2082 <input id="openai_show_thoughts" type="checkbox" />2082 <input id="openai_show_thoughts" type="checkbox" />
2083 <span data-i18n="Request model reasoning">Request model reasoning</span>2083 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2091,7 +2091,7 @@
2091 </span>2091 </span>
2092 </div>2092 </div>
2093 </div>2093 </div>
2094 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi">2094 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub">
2095 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;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.">2095 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;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.">
2096 <label for="openai_reasoning_effort">2096 <label for="openai_reasoning_effort">
2097 <span data-i18n="Reasoning Effort">Reasoning Effort</span>2097 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2105,7 +2105,7 @@
2105 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>2105 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>
2106 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>2106 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>
2107 </select>2107 </select>
2108 <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.">2108 <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.">
2109 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.2109 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.
2110 </div>2110 </div>
2111 <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.">2111 <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 @@
2807 <!-- Temporarily disabled. -->2807 <!-- Temporarily disabled. -->
2808 <!-- <option value="cometapi">CometAPI</option> -->2808 <!-- <option value="cometapi">CometAPI</option> -->
2809 <option value="deepseek">DeepSeek</option>2809 <option value="deepseek">DeepSeek</option>
2810 <option value="electronhub">Electron Hub</option>
2810 <option value="fireworks">Fireworks AI</option>2811 <option value="fireworks">Fireworks AI</option>
2811 <option value="groq">Groq</option>2812 <option value="groq">Groq</option>
2812 <option value="makersuite">Google AI Studio</option>2813 <option value="makersuite">Google AI Studio</option>
@@ -3469,6 +3470,20 @@
3469 <option value="mistral-saba-24b">mistral-saba-24b</option>3470 <option value="mistral-saba-24b">mistral-saba-24b</option>
3470 </select>3471 </select>
3471 </div>3472 </div>
3473 <div id="electronhub_form" data-source="electronhub">
3474 <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4>
3475 <div class="flex-container">
3476 <input id="api_key_electronhub" name="api_key_electronhub" class="text_pole flex1" value="" type="text" autocomplete="off">
3477 <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>
3478 </div>
3479 <div data-for="api_key_electronhub" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3480 For privacy reasons, your API key will be hidden after you click 'Connect'.
3481 </div>
3482 <h4 data-i18n="Electron Hub Model">Electron Hub Model</h4>
3483 <select id="model_electronhub_select">
3484 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3485 </select>
3486 </div>
3472 <div id="nanogpt_form" data-source="nanogpt">3487 <div id="nanogpt_form" data-source="nanogpt">
3473 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>3488 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>
3474 <div class="flex-container">3489 <div class="flex-container">
public/locales/ar-sa.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "وضع علامة",317 "flag": "وضع علامة",
318 "API key (optional)": "مفتاح API (اختياري)",318 "API key (optional)": "مفتاح API (اختياري)",
319 "Server url": "رابط الخادم",319 "Server url": "رابط الخادم",
320 "Electron Hub API Key": "مفتاح API لـ Electron Hub",
321 "Electron Hub Model": "نموذج Electron Hub",
320 "Example: http://127.0.0.1:5000": "مثال: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "مثال: http://127.0.0.1:5000",
321 "Custom model (optional)": "نموذج مخصص (اختياري)",323 "Custom model (optional)": "نموذج مخصص (اختياري)",
322 "vllm-project/vllm": "vllm-project/vllm (وضع غلاف OpenAI API)",324 "vllm-project/vllm": "vllm-project/vllm (وضع غلاف OpenAI API)",
public/locales/de-de.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "Flagge",317 "flag": "Flagge",
318 "API key (optional)": "API-Schlüssel (optional)",318 "API key (optional)": "API-Schlüssel (optional)",
319 "Server url": "Server-URL",319 "Server url": "Server-URL",
320 "Electron Hub API Key": "Electron Hub API-Schlüssel",
321 "Electron Hub Model": "Electron Hub-Modell",
320 "Example: http://127.0.0.1:5000": "Beispiel: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "Beispiel: http://127.0.0.1:5000",
321 "Custom model (optional)": "Benutzerdefiniertes Modell (optional)",323 "Custom model (optional)": "Benutzerdefiniertes Modell (optional)",
322 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-Wrappermodus)",324 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-Wrappermodus)",
public/locales/es-es.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "bandera",317 "flag": "bandera",
318 "API key (optional)": "Clave API (opcional)",318 "API key (optional)": "Clave API (opcional)",
319 "Server url": "URL del servidor",319 "Server url": "URL del servidor",
320 "Electron Hub API Key": "Clave API de Electron Hub",
321 "Electron Hub Model": "Modelo de Electron Hub",
320 "Example: http://127.0.0.1:5000": "Ejemplo: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "Ejemplo: http://127.0.0.1:5000",
321 "Custom model (optional)": "Modelo personalizado (opcional)",323 "Custom model (optional)": "Modelo personalizado (opcional)",
322 "vllm-project/vllm": "vllm-project/vllm (modo contenedor de API OpenAI)",324 "vllm-project/vllm": "vllm-project/vllm (modo contenedor de API OpenAI)",
public/locales/fr-fr.json+2 -0
@@ -1411,6 +1411,8 @@
1411 "Do not proceed if you do not agree to this!": "Ne continuez pas si vous n'êtes pas d'accord avec cela !",1411 "Do not proceed if you do not agree to this!": "Ne continuez pas si vous n'êtes pas d'accord avec cela !",
1412 "Claude API Key": "Clé API Claude",1412 "Claude API Key": "Clé API Claude",
1413 "Allow fallback models": "Autoriser les modèles de secours",1413 "Allow fallback models": "Autoriser les modèles de secours",
1414 "Electron Hub API Key": "Clé API Electron Hub",
1415 "Electron Hub Model": "Modèle Electron Hub",
1414 "NanoGPT API Key": "Clé API NanoGPT",1416 "NanoGPT API Key": "Clé API NanoGPT",
1415 "NanoGPT Model": "Modèle NanoGPT",1417 "NanoGPT Model": "Modèle NanoGPT",
1416 "DeepSeek API Key": "Clé API DeepSeek",1418 "DeepSeek API Key": "Clé API DeepSeek",
public/locales/it-it.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "bandiera",317 "flag": "bandiera",
318 "API key (optional)": "Chiave API (opzionale)",318 "API key (optional)": "Chiave API (opzionale)",
319 "Server url": "URL del server",319 "Server url": "URL del server",
320 "Electron Hub API Key": "Chiave API di Electron Hub",
321 "Electron Hub Model": "Modello di Electron Hub",
320 "Example: http://127.0.0.1:5000": "Esempio: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "Esempio: http://127.0.0.1:5000",
321 "Custom model (optional)": "Modello personalizzato (opzionale)",323 "Custom model (optional)": "Modello personalizzato (opzionale)",
322 "vllm-project/vllm": "vllm-project/vllm (modalità wrapper API OpenAI)",324 "vllm-project/vllm": "vllm-project/vllm (modalità wrapper API OpenAI)",
public/locales/ja-jp.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "フラグ",317 "flag": "フラグ",
318 "API key (optional)": "APIキー(オプション)",318 "API key (optional)": "APIキー(オプション)",
319 "Server url": "サーバーURL",319 "Server url": "サーバーURL",
320 "Electron Hub API Key": "Electron Hub API キー",
321 "Electron Hub Model": "Electron Hub モデル",
320 "Example: http://127.0.0.1:5000": "例: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "例: http://127.0.0.1:5000",
321 "Custom model (optional)": "カスタムモデル(オプション)",323 "Custom model (optional)": "カスタムモデル(オプション)",
322 "vllm-project/vllm": "vllm-project/vllm (OpenAI API ラッパーモード)",324 "vllm-project/vllm": "vllm-project/vllm (OpenAI API ラッパーモード)",
public/locales/ko-kr.json+2 -0
@@ -319,6 +319,8 @@
319 "flag": "깃발",319 "flag": "깃발",
320 "API key (optional)": "API 키 (선택 사항)",320 "API key (optional)": "API 키 (선택 사항)",
321 "Server url": "서버 URL",321 "Server url": "서버 URL",
322 "Electron Hub API Key": "Electron Hub API 키",
323 "Electron Hub Model": "Electron Hub 모델",
322 "Example: http://127.0.0.1:5000": "예시: http://127.0.0.1:5000",324 "Example: http://127.0.0.1:5000": "예시: http://127.0.0.1:5000",
323 "Custom model (optional)": "사용자 정의 모델 (선택 사항)",325 "Custom model (optional)": "사용자 정의 모델 (선택 사항)",
324 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 래퍼 모드)",326 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 래퍼 모드)",
public/locales/pt-pt.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "bandeira",317 "flag": "bandeira",
318 "API key (optional)": "Chave da API (opcional)",318 "API key (optional)": "Chave da API (opcional)",
319 "Server url": "URL do servidor",319 "Server url": "URL do servidor",
320 "Electron Hub API Key": "Chave API Electron Hub",
321 "Electron Hub Model": "Modelo Electron Hub",
320 "Example: http://127.0.0.1:5000": "Exemplo: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "Exemplo: http://127.0.0.1:5000",
321 "Custom model (optional)": "Modelo personalizado (opcional)",323 "Custom model (optional)": "Modelo personalizado (opcional)",
322 "vllm-project/vllm": "vllm-project/vllm (modo wrapper da API OpenAI)",324 "vllm-project/vllm": "vllm-project/vllm (modo wrapper da API OpenAI)",
public/locales/ru-ru.json+2 -0
@@ -1994,6 +1994,8 @@
1994 "Click on the setting name to omit it from the profile.": "Нажмите на название настройки, чтобы исключить её из профиля",1994 "Click on the setting name to omit it from the profile.": "Нажмите на название настройки, чтобы исключить её из профиля",
1995 "Included settings:": "Сохранённые параметры:",1995 "Included settings:": "Сохранённые параметры:",
1996 "Server URL": "Адрес сервера",1996 "Server URL": "Адрес сервера",
1997 "Electron Hub API Key": "Ключ от API Electron Hub",
1998 "Electron Hub Model": "Модель Electron Hub",
1997 "NanoGPT API Key": "Ключ от API NanoGPT",1999 "NanoGPT API Key": "Ключ от API NanoGPT",
1998 "NanoGPT Model": "Модель NanoGPT",2000 "NanoGPT Model": "Модель NanoGPT",
1999 "Use extension settings": "Использовать настройки из расширения",2001 "Use extension settings": "Использовать настройки из расширения",
public/locales/uk-ua.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "прапорцем",317 "flag": "прапорцем",
318 "API key (optional)": "Ключ API (необов'язково)",318 "API key (optional)": "Ключ API (необов'язково)",
319 "Server url": "URL-адреса сервера",319 "Server url": "URL-адреса сервера",
320 "Electron Hub API Key": "Ключ API для Electron Hub",
321 "Electron Hub Model": "Модель Electron Hub",
320 "Example: http://127.0.0.1:5000": "Приклад: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "Приклад: http://127.0.0.1:5000",
321 "Custom model (optional)": "Власна модель (необов'язково)",323 "Custom model (optional)": "Власна модель (необов'язково)",
322 "vllm-project/vllm": "vllm-project/vllm (режим оболонки OpenAI API)",324 "vllm-project/vllm": "vllm-project/vllm (режим оболонки OpenAI API)",
public/locales/vi-vn.json+2 -0
@@ -317,6 +317,8 @@
317 "flag": "cờ",317 "flag": "cờ",
318 "API key (optional)": "Key API (tùy chọn)",318 "API key (optional)": "Key API (tùy chọn)",
319 "Server url": "URL máy chủ",319 "Server url": "URL máy chủ",
320 "Electron Hub API Key": "Key API Electron Hub",
321 "Electron Hub Model": "Model Electron Hub",
320 "Example: http://127.0.0.1:5000": "Ví dụ: http://127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "Ví dụ: http://127.0.0.1:5000",
321 "Custom model (optional)": "Model tùy chỉnh (tùy chọn)",323 "Custom model (optional)": "Model tùy chỉnh (tùy chọn)",
322 "vllm-project/vllm": "vllm-project/vllm (Chế độ trình bao bọc API OpenAI)",324 "vllm-project/vllm": "vllm-project/vllm (Chế độ trình bao bọc API OpenAI)",
public/locales/zh-cn.json+2 -0
@@ -481,6 +481,8 @@
481 "MistralAI Model": "MistralAI 模型",481 "MistralAI Model": "MistralAI 模型",
482 "Groq API Key": "Groq API 密钥",482 "Groq API Key": "Groq API 密钥",
483 "Groq Model": "Groq 模型",483 "Groq Model": "Groq 模型",
484 "Electron Hub API Key": "Electron Hub API 密钥",
485 "Electron Hub Model": "Electron Hub 模型",
484 "NanoGPT API Key": "NanoGPT API 密钥",486 "NanoGPT API Key": "NanoGPT API 密钥",
485 "NanoGPT Model": "NanoGPT 模型",487 "NanoGPT Model": "NanoGPT 模型",
486 "DeepSeek API Key": "DeepSeek API 密钥",488 "DeepSeek API Key": "DeepSeek API 密钥",
public/locales/zh-tw.json+2 -0
@@ -1791,6 +1791,8 @@
1791 "Derive context size from backend": "從後端推導上下文大小",1791 "Derive context size from backend": "從後端推導上下文大小",
1792 "Using a proxy that you're not running yourself is a risk to your data privacy.": "使用非自行管理的代理服務可能導致您的資料隱私外洩。",1792 "Using a proxy that you're not running yourself is a risk to your data privacy.": "使用非自行管理的代理服務可能導致您的資料隱私外洩。",
1793 "Claude API Key": "Claude API 金鑰",1793 "Claude API Key": "Claude API 金鑰",
1794 "Electron Hub API Key": "Electron Hub API 金鑰",
1795 "Electron Hub Model": "Electron Hub 模型",
1794 "NanoGPT API Key": "NanoGPT API 金鑰",1796 "NanoGPT API Key": "NanoGPT API 金鑰",
1795 "NanoGPT Model": "NanoGPT 模型",1797 "NanoGPT Model": "NanoGPT 模型",
1796 "context_derived": "若可能,根據模型後設資料推導。",1798 "context_derived": "若可能,根據模型後設資料推導。",
public/script.js+1 -0
@@ -5369,6 +5369,7 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
5369 case chat_completion_sources.CUSTOM:5369 case chat_completion_sources.CUSTOM:
5370 case chat_completion_sources.COHERE:5370 case chat_completion_sources.COHERE:
5371 case chat_completion_sources.XAI:5371 case chat_completion_sources.XAI:
5372 case chat_completion_sources.ELECTRONHUB:
5372 default:5373 default:
5373 result = tryParse(text);5374 result = tryParse(text);
5374 break;5375 break;
public/scripts/RossAscends-mods.js+1 -0
@@ -402,6 +402,7 @@ function RA_autoconnect(PrevApi) {
402 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)402 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
403 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)403 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
404 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)404 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
405 || (secret_state[SECRET_KEYS.ELECTRONHUB] && oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB)
405 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)406 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
406 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)407 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
407 || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)408 || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
public/scripts/extensions/caption/index.js+2 -0
@@ -440,6 +440,7 @@ jQuery(async function () {
440 'aimlapi': SECRET_KEYS.AIMLAPI,440 'aimlapi': SECRET_KEYS.AIMLAPI,
441 'moonshot': SECRET_KEYS.MOONSHOT,441 'moonshot': SECRET_KEYS.MOONSHOT,
442 'nanogpt': SECRET_KEYS.NANOGPT,442 'nanogpt': SECRET_KEYS.NANOGPT,
443 'electronhub': SECRET_KEYS.ELECTRONHUB,
443 };444 };
444445
445 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {446 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
@@ -547,6 +548,7 @@ jQuery(async function () {
547 await processEndpoint('aimlapi', '/api/backends/chat-completions/multimodal-models/aimlapi');548 await processEndpoint('aimlapi', '/api/backends/chat-completions/multimodal-models/aimlapi');
548 await processEndpoint('pollinations', '/api/backends/chat-completions/multimodal-models/pollinations');549 await processEndpoint('pollinations', '/api/backends/chat-completions/multimodal-models/pollinations');
549 await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');550 await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');
551 await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub');
550 }552 }
551553
552 await addSettings();554 await addSettings();
public/scripts/extensions/caption/settings.html+1 -0
@@ -21,6 +21,7 @@
21 <option value="anthropic">Anthropic</option>21 <option value="anthropic">Anthropic</option>
22 <option value="cohere">Cohere</option>22 <option value="cohere">Cohere</option>
23 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>23 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
24 <option value="electronhub">Electron Hub</option>
24 <option value="google">Google AI Studio</option>25 <option value="google">Google AI Studio</option>
25 <option value="vertexai">Google Vertex AI</option>26 <option value="vertexai">Google Vertex AI</option>
26 <option value="groq">Groq</option>27 <option value="groq">Groq</option>
public/scripts/extensions/shared.js+4 -0
@@ -251,6 +251,10 @@ function throwIfInvalidModel(useReverseProxy) {
251 if (multimodalApi === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT]) {251 if (multimodalApi === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT]) {
252 throw new Error('NanoGPT API key is not set.');252 throw new Error('NanoGPT API key is not set.');
253 }253 }
254
255 if (multimodalApi === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB]) {
256 throw new Error('Electron Hub API key is not set.');
257 }
254}258}
255259
256/**260/**
public/scripts/extensions/stable-diffusion/index.js+116 -0
@@ -82,6 +82,7 @@ const sources = {
82 pollinations: 'pollinations',82 pollinations: 'pollinations',
83 stability: 'stability',83 stability: 'stability',
84 huggingface: 'huggingface',84 huggingface: 'huggingface',
85 electronhub: 'electronhub',
85 nanogpt: 'nanogpt',86 nanogpt: 'nanogpt',
86 bfl: 'bfl',87 bfl: 'bfl',
87 falai: 'falai',88 falai: 'falai',
@@ -1289,6 +1290,7 @@ async function onModelChange() {
1289 sources.pollinations,1290 sources.pollinations,
1290 sources.stability,1291 sources.stability,
1291 sources.huggingface,1292 sources.huggingface,
1293 sources.electronhub,
1292 sources.nanogpt,1294 sources.nanogpt,
1293 sources.bfl,1295 sources.bfl,
1294 sources.falai,1296 sources.falai,
@@ -1506,6 +1508,9 @@ async function loadSamplers() {
1506 case sources.huggingface:1508 case sources.huggingface:
1507 samplers = ['N/A'];1509 samplers = ['N/A'];
1508 break;1510 break;
1511 case sources.electronhub:
1512 samplers = ['N/A'];
1513 break;
1509 case sources.nanogpt:1514 case sources.nanogpt:
1510 samplers = ['N/A'];1515 samplers = ['N/A'];
1511 break;1516 break;
@@ -1702,6 +1707,9 @@ async function loadModels() {
1702 case sources.huggingface:1707 case sources.huggingface:
1703 models = [{ value: '', text: '<Enter Model ID above>' }];1708 models = [{ value: '', text: '<Enter Model ID above>' }];
1704 break;1709 break;
1710 case sources.electronhub:
1711 models = await loadElectronHubModels();
1712 break;
1705 case sources.nanogpt:1713 case sources.nanogpt:
1706 models = await loadNanoGPTModels();1714 models = await loadNanoGPTModels();
1707 break;1715 break;
@@ -1806,6 +1814,24 @@ async function loadTogetherAIModels() {
1806 return [];1814 return [];
1807}1815}
18081816
1817async function loadElectronHubModels() {
1818 if (!secret_state[SECRET_KEYS.ELECTRONHUB]) {
1819 console.debug('Electron Hub API key is not set.');
1820 return [];
1821 }
1822
1823 const result = await fetch('/api/sd/electronhub/models', {
1824 method: 'POST',
1825 headers: getRequestHeaders(),
1826 });
1827
1828 if (result.ok) {
1829 return await result.json();
1830 }
1831
1832 return [];
1833}
1834
1809async function loadNanoGPTModels() {1835async function loadNanoGPTModels() {
1810 if (!secret_state[SECRET_KEYS.NANOGPT]) {1836 if (!secret_state[SECRET_KEYS.NANOGPT]) {
1811 console.debug('NanoGPT API key is not set.');1837 console.debug('NanoGPT API key is not set.');
@@ -2131,6 +2157,9 @@ async function loadSchedulers() {
2131 case sources.huggingface:2157 case sources.huggingface:
2132 schedulers = ['N/A'];2158 schedulers = ['N/A'];
2133 break;2159 break;
2160 case sources.electronhub:
2161 schedulers = ['N/A'];
2162 break;
2134 case sources.nanogpt:2163 case sources.nanogpt:
2135 schedulers = ['N/A'];2164 schedulers = ['N/A'];
2136 break;2165 break;
@@ -2228,6 +2257,9 @@ async function loadVaes() {
2228 case sources.huggingface:2257 case sources.huggingface:
2229 vaes = ['N/A'];2258 vaes = ['N/A'];
2230 break;2259 break;
2260 case sources.electronhub:
2261 vaes = ['N/A'];
2262 break;
2231 case sources.nanogpt:2263 case sources.nanogpt:
2232 vaes = ['N/A'];2264 vaes = ['N/A'];
2233 break;2265 break;
@@ -2811,6 +2843,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2811 case sources.huggingface:2843 case sources.huggingface:
2812 result = await generateHuggingFaceImage(prefixedPrompt, signal);2844 result = await generateHuggingFaceImage(prefixedPrompt, signal);
2813 break;2845 break;
2846 case sources.electronhub:
2847 result = await generateElectronHubImage(prefixedPrompt, signal);
2848 break;
2814 case sources.nanogpt:2849 case sources.nanogpt:
2815 result = await generateNanoGPTImage(prefixedPrompt, negativePrompt, signal);2850 result = await generateNanoGPTImage(prefixedPrompt, negativePrompt, signal);
2816 break;2851 break;
@@ -3014,6 +3049,56 @@ function getClosestAspectRatio(width, height, source) {
3014}3049}
30153050
3016/**3051/**
3052 * Get closest size for Electron Hub
3053 * @param {number} width - The width of the image
3054 * @param {number} height - The height of the image
3055 * @returns {Promise<string>} - The closest size
3056 */
3057async function getClosestSize(width, height) {
3058 const response = await fetch('/api/sd/electronhub/sizes', {
3059 method: 'POST',
3060 headers: getRequestHeaders(),
3061 body: JSON.stringify({
3062 model: extension_settings.sd.model,
3063 }),
3064 });
3065 if (!response.ok) {
3066 const text = await response.text();
3067 throw new Error(text);
3068 }
3069 const result = await response.json();
3070 const sizesData = result.sizes;
3071
3072 const closestSize = sizesData.reduce((closest, size) => {
3073 if (!size || typeof size !== 'string') {
3074 return closest;
3075 }
3076 const sizeParts = size.split('x');
3077 if (sizeParts.length !== 2) {
3078 return closest;
3079 }
3080
3081 const sizeWidth = Number(sizeParts[0]);
3082 const sizeHeight = Number(sizeParts[1]);
3083 const targetWidth = Number(width);
3084 const targetHeight = Number(height);
3085
3086 if (isNaN(sizeWidth) || isNaN(sizeHeight) || isNaN(targetWidth) || isNaN(targetHeight)) {
3087 return closest;
3088 }
3089
3090 const sizeArea = sizeWidth * sizeHeight;
3091 const targetArea = targetWidth * targetHeight;
3092 const diff = Math.abs(sizeArea - targetArea);
3093
3094 return diff < closest.diff ? { size, diff } : closest;
3095 }, { size: null, diff: Infinity });
3096
3097 const size = closestSize.size;
3098 return size;
3099}
3100
3101/**
3017 * Generates an image using Stability AI.3102 * Generates an image using Stability AI.
3018 * @param {string} prompt - The main instruction used to guide the image generation.3103 * @param {string} prompt - The main instruction used to guide the image generation.
3019 * @param {string} negativePrompt - The instruction used to restrict the image generation.3104 * @param {string} negativePrompt - The instruction used to restrict the image generation.
@@ -3565,6 +3650,35 @@ async function generateHuggingFaceImage(prompt, signal) {
3565}3650}
35663651
3567/**3652/**
3653 * Generates an image using the Electron Hub API.
3654 * @param {string} prompt - The main instruction used to guide the image generation.
3655 * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
3656 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
3657 */
3658async function generateElectronHubImage(prompt, signal) {
3659 const size = await getClosestSize(extension_settings.sd.width, extension_settings.sd.height);
3660
3661 const result = await fetch('/api/sd/electronhub/generate', {
3662 method: 'POST',
3663 headers: getRequestHeaders(),
3664 signal: signal,
3665 body: JSON.stringify({
3666 model: extension_settings.sd.model,
3667 prompt: prompt,
3668 size: size,
3669 }),
3670 });
3671
3672 if (result.ok) {
3673 const data = await result.json();
3674 return { format: 'jpg', data: data.image };
3675 } else {
3676 const text = await result.text();
3677 throw new Error(text);
3678 }
3679}
3680
3681/**
3568 * Generates an image using the NanoGPT API.3682 * Generates an image using the NanoGPT API.
3569 * @param {string} prompt - The main instruction used to guide the image generation.3683 * @param {string} prompt - The main instruction used to guide the image generation.
3570 * @param {string} negativePrompt - The instruction used to restrict the image generation.3684 * @param {string} negativePrompt - The instruction used to restrict the image generation.
@@ -4014,6 +4128,8 @@ function isValidState() {
4014 return secret_state[SECRET_KEYS.STABILITY];4128 return secret_state[SECRET_KEYS.STABILITY];
4015 case sources.huggingface:4129 case sources.huggingface:
4016 return secret_state[SECRET_KEYS.HUGGINGFACE];4130 return secret_state[SECRET_KEYS.HUGGINGFACE];
4131 case sources.electronhub:
4132 return secret_state[SECRET_KEYS.ELECTRONHUB];
4017 case sources.nanogpt:4133 case sources.nanogpt:
4018 return secret_state[SECRET_KEYS.NANOGPT];4134 return secret_state[SECRET_KEYS.NANOGPT];
4019 case sources.bfl:4135 case sources.bfl:
public/scripts/extensions/stable-diffusion/settings.html+4 -0
@@ -41,6 +41,7 @@
41 <option value="bfl">BFL (Black Forest Labs)</option>41 <option value="bfl">BFL (Black Forest Labs)</option>
42 <option value="comfy">ComfyUI</option>42 <option value="comfy">ComfyUI</option>
43 <option value="drawthings">DrawThings HTTP API</option>43 <option value="drawthings">DrawThings HTTP API</option>
44 <option value="electronhub">Electron Hub</option>
44 <option value="extras">Extras API (deprecated)</option>45 <option value="extras">Extras API (deprecated)</option>
45 <option value="falai">FAL.AI</option>46 <option value="falai">FAL.AI</option>
46 <option value="google">Google AI</option>47 <option value="google">Google AI</option>
@@ -93,6 +94,9 @@
93 <label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label>94 <label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label>
94 <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="" />95 <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="" />
95 </div>96 </div>
97 <div data-sd-source="electronhub">
98 <i>Hint: Save an API key in the Electron Hub (Chat Completion) API settings to use it here.</i>
99 </div>
96 <div data-sd-source="nanogpt">100 <div data-sd-source="nanogpt">
97 <i>Hint: Save an API key in the NanoGPT (Chat Completion) API settings to use it here.</i>101 <i>Hint: Save an API key in the NanoGPT (Chat Completion) API settings to use it here.</i>
98 </div>102 </div>
public/scripts/openai.js+119 -1
@@ -179,6 +179,7 @@ export const chat_completion_sources = {
179 COHERE: 'cohere',179 COHERE: 'cohere',
180 PERPLEXITY: 'perplexity',180 PERPLEXITY: 'perplexity',
181 GROQ: 'groq',181 GROQ: 'groq',
182 ELECTRONHUB: 'electronhub',
182 NANOGPT: 'nanogpt',183 NANOGPT: 'nanogpt',
183 DEEPSEEK: 'deepseek',184 DEEPSEEK: 'deepseek',
184 AIMLAPI: 'aimlapi',185 AIMLAPI: 'aimlapi',
@@ -271,6 +272,7 @@ export const settingsToUpdate = {
271 cohere_model: ['#model_cohere_select', 'cohere_model', false, true],272 cohere_model: ['#model_cohere_select', 'cohere_model', false, true],
272 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],273 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],
273 groq_model: ['#model_groq_select', 'groq_model', false, true],274 groq_model: ['#model_groq_select', 'groq_model', false, true],
275 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],
274 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],276 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],
275 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],277 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],
276 aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],278 aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],
@@ -369,6 +371,7 @@ const default_settings = {
369 cohere_model: 'command-r-plus',371 cohere_model: 'command-r-plus',
370 perplexity_model: 'sonar-pro',372 perplexity_model: 'sonar-pro',
371 groq_model: 'llama-3.3-70b-versatile',373 groq_model: 'llama-3.3-70b-versatile',
374 electronhub_model: 'gpt-4o-mini',
372 nanogpt_model: 'gpt-4o-mini',375 nanogpt_model: 'gpt-4o-mini',
373 deepseek_model: 'deepseek-chat',376 deepseek_model: 'deepseek-chat',
374 aimlapi_model: 'gpt-4o-mini-2024-07-18',377 aimlapi_model: 'gpt-4o-mini-2024-07-18',
@@ -458,6 +461,7 @@ const oai_settings = {
458 cohere_model: 'command-r-plus',461 cohere_model: 'command-r-plus',
459 perplexity_model: 'sonar-pro',462 perplexity_model: 'sonar-pro',
460 groq_model: 'llama-3.1-70b-versatile',463 groq_model: 'llama-3.1-70b-versatile',
464 electronhub_model: 'gpt-4o-mini',
461 nanogpt_model: 'gpt-4o-mini',465 nanogpt_model: 'gpt-4o-mini',
462 deepseek_model: 'deepseek-chat',466 deepseek_model: 'deepseek-chat',
463 aimlapi_model: 'gpt-4-turbo',467 aimlapi_model: 'gpt-4-turbo',
@@ -1542,6 +1546,11 @@ export function tryParseStreamingError(response, decoded, { quiet = false } = {}
1542 !quiet && toastr.error(data.message, 'Chat Completion API');1546 !quiet && toastr.error(data.message, 'Chat Completion API');
1543 throw new Error(data);1547 throw new Error(data);
1544 }1548 }
1549
1550 if (data.detail) {
1551 !quiet && toastr.error(data.detail?.error?.message || response.statusText, 'Chat Completion API');
1552 throw new Error(data);
1553 }
1545 }1554 }
1546 catch {1555 catch {
1547 // No JSON. Do nothing.1556 // No JSON. Do nothing.
@@ -1614,6 +1623,8 @@ export function getChatCompletionModel(source = null) {
1614 return oai_settings.perplexity_model;1623 return oai_settings.perplexity_model;
1615 case chat_completion_sources.GROQ:1624 case chat_completion_sources.GROQ:
1616 return oai_settings.groq_model;1625 return oai_settings.groq_model;
1626 case chat_completion_sources.ELECTRONHUB:
1627 return oai_settings.electronhub_model;
1617 case chat_completion_sources.NANOGPT:1628 case chat_completion_sources.NANOGPT:
1618 return oai_settings.nanogpt_model;1629 return oai_settings.nanogpt_model;
1619 case chat_completion_sources.DEEPSEEK:1630 case chat_completion_sources.DEEPSEEK:
@@ -1774,6 +1785,26 @@ function saveModelList(data) {
1774 }1785 }
1775 }1786 }
17761787
1788 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
1789 $('#model_electronhub_select').empty();
1790 model_list.forEach((model) => {
1791 if (model?.endpoints?.includes('/v1/chat/completions')) {
1792 $('#model_electronhub_select').append(
1793 $('<option>', {
1794 value: model.id,
1795 text: model.name,
1796 }));
1797 }
1798 });
1799
1800 const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model);
1801 if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) {
1802 oai_settings.electronhub_model = model_list[0].id;
1803 }
1804
1805 $('#model_electronhub_select').val(oai_settings.electronhub_model).trigger('change');
1806 }
1807
1777 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {1808 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
1778 $('#model_nanogpt_select').empty();1809 $('#model_nanogpt_select').empty();
1779 model_list.forEach((model) => {1810 model_list.forEach((model) => {
@@ -2044,12 +2075,14 @@ function getReasoningEffort() {
2044 chat_completion_sources.POLLINATIONS,2075 chat_completion_sources.POLLINATIONS,
2045 chat_completion_sources.PERPLEXITY,2076 chat_completion_sources.PERPLEXITY,
2046 chat_completion_sources.COMETAPI,2077 chat_completion_sources.COMETAPI,
2078 chat_completion_sources.ELECTRONHUB,
2047 ];2079 ];
20482080
2049 if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {2081 if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {
2050 return oai_settings.reasoning_effort;2082 return oai_settings.reasoning_effort;
2051 }2083 }
20522084
2085 function resolveReasoningEffort() {
2053 switch (oai_settings.reasoning_effort) {2086 switch (oai_settings.reasoning_effort) {
2054 case reasoning_effort_types.auto:2087 case reasoning_effort_types.auto:
2055 return undefined;2088 return undefined;
@@ -2064,6 +2097,23 @@ function getReasoningEffort() {
2064 }2097 }
2065 }2098 }
20662099
2100 const reasoningEffort = resolveReasoningEffort();
2101
2102 // Check if the resolved effort supported by the model
2103 if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {
2104 if (Array.isArray(model_list) && reasoningEffort) {
2105 const currentModel = model_list.find(m => m.id === oai_settings.electronhub_model);
2106 const supportedEfforts = currentModel?.metadata?.supported_reasoning_efforts;
2107 if (Array.isArray(supportedEfforts) && supportedEfforts.includes(reasoningEffort)) {
2108 return reasoningEffort;
2109 }
2110 return undefined;
2111 }
2112 }
2113
2114 return reasoningEffort;
2115}
2116
2067/**2117/**
2068 * Send a chat completion request to backend2118 * Send a chat completion request to backend
2069 * @param {string} type (impersonate, quiet, continue, etc)2119 * @param {string} type (impersonate, quiet, continue, etc)
@@ -2100,6 +2150,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2100 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;2150 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
2101 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;2151 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
2102 const isAimlapi = oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI;2152 const isAimlapi = oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI;
2153 const isElectronHub = oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB;
2103 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;2154 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
2104 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;2155 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;
2105 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;2156 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;
@@ -2283,6 +2334,11 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2283 delete generate_data.max_tokens;2334 delete generate_data.max_tokens;
2284 }2335 }
22852336
2337 // https://docs.electronhub.ai/api-reference/chat/completions
2338 if (isElectronHub) {
2339 generate_data['top_k'] = Number(oai_settings.top_k_openai);
2340 }
2341
2286 const seedSupportedSources = [2342 const seedSupportedSources = [
2287 chat_completion_sources.OPENAI,2343 chat_completion_sources.OPENAI,
2288 chat_completion_sources.OPENROUTER,2344 chat_completion_sources.OPENROUTER,
@@ -2290,6 +2346,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2290 chat_completion_sources.CUSTOM,2346 chat_completion_sources.CUSTOM,
2291 chat_completion_sources.COHERE,2347 chat_completion_sources.COHERE,
2292 chat_completion_sources.GROQ,2348 chat_completion_sources.GROQ,
2349 chat_completion_sources.ELECTRONHUB,
2293 chat_completion_sources.NANOGPT,2350 chat_completion_sources.NANOGPT,
2294 chat_completion_sources.XAI,2351 chat_completion_sources.XAI,
2295 chat_completion_sources.POLLINATIONS,2352 chat_completion_sources.POLLINATIONS,
@@ -2462,7 +2519,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
2462 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');2519 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
2463 }2520 }
2464 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2521 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2465 } 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)) {2522 } 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)) {
2466 if (show_thoughts) {2523 if (show_thoughts) {
2467 state.reasoning +=2524 state.reasoning +=
2468 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??2525 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
@@ -3424,6 +3481,7 @@ function loadOpenAISettings(data, settings) {
3424 oai_settings.cohere_model = settings.cohere_model ?? default_settings.cohere_model;3481 oai_settings.cohere_model = settings.cohere_model ?? default_settings.cohere_model;
3425 oai_settings.perplexity_model = settings.perplexity_model ?? default_settings.perplexity_model;3482 oai_settings.perplexity_model = settings.perplexity_model ?? default_settings.perplexity_model;
3426 oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;3483 oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
3484 oai_settings.electronhub_model = settings.electronhub_model ?? default_settings.electronhub_model;
3427 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;3485 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
3428 oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;3486 oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
3429 oai_settings.aimlapi_model = settings.aimlapi_model ?? default_settings.aimlapi_model;3487 oai_settings.aimlapi_model = settings.aimlapi_model ?? default_settings.aimlapi_model;
@@ -3519,6 +3577,8 @@ function loadOpenAISettings(data, settings) {
3519 $(`#model_perplexity_select option[value="${oai_settings.perplexity_model}"`).prop('selected', true);3577 $(`#model_perplexity_select option[value="${oai_settings.perplexity_model}"`).prop('selected', true);
3520 $('#model_groq_select').val(oai_settings.groq_model);3578 $('#model_groq_select').val(oai_settings.groq_model);
3521 $(`#model_groq_select option[value="${oai_settings.groq_model}"`).prop('selected', true);3579 $(`#model_groq_select option[value="${oai_settings.groq_model}"`).prop('selected', true);
3580 $('#model_electronhub_select').val(oai_settings.electronhub_model);
3581 $(`#model_electronhub_select option[value="${oai_settings.electronhub_model}"`).prop('selected', true);
3522 $('#model_nanogpt_select').val(oai_settings.nanogpt_model);3582 $('#model_nanogpt_select').val(oai_settings.nanogpt_model);
3523 $(`#model_nanogpt_select option[value="${oai_settings.nanogpt_model}"`).prop('selected', true);3583 $(`#model_nanogpt_select option[value="${oai_settings.nanogpt_model}"`).prop('selected', true);
3524 $('#model_deepseek_select').val(oai_settings.deepseek_model);3584 $('#model_deepseek_select').val(oai_settings.deepseek_model);
@@ -3805,6 +3865,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
3805 xai_model: settings.xai_model,3865 xai_model: settings.xai_model,
3806 pollinations_model: settings.pollinations_model,3866 pollinations_model: settings.pollinations_model,
3807 aimlapi_model: settings.aimlapi_model,3867 aimlapi_model: settings.aimlapi_model,
3868 electronhub_model: settings.electronhub_model,
3808 moonshot_model: settings.moonshot_model,3869 moonshot_model: settings.moonshot_model,
3809 fireworks_model: settings.fireworks_model,3870 fireworks_model: settings.fireworks_model,
3810 cometapi_model: settings.cometapi_model,3871 cometapi_model: settings.cometapi_model,
@@ -4571,6 +4632,26 @@ function getFireworksMaxContext(model, isUnlocked) {
4571}4632}
45724633
4573/**4634/**
4635 * Get the maximum context size for the ElectronHub model
4636 * @param {string} model Model identifier
4637 * @param {boolean} isUnlocked Whether context limits are unlocked
4638 * @returns {number} Maximum context size in tokens
4639 */
4640function getElectronHubMaxContext(model, isUnlocked) {
4641 if (isUnlocked) {
4642 return unlocked_max;
4643 }
4644
4645 if (Array.isArray(model_list)) {
4646 const modelInfo = model_list.find(m => m.id === model);
4647 if (modelInfo?.tokens) {
4648 return modelInfo.tokens;
4649 }
4650 }
4651 return max_8k;
4652}
4653
4654/**
4574 * Get the maximum context size for the NanoGPT model4655 * Get the maximum context size for the NanoGPT model
4575 * @param {string} model Model identifier4656 * @param {string} model Model identifier
4576 * @param {boolean} isUnlocked Whether context limits are unlocked4657 * @param {boolean} isUnlocked Whether context limits are unlocked
@@ -4681,6 +4762,15 @@ async function onModelChange() {
4681 oai_settings.groq_model = value;4762 oai_settings.groq_model = value;
4682 }4763 }
46834764
4765 if ($(this).is('#model_electronhub_select')) {
4766 if (!value) {
4767 console.debug('Null ElectronHub model selected. Ignoring.');
4768 return;
4769 }
4770 console.log('ElectronHub model changed to', value);
4771 oai_settings.electronhub_model = value;
4772 }
4773
4684 if ($(this).is('#model_nanogpt_select')) {4774 if ($(this).is('#model_nanogpt_select')) {
4685 if (!value) {4775 if (!value) {
4686 console.debug('Null NanoGPT model selected. Ignoring.');4776 console.debug('Null NanoGPT model selected. Ignoring.');
@@ -4922,6 +5012,15 @@ async function onModelChange() {
4922 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');5012 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4923 }5013 }
49245014
5015 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
5016 const maxContext = getElectronHubMaxContext(oai_settings.electronhub_model, oai_settings.max_context_unlocked);
5017 $('#openai_max_context').attr('max', maxContext);
5018 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5019 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5020 oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
5021 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
5022 }
5023
4925 if (oai_settings.chat_completion_source === chat_completion_sources.NANOGPT) {5024 if (oai_settings.chat_completion_source === chat_completion_sources.NANOGPT) {
4926 const maxContext = getNanoGptMaxContext(oai_settings.nanogpt_model, oai_settings.max_context_unlocked);5025 const maxContext = getNanoGptMaxContext(oai_settings.nanogpt_model, oai_settings.max_context_unlocked);
4927 $('#openai_max_context').attr('max', maxContext);5026 $('#openai_max_context').attr('max', maxContext);
@@ -5216,6 +5315,19 @@ async function onConnectButtonClick(e) {
5216 }5315 }
5217 }5316 }
52185317
5318 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
5319 const api_key_electronhub = String($('#api_key_electronhub').val()).trim();
5320
5321 if (api_key_electronhub.length) {
5322 await writeSecret(SECRET_KEYS.ELECTRONHUB, api_key_electronhub);
5323 }
5324
5325 if (!secret_state[SECRET_KEYS.ELECTRONHUB]) {
5326 console.log('No secret key saved for Electron Hub');
5327 return;
5328 }
5329 }
5330
5219 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {5331 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
5220 const api_key_nanogpt = String($('#api_key_nanogpt').val()).trim();5332 const api_key_nanogpt = String($('#api_key_nanogpt').val()).trim();
52215333
@@ -5350,6 +5462,9 @@ function toggleChatCompletionForms() {
5350 else if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {5462 else if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {
5351 $('#model_groq_select').trigger('change');5463 $('#model_groq_select').trigger('change');
5352 }5464 }
5465 else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
5466 $('#model_electronhub_select').trigger('change');
5467 }
5353 else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {5468 else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
5354 $('#model_nanogpt_select').trigger('change');5469 $('#model_nanogpt_select').trigger('change');
5355 }5470 }
@@ -5518,6 +5633,8 @@ export function isImageInliningSupported() {
5518 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));5633 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
5519 case chat_completion_sources.AIMLAPI:5634 case chat_completion_sources.AIMLAPI:
5520 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision'));5635 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision'));
5636 case chat_completion_sources.ELECTRONHUB:
5637 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.electronhub_model)?.metadata?.vision);
5521 case chat_completion_sources.POLLINATIONS:5638 case chat_completion_sources.POLLINATIONS:
5522 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.pollinations_model)?.vision);5639 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.pollinations_model)?.vision);
5523 case chat_completion_sources.COMETAPI:5640 case chat_completion_sources.COMETAPI:
@@ -6326,6 +6443,7 @@ export function initOpenAI() {
6326 $('#model_cohere_select').on('change', onModelChange);6443 $('#model_cohere_select').on('change', onModelChange);
6327 $('#model_perplexity_select').on('change', onModelChange);6444 $('#model_perplexity_select').on('change', onModelChange);
6328 $('#model_groq_select').on('change', onModelChange);6445 $('#model_groq_select').on('change', onModelChange);
6446 $('#model_electronhub_select').on('change', onModelChange);
6329 $('#model_nanogpt_select').on('change', onModelChange);6447 $('#model_nanogpt_select').on('change', onModelChange);
6330 $('#model_deepseek_select').on('change', onModelChange);6448 $('#model_deepseek_select').on('change', onModelChange);
6331 $('#model_aimlapi_select').on('change', onModelChange);6449 $('#model_aimlapi_select').on('change', onModelChange);
public/scripts/secrets.js+3 -0
@@ -51,6 +51,7 @@ export const SECRET_KEYS = {
51 HUGGINGFACE: 'api_key_huggingface',51 HUGGINGFACE: 'api_key_huggingface',
52 STABILITY: 'api_key_stability',52 STABILITY: 'api_key_stability',
53 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',53 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
54 ELECTRONHUB: 'api_key_electronhub',
54 NANOGPT: 'api_key_nanogpt',55 NANOGPT: 'api_key_nanogpt',
55 TAVILY: 'api_key_tavily',56 TAVILY: 'api_key_tavily',
56 BFL: 'api_key_bfl',57 BFL: 'api_key_bfl',
@@ -95,6 +96,7 @@ const FRIENDLY_NAMES = {
95 [SECRET_KEYS.GROQ]: 'Groq',96 [SECRET_KEYS.GROQ]: 'Groq',
96 [SECRET_KEYS.FEATHERLESS]: 'Featherless',97 [SECRET_KEYS.FEATHERLESS]: 'Featherless',
97 [SECRET_KEYS.HUGGINGFACE]: 'HuggingFace',98 [SECRET_KEYS.HUGGINGFACE]: 'HuggingFace',
99 [SECRET_KEYS.ELECTRONHUB]: 'Electron Hub',
98 [SECRET_KEYS.NANOGPT]: 'NanoGPT',100 [SECRET_KEYS.NANOGPT]: 'NanoGPT',
99 [SECRET_KEYS.GENERIC]: 'Generic (OpenAI-compatible)',101 [SECRET_KEYS.GENERIC]: 'Generic (OpenAI-compatible)',
100 [SECRET_KEYS.DEEPSEEK]: 'DeepSeek',102 [SECRET_KEYS.DEEPSEEK]: 'DeepSeek',
@@ -148,6 +150,7 @@ const INPUT_MAP = {
148 [SECRET_KEYS.GROQ]: '#api_key_groq',150 [SECRET_KEYS.GROQ]: '#api_key_groq',
149 [SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',151 [SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
150 [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',152 [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
153 [SECRET_KEYS.ELECTRONHUB]: '#api_key_electronhub',
151 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',154 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
152 [SECRET_KEYS.GENERIC]: '#api_key_generic',155 [SECRET_KEYS.GENERIC]: '#api_key_generic',
153 [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',156 [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
public/scripts/slash-commands.js+1 -0
@@ -4841,6 +4841,7 @@ function getModelOptions(quiet) {
4841 { id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },4841 { id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },
4842 { id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },4842 { id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },
4843 { id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },4843 { id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
4844 { id: 'model_electronhub_select', api: 'openai', type: chat_completion_sources.ELECTRONHUB },
4844 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },4845 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
4845 { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },4846 { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
4846 { id: 'model_aimlapi_select', api: 'openai', type: chat_completion_sources.AIMLAPI },4847 { id: 'model_aimlapi_select', api: 'openai', type: chat_completion_sources.AIMLAPI },
public/scripts/tool-calling.js+8 -0
@@ -633,6 +633,13 @@ export class ToolManager {
633 }633 }
634 }634 }
635635
636 if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB && Array.isArray(model_list)) {
637 const currentModel = model_list.find(model => model.id === oai_settings.electronhub_model);
638 if (currentModel && currentModel.metadata?.function_call) {
639 return currentModel.metadata.function_call;
640 }
641 }
642
636 const supportedSources = [643 const supportedSources = [
637 chat_completion_sources.OPENAI,644 chat_completion_sources.OPENAI,
638 chat_completion_sources.CUSTOM,645 chat_completion_sources.CUSTOM,
@@ -651,6 +658,7 @@ export class ToolManager {
651 chat_completion_sources.MOONSHOT,658 chat_completion_sources.MOONSHOT,
652 chat_completion_sources.FIREWORKS,659 chat_completion_sources.FIREWORKS,
653 chat_completion_sources.COMETAPI,660 chat_completion_sources.COMETAPI,
661 chat_completion_sources.ELECTRONHUB,
654 ];662 ];
655 return supportedSources.includes(oai_settings.chat_completion_source);663 return supportedSources.includes(oai_settings.chat_completion_source);
656 }664 }
src/constants.js+1 -0
@@ -173,6 +173,7 @@ export const CHAT_COMPLETION_SOURCES = {
173 COHERE: 'cohere',173 COHERE: 'cohere',
174 PERPLEXITY: 'perplexity',174 PERPLEXITY: 'perplexity',
175 GROQ: 'groq',175 GROQ: 'groq',
176 ELECTRONHUB: 'electronhub',
176 NANOGPT: 'nanogpt',177 NANOGPT: 'nanogpt',
177 DEEPSEEK: 'deepseek',178 DEEPSEEK: 'deepseek',
178 AIMLAPI: 'aimlapi',179 AIMLAPI: 'aimlapi',
src/endpoints/backends/chat-completions.js+124 -0
@@ -61,6 +61,7 @@ const API_GROQ = 'https://api.groq.com/openai/v1';
61const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';61const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
62const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com';62const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com';
63const API_AI21 = 'https://api.ai21.com/studio/v1';63const API_AI21 = 'https://api.ai21.com/studio/v1';
64const API_ELECTRONHUB = 'https://api.electronhub.ai/v1';
64const API_NANOGPT = 'https://nano-gpt.com/api/v1';65const API_NANOGPT = 'https://nano-gpt.com/api/v1';
65const API_DEEPSEEK = 'https://api.deepseek.com/beta';66const API_DEEPSEEK = 'https://api.deepseek.com/beta';
66const API_XAI = 'https://api.x.ai/v1';67const API_XAI = 'https://api.x.ai/v1';
@@ -1193,6 +1194,106 @@ async function sendAimlapiRequest(request, response) {
1193 }1194 }
1194}1195}
11951196
1197/**
1198 * Sends a request to Electron Hub.
1199 * @param {express.Request} request Express request
1200 * @param {express.Response} response Express response
1201 */
1202async function sendElectronHubRequest(request, response) {
1203 const apiUrl = API_ELECTRONHUB;
1204 const apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
1205
1206 if (!apiKey) {
1207 console.warn('Electron Hub key is missing.');
1208 return response.status(400).send({ error: true });
1209 }
1210
1211 const controller = new AbortController();
1212 request.socket.removeAllListeners('close');
1213 request.socket.on('close', function () {
1214 controller.abort();
1215 });
1216
1217 try {
1218 let bodyParams = {};
1219
1220 if (request.body.enable_web_search) {
1221 bodyParams['web_search'] = true;
1222 }
1223
1224 if (Array.isArray(request.body.tools) && request.body.tools.length > 0) {
1225 bodyParams['tools'] = request.body.tools;
1226 bodyParams['tool_choice'] = request.body.tool_choice;
1227 }
1228
1229 if (request.body.reasoning_effort) {
1230 bodyParams['reasoning_effort'] = request.body.reasoning_effort;
1231 }
1232
1233 if (request.body.json_schema) {
1234 bodyParams['response_format'] = {
1235 type: 'json_schema',
1236 json_schema: {
1237 name: request.body.json_schema.name,
1238 description: request.body.json_schema.description,
1239 schema: request.body.json_schema.value,
1240 strict: request.body.json_schema.strict ?? true,
1241 },
1242 };
1243 }
1244
1245 const requestBody = {
1246 'messages': request.body.messages,
1247 'model': request.body.model,
1248 'temperature': request.body.temperature,
1249 'max_tokens': request.body.max_tokens,
1250 'stream': request.body.stream,
1251 'presence_penalty': request.body.presence_penalty,
1252 'frequency_penalty': request.body.frequency_penalty,
1253 'top_p': request.body.top_p,
1254 'top_k': request.body.top_k,
1255 'seed': request.body.seed,
1256 ...bodyParams,
1257 };
1258
1259 const config = {
1260 method: 'POST',
1261 headers: {
1262 'Content-Type': 'application/json',
1263 'Authorization': 'Bearer ' + apiKey,
1264 },
1265 body: JSON.stringify(requestBody),
1266 signal: controller.signal,
1267 };
1268
1269 console.debug('Electron Hub request:', requestBody);
1270
1271 const generateResponse = await fetch(apiUrl + '/chat/completions', config);
1272
1273 if (request.body.stream) {
1274 forwardFetchResponse(generateResponse, response);
1275 } else {
1276 if (!generateResponse.ok) {
1277 const errorText = await generateResponse.text();
1278 console.warn('Electron Hub returned error: ', errorText);
1279 const errorJson = tryParse(errorText) ?? { error: true };
1280 return response.status(500).send(errorJson);
1281 }
1282 const generateResponseJson = await generateResponse.json();
1283 console.debug('Electron Hub response:', generateResponseJson);
1284 return response.send(generateResponseJson);
1285 }
1286 }
1287 catch (error) {
1288 console.error('Error communicating with Electron Hub: ', error);
1289 if (!response.headersSent) {
1290 response.send({ error: true });
1291 } else {
1292 response.end();
1293 }
1294 }
1295}
1296
1196export const router = express.Router();1297export const router = express.Router();
11971298
1198router.post('/status', async function (request, statusResponse) {1299router.post('/status', async function (request, statusResponse) {
@@ -1225,6 +1326,10 @@ router.post('/status', async function (request, statusResponse) {
1225 apiUrl = API_COHERE_V1;1326 apiUrl = API_COHERE_V1;
1226 apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE);1327 apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE);
1227 headers = {};1328 headers = {};
1329 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ELECTRONHUB) {
1330 apiUrl = API_ELECTRONHUB;
1331 apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
1332 headers = {};
1228 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.NANOGPT) {1333 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.NANOGPT) {
1229 apiUrl = API_NANOGPT;1334 apiUrl = API_NANOGPT;
1230 apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);1335 apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
@@ -1490,6 +1595,7 @@ router.post('/generate', function (request, response) {
1490 case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);1595 case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);
1491 case CHAT_COMPLETION_SOURCES.AIMLAPI: return sendAimlapiRequest(request, response);1596 case CHAT_COMPLETION_SOURCES.AIMLAPI: return sendAimlapiRequest(request, response);
1492 case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response);1597 case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response);
1598 case CHAT_COMPLETION_SOURCES.ELECTRONHUB: return sendElectronHubRequest(request, response);
1493 }1599 }
14941600
1495 let apiUrl;1601 let apiUrl;
@@ -1927,4 +2033,22 @@ multimodalModels.post('/nanogpt', async (_req, res) => {
1927 }2033 }
1928});2034});
19292035
2036multimodalModels.post('/electronhub', async (_req, res) => {
2037 try {
2038 const response = await fetch('https://api.electronhub.ai/v1/models');
2039
2040 if (!response.ok) {
2041 return res.json([]);
2042 }
2043
2044 /** @type {any} */
2045 const data = await response.json();
2046 const multimodalModels = data.data.filter(m => m.metadata?.vision).map(m => m.id);
2047 return res.json(multimodalModels);
2048 } catch (error) {
2049 console.error(error);
2050 return res.sendStatus(500);
2051 }
2052});
2053
1930router.use('/multimodal-models', multimodalModels);2054router.use('/multimodal-models', multimodalModels);
src/endpoints/openai.js+8 -0
@@ -81,6 +81,10 @@ router.post('/caption-image', async (request, response) => {
81 key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);81 key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
82 }82 }
8383
84 if (request.body.api === 'electronhub') {
85 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
86 }
87
84 const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations'];88 const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations'];
85 if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) {89 if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) {
86 console.warn('No key found for API', request.body.api);90 console.warn('No key found for API', request.body.api);
@@ -169,6 +173,10 @@ router.post('/caption-image', async (request, response) => {
169 apiUrl = 'https://nano-gpt.com/api/v1/chat/completions';173 apiUrl = 'https://nano-gpt.com/api/v1/chat/completions';
170 }174 }
171175
176 if (request.body.api === 'electronhub') {
177 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';
178 }
179
172 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {180 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {
173 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;181 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
174 }182 }
src/endpoints/secrets.js+1 -0
@@ -45,6 +45,7 @@ export const SECRET_KEYS = {
45 STABILITY: 'api_key_stability',45 STABILITY: 'api_key_stability',
46 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',46 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
47 TAVILY: 'api_key_tavily',47 TAVILY: 'api_key_tavily',
48 ELECTRONHUB: 'api_key_electronhub',
48 NANOGPT: 'api_key_nanogpt',49 NANOGPT: 'api_key_nanogpt',
49 BFL: 'api_key_bfl',50 BFL: 'api_key_bfl',
50 FALAI: 'api_key_falai',51 FALAI: 'api_key_falai',
src/endpoints/stable-diffusion.js+113 -0
@@ -957,6 +957,118 @@ huggingface.post('/generate', async (request, response) => {
957 }957 }
958});958});
959959
960const electronhub = express.Router();
961
962electronhub.post('/models', async (request, response) => {
963 try {
964 const key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
965
966 if (!key) {
967 console.warn('Electron Hub key not found.');
968 return response.sendStatus(400);
969 }
970
971 const modelsResponse = await fetch('https://api.electronhub.ai/v1/models', {
972 method: 'GET',
973 headers: {
974 'Authorization': `Bearer ${key}`,
975 'Content-Type': 'application/json',
976 },
977 });
978
979 if (!modelsResponse.ok) {
980 console.warn('Electron Hub returned an error.');
981 return response.sendStatus(500);
982 }
983
984 /** @type {any} */
985 const data = await modelsResponse.json();
986 const models = data.data.filter(x => x.endpoints.includes('/v1/images/generations')).map(x => ({ value: x.id, text: x.name }));
987 return response.send(models);
988 } catch (error) {
989 console.error(error);
990 return response.sendStatus(500);
991 }
992});
993
994electronhub.post('/generate', async (request, response) => {
995 try {
996 const key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
997
998 if (!key) {
999 console.warn('Electron Hub key not found.');
1000 return response.sendStatus(400);
1001 }
1002
1003 let bodyParams = {
1004 model: request.body.model,
1005 prompt: request.body.prompt,
1006 response_format: 'b64_json',
1007 };
1008
1009 if (request.body.size) {
1010 bodyParams.size = request.body.size;
1011 }
1012
1013 const result = await fetch('https://api.electronhub.ai/v1/images/generations', {
1014 method: 'POST',
1015 headers: {
1016 'Authorization': `Bearer ${key}`,
1017 'Content-Type': 'application/json',
1018 },
1019 body: JSON.stringify({
1020 ...bodyParams,
1021 }),
1022 });
1023
1024 if (!result.ok) {
1025 const errorText = await result.text();
1026 console.warn('Electron Hub returned an error.', result.status, result.statusText, errorText);
1027 return response.sendStatus(500);
1028 }
1029
1030 /** @type {any} */
1031 const data = await result.json();
1032 const image = data?.data?.[0]?.b64_json;
1033
1034 if (!image) {
1035 console.warn('Electron Hub returned invalid data.');
1036 return response.sendStatus(500);
1037 }
1038
1039 return response.send({ image });
1040 } catch (error) {
1041 console.error(error);
1042 return response.sendStatus(500);
1043 }
1044});
1045
1046electronhub.post('/sizes', async (request, response) => {
1047 const result = await fetch(`https://api.electronhub.ai/v1/models/${request.body.model}`, {
1048 method: 'GET',
1049 headers: {
1050 'Content-Type': 'application/json',
1051 },
1052 });
1053
1054 if (!result.ok) {
1055 console.warn('Electron Hub returned an error.');
1056 return response.sendStatus(500);
1057 }
1058
1059 /** @type {any} */
1060 const data = await result.json();
1061
1062 const sizes = data.sizes;
1063
1064 if (!sizes) {
1065 console.warn('Electron Hub returned invalid data.');
1066 return response.sendStatus(500);
1067 }
1068
1069 return response.send({ sizes });
1070});
1071
960const nanogpt = express.Router();1072const nanogpt = express.Router();
9611073
962nanogpt.post('/models', async (request, response) => {1074nanogpt.post('/models', async (request, response) => {
@@ -1439,6 +1551,7 @@ router.use('/drawthings', drawthings);
1439router.use('/pollinations', pollinations);1551router.use('/pollinations', pollinations);
1440router.use('/stability', stability);1552router.use('/stability', stability);
1441router.use('/huggingface', huggingface);1553router.use('/huggingface', huggingface);
1554router.use('/electronhub', electronhub);
1442router.use('/nanogpt', nanogpt);1555router.use('/nanogpt', nanogpt);
1443router.use('/bfl', bfl);1556router.use('/bfl', bfl);
1444router.use('/falai', falai);1557router.use('/falai', falai);