Merge pull request #3839 from SillyTavern/gork-ai xAI grok
Signed| @@ -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" /> <g | |
| 37 | + id="g4" | |
| 38 | + transform="translate(-201.61,-56.91)"> <polygon | |
| 39 | + points="631.96,538.36 640.28,93.18 557.09,211.99 565.4,538.36 " | |
| 40 | + id="polygon1" /> <polygon | |
| 41 | + points="379.35,284.53 430.13,357.05 640.28,56.91 538.72,56.91 " | |
| 42 | + id="polygon2" /> <polygon | |
| 43 | + points="353.96,465.84 303.17,393.31 201.61,538.36 303.17,538.36 " | |
| 44 | + id="polygon3" /> <polygon | |
| 45 | + points="531.69,538.36 303.17,211.99 201.61,211.99 430.13,538.36 " | |
| 46 | + id="polygon4" /> </g> </svg> | |
| @@ -685,7 +685,7 @@ | ||
| 685 | 685 | </span> |
| 686 | 686 | </div> |
| 687 | 687 | </div> |
| 688 | 688 | <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai"> |
| 689 | 689 | <div class="range-block-title" data-i18n="Temperature"> |
| 690 | 690 | Temperature |
| 691 | 691 | </div> |
| @@ -698,7 +698,7 @@ | ||
| 698 | 698 | </div> |
| 699 | 699 | </div> |
| 700 | 700 | </div> |
| 701 | 701 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai"> |
| 702 | 702 | <div class="range-block-title" data-i18n="Frequency Penalty"> |
| 703 | 703 | Frequency Penalty |
| 704 | 704 | </div> |
| @@ -711,7 +711,7 @@ | ||
| 711 | 711 | </div> |
| 712 | 712 | </div> |
| 713 | 713 | </div> |
| 714 | 714 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai"> |
| 715 | 715 | <div class="range-block-title" data-i18n="Presence Penalty"> |
| 716 | 716 | Presence Penalty |
| 717 | 717 | </div> |
| @@ -737,7 +737,7 @@ | ||
| 737 | 737 | </div> |
| 738 | 738 | </div> |
| 739 | 739 | </div> |
| 740 | 740 | <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai"> |
| 741 | 741 | <div class="range-block-title" data-i18n="Top P"> |
| 742 | 742 | Top P |
| 743 | 743 | </div> |
| @@ -974,7 +974,7 @@ | ||
| 974 | 974 | </div> |
| 975 | 975 | </div> |
| 976 | 976 | </div> |
| 977 | 977 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai"> |
| 978 | 978 | <div class="range-block-title justifyLeft" data-i18n="Seed"> |
| 979 | 979 | Seed |
| 980 | 980 | </div> |
| @@ -1965,7 +1965,7 @@ | ||
| 1965 | 1965 | </span> |
| 1966 | 1966 | </div> |
| 1967 | 1967 | </div> |
| 1968 | 1968 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai"> |
| 1969 | 1969 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 1970 | 1970 | <input id="openai_function_calling" type="checkbox" /> |
| 1971 | 1971 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -1975,7 +1975,7 @@ | ||
| 1975 | 1975 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> |
| 1976 | 1976 | </div> |
| 1977 | 1977 | </div> |
| 1978 | 1978 | <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai"> |
| 1979 | 1979 | <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 1980 | 1980 | <input id="openai_image_inlining" type="checkbox" /> |
| 1981 | 1981 | <span data-i18n="Send inline images">Send inline images</span> |
| @@ -1987,7 +1987,7 @@ | ||
| 1987 | 1987 | <code><i class="fa-solid fa-wand-magic-sparkles"></i></code> |
| 1988 | 1988 | <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span> |
| 1989 | 1989 | </div> |
| 1990 | 1990 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai"> |
| 1991 | 1991 | <div class="flex-container oneline-dropdown"> |
| 1992 | 1992 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> |
| 1993 | 1993 | Inline Image Quality |
| @@ -2031,7 +2031,7 @@ | ||
| 2031 | 2031 | </span> |
| 2032 | 2032 | </div> |
| 2033 | 2033 | </div> |
| 2034 | 2034 | <div class="range-block" data-source="deepseek,openrouter,custom,claude,xai"> |
| 2035 | 2035 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 2036 | 2036 | <input id="openai_show_thoughts" type="checkbox" /> |
| 2037 | 2037 | <span> |
| @@ -2045,7 +2045,7 @@ | ||
| 2045 | 2045 | </span> |
| 2046 | 2046 | </div> |
| 2047 | 2047 | </div> |
| 2048 | 2048 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai"> |
| 2049 | 2049 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. 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 | 2050 | <label for="openai_reasoning_effort"> |
| 2051 | 2051 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| @@ -2756,6 +2756,7 @@ | ||
| 2756 | 2756 | <option value="perplexity">Perplexity</option> |
| 2757 | 2757 | <option value="scale">Scale</option> |
| 2758 | 2758 | <option value="windowai">Window AI</option> |
| 2759 | + <option value="xai">xAI (Grok)</option> | |
| 2759 | 2760 | </optgroup> |
| 2760 | 2761 | </select> |
| 2761 | 2762 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek"> |
| @@ -3424,6 +3425,31 @@ | ||
| 3424 | 3425 | <select id="model_01ai_select"> |
| 3425 | 3426 | </select> |
| 3426 | 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 | 3453 | <div id="prompt_post_porcessing_form" data-source="custom,openrouter"> |
| 3428 | 3454 | <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4> |
| 3429 | 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."> |
| @@ -409,6 +409,7 @@ function RA_autoconnect(PrevApi) { | ||
| 409 | 409 | || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) |
| 410 | 410 | || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) |
| 411 | 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 | 413 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) |
| 413 | 414 | ) { |
| 414 | 415 | $('#api_button_openai').trigger('click'); |
| @@ -428,6 +428,7 @@ jQuery(async function () { | ||
| 428 | 428 | 'zerooneai': SECRET_KEYS.ZEROONEAI, |
| 429 | 429 | 'groq': SECRET_KEYS.GROQ, |
| 430 | 430 | 'cohere': SECRET_KEYS.COHERE, |
| 431 | + 'xai': SECRET_KEYS.XAI, | |
| 431 | 432 | }; |
| 432 | 433 | |
| 433 | 434 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { |
| @@ -31,6 +31,7 @@ | ||
| 31 | 31 | <option value="openrouter">OpenRouter</option> |
| 32 | 32 | <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option> |
| 33 | 33 | <option value="vllm">vLLM</option> |
| 34 | + <option value="xai">xAI (Grok)</option> | |
| 34 | 35 | </select> |
| 35 | 36 | </div> |
| 36 | 37 | <div class="flex1 flex-container flexFlowColumn flexNoGap"> |
| @@ -134,6 +135,8 @@ | ||
| 134 | 135 | <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option> |
| 135 | 136 | <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option> |
| 136 | 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 | 140 | </select> |
| 138 | 141 | </div> |
| 139 | 142 | <div data-type="ollama"> |
| @@ -153,6 +153,10 @@ function throwIfInvalidModel(useReverseProxy) { | ||
| 153 | 153 | throw new Error('Cohere API key is not set.'); |
| 154 | 154 | } |
| 155 | 155 | |
| 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 | 160 | if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) { |
| 157 | 161 | throw new Error('Ollama server URL is not set.'); |
| 158 | 162 | } |
| @@ -81,6 +81,7 @@ const sources = { | ||
| 81 | 81 | nanogpt: 'nanogpt', |
| 82 | 82 | bfl: 'bfl', |
| 83 | 83 | falai: 'falai', |
| 84 | + xai: 'xai', | |
| 84 | 85 | }; |
| 85 | 86 | |
| 86 | 87 | const initiators = { |
| @@ -1303,6 +1304,7 @@ async function onModelChange() { | ||
| 1303 | 1304 | sources.nanogpt, |
| 1304 | 1305 | sources.bfl, |
| 1305 | 1306 | sources.falai, |
| 1307 | + sources.xai, | |
| 1306 | 1308 | ]; |
| 1307 | 1309 | |
| 1308 | 1310 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1518,6 +1520,9 @@ async function loadSamplers() { | ||
| 1518 | 1520 | case sources.bfl: |
| 1519 | 1521 | samplers = ['N/A']; |
| 1520 | 1522 | break; |
| 1523 | + case sources.xai: | |
| 1524 | + samplers = ['N/A']; | |
| 1525 | + break; | |
| 1521 | 1526 | } |
| 1522 | 1527 | |
| 1523 | 1528 | for (const sampler of samplers) { |
| @@ -1708,6 +1713,9 @@ async function loadModels() { | ||
| 1708 | 1713 | case sources.falai: |
| 1709 | 1714 | models = await loadFalaiModels(); |
| 1710 | 1715 | break; |
| 1716 | + case sources.xai: | |
| 1717 | + models = await loadXAIModels(); | |
| 1718 | + break; | |
| 1711 | 1719 | } |
| 1712 | 1720 | |
| 1713 | 1721 | for (const model of models) { |
| @@ -1760,6 +1768,12 @@ async function loadFalaiModels() { | ||
| 1760 | 1768 | return []; |
| 1761 | 1769 | } |
| 1762 | 1770 | |
| 1771 | +async function loadXAIModels() { | |
| 1772 | + return [ | |
| 1773 | + { value: 'grok-2-image-1212', text: 'grok-2-image-1212' }, | |
| 1774 | + ]; | |
| 1775 | +} | |
| 1776 | + | |
| 1763 | 1777 | async function loadPollinationsModels() { |
| 1764 | 1778 | const result = await fetch('/api/sd/pollinations/models', { |
| 1765 | 1779 | method: 'POST', |
| @@ -2081,6 +2095,9 @@ async function loadSchedulers() { | ||
| 2081 | 2095 | case sources.falai: |
| 2082 | 2096 | schedulers = ['N/A']; |
| 2083 | 2097 | break; |
| 2098 | + case sources.xai: | |
| 2099 | + schedulers = ['N/A']; | |
| 2100 | + break; | |
| 2084 | 2101 | } |
| 2085 | 2102 | |
| 2086 | 2103 | for (const scheduler of schedulers) { |
| @@ -2166,6 +2183,12 @@ async function loadVaes() { | ||
| 2166 | 2183 | case sources.bfl: |
| 2167 | 2184 | vaes = ['N/A']; |
| 2168 | 2185 | break; |
| 2186 | + case sources.falai: | |
| 2187 | + vaes = ['N/A']; | |
| 2188 | + break; | |
| 2189 | + case sources.xai: | |
| 2190 | + vaes = ['N/A']; | |
| 2191 | + break; | |
| 2169 | 2192 | } |
| 2170 | 2193 | |
| 2171 | 2194 | for (const vae of vaes) { |
| @@ -2735,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 2735 | 2758 | case sources.falai: |
| 2736 | 2759 | result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal); |
| 2737 | 2760 | break; |
| 2761 | + case sources.xai: | |
| 2762 | + result = await generateXAIImage(prefixedPrompt, negativePrompt, signal); | |
| 2763 | + break; | |
| 2738 | 2764 | } |
| 2739 | 2765 | |
| 2740 | 2766 | if (!result.data) { |
| @@ -3464,6 +3490,33 @@ async function generateBflImage(prompt, signal) { | ||
| 3464 | 3490 | } |
| 3465 | 3491 | |
| 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 | + */ | |
| 3499 | +async 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 | 3520 | * Generates an image using the FAL.AI API. |
| 3468 | 3521 | * @param {string} prompt - The main instruction used to guide the image generation. |
| 3469 | 3522 | * @param {string} negativePrompt - The negative prompt used to guide the image generation. |
| @@ -3782,6 +3835,8 @@ function isValidState() { | ||
| 3782 | 3835 | return secret_state[SECRET_KEYS.BFL]; |
| 3783 | 3836 | case sources.falai: |
| 3784 | 3837 | return secret_state[SECRET_KEYS.FALAI]; |
| 3838 | + case sources.xai: | |
| 3839 | + return secret_state[SECRET_KEYS.XAI]; | |
| 3785 | 3840 | } |
| 3786 | 3841 | } |
| 3787 | 3842 | |
| @@ -52,6 +52,7 @@ | ||
| 52 | 52 | <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option> |
| 53 | 53 | <option value="horde">Stable Horde</option> |
| 54 | 54 | <option value="togetherai">TogetherAI</option> |
| 55 | + <option value="xai">xAI (Grok)</option> | |
| 55 | 56 | </select> |
| 56 | 57 | <div data-sd-source="auto"> |
| 57 | 58 | <label for="sd_auto_url">SD Web UI URL</label> |
| @@ -184,6 +184,7 @@ export const chat_completion_sources = { | ||
| 184 | 184 | ZEROONEAI: '01ai', |
| 185 | 185 | NANOGPT: 'nanogpt', |
| 186 | 186 | DEEPSEEK: 'deepseek', |
| 187 | + XAI: 'xai', | |
| 187 | 188 | }; |
| 188 | 189 | |
| 189 | 190 | const character_names_behavior = { |
| @@ -257,6 +258,7 @@ export const settingsToUpdate = { | ||
| 257 | 258 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false], |
| 258 | 259 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false], |
| 259 | 260 | zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false], |
| 261 | + xai_model: ['#model_xai_select', 'xai_model', false], | |
| 260 | 262 | custom_model: ['#custom_model_id', 'custom_model', false], |
| 261 | 263 | custom_url: ['#custom_api_url_text', 'custom_url', false], |
| 262 | 264 | custom_include_body: ['#custom_include_body', 'custom_include_body', false], |
| @@ -345,6 +347,7 @@ const default_settings = { | ||
| 345 | 347 | nanogpt_model: 'gpt-4o-mini', |
| 346 | 348 | zerooneai_model: 'yi-large', |
| 347 | 349 | deepseek_model: 'deepseek-chat', |
| 350 | + xai_model: 'grok-3-beta', | |
| 348 | 351 | custom_model: '', |
| 349 | 352 | custom_url: '', |
| 350 | 353 | custom_include_body: '', |
| @@ -425,6 +428,7 @@ const oai_settings = { | ||
| 425 | 428 | nanogpt_model: 'gpt-4o-mini', |
| 426 | 429 | zerooneai_model: 'yi-large', |
| 427 | 430 | deepseek_model: 'deepseek-chat', |
| 431 | + xai_model: 'grok-3-beta', | |
| 428 | 432 | custom_model: '', |
| 429 | 433 | custom_url: '', |
| 430 | 434 | custom_include_body: '', |
| @@ -1644,6 +1648,8 @@ export function getChatCompletionModel(source = null) { | ||
| 1644 | 1648 | return oai_settings.nanogpt_model; |
| 1645 | 1649 | case chat_completion_sources.DEEPSEEK: |
| 1646 | 1650 | return oai_settings.deepseek_model; |
| 1651 | + case chat_completion_sources.XAI: | |
| 1652 | + return oai_settings.xai_model; | |
| 1647 | 1653 | default: |
| 1648 | 1654 | throw new Error(`Unknown chat completion source: ${activeSource}`); |
| 1649 | 1655 | } |
| @@ -1961,6 +1967,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 1961 | 1967 | const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI; |
| 1962 | 1968 | const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT; |
| 1963 | 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 | 1971 | const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model); |
| 1965 | 1972 | const isQuiet = type === 'quiet'; |
| 1966 | 1973 | const isImpersonate = type === 'impersonate'; |
| @@ -2033,7 +2040,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2033 | 2040 | } |
| 2034 | 2041 | |
| 2035 | 2042 | // Add logprobs request (currently OpenAI only, max 5 on their side) |
| 2036 | 2043 | if (useLogprobs && (isOAI || isCustom || isDeepSeek || isXAI)) { |
| 2037 | 2044 | generate_data['logprobs'] = 5; |
| 2038 | 2045 | } |
| 2039 | 2046 | |
| @@ -2152,7 +2159,18 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2152 | 2159 | } |
| 2153 | 2160 | } |
| 2154 | 2161 | |
| 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 | 2174 | generate_data['seed'] = oai_settings.seed; |
| 2157 | 2175 | } |
| 2158 | 2176 | |
| @@ -2278,6 +2296,11 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov | ||
| 2278 | 2296 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); |
| 2279 | 2297 | } |
| 2280 | 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 | 2304 | } else if (chat_completion_source === chat_completion_sources.OPENROUTER) { |
| 2282 | 2305 | if (show_thoughts) { |
| 2283 | 2306 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); |
| @@ -2310,6 +2333,7 @@ function parseChatCompletionLogprobs(data) { | ||
| 2310 | 2333 | switch (oai_settings.chat_completion_source) { |
| 2311 | 2334 | case chat_completion_sources.OPENAI: |
| 2312 | 2335 | case chat_completion_sources.DEEPSEEK: |
| 2336 | + case chat_completion_sources.XAI: | |
| 2313 | 2337 | case chat_completion_sources.CUSTOM: |
| 2314 | 2338 | if (!data.choices?.length) { |
| 2315 | 2339 | return null; |
| @@ -3231,6 +3255,7 @@ function loadOpenAISettings(data, settings) { | ||
| 3231 | 3255 | oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model; |
| 3232 | 3256 | oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model; |
| 3233 | 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 | 3259 | oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model; |
| 3235 | 3260 | oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url; |
| 3236 | 3261 | oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body; |
| @@ -3316,6 +3341,8 @@ function loadOpenAISettings(data, settings) { | ||
| 3316 | 3341 | $('#model_deepseek_select').val(oai_settings.deepseek_model); |
| 3317 | 3342 | $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true); |
| 3318 | 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 | 3346 | $('#custom_model_id').val(oai_settings.custom_model); |
| 3320 | 3347 | $('#custom_api_url_text').val(oai_settings.custom_url); |
| 3321 | 3348 | $('#openai_max_context').val(oai_settings.openai_max_context); |
| @@ -4386,6 +4413,11 @@ async function onModelChange() { | ||
| 4386 | 4413 | $('#custom_model_id').val(value).trigger('input'); |
| 4387 | 4414 | } |
| 4388 | 4415 | |
| 4416 | + if ($(this).is('#model_xai_select')) { | |
| 4417 | + console.log('XAI model changed to', value); | |
| 4418 | + oai_settings.xai_model = value; | |
| 4419 | + } | |
| 4420 | + | |
| 4389 | 4421 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { |
| 4390 | 4422 | if (oai_settings.max_context_unlocked) { |
| 4391 | 4423 | $('#openai_max_context').attr('max', unlocked_max); |
| @@ -4641,6 +4673,22 @@ async function onModelChange() { | ||
| 4641 | 4673 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); |
| 4642 | 4674 | } |
| 4643 | 4675 | |
| 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 | 4692 | if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { |
| 4645 | 4693 | oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1); |
| 4646 | 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 | } |
| 4881 | 4929 | |
| 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 | 4943 | startStatusLoading(); |
| 4883 | 4944 | saveSettingsDebounced(); |
| 4884 | 4945 | await getStatusOpen(); |
| @@ -4935,6 +4996,9 @@ function toggleChatCompletionForms() { | ||
| 4935 | 4996 | else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) { |
| 4936 | 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 | 5002 | $('[data-source]').each(function () { |
| 4939 | 5003 | const validSources = $(this).data('source').split(','); |
| 4940 | 5004 | $(this).toggle(validSources.includes(oai_settings.chat_completion_source)); |
| @@ -5073,6 +5137,8 @@ export function isImageInliningSupported() { | ||
| 5073 | 5137 | 'pixtral-large-2411', |
| 5074 | 5138 | 'c4ai-aya-vision-8b', |
| 5075 | 5139 | 'c4ai-aya-vision-32b', |
| 5140 | + 'grok-2-vision', | |
| 5141 | + 'grok-vision', | |
| 5076 | 5142 | ]; |
| 5077 | 5143 | |
| 5078 | 5144 | switch (oai_settings.chat_completion_source) { |
| @@ -5092,6 +5158,8 @@ export function isImageInliningSupported() { | ||
| 5092 | 5158 | return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model)); |
| 5093 | 5159 | case chat_completion_sources.COHERE: |
| 5094 | 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 | 5163 | default: |
| 5096 | 5164 | return false; |
| 5097 | 5165 | } |
| @@ -5688,6 +5756,7 @@ export function initOpenAI() { | ||
| 5688 | 5756 | $('#model_deepseek_select').on('change', onModelChange); |
| 5689 | 5757 | $('#model_01ai_select').on('change', onModelChange); |
| 5690 | 5758 | $('#model_custom_select').on('change', onModelChange); |
| 5759 | + $('#model_xai_select').on('change', onModelChange); | |
| 5691 | 5760 | $('#settings_preset_openai').on('change', onSettingsPresetChange); |
| 5692 | 5761 | $('#new_oai_preset').on('click', onNewPresetClick); |
| 5693 | 5762 | $('#delete_oai_preset').on('click', onDeletePresetClick); |
| @@ -109,6 +109,8 @@ export function extractReasoningFromData(data, { | ||
| 109 | 109 | switch (chatCompletionSource ?? oai_settings.chat_completion_source) { |
| 110 | 110 | case chat_completion_sources.DEEPSEEK: |
| 111 | 111 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; |
| 112 | + case chat_completion_sources.XAI: | |
| 113 | + return data?.choices?.[0]?.message?.reasoning_content ?? ''; | |
| 112 | 114 | case chat_completion_sources.OPENROUTER: |
| 113 | 115 | return data?.choices?.[0]?.message?.reasoning ?? ''; |
| 114 | 116 | case chat_completion_sources.MAKERSUITE: |
| @@ -42,6 +42,7 @@ export const SECRET_KEYS = { | ||
| 42 | 42 | DEEPSEEK: 'api_key_deepseek', |
| 43 | 43 | SERPER: 'api_key_serper', |
| 44 | 44 | FALAI: 'api_key_falai', |
| 45 | + XAI: 'api_key_xai', | |
| 45 | 46 | }; |
| 46 | 47 | |
| 47 | 48 | const INPUT_MAP = { |
| @@ -76,6 +77,7 @@ const INPUT_MAP = { | ||
| 76 | 77 | [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt', |
| 77 | 78 | [SECRET_KEYS.GENERIC]: '#api_key_generic', |
| 78 | 79 | [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek', |
| 80 | + [SECRET_KEYS.XAI]: '#api_key_xai', | |
| 79 | 81 | }; |
| 80 | 82 | |
| 81 | 83 | async function clearSecret() { |
| @@ -3942,6 +3942,7 @@ function getModelOptions(quiet) { | ||
| 3942 | 3942 | { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT }, |
| 3943 | 3943 | { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI }, |
| 3944 | 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 | 3946 | { id: 'model_novel_select', api: 'novel', type: null }, |
| 3946 | 3947 | { id: 'horde_model', api: 'koboldhorde', type: null }, |
| 3947 | 3948 | ]; |
| @@ -586,6 +586,7 @@ export class ToolManager { | ||
| 586 | 586 | chat_completion_sources.DEEPSEEK, |
| 587 | 587 | chat_completion_sources.MAKERSUITE, |
| 588 | 588 | chat_completion_sources.AI21, |
| 589 | + chat_completion_sources.XAI, | |
| 589 | 590 | ]; |
| 590 | 591 | return supportedSources.includes(oai_settings.chat_completion_source); |
| 591 | 592 | } |
| @@ -176,6 +176,7 @@ export const CHAT_COMPLETION_SOURCES = { | ||
| 176 | 176 | ZEROONEAI: '01ai', |
| 177 | 177 | NANOGPT: 'nanogpt', |
| 178 | 178 | DEEPSEEK: 'deepseek', |
| 179 | + XAI: 'xai', | |
| 179 | 180 | }; |
| 180 | 181 | |
| 181 | 182 | /** |
| @@ -23,6 +23,7 @@ import { | ||
| 23 | 23 | convertCohereMessages, |
| 24 | 24 | convertMistralMessages, |
| 25 | 25 | convertAI21Messages, |
| 26 | + convertXAIMessages, | |
| 26 | 27 | mergeMessages, |
| 27 | 28 | cachingAtDepthForOpenRouterClaude, |
| 28 | 29 | cachingAtDepthForClaude, |
| @@ -53,6 +54,7 @@ const API_01AI = 'https://api.lingyiwanwu.com/v1'; | ||
| 53 | 54 | const API_AI21 = 'https://api.ai21.com/studio/v1'; |
| 54 | 55 | const API_NANOGPT = 'https://nano-gpt.com/api/v1'; |
| 55 | 56 | const API_DEEPSEEK = 'https://api.deepseek.com/beta'; |
| 57 | +const API_XAI = 'https://api.x.ai/v1'; | |
| 56 | 58 | |
| 57 | 59 | /** |
| 58 | 60 | * Applies a post-processing step to the generated messages. |
| @@ -872,6 +874,9 @@ router.post('/status', async function (request, response_getstatus_openai) { | ||
| 872 | 874 | api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', '')); |
| 873 | 875 | api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); |
| 874 | 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 | 880 | } else { |
| 876 | 881 | console.warn('This chat completion source is not supported yet.'); |
| 877 | 882 | return response_getstatus_openai.status(400).send({ error: true }); |
| @@ -1150,6 +1155,12 @@ router.post('/generate', function (request, response) { | ||
| 1150 | 1155 | apiKey = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI); |
| 1151 | 1156 | headers = {}; |
| 1152 | 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 | 1164 | } else { |
| 1154 | 1165 | console.warn('This chat completion source is not supported yet.'); |
| 1155 | 1166 | return response.status(400).send({ error: true }); |
| @@ -1162,6 +1173,12 @@ router.post('/generate', function (request, response) { | ||
| 1162 | 1173 | } |
| 1163 | 1174 | } |
| 1164 | 1175 | |
| 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 | 1182 | if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { |
| 1166 | 1183 | console.warn('OpenAI API key is missing.'); |
| 1167 | 1184 | return response.status(400).send({ error: true }); |
| @@ -65,6 +65,10 @@ router.post('/caption-image', async (request, response) => { | ||
| 65 | 65 | key = readSecret(request.user.directories, SECRET_KEYS.COHERE); |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | + if (request.body.api === 'xai') { | |
| 69 | + key = readSecret(request.user.directories, SECRET_KEYS.XAI); | |
| 70 | + } | |
| 71 | + | |
| 68 | 72 | if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) { |
| 69 | 73 | console.warn('No key found for API', request.body.api); |
| 70 | 74 | return response.sendStatus(400); |
| @@ -134,6 +138,10 @@ router.post('/caption-image', async (request, response) => { | ||
| 134 | 138 | apiUrl = 'https://api.cohere.ai/v2/chat'; |
| 135 | 139 | } |
| 136 | 140 | |
| 141 | + if (request.body.api === 'xai') { | |
| 142 | + apiUrl = 'https://api.x.ai/v1/chat/completions'; | |
| 143 | + } | |
| 144 | + | |
| 137 | 145 | if (request.body.api === 'ooba') { |
| 138 | 146 | apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`; |
| 139 | 147 | const imgMessage = body.messages.pop(); |
| @@ -52,6 +52,7 @@ export const SECRET_KEYS = { | ||
| 52 | 52 | GENERIC: 'api_key_generic', |
| 53 | 53 | DEEPSEEK: 'api_key_deepseek', |
| 54 | 54 | SERPER: 'api_key_serper', |
| 55 | + XAI: 'api_key_xai', | |
| 55 | 56 | }; |
| 56 | 57 | |
| 57 | 58 | // These are the keys that are safe to expose, even if allowKeysExposure is false |
| @@ -627,8 +627,8 @@ together.post('/models', async (request, response) => { | ||
| 627 | 627 | } |
| 628 | 628 | |
| 629 | 629 | const models = data |
| 630 | 630 | .filter(x => x.display_typetype === 'image') |
| 631 | 631 | .map(x => ({ value: x.nameid, text: x.display_name })); |
| 632 | 632 | |
| 633 | 633 | return response.send(models); |
| 634 | 634 | } catch (error) { |
| @@ -1246,6 +1246,7 @@ falai.post('/generate', async (request, response) => { | ||
| 1246 | 1246 | 'Authorization': `Key ${key}`, |
| 1247 | 1247 | }, |
| 1248 | 1248 | }); |
| 1249 | + /** @type {any} */ | |
| 1249 | 1250 | const resultData = await resultFetch.json(); |
| 1250 | 1251 | |
| 1251 | 1252 | if (resultData.detail !== null && resultData.detail !== undefined) { |
| @@ -1271,6 +1272,56 @@ falai.post('/generate', async (request, response) => { | ||
| 1271 | 1272 | } |
| 1272 | 1273 | }); |
| 1273 | 1274 | |
| 1275 | +const xai = express.Router(); | |
| 1276 | + | |
| 1277 | +xai.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 | + | |
| 1274 | 1325 | router.use('/comfy', comfy); |
| 1275 | 1326 | router.use('/together', together); |
| 1276 | 1327 | router.use('/drawthings', drawthings); |
| @@ -1280,3 +1331,4 @@ router.use('/huggingface', huggingface); | ||
| 1280 | 1331 | router.use('/nanogpt', nanogpt); |
| 1281 | 1332 | router.use('/bfl', bfl); |
| 1282 | 1333 | router.use('/falai', falai); |
| 1334 | +router.use('/xai', xai); | |
| @@ -680,6 +680,43 @@ export function convertMistralMessages(messages, names) { | ||
| 680 | 680 | } |
| 681 | 681 | |
| 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 | + */ | |
| 688 | +export 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 | 720 | * Merge messages with the same consecutive role, removing names if they exist. |
| 684 | 721 | * @param {any[]} messages Messages to merge |
| 685 | 722 | * @param {PromptNames} names Prompt names |