diff --git a/plugins.md b/plugins.md index f29edbc6..92832dab 100644 --- a/plugins.md +++ b/plugins.md @@ -9,7 +9,7 @@ A plugin is a js file with a header. for example: ```js //@name exampleplugin -//display-name: Example Plugin +//@display-name Example Plugin // Plugin code here ``` @@ -118,7 +118,7 @@ Adds a risu script handler to the plugin. - `type: string` - The handler type. one of `display`, `output`, `input`, `process` - `display` - The handler will be called when the data is displayed. - `output` - The handler will be called when the data is outputted by the AI model. - - `input` - The handler will be called when the data is inputted to the user. + - `input` - The handler will be called when the data is inputted by the user. - `process` - The handler will be called when creating actual request data. - `func: (content:string) => string|null|undefined|Promise` - The handler function. - `content: string` - The content to handle. @@ -163,4 +163,4 @@ The plugin system has been updated to V2. The following changes have been made: - `addCharaJs` function has been removed. use `addRisuScriptHandler` instead. - `risuLog` function has been removed. use `console.log` instead. - Many security restrictions have been removed. - - `@risu-name`, `@risu-display-name`, `@risu-arg` headers has been removed. use `@name`, `@display-name`, `@arg` instead. if it's not present, it will be ran as V1 plugin. \ No newline at end of file + - `@risu-name`, `@risu-display-name`, `@risu-arg` headers has been removed. use `@name`, `@display-name`, `@arg` instead. if it's not present, it will be ran as V1 plugin. diff --git a/src/lang/en.ts b/src/lang/en.ts index 2145bb5d..aa4e2a9a 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -490,6 +490,7 @@ export const languageEnglish = { cancel: "Cancel", renameFolder: "Rename Folder", changeFolderColor: "Change Folder Color", + changeFolderImage: "Change Folder Image", fullWordMatching: "Full Word Matching", botSettingAtStart: "Bot Menu when Launch", triggerStart: "On chat Send", diff --git a/src/lang/ko.ts b/src/lang/ko.ts index 943ea439..9d3cfa0b 100644 --- a/src/lang/ko.ts +++ b/src/lang/ko.ts @@ -441,6 +441,7 @@ export const languageKorean = { "cancel": "취소", "renameFolder": "폴더 이름 변경하기", "changeFolderColor": "폴더 색상 변경하기", + "changeFolderImage": "폴더 이미지 변경하기", "fullWordMatching": "단어 단위 매칭", "botSettingAtStart": "실행 시 봇 설정으로 시작하기", "triggerStart": "채팅 보낼 시", diff --git a/src/lib/SideBars/Sidebar.svelte b/src/lib/SideBars/Sidebar.svelte index 9d7dcbe6..20f4b9cf 100644 --- a/src/lib/SideBars/Sidebar.svelte +++ b/src/lib/SideBars/Sidebar.svelte @@ -37,9 +37,9 @@ import SidebarAvatar from "./SidebarAvatar.svelte"; import BaseRoundedButton from "../UI/BaseRoundedButton.svelte"; import { get } from "svelte/store"; - import { getCharacterIndexObject } from "src/ts/util"; + import { getCharacterIndexObject, selectSingleFile } from "src/ts/util"; import { v4 } from "uuid"; - import { checkCharOrder } from "src/ts/globalApi.svelte"; + import { checkCharOrder, getFileSrc, saveAsset } from "src/ts/globalApi.svelte"; import { alertInput, alertSelect } from "src/ts/alert"; import SideChatList from "./SideChatList.svelte"; import { ConnectionIsHost, ConnectionOpenStore, RoomIdStore } from "src/ts/sync/multiuser"; @@ -59,7 +59,7 @@ } type sortTypeNormal = { type:'normal',img: string, index: number, name:string } - type sortType = sortTypeNormal|{type:'folder',folder:sortTypeNormal[],id:string, name:string, color:string} + type sortType = sortTypeNormal|{type:'folder',folder:sortTypeNormal[],id:string, name:string, color:string, img?:string} let charImages: sortType[] = $state([]); let IconRounded = $state(false) let openFolders:string[] = $state([]) @@ -109,7 +109,8 @@ type: "folder", id: folder.id, name: folder.name, - color: folder.color + color: folder.color, + img: folder.img, }); } } @@ -472,10 +473,10 @@ {:else if char.type === "folder"} {#key char.color} {#key char.name} - { e.preventDefault() - const sel = parseInt(await alertSelect([language.renameFolder,language.changeFolderColor,language.cancel])) + const sel = parseInt(await alertSelect([language.renameFolder,language.changeFolderColor,language.changeFolderImage,language.cancel])) if(sel === 0){ const v = await alertInput(language.changeFolderName) const db = DBState.db @@ -501,6 +502,40 @@ db.characterOrder[ind] = oder setDatabase(db) } + else if(sel === 2) { + const sel = parseInt(await alertSelect(['Reset to Default Image', 'Select Image File'])) + const db = DBState.db + const oder = db.characterOrder[ind] + if(typeof(oder) === 'string'){ + return + } + + switch (sel) { + case 0: + oder.imgFile = null + oder.img = '' + break; + + case 1: + const folderImage = await selectSingleFile([ + 'png', + 'jpg', + 'webp', + ]) + + if(!folderImage) { + return + } + + const folderImageData = await saveAsset(folderImage.data) + + oder.imgFile = folderImageData + oder.img = await getFileSrc(folderImageData) + db.characterOrder[ind] = oder + setDatabase(db) + break; + } + } }} onClick={() => { if(char.type !== 'folder'){ diff --git a/src/lib/SideBars/SidebarAvatar.svelte b/src/lib/SideBars/SidebarAvatar.svelte index 3be150d1..48602d8a 100644 --- a/src/lib/SideBars/SidebarAvatar.svelte +++ b/src/lib/SideBars/SidebarAvatar.svelte @@ -9,6 +9,7 @@ onClick?: any; bordered?: boolean; color?: string; + backgroundimg?: string; children?: import('svelte').Snippet; oncontextmenu?: (event: MouseEvent & { currentTarget: EventTarget & HTMLDivElement; @@ -23,6 +24,7 @@ onClick = () => {}, bordered = false, color = '', + backgroundimg = '', children, oncontextmenu }: Props = $props(); @@ -55,8 +57,15 @@ style:width={size + "px"} style:height={size + "px"} style:minWidth={size + "px"} + style:background-image={backgroundimg ? `url('${backgroundimg}')` : undefined} + style:background-size={backgroundimg ? "cover" : undefined} + style:background-position={backgroundimg ? "center" : undefined} class:rounded-md={!rounded} class:rounded-full={rounded} - >{@render children?.()} + > + {#if !backgroundimg} + {@render children?.()} + {/if} + {:else} {#await src}
{ + if (mainChunk.chatMemos && Array.isArray(mainChunk.chatMemos)) { + mainChunk.chatMemos = new Set(mainChunk.chatMemos); + } + }); // Clean invalid HypaV2 data cleanInvalidChunks(chats, data); let allocatedTokens = db.hypaAllocatedTokens; let chunkSize = db.hypaChunkSize; - currentTokens += allocatedTokens + chats.length * 4; // ChatML token counting from official openai documentation + currentTokens += allocatedTokens; // WARNING: VIRTUAL VALUE. This token is NOT real. This is a placeholder appended to calculate the maximum amount of HypaV2 memory retrieved data. let mainPrompt = ""; const lastTwoChats = chats.slice(-2); - // Error handling for infinite summarization attempts + // Error handling for failed summarization let summarizationFailures = 0; const maxSummarizationFailures = 3; // Find the index to start summarizing from - let idx = 2; // first two should not be considered + let idx = 2; // first two should not be considered([Start a new chat], Memory prompt) if (data.mainChunks.length > 0) { const lastMainChunk = data.mainChunks[data.mainChunks.length - 1]; const lastChatMemo = lastMainChunk.lastChatMemo; @@ -345,14 +351,14 @@ export async function hypaMemoryV2( // Starting chat index of new mainChunk to be generated // Token management loop (If current token usage exceeds allowed amount) - while (currentTokens >= maxContextTokens) { + while (currentTokens > maxContextTokens) { const halfData: OpenAIChat[] = []; let halfDataTokens = 0; const startIdx = idx; console.log( - "Starting summarization iteration:", + "[HypaV2] Starting summarization iteration:", "\nCurrent Tokens (before):", currentTokens, "\nMax Context Tokens:", maxContextTokens, "\nStartIdx:", startIdx, @@ -362,13 +368,13 @@ export async function hypaMemoryV2( // Accumulate chats to summarize while ( halfDataTokens < chunkSize && - idx < chats.length - 2 // keep the last two chats from summarizing(else, the roles will be fucked up) + (idx < chats.length - 4) // keep the last two chats from summarizing(else, the roles will be fucked up) ) { const chat = chats[idx]; const chatTokens = await tokenizer.tokenizeChat(chat); console.log( - "Evaluating chat for summarization:", + "[HypaV2] Evaluating chat for summarization:", "\nIndex:", idx, "\nRole:", chat.role, "\nContent:", chat.content, @@ -392,7 +398,7 @@ export async function hypaMemoryV2( const endIdx = idx - 1; console.log( - "Summarization batch chosen with this:", + "[HypaV2] Summarization batch chosen with this:", "\nStartIdx:", startIdx, "\nEndIdx:", endIdx, "\nNumber of chats in halfData:", halfData.length, @@ -402,7 +408,7 @@ export async function hypaMemoryV2( // If no chats were added, break to avoid infinite loop if (halfData.length === 0) { - console.log("No chats to summarize in this iteration, breaking out."); + console.log("HOW DID WE GET HERE???"); break; } @@ -436,27 +442,11 @@ export async function hypaMemoryV2( }); console.log( - "Summarization success:", + "[HypaV2] Summarization success:", "\nSummary Data:", summaryData.data, "\nSummary Token Count:", summaryDataToken ); - // **Token accounting fix:** - // Previous commits, the code likely have missed removing summarized chat's tokens. - // and never actually accounted for adding the summary tokens. - // Now we: - // 1. Remove old chats' tokens (they are replaced by summary) - // 2. Add summary tokens instead - currentTokens -= halfDataTokens; // remove original chats' tokens - currentTokens += summaryDataToken; // add the summary's tokens - - console.log( - "After token adjustment:", - "\nRemoved halfDataTokens:", halfDataTokens, - "\nAdded summaryDataToken:", summaryDataToken, - "\nCurrent Tokens (after):", currentTokens - ); - // Update lastMainChunkID and create a new mainChunk data.lastMainChunkID++; const newMainChunkId = data.lastMainChunkID; @@ -485,11 +475,14 @@ export async function hypaMemoryV2( ); console.log( - "Chunks added:", + "[HypaV2] Chunks added:", splitted, "\nUpdated mainChunks count:", data.mainChunks.length, "\nUpdated chunks count:", data.chunks.length ); + + currentTokens -= halfDataTokens; + console.log("[HypaV2] tokens after summarization deduction:", currentTokens); } // Construct the mainPrompt from mainChunks @@ -557,28 +550,33 @@ export async function hypaMemoryV2( } const fullResult = `${mainPrompt}\n${chunkResultPrompts}`; - - // Filter out summarized chats - const unsummarizedChats = chats.slice(idx); - - // Insert the memory system prompt at the beginning - unsummarizedChats.unshift({ + const fullResultTokens = await tokenizer.tokenizeChat({ role: "system", content: fullResult, - memo: "supaMemory", }); + currentTokens += fullResultTokens; + // Filter out summarized chats and prepend the memory prompt + const unsummarizedChats: OpenAIChat[] = [ + { + role: "system", + content: fullResult, + memo: "supaMemory", + }, + ...chats.slice(idx) // Use the idx to slice out the summarized chats + ]; + + // Remove this later, as this is already done by the index for (const chat of lastTwoChats) { if (!unsummarizedChats.find((c) => c.memo === chat.memo)) { unsummarizedChats.push(chat); } } - // Recalculate currentTokens - currentTokens = await tokenizer.tokenizeChats(unsummarizedChats); + currentTokens -= allocatedTokens; // Virtually added memory tokens got removed. Bad way, but had no choice. console.log( - "Model being used: ", + "[HypaV2] Model being used: ", db.hypaModel, db.supaModelType, "\nCurrent session tokens: ", diff --git a/src/ts/process/tts.ts b/src/ts/process/tts.ts index df5c4097..25aefa07 100644 --- a/src/ts/process/tts.ts +++ b/src/ts/process/tts.ts @@ -309,6 +309,7 @@ export async function sayTTS(character:character,text:string) { const text = Buffer.from(textBuffer).toString('utf-8') throw new Error(text); } + break; } case 'fishspeech':{ if (character.fishSpeechConfig.model._id === ''){ diff --git a/src/ts/storage/database.svelte.ts b/src/ts/storage/database.svelte.ts index b717fa1f..978c0100 100644 --- a/src/ts/storage/database.svelte.ts +++ b/src/ts/storage/database.svelte.ts @@ -1205,6 +1205,8 @@ export interface folder{ data:string[] color:string id:string + imgFile?:string + img?:string }