diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 105a71b8..3f089455 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ dependencies: isomorphic-dompurify: specifier: ^1.8.0 version: 1.8.0 + jszip: + specifier: ^3.10.1 + version: 3.10.1 libsodium-wrappers-sumo: specifier: ^0.7.11 version: 0.7.11 @@ -1537,6 +1540,10 @@ packages: requiresBuild: true dev: false + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false + /cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -2238,6 +2245,10 @@ packages: engines: {node: '>=8'} dev: true + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: false + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -2298,6 +2309,15 @@ packages: - utf-8-validate dev: false + /jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + dev: false + /kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -2319,6 +2339,12 @@ packages: immediate: 3.0.6 dev: false + /lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + dependencies: + immediate: 3.0.6 + dev: false + /lil-gui@0.17.0: resolution: {integrity: sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==} dev: true @@ -2730,6 +2756,10 @@ packages: p-finally: 1.0.0 dev: true + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2931,6 +2961,10 @@ packages: tunnel-agent: 0.6.0 dev: false + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: false + /protobufjs@6.11.3: resolution: {integrity: sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==} hasBin: true @@ -3035,6 +3069,18 @@ packages: pify: 2.3.0 dev: true + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: false + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -3105,6 +3151,10 @@ packages: mri: 1.2.0 dev: true + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: false + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false @@ -3174,6 +3224,10 @@ packages: - supports-color dev: false + /setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: false + /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false @@ -3276,6 +3330,12 @@ packages: queue-tick: 1.0.1 dev: false + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: false + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2b404eb5..f21e091b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "RisuAI", - "version": "1.59.9" + "version": "1.60.1" }, "tauri": { "allowlist": { diff --git a/src/lang/en.ts b/src/lang/en.ts index 0a993050..bfea2e23 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -453,4 +453,8 @@ export const languageEnglish = { joinMultiUserRoom: "Join MultiUser Room", exactTokens: "Exact Tokens", fixedTokens: "Approximate Tokens", + inlayViewScreen: "Inlay Screen", + imgGenPrompt: "Image Generation Prompt", + imgGenNegatives: "Image Generation Negative Prompt", + imgGenInstructions: "Image Generation Instructions", } \ No newline at end of file diff --git a/src/lib/ChatScreens/Chat.svelte b/src/lib/ChatScreens/Chat.svelte index c6d7cd78..52b19c72 100644 --- a/src/lib/ChatScreens/Chat.svelte +++ b/src/lib/ChatScreens/Chat.svelte @@ -1,5 +1,5 @@

{language.otherBots}

-{language.imageGeneration} -{language.imageGeneration} {language.provider} - - None - Stable Diffusion WebUI - Novel AI - - - - -{#if $DataBase.sdProvider === 'webui'} -You must use WebUI with --api flag - You must use WebUI without agpl license or use unmodified version with agpl license to observe the contents of the agpl license. - {#if !isTauri} - You are using web version. you must use ngrok or other tunnels to use your local webui. - {/if} - WebUI {language.providerURL} - - Steps - - - CFG Scale - - - Width - - Height - - Sampler - - -
- -
- {#if $DataBase.sdConfig.enable_hr === true} - denoising_strength - - hr_scale - - Upscaler - - {/if} -{/if} - -{#if $DataBase.sdProvider === 'novelai'} - Novel AI {language.providerURL} - - API Key - - - Model - - - Enable I2I - - - - {#if $DataBase.NAII2I} - - strength - - {$DataBase.NAIImgConfig.strength} - noise - - {$DataBase.NAIImgConfig.noise} - - base image - - If empty, a profile picture is sent. - - - {/if} - - Width - - Height - - Sampler - - steps - - CFG scale - - - {#if !$DataBase.NAII2I} - Use SMEA - - Use DYN - - {/if} -{/if} - -TTS -ElevenLabs API key - - -VOICEVOX URL - - -NovelAI API key - - -{language.emotionImage} - -{language.emotionMethod} - - Ax. Model - MiniLM-L6-v2 - - -{language.SuperMemory} -{language.SuperMemory} {language.model} - - None - distilbart-cnn-6-6 (Free/Local) - OpenAI 3.5 Turbo Instruct - OpenAI Davinci - OpenAI Curie - {language.submodel} ({language.unrecommended}) - -{#if $DataBase.supaMemoryType === 'davinci' || $DataBase.supaMemoryType === 'curie' || $DataBase.supaMemoryType === 'instruct35'} - {language.SuperMemory} OpenAI Key - -{/if} -{#if $DataBase.supaMemoryType !== 'none'} - {language.SuperMemory} Prompt - -{/if} -{#if $DataBase.hypaMemory} - {language.HypaMemory} Model - - MiniLM-L6-v2 (Free / Local) - OpenAI Ada (Davinci / Curie Only) + + {language.imageGeneration} {language.provider} + + None + Stable Diffusion WebUI + Novel AI + + -{/if} -{#if $DataBase.useExperimental} -
- -
-{/if} \ No newline at end of file + + {#if $DataBase.sdProvider === 'webui'} + You must use WebUI with --api flag + You must use WebUI without agpl license or use unmodified version with agpl license to observe the contents of the agpl license. + {#if !isTauri} + You are using web version. you must use ngrok or other tunnels to use your local webui. + {/if} + WebUI {language.providerURL} + + Steps + + + CFG Scale + + + Width + + Height + + Sampler + + +
+ +
+ {#if $DataBase.sdConfig.enable_hr === true} + denoising_strength + + hr_scale + + Upscaler + + {/if} + {/if} + + {#if $DataBase.sdProvider === 'novelai'} + Novel AI {language.providerURL} + + API Key + + + Model + + + Width + + Height + + Sampler + + steps + + CFG scale + + + {#if !$DataBase.NAII2I} + + + {/if} + + + {#if $DataBase.NAII2I} + + strength + + {$DataBase.NAIImgConfig.strength} + noise + + {$DataBase.NAIImgConfig.noise} + + Base image + + + {/if} + {/if} +
+ + + ElevenLabs API key + + + VOICEVOX URL + + + NovelAI API key + + + + + + + {language.emotionMethod} + + + Ax. Model + MiniLM-L6-v2 + + + + + {language.SuperMemory} {language.model} + + None + distilbart-cnn-6-6 (Free/Local) + OpenAI 3.5 Turbo Instruct + OpenAI Davinci + OpenAI Curie + {language.submodel} ({language.unrecommended}) + + {#if $DataBase.supaMemoryType === 'davinci' || $DataBase.supaMemoryType === 'curie' || $DataBase.supaMemoryType === 'instruct35'} + {language.SuperMemory} OpenAI Key + + {/if} + {#if $DataBase.supaMemoryType !== 'none'} + {language.SuperMemory} Prompt + + {/if} + {#if $DataBase.hypaMemory} + {language.HypaMemory} Model + + MiniLM-L6-v2 (Free / Local) + OpenAI Ada (Davinci / Curie Only) + + {/if} + {#if $DataBase.useExperimental} +
+ +
+ {/if} +
\ No newline at end of file diff --git a/src/lib/SideBars/CharConfig.svelte b/src/lib/SideBars/CharConfig.svelte index 5092502d..86783858 100644 --- a/src/lib/SideBars/CharConfig.svelte +++ b/src/lib/SideBars/CharConfig.svelte @@ -27,6 +27,8 @@ import OptionInput from "../UI/GUI/OptionInput.svelte"; import RegexList from "./Scripts/RegexList.svelte"; import TriggerList from "./Scripts/TriggerList.svelte"; + import CheckInput from "../UI/GUI/CheckInput.svelte"; + import { updateInlayScreen } from "src/ts/process/inlayScreen"; let subMenu = 0 @@ -319,7 +321,11 @@ {#if currentChar.type !== 'group'} - + { + if(currentChar.type === 'character'){ + currentChar.data = updateInlayScreen(currentChar.data) + } + }}> {language.none} {language.emotionImage} {language.imageGeneration} @@ -383,73 +389,34 @@ Loading... {/if} + + {#if currentChar.data.inlayViewScreen} + {language.imgGenInstructions} + + {/if} + + { + if(currentChar.type === 'character'){ + currentChar.data = updateInlayScreen(currentChar.data) + } + }}/> {/if} {#if currentChar.data.viewScreen === 'imggen'} {language.imageGeneration} {language.emotionWarn} -
- - - - - - - {#if currentChar.data.sdData.length === 0} - -
{language.noData}
- - {/if} - {#each currentChar.data.sdData as emo, i} - - - - {#if (!['always','negative'].includes(currentChar.data.sdData[i][0]))} - - {:else} - - {/if} - - {/each} -
{language.key}{language.value}
- - - -
-
-
- {#if !$addingEmotion} - - {:else} - Loading... - {/if} -
- {language.currentImageGeneration} - {#if currentChar.data.chats[currentChar.data.chatPage].sdData} - - {:else} -
{language.noData}
- {/if} + {language.imgGenPrompt} + + {language.imgGenNegatives} + + {language.imgGenInstructions} + + + { + if(currentChar.type === 'character'){ + currentChar.data = updateInlayScreen(currentChar.data) + } + }}/> {/if} {:else if subMenu === 3}

{language.loreBook}

diff --git a/src/lib/UI/Arcodion.svelte b/src/lib/UI/Arcodion.svelte index fc33234e..75bc3900 100644 --- a/src/lib/UI/Arcodion.svelte +++ b/src/lib/UI/Arcodion.svelte @@ -1,15 +1,33 @@ -
- - {#if open} -
- -
- {/if} -
+{#if styled} +
+ + {#if open} +
+ +
+ {/if} +
+{:else} +
+ + {#if open} +
+ +
+ {/if} +
+{/if} \ No newline at end of file diff --git a/src/lib/VisualNovel/VisualNovelChat.svelte b/src/lib/VisualNovel/VisualNovelChat.svelte index 7fc0a7ba..5d95813b 100644 --- a/src/lib/VisualNovel/VisualNovelChat.svelte +++ b/src/lib/VisualNovel/VisualNovelChat.svelte @@ -1,17 +1,78 @@ -
-
- -
- {$CurrentCharacter.name} +{#if $CurrentCharacter.type === 'character' && $CurrentCharacter.emotionImages[0]} + {#await getFileSrc($CurrentCharacter.emotionImages[0][1]) then imglink} +
+ character
-
- Test + {/await} +{/if} +{#if style === 0} +
+
+ +
+ {$CurrentCharacter.name} +
+
+ Test +
-
\ No newline at end of file +{:else} +
+
+ +
+
+
+ {$CurrentCharacter.name} +
+
+
+
+
+
+ {renderedText} +
+
+
+
+
+{/if} \ No newline at end of file diff --git a/src/lib/VisualNovel/VisualNovelMain.svelte b/src/lib/VisualNovel/VisualNovelMain.svelte index f718f398..38485462 100644 --- a/src/lib/VisualNovel/VisualNovelMain.svelte +++ b/src/lib/VisualNovel/VisualNovelMain.svelte @@ -3,11 +3,11 @@ import { DataBase } from "../../ts/storage/database"; import BackgroundDom from "../ChatScreens/BackgroundDom.svelte"; import SideBarArrow from "../UI/GUI/SideBarArrow.svelte"; - import defaultWallpaper from '../../etc/bg.jpg' + import defaultWallpaper from './test.png' import VisualNovelChat from "./VisualNovelChat.svelte"; - const wallPaper = `background: url(${defaultWallpaper})` - + const wallPaper = `background-image: url(${defaultWallpaper})` + let forceRender:() => void let bgImg= '' let lastBg = '' $: (async () =>{ @@ -20,7 +20,9 @@
-
- +
{ + forceRender() + }}> +
\ No newline at end of file diff --git a/src/lib/VisualNovel/test.png b/src/lib/VisualNovel/test.png new file mode 100644 index 00000000..fa23465f Binary files /dev/null and b/src/lib/VisualNovel/test.png differ diff --git a/src/styles.css b/src/styles.css index f309373b..978b47f5 100644 --- a/src/styles.css +++ b/src/styles.css @@ -71,6 +71,11 @@ html, body{ font-weight: bold; } +.strokeme { + color: #000; + text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white; +} + ::-webkit-scrollbar { width: 5px; height: 5px; diff --git a/src/ts/characters.ts b/src/ts/characters.ts index 1ad4c8cc..776ff7ad 100644 --- a/src/ts/characters.ts +++ b/src/ts/characters.ts @@ -9,6 +9,7 @@ import { selectedCharID } from "./stores"; import { checkCharOrder, downloadFile, getFileSrc, readImage } from "./storage/globalApi"; import * as yuso from 'yuso' import { reencodeImage } from "./image"; +import { updateInlayScreen } from "./process/inlayScreen"; export function createNewCharacter() { let db = get(DataBase) @@ -300,6 +301,9 @@ export function characterFormatUpdate(index:number|character){ depth: 0, prompt: '' } + if(!cha.newGenData){ + cha = updateInlayScreen(cha) + } } else{ if((!cha.characterTalks) || cha.characterTalks.length !== cha.characters.length){ diff --git a/src/ts/image.ts b/src/ts/image.ts index 25e37033..f944dcb3 100644 --- a/src/ts/image.ts +++ b/src/ts/image.ts @@ -24,12 +24,21 @@ export async function postInlayImage(){ const extention = img.name.split('.').at(-1) - //darw in canvas to convert to png + const imgObj = new Image() + imgObj.src = URL.createObjectURL(new Blob([img.data], {type: `image/${extention}`})) + + return await writeInlayImage(imgObj, { + name: img.name, + ext: extention + }) +} + +export async function writeInlayImage(imgObj:HTMLImageElement, arg:{name?:string, ext?:string} = {}) { + + let drawHeight = 0 + let drawWidth = 0 const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') - const imgObj = new Image() - let drawHeight, drawWidth = 0 - imgObj.src = URL.createObjectURL(new Blob([img.data], {type: `image/${extention}`})) await new Promise((resolve) => { imgObj.onload = () => { drawHeight = imgObj.height @@ -59,9 +68,9 @@ export async function postInlayImage(){ const imgid = v4() await inlayStorage.setItem(imgid, { - name: img.name, + name: arg.name ?? imgid, data: dataURI, - ext: extention, + ext: arg.ext ?? 'png', height: drawHeight, width: drawWidth }) diff --git a/src/ts/parser.ts b/src/ts/parser.ts index 3d2132e0..b2512997 100644 --- a/src/ts/parser.ts +++ b/src/ts/parser.ts @@ -65,7 +65,7 @@ DOMPurify.addHook("uponSanitizeAttribute", (node, data) => { }) -const assetRegex = /{{(raw|img|video|audio|bg)::(.+?)}}/g +const assetRegex = /{{(raw|img|video|audio|bg|emotion)::(.+?)}}/g async function parseAdditionalAssets(data:string, char:simpleCharacterArgument|character, mode:'normal'|'back', mode2:'unset'|'pre'|'post' = 'unset'){ const db = get(DataBase) @@ -74,13 +74,28 @@ async function parseAdditionalAssets(data:string, char:simpleCharacterArgument|c if(char.additionalAssets){ let assetPaths:{[key:string]:string} = {} + let emoPaths:{[key:string]:string} = {} for(const asset of char.additionalAssets){ const assetPath = await getFileSrc(asset[1]) assetPaths[asset[0].toLocaleLowerCase()] = assetPath } + if(char.emotionImages){ + for(const emo of char.emotionImages){ + const emoPath = await getFileSrc(emo[1]) + emoPaths[emo[0].toLocaleLowerCase()] = emoPath + } + } data = data.replaceAll(assetRegex, (full:string, type:string, name:string) => { name = name.toLocaleLowerCase() + if(type === 'emotion'){ + console.log(emoPaths, name) + const path = emoPaths[name] + if(!path){ + return '' + } + return `${path}` + } const path = assetPaths[name] if(!path){ return '' @@ -129,6 +144,7 @@ export interface simpleCharacterArgument{ customscript: customscript[] chaId: string, virtualscript?: string + emotionImages?: [string, string][] } diff --git a/src/ts/process/generateSeed.ts b/src/ts/process/generateSeed.ts deleted file mode 100644 index 670cbc98..00000000 --- a/src/ts/process/generateSeed.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function generateRandomSeed(length) { - let result = ''; - const characters = '0123456789'; - const charactersLength = characters.length; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} \ No newline at end of file diff --git a/src/ts/process/index.ts b/src/ts/process/index.ts index 2879545f..f23ba439 100644 --- a/src/ts/process/index.ts +++ b/src/ts/process/index.ts @@ -22,6 +22,7 @@ import { cipherChat, decipherChat } from "./cipherChat"; import { getInlayImage, supportsInlayImage } from "../image"; import { getGenerationModelString } from "./models/modelString"; import { sendPeerChar } from "../sync/multiuser"; +import { runInlayScreen } from "./inlayScreen"; export interface OpenAIChat{ role: 'system'|'user'|'assistant'|'function' @@ -299,6 +300,21 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n content: risuChatParser(db.personaPrompt, {chara: currentChar}) }) } + + if(currentChar.inlayViewScreen){ + if(currentChar.viewScreen === 'emotion'){ + unformated.postEverything.push({ + role: 'system', + content: currentChar.newGenData.emotionInstructions.replaceAll('{{slot}}', currentChar.emotionImages.map((v) => v[0]).join(', ')) + }) + } + if(currentChar.viewScreen === 'imggen'){ + unformated.postEverything.push({ + role: 'system', + content: currentChar.newGenData.instructions + }) + } + } if(lorepmt.special_act){ unformated.postEverything.push({ @@ -498,10 +514,15 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n } let inlays:string[] = [] if(db.inlayImage){ - const inlayMatch = formedChat.match(/{{inlay::(.+?)}}/g) - if(inlayMatch){ - for(const inlay of inlayMatch){ - inlays.push(inlay) + if(msg.role === 'char'){ + formedChat = formedChat.replace(/{{inlay::(.+?)}}/g, '') + } + else{ + const inlayMatch = formedChat.match(/{{inlay::(.+?)}}/g) + if(inlayMatch){ + for(const inlay of inlayMatch){ + inlays.push(inlay) + } } } } @@ -653,7 +674,6 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n }) } - function pushPrompts(cha:OpenAIChat[]){ for(const chat of cha){ if(!chat.content){ @@ -913,9 +933,17 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n currentChat = db.characters[selectedChar].chats[selectedChat] const triggerResult = await runTrigger(currentChar, 'output', {chat:currentChat}) - console.log(triggerResult) if(triggerResult && triggerResult.chat){ - db.characters[selectedChar].chats[selectedChat] = triggerResult.chat + currentChat = triggerResult.chat + } + const inlayr = runInlayScreen(currentChar, currentChat.message[msgIndex].data) + currentChat.message[msgIndex].data = inlayr.text + db.characters[selectedChar].chats[selectedChat] = currentChat + setDatabase(db) + if(inlayr.promise){ + const t = await inlayr.promise + currentChat.message[msgIndex].data = t + db.characters[selectedChar].chats[selectedChat] = currentChat setDatabase(db) } await sayTTS(currentChar, result) @@ -938,6 +966,8 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n result2 = await processScriptFull(nowChatroom, reformatContent(beforeChat.data + mess), 'editoutput', msgIndex) } result = result2.data + const inlayResult = runInlayScreen(currentChar, result) + result = inlayResult.text emoChanged = result2.emoChanged if(i === 0 && arg.continue){ db.characters[selectedChar].chats[selectedChat].message[msgIndex] = { @@ -949,6 +979,10 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n model: generationModel, generationId: generationId, } + } + if(inlayResult.promise){ + const p = await inlayResult.promise + db.characters[selectedChar].chats[selectedChat].message[msgIndex].data = p } } else{ @@ -962,6 +996,11 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n generationId: generationId, } }) + const ind = db.characters[selectedChar].chats[selectedChat].message.length - 1 + if(inlayResult.promise){ + const p = await inlayResult.promise + db.characters[selectedChar].chats[selectedChat].message[ind].data = p + } } db.characters[selectedChar].reloadKeys += 1 await sayTTS(currentChar, result) @@ -1005,154 +1044,144 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n } } - if(currentChar.viewScreen === 'emotion' && (!emoChanged) && (abortSignal.aborted === false)){ + if(!currentChar.inlayViewScreen){ + if(currentChar.viewScreen === 'emotion' && (!emoChanged) && (abortSignal.aborted === false)){ - let currentEmotion = currentChar.emotionImages - let emotionList = currentEmotion.map((a) => { - return a[0] - }) - let charemotions = get(CharEmotion) - - let tempEmotion = charemotions[currentChar.chaId] - if(!tempEmotion){ - tempEmotion = [] - } - if(tempEmotion.length > 4){ - tempEmotion.splice(0, 1) - } - - if(db.emotionProcesser === 'embedding'){ - const hypaProcesser = new HypaProcesser('MiniLM') - await hypaProcesser.addText(emotionList.map((v) => 'emotion:' + v)) - let searched = (await hypaProcesser.similaritySearchScored(result)).map((v) => { - v[0] = v[0].replace("emotion:",'') - return v - }) - - //give panaltys - for(let i =0;i { - return v[0] === emo[0] - }) - - const modifier = ((5 - ((tempEmotion.length - (i + 1))))) / 200 - - if(index !== -1){ - searched[index][1] -= modifier - } - } - - //make a sorted array by score - const emoresult = searched.sort((a,b) => { - return b[1] - a[1] - }).map((v) => { - return v[0] - }) - - console.log(searched) - - for(const emo of currentEmotion){ - if(emo[0] === emoresult[0]){ - const emos:[string, string,number] = [emo[0], emo[1], Date.now()] - tempEmotion.push(emos) - charemotions[currentChar.chaId] = tempEmotion - CharEmotion.set(charemotions) - break - } - } - - - - return true - } - - function shuffleArray(array:string[]) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } - return array - } - - let emobias:{[key:number]:number} = {} - - for(const emo of emotionList){ - const tokens = await tokenizeNum(emo) - for(const token of tokens){ - emobias[token] = 10 - } - } - - for(let i =0;i { + let currentEmotion = currentChar.emotionImages + let emotionList = currentEmotion.map((a) => { return a[0] }) - try { - const emotion:string = rq.result.replace(/ |\n/g,'').trim().toLocaleLowerCase() - let emotionSelected = false + let charemotions = get(CharEmotion) + + let tempEmotion = charemotions[currentChar.chaId] + if(!tempEmotion){ + tempEmotion = [] + } + if(tempEmotion.length > 4){ + tempEmotion.splice(0, 1) + } + + if(db.emotionProcesser === 'embedding'){ + const hypaProcesser = new HypaProcesser('MiniLM') + await hypaProcesser.addText(emotionList.map((v) => 'emotion:' + v)) + let searched = (await hypaProcesser.similaritySearchScored(result)).map((v) => { + v[0] = v[0].replace("emotion:",'') + return v + }) + + //give panaltys + for(let i =0;i { + return v[0] === emo[0] + }) + + const modifier = ((5 - ((tempEmotion.length - (i + 1))))) / 200 + + if(index !== -1){ + searched[index][1] -= modifier + } + } + + //make a sorted array by score + const emoresult = searched.sort((a,b) => { + return b[1] - a[1] + }).map((v) => { + return v[0] + }) + + console.log(searched) + for(const emo of currentEmotion){ - if(emo[0] === emotion){ + if(emo[0] === emoresult[0]){ const emos:[string, string,number] = [emo[0], emo[1], Date.now()] tempEmotion.push(emos) charemotions[currentChar.chaId] = tempEmotion CharEmotion.set(charemotions) - emotionSelected = true break } } - if(!emotionSelected){ + + + + return true + } + + function shuffleArray(array:string[]) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array + } + + let emobias:{[key:number]:number} = {} + + for(const emo of emotionList){ + const tokens = await tokenizeNum(emo) + for(const token of tokens){ + emobias[token] = 10 + } + } + + for(let i =0;i { + return a[0] + }) + try { + const emotion:string = rq.result.replace(/ |\n/g,'').trim().toLocaleLowerCase() + let emotionSelected = false for(const emo of currentEmotion){ - if(emotion.includes(emo[0])){ + if(emo[0] === emotion){ const emos:[string, string,number] = [emo[0], emo[1], Date.now()] tempEmotion.push(emos) charemotions[currentChar.chaId] = tempEmotion @@ -1161,47 +1190,55 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n break } } + if(!emotionSelected){ + for(const emo of currentEmotion){ + if(emotion.includes(emo[0])){ + const emos:[string, string,number] = [emo[0], emo[1], Date.now()] + tempEmotion.push(emos) + charemotions[currentChar.chaId] = tempEmotion + CharEmotion.set(charemotions) + emotionSelected = true + break + } + } + } + if(!emotionSelected && emotionList.includes('neutral')){ + const emo = currentEmotion[emotionList.indexOf('neutral')] + const emos:[string, string,number] = [emo[0], emo[1], Date.now()] + tempEmotion.push(emos) + charemotions[currentChar.chaId] = tempEmotion + CharEmotion.set(charemotions) + emotionSelected = true + } + } catch (error) { + alertError(language.errors.httpError + `${error}`) + return true } - if(!emotionSelected && emotionList.includes('neutral')){ - const emo = currentEmotion[emotionList.indexOf('neutral')] - const emos:[string, string,number] = [emo[0], emo[1], Date.now()] - tempEmotion.push(emos) - charemotions[currentChar.chaId] = tempEmotion - CharEmotion.set(charemotions) - emotionSelected = true + } + + return true + + + } + else if(currentChar.viewScreen === 'imggen'){ + if(chatProcessIndex !== -1){ + alertError("Stable diffusion in group chat is not supported") + } + + const msgs = db.characters[selectedChar].chats[selectedChat].message + let msgStr = '' + for(let i = (msgs.length - 1);i>=0;i--){ + if(msgs[i].role === 'char'){ + msgStr = `character: ${msgs[i].data.replace(/\n/, ' ')} \n` + msgStr + } + else{ + msgStr = `user: ${msgs[i].data.replace(/\n/, ' ')} \n` + msgStr + break } - } catch (error) { - alertError(language.errors.httpError + `${error}`) - return true } - } - - return true - } - else if(currentChar.viewScreen === 'imggen'){ - if(chatProcessIndex !== -1){ - alertError("Stable diffusion in group chat is not supported") - } - - const msgs = db.characters[selectedChar].chats[selectedChat].message - let msgStr = '' - for(let i = (msgs.length - 1);i>=0;i--){ - if(msgs[i].role === 'char'){ - msgStr = `character: ${msgs[i].data.replace(/\n/, ' ')} \n` + msgStr - } - else{ - msgStr = `user: ${msgs[i].data.replace(/\n/, ' ')} \n` + msgStr - break - } - } - - - const ch = await stableDiff(currentChar, msgStr) - if(ch){ - db.characters[selectedChar].chats[selectedChat].sdData = ch - setDatabase(db) + await stableDiff(currentChar, msgStr) } } diff --git a/src/ts/process/inlayScreen.ts b/src/ts/process/inlayScreen.ts new file mode 100644 index 00000000..87839eb8 --- /dev/null +++ b/src/ts/process/inlayScreen.ts @@ -0,0 +1,97 @@ +import { writeInlayImage } from "../image"; +import type { character } from "../storage/database"; +import { generateAIImage } from "./stableDiff"; + +const imggenRegex = [//gi, /{{ImgGen="(.+?)"}}/gi] as const + +export function runInlayScreen(char:character, data:string):{text:string, promise?:Promise} { + if(char.inlayViewScreen){ + if(char.viewScreen === 'emotion'){ + return {text: data.replace(//gi, '{{emotion::$1}}')} + } + if(char.viewScreen === 'imggen'){ + return { + text: data.replace(imggenRegex[0],'[Generating...]').replace(imggenRegex[1],'[Generating...]'), + promise : (async () => { + for(const regex of imggenRegex){ + const promises:Promise[] = []; + const neg = char.newGenData.negative + data.replace(regex, (match, p1) => { + const prompt = char.newGenData.prompt.replaceAll('{{slot}}', p1) + promises.push((async () => { + const v = await generateAIImage(prompt, char, neg, 'inlay') + if(!v){ + return '' + } + const imgHTML = new Image() + imgHTML.src = v + const inlay = await writeInlayImage(imgHTML) + return inlay + })()) + return match + }) + const d = await Promise.all(promises) + data = data.replace(regex, () => { + const result = d.shift() + if(result === false){ + return '' + } + return result + }) + } + return data + })() + } + } + + } + + return {text: data} +} + +export function updateInlayScreen(char:character):character { + switch(char.viewScreen){ + case 'emotion': + if(char.inlayViewScreen){ + char.newGenData = { + prompt: '', + negative: '', + instructions: '', + emotionInstructions: `You must always output the character's emotional image as a command at the end of a conversation. The command must be selected from a given list, and it's better to have variety than to repeat images used in previous chats. Use one image, depending on the character's emotion. See the list below. Form: "> Example: List of commands: {{slot}}`, + } + return char + } + char.newGenData = { + prompt: '', + negative: '', + instructions: '', + emotionInstructions: `You must always output the character's emotional image as a command. The command must be selected from a given list, only output the command, depending on the character's emotion. List of commands: {{slot}}` + } + return char + case 'imggen': + if(char.inlayViewScreen){ + char.newGenData = { + prompt: 'best quality, {{slot}}', + negative: 'worse quality', + instructions: 'You must always output the character\'s image as a keyword-formatted prompts that can be used in stable diffusion at the end of a conversation. Use one image, depending on character, place, situation, etc. keyword should be long enough. Form: ">', + emotionInstructions: '' + } + return char + } + char.newGenData = { + prompt: 'best quality, {{slot}}', + negative: 'worse quality', + instructions: 'You must always output the character\'s image as a keyword-formatted prompts that can be used in stable diffusion. only output the that prompt, depending on character, place, situation, etc. keyword should be long enough.', + emotionInstructions: '' + } + return char + default: + char.newGenData = { + prompt: '', + negative: '', + instructions: '', + emotionInstructions: '' + } + return char + } +} \ No newline at end of file diff --git a/src/ts/process/processzip.ts b/src/ts/process/processzip.ts index 6ef98fb9..8fefe851 100644 --- a/src/ts/process/processzip.ts +++ b/src/ts/process/processzip.ts @@ -1,8 +1,8 @@ -import JSZip from "jszip"; export async function processZip(dataArray: Uint8Array): Promise { + const jszip = await import("jszip"); const blob = new Blob([dataArray], { type: "application/zip" }); - const zip = new JSZip(); + const zip = new jszip.default(); const zipData = await zip.loadAsync(blob); const imageFile = Object.keys(zipData.files).find(fileName => /\.(jpg|jpeg|png)$/.test(fileName)); diff --git a/src/ts/process/stableDiff.ts b/src/ts/process/stableDiff.ts index 262fbb14..26f6f077 100644 --- a/src/ts/process/stableDiff.ts +++ b/src/ts/process/stableDiff.ts @@ -6,11 +6,7 @@ import { globalFetch, readImage } from "../storage/globalApi" import { CharEmotion } from "../stores" import type { OpenAIChat } from "." import { processZip } from "./processzip" -import { convertToBase64 } from "./uinttobase64" -import type { List } from "lodash" -import { generateRandomSeed } from "./generateSeed" export async function stableDiff(currentChar:character,prompt:string){ - const mainPrompt = "assistant is a chat analyzer.\nuser will input a data of situation with key and values before chat, and a chat of a user and character.\nView the status of the chat and change the data.\nif data's key starts with $, it must change it every time.\nif data value is none, it must change it." let db = get(DataBase) if(db.sdProvider === ''){ @@ -18,53 +14,14 @@ export async function stableDiff(currentChar:character,prompt:string){ return false } - let proompt = 'Data:' - let currentSd:[string,string][] = [] - - const sdData = currentChar.chats[currentChar.chatPage].sdData - if(sdData){ - const das = sdData.split('\n') - for(const data of das){ - const splited = data.split(':::') - currentSd.push([splited[0].trim(), splited[1].trim()]) - } - } - else{ - currentSd = JSON.parse(JSON.stringify(currentChar.sdData)) - } - - for(const d of currentSd){ - let val = d[1].trim() - if(val === ''){ - val = 'none' - } - - if(!d[0].startsWith('|') || d[0] === 'negative' || d[0] === 'always'){ - proompt += `\n${d[0].trim()}: ${val}` - } - } - - proompt += `\n\nChat:\n${prompt}` + const proompt = `Chat:\n${prompt}` const promptbody:OpenAIChat[] = [ { role:'system', - content: mainPrompt - }, - { - role: 'user', - content: `Data:\ncharacter's appearance: red hair, cute, black eyes\ncurrent situation: none\n$character's pose: none\n$character's emotion: none\n\nChat:\nuser: *eats breakfeast* \n I'm ready.\ncharacter: Lemon waits patiently outside your room while you get ready. Once you are dressed and have finished your breakfast, she escorts you to the door.\n"Have a good day at school, Master. Don't forget to study hard and make the most of your time there," Lemon reminds you with a smile as she sees you off.` - }, - { - role: 'assistant', - content: "character's appearance: red hair, cute, black eyes\ncurrent situation: waking up in the morning\n$character's pose: standing\n$character's emotion: apologetic" - }, - { - - role:'system', - content: mainPrompt + content: currentChar.newGenData.instructions }, { role: 'user', @@ -72,7 +29,6 @@ export async function stableDiff(currentChar:character,prompt:string){ }, ] - console.log(proompt) const rq = await requestChatData({ formated: promptbody, currentChar: currentChar, @@ -86,38 +42,20 @@ export async function stableDiff(currentChar:character,prompt:string){ alertError(`${rq.result}`) return false } - else{ - const res = rq.result - const das = res.split('\n') - for(const data of das){ - const splited = data.split(':') - if(splited.length === 2){ - for(let i=0;i { - return val.join(':::') - }).join('\n') + const r = rq.result + + const genPrompt = currentChar.newGenData.prompt.replaceAll('{{slot}}', r) + const neg = currentChar.newGenData.negative + + return await generateAIImage(genPrompt, currentChar, neg, '') +} + +export async function generateAIImage(genPrompt:string, currentChar:character, neg:string, returnSdData:string){ + const db = get(DataBase) if(db.sdProvider === 'webui'){ - let prompts:string[] = [] - let neg = '' - for(let i=0;i { - return new Promise((resolve, reject) => { - const blob = new Blob([data]); - const reader = new FileReader(); - - reader.onloadend = function() { - const base64String = reader.result as string; - resolve(base64String); - }; - - reader.onerror = function(error) { - reject(error); - }; - - reader.readAsDataURL(blob); - }); -} \ No newline at end of file diff --git a/src/ts/storage/database.ts b/src/ts/storage/database.ts index 736b8e94..7be52f40 100644 --- a/src/ts/storage/database.ts +++ b/src/ts/storage/database.ts @@ -15,7 +15,7 @@ import type { OobaChatCompletionRequestParams } from '../model/ooba'; export const DataBase = writable({} as any as Database) export const loadedStore = writable(false) -export let appVer = "1.59.9" +export let appVer = "1.60.1" export let webAppSubVer = '' export function setDatabase(data:Database){ @@ -588,6 +588,12 @@ export interface character{ globalLore: loreBook[] chaId: string sdData: [string, string][] + newGenData?: { + prompt: string, + negative: string, + instructions: string, + emotionInstructions: string, + } customscript: customscript[] triggerscript: triggerscript[] utilityBot: boolean @@ -641,6 +647,7 @@ export interface character{ extentions?:{[key:string]:any} largePortrait?:boolean lorePlus?:boolean + inlayViewScreen?:boolean } diff --git a/src/ts/storage/globalApi.ts b/src/ts/storage/globalApi.ts index e7db8f9b..4036e157 100644 --- a/src/ts/storage/globalApi.ts +++ b/src/ts/storage/globalApi.ts @@ -685,16 +685,6 @@ export async function globalFetch(url:string, arg:{ headers: headers, method: method }) - if(da.headers.get('content-type')?.includes('application/x-zip-compressed')){ - const daText = await da.blob() - - addFetchLog(daText, da.ok && da.status >= 200 && da.status < 300) - return { - ok: da.ok && da.status >= 200 && da.status < 300, - data: daText, - headers: Object.fromEntries(da.headers) - } - } const daText = await da.text() try { const dat = JSON.parse(daText) diff --git a/src/ts/stores.ts b/src/ts/stores.ts index 6140b1a5..d6c5f4f2 100644 --- a/src/ts/stores.ts +++ b/src/ts/stores.ts @@ -49,6 +49,7 @@ function createSimpleCharacter(char:character|groupChat){ chaId: char.chaId, additionalAssets: char.additionalAssets, virtualscript: char.virtualscript, + emotionImages: char.emotionImages, } return simpleChar @@ -71,7 +72,9 @@ function updateCurrentCharacter(){ if(isEqual(gotCharacter, currentChar)){ return } - ShowVN.set(currentChar?.viewScreen === 'vn') + if((currentChar?.viewScreen === 'vn') !== get(ShowVN)){ + ShowVN.set(currentChar?.viewScreen === 'vn') + } console.log("Character updated") CurrentCharacter.set(cloneDeep(currentChar)) diff --git a/version.json b/version.json index 2dbe093e..e1139a9e 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{"version":"1.59.9"} \ No newline at end of file +{"version":"1.60.1"} \ No newline at end of file