Spaces:
Running
Running
Upload 3 files
Browse files- kimi-js/kimi-memory-system.js +20 -0
- kimi-js/kimi-videos.js +174 -28
- kimi-js/kimi-voices.js +43 -0
kimi-js/kimi-memory-system.js
CHANGED
|
@@ -1162,6 +1162,26 @@ class KimiMemorySystem {
|
|
| 1162 |
}, 500);
|
| 1163 |
}
|
| 1164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
async getMemoriesByCategory(category, character = null) {
|
| 1166 |
if (!this.db) return [];
|
| 1167 |
|
|
|
|
| 1162 |
}, 500);
|
| 1163 |
}
|
| 1164 |
|
| 1165 |
+
// Add cleanup method for memory system
|
| 1166 |
+
cleanup() {
|
| 1167 |
+
if (this.contextUpdateTimeout) {
|
| 1168 |
+
clearTimeout(this.contextUpdateTimeout);
|
| 1169 |
+
this.contextUpdateTimeout = null;
|
| 1170 |
+
}
|
| 1171 |
+
|
| 1172 |
+
// Clear caches to prevent memory leaks
|
| 1173 |
+
if (this.keywordCache) {
|
| 1174 |
+
this.keywordCache.clear();
|
| 1175 |
+
}
|
| 1176 |
+
|
| 1177 |
+
// Reset stats arrays to prevent accumulation
|
| 1178 |
+
if (this.queryStats) {
|
| 1179 |
+
this.queryStats.extractionTime.length = 0;
|
| 1180 |
+
this.queryStats.addMemoryTime.length = 0;
|
| 1181 |
+
this.queryStats.retrievalTime.length = 0;
|
| 1182 |
+
}
|
| 1183 |
+
}
|
| 1184 |
+
|
| 1185 |
async getMemoriesByCategory(category, character = null) {
|
| 1186 |
if (!this.db) return [];
|
| 1187 |
|
kimi-js/kimi-videos.js
CHANGED
|
@@ -250,13 +250,13 @@ class KimiVideoManager {
|
|
| 250 |
}
|
| 251 |
|
| 252 |
_logDebug(message, payload = null) {
|
| 253 |
-
if (!this._debug) return;
|
| 254 |
if (payload) console.log("π¬ VideoManager:", message, payload);
|
| 255 |
else console.log("π¬ VideoManager:", message);
|
| 256 |
}
|
| 257 |
|
| 258 |
_logSelection(category, selectedSrc, candidates = []) {
|
| 259 |
-
if (!this._debug) return;
|
| 260 |
const recent = (this.playHistory && this.playHistory[category]) || [];
|
| 261 |
const adaptive = typeof this.getAdaptiveHistorySize === "function" ? this.getAdaptiveHistorySize(category) : null;
|
| 262 |
console.log("π¬ VideoManager: selection", {
|
|
@@ -269,7 +269,7 @@ class KimiVideoManager {
|
|
| 269 |
}
|
| 270 |
|
| 271 |
debugPrintHistory(category = null) {
|
| 272 |
-
if (!this._debug) return;
|
| 273 |
if (!this.playHistory) {
|
| 274 |
console.log("π¬ VideoManager: no play history yet");
|
| 275 |
return;
|
|
@@ -800,7 +800,17 @@ class KimiVideoManager {
|
|
| 800 |
|
| 801 |
// Neutral: on end, pick another neutral to avoid static last frame
|
| 802 |
if (context === "neutral") {
|
| 803 |
-
this._globalEndedHandler = () =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
this.activeVideo.addEventListener("ended", this._globalEndedHandler, { once: true });
|
| 805 |
}
|
| 806 |
}
|
|
@@ -828,27 +838,46 @@ class KimiVideoManager {
|
|
| 828 |
candidateVideos = availableVideos.filter(video => video !== currentVideoSrc && this._isVideoSafe(video));
|
| 829 |
}
|
| 830 |
|
| 831 |
-
// If still no safe videos, use any available (excluding current)
|
| 832 |
if (candidateVideos.length === 0) {
|
| 833 |
candidateVideos = availableVideos.filter(video => video !== currentVideoSrc);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
}
|
| 835 |
|
| 836 |
// Ultimate fallback - use all available
|
| 837 |
if (candidateVideos.length === 0) {
|
| 838 |
candidateVideos = availableVideos;
|
|
|
|
|
|
|
| 839 |
}
|
| 840 |
|
| 841 |
-
// Final fallback to neutral category if current category is empty
|
| 842 |
-
if (candidateVideos.length === 0) {
|
|
|
|
| 843 |
const neutralVideos = this.videoCategories.neutral || [];
|
| 844 |
candidateVideos = neutralVideos.filter(video => this._isVideoSafe(video));
|
| 845 |
if (candidateVideos.length === 0) {
|
| 846 |
candidateVideos = neutralVideos; // Last resort
|
|
|
|
|
|
|
| 847 |
}
|
| 848 |
}
|
| 849 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
// If traits and affection are provided, weight the selection more subtly
|
| 851 |
-
if (traits && typeof affection === "number") {
|
| 852 |
let weights = candidateVideos.map(video => {
|
| 853 |
if (category === "speakingPositive") {
|
| 854 |
// Positive videos favored by affection, romance, and humor
|
|
@@ -1019,7 +1048,7 @@ class KimiVideoManager {
|
|
| 1019 |
}
|
| 1020 |
|
| 1021 |
returnToNeutral() {
|
| 1022 |
-
// Always ensure we resume
|
| 1023 |
if (this._neutralLock) return;
|
| 1024 |
this._neutralLock = true;
|
| 1025 |
setTimeout(() => {
|
|
@@ -1030,39 +1059,79 @@ class KimiVideoManager {
|
|
| 1030 |
this.isEmotionVideoPlaying = false;
|
| 1031 |
this.currentEmotionContext = null;
|
| 1032 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1033 |
// Si la voix est encore en cours, relancer une vidΓ©o neutre en boucle
|
| 1034 |
const category = "neutral";
|
| 1035 |
const currentVideoSrc = this.activeVideo.querySelector("source").getAttribute("src");
|
| 1036 |
const available = this.videoCategories[category] || [];
|
| 1037 |
let nextSrc = null;
|
|
|
|
| 1038 |
if (available.length > 0) {
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
|
| 1043 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1044 |
}
|
|
|
|
| 1045 |
if (nextSrc) {
|
| 1046 |
this.loadAndSwitchVideo(nextSrc, "normal");
|
| 1047 |
if (typeof this.updatePlayHistory === "function") this.updatePlayHistory(category, nextSrc);
|
| 1048 |
this.currentContext = "neutral";
|
| 1049 |
this.currentEmotion = "neutral";
|
| 1050 |
this.lastSwitchTime = Date.now();
|
| 1051 |
-
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1063 |
} else {
|
| 1064 |
-
//
|
| 1065 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1066 |
}
|
| 1067 |
}
|
| 1068 |
|
|
@@ -1778,10 +1847,87 @@ class KimiVideoManager {
|
|
| 1778 |
this._loadingInProgress = false;
|
| 1779 |
this._switchInProgress = false;
|
| 1780 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1781 |
}
|
| 1782 |
|
| 1783 |
// Expose globally for code that expects a window-level KimiVideoManager
|
| 1784 |
window.KimiVideoManager = KimiVideoManager;
|
| 1785 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
// Also provide ES module exports for modern imports
|
| 1787 |
export { KimiVideoManager };
|
|
|
|
| 250 |
}
|
| 251 |
|
| 252 |
_logDebug(message, payload = null) {
|
| 253 |
+
if (!this._debug && !window.KIMI_CONFIG?.DEBUG?.VIDEO) return;
|
| 254 |
if (payload) console.log("π¬ VideoManager:", message, payload);
|
| 255 |
else console.log("π¬ VideoManager:", message);
|
| 256 |
}
|
| 257 |
|
| 258 |
_logSelection(category, selectedSrc, candidates = []) {
|
| 259 |
+
if (!this._debug && !window.KIMI_CONFIG?.DEBUG?.VIDEO) return;
|
| 260 |
const recent = (this.playHistory && this.playHistory[category]) || [];
|
| 261 |
const adaptive = typeof this.getAdaptiveHistorySize === "function" ? this.getAdaptiveHistorySize(category) : null;
|
| 262 |
console.log("π¬ VideoManager: selection", {
|
|
|
|
| 269 |
}
|
| 270 |
|
| 271 |
debugPrintHistory(category = null) {
|
| 272 |
+
if (!this._debug && !window.KIMI_CONFIG?.DEBUG?.VIDEO) return;
|
| 273 |
if (!this.playHistory) {
|
| 274 |
console.log("π¬ VideoManager: no play history yet");
|
| 275 |
return;
|
|
|
|
| 800 |
|
| 801 |
// Neutral: on end, pick another neutral to avoid static last frame
|
| 802 |
if (context === "neutral") {
|
| 803 |
+
this._globalEndedHandler = () => {
|
| 804 |
+
// Additional safety: ensure we're still in neutral context before looping
|
| 805 |
+
if (this.currentContext === "neutral") {
|
| 806 |
+
this.returnToNeutral();
|
| 807 |
+
} else {
|
| 808 |
+
// Context changed, don't loop back to neutral
|
| 809 |
+
if (window.KIMI_CONFIG?.DEBUG?.VIDEO) {
|
| 810 |
+
console.log("π¬ Context changed from neutral, skipping auto-loop");
|
| 811 |
+
}
|
| 812 |
+
}
|
| 813 |
+
};
|
| 814 |
this.activeVideo.addEventListener("ended", this._globalEndedHandler, { once: true });
|
| 815 |
}
|
| 816 |
}
|
|
|
|
| 838 |
candidateVideos = availableVideos.filter(video => video !== currentVideoSrc && this._isVideoSafe(video));
|
| 839 |
}
|
| 840 |
|
| 841 |
+
// If still no safe videos, use any available (excluding current) - ignore safety temporarily
|
| 842 |
if (candidateVideos.length === 0) {
|
| 843 |
candidateVideos = availableVideos.filter(video => video !== currentVideoSrc);
|
| 844 |
+
// Reset safety flags for this category since we're desperate
|
| 845 |
+
if (candidateVideos.length > 0) {
|
| 846 |
+
console.warn(`π¬ All videos in category '${category}' marked unsafe, resetting safety flags`);
|
| 847 |
+
candidateVideos.forEach(video => this._recentFailures.delete(video));
|
| 848 |
+
}
|
| 849 |
}
|
| 850 |
|
| 851 |
// Ultimate fallback - use all available
|
| 852 |
if (candidateVideos.length === 0) {
|
| 853 |
candidateVideos = availableVideos;
|
| 854 |
+
// Reset all safety flags for this category
|
| 855 |
+
availableVideos.forEach(video => this._recentFailures.delete(video));
|
| 856 |
}
|
| 857 |
|
| 858 |
+
// Final fallback to neutral category if current category is empty or exhausted
|
| 859 |
+
if (candidateVideos.length === 0 && category !== "neutral") {
|
| 860 |
+
console.warn(`π¬ Category '${category}' exhausted, falling back to neutral`);
|
| 861 |
const neutralVideos = this.videoCategories.neutral || [];
|
| 862 |
candidateVideos = neutralVideos.filter(video => this._isVideoSafe(video));
|
| 863 |
if (candidateVideos.length === 0) {
|
| 864 |
candidateVideos = neutralVideos; // Last resort
|
| 865 |
+
// Reset safety flags for neutral videos too
|
| 866 |
+
neutralVideos.forEach(video => this._recentFailures.delete(video));
|
| 867 |
}
|
| 868 |
}
|
| 869 |
|
| 870 |
+
// Critical error protection: if we still have no candidates, force emergency state
|
| 871 |
+
if (candidateVideos.length === 0) {
|
| 872 |
+
console.error(`π¬ CRITICAL: No videos available for category '${category}' or neutral fallback!`);
|
| 873 |
+
// Clear ALL safety flags as emergency measure
|
| 874 |
+
this._recentFailures.clear();
|
| 875 |
+
// Try to use any video from the original category
|
| 876 |
+
candidateVideos = availableVideos.length > 0 ? availableVideos : this.videoCategories.neutral || [];
|
| 877 |
+
}
|
| 878 |
+
|
| 879 |
// If traits and affection are provided, weight the selection more subtly
|
| 880 |
+
if (traits && typeof affection === "number" && candidateVideos.length > 1) {
|
| 881 |
let weights = candidateVideos.map(video => {
|
| 882 |
if (category === "speakingPositive") {
|
| 883 |
// Positive videos favored by affection, romance, and humor
|
|
|
|
| 1048 |
}
|
| 1049 |
|
| 1050 |
returnToNeutral() {
|
| 1051 |
+
// Always ensure we resume playbook with a fresh neutral video to avoid freeze
|
| 1052 |
if (this._neutralLock) return;
|
| 1053 |
this._neutralLock = true;
|
| 1054 |
setTimeout(() => {
|
|
|
|
| 1059 |
this.isEmotionVideoPlaying = false;
|
| 1060 |
this.currentEmotionContext = null;
|
| 1061 |
|
| 1062 |
+
// Clean up any existing ended handlers before proceeding
|
| 1063 |
+
if (this._globalEndedHandler) {
|
| 1064 |
+
this.activeVideo.removeEventListener("ended", this._globalEndedHandler);
|
| 1065 |
+
this.inactiveVideo.removeEventListener("ended", this._globalEndedHandler);
|
| 1066 |
+
this._globalEndedHandler = null;
|
| 1067 |
+
}
|
| 1068 |
+
|
| 1069 |
// Si la voix est encore en cours, relancer une vidΓ©o neutre en boucle
|
| 1070 |
const category = "neutral";
|
| 1071 |
const currentVideoSrc = this.activeVideo.querySelector("source").getAttribute("src");
|
| 1072 |
const available = this.videoCategories[category] || [];
|
| 1073 |
let nextSrc = null;
|
| 1074 |
+
|
| 1075 |
if (available.length > 0) {
|
| 1076 |
+
// First try: non-current videos that are safe
|
| 1077 |
+
let candidates = available.filter(v => v !== currentVideoSrc && this._isVideoSafe(v));
|
| 1078 |
+
|
| 1079 |
+
// Second try: any non-current video (ignore safety temporarily)
|
| 1080 |
+
if (candidates.length === 0) {
|
| 1081 |
+
candidates = available.filter(v => v !== currentVideoSrc);
|
| 1082 |
+
// Reset safety flags for neutral videos if all marked as unsafe
|
| 1083 |
+
if (candidates.length > 0) {
|
| 1084 |
+
candidates.forEach(video => this._recentFailures.delete(video));
|
| 1085 |
+
}
|
| 1086 |
+
}
|
| 1087 |
+
|
| 1088 |
+
// Third try: any video including current (ultimate fallback)
|
| 1089 |
+
if (candidates.length === 0) {
|
| 1090 |
+
candidates = [...available];
|
| 1091 |
+
// Reset all safety flags as emergency measure
|
| 1092 |
+
this._recentFailures.clear();
|
| 1093 |
+
}
|
| 1094 |
+
|
| 1095 |
+
nextSrc = candidates[Math.floor(Math.random() * candidates.length)];
|
| 1096 |
}
|
| 1097 |
+
|
| 1098 |
if (nextSrc) {
|
| 1099 |
this.loadAndSwitchVideo(nextSrc, "normal");
|
| 1100 |
if (typeof this.updatePlayHistory === "function") this.updatePlayHistory(category, nextSrc);
|
| 1101 |
this.currentContext = "neutral";
|
| 1102 |
this.currentEmotion = "neutral";
|
| 1103 |
this.lastSwitchTime = Date.now();
|
| 1104 |
+
|
| 1105 |
+
// Set up proper ended handler for continuous neutral playback
|
| 1106 |
+
this._globalEndedHandler = () => {
|
| 1107 |
+
// Double check we're still in neutral before looping
|
| 1108 |
+
if (this.currentContext === "neutral") {
|
| 1109 |
+
this.returnToNeutral();
|
| 1110 |
+
}
|
| 1111 |
+
};
|
| 1112 |
+
this.activeVideo.addEventListener("ended", this._globalEndedHandler, { once: true });
|
| 1113 |
+
|
| 1114 |
+
// Additional fallback: if video doesn't start playing in 5 seconds, retry
|
| 1115 |
+
setTimeout(() => {
|
| 1116 |
+
if (this.activeVideo.paused && this.currentContext === "neutral") {
|
| 1117 |
+
console.warn("π¬ Neutral video seems stuck, attempting recovery");
|
| 1118 |
+
this.returnToNeutral();
|
| 1119 |
+
}
|
| 1120 |
+
}, 5000);
|
| 1121 |
} else {
|
| 1122 |
+
// Emergency fallback: force reload the first available neutral video
|
| 1123 |
+
console.error("π¬ No neutral videos available, forcing emergency reload");
|
| 1124 |
+
if (available.length > 0) {
|
| 1125 |
+
this._recentFailures.clear(); // Clear all safety blocks
|
| 1126 |
+
this.loadAndSwitchVideo(available[0], "high");
|
| 1127 |
+
this.currentContext = "neutral";
|
| 1128 |
+
this.currentEmotion = "neutral";
|
| 1129 |
+
this.lastSwitchTime = Date.now();
|
| 1130 |
+
} else {
|
| 1131 |
+
// Critical error: try existing path as absolute last resort
|
| 1132 |
+
console.error("π¬ CRITICAL: No neutral videos defined!");
|
| 1133 |
+
this.switchToContext("neutral");
|
| 1134 |
+
}
|
| 1135 |
}
|
| 1136 |
}
|
| 1137 |
|
|
|
|
| 1847 |
this._loadingInProgress = false;
|
| 1848 |
this._switchInProgress = false;
|
| 1849 |
}
|
| 1850 |
+
|
| 1851 |
+
// Diagnostic method to check video system health
|
| 1852 |
+
_diagnoseVideoState() {
|
| 1853 |
+
const activeVideo = this.activeVideo;
|
| 1854 |
+
const inactiveVideo = this.inactiveVideo;
|
| 1855 |
+
|
| 1856 |
+
const diagnostics = {
|
| 1857 |
+
timestamp: new Date().toISOString(),
|
| 1858 |
+
currentContext: this.currentContext,
|
| 1859 |
+
currentEmotion: this.currentEmotion,
|
| 1860 |
+
activeVideo: {
|
| 1861 |
+
src: activeVideo?.querySelector("source")?.getAttribute("src") || "none",
|
| 1862 |
+
readyState: activeVideo?.readyState || "undefined",
|
| 1863 |
+
networkState: activeVideo?.networkState || "undefined",
|
| 1864 |
+
paused: activeVideo?.paused,
|
| 1865 |
+
ended: activeVideo?.ended,
|
| 1866 |
+
duration: activeVideo?.duration || "unknown"
|
| 1867 |
+
},
|
| 1868 |
+
inactiveVideo: {
|
| 1869 |
+
src: inactiveVideo?.querySelector("source")?.getAttribute("src") || "none",
|
| 1870 |
+
readyState: inactiveVideo?.readyState || "undefined",
|
| 1871 |
+
networkState: inactiveVideo?.networkState || "undefined"
|
| 1872 |
+
},
|
| 1873 |
+
systemState: {
|
| 1874 |
+
switchInProgress: this._switchInProgress,
|
| 1875 |
+
loadingInProgress: this._loadingInProgress,
|
| 1876 |
+
neutralLock: this._neutralLock,
|
| 1877 |
+
stickyContext: this._stickyContext,
|
| 1878 |
+
recentFailuresCount: this._recentFailures.size,
|
| 1879 |
+
consecutiveErrors: this._consecutiveErrorCount
|
| 1880 |
+
}
|
| 1881 |
+
};
|
| 1882 |
+
|
| 1883 |
+
return diagnostics;
|
| 1884 |
+
}
|
| 1885 |
+
|
| 1886 |
+
// Method to force video recovery when stuck
|
| 1887 |
+
_forceVideoRecovery() {
|
| 1888 |
+
if (window.KIMI_CONFIG?.DEBUG?.VIDEO) {
|
| 1889 |
+
console.warn("π¬ Forcing video recovery due to detected stall");
|
| 1890 |
+
}
|
| 1891 |
+
|
| 1892 |
+
// Log current state for debugging only if debug enabled
|
| 1893 |
+
const diagnostics = this._diagnoseVideoState();
|
| 1894 |
+
if (window.KIMI_CONFIG?.DEBUG?.VIDEO) {
|
| 1895 |
+
console.log("π¬ Video diagnostics:", diagnostics);
|
| 1896 |
+
}
|
| 1897 |
+
|
| 1898 |
+
// Clear any problematic state
|
| 1899 |
+
this._switchInProgress = false;
|
| 1900 |
+
this._loadingInProgress = false;
|
| 1901 |
+
this._neutralLock = false;
|
| 1902 |
+
|
| 1903 |
+
// Clear safety flags to allow all videos
|
| 1904 |
+
this._recentFailures.clear();
|
| 1905 |
+
|
| 1906 |
+
// Force return to neutral with high priority
|
| 1907 |
+
this.currentContext = "neutral";
|
| 1908 |
+
this.returnToNeutral();
|
| 1909 |
+
|
| 1910 |
+
return diagnostics;
|
| 1911 |
+
}
|
| 1912 |
}
|
| 1913 |
|
| 1914 |
// Expose globally for code that expects a window-level KimiVideoManager
|
| 1915 |
window.KimiVideoManager = KimiVideoManager;
|
| 1916 |
|
| 1917 |
+
// Expose diagnostic utilities globally for debugging
|
| 1918 |
+
window.kimiVideoDiagnostics = function () {
|
| 1919 |
+
if (window.videoManager && typeof window.videoManager._diagnoseVideoState === "function") {
|
| 1920 |
+
return window.videoManager._diagnoseVideoState();
|
| 1921 |
+
}
|
| 1922 |
+
return { error: "Video manager not available" };
|
| 1923 |
+
};
|
| 1924 |
+
|
| 1925 |
+
window.kimiVideoRecovery = function () {
|
| 1926 |
+
if (window.videoManager && typeof window.videoManager._forceVideoRecovery === "function") {
|
| 1927 |
+
return window.videoManager._forceVideoRecovery();
|
| 1928 |
+
}
|
| 1929 |
+
return { error: "Video manager not available" };
|
| 1930 |
+
};
|
| 1931 |
+
|
| 1932 |
// Also provide ES module exports for modern imports
|
| 1933 |
export { KimiVideoManager };
|
kimi-js/kimi-voices.js
CHANGED
|
@@ -1518,6 +1518,49 @@ class KimiVoiceManager {
|
|
| 1518 |
autoStopDuration: this.autoStopDuration
|
| 1519 |
};
|
| 1520 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1521 |
}
|
| 1522 |
|
| 1523 |
// Export for usage
|
|
|
|
| 1518 |
autoStopDuration: this.autoStopDuration
|
| 1519 |
};
|
| 1520 |
}
|
| 1521 |
+
|
| 1522 |
+
// Cleanup method to prevent memory leaks
|
| 1523 |
+
cleanup() {
|
| 1524 |
+
// Clean up permission listener if it exists
|
| 1525 |
+
if (this.permissionStatus && this.permissionChangeHandler) {
|
| 1526 |
+
try {
|
| 1527 |
+
this.permissionStatus.removeEventListener("change", this.permissionChangeHandler);
|
| 1528 |
+
} catch (e) {
|
| 1529 |
+
// Silent cleanup - permission API may not support removal
|
| 1530 |
+
}
|
| 1531 |
+
}
|
| 1532 |
+
|
| 1533 |
+
// Clean up timeouts
|
| 1534 |
+
if (this.listeningTimeout) {
|
| 1535 |
+
clearTimeout(this.listeningTimeout);
|
| 1536 |
+
this.listeningTimeout = null;
|
| 1537 |
+
}
|
| 1538 |
+
|
| 1539 |
+
if (this.transcriptHideTimeout) {
|
| 1540 |
+
clearTimeout(this.transcriptHideTimeout);
|
| 1541 |
+
this.transcriptHideTimeout = null;
|
| 1542 |
+
}
|
| 1543 |
+
|
| 1544 |
+
// Clean up recognition
|
| 1545 |
+
if (this.recognition) {
|
| 1546 |
+
try {
|
| 1547 |
+
this.recognition.abort();
|
| 1548 |
+
} catch (e) {
|
| 1549 |
+
// Silent cleanup
|
| 1550 |
+
}
|
| 1551 |
+
}
|
| 1552 |
+
|
| 1553 |
+
// Clean up mic button listener
|
| 1554 |
+
if (this.micButton && this.handleMicClick) {
|
| 1555 |
+
this.micButton.removeEventListener("click", this.handleMicClick);
|
| 1556 |
+
}
|
| 1557 |
+
|
| 1558 |
+
// Clean up voice select listener
|
| 1559 |
+
const voiceSelect = document.getElementById("voice-select");
|
| 1560 |
+
if (voiceSelect && this.voiceChangeHandler) {
|
| 1561 |
+
voiceSelect.removeEventListener("change", this.voiceChangeHandler);
|
| 1562 |
+
}
|
| 1563 |
+
}
|
| 1564 |
}
|
| 1565 |
|
| 1566 |
// Export for usage
|