Update to 1.16.0 (#96)

This commit is contained in:
kwaroran
2023-05-24 10:11:35 +09:00
committed by GitHub
24 changed files with 753 additions and 274 deletions

View File

@@ -0,0 +1 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 960 B

BIN
public/icon/patreon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

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

View File

@@ -67,7 +67,7 @@ export const languageEnglish = {
+ "\n\n```<START>``` Marks the beginning of a new conversation.", + "\n\n```<START>``` Marks the beginning of a new conversation.",
creatorQuotes: "Note that appearances on top of first message. Used to inform users about this character. It doesn't go into prompt.", creatorQuotes: "Note that appearances on top of first message. Used to inform users about this character. It doesn't go into prompt.",
systemPrompt: "A prompt that replaces main prompt in settings if its not blank.", systemPrompt: "A prompt that replaces main prompt in settings if its not blank.",
chatNote: "a note that strongly effects model behavior. embbedded to current chat. also known as memory.", chatNote: "a note that strongly effects model behavior. embbedded to current chat. also known as memory or ujb.",
personality: "A brief description about character's personality. \n\n**It is not recommended to use this option. Describe it in character description instead.**", personality: "A brief description about character's personality. \n\n**It is not recommended to use this option. Describe it in character description instead.**",
scenario: "A brief description about character's scenario. \n\n**It is not recommended to use this option. Describe it in character description instead.**", scenario: "A brief description about character's scenario. \n\n**It is not recommended to use this option. Describe it in character description instead.**",
utilityBot: "When activated, it ignores main prompt. \n\n**It is not recommended to use this option. Modifiy system prompt instead.**", utilityBot: "When activated, it ignores main prompt. \n\n**It is not recommended to use this option. Modifiy system prompt instead.**",
@@ -76,7 +76,8 @@ export const languageEnglish = {
superMemory: "SuperMemory makes your character memorize more by giving summarized data to AI.\n\n" superMemory: "SuperMemory makes your character memorize more by giving summarized data to AI.\n\n"
+ "SuperMemory model is a model that summarizes that text. davinci is recommended, and Auxiliary models are not recommended unless it is an unfiltered model with over 2000 tokens with great summarizing skill.\n\n" + "SuperMemory model is a model that summarizes that text. davinci is recommended, and Auxiliary models are not recommended unless it is an unfiltered model with over 2000 tokens with great summarizing skill.\n\n"
+ "SuperMemory Prompt decides what prompt should be sent to summarize. if you leave it blank, it will use the default prompt. leaving blank is recommended.\n\n" + "SuperMemory Prompt decides what prompt should be sent to summarize. if you leave it blank, it will use the default prompt. leaving blank is recommended.\n\n"
+ "After it is all setup, you can able it in the setting of a character." + "After it is all setup, you can able it in the setting of a character.",
replaceGlobalNote: "If its not blank, it replaces current global note to this."
}, },
setup: { setup: {
chooseProvider: "Choose AI Provider", chooseProvider: "Choose AI Provider",
@@ -266,5 +267,8 @@ export const languageEnglish = {
textScreenBorder: "Text Screen Borders", textScreenBorder: "Text Screen Borders",
ttsReadOnlyQuoted: "Read Only Quoted", ttsReadOnlyQuoted: "Read Only Quoted",
ttsStop: "Stop TTS", ttsStop: "Stop TTS",
askRemoval:"Ask Removal" askRemoval:"Ask Removal",
replaceGlobalNote: "Global Note Replacement",
charLoreBook: 'Character Lorebook',
globalLoreBook: 'Global Lorebook',
} }

View File

@@ -246,6 +246,9 @@ export const languageKorean = {
textScreenBorder: "채팅창 윤곽선", textScreenBorder: "채팅창 윤곽선",
ttsReadOnlyQuoted: "따옴표 안 텍스트만 읽기", ttsReadOnlyQuoted: "따옴표 안 텍스트만 읽기",
ttsStop: "TTS 중지", ttsStop: "TTS 중지",
askRemoval:"삭제 확인" askRemoval:"삭제 확인",
replaceGlobalNote: "글로벌 노트 덮어쓰기",
charLoreBook: '캐릭터 로어북',
globalLoreBook: '글로벌 로어북',
} }

View File

@@ -1,11 +1,24 @@
<script lang="ts"> <script lang="ts">
import { isTauri } from "src/ts/globalApi"; import { isTauri, openURL } from "src/ts/globalApi";
</script> </script>
<svelte:head> <svelte:head>
<script async defer src="https://buttons.github.io/buttons.js"></script> <script async defer src="https://buttons.github.io/buttons.js"></script>
</svelte:head> </svelte:head>
<!-- Place this tag where you want the button to render. -->
{#if !isTauri} <div class="flex gap-2 items-center mt-2">
{#if !isTauri}
<a class="github-button mt-4" href="https://github.com/kwaroran/risuAI" data-color-scheme="no-preference: dark; light: dark; dark: dark;" data-size="large" data-show-count="true" aria-label="Star kwaroran/risuAI on GitHub">Star</a> <a class="github-button mt-4" href="https://github.com/kwaroran/risuAI" data-color-scheme="no-preference: dark; light: dark; dark: dark;" data-size="large" data-show-count="true" aria-label="Star kwaroran/risuAI on GitHub">Star</a>
{/if} {:else}
<button on:click={() => {
openURL("https://github.com/kwaroran/risuAI")
}}>
<img src="/icon/github-mark-white.svg" width="24" alt="github" />
</button>
{/if}
<button on:click={() => {
openURL("https://www.patreon.com/RisuAI")
}}>
<img src="/icon/patreon.png" width="24" alt="github" />
</button>
</div>

View File

@@ -6,6 +6,7 @@
import { customProviderStore, getCurrentPluginMax } from "src/ts/process/plugins"; import { customProviderStore, getCurrentPluginMax } from "src/ts/process/plugins";
import { isTauri } from "src/ts/globalApi"; import { isTauri } from "src/ts/globalApi";
import { tokenize } from "src/ts/tokenizer"; import { tokenize } from "src/ts/tokenizer";
import ModelList from "src/lib/UI/ModelList.svelte";
import DropList from "src/lib/SideBars/DropList.svelte"; import DropList from "src/lib/SideBars/DropList.svelte";
import { PlusIcon, TrashIcon } from "lucide-svelte"; import { PlusIcon, TrashIcon } from "lucide-svelte";
let tokens = { let tokens = {
@@ -36,26 +37,10 @@
<h2 class="mb-2 text-2xl font-bold mt-2">{language.chatBot}</h2> <h2 class="mb-2 text-2xl font-bold mt-2">{language.chatBot}</h2>
<span class="text-neutral-200 mt-4">{language.model} <Help key="model"/></span> <span class="text-neutral-200 mt-4">{language.model} <Help key="model"/></span>
<select class="bg-transparent input-text mt-2 mb-2 text-gray-200 appearance-none text-sm" bind:value={$DataBase.aiModel}> <ModelList bind:value={$DataBase.aiModel}/>
<option value="gpt35" class="bg-darkbg appearance-none">OpenAI GPT-3.5</option>
<option value="gpt4" class="bg-darkbg appearance-none">OpenAI GPT-4</option>
<option value="textgen_webui" class="bg-darkbg appearance-none">Text Generation WebUI</option>
<option value="palm2" class="bg-darkbg appearance-none">Google Palm2</option>
{#if $DataBase.plugins.length > 0}
<option value="custom" class="bg-darkbg appearance-none">Plugin</option>
{/if}
</select>
<span class="text-neutral-200 mt-2">{language.submodel} <Help key="submodel"/></span> <span class="text-neutral-200 mt-2">{language.submodel} <Help key="submodel"/></span>
<select class="bg-transparent input-text mt-2 mb-4 text-gray-200 appearance-none text-sm" bind:value={$DataBase.subModel}> <ModelList bind:value={$DataBase.subModel}/>
<option value="gpt35" class="bg-darkbg appearance-none">OpenAI GPT-3.5</option>
<option value="gpt4" class="bg-darkbg appearance-none">OpenAI GPT-4</option>
<option value="palm2" class="bg-darkbg appearance-none">Google Palm2</option>
<option value="textgen_webui" class="bg-darkbg appearance-none">Text Generation WebUI</option>
{#if $customProviderStore.length > 0}
<option value="custom" class="bg-darkbg appearance-none">Plugin</option>
{/if}
</select>
{#if $DataBase.aiModel === 'palm2' || $DataBase.subModel === 'palm2'} {#if $DataBase.aiModel === 'palm2' || $DataBase.subModel === 'palm2'}
<span class="text-neutral-200">Palm2 {language.apiKey}</span> <span class="text-neutral-200">Palm2 {language.apiKey}</span>
@@ -78,6 +63,17 @@
{/each} {/each}
</select> </select>
{/if} {/if}
{#if $DataBase.aiModel === "novelai" || $DataBase.subModel === "novelai"}
<span class="text-neutral-200">NovelAI Bearer Token</span>
<input class="text-neutral-200 p-2 bg-transparent input-text focus:bg-selected text-sm mb-2" bind:value={$DataBase.novelai.token}>
{/if}
{#if $DataBase.aiModel.startsWith("horde") || $DataBase.subModel.startsWith("horde") }
<span class="text-neutral-200">Horde {language.apiKey}</span>
<input class="text-neutral-200 p-2 bg-transparent input-text focus:bg-selected text-sm mb-2" bind:value={$DataBase.hordeConfig.apiKey}>
{/if}
{#if $DataBase.aiModel === 'textgen_webui' || $DataBase.subModel === 'textgen_webui'} {#if $DataBase.aiModel === 'textgen_webui' || $DataBase.subModel === 'textgen_webui'}
<span class="text-neutral-200">TextGen {language.providerURL} <Help key="oogaboogaURL"/></span> <span class="text-neutral-200">TextGen {language.providerURL} <Help key="oogaboogaURL"/></span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected" placeholder="https://..." bind:value={$DataBase.textgenWebUIURL}> <input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected" placeholder="https://..." bind:value={$DataBase.textgenWebUIURL}>
@@ -98,12 +94,15 @@
<span class="text-gray-400 mb-6 text-sm">{tokens.globalNote} {language.tokens}</span> <span class="text-gray-400 mb-6 text-sm">{tokens.globalNote} {language.tokens}</span>
<span class="text-neutral-200">{language.maxContextSize}</span> <span class="text-neutral-200">{language.maxContextSize}</span>
{#if $DataBase.aiModel === 'gpt35'} {#if $DataBase.aiModel === 'gpt35'}
<input class="text-neutral-200 mb-4 text-sm p-2 bg-transparent input-text focus:bg-selected" type="number" min={0} max="4000" bind:value={$DataBase.maxContext}> <input class="text-neutral-200 mb-4 text-sm p-2 bg-transparent input-text focus:bg-selected" type="number" min={0} max="4000" bind:value={$DataBase.maxContext}>
{:else if $DataBase.aiModel === 'gpt4' || $DataBase.aiModel === 'textgen_webui'} {:else if $DataBase.aiModel === 'gpt4' || $DataBase.aiModel === 'textgen_webui'}
<input class="text-neutral-200 mb-4 text-sm p-2 bg-transparent input-text focus:bg-selected" type="number" min={0} max="8000" bind:value={$DataBase.maxContext}> <input class="text-neutral-200 mb-4 text-sm p-2 bg-transparent input-text focus:bg-selected" type="number" min={0} max="8000" bind:value={$DataBase.maxContext}>
{:else if $DataBase.aiModel === 'custom'} {:else if $DataBase.aiModel === 'custom'}
<input class="text-neutral-200 mb-4 text-sm p-2 bg-transparent input-text focus:bg-selected" type="number" min={0} max={getCurrentPluginMax($DataBase.currentPluginProvider)} bind:value={$DataBase.maxContext}> <input class="text-neutral-200 mb-4 text-sm p-2 bg-transparent input-text focus:bg-selected" type="number" min={0} max={getCurrentPluginMax($DataBase.currentPluginProvider)} bind:value={$DataBase.maxContext}>
{:else}
<input class="text-neutral-200 mb-4 text-sm p-2 bg-transparent input-text focus:bg-selected" type="number" min={0} bind:value={$DataBase.maxContext}>
{/if} {/if}
<span class="text-neutral-200">{language.maxResponseSize}</span> <span class="text-neutral-200">{language.maxResponseSize}</span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" type="number" min={0} max="2048" bind:value={$DataBase.maxResponse}> <input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" type="number" min={0} max="2048" bind:value={$DataBase.maxResponse}>

View File

@@ -0,0 +1,11 @@
<script lang="ts">
import { language } from "src/lang";
import Help from "src/lib/Others/Help.svelte";
import LoreBookSetting from "src/lib/SideBars/LoreBookSetting.svelte";
import { DataBase } from "src/ts/database";
export let openLoreList = false
</script>
<h2 class="mb-2 text-2xl font-bold mt-2">{language.globalLoreBook} <Help key="lorebook" /></h2>
<button on:click={() => {openLoreList = true}} class="mt-4 drop-shadow-lg p-3 flex justify-center items-center ml-2 mr-2 rounded-lg bg-selected mb-4">{$DataBase.loreBook[$DataBase.loreBookPage].name}</button>
<LoreBookSetting globalMode />

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { ActivityIcon, BotIcon, BoxIcon, CodeIcon, DiamondIcon, FolderIcon, MonitorIcon, Sailboat, UserIcon, XCircleIcon } from "lucide-svelte"; import { ActivityIcon, BookIcon, BotIcon, BoxIcon, CodeIcon, DiamondIcon, FolderIcon, MonitorIcon, Sailboat, UserIcon, XCircleIcon } from "lucide-svelte";
import { language } from "src/lang"; import { language } from "src/lang";
import DisplaySettings from "./Pages/DisplaySettings.svelte"; import DisplaySettings from "./Pages/DisplaySettings.svelte";
import UserSettings from "./Pages/UserSettings.svelte"; import UserSettings from "./Pages/UserSettings.svelte";
@@ -8,12 +8,14 @@
import PluginSettings from "./Pages/PluginSettings.svelte"; import PluginSettings from "./Pages/PluginSettings.svelte";
import FilesSettings from "./Pages/FilesSettings.svelte"; import FilesSettings from "./Pages/FilesSettings.svelte";
import AdvancedSettings from "./Pages/AdvancedSettings.svelte"; import AdvancedSettings from "./Pages/AdvancedSettings.svelte";
import { SizeStore, settingsOpen } from "src/ts/stores"; import { settingsOpen } from "src/ts/stores";
import Botpreset from "./botpreset.svelte"; import Botpreset from "./botpreset.svelte";
import Communities from "./Pages/Communities.svelte"; import Communities from "./Pages/Communities.svelte";
import { openURL } from "src/ts/globalApi"; import GlobalLoreBookSettings from "./Pages/GlobalLoreBookSettings.svelte";
import Lorepreset from "./lorepreset.svelte";
let selected = -1 let selected = -1
let openPresetList = false let openPresetList = false
let openLoreList = false
if(window.innerWidth >= 700){ if(window.innerWidth >= 700){
selected = 0 selected = 0
} }
@@ -48,6 +50,12 @@
<MonitorIcon /> <MonitorIcon />
<span>{language.display}</span> <span>{language.display}</span>
</button> </button>
<button class="text-gray-400 flex gap-2 items-center hover:text-gray-200" class:text-white={selected === 8} on:click={() => {
selected = 8
}}>
<BookIcon />
<span>{language.globalLoreBook}</span>
</button>
<button class="text-gray-400 flex gap-2 items-center hover:text-gray-200" class:text-white={selected === 4} on:click={() => { <button class="text-gray-400 flex gap-2 items-center hover:text-gray-200" class:text-white={selected === 4} on:click={() => {
selected = 4 selected = 4
}}> }}>
@@ -97,6 +105,8 @@
<AdvancedSettings /> <AdvancedSettings />
{:else if selected === 7} {:else if selected === 7}
<Communities /> <Communities />
{:else if selected === 8}
<GlobalLoreBookSettings bind:openLoreList />
{/if} {/if}
<button class="absolute top-2 right-2 hover:text-green-500" on:click={() => { <button class="absolute top-2 right-2 hover:text-green-500" on:click={() => {
if(window.innerWidth >= 700){ if(window.innerWidth >= 700){
@@ -115,6 +125,9 @@
{#if openPresetList} {#if openPresetList}
<Botpreset close={() => {openPresetList = false}} /> <Botpreset close={() => {openPresetList = false}} />
{/if} {/if}
{#if openLoreList}
<Lorepreset close={() => {openLoreList = false}} />
{/if}
<style> <style>
.setting-bg{ .setting-bg{
background: linear-gradient(to right, #21222C 50%, #282a36 50%); background: linear-gradient(to right, #21222C 50%, #282a36 50%);

View File

@@ -1,8 +1,8 @@
<script> <script>
import { alertConfirm, alertError } from "../../ts/alert"; import { alertConfirm, alertError } from "../../ts/alert";
import { language } from "../../lang"; import { language } from "../../lang";
import { DataBase, changeToPreset, presetTemplate } from "../../ts/database"; import { DataBase, changeToPreset, copyPreset, presetTemplate } from "../../ts/database";
import { EditIcon, PlusIcon, TrashIcon, XIcon } from "lucide-svelte"; import { CopyIcon, EditIcon, PlusIcon, TrashIcon, XIcon } from "lucide-svelte";
let editMode = false let editMode = false
export let close = () => {} export let close = () => {}
@@ -10,7 +10,7 @@
</script> </script>
<div class="absolute w-full h-full z-40 bg-black bg-opacity-50 flex justify-center items-center"> <div class="absolute w-full h-full z-40 bg-black bg-opacity-50 flex justify-center items-center">
<div class="bg-darkbg p-4 break-any rounded-md flex flex-col max-w-3xl w-96"> <div class="bg-darkbg p-4 break-any rounded-md flex flex-col max-w-3xl w-96 max-h-full overflow-y-auto">
<div class="flex items-center text-neutral-200 mb-4"> <div class="flex items-center text-neutral-200 mb-4">
<h2 class="mt-0 mb-0">{language.presets}</h2> <h2 class="mt-0 mb-0">{language.presets}</h2>
<div class="flex-grow flex justify-end"> <div class="flex-grow flex justify-end">
@@ -35,6 +35,12 @@
<span>{presets.name}</span> <span>{presets.name}</span>
{/if} {/if}
<div class="flex-grow flex justify-end"> <div class="flex-grow flex justify-end">
<button class="text-gray-500 hover:text-green-500 cursor-pointer mr-2" on:click={(e) => {
e.stopPropagation()
copyPreset(i)
}}>
<CopyIcon size={18}/>
</button>
<button class="text-gray-500 hover:text-green-500 cursor-pointer" on:click={async (e) => { <button class="text-gray-500 hover:text-green-500 cursor-pointer" on:click={async (e) => {
e.stopPropagation() e.stopPropagation()
if($DataBase.botPresets.length === 1){ if($DataBase.botPresets.length === 1){

View File

@@ -0,0 +1,77 @@
<script>
import { alertConfirm, alertError } from "../../ts/alert";
import { language } from "../../lang";
import { DataBase } from "../../ts/database";
import { EditIcon, PlusIcon, TrashIcon, XIcon } from "lucide-svelte";
let editMode = false
export let close = () => {}
</script>
<div class="absolute w-full h-full z-40 bg-black bg-opacity-50 flex justify-center items-center">
<div class="bg-darkbg p-4 break-any rounded-md flex flex-col max-w-3xl w-96 max-h-full overflow-y-auto">
<div class="flex items-center text-neutral-200 mb-4">
<h2 class="mt-0 mb-0">{language.loreBook}</h2>
<div class="flex-grow flex justify-end">
<button class="text-gray-500 hover:text-green-500 mr-2 cursor-pointer items-center" on:click={close}>
<XIcon size={24}/>
</button>
</div>
</div>
{#each $DataBase.loreBook as lore, ind}
<button on:click={() => {
if(!editMode){
$DataBase.loreBookPage = ind
}
}} class="flex items-center text-neutral-200 border-t-1 border-solid border-0 border-gray-600 p-2 cursor-pointer" class:bg-selected={ind === $DataBase.loreBookPage}>
{#if editMode}
<input class="text-neutral-200 p-2 bg-transparent input-text focus:bg-selected" bind:value={$DataBase.loreBook[ind].name} placeholder="string">
{:else}
<span>{lore.name}</span>
{/if}
<div class="flex-grow flex justify-end">
<button class="text-gray-500 hover:text-green-500 cursor-pointer" on:click={async (e) => {
e.stopPropagation()
if($DataBase.loreBook.length === 1){
return
}
const d = await alertConfirm(`${language.removeConfirm}${lore.name}`)
if(d){
$DataBase.loreBookPage = 0
let loreBook = $DataBase.loreBook
loreBook.splice(ind, 1)
$DataBase.loreBook = loreBook
}
}}>
<TrashIcon size={18}/>
</button>
</div>
</button>
{/each}
<div class="flex mt-2 items-center">
<button class="text-gray-500 hover:text-green-500 cursor-pointer mr-1" on:click={() => {
let loreBooks = $DataBase.loreBook
let newLoreBook = {
name: `New LoreBook`,
data: []
}
loreBooks.push(newLoreBook)
$DataBase.loreBook = loreBooks
}}>
<PlusIcon/>
</button>
<button class="text-gray-500 hover:text-green-500 cursor-pointer" on:click={() => {
editMode = !editMode
}}>
<EditIcon size={18}/>
</button>
</div>
</div>
</div>
<style>
.break-any{
word-break: normal;
overflow-wrap: anywhere;
}
</style>

View File

@@ -181,9 +181,9 @@
<span class="text-neutral-200">{language.firstMessage} <Help key="charFirstMessage"/></span> <span class="text-neutral-200">{language.firstMessage} <Help key="charFirstMessage"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.firstMessage}></textarea> <textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.firstMessage}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.firstMsg} {language.tokens}</span> <span class="text-gray-400 mb-6 text-sm">{tokens.firstMsg} {language.tokens}</span>
<span class="text-neutral-200">{language.authorNote} <Help key="charNote"/></span> <span class="text-neutral-200">{language.authorNote} <Help key="chatNote"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.postHistoryInstructions}></textarea> <textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.chats[currentChar.data.chatPage].note}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.charaNote} {language.tokens}</span> <span class="text-gray-400 mb-6 text-sm">{tokens.localNote} {language.tokens}</span>
{:else} {:else}
<input class="text-neutral-200 mt-2 mb-4 p-2 bg-transparent input-text text-xl focus:bg-selected" placeholder="Group Name" bind:value={currentChar.data.name}> <input class="text-neutral-200 mt-2 mb-4 p-2 bg-transparent input-text text-xl focus:bg-selected" placeholder="Group Name" bind:value={currentChar.data.name}>
@@ -213,11 +213,6 @@
</button> </button>
</div> </div>
<span class="text-neutral-200">{language.chatNotes} <Help key="charNote"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.chats[currentChar.data.chatPage].note}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.localNote} {language.tokens}</span>
{/if} {/if}
<div class="flex mt-6 items-center"> <div class="flex mt-6 items-center">
@@ -520,9 +515,9 @@
<span class="text-neutral-200">{language.systemPrompt} <Help key="systemPrompt"/></span> <span class="text-neutral-200">{language.systemPrompt} <Help key="systemPrompt"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.systemPrompt}></textarea> <textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.systemPrompt}></textarea>
<span class="text-neutral-200">{language.chatNotes} <Help key="chatNote"/></span> <span class="text-neutral-200">{language.replaceGlobalNote} <Help key="replaceGlobalNote"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.chats[currentChar.data.chatPage].note}></textarea> <textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.replaceGlobalNote}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.localNote} {language.tokens}</span>
{#if currentChar.data.chats[currentChar.data.chatPage].supaMemoryData && currentChar.data.chats[currentChar.data.chatPage].supaMemoryData.length > 4} {#if currentChar.data.chats[currentChar.data.chatPage].supaMemoryData && currentChar.data.chats[currentChar.data.chatPage].supaMemoryData.length > 4}
<span class="text-neutral-200">{language.SuperMemory}</span> <span class="text-neutral-200">{language.SuperMemory}</span>

View File

@@ -7,7 +7,7 @@
import LoreBookData from "./LoreBookData.svelte"; import LoreBookData from "./LoreBookData.svelte";
import Check from "../Others/Check.svelte"; import Check from "../Others/Check.svelte";
let submenu = 0 let submenu = 0
let globalMode = false export let globalMode = false
</script> </script>
{#if !globalMode} {#if !globalMode}
@@ -30,9 +30,26 @@
</div> </div>
{/if} {/if}
{#if submenu !== 2} {#if submenu !== 2}
{#if !globalMode}
<span class="text-gray-500 mt-2 mb-6 text-sm">{submenu === 0 ? $DataBase.characters[$selectedCharID].type === 'group' ? language.groupLoreInfo : language.globalLoreInfo : language.localLoreInfo}</span> <span class="text-gray-500 mt-2 mb-6 text-sm">{submenu === 0 ? $DataBase.characters[$selectedCharID].type === 'group' ? language.groupLoreInfo : language.globalLoreInfo : language.localLoreInfo}</span>
{/if}
<div class="border-solid border-borderc p-2 flex flex-col border-1"> <div class="border-solid border-borderc p-2 flex flex-col border-1">
{#if submenu === 0} {#if globalMode}
{#if $DataBase.loreBook[$DataBase.loreBookPage].data.length === 0}
<span class="text-gray-500">No Lorebook</span>
{:else}
{#each $DataBase.loreBook[$DataBase.loreBookPage].data as book, i}
{#if i !== 0}
<div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div>
{/if}
<LoreBookData bind:value={$DataBase.loreBook[$DataBase.loreBookPage].data[i]} onRemove={() => {
let lore = $DataBase.loreBook[$DataBase.loreBookPage].data
lore.splice(i, 1)
$DataBase.loreBook[$DataBase.loreBookPage].data = lore
}}/>
{/each}
{/if}
{:else if submenu === 0}
{#if $DataBase.characters[$selectedCharID].globalLore.length === 0} {#if $DataBase.characters[$selectedCharID].globalLore.length === 0}
<span class="text-gray-500">No Lorebook</span> <span class="text-gray-500">No Lorebook</span>
{:else} {:else}
@@ -96,16 +113,16 @@
{#if submenu !== 2} {#if submenu !== 2}
<div class="text-gray-500 mt-2 flex"> <div class="text-gray-500 mt-2 flex">
<button on:click={() => {addLorebook(submenu)}} class="hover:text-neutral-200 cursor-pointer"> <button on:click={() => {addLorebook(globalMode ? -1 : submenu)}} class="hover:text-neutral-200 cursor-pointer">
<PlusIcon /> <PlusIcon />
</button> </button>
<button on:click={() => { <button on:click={() => {
exportLoreBook(submenu === 0 ? 'global' : 'local') exportLoreBook(globalMode ? 'sglobal' : submenu === 0 ? 'global' : 'local')
}} class="hover:text-neutral-200 ml-1 cursor-pointer"> }} class="hover:text-neutral-200 ml-1 cursor-pointer">
<DownloadIcon /> <DownloadIcon />
</button> </button>
<button on:click={() => { <button on:click={() => {
importLoreBook(submenu === 0 ? 'global' : 'local') importLoreBook(globalMode ? 'sglobal' : submenu === 0 ? 'global' : 'local')
}} class="hover:text-neutral-200 ml-2 cursor-pointer"> }} class="hover:text-neutral-200 ml-2 cursor-pointer">
<FolderUpIcon /> <FolderUpIcon />
</button> </button>

View File

@@ -289,16 +289,16 @@
</script> </script>
<div <div
class="flex h-full w-20 min-w-20 flex-col items-center overflow-x-hidden overflow-y-scroll bg-bgcolor text-white shadow-lg" class="flex h-full w-20 min-w-20 flex-col items-center bg-bgcolor text-white shadow-lg"
class:editMode class:editMode
> >
<button <button
class="absolute top-0 flex h-8 w-14 min-w-14 cursor-pointer items-center justify-center rounded-b-md bg-gray-500 transition-colors hover:bg-green-500" class="flex h-8 w-14 min-w-14 cursor-pointer items-center justify-center rounded-b-md bg-gray-500 transition-colors hover:bg-green-500"
on:click={() => { on:click={() => {
menuMode = 1 - menuMode; menuMode = 1 - menuMode;
}}><ListIcon /></button }}><ListIcon />
> </button>
<div class="h-8 min-h-8 w-14 min-w-14 bg-transparent" /> <div class="flex flex-grow w-full flex-col items-center overflow-x-hidden overflow-y-auto pr-0">
<div class="h-4 min-h-4 w-14" on:dragover={(e) => { <div class="h-4 min-h-4 w-14" on:dragover={(e) => {
e.preventDefault() e.preventDefault()
e.dataTransfer.dropEffect = 'move' e.dataTransfer.dropEffect = 'move'
@@ -480,6 +480,7 @@
} }
}}><Settings /></BarIcon }}><Settings /></BarIcon
> >
<div class="mt-2"></div>
<BarIcon <BarIcon
onClick={() => { onClick={() => {
reseter(); reseter();
@@ -487,6 +488,7 @@
}}><LayoutGridIcon /></BarIcon }}><LayoutGridIcon /></BarIcon
> >
{/if} {/if}
</div>
</div> </div>
<div <div
class="setting-area flex w-96 flex-col overflow-y-auto overflow-x-hidden bg-darkbg p-6 text-gray-200" class="setting-area flex w-96 flex-col overflow-y-auto overflow-x-hidden bg-darkbg p-6 text-gray-200"

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import { DataBase } from "src/ts/database";
import { isTauri } from "src/ts/globalApi";
import { getHordeModels } from "src/ts/horde/getModels";
export let value = ""
</script>
{#await getHordeModels()}
<select class="bg-transparent input-text mt-2 mb-2 text-gray-200 appearance-none text-sm" value="">
<option value="" class="bg-darkbg appearance-none">Loading...</option>
</select>
{:then models}
<select class="bg-transparent input-text mt-2 mb-2 text-gray-200 appearance-none text-sm" bind:value>
<optgroup class="bg-darkbg appearance-none" label="OpenAI">
<option value="gpt35" class="bg-darkbg appearance-none">OpenAI GPT-3.5</option>
<option value="gpt4" class="bg-darkbg appearance-none">OpenAI GPT-4</option>
</optgroup>
<optgroup class="bg-darkbg appearance-none" label="Other Providers">
<option value="palm2" class="bg-darkbg appearance-none">Google Palm2</option>
<option value="textgen_webui" class="bg-darkbg appearance-none">Text Generation WebUI</option>
{#if $DataBase.plugins.length > 0}
<option value="custom" class="bg-darkbg appearance-none">Plugin</option>
{/if}
</optgroup>
<!-- <optgroup class="bg-darkbg appearance-none" label="Horde">
{#each models as model}
<option value={"horde:::" + model} class="bg-darkbg appearance-none">{model}</option>
{/each}
</optgroup> -->
</select>
{/await}

View File

@@ -203,7 +203,8 @@ function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined
characterVersion: 0, characterVersion: 0,
personality: charaData.personality ?? '', personality: charaData.personality ?? '',
scenario:charaData.scenario ?? '', scenario:charaData.scenario ?? '',
firstMsgIndex: -1 firstMsgIndex: -1,
replaceGlobalNote: ""
} }
} }
@@ -381,7 +382,7 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
mode: "normal", mode: "normal",
alwaysActive: book.constant ?? false, alwaysActive: book.constant ?? false,
selective: book.selective ?? false, selective: book.selective ?? false,
extentions: book.extensions extentions: {...book.extensions, risu_case_sensitive: book.case_sensitive}
}) })
} }
@@ -412,7 +413,7 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
exampleMessage: data.mes_example ?? '', exampleMessage: data.mes_example ?? '',
creatorNotes:data.creator_notes ?? '', creatorNotes:data.creator_notes ?? '',
systemPrompt:data.system_prompt ?? '', systemPrompt:data.system_prompt ?? '',
postHistoryInstructions:data.post_history_instructions ?? '', postHistoryInstructions:'',
alternateGreetings:data.alternate_greetings ?? [], alternateGreetings:data.alternate_greetings ?? [],
tags:data.tags ?? [], tags:data.tags ?? [],
creator:data.creator ?? '', creator:data.creator ?? '',
@@ -428,7 +429,8 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
creator: data.creator, creator: data.creator,
character_version: data.character_version character_version: data.character_version
}, },
additionalAssets: extAssets additionalAssets: extAssets,
replaceGlobalNote: data.post_history_instructions ?? ''
} }
db.characters.push(char) db.characters.push(char)
@@ -458,7 +460,8 @@ export async function exportSpecV2(char:character) {
constant: lore.alwaysActive, constant: lore.alwaysActive,
selective:lore.selective, selective:lore.selective,
name: lore.comment, name: lore.comment,
comment: lore.comment comment: lore.comment,
case_sensitive: lore.extentions?.risu_case_sensitive
}) })
} }
@@ -474,7 +477,7 @@ export async function exportSpecV2(char:character) {
mes_example: char.exampleMessage, mes_example: char.exampleMessage,
creator_notes: char.creatorNotes, creator_notes: char.creatorNotes,
system_prompt: char.systemPrompt, system_prompt: char.systemPrompt,
post_history_instructions: char.postHistoryInstructions, post_history_instructions: char.replaceGlobalNote,
alternate_greetings: char.alternateGreetings, alternate_greetings: char.alternateGreetings,
character_book: { character_book: {
scan_depth: char.loreSettings?.scanDepth, scan_depth: char.loreSettings?.scanDepth,
@@ -625,5 +628,5 @@ interface charBookEntry{
secondary_keys?: Array<string> // see field `selective`. ignored if selective == false secondary_keys?: Array<string> // see field `selective`. ignored if selective == false
constant?: boolean // if true, always inserted in the prompt (within budget limit) constant?: boolean // if true, always inserted in the prompt (within budget limit)
position?: 'before_char' | 'after_char' // whether the entry is placed before or after the character defs position?: 'before_char' | 'after_char' // whether the entry is placed before or after the character defs
case_sensitive?:boolean
} }

View File

@@ -275,7 +275,6 @@ export function characterFormatUpdate(index:number|character){
cha.exampleMessage = cha.exampleMessage ?? '' cha.exampleMessage = cha.exampleMessage ?? ''
cha.creatorNotes = cha.creatorNotes ?? '' cha.creatorNotes = cha.creatorNotes ?? ''
cha.systemPrompt = cha.systemPrompt ?? '' cha.systemPrompt = cha.systemPrompt ?? ''
cha.postHistoryInstructions = cha.postHistoryInstructions ?? ''
cha.tags = cha.tags ?? [] cha.tags = cha.tags ?? []
cha.creator = cha.creator ?? '' cha.creator = cha.creator ?? ''
cha.characterVersion = cha.characterVersion ?? 0 cha.characterVersion = cha.characterVersion ?? 0
@@ -288,6 +287,12 @@ export function characterFormatUpdate(index:number|character){
character_version: 0 character_version: 0
} }
if(cha.postHistoryInstructions){
cha.chats[cha.chatPage].note += "\n" + cha.postHistoryInstructions
cha.chats[cha.chatPage].note = cha.chats[cha.chatPage].note.trim()
cha.postHistoryInstructions = null
}
} }
if(checkNullish(cha.customscript)){ if(checkNullish(cha.customscript)){
cha.customscript = [] cha.customscript = []
@@ -332,7 +337,8 @@ export function createBlankChar():character{
characterVersion: 0, characterVersion: 0,
personality:"", personality:"",
scenario:"", scenario:"",
firstMsgIndex: -1 firstMsgIndex: -1,
replaceGlobalNote: ""
} }
} }

View File

@@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash';
export const DataBase = writable({} as any as Database) export const DataBase = writable({} as any as Database)
export const loadedStore = writable(false) export const loadedStore = writable(false)
export let appVer = '1.15.6' export let appVer = '1.16.0'
export function setDatabase(data:Database){ export function setDatabase(data:Database){
@@ -222,6 +222,26 @@ export function setDatabase(data:Database){
FontColorItalicBold: "#8C8D93" FontColorItalicBold: "#8C8D93"
} }
} }
if(checkNullish(data.hordeConfig)){
data.hordeConfig = {
apiKey: "",
model: "",
softPrompt: ""
}
}
if(checkNullish(data.novelai)){
data.novelai = {
token: "",
model: "clio-v1",
}
}
if(checkNullish(data.loreBook)){
data.loreBookPage = 0
data.loreBook = [{
name: "My First LoreBook",
data: []
}]
}
changeLanguage(data.language) changeLanguage(data.language)
@@ -246,7 +266,9 @@ export interface loreBook{
mode: 'multiple'|'constant'|'normal', mode: 'multiple'|'constant'|'normal',
alwaysActive: boolean alwaysActive: boolean
selective:boolean selective:boolean
extentions?:{} extentions?:{
risu_case_sensitive:boolean
}
} }
export interface character{ export interface character{
@@ -290,6 +312,7 @@ export interface character{
supaMemory?:boolean supaMemory?:boolean
additionalAssets?:[string, string][] additionalAssets?:[string, string][]
ttsReadOnlyQuoted?:boolean ttsReadOnlyQuoted?:boolean
replaceGlobalNote:string
} }
@@ -366,6 +389,11 @@ export interface Database{
jailbreakToggle:boolean jailbreakToggle:boolean
loreBookDepth: number loreBookDepth: number
loreBookToken: number, loreBookToken: number,
loreBook: {
name:string
data:loreBook[]
}[]
loreBookPage: number
supaMemoryPrompt: string supaMemoryPrompt: string
username: string username: string
userIcon: string userIcon: string
@@ -431,6 +459,17 @@ export interface Database{
textScreenRounded?:boolean textScreenRounded?:boolean
textScreenBorder?:string textScreenBorder?:string
characterOrder:(string|folder)[] characterOrder:(string|folder)[]
hordeConfig:hordeConfig,
novelai:{
token:string,
model:string
}
}
interface hordeConfig{
apiKey:string
model:string
softPrompt:string
} }
export interface folder{ export interface folder{
@@ -542,7 +581,7 @@ export function updateTextTheme(){
} }
} }
export function changeToPreset(id =0){ export function saveCurrentPreset(){
let db = get(DataBase) let db = get(DataBase)
let pres = db.botPresets let pres = db.botPresets
pres[db.botPresetsId] = { pres[db.botPresetsId] = {
@@ -568,6 +607,23 @@ export function changeToPreset(id =0){
bias: db.bias bias: db.bias
} }
db.botPresets = pres db.botPresets = pres
DataBase.set(db)
}
export function copyPreset(id:number){
saveCurrentPreset()
let db = get(DataBase)
let pres = db.botPresets
const newPres = cloneDeep(pres[id])
newPres.name += " Copy"
db.botPresets.push(newPres)
DataBase.set(db)
}
export function changeToPreset(id =0){
saveCurrentPreset()
let db = get(DataBase)
let pres = db.botPresets
const newPres = pres[id] const newPres = pres[id]
db.botPresetsId = id db.botPresetsId = id
db.apiType = newPres.apiType ?? db.apiType db.apiType = newPres.apiType ?? db.apiType

34
src/ts/horde/getModels.ts Normal file
View File

@@ -0,0 +1,34 @@
import { sleep } from "../util"
let modelList:string[]|'loading' = null
//until horde is ready
modelList = []
export async function getHordeModels():Promise<string[]> {
if(modelList === null){
try {
modelList = 'loading'
const models = await fetch("https://stablehorde.net/api/v2/status/models?type=text")
modelList = ((await models.json()).map((a) => {
return a.name
}) as string[])
return modelList
} catch (error) {
modelList = null
return []
}
}
else if(modelList === 'loading'){
while(true){
if(modelList !== 'loading'){
return getHordeModels()
}
await sleep(10)
}
}
else{
return modelList
}
}

View File

@@ -18,6 +18,7 @@ export interface OpenAIChat{
role: 'system'|'user'|'assistant' role: 'system'|'user'|'assistant'
content: string content: string
memo?:string memo?:string
name?:string
} }
export const doingChat = writable(false) export const doingChat = writable(false)
@@ -104,7 +105,7 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
} }
if(!currentChar.utilityBot){ if(!currentChar.utilityBot){
const mainp = currentChar.systemPrompt.length > 3 ? currentChar.systemPrompt : db.mainPrompt const mainp = currentChar.systemPrompt || db.mainPrompt
unformated.main.push({ unformated.main.push({
role: 'system', role: 'system',
@@ -120,14 +121,14 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
unformated.globalNote.push({ unformated.globalNote.push({
role: 'system', role: 'system',
content: replacePlaceholders(db.globalNote, currentChar.name) content: replacePlaceholders(currentChar.replaceGlobalNote || db.globalNote, currentChar.name)
}) })
} }
if(currentChat.note !== ''){ if(currentChat.note !== ''){
unformated.authorNote.push({ unformated.authorNote.push({
role: 'system', role: 'system',
content: replacePlaceholders(currentChar.postHistoryInstructions, currentChat.note) content: replacePlaceholders(currentChat.note, currentChar.name)
}) })
} }
@@ -200,14 +201,17 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
const ms = currentChat.message const ms = currentChat.message
for(const msg of ms){ for(const msg of ms){
let formedChat = processScript(currentChar,replacePlaceholders(msg.data, currentChar.name), 'editprocess') let formedChat = processScript(currentChar,replacePlaceholders(msg.data, currentChar.name), 'editprocess')
if(nowChatroom.type === 'group'){ let name = ''
if(msg.saying && msg.role === 'char'){ if(msg.role === 'char'){
formedChat = `${findCharacterbyIdwithCache(msg.saying).name}: ${formedChat}` if(msg.saying){
name = `${findCharacterbyIdwithCache(msg.saying).name}`
}
else{
name = `${currentChar.name}`
}
} }
else if(msg.role === 'user'){ else if(msg.role === 'user'){
formedChat = `${db.username}: ${formedChat}` name = `${db.username}`
}
} }
if(!msg.chatId){ if(!msg.chatId){
msg.chatId = v4() msg.chatId = v4()
@@ -215,7 +219,8 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
chats.push({ chats.push({
role: msg.role === 'user' ? 'user' : 'assistant', role: msg.role === 'user' ? 'user' : 'assistant',
content: formedChat, content: formedChat,
memo: msg.chatId memo: msg.chatId,
name: name
}) })
currentTokens += (await tokenize(formedChat) + 1) currentTokens += (await tokenize(formedChat) + 1)
} }

View File

@@ -10,7 +10,19 @@ import { downloadFile } from "../globalApi";
export function addLorebook(type:number) { export function addLorebook(type:number) {
let selectedID = get(selectedCharID) let selectedID = get(selectedCharID)
let db = get(DataBase) let db = get(DataBase)
if(type === 0){ if(type === -1){
db.loreBook[db.loreBookPage].data.push({
key: '',
comment: `New Lore ${db.loreBook[db.loreBookPage].data.length + 1}`,
content: '',
mode: 'normal',
insertorder: 100,
alwaysActive: false,
secondkey: "",
selective: false
})
}
else if(type === 0){
db.characters[selectedID].globalLore.push({ db.characters[selectedID].globalLore.push({
key: '', key: '',
comment: `New Lore ${db.characters[selectedID].globalLore.length + 1}`, comment: `New Lore ${db.characters[selectedID].globalLore.length + 1}`,
@@ -53,9 +65,10 @@ export async function loadLoreBookPrompt(){
const db = get(DataBase) const db = get(DataBase)
const char = db.characters[selectedID] const char = db.characters[selectedID]
const page = char.chatPage const page = char.chatPage
const globalLore = char.globalLore const characterLore = char.globalLore
const charLore = char.chats[page].localLore const chatLore = char.chats[page].localLore
const fullLore = globalLore.concat(charLore) const globalLore = db.loreBook[db.loreBookPage].data
const fullLore = characterLore.concat(chatLore.concat(globalLore))
const currentChat = char.chats[page].message const currentChat = char.chats[page].message
const loreDepth = char.loreSettings?.scanDepth ?? db.loreBookDepth const loreDepth = char.loreSettings?.scanDepth ?? db.loreBookDepth
const loreToken = char.loreSettings?.tokenBudget ?? db.loreBookToken const loreToken = char.loreSettings?.tokenBudget ?? db.loreBookToken
@@ -144,11 +157,14 @@ export async function loadLoreBookPrompt(){
} }
export async function importLoreBook(mode:'global'|'local'){ export async function importLoreBook(mode:'global'|'local'|'sglobal'){
const selectedID = get(selectedCharID) const selectedID = get(selectedCharID)
let db = get(DataBase) let db = get(DataBase)
const page = db.characters[selectedID].chatPage const page = db.characters[selectedID].chatPage
let lore = mode === 'global' ? db.characters[selectedID].globalLore : db.characters[selectedID].chats[page].localLore let lore =
mode === 'global' ? db.characters[selectedID].globalLore :
mode === 'sglobal' ? db.loreBook[db.loreBookPage].data :
db.characters[selectedID].chats[page].localLore
const lorebook = (await selectSingleFile(['json'])).data const lorebook = (await selectSingleFile(['json'])).data
if(!lorebook){ if(!lorebook){
return return
@@ -189,6 +205,9 @@ export async function importLoreBook(mode:'global'|'local'){
if(mode === 'global'){ if(mode === 'global'){
db.characters[selectedID].globalLore = lore db.characters[selectedID].globalLore = lore
} }
if(mode === 'sglobal'){
db.loreBook[db.loreBookPage].data = lore
}
else{ else{
db.characters[selectedID].chats[page].localLore = lore db.characters[selectedID].chats[page].localLore = lore
} }
@@ -198,13 +217,15 @@ export async function importLoreBook(mode:'global'|'local'){
} }
} }
export async function exportLoreBook(mode:'global'|'local'){ export async function exportLoreBook(mode:'global'|'local'|'sglobal'){
try { try {
const selectedID = get(selectedCharID) const selectedID = get(selectedCharID)
const db = get(DataBase) const db = get(DataBase)
const page = db.characters[selectedID].chatPage const page = db.characters[selectedID].chatPage
const lore = mode === 'global' ? db.characters[selectedID].globalLore : db.characters[selectedID].chats[page].localLore const lore =
mode === 'global' ? db.characters[selectedID].globalLore :
mode === 'sglobal' ? db.loreBook[db.loreBookPage].data :
db.characters[selectedID].chats[page].localLore
const stringl = Buffer.from(JSON.stringify({ const stringl = Buffer.from(JSON.stringify({
type: 'risu', type: 'risu',
ver: 1, ver: 1,

View File

@@ -3,8 +3,10 @@ import type { OpenAIChat } from ".";
import { DataBase, setDatabase, type character } from "../database"; import { DataBase, setDatabase, type character } from "../database";
import { pluginProcess } from "./plugins"; import { pluginProcess } from "./plugins";
import { language } from "../../lang"; import { language } from "../../lang";
import { stringlizeChat } from "./stringlize"; import { stringlizeChat, unstringlizeChat } from "./stringlize";
import { globalFetch } from "../globalApi"; import { globalFetch, isTauri } from "../globalApi";
import { alertError } from "../alert";
import { sleep } from "../util";
interface requestDataArgument{ interface requestDataArgument{
formated: OpenAIChat[] formated: OpenAIChat[]
@@ -34,7 +36,7 @@ export async function requestChatData(arg:requestDataArgument, model:'model'|'su
return da return da
} }
trys += 1 trys += 1
if(trys > db.requestRetrys){ if(trys > db.requestRetrys || model.startsWith('horde')){
return da return da
} }
} }
@@ -53,6 +55,11 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
switch(aiModel){ switch(aiModel){
case 'gpt35': case 'gpt35':
case 'gpt4':{ case 'gpt4':{
for(let i=0;i<formated.length;i++){
formated[i].name = undefined
}
const body = ({ const body = ({
model: aiModel === 'gpt35' ? 'gpt-3.5-turbo' : 'gpt-4', model: aiModel === 'gpt35' ? 'gpt-3.5-turbo' : 'gpt-4',
messages: formated, messages: formated,
@@ -168,6 +175,52 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
break break
} }
case 'novelai':{
if(!isTauri){
return{
type: 'fail',
result: "NovelAI doesn't work in web version."
}
}
const proompt = stringlizeChat(formated, currentChar?.name ?? '')
const params = {
"input": proompt,
"model":db.novelai.model,
"parameters":{
"use_string":true,
"temperature":1.7,
"max_length":90,
"min_length":1,
"tail_free_sampling":0.6602,
"repetition_penalty":1.0565,
"repetition_penalty_range":340,
"repetition_penalty_frequency":0,
"repetition_penalty_presence":0,
"use_cache":false,
"return_full_text":false,
"prefix":"vanilla",
"order":[3,0]}
}
const da = await globalFetch("https://api.novelai.net/ai/generate", {
body: params,
headers: {
"Authorization": "Bearer " + db.novelai.token
}
})
if((!da.ok )|| (!da.data.output)){
return {
type: 'fail',
result: (language.errors.httpError + `${JSON.stringify(da.data)}`)
}
}
return {
type: "success",
result: unstringlizeChat(da.data.output, formated, currentChar?.name ?? '')
}
}
case "textgen_webui":{ case "textgen_webui":{
let DURL = db.textgenWebUIURL let DURL = db.textgenWebUIURL
let bodyTemplate:any let bodyTemplate:any
@@ -240,15 +293,9 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
try { try {
let result:string = isNewAPI ? dat.results[0].text : dat.data[0].substring(proompt.length) let result:string = isNewAPI ? dat.results[0].text : dat.data[0].substring(proompt.length)
for(const stopStr of stopStrings){
if(result.endsWith(stopStr)){
result.substring(0,result.length - stopStr.length)
}
}
return { return {
type: 'success', type: 'success',
result: result result: unstringlizeChat(result, formated, currentChar?.name ?? '')
} }
} catch (error) { } catch (error) {
return { return {
@@ -367,6 +414,106 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
} }
} }
default:{ default:{
if(aiModel.startsWith("horde:::")){
const realModel = aiModel.split(":::")[1].trim()
const workers = ((await (await fetch("https://stablehorde.net/api/v2/workers")).json()) as {id:string,models:string[]}[]).filter((a) => {
if(a && a.models && a.id){
console.log(a)
return a.models.includes(realModel)
}
return false
}).map((a) => {
return a.id
})
const argument = {
"prompt": "string",
"params": {
"n": 1,
"frmtadsnsp": false,
"frmtrmblln": false,
"frmtrmspch": false,
"frmttriminc": false,
"max_context_length": 200,
"max_length": 20,
"rep_pen": 3,
"rep_pen_range": 0,
"rep_pen_slope": 10,
"singleline": false,
"temperature": db.temperature / 25,
"tfs": 1,
"top_a": 1,
"top_k": 100,
"top_p": 1,
"typical": 1,
"sampler_order": [
0
]
},
"trusted_workers": false,
"slow_workers": true,
"worker_blacklist": false,
"dry_run": false
}
const da = await fetch("https://stablehorde.net/api/v2/generate/text/async", {
body: JSON.stringify(argument),
method: "POST",
headers: {
"content-type": "application/json",
"apikey": db.hordeConfig.apiKey
}
})
if(da.status !== 202){
return {
type: "fail",
result: await da.text()
}
}
const json:{
id:string,
kudos:number,
message:string
} = await da.json()
let warnMessage = ""
if(json.message && json.message.startsWith("Warning:")){
warnMessage = "with " + json.message
}
while(true){
await sleep(1000)
const data = await (await fetch("https://stablehorde.net/api/v2/generate/text/status/" + json.id)).json()
if(!data.is_possible){
fetch("https://stablehorde.net/api/v2/generate/text/status/" + json.id, {
method: "DELETE"
})
return {
type: 'fail',
result: "Response not possible" + warnMessage
}
}
if(data.done){
const generations:{text:string}[] = data.generations
if(generations && generations.length > 0){
return {
type: "success",
result: generations[0].text
}
}
return {
type: 'fail',
result: "No Generations when done"
}
}
}
}
return { return {
type: 'fail', type: 'fail',
result: (language.errors.unknownModel) result: (language.errors.unknownModel)

View File

@@ -8,14 +8,48 @@ export function stringlizeChat(formated:OpenAIChat[], char:string = ''){
let resultString:string[] = [] let resultString:string[] = []
for(const form of formated){ for(const form of formated){
if(form.role === 'system'){ if(form.role === 'system'){
resultString.push("'System Note: " + form.content) resultString.push("system note: " + form.content)
} }
else if(form.role === 'user'){ else if(form.name){
resultString.push("user: " + form.content) resultString.push(form.name + ": " + form.content)
} }
else if(form.role === 'assistant'){ else{
resultString.push("assistant: " + form.content) resultString.push(form.content)
} }
} }
return resultString.join('\n\n') + `\n\n${char}:` return resultString.join('\n\n') + `\n\n${char}:`
} }
export function unstringlizeChat(text:string, formated:OpenAIChat[], char:string = ''){
console.log(text)
let minIndex = -1
let chunks:string[] = ["system note:"]
if(char){
chunks.push(`${char}:`)
}
for(const form of formated){
if(form.name){
const chunk = `${form.name}:`
if(!chunks.includes(chunk)){
chunks.push(chunk)
}
}
}
for(const chunk of chunks){
const ind = text.indexOf(chunk)
if(ind === -1){
continue
}
if(minIndex === -1 || minIndex > ind){
minIndex = ind
}
}
if(minIndex !== -1){
text = text.substring(0, minIndex).trim()
}
return text
}

View File

@@ -1 +1 @@
{"version":"1.15.6"} {"version":"1.16.0"}