Add V2 plugin
This commit is contained in:
159
plugins.md
Normal file
159
plugins.md
Normal file
@@ -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 <name>` - The name of the plugin. This is used to identify the plugin. required.
|
||||||
|
- `@display-name <display_name>` - The display name of the plugin. This is used to display the plugin in the UI.
|
||||||
|
- `@arg <name> <type>` 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<GlobalFetchResult>`
|
||||||
|
|
||||||
|
> 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<string, string>` - 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<GlobalFetchResult>` - 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<string, string>` - The response headers.
|
||||||
|
|
||||||
|
### `nativeFetch(url: string, arg: NativeFetchArg = {}): Promise<NativeFetchResults>`
|
||||||
|
|
||||||
|
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<string, string>` - 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<NativeFetchResults>` - The fetch result.
|
||||||
|
- `body: ReadableStream<Uint8Array>` - The response body.
|
||||||
|
- `headers: Headers` - The response headers.
|
||||||
|
- `status: number` - The response status.
|
||||||
|
- `json: () => Promise<any>` - A function that returns a promise that resolves to the JSON representation of the response body.
|
||||||
|
- `text: () => Promise<string>` - 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 `<plugin_name>::<arg_name>` 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<string|null|undefined>): 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<string|null|undefined>` - The handler function.
|
||||||
|
- `content: string` - The content to handle.
|
||||||
|
- `string|null|undefined|Promise<string|null|undefined>` - 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<string|null|undefined>): 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.
|
||||||
@@ -357,12 +357,14 @@ async fn streamed_fetch(
|
|||||||
return format!(r#"{{"success":false,"body":"Invalid header JSON"}}"#);
|
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 client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.headers(headers)
|
.headers(headers)
|
||||||
.timeout(Duration::from_secs(240))
|
.timeout(Duration::from_secs(240))
|
||||||
.body(body)
|
.body(body_decoded)
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"productName": "RisuAI",
|
"productName": "RisuAI",
|
||||||
"mainBinaryName": "RisuAI",
|
"mainBinaryName": "RisuAI",
|
||||||
"version": "143.9.1",
|
"version": "144.0.0",
|
||||||
"identifier": "co.aiclient.risu",
|
"identifier": "co.aiclient.risu",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ export const languageChinese = {
|
|||||||
"singleView": "单角色模式",
|
"singleView": "单角色模式",
|
||||||
"SpacedView": "多角色模式",
|
"SpacedView": "多角色模式",
|
||||||
"emphasizedView": "双角色模式",
|
"emphasizedView": "双角色模式",
|
||||||
"pluginWarn": "插件可在隔离环境中运行,但安装恶意插件可能导致问题。",
|
"pluginWarn": "但安装恶意插件可能导致问题。",
|
||||||
"createGroupImg": "产生群组头像",
|
"createGroupImg": "产生群组头像",
|
||||||
"waifuWidth": "角色对话框宽度",
|
"waifuWidth": "角色对话框宽度",
|
||||||
"savebackup": "备份至 Google",
|
"savebackup": "备份至 Google",
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export const languageGerman = {
|
|||||||
singleView: "Einzelansicht",
|
singleView: "Einzelansicht",
|
||||||
SpacedView: "Mehrere Charakteransicht",
|
SpacedView: "Mehrere Charakteransicht",
|
||||||
emphasizedView: "Doppelte 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",
|
createGroupImg: "Gruppenicon generieren",
|
||||||
waifuWidth: "Breite des Waifu Chat-Bereichs",
|
waifuWidth: "Breite des Waifu Chat-Bereichs",
|
||||||
savebackup: "Erstellen und laden Sie ein Backup auf Google hoch",
|
savebackup: "Erstellen und laden Sie ein Backup auf Google hoch",
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ export const languageEnglish = {
|
|||||||
singleView: "Single View",
|
singleView: "Single View",
|
||||||
SpacedView: "Multiple Character View",
|
SpacedView: "Multiple Character View",
|
||||||
emphasizedView: "Double 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",
|
createGroupImg: "Generate group icon",
|
||||||
waifuWidth: "Waifu Chat Width",
|
waifuWidth: "Waifu Chat Width",
|
||||||
savebackup: "Save Backup to google",
|
savebackup: "Save Backup to google",
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ export const languageSpanish = {
|
|||||||
singleView: "Vista Única",
|
singleView: "Vista Única",
|
||||||
SpacedView: "Vista de Múltiples Personajes",
|
SpacedView: "Vista de Múltiples Personajes",
|
||||||
emphasizedView: "Vista de Personajes Doble",
|
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",
|
createGroupImg: "Generar icono de grupo",
|
||||||
waifuWidth: "Ancho del Chat Waifu",
|
waifuWidth: "Ancho del Chat Waifu",
|
||||||
savebackup: "Guardar Respaldo en Google",
|
savebackup: "Guardar Respaldo en Google",
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ export const languageKorean = {
|
|||||||
"singleView": "싱글",
|
"singleView": "싱글",
|
||||||
"SpacedView": "멀티플",
|
"SpacedView": "멀티플",
|
||||||
"emphasizedView": "더블",
|
"emphasizedView": "더블",
|
||||||
"pluginWarn": "플러그인은 기본적으로 분리된 환경에서 실행되지만, 악성 플러그인 설치 시 문제가 생길 수 있습니다.",
|
"pluginWarn": "악성 플러그인 설치 시 문제가 생길 수 있습니다.",
|
||||||
"createGroupImg": "그룹 아이콘 자동생성",
|
"createGroupImg": "그룹 아이콘 자동생성",
|
||||||
"waifuWidth": "Waifu 채팅창 넓이",
|
"waifuWidth": "Waifu 채팅창 넓이",
|
||||||
"savebackup": "구글 백업 저장",
|
"savebackup": "구글 백업 저장",
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export const LanguageVietnamese = {
|
|||||||
"singleView": "Chế độ xem đơn",
|
"singleView": "Chế độ xem đơn",
|
||||||
"SpacedView": "Xem nhiều ký tự",
|
"SpacedView": "Xem nhiều ký tự",
|
||||||
"emphasizedView": "Chế độ xem nhân vật đôi",
|
"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",
|
"createGroupImg": "Tạo biểu tượng nhóm",
|
||||||
"waifuWidth": "Chiều rộng trò chuyện Waifu",
|
"waifuWidth": "Chiều rộng trò chuyện Waifu",
|
||||||
"savebackup": "Lưu Sao lưu vào google",
|
"savebackup": "Lưu Sao lưu vào google",
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ export const languageChineseTraditional = {
|
|||||||
"singleView": "單角色模式",
|
"singleView": "單角色模式",
|
||||||
"SpacedView": "多角色模式",
|
"SpacedView": "多角色模式",
|
||||||
"emphasizedView": "雙角色模式",
|
"emphasizedView": "雙角色模式",
|
||||||
"pluginWarn": "外掛程式可在隔離環境中運行,但安裝惡意外掛可能導致問題。",
|
"pluginWarn": "但安裝惡意外掛可能導致問題。",
|
||||||
"createGroupImg": "產生群組頭像",
|
"createGroupImg": "產生群組頭像",
|
||||||
"waifuWidth": "角色對話框寬度",
|
"waifuWidth": "角色對話框寬度",
|
||||||
"savebackup": "備份至 Google",
|
"savebackup": "備份至 Google",
|
||||||
|
|||||||
@@ -1809,18 +1809,53 @@ const pipeFetchLog = (fetchLogIndex: number, readableStream: ReadableStream<Uint
|
|||||||
* @throws {Error} - Throws an error if the request is aborted or if there is an error in the response.
|
* @throws {Error} - Throws an error if the request is aborted or if there is an error in the response.
|
||||||
*/
|
*/
|
||||||
export async function fetchNative(url:string, arg:{
|
export async function fetchNative(url:string, arg:{
|
||||||
body:string,
|
body:string|Uint8Array|ArrayBuffer,
|
||||||
headers?:{[key:string]:string},
|
headers?:{[key:string]:string},
|
||||||
method?:"POST",
|
method?:"POST",
|
||||||
signal?:AbortSignal,
|
signal?:AbortSignal,
|
||||||
useRisuTk?:boolean,
|
useRisuTk?:boolean,
|
||||||
chatId?:string
|
chatId?:string
|
||||||
}):Promise<{ body: ReadableStream<Uint8Array>; headers: Headers; status: number }> {
|
}):Promise<{
|
||||||
|
body: ReadableStream<Uint8Array>;
|
||||||
|
headers: Headers;
|
||||||
|
status: number;
|
||||||
|
json: () => Promise<any>;
|
||||||
|
text: () => Promise<string>;
|
||||||
|
}> {
|
||||||
|
|
||||||
|
const jsonizer = (body:ReadableStream<Uint8Array>) => {
|
||||||
|
return async () => {
|
||||||
|
const text = await textifyReadableStream(body)
|
||||||
|
return JSON.parse(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const textizer = (body:ReadableStream<Uint8Array>) => {
|
||||||
|
return async () => {
|
||||||
|
const text = await textifyReadableStream(body)
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let headers = arg.headers ?? {}
|
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()
|
const db = getDatabase()
|
||||||
let throughProxy = (!isTauri) && (!isNodeServer) && (!db.usePlainFetch)
|
let throughProxy = (!isTauri) && (!isNodeServer) && (!db.usePlainFetch)
|
||||||
let fetchLogIndex = addFetchLog({
|
let fetchLogIndex = addFetchLog({
|
||||||
body: arg.body,
|
body: new TextDecoder().decode(realBody),
|
||||||
headers: arg.headers,
|
headers: arg.headers,
|
||||||
response: 'Streamed Fetch',
|
response: 'Streamed Fetch',
|
||||||
success: true,
|
success: true,
|
||||||
@@ -1849,7 +1884,7 @@ export async function fetchNative(url:string, arg:{
|
|||||||
id: fetchId,
|
id: fetchId,
|
||||||
url: url,
|
url: url,
|
||||||
headers: JSON.stringify(headers),
|
headers: JSON.stringify(headers),
|
||||||
body: arg.body,
|
body: Buffer.from(realBody).toString('base64'),
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
try {
|
try {
|
||||||
const parsedRes = JSON.parse(res as string)
|
const parsedRes = JSON.parse(res as string)
|
||||||
@@ -1868,7 +1903,7 @@ export async function fetchNative(url:string, arg:{
|
|||||||
id: fetchId,
|
id: fetchId,
|
||||||
url: url,
|
url: url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: Buffer.from(arg.body).toString('base64'),
|
body: Buffer.from(realBody).toString('base64'),
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if(!res.success){
|
if(!res.success){
|
||||||
error = res.error
|
error = res.error
|
||||||
@@ -1918,14 +1953,16 @@ export async function fetchNative(url:string, arg:{
|
|||||||
return {
|
return {
|
||||||
body: readableStream,
|
body: readableStream,
|
||||||
headers: new Headers(resHeaders),
|
headers: new Headers(resHeaders),
|
||||||
status: status
|
status: status,
|
||||||
|
json: jsonizer(readableStream),
|
||||||
|
text: textizer(readableStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else if(throughProxy){
|
else if(throughProxy){
|
||||||
const r = await fetch(hubURL + `/proxy2`, {
|
const r = await fetch(hubURL + `/proxy2`, {
|
||||||
body: arg.body,
|
body: realBody,
|
||||||
headers: arg.useRisuTk ? {
|
headers: arg.useRisuTk ? {
|
||||||
"risu-header": encodeURIComponent(JSON.stringify(headers)),
|
"risu-header": encodeURIComponent(JSON.stringify(headers)),
|
||||||
"risu-url": encodeURIComponent(url),
|
"risu-url": encodeURIComponent(url),
|
||||||
@@ -1943,12 +1980,14 @@ export async function fetchNative(url:string, arg:{
|
|||||||
return {
|
return {
|
||||||
body: pipeFetchLog(fetchLogIndex, r.body),
|
body: pipeFetchLog(fetchLogIndex, r.body),
|
||||||
headers: r.headers,
|
headers: r.headers,
|
||||||
status: r.status
|
status: r.status,
|
||||||
|
json: jsonizer(r.body),
|
||||||
|
text: textizer(r.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return await fetch(url, {
|
return await fetch(url, {
|
||||||
body: arg.body,
|
body: realBody,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
method: arg.method,
|
method: arg.method,
|
||||||
signal: arg.signal
|
signal: arg.signal
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import { get, writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
import { language } from "../../lang";
|
import { language } from "../../lang";
|
||||||
import { alertError } from "../alert";
|
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 { checkNullish, selectSingleFile, sleep } from "../util";
|
||||||
import type { OpenAIChat } from "../process/index.svelte";
|
import type { OpenAIChat } from "../process/index.svelte";
|
||||||
import { globalFetch } from "../globalApi.svelte";
|
import { fetchNative, globalFetch } from "../globalApi.svelte";
|
||||||
import { selectedCharID } from "../stores.svelte";
|
import { selectedCharID } from "../stores.svelte";
|
||||||
import { addAdditionalCharaJS } from "./embedscript";
|
import { addAdditionalCharaJS } from "./embedscript";
|
||||||
|
import type { ScriptMode } from "../process/scripts";
|
||||||
|
|
||||||
export const customProviderStore = writable([] as string[])
|
export const customProviderStore = writable([] as string[])
|
||||||
|
|
||||||
interface PluginRequest{
|
|
||||||
url: string
|
|
||||||
header?:{[key:string]:string}
|
|
||||||
body: any,
|
|
||||||
res: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProviderPlugin{
|
interface ProviderPlugin{
|
||||||
name:string
|
name:string
|
||||||
@@ -23,6 +18,7 @@ interface ProviderPlugin{
|
|||||||
script:string
|
script:string
|
||||||
arguments:{[key:string]:'int'|'string'|string[]}
|
arguments:{[key:string]:'int'|'string'|string[]}
|
||||||
realArg:{[key:string]:number|string}
|
realArg:{[key:string]:number|string}
|
||||||
|
version?:1|2
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RisuPlugin = ProviderPlugin
|
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 jsFile = Buffer.from(f.data).toString('utf-8').replace(/^\uFEFF/gm, "");
|
||||||
const splitedJs = jsFile.split('\n')
|
const splitedJs = jsFile.split('\n')
|
||||||
let name = ''
|
let name = ''
|
||||||
|
let version:1|2 = 1
|
||||||
let displayName:string = undefined
|
let displayName:string = undefined
|
||||||
let arg:{[key:string]:'int'|'string'|string[]} = {}
|
let arg:{[key:string]:'int'|'string'|string[]} = {}
|
||||||
let realArg:{[key:string]:number|string} = {}
|
let realArg:{[key:string]:number|string} = {}
|
||||||
@@ -49,15 +46,32 @@ export async function importPlugin(){
|
|||||||
}
|
}
|
||||||
name = provied.trim()
|
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')){
|
if(line.startsWith('//@risu-display-name')){
|
||||||
const provied = line.slice('//@risu-display-name'.length + 1)
|
const provied = line.slice('//@risu-display-name'.length + 1)
|
||||||
if(provied === ''){
|
if(provied === ''){
|
||||||
alertError('plugin display name must be longer than "", did you put it correctly?')
|
alertError('plugin display name must be longer than "", did you put it correctly?')
|
||||||
return
|
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(' ')
|
const provied = line.trim().split(' ')
|
||||||
if(provied.length < 3){
|
if(provied.length < 3){
|
||||||
alertError('plugin argument is incorrect, did you put space in argument name?')
|
alertError('plugin argument is incorrect, did you put space in argument name?')
|
||||||
@@ -90,7 +104,8 @@ export async function importPlugin(){
|
|||||||
script: jsFile,
|
script: jsFile,
|
||||||
realArg: realArg,
|
realArg: realArg,
|
||||||
arguments: arg,
|
arguments: arg,
|
||||||
displayName: displayName
|
displayName: displayName,
|
||||||
|
version: version
|
||||||
}
|
}
|
||||||
|
|
||||||
db.plugins ??= []
|
db.plugins ??= []
|
||||||
@@ -124,11 +139,18 @@ let pluginTranslator = false
|
|||||||
|
|
||||||
export async function loadPlugins() {
|
export async function loadPlugins() {
|
||||||
let db = getDatabase()
|
let db = getDatabase()
|
||||||
|
|
||||||
if(pluginWorker){
|
if(pluginWorker){
|
||||||
pluginWorker.terminate()
|
pluginWorker.terminate()
|
||||||
pluginWorker = null
|
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 da = await fetch("/pluginApi.js")
|
||||||
const pluginApiString = await da.text()
|
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<string|null|undefined>
|
||||||
|
type ReplacerFunction = (content:OpenAIChat[], type:string) => OpenAIChat[]|Promise<OpenAIChat[]>
|
||||||
|
|
||||||
|
export const pluginV2 = {
|
||||||
|
providers: new Map<string, (arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string}> >(),
|
||||||
|
editdisplay: new Set<EditFunction>(),
|
||||||
|
editoutput: new Set<EditFunction>(),
|
||||||
|
editprocess: new Set<EditFunction>(),
|
||||||
|
editinput: new Set<EditFunction>(),
|
||||||
|
replacerbeforeRequest: new Set<ReplacerFunction>(),
|
||||||
|
replacerafterRequest: new Set<(content:string, type:string) => string|Promise<string>>(),
|
||||||
|
unload: new Set<() => void|Promise<void>>(),
|
||||||
|
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<void>) => {
|
||||||
|
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) {
|
export async function translatorPlugin(text:string, from:string, to:string) {
|
||||||
if(!pluginTranslator){
|
if(!pluginTranslator){
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { hypaMemoryV2 } from "./memory/hypav2";
|
|||||||
import { runLuaEditTrigger } from "./lua";
|
import { runLuaEditTrigger } from "./lua";
|
||||||
import { parseChatML } from "../parser.svelte";
|
import { parseChatML } from "../parser.svelte";
|
||||||
import { getModelInfo, LLMFlags } from "../model/modellist";
|
import { getModelInfo, LLMFlags } from "../model/modellist";
|
||||||
|
import { pluginV2 } from "../plugins/plugins";
|
||||||
|
|
||||||
export interface OpenAIChat{
|
export interface OpenAIChat{
|
||||||
role: 'system'|'user'|'assistant'|'function'
|
role: 'system'|'user'|'assistant'|'function'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { MultiModal, OpenAIChat, OpenAIChatFull } from "./index.svelte";
|
import type { MultiModal, OpenAIChat, OpenAIChatFull } from "./index.svelte";
|
||||||
import { getCurrentCharacter, getDatabase, setDatabase, type character } from "../storage/database.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 { language } from "../../lang";
|
||||||
import { stringlizeAINChat, getStopStrings, unstringlizeAIN, unstringlizeChat } from "./stringlize";
|
import { stringlizeAINChat, getStopStrings, unstringlizeAIN, unstringlizeChat } from "./stringlize";
|
||||||
import { addFetchLog, fetchNative, globalFetch, isNodeServer, isTauri, textifyReadableStream } from "../globalApi.svelte";
|
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()
|
const db = getDatabase()
|
||||||
let trys = 0
|
let trys = 0
|
||||||
while(true){
|
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)
|
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){
|
if(da.type !== 'fail' || da.noRetry){
|
||||||
return da
|
return da
|
||||||
}
|
}
|
||||||
@@ -1379,7 +1394,15 @@ async function requestPlugin(arg:RequestDataArgumentExtended):Promise<requestDat
|
|||||||
const db = getDatabase()
|
const db = getDatabase()
|
||||||
const maxTokens = arg.maxTokens
|
const maxTokens = arg.maxTokens
|
||||||
const bias = arg.biasString
|
const bias = arg.biasString
|
||||||
const d = await pluginProcess({
|
const v2Function = pluginV2.providers.get(db.currentPluginProvider)
|
||||||
|
|
||||||
|
const d = v2Function ? (await v2Function(applyParameters({
|
||||||
|
prompt_chat: formated,
|
||||||
|
mode: arg.mode,
|
||||||
|
bias: []
|
||||||
|
}, [
|
||||||
|
'frequency_penalty','min_p','presence_penalty','repetition_penalty','top_k','top_p','temperature'
|
||||||
|
], {}, arg.mode) as any)) : await pluginProcess({
|
||||||
bias: bias,
|
bias: bias,
|
||||||
prompt_chat: formated,
|
prompt_chat: formated,
|
||||||
temperature: (db.temperature / 100),
|
temperature: (db.temperature / 100),
|
||||||
@@ -1387,6 +1410,7 @@ async function requestPlugin(arg:RequestDataArgumentExtended):Promise<requestDat
|
|||||||
presence_penalty: (db.PresensePenalty / 100),
|
presence_penalty: (db.PresensePenalty / 100),
|
||||||
frequency_penalty: (db.frequencyPenalty / 100)
|
frequency_penalty: (db.frequencyPenalty / 100)
|
||||||
})
|
})
|
||||||
|
|
||||||
if(!d){
|
if(!d){
|
||||||
return {
|
return {
|
||||||
type: 'fail',
|
type: 'fail',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { runCharacterJS } from "../plugins/embedscript";
|
|||||||
import { getModuleAssets, getModuleRegexScripts } from "./modules";
|
import { getModuleAssets, getModuleRegexScripts } from "./modules";
|
||||||
import { HypaProcesser } from "./memory/hypamemory";
|
import { HypaProcesser } from "./memory/hypamemory";
|
||||||
import { runLuaEditTrigger } from "./lua";
|
import { runLuaEditTrigger } from "./lua";
|
||||||
|
import { pluginV2 } from "../plugins/plugins";
|
||||||
|
|
||||||
const dreg = /{{data}}/g
|
const dreg = /{{data}}/g
|
||||||
const randomness = /\|\|\|/g
|
const randomness = /\|\|\|/g
|
||||||
@@ -109,6 +110,15 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
|||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
data = await runLuaEditTrigger(char, mode, data)
|
data = await runLuaEditTrigger(char, mode, data)
|
||||||
|
if(pluginV2[mode].size > 0){
|
||||||
|
for(const plugin of pluginV2[mode]){
|
||||||
|
const res = await plugin(data)
|
||||||
|
if(res !== null && res !== undefined){
|
||||||
|
data = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(scripts.length === 0){
|
if(scripts.length === 0){
|
||||||
cacheScript(scripts, originalData, data, mode)
|
cacheScript(scripts, originalData, data, mode)
|
||||||
return {data, emoChanged}
|
return {data, emoChanged}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { defaultColorScheme, type ColorScheme } from '../gui/colorscheme';
|
|||||||
import type { PromptItem, PromptSettings } from '../process/prompt';
|
import type { PromptItem, PromptSettings } from '../process/prompt';
|
||||||
import type { OobaChatCompletionRequestParams } from '../model/ooba';
|
import type { OobaChatCompletionRequestParams } from '../model/ooba';
|
||||||
|
|
||||||
export let appVer = "143.9.1"
|
export let appVer = "144.0.0"
|
||||||
export let webAppSubVer = ''
|
export let webAppSubVer = ''
|
||||||
|
|
||||||
|
|
||||||
@@ -857,6 +857,7 @@ export interface Database{
|
|||||||
geminiStream?:boolean
|
geminiStream?:boolean
|
||||||
assetMaxDifference:number
|
assetMaxDifference:number
|
||||||
menuSideBar:boolean
|
menuSideBar:boolean
|
||||||
|
pluginV2: RisuPlugin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeparateParameters{
|
interface SeparateParameters{
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":"143.9.1"}
|
{"version":"144.0.0"}
|
||||||
Reference in New Issue
Block a user