[feat] better group chat

This commit is contained in:
kwaroran
2023-05-31 05:08:46 +09:00
parent 7805092992
commit 7ea768cb5b
9 changed files with 208 additions and 57 deletions

View File

@@ -286,5 +286,9 @@ export const languageEnglish = {
setNodePassword: "Set your password to security", setNodePassword: "Set your password to security",
inputNodePassword: "Input your password. if you can't remember, remove save/__password.txt in your server files and restart the server.", inputNodePassword: "Input your password. if you can't remember, remove save/__password.txt in your server files and restart the server.",
simple:"Simple", simple:"Simple",
advanced: "Advanced" advanced: "Advanced",
orderByOrder: "Talk by Order",
removeFromGroup: "Do you really want to remove {{char}} from group?",
talkness: "Talkativeness",
active: "Active"
} }

View File

@@ -261,5 +261,13 @@ export const languageKorean = {
globalRegexScript: "글로별 정규식", globalRegexScript: "글로별 정규식",
accessibility: "접근성", accessibility: "접근성",
sendWithEnter: "엔터키로 메세지 보내기", sendWithEnter: "엔터키로 메세지 보내기",
clickToEdit: "클릭해서 수정하기" clickToEdit: "클릭해서 수정하기",
setNodePassword: "보안을 위해 비밀번호를 정해주세요",
inputNodePassword: "비밀번호를 입력해주세요. 기억이 안나신다면, save/__password를 지우고 서버를 재시작해주세요.",
simple:"간단",
advanced: "고급",
orderByOrder: "순서대로 말하기",
removeFromGroup: "정말로 {{char}}을 그룹에서 삭제시키겠습니까?",
talkness: "대화량",
active: "활성화를"
} }

View File

@@ -3,9 +3,10 @@
export let check = false export let check = false
export let onChange = (check) => {} export let onChange = (check) => {}
export let margin = true
</script> </script>
<label class="mr-2"> <label class:mr-2={margin}>
<input type="checkbox" class="hidden" bind:checked={check} on:change={() => { <input type="checkbox" class="hidden" bind:checked={check} on:change={() => {
onChange(check) onChange(check)
}}> }}>

View File

@@ -3,7 +3,7 @@
import { tokenize } from "../../ts/tokenizer"; import { tokenize } from "../../ts/tokenizer";
import { DataBase, saveImage as saveAsset, type Database, type character, type groupChat } from "../../ts/storage/database"; import { DataBase, saveImage as saveAsset, type Database, type character, type groupChat } from "../../ts/storage/database";
import { selectedCharID } from "../../ts/stores"; import { selectedCharID } from "../../ts/stores";
import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, LoaderIcon, User, DnaIcon, CurlyBracesIcon, Volume2Icon } from 'lucide-svelte' import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, LoaderIcon, User, DnaIcon, CurlyBracesIcon, Volume2Icon, XIcon } from 'lucide-svelte'
import Check from "../Others/Check.svelte"; import Check from "../Others/Check.svelte";
import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters"; import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters";
import LoreBook from "./LoreBookSetting.svelte"; import LoreBook from "./LoreBookSetting.svelte";
@@ -17,6 +17,7 @@
import { exportChar } from "src/ts/characterCards"; import { exportChar } from "src/ts/characterCards";
import { getElevenTTSVoices, getWebSpeechTTSVoices, getVOICEVOXVoices } from "src/ts/process/tts"; import { getElevenTTSVoices, getWebSpeechTTSVoices, getVOICEVOXVoices } from "src/ts/process/tts";
import { checkCharOrder } from "src/ts/storage/globalApi"; import { checkCharOrder } from "src/ts/storage/globalApi";
import { addGroupChar, rmCharFromGroup } from "src/ts/process/group";
let subMenu = 0 let subMenu = 0
let subberMenu = 0 let subberMenu = 0
@@ -58,41 +59,6 @@
} }
async function addGroupChar(){
let group = currentChar.data
if(group.type === 'group'){
const res = await alertSelectChar()
if(res){
if(group.characters.includes(res)){
alertError(language.errors.alreadyCharInGroup)
}
else{
if(await alertConfirm(language.askLoadFirstMsg)){
group.chats[group.chatPage].message.push({
role:'char',
data: findCharacterbyId(res).firstMessage,
saying: res,
})
}
group.characters.push(res)
currentChar.data = group
}
}
}
currentChar = currentChar
}
function rmCharFromGroup(index:number){
let group = currentChar.data
if(group.type === 'group'){
group.characters.splice(index, 1)
currentChar.data = group
}
}
let database:Database let database:Database
let currentChar:{ let currentChar:{
type: 'character', type: 'character',
@@ -182,10 +148,13 @@
{: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}>
<span class="text-neutral-200">{language.character}</span> <span class="text-neutral-200">{language.character}</span>
<div class="p-2 flex gap-2"> <div class="p-4 gap-2 bg-bgcolor rounded-lg char-grid">
{#if currentChar.data.characters.length === 0} {#if currentChar.data.characters.length === 0}
<span class="text-gray-500">No Character</span> <span class="text-gray-500">No Character</span>
{:else} {:else}
<div></div>
<div class="text-center">{language.talkness}</div>
<div class="text-center">{language.active}</div>
{#each currentChar.data.characters as char, i} {#each currentChar.data.characters as char, i}
{#await getCharImage(findCharacterbyId(char).image, 'css')} {#await getCharImage(findCharacterbyId(char).image, 'css')}
<BarIcon onClick={() => { <BarIcon onClick={() => {
@@ -198,6 +167,24 @@
rmCharFromGroup(i) rmCharFromGroup(i)
}} additionalStyle={im} /> }} additionalStyle={im} />
{/await} {/await}
<div class="flex items-center px-2 py-3">
{#each [1,2,3,4,5,6] as barIndex}
<button class="bg-selected h-full flex-1 border-r-bgcolor border-r"
class:bg-green-500={currentChar.data.characterTalks[i] >= (1 / 6 * barIndex)}
class:bg-selected={currentChar.data.characterTalks[i] < (1 / 6 * barIndex)}
class:rounded-l-lg={barIndex === 1}
class:rounded-r-lg={barIndex === 6}
on:click={() => {
if(currentChar.data.type === 'group'){
currentChar.data.characterTalks[i] = (1 / 6 * barIndex)
}
}}
></button>
{/each}
</div>
<div class="flex items-center justify-center">
<Check margin={false} bind:check={currentChar.data.characterActive[i]} />
</div>
{/each} {/each}
{/if} {/if}
</div> </div>
@@ -222,6 +209,13 @@
<span class="text-neutral-200 ml-2">{language.ToggleSuperMemory}</span> <span class="text-neutral-200 ml-2">{language.ToggleSuperMemory}</span>
</div> </div>
{/if} {/if}
{#if currentChar.type === 'group'}
<div class="flex mt-2 items-center">
<Check bind:check={currentChar.data.orderByOrder}/>
<span class="text-neutral-200 ml-2">{language.orderByOrder}</span>
</div>
{/if}
{:else if subMenu === 1} {:else if subMenu === 1}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.characterDisplay}</h2> <h2 class="mb-2 text-2xl font-bold mt-2">{language.characterDisplay}</h2>
<span class="text-neutral-200 mt-2 mb-2">{currentChar.type !== 'group' ? language.charIcon : language.groupIcon}</span> <span class="text-neutral-200 mt-2 mb-2">{currentChar.type !== 'group' ? language.charIcon : language.groupIcon}</span>
@@ -714,4 +708,9 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.char-grid{
display: grid;
grid-template-columns: auto 1fr auto;
}
</style> </style>

View File

@@ -38,7 +38,9 @@ export function createNewGroup(){
emotionImages: [], emotionImages: [],
customscript: [], customscript: [],
chaId: uuidv4(), chaId: uuidv4(),
firstMsgIndex: -1 firstMsgIndex: -1,
characterTalks: [],
characterActive: []
}) })
setDatabase(db) setDatabase(db)
checkCharOrder() checkCharOrder()
@@ -300,6 +302,20 @@ export function characterFormatUpdate(index:number|character){
} }
} }
else{
if((!cha.characterTalks) || cha.characterTalks.length !== cha.characters.length){
cha.characterTalks = []
for(let i=0;i<cha.characters.length;i++){
cha.characterTalks.push(1 / 6 * 4)
}
}
if((!cha.characterActive) || cha.characterActive.length !== cha.characters.length){
cha.characterActive = []
for(let i=0;i<cha.characters.length;i++){
cha.characterActive.push(true)
}
}
}
if(checkNullish(cha.customscript)){ if(checkNullish(cha.customscript)){
cha.customscript = [] cha.customscript = []
} }

103
src/ts/process/group.ts Normal file
View File

@@ -0,0 +1,103 @@
import { shuffle } from "lodash";
import { findCharacterbyId } from "../util";
import { alertConfirm, alertError, alertSelectChar } from "../alert";
import { language } from "src/lang";
import { get } from "svelte/store";
import { DataBase, setDatabase } from "../storage/database";
import { selectedCharID } from "../stores";
export async function addGroupChar(){
let db = get(DataBase)
let selectedId = get(selectedCharID)
let group = db.characters[selectedId]
if(group.type === 'group'){
const res = await alertSelectChar()
if(res){
if(group.characters.includes(res)){
alertError(language.errors.alreadyCharInGroup)
}
else{
if(await alertConfirm(language.askLoadFirstMsg)){
group.chats[group.chatPage].message.push({
role:'char',
data: findCharacterbyId(res).firstMessage,
saying: res,
})
}
group.characters.push(res)
group.characterTalks.push(1 / 6 * 4)
group.characterActive.push(true)
}
}
setDatabase(db)
}
}
export function rmCharFromGroup(index:number){
let db = get(DataBase)
let selectedId = get(selectedCharID)
let group = db.characters[selectedId]
if(group.type === 'group'){
group.characters.splice(index, 1)
group.characterTalks.splice(index, 1)
group.characterActive.splice(index, 1)
setDatabase(db)
}
}
export type GroupOrder = {
id: string,
talkness: number,
index: number
}
export function groupOrder(chars:GroupOrder[], input:string):GroupOrder[] {
let order:GroupOrder[] = [];
if (input) {
const words = getWords(input)
for (const word of words) {
for (let char of chars) {
const charNameChunks = getWords(findCharacterbyId(char.id).name)
console.log(charNameChunks)
if (charNameChunks.includes(word)) {
order.push(char);
break;
}
}
}
}
const shuffled = shuffle(chars)
for (const char of shuffled) {
if(order.includes(char)){
continue
}
//TODO
const chance = 0.5
if (chance >= Math.random()) {
order.push(char);
}
}
while (order.length === 0) {
order.push(chars[Math.floor(Math.random() * chars.length)]);
}
return order;
}
function getWords(data:string){
const matches = data.match(/\b\w+\b/gmi)
let words:string[] = []
for(const match of matches){
words.push(match.toLocaleLowerCase())
}
return words
}

View File

@@ -13,6 +13,8 @@ import { exampleMessage } from "./exampleMessages";
import { sayTTS } from "./tts"; import { sayTTS } from "./tts";
import { supaMemory } from "./supaMemory"; import { supaMemory } from "./supaMemory";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { cloneDeep } from "lodash";
import { groupOrder } from "./group";
export interface OpenAIChat{ export interface OpenAIChat{
role: 'system'|'user'|'assistant' role: 'system'|'user'|'assistant'
@@ -58,8 +60,25 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
if(nowChatroom.type === 'group'){ if(nowChatroom.type === 'group'){
if(chatProcessIndex === -1){ if(chatProcessIndex === -1){
for(let i=0;i<nowChatroom.characters.length;i++){ const messages = nowChatroom.chats[nowChatroom.chatPage].message
const r = await sendChat(i) const lastMessage = messages[messages.length-1]
let order = nowChatroom.characters.map((v,i) => {
return {
id: v,
talkness: nowChatroom.characterActive[i] ? nowChatroom.characterTalks[i] : -1,
index: i
}
})
if(!nowChatroom.orderByOrder){
order = groupOrder(order, lastMessage?.data).filter((v) => {
if(v.id === lastMessage?.saying){
return false
}
return true
})
}
for(let i=0;i<order.length;i++){
const r = await sendChat(order[i].index)
if(!r){ if(!r){
return false return false
} }
@@ -103,6 +122,7 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
'authorNote':([] as OpenAIChat[]), 'authorNote':([] as OpenAIChat[]),
'lastChat':([] as OpenAIChat[]), 'lastChat':([] as OpenAIChat[]),
'description':([] as OpenAIChat[]), 'description':([] as OpenAIChat[]),
'postEverything':([] as OpenAIChat[]),
} }
if(!currentChar.utilityBot){ if(!currentChar.utilityBot){
@@ -149,6 +169,13 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
content: description content: description
}) })
if(nowChatroom.type === 'group'){
const systemMsg = `[Write the next reply only as ${currentChar.name}]`
unformated.postEverything.push({
role: 'system',
content: systemMsg
})
}
} }
unformated.lorebook.push({ unformated.lorebook.push({
@@ -220,15 +247,6 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
currentTokens += (await tokenize(formedChat) + 5) currentTokens += (await tokenize(formedChat) + 5)
} }
if(nowChatroom.type === 'group'){
const systemMsg = `[Write the next reply only as ${currentChar.name}]`
chats.push({
role: 'system',
content: systemMsg
})
currentTokens += (await tokenize(systemMsg) + 5)
}
if(nowChatroom.supaMemory && db.supaMemoryType !== 'none'){ if(nowChatroom.supaMemory && db.supaMemoryType !== 'none'){
const sp = await supaMemory(chats, currentTokens, maxContextTokens, currentChat, nowChatroom) const sp = await supaMemory(chats, currentTokens, maxContextTokens, currentChat, nowChatroom)
if(sp.error){ if(sp.error){
@@ -252,7 +270,6 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
chats.splice(0, 1) chats.splice(0, 1)
} }
currentChat.lastMemory = chats[0].memo currentChat.lastMemory = chats[0].memo
console.log(currentChat.lastMemory)
} }
let bias:{[key:number]:number} = {} let bias:{[key:number]:number} = {}
@@ -283,7 +300,8 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
//make into one //make into one
let formated:OpenAIChat[] = [] let formated:OpenAIChat[] = []
const formatOrder = db.formatingOrder const formatOrder = cloneDeep(db.formatingOrder)
formatOrder.push('postEverything')
let sysPrompts:string[] = [] let sysPrompts:string[] = []
for(let i=0;i<formatOrder.length;i++){ for(let i=0;i<formatOrder.length;i++){
const cha = unformated[formatOrder[i]] const cha = unformated[formatOrder[i]]
@@ -443,7 +461,6 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
}, },
] ]
console.log('requesting chat')
const rq = await requestChatData({ const rq = await requestChatData({
formated: promptbody, formated: promptbody,
bias: emobias, bias: emobias,

View File

@@ -61,7 +61,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
case 'gpt4_32k':{ case 'gpt4_32k':{
for(let i=0;i<formated.length;i++){ for(let i=0;i<formated.length;i++){
if(arg.isGroupChat){ if(arg.isGroupChat && formated[i].name){
formated[i].content = formated[i].name + ": " + formated[i].content formated[i].content = formated[i].name + ": " + formated[i].content
} }
formated[i].name = undefined formated[i].name = undefined

View File

@@ -351,6 +351,8 @@ export interface groupChat{
name:string name:string
viewScreen: 'single'|'multiple'|'none'|'emp', viewScreen: 'single'|'multiple'|'none'|'emp',
characters:string[] characters:string[]
characterTalks:number[]
characterActive:boolean[]
globalLore: loreBook[] globalLore: loreBook[]
autoMode: boolean autoMode: boolean
useCharacterLore :boolean useCharacterLore :boolean
@@ -364,6 +366,7 @@ export interface groupChat{
loreSettings?:loreSettings loreSettings?:loreSettings
supaMemory?:boolean supaMemory?:boolean
ttsMode?:string ttsMode?:string
orderByOrder?:boolean
} }
export interface botPreset{ export interface botPreset{
@@ -517,7 +520,7 @@ interface sdConfig{
hr_upscaler:string hr_upscaler:string
} }
export type FormatingOrderItem = 'main'|'jailbreak'|'chats'|'lorebook'|'globalNote'|'authorNote'|'lastChat'|'description' export type FormatingOrderItem = 'main'|'jailbreak'|'chats'|'lorebook'|'globalNote'|'authorNote'|'lastChat'|'description'|'postEverything'
export interface Chat{ export interface Chat{
message: Message[] message: Message[]