|
import { getPresetManager } from './preset-manager.js'; |
|
import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js'; |
|
import { getTextGenServer } from './textgen-settings.js'; |
|
import { extractReasoningFromData } from './reasoning.js'; |
|
import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js'; |
|
import { getStreamingReply, tryParseStreamingError } from './openai.js'; |
|
import EventSourceStream from './sse-stream.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class TextCompletionService { |
|
static TYPE = 'textgenerationwebui'; |
|
|
|
|
|
|
|
|
|
|
|
static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) { |
|
const payload = { |
|
stream, |
|
prompt, |
|
max_tokens, |
|
max_new_tokens: max_tokens, |
|
model, |
|
api_type, |
|
api_server: api_server ?? getTextGenServer(api_type), |
|
temperature, |
|
min_p, |
|
...props, |
|
}; |
|
|
|
|
|
Object.keys(payload).forEach(key => { |
|
if (payload[key] === undefined) { |
|
delete payload[key]; |
|
} |
|
}); |
|
|
|
return payload; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async sendRequest(data, extractData = true, signal = null) { |
|
if (!data.stream) { |
|
const response = await fetch(getGenerateUrl(this.TYPE), { |
|
method: 'POST', |
|
headers: getRequestHeaders(), |
|
cache: 'no-cache', |
|
body: JSON.stringify(data), |
|
signal: signal ?? new AbortController().signal, |
|
}); |
|
|
|
const json = await response.json(); |
|
if (!response.ok || json.error) { |
|
throw json; |
|
} |
|
|
|
if (!extractData) { |
|
return json; |
|
} |
|
|
|
return { |
|
content: extractMessageFromData(json, this.TYPE), |
|
reasoning: extractReasoningFromData(json, { |
|
mainApi: this.TYPE, |
|
textGenType: data.api_type, |
|
ignoreShowThoughts: true, |
|
}), |
|
}; |
|
} |
|
|
|
const response = await fetch('/api/backends/text-completions/generate', { |
|
method: 'POST', |
|
headers: getRequestHeaders(), |
|
cache: 'no-cache', |
|
body: JSON.stringify(data), |
|
signal: signal ?? new AbortController().signal, |
|
}); |
|
|
|
if (!response.ok) { |
|
const text = await response.text(); |
|
tryParseStreamingError(response, text, { quiet: true }); |
|
|
|
throw new Error(`Got response status ${response.status}`); |
|
} |
|
|
|
const eventStream = new EventSourceStream(); |
|
response.body.pipeThrough(eventStream); |
|
const reader = eventStream.readable.getReader(); |
|
return async function* streamData() { |
|
let text = ''; |
|
const swipes = []; |
|
const state = { reasoning: '' }; |
|
while (true) { |
|
const { done, value } = await reader.read(); |
|
if (done) return; |
|
if (value.data === '[DONE]') return; |
|
|
|
tryParseStreamingError(response, value.data, { quiet: true }); |
|
|
|
let data = JSON.parse(value.data); |
|
|
|
if (data?.choices?.[0]?.index > 0) { |
|
const swipeIndex = data.choices[0].index - 1; |
|
swipes[swipeIndex] = (swipes[swipeIndex] || '') + data.choices[0].text; |
|
} else { |
|
const newText = data?.choices?.[0]?.text || data?.content || ''; |
|
text += newText; |
|
state.reasoning += data?.choices?.[0]?.reasoning ?? ''; |
|
} |
|
|
|
yield { text, swipes, state }; |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async processRequest( |
|
custom, |
|
options = {}, |
|
extractData = true, |
|
signal = null, |
|
) { |
|
const { presetName, instructName } = options; |
|
let requestData = { ...custom }; |
|
const prompt = custom.prompt; |
|
|
|
|
|
if (presetName) { |
|
const presetManager = getPresetManager(this.TYPE); |
|
if (presetManager) { |
|
const preset = presetManager.getCompletionPresetByName(presetName); |
|
if (preset) { |
|
|
|
const presetPayload = this.presetToGeneratePayload(preset, {}); |
|
requestData = { ...presetPayload, ...requestData }; |
|
} else { |
|
console.warn(`Preset "${presetName}" not found, continuing with default settings`); |
|
} |
|
} else { |
|
console.warn('Preset manager not found, continuing with default settings'); |
|
} |
|
} |
|
|
|
|
|
|
|
let instructPreset; |
|
|
|
if (Array.isArray(prompt) && instructName) { |
|
const instructPresetManager = getPresetManager('instruct'); |
|
instructPreset = instructPresetManager?.getCompletionPresetByName(instructName); |
|
if (instructPreset) { |
|
|
|
instructPreset = structuredClone(instructPreset); |
|
instructPreset.names_behavior = names_behavior_types.NONE; |
|
if (options.instructSettings) { |
|
Object.assign(instructPreset, options.instructSettings); |
|
} |
|
|
|
|
|
const formattedMessages = []; |
|
const prefillActive = prompt.length > 0 ? prompt[prompt.length - 1].role === 'assistant' : false; |
|
for (const message of prompt) { |
|
let messageContent = message.content; |
|
if (!message.ignoreInstruct) { |
|
const isLastMessage = message === prompt[prompt.length - 1]; |
|
|
|
|
|
|
|
|
|
if (!isLastMessage || !prefillActive) { |
|
messageContent = formatInstructModeChat( |
|
message.role, |
|
message.content, |
|
message.role === 'user', |
|
false, |
|
undefined, |
|
undefined, |
|
undefined, |
|
undefined, |
|
instructPreset, |
|
); |
|
} |
|
|
|
|
|
if (isLastMessage) { |
|
if (!prefillActive) { |
|
messageContent += formatInstructModePrompt( |
|
undefined, |
|
false, |
|
undefined, |
|
undefined, |
|
undefined, |
|
false, |
|
false, |
|
instructPreset, |
|
); |
|
} else { |
|
const overridenInstructPreset = structuredClone(instructPreset); |
|
overridenInstructPreset.output_suffix = ''; |
|
overridenInstructPreset.wrap = false; |
|
messageContent = formatInstructModeChat( |
|
message.role, |
|
message.content, |
|
false, |
|
false, |
|
undefined, |
|
undefined, |
|
undefined, |
|
undefined, |
|
overridenInstructPreset, |
|
); |
|
} |
|
} |
|
} |
|
formattedMessages.push(messageContent); |
|
} |
|
requestData.prompt = formattedMessages.join(''); |
|
const stoppingStrings = getInstructStoppingSequences({ customInstruct: instructPreset, useStopStrings: false }); |
|
requestData.stop = stoppingStrings; |
|
requestData.stopping_strings = stoppingStrings; |
|
} else { |
|
console.warn(`Instruct preset "${instructName}" not found, using basic formatting`); |
|
requestData.prompt = prompt.map(x => x.content).join('\n\n'); |
|
} |
|
} else if (typeof prompt === 'string') { |
|
requestData.prompt = prompt; |
|
} else { |
|
requestData.prompt = prompt.map(x => x.content).join('\n\n'); |
|
} |
|
|
|
|
|
const data = this.createRequestData(requestData); |
|
|
|
const response = await this.sendRequest(data, extractData, signal); |
|
|
|
if (!data.stream && extractData) { |
|
|
|
|
|
const extractedData = response; |
|
|
|
let message = extractedData.content; |
|
|
|
message = message.replace(/[^\S\r\n]+$/gm, ''); |
|
|
|
if (requestData.stopping_strings) { |
|
for (const stoppingString of requestData.stopping_strings) { |
|
if (stoppingString.length) { |
|
for (let j = stoppingString.length; j > 0; j--) { |
|
if (message.slice(-j) === stoppingString.slice(0, j)) { |
|
message = message.slice(0, -j); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (instructPreset) { |
|
[ |
|
instructPreset.stop_sequence, |
|
instructPreset.input_sequence, |
|
].forEach(sequence => { |
|
if (sequence?.trim()) { |
|
const index = message.indexOf(sequence); |
|
if (index !== -1) { |
|
message = message.substring(0, index); |
|
} |
|
} |
|
}); |
|
|
|
[ |
|
instructPreset.output_sequence, |
|
instructPreset.last_output_sequence, |
|
].forEach(sequences => { |
|
if (sequences) { |
|
sequences.split('\n') |
|
.filter(line => line.trim() !== '') |
|
.forEach(line => { |
|
message = message.replaceAll(line, ''); |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
extractedData.content = message; |
|
} |
|
|
|
return response; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static presetToGeneratePayload(preset, customPreset = {}) { |
|
if (!preset || typeof preset !== 'object') { |
|
throw new Error('Invalid preset: must be an object'); |
|
} |
|
|
|
|
|
const settings = { ...preset, ...customPreset }; |
|
|
|
|
|
let payload = { |
|
'temperature': settings.temp ? Number(settings.temp) : undefined, |
|
'min_p': settings.min_p ? Number(settings.min_p) : undefined, |
|
}; |
|
|
|
|
|
Object.keys(payload).forEach(key => { |
|
if (payload[key] === undefined) { |
|
delete payload[key]; |
|
} |
|
}); |
|
|
|
return payload; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
export class ChatCompletionService { |
|
static TYPE = 'openai'; |
|
|
|
|
|
|
|
|
|
|
|
static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, reverse_proxy, proxy_password, ...props }) { |
|
const payload = { |
|
stream, |
|
messages, |
|
model, |
|
chat_completion_source, |
|
max_tokens, |
|
temperature, |
|
custom_url, |
|
reverse_proxy, |
|
proxy_password, |
|
use_makersuite_sysprompt: true, |
|
claude_use_sysprompt: true, |
|
...props, |
|
}; |
|
|
|
|
|
Object.keys(payload).forEach(key => { |
|
if (payload[key] === undefined) { |
|
delete payload[key]; |
|
} |
|
}); |
|
|
|
return payload; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async sendRequest(data, extractData = true, signal = null) { |
|
const response = await fetch('/api/backends/chat-completions/generate', { |
|
method: 'POST', |
|
headers: getRequestHeaders(), |
|
cache: 'no-cache', |
|
body: JSON.stringify(data), |
|
signal: signal ?? new AbortController().signal, |
|
}); |
|
|
|
if (!data.stream) { |
|
const json = await response.json(); |
|
if (!response.ok || json.error) { |
|
throw json; |
|
} |
|
|
|
if (!extractData) { |
|
return json; |
|
} |
|
|
|
return { |
|
content: extractMessageFromData(json, this.TYPE), |
|
reasoning: extractReasoningFromData(json, { |
|
mainApi: this.TYPE, |
|
textGenType: data.chat_completion_source, |
|
ignoreShowThoughts: true, |
|
}), |
|
}; |
|
} |
|
|
|
if (!response.ok) { |
|
const text = await response.text(); |
|
tryParseStreamingError(response, text, { quiet: true }); |
|
|
|
throw new Error(`Got response status ${response.status}`); |
|
} |
|
|
|
const eventStream = new EventSourceStream(); |
|
response.body.pipeThrough(eventStream); |
|
const reader = eventStream.readable.getReader(); |
|
return async function* streamData() { |
|
let text = ''; |
|
const swipes = []; |
|
const state = { reasoning: '', image: '' }; |
|
while (true) { |
|
const { done, value } = await reader.read(); |
|
if (done) return; |
|
const rawData = value.data; |
|
if (rawData === '[DONE]') return; |
|
tryParseStreamingError(response, rawData, { quiet: true }); |
|
const parsed = JSON.parse(rawData); |
|
|
|
const reply = getStreamingReply(parsed, state, { |
|
chatCompletionSource: data.chat_completion_source, |
|
overrideShowThoughts: true, |
|
}); |
|
if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { |
|
const swipeIndex = parsed.choices[0].index - 1; |
|
swipes[swipeIndex] = (swipes[swipeIndex] || '') + reply; |
|
} else { |
|
text += reply; |
|
} |
|
|
|
yield { text, swipes: swipes, state }; |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async processRequest(custom, options, extractData = true, signal = null) { |
|
const { presetName } = options; |
|
let requestData = { ...custom }; |
|
|
|
|
|
if (presetName) { |
|
const presetManager = getPresetManager(this.TYPE); |
|
if (presetManager) { |
|
const preset = presetManager.getCompletionPresetByName(presetName); |
|
if (preset) { |
|
|
|
const presetPayload = this.presetToGeneratePayload(preset, {}); |
|
requestData = { ...presetPayload, ...requestData }; |
|
} else { |
|
console.warn(`Preset "${presetName}" not found, continuing with default settings`); |
|
} |
|
} else { |
|
console.warn('Preset manager not found, continuing with default settings'); |
|
} |
|
} |
|
|
|
const data = this.createRequestData(requestData); |
|
|
|
return await this.sendRequest(data, extractData, signal); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static presetToGeneratePayload(preset, customParams = {}) { |
|
if (!preset || typeof preset !== 'object') { |
|
throw new Error('Invalid preset: must be an object'); |
|
} |
|
|
|
|
|
const settings = { ...preset, ...customParams }; |
|
|
|
|
|
const payload = { |
|
temperature: settings.temperature ? Number(settings.temperature) : undefined, |
|
}; |
|
|
|
|
|
Object.keys(payload).forEach(key => { |
|
if (payload[key] === undefined) { |
|
delete payload[key]; |
|
} |
|
}); |
|
|
|
return payload; |
|
} |
|
} |
|
|