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
27052705 quietName: quietName,
27062706 };
27072707 originalResponseLength = responseLengthCustomized ? saveResponseLength(main_api, responseLength) : -1;
27082708 const generateFinished =return await Generate('quiet', options);
2709- return generateFinished;
27102709 } finally {
27112710 if (responseLengthCustomized) {
27122711 restoreResponseLength(main_api, originalResponseLength);
@@ -3361,9 +3360,9 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
33613360
33623361 let data = {};
33633362
33643363 if (api === 'koboldhorde') {
33653364 data = await generateHorde(prompt, generateData, abortController.signal, false);
33663365 } else if (api === 'openai') {
33673366 data = await sendOpenAIRequest('quiet', generateData, abortController.signal);
33683367 } else {
33693368 const generateUrl = getGenerateUrl(api);
@@ -3376,13 +3375,15 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
33763375 });
33773376
33783377 if (!response.ok) {
33793378 const error =throw await response.json();
3380- throw error;
33813379 }
33823380
33833381 data = await response.json();
33843382 }
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
33863387 if (data.error) {
33873388 throw new Error(data.response);
33883389 }
@@ -4434,6 +4435,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
44344435 return Promise.resolve();
44354436 }
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+ */
44374443 async function finishGenerating() {
44384444 if (power_user.console_log_prompts) {
44394445 console.log(generate_data.prompt);
@@ -4545,6 +4551,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
45454551
45464552 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+ */
45484560 async function onSuccess(data) {
45494561 if (!data) return;
45504562
@@ -4554,6 +4566,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
45544566
45554567 let messageChunk = '';
45564568
4569+ // if an error was returned in data (textgenwebui), show it and throw it
45574570 if (data.error) {
45584571 unblockGeneration(type);
45594572 generatedPromptCache = '';
@@ -4668,9 +4681,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
46684681 return Object.defineProperty(new String(getMessage), 'messageChunk', { value: messageChunk });
46694682 }
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+ */
46714689 function onError(exception) {
4690+ // if the response JSON was thrown (novel|textgenerationwebui|kobold), show the error message
46724691 if (typeof exception?.error?.message === 'string') {
46734692 toastr.error(exception.error.message, t`ErrorText generation error`, { timeOut: 10000, extendedTimeOut: 20000 });
46744693 }
46754694
46764695 generatedPromptCache = '';
@@ -5338,6 +5357,7 @@ function setInContextMessages(lastmsg, type) {
53385357 * @param {string} type Generation type
53395358 * @param {object} data Generation data
53405359 * @returns {Promise<object>} Response data from the API
5360+ * @throws {Error|object}
53415361 */
53425362export async function sendGenerationRequest(type, data) {
53435363 if (main_api === 'openai') {
@@ -5357,12 +5377,10 @@ export async function sendGenerationRequest(type, data) {
53575377 });
53585378
53595379 if (!response.ok) {
53605380 const error =throw await response.json();
5361- throw error;
53625381 }
53635382
53645383 const responseData =return await response.json();
5365- return responseData;
53665384}
53675385
53685386/**
@@ -5394,6 +5412,7 @@ export async function sendStreamingRequest(type, data) {
53945412 * Gets the generation endpoint URL for the specified API.
53955413 * @param {string} api API name
53965414 * @returns {string} Generation URL
5415+ * @throws {Error} If the API is unknown
53975416 */
53985417function getGenerateUrl(api) {
53995418 switch (api) {
public/scripts/extensions/stable-diffusion/index.js+13 -5
@@ -2373,6 +2373,7 @@ function ensureSelectionExists(setting, selector) {
23732373 * @param {string} [message] Chat message
23742374 * @param {function} [callback] Callback function
23752375 * @returns {Promise<string|undefined>} Image path
2376+ * @throws {Error} If the prompt or image generation fails
23762377 */
23772378async function generatePicture(initiator, args, trigger, message, callback) {
23782379 if (!trigger || trigger.trim().length === 0) {
@@ -2391,7 +2392,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
23912392 trigger = trigger.trim();
23922393 const generationType = getGenerationType(trigger);
23932394 const generationTypeKey = Object.keys(generationMode).find(key => generationMode[key] === generationType);
23942395 console.log(`GenerationImage generation mode ${generationTypeKey} triggered with "${trigger}"`);
23952396
23962397 const quietPrompt = getQuietPrompt(generationType, trigger);
23972398 const context = getContext();
@@ -2428,6 +2429,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
24282429
24292430 try {
24302431 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
2432+
2433+ // generate the text prompt for the image
24312434 const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
24322435 console.log('Processed image prompt:', prompt);
24332436
@@ -2438,11 +2441,16 @@ async function generatePicture(initiator, args, trigger, message, callback) {
24382441 args._abortController.addEventListener('abort', stopListener);
24392442 }
24402443
2444+ // generate the image
24412445 imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
24422446 } catch (err) {
24432447 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);
24462454 }
24472455 finally {
24482456 $(stopButton).hide();
@@ -2513,7 +2521,7 @@ function restoreOriginalDimensions(savedParams) {
25132521 */
25142522async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) {
25152523 let prompt;
2516-
2524+ console.log('getPrompt: Generation mode', generationType, 'triggered with', trigger);
25172525 switch (generationType) {
25182526 case generationMode.RAW_LAST:
25192527 prompt = message || getRawLastMessage();
@@ -2729,7 +2737,7 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
27292737 throw new Error('Endpoint did not return image data.');
27302738 }
27312739 } catch (err) {
27322740 console.error('Image generation request error: ', err);
27332741 toastr.error('Image generation failed. Please try again.' + '\n\n' + String(err), 'Image Generation');
27342742 return;
27352743 }
public/scripts/horde.js+8 -0
@@ -181,6 +181,14 @@ function setContextSizePreview() {
181181 }
182182}
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+ */
184192async function generateHorde(prompt, params, signal, reportProgress) {
185193 validateHordeModel();
186194 delete params.prompt;
public/scripts/openai.js+32 -7
@@ -1313,6 +1313,11 @@ export async function prepareOpenAIMessages({
13131313 return [chat, promptManager.tokenHandler.counts];
13141314}
13151315
1316+/**
1317+ * Handles errors during streaming requests.
1318+ * @param {Response} response
1319+ * @param {string} decoded - response text or decoded stream data
1320+ */
13161321function tryParseStreamingError(response, decoded) {
13171322 try {
13181323 const data = JSON.parse(decoded);
@@ -1324,6 +1329,9 @@ function tryParseStreamingError(response, decoded) {
13241329 checkQuotaError(data);
13251330 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+
13271335 if (data.error) {
13281336 toastr.error(data.error.message || response.statusText, 'Chat Completion API');
13291337 throw new Error(data);
@@ -1339,15 +1347,22 @@ function tryParseStreamingError(response, decoded) {
13391347 }
13401348}
13411349
1342-async 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.
1344-
1352+ * @param data
1353+ * @returns {void}
1354+ * @throws {object} - response JSON
1355+ */
1356+function checkQuotaError(data) {
13451357 if (!data) {
13461358 return;
13471359 }
13481360
13491361 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
13511366 throw new Error(data);
13521367 }
13531368}
@@ -1766,6 +1781,15 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) {
17661781 return data.output;
17671782}
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+
17691793async function sendOpenAIRequest(type, messages, signal) {
17701794 // Provide default abort signal
17711795 if (!signal) {
@@ -2028,12 +2052,13 @@ async function sendOpenAIRequest(type, messages, signal) {
20282052 else {
20292053 const data = await response.json();
20302054
20312055 await checkQuotaError(data);
20322056 checkModerationError(data);
20332057
20342058 if (data.error) {
20352059 toastr.error(const message = data.error.message || response.statusText, || t`API returned anUnknown error`);
2036- throw new Error(data);
2060+ toastr.error(message, t`API returned an error`);
2061+ throw new Error(message);
20372062 }
20382063
20392064 if (type !== 'quiet') {
public/scripts/slash-commands/SlashCommandBrowser.js+6 -7
@@ -1,5 +1,4 @@
11import { escapeRegex } from '../utils.js';
2-import { SlashCommand } from './SlashCommand.js';
32import { SlashCommandParser } from './SlashCommandParser.js';
43
54export class SlashCommandBrowser {
@@ -30,7 +29,7 @@ export class SlashCommandBrowser {
3029 this.details?.remove();
3130 this.details = null;
3231 let query = inp.value.trim();
3332 if (query.slice(-1) === '"' && !/(?:^|\s+)"/.test(query)) {
3433 query = `"${query}`;
3534 }
3635 let fuzzyList = [];
@@ -59,7 +58,7 @@ export class SlashCommandBrowser {
5958 cmd.helpString,
6059 ];
6160 const find = ()=>targets.find(t=>(fuzzyList.find(f=>f.test(t)) ?? quotedList.find(q=>t.includes(q))) !== undefined) !== undefined;
6261 if (fuzzyList.length + quotedList.length === 0 || find()) {
6362 this.itemMap[cmd.name].classList.remove('isFiltered');
6463 } else {
6564 this.itemMap[cmd.name].classList.add('isFiltered');
@@ -78,7 +77,7 @@ export class SlashCommandBrowser {
7877 list.classList.add('autoComplete');
7978 this.cmdList = Object
8079 .keys(SlashCommandParser.commands)
8180 .filter(key => SlashCommandParser.commands[key].name === key) // exclude aliases
8281 .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
8382 .map(key => SlashCommandParser.commands[key])
8483 ;
@@ -97,7 +96,7 @@ export class SlashCommandBrowser {
9796 }
9897 }
9998 }
10099 if (this.details !== details) {
101100 Array.from(list.querySelectorAll('.selected')).forEach(it=>it.classList.remove('selected'));
102101 item.classList.add('selected');
103102 this.details?.remove();
@@ -124,7 +123,7 @@ export class SlashCommandBrowser {
124123 parent.append(this.dom);
125124
126125 this.mo = new MutationObserver(muts=>{
127126 if (muts.find(mut=>Array.from(mut.removedNodes).find(it=>it === this.dom || it.contains(this.dom)))) {
128127 this.mo.disconnect();
129128 window.removeEventListener('keydown', boundHandler);
130129 }
@@ -136,7 +135,7 @@ export class SlashCommandBrowser {
136135 }
137136
138137 handleKeyDown(evt) {
139138 if (!evt.shiftKey && !evt.altKey && evt.ctrlKey && evt.key.toLowerCase() === 'f') {
140139 if (!this.dom.closest('body')) return;
141140 if (this.dom.closest('.mes') && !this.dom.closest('.last_mes')) return;
142141 evt.preventDefault();
public/scripts/textgen-settings.js+8 -0
@@ -880,6 +880,13 @@ function setSettingByName(setting, value, trigger) {
880880 }
881881}
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+ */
883890async function generateTextGenWithStreaming(generate_data, signal) {
884891 generate_data.stream = true;
885892
@@ -995,6 +1002,7 @@ export function parseTabbyLogprobs(data) {
9951002 * @param {Response} response - Response from the server.
9961003 * @param {string} decoded - Decoded response body.
9971004 * @returns {void} Nothing.
1005+ * @throws {Error} If the response contains an error message, throws Error with the message.
9981006 */
9991007function tryParseStreamingError(response, decoded) {
10001008 let data = {};
src/endpoints/backends/chat-completions.js+6 -2
@@ -1051,8 +1051,12 @@ router.post('/generate', jsonParser, function (request, response) {
10511051 }
10521052 } catch (error) {
10531053 console.log('Generation failed', error);
1054+ const message = error.code === 'ECONNREFUSED'
1055+ ? `Connection refused: ${error.message}`
1056+ : error.message || 'Unknown error occurred';
1057+
10541058 if (!response.headersSent) {
10551059 response.status(502).send({ error: true{ message, ...error } });
10561060 } else {
10571061 response.end();
10581062 }
@@ -1068,7 +1072,7 @@ router.post('/generate', jsonParser, function (request, response) {
10681072
10691073 const message = errorResponse.statusText || 'Unknown error occurred';
10701074 const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota';
10711075 console.log('Chat completion request error: ', message, responseText);
10721076
10731077 if (!response.headersSent) {
10741078 response.send({ error: { message }, quota_error: quota_error });