diff --git a/src/gateway/static/dashboard/assets/ActivityPage-DDIA-0sC.js b/src/gateway/static/dashboard/assets/ActivityPage-B6j_aVh9.js similarity index 97% rename from src/gateway/static/dashboard/assets/ActivityPage-DDIA-0sC.js rename to src/gateway/static/dashboard/assets/ActivityPage-B6j_aVh9.js index 0eecda43..5c1e8833 100644 --- a/src/gateway/static/dashboard/assets/ActivityPage-DDIA-0sC.js +++ b/src/gateway/static/dashboard/assets/ActivityPage-B6j_aVh9.js @@ -1 +1 @@ -import{j as t}from"./tanstack-query-1t81HyiD.js";import{B as _,c as we}from"./heroui-DZj66Arc.js";import{u as Pe,r as n}from"./react-dgEcD0HR.js";import{u as Ce,a as Ae,b as De,c as re,d as Fe,e as Me,f as Ee,P as Oe,E as Te,F as ne,g as R}from"./index-DHOrDaWF.js";import{u as Ue,r as Ie,B as Re}from"./tableSelection-CojkFPwd.js";import{C as Le}from"./ConfirmDialog-CvV5gDQA.js";import{D as $e}from"./DataTable-CrQq0sYf.js";import{T as Be,S as Ke,P as ze}from"./TablePagination-Dob9a4wG.js";function qe(a){const[r,o]=Pe(),s=n.useCallback(u=>r.get(u)??a[u],[r,a]),d=n.useCallback(u=>{const g=Number.parseInt(r.get(u)??"",10);if(!Number.isNaN(g))return g;const c=Number.parseInt(a[u],10);return Number.isNaN(c)?0:c},[r,a]),p=n.useCallback(u=>{o(g=>{const c=new URLSearchParams(g);for(const[x,f]of Object.entries(u)){const b=String(f);b===""||b===a[x]?c.delete(x):c.set(x,b)}return c},{replace:!0})},[o,a]);return{get:s,getNumber:d,patch:p}}const Ge=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function A(a){return a===null?"—":Ge.format(a)}function j(a){return a===null?"—":a.toLocaleString()}function oe(a){return a===null?"—":a<1e3?`${a} ms`:`${(a/1e3).toFixed(a<1e4?2:1)} s`}function He(a){const r=new Date(a);return Number.isNaN(r.getTime())?a:r.toLocaleString()}function We(a){const r=new Date(a).getTime();if(Number.isNaN(r))return a;const o=Math.max(0,Math.round((Date.now()-r)/1e3));if(o<60)return`${o}s ago`;const s=Math.round(o/60);if(s<60)return`${s}m ago`;const d=Math.round(s/60);return d<24?`${d}h ago`:`${Math.round(d/24)}d ago`}const Ze=a=>a.id,Je=a=>a.status==="error"?"bg-red-50":void 0,Ye=3600,L=86400,B=[{key:"1h",label:"1h",seconds:Ye},{key:"24h",label:"24h",seconds:L},{key:"7d",label:"7d",seconds:7*L},{key:"30d",label:"30d",seconds:30*L},{key:"all",label:"All",seconds:null}],ie="24h",Qe=[{label:"All",value:""},{label:"Success",value:"success"},{label:"Error",value:"error"}],Ve=[{label:"All",value:""},{label:"Priced",value:"true"},{label:"Unpriced",value:"false"}],Xe=50,et={range:ie,start_date:"",end_date:"",status:"",model:"",user_id:"",api_key_id:"",priced:"",page:"0",size:String(Xe)};function tt(a){return new Date(Date.now()-a*1e3).toISOString()}function $(a,r,o){if(r||o)return{start:r||void 0,end:o||void 0};if(a==="custom")return{};const s=B.find(p=>p.key===a)??B.find(p=>p.key===ie),d=(s==null?void 0:s.seconds)??null;return{start:d==null?void 0:tt(d),end:void 0}}function at({status:a}){const r=a==="error"?"border-red-200 bg-red-50 text-red-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${r}`,children:a})}const st={gateway:"Gateway",claude_code:"Claude Code",codex:"Codex"};function lt(a){return st[a]??a}function i({label:a,children:r}){return t.jsxs("div",{className:"flex flex-col gap-0.5",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:a}),t.jsx("span",{className:"text-sm text-[var(--otari-ink)] break-all",children:r})]})}function rt({entry:a}){var r;return t.jsxs("div",{className:"flex flex-col gap-4 px-4 py-4",children:[a.error_message?t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:"Error"}),t.jsx("pre",{className:"max-h-48 overflow-auto rounded-lg border border-red-200 bg-red-50 p-3 text-xs whitespace-pre-wrap break-all text-red-700",children:"The provider returned an error. Inspect gateway logs for details."})]}):null,t.jsxs("div",{className:"grid gap-4 sm:grid-cols-2 lg:grid-cols-4",children:[t.jsx(i,{label:"Provider",children:a.provider??"—"}),t.jsx(i,{label:"Endpoint",children:a.endpoint}),t.jsx(i,{label:"Source",children:lt(a.source)}),a.source_label?t.jsx(i,{label:"Session",children:a.source_label}):null,t.jsx(i,{label:"User",children:a.user_id??"—"}),t.jsx(i,{label:"API key",children:a.api_key_id??"—"}),t.jsx(i,{label:"Prompt tokens",children:j(a.prompt_tokens)}),t.jsx(i,{label:"Completion tokens",children:j(a.completion_tokens)}),t.jsx(i,{label:"Total tokens",children:j(a.total_tokens)}),t.jsx(i,{label:"Cost",children:A(a.cost)}),t.jsx(i,{label:"Cache read tokens",children:j(a.cache_read_tokens)}),t.jsx(i,{label:"Cache write tokens",children:j(a.cache_write_tokens)}),t.jsx(i,{label:"1h cache writes",children:j(a.cache_write_1h_tokens??null)}),t.jsx(i,{label:"Total time",children:oe(a.latency_ms)}),t.jsx(i,{label:"Request ID",children:a.id})]}),(r=a.pricing_breakdown)!=null&&r.length?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:"Billed meters"}),t.jsx("div",{className:"grid gap-2 sm:grid-cols-2 lg:grid-cols-3",children:a.pricing_breakdown.map(o=>t.jsxs(i,{label:o.meter.replaceAll("_"," "),children:[j(o.units)," at ",A(o.rate_per_million)," / 1M, ",A(o.cost)]},o.meter))})]}):null]})}function ft(){var te,ae,se,le;const a=Ce(),r=Ae(),o=n.useMemo(()=>{const e=new Map;for(const l of r.data??[])e.set(l.id,l.key_name??`${l.id.slice(0,8)}…`);return e},[r.data]),s=qe(et),d=s.get("range"),p=s.get("start_date"),u=s.get("end_date"),g=s.get("status"),c=s.get("model"),x=s.get("user_id"),f=s.get("api_key_id"),b=s.get("priced"),K=Math.max(0,s.getNumber("page")),z=s.getNumber("size"),D=ze.reduce((e,l)=>Math.abs(l-z)$(d,p,u));n.useEffect(()=>{q($(d,p,u))},[d,p,u]);const F=p||u?"custom":d,ce=!!(v.start||v.end),G=b==="true"?!0:b==="false"?!1:void 0,h=n.useMemo(()=>({start_date:v.start,end_date:v.end,status:g||void 0,model:c.trim()||void 0,user_id:x||void 0,api_key_id:f||void 0,priced:G}),[v,g,c,x,f,G]),m=Ue(),w=JSON.stringify(h),H=n.useRef(w);n.useEffect(()=>{H.current!==w&&(H.current=w,s.patch({page:0}),m.clear())},[w,s,m]);const S=De(h,K,D),k=re(h),de=n.useMemo(()=>({start_date:v.start,end_date:v.end,status:g||void 0,user_id:x||void 0,api_key_id:f||void 0}),[v,g,x,f]),M=((ae=(te=Fe(de,"day").data)==null?void 0:te.by_model)==null?void 0:ae.filter(e=>!e.is_other&&e.key!==null).map(e=>e.key))??[],ue=(r.data??[]).map(e=>({value:e.id,label:e.key_name??`${e.id.slice(0,8)}…`})),y=S.data??[],me=k.isSuccess&&!k.isPlaceholderData?((se=k.data)==null?void 0:se.total)??0:null,W=!!(g||c.trim()||x||f||b||ce),[Z,pe]=n.useState(!1),J=[g,b,f,x,c.trim()].filter(Boolean).length,E=n.useMemo(()=>y.filter(e=>!e.counts_toward_budget).map(e=>e.id),[y]),ge=n.useMemo(()=>y.filter(e=>e.counts_toward_budget).map(e=>e.id),[y]),Y=Ie(m.selectedKeys,E),N=Y.length,Q=m.allMatching||N>0,he=n.useMemo(()=>({...h,counts_toward_budget:!1}),[h]),V=re(he,Q),P=V.isSuccess?((le=V.data)==null?void 0:le.total)??null:null,xe=E.length>0&&N===E.length&&P!=null&&P>N,C=m.allMatching?P??N:N,O=Me(),T=Ee(),[fe,U]=n.useState(!1),[be,I]=n.useState(!1),[ve,X]=n.useState(null),_e=n.useCallback(e=>t.jsxs("div",{children:[t.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Request detail"}),t.jsx(_,{size:"sm",variant:"ghost",onPress:()=>X(null),children:"Close"})]}),t.jsx(rt,{entry:e})]}),[]),ee=()=>m.allMatching?{by_filter:!0,model:h.model,user_id:h.user_id,api_key_id:h.api_key_id,status:h.status,start_date:h.start_date,end_date:h.end_date,priced:h.priced}:{ids:Y},je=()=>{O.mutate(ee(),{onSuccess:()=>{U(!1),m.clear()}})},ye=e=>{T.mutate({...ee(),...e},{onSuccess:()=>{I(!1),m.clear()}})},Se=()=>{s.patch({range:"all",start_date:"",end_date:"",status:"",model:"",user_id:"",api_key_id:"",priced:""})},ke=()=>{q($(d,p,u)),S.refetch(),k.refetch()},Ne=n.useMemo(()=>{const e=l=>l===null?"—":o.get(l)??`${l.slice(0,8)}…`;return[{id:"time",header:"Time",cell:l=>t.jsx("span",{title:He(l.timestamp),className:"text-[var(--otari-muted)]",children:We(l.timestamp)})},{id:"user",header:"User",cell:l=>l.user_id??"—"},{id:"model",header:"Model",isRowHeader:!0,cell:l=>l.model},{id:"api_key",header:"API key",cell:l=>t.jsx("span",{className:"text-[var(--otari-muted)]",children:e(l.api_key_id)})},{id:"tokens",header:"Tokens",align:"end",cell:l=>j(l.total_tokens)},{id:"cost",header:"Cost",align:"end",cell:l=>A(l.cost)},{id:"latency",header:"Total time",align:"end",cell:l=>oe(l.latency_ms)},{id:"status",header:"Status",cell:l=>t.jsx(at,{status:l.status})}]},[o]);return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(Oe,{title:"Activity",description:"A per-request log of what the gateway served: tokens, cost, latency, and failures. No request or response content is stored.",action:t.jsx(_,{variant:"outline",onPress:ke,isDisabled:S.isFetching,children:"Refresh"})}),t.jsx(Te,{error:S.error??k.error}),t.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[t.jsxs("div",{className:"flex flex-wrap gap-2",children:[B.map(e=>t.jsx(_,{size:"sm",variant:F===e.key?"primary":"outline",onPress:()=>s.patch({range:e.key,start_date:"",end_date:""}),children:e.label},e.key)),t.jsx(_,{size:"sm",variant:F==="custom"?"primary":"outline",onPress:()=>s.patch({range:"custom"}),children:"Custom…"})]}),F==="custom"?t.jsxs("div",{className:"flex flex-wrap items-end gap-2",children:[t.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["From",t.jsx("input",{type:"datetime-local",value:p,onChange:e=>s.patch({start_date:e.target.value}),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]}),t.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["To",t.jsx("input",{type:"datetime-local",value:u,onChange:e=>s.patch({end_date:e.target.value}),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]})]}):null,t.jsxs(_,{size:"sm",variant:"outline",className:"md:hidden",onPress:()=>pe(e=>!e),"aria-expanded":Z,"aria-controls":"activity-filters",children:["Filters",J?` (${J})`:""]}),t.jsxs("div",{id:"activity-filters",className:we("flex-wrap items-end gap-3 md:flex",Z?"flex w-full md:w-auto":"hidden"),children:[t.jsx(ne,{id:"filter-status",label:"Status",value:g,onChange:e=>s.patch({status:e}),children:Qe.map(e=>t.jsx("option",{value:e.value,children:e.label},e.value))}),t.jsx(ne,{id:"filter-priced",label:"Priced?",value:b,onChange:e=>s.patch({priced:e}),children:Ve.map(e=>t.jsx("option",{value:e.value,children:e.label},e.value))}),t.jsx(R,{label:"API key",value:f,onChange:e=>s.patch({api_key_id:e}),placeholder:"All keys",options:ue}),t.jsx(R,{label:"User",value:x,onChange:e=>s.patch({user_id:e}),placeholder:"All users",options:(a.data??[]).map(e=>({value:e.user_id,label:e.alias?`${e.alias} (${e.user_id})`:e.user_id}))}),t.jsx(R,{label:"Model",value:c,onChange:e=>s.patch({model:e}),allowsCustom:!0,placeholder:"Any model",options:(c&&!M.includes(c)?[c,...M]:M).map(e=>({value:e,label:e}))}),W?t.jsx(_,{size:"sm",variant:"ghost",onPress:Se,children:"Clear filters"}):null]})]}),Q?t.jsxs(Re,{selectedCount:C,allMatching:m.allMatching,matchingTotal:P,canSelectAllMatching:xe,onSelectAllMatching:m.enableAllMatching,onClear:m.clear,children:[t.jsx(_,{size:"sm",variant:"primary",onPress:()=>I(!0),children:"Set price"}),t.jsx(_,{size:"sm",variant:"danger",onPress:()=>U(!0),children:"Delete"})]}):null,t.jsx($e,{ariaLabel:"Activity log",columns:Ne,rows:y,getRowKey:Ze,isLoading:S.isLoading,emptyContent:W?"No requests match these filters.":"No requests recorded yet.",selectionMode:"multiple",selectedKeys:m.selectedKeys,onSelectionChange:m.onSelectionChange,disabledKeys:ge,onRowAction:e=>X(l=>l===e?null:e),rowClassName:Je,detailKey:ve,renderDetail:_e}),t.jsx(Be,{page:K,pageSize:D,total:me,rowsOnPage:y.length,onPageChange:e=>s.patch({page:e}),onPageSizeChange:e=>s.patch({size:e,page:0}),isFetching:S.isFetching,hasNextFallback:y.length===D}),t.jsx(Le,{isOpen:fe,onOpenChange:U,heading:"Delete usage rows",body:`Delete ${C.toLocaleString()} imported ${C===1?"row":"rows"}? Only imported rows are removed, and this cannot be undone.`,confirmLabel:"Delete",isPending:O.isPending,error:O.error,onConfirm:je}),t.jsx(Ke,{isOpen:be,onOpenChange:I,targetCount:C,isPending:T.isPending,error:T.error,onSubmit:ye})]})}export{ft as ActivityPage}; +import{j as t}from"./tanstack-query-1t81HyiD.js";import{B as _,c as we}from"./heroui-DZj66Arc.js";import{u as Pe,r as n}from"./react-dgEcD0HR.js";import{u as Ce,a as Ae,b as De,c as re,d as Fe,e as Me,f as Ee,P as Oe,E as Te,F as ne,g as R}from"./index-BzgMrLHf.js";import{u as Ue,r as Ie,B as Re}from"./tableSelection-CojkFPwd.js";import{C as Le}from"./ConfirmDialog-CCJnWoSc.js";import{D as $e}from"./DataTable-CrQq0sYf.js";import{T as Be,S as Ke,P as ze}from"./TablePagination-DlTV4kUZ.js";function qe(a){const[r,o]=Pe(),s=n.useCallback(u=>r.get(u)??a[u],[r,a]),d=n.useCallback(u=>{const g=Number.parseInt(r.get(u)??"",10);if(!Number.isNaN(g))return g;const c=Number.parseInt(a[u],10);return Number.isNaN(c)?0:c},[r,a]),p=n.useCallback(u=>{o(g=>{const c=new URLSearchParams(g);for(const[x,f]of Object.entries(u)){const b=String(f);b===""||b===a[x]?c.delete(x):c.set(x,b)}return c},{replace:!0})},[o,a]);return{get:s,getNumber:d,patch:p}}const Ge=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function A(a){return a===null?"—":Ge.format(a)}function j(a){return a===null?"—":a.toLocaleString()}function oe(a){return a===null?"—":a<1e3?`${a} ms`:`${(a/1e3).toFixed(a<1e4?2:1)} s`}function He(a){const r=new Date(a);return Number.isNaN(r.getTime())?a:r.toLocaleString()}function We(a){const r=new Date(a).getTime();if(Number.isNaN(r))return a;const o=Math.max(0,Math.round((Date.now()-r)/1e3));if(o<60)return`${o}s ago`;const s=Math.round(o/60);if(s<60)return`${s}m ago`;const d=Math.round(s/60);return d<24?`${d}h ago`:`${Math.round(d/24)}d ago`}const Ze=a=>a.id,Je=a=>a.status==="error"?"bg-red-50":void 0,Ye=3600,L=86400,B=[{key:"1h",label:"1h",seconds:Ye},{key:"24h",label:"24h",seconds:L},{key:"7d",label:"7d",seconds:7*L},{key:"30d",label:"30d",seconds:30*L},{key:"all",label:"All",seconds:null}],ie="24h",Qe=[{label:"All",value:""},{label:"Success",value:"success"},{label:"Error",value:"error"}],Ve=[{label:"All",value:""},{label:"Priced",value:"true"},{label:"Unpriced",value:"false"}],Xe=50,et={range:ie,start_date:"",end_date:"",status:"",model:"",user_id:"",api_key_id:"",priced:"",page:"0",size:String(Xe)};function tt(a){return new Date(Date.now()-a*1e3).toISOString()}function $(a,r,o){if(r||o)return{start:r||void 0,end:o||void 0};if(a==="custom")return{};const s=B.find(p=>p.key===a)??B.find(p=>p.key===ie),d=(s==null?void 0:s.seconds)??null;return{start:d==null?void 0:tt(d),end:void 0}}function at({status:a}){const r=a==="error"?"border-red-200 bg-red-50 text-red-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${r}`,children:a})}const st={gateway:"Gateway",claude_code:"Claude Code",codex:"Codex"};function lt(a){return st[a]??a}function i({label:a,children:r}){return t.jsxs("div",{className:"flex flex-col gap-0.5",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:a}),t.jsx("span",{className:"text-sm text-[var(--otari-ink)] break-all",children:r})]})}function rt({entry:a}){var r;return t.jsxs("div",{className:"flex flex-col gap-4 px-4 py-4",children:[a.error_message?t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:"Error"}),t.jsx("pre",{className:"max-h-48 overflow-auto rounded-lg border border-red-200 bg-red-50 p-3 text-xs whitespace-pre-wrap break-all text-red-700",children:"The provider returned an error. Inspect gateway logs for details."})]}):null,t.jsxs("div",{className:"grid gap-4 sm:grid-cols-2 lg:grid-cols-4",children:[t.jsx(i,{label:"Provider",children:a.provider??"—"}),t.jsx(i,{label:"Endpoint",children:a.endpoint}),t.jsx(i,{label:"Source",children:lt(a.source)}),a.source_label?t.jsx(i,{label:"Session",children:a.source_label}):null,t.jsx(i,{label:"User",children:a.user_id??"—"}),t.jsx(i,{label:"API key",children:a.api_key_id??"—"}),t.jsx(i,{label:"Prompt tokens",children:j(a.prompt_tokens)}),t.jsx(i,{label:"Completion tokens",children:j(a.completion_tokens)}),t.jsx(i,{label:"Total tokens",children:j(a.total_tokens)}),t.jsx(i,{label:"Cost",children:A(a.cost)}),t.jsx(i,{label:"Cache read tokens",children:j(a.cache_read_tokens)}),t.jsx(i,{label:"Cache write tokens",children:j(a.cache_write_tokens)}),t.jsx(i,{label:"1h cache writes",children:j(a.cache_write_1h_tokens??null)}),t.jsx(i,{label:"Total time",children:oe(a.latency_ms)}),t.jsx(i,{label:"Request ID",children:a.id})]}),(r=a.pricing_breakdown)!=null&&r.length?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:"Billed meters"}),t.jsx("div",{className:"grid gap-2 sm:grid-cols-2 lg:grid-cols-3",children:a.pricing_breakdown.map(o=>t.jsxs(i,{label:o.meter.replaceAll("_"," "),children:[j(o.units)," at ",A(o.rate_per_million)," / 1M, ",A(o.cost)]},o.meter))})]}):null]})}function ft(){var te,ae,se,le;const a=Ce(),r=Ae(),o=n.useMemo(()=>{const e=new Map;for(const l of r.data??[])e.set(l.id,l.key_name??`${l.id.slice(0,8)}…`);return e},[r.data]),s=qe(et),d=s.get("range"),p=s.get("start_date"),u=s.get("end_date"),g=s.get("status"),c=s.get("model"),x=s.get("user_id"),f=s.get("api_key_id"),b=s.get("priced"),K=Math.max(0,s.getNumber("page")),z=s.getNumber("size"),D=ze.reduce((e,l)=>Math.abs(l-z)$(d,p,u));n.useEffect(()=>{q($(d,p,u))},[d,p,u]);const F=p||u?"custom":d,ce=!!(v.start||v.end),G=b==="true"?!0:b==="false"?!1:void 0,h=n.useMemo(()=>({start_date:v.start,end_date:v.end,status:g||void 0,model:c.trim()||void 0,user_id:x||void 0,api_key_id:f||void 0,priced:G}),[v,g,c,x,f,G]),m=Ue(),w=JSON.stringify(h),H=n.useRef(w);n.useEffect(()=>{H.current!==w&&(H.current=w,s.patch({page:0}),m.clear())},[w,s,m]);const S=De(h,K,D),k=re(h),de=n.useMemo(()=>({start_date:v.start,end_date:v.end,status:g||void 0,user_id:x||void 0,api_key_id:f||void 0}),[v,g,x,f]),M=((ae=(te=Fe(de,"day").data)==null?void 0:te.by_model)==null?void 0:ae.filter(e=>!e.is_other&&e.key!==null).map(e=>e.key))??[],ue=(r.data??[]).map(e=>({value:e.id,label:e.key_name??`${e.id.slice(0,8)}…`})),y=S.data??[],me=k.isSuccess&&!k.isPlaceholderData?((se=k.data)==null?void 0:se.total)??0:null,W=!!(g||c.trim()||x||f||b||ce),[Z,pe]=n.useState(!1),J=[g,b,f,x,c.trim()].filter(Boolean).length,E=n.useMemo(()=>y.filter(e=>!e.counts_toward_budget).map(e=>e.id),[y]),ge=n.useMemo(()=>y.filter(e=>e.counts_toward_budget).map(e=>e.id),[y]),Y=Ie(m.selectedKeys,E),N=Y.length,Q=m.allMatching||N>0,he=n.useMemo(()=>({...h,counts_toward_budget:!1}),[h]),V=re(he,Q),P=V.isSuccess?((le=V.data)==null?void 0:le.total)??null:null,xe=E.length>0&&N===E.length&&P!=null&&P>N,C=m.allMatching?P??N:N,O=Me(),T=Ee(),[fe,U]=n.useState(!1),[be,I]=n.useState(!1),[ve,X]=n.useState(null),_e=n.useCallback(e=>t.jsxs("div",{children:[t.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Request detail"}),t.jsx(_,{size:"sm",variant:"ghost",onPress:()=>X(null),children:"Close"})]}),t.jsx(rt,{entry:e})]}),[]),ee=()=>m.allMatching?{by_filter:!0,model:h.model,user_id:h.user_id,api_key_id:h.api_key_id,status:h.status,start_date:h.start_date,end_date:h.end_date,priced:h.priced}:{ids:Y},je=()=>{O.mutate(ee(),{onSuccess:()=>{U(!1),m.clear()}})},ye=e=>{T.mutate({...ee(),...e},{onSuccess:()=>{I(!1),m.clear()}})},Se=()=>{s.patch({range:"all",start_date:"",end_date:"",status:"",model:"",user_id:"",api_key_id:"",priced:""})},ke=()=>{q($(d,p,u)),S.refetch(),k.refetch()},Ne=n.useMemo(()=>{const e=l=>l===null?"—":o.get(l)??`${l.slice(0,8)}…`;return[{id:"time",header:"Time",cell:l=>t.jsx("span",{title:He(l.timestamp),className:"text-[var(--otari-muted)]",children:We(l.timestamp)})},{id:"user",header:"User",cell:l=>l.user_id??"—"},{id:"model",header:"Model",isRowHeader:!0,cell:l=>l.model},{id:"api_key",header:"API key",cell:l=>t.jsx("span",{className:"text-[var(--otari-muted)]",children:e(l.api_key_id)})},{id:"tokens",header:"Tokens",align:"end",cell:l=>j(l.total_tokens)},{id:"cost",header:"Cost",align:"end",cell:l=>A(l.cost)},{id:"latency",header:"Total time",align:"end",cell:l=>oe(l.latency_ms)},{id:"status",header:"Status",cell:l=>t.jsx(at,{status:l.status})}]},[o]);return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(Oe,{title:"Activity",description:"A per-request log of what the gateway served: tokens, cost, latency, and failures. No request or response content is stored.",action:t.jsx(_,{variant:"outline",onPress:ke,isDisabled:S.isFetching,children:"Refresh"})}),t.jsx(Te,{error:S.error??k.error}),t.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[t.jsxs("div",{className:"flex flex-wrap gap-2",children:[B.map(e=>t.jsx(_,{size:"sm",variant:F===e.key?"primary":"outline",onPress:()=>s.patch({range:e.key,start_date:"",end_date:""}),children:e.label},e.key)),t.jsx(_,{size:"sm",variant:F==="custom"?"primary":"outline",onPress:()=>s.patch({range:"custom"}),children:"Custom…"})]}),F==="custom"?t.jsxs("div",{className:"flex flex-wrap items-end gap-2",children:[t.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["From",t.jsx("input",{type:"datetime-local",value:p,onChange:e=>s.patch({start_date:e.target.value}),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]}),t.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["To",t.jsx("input",{type:"datetime-local",value:u,onChange:e=>s.patch({end_date:e.target.value}),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]})]}):null,t.jsxs(_,{size:"sm",variant:"outline",className:"md:hidden",onPress:()=>pe(e=>!e),"aria-expanded":Z,"aria-controls":"activity-filters",children:["Filters",J?` (${J})`:""]}),t.jsxs("div",{id:"activity-filters",className:we("flex-wrap items-end gap-3 md:flex",Z?"flex w-full md:w-auto":"hidden"),children:[t.jsx(ne,{id:"filter-status",label:"Status",value:g,onChange:e=>s.patch({status:e}),children:Qe.map(e=>t.jsx("option",{value:e.value,children:e.label},e.value))}),t.jsx(ne,{id:"filter-priced",label:"Priced?",value:b,onChange:e=>s.patch({priced:e}),children:Ve.map(e=>t.jsx("option",{value:e.value,children:e.label},e.value))}),t.jsx(R,{label:"API key",value:f,onChange:e=>s.patch({api_key_id:e}),placeholder:"All keys",options:ue}),t.jsx(R,{label:"User",value:x,onChange:e=>s.patch({user_id:e}),placeholder:"All users",options:(a.data??[]).map(e=>({value:e.user_id,label:e.alias?`${e.alias} (${e.user_id})`:e.user_id}))}),t.jsx(R,{label:"Model",value:c,onChange:e=>s.patch({model:e}),allowsCustom:!0,placeholder:"Any model",options:(c&&!M.includes(c)?[c,...M]:M).map(e=>({value:e,label:e}))}),W?t.jsx(_,{size:"sm",variant:"ghost",onPress:Se,children:"Clear filters"}):null]})]}),Q?t.jsxs(Re,{selectedCount:C,allMatching:m.allMatching,matchingTotal:P,canSelectAllMatching:xe,onSelectAllMatching:m.enableAllMatching,onClear:m.clear,children:[t.jsx(_,{size:"sm",variant:"primary",onPress:()=>I(!0),children:"Set price"}),t.jsx(_,{size:"sm",variant:"danger",onPress:()=>U(!0),children:"Delete"})]}):null,t.jsx($e,{ariaLabel:"Activity log",columns:Ne,rows:y,getRowKey:Ze,isLoading:S.isLoading,emptyContent:W?"No requests match these filters.":"No requests recorded yet.",selectionMode:"multiple",selectedKeys:m.selectedKeys,onSelectionChange:m.onSelectionChange,disabledKeys:ge,onRowAction:e=>X(l=>l===e?null:e),rowClassName:Je,detailKey:ve,renderDetail:_e}),t.jsx(Be,{page:K,pageSize:D,total:me,rowsOnPage:y.length,onPageChange:e=>s.patch({page:e}),onPageSizeChange:e=>s.patch({size:e,page:0}),isFetching:S.isFetching,hasNextFallback:y.length===D}),t.jsx(Le,{isOpen:fe,onOpenChange:U,heading:"Delete usage rows",body:`Delete ${C.toLocaleString()} imported ${C===1?"row":"rows"}? Only imported rows are removed, and this cannot be undone.`,confirmLabel:"Delete",isPending:O.isPending,error:O.error,onConfirm:je}),t.jsx(Ke,{isOpen:be,onOpenChange:I,targetCount:C,isPending:T.isPending,error:T.error,onSubmit:ye})]})}export{ft as ActivityPage}; diff --git a/src/gateway/static/dashboard/assets/AliasesPage-CNowZYqH.js b/src/gateway/static/dashboard/assets/AliasesPage-Chlhfn8l.js similarity index 98% rename from src/gateway/static/dashboard/assets/AliasesPage-CNowZYqH.js rename to src/gateway/static/dashboard/assets/AliasesPage-Chlhfn8l.js index 804b1e2b..55ea01e4 100644 --- a/src/gateway/static/dashboard/assets/AliasesPage-CNowZYqH.js +++ b/src/gateway/static/dashboard/assets/AliasesPage-Chlhfn8l.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as d,u as L}from"./react-dgEcD0HR.js";import{h as T,i as E,j as M,C as I,P as K,E as S,k as A}from"./index-DHOrDaWF.js";import{u as R,r as F,B as $}from"./tableSelection-CojkFPwd.js";import{C as q}from"./ConfirmDialog-CvV5gDQA.js";import{D as O}from"./DataTable-CrQq0sYf.js";import{F as V}from"./Field-DEJ6Wjg9.js";import{C as N,L as z,I as H,a as G,b as W,f as X,B as h,d as k}from"./heroui-DZj66Arc.js";const _=50;function P({label:t,value:a,onChange:r,description:n,placeholder:u="provider:model",autoFocus:i,isRequired:m}){const c=T(),{visible:o,total:x,failed:p}=d.useMemo(()=>{var y;const l=a.trim().toLowerCase(),f=((y=c.data)==null?void 0:y.providers)??[],C=f.flatMap(g=>g.models),j=l?C.filter(g=>g.key.toLowerCase().includes(l)):C;return{visible:j.slice(0,_),total:j.length,failed:f.filter(g=>!g.ok)}},[c.data,a]),b=c.isLoading?"Loading models from your providers…":p.length>0?`Could not list models for ${p.map(f=>f.provider).join(", ")}. Check that provider's credentials, or type the model key directly.`:x>o.length?`Showing ${o.length} of ${x} matches. Keep typing to narrow them.`:n;return e.jsxs(N.Root,{allowsCustomValue:!0,allowsEmptyCollection:!0,menuTrigger:"input",inputValue:a,onInputChange:r,onSelectionChange:l=>{l!=null&&r(String(l))},isRequired:m,className:"flex max-w-md flex-col gap-1",children:[e.jsx(z,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsxs(N.InputGroup,{children:[e.jsx(H,{placeholder:u,autoFocus:i}),e.jsx(N.Trigger,{})]}),e.jsx(N.Popover,{children:e.jsx(G,{items:o,className:"max-h-72 overflow-auto",children:l=>e.jsx(W,{id:l.key,textValue:l.key,children:l.key})})}),b?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:b}):null]})}const J=t=>t.name;function Q({alias:t,onClose:a}){const r=A(),[n,u]=d.useState(t.target),i=n.trim()!==""&&n.trim()!==t.target,m=()=>{i&&r.mutate({name:t.name,target:n.trim()},{onSuccess:a})};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit alias ",e.jsx("code",{children:t.name})]}),e.jsx(S,{error:r.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Alias name"}),e.jsx("code",{className:"text-sm text-[var(--otari-muted)]",children:t.name}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The alias name is the key and cannot be changed here. Delete and recreate to rename."})]}),e.jsx(P,{label:"Target",value:n,onChange:u,isRequired:!0,description:"The real model this resolves to. Callers never see it."})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(h,{variant:"primary",isDisabled:!i||r.isPending,onPress:m,children:r.isPending?"Saving…":"Save changes"}),e.jsx(h,{variant:"ghost",onPress:a,children:"Cancel"})]})]})})}function U({onClose:t,initialTarget:a=""}){const r=A(),[n,u]=d.useState(""),[i,m]=d.useState(a),c=/[:/]/.test(n),o=n.trim()!==""&&i.trim()!==""&&!c,x=()=>{o&&r.mutate({name:n.trim(),target:i.trim()},{onSuccess:t})};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"New alias"}),e.jsx(S,{error:r.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(V,{label:"Alias name",value:n,onChange:u,placeholder:"fast-model",isRequired:!0,autoFocus:!0,description:c?e.jsx("span",{className:"text-red-700",children:"An alias name cannot contain “:” or “/”."}):"What callers send as `model`."}),e.jsx(P,{label:"Target",value:i,onChange:m,isRequired:!0,description:"The real model this resolves to. Callers never see it."})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(h,{variant:"primary",isDisabled:!o||r.isPending,onPress:x,children:r.isPending?"Creating…":"Create alias"}),e.jsx(h,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function le(){const t=E(),a=M(),[r]=L(),n=r.get("target")??"",[u,i]=d.useState(n!==""),[m,c]=d.useState(null),o=R(),[x,p]=d.useState(!1),[b,l]=d.useState(void 0),[f,C]=d.useState(!1),j=[...t.data??[]].sort((s,B)=>s.name.localeCompare(B.name)),y=j.filter(s=>s.source==="stored").map(s=>s.name),g=j.filter(s=>s.source!=="stored").map(s=>s.name),v=F(o.selectedKeys,y),w=async()=>{C(!0),l(void 0);try{for(const s of v)await a.mutateAsync(s);o.clear(),p(!1)}catch(s){l(s)}finally{C(!1)}},D=d.useMemo(()=>[{id:"alias",header:"Alias",isRowHeader:!0,cell:s=>e.jsx("span",{className:"font-medium break-all text-[var(--otari-ink)]",children:s.name})},{id:"target",header:"Target",cell:s=>e.jsx("span",{className:"break-all text-[var(--otari-muted)]",children:s.target})},{id:"source",header:"Source",cell:s=>e.jsx(X,{size:"sm",color:s.source==="stored"?"accent":"default",children:s.source})},{id:"actions",header:"Actions",align:"end",cell:s=>s.source==="stored"?e.jsxs("span",{className:"inline-flex items-center gap-2 whitespace-nowrap",children:[e.jsx(h,{size:"sm",variant:"ghost",onPress:()=>{i(!1),c(s)},children:"Edit"}),e.jsx(I,{confirmLabel:"Delete",isPending:a.isPending,onConfirm:()=>a.mutate(s.name),children:"Delete"})]}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"set in config.yml"})}],[a.isPending,a.mutate]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(K,{title:"Aliases",description:"Friendly names that map to a real provider:model. Callers send the alias as the model; pricing, budgets, and usage key on the target.",action:u||m?null:e.jsx(h,{variant:"primary",onPress:()=>{c(null),i(!0)},children:"New alias"})}),e.jsx(S,{error:t.error}),u?e.jsx(U,{initialTarget:n,onClose:()=>i(!1)}):null,m?e.jsx(Q,{alias:m,onClose:()=>c(null)}):null,a.error?e.jsx(S,{error:a.error}):null,v.length>0?e.jsx($,{selectedCount:v.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:o.clear,children:e.jsx(h,{size:"sm",variant:"danger",onPress:()=>p(!0),children:"Delete"})}):null,e.jsx(O,{ariaLabel:"Aliases",columns:D,rows:j,getRowKey:J,isLoading:t.isLoading,emptyContent:"No aliases yet. Create one to give a model a friendly name.",selectionMode:"multiple",selectedKeys:o.selectedKeys,onSelectionChange:o.onSelectionChange,disabledKeys:g}),e.jsx(q,{isOpen:x,onOpenChange:p,heading:"Delete aliases",body:`Delete ${v.length} stored ${v.length===1?"alias":"aliases"}? Callers using ${v.length===1?"it":"them"} will get a model-not-found error.`,confirmLabel:"Delete",isPending:f,error:b,onConfirm:w})]})}export{le as AliasesPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as d,u as L}from"./react-dgEcD0HR.js";import{h as T,i as E,j as M,C as I,P as K,E as S,k as A}from"./index-BzgMrLHf.js";import{u as R,r as F,B as $}from"./tableSelection-CojkFPwd.js";import{C as q}from"./ConfirmDialog-CCJnWoSc.js";import{D as O}from"./DataTable-CrQq0sYf.js";import{F as V}from"./Field-DEJ6Wjg9.js";import{C as N,L as z,I as H,a as G,b as W,f as X,B as h,d as k}from"./heroui-DZj66Arc.js";const _=50;function P({label:t,value:a,onChange:r,description:n,placeholder:u="provider:model",autoFocus:i,isRequired:m}){const c=T(),{visible:o,total:x,failed:p}=d.useMemo(()=>{var y;const l=a.trim().toLowerCase(),f=((y=c.data)==null?void 0:y.providers)??[],C=f.flatMap(g=>g.models),j=l?C.filter(g=>g.key.toLowerCase().includes(l)):C;return{visible:j.slice(0,_),total:j.length,failed:f.filter(g=>!g.ok)}},[c.data,a]),b=c.isLoading?"Loading models from your providers…":p.length>0?`Could not list models for ${p.map(f=>f.provider).join(", ")}. Check that provider's credentials, or type the model key directly.`:x>o.length?`Showing ${o.length} of ${x} matches. Keep typing to narrow them.`:n;return e.jsxs(N.Root,{allowsCustomValue:!0,allowsEmptyCollection:!0,menuTrigger:"input",inputValue:a,onInputChange:r,onSelectionChange:l=>{l!=null&&r(String(l))},isRequired:m,className:"flex max-w-md flex-col gap-1",children:[e.jsx(z,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsxs(N.InputGroup,{children:[e.jsx(H,{placeholder:u,autoFocus:i}),e.jsx(N.Trigger,{})]}),e.jsx(N.Popover,{children:e.jsx(G,{items:o,className:"max-h-72 overflow-auto",children:l=>e.jsx(W,{id:l.key,textValue:l.key,children:l.key})})}),b?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:b}):null]})}const J=t=>t.name;function Q({alias:t,onClose:a}){const r=A(),[n,u]=d.useState(t.target),i=n.trim()!==""&&n.trim()!==t.target,m=()=>{i&&r.mutate({name:t.name,target:n.trim()},{onSuccess:a})};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit alias ",e.jsx("code",{children:t.name})]}),e.jsx(S,{error:r.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Alias name"}),e.jsx("code",{className:"text-sm text-[var(--otari-muted)]",children:t.name}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The alias name is the key and cannot be changed here. Delete and recreate to rename."})]}),e.jsx(P,{label:"Target",value:n,onChange:u,isRequired:!0,description:"The real model this resolves to. Callers never see it."})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(h,{variant:"primary",isDisabled:!i||r.isPending,onPress:m,children:r.isPending?"Saving…":"Save changes"}),e.jsx(h,{variant:"ghost",onPress:a,children:"Cancel"})]})]})})}function U({onClose:t,initialTarget:a=""}){const r=A(),[n,u]=d.useState(""),[i,m]=d.useState(a),c=/[:/]/.test(n),o=n.trim()!==""&&i.trim()!==""&&!c,x=()=>{o&&r.mutate({name:n.trim(),target:i.trim()},{onSuccess:t})};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"New alias"}),e.jsx(S,{error:r.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(V,{label:"Alias name",value:n,onChange:u,placeholder:"fast-model",isRequired:!0,autoFocus:!0,description:c?e.jsx("span",{className:"text-red-700",children:"An alias name cannot contain “:” or “/”."}):"What callers send as `model`."}),e.jsx(P,{label:"Target",value:i,onChange:m,isRequired:!0,description:"The real model this resolves to. Callers never see it."})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(h,{variant:"primary",isDisabled:!o||r.isPending,onPress:x,children:r.isPending?"Creating…":"Create alias"}),e.jsx(h,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function le(){const t=E(),a=M(),[r]=L(),n=r.get("target")??"",[u,i]=d.useState(n!==""),[m,c]=d.useState(null),o=R(),[x,p]=d.useState(!1),[b,l]=d.useState(void 0),[f,C]=d.useState(!1),j=[...t.data??[]].sort((s,B)=>s.name.localeCompare(B.name)),y=j.filter(s=>s.source==="stored").map(s=>s.name),g=j.filter(s=>s.source!=="stored").map(s=>s.name),v=F(o.selectedKeys,y),w=async()=>{C(!0),l(void 0);try{for(const s of v)await a.mutateAsync(s);o.clear(),p(!1)}catch(s){l(s)}finally{C(!1)}},D=d.useMemo(()=>[{id:"alias",header:"Alias",isRowHeader:!0,cell:s=>e.jsx("span",{className:"font-medium break-all text-[var(--otari-ink)]",children:s.name})},{id:"target",header:"Target",cell:s=>e.jsx("span",{className:"break-all text-[var(--otari-muted)]",children:s.target})},{id:"source",header:"Source",cell:s=>e.jsx(X,{size:"sm",color:s.source==="stored"?"accent":"default",children:s.source})},{id:"actions",header:"Actions",align:"end",cell:s=>s.source==="stored"?e.jsxs("span",{className:"inline-flex items-center gap-2 whitespace-nowrap",children:[e.jsx(h,{size:"sm",variant:"ghost",onPress:()=>{i(!1),c(s)},children:"Edit"}),e.jsx(I,{confirmLabel:"Delete",isPending:a.isPending,onConfirm:()=>a.mutate(s.name),children:"Delete"})]}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"set in config.yml"})}],[a.isPending,a.mutate]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(K,{title:"Aliases",description:"Friendly names that map to a real provider:model. Callers send the alias as the model; pricing, budgets, and usage key on the target.",action:u||m?null:e.jsx(h,{variant:"primary",onPress:()=>{c(null),i(!0)},children:"New alias"})}),e.jsx(S,{error:t.error}),u?e.jsx(U,{initialTarget:n,onClose:()=>i(!1)}):null,m?e.jsx(Q,{alias:m,onClose:()=>c(null)}):null,a.error?e.jsx(S,{error:a.error}):null,v.length>0?e.jsx($,{selectedCount:v.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:o.clear,children:e.jsx(h,{size:"sm",variant:"danger",onPress:()=>p(!0),children:"Delete"})}):null,e.jsx(O,{ariaLabel:"Aliases",columns:D,rows:j,getRowKey:J,isLoading:t.isLoading,emptyContent:"No aliases yet. Create one to give a model a friendly name.",selectionMode:"multiple",selectedKeys:o.selectedKeys,onSelectionChange:o.onSelectionChange,disabledKeys:g}),e.jsx(q,{isOpen:x,onOpenChange:p,heading:"Delete aliases",body:`Delete ${v.length} stored ${v.length===1?"alias":"aliases"}? Callers using ${v.length===1?"it":"them"} will get a model-not-found error.`,confirmLabel:"Delete",isPending:f,error:b,onConfirm:w})]})}export{le as AliasesPage}; diff --git a/src/gateway/static/dashboard/assets/BudgetsPage-Bt8e_tU9.js b/src/gateway/static/dashboard/assets/BudgetsPage-BAJNRbKO.js similarity index 98% rename from src/gateway/static/dashboard/assets/BudgetsPage-Bt8e_tU9.js rename to src/gateway/static/dashboard/assets/BudgetsPage-BAJNRbKO.js index 6e86f037..f770ea15 100644 --- a/src/gateway/static/dashboard/assets/BudgetsPage-Bt8e_tU9.js +++ b/src/gateway/static/dashboard/assets/BudgetsPage-BAJNRbKO.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as i}from"./react-dgEcD0HR.js";import{l as re,u as ae,m as le,n as ie,o as oe,p as de,P as ue,E as O,I as ce,q as me}from"./index-DHOrDaWF.js";import{u as xe,r as ge,B as he}from"./tableSelection-CojkFPwd.js";import{C as pe}from"./ConfirmDialog-CvV5gDQA.js";import{D as fe}from"./DataTable-CrQq0sYf.js";import{F as $}from"./Field-DEJ6Wjg9.js";import{C as E,I as be,a as je,b as ve,B as c,d as C,S as ye}from"./heroui-DZj66Arc.js";const Ne=50;function Se({value:t,onChange:a,users:r,label:o,description:l}){const[g,h]=i.useState(""),d=i.useMemo(()=>r.filter(s=>!s.user_id.startsWith("apikey-")).map(s=>({id:s.user_id,label:s.alias?`${s.user_id} (${s.alias})`:s.user_id})),[r]),p=i.useMemo(()=>{const s=g.trim().toLowerCase();return d.filter(u=>!t.includes(u.id)).filter(u=>!s||u.id.toLowerCase().includes(s)||u.label.toLowerCase().includes(s)).slice(0,Ne)},[d,t,g]),m=s=>{t.includes(s)||a([...t,s]),h("")},x=s=>a(t.filter(u=>u!==s));return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:o}),l?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:l}):null]}),t.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:t.map(s=>e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[s,e.jsx("button",{type:"button","aria-label":`Remove ${s}`,onClick:()=>x(s),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},s))}):null,d.length===0?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users yet. Create users first, then assign them here or from the Users page."}):e.jsxs(E.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:g,onInputChange:h,selectedKey:null,onSelectionChange:s=>{s!=null&&m(String(s))},className:"flex flex-col gap-1",children:[e.jsxs(E.InputGroup,{children:[e.jsx(be,{"aria-label":"Add a user",placeholder:"Search users…",autoComplete:"off"}),e.jsx(E.Trigger,{})]}),e.jsx(E.Popover,{children:e.jsx(je,{items:p,className:"max-h-72 overflow-auto",children:s=>e.jsx(ve,{id:s.id,textValue:s.label,children:s.label})})})]})]})}const _e=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:2});function P(t){return _e.format(t)}const j=86400,q=3600,z=[{label:"No reset",seconds:null},{label:"Daily",seconds:j},{label:"Weekly",seconds:7*j},{label:"Monthly",seconds:30*j}];function Ce(t){if(t===null)return"No reset";const a=z.find(r=>r.seconds===t);return a?a.label:t%j===0?`Every ${t/j} days`:t%q===0?`Every ${t/q} hours`:`Every ${t}s`}function W(t){if(!t)return"—";const a=new Date(t);return Number.isNaN(a.getTime())?"—":a.toLocaleString()}function we(t){const a=t.trim();if(a==="")return{value:null,valid:!0};const r=Number(a);return!Number.isFinite(r)||r<0?{value:null,valid:!1}:{value:r,valid:!0}}function Y(t){return t!==null&&t%j===0?String(t/j):""}function De({value:t,onChange:a,onInvalidChange:r}){const o=z.some(s=>s.seconds===t),[l,g]=i.useState(!o),[h,d]=i.useState(()=>Y(t)),p=h.trim(),m=Number(p),x=p!==""&&(!Number.isSafeInteger(m)||m<=0);return i.useEffect(()=>{r==null||r(x)},[x,r]),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Reset period"}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[z.map(s=>e.jsx(c,{size:"sm",variant:!l&&t===s.seconds?"primary":"outline",onPress:()=>{g(!1),d(Y(s.seconds)),a(s.seconds)},children:s.label},s.label)),e.jsx(c,{size:"sm",variant:l?"primary":"outline",onPress:()=>g(!0),children:"Custom"})]}),l?e.jsx("div",{className:"flex items-end gap-2",children:e.jsx($,{label:"Every N days",value:h,onChange:s=>{d(s);const u=Number(s.trim());a(s.trim()===""||!Number.isSafeInteger(u)||u<=0?null:u*j)},placeholder:"14",description:x?e.jsx("span",{className:"text-red-700",children:"Enter a whole number of days."}):"Whole days between resets."})}):null,e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Spend returns to zero each period. A user’s clock starts when the budget is assigned to them."})]})}function G({title:t,submitLabel:a,initial:r,error:o,isPending:l,onSubmit:g,onClose:h,assignUsers:d}){const[p,m]=i.useState(r.name??""),[x,s]=i.useState(r.max_budget===null?"":String(r.max_budget)),[u,b]=i.useState(r.budget_duration_sec),[S,v]=i.useState(!1),[B,k]=i.useState([]),f=we(x),A=!l&&f.valid&&!S,w=()=>{A&&g({name:p.trim()||null,max_budget:f.value,budget_duration_sec:u},B)};return e.jsx(C,{children:e.jsxs(C.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(O,{error:o}),e.jsx($,{label:"Name (optional)",value:p,onChange:m,autoFocus:!0,placeholder:"team-free-tier",description:"A label to recognize this budget later."}),e.jsx($,{label:"Spending limit (USD)",value:x,onChange:s,placeholder:"100.00",description:f.valid?"The most a single user on this budget may spend per period. Leave blank for no limit.":e.jsx("span",{className:"text-red-700",children:"Enter a non-negative number, or leave blank for no limit."})}),e.jsx(De,{value:u,onChange:b,onInvalidChange:v}),d?e.jsx(Se,{label:"Assign to users (optional)",description:"Attach this budget to existing users now. You can also manage assignments later on the Users page.",value:B,onChange:k,users:d}):null,e.jsxs("div",{className:"flex gap-2",children:[e.jsx(c,{variant:"primary",isDisabled:!A,onPress:w,children:l?"Saving…":a}),e.jsx(c,{variant:"ghost",isDisabled:l,onPress:h,children:"Cancel"})]})]})})}function Pe({budget:t}){if(t.user_count===0)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users assigned"});const a=t.total_spend;if(t.max_budget===null)return e.jsxs("span",{className:"text-xs text-[var(--otari-ink)]",children:[P(a)," spent",e.jsx("span",{className:"text-[var(--otari-muted)]",children:" · no limit"})]});const r=t.max_budget*t.user_count,o=r>0?Math.min(100,a/r*100):0,l=a>r;return e.jsxs("div",{className:"flex min-w-[140px] flex-col gap-1",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-2 text-xs",children:[e.jsx("span",{className:"text-[var(--otari-ink)]",children:P(a)}),e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["of ",P(r)]})]}),e.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",role:"progressbar","aria-valuenow":Math.round(o),"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Aggregate spend against total allocation",children:e.jsx("div",{className:`h-full rounded-full ${l?"bg-red-500":"bg-[var(--otari-brand)]"}`,style:{width:`${Math.max(o,l?100:2)}%`}})})]})}function Be({budgetId:t}){const a=me(t);if(a.isLoading)return e.jsxs("div",{className:"flex items-center gap-2 px-4 py-4 text-sm text-[var(--otari-muted)]",children:[e.jsx(ye,{size:"sm"})," Loading reset history…"]});if(a.error)return e.jsx("div",{className:"px-4 py-4",children:e.jsx(O,{error:a.error})});const r=a.data??[];return r.length===0?e.jsx("div",{className:"px-4 py-4 text-sm text-[var(--otari-muted)]",children:"No resets recorded yet for this budget."}):e.jsx("div",{className:"overflow-x-auto px-4 py-3",children:e.jsxs("table",{className:"w-full border-collapse text-xs",children:[e.jsx("thead",{className:"text-left text-[var(--otari-muted)]",children:e.jsxs("tr",{children:[e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"User"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Spend cleared"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Reset at"}),e.jsx("th",{className:"py-1.5 font-medium",children:"Next reset"})]})}),e.jsx("tbody",{children:r.map(o=>e.jsxs("tr",{className:"border-t border-[var(--otari-line)]",children:[e.jsx("td",{className:"py-1.5 pr-4",children:e.jsx("code",{children:o.user_id??"—"})}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-ink)]",children:P(o.previous_spend)}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-muted)]",children:W(o.reset_at)}),e.jsx("td",{className:"py-1.5 text-[var(--otari-muted)]",children:W(o.next_reset_at)})]},o.id))})]})})}function ke({onCreate:t}){return e.jsx(C,{children:e.jsxs(C.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"No budgets yet"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"A budget caps how much a user may spend and, optionally, resets that spend on a schedule. Create one, then assign it to users to enforce a limit."})]}),e.jsx("div",{children:e.jsx(c,{variant:"primary",onPress:t,children:"Create your first budget"})})]})})}function Ae({label:t,isPending:a,onConfirm:r}){const[o,l]=i.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsxs("span",{className:"max-w-xs text-xs text-amber-800",children:["Delete ",e.jsx("strong",{children:t}),"? Users keep their spend but lose this limit. Cannot be undone."]}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(c,{size:"sm",variant:"danger",isDisabled:a,onPress:r,children:"Delete permanently"}),e.jsx(c,{size:"sm",variant:"ghost",isDisabled:a,onPress:()=>l(!1),children:"Cancel"})]})]}):e.jsx(c,{size:"sm",variant:"danger-soft",onPress:()=>l(!0),children:"Delete"})}const Ee=t=>t.budget_id;function Q(t){return t.split("-")[0]}function M(t){return t.name??Q(t.budget_id)}function Te(){const t=re(),a=ae(),r=le(),o=ie(),l=oe(),g=de(),[h,d]=i.useState(!1),[p,m]=i.useState(null),[x,s]=i.useState(null),[u,b]=i.useState(null),[S,v]=i.useState(null),[B,k]=i.useState(!1),f=xe(),[A,w]=i.useState(!1),[X,T]=i.useState(void 0),[J,F]=i.useState(!1),D=t.data??[],H=t.isLoading,y=D.find(n=>n.budget_id===p)??null,U=D.find(n=>n.budget_id===x)??null,K=!H&&D.length===0&&!h,Z=D.map(n=>n.budget_id),_=ge(f.selectedKeys,Z),ee=async()=>{F(!0),T(void 0);try{for(const n of _)await l.mutateAsync(n);f.clear(),w(!1)}catch(n){T(n)}finally{F(!1)}},te=i.useMemo(()=>[{id:"budget",header:"Budget",isRowHeader:!0,cell:n=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:n.name??e.jsx("span",{className:"text-[var(--otari-muted)]",children:"(unnamed)"})}),e.jsx("code",{className:"text-[11px] text-[var(--otari-muted)]",title:n.budget_id,children:Q(n.budget_id)})]})},{id:"limit",header:"Limit (per user)",cell:n=>n.max_budget===null?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"Unlimited"}):P(n.max_budget)},{id:"reset",header:"Reset",cell:n=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:Ce(n.budget_duration_sec)})},{id:"users",header:"Users",cell:n=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:n.user_count})},{id:"usage",header:"Usage",cell:n=>e.jsx(Pe,{budget:n})},{id:"actions",header:"Actions",align:"end",cell:n=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(c,{size:"sm",variant:"ghost",onPress:()=>s(N=>N===n.budget_id?null:n.budget_id),children:x===n.budget_id?"Hide history":"History"}),e.jsx(c,{size:"sm",variant:"ghost",onPress:()=>{d(!1),m(n.budget_id)},children:"Edit"}),e.jsx(Ae,{label:M(n),isPending:l.isPending,onConfirm:()=>l.mutate(n.budget_id)})]})}],[x,l.isPending,l.mutate]),V=async(n,N)=>{k(!0),b(null);const L=await Promise.allSettled(N.map(R=>g.mutateAsync({id:R,body:{budget_id:n}})));k(!1);const I=L.flatMap((R,ne)=>R.status==="rejected"?[N[ne]]:[]);if(I.length>0){v({budgetId:n,userIds:I}),b(new Error(`Budget created, but could not assign it to: ${I.join(", ")}. Retry to try again.`));return}v(null),d(!1)},se=(n,N)=>{if(S){V(S.budgetId,S.userIds);return}b(null),r.mutate(n,{onSuccess:async L=>{if(N.length>0){await V(L.budget_id,N);return}d(!1)}})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(ue,{title:"Budgets",description:"Define spending limits and reset schedules. Assign a budget to users to enforce it.",action:h||K?null:e.jsx(c,{variant:"primary",onPress:()=>{m(null),b(null),v(null),d(!0)},children:"Create budget"})}),e.jsx(O,{error:t.error??r.error??o.error??l.error??g.error}),e.jsx(ce,{children:"Assign a budget to users when you create it, or later from the Users page. Each row’s usage aggregates the spend of the users currently on that budget."}),K?e.jsx(ke,{onCreate:()=>{m(null),b(null),v(null),d(!0)}}):null,h?e.jsx(G,{title:"Create budget",submitLabel:S?"Retry assignments":"Create budget",initial:{name:null,max_budget:null,budget_duration_sec:null},error:r.error??u,isPending:r.isPending||B,assignUsers:a.data??[],onSubmit:se,onClose:()=>{b(null),v(null),d(!1)}}):null,y?e.jsx(G,{title:`Edit budget ${M(y)}`,submitLabel:"Save changes",initial:{name:y.name,max_budget:y.max_budget,budget_duration_sec:y.budget_duration_sec},error:o.error,isPending:o.isPending,onSubmit:n=>o.mutate({id:y.budget_id,body:n},{onSuccess:()=>m(null)}),onClose:()=>m(null)},y.budget_id):null,_.length>0?e.jsx(he,{selectedCount:_.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:f.clear,children:e.jsx(c,{size:"sm",variant:"danger",onPress:()=>w(!0),children:"Delete"})}):null,e.jsx(fe,{ariaLabel:"Budgets",columns:te,rows:D,getRowKey:Ee,isLoading:H,emptyContent:"No budgets yet. Create one to cap spending.",selectionMode:"multiple",selectedKeys:f.selectedKeys,onSelectionChange:f.onSelectionChange}),U?e.jsx(C,{children:e.jsxs(C.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsxs("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:["Reset history — ",M(U)]}),e.jsx(c,{size:"sm",variant:"ghost",onPress:()=>s(null),children:"Close"})]}),e.jsx(Be,{budgetId:U.budget_id})]})}):null,e.jsx(pe,{isOpen:A,onOpenChange:w,heading:"Delete budgets",body:`Delete ${_.length} ${_.length===1?"budget":"budgets"}? Users on ${_.length===1?"it":"them"} will no longer be capped.`,confirmLabel:"Delete",isPending:J,error:X,onConfirm:ee})]})}export{Te as BudgetsPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as i}from"./react-dgEcD0HR.js";import{l as re,u as ae,m as le,n as ie,o as oe,p as de,P as ue,E as O,I as ce,q as me}from"./index-BzgMrLHf.js";import{u as xe,r as ge,B as he}from"./tableSelection-CojkFPwd.js";import{C as pe}from"./ConfirmDialog-CCJnWoSc.js";import{D as fe}from"./DataTable-CrQq0sYf.js";import{F as $}from"./Field-DEJ6Wjg9.js";import{C as E,I as be,a as je,b as ve,B as c,d as C,S as ye}from"./heroui-DZj66Arc.js";const Ne=50;function Se({value:t,onChange:a,users:r,label:o,description:l}){const[g,h]=i.useState(""),d=i.useMemo(()=>r.filter(s=>!s.user_id.startsWith("apikey-")).map(s=>({id:s.user_id,label:s.alias?`${s.user_id} (${s.alias})`:s.user_id})),[r]),p=i.useMemo(()=>{const s=g.trim().toLowerCase();return d.filter(u=>!t.includes(u.id)).filter(u=>!s||u.id.toLowerCase().includes(s)||u.label.toLowerCase().includes(s)).slice(0,Ne)},[d,t,g]),m=s=>{t.includes(s)||a([...t,s]),h("")},x=s=>a(t.filter(u=>u!==s));return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:o}),l?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:l}):null]}),t.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:t.map(s=>e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[s,e.jsx("button",{type:"button","aria-label":`Remove ${s}`,onClick:()=>x(s),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},s))}):null,d.length===0?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users yet. Create users first, then assign them here or from the Users page."}):e.jsxs(E.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:g,onInputChange:h,selectedKey:null,onSelectionChange:s=>{s!=null&&m(String(s))},className:"flex flex-col gap-1",children:[e.jsxs(E.InputGroup,{children:[e.jsx(be,{"aria-label":"Add a user",placeholder:"Search users…",autoComplete:"off"}),e.jsx(E.Trigger,{})]}),e.jsx(E.Popover,{children:e.jsx(je,{items:p,className:"max-h-72 overflow-auto",children:s=>e.jsx(ve,{id:s.id,textValue:s.label,children:s.label})})})]})]})}const _e=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:2});function P(t){return _e.format(t)}const j=86400,q=3600,z=[{label:"No reset",seconds:null},{label:"Daily",seconds:j},{label:"Weekly",seconds:7*j},{label:"Monthly",seconds:30*j}];function Ce(t){if(t===null)return"No reset";const a=z.find(r=>r.seconds===t);return a?a.label:t%j===0?`Every ${t/j} days`:t%q===0?`Every ${t/q} hours`:`Every ${t}s`}function W(t){if(!t)return"—";const a=new Date(t);return Number.isNaN(a.getTime())?"—":a.toLocaleString()}function we(t){const a=t.trim();if(a==="")return{value:null,valid:!0};const r=Number(a);return!Number.isFinite(r)||r<0?{value:null,valid:!1}:{value:r,valid:!0}}function Y(t){return t!==null&&t%j===0?String(t/j):""}function De({value:t,onChange:a,onInvalidChange:r}){const o=z.some(s=>s.seconds===t),[l,g]=i.useState(!o),[h,d]=i.useState(()=>Y(t)),p=h.trim(),m=Number(p),x=p!==""&&(!Number.isSafeInteger(m)||m<=0);return i.useEffect(()=>{r==null||r(x)},[x,r]),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Reset period"}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[z.map(s=>e.jsx(c,{size:"sm",variant:!l&&t===s.seconds?"primary":"outline",onPress:()=>{g(!1),d(Y(s.seconds)),a(s.seconds)},children:s.label},s.label)),e.jsx(c,{size:"sm",variant:l?"primary":"outline",onPress:()=>g(!0),children:"Custom"})]}),l?e.jsx("div",{className:"flex items-end gap-2",children:e.jsx($,{label:"Every N days",value:h,onChange:s=>{d(s);const u=Number(s.trim());a(s.trim()===""||!Number.isSafeInteger(u)||u<=0?null:u*j)},placeholder:"14",description:x?e.jsx("span",{className:"text-red-700",children:"Enter a whole number of days."}):"Whole days between resets."})}):null,e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Spend returns to zero each period. A user’s clock starts when the budget is assigned to them."})]})}function G({title:t,submitLabel:a,initial:r,error:o,isPending:l,onSubmit:g,onClose:h,assignUsers:d}){const[p,m]=i.useState(r.name??""),[x,s]=i.useState(r.max_budget===null?"":String(r.max_budget)),[u,b]=i.useState(r.budget_duration_sec),[S,v]=i.useState(!1),[B,k]=i.useState([]),f=we(x),A=!l&&f.valid&&!S,w=()=>{A&&g({name:p.trim()||null,max_budget:f.value,budget_duration_sec:u},B)};return e.jsx(C,{children:e.jsxs(C.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(O,{error:o}),e.jsx($,{label:"Name (optional)",value:p,onChange:m,autoFocus:!0,placeholder:"team-free-tier",description:"A label to recognize this budget later."}),e.jsx($,{label:"Spending limit (USD)",value:x,onChange:s,placeholder:"100.00",description:f.valid?"The most a single user on this budget may spend per period. Leave blank for no limit.":e.jsx("span",{className:"text-red-700",children:"Enter a non-negative number, or leave blank for no limit."})}),e.jsx(De,{value:u,onChange:b,onInvalidChange:v}),d?e.jsx(Se,{label:"Assign to users (optional)",description:"Attach this budget to existing users now. You can also manage assignments later on the Users page.",value:B,onChange:k,users:d}):null,e.jsxs("div",{className:"flex gap-2",children:[e.jsx(c,{variant:"primary",isDisabled:!A,onPress:w,children:l?"Saving…":a}),e.jsx(c,{variant:"ghost",isDisabled:l,onPress:h,children:"Cancel"})]})]})})}function Pe({budget:t}){if(t.user_count===0)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users assigned"});const a=t.total_spend;if(t.max_budget===null)return e.jsxs("span",{className:"text-xs text-[var(--otari-ink)]",children:[P(a)," spent",e.jsx("span",{className:"text-[var(--otari-muted)]",children:" · no limit"})]});const r=t.max_budget*t.user_count,o=r>0?Math.min(100,a/r*100):0,l=a>r;return e.jsxs("div",{className:"flex min-w-[140px] flex-col gap-1",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-2 text-xs",children:[e.jsx("span",{className:"text-[var(--otari-ink)]",children:P(a)}),e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["of ",P(r)]})]}),e.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",role:"progressbar","aria-valuenow":Math.round(o),"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Aggregate spend against total allocation",children:e.jsx("div",{className:`h-full rounded-full ${l?"bg-red-500":"bg-[var(--otari-brand)]"}`,style:{width:`${Math.max(o,l?100:2)}%`}})})]})}function Be({budgetId:t}){const a=me(t);if(a.isLoading)return e.jsxs("div",{className:"flex items-center gap-2 px-4 py-4 text-sm text-[var(--otari-muted)]",children:[e.jsx(ye,{size:"sm"})," Loading reset history…"]});if(a.error)return e.jsx("div",{className:"px-4 py-4",children:e.jsx(O,{error:a.error})});const r=a.data??[];return r.length===0?e.jsx("div",{className:"px-4 py-4 text-sm text-[var(--otari-muted)]",children:"No resets recorded yet for this budget."}):e.jsx("div",{className:"overflow-x-auto px-4 py-3",children:e.jsxs("table",{className:"w-full border-collapse text-xs",children:[e.jsx("thead",{className:"text-left text-[var(--otari-muted)]",children:e.jsxs("tr",{children:[e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"User"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Spend cleared"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Reset at"}),e.jsx("th",{className:"py-1.5 font-medium",children:"Next reset"})]})}),e.jsx("tbody",{children:r.map(o=>e.jsxs("tr",{className:"border-t border-[var(--otari-line)]",children:[e.jsx("td",{className:"py-1.5 pr-4",children:e.jsx("code",{children:o.user_id??"—"})}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-ink)]",children:P(o.previous_spend)}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-muted)]",children:W(o.reset_at)}),e.jsx("td",{className:"py-1.5 text-[var(--otari-muted)]",children:W(o.next_reset_at)})]},o.id))})]})})}function ke({onCreate:t}){return e.jsx(C,{children:e.jsxs(C.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"No budgets yet"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"A budget caps how much a user may spend and, optionally, resets that spend on a schedule. Create one, then assign it to users to enforce a limit."})]}),e.jsx("div",{children:e.jsx(c,{variant:"primary",onPress:t,children:"Create your first budget"})})]})})}function Ae({label:t,isPending:a,onConfirm:r}){const[o,l]=i.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsxs("span",{className:"max-w-xs text-xs text-amber-800",children:["Delete ",e.jsx("strong",{children:t}),"? Users keep their spend but lose this limit. Cannot be undone."]}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(c,{size:"sm",variant:"danger",isDisabled:a,onPress:r,children:"Delete permanently"}),e.jsx(c,{size:"sm",variant:"ghost",isDisabled:a,onPress:()=>l(!1),children:"Cancel"})]})]}):e.jsx(c,{size:"sm",variant:"danger-soft",onPress:()=>l(!0),children:"Delete"})}const Ee=t=>t.budget_id;function Q(t){return t.split("-")[0]}function M(t){return t.name??Q(t.budget_id)}function Te(){const t=re(),a=ae(),r=le(),o=ie(),l=oe(),g=de(),[h,d]=i.useState(!1),[p,m]=i.useState(null),[x,s]=i.useState(null),[u,b]=i.useState(null),[S,v]=i.useState(null),[B,k]=i.useState(!1),f=xe(),[A,w]=i.useState(!1),[X,T]=i.useState(void 0),[J,F]=i.useState(!1),D=t.data??[],H=t.isLoading,y=D.find(n=>n.budget_id===p)??null,U=D.find(n=>n.budget_id===x)??null,K=!H&&D.length===0&&!h,Z=D.map(n=>n.budget_id),_=ge(f.selectedKeys,Z),ee=async()=>{F(!0),T(void 0);try{for(const n of _)await l.mutateAsync(n);f.clear(),w(!1)}catch(n){T(n)}finally{F(!1)}},te=i.useMemo(()=>[{id:"budget",header:"Budget",isRowHeader:!0,cell:n=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:n.name??e.jsx("span",{className:"text-[var(--otari-muted)]",children:"(unnamed)"})}),e.jsx("code",{className:"text-[11px] text-[var(--otari-muted)]",title:n.budget_id,children:Q(n.budget_id)})]})},{id:"limit",header:"Limit (per user)",cell:n=>n.max_budget===null?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"Unlimited"}):P(n.max_budget)},{id:"reset",header:"Reset",cell:n=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:Ce(n.budget_duration_sec)})},{id:"users",header:"Users",cell:n=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:n.user_count})},{id:"usage",header:"Usage",cell:n=>e.jsx(Pe,{budget:n})},{id:"actions",header:"Actions",align:"end",cell:n=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(c,{size:"sm",variant:"ghost",onPress:()=>s(N=>N===n.budget_id?null:n.budget_id),children:x===n.budget_id?"Hide history":"History"}),e.jsx(c,{size:"sm",variant:"ghost",onPress:()=>{d(!1),m(n.budget_id)},children:"Edit"}),e.jsx(Ae,{label:M(n),isPending:l.isPending,onConfirm:()=>l.mutate(n.budget_id)})]})}],[x,l.isPending,l.mutate]),V=async(n,N)=>{k(!0),b(null);const L=await Promise.allSettled(N.map(R=>g.mutateAsync({id:R,body:{budget_id:n}})));k(!1);const I=L.flatMap((R,ne)=>R.status==="rejected"?[N[ne]]:[]);if(I.length>0){v({budgetId:n,userIds:I}),b(new Error(`Budget created, but could not assign it to: ${I.join(", ")}. Retry to try again.`));return}v(null),d(!1)},se=(n,N)=>{if(S){V(S.budgetId,S.userIds);return}b(null),r.mutate(n,{onSuccess:async L=>{if(N.length>0){await V(L.budget_id,N);return}d(!1)}})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(ue,{title:"Budgets",description:"Define spending limits and reset schedules. Assign a budget to users to enforce it.",action:h||K?null:e.jsx(c,{variant:"primary",onPress:()=>{m(null),b(null),v(null),d(!0)},children:"Create budget"})}),e.jsx(O,{error:t.error??r.error??o.error??l.error??g.error}),e.jsx(ce,{children:"Assign a budget to users when you create it, or later from the Users page. Each row’s usage aggregates the spend of the users currently on that budget."}),K?e.jsx(ke,{onCreate:()=>{m(null),b(null),v(null),d(!0)}}):null,h?e.jsx(G,{title:"Create budget",submitLabel:S?"Retry assignments":"Create budget",initial:{name:null,max_budget:null,budget_duration_sec:null},error:r.error??u,isPending:r.isPending||B,assignUsers:a.data??[],onSubmit:se,onClose:()=>{b(null),v(null),d(!1)}}):null,y?e.jsx(G,{title:`Edit budget ${M(y)}`,submitLabel:"Save changes",initial:{name:y.name,max_budget:y.max_budget,budget_duration_sec:y.budget_duration_sec},error:o.error,isPending:o.isPending,onSubmit:n=>o.mutate({id:y.budget_id,body:n},{onSuccess:()=>m(null)}),onClose:()=>m(null)},y.budget_id):null,_.length>0?e.jsx(he,{selectedCount:_.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:f.clear,children:e.jsx(c,{size:"sm",variant:"danger",onPress:()=>w(!0),children:"Delete"})}):null,e.jsx(fe,{ariaLabel:"Budgets",columns:te,rows:D,getRowKey:Ee,isLoading:H,emptyContent:"No budgets yet. Create one to cap spending.",selectionMode:"multiple",selectedKeys:f.selectedKeys,onSelectionChange:f.onSelectionChange}),U?e.jsx(C,{children:e.jsxs(C.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsxs("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:["Reset history — ",M(U)]}),e.jsx(c,{size:"sm",variant:"ghost",onPress:()=>s(null),children:"Close"})]}),e.jsx(Be,{budgetId:U.budget_id})]})}):null,e.jsx(pe,{isOpen:A,onOpenChange:w,heading:"Delete budgets",body:`Delete ${_.length} ${_.length===1?"budget":"budgets"}? Users on ${_.length===1?"it":"them"} will no longer be capped.`,confirmLabel:"Delete",isPending:J,error:X,onConfirm:ee})]})}export{Te as BudgetsPage}; diff --git a/src/gateway/static/dashboard/assets/ConfirmDialog-CvV5gDQA.js b/src/gateway/static/dashboard/assets/ConfirmDialog-CCJnWoSc.js similarity index 92% rename from src/gateway/static/dashboard/assets/ConfirmDialog-CvV5gDQA.js rename to src/gateway/static/dashboard/assets/ConfirmDialog-CCJnWoSc.js index 16651e84..afaa7866 100644 --- a/src/gateway/static/dashboard/assets/ConfirmDialog-CvV5gDQA.js +++ b/src/gateway/static/dashboard/assets/ConfirmDialog-CCJnWoSc.js @@ -1 +1 @@ -import{j as r}from"./tanstack-query-1t81HyiD.js";import{E as j}from"./index-DHOrDaWF.js";import{A as e,B as l}from"./heroui-DZj66Arc.js";function p({isOpen:s,onOpenChange:a,heading:n,body:o,confirmLabel:t,confirmVariant:c="danger",isPending:i,error:d,onConfirm:x}){return r.jsx(e,{isOpen:s,onOpenChange:a,children:s?r.jsx(e.Backdrop,{children:r.jsx(e.Container,{placement:"center",size:"md",children:r.jsxs(e.Dialog,{children:[r.jsx(e.Header,{children:r.jsx(e.Heading,{children:n})}),r.jsxs(e.Body,{className:"flex flex-col gap-4",children:[r.jsx("div",{className:"text-sm text-[var(--otari-muted)]",children:o}),r.jsx(j,{error:d})]}),r.jsxs(e.Footer,{children:[r.jsx(l,{variant:"ghost",isDisabled:i,onPress:()=>a(!1),children:"Cancel"}),r.jsx(l,{variant:c,isPending:i,onPress:x,children:t})]})]})})}):null})}export{p as C}; +import{j as r}from"./tanstack-query-1t81HyiD.js";import{E as j}from"./index-BzgMrLHf.js";import{A as e,B as l}from"./heroui-DZj66Arc.js";function p({isOpen:s,onOpenChange:a,heading:n,body:o,confirmLabel:t,confirmVariant:c="danger",isPending:i,error:d,onConfirm:x}){return r.jsx(e,{isOpen:s,onOpenChange:a,children:s?r.jsx(e.Backdrop,{children:r.jsx(e.Container,{placement:"center",size:"md",children:r.jsxs(e.Dialog,{children:[r.jsx(e.Header,{children:r.jsx(e.Heading,{children:n})}),r.jsxs(e.Body,{className:"flex flex-col gap-4",children:[r.jsx("div",{className:"text-sm text-[var(--otari-muted)]",children:o}),r.jsx(j,{error:d})]}),r.jsxs(e.Footer,{children:[r.jsx(l,{variant:"ghost",isDisabled:i,onPress:()=>a(!1),children:"Cancel"}),r.jsx(l,{variant:c,isPending:i,onPress:x,children:t})]})]})})}):null})}export{p as C}; diff --git a/src/gateway/static/dashboard/assets/KeysPage-C2ndVSn9.js b/src/gateway/static/dashboard/assets/KeysPage-D5EjvMLy.js similarity index 98% rename from src/gateway/static/dashboard/assets/KeysPage-C2ndVSn9.js rename to src/gateway/static/dashboard/assets/KeysPage-D5EjvMLy.js index 318ac9d9..c7dcec7c 100644 --- a/src/gateway/static/dashboard/assets/KeysPage-C2ndVSn9.js +++ b/src/gateway/static/dashboard/assets/KeysPage-D5EjvMLy.js @@ -1,4 +1,4 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as l}from"./react-dgEcD0HR.js";import{a as Q,r as q,s as X,t as Z,P as ee,E as K,v as te,u as U,I as se}from"./index-DHOrDaWF.js";import{u as ae,r as ne,B as re}from"./tableSelection-CojkFPwd.js";import{C as ie}from"./ConfirmDialog-CvV5gDQA.js";import{D as le}from"./DataTable-CrQq0sYf.js";import{F as I}from"./Field-DEJ6Wjg9.js";import{a as F,M as W}from"./ModelScopeControl-CTGSrkZ9.js";import{C as D,L as oe,I as ce,a as de,b as ue,D as me,f as A,B as p,d as k}from"./heroui-DZj66Arc.js";function xe({value:t,onChange:a,users:n,description:i}){const u=n.filter(r=>!r.user_id.startsWith("apikey-")).map(r=>({id:r.user_id,name:r.alias?`${r.user_id} (${r.alias})`:r.user_id})),[o,c]=l.useState(t),x=o.trim().toLowerCase(),y=u.filter(r=>!x||r.id.toLowerCase().includes(x)||r.name.toLowerCase().includes(x)).slice(0,50),g=r=>{const m=r.trim(),v=u.find(j=>j.id===m||j.name===m);return v?v.id:m},d=g(o),h=u.some(r=>r.id===d),f=d!==""&&!h?e.jsxs("span",{children:["Creates a new user ",e.jsx("code",{children:d}),"."]}):i??"Spend and budgets track against this user.";return e.jsxs(D.Root,{allowsCustomValue:!0,allowsEmptyCollection:!0,menuTrigger:"focus",inputValue:o,onInputChange:r=>{c(r),a(g(r))},onSelectionChange:r=>{if(r!=null){const m=String(r);c(m),a(m)}},className:"flex max-w-md flex-col gap-1",children:[e.jsx(oe,{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Owner"}),e.jsxs(D.InputGroup,{children:[e.jsx(ce,{placeholder:"Pick a user, or type a new id…",autoComplete:"off","data-1p-ignore":!0,"data-lpignore":"true",onFocus:r=>r.currentTarget.select()}),e.jsx(D.Trigger,{})]}),e.jsx(D.Popover,{children:e.jsx(de,{items:y,className:"max-h-72 overflow-auto",children:r=>e.jsx(ue,{id:r.id,textValue:r.name,children:r.name})})}),e.jsx(me,{className:"text-xs text-[var(--otari-muted)]",children:f})]})}function H(t){if(!t)return"—";const a=new Date(t);return Number.isNaN(a.getTime())?"—":a.toLocaleDateString()}function he(t){if(!t)return null;const a=new Date(t).getTime();if(Number.isNaN(a))return null;const n=Math.round((a-Date.now())/1e3),i=Math.abs(n),u=[["day",86400],["hour",3600],["minute",60]],o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto"});for(const[c,x]of u)if(i>=x)return o.format(Math.round(n/x),c);return o.format(n,"second")}function fe(t){if(!t.expires_at)return!1;const a=new Date(t.expires_at).getTime();return!Number.isNaN(a)&&aString(i).padStart(2,"0");return`${a.getFullYear()}-${n(a.getMonth()+1)}-${n(a.getDate())}T${n(a.getHours())}:${n(a.getMinutes())}`}const ge=t=>(t??"").startsWith("apikey-"),M=t=>t.key_name??t.id,ye=t=>t.id;function O({label:t,value:a,multiline:n=!1,fieldRef:i}){const u=l.useRef(null),o=i??u,[c,x]=l.useState(!1),[y,g]=l.useState(!1),d=async()=>{var f,r,m;(f=o.current)==null||f.focus(),(r=o.current)==null||r.select();try{if((m=navigator.clipboard)!=null&&m.writeText){await navigator.clipboard.writeText(a),x(!0),g(!1),window.setTimeout(()=>x(!1),2e3);return}}catch{}g(!0)},h="w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 font-mono text-xs text-[var(--otari-ink)]";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),e.jsx(p,{size:"sm",variant:"outline",onPress:d,children:c?"Copied":"Copy"})]}),n?e.jsx("textarea",{ref:o,readOnly:!0,rows:a.split(` +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as l}from"./react-dgEcD0HR.js";import{a as Q,r as q,s as X,t as Z,P as ee,E as K,v as te,u as U,I as se}from"./index-BzgMrLHf.js";import{u as ae,r as ne,B as re}from"./tableSelection-CojkFPwd.js";import{C as ie}from"./ConfirmDialog-CCJnWoSc.js";import{D as le}from"./DataTable-CrQq0sYf.js";import{F as I}from"./Field-DEJ6Wjg9.js";import{a as F,M as W}from"./ModelScopeControl-Dz7NaZdS.js";import{C as D,L as oe,I as ce,a as de,b as ue,D as me,f as A,B as p,d as k}from"./heroui-DZj66Arc.js";function xe({value:t,onChange:a,users:n,description:i}){const u=n.filter(r=>!r.user_id.startsWith("apikey-")).map(r=>({id:r.user_id,name:r.alias?`${r.user_id} (${r.alias})`:r.user_id})),[o,c]=l.useState(t),x=o.trim().toLowerCase(),y=u.filter(r=>!x||r.id.toLowerCase().includes(x)||r.name.toLowerCase().includes(x)).slice(0,50),g=r=>{const m=r.trim(),v=u.find(j=>j.id===m||j.name===m);return v?v.id:m},d=g(o),h=u.some(r=>r.id===d),f=d!==""&&!h?e.jsxs("span",{children:["Creates a new user ",e.jsx("code",{children:d}),"."]}):i??"Spend and budgets track against this user.";return e.jsxs(D.Root,{allowsCustomValue:!0,allowsEmptyCollection:!0,menuTrigger:"focus",inputValue:o,onInputChange:r=>{c(r),a(g(r))},onSelectionChange:r=>{if(r!=null){const m=String(r);c(m),a(m)}},className:"flex max-w-md flex-col gap-1",children:[e.jsx(oe,{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Owner"}),e.jsxs(D.InputGroup,{children:[e.jsx(ce,{placeholder:"Pick a user, or type a new id…",autoComplete:"off","data-1p-ignore":!0,"data-lpignore":"true",onFocus:r=>r.currentTarget.select()}),e.jsx(D.Trigger,{})]}),e.jsx(D.Popover,{children:e.jsx(de,{items:y,className:"max-h-72 overflow-auto",children:r=>e.jsx(ue,{id:r.id,textValue:r.name,children:r.name})})}),e.jsx(me,{className:"text-xs text-[var(--otari-muted)]",children:f})]})}function H(t){if(!t)return"—";const a=new Date(t);return Number.isNaN(a.getTime())?"—":a.toLocaleDateString()}function he(t){if(!t)return null;const a=new Date(t).getTime();if(Number.isNaN(a))return null;const n=Math.round((a-Date.now())/1e3),i=Math.abs(n),u=[["day",86400],["hour",3600],["minute",60]],o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto"});for(const[c,x]of u)if(i>=x)return o.format(Math.round(n/x),c);return o.format(n,"second")}function fe(t){if(!t.expires_at)return!1;const a=new Date(t.expires_at).getTime();return!Number.isNaN(a)&&aString(i).padStart(2,"0");return`${a.getFullYear()}-${n(a.getMonth()+1)}-${n(a.getDate())}T${n(a.getHours())}:${n(a.getMinutes())}`}const ge=t=>(t??"").startsWith("apikey-"),M=t=>t.key_name??t.id,ye=t=>t.id;function O({label:t,value:a,multiline:n=!1,fieldRef:i}){const u=l.useRef(null),o=i??u,[c,x]=l.useState(!1),[y,g]=l.useState(!1),d=async()=>{var f,r,m;(f=o.current)==null||f.focus(),(r=o.current)==null||r.select();try{if((m=navigator.clipboard)!=null&&m.writeText){await navigator.clipboard.writeText(a),x(!0),g(!1),window.setTimeout(()=>x(!1),2e3);return}}catch{}g(!0)},h="w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 font-mono text-xs text-[var(--otari-ink)]";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),e.jsx(p,{size:"sm",variant:"outline",onPress:d,children:c?"Copied":"Copy"})]}),n?e.jsx("textarea",{ref:o,readOnly:!0,rows:a.split(` `).length,value:a,onFocus:f=>f.currentTarget.select(),className:`${h} resize-none whitespace-pre`}):e.jsx("input",{ref:o,readOnly:!0,value:a,onFocus:f=>f.currentTarget.select(),className:h}),e.jsx("span",{"aria-live":"polite",className:"text-xs text-green-700",children:c?"Copied to clipboard.":""}),y?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Selected. Press Ctrl/Cmd-C to copy."}):null]})}function je({title:t,result:a,onClose:n}){const i=l.useRef(null),u=l.useRef(null),o=typeof window<"u"?window.location.origin:"",c=a.key;l.useEffect(()=>{var d,h;(d=u.current)==null||d.focus(),(h=u.current)==null||h.select()},[]);const x=d=>{var m;if(d.key!=="Tab")return;const h=(m=i.current)==null?void 0:m.querySelectorAll('button, input, textarea, a[href], [tabindex]:not([tabindex="-1"])');if(!h||h.length===0)return;const f=h[0],r=h[h.length-1];d.shiftKey&&document.activeElement===f?(d.preventDefault(),r.focus()):!d.shiftKey&&document.activeElement===r&&(d.preventDefault(),f.focus())},y=[`curl ${o}/v1/chat/completions \\`,` -H "Otari-Key: ${c}" \\`,' -H "Content-Type: application/json" \\',` -d '{"model": "your-model", "messages": [{"role": "user", "content": "Hello"}]}'`].join(` `),g=["from openai import OpenAI","",`client = OpenAI(base_url="${o}/v1", api_key="${c}")`,"resp = client.chat.completions.create(",' model="your-model",',' messages=[{"role": "user", "content": "Hello"}],',")","print(resp.choices[0].message.content)"].join(` `);return e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",role:"presentation",children:e.jsxs("div",{ref:i,role:"dialog","aria-modal":"true","aria-labelledby":"reveal-title",onKeyDown:x,className:"flex max-h-[90vh] w-full max-w-2xl flex-col gap-4 overflow-y-auto rounded-xl bg-[var(--otari-surface)] p-6 shadow-xl",children:[e.jsx("h2",{id:"reveal-title",className:"text-lg font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(se,{tone:"warning",children:"Copy this key now. For security it is shown only once and cannot be retrieved later. If you lose it, use Regenerate to issue a new secret."}),e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Model access: ",F(a.allowed_models).text,"."]}),e.jsx(O,{label:"Secret key",value:c,fieldRef:u}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Make your first call"}),e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Replace ",e.jsx("code",{children:"your-model"})," with a model from the Models page."]})]}),e.jsx(O,{label:"curl",value:y,multiline:!0}),e.jsx(O,{label:"Python (OpenAI SDK)",value:g,multiline:!0})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(p,{variant:"primary",onPress:n,children:"I’ve saved this key"})})]})})}function V({trigger:t,message:a,confirmLabel:n,isPending:i,onConfirm:u}){const[o,c]=l.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsx("span",{className:"max-w-xs text-xs text-amber-800",children:a}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(p,{size:"sm",variant:"danger",isDisabled:i,onPress:u,children:n}),e.jsx(p,{size:"sm",variant:"ghost",isDisabled:i,onPress:()=>c(!1),children:"Cancel"})]})]}):e.jsx(p,{size:"sm",variant:"danger-soft",onPress:()=>c(!0),children:t})}function G({userId:t,users:a}){const n=t.trim();if(n==="")return e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Choose an owner above to see the models this key can inherit."});const i=a.find(c=>c.user_id===n);if(!i)return e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["New user ",e.jsx("code",{children:n})," starts unrestricted, so this key may allow any model."]});const{text:u}=F(i.allowed_models),o=i.allowed_models&&i.allowed_models.length>0?i.allowed_models.join(", "):null;return e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Owner ",e.jsx("code",{children:n})," allows ",e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:u.toLowerCase()}),o?e.jsxs(e.Fragment,{children:[" (",e.jsx("span",{className:"font-mono",children:o}),")"]}):null,". This key inherits that, or narrows within it."]})}function Y({checked:t,onChange:a}){return e.jsxs("label",{className:"flex items-start gap-2 rounded-lg border border-[var(--otari-line)] p-3 text-sm",children:[e.jsx("input",{type:"checkbox",checked:t,onChange:n=>a(n.target.checked),className:"mt-0.5 h-4 w-4 accent-[var(--otari-brand)]","aria-label":"Exempt this key from budget"}),e.jsxs("span",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:"Exempt from budget"}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Requests on this key are logged with their cost but never counted toward the owner's budget or spend, and never blocked by it."})]})]})}function ve({onClose:t,onCreated:a}){const n=te(),i=U(),[u,o]=l.useState(""),[c,x]=l.useState(""),[y,g]=l.useState(!1),[d,h]=l.useState(""),[f,r]=l.useState(null),[m,v]=l.useState(!1),[j,T]=l.useState(!0),_=c!==""&&new Date(c).getTime(){if(n.isPending||!j||N)return;const b={key_name:u.trim()||null,user_id:d.trim(),expires_at:c?new Date(c).toISOString():null,allowed_models:f,exclude_from_budget:m};n.mutate(b,{onSuccess:w=>{a(w),t()}})};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Create API key"}),e.jsx(K,{error:n.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(I,{label:"Name",value:u,onChange:o,placeholder:"ci-bot",autoFocus:!0,description:"A label to recognize this key later."}),e.jsx(I,{label:"Expires (optional)",value:c,onChange:x,type:"datetime-local",description:_?e.jsx("span",{className:"text-red-700",children:"That time is in the past; the key would be rejected immediately."}):"Leave blank for a key that never expires."})]}),e.jsx(xe,{value:d,onChange:h,users:i.data??[]}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>g(b=>!b),children:y?"Hide advanced":"Advanced"}),y?e.jsxs("div",{className:"flex flex-col gap-4 rounded-lg border border-[var(--otari-line)] p-4",children:[e.jsx(G,{userId:d,users:i.data??[]}),e.jsx(W,{title:"Restrict this key's models",description:"By default this key inherits its owner's access. Optionally narrow it to a subset; a key can never exceed its owner's allowed models.",anyLabel:"Inherit owner access",initial:null,onChange:(b,w)=>{r(b),T(w)}}),e.jsx(Y,{checked:m,onChange:v})]}):null,e.jsxs("div",{className:"flex gap-2",children:[e.jsx(p,{variant:"primary",isDisabled:n.isPending||!j||N,onPress:S,children:n.isPending?"Creating…":"Create key"}),e.jsx(p,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function be({apiKey:t,onClose:a}){const n=q(),i=U(),[u,o]=l.useState(t.key_name??""),[c,x]=l.useState(pe(t.expires_at)),[y,g]=l.useState(t.allowed_models),[d,h]=l.useState(t.exclude_from_budget),[f,r]=l.useState(!0),m=()=>{n.isPending||!f||n.mutate({id:t.id,body:{key_name:u.trim()||null,expires_at:c?new Date(c).toISOString():null,allowed_models:y,exclude_from_budget:d}},{onSuccess:a})};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.key_name??t.id})]}),e.jsx(K,{error:n.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(I,{label:"Name",value:u,onChange:o,placeholder:"ci-bot"}),e.jsx(I,{label:"Expires",value:c,onChange:x,type:"datetime-local",description:"Blank clears the expiry."})]}),t.user_id?e.jsx(G,{userId:t.user_id,users:i.data??[]}):null,e.jsx(W,{title:"Restrict this key's models",description:"This key inherits its owner's access by default. Narrow it to a subset here; it can never exceed the owner's allowed models.",anyLabel:"Inherit owner access",initial:t.allowed_models,onChange:(v,j)=>{g(v),r(j)}}),e.jsx(Y,{checked:d,onChange:h}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(p,{variant:"primary",isDisabled:n.isPending||!f,onPress:m,children:n.isPending?"Saving…":"Save changes"}),e.jsx(p,{variant:"ghost",onPress:a,children:"Cancel"})]})]})})}function we({apiKey:t}){return t.is_active?fe(t)?e.jsx(A,{size:"sm",color:"warning",children:"Expired"}):e.jsx(A,{size:"sm",color:"accent",children:"Active"}):e.jsx(A,{size:"sm",color:"default",children:"Disabled"})}function Ne({allowed:t}){const{text:a,tone:n}=F(t),i=n==="danger"?"text-red-700 font-medium":n==="muted"?"text-[var(--otari-muted)]":"text-[var(--otari-brand-dark)] font-medium",u=t&&t.length>0?t.join(", "):void 0;return e.jsx("span",{className:`text-xs ${i}`,title:u,children:a})}function ke({onCreate:t}){return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"No API keys yet"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"An API key authenticates callers to this gateway. Create one to make your first request; the secret is shown once, so keep it somewhere safe."})]}),e.jsx("div",{children:e.jsx(p,{variant:"primary",onPress:t,children:"Create your first key"})})]})})}function Re(){const t=Q(),a=q(),n=X(),i=Z(),[u,o]=l.useState(!1),[c,x]=l.useState(null),[y,g]=l.useState(null),d=t.data??[],h=t.isLoading,f=d.find(s=>s.id===c)??null,r=!h&&d.length===0&&!u,m=ae(),[v,j]=l.useState(!1),[T,_]=l.useState(void 0),[N,S]=l.useState(!1),b=d.map(s=>s.id),w=ne(m.selectedKeys,b),E=d.filter(s=>w.includes(s.id)),$=l.useCallback((s,C)=>a.mutate({id:s.id,body:{is_active:C}}),[a.mutate]),z=l.useCallback(s=>n.mutate(s.id,{onSuccess:C=>g({title:`New secret for ${M(s)}`,result:C})}),[n.mutate]),R=async(s,C,B)=>{S(!0),_(void 0);try{for(const L of s)await C(L);m.clear(),B==null||B()}catch(L){_(L)}finally{S(!1)}},J=l.useMemo(()=>[{id:"name",header:"Name",isRowHeader:!0,cell:s=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:s.key_name??e.jsx("span",{className:"text-[var(--otari-muted)]",children:"(unnamed)"})}),e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx(Ne,{allowed:s.allowed_models}),s.exclude_from_budget?e.jsx("span",{className:"inline-flex items-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-brand-tint)] px-2 py-0.5 text-xs font-medium text-[var(--otari-brand-dark)]",title:"Requests on this key are logged with cost but never counted toward budget",children:"Budget-exempt"}):null]})]})},{id:"status",header:"Status",cell:s=>e.jsx(we,{apiKey:s})},{id:"owner",header:"Owner",cell:s=>ge(s.user_id)?e.jsx(A,{size:"sm",color:"default",children:"virtual"}):e.jsx("code",{className:"text-xs text-[var(--otari-muted)]",children:s.user_id??"—"})},{id:"key",header:"Key",cell:s=>e.jsx("code",{className:"text-xs text-[var(--otari-muted)]",children:s.key_prefix?`${s.key_prefix}…`:"—"})},{id:"created",header:"Created",cell:s=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:H(s.created_at)})},{id:"last_used",header:"Last used",cell:s=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:he(s.last_used_at)??"never"})},{id:"expires",header:"Expires",cell:s=>e.jsx("span",{className:"text-[var(--otari-muted)]",title:s.expires_at?new Date(s.expires_at).toLocaleString():void 0,children:s.expires_at?H(s.expires_at):"never"})},{id:"actions",header:"Actions",align:"end",cell:s=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(p,{size:"sm",variant:"outline",isDisabled:a.isPending,onPress:()=>$(s,!s.is_active),children:s.is_active?"Disable":"Enable"}),e.jsx(p,{size:"sm",variant:"ghost",onPress:()=>{o(!1),x(s.id)},children:"Edit"}),e.jsx(V,{trigger:"Regenerate",confirmLabel:"Regenerate",isPending:n.isPending,message:e.jsxs(e.Fragment,{children:["Regenerate the secret for ",e.jsx("strong",{children:M(s)}),"? The current secret stops working immediately, with no grace period."]}),onConfirm:()=>z(s)}),s.is_active?null:e.jsx(V,{trigger:"Delete",confirmLabel:"Delete permanently",isPending:i.isPending,message:e.jsxs(e.Fragment,{children:["Permanently delete ",e.jsx("strong",{children:M(s)}),"? This removes the key and unlinks its usage history. Cannot be undone."]}),onConfirm:()=>i.mutate(s.id)})]})}],[a.isPending,n.isPending,i.isPending,i.mutate,$,z]),P=E.filter(s=>!s.is_active);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(ee,{title:"API keys",description:"Issue and revoke the keys that authenticate callers to this gateway. Secrets are shown once at creation.",action:u?null:e.jsx(p,{variant:"primary",onPress:()=>{x(null),o(!0)},children:"Create key"})}),e.jsx(K,{error:t.error??a.error??n.error??i.error}),r?e.jsx(ke,{onCreate:()=>{x(null),o(!0)}}):null,u?e.jsx(ve,{onClose:()=>o(!1),onCreated:s=>g({title:"API key created",result:s})}):null,f?e.jsx(be,{apiKey:f,onClose:()=>x(null)},f.id):null,w.length>0?e.jsxs(re,{selectedCount:w.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:m.clear,children:[e.jsx(p,{size:"sm",variant:"outline",isDisabled:N,onPress:()=>void R(E,s=>a.mutateAsync({id:s.id,body:{is_active:!1}})),children:"Disable"}),e.jsx(p,{size:"sm",variant:"outline",isDisabled:N,onPress:()=>void R(E,s=>a.mutateAsync({id:s.id,body:{exclude_from_budget:!0}})),children:"Budget-exempt"}),e.jsx(p,{size:"sm",variant:"danger",isDisabled:P.length===0,onPress:()=>j(!0),children:"Delete"})]}):null,e.jsx(le,{ariaLabel:"API keys",columns:J,rows:d,getRowKey:ye,isLoading:h,emptyContent:"No API keys yet. Create one to authenticate a caller.",selectionMode:"multiple",selectedKeys:m.selectedKeys,onSelectionChange:m.onSelectionChange}),e.jsx(ie,{isOpen:v,onOpenChange:j,heading:"Delete API keys",body:`Permanently delete ${P.length} disabled ${P.length===1?"key":"keys"}? This removes them and unlinks their usage history. Cannot be undone. Active keys in the selection are skipped; disable them first.`,confirmLabel:"Delete permanently",isPending:N,error:T,onConfirm:()=>void R(P,s=>i.mutateAsync(s.id),()=>j(!1))}),y?e.jsx(je,{title:y.title,result:y.result,onClose:()=>{g(null),n.reset()}}):null]})}export{Re as KeysPage}; diff --git a/src/gateway/static/dashboard/assets/ModelScopeControl-CTGSrkZ9.js b/src/gateway/static/dashboard/assets/ModelScopeControl-Dz7NaZdS.js similarity index 98% rename from src/gateway/static/dashboard/assets/ModelScopeControl-CTGSrkZ9.js rename to src/gateway/static/dashboard/assets/ModelScopeControl-Dz7NaZdS.js index 33179af6..71257c5e 100644 --- a/src/gateway/static/dashboard/assets/ModelScopeControl-CTGSrkZ9.js +++ b/src/gateway/static/dashboard/assets/ModelScopeControl-Dz7NaZdS.js @@ -1 +1 @@ -import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as n}from"./react-dgEcD0HR.js";import{K as A,h as $,i as P}from"./index-DHOrDaWF.js";import{C as d,I as R,a as T,b as V}from"./heroui-DZj66Arc.js";function q(r){return r===null?"any":r.length===0?"block":"only"}const K=50;function W({initial:r,onChange:m,title:N="Model access",description:w,anyLabel:C="Any model"}){const x=A(),u=$(),v=P(),[i,S]=n.useState(q(r)),[a,g]=n.useState(r??[]),[p,y]=n.useState(""),f=n.useMemo(()=>{var j,k;const e=new Set,s=[],l=(o,c)=>{o&&!e.has(o)&&(e.add(o),s.push({id:o,label:c}))};for(const o of((j=x.data)==null?void 0:j.providers)??[])l(`${o.instance}:*`,`${o.instance}:* · all ${o.instance} models`);for(const o of((k=u.data)==null?void 0:k.providers)??[])for(const c of o.models)l(c.key,c.key);for(const o of v.data??[])l(o.target,`${o.name} · alias`);return s},[x.data,u.data,v.data]),L=n.useMemo(()=>{const e=p.trim().toLowerCase();return f.filter(s=>!a.includes(s.id)).filter(s=>!e||s.id.toLowerCase().includes(e)||s.label.toLowerCase().includes(e)).slice(0,K)},[f,a,p]),h=(e,s)=>{e==="any"?m(null,!0):e==="block"?m([],!0):m(s,s.length>0)},B=e=>{S(e),h(e,a)},M=e=>{const s=a.includes(e)?a:[...a,e];g(s),y(""),h("only",s)},E=e=>{const s=a.filter(l=>l!==e);g(s),h("only",s)},b=(e,s)=>t.jsx("button",{type:"button","aria-pressed":i===e,onClick:()=>B(e),className:i===e?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:s}),I=!u.isLoading&&!x.isLoading&&f.length===0;return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:N}),t.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:w??"Which models this key may list and call. The master key is never restricted, so blocking a key cannot lock you out of the dashboard."})]}),t.jsxs("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[b("any",C),b("only","Only selected"),b("block","Block all")]}),i==="block"?t.jsxs("div",{className:"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800",children:["Blocked from ",t.jsx("strong",{children:"every"})," model until you change this access."]}):null,i==="only"?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("div",{className:"flex flex-wrap gap-1.5",children:a.length===0?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Pick at least one model below, or choose “Block all”."}):a.map(e=>t.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[e,t.jsx("button",{type:"button","aria-label":`Remove ${e}`,onClick:()=>E(e),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},e))}),I?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No providers or models discovered yet. Configure a provider first, then scope this key."}):t.jsxs(d.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:p,onInputChange:y,selectedKey:null,onSelectionChange:e=>{e!=null&&M(String(e))},className:"flex max-w-md flex-col gap-1",children:[t.jsxs(d.InputGroup,{children:[t.jsx(R,{"aria-label":"Add a model",placeholder:"Search providers, models, aliases…",autoComplete:"off"}),t.jsx(d.Trigger,{})]}),t.jsx(d.Popover,{children:t.jsx(T,{items:L,className:"max-h-72 overflow-auto",children:e=>t.jsx(V,{id:e.id,textValue:e.label,children:e.label})})})]})]}):null]})}function X(r){return r===null?{text:"All models",tone:"muted"}:r.length===0?{text:"No models",tone:"danger"}:{text:"Selected models",tone:"normal"}}export{W as M,X as a}; +import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as n}from"./react-dgEcD0HR.js";import{K as A,h as $,i as P}from"./index-BzgMrLHf.js";import{C as d,I as R,a as T,b as V}from"./heroui-DZj66Arc.js";function q(r){return r===null?"any":r.length===0?"block":"only"}const K=50;function W({initial:r,onChange:m,title:N="Model access",description:w,anyLabel:C="Any model"}){const x=A(),u=$(),v=P(),[i,S]=n.useState(q(r)),[a,g]=n.useState(r??[]),[p,y]=n.useState(""),f=n.useMemo(()=>{var j,k;const e=new Set,s=[],l=(o,c)=>{o&&!e.has(o)&&(e.add(o),s.push({id:o,label:c}))};for(const o of((j=x.data)==null?void 0:j.providers)??[])l(`${o.instance}:*`,`${o.instance}:* · all ${o.instance} models`);for(const o of((k=u.data)==null?void 0:k.providers)??[])for(const c of o.models)l(c.key,c.key);for(const o of v.data??[])l(o.target,`${o.name} · alias`);return s},[x.data,u.data,v.data]),L=n.useMemo(()=>{const e=p.trim().toLowerCase();return f.filter(s=>!a.includes(s.id)).filter(s=>!e||s.id.toLowerCase().includes(e)||s.label.toLowerCase().includes(e)).slice(0,K)},[f,a,p]),h=(e,s)=>{e==="any"?m(null,!0):e==="block"?m([],!0):m(s,s.length>0)},B=e=>{S(e),h(e,a)},M=e=>{const s=a.includes(e)?a:[...a,e];g(s),y(""),h("only",s)},E=e=>{const s=a.filter(l=>l!==e);g(s),h("only",s)},b=(e,s)=>t.jsx("button",{type:"button","aria-pressed":i===e,onClick:()=>B(e),className:i===e?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:s}),I=!u.isLoading&&!x.isLoading&&f.length===0;return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:N}),t.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:w??"Which models this key may list and call. The master key is never restricted, so blocking a key cannot lock you out of the dashboard."})]}),t.jsxs("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[b("any",C),b("only","Only selected"),b("block","Block all")]}),i==="block"?t.jsxs("div",{className:"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800",children:["Blocked from ",t.jsx("strong",{children:"every"})," model until you change this access."]}):null,i==="only"?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("div",{className:"flex flex-wrap gap-1.5",children:a.length===0?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Pick at least one model below, or choose “Block all”."}):a.map(e=>t.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[e,t.jsx("button",{type:"button","aria-label":`Remove ${e}`,onClick:()=>E(e),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},e))}),I?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No providers or models discovered yet. Configure a provider first, then scope this key."}):t.jsxs(d.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:p,onInputChange:y,selectedKey:null,onSelectionChange:e=>{e!=null&&M(String(e))},className:"flex max-w-md flex-col gap-1",children:[t.jsxs(d.InputGroup,{children:[t.jsx(R,{"aria-label":"Add a model",placeholder:"Search providers, models, aliases…",autoComplete:"off"}),t.jsx(d.Trigger,{})]}),t.jsx(d.Popover,{children:t.jsx(T,{items:L,className:"max-h-72 overflow-auto",children:e=>t.jsx(V,{id:e.id,textValue:e.label,children:e.label})})})]})]}):null]})}function X(r){return r===null?{text:"All models",tone:"muted"}:r.length===0?{text:"No models",tone:"danger"}:{text:"Selected models",tone:"normal"}}export{W as M,X as a}; diff --git a/src/gateway/static/dashboard/assets/ModelsPage-CAW2PJJV.js b/src/gateway/static/dashboard/assets/ModelsPage-rWHJVsLp.js similarity index 99% rename from src/gateway/static/dashboard/assets/ModelsPage-CAW2PJJV.js rename to src/gateway/static/dashboard/assets/ModelsPage-rWHJVsLp.js index d091a2e1..9ae18f96 100644 --- a/src/gateway/static/dashboard/assets/ModelsPage-CAW2PJJV.js +++ b/src/gateway/static/dashboard/assets/ModelsPage-rWHJVsLp.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{i as gt,u as vt,r as c}from"./react-dgEcD0HR.js";import{w as _t,x as bt,h as jt,y as yt,z as je,P as Pt,E as Nt,F as B,I as St,A as ae,B as Re,C as ze,D as be,G as Ct,H as kt,J as E}from"./index-DHOrDaWF.js";import{u as Mt,r as Wt,B as It}from"./tableSelection-CojkFPwd.js";import{D as Tt}from"./DataTable-CrQq0sYf.js";import{T as Lt,S as At}from"./TablePagination-Dob9a4wG.js";import{B as A,d as se,f as V}from"./heroui-DZj66Arc.js";function Oe(t){const i=t.indexOf(":");return i>0?t.slice(0,i):"—"}function $t(t,i=Date.now()){const r=new Map;for(const n of t){const a=r.get(n.model_key)??[];a.push(n),r.set(n.model_key,a)}const s=[];for(const n of r.values()){const a=[...n].sort((p,u)=>Date.parse(p.effective_at)-Date.parse(u.effective_at)),f=[...a].reverse().find(p=>Date.parse(p.effective_at)<=i);s.push(f??a[0])}return s.sort((n,a)=>n.model_key.localeCompare(a.model_key))}const Et="otari",De=[{value:"vision",label:"Vision",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("image")},{value:"tool_call",label:"Tool calling",test:t=>!!t.tool_call},{value:"reasoning",label:"Reasoning",test:t=>!!t.reasoning},{value:"structured_output",label:"Structured output",test:t=>!!t.structured_output},{value:"attachment",label:"Attachments",test:t=>!!t.attachment},{value:"audio",label:"Audio",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("audio")},{value:"pdf",label:"PDF",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("pdf")}],Ot=[{key:"reasoning",label:"Reasoning"},{key:"tool_call",label:"Tool calling"},{key:"structured_output",label:"Structured output"},{key:"attachment",label:"Attachments"},{key:"temperature",label:"Temperature"}],Fe={text:"Text",image:"Image",audio:"Audio",video:"Video",pdf:"PDF"},Dt=[{value:"0",label:"Any context"},{value:"8000",label:"≥ 8K"},{value:"32000",label:"≥ 32K"},{value:"128000",label:"≥ 128K"},{value:"200000",label:"≥ 200K"},{value:"1000000",label:"≥ 1M"}],Ft=[{value:"",label:"Any price"},{value:"1",label:"≤ $1 / 1M in"},{value:"3",label:"≤ $3 / 1M in"},{value:"10",label:"≤ $10 / 1M in"},{value:"30",label:"≤ $30 / 1M in"}],Bt=[{value:"",label:"Base prices"},{value:"8000",label:"Compare at 8K"},{value:"128000",label:"Compare at 128K"},{value:"200000",label:"Compare at 200K"},{value:"500000",label:"Compare at 500K"},{value:"1000000",label:"Compare at 1M"}],Kt=[{value:"all",label:"Any release date"},{value:"365",label:"Past year"},{value:"730",label:"Past 2 years"},{value:"1095",label:"Past 3 years"}],Rt=1440*60*1e3,zt=t=>t.key;function Be(t,i){const r=`${i}:`;return t.startsWith(r)?t.slice(r.length):t}function Ke(t,i){const r={inputPrice:t.inputPrice,outputPrice:t.outputPrice,cacheReadPrice:t.cacheReadPrice,cacheWritePrice:t.cacheWritePrice,cacheWrite1hPrice:t.cacheWrite1hPrice};if(i==null)return r;const s=t.pricingTiers.filter(n=>n.min_input_tokens<=i).sort((n,a)=>a.min_input_tokens-n.min_input_tokens)[0];return s?{inputPrice:s.input_price_per_million??r.inputPrice,outputPrice:s.output_price_per_million??r.outputPrice,cacheReadPrice:s.cache_read_price_per_million??r.cacheReadPrice,cacheWritePrice:s.cache_write_price_per_million??r.cacheWritePrice,cacheWrite1hPrice:s.cache_write_1h_price_per_million??r.cacheWrite1hPrice}:r}function ce(t){const i=Number(t);return t.trim()!==""&&Number.isFinite(i)&&i>=0}function w(t){if(t.trim()==="")return!0;const i=Number(t);return Number.isFinite(i)&&i>=0}function Y(t){return t.trim()===""?null:Number(t)}function we(t){return t.map((i,r)=>({id:r,minInputTokens:String(i.min_input_tokens),input:i.input_price_per_million==null?"":String(i.input_price_per_million),output:i.output_price_per_million==null?"":String(i.output_price_per_million),cacheRead:i.cache_read_price_per_million==null?"":String(i.cache_read_price_per_million),cacheWrite:i.cache_write_price_per_million==null?"":String(i.cache_write_price_per_million),cacheWrite1h:i.cache_write_1h_price_per_million==null?"":String(i.cache_write_1h_price_per_million)}))}function Ve(t){const i=new Set;return t.every(r=>{const s=Number(r.minInputTokens),n=[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].some(a=>a.trim()!=="");return!Number.isInteger(s)||s<=0||i.has(s)||!n?!1:(i.add(s),[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].every(w))})}function Ye(t){return t.map(i=>({min_input_tokens:Number(i.minInputTokens),...i.input.trim()===""?{}:{input_price_per_million:Number(i.input)},...i.output.trim()===""?{}:{output_price_per_million:Number(i.output)},...i.cacheRead.trim()===""?{}:{cache_read_price_per_million:Number(i.cacheRead)},...i.cacheWrite.trim()===""?{}:{cache_write_price_per_million:Number(i.cacheWrite)},...i.cacheWrite1h.trim()===""?{}:{cache_write_1h_price_per_million:Number(i.cacheWrite1h)}}))}function b({value:t,onChange:i,ariaLabel:r}){return e.jsx("input",{type:"number",step:"any",min:"0",inputMode:"decimal","aria-label":r,value:t,onChange:s=>i(s.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})}function He({tiers:t,onChange:i}){const r=(n,a,f)=>{i(t.map(p=>p.id===n?{...p,[a]:f}:p))},s=()=>{const n=t.reduce((a,f)=>Math.max(a,f.id),-1)+1;i([...t,{id:n,minInputTokens:"128000",input:"",output:"",cacheRead:"",cacheWrite:"",cacheWrite1h:""}])};return e.jsxs("div",{className:"flex flex-col gap-2 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs font-medium text-[var(--otari-ink)]",children:"Long-context price tiers"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"At a threshold, listed rates replace the base rate for the whole request."})]}),e.jsx(A,{size:"sm",variant:"outline",onPress:s,children:"Add tier"})]}),t.map(n=>e.jsxs("div",{className:"flex flex-wrap items-end gap-2 border-t border-[var(--otari-line)] pt-2",children:[e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Context ≥ tokens",e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":"Tier context threshold",value:n.minInputTokens,onChange:a=>r(n.id,"minInputTokens",a.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Input",e.jsx(b,{value:n.input,onChange:a=>r(n.id,"input",a),ariaLabel:"Tier input price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Output",e.jsx(b,{value:n.output,onChange:a=>r(n.id,"output",a),ariaLabel:"Tier output price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache read",e.jsx(b,{value:n.cacheRead,onChange:a=>r(n.id,"cacheRead",a),ariaLabel:"Tier cache read price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache write",e.jsx(b,{value:n.cacheWrite,onChange:a=>r(n.id,"cacheWrite",a),ariaLabel:"Tier cache write price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["1h write",e.jsx(b,{value:n.cacheWrite1h,onChange:a=>r(n.id,"cacheWrite1h",a),ariaLabel:"Tier 1 hour cache write price"})]}),e.jsx(A,{size:"sm",variant:"ghost",onPress:()=>i(t.filter(a=>a.id!==n.id)),children:"Remove"})]},n.id))]})}function wt({source:t}){return t==="configured"?e.jsx(V,{size:"sm",color:"default",children:"configured"}):t==="default"||t==="alias"?e.jsx(V,{size:"sm",color:"accent",children:t}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not priced"})}function oe({label:t,tone:i="info",children:r}){const s=c.useId();return e.jsxs("span",{className:"group relative inline-flex items-center font-normal normal-case",children:[e.jsx("button",{type:"button","aria-label":t,"aria-describedby":s,className:`inline-flex h-4 w-4 items-center justify-center rounded-full border text-[10px] leading-none ${i==="warning"?"border-[#c2843a] text-[#b45309]":"border-[var(--otari-line)] text-[var(--otari-muted)] hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand)]"}`,children:"i"}),e.jsx("span",{id:s,role:"tooltip",className:"pointer-events-none absolute top-full right-0 z-20 mt-1.5 w-72 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-surface)] px-3 py-2 text-left text-xs font-normal whitespace-normal break-words text-[var(--otari-ink)] opacity-0 shadow-lg transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",children:r})]})}function Vt(){const t=kt();return t.data?t.data.default_pricing?e.jsx(oe,{label:"How unpriced models are metered",tone:"info",children:"Default pricing is on: models without a configured price are metered using community-maintained rates (the bundled genai-prices dataset). Set a price to override the fallback."}):e.jsxs(oe,{label:"How unpriced models are metered",tone:"warning",children:["Default pricing is off: only models with a configured price are metered.",t.data.require_pricing?" Requests for any other model are rejected (HTTP 402) because require_pricing is on.":" Other models are served without cost tracking."]}):null}function I({label:t,value:i}){return e.jsxs("div",{className:"flex items-baseline justify-between gap-3",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:t}),e.jsx("span",{className:"text-right text-sm text-[var(--otari-ink)] tabular-nums",children:i})]})}function le({title:t,children:i}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-xs font-semibold uppercase tracking-wide text-[var(--otari-muted)]",children:t}),i]})}function Yt({row:t}){const i=je(),r=Re(),[s,n]=c.useState(!1),[a,f]=c.useState(""),[p,u]=c.useState(""),[P,j]=c.useState(""),[k,T]=c.useState(""),[y,h]=c.useState(""),[N,O]=c.useState([]),S=()=>{f(t.inputPrice==null?"":String(t.inputPrice)),u(t.outputPrice==null?"":String(t.outputPrice)),j(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),T(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),h(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),O(we(t.pricingTiers)),n(!0)},Z=ce(a)&&ce(p)&&w(P)&&w(k)&&w(y)&&Ve(N),W=()=>{Z&&i.mutate({model_key:t.key,input_price_per_million:Number(a),output_price_per_million:Number(p),cache_read_price_per_million:Y(P),cache_write_price_per_million:Y(k),cache_write_1h_price_per_million:Y(y),pricing_tiers:Ye(N)},{onSuccess:()=>n(!1)})};return s?e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Input $ / 1M"}),e.jsx(b,{value:a,onChange:f,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Output $ / 1M"}),e.jsx(b,{value:p,onChange:u,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache read $ / 1M"}),e.jsx(b,{value:P,onChange:j,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache write $ / 1M"}),e.jsx(b,{value:k,onChange:T,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"1h cache write $ / 1M"}),e.jsx(b,{value:y,onChange:h,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(He,{tiers:N,onChange:O}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(A,{size:"sm",variant:"primary",isDisabled:i.isPending||!Z,onPress:W,children:"Save"}),e.jsx(A,{size:"sm",variant:"ghost",isDisabled:i.isPending,onPress:()=>n(!1),children:"Cancel"})]}),i.error?e.jsx("span",{className:"text-xs text-red-700",children:be(i.error)}):null]}):e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(I,{label:"Input",value:t.inputPrice==null?"—":`${E(t.inputPrice)} / 1M`}),e.jsx(I,{label:"Output",value:t.outputPrice==null?"—":`${E(t.outputPrice)} / 1M`}),e.jsx(I,{label:"Cache read",value:t.cacheReadPrice==null?"—":`${E(t.cacheReadPrice)} / 1M`}),e.jsx(I,{label:"Cache write",value:t.cacheWritePrice==null?"—":`${E(t.cacheWritePrice)} / 1M`}),e.jsx(I,{label:"1h cache write",value:t.cacheWrite1hPrice==null?"—":`${E(t.cacheWrite1hPrice)} / 1M`}),e.jsx(I,{label:"Context tiers",value:t.pricingTiers.length?`${t.pricingTiers.length} configured`:"—"}),e.jsxs("div",{className:"flex items-center gap-2 pt-1",children:[e.jsx(A,{size:"sm",variant:"outline",onPress:S,children:t.source==="configured"?"Edit price":"Set price"}),t.source==="configured"?e.jsxs(e.Fragment,{children:[e.jsx(ze,{confirmLabel:"Reset",isPending:r.isPending,onConfirm:()=>r.mutate(t.key),children:"Reset"}),e.jsx(oe,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error?e.jsx("span",{className:"text-xs text-red-700",children:be(r.error)}):null]})]})}function Ht({row:t,metadata:i,metadataAvailable:r,onMakeAlias:s,onClose:n}){const a=(i==null?void 0:i.input_modalities)??[],f=(i==null?void 0:i.output_modalities)??[],p=Ot.filter(({key:u})=>i==null?void 0:i[u]);return e.jsx(se,{children:e.jsxs(se.Content,{className:"flex flex-col gap-5 p-5",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-base font-semibold break-all text-[var(--otari-ink)]",children:t.model}),i!=null&&i.deprecated?e.jsx(V,{size:"sm",color:"danger",children:"deprecated"}):null]}),e.jsxs("p",{className:"mt-1 text-xs break-all text-[var(--otari-muted)]",children:["Selector: ",e.jsx("code",{children:t.key})]}),i!=null&&i.family?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:i.family}):null]}),e.jsx("button",{type:"button","aria-label":"Close model details",onClick:n,className:"-mt-1 -mr-1 shrink-0 rounded-md px-1.5 py-0.5 text-lg leading-none text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]",children:"✕"})]}),i!=null&&i.description?e.jsx("p",{className:"text-sm text-[var(--otari-ink)]",children:i.description}):null,e.jsxs(le,{title:"Pricing",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(wt,{source:t.source}),t.isDiscovered?null:e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not discovered"})]}),e.jsx(Yt,{row:t},t.key),e.jsx(A,{size:"sm",variant:"outline",onPress:()=>s(t.key),children:"Make an alias"})]}),e.jsxs(le,{title:"Specs",children:[e.jsx(I,{label:"Context window",value:ae(t.contextWindow)}),e.jsx(I,{label:"Max output",value:ae((i==null?void 0:i.max_output_tokens)??null)}),e.jsx(I,{label:"Knowledge cutoff",value:(i==null?void 0:i.knowledge_cutoff)??"—"}),e.jsx(I,{label:"Released",value:Ct(i==null?void 0:i.release_date)}),e.jsx(I,{label:"Open weights",value:i?i.open_weights?"Yes":"No":"—"})]}),e.jsx(le,{title:"Modalities",children:a.length===0&&f.length===0?e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:"Unknown."}):e.jsxs("div",{className:"flex flex-col gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"In:"}),a.map(u=>e.jsx(V,{size:"sm",color:"default",children:Fe[u]??u},u))]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"Out:"}),f.map(u=>e.jsx(V,{size:"sm",color:"default",children:Fe[u]??u},u))]})]})}),e.jsx(le,{title:"Capabilities",children:p.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:p.map(({key:u,label:P})=>e.jsx(V,{size:"sm",color:"default",children:P},u))}):e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:r?"None reported.":"Extended metadata unavailable (models.dev disabled or unreachable)."})})]})})}const qt=15;function Ut({value:t,onChange:i,placeholder:r}){return e.jsx("input",{type:"search",value:t,onChange:s=>i(s.target.value),placeholder:r,"aria-label":r,className:"w-full max-w-xs rounded-md border border-[var(--otari-line)] bg-white px-3 py-1.5 text-sm focus:border-[var(--otari-brand)] focus:outline-none"})}const qe="otari.dashboard.modelsSort",_e={col:"model",dir:"asc"},Gt=["model","released","input","output"];function Jt(){if(typeof window>"u")return _e;try{const t=window.localStorage.getItem(qe);if(!t)return _e;const i=JSON.parse(t);if(Gt.includes(i.col)&&(i.dir==="asc"||i.dir==="desc"))return{col:i.col,dir:i.dir}}catch{}return _e}function Xt({row:t,onClose:i}){const r=je(),s=Re(),[n,a]=c.useState(t.inputPrice==null?"":String(t.inputPrice)),[f,p]=c.useState(t.outputPrice==null?"":String(t.outputPrice)),[u,P]=c.useState(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),[j,k]=c.useState(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),[T,y]=c.useState(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),[h,N]=c.useState(we(t.pricingTiers)),O=ce(n)&&ce(f)&&w(u)&&w(j)&&w(T)&&Ve(h),S=()=>{O&&r.mutate({model_key:t.key,input_price_per_million:Number(n),output_price_per_million:Number(f),cache_read_price_per_million:Y(u),cache_write_price_per_million:Y(j),cache_write_1h_price_per_million:Y(T),pricing_tiers:Ye(h)},{onSuccess:i})};return e.jsxs("div",{className:"flex flex-col gap-3 px-4 py-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("span",{className:"text-xs font-medium break-all text-[var(--otari-muted)]",children:t.key}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Input $ / 1M",e.jsx(b,{value:n,onChange:a,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Output $ / 1M",e.jsx(b,{value:f,onChange:p,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache read $ / 1M",e.jsx(b,{value:u,onChange:P,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache write $ / 1M",e.jsx(b,{value:j,onChange:k,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["1h cache write $ / 1M",e.jsx(b,{value:T,onChange:y,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(A,{size:"sm",variant:"primary",isDisabled:r.isPending||!O,onPress:S,children:r.isPending?"Saving…":"Save"}),e.jsx(A,{size:"sm",variant:"ghost",isDisabled:r.isPending,onPress:i,children:"Cancel"}),t.source==="configured"?e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx(ze,{confirmLabel:"Reset",isPending:s.isPending,onConfirm:()=>s.mutate(t.key,{onSuccess:i}),children:"Reset"}),e.jsx(oe,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error||s.error?e.jsx("span",{className:"text-xs text-red-700",children:be(r.error??s.error)}):null]}),e.jsx(He,{tiers:h,onChange:N})]})}function Zt({primary:t,secondary:i,rowKey:r,primaryLabel:s,secondaryLabel:n,onEdit:a}){const f=(p,u)=>e.jsx("button",{type:"button","aria-label":`Edit ${u} price for ${r}`,className:"tabular-nums hover:text-[var(--otari-brand-dark)] hover:underline",onClick:P=>{P.stopPropagation(),a()},children:p==null?"—":E(p)});return e.jsxs("span",{className:"inline-flex items-center justify-end gap-1",children:[f(t,s),e.jsx("span",{className:"text-[var(--otari-muted)]",children:"/"}),f(i,n)]})}function Qt({rates:t,rowKey:i,onEdit:r}){const s=[t.cacheReadPrice==null?null:`R ${E(t.cacheReadPrice)}`,t.cacheWritePrice==null?null:`W ${E(t.cacheWritePrice)}`,t.cacheWrite1hPrice==null?null:`1h ${E(t.cacheWrite1hPrice)}`].filter(n=>n!==null);return e.jsx("button",{type:"button","aria-label":`Edit caching price for ${i}`,className:"max-w-44 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),r()},children:s.length>0?s.join(" · "):"Input-rate fallback"})}function ei({row:t,onEdit:i}){const r=[...t.pricingTiers].sort((n,a)=>n.min_input_tokens-a.min_input_tokens).map(n=>ae(n.min_input_tokens)),s=r.length===0?"Base only":`${r.length} tier${r.length===1?"":"s"} · ≥ ${r.join(", ")}`;return e.jsx("button",{type:"button","aria-label":`Edit pricing policy for ${t.key}`,className:"max-w-40 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),i()},children:s})}function ti({rows:t,isLoading:i,empty:r,sortDescriptor:s,onSortChange:n,selectedKey:a,onSelect:f,onEditPricing:p,comparisonContextTokens:u,selectedKeys:P,onSelectionChange:j}){const k=c.useMemo(()=>{const y=u==null?"Base":`at ${ae(u)}`;return[{id:"model",header:"Model",isRowHeader:!0,allowsSorting:!0,cell:h=>e.jsxs("span",{className:"font-medium break-all",children:[h.model,e.jsx("span",{className:"sr-only",children:h.key})]})},{id:"provider",header:"Provider",cell:h=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:h.provider})},{id:"input",header:e.jsxs("span",{className:"inline-flex items-center gap-1",children:[`${y} in / out $ / 1M`,e.jsx(Vt,{})]}),align:"end",allowsSorting:!0,cell:h=>{const N=Ke(h,u);return e.jsx(Zt,{primary:N.inputPrice,secondary:N.outputPrice,rowKey:h.key,primaryLabel:"input",secondaryLabel:"output",onEdit:()=>p(h.key)})}},{id:"caching",header:`Caching ${u==null?"policy":y}`,align:"end",cell:h=>e.jsx(Qt,{rates:Ke(h,u),rowKey:h.key,onEdit:()=>p(h.key)})},{id:"policy",header:"Pricing policy",align:"end",cell:h=>e.jsx(ei,{row:h,onEdit:()=>p(h.key)})}]},[u,p]),T=c.useCallback(y=>y.key===a?"bg-[var(--otari-brand-tint)]":void 0,[a]);return e.jsx(Tt,{ariaLabel:"Models",columns:k,rows:t,getRowKey:zt,isLoading:i,emptyContent:r,selectionMode:"multiple",selectedKeys:P,onSelectionChange:j,sortDescriptor:s,onSortChange:n,onRowAction:f,rowClassName:T})}function ii({providers:t}){return t.length===0?null:e.jsxs(St,{tone:"warning",children:["Could not list ",t.map(i=>i.provider).join(", "),". Check that provider's credentials in config.yml; its models are missing from the list below."]})}function pi(){var Te,Le,Ae;const t=gt(),[i]=vt(),r=_t(),s=bt(),n=jt(),a=yt(),f=je(),[p,u]=c.useState(""),[P,j]=c.useState(0),[k,T]=c.useState(qt),[y,h]=c.useState(null),[N,O]=c.useState(null),[S,Z]=c.useState(Jt),W=Mt(),[Ue,ue]=c.useState(!1),[Ge,ye]=c.useState(!1),[Je,Pe]=c.useState(void 0);c.useEffect(()=>{try{window.localStorage.setItem(qe,JSON.stringify(S))}catch{}},[S]);const[D,Ne]=c.useState(i.get("provider")||"all"),[K,Xe]=c.useState("all"),[H,Ze]=c.useState("all"),[q,Qe]=c.useState("all"),[de,et]=c.useState("0"),[Q,tt]=c.useState(""),[ee,it]=c.useState("all"),[pe,rt]=c.useState(""),F=((Te=a.data)==null?void 0:Te.models)??{},nt=((Le=a.data)==null?void 0:Le.available)??!1,Se=c.useMemo(()=>{var l;return new Set((((l=n.data)==null?void 0:l.providers)??[]).flatMap(m=>m.models.map(o=>o.key)))},[n.data]),lt=l=>{u(l),j(0)},R=l=>m=>{l(m),j(0)},te=c.useMemo(()=>{var M,v,_,$,L,z,$e;const l=new Map($t(s.data??[]).map(d=>[d.model_key,d])),m=[],o=new Set,g=(d,ve,Ee,x)=>{if(o.has(d))return;o.add(d);const C=l.get(d);m.push({key:d,model:Be(ve,Ee),provider:Ee,isDiscovered:Se.has(d),contextWindow:(x==null?void 0:x.contextWindow)??null,inputPrice:C?C.input_price_per_million:(x==null?void 0:x.inputPrice)??null,outputPrice:C?C.output_price_per_million:(x==null?void 0:x.outputPrice)??null,cacheReadPrice:C?C.cache_read_price_per_million:(x==null?void 0:x.cacheReadPrice)??null,cacheWritePrice:C?C.cache_write_price_per_million:(x==null?void 0:x.cacheWritePrice)??null,cacheWrite1hPrice:C?C.cache_write_1h_price_per_million??null:(x==null?void 0:x.cacheWrite1hPrice)??null,pricingTiers:C?C.pricing_tiers??[]:(x==null?void 0:x.pricingTiers)??[],source:C?"configured":(x==null?void 0:x.source)??"none"})};for(const d of((M=r.data)==null?void 0:M.data)??[]){if(d.owned_by===Et)continue;const ve=d.pricing_source==="default"?"default":d.pricing?"configured":"none";g(d.id,d.id,d.owned_by||Oe(d.id),{key:d.id,model:d.id,provider:d.owned_by,contextWindow:d.context_window,inputPrice:((v=d.pricing)==null?void 0:v.input_price_per_million)??null,outputPrice:((_=d.pricing)==null?void 0:_.output_price_per_million)??null,cacheReadPrice:(($=d.pricing)==null?void 0:$.cache_read_price_per_million)??null,cacheWritePrice:((L=d.pricing)==null?void 0:L.cache_write_price_per_million)??null,cacheWrite1hPrice:((z=d.pricing)==null?void 0:z.cache_write_1h_price_per_million)??null,pricingTiers:(($e=d.pricing)==null?void 0:$e.pricing_tiers)??[],source:ve})}for(const d of l.keys())g(d,d,Oe(d));return m},[r.data,s.data,Se]),Ce=c.useMemo(()=>new Map(te.map(l=>[l.key,l])),[te]),ie=c.useMemo(()=>{var o,g,M;const l=te.map(v=>{var _,$;return{...v,contextWindow:v.contextWindow??((_=F[v.key])==null?void 0:_.context_window)??null,releaseDate:(($=F[v.key])==null?void 0:$.release_date)??null}}),m=new Set(l.map(v=>v.key));for(const v of((o=n.data)==null?void 0:o.providers)??[])for(const _ of v.models)m.has(_.key)||(m.add(_.key),l.push({key:_.key,model:Be(_.key,v.provider),provider:v.provider,isDiscovered:!0,contextWindow:((g=F[_.key])==null?void 0:g.context_window)??null,releaseDate:((M=F[_.key])==null?void 0:M.release_date)??null,inputPrice:null,outputPrice:null,cacheReadPrice:null,cacheWritePrice:null,cacheWrite1hPrice:null,pricingTiers:[],source:"none"}));return l},[te,n.data,F]),at=(((Ae=n.data)==null?void 0:Ae.providers)??[]).filter(l=>!l.ok),re=c.useMemo(()=>{const l=Array.from(new Set(ie.map(m=>m.provider))).sort((m,o)=>m.localeCompare(o));return[{value:"all",label:"All providers"},...l.map(m=>({value:m,label:m}))]},[ie]);c.useEffect(()=>{D==="all"||re.length<=1||re.some(l=>l.value===D)||Ne("all")},[re,D]);const U=p.trim().toLowerCase(),he=Number(de)||0,me=Q===""?Number.POSITIVE_INFINITY:Number(Q),st=pe===""?null:Number(pe),xe=ee==="all"?null:Date.now()-Number(ee)*Rt,G=c.useMemo(()=>{const l=o=>{if(U&&!o.key.toLowerCase().includes(U)&&!o.provider.toLowerCase().includes(U)||D!=="all"&&o.provider!==D||K==="configured"&&o.source!=="configured"||K==="default"&&o.source!=="default"||K==="priced"&&o.inputPrice==null||K==="unpriced"&&o.inputPrice!=null||H==="discovered"&&!o.isDiscovered||H==="custom"&&o.isDiscovered)return!1;if(q!=="all"){const g=De.find(v=>v.value===q),M=F[o.key];if(!g||!M||!g.test(M))return!1}if(he>0&&(o.contextWindow==null||o.contextWindowme))return!1;if(xe!=null){const g=o.releaseDate?Date.parse(o.releaseDate):Number.NaN;if(Number.isNaN(g)||g{const M=S.dir==="asc"?1:-1;if(S.col==="model")return o.model.localeCompare(g.model)*M;if(S.col==="released"){const L=o.releaseDate??null,z=g.releaseDate??null;return!L&&!z?o.model.localeCompare(g.model):L?z?(Lz?1:0)*M||o.model.localeCompare(g.model):-1:1}const v=L=>S.col==="input"?L.inputPrice:L.outputPrice,_=v(o),$=v(g);return _==null&&$==null?o.model.localeCompare(g.model):_==null?1:$==null?-1:(_-$)*M||o.model.localeCompare(g.model)};return ie.filter(l).sort(m)},[ie,U,D,K,H,q,he,me,xe,F,S]),J=G.length,ct=Math.max(1,Math.ceil(J/k)),ke=Math.min(P,ct-1),Me=ke*k,fe=G.slice(Me,Me+k),ot={column:S.col,direction:S.dir==="asc"?"ascending":"descending"},ut=l=>{Z({col:String(l.column),dir:l.direction==="ascending"?"asc":"desc"}),j(0)},dt=c.useCallback(l=>h(m=>m===l?null:l),[]),ge=fe.map(l=>l.key),X=Wt(W.selectedKeys,ge),pt=ge.length>0&&X.length===ge.length&&J>X.length,ht=W.allMatching?G.map(l=>l.key):X,We=W.allMatching?J:X.length,Ie=y?G.find(l=>l.key===y)??Ce.get(y)??null:null,mt=async l=>{ye(!0),Pe(void 0);try{for(const m of ht)await f.mutateAsync({model_key:m,input_price_per_million:l.input_price_per_million,output_price_per_million:l.output_price_per_million,cache_read_price_per_million:l.cache_read_price_per_million??null,cache_write_price_per_million:l.cache_write_price_per_million??null,cache_write_1h_price_per_million:null,pricing_tiers:[]});W.clear(),ue(!1)}catch(m){Pe(m)}finally{ye(!1)}},xt=r.isLoading||s.isLoading||n.isLoading,ft=U!==""||D!=="all"||K!=="all"||H!=="all"||q!=="all"||de!=="0"||Q!==""||ee!=="all"?"No models match your filters.":"No models yet. Add a provider on the Providers page, or price a model below.",ne=N?G.find(l=>l.key===N)??Ce.get(N)??null:null;return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Pt,{title:"Models",description:"Every model your providers can serve. Set a price on any model so budgets and usage tracking work."}),e.jsx(Nt,{error:r.error??s.error??n.error??a.error}),e.jsxs("div",{className:`grid gap-4 lg:items-start ${ne?"lg:grid-cols-[minmax(0,1fr)_360px]":"grid-cols-1"}`,children:[e.jsxs("div",{className:"flex min-w-0 flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(Ut,{value:p,onChange:lt,placeholder:"Search models…"}),e.jsx(B,{ariaLabel:"Filter by provider",value:D,onChange:R(Ne),options:re}),e.jsx(B,{ariaLabel:"Filter by pricing",value:K,onChange:R(Xe),options:[{value:"all",label:"Any pricing"},{value:"configured",label:"Custom price"},{value:"default",label:"Default price"},{value:"priced",label:"Priced"},{value:"unpriced",label:"Unpriced"}]}),e.jsx(B,{ariaLabel:"Filter by source",value:H,onChange:R(Ze),options:[{value:"all",label:"Any source"},{value:"discovered",label:"Discovered"},{value:"custom",label:"Custom (not discovered)"}]}),e.jsx(B,{ariaLabel:"Filter by capability",value:q,onChange:R(Qe),options:[{value:"all",label:"Any capability"},...De.map(l=>({value:l.value,label:l.label}))]}),e.jsx(B,{ariaLabel:"Minimum context window",value:de,onChange:R(et),options:Dt}),e.jsx(B,{ariaLabel:"Maximum input price",value:Q,onChange:R(tt),options:Ft}),e.jsx(B,{ariaLabel:"Compare prices at context",value:pe,onChange:rt,options:Bt}),e.jsx(B,{ariaLabel:"Filter by release date",value:ee,onChange:R(it),options:Kt})]}),e.jsx(ii,{providers:at}),X.length>0?e.jsx(It,{selectedCount:We,allMatching:W.allMatching,matchingTotal:J,canSelectAllMatching:pt,onSelectAllMatching:W.enableAllMatching,onClear:W.clear,children:e.jsx(A,{size:"sm",variant:"primary",onPress:()=>ue(!0),children:"Set pricing"})}):null,e.jsx(ti,{rows:fe,isLoading:xt,empty:ft,sortDescriptor:ot,onSortChange:ut,selectedKey:N,onSelect:O,onEditPricing:dt,comparisonContextTokens:st,selectedKeys:W.selectedKeys,onSelectionChange:W.onSelectionChange}),Ie?e.jsx(se,{children:e.jsxs(se.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Edit pricing"}),e.jsx(A,{size:"sm",variant:"ghost",onPress:()=>h(null),children:"Close"})]}),e.jsx(Xt,{row:Ie,onClose:()=>h(null)})]})}):null,e.jsx(Lt,{page:ke,pageSize:k,total:J,rowsOnPage:fe.length,onPageChange:j,onPageSizeChange:l=>{T(l),j(0)},pageSizeOptions:[15,25,50]})]}),ne?e.jsx("aside",{className:"lg:sticky lg:top-4",children:e.jsx(Ht,{row:ne,metadata:F[ne.key],metadataAvailable:nt,onMakeAlias:l=>t(`/aliases?target=${encodeURIComponent(l)}`),onClose:()=>O(null)})}):null]}),e.jsx(At,{isOpen:Ue,onOpenChange:ue,targetCount:We,isPending:Ge,error:Je,onSubmit:mt,title:"Set pricing",description:l=>`Apply these per-1M rates to ${l.toLocaleString()} selected ${l===1?"model":"models"}. This replaces each model's price; pricing tiers and the 1h cache rate are cleared. Edit a single model for tiers.`})]})}export{pi as ModelsPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{i as gt,u as vt,r as c}from"./react-dgEcD0HR.js";import{w as _t,x as bt,h as jt,y as yt,z as je,P as Pt,E as Nt,F as B,I as St,A as ae,B as Re,C as ze,D as be,G as Ct,H as kt,J as E}from"./index-BzgMrLHf.js";import{u as Mt,r as Wt,B as It}from"./tableSelection-CojkFPwd.js";import{D as Tt}from"./DataTable-CrQq0sYf.js";import{T as Lt,S as At}from"./TablePagination-DlTV4kUZ.js";import{B as A,d as se,f as V}from"./heroui-DZj66Arc.js";function Oe(t){const i=t.indexOf(":");return i>0?t.slice(0,i):"—"}function $t(t,i=Date.now()){const r=new Map;for(const n of t){const a=r.get(n.model_key)??[];a.push(n),r.set(n.model_key,a)}const s=[];for(const n of r.values()){const a=[...n].sort((p,u)=>Date.parse(p.effective_at)-Date.parse(u.effective_at)),f=[...a].reverse().find(p=>Date.parse(p.effective_at)<=i);s.push(f??a[0])}return s.sort((n,a)=>n.model_key.localeCompare(a.model_key))}const Et="otari",De=[{value:"vision",label:"Vision",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("image")},{value:"tool_call",label:"Tool calling",test:t=>!!t.tool_call},{value:"reasoning",label:"Reasoning",test:t=>!!t.reasoning},{value:"structured_output",label:"Structured output",test:t=>!!t.structured_output},{value:"attachment",label:"Attachments",test:t=>!!t.attachment},{value:"audio",label:"Audio",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("audio")},{value:"pdf",label:"PDF",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("pdf")}],Ot=[{key:"reasoning",label:"Reasoning"},{key:"tool_call",label:"Tool calling"},{key:"structured_output",label:"Structured output"},{key:"attachment",label:"Attachments"},{key:"temperature",label:"Temperature"}],Fe={text:"Text",image:"Image",audio:"Audio",video:"Video",pdf:"PDF"},Dt=[{value:"0",label:"Any context"},{value:"8000",label:"≥ 8K"},{value:"32000",label:"≥ 32K"},{value:"128000",label:"≥ 128K"},{value:"200000",label:"≥ 200K"},{value:"1000000",label:"≥ 1M"}],Ft=[{value:"",label:"Any price"},{value:"1",label:"≤ $1 / 1M in"},{value:"3",label:"≤ $3 / 1M in"},{value:"10",label:"≤ $10 / 1M in"},{value:"30",label:"≤ $30 / 1M in"}],Bt=[{value:"",label:"Base prices"},{value:"8000",label:"Compare at 8K"},{value:"128000",label:"Compare at 128K"},{value:"200000",label:"Compare at 200K"},{value:"500000",label:"Compare at 500K"},{value:"1000000",label:"Compare at 1M"}],Kt=[{value:"all",label:"Any release date"},{value:"365",label:"Past year"},{value:"730",label:"Past 2 years"},{value:"1095",label:"Past 3 years"}],Rt=1440*60*1e3,zt=t=>t.key;function Be(t,i){const r=`${i}:`;return t.startsWith(r)?t.slice(r.length):t}function Ke(t,i){const r={inputPrice:t.inputPrice,outputPrice:t.outputPrice,cacheReadPrice:t.cacheReadPrice,cacheWritePrice:t.cacheWritePrice,cacheWrite1hPrice:t.cacheWrite1hPrice};if(i==null)return r;const s=t.pricingTiers.filter(n=>n.min_input_tokens<=i).sort((n,a)=>a.min_input_tokens-n.min_input_tokens)[0];return s?{inputPrice:s.input_price_per_million??r.inputPrice,outputPrice:s.output_price_per_million??r.outputPrice,cacheReadPrice:s.cache_read_price_per_million??r.cacheReadPrice,cacheWritePrice:s.cache_write_price_per_million??r.cacheWritePrice,cacheWrite1hPrice:s.cache_write_1h_price_per_million??r.cacheWrite1hPrice}:r}function ce(t){const i=Number(t);return t.trim()!==""&&Number.isFinite(i)&&i>=0}function w(t){if(t.trim()==="")return!0;const i=Number(t);return Number.isFinite(i)&&i>=0}function Y(t){return t.trim()===""?null:Number(t)}function we(t){return t.map((i,r)=>({id:r,minInputTokens:String(i.min_input_tokens),input:i.input_price_per_million==null?"":String(i.input_price_per_million),output:i.output_price_per_million==null?"":String(i.output_price_per_million),cacheRead:i.cache_read_price_per_million==null?"":String(i.cache_read_price_per_million),cacheWrite:i.cache_write_price_per_million==null?"":String(i.cache_write_price_per_million),cacheWrite1h:i.cache_write_1h_price_per_million==null?"":String(i.cache_write_1h_price_per_million)}))}function Ve(t){const i=new Set;return t.every(r=>{const s=Number(r.minInputTokens),n=[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].some(a=>a.trim()!=="");return!Number.isInteger(s)||s<=0||i.has(s)||!n?!1:(i.add(s),[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].every(w))})}function Ye(t){return t.map(i=>({min_input_tokens:Number(i.minInputTokens),...i.input.trim()===""?{}:{input_price_per_million:Number(i.input)},...i.output.trim()===""?{}:{output_price_per_million:Number(i.output)},...i.cacheRead.trim()===""?{}:{cache_read_price_per_million:Number(i.cacheRead)},...i.cacheWrite.trim()===""?{}:{cache_write_price_per_million:Number(i.cacheWrite)},...i.cacheWrite1h.trim()===""?{}:{cache_write_1h_price_per_million:Number(i.cacheWrite1h)}}))}function b({value:t,onChange:i,ariaLabel:r}){return e.jsx("input",{type:"number",step:"any",min:"0",inputMode:"decimal","aria-label":r,value:t,onChange:s=>i(s.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})}function He({tiers:t,onChange:i}){const r=(n,a,f)=>{i(t.map(p=>p.id===n?{...p,[a]:f}:p))},s=()=>{const n=t.reduce((a,f)=>Math.max(a,f.id),-1)+1;i([...t,{id:n,minInputTokens:"128000",input:"",output:"",cacheRead:"",cacheWrite:"",cacheWrite1h:""}])};return e.jsxs("div",{className:"flex flex-col gap-2 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs font-medium text-[var(--otari-ink)]",children:"Long-context price tiers"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"At a threshold, listed rates replace the base rate for the whole request."})]}),e.jsx(A,{size:"sm",variant:"outline",onPress:s,children:"Add tier"})]}),t.map(n=>e.jsxs("div",{className:"flex flex-wrap items-end gap-2 border-t border-[var(--otari-line)] pt-2",children:[e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Context ≥ tokens",e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":"Tier context threshold",value:n.minInputTokens,onChange:a=>r(n.id,"minInputTokens",a.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Input",e.jsx(b,{value:n.input,onChange:a=>r(n.id,"input",a),ariaLabel:"Tier input price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Output",e.jsx(b,{value:n.output,onChange:a=>r(n.id,"output",a),ariaLabel:"Tier output price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache read",e.jsx(b,{value:n.cacheRead,onChange:a=>r(n.id,"cacheRead",a),ariaLabel:"Tier cache read price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache write",e.jsx(b,{value:n.cacheWrite,onChange:a=>r(n.id,"cacheWrite",a),ariaLabel:"Tier cache write price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["1h write",e.jsx(b,{value:n.cacheWrite1h,onChange:a=>r(n.id,"cacheWrite1h",a),ariaLabel:"Tier 1 hour cache write price"})]}),e.jsx(A,{size:"sm",variant:"ghost",onPress:()=>i(t.filter(a=>a.id!==n.id)),children:"Remove"})]},n.id))]})}function wt({source:t}){return t==="configured"?e.jsx(V,{size:"sm",color:"default",children:"configured"}):t==="default"||t==="alias"?e.jsx(V,{size:"sm",color:"accent",children:t}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not priced"})}function oe({label:t,tone:i="info",children:r}){const s=c.useId();return e.jsxs("span",{className:"group relative inline-flex items-center font-normal normal-case",children:[e.jsx("button",{type:"button","aria-label":t,"aria-describedby":s,className:`inline-flex h-4 w-4 items-center justify-center rounded-full border text-[10px] leading-none ${i==="warning"?"border-[#c2843a] text-[#b45309]":"border-[var(--otari-line)] text-[var(--otari-muted)] hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand)]"}`,children:"i"}),e.jsx("span",{id:s,role:"tooltip",className:"pointer-events-none absolute top-full right-0 z-20 mt-1.5 w-72 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-surface)] px-3 py-2 text-left text-xs font-normal whitespace-normal break-words text-[var(--otari-ink)] opacity-0 shadow-lg transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",children:r})]})}function Vt(){const t=kt();return t.data?t.data.default_pricing?e.jsx(oe,{label:"How unpriced models are metered",tone:"info",children:"Default pricing is on: models without a configured price are metered using community-maintained rates (the bundled genai-prices dataset). Set a price to override the fallback."}):e.jsxs(oe,{label:"How unpriced models are metered",tone:"warning",children:["Default pricing is off: only models with a configured price are metered.",t.data.require_pricing?" Requests for any other model are rejected (HTTP 402) because require_pricing is on.":" Other models are served without cost tracking."]}):null}function I({label:t,value:i}){return e.jsxs("div",{className:"flex items-baseline justify-between gap-3",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:t}),e.jsx("span",{className:"text-right text-sm text-[var(--otari-ink)] tabular-nums",children:i})]})}function le({title:t,children:i}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-xs font-semibold uppercase tracking-wide text-[var(--otari-muted)]",children:t}),i]})}function Yt({row:t}){const i=je(),r=Re(),[s,n]=c.useState(!1),[a,f]=c.useState(""),[p,u]=c.useState(""),[P,j]=c.useState(""),[k,T]=c.useState(""),[y,h]=c.useState(""),[N,O]=c.useState([]),S=()=>{f(t.inputPrice==null?"":String(t.inputPrice)),u(t.outputPrice==null?"":String(t.outputPrice)),j(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),T(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),h(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),O(we(t.pricingTiers)),n(!0)},Z=ce(a)&&ce(p)&&w(P)&&w(k)&&w(y)&&Ve(N),W=()=>{Z&&i.mutate({model_key:t.key,input_price_per_million:Number(a),output_price_per_million:Number(p),cache_read_price_per_million:Y(P),cache_write_price_per_million:Y(k),cache_write_1h_price_per_million:Y(y),pricing_tiers:Ye(N)},{onSuccess:()=>n(!1)})};return s?e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Input $ / 1M"}),e.jsx(b,{value:a,onChange:f,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Output $ / 1M"}),e.jsx(b,{value:p,onChange:u,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache read $ / 1M"}),e.jsx(b,{value:P,onChange:j,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache write $ / 1M"}),e.jsx(b,{value:k,onChange:T,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"1h cache write $ / 1M"}),e.jsx(b,{value:y,onChange:h,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(He,{tiers:N,onChange:O}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(A,{size:"sm",variant:"primary",isDisabled:i.isPending||!Z,onPress:W,children:"Save"}),e.jsx(A,{size:"sm",variant:"ghost",isDisabled:i.isPending,onPress:()=>n(!1),children:"Cancel"})]}),i.error?e.jsx("span",{className:"text-xs text-red-700",children:be(i.error)}):null]}):e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(I,{label:"Input",value:t.inputPrice==null?"—":`${E(t.inputPrice)} / 1M`}),e.jsx(I,{label:"Output",value:t.outputPrice==null?"—":`${E(t.outputPrice)} / 1M`}),e.jsx(I,{label:"Cache read",value:t.cacheReadPrice==null?"—":`${E(t.cacheReadPrice)} / 1M`}),e.jsx(I,{label:"Cache write",value:t.cacheWritePrice==null?"—":`${E(t.cacheWritePrice)} / 1M`}),e.jsx(I,{label:"1h cache write",value:t.cacheWrite1hPrice==null?"—":`${E(t.cacheWrite1hPrice)} / 1M`}),e.jsx(I,{label:"Context tiers",value:t.pricingTiers.length?`${t.pricingTiers.length} configured`:"—"}),e.jsxs("div",{className:"flex items-center gap-2 pt-1",children:[e.jsx(A,{size:"sm",variant:"outline",onPress:S,children:t.source==="configured"?"Edit price":"Set price"}),t.source==="configured"?e.jsxs(e.Fragment,{children:[e.jsx(ze,{confirmLabel:"Reset",isPending:r.isPending,onConfirm:()=>r.mutate(t.key),children:"Reset"}),e.jsx(oe,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error?e.jsx("span",{className:"text-xs text-red-700",children:be(r.error)}):null]})]})}function Ht({row:t,metadata:i,metadataAvailable:r,onMakeAlias:s,onClose:n}){const a=(i==null?void 0:i.input_modalities)??[],f=(i==null?void 0:i.output_modalities)??[],p=Ot.filter(({key:u})=>i==null?void 0:i[u]);return e.jsx(se,{children:e.jsxs(se.Content,{className:"flex flex-col gap-5 p-5",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-base font-semibold break-all text-[var(--otari-ink)]",children:t.model}),i!=null&&i.deprecated?e.jsx(V,{size:"sm",color:"danger",children:"deprecated"}):null]}),e.jsxs("p",{className:"mt-1 text-xs break-all text-[var(--otari-muted)]",children:["Selector: ",e.jsx("code",{children:t.key})]}),i!=null&&i.family?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:i.family}):null]}),e.jsx("button",{type:"button","aria-label":"Close model details",onClick:n,className:"-mt-1 -mr-1 shrink-0 rounded-md px-1.5 py-0.5 text-lg leading-none text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]",children:"✕"})]}),i!=null&&i.description?e.jsx("p",{className:"text-sm text-[var(--otari-ink)]",children:i.description}):null,e.jsxs(le,{title:"Pricing",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(wt,{source:t.source}),t.isDiscovered?null:e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not discovered"})]}),e.jsx(Yt,{row:t},t.key),e.jsx(A,{size:"sm",variant:"outline",onPress:()=>s(t.key),children:"Make an alias"})]}),e.jsxs(le,{title:"Specs",children:[e.jsx(I,{label:"Context window",value:ae(t.contextWindow)}),e.jsx(I,{label:"Max output",value:ae((i==null?void 0:i.max_output_tokens)??null)}),e.jsx(I,{label:"Knowledge cutoff",value:(i==null?void 0:i.knowledge_cutoff)??"—"}),e.jsx(I,{label:"Released",value:Ct(i==null?void 0:i.release_date)}),e.jsx(I,{label:"Open weights",value:i?i.open_weights?"Yes":"No":"—"})]}),e.jsx(le,{title:"Modalities",children:a.length===0&&f.length===0?e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:"Unknown."}):e.jsxs("div",{className:"flex flex-col gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"In:"}),a.map(u=>e.jsx(V,{size:"sm",color:"default",children:Fe[u]??u},u))]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"Out:"}),f.map(u=>e.jsx(V,{size:"sm",color:"default",children:Fe[u]??u},u))]})]})}),e.jsx(le,{title:"Capabilities",children:p.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:p.map(({key:u,label:P})=>e.jsx(V,{size:"sm",color:"default",children:P},u))}):e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:r?"None reported.":"Extended metadata unavailable (models.dev disabled or unreachable)."})})]})})}const qt=15;function Ut({value:t,onChange:i,placeholder:r}){return e.jsx("input",{type:"search",value:t,onChange:s=>i(s.target.value),placeholder:r,"aria-label":r,className:"w-full max-w-xs rounded-md border border-[var(--otari-line)] bg-white px-3 py-1.5 text-sm focus:border-[var(--otari-brand)] focus:outline-none"})}const qe="otari.dashboard.modelsSort",_e={col:"model",dir:"asc"},Gt=["model","released","input","output"];function Jt(){if(typeof window>"u")return _e;try{const t=window.localStorage.getItem(qe);if(!t)return _e;const i=JSON.parse(t);if(Gt.includes(i.col)&&(i.dir==="asc"||i.dir==="desc"))return{col:i.col,dir:i.dir}}catch{}return _e}function Xt({row:t,onClose:i}){const r=je(),s=Re(),[n,a]=c.useState(t.inputPrice==null?"":String(t.inputPrice)),[f,p]=c.useState(t.outputPrice==null?"":String(t.outputPrice)),[u,P]=c.useState(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),[j,k]=c.useState(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),[T,y]=c.useState(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),[h,N]=c.useState(we(t.pricingTiers)),O=ce(n)&&ce(f)&&w(u)&&w(j)&&w(T)&&Ve(h),S=()=>{O&&r.mutate({model_key:t.key,input_price_per_million:Number(n),output_price_per_million:Number(f),cache_read_price_per_million:Y(u),cache_write_price_per_million:Y(j),cache_write_1h_price_per_million:Y(T),pricing_tiers:Ye(h)},{onSuccess:i})};return e.jsxs("div",{className:"flex flex-col gap-3 px-4 py-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("span",{className:"text-xs font-medium break-all text-[var(--otari-muted)]",children:t.key}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Input $ / 1M",e.jsx(b,{value:n,onChange:a,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Output $ / 1M",e.jsx(b,{value:f,onChange:p,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache read $ / 1M",e.jsx(b,{value:u,onChange:P,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache write $ / 1M",e.jsx(b,{value:j,onChange:k,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["1h cache write $ / 1M",e.jsx(b,{value:T,onChange:y,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(A,{size:"sm",variant:"primary",isDisabled:r.isPending||!O,onPress:S,children:r.isPending?"Saving…":"Save"}),e.jsx(A,{size:"sm",variant:"ghost",isDisabled:r.isPending,onPress:i,children:"Cancel"}),t.source==="configured"?e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx(ze,{confirmLabel:"Reset",isPending:s.isPending,onConfirm:()=>s.mutate(t.key,{onSuccess:i}),children:"Reset"}),e.jsx(oe,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error||s.error?e.jsx("span",{className:"text-xs text-red-700",children:be(r.error??s.error)}):null]}),e.jsx(He,{tiers:h,onChange:N})]})}function Zt({primary:t,secondary:i,rowKey:r,primaryLabel:s,secondaryLabel:n,onEdit:a}){const f=(p,u)=>e.jsx("button",{type:"button","aria-label":`Edit ${u} price for ${r}`,className:"tabular-nums hover:text-[var(--otari-brand-dark)] hover:underline",onClick:P=>{P.stopPropagation(),a()},children:p==null?"—":E(p)});return e.jsxs("span",{className:"inline-flex items-center justify-end gap-1",children:[f(t,s),e.jsx("span",{className:"text-[var(--otari-muted)]",children:"/"}),f(i,n)]})}function Qt({rates:t,rowKey:i,onEdit:r}){const s=[t.cacheReadPrice==null?null:`R ${E(t.cacheReadPrice)}`,t.cacheWritePrice==null?null:`W ${E(t.cacheWritePrice)}`,t.cacheWrite1hPrice==null?null:`1h ${E(t.cacheWrite1hPrice)}`].filter(n=>n!==null);return e.jsx("button",{type:"button","aria-label":`Edit caching price for ${i}`,className:"max-w-44 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),r()},children:s.length>0?s.join(" · "):"Input-rate fallback"})}function ei({row:t,onEdit:i}){const r=[...t.pricingTiers].sort((n,a)=>n.min_input_tokens-a.min_input_tokens).map(n=>ae(n.min_input_tokens)),s=r.length===0?"Base only":`${r.length} tier${r.length===1?"":"s"} · ≥ ${r.join(", ")}`;return e.jsx("button",{type:"button","aria-label":`Edit pricing policy for ${t.key}`,className:"max-w-40 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),i()},children:s})}function ti({rows:t,isLoading:i,empty:r,sortDescriptor:s,onSortChange:n,selectedKey:a,onSelect:f,onEditPricing:p,comparisonContextTokens:u,selectedKeys:P,onSelectionChange:j}){const k=c.useMemo(()=>{const y=u==null?"Base":`at ${ae(u)}`;return[{id:"model",header:"Model",isRowHeader:!0,allowsSorting:!0,cell:h=>e.jsxs("span",{className:"font-medium break-all",children:[h.model,e.jsx("span",{className:"sr-only",children:h.key})]})},{id:"provider",header:"Provider",cell:h=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:h.provider})},{id:"input",header:e.jsxs("span",{className:"inline-flex items-center gap-1",children:[`${y} in / out $ / 1M`,e.jsx(Vt,{})]}),align:"end",allowsSorting:!0,cell:h=>{const N=Ke(h,u);return e.jsx(Zt,{primary:N.inputPrice,secondary:N.outputPrice,rowKey:h.key,primaryLabel:"input",secondaryLabel:"output",onEdit:()=>p(h.key)})}},{id:"caching",header:`Caching ${u==null?"policy":y}`,align:"end",cell:h=>e.jsx(Qt,{rates:Ke(h,u),rowKey:h.key,onEdit:()=>p(h.key)})},{id:"policy",header:"Pricing policy",align:"end",cell:h=>e.jsx(ei,{row:h,onEdit:()=>p(h.key)})}]},[u,p]),T=c.useCallback(y=>y.key===a?"bg-[var(--otari-brand-tint)]":void 0,[a]);return e.jsx(Tt,{ariaLabel:"Models",columns:k,rows:t,getRowKey:zt,isLoading:i,emptyContent:r,selectionMode:"multiple",selectedKeys:P,onSelectionChange:j,sortDescriptor:s,onSortChange:n,onRowAction:f,rowClassName:T})}function ii({providers:t}){return t.length===0?null:e.jsxs(St,{tone:"warning",children:["Could not list ",t.map(i=>i.provider).join(", "),". Check that provider's credentials in config.yml; its models are missing from the list below."]})}function pi(){var Te,Le,Ae;const t=gt(),[i]=vt(),r=_t(),s=bt(),n=jt(),a=yt(),f=je(),[p,u]=c.useState(""),[P,j]=c.useState(0),[k,T]=c.useState(qt),[y,h]=c.useState(null),[N,O]=c.useState(null),[S,Z]=c.useState(Jt),W=Mt(),[Ue,ue]=c.useState(!1),[Ge,ye]=c.useState(!1),[Je,Pe]=c.useState(void 0);c.useEffect(()=>{try{window.localStorage.setItem(qe,JSON.stringify(S))}catch{}},[S]);const[D,Ne]=c.useState(i.get("provider")||"all"),[K,Xe]=c.useState("all"),[H,Ze]=c.useState("all"),[q,Qe]=c.useState("all"),[de,et]=c.useState("0"),[Q,tt]=c.useState(""),[ee,it]=c.useState("all"),[pe,rt]=c.useState(""),F=((Te=a.data)==null?void 0:Te.models)??{},nt=((Le=a.data)==null?void 0:Le.available)??!1,Se=c.useMemo(()=>{var l;return new Set((((l=n.data)==null?void 0:l.providers)??[]).flatMap(m=>m.models.map(o=>o.key)))},[n.data]),lt=l=>{u(l),j(0)},R=l=>m=>{l(m),j(0)},te=c.useMemo(()=>{var M,v,_,$,L,z,$e;const l=new Map($t(s.data??[]).map(d=>[d.model_key,d])),m=[],o=new Set,g=(d,ve,Ee,x)=>{if(o.has(d))return;o.add(d);const C=l.get(d);m.push({key:d,model:Be(ve,Ee),provider:Ee,isDiscovered:Se.has(d),contextWindow:(x==null?void 0:x.contextWindow)??null,inputPrice:C?C.input_price_per_million:(x==null?void 0:x.inputPrice)??null,outputPrice:C?C.output_price_per_million:(x==null?void 0:x.outputPrice)??null,cacheReadPrice:C?C.cache_read_price_per_million:(x==null?void 0:x.cacheReadPrice)??null,cacheWritePrice:C?C.cache_write_price_per_million:(x==null?void 0:x.cacheWritePrice)??null,cacheWrite1hPrice:C?C.cache_write_1h_price_per_million??null:(x==null?void 0:x.cacheWrite1hPrice)??null,pricingTiers:C?C.pricing_tiers??[]:(x==null?void 0:x.pricingTiers)??[],source:C?"configured":(x==null?void 0:x.source)??"none"})};for(const d of((M=r.data)==null?void 0:M.data)??[]){if(d.owned_by===Et)continue;const ve=d.pricing_source==="default"?"default":d.pricing?"configured":"none";g(d.id,d.id,d.owned_by||Oe(d.id),{key:d.id,model:d.id,provider:d.owned_by,contextWindow:d.context_window,inputPrice:((v=d.pricing)==null?void 0:v.input_price_per_million)??null,outputPrice:((_=d.pricing)==null?void 0:_.output_price_per_million)??null,cacheReadPrice:(($=d.pricing)==null?void 0:$.cache_read_price_per_million)??null,cacheWritePrice:((L=d.pricing)==null?void 0:L.cache_write_price_per_million)??null,cacheWrite1hPrice:((z=d.pricing)==null?void 0:z.cache_write_1h_price_per_million)??null,pricingTiers:(($e=d.pricing)==null?void 0:$e.pricing_tiers)??[],source:ve})}for(const d of l.keys())g(d,d,Oe(d));return m},[r.data,s.data,Se]),Ce=c.useMemo(()=>new Map(te.map(l=>[l.key,l])),[te]),ie=c.useMemo(()=>{var o,g,M;const l=te.map(v=>{var _,$;return{...v,contextWindow:v.contextWindow??((_=F[v.key])==null?void 0:_.context_window)??null,releaseDate:(($=F[v.key])==null?void 0:$.release_date)??null}}),m=new Set(l.map(v=>v.key));for(const v of((o=n.data)==null?void 0:o.providers)??[])for(const _ of v.models)m.has(_.key)||(m.add(_.key),l.push({key:_.key,model:Be(_.key,v.provider),provider:v.provider,isDiscovered:!0,contextWindow:((g=F[_.key])==null?void 0:g.context_window)??null,releaseDate:((M=F[_.key])==null?void 0:M.release_date)??null,inputPrice:null,outputPrice:null,cacheReadPrice:null,cacheWritePrice:null,cacheWrite1hPrice:null,pricingTiers:[],source:"none"}));return l},[te,n.data,F]),at=(((Ae=n.data)==null?void 0:Ae.providers)??[]).filter(l=>!l.ok),re=c.useMemo(()=>{const l=Array.from(new Set(ie.map(m=>m.provider))).sort((m,o)=>m.localeCompare(o));return[{value:"all",label:"All providers"},...l.map(m=>({value:m,label:m}))]},[ie]);c.useEffect(()=>{D==="all"||re.length<=1||re.some(l=>l.value===D)||Ne("all")},[re,D]);const U=p.trim().toLowerCase(),he=Number(de)||0,me=Q===""?Number.POSITIVE_INFINITY:Number(Q),st=pe===""?null:Number(pe),xe=ee==="all"?null:Date.now()-Number(ee)*Rt,G=c.useMemo(()=>{const l=o=>{if(U&&!o.key.toLowerCase().includes(U)&&!o.provider.toLowerCase().includes(U)||D!=="all"&&o.provider!==D||K==="configured"&&o.source!=="configured"||K==="default"&&o.source!=="default"||K==="priced"&&o.inputPrice==null||K==="unpriced"&&o.inputPrice!=null||H==="discovered"&&!o.isDiscovered||H==="custom"&&o.isDiscovered)return!1;if(q!=="all"){const g=De.find(v=>v.value===q),M=F[o.key];if(!g||!M||!g.test(M))return!1}if(he>0&&(o.contextWindow==null||o.contextWindowme))return!1;if(xe!=null){const g=o.releaseDate?Date.parse(o.releaseDate):Number.NaN;if(Number.isNaN(g)||g{const M=S.dir==="asc"?1:-1;if(S.col==="model")return o.model.localeCompare(g.model)*M;if(S.col==="released"){const L=o.releaseDate??null,z=g.releaseDate??null;return!L&&!z?o.model.localeCompare(g.model):L?z?(Lz?1:0)*M||o.model.localeCompare(g.model):-1:1}const v=L=>S.col==="input"?L.inputPrice:L.outputPrice,_=v(o),$=v(g);return _==null&&$==null?o.model.localeCompare(g.model):_==null?1:$==null?-1:(_-$)*M||o.model.localeCompare(g.model)};return ie.filter(l).sort(m)},[ie,U,D,K,H,q,he,me,xe,F,S]),J=G.length,ct=Math.max(1,Math.ceil(J/k)),ke=Math.min(P,ct-1),Me=ke*k,fe=G.slice(Me,Me+k),ot={column:S.col,direction:S.dir==="asc"?"ascending":"descending"},ut=l=>{Z({col:String(l.column),dir:l.direction==="ascending"?"asc":"desc"}),j(0)},dt=c.useCallback(l=>h(m=>m===l?null:l),[]),ge=fe.map(l=>l.key),X=Wt(W.selectedKeys,ge),pt=ge.length>0&&X.length===ge.length&&J>X.length,ht=W.allMatching?G.map(l=>l.key):X,We=W.allMatching?J:X.length,Ie=y?G.find(l=>l.key===y)??Ce.get(y)??null:null,mt=async l=>{ye(!0),Pe(void 0);try{for(const m of ht)await f.mutateAsync({model_key:m,input_price_per_million:l.input_price_per_million,output_price_per_million:l.output_price_per_million,cache_read_price_per_million:l.cache_read_price_per_million??null,cache_write_price_per_million:l.cache_write_price_per_million??null,cache_write_1h_price_per_million:null,pricing_tiers:[]});W.clear(),ue(!1)}catch(m){Pe(m)}finally{ye(!1)}},xt=r.isLoading||s.isLoading||n.isLoading,ft=U!==""||D!=="all"||K!=="all"||H!=="all"||q!=="all"||de!=="0"||Q!==""||ee!=="all"?"No models match your filters.":"No models yet. Add a provider on the Providers page, or price a model below.",ne=N?G.find(l=>l.key===N)??Ce.get(N)??null:null;return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Pt,{title:"Models",description:"Every model your providers can serve. Set a price on any model so budgets and usage tracking work."}),e.jsx(Nt,{error:r.error??s.error??n.error??a.error}),e.jsxs("div",{className:`grid gap-4 lg:items-start ${ne?"lg:grid-cols-[minmax(0,1fr)_360px]":"grid-cols-1"}`,children:[e.jsxs("div",{className:"flex min-w-0 flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(Ut,{value:p,onChange:lt,placeholder:"Search models…"}),e.jsx(B,{ariaLabel:"Filter by provider",value:D,onChange:R(Ne),options:re}),e.jsx(B,{ariaLabel:"Filter by pricing",value:K,onChange:R(Xe),options:[{value:"all",label:"Any pricing"},{value:"configured",label:"Custom price"},{value:"default",label:"Default price"},{value:"priced",label:"Priced"},{value:"unpriced",label:"Unpriced"}]}),e.jsx(B,{ariaLabel:"Filter by source",value:H,onChange:R(Ze),options:[{value:"all",label:"Any source"},{value:"discovered",label:"Discovered"},{value:"custom",label:"Custom (not discovered)"}]}),e.jsx(B,{ariaLabel:"Filter by capability",value:q,onChange:R(Qe),options:[{value:"all",label:"Any capability"},...De.map(l=>({value:l.value,label:l.label}))]}),e.jsx(B,{ariaLabel:"Minimum context window",value:de,onChange:R(et),options:Dt}),e.jsx(B,{ariaLabel:"Maximum input price",value:Q,onChange:R(tt),options:Ft}),e.jsx(B,{ariaLabel:"Compare prices at context",value:pe,onChange:rt,options:Bt}),e.jsx(B,{ariaLabel:"Filter by release date",value:ee,onChange:R(it),options:Kt})]}),e.jsx(ii,{providers:at}),X.length>0?e.jsx(It,{selectedCount:We,allMatching:W.allMatching,matchingTotal:J,canSelectAllMatching:pt,onSelectAllMatching:W.enableAllMatching,onClear:W.clear,children:e.jsx(A,{size:"sm",variant:"primary",onPress:()=>ue(!0),children:"Set pricing"})}):null,e.jsx(ti,{rows:fe,isLoading:xt,empty:ft,sortDescriptor:ot,onSortChange:ut,selectedKey:N,onSelect:O,onEditPricing:dt,comparisonContextTokens:st,selectedKeys:W.selectedKeys,onSelectionChange:W.onSelectionChange}),Ie?e.jsx(se,{children:e.jsxs(se.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Edit pricing"}),e.jsx(A,{size:"sm",variant:"ghost",onPress:()=>h(null),children:"Close"})]}),e.jsx(Xt,{row:Ie,onClose:()=>h(null)})]})}):null,e.jsx(Lt,{page:ke,pageSize:k,total:J,rowsOnPage:fe.length,onPageChange:j,onPageSizeChange:l=>{T(l),j(0)},pageSizeOptions:[15,25,50]})]}),ne?e.jsx("aside",{className:"lg:sticky lg:top-4",children:e.jsx(Ht,{row:ne,metadata:F[ne.key],metadataAvailable:nt,onMakeAlias:l=>t(`/aliases?target=${encodeURIComponent(l)}`),onClose:()=>O(null)})}):null]}),e.jsx(At,{isOpen:Ue,onOpenChange:ue,targetCount:We,isPending:Ge,error:Je,onSubmit:mt,title:"Set pricing",description:l=>`Apply these per-1M rates to ${l.toLocaleString()} selected ${l===1?"model":"models"}. This replaces each model's price; pricing tiers and the 1h cache rate are cleared. Edit a single model for tiers.`})]})}export{pi as ModelsPage}; diff --git a/src/gateway/static/dashboard/assets/OverviewPage-CB0oRBw3.js b/src/gateway/static/dashboard/assets/OverviewPage-qo0xwTL4.js similarity index 99% rename from src/gateway/static/dashboard/assets/OverviewPage-CB0oRBw3.js rename to src/gateway/static/dashboard/assets/OverviewPage-qo0xwTL4.js index defc3614..2db0e6bb 100644 --- a/src/gateway/static/dashboard/assets/OverviewPage-CB0oRBw3.js +++ b/src/gateway/static/dashboard/assets/OverviewPage-qo0xwTL4.js @@ -1 +1 @@ -import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as h,i as tt,N as G}from"./react-dgEcD0HR.js";import{K as et,d as j,L as rt,l as at,a as st,u as nt,b as ot,M as w,P as it,E as I,S as p,N as _,O as N,Q as R,R as D,T as lt}from"./index-DHOrDaWF.js";import{S as M}from"./charts-CuOShutR.js";import{D as dt}from"./DataTable-CrQq0sYf.js";import{d as U,B as ut}from"./heroui-DZj66Arc.js";import"./recharts-D7kuKp97.js";function B(e){return e==="neutral"?void 0:e}const ct=.02,mt=.1;function K(e){if(!e||e.request_count===0)return{rate:null,status:"neutral"};const o=e.error_count/e.request_count,r=o>=mt?"alert":o>=ct?"warn":"ok";return{rate:o,status:r}}function xt(e){return!e||e.total===0?"neutral":e.healthy>=e.total?"ok":e.healthy===0?"alert":"warn"}const vt=.8;function ft(e){if(e.length===0)return{status:"neutral",label:"No budgets configured",overCount:0,nearCount:0,cappedCount:0};const o=e.filter(s=>s.max_budget!==null&&s.user_count>0);if(o.length===0)return{status:"neutral",label:"No capped budgets",overCount:0,nearCount:0,cappedCount:0};let r=0,a=0,n,x=-1;for(const s of o){const i=s.max_budget*s.user_count,l=i>0?s.total_spend/i:0;l>=1?r+=1:l>=vt&&(a+=1),l>x&&(x=l,n={name:s.name??s.budget_id,spent:s.total_spend,allocated:i,pct:l})}const c=r>0?"alert":a>0?"warn":"ok",g=r>0?`${r} over limit`:a>0?`${a} near limit`:"All within budget";return{status:c,label:g,overCount:r,nearCount:a,cappedCount:o.length,worst:n}}const F=864e5,T=30;function W(){const e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function pt(){const[e,o]=h.useState(W);return h.useEffect(()=>{const r=()=>{if(document.visibilityState==="visible"){const a=W();o(n=>n===a?n:a)}};return document.addEventListener("visibilitychange",r),window.addEventListener("focus",r),()=>{document.removeEventListener("visibilitychange",r),window.removeEventListener("focus",r)}},[]),h.useMemo(()=>{const r=Date.now(),a=new Date(r);return{today:new Date(a.getFullYear(),a.getMonth(),a.getDate()).toISOString(),periodStart:new Date(r-T*F).toISOString(),prevStart:new Date(r-2*T*F).toISOString()}},[e])}const ht={ok:"Healthy",warn:"Elevated",alert:"High"},gt={ok:"On track",warn:"Near limit",alert:"Over budget"};function Lt(){const e=et();return e.isLoading?null:t.jsx(bt,{needsSetup:e.isSuccess&&e.data.providers.length===0})}function bt({needsSetup:e=!1}){var O,L,$,A,P,q,H;const o=pt(),r=h.useMemo(()=>({start_date:o.today}),[o]),a=h.useMemo(()=>({start_date:o.periodStart}),[o]),n=h.useMemo(()=>({start_date:o.prevStart,end_date:o.periodStart}),[o]),x=j(r,"hour"),c=j(a,"day"),g=j(n,"day"),s=rt(),i=at(),l=st(),y=nt(),b=ot({},0,5),E=(O=x.data)==null?void 0:O.totals,m=(L=c.data)==null?void 0:L.totals,v=($=g.data)==null?void 0:$.totals,S=((A=c.data)==null?void 0:A.series)??[],C=S.length>1,d=K(m),k=K(v),Y=d.rate!==null&&k.rate!==null?w(d.rate,k.rate):null,u=ft(i.data??[]),Q=xt(s.data),V=(l.data??[]).filter(f=>f.is_active).length,z=(y.data??[]).filter(f=>!f.blocked).length,J=(((P=b.data)==null?void 0:P.length)??0)>0,X=e&&b.isSuccess&&!J,Z=x.error??c.error??s.error??i.error??l.error??y.error;return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(it,{title:"Overview",description:"At-a-glance spend, traffic, and health across the gateway."}),X?t.jsx(yt,{}):null,t.jsx(I,{error:Z}),t.jsx(jt,{providerHealth:Q,healthy:((q=s.data)==null?void 0:q.healthy)??0,total:((H=s.data)==null?void 0:H.total)??0,budget:u,errStatus:d.status,errRate:d.rate,ready:s.isSuccess&&i.isSuccess&&c.isSuccess,failed:s.isError||i.isError||c.isError}),t.jsxs("div",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4",children:[t.jsx(p,{label:"Spend today",value:E?_(E.cost):"—"}),t.jsx(p,{label:"Spend, last 30 days",value:m?_(m.cost):"—",hint:m?t.jsx(N,{fraction:w(m.cost,v==null?void 0:v.cost)}):null,chart:C?t.jsx(M,{values:S.map(f=>f.cost),ariaLabel:"Spend trend over the last 30 days"}):void 0}),t.jsx(p,{label:"Requests, last 30 days",value:m?R(m.request_count):"—",hint:m?t.jsx(N,{fraction:w(m.request_count,v==null?void 0:v.request_count)}):null,chart:C?t.jsx(M,{values:S.map(f=>f.requests),ariaLabel:"Request volume trend over the last 30 days"}):void 0}),t.jsx(p,{label:"Error rate, last 30 days",value:d.rate===null?"—":D(d.rate),status:B(d.status),statusLabel:d.status==="neutral"?void 0:ht[d.status],hint:d.rate!==null?t.jsx(N,{fraction:Y}):null}),t.jsx(p,{label:"Budget health",value:i.data&&u.worst?D(u.worst.pct):"—",status:i.data?B(u.status):void 0,statusLabel:i.data&&u.status!=="neutral"?gt[u.status]:void 0,hint:i.data?u.worst?`${u.label} · worst: ${u.worst.name}`:u.label:void 0,to:"/budgets"}),t.jsx(p,{label:"Active keys",value:l.data?R(V):"—",to:"/keys"}),t.jsx(p,{label:"Active users",value:y.data?R(z):"—",to:"/users"})]}),t.jsx(Nt,{entries:b.data??[],loading:b.isLoading,error:b.error})]})}function yt(){const e=tt();return t.jsx(U,{children:t.jsxs(U.Content,{className:"flex flex-col gap-3 p-6",children:[t.jsxs("div",{children:[t.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Get started with Otari"}),t.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Add a provider to begin serving models. Once it is configured, this page will show your gateway’s traffic, spend, and health."})]}),t.jsx("div",{children:t.jsx(ut,{variant:"primary",onPress:()=>e("/providers"),children:"Add your first provider"})})]})})}function St({text:e}){return t.jsx("div",{role:"status",className:"flex items-center gap-2 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-bg)] px-4 py-3 text-sm text-[var(--otari-muted)]",children:e})}function jt({providerHealth:e,healthy:o,total:r,budget:a,errStatus:n,errRate:x,ready:c,failed:g}){if(g)return t.jsx(St,{text:"Some status data could not be loaded."});if(!c)return null;const s=[];if((e==="warn"||e==="alert")&&r>0){const i=r-o;s.push({text:`${i} provider${i===1?"":"s"} unreachable`,to:"/providers"})}return a.overCount>0?s.push({text:`${a.overCount} budget${a.overCount===1?"":"s"} over limit`,to:"/budgets"}):a.nearCount>0&&s.push({text:`${a.nearCount} budget${a.nearCount===1?"":"s"} near limit`,to:"/budgets"}),n==="alert"&&x!==null&&s.push({text:`error rate ${D(x)}`,to:"/activity?status=error"}),s.length===0?null:t.jsxs("div",{role:"alert",className:"flex flex-col gap-2 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900 sm:flex-row sm:flex-wrap sm:items-center",children:[t.jsx("span",{className:"font-medium",children:"Needs attention:"}),s.map((i,l)=>t.jsxs("span",{className:"flex items-center gap-2",children:[l>0?t.jsx("span",{"aria-hidden":!0,className:"text-amber-400",children:"·"}):null,t.jsx(G,{to:i.to,className:"underline underline-offset-2 hover:text-amber-950",children:i.text})]},i.to+i.text))]})}function wt(e){return e==="error"?"error":"ok"}function Nt({entries:e,loading:o,error:r}){const a=[{id:"time",header:"Time",cell:n=>t.jsx("span",{className:"text-[var(--otari-muted)]",title:new Date(n.timestamp).toLocaleString(),children:lt(n.timestamp)})},{id:"model",header:"Model",isRowHeader:!0,cell:n=>t.jsx("span",{className:"text-[var(--otari-ink)]",children:n.model})},{id:"cost",header:"Cost",align:"end",cell:n=>n.cost===null?"—":_(n.cost)},{id:"status",header:"Status",cell:n=>t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${n.status==="error"?"border-red-200 bg-red-50 text-red-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]"}`,children:wt(n.status)})}];return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{className:"flex items-center justify-between",children:[t.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Recent activity"}),t.jsx(G,{to:"/activity",className:"text-sm text-[var(--otari-brand-dark)] hover:underline",children:"View all →"})]}),t.jsx(I,{error:r}),t.jsx(dt,{ariaLabel:"Recent activity",columns:a,rows:e,getRowKey:n=>n.id,isLoading:o,emptyContent:"No requests yet. Once the gateway serves traffic, it appears here."})]})}export{Lt as OverviewIndex,bt as OverviewPage,W as localDayKey}; +import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as h,i as tt,N as G}from"./react-dgEcD0HR.js";import{K as et,d as j,L as rt,l as at,a as st,u as nt,b as ot,M as w,P as it,E as I,S as p,N as _,O as N,Q as R,R as D,T as lt}from"./index-BzgMrLHf.js";import{S as M}from"./charts-CuOShutR.js";import{D as dt}from"./DataTable-CrQq0sYf.js";import{d as U,B as ut}from"./heroui-DZj66Arc.js";import"./recharts-D7kuKp97.js";function B(e){return e==="neutral"?void 0:e}const ct=.02,mt=.1;function K(e){if(!e||e.request_count===0)return{rate:null,status:"neutral"};const o=e.error_count/e.request_count,r=o>=mt?"alert":o>=ct?"warn":"ok";return{rate:o,status:r}}function xt(e){return!e||e.total===0?"neutral":e.healthy>=e.total?"ok":e.healthy===0?"alert":"warn"}const vt=.8;function ft(e){if(e.length===0)return{status:"neutral",label:"No budgets configured",overCount:0,nearCount:0,cappedCount:0};const o=e.filter(s=>s.max_budget!==null&&s.user_count>0);if(o.length===0)return{status:"neutral",label:"No capped budgets",overCount:0,nearCount:0,cappedCount:0};let r=0,a=0,n,x=-1;for(const s of o){const i=s.max_budget*s.user_count,l=i>0?s.total_spend/i:0;l>=1?r+=1:l>=vt&&(a+=1),l>x&&(x=l,n={name:s.name??s.budget_id,spent:s.total_spend,allocated:i,pct:l})}const c=r>0?"alert":a>0?"warn":"ok",g=r>0?`${r} over limit`:a>0?`${a} near limit`:"All within budget";return{status:c,label:g,overCount:r,nearCount:a,cappedCount:o.length,worst:n}}const F=864e5,T=30;function W(){const e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function pt(){const[e,o]=h.useState(W);return h.useEffect(()=>{const r=()=>{if(document.visibilityState==="visible"){const a=W();o(n=>n===a?n:a)}};return document.addEventListener("visibilitychange",r),window.addEventListener("focus",r),()=>{document.removeEventListener("visibilitychange",r),window.removeEventListener("focus",r)}},[]),h.useMemo(()=>{const r=Date.now(),a=new Date(r);return{today:new Date(a.getFullYear(),a.getMonth(),a.getDate()).toISOString(),periodStart:new Date(r-T*F).toISOString(),prevStart:new Date(r-2*T*F).toISOString()}},[e])}const ht={ok:"Healthy",warn:"Elevated",alert:"High"},gt={ok:"On track",warn:"Near limit",alert:"Over budget"};function Lt(){const e=et();return e.isLoading?null:t.jsx(bt,{needsSetup:e.isSuccess&&e.data.providers.length===0})}function bt({needsSetup:e=!1}){var O,L,$,A,P,q,H;const o=pt(),r=h.useMemo(()=>({start_date:o.today}),[o]),a=h.useMemo(()=>({start_date:o.periodStart}),[o]),n=h.useMemo(()=>({start_date:o.prevStart,end_date:o.periodStart}),[o]),x=j(r,"hour"),c=j(a,"day"),g=j(n,"day"),s=rt(),i=at(),l=st(),y=nt(),b=ot({},0,5),E=(O=x.data)==null?void 0:O.totals,m=(L=c.data)==null?void 0:L.totals,v=($=g.data)==null?void 0:$.totals,S=((A=c.data)==null?void 0:A.series)??[],C=S.length>1,d=K(m),k=K(v),Y=d.rate!==null&&k.rate!==null?w(d.rate,k.rate):null,u=ft(i.data??[]),Q=xt(s.data),V=(l.data??[]).filter(f=>f.is_active).length,z=(y.data??[]).filter(f=>!f.blocked).length,J=(((P=b.data)==null?void 0:P.length)??0)>0,X=e&&b.isSuccess&&!J,Z=x.error??c.error??s.error??i.error??l.error??y.error;return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(it,{title:"Overview",description:"At-a-glance spend, traffic, and health across the gateway."}),X?t.jsx(yt,{}):null,t.jsx(I,{error:Z}),t.jsx(jt,{providerHealth:Q,healthy:((q=s.data)==null?void 0:q.healthy)??0,total:((H=s.data)==null?void 0:H.total)??0,budget:u,errStatus:d.status,errRate:d.rate,ready:s.isSuccess&&i.isSuccess&&c.isSuccess,failed:s.isError||i.isError||c.isError}),t.jsxs("div",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4",children:[t.jsx(p,{label:"Spend today",value:E?_(E.cost):"—"}),t.jsx(p,{label:"Spend, last 30 days",value:m?_(m.cost):"—",hint:m?t.jsx(N,{fraction:w(m.cost,v==null?void 0:v.cost)}):null,chart:C?t.jsx(M,{values:S.map(f=>f.cost),ariaLabel:"Spend trend over the last 30 days"}):void 0}),t.jsx(p,{label:"Requests, last 30 days",value:m?R(m.request_count):"—",hint:m?t.jsx(N,{fraction:w(m.request_count,v==null?void 0:v.request_count)}):null,chart:C?t.jsx(M,{values:S.map(f=>f.requests),ariaLabel:"Request volume trend over the last 30 days"}):void 0}),t.jsx(p,{label:"Error rate, last 30 days",value:d.rate===null?"—":D(d.rate),status:B(d.status),statusLabel:d.status==="neutral"?void 0:ht[d.status],hint:d.rate!==null?t.jsx(N,{fraction:Y}):null}),t.jsx(p,{label:"Budget health",value:i.data&&u.worst?D(u.worst.pct):"—",status:i.data?B(u.status):void 0,statusLabel:i.data&&u.status!=="neutral"?gt[u.status]:void 0,hint:i.data?u.worst?`${u.label} · worst: ${u.worst.name}`:u.label:void 0,to:"/budgets"}),t.jsx(p,{label:"Active keys",value:l.data?R(V):"—",to:"/keys"}),t.jsx(p,{label:"Active users",value:y.data?R(z):"—",to:"/users"})]}),t.jsx(Nt,{entries:b.data??[],loading:b.isLoading,error:b.error})]})}function yt(){const e=tt();return t.jsx(U,{children:t.jsxs(U.Content,{className:"flex flex-col gap-3 p-6",children:[t.jsxs("div",{children:[t.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Get started with Otari"}),t.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Add a provider to begin serving models. Once it is configured, this page will show your gateway’s traffic, spend, and health."})]}),t.jsx("div",{children:t.jsx(ut,{variant:"primary",onPress:()=>e("/providers"),children:"Add your first provider"})})]})})}function St({text:e}){return t.jsx("div",{role:"status",className:"flex items-center gap-2 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-bg)] px-4 py-3 text-sm text-[var(--otari-muted)]",children:e})}function jt({providerHealth:e,healthy:o,total:r,budget:a,errStatus:n,errRate:x,ready:c,failed:g}){if(g)return t.jsx(St,{text:"Some status data could not be loaded."});if(!c)return null;const s=[];if((e==="warn"||e==="alert")&&r>0){const i=r-o;s.push({text:`${i} provider${i===1?"":"s"} unreachable`,to:"/providers"})}return a.overCount>0?s.push({text:`${a.overCount} budget${a.overCount===1?"":"s"} over limit`,to:"/budgets"}):a.nearCount>0&&s.push({text:`${a.nearCount} budget${a.nearCount===1?"":"s"} near limit`,to:"/budgets"}),n==="alert"&&x!==null&&s.push({text:`error rate ${D(x)}`,to:"/activity?status=error"}),s.length===0?null:t.jsxs("div",{role:"alert",className:"flex flex-col gap-2 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900 sm:flex-row sm:flex-wrap sm:items-center",children:[t.jsx("span",{className:"font-medium",children:"Needs attention:"}),s.map((i,l)=>t.jsxs("span",{className:"flex items-center gap-2",children:[l>0?t.jsx("span",{"aria-hidden":!0,className:"text-amber-400",children:"·"}):null,t.jsx(G,{to:i.to,className:"underline underline-offset-2 hover:text-amber-950",children:i.text})]},i.to+i.text))]})}function wt(e){return e==="error"?"error":"ok"}function Nt({entries:e,loading:o,error:r}){const a=[{id:"time",header:"Time",cell:n=>t.jsx("span",{className:"text-[var(--otari-muted)]",title:new Date(n.timestamp).toLocaleString(),children:lt(n.timestamp)})},{id:"model",header:"Model",isRowHeader:!0,cell:n=>t.jsx("span",{className:"text-[var(--otari-ink)]",children:n.model})},{id:"cost",header:"Cost",align:"end",cell:n=>n.cost===null?"—":_(n.cost)},{id:"status",header:"Status",cell:n=>t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${n.status==="error"?"border-red-200 bg-red-50 text-red-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]"}`,children:wt(n.status)})}];return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{className:"flex items-center justify-between",children:[t.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Recent activity"}),t.jsx(G,{to:"/activity",className:"text-sm text-[var(--otari-brand-dark)] hover:underline",children:"View all →"})]}),t.jsx(I,{error:r}),t.jsx(dt,{ariaLabel:"Recent activity",columns:a,rows:e,getRowKey:n=>n.id,isLoading:o,emptyContent:"No requests yet. Once the gateway serves traffic, it appears here."})]})}export{Lt as OverviewIndex,bt as OverviewPage,W as localDayKey}; diff --git a/src/gateway/static/dashboard/assets/ProvidersPage-BULpjU4h.js b/src/gateway/static/dashboard/assets/ProvidersPage-BKCHydlE.js similarity index 99% rename from src/gateway/static/dashboard/assets/ProvidersPage-BULpjU4h.js rename to src/gateway/static/dashboard/assets/ProvidersPage-BKCHydlE.js index 5120493c..6c780825 100644 --- a/src/gateway/static/dashboard/assets/ProvidersPage-BULpjU4h.js +++ b/src/gateway/static/dashboard/assets/ProvidersPage-BKCHydlE.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as x,L as M}from"./react-dgEcD0HR.js";import{K as Y,U as V,H as W,L as G,V as X,W as Z,X as J,P as Q,E as I,I as ee,Y as te,Z as se,T as $,C as ae,_ as O,$ as ne,D as q,a0 as re,a1 as ie}from"./index-DHOrDaWF.js";import{F as S}from"./Field-DEJ6Wjg9.js";import{D as oe}from"./DataTable-CrQq0sYf.js";import{B as f,d as _,f as de,T as le,L as F,I as H,D as ce,S as me,C as T,a as ue,b as xe}from"./heroui-DZj66Arc.js";function R({value:s,onChange:a,label:t,placeholder:r,description:i}){return e.jsxs(le,{value:s,onChange:a,className:"flex max-w-md flex-col gap-1",children:[e.jsx(F,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsx(H,{type:"password",placeholder:r??"sk-…",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),i?e.jsx(ce,{className:"text-xs text-[var(--otari-muted)]",children:i}):null]})}function U({label:s,value:a,onChange:t,description:r,placeholder:i,extra:l=[],includeCatalog:m=!0}){var k;const g=re(),u=x.useMemo(()=>m?[...l,...(g.data??[]).map(o=>({id:o.id,name:o.name}))]:l,[g.data,l,m]),[p,c]=x.useState(()=>{var o;return((o=u.find(y=>y.id===a))==null?void 0:o.name)??""}),j=((k=u.find(o=>o.id===a))==null?void 0:k.name)??"",h=p.trim()===j.trim()?"":p.trim().toLowerCase(),d=u.filter(o=>!h||o.name.toLowerCase().includes(h)||o.id.toLowerCase().includes(h)).slice(0,50);return e.jsxs(T.Root,{allowsEmptyCollection:!0,menuTrigger:"focus",inputValue:p,onInputChange:c,onSelectionChange:o=>{var y;o!=null?(t(String(o)),c(((y=u.find(N=>N.id===String(o)))==null?void 0:y.name)??"")):(t(""),c(""))},className:"flex max-w-md flex-col gap-1",children:[e.jsx(F,{className:"text-sm font-medium text-[var(--otari-ink)]",children:s}),e.jsxs(T.InputGroup,{children:[e.jsx(H,{placeholder:i??"Search providers…",autoComplete:"off","data-1p-ignore":!0,"data-lpignore":"true",onFocus:o=>o.currentTarget.select()}),e.jsx(T.Trigger,{})]}),e.jsx(T.Popover,{children:e.jsx(ue,{items:d,className:"max-h-72 overflow-auto",children:o=>e.jsx(xe,{id:o.id,textValue:o.name,children:o.name})})}),r?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:r}):null]})}function z({getPayload:s}){const a=ie(),t=s();return e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(f,{variant:"outline",isDisabled:t===null||a.isPending,onPress:()=>{t&&a.mutate(t)},children:a.isPending?"Testing…":"Test connection"}),e.jsx("span",{role:"status","aria-live":"polite",children:a.isPending?null:a.error?e.jsx("span",{className:"text-xs text-red-700",children:q(a.error)}):a.data?a.data.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",a.data.model_count," model",a.data.model_count===1?"":"s"," available."]}):e.jsx("span",{className:"block max-w-md break-words text-xs text-red-700",children:a.data.error??"Connection failed."}):null})]})}function pe({onClose:s}){var w;const a=O(),[t,r]=x.useState(""),[i,l]=x.useState(""),[m,g]=x.useState(!1),[u,p]=x.useState(""),[c,j]=x.useState(""),h=ne(t),d=((w=h.data)==null?void 0:w.id)===t?h.data:void 0;x.useEffect(()=>{d&&p(d.default_api_base??"")},[d]);const k=(d==null?void 0:d.env_key_present)??!1,o=((d==null?void 0:d.requires_api_key)??!0)&&!k,y=c.trim()!==""&&c.trim()!==t,N=/[:/]/.test(c),P=t!==""&&!N&&(!o||i.trim()!=="")&&!a.isPending,A=()=>{P&&a.mutate({instance:y?c.trim():t,provider_type:y?t:null,api_base:u.trim()||null,api_key:i.trim()||null},{onSuccess:s})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:a.error}),e.jsx(U,{label:"Provider",value:t,onChange:C=>{r(C),j(""),p("")},description:"Its endpoint is built in."}),e.jsx(R,{value:i,onChange:l,label:d&&!o?"API key (optional)":"API key",description:d?o?`${d.name}'s endpoint is built in — just add your key.`:k?`${d.env_key} is set on the server, so a key is optional here. Paste one to override it.`:`${d.name} needs no API key.`:"Stored encrypted. Requires OTARI_SECRET_KEY on the server."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>g(C=>!C),children:m?"Hide advanced":"Advanced (API base, rename)"}),m?e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(S,{label:"API base",value:u,onChange:p,placeholder:(d==null?void 0:d.default_api_base)??"https://…/v1",description:"Only if you route through a proxy. Blank uses the built-in default."}),e.jsx(S,{label:"Name",value:c,onChange:j,placeholder:t||"instance name",description:N?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Rename to run two instances of the same provider."})]}):null,e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:!P,onPress:A,children:a.isPending?"Adding…":"Add provider"}),e.jsx(f,{variant:"ghost",onPress:s,children:"Cancel"}),e.jsx(z,{getPayload:()=>t===""?null:{instance:y?c.trim():t,provider_type:y?t:null,api_base:u.trim()||null,api_key:i.trim()||null}})]})]})}function he({onClose:s}){const a=O(),[t,r]=x.useState(""),[i,l]=x.useState("openai-compatible"),[m,g]=x.useState(""),[u,p]=x.useState(""),c=/[:/]/.test(t),j=t.trim()!==""&&!c&&m.trim()!==""&&!a.isPending,h=()=>{j&&a.mutate({instance:t.trim(),provider_type:i||"openai-compatible",api_base:m.trim(),api_key:u.trim()||null},{onSuccess:s})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:a.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(S,{label:"Name",value:t,onChange:r,placeholder:"my-local-llm",isRequired:!0,autoFocus:!0,description:c?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Call it whatever you want."}),e.jsx(U,{label:"Compatible with",value:i,onChange:l,includeCatalog:!1,description:"The API this endpoint speaks.",extra:[{id:"openai-compatible",name:"OpenAI"},{id:"anthropic-compatible",name:"Anthropic"}]})]}),e.jsx(S,{label:"API base",value:m,onChange:g,placeholder:"http://localhost:8000/v1",isRequired:!0,description:"The endpoint URL of your server."}),e.jsx(R,{value:u,onChange:p,label:"API key (optional)",description:"Many local backends need none. Stored encrypted."}),e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:!j,onPress:h,children:a.isPending?"Adding…":"Add provider"}),e.jsx(f,{variant:"ghost",onPress:s,children:"Cancel"}),e.jsx(z,{getPayload:()=>t.trim()===""||m.trim()===""?null:{instance:t.trim(),provider_type:i||"openai-compatible",api_base:m.trim(),api_key:u.trim()||null}})]})]})}function ve({onClose:s}){const[a,t]=x.useState("known");return e.jsx(_,{children:e.jsxs(_.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"flex items-center justify-between",children:e.jsx("div",{className:"flex items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[["known","Known provider"],["custom","Custom endpoint"]].map(([r,i])=>e.jsx("button",{type:"button","aria-pressed":a===r,onClick:()=>t(r),className:a===r?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:i},r))})}),a==="known"?e.jsx(pe,{onClose:s}):e.jsx(he,{onClose:s})]})})}function ge({provider:s,onClose:a}){const t=te(),[r,i]=x.useState(s.provider_type??""),[l,m]=x.useState(s.api_base??""),[g,u]=x.useState(!1),[p,c]=x.useState(""),j=()=>{if(t.isPending)return;const h={provider_type:r.trim()||null,api_base:l.trim()||null,expected_updated_at:s.updated_at};g&&p.trim()&&(h.api_key=p.trim()),t.mutate({instance:s.instance,body:h},{onSuccess:a})};return e.jsx(_,{children:e.jsxs(_.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:s.instance})]}),e.jsx(I,{error:t.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(S,{label:"Provider type",value:r,onChange:i,placeholder:"openai"}),e.jsx(S,{label:"API base",value:l,onChange:m,placeholder:"https://api.openai.com/v1"})]}),e.jsx("div",{className:"flex flex-col gap-2",children:g?e.jsxs(e.Fragment,{children:[e.jsx(R,{value:p,onChange:c,label:"New API key",description:"Stored encrypted. The old key is replaced when you save."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>{u(!1),c("")},children:"Keep the current key"})]}):e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"text-sm text-[var(--otari-muted)]",children:["API key: ",e.jsx("code",{children:s.last4?`••••${s.last4}`:"none set"})]}),e.jsx(f,{size:"sm",variant:"outline",onPress:()=>u(!0),children:"Replace key"})]})}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:t.isPending,onPress:j,children:t.isPending?"Saving…":"Save changes"}),e.jsx(f,{variant:"ghost",onPress:a,children:"Cancel"})]})]})})}function fe(s,a){const t=new Map((a??[]).map(l=>[l.instance,l])),r=new Map((s??[]).map(l=>[l.instance,l]));return[...new Set([...t.keys(),...r.keys()])].sort().map(l=>{const m=t.get(l);return{instance:l,source:m?"stored":"config",stored:m,meta:r.get(l)}})}function je({state:s}){return s?s.status==="pending"?e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsx(me,{size:"sm"})," Testing…"]}):s.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",s.model_count," model",s.model_count===1?"":"s"," available."]}):e.jsx("span",{className:"block max-w-xs break-words text-xs text-red-700",children:s.error??"Connection failed."}):null}function ye({health:s}){if(!s)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"—"});const a=s.ok?"border-green-200 bg-green-50 text-green-700":"border-red-200 bg-red-50 text-red-700",t=s.checked_at?`Last checked ${$(s.checked_at)}`:"Not checked yet",r=s.ok?t:`${s.error??"Unreachable"} · ${t}`;return e.jsxs("span",{title:r,className:`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium ${a}`,children:[e.jsx("span",{"aria-hidden":!0,className:`h-1.5 w-1.5 rounded-full ${s.ok?"bg-green-500":"bg-red-500"}`}),s.ok?"Reachable":"Unreachable"]})}function be({healthy:s,total:a,checkedAt:t}){const r=s===a,i=se();return e.jsxs("div",{className:"flex flex-wrap items-center gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] px-4 py-2.5 text-sm",children:[e.jsx("span",{"aria-hidden":!0,className:`h-2 w-2 rounded-full ${r?"bg-green-500":"bg-red-500"}`}),e.jsxs("span",{className:"font-medium text-[var(--otari-ink)]",children:[s," of ",a," provider",a===1?"":"s"," reachable"]}),t?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["Last checked ",$(t)]}):null,e.jsx(f,{size:"sm",variant:"ghost",className:"ml-auto",isDisabled:i.isPending,onPress:()=>i.mutate(),children:i.isPending?"Re-checking…":"Re-check all"})]})}function E({n:s,title:a,children:t}){return e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--otari-brand-tint)] text-xs font-semibold text-[var(--otari-brand-dark)]",children:s}),e.jsxs("div",{className:"text-sm",children:[e.jsx("div",{className:"font-medium text-[var(--otari-ink)]",children:a}),e.jsx("div",{className:"text-[var(--otari-muted)]",children:t})]})]})}function ke({onAddProvider:s,needsPricing:a,onEnablePricing:t,enabling:r,secretKeyConfigured:i}){return e.jsx(_,{children:e.jsxs(_.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Welcome to Otari"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"You are signed in. Add a provider to start serving models: three quick steps."})]}),e.jsxs("ol",{className:"flex flex-col gap-3",children:[e.jsxs(E,{n:1,title:"Add a provider",children:["Enter a provider name (like ",e.jsx("code",{children:"openai"}),") and its API key. Keys are encrypted at rest."]}),e.jsxs(E,{n:2,title:"Test the connection",children:["Use ",e.jsx("strong",{children:"Test"})," on the provider row to confirm the key works and see how many models it serves."]}),e.jsxs(E,{n:3,title:"Send your first request",children:["Point your app at ",e.jsx("code",{children:"/v1"})," on this gateway with the API key printed in the server logs (",e.jsx("code",{children:"gw-…"}),"). See the"," ",e.jsx("a",{href:"/welcome",className:"font-medium text-[var(--otari-brand-dark)]",children:"quickstart"}),"."]})]}),a?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Tip: ",e.jsx("code",{children:"require_pricing"})," is on, so requests are rejected until pricing is set."," ",e.jsx("button",{type:"button",className:"font-medium text-[var(--otari-brand-dark)] disabled:opacity-50",disabled:r,onClick:t,children:"Enable default pricing"})," ","to meter new models with public rates."]}):null,e.jsx("div",{children:e.jsx(f,{variant:"primary",isDisabled:!i,onPress:s,children:"Add your first provider"})})]})})}function we(){var K,B,D,L;const s=Y(),a=V(),t=W(),r=G(),i=X(),l=Z(),m=J(),[g,u]=x.useState(!1),[p,c]=x.useState(null),[j,h]=x.useState({}),d=fe((K=s.data)==null?void 0:K.providers,a.data),k=new Map((((B=r.data)==null?void 0:B.providers)??[]).map(n=>[n.instance,n])),o=s.isLoading||a.isLoading,y=((D=a.data)==null?void 0:D.find(n=>n.instance===p))??null,N=((L=t.data)==null?void 0:L.require_pricing)===!0&&t.data.default_pricing===!1,P=t.data?t.data.secret_key_configured!==!1:!t.isError,A=!o&&d.length===0&&!g,w=n=>{h(v=>({...v,[n]:{status:"pending"}})),l.mutate(n,{onSuccess:v=>h(b=>({...b,[n]:{status:"done",...v}})),onError:v=>h(b=>({...b,[n]:{status:"done",ok:!1,model_count:0,error:q(v)}}))})},C=[{id:"provider",header:"Provider",isRowHeader:!0,cell:n=>e.jsx(M,{to:`/models?provider=${encodeURIComponent(n.instance)}`,className:"font-medium text-[var(--otari-ink)] hover:text-[var(--otari-brand-dark)] hover:underline",children:n.instance})},{id:"type",header:"Type",cell:n=>{var v,b;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:((v=n.meta)==null?void 0:v.provider_type)??((b=n.stored)==null?void 0:b.provider_type)??n.instance})}},{id:"source",header:"Source",cell:n=>e.jsx(de,{size:"sm",color:n.source==="stored"?"accent":"default",children:n.source==="stored"?"stored":"config"})},{id:"api_key",header:"API key",cell:n=>{var v,b;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:n.source==="stored"?n.stored&&!n.stored.decryptable?e.jsx("span",{className:"text-amber-700",title:"This key can't be decrypted with the current OTARI_SECRET_KEY. Replace the key, or restore the original OTARI_SECRET_KEY.",children:"⚠ key unreadable"}):e.jsx("code",{children:(v=n.stored)!=null&&v.last4?`••••${n.stored.last4}`:"none set"}):(b=n.meta)!=null&&b.env_key?e.jsxs("span",{children:["via ",e.jsx("code",{children:n.meta.env_key})]}):"config.yml"})}},{id:"status",header:"Status",cell:n=>e.jsx(ye,{health:k.get(n.instance)})},{id:"actions",header:"Actions",align:"end",cell:n=>{var v,b;return n.source==="stored"?e.jsxs("div",{className:"flex flex-col items-end gap-1.5",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(f,{size:"sm",variant:"outline",isDisabled:((v=j[n.instance])==null?void 0:v.status)==="pending"||((b=n.stored)==null?void 0:b.decryptable)===!1,onPress:()=>w(n.instance),children:"Test"}),e.jsx(f,{size:"sm",variant:"ghost",onPress:()=>{u(!1),c(n.instance)},children:"Edit"}),e.jsx(ae,{confirmLabel:"Delete",isPending:i.isPending,onConfirm:()=>i.mutate(n.instance),children:"Delete"})]}),e.jsx(je,{state:j[n.instance]})]}):e.jsx("span",{className:"block text-right text-xs text-[var(--otari-muted)]",children:"managed in config.yml"})}}];return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Q,{title:"Providers",description:"Add provider API keys here to serve models without editing config.yml. Keys are encrypted at rest.",action:g||A?null:e.jsx(f,{variant:"primary",isDisabled:!P,onPress:()=>{c(null),u(!0)},children:"Add provider"})}),e.jsx(I,{error:s.error??a.error??t.error??r.error??m.error??i.error}),P?null:e.jsxs(ee,{tone:"warning",children:[e.jsx("code",{children:"OTARI_SECRET_KEY"})," is not set, so provider keys can't be encrypted at rest and adding providers from the dashboard is disabled. Set it on the server and restart to add providers here. Providers defined in"," ",e.jsx("code",{children:"config.yml"})," keep working without it."]}),A?e.jsx(ke,{onAddProvider:()=>{c(null),u(!0)},needsPricing:N,onEnablePricing:()=>m.mutate({default_pricing:!0}),enabling:m.isPending,secretKeyConfigured:P}):null,g&&P?e.jsx(ve,{onClose:()=>u(!1)}):null,y?e.jsx(ge,{provider:y,onClose:()=>c(null)}):null,!o&&d.length>0&&r.data&&r.data.total>0?e.jsx(be,{healthy:r.data.healthy,total:r.data.total,checkedAt:r.data.checked_at}):null,e.jsx(oe,{ariaLabel:"Providers",columns:C,rows:d,getRowKey:n=>n.instance,isLoading:o,emptyContent:"No providers yet. Add your first provider to start serving models."})]})}export{we as ProvidersPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as x,L as M}from"./react-dgEcD0HR.js";import{K as Y,U as V,H as W,L as G,V as X,W as Z,X as J,P as Q,E as I,I as ee,Y as te,Z as se,T as $,C as ae,_ as O,$ as ne,D as q,a0 as re,a1 as ie}from"./index-BzgMrLHf.js";import{F as S}from"./Field-DEJ6Wjg9.js";import{D as oe}from"./DataTable-CrQq0sYf.js";import{B as f,d as _,f as de,T as le,L as F,I as H,D as ce,S as me,C as T,a as ue,b as xe}from"./heroui-DZj66Arc.js";function R({value:s,onChange:a,label:t,placeholder:r,description:i}){return e.jsxs(le,{value:s,onChange:a,className:"flex max-w-md flex-col gap-1",children:[e.jsx(F,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsx(H,{type:"password",placeholder:r??"sk-…",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),i?e.jsx(ce,{className:"text-xs text-[var(--otari-muted)]",children:i}):null]})}function U({label:s,value:a,onChange:t,description:r,placeholder:i,extra:l=[],includeCatalog:m=!0}){var k;const g=re(),u=x.useMemo(()=>m?[...l,...(g.data??[]).map(o=>({id:o.id,name:o.name}))]:l,[g.data,l,m]),[p,c]=x.useState(()=>{var o;return((o=u.find(y=>y.id===a))==null?void 0:o.name)??""}),j=((k=u.find(o=>o.id===a))==null?void 0:k.name)??"",h=p.trim()===j.trim()?"":p.trim().toLowerCase(),d=u.filter(o=>!h||o.name.toLowerCase().includes(h)||o.id.toLowerCase().includes(h)).slice(0,50);return e.jsxs(T.Root,{allowsEmptyCollection:!0,menuTrigger:"focus",inputValue:p,onInputChange:c,onSelectionChange:o=>{var y;o!=null?(t(String(o)),c(((y=u.find(N=>N.id===String(o)))==null?void 0:y.name)??"")):(t(""),c(""))},className:"flex max-w-md flex-col gap-1",children:[e.jsx(F,{className:"text-sm font-medium text-[var(--otari-ink)]",children:s}),e.jsxs(T.InputGroup,{children:[e.jsx(H,{placeholder:i??"Search providers…",autoComplete:"off","data-1p-ignore":!0,"data-lpignore":"true",onFocus:o=>o.currentTarget.select()}),e.jsx(T.Trigger,{})]}),e.jsx(T.Popover,{children:e.jsx(ue,{items:d,className:"max-h-72 overflow-auto",children:o=>e.jsx(xe,{id:o.id,textValue:o.name,children:o.name})})}),r?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:r}):null]})}function z({getPayload:s}){const a=ie(),t=s();return e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(f,{variant:"outline",isDisabled:t===null||a.isPending,onPress:()=>{t&&a.mutate(t)},children:a.isPending?"Testing…":"Test connection"}),e.jsx("span",{role:"status","aria-live":"polite",children:a.isPending?null:a.error?e.jsx("span",{className:"text-xs text-red-700",children:q(a.error)}):a.data?a.data.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",a.data.model_count," model",a.data.model_count===1?"":"s"," available."]}):e.jsx("span",{className:"block max-w-md break-words text-xs text-red-700",children:a.data.error??"Connection failed."}):null})]})}function pe({onClose:s}){var w;const a=O(),[t,r]=x.useState(""),[i,l]=x.useState(""),[m,g]=x.useState(!1),[u,p]=x.useState(""),[c,j]=x.useState(""),h=ne(t),d=((w=h.data)==null?void 0:w.id)===t?h.data:void 0;x.useEffect(()=>{d&&p(d.default_api_base??"")},[d]);const k=(d==null?void 0:d.env_key_present)??!1,o=((d==null?void 0:d.requires_api_key)??!0)&&!k,y=c.trim()!==""&&c.trim()!==t,N=/[:/]/.test(c),P=t!==""&&!N&&(!o||i.trim()!=="")&&!a.isPending,A=()=>{P&&a.mutate({instance:y?c.trim():t,provider_type:y?t:null,api_base:u.trim()||null,api_key:i.trim()||null},{onSuccess:s})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:a.error}),e.jsx(U,{label:"Provider",value:t,onChange:C=>{r(C),j(""),p("")},description:"Its endpoint is built in."}),e.jsx(R,{value:i,onChange:l,label:d&&!o?"API key (optional)":"API key",description:d?o?`${d.name}'s endpoint is built in — just add your key.`:k?`${d.env_key} is set on the server, so a key is optional here. Paste one to override it.`:`${d.name} needs no API key.`:"Stored encrypted. Requires OTARI_SECRET_KEY on the server."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>g(C=>!C),children:m?"Hide advanced":"Advanced (API base, rename)"}),m?e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(S,{label:"API base",value:u,onChange:p,placeholder:(d==null?void 0:d.default_api_base)??"https://…/v1",description:"Only if you route through a proxy. Blank uses the built-in default."}),e.jsx(S,{label:"Name",value:c,onChange:j,placeholder:t||"instance name",description:N?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Rename to run two instances of the same provider."})]}):null,e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:!P,onPress:A,children:a.isPending?"Adding…":"Add provider"}),e.jsx(f,{variant:"ghost",onPress:s,children:"Cancel"}),e.jsx(z,{getPayload:()=>t===""?null:{instance:y?c.trim():t,provider_type:y?t:null,api_base:u.trim()||null,api_key:i.trim()||null}})]})]})}function he({onClose:s}){const a=O(),[t,r]=x.useState(""),[i,l]=x.useState("openai-compatible"),[m,g]=x.useState(""),[u,p]=x.useState(""),c=/[:/]/.test(t),j=t.trim()!==""&&!c&&m.trim()!==""&&!a.isPending,h=()=>{j&&a.mutate({instance:t.trim(),provider_type:i||"openai-compatible",api_base:m.trim(),api_key:u.trim()||null},{onSuccess:s})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:a.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(S,{label:"Name",value:t,onChange:r,placeholder:"my-local-llm",isRequired:!0,autoFocus:!0,description:c?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Call it whatever you want."}),e.jsx(U,{label:"Compatible with",value:i,onChange:l,includeCatalog:!1,description:"The API this endpoint speaks.",extra:[{id:"openai-compatible",name:"OpenAI"},{id:"anthropic-compatible",name:"Anthropic"}]})]}),e.jsx(S,{label:"API base",value:m,onChange:g,placeholder:"http://localhost:8000/v1",isRequired:!0,description:"The endpoint URL of your server."}),e.jsx(R,{value:u,onChange:p,label:"API key (optional)",description:"Many local backends need none. Stored encrypted."}),e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:!j,onPress:h,children:a.isPending?"Adding…":"Add provider"}),e.jsx(f,{variant:"ghost",onPress:s,children:"Cancel"}),e.jsx(z,{getPayload:()=>t.trim()===""||m.trim()===""?null:{instance:t.trim(),provider_type:i||"openai-compatible",api_base:m.trim(),api_key:u.trim()||null}})]})]})}function ve({onClose:s}){const[a,t]=x.useState("known");return e.jsx(_,{children:e.jsxs(_.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"flex items-center justify-between",children:e.jsx("div",{className:"flex items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[["known","Known provider"],["custom","Custom endpoint"]].map(([r,i])=>e.jsx("button",{type:"button","aria-pressed":a===r,onClick:()=>t(r),className:a===r?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:i},r))})}),a==="known"?e.jsx(pe,{onClose:s}):e.jsx(he,{onClose:s})]})})}function ge({provider:s,onClose:a}){const t=te(),[r,i]=x.useState(s.provider_type??""),[l,m]=x.useState(s.api_base??""),[g,u]=x.useState(!1),[p,c]=x.useState(""),j=()=>{if(t.isPending)return;const h={provider_type:r.trim()||null,api_base:l.trim()||null,expected_updated_at:s.updated_at};g&&p.trim()&&(h.api_key=p.trim()),t.mutate({instance:s.instance,body:h},{onSuccess:a})};return e.jsx(_,{children:e.jsxs(_.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:s.instance})]}),e.jsx(I,{error:t.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(S,{label:"Provider type",value:r,onChange:i,placeholder:"openai"}),e.jsx(S,{label:"API base",value:l,onChange:m,placeholder:"https://api.openai.com/v1"})]}),e.jsx("div",{className:"flex flex-col gap-2",children:g?e.jsxs(e.Fragment,{children:[e.jsx(R,{value:p,onChange:c,label:"New API key",description:"Stored encrypted. The old key is replaced when you save."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>{u(!1),c("")},children:"Keep the current key"})]}):e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"text-sm text-[var(--otari-muted)]",children:["API key: ",e.jsx("code",{children:s.last4?`••••${s.last4}`:"none set"})]}),e.jsx(f,{size:"sm",variant:"outline",onPress:()=>u(!0),children:"Replace key"})]})}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:t.isPending,onPress:j,children:t.isPending?"Saving…":"Save changes"}),e.jsx(f,{variant:"ghost",onPress:a,children:"Cancel"})]})]})})}function fe(s,a){const t=new Map((a??[]).map(l=>[l.instance,l])),r=new Map((s??[]).map(l=>[l.instance,l]));return[...new Set([...t.keys(),...r.keys()])].sort().map(l=>{const m=t.get(l);return{instance:l,source:m?"stored":"config",stored:m,meta:r.get(l)}})}function je({state:s}){return s?s.status==="pending"?e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsx(me,{size:"sm"})," Testing…"]}):s.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",s.model_count," model",s.model_count===1?"":"s"," available."]}):e.jsx("span",{className:"block max-w-xs break-words text-xs text-red-700",children:s.error??"Connection failed."}):null}function ye({health:s}){if(!s)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"—"});const a=s.ok?"border-green-200 bg-green-50 text-green-700":"border-red-200 bg-red-50 text-red-700",t=s.checked_at?`Last checked ${$(s.checked_at)}`:"Not checked yet",r=s.ok?t:`${s.error??"Unreachable"} · ${t}`;return e.jsxs("span",{title:r,className:`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium ${a}`,children:[e.jsx("span",{"aria-hidden":!0,className:`h-1.5 w-1.5 rounded-full ${s.ok?"bg-green-500":"bg-red-500"}`}),s.ok?"Reachable":"Unreachable"]})}function be({healthy:s,total:a,checkedAt:t}){const r=s===a,i=se();return e.jsxs("div",{className:"flex flex-wrap items-center gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] px-4 py-2.5 text-sm",children:[e.jsx("span",{"aria-hidden":!0,className:`h-2 w-2 rounded-full ${r?"bg-green-500":"bg-red-500"}`}),e.jsxs("span",{className:"font-medium text-[var(--otari-ink)]",children:[s," of ",a," provider",a===1?"":"s"," reachable"]}),t?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["Last checked ",$(t)]}):null,e.jsx(f,{size:"sm",variant:"ghost",className:"ml-auto",isDisabled:i.isPending,onPress:()=>i.mutate(),children:i.isPending?"Re-checking…":"Re-check all"})]})}function E({n:s,title:a,children:t}){return e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--otari-brand-tint)] text-xs font-semibold text-[var(--otari-brand-dark)]",children:s}),e.jsxs("div",{className:"text-sm",children:[e.jsx("div",{className:"font-medium text-[var(--otari-ink)]",children:a}),e.jsx("div",{className:"text-[var(--otari-muted)]",children:t})]})]})}function ke({onAddProvider:s,needsPricing:a,onEnablePricing:t,enabling:r,secretKeyConfigured:i}){return e.jsx(_,{children:e.jsxs(_.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Welcome to Otari"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"You are signed in. Add a provider to start serving models: three quick steps."})]}),e.jsxs("ol",{className:"flex flex-col gap-3",children:[e.jsxs(E,{n:1,title:"Add a provider",children:["Enter a provider name (like ",e.jsx("code",{children:"openai"}),") and its API key. Keys are encrypted at rest."]}),e.jsxs(E,{n:2,title:"Test the connection",children:["Use ",e.jsx("strong",{children:"Test"})," on the provider row to confirm the key works and see how many models it serves."]}),e.jsxs(E,{n:3,title:"Send your first request",children:["Point your app at ",e.jsx("code",{children:"/v1"})," on this gateway with the API key printed in the server logs (",e.jsx("code",{children:"gw-…"}),"). See the"," ",e.jsx("a",{href:"/welcome",className:"font-medium text-[var(--otari-brand-dark)]",children:"quickstart"}),"."]})]}),a?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Tip: ",e.jsx("code",{children:"require_pricing"})," is on, so requests are rejected until pricing is set."," ",e.jsx("button",{type:"button",className:"font-medium text-[var(--otari-brand-dark)] disabled:opacity-50",disabled:r,onClick:t,children:"Enable default pricing"})," ","to meter new models with public rates."]}):null,e.jsx("div",{children:e.jsx(f,{variant:"primary",isDisabled:!i,onPress:s,children:"Add your first provider"})})]})})}function we(){var K,B,D,L;const s=Y(),a=V(),t=W(),r=G(),i=X(),l=Z(),m=J(),[g,u]=x.useState(!1),[p,c]=x.useState(null),[j,h]=x.useState({}),d=fe((K=s.data)==null?void 0:K.providers,a.data),k=new Map((((B=r.data)==null?void 0:B.providers)??[]).map(n=>[n.instance,n])),o=s.isLoading||a.isLoading,y=((D=a.data)==null?void 0:D.find(n=>n.instance===p))??null,N=((L=t.data)==null?void 0:L.require_pricing)===!0&&t.data.default_pricing===!1,P=t.data?t.data.secret_key_configured!==!1:!t.isError,A=!o&&d.length===0&&!g,w=n=>{h(v=>({...v,[n]:{status:"pending"}})),l.mutate(n,{onSuccess:v=>h(b=>({...b,[n]:{status:"done",...v}})),onError:v=>h(b=>({...b,[n]:{status:"done",ok:!1,model_count:0,error:q(v)}}))})},C=[{id:"provider",header:"Provider",isRowHeader:!0,cell:n=>e.jsx(M,{to:`/models?provider=${encodeURIComponent(n.instance)}`,className:"font-medium text-[var(--otari-ink)] hover:text-[var(--otari-brand-dark)] hover:underline",children:n.instance})},{id:"type",header:"Type",cell:n=>{var v,b;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:((v=n.meta)==null?void 0:v.provider_type)??((b=n.stored)==null?void 0:b.provider_type)??n.instance})}},{id:"source",header:"Source",cell:n=>e.jsx(de,{size:"sm",color:n.source==="stored"?"accent":"default",children:n.source==="stored"?"stored":"config"})},{id:"api_key",header:"API key",cell:n=>{var v,b;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:n.source==="stored"?n.stored&&!n.stored.decryptable?e.jsx("span",{className:"text-amber-700",title:"This key can't be decrypted with the current OTARI_SECRET_KEY. Replace the key, or restore the original OTARI_SECRET_KEY.",children:"⚠ key unreadable"}):e.jsx("code",{children:(v=n.stored)!=null&&v.last4?`••••${n.stored.last4}`:"none set"}):(b=n.meta)!=null&&b.env_key?e.jsxs("span",{children:["via ",e.jsx("code",{children:n.meta.env_key})]}):"config.yml"})}},{id:"status",header:"Status",cell:n=>e.jsx(ye,{health:k.get(n.instance)})},{id:"actions",header:"Actions",align:"end",cell:n=>{var v,b;return n.source==="stored"?e.jsxs("div",{className:"flex flex-col items-end gap-1.5",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(f,{size:"sm",variant:"outline",isDisabled:((v=j[n.instance])==null?void 0:v.status)==="pending"||((b=n.stored)==null?void 0:b.decryptable)===!1,onPress:()=>w(n.instance),children:"Test"}),e.jsx(f,{size:"sm",variant:"ghost",onPress:()=>{u(!1),c(n.instance)},children:"Edit"}),e.jsx(ae,{confirmLabel:"Delete",isPending:i.isPending,onConfirm:()=>i.mutate(n.instance),children:"Delete"})]}),e.jsx(je,{state:j[n.instance]})]}):e.jsx("span",{className:"block text-right text-xs text-[var(--otari-muted)]",children:"managed in config.yml"})}}];return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Q,{title:"Providers",description:"Add provider API keys here to serve models without editing config.yml. Keys are encrypted at rest.",action:g||A?null:e.jsx(f,{variant:"primary",isDisabled:!P,onPress:()=>{c(null),u(!0)},children:"Add provider"})}),e.jsx(I,{error:s.error??a.error??t.error??r.error??m.error??i.error}),P?null:e.jsxs(ee,{tone:"warning",children:[e.jsx("code",{children:"OTARI_SECRET_KEY"})," is not set, so provider keys can't be encrypted at rest and adding providers from the dashboard is disabled. Set it on the server and restart to add providers here. Providers defined in"," ",e.jsx("code",{children:"config.yml"})," keep working without it."]}),A?e.jsx(ke,{onAddProvider:()=>{c(null),u(!0)},needsPricing:N,onEnablePricing:()=>m.mutate({default_pricing:!0}),enabling:m.isPending,secretKeyConfigured:P}):null,g&&P?e.jsx(ve,{onClose:()=>u(!1)}):null,y?e.jsx(ge,{provider:y,onClose:()=>c(null)}):null,!o&&d.length>0&&r.data&&r.data.total>0?e.jsx(be,{healthy:r.data.healthy,total:r.data.total,checkedAt:r.data.checked_at}):null,e.jsx(oe,{ariaLabel:"Providers",columns:C,rows:d,getRowKey:n=>n.instance,isLoading:o,emptyContent:"No providers yet. Add your first provider to start serving models."})]})}export{we as ProvidersPage}; diff --git a/src/gateway/static/dashboard/assets/SettingsPage-DovUcX7T.js b/src/gateway/static/dashboard/assets/SettingsPage-D0bz-SUp.js similarity index 99% rename from src/gateway/static/dashboard/assets/SettingsPage-DovUcX7T.js rename to src/gateway/static/dashboard/assets/SettingsPage-D0bz-SUp.js index 91d26911..5792ed8a 100644 --- a/src/gateway/static/dashboard/assets/SettingsPage-DovUcX7T.js +++ b/src/gateway/static/dashboard/assets/SettingsPage-D0bz-SUp.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as u}from"./react-dgEcD0HR.js";import{H as R,X as C,P,E as y,a2 as E,a3 as T,a4 as _,F as D,a5 as A,U as O,a6 as F,I as w}from"./index-DHOrDaWF.js";import{d as v,B as h,A as d,g as I,I as K}from"./heroui-DZj66Arc.js";function b(t,r){return{[t]:r}}function z(t,r){let s=0;for(const a of r)if(a===t[s]&&(s+=1),s===t.length)return!0;return t.length===0}function M(t,r){const s=r.trim().toLowerCase();if(s==="")return!0;const a=`${t.key} ${t.description??""} ${t.group}`.toLowerCase(),n=t.key.toLowerCase().replace(/[^a-z0-9]/g,"");return s.split(/\s+/).every(i=>a.includes(i)||z(i,n))}function B({checked:t,onChange:r,label:s,disabled:a}){return e.jsx("button",{type:"button",role:"switch","aria-checked":t,"aria-label":s,disabled:a,onClick:()=>r(!t),className:`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors disabled:opacity-50 ${t?"bg-[var(--otari-brand)]":"bg-[var(--otari-line)]"}`,children:e.jsx("span",{className:`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${t?"translate-x-5":"translate-x-0.5"}`})})}function H({field:t,onSave:r,disabled:s}){const a=typeof t.value=="number"?t.value:0,[n,i]=u.useState(String(a)),o=t.type==="float";u.useEffect(()=>{i(String(a))},[a]);const c=Number(n),p=n.trim()!==""&&Number.isFinite(c)&&(o||Number.isInteger(c)),m=t.minimum??void 0,x=t.exclusive_minimum??void 0,g=x!==void 0?c>x:m!==void 0?c>=m:c>=0,l=p&&g&&c!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(K,{type:"number",min:"0",step:o?"any":"1",inputMode:o?"decimal":"numeric","aria-label":t.key,value:n,disabled:s,onChange:f=>i(f.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!l,onPress:()=>r(c),children:"Save"})]})}function L({field:t,onSave:r,disabled:s}){const a=typeof t.value=="string"?t.value:"",[n,i]=u.useState(a);u.useEffect(()=>{i(a)},[a]);const o=n!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text","aria-label":t.key,value:n,disabled:s,placeholder:"unset",onChange:c=>i(c.target.value),className:"w-56 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!o,onPress:()=>r(n.trim()===""?null:n),children:"Save"})]})}function $(t){const{value:r}=t;return r==null?"unset":typeof r=="boolean"?r?"on":"off":Array.isArray(r)?r.length?r.join(", "):"none":String(r)}function Y({field:t,patch:r,disabled:s}){return t.settable?t.type==="bool"?e.jsx(B,{checked:t.value===!0,onChange:a=>r(b(t.key,a)),label:t.key,disabled:s}):t.options&&t.options.length>0?e.jsx(D,{ariaLabel:t.key,value:String(t.value??""),onChange:a=>r(b(t.key,a)),options:t.options.map(a=>({value:a,label:a}))}):t.type==="int"||t.type==="float"?e.jsx(H,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsx(L,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-sm tabular-nums text-[var(--otari-ink)]",children:$(t)}),e.jsx("span",{className:"rounded-full border border-[var(--otari-line)] px-2 py-0.5 text-xs text-[var(--otari-muted)]",children:"startup-only"})]})}function q({field:t,patch:r,disabled:s}){return e.jsxs("div",{className:"flex items-start justify-between gap-6 py-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:t.key}),t.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t.description}):null]}),e.jsx("div",{className:"shrink-0 pt-0.5",children:e.jsx(Y,{field:t,patch:r,disabled:s})})]})}function U({value:t,fieldRef:r}){const s=u.useRef(null),a=r??s,[n,i]=u.useState(!1),[o,c]=u.useState(!1),p=async()=>{var m,x,g;(m=a.current)==null||m.focus(),(x=a.current)==null||x.select();try{if((g=navigator.clipboard)!=null&&g.writeText){await navigator.clipboard.writeText(t),i(!0),c(!1),window.setTimeout(()=>i(!1),2e3);return}}catch{}c(!0)};return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:"New master key"}),e.jsx(h,{size:"sm",variant:"outline",onPress:p,children:n?"Copied":"Copy"})]}),e.jsx("input",{ref:a,readOnly:!0,value:t,onFocus:m=>m.currentTarget.select(),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),e.jsx("span",{"aria-live":"polite",className:"text-xs text-[var(--otari-brand-dark)]",children:n?"Copied to clipboard.":""}),o?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Selected. Press Ctrl/Cmd-C to copy."}):null]})}function V({masterKey:t,error:r,isPending:s,onRegenerate:a,onClose:n}){const i=u.useRef(null);return u.useEffect(()=>{var o,c;t!==void 0&&((o=i.current)==null||o.focus(),(c=i.current)==null||c.select())},[t]),e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:t!==void 0?"Master key regenerated":"Regenerate master key?"})}),e.jsx(d.Body,{className:"flex flex-col gap-4",children:t!==void 0?e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"Copy this key now. It is shown once and cannot be retrieved again after you close this dialog."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The previous master key has stopped working. This browser tab now uses the new key."}),e.jsx(U,{value:t,fieldRef:i})]}):e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"This immediately invalidates the current dashboard master key. Other signed-in dashboard sessions will need the new key to continue."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The replacement key will be shown once. Save it before closing the next screen."}),e.jsx(y,{error:r})]})}),e.jsx(d.Footer,{children:t!==void 0?e.jsx(h,{variant:"primary",onPress:n,children:"I’ve saved this key"}):e.jsxs(e.Fragment,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Cancel"}),e.jsx(h,{variant:"danger",isPending:s,onPress:a,children:"Regenerate key"})]})})]})})})}function G({source:t}){const r=A(),[s,a]=u.useState(!1),[n,i]=u.useState(),o=t==="generated",c=()=>r.mutate(void 0,{onSuccess:x=>{i(x.master_key)}}),p=()=>{a(!1),i(void 0),r.reset()},m=x=>{x?(r.reset(),a(!0)):n===void 0&&p()};return e.jsx("div",{className:"flex flex-col gap-4 py-4",children:e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"master_key"}),e.jsx("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:o?"This gateway uses its first-run generated dashboard key. Regeneration invalidates the current key immediately.":"This gateway uses a key managed through OTARI_MASTER_KEY or config.yml. Rotate it in configuration, then restart the gateway."})]}),e.jsxs(d,{isOpen:s,onOpenChange:m,children:[o?e.jsx(d.Trigger,{className:I({size:"sm",variant:"danger-soft"}),children:"Regenerate"}):e.jsx(h,{size:"sm",variant:"danger-soft",isDisabled:!0,children:"Managed in configuration"}),s?e.jsx(V,{masterKey:n,error:r.error,isPending:r.isPending,onRegenerate:c,onClose:p}):null]})]})})}function X(){var o;const t=O(),r=F(),s=r.data,a=((o=t.data)==null?void 0:o.length)??0,n=(t.data??[]).filter(c=>!c.decryptable).length,i=a>0;return e.jsxs("div",{className:"flex flex-col gap-4 py-4",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"OTARI_SECRET_KEY"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Generate a new key with ",e.jsx("code",{children:"uv run otari gen-secret-key"}),", then restart with"," ",e.jsx("code",{children:"OTARI_SECRET_KEY=,"}),". Re-encrypt the stored provider keys, then restart with ",e.jsx("code",{children:"OTARI_SECRET_KEY="})," once none are unreadable."]})]}),e.jsx("div",{className:"shrink-0",children:e.jsx(h,{size:"sm",variant:"outline",isDisabled:!i||r.isPending,onPress:()=>r.mutate(),children:r.isPending?"Re-encrypting…":"Re-encrypt provider keys"})})]}),e.jsx(y,{error:t.error??r.error}),n>0?e.jsxs(w,{tone:"warning",children:[n," stored provider key",n===1?"":"s"," cannot be decrypted with the current"," ",e.jsx("code",{children:"OTARI_SECRET_KEY"}),". Restore the old secret key and re-encrypt, or edit each affected provider and replace its key."]}):null,s?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",role:"status","aria-live":"polite",children:["Re-encrypted ",s.reencrypted," provider key",s.reencrypted===1?"":"s",".",s.unreadable>0?` ${s.unreadable} still need replacement.`:" All decryptable stored keys now use the primary secret key."]}):!t.isLoading&&!i?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No stored provider keys need re-encryption."}):null]})}function J({masterKeySource:t}){return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Credential security ",e.jsx("span",{className:"font-normal text-[var(--otari-muted)]",children:"(2)"})]}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:[e.jsx(G,{source:t}),e.jsx(X,{})]})})]})}function Q({preview:t,error:r,isPending:s,onAccept:a,onReject:n}){return e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:"Review default price updates"})}),e.jsxs(d.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:[t.added_count," added, ",t.changed_count," changed, and ",t.removed_count," removed upstream model prices. The accepted catalog is saved in the database with source ",e.jsx("code",{children:"genai-prices"})," and reloads after a restart. Your ",t.protected_model_count," custom model price",t.protected_model_count===1?"":"s"," remain unchanged."]}),t.changes.length>0?e.jsx("ul",{className:"max-h-60 list-disc overflow-auto pl-5 text-sm text-[var(--otari-ink)]",children:t.changes.map(i=>e.jsxs("li",{children:[i.model_key,": ",i.change]},i.model_key))}):null,t.changes_truncated?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Only the first 100 changes are shown."}):null,e.jsx(y,{error:r})]}),e.jsxs(d.Footer,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Reject changes"}),e.jsx(h,{variant:"primary",isPending:s,onPress:a,children:"Accept price updates"})]})]})})})}function W(){const t=E(),r=T(),s=_(),a=t.data,n=r.isPending||s.isPending,i=()=>{a===void 0||n||s.mutate(void 0,{onSuccess:t.reset})};return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Default pricing catalog"}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"genai-prices defaults"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Fetch the latest upstream catalog, review the proposed change summary, then accept or reject it. Accepted data is stored as ",e.jsx("code",{children:"genai-prices"}),"; custom prices remain separate and always take precedence."]})]}),e.jsx(h,{size:"sm",variant:"outline",isDisabled:t.isPending||n,onPress:()=>t.mutate(),children:t.isPending?"Checking prices…":"Check for price updates"})]}),e.jsx(y,{error:t.error})]})}),e.jsxs(d,{isOpen:a!==void 0,onOpenChange:o=>o?void 0:i(),children:[e.jsx(d.Trigger,{className:"hidden",children:"Review price updates"}),a?e.jsx(Q,{preview:a,error:r.error??s.error,isPending:n,onAccept:()=>r.mutate(void 0,{onSuccess:t.reset}),onReject:i}):null]})]})}function Z(t){const r=[],s=new Map;for(const a of t){let n=s.get(a.group);n||(n={name:a.group,fields:[]},s.set(a.group,n),r.push(n)),n.fields.push(a)}return r}function ae(){const t=R(),r=C(),s=t.data,a=r.isPending,[n,i]=u.useState(""),[o,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{function l(f){var N;const j=f.target,S=j&&(j.tagName==="INPUT"||j.tagName==="TEXTAREA"||j.tagName==="SELECT");f.key==="/"&&!S&&(f.preventDefault(),(N=p.current)==null||N.focus())}return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[]);const m=l=>r.mutate(l),x=(s==null?void 0:s.config)??[],g=x.filter(l=>(o?l.settable:!0)&&M(l,n)),k=Z(g);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(P,{title:"Settings",description:"Every effective gateway setting. Settable fields apply immediately and persist across restarts; startup-only fields are shown for reference and change only via config.yml or environment variables (then a restart)."}),e.jsx(y,{error:t.error??r.error}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("input",{ref:p,type:"search","aria-label":"Search settings",placeholder:"Search settings (press / to focus)…",value:n,onChange:l=>i(l.target.value),onKeyDown:l=>{l.key==="Escape"&&i("")},className:"min-w-0 flex-1 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"}),e.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:o,onChange:l=>c(l.target.checked),className:"h-4 w-4 accent-[var(--otari-brand)]"}),"Settable only"]})]}),s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",g.length," of ",x.length," settings"]}):null,s&&g.length===0?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No settings match your search."}):null,k.map(l=>e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:[l.name," ",e.jsxs("span",{className:"font-normal text-[var(--otari-muted)]",children:["(",l.fields.length,")"]})]}),e.jsx(v,{children:e.jsx(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:l.fields.map(f=>e.jsx(q,{field:f,patch:m,disabled:!s||a},f.key))})})]},l.name)),s?e.jsx(W,{}):null,s?e.jsx(J,{masterKeySource:s.master_key_source}):null,s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Mode: ",s.mode," · Version ",s.version,s.require_pricing?" · require_pricing on":""]}):null]})}export{ae as SettingsPage,M as fieldMatches}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as u}from"./react-dgEcD0HR.js";import{H as R,X as C,P,E as y,a2 as E,a3 as T,a4 as _,F as D,a5 as A,U as O,a6 as F,I as w}from"./index-BzgMrLHf.js";import{d as v,B as h,A as d,g as I,I as K}from"./heroui-DZj66Arc.js";function b(t,r){return{[t]:r}}function z(t,r){let s=0;for(const a of r)if(a===t[s]&&(s+=1),s===t.length)return!0;return t.length===0}function M(t,r){const s=r.trim().toLowerCase();if(s==="")return!0;const a=`${t.key} ${t.description??""} ${t.group}`.toLowerCase(),n=t.key.toLowerCase().replace(/[^a-z0-9]/g,"");return s.split(/\s+/).every(i=>a.includes(i)||z(i,n))}function B({checked:t,onChange:r,label:s,disabled:a}){return e.jsx("button",{type:"button",role:"switch","aria-checked":t,"aria-label":s,disabled:a,onClick:()=>r(!t),className:`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors disabled:opacity-50 ${t?"bg-[var(--otari-brand)]":"bg-[var(--otari-line)]"}`,children:e.jsx("span",{className:`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${t?"translate-x-5":"translate-x-0.5"}`})})}function H({field:t,onSave:r,disabled:s}){const a=typeof t.value=="number"?t.value:0,[n,i]=u.useState(String(a)),o=t.type==="float";u.useEffect(()=>{i(String(a))},[a]);const c=Number(n),p=n.trim()!==""&&Number.isFinite(c)&&(o||Number.isInteger(c)),m=t.minimum??void 0,x=t.exclusive_minimum??void 0,g=x!==void 0?c>x:m!==void 0?c>=m:c>=0,l=p&&g&&c!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(K,{type:"number",min:"0",step:o?"any":"1",inputMode:o?"decimal":"numeric","aria-label":t.key,value:n,disabled:s,onChange:f=>i(f.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!l,onPress:()=>r(c),children:"Save"})]})}function L({field:t,onSave:r,disabled:s}){const a=typeof t.value=="string"?t.value:"",[n,i]=u.useState(a);u.useEffect(()=>{i(a)},[a]);const o=n!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text","aria-label":t.key,value:n,disabled:s,placeholder:"unset",onChange:c=>i(c.target.value),className:"w-56 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!o,onPress:()=>r(n.trim()===""?null:n),children:"Save"})]})}function $(t){const{value:r}=t;return r==null?"unset":typeof r=="boolean"?r?"on":"off":Array.isArray(r)?r.length?r.join(", "):"none":String(r)}function Y({field:t,patch:r,disabled:s}){return t.settable?t.type==="bool"?e.jsx(B,{checked:t.value===!0,onChange:a=>r(b(t.key,a)),label:t.key,disabled:s}):t.options&&t.options.length>0?e.jsx(D,{ariaLabel:t.key,value:String(t.value??""),onChange:a=>r(b(t.key,a)),options:t.options.map(a=>({value:a,label:a}))}):t.type==="int"||t.type==="float"?e.jsx(H,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsx(L,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-sm tabular-nums text-[var(--otari-ink)]",children:$(t)}),e.jsx("span",{className:"rounded-full border border-[var(--otari-line)] px-2 py-0.5 text-xs text-[var(--otari-muted)]",children:"startup-only"})]})}function q({field:t,patch:r,disabled:s}){return e.jsxs("div",{className:"flex items-start justify-between gap-6 py-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:t.key}),t.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t.description}):null]}),e.jsx("div",{className:"shrink-0 pt-0.5",children:e.jsx(Y,{field:t,patch:r,disabled:s})})]})}function U({value:t,fieldRef:r}){const s=u.useRef(null),a=r??s,[n,i]=u.useState(!1),[o,c]=u.useState(!1),p=async()=>{var m,x,g;(m=a.current)==null||m.focus(),(x=a.current)==null||x.select();try{if((g=navigator.clipboard)!=null&&g.writeText){await navigator.clipboard.writeText(t),i(!0),c(!1),window.setTimeout(()=>i(!1),2e3);return}}catch{}c(!0)};return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:"New master key"}),e.jsx(h,{size:"sm",variant:"outline",onPress:p,children:n?"Copied":"Copy"})]}),e.jsx("input",{ref:a,readOnly:!0,value:t,onFocus:m=>m.currentTarget.select(),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),e.jsx("span",{"aria-live":"polite",className:"text-xs text-[var(--otari-brand-dark)]",children:n?"Copied to clipboard.":""}),o?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Selected. Press Ctrl/Cmd-C to copy."}):null]})}function V({masterKey:t,error:r,isPending:s,onRegenerate:a,onClose:n}){const i=u.useRef(null);return u.useEffect(()=>{var o,c;t!==void 0&&((o=i.current)==null||o.focus(),(c=i.current)==null||c.select())},[t]),e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:t!==void 0?"Master key regenerated":"Regenerate master key?"})}),e.jsx(d.Body,{className:"flex flex-col gap-4",children:t!==void 0?e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"Copy this key now. It is shown once and cannot be retrieved again after you close this dialog."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The previous master key has stopped working. This browser tab now uses the new key."}),e.jsx(U,{value:t,fieldRef:i})]}):e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"This immediately invalidates the current dashboard master key. Other signed-in dashboard sessions will need the new key to continue."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The replacement key will be shown once. Save it before closing the next screen."}),e.jsx(y,{error:r})]})}),e.jsx(d.Footer,{children:t!==void 0?e.jsx(h,{variant:"primary",onPress:n,children:"I’ve saved this key"}):e.jsxs(e.Fragment,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Cancel"}),e.jsx(h,{variant:"danger",isPending:s,onPress:a,children:"Regenerate key"})]})})]})})})}function G({source:t}){const r=A(),[s,a]=u.useState(!1),[n,i]=u.useState(),o=t==="generated",c=()=>r.mutate(void 0,{onSuccess:x=>{i(x.master_key)}}),p=()=>{a(!1),i(void 0),r.reset()},m=x=>{x?(r.reset(),a(!0)):n===void 0&&p()};return e.jsx("div",{className:"flex flex-col gap-4 py-4",children:e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"master_key"}),e.jsx("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:o?"This gateway uses its first-run generated dashboard key. Regeneration invalidates the current key immediately.":"This gateway uses a key managed through OTARI_MASTER_KEY or config.yml. Rotate it in configuration, then restart the gateway."})]}),e.jsxs(d,{isOpen:s,onOpenChange:m,children:[o?e.jsx(d.Trigger,{className:I({size:"sm",variant:"danger-soft"}),children:"Regenerate"}):e.jsx(h,{size:"sm",variant:"danger-soft",isDisabled:!0,children:"Managed in configuration"}),s?e.jsx(V,{masterKey:n,error:r.error,isPending:r.isPending,onRegenerate:c,onClose:p}):null]})]})})}function X(){var o;const t=O(),r=F(),s=r.data,a=((o=t.data)==null?void 0:o.length)??0,n=(t.data??[]).filter(c=>!c.decryptable).length,i=a>0;return e.jsxs("div",{className:"flex flex-col gap-4 py-4",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"OTARI_SECRET_KEY"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Generate a new key with ",e.jsx("code",{children:"uv run otari gen-secret-key"}),", then restart with"," ",e.jsx("code",{children:"OTARI_SECRET_KEY=,"}),". Re-encrypt the stored provider keys, then restart with ",e.jsx("code",{children:"OTARI_SECRET_KEY="})," once none are unreadable."]})]}),e.jsx("div",{className:"shrink-0",children:e.jsx(h,{size:"sm",variant:"outline",isDisabled:!i||r.isPending,onPress:()=>r.mutate(),children:r.isPending?"Re-encrypting…":"Re-encrypt provider keys"})})]}),e.jsx(y,{error:t.error??r.error}),n>0?e.jsxs(w,{tone:"warning",children:[n," stored provider key",n===1?"":"s"," cannot be decrypted with the current"," ",e.jsx("code",{children:"OTARI_SECRET_KEY"}),". Restore the old secret key and re-encrypt, or edit each affected provider and replace its key."]}):null,s?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",role:"status","aria-live":"polite",children:["Re-encrypted ",s.reencrypted," provider key",s.reencrypted===1?"":"s",".",s.unreadable>0?` ${s.unreadable} still need replacement.`:" All decryptable stored keys now use the primary secret key."]}):!t.isLoading&&!i?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No stored provider keys need re-encryption."}):null]})}function J({masterKeySource:t}){return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Credential security ",e.jsx("span",{className:"font-normal text-[var(--otari-muted)]",children:"(2)"})]}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:[e.jsx(G,{source:t}),e.jsx(X,{})]})})]})}function Q({preview:t,error:r,isPending:s,onAccept:a,onReject:n}){return e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:"Review default price updates"})}),e.jsxs(d.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:[t.added_count," added, ",t.changed_count," changed, and ",t.removed_count," removed upstream model prices. The accepted catalog is saved in the database with source ",e.jsx("code",{children:"genai-prices"})," and reloads after a restart. Your ",t.protected_model_count," custom model price",t.protected_model_count===1?"":"s"," remain unchanged."]}),t.changes.length>0?e.jsx("ul",{className:"max-h-60 list-disc overflow-auto pl-5 text-sm text-[var(--otari-ink)]",children:t.changes.map(i=>e.jsxs("li",{children:[i.model_key,": ",i.change]},i.model_key))}):null,t.changes_truncated?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Only the first 100 changes are shown."}):null,e.jsx(y,{error:r})]}),e.jsxs(d.Footer,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Reject changes"}),e.jsx(h,{variant:"primary",isPending:s,onPress:a,children:"Accept price updates"})]})]})})})}function W(){const t=E(),r=T(),s=_(),a=t.data,n=r.isPending||s.isPending,i=()=>{a===void 0||n||s.mutate(void 0,{onSuccess:t.reset})};return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Default pricing catalog"}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"genai-prices defaults"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Fetch the latest upstream catalog, review the proposed change summary, then accept or reject it. Accepted data is stored as ",e.jsx("code",{children:"genai-prices"}),"; custom prices remain separate and always take precedence."]})]}),e.jsx(h,{size:"sm",variant:"outline",isDisabled:t.isPending||n,onPress:()=>t.mutate(),children:t.isPending?"Checking prices…":"Check for price updates"})]}),e.jsx(y,{error:t.error})]})}),e.jsxs(d,{isOpen:a!==void 0,onOpenChange:o=>o?void 0:i(),children:[e.jsx(d.Trigger,{className:"hidden",children:"Review price updates"}),a?e.jsx(Q,{preview:a,error:r.error??s.error,isPending:n,onAccept:()=>r.mutate(void 0,{onSuccess:t.reset}),onReject:i}):null]})]})}function Z(t){const r=[],s=new Map;for(const a of t){let n=s.get(a.group);n||(n={name:a.group,fields:[]},s.set(a.group,n),r.push(n)),n.fields.push(a)}return r}function ae(){const t=R(),r=C(),s=t.data,a=r.isPending,[n,i]=u.useState(""),[o,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{function l(f){var N;const j=f.target,S=j&&(j.tagName==="INPUT"||j.tagName==="TEXTAREA"||j.tagName==="SELECT");f.key==="/"&&!S&&(f.preventDefault(),(N=p.current)==null||N.focus())}return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[]);const m=l=>r.mutate(l),x=(s==null?void 0:s.config)??[],g=x.filter(l=>(o?l.settable:!0)&&M(l,n)),k=Z(g);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(P,{title:"Settings",description:"Every effective gateway setting. Settable fields apply immediately and persist across restarts; startup-only fields are shown for reference and change only via config.yml or environment variables (then a restart)."}),e.jsx(y,{error:t.error??r.error}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("input",{ref:p,type:"search","aria-label":"Search settings",placeholder:"Search settings (press / to focus)…",value:n,onChange:l=>i(l.target.value),onKeyDown:l=>{l.key==="Escape"&&i("")},className:"min-w-0 flex-1 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"}),e.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:o,onChange:l=>c(l.target.checked),className:"h-4 w-4 accent-[var(--otari-brand)]"}),"Settable only"]})]}),s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",g.length," of ",x.length," settings"]}):null,s&&g.length===0?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No settings match your search."}):null,k.map(l=>e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:[l.name," ",e.jsxs("span",{className:"font-normal text-[var(--otari-muted)]",children:["(",l.fields.length,")"]})]}),e.jsx(v,{children:e.jsx(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:l.fields.map(f=>e.jsx(q,{field:f,patch:m,disabled:!s||a},f.key))})})]},l.name)),s?e.jsx(W,{}):null,s?e.jsx(J,{masterKeySource:s.master_key_source}):null,s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Mode: ",s.mode," · Version ",s.version,s.require_pricing?" · require_pricing on":""]}):null]})}export{ae as SettingsPage,M as fieldMatches}; diff --git a/src/gateway/static/dashboard/assets/TablePagination-Dob9a4wG.js b/src/gateway/static/dashboard/assets/TablePagination-DlTV4kUZ.js similarity index 98% rename from src/gateway/static/dashboard/assets/TablePagination-Dob9a4wG.js rename to src/gateway/static/dashboard/assets/TablePagination-DlTV4kUZ.js index 609aeb6e..a6400d8f 100644 --- a/src/gateway/static/dashboard/assets/TablePagination-Dob9a4wG.js +++ b/src/gateway/static/dashboard/assets/TablePagination-DlTV4kUZ.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as u}from"./react-dgEcD0HR.js";import{I as w,E as M,F as D}from"./index-DHOrDaWF.js";import{A as o,B as x,T as F,L as $,I as T,S as y}from"./heroui-DZj66Arc.js";function _({label:t,value:r,onChange:a,isRequired:c,autoFocus:i}){return e.jsxs(F,{value:r,onChange:a,isRequired:c,className:"flex flex-col gap-1",children:[e.jsx($,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsx(T,{inputMode:"decimal",placeholder:"0.00",autoFocus:i})]})}function R(t){const r=t.trim();if(r==="")return null;const a=Number(r);return Number.isFinite(a)&&a>=0?a:Number.NaN}const C=t=>`Recompute cost for ${t.toLocaleString()} imported ${t===1?"row":"rows"} from each row's own token counts at these per-1M rates. Enforced gateway rows are never affected.`;function W({isOpen:t,onOpenChange:r,targetCount:a,isPending:c,error:i,onSubmit:E,title:I="Set price",description:L=C}){const[j,p]=u.useState(""),[n,h]=u.useState(""),[b,N]=u.useState(""),[f,v]=u.useState("");u.useEffect(()=>{t&&(p(""),h(""),N(""),v(""))},[t]);const m=R(j),l=R(n),d=R(b),s=R(f),g=m===null||Number.isNaN(m)||l===null||Number.isNaN(l)||Number.isNaN(d??0)||Number.isNaN(s??0),S=()=>{g||m===null||l===null||E({input_price_per_million:m,output_price_per_million:l,...d!==null&&!Number.isNaN(d)?{cache_read_price_per_million:d}:{},...s!==null&&!Number.isNaN(s)?{cache_write_price_per_million:s}:{}})};return e.jsx(o,{isOpen:t,onOpenChange:r,children:t?e.jsx(o.Backdrop,{children:e.jsx(o.Container,{placement:"center",size:"lg",children:e.jsxs(o.Dialog,{children:[e.jsx(o.Header,{children:e.jsx(o.Heading,{children:I})}),e.jsxs(o.Body,{className:"flex flex-col gap-4",children:[e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:L(a)}),e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2",children:[e.jsx(_,{label:"Input $ / 1M",value:j,onChange:p,isRequired:!0,autoFocus:!0}),e.jsx(_,{label:"Output $ / 1M",value:n,onChange:h,isRequired:!0}),e.jsx(_,{label:"Cache read $ / 1M",value:b,onChange:N}),e.jsx(_,{label:"Cache write $ / 1M",value:f,onChange:v})]}),e.jsx(w,{tone:"info",children:"Leave a cache rate blank to bill those tokens at the input rate."}),e.jsx(M,{error:i})]}),e.jsxs(o.Footer,{children:[e.jsx(x,{variant:"ghost",isDisabled:c,onPress:()=>r(!1),children:"Cancel"}),e.jsx(x,{variant:"primary",isDisabled:g,isPending:c,onPress:S,children:"Set price"})]})]})})}):null})}const P=[25,50,100];function q({page:t,pageSize:r,total:a,rowsOnPage:c,onPageChange:i,onPageSizeChange:E,pageSizeOptions:I=P,isFetching:L=!1,hasNextFallback:j=!1}){const p=u.useId(),n=a!=null?Math.max(1,Math.ceil(a/r)):null,h=t===0,b=n!=null?t>=n-1:!j,N=c>0?t*r+1:0,f=t*r+c,v=a!=null?a===0?"0 of 0":`${N.toLocaleString()}–${f.toLocaleString()} of ${a.toLocaleString()}`:c>0?`${N.toLocaleString()}–${f.toLocaleString()}`:"0",[m,l]=u.useState(String(t+1));u.useEffect(()=>{l(String(t+1))},[t]);const d=()=>{const s=Number.parseInt(m,10);if(Number.isNaN(s)){l(String(t+1));return}const g=n??Number.MAX_SAFE_INTEGER,S=Math.min(Math.max(s,1),g);S-1!==t?i(S-1):l(String(t+1))};return e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("label",{htmlFor:p,className:"text-sm text-[var(--otari-muted)]",children:"Rows"}),e.jsx(D,{id:p,ariaLabel:"Rows per page",value:String(r),onChange:s=>E(Number.parseInt(s,10)),options:I.map(s=>({value:String(s),label:String(s)}))}),L?e.jsx(y,{size:"sm"}):null]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"text-sm text-[var(--otari-muted)] tabular-nums",children:v}),e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(x,{size:"sm",variant:"outline","aria-label":"First page",isDisabled:h,onPress:()=>i(0),children:"«"}),e.jsx(x,{size:"sm",variant:"outline","aria-label":"Previous page",isDisabled:h,onPress:()=>i(t-1),children:"‹"}),e.jsxs("span",{className:"inline-flex items-center gap-1 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{"aria-label":"Page number",inputMode:"numeric",value:m,onChange:s=>l(s.target.value.replace(/[^0-9]/g,"")),onKeyDown:s=>{s.key==="Enter"&&s.currentTarget.blur()},onBlur:d,className:"w-12 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-2 py-1 text-center text-sm text-[var(--otari-ink)] tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"}),n!=null?e.jsxs("span",{className:"tabular-nums",children:["/ ",n.toLocaleString()]}):null]}),e.jsx(x,{size:"sm",variant:"outline","aria-label":"Next page",isDisabled:b,onPress:()=>i(t+1),children:"›"}),e.jsx(x,{size:"sm",variant:"outline","aria-label":"Last page",isDisabled:n==null||b,onPress:()=>n!=null&&i(n-1),children:"»"})]})]})]})}export{P,W as S,q as T}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as u}from"./react-dgEcD0HR.js";import{I as w,E as M,F as D}from"./index-BzgMrLHf.js";import{A as o,B as x,T as F,L as $,I as T,S as y}from"./heroui-DZj66Arc.js";function _({label:t,value:r,onChange:a,isRequired:c,autoFocus:i}){return e.jsxs(F,{value:r,onChange:a,isRequired:c,className:"flex flex-col gap-1",children:[e.jsx($,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsx(T,{inputMode:"decimal",placeholder:"0.00",autoFocus:i})]})}function R(t){const r=t.trim();if(r==="")return null;const a=Number(r);return Number.isFinite(a)&&a>=0?a:Number.NaN}const C=t=>`Recompute cost for ${t.toLocaleString()} imported ${t===1?"row":"rows"} from each row's own token counts at these per-1M rates. Enforced gateway rows are never affected.`;function W({isOpen:t,onOpenChange:r,targetCount:a,isPending:c,error:i,onSubmit:E,title:I="Set price",description:L=C}){const[j,p]=u.useState(""),[n,h]=u.useState(""),[b,N]=u.useState(""),[f,v]=u.useState("");u.useEffect(()=>{t&&(p(""),h(""),N(""),v(""))},[t]);const m=R(j),l=R(n),d=R(b),s=R(f),g=m===null||Number.isNaN(m)||l===null||Number.isNaN(l)||Number.isNaN(d??0)||Number.isNaN(s??0),S=()=>{g||m===null||l===null||E({input_price_per_million:m,output_price_per_million:l,...d!==null&&!Number.isNaN(d)?{cache_read_price_per_million:d}:{},...s!==null&&!Number.isNaN(s)?{cache_write_price_per_million:s}:{}})};return e.jsx(o,{isOpen:t,onOpenChange:r,children:t?e.jsx(o.Backdrop,{children:e.jsx(o.Container,{placement:"center",size:"lg",children:e.jsxs(o.Dialog,{children:[e.jsx(o.Header,{children:e.jsx(o.Heading,{children:I})}),e.jsxs(o.Body,{className:"flex flex-col gap-4",children:[e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:L(a)}),e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2",children:[e.jsx(_,{label:"Input $ / 1M",value:j,onChange:p,isRequired:!0,autoFocus:!0}),e.jsx(_,{label:"Output $ / 1M",value:n,onChange:h,isRequired:!0}),e.jsx(_,{label:"Cache read $ / 1M",value:b,onChange:N}),e.jsx(_,{label:"Cache write $ / 1M",value:f,onChange:v})]}),e.jsx(w,{tone:"info",children:"Leave a cache rate blank to bill those tokens at the input rate."}),e.jsx(M,{error:i})]}),e.jsxs(o.Footer,{children:[e.jsx(x,{variant:"ghost",isDisabled:c,onPress:()=>r(!1),children:"Cancel"}),e.jsx(x,{variant:"primary",isDisabled:g,isPending:c,onPress:S,children:"Set price"})]})]})})}):null})}const P=[25,50,100];function q({page:t,pageSize:r,total:a,rowsOnPage:c,onPageChange:i,onPageSizeChange:E,pageSizeOptions:I=P,isFetching:L=!1,hasNextFallback:j=!1}){const p=u.useId(),n=a!=null?Math.max(1,Math.ceil(a/r)):null,h=t===0,b=n!=null?t>=n-1:!j,N=c>0?t*r+1:0,f=t*r+c,v=a!=null?a===0?"0 of 0":`${N.toLocaleString()}–${f.toLocaleString()} of ${a.toLocaleString()}`:c>0?`${N.toLocaleString()}–${f.toLocaleString()}`:"0",[m,l]=u.useState(String(t+1));u.useEffect(()=>{l(String(t+1))},[t]);const d=()=>{const s=Number.parseInt(m,10);if(Number.isNaN(s)){l(String(t+1));return}const g=n??Number.MAX_SAFE_INTEGER,S=Math.min(Math.max(s,1),g);S-1!==t?i(S-1):l(String(t+1))};return e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("label",{htmlFor:p,className:"text-sm text-[var(--otari-muted)]",children:"Rows"}),e.jsx(D,{id:p,ariaLabel:"Rows per page",value:String(r),onChange:s=>E(Number.parseInt(s,10)),options:I.map(s=>({value:String(s),label:String(s)}))}),L?e.jsx(y,{size:"sm"}):null]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"text-sm text-[var(--otari-muted)] tabular-nums",children:v}),e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(x,{size:"sm",variant:"outline","aria-label":"First page",isDisabled:h,onPress:()=>i(0),children:"«"}),e.jsx(x,{size:"sm",variant:"outline","aria-label":"Previous page",isDisabled:h,onPress:()=>i(t-1),children:"‹"}),e.jsxs("span",{className:"inline-flex items-center gap-1 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{"aria-label":"Page number",inputMode:"numeric",value:m,onChange:s=>l(s.target.value.replace(/[^0-9]/g,"")),onKeyDown:s=>{s.key==="Enter"&&s.currentTarget.blur()},onBlur:d,className:"w-12 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-2 py-1 text-center text-sm text-[var(--otari-ink)] tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"}),n!=null?e.jsxs("span",{className:"tabular-nums",children:["/ ",n.toLocaleString()]}):null]}),e.jsx(x,{size:"sm",variant:"outline","aria-label":"Next page",isDisabled:b,onPress:()=>i(t+1),children:"›"}),e.jsx(x,{size:"sm",variant:"outline","aria-label":"Last page",isDisabled:n==null||b,onPress:()=>n!=null&&i(n-1),children:"»"})]})]})]})}export{P,W as S,q as T}; diff --git a/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-DIVtsycj.js b/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-BVn9h5wq.js similarity index 99% rename from src/gateway/static/dashboard/assets/ToolsGuardrailsPage-DIVtsycj.js rename to src/gateway/static/dashboard/assets/ToolsGuardrailsPage-BVn9h5wq.js index 0083b17a..0004257b 100644 --- a/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-DIVtsycj.js +++ b/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-BVn9h5wq.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as x}from"./react-dgEcD0HR.js";import{a7 as C,a8 as L,P as E,E as P,D as w,a9 as R,F as D}from"./index-DHOrDaWF.js";import{d as N,B as b}from"./heroui-DZj66Arc.js";function M(s,t){return{[s]:t}}const U=[{key:"web_search",label:"Web search",blurb:"Backend for otari_web_search tools (a SearXNG instance or a search adapter).",order:["web_search_url","web_search_engines","web_search_max_results","web_search_extract","web_search_purpose_hint"]},{key:"sandbox",label:"Code execution",blurb:"Backend for otari_code_execution tools (the sandbox that runs generated code).",order:["sandbox_url","sandbox_purpose_hint"]},{key:"guardrails",label:"Guardrails",blurb:"Default input-guardrails service used when a request does not pass its own guardrail URL.",order:["guardrails_url"]}];function B(){const[s,t]=x.useState(null),a=x.useRef(void 0),n=r=>{t(r),window.clearTimeout(a.current),a.current=window.setTimeout(()=>t(null),2500)};return x.useEffect(()=>()=>window.clearTimeout(a.current),[]),[s,n]}function $({message:s}){return s?e.jsxs("div",{role:"status","aria-live":"polite",className:"fixed right-4 bottom-4 z-50 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700 shadow-lg",children:[e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"h-5 w-5",children:e.jsx("path",{d:"M20 6 9 17l-5-5",strokeLinecap:"round",strokeLinejoin:"round"})}),s]}):null}const S="rounded-md border border-[var(--otari-line)] bg-[var(--otari-surface)] px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50",g="grid gap-x-4 gap-y-1.5 py-4 sm:grid-cols-[minmax(0,1fr)_16rem_10rem] sm:items-start",_=`w-full sm:col-start-2 ${S}`,k="flex items-center gap-2 sm:col-start-3",f="flex flex-col gap-1 sm:col-span-2 sm:col-start-2";function j({message:s}){return s?e.jsx("span",{className:"break-words text-xs text-red-700",children:s}):null}function y({field:s,help:t}){return e.jsxs("div",{className:"min-w-0 sm:col-start-1",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:s.key}),s.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:s.description}):null,t?e.jsx("p",{className:"mt-1 text-xs text-[var(--otari-muted)]",children:t}):null]})}function z({field:s,onSave:t,saveError:a,disabled:n}){const r=typeof s.value=="string"?s.value:"",[l,i]=x.useState(r),[m,p]=x.useState(null),c=R();x.useEffect(()=>{i(r)},[r]);const o=l.trim()!==r,d=l.trim(),h=m!==null&&m===d;return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s,help:"Leave blank and Save to fall back to the configured default."}),e.jsx("input",{type:"text",inputMode:"url","aria-label":s.key,value:l,disabled:n,placeholder:"unset",onChange:v=>{i(v.target.value),c.reset()},className:_}),e.jsxs("div",{className:k,children:[e.jsx(b,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:n||!o,onPress:()=>t(d===""?null:d),children:"Save"}),e.jsx(b,{size:"sm",variant:"outline","aria-label":`Test ${s.service}`,isDisabled:d===""||c.isPending,onPress:()=>{p(d),c.mutate({service:s.service,url:d})},children:c.isPending?"Testing…":"Test"})]}),e.jsxs("div",{className:f,children:[e.jsx("span",{role:"status","aria-live":"polite",className:"block break-words text-xs",children:c.isPending||!h?null:c.error?e.jsx("span",{className:"text-red-700",children:w(c.error)}):c.data?e.jsx("span",{className:c.data.ok?"font-medium text-green-700":"text-red-700",children:c.data.reason}):null}),e.jsx(j,{message:a})]})]})}function G({field:s,onSave:t,saveError:a,disabled:n}){const r=typeof s.value=="string"?s.value:"",[l,i]=x.useState(r);x.useEffect(()=>{i(r)},[r]);const m=l!==r;return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s}),e.jsx("input",{type:"text","aria-label":s.key,value:l,disabled:n,placeholder:"default",onChange:p=>i(p.target.value),className:_}),e.jsx("div",{className:k,children:e.jsx(b,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:n||!m,onPress:()=>t(l.trim()===""?null:l.trim()),children:"Save"})}),a?e.jsx("div",{className:f,children:e.jsx(j,{message:a})}):null]})}function I({field:s,onSave:t,saveError:a,disabled:n}){const r=typeof s.value=="number"?String(s.value):"",[l,i]=x.useState(r);x.useEffect(()=>{i(r)},[r]);const m=l.trim(),p=Number(m),o=(m===""||Number.isInteger(p)&&p>=1)&&m!==r;return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s,help:"Leave blank to use the backend default."}),e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":s.key,value:l,disabled:n,placeholder:"default",onChange:d=>i(d.target.value),className:`w-full text-right tabular-nums sm:col-start-2 sm:w-28 sm:justify-self-end ${S}`}),e.jsx("div",{className:k,children:e.jsx(b,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:n||!o,onPress:()=>t(m===""?null:p),children:"Save"})}),a?e.jsx("div",{className:f,children:e.jsx(j,{message:a})}):null]})}function A({field:s,onSave:t,saveError:a,disabled:n}){const r=s.value===!0?"on":s.value===!1?"off":"default";return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s}),e.jsx("div",{className:"sm:col-start-2 sm:justify-self-start",children:e.jsx(D,{ariaLabel:s.key,value:r,onChange:l=>t(l==="default"?null:l==="on"),options:[{value:"default",label:"Default"},{value:"on",label:"On"},{value:"off",label:"Off"}],disabled:n})}),a?e.jsx("div",{className:f,children:e.jsx(j,{message:a})}):null]})}function F({field:s,onSave:t,saveError:a,disabled:n}){return s.type==="url"?e.jsx(z,{field:s,onSave:t,saveError:a,disabled:n}):s.type==="int"?e.jsx(I,{field:s,onSave:t,saveError:a,disabled:n}):s.type==="bool"?e.jsx(A,{field:s,onSave:t,saveError:a,disabled:n}):e.jsx(G,{field:s,onSave:t,saveError:a,disabled:n})}function K(){const s=C(),t=L(),[a,n]=B(),[r,l]=x.useState({}),i=s.data,m=!i||t.isPending,p=new Map(((i==null?void 0:i.fields)??[]).map(o=>[o.key,o])),c=(o,d)=>{l(h=>{const{[o.key]:v,...u}=h;return u}),t.mutate(M(o.key,d),{onSuccess:()=>n(`${o.key} saved`),onError:h=>l(v=>({...v,[o.key]:w(h)}))})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(E,{title:"Tools & Guardrails",description:"Configure the built-in tool and guardrail service endpoints without a restart. Changes apply immediately and persist. URLs are validated for shape (http/https) and can be tested for reachability before saving; the network-safety gates for these services live on the Settings page."}),e.jsx(P,{error:s.error}),U.map(o=>{const d=o.order.map(u=>p.get(u)).filter(u=>u!==void 0),h=((i==null?void 0:i.fields)??[]).filter(u=>u.service===o.key&&!o.order.includes(u.key)),v=[...d,...h];return v.length===0?null:e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:o.label}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:o.blurb}),e.jsx(N,{children:e.jsx(N.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:v.map(u=>e.jsx(F,{field:u,onSave:T=>c(u,T),saveError:r[u.key],disabled:m},u.key))})})]},o.key)}),e.jsx($,{message:a})]})}export{K as ToolsGuardrailsPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as x}from"./react-dgEcD0HR.js";import{a7 as C,a8 as L,P as E,E as P,D as w,a9 as R,F as D}from"./index-BzgMrLHf.js";import{d as N,B as b}from"./heroui-DZj66Arc.js";function M(s,t){return{[s]:t}}const U=[{key:"web_search",label:"Web search",blurb:"Backend for otari_web_search tools (a SearXNG instance or a search adapter).",order:["web_search_url","web_search_engines","web_search_max_results","web_search_extract","web_search_purpose_hint"]},{key:"sandbox",label:"Code execution",blurb:"Backend for otari_code_execution tools (the sandbox that runs generated code).",order:["sandbox_url","sandbox_purpose_hint"]},{key:"guardrails",label:"Guardrails",blurb:"Default input-guardrails service used when a request does not pass its own guardrail URL.",order:["guardrails_url"]}];function B(){const[s,t]=x.useState(null),a=x.useRef(void 0),n=r=>{t(r),window.clearTimeout(a.current),a.current=window.setTimeout(()=>t(null),2500)};return x.useEffect(()=>()=>window.clearTimeout(a.current),[]),[s,n]}function $({message:s}){return s?e.jsxs("div",{role:"status","aria-live":"polite",className:"fixed right-4 bottom-4 z-50 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700 shadow-lg",children:[e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"h-5 w-5",children:e.jsx("path",{d:"M20 6 9 17l-5-5",strokeLinecap:"round",strokeLinejoin:"round"})}),s]}):null}const S="rounded-md border border-[var(--otari-line)] bg-[var(--otari-surface)] px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50",g="grid gap-x-4 gap-y-1.5 py-4 sm:grid-cols-[minmax(0,1fr)_16rem_10rem] sm:items-start",_=`w-full sm:col-start-2 ${S}`,k="flex items-center gap-2 sm:col-start-3",f="flex flex-col gap-1 sm:col-span-2 sm:col-start-2";function j({message:s}){return s?e.jsx("span",{className:"break-words text-xs text-red-700",children:s}):null}function y({field:s,help:t}){return e.jsxs("div",{className:"min-w-0 sm:col-start-1",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:s.key}),s.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:s.description}):null,t?e.jsx("p",{className:"mt-1 text-xs text-[var(--otari-muted)]",children:t}):null]})}function z({field:s,onSave:t,saveError:a,disabled:n}){const r=typeof s.value=="string"?s.value:"",[l,i]=x.useState(r),[m,p]=x.useState(null),c=R();x.useEffect(()=>{i(r)},[r]);const o=l.trim()!==r,d=l.trim(),h=m!==null&&m===d;return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s,help:"Leave blank and Save to fall back to the configured default."}),e.jsx("input",{type:"text",inputMode:"url","aria-label":s.key,value:l,disabled:n,placeholder:"unset",onChange:v=>{i(v.target.value),c.reset()},className:_}),e.jsxs("div",{className:k,children:[e.jsx(b,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:n||!o,onPress:()=>t(d===""?null:d),children:"Save"}),e.jsx(b,{size:"sm",variant:"outline","aria-label":`Test ${s.service}`,isDisabled:d===""||c.isPending,onPress:()=>{p(d),c.mutate({service:s.service,url:d})},children:c.isPending?"Testing…":"Test"})]}),e.jsxs("div",{className:f,children:[e.jsx("span",{role:"status","aria-live":"polite",className:"block break-words text-xs",children:c.isPending||!h?null:c.error?e.jsx("span",{className:"text-red-700",children:w(c.error)}):c.data?e.jsx("span",{className:c.data.ok?"font-medium text-green-700":"text-red-700",children:c.data.reason}):null}),e.jsx(j,{message:a})]})]})}function G({field:s,onSave:t,saveError:a,disabled:n}){const r=typeof s.value=="string"?s.value:"",[l,i]=x.useState(r);x.useEffect(()=>{i(r)},[r]);const m=l!==r;return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s}),e.jsx("input",{type:"text","aria-label":s.key,value:l,disabled:n,placeholder:"default",onChange:p=>i(p.target.value),className:_}),e.jsx("div",{className:k,children:e.jsx(b,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:n||!m,onPress:()=>t(l.trim()===""?null:l.trim()),children:"Save"})}),a?e.jsx("div",{className:f,children:e.jsx(j,{message:a})}):null]})}function I({field:s,onSave:t,saveError:a,disabled:n}){const r=typeof s.value=="number"?String(s.value):"",[l,i]=x.useState(r);x.useEffect(()=>{i(r)},[r]);const m=l.trim(),p=Number(m),o=(m===""||Number.isInteger(p)&&p>=1)&&m!==r;return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s,help:"Leave blank to use the backend default."}),e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":s.key,value:l,disabled:n,placeholder:"default",onChange:d=>i(d.target.value),className:`w-full text-right tabular-nums sm:col-start-2 sm:w-28 sm:justify-self-end ${S}`}),e.jsx("div",{className:k,children:e.jsx(b,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:n||!o,onPress:()=>t(m===""?null:p),children:"Save"})}),a?e.jsx("div",{className:f,children:e.jsx(j,{message:a})}):null]})}function A({field:s,onSave:t,saveError:a,disabled:n}){const r=s.value===!0?"on":s.value===!1?"off":"default";return e.jsxs("div",{className:g,children:[e.jsx(y,{field:s}),e.jsx("div",{className:"sm:col-start-2 sm:justify-self-start",children:e.jsx(D,{ariaLabel:s.key,value:r,onChange:l=>t(l==="default"?null:l==="on"),options:[{value:"default",label:"Default"},{value:"on",label:"On"},{value:"off",label:"Off"}],disabled:n})}),a?e.jsx("div",{className:f,children:e.jsx(j,{message:a})}):null]})}function F({field:s,onSave:t,saveError:a,disabled:n}){return s.type==="url"?e.jsx(z,{field:s,onSave:t,saveError:a,disabled:n}):s.type==="int"?e.jsx(I,{field:s,onSave:t,saveError:a,disabled:n}):s.type==="bool"?e.jsx(A,{field:s,onSave:t,saveError:a,disabled:n}):e.jsx(G,{field:s,onSave:t,saveError:a,disabled:n})}function K(){const s=C(),t=L(),[a,n]=B(),[r,l]=x.useState({}),i=s.data,m=!i||t.isPending,p=new Map(((i==null?void 0:i.fields)??[]).map(o=>[o.key,o])),c=(o,d)=>{l(h=>{const{[o.key]:v,...u}=h;return u}),t.mutate(M(o.key,d),{onSuccess:()=>n(`${o.key} saved`),onError:h=>l(v=>({...v,[o.key]:w(h)}))})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(E,{title:"Tools & Guardrails",description:"Configure the built-in tool and guardrail service endpoints without a restart. Changes apply immediately and persist. URLs are validated for shape (http/https) and can be tested for reachability before saving; the network-safety gates for these services live on the Settings page."}),e.jsx(P,{error:s.error}),U.map(o=>{const d=o.order.map(u=>p.get(u)).filter(u=>u!==void 0),h=((i==null?void 0:i.fields)??[]).filter(u=>u.service===o.key&&!o.order.includes(u.key)),v=[...d,...h];return v.length===0?null:e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:o.label}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:o.blurb}),e.jsx(N,{children:e.jsx(N.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:v.map(u=>e.jsx(F,{field:u,onSave:T=>c(u,T),saveError:r[u.key],disabled:m},u.key))})})]},o.key)}),e.jsx($,{message:a})]})}export{K as ToolsGuardrailsPage}; diff --git a/src/gateway/static/dashboard/assets/UsagePage-BNkia9eC.js b/src/gateway/static/dashboard/assets/UsagePage-DOWOrs0n.js similarity index 67% rename from src/gateway/static/dashboard/assets/UsagePage-BNkia9eC.js rename to src/gateway/static/dashboard/assets/UsagePage-DOWOrs0n.js index 00f60222..74552542 100644 --- a/src/gateway/static/dashboard/assets/UsagePage-BNkia9eC.js +++ b/src/gateway/static/dashboard/assets/UsagePage-DOWOrs0n.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{B as y,c as Me,S as ge}from"./heroui-DZj66Arc.js";import{i as Te,r as i}from"./react-dgEcD0HR.js";import{u as Le,a as qe,d as W,M as O,P as Fe,E as _e,g as X,S as D,O as E,N as Y,R as Oe,aa as K,e as Ee,f as Re,b as Ue,c as xe}from"./index-DHOrDaWF.js";import{u as Ae,r as $e,B as Be}from"./tableSelection-CojkFPwd.js";import{S as ee,B as Ke}from"./charts-CuOShutR.js";import{C as ze}from"./ConfirmDialog-CvV5gDQA.js";import{D as je}from"./DataTable-CrQq0sYf.js";import{T as Ie,S as He}from"./TablePagination-Dob9a4wG.js";import"./recharts-D7kuKp97.js";function H(t){return t.toLocaleString()}function Ye(t){return t===null?"—":t<1e3?`${Math.round(t)} ms`:`${(t/1e3).toFixed(2)} s`}const Ze=3600,te=86400,I=[{label:"Last hour",seconds:Ze,bucket:"hour"},{label:"24h",seconds:te,bucket:"hour"},{label:"7d",seconds:7*te,bucket:"day"},{label:"30d",seconds:30*te,bucket:"day"},{label:"All",seconds:null,bucket:"day"}],se=I[3],ae=15;function ne(t){return new Date(Date.now()-t*1e3).toISOString()}const fe="__other__",pe="__unknown__";function ve({title:t,rows:r,totalCost:u,emptyLabel:o,onDrill:x,loading:d}){const[c,m]=i.useState(!1),k=c?r:r.slice(0,ae),M=r.length-k.length,w=n=>n.is_other?fe:n.key??pe,T=[{id:"name",header:t.replace("Spend by ",""),isRowHeader:!0,cell:n=>{const h=u>0?n.cost/u:0;return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"truncate text-[var(--otari-ink)]",children:n.is_other?`Other (${n.requests.toLocaleString()} req)`:n.key===null?"(unknown)":n.key}),e.jsx("span",{className:"h-1 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",children:e.jsx("span",{className:"block h-full rounded-full bg-[var(--otari-brand)]",style:{width:`${Math.min(100,h*100)}%`}})})]})}},{id:"requests",header:"Requests",align:"end",cell:n=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:H(n.requests)})},{id:"spend",header:"Spend",align:"end",cell:n=>e.jsx("span",{className:"text-[var(--otari-ink)]",children:Y(n.cost)})}];return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(je,{ariaLabel:t,columns:T,rows:k,getRowKey:w,isLoading:d,emptyContent:o,onRowAction:n=>{n!==fe&&n!==pe&&x(n)}}),!d&&M>0?e.jsxs(y,{size:"sm",variant:"ghost",onPress:()=>m(!0),children:["Show all ",r.length]}):null,!d&&c&&r.length>ae?e.jsxs(y,{size:"sm",variant:"ghost",onPress:()=>m(!1),children:["Show top ",ae]}):null]})}const Qe=[{key:"cost",label:"Cost"},{key:"tokens",label:"Tokens"},{key:"requests",label:"Requests"}];function Ve(t,r){return r==="cost"?t.cost:r==="tokens"?t.tokens:t.requests}function be(t,r){return r==="cost"?Y(t):r==="tokens"?K(t):H(t)}function Ge(t,r){const u=new Date(t);return Number.isNaN(u.getTime())?t:r==="hour"?u.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",timeZone:"UTC"}):u.toLocaleDateString(void 0,{month:"short",day:"numeric",timeZone:"UTC"})}function Je(t,r,u){const o=t.map(d=>({label:Ge(d.bucket_start,u),value:Ve(d,r)})),x=o.length?Math.max(...o.map(d=>d.value)):0;return{points:o,peak:x,count:t.length}}const We=50;function Xe(t){const r=new Date(t).getTime();if(Number.isNaN(r))return t;const u=Math.max(0,Math.round((Date.now()-r)/1e3));if(u<60)return`${u}s ago`;const o=Math.round(u/60);if(o<60)return`${o}m ago`;const x=Math.round(o/60);return x<24?`${x}h ago`:`${Math.round(x/24)}d ago`}function et({status:t}){return e.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${t==="error"?"border-red-200 bg-red-50 text-red-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]"}`,children:t})}const tt=[{id:"time",header:"Time",isRowHeader:!0,cell:t=>e.jsx("span",{className:"text-[var(--otari-muted)]",title:new Date(t.timestamp).toLocaleString(),children:Xe(t.timestamp)})},{id:"model",header:"Model",cell:t=>e.jsx("span",{className:"text-[var(--otari-ink)]",children:t.model})},{id:"user",header:"User",cell:t=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:t.user_id??"—"})},{id:"tokens",header:"Tokens",align:"end",cell:t=>t.total_tokens===null?"—":t.total_tokens.toLocaleString()},{id:"cost",header:"Cost",align:"end",cell:t=>t.cost===null?"—":Y(t.cost)},{id:"status",header:"Status",cell:t=>e.jsx(et,{status:t.status})}],st=t=>t.id;function at({filters:t,anyFilter:r}){var q,z;const[u,o]=i.useState(0),[x,d]=i.useState(We),c=Ae(),m=Ee(),k=Re(),[M,w]=i.useState(!1),[T,n]=i.useState(!1),h=JSON.stringify(t),R=i.useRef(h);i.useEffect(()=>{R.current!==h&&(R.current=h,o(0),c.clear())},[h,c]);const b=Ue(t,u,x),C=xe(t),f=b.data??[],P=C.isSuccess&&!C.isPlaceholderData?((q=C.data)==null?void 0:q.total)??0:null,U=f.filter(p=>!p.counts_toward_budget).map(p=>p.id),S=f.filter(p=>p.counts_toward_budget).map(p=>p.id),N=$e(c.selectedKeys,U),v=N.length,_=c.allMatching||v>0,A=i.useMemo(()=>({...t,counts_toward_budget:!1}),[t]),j=xe(A,_),L=j.isSuccess?((z=j.data)==null?void 0:z.total)??null:null,a=U.length>0&&v===U.length&&L!=null&&L>v,l=c.allMatching?L??v:v,$=()=>c.allMatching?{by_filter:!0,model:t.model,user_id:t.user_id,api_key_id:t.api_key_id,start_date:t.start_date,end_date:t.end_date,priced:t.priced}:{ids:N},Q=()=>m.mutate($(),{onSuccess:()=>{w(!1),c.clear()}}),re=p=>k.mutate({...$(),...p},{onSuccess:()=>{n(!1),c.clear()}});return e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Individual requests"}),e.jsx(_e,{error:b.error??C.error}),_?e.jsxs(Be,{selectedCount:l,allMatching:c.allMatching,matchingTotal:L,canSelectAllMatching:a,onSelectAllMatching:c.enableAllMatching,onClear:c.clear,children:[e.jsx(y,{size:"sm",variant:"primary",onPress:()=>n(!0),children:"Set price"}),e.jsx(y,{size:"sm",variant:"danger",onPress:()=>w(!0),children:"Delete"})]}):null,e.jsx(je,{ariaLabel:"Individual requests",columns:tt,rows:f,getRowKey:st,isLoading:b.isLoading,emptyContent:r?"No requests match these filters.":"No requests recorded yet.",selectionMode:"multiple",selectedKeys:c.selectedKeys,onSelectionChange:c.onSelectionChange,disabledKeys:S}),e.jsx(Ie,{page:u,pageSize:x,total:P,rowsOnPage:f.length,onPageChange:o,onPageSizeChange:p=>{d(p),o(0)},isFetching:b.isFetching,hasNextFallback:f.length===x}),e.jsx(ze,{isOpen:M,onOpenChange:w,heading:"Delete usage rows",body:`Delete ${l.toLocaleString()} imported ${l===1?"row":"rows"}? Only imported rows are removed, and this cannot be undone.`,confirmLabel:"Delete",isPending:m.isPending,error:m.error,onConfirm:Q}),e.jsx(He,{isOpen:T,onOpenChange:n,targetCount:l,isPending:k.isPending,error:k.error,onSubmit:re})]})}function gt(){var de,ue,me;const t=Te(),r=Le(),u=qe(),[o,x]=i.useState(se),[d,c]=i.useState(()=>se.seconds===null?void 0:ne(se.seconds)),[m,k]=i.useState(!1),[M,w]=i.useState(""),[T,n]=i.useState(""),[h,R]=i.useState(""),[b,C]=i.useState(""),[f,Z]=i.useState(""),[P,U]=i.useState("cost"),S=m?M||void 0:d,N=m&&T||void 0,v=m?"day":o.bucket,_=i.useMemo(()=>({start_date:S,end_date:N,model:h.trim()||void 0,user_id:b||void 0,api_key_id:f||void 0}),[S,N,h,b,f]),A=i.useMemo(()=>{if(m){if(!S||!N)return null;const s=new Date(N).getTime()-new Date(S).getTime();return s>0?{..._,start_date:new Date(new Date(S).getTime()-s).toISOString(),end_date:S}:null}return o.seconds===null||!d?null:{..._,start_date:new Date(new Date(d).getTime()-o.seconds*1e3).toISOString(),end_date:d}},[m,S,N,_,o.seconds,d]),j=W(_,v),L=W(A??_,v,A!==null),g=j.data,a=g==null?void 0:g.totals,l=A!==null?(de=L.data)==null?void 0:de.totals:void 0,$=a?O(a.cost,l==null?void 0:l.cost):null,Q=i.useMemo(()=>({..._,model:void 0}),[_]),q=((me=(ue=W(Q,v).data)==null?void 0:ue.by_model)==null?void 0:me.filter(s=>!s.is_other&&s.key!==null).map(s=>s.key))??[],z=(r.data??[]).map(s=>({value:s.user_id,label:s.alias?`${s.alias} (${s.user_id})`:s.user_id})),p=(u.data??[]).map(s=>({value:s.id,label:s.key_name??`${s.id.slice(0,8)}…`})),Se=(h&&!q.includes(h)?[h,...q]:q).map(s=>({value:s,label:s})),ye=m?!!(S||N):o.seconds!==null,B=!!(h.trim()||b||f||ye),[oe,ke]=i.useState(!1),le=[h.trim(),b,f].filter(Boolean).length,Ne=!!(g&&a&&a.request_count===0&&!B),ie=s=>{k(!1),x(s),c(s.seconds===null?void 0:ne(s.seconds))},we=()=>{ie(I[I.length-1]),w(""),n(""),R(""),C(""),Z("")},Ce=()=>{o.seconds!==null&&c(ne(o.seconds)),j.refetch()},ce=s=>{const J=new URLSearchParams;d&&J.set("start_date",d);for(const[De,he]of Object.entries(s))he&&J.set(De,he);t(`/activity?${J.toString()}`)},Pe=a&&a.request_count>0?a.error_count/a.request_count:0,F=(g==null?void 0:g.series)??[],V=F.length>1,G=Je(F,P,v);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Fe,{title:"Usage & analytics",description:"Aggregate spend, tokens, and request volume over time. Click a model or user to drill into the request log.",action:e.jsx(y,{variant:"outline",onPress:Ce,isDisabled:j.isFetching,children:"Refresh"})}),e.jsx(_e,{error:j.error}),e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsxs("div",{className:"flex flex-wrap gap-2",children:[I.map(s=>e.jsx(y,{size:"sm",variant:!m&&o.label===s.label?"primary":"outline",onPress:()=>ie(s),children:s.label},s.label)),e.jsx(y,{size:"sm",variant:m?"primary":"outline",onPress:()=>k(!0),children:"Custom…"})]}),m?e.jsxs("div",{className:"flex flex-wrap items-end gap-2",children:[e.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["From",e.jsx("input",{type:"datetime-local",value:M,onChange:s=>w(s.target.value),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["To",e.jsx("input",{type:"datetime-local",value:T,onChange:s=>n(s.target.value),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]})]}):null,e.jsxs(y,{size:"sm",variant:"outline",className:"md:hidden",onPress:()=>ke(s=>!s),"aria-expanded":oe,"aria-controls":"usage-filters",children:["Filters",le?` (${le})`:""]}),e.jsxs("div",{id:"usage-filters",className:Me("flex-wrap items-end gap-3 md:flex",oe?"flex w-full md:w-auto":"hidden"),children:[e.jsx(X,{label:"User",value:b,onChange:C,options:z,placeholder:"All users"}),e.jsx(X,{label:"Model",value:h,onChange:R,options:Se,placeholder:"All models"}),e.jsx(X,{label:"API key",value:f,onChange:Z,options:p,placeholder:"All keys"}),B?e.jsx(y,{size:"sm",variant:"ghost",onPress:we,children:"Clear filters"}):null]})]}),Ne?e.jsx("div",{className:"rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] px-4 py-10 text-center text-sm text-[var(--otari-muted)]",children:"No usage yet. Once the gateway serves requests, spend and volume appear here."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex flex-wrap gap-4",children:[e.jsx(D,{label:"Tracked cost",value:a?Y(a.cost):"—",hint:a?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[e.jsx(E,{fraction:$}),a.unpriced_requests?`${$!==null?" · ":""}${H(a.unpriced_requests)} unpriced`:null]}):null,chart:V?e.jsx(ee,{values:F.map(s=>s.cost),ariaLabel:"Spend trend over the selected window"}):void 0}),e.jsx(D,{label:"Requests",value:a?H(a.request_count):"—",hint:a?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[Oe(Pe)," errors",l?e.jsxs(e.Fragment,{children:[" · ",e.jsx(E,{fraction:O(a.request_count,l.request_count)})]}):null]}):null,chart:V?e.jsx(ee,{values:F.map(s=>s.requests),ariaLabel:"Request volume trend over the selected window"}):void 0}),e.jsx(D,{label:"Tokens",value:a?K(a.total_tokens):"—",hint:a?e.jsx(E,{fraction:O(a.total_tokens,l==null?void 0:l.total_tokens)}):null,chart:V?e.jsx(ee,{values:F.map(s=>s.tokens),ariaLabel:"Token usage trend over the selected window"}):void 0}),e.jsx(D,{label:"Cache read",value:a?K(a.cache_read_tokens):"—",hint:a?e.jsx(E,{fraction:O(a.cache_read_tokens,l==null?void 0:l.cache_read_tokens)}):null}),e.jsx(D,{label:"Cache write",value:a?K(a.cache_write_tokens):"—",hint:a?e.jsx(E,{fraction:O(a.cache_write_tokens,l==null?void 0:l.cache_write_tokens)}):null}),e.jsx(D,{label:"1h cache write",value:a?K(a.cache_write_1h_tokens??0):"—",hint:a?e.jsx(E,{fraction:O(a.cache_write_1h_tokens??0,(l==null?void 0:l.cache_write_1h_tokens)??0)}):null}),e.jsx(D,{label:"Avg latency",value:a?Ye(a.avg_latency_ms):"—"})]}),e.jsxs("div",{className:"grid gap-6 lg:grid-cols-2",children:[e.jsx(ve,{title:"Spend by model",rows:(g==null?void 0:g.by_model)??[],totalCost:(a==null?void 0:a.cost)??0,emptyLabel:B?"No usage matches these filters.":"No usage recorded yet.",onDrill:s=>ce({model:s,user_id:b||void 0,api_key_id:f||void 0}),loading:j.isLoading}),e.jsx(ve,{title:"Spend by user",rows:(g==null?void 0:g.by_user)??[],totalCost:(a==null?void 0:a.cost)??0,emptyLabel:B?"No usage matches these filters.":"No usage recorded yet.",onDrill:s=>ce({user_id:s,model:h.trim()||void 0,api_key_id:f||void 0}),loading:j.isLoading})]}),e.jsx(at,{filters:_,anyFilter:B}),e.jsxs("div",{className:"flex flex-col gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-4",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Over time"}),e.jsxs("div",{className:"inline-flex gap-1.5",children:[Qe.map(s=>e.jsx(y,{size:"sm",variant:P===s.key?"primary":"outline",onPress:()=>U(s.key),children:s.label},s.key)),j.isFetching?e.jsx(ge,{size:"sm"}):null]})]}),j.isLoading?e.jsx("div",{className:"flex h-48 items-center justify-center",children:e.jsx(ge,{size:"sm"})}):F.length===0?e.jsx("div",{className:"flex h-48 items-center justify-center text-sm text-[var(--otari-muted)]",children:"No data in this range."}):e.jsxs("figure",{className:"flex flex-col gap-2",children:[e.jsx(Ke,{data:G.points,formatValue:s=>be(s,P),ariaLabel:`${P} per ${v}`}),e.jsxs("figcaption",{className:"text-xs text-[var(--otari-muted)]",children:[be(G.peak,P)," peak · ",G.count," ",v==="hour"?"hours":"days"," (times in UTC)"]})]})]})]})]})}export{gt as UsagePage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{B as y,c as Me,S as ge}from"./heroui-DZj66Arc.js";import{i as Te,r as i}from"./react-dgEcD0HR.js";import{u as Le,a as qe,d as W,M as O,P as Fe,E as _e,g as X,S as D,O as E,N as Y,R as Oe,aa as K,e as Ee,f as Re,b as Ue,c as xe}from"./index-BzgMrLHf.js";import{u as Ae,r as $e,B as Be}from"./tableSelection-CojkFPwd.js";import{S as ee,B as Ke}from"./charts-CuOShutR.js";import{C as ze}from"./ConfirmDialog-CCJnWoSc.js";import{D as je}from"./DataTable-CrQq0sYf.js";import{T as Ie,S as He}from"./TablePagination-DlTV4kUZ.js";import"./recharts-D7kuKp97.js";function H(t){return t.toLocaleString()}function Ye(t){return t===null?"—":t<1e3?`${Math.round(t)} ms`:`${(t/1e3).toFixed(2)} s`}const Ze=3600,te=86400,I=[{label:"Last hour",seconds:Ze,bucket:"hour"},{label:"24h",seconds:te,bucket:"hour"},{label:"7d",seconds:7*te,bucket:"day"},{label:"30d",seconds:30*te,bucket:"day"},{label:"All",seconds:null,bucket:"day"}],se=I[3],ae=15;function ne(t){return new Date(Date.now()-t*1e3).toISOString()}const pe="__other__",fe="__unknown__";function ve({title:t,rows:r,totalCost:u,emptyLabel:o,onDrill:x,loading:d}){const[c,m]=i.useState(!1),k=c?r:r.slice(0,ae),M=r.length-k.length,w=n=>n.is_other?pe:n.key??fe,T=[{id:"name",header:t.replace("Spend by ",""),isRowHeader:!0,cell:n=>{const h=u>0?n.cost/u:0;return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"truncate text-[var(--otari-ink)]",children:n.is_other?`Other (${n.requests.toLocaleString()} req)`:n.key===null?"(unknown)":n.key}),e.jsx("span",{className:"h-1 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",children:e.jsx("span",{className:"block h-full rounded-full bg-[var(--otari-brand)]",style:{width:`${Math.min(100,h*100)}%`}})})]})}},{id:"requests",header:"Requests",align:"end",cell:n=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:H(n.requests)})},{id:"spend",header:"Spend",align:"end",cell:n=>e.jsx("span",{className:"text-[var(--otari-ink)]",children:Y(n.cost)})}];return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(je,{ariaLabel:t,columns:T,rows:k,getRowKey:w,isLoading:d,emptyContent:o,onRowAction:n=>{n!==pe&&n!==fe&&x(n)}}),!d&&M>0?e.jsxs(y,{size:"sm",variant:"ghost",onPress:()=>m(!0),children:["Show all ",r.length]}):null,!d&&c&&r.length>ae?e.jsxs(y,{size:"sm",variant:"ghost",onPress:()=>m(!1),children:["Show top ",ae]}):null]})}const Qe=[{key:"cost",label:"Cost"},{key:"tokens",label:"Tokens"},{key:"requests",label:"Requests"}];function Ve(t,r){return r==="cost"?t.cost:r==="tokens"?t.tokens:t.requests}function be(t,r){return r==="cost"?Y(t):r==="tokens"?K(t):H(t)}function Ge(t,r){const u=new Date(t);return Number.isNaN(u.getTime())?t:r==="hour"?u.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",timeZone:"UTC"}):u.toLocaleDateString(void 0,{month:"short",day:"numeric",timeZone:"UTC"})}function Je(t,r,u){const o=t.map(d=>({label:Ge(d.bucket_start,u),value:Ve(d,r)})),x=o.length?Math.max(...o.map(d=>d.value)):0;return{points:o,peak:x,count:t.length}}const We=50;function Xe(t){const r=new Date(t).getTime();if(Number.isNaN(r))return t;const u=Math.max(0,Math.round((Date.now()-r)/1e3));if(u<60)return`${u}s ago`;const o=Math.round(u/60);if(o<60)return`${o}m ago`;const x=Math.round(o/60);return x<24?`${x}h ago`:`${Math.round(x/24)}d ago`}function et({status:t}){return e.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${t==="error"?"border-red-200 bg-red-50 text-red-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]"}`,children:t})}const tt=[{id:"time",header:"Time",isRowHeader:!0,cell:t=>e.jsx("span",{className:"text-[var(--otari-muted)]",title:new Date(t.timestamp).toLocaleString(),children:Xe(t.timestamp)})},{id:"model",header:"Model",cell:t=>e.jsx("span",{className:"text-[var(--otari-ink)]",children:t.model})},{id:"user",header:"User",cell:t=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:t.user_id??"—"})},{id:"tokens",header:"Tokens",align:"end",cell:t=>t.total_tokens===null?"—":t.total_tokens.toLocaleString()},{id:"cost",header:"Cost",align:"end",cell:t=>t.cost===null?"—":Y(t.cost)},{id:"status",header:"Status",cell:t=>e.jsx(et,{status:t.status})}],st=t=>t.id;function at({filters:t,anyFilter:r}){var q,z;const[u,o]=i.useState(0),[x,d]=i.useState(We),c=Ae(),m=Ee(),k=Re(),[M,w]=i.useState(!1),[T,n]=i.useState(!1),h=JSON.stringify(t),R=i.useRef(h);i.useEffect(()=>{R.current!==h&&(R.current=h,o(0),c.clear())},[h,c]);const b=Ue(t,u,x),C=xe(t),p=b.data??[],P=C.isSuccess&&!C.isPlaceholderData?((q=C.data)==null?void 0:q.total)??0:null,U=p.filter(f=>!f.counts_toward_budget).map(f=>f.id),S=p.filter(f=>f.counts_toward_budget).map(f=>f.id),N=$e(c.selectedKeys,U),v=N.length,_=c.allMatching||v>0,A=i.useMemo(()=>({...t,counts_toward_budget:!1}),[t]),j=xe(A,_),L=j.isSuccess?((z=j.data)==null?void 0:z.total)??null:null,a=U.length>0&&v===U.length&&L!=null&&L>v,l=c.allMatching?L??v:v,$=()=>c.allMatching?{by_filter:!0,model:t.model,user_id:t.user_id,api_key_id:t.api_key_id,start_date:t.start_date,end_date:t.end_date,priced:t.priced}:{ids:N},Q=()=>m.mutate($(),{onSuccess:()=>{w(!1),c.clear()}}),re=f=>k.mutate({...$(),...f},{onSuccess:()=>{n(!1),c.clear()}});return e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Individual requests"}),e.jsx(_e,{error:b.error??C.error}),_?e.jsxs(Be,{selectedCount:l,allMatching:c.allMatching,matchingTotal:L,canSelectAllMatching:a,onSelectAllMatching:c.enableAllMatching,onClear:c.clear,children:[e.jsx(y,{size:"sm",variant:"primary",onPress:()=>n(!0),children:"Set price"}),e.jsx(y,{size:"sm",variant:"danger",onPress:()=>w(!0),children:"Delete"})]}):null,e.jsx(je,{ariaLabel:"Individual requests",columns:tt,rows:p,getRowKey:st,isLoading:b.isLoading,emptyContent:r?"No requests match these filters.":"No requests recorded yet.",selectionMode:"multiple",selectedKeys:c.selectedKeys,onSelectionChange:c.onSelectionChange,disabledKeys:S}),e.jsx(Ie,{page:u,pageSize:x,total:P,rowsOnPage:p.length,onPageChange:o,onPageSizeChange:f=>{d(f),o(0)},isFetching:b.isFetching,hasNextFallback:p.length===x}),e.jsx(ze,{isOpen:M,onOpenChange:w,heading:"Delete usage rows",body:`Delete ${l.toLocaleString()} imported ${l===1?"row":"rows"}? Only imported rows are removed, and this cannot be undone.`,confirmLabel:"Delete",isPending:m.isPending,error:m.error,onConfirm:Q}),e.jsx(He,{isOpen:T,onOpenChange:n,targetCount:l,isPending:k.isPending,error:k.error,onSubmit:re})]})}function gt(){var de,ue,me;const t=Te(),r=Le(),u=qe(),[o,x]=i.useState(se),[d,c]=i.useState(()=>se.seconds===null?void 0:ne(se.seconds)),[m,k]=i.useState(!1),[M,w]=i.useState(""),[T,n]=i.useState(""),[h,R]=i.useState(""),[b,C]=i.useState(""),[p,Z]=i.useState(""),[P,U]=i.useState("cost"),S=m?M||void 0:d,N=m&&T||void 0,v=m?"day":o.bucket,_=i.useMemo(()=>({start_date:S,end_date:N,model:h.trim()||void 0,user_id:b||void 0,api_key_id:p||void 0}),[S,N,h,b,p]),A=i.useMemo(()=>{if(m){if(!S||!N)return null;const s=new Date(N).getTime()-new Date(S).getTime();return s>0?{..._,start_date:new Date(new Date(S).getTime()-s).toISOString(),end_date:S}:null}return o.seconds===null||!d?null:{..._,start_date:new Date(new Date(d).getTime()-o.seconds*1e3).toISOString(),end_date:d}},[m,S,N,_,o.seconds,d]),j=W(_,v),L=W(A??_,v,A!==null),g=j.data,a=g==null?void 0:g.totals,l=A!==null?(de=L.data)==null?void 0:de.totals:void 0,$=a?O(a.cost,l==null?void 0:l.cost):null,Q=i.useMemo(()=>({..._,model:void 0}),[_]),q=((me=(ue=W(Q,v).data)==null?void 0:ue.by_model)==null?void 0:me.filter(s=>!s.is_other&&s.key!==null).map(s=>s.key))??[],z=(r.data??[]).map(s=>({value:s.user_id,label:s.alias?`${s.alias} (${s.user_id})`:s.user_id})),f=(u.data??[]).map(s=>({value:s.id,label:s.key_name??`${s.id.slice(0,8)}…`})),Se=(h&&!q.includes(h)?[h,...q]:q).map(s=>({value:s,label:s})),ye=m?!!(S||N):o.seconds!==null,B=!!(h.trim()||b||p||ye),[oe,ke]=i.useState(!1),le=[h.trim(),b,p].filter(Boolean).length,Ne=!!(g&&a&&a.request_count===0&&!B),ie=s=>{k(!1),x(s),c(s.seconds===null?void 0:ne(s.seconds))},we=()=>{ie(I[I.length-1]),w(""),n(""),R(""),C(""),Z("")},Ce=()=>{o.seconds!==null&&c(ne(o.seconds)),j.refetch()},ce=s=>{const J=new URLSearchParams;d&&J.set("start_date",d);for(const[De,he]of Object.entries(s))he&&J.set(De,he);t(`/activity?${J.toString()}`)},Pe=a&&a.request_count>0?a.error_count/a.request_count:0,F=(g==null?void 0:g.series)??[],V=F.length>1,G=Je(F,P,v);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Fe,{title:"Usage & analytics",description:"Aggregate spend, tokens, and request volume over time. Click a model or user to drill into the request log.",action:e.jsx(y,{variant:"outline",onPress:Ce,isDisabled:j.isFetching,children:"Refresh"})}),e.jsx(_e,{error:j.error}),e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsxs("div",{className:"flex flex-wrap gap-2",children:[I.map(s=>e.jsx(y,{size:"sm",variant:!m&&o.label===s.label?"primary":"outline",onPress:()=>ie(s),children:s.label},s.label)),e.jsx(y,{size:"sm",variant:m?"primary":"outline",onPress:()=>k(!0),children:"Custom…"})]}),m?e.jsxs("div",{className:"flex flex-wrap items-end gap-2",children:[e.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["From",e.jsx("input",{type:"datetime-local",value:M,onChange:s=>w(s.target.value),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs font-medium text-[var(--otari-muted)]",children:["To",e.jsx("input",{type:"datetime-local",value:T,onChange:s=>n(s.target.value),className:"rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"})]})]}):null,e.jsxs(y,{size:"sm",variant:"outline",className:"md:hidden",onPress:()=>ke(s=>!s),"aria-expanded":oe,"aria-controls":"usage-filters",children:["Filters",le?` (${le})`:""]}),e.jsxs("div",{id:"usage-filters",className:Me("flex-wrap items-end gap-3 md:flex",oe?"flex w-full md:w-auto":"hidden"),children:[e.jsx(X,{label:"User",value:b,onChange:C,options:z,placeholder:"All users"}),e.jsx(X,{label:"Model",value:h,onChange:R,options:Se,placeholder:"All models"}),e.jsx(X,{label:"API key",value:p,onChange:Z,options:f,placeholder:"All keys"}),B?e.jsx(y,{size:"sm",variant:"ghost",onPress:we,children:"Clear filters"}):null]})]}),Ne?e.jsx("div",{className:"rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] px-4 py-10 text-center text-sm text-[var(--otari-muted)]",children:"No usage yet. Once the gateway serves requests, spend and volume appear here."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4",children:[e.jsx(D,{label:"Tracked cost",value:a?Y(a.cost):"—",hint:a?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[e.jsx(E,{fraction:$}),a.unpriced_requests?`${$!==null?" · ":""}${H(a.unpriced_requests)} unpriced`:null]}):null,chart:V?e.jsx(ee,{values:F.map(s=>s.cost),ariaLabel:"Spend trend over the selected window"}):void 0}),e.jsx(D,{label:"Requests",value:a?H(a.request_count):"—",hint:a?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[Oe(Pe)," errors",l?e.jsxs(e.Fragment,{children:[" · ",e.jsx(E,{fraction:O(a.request_count,l.request_count)})]}):null]}):null,chart:V?e.jsx(ee,{values:F.map(s=>s.requests),ariaLabel:"Request volume trend over the selected window"}):void 0}),e.jsx(D,{label:"Tokens",value:a?K(a.total_tokens):"—",hint:a?e.jsx(E,{fraction:O(a.total_tokens,l==null?void 0:l.total_tokens)}):null,chart:V?e.jsx(ee,{values:F.map(s=>s.tokens),ariaLabel:"Token usage trend over the selected window"}):void 0}),e.jsx(D,{label:"Cache read",value:a?K(a.cache_read_tokens):"—",hint:a?e.jsx(E,{fraction:O(a.cache_read_tokens,l==null?void 0:l.cache_read_tokens)}):null}),e.jsx(D,{label:"Cache write",value:a?K(a.cache_write_tokens):"—",hint:a?e.jsx(E,{fraction:O(a.cache_write_tokens,l==null?void 0:l.cache_write_tokens)}):null}),e.jsx(D,{label:"1h cache write",value:a?K(a.cache_write_1h_tokens??0):"—",hint:a?e.jsx(E,{fraction:O(a.cache_write_1h_tokens??0,(l==null?void 0:l.cache_write_1h_tokens)??0)}):null}),e.jsx(D,{label:"Avg latency",value:a?Ye(a.avg_latency_ms):"—"})]}),e.jsxs("div",{className:"grid gap-6 lg:grid-cols-2",children:[e.jsx(ve,{title:"Spend by model",rows:(g==null?void 0:g.by_model)??[],totalCost:(a==null?void 0:a.cost)??0,emptyLabel:B?"No usage matches these filters.":"No usage recorded yet.",onDrill:s=>ce({model:s,user_id:b||void 0,api_key_id:p||void 0}),loading:j.isLoading}),e.jsx(ve,{title:"Spend by user",rows:(g==null?void 0:g.by_user)??[],totalCost:(a==null?void 0:a.cost)??0,emptyLabel:B?"No usage matches these filters.":"No usage recorded yet.",onDrill:s=>ce({user_id:s,model:h.trim()||void 0,api_key_id:p||void 0}),loading:j.isLoading})]}),e.jsx(at,{filters:_,anyFilter:B}),e.jsxs("div",{className:"flex flex-col gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-4",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Over time"}),e.jsxs("div",{className:"inline-flex gap-1.5",children:[Qe.map(s=>e.jsx(y,{size:"sm",variant:P===s.key?"primary":"outline",onPress:()=>U(s.key),children:s.label},s.key)),j.isFetching?e.jsx(ge,{size:"sm"}):null]})]}),j.isLoading?e.jsx("div",{className:"flex h-48 items-center justify-center",children:e.jsx(ge,{size:"sm"})}):F.length===0?e.jsx("div",{className:"flex h-48 items-center justify-center text-sm text-[var(--otari-muted)]",children:"No data in this range."}):e.jsxs("figure",{className:"flex flex-col gap-2",children:[e.jsx(Ke,{data:G.points,formatValue:s=>be(s,P),ariaLabel:`${P} per ${v}`}),e.jsxs("figcaption",{className:"text-xs text-[var(--otari-muted)]",children:[be(G.peak,P)," peak · ",G.count," ",v==="hour"?"hours":"days"," (times in UTC)"]})]})]})]})]})}export{gt as UsagePage}; diff --git a/src/gateway/static/dashboard/assets/UsersPage-C8N0rekv.js b/src/gateway/static/dashboard/assets/UsersPage-B7fcuwaB.js similarity index 98% rename from src/gateway/static/dashboard/assets/UsersPage-C8N0rekv.js rename to src/gateway/static/dashboard/assets/UsersPage-B7fcuwaB.js index 71438a27..c2ffdd68 100644 --- a/src/gateway/static/dashboard/assets/UsersPage-C8N0rekv.js +++ b/src/gateway/static/dashboard/assets/UsersPage-B7fcuwaB.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r}from"./react-dgEcD0HR.js";import{u as Q,l as M,p as K,ab as X,P as Y,E as S,ac as Z,F as ee}from"./index-DHOrDaWF.js";import{u as se,r as te,B as ae}from"./tableSelection-CojkFPwd.js";import{C as ne}from"./ConfirmDialog-CvV5gDQA.js";import{D as re}from"./DataTable-CrQq0sYf.js";import{F as B}from"./Field-DEJ6Wjg9.js";import{a as le,M as R}from"./ModelScopeControl-CTGSrkZ9.js";import{f as I,B as d,d as y,A as f}from"./heroui-DZj66Arc.js";const ie=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function U(t){return ie.format(t)}const de=t=>t.user_id,D=t=>t.startsWith("apikey-");function H(t){return t.split("-")[0]}function F(t){return t.name??H(t.budget_id)}function $({value:t,onChange:l,budgets:n}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("label",{htmlFor:"user-budget",className:"text-sm font-medium text-[var(--otari-ink)]",children:"Budget"}),e.jsxs("select",{id:"user-budget",value:t??"",onChange:a=>l(a.target.value||null),className:"w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)]",children:[e.jsx("option",{value:"",children:"No budget (unlimited)"}),n.map(a=>e.jsxs("option",{value:a.budget_id,children:[F(a),a.max_budget===null?" · no limit":` · ${U(a.max_budget)}`]},a.budget_id))]}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The spending limit this user is held to. Manage budgets on the Budgets page."})]})}function oe({onClose:t}){const l=Z(),n=M(),[a,i]=r.useState(""),[o,u]=r.useState(""),[c,m]=r.useState(null),[p,g]=r.useState(null),[j,b]=r.useState(!0),x=()=>{if(l.isPending||!j||a.trim()==="")return;const h={user_id:a.trim(),alias:o.trim()||null,budget_id:c,allowed_models:p};l.mutate(h,{onSuccess:t})};return e.jsx(y,{children:e.jsxs(y.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Create user"}),e.jsx(S,{error:l.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(B,{label:"User ID",value:a,onChange:i,placeholder:"alice@example.com",isRequired:!0,autoFocus:!0,description:"The identifier callers send as the `user` field; spend and budgets track against it."}),e.jsx(B,{label:"Alias (optional)",value:o,onChange:u,placeholder:"Alice"})]}),e.jsx($,{value:c,onChange:m,budgets:n.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:null,onChange:(h,P)=>{g(h),b(P)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:l.isPending||!j||a.trim()==="",onPress:x,children:l.isPending?"Creating…":"Create user"}),e.jsx(d,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function ce({user:t,onClose:l}){const n=K(),a=M(),[i,o]=r.useState(t.alias??""),[u,c]=r.useState(t.budget_id),[m,p]=r.useState(t.allowed_models),[g,j]=r.useState(!0),b=()=>{if(n.isPending||!g)return;const x={alias:i.trim()||null,budget_id:u,allowed_models:m};n.mutate({id:t.user_id,body:x},{onSuccess:l})};return e.jsx(y,{children:e.jsxs(y.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.user_id})]}),e.jsx(S,{error:n.error}),e.jsx(B,{label:"Alias",value:i,onChange:o,placeholder:"Alice"}),e.jsx($,{value:u,onChange:c,budgets:a.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:t.allowed_models,onChange:(x,h)=>{p(x),j(h)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:n.isPending||!g,onPress:b,children:n.isPending?"Saving…":"Save changes"}),e.jsx(d,{variant:"ghost",onPress:l,children:"Cancel"})]})]})})}function ue({trigger:t,message:l,confirmLabel:n,isPending:a,onConfirm:i}){const[o,u]=r.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsx("span",{className:"max-w-xs text-xs text-amber-800",children:l}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(d,{size:"sm",variant:"danger",isDisabled:a,onPress:i,children:n}),e.jsx(d,{size:"sm",variant:"ghost",isDisabled:a,onPress:()=>u(!1),children:"Cancel"})]})]}):e.jsx(d,{size:"sm",variant:"danger-soft",onPress:()=>u(!0),children:t})}function me({user:t}){return t.blocked?e.jsx(I,{size:"sm",color:"warning",children:"Blocked"}):e.jsx(I,{size:"sm",color:"accent",children:"Active"})}function ge({allowed:t}){const{text:l,tone:n}=le(t),a=n==="danger"?"text-red-700 font-medium":n==="muted"?"text-[var(--otari-muted)]":"text-[var(--otari-brand-dark)] font-medium",i=t&&t.length>0?t.join(", "):void 0;return e.jsx("span",{className:`text-xs ${a}`,title:i,children:l})}function xe({onCreate:t}){return e.jsx(y,{children:e.jsxs(y.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"No users yet"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"A user owns API keys and carries the budget and default model access those keys inherit. Create a user here, then issue its keys on the API keys page."})]}),e.jsx("div",{children:e.jsx(d,{variant:"primary",onPress:t,children:"Create your first user"})})]})})}function he({isOpen:t,onOpenChange:l,budgets:n,count:a,isPending:i,error:o,onAssign:u}){const[c,m]=r.useState("");return r.useEffect(()=>{t&&m("")},[t]),e.jsx(f,{isOpen:t,onOpenChange:l,children:t?e.jsx(f.Backdrop,{children:e.jsx(f.Container,{placement:"center",size:"md",children:e.jsxs(f.Dialog,{children:[e.jsx(f.Header,{children:e.jsx(f.Heading,{children:"Assign budget"})}),e.jsxs(f.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Assign a budget to ",a," selected ",a===1?"user":"users","."]}),e.jsx(ee,{label:"Budget",value:c,onChange:m,options:[{value:"",label:"Select a budget…"},...n.map(p=>({value:p.budget_id,label:F(p)}))]}),e.jsx(S,{error:o})]}),e.jsxs(f.Footer,{children:[e.jsx(d,{variant:"ghost",isDisabled:i,onPress:()=>l(!1),children:"Cancel"}),e.jsx(d,{variant:"primary",isDisabled:!c,isPending:i,onPress:()=>u(c),children:"Assign"})]})]})})}):null})}function Pe(){const t=Q(),l=M(),n=K(),a=X(),[i,o]=r.useState(!1),[u,c]=r.useState(null),[m,p]=r.useState(!1),g=t.data??[],j=t.isLoading,b=g.filter(s=>D(s.user_id)).length,x=m?g:g.filter(s=>!D(s.user_id)),h=g.find(s=>s.user_id===u)??null,P=!j&&x.length===0&&!i,A=r.useMemo(()=>new Map((l.data??[]).map(s=>[s.budget_id,s])),[l.data]),C=se(),[q,_]=r.useState(!1),[W,N]=r.useState(!1),[z,E]=r.useState(void 0),[T,O]=r.useState(!1),G=x.map(s=>s.user_id),v=te(C.selectedKeys,G),L=r.useCallback((s,k)=>n.mutate({id:s.user_id,body:{blocked:k}}),[n.mutate]),V=async(s,k)=>{O(!0),E(void 0);try{for(const w of v)await s(w);C.clear(),k()}catch(w){E(w)}finally{O(!1)}},J=r.useMemo(()=>[{id:"user",header:"User",isRowHeader:!0,cell:s=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx("code",{className:"text-xs font-medium text-[var(--otari-ink)]",children:s.user_id}),D(s.user_id)?e.jsx(I,{size:"sm",color:"default",children:"virtual"}):null]}),s.alias?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:s.alias}):null]})},{id:"status",header:"Status",cell:s=>e.jsx(me,{user:s})},{id:"budget",header:"Budget",cell:s=>s.budget_id?e.jsx("span",{className:"text-[var(--otari-muted)]",title:s.budget_id,children:A.get(s.budget_id)?F(A.get(s.budget_id)):H(s.budget_id)}):e.jsx("span",{className:"text-[var(--otari-muted)]",children:"—"})},{id:"spend",header:"Spend",cell:s=>e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[U(s.spend),s.reserved>0?e.jsxs("span",{children:[" (+",U(s.reserved)," held)"]}):null]})},{id:"access",header:"Model access",cell:s=>e.jsx(ge,{allowed:s.allowed_models})},{id:"actions",header:"Actions",align:"end",cell:s=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(d,{size:"sm",variant:"outline",isDisabled:n.isPending,onPress:()=>L(s,!s.blocked),children:s.blocked?"Unblock":"Block"}),e.jsx(d,{size:"sm",variant:"ghost",onPress:()=>{o(!1),c(s.user_id)},children:"Edit"}),e.jsx(ue,{trigger:"Delete",confirmLabel:"Delete user",isPending:a.isPending,message:e.jsxs(e.Fragment,{children:["Delete ",e.jsx("strong",{children:s.user_id}),"? This deactivates its API keys and hides the user; usage history is preserved."]}),onConfirm:()=>a.mutate(s.user_id)})]})}],[A,n.isPending,a.isPending,a.mutate,L]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Y,{title:"Users",description:"People and teams that own API keys. Set each one's budget and default model access here; issue their keys on the API keys page.",action:i?null:e.jsx(d,{variant:"primary",onPress:()=>{c(null),o(!0)},children:"Create user"})}),e.jsx(S,{error:t.error??n.error??a.error}),P?e.jsx(xe,{onCreate:()=>{c(null),o(!0)}}):null,b>0?e.jsxs("label",{className:"flex w-fit items-center gap-2 text-xs text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:m,onChange:s=>p(s.target.checked)}),"Show auto-created (virtual) users (",b,")"]}):null,i?e.jsx(oe,{onClose:()=>o(!1)}):null,h?e.jsx(ce,{user:h,onClose:()=>c(null)},h.user_id):null,v.length>0?e.jsxs(ae,{selectedCount:v.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:C.clear,children:[e.jsx(d,{size:"sm",variant:"primary",onPress:()=>N(!0),children:"Assign budget"}),e.jsx(d,{size:"sm",variant:"danger",onPress:()=>_(!0),children:"Delete"})]}):null,e.jsx(re,{ariaLabel:"Users",columns:J,rows:x,getRowKey:de,isLoading:j,emptyContent:"No users yet. Create one, or create an API key to auto-create one.",selectionMode:"multiple",selectedKeys:C.selectedKeys,onSelectionChange:C.onSelectionChange}),e.jsx(ne,{isOpen:q,onOpenChange:_,heading:"Delete users",body:`Delete ${v.length} ${v.length===1?"user":"users"}? This deactivates their API keys and hides them; usage history is preserved.`,confirmLabel:"Delete",isPending:T,error:z,onConfirm:()=>V(s=>a.mutateAsync(s),()=>_(!1))}),e.jsx(he,{isOpen:W,onOpenChange:N,budgets:l.data??[],count:v.length,isPending:T,error:z,onAssign:s=>V(k=>n.mutateAsync({id:k,body:{budget_id:s}}),()=>N(!1))})]})}export{Pe as UsersPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r}from"./react-dgEcD0HR.js";import{u as Q,l as M,p as K,ab as X,P as Y,E as S,ac as Z,F as ee}from"./index-BzgMrLHf.js";import{u as se,r as te,B as ae}from"./tableSelection-CojkFPwd.js";import{C as ne}from"./ConfirmDialog-CCJnWoSc.js";import{D as re}from"./DataTable-CrQq0sYf.js";import{F as B}from"./Field-DEJ6Wjg9.js";import{a as le,M as R}from"./ModelScopeControl-Dz7NaZdS.js";import{f as I,B as d,d as y,A as f}from"./heroui-DZj66Arc.js";const ie=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function U(t){return ie.format(t)}const de=t=>t.user_id,D=t=>t.startsWith("apikey-");function H(t){return t.split("-")[0]}function F(t){return t.name??H(t.budget_id)}function $({value:t,onChange:l,budgets:n}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("label",{htmlFor:"user-budget",className:"text-sm font-medium text-[var(--otari-ink)]",children:"Budget"}),e.jsxs("select",{id:"user-budget",value:t??"",onChange:a=>l(a.target.value||null),className:"w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)]",children:[e.jsx("option",{value:"",children:"No budget (unlimited)"}),n.map(a=>e.jsxs("option",{value:a.budget_id,children:[F(a),a.max_budget===null?" · no limit":` · ${U(a.max_budget)}`]},a.budget_id))]}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The spending limit this user is held to. Manage budgets on the Budgets page."})]})}function oe({onClose:t}){const l=Z(),n=M(),[a,i]=r.useState(""),[o,u]=r.useState(""),[c,m]=r.useState(null),[p,g]=r.useState(null),[j,b]=r.useState(!0),x=()=>{if(l.isPending||!j||a.trim()==="")return;const h={user_id:a.trim(),alias:o.trim()||null,budget_id:c,allowed_models:p};l.mutate(h,{onSuccess:t})};return e.jsx(y,{children:e.jsxs(y.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Create user"}),e.jsx(S,{error:l.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(B,{label:"User ID",value:a,onChange:i,placeholder:"alice@example.com",isRequired:!0,autoFocus:!0,description:"The identifier callers send as the `user` field; spend and budgets track against it."}),e.jsx(B,{label:"Alias (optional)",value:o,onChange:u,placeholder:"Alice"})]}),e.jsx($,{value:c,onChange:m,budgets:n.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:null,onChange:(h,P)=>{g(h),b(P)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:l.isPending||!j||a.trim()==="",onPress:x,children:l.isPending?"Creating…":"Create user"}),e.jsx(d,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function ce({user:t,onClose:l}){const n=K(),a=M(),[i,o]=r.useState(t.alias??""),[u,c]=r.useState(t.budget_id),[m,p]=r.useState(t.allowed_models),[g,j]=r.useState(!0),b=()=>{if(n.isPending||!g)return;const x={alias:i.trim()||null,budget_id:u,allowed_models:m};n.mutate({id:t.user_id,body:x},{onSuccess:l})};return e.jsx(y,{children:e.jsxs(y.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.user_id})]}),e.jsx(S,{error:n.error}),e.jsx(B,{label:"Alias",value:i,onChange:o,placeholder:"Alice"}),e.jsx($,{value:u,onChange:c,budgets:a.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:t.allowed_models,onChange:(x,h)=>{p(x),j(h)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:n.isPending||!g,onPress:b,children:n.isPending?"Saving…":"Save changes"}),e.jsx(d,{variant:"ghost",onPress:l,children:"Cancel"})]})]})})}function ue({trigger:t,message:l,confirmLabel:n,isPending:a,onConfirm:i}){const[o,u]=r.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsx("span",{className:"max-w-xs text-xs text-amber-800",children:l}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(d,{size:"sm",variant:"danger",isDisabled:a,onPress:i,children:n}),e.jsx(d,{size:"sm",variant:"ghost",isDisabled:a,onPress:()=>u(!1),children:"Cancel"})]})]}):e.jsx(d,{size:"sm",variant:"danger-soft",onPress:()=>u(!0),children:t})}function me({user:t}){return t.blocked?e.jsx(I,{size:"sm",color:"warning",children:"Blocked"}):e.jsx(I,{size:"sm",color:"accent",children:"Active"})}function ge({allowed:t}){const{text:l,tone:n}=le(t),a=n==="danger"?"text-red-700 font-medium":n==="muted"?"text-[var(--otari-muted)]":"text-[var(--otari-brand-dark)] font-medium",i=t&&t.length>0?t.join(", "):void 0;return e.jsx("span",{className:`text-xs ${a}`,title:i,children:l})}function xe({onCreate:t}){return e.jsx(y,{children:e.jsxs(y.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"No users yet"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"A user owns API keys and carries the budget and default model access those keys inherit. Create a user here, then issue its keys on the API keys page."})]}),e.jsx("div",{children:e.jsx(d,{variant:"primary",onPress:t,children:"Create your first user"})})]})})}function he({isOpen:t,onOpenChange:l,budgets:n,count:a,isPending:i,error:o,onAssign:u}){const[c,m]=r.useState("");return r.useEffect(()=>{t&&m("")},[t]),e.jsx(f,{isOpen:t,onOpenChange:l,children:t?e.jsx(f.Backdrop,{children:e.jsx(f.Container,{placement:"center",size:"md",children:e.jsxs(f.Dialog,{children:[e.jsx(f.Header,{children:e.jsx(f.Heading,{children:"Assign budget"})}),e.jsxs(f.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Assign a budget to ",a," selected ",a===1?"user":"users","."]}),e.jsx(ee,{label:"Budget",value:c,onChange:m,options:[{value:"",label:"Select a budget…"},...n.map(p=>({value:p.budget_id,label:F(p)}))]}),e.jsx(S,{error:o})]}),e.jsxs(f.Footer,{children:[e.jsx(d,{variant:"ghost",isDisabled:i,onPress:()=>l(!1),children:"Cancel"}),e.jsx(d,{variant:"primary",isDisabled:!c,isPending:i,onPress:()=>u(c),children:"Assign"})]})]})})}):null})}function Pe(){const t=Q(),l=M(),n=K(),a=X(),[i,o]=r.useState(!1),[u,c]=r.useState(null),[m,p]=r.useState(!1),g=t.data??[],j=t.isLoading,b=g.filter(s=>D(s.user_id)).length,x=m?g:g.filter(s=>!D(s.user_id)),h=g.find(s=>s.user_id===u)??null,P=!j&&x.length===0&&!i,A=r.useMemo(()=>new Map((l.data??[]).map(s=>[s.budget_id,s])),[l.data]),C=se(),[q,_]=r.useState(!1),[W,N]=r.useState(!1),[z,E]=r.useState(void 0),[T,O]=r.useState(!1),G=x.map(s=>s.user_id),v=te(C.selectedKeys,G),L=r.useCallback((s,k)=>n.mutate({id:s.user_id,body:{blocked:k}}),[n.mutate]),V=async(s,k)=>{O(!0),E(void 0);try{for(const w of v)await s(w);C.clear(),k()}catch(w){E(w)}finally{O(!1)}},J=r.useMemo(()=>[{id:"user",header:"User",isRowHeader:!0,cell:s=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx("code",{className:"text-xs font-medium text-[var(--otari-ink)]",children:s.user_id}),D(s.user_id)?e.jsx(I,{size:"sm",color:"default",children:"virtual"}):null]}),s.alias?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:s.alias}):null]})},{id:"status",header:"Status",cell:s=>e.jsx(me,{user:s})},{id:"budget",header:"Budget",cell:s=>s.budget_id?e.jsx("span",{className:"text-[var(--otari-muted)]",title:s.budget_id,children:A.get(s.budget_id)?F(A.get(s.budget_id)):H(s.budget_id)}):e.jsx("span",{className:"text-[var(--otari-muted)]",children:"—"})},{id:"spend",header:"Spend",cell:s=>e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[U(s.spend),s.reserved>0?e.jsxs("span",{children:[" (+",U(s.reserved)," held)"]}):null]})},{id:"access",header:"Model access",cell:s=>e.jsx(ge,{allowed:s.allowed_models})},{id:"actions",header:"Actions",align:"end",cell:s=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(d,{size:"sm",variant:"outline",isDisabled:n.isPending,onPress:()=>L(s,!s.blocked),children:s.blocked?"Unblock":"Block"}),e.jsx(d,{size:"sm",variant:"ghost",onPress:()=>{o(!1),c(s.user_id)},children:"Edit"}),e.jsx(ue,{trigger:"Delete",confirmLabel:"Delete user",isPending:a.isPending,message:e.jsxs(e.Fragment,{children:["Delete ",e.jsx("strong",{children:s.user_id}),"? This deactivates its API keys and hides the user; usage history is preserved."]}),onConfirm:()=>a.mutate(s.user_id)})]})}],[A,n.isPending,a.isPending,a.mutate,L]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Y,{title:"Users",description:"People and teams that own API keys. Set each one's budget and default model access here; issue their keys on the API keys page.",action:i?null:e.jsx(d,{variant:"primary",onPress:()=>{c(null),o(!0)},children:"Create user"})}),e.jsx(S,{error:t.error??n.error??a.error}),P?e.jsx(xe,{onCreate:()=>{c(null),o(!0)}}):null,b>0?e.jsxs("label",{className:"flex w-fit items-center gap-2 text-xs text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:m,onChange:s=>p(s.target.checked)}),"Show auto-created (virtual) users (",b,")"]}):null,i?e.jsx(oe,{onClose:()=>o(!1)}):null,h?e.jsx(ce,{user:h,onClose:()=>c(null)},h.user_id):null,v.length>0?e.jsxs(ae,{selectedCount:v.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:C.clear,children:[e.jsx(d,{size:"sm",variant:"primary",onPress:()=>N(!0),children:"Assign budget"}),e.jsx(d,{size:"sm",variant:"danger",onPress:()=>_(!0),children:"Delete"})]}):null,e.jsx(re,{ariaLabel:"Users",columns:J,rows:x,getRowKey:de,isLoading:j,emptyContent:"No users yet. Create one, or create an API key to auto-create one.",selectionMode:"multiple",selectedKeys:C.selectedKeys,onSelectionChange:C.onSelectionChange}),e.jsx(ne,{isOpen:q,onOpenChange:_,heading:"Delete users",body:`Delete ${v.length} ${v.length===1?"user":"users"}? This deactivates their API keys and hides them; usage history is preserved.`,confirmLabel:"Delete",isPending:T,error:z,onConfirm:()=>V(s=>a.mutateAsync(s),()=>_(!1))}),e.jsx(he,{isOpen:W,onOpenChange:N,budgets:l.data??[],count:v.length,isPending:T,error:z,onAssign:s=>V(k=>n.mutateAsync({id:k,body:{budget_id:s}}),()=>N(!1))})]})}export{Pe as UsersPage}; diff --git a/src/gateway/static/dashboard/assets/index-DHOrDaWF.js b/src/gateway/static/dashboard/assets/index-BzgMrLHf.js similarity index 96% rename from src/gateway/static/dashboard/assets/index-DHOrDaWF.js rename to src/gateway/static/dashboard/assets/index-BzgMrLHf.js index 8f60d7c4..5aa9797c 100644 --- a/src/gateway/static/dashboard/assets/index-DHOrDaWF.js +++ b/src/gateway/static/dashboard/assets/index-BzgMrLHf.js @@ -1,2 +1,2 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ActivityPage-DDIA-0sC.js","assets/tanstack-query-1t81HyiD.js","assets/react-dgEcD0HR.js","assets/heroui-DZj66Arc.js","assets/tableSelection-CojkFPwd.js","assets/ConfirmDialog-CvV5gDQA.js","assets/DataTable-CrQq0sYf.js","assets/TablePagination-Dob9a4wG.js","assets/AliasesPage-CNowZYqH.js","assets/Field-DEJ6Wjg9.js","assets/BudgetsPage-Bt8e_tU9.js","assets/KeysPage-C2ndVSn9.js","assets/ModelScopeControl-CTGSrkZ9.js","assets/ModelsPage-CAW2PJJV.js","assets/OverviewPage-CB0oRBw3.js","assets/charts-CuOShutR.js","assets/recharts-D7kuKp97.js","assets/ProvidersPage-BULpjU4h.js","assets/SettingsPage-DovUcX7T.js","assets/ToolsGuardrailsPage-DIVtsycj.js","assets/UsagePage-BNkia9eC.js","assets/UsersPage-C8N0rekv.js"])))=>i.map(i=>d[i]); -var _e=Object.defineProperty;var Te=(e,t,n)=>t in e?_e(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var ae=(e,t,n)=>Te(e,typeof t!="symbol"?t+"":t,n);import{u as y,j as r,a as p,b as f,k as J,Q as Le,c as Ae}from"./tanstack-query-1t81HyiD.js";import{d as Ie,r as i,N as de,O as qe,H as Re,e as Oe,f as w,h as De}from"./react-dgEcD0HR.js";import{C as M,L as me,I as fe,a as Me,b as Fe,B as C,d as O,c as N,T as Ke,e as Ue}from"./heroui-DZj66Arc.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))s(l);new MutationObserver(l=>{for(const a of l)if(a.type==="childList")for(const x of a.addedNodes)x.tagName==="LINK"&&x.rel==="modulepreload"&&s(x)}).observe(document,{childList:!0,subtree:!0});function n(l){const a={};return l.integrity&&(a.integrity=l.integrity),l.referrerPolicy&&(a.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?a.credentials="include":l.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function s(l){if(l.ep)return;l.ep=!0;const a=n(l);fetch(l.href,a)}})();var Be=Ie();const $e="modulepreload",Qe=function(e){return"/"+e},ie={},E=function(t,n,s){let l=Promise.resolve();if(n&&n.length>0){let x=function(u){return Promise.all(u.map(b=>Promise.resolve(b).then(h=>({status:"fulfilled",value:h}),h=>({status:"rejected",reason:h}))))};document.getElementsByTagName("link");const m=document.querySelector("meta[property=csp-nonce]"),v=(m==null?void 0:m.nonce)||(m==null?void 0:m.getAttribute("nonce"));l=x(n.map(u=>{if(u=Qe(u),u in ie)return;ie[u]=!0;const b=u.endsWith(".css"),h=b?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${u}"]${h}`))return;const d=document.createElement("link");if(d.rel=b?"stylesheet":$e,b||(d.as="script"),d.crossOrigin="",d.href=u,v&&d.setAttribute("nonce",v),document.head.appendChild(d),b)return new Promise((L,q)=>{d.addEventListener("load",L),d.addEventListener("error",()=>q(new Error(`Unable to preload CSS for ${u}`)))})}))}function a(x){const m=new Event("vite:preloadError",{cancelable:!0});if(m.payload=x,window.dispatchEvent(m),!m.defaultPrevented)throw x}return l.then(x=>{for(const m of x||[])m.status==="rejected"&&a(m.reason);return t().catch(a)})};class _ extends Error{constructor(n,s){super(s);ae(this,"status");this.name="ApiError",this.status=n}}let F=null;function oe(e){F=e}async function W(e){try{const t=await e.json();if(typeof t.detail=="string")return t.detail;if(t.detail!=null)return JSON.stringify(t.detail)}catch{}return e.statusText||`Request failed (${e.status})`}async function ze(e){let t;try{t=await fetch("/v1/auth/session",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({master_key:e})})}catch{throw new _(0,"Network error: could not reach the gateway.")}if(t.status===401||t.status===403)return!1;if(!t.ok)throw new _(t.status,await W(t));return!0}async function Ve(){try{await fetch("/v1/auth/session",{method:"DELETE"})}catch{}}async function o(e,t={}){const n=new Headers(t.headers);n.set("Accept","application/json"),t.body!=null&&!n.has("Content-Type")&&n.set("Content-Type","application/json");let s;try{s=await fetch(e,{...t,headers:n})}catch{throw new _(0,"Network error: could not reach the gateway.")}if(s.status===401||s.status===403)throw F==null||F(),new _(s.status,await W(s));if(!s.ok)throw new _(s.status,await W(s));if(s.status!==204)return await s.json()}const G="otari.dashboard.hasSession",he=i.createContext(null);function He(){try{return window.localStorage.getItem(G)==="1"}catch{return!1}}function We({children:e}){const t=y(),[n,s]=i.useState(He),l=i.useCallback(()=>{Ve(),s(!1),t.clear();try{window.localStorage.removeItem(G)}catch{}},[t]),a=i.useCallback(()=>{t.clear(),s(!0);try{window.localStorage.setItem(G,"1")}catch{}},[t]);i.useEffect(()=>(oe(l),()=>oe(null)),[l]);const x=i.useMemo(()=>({isAuthenticated:n,login:a,logout:l}),[n,a,l]);return r.jsx(he.Provider,{value:x,children:e})}function Y(){const e=i.useContext(he);if(!e)throw new Error("useAuth must be used within an AuthProvider");return e}function Ge(e){return e instanceof _&&e.status===0}function Je(){const e=y(),[t,n]=i.useState(!1);return i.useEffect(()=>{const s=e.getQueryCache(),l=()=>s.getAll().some(a=>a.state.status==="error"&&Ge(a.state.error));return n(l()),s.subscribe(()=>n(l()))},[e]),t}function Ye(){return Je()?r.jsxs("div",{role:"alert","aria-live":"assertive",className:"fixed right-4 bottom-4 z-50 flex max-w-sm items-start gap-2.5 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 shadow-lg",children:[r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"mt-0.5 h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M12 9v4M12 17h.01",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z",strokeLinejoin:"round"})]}),r.jsxs("span",{children:[r.jsx("strong",{className:"font-semibold",children:"Can’t reach the gateway."})," The backend isn’t responding; data won’t load or save until the connection is restored."]})]}):null}const T="models",U="pricing",xe="settings",ye="tool-settings",X="aliases",Z="discoverable",ee="providers",te="provider-health",ve="stored-providers",Xe="model-metadata",Ze="build",A="keys",I="budgets",ge="users",D="usage",et=6e4,le=60*6e4;function Gt(){return p({queryKey:[T],queryFn:()=>o("/v1/models"),staleTime:6e4})}function tt(){return p({queryKey:[Ze],queryFn:()=>o("/dashboard-build.json"),refetchInterval:et,refetchOnWindowFocus:!0,staleTime:0,retry:!1})}function Jt(){return p({queryKey:[Z],queryFn:()=>o("/v1/models/discoverable"),staleTime:5*6e4})}function Yt(){return p({queryKey:[ee],queryFn:()=>o("/v1/providers"),staleTime:5*6e4})}function Xt(){return p({queryKey:["provider-catalog"],queryFn:()=>o("/v1/providers/catalog"),staleTime:1/0})}function Zt(e){return p({queryKey:["provider-catalog",e],queryFn:()=>o(`/v1/providers/catalog/${encodeURIComponent(e)}`),enabled:e!=="",staleTime:1/0})}function er(){return p({queryKey:[te],queryFn:()=>o("/v1/providers/health"),staleTime:le,refetchInterval:le})}function tr(){const e=y();return f({mutationFn:()=>o("/v1/providers/health?refresh=true"),onSuccess:t=>e.setQueryData([te],t)})}function rr(){return p({queryKey:[ve],queryFn:()=>o("/v1/provider-credentials"),staleTime:6e4})}function B(e){e.invalidateQueries({queryKey:[ve]}),e.invalidateQueries({queryKey:[ee]}),e.invalidateQueries({queryKey:[T]}),e.invalidateQueries({queryKey:[Z]}),e.invalidateQueries({queryKey:[te]})}function nr(){const e=y();return f({mutationFn:t=>o("/v1/provider-credentials",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>B(e)})}function sr(){const e=y();return f({mutationFn:({instance:t,body:n})=>o(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>B(e)})}function ar(){const e=y();return f({mutationFn:t=>o(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>B(e)})}function ir(){const e=y();return f({mutationFn:()=>o("/v1/provider-credentials/reencrypt",{method:"POST"}),onSuccess:()=>B(e)})}function or(){return f({mutationFn:e=>o(`/v1/provider-credentials/${encodeURIComponent(e)}/test`,{method:"POST"})})}function lr(){return f({mutationFn:e=>o("/v1/provider-credentials/test",{method:"POST",body:JSON.stringify(e)})})}function cr(){return p({queryKey:[Xe],queryFn:()=>o("/v1/models/metadata"),staleTime:10*6e4})}function ur(){return p({queryKey:[X],queryFn:()=>o("/v1/aliases"),staleTime:6e4})}function dr(){const e=y();return f({mutationFn:t=>o("/v1/aliases",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[X]}),e.invalidateQueries({queryKey:[T]})}})}function mr(){const e=y();return f({mutationFn:t=>o(`/v1/aliases/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{e.invalidateQueries({queryKey:[X]}),e.invalidateQueries({queryKey:[T]})}})}function rt(){return p({queryKey:[xe],queryFn:()=>o("/v1/settings"),staleTime:6e4})}function nt(){const e=y();return f({mutationFn:t=>o("/v1/settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([xe],t),e.invalidateQueries({queryKey:[T]}),e.invalidateQueries({queryKey:[Z]})}})}function fr(){return f({mutationFn:()=>o("/v1/settings/master-key/rotate",{method:"POST"})})}function hr(){return p({queryKey:[ye],queryFn:()=>o("/v1/tool-settings"),staleTime:6e4})}function xr(){const e=y();return f({mutationFn:t=>o("/v1/tool-settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([ye],t)}})}function yr(){return f({mutationFn:({service:e,url:t})=>o(`/v1/tool-settings/${encodeURIComponent(e)}/test`,{method:"POST",body:JSON.stringify({url:t})})})}const $=1e3,st=100;async function at(){const e=[];for(let t=0;to("/v1/pricing",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[U]}),e.invalidateQueries({queryKey:[T]})}})}function pr(){const e=y();return f({mutationFn:t=>o(`/v1/pricing/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{e.invalidateQueries({queryKey:[U]}),e.invalidateQueries({queryKey:[T]})}})}function br(){return f({mutationFn:()=>o("/v1/pricing/refresh",{method:"POST"})})}function jr(){const e=y();return f({mutationFn:()=>o("/v1/pricing/refresh/confirm",{method:"POST"}),onSuccess:()=>{e.invalidateQueries({queryKey:[U]}),e.invalidateQueries({queryKey:[T]}),e.invalidateQueries({queryKey:[ee]})}})}function wr(){return f({mutationFn:()=>o("/v1/pricing/refresh/reject",{method:"POST"})})}const Q=1e3,it=100;async function ot(){const e=[];for(let t=0;to("/v1/keys",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function Er(){const e=y();return f({mutationFn:({id:t,body:n})=>o(`/v1/keys/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function Pr(){const e=y();return f({mutationFn:t=>o(`/v1/keys/${encodeURIComponent(t)}/rotate`,{method:"POST"}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function Nr(){const e=y();return f({mutationFn:t=>o(`/v1/keys/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}const z=1e3,lt=100;async function ct(){const e=[];for(let t=0;to(`/v1/budgets/${encodeURIComponent(e)}/reset-logs`),enabled:e!==null,staleTime:6e4})}function Tr(){const e=y();return f({mutationFn:t=>o("/v1/budgets",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[I]})})}function Lr(){const e=y();return f({mutationFn:({id:t,body:n})=>o(`/v1/budgets/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>void e.invalidateQueries({queryKey:[I]})})}function Ar(){const e=y();return f({mutationFn:t=>o(`/v1/budgets/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[I]})})}const V=1e3,ut=100;async function dt(){const e=[];for(let t=0;to("/v1/users",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>re(e)})}function Rr(){const e=y();return f({mutationFn:({id:t,body:n})=>o(`/v1/users/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>re(e)})}function Or(){const e=y();return f({mutationFn:t=>o(`/v1/users/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{re(e),e.invalidateQueries({queryKey:[A]})}})}function ne(e){const t=new URLSearchParams;return e.start_date&&t.set("start_date",e.start_date),e.end_date&&t.set("end_date",e.end_date),e.status&&t.set("status",e.status),e.model&&t.set("model",e.model),e.endpoint&&t.set("endpoint",e.endpoint),e.user_id&&t.set("user_id",e.user_id),e.api_key_id&&t.set("api_key_id",e.api_key_id),e.source&&t.set("source",e.source),e.priced!==void 0&&t.set("priced",String(e.priced)),e.counts_toward_budget!==void 0&&t.set("counts_toward_budget",String(e.counts_toward_budget)),t}function Dr(e,t,n){return p({queryKey:[D,"list",e,t,n],queryFn:()=>{const s=ne(e);return s.set("skip",String(t*n)),s.set("limit",String(n)),o(`/v1/usage?${s.toString()}`)},placeholderData:J,staleTime:1e4})}function Mr(e,t=!0){return p({queryKey:[D,"count",e],queryFn:()=>o(`/v1/usage/count?${ne(e).toString()}`),enabled:t,placeholderData:J,staleTime:1e4})}function Fr(){const e=y();return f({mutationFn:t=>o("/v1/usage",{method:"DELETE",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[D]})}})}function Kr(){const e=y();return f({mutationFn:t=>o("/v1/usage/set-price",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[D]})}})}function Ur(e,t,n=!0){return p({queryKey:[D,"summary",e,t],queryFn:()=>{const s=ne(e);return s.set("bucket",t),o(`/v1/usage/summary?${s.toString()}`)},enabled:n,placeholderData:J,staleTime:3e4})}function Br(e){return e==null?"0":new Intl.NumberFormat("en-US").format(e)}function $r(e){if(e==null)return"$0.00";const t=e!==0&&Math.abs(e)<.01?4:2;return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:t}).format(e)}function Qr(e){if(e==null)return"—";if(e>=1e6){const t=e/1e6;return`${Number.isInteger(t)?t:t.toFixed(1)}M`}if(e>=1e3){const t=Math.round(e/1e3);return t>=1e3?"1M":`${t}K`}return String(e)}const mt=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function zr(e){if(!e)return"—";const t=/^(\d{4})-(\d{2})/.exec(e);if(!t)return e;const n=Number(t[2])-1;return n<0||n>11?t[1]:`${mt[n]} ${t[1]}`}const ft=new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:2});function Vr(e){return ft.format(e)}function Hr(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function ht(e){return`${(e*100).toFixed(1)}%`}function Wr(e,t){return t===void 0||t===0?null:(e-t)/t}function Gr(e,t=Date.now()){if(!e)return"never";const n=new Date(e);if(Number.isNaN(n.getTime()))return e;const s=Math.round((t-n.getTime())/1e3),l=s<0,a=Math.abs(s),x=[["second",60],["minute",60],["hour",24],["day",30],["month",12],["year",Number.POSITIVE_INFINITY]];let m=a,v="second";for(const[b,h]of x){if(v=b,m0?"▲":e<0?"▼":"•";return r.jsxs("span",{className:"text-[var(--otari-muted)]",children:[t," ",ht(Math.abs(e))," vs prev"]})}function xt(e){return e instanceof _||e instanceof Error?e.message:"Something went wrong."}function yt({error:e}){return e?r.jsx("div",{role:"alert",className:"rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700",children:xt(e)}):null}function vt({tone:e="info",children:t}){const n=e==="warning"?"border-amber-200 bg-amber-50 text-amber-800":"border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return r.jsx("div",{className:`rounded-lg border px-4 py-3 text-sm ${n}`,children:t})}function Xr({title:e,description:t,action:n}){return r.jsxs("div",{className:"flex flex-col gap-3",children:[r.jsxs("div",{children:[r.jsx("h1",{className:"text-xl font-semibold text-[var(--otari-ink)]",children:e}),t?r.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t}):null]}),n?r.jsx("div",{className:"flex flex-wrap gap-2",children:n}):null]})}function Zr({children:e,confirmLabel:t,onConfirm:n,isPending:s}){const[l,a]=i.useState(!1);return l?r.jsxs("span",{className:"inline-flex items-center gap-1",children:[r.jsx(C,{size:"sm",variant:"danger",isDisabled:s,onPress:n,children:t}),r.jsx(C,{size:"sm",variant:"ghost",isDisabled:s,onPress:()=>a(!1),children:"Cancel"})]}):r.jsx(C,{size:"sm",variant:"danger-soft",onPress:()=>a(!0),children:e})}const gt="rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none";function en({id:e,label:t,ariaLabel:n,value:s,onChange:l,options:a,children:x,disabled:m}){const v=i.useId(),u=e??(t?v:void 0),b=r.jsx("select",{id:u,"aria-label":t?void 0:n,value:s,disabled:m,onChange:h=>l(h.target.value),className:gt,children:a?a.map(h=>r.jsx("option",{value:h.value,children:h.label},h.value)):x});return t?r.jsxs("div",{className:"flex flex-col gap-1",children:[r.jsx("label",{htmlFor:u,className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),b]}):b}function tn({label:e,value:t,onChange:n,options:s,placeholder:l,maxVisible:a=50,allowsCustom:x=!1}){const m=d=>{var L;return((L=s.find(q=>q.value===d))==null?void 0:L.label)??d},[v,u]=i.useState(()=>m(t));i.useEffect(()=>{u(m(t))},[t]);const b=v.trim().toLowerCase(),h=s.filter(d=>!b||d.value.toLowerCase().includes(b)||d.label.toLowerCase().includes(b)).slice(0,a);return r.jsxs(M.Root,{allowsEmptyCollection:!0,allowsCustomValue:x,menuTrigger:"focus",inputValue:v,onInputChange:d=>{u(d),x?n(d.trim()):d.trim()===""&&n("")},onSelectionChange:d=>{d!=null&&n(String(d))},className:"flex flex-col gap-1",children:[r.jsx(me,{className:"text-xs font-medium text-[var(--otari-muted)]",children:e}),r.jsxs(M.InputGroup,{children:[r.jsx(fe,{placeholder:l,autoComplete:"off",onFocus:d=>d.currentTarget.select()}),r.jsx(M.Trigger,{})]}),r.jsx(M.Popover,{children:r.jsx(Me,{items:h,className:"max-h-72 overflow-auto",children:d=>r.jsx(Fe,{id:d.value,textValue:d.label,children:d.label})})})]})}function pt(){var a;const e=rt(),t=nt(),[n,s]=i.useState(!1);return!(((a=e.data)==null?void 0:a.require_pricing)===!0&&e.data.default_pricing===!1)||n?null:r.jsx("div",{className:"shrink-0 px-6 pt-3",children:r.jsx(vt,{tone:"warning",children:r.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[r.jsxs("span",{children:["Requests are rejected until pricing is set (",r.jsx("code",{children:"require_pricing"})," is on). Enable default pricing to meter new models with public rates right away."]}),r.jsxs("span",{className:"flex items-center gap-2",children:[r.jsx(C,{size:"sm",variant:"primary",isDisabled:t.isPending,onPress:()=>t.mutate({default_pricing:!0}),children:t.isPending?"Enabling…":"Enable default pricing"}),r.jsx(C,{size:"sm",variant:"ghost",onPress:()=>s(!0),children:"Dismiss"})]})]})})})}function bt(){const{data:e}=tt(),t=i.useRef(null);return e&&t.current===null&&(t.current=e.build),e!=null&&t.current!=null&&e.build!==t.current}function jt(){const e=bt(),[t,n]=i.useState(!1);return!e||t?null:r.jsx("div",{className:"pointer-events-none absolute inset-x-0 top-0 z-50 flex justify-center",children:r.jsxs("div",{role:"status",className:"pointer-events-auto mt-1.5 flex items-center gap-3 rounded-full border border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] py-1.5 pr-1.5 pl-4 text-sm text-[var(--otari-brand-dark)] shadow-md",children:[r.jsxs("span",{children:[r.jsx("strong",{className:"font-semibold",children:"An update is available."})," Reloading keeps you signed in."]}),r.jsx(C,{size:"sm",variant:"primary",onPress:()=>window.location.reload(),children:"Update now"}),r.jsx(C,{size:"sm",variant:"ghost",onPress:()=>n(!0),children:"Later"})]})})}const pe=200,be=480,H=240,wt=60,je="otari.dashboard.sidebarWidth",we="otari.dashboard.sidebarCollapsed",ue=16,Se="(max-width: 767px)",K=e=>Math.min(be,Math.max(pe,e));function St(){return typeof window>"u"||typeof window.matchMedia!="function"?!1:window.matchMedia(Se).matches}const kt='a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])';function Et(e){return e?Array.from(e.querySelectorAll(kt)).filter(t=>t.offsetParent!==null||t===document.activeElement):[]}function Pt(){if(typeof window>"u")return H;try{const e=window.localStorage.getItem(je),t=e?Number.parseInt(e,10):Number.NaN;return Number.isNaN(t)?H:K(t)}catch{return H}}function Nt(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(we)==="1"}catch{return!1}}const Ct=[{key:"home"},{key:"observability",label:"Observability"},{key:"catalog",label:"Catalog"},{key:"access",label:"Access"},{key:"system"}],_t=[{to:"/",section:"home",label:"Overview",end:!0,icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("rect",{x:"3.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"13.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"3.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"13.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"})]})},{to:"/activity",section:"observability",label:"Activity",icon:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:r.jsx("path",{d:"M3 12h4l2.5-6 4 12 2.5-6H21",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/usage",section:"observability",label:"Usage",icon:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:r.jsx("path",{d:"M4 20V10M10 20V4M16 20v-7M22 20H2",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/providers",section:"catalog",label:"Providers",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("rect",{x:"3.5",y:"4.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"3.5",y:"13.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),r.jsx("path",{d:"M7 7.5h.01M7 16.5h.01",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/users",section:"access",label:"Users",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("circle",{cx:"9",cy:"8",r:"3.2",strokeLinejoin:"round"}),r.jsx("path",{d:"M3.5 19a5.5 5.5 0 0 1 11 0",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M16 5.2a3.2 3.2 0 0 1 0 5.6M17.5 19a5.5 5.5 0 0 0-3-4.9",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/keys",section:"access",label:"API keys",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("circle",{cx:"7.5",cy:"15.5",r:"3.5"}),r.jsx("path",{d:"M10 13l7-7M14 5l3 3M16.5 7.5l2-2",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/budgets",section:"access",label:"Budgets",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M3 7.5A1.5 1.5 0 0 1 4.5 6H18a1.5 1.5 0 0 1 1.5 1.5V9",strokeLinejoin:"round"}),r.jsx("rect",{x:"3",y:"7.5",width:"18",height:"12",rx:"1.5",strokeLinejoin:"round"}),r.jsx("path",{d:"M16 13.5h.01",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M21 12v3h-3.5a1.5 1.5 0 0 1 0-3H21z",strokeLinejoin:"round"})]})},{to:"/models",section:"catalog",label:"Models",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z",strokeLinejoin:"round"}),r.jsx("path",{d:"M12 12l8-4.5M12 12v9M12 12L4 7.5",strokeLinejoin:"round"})]})},{to:"/aliases",section:"catalog",label:"Aliases",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M20.6 13.4L13.4 20.6a2 2 0 0 1-2.8 0l-7-7A2 2 0 0 1 3 12.2V5a2 2 0 0 1 2-2h7.2a2 2 0 0 1 1.4.6l7 7a2 2 0 0 1 0 2.8z",strokeLinejoin:"round"}),r.jsx("circle",{cx:"7.5",cy:"7.5",r:"1.5"})]})},{to:"/tools",section:"system",label:"Tools & Guardrails",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M14.7 6.3a4 4 0 0 1 5 5l-8.4 8.4a2 2 0 0 1-2.8 0l-2.2-2.2a2 2 0 0 1 0-2.8z",strokeLinejoin:"round"}),r.jsx("path",{d:"M12 9 5 16",strokeLinecap:"round"})]})},{to:"/settings",section:"system",label:"Settings",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("circle",{cx:"12",cy:"12",r:"3"}),r.jsx("path",{d:"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z",strokeLinejoin:"round"})]})}];function Tt(){const{logout:e}=Y(),t=i.useRef(null),n=i.useRef(null),[s,l]=i.useState(Pt),[a,x]=i.useState(Nt),[m,v]=i.useState(!1),[u,b]=i.useState(St),[h,d]=i.useState(!1);i.useEffect(()=>{if(typeof window>"u"||typeof window.matchMedia!="function")return;const c=window.matchMedia(Se),g=j=>{b(j.matches),j.matches||d(!1)};return typeof c.addEventListener=="function"?(c.addEventListener("change",g),()=>c.removeEventListener("change",g)):(c.addListener(g),()=>c.removeListener(g))},[]),i.useEffect(()=>{if(!h)return;const c=g=>{g.key==="Escape"&&d(!1)};return window.addEventListener("keydown",c),()=>window.removeEventListener("keydown",c)},[h]),i.useEffect(()=>{var c,g,j;u&&(h?(c=t.current)==null||c.focus():(g=t.current)!=null&&g.contains(document.activeElement)&&((j=n.current)==null||j.focus()))},[u,h]);const L=i.useCallback(c=>{if(c.key!=="Tab")return;const g=Et(t.current);if(g.length===0)return;const j=g[0],S=g[g.length-1],R=document.activeElement;c.shiftKey&&(R===j||R===t.current)?(c.preventDefault(),S.focus()):!c.shiftKey&&R===S&&(c.preventDefault(),j.focus())},[]);i.useEffect(()=>{const c=window.setTimeout(()=>{try{window.localStorage.setItem(je,String(Math.round(s)))}catch{}},200);return()=>window.clearTimeout(c)},[s]),i.useEffect(()=>{try{window.localStorage.setItem(we,a?"1":"0")}catch{}},[a]);const q=i.useCallback(c=>{c.preventDefault(),c.currentTarget.setPointerCapture(c.pointerId),v(!0)},[]),Ee=i.useCallback(c=>{var j;if(!c.currentTarget.hasPointerCapture(c.pointerId))return;const g=((j=t.current)==null?void 0:j.getBoundingClientRect().left)??0;l(K(c.clientX-g))},[]),Pe=i.useCallback(c=>{c.currentTarget.hasPointerCapture(c.pointerId)&&c.currentTarget.releasePointerCapture(c.pointerId),v(!1)},[]),Ne=i.useCallback(c=>{c.key==="ArrowLeft"?(c.preventDefault(),l(g=>K(g-ue))):c.key==="ArrowRight"&&(c.preventDefault(),l(g=>K(g+ue)))},[]),Ce=a?wt:s,P=u?!1:a,se=u&&h?!0:void 0;return r.jsxs("div",{className:N("relative flex h-full flex-col overflow-hidden",m&&"cursor-col-resize select-none"),children:[r.jsxs("header",{inert:se,className:"flex shrink-0 items-center justify-between border-b border-[var(--otari-line)] bg-[var(--otari-surface)] px-5 py-3",children:[r.jsxs("div",{className:"flex items-center gap-2.5",children:[r.jsx("button",{type:"button",ref:n,onClick:()=>d(c=>!c),"aria-label":h?"Close navigation":"Open navigation","aria-expanded":h,"aria-controls":"app-sidebar",className:"-ml-1 flex h-8 w-8 items-center justify-center rounded-lg text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)] md:hidden",children:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5",children:r.jsx("path",{d:"M4 6h16M4 12h16M4 18h16",strokeLinecap:"round",strokeLinejoin:"round"})})}),r.jsx("img",{src:"/favicon.svg",alt:"",className:"h-7 w-7 shrink-0"}),r.jsx("span",{className:"text-base font-semibold text-[var(--otari-ink)]",children:"Otari"})]}),r.jsx(C,{size:"sm",variant:"outline",onPress:e,"aria-label":"Sign out",children:"Sign out"})]}),r.jsx(jt,{}),r.jsx(Ye,{}),r.jsx(pt,{}),r.jsxs("div",{className:"flex min-h-0 flex-1",children:[u&&h?r.jsx("div",{"aria-hidden":"true",onClick:()=>d(!1),className:"fixed inset-0 z-30 bg-black/40 md:hidden"}):null,r.jsxs("aside",{ref:t,id:"app-sidebar",role:u?"dialog":void 0,"aria-modal":u&&h?!0:void 0,"aria-label":u?"Navigation":void 0,tabIndex:u?-1:void 0,inert:u&&!h?!0:void 0,onKeyDown:u&&h?L:void 0,style:u?void 0:{width:Ce},className:N("flex flex-col border-r border-[var(--otari-line)] bg-[var(--otari-surface)] focus:outline-none",u?N("fixed inset-y-0 left-0 z-40 w-[17rem] shadow-xl transition-transform duration-200",h?"translate-x-0":"-translate-x-full"):N("relative shrink-0",!m&&"transition-[width] duration-150")),children:[r.jsx("button",{type:"button",onClick:()=>x(c=>!c),"aria-label":a?"Expand sidebar":"Collapse sidebar","aria-pressed":a,title:a?"Expand sidebar":"Collapse sidebar",className:"absolute -right-3 top-4 z-30 hidden h-6 w-6 items-center justify-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-surface)] text-[var(--otari-muted)] shadow-sm transition-colors hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand-dark)] md:flex",children:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",className:N("h-3.5 w-3.5 transition-transform",a&&"rotate-180"),children:r.jsx("path",{d:"M15 6l-6 6 6 6",strokeLinecap:"round",strokeLinejoin:"round"})})}),r.jsx("nav",{className:N("flex flex-col py-4",P?"px-2":"px-3"),children:Ct.map((c,g)=>{const j=_t.filter(S=>S.section===c.key);return j.length===0?null:r.jsxs("div",{className:g>0?"mt-4":void 0,children:[!P&&c.label?r.jsx("div",{className:"px-3 pb-1 text-[11px] font-semibold tracking-wider text-[var(--otari-muted)] uppercase",children:c.label}):null,g>0&&(P||!c.label)?r.jsx("div",{className:"mx-1 mb-2 border-t border-[var(--otari-line)]"}):null,r.jsx("div",{className:"flex flex-col gap-1",children:j.map(S=>r.jsxs(de,{to:S.to,end:S.end,onClick:()=>d(!1),"aria-label":P?S.label:void 0,title:P?S.label:void 0,className:({isActive:R})=>N("flex items-center rounded-lg py-2 text-sm font-medium transition-colors",P?"justify-center px-0":"gap-3 px-3",R?"bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]":"text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]"),children:[S.icon,P?null:S.label]},S.to))})]},c.key)})}),r.jsxs("a",{href:"https://otari.ai",target:"_blank",rel:"noreferrer",title:"otari.ai — the hosted Otari gateway",className:N("mt-auto mb-3 flex items-center rounded-lg py-2 text-xs font-medium text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-brand-dark)]",P?"mx-2 justify-center px-0":"mx-3 gap-2 px-3"),children:[r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4 shrink-0",children:r.jsx("path",{d:"M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z",strokeLinejoin:"round"})}),P?null:r.jsxs("span",{className:"flex-1",children:["otari.ai ",r.jsx("span",{"aria-hidden":!0,children:"↗"})]})]}),a||u?null:r.jsx("div",{role:"separator","aria-orientation":"vertical","aria-label":"Resize sidebar","aria-valuenow":Math.round(s),"aria-valuemin":pe,"aria-valuemax":be,tabIndex:0,onPointerDown:q,onPointerMove:Ee,onPointerUp:Pe,onKeyDown:Ne,className:N("absolute top-0 right-0 z-10 h-full w-1.5 cursor-col-resize touch-none transition-colors","hover:bg-[var(--otari-brand)] focus-visible:bg-[var(--otari-brand)] focus:outline-none",m?"bg-[var(--otari-brand)]":"bg-transparent")})]}),r.jsx("main",{inert:se,className:"flex-1 overflow-y-auto",children:r.jsx("div",{className:"mx-auto flex max-w-[1800px] flex-col gap-6 px-4 py-5 md:px-6 md:py-6",children:r.jsx(qe,{})})})]})]})}function Lt(){const{login:e}=Y(),[t,n]=i.useState(""),[s,l]=i.useState(null),[a,x]=i.useState(!1),m=async()=>{const v=t.trim();if(!(!v||a)){x(!0),l(null);try{await ze(v)?e():l(new Error("Invalid master key."))}catch(u){l(u)}finally{x(!1)}}};return r.jsx("div",{className:"flex min-h-full items-center justify-center p-6",children:r.jsx(O,{className:"w-full max-w-md",children:r.jsxs(O.Content,{className:"flex flex-col gap-5 p-7",children:[r.jsxs("div",{className:"flex flex-col items-center gap-3 text-center",children:[r.jsx("img",{src:"/favicon.svg",alt:"Otari",className:"h-12 w-12"}),r.jsxs("div",{children:[r.jsx("h1",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Otari Dashboard"}),r.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Sign in with your master key to browse models, set pricing, and manage settings."})]})]}),r.jsxs("form",{className:"flex flex-col gap-4",onSubmit:v=>{v.preventDefault(),m()},children:[r.jsxs(Ke,{value:t,onChange:v=>{n(v),s&&l(null)},type:"password",isRequired:!0,className:"flex flex-col gap-1",children:[r.jsx(me,{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Master key"}),r.jsx(fe,{placeholder:"otari-mk-… or your master key",autoFocus:!0,autoComplete:"off"})]}),r.jsxs("details",{className:"text-xs text-[var(--otari-muted)]",children:[r.jsx("summary",{className:"cursor-pointer font-medium text-[var(--otari-brand-dark)]",children:"First run? Where to find your key"}),r.jsxs("p",{className:"mt-2 leading-relaxed",children:["If you did not set ",r.jsx("code",{children:"OTARI_MASTER_KEY"}),", Otari generated one and printed it to the server logs on startup. Look for the line ",r.jsx("code",{children:"Your master key:"})," (for example, run"," ",r.jsx("code",{children:"docker logs "}),") and paste it above."]})]}),r.jsx(yt,{error:s}),r.jsx(C,{type:"submit",variant:"primary",fullWidth:!0,isDisabled:!t.trim()||a,children:a?"Signing in…":"Sign in"})]}),r.jsx("p",{className:"text-center text-xs text-[var(--otari-muted)]",children:"The key is sent once to this gateway and exchanged for a session cookie; it is never stored in the browser."}),r.jsx("div",{className:"border-t border-[var(--otari-line)] pt-4 text-center",children:r.jsx(Ue,{href:"/welcome",className:"text-sm font-medium text-[var(--otari-brand-dark)]",children:"New to Otari? Open the welcome guide"})})]})})})}const At=i.lazy(async()=>({default:(await E(async()=>{const{ActivityPage:e}=await import("./ActivityPage-DDIA-0sC.js");return{ActivityPage:e}},__vite__mapDeps([0,1,2,3,4,5,6,7]))).ActivityPage})),It=i.lazy(async()=>({default:(await E(async()=>{const{AliasesPage:e}=await import("./AliasesPage-CNowZYqH.js");return{AliasesPage:e}},__vite__mapDeps([8,1,2,4,3,5,6,9]))).AliasesPage})),qt=i.lazy(async()=>({default:(await E(async()=>{const{BudgetsPage:e}=await import("./BudgetsPage-Bt8e_tU9.js");return{BudgetsPage:e}},__vite__mapDeps([10,1,2,4,3,5,6,9]))).BudgetsPage})),Rt=i.lazy(async()=>({default:(await E(async()=>{const{KeysPage:e}=await import("./KeysPage-C2ndVSn9.js");return{KeysPage:e}},__vite__mapDeps([11,1,2,4,3,5,6,9,12]))).KeysPage})),Ot=i.lazy(async()=>({default:(await E(async()=>{const{ModelsPage:e}=await import("./ModelsPage-CAW2PJJV.js");return{ModelsPage:e}},__vite__mapDeps([13,1,2,4,3,6,7]))).ModelsPage})),Dt=i.lazy(async()=>({default:(await E(async()=>{const{OverviewIndex:e}=await import("./OverviewPage-CB0oRBw3.js");return{OverviewIndex:e}},__vite__mapDeps([14,1,2,15,16,3,6]))).OverviewIndex})),Mt=i.lazy(async()=>({default:(await E(async()=>{const{ProvidersPage:e}=await import("./ProvidersPage-BULpjU4h.js");return{ProvidersPage:e}},__vite__mapDeps([17,1,2,9,3,6]))).ProvidersPage})),Ft=i.lazy(async()=>({default:(await E(async()=>{const{SettingsPage:e}=await import("./SettingsPage-DovUcX7T.js");return{SettingsPage:e}},__vite__mapDeps([18,1,2,3]))).SettingsPage})),Kt=i.lazy(async()=>({default:(await E(async()=>{const{ToolsGuardrailsPage:e}=await import("./ToolsGuardrailsPage-DIVtsycj.js");return{ToolsGuardrailsPage:e}},__vite__mapDeps([19,1,2,3]))).ToolsGuardrailsPage})),Ut=i.lazy(async()=>({default:(await E(async()=>{const{UsagePage:e}=await import("./UsagePage-BNkia9eC.js");return{UsagePage:e}},__vite__mapDeps([20,1,2,3,4,15,16,5,6,7]))).UsagePage})),Bt=i.lazy(async()=>({default:(await E(async()=>{const{UsersPage:e}=await import("./UsersPage-C8N0rekv.js");return{UsersPage:e}},__vite__mapDeps([21,1,2,4,3,5,6,9,12]))).UsersPage}));function k(e){return r.jsx(i.Suspense,{fallback:r.jsx("div",{role:"status",children:"Loading page…"}),children:e})}function $t(){const{isAuthenticated:e}=Y();return e?r.jsx(Re,{children:r.jsx(Oe,{children:r.jsxs(w,{element:r.jsx(Tt,{}),children:[r.jsx(w,{index:!0,element:k(r.jsx(Dt,{}))}),r.jsx(w,{path:"providers",element:k(r.jsx(Mt,{}))}),r.jsx(w,{path:"keys",element:k(r.jsx(Rt,{}))}),r.jsx(w,{path:"users",element:k(r.jsx(Bt,{}))}),r.jsx(w,{path:"budgets",element:k(r.jsx(qt,{}))}),r.jsx(w,{path:"activity",element:k(r.jsx(At,{}))}),r.jsx(w,{path:"usage",element:k(r.jsx(Ut,{}))}),r.jsx(w,{path:"models",element:k(r.jsx(Ot,{}))}),r.jsx(w,{path:"aliases",element:k(r.jsx(It,{}))}),r.jsx(w,{path:"tools",element:k(r.jsx(Kt,{}))}),r.jsx(w,{path:"settings",element:k(r.jsx(Ft,{}))}),r.jsx(w,{path:"*",element:r.jsx(De,{to:"/",replace:!0})})]})})}):r.jsx(Lt,{})}function Qt({children:e}){const[t]=i.useState(()=>new Le({defaultOptions:{queries:{refetchOnWindowFocus:!1,retry:(n,s)=>s instanceof _&&(s.status===401||s.status===403)?!1:n<2}}}));return r.jsx(Ae,{client:t,children:r.jsx(We,{children:e})})}const ke=document.getElementById("root");if(!ke)throw new Error("Root element #root not found");Be.createRoot(ke).render(r.jsx(i.StrictMode,{children:r.jsx(Qt,{children:r.jsx($t,{})})}));export{Zt as $,Qr as A,pr as B,Zr as C,xt as D,yt as E,en as F,zr as G,rt as H,vt as I,$r as J,Yt as K,er as L,Wr as M,Vr as N,Yr as O,Xr as P,Br as Q,ht as R,Jr as S,Gr as T,rr as U,ar as V,or as W,nt as X,sr as Y,tr as Z,nr as _,Sr as a,Xt as a0,lr as a1,br as a2,jr as a3,wr as a4,fr as a5,ir as a6,hr as a7,xr as a8,yr as a9,Hr as aa,Or as ab,qr as ac,Dr as b,Mr as c,Ur as d,Fr as e,Kr as f,tn as g,Jt as h,ur as i,mr as j,dr as k,Cr as l,Tr as m,Lr as n,Ar as o,Rr as p,_r as q,Er as r,Pr as s,Nr as t,Ir as u,kr as v,Gt as w,vr as x,cr as y,gr as z}; +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ActivityPage-B6j_aVh9.js","assets/tanstack-query-1t81HyiD.js","assets/react-dgEcD0HR.js","assets/heroui-DZj66Arc.js","assets/tableSelection-CojkFPwd.js","assets/ConfirmDialog-CCJnWoSc.js","assets/DataTable-CrQq0sYf.js","assets/TablePagination-DlTV4kUZ.js","assets/AliasesPage-Chlhfn8l.js","assets/Field-DEJ6Wjg9.js","assets/BudgetsPage-BAJNRbKO.js","assets/KeysPage-D5EjvMLy.js","assets/ModelScopeControl-Dz7NaZdS.js","assets/ModelsPage-rWHJVsLp.js","assets/OverviewPage-qo0xwTL4.js","assets/charts-CuOShutR.js","assets/recharts-D7kuKp97.js","assets/ProvidersPage-BKCHydlE.js","assets/SettingsPage-D0bz-SUp.js","assets/ToolsGuardrailsPage-BVn9h5wq.js","assets/UsagePage-DOWOrs0n.js","assets/UsersPage-B7fcuwaB.js"])))=>i.map(i=>d[i]); +var _e=Object.defineProperty;var Te=(e,t,n)=>t in e?_e(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var ae=(e,t,n)=>Te(e,typeof t!="symbol"?t+"":t,n);import{u as y,j as r,a as p,b as f,k as J,Q as Le,c as Ae}from"./tanstack-query-1t81HyiD.js";import{d as Ie,r as i,N as de,O as qe,H as Re,e as Oe,f as w,h as De}from"./react-dgEcD0HR.js";import{C as M,L as me,I as fe,a as Me,b as Fe,B as C,d as O,c as N,T as Ke,e as Ue}from"./heroui-DZj66Arc.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))s(l);new MutationObserver(l=>{for(const a of l)if(a.type==="childList")for(const x of a.addedNodes)x.tagName==="LINK"&&x.rel==="modulepreload"&&s(x)}).observe(document,{childList:!0,subtree:!0});function n(l){const a={};return l.integrity&&(a.integrity=l.integrity),l.referrerPolicy&&(a.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?a.credentials="include":l.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function s(l){if(l.ep)return;l.ep=!0;const a=n(l);fetch(l.href,a)}})();var Be=Ie();const $e="modulepreload",Qe=function(e){return"/"+e},ie={},E=function(t,n,s){let l=Promise.resolve();if(n&&n.length>0){let x=function(u){return Promise.all(u.map(b=>Promise.resolve(b).then(h=>({status:"fulfilled",value:h}),h=>({status:"rejected",reason:h}))))};document.getElementsByTagName("link");const m=document.querySelector("meta[property=csp-nonce]"),v=(m==null?void 0:m.nonce)||(m==null?void 0:m.getAttribute("nonce"));l=x(n.map(u=>{if(u=Qe(u),u in ie)return;ie[u]=!0;const b=u.endsWith(".css"),h=b?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${u}"]${h}`))return;const d=document.createElement("link");if(d.rel=b?"stylesheet":$e,b||(d.as="script"),d.crossOrigin="",d.href=u,v&&d.setAttribute("nonce",v),document.head.appendChild(d),b)return new Promise((L,q)=>{d.addEventListener("load",L),d.addEventListener("error",()=>q(new Error(`Unable to preload CSS for ${u}`)))})}))}function a(x){const m=new Event("vite:preloadError",{cancelable:!0});if(m.payload=x,window.dispatchEvent(m),!m.defaultPrevented)throw x}return l.then(x=>{for(const m of x||[])m.status==="rejected"&&a(m.reason);return t().catch(a)})};class _ extends Error{constructor(n,s){super(s);ae(this,"status");this.name="ApiError",this.status=n}}let F=null;function oe(e){F=e}async function W(e){try{const t=await e.json();if(typeof t.detail=="string")return t.detail;if(t.detail!=null)return JSON.stringify(t.detail)}catch{}return e.statusText||`Request failed (${e.status})`}async function ze(e){let t;try{t=await fetch("/v1/auth/session",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({master_key:e})})}catch{throw new _(0,"Network error: could not reach the gateway.")}if(t.status===401||t.status===403)return!1;if(!t.ok)throw new _(t.status,await W(t));return!0}async function Ve(){try{await fetch("/v1/auth/session",{method:"DELETE"})}catch{}}async function o(e,t={}){const n=new Headers(t.headers);n.set("Accept","application/json"),t.body!=null&&!n.has("Content-Type")&&n.set("Content-Type","application/json");let s;try{s=await fetch(e,{...t,headers:n})}catch{throw new _(0,"Network error: could not reach the gateway.")}if(s.status===401||s.status===403)throw F==null||F(),new _(s.status,await W(s));if(!s.ok)throw new _(s.status,await W(s));if(s.status!==204)return await s.json()}const G="otari.dashboard.hasSession",he=i.createContext(null);function He(){try{return window.localStorage.getItem(G)==="1"}catch{return!1}}function We({children:e}){const t=y(),[n,s]=i.useState(He),l=i.useCallback(()=>{Ve(),s(!1),t.clear();try{window.localStorage.removeItem(G)}catch{}},[t]),a=i.useCallback(()=>{t.clear(),s(!0);try{window.localStorage.setItem(G,"1")}catch{}},[t]);i.useEffect(()=>(oe(l),()=>oe(null)),[l]);const x=i.useMemo(()=>({isAuthenticated:n,login:a,logout:l}),[n,a,l]);return r.jsx(he.Provider,{value:x,children:e})}function Y(){const e=i.useContext(he);if(!e)throw new Error("useAuth must be used within an AuthProvider");return e}function Ge(e){return e instanceof _&&e.status===0}function Je(){const e=y(),[t,n]=i.useState(!1);return i.useEffect(()=>{const s=e.getQueryCache(),l=()=>s.getAll().some(a=>a.state.status==="error"&&Ge(a.state.error));return n(l()),s.subscribe(()=>n(l()))},[e]),t}function Ye(){return Je()?r.jsxs("div",{role:"alert","aria-live":"assertive",className:"fixed right-4 bottom-4 z-50 flex max-w-sm items-start gap-2.5 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 shadow-lg",children:[r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"mt-0.5 h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M12 9v4M12 17h.01",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z",strokeLinejoin:"round"})]}),r.jsxs("span",{children:[r.jsx("strong",{className:"font-semibold",children:"Can’t reach the gateway."})," The backend isn’t responding; data won’t load or save until the connection is restored."]})]}):null}const T="models",U="pricing",xe="settings",ye="tool-settings",X="aliases",Z="discoverable",ee="providers",te="provider-health",ve="stored-providers",Xe="model-metadata",Ze="build",A="keys",I="budgets",ge="users",D="usage",et=6e4,le=60*6e4;function Gt(){return p({queryKey:[T],queryFn:()=>o("/v1/models"),staleTime:6e4})}function tt(){return p({queryKey:[Ze],queryFn:()=>o("/dashboard-build.json"),refetchInterval:et,refetchOnWindowFocus:!0,staleTime:0,retry:!1})}function Jt(){return p({queryKey:[Z],queryFn:()=>o("/v1/models/discoverable"),staleTime:5*6e4})}function Yt(){return p({queryKey:[ee],queryFn:()=>o("/v1/providers"),staleTime:5*6e4})}function Xt(){return p({queryKey:["provider-catalog"],queryFn:()=>o("/v1/providers/catalog"),staleTime:1/0})}function Zt(e){return p({queryKey:["provider-catalog",e],queryFn:()=>o(`/v1/providers/catalog/${encodeURIComponent(e)}`),enabled:e!=="",staleTime:1/0})}function er(){return p({queryKey:[te],queryFn:()=>o("/v1/providers/health"),staleTime:le,refetchInterval:le})}function tr(){const e=y();return f({mutationFn:()=>o("/v1/providers/health?refresh=true"),onSuccess:t=>e.setQueryData([te],t)})}function rr(){return p({queryKey:[ve],queryFn:()=>o("/v1/provider-credentials"),staleTime:6e4})}function B(e){e.invalidateQueries({queryKey:[ve]}),e.invalidateQueries({queryKey:[ee]}),e.invalidateQueries({queryKey:[T]}),e.invalidateQueries({queryKey:[Z]}),e.invalidateQueries({queryKey:[te]})}function nr(){const e=y();return f({mutationFn:t=>o("/v1/provider-credentials",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>B(e)})}function sr(){const e=y();return f({mutationFn:({instance:t,body:n})=>o(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>B(e)})}function ar(){const e=y();return f({mutationFn:t=>o(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>B(e)})}function ir(){const e=y();return f({mutationFn:()=>o("/v1/provider-credentials/reencrypt",{method:"POST"}),onSuccess:()=>B(e)})}function or(){return f({mutationFn:e=>o(`/v1/provider-credentials/${encodeURIComponent(e)}/test`,{method:"POST"})})}function lr(){return f({mutationFn:e=>o("/v1/provider-credentials/test",{method:"POST",body:JSON.stringify(e)})})}function cr(){return p({queryKey:[Xe],queryFn:()=>o("/v1/models/metadata"),staleTime:10*6e4})}function ur(){return p({queryKey:[X],queryFn:()=>o("/v1/aliases"),staleTime:6e4})}function dr(){const e=y();return f({mutationFn:t=>o("/v1/aliases",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[X]}),e.invalidateQueries({queryKey:[T]})}})}function mr(){const e=y();return f({mutationFn:t=>o(`/v1/aliases/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{e.invalidateQueries({queryKey:[X]}),e.invalidateQueries({queryKey:[T]})}})}function rt(){return p({queryKey:[xe],queryFn:()=>o("/v1/settings"),staleTime:6e4})}function nt(){const e=y();return f({mutationFn:t=>o("/v1/settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([xe],t),e.invalidateQueries({queryKey:[T]}),e.invalidateQueries({queryKey:[Z]})}})}function fr(){return f({mutationFn:()=>o("/v1/settings/master-key/rotate",{method:"POST"})})}function hr(){return p({queryKey:[ye],queryFn:()=>o("/v1/tool-settings"),staleTime:6e4})}function xr(){const e=y();return f({mutationFn:t=>o("/v1/tool-settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([ye],t)}})}function yr(){return f({mutationFn:({service:e,url:t})=>o(`/v1/tool-settings/${encodeURIComponent(e)}/test`,{method:"POST",body:JSON.stringify({url:t})})})}const $=1e3,st=100;async function at(){const e=[];for(let t=0;to("/v1/pricing",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[U]}),e.invalidateQueries({queryKey:[T]})}})}function pr(){const e=y();return f({mutationFn:t=>o(`/v1/pricing/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{e.invalidateQueries({queryKey:[U]}),e.invalidateQueries({queryKey:[T]})}})}function br(){return f({mutationFn:()=>o("/v1/pricing/refresh",{method:"POST"})})}function jr(){const e=y();return f({mutationFn:()=>o("/v1/pricing/refresh/confirm",{method:"POST"}),onSuccess:()=>{e.invalidateQueries({queryKey:[U]}),e.invalidateQueries({queryKey:[T]}),e.invalidateQueries({queryKey:[ee]})}})}function wr(){return f({mutationFn:()=>o("/v1/pricing/refresh/reject",{method:"POST"})})}const Q=1e3,it=100;async function ot(){const e=[];for(let t=0;to("/v1/keys",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function Er(){const e=y();return f({mutationFn:({id:t,body:n})=>o(`/v1/keys/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function Pr(){const e=y();return f({mutationFn:t=>o(`/v1/keys/${encodeURIComponent(t)}/rotate`,{method:"POST"}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function Nr(){const e=y();return f({mutationFn:t=>o(`/v1/keys/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}const z=1e3,lt=100;async function ct(){const e=[];for(let t=0;to(`/v1/budgets/${encodeURIComponent(e)}/reset-logs`),enabled:e!==null,staleTime:6e4})}function Tr(){const e=y();return f({mutationFn:t=>o("/v1/budgets",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[I]})})}function Lr(){const e=y();return f({mutationFn:({id:t,body:n})=>o(`/v1/budgets/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>void e.invalidateQueries({queryKey:[I]})})}function Ar(){const e=y();return f({mutationFn:t=>o(`/v1/budgets/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[I]})})}const V=1e3,ut=100;async function dt(){const e=[];for(let t=0;to("/v1/users",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>re(e)})}function Rr(){const e=y();return f({mutationFn:({id:t,body:n})=>o(`/v1/users/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(n)}),onSuccess:()=>re(e)})}function Or(){const e=y();return f({mutationFn:t=>o(`/v1/users/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{re(e),e.invalidateQueries({queryKey:[A]})}})}function ne(e){const t=new URLSearchParams;return e.start_date&&t.set("start_date",e.start_date),e.end_date&&t.set("end_date",e.end_date),e.status&&t.set("status",e.status),e.model&&t.set("model",e.model),e.endpoint&&t.set("endpoint",e.endpoint),e.user_id&&t.set("user_id",e.user_id),e.api_key_id&&t.set("api_key_id",e.api_key_id),e.source&&t.set("source",e.source),e.priced!==void 0&&t.set("priced",String(e.priced)),e.counts_toward_budget!==void 0&&t.set("counts_toward_budget",String(e.counts_toward_budget)),t}function Dr(e,t,n){return p({queryKey:[D,"list",e,t,n],queryFn:()=>{const s=ne(e);return s.set("skip",String(t*n)),s.set("limit",String(n)),o(`/v1/usage?${s.toString()}`)},placeholderData:J,staleTime:1e4})}function Mr(e,t=!0){return p({queryKey:[D,"count",e],queryFn:()=>o(`/v1/usage/count?${ne(e).toString()}`),enabled:t,placeholderData:J,staleTime:1e4})}function Fr(){const e=y();return f({mutationFn:t=>o("/v1/usage",{method:"DELETE",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[D]})}})}function Kr(){const e=y();return f({mutationFn:t=>o("/v1/usage/set-price",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[D]})}})}function Ur(e,t,n=!0){return p({queryKey:[D,"summary",e,t],queryFn:()=>{const s=ne(e);return s.set("bucket",t),o(`/v1/usage/summary?${s.toString()}`)},enabled:n,placeholderData:J,staleTime:3e4})}function Br(e){return e==null?"0":new Intl.NumberFormat("en-US").format(e)}function $r(e){if(e==null)return"$0.00";const t=e!==0&&Math.abs(e)<.01?4:2;return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:t}).format(e)}function Qr(e){if(e==null)return"—";if(e>=1e6){const t=e/1e6;return`${Number.isInteger(t)?t:t.toFixed(1)}M`}if(e>=1e3){const t=Math.round(e/1e3);return t>=1e3?"1M":`${t}K`}return String(e)}const mt=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function zr(e){if(!e)return"—";const t=/^(\d{4})-(\d{2})/.exec(e);if(!t)return e;const n=Number(t[2])-1;return n<0||n>11?t[1]:`${mt[n]} ${t[1]}`}const ft=new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:2});function Vr(e){return ft.format(e)}function Hr(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function ht(e){return`${(e*100).toFixed(1)}%`}function Wr(e,t){return t===void 0||t===0?null:(e-t)/t}function Gr(e,t=Date.now()){if(!e)return"never";const n=new Date(e);if(Number.isNaN(n.getTime()))return e;const s=Math.round((t-n.getTime())/1e3),l=s<0,a=Math.abs(s),x=[["second",60],["minute",60],["hour",24],["day",30],["month",12],["year",Number.POSITIVE_INFINITY]];let m=a,v="second";for(const[b,h]of x){if(v=b,m0?"▲":e<0?"▼":"•";return r.jsxs("span",{className:"text-[var(--otari-muted)]",children:[t," ",ht(Math.abs(e))," vs prev"]})}function xt(e){return e instanceof _||e instanceof Error?e.message:"Something went wrong."}function yt({error:e}){return e?r.jsx("div",{role:"alert",className:"rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700",children:xt(e)}):null}function vt({tone:e="info",children:t}){const n=e==="warning"?"border-amber-200 bg-amber-50 text-amber-800":"border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return r.jsx("div",{className:`rounded-lg border px-4 py-3 text-sm ${n}`,children:t})}function Xr({title:e,description:t,action:n}){return r.jsxs("div",{className:"flex flex-col gap-3",children:[r.jsxs("div",{children:[r.jsx("h1",{className:"text-xl font-semibold text-[var(--otari-ink)]",children:e}),t?r.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t}):null]}),n?r.jsx("div",{className:"flex flex-wrap gap-2",children:n}):null]})}function Zr({children:e,confirmLabel:t,onConfirm:n,isPending:s}){const[l,a]=i.useState(!1);return l?r.jsxs("span",{className:"inline-flex items-center gap-1",children:[r.jsx(C,{size:"sm",variant:"danger",isDisabled:s,onPress:n,children:t}),r.jsx(C,{size:"sm",variant:"ghost",isDisabled:s,onPress:()=>a(!1),children:"Cancel"})]}):r.jsx(C,{size:"sm",variant:"danger-soft",onPress:()=>a(!0),children:e})}const gt="rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none";function en({id:e,label:t,ariaLabel:n,value:s,onChange:l,options:a,children:x,disabled:m}){const v=i.useId(),u=e??(t?v:void 0),b=r.jsx("select",{id:u,"aria-label":t?void 0:n,value:s,disabled:m,onChange:h=>l(h.target.value),className:gt,children:a?a.map(h=>r.jsx("option",{value:h.value,children:h.label},h.value)):x});return t?r.jsxs("div",{className:"flex flex-col gap-1",children:[r.jsx("label",{htmlFor:u,className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),b]}):b}function tn({label:e,value:t,onChange:n,options:s,placeholder:l,maxVisible:a=50,allowsCustom:x=!1}){const m=d=>{var L;return((L=s.find(q=>q.value===d))==null?void 0:L.label)??d},[v,u]=i.useState(()=>m(t));i.useEffect(()=>{u(m(t))},[t]);const b=v.trim().toLowerCase(),h=s.filter(d=>!b||d.value.toLowerCase().includes(b)||d.label.toLowerCase().includes(b)).slice(0,a);return r.jsxs(M.Root,{allowsEmptyCollection:!0,allowsCustomValue:x,menuTrigger:"focus",inputValue:v,onInputChange:d=>{u(d),x?n(d.trim()):d.trim()===""&&n("")},onSelectionChange:d=>{d!=null&&n(String(d))},className:"flex flex-col gap-1",children:[r.jsx(me,{className:"text-xs font-medium text-[var(--otari-muted)]",children:e}),r.jsxs(M.InputGroup,{children:[r.jsx(fe,{placeholder:l,autoComplete:"off",onFocus:d=>d.currentTarget.select()}),r.jsx(M.Trigger,{})]}),r.jsx(M.Popover,{children:r.jsx(Me,{items:h,className:"max-h-72 overflow-auto",children:d=>r.jsx(Fe,{id:d.value,textValue:d.label,children:d.label})})})]})}function pt(){var a;const e=rt(),t=nt(),[n,s]=i.useState(!1);return!(((a=e.data)==null?void 0:a.require_pricing)===!0&&e.data.default_pricing===!1)||n?null:r.jsx("div",{className:"shrink-0 px-6 pt-3",children:r.jsx(vt,{tone:"warning",children:r.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[r.jsxs("span",{children:["Requests are rejected until pricing is set (",r.jsx("code",{children:"require_pricing"})," is on). Enable default pricing to meter new models with public rates right away."]}),r.jsxs("span",{className:"flex items-center gap-2",children:[r.jsx(C,{size:"sm",variant:"primary",isDisabled:t.isPending,onPress:()=>t.mutate({default_pricing:!0}),children:t.isPending?"Enabling…":"Enable default pricing"}),r.jsx(C,{size:"sm",variant:"ghost",onPress:()=>s(!0),children:"Dismiss"})]})]})})})}function bt(){const{data:e}=tt(),t=i.useRef(null);return e&&t.current===null&&(t.current=e.build),e!=null&&t.current!=null&&e.build!==t.current}function jt(){const e=bt(),[t,n]=i.useState(!1);return!e||t?null:r.jsx("div",{className:"pointer-events-none absolute inset-x-0 top-0 z-50 flex justify-center",children:r.jsxs("div",{role:"status",className:"pointer-events-auto mt-1.5 flex items-center gap-3 rounded-full border border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] py-1.5 pr-1.5 pl-4 text-sm text-[var(--otari-brand-dark)] shadow-md",children:[r.jsxs("span",{children:[r.jsx("strong",{className:"font-semibold",children:"An update is available."})," Reloading keeps you signed in."]}),r.jsx(C,{size:"sm",variant:"primary",onPress:()=>window.location.reload(),children:"Update now"}),r.jsx(C,{size:"sm",variant:"ghost",onPress:()=>n(!0),children:"Later"})]})})}const pe=200,be=480,H=240,wt=60,je="otari.dashboard.sidebarWidth",we="otari.dashboard.sidebarCollapsed",ue=16,Se="(max-width: 767px)",K=e=>Math.min(be,Math.max(pe,e));function St(){return typeof window>"u"||typeof window.matchMedia!="function"?!1:window.matchMedia(Se).matches}const kt='a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])';function Et(e){return e?Array.from(e.querySelectorAll(kt)).filter(t=>t.offsetParent!==null||t===document.activeElement):[]}function Pt(){if(typeof window>"u")return H;try{const e=window.localStorage.getItem(je),t=e?Number.parseInt(e,10):Number.NaN;return Number.isNaN(t)?H:K(t)}catch{return H}}function Nt(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(we)==="1"}catch{return!1}}const Ct=[{key:"home"},{key:"observability",label:"Observability"},{key:"catalog",label:"Catalog"},{key:"access",label:"Access"},{key:"system"}],_t=[{to:"/",section:"home",label:"Overview",end:!0,icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("rect",{x:"3.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"13.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"3.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"13.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"})]})},{to:"/activity",section:"observability",label:"Activity",icon:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:r.jsx("path",{d:"M3 12h4l2.5-6 4 12 2.5-6H21",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/usage",section:"observability",label:"Usage",icon:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:r.jsx("path",{d:"M4 20V10M10 20V4M16 20v-7M22 20H2",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/providers",section:"catalog",label:"Providers",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("rect",{x:"3.5",y:"4.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),r.jsx("rect",{x:"3.5",y:"13.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),r.jsx("path",{d:"M7 7.5h.01M7 16.5h.01",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/users",section:"access",label:"Users",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("circle",{cx:"9",cy:"8",r:"3.2",strokeLinejoin:"round"}),r.jsx("path",{d:"M3.5 19a5.5 5.5 0 0 1 11 0",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M16 5.2a3.2 3.2 0 0 1 0 5.6M17.5 19a5.5 5.5 0 0 0-3-4.9",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/keys",section:"access",label:"API keys",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("circle",{cx:"7.5",cy:"15.5",r:"3.5"}),r.jsx("path",{d:"M10 13l7-7M14 5l3 3M16.5 7.5l2-2",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/budgets",section:"access",label:"Budgets",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M3 7.5A1.5 1.5 0 0 1 4.5 6H18a1.5 1.5 0 0 1 1.5 1.5V9",strokeLinejoin:"round"}),r.jsx("rect",{x:"3",y:"7.5",width:"18",height:"12",rx:"1.5",strokeLinejoin:"round"}),r.jsx("path",{d:"M16 13.5h.01",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M21 12v3h-3.5a1.5 1.5 0 0 1 0-3H21z",strokeLinejoin:"round"})]})},{to:"/models",section:"catalog",label:"Models",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z",strokeLinejoin:"round"}),r.jsx("path",{d:"M12 12l8-4.5M12 12v9M12 12L4 7.5",strokeLinejoin:"round"})]})},{to:"/aliases",section:"catalog",label:"Aliases",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M20.6 13.4L13.4 20.6a2 2 0 0 1-2.8 0l-7-7A2 2 0 0 1 3 12.2V5a2 2 0 0 1 2-2h7.2a2 2 0 0 1 1.4.6l7 7a2 2 0 0 1 0 2.8z",strokeLinejoin:"round"}),r.jsx("circle",{cx:"7.5",cy:"7.5",r:"1.5"})]})},{to:"/tools",section:"system",label:"Tools & Guardrails",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("path",{d:"M14.7 6.3a4 4 0 0 1 5 5l-8.4 8.4a2 2 0 0 1-2.8 0l-2.2-2.2a2 2 0 0 1 0-2.8z",strokeLinejoin:"round"}),r.jsx("path",{d:"M12 9 5 16",strokeLinecap:"round"})]})},{to:"/settings",section:"system",label:"Settings",icon:r.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[r.jsx("circle",{cx:"12",cy:"12",r:"3"}),r.jsx("path",{d:"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z",strokeLinejoin:"round"})]})}];function Tt(){const{logout:e}=Y(),t=i.useRef(null),n=i.useRef(null),[s,l]=i.useState(Pt),[a,x]=i.useState(Nt),[m,v]=i.useState(!1),[u,b]=i.useState(St),[h,d]=i.useState(!1);i.useEffect(()=>{if(typeof window>"u"||typeof window.matchMedia!="function")return;const c=window.matchMedia(Se),g=j=>{b(j.matches),j.matches||d(!1)};return typeof c.addEventListener=="function"?(c.addEventListener("change",g),()=>c.removeEventListener("change",g)):(c.addListener(g),()=>c.removeListener(g))},[]),i.useEffect(()=>{if(!h)return;const c=g=>{g.key==="Escape"&&d(!1)};return window.addEventListener("keydown",c),()=>window.removeEventListener("keydown",c)},[h]),i.useEffect(()=>{var c,g,j;u&&(h?(c=t.current)==null||c.focus():(g=t.current)!=null&&g.contains(document.activeElement)&&((j=n.current)==null||j.focus()))},[u,h]);const L=i.useCallback(c=>{if(c.key!=="Tab")return;const g=Et(t.current);if(g.length===0)return;const j=g[0],S=g[g.length-1],R=document.activeElement;c.shiftKey&&(R===j||R===t.current)?(c.preventDefault(),S.focus()):!c.shiftKey&&R===S&&(c.preventDefault(),j.focus())},[]);i.useEffect(()=>{const c=window.setTimeout(()=>{try{window.localStorage.setItem(je,String(Math.round(s)))}catch{}},200);return()=>window.clearTimeout(c)},[s]),i.useEffect(()=>{try{window.localStorage.setItem(we,a?"1":"0")}catch{}},[a]);const q=i.useCallback(c=>{c.preventDefault(),c.currentTarget.setPointerCapture(c.pointerId),v(!0)},[]),Ee=i.useCallback(c=>{var j;if(!c.currentTarget.hasPointerCapture(c.pointerId))return;const g=((j=t.current)==null?void 0:j.getBoundingClientRect().left)??0;l(K(c.clientX-g))},[]),Pe=i.useCallback(c=>{c.currentTarget.hasPointerCapture(c.pointerId)&&c.currentTarget.releasePointerCapture(c.pointerId),v(!1)},[]),Ne=i.useCallback(c=>{c.key==="ArrowLeft"?(c.preventDefault(),l(g=>K(g-ue))):c.key==="ArrowRight"&&(c.preventDefault(),l(g=>K(g+ue)))},[]),Ce=a?wt:s,P=u?!1:a,se=u&&h?!0:void 0;return r.jsxs("div",{className:N("relative flex h-full flex-col overflow-hidden",m&&"cursor-col-resize select-none"),children:[r.jsxs("header",{inert:se,className:"flex shrink-0 items-center justify-between border-b border-[var(--otari-line)] bg-[var(--otari-surface)] px-5 py-3",children:[r.jsxs("div",{className:"flex items-center gap-2.5",children:[r.jsx("button",{type:"button",ref:n,onClick:()=>d(c=>!c),"aria-label":h?"Close navigation":"Open navigation","aria-expanded":h,"aria-controls":"app-sidebar",className:"-ml-1 flex h-8 w-8 items-center justify-center rounded-lg text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)] md:hidden",children:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5",children:r.jsx("path",{d:"M4 6h16M4 12h16M4 18h16",strokeLinecap:"round",strokeLinejoin:"round"})})}),r.jsx("img",{src:"/favicon.svg",alt:"",className:"h-7 w-7 shrink-0"}),r.jsx("span",{className:"text-base font-semibold text-[var(--otari-ink)]",children:"Otari"})]}),r.jsx(C,{size:"sm",variant:"outline",onPress:e,"aria-label":"Sign out",children:"Sign out"})]}),r.jsx(jt,{}),r.jsx(Ye,{}),r.jsx(pt,{}),r.jsxs("div",{className:"flex min-h-0 flex-1",children:[u&&h?r.jsx("div",{"aria-hidden":"true",onClick:()=>d(!1),className:"fixed inset-0 z-30 bg-black/40 md:hidden"}):null,r.jsxs("aside",{ref:t,id:"app-sidebar",role:u?"dialog":void 0,"aria-modal":u&&h?!0:void 0,"aria-label":u?"Navigation":void 0,tabIndex:u?-1:void 0,inert:u&&!h?!0:void 0,onKeyDown:u&&h?L:void 0,style:u?void 0:{width:Ce},className:N("flex flex-col border-r border-[var(--otari-line)] bg-[var(--otari-surface)] focus:outline-none",u?N("fixed inset-y-0 left-0 z-40 w-[17rem] shadow-xl transition-transform duration-200",h?"translate-x-0":"-translate-x-full"):N("relative shrink-0",!m&&"transition-[width] duration-150")),children:[r.jsx("button",{type:"button",onClick:()=>x(c=>!c),"aria-label":a?"Expand sidebar":"Collapse sidebar","aria-pressed":a,title:a?"Expand sidebar":"Collapse sidebar",className:"absolute -right-3 top-4 z-30 hidden h-6 w-6 items-center justify-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-surface)] text-[var(--otari-muted)] shadow-sm transition-colors hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand-dark)] md:flex",children:r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",className:N("h-3.5 w-3.5 transition-transform",a&&"rotate-180"),children:r.jsx("path",{d:"M15 6l-6 6 6 6",strokeLinecap:"round",strokeLinejoin:"round"})})}),r.jsx("nav",{className:N("flex flex-col py-4",P?"px-2":"px-3"),children:Ct.map((c,g)=>{const j=_t.filter(S=>S.section===c.key);return j.length===0?null:r.jsxs("div",{className:g>0?"mt-4":void 0,children:[!P&&c.label?r.jsx("div",{className:"px-3 pb-1 text-[11px] font-semibold tracking-wider text-[var(--otari-muted)] uppercase",children:c.label}):null,g>0&&(P||!c.label)?r.jsx("div",{className:"mx-1 mb-2 border-t border-[var(--otari-line)]"}):null,r.jsx("div",{className:"flex flex-col gap-1",children:j.map(S=>r.jsxs(de,{to:S.to,end:S.end,onClick:()=>d(!1),"aria-label":P?S.label:void 0,title:P?S.label:void 0,className:({isActive:R})=>N("flex items-center rounded-lg py-2 text-sm font-medium transition-colors",P?"justify-center px-0":"gap-3 px-3",R?"bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]":"text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]"),children:[S.icon,P?null:S.label]},S.to))})]},c.key)})}),r.jsxs("a",{href:"https://otari.ai",target:"_blank",rel:"noreferrer",title:"otari.ai — the hosted Otari gateway",className:N("mt-auto mb-3 flex items-center rounded-lg py-2 text-xs font-medium text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-brand-dark)]",P?"mx-2 justify-center px-0":"mx-3 gap-2 px-3"),children:[r.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4 shrink-0",children:r.jsx("path",{d:"M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z",strokeLinejoin:"round"})}),P?null:r.jsxs("span",{className:"flex-1",children:["otari.ai ",r.jsx("span",{"aria-hidden":!0,children:"↗"})]})]}),a||u?null:r.jsx("div",{role:"separator","aria-orientation":"vertical","aria-label":"Resize sidebar","aria-valuenow":Math.round(s),"aria-valuemin":pe,"aria-valuemax":be,tabIndex:0,onPointerDown:q,onPointerMove:Ee,onPointerUp:Pe,onKeyDown:Ne,className:N("absolute top-0 right-0 z-10 h-full w-1.5 cursor-col-resize touch-none transition-colors","hover:bg-[var(--otari-brand)] focus-visible:bg-[var(--otari-brand)] focus:outline-none",m?"bg-[var(--otari-brand)]":"bg-transparent")})]}),r.jsx("main",{inert:se,className:"flex-1 overflow-y-auto",children:r.jsx("div",{className:"mx-auto flex max-w-[1800px] flex-col gap-6 px-4 py-5 md:px-6 md:py-6",children:r.jsx(qe,{})})})]})]})}function Lt(){const{login:e}=Y(),[t,n]=i.useState(""),[s,l]=i.useState(null),[a,x]=i.useState(!1),m=async()=>{const v=t.trim();if(!(!v||a)){x(!0),l(null);try{await ze(v)?e():l(new Error("Invalid master key."))}catch(u){l(u)}finally{x(!1)}}};return r.jsx("div",{className:"flex min-h-full items-center justify-center p-6",children:r.jsx(O,{className:"w-full max-w-md",children:r.jsxs(O.Content,{className:"flex flex-col gap-5 p-7",children:[r.jsxs("div",{className:"flex flex-col items-center gap-3 text-center",children:[r.jsx("img",{src:"/favicon.svg",alt:"Otari",className:"h-12 w-12"}),r.jsxs("div",{children:[r.jsx("h1",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Otari Dashboard"}),r.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Sign in with your master key to browse models, set pricing, and manage settings."})]})]}),r.jsxs("form",{className:"flex flex-col gap-4",onSubmit:v=>{v.preventDefault(),m()},children:[r.jsxs(Ke,{value:t,onChange:v=>{n(v),s&&l(null)},type:"password",isRequired:!0,className:"flex flex-col gap-1",children:[r.jsx(me,{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Master key"}),r.jsx(fe,{placeholder:"otari-mk-… or your master key",autoFocus:!0,autoComplete:"off"})]}),r.jsxs("details",{className:"text-xs text-[var(--otari-muted)]",children:[r.jsx("summary",{className:"cursor-pointer font-medium text-[var(--otari-brand-dark)]",children:"First run? Where to find your key"}),r.jsxs("p",{className:"mt-2 leading-relaxed",children:["If you did not set ",r.jsx("code",{children:"OTARI_MASTER_KEY"}),", Otari generated one and printed it to the server logs on startup. Look for the line ",r.jsx("code",{children:"Your master key:"})," (for example, run"," ",r.jsx("code",{children:"docker logs "}),") and paste it above."]})]}),r.jsx(yt,{error:s}),r.jsx(C,{type:"submit",variant:"primary",fullWidth:!0,isDisabled:!t.trim()||a,children:a?"Signing in…":"Sign in"})]}),r.jsx("p",{className:"text-center text-xs text-[var(--otari-muted)]",children:"The key is sent once to this gateway and exchanged for a session cookie; it is never stored in the browser."}),r.jsx("div",{className:"border-t border-[var(--otari-line)] pt-4 text-center",children:r.jsx(Ue,{href:"/welcome",className:"text-sm font-medium text-[var(--otari-brand-dark)]",children:"New to Otari? Open the welcome guide"})})]})})})}const At=i.lazy(async()=>({default:(await E(async()=>{const{ActivityPage:e}=await import("./ActivityPage-B6j_aVh9.js");return{ActivityPage:e}},__vite__mapDeps([0,1,2,3,4,5,6,7]))).ActivityPage})),It=i.lazy(async()=>({default:(await E(async()=>{const{AliasesPage:e}=await import("./AliasesPage-Chlhfn8l.js");return{AliasesPage:e}},__vite__mapDeps([8,1,2,4,3,5,6,9]))).AliasesPage})),qt=i.lazy(async()=>({default:(await E(async()=>{const{BudgetsPage:e}=await import("./BudgetsPage-BAJNRbKO.js");return{BudgetsPage:e}},__vite__mapDeps([10,1,2,4,3,5,6,9]))).BudgetsPage})),Rt=i.lazy(async()=>({default:(await E(async()=>{const{KeysPage:e}=await import("./KeysPage-D5EjvMLy.js");return{KeysPage:e}},__vite__mapDeps([11,1,2,4,3,5,6,9,12]))).KeysPage})),Ot=i.lazy(async()=>({default:(await E(async()=>{const{ModelsPage:e}=await import("./ModelsPage-rWHJVsLp.js");return{ModelsPage:e}},__vite__mapDeps([13,1,2,4,3,6,7]))).ModelsPage})),Dt=i.lazy(async()=>({default:(await E(async()=>{const{OverviewIndex:e}=await import("./OverviewPage-qo0xwTL4.js");return{OverviewIndex:e}},__vite__mapDeps([14,1,2,15,16,3,6]))).OverviewIndex})),Mt=i.lazy(async()=>({default:(await E(async()=>{const{ProvidersPage:e}=await import("./ProvidersPage-BKCHydlE.js");return{ProvidersPage:e}},__vite__mapDeps([17,1,2,9,3,6]))).ProvidersPage})),Ft=i.lazy(async()=>({default:(await E(async()=>{const{SettingsPage:e}=await import("./SettingsPage-D0bz-SUp.js");return{SettingsPage:e}},__vite__mapDeps([18,1,2,3]))).SettingsPage})),Kt=i.lazy(async()=>({default:(await E(async()=>{const{ToolsGuardrailsPage:e}=await import("./ToolsGuardrailsPage-BVn9h5wq.js");return{ToolsGuardrailsPage:e}},__vite__mapDeps([19,1,2,3]))).ToolsGuardrailsPage})),Ut=i.lazy(async()=>({default:(await E(async()=>{const{UsagePage:e}=await import("./UsagePage-DOWOrs0n.js");return{UsagePage:e}},__vite__mapDeps([20,1,2,3,4,15,16,5,6,7]))).UsagePage})),Bt=i.lazy(async()=>({default:(await E(async()=>{const{UsersPage:e}=await import("./UsersPage-B7fcuwaB.js");return{UsersPage:e}},__vite__mapDeps([21,1,2,4,3,5,6,9,12]))).UsersPage}));function k(e){return r.jsx(i.Suspense,{fallback:r.jsx("div",{role:"status",children:"Loading page…"}),children:e})}function $t(){const{isAuthenticated:e}=Y();return e?r.jsx(Re,{children:r.jsx(Oe,{children:r.jsxs(w,{element:r.jsx(Tt,{}),children:[r.jsx(w,{index:!0,element:k(r.jsx(Dt,{}))}),r.jsx(w,{path:"providers",element:k(r.jsx(Mt,{}))}),r.jsx(w,{path:"keys",element:k(r.jsx(Rt,{}))}),r.jsx(w,{path:"users",element:k(r.jsx(Bt,{}))}),r.jsx(w,{path:"budgets",element:k(r.jsx(qt,{}))}),r.jsx(w,{path:"activity",element:k(r.jsx(At,{}))}),r.jsx(w,{path:"usage",element:k(r.jsx(Ut,{}))}),r.jsx(w,{path:"models",element:k(r.jsx(Ot,{}))}),r.jsx(w,{path:"aliases",element:k(r.jsx(It,{}))}),r.jsx(w,{path:"tools",element:k(r.jsx(Kt,{}))}),r.jsx(w,{path:"settings",element:k(r.jsx(Ft,{}))}),r.jsx(w,{path:"*",element:r.jsx(De,{to:"/",replace:!0})})]})})}):r.jsx(Lt,{})}function Qt({children:e}){const[t]=i.useState(()=>new Le({defaultOptions:{queries:{refetchOnWindowFocus:!1,retry:(n,s)=>s instanceof _&&(s.status===401||s.status===403)?!1:n<2}}}));return r.jsx(Ae,{client:t,children:r.jsx(We,{children:e})})}const ke=document.getElementById("root");if(!ke)throw new Error("Root element #root not found");Be.createRoot(ke).render(r.jsx(i.StrictMode,{children:r.jsx(Qt,{children:r.jsx($t,{})})}));export{Zt as $,Qr as A,pr as B,Zr as C,xt as D,yt as E,en as F,zr as G,rt as H,vt as I,$r as J,Yt as K,er as L,Wr as M,Vr as N,Yr as O,Xr as P,Br as Q,ht as R,Jr as S,Gr as T,rr as U,ar as V,or as W,nt as X,sr as Y,tr as Z,nr as _,Sr as a,Xt as a0,lr as a1,br as a2,jr as a3,wr as a4,fr as a5,ir as a6,hr as a7,xr as a8,yr as a9,Hr as aa,Or as ab,qr as ac,Dr as b,Mr as c,Ur as d,Fr as e,Kr as f,tn as g,Jt as h,ur as i,mr as j,dr as k,Cr as l,Tr as m,Lr as n,Ar as o,Rr as p,_r as q,Er as r,Pr as s,Nr as t,Ir as u,kr as v,Gt as w,vr as x,cr as y,gr as z}; diff --git a/src/gateway/static/dashboard/index.html b/src/gateway/static/dashboard/index.html index 5164c49f..0f5572dc 100644 --- a/src/gateway/static/dashboard/index.html +++ b/src/gateway/static/dashboard/index.html @@ -6,7 +6,7 @@ Otari Dashboard - + diff --git a/web/src/pages/UsagePage.tsx b/web/src/pages/UsagePage.tsx index b9a0e0e7..9f7edc1f 100644 --- a/web/src/pages/UsagePage.tsx +++ b/web/src/pages/UsagePage.tsx @@ -649,8 +649,11 @@ export function UsagePage() { ) : ( <> - {/* Tiles */} -
+ {/* Tiles. A responsive grid, matching OverviewPage: StatCard is + flex-1/min-w-0 (sized by its track), so a wrapping flex row lets + all seven tiles shrink onto one unusable line on phones instead + of wrapping. */} +