Files
llama-link/internal/web/dist/assets/Models-DBqDIbsA.js
T
darroyo 0d6c8c7989
CI / test (push) Failing after 13m11s
Add embedded Vue SPA admin panel with go:embed
This is the complete fix for the missing /admin/ route and frontend serving:

Backend:
- internal/web/web.go: new package with go:embed for web/dist/
- internal/api/router.go: add routes for /admin/, /admin/*, /assets/*
- internal/db/db.go: fix SQLite DSN parsing (sqlite:///path -> path)

Build system:
- Makefile: new 'embed-prep' target copies web/dist to internal/web/dist
- make build now runs embed-prep -> frontend/build automatically

Deployment:
- deploy/llamalink.service: remove invalid --host/--port flags,
  add EnvironmentFile=/etc/llamalink/env

Verified:
- /health returns 200
- /admin/ serves Vue SPA HTML
- /assets/* serves CSS and JS files from embedded FS
- sqlite:///./llamalink.db works correctly
2026-07-31 14:47:15 -04:00

7 lines
5.1 KiB
JavaScript

import{d as w,q as h,c as a,a as t,b as y,e as x,x as g,g as v,F as M,i as C,w as U,f as d,v as i,y as V,r as m,s as f,o as n,t as u}from"./index-r2SG-Kf3.js";import{P as L}from"./plus-Bfa9PGWP.js";import{c as N}from"./createLucideIcon-CUrbWv4G.js";/**
* @license lucide-vue-next v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const z=N("UploadIcon",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"17 8 12 3 7 8",key:"t8dd8p"}],["line",{x1:"12",x2:"12",y1:"3",y2:"15",key:"widbto"}]]),A={class:"flex items-center justify-between mb-6"},P={key:0,class:"text-text-muted"},S={class:"card"},q={class:"table"},D={class:"font-mono"},F={class:"font-mono text-text-muted text-sm"},j={class:"font-mono"},$={key:0,class:"badge badge-info"},B={key:1,class:"badge"},I={key:0,class:"badge badge-success"},G={key:1,class:"badge"},T=["onClick"],E={key:1,class:"badge badge-success"},H={key:0},O={key:1,class:"fixed inset-0 bg-black/50 flex items-center justify-center z-50"},J={class:"card w-full max-w-lg"},K={class:"grid grid-cols-2 gap-4"},Q={class:"flex items-center gap-2"},R={class:"flex gap-3 justify-end pt-2"},et=w({__name:"Models",setup(W){const p=m([]),c=m(!0),r=m(!1),s=m({name:"",model_path:"",alias:"",ctx_size:8192,n_gpu_layers:-1,is_default:!1});async function b(){c.value=!0;try{const o=await f.get("/api/v1/models");p.value=o.data.data}catch(o){console.error("Failed to fetch models:",o)}finally{c.value=!1}}async function _(){try{await f.post("/api/v1/models",s.value),r.value=!1,Object.assign(s.value,{name:"",model_path:"",alias:"",ctx_size:8192,n_gpu_layers:-1,is_default:!1}),await b()}catch(o){console.error("Failed to create model:",o)}}async function k(o){try{await f.post(`/api/v1/models/${o}/load`),await b()}catch(e){console.error("Failed to load model:",e)}}return h(b),(o,e)=>(n(),a("div",null,[t("div",A,[e[9]||(e[9]=t("h1",{class:"text-2xl font-bold"},"Models",-1)),t("button",{onClick:e[0]||(e[0]=l=>r.value=!0),class:"btn btn-primary"},[y(x(L),{class:"w-4 h-4 mr-2"}),e[8]||(e[8]=g(" Add Model ",-1))])]),c.value?(n(),a("div",P,"Loading...")):v("",!0),t("div",S,[t("table",q,[e[12]||(e[12]=t("thead",null,[t("tr",null,[t("th",null,"Name"),t("th",null,"Path"),t("th",null,"Alias"),t("th",null,"Context"),t("th",null,"GPU Layers"),t("th",null,"Default"),t("th",null,"Status"),t("th",null,"Actions")])],-1)),t("tbody",null,[(n(!0),a(M,null,C(p.value,l=>(n(),a("tr",{key:l.id},[t("td",D,u(l.name),1),t("td",F,u(l.model_path),1),t("td",j,u(l.alias),1),t("td",null,u(l.ctx_size.toLocaleString()),1),t("td",null,u(l.n_gpu_layers),1),t("td",null,[l.is_default?(n(),a("span",$,"Default")):(n(),a("span",B,"No"))]),t("td",null,[l.is_active?(n(),a("span",I,"Active")):(n(),a("span",G,"Inactive"))]),t("td",null,[l.is_active?(n(),a("span",E,"Loaded")):(n(),a("button",{key:0,onClick:X=>k(l.name),class:"btn btn-primary btn-sm"},[y(x(z),{class:"w-4 h-4 mr-1"}),e[10]||(e[10]=g(" Load ",-1))],8,T))])]))),128)),p.value.length===0?(n(),a("tr",H,[...e[11]||(e[11]=[t("td",{colspan:"8",class:"text-center text-text-muted py-8"}," No models configured. Add one to get started. ",-1)])])):v("",!0)])])]),r.value?(n(),a("div",O,[t("div",J,[e[20]||(e[20]=t("h2",{class:"text-lg font-semibold mb-4"},"Add Model",-1)),t("form",{onSubmit:U(_,["prevent"]),class:"space-y-4"},[t("div",null,[e[13]||(e[13]=t("label",{class:"block text-sm font-medium mb-2"},"Name",-1)),d(t("input",{"onUpdate:modelValue":e[1]||(e[1]=l=>s.value.name=l),type:"text",class:"input",placeholder:"llama-3.2-1b",required:""},null,512),[[i,s.value.name]])]),t("div",null,[e[14]||(e[14]=t("label",{class:"block text-sm font-medium mb-2"},"Model Path",-1)),d(t("input",{"onUpdate:modelValue":e[2]||(e[2]=l=>s.value.model_path=l),type:"text",class:"input",placeholder:"/models/llama-3.2-1b.q4_k_m.gguf",required:""},null,512),[[i,s.value.model_path]])]),t("div",null,[e[15]||(e[15]=t("label",{class:"block text-sm font-medium mb-2"},"Alias",-1)),d(t("input",{"onUpdate:modelValue":e[3]||(e[3]=l=>s.value.alias=l),type:"text",class:"input",placeholder:"llama-3.2-1b",required:""},null,512),[[i,s.value.alias]])]),t("div",K,[t("div",null,[e[16]||(e[16]=t("label",{class:"block text-sm font-medium mb-2"},"Context Size",-1)),d(t("input",{"onUpdate:modelValue":e[4]||(e[4]=l=>s.value.ctx_size=l),type:"number",class:"input"},null,512),[[i,s.value.ctx_size,void 0,{number:!0}]])]),t("div",null,[e[17]||(e[17]=t("label",{class:"block text-sm font-medium mb-2"},"GPU Layers",-1)),d(t("input",{"onUpdate:modelValue":e[5]||(e[5]=l=>s.value.n_gpu_layers=l),type:"number",class:"input"},null,512),[[i,s.value.n_gpu_layers,void 0,{number:!0}]])])]),t("div",Q,[d(t("input",{"onUpdate:modelValue":e[6]||(e[6]=l=>s.value.is_default=l),type:"checkbox",id:"is_default",class:"w-4 h-4 rounded"},null,512),[[V,s.value.is_default]]),e[18]||(e[18]=t("label",{for:"is_default",class:"text-sm"},"Set as default model",-1))]),t("div",R,[t("button",{type:"button",onClick:e[7]||(e[7]=l=>r.value=!1),class:"btn btn-secondary"}," Cancel "),e[19]||(e[19]=t("button",{type:"submit",class:"btn btn-primary"},"Create",-1))])],32)])])):v("",!0)]))}});export{et as default};