feat: localchat, a Go + Templ + HTMX chat app for local Gemma models
Streams replies over SSE from any OpenAI-compatible server (oMLX, llama.cpp, Ollama, ...). Single binary that can install itself as an OS service; docker compose bundles llama.cpp + Gemma 4 E2B.
This commit is contained in:
28 files changed
+2459
No files matched your search
Vendored
+1
@@ -0,0 +1 @@
|
||||
var htmx=function(){"use strict";const Y={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=pn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true,historyRestoreAsHxRequest:true,reportValidityOfForms:false},parseInterval:null,location:location,_:null,version:"2.0.11"};Y.onLoad=V;Y.process=Bt;Y.on=ye;Y.off=xe;Y.trigger=ae;Y.ajax=In;Y.find=a;Y.findAll=x;Y.closest=g;Y.remove=z;Y.addClass=w;Y.removeClass=S;Y.toggleClass=G;Y.takeClass=W;Y.swap=C;Y.defineExtension=zn;Y.removeExtension=Jn;Y.logAll=$;Y.logNone=_;Y.parseInterval=d;Y._=e;const n={addTriggerHandler:Et,bodyContains:ie,canAccessLocalStorage:U,findThisElement:we,filterValues:xn,swap:C,hasAttribute:s,getAttributeValue:f,getClosestAttributeValue:te,getClosestMatch:A,getExpressionVars:qn,getHeaders:yn,getInputValues:pn,getInternalData:re,getSwapSpecification:vn,getTriggerSpecs:lt,getTarget:Se,makeFragment:D,mergeObjects:se,makeSettleInfo:En,oobSwap:Te,querySelectorExt:ce,settleImmediately:Qt,shouldCancel:dt,triggerEvent:ae,triggerErrorEvent:ue,withExtensions:Vt};const he=["get","post","put","delete","patch"];const R=he.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function Q(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function f(e,t){return Q(e,t)||Q(e,"data-"+t)}function u(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function ee(){return document}function q(e,t){return e.getRootNode?e.getRootNode({composed:t}):ee()}function A(e,t){while(e&&!t(e)){e=u(e)}return e||null}function o(e,t,n){const r=f(t,n);const o=f(t,"hx-disinherit");var i=f(t,"hx-inherit");if(e!==t){if(Y.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function te(t,n){let r=null;A(t,function(e){return!!(r=o(t,le(e),n))});if(r!=="unset"){return r}}function h(e,t){return e instanceof Element&&e.matches(t)}function N(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function I(e){if("parseHTMLUnsafe"in Document){return Document.parseHTMLUnsafe(e)}const t=new DOMParser;return t.parseFromString(e,"text/html")}function L(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function r(e){const t=ee().createElement("script");oe(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Y.config.inlineScriptNonce){t.nonce=Y.config.inlineScriptNonce}return t}function i(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function k(e){if(!Y.config.allowScriptTags){e.querySelectorAll("script").forEach(e=>e.remove());return}Array.from(e.querySelectorAll("script")).forEach(e=>{if(i(e)){const t=r(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){T(e)}finally{e.remove()}}})}function D(e){e=e.replace(/<hx-([a-z]+)((?:\s[^>]*)?)>/gi,'<template hx type="$1"$2>').replace(/<\/hx-[a-z]+>/gi,"</template>");const t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,"");const n=N(t);let r;if(n==="html"){r=new DocumentFragment;const i=I(e);L(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=I(t);L(r,i.body);r.title=i.title}else{const i=I('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){oLine truncated
|
||||
Reference in new issue
Block a user