Merge pull request #3099 from ceruleandeep/fix/connRefusedErrMsg Fix/conn refused err msg
Signed| @@ -2705,8 +2705,7 @@ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, q | ||
| 2705 | 2705 | quietName: quietName, |
| 2706 | 2706 | }; |
| 2707 | 2707 | originalResponseLength = responseLengthCustomized ? saveResponseLength(main_api, responseLength) : -1; |
| 2708 | 2708 | const generateFinished =return await Generate('quiet', options); |
| 2709 | - return generateFinished; | |
| 2710 | 2709 | } finally { |
| 2711 | 2710 | if (responseLengthCustomized) { |
| 2712 | 2711 | restoreResponseLength(main_api, originalResponseLength); |
| @@ -3361,9 +3360,9 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | ||
| 3361 | 3360 | |
| 3362 | 3361 | let data = {}; |
| 3363 | 3362 | |
| 3364 | 3363 | if (api === 'koboldhorde') { |
| 3365 | 3364 | data = await generateHorde(prompt, generateData, abortController.signal, false); |
| 3366 | 3365 | } else if (api === 'openai') { |
| 3367 | 3366 | data = await sendOpenAIRequest('quiet', generateData, abortController.signal); |
| 3368 | 3367 | } else { |
| 3369 | 3368 | const generateUrl = getGenerateUrl(api); |
| @@ -3376,13 +3375,15 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | ||
| 3376 | 3375 | }); |
| 3377 | 3376 | |
| 3378 | 3377 | if (!response.ok) { |
| 3379 | 3378 | const error =throw await response.json(); |
| 3380 | - throw error; | |
| 3381 | 3379 | } |
| 3382 | 3380 | |
| 3383 | 3381 | data = await response.json(); |
| 3384 | 3382 | } |
| 3385 | 3383 | |
| 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 | 3387 | if (data.error) { |
| 3387 | 3388 | throw new Error(data.response); |
| 3388 | 3389 | } |
| @@ -4434,6 +4435,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4434 | 4435 | return Promise.resolve(); |
| 4435 | 4436 | } |
| 4436 | 4437 | |
| 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 | 4443 | async function finishGenerating() { |
| 4438 | 4444 | if (power_user.console_log_prompts) { |
| 4439 | 4445 | console.log(generate_data.prompt); |
| @@ -4545,6 +4551,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4545 | 4551 | |
| 4546 | 4552 | return finishGenerating().then(onSuccess, onError); |
| 4547 | 4553 | |
| 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 | 4560 | async function onSuccess(data) { |
| 4549 | 4561 | if (!data) return; |
| 4550 | 4562 | |
| @@ -4554,6 +4566,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4554 | 4566 | |
| 4555 | 4567 | let messageChunk = ''; |
| 4556 | 4568 | |
| 4569 | + // if an error was returned in data (textgenwebui), show it and throw it | |
| 4557 | 4570 | if (data.error) { |
| 4558 | 4571 | unblockGeneration(type); |
| 4559 | 4572 | generatedPromptCache = ''; |
| @@ -4668,9 +4681,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4668 | 4681 | return Object.defineProperty(new String(getMessage), 'messageChunk', { value: messageChunk }); |
| 4669 | 4682 | } |
| 4670 | 4683 | |
| 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 | 4689 | function onError(exception) { |
| 4690 | + // if the response JSON was thrown (novel|textgenerationwebui|kobold), show the error message | |
| 4672 | 4691 | if (typeof exception?.error?.message === 'string') { |
| 4673 | 4692 | toastr.error(exception.error.message, t`ErrorText generation error`, { timeOut: 10000, extendedTimeOut: 20000 }); |
| 4674 | 4693 | } |
| 4675 | 4694 | |
| 4676 | 4695 | generatedPromptCache = ''; |
| @@ -5338,6 +5357,7 @@ function setInContextMessages(lastmsg, type) { | ||
| 5338 | 5357 | * @param {string} type Generation type |
| 5339 | 5358 | * @param {object} data Generation data |
| 5340 | 5359 | * @returns {Promise<object>} Response data from the API |
| 5360 | + * @throws {Error|object} | |
| 5341 | 5361 | */ |
| 5342 | 5362 | export async function sendGenerationRequest(type, data) { |
| 5343 | 5363 | if (main_api === 'openai') { |
| @@ -5357,12 +5377,10 @@ export async function sendGenerationRequest(type, data) { | ||
| 5357 | 5377 | }); |
| 5358 | 5378 | |
| 5359 | 5379 | if (!response.ok) { |
| 5360 | 5380 | const error =throw await response.json(); |
| 5361 | - throw error; | |
| 5362 | 5381 | } |
| 5363 | 5382 | |
| 5364 | 5383 | const responseData =return await response.json(); |
| 5365 | - return responseData; | |
| 5366 | 5384 | } |
| 5367 | 5385 | |
| 5368 | 5386 | /** |
| @@ -5394,6 +5412,7 @@ export async function sendStreamingRequest(type, data) { | ||
| 5394 | 5412 | * Gets the generation endpoint URL for the specified API. |
| 5395 | 5413 | * @param {string} api API name |
| 5396 | 5414 | * @returns {string} Generation URL |
| 5415 | + * @throws {Error} If the API is unknown | |
| 5397 | 5416 | */ |
| 5398 | 5417 | function getGenerateUrl(api) { |
| 5399 | 5418 | switch (api) { |
| @@ -2373,6 +2373,7 @@ function ensureSelectionExists(setting, selector) { | ||
| 2373 | 2373 | * @param {string} [message] Chat message |
| 2374 | 2374 | * @param {function} [callback] Callback function |
| 2375 | 2375 | * @returns {Promise<string|undefined>} Image path |
| 2376 | + * @throws {Error} If the prompt or image generation fails | |
| 2376 | 2377 | */ |
| 2377 | 2378 | async function generatePicture(initiator, args, trigger, message, callback) { |
| 2378 | 2379 | if (!trigger || trigger.trim().length === 0) { |
| @@ -2391,7 +2392,7 @@ async function generatePicture(initiator, args, trigger, message, callback) { | ||
| 2391 | 2392 | trigger = trigger.trim(); |
| 2392 | 2393 | const generationType = getGenerationType(trigger); |
| 2393 | 2394 | const generationTypeKey = Object.keys(generationMode).find(key => generationMode[key] === generationType); |
| 2394 | 2395 | console.log(`GenerationImage generation mode ${generationTypeKey} triggered with "${trigger}"`); |
| 2395 | 2396 | |
| 2396 | 2397 | const quietPrompt = getQuietPrompt(generationType, trigger); |
| 2397 | 2398 | const context = getContext(); |
| @@ -2428,6 +2429,8 @@ async function generatePicture(initiator, args, trigger, message, callback) { | ||
| 2428 | 2429 | |
| 2429 | 2430 | try { |
| 2430 | 2431 | const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); }; |
| 2432 | + | |
| 2433 | + // generate the text prompt for the image | |
| 2431 | 2434 | const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives); |
| 2432 | 2435 | console.log('Processed image prompt:', prompt); |
| 2433 | 2436 | |
| @@ -2438,11 +2441,16 @@ async function generatePicture(initiator, args, trigger, message, callback) { | ||
| 2438 | 2441 | args._abortController.addEventListener('abort', stopListener); |
| 2439 | 2442 | } |
| 2440 | 2443 | |
| 2444 | + // generate the image | |
| 2441 | 2445 | imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal); |
| 2442 | 2446 | } catch (err) { |
| 2443 | 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 | 2455 | finally { |
| 2448 | 2456 | $(stopButton).hide(); |
| @@ -2513,7 +2521,7 @@ function restoreOriginalDimensions(savedParams) { | ||
| 2513 | 2521 | */ |
| 2514 | 2522 | async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) { |
| 2515 | 2523 | let prompt; |
| 2516 | - | |
| 2524 | + console.log('getPrompt: Generation mode', generationType, 'triggered with', trigger); | |
| 2517 | 2525 | switch (generationType) { |
| 2518 | 2526 | case generationMode.RAW_LAST: |
| 2519 | 2527 | prompt = message || getRawLastMessage(); |
| @@ -2729,7 +2737,7 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 2729 | 2737 | throw new Error('Endpoint did not return image data.'); |
| 2730 | 2738 | } |
| 2731 | 2739 | } catch (err) { |
| 2732 | 2740 | console.error('Image generation request error: ', err); |
| 2733 | 2741 | toastr.error('Image generation failed. Please try again.' + '\n\n' + String(err), 'Image Generation'); |
| 2734 | 2742 | return; |
| 2735 | 2743 | } |
| @@ -181,6 +181,14 @@ function setContextSizePreview() { | ||
| 181 | 181 | } |
| 182 | 182 | } |
| 183 | 183 | |
| 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 | + */ | |
| 184 | 192 | async function generateHorde(prompt, params, signal, reportProgress) { |
| 185 | 193 | validateHordeModel(); |
| 186 | 194 | delete params.prompt; |
| @@ -1313,6 +1313,11 @@ export async function prepareOpenAIMessages({ | ||
| 1313 | 1313 | return [chat, promptManager.tokenHandler.counts]; |
| 1314 | 1314 | } |
| 1315 | 1315 | |
| 1316 | +/** | |
| 1317 | + * Handles errors during streaming requests. | |
| 1318 | + * @param {Response} response | |
| 1319 | + * @param {string} decoded - response text or decoded stream data | |
| 1320 | + */ | |
| 1316 | 1321 | function tryParseStreamingError(response, decoded) { |
| 1317 | 1322 | try { |
| 1318 | 1323 | const data = JSON.parse(decoded); |
| @@ -1324,6 +1329,9 @@ function tryParseStreamingError(response, decoded) { | ||
| 1324 | 1329 | checkQuotaError(data); |
| 1325 | 1330 | checkModerationError(data); |
| 1326 | 1331 | |
| 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 | 1335 | if (data.error) { |
| 1328 | 1336 | toastr.error(data.error.message || response.statusText, 'Chat Completion API'); |
| 1329 | 1337 | throw new Error(data); |
| @@ -1339,15 +1347,22 @@ function tryParseStreamingError(response, decoded) { | ||
| 1339 | 1347 | } |
| 1340 | 1348 | } |
| 1341 | 1349 | |
| 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) { | |
| 1345 | 1357 | if (!data) { |
| 1346 | 1358 | return; |
| 1347 | 1359 | } |
| 1348 | 1360 | |
| 1349 | 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 | 1366 | throw new Error(data); |
| 1352 | 1367 | } |
| 1353 | 1368 | } |
| @@ -1766,6 +1781,15 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) { | ||
| 1766 | 1781 | return data.output; |
| 1767 | 1782 | } |
| 1768 | 1783 | |
| 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 | + | |
| 1769 | 1793 | async function sendOpenAIRequest(type, messages, signal) { |
| 1770 | 1794 | // Provide default abort signal |
| 1771 | 1795 | if (!signal) { |
| @@ -2028,12 +2052,13 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2028 | 2052 | else { |
| 2029 | 2053 | const data = await response.json(); |
| 2030 | 2054 | |
| 2031 | 2055 | await checkQuotaError(data); |
| 2032 | 2056 | checkModerationError(data); |
| 2033 | 2057 | |
| 2034 | 2058 | if (data.error) { |
| 2035 | 2059 | 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); | |
| 2037 | 2062 | } |
| 2038 | 2063 | |
| 2039 | 2064 | if (type !== 'quiet') { |
| @@ -1,5 +1,4 @@ | ||
| 1 | 1 | import { escapeRegex } from '../utils.js'; |
| 2 | -import { SlashCommand } from './SlashCommand.js'; | |
| 3 | 2 | import { SlashCommandParser } from './SlashCommandParser.js'; |
| 4 | 3 | |
| 5 | 4 | export class SlashCommandBrowser { |
| @@ -30,7 +29,7 @@ export class SlashCommandBrowser { | ||
| 30 | 29 | this.details?.remove(); |
| 31 | 30 | this.details = null; |
| 32 | 31 | let query = inp.value.trim(); |
| 33 | 32 | if (query.slice(-1) === '"' && !/(?:^|\s+)"/.test(query)) { |
| 34 | 33 | query = `"${query}`; |
| 35 | 34 | } |
| 36 | 35 | let fuzzyList = []; |
| @@ -59,7 +58,7 @@ export class SlashCommandBrowser { | ||
| 59 | 58 | cmd.helpString, |
| 60 | 59 | ]; |
| 61 | 60 | const find = ()=>targets.find(t=>(fuzzyList.find(f=>f.test(t)) ?? quotedList.find(q=>t.includes(q))) !== undefined) !== undefined; |
| 62 | 61 | if (fuzzyList.length + quotedList.length === 0 || find()) { |
| 63 | 62 | this.itemMap[cmd.name].classList.remove('isFiltered'); |
| 64 | 63 | } else { |
| 65 | 64 | this.itemMap[cmd.name].classList.add('isFiltered'); |
| @@ -78,7 +77,7 @@ export class SlashCommandBrowser { | ||
| 78 | 77 | list.classList.add('autoComplete'); |
| 79 | 78 | this.cmdList = Object |
| 80 | 79 | .keys(SlashCommandParser.commands) |
| 81 | 80 | .filter(key => SlashCommandParser.commands[key].name === key) // exclude aliases |
| 82 | 81 | .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) |
| 83 | 82 | .map(key => SlashCommandParser.commands[key]) |
| 84 | 83 | ; |
| @@ -97,7 +96,7 @@ export class SlashCommandBrowser { | ||
| 97 | 96 | } |
| 98 | 97 | } |
| 99 | 98 | } |
| 100 | 99 | if (this.details !== details) { |
| 101 | 100 | Array.from(list.querySelectorAll('.selected')).forEach(it=>it.classList.remove('selected')); |
| 102 | 101 | item.classList.add('selected'); |
| 103 | 102 | this.details?.remove(); |
| @@ -124,7 +123,7 @@ export class SlashCommandBrowser { | ||
| 124 | 123 | parent.append(this.dom); |
| 125 | 124 | |
| 126 | 125 | this.mo = new MutationObserver(muts=>{ |
| 127 | 126 | if (muts.find(mut=>Array.from(mut.removedNodes).find(it=>it === this.dom || it.contains(this.dom)))) { |
| 128 | 127 | this.mo.disconnect(); |
| 129 | 128 | window.removeEventListener('keydown', boundHandler); |
| 130 | 129 | } |
| @@ -136,7 +135,7 @@ export class SlashCommandBrowser { | ||
| 136 | 135 | } |
| 137 | 136 | |
| 138 | 137 | handleKeyDown(evt) { |
| 139 | 138 | if (!evt.shiftKey && !evt.altKey && evt.ctrlKey && evt.key.toLowerCase() === 'f') { |
| 140 | 139 | if (!this.dom.closest('body')) return; |
| 141 | 140 | if (this.dom.closest('.mes') && !this.dom.closest('.last_mes')) return; |
| 142 | 141 | evt.preventDefault(); |
| @@ -880,6 +880,13 @@ function setSettingByName(setting, value, trigger) { | ||
| 880 | 880 | } |
| 881 | 881 | } |
| 882 | 882 | |
| 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 | + */ | |
| 883 | 890 | async function generateTextGenWithStreaming(generate_data, signal) { |
| 884 | 891 | generate_data.stream = true; |
| 885 | 892 | |
| @@ -995,6 +1002,7 @@ export function parseTabbyLogprobs(data) { | ||
| 995 | 1002 | * @param {Response} response - Response from the server. |
| 996 | 1003 | * @param {string} decoded - Decoded response body. |
| 997 | 1004 | * @returns {void} Nothing. |
| 1005 | + * @throws {Error} If the response contains an error message, throws Error with the message. | |
| 998 | 1006 | */ |
| 999 | 1007 | function tryParseStreamingError(response, decoded) { |
| 1000 | 1008 | let data = {}; |
| @@ -1051,8 +1051,12 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 1051 | 1051 | } |
| 1052 | 1052 | } catch (error) { |
| 1053 | 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 | 1058 | if (!response.headersSent) { |
| 1055 | 1059 | response.status(502).send({ error: true{ message, ...error } }); |
| 1056 | 1060 | } else { |
| 1057 | 1061 | response.end(); |
| 1058 | 1062 | } |
| @@ -1068,7 +1072,7 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 1068 | 1072 | |
| 1069 | 1073 | const message = errorResponse.statusText || 'Unknown error occurred'; |
| 1070 | 1074 | const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota'; |
| 1071 | 1075 | console.log('Chat completion request error: ', message, responseText); |
| 1072 | 1076 | |
| 1073 | 1077 | if (!response.headersSent) { |
| 1074 | 1078 | response.send({ error: { message }, quota_error: quota_error }); |