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>
Signed| @@ -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 file | 1 | \ No newline at end of file | |
| @@ -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 | Temperature | 696 | 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 Penalty | 709 | 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 Penalty | 722 | 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 K | 735 | 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 P | 748 | 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 | Seed | 985 | 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 Quality | 2021 | 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. 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. 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"> |
| @@ -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)", |
| @@ -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)", |
| @@ -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)", |
| @@ -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", |
| @@ -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)", |
| @@ -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 ラッパーモード)", |
| @@ -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 래퍼 모드)", |
| @@ -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)", |
| @@ -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": "Использовать настройки из расширения", |
| @@ -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)", |
| @@ -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)", |
| @@ -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 密钥", |
| @@ -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": "若可能,根據模型後設資料推導。", |
| @@ -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; |
| @@ -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) |
| @@ -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 | }; |
| 444 | 445 | ||
| 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 | } |
| 551 | 553 | ||
| 552 | await addSettings(); | 554 | await addSettings(); |
| @@ -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> |
| @@ -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 | } |
| 255 | 259 | ||
| 256 | /** | 260 | /** |
| @@ -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 | } |
| 1808 | 1816 | ||
| 1817 | async 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 | |||
| 1809 | async function loadNanoGPTModels() { | 1835 | async 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 | } |
| 3015 | 3050 | ||
| 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 | */ | ||
| 3057 | async 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 | } |
| 3566 | 3651 | ||
| 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 | */ | ||
| 3658 | async 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: |
| @@ -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> |
| @@ -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 | } |
| 1776 | 1787 | ||
| 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 | ]; |
| 2048 | 2080 | ||
| 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 | } |
| 2052 | 2084 | ||
| 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 | } |
| 2066 | 2099 | ||
| 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 backend | 2118 | * 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 | } |
| 2285 | 2336 | ||
| 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 | } |
| 4572 | 4633 | ||
| 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 | */ | ||
| 4640 | function 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 model | 4655 | * Get the maximum context size for the NanoGPT model |
| 4575 | * @param {string} model Model identifier | 4656 | * @param {string} model Model identifier |
| 4576 | * @param {boolean} isUnlocked Whether context limits are unlocked | 4657 | * @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 | } |
| 4683 | 4764 | ||
| 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 | } |
| 4924 | 5014 | ||
| 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 | } |
| 5218 | 5317 | ||
| 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(); |
| 5221 | 5333 | ||
| @@ -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); |
| @@ -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', |
| @@ -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 }, |
| @@ -633,6 +633,13 @@ export class ToolManager { | |||
| 633 | } | 633 | } |
| 634 | } | 634 | } |
| 635 | 635 | ||
| 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 | } |
| @@ -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', |
| @@ -61,6 +61,7 @@ const API_GROQ = 'https://api.groq.com/openai/v1'; | |||
| 61 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; | 61 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; |
| 62 | const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com'; | 62 | const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com'; |
| 63 | const API_AI21 = 'https://api.ai21.com/studio/v1'; | 63 | const API_AI21 = 'https://api.ai21.com/studio/v1'; |
| 64 | const API_ELECTRONHUB = 'https://api.electronhub.ai/v1'; | ||
| 64 | const API_NANOGPT = 'https://nano-gpt.com/api/v1'; | 65 | const API_NANOGPT = 'https://nano-gpt.com/api/v1'; |
| 65 | const API_DEEPSEEK = 'https://api.deepseek.com/beta'; | 66 | const API_DEEPSEEK = 'https://api.deepseek.com/beta'; |
| 66 | const API_XAI = 'https://api.x.ai/v1'; | 67 | const API_XAI = 'https://api.x.ai/v1'; |
| @@ -1193,6 +1194,106 @@ async function sendAimlapiRequest(request, response) { | |||
| 1193 | } | 1194 | } |
| 1194 | } | 1195 | } |
| 1195 | 1196 | ||
| 1197 | /** | ||
| 1198 | * Sends a request to Electron Hub. | ||
| 1199 | * @param {express.Request} request Express request | ||
| 1200 | * @param {express.Response} response Express response | ||
| 1201 | */ | ||
| 1202 | async 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 | |||
| 1196 | export const router = express.Router(); | 1297 | export const router = express.Router(); |
| 1197 | 1298 | ||
| 1198 | router.post('/status', async function (request, statusResponse) { | 1299 | router.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 | } |
| 1494 | 1600 | ||
| 1495 | let apiUrl; | 1601 | let apiUrl; |
| @@ -1927,4 +2033,22 @@ multimodalModels.post('/nanogpt', async (_req, res) => { | |||
| 1927 | } | 2033 | } |
| 1928 | }); | 2034 | }); |
| 1929 | 2035 | ||
| 2036 | multimodalModels.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 | |||
| 1930 | router.use('/multimodal-models', multimodalModels); | 2054 | router.use('/multimodal-models', multimodalModels); |
| @@ -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 | } |
| 83 | 83 | ||
| 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 | } |
| 171 | 175 | ||
| 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 | } |
| @@ -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', |
| @@ -957,6 +957,118 @@ huggingface.post('/generate', async (request, response) => { | |||
| 957 | } | 957 | } |
| 958 | }); | 958 | }); |
| 959 | 959 | ||
| 960 | const electronhub = express.Router(); | ||
| 961 | |||
| 962 | electronhub.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 | |||
| 994 | electronhub.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 | |||
| 1046 | electronhub.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 | |||
| 960 | const nanogpt = express.Router(); | 1072 | const nanogpt = express.Router(); |
| 961 | 1073 | ||
| 962 | nanogpt.post('/models', async (request, response) => { | 1074 | nanogpt.post('/models', async (request, response) => { |
| @@ -1439,6 +1551,7 @@ router.use('/drawthings', drawthings); | |||
| 1439 | router.use('/pollinations', pollinations); | 1551 | router.use('/pollinations', pollinations); |
| 1440 | router.use('/stability', stability); | 1552 | router.use('/stability', stability); |
| 1441 | router.use('/huggingface', huggingface); | 1553 | router.use('/huggingface', huggingface); |
| 1554 | router.use('/electronhub', electronhub); | ||
| 1442 | router.use('/nanogpt', nanogpt); | 1555 | router.use('/nanogpt', nanogpt); |
| 1443 | router.use('/bfl', bfl); | 1556 | router.use('/bfl', bfl); |
| 1444 | router.use('/falai', falai); | 1557 | router.use('/falai', falai); |