@alanhelmick/memorable
Version:
An AI memory system enabling personalized, context-aware interactions through advanced memory management and emotional intelligence
3 lines (2 loc) • 26 kB
JavaScript
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),require("winston");var e=require("ws");require("redis"),require("mongodb");var t=require("os");function s(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}require("weaviate-client");var a=s(e),i=s(t);let o=null;const n={admiration:"#ffc58f",adoration:"#ffc6cc",aestheticAppreciation:"#e2cbff",amusement:"#febf52",anger:"#b21816",annoyance:"#ffffff",anxiety:"#6e42cc",awe:"#7dabd3",awkwardness:"#d7d99d",boredom:"#a4a4a4",calmness:"#a9cce1",concentration:"#336cff",contemplation:"#b0aeef",confusion:"#c66a26",contempt:"#76842d",contentment:"#e5c6b4",craving:"#54591c",determination:"#ff5c00",disappointment:"#006c7c",disapproval:"#ffffff",disgust:"#1a7a41",distress:"#c5f264",doubt:"#998644",ecstasy:"#ff48a4",embarrassment:"#63c653",empathicPain:"#ca5555",enthusiasm:"#ffffff",entrancement:"#7554d6",envy:"#1d4921",excitement:"#fff974",fear:"#d1c9ef",gratitude:"#ffffff",guilt:"#879aa1",horror:"#772e7a",interest:"#a9cce1",joy:"#ffd600",love:"#f44f4c",neutral:"#879aa1",nostalgia:"#b087a1",pain:"#8c1d1d",pride:"#9a4cb6",realization:"#217aa8",relief:"#fe927a",romance:"#f0cc86",sadness:"#305575",sarcasm:"#ffffff",satisfaction:"#a6ddaf",sexualDesire:"#aa0d59",shame:"#8a6262",surprise:"#70e63a",surpriseNegative:"#70e63a",surprisePositive:"#7affff",sympathy:"#7f88e0",tiredness:"#757575",triumph:"#ec8132"};Object.keys(n).length;const r=e=>{const t=n[e];return t?[parseInt(t.slice(1,3),16)/255,parseInt(t.slice(3,5),16)/255,parseInt(t.slice(5,7),16)/255]:null},c=e=>{if(!e||3!==e.length)return"neutral";const t=e=>{const t=Math.round(255*e).toString(16);return 1===t.length?"0"+t:t},s=`#${t(e[0])}${t(e[1])}${t(e[2])}`;let a="neutral",i=1/0;for(const[e,t]of Object.entries(n)){const o=l(s,t);o<i&&(i=o,a=e)}return a},l=(e,t)=>{const s=parseInt(e.slice(1,3),16),a=parseInt(e.slice(3,5),16),i=parseInt(e.slice(5,7),16),o=parseInt(t.slice(1,3),16),n=parseInt(t.slice(3,5),16),r=parseInt(t.slice(5,7),16);return Math.sqrt(Math.pow(s-o,2)+Math.pow(a-n,2)+Math.pow(i-r,2))},m=new class{constructor(){this.ws=null,this.apiKey=process.env.HUME_API_KEY,this.endpoint=process.env.HUME_ENDPOINT,this.isConnected=!1,this.activeStreams=new Map,this.messageQueue=[],this.processingQueue=!1,this.lastActivityTime=Date.now(),this.inactivityTimeout=6e4,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectDelay=1e3}async connect(e={}){if(!this.apiKey)throw Error("Hume API key not configured");return new Promise(((t,s)=>{try{const s=new URLSearchParams({apiKey:this.apiKey,...e}),i=`${this.endpoint}?${s.toString()}`;this.ws=new a.default(i),this.ws.on("open",(()=>{o.info("Connected to Hume.ai websocket"),this.isConnected=!0,this.reconnectAttempts=0,this.reconnectDelay=1e3,this.setupInactivityCheck(),t()})),this.ws.on("message",(e=>{this.lastActivityTime=Date.now(),this.handleMessage(e)})),this.ws.on("error",(e=>{o.error("Hume websocket error:",e),this.handleError(e)})),this.ws.on("close",(()=>{o.info("Hume websocket closed"),this.isConnected=!1,this.handleDisconnect()}))}catch(e){s(e)}}))}setupInactivityCheck(){setInterval((()=>{Date.now()-this.lastActivityTime>=this.inactivityTimeout&&(o.warn("WebSocket inactive, reconnecting..."),this.reconnect())}),1e4)}async reconnect(){this.reconnectAttempts<this.maxReconnectAttempts?(this.reconnectAttempts++,this.reconnectDelay*=2,o.info(`Attempting to reconnect in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempts})`),setTimeout((async()=>{try{await this.connect();for(const[e,t]of this.activeStreams)await this.startStream(e,t)}catch(e){o.error("Reconnection attempt failed:",e)}}),this.reconnectDelay)):o.error("Max reconnection attempts reached")}async startStream(e,t){this.isConnected||await this.connect();const s={models:t.models||{language:{},face:{},prosody:{}},raw_text:t.rawText||!0,reset_stream:t.resetStream||!1};this.activeStreams.set(e,{config:s,callbacks:new Map,buffer:[]}),await this.sendMessage({type:"stream_start",stream_id:e,config:s}),o.info("Started stream "+e)}async stopStream(e){this.activeStreams.get(e)&&(await this.sendMessage({type:"stream_end",stream_id:e}),this.activeStreams.delete(e),o.info("Stopped stream "+e))}async processText(e,t=null){const s=t||"text_"+Date.now();return t||await this.startStream(s,{models:{language:{}}}),this.sendData(s,{type:"text",data:e})}async processVoice(e,t=null){const s=t||"voice_"+Date.now();t||await this.startStream(s,{models:{prosody:{}}});const a=this.splitAudioIntoChunks(e),i=[];for(const e of a){const t=await this.sendData(s,{type:"prosody",data:e.toString("base64")});i.push(t)}return t||await this.stopStream(s),this.mergeResults(i)}async processFacial(e,t=null){const s=t||"face_"+Date.now();t||await this.startStream(s,{models:{face:{}}});const a=await this.sendData(s,{type:"face",data:e.toString("base64")});return t||await this.stopStream(s),a}splitAudioIntoChunks(e,t=5e3){const s=[];let a=0;for(;a<e.length;)s.push(e.slice(a,a+t)),a+=t;return s}mergeResults(e){const t={emotions:new Map};return e.forEach((e=>{e.emotions.forEach((e=>{const s=t.emotions.get(e.name)||{score:0,count:0};s.score+=e.score,s.count+=1,t.emotions.set(e.name,s)}))})),Array.from(t.emotions.entries()).map((([e,t])=>({name:e,score:t.score/t.count})))}async sendData(e,t){return new Promise(((s,a)=>{const i=Date.now().toString(),o=this.activeStreams.get(e);o?(o.callbacks.set(i,(e=>{e.error?a(Error(e.error)):s(this.processEmotions(e.emotions))})),this.sendMessage({id:i,stream_id:e,...t})):a(Error(`Stream ${e} not found`))}))}async sendMessage(e){if(!this.isConnected)throw Error("WebSocket not connected");this.messageQueue.push(e),this.processingQueue||await this.processMessageQueue()}async processMessageQueue(){for(this.processingQueue=!0;this.messageQueue.length>0;){const e=this.messageQueue.shift();try{this.ws.send(JSON.stringify(e)),this.lastActivityTime=Date.now(),await new Promise((e=>setTimeout(e,20)))}catch(t){o.error("Failed to send message:",t),this.messageQueue.unshift(e);break}}this.processingQueue=!1}handleMessage(e){try{const t=JSON.parse(e),s=this.activeStreams.get(t.stream_id);s&&t.id&&s.callbacks.has(t.id)&&(s.callbacks.get(t.id)(t),s.callbacks.delete(t.id))}catch(e){o.error("Error handling Hume message:",e)}}processEmotions(e){return e.map((e=>({name:e.name,score:e.score,vector:r(e.name),color:n[e.name],confidence:e.confidence||e.score}))).filter((e=>e.confidence>=.1)).sort(((e,t)=>t.score-e.score))}async close(){for(const e of this.activeStreams.keys())await this.stopStream(e);this.ws&&(this.ws.close(),this.ws=null,this.isConnected=!1,o.info("Hume websocket connection closed"))}},h=new class{constructor(){this.activeStreams=new Map,this.chunkDuration=5e3,this.maxResolution={width:3e3,height:3e3},this.processingInterval=1e3}async startStream(e,t,s={}){if(this.activeStreams.has(e))return void o.warn(`Stream ${e} is already active`);const a={id:e,buffer:[],lastProcessed:Date.now(),onUpdate:t,processingInterval:null,config:{resetStream:s.resetStream||!1,models:{face:s.faceConfig||{}},...s}};try{await m.startStream(e,a.config),a.processingInterval=setInterval((()=>this.processStreamBuffer(a)),this.processingInterval),this.activeStreams.set(e,a),o.info("Started video stream "+e)}catch(t){throw o.error(`Failed to start video stream ${e}:`,t),t}}async stopStream(e){const t=this.activeStreams.get(e);t?(t.processingInterval&&clearInterval(t.processingInterval),await this.processStreamBuffer(t),await m.stopStream(e),this.activeStreams.delete(e),o.info("Stopped video stream "+e)):o.warn(`Stream ${e} not found`)}async addFrame(e,t,s=Date.now()){const a=this.activeStreams.get(e);if(a)try{const e=await this.getFrameDimensions(t);if(!this.validateFrameDimensions(e))return void o.warn(`Frame dimensions exceed maximum (${e.width}x${e.height})`);a.buffer.push({data:t,timestamp:s}),this.trimBuffer(a)}catch(t){o.error(`Error adding frame to stream ${e}:`,t)}else o.warn(`Stream ${e} not found, frame discarded`)}async processStreamBuffer(e){if(0!==e.buffer.length)try{const t=Date.now(),s=t-this.chunkDuration,a=e.buffer.filter((e=>e.timestamp>=s));if(0===a.length)return;const i=this.selectBestFrame(a),o=await m.processFacial(i.data,e.id);e.onUpdate&&o.length>0&&e.onUpdate({streamId:e.id,timestamp:t,emotions:o,frameCount:a.length,selectedFrameTime:i.timestamp}),e.buffer=e.buffer.filter((e=>e.timestamp>s)),e.lastProcessed=t}catch(t){o.error(`Error processing stream ${e.id}:`,t)}}selectBestFrame(e){return e[Math.floor(e.length/2)]}trimBuffer(e){const t=Date.now();e.buffer=e.buffer.filter((e=>t-e.timestamp<=this.chunkDuration))}async getFrameDimensions(e){return{width:1280,height:720}}validateFrameDimensions(e){return e.width<=this.maxResolution.width&&e.height<=this.maxResolution.height}getStreamStatus(e){const t=this.activeStreams.get(e);return t?{id:t.id,isActive:!0,bufferSize:t.buffer.length,lastProcessed:t.lastProcessed,timeSinceLastProcess:Date.now()-t.lastProcessed,config:t.config}:null}getAllStreams(){return Array.from(this.activeStreams.keys()).map((e=>this.getStreamStatus(e)))}async cleanup(){for(const e of this.activeStreams.keys())await this.stopStream(e)}};function d(){throw Error("Redis client not initialized. Call setupRedis first.")}async function u(){}process.on("SIGTERM",(async()=>{o.info("SIGTERM received, closing Redis connection..."),await u()})),process.on("SIGINT",(async()=>{o.info("SIGINT received, closing Redis connection..."),await u()}));const f=new class{constructor(){this.redis=null,this.activeContexts=new Map,this.emotionalBuffer=new Map,this.bufferTimeout=5e3,this.customModelEnabled=!1,this.weights={evi:.5,video:.3,voice:.2}}async initialize(){this.redis=d(),await this.setupEmotionalBuffers(),await this.loadCustomModel()}async loadCustomModel(){try{if(await this.redis.exists("custom_model_config")){const e=JSON.parse(await this.redis.get("custom_model_config"));this.customModelEnabled=e.enabled,e.weights&&(this.weights=e.weights),o.info("Custom model configuration loaded")}}catch(e){o.error("Failed to load custom model:",e)}}async setupEmotionalBuffers(){try{const e="emotional_context_buffers";await this.redis.exists(e)||await this.redis.hSet(e,{active_sessions:"{}",buffer_timeouts:"{}"})}catch(e){throw o.error("Failed to setup emotional buffers:",e),e}}async startContext(e,t={}){const s={id:e,startTime:Date.now(),options:{useVideo:t.useVideo??!1,useVoice:t.useVoice??!0,useEVI:t.useEVI??!1,customModel:t.customModel??this.customModelEnabled,bufferSize:t.bufferSize??5,...t},emotionalState:{current:"neutral",confidence:1,vector:r("neutral"),history:[],sources:{}}};return this.activeContexts.set(e,s),s.options.useVideo&&await h.startStream(e,(t=>{this.handleVideoEmotion(e,t)}),{resetStream:!0,faceConfig:{minConfidence:.7,returnPoints:!0}}),s.options.useVoice&&!s.options.useEVI&&await m.startStream(e,{models:{prosody:{}},resetStream:!0}),o.info(`Started emotional context ${e} with options:`,s.options),s}async handleEVIEmotion(e,t){const s=this.activeContexts.get(e);s?(s.emotionalState.sources.evi={emotion:t.emotion,confidence:t.confidence,vector:t.vector,timestamp:Date.now()},await this.updateEmotionalState(e,{emotion:t.emotion,confidence:t.confidence,vector:t.vector,source:"evi",timestamp:Date.now()})):o.warn(`Context ${e} not found for EVI emotion`)}async handleVideoEmotion(e,t){const s=this.activeContexts.get(e);if(s){if(t.emotions.length>0){const a=t.emotions[0];s.emotionalState.sources.video={emotion:a.name,confidence:a.confidence,vector:a.vector,timestamp:t.timestamp},await this.updateEmotionalState(e,{emotion:a.name,confidence:a.confidence,vector:a.vector,source:"video",timestamp:t.timestamp})}}else o.warn(`Context ${e} not found for video emotion`)}async handleVoiceEmotion(e,t){const s=this.activeContexts.get(e);if(s)try{let a;if(s.options.useEVI)return;if(a=await m.processVoice(t,e),a.length>0){const t=a[0];s.emotionalState.sources.voice={emotion:t.name,confidence:t.confidence,vector:t.vector,timestamp:Date.now()},await this.updateEmotionalState(e,{emotion:t.name,confidence:t.confidence,vector:t.vector,source:"voice",timestamp:Date.now()})}}catch(t){o.error(`Error processing voice emotion for context ${e}:`,t)}else o.warn(`Context ${e} not found for voice emotion`)}async updateEmotionalState(e,t){const s=this.activeContexts.get(e);s&&(this.emotionalBuffer.has(e)||this.emotionalBuffer.set(e,[]),this.emotionalBuffer.get(e).push(t),this.emotionalBuffer.get(e).length>=s.options.bufferSize?await this.processEmotionalBuffer(e):setTimeout((async()=>{await this.processEmotionalBuffer(e)}),this.bufferTimeout))}async processEmotionalBuffer(e){const t=this.emotionalBuffer.get(e);if(!t||0===t.length)return;const s=this.activeContexts.get(e);if(!s)return;const a=Array(t[0].vector.length).fill(0);let i=0;const o=this.groupBySource(t);for(const[e,t]of Object.entries(o)){const s=this.weights[e]*this.calculateSourceConfidence(t);this.combineSourceEmotions(t).forEach(((e,t)=>{a[t]+=e*s})),i+=s}i>0&&a.forEach(((e,t)=>{a[t]/=i})),s.emotionalState={current:c(a),confidence:i,vector:a,sources:s.emotionalState.sources,history:[...s.emotionalState.history,{timestamp:Date.now(),emotions:t,sources:{...s.emotionalState.sources}}].slice(-100)},await this.redis.hSet("emotional_context:"+e,{state:JSON.stringify(s.emotionalState),lastUpdate:Date.now().toString()}),this.emotionalBuffer.set(e,[])}groupBySource(e){return e.reduce(((e,t)=>{const s=t.source;return e[s]||(e[s]=[]),e[s].push(t),e}),{})}calculateSourceConfidence(e){return e.reduce(((e,t)=>e+t.confidence),0)/e.length}combineSourceEmotions(e){const t=Array(e[0].vector.length).fill(0);return e.forEach((e=>{e.vector.forEach(((s,a)=>{t[a]+=s*e.confidence}))})),t}async getEmotionalContext(e){const t=this.activeContexts.get(e);if(!t){const t=await this.redis.hGetAll("emotional_context:"+e);return t.state?JSON.parse(t.state):null}return t.emotionalState}async stopContext(e){const t=this.activeContexts.get(e);t&&(await this.processEmotionalBuffer(e),t.options.useVideo&&await h.stopStream(e),t.options.useVoice&&!t.options.useEVI&&await m.stopStream(e),this.activeContexts.delete(e),this.emotionalBuffer.delete(e),o.info("Stopped emotional context "+e))}async cleanup(){for(const e of this.activeContexts.keys())await this.stopContext(e)}};function p(){throw Error("Database not initialized. Call setupDatabase first.")}const g=new class{constructor(){this.redis=null,this.db=null,this.apiKey=process.env.HUME_API_KEY,this.apiEndpoint="https://api.hume.ai/v0/custom/models",this.activeModels=new Map,this.trainingJobs=new Map}async initialize(){this.redis=d(),this.db=p(),await this.loadActiveModels()}async loadActiveModels(){try{const e=await this.db.collection("custom_models").find({status:"active"}).toArray();e.forEach((e=>{this.activeModels.set(e.modelId,e)})),o.info(`Loaded ${e.length} active custom models`)}catch(e){o.error("Failed to load active models:",e)}}async createTrainingJob(e,t){try{const s=`train_${Date.now()}_${e}`,a={id:s,userId:e,status:"preparing",config:{name:t.name,description:t.description,labelSet:t.labels||[],dataConfig:{includeExpressions:!0,includeLanguage:!0,includeProsody:!0},...t},created:new Date,updated:new Date};return await this.db.collection("training_jobs").insertOne(a),this.trainingJobs.set(s,a),await this.collectTrainingData(s),s}catch(e){throw o.error("Failed to create training job:",e),e}}async collectTrainingData(e){const t=this.trainingJobs.get(e);if(!t)throw Error(`Training job ${e} not found`);try{t.status="collecting",await this.updateJobStatus(t);const s=await this.getEmotionalHistory(t.userId),a=await this.processTrainingData(s,t.config);await this.storeTrainingData(e,a),a.length>=100?await this.startModelTraining(e):(t.status="insufficient_data",await this.updateJobStatus(t))}catch(s){o.error(`Failed to collect training data for job ${e}:`,s),t.status="failed",t.error=s.message,await this.updateJobStatus(t)}}async getEmotionalHistory(e){return await this.db.collection("emotional_history").find({userId:e,timestamp:{$gte:new Date(Date.now()-2592e6)}}).sort({timestamp:1}).toArray()}async processTrainingData(e,t){const s=[];for(const a of e){if(!a.emotionalState||!a.context)continue;const e={timestamp:a.timestamp,labels:this.generateLabels(a,t.labelSet),data:{expressions:a.emotionalState.sources.video||null,language:a.context.text||null,prosody:a.emotionalState.sources.voice||null}};this.validateTrainingEntry(e)&&s.push(e)}return s}generateLabels(e,t){const s=new Set;return t.forEach((t=>{this.matchesLabelCriteria(e,t)&&s.add(t)})),Array.from(s)}matchesLabelCriteria(e,t){return!1}validateTrainingEntry(e){return e.labels.length>0&&(e.data.expressions||e.data.language||e.data.prosody)}async storeTrainingData(e,t){await this.db.collection("training_data").insertOne({jobId:e,data:t,timestamp:new Date})}async startModelTraining(e){const t=this.trainingJobs.get(e);if(!t)throw Error(`Training job ${e} not found`);try{t.status="training",await this.updateJobStatus(t);const s=await this.db.collection("training_data").findOne({jobId:e}),a=await fetch(this.apiEndpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Hume-Api-Key":this.apiKey},body:JSON.stringify({name:t.config.name,description:t.config.description,data:s.data})});if(!a.ok)throw Error("Hume API error: "+a.statusText);const i=await a.json();t.modelId=i.model_id,t.status="training",await this.updateJobStatus(t),this.monitorTraining(e)}catch(s){o.error(`Failed to start training for job ${e}:`,s),t.status="failed",t.error=s.message,await this.updateJobStatus(t)}}async monitorTraining(e){const t=this.trainingJobs.get(e);if(t&&t.modelId)try{const s=await fetch(`${this.apiEndpoint}/${t.modelId}`,{headers:{"X-Hume-Api-Key":this.apiKey}});if(!s.ok)throw Error("Hume API error: "+s.statusText);const a=await s.json();"completed"===a.status?await this.handleTrainingComplete(t):"failed"===a.status?await this.handleTrainingFailed(t,a.error):setTimeout((()=>this.monitorTraining(e)),3e5)}catch(t){o.error(`Error monitoring training for job ${e}:`,t)}}async handleTrainingComplete(e){try{e.status="completed",await this.updateJobStatus(e);const t={modelId:e.modelId,name:e.config.name,description:e.config.description,userId:e.userId,status:"active",created:new Date,lastUsed:null};await this.db.collection("custom_models").insertOne(t),this.activeModels.set(e.modelId,t),o.info("Training completed for job "+e.id)}catch(t){o.error(`Error handling training completion for job ${e.id}:`,t)}}async handleTrainingFailed(e,t){e.status="failed",e.error=t,await this.updateJobStatus(e),o.error(`Training failed for job ${e.id}:`,t)}async updateJobStatus(e){await this.db.collection("training_jobs").updateOne({id:e.id},{$set:{status:e.status,error:e.error,updated:new Date,modelId:e.modelId}})}async getJobStatus(e){const t=this.trainingJobs.get(e);return t||await this.db.collection("training_jobs").findOne({id:e})}async getActiveModels(e){return Array.from(this.activeModels.values()).filter((t=>t.userId===e))}async deleteModel(e){try{await fetch(`${this.apiEndpoint}/${e}`,{method:"DELETE",headers:{"X-Hume-Api-Key":this.apiKey}}),this.activeModels.delete(e),await this.db.collection("custom_models").updateOne({modelId:e},{$set:{status:"deleted"}}),o.info("Deleted custom model "+e)}catch(t){throw o.error(`Failed to delete model ${e}:`,t),t}}},w=new class{constructor(){this.isServerEnvironment="production"===process.env.NODE_ENV,this.hasGPU="1"===process.env.ENABLE_CUDA,this.modelConfigs={local:{default:"ollama/mistral:3.2-small",management:"ollama/mistral:3.2-small",embedding:"ollama/nomic-embed-text",fallback:"ollama/tinyllama"},server:{default:"ollama/mistral:7b-instruct",management:"ollama/mixtral:8x7b-instruct",embedding:"ollama/nomic-embed-text:latest",fallback:"ollama/mistral:3.2-small"}},this.metrics={requestCount:0,totalLatency:0,errors:0,lastSwitchTime:Date.now(),modelUsage:new Map},this.memoryThresholds={warning:80,critical:90},this.responseCache=new Map,this.modelStateCache=new Map,this.taskPatternCache=new Map,this.cacheConfig={maxSize:1e3,ttl:36e5,criticalityThreshold:.8},this.startPerformanceMonitoring()}getModelConfig(e="default"){const t=this.isServerEnvironment?"server":"local",s=this.modelConfigs[t];return!this.isServerEnvironment&&this.hasGPU?this.modelConfigs.server[e]:s[e]}async validateModel(e){try{const t=await fetch("http://localhost:11434/api/tags"),{models:s}=await t.json();return s.some((t=>t.name===e))}catch(e){return o.error("Error validating model:",e),this.metrics.errors++,!1}}async ensureModel(e="default"){const t=this.getModelConfig(e);return await this.validateModel(t)?t:(o.info(`Model ${t} not found, falling back to smaller model`),this.modelConfigs[this.isServerEnvironment?"server":"local"].fallback)}getResourceLimits(){return this.isServerEnvironment?{maxMemory:"16gb",maxThreads:8,batchSize:32}:{maxMemory:"4gb",maxThreads:4,batchSize:8}}async getOptimalConfig(){const e=Date.now(),t=await this.ensureModel(),s=this.getResourceLimits();return this.metrics.requestCount++,this.metrics.totalLatency+=Date.now()-e,this.updateModelUsage(t),{model:t,...s,environment:this.isServerEnvironment?"server":"local",gpu:this.hasGPU}}async getMemoizedResponse(e,t,s){const a=this.generateCacheKey(e,t,s);if(this.responseCache.has(a)){const e=this.responseCache.get(a);if(Date.now()-e.timestamp<this.cacheConfig.ttl)return o.info("Using memoized response"),e.response;this.responseCache.delete(a)}return null}async memoizeResponse(e,t,s,a,i){const o=this.generateCacheKey(e,s,a);if(i>=this.cacheConfig.criticalityThreshold){if(this.responseCache.set(o,{response:t,timestamp:Date.now(),criticality:i}),this.responseCache.size>this.cacheConfig.maxSize){const e=Array.from(this.responseCache.keys())[0];this.responseCache.delete(e)}this.updateTaskPattern(a,e)}}generateCacheKey(e,t,s){return`${t}:${s}:${e.slice(0,100)}`}updateTaskPattern(e,t){const s=this.taskPatternCache.get(e)||[];s.push({prompt:t.slice(0,100),timestamp:Date.now()}),this.taskPatternCache.set(e,s.slice(-100))}async getModelState(e){return this.modelStateCache.get(e)||{lastUsed:0,performance:{},errors:0}}async updateModelState(e,t){const s=await this.getModelState(e);this.modelStateCache.set(e,{...s,...t,lastUsed:Date.now()})}startPerformanceMonitoring(){setInterval((()=>this.checkSystemHealth()),3e4),o.info("Performance monitoring started")}checkSystemHealth(){const e=this.getMemoryUsage(),t=this.metrics.totalLatency/this.metrics.requestCount||0;o.info("System health metrics:",{memoryUsage:e,avgLatency:t,requestCount:this.metrics.requestCount,errors:this.metrics.errors,cacheSize:this.responseCache.size}),this.shouldSwitchModel(e)&&this.switchToSmallerModel()}getMemoryUsage(){const e=process.memoryUsage(),t=i.default.totalmem();return{heapUsed:(e.heapUsed/1024/1024).toFixed(2)+"MB",heapTotal:(e.heapTotal/1024/1024).toFixed(2)+"MB",rss:(e.rss/1024/1024).toFixed(2)+"MB",percentage:(e.heapUsed/t*100).toFixed(2)+"%"}}shouldSwitchModel(e){return parseFloat(e.percentage)>this.memoryThresholds.warning}async switchToSmallerModel(){const e=Date.now();if(e-this.metrics.lastSwitchTime<3e5)return;o.warn("Switching to smaller model due to high memory usage");const t=this.modelConfigs[this.isServerEnvironment?"server":"local"].fallback;await this.warmupModel(t),this.metrics.lastSwitchTime=e}updateModelUsage(e){const t=this.metrics.modelUsage.get(e)||0;this.metrics.modelUsage.set(e,t+1)}async warmupModel(e){try{o.info("Warming up model: "+e);const t=await fetch("http://localhost:11434/api/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:e,prompt:"Warm up test.",stream:!1})});if(!t.ok)throw Error("Failed to warm up model: "+t.statusText);return o.info(`Model ${e} warmed up successfully`),!0}catch(e){return o.error("Error warming up model:",e),this.metrics.errors++,!1}}getMetrics(){return{...this.metrics,avgLatency:this.metrics.totalLatency/this.metrics.requestCount||0,modelUsage:Object.fromEntries(this.metrics.modelUsage),cacheStats:{size:this.responseCache.size,taskPatterns:Object.fromEntries(this.taskPatternCache)}}}};async function y(){const e={healthy:!0,services:{mongodb:!1,redis:!1,weaviate:!1},timestamp:(new Date).toISOString()};try{const t=p();await t.command({ping:1}),e.services.mongodb=!0}catch(t){o.error("MongoDB health check failed:",t),e.healthy=!1}try{const t=d();await t.ping(),e.services.redis=!0}catch(t){o.error("Redis health check failed:",t),e.healthy=!1}try{const t=function(){throw Error("Weaviate not initialized. Call setupWeaviate first.")}();await t.schema.getter().do(),e.services.weaviate=!0}catch(t){o.error("Weaviate health check failed:",t),e.healthy=!1}return e}exports.checkHealth=y,exports.customModelService=g,exports.default=class{constructor(e={}){this.config={humeApiKey:e.humeApiKey||process.env.HUME_API_KEY,mongoUri:e.mongoUri||process.env.MONGODB_URI,redisUrl:e.redisUrl||process.env.REDIS_URL,weaviateUrl:e.weaviateUrl||process.env.WEAVIATE_URL,...e}}async initialize(){return await f.initialize(),await m.connect(),await g.initialize(),this}async checkHealth(){return y()}async processEmotion(e,t,s=null){return f.processEmotion(e,t,s)}async startEmotionalContext(e,t={}){return f.startContext(e,t)}async stopEmotionalContext(e){return f.stopContext(e)}async startVideoStream(e,t,s={}){return h.startStream(e,t,s)}async addVideoFrame(e,t,s){return h.addFrame(e,t,s)}async stopVideoStream(e){return h.stopStream(e)}async createCustomModel(e,t){return g.createTrainingJob(e,t)}async getCustomModels(e){return g.getActiveModels(e)}getEmotionColor(e){return n[e]}emotionToVector(e){return r(e)}vectorToEmotion(e){return c(e)}async cleanup(){await f.cleanup(),await m.close(),await h.cleanup(),await g.cleanup()}},exports.emotionToVector=r,exports.emotionalContextService=f,exports.expressionColors=n,exports.humeService=m,exports.modelSelectionService=w,exports.vectorToEmotion=c,exports.videoStreamService=h;
//# sourceMappingURL=index.cjs.map