umans-gate 0.1.1 → 0.1.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +165 -45
  3. package/npm-shim.cjs +71 -0
  4. package/package.json +16 -80
  5. package/dashboard/dist/assets/alert-dialog-DlhAyxD3.js +0 -2
  6. package/dashboard/dist/assets/alert-dialog-DlhAyxD3.js.map +0 -1
  7. package/dashboard/dist/assets/clear-confirm-dialog-BOcl93wD.js +0 -2
  8. package/dashboard/dist/assets/clear-confirm-dialog-BOcl93wD.js.map +0 -1
  9. package/dashboard/dist/assets/config-tab-pnBjiSyC.js +0 -4
  10. package/dashboard/dist/assets/config-tab-pnBjiSyC.js.map +0 -1
  11. package/dashboard/dist/assets/economics-tab-BJXa5ydD.js +0 -2
  12. package/dashboard/dist/assets/economics-tab-BJXa5ydD.js.map +0 -1
  13. package/dashboard/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  14. package/dashboard/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  15. package/dashboard/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  16. package/dashboard/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  17. package/dashboard/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  18. package/dashboard/dist/assets/headers-viewer-C9FKdG-_.js +0 -2
  19. package/dashboard/dist/assets/headers-viewer-C9FKdG-_.js.map +0 -1
  20. package/dashboard/dist/assets/index-72wCsW4_.js +0 -3
  21. package/dashboard/dist/assets/index-72wCsW4_.js.map +0 -1
  22. package/dashboard/dist/assets/index-DFhG4t-C.css +0 -1
  23. package/dashboard/dist/assets/json-viewer-CXkZMoVn.js +0 -2
  24. package/dashboard/dist/assets/json-viewer-CXkZMoVn.js.map +0 -1
  25. package/dashboard/dist/assets/mode-toggle-BxkGFiYG.js +0 -2
  26. package/dashboard/dist/assets/mode-toggle-BxkGFiYG.js.map +0 -1
  27. package/dashboard/dist/assets/models-tab-Deqk3WT3.js +0 -2
  28. package/dashboard/dist/assets/models-tab-Deqk3WT3.js.map +0 -1
  29. package/dashboard/dist/assets/performance-meter-Bd152C4C.js +0 -2
  30. package/dashboard/dist/assets/performance-meter-Bd152C4C.js.map +0 -1
  31. package/dashboard/dist/assets/select-mZPf2BR3.js +0 -2
  32. package/dashboard/dist/assets/select-mZPf2BR3.js.map +0 -1
  33. package/dashboard/dist/assets/sheet-BgQXtbdw.js +0 -2
  34. package/dashboard/dist/assets/sheet-BgQXtbdw.js.map +0 -1
  35. package/dashboard/dist/assets/sonner-DGu1DGeO.js +0 -2
  36. package/dashboard/dist/assets/sonner-DGu1DGeO.js.map +0 -1
  37. package/dashboard/dist/assets/sse-viewer-BHnn3FcY.js +0 -3
  38. package/dashboard/dist/assets/sse-viewer-BHnn3FcY.js.map +0 -1
  39. package/dashboard/dist/assets/use-capture-done-listener-u5RxfYQP.js +0 -2
  40. package/dashboard/dist/assets/use-capture-done-listener-u5RxfYQP.js.map +0 -1
  41. package/dashboard/dist/assets/use-models-B4MLkfpt.js +0 -2
  42. package/dashboard/dist/assets/use-models-B4MLkfpt.js.map +0 -1
  43. package/dashboard/dist/assets/use-polling-resource-D_vWHFwW.js +0 -2
  44. package/dashboard/dist/assets/use-polling-resource-D_vWHFwW.js.map +0 -1
  45. package/dashboard/dist/assets/vendor-CVKsgXJU.js +0 -312
  46. package/dashboard/dist/assets/vendor-CVKsgXJU.js.map +0 -1
  47. package/dashboard/dist/assets/vendor-DtX4s2-2.css +0 -1
  48. package/dashboard/dist/assets/vendor-react-Dtud3GyP.js +0 -41
  49. package/dashboard/dist/assets/vendor-react-Dtud3GyP.js.map +0 -1
  50. package/dashboard/dist/assets/vision-calls-BquNiDzm.js +0 -3
  51. package/dashboard/dist/assets/vision-calls-BquNiDzm.js.map +0 -1
  52. package/dashboard/dist/index.html +0 -34
  53. package/dist/cli.d.ts +0 -1
  54. package/dist/cli.js +0 -6788
  55. package/dist/cli.js.map +0 -1
  56. package/dist/index.d.ts +0 -1094
  57. package/dist/index.js +0 -6443
  58. package/dist/index.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"alert-dialog-DlhAyxD3.js","sources":["../../src/components/ui/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertDialog as AlertDialogPrimitive } from \"@base-ui/react/alert-dialog\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nfunction AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {\n return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n}\n\nfunction AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {\n return <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />;\n}\n\nfunction AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {\n return <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />;\n}\n\nfunction AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {\n return (\n <AlertDialogPrimitive.Backdrop\n data-slot=\"alert-dialog-overlay\"\n className={cn(\n \"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDialogContent({\n className,\n size = \"default\",\n ...props\n}: AlertDialogPrimitive.Popup.Props & {\n size?: \"default\" | \"sm\";\n}) {\n return (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Popup\n data-slot=\"alert-dialog-content\"\n data-size={size}\n className={cn(\n \"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n className,\n )}\n {...props}\n />\n </AlertDialogPortal>\n );\n}\n\nfunction AlertDialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(\n \"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(\n \"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDialogMedia({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-media\"\n className={cn(\n \"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDialogTitle({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n return (\n <AlertDialogPrimitive.Title\n data-slot=\"alert-dialog-title\"\n className={cn(\n \"font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n return (\n <AlertDialogPrimitive.Description\n data-slot=\"alert-dialog-description\"\n className={cn(\n \"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground\",\n className,\n )}\n {...props}\n />\n );\n}\n\nconst AlertDialogAction = React.forwardRef<\n HTMLButtonElement,\n Omit<AlertDialogPrimitive.Close.Props, \"render\"> &\n Pick<React.ComponentProps<typeof Button>, \"variant\" | \"size\">\n>(function AlertDialogAction({ className, variant = \"default\", size = \"default\", ...props }, ref) {\n return (\n <AlertDialogPrimitive.Close\n ref={ref}\n data-slot=\"alert-dialog-action\"\n className={cn(className)}\n render={<Button variant={variant} size={size} />}\n {...props}\n />\n );\n});\n\nconst AlertDialogCancel = React.forwardRef<\n HTMLButtonElement,\n AlertDialogPrimitive.Close.Props & Pick<React.ComponentProps<typeof Button>, \"variant\" | \"size\">\n>(function AlertDialogCancel({ className, variant = \"outline\", size = \"default\", ...props }, ref) {\n return (\n <AlertDialogPrimitive.Close\n ref={ref}\n data-slot=\"alert-dialog-cancel\"\n className={cn(className)}\n render={<Button variant={variant} size={size} />}\n {...props}\n />\n );\n});\n\nexport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogMedia,\n AlertDialogOverlay,\n AlertDialogPortal,\n AlertDialogTitle,\n AlertDialogTrigger,\n};\n"],"names":["AlertDialog","props","AlertDialogPrimitive.Root","AlertDialogTrigger","AlertDialogPrimitive.Trigger","AlertDialogPortal","AlertDialogPrimitive.Portal","AlertDialogOverlay","className","jsx","AlertDialogPrimitive.Backdrop","cn","AlertDialogContent","size","AlertDialogPrimitive.Popup","AlertDialogHeader","AlertDialogFooter","AlertDialogTitle","AlertDialogPrimitive.Title","AlertDialogDescription","AlertDialogPrimitive.Description","AlertDialogAction","React.forwardRef","variant","ref","AlertDialogPrimitive.Close","Button","AlertDialogCancel"],"mappings":"uMAQA,SAASA,EAAY,CAAE,GAAGC,GAA0C,CAClE,aAAQC,EAAA,CAA0B,YAAU,eAAgB,GAAGD,EAAO,CACxE,CAEA,SAASE,EAAmB,CAAE,GAAGF,GAA6C,CAC5E,aAAQG,EAAA,CAA6B,YAAU,uBAAwB,GAAGH,EAAO,CACnF,CAEA,SAASI,EAAkB,CAAE,GAAGJ,GAA4C,CAC1E,aAAQK,EAAA,CAA4B,YAAU,sBAAuB,GAAGL,EAAO,CACjF,CAEA,SAASM,EAAmB,CAAE,UAAAC,EAAW,GAAGP,GAA8C,CACxF,OACEQ,EAAAA,IAACC,EAAA,CACC,YAAU,uBACV,UAAWC,EACT,wLACAH,CAAA,EAED,GAAGP,CAAA,CAAA,CAGV,CAEA,SAASW,EAAmB,CAC1B,UAAAJ,EACA,KAAAK,EAAO,UACP,GAAGZ,CACL,EAEG,CACD,cACGI,EAAA,CACC,SAAA,CAAAI,EAAAA,IAACF,EAAA,EAAmB,EACpBE,EAAAA,IAACK,EAAA,CACC,YAAU,uBACV,YAAWD,EACX,UAAWF,EACT,6aACAH,CAAA,EAED,GAAGP,CAAA,CAAA,CACN,EACF,CAEJ,CAEA,SAASc,EAAkB,CAAE,UAAAP,EAAW,GAAGP,GAAsC,CAC/E,OACEQ,EAAAA,IAAC,MAAA,CACC,YAAU,sBACV,UAAWE,EACT,qZACAH,CAAA,EAED,GAAGP,CAAA,CAAA,CAGV,CAEA,SAASe,EAAkB,CAAE,UAAAR,EAAW,GAAGP,GAAsC,CAC/E,OACEQ,EAAAA,IAAC,MAAA,CACC,YAAU,sBACV,UAAWE,EACT,gNACAH,CAAA,EAED,GAAGP,CAAA,CAAA,CAGV,CAeA,SAASgB,EAAiB,CACxB,UAAAT,EACA,GAAGP,CACL,EAA4D,CAC1D,OACEQ,EAAAA,IAACS,EAAA,CACC,YAAU,qBACV,UAAWP,EACT,iKACAH,CAAA,EAED,GAAGP,CAAA,CAAA,CAGV,CAEA,SAASkB,EAAuB,CAC9B,UAAAX,EACA,GAAGP,CACL,EAAkE,CAChE,OACEQ,EAAAA,IAACW,EAAA,CACC,YAAU,2BACV,UAAWT,EACT,iIACAH,CAAA,EAED,GAAGP,CAAA,CAAA,CAGV,CAEA,MAAMoB,EAAoBC,EAAAA,WAIxB,SAA2B,CAAE,UAAAd,EAAW,QAAAe,EAAU,UAAW,KAAAV,EAAO,UAAW,GAAGZ,CAAA,EAASuB,EAAK,CAChG,OACEf,EAAAA,IAACgB,EAAA,CACC,IAAAD,EACA,YAAU,sBACV,UAAWb,EAAGH,CAAS,EACvB,OAAQC,EAAAA,IAACiB,EAAA,CAAO,QAAAH,EAAkB,KAAAV,CAAA,CAAY,EAC7C,GAAGZ,CAAA,CAAA,CAGV,CAAC,EAEK0B,EAAoBL,EAAAA,WAGxB,SAA2B,CAAE,UAAAd,EAAW,QAAAe,EAAU,UAAW,KAAAV,EAAO,UAAW,GAAGZ,CAAA,EAASuB,EAAK,CAChG,OACEf,EAAAA,IAACgB,EAAA,CACC,IAAAD,EACA,YAAU,sBACV,UAAWb,EAAGH,CAAS,EACvB,OAAQC,EAAAA,IAACiB,EAAA,CAAO,QAAAH,EAAkB,KAAAV,CAAA,CAAY,EAC7C,GAAGZ,CAAA,CAAA,CAGV,CAAC"}
@@ -1,2 +0,0 @@
1
- import{r as h,j as e}from"./vendor-react-Dtud3GyP.js";import{A as j,b as g,c as u,d as m,e as D,f,g as A,h as w}from"./alert-dialog-DlhAyxD3.js";import{T as n,a,b as o}from"./index-72wCsW4_.js";import"./vendor-CVKsgXJU.js";function k({open:t,onConfirm:d,onClose:s,count:l,title:c="Clear all captures?",itemLabel:p="captured request",confirmTooltip:x="Permanently delete all captured requests"}){return h.useEffect(()=>{if(!t)return;const r=i=>{i.key==="Escape"&&(i.preventDefault(),i.stopPropagation(),s())};return window.addEventListener("keydown",r,!0),()=>window.removeEventListener("keydown",r,!0)},[t,s]),e.jsx(j,{open:t,onOpenChange:r=>{r||s()},children:e.jsxs(g,{children:[e.jsxs(u,{children:[e.jsx(m,{children:c}),e.jsxs(D,{children:["Delete ",l," ",p,l===1?"":"s","? This cannot be undone."]})]}),e.jsxs(f,{children:[e.jsxs(n,{children:[e.jsx(a,{render:e.jsx(A,{}),children:"Cancel"}),e.jsx(o,{side:"top",children:"Dismiss without deleting"})]}),e.jsxs(n,{children:[e.jsx(a,{render:e.jsx(w,{onClick:d}),children:"Clear"}),e.jsx(o,{side:"top",className:"max-w-[220px]",children:x})]})]})]})})}export{k as ClearConfirmDialog};
2
- //# sourceMappingURL=clear-confirm-dialog-BOcl93wD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clear-confirm-dialog-BOcl93wD.js","sources":["../../src/components/clear-confirm-dialog.tsx"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\n\ninterface ClearConfirmDialogProps {\n open: boolean;\n onConfirm: () => void;\n onClose: () => void;\n count: number;\n title?: string;\n itemLabel?: string;\n confirmTooltip?: string;\n}\n\nexport function ClearConfirmDialog({\n open,\n onConfirm,\n onClose,\n count,\n title = \"Clear all captures?\",\n itemLabel = \"captured request\",\n confirmTooltip = \"Permanently delete all captured requests\",\n}: ClearConfirmDialogProps) {\n useEffect(() => {\n if (!open) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n }\n };\n\n // Use capture phase so we intercept Escape before Base UI's AlertDialog\n // (which blocks Escape to enforce non-dismissable behavior) can swallow it.\n window.addEventListener(\"keydown\", handleKeyDown, true);\n return () => window.removeEventListener(\"keydown\", handleKeyDown, true);\n }, [open, onClose]);\n\n return (\n <AlertDialog\n open={open}\n onOpenChange={(v) => {\n if (!v) onClose();\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>{title}</AlertDialogTitle>\n <AlertDialogDescription>\n Delete {count} {itemLabel}\n {count === 1 ? \"\" : \"s\"}? This cannot be undone.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <Tooltip>\n <TooltipTrigger render={<AlertDialogCancel />}>Cancel</TooltipTrigger>\n <TooltipContent side=\"top\">Dismiss without deleting</TooltipContent>\n </Tooltip>\n <Tooltip>\n <TooltipTrigger render={<AlertDialogAction onClick={onConfirm} />}>\n Clear\n </TooltipTrigger>\n <TooltipContent side=\"top\" className=\"max-w-[220px]\">\n {confirmTooltip}\n </TooltipContent>\n </Tooltip>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"names":["ClearConfirmDialog","open","onConfirm","onClose","count","title","itemLabel","confirmTooltip","useEffect","handleKeyDown","e","jsx","AlertDialog","v","AlertDialogContent","jsxs","AlertDialogHeader","AlertDialogTitle","AlertDialogDescription","AlertDialogFooter","Tooltip","TooltipTrigger","AlertDialogCancel","TooltipContent","AlertDialogAction"],"mappings":"+NAwBO,SAASA,EAAmB,CACjC,KAAAC,EACA,UAAAC,EACA,QAAAC,EACA,MAAAC,EACA,MAAAC,EAAQ,sBACR,UAAAC,EAAY,mBACZ,eAAAC,EAAiB,0CACnB,EAA4B,CAC1BC,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAACP,EAAM,OAEX,MAAMQ,EAAiBC,GAAqB,CACtCA,EAAE,MAAQ,WACZA,EAAE,eAAA,EACFA,EAAE,gBAAA,EACFP,EAAA,EAEJ,EAIA,cAAO,iBAAiB,UAAWM,EAAe,EAAI,EAC/C,IAAM,OAAO,oBAAoB,UAAWA,EAAe,EAAI,CACxE,EAAG,CAACR,EAAME,CAAO,CAAC,EAGhBQ,EAAAA,IAACC,EAAA,CACC,KAAAX,EACA,aAAeY,GAAM,CACdA,GAAGV,EAAA,CACV,EAEA,gBAACW,EAAA,CACC,SAAA,CAAAC,OAACC,EAAA,CACC,SAAA,CAAAL,EAAAA,IAACM,GAAkB,SAAAZ,CAAA,CAAM,SACxBa,EAAA,CAAuB,SAAA,CAAA,UACdd,EAAM,IAAEE,EACfF,IAAU,EAAI,GAAK,IAAI,0BAAA,CAAA,CAC1B,CAAA,EACF,SACCe,EAAA,CACC,SAAA,CAAAJ,OAACK,EAAA,CACC,SAAA,CAAAT,MAACU,EAAA,CAAe,OAAQV,EAAAA,IAACW,EAAA,EAAkB,EAAI,SAAA,SAAM,EACrDX,EAAAA,IAACY,EAAA,CAAe,KAAK,MAAM,SAAA,0BAAA,CAAwB,CAAA,EACrD,SACCH,EAAA,CACC,SAAA,CAAAT,EAAAA,IAACU,GAAe,OAAQV,EAAAA,IAACa,GAAkB,QAAStB,CAAA,CAAW,EAAI,SAAA,OAAA,CAEnE,QACCqB,EAAA,CAAe,KAAK,MAAM,UAAU,gBAClC,SAAAhB,CAAA,CACH,CAAA,CAAA,CACF,CAAA,CAAA,CACF,CAAA,CAAA,CACF,CAAA,CAAA,CAGN"}
@@ -1,4 +0,0 @@
1
- import{j as t,r as h}from"./vendor-react-Dtud3GyP.js";import{T as w,a as j,B as _,b as R,I as U,c as C,L as ne,d as L,e as ae,f as oe,P as le,u as ue,g as de,S as ce}from"./index-72wCsW4_.js";import{Y as me,Z as pe,_ as he,$ as fe,a0 as be,a1 as G,a2 as ge,a3 as xe,a4 as ve,m as b,H as ye,R as H,a5 as ke}from"./vendor-CVKsgXJU.js";import{S as we,a as je,b as _e,c as Re,d as Se}from"./select-mZPf2BR3.js";import{A as qe,a as Ne,b as De,c as Ce,d as Me,e as Te,f as Ie,g as Ee,h as Ae}from"./alert-dialog-DlhAyxD3.js";import{u as Le}from"./use-models-B4MLkfpt.js";import{u as ze}from"./use-polling-resource-D_vWHFwW.js";import"./use-capture-done-listener-u5RxfYQP.js";function Pe({className:e,min:r,max:s,suffix:i,onChange:a,value:n,disabled:o,...d}){const u=typeof n=="string"||typeof n=="number"?n:"";function l(c){if(o)return;const m=typeof u=="string"?Number(u):u;if(Number.isNaN(m))return;let f=m+c;r!==void 0&&f<r&&(f=r),s!==void 0&&f>s&&(f=s),a==null||a(String(f))}return t.jsxs("div",{className:"flex items-stretch",children:[t.jsxs(w,{children:[t.jsx(j,{render:t.jsx(_,{type:"button",variant:"outline",size:"icon",className:"h-8 w-8 rounded-r-none border-r-0 text-muted-foreground hover:text-foreground",onClick:()=>l(-1),disabled:o||r!==void 0&&typeof u=="number"&&u<=r,"aria-label":"Decrease",tabIndex:-1}),children:t.jsx(me,{className:"h-3.5 w-3.5"})}),t.jsx(R,{side:"top",children:"Decrease value"})]}),t.jsxs("div",{className:"relative flex-1",children:[t.jsx(U,{type:"number",inputMode:"numeric",className:C("no-spinner rounded-none text-center",i&&"pr-9",e),value:n,onChange:c=>a==null?void 0:a(c.target.value),disabled:o,...d}),i?t.jsx("span",{className:"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-xs text-muted-foreground",children:i}):null]}),t.jsxs(w,{children:[t.jsx(j,{render:t.jsx(_,{type:"button",variant:"outline",size:"icon",className:"h-8 w-8 rounded-l-none border-l-0 text-muted-foreground hover:text-foreground",onClick:()=>l(1),disabled:o||s!==void 0&&typeof u=="number"&&u>=s,"aria-label":"Increase",tabIndex:-1}),children:t.jsx(pe,{className:"h-3.5 w-3.5"})}),t.jsx(R,{side:"top",children:"Increase value"})]})]})}function J({className:e,orientation:r="horizontal",...s}){return t.jsx(he,{"data-slot":"separator",orientation:r,className:C("shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch",e),...s})}function Q({className:e,size:r="default",...s}){return t.jsx(fe,{"data-slot":"switch","data-size":r,className:C("peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",e),...s,children:t.jsx(be,{"data-slot":"switch-thumb",className:"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"})})}function K({className:e,...r}){return t.jsx("textarea",{"data-slot":"textarea",className:C("flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",e),...r})}function z(e,r){return e==null?"":String(e)}function $(e){if(e==null)return"";try{return JSON.stringify(e,null,2)}catch{return String(e)}}function Fe({id:e,value:r,onChange:s}){return t.jsx(Q,{id:e,checked:!!r,onCheckedChange:i=>s(i)})}function Oe({id:e,value:r,onChange:s}){return t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(Q,{id:e,checked:!!r,onCheckedChange:i=>s(i)}),t.jsx("span",{className:"text-xs text-muted-foreground",children:r?"On":"Off"})]})}function Be({def:e,value:r,onChange:s,id:i,isInvalid:a}){return t.jsxs(we,{value:String(r??""),onValueChange:n=>s(e.nullable&&n===""?null:n),children:[t.jsx(je,{id:i,"aria-invalid":a||void 0,children:t.jsx(_e,{placeholder:e.placeholder??"Select…"})}),t.jsx(Re,{children:(e.options??[]).map(n=>t.jsx(Se,{value:n.value,children:n.label},n.value))})]})}function We({def:e,value:r,onChange:s,id:i,isInvalid:a,placeholder:n}){return t.jsx(K,{id:i,value:z(r,e.nullable),placeholder:n,onChange:o=>s(o.target.value),rows:3,"aria-invalid":a||void 0})}function He({def:e,value:r,onChange:s,id:i,isInvalid:a,placeholder:n}){return t.jsx(le,{id:i,value:z(r,e.nullable),placeholder:n,onChange:o=>s(o.target.value),disabled:e.disabled,"aria-invalid":a||void 0})}function $e({def:e,value:r,onChange:s,id:i,isInvalid:a,placeholder:n}){return t.jsx(Pe,{id:i,min:e.min,max:e.max,suffix:e.suffix,value:z(r,e.nullable),placeholder:n,onChange:o=>s(o),disabled:e.disabled,"aria-invalid":a||void 0})}function Ve({def:e,value:r,onChange:s,id:i,isInvalid:a}){return t.jsx(Je,{def:e,value:r,onChange:s,id:i,isInvalid:a})}function Ue({def:e,value:r,onChange:s,id:i,isInvalid:a,placeholder:n}){return t.jsxs("div",{className:"relative flex items-center",children:[t.jsx(U,{id:i,type:e.inputMode==="url"?"url":"text",value:z(r,e.nullable),placeholder:n,onChange:o=>s(o.target.value),disabled:e.disabled,"aria-invalid":a||void 0}),e.suffix?t.jsx("span",{className:"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-xs text-muted-foreground",children:e.suffix}):null]})}const Ge={boolean:Fe,toggle:Oe,select:Be,textarea:We,password:He,number:$e,json:Ve,text:Ue};function Je({def:e,value:r,onChange:s,id:i,isInvalid:a}){const[n,o]=h.useState(()=>$(r)),[d,u]=h.useState(null),l=h.useRef(r);h.useEffect(()=>{r!==l.current&&(l.current=r,o($(r)),u(null))},[r]);const c=m=>{if(o(m),m.trim()===""){u(null);const f=e.nullable?null:{};l.current!==f&&(l.current=f,s(f));return}try{const f=JSON.parse(m);u(null),l.current=f,s(f)}catch(f){u(f instanceof Error?f.message:"Invalid JSON")}};return t.jsxs(t.Fragment,{children:[t.jsx(K,{id:i,value:n,placeholder:e.placeholder??"JSON object",onChange:m=>c(m.target.value),rows:6,"aria-invalid":a||!!d||void 0,className:"font-mono text-xs"}),d?t.jsx("p",{className:"text-xs font-medium text-destructive",role:"alert",children:d}):null]})}function Qe({def:e,value:r,onChange:s,dirty:i,error:a,warning:n,onRefreshSource:o,refreshing:d}){const u=`cfg-${e.key}`,l=!!a,c=l,m=e.placeholder??e.patternHint??void 0;return t.jsxs("div",{className:"grid grid-cols-1 gap-1.5 py-2.5 sm:grid-cols-[minmax(200px,1fr)_2fr] sm:items-start sm:gap-4",children:[t.jsxs("div",{className:"flex flex-col gap-1",children:[t.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[t.jsx(ne,{htmlFor:u,className:C(i&&"text-primary",l&&"text-destructive"),children:e.label}),e.required?t.jsx(L,{variant:"destructive",size:"sm",children:"Required"}):null,e.restartRequired?t.jsxs(L,{variant:"secondary",size:"sm",children:[t.jsx(G,{className:"h-2.5 w-2.5"}),"restart"]}):null,e.experimental?t.jsxs(L,{variant:"secondary",size:"sm",className:ae,children:[t.jsx(ge,{className:"h-2.5 w-2.5"}),"experimental"]}):null,e.umansSourced?t.jsxs(L,{variant:"secondary",size:"sm",className:oe,children:[t.jsx(xe,{className:"h-2.5 w-2.5"}),"Umans API"]}):null]}),e.description?t.jsx("span",{className:"text-xs text-muted-foreground",children:e.description}):null]}),t.jsxs("div",{className:"flex flex-col gap-1",children:[t.jsxs("div",{className:"flex items-center gap-1.5",children:[t.jsx("div",{className:"flex-1",children:Ge[e.kind]({def:e,value:r,onChange:s,id:u,isInvalid:c,placeholder:m})}),e.refreshSource&&o?t.jsxs(w,{children:[t.jsx(j,{asChild:!0,children:t.jsx(_,{type:"button",variant:"ghost",size:"icon",className:"h-8 w-8 shrink-0",disabled:d,onClick:o,"aria-label":`Refresh ${e.label} from source`,children:t.jsx(ve,{className:C("h-3.5 w-3.5",d&&"animate-pulse")})})}),t.jsx(R,{side:"left",children:"Re-fetch from upstream rate-limit headers"})]}):null]}),l?t.jsx("p",{className:"text-xs font-medium text-destructive",role:"alert",children:a}):null,n&&!l?t.jsx("p",{className:"text-xs font-medium text-amber-600 dark:text-amber-400",children:n}):null]})]})}function Ke({section:e,values:r,originals:s,onField:i,errors:a,warnings:n,isLast:o,onRefreshSource:d,refreshingSource:u}){return t.jsxs("section",{className:"space-y-0.5",children:[t.jsx("div",{className:"flex items-baseline justify-between gap-3 pb-1",children:t.jsxs("div",{children:[t.jsx("h4",{className:"text-sm font-semibold tracking-tight",children:e.title}),t.jsx("p",{className:"text-xs text-muted-foreground",children:e.description})]})}),e.fields.map(l=>t.jsx(Qe,{def:l,value:r[l.key],onChange:c=>i(l.key,c),dirty:JSON.stringify(r[l.key]??null)!==JSON.stringify(s[l.key]??null),error:a[l.key],warning:n[l.key],onRefreshSource:l.refreshSource?d:void 0,refreshing:u},l.key)),o?null:t.jsx(J,{className:"my-2"})]})}function Ye({group:e,values:r,originals:s,onField:i,errors:a,warnings:n,isLast:o,onRefreshSource:d,refreshingSource:u}){return t.jsxs("div",{className:"space-y-1",children:[t.jsxs("div",{className:"pb-1",children:[t.jsx("h3",{className:"text-base font-semibold tracking-tight",children:e.title}),t.jsx("p",{className:"text-xs text-muted-foreground",children:e.description})]}),e.sections.map((l,c)=>t.jsx(Ke,{section:l,values:r,originals:s,onField:i,errors:a,warnings:n,isLast:o&&c===e.sections.length-1,onRefreshSource:d,refreshingSource:u},l.title)),o?null:t.jsx(J,{className:"my-3"})]})}const Ze=[{key:"vision_strategy",label:"Strategy",kind:"select",description:'Controls when the proxy intercepts image-bearing requests to generate text descriptions via a vision model. "never" disables interception entirely; "catalog" uses a cache-first strategy where vision-capable models process images themselves and only non-capable models get descriptions; "always" forces interception for all requests regardless of model capability. Default: catalog.',options:[{value:"never",label:"Never"},{value:"catalog",label:"Catalog"},{value:"always",label:"Always"}],restartRequired:!0,required:!0},{key:"vision_model",label:"Model",kind:"select",description:"Model used to generate image descriptions during vision interception. Must be a vision-capable model. The dropdown is populated from /v1/models/info at runtime. Default: umans-flash.",restartRequired:!0,required:!0}],Xe=[{key:"vision_prompt",label:"Prompt",kind:"textarea",description:"System prompt sent to the vision model when generating image descriptions. Changing this increments vision_prompt_version to bust the cache. Default: a built-in prompt that produces exhaustive, structured descriptions (image type, OCR, visual elements, data/charts, contextual clues, quality notes).",restartRequired:!0,required:!0},{key:"vision_prompt_version",label:"Prompt Version",kind:"number",description:"Version tag for the vision prompt. Bump this when you edit vision_prompt to invalidate cached descriptions generated with an older prompt. Cached entries with a mismatched version are treated as misses. Default: 2.",restartRequired:!0,required:!0,min:1},{key:"vision_max_images",label:"Max Images",kind:"number",description:"Maximum number of images processed in a single vision interception request. Additional images in the same request are ignored. Range: 1–100. Default: 5.",restartRequired:!0,required:!0,min:1,max:100},{key:"vision_max_description_tokens",label:"Max Description Tokens",kind:"number",description:"Maximum number of tokens the vision model may generate per image description. Higher values allow more detailed descriptions at the cost of latency. Range: 1–200,000. Default: 4,096.",restartRequired:!0,required:!0,min:1,max:2e5},{key:"vision_reasoning_effort",label:"Reasoning Effort",kind:"select",description:`Reasoning effort for the vision model. "none" disables reasoning; "low"/"medium"/"high" increase reasoning depth at the cost of latency. Selecting "Default (null)" uses the model's built-in default. Default: none.`,options:[{value:"",label:"Default (null)"},{value:"none",label:"None"},{value:"low",label:"Low"},{value:"medium",label:"Medium"},{value:"high",label:"High"}],nullable:!0,restartRequired:!0},{key:"vision_timeout_ms",label:"Timeout",kind:"number",description:"Timeout for vision interception requests in milliseconds. 0 means no timeout — the proxy waits indefinitely for the vision model to respond. Default: 0 (no timeout).",restartRequired:!0,required:!0,min:0,suffix:"ms"},{key:"vision_cache_size",label:"Cache Size",kind:"number",description:"Maximum number of entries in the in-memory vision description cache. When the cache is full, oldest entries are evicted. Must be ≥ 100. Default: 1,000.",restartRequired:!0,required:!0,min:100},{key:"vision_cache_ttl_ms",label:"Cache TTL",kind:"number",description:"Time-to-live for vision description cache entries in milliseconds. Expired entries are treated as misses and re-fetched from the vision model. Must be ≥ 1,000 ms. Default: 604,800,000 ms (7 days).",restartRequired:!0,min:1e3,suffix:"ms"},{key:"vision_cache_max_rows",label:"Cache Max Rows",kind:"number",description:"Maximum number of rows in the persistent (on-disk SQLite) vision cache. When the table is full, oldest entries are evicted. Must be ≥ 100. Default: 10,000.",restartRequired:!0,min:100},{key:"vision_persistent_cache",label:"Persistent Cache",kind:"boolean",description:"When on, vision description cache is persisted to disk (SQLite) and survives restarts. When off, cache is in-memory only and lost on restart. Default: true.",restartRequired:!0},{key:"vision_concurrency",label:"Concurrency",kind:"number",description:"Maximum number of concurrent vision interception requests. Higher values allow parallel image processing but increase upstream load. Range: 1–20. Default: 1.",restartRequired:!0,required:!0,min:1,max:20},{key:"vision_max_dimension",label:"Max Dimension",kind:"number",description:"Maximum width or height (in pixels) for preprocessed images. Images exceeding this in either dimension are downscaled proportionally before being sent to the vision model. Range: 256–8,192. Default: 2,048 px.",restartRequired:!0,min:256,max:8192,suffix:"px"},{key:"vision_jpeg_quality",label:"JPEG Quality",kind:"number",description:'JPEG encoding quality (1–100) for preprocessed images when vision_image_format is "jpeg". Higher values produce sharper images at larger file sizes. Ignored when format is "png". Range: 1–100. Default: 92.',restartRequired:!0,min:1,max:100},{key:"vision_image_format",label:"Image Format",kind:"select",description:'Output image format for preprocessed images sent to the vision model. "jpeg" produces smaller payloads with lossy compression; "png" is lossless but larger. Default: png.',options:[{value:"jpeg",label:"JPEG"},{value:"png",label:"PNG"}],restartRequired:!0},{key:"vision_image_detail",label:"Image Detail",kind:"select",description:'OpenAI-style image detail level sent to the vision model. "low" uses a single low-resolution pass; "high" processes the image at higher detail (more tokens, more accurate); "auto" lets the model decide based on image size. Default: high.',options:[{value:"auto",label:"Auto"},{value:"low",label:"Low"},{value:"high",label:"High"}],restartRequired:!0},{key:"vision_pending_max_batch",label:"Pending Max Batch",kind:"number",description:"Maximum number of pending vision requests to batch together into a single processing cycle. Higher values increase throughput under load but add latency for the first request in the batch. Must be ≥ 1. Default: 50.",restartRequired:!0,min:1}],et=[{key:"port",label:"Port",kind:"number",description:"TCP port the proxy listens on. Range: 1–65,535. Default: 1,945.",restartRequired:!0,required:!0,min:1,max:65535},{key:"max_captures",label:"Max Captures",kind:"number",description:"Maximum number of captures kept in the SQLite ring buffer. When this limit is reached, the oldest entries are evicted to make room for new ones. Must be ≥ 1. Default: 200.",restartRequired:!0,required:!0,min:1},{key:"db_path",label:"DB Path",kind:"text",description:"Filesystem path to the SQLite database used for storing captures. Relative paths are resolved from the proxy's working directory. Default: ./umans-gate.db.",restartRequired:!0,required:!0},{key:"idle_timeout",label:"Idle Timeout",kind:"number",description:"Bun.serve idleTimeout — seconds before an idle HTTP connection is closed. Range: 1–255 (Bun's maximum). Default: 255 s.",restartRequired:!0,required:!0,min:1,max:255,suffix:"s"},{key:"upstream_protocol",label:"Upstream Protocol",kind:"select",description:'HTTP protocol used for upstream connections. "http1.1" uses HTTP/1.1 (default, broadest compatibility); "http2" uses HTTP/2 (multiplexing, lower latency for concurrent requests). Default: http1.1.',options:[{value:"http1.1",label:"HTTP/1.1"},{value:"http2",label:"HTTP/2"}],restartRequired:!0,required:!0},{key:"compression_enabled",label:"Compression",kind:"toggle",restartRequired:!1,description:"When on, stored request/response bodies are compressed with zstd to reduce SQLite database size. Hot-reloadable — changes take effect immediately without restart. Default: on."}],tt=[{key:"stamp_claude_code_enabled",label:"Claude Code Style",kind:"toggle",description:"When on, applies the full Claude Code stamp bundle on Anthropic requests: cache TTL=1h, temperature=1.0, top_k=20 (GLM only), max_tokens (131,071 GLM / 32,767 others), thinking={type:'adaptive'}, output_config (effort=max GLM / high others), and context_management injection (requires anthropic-version: 2023-06-01). Off by default.",experimental:!0},{key:"stamp_reasoning_effort_enabled",label:"Reasoning Effort (OpenAI)",kind:"toggle",description:"When on, stamps reasoning_effort='high' onto OpenAI-compatible requests (umans-glm* models get 'max') and removes max_tokens/thinking from the request body. Off by default.",experimental:!0}],rt=[{key:"warmer_enabled",label:"Enabled",kind:"boolean",description:"When on, the proxy periodically pings the upstream /v1/models endpoint to keep connections warm, avoiding ~750 ms cold-start latency on the first request after an idle period. Default: true.",restartRequired:!0},{key:"warmer_interval_ms",label:"Interval",kind:"number",description:"Interval between upstream keep-alive pings in milliseconds. Shorter intervals keep connections warmer but increase background traffic. Must be ≥ 1,000 ms. Default: 20,000 ms (20 s).",restartRequired:!0,required:!0,min:1e3,suffix:"ms"}],st=[{key:"concurrency_hard_cap",label:"Hard Cap",kind:"number",description:"Absolute maximum number of concurrent upstream requests the proxy will allow. Excess requests are queued. This value is validated against the upstream /v1/usage limit when an Umans API key is set. Must be ≥ 1. Default: 1.",required:!0,min:1,refreshSource:!0,umansSourced:!0},{key:"concurrency_soft_limit",label:"Soft Limit",kind:"number",description:"Soft concurrency limit read from the upstream /v1/usage endpoint. Read-only — auto-derived from the upstream and cannot be edited directly. Use the refresh button to re-fetch from source. Default: 1.",disabled:!0,min:1,refreshSource:!0,umansSourced:!0},{key:"concurrency_main_reservation",label:"Main Reservation",kind:"number",description:"Number of concurrency slots reserved exclusively for main (non-vision) requests. Ensures vision traffic cannot starve main requests. Must be ≥ 1 and ≤ (hard_cap − 2) when hard_cap ≥ 3. Default: 1.",required:!0,min:1},{key:"concurrency_vision_reservation",label:"Vision Reservation",kind:"number",description:"Number of concurrency slots reserved exclusively for vision interception requests. Ensures main traffic cannot starve vision handoffs. Must be ≥ 1 and ≤ (hard_cap − 2) when hard_cap ≥ 3. Default: 1.",required:!0,min:1},{key:"release_cooldown_ms",label:"Release Cooldown",kind:"number",description:"Delay in milliseconds before releasing a concurrency slot back to the pool after a response completes. A non-zero value adds a brief cooldown to prevent immediate burst re-use. Must be ≥ 0. Default: 1,000 ms (1 s).",required:!0,min:0,suffix:"ms"},{key:"rate_limit_requests",label:"Rate Limit",kind:"number",description:"Controls upstream request rate limiting. -1 = unlimited (no limiter at all), 0 = auto-derive the limit from the upstream /v1/usage endpoint (requires umans_api_key), >0 = use this explicit value as the max requests per rate-limit window. Default: 0 (auto-derive).",min:-1,umansSourced:!0,refreshSource:!0}],it=[{key:"breaker_threshold",label:"Breaker Threshold",kind:"number",description:"Number of upstream failures within the breaker window required to open (trip) the circuit breaker. When open, the proxy short-circuits upstream requests instead of retrying. Must be ≥ 1. Default: 5.",required:!0,min:1},{key:"breaker_window_ms",label:"Breaker Window",kind:"number",description:"Sliding time window in milliseconds during which upstream failures are counted toward the breaker threshold. Must be ≥ 1,000 ms. Default: 300,000 ms (5 minutes).",required:!0,min:1e3,suffix:"ms"},{key:"breaker_cooldown_ms",label:"Breaker Cooldown",kind:"number",description:"Duration in milliseconds the circuit breaker stays open before allowing a half-open probe request through. If the probe succeeds, the breaker closes; if it fails, the timer restarts. Must be ≥ 1,000 ms. Default: 60,000 ms (1 minute).",required:!0,min:1e3,suffix:"ms"}],nt=[{key:"queue_timeout_ms",label:"Queue Timeout",kind:"number",description:"Maximum time in milliseconds a request can wait in the concurrency queue before timing out and returning an error to the client. Must be ≥ 100 ms. Default: 30,000 ms (30 s).",required:!0,min:100,suffix:"ms"},{key:"max_queue_depth",label:"Max Queue Depth",kind:"number",description:"Maximum number of requests that can be waiting in the concurrency queue simultaneously. When the queue is full, new requests are rejected immediately. Must be ≥ 1. Default: 256.",required:!0,min:1},{key:"queue_max_depth",label:"Write Queue Depth",kind:"number",description:"Maximum depth of the write-behind database flush queue (distinct from the concurrency waiters queue above). Controls how many capture writes can be buffered before the queue applies backpressure. Must be ≥ 1. Default: 100.",min:1}],at=[{key:"capture_body_max_bytes",label:"Capture Body Max Bytes",kind:"number",description:"Maximum size (in bytes) of captured request/response bodies stored in SQLite. Bodies exceeding this limit are truncated. 0 = no limit (capture full bodies). Must be ≥ 0. Default: 10,000,000 (10 MB).",min:0},{key:"ws_backpressure_limit",label:"WS Backpressure Limit",kind:"number",description:"Maximum buffered bytes per WebSocket connection before backpressure is applied. When a client falls behind, the proxy stops sending until the client catches up. 0 = use Bun's default limit. Must be ≥ 0. Default: 1,048,576 (1 MB).",min:0,suffix:"bytes"},{key:"ws_close_on_backpressure_limit",label:"Close on Backpressure Limit",kind:"boolean",description:"When on, WebSocket connections that exceed the backpressure limit are forcibly closed instead of merely pausing. This prevents slow clients from accumulating unbounded buffered data. Default: true."}],ot=[{key:"umans_api_key",label:"Umans API Key",kind:"password",description:"Umans API key used to authenticate upstream requests and fetch /v1/usage data (concurrency limits, rate-limit source). When empty, the proxy runs in fail-safe mode with worst-case limits and priority_low=true. Leave empty to disable upstream usage tracking. Default: empty.",restartRequired:!0},{key:"usage_refresh_ms",label:"Usage Refresh",kind:"number",description:"Interval in milliseconds between background /v1/usage fetches. The usage data drives concurrency limit auto-derivation and rate-limit source values. Shorter intervals keep limits fresher but increase API calls. Must be ≥ 1,000 ms. Default: 60,000 ms (1 minute).",restartRequired:!0,required:!0,min:1e3,suffix:"ms"},{key:"models_refresh_ms",label:"Models Refresh",kind:"number",description:"Interval in milliseconds between background /v1/models fetches. The models list populates the vision model dropdown and validates model names. Must be ≥ 1,000 ms. Default: 3,600,000 ms (1 hour).",restartRequired:!0,required:!0,min:1e3,suffix:"ms"}],Y=[{title:"General",description:"Core proxy, credentials, vision, and concurrency settings.",sections:[{title:"Server",description:"Core network settings and SQLite storage for captures.",fields:et},{title:"Credentials",description:"API key and upstream usage/model refresh intervals.",fields:ot},{title:"Vision",description:"Vision image-to-description interception pipeline.",fields:Ze},{title:"Warmer",description:"Background keep-alive pings to the upstream /v1/models endpoint.",fields:rt},{title:"Concurrency & Gate",description:"Concurrency limits, reservations, and rate limiting.",fields:st}]},{title:"Experimental",description:"Optional request-body stamping features that may change behavior.",sections:[{title:"Request Stamp",description:"Optional request-body stamping for Anthropic and OpenAI-compatible routes.",fields:tt}]},{title:"Advanced",description:"Circuit breaker, queue tuning, vision parameters, and storage internals.",sections:[{title:"Circuit Breaker",description:"Failure detection and circuit breaker thresholds.",fields:it},{title:"Queue",description:"Request queue timeouts, depths, and write-behind buffering.",fields:nt},{title:"Vision Tuning",description:"Vision prompt, cache, concurrency, and image processing parameters.",fields:Xe},{title:"Capture & Storage",description:"Body capture limits and WebSocket backpressure controls.",fields:at}]}];Y.flatMap(e=>e.sections);function lt(e){const[r,s]=h.useState(null);h.useEffect(()=>{e&&s({...e})},[e]);const i=h.useMemo(()=>{if(!r||!e)return new Set;const o=new Set;for(const d of Object.keys(r))JSON.stringify(r[d]??null)!==JSON.stringify(e[d]??null)&&o.add(d);return o},[r,e]);function a(){e&&s({...e})}function n(o,d){s(u=>u&&{...u,[o]:d})}return{draft:r,setDraft:s,resetDraft:a,updateField:n,isDirty:i.size>0,dirtyKeys:i}}function ut(e){const[r,s]=h.useState(!1),[i,a]=h.useState(!1),[n,o]=h.useState(!1),[d,u]=h.useState(!1),[l,c]=h.useState([]),m=h.useCallback(async()=>{if(!e.draft)return;if(!e.isDirty){b.info("Nothing to save",{description:"No changes since last load."});return}s(!0);const p={};for(const y of e.dirtyKeys)p[y]=e.draft[y];const g=await e.save(p);if(!g){s(!1),b.error("Save failed",{description:"Server unreachable."}),c(["Server unreachable."]);return}if(g.ok){const y=await e.reloadFromDisk();s(!1),y!=null&&y.ok?b.success("Config saved",{description:g.warnings.length?`${g.warnings.length} warning(s)`:"Saved and applied live."}):b.warning("Config saved",{description:"Saved to disk, but live reload failed. Restart to apply."}),c([])}else s(!1),b.error("Save rejected",{description:g.errors.join(`
2
- `)}),c(g.errors);if(g.warnings.length)for(const y of g.warnings)b.warning("Warning",{description:y})},[e]),f=h.useCallback(()=>{e.resetDraft(),b.info("Draft reset",{description:"Reverted to last loaded config."})},[e]),P=h.useCallback(async()=>{u(!0);const p=await e.resetToDefault();if(u(!1),!p){b.error("Reset failed",{description:"Server unreachable."});return}p.ok?(b.success("Config reset to defaults",{description:"API key preserved. Limits refetched from upstream."}),c([])):b.error("Reset failed",{description:p.errors.join(`
3
- `)})},[e]),D=h.useCallback(async()=>{a(!0);const p=await e.refreshFromSource();if(a(!1),!p){b.error("Refresh failed",{description:"Server unreachable."});return}if(p.ok){const g=[`Hard cap set to ${p.hardCap}`,`soft limit set to ${p.softLimit}`];p.requestsHardCap!==null&&p.requestsHardCap!==void 0?g.push(`rate limit hard cap set to ${p.requestsHardCap}`):g.push("rate limit: unlimited"),b.success("Limits reloaded from source",{description:`${g.join(", ")}.`})}else b.error("Refresh failed",{description:p.error})},[e]),M=h.useCallback(async()=>{o(!0);const p=await e.restart();if(!p){b.error("Restart failed",{description:"Server unreachable."}),o(!1);return}p.ok?(b.success("Restarting",{description:p.message??"Server is restarting. Reconnect in a few seconds. Requires a process manager (bun --watch, systemd, pm2) to auto-restart."}),setTimeout(()=>o(!1),5e3)):(b.error("Restart failed",{description:p.error}),o(!1))},[e]);return{save:m,refreshFromSource:D,restart:M,reset:f,resetToDefault:P,loading:{saving:r,refreshingSource:i,restarting:n,resetting:d},validationErrors:l,setValidationErrors:c}}const dt=3e4;function ct(){const e=n=>n??null,{data:r,loading:s,error:i,refresh:a}=ze({endpoint:"/dashboard/api/usage",pollInterval:dt,errorMessage:"Failed to fetch usage",parse:e});return{data:r,loading:s,error:i,refresh:a}}const mt={number:pt,text:V,password:V,json:ht,boolean:()=>({}),select:()=>({}),textarea:()=>({}),toggle:()=>({})};function pt(e,r){const s=r==null?"":String(r),i=Number(s);return s.length===0?{}:Number.isInteger(i)?e.min!==void 0&&i<e.min?{error:`${e.label} must be ≥ ${e.min}`}:e.max!==void 0&&i>e.max?{error:`${e.label} must be ≤ ${e.max}`}:{}:{error:`${e.label} must be an integer`}}function V(e,r){const s=r==null?"":String(r);if(e.pattern&&s.length>0&&!new RegExp(e.pattern).test(s))return{error:`${e.label} must match ${e.patternHint??e.pattern}`};if(e.inputMode==="url"&&s.length>0)try{const i=new URL(s);if(i.protocol!=="http:"&&i.protocol!=="https:")return{error:`${e.label} must use http: or https: protocol`}}catch{return{error:`${e.label} is not a valid URL`}}return{}}function ht(e,r){if(r==null)return{};if(typeof r!="object"||Array.isArray(r))return{error:`${e.label} must be a JSON object`};for(const[s,i]of Object.entries(r))if(typeof i!="number"||!Number.isFinite(i)||i<=0)return{error:`${e.label}: value for "${s}" must be a positive number`};return{}}const ft={rate_limit_requests:e=>Number(e)===-1?"Unlimited — no request cap is enforced. The upstream may still reject excessive traffic.":null};function bt(e,r){const s=r==null?"":String(r);if(e.required&&!e.nullable&&s.length===0)return{error:`${e.label} is required`};if(e.nullable&&s.length===0)return{};const i=mt[e.kind];return i?i(e,r):{}}function gt(e,r){const s={},i={};for(const a of r)for(const n of a.fields){if(n.disabled)continue;const o=bt(n,e[n.key]);o.error&&(s[n.key]=o.error);const d=ft[n.key];if(d){const u=d(e[n.key]);u&&(i[n.key]=u)}}return{errors:s,warnings:i}}function St(){const{config:e,loading:r,error:s,reload:i,save:a,reloadFromDisk:n,refreshFromSource:o,restart:d,resetToDefault:u}=ue(),{data:l}=Le(),{data:c}=ct(),{draft:m,updateField:f,resetDraft:P,isDirty:D,dirtyKeys:M}=lt(e),{save:p,refreshFromSource:g,restart:y,reset:Z,resetToDefault:X,loading:S,validationErrors:T,setValidationErrors:ee}=ut({draft:m,dirtyKeys:M,isDirty:D,resetDraft:P,save:a,reloadFromDisk:n,refreshFromSource:o,restart:d,resetToDefault:u}),[te,F]=h.useState(!1),E=h.useMemo(()=>{const x=((l==null?void 0:l.models)??[]).filter(k=>{var N;return((N=k.info)==null?void 0:N.capabilities.supports_vision)===!0}).map(k=>({value:k.id,label:k.id})).sort((k,N)=>k.label.localeCompare(N.label)),v=(c==null?void 0:c.concurrencyHardCap)??void 0,B=v!==void 0?v-2:void 0,W=(c==null?void 0:c.requestsHardCap)??void 0,q={};return v!==void 0&&(q.concurrency_hard_cap=v,q.concurrency_soft_limit=v,q.concurrency_main_reservation=B,q.concurrency_vision_reservation=B),W!==void 0&&(q.rate_limit_requests=W),Y.map(k=>({...k,sections:k.sections.map(N=>({...N,fields:N.fields.map(A=>{let I=A;return A.key==="vision_model"&&(I={...I,options:x}),A.key in q&&(I={...I,max:q[A.key]}),I})}))}))},[l,c]),{errors:O,warnings:re}=h.useMemo(()=>{if(!m)return{errors:{},warnings:{}};const x=E.flatMap(v=>v.sections);return gt(m,x)},[m,E]),se=Object.keys(O).length>0;function ie(x,v){m&&f(x,v),T.length>0&&ee([])}return r&&!m?t.jsx("div",{className:"flex h-full items-center justify-center",children:t.jsx(de,{})}):s&&!m?t.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3 p-8 text-center text-muted-foreground",children:[t.jsx(ye,{className:"h-12 w-12 text-destructive"}),t.jsx("p",{className:"text-sm font-medium text-destructive",children:"Something went wrong"}),t.jsx("p",{className:"text-xs",children:s}),t.jsxs(w,{children:[t.jsx(j,{asChild:!0,children:t.jsxs(_,{variant:"outline",size:"sm",onClick:i,children:[t.jsx(H,{className:"mr-1.5 h-3.5 w-3.5"}),"Retry"]})}),t.jsx(R,{side:"top",children:"Retry loading config"})]})]}):m?t.jsxs("div",{className:"flex h-full flex-col",children:[t.jsxs("header",{className:"flex items-center justify-between border-b border-border bg-card px-4 py-3",children:[t.jsx("h2",{className:"sr-only",children:"Configuration"}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsxs(w,{children:[t.jsx(j,{asChild:!0,children:t.jsx(_,{size:"sm",variant:"default",disabled:S.saving||!D||se,onClick:p,children:S.saving?"Saving…":"Save"})}),t.jsx(R,{side:"bottom",children:"Save changes to disk and apply live fields automatically"})]}),t.jsxs(w,{children:[t.jsx(j,{asChild:!0,children:t.jsx(_,{size:"sm",variant:"ghost",disabled:!D,onClick:Z,children:"Reset Draft"})}),t.jsx(R,{side:"bottom",children:"Discard unsaved changes"})]}),t.jsxs(qe,{open:te,onOpenChange:F,children:[t.jsxs(w,{children:[t.jsx(j,{asChild:!0,children:t.jsx(Ne,{render:t.jsxs(_,{size:"sm",variant:"outline",disabled:S.resetting,children:[t.jsx(H,{className:"mr-1.5 h-3.5 w-3.5"}),S.resetting?"Resetting…":"Reset to Default"]})})}),t.jsx(R,{side:"bottom",children:"Reset all config fields to defaults (API key is preserved)"})]}),t.jsxs(De,{children:[t.jsxs(Ce,{children:[t.jsx(Me,{children:"Reset all config to defaults?"}),t.jsx(Te,{children:"This writes the default configuration to disk, replacing all current values. Your API key is preserved. A restart will be needed for some fields to take effect."})]}),t.jsxs(Ie,{children:[t.jsx(Ee,{children:"Cancel"}),t.jsx(Ae,{variant:"destructive",onClick:async()=>{F(!1),await X()},children:"Reset to Default"})]})]})]}),t.jsxs(w,{children:[t.jsx(j,{asChild:!0,children:t.jsxs(_,{size:"sm",variant:"destructive",disabled:S.restarting,onClick:y,children:[t.jsx(ke,{className:"mr-1.5 h-3.5 w-3.5"}),S.restarting?"Restarting…":"Restart"]})}),t.jsx(R,{side:"bottom",className:"max-w-[260px]",children:"Restart the server. Requires an external process manager (bun --watch, systemd, pm2)."})]})]}),D?t.jsxs("span",{className:"text-xs text-muted-foreground",children:[M.size," unsaved change",M.size>1?"s":""]}):t.jsx("span",{className:"text-xs text-muted-foreground",children:"All changes saved"})]}),T.length>0&&t.jsxs("div",{role:"alert","aria-live":"polite",className:"border-b border-destructive/30 bg-destructive/5 px-4 py-2",children:[t.jsx("p",{className:"text-xs font-medium text-destructive",children:T.length===1?"1 issue":`${T.length} issues`}),t.jsx("ul",{className:"mt-1 space-y-0.5",children:T.map(x=>t.jsx("li",{className:"text-xs text-destructive/80",children:x},x))})]}),t.jsx(ce,{className:"min-h-0 flex-1",children:t.jsxs("div",{className:"mx-auto w-full max-w-3xl px-6 py-6",children:[E.map((x,v)=>t.jsx(Ye,{group:x,values:m,originals:e??{},onField:ie,errors:O,warnings:re,isLast:v===E.length-1,onRefreshSource:g,refreshingSource:S.refreshingSource},x.title)),t.jsxs("div",{className:"pt-6 text-xs text-muted-foreground",children:["Fields marked"," ",t.jsxs("span",{className:"text-muted-foreground",children:[t.jsx(G,{className:"inline h-3 w-3 mr-1","aria-hidden":!0}),"restart"]})," ","require a server restart to take effect. Other fields are applied live when you save."]})]})})]}):null}export{St as ConfigTab};
4
- //# sourceMappingURL=config-tab-pnBjiSyC.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-tab-pnBjiSyC.js","sources":["../../src/components/ui/number-input.tsx","../../src/components/ui/separator.tsx","../../src/components/ui/switch.tsx","../../src/components/ui/textarea.tsx","../../src/components/config-fields.tsx","../../src/components/config-vision-fields.ts","../../src/components/config-sections.ts","../../src/hooks/use-config-draft.ts","../../src/hooks/use-config-mutation.ts","../../src/hooks/use-usage.ts","../../src/lib/config-validation.ts","../../src/components/config-tab.tsx"],"sourcesContent":["import { Minus, Plus } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\ninterface NumberInputProps extends Omit<React.ComponentProps<\"input\">, \"onChange\"> {\n min?: number;\n max?: number;\n suffix?: string;\n onChange?: (value: string) => void;\n}\n\n/**\n * Number input with hidden native spinner and custom +/- stepper buttons.\n * The native spinner is hidden via CSS (see index.css `.no-spinner`).\n * The stepper buttons clamp to min/max when provided.\n */\nexport function NumberInput({\n className,\n min,\n max,\n suffix,\n onChange,\n value,\n disabled,\n ...props\n}: NumberInputProps) {\n const numVal = typeof value === \"string\" || typeof value === \"number\" ? value : \"\";\n\n function step(delta: number) {\n if (disabled) return;\n const current = typeof numVal === \"string\" ? Number(numVal) : numVal;\n if (Number.isNaN(current)) return;\n let next = current + delta;\n if (min !== undefined && next < min) next = min;\n if (max !== undefined && next > max) next = max;\n onChange?.(String(next));\n }\n\n return (\n <div className=\"flex items-stretch\">\n <Tooltip>\n <TooltipTrigger\n render={\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8 rounded-r-none border-r-0 text-muted-foreground hover:text-foreground\"\n onClick={() => step(-1)}\n disabled={\n disabled || (min !== undefined && typeof numVal === \"number\" && numVal <= min)\n }\n aria-label=\"Decrease\"\n tabIndex={-1}\n />\n }\n >\n <Minus className=\"h-3.5 w-3.5\" />\n </TooltipTrigger>\n <TooltipContent side=\"top\">Decrease value</TooltipContent>\n </Tooltip>\n <div className=\"relative flex-1\">\n <Input\n type=\"number\"\n inputMode=\"numeric\"\n className={cn(\"no-spinner rounded-none text-center\", suffix && \"pr-9\", className)}\n value={value}\n onChange={(e) => onChange?.(e.target.value)}\n disabled={disabled}\n {...props}\n />\n {suffix ? (\n <span className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-xs text-muted-foreground\">\n {suffix}\n </span>\n ) : null}\n </div>\n <Tooltip>\n <TooltipTrigger\n render={\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8 rounded-l-none border-l-0 text-muted-foreground hover:text-foreground\"\n onClick={() => step(1)}\n disabled={\n disabled || (max !== undefined && typeof numVal === \"number\" && numVal >= max)\n }\n aria-label=\"Increase\"\n tabIndex={-1}\n />\n }\n >\n <Plus className=\"h-3.5 w-3.5\" />\n </TooltipTrigger>\n <TooltipContent side=\"top\">Increase value</TooltipContent>\n </Tooltip>\n </div>\n );\n}\n","\"use client\";\n\nimport { Separator as SeparatorPrimitive } from \"@base-ui/react/separator\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Separator({ className, orientation = \"horizontal\", ...props }: SeparatorPrimitive.Props) {\n return (\n <SeparatorPrimitive\n data-slot=\"separator\"\n orientation={orientation}\n className={cn(\n \"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Separator };\n","import { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Switch({\n className,\n size = \"default\",\n ...props\n}: SwitchPrimitive.Root.Props & {\n size?: \"sm\" | \"default\";\n}) {\n return (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n data-size={size}\n className={cn(\n \"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className=\"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground\"\n />\n </SwitchPrimitive.Root>\n );\n}\n\nexport { Switch };\n","import type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n \"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n","import { Beaker, Cloud, Download, RotateCw } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport type { FieldDef } from \"@/components/config-sections\";\nimport type { GroupDef } from \"@/components/config-sections\";\nimport type { SectionDef } from \"@/components/config-sections\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { NumberInput } from \"@/components/ui/number-input\";\nimport { PasswordInput } from \"@/components/ui/password-input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport type { RawConfig } from \"@/hooks/use-config\";\nimport { badgeInfo, badgeWarning } from \"@/lib/badge-colors\";\nimport { cn } from \"@/lib/utils\";\n\n/** Convert a RawConfig value into a string suitable for an input field. */\nfunction valueToString(v: unknown, nullable?: boolean): string {\n if (v === null || v === undefined) return nullable ? \"\" : \"\";\n return String(v);\n}\n\nfunction serializeJson(v: unknown): string {\n if (v === null || v === undefined) return \"\";\n try {\n return JSON.stringify(v, null, 2);\n } catch {\n return String(v);\n }\n}\n\n/** Shared props every field renderer receives. */\ninterface RendererProps {\n def: FieldDef;\n value: unknown;\n onChange: (v: unknown) => void;\n id: string;\n isInvalid: boolean;\n placeholder: string | undefined;\n}\n\ntype FieldRenderer = (props: RendererProps) => React.JSX.Element;\n\nfunction BooleanRenderer({ id, value, onChange }: RendererProps) {\n return <Switch id={id} checked={Boolean(value)} onCheckedChange={(c) => onChange(c)} />;\n}\n\nfunction ToggleRenderer({ id, value, onChange }: RendererProps) {\n return (\n <div className=\"flex items-center gap-2\">\n <Switch id={id} checked={Boolean(value)} onCheckedChange={(c) => onChange(c)} />\n <span className=\"text-xs text-muted-foreground\">{value ? \"On\" : \"Off\"}</span>\n </div>\n );\n}\n\nfunction SelectRenderer({ def, value, onChange, id, isInvalid }: RendererProps) {\n return (\n <Select\n value={String(value ?? \"\")}\n onValueChange={(v) => onChange(def.nullable && v === \"\" ? null : v)}\n >\n <SelectTrigger id={id} aria-invalid={isInvalid || undefined}>\n <SelectValue placeholder={def.placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n {(def.options ?? []).map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n\nfunction TextareaRenderer({ def, value, onChange, id, isInvalid, placeholder }: RendererProps) {\n return (\n <Textarea\n id={id}\n value={valueToString(value, def.nullable)}\n placeholder={placeholder}\n onChange={(e) => onChange(e.target.value)}\n rows={3}\n aria-invalid={isInvalid || undefined}\n />\n );\n}\n\nfunction PasswordRenderer({ def, value, onChange, id, isInvalid, placeholder }: RendererProps) {\n return (\n <PasswordInput\n id={id}\n value={valueToString(value, def.nullable)}\n placeholder={placeholder}\n onChange={(e) => onChange(e.target.value)}\n disabled={def.disabled}\n aria-invalid={isInvalid || undefined}\n />\n );\n}\n\nfunction NumberRenderer({ def, value, onChange, id, isInvalid, placeholder }: RendererProps) {\n return (\n <NumberInput\n id={id}\n min={def.min}\n max={def.max}\n suffix={def.suffix}\n value={valueToString(value, def.nullable)}\n placeholder={placeholder}\n onChange={(v) => onChange(v)}\n disabled={def.disabled}\n aria-invalid={isInvalid || undefined}\n />\n );\n}\n\nfunction JsonRenderer({ def, value, onChange, id, isInvalid }: RendererProps) {\n return <JsonFieldRow def={def} value={value} onChange={onChange} id={id} isInvalid={isInvalid} />;\n}\n\nfunction TextRenderer({ def, value, onChange, id, isInvalid, placeholder }: RendererProps) {\n return (\n <div className=\"relative flex items-center\">\n <Input\n id={id}\n type={def.inputMode === \"url\" ? \"url\" : \"text\"}\n value={valueToString(value, def.nullable)}\n placeholder={placeholder}\n onChange={(e) => onChange(e.target.value)}\n disabled={def.disabled}\n aria-invalid={isInvalid || undefined}\n />\n {def.suffix ? (\n <span className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-xs text-muted-foreground\">\n {def.suffix}\n </span>\n ) : null}\n </div>\n );\n}\n\n/**\n * Registry of field renderers keyed by FieldDef.kind.\n * Adding a new field kind requires only one entry here.\n */\nconst FIELD_RENDERERS: Record<FieldDef[\"kind\"], FieldRenderer> = {\n boolean: BooleanRenderer,\n toggle: ToggleRenderer,\n select: SelectRenderer,\n textarea: TextareaRenderer,\n password: PasswordRenderer,\n number: NumberRenderer,\n json: JsonRenderer,\n text: TextRenderer,\n};\n\nfunction JsonFieldRow({\n def,\n value,\n onChange,\n id,\n isInvalid,\n}: {\n def: FieldDef;\n value: unknown;\n onChange: (v: unknown) => void;\n id: string;\n isInvalid: boolean;\n}) {\n const [text, setText] = useState(() => serializeJson(value));\n const [parseError, setParseError] = useState<string | null>(null);\n const lastValueRef = useRef<unknown>(value);\n\n useEffect(() => {\n if (value !== lastValueRef.current) {\n lastValueRef.current = value;\n setText(serializeJson(value));\n setParseError(null);\n }\n }, [value]);\n\n const handleChange = (newText: string) => {\n setText(newText);\n if (newText.trim() === \"\") {\n setParseError(null);\n const fallback = def.nullable ? null : {};\n if (lastValueRef.current !== fallback) {\n lastValueRef.current = fallback;\n onChange(fallback);\n }\n return;\n }\n try {\n const parsed = JSON.parse(newText);\n setParseError(null);\n lastValueRef.current = parsed;\n onChange(parsed);\n } catch (e) {\n setParseError(e instanceof Error ? e.message : \"Invalid JSON\");\n // Don't call onChange — keep previous valid value in draft.\n }\n };\n\n return (\n <>\n <Textarea\n id={id}\n value={text}\n placeholder={def.placeholder ?? \"JSON object\"}\n onChange={(e) => handleChange(e.target.value)}\n rows={6}\n aria-invalid={isInvalid || Boolean(parseError) || undefined}\n className=\"font-mono text-xs\"\n />\n {parseError ? (\n <p className=\"text-xs font-medium text-destructive\" role=\"alert\">\n {parseError}\n </p>\n ) : null}\n </>\n );\n}\n\nexport function FieldRow({\n def,\n value,\n onChange,\n dirty,\n error,\n warning,\n onRefreshSource,\n refreshing,\n}: {\n def: FieldDef;\n value: unknown;\n onChange: (v: unknown) => void;\n dirty: boolean;\n error?: string;\n warning?: string;\n onRefreshSource?: () => void;\n refreshing?: boolean;\n}) {\n const id = `cfg-${def.key}`;\n const hasError = Boolean(error);\n const isInvalid = hasError;\n const placeholder = def.placeholder ?? def.patternHint ?? undefined;\n\n return (\n <div className=\"grid grid-cols-1 gap-1.5 py-2.5 sm:grid-cols-[minmax(200px,1fr)_2fr] sm:items-start sm:gap-4\">\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Label\n htmlFor={id}\n className={cn(dirty && \"text-primary\", hasError && \"text-destructive\")}\n >\n {def.label}\n </Label>\n {def.required ? (\n <Badge variant=\"destructive\" size=\"sm\">\n Required\n </Badge>\n ) : null}\n {def.restartRequired ? (\n <Badge variant=\"secondary\" size=\"sm\">\n <RotateCw className=\"h-2.5 w-2.5\" />\n restart\n </Badge>\n ) : null}\n {def.experimental ? (\n <Badge variant=\"secondary\" size=\"sm\" className={badgeWarning}>\n <Beaker className=\"h-2.5 w-2.5\" />\n experimental\n </Badge>\n ) : null}\n {def.umansSourced ? (\n <Badge variant=\"secondary\" size=\"sm\" className={badgeInfo}>\n <Cloud className=\"h-2.5 w-2.5\" />\n Umans API\n </Badge>\n ) : null}\n </div>\n {def.description ? (\n <span className=\"text-xs text-muted-foreground\">{def.description}</span>\n ) : null}\n </div>\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-center gap-1.5\">\n <div className=\"flex-1\">\n {FIELD_RENDERERS[def.kind]({\n def,\n value,\n onChange,\n id,\n isInvalid,\n placeholder,\n })}\n </div>\n {def.refreshSource && onRefreshSource ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 shrink-0\"\n disabled={refreshing}\n onClick={onRefreshSource}\n aria-label={`Refresh ${def.label} from source`}\n >\n <Download className={cn(\"h-3.5 w-3.5\", refreshing && \"animate-pulse\")} />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"left\">Re-fetch from upstream rate-limit headers</TooltipContent>\n </Tooltip>\n ) : null}\n </div>\n {hasError ? (\n <p className=\"text-xs font-medium text-destructive\" role=\"alert\">\n {error}\n </p>\n ) : null}\n {warning && !hasError ? (\n <p className=\"text-xs font-medium text-amber-600 dark:text-amber-400\">{warning}</p>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport function SectionBlock({\n section,\n values,\n originals,\n onField,\n errors,\n warnings,\n isLast,\n onRefreshSource,\n refreshingSource,\n}: {\n section: SectionDef;\n values: RawConfig;\n originals: RawConfig;\n onField: (key: keyof RawConfig, v: unknown) => void;\n errors: Record<string, string>;\n warnings: Record<string, string>;\n isLast: boolean;\n onRefreshSource?: () => void;\n refreshingSource?: boolean;\n}) {\n return (\n <section className=\"space-y-0.5\">\n <div className=\"flex items-baseline justify-between gap-3 pb-1\">\n <div>\n <h4 className=\"text-sm font-semibold tracking-tight\">{section.title}</h4>\n <p className=\"text-xs text-muted-foreground\">{section.description}</p>\n </div>\n </div>\n {section.fields.map((f) => (\n <FieldRow\n key={f.key}\n def={f}\n value={values[f.key]}\n onChange={(v) => onField(f.key, v)}\n dirty={JSON.stringify(values[f.key] ?? null) !== JSON.stringify(originals[f.key] ?? null)}\n error={errors[f.key as string]}\n warning={warnings[f.key as string]}\n onRefreshSource={f.refreshSource ? onRefreshSource : undefined}\n refreshing={refreshingSource}\n />\n ))}\n {!isLast ? <Separator className=\"my-2\" /> : null}\n </section>\n );\n}\n\nexport function GroupBlock({\n group,\n values,\n originals,\n onField,\n errors,\n warnings,\n isLast,\n onRefreshSource,\n refreshingSource,\n}: {\n group: GroupDef;\n values: RawConfig;\n originals: RawConfig;\n onField: (key: keyof RawConfig, v: unknown) => void;\n errors: Record<string, string>;\n warnings: Record<string, string>;\n isLast: boolean;\n onRefreshSource?: () => void;\n refreshingSource?: boolean;\n}) {\n return (\n <div className=\"space-y-1\">\n <div className=\"pb-1\">\n <h3 className=\"text-base font-semibold tracking-tight\">{group.title}</h3>\n <p className=\"text-xs text-muted-foreground\">{group.description}</p>\n </div>\n {group.sections.map((s, i) => (\n <SectionBlock\n key={s.title}\n section={s}\n values={values}\n originals={originals}\n onField={onField}\n errors={errors}\n warnings={warnings}\n isLast={isLast && i === group.sections.length - 1}\n onRefreshSource={onRefreshSource}\n refreshingSource={refreshingSource}\n />\n ))}\n {!isLast ? <Separator className=\"my-3\" /> : null}\n </div>\n );\n}\n","import type { FieldDef } from \"@/components/config-sections\";\n\nexport const VISION_GENERAL_FIELDS: FieldDef[] = [\n {\n key: \"vision_strategy\",\n label: \"Strategy\",\n kind: \"select\",\n description:\n 'Controls when the proxy intercepts image-bearing requests to generate text descriptions via a vision model. \"never\" disables interception entirely; \"catalog\" uses a cache-first strategy where vision-capable models process images themselves and only non-capable models get descriptions; \"always\" forces interception for all requests regardless of model capability. Default: catalog.',\n options: [\n { value: \"never\", label: \"Never\" },\n { value: \"catalog\", label: \"Catalog\" },\n { value: \"always\", label: \"Always\" },\n ],\n restartRequired: true,\n required: true,\n },\n {\n key: \"vision_model\",\n label: \"Model\",\n kind: \"select\",\n description:\n \"Model used to generate image descriptions during vision interception. Must be a vision-capable model. The dropdown is populated from /v1/models/info at runtime. Default: umans-flash.\",\n restartRequired: true,\n required: true,\n },\n];\n\nexport const VISION_TUNING_FIELDS: FieldDef[] = [\n {\n key: \"vision_prompt\",\n label: \"Prompt\",\n kind: \"textarea\",\n description:\n \"System prompt sent to the vision model when generating image descriptions. Changing this increments vision_prompt_version to bust the cache. Default: a built-in prompt that produces exhaustive, structured descriptions (image type, OCR, visual elements, data/charts, contextual clues, quality notes).\",\n restartRequired: true,\n required: true,\n },\n {\n key: \"vision_prompt_version\",\n label: \"Prompt Version\",\n kind: \"number\",\n description:\n \"Version tag for the vision prompt. Bump this when you edit vision_prompt to invalidate cached descriptions generated with an older prompt. Cached entries with a mismatched version are treated as misses. Default: 2.\",\n restartRequired: true,\n required: true,\n min: 1,\n },\n {\n key: \"vision_max_images\",\n label: \"Max Images\",\n kind: \"number\",\n description:\n \"Maximum number of images processed in a single vision interception request. Additional images in the same request are ignored. Range: 1–100. Default: 5.\",\n restartRequired: true,\n required: true,\n min: 1,\n max: 100,\n },\n {\n key: \"vision_max_description_tokens\",\n label: \"Max Description Tokens\",\n kind: \"number\",\n description:\n \"Maximum number of tokens the vision model may generate per image description. Higher values allow more detailed descriptions at the cost of latency. Range: 1–200,000. Default: 4,096.\",\n restartRequired: true,\n required: true,\n min: 1,\n max: 200000,\n },\n {\n key: \"vision_reasoning_effort\",\n label: \"Reasoning Effort\",\n kind: \"select\",\n description:\n 'Reasoning effort for the vision model. \"none\" disables reasoning; \"low\"/\"medium\"/\"high\" increase reasoning depth at the cost of latency. Selecting \"Default (null)\" uses the model\\'s built-in default. Default: none.',\n options: [\n { value: \"\", label: \"Default (null)\" },\n { value: \"none\", label: \"None\" },\n { value: \"low\", label: \"Low\" },\n { value: \"medium\", label: \"Medium\" },\n { value: \"high\", label: \"High\" },\n ],\n nullable: true,\n restartRequired: true,\n },\n {\n key: \"vision_timeout_ms\",\n label: \"Timeout\",\n kind: \"number\",\n description:\n \"Timeout for vision interception requests in milliseconds. 0 means no timeout — the proxy waits indefinitely for the vision model to respond. Default: 0 (no timeout).\",\n restartRequired: true,\n required: true,\n min: 0,\n suffix: \"ms\",\n },\n {\n key: \"vision_cache_size\",\n label: \"Cache Size\",\n kind: \"number\",\n description:\n \"Maximum number of entries in the in-memory vision description cache. When the cache is full, oldest entries are evicted. Must be ≥ 100. Default: 1,000.\",\n restartRequired: true,\n required: true,\n min: 100,\n },\n {\n key: \"vision_cache_ttl_ms\",\n label: \"Cache TTL\",\n kind: \"number\",\n description:\n \"Time-to-live for vision description cache entries in milliseconds. Expired entries are treated as misses and re-fetched from the vision model. Must be ≥ 1,000 ms. Default: 604,800,000 ms (7 days).\",\n restartRequired: true,\n min: 1000,\n suffix: \"ms\",\n },\n {\n key: \"vision_cache_max_rows\",\n label: \"Cache Max Rows\",\n kind: \"number\",\n description:\n \"Maximum number of rows in the persistent (on-disk SQLite) vision cache. When the table is full, oldest entries are evicted. Must be ≥ 100. Default: 10,000.\",\n restartRequired: true,\n min: 100,\n },\n {\n key: \"vision_persistent_cache\",\n label: \"Persistent Cache\",\n kind: \"boolean\",\n description:\n \"When on, vision description cache is persisted to disk (SQLite) and survives restarts. When off, cache is in-memory only and lost on restart. Default: true.\",\n restartRequired: true,\n },\n {\n key: \"vision_concurrency\",\n label: \"Concurrency\",\n kind: \"number\",\n description:\n \"Maximum number of concurrent vision interception requests. Higher values allow parallel image processing but increase upstream load. Range: 1–20. Default: 1.\",\n restartRequired: true,\n required: true,\n min: 1,\n max: 20,\n },\n {\n key: \"vision_max_dimension\",\n label: \"Max Dimension\",\n kind: \"number\",\n description:\n \"Maximum width or height (in pixels) for preprocessed images. Images exceeding this in either dimension are downscaled proportionally before being sent to the vision model. Range: 256–8,192. Default: 2,048 px.\",\n restartRequired: true,\n min: 256,\n max: 8192,\n suffix: \"px\",\n },\n {\n key: \"vision_jpeg_quality\",\n label: \"JPEG Quality\",\n kind: \"number\",\n description:\n 'JPEG encoding quality (1–100) for preprocessed images when vision_image_format is \"jpeg\". Higher values produce sharper images at larger file sizes. Ignored when format is \"png\". Range: 1–100. Default: 92.',\n restartRequired: true,\n min: 1,\n max: 100,\n },\n {\n key: \"vision_image_format\",\n label: \"Image Format\",\n kind: \"select\",\n description:\n 'Output image format for preprocessed images sent to the vision model. \"jpeg\" produces smaller payloads with lossy compression; \"png\" is lossless but larger. Default: png.',\n options: [\n { value: \"jpeg\", label: \"JPEG\" },\n { value: \"png\", label: \"PNG\" },\n ],\n restartRequired: true,\n },\n {\n key: \"vision_image_detail\",\n label: \"Image Detail\",\n kind: \"select\",\n description:\n 'OpenAI-style image detail level sent to the vision model. \"low\" uses a single low-resolution pass; \"high\" processes the image at higher detail (more tokens, more accurate); \"auto\" lets the model decide based on image size. Default: high.',\n options: [\n { value: \"auto\", label: \"Auto\" },\n { value: \"low\", label: \"Low\" },\n { value: \"high\", label: \"High\" },\n ],\n restartRequired: true,\n },\n {\n key: \"vision_pending_max_batch\",\n label: \"Pending Max Batch\",\n kind: \"number\",\n description:\n \"Maximum number of pending vision requests to batch together into a single processing cycle. Higher values increase throughput under load but add latency for the first request in the batch. Must be ≥ 1. Default: 50.\",\n restartRequired: true,\n min: 1,\n },\n];\n","import { VISION_GENERAL_FIELDS, VISION_TUNING_FIELDS } from \"@/components/config-vision-fields\";\nimport type { RawConfig } from \"@/hooks/use-config\";\n\n/**\n * Field helpers.\n * Each field knows how to read/write a primitive value from a RawConfig patch.\n */\ntype FieldKind =\n | \"number\"\n | \"text\"\n | \"boolean\"\n | \"select\"\n | \"textarea\"\n | \"password\"\n | \"toggle\"\n | \"json\";\n\nexport interface FieldDef {\n key: keyof RawConfig;\n label: string;\n kind: FieldKind;\n description?: string;\n placeholder?: string;\n options?: { value: string; label: string }[];\n /** when true, null/empty is a valid value */\n nullable?: boolean;\n restartRequired?: boolean;\n /** when true, field is rendered read-only (user cannot edit) */\n disabled?: boolean;\n /** validation: minimum numeric value */\n min?: number;\n /** validation: maximum numeric value */\n max?: number;\n /** validation: regex pattern for text fields */\n pattern?: string;\n /** validation: human-readable pattern hint shown as placeholder/helper */\n patternHint?: string;\n /** when true, field must be non-empty to pass validation */\n required?: boolean;\n /** input type hint for text inputs: \"url\", \"text\", \"password\" */\n inputMode?: \"url\" | \"text\" | \"password\" | \"numeric\";\n /** suffix label for input groups, e.g. \"ms\", \"s\", \"px\" */\n suffix?: string;\n /** when true, field is marked as experimental with a badge */\n experimental?: boolean;\n /** when true, show a per-field refresh-from-source icon button */\n refreshSource?: boolean;\n /** when true, field max bounds are sourced from /v1/usage and an \"Umans API\" badge is shown */\n umansSourced?: boolean;\n}\n\nconst SERVER_FIELDS: FieldDef[] = [\n {\n key: \"port\",\n label: \"Port\",\n kind: \"number\",\n description: \"TCP port the proxy listens on. Range: 1–65,535. Default: 1,945.\",\n restartRequired: true,\n required: true,\n min: 1,\n max: 65535,\n },\n {\n key: \"max_captures\",\n label: \"Max Captures\",\n kind: \"number\",\n description:\n \"Maximum number of captures kept in the SQLite ring buffer. When this limit is reached, the oldest entries are evicted to make room for new ones. Must be ≥ 1. Default: 200.\",\n restartRequired: true,\n required: true,\n min: 1,\n },\n {\n key: \"db_path\",\n label: \"DB Path\",\n kind: \"text\",\n description:\n \"Filesystem path to the SQLite database used for storing captures. Relative paths are resolved from the proxy's working directory. Default: ./umans-gate.db.\",\n restartRequired: true,\n required: true,\n },\n {\n key: \"idle_timeout\",\n label: \"Idle Timeout\",\n kind: \"number\",\n description:\n \"Bun.serve idleTimeout — seconds before an idle HTTP connection is closed. Range: 1–255 (Bun's maximum). Default: 255 s.\",\n restartRequired: true,\n required: true,\n min: 1,\n max: 255,\n suffix: \"s\",\n },\n {\n key: \"upstream_protocol\",\n label: \"Upstream Protocol\",\n kind: \"select\",\n description:\n 'HTTP protocol used for upstream connections. \"http1.1\" uses HTTP/1.1 (default, broadest compatibility); \"http2\" uses HTTP/2 (multiplexing, lower latency for concurrent requests). Default: http1.1.',\n options: [\n { value: \"http1.1\", label: \"HTTP/1.1\" },\n { value: \"http2\", label: \"HTTP/2\" },\n ],\n restartRequired: true,\n required: true,\n },\n {\n key: \"compression_enabled\",\n label: \"Compression\",\n kind: \"toggle\",\n restartRequired: false,\n description:\n \"When on, stored request/response bodies are compressed with zstd to reduce SQLite database size. Hot-reloadable — changes take effect immediately without restart. Default: on.\",\n },\n];\n\nconst STAMP_FIELDS: FieldDef[] = [\n {\n key: \"stamp_claude_code_enabled\",\n label: \"Claude Code Style\",\n kind: \"toggle\",\n description:\n \"When on, applies the full Claude Code stamp bundle on Anthropic requests: cache TTL=1h, temperature=1.0, top_k=20 (GLM only), max_tokens (131,071 GLM / 32,767 others), thinking={type:'adaptive'}, output_config (effort=max GLM / high others), and context_management injection (requires anthropic-version: 2023-06-01). Off by default.\",\n experimental: true,\n },\n {\n key: \"stamp_reasoning_effort_enabled\",\n label: \"Reasoning Effort (OpenAI)\",\n kind: \"toggle\",\n description:\n \"When on, stamps reasoning_effort='high' onto OpenAI-compatible requests (umans-glm* models get 'max') and removes max_tokens/thinking from the request body. Off by default.\",\n experimental: true,\n },\n];\n\nconst WARMER_FIELDS: FieldDef[] = [\n {\n key: \"warmer_enabled\",\n label: \"Enabled\",\n kind: \"boolean\",\n description:\n \"When on, the proxy periodically pings the upstream /v1/models endpoint to keep connections warm, avoiding ~750 ms cold-start latency on the first request after an idle period. Default: true.\",\n restartRequired: true,\n },\n {\n key: \"warmer_interval_ms\",\n label: \"Interval\",\n kind: \"number\",\n description:\n \"Interval between upstream keep-alive pings in milliseconds. Shorter intervals keep connections warmer but increase background traffic. Must be ≥ 1,000 ms. Default: 20,000 ms (20 s).\",\n restartRequired: true,\n required: true,\n min: 1000,\n suffix: \"ms\",\n },\n];\n\nconst CONCURRENCY_GATE_FIELDS: FieldDef[] = [\n {\n key: \"concurrency_hard_cap\",\n label: \"Hard Cap\",\n kind: \"number\",\n description:\n \"Absolute maximum number of concurrent upstream requests the proxy will allow. Excess requests are queued. This value is validated against the upstream /v1/usage limit when an Umans API key is set. Must be ≥ 1. Default: 1.\",\n required: true,\n min: 1,\n refreshSource: true,\n umansSourced: true,\n },\n {\n key: \"concurrency_soft_limit\",\n label: \"Soft Limit\",\n kind: \"number\",\n description:\n \"Soft concurrency limit read from the upstream /v1/usage endpoint. Read-only — auto-derived from the upstream and cannot be edited directly. Use the refresh button to re-fetch from source. Default: 1.\",\n disabled: true,\n min: 1,\n refreshSource: true,\n umansSourced: true,\n },\n {\n key: \"concurrency_main_reservation\",\n label: \"Main Reservation\",\n kind: \"number\",\n description:\n \"Number of concurrency slots reserved exclusively for main (non-vision) requests. Ensures vision traffic cannot starve main requests. Must be ≥ 1 and ≤ (hard_cap − 2) when hard_cap ≥ 3. Default: 1.\",\n required: true,\n min: 1,\n },\n {\n key: \"concurrency_vision_reservation\",\n label: \"Vision Reservation\",\n kind: \"number\",\n description:\n \"Number of concurrency slots reserved exclusively for vision interception requests. Ensures main traffic cannot starve vision handoffs. Must be ≥ 1 and ≤ (hard_cap − 2) when hard_cap ≥ 3. Default: 1.\",\n required: true,\n min: 1,\n },\n {\n key: \"release_cooldown_ms\",\n label: \"Release Cooldown\",\n kind: \"number\",\n description:\n \"Delay in milliseconds before releasing a concurrency slot back to the pool after a response completes. A non-zero value adds a brief cooldown to prevent immediate burst re-use. Must be ≥ 0. Default: 1,000 ms (1 s).\",\n required: true,\n min: 0,\n suffix: \"ms\",\n },\n {\n key: \"rate_limit_requests\",\n label: \"Rate Limit\",\n kind: \"number\",\n description:\n \"Controls upstream request rate limiting. -1 = unlimited (no limiter at all), 0 = auto-derive the limit from the upstream /v1/usage endpoint (requires umans_api_key), >0 = use this explicit value as the max requests per rate-limit window. Default: 0 (auto-derive).\",\n min: -1,\n umansSourced: true,\n refreshSource: true,\n },\n];\n\nconst CIRCUIT_BREAKER_FIELDS: FieldDef[] = [\n {\n key: \"breaker_threshold\",\n label: \"Breaker Threshold\",\n kind: \"number\",\n description:\n \"Number of upstream failures within the breaker window required to open (trip) the circuit breaker. When open, the proxy short-circuits upstream requests instead of retrying. Must be ≥ 1. Default: 5.\",\n required: true,\n min: 1,\n },\n {\n key: \"breaker_window_ms\",\n label: \"Breaker Window\",\n kind: \"number\",\n description:\n \"Sliding time window in milliseconds during which upstream failures are counted toward the breaker threshold. Must be ≥ 1,000 ms. Default: 300,000 ms (5 minutes).\",\n required: true,\n min: 1000,\n suffix: \"ms\",\n },\n {\n key: \"breaker_cooldown_ms\",\n label: \"Breaker Cooldown\",\n kind: \"number\",\n description:\n \"Duration in milliseconds the circuit breaker stays open before allowing a half-open probe request through. If the probe succeeds, the breaker closes; if it fails, the timer restarts. Must be ≥ 1,000 ms. Default: 60,000 ms (1 minute).\",\n required: true,\n min: 1000,\n suffix: \"ms\",\n },\n];\n\nconst QUEUE_FIELDS: FieldDef[] = [\n {\n key: \"queue_timeout_ms\",\n label: \"Queue Timeout\",\n kind: \"number\",\n description:\n \"Maximum time in milliseconds a request can wait in the concurrency queue before timing out and returning an error to the client. Must be ≥ 100 ms. Default: 30,000 ms (30 s).\",\n required: true,\n min: 100,\n suffix: \"ms\",\n },\n {\n key: \"max_queue_depth\",\n label: \"Max Queue Depth\",\n kind: \"number\",\n description:\n \"Maximum number of requests that can be waiting in the concurrency queue simultaneously. When the queue is full, new requests are rejected immediately. Must be ≥ 1. Default: 256.\",\n required: true,\n min: 1,\n },\n {\n key: \"queue_max_depth\",\n label: \"Write Queue Depth\",\n kind: \"number\",\n description:\n \"Maximum depth of the write-behind database flush queue (distinct from the concurrency waiters queue above). Controls how many capture writes can be buffered before the queue applies backpressure. Must be ≥ 1. Default: 100.\",\n min: 1,\n },\n];\n\nconst CAPTURE_STORAGE_FIELDS: FieldDef[] = [\n {\n key: \"capture_body_max_bytes\",\n label: \"Capture Body Max Bytes\",\n kind: \"number\",\n description:\n \"Maximum size (in bytes) of captured request/response bodies stored in SQLite. Bodies exceeding this limit are truncated. 0 = no limit (capture full bodies). Must be ≥ 0. Default: 10,000,000 (10 MB).\",\n min: 0,\n },\n {\n key: \"ws_backpressure_limit\",\n label: \"WS Backpressure Limit\",\n kind: \"number\",\n description:\n \"Maximum buffered bytes per WebSocket connection before backpressure is applied. When a client falls behind, the proxy stops sending until the client catches up. 0 = use Bun's default limit. Must be ≥ 0. Default: 1,048,576 (1 MB).\",\n min: 0,\n suffix: \"bytes\",\n },\n {\n key: \"ws_close_on_backpressure_limit\",\n label: \"Close on Backpressure Limit\",\n kind: \"boolean\",\n description:\n \"When on, WebSocket connections that exceed the backpressure limit are forcibly closed instead of merely pausing. This prevents slow clients from accumulating unbounded buffered data. Default: true.\",\n },\n];\n\nconst CREDENTIALS_FIELDS: FieldDef[] = [\n {\n key: \"umans_api_key\",\n label: \"Umans API Key\",\n kind: \"password\",\n description:\n \"Umans API key used to authenticate upstream requests and fetch /v1/usage data (concurrency limits, rate-limit source). When empty, the proxy runs in fail-safe mode with worst-case limits and priority_low=true. Leave empty to disable upstream usage tracking. Default: empty.\",\n restartRequired: true,\n },\n {\n key: \"usage_refresh_ms\",\n label: \"Usage Refresh\",\n kind: \"number\",\n description:\n \"Interval in milliseconds between background /v1/usage fetches. The usage data drives concurrency limit auto-derivation and rate-limit source values. Shorter intervals keep limits fresher but increase API calls. Must be ≥ 1,000 ms. Default: 60,000 ms (1 minute).\",\n restartRequired: true,\n required: true,\n min: 1000,\n suffix: \"ms\",\n },\n {\n key: \"models_refresh_ms\",\n label: \"Models Refresh\",\n kind: \"number\",\n description:\n \"Interval in milliseconds between background /v1/models fetches. The models list populates the vision model dropdown and validates model names. Must be ≥ 1,000 ms. Default: 3,600,000 ms (1 hour).\",\n restartRequired: true,\n required: true,\n min: 1000,\n suffix: \"ms\",\n },\n];\n\nexport interface SectionDef {\n title: string;\n description: string;\n fields: FieldDef[];\n}\n\nexport interface GroupDef {\n title: string;\n description: string;\n sections: SectionDef[];\n}\n\nexport const GROUPS: GroupDef[] = [\n {\n title: \"General\",\n description: \"Core proxy, credentials, vision, and concurrency settings.\",\n sections: [\n {\n title: \"Server\",\n description: \"Core network settings and SQLite storage for captures.\",\n fields: SERVER_FIELDS,\n },\n {\n title: \"Credentials\",\n description: \"API key and upstream usage/model refresh intervals.\",\n fields: CREDENTIALS_FIELDS,\n },\n {\n title: \"Vision\",\n description: \"Vision image-to-description interception pipeline.\",\n fields: VISION_GENERAL_FIELDS,\n },\n {\n title: \"Warmer\",\n description: \"Background keep-alive pings to the upstream /v1/models endpoint.\",\n fields: WARMER_FIELDS,\n },\n {\n title: \"Concurrency & Gate\",\n description: \"Concurrency limits, reservations, and rate limiting.\",\n fields: CONCURRENCY_GATE_FIELDS,\n },\n ],\n },\n {\n title: \"Experimental\",\n description: \"Optional request-body stamping features that may change behavior.\",\n sections: [\n {\n title: \"Request Stamp\",\n description: \"Optional request-body stamping for Anthropic and OpenAI-compatible routes.\",\n fields: STAMP_FIELDS,\n },\n ],\n },\n {\n title: \"Advanced\",\n description: \"Circuit breaker, queue tuning, vision parameters, and storage internals.\",\n sections: [\n {\n title: \"Circuit Breaker\",\n description: \"Failure detection and circuit breaker thresholds.\",\n fields: CIRCUIT_BREAKER_FIELDS,\n },\n {\n title: \"Queue\",\n description: \"Request queue timeouts, depths, and write-behind buffering.\",\n fields: QUEUE_FIELDS,\n },\n {\n title: \"Vision Tuning\",\n description: \"Vision prompt, cache, concurrency, and image processing parameters.\",\n fields: VISION_TUNING_FIELDS,\n },\n {\n title: \"Capture & Storage\",\n description: \"Body capture limits and WebSocket backpressure controls.\",\n fields: CAPTURE_STORAGE_FIELDS,\n },\n ],\n },\n];\n\n/** Flat list of all sections across all groups (for validation and lookups). */\nexport const SECTIONS: SectionDef[] = GROUPS.flatMap((g) => g.sections);\n","import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from \"react\";\n\nimport type { RawConfig } from \"@/hooks/use-config\";\n\nexport interface UseConfigDraftResult {\n draft: RawConfig | null;\n setDraft: Dispatch<SetStateAction<RawConfig | null>>;\n resetDraft: () => void;\n updateField: (key: keyof RawConfig, v: unknown) => void;\n isDirty: boolean;\n dirtyKeys: Set<keyof RawConfig>;\n}\n\n/**\n * Manages the editable draft state for the config tab.\n * Syncs from `initialConfig` when a fresh config arrives,\n * tracks dirty keys, and provides field-level updates.\n */\nexport function useConfigDraft(initialConfig: RawConfig | null): UseConfigDraftResult {\n const [draft, setDraft] = useState<RawConfig | null>(null);\n\n // Sync draft when fresh config arrives.\n useEffect(() => {\n if (initialConfig) setDraft({ ...initialConfig });\n }, [initialConfig]);\n\n const dirtyKeys = useMemo(() => {\n if (!draft || !initialConfig) return new Set<keyof RawConfig>();\n const keys = new Set<keyof RawConfig>();\n for (const k of Object.keys(draft) as (keyof RawConfig)[]) {\n if (JSON.stringify(draft[k] ?? null) !== JSON.stringify(initialConfig[k] ?? null)) {\n keys.add(k);\n }\n }\n return keys;\n }, [draft, initialConfig]);\n\n function resetDraft() {\n if (initialConfig) setDraft({ ...initialConfig });\n }\n\n function updateField(key: keyof RawConfig, v: unknown) {\n setDraft((prev) => (prev ? { ...prev, [key]: v } : prev));\n }\n\n return { draft, setDraft, resetDraft, updateField, isDirty: dirtyKeys.size > 0, dirtyKeys };\n}\n","import { useCallback, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport type {\n RawConfig,\n RefreshSourceResult,\n ReloadResult,\n RestartResult,\n SaveResult,\n} from \"@/hooks/use-config\";\n\nexport interface ConfigApi {\n draft: RawConfig | null;\n dirtyKeys: Set<keyof RawConfig>;\n isDirty: boolean;\n resetDraft: () => void;\n save: (patch: RawConfig) => Promise<SaveResult | null>;\n reloadFromDisk: () => Promise<ReloadResult | null>;\n refreshFromSource: () => Promise<RefreshSourceResult | null>;\n restart: () => Promise<RestartResult | null>;\n resetToDefault: () => Promise<SaveResult | null>;\n}\n\nexport interface UseConfigMutationResult {\n save: () => Promise<void>;\n refreshFromSource: () => Promise<void>;\n restart: () => Promise<void>;\n reset: () => void;\n resetToDefault: () => Promise<void>;\n loading: {\n saving: boolean;\n refreshingSource: boolean;\n restarting: boolean;\n resetting: boolean;\n };\n validationErrors: string[];\n setValidationErrors: (errors: string[]) => void;\n}\n\n/**\n * Manages mutation actions for the config tab: save (+ auto-reload),\n * refresh-from-source, restart, and reset.\n * Validation is field-level (live clientErrors) — no standalone validate button.\n * Reload-from-disk is auto-triggered after a successful save to apply hot fields.\n */\nexport function useConfigMutation(api: ConfigApi): UseConfigMutationResult {\n const [saving, setSaving] = useState(false);\n const [refreshingSource, setRefreshingSource] = useState(false);\n const [restarting, setRestarting] = useState(false);\n const [resetting, setResetting] = useState(false);\n const [validationErrors, setValidationErrors] = useState<string[]>([]);\n\n const handleSave = useCallback(async () => {\n if (!api.draft) return;\n if (!api.isDirty) {\n toast.info(\"Nothing to save\", { description: \"No changes since last load.\" });\n return;\n }\n setSaving(true);\n // Only send dirty keys — avoids clobbering fields the UI doesn't render.\n const patch: Record<string, unknown> = {};\n for (const k of api.dirtyKeys) patch[k] = api.draft[k];\n const r = await api.save(patch as RawConfig);\n if (!r) {\n setSaving(false);\n toast.error(\"Save failed\", { description: \"Server unreachable.\" });\n setValidationErrors([\"Server unreachable.\"]);\n return;\n }\n if (r.ok) {\n // Auto-reload from disk to apply hot fields live.\n const reloaded = await api.reloadFromDisk();\n setSaving(false);\n if (reloaded?.ok) {\n toast.success(\"Config saved\", {\n description: r.warnings.length\n ? `${r.warnings.length} warning(s)`\n : \"Saved and applied live.\",\n });\n } else {\n toast.warning(\"Config saved\", {\n description: \"Saved to disk, but live reload failed. Restart to apply.\",\n });\n }\n setValidationErrors([]);\n } else {\n setSaving(false);\n toast.error(\"Save rejected\", { description: r.errors.join(\"\\n\") });\n setValidationErrors(r.errors);\n }\n if (r.warnings.length) {\n for (const w of r.warnings) toast.warning(\"Warning\", { description: w });\n }\n }, [api]);\n\n const handleReset = useCallback(() => {\n api.resetDraft();\n toast.info(\"Draft reset\", { description: \"Reverted to last loaded config.\" });\n }, [api]);\n\n const handleResetToDefault = useCallback(async () => {\n setResetting(true);\n const r = await api.resetToDefault();\n setResetting(false);\n if (!r) {\n toast.error(\"Reset failed\", { description: \"Server unreachable.\" });\n return;\n }\n if (r.ok) {\n toast.success(\"Config reset to defaults\", {\n description: \"API key preserved. Limits refetched from upstream.\",\n });\n setValidationErrors([]);\n } else {\n toast.error(\"Reset failed\", { description: r.errors.join(\"\\n\") });\n }\n }, [api]);\n\n const handleRefreshSource = useCallback(async () => {\n setRefreshingSource(true);\n const r = await api.refreshFromSource();\n setRefreshingSource(false);\n if (!r) {\n toast.error(\"Refresh failed\", { description: \"Server unreachable.\" });\n return;\n }\n if (r.ok) {\n const parts = [`Hard cap set to ${r.hardCap}`, `soft limit set to ${r.softLimit}`];\n if (r.requestsHardCap !== null && r.requestsHardCap !== undefined) {\n parts.push(`rate limit hard cap set to ${r.requestsHardCap}`);\n } else {\n parts.push(\"rate limit: unlimited\");\n }\n toast.success(\"Limits reloaded from source\", {\n description: `${parts.join(\", \")}.`,\n });\n } else {\n toast.error(\"Refresh failed\", { description: r.error });\n }\n }, [api]);\n\n const handleRestart = useCallback(async () => {\n setRestarting(true);\n const r = await api.restart();\n if (!r) {\n toast.error(\"Restart failed\", { description: \"Server unreachable.\" });\n setRestarting(false);\n return;\n }\n if (r.ok) {\n toast.success(\"Restarting\", {\n description:\n r.message ??\n \"Server is restarting. Reconnect in a few seconds. Requires a process manager (bun --watch, systemd, pm2) to auto-restart.\",\n });\n // Reset button after 5s — if a process manager restarts the server,\n // the page will reconnect via WebSocket and reload anyway.\n setTimeout(() => setRestarting(false), 5000);\n } else {\n toast.error(\"Restart failed\", { description: r.error });\n setRestarting(false);\n }\n }, [api]);\n\n return {\n save: handleSave,\n refreshFromSource: handleRefreshSource,\n restart: handleRestart,\n reset: handleReset,\n resetToDefault: handleResetToDefault,\n loading: {\n saving,\n refreshingSource,\n restarting,\n resetting,\n },\n validationErrors,\n setValidationErrors,\n };\n}\n","import { usePollingResource } from \"@/hooks/use-polling-resource\";\nimport type { UsageSnapshot } from \"@/types\";\n\nconst POLL_INTERVAL = 30000;\n\nexport interface UseUsageResult {\n data: UsageSnapshot | null;\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\nexport function useUsage(): UseUsageResult {\n const parse = (value: unknown): UsageSnapshot | null =>\n value === undefined || value === null ? null : (value as UsageSnapshot);\n\n const { data, loading, error, refresh } = usePollingResource<UsageSnapshot | null>({\n endpoint: \"/dashboard/api/usage\",\n pollInterval: POLL_INTERVAL,\n errorMessage: \"Failed to fetch usage\",\n parse,\n });\n\n return { data, loading, error, refresh };\n}\n","import type { FieldDef } from \"@/components/config-sections\";\nimport type { RawConfig } from \"@/hooks/use-config\";\n\nexport interface FieldValidation {\n error?: string;\n warning?: string;\n}\n\nexport interface ConfigDraftValidation {\n errors: Record<string, string>;\n warnings: Record<string, string>;\n}\n\ntype FieldValidator = (def: FieldDef, value: unknown) => FieldValidation;\n\ntype FieldKind = FieldDef[\"kind\"];\n\nconst KIND_VALIDATORS: Record<FieldKind, FieldValidator> = {\n number: validateNumberField,\n text: validateTextField,\n password: validateTextField,\n json: validateJsonField,\n boolean: () => ({}),\n select: () => ({}),\n textarea: () => ({}),\n toggle: () => ({}),\n};\n\nfunction validateNumberField(def: FieldDef, value: unknown): FieldValidation {\n const strVal = value === null || value === undefined ? \"\" : String(value);\n const n = Number(strVal);\n if (strVal.length === 0) return {};\n if (!Number.isInteger(n)) {\n return { error: `${def.label} must be an integer` };\n }\n if (def.min !== undefined && n < def.min) {\n return { error: `${def.label} must be ≥ ${def.min}` };\n }\n if (def.max !== undefined && n > def.max) {\n return { error: `${def.label} must be ≤ ${def.max}` };\n }\n return {};\n}\n\nfunction validateTextField(def: FieldDef, value: unknown): FieldValidation {\n const strVal = value === null || value === undefined ? \"\" : String(value);\n if (def.pattern && strVal.length > 0) {\n const re = new RegExp(def.pattern);\n if (!re.test(strVal)) {\n return { error: `${def.label} must match ${def.patternHint ?? def.pattern}` };\n }\n }\n if (def.inputMode === \"url\" && strVal.length > 0) {\n try {\n const u = new URL(strVal);\n if (u.protocol !== \"http:\" && u.protocol !== \"https:\") {\n return { error: `${def.label} must use http: or https: protocol` };\n }\n } catch {\n return { error: `${def.label} is not a valid URL` };\n }\n }\n return {};\n}\n\nfunction validateJsonField(def: FieldDef, value: unknown): FieldValidation {\n if (value === null || value === undefined) return {};\n if (typeof value !== \"object\" || Array.isArray(value)) {\n return { error: `${def.label} must be a JSON object` };\n }\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (typeof v !== \"number\" || !Number.isFinite(v) || v <= 0) {\n return { error: `${def.label}: value for \"${k}\" must be a positive number` };\n }\n }\n return {};\n}\n\nconst FIELD_WARNINGS: Partial<Record<keyof RawConfig, (value: unknown) => string | null>> = {\n rate_limit_requests: (v) => {\n const n = Number(v);\n return n === -1\n ? \"Unlimited — no request cap is enforced. The upstream may still reject excessive traffic.\"\n : null;\n },\n};\n\nfunction validateField(def: FieldDef, value: unknown): FieldValidation {\n const strVal = value === null || value === undefined ? \"\" : String(value);\n\n if (def.required && !def.nullable && strVal.length === 0) {\n return { error: `${def.label} is required` };\n }\n\n if (def.nullable && strVal.length === 0) return {};\n\n const validator = KIND_VALIDATORS[def.kind];\n if (validator) {\n return validator(def, value);\n }\n\n return {};\n}\n\nexport function validateConfigDraft(\n draft: RawConfig,\n sections: { fields: FieldDef[] }[],\n): ConfigDraftValidation {\n const errors: Record<string, string> = {};\n const warnings: Record<string, string> = {};\n for (const section of sections) {\n for (const field of section.fields) {\n if (field.disabled) continue;\n const result = validateField(field, draft[field.key]);\n if (result.error) {\n errors[field.key as string] = result.error;\n }\n const warningFn = FIELD_WARNINGS[field.key];\n if (warningFn) {\n const w = warningFn(draft[field.key]);\n if (w) warnings[field.key as string] = w;\n }\n }\n }\n return { errors, warnings };\n}\n","import { AlertCircle, Power, RotateCcw, RotateCw } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\n\nimport { GroupBlock } from \"@/components/config-fields\";\nimport { GROUPS } from \"@/components/config-sections\";\nimport type { GroupDef } from \"@/components/config-sections\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Loader } from \"@/components/ui/loader\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport type { RawConfig } from \"@/hooks/use-config\";\nimport { useConfigContext } from \"@/hooks/use-config-context\";\nimport { useConfigDraft } from \"@/hooks/use-config-draft\";\nimport { useConfigMutation } from \"@/hooks/use-config-mutation\";\nimport { useModels } from \"@/hooks/use-models\";\nimport { useUsage } from \"@/hooks/use-usage\";\nimport { validateConfigDraft } from \"@/lib/config-validation\";\n\nexport function ConfigTab() {\n const {\n config,\n loading,\n error,\n reload,\n save,\n reloadFromDisk,\n refreshFromSource,\n restart,\n resetToDefault,\n } = useConfigContext();\n const { data: modelsData } = useModels();\n const { data: usage } = useUsage();\n\n const { draft, updateField, resetDraft, isDirty, dirtyKeys } = useConfigDraft(config);\n const {\n save: handleSave,\n refreshFromSource: handleRefreshSource,\n restart: handleRestart,\n reset: handleReset,\n resetToDefault: handleResetToDefault,\n loading: mutationLoading,\n validationErrors,\n setValidationErrors,\n } = useConfigMutation({\n draft,\n dirtyKeys,\n isDirty,\n resetDraft,\n save,\n reloadFromDisk,\n refreshFromSource,\n restart,\n resetToDefault,\n });\n\n const [resetDialogOpen, setResetDialogOpen] = useState(false);\n\n const groupsWithOverrides = useMemo<GroupDef[]>(() => {\n const visionModels = (modelsData?.models ?? [])\n .filter((m) => m.info?.capabilities.supports_vision === true)\n .map((m) => ({ value: m.id, label: m.id }))\n .sort((a, b) => a.label.localeCompare(b.label));\n\n const hardCap = usage?.concurrencyHardCap ?? undefined;\n const resMax = hardCap !== undefined ? hardCap - 2 : undefined;\n const rateMax = usage?.requestsHardCap ?? undefined;\n\n const fieldMaxOverride: Partial<Record<string, number | undefined>> = {};\n if (hardCap !== undefined) {\n fieldMaxOverride.concurrency_hard_cap = hardCap;\n fieldMaxOverride.concurrency_soft_limit = hardCap;\n fieldMaxOverride.concurrency_main_reservation = resMax;\n fieldMaxOverride.concurrency_vision_reservation = resMax;\n }\n if (rateMax !== undefined) {\n fieldMaxOverride.rate_limit_requests = rateMax;\n }\n\n return GROUPS.map((g) => ({\n ...g,\n sections: g.sections.map((s) => ({\n ...s,\n fields: s.fields.map((f) => {\n let patched = f;\n if (f.key === \"vision_model\") {\n patched = { ...patched, options: visionModels };\n }\n if (f.key in fieldMaxOverride) {\n patched = { ...patched, max: fieldMaxOverride[f.key] };\n }\n return patched;\n }),\n })),\n }));\n }, [modelsData, usage]);\n\n const { errors: clientErrors, warnings: clientWarnings } = useMemo(() => {\n if (!draft) return { errors: {}, warnings: {} };\n const allSections = groupsWithOverrides.flatMap((g) => g.sections);\n return validateConfigDraft(draft, allSections);\n }, [draft, groupsWithOverrides]);\n const hasClientErrors = Object.keys(clientErrors).length > 0;\n\n function onField(key: keyof RawConfig, v: unknown) {\n if (draft) updateField(key, v);\n if (validationErrors.length > 0) setValidationErrors([]);\n }\n\n if (loading && !draft) {\n return (\n <div className=\"flex h-full items-center justify-center\">\n <Loader />\n </div>\n );\n }\n\n if (error && !draft) {\n return (\n <div className=\"flex h-full flex-col items-center justify-center gap-3 p-8 text-center text-muted-foreground\">\n <AlertCircle className=\"h-12 w-12 text-destructive\" />\n <p className=\"text-sm font-medium text-destructive\">Something went wrong</p>\n <p className=\"text-xs\">{error}</p>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" onClick={reload}>\n <RotateCcw className=\"mr-1.5 h-3.5 w-3.5\" />\n Retry\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"top\">Retry loading config</TooltipContent>\n </Tooltip>\n </div>\n );\n }\n\n if (!draft) return null;\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"flex items-center justify-between border-b border-border bg-card px-4 py-3\">\n <h2 className=\"sr-only\">Configuration</h2>\n <div className=\"flex items-center gap-2\">\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"default\"\n disabled={mutationLoading.saving || !isDirty || hasClientErrors}\n onClick={handleSave}\n >\n {mutationLoading.saving ? \"Saving…\" : \"Save\"}\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n Save changes to disk and apply live fields automatically\n </TooltipContent>\n </Tooltip>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button size=\"sm\" variant=\"ghost\" disabled={!isDirty} onClick={handleReset}>\n Reset Draft\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">Discard unsaved changes</TooltipContent>\n </Tooltip>\n <AlertDialog open={resetDialogOpen} onOpenChange={setResetDialogOpen}>\n <Tooltip>\n <TooltipTrigger asChild>\n <AlertDialogTrigger\n render={\n <Button size=\"sm\" variant=\"outline\" disabled={mutationLoading.resetting}>\n <RotateCcw className=\"mr-1.5 h-3.5 w-3.5\" />\n {mutationLoading.resetting ? \"Resetting…\" : \"Reset to Default\"}\n </Button>\n }\n />\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n Reset all config fields to defaults (API key is preserved)\n </TooltipContent>\n </Tooltip>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Reset all config to defaults?</AlertDialogTitle>\n <AlertDialogDescription>\n This writes the default configuration to disk, replacing all current values. Your\n API key is preserved. A restart will be needed for some fields to take effect.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n variant=\"destructive\"\n onClick={async () => {\n setResetDialogOpen(false);\n await handleResetToDefault();\n }}\n >\n Reset to Default\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={mutationLoading.restarting}\n onClick={handleRestart}\n >\n <Power className=\"mr-1.5 h-3.5 w-3.5\" />\n {mutationLoading.restarting ? \"Restarting…\" : \"Restart\"}\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" className=\"max-w-[260px]\">\n Restart the server. Requires an external process manager (bun --watch, systemd, pm2).\n </TooltipContent>\n </Tooltip>\n </div>\n {isDirty ? (\n <span className=\"text-xs text-muted-foreground\">\n {dirtyKeys.size} unsaved change{dirtyKeys.size > 1 ? \"s\" : \"\"}\n </span>\n ) : (\n <span className=\"text-xs text-muted-foreground\">All changes saved</span>\n )}\n </header>\n {validationErrors.length > 0 && (\n <div\n role=\"alert\"\n aria-live=\"polite\"\n className=\"border-b border-destructive/30 bg-destructive/5 px-4 py-2\"\n >\n <p className=\"text-xs font-medium text-destructive\">\n {validationErrors.length === 1 ? \"1 issue\" : `${validationErrors.length} issues`}\n </p>\n <ul className=\"mt-1 space-y-0.5\">\n {validationErrors.map((e) => (\n <li key={e} className=\"text-xs text-destructive/80\">\n {e}\n </li>\n ))}\n </ul>\n </div>\n )}\n <ScrollArea className=\"min-h-0 flex-1\">\n <div className=\"mx-auto w-full max-w-3xl px-6 py-6\">\n {groupsWithOverrides.map((g, gi) => (\n <GroupBlock\n key={g.title}\n group={g}\n values={draft}\n originals={config ?? {}}\n onField={onField}\n errors={clientErrors}\n warnings={clientWarnings}\n isLast={gi === groupsWithOverrides.length - 1}\n onRefreshSource={handleRefreshSource}\n refreshingSource={mutationLoading.refreshingSource}\n />\n ))}\n <div className=\"pt-6 text-xs text-muted-foreground\">\n Fields marked{\" \"}\n <span className=\"text-muted-foreground\">\n <RotateCw className=\"inline h-3 w-3 mr-1\" aria-hidden />\n restart\n </span>{\" \"}\n require a server restart to take effect. Other fields are applied live when you save.\n </div>\n </div>\n </ScrollArea>\n </div>\n );\n}\n"],"names":["NumberInput","className","min","max","suffix","onChange","value","disabled","props","numVal","step","delta","current","next","jsxs","Tooltip","jsx","TooltipTrigger","Button","Minus","TooltipContent","Input","cn","e","Plus","Separator","orientation","SeparatorPrimitive","Switch","size","SwitchPrimitive.Root","SwitchPrimitive.Thumb","Textarea","valueToString","v","nullable","serializeJson","BooleanRenderer","id","c","ToggleRenderer","SelectRenderer","def","isInvalid","Select","SelectTrigger","SelectValue","SelectContent","o","SelectItem","TextareaRenderer","placeholder","PasswordRenderer","PasswordInput","NumberRenderer","JsonRenderer","JsonFieldRow","TextRenderer","FIELD_RENDERERS","text","setText","useState","parseError","setParseError","lastValueRef","useRef","useEffect","handleChange","newText","fallback","parsed","Fragment","FieldRow","dirty","error","warning","onRefreshSource","refreshing","hasError","Label","Badge","RotateCw","badgeWarning","Beaker","badgeInfo","Cloud","Download","SectionBlock","section","values","originals","onField","errors","warnings","isLast","refreshingSource","f","GroupBlock","group","s","i","VISION_GENERAL_FIELDS","VISION_TUNING_FIELDS","SERVER_FIELDS","STAMP_FIELDS","WARMER_FIELDS","CONCURRENCY_GATE_FIELDS","CIRCUIT_BREAKER_FIELDS","QUEUE_FIELDS","CAPTURE_STORAGE_FIELDS","CREDENTIALS_FIELDS","GROUPS","g","useConfigDraft","initialConfig","draft","setDraft","dirtyKeys","useMemo","keys","k","resetDraft","updateField","key","prev","useConfigMutation","api","saving","setSaving","setRefreshingSource","restarting","setRestarting","resetting","setResetting","validationErrors","setValidationErrors","handleSave","useCallback","toast","patch","r","reloaded","w","handleReset","handleResetToDefault","handleRefreshSource","parts","handleRestart","POLL_INTERVAL","useUsage","parse","data","loading","refresh","usePollingResource","KIND_VALIDATORS","validateNumberField","validateTextField","validateJsonField","strVal","n","u","FIELD_WARNINGS","validateField","validator","validateConfigDraft","sections","field","result","warningFn","ConfigTab","config","reload","save","reloadFromDisk","refreshFromSource","restart","resetToDefault","useConfigContext","modelsData","useModels","usage","isDirty","mutationLoading","resetDialogOpen","setResetDialogOpen","groupsWithOverrides","visionModels","m","_a","a","b","hardCap","resMax","rateMax","fieldMaxOverride","patched","clientErrors","clientWarnings","allSections","hasClientErrors","Loader","AlertCircle","RotateCcw","AlertDialog","AlertDialogTrigger","AlertDialogContent","AlertDialogHeader","AlertDialogTitle","AlertDialogDescription","AlertDialogFooter","AlertDialogCancel","AlertDialogAction","Power","ScrollArea","gi"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{j as e,r as c}from"./vendor-react-Dtud3GyP.js";import{c as N,A as D,T as E,a as R,B as $,h as U,b as A,S as J,g as K,C,i as _,d as M,j as b}from"./index-72wCsW4_.js";import{S as L,a as F,b as I,c as P,d as q}from"./select-mZPf2BR3.js";import{u as W}from"./use-capture-done-listener-u5RxfYQP.js";import{a6 as Y,W as G,H as Q,af as X,ab as Z,ag as ee,ah as se}from"./vendor-CVKsgXJU.js";function z({className:t,...s}){return e.jsx("div",{"data-slot":"table-container",className:"relative w-full overflow-x-auto",children:e.jsx("table",{"data-slot":"table",className:N("w-full caption-bottom text-sm",t),...s})})}function O({className:t,...s}){return e.jsx("thead",{"data-slot":"table-header",className:N("[&_tr]:border-b",t),...s})}function B({className:t,...s}){return e.jsx("tbody",{"data-slot":"table-body",className:N("[&_tr:last-child]:border-0",t),...s})}function k({className:t,...s}){return e.jsx("tr",{"data-slot":"table-row",className:N("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",t),...s})}function j({className:t,...s}){return e.jsx("th",{"data-slot":"table-head",className:N("h-9 px-2 text-left align-middle text-xs font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",t),...s})}function d({className:t,...s}){return e.jsx("td",{"data-slot":"table-cell",className:N("px-2 py-1.5 align-middle [&:has([role=checkbox])]:pr-0",t),...s})}const te=3e4;function ae(t,s){const[p,o]=c.useState(null),[f,i]=c.useState(null),[y,r]=c.useState(!0),[x,g]=c.useState(null),l=c.useCallback(async a=>{try{const[n,u]=await Promise.all([fetch(`${D}/economics/summary?year=${t}&month=${s}`,{signal:a}),fetch(`${D}/economics/daily?limit=90`,{signal:a})]);if(!n.ok)throw new Error(`HTTP ${n.status}`);if(!u.ok)throw new Error(`HTTP ${u.status}`);const H=await n.json(),V=await u.json();o(H),i(V),g(null)}catch(n){if(n instanceof DOMException&&n.name==="AbortError")return;g(n instanceof Error?n.message:"Failed to fetch economics data")}finally{r(!1)}},[t,s]),h=c.useCallback(()=>{const a=new AbortController;return r(!0),g(null),l(a.signal),()=>a.abort()},[l]),m=c.useRef(h);return m.current=h,W(()=>m.current()),c.useEffect(()=>{const a=new AbortController;l(a.signal);const n=setInterval(()=>l(a.signal),te),u=()=>{document.hidden||l(a.signal)};return document.addEventListener("visibilitychange",u),()=>{a.abort(),clearInterval(n),document.removeEventListener("visibilitychange",u)}},[l]),{summary:p,daily:f,loading:y,error:x,refresh:h}}const w=["January","February","March","April","May","June","July","August","September","October","November","December"];function v(t){return t===0?"$0.00":t<.01?"<$0.01":`$${t.toFixed(2)}`}function T(t){return t===0?"$0.0000":`$${t.toFixed(4)}`}const re=[{key:"date",label:"Date"},{key:"model",label:"Model"},{key:"requests",label:"Requests"}];function xe(){const t=new Date,[s,p]=c.useState(t.getFullYear()),[o,f]=c.useState(t.getMonth()+1),{summary:i,daily:y,loading:r,error:x,refresh:g}=ae(s,o),l=(i==null?void 0:i.months)??[],h=c.useMemo(()=>{const a=new Set(l.map(n=>n.year));return a.add(t.getFullYear()),[...a].sort((n,u)=>u-n)},[l,t]),m=c.useMemo(()=>{const a=l.filter(n=>n.year===s).map(n=>n.month).sort((n,u)=>n-u);return!a.includes(t.getMonth()+1)&&s===t.getFullYear()&&a.push(t.getMonth()+1),a},[l,s,t]);return e.jsxs("div",{className:"flex h-full flex-col",children:[e.jsxs("header",{className:"flex items-center justify-between border-b border-border bg-card px-4 py-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Economics"}),i&&e.jsxs("span",{className:"text-xs text-muted-foreground",children:[w[i.summary.month-1]," ",i.summary.year]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(L,{value:String(s),onValueChange:a=>p(Number(a)),children:[e.jsx(F,{size:"sm",className:"w-[90px]",children:e.jsx(I,{placeholder:"Year"})}),e.jsx(P,{children:h.map(a=>e.jsx(q,{value:String(a),children:a},a))})]}),e.jsxs(L,{value:String(o),onValueChange:a=>f(Number(a)),children:[e.jsx(F,{size:"sm",className:"w-[120px]",children:e.jsx(I,{placeholder:"Month",children:a=>w[Number(a)-1]})}),e.jsx(P,{children:(m.length>0?m:Array.from({length:12},(a,n)=>n+1)).map(a=>e.jsx(q,{value:String(a),children:w[a-1]},a))})]}),e.jsxs(E,{children:[e.jsx(R,{asChild:!0,children:e.jsxs($,{variant:"outline",size:"sm",onClick:g,disabled:r,children:[r?e.jsx(U,{className:"mr-1.5 size-3.5"}):e.jsx(Y,{className:"mr-1.5 h-3.5 w-3.5"}),"Refresh"]})}),e.jsx(A,{side:"bottom",children:"Reload economics data"})]})]})]}),e.jsx(J,{className:"min-h-0 flex-1",children:e.jsx("div",{className:"mx-auto flex min-h-full w-full max-w-7xl flex-col p-4",children:r&&!i?e.jsx("div",{className:"flex flex-1 items-center justify-center",children:e.jsx(K,{className:"h-auto"})}):i===null?e.jsxs("div",{className:"flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center text-muted-foreground",children:[e.jsx(G,{className:"h-12 w-12 text-muted-foreground/40"}),e.jsx("p",{className:"text-sm font-medium",children:x?"Something went wrong":"No economics data yet"}),x?e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"text-xs",children:x}),e.jsxs(E,{children:[e.jsx(R,{asChild:!0,children:e.jsx($,{variant:"outline",size:"sm",onClick:g,children:"Retry"})}),e.jsx(A,{side:"top",children:"Retry loading economics data"})]})]}):e.jsx("p",{className:"text-xs",children:"Send requests through the proxy to accumulate daily usage."})]}):e.jsxs("div",{className:"space-y-4",children:[x&&e.jsxs("div",{className:"flex items-center gap-2 rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2 text-xs text-destructive",children:[e.jsx(Q,{className:"h-3.5 w-3.5"}),e.jsx("span",{className:"font-medium",children:"Failed to refresh:"}),e.jsx("span",{className:"text-muted-foreground",children:x})]}),e.jsx(ne,{summary:i}),e.jsx(le,{daily:y}),e.jsx(ce,{pricing:i.pricing})]})})}),e.jsx("footer",{className:"border-t border-border bg-green-50 dark:bg-green-800 px-4 py-2 text-center",children:e.jsx("p",{className:"text-xs font-bold text-green-900 dark:text-green-100",children:"Built with gratitude for everyone leveraging Umans"})})]})}function ne({summary:t}){const s=t.summary;return e.jsx(C,{children:e.jsxs(_,{className:"px-4 py-0",children:[e.jsxs("div",{className:"mb-3 flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("h3",{className:"text-sm font-semibold",children:[w[s.month-1]," ",s.year," Summary"]}),e.jsxs("p",{className:"mt-0.5 text-xs text-muted-foreground",children:[s.requests.toLocaleString()," request",s.requests===1?"":"s"]})]}),s.has_unpriced&&e.jsxs(E,{children:[e.jsx(R,{render:e.jsx("span",{className:"shrink-0 inline-flex"}),children:e.jsx(M,{variant:"outline",children:"est."})}),e.jsx(A,{side:"left",className:"max-w-[240px]",children:"Some models lacked pricing at capture time. Costs shown are estimates and may update once pricing syncs."})]})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4",children:[e.jsx(S,{icon:e.jsx(X,{className:"h-3.5 w-3.5"}),label:"Input",primary:b(s.input_tokens),sub:v(s.cost_input)}),e.jsx(S,{icon:e.jsx(Z,{className:"h-3.5 w-3.5"}),label:"Output",primary:b(s.output_tokens),sub:v(s.cost_output)}),e.jsx(S,{icon:e.jsx(Y,{className:"h-3.5 w-3.5"}),label:"Cache Read",primary:b(s.cache_read_tokens),sub:v(s.cost_cache_read)}),e.jsx(S,{icon:e.jsx(ee,{className:"h-3.5 w-3.5"}),label:"Total Cost",primary:v(s.cost_total),sub:`${s.requests.toLocaleString()} reqs`,highlight:!0})]})]})})}function le({daily:t}){const[s,p]=c.useState("date"),[o,f]=c.useState("desc"),i=c.useMemo(()=>{if(!t)return[];const r=[...t];return r.sort((x,g)=>{const l=x[s],h=g[s];let m;return typeof l=="number"&&typeof h=="number"?m=l-h:m=String(l??"").localeCompare(String(h??"")),o==="asc"?m:-m}),r},[t,s,o]),y=r=>{s===r?f(o==="asc"?"desc":"asc"):(p(r),f("desc"))};return!t||t.length===0?e.jsx(C,{children:e.jsx(_,{className:"py-8 text-center text-xs text-muted-foreground",children:"No daily usage records yet."})}):e.jsx(C,{children:e.jsxs(_,{className:"px-4 py-0",children:[e.jsx("h3",{className:"py-3 text-sm font-semibold",children:"Daily Usage"}),e.jsxs(z,{className:"text-xs",children:[e.jsx(O,{children:e.jsxs(k,{className:"text-left text-muted-foreground hover:bg-transparent",children:[re.map(r=>e.jsx(j,{children:e.jsxs($,{variant:"ghost",size:"sm",onClick:()=>y(r.key),className:"h-auto px-0 font-medium text-muted-foreground hover:text-foreground",children:[r.label,s===r.key&&e.jsx(se,{className:"h-3 w-3",style:{transform:o==="asc"?"scaleY(-1)":void 0}})]})},r.key)),e.jsx(j,{children:"Input"}),e.jsx(j,{children:"Output"}),e.jsx(j,{children:"Cache Read"}),e.jsx(j,{className:"text-right",children:"Cost"})]})}),e.jsx(B,{children:i.map(r=>e.jsxs(k,{children:[e.jsx(d,{className:"tabular-nums",children:r.date}),e.jsx(d,{className:"max-w-[180px] truncate",title:r.model,children:r.model}),e.jsx(d,{className:"tabular-nums",children:r.requests.toLocaleString()}),e.jsx(d,{className:"tabular-nums",children:b(r.input_tokens)}),e.jsx(d,{className:"tabular-nums",children:b(r.output_tokens)}),e.jsx(d,{className:"tabular-nums",children:b(r.cache_read_tokens)}),e.jsxs(d,{className:"text-right tabular-nums",children:[v(r.cost_total),r.pricing_known===0&&e.jsx(M,{variant:"outline",size:"sm",className:"ml-1",children:"est."})]})]},`${r.date}-${r.model}`))})]})]})})}function ce({pricing:t}){return t.length===0?null:e.jsx(C,{children:e.jsxs(_,{className:"px-4 py-0",children:[e.jsx("h3",{className:"py-3 text-sm font-semibold",children:"Pricing Reference"}),e.jsxs(z,{className:"text-xs",children:[e.jsx(O,{children:e.jsxs(k,{className:"text-left text-muted-foreground hover:bg-transparent",children:[e.jsx(j,{children:"Model"}),e.jsx(j,{className:"text-right",children:"Input"}),e.jsx(j,{className:"text-right",children:"Output"}),e.jsx(j,{className:"text-right",children:"Cache Read"})]})}),e.jsx(B,{children:t.map(s=>e.jsxs(k,{children:[e.jsxs(d,{className:"max-w-[200px] truncate",title:s.model_id,children:[s.model_id,s.pricing_known===0&&e.jsx(M,{variant:"outline",size:"sm",className:"ml-1",children:"est."})]}),e.jsx(d,{className:"text-right tabular-nums",children:T(s.input_per_mtoken)}),e.jsx(d,{className:"text-right tabular-nums",children:T(s.output_per_mtoken)}),e.jsx(d,{className:"text-right tabular-nums",children:T(s.cache_read_per_mtoken)})]},s.model_id))})]}),e.jsx("p",{className:"pb-3 text-[0.6875rem] text-muted-foreground",children:"Prices per million tokens. Cache read is estimated at ~18.6% of input price when not provided by the API."})]})})}function S({icon:t,label:s,primary:p,sub:o,highlight:f}){return e.jsxs("div",{className:`rounded-lg border px-3 py-2 ${f?"border-primary/30 bg-primary/5":"border-border bg-muted/30"}`,children:[e.jsxs("div",{className:"flex items-center gap-1 text-xs font-medium text-muted-foreground",children:[t,s]}),e.jsx("div",{className:"mt-1 flex items-baseline gap-1.5",children:e.jsx("span",{className:"text-lg font-semibold tabular-nums leading-tight",children:p})}),o&&e.jsx("div",{className:"mt-0.5 text-xs text-muted-foreground tabular-nums",children:o})]})}export{xe as EconomicsTab};
2
- //# sourceMappingURL=economics-tab-BJXa5ydD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"economics-tab-BJXa5ydD.js","sources":["../../src/components/ui/table.tsx","../../src/hooks/use-economics.ts","../../src/components/economics-tab.tsx"],"sourcesContent":["import type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Table({ className, ...props }: React.ComponentProps<\"table\">) {\n return (\n <div data-slot=\"table-container\" className=\"relative w-full overflow-x-auto\">\n <table\n data-slot=\"table\"\n className={cn(\"w-full caption-bottom text-sm\", className)}\n {...props}\n />\n </div>\n );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<\"thead\">) {\n return <thead data-slot=\"table-header\" className={cn(\"[&_tr]:border-b\", className)} {...props} />;\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<\"tbody\">) {\n return (\n <tbody\n data-slot=\"table-body\"\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<\"tfoot\">) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn(\"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\", className)}\n {...props}\n />\n );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<\"tr\">) {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n \"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<\"th\">) {\n return (\n <th\n data-slot=\"table-head\"\n className={cn(\n \"h-9 px-2 text-left align-middle text-xs font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<\"td\">) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\"px-2 py-1.5 align-middle [&:has([role=checkbox])]:pr-0\", className)}\n {...props}\n />\n );\n}\n\nfunction TableCaption({ className, ...props }: React.ComponentProps<\"caption\">) {\n return (\n <caption\n data-slot=\"table-caption\"\n className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useCaptureDoneListener } from \"@/hooks/use-capture-done-listener\";\nimport { API_BASE } from \"@/lib/constants\";\nimport type { EconomicsDailyRow, EconomicsSummaryResponse } from \"@/types\";\n\nconst POLL_INTERVAL = 30000;\n\nexport interface UseEconomicsResult {\n summary: EconomicsSummaryResponse | null;\n daily: EconomicsDailyRow[] | null;\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\nexport function useEconomics(year: number, month: number): UseEconomicsResult {\n const [summary, setSummary] = useState<EconomicsSummaryResponse | null>(null);\n const [daily, setDaily] = useState<EconomicsDailyRow[] | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const fetchData = useCallback(\n async (signal: AbortSignal) => {\n try {\n const [summaryRes, dailyRes] = await Promise.all([\n fetch(`${API_BASE}/economics/summary?year=${year}&month=${month}`, { signal }),\n fetch(`${API_BASE}/economics/daily?limit=90`, { signal }),\n ]);\n if (!summaryRes.ok) throw new Error(`HTTP ${summaryRes.status}`);\n if (!dailyRes.ok) throw new Error(`HTTP ${dailyRes.status}`);\n const summaryData = (await summaryRes.json()) as EconomicsSummaryResponse;\n const dailyData = (await dailyRes.json()) as EconomicsDailyRow[];\n setSummary(summaryData);\n setDaily(dailyData);\n setError(null);\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setError(err instanceof Error ? err.message : \"Failed to fetch economics data\");\n } finally {\n setLoading(false);\n }\n },\n [year, month],\n );\n\n const refresh = useCallback(() => {\n const controller = new AbortController();\n setLoading(true);\n setError(null);\n void fetchData(controller.signal);\n return () => controller.abort();\n }, [fetchData]);\n\n const refreshRef = useRef(refresh);\n refreshRef.current = refresh;\n useCaptureDoneListener(() => refreshRef.current());\n\n useEffect(() => {\n const controller = new AbortController();\n void fetchData(controller.signal);\n\n const interval = setInterval(() => fetchData(controller.signal), POLL_INTERVAL);\n const handleVisibilityChange = () => {\n if (!document.hidden) {\n void fetchData(controller.signal);\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n controller.abort();\n clearInterval(interval);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [fetchData]);\n\n return { summary, daily, loading, error, refresh };\n}\n","import {\n Activity,\n AlertCircle,\n ArrowDownUp,\n DollarSign,\n Inbox,\n RefreshCw,\n TrendingUp,\n} from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Loader } from \"@/components/ui/loader\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/ui/table\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { useEconomics } from \"@/hooks/use-economics\";\nimport { fmtTokensCompact } from \"@/lib/format\";\nimport type { EconomicsDailyRow, EconomicsSummaryResponse, ModelPricingRow } from \"@/types\";\n\nconst MONTH_NAMES = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\nfunction fmtCost(n: number): string {\n if (n === 0) return \"$0.00\";\n if (n < 0.01) return \"<$0.01\";\n return `$${n.toFixed(2)}`;\n}\n\nfunction fmtCostPrecise(n: number): string {\n if (n === 0) return \"$0.0000\";\n return `$${n.toFixed(4)}`;\n}\n\ninterface SortKey {\n key: keyof EconomicsDailyRow;\n label: string;\n}\n\nconst SORT_KEYS: SortKey[] = [\n { key: \"date\", label: \"Date\" },\n { key: \"model\", label: \"Model\" },\n { key: \"requests\", label: \"Requests\" },\n];\n\nexport function EconomicsTab() {\n const now = new Date();\n const [year, setYear] = useState(now.getFullYear());\n const [month, setMonth] = useState(now.getMonth() + 1);\n const { summary, daily, loading, error, refresh } = useEconomics(year, month);\n\n const availableMonths = summary?.months ?? [];\n const availableYears = useMemo(() => {\n const years = new Set(availableMonths.map((m) => m.year));\n years.add(now.getFullYear());\n return [...years].sort((a, b) => b - a);\n }, [availableMonths, now]);\n\n // Available months for the currently selected year\n const monthsForYear = useMemo(() => {\n const months = availableMonths\n .filter((m) => m.year === year)\n .map((m) => m.month)\n .sort((a, b) => a - b);\n if (!months.includes(now.getMonth() + 1) && year === now.getFullYear()) {\n months.push(now.getMonth() + 1);\n }\n return months;\n }, [availableMonths, year, now]);\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"flex items-center justify-between border-b border-border bg-card px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-sm font-semibold\">Economics</h2>\n {summary && (\n <span className=\"text-xs text-muted-foreground\">\n {MONTH_NAMES[summary.summary.month - 1]} {summary.summary.year}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <Select value={String(year)} onValueChange={(v) => setYear(Number(v))}>\n <SelectTrigger size=\"sm\" className=\"w-[90px]\">\n <SelectValue placeholder=\"Year\" />\n </SelectTrigger>\n <SelectContent>\n {availableYears.map((y) => (\n <SelectItem key={y} value={String(y)}>\n {y}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select value={String(month)} onValueChange={(v) => setMonth(Number(v))}>\n <SelectTrigger size=\"sm\" className=\"w-[120px]\">\n <SelectValue placeholder=\"Month\">\n {(value: string) => MONTH_NAMES[Number(value) - 1]}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n {(monthsForYear.length > 0\n ? monthsForYear\n : Array.from({ length: 12 }, (_, i) => i + 1)\n ).map((m) => (\n <SelectItem key={m} value={String(m)}>\n {MONTH_NAMES[m - 1]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" onClick={refresh} disabled={loading}>\n {loading ? (\n <Spinner className=\"mr-1.5 size-3.5\" />\n ) : (\n <RefreshCw className=\"mr-1.5 h-3.5 w-3.5\" />\n )}\n Refresh\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">Reload economics data</TooltipContent>\n </Tooltip>\n </div>\n </header>\n\n <ScrollArea className=\"min-h-0 flex-1\">\n <div className=\"mx-auto flex min-h-full w-full max-w-7xl flex-col p-4\">\n {loading && !summary ? (\n <div className=\"flex flex-1 items-center justify-center\">\n <Loader className=\"h-auto\" />\n </div>\n ) : summary === null ? (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center text-muted-foreground\">\n <Inbox className=\"h-12 w-12 text-muted-foreground/40\" />\n <p className=\"text-sm font-medium\">\n {error ? \"Something went wrong\" : \"No economics data yet\"}\n </p>\n {error ? (\n <>\n <p className=\"text-xs\">{error}</p>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" onClick={refresh}>\n Retry\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"top\">Retry loading economics data</TooltipContent>\n </Tooltip>\n </>\n ) : (\n <p className=\"text-xs\">\n Send requests through the proxy to accumulate daily usage.\n </p>\n )}\n </div>\n ) : (\n <div className=\"space-y-4\">\n {error && (\n <div className=\"flex items-center gap-2 rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n <AlertCircle className=\"h-3.5 w-3.5\" />\n <span className=\"font-medium\">Failed to refresh:</span>\n <span className=\"text-muted-foreground\">{error}</span>\n </div>\n )}\n\n <SummaryCard summary={summary} />\n <DailyTable daily={daily} />\n <PricingCard pricing={summary.pricing} />\n </div>\n )}\n </div>\n </ScrollArea>\n\n <footer className=\"border-t border-border bg-green-50 dark:bg-green-800 px-4 py-2 text-center\">\n <p className=\"text-xs font-bold text-green-900 dark:text-green-100\">\n Built with gratitude for everyone leveraging Umans\n </p>\n </footer>\n </div>\n );\n}\n\nfunction SummaryCard({ summary }: { summary: EconomicsSummaryResponse }) {\n const s = summary.summary;\n return (\n <Card>\n <CardContent className=\"px-4 py-0\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold\">\n {MONTH_NAMES[s.month - 1]} {s.year} Summary\n </h3>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n {s.requests.toLocaleString()} request{s.requests === 1 ? \"\" : \"s\"}\n </p>\n </div>\n {s.has_unpriced && (\n <Tooltip>\n <TooltipTrigger render={<span className=\"shrink-0 inline-flex\" />}>\n <Badge variant=\"outline\">est.</Badge>\n </TooltipTrigger>\n <TooltipContent side=\"left\" className=\"max-w-[240px]\">\n Some models lacked pricing at capture time. Costs shown are estimates and may update\n once pricing syncs.\n </TooltipContent>\n </Tooltip>\n )}\n </div>\n\n <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4\">\n <StatTile\n icon={<TrendingUp className=\"h-3.5 w-3.5\" />}\n label=\"Input\"\n primary={fmtTokensCompact(s.input_tokens)}\n sub={fmtCost(s.cost_input)}\n />\n <StatTile\n icon={<Activity className=\"h-3.5 w-3.5\" />}\n label=\"Output\"\n primary={fmtTokensCompact(s.output_tokens)}\n sub={fmtCost(s.cost_output)}\n />\n <StatTile\n icon={<RefreshCw className=\"h-3.5 w-3.5\" />}\n label=\"Cache Read\"\n primary={fmtTokensCompact(s.cache_read_tokens)}\n sub={fmtCost(s.cost_cache_read)}\n />\n <StatTile\n icon={<DollarSign className=\"h-3.5 w-3.5\" />}\n label=\"Total Cost\"\n primary={fmtCost(s.cost_total)}\n sub={`${s.requests.toLocaleString()} reqs`}\n highlight\n />\n </div>\n </CardContent>\n </Card>\n );\n}\n\nfunction DailyTable({ daily }: { daily: EconomicsDailyRow[] | null }) {\n const [sortKey, setSortKey] = useState<keyof EconomicsDailyRow>(\"date\");\n const [sortDir, setSortDir] = useState<\"asc\" | \"desc\">(\"desc\");\n\n const sorted = useMemo(() => {\n if (!daily) return [];\n const rows = [...daily];\n rows.sort((a, b) => {\n const av = a[sortKey];\n const bv = b[sortKey];\n let cmp: number;\n if (typeof av === \"number\" && typeof bv === \"number\") {\n cmp = av - bv;\n } else {\n cmp = String(av ?? \"\").localeCompare(String(bv ?? \"\"));\n }\n return sortDir === \"asc\" ? cmp : -cmp;\n });\n return rows;\n }, [daily, sortKey, sortDir]);\n\n const toggleSort = (key: keyof EconomicsDailyRow) => {\n if (sortKey === key) {\n setSortDir(sortDir === \"asc\" ? \"desc\" : \"asc\");\n } else {\n setSortKey(key);\n setSortDir(\"desc\");\n }\n };\n\n if (!daily || daily.length === 0) {\n return (\n <Card>\n <CardContent className=\"py-8 text-center text-xs text-muted-foreground\">\n No daily usage records yet.\n </CardContent>\n </Card>\n );\n }\n\n return (\n <Card>\n <CardContent className=\"px-4 py-0\">\n <h3 className=\"py-3 text-sm font-semibold\">Daily Usage</h3>\n <Table className=\"text-xs\">\n <TableHeader>\n <TableRow className=\"text-left text-muted-foreground hover:bg-transparent\">\n {SORT_KEYS.map((col) => (\n <TableHead key={col.key}>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => toggleSort(col.key)}\n className=\"h-auto px-0 font-medium text-muted-foreground hover:text-foreground\"\n >\n {col.label}\n {sortKey === col.key && (\n <ArrowDownUp\n className=\"h-3 w-3\"\n style={{ transform: sortDir === \"asc\" ? \"scaleY(-1)\" : undefined }}\n />\n )}\n </Button>\n </TableHead>\n ))}\n <TableHead>Input</TableHead>\n <TableHead>Output</TableHead>\n <TableHead>Cache Read</TableHead>\n <TableHead className=\"text-right\">Cost</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sorted.map((row) => (\n <TableRow key={`${row.date}-${row.model}`}>\n <TableCell className=\"tabular-nums\">{row.date}</TableCell>\n <TableCell className=\"max-w-[180px] truncate\" title={row.model}>\n {row.model}\n </TableCell>\n <TableCell className=\"tabular-nums\">{row.requests.toLocaleString()}</TableCell>\n <TableCell className=\"tabular-nums\">{fmtTokensCompact(row.input_tokens)}</TableCell>\n <TableCell className=\"tabular-nums\">\n {fmtTokensCompact(row.output_tokens)}\n </TableCell>\n <TableCell className=\"tabular-nums\">\n {fmtTokensCompact(row.cache_read_tokens)}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {fmtCost(row.cost_total)}\n {row.pricing_known === 0 && (\n <Badge variant=\"outline\" size=\"sm\" className=\"ml-1\">\n est.\n </Badge>\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </CardContent>\n </Card>\n );\n}\n\nfunction PricingCard({ pricing }: { pricing: ModelPricingRow[] }) {\n if (pricing.length === 0) return null;\n return (\n <Card>\n <CardContent className=\"px-4 py-0\">\n <h3 className=\"py-3 text-sm font-semibold\">Pricing Reference</h3>\n <Table className=\"text-xs\">\n <TableHeader>\n <TableRow className=\"text-left text-muted-foreground hover:bg-transparent\">\n <TableHead>Model</TableHead>\n <TableHead className=\"text-right\">Input</TableHead>\n <TableHead className=\"text-right\">Output</TableHead>\n <TableHead className=\"text-right\">Cache Read</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {pricing.map((p) => (\n <TableRow key={p.model_id}>\n <TableCell className=\"max-w-[200px] truncate\" title={p.model_id}>\n {p.model_id}\n {p.pricing_known === 0 && (\n <Badge variant=\"outline\" size=\"sm\" className=\"ml-1\">\n est.\n </Badge>\n )}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {fmtCostPrecise(p.input_per_mtoken)}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {fmtCostPrecise(p.output_per_mtoken)}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {fmtCostPrecise(p.cache_read_per_mtoken)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n <p className=\"pb-3 text-[0.6875rem] text-muted-foreground\">\n Prices per million tokens. Cache read is estimated at ~18.6% of input price when not\n provided by the API.\n </p>\n </CardContent>\n </Card>\n );\n}\n\nfunction StatTile({\n icon,\n label,\n primary,\n sub,\n highlight,\n}: {\n icon: React.ReactNode;\n label: string;\n primary: string;\n sub?: string;\n highlight?: boolean;\n}) {\n return (\n <div\n className={`rounded-lg border px-3 py-2 ${highlight ? \"border-primary/30 bg-primary/5\" : \"border-border bg-muted/30\"}`}\n >\n <div className=\"flex items-center gap-1 text-xs font-medium text-muted-foreground\">\n {icon}\n {label}\n </div>\n <div className=\"mt-1 flex items-baseline gap-1.5\">\n <span className=\"text-lg font-semibold tabular-nums leading-tight\">{primary}</span>\n </div>\n {sub && <div className=\"mt-0.5 text-xs text-muted-foreground tabular-nums\">{sub}</div>}\n </div>\n );\n}\n"],"names":["Table","className","props","jsx","cn","TableHeader","TableBody","TableRow","TableHead","TableCell","POLL_INTERVAL","useEconomics","year","month","summary","setSummary","useState","daily","setDaily","loading","setLoading","error","setError","fetchData","useCallback","signal","summaryRes","dailyRes","API_BASE","summaryData","dailyData","err","refresh","controller","refreshRef","useRef","useCaptureDoneListener","useEffect","interval","handleVisibilityChange","MONTH_NAMES","fmtCost","n","fmtCostPrecise","SORT_KEYS","EconomicsTab","now","setYear","setMonth","availableMonths","availableYears","useMemo","years","m","a","b","monthsForYear","months","jsxs","Select","v","SelectTrigger","SelectValue","SelectContent","y","SelectItem","value","_","i","Tooltip","TooltipTrigger","Button","Spinner","RefreshCw","TooltipContent","ScrollArea","Loader","Inbox","Fragment","AlertCircle","SummaryCard","DailyTable","PricingCard","Card","CardContent","Badge","StatTile","TrendingUp","fmtTokensCompact","Activity","DollarSign","sortKey","setSortKey","sortDir","setSortDir","sorted","rows","av","bv","cmp","toggleSort","key","col","ArrowDownUp","row","pricing","p","icon","label","primary","sub","highlight"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{j as e}from"./vendor-react-Dtud3GyP.js";import{s as n}from"./index-72wCsW4_.js";import"./vendor-CVKsgXJU.js";function c({headers:a}){const r=n(a),t=Object.keys(r);return t.length===0?e.jsx("div",{className:"text-muted-foreground italic",children:"no headers"}):e.jsx("div",{className:"grid grid-cols-[minmax(140px,max-content)_1fr] gap-x-3 gap-y-0.5",children:t.map(s=>e.jsxs("div",{className:"contents",children:[e.jsx("div",{className:"font-mono text-xs text-primary",children:s}),e.jsx("div",{className:"break-all font-mono text-xs",children:r[s]})]},s))})}export{c as HeadersViewer};
2
- //# sourceMappingURL=headers-viewer-C9FKdG-_.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"headers-viewer-C9FKdG-_.js","sources":["../../src/components/headers-viewer.tsx"],"sourcesContent":["import { safeParseHeaders } from \"@/lib/format\";\n\ninterface HeadersViewerProps {\n headers: string | null | undefined;\n}\n\nexport function HeadersViewer({ headers }: HeadersViewerProps) {\n const parsed = safeParseHeaders(headers);\n const keys = Object.keys(parsed);\n\n if (keys.length === 0) {\n return <div className=\"text-muted-foreground italic\">no headers</div>;\n }\n\n return (\n <div className=\"grid grid-cols-[minmax(140px,max-content)_1fr] gap-x-3 gap-y-0.5\">\n {keys.map((k) => (\n <div key={k} className=\"contents\">\n <div className=\"font-mono text-xs text-primary\">{k}</div>\n <div className=\"break-all font-mono text-xs\">{parsed[k]}</div>\n </div>\n ))}\n </div>\n );\n}\n"],"names":["HeadersViewer","headers","parsed","safeParseHeaders","keys","jsx","k","jsxs"],"mappings":"oHAMO,SAASA,EAAc,CAAE,QAAAC,GAA+B,CAC7D,MAAMC,EAASC,EAAiBF,CAAO,EACjCG,EAAO,OAAO,KAAKF,CAAM,EAE/B,OAAIE,EAAK,SAAW,EACXC,EAAAA,IAAC,MAAA,CAAI,UAAU,+BAA+B,SAAA,aAAU,EAI/DA,EAAAA,IAAC,MAAA,CAAI,UAAU,mEACZ,SAAAD,EAAK,IAAKE,GACTC,EAAAA,KAAC,MAAA,CAAY,UAAU,WACrB,SAAA,CAAAF,EAAAA,IAAC,MAAA,CAAI,UAAU,iCAAkC,SAAAC,EAAE,QAClD,MAAA,CAAI,UAAU,8BAA+B,SAAAJ,EAAOI,CAAC,CAAA,CAAE,CAAA,GAFhDA,CAGV,CACD,EACH,CAEJ"}