@alanhelmick/memorable
Version:
An AI memory system enabling personalized, context-aware interactions through advanced memory management and emotional intelligence
3 lines (2 loc) • 25.7 kB
JavaScript
import"winston";import t from"ws";import"redis";import"mongodb";import e from"os";import"weaviate-client";let s=null;const a={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(a).length;const i=t=>{const e=a[t];return e?[parseInt(e.slice(1,3),16)/255,parseInt(e.slice(3,5),16)/255,parseInt(e.slice(5,7),16)/255]:null},o=t=>{if(!t||3!==t.length)return"neutral";const e=t=>{const e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e},s=`#${e(t[0])}${e(t[1])}${e(t[2])}`;let i="neutral",o=1/0;for(const[t,e]of Object.entries(a)){const a=n(s,e);a<o&&(o=a,i=t)}return i},n=(t,e)=>{const s=parseInt(t.slice(1,3),16),a=parseInt(t.slice(3,5),16),i=parseInt(t.slice(5,7),16),o=parseInt(e.slice(1,3),16),n=parseInt(e.slice(3,5),16),r=parseInt(e.slice(5,7),16);return Math.sqrt(Math.pow(s-o,2)+Math.pow(a-n,2)+Math.pow(i-r,2))},r=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(((a,i)=>{try{const i=new URLSearchParams({apiKey:this.apiKey,...e}),o=`${this.endpoint}?${i.toString()}`;this.ws=new t(o),this.ws.on("open",(()=>{s.info("Connected to Hume.ai websocket"),this.isConnected=!0,this.reconnectAttempts=0,this.reconnectDelay=1e3,this.setupInactivityCheck(),a()})),this.ws.on("message",(t=>{this.lastActivityTime=Date.now(),this.handleMessage(t)})),this.ws.on("error",(t=>{s.error("Hume websocket error:",t),this.handleError(t)})),this.ws.on("close",(()=>{s.info("Hume websocket closed"),this.isConnected=!1,this.handleDisconnect()}))}catch(t){i(t)}}))}setupInactivityCheck(){setInterval((()=>{Date.now()-this.lastActivityTime>=this.inactivityTimeout&&(s.warn("WebSocket inactive, reconnecting..."),this.reconnect())}),1e4)}async reconnect(){this.reconnectAttempts<this.maxReconnectAttempts?(this.reconnectAttempts++,this.reconnectDelay*=2,s.info(`Attempting to reconnect in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempts})`),setTimeout((async()=>{try{await this.connect();for(const[t,e]of this.activeStreams)await this.startStream(t,e)}catch(t){s.error("Reconnection attempt failed:",t)}}),this.reconnectDelay)):s.error("Max reconnection attempts reached")}async startStream(t,e){this.isConnected||await this.connect();const a={models:e.models||{language:{},face:{},prosody:{}},raw_text:e.rawText||!0,reset_stream:e.resetStream||!1};this.activeStreams.set(t,{config:a,callbacks:new Map,buffer:[]}),await this.sendMessage({type:"stream_start",stream_id:t,config:a}),s.info("Started stream "+t)}async stopStream(t){this.activeStreams.get(t)&&(await this.sendMessage({type:"stream_end",stream_id:t}),this.activeStreams.delete(t),s.info("Stopped stream "+t))}async processText(t,e=null){const s=e||"text_"+Date.now();return e||await this.startStream(s,{models:{language:{}}}),this.sendData(s,{type:"text",data:t})}async processVoice(t,e=null){const s=e||"voice_"+Date.now();e||await this.startStream(s,{models:{prosody:{}}});const a=this.splitAudioIntoChunks(t),i=[];for(const t of a){const e=await this.sendData(s,{type:"prosody",data:t.toString("base64")});i.push(e)}return e||await this.stopStream(s),this.mergeResults(i)}async processFacial(t,e=null){const s=e||"face_"+Date.now();e||await this.startStream(s,{models:{face:{}}});const a=await this.sendData(s,{type:"face",data:t.toString("base64")});return e||await this.stopStream(s),a}splitAudioIntoChunks(t,e=5e3){const s=[];let a=0;for(;a<t.length;)s.push(t.slice(a,a+e)),a+=e;return s}mergeResults(t){const e={emotions:new Map};return t.forEach((t=>{t.emotions.forEach((t=>{const s=e.emotions.get(t.name)||{score:0,count:0};s.score+=t.score,s.count+=1,e.emotions.set(t.name,s)}))})),Array.from(e.emotions.entries()).map((([t,e])=>({name:t,score:e.score/e.count})))}async sendData(t,e){return new Promise(((s,a)=>{const i=Date.now().toString(),o=this.activeStreams.get(t);o?(o.callbacks.set(i,(t=>{t.error?a(Error(t.error)):s(this.processEmotions(t.emotions))})),this.sendMessage({id:i,stream_id:t,...e})):a(Error(`Stream ${t} not found`))}))}async sendMessage(t){if(!this.isConnected)throw Error("WebSocket not connected");this.messageQueue.push(t),this.processingQueue||await this.processMessageQueue()}async processMessageQueue(){for(this.processingQueue=!0;this.messageQueue.length>0;){const t=this.messageQueue.shift();try{this.ws.send(JSON.stringify(t)),this.lastActivityTime=Date.now(),await new Promise((t=>setTimeout(t,20)))}catch(e){s.error("Failed to send message:",e),this.messageQueue.unshift(t);break}}this.processingQueue=!1}handleMessage(t){try{const e=JSON.parse(t),s=this.activeStreams.get(e.stream_id);s&&e.id&&s.callbacks.has(e.id)&&(s.callbacks.get(e.id)(e),s.callbacks.delete(e.id))}catch(t){s.error("Error handling Hume message:",t)}}processEmotions(t){return t.map((t=>({name:t.name,score:t.score,vector:i(t.name),color:a[t.name],confidence:t.confidence||t.score}))).filter((t=>t.confidence>=.1)).sort(((t,e)=>e.score-t.score))}async close(){for(const t of this.activeStreams.keys())await this.stopStream(t);this.ws&&(this.ws.close(),this.ws=null,this.isConnected=!1,s.info("Hume websocket connection closed"))}},c=new class{constructor(){this.activeStreams=new Map,this.chunkDuration=5e3,this.maxResolution={width:3e3,height:3e3},this.processingInterval=1e3}async startStream(t,e,a={}){if(this.activeStreams.has(t))return void s.warn(`Stream ${t} is already active`);const i={id:t,buffer:[],lastProcessed:Date.now(),onUpdate:e,processingInterval:null,config:{resetStream:a.resetStream||!1,models:{face:a.faceConfig||{}},...a}};try{await r.startStream(t,i.config),i.processingInterval=setInterval((()=>this.processStreamBuffer(i)),this.processingInterval),this.activeStreams.set(t,i),s.info("Started video stream "+t)}catch(e){throw s.error(`Failed to start video stream ${t}:`,e),e}}async stopStream(t){const e=this.activeStreams.get(t);e?(e.processingInterval&&clearInterval(e.processingInterval),await this.processStreamBuffer(e),await r.stopStream(t),this.activeStreams.delete(t),s.info("Stopped video stream "+t)):s.warn(`Stream ${t} not found`)}async addFrame(t,e,a=Date.now()){const i=this.activeStreams.get(t);if(i)try{const t=await this.getFrameDimensions(e);if(!this.validateFrameDimensions(t))return void s.warn(`Frame dimensions exceed maximum (${t.width}x${t.height})`);i.buffer.push({data:e,timestamp:a}),this.trimBuffer(i)}catch(e){s.error(`Error adding frame to stream ${t}:`,e)}else s.warn(`Stream ${t} not found, frame discarded`)}async processStreamBuffer(t){if(0!==t.buffer.length)try{const e=Date.now(),s=e-this.chunkDuration,a=t.buffer.filter((t=>t.timestamp>=s));if(0===a.length)return;const i=this.selectBestFrame(a),o=await r.processFacial(i.data,t.id);t.onUpdate&&o.length>0&&t.onUpdate({streamId:t.id,timestamp:e,emotions:o,frameCount:a.length,selectedFrameTime:i.timestamp}),t.buffer=t.buffer.filter((t=>t.timestamp>s)),t.lastProcessed=e}catch(e){s.error(`Error processing stream ${t.id}:`,e)}}selectBestFrame(t){return t[Math.floor(t.length/2)]}trimBuffer(t){const e=Date.now();t.buffer=t.buffer.filter((t=>e-t.timestamp<=this.chunkDuration))}async getFrameDimensions(t){return{width:1280,height:720}}validateFrameDimensions(t){return t.width<=this.maxResolution.width&&t.height<=this.maxResolution.height}getStreamStatus(t){const e=this.activeStreams.get(t);return e?{id:e.id,isActive:!0,bufferSize:e.buffer.length,lastProcessed:e.lastProcessed,timeSinceLastProcess:Date.now()-e.lastProcessed,config:e.config}:null}getAllStreams(){return Array.from(this.activeStreams.keys()).map((t=>this.getStreamStatus(t)))}async cleanup(){for(const t of this.activeStreams.keys())await this.stopStream(t)}};function l(){throw Error("Redis client not initialized. Call setupRedis first.")}async function m(){}process.on("SIGTERM",(async()=>{s.info("SIGTERM received, closing Redis connection..."),await m()})),process.on("SIGINT",(async()=>{s.info("SIGINT received, closing Redis connection..."),await m()}));const h=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=l(),await this.setupEmotionalBuffers(),await this.loadCustomModel()}async loadCustomModel(){try{if(await this.redis.exists("custom_model_config")){const t=JSON.parse(await this.redis.get("custom_model_config"));this.customModelEnabled=t.enabled,t.weights&&(this.weights=t.weights),s.info("Custom model configuration loaded")}}catch(t){s.error("Failed to load custom model:",t)}}async setupEmotionalBuffers(){try{const t="emotional_context_buffers";await this.redis.exists(t)||await this.redis.hSet(t,{active_sessions:"{}",buffer_timeouts:"{}"})}catch(t){throw s.error("Failed to setup emotional buffers:",t),t}}async startContext(t,e={}){const a={id:t,startTime:Date.now(),options:{useVideo:e.useVideo??!1,useVoice:e.useVoice??!0,useEVI:e.useEVI??!1,customModel:e.customModel??this.customModelEnabled,bufferSize:e.bufferSize??5,...e},emotionalState:{current:"neutral",confidence:1,vector:i("neutral"),history:[],sources:{}}};return this.activeContexts.set(t,a),a.options.useVideo&&await c.startStream(t,(e=>{this.handleVideoEmotion(t,e)}),{resetStream:!0,faceConfig:{minConfidence:.7,returnPoints:!0}}),a.options.useVoice&&!a.options.useEVI&&await r.startStream(t,{models:{prosody:{}},resetStream:!0}),s.info(`Started emotional context ${t} with options:`,a.options),a}async handleEVIEmotion(t,e){const a=this.activeContexts.get(t);a?(a.emotionalState.sources.evi={emotion:e.emotion,confidence:e.confidence,vector:e.vector,timestamp:Date.now()},await this.updateEmotionalState(t,{emotion:e.emotion,confidence:e.confidence,vector:e.vector,source:"evi",timestamp:Date.now()})):s.warn(`Context ${t} not found for EVI emotion`)}async handleVideoEmotion(t,e){const a=this.activeContexts.get(t);if(a){if(e.emotions.length>0){const s=e.emotions[0];a.emotionalState.sources.video={emotion:s.name,confidence:s.confidence,vector:s.vector,timestamp:e.timestamp},await this.updateEmotionalState(t,{emotion:s.name,confidence:s.confidence,vector:s.vector,source:"video",timestamp:e.timestamp})}}else s.warn(`Context ${t} not found for video emotion`)}async handleVoiceEmotion(t,e){const a=this.activeContexts.get(t);if(a)try{let s;if(a.options.useEVI)return;if(s=await r.processVoice(e,t),s.length>0){const e=s[0];a.emotionalState.sources.voice={emotion:e.name,confidence:e.confidence,vector:e.vector,timestamp:Date.now()},await this.updateEmotionalState(t,{emotion:e.name,confidence:e.confidence,vector:e.vector,source:"voice",timestamp:Date.now()})}}catch(e){s.error(`Error processing voice emotion for context ${t}:`,e)}else s.warn(`Context ${t} not found for voice emotion`)}async updateEmotionalState(t,e){const s=this.activeContexts.get(t);s&&(this.emotionalBuffer.has(t)||this.emotionalBuffer.set(t,[]),this.emotionalBuffer.get(t).push(e),this.emotionalBuffer.get(t).length>=s.options.bufferSize?await this.processEmotionalBuffer(t):setTimeout((async()=>{await this.processEmotionalBuffer(t)}),this.bufferTimeout))}async processEmotionalBuffer(t){const e=this.emotionalBuffer.get(t);if(!e||0===e.length)return;const s=this.activeContexts.get(t);if(!s)return;const a=Array(e[0].vector.length).fill(0);let i=0;const n=this.groupBySource(e);for(const[t,e]of Object.entries(n)){const s=this.weights[t]*this.calculateSourceConfidence(e);this.combineSourceEmotions(e).forEach(((t,e)=>{a[e]+=t*s})),i+=s}i>0&&a.forEach(((t,e)=>{a[e]/=i})),s.emotionalState={current:o(a),confidence:i,vector:a,sources:s.emotionalState.sources,history:[...s.emotionalState.history,{timestamp:Date.now(),emotions:e,sources:{...s.emotionalState.sources}}].slice(-100)},await this.redis.hSet("emotional_context:"+t,{state:JSON.stringify(s.emotionalState),lastUpdate:Date.now().toString()}),this.emotionalBuffer.set(t,[])}groupBySource(t){return t.reduce(((t,e)=>{const s=e.source;return t[s]||(t[s]=[]),t[s].push(e),t}),{})}calculateSourceConfidence(t){return t.reduce(((t,e)=>t+e.confidence),0)/t.length}combineSourceEmotions(t){const e=Array(t[0].vector.length).fill(0);return t.forEach((t=>{t.vector.forEach(((s,a)=>{e[a]+=s*t.confidence}))})),e}async getEmotionalContext(t){const e=this.activeContexts.get(t);if(!e){const e=await this.redis.hGetAll("emotional_context:"+t);return e.state?JSON.parse(e.state):null}return e.emotionalState}async stopContext(t){const e=this.activeContexts.get(t);e&&(await this.processEmotionalBuffer(t),e.options.useVideo&&await c.stopStream(t),e.options.useVoice&&!e.options.useEVI&&await r.stopStream(t),this.activeContexts.delete(t),this.emotionalBuffer.delete(t),s.info("Stopped emotional context "+t))}async cleanup(){for(const t of this.activeContexts.keys())await this.stopContext(t)}};function d(){throw Error("Database not initialized. Call setupDatabase first.")}const u=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=l(),this.db=d(),await this.loadActiveModels()}async loadActiveModels(){try{const t=await this.db.collection("custom_models").find({status:"active"}).toArray();t.forEach((t=>{this.activeModels.set(t.modelId,t)})),s.info(`Loaded ${t.length} active custom models`)}catch(t){s.error("Failed to load active models:",t)}}async createTrainingJob(t,e){try{const s=`train_${Date.now()}_${t}`,a={id:s,userId:t,status:"preparing",config:{name:e.name,description:e.description,labelSet:e.labels||[],dataConfig:{includeExpressions:!0,includeLanguage:!0,includeProsody:!0},...e},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(t){throw s.error("Failed to create training job:",t),t}}async collectTrainingData(t){const e=this.trainingJobs.get(t);if(!e)throw Error(`Training job ${t} not found`);try{e.status="collecting",await this.updateJobStatus(e);const s=await this.getEmotionalHistory(e.userId),a=await this.processTrainingData(s,e.config);await this.storeTrainingData(t,a),a.length>=100?await this.startModelTraining(t):(e.status="insufficient_data",await this.updateJobStatus(e))}catch(a){s.error(`Failed to collect training data for job ${t}:`,a),e.status="failed",e.error=a.message,await this.updateJobStatus(e)}}async getEmotionalHistory(t){return await this.db.collection("emotional_history").find({userId:t,timestamp:{$gte:new Date(Date.now()-2592e6)}}).sort({timestamp:1}).toArray()}async processTrainingData(t,e){const s=[];for(const a of t){if(!a.emotionalState||!a.context)continue;const t={timestamp:a.timestamp,labels:this.generateLabels(a,e.labelSet),data:{expressions:a.emotionalState.sources.video||null,language:a.context.text||null,prosody:a.emotionalState.sources.voice||null}};this.validateTrainingEntry(t)&&s.push(t)}return s}generateLabels(t,e){const s=new Set;return e.forEach((e=>{this.matchesLabelCriteria(t,e)&&s.add(e)})),Array.from(s)}matchesLabelCriteria(t,e){return!1}validateTrainingEntry(t){return t.labels.length>0&&(t.data.expressions||t.data.language||t.data.prosody)}async storeTrainingData(t,e){await this.db.collection("training_data").insertOne({jobId:t,data:e,timestamp:new Date})}async startModelTraining(t){const e=this.trainingJobs.get(t);if(!e)throw Error(`Training job ${t} not found`);try{e.status="training",await this.updateJobStatus(e);const s=await this.db.collection("training_data").findOne({jobId:t}),a=await fetch(this.apiEndpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Hume-Api-Key":this.apiKey},body:JSON.stringify({name:e.config.name,description:e.config.description,data:s.data})});if(!a.ok)throw Error("Hume API error: "+a.statusText);const i=await a.json();e.modelId=i.model_id,e.status="training",await this.updateJobStatus(e),this.monitorTraining(t)}catch(a){s.error(`Failed to start training for job ${t}:`,a),e.status="failed",e.error=a.message,await this.updateJobStatus(e)}}async monitorTraining(t){const e=this.trainingJobs.get(t);if(e&&e.modelId)try{const s=await fetch(`${this.apiEndpoint}/${e.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(e):"failed"===a.status?await this.handleTrainingFailed(e,a.error):setTimeout((()=>this.monitorTraining(t)),3e5)}catch(e){s.error(`Error monitoring training for job ${t}:`,e)}}async handleTrainingComplete(t){try{t.status="completed",await this.updateJobStatus(t);const e={modelId:t.modelId,name:t.config.name,description:t.config.description,userId:t.userId,status:"active",created:new Date,lastUsed:null};await this.db.collection("custom_models").insertOne(e),this.activeModels.set(t.modelId,e),s.info("Training completed for job "+t.id)}catch(e){s.error(`Error handling training completion for job ${t.id}:`,e)}}async handleTrainingFailed(t,e){t.status="failed",t.error=e,await this.updateJobStatus(t),s.error(`Training failed for job ${t.id}:`,e)}async updateJobStatus(t){await this.db.collection("training_jobs").updateOne({id:t.id},{$set:{status:t.status,error:t.error,updated:new Date,modelId:t.modelId}})}async getJobStatus(t){const e=this.trainingJobs.get(t);return e||await this.db.collection("training_jobs").findOne({id:t})}async getActiveModels(t){return Array.from(this.activeModels.values()).filter((e=>e.userId===t))}async deleteModel(t){try{await fetch(`${this.apiEndpoint}/${t}`,{method:"DELETE",headers:{"X-Hume-Api-Key":this.apiKey}}),this.activeModels.delete(t),await this.db.collection("custom_models").updateOne({modelId:t},{$set:{status:"deleted"}}),s.info("Deleted custom model "+t)}catch(e){throw s.error(`Failed to delete model ${t}:`,e),e}}},f=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(t="default"){const e=this.isServerEnvironment?"server":"local",s=this.modelConfigs[e];return!this.isServerEnvironment&&this.hasGPU?this.modelConfigs.server[t]:s[t]}async validateModel(t){try{const e=await fetch("http://localhost:11434/api/tags"),{models:s}=await e.json();return s.some((e=>e.name===t))}catch(t){return s.error("Error validating model:",t),this.metrics.errors++,!1}}async ensureModel(t="default"){const e=this.getModelConfig(t);return await this.validateModel(e)?e:(s.info(`Model ${e} 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 t=Date.now(),e=await this.ensureModel(),s=this.getResourceLimits();return this.metrics.requestCount++,this.metrics.totalLatency+=Date.now()-t,this.updateModelUsage(e),{model:e,...s,environment:this.isServerEnvironment?"server":"local",gpu:this.hasGPU}}async getMemoizedResponse(t,e,a){const i=this.generateCacheKey(t,e,a);if(this.responseCache.has(i)){const t=this.responseCache.get(i);if(Date.now()-t.timestamp<this.cacheConfig.ttl)return s.info("Using memoized response"),t.response;this.responseCache.delete(i)}return null}async memoizeResponse(t,e,s,a,i){const o=this.generateCacheKey(t,s,a);if(i>=this.cacheConfig.criticalityThreshold){if(this.responseCache.set(o,{response:e,timestamp:Date.now(),criticality:i}),this.responseCache.size>this.cacheConfig.maxSize){const t=Array.from(this.responseCache.keys())[0];this.responseCache.delete(t)}this.updateTaskPattern(a,t)}}generateCacheKey(t,e,s){return`${e}:${s}:${t.slice(0,100)}`}updateTaskPattern(t,e){const s=this.taskPatternCache.get(t)||[];s.push({prompt:e.slice(0,100),timestamp:Date.now()}),this.taskPatternCache.set(t,s.slice(-100))}async getModelState(t){return this.modelStateCache.get(t)||{lastUsed:0,performance:{},errors:0}}async updateModelState(t,e){const s=await this.getModelState(t);this.modelStateCache.set(t,{...s,...e,lastUsed:Date.now()})}startPerformanceMonitoring(){setInterval((()=>this.checkSystemHealth()),3e4),s.info("Performance monitoring started")}checkSystemHealth(){const t=this.getMemoryUsage(),e=this.metrics.totalLatency/this.metrics.requestCount||0;s.info("System health metrics:",{memoryUsage:t,avgLatency:e,requestCount:this.metrics.requestCount,errors:this.metrics.errors,cacheSize:this.responseCache.size}),this.shouldSwitchModel(t)&&this.switchToSmallerModel()}getMemoryUsage(){const t=process.memoryUsage(),s=e.totalmem();return{heapUsed:(t.heapUsed/1024/1024).toFixed(2)+"MB",heapTotal:(t.heapTotal/1024/1024).toFixed(2)+"MB",rss:(t.rss/1024/1024).toFixed(2)+"MB",percentage:(t.heapUsed/s*100).toFixed(2)+"%"}}shouldSwitchModel(t){return parseFloat(t.percentage)>this.memoryThresholds.warning}async switchToSmallerModel(){const t=Date.now();if(t-this.metrics.lastSwitchTime<3e5)return;s.warn("Switching to smaller model due to high memory usage");const e=this.modelConfigs[this.isServerEnvironment?"server":"local"].fallback;await this.warmupModel(e),this.metrics.lastSwitchTime=t}updateModelUsage(t){const e=this.metrics.modelUsage.get(t)||0;this.metrics.modelUsage.set(t,e+1)}async warmupModel(t){try{s.info("Warming up model: "+t);const e=await fetch("http://localhost:11434/api/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:t,prompt:"Warm up test.",stream:!1})});if(!e.ok)throw Error("Failed to warm up model: "+e.statusText);return s.info(`Model ${t} warmed up successfully`),!0}catch(t){return s.error("Error warming up model:",t),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 p(){const t={healthy:!0,services:{mongodb:!1,redis:!1,weaviate:!1},timestamp:(new Date).toISOString()};try{const e=d();await e.command({ping:1}),t.services.mongodb=!0}catch(e){s.error("MongoDB health check failed:",e),t.healthy=!1}try{const e=l();await e.ping(),t.services.redis=!0}catch(e){s.error("Redis health check failed:",e),t.healthy=!1}try{const e=function(){throw Error("Weaviate not initialized. Call setupWeaviate first.")}();await e.schema.getter().do(),t.services.weaviate=!0}catch(e){s.error("Weaviate health check failed:",e),t.healthy=!1}return t}class g{constructor(t={}){this.config={humeApiKey:t.humeApiKey||process.env.HUME_API_KEY,mongoUri:t.mongoUri||process.env.MONGODB_URI,redisUrl:t.redisUrl||process.env.REDIS_URL,weaviateUrl:t.weaviateUrl||process.env.WEAVIATE_URL,...t}}async initialize(){return await h.initialize(),await r.connect(),await u.initialize(),this}async checkHealth(){return p()}async processEmotion(t,e,s=null){return h.processEmotion(t,e,s)}async startEmotionalContext(t,e={}){return h.startContext(t,e)}async stopEmotionalContext(t){return h.stopContext(t)}async startVideoStream(t,e,s={}){return c.startStream(t,e,s)}async addVideoFrame(t,e,s){return c.addFrame(t,e,s)}async stopVideoStream(t){return c.stopStream(t)}async createCustomModel(t,e){return u.createTrainingJob(t,e)}async getCustomModels(t){return u.getActiveModels(t)}getEmotionColor(t){return a[t]}emotionToVector(t){return i(t)}vectorToEmotion(t){return o(t)}async cleanup(){await h.cleanup(),await r.close(),await c.cleanup(),await u.cleanup()}}export{p as checkHealth,u as customModelService,g as default,i as emotionToVector,h as emotionalContextService,a as expressionColors,r as humeService,f as modelSelectionService,o as vectorToEmotion,c as videoStreamService};
//# sourceMappingURL=index.js.map