UNPKG

@rcb-plugins/llm-connector

Version:

A generic LLM connector for integrating Large Language Models (LLMs) in React ChatBotify!

3 lines (2 loc) 12.7 kB
"use strict";var $=Object.create;var j=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,K=Object.prototype.hasOwnProperty;var N=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of L(e))!K.call(n,o)&&o!==t&&j(n,o,{get:()=>e[o],enumerable:!(s=G(e,o))||s.enumerable});return n};var J=(n,e,t)=>(t=n!=null?$(z(n)):{},N(e||!n||!n.__esModule?j(t,"default",{value:n,enumerable:!0}):t,n));Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const g=require("react"),h=require("react-chatbotify"),q={autoConfig:!0},H=(n,e)=>{const t=g.useCallback(s=>{const r=n()[s.data.nextPath];e(r)},[n,e]);h.useOnRcbEvent(h.RcbEvent.CHANGE_PATH,t)},Y=(n,e)=>{const{outputTypeRef:t}=n,{toggleTextAreaDisabled:s,toggleIsBotTyping:o,focusTextArea:r,injectMessage:a,simulateStreamMessage:i,getIsChatBotVisible:c}=e,l=g.useCallback(d=>{var m;const p=d.data.block;p.llmConnector&&(d.preventDefault(),d.type==="rcb-pre-process-block"&&((m=p.llmConnector)!=null&&m.initialMessage&&(t.current==="full"?a(n.initialMessageRef.current):i(n.initialMessageRef.current)),o(!1),s(!1),setTimeout(()=>{c()&&r()})))},[o,s,r,c]);h.useOnRcbEvent(h.RcbEvent.PRE_PROCESS_BLOCK,l),h.useOnRcbEvent(h.RcbEvent.POST_PROCESS_BLOCK,l)},V=async function*(n,e){for await(const t of n)for(const s of t)yield s,await new Promise(o=>setTimeout(o,e))},Q=async function*(n,e){for await(const t of n)yield t,await new Promise(s=>setTimeout(s,e))},X=async function*(n,e,t){e==="character"?yield*V(n,t):yield*Q(n,t)},Z=async function*(n,e){for await(const t of n)e(t),yield t},ee=async(n,e,t,s={})=>{var R,M;if(!e.providerRef.current)return;const{speakAudio:o,toggleIsBotTyping:r,toggleTextAreaDisabled:a,focusTextArea:i,injectMessage:c,streamMessage:l,endStreamMessage:d,getIsChatBotVisible:p}=t,m=e.providerRef.current.sendMessages(n),b=e.outputTypeRef.current,y=e.outputSpeedRef.current;if(b==="full"){let u="";for await(const f of m){if((R=s.signal)!=null&&R.aborted)break;u+=f}r(!1),c(u),setTimeout(()=>{a(!1),p()&&i()})}else{const u=X(Z(m,o),b,y);let f="",S=!1;for await(const E of u){if((M=s.signal)!=null&&M.aborted)break;S||(r(!1),S=!0),f+=E,l(f)}d(),setTimeout(()=>{a(!1),p()&&i()})}},te=500,se=(n,e)=>{const{messagesRef:t,outputTypeRef:s,onUserMessageRef:o,onKeyDownRef:r,errorMessageRef:a}=n,{injectMessage:i,simulateStreamMessage:c,toggleTextAreaDisabled:l,toggleIsBotTyping:d,goToPath:p,focusTextArea:m,getIsChatBotVisible:b}=e,y=g.useRef(null),R=g.useCallback(M=>{if(!n.providerRef.current)return;const u=M.data.message,f=u.sender.toUpperCase();u.tags=u.tags??[],u.tags.push(`rcb-llm-connector-plugin:${f}`),f==="USER"&&(d(!0),l(!0),setTimeout(async()=>{var T;if(o.current){const C=await o.current(u);if(C)return(T=y.current)==null||T.abort(),y.current=null,p(C)}const S=n.historySizeRef.current,E=t.current,v=S?[...E.slice(-(S-1)),u]:[u],P=new AbortController;y.current=P,ee(v,n,e,{signal:P.signal}).catch(C=>{d(!1),l(!1),setTimeout(()=>{b()&&m()}),console.error("LLM prompt failed",C),s.current==="full"?i(a.current):c(a.current)})},te))},[n,e]);h.useOnRcbEvent(h.RcbEvent.POST_INJECT_MESSAGE,R),h.useOnRcbEvent(h.RcbEvent.STOP_SIMULATE_STREAM_MESSAGE,R),h.useOnRcbEvent(h.RcbEvent.STOP_STREAM_MESSAGE,R),g.useEffect(()=>{const M=async u=>{var f;if(r.current){const S=await r.current(u);S&&((f=y.current)==null||f.abort(),y.current=null,p(S))}};return window.addEventListener("keydown",M),()=>window.removeEventListener("keydown",M)},[])},oe=n=>{const e=g.useRef([]),t=g.useRef(null),s=g.useRef("chunk"),o=g.useRef(30),r=g.useRef(0),a=g.useRef(""),i=g.useRef("Unable to get response, please try again."),c=g.useRef(null),l=g.useRef(null),{getFlow:d}=h.useFlow(),{speakAudio:p}=h.useAudio(),{messages:m,injectMessage:b,simulateStreamMessage:y,streamMessage:R,endStreamMessage:M}=h.useMessages(),{goToPath:u}=h.usePaths(),{toggleTextAreaDisabled:f,focusTextArea:S}=h.useTextArea(),{toggleIsBotTyping:E,getIsChatBotVisible:v}=h.useChatWindow(),P={...q,...n??{}};g.useEffect(()=>{e.current=m},[m]),H(d,w=>{var x,A,k,B,U,F,I,D,W,_;t.current=((x=w.llmConnector)==null?void 0:x.provider)??null,s.current=((A=w.llmConnector)==null?void 0:A.outputType)??"chunk",o.current=((k=w.llmConnector)==null?void 0:k.outputSpeed)??30,r.current=((B=w.llmConnector)==null?void 0:B.historySize)??0,a.current=((U=w.llmConnector)==null?void 0:U.initialMessage)??"",i.current=((F=w.llmConnector)==null?void 0:F.errorMessage)??"Unable to get response, please try again.",c.current=((D=(I=w.llmConnector)==null?void 0:I.stopConditions)==null?void 0:D.onUserMessage)??null,l.current=((_=(W=w.llmConnector)==null?void 0:W.stopConditions)==null?void 0:_.onKeyDown)??null});const T={providerRef:t,messagesRef:e,outputTypeRef:s,outputSpeedRef:o,historySizeRef:r,initialMessageRef:a,errorMessageRef:i,onUserMessageRef:c,onKeyDownRef:l},C={speakAudio:p,injectMessage:b,simulateStreamMessage:y,streamMessage:R,endStreamMessage:M,toggleTextAreaDisabled:f,toggleIsBotTyping:E,focusTextArea:S,goToPath:u,getIsChatBotVisible:v};Y(T,C),se(T,C);const O={name:"@rcb-plugins/llm-connector"};return P!=null&&P.autoConfig&&(O.settings={event:{rcbChangePath:!0,rcbPostInjectMessage:!0,rcbStopSimulateStreamMessage:!0,rcbStopStreamMessage:!0,rcbPreProcessBlock:!0,rcbPostProcessBlock:!0}}),O},re=n=>()=>oe(n);class ne{constructor(e){this.debug=!1,this.roleMap=s=>{switch(s){case"USER":return"user";default:return"model"}},this.constructBodyWithMessages=s=>{let o;return this.messageParser?o=this.messageParser(s):o=s.filter(a=>typeof a.content=="string"&&a.sender.toUpperCase()!=="SYSTEM").map(a=>{const i=this.roleMap(a.sender.toUpperCase()),c=a.content;return{role:i,parts:[{text:c}]}}),this.systemMessage&&(o=[{role:"user",parts:[{text:this.systemMessage}]},...o]),{contents:o,...this.body}},this.handleStreamResponse=async function*(s){var a,i,c,l,d;const o=new TextDecoder("utf-8");let r="";for(;;){const{value:p,done:m}=await s.read();if(m)break;r+=o.decode(p,{stream:!0});const b=r.split(` `);r=b.pop();for(const y of b){const R=y.trim();if(!R.startsWith("data: "))continue;const M=R.slice(6);try{const f=(d=(l=(c=(i=(a=JSON.parse(M).candidates)==null?void 0:a[0])==null?void 0:i.content)==null?void 0:c.parts)==null?void 0:l[0])==null?void 0:d.text;f&&(yield f)}catch(u){console.error("SSE JSON parse error:",M,u)}}}},this.method=e.method??"POST",this.body=e.body??{},this.systemMessage=e.systemMessage,this.responseFormat=e.responseFormat??"stream",this.messageParser=e.messageParser,this.debug=e.debug??!1,this.headers={"Content-Type":"application/json",Accept:this.responseFormat==="stream"?"text/event-stream":"application/json",...e.headers};const t=e.baseUrl??"https://generativelanguage.googleapis.com/v1beta";if(e.mode==="direct")this.endpoint=this.responseFormat==="stream"?`${t}/models/${e.model}:streamGenerateContent?alt=sse&key=${e.apiKey||""}`:`${t}/models/${e.model}:generateContent?key=${e.apiKey||""}`;else if(e.mode==="proxy")this.endpoint=`${t}/${e.model}`;else throw Error("Invalid mode specified for Gemini provider ('direct' or 'proxy').")}async*sendMessages(e){var s,o,r,a,i;if(this.debug){const c=this.endpoint.replace(/\?key=([^&]+)/,"?key=[REDACTED]"),l={...this.headers};console.log("[GeminiProvider] Request:",{method:this.method,endpoint:c,headers:l,body:this.constructBodyWithMessages(e)})}const t=await fetch(this.endpoint,{method:this.method,headers:this.headers,body:JSON.stringify(this.constructBodyWithMessages(e))});if(this.debug&&console.log("[GeminiProvider] Response status:",t.status),!t.ok)throw new Error(`Gemini API error ${t.status}: ${await t.text()}`);if(this.responseFormat==="stream"){if(!t.body)throw new Error("Response body is empty – cannot stream");const c=t.body.getReader();for await(const l of this.handleStreamResponse(c))yield l}else{const c=await t.json();this.debug&&console.log("[GeminiProvider] Response body:",c);const l=(i=(a=(r=(o=(s=c.candidates)==null?void 0:s[0])==null?void 0:o.content)==null?void 0:r.parts)==null?void 0:a[0])==null?void 0:i.text;if(typeof l=="string")yield l;else throw new Error("Unexpected response shape – no text candidate")}}}class ae{constructor(e){if(this.debug=!1,this.roleMap=t=>{switch(t){case"USER":return"user";case"SYSTEM":return"system";default:return"assistant"}},this.constructBodyWithMessages=t=>{let s;return this.messageParser?s=this.messageParser(t):s=t.filter(r=>typeof r.content=="string"&&r.sender.toUpperCase()!=="SYSTEM").map(r=>{const a=this.roleMap(r.sender.toUpperCase()),i=r.content;return{role:a,content:i}}),this.systemMessage&&(s=[{role:"system",content:this.systemMessage},...s]),{messages:s,...this.body}},this.handleStreamResponse=async function*(t){var r,a,i;const s=new TextDecoder("utf-8");let o="";for(;;){const{value:c,done:l}=await t.read();if(l)break;o+=s.decode(c,{stream:!0});const d=o.split(/\r?\n/);o=d.pop();for(const p of d){if(!p.startsWith("data: "))continue;const m=p.slice(6).trim();if(m==="[DONE]")return;try{const y=(i=(a=(r=JSON.parse(m).choices)==null?void 0:r[0])==null?void 0:a.delta)==null?void 0:i.content;y&&(yield y)}catch(b){console.error("Stream parse error",b)}}}},this.method=e.method??"POST",this.endpoint=e.baseUrl??"https://api.openai.com/v1/chat/completions",this.systemMessage=e.systemMessage,this.responseFormat=e.responseFormat??"stream",this.messageParser=e.messageParser,this.debug=e.debug??!1,this.headers={"Content-Type":"application/json",Accept:this.responseFormat==="stream"?"text/event-stream":"application/json",...e.headers},this.body={model:e.model,stream:this.responseFormat==="stream",...e.body},e.mode==="direct"){this.headers={...this.headers,Authorization:`Bearer ${e.apiKey}`};return}if(e.mode!=="proxy")throw Error("Invalid mode specified for OpenAI provider ('direct' or 'proxy').")}async*sendMessages(e){var s,o,r;if(this.debug){const a={...this.headers};delete a.Authorization,console.log("[OpenaiProvider] Request:",{method:this.method,endpoint:this.endpoint,headers:a,body:this.constructBodyWithMessages(e)})}const t=await fetch(this.endpoint,{method:this.method,headers:this.headers,body:JSON.stringify(this.constructBodyWithMessages(e))});if(this.debug&&console.log("[OpenaiProvider] Response status:",t.status),!t.ok)throw new Error(`Openai API error ${t.status}: ${await t.text()}`);if(this.responseFormat==="stream"){if(!t.body)throw new Error("Response body is empty – cannot stream");const a=t.body.getReader();for await(const i of this.handleStreamResponse(a))yield i}else{const a=await t.json();this.debug&&console.log("[OpenaiProvider] Response body:",a);const i=(r=(o=(s=a.choices)==null?void 0:s[0])==null?void 0:o.message)==null?void 0:r.content;if(typeof i=="string")yield i;else throw new Error("Unexpected response shape – no text candidate")}}}class ie{constructor(e){this.debug=!1,this.roleMap=t=>{switch(t){case"USER":return"user";case"SYSTEM":return"system";default:return"assistant"}},this.constructBodyWithMessages=t=>{let s;return this.messageParser?s=this.messageParser(t):s=t.filter(r=>typeof r.content=="string"&&r.sender.toUpperCase()!=="SYSTEM").map(r=>{const a=this.roleMap(r.sender.toUpperCase()),i=r.content;return{role:a,content:i}}),this.systemMessage&&(s=[{role:"system",content:this.systemMessage},...s]),{messages:s,stream:this.responseFormat==="stream",...this.chatCompletionOptions}},this.model=e.model,this.systemMessage=e.systemMessage,this.responseFormat=e.responseFormat??"stream",this.messageParser=e.messageParser,this.engineConfig=e.engineConfig??{},this.chatCompletionOptions=e.chatCompletionOptions??{},this.debug=e.debug??!1,this.createEngine()}async createEngine(){const{CreateMLCEngine:e}=await import("@mlc-ai/web-llm");this.engine=await e(this.model,{...this.engineConfig})}async*sendMessages(e){var s,o,r,a,i,c;this.engine||await this.createEngine(),this.debug&&console.log("[WebLlmProvider] Request:",{model:this.model,systemMessage:this.systemMessage,responseFormat:this.responseFormat,engineConfig:this.engineConfig,chatCompletionOptions:this.chatCompletionOptions,messages:this.constructBodyWithMessages(e).messages});const t=await((s=this.engine)==null?void 0:s.chat.completions.create(this.constructBodyWithMessages(e)));if(this.debug&&console.log("[WebLlmProvider] Response:",t),t&&Symbol.asyncIterator in t)for await(const l of t){const d=(r=(o=l.choices[0])==null?void 0:o.delta)==null?void 0:r.content;d&&(yield d)}else(c=(i=(a=t==null?void 0:t.choices)==null?void 0:a[0])==null?void 0:i.message)!=null&&c.content&&(yield t.choices[0].message.content)}}exports.GeminiProvider=ne;exports.OpenaiProvider=ae;exports.WebLlmProvider=ie;exports.default=re;