Updated to 0.7.0 (#8)

This commit is contained in:
kwaroran
2023-05-08 00:15:27 +09:00
committed by GitHub
15 changed files with 357 additions and 154 deletions

View File

@@ -1,30 +1,49 @@
interface risuPlugin{ (() => {
interface risuPlugin{
providers: {name:string, func:(arg:providerArgument) => Promise<{success:boolean,content:string}>}[] providers: {name:string, func:(arg:providerArgument) => Promise<{success:boolean,content:string}>}[]
fetchResponseQueue:{id:string,data:any}[] fetchResponseQueue:{id:string,data:any}[]
} }
let __risuPlugin__:risuPlugin = { let __risuPlugin__:risuPlugin = {
providers: [], providers: [],
fetchResponseQueue: [] fetchResponseQueue: []
} }
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
interface OpenAIChat{ interface OpenAIChat{
role: 'system'|'user'|'assistant' role: 'system'|'user'|'assistant'
content: string content: string
} }
interface providerArgument{ interface providerArgument{
prompt_chat?: OpenAIChat, prompt_chat?: OpenAIChat,
temperature?: number, temperature?: number,
max_tokens?: number, max_tokens?: number,
presence_penalty?: number presence_penalty?: number
frequency_penalty?: number frequency_penalty?: number
bias?: {[key:string]:string} bias?: {[key:string]:string}
} }
async function risuFetch(url:string, arg:{body:any,headers?:{[key:string]:string}}){ async function transferDataAsync(type:string,body:any) {
const id = `${Date.now()}_${Math.random()}`
postMessage({
type: 'fetch',
body: {id: id, ...body}
})
while(true){
await sleep(50)
for(let i=0;i<__risuPlugin__.fetchResponseQueue.length;i++){
const q = __risuPlugin__.fetchResponseQueue[i]
if(q.id === id){
__risuPlugin__.fetchResponseQueue.splice(i, 1)
return q.data
}
}
}
}
async function risuFetch(url:string, arg:{body:any,headers?:{[key:string]:string}}){
const id = `${Date.now()}_${Math.random()}` const id = `${Date.now()}_${Math.random()}`
postMessage({ postMessage({
type: 'fetch', type: 'fetch',
@@ -48,9 +67,9 @@ async function risuFetch(url:string, arg:{body:any,headers?:{[key:string]:string
} }
} }
} }
} }
async function getArg(arg:string){ async function getArg(arg:string){
const id = `${Date.now()}_${Math.random()}` const id = `${Date.now()}_${Math.random()}`
postMessage({ postMessage({
type: 'getArg', type: 'getArg',
@@ -69,9 +88,9 @@ async function getArg(arg:string){
} }
} }
} }
} }
function addProvider(name:string, func:(arg:providerArgument) => Promise<{success:boolean,content:string}>){ function addProvider(name:string, func:(arg:providerArgument) => Promise<{success:boolean,content:string}>){
postMessage({ postMessage({
type: 'addProvider', type: 'addProvider',
body: name body: name
@@ -80,16 +99,28 @@ function addProvider(name:string, func:(arg:providerArgument) => Promise<{succes
name: name, name: name,
func: func func: func
}) })
} }
function printLog(data:any){ function printLog(data:any){
postMessage({ postMessage({
type: 'log', type: 'log',
body: data body: data
}) })
} }
async function handleOnmessage(data:{type:string,body:any}) {
function getChar(){
return transferDataAsync('getChar', '')
}
function setChar(char:any){
postMessage({
type: 'setChar',
body: char
})
}
async function handleOnmessage(data:{type:string,body:any}) {
if(!data.type){ if(!data.type){
return return
} }
@@ -135,9 +166,16 @@ async function handleOnmessage(data:{type:string,body:any}) {
break break
} }
} }
} }
onmessage = (ev) => { onmessage = (ev) => {
handleOnmessage(ev.data) handleOnmessage(ev.data)
const data:{type:string,body:any} = ev.data const data:{type:string,body:any} = ev.data
} }
{
const __risuPlugin__ = null
const transferDataAsync = null
//{{placeholder}}
}
})()

View File

@@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "RisuAI", "productName": "RisuAI",
"version": "0.6.7" "version": "0.7.0"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@@ -50,6 +50,13 @@ export const languageEnglish = {
+ "\n- if the key starts with **|**, the key's value will not change." + "\n- if the key starts with **|**, the key's value will not change."
+ "\n- if the key starts with **$**, the key's value will more likely to change." + "\n- if the key starts with **$**, the key's value will more likely to change."
+ "\n\nwhen the image is first generated, you can only change it by modifying 'Current Image Generation Data' in below.", + "\n\nwhen the image is first generated, you can only change it by modifying 'Current Image Generation Data' in below.",
regexScript: "Regex Script is a custom script that is embbedded to the character. it replaces string that matches IN to OUT.\n\nThere are three type options."
+ "- **Modify Input** modifys user's input"
+ "- **Modify Output** modifys character's output"
+ "- **Modify Request Data** modifys current chat data when sent.\n\nIN must be a regex without flags and *\\*.\n\nOUT is a normal string."
+ "\n\n If OUT starts with @@, it doesn't replaces the string, but instead does a special effect if matching string founds."
+ "\n\n- @@emo (emotion name)\n\n if character is Emotion Images mode, sets (emotion name) as emotion and prevents default.",
experimental: "This is a experimental setting. it might be unstable." experimental: "This is a experimental setting. it might be unstable."
}, },
setup: { setup: {
@@ -199,6 +206,8 @@ export const languageEnglish = {
type: "Type", type: "Type",
editInput: "Modfiy Input", editInput: "Modfiy Input",
editOutput: "Modfiy Output", editOutput: "Modfiy Output",
editProcess: "Modfiy Request Data" editProcess: "Modfiy Request Data",
loadLatest: "Load Latest Backup",
loadOthers: "Load Other Backups"
} }

View File

@@ -179,7 +179,13 @@ export const languageKorean = {
+ "\n- 키의 이름이 **|** 로 시작할 시, 값은 고정됩니다." + "\n- 키의 이름이 **|** 로 시작할 시, 값은 고정됩니다."
+ "\n- 키의 이름이 **$** 로 시작할 시, 값은 더 자주 변합니다." + "\n- 키의 이름이 **$** 로 시작할 시, 값은 더 자주 변합니다."
+ "\n\n이미지가 처음 생성된 이후부터는 '현재 이미지 생성 데이터'를 수정하여 변경할 수 있습니다.", + "\n\n이미지가 처음 생성된 이후부터는 '현재 이미지 생성 데이터'를 수정하여 변경할 수 있습니다.",
experimental: "실험적 기능입니다. 불안정할 수 있습니다." experimental: "실험적 기능입니다. 불안정할 수 있습니다.",
regexScript: "정규식 스크립트는 캐릭터에 종속된 커스텀 스크립트입니다. IN의 조건에 맞는 문자열을 OUT으로 변경합니다.\n\n타입은 세가지가 있습니다."
+ "- **입력문 수정** 유저의 입력문을 수정합니다"
+ "- **출력문 수정** 캐릭터의 출력문을 수정합니다"
+ "- **리퀘스트 데이터 수정** 리퀘스트를 보낼 때 채팅 데이터를 수정합니다.\n\nIN은 flag와 *\\* 가 없는 Regex여야 합니다.\n\nOUT은 일반 문자열입니다."
+ "\n\n 만약 OUT이 @@로 시작된다면, 특수한 효과를 냅니다"
+ "\n\n- @@emo (emotion name)\n\n 감정 이미지 모드일 시 (emotion name)을 감정으로 정하고 감정 처리를 하지 않습니다.",
}, },
setup: { setup: {
chooseProvider: "AI 제공자를 선택해 주세요", chooseProvider: "AI 제공자를 선택해 주세요",
@@ -199,5 +205,7 @@ export const languageKorean = {
type: "타입", type: "타입",
editInput: "입력문 수정", editInput: "입력문 수정",
editOutput: "출력문 수정", editOutput: "출력문 수정",
editProcess: "리퀘스트 데이터 수정" editProcess: "리퀘스트 데이터 수정",
loadLatest: "가장 최근 백업 불러오기",
loadOthers: "다른 백업 불러오기"
} }

View File

@@ -12,6 +12,7 @@
import sendSound from '../../etc/send.mp3' import sendSound from '../../etc/send.mp3'
import {cloneDeep} from 'lodash' import {cloneDeep} from 'lodash'
import { processScript } from "src/ts/process/scripts"; import { processScript } from "src/ts/process/scripts";
import GithubStars from "../Others/GithubStars.svelte";
let messageInput = '' let messageInput = ''
let openMenu = false let openMenu = false
@@ -186,6 +187,7 @@
<div class="h-full w-full flex flex-col overflow-y-auto items-center"> <div class="h-full w-full flex flex-col overflow-y-auto items-center">
<h2 class="text-4xl text-white mb-0 mt-6 font-black">RisuAI</h2> <h2 class="text-4xl text-white mb-0 mt-6 font-black">RisuAI</h2>
<h3 class="text-gray-500 mt-1">Version {appVer}</h3> <h3 class="text-gray-500 mt-1">Version {appVer}</h3>
<GithubStars />
</div> </div>
{:else} {:else}
<div class="h-full w-full flex flex-col-reverse overflow-y-auto relative" on:scroll={(e) => { <div class="h-full w-full flex flex-col-reverse overflow-y-auto relative" on:scroll={(e) => {

View File

@@ -22,7 +22,7 @@
{#if $alertStore.type !== 'none' && $alertStore.type !== 'toast'} {#if $alertStore.type !== 'none' && $alertStore.type !== 'toast'}
<div class="absolute w-full h-full z-50 bg-black bg-opacity-50 flex justify-center items-center" class:vis={ $alertStore.type === 'wait2'}> <div class="absolute w-full h-full z-50 bg-black bg-opacity-50 flex justify-center items-center" class:vis={ $alertStore.type === 'wait2'}>
<div class="bg-darkbg p-4 break-any rounded-md flex flex-col max-w-3xl max-h-11/12 overflow-y-auto"> <div class="bg-darkbg p-4 break-any rounded-md flex flex-col max-w-3xl max-h-full overflow-y-auto">
{#if $alertStore.type === 'error'} {#if $alertStore.type === 'error'}
<h2 class="text-red-700 mt-0 mb-2 w-40 max-w-full">Error</h2> <h2 class="text-red-700 mt-0 mb-2 w-40 max-w-full">Error</h2>
{:else if $alertStore.type === 'ask'} {:else if $alertStore.type === 'ask'}

View File

@@ -0,0 +1,11 @@
<script lang="ts">
import { isTauri } from "src/ts/globalApi";
</script>
<svelte:head>
<script async defer src="https://buttons.github.io/buttons.js"></script>
</svelte:head>
<!-- Place this tag where you want the button to render. -->
{#if !isTauri}
<a class="github-button mt-4" href="https://github.com/kwaroran/risuAI" data-color-scheme="no-preference: dark; light: dark; dark: dark;" data-size="large" data-show-count="true" aria-label="Star kwaroran/risuAI on GitHub">Star</a>
{/if}

View File

@@ -396,7 +396,7 @@
</tr> </tr>
{/each} {/each}
</table> </table>
<span class="text-neutral-200 mt-4">{language.regexScript} <Help key="experimental"/></span> <span class="text-neutral-200 mt-4">{language.regexScript} <Help key="regexScript"/></span>
<table class="contain w-full max-w-full tabler mt-2 flex flex-col p-2 gap-2"> <table class="contain w-full max-w-full tabler mt-2 flex flex-col p-2 gap-2">
{#if currentChar.data.customscript.length === 0} {#if currentChar.data.customscript.length === 0}
<div class="text-gray-500">No Scripts</div> <div class="text-gray-500">No Scripts</div>

View File

@@ -6,7 +6,7 @@ import { saveImage as saveImageGlobal } from './globalApi';
export const DataBase = writable({} as any as Database) export const DataBase = writable({} as any as Database)
export const loadedStore = writable(false) export const loadedStore = writable(false)
export let appVer = '0.6.7' export let appVer = '0.7.0'
export function setDatabase(data:Database){ export function setDatabase(data:Database){

View File

@@ -1,5 +1,5 @@
import { get } from "svelte/store"; import { get } from "svelte/store";
import { alertError, alertInput, alertNormal, alertStore } from "../alert"; import { alertError, alertInput, alertNormal, alertSelect, alertStore } from "../alert";
import { DataBase, setDatabase, type Database } from "../database"; import { DataBase, setDatabase, type Database } from "../database";
import { forageStorage, getUnpargeables, isTauri } from "../globalApi"; import { forageStorage, getUnpargeables, isTauri } from "../globalApi";
import pako from "pako"; import pako from "pako";
@@ -177,8 +177,8 @@ async function loadDrive(ACCESS_TOKEN:string) {
return d.name return d.name
}) })
let latestDb:DriveFile = null
let latestDbDate = 0 let dbs:[DriveFile,number][] = []
for(const f of files){ for(const f of files){
if(f.name.endsWith("-database.risudat")){ if(f.name.endsWith("-database.risudat")){
@@ -187,15 +187,26 @@ async function loadDrive(ACCESS_TOKEN:string) {
continue continue
} }
else{ else{
if(tm > latestDbDate){ dbs.push([f,tm])
latestDb = f
latestDbDate = tm
} }
} }
} }
dbs.sort((a,b) => {
return b[1] - a[1]
})
if(dbs.length !== 0){
let selectables:string[] = []
for(let i=0;i<dbs.length;i++){
selectables.push(`Backup saved in ${(new Date(dbs[i][1] * 1000)).toLocaleString()}`)
if(selectables.length > 7){
break
} }
if(latestDbDate !== 0){ }
const db:Database = JSON.parse(Buffer.from(pako.inflate(await getFileData(ACCESS_TOKEN, latestDb.id))).toString('utf-8')) const selectedIndex = (await alertSelect([language.loadLatest, language.loadOthers]) === '0') ? 0 : parseInt(await alertSelect(selectables))
const selectedDb = dbs[selectedIndex][0]
const db:Database = JSON.parse(Buffer.from(pako.inflate(await getFileData(ACCESS_TOKEN, selectedDb.id))).toString('utf-8'))
const requiredImages = (getUnpargeables(db)) const requiredImages = (getUnpargeables(db))
let ind = 0; let ind = 0;
for(const images of requiredImages){ for(const images of requiredImages){
@@ -253,6 +264,9 @@ async function loadDrive(ACCESS_TOKEN:string) {
}) })
} }
} }
else{
location.search = ''
}
} }
function checkImageExist(image:string){ function checkImageExist(image:string){

View File

@@ -8,7 +8,7 @@ import { loadLoreBookPrompt } from "../lorebook";
import { findCharacterbyId, replacePlaceholders } from "../util"; import { findCharacterbyId, replacePlaceholders } from "../util";
import { requestChatData } from "./request"; import { requestChatData } from "./request";
import { stableDiff } from "./stableDiff"; import { stableDiff } from "./stableDiff";
import { processScript } from "./scripts"; import { processScript, processScriptFull } from "./scripts";
export interface OpenAIChat{ export interface OpenAIChat{
role: 'system'|'user'|'assistant' role: 'system'|'user'|'assistant'
@@ -282,23 +282,26 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
}, 'model') }, 'model')
let result = '' let result = ''
let emoChanged = false
if(req.type === 'fail'){ if(req.type === 'fail'){
alertError(req.result) alertError(req.result)
return false return false
} }
else{ else{
result = reformatContent(req.result) const result2 = processScriptFull(currentChar, reformatContent(req.result), 'editoutput')
result = result2.data
emoChanged = result2.emoChanged
db.characters[selectedChar].chats[selectedChat].message.push({ db.characters[selectedChar].chats[selectedChat].message.push({
role: 'char', role: 'char',
data: result, data: result,
saying: processScript(currentChar,currentChar.chaId, 'editoutput') saying: currentChar.chaId
}) })
setDatabase(db) setDatabase(db)
} }
if(currentChar.viewScreen === 'emotion'){ if(currentChar.viewScreen === 'emotion' && (!emoChanged)){
let currentEmotion = currentChar.emotionImages let currentEmotion = currentChar.emotionImages

View File

@@ -5,6 +5,7 @@ import { DataBase } from "../database";
import { checkNullish, selectSingleFile, sleep } from "../util"; import { checkNullish, selectSingleFile, sleep } from "../util";
import type { OpenAIChat } from "."; import type { OpenAIChat } from ".";
import { globalFetch } from "../globalApi"; import { globalFetch } from "../globalApi";
import { selectedCharID } from "../stores";
export const customProviderStore = writable([] as string[]) export const customProviderStore = writable([] as string[])
@@ -107,6 +108,7 @@ export function getCurrentPluginMax(prov:string){
let pluginWorker:Worker = null let pluginWorker:Worker = null
let providerRes:{success:boolean, content:string} = null let providerRes:{success:boolean, content:string} = null
let translatorRes:{success:boolean, content:string} = null
function postMsgPluginWorker(type:string, body:any){ function postMsgPluginWorker(type:string, body:any){
const bod = { const bod = {
@@ -116,6 +118,8 @@ function postMsgPluginWorker(type:string, body:any){
pluginWorker.postMessage(bod) pluginWorker.postMessage(bod)
} }
let pluginTranslator = false
export async function loadPlugins() { export async function loadPlugins() {
let db = get(DataBase) let db = get(DataBase)
if(pluginWorker){ if(pluginWorker){
@@ -127,10 +131,12 @@ export async function loadPlugins() {
const da = await fetch("/pluginApi.js") const da = await fetch("/pluginApi.js")
const pluginApiString = await da.text() const pluginApiString = await da.text()
let pluginjs = `${pluginApiString}\n` let pluginjs = `${pluginApiString}\n`
let pluginLoadedJs = ''
for(const plug of db.plugins){ for(const plug of db.plugins){
pluginjs += `(() => {${plug.script}})()` pluginLoadedJs += `(() => {${plug.script}})()`
} }
pluginjs = pluginjs.replace('//{{placeholder}}',pluginLoadedJs)
const blob = new Blob([pluginjs], {type: 'application/javascript'}); const blob = new Blob([pluginjs], {type: 'application/javascript'});
pluginWorker = new Worker(URL.createObjectURL(blob)); pluginWorker = new Worker(URL.createObjectURL(blob));
@@ -167,6 +173,31 @@ export async function loadPlugins() {
} }
break break
} }
case "resTrans":{
const provres:{success:boolean, content:string} = data.body
if(checkNullish(provres.success) || checkNullish(provres.content)){
translatorRes = {
success: false,
content :"plugin didn't respond 'success' or 'content' in response object"
}
}
else if(typeof(provres.content) !== 'string'){
translatorRes = {
success: false,
content :"plugin didn't respond 'content' in response object in string"
}
}
else{
translatorRes = {
success: !!provres.success,
content: provres.content
}
}
break
}
case "useTranslator": {
pluginTranslator = true
}
case "fetch": { case "fetch": {
postMsgPluginWorker('fetchData',{ postMsgPluginWorker('fetchData',{
id: data.body.id, id: data.body.id,
@@ -199,6 +230,20 @@ export async function loadPlugins() {
} }
break break
} }
case "getChar":{
const db = get(DataBase)
const charid = get(selectedCharID)
const char = db.characters[charid]
postMsgPluginWorker('fetchData',{
id: data.body.id,
data: char
})
}
case "setChar":{
const db = get(DataBase)
const charid = get(selectedCharID)
db.characters[charid] = data.body
}
case "log":{ case "log":{
console.log(data.body) console.log(data.body)
break break
@@ -208,6 +253,33 @@ export async function loadPlugins() {
} }
} }
export async function translatorPlugin(text:string, from:string, to:string) {
if(!pluginTranslator){
return false
}
else{
try {
translatorRes = null
postMsgPluginWorker("requestTrans", {text, from, to})
while(true){
await sleep(50)
if(providerRes){
break
}
}
return {
success: translatorRes.success,
content: translatorRes.content
}
} catch (error) {
return {
success: false,
content: "unknownError"
}
}
}
}
export async function pluginProcess(arg:{ export async function pluginProcess(arg:{
prompt_chat: OpenAIChat, prompt_chat: OpenAIChat,
temperature: number, temperature: number,

View File

@@ -1,15 +1,52 @@
import { get } from "svelte/store";
import { CharEmotion, selectedCharID } from "../stores";
import type { character } from "../database"; import type { character } from "../database";
const dreg = /{{data}}/g const dreg = /{{data}}/g
export function processScript(char:character, data:string, mode:'editinput'|'editoutput'|'editprocess'){ type ScriptMode = 'editinput'|'editoutput'|'editprocess'
export function processScript(char:character, data:string, mode:ScriptMode){
return processScriptFull(char, data, mode).data
}
export function processScriptFull(char:character, data:string, mode:ScriptMode){
let emoChanged = false
for (const script of char.customscript){ for (const script of char.customscript){
if(script.type === mode){ if(script.type === mode){
const reg = new RegExp(script.in,'g') const reg = new RegExp(script.in,'g')
data = data.replace(reg, (v) => { data = data.replace(reg, (v) => {
if(script.out.startsWith('@@emo ')){
if(char.viewScreen !== 'emotion'){
return v
}
if(emoChanged){
return v
}
const emoName = script.out.substring(6).trim()
let charemotions = get(CharEmotion)
let tempEmotion = charemotions[char.chaId]
if(!tempEmotion){
tempEmotion = []
}
if(tempEmotion.length > 4){
tempEmotion.splice(0, 1)
}
for(const emo of char.emotionImages){
if(emo[0] === emoName){
const emos:[string, string,number] = [emo[0], emo[1], Date.now()]
tempEmotion.push(emos)
charemotions[char.chaId] = tempEmotion
CharEmotion.set(charemotions)
emoChanged = true
break
}
}
return v
}
return script.out.replace(dreg, v) return script.out.replace(dreg, v)
}) })
} }
} }
return data return {data, emoChanged}
} }

View File

@@ -1,28 +1,34 @@
import { Body,fetch,ResponseType } from "@tauri-apps/api/http" import { Body,fetch,ResponseType } from "@tauri-apps/api/http"
import { isTauri } from "../globalApi" import { isTauri } from "../globalApi"
import { translatorPlugin } from "../process/plugins"
let cache={ let cache={
origin: [''], origin: [''],
trans: [''] trans: ['']
} }
export async function translate(params:string, reverse:boolean) { export async function translate(text:string, reverse:boolean) {
if(!isTauri){ if(!isTauri){
return params return text
}
const plug = await translatorPlugin(text, reverse ? 'ko' : 'en', reverse ? 'en' : 'ko')
if(plug){
return plug.content
} }
if(!reverse){ if(!reverse){
const ind = cache.origin.indexOf(params) const ind = cache.origin.indexOf(text)
if(ind !== -1){ if(ind !== -1){
return cache.trans[ind] return cache.trans[ind]
} }
} }
else{ else{
const ind = cache.trans.indexOf(params) const ind = cache.trans.indexOf(text)
if(ind !== -1){ if(ind !== -1){
return cache.origin[ind] return cache.origin[ind]
} }
} }
return googleTrans(params, reverse)
return googleTrans(text, reverse)
} }
async function googleTrans(text:string, reverse:boolean) { async function googleTrans(text:string, reverse:boolean) {
@@ -45,5 +51,8 @@ async function googleTrans(text:string, reverse:boolean) {
}) })
const res = f.data as {sentences:{trans?:string}[]} const res = f.data as {sentences:{trans?:string}[]}
if(typeof(f.data) === 'string'){
return res as unknown as string
}
return res.sentences.filter((s) => 'trans' in s).map((s) => s.trans).join(''); return res.sentences.filter((s) => 'trans' in s).map((s) => s.trans).join('');
} }

View File

@@ -1 +1 @@
{"version":"0.6.7"} {"version":"0.7.0"}