Added stream support to "custom-request"
| @@ -3,10 +3,13 @@ import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../sc | ||
| 3 | 3 | import { getTextGenServer } from './textgen-settings.js'; |
| 4 | 4 | import { extractReasoningFromData } from './reasoning.js'; |
| 5 | 5 | import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types } from './instruct-mode.js'; |
| 6 | +import { getStreamingReply, tryParseStreamingError } from './openai.js'; | |
| 7 | +import EventSourceStream from './sse-stream.js'; | |
| 6 | 8 | |
| 7 | 9 | // #region Type Definitions |
| 8 | 10 | /** |
| 9 | 11 | * @typedef {Object} TextCompletionRequestBase |
| 12 | + * @property {boolean?} [stream=false] - Whether to stream the response | |
| 10 | 13 | * @property {number} max_tokens - Maximum number of tokens to generate |
| 11 | 14 | * @property {string} [model] - Optional model name |
| 12 | 15 | * @property {string} api_type - Type of API to use |
| @@ -17,6 +20,7 @@ import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types | ||
| 17 | 20 | |
| 18 | 21 | /** |
| 19 | 22 | * @typedef {Object} TextCompletionPayloadBase |
| 23 | + * @property {boolean?} [stream=false] - Whether to stream the response | |
| 20 | 24 | * @property {string} prompt - The text prompt for completion |
| 21 | 25 | * @property {number} max_tokens - Maximum number of tokens to generate |
| 22 | 26 | * @property {number} max_new_tokens - Alias for max_tokens |
| @@ -36,6 +40,7 @@ import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types | ||
| 36 | 40 | |
| 37 | 41 | /** |
| 38 | 42 | * @typedef {Object} ChatCompletionPayloadBase |
| 43 | + * @property {boolean?} [stream=false] - Whether to stream the response | |
| 39 | 44 | * @property {ChatCompletionMessage[]} messages - Array of chat messages |
| 40 | 45 | * @property {string} [model] - Optional model name to use for completion |
| 41 | 46 | * @property {string} chat_completion_source - Source provider for chat completion |
| @@ -52,10 +57,20 @@ import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types | ||
| 52 | 57 | * @property {string} reasoning - Extracted reasoning. |
| 53 | 58 | */ |
| 54 | 59 | |
| 60 | +/** | |
| 61 | + * @typedef {Object} StreamResponse | |
| 62 | + * @property {string} text - Generated text. | |
| 63 | + * @property {string[]} swipes - Generated swipes | |
| 64 | + * @property {Object} state - Generated state | |
| 65 | + * @property {string?} [state.reasoning] - Generated reasoning | |
| 66 | + * @property {string?} [state.image] - Generated image | |
| 67 | + * @returns {StreamResponse} | |
| 68 | + */ | |
| 69 | + | |
| 55 | 70 | // #endregion |
| 56 | 71 | |
| 57 | 72 | /** |
| 58 | 73 | * Creates & sends a text completion request. Streaming is not supported. |
| 59 | 74 | */ |
| 60 | 75 | export class TextCompletionService { |
| 61 | 76 | static TYPE = 'textgenerationwebui'; |
| @@ -64,9 +79,10 @@ export class TextCompletionService { | ||
| 64 | 79 | * @param {Record<string, any> & TextCompletionRequestBase & {prompt: string}} custom |
| 65 | 80 | * @returns {TextCompletionPayload} |
| 66 | 81 | */ |
| 67 | 82 | static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) { |
| 68 | 83 | const payload = { |
| 69 | 84 | ...props, |
| 85 | + stream, | |
| 70 | 86 | prompt, |
| 71 | 87 | max_tokens, |
| 72 | 88 | max_new_tokens: max_tokens, |
| @@ -75,7 +91,6 @@ export class TextCompletionService { | ||
| 75 | 91 | api_server: api_server ?? getTextGenServer(api_type), |
| 76 | 92 | temperature, |
| 77 | 93 | min_p, |
| 78 | - stream: false, | |
| 79 | 94 | }; |
| 80 | 95 | |
| 81 | 96 | // Remove undefined values to avoid API errors |
| @@ -92,16 +107,18 @@ export class TextCompletionService { | ||
| 92 | 107 | * Sends a text completion request to the specified server |
| 93 | 108 | * @param {TextCompletionPayload} data Request data |
| 94 | 109 | * @param {boolean?} extractData Extract message from the response. Default true |
| 95 | - * @returns {Promise<ExtractedData | any>} Extracted data or the raw response | |
| 110 | + * @param {AbortSignal?} signal | |
| 111 | + * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator | |
| 96 | 112 | * @throws {Error} |
| 97 | 113 | */ |
| 98 | 114 | static async sendRequest(data, extractData = true, signal = null) { |
| 115 | + if (!data.stream) { | |
| 99 | 116 | const response = await fetch(getGenerateUrl(this.TYPE), { |
| 100 | 117 | method: 'POST', |
| 101 | 118 | headers: getRequestHeaders(), |
| 102 | 119 | cache: 'no-cache', |
| 103 | 120 | body: JSON.stringify(data), |
| 104 | 121 | signal: signal ?? new AbortController().signal, |
| 105 | 122 | }); |
| 106 | 123 | |
| 107 | 124 | const json = await response.json(); |
| @@ -123,6 +140,51 @@ export class TextCompletionService { | ||
| 123 | 140 | }; |
| 124 | 141 | } |
| 125 | 142 | |
| 143 | + const response = await fetch('/api/backends/text-completions/generate', { | |
| 144 | + method: 'POST', | |
| 145 | + headers: getRequestHeaders(), | |
| 146 | + cache: 'no-cache', | |
| 147 | + body: JSON.stringify(data), | |
| 148 | + signal: signal ?? new AbortController().signal, | |
| 149 | + }); | |
| 150 | + | |
| 151 | + if (!response.ok) { | |
| 152 | + const text = await response.text(); | |
| 153 | + tryParseStreamingError(response, text, true); | |
| 154 | + | |
| 155 | + throw new Error(`Got response status ${response.status}`); | |
| 156 | + } | |
| 157 | + | |
| 158 | + const eventStream = new EventSourceStream(); | |
| 159 | + response.body.pipeThrough(eventStream); | |
| 160 | + const reader = eventStream.readable.getReader(); | |
| 161 | + return async function* streamData() { | |
| 162 | + let text = ''; | |
| 163 | + const swipes = []; | |
| 164 | + const state = { reasoning: '' }; | |
| 165 | + while (true) { | |
| 166 | + const { done, value } = await reader.read(); | |
| 167 | + if (done) return; | |
| 168 | + if (value.data === '[DONE]') return; | |
| 169 | + | |
| 170 | + tryParseStreamingError(response, value.data, true); | |
| 171 | + | |
| 172 | + let data = JSON.parse(value.data); | |
| 173 | + | |
| 174 | + if (data?.choices?.[0]?.index > 0) { | |
| 175 | + const swipeIndex = data.choices[0].index - 1; | |
| 176 | + swipes[swipeIndex] = (swipes[swipeIndex] || '') + data.choices[0].text; | |
| 177 | + } else { | |
| 178 | + const newText = data?.choices?.[0]?.text || data?.content || ''; | |
| 179 | + text += newText; | |
| 180 | + state.reasoning += data?.choices?.[0]?.reasoning ?? ''; | |
| 181 | + } | |
| 182 | + | |
| 183 | + yield { text, swipes, state }; | |
| 184 | + } | |
| 185 | + }; | |
| 186 | + } | |
| 187 | + | |
| 126 | 188 | /** |
| 127 | 189 | * Process and send a text completion request with optional preset & instruct |
| 128 | 190 | * @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} custom |
| @@ -130,13 +192,15 @@ export class TextCompletionService { | ||
| 130 | 192 | * @param {string?} [options.presetName] - Name of the preset to use for generation settings |
| 131 | 193 | * @param {string?} [options.instructName] - Name of instruct preset for message formatting |
| 132 | 194 | * @param {boolean} extractData - Whether to extract structured data from response |
| 133 | - * @returns {Promise<ExtractedData | any>} Extracted data or the raw response | |
| 195 | + * @param {AbortSignal?} [signal] | |
| 196 | + * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator | |
| 134 | 197 | * @throws {Error} |
| 135 | 198 | */ |
| 136 | 199 | static async processRequest( |
| 137 | 200 | custom, |
| 138 | 201 | options = {}, |
| 139 | 202 | extractData = true, |
| 203 | + signal = null, | |
| 140 | 204 | ) { |
| 141 | 205 | const { presetName, instructName } = options; |
| 142 | 206 | let requestData = { ...custom }; |
| @@ -220,7 +284,7 @@ export class TextCompletionService { | ||
| 220 | 284 | // @ts-ignore |
| 221 | 285 | const data = this.createRequestData(requestData); |
| 222 | 286 | |
| 223 | 287 | return await this.sendRequest(data, extractData, signal); |
| 224 | 288 | } |
| 225 | 289 | |
| 226 | 290 | /** |
| @@ -256,7 +320,7 @@ export class TextCompletionService { | ||
| 256 | 320 | } |
| 257 | 321 | |
| 258 | 322 | /** |
| 259 | 323 | * Creates & sends a chat completion request. Streaming is not supported. |
| 260 | 324 | */ |
| 261 | 325 | export class ChatCompletionService { |
| 262 | 326 | static TYPE = 'openai'; |
| @@ -265,16 +329,16 @@ export class ChatCompletionService { | ||
| 265 | 329 | * @param {ChatCompletionPayload} custom |
| 266 | 330 | * @returns {ChatCompletionPayload} |
| 267 | 331 | */ |
| 268 | 332 | static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) { |
| 269 | 333 | const payload = { |
| 270 | 334 | ...props, |
| 335 | + stream, | |
| 271 | 336 | messages, |
| 272 | 337 | model, |
| 273 | 338 | chat_completion_source, |
| 274 | 339 | max_tokens, |
| 275 | 340 | temperature, |
| 276 | 341 | custom_url, |
| 277 | - stream: false, | |
| 278 | 342 | }; |
| 279 | 343 | |
| 280 | 344 | // Remove undefined values to avoid API errors |
| @@ -291,18 +355,20 @@ export class ChatCompletionService { | ||
| 291 | 355 | * Sends a chat completion request |
| 292 | 356 | * @param {ChatCompletionPayload} data Request data |
| 293 | 357 | * @param {boolean?} extractData Extract message from the response. Default true |
| 294 | - * @returns {Promise<ExtractedData | any>} Extracted data or the raw response | |
| 358 | + * @param {AbortSignal?} signal Abort signal | |
| 359 | + * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator | |
| 295 | 360 | * @throws {Error} |
| 296 | 361 | */ |
| 297 | 362 | static async sendRequest(data, extractData = true, signal = null) { |
| 298 | 363 | const response = await fetch('/api/backends/chat-completions/generate', { |
| 299 | 364 | method: 'POST', |
| 300 | 365 | headers: getRequestHeaders(), |
| 301 | 366 | cache: 'no-cache', |
| 302 | 367 | body: JSON.stringify(data), |
| 303 | 368 | signal: signal ?? new AbortController().signal, |
| 304 | 369 | }); |
| 305 | 370 | |
| 371 | + if (!data.stream) { | |
| 306 | 372 | const json = await response.json(); |
| 307 | 373 | if (!response.ok || json.error) { |
| 308 | 374 | throw json; |
| @@ -322,16 +388,55 @@ export class ChatCompletionService { | ||
| 322 | 388 | }; |
| 323 | 389 | } |
| 324 | 390 | |
| 391 | + if (!response.ok) { | |
| 392 | + const text = await response.text(); | |
| 393 | + tryParseStreamingError(response, text, true); | |
| 394 | + | |
| 395 | + throw new Error(`Got response status ${response.status}`); | |
| 396 | + } | |
| 397 | + | |
| 398 | + const eventStream = new EventSourceStream(); | |
| 399 | + response.body.pipeThrough(eventStream); | |
| 400 | + const reader = eventStream.readable.getReader(); | |
| 401 | + return async function* streamData() { | |
| 402 | + let text = ''; | |
| 403 | + const swipes = []; | |
| 404 | + const state = { reasoning: '', image: '' }; | |
| 405 | + while (true) { | |
| 406 | + const { done, value } = await reader.read(); | |
| 407 | + if (done) return; | |
| 408 | + const rawData = value.data; | |
| 409 | + if (rawData === '[DONE]') return; | |
| 410 | + tryParseStreamingError(response, rawData, true); | |
| 411 | + const parsed = JSON.parse(rawData); | |
| 412 | + | |
| 413 | + const reply = getStreamingReply(parsed, state, { | |
| 414 | + chatCompletionSource: data.chat_completion_source, | |
| 415 | + ignoreShowThoughts: true, | |
| 416 | + }); | |
| 417 | + if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { | |
| 418 | + const swipeIndex = parsed.choices[0].index - 1; | |
| 419 | + swipes[swipeIndex] = (swipes[swipeIndex] || '') + reply; | |
| 420 | + } else { | |
| 421 | + text += reply; | |
| 422 | + } | |
| 423 | + | |
| 424 | + yield { text, swipes: swipes, state }; | |
| 425 | + } | |
| 426 | + }; | |
| 427 | + } | |
| 428 | + | |
| 325 | 429 | /** |
| 326 | 430 | * Process and send a chat completion request with optional preset |
| 327 | 431 | * @param {ChatCompletionPayload} custom |
| 328 | 432 | * @param {Object} options - Configuration options |
| 329 | 433 | * @param {string?} [options.presetName] - Name of the preset to use for generation settings |
| 330 | 434 | * @param {boolean} [extractData=true] - Whether to extract structured data from response |
| 331 | - * @returns {Promise<ExtractedData | any>} Extracted data or the raw response | |
| 435 | + * @param {AbortSignal?} [signal] - Abort signal | |
| 436 | + * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator | |
| 332 | 437 | * @throws {Error} |
| 333 | 438 | */ |
| 334 | 439 | static async processRequest(custom, options, extractData = true, signal = null) { |
| 335 | 440 | const { presetName } = options; |
| 336 | 441 | let requestData = { ...custom }; |
| 337 | 442 | |
| @@ -354,7 +459,7 @@ export class ChatCompletionService { | ||
| 354 | 459 | |
| 355 | 460 | const data = this.createRequestData(requestData); |
| 356 | 461 | |
| 357 | 462 | return await this.sendRequest(data, extractData, signal); |
| 358 | 463 | } |
| 359 | 464 | |
| 360 | 465 | /** |
| @@ -276,10 +276,12 @@ export async function getWebLlmContextSize() { | ||
| 276 | 276 | } |
| 277 | 277 | |
| 278 | 278 | /** |
| 279 | 279 | * It uses the profiles to send a generate request to the API. Doesn't support streaming. |
| 280 | 280 | */ |
| 281 | 281 | export class ConnectionManagerRequestService { |
| 282 | 282 | static defaultSendRequestParams = { |
| 283 | + stream: false, | |
| 284 | + signal: null, | |
| 283 | 285 | extractData: true, |
| 284 | 286 | includePreset: true, |
| 285 | 287 | includeInstruct: true, |
| @@ -296,11 +298,11 @@ export class ConnectionManagerRequestService { | ||
| 296 | 298 | * @param {string} profileId |
| 297 | 299 | * @param {string | (import('../custom-request.js').ChatCompletionMessage & {ignoreInstruct?: boolean})[]} prompt |
| 298 | 300 | * @param {number} maxTokens |
| 299 | 301 | * @param {{stream?: boolean, signal?: AbortSignal, extractData?: boolean, includePreset?: boolean, includeInstruct?: boolean}} custom - default values are true |
| 300 | 302 | * @returns {Promise<import('../custom-request.js').ExtractedData | any(() => AsyncGenerator<import('../custom-request.js').StreamResponse>)>} ExtractedIf not streaming, returns extracted data; orif thestreaming, rawreturns responsea function that creates an AsyncGenerator |
| 301 | 303 | */ |
| 302 | 304 | static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams) { |
| 303 | 305 | const { stream, signal, extractData, includePreset, includeInstruct } = { ...this.defaultSendRequestParams, ...custom }; |
| 304 | 306 | |
| 305 | 307 | const context = SillyTavern.getContext(); |
| 306 | 308 | if (context.extensionSettings.disabledExtensions.includes('connection-manager')) { |
| @@ -319,6 +321,7 @@ export class ConnectionManagerRequestService { | ||
| 319 | 321 | |
| 320 | 322 | const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }]; |
| 321 | 323 | return await context.ChatCompletionService.processRequest({ |
| 324 | + stream, | |
| 322 | 325 | messages, |
| 323 | 326 | max_tokens: maxTokens, |
| 324 | 327 | model: profile.model, |
| @@ -326,7 +329,7 @@ export class ConnectionManagerRequestService { | ||
| 326 | 329 | custom_url: profile['api-url'], |
| 327 | 330 | }, { |
| 328 | 331 | presetName: includePreset ? profile.preset : undefined, |
| 329 | 332 | }, extractData, signal); |
| 330 | 333 | } |
| 331 | 334 | case 'textgenerationwebui': { |
| 332 | 335 | if (!selectedApiMap.type) { |
| @@ -334,6 +337,7 @@ export class ConnectionManagerRequestService { | ||
| 334 | 337 | } |
| 335 | 338 | |
| 336 | 339 | return await context.TextCompletionService.processRequest({ |
| 340 | + stream, | |
| 337 | 341 | prompt, |
| 338 | 342 | max_tokens: maxTokens, |
| 339 | 343 | model: profile.model, |
| @@ -342,7 +346,7 @@ export class ConnectionManagerRequestService { | ||
| 342 | 346 | }, { |
| 343 | 347 | instructName: includeInstruct ? profile.instruct : undefined, |
| 344 | 348 | presetName: includePreset ? profile.preset : undefined, |
| 345 | 349 | }, extractData, signal); |
| 346 | 350 | } |
| 347 | 351 | default: { |
| 348 | 352 | throw new Error(`Unknown API type ${selectedApiMap.selected}`); |
| @@ -1444,8 +1444,9 @@ export async function prepareOpenAIMessages({ | ||
| 1444 | 1444 | * Handles errors during streaming requests. |
| 1445 | 1445 | * @param {Response} response |
| 1446 | 1446 | * @param {string} decoded - response text or decoded stream data |
| 1447 | + * @param {boolean?} [supressToastr=false] | |
| 1447 | 1448 | */ |
| 1448 | 1449 | export function tryParseStreamingError(response, decoded, supressToastr = false) { |
| 1449 | 1450 | try { |
| 1450 | 1451 | const data = JSON.parse(decoded); |
| 1451 | 1452 | |
| @@ -1453,19 +1454,19 @@ function tryParseStreamingError(response, decoded) { | ||
| 1453 | 1454 | return; |
| 1454 | 1455 | } |
| 1455 | 1456 | |
| 1456 | 1457 | checkQuotaError(data, supressToastr); |
| 1457 | 1458 | checkModerationError(data, supressToastr); |
| 1458 | 1459 | |
| 1459 | 1460 | // these do not throw correctly (equiv to Error("[object Object]")) |
| 1460 | 1461 | // if trying to fix "[object Object]" displayed to users, start here |
| 1461 | 1462 | |
| 1462 | 1463 | if (data.error) { |
| 1463 | 1464 | !supressToastr && toastr.error(data.error.message || response.statusText, 'Chat Completion API'); |
| 1464 | 1465 | throw new Error(data); |
| 1465 | 1466 | } |
| 1466 | 1467 | |
| 1467 | 1468 | if (data.message) { |
| 1468 | 1469 | !supressToastr && toastr.error(data.message, 'Chat Completion API'); |
| 1469 | 1470 | throw new Error(data); |
| 1470 | 1471 | } |
| 1471 | 1472 | } |
| @@ -1477,16 +1478,17 @@ function tryParseStreamingError(response, decoded) { | ||
| 1477 | 1478 | /** |
| 1478 | 1479 | * Checks if the response contains a quota error and displays a popup if it does. |
| 1479 | 1480 | * @param data |
| 1481 | + * @param {boolean?} [supressToastr=false] | |
| 1480 | 1482 | * @returns {void} |
| 1481 | 1483 | * @throws {object} - response JSON |
| 1482 | 1484 | */ |
| 1483 | 1485 | function checkQuotaError(data, supressToastr = false) { |
| 1484 | 1486 | if (!data) { |
| 1485 | 1487 | return; |
| 1486 | 1488 | } |
| 1487 | 1489 | |
| 1488 | 1490 | if (data.quota_error) { |
| 1489 | 1491 | !supressToastr && renderTemplateAsync('quotaError').then((html) => Popup.show.text('Quota Error', html)); |
| 1490 | 1492 | |
| 1491 | 1493 | // this does not throw correctly (equiv to Error("[object Object]")) |
| 1492 | 1494 | // if trying to fix "[object Object]" displayed to users, start here |
| @@ -1494,9 +1496,13 @@ function checkQuotaError(data) { | ||
| 1494 | 1496 | } |
| 1495 | 1497 | } |
| 1496 | 1498 | |
| 1497 | -function checkModerationError(data) { | |
| 1499 | +/** | |
| 1500 | + * @param {any} data | |
| 1501 | + * @param {boolean?} [supressToastr=false] | |
| 1502 | + */ | |
| 1503 | +function checkModerationError(data, supressToastr = false) { | |
| 1498 | 1504 | const moderationError = data?.error?.message?.includes('requires moderation'); |
| 1499 | 1505 | if (moderationError && !supressToastr) { |
| 1500 | 1506 | const moderationReason = `Reasons: ${data?.error?.metadata?.reasons?.join(', ') ?? '(N/A)'}`; |
| 1501 | 1507 | const flaggedText = data?.error?.metadata?.flagged_input ?? '(N/A)'; |
| 1502 | 1508 | toastr.info(flaggedText, moderationReason, { timeOut: 10000 }); |
| @@ -2255,37 +2261,43 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2255 | 2261 | * Extracts the reply from the response data from a chat completions-like source |
| 2256 | 2262 | * @param {object} data Response data from the chat completions-like source |
| 2257 | 2263 | * @param {object} state Additional state to keep track of |
| 2264 | + * @param {object} options Additional options | |
| 2265 | + * @param {string?} [options.chatCompletionSource] Chat completion source | |
| 2266 | + * @param {boolean?} [options.ignoreShowThoughts] Ignore show thoughts | |
| 2258 | 2267 | * @returns {string} The reply extracted from the response data |
| 2259 | 2268 | */ |
| 2260 | -function getStreamingReply(data, state) { | |
| 2269 | +export function getStreamingReply(data, state, { chatCompletionSource = null, ignoreShowThoughts = false } = {}) { | |
| 2261 | - if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) { | |
| 2270 | + const chat_completion_source = chatCompletionSource ?? oai_settings.chat_completion_source; | |
| 2262 | - if (oai_settings.show_thoughts) { | |
| 2271 | + const show_thoughts = ignoreShowThoughts ? true : oai_settings.show_thoughts; | |
| 2272 | + | |
| 2273 | + if (chat_completion_source === chat_completion_sources.CLAUDE) { | |
| 2274 | + if (show_thoughts) { | |
| 2263 | 2275 | state.reasoning += data?.delta?.thinking || ''; |
| 2264 | 2276 | } |
| 2265 | 2277 | return data?.delta?.text || ''; |
| 2266 | 2278 | } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) { |
| 2267 | 2279 | const inlineData = data?.candidates?.[0]?.content?.parts?.find(x => x.inlineData)?.inlineData; |
| 2268 | 2280 | if (inlineData) { |
| 2269 | 2281 | state.image = `data:${inlineData.mimeType};base64,${inlineData.data}`; |
| 2270 | 2282 | } |
| 2271 | 2283 | if (oai_settings.show_thoughts) { |
| 2272 | 2284 | state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || ''); |
| 2273 | 2285 | } |
| 2274 | 2286 | return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || ''; |
| 2275 | 2287 | } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { |
| 2276 | 2288 | return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || ''; |
| 2277 | 2289 | } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { |
| 2278 | 2290 | if (oai_settings.show_thoughts) { |
| 2279 | 2291 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); |
| 2280 | 2292 | } |
| 2281 | 2293 | return data.choices?.[0]?.delta?.content || ''; |
| 2282 | 2294 | } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) { |
| 2283 | 2295 | if (oai_settings.show_thoughts) { |
| 2284 | 2296 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); |
| 2285 | 2297 | } |
| 2286 | 2298 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 2287 | 2299 | } else if (oai_settings.chat_completion_source === chat_completion_sources.CUSTOM) { |
| 2288 | 2300 | if (oai_settings.show_thoughts) { |
| 2289 | 2301 | state.reasoning += |
| 2290 | 2302 | data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ?? |
| 2291 | 2303 | data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning ?? |