Chutes integration (#4844) * Chutes integration * Fix eslint * Fix key saving * Fix logo coloration * Fix tool checks * Unhide image inlining controls * Fix order of options * Fix type use in TTS extension script * Add Chutes as a vector storage source * Change log levels to debug * Fix streamed reasoning parsing * Skip remote models update * TTS: Fix API key highlight * Sort image models A-Z * TTS: Fixes * Remove unused SD endpoint * Skip setting context size if models list is not yet loaded * remove chutes quota / balance * Fix: streamed tool calling * Hide reasoning effort control * Add image request debug log * Fix: scroll down on media load in extensions * Unhide some samplers * Bring back reasoning effort * This code will never execute * Reformat else if cases * Add stop strings to request * Remove conditional from reasoning_effort body param * Preserve original pricing fields * Unhide logit bias setting * Pass repetition penalty and logit bias to backend * Swap llama tokenizer for llama3 * Pass min_p, remove supported_sampling_parameters checks * Enable logprobs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

55a07d445daf7b37ec5ad2bcb39feab5ddac4362

Ben <44178664+cxmplex@users.noreply.github.com>

Signed
28 files changed, +1049 -33Ignore whitespace
default/content/presets/openai/Default.json+2 -0
@@ -8,6 +8,8 @@
8 "openrouter_sort_models": "alphabetically",8 "openrouter_sort_models": "alphabetically",
9 "ai21_model": "jamba-large",9 "ai21_model": "jamba-large",
10 "mistralai_model": "mistral-large-latest",10 "mistralai_model": "mistral-large-latest",
11 "chutes_model": "deepseek-ai/DeepSeek-V3-0324",
12 "chutes_sort_models": "alphabetically",
11 "electronhub_model": "gpt-4o-mini",13 "electronhub_model": "gpt-4o-mini",
12 "electronhub_sort_models": "alphabetically",14 "electronhub_sort_models": "alphabetically",
13 "electronhub_group_models": false,15 "electronhub_group_models": false,
public/img/chutes.svg+4 -0
@@ -0,0 +1,4 @@
1<svg width="246" height="153" viewBox="0 0 246 153" fill="none" xmlns="http://www.w3.org/2000/svg">
2<path d="M153.334 146.457C149.769 151.894 142.21 152.909 137.336 148.605L115.671 129.479C110.797 125.176 110.872 117.554 115.83 113.348L137.665 94.8248C161.939 74.232 193.912 66.2053 224.344 73.0645L240.772 76.7673C241.359 76.8995 241.902 77.1773 242.353 77.575C244.73 79.6736 243.399 83.5966 240.235 83.8175L239.884 83.842C209.831 85.9395 182.262 102.339 165.298 128.211L153.334 146.457Z" />
3<path d="M61.7046 134.759C57.1727 139.455 49.5229 139.022 45.5496 133.846L2.20718 77.3823C-1.74588 72.2325 -0.220772 64.7778 5.43762 61.5919L91.8585 12.9343C119.419 -2.58324 152.212 -4.22605 180.759 8.48062L243.451 36.385C244.421 36.8168 245.268 37.4842 245.914 38.3264C249.363 42.8196 245.669 49.2399 240.048 48.5205L202.701 43.7404C172.88 39.9236 142.862 50.6695 121.491 72.8128L61.7046 134.759Z" />
4</svg>
public/index.html+59 -16
@@ -678,6 +678,9 @@
678 <div data-source="electronhub">678 <div data-source="electronhub">
679 <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="electronhub_max_prompt_cost" data-i18n="Unknown">Unknown</span>679 <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="electronhub_max_prompt_cost" data-i18n="Unknown">Unknown</span>
680 </div>680 </div>
681 <div data-source="chutes">
682 <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="chutes_max_prompt_cost" data-i18n="Unknown">Unknown</span>
683 </div>
681 <hr>684 <hr>
682 <div class="range-block">685 <div class="range-block">
683 <label for="stream_toggle" title="Enable OpenAI completion streaming" data-i18n="[title]Enable OpenAI completion streaming" class="checkbox_label widthFreeExpand">686 <label for="stream_toggle" title="Enable OpenAI completion streaming" data-i18n="[title]Enable OpenAI completion streaming" class="checkbox_label widthFreeExpand">
@@ -693,7 +696,7 @@
693 </span>696 </span>
694 </div>697 </div>
695 </div>698 </div>
696 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">699 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">
697 <div class="range-block-title" data-i18n="Temperature">700 <div class="range-block-title" data-i18n="Temperature">
698 Temperature701 Temperature
699 </div>702 </div>
@@ -706,7 +709,7 @@
706 </div>709 </div>
707 </div>710 </div>
708 </div>711 </div>
709 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">712 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes">
710 <div class="range-block-title" data-i18n="Frequency Penalty">713 <div class="range-block-title" data-i18n="Frequency Penalty">
711 Frequency Penalty714 Frequency Penalty
712 </div>715 </div>
@@ -719,7 +722,7 @@
719 </div>722 </div>
720 </div>723 </div>
721 </div>724 </div>
722 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">725 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes">
723 <div class="range-block-title" data-i18n="Presence Penalty">726 <div class="range-block-title" data-i18n="Presence Penalty">
724 Presence Penalty727 Presence Penalty
725 </div>728 </div>
@@ -732,7 +735,7 @@
732 </div>735 </div>
733 </div>736 </div>
734 </div>737 </div>
735 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,nanogpt">738 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt">
736 <div class="range-block-title" data-i18n="Top K">739 <div class="range-block-title" data-i18n="Top K">
737 Top K740 Top K
738 </div>741 </div>
@@ -745,7 +748,7 @@
745 </div>748 </div>
746 </div>749 </div>
747 </div>750 </div>
748 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">751 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">
749 <div class="range-block-title" data-i18n="Top P">752 <div class="range-block-title" data-i18n="Top P">
750 Top P753 Top P
751 </div>754 </div>
@@ -758,7 +761,7 @@
758 </div>761 </div>
759 </div>762 </div>
760 </div>763 </div>
761 <div class="range-block" data-source="openrouter,nanogpt">764 <div class="range-block" data-source="openrouter,nanogpt,chutes">
762 <div class="range-block-title" data-i18n="Repetition Penalty">765 <div class="range-block-title" data-i18n="Repetition Penalty">
763 Repetition Penalty766 Repetition Penalty
764 </div>767 </div>
@@ -771,7 +774,7 @@
771 </div>774 </div>
772 </div>775 </div>
773 </div>776 </div>
774 <div class="range-block" data-source="openrouter,nanogpt">777 <div class="range-block" data-source="openrouter,nanogpt,chutes">
775 <div class="range-block-title" data-i18n="Min P">778 <div class="range-block-title" data-i18n="Min P">
776 Min P779 Min P
777 </div>780 </div>
@@ -982,7 +985,7 @@
982 </div>985 </div>
983 </div>986 </div>
984 </div>987 </div>
985 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai">988 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai">
986 <div class="range-block-title justifyLeft" data-i18n="Seed">989 <div class="range-block-title justifyLeft" data-i18n="Seed">
987 Seed990 Seed
988 </div>991 </div>
@@ -1956,7 +1959,7 @@
1956 </span>1959 </span>
1957 </div>1960 </div>
1958 </div>1961 </div>
1959 <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai,electronhub,nanogpt">1962 <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai,electronhub,chutes,nanogpt">
1960 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">1963 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">
1961 <input id="openai_enable_web_search" type="checkbox" />1964 <input id="openai_enable_web_search" type="checkbox" />
1962 <span data-i18n="Enable web search">Enable web search</span>1965 <span data-i18n="Enable web search">Enable web search</span>
@@ -1970,7 +1973,7 @@
1970 </b>1973 </b>
1971 </div>1974 </div>
1972 </div>1975 </div>
1973 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,azure_openai,zai">1976 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,chutes,azure_openai,zai">
1974 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">1977 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
1975 <input id="openai_function_calling" type="checkbox" />1978 <input id="openai_function_calling" type="checkbox" />
1976 <span data-i18n="Enable function calling">Enable function calling</span>1979 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1985,7 +1988,7 @@
1985 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>1988 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
1986 </div>1989 </div>
1987 </div>1990 </div>
1988 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow">1991 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow,chutes">
1989 <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand">1992 <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand">
1990 <input id="openai_media_inlining" type="checkbox" />1993 <input id="openai_media_inlining" type="checkbox" />
1991 <span data-i18n="Send inline media">Send inline media</span>1994 <span data-i18n="Send inline media">Send inline media</span>
@@ -2009,7 +2012,7 @@
2009 <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong>2012 <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong>
2010 <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong>2013 <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong>
2011 </div>2014 </div>
2012 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow">2015 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow,chutes">
2013 <div class="flex-container oneline-dropdown">2016 <div class="flex-container oneline-dropdown">
2014 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">2017 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
2015 Inline Image Quality2018 Inline Image Quality
@@ -2070,7 +2073,7 @@
2070 </div>2073 </div>
2071 </div>2074 </div>
2072 </div>2075 </div>
2073 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,azure_openai,nanogpt,zai">2076 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,chutes,azure_openai,nanogpt,zai">
2074 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">2077 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
2075 <input id="openai_show_thoughts" type="checkbox" />2078 <input id="openai_show_thoughts" type="checkbox" />
2076 <span data-i18n="Request model reasoning">Request model reasoning</span>2079 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2084,7 +2087,7 @@
2084 </strong>2087 </strong>
2085 </div>2088 </div>
2086 </div>2089 </div>
2087 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai">2090 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai,chutes">
2088 <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.">2091 <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.">
2089 <label for="openai_reasoning_effort">2092 <label for="openai_reasoning_effort">
2090 <span data-i18n="Reasoning Effort">Reasoning Effort</span>2093 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2098,7 +2101,7 @@
2098 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>2101 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>
2099 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>2102 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>
2100 </select>2103 </select>
2101 <div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter,perplexity,electronhub,azure_openai" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.">2104 <div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai,chutes" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.">
2102 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.2105 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.
2103 </div>2106 </div>
2104 <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.">2107 <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.">
@@ -2144,7 +2147,7 @@
2144 </div>2147 </div>
2145 </div>2148 </div>
2146 </div>2149 </div>
2147 <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,custom,electronhub,azure_openai">2150 <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,custom,electronhub,azure_openai,chutes">
2148 <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias">2151 <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias">
2149 Logit Bias2152 Logit Bias
2150 </div>2153 </div>
@@ -2803,6 +2806,7 @@
2803 <option value="ai21">AI21</option>2806 <option value="ai21">AI21</option>
2804 <option value="aimlapi">AI/ML API</option>2807 <option value="aimlapi">AI/ML API</option>
2805 <option value="azure_openai">Azure OpenAI</option>2808 <option value="azure_openai">Azure OpenAI</option>
2809 <option value="chutes">Chutes</option>
2806 <option value="claude">Claude</option>2810 <option value="claude">Claude</option>
2807 <option value="cohere">Cohere</option>2811 <option value="cohere">Cohere</option>
2808 <!-- Temporarily disabled. -->2812 <!-- Temporarily disabled. -->
@@ -3479,6 +3483,45 @@
3479 </div>3483 </div>
3480 </div>3484 </div>
3481 </div>3485 </div>
3486 <div id="chutes_form" data-source="chutes">
3487 <h4 data-i18n="Chutes API Key">Chutes API Key</h4>
3488 <div id="chutes_credits_display">
3489 <a href="https://chutes.ai/app/api/billing-balance" target="_blank" data-i18n="View Billing/Balance">View Billing/Balance</a>
3490 </div>
3491 <div class="flex-container">
3492 <input id="api_key_chutes" name="api_key_chutes" class="text_pole flex1" value="" type="text" autocomplete="off">
3493 <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_chutes"></div>
3494 </div>
3495 <div data-for="api_key_chutes" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3496 For privacy reasons, your API key will be hidden after you click 'Connect'.
3497 </div>
3498 <div>
3499 <h4 data-i18n="Chutes Model">Chutes Model</h4>
3500 <select id="model_chutes_select">
3501 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3502 </select>
3503 </div>
3504 <div class="marginTopBot5">
3505 <div class="inline-drawer wide100p">
3506 <div class="inline-drawer-toggle inline-drawer-header">
3507 <b data-i18n="Chutes Model Sorting">Chutes Model Sorting</b>
3508 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3509 </div>
3510 <div class="inline-drawer-content m-b-1">
3511 <div class="marginTopBot5">
3512 <label for="chutes_sort_models" class="checkbox_label">
3513 <select id="chutes_sort_models">
3514 <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3515 <option data-i18n="Input Price" value="pricing.input">Input Price (cheapest)</option>
3516 <option data-i18n="Output Price" value="pricing.output">Output Price (cheapest)</option>
3517 <option data-i18n="Context Size" value="context_length">Context Size</option>
3518 </select>
3519 </label>
3520 </div>
3521 </div>
3522 </div>
3523 </div>
3524 </div>
3482 <div id="nanogpt_form" data-source="nanogpt">3525 <div id="nanogpt_form" data-source="nanogpt">
3483 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>3526 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>
3484 <div class="flex-container">3527 <div class="flex-container">
public/script.js+2 -1
@@ -1431,7 +1431,7 @@ export async function printMessages() {
1431 delay(debounce_timeout.short).then(() => scrollOnMediaLoad());1431 delay(debounce_timeout.short).then(() => scrollOnMediaLoad());
1432}1432}
14331433
1434function scrollOnMediaLoad() {1434export function scrollOnMediaLoad() {
1435 const started = Date.now();1435 const started = Date.now();
1436 const media = chatElement.find('.mes_block img, .mes_block video, .mes_block audio').toArray();1436 const media = chatElement.find('.mes_block img, .mes_block video, .mes_block audio').toArray();
1437 let mediaLoaded = 0;1437 let mediaLoaded = 0;
@@ -5928,6 +5928,7 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
5928 case chat_completion_sources.COHERE:5928 case chat_completion_sources.COHERE:
5929 case chat_completion_sources.XAI:5929 case chat_completion_sources.XAI:
5930 case chat_completion_sources.ELECTRONHUB:5930 case chat_completion_sources.ELECTRONHUB:
5931 case chat_completion_sources.CHUTES:
5931 case chat_completion_sources.AZURE_OPENAI:5932 case chat_completion_sources.AZURE_OPENAI:
5932 case chat_completion_sources.ZAI:5933 case chat_completion_sources.ZAI:
5933 default:5934 default:
public/scripts/RossAscends-mods.js+1 -0
@@ -397,6 +397,7 @@ function RA_autoconnect(PrevApi) {
397 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)397 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
398 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)398 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
399 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)399 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
400 || (secret_state[SECRET_KEYS.CHUTES] && oai_settings.chat_completion_source == chat_completion_sources.CHUTES)
400 || (secret_state[SECRET_KEYS.SILICONFLOW] && oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW)401 || (secret_state[SECRET_KEYS.SILICONFLOW] && oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW)
401 || (secret_state[SECRET_KEYS.ELECTRONHUB] && oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB)402 || (secret_state[SECRET_KEYS.ELECTRONHUB] && oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB)
402 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)403 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
public/scripts/extensions/caption/index.js+4 -1
@@ -10,7 +10,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js';
10import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';10import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
11import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';11import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
12import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';12import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
13import { MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR } from '../../constants.js';13import { debounce_timeout, MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR } from '../../constants.js';
14export { MODULE_NAME };14export { MODULE_NAME };
1515
16const MODULE_NAME = 'caption';16const MODULE_NAME = 'caption';
@@ -211,6 +211,7 @@ async function sendCaptionedMessage(caption, image, mimeType) {
211 context.addOneMessage(message);211 context.addOneMessage(message);
212 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, messageId);212 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, messageId);
213 await context.saveChat();213 await context.saveChat();
214 setTimeout(() => context.scrollOnMediaLoad(), debounce_timeout.short);
214}215}
215216
216/**217/**
@@ -503,6 +504,7 @@ jQuery(async function () {
503 'aimlapi': SECRET_KEYS.AIMLAPI,504 'aimlapi': SECRET_KEYS.AIMLAPI,
504 'moonshot': SECRET_KEYS.MOONSHOT,505 'moonshot': SECRET_KEYS.MOONSHOT,
505 'nanogpt': SECRET_KEYS.NANOGPT,506 'nanogpt': SECRET_KEYS.NANOGPT,
507 'chutes': SECRET_KEYS.CHUTES,
506 'electronhub': SECRET_KEYS.ELECTRONHUB,508 'electronhub': SECRET_KEYS.ELECTRONHUB,
507 'zai': SECRET_KEYS.ZAI,509 'zai': SECRET_KEYS.ZAI,
508 };510 };
@@ -616,6 +618,7 @@ jQuery(async function () {
616 await processEndpoint('aimlapi', '/api/backends/chat-completions/multimodal-models/aimlapi');618 await processEndpoint('aimlapi', '/api/backends/chat-completions/multimodal-models/aimlapi');
617 await processEndpoint('pollinations', '/api/backends/chat-completions/multimodal-models/pollinations');619 await processEndpoint('pollinations', '/api/backends/chat-completions/multimodal-models/pollinations');
618 await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');620 await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');
621 await processEndpoint('chutes', '/api/backends/chat-completions/multimodal-models/chutes');
619 await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub');622 await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub');
620 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');623 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');
621 await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai');624 await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai');
public/scripts/extensions/caption/settings.html+1 -0
@@ -18,6 +18,7 @@
18 <label for="caption_multimodal_api" data-i18n="API">API</label>18 <label for="caption_multimodal_api" data-i18n="API">API</label>
19 <select id="caption_multimodal_api" class="flex1 text_pole">19 <select id="caption_multimodal_api" class="flex1 text_pole">
20 <option value="aimlapi">AI/ML API</option>20 <option value="aimlapi">AI/ML API</option>
21 <option value="chutes">Chutes</option>
21 <option value="anthropic">Claude</option>22 <option value="anthropic">Claude</option>
22 <option value="cohere">Cohere</option>23 <option value="cohere">Cohere</option>
23 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>24 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
public/scripts/extensions/shared.js+4 -0
@@ -257,6 +257,10 @@ function throwIfInvalidModel(useReverseProxy) {
257 throw new Error('Electron Hub API key is not set.');257 throw new Error('Electron Hub API key is not set.');
258 }258 }
259259
260 if (multimodalApi === 'chutes' && !secret_state[SECRET_KEYS.CHUTES]) {
261 throw new Error('Chutes API key is not set.');
262 }
263
260 if (multimodalApi === 'zai' && !secret_state[SECRET_KEYS.ZAI]) {264 if (multimodalApi === 'zai' && !secret_state[SECRET_KEYS.ZAI]) {
261 throw new Error('Z.AI API key is not set.');265 throw new Error('Z.AI API key is not set.');
262 }266 }
public/scripts/extensions/stable-diffusion/index.js+73 -0
@@ -81,6 +81,7 @@ const sources = {
81 pollinations: 'pollinations',81 pollinations: 'pollinations',
82 stability: 'stability',82 stability: 'stability',
83 huggingface: 'huggingface',83 huggingface: 'huggingface',
84 chutes: 'chutes',
84 electronhub: 'electronhub',85 electronhub: 'electronhub',
85 nanogpt: 'nanogpt',86 nanogpt: 'nanogpt',
86 bfl: 'bfl',87 bfl: 'bfl',
@@ -1319,6 +1320,7 @@ async function onModelChange() {
1319 sources.falai,1320 sources.falai,
1320 sources.xai,1321 sources.xai,
1321 sources.google,1322 sources.google,
1323 sources.chutes,
1322 ];1324 ];
13231325
1324 if (cloudSources.includes(extension_settings.sd.source)) {1326 if (cloudSources.includes(extension_settings.sd.source)) {
@@ -1531,6 +1533,9 @@ async function loadSamplers() {
1531 case sources.huggingface:1533 case sources.huggingface:
1532 samplers = ['N/A'];1534 samplers = ['N/A'];
1533 break;1535 break;
1536 case sources.chutes:
1537 samplers = ['N/A'];
1538 break;
1534 case sources.electronhub:1539 case sources.electronhub:
1535 samplers = ['N/A'];1540 samplers = ['N/A'];
1536 break;1541 break;
@@ -1730,6 +1735,9 @@ async function loadModels() {
1730 case sources.huggingface:1735 case sources.huggingface:
1731 models = [{ value: '', text: t`<Enter Model ID above>` }];1736 models = [{ value: '', text: t`<Enter Model ID above>` }];
1732 break;1737 break;
1738 case sources.chutes:
1739 models = await loadChutesModels();
1740 break;
1733 case sources.electronhub:1741 case sources.electronhub:
1734 models = await loadElectronHubModels();1742 models = await loadElectronHubModels();
1735 break;1743 break;
@@ -1905,6 +1913,27 @@ async function loadTogetherAIModels() {
1905 return [];1913 return [];
1906}1914}
19071915
1916async function loadChutesModels() {
1917 if (!secret_state[SECRET_KEYS.CHUTES]) {
1918 console.debug('Chutes API key is not set.');
1919 return [];
1920 }
1921
1922 const result = await fetch('/api/sd/chutes/models', {
1923 method: 'POST',
1924 headers: getRequestHeaders(),
1925 });
1926
1927 if (result.ok) {
1928 const models = await result.json();
1929 console.debug('Loaded Chutes image models:', models);
1930 return models;
1931 }
1932
1933 console.warn('Failed to load Chutes models:', result.status);
1934 return [];
1935}
1936
1908async function loadElectronHubModels() {1937async function loadElectronHubModels() {
1909 if (!secret_state[SECRET_KEYS.ELECTRONHUB]) {1938 if (!secret_state[SECRET_KEYS.ELECTRONHUB]) {
1910 console.debug('Electron Hub API key is not set.');1939 console.debug('Electron Hub API key is not set.');
@@ -2275,6 +2304,9 @@ async function loadSchedulers() {
2275 case sources.huggingface:2304 case sources.huggingface:
2276 schedulers = ['N/A'];2305 schedulers = ['N/A'];
2277 break;2306 break;
2307 case sources.chutes:
2308 schedulers = ['N/A'];
2309 break;
2278 case sources.electronhub:2310 case sources.electronhub:
2279 schedulers = ['N/A'];2311 schedulers = ['N/A'];
2280 break;2312 break;
@@ -2375,6 +2407,9 @@ async function loadVaes() {
2375 case sources.huggingface:2407 case sources.huggingface:
2376 vaes = ['N/A'];2408 vaes = ['N/A'];
2377 break;2409 break;
2410 case sources.chutes:
2411 vaes = ['N/A'];
2412 break;
2378 case sources.electronhub:2413 case sources.electronhub:
2379 vaes = ['N/A'];2414 vaes = ['N/A'];
2380 break;2415 break;
@@ -2967,6 +3002,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2967 case sources.huggingface:3002 case sources.huggingface:
2968 result = await generateHuggingFaceImage(prefixedPrompt, signal);3003 result = await generateHuggingFaceImage(prefixedPrompt, signal);
2969 break;3004 break;
3005 case sources.chutes:
3006 result = await generateChutesImage(prefixedPrompt, negativePrompt, signal);
3007 break;
2970 case sources.electronhub:3008 case sources.electronhub:
2971 result = await generateElectronHubImage(prefixedPrompt, signal);3009 result = await generateElectronHubImage(prefixedPrompt, signal);
2972 break;3010 break;
@@ -3799,6 +3837,38 @@ async function generateHuggingFaceImage(prompt, signal) {
3799}3837}
38003838
3801/**3839/**
3840 * Generates an image using the Chutes API.
3841 * @param {string} prompt - The main instruction used to guide the image generation.
3842 * @param {string} negativePrompt - The instruction used to restrict the image generation.
3843 * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
3844 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
3845 */
3846async function generateChutesImage(prompt, negativePrompt, signal) {
3847 const result = await fetch('/api/sd/chutes/generate', {
3848 method: 'POST',
3849 headers: getRequestHeaders(),
3850 signal: signal,
3851 body: JSON.stringify({
3852 model: extension_settings.sd.model,
3853 prompt: prompt,
3854 negative_prompt: negativePrompt,
3855 width: extension_settings.sd.width,
3856 height: extension_settings.sd.height,
3857 steps: extension_settings.sd.steps,
3858 guidance_scale: extension_settings.sd.scale,
3859 }),
3860 });
3861
3862 if (result.ok) {
3863 const data = await result.json();
3864 return { format: 'jpg', data: data.image };
3865 } else {
3866 const text = await result.text();
3867 throw new Error(text);
3868 }
3869}
3870
3871/**
3802 * Generates an image using the Electron Hub API.3872 * Generates an image using the Electron Hub API.
3803 * @param {string} prompt - The main instruction used to guide the image generation.3873 * @param {string} prompt - The main instruction used to guide the image generation.
3804 * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.3874 * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
@@ -4207,6 +4277,7 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref
4207 context.addOneMessage(message);4277 context.addOneMessage(message);
4208 await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, messageId, 'extension');4278 await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, messageId, 'extension');
4209 await context.saveChat();4279 await context.saveChat();
4280 setTimeout(() => context.scrollOnMediaLoad(), debounce_timeout.short);
4210}4281}
42114282
4212/**4283/**
@@ -4313,6 +4384,8 @@ function isValidState() {
4313 return secret_state[SECRET_KEYS.STABILITY];4384 return secret_state[SECRET_KEYS.STABILITY];
4314 case sources.huggingface:4385 case sources.huggingface:
4315 return secret_state[SECRET_KEYS.HUGGINGFACE];4386 return secret_state[SECRET_KEYS.HUGGINGFACE];
4387 case sources.chutes:
4388 return secret_state[SECRET_KEYS.CHUTES];
4316 case sources.electronhub:4389 case sources.electronhub:
4317 return secret_state[SECRET_KEYS.ELECTRONHUB];4390 return secret_state[SECRET_KEYS.ELECTRONHUB];
4318 case sources.nanogpt:4391 case sources.nanogpt:
public/scripts/extensions/stable-diffusion/settings.html+4 -0
@@ -39,6 +39,7 @@
39 <select id="sd_source">39 <select id="sd_source">
40 <option value="aimlapi">AI/ML API</option>40 <option value="aimlapi">AI/ML API</option>
41 <option value="bfl">BFL (Black Forest Labs)</option>41 <option value="bfl">BFL (Black Forest Labs)</option>
42 <option value="chutes">Chutes</option>
42 <option value="comfy">ComfyUI</option>43 <option value="comfy">ComfyUI</option>
43 <option value="drawthings">DrawThings HTTP API</option>44 <option value="drawthings">DrawThings HTTP API</option>
44 <option value="electronhub">Electron Hub</option>45 <option value="electronhub">Electron Hub</option>
@@ -94,6 +95,9 @@
94 <label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label>95 <label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label>
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="" />96 <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="" />
96 </div>97 </div>
98 <div data-sd-source="chutes">
99 <i data-i18n="Hint: Save an API key in the Chutes (Chat Completion) API settings to use it here.">Hint: Save an API key in the Chutes (Chat Completion) API settings to use it here.</i>
100 </div>
97 <div data-sd-source="electronhub">101 <div data-sd-source="electronhub">
98 <i data-i18n="Hint: Save an API key in the Electron Hub (Chat Completion) API settings to use it here.">Hint: Save an API key in the Electron Hub (Chat Completion) API settings to use it here.</i>102 <i data-i18n="Hint: Save an API key in the Electron Hub (Chat Completion) API settings to use it here.">Hint: Save an API key in the Electron Hub (Chat Completion) API settings to use it here.</i>
99 <div class="flex-container" id="sd_electronhub_quality_row">103 <div class="flex-container" id="sd_electronhub_quality_row">
public/scripts/extensions/tts/chutes.js+233 -0
@@ -0,0 +1,233 @@
1import { event_types, eventSource, getRequestHeaders } from '../../../script.js';
2import { SECRET_KEYS, secret_state } from '../../secrets.js';
3import { getPreviewString, saveTtsProviderSettings } from './index.js';
4
5export { ChutesTtsProvider };
6
7class ChutesTtsProvider {
8 settings;
9 voices = [];
10 models = [];
11 separator = ' . ';
12
13 defaultSettings = {
14 voiceMap: {},
15 model: 'kokoro',
16 speed: 1,
17 };
18
19 get settingsHtml() {
20 let html = `
21 <div class="flex-container alignItemsCenter">
22 <div class="flex1">Chutes TTS API</div>
23 <div id="chutes_tts_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_chutes">
24 <i class="fa-solid fa-key"></i>
25 <span>API Key</span>
26 </div>
27 </div>
28 <div class="flex-container flexFlowColumn">
29 <div class="flex1">
30 <label for="chutes_tts_model">Model</label>
31 <select id="chutes_tts_model" class="text_pole"></select>
32 </div>
33 <div>
34 <label for="chutes_tts_speed">Speed <span id="chutes_tts_speed_output"></span></label>
35 <input type="range" id="chutes_tts_speed" value="1" min="0.25" max="4" step="0.05">
36 </div>
37 </div>`;
38 return html;
39 }
40
41 constructor() {
42 this.handler = async function (/** @type {string} */ key) {
43 if (key !== SECRET_KEYS.CHUTES) return;
44 $('#chutes_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.CHUTES]);
45 await this.onRefreshClick();
46 }.bind(this);
47 }
48
49 dispose() {
50 [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => {
51 eventSource.removeListener(event, this.handler);
52 });
53 }
54
55 onSettingsChange() {
56 this.settings.model = $('#chutes_tts_model').val();
57 this.settings.speed = $('#chutes_tts_speed').val();
58 saveTtsProviderSettings();
59 }
60
61 async loadSettings(settings) {
62 if (Object.keys(settings).length === 0) {
63 Object.assign(settings, this.defaultSettings);
64 }
65
66 this.settings = settings;
67
68 if (!this.settings.voiceMap) {
69 this.settings.voiceMap = {};
70 }
71
72 // Update UI
73 $('#chutes_tts_model').val(this.settings.model);
74 $('#chutes_tts_speed').val(this.settings.speed);
75 $('#chutes_tts_speed_output').text(this.settings.speed);
76
77 $('#chutes_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.CHUTES]);
78 [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => {
79 eventSource.on(event, this.handler);
80 });
81
82 await this.checkReady();
83
84 $('#chutes_tts_model').on('change', () => this.onSettingsChange());
85 $('#chutes_tts_speed').on('input', function () {
86 const value = $(this).val();
87 $('#chutes_tts_speed_output').text(String(value));
88 });
89 $('#chutes_tts_speed').on('change', () => this.onSettingsChange());
90 }
91
92 async checkReady() {
93 await this.updateModels();
94 if (this.models.length === 0) {
95 // No models available
96 }
97 await this.updateVoices();
98 }
99
100 async onRefreshClick() {
101 return await this.checkReady();
102 }
103
104 async updateModels() {
105 // For Chutes TTS, we always use the Kokoro model currently.
106 this.models = ['kokoro'];
107
108 $('#chutes_tts_model').empty();
109 $('#chutes_tts_model').append($('<option>').val('kokoro').text('Kokoro'));
110 $('#chutes_tts_model').val('kokoro');
111
112 this.settings.model = 'kokoro';
113 }
114
115 async updateVoices() {
116 // Kokoro voices list
117 const kokoroVoices = [
118 { id: 'af_alloy', name: 'Alloy (Female)', lang: 'en-US' },
119 { id: 'af_aoede', name: 'Aoede (Female)', lang: 'en-US' },
120 { id: 'af_bella', name: 'Bella (Female)', lang: 'en-US' },
121 { id: 'af_heart', name: 'Heart (Female) - Default', lang: 'en-US' },
122 { id: 'af_jessica', name: 'Jessica (Female)', lang: 'en-US' },
123 { id: 'af_kore', name: 'Kore (Female)', lang: 'en-US' },
124 { id: 'af_nicole', name: 'Nicole (Female)', lang: 'en-US' },
125 { id: 'af_nova', name: 'Nova (Female)', lang: 'en-US' },
126 { id: 'af_river', name: 'River (Female)', lang: 'en-US' },
127 { id: 'af_sarah', name: 'Sarah (Female)', lang: 'en-US' },
128 { id: 'af_sky', name: 'Sky (Female)', lang: 'en-US' },
129 { id: 'am_adam', name: 'Adam (Male)', lang: 'en-US' },
130 { id: 'am_echo', name: 'Echo (Male)', lang: 'en-US' },
131 { id: 'am_eric', name: 'Eric (Male)', lang: 'en-US' },
132 { id: 'am_fenrir', name: 'Fenrir (Male)', lang: 'en-US' },
133 { id: 'am_liam', name: 'Liam (Male)', lang: 'en-US' },
134 { id: 'am_michael', name: 'Michael (Male)', lang: 'en-US' },
135 { id: 'am_onyx', name: 'Onyx (Male)', lang: 'en-US' },
136 { id: 'am_puck', name: 'Puck (Male)', lang: 'en-US' },
137 { id: 'am_santa', name: 'Santa (Male)', lang: 'en-US' },
138 { id: 'bf_alice', name: 'Alice (British Female)', lang: 'en-GB' },
139 { id: 'bf_emma', name: 'Emma (British Female)', lang: 'en-GB' },
140 { id: 'bf_isabella', name: 'Isabella (British Female)', lang: 'en-GB' },
141 { id: 'bf_lily', name: 'Lily (British Female)', lang: 'en-GB' },
142 { id: 'bm_daniel', name: 'Daniel (British Male)', lang: 'en-GB' },
143 { id: 'bm_fable', name: 'Fable (British Male)', lang: 'en-GB' },
144 { id: 'bm_george', name: 'George (British Male)', lang: 'en-GB' },
145 { id: 'bm_lewis', name: 'Lewis (British Male)', lang: 'en-GB' },
146 { id: 'ef_dora', name: 'Dora (European Female)', lang: 'es-ES' },
147 { id: 'em_alex', name: 'Alex (European Male)', lang: 'es-ES' },
148 { id: 'em_santa', name: 'Santa (European Male)', lang: 'es-ES' },
149 { id: 'ff_siwis', name: 'Siwis (French Female)', lang: 'fr-FR' },
150 { id: 'hf_alpha', name: 'Alpha (Hindi Female)', lang: 'hi-IN' },
151 { id: 'hf_beta', name: 'Beta (Hindi Female)', lang: 'hi-IN' },
152 { id: 'hm_omega', name: 'Omega (Hindi Male)', lang: 'hi-IN' },
153 { id: 'hm_psi', name: 'Psi (Hindi Male)', lang: 'hi-IN' },
154 { id: 'if_sara', name: 'Sara (Italian Female)', lang: 'it-IT' },
155 { id: 'im_nicola', name: 'Nicola (Italian Male)', lang: 'it-IT' },
156 { id: 'jf_alpha', name: 'Alpha (Japanese Female)', lang: 'ja-JP' },
157 { id: 'jf_gongitsune', name: 'Gongitsune (Japanese Female)', lang: 'ja-JP' },
158 { id: 'jf_nezumi', name: 'Nezumi (Japanese Female)', lang: 'ja-JP' },
159 { id: 'jf_tebukuro', name: 'Tebukuro (Japanese Female)', lang: 'ja-JP' },
160 { id: 'jm_kumo', name: 'Kumo (Japanese Male)', lang: 'ja-JP' },
161 { id: 'pf_dora', name: 'Dora (Portuguese Female)', lang: 'pt-PT' },
162 { id: 'pm_alex', name: 'Alex (Portuguese Male)', lang: 'pt-PT' },
163 { id: 'pm_santa', name: 'Santa (Portuguese Male)', lang: 'pt-PT' },
164 { id: 'zf_xiaobei', name: 'Xiaobei (Chinese Female)', lang: 'zh-CN' },
165 { id: 'zf_xiaoni', name: 'Xiaoni (Chinese Female)', lang: 'zh-CN' },
166 { id: 'zf_xiaoxiao', name: 'Xiaoxiao (Chinese Female)', lang: 'zh-CN' },
167 { id: 'zf_xiaoyi', name: 'Xiaoyi (Chinese Female)', lang: 'zh-CN' },
168 { id: 'zm_yunjian', name: 'Yunjian (Chinese Male)', lang: 'zh-CN' },
169 { id: 'zm_yunxi', name: 'Yunxi (Chinese Male)', lang: 'zh-CN' },
170 { id: 'zm_yunxia', name: 'Yunxia (Chinese Male)', lang: 'zh-CN' },
171 { id: 'zm_yunyang', name: 'Yunyang (Chinese Male)', lang: 'zh-CN' },
172 ];
173
174 this.voices = kokoroVoices.map(v => ({
175 name: v.name,
176 voice_id: v.id,
177 lang: v.lang,
178 }));
179 }
180
181 async getVoice(voiceName) {
182 if (this.voices.length === 0) {
183 await this.updateVoices();
184 }
185 const voice = this.voices.find(v => v.name === voiceName || v.voice_id === voiceName);
186 return voice || this.voices.find(v => v.voice_id === 'af_heart');
187 }
188
189 async generateTts(text, voiceId) {
190 const response = await this.fetchTtsGeneration(text, voiceId);
191 return response;
192 }
193
194 async fetchTtsGeneration(text, voiceId) {
195 const apiKey = secret_state[SECRET_KEYS.CHUTES];
196
197 if (!apiKey) {
198 throw new Error('No Chutes API key found');
199 }
200
201 const response = await fetch('/api/openai/chutes/generate-voice', {
202 method: 'POST',
203 headers: getRequestHeaders(),
204 body: JSON.stringify({
205 input: text,
206 voice: voiceId || 'af_heart',
207 speed: this.settings.speed || 1,
208 }),
209 });
210
211 if (!response.ok) {
212 const error = await response.text();
213 throw new Error(`Chutes TTS failed: ${error}`);
214 }
215
216 return response;
217 }
218
219 async fetchTtsVoiceObjects() {
220 if (this.voices.length === 0) {
221 await this.updateVoices();
222 }
223
224 const voiceIds = this.voices
225 .map(voice => ({ name: voice.name, voice_id: voice.voice_id, preview_url: false }));
226 return voiceIds;
227 }
228
229 async previewTtsVoice(voiceId) {
230 const text = getPreviewString(voiceId);
231 await this.generateTts(text, voiceId);
232 }
233}
public/scripts/extensions/tts/index.js+3 -3
@@ -34,6 +34,7 @@ import { TtsWebuiProvider } from './tts-webui.js';
34import { PollinationsTtsProvider } from './pollinations.js';34import { PollinationsTtsProvider } from './pollinations.js';
35import { MiniMaxTtsProvider } from './minimax.js';35import { MiniMaxTtsProvider } from './minimax.js';
36import { ElectronHubTtsProvider } from './electronhub.js';36import { ElectronHubTtsProvider } from './electronhub.js';
37import { ChutesTtsProvider } from './chutes.js';
3738
38const UPDATE_INTERVAL = 1000;39const UPDATE_INTERVAL = 1000;
39const wrapper = new ModuleWorkerWrapper(moduleWorker);40const wrapper = new ModuleWorkerWrapper(moduleWorker);
@@ -120,6 +121,7 @@ const ttsProviders = {
120 AllTalk: AllTalkTtsProvider,121 AllTalk: AllTalkTtsProvider,
121 Azure: AzureTtsProvider,122 Azure: AzureTtsProvider,
122 Chatterbox: ChatterboxTtsProvider,123 Chatterbox: ChatterboxTtsProvider,
124 Chutes: ChutesTtsProvider,
123 Coqui: CoquiTtsProvider,125 Coqui: CoquiTtsProvider,
124 'CosyVoice (Unofficial)': CosyVoiceProvider,126 'CosyVoice (Unofficial)': CosyVoiceProvider,
125 Edge: EdgeTtsProvider,127 Edge: EdgeTtsProvider,
@@ -239,9 +241,7 @@ function isTtsProcessing() {
239241
240/**242/**
241 * Splits a message into lines and adds each non-empty line to the TTS job queue.243 * Splits a message into lines and adds each non-empty line to the TTS job queue.
242 * @param {Object} message - The message object to be processed.244 * @param {ChatMessage} message - The message object to be processed.
243 * @param {string} message.mes - The text of the message to be split into lines.
244 * @param {string} message.name - The name associated with the message.
245 * @returns {void}245 * @returns {void}
246 */246 */
247function processAndQueueTtsMessage(message) {247function processAndQueueTtsMessage(message) {
public/scripts/extensions/vectors/index.js+48 -0
@@ -69,6 +69,7 @@ const settings = {
69 vllm_model: '',69 vllm_model: '',
70 webllm_model: '',70 webllm_model: '',
71 google_model: 'text-embedding-005',71 google_model: 'text-embedding-005',
72 chutes_model: 'chutes-qwen-qwen3-embedding-8b',
72 summarize: false,73 summarize: false,
73 summarize_sent: false,74 summarize_sent: false,
74 summary_source: 'main',75 summary_source: 'main',
@@ -829,6 +830,9 @@ function getVectorsRequestBody(args = {}) {
829 body.vertexai_region = oai_settings.vertexai_region;830 body.vertexai_region = oai_settings.vertexai_region;
830 body.vertexai_express_project_id = oai_settings.vertexai_express_project_id;831 body.vertexai_express_project_id = oai_settings.vertexai_express_project_id;
831 break;832 break;
833 case 'chutes':
834 body.model = extension_settings.vectors.chutes_model;
835 break;
832 default:836 default:
833 break;837 break;
834 }838 }
@@ -913,6 +917,7 @@ async function insertVectorItems(collectionId, items) {
913function throwIfSourceInvalid() {917function throwIfSourceInvalid() {
914 if (settings.source === 'openai' && !secret_state[SECRET_KEYS.OPENAI] ||918 if (settings.source === 'openai' && !secret_state[SECRET_KEYS.OPENAI] ||
915 settings.source === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB] ||919 settings.source === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB] ||
920 settings.source === 'chutes' && !secret_state[SECRET_KEYS.CHUTES] ||
916 settings.source === 'openrouter' && !secret_state[SECRET_KEYS.OPENROUTER] ||921 settings.source === 'openrouter' && !secret_state[SECRET_KEYS.OPENROUTER] ||
917 settings.source === 'palm' && !secret_state[SECRET_KEYS.MAKERSUITE] ||922 settings.source === 'palm' && !secret_state[SECRET_KEYS.MAKERSUITE] ||
918 settings.source === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT] ||923 settings.source === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT] ||
@@ -1127,6 +1132,7 @@ function toggleSettings() {
1127 $('#together_vectorsModel').toggle(settings.source === 'togetherai');1132 $('#together_vectorsModel').toggle(settings.source === 'togetherai');
1128 $('#openai_vectorsModel').toggle(settings.source === 'openai');1133 $('#openai_vectorsModel').toggle(settings.source === 'openai');
1129 $('#electronhub_vectorsModel').toggle(settings.source === 'electronhub');1134 $('#electronhub_vectorsModel').toggle(settings.source === 'electronhub');
1135 $('#chutes_vectorsModel').toggle(settings.source === 'chutes');
1130 $('#openrouter_vectorsModel').toggle(settings.source === 'openrouter');1136 $('#openrouter_vectorsModel').toggle(settings.source === 'openrouter');
1131 $('#cohere_vectorsModel').toggle(settings.source === 'cohere');1137 $('#cohere_vectorsModel').toggle(settings.source === 'cohere');
1132 $('#ollama_vectorsModel').toggle(settings.source === 'ollama');1138 $('#ollama_vectorsModel').toggle(settings.source === 'ollama');
@@ -1147,9 +1153,46 @@ function toggleSettings() {
1147 case 'openrouter':1153 case 'openrouter':
1148 loadOpenRouterModels();1154 loadOpenRouterModels();
1149 break;1155 break;
1156 case 'chutes':
1157 loadChutesModels();
1158 break;
1150 }1159 }
1151}1160}
11521161
1162async function loadChutesModels() {
1163 try {
1164 const response = await fetch('/api/openai/chutes/models/embedding', {
1165 method: 'POST',
1166 headers: getRequestHeaders(),
1167 });
1168 if (!response.ok) {
1169 throw new Error(`HTTP ${response.status}`);
1170 }
1171 /** @type {Array<any>} */
1172 const data = await response.json();
1173 const models = Array.isArray(data) ? data : [];
1174 populateChutesModelSelect(models);
1175 } catch (err) {
1176 console.warn('Chutes models fetch failed', err);
1177 populateChutesModelSelect([]);
1178 }
1179}
1180
1181function populateChutesModelSelect(models) {
1182 const select = $('#vectors_chutes_model');
1183 select.empty();
1184 for (const m of models) {
1185 const option = document.createElement('option');
1186 option.value = m.slug;
1187 option.text = m.name;
1188 select.append(option);
1189 }
1190 if (!settings.chutes_model && models.length) {
1191 settings.chutes_model = models[0].slug;
1192 }
1193 $('#vectors_chutes_model').val(settings.chutes_model);
1194}
1195
1153async function loadElectronHubModels() {1196async function loadElectronHubModels() {
1154 try {1197 try {
1155 const response = await fetch('/api/openai/electronhub/models', {1198 const response = await fetch('/api/openai/electronhub/models', {
@@ -1630,6 +1673,11 @@ jQuery(async () => {
1630 Object.assign(extension_settings.vectors, settings);1673 Object.assign(extension_settings.vectors, settings);
1631 saveSettingsDebounced();1674 saveSettingsDebounced();
1632 });1675 });
1676 $('#vectors_chutes_model').val(settings.chutes_model).on('change', () => {
1677 settings.chutes_model = String($('#vectors_chutes_model').val());
1678 Object.assign(extension_settings.vectors, settings);
1679 saveSettingsDebounced();
1680 });
1633 $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {1681 $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {
1634 settings.openrouter_model = String($('#vectors_openrouter_model').val());1682 settings.openrouter_model = String($('#vectors_openrouter_model').val());
1635 Object.assign(extension_settings.vectors, settings);1683 Object.assign(extension_settings.vectors, settings);
public/scripts/extensions/vectors/settings.html+10 -0
@@ -10,6 +10,7 @@
10 Vectorization Source10 Vectorization Source
11 </label>11 </label>
12 <select id="vectors_source" class="text_pole">12 <select id="vectors_source" class="text_pole">
13 <option value="chutes">Chutes</option>
13 <option value="cohere">Cohere</option>14 <option value="cohere">Cohere</option>
14 <option value="electronhub">Electron Hub</option>15 <option value="electronhub">Electron Hub</option>
15 <option value="extras">Extras (deprecated)</option>16 <option value="extras">Extras (deprecated)</option>
@@ -28,6 +29,15 @@
28 <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>29 <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>
29 </select>30 </select>
30 </div>31 </div>
32 <div class="flex-container flexFlowColumn" id="chutes_vectorsModel">
33 <label for="vectors_chutes_model" data-i18n="Vectorization Model">
34 Vectorization Model
35 </label>
36 <select id="vectors_chutes_model" class="text_pole"></select>
37 <i data-i18n="Hint: Set your Chutes API key in API Connections.">
38 Hint: Set your Chutes API key in API Connections.
39 </i>
40 </div>
31 <div class="flex-container flexFlowColumn" id="electronhub_vectorsModel">41 <div class="flex-container flexFlowColumn" id="electronhub_vectorsModel">
32 <label for="vectors_electronhub_model" data-i18n="Vectorization Model">42 <label for="vectors_electronhub_model" data-i18n="Vectorization Model">
33 Vectorization Model43 Vectorization Model
public/scripts/openai.js+193 -3
@@ -184,6 +184,7 @@ export const chat_completion_sources = {
184 PERPLEXITY: 'perplexity',184 PERPLEXITY: 'perplexity',
185 GROQ: 'groq',185 GROQ: 'groq',
186 ELECTRONHUB: 'electronhub',186 ELECTRONHUB: 'electronhub',
187 CHUTES: 'chutes',
187 NANOGPT: 'nanogpt',188 NANOGPT: 'nanogpt',
188 DEEPSEEK: 'deepseek',189 DEEPSEEK: 'deepseek',
189 AIMLAPI: 'aimlapi',190 AIMLAPI: 'aimlapi',
@@ -293,6 +294,8 @@ export const settingsToUpdate = {
293 cohere_model: ['#model_cohere_select', 'cohere_model', false, true],294 cohere_model: ['#model_cohere_select', 'cohere_model', false, true],
294 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],295 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],
295 groq_model: ['#model_groq_select', 'groq_model', false, true],296 groq_model: ['#model_groq_select', 'groq_model', false, true],
297 chutes_model: ['#model_chutes_select', 'chutes_model', false, true],
298 chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true],
296 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],299 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],
297 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],300 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],
298 electronhub_sort_models: ['#electronhub_sort_models', 'electronhub_sort_models', false, true],301 electronhub_sort_models: ['#electronhub_sort_models', 'electronhub_sort_models', false, true],
@@ -400,6 +403,8 @@ const default_settings = {
400 cohere_model: 'command-r-plus',403 cohere_model: 'command-r-plus',
401 perplexity_model: 'sonar-pro',404 perplexity_model: 'sonar-pro',
402 groq_model: 'llama-3.3-70b-versatile',405 groq_model: 'llama-3.3-70b-versatile',
406 chutes_model: 'deepseek-ai/DeepSeek-V3-0324',
407 chutes_sort_models: 'alphabetically',
403 siliconflow_model: 'deepseek-ai/DeepSeek-V3',408 siliconflow_model: 'deepseek-ai/DeepSeek-V3',
404 electronhub_model: 'gpt-4o-mini',409 electronhub_model: 'gpt-4o-mini',
405 electronhub_sort_models: 'alphabetically',410 electronhub_sort_models: 'alphabetically',
@@ -1592,6 +1597,8 @@ export function getChatCompletionModel(source = null) {
1592 return oai_settings.siliconflow_model;1597 return oai_settings.siliconflow_model;
1593 case chat_completion_sources.ELECTRONHUB:1598 case chat_completion_sources.ELECTRONHUB:
1594 return oai_settings.electronhub_model;1599 return oai_settings.electronhub_model;
1600 case chat_completion_sources.CHUTES:
1601 return oai_settings.chutes_model;
1595 case chat_completion_sources.NANOGPT:1602 case chat_completion_sources.NANOGPT:
1596 return oai_settings.nanogpt_model;1603 return oai_settings.nanogpt_model;
1597 case chat_completion_sources.DEEPSEEK:1604 case chat_completion_sources.DEEPSEEK:
@@ -1717,6 +1724,72 @@ function calculateElectronHubCost() {
1717 $('#electronhub_max_prompt_cost').text(cost);1724 $('#electronhub_max_prompt_cost').text(cost);
1718}1725}
17191726
1727function getChutesModelTemplate(option) {
1728 const model = model_list.find(x => x.id === option?.element?.value);
1729
1730 if (!option.id || !model) {
1731 return option.text;
1732 }
1733
1734 const inputPrice = model.pricing?.input;
1735 const outputPrice = model.pricing?.output;
1736
1737 let price = 'Unknown';
1738 if (inputPrice !== undefined && outputPrice !== undefined) {
1739 // Check if both prices are 0 (free model)
1740 if (inputPrice === 0 && outputPrice === 0) {
1741 price = 'Free';
1742 } else {
1743 price = `$${inputPrice}/$${outputPrice} in/out Mtoken`;
1744 }
1745 }
1746
1747 const contextLength = model.context_length || model.max_model_len || 'Unknown';
1748 const visionIcon = model.input_modalities?.includes('image') ? '<i class="fa-solid fa-eye fa-sm" title="This model supports vision"></i>' : '';
1749 const reasoningIcon = model.supported_features?.includes('reasoning') ? '<i class="fa-solid fa-brain fa-sm" title="This model supports reasoning"></i>' : '';
1750 const toolCallsIcon = model.supported_features?.includes('structured_outputs') ? '<i class="fa-solid fa-wrench fa-sm" title="This model supports function tools"></i>' : '';
1751
1752 const iconsContainer = document.createElement('span');
1753 iconsContainer.insertAdjacentHTML('beforeend', visionIcon);
1754 iconsContainer.insertAdjacentHTML('beforeend', reasoningIcon);
1755 iconsContainer.insertAdjacentHTML('beforeend', toolCallsIcon);
1756
1757 const capabilities = (iconsContainer.children.length) ? ` | ${iconsContainer.innerHTML}` : '';
1758
1759 return $((`
1760 <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}">
1761 <strong>${DOMPurify.sanitize(model.id)}</strong> | ${contextLength} ctx | <small>${price}</small>${capabilities}
1762 </div>
1763 `));
1764}
1765
1766function calculateChutesCost() {
1767 if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) {
1768 return;
1769 }
1770
1771 let cost = 'Unknown';
1772 const model = model_list.find(x => x.id === oai_settings.chutes_model);
1773
1774 if (model?.pricing) {
1775 const outputPrice = model.pricing?.output;
1776 const inputPrice = model.pricing?.input;
1777
1778 if (outputPrice !== undefined && inputPrice !== undefined) {
1779 const outputCost = Number(outputPrice / 1000000);
1780 const inputCost = Number(inputPrice / 1000000);
1781 const outputTokens = oai_settings.openai_max_tokens;
1782 const inputTokens = (oai_settings.openai_max_context - outputTokens);
1783 const totalCost = (outputCost * outputTokens) + (inputCost * inputTokens);
1784 if (!isNaN(totalCost)) {
1785 cost = '$' + totalCost.toFixed(4);
1786 }
1787 }
1788 }
1789
1790 $('#chutes_max_prompt_cost').text(cost);
1791}
1792
1720function saveModelList(data) {1793function saveModelList(data) {
1721 model_list = data.map((model) => ({ ...model }));1794 model_list = data.map((model) => ({ ...model }));
1722 model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id));1795 model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id));
@@ -1814,6 +1887,27 @@ function saveModelList(data) {
1814 $('#model_electronhub_select').val(oai_settings.electronhub_model).trigger('change');1887 $('#model_electronhub_select').val(oai_settings.electronhub_model).trigger('change');
1815 }1888 }
18161889
1890 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {
1891 model_list = model_list.filter(model => !model.id.toLowerCase().includes('affine'));
1892
1893 model_list = chutesSortBy(model_list, oai_settings.chutes_sort_models);
1894
1895 $('#model_chutes_select').empty();
1896
1897 for (const model of model_list) {
1898 const option = $('<option>').val(model.id).text(model.id);
1899 option.attr('data-model', JSON.stringify(model));
1900 $('#model_chutes_select').append(option);
1901 }
1902
1903 const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model);
1904 if (model_list.length > 0 && (!selectedModel || !oai_settings.chutes_model)) {
1905 oai_settings.chutes_model = model_list[0].id;
1906 }
1907
1908 $('#model_chutes_select').val(oai_settings.chutes_model).trigger('change');
1909 }
1910
1817 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {1911 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
1818 $('#model_nanogpt_select').empty();1912 $('#model_nanogpt_select').empty();
1819 model_list.forEach((model) => {1913 model_list.forEach((model) => {
@@ -2083,6 +2177,24 @@ function openRouterGroupByVendor(array) {
2083 }, new Map());2177 }, new Map());
2084}2178}
20852179
2180function chutesSortBy(data, property = 'alphabetically') {
2181 return data.sort((a, b) => {
2182 if (property === 'context_length') {
2183 return b.context_length - a.context_length;
2184 } else if (property === 'pricing.input') {
2185 const aPrice = parseFloat(a.pricing?.input || 0);
2186 const bPrice = parseFloat(b.pricing?.input || 0);
2187 return aPrice - bPrice;
2188 } else if (property === 'pricing.output') {
2189 const aPrice = parseFloat(a.pricing?.output || 0);
2190 const bPrice = parseFloat(b.pricing?.output || 0);
2191 return aPrice - bPrice;
2192 } else {
2193 return a?.id && b?.id && a.id.localeCompare(b.id);
2194 }
2195 });
2196}
2197
2086function appendElectronHubOptions(model_list, groupModels = false) {2198function appendElectronHubOptions(model_list, groupModels = false) {
2087 const appendOption = (model, parent = null) => {2199 const appendOption = (model, parent = null) => {
2088 (parent || $('#model_electronhub_select')).append(2200 (parent || $('#model_electronhub_select')).append(
@@ -2201,6 +2313,7 @@ function getReasoningEffort() {
2201 chat_completion_sources.PERPLEXITY,2313 chat_completion_sources.PERPLEXITY,
2202 chat_completion_sources.COMETAPI,2314 chat_completion_sources.COMETAPI,
2203 chat_completion_sources.ELECTRONHUB,2315 chat_completion_sources.ELECTRONHUB,
2316 chat_completion_sources.CHUTES,
2204 ];2317 ];
22052318
2206 if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {2319 if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {
@@ -2285,6 +2398,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2285 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;2398 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
2286 const isAimlapi = oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI;2399 const isAimlapi = oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI;
2287 const isElectronHub = oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB;2400 const isElectronHub = oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB;
2401 const isChutes = oai_settings.chat_completion_source == chat_completion_sources.CHUTES;
2288 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;2402 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
2289 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;2403 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;
2290 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;2404 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;
@@ -2299,7 +2413,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2299 const useLogprobs = !!power_user.request_token_probabilities;2413 const useLogprobs = !!power_user.request_token_probabilities;
2300 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isAzureOpenAI || isCustom || isXAI || isAimlapi || isMoonshot);2414 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isAzureOpenAI || isCustom || isXAI || isAimlapi || isMoonshot);
23012415
2302 const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.ELECTRONHUB, chat_completion_sources.CUSTOM];2416 const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.ELECTRONHUB, chat_completion_sources.CHUTES, chat_completion_sources.CUSTOM];
2303 if (oai_settings.bias_preset_selected2417 if (oai_settings.bias_preset_selected
2304 && logitBiasSources.includes(oai_settings.chat_completion_source)2418 && logitBiasSources.includes(oai_settings.chat_completion_source)
2305 && Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])2419 && Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])
@@ -2367,7 +2481,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2367 }2481 }
23682482
2369 // Add logprobs request (currently OpenAI only, max 5 on their side)2483 // Add logprobs request (currently OpenAI only, max 5 on their side)
2370 if (useLogprobs && (isOAI || isAzureOpenAI || isCustom || isDeepSeek || isXAI || isAimlapi)) {2484 if (useLogprobs && (isOAI || isAzureOpenAI || isCustom || isDeepSeek || isXAI || isAimlapi || isChutes)) {
2371 generate_data['logprobs'] = 5;2485 generate_data['logprobs'] = 5;
2372 }2486 }
23732487
@@ -2494,6 +2608,14 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2494 generate_data['top_k'] = Number(oai_settings.top_k_openai);2608 generate_data['top_k'] = Number(oai_settings.top_k_openai);
2495 }2609 }
24962610
2611 if (isChutes) {
2612 generate_data['min_p'] = Number(oai_settings.min_p_openai);
2613 generate_data['top_k'] = oai_settings.top_k_openai > 0 ? Number(oai_settings.top_k_openai) : undefined;
2614 generate_data['repetition_penalty'] = Number(oai_settings.repetition_penalty_openai);
2615 generate_data['seed'] = oai_settings.seed >= 0 ? oai_settings.seed : undefined;
2616 generate_data['stop'] = getCustomStoppingStrings();
2617 }
2618
2497 // https://docs.z.ai/api-reference/llm/chat-completion2619 // https://docs.z.ai/api-reference/llm/chat-completion
2498 if (isZai) {2620 if (isZai) {
2499 generate_data['top_p'] = generate_data.top_p || 0.01;2621 generate_data['top_p'] = generate_data.top_p || 0.01;
@@ -2697,7 +2819,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
2697 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');2819 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
2698 }2820 }
2699 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2821 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2700 } 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, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI, chat_completion_sources.SILICONFLOW].includes(chat_completion_source)) {2822 } 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, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI, chat_completion_sources.SILICONFLOW, chat_completion_sources.CHUTES].includes(chat_completion_source)) {
2701 if (show_thoughts) {2823 if (show_thoughts) {
2702 state.reasoning +=2824 state.reasoning +=
2703 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??2825 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
@@ -2733,6 +2855,7 @@ function parseChatCompletionLogprobs(data) {
2733 case chat_completion_sources.DEEPSEEK:2855 case chat_completion_sources.DEEPSEEK:
2734 case chat_completion_sources.XAI:2856 case chat_completion_sources.XAI:
2735 case chat_completion_sources.CUSTOM:2857 case chat_completion_sources.CUSTOM:
2858 case chat_completion_sources.CHUTES:
2736 if (!data.choices?.length) {2859 if (!data.choices?.length) {
2737 return null;2860 return null;
2738 }2861 }
@@ -4659,6 +4782,26 @@ function getFireworksMaxContext(model, isUnlocked) {
4659}4782}
46604783
4661/**4784/**
4785 * Get the maximum context size for the Chutes model
4786 * @param {string} model Model identifier
4787 * @param {boolean} isUnlocked Whether context limits are unlocked
4788 * @returns {number} Maximum context size in tokens
4789 */
4790function getChutesMaxContext(model, isUnlocked) {
4791 if (isUnlocked) {
4792 return unlocked_max;
4793 }
4794
4795 if (Array.isArray(model_list)) {
4796 const modelInfo = model_list.find(m => m.id === model);
4797 if (modelInfo?.context_length) {
4798 return modelInfo.context_length;
4799 }
4800 }
4801 return max_8k;
4802}
4803
4804/**
4662 * Get the maximum context size for the ElectronHub model4805 * Get the maximum context size for the ElectronHub model
4663 * @param {string} model Model identifier4806 * @param {string} model Model identifier
4664 * @param {boolean} isUnlocked Whether context limits are unlocked4807 * @param {boolean} isUnlocked Whether context limits are unlocked
@@ -4805,6 +4948,15 @@ async function onModelChange() {
4805 oai_settings.electronhub_model = value;4948 oai_settings.electronhub_model = value;
4806 }4949 }
48074950
4951 if ($(this).is('#model_chutes_select')) {
4952 if (!value || !hasModelsLoaded) {
4953 console.debug('Null Chutes model selected. Ignoring.');
4954 return;
4955 }
4956 console.log('Chutes model changed to', value);
4957 oai_settings.chutes_model = value;
4958 }
4959
4808 if ($(this).is('#model_nanogpt_select')) {4960 if ($(this).is('#model_nanogpt_select')) {
4809 if (!value || !hasModelsLoaded) {4961 if (!value || !hasModelsLoaded) {
4810 console.debug('Null NanoGPT model selected. Ignoring.');4962 console.debug('Null NanoGPT model selected. Ignoring.');
@@ -5070,6 +5222,17 @@ async function onModelChange() {
5070 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');5222 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
5071 }5223 }
50725224
5225 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {
5226 const maxContext = getChutesMaxContext(oai_settings.chutes_model, oai_settings.max_context_unlocked);
5227 $('#openai_max_context').attr('max', maxContext);
5228 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5229 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5230 oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
5231 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
5232
5233 calculateChutesCost();
5234 }
5235
5073 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {5236 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
5074 const maxContext = getElectronHubMaxContext(oai_settings.electronhub_model, oai_settings.max_context_unlocked);5237 const maxContext = getElectronHubMaxContext(oai_settings.electronhub_model, oai_settings.max_context_unlocked);
5075 $('#openai_max_context').attr('max', maxContext);5238 $('#openai_max_context').attr('max', maxContext);
@@ -5227,6 +5390,10 @@ async function onOpenrouterModelSortChange() {
5227 await getStatusOpen();5390 await getStatusOpen();
5228}5391}
52295392
5393async function onChutesModelSortChange() {
5394 await getStatusOpen();
5395}
5396
5230async function onElectronHubModelSortChange() {5397async function onElectronHubModelSortChange() {
5231 await getStatusOpen();5398 await getStatusOpen();
5232}5399}
@@ -5273,6 +5440,7 @@ async function onConnectButtonClick(e) {
5273 [chat_completion_sources.COMETAPI]: { key: SECRET_KEYS.COMETAPI, selector: '#api_key_cometapi', proxy: false },5440 [chat_completion_sources.COMETAPI]: { key: SECRET_KEYS.COMETAPI, selector: '#api_key_cometapi', proxy: false },
5274 [chat_completion_sources.AZURE_OPENAI]: { key: SECRET_KEYS.AZURE_OPENAI, selector: '#api_key_azure_openai', proxy: false },5441 [chat_completion_sources.AZURE_OPENAI]: { key: SECRET_KEYS.AZURE_OPENAI, selector: '#api_key_azure_openai', proxy: false },
5275 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: false },5442 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: false },
5443 [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false },
5276 };5444 };
52775445
5278 // Vertex AI Express version - use API key5446 // Vertex AI Express version - use API key
@@ -5345,6 +5513,9 @@ function toggleChatCompletionForms() {
5345 else if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {5513 else if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {
5346 $('#model_groq_select').trigger('change');5514 $('#model_groq_select').trigger('change');
5347 }5515 }
5516 else if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {
5517 $('#model_chutes_select').trigger('change');
5518 }
5348 else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) {5519 else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) {
5349 $('#model_siliconflow_select').trigger('change');5520 $('#model_siliconflow_select').trigger('change');
5350 }5521 }
@@ -5542,6 +5713,8 @@ export function isImageInliningSupported() {
5542 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));5713 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
5543 case chat_completion_sources.AIMLAPI:5714 case chat_completion_sources.AIMLAPI:
5544 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision'));5715 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision'));
5716 case chat_completion_sources.CHUTES:
5717 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.chutes_model)?.input_modalities?.includes('image'));
5545 case chat_completion_sources.ELECTRONHUB:5718 case chat_completion_sources.ELECTRONHUB:
5546 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.electronhub_model)?.metadata?.vision);5719 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.electronhub_model)?.metadata?.vision);
5547 case chat_completion_sources.POLLINATIONS:5720 case chat_completion_sources.POLLINATIONS:
@@ -5972,6 +6145,7 @@ export function initOpenAI() {
5972 $('#openai_max_context_counter').val(`${$(this).val()}`);6145 $('#openai_max_context_counter').val(`${$(this).val()}`);
5973 calculateOpenRouterCost();6146 calculateOpenRouterCost();
5974 calculateElectronHubCost();6147 calculateElectronHubCost();
6148 calculateChutesCost();
5975 saveSettingsDebounced();6149 saveSettingsDebounced();
5976 });6150 });
59776151
@@ -5979,6 +6153,7 @@ export function initOpenAI() {
5979 oai_settings.openai_max_tokens = Number($(this).val());6153 oai_settings.openai_max_tokens = Number($(this).val());
5980 calculateOpenRouterCost();6154 calculateOpenRouterCost();
5981 calculateElectronHubCost();6155 calculateElectronHubCost();
6156 calculateChutesCost();
5982 saveSettingsDebounced();6157 saveSettingsDebounced();
5983 });6158 });
59846159
@@ -6178,6 +6353,11 @@ export function initOpenAI() {
6178 saveSettingsDebounced();6353 saveSettingsDebounced();
6179 });6354 });
61806355
6356 $('#chutes_sort_models').on('input', function () {
6357 oai_settings.chutes_sort_models = String($(this).val());
6358 saveSettingsDebounced();
6359 });
6360
6181 $('#electronhub_group_models').on('input', function () {6361 $('#electronhub_group_models').on('input', function () {
6182 oai_settings.electronhub_group_models = !!$(this).prop('checked');6362 oai_settings.electronhub_group_models = !!$(this).prop('checked');
6183 saveSettingsDebounced();6363 saveSettingsDebounced();
@@ -6377,6 +6557,14 @@ export function initOpenAI() {
6377 templateResult: getElectronHubModelTemplate,6557 templateResult: getElectronHubModelTemplate,
6378 matcher: textValueMatcher,6558 matcher: textValueMatcher,
6379 });6559 });
6560 $('#model_chutes_select').select2({
6561 placeholder: t`Select a model`,
6562 searchInputPlaceholder: t`Search models...`,
6563 searchInputCssClass: 'text_pole',
6564 width: '100%',
6565 templateResult: getChutesModelTemplate,
6566 matcher: textValueMatcher,
6567 });
6380 $('#completion_prompt_manager_popup_entry_form_injection_trigger').select2({6568 $('#completion_prompt_manager_popup_entry_form_injection_trigger').select2({
6381 placeholder: t`All types (default)`,6569 placeholder: t`All types (default)`,
6382 width: '100%',6570 width: '100%',
@@ -6427,6 +6615,7 @@ export function initOpenAI() {
6427 $('#model_openrouter_select').on('change', onModelChange);6615 $('#model_openrouter_select').on('change', onModelChange);
6428 $('#openrouter_group_models').on('change', onOpenrouterModelSortChange);6616 $('#openrouter_group_models').on('change', onOpenrouterModelSortChange);
6429 $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);6617 $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);
6618 $('#chutes_sort_models').on('change', onChutesModelSortChange);
6430 $('#electronhub_group_models').on('change', onElectronHubModelSortChange);6619 $('#electronhub_group_models').on('change', onElectronHubModelSortChange);
6431 $('#electronhub_sort_models').on('change', onElectronHubModelSortChange);6620 $('#electronhub_sort_models').on('change', onElectronHubModelSortChange);
6432 $('#model_ai21_select').on('change', onModelChange);6621 $('#model_ai21_select').on('change', onModelChange);
@@ -6434,6 +6623,7 @@ export function initOpenAI() {
6434 $('#model_cohere_select').on('change', onModelChange);6623 $('#model_cohere_select').on('change', onModelChange);
6435 $('#model_perplexity_select').on('change', onModelChange);6624 $('#model_perplexity_select').on('change', onModelChange);
6436 $('#model_groq_select').on('change', onModelChange);6625 $('#model_groq_select').on('change', onModelChange);
6626 $('#model_chutes_select').on('change', onModelChange);
6437 $('#model_siliconflow_select').on('change', onModelChange);6627 $('#model_siliconflow_select').on('change', onModelChange);
6438 $('#model_electronhub_select').on('change', onModelChange);6628 $('#model_electronhub_select').on('change', onModelChange);
6439 $('#model_nanogpt_select').on('change', onModelChange);6629 $('#model_nanogpt_select').on('change', onModelChange);
public/scripts/reasoning.js+1 -0
@@ -125,6 +125,7 @@ export function extractReasoningFromData(data, {
125 case chat_completion_sources.POLLINATIONS:125 case chat_completion_sources.POLLINATIONS:
126 case chat_completion_sources.MOONSHOT:126 case chat_completion_sources.MOONSHOT:
127 case chat_completion_sources.COMETAPI:127 case chat_completion_sources.COMETAPI:
128 case chat_completion_sources.CHUTES:
128 case chat_completion_sources.ELECTRONHUB:129 case chat_completion_sources.ELECTRONHUB:
129 case chat_completion_sources.NANOGPT:130 case chat_completion_sources.NANOGPT:
130 case chat_completion_sources.SILICONFLOW:131 case chat_completion_sources.SILICONFLOW:
public/scripts/secrets.js+3 -0
@@ -52,6 +52,7 @@ export const SECRET_KEYS = {
52 HUGGINGFACE: 'api_key_huggingface',52 HUGGINGFACE: 'api_key_huggingface',
53 STABILITY: 'api_key_stability',53 STABILITY: 'api_key_stability',
54 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',54 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
55 CHUTES: 'api_key_chutes',
55 ELECTRONHUB: 'api_key_electronhub',56 ELECTRONHUB: 'api_key_electronhub',
56 NANOGPT: 'api_key_nanogpt',57 NANOGPT: 'api_key_nanogpt',
57 TAVILY: 'api_key_tavily',58 TAVILY: 'api_key_tavily',
@@ -99,6 +100,7 @@ const FRIENDLY_NAMES = {
99 [SECRET_KEYS.GROQ]: 'Groq',100 [SECRET_KEYS.GROQ]: 'Groq',
100 [SECRET_KEYS.FEATHERLESS]: 'Featherless',101 [SECRET_KEYS.FEATHERLESS]: 'Featherless',
101 [SECRET_KEYS.HUGGINGFACE]: 'HuggingFace',102 [SECRET_KEYS.HUGGINGFACE]: 'HuggingFace',
103 [SECRET_KEYS.CHUTES]: 'Chutes',
102 [SECRET_KEYS.ELECTRONHUB]: 'Electron Hub',104 [SECRET_KEYS.ELECTRONHUB]: 'Electron Hub',
103 [SECRET_KEYS.NANOGPT]: 'NanoGPT',105 [SECRET_KEYS.NANOGPT]: 'NanoGPT',
104 [SECRET_KEYS.GENERIC]: 'Generic (OpenAI-compatible)',106 [SECRET_KEYS.GENERIC]: 'Generic (OpenAI-compatible)',
@@ -156,6 +158,7 @@ const INPUT_MAP = {
156 [SECRET_KEYS.GROQ]: '#api_key_groq',158 [SECRET_KEYS.GROQ]: '#api_key_groq',
157 [SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',159 [SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
158 [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',160 [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
161 [SECRET_KEYS.CHUTES]: '#api_key_chutes',
159 [SECRET_KEYS.ELECTRONHUB]: '#api_key_electronhub',162 [SECRET_KEYS.ELECTRONHUB]: '#api_key_electronhub',
160 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',163 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
161 [SECRET_KEYS.GENERIC]: '#api_key_generic',164 [SECRET_KEYS.GENERIC]: '#api_key_generic',
public/scripts/slash-commands.js+1 -0
@@ -4846,6 +4846,7 @@ function getModelOptions(quiet) {
4846 { id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },4846 { id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },
4847 { id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },4847 { id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },
4848 { id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },4848 { id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
4849 { id: 'model_chutes_select', api: 'openai', type: chat_completion_sources.CHUTES },
4849 { id: 'model_siliconflow_select', api: 'openai', type: chat_completion_sources.SILICONFLOW },4850 { id: 'model_siliconflow_select', api: 'openai', type: chat_completion_sources.SILICONFLOW },
4850 { id: 'model_electronhub_select', api: 'openai', type: chat_completion_sources.ELECTRONHUB },4851 { id: 'model_electronhub_select', api: 'openai', type: chat_completion_sources.ELECTRONHUB },
4851 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },4852 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
public/scripts/st-context.js+4 -0
@@ -63,6 +63,8 @@ import {
63 ensureMessageMediaIsArray,63 ensureMessageMediaIsArray,
64 getMediaDisplay,64 getMediaDisplay,
65 getMediaIndex,65 getMediaIndex,
66 scrollChatToBottom,
67 scrollOnMediaLoad,
66} from '../script.js';68} from '../script.js';
67import {69import {
68 extension_settings,70 extension_settings,
@@ -217,6 +219,8 @@ export function getContext() {
217 ensureMessageMediaIsArray,219 ensureMessageMediaIsArray,
218 getMediaDisplay,220 getMediaDisplay,
219 getMediaIndex,221 getMediaIndex,
222 scrollChatToBottom,
223 scrollOnMediaLoad,
220 swipe: {224 swipe: {
221 left: swipe_left,225 left: swipe_left,
222 right: swipe_right,226 right: swipe_right,
public/scripts/tokenizers.js+21 -0
@@ -699,6 +699,26 @@ export function getTokenizerModel() {
699 }699 }
700 }700 }
701701
702 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES && oai_settings.chutes_model) {
703 const model = oai_settings.chutes_model.toLowerCase();
704
705 if (model.includes('deepseek') || model.includes('mai-ds')) {
706 return deepseekTokenizer;
707 } else if (model.includes('qwen') || model.includes('qwq') || model.includes('tongyi') || model.includes('kimi')) {
708 return qwen2Tokenizer;
709 } else if (model.includes('llama') || model.includes('longcat') || model.includes('hermes')) {
710 return llama3Tokenizer;
711 } else if (model.includes('gemma')) {
712 return gemmaTokenizer;
713 } else if (model.includes('nemo')) {
714 return nemoTokenizer;
715 } else if (model.includes('mistral')) {
716 return mistralTokenizer;
717 } else if (model.includes('gpt-oss')) {
718 return gpt4oTokenizer;
719 }
720 }
721
702 if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) {722 if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) {
703 if (oai_settings.cohere_model.includes('command-a')) {723 if (oai_settings.cohere_model.includes('command-a')) {
704 return commandATokenizer;724 return commandATokenizer;
@@ -1206,3 +1226,4 @@ export async function initTokenizers() {
1206 await loadTokenCache();1226 await loadTokenCache();
1207 registerDebugFunction('resetTokenCache', 'Reset token cache', 'Purges the calculated token counts. Use this if you want to force a full re-tokenization of all chats or suspect the token counts are wrong.', resetTokenCache);1227 registerDebugFunction('resetTokenCache', 'Reset token cache', 'Purges the calculated token counts. Use this if you want to force a full re-tokenization of all chats or suspect the token counts are wrong.', resetTokenCache);
1208}1228}
1229
public/scripts/tool-calling.js+14 -2
@@ -559,6 +559,10 @@ export class ToolManager {
559 const targetValue = target[key];559 const targetValue = target[key];
560560
561 if (deltaValue === null || deltaValue === undefined) {561 if (deltaValue === null || deltaValue === undefined) {
562 // Don't reset the value if it already exists
563 if (targetValue) {
564 continue;
565 }
562 target[key] = deltaValue;566 target[key] = deltaValue;
563 continue;567 continue;
564 }568 }
@@ -634,10 +638,17 @@ export class ToolManager {
634 }638 }
635 }639 }
636640
641 if (oai_settings.chat_completion_source === chat_completion_sources.CHUTES && Array.isArray(model_list)) {
642 const currentModel = model_list.find(model => model.id === oai_settings.chutes_model);
643 if (currentModel) {
644 return currentModel.supported_features?.includes('tools');
645 }
646 }
647
637 if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB && Array.isArray(model_list)) {648 if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB && Array.isArray(model_list)) {
638 const currentModel = model_list.find(model => model.id === oai_settings.electronhub_model);649 const currentModel = model_list.find(model => model.id === oai_settings.electronhub_model);
639 if (currentModel && currentModel.metadata?.function_call) {650 if (currentModel) {
640 return currentModel.metadata.function_call;651 return currentModel.metadata?.function_call;
641 }652 }
642 }653 }
643654
@@ -659,6 +670,7 @@ export class ToolManager {
659 chat_completion_sources.MOONSHOT,670 chat_completion_sources.MOONSHOT,
660 chat_completion_sources.FIREWORKS,671 chat_completion_sources.FIREWORKS,
661 chat_completion_sources.COMETAPI,672 chat_completion_sources.COMETAPI,
673 chat_completion_sources.CHUTES,
662 chat_completion_sources.ELECTRONHUB,674 chat_completion_sources.ELECTRONHUB,
663 chat_completion_sources.AZURE_OPENAI,675 chat_completion_sources.AZURE_OPENAI,
664 chat_completion_sources.ZAI,676 chat_completion_sources.ZAI,
src/constants.js+1 -0
@@ -196,6 +196,7 @@ export const CHAT_COMPLETION_SOURCES = {
196 COHERE: 'cohere',196 COHERE: 'cohere',
197 PERPLEXITY: 'perplexity',197 PERPLEXITY: 'perplexity',
198 GROQ: 'groq',198 GROQ: 'groq',
199 CHUTES: 'chutes',
199 ELECTRONHUB: 'electronhub',200 ELECTRONHUB: 'electronhub',
200 NANOGPT: 'nanogpt',201 NANOGPT: 'nanogpt',
201 DEEPSEEK: 'deepseek',202 DEEPSEEK: 'deepseek',
src/endpoints/backends/chat-completions.js+156 -2
@@ -68,6 +68,7 @@ const API_GROQ = 'https://api.groq.com/openai/v1';
68const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';68const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
69const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com';69const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com';
70const API_AI21 = 'https://api.ai21.com/studio/v1';70const API_AI21 = 'https://api.ai21.com/studio/v1';
71const API_CHUTES = 'https://llm.chutes.ai/v1';
71const API_ELECTRONHUB = 'https://api.electronhub.ai/v1';72const API_ELECTRONHUB = 'https://api.electronhub.ai/v1';
72const API_NANOGPT = 'https://nano-gpt.com/api/v1';73const API_NANOGPT = 'https://nano-gpt.com/api/v1';
73const API_DEEPSEEK = 'https://api.deepseek.com/beta';74const API_DEEPSEEK = 'https://api.deepseek.com/beta';
@@ -1355,6 +1356,108 @@ async function sendElectronHubRequest(request, response) {
1355}1356}
13561357
1357/**1358/**
1359 * Sends a request to Chutes.
1360 * @param {express.Request} request Express request
1361 * @param {express.Response} response Express response
1362 */
1363async function sendChutesRequest(request, response) {
1364 const apiUrl = API_CHUTES;
1365 const apiKey = readSecret(request.user.directories, SECRET_KEYS.CHUTES);
1366
1367 if (!apiKey) {
1368 console.warn('Chutes key is missing.');
1369 return response.status(400).send({ error: true });
1370 }
1371
1372 const controller = new AbortController();
1373 request.socket.removeAllListeners('close');
1374 request.socket.on('close', function () {
1375 controller.abort();
1376 });
1377
1378 try {
1379 let bodyParams = {};
1380
1381 if (Array.isArray(request.body.tools) && request.body.tools.length > 0) {
1382 bodyParams['tools'] = request.body.tools;
1383 bodyParams['tool_choice'] = request.body.tool_choice;
1384 }
1385
1386 if (request.body.logprobs > 0) {
1387 bodyParams['top_logprobs'] = request.body.logprobs;
1388 bodyParams['logprobs'] = true;
1389 }
1390
1391 if (request.body.json_schema) {
1392 bodyParams['response_format'] = {
1393 type: 'json_schema',
1394 json_schema: {
1395 name: request.body.json_schema.name,
1396 description: request.body.json_schema.description,
1397 schema: request.body.json_schema.value,
1398 strict: request.body.json_schema.strict ?? true,
1399 },
1400 };
1401 }
1402
1403 const requestBody = {
1404 'messages': request.body.messages,
1405 'model': request.body.model,
1406 'temperature': request.body.temperature,
1407 'max_tokens': request.body.max_tokens,
1408 'stream': request.body.stream,
1409 'presence_penalty': request.body.presence_penalty,
1410 'frequency_penalty': request.body.frequency_penalty,
1411 'repetition_penalty': request.body.repetition_penalty,
1412 'min_p': request.body.min_p,
1413 'top_p': request.body.top_p,
1414 'top_k': request.body.top_k,
1415 'seed': request.body.seed,
1416 'stop': request.body.stop,
1417 'reasoning_effort': request.body.reasoning_effort,
1418 'logit_bias': request.body.logit_bias,
1419 ...bodyParams,
1420 };
1421
1422 const config = {
1423 method: 'POST',
1424 headers: {
1425 'Content-Type': 'application/json',
1426 'Authorization': 'Bearer ' + apiKey,
1427 },
1428 body: JSON.stringify(requestBody),
1429 signal: controller.signal,
1430 };
1431
1432 console.debug('Chutes request:', requestBody);
1433
1434 const generateResponse = await fetch(apiUrl + '/chat/completions', config);
1435
1436 if (request.body.stream) {
1437 forwardFetchResponse(generateResponse, response);
1438 } else {
1439 if (!generateResponse.ok) {
1440 const errorText = await generateResponse.text();
1441 console.warn('Chutes returned error: ', errorText);
1442 const errorJson = tryParse(errorText) ?? { error: true };
1443 return response.status(500).send(errorJson);
1444 }
1445 const generateResponseJson = await generateResponse.json();
1446 console.debug('Chutes response:', generateResponseJson);
1447 return response.send(generateResponseJson);
1448 }
1449 }
1450 catch (error) {
1451 console.error('Error communicating with Chutes: ', error);
1452 if (!response.headersSent) {
1453 response.send({ error: true });
1454 } else {
1455 response.end();
1456 }
1457 }
1458}
1459
1460/**
1358 * Sends a chat completion request to Azure OpenAI.1461 * Sends a chat completion request to Azure OpenAI.
1359 * @param {express.Request} request Express request object (contains request.body with all generate_data)1462 * @param {express.Request} request Express request object (contains request.body with all generate_data)
1360 * @param {express.Response} response Express response object1463 * @param {express.Response} response Express response object
@@ -1480,6 +1583,10 @@ router.post('/status', async function (request, statusResponse) {
1480 apiUrl = API_COHERE_V1;1583 apiUrl = API_COHERE_V1;
1481 apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE);1584 apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE);
1482 headers = {};1585 headers = {};
1586 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CHUTES) {
1587 apiUrl = API_CHUTES;
1588 apiKey = readSecret(request.user.directories, SECRET_KEYS.CHUTES);
1589 headers = {};
1483 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ELECTRONHUB) {1590 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ELECTRONHUB) {
1484 apiUrl = API_ELECTRONHUB;1591 apiUrl = API_ELECTRONHUB;
1485 apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);1592 apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
@@ -1671,6 +1778,22 @@ router.post('/status', async function (request, statusResponse) {
1671 data = { data: data.map(model => ({ id: model.name, ...model })) };1778 data = { data: data.map(model => ({ id: model.name, ...model })) };
1672 }1779 }
16731780
1781 if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CHUTES && Array.isArray(data?.data)) {
1782 data.data = data.data.map(model => {
1783 if (model.pricing?.prompt !== undefined && model.pricing?.completion !== undefined) {
1784 return {
1785 ...model,
1786 pricing: {
1787 ...model.pricing,
1788 input: model.pricing.prompt,
1789 output: model.pricing.completion,
1790 },
1791 };
1792 }
1793 return model;
1794 });
1795 }
1796
1674 statusResponse.send(data);1797 statusResponse.send(data);
16751798
1676 if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE && Array.isArray(data?.models)) {1799 if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE && Array.isArray(data?.models)) {
@@ -1831,6 +1954,7 @@ router.post('/generate', function (request, response) {
1831 case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);1954 case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);
1832 case CHAT_COMPLETION_SOURCES.AIMLAPI: return sendAimlapiRequest(request, response);1955 case CHAT_COMPLETION_SOURCES.AIMLAPI: return sendAimlapiRequest(request, response);
1833 case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response);1956 case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response);
1957 case CHAT_COMPLETION_SOURCES.CHUTES: return sendChutesRequest(request, response);
1834 case CHAT_COMPLETION_SOURCES.ELECTRONHUB: return sendElectronHubRequest(request, response);1958 case CHAT_COMPLETION_SOURCES.ELECTRONHUB: return sendElectronHubRequest(request, response);
1835 case CHAT_COMPLETION_SOURCES.AZURE_OPENAI: return sendAzureOpenAIRequest(request, response);1959 case CHAT_COMPLETION_SOURCES.AZURE_OPENAI: return sendAzureOpenAIRequest(request, response);
1836 }1960 }
@@ -2017,8 +2141,7 @@ router.post('/generate', function (request, response) {
2017 'ttl': cacheTTL,2141 'ttl': cacheTTL,
2018 };2142 };
2019 }2143 }
2020 }2144 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) {
2021 else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) {
2022 apiUrl = API_POLLINATIONS;2145 apiUrl = API_POLLINATIONS;
2023 apiKey = 'NONE';2146 apiKey = 'NONE';
2024 headers = {2147 headers = {
@@ -2314,6 +2437,37 @@ multimodalModels.post('/electronhub', async (_req, res) => {
2314 }2437 }
2315});2438});
23162439
2440multimodalModels.post('/chutes', async (req, res) => {
2441 try {
2442 const key = readSecret(req.user.directories, SECRET_KEYS.CHUTES);
2443
2444 if (!key) {
2445 return res.json([]);
2446 }
2447
2448 const response = await fetch('https://llm.chutes.ai/v1/models', {
2449 headers: {
2450 'Authorization': `Bearer ${key}`,
2451 },
2452 });
2453
2454 if (!response.ok) {
2455 return res.json([]);
2456 }
2457
2458 const data = await response.json();
2459
2460 const modelsData = /** @type {{object: string, data: Array<{id: string, input_modalities?: string[]}>}} */ (data);
2461 const multimodalModels = modelsData.data
2462 .filter(m => m.input_modalities?.includes('image'))
2463 .map(m => m.id);
2464 return res.json(multimodalModels);
2465 } catch (error) {
2466 console.error(error);
2467 return res.sendStatus(500);
2468 }
2469});
2470
2317multimodalModels.post('/mistral', async (req, res) => {2471multimodalModels.post('/mistral', async (req, res) => {
2318 try {2472 try {
2319 const key = readSecret(req.user.directories, SECRET_KEYS.MISTRALAI);2473 const key = readSecret(req.user.directories, SECRET_KEYS.MISTRALAI);
src/endpoints/openai.js+86 -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 === 'chutes') {
85 key = readSecret(request.user.directories, SECRET_KEYS.CHUTES);
86 }
87
84 if (request.body.api === 'electronhub') {88 if (request.body.api === 'electronhub') {
85 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);89 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
86 }90 }
@@ -177,6 +181,10 @@ router.post('/caption-image', async (request, response) => {
177 apiUrl = 'https://nano-gpt.com/api/v1/chat/completions';181 apiUrl = 'https://nano-gpt.com/api/v1/chat/completions';
178 }182 }
179183
184 if (request.body.api === 'chutes') {
185 apiUrl = 'https://llm.chutes.ai/v1/chat/completions';
186 }
187
180 if (request.body.api === 'electronhub') {188 if (request.body.api === 'electronhub') {
181 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';189 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';
182 }190 }
@@ -431,6 +439,84 @@ router.post('/electronhub/models', async (request, response) => {
431 }439 }
432});440});
433441
442// Chutes TTS
443router.post('/chutes/generate-voice', async (request, response) => {
444 try {
445 const key = readSecret(request.user.directories, SECRET_KEYS.CHUTES);
446
447 if (!key) {
448 console.warn('No Chutes key found');
449 return response.sendStatus(400);
450 }
451
452 const requestBody = {
453 text: request.body.input,
454 voice: request.body.voice || 'af_heart',
455 };
456
457 console.debug('Chutes TTS request', requestBody);
458
459 const result = await fetch('https://chutes-kokoro.chutes.ai/speak', {
460 method: 'POST',
461 headers: {
462 'Content-Type': 'application/json',
463 Authorization: `Bearer ${key}`,
464 },
465 body: JSON.stringify(requestBody),
466 });
467
468 if (!result.ok) {
469 const text = await result.text();
470 console.warn('Chutes TTS request failed', result.statusText, text);
471 return response.status(500).send(text);
472 }
473
474 const contentType = result.headers.get('content-type') || 'audio/mpeg';
475 const buffer = await result.arrayBuffer();
476 response.setHeader('Content-Type', contentType);
477 return response.send(Buffer.from(buffer));
478 } catch (error) {
479 console.error('Chutes TTS generation failed', error);
480 response.status(500).send('Internal server error');
481 }
482});
483
484router.post('/chutes/models/embedding', async (request, response) => {
485 try {
486 const key = readSecret(request.user.directories, SECRET_KEYS.CHUTES);
487
488 if (!key) {
489 console.warn('No Chutes key found');
490 return response.sendStatus(400);
491 }
492
493 const result = await fetch('https://api.chutes.ai/chutes/?template=embedding&include_public=true&limit=999', {
494 method: 'GET',
495 headers: {
496 Authorization: `Bearer ${key}`,
497 },
498 });
499
500 if (!result.ok) {
501 const text = await result.text();
502 console.warn('Chutes embedding models request failed', result.statusText, text);
503 return response.status(500).send(text);
504 }
505
506 /** @type {any} */
507 const data = await result.json();
508
509 if (!Array.isArray(data?.items)) {
510 console.warn('Chutes embedding models response invalid', data);
511 return response.sendStatus(500);
512 }
513 return response.json(data.items);
514 } catch (error) {
515 console.error('Chutes embedding models fetch failed', error);
516 response.sendStatus(500);
517 }
518});
519
434router.post('/generate-image', async (request, response) => {520router.post('/generate-image', async (request, response) => {
435 try {521 try {
436 const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);522 const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);
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 CHUTES: 'api_key_chutes',
48 ELECTRONHUB: 'api_key_electronhub',49 ELECTRONHUB: 'api_key_electronhub',
49 NANOGPT: 'api_key_nanogpt',50 NANOGPT: 'api_key_nanogpt',
50 BFL: 'api_key_bfl',51 BFL: 'api_key_bfl',
src/endpoints/stable-diffusion.js+84 -0
@@ -1083,6 +1083,89 @@ electronhub.post('/sizes', async (request, response) => {
1083 return response.send({ sizes });1083 return response.send({ sizes });
1084});1084});
10851085
1086const chutes = express.Router();
1087
1088chutes.post('/models', async (request, response) => {
1089 try {
1090 const key = readSecret(request.user.directories, SECRET_KEYS.CHUTES);
1091
1092 if (!key) {
1093 console.warn('Chutes key not found.');
1094 return response.sendStatus(400);
1095 }
1096
1097 const modelsResponse = await fetch('https://api.chutes.ai/chutes/?template=diffusion&include_public=true&limit=999', {
1098 method: 'GET',
1099 headers: {
1100 'Authorization': `Bearer ${key}`,
1101 'Content-Type': 'application/json',
1102 },
1103 });
1104
1105 if (!modelsResponse.ok) {
1106 console.warn('Chutes returned an error.');
1107 return response.sendStatus(500);
1108 }
1109
1110 const data = await modelsResponse.json();
1111
1112 const chutesData = /** @type {{items: Array<{name: string}>}} */ (data);
1113 const models = chutesData.items.map(x => ({ value: x.name, text: x.name })).sort((a, b) => a?.text?.localeCompare(b?.text));
1114 return response.send(models);
1115 }
1116 catch (error) {
1117 console.error(error);
1118 return response.sendStatus(500);
1119 }
1120});
1121
1122chutes.post('/generate', async (request, response) => {
1123 try {
1124 const key = readSecret(request.user.directories, SECRET_KEYS.CHUTES);
1125
1126 if (!key) {
1127 console.warn('Chutes key not found.');
1128 return response.sendStatus(400);
1129 }
1130
1131 const bodyParams = {
1132 model: request.body.model,
1133 prompt: request.body.prompt,
1134 negative_prompt: request.body.negative_prompt,
1135 guidance_scale: request.body.guidance_scale || 7.0,
1136 width: request.body.width || 1024,
1137 height: request.body.height || 1024,
1138 num_inference_steps: request.body.steps || 10,
1139 };
1140
1141 console.debug('Chutes request:', bodyParams);
1142
1143 const result = await fetch('https://image.chutes.ai/generate', {
1144 method: 'POST',
1145 headers: {
1146 'Authorization': `Bearer ${key}`,
1147 'Content-Type': 'application/json',
1148 },
1149 body: JSON.stringify(bodyParams),
1150 });
1151
1152 if (!result.ok) {
1153 const text = await result.text();
1154 console.warn('Chutes returned an error:', text);
1155 return response.sendStatus(500);
1156 }
1157
1158 const buffer = await result.arrayBuffer();
1159 const base64 = Buffer.from(buffer).toString('base64');
1160
1161 return response.send({ image: base64 });
1162 }
1163 catch (error) {
1164 console.error(error);
1165 return response.sendStatus(500);
1166 }
1167});
1168
1086const nanogpt = express.Router();1169const nanogpt = express.Router();
10871170
1088nanogpt.post('/models', async (request, response) => {1171nanogpt.post('/models', async (request, response) => {
@@ -1566,6 +1649,7 @@ router.use('/drawthings', drawthings);
1566router.use('/pollinations', pollinations);1649router.use('/pollinations', pollinations);
1567router.use('/stability', stability);1650router.use('/stability', stability);
1568router.use('/huggingface', huggingface);1651router.use('/huggingface', huggingface);
1652router.use('/chutes', chutes);
1569router.use('/electronhub', electronhub);1653router.use('/electronhub', electronhub);
1570router.use('/nanogpt', nanogpt);1654router.use('/nanogpt', nanogpt);
1571router.use('/bfl', bfl);1655router.use('/bfl', bfl);
src/endpoints/vectors.js+10 -0
@@ -36,6 +36,7 @@ const SOURCES = [
36 'vertexai',36 'vertexai',
37 'electronhub',37 'electronhub',
38 'openrouter',38 'openrouter',
39 'chutes',
39];40];
4041
41/**42/**
@@ -79,6 +80,8 @@ async function getVector(source, sourceSettings, text, isQuery, directories) {
79 return sourceSettings.embeddings[text];80 return sourceSettings.embeddings[text];
80 case 'koboldcpp':81 case 'koboldcpp':
81 return sourceSettings.embeddings[text];82 return sourceSettings.embeddings[text];
83 case 'chutes':
84 return getOpenAIVector(text, source, directories, sourceSettings.model);
82 }85 }
8386
84 throw new Error(`Unknown vector source ${source}`);87 throw new Error(`Unknown vector source ${source}`);
@@ -144,6 +147,9 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie
144 case 'koboldcpp':147 case 'koboldcpp':
145 results.push(...texts.map(x => sourceSettings.embeddings[x]));148 results.push(...texts.map(x => sourceSettings.embeddings[x]));
146 break;149 break;
150 case 'chutes':
151 results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model));
152 break;
147 default:153 default:
148 throw new Error(`Unknown vector source ${source}`);154 throw new Error(`Unknown vector source ${source}`);
149 }155 }
@@ -228,6 +234,10 @@ function getSourceSettings(source, request) {
228 model: String(request.body.model),234 model: String(request.body.model),
229 embeddings: request.body.embeddings ?? {},235 embeddings: request.body.embeddings ?? {},
230 };236 };
237 case 'chutes':
238 return {
239 model: String(request.body.model || 'chutes-qwen-qwen3-embedding-8b'),
240 };
231 default:241 default:
232 return {};242 return {};
233 }243 }
src/vectors/openai-vectors.js+26 -5
@@ -8,30 +8,44 @@ const SOURCES = {
8 url: 'https://api.together.xyz/v1',8 url: 'https://api.together.xyz/v1',
9 model: 'togethercomputer/m2-bert-80M-32k-retrieval',9 model: 'togethercomputer/m2-bert-80M-32k-retrieval',
10 headers: {},10 headers: {},
11 processBody: () => {},
11 },12 },
12 'mistral': {13 'mistral': {
13 secretKey: SECRET_KEYS.MISTRALAI,14 secretKey: SECRET_KEYS.MISTRALAI,
14 url: 'https://api.mistral.ai/v1',15 url: 'https://api.mistral.ai/v1',
15 model: 'mistral-embed',16 model: 'mistral-embed',
16 headers: {},17 headers: {},
18 processBody: () => {},
17 },19 },
18 'openai': {20 'openai': {
19 secretKey: SECRET_KEYS.OPENAI,21 secretKey: SECRET_KEYS.OPENAI,
20 url: 'https://api.openai.com/v1',22 url: 'https://api.openai.com/v1',
21 model: 'text-embedding-ada-002',23 model: 'text-embedding-ada-002',
22 headers: {},24 headers: {},
25 processBody: () => {},
23 },26 },
24 'electronhub': {27 'electronhub': {
25 secretKey: SECRET_KEYS.ELECTRONHUB,28 secretKey: SECRET_KEYS.ELECTRONHUB,
26 url: 'https://api.electronhub.ai/v1',29 url: 'https://api.electronhub.ai/v1',
27 model: 'text-embedding-3-small',30 model: 'text-embedding-3-small',
28 headers: {},31 headers: {},
32 processBody: () => {},
29 },33 },
30 'openrouter': {34 'openrouter': {
31 secretKey: SECRET_KEYS.OPENROUTER,35 secretKey: SECRET_KEYS.OPENROUTER,
32 url: 'https://openrouter.ai/api/v1',36 url: 'https://openrouter.ai/api/v1',
33 model: 'openai/text-embedding-3-large',37 model: 'openai/text-embedding-3-large',
34 headers: { ...OPENROUTER_HEADERS },38 headers: { ...OPENROUTER_HEADERS },
39 processBody: () => {},
40 },
41 'chutes': {
42 secretKey: SECRET_KEYS.CHUTES,
43 url: 'https://{{MODEL}}.chutes.ai/v1',
44 model: 'chutes-qwen-qwen3-embedding-8b',
45 headers: {},
46 processBody: (body) => {
47 body.model = null;
48 },
35 },49 },
36};50};
3751
@@ -58,7 +72,17 @@ export async function getOpenAIBatchVector(texts, source, directories, model = '
58 throw new Error('No API key found');72 throw new Error('No API key found');
59 }73 }
6074
61 const url = config.url;75 const modelName = model || config.model;
76 const url = config.url.replace('{{MODEL}}', modelName);
77 const body = {
78 input: texts,
79 model: modelName,
80 };
81
82 if (typeof config.processBody === 'function') {
83 config.processBody(body);
84 }
85
62 const response = await fetch(`${url}/embeddings`, {86 const response = await fetch(`${url}/embeddings`, {
63 method: 'POST',87 method: 'POST',
64 headers: {88 headers: {
@@ -66,10 +90,7 @@ export async function getOpenAIBatchVector(texts, source, directories, model = '
66 'Authorization': `Bearer ${key}`,90 'Authorization': `Bearer ${key}`,
67 ...config.headers,91 ...config.headers,
68 },92 },
69 body: JSON.stringify({93 body: JSON.stringify(body),
70 input: texts,
71 model: model || config.model,
72 }),
73 });94 });
7495
75 if (!response.ok) {96 if (!response.ok) {