@decido/kernel-bridge 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +25 -2552
- package/dist/index.mjs +25 -2477
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,1216 +1,17 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
1
|
+
"use strict";var Ye=Object.create;var H=Object.defineProperty;var Qe=Object.getOwnPropertyDescriptor;var Ze=Object.getOwnPropertyNames;var et=Object.getPrototypeOf,tt=Object.prototype.hasOwnProperty;var nt=(n,e)=>{for(var t in e)H(n,t,{get:e[t],enumerable:!0})},he=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Ze(e))!tt.call(n,r)&&r!==t&&H(n,r,{get:()=>e[r],enumerable:!(s=Qe(e,r))||s.enumerable});return n};var M=(n,e,t)=>(t=n!=null?Ye(et(n)):{},he(e||!n||!n.__esModule?H(t,"default",{value:n,enumerable:!0}):t,n)),st=n=>he(H({},"__esModule",{value:!0}),n);var zt={};nt(zt,{AnthropicProvider:()=>L,GeminiProvider:()=>_,OllamaProvider:()=>T,OpenAIProvider:()=>R,PeerNetworkPanel:()=>Xe,StateRehydrationManager:()=>qt,TokenWalletPanel:()=>Je,chat:()=>ie,chatStream:()=>ce,clearConversationHistory:()=>_e,decryptEvent:()=>ge,embeddingService:()=>Ke,getAllProviders:()=>O,getProviderStatuses:()=>te,inferenceRouter:()=>K,initProviders:()=>Z,isOllamaAvailable:()=>Le,kernel:()=>F,loadProviderKeys:()=>D,mlxBenchmarkModel:()=>$e,mlxCompareModels:()=>Ne,mlxListModels:()=>Ie,mlxPullModel:()=>Ee,mlxRunInference:()=>B,ollamaChat:()=>ie,ollamaChatStream:()=>ce,ollamaListModels:()=>Re,parseToolCalls:()=>Ce,peerMesh:()=>b,processLocalMessage:()=>Fe,routeChat:()=>A,routeInference:()=>ne,saveProviderKeys:()=>Q,setProviderApiKey:()=>ee,startLoRATraining:()=>Oe,stripToolCalls:()=>Te,tokenWallet:()=>P,usePeerMesh:()=>q,useTokenWallet:()=>W});module.exports=st(zt);var ke=require("socket.io-client");var z=new Map;function E(n){z.set(n.id,n),console.log(`\u{1F50C} [LLM] Provider registered: ${n.name} (${n.id})`)}function $(n){return z.get(n)}function O(){return Array.from(z.values())}var G="http://localhost:11434",rt="qwen2:latest",T=class{id="ollama";name="Ollama (Local)";requiresApiKey=!1;defaultModel=rt;async listModels(){try{let e=await fetch(`${G}/api/tags`);return e.ok?((await e.json()).models||[]).map(s=>s.name):[]}catch{return[]}}async checkStatus(){try{return(await fetch(`${G}/api/tags`,{signal:AbortSignal.timeout(2e3)})).ok?"available":"error"}catch{return"unavailable"}}async chat(e,t){let s=t?.model||this.defaultModel,r=Date.now(),o=e.map(l=>({role:l.role,content:l.content})),a=await fetch(`${G}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:s,messages:o,stream:!1,options:{temperature:t?.temperature??.7,num_predict:t?.maxTokens??512}}),signal:t?.signal??AbortSignal.timeout(3e4)});if(!a.ok){let l=await a.text();throw new Error(`Ollama API error ${a.status}: ${l}`)}let i=await a.json(),d=Date.now()-r;return{text:i.message?.content||"(sin respuesta)",model:s,latencyMs:d,tokensUsed:i.eval_count}}};var fe="https://generativelanguage.googleapis.com/v1beta",ot="gemini-flash-lite-latest",at=["gemini-flash-lite-latest","gemini-flash-lite-latest-lite","gemini-1.5-pro","gemini-flash-latest"],_=class{id="gemini";name="Google Gemini";requiresApiKey=!0;defaultModel=ot;apiKey=null;setApiKey(e){this.apiKey=e}async listModels(){return at}async checkStatus(){if(!this.apiKey)return"unconfigured";try{return(await fetch(`${fe}/models?key=${this.apiKey}`,{signal:AbortSignal.timeout(5e3)})).ok?"available":"error"}catch{return"unavailable"}}async chat(e,t){if(!this.apiKey)throw new Error("Gemini API key not configured. Set it in Settings \u2192 Providers.");let s=t?.model||this.defaultModel,r=Date.now(),o=e.find(y=>y.role==="system"),d={contents:e.filter(y=>y.role!=="system").map(y=>({role:y.role==="assistant"?"model":"user",parts:[{text:y.content}]})),generationConfig:{temperature:t?.temperature??.7,maxOutputTokens:t?.maxTokens??2048}};o&&(d.systemInstruction={parts:[{text:o.content}]});let l=`${fe}/models/${s}:generateContent?key=${this.apiKey}`,h=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),signal:t?.signal??AbortSignal.timeout(6e4)});if(!h.ok){let y=await h.text();throw new Error(`Gemini API error ${h.status}: ${y}`)}let m=await h.json(),g=Date.now()-r,p=m.candidates?.[0]?.content?.parts?.map(y=>y.text||"").join("")||"(sin respuesta)",f=(m.usageMetadata?.promptTokenCount||0)+(m.usageMetadata?.candidatesTokenCount||0);return{text:p,model:s,latencyMs:g,tokensUsed:f}}};var J="https://api.anthropic.com/v1",it="claude-sonnet-4-20250514",X="2023-06-01",ct=["claude-sonnet-4-20250514","claude-3-5-sonnet-20241022","claude-3-5-haiku-20241022","claude-3-opus-20240229"],L=class{id="anthropic";name="Anthropic Claude";requiresApiKey=!0;defaultModel=it;apiKey=null;setApiKey(e){this.apiKey=e}async listModels(){return ct}async checkStatus(){if(!this.apiKey)return"unconfigured";try{let e=await fetch(`${J}/messages`,{method:"POST",headers:{"x-api-key":this.apiKey,"anthropic-version":X,"content-type":"application/json"},body:JSON.stringify({model:this.defaultModel,max_tokens:1,messages:[{role:"user",content:"ping"}]}),signal:AbortSignal.timeout(5e3)});return e.ok||e.status===429?"available":"error"}catch{return"unavailable"}}async chat(e,t){if(!this.apiKey)throw new Error("Anthropic API key not configured. Set it in Settings \u2192 Providers.");let s=t?.model||this.defaultModel,r=Date.now(),o=e.find(p=>p.role==="system"),a=e.filter(p=>p.role!=="system").map(p=>({role:p.role,content:p.content})),i=this._sanitizeMessages(a),d={model:s,max_tokens:t?.maxTokens??2048,messages:i};t?.temperature!==void 0&&(d.temperature=t.temperature),o&&(d.system=o.content);let l=await fetch(`${J}/messages`,{method:"POST",headers:{"x-api-key":this.apiKey,"anthropic-version":X,"content-type":"application/json"},body:JSON.stringify(d),signal:t?.signal??AbortSignal.timeout(6e4)});if(!l.ok){let p=await l.text();throw new Error(`Anthropic API error ${l.status}: ${p}`)}let h=await l.json(),m=Date.now()-r,g=(h.content||[]).filter(p=>p.type==="text").map(p=>p.text).join("")||"(sin respuesta)",v=(h.usage?.input_tokens||0)+(h.usage?.output_tokens||0);return{text:g,model:s,latencyMs:m,tokensUsed:v}}async*chatStream(e,t){if(!this.apiKey)throw new Error("Anthropic API key not configured.");let s=t?.model||this.defaultModel,r=e.find(g=>g.role==="system"),o=e.filter(g=>g.role!=="system").map(g=>({role:g.role,content:g.content})),a=this._sanitizeMessages(o),i={model:s,max_tokens:t?.maxTokens??2048,messages:a,stream:!0};t?.temperature!==void 0&&(i.temperature=t.temperature),r&&(i.system=r.content);let d=await fetch(`${J}/messages`,{method:"POST",headers:{"x-api-key":this.apiKey,"anthropic-version":X,"content-type":"application/json"},body:JSON.stringify(i),signal:t?.signal??AbortSignal.timeout(12e4)});if(!d.ok){let g=await d.text();throw new Error(`Anthropic stream error ${d.status}: ${g}`)}let l=d.body?.getReader();if(!l)return;let h=new TextDecoder,m="";for(;;){let{done:g,value:v}=await l.read();if(g)break;m+=h.decode(v,{stream:!0});let p=m.split(`
|
|
2
|
+
`);m=p.pop()||"";for(let f of p){if(!f.startsWith("data: "))continue;let y=f.slice(6).trim();if(y==="[DONE]")return;try{let N=JSON.parse(y);N.type==="content_block_delta"&&N.delta?.text&&(yield N.delta.text)}catch{}}}}_sanitizeMessages(e){if(e.length===0)return[{role:"user",content:"..."}];let t=[],s="";for(let r of e)r.role===s?t[t.length-1].content+=`
|
|
3
|
+
`+r.content:(t.push({...r}),s=r.role);return t[0]?.role!=="user"&&t.unshift({role:"user",content:"..."}),t}};var lt="https://api.openai.com/v1",dt="gpt-4o",U=["gpt-4o","gpt-4o-mini","gpt-4-turbo","gpt-4","gpt-3.5-turbo","o3-mini"],R=class{id="openai";name="OpenAI";requiresApiKey=!0;defaultModel=dt;apiKey=null;baseUrl=lt;setApiKey(e){this.apiKey=e}setBaseUrl(e){this.baseUrl=e.replace(/\/$/,"")}async listModels(){if(!this.apiKey)return U;try{let e=await fetch(`${this.baseUrl}/models`,{headers:{Authorization:`Bearer ${this.apiKey}`},signal:AbortSignal.timeout(5e3)});if(!e.ok)return U;let s=((await e.json()).data||[]).map(r=>r.id).filter(r=>r.startsWith("gpt-")||r.startsWith("o"));return s.length>0?s:U}catch{return U}}async checkStatus(){if(!this.apiKey)return"unconfigured";try{return(await fetch(`${this.baseUrl}/models`,{headers:{Authorization:`Bearer ${this.apiKey}`},signal:AbortSignal.timeout(5e3)})).ok?"available":"error"}catch{return"unavailable"}}async chat(e,t){if(!this.apiKey)throw new Error("OpenAI API key not configured. Set it in Settings \u2192 Providers.");let s=t?.model||this.defaultModel,r=Date.now(),o={model:s,messages:e.map(m=>({role:m.role,content:m.content})),temperature:t?.temperature??.7,max_tokens:t?.maxTokens??2048},a=await fetch(`${this.baseUrl}/chat/completions`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(o),signal:t?.signal??AbortSignal.timeout(6e4)});if(!a.ok){let m=await a.text();throw new Error(`OpenAI API error ${a.status}: ${m}`)}let i=await a.json(),d=Date.now()-r,l=i.choices?.[0]?.message?.content||"(sin respuesta)",h=(i.usage?.prompt_tokens||0)+(i.usage?.completion_tokens||0);return{text:l,model:s,latencyMs:d,tokensUsed:h}}async*chatStream(e,t){if(!this.apiKey)throw new Error("OpenAI API key not configured.");let r={model:t?.model||this.defaultModel,messages:e.map(l=>({role:l.role,content:l.content})),temperature:t?.temperature??.7,max_tokens:t?.maxTokens??2048,stream:!0},o=await fetch(`${this.baseUrl}/chat/completions`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(r),signal:t?.signal??AbortSignal.timeout(12e4)});if(!o.ok){let l=await o.text();throw new Error(`OpenAI stream error ${o.status}: ${l}`)}let a=o.body?.getReader();if(!a)return;let i=new TextDecoder,d="";for(;;){let{done:l,value:h}=await a.read();if(l)break;d+=i.decode(h,{stream:!0});let m=d.split(`
|
|
4
|
+
`);d=m.pop()||"";for(let g of m){if(!g.startsWith("data: "))continue;let v=g.slice(6).trim();if(v==="[DONE]")return;try{let f=JSON.parse(v).choices?.[0]?.delta?.content;f&&(yield f)}catch{}}}}};var ye={"qwen2:latest":{input:0,output:0},"llama3:latest":{input:0,output:0},"mistral:latest":{input:0,output:0},"codellama:latest":{input:0,output:0},"gemini-flash-lite-latest":{input:.1,output:.4},"gemini-flash-lite-latest-lite":{input:.075,output:.3},"gemini-1.5-pro":{input:1.25,output:5},"gemini-flash-latest":{input:.075,output:.3},"claude-sonnet-4-20250514":{input:3,output:15},"claude-3-5-sonnet-20241022":{input:3,output:15},"claude-3-5-haiku-20241022":{input:.8,output:4},"claude-3-opus-20240229":{input:15,output:75},"gpt-4o":{input:2.5,output:10},"gpt-4o-mini":{input:.15,output:.6},"gpt-4-turbo":{input:10,output:30},"gpt-4":{input:30,output:60},"gpt-3.5-turbo":{input:.5,output:1.5},"o3-mini":{input:1.1,output:4.4}},ut={input:1,output:3},ve="decido-token-wallet",xe=500,V=class{history=[];sessionStart=Date.now();listeners=new Set;constructor(){this._loadFromStorage()}record(e){let t=e.tokensUsed??(e.inputTokens??0)+(e.outputTokens??0),s=e.inputTokens??Math.round(t*.4),r=e.outputTokens??t-s,o=this._getPricing(e.model,e.provider),a=s/1e6*o.input+r/1e6*o.output,i={provider:e.provider,model:e.model,inputTokens:s,outputTokens:r,totalTokens:t,estimatedCostUsd:a,timestamp:Date.now()};this.history.push(i),this.history.length>xe&&(this.history=this.history.slice(-xe)),this._saveToStorage(),this._emit(),console.log(`\u{1F4B0} [Wallet] ${e.provider}/${e.model}: ${t} tokens \u2248 $${a.toFixed(6)}`)}getSummary(){let e={},t=0,s=0;for(let r of this.history)t+=r.totalTokens,s+=r.estimatedCostUsd,e[r.provider]||(e[r.provider]={tokens:0,cost:0,calls:0}),e[r.provider].tokens+=r.totalTokens,e[r.provider].cost+=r.estimatedCostUsd,e[r.provider].calls+=1;return{totalTokens:t,totalCostUsd:s,totalCalls:this.history.length,byProvider:e,sessionStart:this.sessionStart}}getHistory(){return[...this.history]}getRecentHistory(e){return this.history.slice(-e)}clearHistory(){this.history=[],this.sessionStart=Date.now(),this._saveToStorage(),this._emit()}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}_getPricing(e,t){return ye[e]?ye[e]:t==="ollama"||t==="mlx"?{input:0,output:0}:ut}_saveToStorage(){try{let e=JSON.stringify({history:this.history,sessionStart:this.sessionStart});localStorage.setItem(ve,e)}catch{}}_loadFromStorage(){try{let e=localStorage.getItem(ve);if(e){let t=JSON.parse(e);this.history=t.history||[],this.sessionStart=t.sessionStart||Date.now()}}catch{}}_emit(){let e=this.getSummary();for(let t of this.listeners)try{t(e)}catch{}}},P=new V;var mt={preferredBackend:"auto",maxLatencyMs:3e4,enableFallback:!0,temperature:.7,maxTokens:512},be=!1;function Y(){be||(be=!0,E(new T),E(new _),E(new L),E(new R),console.log("\u{1F50C} [InferenceRouter] 4 providers initialized (Ollama, Gemini, Anthropic, OpenAI)"))}var k=null;async function Pe(){try{return(await import("@tauri-apps/api/core")).invoke}catch{return null}}async function D(){if(k)return k;try{let n=await Pe();if(n){let e=await n("load_provider_keys");if(e)return k=JSON.parse(e),k}}catch(n){console.warn("[InferenceRouter] Failed to load provider keys:",n)}try{let n=localStorage.getItem("macia-provider-keys");if(n)return k=JSON.parse(n),k}catch{}return k={},k}async function Q(n){k=n;try{let e=await Pe();if(e){await e("save_provider_keys",{json:JSON.stringify(n,null,2)});return}}catch(e){console.warn("[InferenceRouter] Failed to save provider keys via Tauri:",e)}try{localStorage.setItem("macia-provider-keys",JSON.stringify(n))}catch{}}async function Z(){Y();let n=await D(),e={gemini:n.gemini,anthropic:n.anthropic,openai:n.openai};for(let[t,s]of Object.entries(e))s&&($(t)?.setApiKey?.(s),console.log(`\u{1F511} [InferenceRouter] ${t} API key loaded`))}async function ee(n,e){let t=$(n);t?.setApiKey&&t.setApiKey(e);let s=await D();s[n]=e,await Q(s)}async function te(){Y();let n={};for(let e of O())n[e.id]=await e.checkStatus();return n}function pt(n){let e=n.toLowerCase();return["ejecuta","revisa","escanea","limpia","optimiza","monitorea","diagnostica","analiza el sistema","procesos","disco","memoria","red","seguridad","git","commit","deploy","backup"].some(a=>e.includes(a))?"tool-calling":["codigo","funcion","clase","error","debug","refactor","typescript","python","rust"].some(a=>e.includes(a))?"code":["analiza","compara","benchmark","rendimiento","metricas","estadisticas"].some(a=>e.includes(a))?"analysis":["genera","crea","escribe","inventa","imagina","dise\xF1a"].some(a=>e.includes(a))?"creative":"chat"}async function A(n,e={}){Y();let t={...mt,...e},s=gt(t.preferredBackend);for(let r of s){let o=$(r);if(!o)continue;let a=await o.checkStatus();if(a!=="available"){if(!t.enableFallback)throw new Error(`Provider ${o.name} is ${a}`);continue}try{let i=await o.chat(n,{model:void 0,temperature:t.temperature,maxTokens:t.maxTokens});return P.record({provider:r,model:i.model,tokensUsed:i.tokensUsed}),{...i,backend:r}}catch(i){if(console.warn(`[InferenceRouter] ${o.name} failed:`,i),!t.enableFallback)throw i}}throw new Error("No hay backends de IA disponibles. Inicia Ollama o configura una API key de Gemini.")}function gt(n){switch(n){case"ollama":return["ollama","gemini","anthropic","openai"];case"gemini":return["gemini","ollama","anthropic","openai"];case"anthropic":return["anthropic","gemini","ollama","openai"];case"openai":return["openai","gemini","ollama","anthropic"];default:return["ollama","gemini","anthropic","openai"]}}async function ne(n,e={}){let t=[{role:"user",content:n}];try{let s=await A(t,e);return{text:s.text,backend:s.backend,model:s.model,latencyMs:s.latencyMs,tokensPerSecond:s.tokensUsed?s.tokensUsed/(s.latencyMs/1e3):0,toolsInvoked:[]}}catch{return{text:"No hay backends disponibles. Inicia Ollama o configura un modelo.",backend:"auto",model:"none",latencyMs:0,tokensPerSecond:0,toolsInvoked:[]}}}var K={route:ne,routeChat:A,classifyTask:pt,initProviders:Z,getProviderStatuses:te,setProviderApiKey:ee,loadProviderKeys:D,getAllProviders:O,getProvider:$,tokenWallet:P};var we=require("@decido/sdk");async function ht(){return import("@tauri-apps/api/core")}async function ft(){return import("@tauri-apps/api/event")}var se=class{name="Anillo 0/1 (Tauri IPC)";isConnectedToCloud=!1;unlistenFns=[];connectToSwarm(e,t,s){console.log(`[Kernel/Tauri] Conexi\xF3n nativa delegada a Core-Rust para tenant: ${e}`)}async execute(e,t,s){let{invoke:r}=await ht();return r(t,{tenantId:e,...s})}async notify(e,t){try{let{sendNotification:s}=await import("@tauri-apps/plugin-notification");await s({title:e,body:t})}catch{console.log("[Kernel/Tauri] Notification plugin not available.")}}async vibrate(e){try{let{vibrate:t}=await import("@tauri-apps/plugin-haptics");await t(e)}catch{console.log("[Kernel/Tauri] Haptics not available on this platform.")}}async getEventHistory(e,t){return this.execute(e,"get_stream_history",{limit:t})}listenEvents(e){let t=null;return ft().then(({listen:s})=>{s("orchestrator-event",r=>{e({type:"ipc_in",channel:"orchestrator-event",payload:r.payload})}).then(r=>{t=r,this.unlistenFns.push(r)})}),()=>{t&&(t(),this.unlistenFns=this.unlistenFns.filter(s=>s!==t))}}},re=class{name="Sat\xE9lite Web (WebSocket)";isConnectedToCloud=!1;sockets={};eventListeners=new Set;connectToSwarm(e,t,s){if(this.sockets[e])return;let r=(0,ke.io)(t,{auth:s?{token:s}:void 0});r.on("connect",()=>{this.isConnectedToCloud=!0,console.log(`\u2705 [Kernel/WS] Sat\xE9lite conectado al Enjambre del Tenant: ${e}`)}),r.on("orchestrator-event",o=>{this.eventListeners.forEach(a=>a({type:"ipc_in",channel:"orchestrator-event",payload:{tenantId:e,...o}}))}),r.on("rpc_broadcast",o=>{console.log(`[Kernel] \u{1F310} Broadcast Recibido [${e}]:`,o),this.eventListeners.forEach(a=>a({type:"ipc_in",channel:"rpc_broadcast",payload:{tenantId:e,...o}}))}),this.sockets[e]=r}async execute(e,t,s){let r=this.sockets[e];return r?new Promise((o,a)=>{r.emit("rpc_call",{cmd:t,args:s},i=>{i.error?a(new Error(i.error)):o(i.data)})}):(console.warn(`[Kernel Mock] Executing ${t} for tenant ${e}`,s),{})}async notify(e,t){console.log(`[Notification Mock] ${e}: ${t}`)}async vibrate(e){typeof navigator<"u"&&navigator.vibrate&&navigator.vibrate(e==="heavy"?200:50)}async getEventHistory(e,t){return console.warn("[Kernel Mock] Cannot fetch stream history in Web Sandbox."),[]}listenEvents(e){return this.eventListeners.add(e),()=>this.eventListeners.delete(e)}},oe=class{logs=[];logListeners=new Set;universalListeners=new Set;transport;rateLimits=new Map;checkRateLimit(e){let t=Date.now(),s=1e3,r=50,o=this.rateLimits.get(e);return(!o||o.resetAt<t)&&(o={calls:0,resetAt:t+s},this.rateLimits.set(e,o)),o.calls++,o.calls>r?(console.warn(`[Kernel Throttle] \u{1F6A8} Command ${e} blocked (Rate limit: >50 calls/sec). Possible plugin infinite loop.`),!1):!0}constructor(){let e=typeof window<"u"&&"__TAURI_INTERNALS__"in window;this.transport=e?new se:new re,console.log(`[Kernel] Entorno Detectado. Operando en Modo ${this.transport.name}.`),this.transport.listenEvents(t=>{t.type==="ipc_in"&&(this.addLog({type:"ipc_in",channel:t.channel,payload:t.payload}),this.notifyUniversalListeners(t.payload.payload||t.payload))})}get isConnectedToCloud(){return this.transport.isConnectedToCloud}connectToSwarm(e,t,s){this.transport.connectToSwarm(e,t,s)}notifyUniversalListeners(e){let t=(0,we.createSafeEvent)(e);if(!t.success){console.error("[Kernel] \u{1F6A8} Inbound Mesh Event Failed Contract Validation:",t.error.format()),this.universalListeners.forEach(s=>s({type:"system_alert",source:"kernel-bridge",payload:{level:"error",title:"Contract Violation",message:"A received event did not match the strict schema. Ignored."}}));return}this.universalListeners.forEach(s=>s(t.data))}injectEvent(e){this.notifyUniversalListeners(e)}addLog(e){let t={id:typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():Math.random().toString(36).substring(2,9),timestamp:Date.now(),...e};this.logs=[t,...this.logs].slice(0,500),this.logListeners.forEach(s=>s(this.logs))}onLogsChange(e){return this.logListeners.add(e),e(this.logs),()=>this.logListeners.delete(e)}async execute(e,t,s){let r="system",o=e,a=t;if(typeof t=="string"&&(r=e,o=t,a=s),!this.checkRateLimit(o))throw new Error(`[Kernel Throttle] Request dropped for ${o} (Rate limit exceeded)`);let i=performance.now();try{let d=await this.transport.execute(r,o,a);return this.addLog({type:"ipc_out",channel:o,payload:{tenantId:r,...a},duration:performance.now()-i}),d}catch(d){throw this.addLog({type:"error",channel:o,payload:{tenantId:r,args:a,error:d},duration:performance.now()-i}),d}}async notify(e,t){return this.transport.notify(e,t)}async vibrate(e="medium"){return this.transport.vibrate(e)}async captureScreen(e="system"){return this.execute(e,"capture_screen")}async getMachineId(e="system"){return this.execute(e,"get_machine_id")}async initiateSingularity(e="system"){return this.execute(e,"initiate_singularity")}async broadcastMessage(e,t){return this.execute(e,"broadcast_message",{message:t})}async fetchMemories(e="system",t,s=20){return this.execute(e,"fetch_memories",{query:t,limit:s})}async storeMemory(e="system",t,s={}){return this.execute(e,"store_memory",{text:t,metadata:s})}async deleteMemory(e="system",t){return this.execute(e,"delete_memory",{id:t})}async reasonWithMLX(e,t,s){let r=t;s&&(r+=`
|
|
29
5
|
|
|
30
|
-
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
AnthropicProvider: () => AnthropicProvider,
|
|
34
|
-
GeminiProvider: () => GeminiProvider,
|
|
35
|
-
OllamaProvider: () => OllamaProvider,
|
|
36
|
-
OpenAIProvider: () => OpenAIProvider,
|
|
37
|
-
PeerNetworkPanel: () => PeerNetworkPanel,
|
|
38
|
-
StateRehydrationManager: () => StateRehydrationManager,
|
|
39
|
-
TokenWalletPanel: () => TokenWalletPanel,
|
|
40
|
-
chat: () => chat,
|
|
41
|
-
chatStream: () => chatStream,
|
|
42
|
-
clearConversationHistory: () => clearConversationHistory,
|
|
43
|
-
decryptEvent: () => decryptEvent,
|
|
44
|
-
embeddingService: () => embeddingService,
|
|
45
|
-
getAllProviders: () => getAllProviders,
|
|
46
|
-
getProviderStatuses: () => getProviderStatuses,
|
|
47
|
-
inferenceRouter: () => inferenceRouter,
|
|
48
|
-
initProviders: () => initProviders,
|
|
49
|
-
isOllamaAvailable: () => isOllamaAvailable,
|
|
50
|
-
kernel: () => kernel,
|
|
51
|
-
loadProviderKeys: () => loadProviderKeys,
|
|
52
|
-
mlxBenchmarkModel: () => benchmarkModel,
|
|
53
|
-
mlxCompareModels: () => compareModels,
|
|
54
|
-
mlxListModels: () => listAvailableModels,
|
|
55
|
-
mlxPullModel: () => pullModel,
|
|
56
|
-
mlxRunInference: () => runInference,
|
|
57
|
-
ollamaChat: () => chat,
|
|
58
|
-
ollamaChatStream: () => chatStream,
|
|
59
|
-
ollamaListModels: () => listModels,
|
|
60
|
-
parseToolCalls: () => parseToolCalls,
|
|
61
|
-
peerMesh: () => peerMesh,
|
|
62
|
-
processLocalMessage: () => processLocalMessage,
|
|
63
|
-
routeChat: () => routeChat,
|
|
64
|
-
routeInference: () => routeInference,
|
|
65
|
-
saveProviderKeys: () => saveProviderKeys,
|
|
66
|
-
setProviderApiKey: () => setProviderApiKey,
|
|
67
|
-
startLoRATraining: () => startLoRATraining,
|
|
68
|
-
stripToolCalls: () => stripToolCalls,
|
|
69
|
-
tokenWallet: () => tokenWallet,
|
|
70
|
-
usePeerMesh: () => usePeerMesh,
|
|
71
|
-
useTokenWallet: () => useTokenWallet
|
|
72
|
-
});
|
|
73
|
-
module.exports = __toCommonJS(index_exports);
|
|
74
|
-
|
|
75
|
-
// src/kernel.ts
|
|
76
|
-
var import_socket = require("socket.io-client");
|
|
77
|
-
|
|
78
|
-
// src/ai/services/providers/LLMProvider.ts
|
|
79
|
-
var providers = /* @__PURE__ */ new Map();
|
|
80
|
-
function registerProvider(provider) {
|
|
81
|
-
providers.set(provider.id, provider);
|
|
82
|
-
console.log(`\u{1F50C} [LLM] Provider registered: ${provider.name} (${provider.id})`);
|
|
83
|
-
}
|
|
84
|
-
function getProvider(id) {
|
|
85
|
-
return providers.get(id);
|
|
86
|
-
}
|
|
87
|
-
function getAllProviders() {
|
|
88
|
-
return Array.from(providers.values());
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// src/ai/services/providers/OllamaProvider.ts
|
|
92
|
-
var OLLAMA_BASE_URL = "http://localhost:11434";
|
|
93
|
-
var DEFAULT_MODEL = "qwen2:latest";
|
|
94
|
-
var OllamaProvider = class {
|
|
95
|
-
id = "ollama";
|
|
96
|
-
name = "Ollama (Local)";
|
|
97
|
-
requiresApiKey = false;
|
|
98
|
-
defaultModel = DEFAULT_MODEL;
|
|
99
|
-
async listModels() {
|
|
100
|
-
try {
|
|
101
|
-
const res = await fetch(`${OLLAMA_BASE_URL}/api/tags`);
|
|
102
|
-
if (!res.ok) return [];
|
|
103
|
-
const data = await res.json();
|
|
104
|
-
return (data.models || []).map((m) => m.name);
|
|
105
|
-
} catch {
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async checkStatus() {
|
|
110
|
-
try {
|
|
111
|
-
const res = await fetch(`${OLLAMA_BASE_URL}/api/tags`, {
|
|
112
|
-
signal: AbortSignal.timeout(2e3)
|
|
113
|
-
});
|
|
114
|
-
return res.ok ? "available" : "error";
|
|
115
|
-
} catch {
|
|
116
|
-
return "unavailable";
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
async chat(messages, options) {
|
|
120
|
-
const model = options?.model || this.defaultModel;
|
|
121
|
-
const start = Date.now();
|
|
122
|
-
const ollamaMessages = messages.map((m) => ({
|
|
123
|
-
role: m.role,
|
|
124
|
-
content: m.content
|
|
125
|
-
}));
|
|
126
|
-
const res = await fetch(`${OLLAMA_BASE_URL}/api/chat`, {
|
|
127
|
-
method: "POST",
|
|
128
|
-
headers: { "Content-Type": "application/json" },
|
|
129
|
-
body: JSON.stringify({
|
|
130
|
-
model,
|
|
131
|
-
messages: ollamaMessages,
|
|
132
|
-
stream: false,
|
|
133
|
-
options: {
|
|
134
|
-
temperature: options?.temperature ?? 0.7,
|
|
135
|
-
num_predict: options?.maxTokens ?? 512
|
|
136
|
-
}
|
|
137
|
-
}),
|
|
138
|
-
signal: options?.signal ?? AbortSignal.timeout(3e4)
|
|
139
|
-
});
|
|
140
|
-
if (!res.ok) {
|
|
141
|
-
const errorText = await res.text();
|
|
142
|
-
throw new Error(`Ollama API error ${res.status}: ${errorText}`);
|
|
143
|
-
}
|
|
144
|
-
const data = await res.json();
|
|
145
|
-
const latencyMs = Date.now() - start;
|
|
146
|
-
return {
|
|
147
|
-
text: data.message?.content || "(sin respuesta)",
|
|
148
|
-
model,
|
|
149
|
-
latencyMs,
|
|
150
|
-
tokensUsed: data.eval_count
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// src/ai/services/providers/GeminiProvider.ts
|
|
156
|
-
var GEMINI_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
157
|
-
var DEFAULT_MODEL2 = "gemini-flash-lite-latest";
|
|
158
|
-
var AVAILABLE_MODELS = [
|
|
159
|
-
"gemini-flash-lite-latest",
|
|
160
|
-
"gemini-flash-lite-latest-lite",
|
|
161
|
-
"gemini-1.5-pro",
|
|
162
|
-
"gemini-flash-latest"
|
|
163
|
-
];
|
|
164
|
-
var GeminiProvider = class {
|
|
165
|
-
id = "gemini";
|
|
166
|
-
name = "Google Gemini";
|
|
167
|
-
requiresApiKey = true;
|
|
168
|
-
defaultModel = DEFAULT_MODEL2;
|
|
169
|
-
apiKey = null;
|
|
170
|
-
setApiKey(key) {
|
|
171
|
-
this.apiKey = key;
|
|
172
|
-
}
|
|
173
|
-
async listModels() {
|
|
174
|
-
return AVAILABLE_MODELS;
|
|
175
|
-
}
|
|
176
|
-
async checkStatus() {
|
|
177
|
-
if (!this.apiKey) return "unconfigured";
|
|
178
|
-
try {
|
|
179
|
-
const res = await fetch(
|
|
180
|
-
`${GEMINI_API_BASE}/models?key=${this.apiKey}`,
|
|
181
|
-
{ signal: AbortSignal.timeout(5e3) }
|
|
182
|
-
);
|
|
183
|
-
return res.ok ? "available" : "error";
|
|
184
|
-
} catch {
|
|
185
|
-
return "unavailable";
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
async chat(messages, options) {
|
|
189
|
-
if (!this.apiKey) {
|
|
190
|
-
throw new Error("Gemini API key not configured. Set it in Settings \u2192 Providers.");
|
|
191
|
-
}
|
|
192
|
-
const model = options?.model || this.defaultModel;
|
|
193
|
-
const start = Date.now();
|
|
194
|
-
const systemMsg = messages.find((m) => m.role === "system");
|
|
195
|
-
const conversationMsgs = messages.filter((m) => m.role !== "system");
|
|
196
|
-
const contents = conversationMsgs.map((m) => ({
|
|
197
|
-
role: m.role === "assistant" ? "model" : "user",
|
|
198
|
-
parts: [{ text: m.content }]
|
|
199
|
-
}));
|
|
200
|
-
const body = {
|
|
201
|
-
contents,
|
|
202
|
-
generationConfig: {
|
|
203
|
-
temperature: options?.temperature ?? 0.7,
|
|
204
|
-
maxOutputTokens: options?.maxTokens ?? 2048
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
if (systemMsg) {
|
|
208
|
-
body.systemInstruction = {
|
|
209
|
-
parts: [{ text: systemMsg.content }]
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
const url = `${GEMINI_API_BASE}/models/${model}:generateContent?key=${this.apiKey}`;
|
|
213
|
-
const res = await fetch(url, {
|
|
214
|
-
method: "POST",
|
|
215
|
-
headers: { "Content-Type": "application/json" },
|
|
216
|
-
body: JSON.stringify(body),
|
|
217
|
-
signal: options?.signal ?? AbortSignal.timeout(6e4)
|
|
218
|
-
});
|
|
219
|
-
if (!res.ok) {
|
|
220
|
-
const errorBody = await res.text();
|
|
221
|
-
throw new Error(`Gemini API error ${res.status}: ${errorBody}`);
|
|
222
|
-
}
|
|
223
|
-
const data = await res.json();
|
|
224
|
-
const latencyMs = Date.now() - start;
|
|
225
|
-
const candidate = data.candidates?.[0];
|
|
226
|
-
const text = candidate?.content?.parts?.map((p) => p.text || "").join("") || "(sin respuesta)";
|
|
227
|
-
const tokensUsed = (data.usageMetadata?.promptTokenCount || 0) + (data.usageMetadata?.candidatesTokenCount || 0);
|
|
228
|
-
return {
|
|
229
|
-
text,
|
|
230
|
-
model,
|
|
231
|
-
latencyMs,
|
|
232
|
-
tokensUsed
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// src/ai/services/providers/AnthropicProvider.ts
|
|
238
|
-
var ANTHROPIC_API_BASE = "https://api.anthropic.com/v1";
|
|
239
|
-
var DEFAULT_MODEL3 = "claude-sonnet-4-20250514";
|
|
240
|
-
var API_VERSION = "2023-06-01";
|
|
241
|
-
var AVAILABLE_MODELS2 = [
|
|
242
|
-
"claude-sonnet-4-20250514",
|
|
243
|
-
"claude-3-5-sonnet-20241022",
|
|
244
|
-
"claude-3-5-haiku-20241022",
|
|
245
|
-
"claude-3-opus-20240229"
|
|
246
|
-
];
|
|
247
|
-
var AnthropicProvider = class {
|
|
248
|
-
id = "anthropic";
|
|
249
|
-
name = "Anthropic Claude";
|
|
250
|
-
requiresApiKey = true;
|
|
251
|
-
defaultModel = DEFAULT_MODEL3;
|
|
252
|
-
apiKey = null;
|
|
253
|
-
setApiKey(key) {
|
|
254
|
-
this.apiKey = key;
|
|
255
|
-
}
|
|
256
|
-
async listModels() {
|
|
257
|
-
return AVAILABLE_MODELS2;
|
|
258
|
-
}
|
|
259
|
-
async checkStatus() {
|
|
260
|
-
if (!this.apiKey) return "unconfigured";
|
|
261
|
-
try {
|
|
262
|
-
const res = await fetch(`${ANTHROPIC_API_BASE}/messages`, {
|
|
263
|
-
method: "POST",
|
|
264
|
-
headers: {
|
|
265
|
-
"x-api-key": this.apiKey,
|
|
266
|
-
"anthropic-version": API_VERSION,
|
|
267
|
-
"content-type": "application/json"
|
|
268
|
-
},
|
|
269
|
-
body: JSON.stringify({
|
|
270
|
-
model: this.defaultModel,
|
|
271
|
-
max_tokens: 1,
|
|
272
|
-
messages: [{ role: "user", content: "ping" }]
|
|
273
|
-
}),
|
|
274
|
-
signal: AbortSignal.timeout(5e3)
|
|
275
|
-
});
|
|
276
|
-
return res.ok || res.status === 429 ? "available" : "error";
|
|
277
|
-
} catch {
|
|
278
|
-
return "unavailable";
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
async chat(messages, options) {
|
|
282
|
-
if (!this.apiKey) {
|
|
283
|
-
throw new Error("Anthropic API key not configured. Set it in Settings \u2192 Providers.");
|
|
284
|
-
}
|
|
285
|
-
const model = options?.model || this.defaultModel;
|
|
286
|
-
const start = Date.now();
|
|
287
|
-
const systemMsg = messages.find((m) => m.role === "system");
|
|
288
|
-
const conversationMsgs = messages.filter((m) => m.role !== "system").map((m) => ({
|
|
289
|
-
role: m.role,
|
|
290
|
-
content: m.content
|
|
291
|
-
}));
|
|
292
|
-
const sanitized = this._sanitizeMessages(conversationMsgs);
|
|
293
|
-
const body = {
|
|
294
|
-
model,
|
|
295
|
-
max_tokens: options?.maxTokens ?? 2048,
|
|
296
|
-
messages: sanitized
|
|
297
|
-
};
|
|
298
|
-
if (options?.temperature !== void 0) {
|
|
299
|
-
body.temperature = options.temperature;
|
|
300
|
-
}
|
|
301
|
-
if (systemMsg) {
|
|
302
|
-
body.system = systemMsg.content;
|
|
303
|
-
}
|
|
304
|
-
const res = await fetch(`${ANTHROPIC_API_BASE}/messages`, {
|
|
305
|
-
method: "POST",
|
|
306
|
-
headers: {
|
|
307
|
-
"x-api-key": this.apiKey,
|
|
308
|
-
"anthropic-version": API_VERSION,
|
|
309
|
-
"content-type": "application/json"
|
|
310
|
-
},
|
|
311
|
-
body: JSON.stringify(body),
|
|
312
|
-
signal: options?.signal ?? AbortSignal.timeout(6e4)
|
|
313
|
-
});
|
|
314
|
-
if (!res.ok) {
|
|
315
|
-
const errorBody = await res.text();
|
|
316
|
-
throw new Error(`Anthropic API error ${res.status}: ${errorBody}`);
|
|
317
|
-
}
|
|
318
|
-
const data = await res.json();
|
|
319
|
-
const latencyMs = Date.now() - start;
|
|
320
|
-
const text = (data.content || []).filter((block) => block.type === "text").map((block) => block.text).join("") || "(sin respuesta)";
|
|
321
|
-
const tokensUsed = (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0);
|
|
322
|
-
return { text, model, latencyMs, tokensUsed };
|
|
323
|
-
}
|
|
324
|
-
/** Stream chat completion — yields text chunks */
|
|
325
|
-
async *chatStream(messages, options) {
|
|
326
|
-
if (!this.apiKey) {
|
|
327
|
-
throw new Error("Anthropic API key not configured.");
|
|
328
|
-
}
|
|
329
|
-
const model = options?.model || this.defaultModel;
|
|
330
|
-
const systemMsg = messages.find((m) => m.role === "system");
|
|
331
|
-
const conversationMsgs = messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
|
|
332
|
-
const sanitized = this._sanitizeMessages(conversationMsgs);
|
|
333
|
-
const body = {
|
|
334
|
-
model,
|
|
335
|
-
max_tokens: options?.maxTokens ?? 2048,
|
|
336
|
-
messages: sanitized,
|
|
337
|
-
stream: true
|
|
338
|
-
};
|
|
339
|
-
if (options?.temperature !== void 0) body.temperature = options.temperature;
|
|
340
|
-
if (systemMsg) body.system = systemMsg.content;
|
|
341
|
-
const res = await fetch(`${ANTHROPIC_API_BASE}/messages`, {
|
|
342
|
-
method: "POST",
|
|
343
|
-
headers: {
|
|
344
|
-
"x-api-key": this.apiKey,
|
|
345
|
-
"anthropic-version": API_VERSION,
|
|
346
|
-
"content-type": "application/json"
|
|
347
|
-
},
|
|
348
|
-
body: JSON.stringify(body),
|
|
349
|
-
signal: options?.signal ?? AbortSignal.timeout(12e4)
|
|
350
|
-
});
|
|
351
|
-
if (!res.ok) {
|
|
352
|
-
const errorBody = await res.text();
|
|
353
|
-
throw new Error(`Anthropic stream error ${res.status}: ${errorBody}`);
|
|
354
|
-
}
|
|
355
|
-
const reader = res.body?.getReader();
|
|
356
|
-
if (!reader) return;
|
|
357
|
-
const decoder = new TextDecoder();
|
|
358
|
-
let buffer = "";
|
|
359
|
-
while (true) {
|
|
360
|
-
const { done, value } = await reader.read();
|
|
361
|
-
if (done) break;
|
|
362
|
-
buffer += decoder.decode(value, { stream: true });
|
|
363
|
-
const lines = buffer.split("\n");
|
|
364
|
-
buffer = lines.pop() || "";
|
|
365
|
-
for (const line of lines) {
|
|
366
|
-
if (!line.startsWith("data: ")) continue;
|
|
367
|
-
const data = line.slice(6).trim();
|
|
368
|
-
if (data === "[DONE]") return;
|
|
369
|
-
try {
|
|
370
|
-
const event = JSON.parse(data);
|
|
371
|
-
if (event.type === "content_block_delta" && event.delta?.text) {
|
|
372
|
-
yield event.delta.text;
|
|
373
|
-
}
|
|
374
|
-
} catch {
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
/** Ensure alternating user/assistant (Anthropic requires this) */
|
|
380
|
-
_sanitizeMessages(msgs) {
|
|
381
|
-
if (msgs.length === 0) return [{ role: "user", content: "..." }];
|
|
382
|
-
const result = [];
|
|
383
|
-
let lastRole = "";
|
|
384
|
-
for (const msg of msgs) {
|
|
385
|
-
if (msg.role === lastRole) {
|
|
386
|
-
result[result.length - 1].content += "\n" + msg.content;
|
|
387
|
-
} else {
|
|
388
|
-
result.push({ ...msg });
|
|
389
|
-
lastRole = msg.role;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
if (result[0]?.role !== "user") {
|
|
393
|
-
result.unshift({ role: "user", content: "..." });
|
|
394
|
-
}
|
|
395
|
-
return result;
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// src/ai/services/providers/OpenAIProvider.ts
|
|
400
|
-
var OPENAI_API_BASE = "https://api.openai.com/v1";
|
|
401
|
-
var DEFAULT_MODEL4 = "gpt-4o";
|
|
402
|
-
var AVAILABLE_MODELS3 = [
|
|
403
|
-
"gpt-4o",
|
|
404
|
-
"gpt-4o-mini",
|
|
405
|
-
"gpt-4-turbo",
|
|
406
|
-
"gpt-4",
|
|
407
|
-
"gpt-3.5-turbo",
|
|
408
|
-
"o3-mini"
|
|
409
|
-
];
|
|
410
|
-
var OpenAIProvider = class {
|
|
411
|
-
id = "openai";
|
|
412
|
-
name = "OpenAI";
|
|
413
|
-
requiresApiKey = true;
|
|
414
|
-
defaultModel = DEFAULT_MODEL4;
|
|
415
|
-
apiKey = null;
|
|
416
|
-
baseUrl = OPENAI_API_BASE;
|
|
417
|
-
setApiKey(key) {
|
|
418
|
-
this.apiKey = key;
|
|
419
|
-
}
|
|
420
|
-
/** Override base URL for OpenAI-compatible endpoints */
|
|
421
|
-
setBaseUrl(url) {
|
|
422
|
-
this.baseUrl = url.replace(/\/$/, "");
|
|
423
|
-
}
|
|
424
|
-
async listModels() {
|
|
425
|
-
if (!this.apiKey) return AVAILABLE_MODELS3;
|
|
426
|
-
try {
|
|
427
|
-
const res = await fetch(`${this.baseUrl}/models`, {
|
|
428
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
429
|
-
signal: AbortSignal.timeout(5e3)
|
|
430
|
-
});
|
|
431
|
-
if (!res.ok) return AVAILABLE_MODELS3;
|
|
432
|
-
const data = await res.json();
|
|
433
|
-
const models = (data.data || []).map((m) => m.id).filter((id) => id.startsWith("gpt-") || id.startsWith("o"));
|
|
434
|
-
return models.length > 0 ? models : AVAILABLE_MODELS3;
|
|
435
|
-
} catch {
|
|
436
|
-
return AVAILABLE_MODELS3;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
async checkStatus() {
|
|
440
|
-
if (!this.apiKey) return "unconfigured";
|
|
441
|
-
try {
|
|
442
|
-
const res = await fetch(`${this.baseUrl}/models`, {
|
|
443
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
444
|
-
signal: AbortSignal.timeout(5e3)
|
|
445
|
-
});
|
|
446
|
-
return res.ok ? "available" : "error";
|
|
447
|
-
} catch {
|
|
448
|
-
return "unavailable";
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
async chat(messages, options) {
|
|
452
|
-
if (!this.apiKey) {
|
|
453
|
-
throw new Error("OpenAI API key not configured. Set it in Settings \u2192 Providers.");
|
|
454
|
-
}
|
|
455
|
-
const model = options?.model || this.defaultModel;
|
|
456
|
-
const start = Date.now();
|
|
457
|
-
const body = {
|
|
458
|
-
model,
|
|
459
|
-
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
460
|
-
temperature: options?.temperature ?? 0.7,
|
|
461
|
-
max_tokens: options?.maxTokens ?? 2048
|
|
462
|
-
};
|
|
463
|
-
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
464
|
-
method: "POST",
|
|
465
|
-
headers: {
|
|
466
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
467
|
-
"Content-Type": "application/json"
|
|
468
|
-
},
|
|
469
|
-
body: JSON.stringify(body),
|
|
470
|
-
signal: options?.signal ?? AbortSignal.timeout(6e4)
|
|
471
|
-
});
|
|
472
|
-
if (!res.ok) {
|
|
473
|
-
const errorBody = await res.text();
|
|
474
|
-
throw new Error(`OpenAI API error ${res.status}: ${errorBody}`);
|
|
475
|
-
}
|
|
476
|
-
const data = await res.json();
|
|
477
|
-
const latencyMs = Date.now() - start;
|
|
478
|
-
const text = data.choices?.[0]?.message?.content || "(sin respuesta)";
|
|
479
|
-
const tokensUsed = (data.usage?.prompt_tokens || 0) + (data.usage?.completion_tokens || 0);
|
|
480
|
-
return { text, model, latencyMs, tokensUsed };
|
|
481
|
-
}
|
|
482
|
-
/** Stream chat completion — yields text chunks */
|
|
483
|
-
async *chatStream(messages, options) {
|
|
484
|
-
if (!this.apiKey) {
|
|
485
|
-
throw new Error("OpenAI API key not configured.");
|
|
486
|
-
}
|
|
487
|
-
const model = options?.model || this.defaultModel;
|
|
488
|
-
const body = {
|
|
489
|
-
model,
|
|
490
|
-
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
491
|
-
temperature: options?.temperature ?? 0.7,
|
|
492
|
-
max_tokens: options?.maxTokens ?? 2048,
|
|
493
|
-
stream: true
|
|
494
|
-
};
|
|
495
|
-
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
496
|
-
method: "POST",
|
|
497
|
-
headers: {
|
|
498
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
499
|
-
"Content-Type": "application/json"
|
|
500
|
-
},
|
|
501
|
-
body: JSON.stringify(body),
|
|
502
|
-
signal: options?.signal ?? AbortSignal.timeout(12e4)
|
|
503
|
-
});
|
|
504
|
-
if (!res.ok) {
|
|
505
|
-
const errorBody = await res.text();
|
|
506
|
-
throw new Error(`OpenAI stream error ${res.status}: ${errorBody}`);
|
|
507
|
-
}
|
|
508
|
-
const reader = res.body?.getReader();
|
|
509
|
-
if (!reader) return;
|
|
510
|
-
const decoder = new TextDecoder();
|
|
511
|
-
let buffer = "";
|
|
512
|
-
while (true) {
|
|
513
|
-
const { done, value } = await reader.read();
|
|
514
|
-
if (done) break;
|
|
515
|
-
buffer += decoder.decode(value, { stream: true });
|
|
516
|
-
const lines = buffer.split("\n");
|
|
517
|
-
buffer = lines.pop() || "";
|
|
518
|
-
for (const line of lines) {
|
|
519
|
-
if (!line.startsWith("data: ")) continue;
|
|
520
|
-
const data = line.slice(6).trim();
|
|
521
|
-
if (data === "[DONE]") return;
|
|
522
|
-
try {
|
|
523
|
-
const chunk = JSON.parse(data);
|
|
524
|
-
const delta = chunk.choices?.[0]?.delta?.content;
|
|
525
|
-
if (delta) yield delta;
|
|
526
|
-
} catch {
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
// src/ai/services/TokenWallet.ts
|
|
534
|
-
var PRICING = {
|
|
535
|
-
// Ollama / local — free
|
|
536
|
-
"qwen2:latest": { input: 0, output: 0 },
|
|
537
|
-
"llama3:latest": { input: 0, output: 0 },
|
|
538
|
-
"mistral:latest": { input: 0, output: 0 },
|
|
539
|
-
"codellama:latest": { input: 0, output: 0 },
|
|
540
|
-
// Gemini
|
|
541
|
-
"gemini-flash-lite-latest": { input: 0.1, output: 0.4 },
|
|
542
|
-
"gemini-flash-lite-latest-lite": { input: 0.075, output: 0.3 },
|
|
543
|
-
"gemini-1.5-pro": { input: 1.25, output: 5 },
|
|
544
|
-
"gemini-flash-latest": { input: 0.075, output: 0.3 },
|
|
545
|
-
// Anthropic
|
|
546
|
-
"claude-sonnet-4-20250514": { input: 3, output: 15 },
|
|
547
|
-
"claude-3-5-sonnet-20241022": { input: 3, output: 15 },
|
|
548
|
-
"claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
|
|
549
|
-
"claude-3-opus-20240229": { input: 15, output: 75 },
|
|
550
|
-
// OpenAI
|
|
551
|
-
"gpt-4o": { input: 2.5, output: 10 },
|
|
552
|
-
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
553
|
-
"gpt-4-turbo": { input: 10, output: 30 },
|
|
554
|
-
"gpt-4": { input: 30, output: 60 },
|
|
555
|
-
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
556
|
-
"o3-mini": { input: 1.1, output: 4.4 }
|
|
557
|
-
};
|
|
558
|
-
var DEFAULT_PRICING = { input: 1, output: 3 };
|
|
559
|
-
var STORAGE_KEY = "decido-token-wallet";
|
|
560
|
-
var MAX_HISTORY = 500;
|
|
561
|
-
var TokenWalletImpl = class {
|
|
562
|
-
history = [];
|
|
563
|
-
sessionStart = Date.now();
|
|
564
|
-
listeners = /* @__PURE__ */ new Set();
|
|
565
|
-
constructor() {
|
|
566
|
-
this._loadFromStorage();
|
|
567
|
-
}
|
|
568
|
-
// ── Record Usage ────────────────────────────────────────
|
|
569
|
-
record(entry) {
|
|
570
|
-
const totalTokens = entry.tokensUsed ?? (entry.inputTokens ?? 0) + (entry.outputTokens ?? 0);
|
|
571
|
-
const inputTokens = entry.inputTokens ?? Math.round(totalTokens * 0.4);
|
|
572
|
-
const outputTokens = entry.outputTokens ?? totalTokens - inputTokens;
|
|
573
|
-
const pricing = this._getPricing(entry.model, entry.provider);
|
|
574
|
-
const estimatedCostUsd = inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
|
|
575
|
-
const usageEntry = {
|
|
576
|
-
provider: entry.provider,
|
|
577
|
-
model: entry.model,
|
|
578
|
-
inputTokens,
|
|
579
|
-
outputTokens,
|
|
580
|
-
totalTokens,
|
|
581
|
-
estimatedCostUsd,
|
|
582
|
-
timestamp: Date.now()
|
|
583
|
-
};
|
|
584
|
-
this.history.push(usageEntry);
|
|
585
|
-
if (this.history.length > MAX_HISTORY) {
|
|
586
|
-
this.history = this.history.slice(-MAX_HISTORY);
|
|
587
|
-
}
|
|
588
|
-
this._saveToStorage();
|
|
589
|
-
this._emit();
|
|
590
|
-
console.log(
|
|
591
|
-
`\u{1F4B0} [Wallet] ${entry.provider}/${entry.model}: ${totalTokens} tokens \u2248 $${estimatedCostUsd.toFixed(6)}`
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
// ── Summary ─────────────────────────────────────────────
|
|
595
|
-
getSummary() {
|
|
596
|
-
const byProvider = {};
|
|
597
|
-
let totalTokens = 0;
|
|
598
|
-
let totalCostUsd = 0;
|
|
599
|
-
for (const entry of this.history) {
|
|
600
|
-
totalTokens += entry.totalTokens;
|
|
601
|
-
totalCostUsd += entry.estimatedCostUsd;
|
|
602
|
-
if (!byProvider[entry.provider]) {
|
|
603
|
-
byProvider[entry.provider] = { tokens: 0, cost: 0, calls: 0 };
|
|
604
|
-
}
|
|
605
|
-
byProvider[entry.provider].tokens += entry.totalTokens;
|
|
606
|
-
byProvider[entry.provider].cost += entry.estimatedCostUsd;
|
|
607
|
-
byProvider[entry.provider].calls += 1;
|
|
608
|
-
}
|
|
609
|
-
return {
|
|
610
|
-
totalTokens,
|
|
611
|
-
totalCostUsd,
|
|
612
|
-
totalCalls: this.history.length,
|
|
613
|
-
byProvider,
|
|
614
|
-
sessionStart: this.sessionStart
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
getHistory() {
|
|
618
|
-
return [...this.history];
|
|
619
|
-
}
|
|
620
|
-
getRecentHistory(count) {
|
|
621
|
-
return this.history.slice(-count);
|
|
622
|
-
}
|
|
623
|
-
// ── Lifecycle ───────────────────────────────────────────
|
|
624
|
-
clearHistory() {
|
|
625
|
-
this.history = [];
|
|
626
|
-
this.sessionStart = Date.now();
|
|
627
|
-
this._saveToStorage();
|
|
628
|
-
this._emit();
|
|
629
|
-
}
|
|
630
|
-
// ── Subscriptions ───────────────────────────────────────
|
|
631
|
-
subscribe(listener) {
|
|
632
|
-
this.listeners.add(listener);
|
|
633
|
-
return () => {
|
|
634
|
-
this.listeners.delete(listener);
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
// ── Pricing Lookup ──────────────────────────────────────
|
|
638
|
-
_getPricing(model, provider) {
|
|
639
|
-
if (PRICING[model]) return PRICING[model];
|
|
640
|
-
if (provider === "ollama" || provider === "mlx") {
|
|
641
|
-
return { input: 0, output: 0 };
|
|
642
|
-
}
|
|
643
|
-
return DEFAULT_PRICING;
|
|
644
|
-
}
|
|
645
|
-
// ── Persistence ─────────────────────────────────────────
|
|
646
|
-
_saveToStorage() {
|
|
647
|
-
try {
|
|
648
|
-
const data = JSON.stringify({
|
|
649
|
-
history: this.history,
|
|
650
|
-
sessionStart: this.sessionStart
|
|
651
|
-
});
|
|
652
|
-
localStorage.setItem(STORAGE_KEY, data);
|
|
653
|
-
} catch {
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
_loadFromStorage() {
|
|
657
|
-
try {
|
|
658
|
-
const raw = localStorage.getItem(STORAGE_KEY);
|
|
659
|
-
if (raw) {
|
|
660
|
-
const data = JSON.parse(raw);
|
|
661
|
-
this.history = data.history || [];
|
|
662
|
-
this.sessionStart = data.sessionStart || Date.now();
|
|
663
|
-
}
|
|
664
|
-
} catch {
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
// ── Emit ────────────────────────────────────────────────
|
|
668
|
-
_emit() {
|
|
669
|
-
const summary = this.getSummary();
|
|
670
|
-
for (const listener of this.listeners) {
|
|
671
|
-
try {
|
|
672
|
-
listener(summary);
|
|
673
|
-
} catch {
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
};
|
|
678
|
-
var tokenWallet = new TokenWalletImpl();
|
|
679
|
-
|
|
680
|
-
// src/ai/services/InferenceRouter.ts
|
|
681
|
-
var DEFAULT_CONFIG = {
|
|
682
|
-
preferredBackend: "auto",
|
|
683
|
-
maxLatencyMs: 3e4,
|
|
684
|
-
enableFallback: true,
|
|
685
|
-
temperature: 0.7,
|
|
686
|
-
maxTokens: 512
|
|
687
|
-
};
|
|
688
|
-
var _initialized = false;
|
|
689
|
-
function ensureProviders() {
|
|
690
|
-
if (_initialized) return;
|
|
691
|
-
_initialized = true;
|
|
692
|
-
registerProvider(new OllamaProvider());
|
|
693
|
-
registerProvider(new GeminiProvider());
|
|
694
|
-
registerProvider(new AnthropicProvider());
|
|
695
|
-
registerProvider(new OpenAIProvider());
|
|
696
|
-
console.log("\u{1F50C} [InferenceRouter] 4 providers initialized (Ollama, Gemini, Anthropic, OpenAI)");
|
|
697
|
-
}
|
|
698
|
-
var _cachedKeys = null;
|
|
699
|
-
async function getInvoke() {
|
|
700
|
-
try {
|
|
701
|
-
const tauri = await import("@tauri-apps/api/core");
|
|
702
|
-
return tauri.invoke;
|
|
703
|
-
} catch {
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
async function loadProviderKeys() {
|
|
708
|
-
if (_cachedKeys) return _cachedKeys;
|
|
709
|
-
try {
|
|
710
|
-
const invoke = await getInvoke();
|
|
711
|
-
if (invoke) {
|
|
712
|
-
const raw = await invoke("load_provider_keys");
|
|
713
|
-
if (raw) {
|
|
714
|
-
_cachedKeys = JSON.parse(raw);
|
|
715
|
-
return _cachedKeys;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
} catch (e) {
|
|
719
|
-
console.warn("[InferenceRouter] Failed to load provider keys:", e);
|
|
720
|
-
}
|
|
721
|
-
try {
|
|
722
|
-
const stored = localStorage.getItem("macia-provider-keys");
|
|
723
|
-
if (stored) {
|
|
724
|
-
_cachedKeys = JSON.parse(stored);
|
|
725
|
-
return _cachedKeys;
|
|
726
|
-
}
|
|
727
|
-
} catch {
|
|
728
|
-
}
|
|
729
|
-
_cachedKeys = {};
|
|
730
|
-
return _cachedKeys;
|
|
731
|
-
}
|
|
732
|
-
async function saveProviderKeys(keys) {
|
|
733
|
-
_cachedKeys = keys;
|
|
734
|
-
try {
|
|
735
|
-
const invoke = await getInvoke();
|
|
736
|
-
if (invoke) {
|
|
737
|
-
await invoke("save_provider_keys", {
|
|
738
|
-
json: JSON.stringify(keys, null, 2)
|
|
739
|
-
});
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
} catch (e) {
|
|
743
|
-
console.warn("[InferenceRouter] Failed to save provider keys via Tauri:", e);
|
|
744
|
-
}
|
|
745
|
-
try {
|
|
746
|
-
localStorage.setItem("macia-provider-keys", JSON.stringify(keys));
|
|
747
|
-
} catch {
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
async function initProviders() {
|
|
751
|
-
ensureProviders();
|
|
752
|
-
const keys = await loadProviderKeys();
|
|
753
|
-
const providerKeyMap = {
|
|
754
|
-
gemini: keys.gemini,
|
|
755
|
-
anthropic: keys.anthropic,
|
|
756
|
-
openai: keys.openai
|
|
757
|
-
};
|
|
758
|
-
for (const [id, key] of Object.entries(providerKeyMap)) {
|
|
759
|
-
if (key) {
|
|
760
|
-
const provider = getProvider(id);
|
|
761
|
-
provider?.setApiKey?.(key);
|
|
762
|
-
console.log(`\u{1F511} [InferenceRouter] ${id} API key loaded`);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
async function setProviderApiKey(providerId, apiKey) {
|
|
767
|
-
const provider = getProvider(providerId);
|
|
768
|
-
if (provider?.setApiKey) {
|
|
769
|
-
provider.setApiKey(apiKey);
|
|
770
|
-
}
|
|
771
|
-
const keys = await loadProviderKeys();
|
|
772
|
-
keys[providerId] = apiKey;
|
|
773
|
-
await saveProviderKeys(keys);
|
|
774
|
-
}
|
|
775
|
-
async function getProviderStatuses() {
|
|
776
|
-
ensureProviders();
|
|
777
|
-
const statuses = {};
|
|
778
|
-
for (const provider of getAllProviders()) {
|
|
779
|
-
statuses[provider.id] = await provider.checkStatus();
|
|
780
|
-
}
|
|
781
|
-
return statuses;
|
|
782
|
-
}
|
|
783
|
-
function classifyTask(prompt) {
|
|
784
|
-
const lower = prompt.toLowerCase();
|
|
785
|
-
const toolPatterns = [
|
|
786
|
-
"ejecuta",
|
|
787
|
-
"revisa",
|
|
788
|
-
"escanea",
|
|
789
|
-
"limpia",
|
|
790
|
-
"optimiza",
|
|
791
|
-
"monitorea",
|
|
792
|
-
"diagnostica",
|
|
793
|
-
"analiza el sistema",
|
|
794
|
-
"procesos",
|
|
795
|
-
"disco",
|
|
796
|
-
"memoria",
|
|
797
|
-
"red",
|
|
798
|
-
"seguridad",
|
|
799
|
-
"git",
|
|
800
|
-
"commit",
|
|
801
|
-
"deploy",
|
|
802
|
-
"backup"
|
|
803
|
-
];
|
|
804
|
-
if (toolPatterns.some((p) => lower.includes(p))) return "tool-calling";
|
|
805
|
-
const codePatterns = ["codigo", "funcion", "clase", "error", "debug", "refactor", "typescript", "python", "rust"];
|
|
806
|
-
if (codePatterns.some((p) => lower.includes(p))) return "code";
|
|
807
|
-
const analysisPatterns = ["analiza", "compara", "benchmark", "rendimiento", "metricas", "estadisticas"];
|
|
808
|
-
if (analysisPatterns.some((p) => lower.includes(p))) return "analysis";
|
|
809
|
-
const creativePatterns = ["genera", "crea", "escribe", "inventa", "imagina", "dise\xF1a"];
|
|
810
|
-
if (creativePatterns.some((p) => lower.includes(p))) return "creative";
|
|
811
|
-
return "chat";
|
|
812
|
-
}
|
|
813
|
-
async function routeChat(messages, config = {}) {
|
|
814
|
-
ensureProviders();
|
|
815
|
-
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
816
|
-
const providerOrder = resolveProviderOrder(cfg.preferredBackend);
|
|
817
|
-
for (const providerId of providerOrder) {
|
|
818
|
-
const provider = getProvider(providerId);
|
|
819
|
-
if (!provider) continue;
|
|
820
|
-
const status = await provider.checkStatus();
|
|
821
|
-
if (status !== "available") {
|
|
822
|
-
if (!cfg.enableFallback) {
|
|
823
|
-
throw new Error(`Provider ${provider.name} is ${status}`);
|
|
824
|
-
}
|
|
825
|
-
continue;
|
|
826
|
-
}
|
|
827
|
-
try {
|
|
828
|
-
const result = await provider.chat(messages, {
|
|
829
|
-
model: void 0,
|
|
830
|
-
// use provider default
|
|
831
|
-
temperature: cfg.temperature,
|
|
832
|
-
maxTokens: cfg.maxTokens
|
|
833
|
-
});
|
|
834
|
-
tokenWallet.record({
|
|
835
|
-
provider: providerId,
|
|
836
|
-
model: result.model,
|
|
837
|
-
tokensUsed: result.tokensUsed
|
|
838
|
-
});
|
|
839
|
-
return {
|
|
840
|
-
...result,
|
|
841
|
-
backend: providerId
|
|
842
|
-
};
|
|
843
|
-
} catch (err) {
|
|
844
|
-
console.warn(`[InferenceRouter] ${provider.name} failed:`, err);
|
|
845
|
-
if (!cfg.enableFallback) throw err;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
throw new Error("No hay backends de IA disponibles. Inicia Ollama o configura una API key de Gemini.");
|
|
849
|
-
}
|
|
850
|
-
function resolveProviderOrder(preferred) {
|
|
851
|
-
switch (preferred) {
|
|
852
|
-
case "ollama":
|
|
853
|
-
return ["ollama", "gemini", "anthropic", "openai"];
|
|
854
|
-
case "gemini":
|
|
855
|
-
return ["gemini", "ollama", "anthropic", "openai"];
|
|
856
|
-
case "anthropic":
|
|
857
|
-
return ["anthropic", "gemini", "ollama", "openai"];
|
|
858
|
-
case "openai":
|
|
859
|
-
return ["openai", "gemini", "ollama", "anthropic"];
|
|
860
|
-
case "auto":
|
|
861
|
-
default:
|
|
862
|
-
return ["ollama", "gemini", "anthropic", "openai"];
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
async function routeInference(prompt, config = {}) {
|
|
866
|
-
const messages = [
|
|
867
|
-
{ role: "user", content: prompt }
|
|
868
|
-
];
|
|
869
|
-
try {
|
|
870
|
-
const result = await routeChat(messages, config);
|
|
871
|
-
return {
|
|
872
|
-
text: result.text,
|
|
873
|
-
backend: result.backend,
|
|
874
|
-
model: result.model,
|
|
875
|
-
latencyMs: result.latencyMs,
|
|
876
|
-
tokensPerSecond: result.tokensUsed ? result.tokensUsed / (result.latencyMs / 1e3) : 0,
|
|
877
|
-
toolsInvoked: []
|
|
878
|
-
};
|
|
879
|
-
} catch {
|
|
880
|
-
return {
|
|
881
|
-
text: "No hay backends disponibles. Inicia Ollama o configura un modelo.",
|
|
882
|
-
backend: "auto",
|
|
883
|
-
model: "none",
|
|
884
|
-
latencyMs: 0,
|
|
885
|
-
tokensPerSecond: 0,
|
|
886
|
-
toolsInvoked: []
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
var inferenceRouter = {
|
|
891
|
-
route: routeInference,
|
|
892
|
-
routeChat,
|
|
893
|
-
classifyTask,
|
|
894
|
-
initProviders,
|
|
895
|
-
getProviderStatuses,
|
|
896
|
-
setProviderApiKey,
|
|
897
|
-
loadProviderKeys,
|
|
898
|
-
getAllProviders,
|
|
899
|
-
getProvider,
|
|
900
|
-
tokenWallet
|
|
901
|
-
};
|
|
902
|
-
|
|
903
|
-
// src/kernel.ts
|
|
904
|
-
var import_sdk = require("@decido/sdk");
|
|
905
|
-
async function getTauriCore() {
|
|
906
|
-
return import("@tauri-apps/api/core");
|
|
907
|
-
}
|
|
908
|
-
async function getTauriEvent() {
|
|
909
|
-
return import("@tauri-apps/api/event");
|
|
910
|
-
}
|
|
911
|
-
var TauriTransport = class {
|
|
912
|
-
name = "Anillo 0/1 (Tauri IPC)";
|
|
913
|
-
isConnectedToCloud = false;
|
|
914
|
-
unlistenFns = [];
|
|
915
|
-
connectToSwarm(tenantId, url, token) {
|
|
916
|
-
console.log(`[Kernel/Tauri] Conexi\xF3n nativa delegada a Core-Rust para tenant: ${tenantId}`);
|
|
917
|
-
}
|
|
918
|
-
async execute(tenantId, cmd, args) {
|
|
919
|
-
const { invoke } = await getTauriCore();
|
|
920
|
-
return invoke(cmd, { tenantId, ...args });
|
|
921
|
-
}
|
|
922
|
-
async notify(title, body) {
|
|
923
|
-
try {
|
|
924
|
-
const { sendNotification } = await import("@tauri-apps/plugin-notification");
|
|
925
|
-
await sendNotification({ title, body });
|
|
926
|
-
} catch (e) {
|
|
927
|
-
console.log("[Kernel/Tauri] Notification plugin not available.");
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
async vibrate(pattern) {
|
|
931
|
-
try {
|
|
932
|
-
const { vibrate: nativeVibrate } = await import("@tauri-apps/plugin-haptics");
|
|
933
|
-
await nativeVibrate(pattern);
|
|
934
|
-
} catch (e) {
|
|
935
|
-
console.log("[Kernel/Tauri] Haptics not available on this platform.");
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
async getEventHistory(tenantId, limit) {
|
|
939
|
-
return this.execute(tenantId, "get_stream_history", { limit });
|
|
940
|
-
}
|
|
941
|
-
listenEvents(onEvent) {
|
|
942
|
-
let unlistenFn = null;
|
|
943
|
-
getTauriEvent().then(({ listen }) => {
|
|
944
|
-
listen("orchestrator-event", (event) => {
|
|
945
|
-
onEvent({ type: "ipc_in", channel: "orchestrator-event", payload: event.payload });
|
|
946
|
-
}).then((fn) => {
|
|
947
|
-
unlistenFn = fn;
|
|
948
|
-
this.unlistenFns.push(fn);
|
|
949
|
-
});
|
|
950
|
-
});
|
|
951
|
-
return () => {
|
|
952
|
-
if (unlistenFn) {
|
|
953
|
-
unlistenFn();
|
|
954
|
-
this.unlistenFns = this.unlistenFns.filter((f) => f !== unlistenFn);
|
|
955
|
-
}
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
var WebSocketTransport = class {
|
|
960
|
-
name = "Sat\xE9lite Web (WebSocket)";
|
|
961
|
-
isConnectedToCloud = false;
|
|
962
|
-
sockets = {};
|
|
963
|
-
eventListeners = /* @__PURE__ */ new Set();
|
|
964
|
-
connectToSwarm(tenantId, url, token) {
|
|
965
|
-
if (this.sockets[tenantId]) return;
|
|
966
|
-
const socket = (0, import_socket.io)(url, { auth: token ? { token } : void 0 });
|
|
967
|
-
socket.on("connect", () => {
|
|
968
|
-
this.isConnectedToCloud = true;
|
|
969
|
-
console.log(`\u2705 [Kernel/WS] Sat\xE9lite conectado al Enjambre del Tenant: ${tenantId}`);
|
|
970
|
-
});
|
|
971
|
-
socket.on("orchestrator-event", (eventPayload) => {
|
|
972
|
-
this.eventListeners.forEach((cb) => cb({ type: "ipc_in", channel: "orchestrator-event", payload: { tenantId, ...eventPayload } }));
|
|
973
|
-
});
|
|
974
|
-
socket.on("rpc_broadcast", (eventPayload) => {
|
|
975
|
-
console.log(`[Kernel] \u{1F310} Broadcast Recibido [${tenantId}]:`, eventPayload);
|
|
976
|
-
this.eventListeners.forEach((cb) => cb({ type: "ipc_in", channel: "rpc_broadcast", payload: { tenantId, ...eventPayload } }));
|
|
977
|
-
});
|
|
978
|
-
this.sockets[tenantId] = socket;
|
|
979
|
-
}
|
|
980
|
-
async execute(tenantId, cmd, args) {
|
|
981
|
-
const socket = this.sockets[tenantId];
|
|
982
|
-
if (socket) {
|
|
983
|
-
return new Promise((resolve, reject) => {
|
|
984
|
-
socket.emit("rpc_call", { cmd, args }, (response) => {
|
|
985
|
-
if (response.error) reject(new Error(response.error));
|
|
986
|
-
else resolve(response.data);
|
|
987
|
-
});
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
console.warn(`[Kernel Mock] Executing ${cmd} for tenant ${tenantId}`, args);
|
|
991
|
-
return {};
|
|
992
|
-
}
|
|
993
|
-
async notify(title, body) {
|
|
994
|
-
console.log(`[Notification Mock] ${title}: ${body}`);
|
|
995
|
-
}
|
|
996
|
-
async vibrate(pattern) {
|
|
997
|
-
if (typeof navigator !== "undefined" && navigator.vibrate) {
|
|
998
|
-
navigator.vibrate(pattern === "heavy" ? 200 : 50);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
async getEventHistory(tenantId, limit) {
|
|
1002
|
-
console.warn("[Kernel Mock] Cannot fetch stream history in Web Sandbox.");
|
|
1003
|
-
return [];
|
|
1004
|
-
}
|
|
1005
|
-
listenEvents(onEvent) {
|
|
1006
|
-
this.eventListeners.add(onEvent);
|
|
1007
|
-
return () => this.eventListeners.delete(onEvent);
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
var DecidoKernel = class {
|
|
1011
|
-
logs = [];
|
|
1012
|
-
logListeners = /* @__PURE__ */ new Set();
|
|
1013
|
-
universalListeners = /* @__PURE__ */ new Set();
|
|
1014
|
-
transport;
|
|
1015
|
-
rateLimits = /* @__PURE__ */ new Map();
|
|
1016
|
-
checkRateLimit(cmd) {
|
|
1017
|
-
const now = Date.now();
|
|
1018
|
-
const limitWin = 1e3;
|
|
1019
|
-
const maxCalls = 50;
|
|
1020
|
-
let tracker = this.rateLimits.get(cmd);
|
|
1021
|
-
if (!tracker || tracker.resetAt < now) {
|
|
1022
|
-
tracker = { calls: 0, resetAt: now + limitWin };
|
|
1023
|
-
this.rateLimits.set(cmd, tracker);
|
|
1024
|
-
}
|
|
1025
|
-
tracker.calls++;
|
|
1026
|
-
if (tracker.calls > maxCalls) {
|
|
1027
|
-
console.warn(`[Kernel Throttle] \u{1F6A8} Command ${cmd} blocked (Rate limit: >50 calls/sec). Possible plugin infinite loop.`);
|
|
1028
|
-
return false;
|
|
1029
|
-
}
|
|
1030
|
-
return true;
|
|
1031
|
-
}
|
|
1032
|
-
constructor() {
|
|
1033
|
-
const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
|
1034
|
-
this.transport = isTauri ? new TauriTransport() : new WebSocketTransport();
|
|
1035
|
-
console.log(`[Kernel] Entorno Detectado. Operando en Modo ${this.transport.name}.`);
|
|
1036
|
-
this.transport.listenEvents((event) => {
|
|
1037
|
-
if (event.type === "ipc_in") {
|
|
1038
|
-
this.addLog({ type: "ipc_in", channel: event.channel, payload: event.payload });
|
|
1039
|
-
this.notifyUniversalListeners(event.payload.payload || event.payload);
|
|
1040
|
-
}
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
get isConnectedToCloud() {
|
|
1044
|
-
return this.transport.isConnectedToCloud;
|
|
1045
|
-
}
|
|
1046
|
-
connectToSwarm(tenantId, url, token) {
|
|
1047
|
-
this.transport.connectToSwarm(tenantId, url, token);
|
|
1048
|
-
}
|
|
1049
|
-
// Validador Zod y Notificador Universal
|
|
1050
|
-
notifyUniversalListeners(rawPayload) {
|
|
1051
|
-
const parsed = (0, import_sdk.createSafeEvent)(rawPayload);
|
|
1052
|
-
if (!parsed.success) {
|
|
1053
|
-
console.error("[Kernel] \u{1F6A8} Inbound Mesh Event Failed Contract Validation:", parsed.error.format());
|
|
1054
|
-
this.universalListeners.forEach((cb) => cb({
|
|
1055
|
-
type: "system_alert",
|
|
1056
|
-
source: "kernel-bridge",
|
|
1057
|
-
payload: {
|
|
1058
|
-
level: "error",
|
|
1059
|
-
title: "Contract Violation",
|
|
1060
|
-
message: "A received event did not match the strict schema. Ignored."
|
|
1061
|
-
}
|
|
1062
|
-
}));
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
this.universalListeners.forEach((cb) => cb(parsed.data));
|
|
1066
|
-
}
|
|
1067
|
-
injectEvent(payload) {
|
|
1068
|
-
this.notifyUniversalListeners(payload);
|
|
1069
|
-
}
|
|
1070
|
-
addLog(entry) {
|
|
1071
|
-
const log = {
|
|
1072
|
-
id: typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 9),
|
|
1073
|
-
timestamp: Date.now(),
|
|
1074
|
-
...entry
|
|
1075
|
-
};
|
|
1076
|
-
this.logs = [log, ...this.logs].slice(0, 500);
|
|
1077
|
-
this.logListeners.forEach((listener) => listener(this.logs));
|
|
1078
|
-
}
|
|
1079
|
-
onLogsChange(callback) {
|
|
1080
|
-
this.logListeners.add(callback);
|
|
1081
|
-
callback(this.logs);
|
|
1082
|
-
return () => this.logListeners.delete(callback);
|
|
1083
|
-
}
|
|
1084
|
-
async execute(tenantIdOrCmd, cmdOrArgs, argsOpts) {
|
|
1085
|
-
let tenantId = "system";
|
|
1086
|
-
let cmd = tenantIdOrCmd;
|
|
1087
|
-
let args = cmdOrArgs;
|
|
1088
|
-
if (typeof cmdOrArgs === "string") {
|
|
1089
|
-
tenantId = tenantIdOrCmd;
|
|
1090
|
-
cmd = cmdOrArgs;
|
|
1091
|
-
args = argsOpts;
|
|
1092
|
-
}
|
|
1093
|
-
if (!this.checkRateLimit(cmd)) {
|
|
1094
|
-
throw new Error(`[Kernel Throttle] Request dropped for ${cmd} (Rate limit exceeded)`);
|
|
1095
|
-
}
|
|
1096
|
-
const start = performance.now();
|
|
1097
|
-
try {
|
|
1098
|
-
const result = await this.transport.execute(tenantId, cmd, args);
|
|
1099
|
-
this.addLog({ type: "ipc_out", channel: cmd, payload: { tenantId, ...args }, duration: performance.now() - start });
|
|
1100
|
-
return result;
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
this.addLog({ type: "error", channel: cmd, payload: { tenantId, args, error }, duration: performance.now() - start });
|
|
1103
|
-
throw error;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
async notify(title, body) {
|
|
1107
|
-
return this.transport.notify(title, body);
|
|
1108
|
-
}
|
|
1109
|
-
async vibrate(pattern = "medium") {
|
|
1110
|
-
return this.transport.vibrate(pattern);
|
|
1111
|
-
}
|
|
1112
|
-
async captureScreen(tenantId = "system") {
|
|
1113
|
-
return this.execute(tenantId, "capture_screen");
|
|
1114
|
-
}
|
|
1115
|
-
async getMachineId(tenantId = "system") {
|
|
1116
|
-
return this.execute(tenantId, "get_machine_id");
|
|
1117
|
-
}
|
|
1118
|
-
async initiateSingularity(tenantId = "system") {
|
|
1119
|
-
return this.execute(tenantId, "initiate_singularity");
|
|
1120
|
-
}
|
|
1121
|
-
async broadcastMessage(tenantId, message) {
|
|
1122
|
-
return this.execute(tenantId, "broadcast_message", { message });
|
|
1123
|
-
}
|
|
1124
|
-
async fetchMemories(tenantId = "system", query, limit = 20) {
|
|
1125
|
-
return this.execute(tenantId, "fetch_memories", { query, limit });
|
|
1126
|
-
}
|
|
1127
|
-
async storeMemory(tenantId = "system", text, metadata = {}) {
|
|
1128
|
-
return this.execute(tenantId, "store_memory", { text, metadata });
|
|
1129
|
-
}
|
|
1130
|
-
async deleteMemory(tenantId = "system", id) {
|
|
1131
|
-
return this.execute(tenantId, "delete_memory", { id });
|
|
1132
|
-
}
|
|
1133
|
-
async reasonWithMLX(base64Image, prompt, previousError) {
|
|
1134
|
-
let finalPrompt = prompt;
|
|
1135
|
-
if (previousError) {
|
|
1136
|
-
finalPrompt += `
|
|
1137
|
-
|
|
1138
|
-
WARNING: Your previous response failed to parse as JSON with the following error: ${previousError}. Fix the JSON syntax and return ONLY a valid JSON object.`;
|
|
1139
|
-
}
|
|
1140
|
-
try {
|
|
1141
|
-
const response = await fetch("http://127.0.0.1:8080/v1/chat/completions", {
|
|
1142
|
-
method: "POST",
|
|
1143
|
-
headers: { "Content-Type": "application/json" },
|
|
1144
|
-
body: JSON.stringify({
|
|
1145
|
-
model: "llava",
|
|
1146
|
-
messages: [{
|
|
1147
|
-
role: "user",
|
|
1148
|
-
content: [
|
|
1149
|
-
{ type: "text", text: finalPrompt },
|
|
1150
|
-
{ type: "image_url", image_url: { url: base64Image } }
|
|
1151
|
-
]
|
|
1152
|
-
}],
|
|
1153
|
-
max_tokens: 150,
|
|
1154
|
-
temperature: 0.1
|
|
1155
|
-
})
|
|
1156
|
-
});
|
|
1157
|
-
const data = await response.json();
|
|
1158
|
-
let content = data.choices[0].message.content.trim();
|
|
1159
|
-
if (content.startsWith("```json")) content = content.replace(/```json\n?/, "").replace(/```$/, "").trim();
|
|
1160
|
-
else if (content.startsWith("```")) content = content.replace(/```\n?/, "").replace(/```$/, "").trim();
|
|
1161
|
-
return JSON.parse(content);
|
|
1162
|
-
} catch (e) {
|
|
1163
|
-
console.error("[MLX Bridge] Reasoning failed:", e);
|
|
1164
|
-
throw e;
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
async executeAction(tenantId = "system", action) {
|
|
1168
|
-
return this.execute(tenantId, "execute_action", { action });
|
|
1169
|
-
}
|
|
1170
|
-
async runAutonomousStep(tenantId = "system", objective) {
|
|
1171
|
-
const image = await this.captureScreen(tenantId);
|
|
1172
|
-
let decision = null, retries = 0, lastError = null;
|
|
1173
|
-
const instruction = `You are an AI OS agent. Based on the screen image, what action should be taken to achieve: "${objective}"? Return ONLY a strict JSON object of the action, no markdown. E.g. {"type": "mouseClick", "x": 100, "y": 200, "button": "left", "count": 1}`;
|
|
1174
|
-
while (retries <= 2 && !decision) {
|
|
1175
|
-
try {
|
|
1176
|
-
decision = await this.reasonWithMLX(image, instruction, lastError || void 0);
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
lastError = error.message || String(error);
|
|
1179
|
-
retries++;
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
if (!decision) return;
|
|
1183
|
-
await this.executeAction(tenantId, decision);
|
|
1184
|
-
}
|
|
1185
|
-
async askAI(prompt, taskType = "reasoning", forceProvider) {
|
|
1186
|
-
const result = await inferenceRouter.route(prompt, { preferredBackend: forceProvider || "auto" });
|
|
1187
|
-
return result.text;
|
|
1188
|
-
}
|
|
1189
|
-
onEvent(callback) {
|
|
1190
|
-
this.universalListeners.add(callback);
|
|
1191
|
-
return () => this.universalListeners.delete(callback);
|
|
1192
|
-
}
|
|
1193
|
-
async getEventHistory(tenantId, limit = 50) {
|
|
1194
|
-
return this.transport.getEventHistory(tenantId, limit);
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
|
-
var kernel = new DecidoKernel();
|
|
1198
|
-
|
|
1199
|
-
// src/ai/services/OllamaService.ts
|
|
1200
|
-
var OLLAMA_BASE_URL2 = "http://localhost:11434";
|
|
1201
|
-
var DEFAULT_MODEL5 = "qwen2:latest";
|
|
1202
|
-
function buildSystemPrompt() {
|
|
1203
|
-
const liveContext = buildLiveContext();
|
|
1204
|
-
const toolSchemas = "";
|
|
1205
|
-
return `- Est\xE1s integrado en DecidoOS, una plataforma empresarial de escritorio
|
|
6
|
+
WARNING: Your previous response failed to parse as JSON with the following error: ${s}. Fix the JSON syntax and return ONLY a valid JSON object.`);try{let i=(await(await fetch("http://127.0.0.1:8080/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:"llava",messages:[{role:"user",content:[{type:"text",text:r},{type:"image_url",image_url:{url:e}}]}],max_tokens:150,temperature:.1})})).json()).choices[0].message.content.trim();return i.startsWith("```json")?i=i.replace(/```json\n?/,"").replace(/```$/,"").trim():i.startsWith("```")&&(i=i.replace(/```\n?/,"").replace(/```$/,"").trim()),JSON.parse(i)}catch(o){throw console.error("[MLX Bridge] Reasoning failed:",o),o}}async executeAction(e="system",t){return this.execute(e,"execute_action",{action:t})}async runAutonomousStep(e="system",t){let s=await this.captureScreen(e),r=null,o=0,a=null,i=`You are an AI OS agent. Based on the screen image, what action should be taken to achieve: "${t}"? Return ONLY a strict JSON object of the action, no markdown. E.g. {"type": "mouseClick", "x": 100, "y": 200, "button": "left", "count": 1}`;for(;o<=2&&!r;)try{r=await this.reasonWithMLX(s,i,a||void 0)}catch(d){a=d.message||String(d),o++}r&&await this.executeAction(e,r)}async askAI(e,t="reasoning",s){return(await K.route(e,{preferredBackend:s||"auto"})).text}onEvent(e){return this.universalListeners.add(e),()=>this.universalListeners.delete(e)}async getEventHistory(e,t=50){return this.transport.getEventHistory(e,t)}},F=new oe;var ae="http://localhost:11434",yt="qwen2:latest";function Se(){return`- Est\xE1s integrado en DecidoOS, una plataforma empresarial de escritorio
|
|
1206
7
|
- Corres localmente en la m\xE1quina del usuario \u2014 todo es privado
|
|
1207
8
|
- Puedes ejecutar herramientas del sistema para ayudar al usuario
|
|
1208
9
|
|
|
1209
|
-
${
|
|
10
|
+
${vt()}
|
|
1210
11
|
|
|
1211
12
|
## Herramientas disponibles
|
|
1212
13
|
|
|
1213
|
-
|
|
14
|
+
|
|
1214
15
|
|
|
1215
16
|
## Reglas de respuesta
|
|
1216
17
|
1. Responde siempre en espa\xF1ol a menos que el usuario hable en otro idioma
|
|
@@ -1219,471 +20,9 @@ ${toolSchemas}
|
|
|
1219
20
|
4. Mant\xE9n respuestas bajo 300 palabras
|
|
1220
21
|
5. Si no sabes algo, dilo honestamente
|
|
1221
22
|
6. NO inventes resultados de herramientas \u2014 espera el resultado real
|
|
1222
|
-
7. Cuando el usuario pregunte sobre el estado del sistema, USA LOS DATOS del "Estado actual del sistema" que tienes arriba \u2014 esos son datos reales y recientes
|
|
1223
|
-
}
|
|
1224
|
-
function
|
|
1225
|
-
const parts = ["## Estado actual del sistema"];
|
|
1226
|
-
const now = (/* @__PURE__ */ new Date()).toLocaleString("es-CO", { hour12: false });
|
|
1227
|
-
parts.push(`Hora actual: ${now}`);
|
|
1228
|
-
try {
|
|
1229
|
-
const watchdogModule = globalThis.__systemWatchdog;
|
|
1230
|
-
if (watchdogModule) {
|
|
1231
|
-
const snapshot = watchdogModule.getLastSnapshot?.();
|
|
1232
|
-
if (snapshot) {
|
|
1233
|
-
const metrics = [];
|
|
1234
|
-
if (snapshot.cpuPercent !== null) metrics.push(`CPU: ${snapshot.cpuPercent.toFixed(1)}%`);
|
|
1235
|
-
if (snapshot.memoryPercent !== null) metrics.push(`Memoria: ${snapshot.memoryPercent.toFixed(1)}%`);
|
|
1236
|
-
if (snapshot.diskFreeGB !== null) metrics.push(`Disco libre: ${snapshot.diskFreeGB.toFixed(1)} GB`);
|
|
1237
|
-
if (snapshot.connectionCount !== null) metrics.push(`Conexiones de red: ${snapshot.connectionCount}`);
|
|
1238
|
-
if (metrics.length > 0) {
|
|
1239
|
-
parts.push(`M\xE9tricas del sistema: ${metrics.join(" | ")}`);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
const alerts = watchdogModule.getAlerts?.()?.filter((a) => !a.dismissed).slice(-5) ?? [];
|
|
1243
|
-
if (alerts.length > 0) {
|
|
1244
|
-
parts.push("Alertas recientes:");
|
|
1245
|
-
for (const alert of alerts) {
|
|
1246
|
-
const emoji = alert.severity === "critical" ? "\u{1F6A8}" : "\u26A0\uFE0F";
|
|
1247
|
-
parts.push(` ${emoji} ${alert.title}`);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
} catch {
|
|
1252
|
-
}
|
|
1253
|
-
try {
|
|
1254
|
-
const appStore = globalThis.__appStore;
|
|
1255
|
-
const ctx = appStore?.getState?.()?.contextSnapshot;
|
|
1256
|
-
if (ctx) {
|
|
1257
|
-
if (ctx.canvasNodeCount > 0) parts.push(`Canvas: ${ctx.canvasNodeCount} nodos`);
|
|
1258
|
-
if (ctx.gitBranch) parts.push(`Git: rama ${ctx.gitBranch}${ctx.gitModifiedFiles ? ` (${ctx.gitModifiedFiles} archivos modificados)` : ""}`);
|
|
1259
|
-
if (ctx.activeInsights > 0) parts.push(`Insights activos: ${ctx.activeInsights}`);
|
|
1260
|
-
if (ctx.criticalInsightsSummary?.length > 0) {
|
|
1261
|
-
parts.push("Insights cr\xEDticos: " + ctx.criticalInsightsSummary.slice(0, 3).join("; "));
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
} catch {
|
|
1265
|
-
}
|
|
1266
|
-
try {
|
|
1267
|
-
const memoryModule = globalThis.__agentMemory;
|
|
1268
|
-
if (memoryModule) {
|
|
1269
|
-
const memContext = memoryModule.buildMemoryContext?.();
|
|
1270
|
-
if (memContext) {
|
|
1271
|
-
parts.push("");
|
|
1272
|
-
parts.push(memContext);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
} catch {
|
|
1276
|
-
}
|
|
1277
|
-
return parts.join("\n");
|
|
1278
|
-
}
|
|
1279
|
-
function parseToolCalls(message) {
|
|
1280
|
-
const calls = [];
|
|
1281
|
-
if (message?.tool_calls && Array.isArray(message.tool_calls)) {
|
|
1282
|
-
for (const tc of message.tool_calls) {
|
|
1283
|
-
if (tc.function?.name) {
|
|
1284
|
-
calls.push({
|
|
1285
|
-
name: tc.function.name,
|
|
1286
|
-
args: tc.function.arguments || {}
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
return calls;
|
|
1292
|
-
}
|
|
1293
|
-
function stripToolCalls(text) {
|
|
1294
|
-
if (!text) return "";
|
|
1295
|
-
return text.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "").trim();
|
|
1296
|
-
}
|
|
1297
|
-
var conversationHistory = [];
|
|
1298
|
-
var MAX_HISTORY2 = 20;
|
|
1299
|
-
function addToHistory(msg) {
|
|
1300
|
-
conversationHistory.push(msg);
|
|
1301
|
-
if (conversationHistory.length > MAX_HISTORY2) {
|
|
1302
|
-
conversationHistory = conversationHistory.slice(-MAX_HISTORY2);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
function clearConversationHistory() {
|
|
1306
|
-
conversationHistory = [];
|
|
1307
|
-
}
|
|
1308
|
-
async function isOllamaAvailable() {
|
|
1309
|
-
try {
|
|
1310
|
-
const res = await fetch(`${OLLAMA_BASE_URL2}/api/tags`, {
|
|
1311
|
-
signal: AbortSignal.timeout(2e3)
|
|
1312
|
-
});
|
|
1313
|
-
return res.ok;
|
|
1314
|
-
} catch {
|
|
1315
|
-
return false;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
async function listModels() {
|
|
1319
|
-
try {
|
|
1320
|
-
const res = await fetch(`${OLLAMA_BASE_URL2}/api/tags`);
|
|
1321
|
-
if (!res.ok) return [];
|
|
1322
|
-
const data = await res.json();
|
|
1323
|
-
return (data.models || []).map((m) => m.name);
|
|
1324
|
-
} catch {
|
|
1325
|
-
return [];
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
async function chat(userMessage, options) {
|
|
1329
|
-
const userMsg = { role: "user", content: userMessage };
|
|
1330
|
-
addToHistory(userMsg);
|
|
1331
|
-
const messages = [
|
|
1332
|
-
{ role: "system", content: buildSystemPrompt() },
|
|
1333
|
-
...conversationHistory
|
|
1334
|
-
];
|
|
1335
|
-
try {
|
|
1336
|
-
const result = await routeChat(messages, {
|
|
1337
|
-
temperature: options?.temperature ?? 0.7,
|
|
1338
|
-
maxTokens: 512
|
|
1339
|
-
});
|
|
1340
|
-
const assistantContent = result?.text || "";
|
|
1341
|
-
addToHistory({ role: "assistant", content: assistantContent });
|
|
1342
|
-
if (result) {
|
|
1343
|
-
console.log(`\u{1F9E0}[Chat] Response via ${result.backend} (${result.model}) in ${result.latencyMs} ms`);
|
|
1344
|
-
}
|
|
1345
|
-
return assistantContent;
|
|
1346
|
-
} catch (error) {
|
|
1347
|
-
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
1348
|
-
return "\u23F3 La respuesta del modelo tard\xF3 demasiado. Intenta con una pregunta m\xE1s corta.";
|
|
1349
|
-
}
|
|
1350
|
-
throw error;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
async function* chatStream(userMessage, options) {
|
|
1354
|
-
const model = options?.model || DEFAULT_MODEL5;
|
|
1355
|
-
const userMsg = { role: "user", content: userMessage };
|
|
1356
|
-
addToHistory(userMsg);
|
|
1357
|
-
const messages = [
|
|
1358
|
-
{ role: "system", content: buildSystemPrompt() },
|
|
1359
|
-
...conversationHistory
|
|
1360
|
-
];
|
|
1361
|
-
const res = await fetch(`${OLLAMA_BASE_URL2}/api/chat`, {
|
|
1362
|
-
method: "POST",
|
|
1363
|
-
headers: { "Content-Type": "application/json" },
|
|
1364
|
-
body: JSON.stringify({
|
|
1365
|
-
model,
|
|
1366
|
-
messages,
|
|
1367
|
-
stream: true,
|
|
1368
|
-
options: {
|
|
1369
|
-
temperature: options?.temperature ?? 0.7,
|
|
1370
|
-
num_predict: 512
|
|
1371
|
-
}
|
|
1372
|
-
})
|
|
1373
|
-
});
|
|
1374
|
-
if (!res.ok || !res.body) {
|
|
1375
|
-
throw new Error(`Ollama stream error: ${res.status}`);
|
|
1376
|
-
}
|
|
1377
|
-
const reader = res.body.getReader();
|
|
1378
|
-
const decoder = new TextDecoder();
|
|
1379
|
-
let fullContent = "";
|
|
1380
|
-
try {
|
|
1381
|
-
while (true) {
|
|
1382
|
-
const { done, value } = await reader.read();
|
|
1383
|
-
if (done) break;
|
|
1384
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1385
|
-
const lines = chunk.split("\n").filter(Boolean);
|
|
1386
|
-
for (const line of lines) {
|
|
1387
|
-
try {
|
|
1388
|
-
const data = JSON.parse(line);
|
|
1389
|
-
if (data.message?.content) {
|
|
1390
|
-
fullContent += data.message.content;
|
|
1391
|
-
yield data.message.content;
|
|
1392
|
-
}
|
|
1393
|
-
} catch {
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
} finally {
|
|
1398
|
-
reader.releaseLock();
|
|
1399
|
-
}
|
|
1400
|
-
addToHistory({ role: "assistant", content: fullContent });
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
// src/ai/services/MLXBridge.ts
|
|
1404
|
-
async function runShellCommand(cmd, args) {
|
|
1405
|
-
try {
|
|
1406
|
-
const { invoke } = await import("@tauri-apps/api/core");
|
|
1407
|
-
const result = await invoke("run_shell_command", {
|
|
1408
|
-
command: cmd,
|
|
1409
|
-
args
|
|
1410
|
-
});
|
|
1411
|
-
return result;
|
|
1412
|
-
} catch (err) {
|
|
1413
|
-
console.error("[MLXBridge] Shell command failed:", err);
|
|
1414
|
-
throw err;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
var OLLAMA_URL = "http://localhost:11434";
|
|
1418
|
-
async function ollamaChat(model, prompt, temperature = 0.7) {
|
|
1419
|
-
const start = Date.now();
|
|
1420
|
-
try {
|
|
1421
|
-
const res = await fetch(`${OLLAMA_URL}/api/chat`, {
|
|
1422
|
-
method: "POST",
|
|
1423
|
-
headers: { "Content-Type": "application/json" },
|
|
1424
|
-
body: JSON.stringify({
|
|
1425
|
-
model,
|
|
1426
|
-
messages: [{ role: "user", content: prompt }],
|
|
1427
|
-
stream: false,
|
|
1428
|
-
options: { temperature }
|
|
1429
|
-
})
|
|
1430
|
-
});
|
|
1431
|
-
const data = await res.json();
|
|
1432
|
-
const latencyMs = Date.now() - start;
|
|
1433
|
-
return {
|
|
1434
|
-
text: data.message?.content ?? "",
|
|
1435
|
-
tokensPerSecond: data.eval_count ? data.eval_count / (latencyMs / 1e3) : 0,
|
|
1436
|
-
totalTokens: data.eval_count ?? 0,
|
|
1437
|
-
latencyMs,
|
|
1438
|
-
model
|
|
1439
|
-
};
|
|
1440
|
-
} catch {
|
|
1441
|
-
return { text: "Error: Ollama no disponible", tokensPerSecond: 0, totalTokens: 0, latencyMs: Date.now() - start, model };
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
async function ollamaListModels() {
|
|
1445
|
-
try {
|
|
1446
|
-
const res = await fetch(`${OLLAMA_URL}/api/tags`);
|
|
1447
|
-
if (!res.ok) return [];
|
|
1448
|
-
const data = await res.json();
|
|
1449
|
-
return (data.models || []).map((m) => m.name);
|
|
1450
|
-
} catch {
|
|
1451
|
-
return [];
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
async function ollamaPull(model) {
|
|
1455
|
-
await fetch(`${OLLAMA_URL}/api/pull`, {
|
|
1456
|
-
method: "POST",
|
|
1457
|
-
headers: { "Content-Type": "application/json" },
|
|
1458
|
-
body: JSON.stringify({ name: model })
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
async function mlxGenerate(model, prompt) {
|
|
1462
|
-
const start = Date.now();
|
|
1463
|
-
try {
|
|
1464
|
-
const output = await runShellCommand("python3", [
|
|
1465
|
-
"-m",
|
|
1466
|
-
"mlx_lm.generate",
|
|
1467
|
-
"--model",
|
|
1468
|
-
model,
|
|
1469
|
-
"--prompt",
|
|
1470
|
-
prompt,
|
|
1471
|
-
"--max-tokens",
|
|
1472
|
-
"512"
|
|
1473
|
-
]);
|
|
1474
|
-
return {
|
|
1475
|
-
text: output,
|
|
1476
|
-
tokensPerSecond: 0,
|
|
1477
|
-
// parsed from output in production
|
|
1478
|
-
totalTokens: output.split(" ").length,
|
|
1479
|
-
latencyMs: Date.now() - start,
|
|
1480
|
-
model
|
|
1481
|
-
};
|
|
1482
|
-
} catch (err) {
|
|
1483
|
-
return { text: `MLX Error: ${err}`, tokensPerSecond: 0, totalTokens: 0, latencyMs: Date.now() - start, model };
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
async function listAvailableModels() {
|
|
1487
|
-
const models = [];
|
|
1488
|
-
const ollamaModels = await ollamaListModels();
|
|
1489
|
-
for (const name of ollamaModels) {
|
|
1490
|
-
models.push({
|
|
1491
|
-
name,
|
|
1492
|
-
family: "llm",
|
|
1493
|
-
path: `ollama:${name}`,
|
|
1494
|
-
loaded: true
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
const mlxCatalog = [
|
|
1498
|
-
{ name: "Qwen2.5-7B-Instruct-4bit", family: "llm", paramCount: "7B", quantization: "4bit", path: "mlx-community/Qwen2.5-7B-Instruct-4bit", loaded: false },
|
|
1499
|
-
{ name: "Mistral-7B-Instruct-v0.3-4bit", family: "llm", paramCount: "7B", quantization: "4bit", path: "mlx-community/Mistral-7B-Instruct-v0.3-4bit", loaded: false },
|
|
1500
|
-
{ name: "Llama-3.2-3B-Instruct-4bit", family: "llm", paramCount: "3B", quantization: "4bit", path: "mlx-community/Llama-3.2-3B-Instruct-4bit", loaded: false },
|
|
1501
|
-
{ name: "Mixtral-8x7B-Instruct-v0.1-4bit", family: "llm", paramCount: "46.7B", quantization: "4bit", path: "mlx-community/Mixtral-8x7B-Instruct-v0.1-4bit", loaded: false },
|
|
1502
|
-
{ name: "Whisper-large-v3", family: "audio", path: "mlx-community/whisper-large-v3", loaded: false },
|
|
1503
|
-
{ name: "CLIP-ViT-B-32", family: "vision", path: "openai/clip-vit-base-patch32", loaded: false },
|
|
1504
|
-
{ name: "FLUX.1-schnell-4bit", family: "image-gen", path: "mlx-community/FLUX.1-schnell-4bit-quantized", loaded: false },
|
|
1505
|
-
{ name: "Stable-Diffusion-XL", family: "image-gen", path: "mlx-community/sdxl-turbo", loaded: false }
|
|
1506
|
-
];
|
|
1507
|
-
models.push(...mlxCatalog);
|
|
1508
|
-
return models;
|
|
1509
|
-
}
|
|
1510
|
-
async function runInference(modelPath, prompt, options) {
|
|
1511
|
-
if (modelPath.startsWith("ollama:")) {
|
|
1512
|
-
const model = modelPath.replace("ollama:", "");
|
|
1513
|
-
return ollamaChat(model, prompt, options?.temperature);
|
|
1514
|
-
}
|
|
1515
|
-
return mlxGenerate(modelPath, prompt);
|
|
1516
|
-
}
|
|
1517
|
-
async function compareModels(modelA, modelB, prompt) {
|
|
1518
|
-
const [a, b] = await Promise.all([
|
|
1519
|
-
runInference(modelA, prompt),
|
|
1520
|
-
runInference(modelB, prompt)
|
|
1521
|
-
]);
|
|
1522
|
-
return { a, b };
|
|
1523
|
-
}
|
|
1524
|
-
async function pullModel(model) {
|
|
1525
|
-
if (model.startsWith("ollama:")) {
|
|
1526
|
-
await ollamaPull(model.replace("ollama:", ""));
|
|
1527
|
-
} else {
|
|
1528
|
-
await runShellCommand("huggingface-cli", ["download", model]);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
async function benchmarkModel(modelPath) {
|
|
1532
|
-
const testPrompts = [
|
|
1533
|
-
"Explain quantum computing in one paragraph.",
|
|
1534
|
-
"Write a Python function to sort a list.",
|
|
1535
|
-
"What is the capital of Colombia?"
|
|
1536
|
-
];
|
|
1537
|
-
let totalTokens = 0;
|
|
1538
|
-
let totalMs = 0;
|
|
1539
|
-
for (const prompt of testPrompts) {
|
|
1540
|
-
const result = await runInference(modelPath, prompt);
|
|
1541
|
-
totalTokens += result.totalTokens;
|
|
1542
|
-
totalMs += result.latencyMs;
|
|
1543
|
-
}
|
|
1544
|
-
return {
|
|
1545
|
-
model: modelPath,
|
|
1546
|
-
promptTokens: testPrompts.join(" ").split(" ").length,
|
|
1547
|
-
generatedTokens: totalTokens,
|
|
1548
|
-
tokensPerSecond: totalTokens / (totalMs / 1e3),
|
|
1549
|
-
latencyMs: totalMs / testPrompts.length,
|
|
1550
|
-
memoryMb: 0
|
|
1551
|
-
// would need system metrics
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
function startLoRATraining(config) {
|
|
1555
|
-
let stopped = false;
|
|
1556
|
-
let progressCallback = null;
|
|
1557
|
-
const totalSteps = (config.epochs ?? 10) * 100;
|
|
1558
|
-
let step = 0;
|
|
1559
|
-
const start = Date.now();
|
|
1560
|
-
const interval = setInterval(() => {
|
|
1561
|
-
if (stopped || step >= totalSteps) {
|
|
1562
|
-
clearInterval(interval);
|
|
1563
|
-
return;
|
|
1564
|
-
}
|
|
1565
|
-
step++;
|
|
1566
|
-
const progress = {
|
|
1567
|
-
epoch: Math.floor(step / 100) + 1,
|
|
1568
|
-
totalEpochs: config.epochs ?? 10,
|
|
1569
|
-
step,
|
|
1570
|
-
totalSteps,
|
|
1571
|
-
loss: 2.5 * Math.exp(-step / 200) + 0.3 + Math.random() * 0.1,
|
|
1572
|
-
learningRate: config.learningRate ?? 1e-5,
|
|
1573
|
-
tokensPerSecond: 150 + Math.random() * 50,
|
|
1574
|
-
elapsedMs: Date.now() - start
|
|
1575
|
-
};
|
|
1576
|
-
progressCallback?.(progress);
|
|
1577
|
-
}, 500);
|
|
1578
|
-
return {
|
|
1579
|
-
stop: () => {
|
|
1580
|
-
stopped = true;
|
|
1581
|
-
clearInterval(interval);
|
|
1582
|
-
},
|
|
1583
|
-
onProgress: (cb) => {
|
|
1584
|
-
progressCallback = cb;
|
|
1585
|
-
}
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
// src/ai/services/EmbeddingService.ts
|
|
1590
|
-
var OLLAMA_URL2 = "http://localhost:11434";
|
|
1591
|
-
var EMBED_MODEL = "nomic-embed-text";
|
|
1592
|
-
var TFIDF_DIMS = 256;
|
|
1593
|
-
async function ollamaEmbed(text) {
|
|
1594
|
-
const start = Date.now();
|
|
1595
|
-
try {
|
|
1596
|
-
const res = await fetch(`${OLLAMA_URL2}/api/embed`, {
|
|
1597
|
-
method: "POST",
|
|
1598
|
-
headers: { "Content-Type": "application/json" },
|
|
1599
|
-
body: JSON.stringify({ model: EMBED_MODEL, input: text })
|
|
1600
|
-
});
|
|
1601
|
-
if (!res.ok) throw new Error(`Ollama embed failed: ${res.status}`);
|
|
1602
|
-
const data = await res.json();
|
|
1603
|
-
const vector = data.embeddings?.[0] || data.embedding || [];
|
|
1604
|
-
return {
|
|
1605
|
-
vector,
|
|
1606
|
-
dimensions: vector.length,
|
|
1607
|
-
model: EMBED_MODEL,
|
|
1608
|
-
latencyMs: Date.now() - start
|
|
1609
|
-
};
|
|
1610
|
-
} catch (err) {
|
|
1611
|
-
console.warn("[EmbeddingService] Ollama not available, falling back to TF-IDF", err);
|
|
1612
|
-
return tfidfEmbed(text);
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
function tfidfEmbed(text) {
|
|
1616
|
-
const start = Date.now();
|
|
1617
|
-
const tokens = text.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter((t) => t.length > 2);
|
|
1618
|
-
const vector = new Array(TFIDF_DIMS).fill(0);
|
|
1619
|
-
for (const token of tokens) {
|
|
1620
|
-
let hash = 0;
|
|
1621
|
-
for (let i = 0; i < token.length; i++) {
|
|
1622
|
-
hash = (hash << 5) - hash + token.charCodeAt(i) | 0;
|
|
1623
|
-
}
|
|
1624
|
-
const idx = Math.abs(hash) % TFIDF_DIMS;
|
|
1625
|
-
vector[idx] += 1 / tokens.length;
|
|
1626
|
-
}
|
|
1627
|
-
const norm = Math.sqrt(vector.reduce((s, v) => s + v * v, 0));
|
|
1628
|
-
if (norm > 0) {
|
|
1629
|
-
for (let i = 0; i < vector.length; i++) vector[i] /= norm;
|
|
1630
|
-
}
|
|
1631
|
-
return {
|
|
1632
|
-
vector,
|
|
1633
|
-
dimensions: TFIDF_DIMS,
|
|
1634
|
-
model: "tfidf-fallback",
|
|
1635
|
-
latencyMs: Date.now() - start
|
|
1636
|
-
};
|
|
1637
|
-
}
|
|
1638
|
-
var preferredBackend = "ollama";
|
|
1639
|
-
async function embed(text) {
|
|
1640
|
-
if (preferredBackend === "ollama") {
|
|
1641
|
-
return ollamaEmbed(text);
|
|
1642
|
-
}
|
|
1643
|
-
return tfidfEmbed(text);
|
|
1644
|
-
}
|
|
1645
|
-
async function embedBatch(texts) {
|
|
1646
|
-
return Promise.all(texts.map((t) => embed(t)));
|
|
1647
|
-
}
|
|
1648
|
-
function setBackend(backend) {
|
|
1649
|
-
preferredBackend = backend;
|
|
1650
|
-
}
|
|
1651
|
-
function cosineSimilarity(a, b) {
|
|
1652
|
-
if (a.length !== b.length) return 0;
|
|
1653
|
-
let dot = 0, normA = 0, normB = 0;
|
|
1654
|
-
for (let i = 0; i < a.length; i++) {
|
|
1655
|
-
dot += a[i] * b[i];
|
|
1656
|
-
normA += a[i] * a[i];
|
|
1657
|
-
normB += b[i] * b[i];
|
|
1658
|
-
}
|
|
1659
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1660
|
-
return denom === 0 ? 0 : dot / denom;
|
|
1661
|
-
}
|
|
1662
|
-
var embeddingService = { embed, embedBatch, setBackend, cosineSimilarity };
|
|
1663
|
-
|
|
1664
|
-
// src/ai/services/LocalAgentResponder.ts
|
|
1665
|
-
var GREETING_PATTERNS = [
|
|
1666
|
-
/^(hola|hey|hi|hello|buenas|qué tal|que tal|saludos)\s*(decido|decidoos|agente|asistente)?/i,
|
|
1667
|
-
/^(buenos?\s*(días|tardes|noches))/i,
|
|
1668
|
-
/^(órale|oye|ey)\s*(decido)?/i
|
|
1669
|
-
];
|
|
1670
|
-
var HELP_PATTERNS = [
|
|
1671
|
-
/^(ayuda|help|qué puedes hacer|que puedes hacer|comandos|tools|herramientas)/i,
|
|
1672
|
-
/^(qué|que)\s*(sabes|haces|puedes)/i
|
|
1673
|
-
];
|
|
1674
|
-
var SYSTEM_PATTERNS = [
|
|
1675
|
-
/^(estado|status|sistema|system)\s*(del\s*sistema)?/i,
|
|
1676
|
-
/^(cómo|como)\s*(estás|estas|está|va)/i
|
|
1677
|
-
];
|
|
1678
|
-
function getTimeGreeting() {
|
|
1679
|
-
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
1680
|
-
if (hour < 12) return "Buenos d\xEDas";
|
|
1681
|
-
if (hour < 18) return "Buenas tardes";
|
|
1682
|
-
return "Buenas noches";
|
|
1683
|
-
}
|
|
1684
|
-
function getWelcomeMessage() {
|
|
1685
|
-
const greeting = getTimeGreeting();
|
|
1686
|
-
return `${greeting}, operador. Soy **DecidoOS Agent** \u{1F9E0}
|
|
23
|
+
7. Cuando el usuario pregunte sobre el estado del sistema, USA LOS DATOS del "Estado actual del sistema" que tienes arriba \u2014 esos son datos reales y recientes`}function vt(){let n=["## Estado actual del sistema"],e=new Date().toLocaleString("es-CO",{hour12:!1});n.push(`Hora actual: ${e}`);try{let t=globalThis.__systemWatchdog;if(t){let s=t.getLastSnapshot?.();if(s){let o=[];s.cpuPercent!==null&&o.push(`CPU: ${s.cpuPercent.toFixed(1)}%`),s.memoryPercent!==null&&o.push(`Memoria: ${s.memoryPercent.toFixed(1)}%`),s.diskFreeGB!==null&&o.push(`Disco libre: ${s.diskFreeGB.toFixed(1)} GB`),s.connectionCount!==null&&o.push(`Conexiones de red: ${s.connectionCount}`),o.length>0&&n.push(`M\xE9tricas del sistema: ${o.join(" | ")}`)}let r=t.getAlerts?.()?.filter(o=>!o.dismissed).slice(-5)??[];if(r.length>0){n.push("Alertas recientes:");for(let o of r){let a=o.severity==="critical"?"\u{1F6A8}":"\u26A0\uFE0F";n.push(` ${a} ${o.title}`)}}}}catch{}try{let s=globalThis.__appStore?.getState?.()?.contextSnapshot;s&&(s.canvasNodeCount>0&&n.push(`Canvas: ${s.canvasNodeCount} nodos`),s.gitBranch&&n.push(`Git: rama ${s.gitBranch}${s.gitModifiedFiles?` (${s.gitModifiedFiles} archivos modificados)`:""}`),s.activeInsights>0&&n.push(`Insights activos: ${s.activeInsights}`),s.criticalInsightsSummary?.length>0&&n.push("Insights cr\xEDticos: "+s.criticalInsightsSummary.slice(0,3).join("; ")))}catch{}try{let t=globalThis.__agentMemory;if(t){let s=t.buildMemoryContext?.();s&&(n.push(""),n.push(s))}}catch{}return n.join(`
|
|
24
|
+
`)}function Ce(n){let e=[];if(n?.tool_calls&&Array.isArray(n.tool_calls))for(let t of n.tool_calls)t.function?.name&&e.push({name:t.function.name,args:t.function.arguments||{}});return e}function Te(n){return n?n.replace(/<tool_call>[\s\S]*?<\/tool_call>/g,"").trim():""}var S=[],Me=20;function j(n){S.push(n),S.length>Me&&(S=S.slice(-Me))}function _e(){S=[]}async function Le(){try{return(await fetch(`${ae}/api/tags`,{signal:AbortSignal.timeout(2e3)})).ok}catch{return!1}}async function Re(){try{let n=await fetch(`${ae}/api/tags`);return n.ok?((await n.json()).models||[]).map(t=>t.name):[]}catch{return[]}}async function ie(n,e){j({role:"user",content:n});let s=[{role:"system",content:Se()},...S];try{let r=await A(s,{temperature:e?.temperature??.7,maxTokens:512}),o=r?.text||"";return j({role:"assistant",content:o}),r&&console.log(`\u{1F9E0}[Chat] Response via ${r.backend} (${r.model}) in ${r.latencyMs} ms`),o}catch(r){if(r instanceof DOMException&&r.name==="TimeoutError")return"\u23F3 La respuesta del modelo tard\xF3 demasiado. Intenta con una pregunta m\xE1s corta.";throw r}}async function*ce(n,e){let t=e?.model||yt;j({role:"user",content:n});let r=[{role:"system",content:Se()},...S],o=await fetch(`${ae}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:t,messages:r,stream:!0,options:{temperature:e?.temperature??.7,num_predict:512}})});if(!o.ok||!o.body)throw new Error(`Ollama stream error: ${o.status}`);let a=o.body.getReader(),i=new TextDecoder,d="";try{for(;;){let{done:l,value:h}=await a.read();if(l)break;let g=i.decode(h,{stream:!0}).split(`
|
|
25
|
+
`).filter(Boolean);for(let v of g)try{let p=JSON.parse(v);p.message?.content&&(d+=p.message.content,yield p.message.content)}catch{}}}finally{a.releaseLock()}j({role:"assistant",content:d})}async function Ae(n,e){try{let{invoke:t}=await import("@tauri-apps/api/core");return await t("run_shell_command",{command:n,args:e})}catch(t){throw console.error("[MLXBridge] Shell command failed:",t),t}}var le="http://localhost:11434";async function xt(n,e,t=.7){let s=Date.now();try{let o=await(await fetch(`${le}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:n,messages:[{role:"user",content:e}],stream:!1,options:{temperature:t}})})).json(),a=Date.now()-s;return{text:o.message?.content??"",tokensPerSecond:o.eval_count?o.eval_count/(a/1e3):0,totalTokens:o.eval_count??0,latencyMs:a,model:n}}catch{return{text:"Error: Ollama no disponible",tokensPerSecond:0,totalTokens:0,latencyMs:Date.now()-s,model:n}}}async function bt(){try{let n=await fetch(`${le}/api/tags`);return n.ok?((await n.json()).models||[]).map(t=>t.name):[]}catch{return[]}}async function Pt(n){await fetch(`${le}/api/pull`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n})})}async function kt(n,e){let t=Date.now();try{let s=await Ae("python3",["-m","mlx_lm.generate","--model",n,"--prompt",e,"--max-tokens","512"]);return{text:s,tokensPerSecond:0,totalTokens:s.split(" ").length,latencyMs:Date.now()-t,model:n}}catch(s){return{text:`MLX Error: ${s}`,tokensPerSecond:0,totalTokens:0,latencyMs:Date.now()-t,model:n}}}async function Ie(){let n=[],e=await bt();for(let s of e)n.push({name:s,family:"llm",path:`ollama:${s}`,loaded:!0});let t=[{name:"Qwen2.5-7B-Instruct-4bit",family:"llm",paramCount:"7B",quantization:"4bit",path:"mlx-community/Qwen2.5-7B-Instruct-4bit",loaded:!1},{name:"Mistral-7B-Instruct-v0.3-4bit",family:"llm",paramCount:"7B",quantization:"4bit",path:"mlx-community/Mistral-7B-Instruct-v0.3-4bit",loaded:!1},{name:"Llama-3.2-3B-Instruct-4bit",family:"llm",paramCount:"3B",quantization:"4bit",path:"mlx-community/Llama-3.2-3B-Instruct-4bit",loaded:!1},{name:"Mixtral-8x7B-Instruct-v0.1-4bit",family:"llm",paramCount:"46.7B",quantization:"4bit",path:"mlx-community/Mixtral-8x7B-Instruct-v0.1-4bit",loaded:!1},{name:"Whisper-large-v3",family:"audio",path:"mlx-community/whisper-large-v3",loaded:!1},{name:"CLIP-ViT-B-32",family:"vision",path:"openai/clip-vit-base-patch32",loaded:!1},{name:"FLUX.1-schnell-4bit",family:"image-gen",path:"mlx-community/FLUX.1-schnell-4bit-quantized",loaded:!1},{name:"Stable-Diffusion-XL",family:"image-gen",path:"mlx-community/sdxl-turbo",loaded:!1}];return n.push(...t),n}async function B(n,e,t){if(n.startsWith("ollama:")){let s=n.replace("ollama:","");return xt(s,e,t?.temperature)}return kt(n,e)}async function Ne(n,e,t){let[s,r]=await Promise.all([B(n,t),B(e,t)]);return{a:s,b:r}}async function Ee(n){n.startsWith("ollama:")?await Pt(n.replace("ollama:","")):await Ae("huggingface-cli",["download",n])}async function $e(n){let e=["Explain quantum computing in one paragraph.","Write a Python function to sort a list.","What is the capital of Colombia?"],t=0,s=0;for(let r of e){let o=await B(n,r);t+=o.totalTokens,s+=o.latencyMs}return{model:n,promptTokens:e.join(" ").split(" ").length,generatedTokens:t,tokensPerSecond:t/(s/1e3),latencyMs:s/e.length,memoryMb:0}}function Oe(n){let e=!1,t=null,s=(n.epochs??10)*100,r=0,o=Date.now(),a=setInterval(()=>{if(e||r>=s){clearInterval(a);return}r++;let i={epoch:Math.floor(r/100)+1,totalEpochs:n.epochs??10,step:r,totalSteps:s,loss:2.5*Math.exp(-r/200)+.3+Math.random()*.1,learningRate:n.learningRate??1e-5,tokensPerSecond:150+Math.random()*50,elapsedMs:Date.now()-o};t?.(i)},500);return{stop:()=>{e=!0,clearInterval(a)},onProgress:i=>{t=i}}}var wt="http://localhost:11434",De="nomic-embed-text";async function Mt(n){let e=Date.now();try{let t=await fetch(`${wt}/api/embed`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:De,input:n})});if(!t.ok)throw new Error(`Ollama embed failed: ${t.status}`);let s=await t.json(),r=s.embeddings?.[0]||s.embedding||[];return{vector:r,dimensions:r.length,model:De,latencyMs:Date.now()-e}}catch(t){return console.warn("[EmbeddingService] Ollama not available, falling back to TF-IDF",t),Be(n)}}function Be(n){let e=Date.now(),t=n.toLowerCase().replace(/[^\w\s]/g,"").split(/\s+/).filter(o=>o.length>2),s=new Array(256).fill(0);for(let o of t){let a=0;for(let d=0;d<o.length;d++)a=(a<<5)-a+o.charCodeAt(d)|0;let i=Math.abs(a)%256;s[i]+=1/t.length}let r=Math.sqrt(s.reduce((o,a)=>o+a*a,0));if(r>0)for(let o=0;o<s.length;o++)s[o]/=r;return{vector:s,dimensions:256,model:"tfidf-fallback",latencyMs:Date.now()-e}}var He="ollama";async function Ue(n){return He==="ollama"?Mt(n):Be(n)}async function St(n){return Promise.all(n.map(e=>Ue(e)))}function Ct(n){He=n}function Tt(n,e){if(n.length!==e.length)return 0;let t=0,s=0,r=0;for(let a=0;a<n.length;a++)t+=n[a]*e[a],s+=n[a]*n[a],r+=e[a]*e[a];let o=Math.sqrt(s)*Math.sqrt(r);return o===0?0:t/o}var Ke={embed:Ue,embedBatch:St,setBackend:Ct,cosineSimilarity:Tt};var _t=[/^(hola|hey|hi|hello|buenas|qué tal|que tal|saludos)\s*(decido|decidoos|agente|asistente)?/i,/^(buenos?\s*(días|tardes|noches))/i,/^(órale|oye|ey)\s*(decido)?/i],Lt=[/^(ayuda|help|qué puedes hacer|que puedes hacer|comandos|tools|herramientas)/i,/^(qué|que)\s*(sabes|haces|puedes)/i],Rt=[/^(estado|status|sistema|system)\s*(del\s*sistema)?/i,/^(cómo|como)\s*(estás|estas|está|va)/i];function At(){let n=new Date().getHours();return n<12?"Buenos d\xEDas":n<18?"Buenas tardes":"Buenas noches"}function It(){return`${At()}, operador. Soy **DecidoOS Agent** \u{1F9E0}
|
|
1687
26
|
|
|
1688
27
|
Estoy en l\xEDnea y operativo. Aqu\xED est\xE1 mi reporte de estado:
|
|
1689
28
|
|
|
@@ -1692,10 +31,7 @@ Estoy en l\xEDnea y operativo. Aqu\xED est\xE1 mi reporte de estado:
|
|
|
1692
31
|
|
|
1693
32
|
Puedo auditar tu seguridad, escanear tu flota de procesos y m\xE1s.
|
|
1694
33
|
|
|
1695
|
-
Escribe **"ayuda"** para ver mis comandos principales
|
|
1696
|
-
}
|
|
1697
|
-
function getHelpMessage() {
|
|
1698
|
-
return `\u{1F6E0}\uFE0F **Capacidades de DecidoOS Agent**
|
|
34
|
+
Escribe **"ayuda"** para ver mis comandos principales.`}function Nt(){return`\u{1F6E0}\uFE0F **Capacidades de DecidoOS Agent**
|
|
1699
35
|
|
|
1700
36
|
\u2022 "escanea puertos" \u2014 Auditar puertos abiertos
|
|
1701
37
|
\u2022 "escanea red" \u2014 Monitorear conexiones de red
|
|
@@ -1704,102 +40,14 @@ function getHelpMessage() {
|
|
|
1704
40
|
\u2022 "estado" \u2014 Estado del sistema
|
|
1705
41
|
|
|
1706
42
|
**Voice Mode:**
|
|
1707
|
-
Presiona el bot\xF3n verde \u{1F4DE} para activar conversaci\xF3n por voz con Cortex
|
|
1708
|
-
}
|
|
1709
|
-
async function getSystemStatus() {
|
|
1710
|
-
let cpuInfo = "N/A";
|
|
1711
|
-
let memInfo = "N/A";
|
|
1712
|
-
try {
|
|
1713
|
-
const { invoke } = await import("@tauri-apps/api/core");
|
|
1714
|
-
const cpu = await invoke("get_cpu_usage");
|
|
1715
|
-
const mem = await invoke("get_memory_usage");
|
|
1716
|
-
cpuInfo = `${cpu.usage?.toFixed(1)}%`;
|
|
1717
|
-
memInfo = `${(mem.used / 1024 / 1024 / 1024).toFixed(1)} GB / ${(mem.total / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
|
1718
|
-
} catch {
|
|
1719
|
-
cpuInfo = "(requiere Tauri)";
|
|
1720
|
-
memInfo = "(requiere Tauri)";
|
|
1721
|
-
}
|
|
1722
|
-
return `\u{1F4CA} **Estado del Sistema DecidoOS**
|
|
43
|
+
Presiona el bot\xF3n verde \u{1F4DE} para activar conversaci\xF3n por voz con Cortex.`}async function Et(){let n="N/A",e="N/A";try{let{invoke:t}=await import("@tauri-apps/api/core"),s=await t("get_cpu_usage"),r=await t("get_memory_usage");n=`${s.usage?.toFixed(1)}%`,e=`${(r.used/1024/1024/1024).toFixed(1)} GB / ${(r.total/1024/1024/1024).toFixed(1)} GB`}catch{n="(requiere Tauri)",e="(requiere Tauri)"}return`\u{1F4CA} **Estado del Sistema DecidoOS**
|
|
1723
44
|
|
|
1724
|
-
\u25B8 **CPU**: ${
|
|
1725
|
-
\u25B8 **RAM**: ${
|
|
45
|
+
\u25B8 **CPU**: ${n}
|
|
46
|
+
\u25B8 **RAM**: ${e}
|
|
1726
47
|
\u25B8 **Cortex**: Local mode
|
|
1727
|
-
\u25B8 **Uptime Session**: ${
|
|
48
|
+
\u25B8 **Uptime Session**: ${$t()}
|
|
1728
49
|
|
|
1729
|
-
Todo operativo. \xBFNecesitas algo m\xE1s
|
|
1730
|
-
}
|
|
1731
|
-
function getSessionUptime() {
|
|
1732
|
-
const uptime = performance.now();
|
|
1733
|
-
const minutes = Math.floor(uptime / 6e4);
|
|
1734
|
-
const hours = Math.floor(minutes / 60);
|
|
1735
|
-
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
1736
|
-
return `${minutes}m`;
|
|
1737
|
-
}
|
|
1738
|
-
async function processLocalMessage(input) {
|
|
1739
|
-
const trimmed = input.trim();
|
|
1740
|
-
if (GREETING_PATTERNS.some((p) => p.test(trimmed))) {
|
|
1741
|
-
return { text: getWelcomeMessage() };
|
|
1742
|
-
}
|
|
1743
|
-
if (HELP_PATTERNS.some((p) => p.test(trimmed))) {
|
|
1744
|
-
return { text: getHelpMessage() };
|
|
1745
|
-
}
|
|
1746
|
-
if (SYSTEM_PATTERNS.some((p) => p.test(trimmed))) {
|
|
1747
|
-
const status = await getSystemStatus();
|
|
1748
|
-
return { text: status };
|
|
1749
|
-
}
|
|
1750
|
-
const execMatch = trimmed.match(/^(ejecuta|run|exec|corre)\s+(.+)/i);
|
|
1751
|
-
if (execMatch) {
|
|
1752
|
-
const command = execMatch[2];
|
|
1753
|
-
return {
|
|
1754
|
-
text: `\u26A1 Ejecutando: \`${command}\`...`,
|
|
1755
|
-
toolCalls: [{ name: "tactical.execute_command", args: { command } }]
|
|
1756
|
-
};
|
|
1757
|
-
}
|
|
1758
|
-
const scanMatch = trimmed.match(/^(escanea|scan|audita|audit)\s*(puertos|ports)/i);
|
|
1759
|
-
if (scanMatch) {
|
|
1760
|
-
return {
|
|
1761
|
-
text: "\u{1F50D} Escaneando puertos del sistema...",
|
|
1762
|
-
toolCalls: [{ name: "security.audit_ports", args: { includeLoopback: false } }]
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
const networkMatch = trimmed.match(/^(escanea|scan|monitorea|monitor)\s*(red|network|conexiones|connections)/i);
|
|
1766
|
-
if (networkMatch) {
|
|
1767
|
-
return {
|
|
1768
|
-
text: "\u{1F310} Analizando conexiones de red...",
|
|
1769
|
-
toolCalls: [{ name: "security.network_monitor", args: {} }]
|
|
1770
|
-
};
|
|
1771
|
-
}
|
|
1772
|
-
const fleetMatch = trimmed.match(/^(escanea|scan)\s*(flota|fleet|procesos|processes)/i);
|
|
1773
|
-
if (fleetMatch) {
|
|
1774
|
-
return {
|
|
1775
|
-
text: "\u{1F6F8} Escaneando flotilla de agentes...",
|
|
1776
|
-
toolCalls: [{ name: "tactical.scan_fleet", args: {} }]
|
|
1777
|
-
};
|
|
1778
|
-
}
|
|
1779
|
-
const listMatch = trimmed.match(/^(lista|list|muestra|show)\s*(tareas|tasks|playbooks)/i);
|
|
1780
|
-
if (listMatch) {
|
|
1781
|
-
return {
|
|
1782
|
-
text: "\u{1F4CB} Listando tareas...",
|
|
1783
|
-
toolCalls: [{ name: "tactical.list_tasks", args: { status: "all" } }]
|
|
1784
|
-
};
|
|
1785
|
-
}
|
|
1786
|
-
const forensicMatch = trimmed.match(/^(analiza|analyze|forense|forensic)\s*(proceso|process)?\s*(\d+)?/i);
|
|
1787
|
-
if (forensicMatch) {
|
|
1788
|
-
const pid = forensicMatch[3] ? parseInt(forensicMatch[3]) : void 0;
|
|
1789
|
-
return {
|
|
1790
|
-
text: pid ? `\u{1F52C} Analizando proceso PID ${pid}...` : "\u{1F52C} Escaneando procesos sospechosos...",
|
|
1791
|
-
toolCalls: [{ name: "security.forensic_scan", args: { pid, deep: true } }]
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
const vulnMatch = trimmed.match(/^(vulnerabilidades|vulnerabilities|vuln|audit\s*npm)/i);
|
|
1795
|
-
if (vulnMatch) {
|
|
1796
|
-
return {
|
|
1797
|
-
text: "\u{1F6E1}\uFE0F Escaneando vulnerabilidades en dependencias...",
|
|
1798
|
-
toolCalls: [{ name: "security.scan_vulnerabilities", args: {} }]
|
|
1799
|
-
};
|
|
1800
|
-
}
|
|
1801
|
-
return {
|
|
1802
|
-
text: `\u{1F916} Entendido: "${trimmed.slice(0, 100)}"
|
|
50
|
+
Todo operativo. \xBFNecesitas algo m\xE1s?`}function $t(){let n=performance.now(),e=Math.floor(n/6e4),t=Math.floor(e/60);return t>0?`${t}h ${e%60}m`:`${e}m`}async function Fe(n){let e=n.trim();if(_t.some(l=>l.test(e)))return{text:It()};if(Lt.some(l=>l.test(e)))return{text:Nt()};if(Rt.some(l=>l.test(e)))return{text:await Et()};let t=e.match(/^(ejecuta|run|exec|corre)\s+(.+)/i);if(t){let l=t[2];return{text:`\u26A1 Ejecutando: \`${l}\`...`,toolCalls:[{name:"tactical.execute_command",args:{command:l}}]}}if(e.match(/^(escanea|scan|audita|audit)\s*(puertos|ports)/i))return{text:"\u{1F50D} Escaneando puertos del sistema...",toolCalls:[{name:"security.audit_ports",args:{includeLoopback:!1}}]};if(e.match(/^(escanea|scan|monitorea|monitor)\s*(red|network|conexiones|connections)/i))return{text:"\u{1F310} Analizando conexiones de red...",toolCalls:[{name:"security.network_monitor",args:{}}]};if(e.match(/^(escanea|scan)\s*(flota|fleet|procesos|processes)/i))return{text:"\u{1F6F8} Escaneando flotilla de agentes...",toolCalls:[{name:"tactical.scan_fleet",args:{}}]};if(e.match(/^(lista|list|muestra|show)\s*(tareas|tasks|playbooks)/i))return{text:"\u{1F4CB} Listando tareas...",toolCalls:[{name:"tactical.list_tasks",args:{status:"all"}}]};let i=e.match(/^(analiza|analyze|forense|forensic)\s*(proceso|process)?\s*(\d+)?/i);if(i){let l=i[3]?parseInt(i[3]):void 0;return{text:l?`\u{1F52C} Analizando proceso PID ${l}...`:"\u{1F52C} Escaneando procesos sospechosos...",toolCalls:[{name:"security.forensic_scan",args:{pid:l,deep:!0}}]}}return e.match(/^(vulnerabilidades|vulnerabilities|vuln|audit\s*npm)/i)?{text:"\u{1F6E1}\uFE0F Escaneando vulnerabilidades en dependencias...",toolCalls:[{name:"security.scan_vulnerabilities",args:{}}]}:{text:`\u{1F916} Entendido: "${e.slice(0,100)}"
|
|
1803
51
|
|
|
1804
52
|
No tengo una respuesta local para esto. Para respuestas inteligentes con IA, conecta a **Cortex** (mindframe-cortex).
|
|
1805
53
|
|
|
@@ -1807,787 +55,12 @@ Mientras tanto, prueba:
|
|
|
1807
55
|
\u2022 "ayuda" \u2014 ver mis capacidades
|
|
1808
56
|
\u2022 "ejecuta ls" \u2014 ejecutar un comando
|
|
1809
57
|
\u2022 "escanea puertos" \u2014 auditar seguridad
|
|
1810
|
-
\u2022 "estado" \u2014 ver estado del sistema`
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
var
|
|
1819
|
-
var MAX_PEERS = 8;
|
|
1820
|
-
var PeerMeshImpl = class {
|
|
1821
|
-
myId = (0, import_uuid.v4)();
|
|
1822
|
-
myName = this._getHostname();
|
|
1823
|
-
peers = /* @__PURE__ */ new Map();
|
|
1824
|
-
connections = /* @__PURE__ */ new Map();
|
|
1825
|
-
isHostingFlag = false;
|
|
1826
|
-
isConnectedFlag = false;
|
|
1827
|
-
heartbeatTimer = null;
|
|
1828
|
-
cleanupTimer = null;
|
|
1829
|
-
// Event listeners
|
|
1830
|
-
peersListeners = /* @__PURE__ */ new Set();
|
|
1831
|
-
hostingListeners = /* @__PURE__ */ new Set();
|
|
1832
|
-
connectionListeners = /* @__PURE__ */ new Set();
|
|
1833
|
-
resultListeners = /* @__PURE__ */ new Set();
|
|
1834
|
-
// ── Host Mode ─────────────────────────────────────────
|
|
1835
|
-
/**
|
|
1836
|
-
* Start hosting a peer group.
|
|
1837
|
-
* Note: In browser WebSocket server is not available. This method
|
|
1838
|
-
* is a placeholder for Tauri (Rust-side server). In browser mode,
|
|
1839
|
-
* we simulate by acting as a "relay" through a shared connection.
|
|
1840
|
-
*/
|
|
1841
|
-
startHost(port = DEFAULT_PORT) {
|
|
1842
|
-
if (this.isHostingFlag) return;
|
|
1843
|
-
this.isHostingFlag = true;
|
|
1844
|
-
this._emitHosting(true);
|
|
1845
|
-
this._startHeartbeat();
|
|
1846
|
-
console.log(`\u{1F310} [PeerMesh] Hosting on port ${port} (peer: ${this.myId.slice(0, 8)})`);
|
|
1847
|
-
console.log(`\u{1F310} [PeerMesh] \u26A0\uFE0F Browser-mode: WebSocket server requires Tauri backend.`);
|
|
1848
|
-
console.log(`\u{1F310} [PeerMesh] Use connectToPeer() from another instance to connect.`);
|
|
1849
|
-
}
|
|
1850
|
-
stopHost() {
|
|
1851
|
-
if (!this.isHostingFlag) return;
|
|
1852
|
-
this.isHostingFlag = false;
|
|
1853
|
-
this._stopHeartbeat();
|
|
1854
|
-
this._broadcast({ type: "leave", peerId: this.myId, peerName: this.myName });
|
|
1855
|
-
this._disconnectAll();
|
|
1856
|
-
this._emitHosting(false);
|
|
1857
|
-
console.log("\u{1F310} [PeerMesh] Stopped hosting");
|
|
1858
|
-
}
|
|
1859
|
-
// ── Client Mode ───────────────────────────────────────
|
|
1860
|
-
connectToPeer(address) {
|
|
1861
|
-
if (this.connections.size >= MAX_PEERS) {
|
|
1862
|
-
console.warn(`\u{1F310} [PeerMesh] Max peers (${MAX_PEERS}) reached`);
|
|
1863
|
-
return;
|
|
1864
|
-
}
|
|
1865
|
-
const wsUrl = address.startsWith("ws") ? address : `ws://${address}`;
|
|
1866
|
-
console.log(`\u{1F310} [PeerMesh] Connecting to ${wsUrl}...`);
|
|
1867
|
-
try {
|
|
1868
|
-
const ws = new WebSocket(wsUrl);
|
|
1869
|
-
ws.onopen = () => {
|
|
1870
|
-
console.log(`\u{1F310} [PeerMesh] \u2705 Connected to ${address}`);
|
|
1871
|
-
this.connections.set(address, ws);
|
|
1872
|
-
this.isConnectedFlag = true;
|
|
1873
|
-
this._emitConnection(true);
|
|
1874
|
-
this._send(ws, {
|
|
1875
|
-
type: "join",
|
|
1876
|
-
peerId: this.myId,
|
|
1877
|
-
peerName: this.myName
|
|
1878
|
-
});
|
|
1879
|
-
this._startHeartbeat();
|
|
1880
|
-
};
|
|
1881
|
-
ws.onmessage = (event) => {
|
|
1882
|
-
try {
|
|
1883
|
-
const msg = JSON.parse(event.data);
|
|
1884
|
-
this._handleMessage(msg, address);
|
|
1885
|
-
} catch {
|
|
1886
|
-
}
|
|
1887
|
-
};
|
|
1888
|
-
ws.onclose = () => {
|
|
1889
|
-
console.log(`\u{1F310} [PeerMesh] Disconnected from ${address}`);
|
|
1890
|
-
this.connections.delete(address);
|
|
1891
|
-
for (const [id, peer] of this.peers) {
|
|
1892
|
-
if (peer.address === address) {
|
|
1893
|
-
this.peers.delete(id);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
this._emitPeers();
|
|
1897
|
-
if (this.connections.size === 0) {
|
|
1898
|
-
this.isConnectedFlag = false;
|
|
1899
|
-
this._emitConnection(false);
|
|
1900
|
-
this._stopHeartbeat();
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
ws.onerror = (err) => {
|
|
1904
|
-
console.error(`\u{1F310} [PeerMesh] Connection error to ${address}:`, err);
|
|
1905
|
-
};
|
|
1906
|
-
} catch (err) {
|
|
1907
|
-
console.error(`\u{1F310} [PeerMesh] Failed to connect:`, err);
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
disconnect() {
|
|
1911
|
-
this._broadcast({ type: "leave", peerId: this.myId, peerName: this.myName });
|
|
1912
|
-
this._disconnectAll();
|
|
1913
|
-
}
|
|
1914
|
-
// ── Share Results ─────────────────────────────────────
|
|
1915
|
-
shareResult(result) {
|
|
1916
|
-
const fullResult = {
|
|
1917
|
-
...result,
|
|
1918
|
-
peerId: this.myId,
|
|
1919
|
-
timestamp: Date.now()
|
|
1920
|
-
};
|
|
1921
|
-
this._broadcast({
|
|
1922
|
-
type: "share-result",
|
|
1923
|
-
peerId: this.myId,
|
|
1924
|
-
peerName: this.myName,
|
|
1925
|
-
payload: fullResult
|
|
1926
|
-
});
|
|
1927
|
-
console.log(`\u{1F310} [PeerMesh] Shared result to ${this.connections.size} peers`);
|
|
1928
|
-
}
|
|
1929
|
-
// ── Event Subscriptions ───────────────────────────────
|
|
1930
|
-
onPeersChanged(cb) {
|
|
1931
|
-
this.peersListeners.add(cb);
|
|
1932
|
-
return () => {
|
|
1933
|
-
this.peersListeners.delete(cb);
|
|
1934
|
-
};
|
|
1935
|
-
}
|
|
1936
|
-
onHostingChanged(cb) {
|
|
1937
|
-
this.hostingListeners.add(cb);
|
|
1938
|
-
return () => {
|
|
1939
|
-
this.hostingListeners.delete(cb);
|
|
1940
|
-
};
|
|
1941
|
-
}
|
|
1942
|
-
onConnectionChanged(cb) {
|
|
1943
|
-
this.connectionListeners.add(cb);
|
|
1944
|
-
return () => {
|
|
1945
|
-
this.connectionListeners.delete(cb);
|
|
1946
|
-
};
|
|
1947
|
-
}
|
|
1948
|
-
onSharedResult(cb) {
|
|
1949
|
-
this.resultListeners.add(cb);
|
|
1950
|
-
return () => {
|
|
1951
|
-
this.resultListeners.delete(cb);
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
// ── Getters ───────────────────────────────────────────
|
|
1955
|
-
getPeers() {
|
|
1956
|
-
return Array.from(this.peers.values());
|
|
1957
|
-
}
|
|
1958
|
-
getMyId() {
|
|
1959
|
-
return this.myId;
|
|
1960
|
-
}
|
|
1961
|
-
getMyName() {
|
|
1962
|
-
return this.myName;
|
|
1963
|
-
}
|
|
1964
|
-
// ── Message Handling ──────────────────────────────────
|
|
1965
|
-
_handleMessage(msg, fromAddress) {
|
|
1966
|
-
switch (msg.type) {
|
|
1967
|
-
case "join":
|
|
1968
|
-
this.peers.set(msg.peerId, {
|
|
1969
|
-
id: msg.peerId,
|
|
1970
|
-
name: msg.peerName,
|
|
1971
|
-
address: fromAddress,
|
|
1972
|
-
joinedAt: Date.now(),
|
|
1973
|
-
lastSeen: Date.now()
|
|
1974
|
-
});
|
|
1975
|
-
this._emitPeers();
|
|
1976
|
-
console.log(`\u{1F310} [PeerMesh] Peer joined: ${msg.peerName} (${msg.peerId.slice(0, 8)})`);
|
|
1977
|
-
break;
|
|
1978
|
-
case "leave":
|
|
1979
|
-
this.peers.delete(msg.peerId);
|
|
1980
|
-
this._emitPeers();
|
|
1981
|
-
console.log(`\u{1F310} [PeerMesh] Peer left: ${msg.peerName}`);
|
|
1982
|
-
break;
|
|
1983
|
-
case "heartbeat":
|
|
1984
|
-
if (this.peers.has(msg.peerId)) {
|
|
1985
|
-
this.peers.get(msg.peerId).lastSeen = Date.now();
|
|
1986
|
-
}
|
|
1987
|
-
break;
|
|
1988
|
-
case "share-result":
|
|
1989
|
-
if (msg.payload) {
|
|
1990
|
-
const result = msg.payload;
|
|
1991
|
-
for (const cb of this.resultListeners) {
|
|
1992
|
-
try {
|
|
1993
|
-
cb(result);
|
|
1994
|
-
} catch {
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
break;
|
|
1999
|
-
case "peers-update":
|
|
2000
|
-
if (Array.isArray(msg.payload)) {
|
|
2001
|
-
for (const p of msg.payload) {
|
|
2002
|
-
if (p.id !== this.myId && !this.peers.has(p.id)) {
|
|
2003
|
-
this.peers.set(p.id, p);
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
this._emitPeers();
|
|
2007
|
-
}
|
|
2008
|
-
break;
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
// ── Internal Helpers ──────────────────────────────────
|
|
2012
|
-
_send(ws, msg) {
|
|
2013
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
2014
|
-
ws.send(JSON.stringify(msg));
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
_broadcast(msg) {
|
|
2018
|
-
for (const ws of this.connections.values()) {
|
|
2019
|
-
this._send(ws, msg);
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
_disconnectAll() {
|
|
2023
|
-
for (const ws of this.connections.values()) {
|
|
2024
|
-
try {
|
|
2025
|
-
ws.close();
|
|
2026
|
-
} catch {
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
this.connections.clear();
|
|
2030
|
-
this.peers.clear();
|
|
2031
|
-
this.isConnectedFlag = false;
|
|
2032
|
-
this._stopHeartbeat();
|
|
2033
|
-
this._emitPeers();
|
|
2034
|
-
this._emitConnection(false);
|
|
2035
|
-
}
|
|
2036
|
-
_startHeartbeat() {
|
|
2037
|
-
if (this.heartbeatTimer) return;
|
|
2038
|
-
this.heartbeatTimer = setInterval(() => {
|
|
2039
|
-
this._broadcast({
|
|
2040
|
-
type: "heartbeat",
|
|
2041
|
-
peerId: this.myId,
|
|
2042
|
-
peerName: this.myName
|
|
2043
|
-
});
|
|
2044
|
-
}, HEARTBEAT_INTERVAL);
|
|
2045
|
-
this.cleanupTimer = setInterval(() => {
|
|
2046
|
-
const now = Date.now();
|
|
2047
|
-
let changed = false;
|
|
2048
|
-
for (const [id, peer] of this.peers) {
|
|
2049
|
-
if (now - peer.lastSeen > PEER_TIMEOUT) {
|
|
2050
|
-
this.peers.delete(id);
|
|
2051
|
-
changed = true;
|
|
2052
|
-
console.log(`\u{1F310} [PeerMesh] Peer timed out: ${peer.name}`);
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
if (changed) this._emitPeers();
|
|
2056
|
-
}, PEER_TIMEOUT);
|
|
2057
|
-
}
|
|
2058
|
-
_stopHeartbeat() {
|
|
2059
|
-
if (this.heartbeatTimer) {
|
|
2060
|
-
clearInterval(this.heartbeatTimer);
|
|
2061
|
-
this.heartbeatTimer = null;
|
|
2062
|
-
}
|
|
2063
|
-
if (this.cleanupTimer) {
|
|
2064
|
-
clearInterval(this.cleanupTimer);
|
|
2065
|
-
this.cleanupTimer = null;
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
// ── Event Emitters ────────────────────────────────────
|
|
2069
|
-
_emitPeers() {
|
|
2070
|
-
const list = this.getPeers();
|
|
2071
|
-
for (const cb of this.peersListeners) {
|
|
2072
|
-
try {
|
|
2073
|
-
cb(list);
|
|
2074
|
-
} catch {
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
_emitHosting(hosting) {
|
|
2079
|
-
for (const cb of this.hostingListeners) {
|
|
2080
|
-
try {
|
|
2081
|
-
cb(hosting);
|
|
2082
|
-
} catch {
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
_emitConnection(connected) {
|
|
2087
|
-
for (const cb of this.connectionListeners) {
|
|
2088
|
-
try {
|
|
2089
|
-
cb(connected);
|
|
2090
|
-
} catch {
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
// ── Hostname ──────────────────────────────────────────
|
|
2095
|
-
_getHostname() {
|
|
2096
|
-
try {
|
|
2097
|
-
return `user-${Math.random().toString(36).slice(2, 6)}`;
|
|
2098
|
-
} catch {
|
|
2099
|
-
return "anonymous";
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
};
|
|
2103
|
-
var peerMesh = new PeerMeshImpl();
|
|
2104
|
-
|
|
2105
|
-
// src/ai/hooks/useTokenWallet.ts
|
|
2106
|
-
var import_react = require("react");
|
|
2107
|
-
function useTokenWallet() {
|
|
2108
|
-
const [summary, setSummary] = (0, import_react.useState)(tokenWallet.getSummary());
|
|
2109
|
-
(0, import_react.useEffect)(() => {
|
|
2110
|
-
const unsubscribe = tokenWallet.subscribe(setSummary);
|
|
2111
|
-
return unsubscribe;
|
|
2112
|
-
}, []);
|
|
2113
|
-
const clearHistory = (0, import_react.useCallback)(() => {
|
|
2114
|
-
tokenWallet.clearHistory();
|
|
2115
|
-
}, []);
|
|
2116
|
-
const getRecentHistory = (0, import_react.useCallback)((count = 20) => {
|
|
2117
|
-
return tokenWallet.getRecentHistory(count);
|
|
2118
|
-
}, []);
|
|
2119
|
-
return {
|
|
2120
|
-
summary,
|
|
2121
|
-
clearHistory,
|
|
2122
|
-
getRecentHistory,
|
|
2123
|
-
totalTokens: summary.totalTokens,
|
|
2124
|
-
totalCost: summary.totalCostUsd,
|
|
2125
|
-
totalCalls: summary.totalCalls,
|
|
2126
|
-
byProvider: summary.byProvider
|
|
2127
|
-
};
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
// src/ai/hooks/usePeerMesh.ts
|
|
2131
|
-
var import_react2 = require("react");
|
|
2132
|
-
function usePeerMesh() {
|
|
2133
|
-
const [peers, setPeers] = (0, import_react2.useState)([]);
|
|
2134
|
-
const [isHosting, setIsHosting] = (0, import_react2.useState)(false);
|
|
2135
|
-
const [isConnected, setIsConnected] = (0, import_react2.useState)(false);
|
|
2136
|
-
const [sharedResults, setSharedResults] = (0, import_react2.useState)([]);
|
|
2137
|
-
const resultsRef = (0, import_react2.useRef)(sharedResults);
|
|
2138
|
-
resultsRef.current = sharedResults;
|
|
2139
|
-
(0, import_react2.useEffect)(() => {
|
|
2140
|
-
const unsubPeers = peerMesh.onPeersChanged((newPeers) => {
|
|
2141
|
-
setPeers([...newPeers]);
|
|
2142
|
-
});
|
|
2143
|
-
const unsubHosting = peerMesh.onHostingChanged((hosting) => {
|
|
2144
|
-
setIsHosting(hosting);
|
|
2145
|
-
});
|
|
2146
|
-
const unsubConnected = peerMesh.onConnectionChanged((connected) => {
|
|
2147
|
-
setIsConnected(connected);
|
|
2148
|
-
});
|
|
2149
|
-
const unsubResult = peerMesh.onSharedResult((result) => {
|
|
2150
|
-
setSharedResults((prev) => [...prev.slice(-49), result]);
|
|
2151
|
-
});
|
|
2152
|
-
return () => {
|
|
2153
|
-
unsubPeers();
|
|
2154
|
-
unsubHosting();
|
|
2155
|
-
unsubConnected();
|
|
2156
|
-
unsubResult();
|
|
2157
|
-
};
|
|
2158
|
-
}, []);
|
|
2159
|
-
const startHost = (0, import_react2.useCallback)((port) => {
|
|
2160
|
-
peerMesh.startHost(port);
|
|
2161
|
-
}, []);
|
|
2162
|
-
const stopHost = (0, import_react2.useCallback)(() => {
|
|
2163
|
-
peerMesh.stopHost();
|
|
2164
|
-
}, []);
|
|
2165
|
-
const connectToPeer = (0, import_react2.useCallback)((address) => {
|
|
2166
|
-
peerMesh.connectToPeer(address);
|
|
2167
|
-
}, []);
|
|
2168
|
-
const disconnect = (0, import_react2.useCallback)(() => {
|
|
2169
|
-
peerMesh.disconnect();
|
|
2170
|
-
}, []);
|
|
2171
|
-
const shareResult = (0, import_react2.useCallback)((result) => {
|
|
2172
|
-
peerMesh.shareResult(result);
|
|
2173
|
-
}, []);
|
|
2174
|
-
return {
|
|
2175
|
-
peers,
|
|
2176
|
-
isHosting,
|
|
2177
|
-
isConnected,
|
|
2178
|
-
sharedResults,
|
|
2179
|
-
peerCount: peers.length,
|
|
2180
|
-
startHost,
|
|
2181
|
-
stopHost,
|
|
2182
|
-
connectToPeer,
|
|
2183
|
-
disconnect,
|
|
2184
|
-
shareResult
|
|
2185
|
-
};
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
// src/ai/components/TokenWalletPanel.tsx
|
|
2189
|
-
var import_react3 = require("react");
|
|
2190
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
2191
|
-
var PROVIDER_COLORS = {
|
|
2192
|
-
ollama: "text-accent-green",
|
|
2193
|
-
gemini: "text-accent-blue",
|
|
2194
|
-
anthropic: "text-accent-purple",
|
|
2195
|
-
openai: "text-accent-cyan",
|
|
2196
|
-
mlx: "text-accent-amber"
|
|
2197
|
-
};
|
|
2198
|
-
var PROVIDER_BG = {
|
|
2199
|
-
ollama: "bg-emerald-500/10",
|
|
2200
|
-
gemini: "bg-blue-500/10",
|
|
2201
|
-
anthropic: "bg-purple-500/10",
|
|
2202
|
-
openai: "bg-cyan-500/10",
|
|
2203
|
-
mlx: "bg-amber-500/10"
|
|
2204
|
-
};
|
|
2205
|
-
var PROVIDER_ICONS = {
|
|
2206
|
-
ollama: "\u{1F999}",
|
|
2207
|
-
gemini: "\u2728",
|
|
2208
|
-
anthropic: "\u{1F9E0}",
|
|
2209
|
-
openai: "\u26A1",
|
|
2210
|
-
mlx: "\u{1F34E}"
|
|
2211
|
-
};
|
|
2212
|
-
function formatCost(usd) {
|
|
2213
|
-
if (usd === 0) return "FREE";
|
|
2214
|
-
if (usd < 0.01) return `$${usd.toFixed(6)}`;
|
|
2215
|
-
if (usd < 1) return `$${usd.toFixed(4)}`;
|
|
2216
|
-
return `$${usd.toFixed(2)}`;
|
|
2217
|
-
}
|
|
2218
|
-
function formatTokens(n) {
|
|
2219
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2220
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
2221
|
-
return String(n);
|
|
2222
|
-
}
|
|
2223
|
-
function timeAgo(ts) {
|
|
2224
|
-
const diff = Date.now() - ts;
|
|
2225
|
-
if (diff < 6e4) return "just now";
|
|
2226
|
-
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
2227
|
-
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
2228
|
-
return `${Math.floor(diff / 864e5)}d ago`;
|
|
2229
|
-
}
|
|
2230
|
-
function TokenWalletPanel() {
|
|
2231
|
-
const { summary, totalTokens, totalCost, totalCalls, byProvider, clearHistory, getRecentHistory } = useTokenWallet();
|
|
2232
|
-
const [showHistory, setShowHistory] = (0, import_react3.useState)(false);
|
|
2233
|
-
const providers2 = Object.entries(byProvider).sort((a, b) => b[1].cost - a[1].cost);
|
|
2234
|
-
const history = showHistory ? getRecentHistory(15) : [];
|
|
2235
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle", children: [
|
|
2236
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
2237
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2238
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-lg", children: "\u{1F4B0}" }),
|
|
2239
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-sm font-semibold text-text-primary", children: "Token Wallet" })
|
|
2240
|
-
] }),
|
|
2241
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2242
|
-
"button",
|
|
2243
|
-
{
|
|
2244
|
-
onClick: clearHistory,
|
|
2245
|
-
className: "text-[10px] px-2 py-0.5 rounded-md bg-surface-glass text-text-muted\n hover:bg-surface-tertiary hover:text-text-secondary transition-colors",
|
|
2246
|
-
children: "Clear"
|
|
2247
|
-
}
|
|
2248
|
-
)
|
|
2249
|
-
] }),
|
|
2250
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
2251
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { label: "Tokens", value: formatTokens(totalTokens), accent: "text-accent-cyan" }),
|
|
2252
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { label: "Cost", value: formatCost(totalCost), accent: totalCost === 0 ? "text-accent-green" : "text-accent-amber" }),
|
|
2253
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { label: "Calls", value: String(totalCalls), accent: "text-accent-blue" })
|
|
2254
|
-
] }),
|
|
2255
|
-
providers2.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mt-1", children: [
|
|
2256
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] font-medium text-text-muted uppercase tracking-wider", children: "By Provider" }),
|
|
2257
|
-
providers2.map(([id, data]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2258
|
-
"div",
|
|
2259
|
-
{
|
|
2260
|
-
className: `flex items-center justify-between px-3 py-2 rounded-xl ${PROVIDER_BG[id] || "bg-surface-glass"} transition-colors`,
|
|
2261
|
-
children: [
|
|
2262
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2263
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm", children: PROVIDER_ICONS[id] || "\u{1F916}" }),
|
|
2264
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `text-xs font-medium ${PROVIDER_COLORS[id] || "text-text-primary"}`, children: id.charAt(0).toUpperCase() + id.slice(1) })
|
|
2265
|
-
] }),
|
|
2266
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-3 text-[11px]", children: [
|
|
2267
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-text-muted", children: [
|
|
2268
|
-
data.calls,
|
|
2269
|
-
" calls"
|
|
2270
|
-
] }),
|
|
2271
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-text-secondary font-mono", children: formatTokens(data.tokens) }),
|
|
2272
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold text-text-primary font-mono", children: formatCost(data.cost) })
|
|
2273
|
-
] })
|
|
2274
|
-
]
|
|
2275
|
-
},
|
|
2276
|
-
id
|
|
2277
|
-
))
|
|
2278
|
-
] }),
|
|
2279
|
-
providers2.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center py-6 text-text-muted gap-1", children: [
|
|
2280
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-2xl opacity-50", children: "\u{1F4CA}" }),
|
|
2281
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs", children: "No AI calls recorded yet" })
|
|
2282
|
-
] }),
|
|
2283
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2284
|
-
"button",
|
|
2285
|
-
{
|
|
2286
|
-
onClick: () => setShowHistory(!showHistory),
|
|
2287
|
-
className: "text-[10px] text-text-muted hover:text-text-secondary transition-colors self-center",
|
|
2288
|
-
children: showHistory ? "\u25B2 Hide recent" : "\u25BC Show recent calls"
|
|
2289
|
-
}
|
|
2290
|
-
),
|
|
2291
|
-
showHistory && history.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1 max-h-48 overflow-y-auto", children: history.reverse().map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HistoryRow, { entry }, `${entry.timestamp}-${i}`)) })
|
|
2292
|
-
] });
|
|
2293
|
-
}
|
|
2294
|
-
function StatCard({ label, value, accent }) {
|
|
2295
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center p-2 rounded-xl bg-surface-tertiary/50", children: [
|
|
2296
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[9px] text-text-muted uppercase tracking-wider", children: label }),
|
|
2297
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `text-base font-bold font-mono ${accent}`, children: value })
|
|
2298
|
-
] });
|
|
2299
|
-
}
|
|
2300
|
-
function HistoryRow({ entry }) {
|
|
2301
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between px-2 py-1.5 rounded-lg bg-surface-glass text-[10px]", children: [
|
|
2302
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
2303
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: PROVIDER_ICONS[entry.provider] || "\u{1F916}" }),
|
|
2304
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-text-secondary font-mono", children: entry.model.split("/").pop()?.slice(0, 18) })
|
|
2305
|
-
] }),
|
|
2306
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2307
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-text-muted", children: formatTokens(entry.totalTokens) }),
|
|
2308
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-text-primary font-mono", children: formatCost(entry.estimatedCostUsd) }),
|
|
2309
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-text-muted", children: timeAgo(entry.timestamp) })
|
|
2310
|
-
] })
|
|
2311
|
-
] });
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
// src/ai/components/PeerNetworkPanel.tsx
|
|
2315
|
-
var import_react4 = require("react");
|
|
2316
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
2317
|
-
function timeAgo2(ts) {
|
|
2318
|
-
const diff = Date.now() - ts;
|
|
2319
|
-
if (diff < 6e4) return "just now";
|
|
2320
|
-
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
2321
|
-
return `${Math.floor(diff / 36e5)}h ago`;
|
|
2322
|
-
}
|
|
2323
|
-
function PeerNetworkPanel() {
|
|
2324
|
-
const {
|
|
2325
|
-
peers,
|
|
2326
|
-
isHosting,
|
|
2327
|
-
isConnected,
|
|
2328
|
-
sharedResults,
|
|
2329
|
-
peerCount,
|
|
2330
|
-
startHost,
|
|
2331
|
-
stopHost,
|
|
2332
|
-
connectToPeer,
|
|
2333
|
-
disconnect
|
|
2334
|
-
} = usePeerMesh();
|
|
2335
|
-
const [peerAddress, setPeerAddress] = (0, import_react4.useState)("");
|
|
2336
|
-
const [hostPort, setHostPort] = (0, import_react4.useState)("9876");
|
|
2337
|
-
const handleConnect = (0, import_react4.useCallback)(() => {
|
|
2338
|
-
if (!peerAddress.trim()) return;
|
|
2339
|
-
connectToPeer(peerAddress.trim());
|
|
2340
|
-
setPeerAddress("");
|
|
2341
|
-
}, [peerAddress, connectToPeer]);
|
|
2342
|
-
const handleKeyDown = (0, import_react4.useCallback)((e) => {
|
|
2343
|
-
if (e.key === "Enter") handleConnect();
|
|
2344
|
-
}, [handleConnect]);
|
|
2345
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle", children: [
|
|
2346
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
2347
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2348
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-lg", children: "\u{1F310}" }),
|
|
2349
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-text-primary", children: "Peer Network" }),
|
|
2350
|
-
peerCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "px-1.5 py-0.5 text-[10px] font-bold rounded-full bg-emerald-500/20 text-accent-green", children: peerCount })
|
|
2351
|
-
] }),
|
|
2352
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatusBadge, { isHosting, isConnected })
|
|
2353
|
-
] }),
|
|
2354
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 p-3 rounded-xl bg-surface-tertiary/50", children: [
|
|
2355
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
2356
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col", children: [
|
|
2357
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[11px] font-medium text-text-secondary", children: "Host a Group" }),
|
|
2358
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[9px] text-text-muted", children: "Others can connect to you" })
|
|
2359
|
-
] }),
|
|
2360
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2361
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2362
|
-
"input",
|
|
2363
|
-
{
|
|
2364
|
-
className: "w-16 px-2 py-1 text-[11px] font-mono text-text-primary bg-surface-primary\n border border-border-subtle rounded-lg text-center\n focus:border-border-strong focus:outline-none transition-colors",
|
|
2365
|
-
value: hostPort,
|
|
2366
|
-
onChange: (e) => setHostPort(e.target.value),
|
|
2367
|
-
placeholder: "Port",
|
|
2368
|
-
disabled: isHosting
|
|
2369
|
-
}
|
|
2370
|
-
),
|
|
2371
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2372
|
-
"button",
|
|
2373
|
-
{
|
|
2374
|
-
onClick: () => isHosting ? stopHost() : startHost(Number(hostPort)),
|
|
2375
|
-
className: `px-3 py-1 text-[11px] font-medium rounded-lg transition-all
|
|
2376
|
-
${isHosting ? "bg-red-500/15 text-accent-red hover:bg-red-500/25" : "bg-emerald-500/15 text-accent-green hover:bg-emerald-500/25"}`,
|
|
2377
|
-
children: isHosting ? "\u23F9 Stop" : "\u25B6 Start"
|
|
2378
|
-
}
|
|
2379
|
-
)
|
|
2380
|
-
] })
|
|
2381
|
-
] }),
|
|
2382
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-border-subtle" }),
|
|
2383
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
2384
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[11px] font-medium text-text-secondary", children: "Join a Group" }),
|
|
2385
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
|
|
2386
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2387
|
-
"input",
|
|
2388
|
-
{
|
|
2389
|
-
className: "flex-1 px-3 py-1.5 text-[11px] font-mono text-text-primary bg-surface-primary\n border border-border-subtle rounded-lg placeholder:text-text-muted\n focus:border-border-strong focus:outline-none transition-colors",
|
|
2390
|
-
value: peerAddress,
|
|
2391
|
-
onChange: (e) => setPeerAddress(e.target.value),
|
|
2392
|
-
onKeyDown: handleKeyDown,
|
|
2393
|
-
placeholder: "192.168.1.10:9876",
|
|
2394
|
-
disabled: isConnected
|
|
2395
|
-
}
|
|
2396
|
-
),
|
|
2397
|
-
isConnected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2398
|
-
"button",
|
|
2399
|
-
{
|
|
2400
|
-
onClick: disconnect,
|
|
2401
|
-
className: "px-3 py-1.5 text-[11px] font-medium rounded-lg bg-red-500/15 text-accent-red\n hover:bg-red-500/25 transition-all",
|
|
2402
|
-
children: "Disconnect"
|
|
2403
|
-
}
|
|
2404
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2405
|
-
"button",
|
|
2406
|
-
{
|
|
2407
|
-
onClick: handleConnect,
|
|
2408
|
-
className: "px-3 py-1.5 text-[11px] font-medium rounded-lg bg-cyan-500/15 text-accent-cyan\n hover:bg-cyan-500/25 transition-all",
|
|
2409
|
-
children: "Connect"
|
|
2410
|
-
}
|
|
2411
|
-
)
|
|
2412
|
-
] })
|
|
2413
|
-
] })
|
|
2414
|
-
] }),
|
|
2415
|
-
peers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
2416
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] font-medium text-text-muted uppercase tracking-wider", children: "Connected Peers" }),
|
|
2417
|
-
peers.map((peer) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PeerRow, { peer }, peer.id))
|
|
2418
|
-
] }),
|
|
2419
|
-
!isHosting && !isConnected && peers.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center justify-center py-5 text-text-muted gap-1.5", children: [
|
|
2420
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-2xl opacity-40", children: "\u{1F517}" }),
|
|
2421
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-xs text-center", children: [
|
|
2422
|
-
"Host a group or connect to a peer",
|
|
2423
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}),
|
|
2424
|
-
"to share AI results"
|
|
2425
|
-
] })
|
|
2426
|
-
] }),
|
|
2427
|
-
sharedResults.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1.5 mt-1", children: [
|
|
2428
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] font-medium text-text-muted uppercase tracking-wider", children: [
|
|
2429
|
-
"Shared Results (",
|
|
2430
|
-
sharedResults.length,
|
|
2431
|
-
")"
|
|
2432
|
-
] }),
|
|
2433
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col gap-1 max-h-40 overflow-y-auto", children: sharedResults.slice(-8).reverse().map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2434
|
-
"div",
|
|
2435
|
-
{
|
|
2436
|
-
className: "px-3 py-2 rounded-xl bg-surface-glass text-[10px]",
|
|
2437
|
-
children: [
|
|
2438
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
|
|
2439
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-medium text-accent-purple", children: r.peerId.slice(0, 8) }),
|
|
2440
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-text-muted font-mono", children: r.model })
|
|
2441
|
-
] }),
|
|
2442
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-text-secondary line-clamp-2", children: [
|
|
2443
|
-
r.response.slice(0, 120),
|
|
2444
|
-
"..."
|
|
2445
|
-
] })
|
|
2446
|
-
]
|
|
2447
|
-
},
|
|
2448
|
-
`${r.timestamp}-${i}`
|
|
2449
|
-
)) })
|
|
2450
|
-
] })
|
|
2451
|
-
] });
|
|
2452
|
-
}
|
|
2453
|
-
function StatusBadge({ isHosting, isConnected }) {
|
|
2454
|
-
if (isHosting) {
|
|
2455
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-emerald-500/15 text-[10px] font-medium text-accent-green", children: [
|
|
2456
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" }),
|
|
2457
|
-
"Hosting"
|
|
2458
|
-
] });
|
|
2459
|
-
}
|
|
2460
|
-
if (isConnected) {
|
|
2461
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-cyan-500/15 text-[10px] font-medium text-accent-cyan", children: [
|
|
2462
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" }),
|
|
2463
|
-
"Connected"
|
|
2464
|
-
] });
|
|
2465
|
-
}
|
|
2466
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-surface-glass text-[10px] text-text-muted", children: [
|
|
2467
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-surface-tertiary" }),
|
|
2468
|
-
"Offline"
|
|
2469
|
-
] });
|
|
2470
|
-
}
|
|
2471
|
-
function PeerRow({ peer }) {
|
|
2472
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 rounded-xl bg-surface-glass transition-colors hover:bg-surface-tertiary/50", children: [
|
|
2473
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2474
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-2 h-2 rounded-full bg-emerald-400" }),
|
|
2475
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs font-medium text-text-primary", children: peer.name })
|
|
2476
|
-
] }),
|
|
2477
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 text-[10px] text-text-muted", children: [
|
|
2478
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-mono", children: peer.address }),
|
|
2479
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\xB7" }),
|
|
2480
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: timeAgo2(peer.lastSeen) })
|
|
2481
|
-
] })
|
|
2482
|
-
] });
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
// src/crypto.ts
|
|
2486
|
-
async function decryptEvent(base64Payload, base64Key) {
|
|
2487
|
-
try {
|
|
2488
|
-
const keyBuffer = Uint8Array.from(atob(base64Key), (c) => c.charCodeAt(0));
|
|
2489
|
-
const payloadBuffer = Uint8Array.from(atob(base64Payload), (c) => c.charCodeAt(0));
|
|
2490
|
-
if (payloadBuffer.byteLength < 12) {
|
|
2491
|
-
throw new Error("Payload too short to contain a valid nonce");
|
|
2492
|
-
}
|
|
2493
|
-
const nonce = payloadBuffer.slice(0, 12);
|
|
2494
|
-
const ciphertext = payloadBuffer.slice(12);
|
|
2495
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
2496
|
-
"raw",
|
|
2497
|
-
keyBuffer,
|
|
2498
|
-
{ name: "AES-GCM" },
|
|
2499
|
-
false,
|
|
2500
|
-
["decrypt"]
|
|
2501
|
-
);
|
|
2502
|
-
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
2503
|
-
{ name: "AES-GCM", iv: nonce },
|
|
2504
|
-
cryptoKey,
|
|
2505
|
-
ciphertext
|
|
2506
|
-
);
|
|
2507
|
-
const decoder = new TextDecoder();
|
|
2508
|
-
const jsonString = decoder.decode(decryptedBuffer);
|
|
2509
|
-
return JSON.parse(jsonString);
|
|
2510
|
-
} catch (e) {
|
|
2511
|
-
console.error("[Crypto Bridge] Failed to decrypt secure event:", e);
|
|
2512
|
-
throw e;
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
// src/rehydration.ts
|
|
2517
|
-
var StateRehydrationManager = {
|
|
2518
|
-
async rehydrate(swarmKey) {
|
|
2519
|
-
console.log("[Rehydration] \u{1F504} Iniciando recuperaci\xF3n de estado...");
|
|
2520
|
-
try {
|
|
2521
|
-
const history = await kernel.execute("get_stream_history", { limit: 100 });
|
|
2522
|
-
if (!Array.isArray(history)) {
|
|
2523
|
-
console.warn("[Rehydration] El historial devuelto no es un array v\xE1lido.");
|
|
2524
|
-
return;
|
|
2525
|
-
}
|
|
2526
|
-
const events = history.reverse();
|
|
2527
|
-
for (const rawEvent of events) {
|
|
2528
|
-
let event = rawEvent;
|
|
2529
|
-
if (typeof rawEvent === "string") {
|
|
2530
|
-
try {
|
|
2531
|
-
event = await decryptEvent(rawEvent, swarmKey);
|
|
2532
|
-
} catch (e) {
|
|
2533
|
-
console.warn("[Rehydration] No se pudo descifrar un evento, saltando...");
|
|
2534
|
-
continue;
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
this.replayEvent(event);
|
|
2538
|
-
}
|
|
2539
|
-
console.log(`[Rehydration] \u2705 Memoria restaurada: ${events.length} eventos procesados.`);
|
|
2540
|
-
} catch (error) {
|
|
2541
|
-
console.error("[Rehydration] Fallo cr\xEDtico en la rehidrataci\xF3n:", error);
|
|
2542
|
-
}
|
|
2543
|
-
},
|
|
2544
|
-
replayEvent(event) {
|
|
2545
|
-
try {
|
|
2546
|
-
kernel.injectEvent({ ...event, is_rehydration: true });
|
|
2547
|
-
} catch (e) {
|
|
2548
|
-
console.warn("[Rehydration] Error inyectando evento de historial", e);
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
};
|
|
2552
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2553
|
-
0 && (module.exports = {
|
|
2554
|
-
AnthropicProvider,
|
|
2555
|
-
GeminiProvider,
|
|
2556
|
-
OllamaProvider,
|
|
2557
|
-
OpenAIProvider,
|
|
2558
|
-
PeerNetworkPanel,
|
|
2559
|
-
StateRehydrationManager,
|
|
2560
|
-
TokenWalletPanel,
|
|
2561
|
-
chat,
|
|
2562
|
-
chatStream,
|
|
2563
|
-
clearConversationHistory,
|
|
2564
|
-
decryptEvent,
|
|
2565
|
-
embeddingService,
|
|
2566
|
-
getAllProviders,
|
|
2567
|
-
getProviderStatuses,
|
|
2568
|
-
inferenceRouter,
|
|
2569
|
-
initProviders,
|
|
2570
|
-
isOllamaAvailable,
|
|
2571
|
-
kernel,
|
|
2572
|
-
loadProviderKeys,
|
|
2573
|
-
mlxBenchmarkModel,
|
|
2574
|
-
mlxCompareModels,
|
|
2575
|
-
mlxListModels,
|
|
2576
|
-
mlxPullModel,
|
|
2577
|
-
mlxRunInference,
|
|
2578
|
-
ollamaChat,
|
|
2579
|
-
ollamaChatStream,
|
|
2580
|
-
ollamaListModels,
|
|
2581
|
-
parseToolCalls,
|
|
2582
|
-
peerMesh,
|
|
2583
|
-
processLocalMessage,
|
|
2584
|
-
routeChat,
|
|
2585
|
-
routeInference,
|
|
2586
|
-
saveProviderKeys,
|
|
2587
|
-
setProviderApiKey,
|
|
2588
|
-
startLoRATraining,
|
|
2589
|
-
stripToolCalls,
|
|
2590
|
-
tokenWallet,
|
|
2591
|
-
usePeerMesh,
|
|
2592
|
-
useTokenWallet
|
|
2593
|
-
});
|
|
58
|
+
\u2022 "estado" \u2014 ver estado del sistema`}}var qe=require("uuid"),Ot=9876,Dt=1e4,je=3e4,We=8,de=class{myId=(0,qe.v4)();myName=this._getHostname();peers=new Map;connections=new Map;isHostingFlag=!1;isConnectedFlag=!1;heartbeatTimer=null;cleanupTimer=null;peersListeners=new Set;hostingListeners=new Set;connectionListeners=new Set;resultListeners=new Set;startHost(e=Ot){this.isHostingFlag||(this.isHostingFlag=!0,this._emitHosting(!0),this._startHeartbeat(),console.log(`\u{1F310} [PeerMesh] Hosting on port ${e} (peer: ${this.myId.slice(0,8)})`),console.log("\u{1F310} [PeerMesh] \u26A0\uFE0F Browser-mode: WebSocket server requires Tauri backend."),console.log("\u{1F310} [PeerMesh] Use connectToPeer() from another instance to connect."))}stopHost(){this.isHostingFlag&&(this.isHostingFlag=!1,this._stopHeartbeat(),this._broadcast({type:"leave",peerId:this.myId,peerName:this.myName}),this._disconnectAll(),this._emitHosting(!1),console.log("\u{1F310} [PeerMesh] Stopped hosting"))}connectToPeer(e){if(this.connections.size>=We){console.warn(`\u{1F310} [PeerMesh] Max peers (${We}) reached`);return}let t=e.startsWith("ws")?e:`ws://${e}`;console.log(`\u{1F310} [PeerMesh] Connecting to ${t}...`);try{let s=new WebSocket(t);s.onopen=()=>{console.log(`\u{1F310} [PeerMesh] \u2705 Connected to ${e}`),this.connections.set(e,s),this.isConnectedFlag=!0,this._emitConnection(!0),this._send(s,{type:"join",peerId:this.myId,peerName:this.myName}),this._startHeartbeat()},s.onmessage=r=>{try{let o=JSON.parse(r.data);this._handleMessage(o,e)}catch{}},s.onclose=()=>{console.log(`\u{1F310} [PeerMesh] Disconnected from ${e}`),this.connections.delete(e);for(let[r,o]of this.peers)o.address===e&&this.peers.delete(r);this._emitPeers(),this.connections.size===0&&(this.isConnectedFlag=!1,this._emitConnection(!1),this._stopHeartbeat())},s.onerror=r=>{console.error(`\u{1F310} [PeerMesh] Connection error to ${e}:`,r)}}catch(s){console.error("\u{1F310} [PeerMesh] Failed to connect:",s)}}disconnect(){this._broadcast({type:"leave",peerId:this.myId,peerName:this.myName}),this._disconnectAll()}shareResult(e){let t={...e,peerId:this.myId,timestamp:Date.now()};this._broadcast({type:"share-result",peerId:this.myId,peerName:this.myName,payload:t}),console.log(`\u{1F310} [PeerMesh] Shared result to ${this.connections.size} peers`)}onPeersChanged(e){return this.peersListeners.add(e),()=>{this.peersListeners.delete(e)}}onHostingChanged(e){return this.hostingListeners.add(e),()=>{this.hostingListeners.delete(e)}}onConnectionChanged(e){return this.connectionListeners.add(e),()=>{this.connectionListeners.delete(e)}}onSharedResult(e){return this.resultListeners.add(e),()=>{this.resultListeners.delete(e)}}getPeers(){return Array.from(this.peers.values())}getMyId(){return this.myId}getMyName(){return this.myName}_handleMessage(e,t){switch(e.type){case"join":this.peers.set(e.peerId,{id:e.peerId,name:e.peerName,address:t,joinedAt:Date.now(),lastSeen:Date.now()}),this._emitPeers(),console.log(`\u{1F310} [PeerMesh] Peer joined: ${e.peerName} (${e.peerId.slice(0,8)})`);break;case"leave":this.peers.delete(e.peerId),this._emitPeers(),console.log(`\u{1F310} [PeerMesh] Peer left: ${e.peerName}`);break;case"heartbeat":this.peers.has(e.peerId)&&(this.peers.get(e.peerId).lastSeen=Date.now());break;case"share-result":if(e.payload){let s=e.payload;for(let r of this.resultListeners)try{r(s)}catch{}}break;case"peers-update":if(Array.isArray(e.payload)){for(let s of e.payload)s.id!==this.myId&&!this.peers.has(s.id)&&this.peers.set(s.id,s);this._emitPeers()}break}}_send(e,t){e.readyState===WebSocket.OPEN&&e.send(JSON.stringify(t))}_broadcast(e){for(let t of this.connections.values())this._send(t,e)}_disconnectAll(){for(let e of this.connections.values())try{e.close()}catch{}this.connections.clear(),this.peers.clear(),this.isConnectedFlag=!1,this._stopHeartbeat(),this._emitPeers(),this._emitConnection(!1)}_startHeartbeat(){this.heartbeatTimer||(this.heartbeatTimer=setInterval(()=>{this._broadcast({type:"heartbeat",peerId:this.myId,peerName:this.myName})},Dt),this.cleanupTimer=setInterval(()=>{let e=Date.now(),t=!1;for(let[s,r]of this.peers)e-r.lastSeen>je&&(this.peers.delete(s),t=!0,console.log(`\u{1F310} [PeerMesh] Peer timed out: ${r.name}`));t&&this._emitPeers()},je))}_stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null)}_emitPeers(){let e=this.getPeers();for(let t of this.peersListeners)try{t(e)}catch{}}_emitHosting(e){for(let t of this.hostingListeners)try{t(e)}catch{}}_emitConnection(e){for(let t of this.connectionListeners)try{t(e)}catch{}}_getHostname(){try{return`user-${Math.random().toString(36).slice(2,6)}`}catch{return"anonymous"}}},b=new de;var C=require("react");function W(){let[n,e]=(0,C.useState)(P.getSummary());(0,C.useEffect)(()=>P.subscribe(e),[]);let t=(0,C.useCallback)(()=>{P.clearHistory()},[]),s=(0,C.useCallback)((r=20)=>P.getRecentHistory(r),[]);return{summary:n,clearHistory:t,getRecentHistory:s,totalTokens:n.totalTokens,totalCost:n.totalCostUsd,totalCalls:n.totalCalls,byProvider:n.byProvider}}var x=require("react");function q(){let[n,e]=(0,x.useState)([]),[t,s]=(0,x.useState)(!1),[r,o]=(0,x.useState)(!1),[a,i]=(0,x.useState)([]),d=(0,x.useRef)(a);d.current=a,(0,x.useEffect)(()=>{let p=b.onPeersChanged(w=>{e([...w])}),f=b.onHostingChanged(w=>{s(w)}),y=b.onConnectionChanged(w=>{o(w)}),N=b.onSharedResult(w=>{i(Ve=>[...Ve.slice(-49),w])});return()=>{p(),f(),y(),N()}},[]);let l=(0,x.useCallback)(p=>{b.startHost(p)},[]),h=(0,x.useCallback)(()=>{b.stopHost()},[]),m=(0,x.useCallback)(p=>{b.connectToPeer(p)},[]),g=(0,x.useCallback)(()=>{b.disconnect()},[]),v=(0,x.useCallback)(p=>{b.shareResult(p)},[]);return{peers:n,isHosting:t,isConnected:r,sharedResults:a,peerCount:n.length,startHost:l,stopHost:h,connectToPeer:m,disconnect:g,shareResult:v}}var ze=require("react");var u=require("react/jsx-runtime"),Bt={ollama:"text-accent-green",gemini:"text-accent-blue",anthropic:"text-accent-purple",openai:"text-accent-cyan",mlx:"text-accent-amber"},Ht={ollama:"bg-emerald-500/10",gemini:"bg-blue-500/10",anthropic:"bg-purple-500/10",openai:"bg-cyan-500/10",mlx:"bg-amber-500/10"},Ge={ollama:"\u{1F999}",gemini:"\u2728",anthropic:"\u{1F9E0}",openai:"\u26A1",mlx:"\u{1F34E}"};function me(n){return n===0?"FREE":n<.01?`$${n.toFixed(6)}`:n<1?`$${n.toFixed(4)}`:`$${n.toFixed(2)}`}function pe(n){return n>=1e6?`${(n/1e6).toFixed(1)}M`:n>=1e3?`${(n/1e3).toFixed(1)}K`:String(n)}function Ut(n){let e=Date.now()-n;return e<6e4?"just now":e<36e5?`${Math.floor(e/6e4)}m ago`:e<864e5?`${Math.floor(e/36e5)}h ago`:`${Math.floor(e/864e5)}d ago`}function Je(){let{summary:n,totalTokens:e,totalCost:t,totalCalls:s,byProvider:r,clearHistory:o,getRecentHistory:a}=W(),[i,d]=(0,ze.useState)(!1),l=Object.entries(r).sort((m,g)=>g[1].cost-m[1].cost),h=i?a(15):[];return(0,u.jsxs)("div",{className:"flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle",children:[(0,u.jsxs)("div",{className:"flex items-center justify-between",children:[(0,u.jsxs)("div",{className:"flex items-center gap-2",children:[(0,u.jsx)("span",{className:"text-lg",children:"\u{1F4B0}"}),(0,u.jsx)("h3",{className:"text-sm font-semibold text-text-primary",children:"Token Wallet"})]}),(0,u.jsx)("button",{onClick:o,className:`text-[10px] px-2 py-0.5 rounded-md bg-surface-glass text-text-muted
|
|
59
|
+
hover:bg-surface-tertiary hover:text-text-secondary transition-colors`,children:"Clear"})]}),(0,u.jsxs)("div",{className:"grid grid-cols-3 gap-2",children:[(0,u.jsx)(ue,{label:"Tokens",value:pe(e),accent:"text-accent-cyan"}),(0,u.jsx)(ue,{label:"Cost",value:me(t),accent:t===0?"text-accent-green":"text-accent-amber"}),(0,u.jsx)(ue,{label:"Calls",value:String(s),accent:"text-accent-blue"})]}),l.length>0&&(0,u.jsxs)("div",{className:"flex flex-col gap-1.5 mt-1",children:[(0,u.jsx)("span",{className:"text-[10px] font-medium text-text-muted uppercase tracking-wider",children:"By Provider"}),l.map(([m,g])=>(0,u.jsxs)("div",{className:`flex items-center justify-between px-3 py-2 rounded-xl ${Ht[m]||"bg-surface-glass"} transition-colors`,children:[(0,u.jsxs)("div",{className:"flex items-center gap-2",children:[(0,u.jsx)("span",{className:"text-sm",children:Ge[m]||"\u{1F916}"}),(0,u.jsx)("span",{className:`text-xs font-medium ${Bt[m]||"text-text-primary"}`,children:m.charAt(0).toUpperCase()+m.slice(1)})]}),(0,u.jsxs)("div",{className:"flex items-center gap-3 text-[11px]",children:[(0,u.jsxs)("span",{className:"text-text-muted",children:[g.calls," calls"]}),(0,u.jsx)("span",{className:"text-text-secondary font-mono",children:pe(g.tokens)}),(0,u.jsx)("span",{className:"font-semibold text-text-primary font-mono",children:me(g.cost)})]})]},m))]}),l.length===0&&(0,u.jsxs)("div",{className:"flex flex-col items-center justify-center py-6 text-text-muted gap-1",children:[(0,u.jsx)("span",{className:"text-2xl opacity-50",children:"\u{1F4CA}"}),(0,u.jsx)("span",{className:"text-xs",children:"No AI calls recorded yet"})]}),(0,u.jsx)("button",{onClick:()=>d(!i),className:"text-[10px] text-text-muted hover:text-text-secondary transition-colors self-center",children:i?"\u25B2 Hide recent":"\u25BC Show recent calls"}),i&&h.length>0&&(0,u.jsx)("div",{className:"flex flex-col gap-1 max-h-48 overflow-y-auto",children:h.reverse().map((m,g)=>(0,u.jsx)(Kt,{entry:m},`${m.timestamp}-${g}`))})]})}function ue({label:n,value:e,accent:t}){return(0,u.jsxs)("div",{className:"flex flex-col items-center p-2 rounded-xl bg-surface-tertiary/50",children:[(0,u.jsx)("span",{className:"text-[9px] text-text-muted uppercase tracking-wider",children:n}),(0,u.jsx)("span",{className:`text-base font-bold font-mono ${t}`,children:e})]})}function Kt({entry:n}){return(0,u.jsxs)("div",{className:"flex items-center justify-between px-2 py-1.5 rounded-lg bg-surface-glass text-[10px]",children:[(0,u.jsxs)("div",{className:"flex items-center gap-1.5",children:[(0,u.jsx)("span",{children:Ge[n.provider]||"\u{1F916}"}),(0,u.jsx)("span",{className:"text-text-secondary font-mono",children:n.model.split("/").pop()?.slice(0,18)})]}),(0,u.jsxs)("div",{className:"flex items-center gap-2",children:[(0,u.jsx)("span",{className:"text-text-muted",children:pe(n.totalTokens)}),(0,u.jsx)("span",{className:"text-text-primary font-mono",children:me(n.estimatedCostUsd)}),(0,u.jsx)("span",{className:"text-text-muted",children:Ut(n.timestamp)})]})]})}var I=require("react");var c=require("react/jsx-runtime");function Ft(n){let e=Date.now()-n;return e<6e4?"just now":e<36e5?`${Math.floor(e/6e4)}m ago`:`${Math.floor(e/36e5)}h ago`}function Xe(){let{peers:n,isHosting:e,isConnected:t,sharedResults:s,peerCount:r,startHost:o,stopHost:a,connectToPeer:i,disconnect:d}=q(),[l,h]=(0,I.useState)(""),[m,g]=(0,I.useState)("9876"),v=(0,I.useCallback)(()=>{l.trim()&&(i(l.trim()),h(""))},[l,i]),p=(0,I.useCallback)(f=>{f.key==="Enter"&&v()},[v]);return(0,c.jsxs)("div",{className:"flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle",children:[(0,c.jsxs)("div",{className:"flex items-center justify-between",children:[(0,c.jsxs)("div",{className:"flex items-center gap-2",children:[(0,c.jsx)("span",{className:"text-lg",children:"\u{1F310}"}),(0,c.jsx)("h3",{className:"text-sm font-semibold text-text-primary",children:"Peer Network"}),r>0&&(0,c.jsx)("span",{className:"px-1.5 py-0.5 text-[10px] font-bold rounded-full bg-emerald-500/20 text-accent-green",children:r})]}),(0,c.jsx)(jt,{isHosting:e,isConnected:t})]}),(0,c.jsxs)("div",{className:"flex flex-col gap-2 p-3 rounded-xl bg-surface-tertiary/50",children:[(0,c.jsxs)("div",{className:"flex items-center justify-between",children:[(0,c.jsxs)("div",{className:"flex flex-col",children:[(0,c.jsx)("span",{className:"text-[11px] font-medium text-text-secondary",children:"Host a Group"}),(0,c.jsx)("span",{className:"text-[9px] text-text-muted",children:"Others can connect to you"})]}),(0,c.jsxs)("div",{className:"flex items-center gap-2",children:[(0,c.jsx)("input",{className:`w-16 px-2 py-1 text-[11px] font-mono text-text-primary bg-surface-primary
|
|
60
|
+
border border-border-subtle rounded-lg text-center
|
|
61
|
+
focus:border-border-strong focus:outline-none transition-colors`,value:m,onChange:f=>g(f.target.value),placeholder:"Port",disabled:e}),(0,c.jsx)("button",{onClick:()=>e?a():o(Number(m)),className:`px-3 py-1 text-[11px] font-medium rounded-lg transition-all
|
|
62
|
+
${e?"bg-red-500/15 text-accent-red hover:bg-red-500/25":"bg-emerald-500/15 text-accent-green hover:bg-emerald-500/25"}`,children:e?"\u23F9 Stop":"\u25B6 Start"})]})]}),(0,c.jsx)("div",{className:"h-px bg-border-subtle"}),(0,c.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,c.jsx)("span",{className:"text-[11px] font-medium text-text-secondary",children:"Join a Group"}),(0,c.jsxs)("div",{className:"flex gap-2",children:[(0,c.jsx)("input",{className:`flex-1 px-3 py-1.5 text-[11px] font-mono text-text-primary bg-surface-primary
|
|
63
|
+
border border-border-subtle rounded-lg placeholder:text-text-muted
|
|
64
|
+
focus:border-border-strong focus:outline-none transition-colors`,value:l,onChange:f=>h(f.target.value),onKeyDown:p,placeholder:"192.168.1.10:9876",disabled:t}),t?(0,c.jsx)("button",{onClick:d,className:`px-3 py-1.5 text-[11px] font-medium rounded-lg bg-red-500/15 text-accent-red
|
|
65
|
+
hover:bg-red-500/25 transition-all`,children:"Disconnect"}):(0,c.jsx)("button",{onClick:v,className:`px-3 py-1.5 text-[11px] font-medium rounded-lg bg-cyan-500/15 text-accent-cyan
|
|
66
|
+
hover:bg-cyan-500/25 transition-all`,children:"Connect"})]})]})]}),n.length>0&&(0,c.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,c.jsx)("span",{className:"text-[10px] font-medium text-text-muted uppercase tracking-wider",children:"Connected Peers"}),n.map(f=>(0,c.jsx)(Wt,{peer:f},f.id))]}),!e&&!t&&n.length===0&&(0,c.jsxs)("div",{className:"flex flex-col items-center justify-center py-5 text-text-muted gap-1.5",children:[(0,c.jsx)("span",{className:"text-2xl opacity-40",children:"\u{1F517}"}),(0,c.jsxs)("span",{className:"text-xs text-center",children:["Host a group or connect to a peer",(0,c.jsx)("br",{}),"to share AI results"]})]}),s.length>0&&(0,c.jsxs)("div",{className:"flex flex-col gap-1.5 mt-1",children:[(0,c.jsxs)("span",{className:"text-[10px] font-medium text-text-muted uppercase tracking-wider",children:["Shared Results (",s.length,")"]}),(0,c.jsx)("div",{className:"flex flex-col gap-1 max-h-40 overflow-y-auto",children:s.slice(-8).reverse().map((f,y)=>(0,c.jsxs)("div",{className:"px-3 py-2 rounded-xl bg-surface-glass text-[10px]",children:[(0,c.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,c.jsx)("span",{className:"font-medium text-accent-purple",children:f.peerId.slice(0,8)}),(0,c.jsx)("span",{className:"text-text-muted font-mono",children:f.model})]}),(0,c.jsxs)("p",{className:"text-text-secondary line-clamp-2",children:[f.response.slice(0,120),"..."]})]},`${f.timestamp}-${y}`))})]})]})}function jt({isHosting:n,isConnected:e}){return n?(0,c.jsxs)("span",{className:"flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-emerald-500/15 text-[10px] font-medium text-accent-green",children:[(0,c.jsx)("span",{className:"w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"}),"Hosting"]}):e?(0,c.jsxs)("span",{className:"flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-cyan-500/15 text-[10px] font-medium text-accent-cyan",children:[(0,c.jsx)("span",{className:"w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse"}),"Connected"]}):(0,c.jsxs)("span",{className:"flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-surface-glass text-[10px] text-text-muted",children:[(0,c.jsx)("span",{className:"w-1.5 h-1.5 rounded-full bg-surface-tertiary"}),"Offline"]})}function Wt({peer:n}){return(0,c.jsxs)("div",{className:"flex items-center justify-between px-3 py-2 rounded-xl bg-surface-glass transition-colors hover:bg-surface-tertiary/50",children:[(0,c.jsxs)("div",{className:"flex items-center gap-2",children:[(0,c.jsx)("span",{className:"w-2 h-2 rounded-full bg-emerald-400"}),(0,c.jsx)("span",{className:"text-xs font-medium text-text-primary",children:n.name})]}),(0,c.jsxs)("div",{className:"flex items-center gap-2 text-[10px] text-text-muted",children:[(0,c.jsx)("span",{className:"font-mono",children:n.address}),(0,c.jsx)("span",{children:"\xB7"}),(0,c.jsx)("span",{children:Ft(n.lastSeen)})]})]})}async function ge(n,e){try{let t=Uint8Array.from(atob(e),h=>h.charCodeAt(0)),s=Uint8Array.from(atob(n),h=>h.charCodeAt(0));if(s.byteLength<12)throw new Error("Payload too short to contain a valid nonce");let r=s.slice(0,12),o=s.slice(12),a=await crypto.subtle.importKey("raw",t,{name:"AES-GCM"},!1,["decrypt"]),i=await crypto.subtle.decrypt({name:"AES-GCM",iv:r},a,o),l=new TextDecoder().decode(i);return JSON.parse(l)}catch(t){throw console.error("[Crypto Bridge] Failed to decrypt secure event:",t),t}}var qt={async rehydrate(n){console.log("[Rehydration] \u{1F504} Iniciando recuperaci\xF3n de estado...");try{let e=await F.execute("get_stream_history",{limit:100});if(!Array.isArray(e)){console.warn("[Rehydration] El historial devuelto no es un array v\xE1lido.");return}let t=e.reverse();for(let s of t){let r=s;if(typeof s=="string")try{r=await ge(s,n)}catch{console.warn("[Rehydration] No se pudo descifrar un evento, saltando...");continue}this.replayEvent(r)}console.log(`[Rehydration] \u2705 Memoria restaurada: ${t.length} eventos procesados.`)}catch(e){console.error("[Rehydration] Fallo cr\xEDtico en la rehidrataci\xF3n:",e)}},replayEvent(n){try{F.injectEvent({...n,is_rehydration:!0})}catch(e){console.warn("[Rehydration] Error inyectando evento de historial",e)}}};0&&(module.exports={AnthropicProvider,GeminiProvider,OllamaProvider,OpenAIProvider,PeerNetworkPanel,StateRehydrationManager,TokenWalletPanel,chat,chatStream,clearConversationHistory,decryptEvent,embeddingService,getAllProviders,getProviderStatuses,inferenceRouter,initProviders,isOllamaAvailable,kernel,loadProviderKeys,mlxBenchmarkModel,mlxCompareModels,mlxListModels,mlxPullModel,mlxRunInference,ollamaChat,ollamaChatStream,ollamaListModels,parseToolCalls,peerMesh,processLocalMessage,routeChat,routeInference,saveProviderKeys,setProviderApiKey,startLoRATraining,stripToolCalls,tokenWallet,usePeerMesh,useTokenWallet});
|