Merge pull request #3839 from SillyTavern/gork-ai xAI grok

4dfc33b2a071c29461e29784ebdb4ca8787ee55a

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
19 files changed, +343 -15Ignore whitespace
public/img/xai.svg+46 -0
@@ -0,0 +1,46 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
4<svg
5 version="1.1"
6 id="katman_1"
7 x="0px"
8 y="0px"
9 viewBox="0 0 438.67001 481.44999"
10 xml:space="preserve"
11 sodipodi:docname="XAI_Logo.svg"
12 width="438.67001"
13 height="481.45001"
14 inkscape:version="1.3 (0e150ed, 2023-07-21)"
15 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
16 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
17 xmlns="http://www.w3.org/2000/svg"
18 xmlns:svg="http://www.w3.org/2000/svg"><defs
19 id="defs4" /><sodipodi:namedview
20 id="namedview4"
21 pagecolor="#ffffff"
22 bordercolor="#000000"
23 borderopacity="0.25"
24 inkscape:showpageshadow="2"
25 inkscape:pageopacity="0.0"
26 inkscape:pagecheckerboard="0"
27 inkscape:deskcolor="#d1d1d1"
28 inkscape:zoom="0.39645207"
29 inkscape:cx="219.44645"
30 inkscape:cy="238.36425"
31 inkscape:window-width="1512"
32 inkscape:window-height="856"
33 inkscape:window-x="0"
34 inkscape:window-y="38"
35 inkscape:window-maximized="1"
36 inkscape:current-layer="katman_1" />&#10;<g
37 id="g4"
38 transform="translate(-201.61,-56.91)">&#10; <polygon
39 points="631.96,538.36 640.28,93.18 557.09,211.99 565.4,538.36 "
40 id="polygon1" />&#10; <polygon
41 points="379.35,284.53 430.13,357.05 640.28,56.91 538.72,56.91 "
42 id="polygon2" />&#10; <polygon
43 points="353.96,465.84 303.17,393.31 201.61,538.36 303.17,538.36 "
44 id="polygon3" />&#10; <polygon
45 points="531.69,538.36 303.17,211.99 201.61,211.99 430.13,538.36 "
46 id="polygon4" />&#10;</g>&#10;</svg>
public/index.html+36 -10
@@ -685,7 +685,7 @@
685 </span>685 </span>
686 </div>686 </div>
687 </div>687 </div>
688 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">688 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
689 <div class="range-block-title" data-i18n="Temperature">689 <div class="range-block-title" data-i18n="Temperature">
690 Temperature690 Temperature
691 </div>691 </div>
@@ -698,7 +698,7 @@
698 </div>698 </div>
699 </div>699 </div>
700 </div>700 </div>
701 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">701 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
702 <div class="range-block-title" data-i18n="Frequency Penalty">702 <div class="range-block-title" data-i18n="Frequency Penalty">
703 Frequency Penalty703 Frequency Penalty
704 </div>704 </div>
@@ -711,7 +711,7 @@
711 </div>711 </div>
712 </div>712 </div>
713 </div>713 </div>
714 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">714 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
715 <div class="range-block-title" data-i18n="Presence Penalty">715 <div class="range-block-title" data-i18n="Presence Penalty">
716 Presence Penalty716 Presence Penalty
717 </div>717 </div>
@@ -737,7 +737,7 @@
737 </div>737 </div>
738 </div>738 </div>
739 </div>739 </div>
740 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">740 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
741 <div class="range-block-title" data-i18n="Top P">741 <div class="range-block-title" data-i18n="Top P">
742 Top P742 Top P
743 </div>743 </div>
@@ -974,7 +974,7 @@
974 </div>974 </div>
975 </div>975 </div>
976 </div>976 </div>
977 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt">977 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai">
978 <div class="range-block-title justifyLeft" data-i18n="Seed">978 <div class="range-block-title justifyLeft" data-i18n="Seed">
979 Seed979 Seed
980 </div>980 </div>
@@ -1965,7 +1965,7 @@
1965 </span>1965 </span>
1966 </div>1966 </div>
1967 </div>1967 </div>
1968 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21">1968 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai">
1969 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">1969 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
1970 <input id="openai_function_calling" type="checkbox" />1970 <input id="openai_function_calling" type="checkbox" />
1971 <span data-i18n="Enable function calling">Enable function calling</span>1971 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1975,7 +1975,7 @@
1975 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>1975 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
1976 </div>1976 </div>
1977 </div>1977 </div>
1978 <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai">1978 <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai">
1979 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">1979 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
1980 <input id="openai_image_inlining" type="checkbox" />1980 <input id="openai_image_inlining" type="checkbox" />
1981 <span data-i18n="Send inline images">Send inline images</span>1981 <span data-i18n="Send inline images">Send inline images</span>
@@ -1987,7 +1987,7 @@
1987 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>1987 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
1988 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>1988 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
1989 </div>1989 </div>
1990 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom">1990 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai">
1991 <div class="flex-container oneline-dropdown">1991 <div class="flex-container oneline-dropdown">
1992 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">1992 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
1993 Inline Image Quality1993 Inline Image Quality
@@ -2031,7 +2031,7 @@
2031 </span>2031 </span>
2032 </div>2032 </div>
2033 </div>2033 </div>
2034 <div class="range-block" data-source="deepseek,openrouter,custom,claude">2034 <div class="range-block" data-source="deepseek,openrouter,custom,claude,xai">
2035 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">2035 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
2036 <input id="openai_show_thoughts" type="checkbox" />2036 <input id="openai_show_thoughts" type="checkbox" />
2037 <span>2037 <span>
@@ -2045,7 +2045,7 @@
2045 </span>2045 </span>
2046 </div>2046 </div>
2047 </div>2047 </div>
2048 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude">2048 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai">
2049 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Currently supported values are low, medium, and high.&#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.">2049 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Currently supported values are low, medium, and high.&#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.">
2050 <label for="openai_reasoning_effort">2050 <label for="openai_reasoning_effort">
2051 <span data-i18n="Reasoning Effort">Reasoning Effort</span>2051 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2756,6 +2756,7 @@
2756 <option value="perplexity">Perplexity</option>2756 <option value="perplexity">Perplexity</option>
2757 <option value="scale">Scale</option>2757 <option value="scale">Scale</option>
2758 <option value="windowai">Window AI</option>2758 <option value="windowai">Window AI</option>
2759 <option value="xai">xAI (Grok)</option>
2759 </optgroup>2760 </optgroup>
2760 </select>2761 </select>
2761 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek">2762 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek">
@@ -3424,6 +3425,31 @@
3424 <select id="model_01ai_select">3425 <select id="model_01ai_select">
3425 </select>3426 </select>
3426 </div>3427 </div>
3428 <div id="xai_form" data-source="xai">
3429 <h4>
3430 <a data-i18n="xAI API Key" href="https://console.x.ai/" target="_blank" rel="noopener noreferrer">
3431 xAI API Key
3432 </a>
3433 </h4>
3434 <div class="flex-container">
3435 <input id="api_key_xai" name="api_key_xai" class="text_pole flex1" value="" type="text" autocomplete="off">
3436 <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_xai"></div>
3437 </div>
3438 <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
3439 For privacy reasons, your API key will be hidden after you reload the page.
3440 </div>
3441 <h4 data-i18n="xAI Model">xAI Model</h4>
3442 <select id="model_xai_select">
3443 <option value="grok-3-beta">grok-3-beta</option>
3444 <option value="grok-3-fast-beta">grok-3-fast-beta</option>
3445 <option value="grok-3-mini-beta">grok-3-mini-beta</option>
3446 <option value="grok-3-mini-fast-beta">grok-3-mini-fast-beta</option>
3447 <option value="grok-2-vision-1212">grok-2-vision-1212</option>
3448 <option value="grok-2-1212">grok-2-1212</option>
3449 <option value="grok-vision-beta">grok-vision-beta</option>
3450 <option value="grok-beta">grok-beta</option>
3451 </select>
3452 </div>
3427 <div id="prompt_post_porcessing_form" data-source="custom,openrouter">3453 <div id="prompt_post_porcessing_form" data-source="custom,openrouter">
3428 <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>3454 <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
3429 <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">3455 <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
public/scripts/RossAscends-mods.js+1 -0
@@ -409,6 +409,7 @@ function RA_autoconnect(PrevApi) {
409 || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)409 || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
410 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)410 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
411 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)411 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
412 || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
412 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)413 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
413 ) {414 ) {
414 $('#api_button_openai').trigger('click');415 $('#api_button_openai').trigger('click');
public/scripts/extensions/caption/index.js+1 -0
@@ -428,6 +428,7 @@ jQuery(async function () {
428 'zerooneai': SECRET_KEYS.ZEROONEAI,428 'zerooneai': SECRET_KEYS.ZEROONEAI,
429 'groq': SECRET_KEYS.GROQ,429 'groq': SECRET_KEYS.GROQ,
430 'cohere': SECRET_KEYS.COHERE,430 'cohere': SECRET_KEYS.COHERE,
431 'xai': SECRET_KEYS.XAI,
431 };432 };
432433
433 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {434 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+3 -0
@@ -31,6 +31,7 @@
31 <option value="openrouter">OpenRouter</option>31 <option value="openrouter">OpenRouter</option>
32 <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>32 <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
33 <option value="vllm">vLLM</option>33 <option value="vllm">vLLM</option>
34 <option value="xai">xAI (Grok)</option>
34 </select>35 </select>
35 </div>36 </div>
36 <div class="flex1 flex-container flexFlowColumn flexNoGap">37 <div class="flex1 flex-container flexFlowColumn flexNoGap">
@@ -134,6 +135,8 @@
134 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>135 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
135 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>136 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
136 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>137 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
138 <option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option>
139 <option data-type="xai" value="grok-vision-beta">grok-vision-beta</option>
137 </select>140 </select>
138 </div>141 </div>
139 <div data-type="ollama">142 <div data-type="ollama">
public/scripts/extensions/shared.js+4 -0
@@ -153,6 +153,10 @@ function throwIfInvalidModel(useReverseProxy) {
153 throw new Error('Cohere API key is not set.');153 throw new Error('Cohere API key is not set.');
154 }154 }
155155
156 if (extension_settings.caption.multimodal_api === 'xai' && !secret_state[SECRET_KEYS.XAI]) {
157 throw new Error('xAI API key is not set.');
158 }
159
156 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {160 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
157 throw new Error('Ollama server URL is not set.');161 throw new Error('Ollama server URL is not set.');
158 }162 }
public/scripts/extensions/stable-diffusion/index.js+55 -0
@@ -81,6 +81,7 @@ const sources = {
81 nanogpt: 'nanogpt',81 nanogpt: 'nanogpt',
82 bfl: 'bfl',82 bfl: 'bfl',
83 falai: 'falai',83 falai: 'falai',
84 xai: 'xai',
84};85};
8586
86const initiators = {87const initiators = {
@@ -1303,6 +1304,7 @@ async function onModelChange() {
1303 sources.nanogpt,1304 sources.nanogpt,
1304 sources.bfl,1305 sources.bfl,
1305 sources.falai,1306 sources.falai,
1307 sources.xai,
1306 ];1308 ];
13071309
1308 if (cloudSources.includes(extension_settings.sd.source)) {1310 if (cloudSources.includes(extension_settings.sd.source)) {
@@ -1518,6 +1520,9 @@ async function loadSamplers() {
1518 case sources.bfl:1520 case sources.bfl:
1519 samplers = ['N/A'];1521 samplers = ['N/A'];
1520 break;1522 break;
1523 case sources.xai:
1524 samplers = ['N/A'];
1525 break;
1521 }1526 }
15221527
1523 for (const sampler of samplers) {1528 for (const sampler of samplers) {
@@ -1708,6 +1713,9 @@ async function loadModels() {
1708 case sources.falai:1713 case sources.falai:
1709 models = await loadFalaiModels();1714 models = await loadFalaiModels();
1710 break;1715 break;
1716 case sources.xai:
1717 models = await loadXAIModels();
1718 break;
1711 }1719 }
17121720
1713 for (const model of models) {1721 for (const model of models) {
@@ -1760,6 +1768,12 @@ async function loadFalaiModels() {
1760 return [];1768 return [];
1761}1769}
17621770
1771async function loadXAIModels() {
1772 return [
1773 { value: 'grok-2-image-1212', text: 'grok-2-image-1212' },
1774 ];
1775}
1776
1763async function loadPollinationsModels() {1777async function loadPollinationsModels() {
1764 const result = await fetch('/api/sd/pollinations/models', {1778 const result = await fetch('/api/sd/pollinations/models', {
1765 method: 'POST',1779 method: 'POST',
@@ -2081,6 +2095,9 @@ async function loadSchedulers() {
2081 case sources.falai:2095 case sources.falai:
2082 schedulers = ['N/A'];2096 schedulers = ['N/A'];
2083 break;2097 break;
2098 case sources.xai:
2099 schedulers = ['N/A'];
2100 break;
2084 }2101 }
20852102
2086 for (const scheduler of schedulers) {2103 for (const scheduler of schedulers) {
@@ -2166,6 +2183,12 @@ async function loadVaes() {
2166 case sources.bfl:2183 case sources.bfl:
2167 vaes = ['N/A'];2184 vaes = ['N/A'];
2168 break;2185 break;
2186 case sources.falai:
2187 vaes = ['N/A'];
2188 break;
2189 case sources.xai:
2190 vaes = ['N/A'];
2191 break;
2169 }2192 }
21702193
2171 for (const vae of vaes) {2194 for (const vae of vaes) {
@@ -2735,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2735 case sources.falai:2758 case sources.falai:
2736 result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal);2759 result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal);
2737 break;2760 break;
2761 case sources.xai:
2762 result = await generateXAIImage(prefixedPrompt, negativePrompt, signal);
2763 break;
2738 }2764 }
27392765
2740 if (!result.data) {2766 if (!result.data) {
@@ -3464,6 +3490,33 @@ async function generateBflImage(prompt, signal) {
3464}3490}
34653491
3466/**3492/**
3493 * Generates an image using the xAI API.
3494 * @param {string} prompt The main instruction used to guide the image generation.
3495 * @param {string} _negativePrompt Negative prompt is not used in this API
3496 * @param {AbortSignal} signal An AbortSignal object that can be used to cancel the request.
3497 * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete.
3498 */
3499async function generateXAIImage(prompt, _negativePrompt, signal) {
3500 const result = await fetch('/api/sd/xai/generate', {
3501 method: 'POST',
3502 headers: getRequestHeaders(),
3503 signal: signal,
3504 body: JSON.stringify({
3505 prompt: prompt,
3506 model: extension_settings.sd.model,
3507 }),
3508 });
3509
3510 if (result.ok) {
3511 const data = await result.json();
3512 return { format: 'jpg', data: data.image };
3513 } else {
3514 const text = await result.text();
3515 throw new Error(text);
3516 }
3517}
3518
3519/**
3467 * Generates an image using the FAL.AI API.3520 * Generates an image using the FAL.AI API.
3468 * @param {string} prompt - The main instruction used to guide the image generation.3521 * @param {string} prompt - The main instruction used to guide the image generation.
3469 * @param {string} negativePrompt - The negative prompt used to guide the image generation.3522 * @param {string} negativePrompt - The negative prompt used to guide the image generation.
@@ -3782,6 +3835,8 @@ function isValidState() {
3782 return secret_state[SECRET_KEYS.BFL];3835 return secret_state[SECRET_KEYS.BFL];
3783 case sources.falai:3836 case sources.falai:
3784 return secret_state[SECRET_KEYS.FALAI];3837 return secret_state[SECRET_KEYS.FALAI];
3838 case sources.xai:
3839 return secret_state[SECRET_KEYS.XAI];
3785 }3840 }
3786}3841}
37873842
public/scripts/extensions/stable-diffusion/settings.html+1 -0
@@ -52,6 +52,7 @@
52 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>52 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
53 <option value="horde">Stable Horde</option>53 <option value="horde">Stable Horde</option>
54 <option value="togetherai">TogetherAI</option>54 <option value="togetherai">TogetherAI</option>
55 <option value="xai">xAI (Grok)</option>
55 </select>56 </select>
56 <div data-sd-source="auto">57 <div data-sd-source="auto">
57 <label for="sd_auto_url">SD Web UI URL</label>58 <label for="sd_auto_url">SD Web UI URL</label>
public/scripts/openai.js+71 -2
@@ -184,6 +184,7 @@ export const chat_completion_sources = {
184 ZEROONEAI: '01ai',184 ZEROONEAI: '01ai',
185 NANOGPT: 'nanogpt',185 NANOGPT: 'nanogpt',
186 DEEPSEEK: 'deepseek',186 DEEPSEEK: 'deepseek',
187 XAI: 'xai',
187};188};
188189
189const character_names_behavior = {190const character_names_behavior = {
@@ -257,6 +258,7 @@ export const settingsToUpdate = {
257 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],258 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],
258 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],259 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],
259 zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],260 zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
261 xai_model: ['#model_xai_select', 'xai_model', false],
260 custom_model: ['#custom_model_id', 'custom_model', false],262 custom_model: ['#custom_model_id', 'custom_model', false],
261 custom_url: ['#custom_api_url_text', 'custom_url', false],263 custom_url: ['#custom_api_url_text', 'custom_url', false],
262 custom_include_body: ['#custom_include_body', 'custom_include_body', false],264 custom_include_body: ['#custom_include_body', 'custom_include_body', false],
@@ -345,6 +347,7 @@ const default_settings = {
345 nanogpt_model: 'gpt-4o-mini',347 nanogpt_model: 'gpt-4o-mini',
346 zerooneai_model: 'yi-large',348 zerooneai_model: 'yi-large',
347 deepseek_model: 'deepseek-chat',349 deepseek_model: 'deepseek-chat',
350 xai_model: 'grok-3-beta',
348 custom_model: '',351 custom_model: '',
349 custom_url: '',352 custom_url: '',
350 custom_include_body: '',353 custom_include_body: '',
@@ -425,6 +428,7 @@ const oai_settings = {
425 nanogpt_model: 'gpt-4o-mini',428 nanogpt_model: 'gpt-4o-mini',
426 zerooneai_model: 'yi-large',429 zerooneai_model: 'yi-large',
427 deepseek_model: 'deepseek-chat',430 deepseek_model: 'deepseek-chat',
431 xai_model: 'grok-3-beta',
428 custom_model: '',432 custom_model: '',
429 custom_url: '',433 custom_url: '',
430 custom_include_body: '',434 custom_include_body: '',
@@ -1644,6 +1648,8 @@ export function getChatCompletionModel(source = null) {
1644 return oai_settings.nanogpt_model;1648 return oai_settings.nanogpt_model;
1645 case chat_completion_sources.DEEPSEEK:1649 case chat_completion_sources.DEEPSEEK:
1646 return oai_settings.deepseek_model;1650 return oai_settings.deepseek_model;
1651 case chat_completion_sources.XAI:
1652 return oai_settings.xai_model;
1647 default:1653 default:
1648 throw new Error(`Unknown chat completion source: ${activeSource}`);1654 throw new Error(`Unknown chat completion source: ${activeSource}`);
1649 }1655 }
@@ -1961,6 +1967,7 @@ async function sendOpenAIRequest(type, messages, signal) {
1961 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;1967 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
1962 const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;1968 const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
1963 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;1969 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
1970 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
1964 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);1971 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
1965 const isQuiet = type === 'quiet';1972 const isQuiet = type === 'quiet';
1966 const isImpersonate = type === 'impersonate';1973 const isImpersonate = type === 'impersonate';
@@ -2033,7 +2040,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2033 }2040 }
20342041
2035 // Add logprobs request (currently OpenAI only, max 5 on their side)2042 // Add logprobs request (currently OpenAI only, max 5 on their side)
2036 if (useLogprobs && (isOAI || isCustom || isDeepSeek)) {2043 if (useLogprobs && (isOAI || isCustom || isDeepSeek || isXAI)) {
2037 generate_data['logprobs'] = 5;2044 generate_data['logprobs'] = 5;
2038 }2045 }
20392046
@@ -2152,7 +2159,18 @@ async function sendOpenAIRequest(type, messages, signal) {
2152 }2159 }
2153 }2160 }
21542161
2155 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {2162 if (isXAI) {
2163 if (generate_data.model.includes('grok-3-mini')) {
2164 delete generate_data.presence_penalty;
2165 delete generate_data.frequency_penalty;
2166 }
2167 if (generate_data.model.includes('grok-vision')) {
2168 delete generate_data.tools;
2169 delete generate_data.tool_choice;
2170 }
2171 }
2172
2173 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano || isXAI) && oai_settings.seed >= 0) {
2156 generate_data['seed'] = oai_settings.seed;2174 generate_data['seed'] = oai_settings.seed;
2157 }2175 }
21582176
@@ -2278,6 +2296,11 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
2278 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');2296 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
2279 }2297 }
2280 return data.choices?.[0]?.delta?.content || '';2298 return data.choices?.[0]?.delta?.content || '';
2299 } else if (chat_completion_source === chat_completion_sources.XAI) {
2300 if (show_thoughts) {
2301 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
2302 }
2303 return data.choices?.[0]?.delta?.content || '';
2281 } else if (chat_completion_source === chat_completion_sources.OPENROUTER) {2304 } else if (chat_completion_source === chat_completion_sources.OPENROUTER) {
2282 if (show_thoughts) {2305 if (show_thoughts) {
2283 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');2306 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
@@ -2310,6 +2333,7 @@ function parseChatCompletionLogprobs(data) {
2310 switch (oai_settings.chat_completion_source) {2333 switch (oai_settings.chat_completion_source) {
2311 case chat_completion_sources.OPENAI:2334 case chat_completion_sources.OPENAI:
2312 case chat_completion_sources.DEEPSEEK:2335 case chat_completion_sources.DEEPSEEK:
2336 case chat_completion_sources.XAI:
2313 case chat_completion_sources.CUSTOM:2337 case chat_completion_sources.CUSTOM:
2314 if (!data.choices?.length) {2338 if (!data.choices?.length) {
2315 return null;2339 return null;
@@ -3231,6 +3255,7 @@ function loadOpenAISettings(data, settings) {
3231 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;3255 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
3232 oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;3256 oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
3233 oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;3257 oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
3258 oai_settings.xai_model = settings.xai_model ?? default_settings.xai_model;
3234 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;3259 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
3235 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;3260 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
3236 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;3261 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@@ -3316,6 +3341,8 @@ function loadOpenAISettings(data, settings) {
3316 $('#model_deepseek_select').val(oai_settings.deepseek_model);3341 $('#model_deepseek_select').val(oai_settings.deepseek_model);
3317 $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);3342 $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);
3318 $('#model_01ai_select').val(oai_settings.zerooneai_model);3343 $('#model_01ai_select').val(oai_settings.zerooneai_model);
3344 $('#model_xai_select').val(oai_settings.xai_model);
3345 $(`#model_xai_select option[value="${oai_settings.xai_model}"`).attr('selected', true);
3319 $('#custom_model_id').val(oai_settings.custom_model);3346 $('#custom_model_id').val(oai_settings.custom_model);
3320 $('#custom_api_url_text').val(oai_settings.custom_url);3347 $('#custom_api_url_text').val(oai_settings.custom_url);
3321 $('#openai_max_context').val(oai_settings.openai_max_context);3348 $('#openai_max_context').val(oai_settings.openai_max_context);
@@ -4386,6 +4413,11 @@ async function onModelChange() {
4386 $('#custom_model_id').val(value).trigger('input');4413 $('#custom_model_id').val(value).trigger('input');
4387 }4414 }
43884415
4416 if ($(this).is('#model_xai_select')) {
4417 console.log('XAI model changed to', value);
4418 oai_settings.xai_model = value;
4419 }
4420
4389 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {4421 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
4390 if (oai_settings.max_context_unlocked) {4422 if (oai_settings.max_context_unlocked) {
4391 $('#openai_max_context').attr('max', unlocked_max);4423 $('#openai_max_context').attr('max', unlocked_max);
@@ -4641,6 +4673,22 @@ async function onModelChange() {
4641 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');4673 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4642 }4674 }
46434675
4676 if (oai_settings.chat_completion_source === chat_completion_sources.XAI) {
4677 if (oai_settings.max_context_unlocked) {
4678 $('#openai_max_context').attr('max', unlocked_max);
4679 } else if (oai_settings.xai_model.includes('grok-2-vision')) {
4680 $('#openai_max_context').attr('max', max_32k);
4681 } else if (oai_settings.xai_model.includes('grok-vision')) {
4682 $('#openai_max_context').attr('max', max_8k);
4683 } else {
4684 $('#openai_max_context').attr('max', max_128k);
4685 }
4686
4687 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
4688 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
4689 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4690 }
4691
4644 if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {4692 if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
4645 oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);4693 oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);
4646 $('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input');4694 $('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input');
@@ -4879,6 +4927,19 @@ async function onConnectButtonClick(e) {
4879 }4927 }
4880 }4928 }
48814929
4930 if (oai_settings.chat_completion_source === chat_completion_sources.XAI) {
4931 const api_key_xai = String($('#api_key_xai').val()).trim();
4932
4933 if (api_key_xai.length) {
4934 await writeSecret(SECRET_KEYS.XAI, api_key_xai);
4935 }
4936
4937 if (!secret_state[SECRET_KEYS.XAI]) {
4938 console.log('No secret key saved for XAI');
4939 return;
4940 }
4941 }
4942
4882 startStatusLoading();4943 startStatusLoading();
4883 saveSettingsDebounced();4944 saveSettingsDebounced();
4884 await getStatusOpen();4945 await getStatusOpen();
@@ -4935,6 +4996,9 @@ function toggleChatCompletionForms() {
4935 else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {4996 else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
4936 $('#model_deepseek_select').trigger('change');4997 $('#model_deepseek_select').trigger('change');
4937 }4998 }
4999 else if (oai_settings.chat_completion_source == chat_completion_sources.XAI) {
5000 $('#model_xai_select').trigger('change');
5001 }
4938 $('[data-source]').each(function () {5002 $('[data-source]').each(function () {
4939 const validSources = $(this).data('source').split(',');5003 const validSources = $(this).data('source').split(',');
4940 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));5004 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
@@ -5073,6 +5137,8 @@ export function isImageInliningSupported() {
5073 'pixtral-large-2411',5137 'pixtral-large-2411',
5074 'c4ai-aya-vision-8b',5138 'c4ai-aya-vision-8b',
5075 'c4ai-aya-vision-32b',5139 'c4ai-aya-vision-32b',
5140 'grok-2-vision',
5141 'grok-vision',
5076 ];5142 ];
50775143
5078 switch (oai_settings.chat_completion_source) {5144 switch (oai_settings.chat_completion_source) {
@@ -5092,6 +5158,8 @@ export function isImageInliningSupported() {
5092 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));5158 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));
5093 case chat_completion_sources.COHERE:5159 case chat_completion_sources.COHERE:
5094 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));5160 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));
5161 case chat_completion_sources.XAI:
5162 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
5095 default:5163 default:
5096 return false;5164 return false;
5097 }5165 }
@@ -5688,6 +5756,7 @@ export function initOpenAI() {
5688 $('#model_deepseek_select').on('change', onModelChange);5756 $('#model_deepseek_select').on('change', onModelChange);
5689 $('#model_01ai_select').on('change', onModelChange);5757 $('#model_01ai_select').on('change', onModelChange);
5690 $('#model_custom_select').on('change', onModelChange);5758 $('#model_custom_select').on('change', onModelChange);
5759 $('#model_xai_select').on('change', onModelChange);
5691 $('#settings_preset_openai').on('change', onSettingsPresetChange);5760 $('#settings_preset_openai').on('change', onSettingsPresetChange);
5692 $('#new_oai_preset').on('click', onNewPresetClick);5761 $('#new_oai_preset').on('click', onNewPresetClick);
5693 $('#delete_oai_preset').on('click', onDeletePresetClick);5762 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/reasoning.js+2 -0
@@ -109,6 +109,8 @@ export function extractReasoningFromData(data, {
109 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {109 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {
110 case chat_completion_sources.DEEPSEEK:110 case chat_completion_sources.DEEPSEEK:
111 return data?.choices?.[0]?.message?.reasoning_content ?? '';111 return data?.choices?.[0]?.message?.reasoning_content ?? '';
112 case chat_completion_sources.XAI:
113 return data?.choices?.[0]?.message?.reasoning_content ?? '';
112 case chat_completion_sources.OPENROUTER:114 case chat_completion_sources.OPENROUTER:
113 return data?.choices?.[0]?.message?.reasoning ?? '';115 return data?.choices?.[0]?.message?.reasoning ?? '';
114 case chat_completion_sources.MAKERSUITE:116 case chat_completion_sources.MAKERSUITE:
public/scripts/secrets.js+2 -0
@@ -42,6 +42,7 @@ export const SECRET_KEYS = {
42 DEEPSEEK: 'api_key_deepseek',42 DEEPSEEK: 'api_key_deepseek',
43 SERPER: 'api_key_serper',43 SERPER: 'api_key_serper',
44 FALAI: 'api_key_falai',44 FALAI: 'api_key_falai',
45 XAI: 'api_key_xai',
45};46};
4647
47const INPUT_MAP = {48const INPUT_MAP = {
@@ -76,6 +77,7 @@ const INPUT_MAP = {
76 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',77 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
77 [SECRET_KEYS.GENERIC]: '#api_key_generic',78 [SECRET_KEYS.GENERIC]: '#api_key_generic',
78 [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',79 [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
80 [SECRET_KEYS.XAI]: '#api_key_xai',
79};81};
8082
81async function clearSecret() {83async function clearSecret() {
public/scripts/slash-commands.js+1 -0
@@ -3942,6 +3942,7 @@ function getModelOptions(quiet) {
3942 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },3942 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
3943 { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },3943 { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
3944 { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },3944 { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
3945 { id: 'model_xai_select', api: 'openai', type: chat_completion_sources.XAI },
3945 { id: 'model_novel_select', api: 'novel', type: null },3946 { id: 'model_novel_select', api: 'novel', type: null },
3946 { id: 'horde_model', api: 'koboldhorde', type: null },3947 { id: 'horde_model', api: 'koboldhorde', type: null },
3947 ];3948 ];
public/scripts/tool-calling.js+1 -0
@@ -586,6 +586,7 @@ export class ToolManager {
586 chat_completion_sources.DEEPSEEK,586 chat_completion_sources.DEEPSEEK,
587 chat_completion_sources.MAKERSUITE,587 chat_completion_sources.MAKERSUITE,
588 chat_completion_sources.AI21,588 chat_completion_sources.AI21,
589 chat_completion_sources.XAI,
589 ];590 ];
590 return supportedSources.includes(oai_settings.chat_completion_source);591 return supportedSources.includes(oai_settings.chat_completion_source);
591 }592 }
src/constants.js+1 -0
@@ -176,6 +176,7 @@ export const CHAT_COMPLETION_SOURCES = {
176 ZEROONEAI: '01ai',176 ZEROONEAI: '01ai',
177 NANOGPT: 'nanogpt',177 NANOGPT: 'nanogpt',
178 DEEPSEEK: 'deepseek',178 DEEPSEEK: 'deepseek',
179 XAI: 'xai',
179};180};
180181
181/**182/**
src/endpoints/backends/chat-completions.js+17 -0
@@ -23,6 +23,7 @@ import {
23 convertCohereMessages,23 convertCohereMessages,
24 convertMistralMessages,24 convertMistralMessages,
25 convertAI21Messages,25 convertAI21Messages,
26 convertXAIMessages,
26 mergeMessages,27 mergeMessages,
27 cachingAtDepthForOpenRouterClaude,28 cachingAtDepthForOpenRouterClaude,
28 cachingAtDepthForClaude,29 cachingAtDepthForClaude,
@@ -53,6 +54,7 @@ const API_01AI = 'https://api.lingyiwanwu.com/v1';
53const API_AI21 = 'https://api.ai21.com/studio/v1';54const API_AI21 = 'https://api.ai21.com/studio/v1';
54const API_NANOGPT = 'https://nano-gpt.com/api/v1';55const API_NANOGPT = 'https://nano-gpt.com/api/v1';
55const API_DEEPSEEK = 'https://api.deepseek.com/beta';56const API_DEEPSEEK = 'https://api.deepseek.com/beta';
57const API_XAI = 'https://api.x.ai/v1';
5658
57/**59/**
58 * Applies a post-processing step to the generated messages.60 * Applies a post-processing step to the generated messages.
@@ -872,6 +874,9 @@ router.post('/status', async function (request, response_getstatus_openai) {
872 api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', ''));874 api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', ''));
873 api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);875 api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
874 headers = {};876 headers = {};
877 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.XAI) {
878 api_url = API_XAI;
879 api_key_openai = readSecret(request.user.directories, SECRET_KEYS.XAI);
875 } else {880 } else {
876 console.warn('This chat completion source is not supported yet.');881 console.warn('This chat completion source is not supported yet.');
877 return response_getstatus_openai.status(400).send({ error: true });882 return response_getstatus_openai.status(400).send({ error: true });
@@ -1150,6 +1155,12 @@ router.post('/generate', function (request, response) {
1150 apiKey = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);1155 apiKey = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);
1151 headers = {};1156 headers = {};
1152 bodyParams = {};1157 bodyParams = {};
1158 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.XAI) {
1159 apiUrl = API_XAI;
1160 apiKey = readSecret(request.user.directories, SECRET_KEYS.XAI);
1161 headers = {};
1162 bodyParams = {};
1163 request.body.messages = convertXAIMessages(request.body.messages, getPromptNames(request));
1153 } else {1164 } else {
1154 console.warn('This chat completion source is not supported yet.');1165 console.warn('This chat completion source is not supported yet.');
1155 return response.status(400).send({ error: true });1166 return response.status(400).send({ error: true });
@@ -1162,6 +1173,12 @@ router.post('/generate', function (request, response) {
1162 }1173 }
1163 }1174 }
11641175
1176 if ([CHAT_COMPLETION_SOURCES.XAI].includes(request.body.chat_completion_source)) {
1177 if (['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) {
1178 bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low';
1179 }
1180 }
1181
1165 if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {1182 if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {
1166 console.warn('OpenAI API key is missing.');1183 console.warn('OpenAI API key is missing.');
1167 return response.status(400).send({ error: true });1184 return response.status(400).send({ error: true });
src/endpoints/openai.js+8 -0
@@ -65,6 +65,10 @@ router.post('/caption-image', async (request, response) => {
65 key = readSecret(request.user.directories, SECRET_KEYS.COHERE);65 key = readSecret(request.user.directories, SECRET_KEYS.COHERE);
66 }66 }
6767
68 if (request.body.api === 'xai') {
69 key = readSecret(request.user.directories, SECRET_KEYS.XAI);
70 }
71
68 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {72 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {
69 console.warn('No key found for API', request.body.api);73 console.warn('No key found for API', request.body.api);
70 return response.sendStatus(400);74 return response.sendStatus(400);
@@ -134,6 +138,10 @@ router.post('/caption-image', async (request, response) => {
134 apiUrl = 'https://api.cohere.ai/v2/chat';138 apiUrl = 'https://api.cohere.ai/v2/chat';
135 }139 }
136140
141 if (request.body.api === 'xai') {
142 apiUrl = 'https://api.x.ai/v1/chat/completions';
143 }
144
137 if (request.body.api === 'ooba') {145 if (request.body.api === 'ooba') {
138 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;146 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
139 const imgMessage = body.messages.pop();147 const imgMessage = body.messages.pop();
src/endpoints/secrets.js+1 -0
@@ -52,6 +52,7 @@ export const SECRET_KEYS = {
52 GENERIC: 'api_key_generic',52 GENERIC: 'api_key_generic',
53 DEEPSEEK: 'api_key_deepseek',53 DEEPSEEK: 'api_key_deepseek',
54 SERPER: 'api_key_serper',54 SERPER: 'api_key_serper',
55 XAI: 'api_key_xai',
55};56};
5657
57// These are the keys that are safe to expose, even if allowKeysExposure is false58// These are the keys that are safe to expose, even if allowKeysExposure is false
src/endpoints/stable-diffusion.js+54 -2
@@ -627,8 +627,8 @@ together.post('/models', async (request, response) => {
627 }627 }
628628
629 const models = data629 const models = data
630 .filter(x => x.display_type === 'image')630 .filter(x => x.type === 'image')
631 .map(x => ({ value: x.name, text: x.display_name }));631 .map(x => ({ value: x.id, text: x.display_name }));
632632
633 return response.send(models);633 return response.send(models);
634 } catch (error) {634 } catch (error) {
@@ -1246,6 +1246,7 @@ falai.post('/generate', async (request, response) => {
1246 'Authorization': `Key ${key}`,1246 'Authorization': `Key ${key}`,
1247 },1247 },
1248 });1248 });
1249 /** @type {any} */
1249 const resultData = await resultFetch.json();1250 const resultData = await resultFetch.json();
12501251
1251 if (resultData.detail !== null && resultData.detail !== undefined) {1252 if (resultData.detail !== null && resultData.detail !== undefined) {
@@ -1271,6 +1272,56 @@ falai.post('/generate', async (request, response) => {
1271 }1272 }
1272});1273});
12731274
1275const xai = express.Router();
1276
1277xai.post('/generate', async (request, response) => {
1278 try {
1279 const key = readSecret(request.user.directories, SECRET_KEYS.XAI);
1280
1281 if (!key) {
1282 console.warn('xAI key not found.');
1283 return response.sendStatus(400);
1284 }
1285
1286 const requestBody = {
1287 prompt: request.body.prompt,
1288 model: request.body.model,
1289 response_format: 'b64_json',
1290 };
1291
1292 console.debug('xAI request:', requestBody);
1293
1294 const result = await fetch('https://api.x.ai/v1/images/generations', {
1295 method: 'POST',
1296 body: JSON.stringify(requestBody),
1297 headers: {
1298 'Content-Type': 'application/json',
1299 'Authorization': `Bearer ${key}`,
1300 },
1301 });
1302
1303 if (!result.ok) {
1304 const text = await result.text();
1305 console.warn('xAI returned an error.', text);
1306 return response.sendStatus(500);
1307 }
1308
1309 /** @type {any} */
1310 const data = await result.json();
1311
1312 const image = data?.data?.[0]?.b64_json;
1313 if (!image) {
1314 console.warn('xAI returned invalid data.');
1315 return response.sendStatus(500);
1316 }
1317
1318 return response.send({ image });
1319 } catch (error) {
1320 console.error('Error communicating with xAI', error);
1321 return response.sendStatus(500);
1322 }
1323});
1324
1274router.use('/comfy', comfy);1325router.use('/comfy', comfy);
1275router.use('/together', together);1326router.use('/together', together);
1276router.use('/drawthings', drawthings);1327router.use('/drawthings', drawthings);
@@ -1280,3 +1331,4 @@ router.use('/huggingface', huggingface);
1280router.use('/nanogpt', nanogpt);1331router.use('/nanogpt', nanogpt);
1281router.use('/bfl', bfl);1332router.use('/bfl', bfl);
1282router.use('/falai', falai);1333router.use('/falai', falai);
1334router.use('/xai', xai);
src/prompt-converters.js+38 -1
@@ -413,7 +413,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) {
413 }413 }
414 }414 }
415415
416 const system_instruction = { parts: [{ text: sys_prompt.trim() }]};416 const system_instruction = { parts: [{ text: sys_prompt.trim() }] };
417 const toolNameMap = {};417 const toolNameMap = {};
418418
419 const contents = [];419 const contents = [];
@@ -680,6 +680,43 @@ export function convertMistralMessages(messages, names) {
680}680}
681681
682/**682/**
683 * Convert a prompt from the messages objects to the format used by xAI.
684 * @param {object[]} messages Array of messages
685 * @param {PromptNames} names Prompt names
686 * @returns {object[]} Prompt for xAI
687 */
688export function convertXAIMessages(messages, names) {
689 if (!Array.isArray(messages)) {
690 return [];
691 }
692
693 messages.forEach(msg => {
694 if (!msg.name || msg.role === 'user') {
695 return;
696 }
697
698 const needsCharNamePrefix = [
699 { role: 'assistant', condition: names.charName && !msg.content.startsWith(`${names.charName}: `) && !names.startsWithGroupName(msg.content) },
700 { role: 'system', name: 'example_assistant', condition: names.charName && !msg.content.startsWith(`${names.charName}: `) && !names.startsWithGroupName(msg.content) },
701 { role: 'system', name: 'example_user', condition: names.userName && !msg.content.startsWith(`${names.userName}: `) },
702 ];
703
704 const matchingRule = needsCharNamePrefix.find(rule =>
705 msg.role === rule.role && (!rule.name || msg.name === rule.name) && rule.condition,
706 );
707
708 if (matchingRule) {
709 const prefix = msg.role === 'system' && msg.name === 'example_user' ? names.userName : names.charName;
710 msg.content = `${prefix}: ${msg.content}`;
711 }
712
713 delete msg.name;
714 });
715
716 return messages;
717}
718
719/**
683 * Merge messages with the same consecutive role, removing names if they exist.720 * Merge messages with the same consecutive role, removing names if they exist.
684 * @param {any[]} messages Messages to merge721 * @param {any[]} messages Messages to merge
685 * @param {PromptNames} names Prompt names722 * @param {PromptNames} names Prompt names