feat: add doNotSummarizeUserMessage option for HypaV3 and improve HypaV3 modal (#750)

# PR Checklist
- [x] Have you checked if it works normally in all models? *Ignore this
if it doesn't use models.*
- [x] Have you checked if it works normally in all web, local, and node
hosted versions? If it doesn't, have you blocked it in those versions?
- [x] Have you added type definitions?

# Description
This PR introduces following:
- Add doNotSummarizeUserMessage option to exclude user messages from
summarization
- Add early return logic to prevent unnecessary similarity checks when
summaries are empty
- Add length check before summarization
- Set to default settings when selecting HypaV3 on OtherBotSettings
- Add localization for HypaV3 settings and modal
- Change HypaV3 modal title
- Add import summary filtering and improve search in HypaV3
This commit is contained in:
kwaroran
2025-02-09 16:23:37 +09:00
committed by GitHub
11 changed files with 916 additions and 391 deletions

View File

@@ -770,5 +770,50 @@ export const languageChinese = {
"translatorPrompt": "翻译提示词", "translatorPrompt": "翻译提示词",
"translateBeforeHTMLFormatting": "於 HTML 格式化前翻译", "translateBeforeHTMLFormatting": "於 HTML 格式化前翻译",
"retranslate": "重新翻译", "retranslate": "重新翻译",
"loading": "加载中" "loading": "加载中",
} "hypaV3Settings": {
"descriptionLabel": "HypaMemory V3 是一个使用总结和向量搜索的长期记忆系统。",
"supaMemoryPromptPlaceHolder": "留空以使用默认值",
"maxMemoryTokensRatioLabel": "最大记忆令牌比率(估计)",
"maxMemoryTokensRatioError": "无法计算最大记忆令牌比率",
"memoryTokensRatioLabel": "记忆令牌比率",
"extraSummarizationRatioLabel": "额外总结比率",
"maxChatsPerSummaryLabel": "每个总结的最大消息数",
"recentMemoryRatioLabel": "最近记忆比率",
"similarMemoryRatioLabel": "相似记忆比率",
"randomMemoryRatioLabel": "随机记忆比率",
"enableSimilarityCorrectionLabel": "启用相似度校正",
"preserveOrphanedMemoryLabel": "保留孤立记忆",
"applyRegexScriptWhenRerollingLabel": "重新生成时应用正则脚本",
"doNotSummarizeUserMessageLabel": "不要总结用户消息",
},
"hypaV3Modal": {
"titleLabel": "HypaV3",
"resetConfirmMessage": "此操作无法撤销。您要重置 HypaV3 数据吗?",
"resetConfirmSecondMessage": "此操作不可逆。您确实要重置 HypaV3 数据吗?",
"convertLabel": "尚无总结,但您可以将 HypaV2 数据转换为 V3。",
"convertButton": "转换为 V3",
"convertSuccessMessage": "成功将 HypaV2 数据转换为 V3",
"convertErrorMessage": "将 HypaV2 数据转换为 V3 失败:{0}",
"noSummariesLabel": "尚无总结",
"searchPlaceholder": "输入 #N、ID 或搜索关键词",
"summaryNumberLabel": "总结 #{0}",
"deleteAfterConfirmMessage": "删除此后的所有总结?",
"deleteAfterConfirmSecondMessage": "此操作无法撤销。您确定吗?",
"translationLabel": "翻译",
"rerolledSummaryLabel": "重新生成的总结",
"rerolledTranslationLabel": "重新生成的总结翻译",
"connectedMessageCountLabel": "关联消息 ({0})",
"connectedFirstMessageLabel": "第一条消息",
"connectedMessageRoleLabel": "{0} 的消息",
"connectedMessageNotFoundLabel": "未找到消息",
"connectedMessageLoadingError": "加载关联消息时出错:{0}",
"connectedMessageTranslationLabel": "翻译",
"nextSummarizationFirstMessageLabel": "第一条消息",
"nextSummarizationNoMessageIdLabel": "无消息 ID",
"nextSummarizationLabel": "HypaV3 将总结 [{0}]",
"nextSummarizationNoMessagesFoundLabel": "警告:未找到消息",
"nextSummarizationLoadingError": "加载下一个总结目标时出错:{0}",
"emptySelectedFirstMessageLabel": "警告:所选的第一条消息为空"
},
}

View File

@@ -435,4 +435,49 @@ export const languageGerman = {
appendNameNAI: "Namen an NAI anhängen", appendNameNAI: "Namen an NAI anhängen",
customStopWords: "Benutzerdefinierte Stoppwörter", customStopWords: "Benutzerdefinierte Stoppwörter",
useAdvancedEditor: "Erweiterten Editor verwenden", useAdvancedEditor: "Erweiterten Editor verwenden",
"hypaV3Settings": {
"descriptionLabel": "HypaMemory V3 ist ein Langzeitgedächtnissystem, das sowohl Zusammenfassung als auch Vektorsuche verwendet.",
"supaMemoryPromptPlaceHolder": "Leer lassen für Standardeinstellung",
"maxMemoryTokensRatioLabel": "Maximales Gedächtnis-Token-Verhältnis (Geschätzt)",
"maxMemoryTokensRatioError": "Maximales Gedächtnis-Token-Verhältnis kann nicht berechnet werden",
"memoryTokensRatioLabel": "Gedächtnis-Token-Verhältnis",
"extraSummarizationRatioLabel": "Zusätzliches Zusammenfassungsverhältnis",
"maxChatsPerSummaryLabel": "Maximale Nachrichten pro Zusammenfassung",
"recentMemoryRatioLabel": "Verhältnis der jüngsten Erinnerungen",
"similarMemoryRatioLabel": "Verhältnis ähnlicher Erinnerungen",
"randomMemoryRatioLabel": "Verhältnis zufälliger Erinnerungen",
"enableSimilarityCorrectionLabel": "Ähnlichkeitskorrektur aktivieren",
"preserveOrphanedMemoryLabel": "Verwaiste Erinnerungen bewahren",
"applyRegexScriptWhenRerollingLabel": "Regex-Skript beim Neugenerieren anwenden",
"doNotSummarizeUserMessageLabel": "Benutzernachrichten nicht zusammenfassen",
},
"hypaV3Modal": {
"titleLabel": "HypaV3",
"resetConfirmMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie die HypaV3-Daten zurücksetzen?",
"resetConfirmSecondMessage": "Diese Aktion ist unwiderruflich. Möchten Sie die HypaV3-Daten wirklich zurücksetzen?",
"convertLabel": "Noch keine Zusammenfassungen, aber Sie können HypaV2-Daten zu V3 konvertieren.",
"convertButton": "Zu V3 konvertieren",
"convertSuccessMessage": "HypaV2-Daten erfolgreich zu V3 konvertiert",
"convertErrorMessage": "Konvertierung von HypaV2 zu V3 fehlgeschlagen: {0}",
"noSummariesLabel": "Noch keine Zusammenfassungen",
"searchPlaceholder": "Geben Sie #N, ID oder Suchanfrage ein",
"summaryNumberLabel": "Zusammenfassung #{0}",
"deleteAfterConfirmMessage": "Alle Zusammenfassungen nach dieser löschen?",
"deleteAfterConfirmSecondMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Sind Sie wirklich sicher?",
"translationLabel": "Übersetzung",
"rerolledSummaryLabel": "Neu generierte Zusammenfassung",
"rerolledTranslationLabel": "Übersetzung der neu generierten Zusammenfassung",
"connectedMessageCountLabel": "Verbundene Nachrichten ({0})",
"connectedFirstMessageLabel": "Erste Nachricht",
"connectedMessageRoleLabel": "Nachricht von {0}",
"connectedMessageNotFoundLabel": "Nachricht nicht gefunden",
"connectedMessageLoadingError": "Fehler beim Laden der verbundenen Nachricht: {0}",
"connectedMessageTranslationLabel": "Übersetzung",
"nextSummarizationFirstMessageLabel": "Erste Nachricht",
"nextSummarizationNoMessageIdLabel": "Keine Nachrichten-ID",
"nextSummarizationLabel": "HypaV3 wird [{0}] zusammenfassen",
"nextSummarizationNoMessagesFoundLabel": "WARNUNG: Keine Nachrichten gefunden",
"nextSummarizationLoadingError": "Fehler beim Laden des nächsten Zusammenfassungsziels: {0}",
"emptySelectedFirstMessageLabel": "WARNUNG: Ausgewählte erste Nachricht ist leer"
},
} }

View File

@@ -841,6 +841,50 @@ export const languageEnglish = {
banCharacterset: 'Auto Regenerate On Characterset', banCharacterset: 'Auto Regenerate On Characterset',
checkCorruption: "Check Corruption", checkCorruption: "Check Corruption",
showPromptComparison: "Show Prompt Comparison", showPromptComparison: "Show Prompt Comparison",
hypaV3Desc: "HypaMemory V3 is a long-term memory system that use both summarized data and vector search.",
inlayErrorResponse: "Inlay Error Response", inlayErrorResponse: "Inlay Error Response",
hypaV3Settings: {
descriptionLabel: "HypaMemory V3 is a long-term memory system that uses both summarization and vector search.",
supaMemoryPromptPlaceHolder: "Leave it blank to use default",
maxMemoryTokensRatioLabel: "Max Memory Tokens Ratio (Estimated)",
maxMemoryTokensRatioError: "Unable to calculate Max Memory Tokens Ratio",
memoryTokensRatioLabel: "Memory Tokens Ratio",
extraSummarizationRatioLabel: "Extra Summarization Ratio",
maxChatsPerSummaryLabel: "Max Messages Per Summary",
recentMemoryRatioLabel: "Recent Memory Ratio",
similarMemoryRatioLabel: "Similar Memory Ratio",
randomMemoryRatioLabel: "Random Memory Ratio",
enableSimilarityCorrectionLabel: "Enable Similarity Correction",
preserveOrphanedMemoryLabel: "Preserve Orphaned Memory",
applyRegexScriptWhenRerollingLabel: "Apply Regex Script When Rerolling",
doNotSummarizeUserMessageLabel: "Do Not Summarize User Message",
},
hypaV3Modal: {
titleLabel: "HypaV3",
resetConfirmMessage: "This action cannot be undone. Do you want to reset HypaV3 data?",
resetConfirmSecondMessage: "This action is irreversible. Do you really, really want to reset HypaV3 data?",
convertLabel: "No summaries yet, but you may convert HypaV2 data to V3.",
convertButton: "Convert to V3",
convertSuccessMessage: "Successfully converted HypaV2 data to V3",
convertErrorMessage: "Failed to convert HypaV2 data to V3: {0}",
noSummariesLabel: "No summaries yet",
searchPlaceholder: "Enter #N, ID, or query",
summaryNumberLabel: "Summary #{0}",
deleteAfterConfirmMessage: "Delete all summaries after this one?",
deleteAfterConfirmSecondMessage: "This action cannot be undone. Are you really sure?",
translationLabel: "Translation",
rerolledSummaryLabel: "Rerolled Summary",
rerolledTranslationLabel: "Rerolled Summary Translation",
connectedMessageCountLabel: "Connected Messages ({0})",
connectedFirstMessageLabel: "First Message",
connectedMessageRoleLabel: "{0}'s Message",
connectedMessageNotFoundLabel: "Message not found",
connectedMessageLoadingError: "Error loading connected message: {0}",
connectedMessageTranslationLabel: "Translation",
nextSummarizationFirstMessageLabel: "First Message",
nextSummarizationNoMessageIdLabel: "No Message ID",
nextSummarizationLabel: "HypaV3 will summarize [{0}]",
nextSummarizationNoMessagesFoundLabel: "WARN: No messages found",
nextSummarizationLoadingError: "Error loading next summarization target: {0}",
emptySelectedFirstMessageLabel: "WARN: Selected first message is empty",
},
} }

View File

@@ -680,4 +680,49 @@ export const languageSpanish = {
parameters: "Parámetros", parameters: "Parámetros",
sizeAndSpeed: "Tamaño y Velocidad", sizeAndSpeed: "Tamaño y Velocidad",
useLegacyGUI: "Usar Interfaz Legacy", useLegacyGUI: "Usar Interfaz Legacy",
"hypaV3Settings": {
"descriptionLabel": "HypaMemory V3 es un sistema de memoria a largo plazo que utiliza tanto resúmenes como búsqueda vectorial.",
"supaMemoryPromptPlaceHolder": "Dejar en blanco para usar el valor predeterminado",
"maxMemoryTokensRatioLabel": "Ratio Máximo de Tokens de Memoria (Estimado)",
"maxMemoryTokensRatioError": "No se puede calcular el ratio máximo de tokens de memoria",
"memoryTokensRatioLabel": "Ratio de Tokens de Memoria",
"extraSummarizationRatioLabel": "Ratio de Resumen Adicional",
"maxChatsPerSummaryLabel": "Mensajes Máximos por Resumen",
"recentMemoryRatioLabel": "Ratio de Memoria Reciente",
"similarMemoryRatioLabel": "Ratio de Memoria Similar",
"randomMemoryRatioLabel": "Ratio de Memoria Aleatoria",
"enableSimilarityCorrectionLabel": "Activar Corrección de Similitud",
"preserveOrphanedMemoryLabel": "Preservar Memoria Huérfana",
"applyRegexScriptWhenRerollingLabel": "Aplicar Script Regex al Regenerar",
"doNotSummarizeUserMessageLabel": "No Resumir Mensajes del Usuario",
},
"hypaV3Modal": {
"titleLabel": "HypaV3",
"resetConfirmMessage": "Esta acción no se puede deshacer. ¿Desea restablecer los datos de HypaV3?",
"resetConfirmSecondMessage": "Esta acción es irreversible. ¿Está realmente seguro de querer restablecer los datos de HypaV3?",
"convertLabel": "Aún no hay resúmenes, pero puede convertir datos de HypaV2 a V3.",
"convertButton": "Convertir a V3",
"convertSuccessMessage": "Datos de HypaV2 convertidos exitosamente a V3",
"convertErrorMessage": "Error al convertir datos de HypaV2 a V3: {0}",
"noSummariesLabel": "Aún no hay resúmenes",
"searchPlaceholder": "Ingrese #N, ID o búsqueda",
"summaryNumberLabel": "Resumen #{0}",
"deleteAfterConfirmMessage": "¿Eliminar todos los resúmenes después de este?",
"deleteAfterConfirmSecondMessage": "Esta acción no se puede deshacer. ¿Está realmente seguro?",
"translationLabel": "Traducción",
"rerolledSummaryLabel": "Resumen Regenerado",
"rerolledTranslationLabel": "Traducción del Resumen Regenerado",
"connectedMessageCountLabel": "Mensajes Conectados ({0})",
"connectedFirstMessageLabel": "Primer Mensaje",
"connectedMessageRoleLabel": "Mensaje de {0}",
"connectedMessageNotFoundLabel": "Mensaje no encontrado",
"connectedMessageLoadingError": "Error al cargar mensaje conectado: {0}",
"connectedMessageTranslationLabel": "Traducción",
"nextSummarizationFirstMessageLabel": "Primer Mensaje",
"nextSummarizationNoMessageIdLabel": "Sin ID de Mensaje",
"nextSummarizationLabel": "HypaV3 resumirá [{0}]",
"nextSummarizationNoMessagesFoundLabel": "ADVERTENCIA: No se encontraron mensajes",
"nextSummarizationLoadingError": "Error al cargar el siguiente objetivo de resumen: {0}",
"emptySelectedFirstMessageLabel": "ADVERTENCIA: El primer mensaje seleccionado está vacío"
},
} }

View File

@@ -767,4 +767,49 @@ export const languageKorean = {
"translateBeforeHTMLFormatting": "HTML 포맷 전 번역", "translateBeforeHTMLFormatting": "HTML 포맷 전 번역",
"retranslate": "다시 번역", "retranslate": "다시 번역",
"loading": "로딩중", "loading": "로딩중",
"hypaV3Settings": {
"descriptionLabel": "HypaMemory V3는 요약과 벡터 검색을 모두 사용하는 장기 기억 시스템입니다.",
"supaMemoryPromptPlaceHolder": "기본값을 사용하려면 비워두세요",
"maxMemoryTokensRatioLabel": "최대 메모리 토큰 비율 (추정)",
"maxMemoryTokensRatioError": "최대 메모리 토큰 비율을 계산할 수 없습니다",
"memoryTokensRatioLabel": "메모리 토큰 비율",
"extraSummarizationRatioLabel": "추가 요약 비율",
"maxChatsPerSummaryLabel": "요약당 최대 메시지 수",
"recentMemoryRatioLabel": "최근 메모리 비율",
"similarMemoryRatioLabel": "유사 메모리 비율",
"randomMemoryRatioLabel": "무작위 메모리 비율",
"enableSimilarityCorrectionLabel": "유사도 보정 활성화",
"preserveOrphanedMemoryLabel": "고아 메모리 보존",
"applyRegexScriptWhenRerollingLabel": "재생성 시 정규식 스크립트 적용",
"doNotSummarizeUserMessageLabel": "유저 메시지 요약하지 않기",
},
"hypaV3Modal": {
"titleLabel": "HypaV3",
"resetConfirmMessage": "이 작업은 되돌릴 수 없습니다. HypaV3 데이터를 초기화하시겠습니까?",
"resetConfirmSecondMessage": "이 작업은 복구할 수 없습니다. 정말로, 정말로 HypaV3 데이터를 초기화하시겠습니까?",
"convertLabel": "아직 요약이 없지만, HypaV2 데이터를 V3로 변환할 수 있습니다.",
"convertButton": "V3로 변환",
"convertSuccessMessage": "HypaV2 데이터를 V3로 성공적으로 변환했습니다",
"convertErrorMessage": "HypaV2 데이터를 V3로 변환하는데 실패했습니다: {0}",
"noSummariesLabel": "아직 요약이 없습니다",
"searchPlaceholder": "#N, ID 또는 검색어 입력",
"summaryNumberLabel": "요약 #{0}",
"deleteAfterConfirmMessage": "이 요약 이후의 모든 요약을 삭제하시겠습니까?",
"deleteAfterConfirmSecondMessage": "이 작업은 되돌릴 수 없습니다. 정말 삭제하시겠습니까?",
"translationLabel": "번역",
"rerolledSummaryLabel": "재생성된 요약",
"rerolledTranslationLabel": "재생성된 요약 번역",
"connectedMessageCountLabel": "연결된 메시지 ({0})",
"connectedFirstMessageLabel": "첫 메시지",
"connectedMessageRoleLabel": "{0}의 메시지",
"connectedMessageNotFoundLabel": "메시지를 찾을 수 없습니다",
"connectedMessageLoadingError": "연결된 메시지를 불러오는 동안 오류 발생: {0}",
"connectedMessageTranslationLabel": "번역",
"nextSummarizationFirstMessageLabel": "첫 메시지",
"nextSummarizationNoMessageIdLabel": "메시지 ID 없음",
"nextSummarizationLabel": "HypaV3가 [{0}]를 요약할 예정입니다",
"nextSummarizationNoMessagesFoundLabel": "경고: 메시지를 찾을 수 없습니다",
"nextSummarizationLoadingError": "다음 요약 대상을 불러오는 동안 오류 발생: {0}",
"emptySelectedFirstMessageLabel": "경고: 선택된 첫 메시지가 비어있습니다"
},
} }

View File

@@ -409,4 +409,49 @@ export const LanguageVietnamese = {
module: "Mô-đun", module: "Mô-đun",
modules: "Mô-đun", modules: "Mô-đun",
useAdvancedEditor: "Sử dụng trình biên tập nâng cao", useAdvancedEditor: "Sử dụng trình biên tập nâng cao",
"hypaV3Settings": {
"descriptionLabel": "HypaMemory V3 là hệ thống bộ nhớ dài hạn sử dụng cả tóm tắt và tìm kiếm vector.",
"supaMemoryPromptPlaceHolder": "Để trống để sử dụng giá trị mặc định",
"maxMemoryTokensRatioLabel": "Tỷ lệ Token Bộ nhớ Tối đa (Ước tính)",
"maxMemoryTokensRatioError": "Không thể tính toán Tỷ lệ Token Bộ nhớ Tối đa",
"memoryTokensRatioLabel": "Tỷ lệ Token Bộ nhớ",
"extraSummarizationRatioLabel": "Tỷ lệ Tóm tắt Bổ sung",
"maxChatsPerSummaryLabel": "Số Tin nhắn Tối đa cho mỗi Tóm tắt",
"recentMemoryRatioLabel": "Tỷ lệ Bộ nhớ Gần đây",
"similarMemoryRatioLabel": "Tỷ lệ Bộ nhớ Tương tự",
"randomMemoryRatioLabel": "Tỷ lệ Bộ nhớ Ngẫu nhiên",
"enableSimilarityCorrectionLabel": "Bật Hiệu chỉnh Độ tương tự",
"preserveOrphanedMemoryLabel": "Giữ Bộ nhớ Mồ côi",
"applyRegexScriptWhenRerollingLabel": "Áp dụng Script Regex khi Tạo lại",
"doNotSummarizeUserMessageLabel": "Không Tóm tắt Tin nhắn Người dùng",
},
"hypaV3Modal": {
"titleLabel": "HypaV3",
"resetConfirmMessage": "Hành động này không thể hoàn tác. Bạn có muốn đặt lại dữ liệu HypaV3 không?",
"resetConfirmSecondMessage": "Hành động này không thể khôi phục. Bạn có thực sự chắc chắn muốn đặt lại dữ liệu HypaV3 không?",
"convertLabel": "Chưa có tóm tắt nào, nhưng bạn có thể chuyển đổi dữ liệu HypaV2 sang V3.",
"convertButton": "Chuyển đổi sang V3",
"convertSuccessMessage": "Đã chuyển đổi thành công dữ liệu HypaV2 sang V3",
"convertErrorMessage": "Chuyển đổi dữ liệu HypaV2 sang V3 thất bại: {0}",
"noSummariesLabel": "Chưa có tóm tắt nào",
"searchPlaceholder": "Nhập #N, ID hoặc từ khóa",
"summaryNumberLabel": "Tóm tắt #{0}",
"deleteAfterConfirmMessage": "Xóa tất cả các tóm tắt sau tóm tắt này?",
"deleteAfterConfirmSecondMessage": "Hành động này không thể hoàn tác. Bạn có chắc chắn không?",
"translationLabel": "Bản dịch",
"rerolledSummaryLabel": "Tóm tắt đã Tạo lại",
"rerolledTranslationLabel": "Bản dịch Tóm tắt đã Tạo lại",
"connectedMessageCountLabel": "Tin nhắn Liên kết ({0})",
"connectedFirstMessageLabel": "Tin nhắn Đầu tiên",
"connectedMessageRoleLabel": "Tin nhắn của {0}",
"connectedMessageNotFoundLabel": "Không tìm thấy tin nhắn",
"connectedMessageLoadingError": "Lỗi khi tải tin nhắn liên kết: {0}",
"connectedMessageTranslationLabel": "Bản dịch",
"nextSummarizationFirstMessageLabel": "Tin nhắn Đầu tiên",
"nextSummarizationNoMessageIdLabel": "Không có ID Tin nhắn",
"nextSummarizationLabel": "HypaV3 sẽ tóm tắt [{0}]",
"nextSummarizationNoMessagesFoundLabel": "CẢNH BÁO: Không tìm thấy tin nhắn",
"nextSummarizationLoadingError": "Lỗi khi tải mục tiêu tóm tắt tiếp theo: {0}",
"emptySelectedFirstMessageLabel": "CẢNH BÁO: Tin nhắn đầu tiên được chọn trống"
},
} }

View File

@@ -802,7 +802,51 @@ export const languageChineseTraditional = {
"banCharacterset": "自動重新生成字符集", "banCharacterset": "自動重新生成字符集",
"checkCorruption": "檢查損壞", "checkCorruption": "檢查損壞",
"showPromptComparison": "顯示提示比較", "showPromptComparison": "顯示提示比較",
"hypaV3Desc": "HypaMemory V3 是一個長期記憶系統,使用摘要資料和向量搜尋。",
"inlayErrorResponse": "嵌入錯誤回應", "inlayErrorResponse": "嵌入錯誤回應",
"APIPool": "API 工具" "APIPool": "API 工具",
"hypaV3Settings": {
"descriptionLabel": "HypaMemory V3 是一個使用摘要和向量搜索的長期記憶系統。",
"supaMemoryPromptPlaceHolder": "留空以使用預設值",
"maxMemoryTokensRatioLabel": "最大記憶標記比率(估計)",
"maxMemoryTokensRatioError": "無法計算最大記憶標記比率",
"memoryTokensRatioLabel": "記憶標記比率",
"extraSummarizationRatioLabel": "額外摘要比率",
"maxChatsPerSummaryLabel": "每個摘要的最大訊息數",
"recentMemoryRatioLabel": "最近記憶比率",
"similarMemoryRatioLabel": "相似記憶比率",
"randomMemoryRatioLabel": "隨機記憶比率",
"enableSimilarityCorrectionLabel": "啟用相似度校正",
"preserveOrphanedMemoryLabel": "保留孤立記憶",
"applyRegexScriptWhenRerollingLabel": "重新生成時應用正則表達式腳本",
"doNotSummarizeUserMessageLabel": "不要摘要用戶訊息"
},
"hypaV3Modal": {
"titleLabel": "HypaV3 數據",
"resetConfirmMessage": "此操作無法撤銷。您要重置 HypaV3 數據嗎?",
"resetConfirmSecondMessage": "此操作不可逆。您真的確定要重置 HypaV3 數據嗎?",
"convertLabel": "尚無摘要,但您可以將 HypaV2 數據轉換為 V3。",
"convertButton": "轉換為 V3",
"convertSuccessMessage": "成功將 HypaV2 數據轉換為 V3",
"convertErrorMessage": "無法將 HypaV2 數據轉換為 V3{0}",
"noSummariesLabel": "尚無摘要",
"searchPlaceholder": "輸入 #N、ID 或搜尋關鍵字",
"summaryNumberLabel": "摘要 #{0}",
"deleteAfterConfirmMessage": "刪除此摘要之後的所有摘要?",
"deleteAfterConfirmSecondMessage": "此操作無法撤銷。您確定要這樣做嗎?",
"translationLabel": "翻譯",
"rerolledSummaryLabel": "重新生成的摘要",
"rerolledTranslationLabel": "重新生成的摘要翻譯",
"connectedMessageCountLabel": "關聯訊息({0}",
"connectedFirstMessageLabel": "第一條訊息",
"connectedMessageRoleLabel": "{0} 的訊息",
"connectedMessageNotFoundLabel": "找不到訊息",
"connectedMessageLoadingError": "載入關聯訊息時出錯:{0}",
"connectedMessageTranslationLabel": "翻譯",
"nextSummarizationFirstMessageLabel": "第一條訊息",
"nextSummarizationNoMessageIdLabel": "無訊息 ID",
"nextSummarizationLabel": "HypaV3 將摘要 [{0}]",
"nextSummarizationNoMessagesFoundLabel": "警告:找不到訊息",
"nextSummarizationLoadingError": "載入下一個摘要目標時出錯:{0}",
"emptySelectedFirstMessageLabel": "警告:選定的第一條訊息為空"
},
} }

View File

@@ -5,6 +5,8 @@
SettingsIcon, SettingsIcon,
Trash2Icon, Trash2Icon,
XIcon, XIcon,
ChevronUpIcon,
ChevronDownIcon,
LanguagesIcon, LanguagesIcon,
StarIcon, StarIcon,
RefreshCw, RefreshCw,
@@ -28,6 +30,7 @@
import { summarize } from "../../ts/process/memory/hypav3"; import { summarize } from "../../ts/process/memory/hypav3";
import { type Message } from "../../ts/storage/database.svelte"; import { type Message } from "../../ts/storage/database.svelte";
import { translateHTML } from "../../ts/translator/translator"; import { translateHTML } from "../../ts/translator/translator";
import { language } from "../../lang";
interface SummaryUI { interface SummaryUI {
originalRef: HTMLTextAreaElement; originalRef: HTMLTextAreaElement;
@@ -50,20 +53,30 @@
translationRef: HTMLTextAreaElement; translationRef: HTMLTextAreaElement;
} }
interface SearchResult { class SummarySearchResult {
element: HTMLElement; constructor(
matchType: "chatMemo" | "summary"; public summaryIndex: number,
summaryPosition?: { public start: number,
start: number; public end: number
end: number; ) {}
};
} }
class ChatMemoSearchResult {
constructor(
public summaryIndex: number,
public memoIndex: number
) {}
}
type SearchResult = SummarySearchResult | ChatMemoSearchResult;
interface SearchUI { interface SearchUI {
ref: HTMLInputElement; ref: HTMLInputElement;
query: string; query: string;
currentIndex: number;
results: SearchResult[]; results: SearchResult[];
currentResultIndex: number;
requestedSearchFromIndex: number;
isNavigating: boolean;
} }
const hypaV3DataState = $derived( const hypaV3DataState = $derived(
@@ -75,6 +88,7 @@
let summaryUIStates = $state<SummaryUI[]>([]); let summaryUIStates = $state<SummaryUI[]>([]);
let expandedMessageUIState = $state<ExpandedMessageUI>(null); let expandedMessageUIState = $state<ExpandedMessageUI>(null);
let searchUIState = $state<SearchUI>(null); let searchUIState = $state<SearchUI>(null);
let showImportantOnly = $state(false);
$effect.pre(() => { $effect.pre(() => {
summaryUIStates = hypaV3DataState.summaries.map((summary) => ({ summaryUIStates = hypaV3DataState.summaries.map((summary) => ({
@@ -110,8 +124,10 @@
searchUIState = { searchUIState = {
ref: null, ref: null,
query: "", query: "",
currentIndex: -1,
results: [], results: [],
currentResultIndex: -1,
requestedSearchFromIndex: -1,
isNavigating: false,
}; };
// Focus on search element after it's rendered // Focus on search element after it's rendered
@@ -134,122 +150,187 @@
} }
if (e.key === "Enter") { if (e.key === "Enter") {
e.preventDefault(); // Prevent event default action e.preventDefault?.(); // Prevent event default action
const query = searchUIState.query.trim(); const query = searchUIState.query.trim();
if (!query) return; if (!query) return;
// When received a new query
if (searchUIState.currentResultIndex === -1) {
const results = generateSearchResults(query);
if (results.length === 0) return;
searchUIState.results = results;
}
const nextResult = getNextSearchResult(e.shiftKey);
if (nextResult) {
navigateToSearchResult(nextResult);
}
}
}
function generateSearchResults(query: string): SearchResult[] {
const results: SearchResult[] = [];
const normalizedQuery = query.trim().toLowerCase();
// Search summary index // Search summary index
if (query.match(/^#\d+$/)) { if (query.match(/^#\d+$/)) {
const summaryNumber = parseInt(query.substring(1)) - 1; const summaryNumber = parseInt(query.substring(1)) - 1;
if ( if (
summaryNumber >= 0 && summaryNumber >= 0 &&
summaryNumber < hypaV3DataState.summaries.length summaryNumber < hypaV3DataState.summaries.length &&
(!showImportantOnly ||
hypaV3DataState.summaries[summaryNumber].isImportant)
) { ) {
summaryUIStates[summaryNumber].originalRef.scrollIntoView({ results.push(new SummarySearchResult(summaryNumber, 0, 0));
behavior: "instant",
block: "center",
});
} }
return; return results;
} }
const normalizedQuery = query.toLowerCase();
if (searchUIState.currentIndex === -1) {
const results: SearchResult[] = [];
if (isGuidLike(query)) { if (isGuidLike(query)) {
// Search chatMemo // Search chatMemo
summaryUIStates.forEach((summaryUI) => { summaryUIStates.forEach((summaryUI, summaryIndex) => {
summaryUI.chatMemoRefs.forEach((buttonRef) => { if (
!showImportantOnly ||
hypaV3DataState.summaries[summaryIndex].isImportant
) {
summaryUI.chatMemoRefs.forEach((buttonRef, memoIndex) => {
const buttonText = buttonRef.textContent?.toLowerCase() || ""; const buttonText = buttonRef.textContent?.toLowerCase() || "";
if (buttonText.includes(normalizedQuery)) { if (buttonText.includes(normalizedQuery)) {
results.push({ results.push(new ChatMemoSearchResult(summaryIndex, memoIndex));
element: buttonRef as HTMLButtonElement,
matchType: "chatMemo",
});
} }
}); });
}
}); });
} else { } else {
// Search summary // Search summary
summaryUIStates.forEach((summaryUI) => { summaryUIStates.forEach((summaryUI, summaryIndex) => {
if (
!showImportantOnly ||
hypaV3DataState.summaries[summaryIndex].isImportant
) {
const textAreaText = summaryUI.originalRef.value?.toLowerCase(); const textAreaText = summaryUI.originalRef.value?.toLowerCase();
let pos = -1; let pos = -1;
while ( while (
(pos = textAreaText.indexOf(normalizedQuery, pos + 1)) !== -1 (pos = textAreaText.indexOf(normalizedQuery, pos + 1)) !== -1
) { ) {
results.push({ results.push(
element: summaryUI.originalRef as HTMLTextAreaElement, new SummarySearchResult(
matchType: "summary", summaryIndex,
summaryPosition: { pos,
start: pos, pos + normalizedQuery.length
end: pos + normalizedQuery.length, )
}, );
}); }
} }
}); });
} }
searchUIState.results = results; return results;
} }
if (searchUIState.results.length === 0) return; function isGuidLike(str: string): boolean {
const strTrimed = str.trim();
// Move to next result // Exclude too short inputs
searchUIState.currentIndex = if (strTrimed.length < 4) return false;
(searchUIState.currentIndex + 1) % searchUIState.results.length;
const result = searchUIState.results[searchUIState.currentIndex]; return /^[0-9a-f]{4,12}(-[0-9a-f]{4,12}){0,4}-?$/i.test(strTrimed);
}
function getNextSearchResult(backward: boolean): SearchResult | null {
if (!searchUIState || searchUIState.results.length === 0) return null;
let nextIndex: number;
if (searchUIState.requestedSearchFromIndex !== -1) {
const fromSummaryIndex = searchUIState.requestedSearchFromIndex;
nextIndex = backward
? searchUIState.results.findLastIndex(
(r) => r.summaryIndex <= fromSummaryIndex
)
: searchUIState.results.findIndex(
(r) => r.summaryIndex >= fromSummaryIndex
);
if (nextIndex === -1) {
nextIndex = backward ? searchUIState.results.length - 1 : 0;
}
searchUIState.requestedSearchFromIndex = -1;
} else {
const delta = backward ? -1 : 1;
nextIndex =
(searchUIState.currentResultIndex +
delta +
searchUIState.results.length) %
searchUIState.results.length;
}
searchUIState.currentResultIndex = nextIndex;
return searchUIState.results[nextIndex];
}
function navigateToSearchResult(result: SearchResult) {
searchUIState.isNavigating = true;
if (result instanceof SummarySearchResult) {
const textarea = summaryUIStates[result.summaryIndex].originalRef;
// Scroll to element // Scroll to element
result.element.scrollIntoView({ textarea.scrollIntoView({
behavior: "instant", behavior: "instant",
block: "center", block: "center",
}); });
if (result.matchType === "chatMemo") { if (result.start === result.end) {
// Highlight chatMemo result return;
result.element.classList.add("ring-2", "ring-zinc-500"); }
// Remove highlight after a short delay // Scroll to query
window.setTimeout(() => { textarea.setSelectionRange(result.start, result.end);
result.element.classList.remove("ring-2", "ring-zinc-500");
}, 1000);
} else {
// Handle summary text selection
const textarea = result.element as HTMLTextAreaElement;
// Make readonly temporarily
textarea.readOnly = true;
// Select query
textarea.setSelectionRange(
result.summaryPosition.start,
result.summaryPosition.end
);
textarea.focus();
scrollToSelection(textarea); scrollToSelection(textarea);
// This only works on firefox // Highlight query on desktop environment
//textarea.scrollTop = textarea.scrollHeight; // Scroll to the bottom if (!("ontouchend" in window)) {
//textarea.blur(); // Collapse selection // Make readonly temporarily
//textarea.focus(); // This scrolls the textarea textarea.readOnly = true;
textarea.focus();
// Highlight textarea
window.setTimeout(() => { window.setTimeout(() => {
searchUIState.ref.focus(); // Restore focus to search bar searchUIState.ref.focus(); // Restore focus to search bar
textarea.readOnly = false; // Remove readonly after focus moved textarea.readOnly = false; // Remove readonly after focus moved
}, 300); }, 300);
} }
} else {
const button =
summaryUIStates[result.summaryIndex].chatMemoRefs[result.memoIndex];
// Scroll to element
button.scrollIntoView({
behavior: "instant",
block: "center",
});
// Highlight chatMemo
button.classList.add("ring-2", "ring-zinc-500");
// Remove highlight after a short delay
window.setTimeout(() => {
button.classList.remove("ring-2", "ring-zinc-500");
}, 1000);
} }
searchUIState.isNavigating = false;
} }
function scrollToSelection(textarea: HTMLTextAreaElement) { function scrollToSelection(textarea: HTMLTextAreaElement) {
@@ -286,15 +367,6 @@
textarea.scrollTop = selectionTop - textarea.clientHeight / 2; textarea.scrollTop = selectionTop - textarea.clientHeight / 2;
} }
function isGuidLike(str: string): boolean {
const strTrimed = str.trim();
// Exclude too short inputs
if (strTrimed.length < 4) return false;
return /^[0-9a-f]{4,12}(-[0-9a-f]{4,12}){0,4}-?$/i.test(strTrimed);
}
async function toggleTranslate( async function toggleTranslate(
summaryIndex: number, summaryIndex: number,
regenerate?: boolean regenerate?: boolean
@@ -759,21 +831,42 @@
<div class="flex justify-between items-center mb-2 sm:mb-4"> <div class="flex justify-between items-center mb-2 sm:mb-4">
<!-- Modal Title --> <!-- Modal Title -->
<h1 class="text-lg sm:text-2xl font-semibold text-zinc-300"> <h1 class="text-lg sm:text-2xl font-semibold text-zinc-300">
HypaV3 Data {language.hypaV3Modal.titleLabel}
</h1> </h1>
<!-- Buttons Container --> <!-- Buttons Container -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Search Button --> <!-- Search Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
onclick={async () => toggleSearch()} onclick={async () => toggleSearch()}
> >
<SearchIcon class="w-6 h-6" /> <SearchIcon class="w-6 h-6" />
</button> </button>
<!-- Filter Important Summary Button -->
<button
class="p-2 transition-colors {showImportantOnly
? 'text-yellow-400 hover:text-yellow-300'
: 'text-zinc-400 hover:text-zinc-200'}"
tabindex="-1"
onclick={() => {
if (searchUIState) {
searchUIState.query = "";
searchUIState.results = [];
searchUIState.currentResultIndex = -1;
}
showImportantOnly = !showImportantOnly;
}}
>
<StarIcon class="w-6 h-6" />
</button>
<!-- Settings Button --> <!-- Settings Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
onclick={() => { onclick={() => {
alertStore.set({ alertStore.set({
type: "none", type: "none",
@@ -790,11 +883,12 @@
<!-- Reset Button --> <!-- Reset Button -->
<button <button
class="p-2 text-zinc-400 hover:text-rose-300 transition-colors" class="p-2 text-zinc-400 hover:text-rose-300 transition-colors"
tabindex="-1"
onclick={async () => { onclick={async () => {
if ( if (
await alertConfirmTwice( await alertConfirmTwice(
"This action cannot be undone. Do you want to reset HypaV3 data?", language.hypaV3Modal.resetConfirmMessage,
"This action is irreversible. Do you really, really want to reset HypaV3 data?" language.hypaV3Modal.resetConfirmSecondMessage
) )
) { ) {
DBState.db.characters[$selectedCharID].chats[ DBState.db.characters[$selectedCharID].chats[
@@ -814,6 +908,7 @@
<!-- Close Button --> <!-- Close Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
onclick={() => { onclick={() => {
alertStore.set({ alertStore.set({
type: "none", type: "none",
@@ -827,7 +922,7 @@
</div> </div>
<!-- Scrollable Container --> <!-- Scrollable Container -->
<div class="flex flex-col gap-2 sm:gap-4 overflow-y-auto"> <div class="flex flex-col gap-2 sm:gap-4 overflow-y-auto" tabindex="-1">
{#if hypaV3DataState.summaries.length === 0} {#if hypaV3DataState.summaries.length === 0}
<!-- Conversion Section --> <!-- Conversion Section -->
{#if isHypaV2ConversionPossible()} {#if isHypaV2ConversionPossible()}
@@ -836,33 +931,37 @@
> >
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<div class="my-1 sm:my-2 text-center text-zinc-300"> <div class="my-1 sm:my-2 text-center text-zinc-300">
No summaries yet, but you may convert HypaV2 data to V3. {language.hypaV3Modal.convertLabel}
</div> </div>
<button <button
class="my-1 sm:my-2 px-4 py-2 rounded-md text-zinc-300 font-semibold bg-zinc-700 hover:bg-zinc-500 transition-colors" class="my-1 sm:my-2 px-4 py-2 rounded-md text-zinc-300 font-semibold bg-zinc-700 hover:bg-zinc-500 transition-colors"
tabindex="-1"
onclick={async () => { onclick={async () => {
const conversionResult = convertHypaV2ToV3(); const conversionResult = convertHypaV2ToV3();
if (conversionResult.success) { if (conversionResult.success) {
await alertNormalWait( await alertNormalWait(
"Successfully converted HypaV2 data to V3" language.hypaV3Modal.convertSuccessMessage
); );
} else { } else {
await alertNormalWait( await alertNormalWait(
`Failed to convert HypaV2 data to V3: ${conversionResult.error}` language.hypaV3Modal.convertErrorMessage.replace(
"{0}",
conversionResult.error
)
); );
} }
showHypaV3Alert(); showHypaV3Alert();
}} }}
> >
Convert to V3 {language.hypaV3Modal.convertButton}
</button> </button>
</div> </div>
</div> </div>
{:else} {:else}
<div class="p-4 sm:p-3 md:p-4 text-center text-zinc-400"> <div class="p-4 sm:p-3 md:p-4 text-center text-zinc-400">
No summaries yet {language.hypaV3Modal.noSummariesLabel}
</div> </div>
{/if} {/if}
@@ -880,13 +979,13 @@
> >
<input <input
class="w-full px-2 sm:px-4 py-2 sm:py-3 rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 text-zinc-200 bg-zinc-900" class="w-full px-2 sm:px-4 py-2 sm:py-3 rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 text-zinc-200 bg-zinc-900"
placeholder="Enter #N, ID, or search query" placeholder={language.hypaV3Modal.searchPlaceholder}
bind:this={searchUIState.ref} bind:this={searchUIState.ref}
bind:value={searchUIState.query} bind:value={searchUIState.query}
oninput={() => { oninput={() => {
if (searchUIState) { if (searchUIState) {
searchUIState.currentIndex = -1;
searchUIState.results = []; searchUIState.results = [];
searchUIState.currentResultIndex = -1;
} }
}} }}
onkeydown={(e) => onSearch(e)} onkeydown={(e) => onSearch(e)}
@@ -897,16 +996,32 @@
<span <span
class="absolute right-3 top-1/2 -translate-y-1/2 px-1.5 sm:px-3 py-1 sm:py-2 rounded text-sm font-semibold text-zinc-100 bg-zinc-700/65" class="absolute right-3 top-1/2 -translate-y-1/2 px-1.5 sm:px-3 py-1 sm:py-2 rounded text-sm font-semibold text-zinc-100 bg-zinc-700/65"
> >
{searchUIState.currentIndex + 1}/{searchUIState.results {searchUIState.currentResultIndex + 1}/{searchUIState
.length} .results.length}
</span> </span>
{/if} {/if}
</div> </div>
<!-- Previous Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
onclick={async () => toggleSearch()} tabindex="-1"
onclick={() => {
onSearch({ shiftKey: true, key: "Enter" } as KeyboardEvent);
}}
> >
<XIcon class="w-6 h-6" /> <ChevronUpIcon class="w-6 h-6" />
</button>
<!-- Next Button -->
<button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
onclick={() => {
onSearch({ key: "Enter" } as KeyboardEvent);
}}
>
<ChevronDownIcon class="w-6 h-6" />
</button> </button>
</div> </div>
</div> </div>
@@ -914,6 +1029,7 @@
<!-- Summaries List --> <!-- Summaries List -->
{#each hypaV3DataState.summaries as summary, i} {#each hypaV3DataState.summaries as summary, i}
{#if !showImportantOnly || summary.isImportant}
{#if summaryUIStates[i]} {#if summaryUIStates[i]}
<!-- Summary Item --> <!-- Summary Item -->
<div <div
@@ -921,12 +1037,18 @@
> >
<!-- Original Summary Header --> <!-- Original Summary Header -->
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-zinc-400">Summary #{i + 1}</span> <span class="text-sm text-zinc-400"
>{language.hypaV3Modal.summaryNumberLabel.replace(
"{0}",
(i + 1).toString()
)}</span
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Translate Button --> <!-- Translate Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
use:handleDualAction={{ use:handleDualAction={{
onMainAction: () => toggleTranslate(i, false), onMainAction: () => toggleTranslate(i, false),
onAlternativeAction: () => toggleTranslate(i, true), onAlternativeAction: () => toggleTranslate(i, true),
@@ -937,9 +1059,10 @@
<!-- Important Button --> <!-- Important Button -->
<button <button
class="p-2 hover:text-zinc-200 transition-colors {summary.isImportant class="p-2 transition-colors {summary.isImportant
? 'text-yellow-400' ? 'text-yellow-400 hover:text-yellow-300'
: 'text-zinc-400'}" : 'text-zinc-400 hover:text-zinc-200'}"
tabindex="-1"
onclick={() => { onclick={() => {
summary.isImportant = !summary.isImportant; summary.isImportant = !summary.isImportant;
}} }}
@@ -950,8 +1073,9 @@
<!-- Reroll Button --> <!-- Reroll Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
onclick={async () => await toggleReroll(i)} tabindex="-1"
disabled={!isRerollable(i)} disabled={!isRerollable(i)}
onclick={async () => await toggleReroll(i)}
> >
<RefreshCw class="w-4 h-4" /> <RefreshCw class="w-4 h-4" />
</button> </button>
@@ -959,11 +1083,12 @@
<!-- Delete After Button --> <!-- Delete After Button -->
<button <button
class="p-2 text-zinc-400 hover:text-rose-300 transition-colors" class="p-2 text-zinc-400 hover:text-rose-300 transition-colors"
tabindex="-1"
onclick={async () => { onclick={async () => {
if ( if (
await alertConfirmTwice( await alertConfirmTwice(
"Delete all summaries after this one?", language.hypaV3Modal.deleteAfterConfirmMessage,
"This action cannot be undone. Are you really sure?" language.hypaV3Modal.deleteAfterConfirmSecondMessage
) )
) { ) {
hypaV3DataState.summaries.splice(i + 1); hypaV3DataState.summaries.splice(i + 1);
@@ -983,6 +1108,11 @@
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-colors text-zinc-200 bg-zinc-900" class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-colors text-zinc-200 bg-zinc-900"
bind:this={summaryUIStates[i].originalRef} bind:this={summaryUIStates[i].originalRef}
bind:value={summary.text} bind:value={summary.text}
onfocus={() => {
if (searchUIState && !searchUIState.isNavigating) {
searchUIState.requestedSearchFromIndex = i;
}
}}
> >
</textarea> </textarea>
</div> </div>
@@ -991,14 +1121,14 @@
{#if summaryUIStates[i].translation} {#if summaryUIStates[i].translation}
<div class="mt-2 sm:mt-4"> <div class="mt-2 sm:mt-4">
<div class="mb-2 sm:mb-4 text-sm text-zinc-400"> <div class="mb-2 sm:mb-4 text-sm text-zinc-400">
Translation {language.hypaV3Modal.translationLabel}
</div> </div>
<textarea <textarea
readonly
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900" class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
bind:this={summaryUIStates[i].translationRef} readonly
tabindex="-1" tabindex="-1"
bind:this={summaryUIStates[i].translationRef}
value={summaryUIStates[i].translation} value={summaryUIStates[i].translation}
></textarea> ></textarea>
</div> </div>
@@ -1008,13 +1138,17 @@
<!-- Rerolled Summary Header --> <!-- Rerolled Summary Header -->
<div class="mt-2 sm:mt-4"> <div class="mt-2 sm:mt-4">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-zinc-400">Rerolled Summary</span> <span class="text-sm text-zinc-400"
>{language.hypaV3Modal.rerolledSummaryLabel}</span
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Translate Rerolled Button --> <!-- Translate Rerolled Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
use:handleDualAction={{ use:handleDualAction={{
onMainAction: () => toggleTranslateRerolled(i, false), onMainAction: () =>
toggleTranslateRerolled(i, false),
onAlternativeAction: () => onAlternativeAction: () =>
toggleTranslateRerolled(i, true), toggleTranslateRerolled(i, true),
}} }}
@@ -1025,6 +1159,7 @@
<!-- Cancel Button --> <!-- Cancel Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
onclick={() => { onclick={() => {
summaryUIStates[i].rerolledText = null; summaryUIStates[i].rerolledText = null;
summaryUIStates[i].rerolledTranslation = null; summaryUIStates[i].rerolledTranslation = null;
@@ -1036,6 +1171,7 @@
<!-- Apply Button --> <!-- Apply Button -->
<button <button
class="p-2 text-zinc-400 hover:text-rose-300 transition-colors" class="p-2 text-zinc-400 hover:text-rose-300 transition-colors"
tabindex="-1"
onclick={() => { onclick={() => {
summary.text = summaryUIStates[i].rerolledText!; summary.text = summaryUIStates[i].rerolledText!;
summaryUIStates[i].translation = null; summaryUIStates[i].translation = null;
@@ -1053,6 +1189,7 @@
<div class="mt-2 sm:mt-4"> <div class="mt-2 sm:mt-4">
<textarea <textarea
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-colors text-zinc-200 bg-zinc-900" class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-colors text-zinc-200 bg-zinc-900"
tabindex="-1"
bind:value={summaryUIStates[i].rerolledText} bind:value={summaryUIStates[i].rerolledText}
> >
</textarea> </textarea>
@@ -1062,14 +1199,14 @@
{#if summaryUIStates[i].rerolledTranslation} {#if summaryUIStates[i].rerolledTranslation}
<div class="mt-2 sm:mt-4"> <div class="mt-2 sm:mt-4">
<div class="mb-2 sm:mb-4 text-sm text-zinc-400"> <div class="mb-2 sm:mb-4 text-sm text-zinc-400">
Rerolled Translation {language.hypaV3Modal.rerolledTranslationLabel}
</div> </div>
<textarea <textarea
readonly
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900" class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
bind:this={summaryUIStates[i].rerolledTranslationRef} readonly
tabindex="-1" tabindex="-1"
bind:this={summaryUIStates[i].rerolledTranslationRef}
value={summaryUIStates[i].rerolledTranslation} value={summaryUIStates[i].rerolledTranslation}
></textarea> ></textarea>
</div> </div>
@@ -1080,13 +1217,17 @@
<div class="mt-2 sm:mt-4"> <div class="mt-2 sm:mt-4">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-sm text-zinc-400" <span class="text-sm text-zinc-400"
>Connected Messages ({summary.chatMemos.length})</span >{language.hypaV3Modal.connectedMessageCountLabel.replace(
"{0}",
summary.chatMemos.length.toString()
)}</span
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Translate Message Button --> <!-- Translate Message Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
tabindex="-1"
use:handleDualAction={{ use:handleDualAction={{
onMainAction: () => onMainAction: () =>
toggleTranslateExpandedMessage(false), toggleTranslateExpandedMessage(false),
@@ -1110,10 +1251,13 @@
) )
? 'ring-2 ring-zinc-500' ? 'ring-2 ring-zinc-500'
: ''}" : ''}"
tabindex="-1"
bind:this={summaryUIStates[i].chatMemoRefs[memoIndex]} bind:this={summaryUIStates[i].chatMemoRefs[memoIndex]}
onclick={() => toggleExpandMessage(i, chatMemo)} onclick={() => toggleExpandMessage(i, chatMemo)}
> >
{chatMemo == null ? "First Message" : chatMemo} {chatMemo == null
? language.hypaV3Modal.connectedFirstMessageLabel
: chatMemo}
</button> </button>
{/each} {/each}
</div> </div>
@@ -1126,22 +1270,31 @@
{#if expandedMessage} {#if expandedMessage}
<!-- Role --> <!-- Role -->
<div class="mb-2 sm:mb-4 text-sm text-zinc-400"> <div class="mb-2 sm:mb-4 text-sm text-zinc-400">
{expandedMessage.role}'s Message {language.hypaV3Modal.connectedMessageRoleLabel.replace(
"{0}",
expandedMessage.role
)}
</div> </div>
<!-- Content --> <!-- Content -->
<textarea <textarea
readonly
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900" class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
readonly
tabindex="-1"
value={expandedMessage.data} value={expandedMessage.data}
></textarea> ></textarea>
{:else} {:else}
<span class="text-sm text-red-400">Message not found</span <span class="text-sm text-red-400"
>{language.hypaV3Modal
.connectedMessageNotFoundLabel}</span
> >
{/if} {/if}
{:catch error} {:catch error}
<span class="text-sm text-red-400" <span class="text-sm text-red-400"
>Error loading expanded message: {error.message}</span >{language.hypaV3Modal.connectedMessageLoadingError.replace(
"{0}",
error.message
)}</span
> >
{/await} {/await}
</div> </div>
@@ -1150,14 +1303,14 @@
{#if expandedMessageUIState.translation} {#if expandedMessageUIState.translation}
<div class="mt-2 sm:mt-4"> <div class="mt-2 sm:mt-4">
<div class="mb-2 sm:mb-4 text-sm text-zinc-400"> <div class="mb-2 sm:mb-4 text-sm text-zinc-400">
Translation {language.hypaV3Modal.connectedMessageTranslationLabel}
</div> </div>
<textarea <textarea
readonly
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900" class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
bind:this={expandedMessageUIState.translationRef} readonly
tabindex="-1" tabindex="-1"
bind:this={expandedMessageUIState.translationRef}
value={expandedMessageUIState.translation} value={expandedMessageUIState.translation}
></textarea> ></textarea>
</div> </div>
@@ -1165,6 +1318,7 @@
{/if} {/if}
</div> </div>
{/if} {/if}
{/if}
{/each} {/each}
<!-- Next Summarization Target --> <!-- Next Summarization Target -->
@@ -1173,25 +1327,34 @@
{#if nextMessage} {#if nextMessage}
{@const chatId = {@const chatId =
nextMessage.chatId === "first" nextMessage.chatId === "first"
? "First Message" ? language.hypaV3Modal.nextSummarizationFirstMessageLabel
: nextMessage.chatId == null : nextMessage.chatId == null
? "No Message ID" ? language.hypaV3Modal.nextSummarizationNoMessageIdLabel
: nextMessage.chatId} : nextMessage.chatId}
<div class="mb-2 sm:mb-4 text-sm text-zinc-400"> <div class="mb-2 sm:mb-4 text-sm text-zinc-400">
HypaV3 will summarize [{chatId}] {language.hypaV3Modal.nextSummarizationLabel.replace(
"{0}",
chatId
)}
</div> </div>
<textarea <textarea
readonly
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-none overflow-y-auto rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900" class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-none overflow-y-auto rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
readonly
value={nextMessage.data} value={nextMessage.data}
></textarea> ></textarea>
{:else} {:else}
<span class="text-sm text-red-400">WARN: No messages found</span> <span class="text-sm text-red-400"
>{language.hypaV3Modal
.nextSummarizationNoMessagesFoundLabel}</span
>
{/if} {/if}
{:catch error} {:catch error}
<span class="text-sm text-red-400" <span class="text-sm text-red-400"
>Error loading next message: {error.message}</span >{language.hypaV3Modal.nextSummarizationLoadingError.replace(
"{0}",
error.message
)}</span
> >
{/await} {/await}
</div> </div>
@@ -1200,7 +1363,7 @@
{#if !getFirstMessage()} {#if !getFirstMessage()}
<div class="mt-2 sm:mt-4"> <div class="mt-2 sm:mt-4">
<span class="text-sm text-red-400" <span class="text-sm text-red-400"
>WARN: Selected first message is empty</span >{language.hypaV3Modal.emptySelectedFirstMessageLabel}</span
> >
</div> </div>
{/if} {/if}

View File

@@ -467,6 +467,15 @@
DBState.db.hypav2 = false DBState.db.hypav2 = false
DBState.db.hanuraiEnable = false DBState.db.hanuraiEnable = false
DBState.db.hypaV3 = true DBState.db.hypaV3 = true
DBState.db.hypaV3Settings.memoryTokensRatio = 0.2
DBState.db.hypaV3Settings.extraSummarizationRatio = 0
DBState.db.hypaV3Settings.maxChatsPerSummary = 4
DBState.db.hypaV3Settings.recentMemoryRatio = 0.4
DBState.db.hypaV3Settings.similarMemoryRatio = 0.4
DBState.db.hypaV3Settings.enableSimilarityCorrection = false
DBState.db.hypaV3Settings.preserveOrphanedMemory = false
DBState.db.hypaV3Settings.processRegexScript = false
DBState.db.hypaV3Settings.doNotSummarizeUserMessage = false
} else { } else {
DBState.db.supaModelType = 'none' DBState.db.supaModelType = 'none'
DBState.db.memoryAlgorithmType = 'none' DBState.db.memoryAlgorithmType = 'none'
@@ -508,46 +517,45 @@
<span class="text-textcolor">{language.hypaAllocatedTokens}</span> <span class="text-textcolor">{language.hypaAllocatedTokens}</span>
<NumberInput size="sm" marginBottom bind:value={DBState.db.hypaAllocatedTokens} min={100} /> <NumberInput size="sm" marginBottom bind:value={DBState.db.hypaAllocatedTokens} min={100} />
{:else if DBState.db.hypaV3} {:else if DBState.db.hypaV3}
<span class="mb-2 text-textcolor2 text-sm text-wrap break-words max-w-full">{language.hypaV3Desc}</span> <span class="mb-2 text-textcolor2 text-sm text-wrap break-words max-w-full">{language.hypaV3Settings.descriptionLabel}</span>
<span class="text-textcolor mt-4">{language.SuperMemory} {language.model}</span> <span class="text-textcolor mt-4">{language.SuperMemory} {language.model}</span>
<SelectInput className="mt-2 mb-2" bind:value={DBState.db.supaModelType}> <SelectInput className="mt-2 mb-2" bind:value={DBState.db.supaModelType}>
<OptionInput value="distilbart">distilbart-cnn-6-6 (Free/Local)</OptionInput> <OptionInput value="distilbart">distilbart-cnn-6-6 (Free/Local)</OptionInput>
<OptionInput value="subModel">{language.submodel}</OptionInput> <OptionInput value="subModel">{language.submodel}</OptionInput>
</SelectInput> </SelectInput>
{#if DBState.db.supaModelType === "instruct35"}
<span class="text-textcolor">OpenAI API Key</span>
<TextInput marginBottom size="sm" bind:value={DBState.db.supaMemoryKey} />
{/if}
<span class="text-textcolor">{language.summarizationPrompt} <Help key="summarizationPrompt"/></span> <span class="text-textcolor">{language.summarizationPrompt} <Help key="summarizationPrompt"/></span>
<div class="mb-2"> <div class="mb-2">
<TextAreaInput size="sm" placeholder="Leave it blank to use default" bind:value={DBState.db.supaMemoryPrompt} /> <TextAreaInput size="sm" placeholder={language.hypaV3Settings.supaMemoryPromptPlaceHolder} bind:value={DBState.db.supaMemoryPrompt} />
</div> </div>
<span class="text-textcolor">Max Memory Tokens Ratio (Estimated)</span>
{#await getMaxMemoryRatio() then maxMemoryRatio} {#await getMaxMemoryRatio() then maxMemoryRatio}
<span class="text-textcolor">{language.hypaV3Settings.maxMemoryTokensRatioLabel}</span>
<NumberInput marginBottom disabled size="sm" value={maxMemoryRatio} /> <NumberInput marginBottom disabled size="sm" value={maxMemoryRatio} />
{:catch error} {:catch error}
<span class="text-textcolor">{error}</span> <span class="text-red-400">{language.hypaV3Settings.maxMemoryTokensRatioError}</span>
{/await} {/await}
<span class="text-textcolor">Memory Tokens Ratio</span> <span class="text-textcolor">{language.hypaV3Settings.memoryTokensRatioLabel}</span>
<SliderInput marginBottom min={0} max={1} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.memoryTokensRatio} /> <SliderInput marginBottom min={0} max={1} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.memoryTokensRatio} />
<span class="text-textcolor">Extra Summarization Ratio</span> <span class="text-textcolor">{language.hypaV3Settings.extraSummarizationRatioLabel}</span>
<SliderInput marginBottom min={0} max={1 - DBState.db.hypaV3Settings.memoryTokensRatio} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.extraSummarizationRatio} /> <SliderInput marginBottom min={0} max={1 - DBState.db.hypaV3Settings.memoryTokensRatio} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.extraSummarizationRatio} />
<span class="text-textcolor">Max Chats Per Summary</span> <span class="text-textcolor">{language.hypaV3Settings.maxChatsPerSummaryLabel}</span>
<NumberInput marginBottom size="sm" min={1} bind:value={DBState.db.hypaV3Settings.maxChatsPerSummary} /> <NumberInput marginBottom size="sm" min={1} bind:value={DBState.db.hypaV3Settings.maxChatsPerSummary} />
<span class="text-textcolor">Recent Memory Ratio</span> <span class="text-textcolor">{language.hypaV3Settings.recentMemoryRatioLabel}</span>
<SliderInput marginBottom min={0} max={1} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.recentMemoryRatio} /> <SliderInput marginBottom min={0} max={1} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.recentMemoryRatio} />
<span class="text-textcolor">Similar Memory Ratio</span> <span class="text-textcolor">{language.hypaV3Settings.similarMemoryRatioLabel}</span>
<SliderInput marginBottom min={0} max={1} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.similarMemoryRatio} /> <SliderInput marginBottom min={0} max={1} step={0.01} fixed={2} bind:value={DBState.db.hypaV3Settings.similarMemoryRatio} />
<span class="text-textcolor">Random Memory Ratio</span> <span class="text-textcolor">{language.hypaV3Settings.randomMemoryRatioLabel}</span>
<NumberInput marginBottom disabled size="sm" value={parseFloat((1 - DBState.db.hypaV3Settings.recentMemoryRatio - DBState.db.hypaV3Settings.similarMemoryRatio).toFixed(2))} /> <NumberInput marginBottom disabled size="sm" value={parseFloat((1 - DBState.db.hypaV3Settings.recentMemoryRatio - DBState.db.hypaV3Settings.similarMemoryRatio).toFixed(2))} />
<div class="flex mb-2"> <div class="flex mb-2">
<Check name="Enable Similarity Correction" bind:check={DBState.db.hypaV3Settings.enableSimilarityCorrection} /> <Check name={language.hypaV3Settings.enableSimilarityCorrectionLabel} bind:check={DBState.db.hypaV3Settings.enableSimilarityCorrection} />
</div> </div>
<div class="flex mb-2"> <div class="flex mb-2">
<Check name="Preserve Orphaned Memory" bind:check={DBState.db.hypaV3Settings.preserveOrphanedMemory} /> <Check name={language.hypaV3Settings.preserveOrphanedMemoryLabel} bind:check={DBState.db.hypaV3Settings.preserveOrphanedMemory} />
</div> </div>
<div class="flex mb-2"> <div class="flex mb-2">
<Check name="Process Regex Script (Reroll Only)" bind:check={DBState.db.hypaV3Settings.processRegexScript} /> <Check name={language.hypaV3Settings.applyRegexScriptWhenRerollingLabel} bind:check={DBState.db.hypaV3Settings.processRegexScript} />
</div>
<div class="flex mb-2">
<Check name={language.hypaV3Settings.doNotSummarizeUserMessageLabel} bind:check={DBState.db.hypaV3Settings.doNotSummarizeUserMessage} />
</div> </div>
{:else if (DBState.db.supaModelType !== 'none' && DBState.db.hypav2 === false && DBState.db.hypaV3 === false)} {:else if (DBState.db.supaModelType !== 'none' && DBState.db.hypav2 === false && DBState.db.hypaV3 === false)}
<span class="mb-2 text-textcolor2 text-sm text-wrap break-words max-w-full">{language.supaDesc}</span> <span class="mb-2 text-textcolor2 text-sm text-wrap break-words max-w-full">{language.supaDesc}</span>

View File

@@ -421,6 +421,12 @@ export async function hypaMemoryV3(
continue; continue;
} }
if (db.hypaV3Settings.doNotSummarizeUserMessage && chat.role === "user") {
console.log(`[HypaV3] Skipping user role at index ${i}`);
continue;
}
toSummarize.push(chat); toSummarize.push(chat);
} }
@@ -436,6 +442,7 @@ export async function hypaMemoryV3(
} }
// Attempt summarization // Attempt summarization
if (toSummarize.length > 0) {
const summarizeResult = await retryableSummarize(toSummarize); const summarizeResult = await retryableSummarize(toSummarize);
if (!summarizeResult.success) { if (!summarizeResult.success) {
@@ -452,6 +459,7 @@ export async function hypaMemoryV3(
chatMemos: new Set(toSummarize.map((chat) => chat.memo)), chatMemos: new Set(toSummarize.map((chat) => chat.memo)),
isImportant: false, isImportant: false,
}); });
}
currentTokens -= toSummarizeTokens; currentTokens -= toSummarizeTokens;
startIdx = endIdx; startIdx = endIdx;
@@ -469,6 +477,37 @@ export async function hypaMemoryV3(
availableMemoryTokens availableMemoryTokens
); );
// Early return if no summaries
if (data.summaries.length === 0) {
// Generate final memory prompt
const memory = encapsulateMemoryPrompt("");
const newChats: OpenAIChat[] = [
{
role: "system",
content: memory,
memo: "supaMemory",
},
...chats.slice(startIdx),
];
console.log(
"[HypaV3] Exiting function:",
"\nCurrent Tokens:",
currentTokens,
"\nAll chats, including memory prompt:",
newChats,
"\nMemory Data:",
data
);
return {
currentTokens,
chats: newChats,
memory: toSerializableHypaV3Data(data),
};
}
const selectedSummaries: Summary[] = []; const selectedSummaries: Summary[] = [];
const randomMemoryRatio = const randomMemoryRatio =
1 - 1 -

View File

@@ -474,13 +474,14 @@ export function setDatabase(data:Database){
data.reasoningEffort ??= 0 data.reasoningEffort ??= 0
data.hypaV3Settings = { data.hypaV3Settings = {
memoryTokensRatio: data.hypaV3Settings?.memoryTokensRatio ?? 0.2, memoryTokensRatio: data.hypaV3Settings?.memoryTokensRatio ?? 0.2,
extraSummarizationRatio: data.hypaV3Settings?.extraSummarizationRatio ?? 0.2, extraSummarizationRatio: data.hypaV3Settings?.extraSummarizationRatio ?? 0,
maxChatsPerSummary: data.hypaV3Settings?.maxChatsPerSummary ?? 4, maxChatsPerSummary: data.hypaV3Settings?.maxChatsPerSummary ?? 4,
recentMemoryRatio: data.hypaV3Settings?.recentMemoryRatio ?? 0.4, recentMemoryRatio: data.hypaV3Settings?.recentMemoryRatio ?? 0.4,
similarMemoryRatio: data.hypaV3Settings?.similarMemoryRatio ?? 0.4, similarMemoryRatio: data.hypaV3Settings?.similarMemoryRatio ?? 0.4,
enableSimilarityCorrection: data.hypaV3Settings?.enableSimilarityCorrection ?? false, enableSimilarityCorrection: data.hypaV3Settings?.enableSimilarityCorrection ?? false,
preserveOrphanedMemory: data.hypaV3Settings?.preserveOrphanedMemory ?? false, preserveOrphanedMemory: data.hypaV3Settings?.preserveOrphanedMemory ?? false,
processRegexScript: data.hypaV3Settings?.processRegexScript ?? false processRegexScript: data.hypaV3Settings?.processRegexScript ?? false,
doNotSummarizeUserMessage: data.hypaV3Settings?.doNotSummarizeUserMessage ?? false
} }
changeLanguage(data.language) changeLanguage(data.language)
setDatabaseLite(data) setDatabaseLite(data)
@@ -894,6 +895,7 @@ export interface Database{
enableSimilarityCorrection: boolean enableSimilarityCorrection: boolean
preserveOrphanedMemory: boolean preserveOrphanedMemory: boolean
processRegexScript: boolean processRegexScript: boolean
doNotSummarizeUserMessage: boolean
}, },
OaiCompAPIKeys: {[key:string]:string} OaiCompAPIKeys: {[key:string]:string}
inlayErrorResponse:boolean inlayErrorResponse:boolean