@cartanova/qgrid-cli 2.6.1 → 2.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.ko.md +2 -0
  2. package/README.md +2 -0
  3. package/bundle/dist/application/qgrid/oauth.js +21 -3
  4. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +40 -6
  5. package/bundle/dist/application/qgrid/qgrid.frame.js +47 -19
  6. package/bundle/dist/application/qgrid/qgrid.types.js +13 -3
  7. package/bundle/dist/application/qgrid/token-death.js +77 -0
  8. package/bundle/dist/application/queries.generated.js +3 -2
  9. package/bundle/dist/application/request-log/request-log.model.js +105 -31
  10. package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
  11. package/bundle/dist/application/sonamu.generated.js +60 -5
  12. package/bundle/dist/application/sonamu.generated.sso.js +18 -2
  13. package/bundle/dist/application/token/token.model.js +53 -2
  14. package/bundle/dist/i18n/en.js +3 -1
  15. package/bundle/dist/i18n/ko.js +3 -1
  16. package/bundle/dist/i18n/sd.generated.js +18 -1
  17. package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +49 -0
  18. package/bundle/dist/migrations/20260807145144_alter_request_logs_alter1.js +13 -0
  19. package/bundle/dist/migrations/20260807164057_alter_request_logs_alter1.js +13 -0
  20. package/bundle/dist/server-error-handler.js +5 -3
  21. package/bundle/dist/sonamu.config.js +5 -1
  22. package/bundle/dist/utils/providers/openai/openai-refresh.js +12 -1
  23. package/bundle/dist/utils/slack-notify.js +65 -0
  24. package/bundle/src/application/qgrid/oauth.ts +20 -1
  25. package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +15 -3
  26. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +30 -2
  27. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +44 -6
  28. package/bundle/src/application/qgrid/qgrid.frame.ts +67 -47
  29. package/bundle/src/application/qgrid/qgrid.types.ts +10 -0
  30. package/bundle/src/application/qgrid/token-death.test.ts +150 -0
  31. package/bundle/src/application/qgrid/token-death.ts +96 -0
  32. package/bundle/src/application/queries.generated.ts +5 -2
  33. package/bundle/src/application/request-log/request-log.entity.json +46 -7
  34. package/bundle/src/application/request-log/request-log.model.test.ts +153 -42
  35. package/bundle/src/application/request-log/request-log.model.ts +136 -50
  36. package/bundle/src/application/sonamu.generated.http +19 -1
  37. package/bundle/src/application/sonamu.generated.sso.ts +16 -0
  38. package/bundle/src/application/sonamu.generated.ts +58 -3
  39. package/bundle/src/application/token/token.model.test.ts +49 -0
  40. package/bundle/src/application/token/token.model.ts +75 -0
  41. package/bundle/src/i18n/en.ts +3 -0
  42. package/bundle/src/i18n/ko.ts +3 -0
  43. package/bundle/src/i18n/sd.generated.ts +17 -0
  44. package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +110 -0
  45. package/bundle/src/migrations/20260807145144_alter_request_logs_alter1.ts +13 -0
  46. package/bundle/src/migrations/20260807164057_alter_request_logs_alter1.ts +13 -0
  47. package/bundle/src/server-error-handler.test.ts +58 -0
  48. package/bundle/src/server-error-handler.ts +16 -5
  49. package/bundle/src/sonamu.config.ts +6 -0
  50. package/bundle/src/utils/providers/openai/openai-refresh.ts +17 -0
  51. package/bundle/src/utils/slack-notify.test.ts +97 -0
  52. package/bundle/src/utils/slack-notify.ts +74 -0
  53. package/bundle/web-dist/client/assets/index-BGQSGMcK.css +1 -0
  54. package/bundle/web-dist/client/assets/index-CnNT0mdq.js +78 -0
  55. package/bundle/web-dist/client/assets/logs-DCY81UWb.js +1 -0
  56. package/bundle/web-dist/client/assets/{monit-CyqMmy1G.js → monit-D5OoAday.js} +1 -1
  57. package/bundle/web-dist/client/assets/routes-B4D9C86u.js +5 -0
  58. package/bundle/web-dist/client/assets/{sd.generated-CjW3rfJ8.js → sd.generated-DYdwSB2I.js} +1 -1
  59. package/bundle/web-dist/client/assets/{services.generated-BuT6Gp8V.js → services.generated-BsAvotjG.js} +1 -1
  60. package/bundle/web-dist/client/assets/{show-C5WxfoAt.js → show-BSX8ii7Z.js} +3 -3
  61. package/bundle/web-dist/client/assets/tokens-Gapk41cz.js +1 -0
  62. package/bundle/web-dist/client/assets/{use-update-token-mutation-DBkxL29Y.js → use-update-token-mutation-DqEpK7It.js} +1 -1
  63. package/bundle/web-dist/client/index.html +3 -3
  64. package/bundle/web-dist/server/assets/{lazyRouteComponent-PRxXWW3t.js → lazyRouteComponent-BaZaveNc.js} +1 -1
  65. package/bundle/web-dist/server/assets/{logs-D0z1Ghj1.js → logs-DMOw3SjD.js} +91 -42
  66. package/bundle/web-dist/server/assets/{monit-dBXxnAyI.js → monit-UmcqHroX.js} +2 -2
  67. package/bundle/web-dist/server/assets/{routes-B3p7fuwk.js → routes-Sv_4FjtH.js} +52 -46
  68. package/bundle/web-dist/server/assets/{sd.generated-ek-tz-7H.js → sd.generated-B0xqnHEo.js} +21 -0
  69. package/bundle/web-dist/server/assets/{services.generated-BNuxWs7K.js → services.generated-DqAKskAS.js} +23 -8
  70. package/bundle/web-dist/server/assets/{show-0iNioJ2I.js → show-C-oSdzHX.js} +31 -25
  71. package/bundle/web-dist/server/assets/{tokens-CvytQ9Xy.js → tokens-h-2A3wfD.js} +292 -191
  72. package/bundle/web-dist/server/assets/{use-update-token-mutation-BBCC2GKM.js → use-update-token-mutation-DHCaa7Ku.js} +1 -1
  73. package/bundle/web-dist/server/entry-server.generated.js +70 -29
  74. package/package.json +1 -1
  75. package/skills/qgrid/references/cli-env-and-server-boot.md +8 -0
  76. package/skills/qgrid/references/token-auth-quota-lifecycle.md +11 -0
  77. package/bundle/web-dist/client/assets/index-BGGBFmzE.js +0 -78
  78. package/bundle/web-dist/client/assets/index-GA9b-zy4.css +0 -1
  79. package/bundle/web-dist/client/assets/logs-Dh19u6Tk.js +0 -1
  80. package/bundle/web-dist/client/assets/routes-Cy0gCq8c.js +0 -5
  81. package/bundle/web-dist/client/assets/tokens-Ciolqw4l.js +0 -1
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-Dw2cE7zH.js";import{y as t}from"./vendor-tanstack-DJRQAbrY.js";import{i as n,r}from"./vendor-react-DH4kUfK2.js";import{i,o as a}from"./index-CnNT0mdq.js";import{a as o,n as s}from"./services.generated-BsAvotjG.js";import{t as c}from"./use-update-token-mutation-DqEpK7It.js";var l=e(n()),u=r(),d=e=>(0,u.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,u.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M5 12h14m-7-7v14`})});function f({isPending:e,isError:t,onSubmit:n,onRestart:r}){let[a,o]=(0,l.useState)(``);return(0,u.jsxs)(`div`,{className:`space-y-2`,children:[(0,u.jsx)(`p`,{className:`text-[12px] text-sand-600 leading-relaxed`,children:`새 탭에서 Claude 로그인을 완료하면 코드가 표시됩니다. 코드 전체를 아래에 붙여넣으세요.`}),(0,u.jsx)(i,{value:a,onChange:e=>o(e.target.value),placeholder:`code#state`,className:`w-full border border-sand-200 rounded-md px-3 py-2 text-sm font-mono text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300`}),t&&(0,u.jsx)(`p`,{className:`text-[11px] text-red-500`,children:`코드 확인에 실패했습니다 — 표시된 코드 전체를 다시 붙여넣거나 로그인을 다시 시작하세요.`}),(0,u.jsx)(`button`,{type:`button`,className:`w-full py-2.5 text-sm font-medium rounded-md bg-sand-900 text-white hover:bg-sand-800 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2`,disabled:!a.trim()||e,onClick:()=>n(a),children:e?(0,u.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,u.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin`}),`Verifying...`]}):`Complete login`}),(0,u.jsx)(`button`,{type:`button`,className:`w-full py-1.5 text-[12px] text-sand-500 hover:text-sand-700 transition-colors duration-150`,onClick:()=>{o(``),r()},children:`처음부터 다시 시작`})]})}var p=3e3,m=3e5;function h(e){let t=0;for(let n of e.getQueryCache().findAll({queryKey:[`Token`]})){let e=n.state.data?.rows;Array.isArray(e)&&(t=Math.max(t,e.length))}return t}function g(){let[e,n]=(0,l.useState)(null),[r,i]=(0,l.useState)(!1),a=t(),o=s.useOauthStartMutation(),c=s.useOauthStartOpenAIMutation(),u=s.useOauthCompleteMutation(),d=(0,l.useRef)({}),f=()=>{d.current.interval!==void 0&&clearInterval(d.current.interval),d.current.timeout!==void 0&&clearTimeout(d.current.timeout),d.current={}};(0,l.useEffect)(()=>f,[]);let g=()=>Promise.all([a.invalidateQueries({queryKey:[`Token`]}),a.invalidateQueries({queryKey:[`Qgrid`]})]);return{loadingProvider:e,codeEntry:r,start:async(e,t)=>{let r=t.trim();if(!r)return;let s=window.open(`about:blank`,`_blank`);n(e);try{if(e===`openai`){let{authUrl:e}=await c.mutateAsync({name:r});s?s.location.href=e:window.open(e,`_blank`);let t=h(a);f(),d.current.interval=window.setInterval(()=>{g().then(()=>{h(a)>t&&(f(),n(null))})},p),d.current.timeout=window.setTimeout(()=>{f(),n(null)},m);return}let{authUrl:t,mode:l}=await o.mutateAsync({name:r});l===`code`?(s?s.location.href=t:window.open(t,`_blank`),i(!0),n(null)):(s?.close(),window.location.href=t)}catch(e){s?.close(),console.error(`OAuth start failed:`,e),n(null)}},submitCode:async e=>{let t=e.trim();if(!t)return!1;try{await u.mutateAsync({pastedCode:t})}catch(e){return console.error(`OAuth complete failed:`,e),!1}return await g(),!0},reset:()=>{f(),n(null),i(!1),u.reset()},completeMutation:u}}function _(){let[e,t]=(0,l.useState)(!1),[n,r]=(0,l.useState)(``),o=g(),s=()=>{t(!1),r(``),o.reset()},c=async e=>{await o.submitCode(e)&&s()};return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)(`button`,{type:`button`,className:`px-3.5 py-1.5 text-[12px] font-medium rounded-lg bg-sienna-500 text-white hover:bg-sienna-600 disabled:opacity-50 transition-all duration-150 active:scale-[0.97] flex items-center gap-1.5 shadow-sm`,onClick:()=>t(!0),children:[(0,u.jsx)(d,{className:`size-3.5`}),`Add Token`]}),e&&(0,u.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,u.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:s,onKeyDown:()=>{}}),(0,u.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-md mx-4`,children:[(0,u.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/50`,children:(0,u.jsx)(`h2`,{className:`text-base font-medium text-sand-900`,children:`Add Token`})}),(0,u.jsxs)(`div`,{className:`px-5 py-4 space-y-4`,children:[(0,u.jsxs)(`div`,{children:[(0,u.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name *`}),(0,u.jsx)(i,{value:n,onChange:e=>r(e.target.value),placeholder:`e.g. your-token-name`,className:`mt-1 w-full border border-sand-200 rounded-md px-3 py-2 text-sm text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300`})]}),o.codeEntry?(0,u.jsx)(f,{isPending:o.completeMutation.isPending,isError:o.completeMutation.isError,onSubmit:e=>void c(e),onRestart:o.reset}):(0,u.jsxs)(`div`,{className:`space-y-2`,children:[(0,u.jsx)(`button`,{type:`button`,className:`w-full py-2.5 text-sm font-medium rounded-md bg-sand-900 text-white hover:bg-sand-800 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2`,disabled:!n.trim()||o.loadingProvider!==null,onClick:()=>void o.start(`openai`,n),children:o.loadingProvider===`openai`?(0,u.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,u.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin`}),`Waiting for login...`]}):(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(a,{className:`size-4`}),`Login with OpenAI`]})}),(0,u.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,u.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`}),(0,u.jsx)(`span`,{className:`text-[10px] text-sand-400 uppercase`,children:`or`}),(0,u.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`})]}),(0,u.jsx)(`button`,{type:`button`,className:`w-full py-2.5 text-sm font-medium rounded-md border border-sand-300 text-sand-700 hover:bg-sand-100 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2`,disabled:!n.trim()||o.loadingProvider!==null,onClick:()=>void o.start(`anthropic`,n),children:o.loadingProvider===`anthropic`?(0,u.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,u.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-sand-400 border-t-transparent rounded-full animate-spin`}),`Waiting for login...`]}):(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(a,{className:`size-4`}),`Login with Claude`]})})]})]}),(0,u.jsx)(`div`,{className:`px-5 py-3 border-t border-sand-100/50 flex items-center justify-end gap-2`,children:(0,u.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150`,onClick:s,children:`Cancel`})})]})]})]})}var v=e=>(0,u.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,u.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497zM15 5l4 4`})}),y=e=>(0,u.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,u.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`})});function b(e){let t=e.credentials;return`expiresAt`in t&&typeof t.expiresAt==`number`?t.expiresAt:`accessTokenExpiresAt`in t&&typeof t.accessTokenExpiresAt==`number`?t.accessTokenExpiresAt:null}function x(e){if(!e)return{text:``,color:``};let t=e-Date.now();if(t<=0)return{text:`expired`,color:`text-red-500`};let n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4),i=n>0?`${n}h ${r}m`:`${r}m`;return t<36e5?{text:i,color:`text-amber-500`}:{text:i,color:`text-sand-400`}}function S(e){let t=e.credentials.accessToken;return t.length<=12?t:`${t.slice(0,8)}...${t.slice(-4)}`}function C({data:e,isLoading:n}){let[r,a]=(0,l.useState)(null),[o,d]=(0,l.useState)(null),[p,m]=(0,l.useState)(``),h=t(),_=s.useRemoveTokenMutation(),C=c(),w=s.useToggleTokenMutation(),T=g(),E=()=>Promise.all([h.invalidateQueries({queryKey:[`Token`]}),h.invalidateQueries({queryKey:[`Qgrid`]})]),D=async e=>{await w.mutateAsync({id:e.id}),await E()},O=async()=>{r&&(await _.mutateAsync({id:r.id}),await E(),a(null))},k=async e=>{await T.submitCode(e)&&T.reset()},A=e=>{d(e),m(e.name??``)},j=async()=>{o&&(await C.mutateAsync({id:o.id,name:p.trim()}),await E(),d(null))};if(n)return(0,u.jsx)(`div`,{className:`space-y-2`,children:Array.from({length:3}).map((e,t)=>(0,u.jsx)(`div`,{className:`h-12 bg-sand-100 rounded-md animate-pulse`},`skeleton-${t}`))});let M=e??[];return M.length===0?(0,u.jsxs)(`div`,{className:`text-sand-400 text-center py-16 text-sm`,children:[`No tokens registered. Click `,(0,u.jsx)(`strong`,{className:`text-sand-600`,children:`Add Token`}),` to get started.`]}):(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(`div`,{className:`panel overflow-hidden`,children:(0,u.jsx)(`div`,{className:`overflow-x-auto`,children:(0,u.jsxs)(`table`,{className:`w-full min-w-[640px] text-[13px]`,children:[(0,u.jsx)(`thead`,{children:(0,u.jsxs)(`tr`,{className:`panel-header`,children:[(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Provider`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Name`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Token`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Status`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Expires`}),(0,u.jsx)(`th`,{className:`w-20 px-3 py-2.5`})]})}),(0,u.jsx)(`tbody`,{className:`divide-y divide-sand-100/80`,children:M.map(e=>{let t=x(b(e));return(0,u.jsxs)(`tr`,{className:`transition-colors duration-150 hover:bg-sand-100/60 ${e.active?``:`opacity-50`}`,children:[(0,u.jsx)(`td`,{className:`px-5 py-3`,children:(0,u.jsx)(`span`,{className:`text-xs font-medium text-sand-500 uppercase`,children:e.provider})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:e.name?(0,u.jsx)(`span`,{className:`text-sm text-sand-700`,children:e.name}):(0,u.jsx)(`span`,{className:`text-[11px] text-sand-400`,children:`—`})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:(0,u.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:S(e)})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:(0,u.jsx)(`button`,{type:`button`,className:`relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-200 ${e.active?`bg-sage-400`:`bg-sand-300`}`,onClick:()=>D(e),disabled:w.isPending,children:(0,u.jsx)(`span`,{className:`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform duration-200 ${e.active?`translate-x-4.5`:`translate-x-0.5`}`})})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:t.text&&(0,u.jsx)(`span`,{className:`text-xs ${t.color}`,children:t.text})}),(0,u.jsx)(`td`,{className:`px-3 py-3`,children:(0,u.jsxs)(`div`,{className:`flex items-center gap-1`,children:[!e.active&&(0,u.jsx)(`button`,{type:`button`,title:`기존 이름으로 다시 로그인`,className:`px-2 py-0.5 mr-1 text-[11px] font-medium rounded-md border border-sand-300 text-sand-600 hover:bg-sand-100 disabled:opacity-50 transition-colors duration-150 inline-flex items-center gap-1`,disabled:T.loadingProvider!==null,onClick:()=>void T.start(e.provider,e.name??``),children:T.loadingProvider===e.provider?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(`span`,{className:`w-3 h-3 border-2 border-sand-400 border-t-transparent rounded-full animate-spin`}),`로그인 대기`]}):`재로그인`}),(0,u.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-sienna-500 transition-colors duration-150`,onClick:()=>A(e),children:(0,u.jsx)(v,{className:`size-4`})}),(0,u.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-red-500 transition-colors duration-150`,onClick:()=>a(e),children:(0,u.jsx)(y,{className:`size-4`})})]})})]},e.id)})})]})})}),T.codeEntry&&(0,u.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,u.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:T.reset,onKeyDown:()=>{}}),(0,u.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-md mx-4`,children:[(0,u.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/60`,children:(0,u.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`재로그인`})}),(0,u.jsx)(`div`,{className:`px-5 py-4`,children:(0,u.jsx)(f,{isPending:T.completeMutation.isPending,isError:T.completeMutation.isError,onSubmit:e=>void k(e),onRestart:T.reset})})]})]}),o&&(0,u.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,u.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>d(null),onKeyDown:()=>{}}),(0,u.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,u.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/60`,children:(0,u.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Edit Token`})}),(0,u.jsx)(`div`,{className:`px-5 py-4 space-y-3`,children:(0,u.jsxs)(`div`,{children:[(0,u.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name`}),(0,u.jsx)(i,{value:p,onChange:e=>m(e.target.value),placeholder:`e.g. prod, dev, team-a`,className:`mt-1 w-full border border-sand-200 rounded-md px-3 py-2 text-sm text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300`})]})}),(0,u.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,u.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150`,onClick:()=>d(null),children:`Cancel`}),(0,u.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md bg-sienna-400 text-white hover:bg-sienna-500 disabled:opacity-50 transition-colors duration-150`,disabled:C.isPending,onClick:j,children:C.isPending?`Saving...`:`Save`})]})]})]}),r&&(0,u.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,u.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>a(null),onKeyDown:()=>{}}),(0,u.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,u.jsxs)(`div`,{className:`px-5 py-4`,children:[(0,u.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Remove Token`}),(0,u.jsxs)(`p`,{className:`text-sm text-sand-700 mt-2`,children:[`Are you sure you want to remove`,` `,(0,u.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:S(r)}),r.name&&(0,u.jsxs)(`span`,{className:`text-sand-500`,children:[` (`,r.name,`)`]}),`?`]})]}),(0,u.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,u.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150`,onClick:()=>a(null),children:`Cancel`}),(0,u.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md bg-red-500 text-white hover:bg-red-600 disabled:opacity-50 transition-colors duration-150`,disabled:_.isPending,onClick:O,children:_.isPending?`Removing...`:`Remove`})]})]})]})]})}function w(){let{data:e,isLoading:t}=o.useTokens(`A`);return(0,u.jsxs)(`div`,{className:`max-w-6xl mx-auto space-y-4`,children:[(0,u.jsx)(`div`,{className:`flex items-center justify-end`,children:(0,u.jsx)(_,{})}),(0,u.jsx)(C,{data:e?.rows,isLoading:t})]})}export{w as component};
@@ -1 +1 @@
1
- import{m as e}from"./vendor-tanstack-DJRQAbrY.js";import{n as t}from"./services.generated-BuT6Gp8V.js";function n(){return e({mutationFn:({id:e,name:n,quotaThreshold:r,weight:i})=>t.updateToken(e,n,r,i)})}export{n as t};
1
+ import{m as e}from"./vendor-tanstack-DJRQAbrY.js";import{n as t}from"./services.generated-BsAvotjG.js";function n(){return e({mutationFn:({id:e,name:n,quotaThreshold:r,weight:i})=>t.updateToken(e,n,r,i)})}export{n as t};
@@ -5,13 +5,13 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/sonamu.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Qgrid</title>
8
- <script type="module" crossorigin src="/assets/index-BGGBFmzE.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CnNT0mdq.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-Dw2cE7zH.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-ca-nBW7U.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DH4kUfK2.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-DJRQAbrY.js">
13
- <link rel="modulepreload" crossorigin href="/assets/sd.generated-CjW3rfJ8.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-GA9b-zy4.css">
13
+ <link rel="modulepreload" crossorigin href="/assets/sd.generated-DYdwSB2I.js">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-BGQSGMcK.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1,4 +1,4 @@
1
- import { J as __commonJSMin, Z as __toESM, _ as require_jsx_runtime, m as require_shim, p as require_react_dom, v as require_react } from "./sd.generated-ek-tz-7H.js";
1
+ import { J as __commonJSMin, Z as __toESM, _ as require_jsx_runtime, m as require_shim, p as require_react_dom, v as require_react } from "./sd.generated-B0xqnHEo.js";
2
2
  //#region ../../node_modules/.pnpm/@tanstack+router-core@1.143.6/node_modules/@tanstack/router-core/dist/esm/utils.js
3
3
  function last(arr) {
4
4
  return arr[arr.length - 1];
@@ -1,6 +1,6 @@
1
- import { _ as require_jsx_runtime, d as string, l as number, u as object, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
- import { a as TokenService, n as QgridService, r as RequestLogService } from "./services.generated-BNuxWs7K.js";
3
- import { a as Link, o as useNavigate } from "./lazyRouteComponent-PRxXWW3t.js";
1
+ import { _ as require_jsx_runtime, d as string, l as number, u as object, v as require_react } from "./sd.generated-B0xqnHEo.js";
2
+ import { a as TokenService, n as QgridService, r as RequestLogService } from "./services.generated-DqAKskAS.js";
3
+ import { a as Link, o as useNavigate } from "./lazyRouteComponent-BaZaveNc.js";
4
4
  import { n as Route } from "../entry-server.generated.js";
5
5
  import { n as formatMicroUsd, r as formatUsd, t as cacheHitRate } from "./cost-CSH_t17I.js";
6
6
  require_react();
@@ -82,12 +82,14 @@ var COLUMNS = [
82
82
  {
83
83
  label: "ID",
84
84
  align: "left",
85
- width: "w-12"
85
+ width: "w-12",
86
+ sortKey: "id"
86
87
  },
87
88
  {
88
89
  label: "Date",
89
90
  align: "left",
90
- width: "w-20"
91
+ width: "w-20",
92
+ sortKey: "created_at"
91
93
  },
92
94
  {
93
95
  label: "Project",
@@ -106,48 +108,55 @@ var COLUMNS = [
106
108
  },
107
109
  {
108
110
  label: "TTFT",
109
- align: "left",
110
- width: "w-16"
111
+ align: "right",
112
+ width: "w-16",
113
+ sortKey: "ttft_ms"
111
114
  },
112
115
  {
113
116
  label: "Duration",
114
- align: "left",
115
- width: "w-20"
117
+ align: "right",
118
+ width: "w-20",
119
+ sortKey: "duration_ms"
116
120
  },
117
121
  {
118
122
  label: "In",
119
- align: "left",
120
- width: "w-16"
123
+ align: "right",
124
+ width: "w-16",
125
+ sortKey: "input_tokens"
121
126
  },
122
127
  {
123
128
  label: "Out",
124
- align: "left",
125
- width: "w-20"
129
+ align: "right",
130
+ width: "w-20",
131
+ sortKey: "output_tokens"
126
132
  },
127
133
  {
128
134
  label: "C.Read",
129
- align: "left",
130
- width: "w-20"
135
+ align: "right",
136
+ width: "w-20",
137
+ sortKey: "cache_read_tokens"
131
138
  },
132
139
  {
133
140
  label: "C.Write",
134
- align: "left",
135
- width: "w-20"
141
+ align: "right",
142
+ width: "w-20",
143
+ sortKey: "cache_creation_tokens"
136
144
  },
137
145
  {
138
146
  label: "Hit",
139
- align: "left",
147
+ align: "right",
140
148
  width: "w-14"
141
149
  },
142
150
  {
143
151
  label: "Tools",
144
- align: "left",
152
+ align: "right",
145
153
  width: "w-14"
146
154
  },
147
155
  {
148
156
  label: "Cost",
149
- align: "left",
150
- width: "w-20"
157
+ align: "right",
158
+ width: "w-20",
159
+ sortKey: "cost_usd"
151
160
  }
152
161
  ];
153
162
  function RequestLogTable({ search, onSearchChange }) {
@@ -155,23 +164,38 @@ function RequestLogTable({ search, onSearchChange }) {
155
164
  const page = search.page ?? 1;
156
165
  const tokenFilter = search.token ?? "";
157
166
  const projectFilter = search.project ?? "";
167
+ const modelFilter = search.model ?? "";
158
168
  const { data: tokensData } = TokenService.useTokens("A");
159
169
  const tokenNames = (tokensData?.rows ?? []).map((t) => t.name).filter(Boolean);
160
170
  const { data: projectData } = QgridService.useProjectNames();
161
171
  const projectNames = projectData?.names ?? [];
172
+ const { data: modelData } = QgridService.useModelNames();
173
+ const modelNames = modelData?.names ?? [];
162
174
  const projectFilterParam = (() => {
163
175
  if (projectFilter === UNASSIGNED) return { project_name_is_null: true };
164
176
  if (projectFilter) return { project_name: projectFilter };
165
177
  return {};
166
178
  })();
179
+ const listFilters = {
180
+ ...tokenFilter ? { token_name: tokenFilter } : {},
181
+ ...projectFilterParam,
182
+ ...modelFilter ? { model_name: modelFilter } : {}
183
+ };
184
+ const sort = search.sort ?? "id-desc";
185
+ const separator = sort.lastIndexOf("-");
186
+ const sortColumn = sort.slice(0, separator);
187
+ const sortDirection = sort.slice(separator + 1) === "asc" ? "asc" : "desc";
167
188
  const { data, isLoading } = RequestLogService.useRequestLogs("P", {
168
189
  num: PAGE_SIZE,
169
190
  page,
170
- orderBy: "id-desc",
171
- ...tokenFilter ? { token_name: tokenFilter } : {},
172
- ...projectFilterParam
191
+ orderBy: sort,
192
+ ...listFilters
193
+ });
194
+ const { data: costData } = QgridService.useTotalCost({
195
+ num: 0,
196
+ page: 1,
197
+ ...listFilters
173
198
  });
174
- const { data: costData } = QgridService.useTotalCost(tokenFilter || void 0);
175
199
  const rows = data?.rows ?? [];
176
200
  const total = data?.total ?? 0;
177
201
  const totalPages = Math.ceil(total / PAGE_SIZE);
@@ -185,15 +209,16 @@ function RequestLogTable({ search, onSearchChange }) {
185
209
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
186
210
  className: "panel overflow-hidden",
187
211
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
188
- className: "panel-header flex items-center gap-3 px-5 py-2.5 flex-wrap",
212
+ className: "panel-header flex items-center gap-2 px-3 py-2.5 flex-wrap sm:gap-3 sm:px-5",
189
213
  children: [
190
- tokenNames.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
191
215
  value: tokenFilter,
192
216
  onChange: (e) => updateFilter({ token: e.target.value || void 0 }),
193
- className: "border border-sand-200/80 rounded-lg px-2.5 py-1.5 text-[11px] text-sand-700 bg-sand-50/50 focus:outline-none focus:border-sienna-300",
217
+ disabled: tokenNames.length === 0,
218
+ className: "border border-sand-200/80 rounded-lg px-2.5 py-1.5 text-[11px] text-sand-700 bg-sand-50/50 focus:outline-none focus:border-sienna-300 disabled:text-sand-300 disabled:cursor-not-allowed",
194
219
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
195
220
  value: "",
196
- children: "All Tokens"
221
+ children: tokenNames.length > 0 ? "All Tokens" : "Tokens —"
197
222
  }), tokenNames.map((name) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
198
223
  value: name,
199
224
  children: name
@@ -218,6 +243,19 @@ function RequestLogTable({ search, onSearchChange }) {
218
243
  }, name))
219
244
  ]
220
245
  }),
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
247
+ value: modelFilter,
248
+ onChange: (e) => updateFilter({ model: e.target.value || void 0 }),
249
+ disabled: modelNames.length === 0,
250
+ className: "border border-sand-200/80 rounded-lg px-2.5 py-1.5 text-[11px] text-sand-700 bg-sand-50/50 focus:outline-none focus:border-sienna-300 disabled:text-sand-300 disabled:cursor-not-allowed",
251
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
252
+ value: "",
253
+ children: modelNames.length > 0 ? "All Models" : "Models —"
254
+ }), modelNames.map((name) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
255
+ value: name,
256
+ children: name
257
+ }, name))]
258
+ }),
221
259
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex-1" }),
222
260
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
223
261
  className: "text-[11px] text-sand-400",
@@ -235,16 +273,25 @@ function RequestLogTable({ search, onSearchChange }) {
235
273
  className: "text-sand-400 text-center py-12 text-sm",
236
274
  children: "No requests yet."
237
275
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
238
- className: "overflow-x-auto px-5 py-3",
276
+ className: "overflow-x-auto px-3 py-3 sm:px-5",
239
277
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
240
- className: "w-full text-[13px]",
278
+ className: "w-full min-w-[900px] text-[13px]",
241
279
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
242
280
  className: "border-b border-sand-100/60",
243
281
  children: [COLUMNS.map((col) => {
244
282
  const padX = col.label === "Duration" ? "pl-5 pr-3" : col.width ? "px-4" : "px-3";
283
+ const active = col.sortKey === sortColumn;
245
284
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
246
- className: `text-${col.align} ${col.width ?? ""} ${col.width ? "whitespace-nowrap" : ""} ${padX} py-1.5 text-[10px] uppercase text-sand-400 font-medium`,
247
- children: col.label
285
+ className: `${col.align === "right" ? "text-right" : "text-left"} ${col.width ?? ""} ${col.width ? "whitespace-nowrap" : ""} ${padX} py-1.5 text-[10px] uppercase font-medium ${active ? "text-sienna-600" : "text-sand-400"}`,
286
+ children: col.sortKey ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
287
+ type: "button",
288
+ onClick: () => updateFilter({ sort: `${col.sortKey}-${active && sortDirection === "desc" ? "asc" : "desc"}` }),
289
+ className: `inline-flex items-center gap-0.5 text-[10px] uppercase font-medium hover:text-sienna-500 transition-colors duration-150 ${col.align === "right" ? "justify-end w-full" : ""}`,
290
+ children: [col.label, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
291
+ className: active ? "" : "text-sand-300",
292
+ children: active ? sortDirection === "desc" ? "↓" : "↑" : "↕"
293
+ })]
294
+ }) : col.label
248
295
  }, col.label);
249
296
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: "w-8" })]
250
297
  }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", {
@@ -303,39 +350,39 @@ function RequestLogTable({ search, onSearchChange }) {
303
350
  })
304
351
  }),
305
352
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
306
- className: "px-4 py-1.5 text-left tabular-nums text-sand-500 whitespace-nowrap",
353
+ className: "px-4 py-1.5 text-right tabular-nums text-sand-500 whitespace-nowrap",
307
354
  children: formatTtft(row.ttft_ms)
308
355
  }),
309
356
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
310
- className: "pl-5 pr-3 py-1.5 text-left tabular-nums text-sand-500 whitespace-nowrap",
357
+ className: "pl-5 pr-3 py-1.5 text-right tabular-nums text-sand-500 whitespace-nowrap",
311
358
  children: formatDuration(row.duration_ms)
312
359
  }),
313
360
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
314
- className: "px-3 py-1.5 text-left tabular-nums text-sand-700",
361
+ className: "px-3 py-1.5 text-right tabular-nums text-sand-700",
315
362
  children: formatNum(row.input_tokens)
316
363
  }),
317
364
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
318
- className: "px-3 py-1.5 text-left tabular-nums text-sand-700",
365
+ className: "px-3 py-1.5 text-right tabular-nums text-sand-700",
319
366
  children: formatNum(row.output_tokens)
320
367
  }),
321
368
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
322
- className: "px-3 py-1.5 text-left tabular-nums text-sand-700",
369
+ className: "px-3 py-1.5 text-right tabular-nums text-sand-700",
323
370
  children: formatNum(row.cache_read_tokens)
324
371
  }),
325
372
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
326
- className: "px-3 py-1.5 text-left tabular-nums text-sand-700",
373
+ className: "px-3 py-1.5 text-right tabular-nums text-sand-700",
327
374
  children: formatNum(row.cache_creation_tokens)
328
375
  }),
329
376
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
330
- className: "px-3 py-1.5 text-left tabular-nums text-sand-700",
377
+ className: "px-3 py-1.5 text-right tabular-nums text-sand-700",
331
378
  children: cacheHitRate(row)
332
379
  }),
333
380
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
334
- className: "px-3 py-1.5 text-left tabular-nums text-sand-500",
381
+ className: "px-3 py-1.5 text-right tabular-nums text-sand-500",
335
382
  children: row.tool_call_count > 0 ? row.tool_call_count : "—"
336
383
  }),
337
384
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
338
- className: "px-3 py-1.5 text-left tabular-nums text-sand-700",
385
+ className: "px-3 py-1.5 text-right tabular-nums text-sand-700",
339
386
  children: row.image_cost_usd !== null ? formatMicroUsd((row.cost_usd ?? 0) + row.image_cost_usd) : row.cost_usd !== null ? formatMicroUsd(row.cost_usd) : "—"
340
387
  }),
341
388
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
@@ -386,6 +433,8 @@ function RequestLogTable({ search, onSearchChange }) {
386
433
  object({
387
434
  token: string().optional(),
388
435
  project: string().optional(),
436
+ model: string().optional(),
437
+ sort: string().optional(),
389
438
  page: number().optional().default(1)
390
439
  });
391
440
  function LogsPage() {
@@ -1,5 +1,5 @@
1
- import { Z as __toESM, _ as require_jsx_runtime, c as _enum, p as require_react_dom, u as object, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
- import { s as useQuery, t as MonitService } from "./services.generated-BNuxWs7K.js";
1
+ import { Z as __toESM, _ as require_jsx_runtime, c as _enum, p as require_react_dom, u as object, v as require_react } from "./sd.generated-B0xqnHEo.js";
2
+ import { s as useQuery, t as MonitService } from "./services.generated-DqAKskAS.js";
3
3
  import { a as clsx, r as Route } from "../entry-server.generated.js";
4
4
  //#region ../../node_modules/.pnpm/@tanstack+virtual-core@3.17.4/node_modules/@tanstack/virtual-core/dist/esm/lazy-measurements.js
5
5
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
@@ -1,8 +1,8 @@
1
- import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, p as require_react_dom, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
- import { a as TokenService, n as QgridService } from "./services.generated-BNuxWs7K.js";
1
+ import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, p as require_react_dom, v as require_react } from "./sd.generated-B0xqnHEo.js";
2
+ import { a as TokenService, n as QgridService } from "./services.generated-DqAKskAS.js";
3
3
  import { i as Q } from "../entry-server.generated.js";
4
4
  import { r as formatUsd } from "./cost-CSH_t17I.js";
5
- import { t as useUpdateTokenMutation } from "./use-update-token-mutation-BBCC2GKM.js";
5
+ import { t as useUpdateTokenMutation } from "./use-update-token-mutation-DHCaa7Ku.js";
6
6
  //#region ../../node_modules/.pnpm/@dnd-kit+utilities@3.2.2_react@19.2.4/node_modules/@dnd-kit/utilities/dist/utilities.esm.js
7
7
  var import_react_dom = /* @__PURE__ */ __toESM(require_react_dom());
8
8
  var import_react = /* @__PURE__ */ __toESM(require_react());
@@ -2387,14 +2387,14 @@ function UsageRow({ label, utilization, resetsAt, windowDurationMins, theme, thr
2387
2387
  const pct = utilization ?? 0;
2388
2388
  const hasThreshold = threshold !== void 0 && threshold !== null;
2389
2389
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2390
- className: "flex items-center gap-3",
2390
+ className: "flex items-center gap-2",
2391
2391
  children: [
2392
2392
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2393
- className: "text-xs text-sand-600 w-20 shrink-0",
2393
+ className: "text-xs text-sand-600 w-7 shrink-0",
2394
2394
  children: formatWindowLabel(label, windowDurationMins)
2395
2395
  }),
2396
2396
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2397
- className: "relative flex-1 h-2 bg-sand-200 rounded-full overflow-hidden",
2397
+ className: "relative flex-1 min-w-0 h-2 bg-sand-200 rounded-full overflow-hidden",
2398
2398
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2399
2399
  className: `h-full rounded-full transition-all duration-300 ${barColor(pct, theme)}`,
2400
2400
  style: { width: `${Math.min(pct, 100)}%` }
@@ -2404,11 +2404,12 @@ function UsageRow({ label, utilization, resetsAt, windowDurationMins, theme, thr
2404
2404
  })]
2405
2405
  }),
2406
2406
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2407
- className: "text-xs tabular-nums text-sand-700 w-10 text-right",
2407
+ className: "text-xs tabular-nums text-sand-700 w-9 text-right shrink-0",
2408
2408
  children: [pct, "%"]
2409
2409
  }),
2410
2410
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2411
- className: "text-[10px] text-sand-400 w-24 text-right",
2411
+ className: "hidden sm:inline text-[10px] text-sand-400 w-24 text-right shrink-0",
2412
+ title: formatResets(resetsAt),
2412
2413
  children: formatResets(resetsAt)
2413
2414
  })
2414
2415
  ]
@@ -2456,9 +2457,6 @@ function TokenUsage({ token, theme }) {
2456
2457
  })]
2457
2458
  });
2458
2459
  }
2459
- function stopDragPropagation(e) {
2460
- e.stopPropagation();
2461
- }
2462
2460
  function ThresholdControl({ token }) {
2463
2461
  const [pos, setPos] = (0, import_react.useState)(null);
2464
2462
  const [value, setValue] = (0, import_react.useState)("");
@@ -2506,8 +2504,6 @@ function ThresholdControl({ token }) {
2506
2504
  };
2507
2505
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2508
2506
  className: "inline-flex",
2509
- onPointerDown: stopDragPropagation,
2510
- onClick: stopDragPropagation,
2511
2507
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2512
2508
  ref: triggerRef,
2513
2509
  type: "button",
@@ -2623,8 +2619,6 @@ function WeightControl({ token }) {
2623
2619
  };
2624
2620
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2625
2621
  className: "inline-flex",
2626
- onPointerDown: stopDragPropagation,
2627
- onClick: stopDragPropagation,
2628
2622
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2629
2623
  ref: triggerRef,
2630
2624
  type: "button",
@@ -2699,7 +2693,11 @@ function SortableTokenCard({ token }) {
2699
2693
  easing: "ease"
2700
2694
  }
2701
2695
  });
2702
- const { data: costData } = QgridService.useTotalCost(token.name, { enabled: !!token.name });
2696
+ const { data: costData } = QgridService.useTotalCost({
2697
+ num: 0,
2698
+ page: 1,
2699
+ ...token.name ? { token_name: token.name } : {}
2700
+ }, { enabled: !!token.name });
2703
2701
  const theme = getProviderTheme(token.provider);
2704
2702
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2705
2703
  ref: setNodeRef,
@@ -2709,27 +2707,50 @@ function SortableTokenCard({ token }) {
2709
2707
  opacity: isDragging ? .5 : 1,
2710
2708
  zIndex: isDragging ? 10 : void 0
2711
2709
  },
2712
- ...attributes,
2713
- ...listeners,
2714
- className: "relative rounded-lg bg-white border border-sand-200/80 overflow-hidden select-none cursor-grab active:cursor-grabbing touch-none shadow-sm hover:shadow-md transition-shadow duration-200",
2710
+ className: "relative rounded-lg bg-white border border-sand-200/80 overflow-hidden shadow-sm hover:shadow-md transition-shadow duration-200",
2715
2711
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `absolute inset-y-0 left-0 w-1 ${theme.stripe}` }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2716
- className: "pl-4 pr-4 py-3",
2712
+ className: "pl-4 pr-3 py-3",
2717
2713
  children: [
2718
2714
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2719
- className: "flex items-center gap-2 mb-2",
2720
- children: [
2721
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideGripVertical, { className: "size-3.5 text-sand-300 shrink-0" }),
2722
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2723
- className: "text-[13px] font-medium text-sand-800 truncate",
2715
+ className: "flex items-start justify-between gap-2 mb-2",
2716
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2717
+ className: "flex items-center gap-1.5 min-w-0",
2718
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2719
+ type: "button",
2720
+ "aria-label": "순서 변경 핸들",
2721
+ ...attributes,
2722
+ ...listeners,
2723
+ className: "shrink-0 -m-1 p-1 text-sand-300 hover:text-sand-500 cursor-grab active:cursor-grabbing touch-none",
2724
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideGripVertical, { className: "size-3.5" })
2725
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2726
+ className: "text-[13px] font-medium text-sand-800 truncate select-none",
2724
2727
  children: token.name ?? "Unnamed"
2725
- }),
2728
+ })]
2729
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2730
+ className: `text-[11px] tabular-nums font-medium shrink-0 ${theme.cost}`,
2731
+ title: "이 토큰의 누적 비용",
2732
+ children: formatUsd(costData?.usd ?? 0)
2733
+ })]
2734
+ }),
2735
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2736
+ className: "flex flex-wrap items-center gap-1 mb-2",
2737
+ children: [
2726
2738
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2727
- className: `text-[10px] px-1.5 py-0.5 rounded-full shrink-0 uppercase ${theme.badge}`,
2739
+ className: `text-[10px] px-1.5 py-0.5 rounded-full uppercase ${theme.badge}`,
2728
2740
  children: token.provider
2729
2741
  }),
2730
2742
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2731
- className: `text-[10px] px-1.5 py-0.5 rounded-full shrink-0 ${token.active ? "bg-sage-100 text-sage-600" : "bg-sand-200 text-sand-500"}`,
2743
+ className: `text-[10px] px-1.5 py-0.5 rounded-full ${token.active ? "bg-sage-100 text-sage-600" : "bg-sand-200 text-sand-500"}`,
2732
2744
  children: token.active ? "Active" : "Inactive"
2745
+ }),
2746
+ QUOTA_THRESHOLD_PROVIDERS.has(token.provider) && token.quota_threshold !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2747
+ className: "px-1.5 py-0.5 rounded-full bg-sienna-50 text-[10px] tabular-nums font-medium text-sienna-600",
2748
+ title: `사용률 ${token.quota_threshold}% 이상이면 이 토큰 제외`,
2749
+ children: [
2750
+ "≤ ",
2751
+ token.quota_threshold,
2752
+ "%"
2753
+ ]
2733
2754
  })
2734
2755
  ]
2735
2756
  }),
@@ -2738,23 +2759,8 @@ function SortableTokenCard({ token }) {
2738
2759
  theme
2739
2760
  }),
2740
2761
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2741
- className: "mt-2 flex items-center justify-end gap-1.5",
2762
+ className: "mt-2 flex flex-wrap items-center justify-end gap-1.5",
2742
2763
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(WeightControl, { token }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThresholdControl, { token })]
2743
- }),
2744
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2745
- className: "absolute top-2.5 right-3 flex items-center gap-1.5",
2746
- children: [QUOTA_THRESHOLD_PROVIDERS.has(token.provider) && token.quota_threshold !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2747
- className: "px-1.5 py-0.5 rounded-md bg-sienna-50 text-[11px] tabular-nums font-medium border border-sienna-200 text-sienna-600",
2748
- title: `사용률 ${token.quota_threshold}% 이상이면 이 토큰 제외`,
2749
- children: [
2750
- "≤ ",
2751
- token.quota_threshold,
2752
- "%"
2753
- ]
2754
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2755
- className: `px-2 py-0.5 rounded-md bg-sand-50 text-[11px] tabular-nums font-medium border border-sand-200/80 ${theme.cost}`,
2756
- children: formatUsd(costData?.usd ?? 0)
2757
- })]
2758
2764
  })
2759
2765
  ]
2760
2766
  })]
@@ -2804,7 +2810,7 @@ function UsageCard() {
2804
2810
  if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2805
2811
  className: "panel p-5",
2806
2812
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2807
- className: "grid grid-cols-2 gap-3",
2813
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-3",
2808
2814
  children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2809
2815
  className: "rounded-lg bg-sand-50 px-4 py-3 animate-pulse",
2810
2816
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-3 w-16 bg-sand-200 rounded mb-3" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-2 w-full bg-sand-200 rounded-full" })]
@@ -2846,7 +2852,7 @@ function UsageCard() {
2846
2852
  items: filteredTokens.map((t) => String(t.id)),
2847
2853
  strategy: rectSortingStrategy,
2848
2854
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2849
- className: "grid grid-cols-2 gap-3",
2855
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-3",
2850
2856
  children: filteredTokens.map((token) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SortableTokenCard, { token }, token.id))
2851
2857
  })
2852
2858
  })
@@ -22308,6 +22308,8 @@ var en_default = {
22308
22308
  "error.internalServerError": "Internal server error",
22309
22309
  "error.notFound": "Not found",
22310
22310
  "error.serviceUnavailable": "Service unavailable",
22311
+ "qgrid.dispatcherFailed": (provider) => `${provider} dispatcher failed to initialize`,
22312
+ "qgrid.dispatcherStarting": (provider) => `${provider} dispatcher is starting up. Please retry shortly`,
22311
22313
  "error.unauthorized": "Unauthorized",
22312
22314
  notFound: (name, id) => `${name} ID ${id} not found`,
22313
22315
  "validation.email": "Invalid email format",
@@ -22368,6 +22370,8 @@ var ko_default = {
22368
22370
  "error.internalServerError": "서버 오류가 발생했습니다",
22369
22371
  "error.notFound": "찾을 수 없습니다",
22370
22372
  "error.serviceUnavailable": "서비스를 사용할 수 없습니다",
22373
+ "qgrid.dispatcherFailed": (provider) => `${provider} dispatcher 초기화에 실패했습니다`,
22374
+ "qgrid.dispatcherStarting": (provider) => `${provider} dispatcher 기동 중입니다. 잠시 후 다시 시도해주세요`,
22371
22375
  "error.unauthorized": "인증이 필요합니다",
22372
22376
  "error.unknownSearchField": (field) => `구현되지 않은 검색 필드 ${field}`,
22373
22377
  notFound: (name, id) => `존재하지 않는 ${name} ID ${id}`,
@@ -22564,6 +22568,23 @@ var entityLabels = {
22564
22568
  "entity.RequestLog.tool_call_count": "tool call 횟수",
22565
22569
  "entity.RequestLog.is_image_generation": "이미지 생성 turn 여부 (quota 귀속용)",
22566
22570
  "enum.RequestLogOrderBy.id-desc": "ID최신순",
22571
+ "enum.RequestLogOrderBy.id-asc": "ID오래된순",
22572
+ "enum.RequestLogOrderBy.created_at-desc": "등록일시최신순",
22573
+ "enum.RequestLogOrderBy.created_at-asc": "등록일시오래된순",
22574
+ "enum.RequestLogOrderBy.ttft_ms-desc": "TTFT긴순",
22575
+ "enum.RequestLogOrderBy.ttft_ms-asc": "TTFT짧은순",
22576
+ "enum.RequestLogOrderBy.duration_ms-desc": "소요시간긴순",
22577
+ "enum.RequestLogOrderBy.duration_ms-asc": "소요시간짧은순",
22578
+ "enum.RequestLogOrderBy.input_tokens-desc": "입력토큰많은순",
22579
+ "enum.RequestLogOrderBy.input_tokens-asc": "입력토큰적은순",
22580
+ "enum.RequestLogOrderBy.output_tokens-desc": "출력토큰많은순",
22581
+ "enum.RequestLogOrderBy.output_tokens-asc": "출력토큰적은순",
22582
+ "enum.RequestLogOrderBy.cache_read_tokens-desc": "캐시읽기많은순",
22583
+ "enum.RequestLogOrderBy.cache_read_tokens-asc": "캐시읽기적은순",
22584
+ "enum.RequestLogOrderBy.cache_creation_tokens-desc": "캐시생성많은순",
22585
+ "enum.RequestLogOrderBy.cache_creation_tokens-asc": "캐시생성적은순",
22586
+ "enum.RequestLogOrderBy.cost_usd-desc": "비용높은순",
22587
+ "enum.RequestLogOrderBy.cost_usd-asc": "비용낮은순",
22567
22588
  "enum.RequestLogSearchField.id": "ID",
22568
22589
  "enum.RequestLogSearchField.token_name": "토큰이름",
22569
22590
  "enum.RequestLogSearchField.user_prompt": "사용자 프롬프트",
@@ -1,4 +1,4 @@
1
- import { B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, G as timeoutManager, H as shouldThrowError, I as noop, K as focusManager, N as isValidTimeout, O as pendingThenable, R as replaceData, S as hasPreviousPage, T as fetchState, V as shallowEqualObjects, W as timeUntilStale, Z as __toESM, _ as require_jsx_runtime, a as fetch, b as getDefaultState, f as require_lib, g as useQueryClient, i as dedupeAndFlatten, j as hashKey, k as environmentManager, o as useRefreshable, q as Subscribable, s as useSSEStream, v as require_react, x as hasNextPage, z as resolveEnabled } from "./sd.generated-ek-tz-7H.js";
1
+ import { B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, G as timeoutManager, H as shouldThrowError, I as noop, K as focusManager, N as isValidTimeout, O as pendingThenable, R as replaceData, S as hasPreviousPage, T as fetchState, V as shallowEqualObjects, W as timeUntilStale, Z as __toESM, _ as require_jsx_runtime, a as fetch, b as getDefaultState, f as require_lib, g as useQueryClient, i as dedupeAndFlatten, j as hashKey, k as environmentManager, o as useRefreshable, q as Subscribable, s as useSSEStream, v as require_react, x as hasNextPage, z as resolveEnabled } from "./sd.generated-B0xqnHEo.js";
2
2
  //#region ../../node_modules/.pnpm/@tanstack+query-core@5.95.2/node_modules/@tanstack/query-core/build/modern/queryObserver.js
3
3
  var QueryObserver = class extends Subscribable {
4
4
  constructor(client, options) {
@@ -989,23 +989,23 @@ var QgridService;
989
989
  ...statsQueryOptions(),
990
990
  ...options
991
991
  }));
992
- async function totalCost(tokenName) {
992
+ async function totalCost(params) {
993
993
  return fetch({
994
994
  method: "GET",
995
- url: `/api/qgrid/totalCost?${import_lib.default.stringify({ tokenName })}`
995
+ url: `/api/qgrid/totalCost?${import_lib.default.stringify({ params })}`
996
996
  });
997
997
  }
998
998
  _QgridService.totalCost = totalCost;
999
- const totalCostQueryOptions = _QgridService.totalCostQueryOptions = (tokenName) => queryOptions({
999
+ const totalCostQueryOptions = _QgridService.totalCostQueryOptions = (params) => queryOptions({
1000
1000
  queryKey: [
1001
1001
  "Qgrid",
1002
1002
  "totalCost",
1003
- tokenName
1003
+ params
1004
1004
  ],
1005
- queryFn: () => totalCost(tokenName)
1005
+ queryFn: () => totalCost(params)
1006
1006
  });
1007
- _QgridService.useTotalCost = (tokenName, options) => useRefreshable(useQuery({
1008
- ...totalCostQueryOptions(tokenName),
1007
+ _QgridService.useTotalCost = (params, options) => useRefreshable(useQuery({
1008
+ ...totalCostQueryOptions(params),
1009
1009
  ...options
1010
1010
  }));
1011
1011
  async function projectNames() {
@@ -1023,6 +1023,21 @@ var QgridService;
1023
1023
  ...projectNamesQueryOptions(),
1024
1024
  ...options
1025
1025
  }));
1026
+ async function modelNames() {
1027
+ return fetch({
1028
+ method: "GET",
1029
+ url: `/api/qgrid/modelNames`
1030
+ });
1031
+ }
1032
+ _QgridService.modelNames = modelNames;
1033
+ const modelNamesQueryOptions = _QgridService.modelNamesQueryOptions = () => queryOptions({
1034
+ queryKey: ["Qgrid", "modelNames"],
1035
+ queryFn: () => modelNames()
1036
+ });
1037
+ _QgridService.useModelNames = (options) => useRefreshable(useQuery({
1038
+ ...modelNamesQueryOptions(),
1039
+ ...options
1040
+ }));
1026
1041
  async function addToken(provider, credentials, name) {
1027
1042
  return fetch({
1028
1043
  method: "POST",