diff --git a/index.html b/index.html index 972d7f37..7859e026 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,7 @@ + @@ -21,6 +22,17 @@ + +
+
+ + + + + Loading... +
+ Loading RisuAI... +
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 323a3cf0..26872853 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -66,6 +66,7 @@ async fn native_request(url: String, body: String, header: String, method: Strin Ok(resp) => { let headers = resp.headers(); let header_json = header_map_to_json(headers); + let status = resp.status().as_u16().to_string(); let bytes = match resp.bytes().await { Ok(b) => b, Err(e) => return format!(r#"{{"success":false,"body":"{}"}}"#, e.to_string()), @@ -73,11 +74,12 @@ async fn native_request(url: String, body: String, header: String, method: Strin let encoded = general_purpose::STANDARD.encode(&bytes); format!( - r#"{{"success":true,"body":"{}","headers":{}}}"#, - encoded, header_json + // r#"{{"success":true,"body":"{}","headers":{}}}"#, + r#"{{"success":true,"body":"{}","headers":{},"status":{}}}"#, + encoded, header_json, status ) } - Err(e) => format!(r#"{{"success":false,"body":"{}"}}"#, e.to_string()), + Err(e) => format!(r#"{{"success":false,"body":"{}","status":400}}"#, e.to_string()), } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5b5503bb..8add7b31 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -29,7 +29,7 @@ }, "productName": "RisuAI", "mainBinaryName": "RisuAI", - "version": "150.2.0", + "version": "150.4.1", "identifier": "co.aiclient.risu", "plugins": { "updater": { diff --git a/src/lang/en.ts b/src/lang/en.ts index d8faf0ec..09393695 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -1059,4 +1059,6 @@ export const languageEnglish = { returnCSSError: "Return CSS Error", alwaysActiveInChat: "Always Active (Current Chat)", childLoreDesc: "This is a copy of Character lore that remains 'Always Active' until removed or manually deactivated in the original.", + thinkingTokens: "Thinking Tokens", + antiServerOverload: "Anti-Server Overload", } diff --git a/src/lib/Playground/PlaygroundTranslation.svelte b/src/lib/Playground/PlaygroundTranslation.svelte index 61d44731..a57e3f7d 100644 --- a/src/lib/Playground/PlaygroundTranslation.svelte +++ b/src/lib/Playground/PlaygroundTranslation.svelte @@ -7,6 +7,8 @@ import SelectInput from "../UI/GUI/SelectInput.svelte"; import { getLanguageCodes } from "src/ts/globalApi.svelte"; import OptionInput from "../UI/GUI/OptionInput.svelte"; + import CheckInput from "../UI/GUI/CheckInput.svelte"; + import { tokenize } from "src/ts/tokenizer"; const userPreferedLang = navigator.language.split('-')[0] @@ -16,6 +18,9 @@ let output = $state('') let outputLang = $state(userPreferedLang) let loading = $state(false) + let bulk = $state(false) + let keepContext = $state(false) + let bulkProgressText = $state('') @@ -36,7 +41,99 @@ + + Bulk + +{#if bulk} + + Keep Context + +{/if} + -
- {#key sorted} - {#each chara.chats as chat, i} - + +
+ {#if chara.chats.filter(chat => chat.folderId == chara.chatFolders[i].id).length == 0} + Empty +
+ {:else} + {#each chara.chats.filter(chat => chat.folderId == chara.chatFolders[i].id) as chat} +
-
{ - if(e.key === 'Enter'){ - e.currentTarget.click() - } - }} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={() => { - editMode = !editMode - }}> - -
-
{ - if(e.key === 'Enter'){ - e.currentTarget.click() - } - }} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={async (e) => { - e.stopPropagation() - exportChat(i) - }}> - -
-
{ - if(e.key === 'Enter'){ - e.currentTarget.click() - } - }} class="text-textcolor2 hover:text-green-500 cursor-pointer" onclick={async (e) => { - e.stopPropagation() - if(chara.chats.length === 1){ - alertError(language.errors.onlyOneChat) - return - } - const d = await alertConfirm(`${language.removeConfirm}${chat.name}`) - if(d){ - chara.chatPage = 0 - $ReloadGUIPointer += 1 - let chats = chara.chats - chats.splice(i, 1) - chara.chats = chats - } - }}> - + }} class="risu-chats flex items-center text-textcolor border-solid border-0 border-darkborderc p-2 cursor-pointer rounded-md"class:bg-selected={chara.chats.indexOf(chat) === chara.chatPage}> + {#if editMode} + + {:else} + {chat.name} + {/if} +
+
{ + if(e.key === 'Enter'){ + e.currentTarget.click() + } + }} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={async () => { + const option = await alertChatOptions() + switch(option){ + case 0:{ + const newChat = safeStructuredClone($state.snapshot(chara.chats[chara.chats.indexOf(chat)])) + newChat.name = `Copy of ${newChat.name}` + chara.chats.unshift(newChat) + chara.chatPage = 0 + chara.chats = chara.chats + break + } + case 1:{ + if(chat.bindedPersona){ + const confirm = await alertConfirm(language.doYouWantToUnbindCurrentPersona) + if(confirm){ + chat.bindedPersona = '' + alertNormal(language.personaUnbindedSuccess) + } + } + else{ + const confirm = await alertConfirm(language.doYouWantToBindCurrentPersona) + if(confirm){ + if(!DBState.db.personas[DBState.db.selectedPersona].id){ + DBState.db.personas[DBState.db.selectedPersona].id = v4() + } + chat.bindedPersona = DBState.db.personas[DBState.db.selectedPersona].id + console.log(DBState.db.personas[DBState.db.selectedPersona]) + alertNormal(language.personaBindedSuccess) + } + } + break + } + case 2:{ + chara.chatPage = chara.chats.indexOf(chat) + createMultiuserRoom() + } + } + }}> + +
+
{ + if(e.key === 'Enter'){ + e.currentTarget.click() + } + }} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={() => { + editMode = !editMode + }}> + +
+
{ + if(e.key === 'Enter'){ + e.currentTarget.click() + } + }} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={async (e) => { + e.stopPropagation() + exportChat(chara.chats.indexOf(chat)) + }}> + +
+
{ + if(e.key === 'Enter'){ + e.currentTarget.click() + } + }} class="text-textcolor2 hover:text-green-500 cursor-pointer" onclick={async (e) => { + e.stopPropagation() + if(chara.chats.length === 1){ + alertError(language.errors.onlyOneChat) + return + } + const d = await alertConfirm(`${language.removeConfirm}${chat.name}`) + if(d){ + chara.chatPage = 0 + $ReloadGUIPointer += 1 + let chats = chara.chats + chats.splice(chara.chats.indexOf(chat), 1) + chara.chats = chats + } + }}> + +
+
+ + {/each} + {/if}
- - {/each} - {/key} + {/each} + + +
+ {#each chara.chats as chat, i} + {#if chat.folderId == null} + + {/if} + {/each} +
- + {/key} +
+
- {#if DBState.db.characters[$selectedCharID]?.chaId !== '§playground'} - - + {#if DBState.db.characters[$selectedCharID]?.chaId !== '§playground'} {#if parseKeyValue(DBState.db.customPromptTemplateToggle + getModuleToggles()).length > 4}
@@ -268,10 +540,9 @@ {/if} {/if}
- {#if chara.type === 'group'} -
- -
+
+ +
{/if}
\ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 7d0b6828..c46c8f25 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,3 @@ -import "./styles.css"; import "./ts/polyfill"; import "core-js/actual" import "./ts/storage/database.svelte" @@ -14,5 +13,6 @@ let app = mount(App, { }); loadData() initHotkey() +document.getElementById('preloading').remove() export default app; \ No newline at end of file diff --git a/src/ts/characterCards.ts b/src/ts/characterCards.ts index c4737140..7e74558e 100644 --- a/src/ts/characterCards.ts +++ b/src/ts/characterCards.ts @@ -1371,6 +1371,7 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha } card.data.assets[i].uri = 'embeded://' + path const imageType = checkImageType(rData) + const metaPath = `x_meta/${name}.json` if(imageType === 'PNG' && writer instanceof CharXWriter){ const metadatas:Record = {} const gen = PngChunk.readGenerator(rData) @@ -1380,10 +1381,20 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha } metadatas[chunk.key] = chunk.value } + console.log(metadatas) if(Object.keys(metadatas).length > 0){ - const metaPath = `x_meta/${name}.json` await writer.write(metaPath, Buffer.from(JSON.stringify(metadatas, null, 4)), 6) } + else{ + await writer.write(metaPath, Buffer.from(JSON.stringify({ + 'type': imageType + }), 'utf-8'), 6) + } + } + else{ + await writer.write(metaPath, Buffer.from(JSON.stringify({ + 'type': imageType + }), 'utf-8'), 6) } await writer.write(path, Buffer.from(await convertImage(rData))) } diff --git a/src/ts/characters.ts b/src/ts/characters.ts index 8f68dc2d..30425706 100644 --- a/src/ts/characters.ts +++ b/src/ts/characters.ts @@ -32,7 +32,9 @@ export function createNewGroup(){ note: '', name: 'Chat 1', localLore: [] - }], chatPage: 0, + }], + chatFolders: [], + chatPage: 0, viewScreen: 'none', globalLore: [], characters: [], @@ -402,6 +404,11 @@ export async function importChat(){ return } + if(db.characters[selectedID].chatFolders + .filter(folder => folder.id === newChat.folderId).length === 0) { + newChat.folderId = null + } + db.characters[selectedID].chats.unshift(newChat) setDatabase(db) alertNormal(language.successImport) @@ -585,6 +592,7 @@ export function createBlankChar():character{ name: 'Chat 1', localLore: [] }], + chatFolders: [], chatPage: 0, emotionImages: [], bias: [], diff --git a/src/ts/globalApi.svelte.ts b/src/ts/globalApi.svelte.ts index a0a3104c..168dafe1 100644 --- a/src/ts/globalApi.svelte.ts +++ b/src/ts/globalApi.svelte.ts @@ -756,6 +756,7 @@ interface GlobalFetchResult { ok: boolean; data: any; headers: { [key: string]: string }; + status: number; } /** @@ -806,13 +807,13 @@ export async function globalFetch(url: string, arg: GlobalFetchArgs = {}): Promi const method = arg.method ?? "POST"; db.requestmet = "normal"; - if (arg.abortSignal?.aborted) { return { ok: false, data: 'aborted', headers: {} }; } + if (arg.abortSignal?.aborted) { return { ok: false, data: 'aborted', headers: {}, status: 400 }; } const urlHost = new URL(url).hostname const forcePlainFetch = ((knownHostes.includes(urlHost) && !isTauri) || db.usePlainFetch || arg.plainFetchForce) && !arg.plainFetchDeforce if (knownHostes.includes(urlHost) && !isTauri && !isNodeServer) { - return { ok: false, headers: {}, data: 'You are trying local request on web version. This is not allowed due to browser security policy. Use the desktop version instead, or use a tunneling service like ngrok and set the CORS to allow all.' }; + return { ok: false, headers: {}, status:400, data: 'You are trying local request on web version. This is not allowed due to browser security policy. Use the desktop version instead, or use a tunneling service like ngrok and set the CORS to allow all.' }; } if (forcePlainFetch) { @@ -832,7 +833,7 @@ export async function globalFetch(url: string, arg: GlobalFetchArgs = {}): Promi } catch (error) { console.error(error); - return { ok: false, data: `${error}`, headers: {} }; + return { ok: false, data: `${error}`, headers: {}, status: 400 }; } } @@ -887,9 +888,9 @@ async function fetchWithPlainFetch(url: string, arg: GlobalFetchArgs): Promise= 200 && response.status < 300; addFetchLogInGlobalFetch(data, ok, url, arg); - return { ok, data, headers: Object.fromEntries(response.headers) }; + return { ok, data, headers: Object.fromEntries(response.headers), status: response.status }; } catch (error) { - return { ok: false, data: `${error}`, headers: {} }; + return { ok: false, data: `${error}`, headers: {}, status: 400 }; } } @@ -907,9 +908,9 @@ async function fetchWithUSFetch(url: string, arg: GlobalFetchArgs): Promise= 200 && response.status < 300; addFetchLogInGlobalFetch(data, ok, url, arg); - return { ok, data, headers: Object.fromEntries(response.headers) }; + return { ok, data, headers: Object.fromEntries(response.headers), status: response.status }; } catch (error) { - return { ok: false, data: `${error}`, headers: {} }; + return { ok: false, data: `${error}`, headers: {}, status: 400 }; } } @@ -927,7 +928,7 @@ async function fetchWithTauri(url: string, arg: GlobalFetchArgs): Promise= 200 && response.status < 300; addFetchLogInGlobalFetch(data, ok, url, arg); - return { ok, data, headers: Object.fromEntries(response.headers) }; + return { ok, data, headers: Object.fromEntries(response.headers), status: response.status }; } catch (error) { } @@ -946,6 +947,7 @@ async function fetchWithCapacitor(url: string, arg: GlobalFetchArgs): Promise { @@ -77,12 +77,17 @@ export class HypaProcesser{ } const {customEmbeddingUrl} = this const replaceUrl = customEmbeddingUrl.endsWith('/embeddings')?customEmbeddingUrl:appendLastPath(customEmbeddingUrl,'embeddings') - - gf = await globalFetch(replaceUrl.toString(), { - body:{ - "input": input - }, - }) + + const db = getDatabase() + const fetchArgs = { + ...(db.hypaCustomSettings.key ? {headers: {"Authorization": "Bearer " + db.hypaCustomSettings.key}} : {}), + body: { + "input": input, + ...(db.hypaCustomSettings.model ? {"model": db.hypaCustomSettings.model} : {}) + } + }; + + gf = await globalFetch(replaceUrl.toString(), fetchArgs) } if(this.model === 'ada' || this.model === 'openai3small' || this.model === 'openai3large'){ const db = getDatabase() diff --git a/src/ts/process/modules.ts b/src/ts/process/modules.ts index 8b870f6e..a0b7873e 100644 --- a/src/ts/process/modules.ts +++ b/src/ts/process/modules.ts @@ -268,9 +268,7 @@ function getModuleByIds(ids:string[]){ modules.push(module) } } - if(db.moduleIntergration){ - modules = deduplicateModuleById(modules) - } + modules = deduplicateModuleById(modules) return modules } diff --git a/src/ts/process/request.ts b/src/ts/process/request.ts index 33d02a3e..8cc4e985 100644 --- a/src/ts/process/request.ts +++ b/src/ts/process/request.ts @@ -15,7 +15,6 @@ import { supportsInlayImage } from "./files/inlays"; import { Capacitor } from "@capacitor/core"; import { getFreeOpenRouterModel } from "../model/openrouter"; import { runTransformers } from "./transformers"; -import {createParser} from 'eventsource-parser' import {Ollama} from 'ollama/dist/browser.mjs' import { applyChatTemplate } from "./templates/chatTemplate"; import { OobaParams } from "./prompt"; @@ -59,7 +58,8 @@ type requestDataResponse = { noRetry?: boolean, special?: { emotion?: string - } + }, + failByServerError?: boolean }|{ type: "streaming", result: ReadableStream, @@ -92,12 +92,28 @@ interface OaiFunctions { } -export type Parameter = 'temperature'|'top_k'|'repetition_penalty'|'min_p'|'top_a'|'top_p'|'frequency_penalty'|'presence_penalty'|'reasoning_effort' +export type Parameter = 'temperature'|'top_k'|'repetition_penalty'|'min_p'|'top_a'|'top_p'|'frequency_penalty'|'presence_penalty'|'reasoning_effort'|'thinking_tokens' export type ModelModeExtended = 'model'|'submodel'|'memory'|'emotion'|'otherAx'|'translate' type ParameterMap = { [key in Parameter]?: string; }; +function setObjectValue(obj: T, key: string, value: any): T { + + const splitKey = key.split('.'); + if(splitKey.length > 1){ + const firstKey = splitKey.shift() + if(!obj[firstKey]){ + obj[firstKey] = {}; + } + obj[firstKey] = setObjectValue(obj[firstKey], splitKey.join('.'), value); + return obj; + } + + obj[key] = value; + return obj; +} + function applyParameters(data: { [key: string]: any }, parameters: Parameter[], rename: ParameterMap, ModelMode:ModelModeExtended, arg:{ ignoreTopKIfZero?:boolean } = {}): { [key: string]: any } { @@ -157,6 +173,10 @@ function applyParameters(data: { [key: string]: any }, parameters: Parameter[], value = db.seperateParameters[ModelMode].top_p break } + case 'thinking_tokens':{ + value = db.seperateParameters[ModelMode].thinking_tokens + break + } case 'frequency_penalty':{ value = db.seperateParameters[ModelMode].frequency_penalty === -1000 ? -1000 : (db.seperateParameters[ModelMode].frequency_penalty / 100) break @@ -175,7 +195,7 @@ function applyParameters(data: { [key: string]: any }, parameters: Parameter[], continue } - data[rename[parameter] ?? parameter] = value + data = setObjectValue(data, rename[parameter] ?? parameter, value) } return data } @@ -223,13 +243,17 @@ function applyParameters(data: { [key: string]: any }, parameters: Parameter[], value = db.PresensePenalty === -1000 ? -1000 : (db.PresensePenalty / 100) break } + case 'thinking_tokens':{ + value = db.thinkingTokens + break + } } if(value === -1000){ continue } - data[rename[parameter] ?? parameter] = value + data = setObjectValue(data, rename[parameter] ?? parameter, value) } return data } @@ -247,7 +271,7 @@ export async function requestChatData(arg:requestDataArgument, model:ModelModeEx try{ const currentChar = getCurrentCharacter() - if(currentChar.type !== 'group'){ + if(currentChar?.type !== 'group'){ const perf = performance.now() const d = await runTrigger(currentChar, 'request', { chat: getCurrentChat(), @@ -305,6 +329,13 @@ export async function requestChatData(arg:requestDataArgument, model:ModelModeEx if(da.type !== 'fail' || da.noRetry){ return da } + + if(da.failByServerError){ + await sleep(1000) + if(db.antiServerOverloads){ + trys -= 0.5 // reduce trys by 0.5, so that it will retry twice as much + } + } trys += 1 if(trys > db.requestRetrys){ @@ -1710,17 +1741,6 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise }) } } - else if(chat.role === 'assistant' && arg.modelInfo.flags.includes(LLMFlags.geminiThinking)){ - reformatedChat.push({ - role: 'MODEL', - parts: [chat.thoughts?.length > 0 ? { - text: chat.thoughts.join('\n\n') - } : null, { - text: chat.content - }] - }) - } - else if(chat.role === 'assistant' || chat.role === 'user'){ reformatedChat.push({ role: chat.role === 'user' ? 'USER' : 'MODEL', @@ -1896,6 +1916,101 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise url = `https://generativelanguage.googleapis.com/v1beta/models/${arg.modelInfo.internalID}:generateContent?key=${db.google.accessToken}` } + const fallBackGemini = async (originalError:string):Promise => { + if(!db.antiServerOverloads){ + return { + type: 'fail', + result: originalError, + failByServerError: true + } + } + + if(arg?.abortSignal?.aborted){ + return { + type: 'fail', + result: originalError, + failByServerError: true + } + } + if(arg.modelInfo.format === LLMFormat.VertexAIGemini){ + return { + type: 'fail', + result: originalError, + failByServerError: true + } + } + try { + const OAIMessages:OpenAIChat[] = body.contents.map((v) => { + return { + role: v.role === 'USER' ? 'user' : 'assistant', + content: v.parts.map((v) => { + return v.text ?? '' + }).join('\n') + } + }) + if(body?.systemInstruction?.parts?.[0]?.text){ + OAIMessages.unshift({ + role: 'system', + content: body.systemInstruction.parts[0].text + }) + } + await sleep(2000) + const res = await fetch('https://generativelanguage.googleapis.com/v1beta/openai/chat/completions', { + body: JSON.stringify({ + model: arg.modelInfo.internalID, + messages: OAIMessages, + max_tokens: maxTokens, + temperature: body.generation_config?.temperature, + top_p: body.generation_config?.topP, + presence_penalty: body.generation_config?.presencePenalty, + frequency_penalty: body.generation_config?.frequencyPenalty, + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${db.google.accessToken}` + }, + signal: arg.abortSignal + }) + + if(!res.ok){ + return { + type: 'fail', + result: originalError, + failByServerError: true + } + } + + if(arg?.abortSignal?.aborted){ + return { + type: 'fail', + result: originalError + } + } + + const d = await res.json() + + if(d?.choices?.[0]?.message?.content){ + return { + type: 'success', + result: d.choices[0].message.content + } + } + else{ + return { + type: 'fail', + result: originalError, + failByServerError: true + } + } + } catch (error) { + return { + type: 'fail', + result: originalError, + failByServerError: true + } + } + } if(arg.modelInfo.format === LLMFormat.GoogleCloud && arg.useStreaming){ headers['Content-Type'] = 'application/json' @@ -1907,9 +2022,13 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise }) if(f.status !== 200){ + const text = await textifyReadableStream(f.body) + if(text.includes('RESOURCE_EXHAUSTED')){ + return fallBackGemini(text) + } return { type: 'fail', - result: await textifyReadableStream(f.body) + result: text } } @@ -1974,8 +2093,13 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise chatId: arg.chatId, abortSignal: arg.abortSignal, }) + if(!res.ok){ + const text = JSON.stringify(res.data) + if(text.includes('RESOURCE_EXHAUSTED')){ + return fallBackGemini(text) + } return { type: 'fail', result: `${JSON.stringify(res.data)}` @@ -2541,14 +2665,23 @@ async function requestClaude(arg:RequestDataArgumentExtended):Promise 0){ + body.thinking.type = 'enabled' + } if(systemPrompt === ''){ delete body.system @@ -2645,8 +2778,18 @@ async function requestClaude(arg:RequestDataArgumentExtended):Promise 8192){ + betas.push('output-128k-2025-02-19') + } + + if(betas.length > 0){ + headers['anthropic-beta'] = betas.join(',') } if(db.usePlainFetch){ @@ -2668,113 +2811,116 @@ async function requestClaude(arg:RequestDataArgumentExtended):Promise({ async start(controller){ let text = '' let reader = res.body.getReader() + let parserData = '' const decoder = new TextDecoder() - const parser = createParser(async (e) => { + const parseEvent = (async (e:string) => { try { - if(e.type === 'event'){ - switch(e.event){ - case 'content_block_delta': { - if(e.data){ - text += JSON.parse(e.data).delta?.text - controller.enqueue({ - "0": text - }) - } - break + const parsedData = JSON.parse(e) + + if(parsedData?.type === 'content_block_delta'){ + if(parsedData?.delta?.type === 'text' || parsedData.delta?.type === 'text_delta'){ + if(thinking){ + text += "\n\n" + thinking = false } - case 'error': { - if(e.data){ - const errormsg:string = JSON.parse(e.data).error?.message - if(errormsg && errormsg.toLocaleLowerCase().includes('overload') && db.antiClaudeOverload){ - console.log('Overload detected, retrying...') - reader.cancel() - rerequesting = true - await sleep(2000) - body.max_tokens -= await tokenize(text) - if(body.max_tokens < 0){ - body.max_tokens = 0 - } - if(body.messages.at(-1)?.role !== 'assistant'){ - body.messages.push({ - role: 'assistant', - content: [{ - type: 'text', - text: '' - }] - }) - } - let block = body.messages[body.messages.length-1].content - if(typeof block === 'string'){ - body.messages[body.messages.length-1].content += text - } - else if(block[0].type === 'text'){ - block[0].text += text - } - const res = await fetchNative(replacerURL, { - body: JSON.stringify(body), - headers: { - "Content-Type": "application/json", - "x-api-key": apiKey, - "anthropic-version": "2023-06-01", - "accept": "application/json", - }, - method: "POST", - chatId: arg.chatId - }) - if(res.status !== 200){ - breakError = 'Error: ' + await textifyReadableStream(res.body) - break - } - reader = res.body.getReader() - rerequesting = false - break - } - text += "Error:" + JSON.parse(e.data).error?.message - if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){ - controller.enqueue({ - "0": extractJSON(text, db.jsonSchema) - }) - } - else{ - controller.enqueue({ - "0": text - }) - } - } - break + text += parsedData.delta?.text ?? '' + } + + if(parsedData?.delta?.type === 'thinking' || parsedData.delta?.type === 'thinking_delta'){ + if(!thinking){ + text += "\n" + thinking = true } + text += parsedData.delta?.thinking ?? '' + } + + if(parsedData?.delta?.type === 'redacted_thinking'){ + if(!thinking){ + text += "\n" + thinking = true + } + text += '\n{{redacted_thinking}}\n' } } - } catch (error) {} + + if(parsedData?.type === 'error'){ + const errormsg:string = parsedData?.error?.message + if(errormsg && errormsg.toLocaleLowerCase().includes('overload') && db.antiServerOverloads){ + // console.log('Overload detected, retrying...') + controller.enqueue({ + "0": "Overload detected, retrying..." + }) + + return 'overload' + } + text += "Error:" + parsedData?.error?.message + + } + + } + catch (error) { + } + + + }) + let breakWhile = false + let i = 0; + let prevText = '' while(true){ - if(rerequesting){ - if(breakError){ - controller.enqueue({ - "0": breakError - }) + try { + if(arg?.abortSignal?.aborted || breakWhile){ break } - await sleep(1000) - continue - } - try { const {done, value} = await reader.read() if(done){ - if(rerequesting){ - continue - } break } - parser.feed(decoder.decode(value)) + parserData += (decoder.decode(value)) + let parts = parserData.split('\n') + for(;i { const formated = arg.formated const db = getDatabase() diff --git a/src/ts/storage/database.svelte.ts b/src/ts/storage/database.svelte.ts index 3c5801f3..a6405b0b 100644 --- a/src/ts/storage/database.svelte.ts +++ b/src/ts/storage/database.svelte.ts @@ -12,7 +12,7 @@ import { defaultColorScheme, type ColorScheme } from '../gui/colorscheme'; import type { PromptItem, PromptSettings } from '../process/prompt'; import type { OobaChatCompletionRequestParams } from '../model/ooba'; -export let appVer = "150.2.0" +export let appVer = "150.4.1" export let webAppSubVer = '' @@ -484,6 +484,16 @@ export function setDatabase(data:Database){ doNotSummarizeUserMessage: data.hypaV3Settings?.doNotSummarizeUserMessage ?? false } data.returnCSSError ??= true + data.useExperimentalGoogleTranslator ??= false + if(data.antiClaudeOverload){ //migration + data.antiClaudeOverload = false + data.antiServerOverloads = true + } + data.hypaCustomSettings = { + url: data.hypaCustomSettings?.url ?? "", + key: data.hypaCustomSettings?.key ?? "", + model: data.hypaCustomSettings?.model ?? "", + } changeLanguage(data.language) setDatabaseLite(data) } @@ -897,7 +907,7 @@ export interface Database{ preserveOrphanedMemory: boolean processRegexScript: boolean doNotSummarizeUserMessage: boolean - }, + } OaiCompAPIKeys: {[key:string]:string} inlayErrorResponse:boolean reasoningEffort:number @@ -905,6 +915,14 @@ export interface Database{ showTranslationLoading: boolean showDeprecatedTriggerV1:boolean returnCSSError:boolean + useExperimentalGoogleTranslator:boolean + thinkingTokens: number + antiServerOverloads: boolean + hypaCustomSettings: { + url: string, + key: string, + model: string, + } } interface SeparateParameters{ @@ -917,6 +935,7 @@ interface SeparateParameters{ frequency_penalty?:number presence_penalty?:number reasoning_effort?:number + thinking_tokens?:number } export interface customscript{ @@ -961,6 +980,7 @@ export interface character{ desc:string notes:string chats:Chat[] + chatFolders: ChatFolder[] chatPage: number viewScreen: 'emotion'|'none'|'imggen'|'vn', bias: [string, number][] @@ -1099,6 +1119,7 @@ export interface groupChat{ image?:string firstMessage:string chats:Chat[] + chatFolders: ChatFolder[] chatPage: number name:string viewScreen: 'single'|'multiple'|'none'|'emp', @@ -1231,6 +1252,7 @@ export interface botPreset{ image?:string regex?:customscript[] reasonEffort?:number + thinkingTokens?:number } @@ -1305,6 +1327,14 @@ export interface Chat{ bindedPersona?:string fmIndex?:number hypaV3Data?:SerializableHypaV3Data + folderId?:string +} + +export interface ChatFolder{ + id:string + name?:string + color?:string + folded:boolean } export interface Message{ @@ -1537,6 +1567,7 @@ export function saveCurrentPreset(){ regex: db.presetRegex, image: pres?.[db.botPresetsId]?.image ?? '', reasonEffort: db.reasoningEffort ?? 0, + thinkingTokens: db.thinkingTokens ?? null, } db.botPresets = pres setDatabase(db) @@ -1647,6 +1678,7 @@ export function setPreset(db:Database, newPres: botPreset){ db.enableCustomFlags = newPres.enableCustomFlags ?? false db.presetRegex = newPres.regex ?? [] db.reasoningEffort = newPres.reasonEffort ?? 0 + db.thinkingTokens = newPres.thinkingTokens ?? null return db } diff --git a/src/ts/tokenizer.ts b/src/ts/tokenizer.ts index ddeb3794..e71528a4 100644 --- a/src/ts/tokenizer.ts +++ b/src/ts/tokenizer.ts @@ -313,7 +313,9 @@ export class ChatTokenizer { this.chatAdditionalTokens = chatAdditionalTokens this.useName = useName } - async tokenizeChat(data:OpenAIChat) { + async tokenizeChat(data:OpenAIChat, args:{ + countThoughts?:boolean, + } = {}) { let encoded = (await encode(data.content)).length + this.chatAdditionalTokens if(data.name && this.useName ==='name'){ encoded += (await encode(data.name)).length + 1 @@ -323,7 +325,7 @@ export class ChatTokenizer { encoded += await this.tokenizeMultiModal(multimodal) } } - if(data.thoughts && data.thoughts.length > 0){ + if(data.thoughts && data.thoughts.length > 0 && args.countThoughts){ for(const thought of data.thoughts){ encoded += (await encode(thought)).length + 1 } diff --git a/src/ts/translator/translator.ts b/src/ts/translator/translator.ts index 1194ac4b..8a687c49 100644 --- a/src/ts/translator/translator.ts +++ b/src/ts/translator/translator.ts @@ -1,7 +1,7 @@ import { get } from "svelte/store" import { translatorPlugin } from "../plugins/plugins" import { getDatabase, type character, type customscript, type groupChat } from "../storage/database.svelte" -import { globalFetch, isTauri } from "../globalApi.svelte" +import { globalFetch, isNodeServer, isTauri } from "../globalApi.svelte" import { alertError } from "../alert" import { requestChatData } from "../process/request" import { doingChat, type OpenAIChat } from "../process/index.svelte" @@ -41,7 +41,7 @@ export async function translate(text:string, reverse:boolean) { return runTranslator(text, reverse, db.translator,db.aiModel.startsWith('novellist') ? 'ja' : 'en') } -export async function runTranslator(text:string, reverse:boolean, from:string,target:string) { +export async function runTranslator(text:string, reverse:boolean, from:string,target:string, exarg?:{translatorNote?:string}) { const arg = { from: reverse ? from : target, @@ -50,6 +50,7 @@ export async function runTranslator(text:string, reverse:boolean, from:string,ta host: 'translate.googleapis.com', + translatorNote: exarg?.translatorNote } const texts = text.split('\n') let chunks:[string,boolean][] = [['', true]] @@ -104,11 +105,11 @@ export async function runTranslator(text:string, reverse:boolean, from:string,ta } -async function translateMain(text:string, arg:{from:string, to:string, host:string}){ +async function translateMain(text:string, arg:{from:string, to:string, host:string, translatorNote?:string}){ let db = getDatabase() if(db.translatorType === 'llm'){ const tr = arg.to || 'en' - return translateLLM(text, {to: tr, from: arg.from}) + return translateLLM(text, {to: tr, from: arg.from, translatorNote: arg.translatorNote}) } if(db.translatorType === 'deepl'){ const body = { @@ -163,6 +164,31 @@ async function translateMain(text:string, arg:{from:string, to:string, host:stri return f.data.data; } + if(db.useExperimentalGoogleTranslator){ + + const hqAvailable = isTauri || isNodeServer || userScriptFetch + + if(hqAvailable){ + try { + const ua = navigator.userAgent + const d = await globalFetch(`https://translate.google.com/m?tl=${arg.to}&sl=${arg.from}&q=${encodeURIComponent(text)}`, { + headers: { + "User-Agent": ua, + "Accept": "*/*", + }, + method: "GET", + }) + const parser = new DOMParser() + const dom = parser.parseFromString(d.data, 'text/html') + const result = dom.querySelector('.result-container')?.textContent?.trim() + if(result){ + return result + } + } catch (error) { + + } + } + } const url = `https://${arg.host}/translate_a/single?client=gtx&dt=t&sl=${db.translatorInputLanguage}&tl=${arg.to}&q=` + encodeURIComponent(text) @@ -448,7 +474,7 @@ function needSuperChunkedTranslate(){ return getDatabase().translatorType === 'deeplX' } -async function translateLLM(text:string, arg:{to:string, from:string, regenerate?:boolean}):Promise{ +async function translateLLM(text:string, arg:{to:string, from:string, regenerate?:boolean,translatorNote?:string}):Promise{ if(!arg.regenerate){ const cacheMatch = await LLMCacheStorage.getItem(text) if(cacheMatch){ @@ -465,12 +491,17 @@ async function translateLLM(text:string, arg:{to:string, from:string, regenerate const db = getDatabase() const charIndex = get(selectedCharID) const currentChar = db.characters[charIndex] - let translatorNote - if (currentChar?.type === "character") { + let translatorNote = "" + console.log(arg.translatorNote) + if(arg.translatorNote){ + translatorNote = arg.translatorNote + } + else if (currentChar?.type === "character") { translatorNote = currentChar.translatorNote ?? "" } else { translatorNote = "" } + console.log(translatorNote) let formated:OpenAIChat[] = [] let prompt = db.translatorPrompt || `You are a translator. translate the following html or text into {{slot}}. do not output anything other than the translation.` diff --git a/src/ts/util.ts b/src/ts/util.ts index 865fdfde..fc5267f6 100644 --- a/src/ts/util.ts +++ b/src/ts/util.ts @@ -1015,4 +1015,7 @@ export const sortableOptions = { delay: 300, // time in milliseconds to define when the sorting should start delayOnTouchOnly: true, filter: '.no-sort', + onMove: (event) => { + return event.related.className.indexOf('no-sort') === -1 + } } as const \ No newline at end of file diff --git a/version.json b/version.json index 571d9169..db3e0eb5 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{"version":"150.2.0"} \ No newline at end of file +{"version":"150.4.1"} \ No newline at end of file