Merge branch 'staging' into char-shallow

8161690ce622a7250330611f1ff2ed4de883f945

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

19 files changed, +1077 -32Ignore whitespace
index.d.ts+31 -3
@@ -1,8 +1,36 @@
1import { UserDirectoryList, User } from "./src/users";1import { EventEmitter } from 'node:events';
2import { CommandLineArguments } from "./src/command-line";2import { CsrfSyncedToken } from 'csrf-sync';
3import { CsrfSyncedToken } from "csrf-sync";3import { UserDirectoryList, User } from './src/users.js';
4import { CommandLineArguments } from './src/command-line.js';
5import { EVENT_NAMES } from './src/server-events.js';
6
7/**
8 * Event payload for SERVER_STARTED event.
9 */
10export interface ServerStartedEvent {
11 /**
12 * The URL the server is listening on.
13 */
14 url: URL;
15}
16
17/**
18 * Map of all server events to their payload types.
19 */
20export interface ServerEventMap {
21 [EVENT_NAMES.SERVER_STARTED]: [ServerStartedEvent];
22}
423
5declare global {24declare global {
25 declare namespace NodeJS {
26 export interface Process {
27 /**
28 * A global instance of the server events emitter.
29 */
30 serverEvents: EventEmitter<ServerEventMap>;
31 }
32 }
33
6 declare namespace CookieSessionInterfaces {34 declare namespace CookieSessionInterfaces {
7 export interface CookieSessionObject {35 export interface CookieSessionObject {
8 /**36 /**
package-lock.json+3 -0
@@ -3408,6 +3408,9 @@
3408 "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",3408 "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
3409 "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",3409 "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
3410 "license": "MIT",3410 "license": "MIT",
3411 "dependencies": {
3412 "get-intrinsic": "^1.2.4"
3413 },
3411 "engines": {3414 "engines": {
3412 "node": ">= 0.4"3415 "node": ">= 0.4"
3413 }3416 }
package.json+1 -0
@@ -94,6 +94,7 @@
94 "scripts": {94 "scripts": {
95 "start": "node server.js",95 "start": "node server.js",
96 "debug": "node --inspect server.js",96 "debug": "node --inspect server.js",
97 "electron": "electron ./src/electron",
97 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",98 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
98 "start:bun": "bun server.js",99 "start:bun": "bun server.js",
99 "start:no-csrf": "node server.js --disableCsrf",100 "start:no-csrf": "node server.js --disableCsrf",
public/index.html+2 -0
@@ -3305,6 +3305,8 @@
3305 <option value="c4ai-aya-23">c4ai-aya-23</option>3305 <option value="c4ai-aya-23">c4ai-aya-23</option>
3306 <option value="c4ai-aya-expanse-8b">c4ai-aya-expanse-8b</option>3306 <option value="c4ai-aya-expanse-8b">c4ai-aya-expanse-8b</option>
3307 <option value="c4ai-aya-expanse-32b">c4ai-aya-expanse-32b</option>3307 <option value="c4ai-aya-expanse-32b">c4ai-aya-expanse-32b</option>
3308 <option value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>
3309 <option value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>
3308 <option value="command-light">command-light</option>3310 <option value="command-light">command-light</option>
3309 <option value="command">command</option>3311 <option value="command">command</option>
3310 <option value="command-r">command-r</option>3312 <option value="command-r">command-r</option>
public/script.js+33 -8
@@ -2475,7 +2475,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2475 timestamp: timestamp,2475 timestamp: timestamp,
2476 extra: mes.extra,2476 extra: mes.extra,
2477 tokenCount: mes.extra?.token_count ?? 0,2477 tokenCount: mes.extra?.token_count ?? 0,
2478 ...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count, mes.extra?.reasoning_duration),2478 ...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count, mes.extra?.reasoning_duration, mes.extra?.time_to_first_token),
2479 };2479 };
24802480
2481 const renderedMessage = getMessageFromTemplate(params);2481 const renderedMessage = getMessageFromTemplate(params);
@@ -2596,13 +2596,14 @@ export function formatCharacterAvatar(characterAvatar) {
2596 * @param {Date} gen_finished Date when generation was finished2596 * @param {Date} gen_finished Date when generation was finished
2597 * @param {number} tokenCount Number of tokens generated (0 if not available)2597 * @param {number} tokenCount Number of tokens generated (0 if not available)
2598 * @param {number?} [reasoningDuration=null] Reasoning duration (null if no reasoning was done)2598 * @param {number?} [reasoningDuration=null] Reasoning duration (null if no reasoning was done)
2599 * @param {number?} [timeToFirstToken=null] Time to first token
2599 * @returns {Object} Object containing the formatted timer value and title2600 * @returns {Object} Object containing the formatted timer value and title
2600 * @example2601 * @example
2601 * const { timerValue, timerTitle } = formatGenerationTimer(gen_started, gen_finished, tokenCount);2602 * const { timerValue, timerTitle } = formatGenerationTimer(gen_started, gen_finished, tokenCount);
2602 * console.log(timerValue); // 1.2s2603 * console.log(timerValue); // 1.2s
2603 * console.log(timerTitle); // Generation queued: 12:34:56 7 Jan 2021\nReply received: 12:34:57 7 Jan 2021\nTime to generate: 1.2 seconds\nToken rate: 5 t/s2604 * console.log(timerTitle); // Generation queued: 12:34:56 7 Jan 2021\nReply received: 12:34:57 7 Jan 2021\nTime to generate: 1.2 seconds\nToken rate: 5 t/s
2604 */2605 */
2605function formatGenerationTimer(gen_started, gen_finished, tokenCount, reasoningDuration = null) {2606function formatGenerationTimer(gen_started, gen_finished, tokenCount, reasoningDuration = null, timeToFirstToken = null) {
2606 if (!gen_started || !gen_finished) {2607 if (!gen_started || !gen_finished) {
2607 return {};2608 return {};
2608 }2609 }
@@ -2616,8 +2617,9 @@ function formatGenerationTimer(gen_started, gen_finished, tokenCount, reasoningD
2616 `Generation queued: ${start.format(dateFormat)}`,2617 `Generation queued: ${start.format(dateFormat)}`,
2617 `Reply received: ${finish.format(dateFormat)}`,2618 `Reply received: ${finish.format(dateFormat)}`,
2618 `Time to generate: ${seconds} seconds`,2619 `Time to generate: ${seconds} seconds`,
2620 timeToFirstToken ? `Time to first token: ${timeToFirstToken / 1000} seconds` : '',
2619 reasoningDuration > 0 ? `Time to think: ${reasoningDuration / 1000} seconds` : '',2621 reasoningDuration > 0 ? `Time to think: ${reasoningDuration / 1000} seconds` : '',
2620 tokenCount > 0 ? `Token rate: ${Number(tokenCount / seconds).toFixed(1)} t/s` : '',2622 tokenCount > 0 ? `Token rate: ${Number(tokenCount / seconds).toFixed(3)} t/s` : '',
2621 ].filter(x => x).join('\n').trim();2623 ].filter(x => x).join('\n').trim();
26222624
2623 if (isNaN(seconds) || seconds < 0) {2625 if (isNaN(seconds) || seconds < 0) {
@@ -3156,6 +3158,9 @@ class StreamingProcessor {
3156 this.abortController = new AbortController();3158 this.abortController = new AbortController();
3157 this.firstMessageText = '...';3159 this.firstMessageText = '...';
3158 this.timeStarted = timeStarted;3160 this.timeStarted = timeStarted;
3161 /** @type {number?} */
3162 this.timeToFirstToken = null;
3163 this.createdAt = new Date();
3159 this.continueMessage = type === 'continue' ? continueMessage : '';3164 this.continueMessage = type === 'continue' ? continueMessage : '';
3160 this.swipes = [];3165 this.swipes = [];
3161 /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */3166 /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */
@@ -3247,6 +3252,7 @@ class StreamingProcessor {
3247 if (!chat[messageId]['extra']) {3252 if (!chat[messageId]['extra']) {
3248 chat[messageId]['extra'] = {};3253 chat[messageId]['extra'] = {};
3249 }3254 }
3255 chat[messageId]['extra']['time_to_first_token'] = this.timeToFirstToken;
32503256
3251 // Update reasoning3257 // Update reasoning
3252 await this.reasoningHandler.process(messageId, mesChanged);3258 await this.reasoningHandler.process(messageId, mesChanged);
@@ -3264,7 +3270,12 @@ class StreamingProcessor {
32643270
3265 if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) {3271 if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) {
3266 chat[messageId]['swipes'][chat[messageId]['swipe_id']] = processedText;3272 chat[messageId]['swipes'][chat[messageId]['swipe_id']] = processedText;
3267 chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };3273 chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = {
3274 'send_date': chat[messageId]['send_date'],
3275 'gen_started': chat[messageId]['gen_started'],
3276 'gen_finished': chat[messageId]['gen_finished'],
3277 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra']))
3278 };
3268 }3279 }
32693280
3270 const formattedText = messageFormatting(3281 const formattedText = messageFormatting(
@@ -3280,7 +3291,7 @@ class StreamingProcessor {
3280 this.messageTextDom.innerHTML = formattedText;3291 this.messageTextDom.innerHTML = formattedText;
3281 }3292 }
32823293
3283 const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount, this.reasoningHandler.getDuration());3294 const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount, this.reasoningHandler.getDuration(), this.timeToFirstToken);
3284 if (this.messageTimerDom instanceof HTMLElement) {3295 if (this.messageTimerDom instanceof HTMLElement) {
3285 this.messageTimerDom.textContent = timePassed.timerValue;3296 this.messageTimerDom.textContent = timePassed.timerValue;
3286 this.messageTimerDom.title = timePassed.timerTitle;3297 this.messageTimerDom.title = timePassed.timerTitle;
@@ -3355,7 +3366,12 @@ class StreamingProcessor {
3355 if (this.type !== 'swipe' && this.type !== 'impersonate') {3366 if (this.type !== 'swipe' && this.type !== 'impersonate') {
3356 if (Array.isArray(chat[messageId]['swipes']) && chat[messageId]['swipes'].length === 1 && chat[messageId]['swipe_id'] === 0) {3367 if (Array.isArray(chat[messageId]['swipes']) && chat[messageId]['swipes'].length === 1 && chat[messageId]['swipe_id'] === 0) {
3357 chat[messageId]['swipes'][0] = chat[messageId]['mes'];3368 chat[messageId]['swipes'][0] = chat[messageId]['mes'];
3358 chat[messageId]['swipe_info'][0] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };3369 chat[messageId]['swipe_info'][0] = {
3370 'send_date': chat[messageId]['send_date'],
3371 'gen_started': chat[messageId]['gen_started'],
3372 'gen_finished': chat[messageId]['gen_finished'],
3373 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])),
3374 };
3359 }3375 }
3360 }3376 }
3361 }3377 }
@@ -3389,7 +3405,11 @@ class StreamingProcessor {
3389 const sw = new Stopwatch(1000 / power_user.streaming_fps);3405 const sw = new Stopwatch(1000 / power_user.streaming_fps);
3390 const timestamps = [];3406 const timestamps = [];
3391 for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) {3407 for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) {
3392 timestamps.push(Date.now());3408 const now = Date.now();
3409 timestamps.push(now);
3410 if (!this.timeToFirstToken) {
3411 this.timeToFirstToken = now - this.createdAt.getTime();
3412 }
3393 if (this.isStopped || this.abortController.signal.aborted) {3413 if (this.isStopped || this.abortController.signal.aborted) {
3394 return this.result;3414 return this.result;
3395 }3415 }
@@ -8865,7 +8885,12 @@ const swipe_right = () => {
8865 chat[chat.length - 1]['swipes'] = []; // empty the array8885 chat[chat.length - 1]['swipes'] = []; // empty the array
8866 chat[chat.length - 1]['swipe_info'] = [];8886 chat[chat.length - 1]['swipe_info'] = [];
8867 chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat8887 chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat
8868 chat[chat.length - 1]['swipe_info'][0] = { 'send_date': chat[chat.length - 1]['send_date'], 'gen_started': chat[chat.length - 1]['gen_started'], 'gen_finished': chat[chat.length - 1]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[chat.length - 1]['extra'])) };8888 chat[chat.length - 1]['swipe_info'][0] = {
8889 'send_date': chat[chat.length - 1]['send_date'],
8890 'gen_started': chat[chat.length - 1]['gen_started'],
8891 'gen_finished': chat[chat.length - 1]['gen_finished'],
8892 'extra': JSON.parse(JSON.stringify(chat[chat.length - 1]['extra'])),
8893 };
8869 //assign swipe info array with last message from chat8894 //assign swipe info array with last message from chat
8870 }8895 }
8871 if (chat.length === 1 && chat[0]['swipe_id'] !== undefined && chat[0]['swipe_id'] === chat[0]['swipes'].length - 1) { // if swipe_right is called on the last alternate greeting, loop back around8896 if (chat.length === 1 && chat[0]['swipe_id'] !== undefined && chat[0]['swipe_id'] === chat[0]['swipes'].length - 1) { // if swipe_right is called on the last alternate greeting, loop back around
public/scripts/extensions/caption/index.js+56 -17
@@ -398,23 +398,62 @@ jQuery(async function () {
398398
399 $('#caption_wand_container').append(sendButton);399 $('#caption_wand_container').append(sendButton);
400 $(sendButton).on('click', () => {400 $(sendButton).on('click', () => {
401 const hasCaptionModule =401 const hasCaptionModule = (() => {
402 (modules.includes('caption') && extension_settings.caption.source === 'extras') ||402 const settings = extension_settings.caption;
403 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||403
404 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||404 // Handle non-multimodal sources
405 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'zerooneai' && secret_state[SECRET_KEYS.ZEROONEAI]) ||405 if (settings.source === 'extras' && modules.includes('caption')) return true;
406 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'groq' && secret_state[SECRET_KEYS.GROQ]) ||406 if (settings.source === 'local' || settings.source === 'horde') return true;
407 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'mistral' && (secret_state[SECRET_KEYS.MISTRALAI] || extension_settings.caption.allow_reverse_proxy)) ||407
408 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||408 // Handle multimodal sources
409 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||409 if (settings.source === 'multimodal') {
410 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) ||410 const api = settings.multimodal_api;
411 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'llamacpp' && textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) ||411
412 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ooba' && textgenerationwebui_settings.server_urls[textgen_types.OOBA]) ||412 // APIs that support reverse proxy
413 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'koboldcpp' && textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP]) ||413 const reverseProxyApis = {
414 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'vllm' && textgenerationwebui_settings.server_urls[textgen_types.VLLM]) ||414 'openai': SECRET_KEYS.OPENAI,
415 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'custom') ||415 'mistral': SECRET_KEYS.MISTRALAI,
416 extension_settings.caption.source === 'local' ||416 'google': SECRET_KEYS.MAKERSUITE,
417 extension_settings.caption.source === 'horde';417 'anthropic': SECRET_KEYS.CLAUDE,
418 };
419
420 if (reverseProxyApis[api]) {
421 if (secret_state[reverseProxyApis[api]] || settings.allow_reverse_proxy) {
422 return true;
423 }
424 }
425
426 const chatCompletionApis = {
427 'openrouter': SECRET_KEYS.OPENROUTER,
428 'zerooneai': SECRET_KEYS.ZEROONEAI,
429 'groq': SECRET_KEYS.GROQ,
430 'cohere': SECRET_KEYS.COHERE,
431 };
432
433 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
434 return true;
435 }
436
437 const textCompletionApis = {
438 'ollama': textgen_types.OLLAMA,
439 'llamacpp': textgen_types.LLAMACPP,
440 'ooba': textgen_types.OOBA,
441 'koboldcpp': textgen_types.KOBOLDCPP,
442 'vllm': textgen_types.VLLM,
443 };
444
445 if (textCompletionApis[api] && textgenerationwebui_settings.server_urls[textCompletionApis[api]]) {
446 return true;
447 }
448
449 // Custom API doesn't need additional checks
450 if (api === 'custom') {
451 return true;
452 }
453 }
454
455 return false;
456 })();
418457
419 if (!hasCaptionModule) {458 if (!hasCaptionModule) {
420 toastr.error('Choose other captioning source in the extension settings.', 'Captioning is not available');459 toastr.error('Choose other captioning source in the extension settings.', 'Captioning is not available');
public/scripts/extensions/caption/settings.html+3 -0
@@ -19,6 +19,7 @@
19 <select id="caption_multimodal_api" class="flex1 text_pole">19 <select id="caption_multimodal_api" class="flex1 text_pole">
20 <option value="zerooneai">01.AI (Yi)</option>20 <option value="zerooneai">01.AI (Yi)</option>
21 <option value="anthropic">Anthropic</option>21 <option value="anthropic">Anthropic</option>
22 <option value="cohere">Cohere</option>
22 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>23 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
23 <option value="google">Google AI Studio</option>24 <option value="google">Google AI Studio</option>
24 <option value="groq">Groq</option>25 <option value="groq">Groq</option>
@@ -35,6 +36,8 @@
35 <div class="flex1 flex-container flexFlowColumn flexNoGap">36 <div class="flex1 flex-container flexFlowColumn flexNoGap">
36 <label for="caption_multimodal_model" data-i18n="Model">Model</label>37 <label for="caption_multimodal_model" data-i18n="Model">Model</label>
37 <select id="caption_multimodal_model" class="flex1 text_pole">38 <select id="caption_multimodal_model" class="flex1 text_pole">
39 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>
40 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>
38 <option data-type="mistral" value="pixtral-12b-latest">pixtral-12b-latest</option>41 <option data-type="mistral" value="pixtral-12b-latest">pixtral-12b-latest</option>
39 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>42 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>
40 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>43 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>
public/scripts/extensions/shared.js+5 -1
@@ -144,10 +144,14 @@ function throwIfInvalidModel(useReverseProxy) {
144 throw new Error('Google AI Studio API key is not set.');144 throw new Error('Google AI Studio API key is not set.');
145 }145 }
146146
147 if (extension_settings.caption.multi_modal_api === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {147 if (extension_settings.caption.multimodal_api === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {
148 throw new Error('Mistral AI API key is not set.');148 throw new Error('Mistral AI API key is not set.');
149 }149 }
150150
151 if (extension_settings.caption.multimodal_api === 'cohere' && !secret_state[SECRET_KEYS.COHERE]) {
152 throw new Error('Cohere API key is not set.');
153 }
154
151 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {155 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
152 throw new Error('Ollama server URL is not set.');156 throw new Error('Ollama server URL is not set.');
153 }157 }
public/scripts/openai.js+7 -0
@@ -4430,6 +4430,9 @@ async function onModelChange() {
4430 else if (['c4ai-aya-23-8b', 'c4ai-aya-expanse-8b'].includes(oai_settings.cohere_model)) {4430 else if (['c4ai-aya-23-8b', 'c4ai-aya-expanse-8b'].includes(oai_settings.cohere_model)) {
4431 $('#openai_max_context').attr('max', max_8k);4431 $('#openai_max_context').attr('max', max_8k);
4432 }4432 }
4433 else if (['c4ai-aya-vision-8b', 'c4ai-aya-vision-32b'].includes(oai_settings.cohere_model)) {
4434 $('#openai_max_context').attr('max', max_16k);
4435 }
4433 else {4436 else {
4434 $('#openai_max_context').attr('max', max_4k);4437 $('#openai_max_context').attr('max', max_4k);
4435 }4438 }
@@ -5010,6 +5013,8 @@ export function isImageInliningSupported() {
5010 'pixtral-12b-2409',5013 'pixtral-12b-2409',
5011 'pixtral-large-latest',5014 'pixtral-large-latest',
5012 'pixtral-large-2411',5015 'pixtral-large-2411',
5016 'c4ai-aya-vision-8b',
5017 'c4ai-aya-vision-32b',
5013 ];5018 ];
50145019
5015 switch (oai_settings.chat_completion_source) {5020 switch (oai_settings.chat_completion_source) {
@@ -5027,6 +5032,8 @@ export function isImageInliningSupported() {
5027 return visionSupportedModels.some(model => oai_settings.zerooneai_model.includes(model));5032 return visionSupportedModels.some(model => oai_settings.zerooneai_model.includes(model));
5028 case chat_completion_sources.MISTRALAI:5033 case chat_completion_sources.MISTRALAI:
5029 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));5034 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));
5035 case chat_completion_sources.COHERE:
5036 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));
5030 default:5037 default:
5031 return false;5038 return false;
5032 }5039 }
server.js+2 -0
@@ -20,6 +20,7 @@ import bodyParser from 'body-parser';
20import open from 'open';20import open from 'open';
2121
22// local library imports22// local library imports
23import { serverEvents, EVENT_NAMES } from './src/server-events.js';
23import { CommandLineParser } from './src/command-line.js';24import { CommandLineParser } from './src/command-line.js';
24import { loadPlugins } from './src/plugin-loader.js';25import { loadPlugins } from './src/plugin-loader.js';
25import {26import {
@@ -348,6 +349,7 @@ async function postSetupTasks(result) {
348 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');349 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
349350
350 setupLogLevel();351 setupLogLevel();
352 serverEvents.emit(EVENT_NAMES.SERVER_STARTED, { url: autorunUrl });
351}353}
352354
353/**355/**
src/electron/Start.bat+6 -0
@@ -0,0 +1,6 @@
1@echo off
2pushd %~dp0
3call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
4npm run start server.js %*
5pause
6popd
src/electron/index.js+62 -0
@@ -0,0 +1,62 @@
1import { app, BrowserWindow } from 'electron';
2import path from 'path';
3import { fileURLToPath } from 'url';
4import yargs from 'yargs';
5import { serverEvents, EVENT_NAMES } from '../server-events.js';
6
7const cliArguments = yargs(process.argv)
8 .usage('Usage: <your-start-script> [options]')
9 .option('width', {
10 type: 'number',
11 default: 800,
12 describe: 'The width of the window',
13 })
14 .option('height', {
15 type: 'number',
16 default: 600,
17 describe: 'The height of the window',
18 })
19 .parseSync();
20
21/** @type {string} The URL to load in the window. */
22let appUrl;
23
24function createSillyTavernWindow() {
25 if (!appUrl) {
26 console.error('The server has not started yet.');
27 return;
28 }
29 new BrowserWindow({
30 height: cliArguments.height,
31 width: cliArguments.width,
32 }).loadURL(appUrl);
33}
34
35function startServer() {
36 return new Promise((_resolve, _reject) => {
37 serverEvents.addListener(EVENT_NAMES.SERVER_STARTED, ({ url }) => {
38 appUrl = url.toString();
39 createSillyTavernWindow();
40 });
41 const sillyTavernRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
42 process.chdir(sillyTavernRoot);
43
44 import('../../server.js');
45 });
46}
47
48app.whenReady().then(() => {
49 app.on('activate', () => {
50 if (BrowserWindow.getAllWindows().length === 0) {
51 createSillyTavernWindow();
52 }
53 });
54
55 startServer();
56});
57
58app.on('window-all-closed', () => {
59 if (process.platform !== 'darwin') {
60 app.quit();
61 }
62});
src/electron/package-lock.json+802 -0
@@ -0,0 +1,802 @@
1{
2 "name": "sillytavern-electron",
3 "version": "1.0.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "sillytavern-electron",
9 "version": "1.0.0",
10 "license": "AGPL-3.0",
11 "dependencies": {
12 "electron": "^35.0.0"
13 }
14 },
15 "node_modules/@electron/get": {
16 "version": "2.0.3",
17 "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
18 "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
19 "license": "MIT",
20 "dependencies": {
21 "debug": "^4.1.1",
22 "env-paths": "^2.2.0",
23 "fs-extra": "^8.1.0",
24 "got": "^11.8.5",
25 "progress": "^2.0.3",
26 "semver": "^6.2.0",
27 "sumchecker": "^3.0.1"
28 },
29 "engines": {
30 "node": ">=12"
31 },
32 "optionalDependencies": {
33 "global-agent": "^3.0.0"
34 }
35 },
36 "node_modules/@sindresorhus/is": {
37 "version": "4.6.0",
38 "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
39 "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
40 "license": "MIT",
41 "engines": {
42 "node": ">=10"
43 },
44 "funding": {
45 "url": "https://github.com/sindresorhus/is?sponsor=1"
46 }
47 },
48 "node_modules/@szmarczak/http-timer": {
49 "version": "4.0.6",
50 "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
51 "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
52 "license": "MIT",
53 "dependencies": {
54 "defer-to-connect": "^2.0.0"
55 },
56 "engines": {
57 "node": ">=10"
58 }
59 },
60 "node_modules/@types/cacheable-request": {
61 "version": "6.0.3",
62 "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
63 "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
64 "license": "MIT",
65 "dependencies": {
66 "@types/http-cache-semantics": "*",
67 "@types/keyv": "^3.1.4",
68 "@types/node": "*",
69 "@types/responselike": "^1.0.0"
70 }
71 },
72 "node_modules/@types/http-cache-semantics": {
73 "version": "4.0.4",
74 "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
75 "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
76 "license": "MIT"
77 },
78 "node_modules/@types/keyv": {
79 "version": "3.1.4",
80 "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
81 "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
82 "license": "MIT",
83 "dependencies": {
84 "@types/node": "*"
85 }
86 },
87 "node_modules/@types/node": {
88 "version": "22.13.9",
89 "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
90 "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
91 "license": "MIT",
92 "dependencies": {
93 "undici-types": "~6.20.0"
94 }
95 },
96 "node_modules/@types/responselike": {
97 "version": "1.0.3",
98 "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
99 "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
100 "license": "MIT",
101 "dependencies": {
102 "@types/node": "*"
103 }
104 },
105 "node_modules/@types/yauzl": {
106 "version": "2.10.3",
107 "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
108 "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
109 "license": "MIT",
110 "optional": true,
111 "dependencies": {
112 "@types/node": "*"
113 }
114 },
115 "node_modules/boolean": {
116 "version": "3.2.0",
117 "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
118 "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
119 "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
120 "license": "MIT",
121 "optional": true
122 },
123 "node_modules/buffer-crc32": {
124 "version": "0.2.13",
125 "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
126 "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
127 "license": "MIT",
128 "engines": {
129 "node": "*"
130 }
131 },
132 "node_modules/cacheable-lookup": {
133 "version": "5.0.4",
134 "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
135 "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
136 "license": "MIT",
137 "engines": {
138 "node": ">=10.6.0"
139 }
140 },
141 "node_modules/cacheable-request": {
142 "version": "7.0.4",
143 "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
144 "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
145 "license": "MIT",
146 "dependencies": {
147 "clone-response": "^1.0.2",
148 "get-stream": "^5.1.0",
149 "http-cache-semantics": "^4.0.0",
150 "keyv": "^4.0.0",
151 "lowercase-keys": "^2.0.0",
152 "normalize-url": "^6.0.1",
153 "responselike": "^2.0.0"
154 },
155 "engines": {
156 "node": ">=8"
157 }
158 },
159 "node_modules/clone-response": {
160 "version": "1.0.3",
161 "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
162 "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
163 "license": "MIT",
164 "dependencies": {
165 "mimic-response": "^1.0.0"
166 },
167 "funding": {
168 "url": "https://github.com/sponsors/sindresorhus"
169 }
170 },
171 "node_modules/debug": {
172 "version": "4.4.0",
173 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
174 "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
175 "license": "MIT",
176 "dependencies": {
177 "ms": "^2.1.3"
178 },
179 "engines": {
180 "node": ">=6.0"
181 },
182 "peerDependenciesMeta": {
183 "supports-color": {
184 "optional": true
185 }
186 }
187 },
188 "node_modules/decompress-response": {
189 "version": "6.0.0",
190 "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
191 "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
192 "license": "MIT",
193 "dependencies": {
194 "mimic-response": "^3.1.0"
195 },
196 "engines": {
197 "node": ">=10"
198 },
199 "funding": {
200 "url": "https://github.com/sponsors/sindresorhus"
201 }
202 },
203 "node_modules/decompress-response/node_modules/mimic-response": {
204 "version": "3.1.0",
205 "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
206 "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
207 "license": "MIT",
208 "engines": {
209 "node": ">=10"
210 },
211 "funding": {
212 "url": "https://github.com/sponsors/sindresorhus"
213 }
214 },
215 "node_modules/defer-to-connect": {
216 "version": "2.0.1",
217 "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
218 "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
219 "license": "MIT",
220 "engines": {
221 "node": ">=10"
222 }
223 },
224 "node_modules/define-data-property": {
225 "version": "1.1.4",
226 "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
227 "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
228 "license": "MIT",
229 "optional": true,
230 "dependencies": {
231 "es-define-property": "^1.0.0",
232 "es-errors": "^1.3.0",
233 "gopd": "^1.0.1"
234 },
235 "engines": {
236 "node": ">= 0.4"
237 },
238 "funding": {
239 "url": "https://github.com/sponsors/ljharb"
240 }
241 },
242 "node_modules/define-properties": {
243 "version": "1.2.1",
244 "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
245 "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
246 "license": "MIT",
247 "optional": true,
248 "dependencies": {
249 "define-data-property": "^1.0.1",
250 "has-property-descriptors": "^1.0.0",
251 "object-keys": "^1.1.1"
252 },
253 "engines": {
254 "node": ">= 0.4"
255 },
256 "funding": {
257 "url": "https://github.com/sponsors/ljharb"
258 }
259 },
260 "node_modules/detect-node": {
261 "version": "2.1.0",
262 "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
263 "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
264 "license": "MIT",
265 "optional": true
266 },
267 "node_modules/electron": {
268 "version": "35.0.0",
269 "resolved": "https://registry.npmjs.org/electron/-/electron-35.0.0.tgz",
270 "integrity": "sha512-mwNQNktYLPnUWZVR8iNkfWCBjmM5e2/CmB1rhACwE9ASDbVU7CYPgp/jLUB3bj/LyQsfSuubD82OUite6SN8Uw==",
271 "hasInstallScript": true,
272 "license": "MIT",
273 "dependencies": {
274 "@electron/get": "^2.0.0",
275 "@types/node": "^22.7.7",
276 "extract-zip": "^2.0.1"
277 },
278 "bin": {
279 "electron": "cli.js"
280 },
281 "engines": {
282 "node": ">= 12.20.55"
283 }
284 },
285 "node_modules/end-of-stream": {
286 "version": "1.4.4",
287 "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
288 "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
289 "license": "MIT",
290 "dependencies": {
291 "once": "^1.4.0"
292 }
293 },
294 "node_modules/env-paths": {
295 "version": "2.2.1",
296 "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
297 "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
298 "license": "MIT",
299 "engines": {
300 "node": ">=6"
301 }
302 },
303 "node_modules/es-define-property": {
304 "version": "1.0.1",
305 "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
306 "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
307 "license": "MIT",
308 "optional": true,
309 "engines": {
310 "node": ">= 0.4"
311 }
312 },
313 "node_modules/es-errors": {
314 "version": "1.3.0",
315 "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
316 "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
317 "license": "MIT",
318 "optional": true,
319 "engines": {
320 "node": ">= 0.4"
321 }
322 },
323 "node_modules/es6-error": {
324 "version": "4.1.1",
325 "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
326 "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
327 "license": "MIT",
328 "optional": true
329 },
330 "node_modules/escape-string-regexp": {
331 "version": "4.0.0",
332 "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
333 "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
334 "license": "MIT",
335 "optional": true,
336 "engines": {
337 "node": ">=10"
338 },
339 "funding": {
340 "url": "https://github.com/sponsors/sindresorhus"
341 }
342 },
343 "node_modules/extract-zip": {
344 "version": "2.0.1",
345 "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
346 "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
347 "license": "BSD-2-Clause",
348 "dependencies": {
349 "debug": "^4.1.1",
350 "get-stream": "^5.1.0",
351 "yauzl": "^2.10.0"
352 },
353 "bin": {
354 "extract-zip": "cli.js"
355 },
356 "engines": {
357 "node": ">= 10.17.0"
358 },
359 "optionalDependencies": {
360 "@types/yauzl": "^2.9.1"
361 }
362 },
363 "node_modules/fd-slicer": {
364 "version": "1.1.0",
365 "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
366 "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
367 "license": "MIT",
368 "dependencies": {
369 "pend": "~1.2.0"
370 }
371 },
372 "node_modules/fs-extra": {
373 "version": "8.1.0",
374 "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
375 "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
376 "license": "MIT",
377 "dependencies": {
378 "graceful-fs": "^4.2.0",
379 "jsonfile": "^4.0.0",
380 "universalify": "^0.1.0"
381 },
382 "engines": {
383 "node": ">=6 <7 || >=8"
384 }
385 },
386 "node_modules/get-stream": {
387 "version": "5.2.0",
388 "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
389 "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
390 "license": "MIT",
391 "dependencies": {
392 "pump": "^3.0.0"
393 },
394 "engines": {
395 "node": ">=8"
396 },
397 "funding": {
398 "url": "https://github.com/sponsors/sindresorhus"
399 }
400 },
401 "node_modules/global-agent": {
402 "version": "3.0.0",
403 "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
404 "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
405 "license": "BSD-3-Clause",
406 "optional": true,
407 "dependencies": {
408 "boolean": "^3.0.1",
409 "es6-error": "^4.1.1",
410 "matcher": "^3.0.0",
411 "roarr": "^2.15.3",
412 "semver": "^7.3.2",
413 "serialize-error": "^7.0.1"
414 },
415 "engines": {
416 "node": ">=10.0"
417 }
418 },
419 "node_modules/global-agent/node_modules/semver": {
420 "version": "7.7.1",
421 "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
422 "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
423 "license": "ISC",
424 "optional": true,
425 "bin": {
426 "semver": "bin/semver.js"
427 },
428 "engines": {
429 "node": ">=10"
430 }
431 },
432 "node_modules/globalthis": {
433 "version": "1.0.4",
434 "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
435 "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
436 "license": "MIT",
437 "optional": true,
438 "dependencies": {
439 "define-properties": "^1.2.1",
440 "gopd": "^1.0.1"
441 },
442 "engines": {
443 "node": ">= 0.4"
444 },
445 "funding": {
446 "url": "https://github.com/sponsors/ljharb"
447 }
448 },
449 "node_modules/gopd": {
450 "version": "1.2.0",
451 "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
452 "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
453 "license": "MIT",
454 "optional": true,
455 "engines": {
456 "node": ">= 0.4"
457 },
458 "funding": {
459 "url": "https://github.com/sponsors/ljharb"
460 }
461 },
462 "node_modules/got": {
463 "version": "11.8.6",
464 "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
465 "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
466 "license": "MIT",
467 "dependencies": {
468 "@sindresorhus/is": "^4.0.0",
469 "@szmarczak/http-timer": "^4.0.5",
470 "@types/cacheable-request": "^6.0.1",
471 "@types/responselike": "^1.0.0",
472 "cacheable-lookup": "^5.0.3",
473 "cacheable-request": "^7.0.2",
474 "decompress-response": "^6.0.0",
475 "http2-wrapper": "^1.0.0-beta.5.2",
476 "lowercase-keys": "^2.0.0",
477 "p-cancelable": "^2.0.0",
478 "responselike": "^2.0.0"
479 },
480 "engines": {
481 "node": ">=10.19.0"
482 },
483 "funding": {
484 "url": "https://github.com/sindresorhus/got?sponsor=1"
485 }
486 },
487 "node_modules/graceful-fs": {
488 "version": "4.2.11",
489 "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
490 "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
491 "license": "ISC"
492 },
493 "node_modules/has-property-descriptors": {
494 "version": "1.0.2",
495 "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
496 "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
497 "license": "MIT",
498 "optional": true,
499 "dependencies": {
500 "es-define-property": "^1.0.0"
501 },
502 "funding": {
503 "url": "https://github.com/sponsors/ljharb"
504 }
505 },
506 "node_modules/http-cache-semantics": {
507 "version": "4.1.1",
508 "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
509 "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
510 "license": "BSD-2-Clause"
511 },
512 "node_modules/http2-wrapper": {
513 "version": "1.0.3",
514 "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
515 "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
516 "license": "MIT",
517 "dependencies": {
518 "quick-lru": "^5.1.1",
519 "resolve-alpn": "^1.0.0"
520 },
521 "engines": {
522 "node": ">=10.19.0"
523 }
524 },
525 "node_modules/json-buffer": {
526 "version": "3.0.1",
527 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
528 "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
529 "license": "MIT"
530 },
531 "node_modules/json-stringify-safe": {
532 "version": "5.0.1",
533 "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
534 "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
535 "license": "ISC",
536 "optional": true
537 },
538 "node_modules/jsonfile": {
539 "version": "4.0.0",
540 "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
541 "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
542 "license": "MIT",
543 "optionalDependencies": {
544 "graceful-fs": "^4.1.6"
545 }
546 },
547 "node_modules/keyv": {
548 "version": "4.5.4",
549 "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
550 "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
551 "license": "MIT",
552 "dependencies": {
553 "json-buffer": "3.0.1"
554 }
555 },
556 "node_modules/lowercase-keys": {
557 "version": "2.0.0",
558 "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
559 "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
560 "license": "MIT",
561 "engines": {
562 "node": ">=8"
563 }
564 },
565 "node_modules/matcher": {
566 "version": "3.0.0",
567 "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
568 "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
569 "license": "MIT",
570 "optional": true,
571 "dependencies": {
572 "escape-string-regexp": "^4.0.0"
573 },
574 "engines": {
575 "node": ">=10"
576 }
577 },
578 "node_modules/mimic-response": {
579 "version": "1.0.1",
580 "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
581 "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
582 "license": "MIT",
583 "engines": {
584 "node": ">=4"
585 }
586 },
587 "node_modules/ms": {
588 "version": "2.1.3",
589 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
590 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
591 "license": "MIT"
592 },
593 "node_modules/normalize-url": {
594 "version": "6.1.0",
595 "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
596 "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
597 "license": "MIT",
598 "engines": {
599 "node": ">=10"
600 },
601 "funding": {
602 "url": "https://github.com/sponsors/sindresorhus"
603 }
604 },
605 "node_modules/object-keys": {
606 "version": "1.1.1",
607 "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
608 "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
609 "license": "MIT",
610 "optional": true,
611 "engines": {
612 "node": ">= 0.4"
613 }
614 },
615 "node_modules/once": {
616 "version": "1.4.0",
617 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
618 "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
619 "license": "ISC",
620 "dependencies": {
621 "wrappy": "1"
622 }
623 },
624 "node_modules/p-cancelable": {
625 "version": "2.1.1",
626 "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
627 "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
628 "license": "MIT",
629 "engines": {
630 "node": ">=8"
631 }
632 },
633 "node_modules/pend": {
634 "version": "1.2.0",
635 "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
636 "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
637 "license": "MIT"
638 },
639 "node_modules/progress": {
640 "version": "2.0.3",
641 "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
642 "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
643 "license": "MIT",
644 "engines": {
645 "node": ">=0.4.0"
646 }
647 },
648 "node_modules/pump": {
649 "version": "3.0.2",
650 "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
651 "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
652 "license": "MIT",
653 "dependencies": {
654 "end-of-stream": "^1.1.0",
655 "once": "^1.3.1"
656 }
657 },
658 "node_modules/quick-lru": {
659 "version": "5.1.1",
660 "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
661 "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
662 "license": "MIT",
663 "engines": {
664 "node": ">=10"
665 },
666 "funding": {
667 "url": "https://github.com/sponsors/sindresorhus"
668 }
669 },
670 "node_modules/resolve-alpn": {
671 "version": "1.2.1",
672 "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
673 "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
674 "license": "MIT"
675 },
676 "node_modules/responselike": {
677 "version": "2.0.1",
678 "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
679 "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
680 "license": "MIT",
681 "dependencies": {
682 "lowercase-keys": "^2.0.0"
683 },
684 "funding": {
685 "url": "https://github.com/sponsors/sindresorhus"
686 }
687 },
688 "node_modules/roarr": {
689 "version": "2.15.4",
690 "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
691 "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
692 "license": "BSD-3-Clause",
693 "optional": true,
694 "dependencies": {
695 "boolean": "^3.0.1",
696 "detect-node": "^2.0.4",
697 "globalthis": "^1.0.1",
698 "json-stringify-safe": "^5.0.1",
699 "semver-compare": "^1.0.0",
700 "sprintf-js": "^1.1.2"
701 },
702 "engines": {
703 "node": ">=8.0"
704 }
705 },
706 "node_modules/semver": {
707 "version": "6.3.1",
708 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
709 "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
710 "license": "ISC",
711 "bin": {
712 "semver": "bin/semver.js"
713 }
714 },
715 "node_modules/semver-compare": {
716 "version": "1.0.0",
717 "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
718 "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
719 "license": "MIT",
720 "optional": true
721 },
722 "node_modules/serialize-error": {
723 "version": "7.0.1",
724 "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
725 "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
726 "license": "MIT",
727 "optional": true,
728 "dependencies": {
729 "type-fest": "^0.13.1"
730 },
731 "engines": {
732 "node": ">=10"
733 },
734 "funding": {
735 "url": "https://github.com/sponsors/sindresorhus"
736 }
737 },
738 "node_modules/sprintf-js": {
739 "version": "1.1.3",
740 "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
741 "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
742 "license": "BSD-3-Clause",
743 "optional": true
744 },
745 "node_modules/sumchecker": {
746 "version": "3.0.1",
747 "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
748 "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
749 "license": "Apache-2.0",
750 "dependencies": {
751 "debug": "^4.1.0"
752 },
753 "engines": {
754 "node": ">= 8.0"
755 }
756 },
757 "node_modules/type-fest": {
758 "version": "0.13.1",
759 "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
760 "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
761 "license": "(MIT OR CC0-1.0)",
762 "optional": true,
763 "engines": {
764 "node": ">=10"
765 },
766 "funding": {
767 "url": "https://github.com/sponsors/sindresorhus"
768 }
769 },
770 "node_modules/undici-types": {
771 "version": "6.20.0",
772 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
773 "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
774 "license": "MIT"
775 },
776 "node_modules/universalify": {
777 "version": "0.1.2",
778 "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
779 "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
780 "license": "MIT",
781 "engines": {
782 "node": ">= 4.0.0"
783 }
784 },
785 "node_modules/wrappy": {
786 "version": "1.0.2",
787 "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
788 "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
789 "license": "ISC"
790 },
791 "node_modules/yauzl": {
792 "version": "2.10.0",
793 "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
794 "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
795 "license": "MIT",
796 "dependencies": {
797 "buffer-crc32": "~0.2.3",
798 "fd-slicer": "~1.1.0"
799 }
800 }
801 }
802}
src/electron/package.json+16 -0
@@ -0,0 +1,16 @@
1{
2 "name": "sillytavern-electron",
3 "version": "1.0.0",
4 "description": "Electron server for SillyTavern",
5 "license": "AGPL-3.0",
6 "author": "",
7 "type": "module",
8 "main": "index.js",
9 "scripts": {
10 "test": "echo \"Error: no test specified\" && exit 1",
11 "start": "electron ."
12 },
13 "dependencies": {
14 "electron": "^35.0.0"
15 }
16}
src/electron/start.sh+11 -0
@@ -0,0 +1,11 @@
1#!/usr/bin/env bash
2
3# Make sure pwd is the directory of the script
4cd "$(dirname "$0")"
5
6echo "Assuming nodejs and npm is already installed. If you haven't installed them already, do so now"
7echo "Installing Electron Wrapper's Node Modules..."
8npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev
9
10echo "Starting Electron Wrapper..."
11npm run start -- "$@"
src/endpoints/backends/chat-completions.js+1 -0
@@ -1182,6 +1182,7 @@ router.post('/generate', jsonParser, function (request, response) {
1182 */1182 */
1183 async function makeRequest(config, response, request, retries = 5, timeout = 5000) {1183 async function makeRequest(config, response, request, retries = 5, timeout = 5000) {
1184 try {1184 try {
1185 controller.signal.throwIfAborted();
1185 const fetchResponse = await fetch(endpointUrl, config);1186 const fetchResponse = await fetch(endpointUrl, config);
11861187
1187 if (request.body.stream) {1188 if (request.body.stream) {
src/endpoints/backends/text-completions.js+2 -0
@@ -438,6 +438,7 @@ ollama.post('/download', jsonParser, async function (request, response) {
438438
439 const name = request.body.name;439 const name = request.body.name;
440 const url = String(request.body.api_server).replace(/\/$/, '');440 const url = String(request.body.api_server).replace(/\/$/, '');
441 console.debug('Pulling Ollama model:', name);
441442
442 const fetchResponse = await fetch(`${url}/api/pull`, {443 const fetchResponse = await fetch(`${url}/api/pull`, {
443 method: 'POST',444 method: 'POST',
@@ -453,6 +454,7 @@ ollama.post('/download', jsonParser, async function (request, response) {
453 return response.status(fetchResponse.status).send({ error: true });454 return response.status(fetchResponse.status).send({ error: true });
454 }455 }
455456
457 console.debug('Ollama pull response:', await fetchResponse.json());
456 return response.send({ ok: true });458 return response.send({ ok: true });
457 } catch (error) {459 } catch (error) {
458 console.error(error);460 console.error(error);
src/endpoints/openai.js+13 -3
@@ -62,6 +62,10 @@ router.post('/caption-image', jsonParser, async (request, response) => {
62 key = readSecret(request.user.directories, SECRET_KEYS.GROQ);62 key = readSecret(request.user.directories, SECRET_KEYS.GROQ);
63 }63 }
6464
65 if (request.body.api === 'cohere') {
66 key = readSecret(request.user.directories, SECRET_KEYS.COHERE);
67 }
68
65 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {69 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {
66 console.warn('No key found for API', request.body.api);70 console.warn('No key found for API', request.body.api);
67 return response.sendStatus(400);71 return response.sendStatus(400);
@@ -93,8 +97,6 @@ router.post('/caption-image', jsonParser, async (request, response) => {
93 excludeKeysByYaml(body, request.body.custom_exclude_body);97 excludeKeysByYaml(body, request.body.custom_exclude_body);
94 }98 }
9599
96 console.debug('Multimodal captioning request', body);
97
98 let apiUrl = '';100 let apiUrl = '';
99101
100 if (request.body.api === 'openrouter') {102 if (request.body.api === 'openrouter') {
@@ -120,12 +122,19 @@ router.post('/caption-image', jsonParser, async (request, response) => {
120122
121 if (request.body.api === 'groq') {123 if (request.body.api === 'groq') {
122 apiUrl = 'https://api.groq.com/openai/v1/chat/completions';124 apiUrl = 'https://api.groq.com/openai/v1/chat/completions';
125 if (body.messages?.[0]?.role === 'system') {
126 body.messages[0].role = 'user';
127 }
123 }128 }
124129
125 if (request.body.api === 'mistral') {130 if (request.body.api === 'mistral') {
126 apiUrl = 'https://api.mistral.ai/v1/chat/completions';131 apiUrl = 'https://api.mistral.ai/v1/chat/completions';
127 }132 }
128133
134 if (request.body.api === 'cohere') {
135 apiUrl = 'https://api.cohere.ai/v2/chat';
136 }
137
129 if (request.body.api === 'ooba') {138 if (request.body.api === 'ooba') {
130 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;139 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
131 const imgMessage = body.messages.pop();140 const imgMessage = body.messages.pop();
@@ -145,6 +154,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
145 }154 }
146155
147 setAdditionalHeaders(request, { headers }, apiUrl);156 setAdditionalHeaders(request, { headers }, apiUrl);
157 console.debug('Multimodal captioning request', body);
148158
149 const result = await fetch(apiUrl, {159 const result = await fetch(apiUrl, {
150 method: 'POST',160 method: 'POST',
@@ -165,7 +175,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
165 /** @type {any} */175 /** @type {any} */
166 const data = await result.json();176 const data = await result.json();
167 console.info('Multimodal captioning response', data);177 console.info('Multimodal captioning response', data);
168 const caption = data?.choices[0]?.message?.content;178 const caption = data?.choices?.[0]?.message?.content ?? data?.message?.content?.[0]?.text;
169179
170 if (!caption) {180 if (!caption) {
171 return response.status(500).send('No caption found');181 return response.status(500).send('No caption found');
src/server-events.js+21 -0
@@ -0,0 +1,21 @@
1import EventEmitter from 'node:events';
2import process from 'node:process';
3
4/**
5 * @typedef {import('../index').ServerEventMap} ServerEventMap
6 * @type {EventEmitter<ServerEventMap>} The default event source.
7 */
8export const serverEvents = new EventEmitter();
9process.serverEvents = serverEvents;
10export default serverEvents;
11
12/**
13 * @enum {string}
14 * @readonly
15 */
16export const EVENT_NAMES = Object.freeze({
17 /**
18 * Emitted when the server has started.
19 */
20 SERVER_STARTED: 'server-started',
21});