Add V2 plugin

This commit is contained in:
Kwaroran
2024-12-20 23:45:07 +09:00
parent e5e01d0688
commit 7cd49fc8c3
17 changed files with 426 additions and 34 deletions

View File

@@ -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.
*/
export async function fetchNative(url:string, arg:{
body:string,
body:string|Uint8Array|ArrayBuffer,
headers?:{[key:string]:string},
method?:"POST",
signal?:AbortSignal,
useRisuTk?:boolean,
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 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

View File

@@ -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<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) {
if(!pluginTranslator){
return false

View File

@@ -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'

View File

@@ -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<requestDat
const db = getDatabase()
const maxTokens = arg.maxTokens
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,
prompt_chat: formated,
temperature: (db.temperature / 100),
@@ -1387,6 +1410,7 @@ async function requestPlugin(arg:RequestDataArgumentExtended):Promise<requestDat
presence_penalty: (db.PresensePenalty / 100),
frequency_penalty: (db.frequencyPenalty / 100)
})
if(!d){
return {
type: 'fail',

View File

@@ -10,6 +10,7 @@ import { runCharacterJS } from "../plugins/embedscript";
import { getModuleAssets, getModuleRegexScripts } from "./modules";
import { HypaProcesser } from "./memory/hypamemory";
import { runLuaEditTrigger } from "./lua";
import { pluginV2 } from "../plugins/plugins";
const dreg = /{{data}}/g
const randomness = /\|\|\|/g
@@ -109,6 +110,15 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
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){
cacheScript(scripts, originalData, data, mode)
return {data, emoChanged}

View File

@@ -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{