diff --git a/src/lib/Others/AlertComp.svelte b/src/lib/Others/AlertComp.svelte index 1a8580d8..85a248be 100644 --- a/src/lib/Others/AlertComp.svelte +++ b/src/lib/Others/AlertComp.svelte @@ -22,9 +22,9 @@ import { ColorSchemeTypeStore } from "src/ts/gui/colorscheme"; import Help from "./Help.svelte"; import { getChatBranches } from "src/ts/gui/branches"; - import { getCurrentCharacter } from "src/ts/storage/database.svelte"; - import { message } from "@tauri-apps/plugin-dialog"; - import HypaV3Modal from './HypaV3Modal.svelte'; + import { getCurrentCharacter } from "src/ts/storage/database.svelte"; + import { message } from "@tauri-apps/plugin-dialog"; + import HypaV3Modal from './HypaV3Modal.svelte'; let btn let input = $state('') let cardExportType = $state('realm') diff --git a/src/lib/Others/HypaV3Modal.svelte b/src/lib/Others/HypaV3Modal.svelte index e6c6b34e..3ac09afd 100644 --- a/src/lib/Others/HypaV3Modal.svelte +++ b/src/lib/Others/HypaV3Modal.svelte @@ -1,61 +1,384 @@ -
+
+
-

HypaV3 Data

+

HypaV3 Data

+
-
- {#each DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].hypaV3Data.summaries as summary, i} -
- -
+ + +
+ {#if hypaV3DataState.summaries.length === 0} + No summaries yet + {/if} + + {#each hypaV3DataState.summaries as summary, i} + {#if summaryUIStates[i]} +
+
Summary #{i + 1}
+ + + - + +
- - -
- -
- - Connected Messages ({summary.chatMemos.length}) - -
- + + + + + {#if summaryUIStates[i].translation} +
+ Translation +
+ {summaryUIStates[i].translation} +
+
+ {/if} + + + {#if summaryUIStates[i].rerolledText} +
+
+ Rerolled Summary +
+ + + + + + + + +
+
+ + + + {#if summaryUIStates[i].rerolledTranslation} +
+ Rerolled Translation +
+ {summaryUIStates[i].rerolledTranslation} +
+
+ {/if} +
+ {/if} + + +
+
+ + Connected Messages ({summary.chatMemos.length}) + + + +
+ +
{#each summary.chatMemos as chatMemo} {/each}
- - {#if hypaV3ExpandedChatMemo.summaryChatMemos === summary.chatMemos && hypaV3ExpandedChatMemo.summaryChatMemo !== ""} -
-
- {(() => { - const char = DBState.db.characters[$selectedCharID]; - const chat = - char.chats[ - DBState.db.characters[$selectedCharID].chatPage - ]; - const firstMessage = - chat.fmIndex === -1 - ? char.firstMessage - : char.alternateGreetings?.[chat.fmIndex ?? 0]; - const targetMessage = - hypaV3ExpandedChatMemo.summaryChatMemo == null - ? { role: "char", data: firstMessage } - : chat.message.find( - (m) => - m.chatId === - hypaV3ExpandedChatMemo.summaryChatMemo - ); + + {#if expandedMessageUIState?.summaryIndex === i} + {@const message = getMessageFromChatMemo( + expandedMessageUIState.selectedChatMemo + )} +
+ {#if message} + +
+ {message.role}'s Message +
+ +
+ {message.data} +
+ {:else} +
Message not found
+ {/if} - if (targetMessage) { - const displayRole = - targetMessage.role === "char" - ? char.name - : targetMessage.role; - return `${displayRole}:\n${targetMessage.data}`; - } - - return "Message not found"; - })()} -
+ + {#if expandedMessageUIState.translation} +
+ Translation +
+ {expandedMessageUIState.translation} +
+
+ {/if}
{/if}
-
+ {/if} {/each} - {#if DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].hypaV3Data.summaries.length === 0} - No summaries yet + + {#if true} + + {@const nextMessage = getNextMessageToSummarize()} + {#if nextMessage} +
+ + HypaV3 will summarize [{nextMessage.chatId}] + +
+ {nextMessage.data} +
+
+ {/if} {/if}
diff --git a/src/lib/Setting/Pages/OtherBotSettings.svelte b/src/lib/Setting/Pages/OtherBotSettings.svelte index 086e234a..8259f29b 100644 --- a/src/lib/Setting/Pages/OtherBotSettings.svelte +++ b/src/lib/Setting/Pages/OtherBotSettings.svelte @@ -15,8 +15,9 @@ import { getCharImage } from "src/ts/characters"; import Arcodion from "src/lib/UI/Arcodion.svelte"; import CheckInput from "src/lib/UI/GUI/CheckInput.svelte"; - import TextAreaInput from "src/lib/UI/GUI/TextAreaInput.svelte"; - import { hypaMemoryV3 } from "src/ts/process/memory/hypav3"; + import TextAreaInput from "src/lib/UI/GUI/TextAreaInput.svelte"; + import { untrack } from "svelte"; + $effect.pre(() => { DBState.db.NAIImgConfig ??= { width: 512, @@ -40,6 +41,32 @@ }); let submenu = $state(DBState.db.useLegacyGUI ? -1 : 0) + + // HypaV3 + $effect(() => { + const newValue = Math.min(DBState.db.hypaV3Settings.recentMemoryRatio, 1); + + untrack(() => { + DBState.db.hypaV3Settings.recentMemoryRatio = newValue; + + if (newValue + DBState.db.hypaV3Settings.similarMemoryRatio > 1) { + DBState.db.hypaV3Settings.similarMemoryRatio = 1 - newValue; + } + }) + }) + + $effect(() => { + const newValue = Math.min(DBState.db.hypaV3Settings.similarMemoryRatio, 1); + + untrack(() => { + DBState.db.hypaV3Settings.similarMemoryRatio = newValue; + + if (newValue + DBState.db.hypaV3Settings.recentMemoryRatio > 1) { + DBState.db.hypaV3Settings.recentMemoryRatio = 1 - newValue; + } + }) + }); + // End HypaV3

{language.otherBots}

@@ -467,31 +494,31 @@ OpenAI 3.5 Turbo Instruct {language.submodel} - {#if DBState.db.supaModelType === 'instruct35'} + {#if DBState.db.supaModelType === "instruct35"} OpenAI API Key - + {/if} {language.summarizationPrompt}
- +
Memory Tokens Ratio - + Extra Summarization Ratio - + Max Chats Per Summary - + Recent Memory Ratio - + Similar Memory Ratio - + Random Memory Ratio - +
- +
- +
{:else if (DBState.db.supaModelType !== 'none' && DBState.db.hypav2 === false && DBState.db.hypaV3 === false)} {language.supaDesc} diff --git a/src/lib/SideBars/CharConfig.svelte b/src/lib/SideBars/CharConfig.svelte index fccb78db..93a4c98a 100644 --- a/src/lib/SideBars/CharConfig.svelte +++ b/src/lib/SideBars/CharConfig.svelte @@ -1113,6 +1113,7 @@ onclick={() => { DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].hypaV3Data ??= { summaries: [], + lastSelectedSummaries: [], } showHypaV3Alert() }} diff --git a/src/ts/process/memory/hypav3.ts b/src/ts/process/memory/hypav3.ts index 4786c1bf..91bef8d6 100644 --- a/src/ts/process/memory/hypav3.ts +++ b/src/ts/process/memory/hypav3.ts @@ -24,6 +24,7 @@ interface Summary { interface HypaV3Data { summaries: Summary[]; + lastSelectedSummaries?: number[]; } export interface SerializableHypaV3Data { @@ -32,6 +33,7 @@ export interface SerializableHypaV3Data { chatMemos: string[]; isImportant: boolean; }[]; + lastSelectedSummaries?: number[]; } interface SummaryChunk { @@ -39,6 +41,10 @@ interface SummaryChunk { summary: Summary; } +const minChatsForSimilarity = 3; +const maxSummarizationFailures = 3; +const summarySeparator = "\n\n"; + // Helper function to check if one Set is a subset of another function isSubset(subset: Set, superset: Set): boolean { for (const elem of subset) { @@ -51,6 +57,7 @@ function isSubset(subset: Set, superset: Set): boolean { function toSerializableHypaV3Data(data: HypaV3Data): SerializableHypaV3Data { return { + ...data, summaries: data.summaries.map((summary) => ({ ...summary, chatMemos: [...summary.chatMemos], @@ -60,6 +67,7 @@ function toSerializableHypaV3Data(data: HypaV3Data): SerializableHypaV3Data { function toHypaV3Data(serialData: SerializableHypaV3Data): HypaV3Data { return { + ...serialData, summaries: serialData.summaries.map((summary) => ({ ...summary, // Convert null back to undefined (JSON serialization converts undefined to null) @@ -87,14 +95,17 @@ function cleanOrphanedSummary(chats: OpenAIChat[], data: HypaV3Data): void { const removedCount = originalLength - data.summaries.length; if (removedCount > 0) { - console.log(`[HypaV3] Cleaned ${removedCount} orphaned summaries`); + console.log(`[HypaV3] Cleaned ${removedCount} orphaned summaries.`); } } export async function summarize( - stringifiedChats: string + oaiChats: OpenAIChat[] ): Promise<{ success: boolean; data: string }> { const db = getDatabase(); + const stringifiedChats = oaiChats + .map((chat) => `${chat.role}: ${chat.content}`) + .join("\n"); if (db.supaModelType === "distilbart") { try { @@ -103,7 +114,7 @@ export async function summarize( } catch (error) { return { success: false, - data: "[HypaV3] " + error, + data: error, }; } } @@ -116,7 +127,7 @@ export async function summarize( switch (db.supaModelType) { case "instruct35": { console.log( - "[HypaV3] Using openAI gpt-3.5-turbo-instruct for summarization" + "[HypaV3] Using openAI gpt-3.5-turbo-instruct for summarization." ); const requestPrompt = `${stringifiedChats}\n\n${summarizePrompt}\n\nOutput:`; @@ -165,7 +176,7 @@ export async function summarize( } case "subModel": { - console.log(`[HypaV3] Using ax model ${db.subModel} for summarization`); + console.log(`[HypaV3] Using ax model ${db.subModel} for summarization.`); const requestMessages: OpenAIChat[] = parseChatML( summarizePrompt.replaceAll("{{slot}}", stringifiedChats) @@ -190,14 +201,17 @@ export async function summarize( "memory" ); - if ( - response.type === "fail" || - response.type === "streaming" || - response.type === "multiline" - ) { + if (response.type === "streaming" || response.type === "multiline") { return { success: false, - data: "Unexpected response type", + data: "unexpected response type", + }; + } + + if (response.type === "fail") { + return { + success: false, + data: response.result, }; } @@ -207,12 +221,43 @@ export async function summarize( default: { return { success: false, - data: `Unsupported model ${db.supaModelType} for summarization`, + data: `unsupported model ${db.supaModelType} for summarization`, }; } } } +async function retryableSummarize( + oaiChats: OpenAIChat[] +): Promise<{ success: boolean; data: string }> { + let summarizationFailures = 0; + + while (summarizationFailures < maxSummarizationFailures) { + console.log( + "[HypaV3] Attempting summarization:", + "\nAttempt:", + summarizationFailures + 1, + "\nTarget:", + oaiChats + ); + + const summarizeResult = await summarize(oaiChats); + + if (!summarizeResult.success) { + console.log("[HypaV3] Summarization failed:", summarizeResult.data); + summarizationFailures++; + + if (summarizationFailures >= maxSummarizationFailures) { + return summarizeResult; + } + + continue; + } + + return summarizeResult; + } +} + export async function hypaMemoryV3( chats: OpenAIChat[], currentTokens: number, @@ -226,9 +271,6 @@ export async function hypaMemoryV3( error?: string; memory?: SerializableHypaV3Data; }> { - const minChatsForSimilarity = 3; - const maxSummarizationFailures = 3; - const summarySeparator = "\n\n"; const db = getDatabase(); // Validate settings @@ -250,6 +292,7 @@ export async function hypaMemoryV3( // Load existing hypa data if available let data: HypaV3Data = { summaries: [], + lastSelectedSummaries: [], }; if (room.hypaV3Data) { @@ -360,8 +403,8 @@ export async function hypaMemoryV3( i, "\nRole:", chat.role, - "\nContent:\n", - chat.content, + "\nContent:", + "\n" + chat.content, "\nTokens:", chatTokens ); @@ -387,53 +430,29 @@ export async function hypaMemoryV3( currentTokens - toSummarizeTokens < targetTokens ) { console.log( - `[HypaV3] Stopping summarization: would reduce below target tokens (${currentTokens} - ${toSummarizeTokens} < ${targetTokens})` + `[HypaV3] Stopping summarization: currentTokens(${currentTokens}) - toSummarizeTokens(${toSummarizeTokens}) < targetTokens(${targetTokens})` ); break; } // Attempt summarization - let summarizationFailures = 0; - const stringifiedChats = toSummarize - .map((chat) => `${chat.role}: ${chat.content}`) - .join("\n"); + const summarizeResult = await retryableSummarize(toSummarize); - while (summarizationFailures < maxSummarizationFailures) { - console.log( - "[HypaV3] Attempting summarization:", - "\nAttempt:", - summarizationFailures + 1, - "\nTarget:", - toSummarize - ); - - const summarizeResult = await summarize(stringifiedChats); - - if (!summarizeResult.success) { - console.log("[HypaV3] Summarization failed:", summarizeResult.data); - summarizationFailures++; - - if (summarizationFailures >= maxSummarizationFailures) { - return { - currentTokens, - chats, - error: "[HypaV3] Summarization failed after maximum retries", - memory: toSerializableHypaV3Data(data), - }; - } - - continue; - } - - data.summaries.push({ - text: summarizeResult.data, - chatMemos: new Set(toSummarize.map((chat) => chat.memo)), - isImportant: false, - }); - - break; + if (!summarizeResult.success) { + return { + currentTokens, + chats, + error: `[HypaV3] Summarization failed after maximum retries: ${summarizeResult.data}`, + memory: toSerializableHypaV3Data(data), + }; } + data.summaries.push({ + text: summarizeResult.data, + chatMemos: new Set(toSummarize.map((chat) => chat.memo)), + isImportant: false, + }); + currentTokens -= toSummarizeTokens; startIdx = endIdx; } @@ -529,9 +548,9 @@ export async function hypaMemoryV3( selectedRecentSummaries.length, "\nSummaries:", selectedRecentSummaries, - "\nReserved Recent Memory Tokens:", + "\nReserved Tokens:", reservedRecentMemoryTokens, - "\nConsumed Recent Memory Tokens:", + "\nConsumed Tokens:", consumedRecentMemoryTokens ); } @@ -608,56 +627,33 @@ export async function hypaMemoryV3( // (2) Summarized recent chat search if (db.hypaV3Settings.enableSimilarityCorrection) { - let summarizationFailures = 0; + // Attempt summarization const recentChats = chats.slice(-minChatsForSimilarity); - const stringifiedRecentChats = recentChats - .map((chat) => `${chat.role}: ${chat.content}`) - .join("\n"); + const summarizeResult = await retryableSummarize(recentChats); - while (summarizationFailures < maxSummarizationFailures) { - console.log( - "[HypaV3] Attempting summarization:", - "\nAttempt:", - summarizationFailures + 1, - "\nTarget:", - recentChats - ); - - const summarizeResult = await summarize(stringifiedRecentChats); - - if (!summarizeResult.success) { - console.log("[HypaV3] Summarization failed:", summarizeResult.data); - summarizationFailures++; - - if (summarizationFailures >= maxSummarizationFailures) { - return { - currentTokens, - chats, - error: "[HypaV3] Summarization failed after maximum retries", - memory: toSerializableHypaV3Data(data), - }; - } - - continue; - } - - const searched = await processor.similaritySearchScoredEx( - summarizeResult.data - ); - - for (const [chunk, similarity] of searched) { - const summary = chunk.summary; - - scoredSummaries.set( - summary, - (scoredSummaries.get(summary) || 0) + similarity - ); - } - - console.log("[HypaV3] Similarity corrected"); - - break; + if (!summarizeResult.success) { + return { + currentTokens, + chats, + error: `[HypaV3] Summarization failed after maximum retries: ${summarizeResult.data}`, + memory: toSerializableHypaV3Data(data), + }; } + + const searched = await processor.similaritySearchScoredEx( + summarizeResult.data + ); + + for (const [chunk, similarity] of searched) { + const summary = chunk.summary; + + scoredSummaries.set( + summary, + (scoredSummaries.get(summary) || 0) + similarity + ); + } + + console.log("[HypaV3] Similarity corrected."); } // Sort in descending order @@ -677,6 +673,8 @@ export async function hypaMemoryV3( "[HypaV3] Trying to add similar summary:", "\nSummary Tokens:", summaryTokens, + "\nConsumed Similar Memory Tokens:", + consumedSimilarMemoryTokens, "\nReserved Tokens:", reservedSimilarMemoryTokens, "\nWould exceed:", @@ -689,7 +687,7 @@ export async function hypaMemoryV3( reservedSimilarMemoryTokens ) { console.log( - `[HypaV3] Stopping similar memory selection: would exceed reserved tokens (${consumedSimilarMemoryTokens} + ${summaryTokens} > ${reservedSimilarMemoryTokens})` + `[HypaV3] Stopping similar memory selection: consumedSimilarMemoryTokens(${consumedSimilarMemoryTokens}) + summaryTokens(${summaryTokens}) > reservedSimilarMemoryTokens(${reservedSimilarMemoryTokens})` ); break; } @@ -706,9 +704,9 @@ export async function hypaMemoryV3( selectedSimilarSummaries.length, "\nSummaries:", selectedSimilarSummaries, - "\nReserved Similar Memory Tokens:", + "\nReserved Tokens:", reservedSimilarMemoryTokens, - "\nConsumed Similar Memory Tokens:", + "\nConsumed Tokens:", consumedSimilarMemoryTokens ); } @@ -770,9 +768,9 @@ export async function hypaMemoryV3( selectedRandomSummaries.length, "\nSummaries:", selectedRandomSummaries, - "\nReserved Random Memory Tokens:", + "\nReserved Tokens:", reservedRandomMemoryTokens, - "\nConsumed Random Memory Tokens:", + "\nConsumed Tokens:", consumedRandomMemoryTokens ); } @@ -814,10 +812,15 @@ export async function hypaMemoryV3( if (currentTokens > maxContextTokens) { throw new Error( - `[HypaV3] Unexpected input token count:\nCurrent Tokens:${currentTokens}\nMax Context Tokens:${maxContextTokens}` + `[HypaV3] Unexpected error: input token count (${currentTokens}) exceeds max context size (${maxContextTokens})` ); } + // Save last selected summaries + data.lastSelectedSummaries = selectedSummaries.map((selectedSummary) => + data.summaries.findIndex((summary) => summary === selectedSummary) + ); + const newChats: OpenAIChat[] = [ { role: "system", @@ -854,7 +857,7 @@ class HypaProcesserEx extends HypaProcesser { summaryChunkVectors: SummaryChunkVector[] = []; // Calculate dot product similarity between two vectors - similarity(a: VectorArray, b: VectorArray) { + similarity(a: VectorArray, b: VectorArray): number { let dot = 0; for (let i = 0; i < a.length; i++) { @@ -864,7 +867,7 @@ class HypaProcesserEx extends HypaProcesser { return dot; } - async addSummaryChunks(chunks: SummaryChunk[]) { + async addSummaryChunks(chunks: SummaryChunk[]): Promise { // Maintain the superclass's caching structure by adding texts const texts = chunks.map((chunk) => chunk.text);