llmxray 0.2.0
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/LICENSE +190 -0
- package/NOTICE +7 -0
- package/README.md +244 -0
- package/bin/cli.mjs +37 -0
- package/dist/assets/AITrainingPage-BbRryhHe.js +1 -0
- package/dist/assets/BenchmarkPage-yZ1Kt8KK.js +2 -0
- package/dist/assets/ComparisonPage-BnBp-VsC.js +1 -0
- package/dist/assets/DashboardPage-DTXPIpEx.js +101 -0
- package/dist/assets/EmbeddingsPage-Bo3g8-qN.css +1 -0
- package/dist/assets/EmbeddingsPage-CghkdGEr.js +1 -0
- package/dist/assets/GoogleCallbackPage-DqfqxXhg.js +1 -0
- package/dist/assets/JsonTreeNode-BE6Ps90W.js +13 -0
- package/dist/assets/JsonTreeNode-o8On9YD6.css +1 -0
- package/dist/assets/ModelCapabilityIcons.vue_vue_type_script_setup_true_lang-CsL7aDHj.js +1 -0
- package/dist/assets/RAGPage-B_c1Nogo.js +1 -0
- package/dist/assets/SessionPage-lWGESxx3.js +14 -0
- package/dist/assets/SettingsPage-k9HWgy9B.js +1 -0
- package/dist/assets/StatusBadge.vue_vue_type_script_setup_true_lang-f9O-U2FX.js +1 -0
- package/dist/assets/StorageGauge-DN4vJnrl.css +1 -0
- package/dist/assets/StorageGauge.vue_vue_type_style_index_0_lang-hO4dId5t.js +1 -0
- package/dist/assets/SystemPage-BgwBHFJB.js +2 -0
- package/dist/assets/TabBar.vue_vue_type_script_setup_true_lang-CnQ8vq8x.js +1 -0
- package/dist/assets/TokenStreamDisplay.vue_vue_type_script_setup_true_lang-CJW7ndOY.js +1 -0
- package/dist/assets/ToolWorkshopPage-DoVwZp_3.js +376 -0
- package/dist/assets/ToolWorkshopPage-gO5DvECk.css +1 -0
- package/dist/assets/_commonjsHelpers-DsqdWQfm.js +1 -0
- package/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +1 -0
- package/dist/assets/agent-store-_RIMRfEn.js +1 -0
- package/dist/assets/base-CrPFMexR.js +1 -0
- package/dist/assets/benchmark-db-D8D5Wf9h.js +1 -0
- package/dist/assets/canvas-ai-db-BolM-_G7.js +2 -0
- package/dist/assets/fira-code-cyrillic-400-normal-UC0NFL4U.woff2 +0 -0
- package/dist/assets/fira-code-cyrillic-400-normal-dc1q3VD7.woff +0 -0
- package/dist/assets/fira-code-cyrillic-500-normal-Bu7VCL72.woff +0 -0
- package/dist/assets/fira-code-cyrillic-500-normal-CX35h3Mg.woff2 +0 -0
- package/dist/assets/fira-code-cyrillic-ext-400-normal-Buh61xzB.woff +0 -0
- package/dist/assets/fira-code-cyrillic-ext-400-normal-txZ9Fk_1.woff2 +0 -0
- package/dist/assets/fira-code-cyrillic-ext-500-normal-CYRspHgm.woff2 +0 -0
- package/dist/assets/fira-code-cyrillic-ext-500-normal-CwtLmSFD.woff +0 -0
- package/dist/assets/fira-code-greek-400-normal-B2Gh_Y8s.woff2 +0 -0
- package/dist/assets/fira-code-greek-400-normal-DobhaxvF.woff +0 -0
- package/dist/assets/fira-code-greek-500-normal-CVO0IRNt.woff2 +0 -0
- package/dist/assets/fira-code-greek-500-normal-DEXIWiey.woff +0 -0
- package/dist/assets/fira-code-greek-ext-400-normal-DR7mBgIM.woff2 +0 -0
- package/dist/assets/fira-code-greek-ext-400-normal-DSPUheWg.woff +0 -0
- package/dist/assets/fira-code-greek-ext-500-normal-BO3iq16n.woff2 +0 -0
- package/dist/assets/fira-code-greek-ext-500-normal-CMiNLXER.woff +0 -0
- package/dist/assets/fira-code-latin-400-normal-C-QZfXAs.woff +0 -0
- package/dist/assets/fira-code-latin-400-normal-DGosTW8U.woff2 +0 -0
- package/dist/assets/fira-code-latin-500-normal-B6gioGu8.woff2 +0 -0
- package/dist/assets/fira-code-latin-500-normal-aMLC2AMm.woff +0 -0
- package/dist/assets/fira-code-latin-ext-400-normal-DG73JjOf.woff +0 -0
- package/dist/assets/fira-code-latin-ext-400-normal-KSMg0QLl.woff2 +0 -0
- package/dist/assets/fira-code-latin-ext-500-normal-CaANqXjI.woff +0 -0
- package/dist/assets/fira-code-latin-ext-500-normal-Crd9e_oI.woff2 +0 -0
- package/dist/assets/fira-code-symbols2-400-normal-_QEZCEwe.woff +0 -0
- package/dist/assets/fira-code-symbols2-500-normal-DwsgbIjQ.woff +0 -0
- package/dist/assets/format-BqVZ9rwM.js +1 -0
- package/dist/assets/generate-service-mPYQA7Xh.js +3 -0
- package/dist/assets/google-auth-CfEcVMA7.js +1 -0
- package/dist/assets/google-auth-store-BnEs-xLw.js +1 -0
- package/dist/assets/index-BqB3FY2M.js +4 -0
- package/dist/assets/index-C0qd34Zo.css +1 -0
- package/dist/assets/index-D87kMEMb.js +3 -0
- package/dist/assets/index-DoAHVxX6.js +221 -0
- package/dist/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/dist/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/dist/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
- package/dist/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
- package/dist/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
- package/dist/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
- package/dist/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
- package/dist/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
- package/dist/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/dist/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/dist/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
- package/dist/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
- package/dist/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
- package/dist/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
- package/dist/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
- package/dist/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
- package/dist/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/dist/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/dist/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
- package/dist/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
- package/dist/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
- package/dist/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
- package/dist/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
- package/dist/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
- package/dist/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/dist/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/dist/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
- package/dist/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
- package/dist/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
- package/dist/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
- package/dist/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
- package/dist/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
- package/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/dist/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
- package/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/dist/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/dist/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/dist/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
- package/dist/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
- package/dist/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
- package/dist/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
- package/dist/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
- package/dist/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
- package/dist/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/dist/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/dist/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- package/dist/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
- package/dist/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
- package/dist/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- package/dist/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
- package/dist/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
- package/dist/assets/message-memory-db-C3Ifhs2B.js +1 -0
- package/dist/assets/metrics-store-HzdMIV8O.js +1 -0
- package/dist/assets/papaparse.min-3s4OkBbM.js +10 -0
- package/dist/assets/pdf-vkbVaD7t.js +12 -0
- package/dist/assets/pdf.worker-CliDBb4N.mjs +63057 -0
- package/dist/assets/rag-store-VWnttX_x.js +18 -0
- package/dist/assets/storage-store-B1XevdC4.js +1 -0
- package/dist/assets/stream-handler-29OjEvbo.js +4 -0
- package/dist/assets/tool-workshop-store-Btx8DDqg.js +1 -0
- package/dist/assets/toolcall-store-BumXt8DE.js +1 -0
- package/dist/assets/vector-db-ChAzHl5A.js +1 -0
- package/dist/favicon.svg +4 -0
- package/dist/index.html +23 -0
- package/package.json +83 -0
- package/server/api-probe.mjs +71 -0
- package/server/index.mjs +76 -0
- package/server/system-info.mjs +120 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-DoAHVxX6.js","assets/_commonjsHelpers-DsqdWQfm.js","assets/papaparse.min-3s4OkBbM.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{_ as y,n as x,o as _,d as $,r as f,c as b}from"./index-BqB3FY2M.js";import{vectorDB as w}from"./vector-db-ChAzHl5A.js";async function I(e,o){switch(o){case"pdf":return O(e);case"docx":return j(e);case"csv":return R(e);case"txt":case"md":return e.text();default:throw new Error(`Unsupported format: ${o}`)}}async function O(e){const o=await y(()=>import("./pdf-vkbVaD7t.js"),[]);o.GlobalWorkerOptions.workerSrc=new URL("/assets/pdf.worker-CliDBb4N.mjs",import.meta.url).toString();const c=await e.arrayBuffer(),n=await o.getDocument({data:c}).promise,r=[];for(let a=1;a<=n.numPages;a++){const l=(await(await n.getPage(a)).getTextContent()).items.map(s=>"str"in s?s.str:"").join(" ");r.push(l)}return r.join(`
|
|
3
|
+
|
|
4
|
+
`)}async function j(e){const o=await y(()=>import("./index-DoAHVxX6.js").then(r=>r.i),__vite__mapDeps([0,1])),c=await e.arrayBuffer();return(await o.extractRawText({arrayBuffer:c})).value}async function R(e){const o=await y(()=>import("./papaparse.min-3s4OkBbM.js").then(n=>n.p),__vite__mapDeps([2,1])),c=await e.text();return new Promise((n,r)=>{o.default.parse(c,{header:!0,complete(a){const t=a.data.filter(u=>Object.values(u).some(l=>l!==""&&l!=null)).map((u,l)=>{const s=Object.entries(u).map(([d,m])=>`${d}: ${m}`).join(", ");return`Row ${l+1}: ${s}`});n(t.join(`
|
|
5
|
+
`))},error(a){r(a)}})})}function T(e){switch(e.split(".").pop()?.toLowerCase()){case"pdf":return"pdf";case"docx":return"docx";case"txt":return"txt";case"md":return"md";case"csv":return"csv";default:return null}}const z={chunkSize:1e3,chunkOverlap:200,separator:`
|
|
6
|
+
|
|
7
|
+
`};function A(e,o,c){const n={...z,...c},r=[],a=o.split(n.separator).filter(s=>s.trim());let t="",u=0,l=0;for(const s of a){const d=t?t+n.separator+s:s;if(d.length>n.chunkSize&&t){r.push(k(e,r.length,t,u));const m=t.slice(-n.chunkOverlap);u=l-m.length,t=m+n.separator+s}else t=d;l+=s.length+n.separator.length}return t.trim()&&r.push(k(e,r.length,t,u)),r}function k(e,o,c,n){const r=c.trim(),a={startChar:n,endChar:n+r.length};return{id:x(),documentId:e,index:o,content:r,tokenEstimate:Math.ceil(r.split(/\s+/).length*1.3),metadata:a}}async function B(e,o,c,n){const r=T(e.name);if(!r)throw new Error(`Unsupported file format: ${e.name}`);const a=x(),t={id:a,name:e.name,format:r,sizeBytes:e.size,addedAt:Date.now(),chunkCount:0,embeddingModel:o,status:"pending"};try{t.status="parsing",n?.({stage:"parsing",message:`Parsing ${e.name}...`,progress:.1});const u=await I(e,r);if(!u.trim())throw new Error("Document produced no extractable text");t.status="chunking",n?.({stage:"chunking",message:"Splitting into chunks...",progress:.3});const l=A(a,u,c);t.chunkCount=l.length,t.status="embedding",n?.({stage:"embedding",message:`Embedding ${l.length} chunks...`,progress:.4});const s=await L(l,o,(d,m)=>{const v=.4+d/m*.5;n?.({stage:"embedding",message:`Embedding chunk ${d}/${m}...`,progress:v})});return n?.({stage:"ready",message:"Storing vectors...",progress:.95}),await w.storeChunks(s),t.status="ready",await w.storeDocument(t),n?.({stage:"ready",message:"Done!",progress:1}),t}catch(u){throw t.status="error",t.error=u instanceof Error?u.message:"Ingestion failed",await w.storeDocument(t),u}}async function L(e,o,c){const n=[];for(let a=0;a<e.length;a+=10){const t=e.slice(a,a+10),u=t.map(s=>s.content),l=await _.embed({model:o,input:u});for(let s=0;s<t.length;s++){const d=t[s],m=l.embeddings[s];m&&n.push({id:x(),documentId:d.documentId,chunkId:d.id,content:d.content,embedding:m,metadata:d.metadata})}c(Math.min(a+10,e.length),e.length)}return n}async function D(e,o,c=5,n){const a=(await _.embed({model:o,input:[e]})).embeddings[0];if(!a)throw new Error("Failed to embed query");const t=await w.search(a,c,n),u=await w.getAllDocuments(),l=new Map(u.map(s=>[s.id,s.name]));return t.map(s=>({...s,documentName:l.get(s.chunk.documentId)??"Unknown"}))}function P(e){return e.length===0?"":`Use the following context to help answer the user's question. If the context doesn't contain relevant information, say so.
|
|
8
|
+
|
|
9
|
+
${e.map((c,n)=>`[Source ${n+1}: ${c.documentName} (relevance: ${(c.score*100).toFixed(1)}%)]
|
|
10
|
+
${c.chunk.content}`).join(`
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
`)}
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
Now answer the user's question based on the context above.`}const N=$("rag",()=>{const e=f([]),o=f(null),c=f(!1),n=f([]),r=f(!1),a=f(null),t=f(new Set),u=b(()=>e.value.filter(i=>i.status==="ready")),l=b(()=>u.value.filter(i=>t.value.has(i.id)));async function s(){try{e.value=await w.getAllDocuments();for(const i of e.value)i.status==="ready"&&t.value.add(i.id)}catch(i){console.error("Failed to load documents:",i)}}async function d(i,g){c.value=!0,o.value=null,a.value=null;try{const h=await B(i,g,void 0,p=>{o.value=p});e.value.push(h),t.value.add(h.id)}catch(h){throw a.value=h instanceof Error?h.message:"Ingestion failed",h}finally{c.value=!1}}async function m(i){await w.deleteDocument(i),e.value=e.value.filter(g=>g.id!==i),t.value.delete(i)}function v(i){t.value.has(i)?t.value.delete(i):t.value.add(i)}async function E(i,g,h=5){r.value=!0,a.value=null;try{const p=[...t.value];n.value=await D(i,g,h,p.length>0?p:void 0)}catch(p){a.value=p instanceof Error?p.message:"Search failed"}finally{r.value=!1}}async function S(i,g,h=5){const p=[...t.value];if(p.length===0)return"";const C=await D(i,g,h,p);return P(C)}return{documents:e,ingestProgress:o,isIngesting:c,searchResults:n,isSearching:r,error:a,enabledDocumentIds:t,readyDocuments:u,enabledDocuments:l,loadDocuments:s,addDocument:d,removeDocument:m,toggleDocument:v,search:E,getContextForQuery:S}});export{T as d,I as p,N as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as g,r as d,c as f}from"./index-BqB3FY2M.js";const b=[{id:"rag",dbName:"llmxray-rag",label:"Knowledge Base",icon:"📚",stores:["chunks","documents"]},{id:"conversations",dbName:"llmxray-conversations",label:"Conversations",icon:"💬",stores:["conversations","messages","sessions","tokens"]},{id:"benchmarks",dbName:"llmxray-benchmarks",label:"Benchmarks",icon:"⏱",stores:["results","custom-suites"]},{id:"message-memory",dbName:"llmxray-message-memory",label:"Message Memory",icon:"🧠",stores:["messages"]},{id:"canvas-ai",dbName:"llmxray-canvas-ai",label:"AI Training",icon:"🎨",stores:["training-pairs"]}];async function p(){if(!navigator.storage?.estimate)return{usage:0,quota:0,persisted:!1};const s=await navigator.storage.estimate(),e=await navigator.storage.persisted?.()??!1;return{usage:s.usage??0,quota:s.quota??0,persisted:e}}function y(s){if(s==null)return 0;if(typeof s!="object")return String(s).length*2;const e=s;let t=0;for(const o of Object.keys(e)){t+=o.length*2;const n=e[o];Array.isArray(n)&&n.length>0&&typeof n[0]=="number"?t+=n.length*8:typeof n=="string"?t+=n.length*2:typeof n=="number"?t+=8:typeof n=="boolean"?t+=4:n!=null&&typeof n=="object"&&(t+=y(n))}return t}function v(s){return new Promise(e=>{const t=indexedDB.open(s);t.onupgradeneeded=()=>{t.transaction?.abort(),e(null)},t.onsuccess=()=>e(t.result),t.onerror=()=>e(null)})}function h(s,e){return new Promise(t=>{if(!s.objectStoreNames.contains(e)){t({name:e,bytes:0,count:0});return}const n=s.transaction(e,"readonly").objectStore(e);let c=0,r=0;const i=n.openCursor();i.onsuccess=()=>{const l=i.result;l?(c+=y(l.value),r++,l.continue()):t({name:e,bytes:c,count:r})},i.onerror=()=>t({name:e,bytes:0,count:0})})}async function w(s,e){const t=await v(s);if(!t)return{totalBytes:0,recordCount:0,stores:e.map(o=>({name:o,bytes:0,count:0}))};try{const o=[];for(const r of e)o.push(await h(t,r));const n=o.reduce((r,i)=>r+i.bytes,0),c=o.reduce((r,i)=>r+i.count,0);return{totalBytes:n,recordCount:c,stores:o}}finally{t.close()}}async function S(){const s=[];for(const e of b){const{totalBytes:t,recordCount:o,stores:n}=await w(e.dbName,e.stores);s.push({id:e.id,label:e.label,icon:e.icon,type:"indexeddb",totalBytes:t,recordCount:o,stores:n})}return s}const B=g("storage",()=>{const s=d(null),e=d([]),t=d(!1),o=d(null),n=f(()=>e.value.reduce((a,u)=>a+u.totalBytes,0)),c=f(()=>!s.value||s.value.quota===0?0:Math.round(s.value.usage/s.value.quota*100)),r=f(()=>e.value.reduce((a,u)=>a+u.recordCount,0));function i(a){return e.value.find(u=>u.id===a)}async function l(){t.value=!0;try{const[a,u]=await Promise.all([p(),S()]);s.value=a,e.value=u,o.value=Date.now()}catch(a){console.error("Failed to estimate storage:",a)}finally{t.value=!1}}async function m(a=3e4){(!o.value||Date.now()-o.value>a)&&await l()}return{origin:s,databases:e,loading:t,lastUpdated:o,totalUsedBytes:n,usedPercent:c,totalRecordCount:r,getDatabaseById:i,refresh:l,refreshIfStale:m}});export{B as u};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{n as u,z as O}from"./index-BqB3FY2M.js";import{u as G,a as q}from"./toolcall-store-BumXt8DE.js";import{a as R,b as E,u as J}from"./agent-store-_RIMRfEn.js";import{u as $}from"./metrics-store-HzdMIV8O.js";const B=1e6;function F(t,n,a,e,c){const s=(e.total_duration??0)/B,o=(e.load_duration??0)/B,i=(e.prompt_eval_duration??0)/B,l=(e.eval_duration??0)/B,p=e.prompt_eval_count??0,r=e.eval_count??0,h=l>0?r/l*1e3:0,f=i>0?p/i*1e3:0,d=c.length>0?c[0]:0;return{sessionId:t,model:n,startedAt:a,completedAt:Date.now(),ttftMs:d,totalDurationMs:s,loadDurationMs:o,promptEvalDurationMs:i,evalDurationMs:l,tokensPerSecond:h,promptTokensPerSecond:f,promptTokenCount:p,completionTokenCount:r,totalTokenCount:p+r,tokenLatencies:c}}class V{constructor(n){this.stepIndex=0,this.activeStep=null,this.inThinkBlock=!1,this.completedSteps=[],this.lastProcessedLineIndex=-1,this.thinkBlockProcessed=!1,this.enablePatternDetection=n?.patternDetection??!0}get isThinking(){return this.inThinkBlock}get thinkingContent(){return this.inThinkBlock&&this.activeStep?this.activeStep.content:""}processToken(n,a){if(!this.inThinkBlock&&!this.thinkBlockProcessed&&a.includes("<think>")){this.inThinkBlock=!0;const i=a.split("<think>").pop()??"";return this.activeStep={type:"thought",content:i,startTokenIndex:n.index,startTimestamp:n.timestamp},null}if(this.inThinkBlock&&a.includes("</think>")){if(this.inThinkBlock=!1,this.thinkBlockProcessed=!0,this.activeStep){const i=a.match(/<think>([\s\S]*?)<\/think>/);if(i?this.activeStep.content=i[1]?.trim()??this.activeStep.content.trim():this.activeStep.content=this.activeStep.content.replace(/<\/think>[\s\S]*$/,"").trim(),this.activeStep.content)return this.finalizeStep(n);this.activeStep=null}return null}if(this.inThinkBlock&&this.activeStep)return this.activeStep.content+=n.text,null;if(!this.enablePatternDetection)return null;const e=a.split(`
|
|
2
|
+
`);if(e.length<2)return null;const c=e.length-2;if(c<=this.lastProcessedLineIndex)return null;this.lastProcessedLineIndex=c;const s=e[c]?.trim()??"";if(!s)return null;const o=K(s);if(o){let i=null;return this.activeStep&&(i=this.finalizeStep(n)),this.activeStep={type:o.type,content:o.content,startTokenIndex:n.index,startTimestamp:n.timestamp},i}return this.activeStep&&(this.activeStep.content+=n.text),null}finalize(){if(this.activeStep){const n={id:u(),index:this.stepIndex++,type:this.activeStep.type,content:this.activeStep.content.trim(),startTokenIndex:this.activeStep.startTokenIndex,endTokenIndex:this.activeStep.startTokenIndex,timestamp:this.activeStep.startTimestamp,durationMs:Date.now()-this.activeStep.startTimestamp};if(this.activeStep=null,n.content)return this.completedSteps.push(n),n}return null}finalizeStep(n){const a={id:u(),index:this.stepIndex++,type:this.activeStep.type,content:this.activeStep.content.trim(),startTokenIndex:this.activeStep.startTokenIndex,endTokenIndex:n.index,timestamp:this.activeStep.startTimestamp,durationMs:n.timestamp-this.activeStep.startTimestamp};return this.completedSteps.push(a),this.activeStep=null,a}}const H=[{regex:/^(?:Step\s+\d+[:.]\s*)(.+)/i,type:"thought"},{regex:/^(?:Thought[:.]\s*)(.+)/i,type:"thought"},{regex:/^(?:Observation[:.]\s*)(.+)/i,type:"observation"},{regex:/^(?:Action[:.]\s*)(.+)/i,type:"action"},{regex:/^(?:Conclusion[:.]\s*)(.+)/i,type:"conclusion"},{regex:/^(?:Therefore[,:.]\s*)(.+)/i,type:"conclusion"}];function K(t){const n=t.trim();for(const{regex:a,type:e}of H){const c=n.match(a);if(c)return{type:e,content:c[1]??n}}return null}async function Z(t,n,a){const e=t.getReader(),c=new TextDecoder;let s="";try{for(;!a?.aborted;){const{done:o,value:i}=await e.read();if(o)break;s+=c.decode(i,{stream:!0});const l=s.split(`
|
|
3
|
+
`);s=l.pop()??"";for(const p of l){const r=p.trim();if(!r||!r.startsWith("data: "))continue;const h=r.slice(6);if(h==="[DONE]")return;try{const f=JSON.parse(h);n(f)}catch{}}}}finally{e.releaseLock()}}async function W(t,n,a){const e=t.getReader(),c=new TextDecoder;let s="";try{for(;!a?.aborted;){const{done:o,value:i}=await e.read();if(o)break;s+=c.decode(i,{stream:!0});const l=s.split(`
|
|
4
|
+
`);s=l.pop()??"";for(const p of l){const r=p.trim();if(r)try{const h=JSON.parse(r);n(h)}catch{}}}if(s.trim())try{const o=JSON.parse(s.trim());n(o)}catch{}}finally{e.releaseLock()}}async function I(t,n,a){const e=G(),c=R(),s=$(),o=E(),i=J(),l=Date.now();let p=0,r=l,h="";const f=[],d=O(),b=e.sessionById(t),y=b?d.isThinkingModel(b.model):!1,x=new V({patternDetection:y});i.initGraph(t),i.addNode(t,{id:u(),type:"start",label:"Generation Start",sessionId:t,stepIndex:0,state:{},timestamp:l}),e.updateSessionStatus(t,"streaming"),await W(n,g=>{const M=Date.now(),D=M-r;h+=g.response;const C=j(D,f),v={id:u(),index:p++,text:g.response,timestamp:M,confidence:C,cumulativeText:h,interTokenLatencyMs:D};c.pushToken(t,v),e.appendOutput(t,g.response),f.push(D),r=M;const k=x.processToken(v,h);if(o.setThinking(t,x.isThinking,x.thinkingContent),k&&(o.addStep(t,k),i.addNode(t,{id:u(),type:"decision",label:`${k.type}: ${k.content.slice(0,40)}...`,sessionId:t,stepIndex:k.index,state:{reasoning:k.type},timestamp:k.timestamp})),g.done){x.finalize(),o.setThinking(t,!1,""),g.done_reason&&e.setDoneReason(t,g.done_reason);const m=e.sessionById(t);if(m){const T=F(t,m.model,l,g,f);s.recordMetrics(t,T),e.finalizeSession(t,T),s.recalculateAggregate()}i.addNode(t,{id:u(),type:"output",label:"Generation Complete",sessionId:t,stepIndex:p,state:{tokenCount:p},timestamp:Date.now()})}},a)}async function tt(t,n,a){const e=G(),c=R(),s=$(),o=E(),i=q(),l=J(),p=Date.now();let r=0,h=p,f="",d="",b="",y=!1;const x=[],g=[],M=O(),D=e.sessionById(t),C=D?M.isThinkingModel(D.model):!1,v=new V({patternDetection:C});l.initGraph(t);const k=l.getGraph(t);return(!k||k.nodes.length===0)&&l.addNode(t,{id:u(),type:"start",label:"Chat Start",sessionId:t,stepIndex:0,state:{},timestamp:p}),e.updateSessionStatus(t,"streaming"),await W(n,m=>{const T=Date.now(),w=T-h,z=m.message.thinking;if(z){y=!0,d+=z,o.setThinking(t,!0,d),x.push(w),h=T;return}y&&d&&!m.done&&(o.setThinking(t,!1,d),d.trim()&&o.addStep(t,{id:u(),index:0,type:"thought",content:d.trim(),startTokenIndex:0,endTokenIndex:r,timestamp:T,durationMs:T-p}),b=d,d="");const N=m.message.content;f+=N;let P,L;m.logprobs&&m.logprobs.length>0?(L=m.logprobs[0].logprob,P=Math.exp(L)):P=j(w,x);const A={id:u(),index:r++,text:N,timestamp:T,confidence:P,logprob:L,cumulativeText:f,interTokenLatencyMs:w};if(c.pushToken(t,A),e.appendOutput(t,N),x.push(w),h=T,!y){const S=v.processToken(A,f);o.setThinking(t,v.isThinking,v.thinkingContent),S&&o.addStep(t,S)}if(m.message.tool_calls)for(const S of m.message.tool_calls){g.push(S);const _={id:u(),sessionId:t,index:i.getToolCalls(t).length,functionName:S.function.name,arguments:S.function.arguments,status:"pending",startedAt:T,tokenIndexStart:r};i.addToolCall(t,_),l.addNode(t,{id:u(),type:"tool_call",label:`Tool: ${S.function.name}`,sessionId:t,stepIndex:r,state:{tool:S.function.name,args:S.function.arguments},timestamp:T})}if(m.done&&(y&&d.trim()&&(b=d,o.addStep(t,{id:u(),index:0,type:"thought",content:d.trim(),startTokenIndex:0,endTokenIndex:r,timestamp:Date.now(),durationMs:Date.now()-p})),v.finalize(),o.setThinking(t,!1,y?b:""),m.done_reason&&e.setDoneReason(t,m.done_reason),g.length===0)){const S=e.sessionById(t);if(S){const _=F(t,S.model,p,m,x);s.recordMetrics(t,_),e.finalizeSession(t,_),s.recalculateAggregate()}l.addNode(t,{id:u(),type:"output",label:"Chat Complete",sessionId:t,stepIndex:r,state:{tokenCount:r},timestamp:Date.now()})}},a),{toolCalls:g}}function j(t,n){if(n.length<2)return .5;const a=[...n].sort((o,i)=>o-i),e=a[Math.floor(a.length/2)];if(e<=0)return .5;const c=t/e,s=Math.max(0,Math.min(1,1-(c-.5)/2));return Math.round(s*100)/100}export{I as a,tt as e,Z as r};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as k,r as f,c as i,n as x}from"./index-BqB3FY2M.js";function m(){return{mode:"code",blocks:[],code:""}}const v="llmxray-workshop-tools",D="llmxray-tool-definitions",_=k("tool-workshop",()=>{const n=f({}),a=f(null);function p(){try{const e=localStorage.getItem(v);if(e){const t=JSON.parse(e),o={};for(const l of t)o[l.id]=l;n.value=o;return}g()}catch{}}function g(){try{const e=localStorage.getItem(D);if(!e)return;const t=JSON.parse(e),o={};for(const l of t){const d={id:l.id,definition:l.definition,implementation:m(),enabled:l.enabled,category:"custom",createdAt:l.createdAt,updatedAt:l.updatedAt,lastTestedAt:null,testResult:null};o[d.id]=d}n.value=o,c()}catch{}}function c(){const e=Object.values(n.value);localStorage.setItem(v,JSON.stringify(e))}p();const u=i(()=>Object.values(n.value)),r=i(()=>u.value.filter(e=>e.enabled)),y=i(()=>r.value.map(e=>e.definition)),b=i(()=>{const e={};for(const t of u.value){const o=t.category||"custom";e[o]||(e[o]=[]),e[o].push(t)}return e}),S=i(()=>a.value?n.value[a.value]??null:null);function s(e){const t=x(),o=Date.now(),l={id:t,definition:e.definition,implementation:e.implementation??m(),enabled:e.enabled??!1,category:e.category??"custom",createdAt:o,updatedAt:o,lastTestedAt:null,testResult:null};return n.value[t]=l,c(),t}function O(e,t){const o=n.value[e];o&&(n.value[e]={...o,...t,updatedAt:Date.now()},c())}function T(e){delete n.value[e],a.value===e&&(a.value=null),c()}function A(){n.value={},a.value=null,c()}function w(e){const t=n.value[e];if(!t)return null;const o=JSON.parse(JSON.stringify(t.definition));return o.function.name=`${o.function.name}_copy`,s({definition:o,implementation:JSON.parse(JSON.stringify(t.implementation)),category:t.category})}function N(e){const t=n.value[e];t&&(n.value[e]={...t,enabled:!t.enabled},c())}function J(){for(const e of Object.keys(n.value))n.value[e]={...n.value[e],enabled:!1};c()}function h(e){a.value=e}function E(e){return n.value[e]}function I(e){return u.value.find(t=>t.definition.function.name===e)}return{tools:n,selectedToolId:a,allTools:u,enabledTools:r,enabledDefinitions:y,toolsByCategory:b,selectedTool:S,addTool:s,updateTool:O,removeTool:T,clearAll:A,duplicateTool:w,toggleEnabled:N,disableAll:J,selectTool:h,getById:E,findByFunctionName:I}});export{_ as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as m,r as f,c as S,n as M,H as x}from"./index-BqB3FY2M.js";const B=m("sessions",()=>{const n=f(new Map),u=f(null),l=f(new Map),v=S(()=>u.value?n.value.get(u.value)??null:null),d=S(()=>[...n.value.values()].sort((e,s)=>s.createdAt-e.createdAt));function o(e){return n.value.get(e)}function a(e){const s=M(),t={id:s,mode:e.mode,model:e.model,status:"idle",createdAt:Date.now(),prompt:e.prompt,messages:e.messages??[],options:e.options??{},outputText:"",metrics:null};return n.value.set(s,t),s}function i(e,s){l.value.set(e,s)}function c(e,s){const t=n.value.get(e);t&&(t.status=s)}function r(e,s){const t=n.value.get(e);t&&(t.outputText+=s)}function g(e,s){const t=n.value.get(e);if(t){t.status="completed",t.metrics=s;const p=l.value.get(e);p&&x.saveSession(t,p).catch(console.error)}}function C(e,s){const t=n.value.get(e);t&&(t.status="error",t.error=s)}function T(e){const s=n.value.get(e);s&&(s.status="cancelled")}function w(e,s){const t=n.value.get(e);t&&(t.doneReason=s)}function A(e){u.value=e}return{sessions:n,activeSessionId:u,activeSession:v,recentSessions:d,sessionConversationMap:l,sessionById:o,createSession:a,linkSessionToConversation:i,updateSessionStatus:c,appendOutput:r,finalizeSession:g,setSessionError:C,cancelSession:T,setDoneReason:w,setActiveSession:A}}),D=m("toolcalls",()=>{const n=f(new Map);function u(o){return n.value.get(o)??[]}function l(o){return u(o).filter(a=>a.status==="pending")}function v(o,a){let i=n.value.get(o);i||(i=[],n.value.set(o,i)),i.push(a)}function d(o,a,i){const c=n.value.get(o);if(!c)return;const r=c.find(g=>g.id===a);r&&Object.assign(r,i)}return{callsBySession:n,getToolCalls:u,getPendingCalls:l,addToolCall:v,updateToolCall:d}});export{D as a,B as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const m="llmxray-rag";const a="chunks",i="documents";function l(){return new Promise((t,e)=>{const o=indexedDB.open(m,1);o.onupgradeneeded=()=>{const n=o.result;n.objectStoreNames.contains(a)||n.createObjectStore(a,{keyPath:"id"}).createIndex("byDocument","documentId",{unique:!1}),n.objectStoreNames.contains(i)||n.createObjectStore(i,{keyPath:"id"})},o.onsuccess=()=>t(o.result),o.onerror=()=>e(o.error)})}function u(t,e){return new Promise((o,n)=>{t.oncomplete=()=>o(e?.result),t.onerror=()=>n(t.error)})}const h={async storeChunks(t){const e=await l(),o=e.transaction(a,"readwrite"),n=o.objectStore(a);for(const r of t)n.put(r);await u(o),e.close()},async getChunksByDocument(t){const e=await l();return new Promise((o,n)=>{const d=e.transaction(a,"readonly").objectStore(a).index("byDocument").getAll(t);d.onsuccess=()=>{o(d.result),e.close()},d.onerror=()=>{n(d.error),e.close()}})},async getAllChunks(){const t=await l();return new Promise((e,o)=>{const r=t.transaction(a,"readonly").objectStore(a).getAll();r.onsuccess=()=>{e(r.result),t.close()},r.onerror=()=>{o(r.error),t.close()}})},async deleteChunksByDocument(t){const e=await l(),o=await this.getChunksByDocument(t),n=e.transaction(a,"readwrite"),r=n.objectStore(a);for(const c of o)r.delete(c.id);await u(n),e.close()},async storeDocument(t){const e=await l(),o=e.transaction(i,"readwrite");o.objectStore(i).put(t),await u(o),e.close()},async getAllDocuments(){const t=await l();return new Promise((e,o)=>{const r=t.transaction(i,"readonly").objectStore(i).getAll();r.onsuccess=()=>{e(r.result),t.close()},r.onerror=()=>{o(r.error),t.close()}})},async deleteDocument(t){const e=await l(),o=e.transaction(i,"readwrite");o.objectStore(i).delete(t),await u(o),await this.deleteChunksByDocument(t),e.close()},async search(t,e=5,o){let n=await this.getAllChunks();if(o&&o.length>0){const c=new Set(o);n=n.filter(s=>c.has(s.documentId))}const r=n.map(c=>({chunk:c,score:w(t,c.embedding)}));return r.sort((c,s)=>s.score-c.score),r.slice(0,e).map(({chunk:c,score:s})=>({chunk:c,score:s,documentName:""}))},async clear(){const t=await l(),e=t.transaction([a,i],"readwrite");e.objectStore(a).clear(),e.objectStore(i).clear(),await u(e),t.close()}};function w(t,e){if(t.length!==e.length)return 0;let o=0,n=0,r=0;for(let s=0;s<t.length;s++)o+=t[s]*e[s],n+=t[s]*t[s],r+=e[s]*e[s];const c=Math.sqrt(n)*Math.sqrt(r);return c===0?0:o/c}export{h as vectorDB};
|
package/dist/favicon.svg
ADDED
package/dist/index.html
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>LLMxRay — Local LLM Observatory</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BqB3FY2M.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C0qd34Zo.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<script>
|
|
13
|
+
// Apply theme before first paint to prevent flash
|
|
14
|
+
(function() {
|
|
15
|
+
var theme = localStorage.getItem('llmxray-theme') || 'system';
|
|
16
|
+
var isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
17
|
+
document.documentElement.className = isDark ? 'dark' : 'light';
|
|
18
|
+
document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
|
|
19
|
+
})();
|
|
20
|
+
</script>
|
|
21
|
+
<div id="app"></div>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llmxray",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Local LLM Observatory — See what your AI is actually doing, token by token.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"llmxray": "./bin/cli.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"server/",
|
|
13
|
+
"bin/",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"NOTICE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build": "vue-tsc -b && vite build",
|
|
21
|
+
"preview": "vite preview",
|
|
22
|
+
"start": "node server/index.mjs",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:coverage": "vitest run --coverage",
|
|
27
|
+
"test:e2e": "playwright test --project=chromium",
|
|
28
|
+
"test:e2e:headed": "playwright test --project=chromium --headed",
|
|
29
|
+
"test:e2e:ui": "playwright test --project=chromium --ui",
|
|
30
|
+
"test:e2e:live": "playwright test --project=live-ollama",
|
|
31
|
+
"test:e2e:live:headed": "playwright test --project=live-ollama --headed",
|
|
32
|
+
"test:e2e:all": "playwright test",
|
|
33
|
+
"docs:dev": "vitepress dev docs",
|
|
34
|
+
"docs:build": "vitepress build docs",
|
|
35
|
+
"docs:preview": "vitepress preview docs",
|
|
36
|
+
"deploy": "bash scripts/deploy-site.sh"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"express": "^5.1.0",
|
|
40
|
+
"http-proxy-middleware": "^3.0.3"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@babel/parser": "^7.29.0",
|
|
44
|
+
"@codemirror/lang-javascript": "^6.2.5",
|
|
45
|
+
"@codemirror/lang-json": "^6.0.2",
|
|
46
|
+
"@codemirror/theme-one-dark": "^6.1.3",
|
|
47
|
+
"@fontsource/fira-code": "^5.2.7",
|
|
48
|
+
"@fontsource/inter": "^5.2.8",
|
|
49
|
+
"@playwright/test": "^1.58.2",
|
|
50
|
+
"@tailwindcss/vite": "^4.2.1",
|
|
51
|
+
"@types/d3": "^7.4.3",
|
|
52
|
+
"@types/papaparse": "^5.5.2",
|
|
53
|
+
"@vitejs/plugin-vue": "^6.0.4",
|
|
54
|
+
"@vue-flow/background": "^1.3.2",
|
|
55
|
+
"@vue-flow/controls": "^1.1.3",
|
|
56
|
+
"@vue-flow/core": "^1.48.2",
|
|
57
|
+
"@vue-flow/minimap": "^1.5.4",
|
|
58
|
+
"@vue/test-utils": "^2.4.6",
|
|
59
|
+
"chart.js": "^4.5.1",
|
|
60
|
+
"codemirror": "^6.0.2",
|
|
61
|
+
"d3": "^7.9.0",
|
|
62
|
+
"diff": "^8.0.3",
|
|
63
|
+
"gh-pages": "^6.3.0",
|
|
64
|
+
"happy-dom": "^20.8.3",
|
|
65
|
+
"mammoth": "^1.11.0",
|
|
66
|
+
"marked": "^17.0.4",
|
|
67
|
+
"nanoid": "^5.1.6",
|
|
68
|
+
"papaparse": "^5.5.3",
|
|
69
|
+
"pdfjs-dist": "^5.5.207",
|
|
70
|
+
"pinia": "^3.0.4",
|
|
71
|
+
"recast": "^0.23.11",
|
|
72
|
+
"tailwindcss": "^4.2.1",
|
|
73
|
+
"typescript": "~5.9.3",
|
|
74
|
+
"vite": "^7.3.1",
|
|
75
|
+
"vitepress": "^1.6.4",
|
|
76
|
+
"vitest": "^4.0.18",
|
|
77
|
+
"vue": "^3.5.30",
|
|
78
|
+
"vue-chartjs": "^5.3.3",
|
|
79
|
+
"vue-codemirror": "^6.1.1",
|
|
80
|
+
"vue-router": "^5.0.3",
|
|
81
|
+
"vue-tsc": "^3.2.5"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const INTERESTING_HEADERS = [
|
|
2
|
+
'content-type', 'www-authenticate', 'server',
|
|
3
|
+
'x-ratelimit-limit', 'x-ratelimit-remaining', 'x-ratelimit-reset',
|
|
4
|
+
'retry-after', 'allow',
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
function pickHeaders(response) {
|
|
8
|
+
const picked = {}
|
|
9
|
+
for (const key of INTERESTING_HEADERS) {
|
|
10
|
+
const val = response.headers.get(key)
|
|
11
|
+
if (val) picked[key] = val
|
|
12
|
+
}
|
|
13
|
+
return picked
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function mountProbeRoutes(app) {
|
|
17
|
+
// Single URL probe
|
|
18
|
+
app.post('/api-probe', async (req, res) => {
|
|
19
|
+
const { url: target, method = 'GET', headers = {}, body } = req.body ?? {}
|
|
20
|
+
if (!target) return res.status(400).json({ error: 'Missing url parameter' })
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(target, {
|
|
24
|
+
method,
|
|
25
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
26
|
+
body: method === 'POST' || method === 'PUT' ? body : undefined,
|
|
27
|
+
})
|
|
28
|
+
const responseBody = await response.text()
|
|
29
|
+
res.json({ status: response.status, headers: pickHeaders(response), body: responseBody })
|
|
30
|
+
} catch (e) {
|
|
31
|
+
res.json({ status: 502, headers: {}, body: '', error: e.message })
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
app.get('/api-probe', async (req, res) => {
|
|
36
|
+
const target = req.query.url
|
|
37
|
+
if (!target) return res.status(400).json({ error: 'Missing url parameter' })
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(target)
|
|
41
|
+
const responseBody = await response.text()
|
|
42
|
+
res.json({ status: response.status, headers: pickHeaders(response), body: responseBody })
|
|
43
|
+
} catch (e) {
|
|
44
|
+
res.json({ status: 502, headers: {}, body: '', error: e.message })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Multi-URL probe
|
|
49
|
+
app.post('/api-probe-multi', async (req, res) => {
|
|
50
|
+
const urls = req.body?.urls ?? []
|
|
51
|
+
const results = await Promise.allSettled(
|
|
52
|
+
urls.map(async (url) => {
|
|
53
|
+
const controller = new AbortController()
|
|
54
|
+
const timer = setTimeout(() => controller.abort(), 2000)
|
|
55
|
+
try {
|
|
56
|
+
const r = await fetch(url, { signal: controller.signal })
|
|
57
|
+
const body = await r.text()
|
|
58
|
+
return { url, status: r.status, body }
|
|
59
|
+
} finally {
|
|
60
|
+
clearTimeout(timer)
|
|
61
|
+
}
|
|
62
|
+
}),
|
|
63
|
+
)
|
|
64
|
+
const out = results.map((r, i) =>
|
|
65
|
+
r.status === 'fulfilled'
|
|
66
|
+
? r.value
|
|
67
|
+
: { url: urls[i], status: 0, body: '', error: r.reason?.message },
|
|
68
|
+
)
|
|
69
|
+
res.json({ results: out })
|
|
70
|
+
})
|
|
71
|
+
}
|
package/server/index.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import { createProxyMiddleware } from 'http-proxy-middleware'
|
|
6
|
+
import { collectSystemInfo } from './system-info.mjs'
|
|
7
|
+
import { mountProbeRoutes } from './api-probe.mjs'
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const DIST_DIR = join(__dirname, '..', 'dist')
|
|
11
|
+
|
|
12
|
+
export function startServer(options = {}) {
|
|
13
|
+
const ollamaUrl = options.ollamaUrl || process.env.OLLAMA_URL || 'http://localhost:11434'
|
|
14
|
+
const port = options.port || process.env.PORT || 5174
|
|
15
|
+
|
|
16
|
+
if (!existsSync(DIST_DIR)) {
|
|
17
|
+
console.error('Error: dist/ directory not found. Run "npm run build" first.')
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const app = express()
|
|
22
|
+
app.use(express.json())
|
|
23
|
+
|
|
24
|
+
// Proxy /api/* to Ollama
|
|
25
|
+
app.use('/api', createProxyMiddleware({
|
|
26
|
+
target: ollamaUrl,
|
|
27
|
+
changeOrigin: true,
|
|
28
|
+
pathRewrite: { '^/api': '/api' },
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
// Proxy /v1/* to Ollama (OpenAI-compat endpoint)
|
|
32
|
+
app.use('/v1', createProxyMiddleware({
|
|
33
|
+
target: ollamaUrl,
|
|
34
|
+
changeOrigin: true,
|
|
35
|
+
pathRewrite: { '^/v1': '/v1' },
|
|
36
|
+
}))
|
|
37
|
+
|
|
38
|
+
// System info endpoint
|
|
39
|
+
let cachedInfo = null
|
|
40
|
+
let cacheTime = 0
|
|
41
|
+
app.get('/__system-info', (_req, res) => {
|
|
42
|
+
const now = Date.now()
|
|
43
|
+
if (!cachedInfo || now - cacheTime > 30_000) {
|
|
44
|
+
cachedInfo = collectSystemInfo()
|
|
45
|
+
cacheTime = now
|
|
46
|
+
}
|
|
47
|
+
res.json(cachedInfo)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// API probe endpoints
|
|
51
|
+
mountProbeRoutes(app)
|
|
52
|
+
|
|
53
|
+
// Serve static files
|
|
54
|
+
app.use(express.static(DIST_DIR))
|
|
55
|
+
|
|
56
|
+
// SPA fallback: serve index.html for client-side routes
|
|
57
|
+
app.get('/{*path}', (_req, res) => {
|
|
58
|
+
res.sendFile(join(DIST_DIR, 'index.html'))
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
|
|
62
|
+
|
|
63
|
+
app.listen(port, () => {
|
|
64
|
+
console.log()
|
|
65
|
+
console.log(` LLMxRay v${pkg.version}`)
|
|
66
|
+
console.log()
|
|
67
|
+
console.log(` App: http://localhost:${port}`)
|
|
68
|
+
console.log(` Ollama: ${ollamaUrl}`)
|
|
69
|
+
console.log()
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Run directly: node server/index.mjs
|
|
74
|
+
if (process.argv[1] && process.argv[1].endsWith('index.mjs')) {
|
|
75
|
+
startServer()
|
|
76
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
|
|
4
|
+
function exec(cmd) {
|
|
5
|
+
try {
|
|
6
|
+
return execSync(cmd, { encoding: 'utf8', timeout: 10_000, stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
7
|
+
} catch {
|
|
8
|
+
return ''
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function psJson(query) {
|
|
13
|
+
const raw = exec(`powershell -NoProfile -Command "${query} | ConvertTo-Json"`)
|
|
14
|
+
if (!raw) return null
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(raw)
|
|
17
|
+
} catch {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ensureArray(val) {
|
|
23
|
+
if (val === null || val === undefined) return []
|
|
24
|
+
return Array.isArray(val) ? val : [val]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getWindowsInfo() {
|
|
28
|
+
const osInfo = psJson('Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber')
|
|
29
|
+
const csInfo = psJson('Get-CimInstance Win32_ComputerSystem | Select-Object Name,Manufacturer,Model,SystemFamily')
|
|
30
|
+
const cpuInfo = psJson('Get-CimInstance Win32_Processor | Select-Object Name,NumberOfCores,NumberOfLogicalProcessors,MaxClockSpeed')
|
|
31
|
+
const gpuInfoRaw = psJson('Get-CimInstance Win32_VideoController | Select-Object Name,AdapterRAM,DriverVersion')
|
|
32
|
+
const diskInfoRaw = psJson('Get-CimInstance Win32_LogicalDisk -Filter \\"DriveType=3\\" | Select-Object DeviceID,Size,FreeSpace')
|
|
33
|
+
|
|
34
|
+
const gpuList = ensureArray(gpuInfoRaw)
|
|
35
|
+
const diskList = ensureArray(diskInfoRaw)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
os: {
|
|
39
|
+
name: osInfo?.Caption?.trim() ?? os.version(),
|
|
40
|
+
version: osInfo?.Version ?? os.release(),
|
|
41
|
+
build: osInfo?.BuildNumber ?? '',
|
|
42
|
+
arch: os.arch(),
|
|
43
|
+
},
|
|
44
|
+
device: {
|
|
45
|
+
name: csInfo?.Name ?? os.hostname(),
|
|
46
|
+
manufacturer: csInfo?.Manufacturer ?? '',
|
|
47
|
+
model: csInfo?.Model ?? '',
|
|
48
|
+
family: csInfo?.SystemFamily ?? '',
|
|
49
|
+
},
|
|
50
|
+
cpu: {
|
|
51
|
+
name: cpuInfo?.Name?.trim() ?? (os.cpus()[0]?.model ?? 'Unknown'),
|
|
52
|
+
cores: cpuInfo?.NumberOfCores ?? os.cpus().length,
|
|
53
|
+
logicalProcessors: cpuInfo?.NumberOfLogicalProcessors ?? os.cpus().length,
|
|
54
|
+
maxClockMhz: cpuInfo?.MaxClockSpeed ?? (os.cpus()[0]?.speed ?? 0),
|
|
55
|
+
},
|
|
56
|
+
memory: {
|
|
57
|
+
totalBytes: os.totalmem(),
|
|
58
|
+
freeBytes: os.freemem(),
|
|
59
|
+
},
|
|
60
|
+
gpu: gpuList.length > 0
|
|
61
|
+
? gpuList.map((g) => ({ name: g.Name ?? 'Unknown', adapterRamBytes: g.AdapterRAM ?? 0, driverVersion: g.DriverVersion ?? '' }))
|
|
62
|
+
: [{ name: 'Not detected', adapterRamBytes: 0, driverVersion: '' }],
|
|
63
|
+
storage: diskList.map((d) => ({ drive: d.DeviceID ?? '', totalBytes: d.Size ?? 0, freeBytes: d.FreeSpace ?? 0 })),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getLinuxInfo() {
|
|
68
|
+
const cpuModel = exec("grep -m1 'model name' /proc/cpuinfo | cut -d: -f2").trim()
|
|
69
|
+
const cpuCores = parseInt(exec("grep -c '^processor' /proc/cpuinfo"), 10) || os.cpus().length
|
|
70
|
+
const gpuLines = exec("lspci | grep -i 'vga\\|3d\\|display'").split('\n').filter(Boolean)
|
|
71
|
+
const gpus = gpuLines.map((line) => ({ name: line.replace(/^[^:]+:\s*/, ''), adapterRamBytes: 0, driverVersion: '' }))
|
|
72
|
+
const dfLines = exec("df -B1 --output=target,size,avail / /home 2>/dev/null").split('\n').slice(1)
|
|
73
|
+
const storage = dfLines.filter(Boolean).map((line) => {
|
|
74
|
+
const parts = line.trim().split(/\s+/)
|
|
75
|
+
return { drive: parts[0] ?? '/', totalBytes: parseInt(parts[1] ?? '0', 10) || 0, freeBytes: parseInt(parts[2] ?? '0', 10) || 0 }
|
|
76
|
+
})
|
|
77
|
+
const prettyName = exec("grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '\"'")
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
os: { name: prettyName || `Linux ${os.release()}`, version: os.release(), build: '', arch: os.arch() },
|
|
81
|
+
device: {
|
|
82
|
+
name: os.hostname(),
|
|
83
|
+
manufacturer: exec('cat /sys/class/dmi/id/sys_vendor 2>/dev/null'),
|
|
84
|
+
model: exec('cat /sys/class/dmi/id/product_name 2>/dev/null'),
|
|
85
|
+
family: exec('cat /sys/class/dmi/id/product_family 2>/dev/null'),
|
|
86
|
+
},
|
|
87
|
+
cpu: { name: cpuModel || (os.cpus()[0]?.model ?? 'Unknown'), cores: cpuCores, logicalProcessors: os.cpus().length, maxClockMhz: os.cpus()[0]?.speed ?? 0 },
|
|
88
|
+
memory: { totalBytes: os.totalmem(), freeBytes: os.freemem() },
|
|
89
|
+
gpu: gpus.length > 0 ? gpus : [{ name: 'Not detected', adapterRamBytes: 0, driverVersion: '' }],
|
|
90
|
+
storage,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getMacInfo() {
|
|
95
|
+
const cpuBrand = exec('sysctl -n machdep.cpu.brand_string')
|
|
96
|
+
const cpuCores = parseInt(exec('sysctl -n hw.physicalcpu'), 10) || os.cpus().length
|
|
97
|
+
const logicalCores = parseInt(exec('sysctl -n hw.logicalcpu'), 10) || os.cpus().length
|
|
98
|
+
const gpuName = exec("system_profiler SPDisplaysDataType 2>/dev/null | grep 'Chipset Model' | head -1 | cut -d: -f2").trim()
|
|
99
|
+
const gpuVram = exec("system_profiler SPDisplaysDataType 2>/dev/null | grep 'VRAM' | head -1 | cut -d: -f2").trim()
|
|
100
|
+
const dfLines = exec("df -b / | tail -1").split(/\s+/)
|
|
101
|
+
const totalBlocks = parseInt(dfLines[1] ?? '0', 10) || 0
|
|
102
|
+
const availBlocks = parseInt(dfLines[3] ?? '0', 10) || 0
|
|
103
|
+
const swVers = exec('sw_vers -productVersion')
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
os: { name: `macOS ${swVers}`, version: swVers, build: exec('sw_vers -buildVersion'), arch: os.arch() },
|
|
107
|
+
device: { name: os.hostname(), manufacturer: 'Apple', model: exec('sysctl -n hw.model'), family: '' },
|
|
108
|
+
cpu: { name: cpuBrand || (os.cpus()[0]?.model ?? 'Unknown'), cores: cpuCores, logicalProcessors: logicalCores, maxClockMhz: os.cpus()[0]?.speed ?? 0 },
|
|
109
|
+
memory: { totalBytes: os.totalmem(), freeBytes: os.freemem() },
|
|
110
|
+
gpu: [{ name: gpuName || 'Integrated', adapterRamBytes: gpuVram ? parseInt(gpuVram, 10) * 1024 * 1024 : 0, driverVersion: '' }],
|
|
111
|
+
storage: [{ drive: '/', totalBytes: totalBlocks * 512, freeBytes: availBlocks * 512 }],
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function collectSystemInfo() {
|
|
116
|
+
const platform = os.platform()
|
|
117
|
+
if (platform === 'win32') return getWindowsInfo()
|
|
118
|
+
if (platform === 'darwin') return getMacInfo()
|
|
119
|
+
return getLinuxInfo()
|
|
120
|
+
}
|