Merge pull request #3099 from ceruleandeep/fix/connRefusedErrMsg Fix/conn refused err msg

0afbd95d09cc2240faa773dc5c3d14c730dcfcb9

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

Signed
7 files changed, +103 -32Ignore whitespace
public/script.js+30 -11
@@ -2705,8 +2705,7 @@ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, q
2705 quietName: quietName,2705 quietName: quietName,
2706 };2706 };
2707 originalResponseLength = responseLengthCustomized ? saveResponseLength(main_api, responseLength) : -1;2707 originalResponseLength = responseLengthCustomized ? saveResponseLength(main_api, responseLength) : -1;
2708 const generateFinished = await Generate('quiet', options);2708 return await Generate('quiet', options);
2709 return generateFinished;
2710 } finally {2709 } finally {
2711 if (responseLengthCustomized) {2710 if (responseLengthCustomized) {
2712 restoreResponseLength(main_api, originalResponseLength);2711 restoreResponseLength(main_api, originalResponseLength);
@@ -3361,9 +3360,9 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
33613360
3362 let data = {};3361 let data = {};
33633362
3364 if (api == 'koboldhorde') {3363 if (api === 'koboldhorde') {
3365 data = await generateHorde(prompt, generateData, abortController.signal, false);3364 data = await generateHorde(prompt, generateData, abortController.signal, false);
3366 } else if (api == 'openai') {3365 } else if (api === 'openai') {
3367 data = await sendOpenAIRequest('quiet', generateData, abortController.signal);3366 data = await sendOpenAIRequest('quiet', generateData, abortController.signal);
3368 } else {3367 } else {
3369 const generateUrl = getGenerateUrl(api);3368 const generateUrl = getGenerateUrl(api);
@@ -3376,13 +3375,15 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
3376 });3375 });
33773376
3378 if (!response.ok) {3377 if (!response.ok) {
3379 const error = await response.json();3378 throw await response.json();
3380 throw error;
3381 }3379 }
33823380
3383 data = await response.json();3381 data = await response.json();
3384 }3382 }
33853383
3384 // should only happen for text completions
3385 // other frontend paths do not return data if calling the backend fails,
3386 // they throw things instead
3386 if (data.error) {3387 if (data.error) {
3387 throw new Error(data.response);3388 throw new Error(data.response);
3388 }3389 }
@@ -4434,6 +4435,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4434 return Promise.resolve();4435 return Promise.resolve();
4435 }4436 }
44364437
4438 /**
4439 * Saves itemized prompt bits and calls streaming or non-streaming generation API.
4440 * @returns {Promise<void|*|Awaited<*>|String|{fromStream}|string|undefined|Object>}
4441 * @throws {Error|object} Error with message text, or Error with response JSON (OAI/Horde), or the actual response JSON (novel|textgenerationwebui|kobold)
4442 */
4437 async function finishGenerating() {4443 async function finishGenerating() {
4438 if (power_user.console_log_prompts) {4444 if (power_user.console_log_prompts) {
4439 console.log(generate_data.prompt);4445 console.log(generate_data.prompt);
@@ -4545,6 +4551,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
45454551
4546 return finishGenerating().then(onSuccess, onError);4552 return finishGenerating().then(onSuccess, onError);
45474553
4554 /**
4555 * Handles the successful response from the generation API.
4556 * @param data
4557 * @returns {Promise<String|{fromStream}|*|string|string|void|Awaited<*>|undefined>}
4558 * @throws {Error} Throws an error if the response data contains an error message
4559 */
4548 async function onSuccess(data) {4560 async function onSuccess(data) {
4549 if (!data) return;4561 if (!data) return;
45504562
@@ -4554,6 +4566,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
45544566
4555 let messageChunk = '';4567 let messageChunk = '';
45564568
4569 // if an error was returned in data (textgenwebui), show it and throw it
4557 if (data.error) {4570 if (data.error) {
4558 unblockGeneration(type);4571 unblockGeneration(type);
4559 generatedPromptCache = '';4572 generatedPromptCache = '';
@@ -4668,9 +4681,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4668 return Object.defineProperty(new String(getMessage), 'messageChunk', { value: messageChunk });4681 return Object.defineProperty(new String(getMessage), 'messageChunk', { value: messageChunk });
4669 }4682 }
46704683
4684 /**
4685 * Exception handler for finishGenerating
4686 * @param {Error|object} exception Error or response JSON
4687 * @throws {Error|object} Re-throws the exception
4688 */
4671 function onError(exception) {4689 function onError(exception) {
4690 // if the response JSON was thrown (novel|textgenerationwebui|kobold), show the error message
4672 if (typeof exception?.error?.message === 'string') {4691 if (typeof exception?.error?.message === 'string') {
4673 toastr.error(exception.error.message, t`Error`, { timeOut: 10000, extendedTimeOut: 20000 });4692 toastr.error(exception.error.message, t`Text generation error`, { timeOut: 10000, extendedTimeOut: 20000 });
4674 }4693 }
46754694
4676 generatedPromptCache = '';4695 generatedPromptCache = '';
@@ -5338,6 +5357,7 @@ function setInContextMessages(lastmsg, type) {
5338 * @param {string} type Generation type5357 * @param {string} type Generation type
5339 * @param {object} data Generation data5358 * @param {object} data Generation data
5340 * @returns {Promise<object>} Response data from the API5359 * @returns {Promise<object>} Response data from the API
5360 * @throws {Error|object}
5341 */5361 */
5342export async function sendGenerationRequest(type, data) {5362export async function sendGenerationRequest(type, data) {
5343 if (main_api === 'openai') {5363 if (main_api === 'openai') {
@@ -5357,12 +5377,10 @@ export async function sendGenerationRequest(type, data) {
5357 });5377 });
53585378
5359 if (!response.ok) {5379 if (!response.ok) {
5360 const error = await response.json();5380 throw await response.json();
5361 throw error;
5362 }5381 }
53635382
5364 const responseData = await response.json();5383 return await response.json();
5365 return responseData;
5366}5384}
53675385
5368/**5386/**
@@ -5394,6 +5412,7 @@ export async function sendStreamingRequest(type, data) {
5394 * Gets the generation endpoint URL for the specified API.5412 * Gets the generation endpoint URL for the specified API.
5395 * @param {string} api API name5413 * @param {string} api API name
5396 * @returns {string} Generation URL5414 * @returns {string} Generation URL
5415 * @throws {Error} If the API is unknown
5397 */5416 */
5398function getGenerateUrl(api) {5417function getGenerateUrl(api) {
5399 switch (api) {5418 switch (api) {
public/scripts/extensions/stable-diffusion/index.js+13 -5
@@ -2373,6 +2373,7 @@ function ensureSelectionExists(setting, selector) {
2373 * @param {string} [message] Chat message2373 * @param {string} [message] Chat message
2374 * @param {function} [callback] Callback function2374 * @param {function} [callback] Callback function
2375 * @returns {Promise<string|undefined>} Image path2375 * @returns {Promise<string|undefined>} Image path
2376 * @throws {Error} If the prompt or image generation fails
2376 */2377 */
2377async function generatePicture(initiator, args, trigger, message, callback) {2378async function generatePicture(initiator, args, trigger, message, callback) {
2378 if (!trigger || trigger.trim().length === 0) {2379 if (!trigger || trigger.trim().length === 0) {
@@ -2391,7 +2392,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
2391 trigger = trigger.trim();2392 trigger = trigger.trim();
2392 const generationType = getGenerationType(trigger);2393 const generationType = getGenerationType(trigger);
2393 const generationTypeKey = Object.keys(generationMode).find(key => generationMode[key] === generationType);2394 const generationTypeKey = Object.keys(generationMode).find(key => generationMode[key] === generationType);
2394 console.log(`Generation mode ${generationTypeKey} triggered with "${trigger}"`);2395 console.log(`Image generation mode ${generationTypeKey} triggered with "${trigger}"`);
23952396
2396 const quietPrompt = getQuietPrompt(generationType, trigger);2397 const quietPrompt = getQuietPrompt(generationType, trigger);
2397 const context = getContext();2398 const context = getContext();
@@ -2428,6 +2429,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
24282429
2429 try {2430 try {
2430 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };2431 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
2432
2433 // generate the text prompt for the image
2431 const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);2434 const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
2432 console.log('Processed image prompt:', prompt);2435 console.log('Processed image prompt:', prompt);
24332436
@@ -2438,11 +2441,16 @@ async function generatePicture(initiator, args, trigger, message, callback) {
2438 args._abortController.addEventListener('abort', stopListener);2441 args._abortController.addEventListener('abort', stopListener);
2439 }2442 }
24402443
2444 // generate the image
2441 imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);2445 imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
2442 } catch (err) {2446 } catch (err) {
2443 console.trace(err);2447 console.trace(err);
2444 toastr.error('SD prompt text generation failed. Reason: ' + err, 'Image Generation');2448 // errors here are most likely due to text generation failure
2445 throw new Error('SD prompt text generation failed. Reason: ' + err);2449 // sendGenerationRequest mostly deals with its own errors
2450 const reason = err.error?.message || err.message || 'Unknown error';
2451 const errorText = 'SD prompt text generation failed. ' + reason;
2452 toastr.error(errorText, 'Image Generation');
2453 throw new Error(errorText);
2446 }2454 }
2447 finally {2455 finally {
2448 $(stopButton).hide();2456 $(stopButton).hide();
@@ -2513,7 +2521,7 @@ function restoreOriginalDimensions(savedParams) {
2513 */2521 */
2514async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) {2522async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) {
2515 let prompt;2523 let prompt;
25162524 console.log('getPrompt: Generation mode', generationType, 'triggered with', trigger);
2517 switch (generationType) {2525 switch (generationType) {
2518 case generationMode.RAW_LAST:2526 case generationMode.RAW_LAST:
2519 prompt = message || getRawLastMessage();2527 prompt = message || getRawLastMessage();
@@ -2729,7 +2737,7 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2729 throw new Error('Endpoint did not return image data.');2737 throw new Error('Endpoint did not return image data.');
2730 }2738 }
2731 } catch (err) {2739 } catch (err) {
2732 console.error(err);2740 console.error('Image generation request error: ', err);
2733 toastr.error('Image generation failed. Please try again.' + '\n\n' + String(err), 'Image Generation');2741 toastr.error('Image generation failed. Please try again.' + '\n\n' + String(err), 'Image Generation');
2734 return;2742 return;
2735 }2743 }
public/scripts/horde.js+8 -0
@@ -181,6 +181,14 @@ function setContextSizePreview() {
181 }181 }
182}182}
183183
184/** Generates text using the Horde API.
185 * @param {string} prompt
186 * @param params
187 * @param signal
188 * @param reportProgress
189 * @returns {Promise<{text: *, workerName: string}>}
190 * @throws {Error}
191 */
184async function generateHorde(prompt, params, signal, reportProgress) {192async function generateHorde(prompt, params, signal, reportProgress) {
185 validateHordeModel();193 validateHordeModel();
186 delete params.prompt;194 delete params.prompt;
public/scripts/openai.js+32 -7
@@ -1313,6 +1313,11 @@ export async function prepareOpenAIMessages({
1313 return [chat, promptManager.tokenHandler.counts];1313 return [chat, promptManager.tokenHandler.counts];
1314}1314}
13151315
1316/**
1317 * Handles errors during streaming requests.
1318 * @param {Response} response
1319 * @param {string} decoded - response text or decoded stream data
1320 */
1316function tryParseStreamingError(response, decoded) {1321function tryParseStreamingError(response, decoded) {
1317 try {1322 try {
1318 const data = JSON.parse(decoded);1323 const data = JSON.parse(decoded);
@@ -1324,6 +1329,9 @@ function tryParseStreamingError(response, decoded) {
1324 checkQuotaError(data);1329 checkQuotaError(data);
1325 checkModerationError(data);1330 checkModerationError(data);
13261331
1332 // these do not throw correctly (equiv to Error("[object Object]"))
1333 // if trying to fix "[object Object]" displayed to users, start here
1334
1327 if (data.error) {1335 if (data.error) {
1328 toastr.error(data.error.message || response.statusText, 'Chat Completion API');1336 toastr.error(data.error.message || response.statusText, 'Chat Completion API');
1329 throw new Error(data);1337 throw new Error(data);
@@ -1339,15 +1347,22 @@ function tryParseStreamingError(response, decoded) {
1339 }1347 }
1340}1348}
13411349
1342async function checkQuotaError(data) {1350/**
1343 const errorText = await renderTemplateAsync('quotaError');1351 * Checks if the response contains a quota error and displays a popup if it does.
13441352 * @param data
1353 * @returns {void}
1354 * @throws {object} - response JSON
1355 */
1356function checkQuotaError(data) {
1345 if (!data) {1357 if (!data) {
1346 return;1358 return;
1347 }1359 }
13481360
1349 if (data.quota_error) {1361 if (data.quota_error) {
1350 callPopup(errorText, 'text');1362 renderTemplateAsync('quotaError').then((html) => Popup.show.text('Quota Error', html));
1363
1364 // this does not throw correctly (equiv to Error("[object Object]"))
1365 // if trying to fix "[object Object]" displayed to users, start here
1351 throw new Error(data);1366 throw new Error(data);
1352 }1367 }
1353}1368}
@@ -1766,6 +1781,15 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) {
1766 return data.output;1781 return data.output;
1767}1782}
17681783
1784/**
1785 * Send a chat completion request to backend
1786 * @param {string} type (impersonate, quiet, continue, etc)
1787 * @param {Array} messages
1788 * @param {AbortSignal?} signal
1789 * @returns {Promise<unknown>}
1790 * @throws {Error}
1791 */
1792
1769async function sendOpenAIRequest(type, messages, signal) {1793async function sendOpenAIRequest(type, messages, signal) {
1770 // Provide default abort signal1794 // Provide default abort signal
1771 if (!signal) {1795 if (!signal) {
@@ -2028,12 +2052,13 @@ async function sendOpenAIRequest(type, messages, signal) {
2028 else {2052 else {
2029 const data = await response.json();2053 const data = await response.json();
20302054
2031 await checkQuotaError(data);2055 checkQuotaError(data);
2032 checkModerationError(data);2056 checkModerationError(data);
20332057
2034 if (data.error) {2058 if (data.error) {
2035 toastr.error(data.error.message || response.statusText, t`API returned an error`);2059 const message = data.error.message || response.statusText || t`Unknown error`;
2036 throw new Error(data);2060 toastr.error(message, t`API returned an error`);
2061 throw new Error(message);
2037 }2062 }
20382063
2039 if (type !== 'quiet') {2064 if (type !== 'quiet') {
public/scripts/slash-commands/SlashCommandBrowser.js+6 -7
@@ -1,5 +1,4 @@
1import { escapeRegex } from '../utils.js';1import { escapeRegex } from '../utils.js';
2import { SlashCommand } from './SlashCommand.js';
3import { SlashCommandParser } from './SlashCommandParser.js';2import { SlashCommandParser } from './SlashCommandParser.js';
43
5export class SlashCommandBrowser {4export class SlashCommandBrowser {
@@ -30,7 +29,7 @@ export class SlashCommandBrowser {
30 this.details?.remove();29 this.details?.remove();
31 this.details = null;30 this.details = null;
32 let query = inp.value.trim();31 let query = inp.value.trim();
33 if (query.slice(-1) == '"' && !/(?:^|\s+)"/.test(query)) {32 if (query.slice(-1) === '"' && !/(?:^|\s+)"/.test(query)) {
34 query = `"${query}`;33 query = `"${query}`;
35 }34 }
36 let fuzzyList = [];35 let fuzzyList = [];
@@ -59,7 +58,7 @@ export class SlashCommandBrowser {
59 cmd.helpString,58 cmd.helpString,
60 ];59 ];
61 const find = ()=>targets.find(t=>(fuzzyList.find(f=>f.test(t)) ?? quotedList.find(q=>t.includes(q))) !== undefined) !== undefined;60 const find = ()=>targets.find(t=>(fuzzyList.find(f=>f.test(t)) ?? quotedList.find(q=>t.includes(q))) !== undefined) !== undefined;
62 if (fuzzyList.length + quotedList.length == 0 || find()) {61 if (fuzzyList.length + quotedList.length === 0 || find()) {
63 this.itemMap[cmd.name].classList.remove('isFiltered');62 this.itemMap[cmd.name].classList.remove('isFiltered');
64 } else {63 } else {
65 this.itemMap[cmd.name].classList.add('isFiltered');64 this.itemMap[cmd.name].classList.add('isFiltered');
@@ -78,7 +77,7 @@ export class SlashCommandBrowser {
78 list.classList.add('autoComplete');77 list.classList.add('autoComplete');
79 this.cmdList = Object78 this.cmdList = Object
80 .keys(SlashCommandParser.commands)79 .keys(SlashCommandParser.commands)
81 .filter(key => SlashCommandParser.commands[key].name == key) // exclude aliases80 .filter(key => SlashCommandParser.commands[key].name === key) // exclude aliases
82 .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))81 .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
83 .map(key => SlashCommandParser.commands[key])82 .map(key => SlashCommandParser.commands[key])
84 ;83 ;
@@ -97,7 +96,7 @@ export class SlashCommandBrowser {
97 }96 }
98 }97 }
99 }98 }
100 if (this.details != details) {99 if (this.details !== details) {
101 Array.from(list.querySelectorAll('.selected')).forEach(it=>it.classList.remove('selected'));100 Array.from(list.querySelectorAll('.selected')).forEach(it=>it.classList.remove('selected'));
102 item.classList.add('selected');101 item.classList.add('selected');
103 this.details?.remove();102 this.details?.remove();
@@ -124,7 +123,7 @@ export class SlashCommandBrowser {
124 parent.append(this.dom);123 parent.append(this.dom);
125124
126 this.mo = new MutationObserver(muts=>{125 this.mo = new MutationObserver(muts=>{
127 if (muts.find(mut=>Array.from(mut.removedNodes).find(it=>it == this.dom || it.contains(this.dom)))) {126 if (muts.find(mut=>Array.from(mut.removedNodes).find(it=>it === this.dom || it.contains(this.dom)))) {
128 this.mo.disconnect();127 this.mo.disconnect();
129 window.removeEventListener('keydown', boundHandler);128 window.removeEventListener('keydown', boundHandler);
130 }129 }
@@ -136,7 +135,7 @@ export class SlashCommandBrowser {
136 }135 }
137136
138 handleKeyDown(evt) {137 handleKeyDown(evt) {
139 if (!evt.shiftKey && !evt.altKey && evt.ctrlKey && evt.key.toLowerCase() == 'f') {138 if (!evt.shiftKey && !evt.altKey && evt.ctrlKey && evt.key.toLowerCase() === 'f') {
140 if (!this.dom.closest('body')) return;139 if (!this.dom.closest('body')) return;
141 if (this.dom.closest('.mes') && !this.dom.closest('.last_mes')) return;140 if (this.dom.closest('.mes') && !this.dom.closest('.last_mes')) return;
142 evt.preventDefault();141 evt.preventDefault();
public/scripts/textgen-settings.js+8 -0
@@ -880,6 +880,13 @@ function setSettingByName(setting, value, trigger) {
880 }880 }
881}881}
882882
883/**
884 * Sends a streaming request for textgenerationwebui.
885 * @param generate_data
886 * @param signal
887 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
888 * @throws {Error} - If the response status is not OK, or from within the generator
889 */
883async function generateTextGenWithStreaming(generate_data, signal) {890async function generateTextGenWithStreaming(generate_data, signal) {
884 generate_data.stream = true;891 generate_data.stream = true;
885892
@@ -995,6 +1002,7 @@ export function parseTabbyLogprobs(data) {
995 * @param {Response} response - Response from the server.1002 * @param {Response} response - Response from the server.
996 * @param {string} decoded - Decoded response body.1003 * @param {string} decoded - Decoded response body.
997 * @returns {void} Nothing.1004 * @returns {void} Nothing.
1005 * @throws {Error} If the response contains an error message, throws Error with the message.
998 */1006 */
999function tryParseStreamingError(response, decoded) {1007function tryParseStreamingError(response, decoded) {
1000 let data = {};1008 let data = {};
src/endpoints/backends/chat-completions.js+6 -2
@@ -1051,8 +1051,12 @@ router.post('/generate', jsonParser, function (request, response) {
1051 }1051 }
1052 } catch (error) {1052 } catch (error) {
1053 console.log('Generation failed', error);1053 console.log('Generation failed', error);
1054 const message = error.code === 'ECONNREFUSED'
1055 ? `Connection refused: ${error.message}`
1056 : error.message || 'Unknown error occurred';
1057
1054 if (!response.headersSent) {1058 if (!response.headersSent) {
1055 response.send({ error: true });1059 response.status(502).send({ error: { message, ...error } });
1056 } else {1060 } else {
1057 response.end();1061 response.end();
1058 }1062 }
@@ -1068,7 +1072,7 @@ router.post('/generate', jsonParser, function (request, response) {
10681072
1069 const message = errorResponse.statusText || 'Unknown error occurred';1073 const message = errorResponse.statusText || 'Unknown error occurred';
1070 const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota';1074 const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota';
1071 console.log(message, responseText);1075 console.log('Chat completion request error: ', message, responseText);
10721076
1073 if (!response.headersSent) {1077 if (!response.headersSent) {
1074 response.send({ error: { message }, quota_error: quota_error });1078 response.send({ error: { message }, quota_error: quota_error });