Merge branch 'main' of https://github.com/kwaroran/RisuAI
This commit is contained in:
@@ -6,7 +6,10 @@ import { Capacitor } from "@capacitor/core"
|
||||
import { DataBase, type MessageGenerationInfo } from "./storage/database"
|
||||
|
||||
interface alertData{
|
||||
type: 'error'| 'normal'|'none'|'ask'|'wait'|'selectChar'|'input'|'toast'|'wait2'|'markdown'|'select'|'login'|'tos'|'cardexport'|'requestdata'|'addchar'|'hypaV2'|'selectModule',
|
||||
type: 'error'|'normal'|'none'|'ask'|'wait'|'selectChar'
|
||||
|'input'|'toast'|'wait2'|'markdown'|'select'|'login'
|
||||
|'tos'|'cardexport'|'requestdata'|'addchar'|'hypaV2'|'selectModule'
|
||||
|'chatOptions',
|
||||
msg: string,
|
||||
submsg?: string
|
||||
}
|
||||
@@ -94,6 +97,21 @@ export async function alertAddCharacter() {
|
||||
return get(alertStore).msg
|
||||
}
|
||||
|
||||
export async function alertChatOptions() {
|
||||
alertStore.set({
|
||||
'type': 'chatOptions',
|
||||
'msg': language.chatOptions
|
||||
})
|
||||
while(true){
|
||||
if (get(alertStore).type === 'none'){
|
||||
break
|
||||
}
|
||||
await sleep(10)
|
||||
}
|
||||
|
||||
return parseInt(get(alertStore).msg)
|
||||
}
|
||||
|
||||
export async function alertLogin(){
|
||||
alertStore.set({
|
||||
'type': 'login',
|
||||
|
||||
@@ -693,7 +693,8 @@ async function importCharacterCardSpec(card:CharacterCardV2Risu|CharacterCardV3,
|
||||
imported: true,
|
||||
source: card?.data?.extensions?.risuai?.source ?? [],
|
||||
ccAssets: ccAssets,
|
||||
lowLevelAccess: risuext?.lowLevelAccess ?? false
|
||||
lowLevelAccess: risuext?.lowLevelAccess ?? false,
|
||||
defaultVariables: data?.extensions?.risuai?.defaultVariables ?? '',
|
||||
}
|
||||
|
||||
if(card.spec === 'chara_card_v3'){
|
||||
@@ -1051,7 +1052,7 @@ export function createBaseV3(char:character){
|
||||
type: 'x-risu-asset',
|
||||
uri: asset[1],
|
||||
name: asset[0],
|
||||
ext: asset[2] || 'unknown'
|
||||
ext: asset[2] || 'png'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1062,7 +1063,7 @@ export function createBaseV3(char:character){
|
||||
type: 'emotion',
|
||||
uri: asset[1],
|
||||
name: asset[0],
|
||||
ext: 'unknown'
|
||||
ext: 'png'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1148,7 +1149,8 @@ export function createBaseV3(char:character){
|
||||
inlayViewScreen: char.inlayViewScreen,
|
||||
newGenData: char.newGenData,
|
||||
vits: {},
|
||||
lowLevelAccess: char.lowLevelAccess ?? false
|
||||
lowLevelAccess: char.lowLevelAccess ?? false,
|
||||
defaultVariables: char.defaultVariables ?? '',
|
||||
},
|
||||
depth_prompt: char.depth_prompt
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DataBase, saveImage, setDatabase, type character, type Chat, defaultSdD
|
||||
import { alertConfirm, alertError, alertNormal, alertSelect, alertStore, alertWait } from "./alert";
|
||||
import { language } from "../lang";
|
||||
import { decode as decodeMsgpack } from "msgpackr";
|
||||
import { checkNullish, findCharacterbyId, selectMultipleFile, selectSingleFile, sleep } from "./util";
|
||||
import { checkNullish, findCharacterbyId, getUserName, selectMultipleFile, selectSingleFile, sleep } from "./util";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { selectedCharID } from "./stores";
|
||||
import { checkCharOrder, downloadFile, getFileSrc } from "./storage/globalApi";
|
||||
@@ -197,7 +197,7 @@ export async function exportChat(page:number){
|
||||
let i = 0
|
||||
for(const v of chat.message){
|
||||
alertWait(`Translating... ${i++}/${chat.message.length}`)
|
||||
const name = v.saying ? findCharacterbyId(v.saying).name : v.role === 'char' ? char.name : anonymous ? '×××' : db.username
|
||||
const name = v.saying ? findCharacterbyId(v.saying).name : v.role === 'char' ? char.name : anonymous ? '×××' : getUserName()
|
||||
chatContentHTML += `<div class="chat">
|
||||
<h2>${name}</h2>
|
||||
<div>${await htmlChatParse(v.data)}</div>
|
||||
@@ -268,7 +268,7 @@ export async function exportChat(page:number){
|
||||
let i = 0
|
||||
for(const v of chat.message){
|
||||
alertWait(`Translating... ${i++}/${chat.message.length}`)
|
||||
const name = v.saying ? findCharacterbyId(v.saying).name : v.role === 'char' ? char.name : anonymous ? '×××' : db.username
|
||||
const name = v.saying ? findCharacterbyId(v.saying).name : v.role === 'char' ? char.name : anonymous ? '×××' : getUserName()
|
||||
chatContentHTML += `<tr>
|
||||
<td>${name}</td>
|
||||
<td>${await htmlChatParse(v.data)}</td>
|
||||
@@ -309,7 +309,7 @@ export async function exportChat(page:number){
|
||||
return `--${findCharacterbyId(v.saying).name}\n${v.data}`
|
||||
}
|
||||
else{
|
||||
return `--${v.role === 'char' ? char.name : db.username}\n${v.data}`
|
||||
return `--${v.role === 'char' ? char.name : getUserName()}\n${v.data}`
|
||||
}
|
||||
}).join('\n\n')
|
||||
|
||||
@@ -409,7 +409,7 @@ export async function importChat(){
|
||||
|
||||
function formatTavernChat(chat:string, charName:string){
|
||||
const db = get(DataBase)
|
||||
return chat.replace(/<([Uu]ser)>|\{\{([Uu]ser)\}\}/g, db.username).replace(/((\{\{)|<)([Cc]har)(=.+)?((\}\})|>)/g, charName)
|
||||
return chat.replace(/<([Uu]ser)>|\{\{([Uu]ser)\}\}/g, getUserName()).replace(/((\{\{)|<)([Cc]har)(=.+)?((\}\})|>)/g, charName)
|
||||
}
|
||||
|
||||
export function characterFormatUpdate(index:number|character){
|
||||
|
||||
@@ -95,9 +95,12 @@ export async function checkDriverInit() {
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
location.search = ''
|
||||
console.error(error)
|
||||
alertError(`Backup Error: ${error}`)
|
||||
const currentURL = new URL(location.href)
|
||||
currentURL.search = ''
|
||||
window.history.replaceState( {} , "", currentURL.href );
|
||||
await sleep(100000)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { get } from "svelte/store";
|
||||
import { get, writable } from "svelte/store";
|
||||
import { DataBase, setDatabase } from "../storage/database";
|
||||
import { downloadFile } from "../storage/globalApi";
|
||||
import { BufferToText, selectSingleFile } from "../util";
|
||||
@@ -96,6 +96,8 @@ const colorShemes = {
|
||||
|
||||
} as const
|
||||
|
||||
export const ColorSchemeTypeStore = writable('dark' as 'dark'|'light')
|
||||
|
||||
export const colorSchemeList = Object.keys(colorShemes) as (keyof typeof colorShemes)[]
|
||||
|
||||
export function changeColorScheme(colorScheme: string){
|
||||
@@ -114,7 +116,7 @@ export function updateColorScheme(){
|
||||
let colorScheme = db.colorScheme
|
||||
|
||||
if(colorScheme == null){
|
||||
colorScheme = defaultColorScheme
|
||||
colorScheme = structuredClone(defaultColorScheme)
|
||||
}
|
||||
|
||||
//set css variables
|
||||
@@ -127,6 +129,7 @@ export function updateColorScheme(){
|
||||
document.documentElement.style.setProperty("--risu-theme-textcolor2", colorScheme.textcolor2);
|
||||
document.documentElement.style.setProperty("--risu-theme-darkborderc", colorScheme.darkBorderc);
|
||||
document.documentElement.style.setProperty("--risu-theme-darkbutton", colorScheme.darkbutton);
|
||||
ColorSchemeTypeStore.set(colorScheme.type)
|
||||
}
|
||||
|
||||
export function exportColorScheme(){
|
||||
|
||||
@@ -79,6 +79,8 @@ export function getModelName(name:string){
|
||||
return 'Gemini Ultra Vision'
|
||||
case 'claude-3-opus-20240229':
|
||||
return 'Claude 3 Opus (20240229)'
|
||||
case 'claude-3-5-sonnet-20240620':
|
||||
return 'Claude 3.5 Sonnet (20240620)'
|
||||
case 'claude-3-sonnet-20240229':
|
||||
return 'Claude 3 Sonnet (20240229)'
|
||||
case 'mistral-large-latest':
|
||||
|
||||
88
src/ts/mutex.ts
Normal file
88
src/ts/mutex.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* A lock for synchronizing async operations.
|
||||
* Use this to protect a critical section
|
||||
* from getting modified by multiple async operations
|
||||
* at the same time.
|
||||
*/
|
||||
export class Mutex {
|
||||
/**
|
||||
* When multiple operations attempt to acquire the lock,
|
||||
* this queue remembers the order of operations.
|
||||
*/
|
||||
private _queue: {
|
||||
resolve: (release: ReleaseFunction) => void
|
||||
}[] = []
|
||||
|
||||
private _isLocked = false
|
||||
|
||||
/**
|
||||
* Wait until the lock is acquired.
|
||||
* @returns A function that releases the acquired lock.
|
||||
*/
|
||||
acquire() {
|
||||
return new Promise<ReleaseFunction>((resolve) => {
|
||||
this._queue.push({resolve})
|
||||
this._dispatch()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a function to be run serially.
|
||||
*
|
||||
* This ensures no other functions will start running
|
||||
* until `callback` finishes running.
|
||||
* @param callback Function to be run exclusively.
|
||||
* @returns The return value of `callback`.
|
||||
*/
|
||||
async runExclusive<T>(callback: () => Promise<T>) {
|
||||
const release = await this.acquire()
|
||||
try {
|
||||
return await callback()
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the availability of the resource
|
||||
* and provide access to the next operation in the queue.
|
||||
*
|
||||
* _dispatch is called whenever availability changes,
|
||||
* such as after lock acquire request or lock release.
|
||||
*/
|
||||
private _dispatch() {
|
||||
if (this._isLocked) {
|
||||
// The resource is still locked.
|
||||
// Wait until next time.
|
||||
return
|
||||
}
|
||||
const nextEntry = this._queue.shift()
|
||||
if (!nextEntry) {
|
||||
// There is nothing in the queue.
|
||||
// Do nothing until next dispatch.
|
||||
return
|
||||
}
|
||||
// The resource is available.
|
||||
this._isLocked = true
|
||||
// and give access to the next operation
|
||||
// in the queue.
|
||||
nextEntry.resolve(this._buildRelease())
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a release function for each operation
|
||||
* so that it can release the lock after
|
||||
* the operation is complete.
|
||||
*/
|
||||
private _buildRelease(): ReleaseFunction {
|
||||
return () => {
|
||||
// Each release function make
|
||||
// the resource available again
|
||||
this._isLocked = false
|
||||
// and call dispatch.
|
||||
this._dispatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ReleaseFunction = () => void
|
||||
850
src/ts/parser.ts
850
src/ts/parser.ts
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,12 @@
|
||||
import { get } from "svelte/store"
|
||||
import { DataBase, saveImage, setDatabase } from "./storage/database"
|
||||
import { selectSingleFile, sleep } from "./util"
|
||||
import { getUserName, selectSingleFile, sleep } from "./util"
|
||||
import { alertError, alertNormal, alertStore } from "./alert"
|
||||
import { downloadFile, readImage } from "./storage/globalApi"
|
||||
import { language } from "src/lang"
|
||||
import { reencodeImage } from "./process/files/image"
|
||||
import { PngChunk } from "./pngChunk"
|
||||
import { v4 } from "uuid"
|
||||
|
||||
export async function selectUserImg() {
|
||||
const selected = await selectSingleFile(['png'])
|
||||
@@ -19,19 +20,18 @@ export async function selectUserImg() {
|
||||
db.personas[db.selectedPersona] = {
|
||||
name: db.username,
|
||||
icon: db.userIcon,
|
||||
personaPrompt: db.personaPrompt
|
||||
personaPrompt: db.personaPrompt,
|
||||
id: v4()
|
||||
}
|
||||
setDatabase(db)
|
||||
}
|
||||
|
||||
export function saveUserPersona() {
|
||||
let db = get(DataBase)
|
||||
db.personas[db.selectedPersona] = {
|
||||
name: db.username,
|
||||
icon: db.userIcon,
|
||||
personaPrompt: db.personaPrompt,
|
||||
largePortrait: db.personas[db.selectedPersona]?.largePortrait,
|
||||
}
|
||||
db.personas[db.selectedPersona].name=db.username
|
||||
db.personas[db.selectedPersona].icon=db.userIcon,
|
||||
db.personas[db.selectedPersona].personaPrompt=db.personaPrompt,
|
||||
db.personas[db.selectedPersona].largePortrait=db.personas[db.selectedPersona]?.largePortrait,
|
||||
setDatabase(db)
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ export async function importUserPersona(){
|
||||
db.personas.push({
|
||||
name: data.name,
|
||||
icon: await saveImage(await reencodeImage(v.data)),
|
||||
personaPrompt: data.personaPrompt
|
||||
personaPrompt: data.personaPrompt,
|
||||
id: v4()
|
||||
})
|
||||
setDatabase(db)
|
||||
alertNormal(language.successImport)
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
|
||||
const excludesDat = ['<','>','{','}','[',']','(',')','-',':',';','…','—','–','_','*','+','/','\\','|','!','?','.',',',' ']
|
||||
const symbols = ['<','>','{','}','[',']','(',')','-',':',';','…','—','–','_','*','+','/','\\','|','!','?','.',',',' ', '\n', '。', '、', '!', '?', ',', ';', ':', '(', ')', '【', '】', '「', '」', '『', '』', '“', '”', '‘', '’', '《', '》', '〈', '〉', '‹', '›', '«', '»', '‟', '„']
|
||||
|
||||
const selfClosingTags = [
|
||||
'br','hr','img','input','meta','link','base','area','col','command','embed','keygen','param','source','track','wbr',
|
||||
//self closing tags defined by HTML5
|
||||
'!',
|
||||
//for doctype <!DOCTYPE html> and comment <!-- -->
|
||||
'user', 'bot', 'char'
|
||||
//special tags for user, bot, and char
|
||||
]
|
||||
|
||||
const checkSelfClosingTag = (dat:string) => {
|
||||
dat = dat.substring(0, 10) //we only need to check the first 10 characters, to avoid checking the whole string
|
||||
dat = dat.toLowerCase() //we don't care about the case
|
||||
for(const tag of selfClosingTags){
|
||||
if(dat.startsWith(tag)){
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function risuFormater(dat:string){
|
||||
const lines:[string,string][] = [['','']] // [type, content]
|
||||
let htmlType = 0 // 0: not inside tag, 1: closing tag, 2: opening tag
|
||||
for(let i=0;i<dat.length;i++){
|
||||
|
||||
const getLastLine = () => {
|
||||
return lines[lines.length-1] ?? [
|
||||
'not-found', ''
|
||||
]
|
||||
}
|
||||
//html tag handling
|
||||
if(dat[i] === '<' && getLastLine()[0] !== 'code-block'){
|
||||
lines.push(['html-tag',''])
|
||||
if(dat[i+1] === '/'){
|
||||
htmlType = 1
|
||||
}
|
||||
else{
|
||||
htmlType = 2
|
||||
}
|
||||
}
|
||||
|
||||
if(dat[i] === '>' && getLastLine()[0] === 'html-tag'){
|
||||
const pop = lines.pop()
|
||||
const tagAttr = pop[1].substring(1).trim()
|
||||
if(htmlType === 1){
|
||||
const pop2 = lines.pop() //probably html-inner
|
||||
const chunk = pop2[1] + pop[1] + '>'
|
||||
if(getLastLine()[0] === ''){
|
||||
lines.push(['html-chunk',chunk])
|
||||
lines.push(['',''])
|
||||
}
|
||||
else{
|
||||
getLastLine()[1] += chunk
|
||||
}
|
||||
continue
|
||||
}
|
||||
else if(checkSelfClosingTag(tagAttr)){
|
||||
const chunk = pop[1] + '>'
|
||||
if(getLastLine()[0] === ''){
|
||||
lines.push(['html-chunk',chunk])
|
||||
lines.push(['',''])
|
||||
}
|
||||
else{
|
||||
getLastLine()[1] += chunk
|
||||
}
|
||||
continue
|
||||
}
|
||||
else{
|
||||
lines.push(['html-inner',pop[1]])
|
||||
}
|
||||
htmlType = 0
|
||||
}
|
||||
|
||||
//code block handling
|
||||
|
||||
if(dat[i] === '`' && dat[i+1] === '`' && dat[i+2] === '`' && getLastLine()[0] === ''){
|
||||
if(getLastLine()[0] === 'code-block'){
|
||||
getLastLine()[1] += '```'
|
||||
lines.push(['',''])
|
||||
}
|
||||
else{
|
||||
lines.push(['code-block','```'])
|
||||
}
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if(dat[i] === '\n' && getLastLine()[0] === ''){
|
||||
lines.push(['newline','\n'])
|
||||
lines.push(['',''])
|
||||
}
|
||||
else if(lines[lines.length-1]){
|
||||
lines[lines.length-1][1] += dat[i]
|
||||
}
|
||||
}
|
||||
|
||||
let result = ''
|
||||
for(let i=0;i<lines.length;i++){
|
||||
if(lines[i][0] !== ''){
|
||||
result += lines[i][1]
|
||||
continue
|
||||
}
|
||||
|
||||
let line = lines[i][1] ??''
|
||||
let isNumbered = false
|
||||
let endMarked = false
|
||||
if(excludesDat.includes(line[0]) || (line[1] === '.' && ['1','2','3','4','5','6','7','8','9'].includes(line[0]))){
|
||||
isNumbered = true
|
||||
}
|
||||
if(line.endsWith('>') || line.endsWith('}') || line.startsWith('<')){
|
||||
endMarked = true
|
||||
}
|
||||
|
||||
if(isNumbered || endMarked){
|
||||
result += line
|
||||
continue
|
||||
}
|
||||
|
||||
let depth = 0
|
||||
let depthChunk:string[] = ['']
|
||||
let depthChunkType:string[] = ['']
|
||||
|
||||
//spaces for detection
|
||||
line = ' ' + line + ' '
|
||||
|
||||
const isNotCharacter = (t:string) => {
|
||||
return symbols.includes(t)
|
||||
}
|
||||
|
||||
for(let j=0;j<line.length;j++){
|
||||
switch(line[j]){
|
||||
case '"':
|
||||
case '“':
|
||||
case '”':{
|
||||
if(depthChunkType[depth] === '"'){
|
||||
depthChunkType.pop()
|
||||
const pop = depthChunk.pop()
|
||||
depth--
|
||||
depthChunk[depth] += `<mark risu-mark="quote2">${pop}${line[j]}</mark>`
|
||||
}
|
||||
else{
|
||||
depthChunkType.push('"')
|
||||
depthChunk.push(line[j])
|
||||
depth++
|
||||
}
|
||||
break
|
||||
}
|
||||
case "'":
|
||||
case '‘':
|
||||
case '’':{
|
||||
if(depthChunkType[depth] === "'"){
|
||||
if(isNotCharacter(line[j-1]) || !isNotCharacter(line[j+1]) || (line[j-2] === 'i' && line[j-1] === 'n')){
|
||||
//this is not a quote
|
||||
depthChunk[depth] += line[j]
|
||||
}
|
||||
else{
|
||||
depthChunkType.pop()
|
||||
const pop = depthChunk.pop()
|
||||
depth--
|
||||
depthChunk[depth] += `<mark risu-mark="quote1">${pop}${line[j]}</mark>`
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(!isNotCharacter(line[j-1]) || isNotCharacter(line[j+1])){
|
||||
//this is not a quote
|
||||
depthChunk[depth] += line[j]
|
||||
}
|
||||
else{
|
||||
depthChunkType.push("'")
|
||||
depthChunk.push(line[j])
|
||||
depth++
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
default:{
|
||||
depthChunk[depth] += line[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lineResult = ''
|
||||
|
||||
while(depthChunk.length > 0){
|
||||
lineResult = depthChunk.pop() + lineResult
|
||||
}
|
||||
|
||||
if(lineResult.startsWith(' ')){
|
||||
lineResult = lineResult.substring(1)
|
||||
}
|
||||
if(lineResult.endsWith(' ')){
|
||||
lineResult = lineResult.substring(0,lineResult.length-1)
|
||||
}
|
||||
|
||||
result += lineResult
|
||||
}
|
||||
|
||||
return result.trim()
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { HypaProcesser } from '../memory/hypamemory'
|
||||
import type { OpenAIChat } from "..";
|
||||
import { stringlizeChat } from "../stringlize";
|
||||
import { get } from "svelte/store";
|
||||
import { getUserName } from "src/ts/util";
|
||||
|
||||
export async function additionalInformations(char: character,chats:Chat,){
|
||||
const processer = new HypaProcesser('MiniLM')
|
||||
@@ -18,7 +19,7 @@ export async function additionalInformations(char: character,chats:Chat,){
|
||||
|
||||
if(!name){
|
||||
if(chat.role === 'user'){
|
||||
name = db.username
|
||||
name = getUserName()
|
||||
}
|
||||
else{
|
||||
name = char.name
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ChatTokenizer, tokenize, tokenizeNum } from "../tokenizer";
|
||||
import { language } from "../../lang";
|
||||
import { alertError } from "../alert";
|
||||
import { loadLoreBookPrompt, loadLoreBookV3Prompt } from "./lorebook";
|
||||
import { findCharacterbyId, getAuthorNoteDefaultText, isLastCharPunctuation, trimUntilPunctuation } from "../util";
|
||||
import { findCharacterbyId, getAuthorNoteDefaultText, getPersonaPrompt, getUserName, isLastCharPunctuation, trimUntilPunctuation } from "../util";
|
||||
import { requestChatData } from "./request";
|
||||
import { stableDiff } from "./stableDiff";
|
||||
import { processScript, processScriptFull, risuChatParser } from "./scripts";
|
||||
@@ -27,6 +27,7 @@ import { addRerolls } from "./prereroll";
|
||||
import { runImageEmbedding } from "./transformers";
|
||||
import { hanuraiMemory } from "./memory/hanuraiMemory";
|
||||
import { hypaMemoryV2 } from "./memory/hypav2";
|
||||
import { runLuaEditTrigger } from "./lua";
|
||||
|
||||
export interface OpenAIChat{
|
||||
role: 'system'|'user'|'assistant'|'function'
|
||||
@@ -371,7 +372,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
if(db.personaPrompt){
|
||||
unformated.personaPrompt.push({
|
||||
role: 'system',
|
||||
content: risuChatParser(db.personaPrompt, {chara: currentChar})
|
||||
content: risuChatParser(getPersonaPrompt(), {chara: currentChar})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -573,7 +574,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
}
|
||||
}
|
||||
|
||||
const examples = exampleMessage(currentChar, db.username)
|
||||
const examples = exampleMessage(currentChar, getUserName())
|
||||
|
||||
for(const example of examples){
|
||||
currentTokens += await tokenizer.tokenizeChat(example)
|
||||
@@ -633,7 +634,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
}
|
||||
}
|
||||
else if(msg.role === 'user'){
|
||||
name = `${db.username}`
|
||||
name = `${getUserName()}`
|
||||
}
|
||||
if(!msg.chatId){
|
||||
msg.chatId = v4()
|
||||
@@ -1046,6 +1047,8 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
data: formated
|
||||
})
|
||||
|
||||
formated = await runLuaEditTrigger(currentChar, 'editRequest', formated)
|
||||
|
||||
//token rechecking
|
||||
let inputTokens = 0
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getChatVar } from "../parser";
|
||||
import { getChatVar, getGlobalChatVar } from "../parser";
|
||||
|
||||
function toRPN(expression:string) {
|
||||
let outputQueue = '';
|
||||
@@ -114,6 +114,13 @@ function executeRPNCalculation(text:string) {
|
||||
return "0"
|
||||
}
|
||||
return parsed.toString()
|
||||
}).replace(/\@([a-zA-Z0-9_]+)/g, (_, p1) => {
|
||||
const v = getGlobalChatVar(p1)
|
||||
const parsed = parseFloat(v)
|
||||
if(isNaN(parsed)){
|
||||
return "0"
|
||||
}
|
||||
return parsed.toString()
|
||||
}).replace(/&&/g, '&').replace(/\|\|/g, '|').replace(/<=/g, '≤').replace(/>=/g, '≥').replace(/==/g, '=').replace(/null/gi, '0')
|
||||
const expression = toRPN(text);
|
||||
const evaluated = calculateRPN(expression);
|
||||
|
||||
664
src/ts/process/lua.ts
Normal file
664
src/ts/process/lua.ts
Normal file
@@ -0,0 +1,664 @@
|
||||
import { getChatVar, risuChatParser, setChatVar, type simpleCharacterArgument } from "../parser";
|
||||
import { LuaEngine, LuaFactory } from "wasmoon";
|
||||
import { DataBase, setDatabase, type Chat, type character, type groupChat } from "../storage/database";
|
||||
import { get } from "svelte/store";
|
||||
import { CurrentCharacter, CurrentChat, CurrentVariablePointer, ReloadGUIPointer, selectedCharID } from "../stores";
|
||||
import { alertError, alertInput, alertNormal } from "../alert";
|
||||
import { HypaProcesser } from "./memory/hypamemory";
|
||||
import { generateAIImage } from "./stableDiff";
|
||||
import { writeInlayImage } from "./files/image";
|
||||
import type { OpenAIChat } from ".";
|
||||
import { requestChatData } from "./request";
|
||||
import { v4 } from "uuid";
|
||||
import { getModuleTriggers } from "./modules";
|
||||
import { Mutex } from "../mutex";
|
||||
|
||||
let luaFactory:LuaFactory
|
||||
let LuaSafeIds = new Set<string>()
|
||||
let LuaEditDisplayIds = new Set<string>()
|
||||
let LuaLowLevelIds = new Set<string>()
|
||||
|
||||
interface LuaEngineState {
|
||||
code: string;
|
||||
engine: LuaEngine;
|
||||
mutex: Mutex;
|
||||
}
|
||||
|
||||
let LuaEngines = new Map<string, LuaEngineState>()
|
||||
|
||||
export async function runLua(code:string, arg:{
|
||||
char?:character|groupChat|simpleCharacterArgument,
|
||||
chat?:Chat
|
||||
setVar?: (key:string, value:string) => void,
|
||||
getVar?: (key:string) => string,
|
||||
lowLevelAccess?: boolean,
|
||||
mode?: string,
|
||||
data?: any
|
||||
}){
|
||||
const char = arg.char ?? get(CurrentCharacter)
|
||||
const setVar = arg.setVar ?? setChatVar
|
||||
const getVar = arg.getVar ?? getChatVar
|
||||
const mode = arg.mode ?? 'manual'
|
||||
const data = arg.data ?? {}
|
||||
let chat = arg.chat ?? get(CurrentChat)
|
||||
let stopSending = false
|
||||
let lowLevelAccess = arg.lowLevelAccess ?? false
|
||||
|
||||
if(!luaFactory){
|
||||
await makeLuaFactory()
|
||||
}
|
||||
let luaEngineState = LuaEngines.get(mode)
|
||||
let wasEmpty = false
|
||||
if (!luaEngineState) {
|
||||
luaEngineState = {
|
||||
code,
|
||||
engine: await luaFactory.createEngine({injectObjects: true}),
|
||||
mutex: new Mutex()
|
||||
}
|
||||
LuaEngines.set(mode, luaEngineState)
|
||||
wasEmpty = true
|
||||
}
|
||||
return await luaEngineState.mutex.runExclusive(async () => {
|
||||
if (wasEmpty || code !== luaEngineState.code) {
|
||||
if (!wasEmpty)
|
||||
luaEngineState.engine.global.close()
|
||||
luaEngineState.engine = await luaFactory.createEngine({injectObjects: true})
|
||||
const luaEngine = luaEngineState.engine
|
||||
luaEngine.global.set('setChatVar', (id:string,key:string, value:string) => {
|
||||
if(!LuaSafeIds.has(id) && !LuaEditDisplayIds.has(id)){
|
||||
return
|
||||
}
|
||||
setVar(key, value)
|
||||
})
|
||||
luaEngine.global.set('getChatVar', (id:string,key:string) => {
|
||||
if(!LuaSafeIds.has(id) && !LuaEditDisplayIds.has(id)){
|
||||
return
|
||||
}
|
||||
return getVar(key)
|
||||
})
|
||||
luaEngine.global.set('stopChat', (id:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
stopSending = true
|
||||
})
|
||||
luaEngine.global.set('alertError', (id:string, value:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
alertError(value)
|
||||
})
|
||||
luaEngine.global.set('alertNormal', (id:string, value:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
alertNormal(value)
|
||||
})
|
||||
luaEngine.global.set('alertInput', (id:string, value:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
return alertInput(value)
|
||||
})
|
||||
luaEngine.global.set('setChat', (id:string, index:number, value:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const message = chat.message?.at(index)
|
||||
if(message){
|
||||
message.data = value
|
||||
}
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
luaEngine.global.set('setChatRole', (id:string, index:number, value:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const message = chat.message?.at(index)
|
||||
if(message){
|
||||
message.role = value === 'user' ? 'user' : 'char'
|
||||
}
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
luaEngine.global.set('cutChat', (id:string, start:number, end:number) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat.message = chat.message.slice(start,end)
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
luaEngine.global.set('removeChat', (id:string, index:number) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat.message.splice(index, 1)
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
luaEngine.global.set('addChat', (id:string, role:string, value:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
let roleData:'user'|'char' = role === 'user' ? 'user' : 'char'
|
||||
chat.message.push({role: roleData, data: value})
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
luaEngine.global.set('insertChat', (id:string, index:number, role:string, value:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
let roleData:'user'|'char' = role === 'user' ? 'user' : 'char'
|
||||
chat.message.splice(index, 0, {role: roleData, data: value})
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
luaEngine.global.set('removeChat', (id:string, index:number) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat.message.splice(index, 1)
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
luaEngine.global.set('getChatLength', (id:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
return chat.message.length
|
||||
})
|
||||
luaEngine.global.set('getFullChatMain', (id:string) => {
|
||||
const data = JSON.stringify(chat.message.map((v) => {
|
||||
return {
|
||||
role: v.role,
|
||||
data: v.data
|
||||
}
|
||||
}))
|
||||
return data
|
||||
})
|
||||
|
||||
luaEngine.global.set('setFullChatMain', (id:string, value:string) => {
|
||||
const realValue = JSON.parse(value)
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat.message = realValue.map((v) => {
|
||||
return {
|
||||
role: v.role,
|
||||
data: v.data
|
||||
}
|
||||
})
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
|
||||
luaEngine.global.set('logMain', (value:string) => {
|
||||
console.log(JSON.parse(value))
|
||||
})
|
||||
|
||||
luaEngine.global.set('reloadDisplay', (id:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
ReloadGUIPointer.set(get(ReloadGUIPointer) + 1)
|
||||
})
|
||||
|
||||
//Low Level Access
|
||||
luaEngine.global.set('similarity', async (id:string, source:string, value:string[]) => {
|
||||
if(!LuaLowLevelIds.has(id)){
|
||||
return
|
||||
}
|
||||
const processer = new HypaProcesser('MiniLM')
|
||||
await processer.addText(value)
|
||||
return await processer.similaritySearch(source)
|
||||
})
|
||||
|
||||
luaEngine.global.set('generateImage', async (id:string, value:string, negValue:string = '') => {
|
||||
if(!LuaLowLevelIds.has(id)){
|
||||
return
|
||||
}
|
||||
const gen = await generateAIImage(value, char as character, negValue, 'inlay')
|
||||
if(!gen){
|
||||
return 'Error: Image generation failed'
|
||||
}
|
||||
const imgHTML = new Image()
|
||||
imgHTML.src = gen
|
||||
const inlay = await writeInlayImage(imgHTML)
|
||||
return `{{inlay::${inlay}}}`
|
||||
})
|
||||
|
||||
luaEngine.global.set('LLMMain', async (id:string, promptStr:string) => {
|
||||
let prompt:{
|
||||
role: string,
|
||||
content: string
|
||||
}[] = JSON.parse(promptStr)
|
||||
if(!LuaLowLevelIds.has(id)){
|
||||
return
|
||||
}
|
||||
let promptbody:OpenAIChat[] = prompt.map((dict) => {
|
||||
let role:'system'|'user'|'assistant' = 'assistant'
|
||||
switch(dict['role']){
|
||||
case 'system':
|
||||
case 'sys':
|
||||
role = 'system'
|
||||
break
|
||||
case 'user':
|
||||
role = 'user'
|
||||
break
|
||||
case 'assistant':
|
||||
case 'bot':
|
||||
case 'char':{
|
||||
role = 'assistant'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: dict['content'] ?? '',
|
||||
role: role,
|
||||
}
|
||||
})
|
||||
const result = await requestChatData({
|
||||
formated: promptbody,
|
||||
bias: {},
|
||||
useStreaming: false,
|
||||
noMultiGen: true,
|
||||
}, 'model')
|
||||
|
||||
if(result.type === 'fail'){
|
||||
return JSON.stringify({
|
||||
success: false,
|
||||
result: 'Error: ' + result.result
|
||||
})
|
||||
}
|
||||
|
||||
if(result.type === 'streaming' || result.type === 'multiline'){
|
||||
return JSON.stringify({
|
||||
success: false,
|
||||
result: result.result
|
||||
})
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
success: true,
|
||||
result: result.result
|
||||
})
|
||||
})
|
||||
|
||||
luaEngine.global.set('simpleLLM', async (id:string, prompt:string) => {
|
||||
if(!LuaLowLevelIds.has(id)){
|
||||
return
|
||||
}
|
||||
const result = await requestChatData({
|
||||
formated: [{
|
||||
role: 'user',
|
||||
content: prompt
|
||||
}],
|
||||
bias: {},
|
||||
useStreaming: false,
|
||||
noMultiGen: true,
|
||||
}, 'model')
|
||||
|
||||
if(result.type === 'fail'){
|
||||
return {
|
||||
success: false,
|
||||
result: 'Error: ' + result.result
|
||||
}
|
||||
}
|
||||
|
||||
if(result.type === 'streaming' || result.type === 'multiline'){
|
||||
return {
|
||||
success: false,
|
||||
result: result.result
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: result.result
|
||||
}
|
||||
})
|
||||
|
||||
luaEngine.global.set('getName', async (id:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
const char = db.characters[selectedChar]
|
||||
return char.name
|
||||
})
|
||||
|
||||
luaEngine.global.set('setName', async (id:string, name:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
if(typeof name !== 'string'){
|
||||
throw('Invalid data type')
|
||||
}
|
||||
db.characters[selectedChar].name = name
|
||||
setDatabase(db)
|
||||
})
|
||||
|
||||
luaEngine.global.set('setDescription', async (id:string, desc:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
const char =db.characters[selectedChar]
|
||||
if(typeof data !== 'string'){
|
||||
throw('Invalid data type')
|
||||
}
|
||||
if(char.type === 'group'){
|
||||
throw('Character is a group')
|
||||
}
|
||||
char.desc = desc
|
||||
db.characters[selectedChar] = char
|
||||
setDatabase(db)
|
||||
})
|
||||
|
||||
luaEngine.global.set('setCharacterFirstMessage', async (id:string, data:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
const char = db.characters[selectedChar]
|
||||
if(typeof data !== 'string'){
|
||||
return false
|
||||
}
|
||||
char.firstMessage = data
|
||||
db.characters[selectedChar] = char
|
||||
setDatabase(db)
|
||||
return true
|
||||
})
|
||||
|
||||
luaEngine.global.set('getCharacterFirstMessage', async (id:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
const char = db.characters[selectedChar]
|
||||
return char.firstMessage
|
||||
})
|
||||
|
||||
luaEngine.global.set('getBackgroundEmbedding', async (id:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
const char = db.characters[selectedChar]
|
||||
return char.backgroundHTML
|
||||
})
|
||||
|
||||
luaEngine.global.set('setBackgroundEmbedding', async (id:string, data:string) => {
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
const db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
if(typeof data !== 'string'){
|
||||
return false
|
||||
}
|
||||
db.characters[selectedChar].backgroundHTML = data
|
||||
setDatabase(db)
|
||||
return true
|
||||
})
|
||||
|
||||
await luaEngine.doString(luaCodeWarper(code))
|
||||
luaEngineState.code = code
|
||||
}
|
||||
let accessKey = v4()
|
||||
if(mode === 'editDisplay'){
|
||||
LuaEditDisplayIds.add(accessKey)
|
||||
}
|
||||
else{
|
||||
LuaSafeIds.add(accessKey)
|
||||
if(lowLevelAccess){
|
||||
LuaLowLevelIds.add(accessKey)
|
||||
}
|
||||
}
|
||||
let res:any
|
||||
const luaEngine = luaEngineState.engine
|
||||
try {
|
||||
switch(mode){
|
||||
case 'input':{
|
||||
const func = luaEngine.global.get('onInput')
|
||||
if(func){
|
||||
res = await func(accessKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'output':{
|
||||
const func = luaEngine.global.get('onOutput')
|
||||
if(func){
|
||||
res = await func(accessKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'start':{
|
||||
const func = luaEngine.global.get('onStart')
|
||||
if(func){
|
||||
res = await func(accessKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'editRequest':
|
||||
case 'editDisplay':
|
||||
case 'editInput':
|
||||
case 'editOutput':{
|
||||
const func = luaEngine.global.get('callListenMain')
|
||||
if(func){
|
||||
res = await func(mode, accessKey, JSON.stringify(data))
|
||||
res = JSON.parse(res)
|
||||
}
|
||||
break
|
||||
}
|
||||
default:{
|
||||
const func = luaEngine.global.get(mode)
|
||||
if(func){
|
||||
res = await func(accessKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if(res === false){
|
||||
stopSending = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
LuaSafeIds.delete(accessKey)
|
||||
LuaLowLevelIds.delete(accessKey)
|
||||
|
||||
return {
|
||||
stopSending, chat, res
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function makeLuaFactory(){
|
||||
luaFactory = new LuaFactory()
|
||||
async function mountFile(name:string){
|
||||
let code = ''
|
||||
for(let i = 0; i < 3; i++){
|
||||
try {
|
||||
const res = await fetch('/lua/' + name)
|
||||
if(res.status >= 200 && res.status < 300){
|
||||
code = await res.text()
|
||||
break
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
await luaFactory.mountFile(name,code)
|
||||
}
|
||||
|
||||
await mountFile('json.lua')
|
||||
}
|
||||
|
||||
function luaCodeWarper(code:string){
|
||||
return `
|
||||
json = require 'json'
|
||||
|
||||
function getFullChat(id)
|
||||
return json.decode(getFullChatMain(id))
|
||||
end
|
||||
|
||||
function setFullChat(id, value)
|
||||
setFullChatMain(id, json.encode(value))
|
||||
end
|
||||
|
||||
function log(value)
|
||||
logMain(json.encode(value))
|
||||
end
|
||||
|
||||
function LLM(id, prompt)
|
||||
return json.decode(LLMMain(id, json.encode(prompt)):await())
|
||||
end
|
||||
|
||||
local editRequestFuncs = {}
|
||||
local editDisplayFuncs = {}
|
||||
local editInputFuncs = {}
|
||||
local editOutputFuncs = {}
|
||||
|
||||
function listenEdit(type, func)
|
||||
if type == 'editRequest' then
|
||||
editRequestFuncs[#editRequestFuncs + 1] = func
|
||||
return
|
||||
end
|
||||
|
||||
if type == 'editDisplay' then
|
||||
editDisplayFuncs[#editDisplayFuncs + 1] = func
|
||||
return
|
||||
end
|
||||
|
||||
if type == 'editInput' then
|
||||
editInputFuncs[#editInputFuncs + 1] = func
|
||||
return
|
||||
end
|
||||
|
||||
if type == 'editOutput' then
|
||||
editOutputFuncs[#editOutputFuncs + 1] = func
|
||||
return
|
||||
end
|
||||
|
||||
throw('Invalid type')
|
||||
end
|
||||
|
||||
function getState(id, name)
|
||||
local escapedName = "__"..name
|
||||
return json.decode(getChatVar(id, escapedName))
|
||||
end
|
||||
|
||||
function setState(id, name, value)
|
||||
local escapedName = "__"..name
|
||||
setChatVar(id, escapedName, json.encode(value))
|
||||
end
|
||||
|
||||
function async(callback)
|
||||
return function(...)
|
||||
local co = coroutine.create(callback)
|
||||
local safe, result = coroutine.resume(co, ...)
|
||||
|
||||
return Promise.create(function(resolve, reject)
|
||||
local checkresult
|
||||
local step = function()
|
||||
if coroutine.status(co) == "dead" then
|
||||
local send = safe and resolve or reject
|
||||
return send(result)
|
||||
end
|
||||
|
||||
safe, result = coroutine.resume(co)
|
||||
checkresult()
|
||||
end
|
||||
|
||||
checkresult = function()
|
||||
if safe and result == Promise.resolve(result) then
|
||||
result:finally(step)
|
||||
else
|
||||
step()
|
||||
end
|
||||
end
|
||||
|
||||
checkresult()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
callListenMain = async(function(type, id, value)
|
||||
local realValue = json.decode(value)
|
||||
|
||||
if type == 'editRequest' then
|
||||
for _, func in ipairs(editRequestFuncs) do
|
||||
realValue = func(id, realValue)
|
||||
end
|
||||
end
|
||||
|
||||
if type == 'editDisplay' then
|
||||
for _, func in ipairs(editDisplayFuncs) do
|
||||
realValue = func(id, realValue)
|
||||
print(realValue)
|
||||
end
|
||||
end
|
||||
|
||||
if type == 'editInput' then
|
||||
for _, func in ipairs(editInputFuncs) do
|
||||
realValue = func(id, realValue)
|
||||
end
|
||||
end
|
||||
|
||||
if type == 'editOutput' then
|
||||
for _, func in ipairs(editOutputFuncs) do
|
||||
realValue = func(id, realValue)
|
||||
end
|
||||
end
|
||||
|
||||
return json.encode(realValue)
|
||||
end)
|
||||
|
||||
${code}
|
||||
`
|
||||
}
|
||||
|
||||
export async function runLuaEditTrigger<T extends any>(char:character|groupChat|simpleCharacterArgument, mode:string, content:T):Promise<T>{
|
||||
let data = content
|
||||
|
||||
switch(mode){
|
||||
case 'editinput':
|
||||
mode = 'editInput'
|
||||
break
|
||||
case 'editoutput':
|
||||
mode = 'editOutput'
|
||||
break
|
||||
case 'editdisplay':
|
||||
mode = 'editDisplay'
|
||||
break
|
||||
case 'editprocess':
|
||||
return content
|
||||
}
|
||||
|
||||
try {
|
||||
const triggers = char.type === 'group' ? (getModuleTriggers()) : (char.triggerscript.map((v) => {
|
||||
v.lowLevelAccess = false
|
||||
return v
|
||||
}).concat(getModuleTriggers()))
|
||||
|
||||
for(let trigger of triggers){
|
||||
if(trigger?.effect?.[0]?.type === 'triggerlua'){
|
||||
const runResult = await runLua(trigger.effect[0].code, {
|
||||
char: char,
|
||||
lowLevelAccess: false,
|
||||
mode: mode,
|
||||
data: data
|
||||
})
|
||||
data = runResult.res ?? data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return data
|
||||
} catch (error) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { HypaProcesser } from "./hypamemory";
|
||||
import { stringlizeChat } from "../stringlize";
|
||||
import { globalFetch } from "src/ts/storage/globalApi";
|
||||
import { runSummarizer } from "../transformers";
|
||||
import { getUserName } from "src/ts/util";
|
||||
|
||||
export async function supaMemory(
|
||||
chats:OpenAIChat[],
|
||||
@@ -329,7 +330,7 @@ export async function supaMemory(
|
||||
if((chunkSize + tokens) > maxChunkSize){
|
||||
if(stringlizedChat === ''){
|
||||
if(cont.role !== 'function' && cont.role !== 'system'){
|
||||
stringlizedChat += `${cont.role === 'assistant' ? char.type === 'group' ? '' : char.name : db.username}: ${cont.content}\n\n`
|
||||
stringlizedChat += `${cont.role === 'assistant' ? char.type === 'group' ? '' : char.name : getUserName()}: ${cont.content}\n\n`
|
||||
spiceLen += 1
|
||||
currentTokens -= tokens
|
||||
chunkSize += tokens
|
||||
@@ -338,7 +339,7 @@ export async function supaMemory(
|
||||
lastId = cont.memo
|
||||
break
|
||||
}
|
||||
stringlizedChat += `${cont.role === 'assistant' ? char.type === 'group' ? '' : char.name : db.username}: ${cont.content}\n\n`
|
||||
stringlizedChat += `${cont.role === 'assistant' ? char.type === 'group' ? '' : char.name : getUserName()}: ${cont.content}\n\n`
|
||||
spiceLen += 1
|
||||
currentTokens -= tokens
|
||||
chunkSize += tokens
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { OpenAIChat } from ".."
|
||||
import { get } from "svelte/store"
|
||||
import { globalFetch } from "src/ts/storage/globalApi"
|
||||
import { alertError, alertInput, alertNormal, alertWait } from "src/ts/alert"
|
||||
import { sleep } from "src/ts/util"
|
||||
import { getUserName, sleep } from "src/ts/util"
|
||||
|
||||
export function stringlizeNAIChat(formated:OpenAIChat[], char:string, continued: boolean){
|
||||
const db = get(DataBase)
|
||||
@@ -34,7 +34,7 @@ export function stringlizeNAIChat(formated:OpenAIChat[], char:string, continued:
|
||||
res += '> '
|
||||
}
|
||||
if(db.NAIappendName){
|
||||
res += db.username + ": "
|
||||
res += getUserName() + ": "
|
||||
}
|
||||
res += form.content
|
||||
resultString.push(res)
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface RisuModule{
|
||||
trigger?: triggerscript[]
|
||||
id: string
|
||||
lowLevelAccess?: boolean
|
||||
hideIcon?: boolean
|
||||
backgroundEmbedding?:string
|
||||
}
|
||||
|
||||
export async function exportModule(module:RisuModule){
|
||||
|
||||
@@ -1525,11 +1525,23 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
let lastChatPrompt = ''
|
||||
let preamble = ''
|
||||
|
||||
const lastChat = formated[formated.length-1]
|
||||
let lastChat = formated[formated.length-1]
|
||||
if(lastChat.role === 'user'){
|
||||
lastChatPrompt = lastChat.content
|
||||
formated.pop()
|
||||
}
|
||||
else{
|
||||
while(lastChat.role !== 'user'){
|
||||
lastChat = formated.pop()
|
||||
if(!lastChat){
|
||||
return {
|
||||
type: 'fail',
|
||||
result: 'Cohere requires a user message to generate a response'
|
||||
}
|
||||
}
|
||||
lastChatPrompt = (lastChat.role === 'user' ? '' : `${lastChat.role}: `) + '\n' + lastChat.content + lastChatPrompt
|
||||
}
|
||||
}
|
||||
|
||||
const firstChat = formated[0]
|
||||
if(firstChat.role === 'system'){
|
||||
@@ -1537,29 +1549,36 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
formated.shift()
|
||||
}
|
||||
|
||||
//reformat chat
|
||||
|
||||
|
||||
|
||||
|
||||
let body = {
|
||||
message: lastChatPrompt,
|
||||
chat_history: formated.map((v) => {
|
||||
if(v.role === 'assistant'){
|
||||
return {
|
||||
role: 'CHATBOT',
|
||||
content: v.content
|
||||
message: v.content
|
||||
}
|
||||
}
|
||||
if(v.role === 'system'){
|
||||
return {
|
||||
role: 'SYSTEM',
|
||||
content: v.content
|
||||
message: v.content
|
||||
}
|
||||
}
|
||||
if(v.role === 'user'){
|
||||
return {
|
||||
role: 'USER',
|
||||
content: v.content
|
||||
message: v.content
|
||||
}
|
||||
}
|
||||
return null
|
||||
}).filter((v) => v !== null),
|
||||
}).filter((v) => v !== null).filter((v) => {
|
||||
return v.message
|
||||
}),
|
||||
temperature: temperature,
|
||||
k: db.top_k,
|
||||
p: (db.top_p > 0.99) ? 0.99 : (db.top_p < 0.01) ? 0.01 : db.top_p,
|
||||
@@ -1568,10 +1587,17 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
}
|
||||
|
||||
if(preamble){
|
||||
// @ts-ignore
|
||||
body.preamble = preamble
|
||||
if(body.chat_history.length > 0){
|
||||
// @ts-ignore
|
||||
body.preamble = preamble
|
||||
}
|
||||
else{
|
||||
body.message = `system: ${preamble}`
|
||||
}
|
||||
}
|
||||
|
||||
console.log(body)
|
||||
|
||||
const res = await globalFetch('https://api.cohere.com/v1/chat', {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -1818,10 +1844,15 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
"anthropic.claude-v2:1",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
"anthropic.claude-3-opus-20240229-v1:0"
|
||||
];
|
||||
|
||||
const awsModel = raiModel.includes("opus") ? modelIDs[4] : raiModel.includes("sonnet") ? modelIDs[3] : modelIDs[2];
|
||||
const awsModel =
|
||||
raiModel.includes("3-opus") ? modelIDs[5] :
|
||||
raiModel.includes("3-5-sonnet") ? modelIDs[4] :
|
||||
raiModel.includes("3-sonnet") ? modelIDs[3] :
|
||||
modelIDs[2];
|
||||
const url = `https://${host}/model/${awsModel}/invoke${stream ? "-with-response-stream" : ""}`
|
||||
|
||||
const params = {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { assetRegex, risuChatParser as risuChatParserOrg, type simpleCharacterAr
|
||||
import { runCharacterJS } from "../plugins/embedscript";
|
||||
import { getModuleRegexScripts } from "./modules";
|
||||
import { HypaProcesser } from "./memory/hypamemory";
|
||||
import { runLuaEditTrigger } from "./lua";
|
||||
|
||||
const dreg = /{{data}}/g
|
||||
const randomness = /\|\|\|/g
|
||||
@@ -56,6 +57,8 @@ export async function importRegex(){
|
||||
}
|
||||
}
|
||||
|
||||
let bestMatchCache = new Map<string, string>()
|
||||
|
||||
export async function processScriptFull(char:character|groupChat|simpleCharacterArgument, data:string, mode:ScriptMode, chatID = -1){
|
||||
let db = get(DataBase)
|
||||
let emoChanged = false
|
||||
@@ -65,6 +68,7 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
mode,
|
||||
data,
|
||||
})
|
||||
data = await runLuaEditTrigger(char, mode, data)
|
||||
if(scripts.length === 0){
|
||||
return {data, emoChanged}
|
||||
}
|
||||
@@ -193,7 +197,7 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
}
|
||||
}
|
||||
else{
|
||||
data = risuChatParser(data.replace(reg, outScript))
|
||||
data = risuChatParser(data.replace(reg, outScript), { chatID: chatID })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,11 +214,18 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
for(const match of matches){
|
||||
const type = match[1]
|
||||
const assetName = match[2]
|
||||
if(!assetNames.includes(assetName)){
|
||||
const searched = await processer.similaritySearch(assetName)
|
||||
const bestMatch = searched[0]
|
||||
if(bestMatch){
|
||||
data = data.replaceAll(match[0], `{{${type}::${bestMatch}}}`)
|
||||
const cacheKey = char.chaId + '::' + assetName
|
||||
if(type !== 'emotion' && type !== 'source'){
|
||||
if(bestMatchCache.has(cacheKey)){
|
||||
data = data.replaceAll(match[0], `{{${type}::${bestMatchCache.get(cacheKey)}}}`)
|
||||
}
|
||||
else if(!assetNames.includes(assetName)){
|
||||
const searched = await processer.similaritySearch(assetName)
|
||||
const bestMatch = searched[0]
|
||||
if(bestMatch){
|
||||
data = data.replaceAll(match[0], `{{${type}::${bestMatch}}}`)
|
||||
bestMatchCache.set(cacheKey, bestMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,6 +413,80 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
|
||||
return returnSdData
|
||||
|
||||
|
||||
}
|
||||
if(db.sdProvider === 'comfy'){
|
||||
const {workflow, posNodeID, posInputName, negNodeID, negInputName} = db.comfyConfig
|
||||
const baseUrl = new URL(db.comfyUiUrl)
|
||||
|
||||
const createUrl = (pathname: string, params: Record<string, string> = {}) => {
|
||||
const url = new URL(pathname, baseUrl)
|
||||
url.search = new URLSearchParams(params).toString()
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
const fetchWrapper = async (url: string, options = {}) => {
|
||||
console.log(url)
|
||||
const response = await globalFetch(url, options)
|
||||
if (!response.ok) {
|
||||
console.log(JSON.stringify(response.data))
|
||||
throw new Error(JSON.stringify(response.data))
|
||||
}
|
||||
return response.data
|
||||
}
|
||||
|
||||
try {
|
||||
const prompt = JSON.parse(workflow)
|
||||
prompt[posNodeID].inputs[posInputName] = genPrompt
|
||||
prompt[negNodeID].inputs[negInputName] = neg
|
||||
|
||||
const { prompt_id: id } = await fetchWrapper(createUrl('/prompt'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: { 'prompt': prompt }
|
||||
})
|
||||
console.log(`prompt id: ${id}`)
|
||||
|
||||
let item
|
||||
|
||||
const startTime = Date.now()
|
||||
const timeout = db.comfyConfig.timeout * 1000
|
||||
while (!(item = (await (await fetch(createUrl('/history'), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'GET'})).json())[id])) {
|
||||
console.log("Checking /history...")
|
||||
if (Date.now() - startTime >= timeout) {
|
||||
alertError("Error: Image generation took longer than expected.");
|
||||
return false
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 1000))
|
||||
} // Check history until the generation is complete.
|
||||
const genImgInfo = Object.values(item.outputs).flatMap((output: any) => output.images)[0];
|
||||
|
||||
const imgResponse = await fetch(createUrl('/view', {
|
||||
filename: genImgInfo.filename,
|
||||
subfolder: genImgInfo.subfolder,
|
||||
type: genImgInfo.type
|
||||
}), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'GET'})
|
||||
const img64 = Buffer.from(await imgResponse.arrayBuffer()).toString('base64')
|
||||
|
||||
if(returnSdData === 'inlay'){
|
||||
return `data:image/png;base64,${img64}`
|
||||
}
|
||||
else {
|
||||
let charemotions = get(CharEmotion)
|
||||
const img = `data:image/png;base64,${img64}`
|
||||
const emos:[string, string,number][] = [[img, img, Date.now()]]
|
||||
charemotions[currentChar.chaId] = emos
|
||||
CharEmotion.set(charemotions)
|
||||
}
|
||||
|
||||
return returnSdData
|
||||
} catch (error) {
|
||||
alertError(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { get } from "svelte/store";
|
||||
import type { OpenAIChat } from ".";
|
||||
import { DataBase } from "../storage/database";
|
||||
import { getUserName } from "../util";
|
||||
|
||||
export function multiChatReplacer(){
|
||||
|
||||
@@ -57,7 +58,7 @@ export function stringlizeChatOba(formated:OpenAIChat[], characterName:string, s
|
||||
let name = form.name
|
||||
if(form.role === 'user'){
|
||||
prefix = appendWhitespace(suggesting ? assistantPrefix : userPrefix, seperator)
|
||||
name ??= `${db.username}`
|
||||
name ??= `${getUserName()}`
|
||||
name += ': '
|
||||
}
|
||||
else if(form.role === 'assistant'){
|
||||
@@ -80,7 +81,7 @@ export function stringlizeChatOba(formated:OpenAIChat[], characterName:string, s
|
||||
if(!continued){
|
||||
if(db.ooba.formating.useName){
|
||||
if (suggesting){
|
||||
resultString.push(appendWhitespace(assistantPrefix, seperator) + `${db.username}:\n` + db.autoSuggestPrefix)
|
||||
resultString.push(appendWhitespace(assistantPrefix, seperator) + `${getUserName()}:\n` + db.autoSuggestPrefix)
|
||||
} else {
|
||||
resultString.push(assistantPrefix + `${characterName}:`)
|
||||
}
|
||||
@@ -173,17 +174,17 @@ export function getUnstringlizerChunks(formated:OpenAIChat[], char:string, mode:
|
||||
chunks.push(`${char}: `)
|
||||
}
|
||||
}
|
||||
if(db.username){
|
||||
charNames.push(db.username)
|
||||
if(getUserName()){
|
||||
charNames.push(getUserName())
|
||||
if(mode === 'ain'){
|
||||
chunks.push(`${db.username} `)
|
||||
chunks.push(`${db.username} `)
|
||||
chunks.push(`${getUserName()} `)
|
||||
chunks.push(`${getUserName()} `)
|
||||
}
|
||||
else{
|
||||
chunks.push(`${db.username}:`)
|
||||
chunks.push(`${db.username}:`)
|
||||
chunks.push(`${db.username}: `)
|
||||
chunks.push(`${db.username}: `)
|
||||
chunks.push(`${getUserName()}:`)
|
||||
chunks.push(`${getUserName()}:`)
|
||||
chunks.push(`${getUserName()}: `)
|
||||
chunks.push(`${getUserName()}: `)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +224,7 @@ export function stringlizeAINChat(formated:OpenAIChat[], char:string, continued:
|
||||
resultString.push(form.content)
|
||||
}
|
||||
else if(form.role === 'user'){
|
||||
resultString.push(...formatToAIN(db.username, form.content))
|
||||
resultString.push(...formatToAIN(getUserName(), form.content))
|
||||
}
|
||||
else if(form.name || form.role === 'assistant'){
|
||||
resultString.push(...formatToAIN(form.name ?? char, form.content))
|
||||
@@ -315,7 +316,7 @@ export function unstringlizeAIN(data:string,formated:OpenAIChat[], char:string =
|
||||
}
|
||||
}
|
||||
else{
|
||||
const role = (cont.character.trim() === db.username ? 'user' : 'char')
|
||||
const role = (cont.character.trim() === getUserName() ? 'user' : 'char')
|
||||
result.push([
|
||||
role,
|
||||
`「${cont.content}」`
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { OpenAIChat } from '..';
|
||||
import { get } from 'svelte/store';
|
||||
import { DataBase } from 'src/ts/storage/database';
|
||||
import { CurrentCharacter } from 'src/ts/stores';
|
||||
import { getUserName } from 'src/ts/util';
|
||||
|
||||
export const chatTemplates = {
|
||||
'llama3': "{% set bos_token = '<|begin_of_text|>' %}{% set loop_messages = messages %}{% for message in loop_messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{% if loop.index0 == 0 %}{% set content = bos_token + content %}{% endif %}{{ content }}{% endfor %}{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}",
|
||||
@@ -93,6 +94,6 @@ export const applyChatTemplate = (messages:OpenAIChat[]) => {
|
||||
"messages": formatedMessages,
|
||||
"add_generation_prompt": true,
|
||||
"risu_char": currentChar.name,
|
||||
"risu_user": db.username
|
||||
"risu_user": getUserName()
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { risuChatParser } from "../parser";
|
||||
import { parseChatML, risuChatParser, risuCommandParser } from "../parser";
|
||||
import { DataBase, type Chat, type character } from "../storage/database";
|
||||
import { tokenize } from "../tokenizer";
|
||||
import { getModuleTriggers } from "./modules";
|
||||
@@ -12,6 +12,8 @@ import { HypaProcesser } from "./memory/hypamemory";
|
||||
import { requestChatData } from "./request";
|
||||
import { generateAIImage } from "./stableDiff";
|
||||
import { writeInlayImage } from "./files/image";
|
||||
import { runLua } from "./lua";
|
||||
|
||||
|
||||
export interface triggerscript{
|
||||
comment: string;
|
||||
@@ -23,7 +25,7 @@ export interface triggerscript{
|
||||
|
||||
export type triggerCondition = triggerConditionsVar|triggerConditionsExists|triggerConditionsChatIndex
|
||||
|
||||
export type triggerEffect = triggerEffectCutChat|triggerEffectModifyChat|triggerEffectImgGen|triggerEffectRegex|triggerEffectRunLLM|triggerEffectCheckSimilarity|triggerEffectSendAIprompt|triggerEffectShowAlert|triggerEffectSetvar|triggerEffectSystemPrompt|triggerEffectImpersonate|triggerEffectCommand|triggerEffectStop|triggerEffectRunTrigger
|
||||
export type triggerEffect = triggerCode|triggerEffectCutChat|triggerEffectModifyChat|triggerEffectImgGen|triggerEffectRegex|triggerEffectRunLLM|triggerEffectCheckSimilarity|triggerEffectSendAIprompt|triggerEffectShowAlert|triggerEffectSetvar|triggerEffectSystemPrompt|triggerEffectImpersonate|triggerEffectCommand|triggerEffectStop|triggerEffectRunTrigger
|
||||
|
||||
export type triggerConditionsVar = {
|
||||
type:'var'|'value'
|
||||
@@ -32,6 +34,11 @@ export type triggerConditionsVar = {
|
||||
operator:'='|'!='|'>'|'<'|'>='|'<='|'null'|'true'
|
||||
}
|
||||
|
||||
export type triggerCode = {
|
||||
type: 'triggercode'|'triggerlua',
|
||||
code: string
|
||||
}
|
||||
|
||||
export type triggerConditionsChatIndex = {
|
||||
type:'chatindex'
|
||||
value:string
|
||||
@@ -197,7 +204,10 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
|
||||
|
||||
for(const trigger of triggers){
|
||||
if(arg.manualName){
|
||||
if(trigger.effect[0]?.type === 'triggercode' || trigger.effect[0]?.type === 'triggerlua'){
|
||||
//
|
||||
}
|
||||
else if(arg.manualName){
|
||||
if(trigger.comment !== arg.manualName){
|
||||
continue
|
||||
}
|
||||
@@ -238,22 +248,22 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
}
|
||||
break
|
||||
case '>':
|
||||
if(Number(varValue) > Number(conditionValue)){
|
||||
if(Number(varValue) <= Number(conditionValue)){
|
||||
pass = false
|
||||
}
|
||||
break
|
||||
case '<':
|
||||
if(Number(varValue) < Number(conditionValue)){
|
||||
pass = false
|
||||
}
|
||||
break
|
||||
case '>=':
|
||||
if(Number(varValue) >= Number(conditionValue)){
|
||||
pass = false
|
||||
}
|
||||
break
|
||||
case '>=':
|
||||
if(Number(varValue) < Number(conditionValue)){
|
||||
pass = false
|
||||
}
|
||||
break
|
||||
case '<=':
|
||||
if(Number(varValue) <= Number(conditionValue)){
|
||||
if(Number(varValue) > Number(conditionValue)){
|
||||
pass = false
|
||||
}
|
||||
break
|
||||
@@ -393,6 +403,7 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
case 'input':{
|
||||
const val = await alertInput(effectValue)
|
||||
setVar(inputVar, val)
|
||||
break;
|
||||
}
|
||||
case 'select':{
|
||||
const val = await alertSelect(effectValue.split('§'))
|
||||
@@ -416,41 +427,10 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
}
|
||||
const effectValue = risuChatParser(effect.value,{chara:char})
|
||||
const varName = effect.inputVar
|
||||
let promptbody:OpenAIChat[] = []
|
||||
let currentRole:'user'|'assistant'|'system'
|
||||
|
||||
const splited = effectValue.split('\n')
|
||||
|
||||
for(let i = 0; i < splited.length; i++){
|
||||
const line = splited[i]
|
||||
if(line.startsWith('@@role ')){
|
||||
const role = line.split(' ')[1]
|
||||
switch(role){
|
||||
case 'user':
|
||||
case 'assistant':
|
||||
case 'system':
|
||||
currentRole = role
|
||||
break
|
||||
default:
|
||||
currentRole = 'system'
|
||||
break
|
||||
}
|
||||
promptbody.push({role: currentRole, content: ''})
|
||||
continue
|
||||
}
|
||||
else if(promptbody.length === 0){
|
||||
promptbody.push({role: 'system', content: line})
|
||||
}
|
||||
else{
|
||||
promptbody[promptbody.length - 1].content += line
|
||||
}
|
||||
let promptbody:OpenAIChat[] = parseChatML(effectValue)
|
||||
if(!promptbody){
|
||||
promptbody = [{role:'user', content:effectValue}]
|
||||
}
|
||||
|
||||
promptbody = promptbody.map((e) => {
|
||||
e.content = e.content.trim()
|
||||
return e
|
||||
}).filter((e) => e.content.length > 0)
|
||||
|
||||
const result = await requestChatData({
|
||||
formated: promptbody,
|
||||
bias: {},
|
||||
@@ -518,6 +498,35 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
setVar(effect.inputVar, res)
|
||||
break
|
||||
}
|
||||
|
||||
case 'triggercode':{
|
||||
const triggerCodeResult = await risuCommandParser(effect.code,{
|
||||
chara:char,
|
||||
lowLevelAccess: trigger.lowLevelAccess,
|
||||
funcName: mode
|
||||
})
|
||||
|
||||
if(triggerCodeResult['__stop_chat__'] === '1'){
|
||||
stopSending = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'triggerlua':{
|
||||
const triggerCodeResult = await runLua(effect.code,{
|
||||
lowLevelAccess: trigger.lowLevelAccess,
|
||||
mode: mode === 'manual' ? arg.manualName : mode,
|
||||
setVar: setVar,
|
||||
getVar: getVar,
|
||||
char: char,
|
||||
chat: chat,
|
||||
})
|
||||
|
||||
if(triggerCodeResult.stopSending){
|
||||
stopSending = true
|
||||
}
|
||||
chat = triggerCodeResult.chat
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { get } from "svelte/store"
|
||||
import { get, writable } from "svelte/store"
|
||||
import { DataBase } from "./database"
|
||||
import { hubURL } from "../characterCards"
|
||||
import localforage from "localforage"
|
||||
@@ -7,6 +7,10 @@ import { forageStorage, getUnpargeables, replaceDbResources } from "./globalApi"
|
||||
import { encodeRisuSave } from "./risuSave"
|
||||
import { v4 } from "uuid"
|
||||
|
||||
export const AccountWarning = writable('')
|
||||
|
||||
let seenWarnings:string[] = []
|
||||
|
||||
export class AccountStorage{
|
||||
auth:string
|
||||
usingSync:boolean
|
||||
@@ -25,10 +29,23 @@ export class AccountStorage{
|
||||
'X-Format': 'nocheck'
|
||||
}
|
||||
})
|
||||
if(da.headers.get('Content-Type') === 'application/json'){
|
||||
const json = (await da.json())
|
||||
if(json?.warning){
|
||||
if(!seenWarnings.includes(json.warning)){
|
||||
seenWarnings.push(json.warning)
|
||||
AccountWarning.set(json.warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(da.status === 304){
|
||||
return key
|
||||
}
|
||||
if(da.status === 403){
|
||||
if(da.headers.get('x-risu-status') === 'warn'){
|
||||
return
|
||||
}
|
||||
localStorage.setItem("fallbackRisuToken",await alertLogin())
|
||||
this.checkAuth()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export const DataBase = writable({} as any as Database)
|
||||
export const loadedStore = writable(false)
|
||||
export let appVer = "122.1.2"
|
||||
export let webAppSubVer = ''
|
||||
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { checkNullish, decryptBuffer, encryptBuffer, selectSingleFile } from '../util';
|
||||
import { changeLanguage, language } from '../../lang';
|
||||
@@ -12,11 +17,6 @@ import { defaultColorScheme, type ColorScheme } from '../gui/colorscheme';
|
||||
import type { PromptItem, PromptSettings } from '../process/prompt';
|
||||
import type { OobaChatCompletionRequestParams } from '../model/ooba';
|
||||
|
||||
export const DataBase = writable({} as any as Database)
|
||||
export const loadedStore = writable(false)
|
||||
export let appVer = "115.0.1"
|
||||
export let webAppSubVer = ''
|
||||
|
||||
export function setDatabase(data:Database){
|
||||
if(checkNullish(data.characters)){
|
||||
data.characters = []
|
||||
@@ -419,6 +419,16 @@ export function setDatabase(data:Database){
|
||||
data.stabilityModel ??= 'sd3-large'
|
||||
data.stabllityStyle ??= ''
|
||||
data.legacyTranslation ??= false
|
||||
data.comfyUiUrl ??= 'http://localhost:8188'
|
||||
data.comfyConfig ??= {
|
||||
workflow: '',
|
||||
posNodeID: '',
|
||||
posInputName: 'text',
|
||||
negNodeID: '',
|
||||
negInputName: 'text',
|
||||
timeout: 30
|
||||
}
|
||||
|
||||
changeLanguage(data.language)
|
||||
DataBase.set(data)
|
||||
}
|
||||
@@ -585,6 +595,7 @@ export interface Database{
|
||||
name:string
|
||||
icon:string
|
||||
largePortrait?:boolean
|
||||
id?:string
|
||||
}[]
|
||||
assetWidth:number
|
||||
animationSpeed:number
|
||||
@@ -695,6 +706,8 @@ export interface Database{
|
||||
stabilityKey: string
|
||||
stabllityStyle: string
|
||||
legacyTranslation: boolean
|
||||
comfyConfig: ComfyConfig
|
||||
comfyUiUrl: string
|
||||
}
|
||||
|
||||
export interface customscript{
|
||||
@@ -826,6 +839,7 @@ export interface character{
|
||||
}>
|
||||
defaultVariables?:string
|
||||
lowLevelAccess?:boolean
|
||||
hideChatIcon?:boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -873,6 +887,7 @@ export interface groupChat{
|
||||
nickname?:string
|
||||
defaultVariables?:string
|
||||
lowLevelAccess?:boolean
|
||||
hideChatIcon?:boolean
|
||||
}
|
||||
|
||||
export interface botPreset{
|
||||
@@ -965,6 +980,16 @@ interface NAIImgConfig{
|
||||
InfoExtracted:number,
|
||||
RefStrength:number
|
||||
}
|
||||
|
||||
interface ComfyConfig{
|
||||
workflow:string,
|
||||
posNodeID: string,
|
||||
posInputName:string,
|
||||
negNodeID: string,
|
||||
negInputName:string,
|
||||
timeout: number
|
||||
}
|
||||
|
||||
export type FormatingOrderItem = 'main'|'jailbreak'|'chats'|'lorebook'|'globalNote'|'authorNote'|'lastChat'|'description'|'postEverything'|'personaPrompt'
|
||||
|
||||
export interface Chat{
|
||||
@@ -981,6 +1006,7 @@ export interface Chat{
|
||||
scriptstate?:{[key:string]:string|number|boolean}
|
||||
modules?:string[]
|
||||
id?:string
|
||||
bindedPersona?:string
|
||||
}
|
||||
|
||||
export interface Message{
|
||||
@@ -1299,9 +1325,9 @@ export async function downloadPreset(id:number, type:'json'|'risupreset'|'return
|
||||
}
|
||||
else if(type === 'risupreset' || type === 'return'){
|
||||
const buf = fflate.compressSync(encodeMsgpack({
|
||||
presetVersion: 0,
|
||||
presetVersion: 2,
|
||||
type: 'preset',
|
||||
pres: await encryptBuffer(
|
||||
preset: await encryptBuffer(
|
||||
encodeMsgpack(pres),
|
||||
'risupreset'
|
||||
)
|
||||
@@ -1342,8 +1368,8 @@ export async function importPreset(f:{
|
||||
if(f.name.endsWith('.risupreset')){
|
||||
const decoded = await decodeMsgpack(fflate.decompressSync(f.data))
|
||||
console.log(decoded)
|
||||
if(decoded.presetVersion === 0 && decoded.type === 'preset'){
|
||||
pre = {...presetTemplate,...decodeMsgpack(Buffer.from(await decryptBuffer(decoded.pres, 'risupreset')))}
|
||||
if((decoded.presetVersion === 0 || decoded.presetVersion === 2) && decoded.type === 'preset'){
|
||||
pre = {...presetTemplate,...decodeMsgpack(Buffer.from(await decryptBuffer(decoded.preset ?? decoded.pres, 'risupreset')))}
|
||||
}
|
||||
}
|
||||
else{
|
||||
@@ -1484,4 +1510,4 @@ export async function importPreset(f:{
|
||||
pre.name ??= "Imported"
|
||||
db.botPresets.push(pre)
|
||||
setDatabase(db)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { writeBinaryFile,BaseDirectory, readBinaryFile, exists, createDir, readDir, removeFile } from "@tauri-apps/api/fs"
|
||||
|
||||
import { changeFullscreen, checkNullish, findCharacterbyId, sleep } from "../util"
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/tauri"
|
||||
import { v4 as uuidv4, v4 } from 'uuid';
|
||||
@@ -11,7 +12,7 @@ import { checkRisuUpdate } from "../update";
|
||||
import { botMakerMode, selectedCharID } from "../stores";
|
||||
import { Body, ResponseType, fetch as TauriFetch } from "@tauri-apps/api/http";
|
||||
import { loadPlugins } from "../plugins/plugins";
|
||||
import { alertConfirm, alertError, alertNormal, alertNormalWait } from "../alert";
|
||||
import { alertConfirm, alertError, alertNormal, alertNormalWait, alertSelect } from "../alert";
|
||||
import { checkDriverInit, syncDrive } from "../drive/drive";
|
||||
import { hasher } from "../parser";
|
||||
import { characterURLImport, hubURL } from "../characterCards";
|
||||
@@ -55,12 +56,26 @@ interface fetchLog{
|
||||
|
||||
let fetchLog:fetchLog[] = []
|
||||
|
||||
/**
|
||||
* Downloads a file with the given name and data.
|
||||
*
|
||||
* @param {string} name - The name of the file to be downloaded.
|
||||
* @param {Uint8Array|ArrayBuffer|string} dat - The data of the file to be downloaded.
|
||||
*/
|
||||
async function writeBinaryFileFast(appPath: string, data: Uint8Array) {
|
||||
const secret = await invoke('get_http_secret') as string;
|
||||
const port = await invoke('get_http_port') as number;
|
||||
|
||||
const apiUrl = `http://127.0.0.1:${port}/?path=${encodeURIComponent(appPath)}`;
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'x-tauri-secret': secret
|
||||
},
|
||||
body: new Blob([data])
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadFile(name:string, dat:Uint8Array|ArrayBuffer|string) {
|
||||
if(typeof(dat) === 'string'){
|
||||
dat = Buffer.from(dat, 'utf-8')
|
||||
@@ -266,7 +281,7 @@ export async function saveAsset(data:Uint8Array, customId:string = '', fileName:
|
||||
fileExtension = fileName.split('.').pop()
|
||||
}
|
||||
if(isTauri){
|
||||
await writeBinaryFile(`assets/${id}.${fileExtension}`, data ,{dir: BaseDirectory.AppData})
|
||||
await writeBinaryFileFast(`assets/${id}.${fileExtension}`, data);
|
||||
return `assets/${id}.${fileExtension}`
|
||||
}
|
||||
else{
|
||||
@@ -341,13 +356,14 @@ export async function saveDb(){
|
||||
changed = false
|
||||
let db = get(DataBase)
|
||||
db.saveTime = Math.floor(Date.now() / 1000)
|
||||
const dbData = encodeRisuSave(db)
|
||||
if(isTauri){
|
||||
await writeBinaryFile('database/database.bin', dbData, {dir: BaseDirectory.AppData})
|
||||
await writeBinaryFile(`database/dbbackup-${(Date.now()/100).toFixed()}.bin`, dbData, {dir: BaseDirectory.AppData})
|
||||
const dbData = encodeRisuSave(db)
|
||||
await writeBinaryFileFast('database/database.bin', dbData);
|
||||
await writeBinaryFileFast(`database/dbbackup-${(Date.now()/100).toFixed()}.bin`, dbData);
|
||||
}
|
||||
else{
|
||||
if(!forageStorage.isAccount){
|
||||
const dbData = encodeRisuSave(db)
|
||||
await forageStorage.setItem('database/database.bin', dbData)
|
||||
await forageStorage.setItem(`database/dbbackup-${(Date.now()/100).toFixed()}.bin`, dbData)
|
||||
}
|
||||
@@ -386,7 +402,7 @@ export async function saveDb(){
|
||||
async function getDbBackups() {
|
||||
let db = get(DataBase)
|
||||
if(db?.account?.useSync){
|
||||
return
|
||||
return []
|
||||
}
|
||||
if(isTauri){
|
||||
const keys = await readDir('database', {dir: BaseDirectory.AppData})
|
||||
@@ -446,14 +462,11 @@ export async function loadData() {
|
||||
await createDir('assets', {dir: BaseDirectory.AppData})
|
||||
}
|
||||
if(!await exists('database/database.bin', {dir: BaseDirectory.AppData})){
|
||||
await writeBinaryFile('database/database.bin',
|
||||
encodeRisuSave({})
|
||||
,{dir: BaseDirectory.AppData})
|
||||
await writeBinaryFileFast('database/database.bin', encodeRisuSave({}));
|
||||
}
|
||||
try {
|
||||
setDatabase(
|
||||
decodeRisuSave(await readBinaryFile('database/database.bin',{dir: BaseDirectory.AppData}))
|
||||
)
|
||||
const decoded = decodeRisuSave(await readBinaryFile('database/database.bin',{dir: BaseDirectory.AppData}))
|
||||
setDatabase(decoded)
|
||||
} catch (error) {
|
||||
const backups = await getDbBackups()
|
||||
let backupLoaded = false
|
||||
@@ -483,10 +496,11 @@ export async function loadData() {
|
||||
await forageStorage.setItem('database/database.bin', gotStorage)
|
||||
}
|
||||
try {
|
||||
setDatabase(
|
||||
decodeRisuSave(gotStorage)
|
||||
)
|
||||
const decoded = decodeRisuSave(gotStorage)
|
||||
console.log(decoded)
|
||||
setDatabase(decoded)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const backups = await getDbBackups()
|
||||
let backupLoaded = false
|
||||
for(const backup of backups){
|
||||
@@ -1076,23 +1090,24 @@ async function checkNewFormat(): Promise<void> {
|
||||
if (v.lorebook) {
|
||||
v.lorebook = updateLorebooks(v.lorebook);
|
||||
}
|
||||
return v;
|
||||
});
|
||||
return v
|
||||
})
|
||||
|
||||
if (!db.formatversion) {
|
||||
/**
|
||||
* Checks and updates the path of a given data string.
|
||||
*
|
||||
* @param {string} data - The data string to be checked and updated.
|
||||
* @returns {string} - The updated data string with the correct path.
|
||||
*/
|
||||
function checkParge(data: string): string {
|
||||
if (data.startsWith('assets') || (data.length < 3)) {
|
||||
return data;
|
||||
} else {
|
||||
const d = 'assets/' + (data.replace(/\\/g, '/').split('assets/')[1]);
|
||||
if (!d) {
|
||||
return data;
|
||||
db.personas = (db.personas ?? []).map((v) => {
|
||||
v.id ??= uuidv4()
|
||||
return v
|
||||
})
|
||||
|
||||
if(!db.formatversion){
|
||||
function checkParge(data:string){
|
||||
|
||||
if(data.startsWith('assets') || (data.length < 3)){
|
||||
return data
|
||||
}
|
||||
else{
|
||||
const d = 'assets/' + (data.replace(/\\/g, '/').split('assets/')[1])
|
||||
if(!d){
|
||||
return data
|
||||
}
|
||||
return d;
|
||||
}
|
||||
@@ -2021,4 +2036,47 @@ export class BlankWriter{
|
||||
async end(){
|
||||
//do nothing, just to make compatible with other writer
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadInternalBackup(){
|
||||
|
||||
const keys = isTauri ? (await readDir('database', {dir: BaseDirectory.AppData})).map((v) => {
|
||||
return v.name
|
||||
}) : (await forageStorage.keys())
|
||||
let internalBackups:string[] = []
|
||||
for(const key of keys){
|
||||
if(key.includes('dbbackup-')){
|
||||
internalBackups.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
const selectOptions = [
|
||||
'Cancel',
|
||||
...(internalBackups.map((a) => {
|
||||
return (new Date(parseInt(a.replace('database/dbbackup-', '').replace('dbbackup-','')) * 100)).toLocaleString()
|
||||
}))
|
||||
]
|
||||
|
||||
const alertResult = parseInt(
|
||||
await alertSelect(selectOptions)
|
||||
) - 1
|
||||
|
||||
if(alertResult === -1){
|
||||
return
|
||||
}
|
||||
|
||||
const selectedBackup = internalBackups[alertResult]
|
||||
|
||||
const data = isTauri ? (
|
||||
await readBinaryFile('database/' + selectedBackup, {dir: BaseDirectory.AppData})
|
||||
) : (await forageStorage.getItem(selectedBackup))
|
||||
|
||||
setDatabase(
|
||||
decodeRisuSave(data)
|
||||
)
|
||||
|
||||
await alertNormal('Loaded backup')
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ const magicCompressedHeader = new Uint8Array([0, 82, 73, 83, 85, 83, 65, 86, 69,
|
||||
|
||||
export function encodeRisuSave(data:any, compression:'noCompression'|'compression' = 'noCompression'){
|
||||
let encoded:Uint8Array = packr.encode(data)
|
||||
if(isTauri || compression === 'compression'){
|
||||
if(compression === 'compression'){
|
||||
encoded = fflate.compressSync(encoded)
|
||||
const result = new Uint8Array(encoded.length + magicCompressedHeader.length);
|
||||
result.set(magicCompressedHeader, 0)
|
||||
|
||||
295
src/ts/stores.ts
295
src/ts/stores.ts
@@ -1,7 +1,9 @@
|
||||
import { get, writable } from "svelte/store";
|
||||
import { DataBase, type character, type groupChat } from "./storage/database";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { DataBase, type Chat, type character, type groupChat } from "./storage/database";
|
||||
import { isEqual } from "lodash";
|
||||
import type { simpleCharacterArgument } from "./parser";
|
||||
import { getUserIcon, getUserIconProtrait, getUserName, sleep } from "./util";
|
||||
import { getModules } from "./process/modules";
|
||||
|
||||
function updateSize(){
|
||||
SizeStore.set({
|
||||
@@ -23,25 +25,29 @@ export const CharEmotion = writable({} as {[key:string]: [string, string, number
|
||||
export const ViewBoxsize = writable({ width: 12 * 16, height: 12 * 16 }); // Default width and height in pixels
|
||||
export const settingsOpen = writable(false)
|
||||
export const botMakerMode = writable(false)
|
||||
export const moduleBackgroundEmbedding = writable('')
|
||||
|
||||
//optimization
|
||||
|
||||
let db = get(DataBase)
|
||||
let currentChar = get(selectedCharID)
|
||||
let currentCharacter = db.characters ? (db.characters[currentChar]) : null
|
||||
let currentChat = currentCharacter ? (currentCharacter.chats[currentCharacter.chatPage]) : null
|
||||
export const CurrentCharacter = writable(structuredClone(currentCharacter))
|
||||
export const CurrentSimpleCharacter = writable(createSimpleCharacter(currentCharacter))
|
||||
export const CurrentChat = writable(structuredClone(currentChat))
|
||||
export const CurrentUsername = writable(db.username)
|
||||
export const CurrentUserIcon = writable(db.userIcon)
|
||||
export const CurrentShowMemoryLimit = writable(db.showMemoryLimit)
|
||||
export const CurrentCharacter = writable(null) as Writable<character | groupChat>
|
||||
export const CurrentSimpleCharacter = writable(null) as Writable<simpleCharacterArgument>
|
||||
export const CurrentChat = writable(null) as Writable<Chat>
|
||||
export const CurrentUsername = writable('') as Writable<string>
|
||||
export const CurrentUserIcon = writable('') as Writable<string>
|
||||
export const CurrentShowMemoryLimit = writable(false) as Writable<boolean>
|
||||
export const ShowVN = writable(false)
|
||||
export const SettingsMenuIndex = writable(-1)
|
||||
export const CurrentVariablePointer = writable({} as {[key:string]: string|number|boolean})
|
||||
export const ReloadGUIPointer = writable(0)
|
||||
export const OpenRealmStore = writable(false)
|
||||
export const ShowRealmFrameStore = writable('')
|
||||
export const PlaygroundStore = writable(0)
|
||||
export const HideIconStore = writable(false)
|
||||
export const UserIconProtrait = writable(false)
|
||||
let lastGlobalEnabledModules: string[] = []
|
||||
let lastChatEnabledModules: string[] = []
|
||||
let moduleHideIcon = false
|
||||
let characterHideIcon = false
|
||||
|
||||
function createSimpleCharacter(char:character|groupChat){
|
||||
if((!char) || char.type === 'group'){
|
||||
@@ -55,107 +61,196 @@ function createSimpleCharacter(char:character|groupChat){
|
||||
additionalAssets: char.additionalAssets,
|
||||
virtualscript: char.virtualscript,
|
||||
emotionImages: char.emotionImages,
|
||||
triggerscript: char.triggerscript,
|
||||
}
|
||||
|
||||
return simpleChar
|
||||
|
||||
}
|
||||
|
||||
|
||||
function updateCurrentCharacter(){
|
||||
|
||||
const db = get(DataBase)
|
||||
if(!db.characters){
|
||||
CurrentCharacter.set(null)
|
||||
updateCurrentChat()
|
||||
return
|
||||
}
|
||||
|
||||
const currentCharId = get(selectedCharID)
|
||||
const currentChar = db.characters[currentCharId]
|
||||
const gotCharacter = get(CurrentCharacter)
|
||||
if(isEqual(gotCharacter, currentChar)){
|
||||
return
|
||||
}
|
||||
if((currentChar?.viewScreen === 'vn') !== get(ShowVN)){
|
||||
ShowVN.set(currentChar?.viewScreen === 'vn')
|
||||
}
|
||||
|
||||
CurrentCharacter.set(structuredClone(currentChar))
|
||||
const simp = createSimpleCharacter(currentChar)
|
||||
|
||||
if(!isEqual(get(CurrentSimpleCharacter), simp)){
|
||||
CurrentSimpleCharacter.set(simp)
|
||||
}
|
||||
|
||||
updateCurrentChat()
|
||||
function trySync(){
|
||||
try {
|
||||
let db = get(DataBase)
|
||||
let currentChar = get(selectedCharID)
|
||||
let currentCharacter = db.characters ? (db.characters[currentChar]) : null
|
||||
let currentChat = currentCharacter ? (currentCharacter.chats[currentCharacter.chatPage]) : null
|
||||
CurrentCharacter.set(structuredClone(currentCharacter))
|
||||
CurrentSimpleCharacter.set(createSimpleCharacter(currentCharacter))
|
||||
CurrentChat.set(structuredClone(currentChat))
|
||||
CurrentUsername.set(getUserName())
|
||||
CurrentUserIcon.set(getUserIcon())
|
||||
CurrentShowMemoryLimit.set(db.showMemoryLimit)
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
function updateCurrentChat(){
|
||||
const currentChar = get(CurrentCharacter)
|
||||
if(!currentChar){
|
||||
CurrentChat.set(null)
|
||||
return
|
||||
}
|
||||
const chat = (currentChar.chats[currentChar.chatPage])
|
||||
const gotChat = get(CurrentChat)
|
||||
if(isEqual(gotChat, chat)){
|
||||
return
|
||||
}
|
||||
CurrentChat.set(structuredClone(chat))
|
||||
}
|
||||
trySync()
|
||||
|
||||
DataBase.subscribe((data) => {
|
||||
updateCurrentCharacter()
|
||||
if(data.username !== get(CurrentUsername)){
|
||||
CurrentUsername.set(data.username)
|
||||
}
|
||||
if(data.userIcon !== get(CurrentUserIcon)){
|
||||
CurrentUserIcon.set(data.userIcon)
|
||||
}
|
||||
if(data.showMemoryLimit !== get(CurrentShowMemoryLimit)){
|
||||
CurrentShowMemoryLimit.set(data.showMemoryLimit)
|
||||
}
|
||||
})
|
||||
|
||||
selectedCharID.subscribe((id) => {
|
||||
|
||||
updateCurrentCharacter()
|
||||
})
|
||||
|
||||
CurrentCharacter.subscribe((char) => {
|
||||
updateCurrentChat()
|
||||
let db = get(DataBase)
|
||||
let charId = get(selectedCharID)
|
||||
if(charId === -1 || charId > db.characters.length){
|
||||
return
|
||||
}
|
||||
let cha = db.characters[charId]
|
||||
if(isEqual(cha, char)){
|
||||
return
|
||||
}
|
||||
db.characters[charId] = structuredClone(char)
|
||||
DataBase.set(db)
|
||||
})
|
||||
|
||||
CurrentChat.subscribe((chat) => {
|
||||
let currentChar = get(CurrentCharacter)
|
||||
|
||||
if(currentChar){
|
||||
if(!isEqual(currentChar.chats[currentChar.chatPage], chat)){
|
||||
currentChar.chats[currentChar.chatPage] = structuredClone(chat)
|
||||
CurrentCharacter.set(currentChar)
|
||||
async function preInit(){
|
||||
await sleep(1)
|
||||
trySync()
|
||||
function updateCurrentCharacter(){
|
||||
|
||||
const db = get(DataBase)
|
||||
if(!db.characters){
|
||||
CurrentCharacter.set(null)
|
||||
updateCurrentChat()
|
||||
return
|
||||
}
|
||||
|
||||
const currentCharId = get(selectedCharID)
|
||||
const currentChar = db.characters[currentCharId]
|
||||
const gotCharacter = get(CurrentCharacter)
|
||||
if(isEqual(gotCharacter, currentChar)){
|
||||
return
|
||||
}
|
||||
if((currentChar?.viewScreen === 'vn') !== get(ShowVN)){
|
||||
ShowVN.set(currentChar?.viewScreen === 'vn')
|
||||
}
|
||||
|
||||
CurrentCharacter.set(structuredClone(currentChar))
|
||||
const simp = createSimpleCharacter(currentChar)
|
||||
|
||||
if(!isEqual(get(CurrentSimpleCharacter), simp)){
|
||||
CurrentSimpleCharacter.set(simp)
|
||||
}
|
||||
|
||||
updateCurrentChat()
|
||||
}
|
||||
|
||||
const variablePointer = get(CurrentVariablePointer)
|
||||
const currentState = structuredClone(chat?.scriptstate)
|
||||
|
||||
if(!isEqual(variablePointer, currentState)){
|
||||
CurrentVariablePointer.set(currentState)
|
||||
function updateCurrentChat(){
|
||||
const currentChar = get(CurrentCharacter)
|
||||
if(!currentChar){
|
||||
CurrentChat.set(null)
|
||||
return
|
||||
}
|
||||
const chat = (currentChar.chats[currentChar.chatPage])
|
||||
const gotChat = get(CurrentChat)
|
||||
if(isEqual(gotChat, chat)){
|
||||
return
|
||||
}
|
||||
CurrentChat.set(structuredClone(chat))
|
||||
}
|
||||
})
|
||||
|
||||
DataBase.subscribe((data) => {
|
||||
updateCurrentCharacter()
|
||||
if(getUserName() !== get(CurrentUsername)){
|
||||
CurrentUsername.set(getUserName())
|
||||
}
|
||||
if(getUserIcon() !== get(CurrentUserIcon)){
|
||||
CurrentUserIcon.set(getUserIcon())
|
||||
}
|
||||
if(getUserIconProtrait() !== get(UserIconProtrait)){
|
||||
UserIconProtrait.set(getUserIconProtrait())
|
||||
}
|
||||
if(data.showMemoryLimit !== get(CurrentShowMemoryLimit)){
|
||||
CurrentShowMemoryLimit.set(data.showMemoryLimit)
|
||||
}
|
||||
if(!isEqual(data.enabledModules, lastGlobalEnabledModules)){
|
||||
lastGlobalEnabledModules = data.enabledModules || []
|
||||
onModuleUpdate()
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
selectedCharID.subscribe((id) => {
|
||||
|
||||
updateCurrentCharacter()
|
||||
})
|
||||
|
||||
CurrentCharacter.subscribe((char) => {
|
||||
updateCurrentChat()
|
||||
let db = get(DataBase)
|
||||
let charId = get(selectedCharID)
|
||||
if(char?.hideChatIcon !== characterHideIcon){
|
||||
characterHideIcon = char?.hideChatIcon
|
||||
HideIconStore.set(characterHideIcon || moduleHideIcon)
|
||||
}
|
||||
if(getUserName() !== get(CurrentUsername)){
|
||||
CurrentUsername.set(getUserName())
|
||||
}
|
||||
if(getUserIcon() !== get(CurrentUserIcon)){
|
||||
CurrentUserIcon.set(getUserIcon())
|
||||
}
|
||||
if(getUserIconProtrait() !== get(UserIconProtrait)){
|
||||
UserIconProtrait.set(getUserIconProtrait())
|
||||
}
|
||||
if(charId === -1 || charId > db.characters.length){
|
||||
return
|
||||
}
|
||||
let cha = db.characters[charId]
|
||||
if(isEqual(cha, char)){
|
||||
return
|
||||
}
|
||||
db.characters[charId] = structuredClone(char)
|
||||
DataBase.set(db)
|
||||
})
|
||||
|
||||
CurrentChat.subscribe((chat) => {
|
||||
let currentChar = get(CurrentCharacter)
|
||||
|
||||
if(currentChar){
|
||||
if(!isEqual(currentChar.chats[currentChar.chatPage], chat)){
|
||||
currentChar.chats[currentChar.chatPage] = structuredClone(chat)
|
||||
CurrentCharacter.set(currentChar)
|
||||
}
|
||||
}
|
||||
|
||||
if(!isEqual(lastChatEnabledModules, chat?.modules)){
|
||||
lastChatEnabledModules = chat?.modules || []
|
||||
onModuleUpdate()
|
||||
return
|
||||
}
|
||||
|
||||
if(getUserName() !== get(CurrentUsername)){
|
||||
CurrentUsername.set(getUserName())
|
||||
}
|
||||
|
||||
if(getUserIcon() !== get(CurrentUserIcon)){
|
||||
CurrentUserIcon.set(getUserIcon())
|
||||
}
|
||||
|
||||
const variablePointer = get(CurrentVariablePointer)
|
||||
const currentState = structuredClone(chat?.scriptstate)
|
||||
|
||||
if(!isEqual(variablePointer, currentState)){
|
||||
CurrentVariablePointer.set(currentState)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onModuleUpdate(){
|
||||
if(!Array.isArray(lastGlobalEnabledModules)){
|
||||
lastGlobalEnabledModules = []
|
||||
}
|
||||
if(!Array.isArray(lastChatEnabledModules)){
|
||||
lastChatEnabledModules = []
|
||||
}
|
||||
|
||||
const m = getModules([
|
||||
...lastGlobalEnabledModules, ...lastChatEnabledModules
|
||||
])
|
||||
|
||||
let moduleHideIcon = false
|
||||
let backgroundEmbedding = ''
|
||||
m.forEach((module) => {
|
||||
if(!module){
|
||||
return
|
||||
}
|
||||
|
||||
if(module.hideIcon){
|
||||
moduleHideIcon = true
|
||||
}
|
||||
if(module.backgroundEmbedding){
|
||||
backgroundEmbedding += '\n' + module.backgroundEmbedding + '\n'
|
||||
}
|
||||
})
|
||||
|
||||
if(backgroundEmbedding){
|
||||
moduleBackgroundEmbedding.set(backgroundEmbedding)
|
||||
}
|
||||
HideIconStore.set(characterHideIcon || moduleHideIcon)
|
||||
}
|
||||
|
||||
updateSize()
|
||||
window.addEventListener("resize", updateSize);
|
||||
window.addEventListener("resize", updateSize);
|
||||
preInit()
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
interface TextSyntSyntaxTree {
|
||||
name: string;
|
||||
children: TextSyntSyntaxTree[];
|
||||
}
|
||||
|
||||
function parseMarkdownLikeYaml(text:string){
|
||||
|
||||
const lines = text.split('\n');
|
||||
const root: TextSyntSyntaxTree = {name: 'root', children: []};
|
||||
let rootHashIndentation = -1;
|
||||
let currentIndentation = 0;
|
||||
|
||||
for(let i = 0; i < lines.length; i++){
|
||||
const line = lines[i];
|
||||
|
||||
if(line.startsWith('#')){
|
||||
let indentations = 0;
|
||||
while(line[indentations] === '#'){
|
||||
indentations++;
|
||||
}
|
||||
|
||||
if(currentIndentation === 0 && rootHashIndentation === -1){
|
||||
rootHashIndentation = indentations;
|
||||
indentations = 1;
|
||||
}
|
||||
else{
|
||||
indentations -= (rootHashIndentation - 1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export const tokenizerList = [
|
||||
['llama', 'Llama'],
|
||||
['llama3', 'Llama3'],
|
||||
['novellist', 'Novellist'],
|
||||
['gemma', 'Gemma'],
|
||||
] as const
|
||||
|
||||
export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Array)>{
|
||||
@@ -35,6 +36,8 @@ export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Arr
|
||||
return await tokenizeWebTokenizers(data, 'novellist')
|
||||
case 'llama3':
|
||||
return await tokenizeWebTokenizers(data, 'llama')
|
||||
case 'gemma':
|
||||
return await tokenizeWebTokenizers(data, 'gemma')
|
||||
default:
|
||||
// Add exception for gpt-4o tokenizers on reverse_proxy
|
||||
if(db.proxyRequestModel?.startsWith('gpt4o') ||
|
||||
|
||||
@@ -4,11 +4,11 @@ import { DataBase, type character, type customscript, type groupChat } from "../
|
||||
import { globalFetch, isTauri } from "../storage/globalApi"
|
||||
import { alertError } from "../alert"
|
||||
import { requestChatData } from "../process/request"
|
||||
import { doingChat } from "../process"
|
||||
import type { simpleCharacterArgument } from "../parser"
|
||||
import { doingChat, type OpenAIChat } from "../process"
|
||||
import { applyMarkdownToNode, parseChatML, type simpleCharacterArgument } from "../parser"
|
||||
import { selectedCharID } from "../stores"
|
||||
import { getModuleRegexScripts } from "../process/modules"
|
||||
import { getNodetextToSentence, sleep, applyMarkdownToNode } from "../util"
|
||||
import { getNodetextToSentence, sleep } from "../util"
|
||||
import { processScriptFull } from "../process/scripts"
|
||||
import { Capacitor } from "@capacitor/core"
|
||||
|
||||
@@ -448,11 +448,23 @@ async function translateLLM(text:string, arg:{to:string}){
|
||||
if(llmCache.has(text)){
|
||||
return llmCache.get(text)
|
||||
}
|
||||
const styleDecodeRegex = /\<risu-style\>(.+?)\<\/risu-style\>/gms
|
||||
let styleDecodes:string[] = []
|
||||
text = text.replace(styleDecodeRegex, (match, p1) => {
|
||||
styleDecodes.push(p1)
|
||||
return `<style-data style-index="${styleDecodes.length-1}"></style-data>`
|
||||
})
|
||||
|
||||
const db = get(DataBase)
|
||||
let formated:OpenAIChat[] = []
|
||||
let prompt = db.translatorPrompt || `You are a translator. translate the following html or text into {{slot}}. do not output anything other than the translation.`
|
||||
prompt = prompt.replace('{{slot}}', arg.to)
|
||||
const rq = await requestChatData({
|
||||
formated: [
|
||||
let parsedPrompt = parseChatML(prompt.replaceAll('{{slot}}', arg.to).replaceAll('{{solt::content}}', text))
|
||||
if(parsedPrompt){
|
||||
formated = parsedPrompt
|
||||
}
|
||||
else{
|
||||
prompt = prompt.replaceAll('{{slot}}', arg.to)
|
||||
formated = [
|
||||
{
|
||||
'role': 'system',
|
||||
'content': prompt
|
||||
@@ -461,7 +473,10 @@ async function translateLLM(text:string, arg:{to:string}){
|
||||
'role': 'user',
|
||||
'content': text
|
||||
}
|
||||
],
|
||||
]
|
||||
}
|
||||
const rq = await requestChatData({
|
||||
formated,
|
||||
bias: {},
|
||||
useStreaming: false,
|
||||
noMultiGen: true,
|
||||
@@ -472,6 +487,9 @@ async function translateLLM(text:string, arg:{to:string}){
|
||||
alertError(`${rq.result}`)
|
||||
return text
|
||||
}
|
||||
llmCache.set(text, rq.result)
|
||||
return rq.result
|
||||
const result = rq.result.replace(/<style-data style-index="(\d+)" ?\/?>/g, (match, p1) => {
|
||||
return styleDecodes[parseInt(p1)] ?? ''
|
||||
}).replace(/<\/style-data>/g, '')
|
||||
llmCache.set(text, result)
|
||||
return result
|
||||
}
|
||||
126
src/ts/util.ts
126
src/ts/util.ts
@@ -8,19 +8,9 @@ import { basename } from "@tauri-apps/api/path"
|
||||
import { createBlankChar, getCharImage } from "./characters"
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
import { isTauri } from "./storage/globalApi"
|
||||
import { Marked } from "marked"
|
||||
|
||||
export const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
||||
|
||||
const mconverted = new Marked({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
silent: true,
|
||||
tokenizer: {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
export interface Messagec extends Message{
|
||||
index: number
|
||||
}
|
||||
@@ -113,10 +103,69 @@ export const replacePlaceholders = (msg:string, name:string) => {
|
||||
let selectedChar = get(selectedCharID)
|
||||
let currentChar = db.characters[selectedChar]
|
||||
return msg .replace(/({{char}})|({{Char}})|(<Char>)|(<char>)/gi, currentChar.name)
|
||||
.replace(/({{user}})|({{User}})|(<User>)|(<user>)/gi, db.username)
|
||||
.replace(/({{user}})|({{User}})|(<User>)|(<user>)/gi, getUserName())
|
||||
.replace(/(\{\{((set)|(get))var::.+?\}\})/gu,'')
|
||||
}
|
||||
|
||||
function checkPersonaBinded(){
|
||||
try {
|
||||
let db = get(DataBase)
|
||||
const selectedChar = get(selectedCharID)
|
||||
const character = db.characters[selectedChar]
|
||||
const chat = character.chats[character.chatPage]
|
||||
console.log(chat.bindedPersona)
|
||||
if(!chat.bindedPersona){
|
||||
return null
|
||||
}
|
||||
const persona = db.personas.find(v => v.id === chat.bindedPersona)
|
||||
console.log(db.personas, persona)
|
||||
return persona
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function getUserName(){
|
||||
const bindedPersona = checkPersonaBinded()
|
||||
if(bindedPersona){
|
||||
return bindedPersona.name
|
||||
}
|
||||
const db = get(DataBase)
|
||||
return db.username ?? 'User'
|
||||
}
|
||||
|
||||
export function getUserIcon(){
|
||||
const bindedPersona = checkPersonaBinded()
|
||||
console.log(`Icon: ${bindedPersona?.icon}`)
|
||||
if(bindedPersona){
|
||||
return bindedPersona.icon
|
||||
}
|
||||
const db = get(DataBase)
|
||||
return db.userIcon ?? ''
|
||||
}
|
||||
|
||||
export function getPersonaPrompt(){
|
||||
const bindedPersona = checkPersonaBinded()
|
||||
if(bindedPersona){
|
||||
return bindedPersona.personaPrompt
|
||||
}
|
||||
const db = get(DataBase)
|
||||
return db.personaPrompt ?? ''
|
||||
}
|
||||
|
||||
export function getUserIconProtrait(){
|
||||
try {
|
||||
const bindedPersona = checkPersonaBinded()
|
||||
if(bindedPersona){
|
||||
return bindedPersona.largePortrait
|
||||
}
|
||||
const db = get(DataBase)
|
||||
return db.personas[db.selectedPersona].largePortrait
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function checkIsIos(){
|
||||
return /(iPad|iPhone|iPod)/g.test(navigator.userAgent)
|
||||
}
|
||||
@@ -585,33 +634,6 @@ export function getNodetextToSentence(node: Node): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function applyMarkdownToNode(node: Node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
const text = node.textContent;
|
||||
if (text) {
|
||||
let markdown = mconverted.parseInline(text);
|
||||
if (markdown !== text) {
|
||||
const span = document.createElement('span');
|
||||
span.innerHTML = markdown;
|
||||
|
||||
// inherit inline style from the parent node
|
||||
const parentStyle = (node.parentNode as HTMLElement)?.style;
|
||||
if(parentStyle){
|
||||
for(let i=0;i<parentStyle.length;i++){
|
||||
span.style.setProperty(parentStyle[i], parentStyle.getPropertyValue(parentStyle[i]))
|
||||
}
|
||||
}
|
||||
(node as Element)?.replaceWith(span);
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const child of node.childNodes) {
|
||||
applyMarkdownToNode(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const TagList = [
|
||||
{
|
||||
@@ -973,20 +995,24 @@ export const isKnownUri = (uri:string) => {
|
||||
}
|
||||
|
||||
export function parseKeyValue(template:string){
|
||||
if(!template){
|
||||
try {
|
||||
if(!template){
|
||||
return []
|
||||
}
|
||||
|
||||
const keyValue:[string, string][] = []
|
||||
|
||||
for(const line of template.split('\n')){
|
||||
const [key, value] = line.split('=')
|
||||
if(key && value){
|
||||
keyValue.push([key, value])
|
||||
}
|
||||
}
|
||||
|
||||
return keyValue
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
|
||||
const keyValue:[string, string][] = []
|
||||
|
||||
for(const line of template.split('\n')){
|
||||
const [key, value] = line.split('=')
|
||||
if(key && value){
|
||||
keyValue.push([key, value])
|
||||
}
|
||||
}
|
||||
|
||||
return keyValue
|
||||
}
|
||||
|
||||
export const sortableOptions = {
|
||||
|
||||
Reference in New Issue
Block a user