Merge branch 'staging' into immutable-config

This commit is contained in:
Cohee
2025-02-25 22:08:35 +02:00
30 changed files with 839 additions and 470 deletions

View File

@ -4,7 +4,7 @@ FROM node:lts-alpine3.19
ARG APP_HOME=/home/node/app
# Install system dependencies
RUN apk add gcompat tini git
RUN apk add --no-cache gcompat tini git
# Create app directory
WORKDIR ${APP_HOME}

View File

@ -2000,7 +2000,7 @@
</span>
</div>
</div>
<div class="range-block" data-source="deepseek,openrouter,custom">
<div class="range-block" data-source="deepseek,openrouter,custom,claude">
<label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
<input id="openai_show_thoughts" type="checkbox" />
<span>
@ -2014,10 +2014,11 @@
</span>
</div>
</div>
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom">
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude">
<div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Currently supported values are low, medium, and high.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
<label for="openai_reasoning_effort" data-i18n="Reasoning Effort">
Reasoning Effort
<label for="openai_reasoning_effort">
<span data-i18n="Reasoning Effort">Reasoning Effort</span>
<i data-source="claude" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%), but minimum 1024 tokens."></i>
</label>
<select id="openai_reasoning_effort">
<option data-i18n="openai_reasoning_effort_low" value="low">Low</option>
@ -2915,6 +2916,8 @@
<h4 data-i18n="Claude Model">Claude Model</h4>
<select id="model_claude_select">
<optgroup label="Versions">
<option value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option>
<option value="claude-3-7-sonnet-20250219">claude-3-7-sonnet-20250219</option>
<option value="claude-3-5-sonnet-latest">claude-3-5-sonnet-latest</option>
<option value="claude-3-5-sonnet-20241022">claude-3-5-sonnet-20241022</option>
<option value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
@ -4429,7 +4432,7 @@
<input id="prefer_character_jailbreak" type="checkbox" />
<small data-i18n="Prefer Character Card Instructions">Prefer Char. Instructions</small>
</label>
<label class="checkbox_label" for="never_resize_avatars" title="Avoid cropping and resizing imported character images. When off, crop/resize to 512x768." data-i18n="[title]Avoid cropping and resizing imported character images. When off, crop/resize to 512x768">
<label class="checkbox_label" for="never_resize_avatars" title="Avoid cropping and resizing imported character images. When off, crop/resize to 512x768.&#10;This will disable the upload cropping popup for avatars." data-i18n="[title]never_resize_avatars_tooltip">
<input id="never_resize_avatars" type="checkbox" />
<small data-i18n="Never resize avatars">Never resize avatars</small>
</label>

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "تفضيل التعليمات من بطاقة الشخصية",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "إذا تم التحقق وكانت بطاقة الشخصية تحتوي على تجاوز للكسر (تعليمات تاريخ المشاركة)، استخدم ذلك بدلاً من ذلك",
"Prefer Character Card Jailbreak": "تفضيل كسر الحصار من بطاقة الشخصية",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "تجنب اقتصاص صور الأحرف المستوردة وتغيير حجمها. عند إيقاف التشغيل، قم بالقص/تغيير الحجم إلى 512 × 768.",
"never_resize_avatars_tooltip": "تجنب اقتصاص صور الأحرف المستوردة وتغيير حجمها. عند إيقاف التشغيل، قم بالقص/تغيير الحجم إلى 512 × 768.",
"Never resize avatars": "لا تغيير حجم الصور الرمزية أبدًا",
"Show actual file names on the disk, in the characters list display only": "عرض الأسماء الفعلية للملفات على القرص، في عرض قائمة الشخصيات فقط",
"Show avatar filenames": "عرض أسماء ملفات الصور الرمزية",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Bevorzuge Charakterkarten-Prompt",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Wenn aktiviert und die Charakterkarte eine Jailbreak-Überschreibung enthält (Post-History-Instruction), verwende stattdessen diese",
"Prefer Character Card Jailbreak": "Bevorzuge Charakterkarten-Jailbreak",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Vermeiden Sie das Zuschneiden und Ändern der Größe importierter Zeichenbilder. Wenn deaktiviert, wird die Größe auf 512 x 768 zugeschnitten/angepasst.",
"never_resize_avatars_tooltip": "Vermeiden Sie das Zuschneiden und Ändern der Größe importierter Zeichenbilder. Wenn deaktiviert, wird die Größe auf 512 x 768 zugeschnitten/angepasst.",
"Never resize avatars": "Avatare niemals verkleinern",
"Show actual file names on the disk, in the characters list display only": "Zeige tatsächliche Dateinamen auf der Festplatte, nur in der Anzeige der Charakterliste",
"Show avatar filenames": "Avatar-Dateinamen anzeigen",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Preferir Indicaciones en Tarjeta de Personaje",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Si está marcado y la tarjeta de personaje contiene una anulación de jailbreak (Instrucciones Post Historial), usar eso en su lugar",
"Prefer Character Card Jailbreak": "Preferir Jailbreak en Tarjeta de Personaje",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Evite recortar y cambiar el tamaño de las imágenes de personajes importados. Cuando esté desactivado, recorte/cambie el tamaño a 512x768.",
"never_resize_avatars_tooltip": "Evite recortar y cambiar el tamaño de las imágenes de personajes importados. Cuando esté desactivado, recorte/cambie el tamaño a 512x768.",
"Never resize avatars": "Nunca redimensionar avatares",
"Show actual file names on the disk, in the characters list display only": "Mostrar nombres de archivo reales en el disco, solo en la visualización de la lista de personajes",
"Show avatar filenames": "Mostrar nombres de archivo de avatares",

View File

@ -581,7 +581,7 @@
"Advanced Character Search": "Recherche de personnage avancée",
"If checked and the character card contains a prompt override (System Prompt), use that instead": "Si cochée et si la carte de personnage contient un prompt de remplacement (prompt système), l'utiliser à la place",
"Prefer Character Card Prompt": "Préférer le prompt du personnage",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Évitez de recadrer et de redimensionner les images de personnages importés. Lorsqu'il est désactivé, recadrez/redimensionnez à 512 x 768.",
"never_resize_avatars_tooltip": "Évitez de recadrer et de redimensionner les images de personnages importés. Lorsqu'il est désactivé, recadrez/redimensionnez à 512 x 768.",
"Never resize avatars": "Ne jamais redimensionner les avatars",
"Show actual file names on the disk, in the characters list display only": "Afficher les noms de fichier réels sur le disque, dans l'affichage de la liste de personnages uniquement",
"Show avatar filenames": "Afficher les noms de fichier des avatars",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Kosstu kvenkortu fyrirspurn",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Ef merkt er og kortið inniheldur fangabrotsskil, notaðu það í staðinn",
"Prefer Character Card Jailbreak": "Kosstu kvenkortu fangabrot",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Forðastu að klippa og breyta stærð innfluttra stafamynda. Þegar slökkt er á því skaltu skera/breyta stærð í 512x768.",
"never_resize_avatars_tooltip": "Forðastu að klippa og breyta stærð innfluttra stafamynda. Þegar slökkt er á því skaltu skera/breyta stærð í 512x768.",
"Never resize avatars": "Aldrei breyta stærðinni á merkjum",
"Show actual file names on the disk, in the characters list display only": "Sýna raunveruleg nöfn skráa á diskinum, í lista yfir persónur sýna aðeins",
"Show avatar filenames": "Sýna nöfn merkja",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Preferisci Prompt della Scheda Personaggio",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Se selezionato e la scheda del personaggio contiene una sovrascrittura jailbreak (Istruzione Storico Post), usalo invece",
"Prefer Character Card Jailbreak": "Preferisci Jailbreak della Scheda Personaggio",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Evita di ritagliare e ridimensionare le immagini dei personaggi importati. Quando è disattivato, ritaglia/ridimensiona a 512x768.",
"never_resize_avatars_tooltip": "Evita di ritagliare e ridimensionare le immagini dei personaggi importati. Quando è disattivato, ritaglia/ridimensiona a 512x768.",
"Never resize avatars": "Non ridimensionare mai gli avatar",
"Show actual file names on the disk, in the characters list display only": "Mostra i nomi file effettivi sul disco, solo nella visualizzazione dell'elenco dei personaggi",
"Show avatar filenames": "Mostra nomi file avatar",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "キャラクターカードのプロンプトを優先",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "チェックされていてキャラクターカードにジェイルブレイクオーバーライド(投稿履歴指示)が含まれている場合、それを代わりに使用します",
"Prefer Character Card Jailbreak": "キャラクターカードのJailbreakを優先",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "インポートした文字画像の切り取りやサイズ変更を避けます。オフにすると、512x768 に切り取り/サイズ変更されます。",
"never_resize_avatars_tooltip": "インポートした文字画像の切り取りやサイズ変更を避けます。オフにすると、512x768 に切り取り/サイズ変更されます。",
"Never resize avatars": "アバターを常にリサイズしない",
"Show actual file names on the disk, in the characters list display only": "ディスク上の実際のファイル名を表示します。キャラクターリストの表示にのみ",
"Show avatar filenames": "アバターのファイル名を表示",

View File

@ -646,7 +646,7 @@
"Prefer Character Card Prompt": "캐릭터 카드 프롬프트 선호",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "선택되어 있고 캐릭터 카드에 (Post-History 지시)탈옥 재정의가 포함 된 경우, 그것을 대신 사용합니다.",
"Prefer Character Card Jailbreak": "캐릭터 카드 탈옥 선호",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "가져온 캐릭터 이미지를 자르거나 크기를 조정하지 마세요. 꺼져 있으면 512x768로 자르거나 크기를 조정합니다.",
"never_resize_avatars_tooltip": "가져온 캐릭터 이미지를 자르거나 크기를 조정하지 마세요. 꺼져 있으면 512x768로 자르거나 크기를 조정합니다.",
"Never resize avatars": "아바타 크기 변경하지 않음",
"Show actual file names on the disk, in the characters list display only": "실제 파일 이름을 디스크에 표시하며 캐릭터 목록 디스플레이에만",
"Show avatar filenames": "아바타 파일 이름 표시",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Voorkeur karakterkaart prompt",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Als aangevinkt en de karakterkaart bevat een jailbreak-override (Post History Instruction), gebruik die in plaats daarvan",
"Prefer Character Card Jailbreak": "Voorkeur karakterkaart jailbreak",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Vermijd het bijsnijden en vergroten/verkleinen van geïmporteerde karakterafbeeldingen. Indien uitgeschakeld, bijsnijden/formaat wijzigen naar 512 x 768.",
"never_resize_avatars_tooltip": "Vermijd het bijsnijden en vergroten/verkleinen van geïmporteerde karakterafbeeldingen. Indien uitgeschakeld, bijsnijden/formaat wijzigen naar 512 x 768.",
"Never resize avatars": "Avatars nooit verkleinen",
"Show actual file names on the disk, in the characters list display only": "Toon de werkelijke bestandsnamen op de schijf, alleen in de weergave van de lijst met personages",
"Show avatar filenames": "Toon avatar bestandsnamen",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Preferir Prompt do Cartão de Personagem",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Se marcado e o cartão de personagem contiver uma substituição de jailbreak (Instrução de Histórico de Postagens), use isso em vez disso",
"Prefer Character Card Jailbreak": "Preferir Jailbreak do Cartão de Personagem",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Evite cortar e redimensionar imagens de personagens importados. Quando desativado, corte/redimensione para 512x768.",
"never_resize_avatars_tooltip": "Evite cortar e redimensionar imagens de personagens importados. Quando desativado, corte/redimensione para 512x768.",
"Never resize avatars": "Nunca redimensionar avatares",
"Show actual file names on the disk, in the characters list display only": "Mostrar nomes de arquivo reais no disco, apenas na exibição da lista de personagens",
"Show avatar filenames": "Mostrar nomes de arquivo de avatar",

View File

@ -995,7 +995,7 @@
"Set your custom avatar.": "Установить аватарку",
"Remove your custom avatar.": "Сбросить аватарку",
"Make a Snapshot": "Сделать снимок",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Не менять размер картинок у импортируемых персонажей. При отключении все картинки будут приводиться к размеру 512х768",
"never_resize_avatars_tooltip": "Не менять размер картинок у импортируемых персонажей. При отключении все картинки будут приводиться к размеру 512х768",
"Char List Subheader": "Доп. заголовок в списке персонажей",
"# Messages to Load": "Сколько сообщений загружать",
"(0 = All)": "(0 = все)",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Перевага запиту персонажа",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Якщо відмічено і картка персонажа містить заміну джейлбрейку (Інструкцію), використовуйте її замість цього",
"Prefer Character Card Jailbreak": "Перевага джейлбрейку персонажа",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Уникайте обрізання та зміни розміру імпортованих зображень символів. Коли вимкнено, обрізати/змінити розмір до 512x768.",
"never_resize_avatars_tooltip": "Уникайте обрізання та зміни розміру імпортованих зображень символів. Коли вимкнено, обрізати/змінити розмір до 512x768.",
"Never resize avatars": "Ніколи не змінювати розмір аватарів",
"Show actual file names on the disk, in the characters list display only": "Показувати фактичні назви файлів на диску, тільки у відображенні списку персонажів",
"Show avatar filenames": "Показувати імена файлів аватарів",

View File

@ -633,7 +633,7 @@
"Prefer Character Card Prompt": "Ưu tiên Gợi ý từ Card",
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Nếu được kiểm tra và thẻ nhân vật chứa một lệnh phá vỡ giam giữ (Hướng dẫn Lịch sử Bài viết), hãy sử dụng thay vào đó",
"Prefer Character Card Jailbreak": "Ưu tiên Jailbreak từ Card",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "Tránh cắt xén và thay đổi kích thước hình ảnh ký tự đã nhập. Khi tắt, hãy cắt/thay đổi kích thước thành 512x768.",
"never_resize_avatars_tooltip": "Tránh cắt xén và thay đổi kích thước hình ảnh ký tự đã nhập. Khi tắt, hãy cắt/thay đổi kích thước thành 512x768.",
"Never resize avatars": "Không bao giờ thay đổi kích thước hình đại diện",
"Show actual file names on the disk, in the characters list display only": "Hiển thị tên tệp thực tế trên đĩa, chỉ trong danh sách nhân vật",
"Show avatar filenames": "Hiển thị tên tệp hình đại diện",

View File

@ -722,7 +722,7 @@
"Prefer Character Card Prompt": "角色卡提示词优先",
"If checked and the character card contains a Post-History Instructions override, use that instead": "开启后,如果角色卡包含后历史指令覆盖,则使用它。",
"Prefer Character Card Instructions": "首选角色卡说明",
"Avoid cropping and resizing imported character images. When off, crop/resize to 512x768": "避免裁剪和调整导入的角色图像的大小。关闭时,裁剪/调整大小为 512x768。",
"never_resize_avatars_tooltip": "避免裁剪和调整导入的角色图像的大小。关闭时,裁剪/调整大小为 512x768。",
"Never resize avatars": "永不调整头像大小",
"Show actual file names on the disk, in the characters list display only": "在角色列表显示中,显示磁盘上实际的文件名。",
"Show avatar filenames": "显示头像文件名",

File diff suppressed because it is too large Load Diff

View File

@ -5708,14 +5708,15 @@ function parseAndSaveLogprobs(data, continueFrom) {
/**
* Extracts the message from the response data.
* @param {object} data Response data
* @param {string} activeApi If it's set, ignores active API
* @returns {string} Extracted message
*/
function extractMessageFromData(data) {
export function extractMessageFromData(data, activeApi = null) {
if (typeof data === 'string') {
return data;
}
switch (main_api) {
switch (activeApi ?? main_api) {
case 'kobold':
return data.results[0].text;
case 'koboldhorde':
@ -5725,7 +5726,7 @@ function extractMessageFromData(data) {
case 'novel':
return data.output;
case 'openai':
return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
return data?.content?.find(p => p.type === 'text')?.text ?? data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
default:
return '';
}
@ -6085,17 +6086,52 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes,
return { type, getMessage };
}
export function syncCurrentSwipeInfoExtras() {
/**
* Syncs the current message and all its data into the swipe data at the given message ID (or the last message if no ID is given).
*
* If the swipe data is invalid in some way, this function will exit out without doing anything.
* @param {number?} [messageId=null] - The ID of the message to sync with the swipe data. If no ID is given, the last message is used.
* @returns {boolean} Whether the message was successfully synced
*/
export function syncMesToSwipe(messageId = null) {
if (!chat.length) {
return;
return false;
}
const currentMessage = chat[chat.length - 1];
if (currentMessage && Array.isArray(currentMessage.swipe_info) && typeof currentMessage.swipe_id === 'number') {
const swipeInfo = currentMessage.swipe_info[currentMessage.swipe_id];
if (swipeInfo && typeof swipeInfo === 'object') {
swipeInfo.extra = structuredClone(currentMessage.extra);
const targetMessageId = messageId ?? chat.length - 1;
if (chat.length > targetMessageId || targetMessageId < 0) {
console.warn(`[syncMesToSwipe] Invalid message ID: ${messageId}`);
return false;
}
const targetMessage = chat[targetMessageId];
// No swipe data there yet, exit out
if (typeof targetMessage.swipe_id !== 'number') {
return false;
}
// If swipes structure is invalid, exit out (for now?)
if (!Array.isArray(targetMessage.swipe_info) || !Array.isArray(targetMessage.swipes)) {
return false;
}
// If the swipe is not present yet, exit out (will likely be copied later)
if (!targetMessage.swipes[targetMessage.swipe_id] || !targetMessage.swipe_info[targetMessage.swipe_id]) {
return false;
}
const targetSwipeInfo = targetMessage.swipe_info[targetMessage.swipe_id];
if (typeof targetSwipeInfo !== 'object') {
return false;
}
targetMessage.swipes[targetMessage.swipe_id] = targetMessage.mes;
targetSwipeInfo.send_date = targetMessage.send_date;
targetSwipeInfo.gen_started = targetMessage.gen_started;
targetSwipeInfo.gen_finished = targetMessage.gen_finished;
targetSwipeInfo.extra = structuredClone(targetMessage.extra);
return true;
}
function saveImageToMessage(img, mes) {
@ -6400,6 +6436,7 @@ export function saveChatDebounced() {
if (chatSaveTimeout) {
console.debug('Clearing chat save timeout');
clearTimeout(chatSaveTimeout);
chatSaveTimeout = null;
}
chatSaveTimeout = setTimeout(async () => {
@ -6416,7 +6453,7 @@ export function saveChatDebounced() {
console.debug('Chat save timeout triggered');
await saveChatConditional();
console.debug('Chat saved');
}, 1000);
}, DEFAULT_SAVE_EDIT_TIMEOUT);
}
export async function saveChat(chatName, withMetadata, mesId) {
@ -8031,6 +8068,12 @@ export async function saveChatConditional() {
}
try {
if (chatSaveTimeout) {
console.debug('Debounced chat save canceled');
clearTimeout(chatSaveTimeout);
chatSaveTimeout = null;
}
isChatSaving = true;
if (selected_group) {
@ -8567,7 +8610,7 @@ function swipe_left() { // when we swipe left..but no generation.
}
// Make sure ad-hoc changes to extras are saved before swiping away
syncCurrentSwipeInfoExtras();
syncMesToSwipe();
const swipe_duration = 120;
const swipe_range = '700px';
@ -8705,7 +8748,7 @@ const swipe_right = () => {
}
// Make sure ad-hoc changes to extras are saved before swiping away
syncCurrentSwipeInfoExtras();
syncMesToSwipe();
const swipe_duration = 200;
const swipe_range = 700;
@ -8873,7 +8916,7 @@ const swipe_right = () => {
}
};
const CONNECT_API_MAP = {
export const CONNECT_API_MAP = {
// Default APIs not contined inside text gen / chat gen
'kobold': {
selected: 'kobold',

View File

@ -45,6 +45,10 @@
<option data-type="openai" value="gpt-4o">gpt-4o</option>
<option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>
<option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
<option data-type="openai" value="o1">o1</option>
<option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option>
<option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option>
<option data-type="anthropic" value="claude-3-7-sonnet-20250219">claude-3-7-sonnet-20250219</option>
<option data-type="anthropic" value="claude-3-5-sonnet-latest">claude-3-5-sonnet-latest</option>
<option data-type="anthropic" value="claude-3-5-sonnet-20241022">claude-3-5-sonnet-20241022</option>
<option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>

View File

@ -32,9 +32,6 @@ export { MODULE_NAME };
const MODULE_NAME = '1_memory';
let lastCharacterId = null;
let lastGroupId = null;
let lastChatId = null;
let lastMessageHash = null;
let lastMessageId = null;
let inApiCall = false;
@ -251,7 +248,7 @@ function onSummarySourceChange(event) {
}
function switchSourceControls(value) {
$('#memory_settings [data-summary-source]').each((_, element) => {
$('#summaryExtensionDrawerContents [data-summary-source], #memory_settings [data-summary-source]').each((_, element) => {
const source = element.dataset.summarySource.split(',').map(s => s.trim());
$(element).toggle(source.includes(value));
});
@ -349,15 +346,6 @@ function onMaxMessagesPerRequestInput() {
saveSettingsDebounced();
}
function saveLastValues() {
const context = getContext();
lastGroupId = context.groupId;
lastCharacterId = context.characterId;
lastChatId = context.chatId;
lastMessageId = context.chat?.length ?? null;
lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1]['mes']) ?? '');
}
function getLatestMemoryFromChat(chat) {
if (!Array.isArray(chat) || !chat.length) {
return '';
@ -390,6 +378,12 @@ function getIndexOfLatestChatSummary(chat) {
return -1;
}
function onChatChanged() {
const context = getContext();
const latestMemory = getLatestMemoryFromChat(context.chat);
setMemoryContext(latestMemory, false);
}
async function onChatEvent() {
// Module not enabled
if (extension_settings.memory.source === summary_sources.extras && !modules.includes('summarize')) {
@ -401,32 +395,19 @@ async function onChatEvent() {
return;
}
const context = getContext();
const chat = context.chat;
// no characters or group selected
if (!context.groupId && context.characterId === undefined) {
return;
}
// Streaming in-progress
if (streamingProcessor && !streamingProcessor.isFinished) {
return;
}
// Chat/character/group changed
if ((context.groupId && lastGroupId !== context.groupId) || (context.characterId !== lastCharacterId) || (context.chatId !== lastChatId)) {
const latestMemory = getLatestMemoryFromChat(chat);
setMemoryContext(latestMemory, false);
saveLastValues();
return;
}
// Currently summarizing or frozen state - skip
if (inApiCall || extension_settings.memory.memoryFrozen) {
return;
}
const context = getContext();
const chat = context.chat;
// No new messages - do nothing
if (chat.length === 0 || (lastMessageId === chat.length && getStringHash(chat[chat.length - 1].mes) === lastMessageHash)) {
return;
@ -449,7 +430,10 @@ async function onChatEvent() {
summarizeChat(context)
.catch(console.error)
.finally(saveLastValues);
.finally(() => {
lastMessageId = context.chat?.length ?? null;
lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1]['mes']) ?? '');
});
}
/**
@ -464,13 +448,7 @@ async function forceSummarizeChat(quiet) {
}
const context = getContext();
const skipWIAN = extension_settings.memory.SkipWIAN;
console.log(`Skipping WIAN? ${skipWIAN}`);
if (!context.chatId) {
toastr.warning('No chat selected');
return '';
}
const toast = quiet ? jQuery() : toastr.info('Summarizing chat...', 'Please wait', { timeOut: 0, extendedTimeOut: 0 });
const value = extension_settings.memory.source === summary_sources.main
@ -993,7 +971,7 @@ function doPopout(e) {
.removeClass('zoomed_avatar')
.addClass('draggable')
.empty();
const prevSummaryBoxContents = $('#memory_contents').val(); //copy summary box before emptying
const prevSummaryBoxContents = $('#memory_contents').val().toString(); //copy summary box before emptying
originalElement.empty();
originalElement.html('<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>');
newElement.append(controlBarHtml).append(originalHTMLClone);
@ -1013,7 +991,7 @@ function doPopout(e) {
const summaryPopoutHTML = $('#summaryExtensionDrawerContents');
$('#summaryExtensionPopout').fadeOut(animation_duration, () => {
originalElement.empty();
originalElement.html(summaryPopoutHTML);
originalElement.append(summaryPopoutHTML);
$('#summaryExtensionPopout').remove();
});
loadSettings();
@ -1027,31 +1005,30 @@ function doPopout(e) {
function setupListeners() {
//setup shared listeners for popout and regular ext menu
$('#memory_restore').off('click').on('click', onMemoryRestoreClick);
$('#memory_contents').off('click').on('input', onMemoryContentInput);
$('#memory_frozen').off('click').on('input', onMemoryFrozenInput);
$('#memory_skipWIAN').off('click').on('input', onMemorySkipWIANInput);
$('#summary_source').off('click').on('change', onSummarySourceChange);
$('#memory_prompt_words').off('click').on('input', onMemoryPromptWordsInput);
$('#memory_prompt_interval').off('click').on('input', onMemoryPromptIntervalInput);
$('#memory_prompt').off('click').on('input', onMemoryPromptInput);
$('#memory_contents').off('input').on('input', onMemoryContentInput);
$('#memory_frozen').off('input').on('input', onMemoryFrozenInput);
$('#memory_skipWIAN').off('input').on('input', onMemorySkipWIANInput);
$('#summary_source').off('change').on('change', onSummarySourceChange);
$('#memory_prompt_words').off('input').on('input', onMemoryPromptWordsInput);
$('#memory_prompt_interval').off('input').on('input', onMemoryPromptIntervalInput);
$('#memory_prompt').off('input').on('input', onMemoryPromptInput);
$('#memory_force_summarize').off('click').on('click', () => forceSummarizeChat(false));
$('#memory_template').off('click').on('input', onMemoryTemplateInput);
$('#memory_depth').off('click').on('input', onMemoryDepthInput);
$('#memory_role').off('click').on('input', onMemoryRoleInput);
$('input[name="memory_position"]').off('click').on('change', onMemoryPositionChange);
$('#memory_prompt_words_force').off('click').on('input', onMemoryPromptWordsForceInput);
$('#memory_prompt_builder_default').off('click').on('input', onMemoryPromptBuilderInput);
$('#memory_prompt_builder_raw_blocking').off('click').on('input', onMemoryPromptBuilderInput);
$('#memory_prompt_builder_raw_non_blocking').off('click').on('input', onMemoryPromptBuilderInput);
$('#memory_template').off('input').on('input', onMemoryTemplateInput);
$('#memory_depth').off('input').on('input', onMemoryDepthInput);
$('#memory_role').off('input').on('input', onMemoryRoleInput);
$('input[name="memory_position"]').off('change').on('change', onMemoryPositionChange);
$('#memory_prompt_words_force').off('input').on('input', onMemoryPromptWordsForceInput);
$('#memory_prompt_builder_default').off('input').on('input', onMemoryPromptBuilderInput);
$('#memory_prompt_builder_raw_blocking').off('input').on('input', onMemoryPromptBuilderInput);
$('#memory_prompt_builder_raw_non_blocking').off('input').on('input', onMemoryPromptBuilderInput);
$('#memory_prompt_restore').off('click').on('click', onMemoryPromptRestoreClick);
$('#memory_prompt_interval_auto').off('click').on('click', onPromptIntervalAutoClick);
$('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick);
$('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput);
$('#memory_max_messages_per_request').off('click').on('input', onMaxMessagesPerRequestInput);
$('#memory_override_response_length').off('input').on('input', onOverrideResponseLengthInput);
$('#memory_max_messages_per_request').off('input').on('input', onMaxMessagesPerRequestInput);
$('#memory_include_wi_scan').off('input').on('input', onMemoryIncludeWIScanInput);
$('#summarySettingsBlockToggle').off('click').on('click', function () {
console.log('saw settings button click');
$('#summarySettingsBlock').slideToggle(200, 'swing'); //toggleClass("hidden");
$('#summarySettingsBlock').slideToggle(200, 'swing');
});
}
@ -1068,11 +1045,11 @@ jQuery(async function () {
await addExtensionControls();
loadSettings();
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onChatEvent);
eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);
eventSource.on(event_types.CHAT_CHANGED, onChatEvent);
for (const event of [event_types.MESSAGE_DELETED, event_types.MESSAGE_UPDATED, event_types.MESSAGE_SWIPED]) {
eventSource.on(event, onChatEvent);
}
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'summarize',
callback: summarizeCallback,

View File

@ -10,16 +10,18 @@
<div class="inline-drawer-content">
<div id="summaryExtensionDrawerContents">
<label for="summary_source" data-i18n="ext_sum_with">Summarize with:</label>
<select id="summary_source">
<select id="summary_source" class="text_pole">
<option value="main" data-i18n="ext_sum_main_api">Main API</option>
<option value="extras">Extras API (deprecated)</option>
<option value="webllm" data-i18n="ext_sum_webllm">WebLLM Extension</option>
</select><br>
<div class="flex-container justifyspacebetween alignitemscenter">
<span class="flex1" data-i18n="ext_sum_current_summary">Current summary:</span>
<div id="memory_restore" class="menu_button flex1 margin0" data-i18n="[title]ext_sum_restore_tip" title="Restore a previous summary; use repeatedly to clear summarization state for this chat.">
<span data-i18n="ext_sum_restore_previous">Restore Previous</span>
<span data-i18n="ext_sum_current_summary">Current summary:</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="memory_contents" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
<span class="flex1">&nbsp;</span>
<div id="memory_restore" class="menu_button margin0" data-i18n="[title]ext_sum_restore_tip" title="Restore a previous summary; use repeatedly to clear summarization state for this chat.">
<small data-i18n="ext_sum_restore_previous">Restore Previous</small>
</div>
</div>

View File

@ -3,9 +3,13 @@
flex-direction: column;
}
#memory_settings textarea {
font-size: calc(var(--mainFontSize) * 0.9);
line-height: 1.2;
#memory_contents {
field-sizing: content;
max-height: 50dvh;
}
#memory_restore {
width: max-content;
}
label[for="memory_frozen"],
@ -35,3 +39,9 @@ label[for="memory_frozen"] input {
flex-direction: column;
row-gap: 5px;
}
#summaryExtensionPopout {
display: flex;
flex-direction: column;
padding-top: 25px;
}

View File

@ -393,8 +393,8 @@ export let proxies = [
];
export let selected_proxy = proxies[0];
let openai_setting_names;
let openai_settings;
export let openai_setting_names;
export let openai_settings;
/** @type {import('./PromptManager.js').PromptManager} */
export let promptManager = null;
@ -1497,8 +1497,14 @@ async function sendWindowAIRequest(messages, signal, stream) {
}
}
export function getChatCompletionModel() {
switch (oai_settings.chat_completion_source) {
/**
* Gets the API model for the selected chat completion source.
* @param {string} source If it's set, ignores active source
* @returns {string} API model
*/
export function getChatCompletionModel(source = null) {
const activeSource = source ?? oai_settings.chat_completion_source;
switch (activeSource) {
case chat_completion_sources.CLAUDE:
return oai_settings.claude_model;
case chat_completion_sources.OPENAI:
@ -1532,7 +1538,7 @@ export function getChatCompletionModel() {
case chat_completion_sources.DEEPSEEK:
return oai_settings.deepseek_model;
default:
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
throw new Error(`Unknown chat completion source: ${activeSource}`);
}
}
@ -2149,6 +2155,9 @@ async function sendOpenAIRequest(type, messages, signal) {
*/
function getStreamingReply(data, state) {
if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
if (oai_settings.show_thoughts) {
state.reasoning += data?.delta?.thinking || '';
}
return data?.delta?.text || '';
} else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
if (oai_settings.show_thoughts) {
@ -3990,6 +3999,8 @@ function onSettingsPresetChange() {
n: ['#n_openai', 'n', false],
};
const presetNameBefore = oai_settings.preset_settings_openai;
const presetName = $('#settings_preset_openai').find(':selected').text();
oai_settings.preset_settings_openai = presetName;
@ -4015,6 +4026,7 @@ function onSettingsPresetChange() {
settingsToUpdate: settingsToUpdate,
settings: oai_settings,
savePreset: saveOpenAIPreset,
presetNameBefore: presetNameBefore,
}).finally(r => {
$('.model_custom_select').empty();
@ -4981,6 +4993,7 @@ export function isImageInliningSupported() {
'gemini-1.5-pro-exp-0827',
'claude-3',
'claude-3-5',
'claude-3-7',
'gpt-4-turbo',
'gpt-4o',
'gpt-4o-mini',

View File

@ -21,6 +21,7 @@ import { groups, selected_group } from './group-chats.js';
import { instruct_presets } from './instruct-mode.js';
import { kai_settings } from './kai-settings.js';
import { convertNovelPreset } from './nai-settings.js';
import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { context_presets, getContextSettings, power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
@ -456,6 +457,10 @@ class PresetManager {
presets = textgenerationwebui_presets;
preset_names = textgenerationwebui_preset_names;
break;
case 'openai':
presets = openai_settings;
preset_names = openai_setting_names;
break;
case 'context':
presets = context_presets;
preset_names = context_presets.map(x => x.name);
@ -606,6 +611,30 @@ class PresetManager {
return settings;
}
getCompletionPresetByName(name) {
// Retrieve a completion preset by name. Return undefined if not found.
let { presets, preset_names } = this.getPresetList();
let preset;
// Some APIs use an array of names, others use an object of {name: index}
if (Array.isArray(preset_names)) { // array of names
if (preset_names.includes(name)) {
preset = presets[preset_names.indexOf(name)];
}
} else { // object of {names: index}
if (preset_names[name] !== undefined) {
preset = presets[preset_names[name]];
}
}
if (preset === undefined) {
console.error(`Preset ${name} not found`);
}
// if the preset isn't found, returns undefined
return preset;
}
// pass no arguments to delete current preset
async deletePreset(name) {
const { preset_names, presets } = this.getPresetList();

View File

@ -1,7 +1,7 @@
import {
moment,
} from '../lib.js';
import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveChatDebounced, saveSettingsDebounced, substituteParams, syncMesToSwipe, updateMessageBlock } from '../script.js';
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
import { getCurrentLocale, t, translate } from './i18n.js';
import { MacrosParser } from './macros.js';
@ -76,6 +76,8 @@ export function extractReasoningFromData(data) {
return data?.choices?.[0]?.message?.reasoning ?? '';
case chat_completion_sources.MAKERSUITE:
return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';
case chat_completion_sources.CLAUDE:
return data?.content?.find(part => part.type === 'thinking')?.thinking ?? '';
case chat_completion_sources.CUSTOM: {
return data?.choices?.[0]?.message?.reasoning_content
?? data?.choices?.[0]?.message?.reasoning
@ -755,6 +757,17 @@ function registerReasoningMacros() {
}
function setReasoningEventHandlers() {
/**
* Updates the reasoning block of a message from a value.
* @param {object} message Message object
* @param {string} value Reasoning value
*/
function updateReasoningFromValue(message, value) {
const reasoning = getRegexedString(value, regex_placement.REASONING, { isEdit: true });
message.extra.reasoning = reasoning;
message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual;
}
$(document).on('click', '.mes_reasoning_details', function (e) {
if (!e.target.closest('.mes_reasoning_actions') && !e.target.closest('.mes_reasoning_header')) {
e.preventDefault();
@ -835,9 +848,7 @@ function setReasoningEventHandlers() {
}
const textarea = messageBlock.find('.reasoning_edit_textarea');
const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });
message.extra.reasoning = reasoning;
message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual;
updateReasoningFromValue(message, String(textarea.val()));
await saveChatConditional();
updateMessageBlock(messageId, message);
textarea.remove();
@ -917,6 +928,20 @@ function setReasoningEventHandlers() {
await copyText(reasoning);
toastr.info(t`Copied!`, '', { timeOut: 2000 });
});
$(document).on('input', '.reasoning_edit_textarea', function () {
if (!power_user.auto_save_msg_edits) {
return;
}
const { message } = getMessageFromJquery(this);
if (!message?.extra) {
return;
}
updateReasoningFromValue(message, String($(this).val()));
saveChatDebounced();
});
}
/**
@ -973,7 +998,7 @@ function parseReasoningFromString(str, { strict = true } = {}) {
}
function registerReasoningAppEvents() {
eventSource.makeFirst(event_types.MESSAGE_RECEIVED, (/** @type {number} */ idx) => {
const eventHandler = (/** @type {number} */ idx) => {
if (!power_user.reasoning.auto_parse) {
return;
}
@ -1021,15 +1046,22 @@ function registerReasoningAppEvents() {
message.mes = parsedReasoning.content;
}
// Find if a message already exists in DOM and must be updated
if (contentUpdated) {
syncMesToSwipe();
saveChatDebounced();
// Find if a message already exists in DOM and must be updated
const messageRendered = document.querySelector(`.mes[mesid="${idx}"]`) !== null;
if (messageRendered) {
console.debug('[Reasoning] Updating message block', idx);
updateMessageBlock(idx, message);
}
}
});
};
for (const event of [event_types.MESSAGE_RECEIVED, event_types.MESSAGE_UPDATED]) {
eventSource.on(event, eventHandler);
}
}
export function initReasoning() {

View File

@ -42,7 +42,7 @@ import {
showMoreMessages,
stopGeneration,
substituteParams,
syncCurrentSwipeInfoExtras,
syncMesToSwipe,
system_avatar,
system_message_types,
this_chid,
@ -2921,7 +2921,7 @@ async function addSwipeCallback(args, value) {
if (isTrueBoolean(args.switch)) {
// Make sure ad-hoc changes to extras are saved before swiping away
syncCurrentSwipeInfoExtras();
syncMesToSwipe();
lastMessage.swipe_id = newSwipeId;
lastMessage.mes = lastMessage.swipes[newSwipeId];
lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {});

View File

@ -6,11 +6,13 @@ import {
characters,
chat,
chat_metadata,
CONNECT_API_MAP,
create_save,
deactivateSendButtons,
event_types,
eventSource,
extension_prompts,
extractMessageFromData,
Generate,
generateQuietPrompt,
getCharacters,
@ -55,9 +57,10 @@ import { groups, openGroupChat, selected_group } from './group-chats.js';
import { t, translate } from './i18n.js';
import { hideLoader, showLoader } from './loader.js';
import { MacrosParser } from './macros.js';
import { oai_settings } from './openai.js';
import { getChatCompletionModel, oai_settings } from './openai.js';
import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { power_user, registerDebugFunction } from './power-user.js';
import { getPresetManager } from './preset-manager.js';
import { humanizedDateTime, isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
import { ScraperManager } from './scrapers.js';
import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';
@ -65,7 +68,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { tag_map, tags } from './tags.js';
import { textgenerationwebui_settings } from './textgen-settings.js';
import { getTextGenServer, textgenerationwebui_settings } from './textgen-settings.js';
import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
import { ToolManager } from './tool-calling.js';
import { accountStorage } from './util/AccountStorage.js';
@ -193,6 +196,11 @@ export function getContext() {
saveWorldInfo,
updateWorldInfoList,
convertCharacterBook,
CONNECT_API_MAP,
getTextGenServer,
extractMessageFromData,
getPresetManager,
getChatCompletionModel,
};
}

View File

@ -318,8 +318,14 @@ export function validateTextGenUrl() {
control.val(formattedUrl);
}
export function getTextGenServer() {
switch (settings.type) {
/**
* Gets the API URL for the selected text generation type.
* @param {string} type If it's set, ignores active type
* @returns {string} API URL
*/
export function getTextGenServer(type = null) {
const selectedType = type ?? settings.type;
switch (selectedType) {
case FEATHERLESS:
return FEATHERLESS_SERVER;
case MANCER:
@ -333,7 +339,7 @@ export function getTextGenServer() {
case OPENROUTER:
return OPENROUTER_SERVER;
default:
return settings.server_urls[settings.type] ?? '';
return settings.server_urls[selectedType] ?? '';
}
}
@ -1215,8 +1221,14 @@ function isDynamicTemperatureSupported() {
return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type);
}
function getLogprobsNumber() {
if (settings.type === VLLM || settings.type === INFERMATICAI) {
/**
* Gets the number of logprobs to request based on the selected type.
* @param {string} type If it's set, ignores active type
* @returns {number} Number of logprobs to request
*/
export function getLogprobsNumber(type = null) {
const selectedType = type ?? settings.type;
if (selectedType === VLLM || selectedType === INFERMATICAI) {
return 5;
}
@ -1228,7 +1240,7 @@ function getLogprobsNumber() {
* @param {string} str Input string
* @returns {string} Output string
*/
function replaceMacrosInList(str) {
export function replaceMacrosInList(str) {
if (!str || typeof str !== 'string') {
return str;
}

View File

@ -28,6 +28,7 @@ import {
cachingAtDepthForOpenRouterClaude,
cachingAtDepthForClaude,
getPromptNames,
calculateBudgetTokens,
} from '../../prompt-converters.js';
import { readSecret, SECRET_KEYS } from '../secrets.js';
@ -125,9 +126,12 @@ async function sendClaudeRequest(request, response) {
controller.abort();
});
const additionalHeaders = {};
const betaHeaders = ['output-128k-2025-02-19'];
const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0;
const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt;
const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, getPromptNames(request));
const useThinking = request.body.model.startsWith('claude-3-7') && Boolean(request.body.include_reasoning);
let voidPrefill = false;
// Add custom stop sequences
const stopSequences = [];
if (Array.isArray(request.body.stop)) {
@ -155,16 +159,16 @@ async function sendClaudeRequest(request, response) {
delete requestBody.system;
}
if (useTools) {
additionalHeaders['anthropic-beta'] = 'tools-2024-05-16';
betaHeaders.push('tools-2024-05-16');
requestBody.tool_choice = { type: request.body.tool_choice };
requestBody.tools = request.body.tools
.filter(tool => tool.type === 'function')
.map(tool => tool.function)
.map(fn => ({ name: fn.name, description: fn.description, input_schema: fn.parameters }));
// Claude doesn't do prefills on function calls, and doesn't allow empty messages
if (requestBody.tools.length && convertedPrompt.messages.length && convertedPrompt.messages[convertedPrompt.messages.length - 1].role === 'assistant') {
convertedPrompt.messages.push({ role: 'user', content: [{ type: 'text', text: '\u200b' }] });
if (requestBody.tools.length) {
// No prefill when using tools
voidPrefill = true;
}
if (enableSystemPromptCache && requestBody.tools.length) {
requestBody.tools[requestBody.tools.length - 1]['cache_control'] = { type: 'ephemeral' };
@ -176,7 +180,38 @@ async function sendClaudeRequest(request, response) {
}
if (enableSystemPromptCache || cachingAtDepth !== -1) {
additionalHeaders['anthropic-beta'] = 'prompt-caching-2024-07-31';
betaHeaders.push('prompt-caching-2024-07-31');
}
if (useThinking) {
// No prefill when thinking
voidPrefill = true;
const reasoningEffort = request.body.reasoning_effort;
const budgetTokens = calculateBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream);
const minThinkTokens = 1024;
if (requestBody.max_tokens <= minThinkTokens) {
const newValue = requestBody.max_tokens + minThinkTokens;
console.warn(color.yellow(`Claude thinking requires a minimum of ${minThinkTokens} response tokens.`));
console.info(color.blue(`Increasing response length to ${newValue}.`));
requestBody.max_tokens = newValue;
}
requestBody.thinking = {
type: 'enabled',
budget_tokens: budgetTokens,
};
// NO I CAN'T SILENTLY IGNORE THE TEMPERATURE.
delete requestBody.temperature;
delete requestBody.top_p;
delete requestBody.top_k;
}
if (voidPrefill && convertedPrompt.messages.length && convertedPrompt.messages[convertedPrompt.messages.length - 1].role === 'assistant') {
convertedPrompt.messages.push({ role: 'user', content: [{ type: 'text', text: '\u200b' }] });
}
if (betaHeaders.length) {
additionalHeaders['anthropic-beta'] = betaHeaders.join(',');
}
console.debug('Claude request:', requestBody);
@ -979,6 +1014,7 @@ router.post('/generate', jsonParser, function (request, response) {
headers = { ...OPENROUTER_HEADERS };
bodyParams = {
'transforms': getOpenRouterTransforms(request),
'include_reasoning': Boolean(request.body.include_reasoning),
};
if (request.body.min_p !== undefined) {
@ -1004,10 +1040,6 @@ router.post('/generate', jsonParser, function (request, response) {
bodyParams['route'] = 'fallback';
}
if (request.body.include_reasoning) {
bodyParams['include_reasoning'] = true;
}
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) {
cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth);

View File

@ -862,3 +862,34 @@ export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth) {
}
}
}
/**
* Calculate the budget tokens for a given reasoning effort.
* @param {number} maxTokens Maximum tokens
* @param {string} reasoningEffort Reasoning effort
* @param {boolean} stream If streaming is enabled
* @returns {number} Budget tokens
*/
export function calculateBudgetTokens(maxTokens, reasoningEffort, stream) {
let budgetTokens = 0;
switch (reasoningEffort) {
case 'low':
budgetTokens = Math.floor(maxTokens * 0.1);
break;
case 'medium':
budgetTokens = Math.floor(maxTokens * 0.25);
break;
case 'high':
budgetTokens = Math.floor(maxTokens * 0.5);
break;
}
budgetTokens = Math.max(budgetTokens, 1024);
if (!stream) {
budgetTokens = Math.min(budgetTokens, 21333);
}
return budgetTokens;
}