From 7cd49fc8c36895923f8eaec500d1ea57355a9304 Mon Sep 17 00:00:00 2001 From: Kwaroran Date: Fri, 20 Dec 2024 23:45:07 +0900 Subject: [PATCH] Add V2 plugin --- plugins.md | 159 ++++++++++++++++++++++++++ src-tauri/src/main.rs | 4 +- src-tauri/tauri.conf.json | 2 +- src/lang/cn.ts | 2 +- src/lang/de.ts | 2 +- src/lang/en.ts | 2 +- src/lang/es.ts | 2 +- src/lang/ko.ts | 2 +- src/lang/vi.ts | 2 +- src/lang/zh-Hant.ts | 2 +- src/ts/globalApi.svelte.ts | 57 ++++++++-- src/ts/plugins/plugins.ts | 180 ++++++++++++++++++++++++++++-- src/ts/process/index.svelte.ts | 1 + src/ts/process/request.ts | 28 ++++- src/ts/process/scripts.ts | 10 ++ src/ts/storage/database.svelte.ts | 3 +- version.json | 2 +- 17 files changed, 426 insertions(+), 34 deletions(-) create mode 100644 plugins.md diff --git a/plugins.md b/plugins.md new file mode 100644 index 00000000..f4ad9b72 --- /dev/null +++ b/plugins.md @@ -0,0 +1,159 @@ + +# Plugins + +RisuAI uses a plugin system to allow for easy extension of the functionality. + +## Creating a Plugin + +A plugin is a js file with a header. for example: + +```js +//@name exampleplugin +//display-name: Example Plugin + +// Plugin code here +``` + +## Header Fields + +- `@name ` - The name of the plugin. This is used to identify the plugin. required. +- `@display-name ` - The display name of the plugin. This is used to display the plugin in the UI. +- `@arg ` Argument definition. This is used to define the arguments that the plugin takes. The type can be `int` or `string`. + +## API Reference + + +### `risuFetch(url: string, arg: GlobalFetchArgs = {}): Promise` + +> Note: `nativeFetch` is recommended for fetching URLs with POST request, as it has the same functionality as `risuFetch`, but with a similar API to `fetch` with more predictable behavior. + +Fetches a URL with a native API, which doesn't have CORS restrictions. + +#### Arguments + +- `url: string` - The URL to fetch. +- `arg: GlobalFetchArgs` - The fetch arguments. + - `body: string|Object` - The body to send with the request. if it's an object, it will be converted to JSON. + - `headers: Record` - The headers to send with the request. + - `method: string` - The method to use for the request `GET` and `POST` are supported. Default: `POST`. + - `abortSignal: AbortSignal` - The signal to use for aborting the request. + - `rawResponse: boolean` - If true, the response will be returned as Uint8Array. Default: `false`. + +#### Returns + +- `Promise` - The fetch result. + - `ok: boolean` - If the request was successful. + - `data: any` - The response data which is parsed JSON if possible. if `rawResponse` is true, it will be a Uint8Array. + - `headers: Record` - The response headers. + +### `nativeFetch(url: string, arg: NativeFetchArg = {}): Promise` + +Fetches a URL with the native fetch API, which has CORS restrictions. + +#### Arguments + +- `url: string` - The URL to fetch. +- `arg: NativeFetchArg` - The fetch arguments. + - `body: string|Uint8Array|ArrayBuffer` - The body to send with the request. + - `headers: Record` - The headers to send with the request. + - `method: string` - The method to use for the request. only `POST` is supported. Default: `POST`. + - `signal: AbortSignal` - The signal to use for aborting the request. + +#### Returns + +- `Promise` - The fetch result. + - `body: ReadableStream` - The response body. + - `headers: Headers` - The response headers. + - `status: number` - The response status. + - `json: () => Promise` - A function that returns a promise that resolves to the JSON representation of the response body. + - `text: () => Promise` - A function that returns a promise that resolves to the text representation of the response body. + +### `getArg(name: string): string|number` + +Gets the argument value by name. + +#### Arguments + +- `name: string` - The argument name. must be format of `::` like `exampleplugin::arg1`. + +#### Returns + +- `string|number` - The argument value. + +### `getChar(): character` + +Gets the current character. + +### `setChar(char: character): void` + +Sets the current character. + +### `addProvider(type: string, func: (arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string}>): void` + +Adds a provider to the plugin. + +#### Arguments + +- `type: string` - The provider name. +- `func: (arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string}>` - The provider function. + - `arg: PluginV2ProviderArgument` - The provider argument. + - `prompt_chat: Chat[]` - The chat prompt. + - `frequency_penalty?: number` - The frequency penalty. + - `min_p?: number` - The minimum p value. + - `presence_penalty?: number` - The presence penalty. + - `repetition_penalty?: number` - The repetition penalty. + - `top_k?: number` - The top k value. + - `top_p?: number` - The top p value. + - `temperature?: number` - The temperature value. + - `mode: string` - The mode. one of `model`, `submodel`, `memory`, `emotion`, `otherAx`, `translate` + - `Promise<{success:boolean,content:string}>` - The provider result. + - `success: boolean` - If the provider was successful. + - `content: string` - The provider content. + +### `addRisuScriptHandler(type: string, func: (content:string) => string|null|undefined|Promise): void` + +Adds a risu script handler to the plugin. + +#### Arguments + +- `type: string` - The handler type. one of `display`, `output`, `input`, `process` +- `func: (content:string) => string|null|undefined|Promise` - The handler function. + - `content: string` - The content to handle. + - `string|null|undefined|Promise` - The handler result. if it is a string or string promise, the data will be replaced with the result. + +### `removeRisuScriptHandler(type: string, func: (content:string) => string|null|undefined|Promise): void` + +Removes a risu script handler from the plugin. + +### `addRisuReplacer(type: string, func: ReplacerFunction): void` + +Adds a risu replacer to the plugin. + +#### Arguments + +- `type: string` - The replacer type. one of `beforeRequest`, `afterRequest`. +- `func: ReplacerFunction` - The replacer function. vary depending on the type. + - If the type is `afterRequest`, the function should be `(content: string) => string`. + - If the type is `beforeRequest`, the function should be `(content: Chat[]) => Chat[]`. + +### `removeRisuReplacer(type: string, func: ReplacerFunction): void` + +Removes a risu replacer from the plugin. + +### `onUnload(func: () => void): void` + +Adds an unload handler to the plugin. + + +## Migration from Plugin V1 + +The plugin system has been updated to V2. The following changes have been made: + - Now runs in same context as the main script rather than in a sandbox, making it accessible to the main script and DOM. + - Added `nativeFetch`, `addRisuScriptHandler`, `removeRisuScriptHandler`, `addRisuReplacer`, `removeRisuReplacer`, `onUnload` functions. + - `method`, `abortSignal`, `rawResponse` arguments has been added to `risuFetch`. + - `min_p`, `top_k`, `top_p`, `mode` arguments has been added to `addProvider`. + - `bias` argument has been removed from `addProvider`. however for compatibility, it still calls with empty array. + - Now plugin doesn't automatically terminates itself. you have to manually unload the plugin using `onUnload` function. + - `addCharaJs` function has been removed. use `addRisuScriptHandler` instead. + - Many security restrictions have been removed. + - `@risu-name`, `@risu-display-name`, `@risu-arg` headers has been removed. use `@name`, `@display-name`, `@arg` instead. if it's not present, it will be ran as V1 plugin. \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 291a28c6..652510aa 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -357,12 +357,14 @@ async fn streamed_fetch( return format!(r#"{{"success":false,"body":"Invalid header JSON"}}"#); } + let body_decoded = general_purpose::STANDARD.decode(body.as_bytes()).unwrap(); + let client = reqwest::Client::new(); let response = client .post(&url) .headers(headers) .timeout(Duration::from_secs(240)) - .body(body) + .body(body_decoded) .send() .await; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0b39f0a6..e84f2f19 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -29,7 +29,7 @@ }, "productName": "RisuAI", "mainBinaryName": "RisuAI", - "version": "143.9.1", + "version": "144.0.0", "identifier": "co.aiclient.risu", "plugins": { "updater": { diff --git a/src/lang/cn.ts b/src/lang/cn.ts index f24a5f20..008ddce3 100644 --- a/src/lang/cn.ts +++ b/src/lang/cn.ts @@ -298,7 +298,7 @@ export const languageChinese = { "singleView": "单角色模式", "SpacedView": "多角色模式", "emphasizedView": "双角色模式", - "pluginWarn": "插件可在隔离环境中运行,但安装恶意插件可能导致问题。", + "pluginWarn": "但安装恶意插件可能导致问题。", "createGroupImg": "产生群组头像", "waifuWidth": "角色对话框宽度", "savebackup": "备份至 Google", diff --git a/src/lang/de.ts b/src/lang/de.ts index 8cf4e451..8d0d8bc6 100644 --- a/src/lang/de.ts +++ b/src/lang/de.ts @@ -210,7 +210,7 @@ export const languageGerman = { singleView: "Einzelansicht", SpacedView: "Mehrere Charakteransicht", emphasizedView: "Doppelte Charakteransicht", - pluginWarn: "Plugins werden in einer isolierten Umgebung ausgeführten, aber das Installieren von Plugins unbekannter Herkunft könnte Probleme verursachen oder sogar schädlichen Code enthalten", + pluginWarn: "Installieren von Plugins unbekannter Herkunft könnte Probleme verursachen oder sogar schädlichen Code enthalten", createGroupImg: "Gruppenicon generieren", waifuWidth: "Breite des Waifu Chat-Bereichs", savebackup: "Erstellen und laden Sie ein Backup auf Google hoch", diff --git a/src/lang/en.ts b/src/lang/en.ts index 6e621293..e51b5b18 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -341,7 +341,7 @@ export const languageEnglish = { singleView: "Single View", SpacedView: "Multiple Character View", emphasizedView: "Double Character View", - pluginWarn: "Plugins run in an isolated environment, but installing malicious plugins can cause problems.", + pluginWarn: "Installing malicious plugins can cause problems.", createGroupImg: "Generate group icon", waifuWidth: "Waifu Chat Width", savebackup: "Save Backup to google", diff --git a/src/lang/es.ts b/src/lang/es.ts index 5d351e62..9d36fbcf 100644 --- a/src/lang/es.ts +++ b/src/lang/es.ts @@ -260,7 +260,7 @@ export const languageSpanish = { singleView: "Vista Única", SpacedView: "Vista de Múltiples Personajes", emphasizedView: "Vista de Personajes Doble", - pluginWarn: "Los plugins se ejecutan en un entorno aislado, pero instalar plugins maliciosos puede causar problemas.", + pluginWarn: "Instalar plugins maliciosos puede causar problemas.", createGroupImg: "Generar icono de grupo", waifuWidth: "Ancho del Chat Waifu", savebackup: "Guardar Respaldo en Google", diff --git a/src/lang/ko.ts b/src/lang/ko.ts index a9b055d6..cb8ae195 100644 --- a/src/lang/ko.ts +++ b/src/lang/ko.ts @@ -293,7 +293,7 @@ export const languageKorean = { "singleView": "싱글", "SpacedView": "멀티플", "emphasizedView": "더블", - "pluginWarn": "플러그인은 기본적으로 분리된 환경에서 실행되지만, 악성 플러그인 설치 시 문제가 생길 수 있습니다.", + "pluginWarn": "악성 플러그인 설치 시 문제가 생길 수 있습니다.", "createGroupImg": "그룹 아이콘 자동생성", "waifuWidth": "Waifu 채팅창 넓이", "savebackup": "구글 백업 저장", diff --git a/src/lang/vi.ts b/src/lang/vi.ts index 0b1bb5cf..6d7133a9 100644 --- a/src/lang/vi.ts +++ b/src/lang/vi.ts @@ -181,7 +181,7 @@ export const LanguageVietnamese = { "singleView": "Chế độ xem đơn", "SpacedView": "Xem nhiều ký tự", "emphasizedView": "Chế độ xem nhân vật đôi", - "pluginWarn": "Các plugin chạy trong môi trường biệt lập nhưng việc cài đặt các plugin độc hại có thể gây ra sự cố.", + "pluginWarn": "Các plugin có thể gây ra sự cố khi cài đặt các plugin độc hại.", "createGroupImg": "Tạo biểu tượng nhóm", "waifuWidth": "Chiều rộng trò chuyện Waifu", "savebackup": "Lưu Sao lưu vào google", diff --git a/src/lang/zh-Hant.ts b/src/lang/zh-Hant.ts index dd4811bb..5d074e7d 100644 --- a/src/lang/zh-Hant.ts +++ b/src/lang/zh-Hant.ts @@ -300,7 +300,7 @@ export const languageChineseTraditional = { "singleView": "單角色模式", "SpacedView": "多角色模式", "emphasizedView": "雙角色模式", - "pluginWarn": "外掛程式可在隔離環境中運行,但安裝惡意外掛可能導致問題。", + "pluginWarn": "但安裝惡意外掛可能導致問題。", "createGroupImg": "產生群組頭像", "waifuWidth": "角色對話框寬度", "savebackup": "備份至 Google", diff --git a/src/ts/globalApi.svelte.ts b/src/ts/globalApi.svelte.ts index 1e971a5e..d2ed95bb 100644 --- a/src/ts/globalApi.svelte.ts +++ b/src/ts/globalApi.svelte.ts @@ -1809,18 +1809,53 @@ const pipeFetchLog = (fetchLogIndex: number, readableStream: ReadableStream; headers: Headers; status: number }> { +}):Promise<{ + body: ReadableStream; + headers: Headers; + status: number; + json: () => Promise; + text: () => Promise; +}> { + + const jsonizer = (body:ReadableStream) => { + return async () => { + const text = await textifyReadableStream(body) + return JSON.parse(text) + } + } + const textizer = (body:ReadableStream) => { + return async () => { + const text = await textifyReadableStream(body) + return text + } + } + let headers = arg.headers ?? {} + let realBody:Uint8Array + + if(typeof arg.body === 'string'){ + realBody = new TextEncoder().encode(arg.body) + } + else if(arg.body instanceof Uint8Array){ + realBody = arg.body + } + else if(arg.body instanceof ArrayBuffer){ + realBody = new Uint8Array(arg.body) + } + else{ + throw new Error('Invalid body type') + } + const db = getDatabase() let throughProxy = (!isTauri) && (!isNodeServer) && (!db.usePlainFetch) let fetchLogIndex = addFetchLog({ - body: arg.body, + body: new TextDecoder().decode(realBody), headers: arg.headers, response: 'Streamed Fetch', success: true, @@ -1849,7 +1884,7 @@ export async function fetchNative(url:string, arg:{ id: fetchId, url: url, headers: JSON.stringify(headers), - body: arg.body, + body: Buffer.from(realBody).toString('base64'), }).then((res) => { try { const parsedRes = JSON.parse(res as string) @@ -1868,7 +1903,7 @@ export async function fetchNative(url:string, arg:{ id: fetchId, url: url, headers: headers, - body: Buffer.from(arg.body).toString('base64'), + body: Buffer.from(realBody).toString('base64'), }).then((res) => { if(!res.success){ error = res.error @@ -1918,14 +1953,16 @@ export async function fetchNative(url:string, arg:{ return { body: readableStream, headers: new Headers(resHeaders), - status: status + status: status, + json: jsonizer(readableStream), + text: textizer(readableStream) } } else if(throughProxy){ const r = await fetch(hubURL + `/proxy2`, { - body: arg.body, + body: realBody, headers: arg.useRisuTk ? { "risu-header": encodeURIComponent(JSON.stringify(headers)), "risu-url": encodeURIComponent(url), @@ -1943,12 +1980,14 @@ export async function fetchNative(url:string, arg:{ return { body: pipeFetchLog(fetchLogIndex, r.body), headers: r.headers, - status: r.status + status: r.status, + json: jsonizer(r.body), + text: textizer(r.body) } } else{ return await fetch(url, { - body: arg.body, + body: realBody, headers: headers, method: arg.method, signal: arg.signal diff --git a/src/ts/plugins/plugins.ts b/src/ts/plugins/plugins.ts index 07d4e1a0..5553c87d 100644 --- a/src/ts/plugins/plugins.ts +++ b/src/ts/plugins/plugins.ts @@ -1,21 +1,16 @@ import { get, writable } from "svelte/store"; import { language } from "../../lang"; import { alertError } from "../alert"; -import { getDatabase, setDatabaseLite } from "../storage/database.svelte"; +import { getCurrentCharacter, getDatabase, setDatabaseLite } from "../storage/database.svelte"; import { checkNullish, selectSingleFile, sleep } from "../util"; import type { OpenAIChat } from "../process/index.svelte"; -import { globalFetch } from "../globalApi.svelte"; +import { fetchNative, globalFetch } from "../globalApi.svelte"; import { selectedCharID } from "../stores.svelte"; import { addAdditionalCharaJS } from "./embedscript"; +import type { ScriptMode } from "../process/scripts"; export const customProviderStore = writable([] as string[]) -interface PluginRequest{ - url: string - header?:{[key:string]:string} - body: any, - res: string -} interface ProviderPlugin{ name:string @@ -23,6 +18,7 @@ interface ProviderPlugin{ script:string arguments:{[key:string]:'int'|'string'|string[]} realArg:{[key:string]:number|string} + version?:1|2 } export type RisuPlugin = ProviderPlugin @@ -37,6 +33,7 @@ export async function importPlugin(){ const jsFile = Buffer.from(f.data).toString('utf-8').replace(/^\uFEFF/gm, ""); const splitedJs = jsFile.split('\n') let name = '' + let version:1|2 = 1 let displayName:string = undefined let arg:{[key:string]:'int'|'string'|string[]} = {} let realArg:{[key:string]:number|string} = {} @@ -49,15 +46,32 @@ export async function importPlugin(){ } name = provied.trim() } + if(line.startsWith('//@name')){ + const provied = line.slice(7) + if(provied === ''){ + alertError('plugin name must be longer than "", did you put it correctly?') + return + } + version = 2 + name = provied.trim() + } if(line.startsWith('//@risu-display-name')){ const provied = line.slice('//@risu-display-name'.length + 1) if(provied === ''){ alertError('plugin display name must be longer than "", did you put it correctly?') return } - name = provied.trim() + displayName = provied.trim() } - if(line.startsWith('//@risu-arg')){ + if(line.startsWith('//@display-name')){ + const provied = line.slice('//@display-name'.length + 1) + if(provied === ''){ + alertError('plugin display name must be longer than "", did you put it correctly?') + return + } + displayName = provied.trim() + } + if(line.startsWith('//@risu-arg') || line.startsWith('//@arg')){ const provied = line.trim().split(' ') if(provied.length < 3){ alertError('plugin argument is incorrect, did you put space in argument name?') @@ -90,7 +104,8 @@ export async function importPlugin(){ script: jsFile, realArg: realArg, arguments: arg, - displayName: displayName + displayName: displayName, + version: version } db.plugins ??= [] @@ -124,11 +139,18 @@ let pluginTranslator = false export async function loadPlugins() { let db = getDatabase() + if(pluginWorker){ pluginWorker.terminate() pluginWorker = null } - if(db.plugins.length > 0){ + + const plugins = safeStructuredClone(db.plugins).filter((a:RisuPlugin) => a.version === 1) + const pluginV2 = safeStructuredClone(db.plugins).filter((a:RisuPlugin) => a.version === 2) + + await loadV2Plugin(pluginV2) + + if(plugins.length > 0){ const da = await fetch("/pluginApi.js") const pluginApiString = await da.text() @@ -267,6 +289,140 @@ export async function loadPlugins() { } } +type PluginV2ProviderArgument = { + prompt_chat: OpenAIChat[], + frequency_penalty: number + min_p: number + presence_penalty: number + repetition_penalty: number + top_k: number + top_p: number + temperature: number + mode: string +} + +type EditFunction = (content:string) => string|null|undefined|Promise +type ReplacerFunction = (content:OpenAIChat[], type:string) => OpenAIChat[]|Promise + +export const pluginV2 = { + providers: new Map Promise<{success:boolean,content:string}> >(), + editdisplay: new Set(), + editoutput: new Set(), + editprocess: new Set(), + editinput: new Set(), + replacerbeforeRequest: new Set(), + replacerafterRequest: new Set<(content:string, type:string) => string|Promise>(), + unload: new Set<() => void|Promise>(), + loaded: false +} + +export async function loadV2Plugin(plugins:RisuPlugin[]){ + + if(pluginV2.loaded){ + for(const unload of pluginV2.unload){ + await unload() + } + + pluginV2.providers.clear() + pluginV2.editdisplay.clear() + pluginV2.editoutput.clear() + pluginV2.editprocess.clear() + pluginV2.editinput.clear() + } + + pluginV2.loaded = true + + globalThis.__pluginApis__ = { + risuFetch: globalFetch, + nativeFetch: fetchNative, + getArg: (arg:string) => { + const [name, realArg] = arg.split('::') + for(const plug of plugins){ + if(plug.name === name){ + return plug.realArg[realArg] + } + } + }, + getChar: () => { + return getCurrentCharacter() + }, + setChar: (char:any) => { + const db = getDatabase() + const charid = get(selectedCharID) + db.characters[charid] = char + setDatabaseLite(db) + }, + addProvider: (name:string, func:(arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string}>) => { + let provs = get(customProviderStore) + provs.push(name) + pluginV2.providers.set(name, func) + customProviderStore.set(provs) + }, + addRisuScriptHandler: (name:ScriptMode, func:EditFunction) => { + if(pluginV2['edit' + name]){ + pluginV2['edit' + name].add(func) + } + else{ + throw (`script handler named ${name} not found`) + } + }, + removeRisuScriptHandler: (name:ScriptMode, func:EditFunction) => { + if(pluginV2['edit' + name]){ + pluginV2['edit' + name].delete(func) + } + else{ + throw (`script handler named ${name} not found`) + } + }, + addRisuReplacer: (name:string, func:ReplacerFunction) => { + if(pluginV2['replacer' + name]){ + pluginV2['replacer' + name].add(func) + } + else{ + throw (`replacer handler named ${name} not found`) + } + }, + removeRisuReplacer: (name:string, func:ReplacerFunction) => { + if(pluginV2['replacer' + name]){ + pluginV2['replacer' + name].delete(func) + } + else{ + throw (`replacer handler named ${name} not found`) + } + }, + onUnload: (func:() => void|Promise) => { + pluginV2.unload.add(func) + } + } + + for(const plugin of plugins){ + const data = plugin.script + + const realScript = `(async () => { + const risuFetch = globalThis.__pluginApis__.risuFetch + const nativeFetch = globalThis.__pluginApis__.nativeFetch + const getArg = globalThis.__pluginApis__.getArg + const printLog = globalThis.__pluginApis__.printLog + const getChar = globalThis.__pluginApis__.getChar + const setChar = globalThis.__pluginApis__.setChar + const addProvider = globalThis.__pluginApis__.addProvider + const addRisuEventHandler = globalThis.__pluginApis__.addRisuEventHandler + const onUnload = globalThis.__pluginApis__.onUnload + + ${data} + })();` + + try { + eval(realScript) + } catch (error) { + console.error(error) + } + + console.log('Loaded V2 Plugin', plugin.name) + + } +} + export async function translatorPlugin(text:string, from:string, to:string) { if(!pluginTranslator){ return false diff --git a/src/ts/process/index.svelte.ts b/src/ts/process/index.svelte.ts index e57f8788..f9c108c7 100644 --- a/src/ts/process/index.svelte.ts +++ b/src/ts/process/index.svelte.ts @@ -30,6 +30,7 @@ import { hypaMemoryV2 } from "./memory/hypav2"; import { runLuaEditTrigger } from "./lua"; import { parseChatML } from "../parser.svelte"; import { getModelInfo, LLMFlags } from "../model/modellist"; +import { pluginV2 } from "../plugins/plugins"; export interface OpenAIChat{ role: 'system'|'user'|'assistant'|'function' diff --git a/src/ts/process/request.ts b/src/ts/process/request.ts index f2a674f8..3aa9d242 100644 --- a/src/ts/process/request.ts +++ b/src/ts/process/request.ts @@ -1,6 +1,6 @@ import type { MultiModal, OpenAIChat, OpenAIChatFull } from "./index.svelte"; import { getCurrentCharacter, getDatabase, setDatabase, type character } from "../storage/database.svelte"; -import { pluginProcess } from "../plugins/plugins"; +import { pluginProcess, pluginV2 } from "../plugins/plugins"; import { language } from "../../lang"; import { stringlizeAINChat, getStopStrings, unstringlizeAIN, unstringlizeChat } from "./stringlize"; import { addFetchLog, fetchNative, globalFetch, isNodeServer, isTauri, textifyReadableStream } from "../globalApi.svelte"; @@ -209,7 +209,22 @@ export async function requestChatData(arg:requestDataArgument, model:ModelModeEx const db = getDatabase() let trys = 0 while(true){ + + if(pluginV2.replacerbeforeRequest.size > 0){ + for(const replacer of pluginV2.replacerbeforeRequest){ + arg.formated = await replacer(arg.formated, model) + } + } + const da = await requestChatDataMain(arg, model, abortSignal) + + if(da.type === 'success' && pluginV2.replacerafterRequest.size > 0){ + for(const replacer of pluginV2.replacerafterRequest){ + da.result = await replacer(da.result, model) + } + } + + if(da.type !== 'fail' || da.noRetry){ return da } @@ -1379,7 +1394,15 @@ async function requestPlugin(arg:RequestDataArgumentExtended):Promise 0){ + for(const plugin of pluginV2[mode]){ + const res = await plugin(data) + if(res !== null && res !== undefined){ + data = res + } + } + } + if(scripts.length === 0){ cacheScript(scripts, originalData, data, mode) return {data, emoChanged} diff --git a/src/ts/storage/database.svelte.ts b/src/ts/storage/database.svelte.ts index 7ce18d48..7773595f 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 = "143.9.1" +export let appVer = "144.0.0" export let webAppSubVer = '' @@ -857,6 +857,7 @@ export interface Database{ geminiStream?:boolean assetMaxDifference:number menuSideBar:boolean + pluginV2: RisuPlugin[] } interface SeparateParameters{ diff --git a/version.json b/version.json index a45d30d0..ecc9e234 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{"version":"143.9.1"} \ No newline at end of file +{"version":"144.0.0"} \ No newline at end of file