Merge branch 'staging' into parser-followup-2

This commit is contained in:
LenAnderson 2024-06-28 16:19:27 -04:00
commit a08ab79181
36 changed files with 949 additions and 335 deletions

1
.gitignore vendored
View File

@ -48,3 +48,4 @@ public/css/user.css
/plugins/
/data
/default/scaffold
public/scripts/extensions/third-party

View File

@ -11,6 +11,14 @@
"filename": "themes/Cappuccino.json",
"type": "theme"
},
{
"filename": "themes/Celestial Macaron.json",
"type": "theme"
},
{
"filename": "themes/Dark V 1.0.json",
"type": "theme"
},
{
"filename": "backgrounds/__transparent.png",
"type": "background"

View File

@ -0,0 +1,37 @@
{
"name": "Celestial Macaron",
"blur_strength": 10,
"main_text_color": "rgba(229, 175, 162, 1)",
"italics_text_color": "rgba(146, 147, 161, 1)",
"underline_text_color": "rgba(157, 215, 198, 1)",
"quote_text_color": "rgba(197, 202, 206, 1)",
"blur_tint_color": "rgba(23, 36, 55, 0.9)",
"chat_tint_color": "rgba(18, 26, 40, 0.9)",
"user_mes_blur_tint_color": "rgba(51, 67, 90, 0.7)",
"bot_mes_blur_tint_color": "rgba(23, 36, 55, 0.75)",
"shadow_color": "rgba(0, 0, 0, 0.3)",
"shadow_width": 1,
"border_color": "rgba(60, 74, 110, 0.93)",
"font_scale": 1,
"fast_ui_mode": false,
"waifuMode": false,
"avatar_style": 0,
"chat_display": 1,
"noShadows": true,
"chat_width": 58,
"timer_enabled": true,
"timestamps_enabled": true,
"timestamp_model_icon": false,
"mesIDDisplay_enabled": true,
"hideChatAvatars_enabled": false,
"message_token_count_enabled": true,
"expand_message_actions": true,
"enableZenSliders": false,
"enableLabMode": false,
"hotswap_enabled": true,
"custom_css": "",
"bogus_folders": true,
"zoomed_avatar_magnification": false,
"reduced_motion": false,
"compact_input_area": true
}

View File

@ -0,0 +1,37 @@
{
"name": "Dark V 1.0",
"blur_strength": 13,
"main_text_color": "rgba(207, 207, 197, 1)",
"italics_text_color": "rgba(145, 145, 145, 1)",
"underline_text_color": "rgba(145, 145, 145, 1)",
"quote_text_color": "rgba(198, 193, 151, 1)",
"blur_tint_color": "rgba(29, 33, 40, 0.9)",
"chat_tint_color": "rgba(29, 33, 40, 0.9)",
"user_mes_blur_tint_color": "rgba(29, 33, 40, 0.9)",
"bot_mes_blur_tint_color": "rgba(29, 33, 40, 0.9)",
"shadow_color": "rgba(0, 0, 0, 0.9)",
"shadow_width": 2,
"border_color": "rgba(0, 0, 0, 1)",
"font_scale": 1,
"fast_ui_mode": false,
"waifuMode": false,
"avatar_style": 0,
"chat_display": 0,
"noShadows": false,
"chat_width": 55,
"timer_enabled": false,
"timestamps_enabled": false,
"timestamp_model_icon": false,
"mesIDDisplay_enabled": false,
"hideChatAvatars_enabled": false,
"message_token_count_enabled": false,
"expand_message_actions": false,
"enableZenSliders": false,
"enableLabMode": false,
"hotswap_enabled": true,
"custom_css": "",
"bogus_folders": true,
"zoomed_avatar_magnification": true,
"reduced_motion": true,
"compact_input_area": false
}

View File

@ -0,0 +1,8 @@
/* iPhone copium land */
@media screen and (max-width: 1000px) {
.ios .popup .popup-body {
height: fit-content;
max-height: 90vh;
max-height: 90svh;
}
}

View File

@ -1,3 +1,5 @@
@import url('./popup-safari-fix.css');
dialog {
color: var(--SmartThemeBodyColor);
}
@ -14,6 +16,10 @@ dialog {
display: flex;
flex-direction: column;
max-height: calc(100svh - 2em);
max-width: calc(100svw - 2em);
min-height: fit-content;
/* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */
overflow: visible;
@ -72,6 +78,11 @@ dialog {
background-color: var(--black30a);
}
body.no-blur .popup[open]::backdrop {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* Closing animation */
.popup[closing] {
animation: pop-out var(--animation-duration-slow) ease-in-out;
@ -92,6 +103,24 @@ dialog {
text-align: left;
}
.popup-crop-wrap {
margin: 10px auto;
max-height: 75vh;
max-height: 75svh;
max-width: 100%;
}
.popup-crop-wrap img {
max-width: 100%;
/* This rule is very important, please do not ignore this! */
}
.popup-inputs {
margin-top: 10px;
font-size: smaller;
opacity: 0.7;
}
.popup-input {
margin-top: 10px;
}
@ -143,3 +172,4 @@ dialog {
/* Fix weird animation issue with font-scaling during popup open */
backface-visibility: hidden;
}

59
public/img/01ai.svg Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="363.44339"
height="375.68854"
viewBox="0 0 363.44339 375.68854"
version="1.1"
id="svg2"
sodipodi:docname="Yi_logo_icon_dark.svg"
inkscape:version="1.3 (0e150ed, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.1073359"
inkscape:cx="192.35355"
inkscape:cy="196.86889"
inkscape:window-width="1512"
inkscape:window-height="857"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<rect
x="287.14771"
y="224.04056"
width="42.3862"
height="151.64799"
rx="21.1931"
id="rect1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 299.41969,17.362538 c -8.916,-7.5830004 -22.291,-6.503 -29.874,2.414 l -118.432,139.253002 c -3.056,3.593 -4.705,7.911 -5.001,12.281 -0.166,1.069 -0.252,2.164 -0.252,3.279 v 178.022 c 0,11.705 9.488,21.193 21.193,21.193 11.705,0 21.193,-9.488 21.193,-21.193 v -171.819 l 113.587,-133.556002 c 7.583,-8.916 6.502,-22.291 -2.414,-29.874 z"
id="path1" />
<rect
x="-18.236605"
y="8.6596518"
width="42.3862"
height="174.745"
rx="21.1931"
transform="rotate(-39.3441)"
id="rect2" />
<circle
cx="337.54071"
cy="163.28656"
r="25.9027"
id="circle2" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
class="logo"
width="36"
height="30.9767"
viewBox="0 0 36 30.9767"
version="1.1"
id="svg2"
sodipodi:docname="featherless.svg"
inkscape:version="1.3 (0e150ed, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="4.0920245"
inkscape:cx="75.268366"
inkscape:cy="15.151424"
inkscape:window-width="1512"
inkscape:window-height="857"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
d="M 34.0866,1.68482 C 32.2902,0.5825 29.863,0 27.0672,0 22.7842,0 18.0653,1.35865 13.8276,3.72206 L 13.7979,3.71083 c 0,0 -0.0042,0.02261 -0.0065,0.0334 C 12.5086,4.4617 11.2656,5.2629 10.0981,6.15731 3.22112,11.4248 1.29519,17.6748 2.92004,21.0156 1.14142,24.0728 0.0457,27.2332 0,30.9767 3.41949,24.421 5.4719,19.108 16.6146,10.1637 13.4309,10.8501 7.9281,14.1057 4.2271,19.0459 3.87793,16.156 6.1477,11.4895 11.2033,7.6174 11.8435,7.127 12.5092,6.66864 13.1886,6.23374 12.6577,7.8934 12.8269,7.4806 11.7254,9.8076 c 1.6289,-1.551 2.7014,-2.5081 4.3096,-5.16615 2.088,-1.03181 4.2598,-1.80301 6.4132,-2.2691 -0.3563,1.18836 -1.0345,3.20231 -1.9527,4.79455 0,0 2.3303,-0.50255 4.2563,-0.38902 -1.0523,1.16802 -1.9991,2.43152 -2.9592,3.72332 -1.3149,1.7684 -2.6742,3.5971 -4.4148,5.2993 -0.2095,0.2049 -0.4098,0.3907 -0.6129,0.5825 -2.6747,-0.2576 -4.4414,0.7485 -6.0966,2.5259 1.3054,-0.6123 3.059,-1.1165 4.1583,-0.813 -2.0258,1.662 -5.216,3.8529 -7.8373,3.6725 -0.4971,0.7611 -0.5285,0.7844 -1.0749,1.7038 4.252,1.0648 9.5926,-3.2817 12.7354,-6.3561 1.8428,-1.803 3.2466,-3.6904 4.6036,-5.5149 2.7947,-3.7585 5.2082,-7.0038 10.5619,-8.2388 L 36,2.85877 Z"
class="logo-mark"
id="path1"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="88.001465mm"
height="81.280983mm"
version="1.1"
id="svg9"
sodipodi:docname="huggingface.svg"
inkscape:version="1.3 (0e150ed, 2023-07-21)"
viewBox="0 0 88.001465 81.280983"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs9" />
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.68605868"
inkscape:cx="424.16197"
inkscape:cy="154.50573"
inkscape:window-width="1512"
inkscape:window-height="857"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg9"
inkscape:clip-to-page="false"
inkscape:document-units="mm" />
<path
id="path2-9"
style="display:inline;"
d="M 40.855186,0.10840487 A 38.75,38.75 0 0 0 5.0016702,38.750983 a 38.75,38.75 0 0 0 1.7871095,11.589844 7.1,7.1 0 0 1 1.871094,0.291015 5.97,5.97 0 0 1 1.330078,-3.761718 c 0.02089,-0.02502 0.04515,-0.04576 0.06641,-0.07031 a 34.75,34.75 0 0 1 -1.0547201,-8.048831 34.750014,34.750014 0 0 1 69.5000291,0 34.75,34.75 0 0 1 -0.957032,7.630859 c 0.163358,0.152193 0.321565,0.31255 0.466797,0.488282 a 5.97,5.97 0 0 1 1.330078,3.761718 7.1,7.1 0 0 1 1.337891,-0.207031 A 38.75,38.75 0 0 0 82.501671,38.750983 38.75,38.75 0 0 0 40.855186,0.10840487 Z M 48.015342,73.165045 a 34.75,34.75 0 0 1 -8.044921,0.03906 c -0.396448,0.901178 -0.898324,1.811009 -1.529297,2.736328 -0.233308,0.342701 -0.489288,0.664577 -0.75586,0.974609 a 38.75,38.75 0 0 0 12.574219,-0.06641 c -0.245421,-0.290144 -0.482504,-0.589875 -0.699219,-0.908203 -0.639915,-0.938432 -1.14623,-1.86177 -1.544922,-2.775391 z M 73.940733,45.000983 c 1.62,0 3.07,0.66 4.07,1.87 a 5.97,5.97 0 0 1 1.33,3.76 7.1,7.1 0 0 1 1.95,-0.3 c 1.55,0 2.95,0.59 3.94,1.66 a 5.8,5.8 0 0 1 0.8,7 5.3,5.3 0 0 1 1.78,2.82 c 0.24,0.9 0.48,2.8 -0.8,4.74 a 5.22,5.22 0 0 1 0.37,5.02 c -1.02,2.32 -3.57,4.14 -8.51,6.1 -3.08,1.22 -5.9,2 -5.92,2.01 a 44.33,44.33 0 0 1 -10.93,1.6 c -5.86,0 -10.05,-1.8 -12.46,-5.34 -3.88,-5.69 -3.33,-10.9 1.7,-15.92 2.78,-2.78 4.63,-6.87 5.01,-7.77 0.78,-2.66 2.83,-5.62 6.24,-5.62 a 5.7,5.7 0 0 1 4.6,2.46 c 1,-1.26 1.98,-2.25 2.87,-2.82 a 7.4,7.4 0 0 1 3.96,-1.27 z m 0,4 c -0.51,0 -1.13,0.22 -1.82,0.65 -2.13,1.36 -6.25,8.43 -7.76,11.18 a 2.43,2.43 0 0 1 -2.14,1.31 c -1.54,0 -2.75,-1.53 -0.14,-3.48 3.91,-2.93 2.54,-7.72 0.67,-8.01 a 1.54,1.54 0 0 0 -0.24,-0.02 c -1.7,0 -2.45,2.93 -2.45,2.93 0,0 -2.2,5.52 -5.97,9.3 -3.78,3.77 -3.98,6.8 -1.22,10.83 1.87,2.75 5.47,3.58 9.15,3.58 3.82,0 7.73,-0.9 9.93,-1.46 0.1,-0.03 13.45,-3.8 11.76,-7 -0.29,-0.54 -0.75,-0.76 -1.34,-0.76 -2.38,0 -6.71,3.54 -8.57,3.54 -0.42,0 -0.71,-0.17 -0.83,-0.6 -0.8,-2.85 12.05,-4.05 10.97,-8.17 -0.19,-0.73 -0.7,-1.02 -1.44,-1.02 -3.14,0 -10.2,5.53 -11.68,5.53 -0.1,0 -0.19,-0.03 -0.23,-0.1 -0.74,-1.2 -0.34,-2.04 4.88,-5.2 5.23,-3.16 8.9,-5.06 6.8,-7.33 -0.23,-0.26 -0.57,-0.38 -0.98,-0.38 -3.18,0 -10.67,6.82 -10.67,6.82 0,0 -2.02,2.1 -3.24,2.1 a 0.74,0.74 0 0 1 -0.68,-0.38 c -0.87,-1.46 8.05,-8.22 8.55,-11.01 0.34,-1.9 -0.24,-2.85 -1.31,-2.85 z m -6.69,-15 a 3.25,3.25 0 1 0 0,-6.5 3.25,3.25 0 0 0 0,6.5 z m -46.5,0 a 3.25,3.25 0 1 0 0,-6.5 3.25,3.25 0 0 0 0,6.5 z m -6.69,11 c -1.62,0 -3.06,0.66 -4.0700003,1.87 a 5.97,5.97 0 0 0 -1.33,3.76 7.1,7.1 0 0 0 -1.94,-0.3 c -1.55,0 -2.95,0.59 -3.94,1.66 a 5.8,5.8 0 0 0 -0.8,7 5.3,5.3 0 0 0 -1.79000004,2.82 c -0.24,0.9 -0.48,2.8 0.8,4.74 a 5.22,5.22 0 0 0 -0.37,5.02 c 1.02000004,2.32 3.57000004,4.14 8.52000004,6.1 3.0700003,1.22 5.8900003,2 5.9100003,2.01 a 44.33,44.33 0 0 0 10.93,1.6 c 5.86,0 10.05,-1.8 12.46,-5.34 3.88,-5.69 3.33,-10.9 -1.7,-15.92 -2.77,-2.78 -4.62,-6.87 -5,-7.77 -0.78,-2.66 -2.84,-5.62 -6.25,-5.62 a 5.7,5.7 0 0 0 -4.6,2.46 c -1,-1.26 -1.98,-2.25 -2.86,-2.82 a 7.4,7.4 0 0 0 -3.97,-1.27 z m 0,4 c 0.51,0 1.14,0.22 1.82,0.65 2.14,1.36 6.25,8.43 7.76,11.18 0.5,0.92 1.37,1.31 2.14,1.31 1.55,0 2.75,-1.53 0.15,-3.48 -3.92,-2.93 -2.55,-7.72 -0.68,-8.01 0.08,-0.02 0.17,-0.02 0.24,-0.02 1.7,0 2.45,2.93 2.45,2.93 0,0 2.2,5.52 5.98,9.3 3.77,3.77 3.97,6.8 1.22,10.83 -1.88,2.75 -5.47,3.58 -9.16,3.58 -3.81,0 -7.73,-0.9 -9.92,-1.46 -0.11,-0.03 -13.4500003,-3.8 -11.7600003,-7 0.28,-0.54 0.75,-0.76 1.34,-0.76 2.38,0 6.7000003,3.54 8.5700003,3.54 0.41,0 0.7,-0.17 0.83,-0.6 0.79,-2.85 -12.0600003,-4.05 -10.9800003,-8.17 0.2,-0.73 0.71,-1.02 1.44,-1.02 3.14,0 10.2000003,5.53 11.6800003,5.53 0.11,0 0.2,-0.03 0.24,-0.1 0.74,-1.2 0.33,-2.04 -4.9,-5.2 -5.2100003,-3.16 -8.8800003,-5.06 -6.8000003,-7.33 0.24,-0.26 0.58,-0.38 1,-0.38 3.17,0 10.6600003,6.82 10.6600003,6.82 0,0 2.02,2.1 3.25,2.1 0.28,0 0.52,-0.1 0.68,-0.38 0.86,-1.46 -8.06,-8.22 -8.56,-11.01 -0.34,-1.9 0.24,-2.85 1.31,-2.85 z m 21.91,2 a 8.7,8.7 0 0 1 5.3,-4.49 c 0.4,-0.12 0.81,0.57 1.24,1.28 0.4,0.68 0.82,1.37 1.24,1.37 0.45,0 0.9,-0.68 1.33,-1.35 0.45,-0.7 0.89,-1.38 1.32,-1.25 a 8.61,8.61 0 0 1 5,4.17 c 3.73,-2.94 5.1,-7.74 5.1,-10.7 0,-2.34 -1.57,-1.6 -4.09,-0.36 l -0.14,0.07 c -2.31,1.15 -5.39,2.67 -8.77,2.67 -3.38,0 -6.45,-1.52 -8.77,-2.67 -2.6,-1.29 -4.23,-2.1 -4.23,0.29 0,3.05 1.46,8.06 5.47,10.97 z m 19.07,-21.7 c 1.28,0.44 1.78,3.06 3.07,2.38 a 5,5 0 1 0 -6.76,-2.07 c 0.61,1.15 2.55,-0.72 3.7,-0.32 z m -23.55,0 c -1.28,0.44 -1.79,3.06 -3.07,2.38 a 5,5 0 1 1 6.76,-2.07 c -0.61,1.15 -2.56,-0.72 -3.7,-0.32 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -423,7 +423,7 @@
</span>
</div>
</div>
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq">
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
<div class="range-block-title" data-i18n="Temperature">
Temperature
</div>
@ -488,7 +488,7 @@
</div>
</div>
</div>
<div data-newbie-hidden class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq">
<div data-newbie-hidden class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
<div class="range-block-title" data-i18n="Top P">
Top P
</div>
@ -1477,7 +1477,7 @@
</label>
</div>
</div>
<div data-tg-type="mancer, ooba, koboldcpp, vllm, aphrodite, llamacpp, ollama, infermaticai" data-newbie-hidden class="flex-container flexFlowColumn alignitemscenter flexBasis48p flexGrow flexShrink gap0">
<div data-tg-type="mancer, ooba, koboldcpp, vllm, aphrodite, llamacpp, ollama, infermaticai, huggingface" data-newbie-hidden class="flex-container flexFlowColumn alignitemscenter flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="Seed" class="textAlignCenter">Seed</small>
<input type="number" id="seed_textgenerationwebui" class="text_pole textAlignCenter" min="-1" value="-1" maxlength="100" />
</div>
@ -2029,6 +2029,8 @@
<option value="ooba" data-i18n="Default (completions compatible)">Default [OpenAI /completions compatible: oobabooga, LM Studio, etc.]</option>
<option value="aphrodite">Aphrodite</option>
<option value="dreamgen">DreamGen</option>
<option value="featherless">Featherless</option>
<option value="huggingface">HuggingFace (Inference Endpoint)</option>
<option value="infermaticai">InfermaticAI</option>
<option value="koboldcpp">KoboldCpp</option>
<option value="llamacpp">llama.cpp</option>
@ -2182,6 +2184,27 @@
</div>
<input id="custom_model_textgenerationwebui" class="text_pole wide100p" maxlength="500" placeholder="Custom model (optional)" data-i18n="[placeholder]Custom model (optional)" type="text">
</div>
<div data-tg-type="featherless" class="flex-container flexFlowColumn">
<div class="flex-container flexFlowColumn">
<a href="https://featherless.ai/models/" target="_blank" rel="noopener noreferrer">
featherless.ai
</a>
</div>
<h4 data-i18n="API key (optional)">API key</h4>
<div class="flex-container">
<input id="api_key_featherless" name="api_key_featherless" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<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_featherless">
</div>
</div>
<div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<select id="featherless_model">
<option data-i18n="-- Connect to the API --">
-- Connect to the API --
</option>
</select>
</div>
<div data-tg-type="vllm">
<div class="flex-container flexFlowColumn">
<a href="https://github.com/vllm-project/vllm" target="_blank" data-i18n="vllm-project/vllm">
@ -2211,6 +2234,22 @@
</select>
</div>
</div>
<div data-tg-type="huggingface">
<h4 data-i18n="HuggingFace Token">HuggingFace Token</h4>
<div class="flex-container">
<input id="api_key_huggingface" name="api_key_huggingface" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<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_huggingface">
</div>
</div>
<div data-for="api_key_huggingface" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<div class="flex1">
<h4 data-i18n="Endpoint URL">Endpoint URL</h4>
<small data-i18n="Example: https://****.endpoints.huggingface.cloud">Example: https://****.endpoints.huggingface.cloud</small>
<input id="huggingface_api_url_text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-server-history="huggingface">
</div>
</div>
<div data-tg-type="aphrodite">
<div class="flex-container flexFlowColumn">
@ -2332,7 +2371,7 @@
</div>
</div>
<div class="flex-container">
<div id="api_button_textgenerationwebui" class="api_button menu_button" type="submit" data-i18n="Connect" data-server-connect="ooba_blocking,vllm,aphrodite,tabby,koboldcpp,ollama,llamacpp">Connect</div>
<div id="api_button_textgenerationwebui" class="api_button menu_button" type="submit" data-i18n="Connect" data-server-connect="ooba_blocking,vllm,aphrodite,tabby,koboldcpp,ollama,llamacpp,huggingface">Connect</div>
<div data-tg-type="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div>
<div class="api_loading menu_button" data-i18n="Cancel">Cancel</div>
</div>
@ -2360,6 +2399,7 @@
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
</optgroup>
<optgroup>
<option value="01ai">01.AI (Yi)</option>
<option value="ai21">AI21</option>
<option value="claude">Claude</option>
<option value="cohere">Cohere</option>
@ -2890,6 +2930,23 @@
<option value="claude">Claude</option>
</select>
</form>
<div id="01ai_form" data-source="01ai">
<h4>
<a data-i18n="01.AI API Key" href="https://platform.01.ai/" target="_blank" rel="noopener noreferrer">
01.AI API Key
</a>
</h4>
<div class="flex-container">
<input id="api_key_01ai" name="api_key_01ai" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
<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_01ai"></div>
</div>
<div data-for="api_key_01ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<h4 data-i18n="01.AI Model">01.AI Model</h4>
<select id="model_01ai_select">
</select>
</div>
<div class="flex-container flex">
<div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
<div class="api_loading menu_button" data-i18n="Cancel">Cancel</div>
@ -3749,95 +3806,6 @@
<input class="neo-range-slider" type="range" id="shadow_width" name="shadow_width" min="0" max="5" step="1">
<input class="neo-range-input" type="number" min="0" max="5" step="1" data-for="shadow_width" id="shadow_width_counter">
</div>
<!-- <div data-newbie-hidden class="range-block">
<label for="compact_input_area" class="range-block-title">
<span data-i18n="Chat Width (PC)">
Chat Width
</span>
<i class="fa-solid fa-desktop"></i>
</label>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input id="chat_width_slider" class="wide100p" type="range" min="25" max="100" step="1" value="50">
</div>
<div class="range-block-counter">
<input type="number" min="25" max="100" step="1" value="50" data-for="chat_width_slider" id="chat_width_slider_counter">
</div>
</div>
</div> -->
<!-- <div id="font-scale-block" class="range-block">
<div class="range-block-title" data-i18n="Font Scale">
Font Scale
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="font_scale" name="font_scale" min="0.8" max="1.2" step="0.01">
</div>
<div class="range-block-counter">
<input type="number" min="0.8" max="1.2" step="0.01" data-for="font_scale" id="font_scale_counter">
</div>
</div>
</div> -->
<!-- <div id="blur-strength-block" class="range-block">
<div class="range-block-title" data-i18n="Blur Strength">
Blur Strength
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="blur_strength" name="blur_strength" min="0" max="30" step="1">
</div>
<div class="range-block-counter">
<input type="number" min="0" max="30" step="1" data-for="blur_strength" id="blur_strength_counter">
</div>
</div>
</div> -->
<!-- <div id="shadow-width-block" class="range-block">
<div class="range-block-title" data-i18n="Text Shadow Width">
Text Shadow Width
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="shadow_width" name="shadow_width" min="0" max="5" step="1">
</div>
<div class="range-block-counter">
<input type="number" min="0" max="5" step="1" data-for="shadow_width" id="shadow_width_counter">
</div>
</div>
</div> -->
<!-- <div id="chat-truncation-block" class="range-block">
<label for="compact_input_area" class="range-block-title">
<span data-i18n="Chat Truncation">
Chat Truncation
</span>
<small data-i18n="(0 = unlimited)">(0 = unlimited)</small>
</label>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="chat_truncation" name="chat_truncation" min="0" max="1000" step="25">
</div>
<div class="range-block-counter">
<input type="number" min="0" max="1000" step="1" data-for="chat_truncation" id="chat_truncation_counter">
</div>
</div>
</div> -->
<!-- <div id="streaming-fps" class="range-block">
<div class="range-block-title" data-i18n="Streaming FPS">
Streaming FPS
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="streaming_fps" name="streaming_fps" min="5" max="100" step="5">
</div>
<div class="range-block-counter">
<input type="number" min="5" max="100" step="1" data-for="streaming_fps" id="streaming_fps_counter">
</div>
</div>
</div> -->
</div>
<hr>
<div name="themeToggles">
@ -4883,6 +4851,7 @@
<img class="popup-crop-image" src="">
</div>
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
<div class="popup-inputs"></div>
<div class="popup-controls">
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
<div class="popup-button-cancel menu_button result-control" data-result="0" data-i18n="Cancel">Cancel</div>

View File

@ -22,7 +22,7 @@ import {
parseTabbyLogprobs,
} from './scripts/textgen-settings.js';
const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER } = textgen_types;
const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER, FEATHERLESS } = textgen_types;
import {
world_info,
@ -223,7 +223,7 @@ import {
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
import { hideLoader, showLoader } from './scripts/loader.js';
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js';
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js';
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
import { initPresetManager } from './scripts/preset-manager.js';
import { MacrosParser, evaluateMacros } from './scripts/macros.js';
@ -1160,6 +1160,9 @@ async function getStatusTextgen() {
} else if (textgen_settings.type === APHRODITE) {
loadAphroditeModels(data?.data);
setOnlineStatus(textgen_settings.aphrodite_model);
} else if (textgen_settings.type === FEATHERLESS) {
loadFeatherlessModels(data?.data);
setOnlineStatus(textgen_settings.featherless_model);
} else {
setOnlineStatus(data?.result);
}
@ -5775,6 +5778,7 @@ export async function saveChat(chat_name, withMetadata, mesId) {
contentType: 'application/json',
success: function (data) { },
error: function (jqXHR, exception) {
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Chat could not be saved');
console.log(exception);
console.log(jqXHR);
},
@ -7800,6 +7804,7 @@ window['SillyTavern'].getContext = function () {
*/
registerHelper: () => { },
registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
registedDebugFunction: registerDebugFunction,
/**
* @deprecated Use renderExtensionTemplateAsync instead.
@ -8289,6 +8294,11 @@ const CONNECT_API_MAP = {
button: '#api_button_openai',
source: chat_completion_sources.GROQ,
},
'01ai': {
selected: 'openai',
button: '#api_button_openai',
source: chat_completion_sources.ZEROONEAI,
},
'infermaticai': {
selected: 'textgenerationwebui',
button: '#api_button_textgenerationwebui',
@ -8304,6 +8314,11 @@ const CONNECT_API_MAP = {
button: '#api_button_textgenerationwebui',
type: textgen_types.OPENROUTER,
},
'huggingface': {
selected: 'textgenerationwebui',
button: '#api_button_textgenerationwebui',
type: textgen_types.HUGGINGFACE,
},
};
async function selectContextCallback(_, name) {
@ -8551,6 +8566,39 @@ async function doImpersonate(args, prompt) {
return '';
}
export async function doNewChat({ deleteCurrentChat = false } = {}) {
//Make a new chat for selected character
if ((!selected_group && this_chid == undefined) || menu_type == 'create') {
return;
}
//Fix it; New chat doesn't create while open create character menu
await clearChat();
chat.length = 0;
chat_file_for_del = getCurrentChatDetails()?.sessionName;
// Make it easier to find in backups
if (deleteCurrentChat) {
await saveChatConditional();
}
if (selected_group) {
await createNewGroupChat(selected_group);
if (deleteCurrentChat) await deleteGroupChat(selected_group, chat_file_for_del);
}
else {
//RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
chat_metadata = {};
characters[this_chid].chat = `${name2} - ${humanizedDateTime()}`;
$('#selected_chat_pole').val(characters[this_chid].chat);
await getChat();
await createOrEditCharacter(new CustomEvent('newChat'));
if (deleteCurrentChat) await delChat(chat_file_for_del + '.jsonl');
}
}
async function doDeleteChat() {
await displayPastChats();
let currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross');
@ -8659,13 +8707,11 @@ function doCloseChat() {
* it proceeds to delete character from UI and saves settings.
* In case of error during the fetch request, it logs the error details.
*
* @param {string} popup_type - The type of popup currently active.
* @param {string} this_chid - The character ID to be deleted.
* @param {boolean} delete_chats - Whether to delete chats or not.
*/
export async function handleDeleteCharacter(popup_type, this_chid, delete_chats) {
if (popup_type !== 'del_ch' ||
!characters[this_chid]) {
export async function handleDeleteCharacter(this_chid, delete_chats) {
if (!characters[this_chid]) {
return;
}
@ -8711,6 +8757,8 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
await eventSource.emit(event_types.CHAT_DELETED, name);
}
}
eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] });
}
/**
@ -9212,46 +9260,9 @@ jQuery(async function () {
}, 2000);
}
}
if (popup_type == 'del_ch') {
const deleteChats = !!$('#del_char_checkbox').prop('checked');
eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] });
await handleDeleteCharacter(popup_type, this_chid, deleteChats);
}
if (popup_type == 'alternate_greeting' && menu_type !== 'create') {
createOrEditCharacter();
}
//Make a new chat for selected character
if (
popup_type == 'new_chat' &&
(selected_group || this_chid !== undefined) &&
menu_type != 'create'
) {
//Fix it; New chat doesn't create while open create character menu
await clearChat();
chat.length = 0;
chat_file_for_del = getCurrentChatDetails()?.sessionName;
const isDelChatCheckbox = document.getElementById('del_chat_checkbox')?.checked;
// Make it easier to find in backups
if (isDelChatCheckbox) {
await saveChatConditional();
}
if (selected_group) {
await createNewGroupChat(selected_group);
if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del);
}
else {
//RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
chat_metadata = {};
characters[this_chid].chat = `${name2} - ${humanizedDateTime()}`;
$('#selected_chat_pole').val(characters[this_chid].chat);
await getChat();
await createOrEditCharacter(new CustomEvent('newChat'));
if (isDelChatCheckbox) await delChat(chat_file_for_del + '.jsonl');
}
}
if (dialogueResolve) {
if (popup_type == 'input') {
@ -9297,15 +9308,27 @@ jQuery(async function () {
$('#form_create').submit(createOrEditCharacter);
$('#delete_button').on('click', function () {
callPopup(`
<h3>Delete the character?</h3>
$('#delete_button').on('click', async function () {
if (!this_chid) {
toastr.warning('No character selected.');
return;
}
let deleteChats = false;
const confirm = await Popup.show.confirm('Delete the character?', `
<b>THIS IS PERMANENT!<br><br>
<label for="del_char_checkbox" class="checkbox_label justifyCenter">
<input type="checkbox" id="del_char_checkbox" />
<small>Also delete the chat files</small>
</label><br></b>`, 'del_ch', '',
);
</label></b>`, {
onClose: () => deleteChats = !!$('#del_char_checkbox').prop('checked'),
});
if (!confirm) {
return;
}
await deleteCharacter(characters[this_chid].avatar, { deleteChats: deleteChats });
});
//////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS ///////////////
@ -9456,6 +9479,8 @@ jQuery(async function () {
{ id: 'api_key_openrouter-tg', secret: SECRET_KEYS.OPENROUTER },
{ id: 'api_key_koboldcpp', secret: SECRET_KEYS.KOBOLDCPP },
{ id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP },
{ id: 'api_key_featherless', secret: SECRET_KEYS.FEATHERLESS },
{ id: 'api_key_huggingface', secret: SECRET_KEYS.HUGGINGFACE },
];
for (const key of keys) {
@ -9567,14 +9592,20 @@ jQuery(async function () {
else if (id == 'option_start_new_chat') {
if ((selected_group || this_chid !== undefined) && !is_send_press) {
callPopup(`
<h3>Start new chat?</h3><br>
let deleteCurrentChat = false;
const result = await Popup.show.confirm('Start new chat?', `
<label for="del_chat_checkbox" class="checkbox_label justifyCenter"
title="If necessary, you can later restore this chat file from the /backups folder">
<input type="checkbox" id="del_chat_checkbox" />
<small>Also delete the current chat file</small>
</label><br>
`, 'new_chat', '');
</label>`, {
onClose: () => deleteCurrentChat = !!$('#del_chat_checkbox').prop('checked'),
});
if (!result) {
return;
}
await doNewChat({ deleteCurrentChat: deleteCurrentChat });
}
}

View File

@ -360,6 +360,7 @@ function RA_autoconnect(PrevApi) {
|| (textgen_settings.type === textgen_types.INFERMATICAI && secret_state[SECRET_KEYS.INFERMATICAI])
|| (textgen_settings.type === textgen_types.DREAMGEN && secret_state[SECRET_KEYS.DREAMGEN])
|| (textgen_settings.type === textgen_types.OPENROUTER && secret_state[SECRET_KEYS.OPENROUTER])
|| (textgen_settings.type === textgen_types.FEATHERLESS && secret_state[SECRET_KEYS.FEATHERLESS])
) {
$('#api_button_textgenerationwebui').trigger('click');
}
@ -379,6 +380,7 @@ function RA_autoconnect(PrevApi) {
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
|| (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
) {
$('#api_button_openai').trigger('click');
@ -476,8 +478,8 @@ export function dragElement(elmnt) {
}
const style = getComputedStyle(target);
height = parseInt(style.height)
width = parseInt(style.width)
height = parseInt(style.height);
width = parseInt(style.width);
top = parseInt(style.top);
left = parseInt(style.left);
right = parseInt(style.right);
@ -723,6 +725,10 @@ export function initRossMods() {
RA_autoconnect();
}
if (getParsedUA()?.os?.name === 'iOS') {
document.body.classList.add('ios');
}
$('#main_api').change(function () {
var PrevAPI = main_api;
setTimeout(() => RA_autoconnect(PrevAPI), 100);
@ -926,8 +932,8 @@ export function initRossMods() {
return false;
}
$(document).on('keydown', function (event) {
processHotkeys(event.originalEvent);
$(document).on('keydown', async function (event) {
await processHotkeys(event.originalEvent);
});
const hotkeyTargets = {
@ -939,7 +945,7 @@ export function initRossMods() {
/**
* @param {KeyboardEvent} event
*/
function processHotkeys(event) {
async function processHotkeys(event) {
//Enter to send when send_textarea in focus
if (document.activeElement == hotkeyTargets['send_textarea']) {
const sendOnEnter = shouldSendOnEnter();
@ -1003,21 +1009,17 @@ export function initRossMods() {
if (skipConfirm) {
doRegenerate();
} else {
Popup.show.confirm('Regenerate Message', `
<span>Are you sure you want to regenerate the latest message?</span>
<label class="checkbox_label justifyCenter marginTop10" for="regenerateWithCtrlEnter">
<input type="checkbox" id="regenerateWithCtrlEnter">
Don't ask again
</label>`, {
onClose: (popup) => {
if (!popup.result) {
let regenerateWithCtrlEnter = false;
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false,
});
if (!result) {
return;
}
const regenerateWithCtrlEnter = $('#regenerateWithCtrlEnter').prop('checked');
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
doRegenerate();
},
})
}
return;
} else {

View File

@ -24,6 +24,7 @@ import {
saveGroupBookmarkChat,
selected_group,
} from './group-chats.js';
import { Popup } from './popup.js';
import { createTagMapFromList } from './tags.js';
import {
@ -239,8 +240,7 @@ async function convertSoloToGroupChat() {
return;
}
const confirm = await callPopup('Are you sure you want to convert this chat to a group chat?', 'confirm');
const confirm = await Popup.show.confirm('Convert to group chat', 'Are you sure you want to convert this chat to a group chat?<br />This cannot be reverted.');
if (!confirm) {
return;
}
@ -336,6 +336,7 @@ async function convertSoloToGroupChat() {
if (!createChatResponse.ok) {
console.error('Group chat creation unsuccessful');
toastr.error('Group chat creation unsuccessful');
return;
}

View File

@ -447,7 +447,7 @@ async function summarizeCallback(args, text) {
}
const source = args.source || extension_settings.memory.source;
const prompt = substituteParamsExtended((resolveVariable(args.prompt) || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords });
const prompt = substituteParamsExtended((args.prompt || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords });
try {
switch (source) {
@ -923,10 +923,8 @@ jQuery(async function () {
SlashCommandNamedArgument.fromProps({
name: 'prompt',
description: 'prompt to use for summarization',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: '',
enumProvider: commonEnumProviders.variables('all'),
forceEnum: false,
}),
],
unnamedArgumentList: [

View File

@ -257,6 +257,7 @@ export class QuickReplySet {
this.rerender();
} else {
warn(`Failed to save Quick Reply Set: ${this.name}`);
console.error('QR could not be saved', response);
}
}

View File

@ -349,7 +349,7 @@ function migrateSettings() {
/**
* /regex slash command callback
* @param {object} args Named arguments
* @param {{name: string}} args Named arguments
* @param {string} value Unnamed argument
* @returns {string} The regexed string
*/
@ -359,11 +359,11 @@ function runRegexCallback(args, value) {
return value;
}
const scriptName = String(resolveVariable(args.name));
const scriptName = args.name;
const scripts = getRegexScripts();
for (const script of scripts) {
if (String(script.scriptName).toLowerCase() === String(scriptName).toLowerCase()) {
if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
if (script.disabled) {
toastr.warning(`Regex script "${scriptName}" is disabled.`);
return value;
@ -588,7 +588,7 @@ jQuery(async () => {
SlashCommandNamedArgument.fromProps({
name: 'name',
description: 'script name',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.regexScripts,
}),

View File

@ -2206,7 +2206,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
}
const dimensions = setTypeSpecificDimensions(generationType);
let negativePromptPrefix = resolveVariable(args?.negative) || '';
let negativePromptPrefix = args?.negative || '';
let imagePath = '';
try {
@ -3356,8 +3356,7 @@ jQuery(async () => {
SlashCommandNamedArgument.fromProps({
name: 'negative',
description: 'negative prompt prefix',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
enumProvider: commonEnumProviders.variables('all'),
typeList: [ARGUMENT_TYPE.STRING],
}),
],
unnamedArgumentList: [

View File

@ -492,7 +492,13 @@ async function saveGroupChat(groupId, shouldSaveGroup) {
body: JSON.stringify({ id: chat_id, chat: [...chat] }),
});
if (shouldSaveGroup && response.ok) {
if (!response.ok) {
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Group Chat could not be saved');
console.error('Group chat could not be saved', response);
return;
}
if (shouldSaveGroup) {
await editGroup(groupId, false, false);
}
}
@ -546,9 +552,11 @@ export async function renameGroupMember(oldAvatar, newAvatar, newName) {
body: JSON.stringify({ id: chatId, chat: [...messages] }),
});
if (saveChatResponse.ok) {
console.log(`Renamed character ${newName} in group chat: ${chatId}`);
if (!saveChatResponse.ok) {
throw new Error('Group member could not be renamed');
}
console.log(`Renamed character ${newName} in group chat: ${chatId}`);
}
}
}
@ -1828,11 +1836,16 @@ export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
await editGroup(groupId, true, false);
await fetch('/api/chats/group/save', {
const response = await fetch('/api/chats/group/save', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ id: name, chat: [...trimmed_chat] }),
});
if (!response.ok) {
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Group chat could not be saved');
console.error('Group chat could not be saved', response);
}
}
function onSendTextareaInput() {

View File

@ -58,6 +58,30 @@ export class MacrosParser {
this.#macros.set(key, value);
}
/**
* Unregisters a global macro with the given key
*
* @param {string} key Macro name (key)
*/
static unregisterMacro(key) {
if (typeof key !== 'string') {
throw new Error('Macro key must be a string');
}
// Allowing surrounding whitespace would just create more confusion...
key = key.trim();
if (!key) {
throw new Error('Macro key must not be empty or whitespace only');
}
const deleted = this.#macros.delete(key);
if (!deleted) {
console.warn(`Macro ${key} was not registered`);
}
}
/**
* Populate the env object with macro values from the current context.
* @param {EnvObject} env Env object for the current evaluation context

View File

@ -181,6 +181,7 @@ export const chat_completion_sources = {
COHERE: 'cohere',
PERPLEXITY: 'perplexity',
GROQ: 'groq',
ZEROONEAI: '01ai',
};
const character_names_behavior = {
@ -251,6 +252,7 @@ const default_settings = {
cohere_model: 'command-r',
perplexity_model: 'llama-3-70b-instruct',
groq_model: 'llama3-70b-8192',
zerooneai_model: 'yi-large',
custom_model: '',
custom_url: '',
custom_include_body: '',
@ -329,6 +331,7 @@ const oai_settings = {
cohere_model: 'command-r',
perplexity_model: 'llama-3-70b-instruct',
groq_model: 'llama3-70b-8192',
zerooneai_model: 'yi-large',
custom_model: '',
custom_url: '',
custom_include_body: '',
@ -1470,6 +1473,8 @@ function getChatCompletionModel() {
return oai_settings.perplexity_model;
case chat_completion_sources.GROQ:
return oai_settings.groq_model;
case chat_completion_sources.ZEROONEAI:
return oai_settings.zerooneai_model;
default:
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
}
@ -1566,6 +1571,23 @@ function saveModelList(data) {
$('#model_custom_select').val(model_list[0].id).trigger('change');
}
}
if (oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) {
$('#model_01ai_select').empty();
model_list.forEach((model) => {
$('#model_01ai_select').append(
$('<option>', {
value: model.id,
text: model.id,
}));
});
if (!oai_settings.zerooneai_model && model_list.length > 0) {
oai_settings.zerooneai_model = model_list[0].id;
}
$('#model_01ai_select').val(oai_settings.zerooneai_model).trigger('change');
}
}
function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
@ -1697,6 +1719,7 @@ async function sendOpenAIRequest(type, messages, signal) {
const isCohere = oai_settings.chat_completion_source == chat_completion_sources.COHERE;
const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;
const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
const isTextCompletion = (isOAI && textCompletionModels.includes(oai_settings.openai_model)) || (isOpenRouter && oai_settings.openrouter_force_instruct && power_user.instruct.enabled);
const isQuiet = type === 'quiet';
const isImpersonate = type === 'impersonate';
@ -1863,6 +1886,17 @@ async function sendOpenAIRequest(type, messages, signal) {
delete generate_data.n;
}
// https://platform.01.ai/docs#request-body
if (is01AI) {
delete generate_data.logprobs;
delete generate_data.logit_bias;
delete generate_data.top_logprobs;
delete generate_data.n;
delete generate_data.frequency_penalty;
delete generate_data.presence_penalty;
delete generate_data.stop;
}
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere) && oai_settings.seed >= 0) {
generate_data['seed'] = oai_settings.seed;
}
@ -2912,6 +2946,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.cohere_model = settings.cohere_model ?? default_settings.cohere_model;
oai_settings.perplexity_model = settings.perplexity_model ?? default_settings.perplexity_model;
oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@ -2988,6 +3023,7 @@ function loadOpenAISettings(data, settings) {
$(`#model_perplexity_select option[value="${oai_settings.perplexity_model}"`).attr('selected', true);
$('#model_groq_select').val(oai_settings.groq_model);
$(`#model_groq_select option[value="${oai_settings.groq_model}"`).attr('selected', true);
$('#model_01ai_select').val(oai_settings.zerooneai_model);
$('#custom_model_id').val(oai_settings.custom_model);
$('#custom_api_url_text').val(oai_settings.custom_url);
$('#openai_max_context').val(oai_settings.openai_max_context);
@ -3240,6 +3276,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
cohere_model: settings.cohere_model,
perplexity_model: settings.perplexity_model,
groq_model: settings.groq_model,
zerooneai_model: settings.zerooneai_model,
custom_model: settings.custom_model,
custom_url: settings.custom_url,
custom_include_body: settings.custom_include_body,
@ -3328,6 +3365,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
}
} else {
toastr.error('Failed to save preset');
throw new Error('Failed to save preset');
}
}
@ -3640,6 +3678,7 @@ function onSettingsPresetChange() {
cohere_model: ['#model_cohere_select', 'cohere_model', false],
perplexity_model: ['#model_perplexity_select', 'perplexity_model', false],
groq_model: ['#model_groq_select', 'groq_model', false],
zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
custom_model: ['#custom_model_id', 'custom_model', false],
custom_url: ['#custom_api_url_text', 'custom_url', false],
custom_include_body: ['#custom_include_body', 'custom_include_body', false],
@ -3882,6 +3921,11 @@ async function onModelChange() {
oai_settings.groq_model = value;
}
if ($(this).is('#model_01ai_select')) {
console.log('01.AI model changed to', value);
oai_settings.zerooneai_model = value;
}
if (value && $(this).is('#model_custom_select')) {
console.log('Custom model changed to', value);
oai_settings.custom_model = value;
@ -3997,7 +4041,9 @@ async function onModelChange() {
}
if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) {
if (oai_settings.mistralai_model.includes('mixtral-8x22b')) {
if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max);
} else if (oai_settings.mistralai_model.includes('mixtral-8x22b')) {
$('#openai_max_context').attr('max', max_64k);
} else {
$('#openai_max_context').attr('max', max_32k);
@ -4120,6 +4166,20 @@ async function onModelChange() {
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
}
if (oai_settings.chat_completion_source === chat_completion_sources.ZEROONEAI) {
if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max);
} else {
$('#openai_max_context').attr('max', max_16k);
}
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
}
$('#openai_max_context_counter').attr('max', Number($('#openai_max_context').attr('max')));
saveSettingsDebounced();
@ -4314,6 +4374,19 @@ async function onConnectButtonClick(e) {
}
}
if (oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) {
const api_key_01ai = String($('#api_key_01ai').val()).trim();
if (api_key_01ai.length) {
await writeSecret(SECRET_KEYS.ZEROONEAI, api_key_01ai);
}
if (!secret_state[SECRET_KEYS.ZEROONEAI]) {
console.log('No secret key saved for 01.AI');
return;
}
}
startStatusLoading();
saveSettingsDebounced();
await getStatusOpen();
@ -4358,6 +4431,9 @@ function toggleChatCompletionForms() {
else if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {
$('#model_groq_select').trigger('change');
}
else if (oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI) {
$('#model_01ai_select').trigger('change');
}
else if (oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) {
$('#model_custom_select').trigger('change');
}
@ -5062,6 +5138,7 @@ $(document).ready(async function () {
$('#model_cohere_select').on('change', onModelChange);
$('#model_perplexity_select').on('change', onModelChange);
$('#model_groq_select').on('change', onModelChange);
$('#model_01ai_select').on('change', onModelChange);
$('#model_custom_select').on('change', onModelChange);
$('#settings_preset_openai').on('change', onSettingsPresetChange);
$('#new_oai_preset').on('click', onNewPresetClick);

View File

@ -37,6 +37,7 @@ export const POPUP_RESULT = {
* @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup
* @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
* @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
* @property {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one.
* @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
* @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
* @property {number?} [cropAspect=null] - Aspect ratio for the crop popup
@ -52,6 +53,14 @@ export const POPUP_RESULT = {
* @property {boolean?} [appendAtEnd] - Whether to append the button to the end of the popup - by default it will be prepended
*/
/**
* @typedef {object} CustomPopupInput
* @property {string} id - The id for the html element
* @property {string} label - The label text for the input
* @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label
* @property {boolean?} [defaultState=false] - The default state when opening the popup (false if not set)
*/
/**
* @typedef {object} ShowPopupHelper
* Local implementation of the helper functionality to show several popups.
@ -78,8 +87,8 @@ const showPopupHelper = {
/**
* Asynchronously displays a confirmation popup with the given header and text, returning the clicked result button value.
*
* @param {string} header - The header text for the popup.
* @param {string} text - The main text for the popup.
* @param {string?} header - The header text for the popup.
* @param {string?} text - The main text for the popup.
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
* @return {Promise<POPUP_RESULT>} A Promise that resolves with the result of the user's interaction.
*/
@ -93,34 +102,37 @@ const showPopupHelper = {
};
export class Popup {
/** @type {POPUP_TYPE} */ type;
/** @readonly @type {POPUP_TYPE} */ type;
/** @type {string} */ id;
/** @readonly @type {string} */ id;
/** @type {HTMLDialogElement} */ dlg;
/** @type {HTMLElement} */ body;
/** @type {HTMLElement} */ content;
/** @type {HTMLTextAreaElement} */ input;
/** @type {HTMLElement} */ controls;
/** @type {HTMLElement} */ okButton;
/** @type {HTMLElement} */ cancelButton;
/** @type {HTMLElement} */ closeButton;
/** @type {HTMLElement} */ cropWrap;
/** @type {HTMLImageElement} */ cropImage;
/** @type {POPUP_RESULT|number?} */ defaultResult;
/** @type {CustomPopupButton[]|string[]?} */ customButtons;
/** @readonly @type {HTMLDialogElement} */ dlg;
/** @readonly @type {HTMLDivElement} */ body;
/** @readonly @type {HTMLDivElement} */ content;
/** @readonly @type {HTMLTextAreaElement} */ mainInput;
/** @readonly @type {HTMLDivElement} */ inputControls;
/** @readonly @type {HTMLDivElement} */ buttonControls;
/** @readonly @type {HTMLDivElement} */ okButton;
/** @readonly @type {HTMLDivElement} */ cancelButton;
/** @readonly @type {HTMLDivElement} */ closeButton;
/** @readonly @type {HTMLDivElement} */ cropWrap;
/** @readonly @type {HTMLImageElement} */ cropImage;
/** @readonly @type {POPUP_RESULT|number?} */ defaultResult;
/** @readonly @type {CustomPopupButton[]|string[]?} */ customButtons;
/** @readonly @type {CustomPopupInput[]} */ customInputs;
/** @type {(popup: Popup) => boolean?} */ onClosing;
/** @type {(popup: Popup) => void?} */ onClose;
/** @type {POPUP_RESULT|number} */ result;
/** @type {any} */ value;
/** @type {Map<string,boolean>?} */ inputResults;
/** @type {any} */ cropData;
/** @type {HTMLElement} */ lastFocus;
/** @type {Promise<any>} */ promise;
/** @type {(result: any) => any} */ resolver;
/** @type {Promise<any>} */ #promise;
/** @type {(result: any) => any} */ #resolver;
/**
* Constructs a new Popup object with the given text content, type, inputValue, and options
@ -130,7 +142,7 @@ export class Popup {
* @param {string} [inputValue=''] - The initial value of the input field
* @param {PopupOptions} [options={}] - Additional options for the popup
*/
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) {
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, customInputs = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) {
Popup.util.popups.push(this);
// Make this popup uniquely identifiable
@ -147,8 +159,9 @@ export class Popup {
this.dlg = template.content.cloneNode(true).querySelector('.popup');
this.body = this.dlg.querySelector('.popup-body');
this.content = this.dlg.querySelector('.popup-content');
this.input = this.dlg.querySelector('.popup-input');
this.controls = this.dlg.querySelector('.popup-controls');
this.mainInput = this.dlg.querySelector('.popup-input');
this.inputControls = this.dlg.querySelector('.popup-inputs');
this.buttonControls = this.dlg.querySelector('.popup-controls');
this.okButton = this.dlg.querySelector('.popup-button-ok');
this.cancelButton = this.dlg.querySelector('.popup-button-cancel');
this.closeButton = this.dlg.querySelector('.popup-button-close');
@ -165,7 +178,9 @@ export class Popup {
// If custom button captions are provided, we set them beforehand
this.okButton.textContent = typeof okButton === 'string' ? okButton : 'OK';
this.okButton.dataset.i18n = this.okButton.textContent;
this.cancelButton.textContent = typeof cancelButton === 'string' ? cancelButton : template.getAttribute('popup-button-cancel');
this.cancelButton.dataset.i18n = this.cancelButton.textContent;
this.defaultResult = defaultResult;
this.customButtons = customButtons;
@ -178,12 +193,13 @@ export class Popup {
buttonElement.classList.add(...(button.classes ?? []));
buttonElement.dataset.result = String(button.result ?? undefined);
buttonElement.textContent = button.text;
buttonElement.dataset.i18n = buttonElement.textContent;
buttonElement.tabIndex = 0;
if (button.appendAtEnd) {
this.controls.appendChild(buttonElement);
this.buttonControls.appendChild(buttonElement);
} else {
this.controls.insertBefore(buttonElement, this.okButton);
this.buttonControls.insertBefore(buttonElement, this.okButton);
}
if (typeof button.action === 'function') {
@ -191,13 +207,45 @@ export class Popup {
}
});
this.customInputs = customInputs;
this.customInputs?.forEach(input => {
if (!input.id || !(typeof input.id === 'string')) {
console.warn('Given custom input does not have a valid id set')
return;
}
const label = document.createElement('label');
label.classList.add('checkbox_label', 'justifyCenter');
label.setAttribute('for', input.id);
const inputElement = document.createElement('input');
inputElement.type = 'checkbox';
inputElement.id = input.id;
inputElement.checked = input.defaultState ?? false;
label.appendChild(inputElement);
const labelText = document.createElement('span');
labelText.innerText = input.label;
labelText.dataset.i18n = input.label;
label.appendChild(labelText);
if (input.tooltip) {
const tooltip = document.createElement('div');
tooltip.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
tooltip.title = input.tooltip;
tooltip.dataset.i18n = '[title]' + input.tooltip;
label.appendChild(tooltip);
}
this.inputControls.appendChild(label);
});
// Set the default button class
const defaultButton = this.controls.querySelector(`[data-result="${this.defaultResult}"]`);
const defaultButton = this.buttonControls.querySelector(`[data-result="${this.defaultResult}"]`);
if (defaultButton) defaultButton.classList.add('menu_button_default');
// Styling differences depending on the popup type
// General styling for all types first, that might be overriden for specific types below
this.input.style.display = 'none';
this.mainInput.style.display = 'none';
this.inputControls.style.display = customInputs ? 'block' : 'none';
this.closeButton.style.display = 'none';
this.cropWrap.style.display = 'none';
@ -212,12 +260,12 @@ export class Popup {
break;
}
case POPUP_TYPE.INPUT: {
this.input.style.display = 'block';
this.mainInput.style.display = 'block';
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-save');
break;
}
case POPUP_TYPE.DISPLAY: {
this.controls.style.display = 'none';
this.buttonControls.style.display = 'none';
this.closeButton.style.display = 'block';
break;
}
@ -243,8 +291,8 @@ export class Popup {
}
}
this.input.value = inputValue;
this.input.rows = rows ?? 1;
this.mainInput.value = inputValue;
this.mainInput.rows = rows ?? 1;
this.content.innerHTML = '';
if (content instanceof jQuery) {
@ -335,10 +383,10 @@ export class Popup {
this.dlg.removeAttribute('opening');
});
this.promise = new Promise((resolve) => {
this.resolver = resolve;
this.#promise = new Promise((resolve) => {
this.#resolver = resolve;
});
return this.promise;
return this.#promise;
}
setAutoFocus({ applyAutoFocus = false } = {}) {
@ -352,12 +400,12 @@ export class Popup {
if (!control) {
switch (this.type) {
case POPUP_TYPE.INPUT: {
control = this.input;
control = this.mainInput;
break;
}
default:
// Select default button
control = this.controls.querySelector(`[data-result="${this.defaultResult}"]`);
control = this.buttonControls.querySelector(`[data-result="${this.defaultResult}"]`);
break;
}
}
@ -389,7 +437,7 @@ export class Popup {
let value = result;
// Input type have special results, so the input can be accessed directly without the need to save the popup and access both result and value
if (this.type === POPUP_TYPE.INPUT) {
if (result >= POPUP_RESULT.AFFIRMATIVE) value = this.input.value;
if (result >= POPUP_RESULT.AFFIRMATIVE) value = this.mainInput.value;
else if (result === POPUP_RESULT.NEGATIVE) value = false;
else if (result === POPUP_RESULT.CANCELLED) value = null;
else value = false; // Might a custom negative value?
@ -402,6 +450,14 @@ export class Popup {
: null;
}
if (this.customInputs?.length) {
this.inputResults = new Map(this.customInputs.map(input => {
/** @type {HTMLInputElement} */
const inputControl = this.dlg.querySelector(`#${input.id}`);
return [inputControl.id, inputControl.checked];
}));
}
this.value = value;
this.result = result;
@ -410,8 +466,8 @@ export class Popup {
if (!shouldClose) return;
}
Popup.util.lastResult = { value, result };
this.hide();
Popup.util.lastResult = { value, result, inputResults: this.inputResults };
this.#hide();
}
completeAffirmative() {
return this.complete(POPUP_RESULT.AFFIRMATIVE);
@ -425,9 +481,8 @@ export class Popup {
/**
* Hides the popup, using the internal resolver to return the value to the original show promise
* @private
*/
hide() {
#hide() {
// We close the dialog, first running the animation
this.dlg.setAttribute('closing', '');
@ -460,9 +515,9 @@ export class Popup {
else popup.setAutoFocus();
}
}
});
this.resolver(this.value);
this.#resolver(this.value);
});
}
/**
@ -476,10 +531,10 @@ export class Popup {
* Contains the list of all currently open popups, and it'll remember the result of the last closed popup.
*/
static util = {
/** @type {Popup[]} Remember all popups */
/** @readonly @type {Popup[]} Remember all popups */
popups: [],
/** @type {{value: any, result: POPUP_RESULT|number?}?} Last popup result */
/** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, boolean>?}?} Last popup result */
lastResult: null,
/** @returns {boolean} Checks if any modal popup dialog is open */
@ -500,9 +555,17 @@ export class Popup {
}
class PopupUtils {
/**
* Builds popup content with header and text below
*
* @param {string} header - The header to be added to the text
* @param {string} text - The main text content
*/
static BuildTextWithHeader(header, text) {
return `
<h3>${header}</h1>
if (!header) {
return text;
}
return `<h3>${header}</h3>
${text}`;
}
}

View File

@ -22,6 +22,7 @@ import {
setActiveGroup,
setActiveCharacter,
entitiesFilter,
doNewChat,
} from '../script.js';
import { isMobile, initMovingUI, favsToHotswap } from './RossAscends-mods.js';
import {
@ -39,11 +40,11 @@ import { tokenizers } from './tokenizers.js';
import { BIAS_CACHE } from './logit-bias.js';
import { renderTemplateAsync } from './templates.js';
import { countOccurrences, debounce, delay, download, getFileText, isOdd, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
import { countOccurrences, debounce, delay, download, getFileText, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
import { FILTER_TYPES } from './filters.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
@ -2315,7 +2316,12 @@ async function saveTheme(name = undefined, theme = undefined) {
body: JSON.stringify(theme),
});
if (response.ok) {
if (!response.ok) {
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Theme could not be saved');
console.error('Theme could not be saved', response);
throw new Error('Theme could not be saved');
}
const themeIndex = themes.findIndex(x => x.name == name);
if (themeIndex == -1) {
@ -2333,7 +2339,6 @@ async function saveTheme(name = undefined, theme = undefined) {
power_user.theme = name;
saveSettingsDebounced();
}
return theme;
}
@ -2400,12 +2405,14 @@ function getNewTheme(parsed) {
}
async function saveMovingUI() {
const name = await callGenericPopup('Enter a name for the MovingUI Preset:', POPUP_TYPE.INPUT);
const popupResult = await callGenericPopup('Enter a name for the MovingUI Preset:', POPUP_TYPE.INPUT);
if (!name) {
if (!popupResult) {
return;
}
const name = String(popupResult);
const movingUIPreset = {
name,
movingUIState: power_user.movingUIState,
@ -2437,7 +2444,8 @@ async function saveMovingUI() {
power_user.movingUIPreset = name;
saveSettingsDebounced();
} else {
toastr.warning('failed to save MovingUI state.');
toastr.error('Failed to save MovingUI state.');
console.error('MovingUI could not be saved', response);
}
}
@ -2530,14 +2538,6 @@ async function resetMovablePanels(type) {
});
}
async function doNewChat() {
$('#option_start_new_chat').trigger('click');
await delay(1);
$('#dialogue_popup_ok').trigger('click');
await delay(1);
return '';
}
/**
* Finds the ID of the tag with the given name.
* @param {string} name
@ -3926,7 +3926,20 @@ $(document).ready(() => {
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'newchat',
callback: doNewChat,
/** @type {(args: { delete: string?}, string) => Promise<''>} */
callback: async (args, _) => {
await doNewChat({ deleteCurrentChat: isTrueBoolean(args.delete) });
return '';
},
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'delete',
description: 'delete the current chat',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'false',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
helpString: 'Start a new chat with the current character',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({

View File

@ -182,17 +182,19 @@ class PresetManager {
async savePreset(name, settings) {
const preset = settings ?? this.getPresetSettings(name);
const res = await fetch('/api/presets/save', {
const response = await fetch('/api/presets/save', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ preset, name, apiId: this.apiId }),
});
if (!res.ok) {
toastr.error('Failed to save preset');
if (!response.ok) {
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Preset could not be saved');
console.error('Preset could not be saved', response);
throw new Error('Preset could not be saved');
}
const data = await res.json();
const data = await response.json();
name = data.name;
this.updateList(name, preset);
@ -327,6 +329,7 @@ class PresetManager {
'infermaticai_model',
'dreamgen_model',
'openrouter_model',
'featherless_model',
'max_tokens_second',
'openrouter_providers',
];

View File

@ -28,6 +28,9 @@ export const SECRET_KEYS = {
PERPLEXITY: 'api_key_perplexity',
GROQ: 'api_key_groq',
AZURE_TTS: 'api_key_azure_tts',
FEATHERLESS: 'api_key_featherless',
ZEROONEAI: 'api_key_01ai',
HUGGINGFACE: 'api_key_huggingface',
};
const INPUT_MAP = {
@ -56,6 +59,9 @@ const INPUT_MAP = {
[SECRET_KEYS.COHERE]: '#api_key_cohere',
[SECRET_KEYS.PERPLEXITY]: '#api_key_perplexity',
[SECRET_KEYS.GROQ]: '#api_key_groq',
[SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
[SECRET_KEYS.ZEROONEAI]: '#api_key_01ai',
[SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
};
async function clearSecret() {

View File

@ -179,8 +179,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
SlashCommandNamedArgument.fromProps({
name: 'at',
description: 'position to insert the message',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}),
],
unnamedArgumentList: [
@ -222,8 +222,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
SlashCommandNamedArgument.fromProps({
name: 'at',
description: 'position to insert the message',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}),
],
unnamedArgumentList: [
@ -276,8 +276,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
SlashCommandNamedArgument.fromProps({
name: 'at',
description: 'position to insert the message',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}),
],
unnamedArgumentList: [
@ -461,18 +461,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
SlashCommandNamedArgument.fromProps({
name: 'at',
description: 'position to insert the message',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}),
SlashCommandNamedArgument.fromProps({
name: 'name',
description: 'display name',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: '{{user}}',
enumProvider: () => [
...commonEnumProviders.characters('character')(),
...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }),
],
enumProvider: commonEnumProviders.characters('character'),
}),
],
unnamedArgumentList: [
@ -1268,13 +1265,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
SlashCommandNamedArgument.fromProps({
name: 'id',
description: 'injection ID or variable name pointing to ID',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => [
...commonEnumProviders.injects(),
...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }),
],
enumProvider: commonEnumProviders.injects,
}),
new SlashCommandNamedArgument(
'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat'],
@ -1318,12 +1311,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'injection ID or a variable name pointing to ID',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: '',
enumProvider: () => [
...commonEnumProviders.injects(),
...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }),
],
enumProvider: commonEnumProviders.injects,
}),
],
callback: flushInjectsCallback,
@ -1416,7 +1406,7 @@ function injectCallback(args, value) {
'assistant': extension_prompt_roles.ASSISTANT,
};
const id = resolveVariable(args?.id);
const id = args?.id;
const ephemeral = isTrueBoolean(args?.ephemeral);
if (!id) {
@ -1494,16 +1484,16 @@ function listInjectsCallback() {
/**
* Flushes script injections for the current chat.
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} _ Named arguments
* @param {string} value Unnamed argument
* @returns {string} Empty string
*/
function flushInjectsCallback(args, value) {
function flushInjectsCallback(_, value) {
if (!chat_metadata.script_injects) {
return '';
}
const idArgument = resolveVariable(value, args._scope);
const idArgument = value;
for (const [id, inject] of Object.entries(chat_metadata.script_injects)) {
if (idArgument && id !== idArgument) {
@ -2443,10 +2433,10 @@ async function sendUserMessageCallback(args, text) {
text = text.trim();
const compact = isTrueBoolean(args?.compact);
const bias = extractMessageBias(text);
const insertAt = Number(resolveVariable(args?.at));
const insertAt = Number(args?.at);
if ('name' in args) {
const name = resolveVariable(args.name) || '';
const name = args.name || '';
const avatar = findPersonaByName(name) || user_avatar;
await sendMessageAsUser(text, bias, insertAt, compact, name, avatar);
}
@ -2752,7 +2742,7 @@ export async function sendMessageAs(args, text) {
},
}];
const insertAt = Number(resolveVariable(args.at));
const insertAt = Number(args.at);
if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
chat.splice(insertAt, 0, message);
@ -2799,7 +2789,7 @@ export async function sendNarratorMessage(args, text) {
},
};
const insertAt = Number(resolveVariable(args.at));
const insertAt = Number(args.at);
if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
chat.splice(insertAt, 0, message);
@ -2881,7 +2871,7 @@ async function sendCommentMessage(args, text) {
},
};
const insertAt = Number(resolveVariable(args.at));
const insertAt = Number(args.at);
if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
chat.splice(insertAt, 0, message);
@ -2999,6 +2989,7 @@ function getModelOptions() {
{ id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },
{ id: 'model_perplexity_select', api: 'openai', type: chat_completion_sources.PERPLEXITY },
{ id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
{ id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
{ id: 'model_novel_select', api: 'novel', type: null },
{ id: 'horde_model', api: 'koboldhorde', type: null },
];
@ -3300,9 +3291,9 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
document.querySelector('#form_sheld').classList.add('script_error');
result = new SlashCommandClosureResult();
result.isError = true;
result.errorMessage = e.message;
result.errorMessage = e.message || 'An unknown error occurred';
if (e.cause !== 'abort') {
toastr.error(e.message);
toastr.error(result.errorMessage);
}
} finally {
delay(1000).then(() => clearCommandProgressDebounced());

View File

@ -801,8 +801,7 @@ async function showTagImportPopup(character, existingTags, newTags, folderTags)
if (folderTags.length === 0) popupContent.find('#folder_tags_block').hide();
function onCloseRemember(/** @type {Popup} */ popup) {
const rememberCheckbox = document.getElementById('import_remember_option');
if (rememberCheckbox instanceof HTMLInputElement && rememberCheckbox.checked) {
if (popup.result && popup.inputResults.get('import_remember_option')) {
const setting = buttonSettingsMap[popup.result];
if (!setting) return;
power_user.tag_import_setting = setting;
@ -812,7 +811,12 @@ async function showTagImportPopup(character, existingTags, newTags, folderTags)
}
}
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, { wider: true, okButton: 'Import', cancelButton: true, customButtons: Object.values(importButtons), onClose: onCloseRemember });
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, {
wider: true, okButton: 'Import', cancelButton: true,
customButtons: Object.values(importButtons),
customInputs: [{ id: 'import_remember_option', label: 'Remember my choice', tooltip: 'Remember the chosen import option\nIf anything besides \'Cancel\' is selected, this dialog will not show up anymore.\nTo change this, go to the settings and modify "Tag Import Option".\n\nIf the "Import" option is chosen, the global setting will stay on "Ask".' }],
onClose: onCloseRemember
});
if (!result) {
return [];
}

View File

@ -19,17 +19,4 @@
</small>
<div id="import_folder_tags_list" class="tags" style="margin-top: 5px;"></div>
</div>
<small>
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-3" for="import_remember_option">
<input type="checkbox" id="import_remember_option" name="import_remember_option" />
<span>
<span data-i18n="Remember my choice">Remember my choice</span>
<div class="fa-solid fa-circle-info opacity50p"
data-i18n="[title]Remember the chosen import option If anything besides 'Cancel' is selected, this dialog will not show up anymore. To change this, go to the settings and modify &quot;Tag Import Option&quot;. If the &quot;Import&quot; option is chosen, the global setting will stay on &quot;Ask&quot;."
title="Remember the chosen import option&#010;If anything besides 'Cancel' is selected, this dialog will not show up anymore.&#010;To change this, go to the settings and modify &quot;Tag Import Option&quot;.&#010;&#010;If the &quot;Import&quot; option is chosen, the global setting will stay on &quot;Ask&quot;.">
</div>
</span>
</label>
</small>
</div>

View File

@ -9,6 +9,7 @@ let infermaticAIModels = [];
let dreamGenModels = [];
let vllmModels = [];
let aphroditeModels = [];
let featherlessModels = [];
export let openRouterModels = [];
/**
@ -233,6 +234,35 @@ export async function loadAphroditeModels(data) {
}
}
export async function loadFeatherlessModels(data) {
if (!Array.isArray(data)) {
console.error('Invalid Featherless models data', data);
return;
}
featherlessModels = data;
if (!data.find(x => x.id === textgen_settings.featherless_model)) {
textgen_settings.featherless_model = data[0]?.id || '';
}
$('#featherless_model').empty();
for (const model of data) {
const option = document.createElement('option');
option.value = model.id;
option.text = model.id;
option.selected = model.id === textgen_settings.featherless_model;
$('#featherless_model').append(option);
}
}
function onFeatherlessModelSelect() {
const modelId = String($('#featherless_model').val());
textgen_settings.featherless_model = modelId;
$('#api_button_textgenerationwebui').trigger('click');
}
function onMancerModelSelect() {
const modelId = String($('#mancer_model').val());
textgen_settings.mancer_model = modelId;
@ -507,6 +537,7 @@ jQuery(function () {
$('#ollama_download_model').on('click', downloadOllamaModel);
$('#vllm_model').on('change', onVllmModelSelect);
$('#aphrodite_model').on('change', onAphroditeModelSelect);
$('#featherless_model').on('change', onFeatherlessModelSelect);
const providersSelect = $('.openrouter_providers');
for (const provider of OPENROUTER_PROVIDERS) {
@ -572,6 +603,12 @@ jQuery(function () {
width: '100%',
templateResult: getAphroditeModelTemplate,
});
$('#featherless_model').select2({
placeholder: 'Select a model',
searchInputPlaceholder: 'Search models...',
searchInputCssClass: 'text_pole',
width: '100%',
});
providersSelect.select2({
sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
placeholder: 'Select providers. No selection = all providers.',

View File

@ -38,9 +38,26 @@ export const textgen_types = {
INFERMATICAI: 'infermaticai',
DREAMGEN: 'dreamgen',
OPENROUTER: 'openrouter',
FEATHERLESS: 'featherless',
HUGGINGFACE: 'huggingface',
};
const { MANCER, VLLM, APHRODITE, TABBY, TOGETHERAI, OOBA, OLLAMA, LLAMACPP, INFERMATICAI, DREAMGEN, OPENROUTER, KOBOLDCPP } = textgen_types;
const {
MANCER,
VLLM,
APHRODITE,
TABBY,
TOGETHERAI,
OOBA,
OLLAMA,
LLAMACPP,
INFERMATICAI,
DREAMGEN,
OPENROUTER,
KOBOLDCPP,
HUGGINGFACE,
FEATHERLESS,
} = textgen_types;
const LLAMACPP_DEFAULT_ORDER = [
'top_k',
@ -75,6 +92,7 @@ let TOGETHERAI_SERVER = 'https://api.together.xyz';
let INFERMATICAI_SERVER = 'https://api.totalgpt.ai';
let DREAMGEN_SERVER = 'https://dreamgen.com';
let OPENROUTER_SERVER = 'https://openrouter.ai/api';
let FEATHERLESS_SERVER = 'https://api.featherless.ai/v1';
const SERVER_INPUTS = {
[textgen_types.OOBA]: '#textgenerationwebui_api_url_text',
@ -84,6 +102,7 @@ const SERVER_INPUTS = {
[textgen_types.KOBOLDCPP]: '#koboldcpp_api_url_text',
[textgen_types.LLAMACPP]: '#llamacpp_api_url_text',
[textgen_types.OLLAMA]: '#ollama_api_url_text',
[textgen_types.HUGGINGFACE]: '#huggingface_api_url_text',
};
const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5];
@ -265,6 +284,8 @@ export function validateTextGenUrl() {
export function getTextGenServer() {
switch (settings.type) {
case FEATHERLESS:
return FEATHERLESS_SERVER;
case MANCER:
return MANCER_SERVER;
case TOGETHERAI:
@ -1009,6 +1030,10 @@ export function getTextGenModel() {
throw new Error('No Ollama model selected');
}
return settings.ollama_model;
case FEATHERLESS:
return settings.featherless_model;
case HUGGINGFACE:
return 'tgi';
default:
return undefined;
}
@ -1146,6 +1171,12 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
params.grammar = settings.grammar_string;
}
if (settings.type === HUGGINGFACE) {
params.top_p = Math.min(Math.max(Number(params.top_p), 0.0), 0.999);
params.stop = Array.isArray(params.stop) ? params.stop.slice(0, 4) : [];
nonAphroditeParams.seed = settings.seed >= 0 ? settings.seed : undefined;
}
if (settings.type === MANCER) {
params.n = canMultiSwipe ? settings.n : 1;
params.epsilon_cutoff /= 1000;

View File

@ -545,6 +545,10 @@ export function getTokenizerModel() {
}
}
if (oai_settings.chat_completion_source === chat_completion_sources.ZEROONEAI) {
return yiTokenizer;
}
// Default to Turbo 3.5
return turboTokenizer;
}

View File

@ -4184,18 +4184,6 @@ h5 {
grid-template-columns: 340px auto;
}
.popup-crop-wrap {
margin: 10px auto;
max-height: 75vh;
max-height: 75svh;
max-width: 100%;
}
.popup-crop-wrap img {
max-width: 100%;
/* This rule is very important, please do not ignore this! */
}
body .ui-autocomplete {
max-height: 300px;
overflow-y: auto;
@ -4595,6 +4583,13 @@ a {
padding: 1em;
}
.img_enlarged_container pre {
max-height: 25vh;
max-height: 25svh;
flex-shrink: 0;
overflow: auto;
}
.popup:has(.img_enlarged.zoomed).large_dialogue_popup {
height: 100vh !important;
height: 100svh !important;

View File

@ -147,6 +147,32 @@ function getKoboldCppHeaders(directories) {
}) : {};
}
/**
* Gets the headers for the Featherless API.
* @param {import('./users').UserDirectoryList} directories
* @returns {object} Headers for the request
*/
function getFeatherlessHeaders(directories) {
const apiKey = readSecret(directories, SECRET_KEYS.FEATHERLESS);
return apiKey ? ({
'Authorization': `Bearer ${apiKey}`,
}) : {};
}
/**
* Gets the headers for the HuggingFace API.
* @param {import('./users').UserDirectoryList} directories
* @returns {object} Headers for the request
*/
function getHuggingFaceHeaders(directories) {
const apiKey = readSecret(directories, SECRET_KEYS.HUGGINGFACE);
return apiKey ? ({
'Authorization': `Bearer ${apiKey}`,
}) : {};
}
function getOverrideHeaders(urlHost) {
const requestOverrides = getConfigValue('requestOverrides', []);
const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers;
@ -187,6 +213,8 @@ function setAdditionalHeadersByType(requestHeaders, type, server, directories) {
[TEXTGEN_TYPES.OPENROUTER]: getOpenRouterHeaders,
[TEXTGEN_TYPES.KOBOLDCPP]: getKoboldCppHeaders,
[TEXTGEN_TYPES.LLAMACPP]: getLlamaCppHeaders,
[TEXTGEN_TYPES.FEATHERLESS]: getFeatherlessHeaders,
[TEXTGEN_TYPES.HUGGINGFACE]: getHuggingFaceHeaders,
};
const getHeaders = headerGetters[type];

View File

@ -194,6 +194,7 @@ const CHAT_COMPLETION_SOURCES = {
COHERE: 'cohere',
PERPLEXITY: 'perplexity',
GROQ: 'groq',
ZEROONEAI: '01ai',
};
/**
@ -215,6 +216,8 @@ const TEXTGEN_TYPES = {
INFERMATICAI: 'infermaticai',
DREAMGEN: 'dreamgen',
OPENROUTER: 'openrouter',
FEATHERLESS: 'featherless',
HUGGINGFACE: 'huggingface',
};
const INFERMATICAI_KEYS = [
@ -240,6 +243,49 @@ const INFERMATICAI_KEYS = [
'logprobs',
];
const FEATHERLESS_KEYS = [
'model',
'prompt',
'best_of',
'echo',
'frequency_penalty',
'logit_bias',
'logprobs',
'max_tokens',
'n',
'presence_penalty',
'seed',
'stop',
'stream',
'suffix',
'temperature',
'top_p',
'user',
'use_beam_search',
'top_k',
'min_p',
'repetition_penalty',
'length_penalty',
'early_stopping',
'stop_token_ids',
'ignore_eos',
'min_tokens',
'skip_special_tokens',
'spaces_between_special_tokens',
'truncate_prompt_tokens',
'include_stop_str_in_output',
'response_format',
'guided_json',
'guided_regex',
'guided_choice',
'guided_grammar',
'guided_decoding_backend',
'guided_whitespace_pattern',
];
// https://dreamgen.com/docs/api#openai-text
const DREAMGEN_KEYS = [
'model',
@ -381,4 +427,5 @@ module.exports = {
OPENROUTER_HEADERS,
OPENROUTER_KEYS,
VLLM_KEYS,
FEATHERLESS_KEYS,
};

View File

@ -17,6 +17,7 @@ const API_COHERE = 'https://api.cohere.ai/v1';
const API_PERPLEXITY = 'https://api.perplexity.ai';
const API_GROQ = 'https://api.groq.com/openai/v1';
const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
const API_01AI = 'https://api.01.ai/v1';
/**
* Applies a post-processing step to the generated messages.
@ -670,6 +671,10 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
api_url = API_COHERE;
api_key_openai = readSecret(request.user.directories, SECRET_KEYS.COHERE);
headers = {};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZEROONEAI) {
api_url = API_01AI;
api_key_openai = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);
headers = {};
} else {
console.log('This chat completion source is not supported yet.');
return response_getstatus_openai.status(400).send({ error: true });
@ -931,6 +936,11 @@ router.post('/generate', jsonParser, function (request, response) {
request.body.tool_choice = 'none';
}
}
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZEROONEAI) {
apiUrl = API_01AI;
apiKey = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);
headers = {};
bodyParams = {};
} else {
console.log('This chat completion source is not supported yet.');
return response.status(400).send({ error: true });

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const Readable = require('stream').Readable;
const { jsonParser } = require('../../express-common');
const { TEXTGEN_TYPES, TOGETHERAI_KEYS, OLLAMA_KEYS, INFERMATICAI_KEYS, OPENROUTER_KEYS, VLLM_KEYS, DREAMGEN_KEYS } = require('../../constants');
const { TEXTGEN_TYPES, TOGETHERAI_KEYS, OLLAMA_KEYS, INFERMATICAI_KEYS, OPENROUTER_KEYS, VLLM_KEYS, DREAMGEN_KEYS, FEATHERLESS_KEYS } = require('../../constants');
const { forwardFetchResponse, trimV1 } = require('../../util');
const { setAdditionalHeaders } = require('../../additional-headers');
@ -95,13 +95,14 @@ router.post('/status', jsonParser, async function (request, response) {
setAdditionalHeaders(request, args, baseUrl);
const apiType = request.body.api_type;
let url = baseUrl;
let result = '';
if (request.body.legacy_api) {
url += '/v1/model';
} else {
switch (request.body.api_type) {
switch (apiType) {
case TEXTGEN_TYPES.OOBA:
case TEXTGEN_TYPES.VLLM:
case TEXTGEN_TYPES.APHRODITE:
@ -126,6 +127,12 @@ router.post('/status', jsonParser, async function (request, response) {
case TEXTGEN_TYPES.OLLAMA:
url += '/api/tags';
break;
case TEXTGEN_TYPES.FEATHERLESS:
url += '/v1/models';
break;
case TEXTGEN_TYPES.HUGGINGFACE:
url += '/info';
break;
}
}
@ -144,14 +151,18 @@ router.post('/status', jsonParser, async function (request, response) {
}
// Rewrap to OAI-like response
if (request.body.api_type === TEXTGEN_TYPES.TOGETHERAI && Array.isArray(data)) {
if (apiType === TEXTGEN_TYPES.TOGETHERAI && Array.isArray(data)) {
data = { data: data.map(x => ({ id: x.name, ...x })) };
}
if (request.body.api_type === TEXTGEN_TYPES.OLLAMA && Array.isArray(data.models)) {
if (apiType === TEXTGEN_TYPES.OLLAMA && Array.isArray(data.models)) {
data = { data: data.models.map(x => ({ id: x.name, ...x })) };
}
if (apiType === TEXTGEN_TYPES.HUGGINGFACE) {
data = { data: [] };
}
if (!Array.isArray(data.data)) {
console.log('Models response is not an array.');
return response.status(400);
@ -163,7 +174,7 @@ router.post('/status', jsonParser, async function (request, response) {
// Set result to the first model ID
result = modelIds[0] || 'Valid';
if (request.body.api_type === TEXTGEN_TYPES.OOBA) {
if (apiType === TEXTGEN_TYPES.OOBA) {
try {
const modelInfoUrl = baseUrl + '/v1/internal/model/info';
const modelInfoReply = await fetch(modelInfoUrl, args);
@ -178,7 +189,7 @@ router.post('/status', jsonParser, async function (request, response) {
} catch (error) {
console.error(`Failed to get Ooba model info: ${error}`);
}
} else if (request.body.api_type === TEXTGEN_TYPES.TABBY) {
} else if (apiType === TEXTGEN_TYPES.TABBY) {
try {
const modelInfoUrl = baseUrl + '/v1/model';
const modelInfoReply = await fetch(modelInfoUrl, args);
@ -235,12 +246,14 @@ router.post('/generate', jsonParser, async function (request, response) {
} else {
switch (request.body.api_type) {
case TEXTGEN_TYPES.VLLM:
case TEXTGEN_TYPES.FEATHERLESS:
case TEXTGEN_TYPES.APHRODITE:
case TEXTGEN_TYPES.OOBA:
case TEXTGEN_TYPES.TABBY:
case TEXTGEN_TYPES.KOBOLDCPP:
case TEXTGEN_TYPES.TOGETHERAI:
case TEXTGEN_TYPES.INFERMATICAI:
case TEXTGEN_TYPES.HUGGINGFACE:
url += '/v1/completions';
break;
case TEXTGEN_TYPES.DREAMGEN:
@ -281,6 +294,11 @@ router.post('/generate', jsonParser, async function (request, response) {
args.body = JSON.stringify(request.body);
}
if (request.body.api_type === TEXTGEN_TYPES.FEATHERLESS) {
request.body = _.pickBy(request.body, (_, key) => FEATHERLESS_KEYS.includes(key));
args.body = JSON.stringify(request.body);
}
if (request.body.api_type === TEXTGEN_TYPES.DREAMGEN) {
request.body = _.pickBy(request.body, (_, key) => DREAMGEN_KEYS.includes(key));
// NOTE: DreamGen sometimes get confused by the unusual formatting in the character cards.

View File

@ -40,6 +40,9 @@ const SECRET_KEYS = {
PERPLEXITY: 'api_key_perplexity',
GROQ: 'api_key_groq',
AZURE_TTS: 'api_key_azure_tts',
FEATHERLESS: 'api_key_featherless',
ZEROONEAI: 'api_key_01ai',
HUGGINGFACE: 'api_key_huggingface',
};
// These are the keys that are safe to expose, even if allowKeysExposure is false