From bccb4a78ecd42dcdac33b9f95d0dacdb8c1bd65a Mon Sep 17 00:00:00 2001 From: bangonicdd <157843588+bangonicdd2@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:43:06 +0900 Subject: [PATCH 01/13] fix: lorebook matches excluding dummy prefix/suffix --- src/ts/process/lorebook.svelte.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/ts/process/lorebook.svelte.ts b/src/ts/process/lorebook.svelte.ts index 5e578be7..973898fc 100644 --- a/src/ts/process/lorebook.svelte.ts +++ b/src/ts/process/lorebook.svelte.ts @@ -55,7 +55,8 @@ export async function loadLoreBookV3Prompt(){ const recursiveScanning = char.loreSettings?.recursiveScanning ?? true let recursivePrompt:{ prompt: string, - source: string + source: string, + data: string }[] = [] let matchLog:{ prompt: string, @@ -75,23 +76,27 @@ export async function loadLoreBookV3Prompt(){ let mList:{ source:string prompt:string + data:string }[] = sliced.map((msg, i) => { if(msg.role === 'user'){ return { source: `message ${i} by user`, - prompt: `\x01{{${DBState.db.username}}}:` + msg.data + '\x01' + prompt: `\x01{{${DBState.db.username}}}:` + msg.data + '\x01', + data: msg.data } } else{ return { source: `message ${i} by char`, - prompt: `\x01{{${msg.name ?? (msg.saying ? findCharacterbyId(msg.saying)?.name : null) ?? char.name}}}:` + msg.data + '\x01' + prompt: `\x01{{${msg.name ?? (msg.saying ? findCharacterbyId(msg.saying)?.name : null) ?? char.name}}}:` + msg.data + '\x01', + data: msg.data } } }).concat(recursivePrompt.map((msg) => { return { source: 'lorebook ' + msg.source, - prompt: msg.prompt + prompt: msg.prompt, + data: msg.data } })) @@ -106,7 +111,7 @@ export async function loadLoreBookV3Prompt(){ arg.keys[0] = regexString.replace('/'+regexFlag,'') try { const regex = new RegExp(arg.keys[0],regexFlag) - const d = regex.test(mText.prompt) + const d = regex.test(mText.data) if(d){ matchLog.push({ prompt: mText.prompt, @@ -127,7 +132,8 @@ export async function loadLoreBookV3Prompt(){ mList = mList.map((m) => { return { source: m.source, - prompt: m.prompt.toLocaleLowerCase().replace(/\{\{\/\/(.+?)\}\}/g,'').replace(/\{\{comment:(.+?)\}\}/g,'') + prompt: m.prompt.toLocaleLowerCase().replace(/\{\{\/\/(.+?)\}\}/g,'').replace(/\{\{comment:(.+?)\}\}/g,''), + data: m.data.toLocaleLowerCase().replace(/\{\{\/\/(.+?)\}\}/g,'').replace(/\{\{comment:(.+?)\}\}/g,'') } }) @@ -135,7 +141,7 @@ export async function loadLoreBookV3Prompt(){ let allModeMatched = true for(const m of mList){ - let mText = m.prompt + let mText = m.data if(arg.fullWordMatching){ const splited = mText.split(' ') for(const key of arg.keys){ From e0038749a411f00d6f1cd539ed40d5120e55e45f Mon Sep 17 00:00:00 2001 From: shirosaki-hana Date: Thu, 17 Apr 2025 13:14:12 +0900 Subject: [PATCH 02/13] Fix: Resolve account login issues in Node.js hosted version In a previous commit, a new proxy endpoint was added to the backend to resolve CORS errors that occurred when the frontend directly fetched data from Risu Realm. However, the proxy endpoint handler failed to properly process POST requests, causing account login failures. Additionally, the backend was inefficiently performing complex body processing. The updated code now directly pipes the original request, providing a more efficient and reliable solution. --- package.json | 1 - pnpm-lock.yaml | 18 +++++- server/node/server.cjs | 126 ++++++++++------------------------------- 3 files changed, 45 insertions(+), 100 deletions(-) diff --git a/package.json b/package.json index 32109138..99f759a0 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "mnemonist": "^0.40.3", "mobile-drag-drop": "3.0.0-rc.0", "msgpackr": "1.10.1", - "node-fetch": "2", "node-html-parser": "^6.1.12", "ollama": "^0.5.0", "pdfjs-dist": "^4.0.379", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c460c2c9..f247c718 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,15 +158,15 @@ importers: ml-distance: specifier: ^4.0.1 version: 4.0.1 + mnemonist: + specifier: ^0.40.3 + version: 0.40.3 mobile-drag-drop: specifier: 3.0.0-rc.0 version: 3.0.0-rc.0 msgpackr: specifier: 1.10.1 version: 1.10.1 - node-fetch: - specifier: '2' - version: 2.7.0 node-html-parser: specifier: ^6.1.12 version: 6.1.12 @@ -2756,6 +2756,9 @@ packages: ml-tree-similarity@1.0.0: resolution: {integrity: sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg==} + mnemonist@0.40.3: + resolution: {integrity: sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==} + mobile-drag-drop@3.0.0-rc.0: resolution: {integrity: sha512-f8wIDTbBYLBW/+5sei1cqUE+StyDpf/LP+FRZELlVX6tmOOmELk84r3wh1z3woxCB9G5octhF06K5COvFjGgqg==} @@ -2900,6 +2903,9 @@ packages: object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + obliterator@2.0.5: + resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + ollama@0.5.0: resolution: {integrity: sha512-CRtRzsho210EGdK52GrUMohA2pU+7NbgEaBG3DcYeRmvQthDO7E2LHOkLlUUeaYUlNmEd8icbjC02ug9meSYnw==} @@ -6505,6 +6511,10 @@ snapshots: binary-search: 1.3.6 num-sort: 2.1.0 + mnemonist@0.40.3: + dependencies: + obliterator: 2.0.5 + mobile-drag-drop@3.0.0-rc.0: {} modify-values@1.0.1: {} @@ -6665,6 +6675,8 @@ snapshots: object-inspect@1.13.1: {} + obliterator@2.0.5: {} + ollama@0.5.0: dependencies: whatwg-fetch: 3.6.20 diff --git a/server/node/server.cjs b/server/node/server.cjs index b2871d9d..6e16bcce 100644 --- a/server/node/server.cjs +++ b/server/node/server.cjs @@ -11,8 +11,7 @@ app.use(express.raw({ type: 'application/octet-stream', limit: '50mb' })); const {pipeline} = require('stream/promises') const https = require('https'); const sslPath = path.join(process.cwd(), 'server/node/ssl/certificate'); -const EXTERNAL_HUB_URL = 'https://sv.risuai.xyz'; -const fetch = require('node-fetch'); +const hubURL = 'https://sv.risuai.xyz'; let password = '' @@ -31,12 +30,17 @@ function isHex(str) { } app.get('/', async (req, res, next) => { - console.log("[Server] Connected") + + const clientIP = req.headers['x-forwarded-for'] || req.ip || req.socket.remoteAddress || 'Unknown IP'; + const timestamp = new Date().toISOString(); + console.log(`[Server] ${timestamp} | Connection from: ${clientIP}`); + try { const mainIndex = await fs.readFile(path.join(process.cwd(), 'dist', 'index.html')) const root = htmlparser.parse(mainIndex) const head = root.querySelector('head') head.innerHTML = `` + head.innerHTML + res.send(root.toString()) } catch (error) { console.log(error) @@ -139,116 +143,49 @@ const reverseProxyFunc_get = async (req, res, next) => { } } -// Risu Realm Proxy -async function hubProxyHandler(req, res, next) { +async function hubProxyFunc(req, res) { + try { - // Extract request path and query parameters const pathAndQuery = req.originalUrl.replace(/^\/hub-proxy/, ''); - const externalURL = EXTERNAL_HUB_URL + pathAndQuery; - - console.log(`[Hub Proxy] Forwarding ${req.method} request to: ${externalURL}`); - - // Prepare headers to send to the realm server (including Accept-Encoding modification) + const externalURL = hubURL + pathAndQuery; + const headersToSend = { ...req.headers }; - delete headersToSend['host']; - delete headersToSend['connection']; - headersToSend['accept-encoding'] = 'gzip, deflate'; // Exclude zstd, etc. - if (!headersToSend['x-forwarded-for']) { - headersToSend['x-forwarded-for'] = req.ip; - } - - // Execute the fetch request to the realm server - const response = await fetch(externalURL, { + delete headersToSend.host; + delete headersToSend.connection; + + const proxyReq = new Request(externalURL, { method: req.method, headers: headersToSend, - body: (req.method !== 'GET' && req.method !== 'HEAD') ? req.body : undefined, + body: req.method !== 'GET' && req.method !== 'HEAD' ? req : undefined, + duplex: 'half' }); - - console.log(`[Hub Proxy] Received status ${response.status} from external server`); - - // Handle the realm server response - // Clean up response headers and extract Content-Type - const responseHeaders = {}; - // Check the Content-Type of the realm server response (use default if missing) - let contentType = response.headers.get('content-type') || 'application/octet-stream'; - - response.headers.forEach((value, key) => { - const lowerKey = key.toLowerCase(); - // List of headers not to be forwarded to the client - const excludedHeaders = [ - 'transfer-encoding', 'connection', 'content-encoding', - 'access-control-allow-origin', 'access-control-allow-methods', - 'access-control-allow-headers', 'content-security-policy', - 'content-security-policy-report-only', 'clear-site-data', - 'strict-transport-security', 'expect-ct', - 'cf-ray', 'cf-cache-status', 'report-to', 'nel', 'server', 'server-timing', 'alt-svc' - ]; - if (!excludedHeaders.includes(lowerKey)) { - responseHeaders[key] = value; - } - }); - - // Set the status code and cleaned headers for the client - res.status(response.status).set(responseHeaders); - - // Determine body processing method based on Content-Type - try { - if (contentType.startsWith('application/json')) { - // JSON response: read as text and send - const bodyText = await response.text(); - console.log(`[Hub Proxy] Processing JSON response (size: ${bodyText.length})`); - res.setHeader('Content-Type', contentType); // Set the final Content-Type - res.send(bodyText); - - } else if (contentType.startsWith('image/')) { - // Image response: read as buffer and send - const bodyBuffer = await response.buffer(); // Assuming 'fetch' response object has a .buffer() method or similar - console.log(`[Hub Proxy] Processing Image response (type: ${contentType}, size: ${bodyBuffer.length} bytes)`); - res.setHeader('Content-Type', contentType); // Set the final Content-Type - res.send(bodyBuffer); - - } else { - // Other responses (HTML, other text, unknown binary, etc.): read as buffer and send safely - const bodyBuffer = await response.buffer(); // Assuming 'fetch' response object has a .buffer() method or similar - console.log(`[Hub Proxy] Processing Other response as buffer (type: ${contentType}, size: ${bodyBuffer.length} bytes)`); - // Use original Content-Type if available, otherwise use octet-stream (already handled by default assignment) - res.setHeader('Content-Type', contentType); - res.send(bodyBuffer); - } - } catch (bodyError) { - // If an error occurs while reading/processing the response body - console.error("[Hub Proxy] Error reading/processing response body:", bodyError); - if (!res.headersSent) { - res.status(500).send({ error: 'Failed to process response body from hub server.' }); - } else { - console.error("[Hub Proxy] Headers already sent, cannot send body error to client."); - res.end(); - } - return; // End the handler + + const response = await fetch(proxyReq); + + for (const [key, value] of response.headers.entries()) { + res.setHeader(key, value); } - + res.status(response.status); + + await pipeline(response.body, res); + } catch (error) { - // Fetch request itself failed or other exceptions - console.error("[Hub Proxy] Request failed:", error); + console.error("[Hub Proxy] Error:", error); if (!res.headersSent) { - res.status(502).send({ error: 'Proxy failed to connect to or get response from the hub server.' }); + res.status(502).send({ error: 'Proxy request failed' }); } else { - console.error("[Hub Proxy] Headers already sent, cannot send connection error to client."); res.end(); } } } -app.get('/hub-proxy/*', hubProxyHandler); -app.post('/hub-proxy/*', hubProxyHandler); -app.put('/hub-proxy/*', hubProxyHandler); - app.get('/proxy', reverseProxyFunc_get); app.get('/proxy2', reverseProxyFunc_get); +app.get('/hub-proxy/*', hubProxyFunc); app.post('/proxy', reverseProxyFunc); app.post('/proxy2', reverseProxyFunc); - +app.post('/hub-proxy/*', hubProxyFunc); app.get('/api/password', async(req, res)=> { if(password === ''){ @@ -408,9 +345,6 @@ async function getHttpsOptions() { const keyPath = path.join(sslPath, 'server.key'); const certPath = path.join(sslPath, 'server.crt'); - console.log(keyPath) - console.log(certPath) - try { await fs.access(keyPath); From 5168565b4f7961e680751a31aceed90453811dc6 Mon Sep 17 00:00:00 2001 From: shirosaki-hana Date: Thu, 17 Apr 2025 17:31:46 +0900 Subject: [PATCH 03/13] Update : server.cjs --- server/node/server.cjs | 29 +++++++++++++++++++---- src/lib/Others/AlertComp.svelte | 2 +- src/lib/Setting/Pages/UserSettings.svelte | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/server/node/server.cjs b/server/node/server.cjs index 6e16bcce..b8feb4fd 100644 --- a/server/node/server.cjs +++ b/server/node/server.cjs @@ -153,26 +153,45 @@ async function hubProxyFunc(req, res) { delete headersToSend.host; delete headersToSend.connection; - const proxyReq = new Request(externalURL, { + const response = await fetch(externalURL, { method: req.method, headers: headersToSend, body: req.method !== 'GET' && req.method !== 'HEAD' ? req : undefined, - duplex: 'half' + redirect: 'manual' }); - const response = await fetch(proxyReq); - for (const [key, value] of response.headers.entries()) { res.setHeader(key, value); } res.status(response.status); + if (response.status >= 300 && response.status < 400) { + // Redirect handling (due to ‘/redirect/docs/lua’) + const redirectUrl = response.headers.get('location'); + if (redirectUrl) { + + if (redirectUrl.startsWith('http')) { + + if (redirectUrl.startsWith(hubURL)) { + const newPath = redirectUrl.replace(hubURL, '/hub-proxy'); + res.setHeader('location', newPath); + } + + } else if (redirectUrl.startsWith('/')) { + + res.setHeader('location', `/hub-proxy${redirectUrl}`); + } + } + + return res.end(); + } + await pipeline(response.body, res); } catch (error) { console.error("[Hub Proxy] Error:", error); if (!res.headersSent) { - res.status(502).send({ error: 'Proxy request failed' }); + res.status(502).send({ error: 'Proxy request failed: ' + error.message }); } else { res.end(); } diff --git a/src/lib/Others/AlertComp.svelte b/src/lib/Others/AlertComp.svelte index 16273635..7801aee6 100644 --- a/src/lib/Others/AlertComp.svelte +++ b/src/lib/Others/AlertComp.svelte @@ -72,7 +72,7 @@ { - if(e.origin.startsWith("https://sv.risuai.xyz") || e.origin.startsWith("http://127.0.0.1")){ + if(e.origin.startsWith("https://sv.risuai.xyz") || e.origin.startsWith("http://127.0.0.1") || e.origin === window.location.origin){ if(e.data.msg.data.vaild && $alertStore.type === 'login'){ $alertStore = { type: 'none', diff --git a/src/lib/Setting/Pages/UserSettings.svelte b/src/lib/Setting/Pages/UserSettings.svelte index 95a4a6c5..2cdcb012 100644 --- a/src/lib/Setting/Pages/UserSettings.svelte +++ b/src/lib/Setting/Pages/UserSettings.svelte @@ -19,7 +19,7 @@ { - if(e.origin.startsWith("https://sv.risuai.xyz") || e.origin.startsWith("http://127.0.0.1")){ + if(e.origin.startsWith("https://sv.risuai.xyz") || e.origin.startsWith("http://127.0.0.1") || e.origin === window.location.origin){ if(e.data.msg.type === 'drive'){ await loadRisuAccountData() DBState.db.account.data.refresh_token = e.data.msg.data.refresh_token From 1b62dc95ed385b1291b813440cf73a33edb4cd34 Mon Sep 17 00:00:00 2001 From: shirosaki-hana Date: Fri, 18 Apr 2025 13:18:37 +0900 Subject: [PATCH 04/13] Add duplex --- server/node/server.cjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/node/server.cjs b/server/node/server.cjs index b8feb4fd..0828d9f7 100644 --- a/server/node/server.cjs +++ b/server/node/server.cjs @@ -157,7 +157,8 @@ async function hubProxyFunc(req, res) { method: req.method, headers: headersToSend, body: req.method !== 'GET' && req.method !== 'HEAD' ? req : undefined, - redirect: 'manual' + redirect: 'manual', + duplex: 'half' }); for (const [key, value] of response.headers.entries()) { From 9af38ced76cb1727119c038046f554b8b6494ba2 Mon Sep 17 00:00:00 2001 From: bangonicdd <157843588+bangonicdd2@users.noreply.github.com> Date: Sat, 19 Apr 2025 12:01:53 +0900 Subject: [PATCH 05/13] feat: add persona sort --- src/lib/Setting/Pages/PersonaSettings.svelte | 54 +++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/lib/Setting/Pages/PersonaSettings.svelte b/src/lib/Setting/Pages/PersonaSettings.svelte index f6824319..f533ca2a 100644 --- a/src/lib/Setting/Pages/PersonaSettings.svelte +++ b/src/lib/Setting/Pages/PersonaSettings.svelte @@ -8,16 +8,65 @@ import { alertConfirm, alertSelect } from "src/ts/alert"; import { getCharImage } from "src/ts/characters"; import { changeUserPersona, exportUserPersona, importUserPersona, saveUserPersona, selectUserImg } from "src/ts/persona"; + import Sortable from 'sortablejs/modular/sortable.core.esm.js'; + import { onDestroy, onMount } from "svelte"; + import { sleep, sortableOptions } from "src/ts/util"; import { setDatabase } from "src/ts/storage/database.svelte"; import { DBState } from 'src/ts/stores.svelte'; import { get } from "svelte/store"; + let stb: Sortable = null + let ele: HTMLDivElement = $state() + let sorted = $state(0) + const createStb = () => { + stb = Sortable.create(ele, { + onStart: async () => { + saveUserPersona() + }, + onEnd: async () => { + let idx:number[] = [] + ele.querySelectorAll('[data-risu-idx]').forEach((e, i) => { + idx.push(parseInt(e.getAttribute('data-risu-idx'))) + }) + let newValue:{ + personaPrompt:string + name:string + icon:string + largePortrait?:boolean + id?:string + }[] = [] + idx.forEach((i) => { + newValue.push(DBState.db.personas[i]) + }) + DBState.db.personas = newValue + changeUserPersona(0, 'noSave') + try { + stb.destroy() + } catch (error) {} + sorted += 1 + await sleep(1) + createStb() + }, + ...sortableOptions + }) + } + + onMount(createStb) + + onDestroy(() => { + if(stb){ + try { + stb.destroy() + } catch (error) {} + } + })

{language.persona}

-
+{#key sorted} +
{#each DBState.db.personas as persona, i} -
+{/key}
From a7aebab65cdcc31560da4f1e17ff104bf879204c Mon Sep 17 00:00:00 2001 From: bangonicdd <157843588+bangonicdd2@users.noreply.github.com> Date: Sat, 19 Apr 2025 15:34:52 +0900 Subject: [PATCH 06/13] fix: preserve selected persona after sorting --- src/lib/Setting/Pages/PersonaSettings.svelte | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/Setting/Pages/PersonaSettings.svelte b/src/lib/Setting/Pages/PersonaSettings.svelte index f533ca2a..6e2e70c6 100644 --- a/src/lib/Setting/Pages/PersonaSettings.svelte +++ b/src/lib/Setting/Pages/PersonaSettings.svelte @@ -14,13 +14,17 @@ import { setDatabase } from "src/ts/storage/database.svelte"; import { DBState } from 'src/ts/stores.svelte'; import { get } from "svelte/store"; + import { v4 } from "uuid" let stb: Sortable = null let ele: HTMLDivElement = $state() let sorted = $state(0) + let selectedId:string = null const createStb = () => { stb = Sortable.create(ele, { onStart: async () => { + DBState.db.personas[DBState.db.selectedPersona].id ??= v4() + selectedId = DBState.db.personas[DBState.db.selectedPersona].id saveUserPersona() }, onEnd: async () => { @@ -39,7 +43,8 @@ newValue.push(DBState.db.personas[i]) }) DBState.db.personas = newValue - changeUserPersona(0, 'noSave') + const selectedPersona = DBState.db.personas.findIndex((e) => e.id === selectedId) + changeUserPersona(selectedPersona !== -1 ? selectedPersona : 0, 'noSave') try { stb.destroy() } catch (error) {} From 5f1d63dcfb1141545ae0c2d16673ceb196b33dac Mon Sep 17 00:00:00 2001 From: bangonicdd <157843588+bangonicdd2@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:00:18 +0900 Subject: [PATCH 07/13] feat: module lorebook sort, import, export --- .../Setting/Pages/Module/ModuleMenu.svelte | 69 ++++++++++++++++--- src/lib/SideBars/LoreBook/LoreBookList.svelte | 22 +++++- src/ts/process/lorebook.svelte.ts | 2 +- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/lib/Setting/Pages/Module/ModuleMenu.svelte b/src/lib/Setting/Pages/Module/ModuleMenu.svelte index 82e51fce..d06d16ac 100644 --- a/src/lib/Setting/Pages/Module/ModuleMenu.svelte +++ b/src/lib/Setting/Pages/Module/ModuleMenu.svelte @@ -2,6 +2,9 @@ import { language } from "src/lang"; import TextInput from "src/lib/UI/GUI/TextInput.svelte"; import LoreBookData from "src/lib/SideBars/LoreBook/LoreBookData.svelte"; + import type { loreBook } from "src/ts/storage/database.svelte"; + import LoreBookList from "src/lib/SideBars/LoreBook/LoreBookList.svelte"; + import { type CCLorebook, convertExternalLorebook } from "src/ts/process/lorebook.svelte"; import type { RisuModule } from "src/ts/process/modules"; import { DownloadIcon, FolderUpIcon, PlusIcon, TrashIcon } from "lucide-svelte"; import RegexList from "src/lib/SideBars/Scripts/RegexList.svelte"; @@ -9,7 +12,8 @@ import Check from "src/lib/UI/GUI/CheckInput.svelte"; import Help from "src/lib/Others/Help.svelte"; import TextAreaInput from "src/lib/UI/GUI/TextAreaInput.svelte"; - import { getFileSrc, openURL, saveAsset } from "src/ts/globalApi.svelte"; + import { getFileSrc, openURL, saveAsset, downloadFile } from "src/ts/globalApi.svelte"; + import { alertNormal, alertError } from "src/ts/alert"; import { exportRegex, importRegex } from "src/ts/process/scripts"; import { selectMultipleFile } from "src/ts/util"; @@ -57,6 +61,48 @@ } } + async function exportLoreBook(){ + try { + const lore = currentModule.lorebook + const stringl = Buffer.from(JSON.stringify({ + type: 'risu', + ver: 1, + data: lore + }), 'utf-8') + + await downloadFile(`lorebook_export.json`, stringl) + + alertNormal(language.successExport) + } catch (error) { + alertError(`${error}`) + } + } + + async function importLoreBook(){ + let lore = currentModule.lorebook + const lorebook = (await selectMultipleFile(['json', 'lorebook'])) + if(!lorebook){ + return + } + try { + for(const f of lorebook){ + const importedlore = JSON.parse(Buffer.from(f.data).toString('utf-8')) + if(importedlore.type === 'risu' && importedlore.data){ + const datas:loreBook[] = importedlore.data + for(const data of datas){ + lore.push(data) + } + } + else if(importedlore.entries){ + const entries:{[key:string]:CCLorebook} = importedlore.entries + lore.push(...convertExternalLorebook(entries)) + } + } + } catch (error) { + alertError(`${error}`) + } + } + function addRegex(){ if(Array.isArray(currentModule.regex)){ currentModule.regex.push({ @@ -150,17 +196,18 @@ {/if} {#if submenu === 1 && (Array.isArray(currentModule.lorebook))} -
- {#each currentModule.lorebook as lore, i} - { - currentModule.lorebook.splice(i, 1) - currentModule.lorebook = currentModule.lorebook - }}/> - {/each} + +
+ + +
- {/if} {#if submenu === 2 && (Array.isArray(currentModule.regex))} diff --git a/src/lib/SideBars/LoreBook/LoreBookList.svelte b/src/lib/SideBars/LoreBook/LoreBookList.svelte index 89b1f455..213eec65 100644 --- a/src/lib/SideBars/LoreBook/LoreBookList.svelte +++ b/src/lib/SideBars/LoreBook/LoreBookList.svelte @@ -11,9 +11,10 @@ globalMode?: boolean; submenu?: number; lorePlus?: boolean; + externalLoreBooks?: loreBook[]; } - let { globalMode = false, submenu = 0, lorePlus = false }: Props = $props(); + let { globalMode = false, submenu = 0, lorePlus = false, externalLoreBooks = null }: Props = $props(); let stb: Sortable = null let ele: HTMLDivElement = $state() let sorted = $state(0) @@ -31,6 +32,13 @@ }) DBState.db.loreBook[DBState.db.loreBookPage].data = newLore } + else if(externalLoreBooks){ + let newLore:loreBook[] = [] + idx.forEach((i) => { + newLore.push(externalLoreBooks[i]) + }) + externalLoreBooks = newLore + } else if(submenu === 1){ let newLore:loreBook[] = [] idx.forEach((i) => { @@ -97,6 +105,18 @@ }} onOpen={onOpen} onClose={onClose}/> {/each} {/if} + {:else if externalLoreBooks} + {#if externalLoreBooks.length === 0} + No Lorebook + {:else} + {#each externalLoreBooks as book, i} + { + let lore = externalLoreBooks + lore.splice(i, 1) + externalLoreBooks = lore + }} onOpen={onOpen} onClose={onClose}/> + {/each} + {/if} {:else if submenu === 0} {#if DBState.db.characters[$selectedCharID].globalLore.length === 0} No Lorebook diff --git a/src/ts/process/lorebook.svelte.ts b/src/ts/process/lorebook.svelte.ts index 5e578be7..b81b503d 100644 --- a/src/ts/process/lorebook.svelte.ts +++ b/src/ts/process/lorebook.svelte.ts @@ -510,7 +510,7 @@ export async function importLoreBook(mode:'global'|'local'|'sglobal'){ } } -interface CCLorebook{ +export interface CCLorebook{ key:string[] comment:string content:string From 4703bd463b7fcb98b93f86de3ab0885aa24cd6a6 Mon Sep 17 00:00:00 2001 From: Bo26fhmC5M <88071760+Bo26fhmC5M@users.noreply.github.com> Date: Sun, 20 Apr 2025 14:57:45 +0900 Subject: [PATCH 08/13] feat: add option to show Hypa modal button in chat menu - feat: add accessibility setting to control visibility of Hypa V2/V3 modal button in chat menu - feat: add summarization condition tip to HypaV3 modal --- src/lang/cn.ts | 1 + src/lang/de.ts | 1 + src/lang/en.ts | 6 +++- src/lang/es.ts | 1 + src/lang/ko.ts | 6 +++- src/lang/vi.ts | 1 + src/lang/zh-Hant.ts | 1 + src/lib/ChatScreens/DefaultChatScreen.svelte | 28 +++++++++++++++++-- src/lib/Others/HypaV3Modal.svelte | 23 +++++++++++---- .../Pages/AccessibilitySettings.svelte | 4 +++ src/lib/SideBars/CharConfig.svelte | 8 ++---- src/ts/storage/database.svelte.ts | 1 + 12 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/lang/cn.ts b/src/lang/cn.ts index 4a4116be..efdf13de 100644 --- a/src/lang/cn.ts +++ b/src/lang/cn.ts @@ -816,6 +816,7 @@ export const languageChinese = { "nextSummarizationLabel": "HypaV3 将总结 [{0}]", "nextSummarizationNoMessagesFoundLabel": "警告:未找到消息", "nextSummarizationLoadingError": "加载下一个总结目标时出错:{0}", + "summarizationConditionLabel": "提示:当输入标记超过最大上下文大小时,HypaV3 将开始进行摘要处理。", "emptySelectedFirstMessageLabel": "警告:所选的第一条消息为空" }, } diff --git a/src/lang/de.ts b/src/lang/de.ts index c3ac6507..744b67a9 100644 --- a/src/lang/de.ts +++ b/src/lang/de.ts @@ -480,6 +480,7 @@ export const languageGerman = { "nextSummarizationLabel": "HypaV3 wird [{0}] zusammenfassen", "nextSummarizationNoMessagesFoundLabel": "WARNUNG: Keine Nachrichten gefunden", "nextSummarizationLoadingError": "Fehler beim Laden des nächsten Zusammenfassungsziels: {0}", + "summarizationConditionLabel": "Hinweis: HypaV3 beginnt mit der Zusammenfassung, wenn die Eingabe-Tokens die maximale Kontextgröße überschreiten.", "emptySelectedFirstMessageLabel": "WARNUNG: Ausgewählte erste Nachricht ist leer" }, } diff --git a/src/lang/en.ts b/src/lang/en.ts index c1cb81df..f410a812 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -1052,6 +1052,7 @@ export const languageEnglish = { nextSummarizationLabel: "HypaV3 will summarize [{0}]", nextSummarizationNoMessagesFoundLabel: "WARN: No messages found", nextSummarizationLoadingError: "Error loading next summarization target: {0}", + summarizationConditionLabel: "Tip: HypaV3 begins summarization when input tokens exceed the maximum context size.", emptySelectedFirstMessageLabel: "WARN: Selected first message is empty", }, bulkEnabling: "Lorebook Bulk Enabling", @@ -1115,5 +1116,8 @@ export const languageEnglish = { fallbackWhenBlankResponse: "Fallback When Blank Response", doNotChangeFallbackModels: "Do Not Change Fallback Models on Preset Change", customModels: "Custom Models", - igpPrompt: "IGP Prompt" + igpPrompt: "IGP Prompt", + hypaMemoryV2Modal: "Hypa V2 Modal", + hypaMemoryV3Modal: "Hypa V3 Modal", + showMenuHypaMemoryModal: "Show Menu Hypa Modal", } diff --git a/src/lang/es.ts b/src/lang/es.ts index af4168b1..369cb839 100644 --- a/src/lang/es.ts +++ b/src/lang/es.ts @@ -725,6 +725,7 @@ export const languageSpanish = { "nextSummarizationLabel": "HypaV3 resumirá [{0}]", "nextSummarizationNoMessagesFoundLabel": "ADVERTENCIA: No se encontraron mensajes", "nextSummarizationLoadingError": "Error al cargar el siguiente objetivo de resumen: {0}", + "summarizationConditionLabel": "Consejo: HypaV3 comienza a resumir cuando los tokens de entrada superan el tamaño máximo de contexto.", "emptySelectedFirstMessageLabel": "ADVERTENCIA: El primer mensaje seleccionado está vacío" }, } diff --git a/src/lang/ko.ts b/src/lang/ko.ts index 863ec86c..97c7fbf0 100644 --- a/src/lang/ko.ts +++ b/src/lang/ko.ts @@ -971,7 +971,8 @@ export const languageKorean = { "nextSummarizationLabel": "HypaV3가 [{0}]를 요약할 예정입니다", "nextSummarizationNoMessagesFoundLabel": "경고: 메시지를 찾을 수 없습니다", "nextSummarizationLoadingError": "다음 요약 대상을 불러오는 동안 오류 발생: {0}", - "emptySelectedFirstMessageLabel": "경고: 선택된 첫 메시지가 비어있습니다" + "summarizationConditionLabel": "팁: HypaV3는 입력 토큰이 최대 컨텍스트 크기를 넘으면 요약을 시작합니다.", + "emptySelectedFirstMessageLabel": "경고: 선택된 첫 메시지가 비어있습니다", }, "bulkEnabling": "한번에 로어북 활성화 버튼", "showTranslationLoading": "번역 로딩 보이기", @@ -984,4 +985,7 @@ export const languageKorean = { "childLoreDesc": "이것은 캐릭터 로어의 복사본이며, 삭제하거나 원본 로어에서 직접 비활성화하기 전에는 '언제나 활성화' 상태로 유지됩니다.", "cachePoint": "캐시 포인트", "all": "모두", + "hypaMemoryV2Modal": "하이파 V2 모달", + "hypaMemoryV3Modal": "하이파 V3 모달", + "showMenuHypaMemoryModal": "메뉴에서 하이파 모달 보이기", } diff --git a/src/lang/vi.ts b/src/lang/vi.ts index 4cbe52b2..da26f795 100644 --- a/src/lang/vi.ts +++ b/src/lang/vi.ts @@ -454,6 +454,7 @@ export const LanguageVietnamese = { "nextSummarizationLabel": "HypaV3 sẽ tóm tắt [{0}]", "nextSummarizationNoMessagesFoundLabel": "CẢNH BÁO: Không tìm thấy tin nhắn", "nextSummarizationLoadingError": "Lỗi khi tải mục tiêu tóm tắt tiếp theo: {0}", + "summarizationConditionLabel": "Mẹo: HypaV3 bắt đầu tóm tắt khi số lượng token đầu vào vượt quá kích thước ngữ cảnh tối đa.", "emptySelectedFirstMessageLabel": "CẢNH BÁO: Tin nhắn đầu tiên được chọn trống" }, } diff --git a/src/lang/zh-Hant.ts b/src/lang/zh-Hant.ts index 481587af..300ba5b9 100644 --- a/src/lang/zh-Hant.ts +++ b/src/lang/zh-Hant.ts @@ -849,6 +849,7 @@ export const languageChineseTraditional = { "nextSummarizationLabel": "HypaV3 將摘要 [{0}]", "nextSummarizationNoMessagesFoundLabel": "警告:找不到訊息", "nextSummarizationLoadingError": "載入下一個摘要目標時出錯:{0}", + "summarizationConditionLabel": "提示:當輸入標記超過最大上下文大小時,HypaV3 將開始進行摘要處理。", "emptySelectedFirstMessageLabel": "警告:選定的第一條訊息為空" }, } diff --git a/src/lib/ChatScreens/DefaultChatScreen.svelte b/src/lib/ChatScreens/DefaultChatScreen.svelte index 8449ee70..508c1cf8 100644 --- a/src/lib/ChatScreens/DefaultChatScreen.svelte +++ b/src/lib/ChatScreens/DefaultChatScreen.svelte @@ -2,7 +2,7 @@ import Suggestion from './Suggestion.svelte'; import AdvancedChatEditor from './AdvancedChatEditor.svelte'; - import { CameraIcon, DatabaseIcon, DicesIcon, GlobeIcon, ImagePlusIcon, LanguagesIcon, Laugh, MenuIcon, MicOffIcon, PackageIcon, Plus, RefreshCcwIcon, ReplyIcon, Send, StepForwardIcon, XIcon } from "lucide-svelte"; + import { CameraIcon, DatabaseIcon, DicesIcon, GlobeIcon, ImagePlusIcon, LanguagesIcon, Laugh, MenuIcon, MicOffIcon, PackageIcon, Plus, RefreshCcwIcon, ReplyIcon, Send, StepForwardIcon, XIcon, BrainIcon } from "lucide-svelte"; import { selectedCharID, PlaygroundStore, createSimpleCharacter } from "../../ts/stores.svelte"; import Chat from "./Chat.svelte"; import { type Message, type character, type groupChat } from "../../ts/storage/database.svelte"; @@ -12,7 +12,7 @@ import { findCharacterbyId, getUserIconProtrait, messageForm, sleep } from "../../ts/util"; import { language } from "../../lang"; import { isExpTranslator, translate } from "../../ts/translator/translator"; - import { alertError, alertNormal, alertWait } from "../../ts/alert"; + import { alertError, alertNormal, alertWait, showHypaV2Alert, showHypaV3Alert } from "../../ts/alert"; import sendSound from '../../etc/send.mp3' import { processScript } from "src/ts/process/scripts"; import CreatorQuote from "./CreatorQuote.svelte"; @@ -803,6 +803,30 @@ {language.chatList}
{/if} + + {#if DBState.db.showMenuHypaMemoryModal} + {#if DBState.db.supaModelType !== 'none' && (DBState.db.hypav2 || DBState.db.hypaV3)} +
{ + if (DBState.db.hypav2) { + DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].hypaV2Data ??= { + lastMainChunkID: 0, + mainChunks: [], + chunks: [], + } + showHypaV2Alert(); + } else if (DBState.db.hypaV3) { + showHypaV3Alert(); + } + + openMenu = false + }}> + + + {DBState.db.hypav2 ? language.hypaMemoryV2Modal : language.hypaMemoryV3Modal} + +
+ {/if} + {/if} {#if DBState.db.translator !== ''}
{ diff --git a/src/lib/Others/HypaV3Modal.svelte b/src/lib/Others/HypaV3Modal.svelte index 51451563..bd1bebb5 100644 --- a/src/lib/Others/HypaV3Modal.svelte +++ b/src/lib/Others/HypaV3Modal.svelte @@ -91,6 +91,15 @@ let showImportantOnly = $state(false); $effect.pre(() => { + untrack(() => { + DBState.db.characters[$selectedCharID].chats[ + DBState.db.characters[$selectedCharID].chatPage + ].hypaV3Data ??= { + summaries: [], + lastSelectedSummaries: [], + }; + }); + summaryUIStates = hypaV3DataState.summaries.map((summary) => ({ originalRef: null, isTranslating: false, @@ -1359,14 +1368,18 @@ {/await}
- - {#if !getFirstMessage()} -
+
+
+ {language.hypaV3Modal.summarizationConditionLabel} +
+ + + {#if !getFirstMessage()} {language.hypaV3Modal.emptySelectedFirstMessageLabel} -
- {/if} + {/if} +
diff --git a/src/lib/Setting/Pages/AccessibilitySettings.svelte b/src/lib/Setting/Pages/AccessibilitySettings.svelte index da9dc756..c5ba653a 100644 --- a/src/lib/Setting/Pages/AccessibilitySettings.svelte +++ b/src/lib/Setting/Pages/AccessibilitySettings.svelte @@ -43,6 +43,10 @@ +
+ +
+
diff --git a/src/lib/SideBars/CharConfig.svelte b/src/lib/SideBars/CharConfig.svelte index 7af6beca..c4c90a6e 100644 --- a/src/lib/SideBars/CharConfig.svelte +++ b/src/lib/SideBars/CharConfig.svelte @@ -1101,20 +1101,16 @@ }} className="mt-4" > - {language.HypaMemory} V2 Data + {language.hypaMemoryV2Modal} {:else if DBState.db.supaModelType !== 'none' && DBState.db.hypaV3} {:else if DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].supaMemoryData && DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].supaMemoryData.length > 4 || DBState.db.characters[$selectedCharID].supaMemory} {language.SuperMemory} diff --git a/src/ts/storage/database.svelte.ts b/src/ts/storage/database.svelte.ts index 12618efe..6c7cc284 100644 --- a/src/ts/storage/database.svelte.ts +++ b/src/ts/storage/database.svelte.ts @@ -1022,6 +1022,7 @@ export interface Database{ flags: LLMFlags[] }[] igpPrompt:string + showMenuHypaMemoryModal:boolean } interface SeparateParameters{ From 33d8ed45680f52dbd545cdd12076eaff576052d6 Mon Sep 17 00:00:00 2001 From: sub-hub <70351692+sub-hub@users.noreply.github.com> Date: Mon, 21 Apr 2025 13:27:43 +0900 Subject: [PATCH 09/13] restore tokenizer caching with old-bug version --- src/ts/tokenizer.ts | 162 ++++++++++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 65 deletions(-) diff --git a/src/ts/tokenizer.ts b/src/ts/tokenizer.ts index e71528a4..b553ba27 100644 --- a/src/ts/tokenizer.ts +++ b/src/ts/tokenizer.ts @@ -6,9 +6,27 @@ import { supportsInlayImage } from "./process/files/inlays"; import { risuChatParser } from "./parser.svelte"; import { tokenizeGGUFModel } from "./process/models/local"; import { globalFetch } from "./globalApi.svelte"; -import { getModelInfo, LLMTokenizer } from "./model/modellist"; +import { getModelInfo, LLMTokenizer, type LLMModel } from "./model/modellist"; import { pluginV2 } from "./plugins/plugins"; import type { GemmaTokenizer } from "@huggingface/transformers"; +import { LRUMap } from 'mnemonist'; + +const MAX_CACHE_SIZE = 1500; + +const encodeCache = new LRUMap(MAX_CACHE_SIZE); + +function getHash( + data: string, + aiModel: string, + customTokenizer: string, + currentPluginProvider: string, + googleClaudeTokenizing: boolean, + modelInfo: LLMModel, + pluginTokenizer: string +): string { + const combined = `${data}::${aiModel}::${customTokenizer}::${currentPluginProvider}::${googleClaudeTokenizing ? '1' : '0'}::${modelInfo.tokenizer}::${pluginTokenizer}`; + return combined; +} export const tokenizerList = [ @@ -25,100 +43,114 @@ export const tokenizerList = [ ] as const export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Array)>{ - let db = getDatabase() + const db = getDatabase(); + const modelInfo = getModelInfo(db.aiModel); + const pluginTokenizer = pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizer ?? "none"; + + let cacheKey = '' + if(db.useTokenizerCaching){ + cacheKey = getHash( + data, + db.aiModel, + db.customTokenizer, + db.currentPluginProvider, + db.googleClaudeTokenizing, + modelInfo, + pluginTokenizer + ); + const cachedResult = encodeCache.get(cacheKey); + if (cachedResult !== undefined) { + return cachedResult; + } + } + + let result: number[] | Uint32Array | Int32Array; + if(db.aiModel === 'openrouter' || db.aiModel === 'reverse_proxy'){ switch(db.customTokenizer){ case 'mistral': - return await tokenizeWebTokenizers(data, 'mistral') + result = await tokenizeWebTokenizers(data, 'mistral'); break; case 'llama': - return await tokenizeWebTokenizers(data, 'llama') + result = await tokenizeWebTokenizers(data, 'llama'); break; case 'novelai': - return await tokenizeWebTokenizers(data, 'novelai') + result = await tokenizeWebTokenizers(data, 'novelai'); break; case 'claude': - return await tokenizeWebTokenizers(data, 'claude') + result = await tokenizeWebTokenizers(data, 'claude'); break; case 'novellist': - return await tokenizeWebTokenizers(data, 'novellist') + result = await tokenizeWebTokenizers(data, 'novellist'); break; case 'llama3': - return await tokenizeWebTokenizers(data, 'llama') + result = await tokenizeWebTokenizers(data, 'llama'); break; case 'gemma': - return await gemmaTokenize(data) + result = await gemmaTokenize(data); break; case 'cohere': - return await tokenizeWebTokenizers(data, 'cohere') + result = await tokenizeWebTokenizers(data, 'cohere'); break; case 'deepseek': - return await tokenizeWebTokenizers(data, 'DeepSeek') + result = await tokenizeWebTokenizers(data, 'DeepSeek'); break; default: - return await tikJS(data, 'o200k_base') + result = await tikJS(data, 'o200k_base'); break; } } - - const modelInfo = getModelInfo(db.aiModel) - - if(db.aiModel === 'custom' && pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizer){ - const tokenizer = pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizer - switch(tokenizer){ + + if(db.aiModel === 'custom' && pluginTokenizer){ + switch(pluginTokenizer){ case 'mistral': - return await tokenizeWebTokenizers(data, 'mistral') + result = await tokenizeWebTokenizers(data, 'mistral'); break; case 'llama': - return await tokenizeWebTokenizers(data, 'llama') + result = await tokenizeWebTokenizers(data, 'llama'); break; case 'novelai': - return await tokenizeWebTokenizers(data, 'novelai') + result = await tokenizeWebTokenizers(data, 'novelai'); break; case 'claude': - return await tokenizeWebTokenizers(data, 'claude') + result = await tokenizeWebTokenizers(data, 'claude'); break; case 'novellist': - return await tokenizeWebTokenizers(data, 'novellist') + result = await tokenizeWebTokenizers(data, 'novellist'); break; case 'llama3': - return await tokenizeWebTokenizers(data, 'llama') + result = await tokenizeWebTokenizers(data, 'llama'); break; case 'gemma': - return await gemmaTokenize(data) + result = await gemmaTokenize(data); break; case 'cohere': - return await tokenizeWebTokenizers(data, 'cohere') + result = await tokenizeWebTokenizers(data, 'cohere'); break; case 'o200k_base': - return await tikJS(data, 'o200k_base') + result = await tikJS(data, 'o200k_base'); break; case 'cl100k_base': - return await tikJS(data, 'cl100k_base') + result = await tikJS(data, 'cl100k_base'); break; case 'custom': - return await pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizerFunc?.(data) ?? [0] + result = await pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizerFunc?.(data) ?? [0]; break; default: - return await tikJS(data, 'o200k_base') + result = await tikJS(data, 'o200k_base'); break; } - } - + } + if(modelInfo.tokenizer === LLMTokenizer.NovelList){ - const nv= await tokenizeWebTokenizers(data, 'novellist') - return nv - } - if(modelInfo.tokenizer === LLMTokenizer.Claude){ - return await tokenizeWebTokenizers(data, 'claude') - } - if(modelInfo.tokenizer === LLMTokenizer.NovelAI){ - return await tokenizeWebTokenizers(data, 'novelai') - } - if(modelInfo.tokenizer === LLMTokenizer.Mistral){ - return await tokenizeWebTokenizers(data, 'mistral') - } - if(modelInfo.tokenizer === LLMTokenizer.Llama){ - return await tokenizeWebTokenizers(data, 'llama') - } - if(modelInfo.tokenizer === LLMTokenizer.Local){ - return await tokenizeGGUFModel(data) - } - if(modelInfo.tokenizer === LLMTokenizer.tiktokenO200Base){ - return await tikJS(data, 'o200k_base') - } - if(modelInfo.tokenizer === LLMTokenizer.GoogleCloud && db.googleClaudeTokenizing){ - return await tokenizeGoogleCloud(data) - } - if(modelInfo.tokenizer === LLMTokenizer.Gemma || modelInfo.tokenizer === LLMTokenizer.GoogleCloud){ - return await gemmaTokenize(data) - } - if(modelInfo.tokenizer === LLMTokenizer.DeepSeek){ - return await tokenizeWebTokenizers(data, 'DeepSeek') - } - if(modelInfo.tokenizer === LLMTokenizer.Cohere){ - return await tokenizeWebTokenizers(data, 'cohere') + result = await tokenizeWebTokenizers(data, 'novellist'); + } else if(modelInfo.tokenizer === LLMTokenizer.Claude){ + result = await tokenizeWebTokenizers(data, 'claude'); + } else if(modelInfo.tokenizer === LLMTokenizer.NovelAI){ + result = await tokenizeWebTokenizers(data, 'novelai'); + } else if(modelInfo.tokenizer === LLMTokenizer.Mistral){ + result = await tokenizeWebTokenizers(data, 'mistral'); + } else if(modelInfo.tokenizer === LLMTokenizer.Llama){ + result = await tokenizeWebTokenizers(data, 'llama'); + } else if(modelInfo.tokenizer === LLMTokenizer.Local){ + result = await tokenizeGGUFModel(data); + } else if(modelInfo.tokenizer === LLMTokenizer.tiktokenO200Base){ + result = await tikJS(data, 'o200k_base'); + } else if(modelInfo.tokenizer === LLMTokenizer.GoogleCloud && db.googleClaudeTokenizing){ + result = await tokenizeGoogleCloud(data); + } else if(modelInfo.tokenizer === LLMTokenizer.Gemma || modelInfo.tokenizer === LLMTokenizer.GoogleCloud){ + result = await gemmaTokenize(data); + } else if(modelInfo.tokenizer === LLMTokenizer.DeepSeek){ + result = await tokenizeWebTokenizers(data, 'DeepSeek'); + } else if(modelInfo.tokenizer === LLMTokenizer.Cohere){ + result = await tokenizeWebTokenizers(data, 'cohere'); + } else { + result = await tikJS(data); } - return await tikJS(data) + if(db.useTokenizerCaching){ + encodeCache.set(cacheKey, result); + } + + return result; } type tokenizerType = 'novellist'|'claude'|'novelai'|'llama'|'mistral'|'llama3'|'gemma'|'cohere'|'googleCloud'|'DeepSeek' From 09228f3f867a2f05b24e5bf1163ab743e3913894 Mon Sep 17 00:00:00 2001 From: sub-hub <70351692+sub-hub@users.noreply.github.com> Date: Mon, 21 Apr 2025 13:34:01 +0900 Subject: [PATCH 10/13] Fix: Correct tokenize flow in tokenizer encode function --- src/ts/tokenizer.ts | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/ts/tokenizer.ts b/src/ts/tokenizer.ts index b553ba27..1e433534 100644 --- a/src/ts/tokenizer.ts +++ b/src/ts/tokenizer.ts @@ -89,9 +89,7 @@ export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Arr default: result = await tikJS(data, 'o200k_base'); break; } - } - - if(db.aiModel === 'custom' && pluginTokenizer){ + } else if (db.aiModel === 'custom' && pluginTokenizer) { switch(pluginTokenizer){ case 'mistral': result = await tokenizeWebTokenizers(data, 'mistral'); break; @@ -120,32 +118,34 @@ export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Arr } } - if(modelInfo.tokenizer === LLMTokenizer.NovelList){ - result = await tokenizeWebTokenizers(data, 'novellist'); - } else if(modelInfo.tokenizer === LLMTokenizer.Claude){ - result = await tokenizeWebTokenizers(data, 'claude'); - } else if(modelInfo.tokenizer === LLMTokenizer.NovelAI){ - result = await tokenizeWebTokenizers(data, 'novelai'); - } else if(modelInfo.tokenizer === LLMTokenizer.Mistral){ - result = await tokenizeWebTokenizers(data, 'mistral'); - } else if(modelInfo.tokenizer === LLMTokenizer.Llama){ - result = await tokenizeWebTokenizers(data, 'llama'); - } else if(modelInfo.tokenizer === LLMTokenizer.Local){ - result = await tokenizeGGUFModel(data); - } else if(modelInfo.tokenizer === LLMTokenizer.tiktokenO200Base){ - result = await tikJS(data, 'o200k_base'); - } else if(modelInfo.tokenizer === LLMTokenizer.GoogleCloud && db.googleClaudeTokenizing){ - result = await tokenizeGoogleCloud(data); - } else if(modelInfo.tokenizer === LLMTokenizer.Gemma || modelInfo.tokenizer === LLMTokenizer.GoogleCloud){ - result = await gemmaTokenize(data); - } else if(modelInfo.tokenizer === LLMTokenizer.DeepSeek){ - result = await tokenizeWebTokenizers(data, 'DeepSeek'); - } else if(modelInfo.tokenizer === LLMTokenizer.Cohere){ - result = await tokenizeWebTokenizers(data, 'cohere'); - } else { - result = await tikJS(data); + // Fallback + if (result === undefined) { + if(modelInfo.tokenizer === LLMTokenizer.NovelList){ + result = await tokenizeWebTokenizers(data, 'novellist'); + } else if(modelInfo.tokenizer === LLMTokenizer.Claude){ + result = await tokenizeWebTokenizers(data, 'claude'); + } else if(modelInfo.tokenizer === LLMTokenizer.NovelAI){ + result = await tokenizeWebTokenizers(data, 'novelai'); + } else if(modelInfo.tokenizer === LLMTokenizer.Mistral){ + result = await tokenizeWebTokenizers(data, 'mistral'); + } else if(modelInfo.tokenizer === LLMTokenizer.Llama){ + result = await tokenizeWebTokenizers(data, 'llama'); + } else if(modelInfo.tokenizer === LLMTokenizer.Local){ + result = await tokenizeGGUFModel(data); + } else if(modelInfo.tokenizer === LLMTokenizer.tiktokenO200Base){ + result = await tikJS(data, 'o200k_base'); + } else if(modelInfo.tokenizer === LLMTokenizer.GoogleCloud && db.googleClaudeTokenizing){ + result = await tokenizeGoogleCloud(data); + } else if(modelInfo.tokenizer === LLMTokenizer.Gemma || modelInfo.tokenizer === LLMTokenizer.GoogleCloud){ + result = await gemmaTokenize(data); + } else if(modelInfo.tokenizer === LLMTokenizer.DeepSeek){ + result = await tokenizeWebTokenizers(data, 'DeepSeek'); + } else if(modelInfo.tokenizer === LLMTokenizer.Cohere){ + result = await tokenizeWebTokenizers(data, 'cohere'); + } else { + result = await tikJS(data); + } } - if(db.useTokenizerCaching){ encodeCache.set(cacheKey, result); } From 2a36743cb6966b3a193dbb692bde6acabf547c7f Mon Sep 17 00:00:00 2001 From: sub-hub <70351692+sub-hub@users.noreply.github.com> Date: Mon, 21 Apr 2025 14:57:19 +0900 Subject: [PATCH 11/13] Fix: Potential problem in tikJS function --- src/ts/tokenizer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ts/tokenizer.ts b/src/ts/tokenizer.ts index 1e433534..25fb442d 100644 --- a/src/ts/tokenizer.ts +++ b/src/ts/tokenizer.ts @@ -209,6 +209,7 @@ async function gemmaTokenize(text:string) { async function tikJS(text:string, model='cl100k_base') { if(!tikParser || lastTikModel !== model){ + tikParser?.free() if(model === 'cl100k_base'){ const {Tiktoken} = await import('@dqbd/tiktoken') const cl100k_base = await import("@dqbd/tiktoken/encoders/cl100k_base.json"); From 0f70c5404abb1e817b6877ec433e903d109b6ab2 Mon Sep 17 00:00:00 2001 From: TiamaTiramisu Date: Mon, 21 Apr 2025 11:42:42 +0900 Subject: [PATCH 12/13] fix: avoid flatMap crash when output.images is undefined --- src/ts/process/stableDiff.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts/process/stableDiff.ts b/src/ts/process/stableDiff.ts index fb24033a..b1c09b1e 100644 --- a/src/ts/process/stableDiff.ts +++ b/src/ts/process/stableDiff.ts @@ -435,7 +435,7 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n } await new Promise(r => setTimeout(r, 1000)) } // Check history until the generation is complete. - const genImgInfo = Object.values(item.outputs).flatMap((output: any) => output.images)[0]; + const genImgInfo = Object.values(item.outputs).flatMap((output: any) => output.images || [])[0]; const imgResponse = await fetchNative(createUrl('/view', { filename: genImgInfo.filename, From a899a02d301eb5296f37545c541a48e4f9a03c39 Mon Sep 17 00:00:00 2001 From: niceandneat Date: Mon, 28 Apr 2025 01:19:47 +0900 Subject: [PATCH 13/13] Add nai cfg_rescale, noise_schedule options settings --- src/lib/Setting/Pages/OtherBotSettings.svelte | 13 +++++++++++++ src/ts/process/stableDiff.ts | 4 ++-- src/ts/storage/database.svelte.ts | 4 ++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/lib/Setting/Pages/OtherBotSettings.svelte b/src/lib/Setting/Pages/OtherBotSettings.svelte index 13b8e9bd..474eb59b 100644 --- a/src/lib/Setting/Pages/OtherBotSettings.svelte +++ b/src/lib/Setting/Pages/OtherBotSettings.svelte @@ -26,8 +26,10 @@ width: 512, height: 512, sampler: 'k_euler', + noise_schedule: 'native', steps: 100, scale: 1, + cfg_rescale: 0, sm: false, sm_dyn: false, strength: 0.5, @@ -245,10 +247,21 @@ {/if} + Noise Schedule + + Choose... + native + karras + exponential + polyexponential + + steps CFG scale + CFG rescale + {#if !DBState.db.NAII2I || DBState.db.NAIImgConfig.sampler !== 'ddim_v3'} diff --git a/src/ts/process/stableDiff.ts b/src/ts/process/stableDiff.ts index fb24033a..34ed094e 100644 --- a/src/ts/process/stableDiff.ts +++ b/src/ts/process/stableDiff.ts @@ -132,7 +132,7 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n "parameters": { "params_version": 3, "add_original_image": true, - "cfg_rescale": 0, + "cfg_rescale": db.NAIImgConfig.cfg_rescale, "controlnet_strength": 1, "dynamic_thresholding": false, "n_samples": 1, @@ -145,7 +145,7 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n "sm": false, "sm_dyn": false, "noise": db.NAIImgConfig.noise, - "noise_schedule": "native", + "noise_schedule": db.NAIImgConfig.noise_schedule, "strength": db.NAIImgConfig.strength, "ucPreset": 3, "uncond_scale": 1, diff --git a/src/ts/storage/database.svelte.ts b/src/ts/storage/database.svelte.ts index 1c0f6aad..49d9a5a8 100644 --- a/src/ts/storage/database.svelte.ts +++ b/src/ts/storage/database.svelte.ts @@ -255,8 +255,10 @@ export function setDatabase(data:Database){ width:512, height:768, sampler:"k_dpmpp_sde", + noise_schedule:"native", steps:28, scale:5, + cfg_rescale: 0, sm:true, sm_dyn:false, noise:0.0, @@ -1408,8 +1410,10 @@ export interface NAIImgConfig{ width:number, height:number, sampler:string, + noise_schedule:string, steps:number, scale:number, + cfg_rescale:number, sm:boolean, sm_dyn:boolean, noise:number,