@cartanova/qgrid-cli 2.3.5 → 2.3.6

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 (92) hide show
  1. package/bundle/dist/application/qgrid/oauth.js +15 -12
  2. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +2 -1
  3. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +2 -1
  4. package/bundle/dist/application/qgrid/qgrid.frame.js +23 -10
  5. package/bundle/dist/application/qgrid/qgrid.types.js +8 -2
  6. package/bundle/dist/application/qgrid/token-subscriber.js +16 -8
  7. package/bundle/dist/application/qgrid/token-trigger-setup.js +3 -2
  8. package/bundle/dist/application/request-log/request-log.model.js +12 -7
  9. package/bundle/dist/application/sonamu.generated.js +11 -3
  10. package/bundle/dist/application/sonamu.generated.sso.js +7 -2
  11. package/bundle/dist/application/token/token.model.js +10 -2
  12. package/bundle/dist/application/token/token.types.js +5 -3
  13. package/bundle/dist/i18n/sd.generated.js +4 -1
  14. package/bundle/dist/migrations/20260630223950_alter_tokens_add1_alter1.js +16 -0
  15. package/bundle/dist/migrations/20260701105327_alter_request_logs_add1_alter1.js +16 -0
  16. package/bundle/dist/migrations/20260701105328_alter_request_log_steps_add1_alter1.js +16 -0
  17. package/bundle/dist/sonamu.config.js +13 -6
  18. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +99 -12
  19. package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +31 -0
  20. package/bundle/dist/utils/providers/anthropic/claude-session.js +8 -3
  21. package/bundle/dist/utils/providers/common/ttft.js +25 -0
  22. package/bundle/dist/utils/providers/openai/codex-worker.js +35 -7
  23. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +237 -39
  24. package/bundle/dist/utils/providers/openai/openai-quota.js +30 -0
  25. package/bundle/src/application/qgrid/oauth.ts +17 -7
  26. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +96 -0
  27. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +1 -0
  28. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +16 -0
  29. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +1 -0
  30. package/bundle/src/application/qgrid/qgrid.frame.test.ts +128 -0
  31. package/bundle/src/application/qgrid/qgrid.frame.ts +41 -20
  32. package/bundle/src/application/qgrid/qgrid.types.ts +6 -0
  33. package/bundle/src/application/qgrid/token-subscriber.test.ts +89 -0
  34. package/bundle/src/application/qgrid/token-subscriber.ts +40 -11
  35. package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -1
  36. package/bundle/src/application/qgrid/tool-emulation.test.ts +7 -3
  37. package/bundle/src/application/request-log/request-log.entity.json +3 -0
  38. package/bundle/src/application/request-log/request-log.model.test.ts +92 -0
  39. package/bundle/src/application/request-log/request-log.model.ts +20 -6
  40. package/bundle/src/application/request-log-step/request-log-step.entity.json +3 -0
  41. package/bundle/src/application/sonamu.generated.http +7 -2
  42. package/bundle/src/application/sonamu.generated.sso.ts +5 -0
  43. package/bundle/src/application/sonamu.generated.ts +11 -1
  44. package/bundle/src/application/token/token.entity.json +8 -2
  45. package/bundle/src/application/token/token.model.test.ts +69 -0
  46. package/bundle/src/application/token/token.model.ts +8 -1
  47. package/bundle/src/application/token/token.types.test.ts +34 -0
  48. package/bundle/src/application/token/token.types.ts +5 -0
  49. package/bundle/src/i18n/sd.generated.ts +3 -0
  50. package/bundle/src/migrations/20260630223950_alter_tokens_add1_alter1.ts +15 -0
  51. package/bundle/src/migrations/20260701105327_alter_request_logs_add1_alter1.ts +15 -0
  52. package/bundle/src/migrations/20260701105328_alter_request_log_steps_add1_alter1.ts +15 -0
  53. package/bundle/src/sonamu.config.ts +8 -1
  54. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +183 -15
  55. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +142 -12
  56. package/bundle/src/utils/providers/anthropic/anthropic-quota.test.ts +89 -0
  57. package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +27 -0
  58. package/bundle/src/utils/providers/anthropic/claude-session.ts +7 -2
  59. package/bundle/src/utils/providers/common/provider-dispatcher.ts +1 -0
  60. package/bundle/src/utils/providers/common/ttft.test.ts +37 -0
  61. package/bundle/src/utils/providers/common/ttft.ts +26 -0
  62. package/bundle/src/utils/providers/openai/codex-worker.test.ts +148 -0
  63. package/bundle/src/utils/providers/openai/codex-worker.ts +50 -8
  64. package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +378 -6
  65. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +332 -37
  66. package/bundle/src/utils/providers/openai/openai-quota.test.ts +85 -0
  67. package/bundle/src/utils/providers/openai/openai-quota.ts +46 -0
  68. package/bundle/web-dist/client/assets/index-5WdFEqZT.css +1 -0
  69. package/bundle/web-dist/client/assets/{index-DeThumA3.js → index-jnUY7nJ-.js} +3 -3
  70. package/bundle/web-dist/client/assets/logs-B0pWpDrg.js +1 -0
  71. package/bundle/web-dist/client/assets/routes-7XhVuG7o.js +5 -0
  72. package/bundle/web-dist/client/assets/{sd.generated-7jzAQL2_.js → sd.generated-DXhjjL2f.js} +1 -1
  73. package/bundle/web-dist/client/assets/{services.generated-Bx1Eh7ot.js → services.generated-B7u0U1wC.js} +1 -1
  74. package/bundle/web-dist/client/assets/{show-C6OofoAL.js → show-CcjZ9nr7.js} +3 -3
  75. package/bundle/web-dist/client/assets/tokens-DlwAm0v4.js +1 -0
  76. package/bundle/web-dist/client/index.html +3 -3
  77. package/bundle/web-dist/server/assets/{lazyRouteComponent-BEaI4mOq.js → lazyRouteComponent-B0LkbiYf.js} +1 -2
  78. package/bundle/web-dist/server/assets/{logs-CHM0fhfO.js → logs-dLx6gMXT.js} +17 -5
  79. package/bundle/web-dist/server/assets/{routes-C9xFMJ_r.js → routes-DD3Hb9xO.js} +201 -13
  80. package/bundle/web-dist/server/assets/{sd.generated-BeUHLWKH.js → sd.generated-DdfpD_iN.js} +131 -1
  81. package/bundle/web-dist/server/assets/{services.generated-C39hdSv0.js → services.generated-D3ZCvIkt.js} +5 -4
  82. package/bundle/web-dist/server/assets/{show-BZWqH09U.js → show-BQjeyv6A.js} +76 -25
  83. package/bundle/web-dist/server/assets/{tokens-IPwv3pNB.js → tokens-CSOsBpP7.js} +4 -3
  84. package/bundle/web-dist/server/entry-server.generated.js +6 -7
  85. package/dist/cli.js +67 -15
  86. package/package.json +1 -1
  87. package/bundle/web-dist/client/assets/index-B5DSo5uH.css +0 -1
  88. package/bundle/web-dist/client/assets/logs-CtmOVH58.js +0 -1
  89. package/bundle/web-dist/client/assets/routes-Cfoe9J0I.js +0 -5
  90. package/bundle/web-dist/client/assets/tokens-BAZq8sTo.js +0 -1
  91. package/bundle/web-dist/server/assets/shim-B9ZrFt3Z.js +0 -129
  92. /package/bundle/web-dist/server/assets/{cost-DzjfVtUr.js → cost-CSH_t17I.js} +0 -0
@@ -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,r as a}from"./index-jnUY7nJ-.js";import{i as o,t as s}from"./services.generated-B7u0U1wC.js";var c=e(n()),l=r(),u=e=>(0,l.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,l.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M5 12h14m-7-7v14`})});function d(){let[e,n]=(0,c.useState)(!1),[r,o]=(0,c.useState)(!1),[d,f]=(0,c.useState)(``),p=t(),m=s.useOauthStartMutation(),h=s.useOauthStartOpenAIMutation(),g=async e=>{if(d.trim())if(e===`openai`){let e=window.open(`about:blank`,`_blank`);o(!0);try{let{authUrl:t}=await h.mutateAsync({name:d.trim()});e?e.location.href=t:window.open(t,`_blank`);let n=setInterval(async()=>{await p.invalidateQueries({queryKey:[`Token`]}),await p.invalidateQueries({queryKey:[`Qgrid`]})},3e3);setTimeout(()=>{clearInterval(n),o(!1)},3e5)}catch(e){console.error(`OAuth start failed:`,e),o(!1)}}else{o(!0);try{let{authUrl:e}=await m.mutateAsync({name:d.trim()});window.location.href=e}catch(e){console.error(`OAuth start failed:`,e),o(!1)}}},_=()=>{n(!1),f(``),o(!1)};return(0,l.jsxs)(l.Fragment,{children:[(0,l.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:()=>n(!0),children:[(0,l.jsx)(u,{className:`size-3.5`}),`Add Token`]}),e&&(0,l.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,l.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:_,onKeyDown:()=>{}}),(0,l.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-md mx-4`,children:[(0,l.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/50`,children:(0,l.jsx)(`h2`,{className:`text-base font-medium text-sand-900`,children:`Add Token`})}),(0,l.jsxs)(`div`,{className:`px-5 py-4 space-y-4`,children:[(0,l.jsxs)(`div`,{children:[(0,l.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name *`}),(0,l.jsx)(a,{value:d,onChange:e=>f(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`})]}),(0,l.jsxs)(`div`,{className:`space-y-2`,children:[(0,l.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:!d.trim()||r,onClick:()=>g(`openai`),children:r?(0,l.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,l.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin`}),`Waiting for login...`]}):(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(i,{className:`size-4`}),`Login with OpenAI`]})}),(0,l.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,l.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`}),(0,l.jsx)(`span`,{className:`text-[10px] text-sand-400 uppercase`,children:`or`}),(0,l.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`})]}),(0,l.jsxs)(`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:!d.trim()||r,onClick:()=>g(`anthropic`),children:[(0,l.jsx)(i,{className:`size-4`}),`Login with Claude`]})]})]}),(0,l.jsx)(`div`,{className:`px-5 py-3 border-t border-sand-100/50 flex items-center justify-end gap-2`,children:(0,l.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:_,children:`Cancel`})})]})]})]})}var f=e=>(0,l.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,l.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`})}),p=e=>(0,l.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,l.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 m(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 h(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 g(e){let t=e.credentials.accessToken;return t.length<=12?t:`${t.slice(0,8)}...${t.slice(-4)}`}function _({data:e,isLoading:n}){let[r,i]=(0,c.useState)(null),[o,u]=(0,c.useState)(null),[d,_]=(0,c.useState)(``),v=t(),y=s.useRemoveTokenMutation(),b=s.useUpdateTokenMutation(),x=s.useToggleTokenMutation(),S=()=>Promise.all([v.invalidateQueries({queryKey:[`Token`]}),v.invalidateQueries({queryKey:[`Qgrid`]})]),C=async e=>{await x.mutateAsync({id:e.id}),await S()},w=async()=>{r&&(await y.mutateAsync({id:r.id}),await S(),i(null))},T=e=>{u(e),_(e.name??``)},E=async()=>{o&&(await b.mutateAsync({id:o.id,name:d.trim(),quotaThreshold:o.quota_threshold}),await S(),u(null))};if(n)return(0,l.jsx)(`div`,{className:`space-y-2`,children:Array.from({length:3}).map((e,t)=>(0,l.jsx)(`div`,{className:`h-12 bg-sand-100 rounded-md animate-pulse`},`skeleton-${t}`))});let D=e??[];return D.length===0?(0,l.jsxs)(`div`,{className:`text-sand-400 text-center py-16 text-sm`,children:[`No tokens registered. Click `,(0,l.jsx)(`strong`,{className:`text-sand-600`,children:`Add Token`}),` to get started.`]}):(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(`div`,{className:`panel overflow-hidden`,children:(0,l.jsxs)(`table`,{className:`w-full text-[13px]`,children:[(0,l.jsx)(`thead`,{children:(0,l.jsxs)(`tr`,{className:`panel-header`,children:[(0,l.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Provider`}),(0,l.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Name`}),(0,l.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Token`}),(0,l.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Status`}),(0,l.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Expires`}),(0,l.jsx)(`th`,{className:`w-20 px-3 py-2.5`})]})}),(0,l.jsx)(`tbody`,{className:`divide-y divide-sand-100/80`,children:D.map(e=>{let t=h(m(e));return(0,l.jsxs)(`tr`,{className:`transition-colors duration-150 hover:bg-sand-100/60 ${e.active?``:`opacity-50`}`,children:[(0,l.jsx)(`td`,{className:`px-5 py-3`,children:(0,l.jsx)(`span`,{className:`text-xs font-medium text-sand-500 uppercase`,children:e.provider})}),(0,l.jsx)(`td`,{className:`px-5 py-3`,children:e.name?(0,l.jsx)(`span`,{className:`text-sm text-sand-700`,children:e.name}):(0,l.jsx)(`span`,{className:`text-[11px] text-sand-400`,children:`—`})}),(0,l.jsx)(`td`,{className:`px-5 py-3`,children:(0,l.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:g(e)})}),(0,l.jsx)(`td`,{className:`px-5 py-3`,children:(0,l.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:()=>C(e),disabled:x.isPending,children:(0,l.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,l.jsx)(`td`,{className:`px-5 py-3`,children:t.text&&(0,l.jsx)(`span`,{className:`text-xs ${t.color}`,children:t.text})}),(0,l.jsx)(`td`,{className:`px-3 py-3`,children:(0,l.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,l.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-sienna-500 transition-colors duration-150`,onClick:()=>T(e),children:(0,l.jsx)(f,{className:`size-4`})}),(0,l.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-red-500 transition-colors duration-150`,onClick:()=>i(e),children:(0,l.jsx)(p,{className:`size-4`})})]})})]},e.id)})})]})}),o&&(0,l.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,l.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>u(null),onKeyDown:()=>{}}),(0,l.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,l.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/60`,children:(0,l.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Edit Token`})}),(0,l.jsx)(`div`,{className:`px-5 py-4 space-y-3`,children:(0,l.jsxs)(`div`,{children:[(0,l.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name`}),(0,l.jsx)(a,{value:d,onChange:e=>_(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,l.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,l.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:()=>u(null),children:`Cancel`}),(0,l.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:b.isPending,onClick:E,children:b.isPending?`Saving...`:`Save`})]})]})]}),r&&(0,l.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,l.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>i(null),onKeyDown:()=>{}}),(0,l.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,l.jsxs)(`div`,{className:`px-5 py-4`,children:[(0,l.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Remove Token`}),(0,l.jsxs)(`p`,{className:`text-sm text-sand-700 mt-2`,children:[`Are you sure you want to remove`,` `,(0,l.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:g(r)}),r.name&&(0,l.jsxs)(`span`,{className:`text-sand-500`,children:[` (`,r.name,`)`]}),`?`]})]}),(0,l.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,l.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:()=>i(null),children:`Cancel`}),(0,l.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:y.isPending,onClick:w,children:y.isPending?`Removing...`:`Remove`})]})]})]})]})}function v(){let{data:e,isLoading:t}=o.useTokens(`A`);return(0,l.jsxs)(`div`,{className:`max-w-6xl mx-auto space-y-4`,children:[(0,l.jsx)(`div`,{className:`flex items-center justify-end`,children:(0,l.jsx)(d,{})}),(0,l.jsx)(_,{data:e?.rows,isLoading:t})]})}export{v as component};
@@ -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-DeThumA3.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-jnUY7nJ-.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-7jzAQL2_.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-B5DSo5uH.css">
13
+ <link rel="modulepreload" crossorigin href="/assets/sd.generated-DXhjjL2f.js">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-5WdFEqZT.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1,5 +1,4 @@
1
- import { K as __commonJSMin, Y as __toESM, f as require_react_dom, g as require_react, h as require_jsx_runtime } from "./sd.generated-BeUHLWKH.js";
2
- import { t as require_shim } from "./shim-B9ZrFt3Z.js";
1
+ import { X as __toESM, _ as require_react, f as require_react_dom, g as require_jsx_runtime, p as require_shim, q as __commonJSMin } from "./sd.generated-DdfpD_iN.js";
3
2
  //#region ../../node_modules/.pnpm/@tanstack+router-core@1.143.6/node_modules/@tanstack/router-core/dist/esm/utils.js
4
3
  function last(arr) {
5
4
  return arr[arr.length - 1];
@@ -1,8 +1,8 @@
1
- import { c as number, g as require_react, h as require_jsx_runtime, l as object, u as string } from "./sd.generated-BeUHLWKH.js";
2
- import { i as TokenService, n as RequestLogService, t as QgridService } from "./services.generated-C39hdSv0.js";
3
- import { a as Link, o as useNavigate } from "./lazyRouteComponent-BEaI4mOq.js";
1
+ import { _ as require_react, c as number, g as require_jsx_runtime, l as object, u as string } from "./sd.generated-DdfpD_iN.js";
2
+ import { i as TokenService, n as RequestLogService, t as QgridService } from "./services.generated-D3ZCvIkt.js";
3
+ import { a as Link, o as useNavigate } from "./lazyRouteComponent-B0LkbiYf.js";
4
4
  import { n as Route } from "../entry-server.generated.js";
5
- import { n as formatMicroUsd, r as formatUsd, t as cacheHitRate } from "./cost-DzjfVtUr.js";
5
+ import { n as formatMicroUsd, r as formatUsd, t as cacheHitRate } from "./cost-CSH_t17I.js";
6
6
  require_react();
7
7
  var import_jsx_runtime = require_jsx_runtime();
8
8
  var lucideChevronLeft = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
@@ -52,6 +52,9 @@ function formatDuration(ms) {
52
52
  const s = total % 60;
53
53
  return m > 0 ? `${m}m ${s}s` : `${s}s`;
54
54
  }
55
+ function formatTtft(ms) {
56
+ return ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${ms}ms`;
57
+ }
55
58
  var COLUMNS = [
56
59
  {
57
60
  label: "ID",
@@ -78,6 +81,11 @@ var COLUMNS = [
78
81
  align: "left",
79
82
  width: "w-20"
80
83
  },
84
+ {
85
+ label: "TTFT",
86
+ align: "left",
87
+ width: "w-16"
88
+ },
81
89
  {
82
90
  label: "Duration",
83
91
  align: "left",
@@ -269,6 +277,10 @@ function RequestLogTable({ search, onSearchChange }) {
269
277
  children: row.model_name ?? "—"
270
278
  })
271
279
  }),
280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
281
+ className: "px-4 py-1.5 text-left tabular-nums text-sand-500 whitespace-nowrap",
282
+ children: formatTtft(row.ttft_ms)
283
+ }),
272
284
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
273
285
  className: "pl-5 pr-3 py-1.5 text-left tabular-nums text-sand-500 whitespace-nowrap",
274
286
  children: formatDuration(row.duration_ms)
@@ -355,7 +367,7 @@ function LogsPage() {
355
367
  const search = Route.useSearch();
356
368
  const navigate = Route.useNavigate();
357
369
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
358
- className: "max-w-[96rem] mx-auto",
370
+ className: "max-w-[100rem] mx-auto",
359
371
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestLogTable, {
360
372
  search,
361
373
  onSearchChange: (next) => navigate({ search: next })
@@ -1,6 +1,7 @@
1
- import { Y as __toESM, f as require_react_dom, g as require_react, h as require_jsx_runtime, m as useQueryClient } from "./sd.generated-BeUHLWKH.js";
2
- import { i as TokenService, t as QgridService } from "./services.generated-C39hdSv0.js";
3
- import { r as formatUsd } from "./cost-DzjfVtUr.js";
1
+ import { X as __toESM, _ as require_react, f as require_react_dom, g as require_jsx_runtime, h as useQueryClient } from "./sd.generated-DdfpD_iN.js";
2
+ import { i as TokenService, t as QgridService } from "./services.generated-D3ZCvIkt.js";
3
+ import { r as Q } from "../entry-server.generated.js";
4
+ import { r as formatUsd } from "./cost-CSH_t17I.js";
4
5
  //#region ../../node_modules/.pnpm/@dnd-kit+utilities@3.2.2_react@19.2.4/node_modules/@dnd-kit/utilities/dist/utilities.esm.js
5
6
  var import_react_dom = /* @__PURE__ */ __toESM(require_react_dom());
6
7
  var import_react = /* @__PURE__ */ __toESM(require_react());
@@ -2231,8 +2232,24 @@ function useSortable({ animateLayoutChanges = defaultAnimateLayoutChanges, attri
2231
2232
  }
2232
2233
  KeyboardCode.Down, KeyboardCode.Right, KeyboardCode.Up, KeyboardCode.Left;
2233
2234
  //#endregion
2234
- //#region ~icons/lucide/grip-vertical.jsx
2235
+ //#region ~icons/lucide/gauge.jsx
2235
2236
  var import_jsx_runtime = require_jsx_runtime();
2237
+ var lucideGauge = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
2238
+ viewBox: "0 0 24 24",
2239
+ width: "1.2em",
2240
+ height: "1.2em",
2241
+ ...props,
2242
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
2243
+ fill: "none",
2244
+ stroke: "currentColor",
2245
+ strokeLinecap: "round",
2246
+ strokeLinejoin: "round",
2247
+ strokeWidth: 2,
2248
+ d: "m12 14l4-4M3.34 19a10 10 0 1 1 17.32 0"
2249
+ })
2250
+ });
2251
+ //#endregion
2252
+ //#region ~icons/lucide/grip-vertical.jsx
2236
2253
  var lucideGripVertical = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
2237
2254
  viewBox: "0 0 24 24",
2238
2255
  width: "1.2em",
@@ -2280,6 +2297,27 @@ var lucideGripVertical = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2280
2297
  });
2281
2298
  //#endregion
2282
2299
  //#region src/components/qgrid/UsageCard.tsx
2300
+ var QUOTA_THRESHOLD_PROVIDERS = new Set(["anthropic", "openai"]);
2301
+ function validateThreshold(raw) {
2302
+ const trimmed = raw.trim();
2303
+ if (trimmed === "") return {
2304
+ ok: true,
2305
+ value: null
2306
+ };
2307
+ if (!/^\d+$/.test(trimmed)) return {
2308
+ ok: false,
2309
+ error: "정수만 입력하세요 (0–100)"
2310
+ };
2311
+ const n = Number(trimmed);
2312
+ if (n > 100) return {
2313
+ ok: false,
2314
+ error: "100 이하여야 합니다"
2315
+ };
2316
+ return {
2317
+ ok: true,
2318
+ value: n === 0 ? null : n
2319
+ };
2320
+ }
2283
2321
  var PROVIDER_THEMES = {
2284
2322
  openai: {
2285
2323
  stripe: "bg-openai-400",
@@ -2322,8 +2360,9 @@ function formatResets(iso) {
2322
2360
  const m = Math.floor(diff % 36e5 / 6e4);
2323
2361
  return h > 0 ? `resets ${h}h ${m}m` : `resets ${m}m`;
2324
2362
  }
2325
- function UsageRow({ label, utilization, resetsAt, theme }) {
2363
+ function UsageRow({ label, utilization, resetsAt, theme, threshold }) {
2326
2364
  const pct = utilization ?? 0;
2365
+ const hasThreshold = threshold !== void 0 && threshold !== null;
2327
2366
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2328
2367
  className: "flex items-center gap-3",
2329
2368
  children: [
@@ -2331,12 +2370,15 @@ function UsageRow({ label, utilization, resetsAt, theme }) {
2331
2370
  className: "text-xs text-sand-600 w-20 shrink-0",
2332
2371
  children: label
2333
2372
  }),
2334
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2335
- className: "flex-1 h-2 bg-sand-200 rounded-full overflow-hidden",
2336
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2374
+ className: "relative flex-1 h-2 bg-sand-200 rounded-full overflow-hidden",
2375
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2337
2376
  className: `h-full rounded-full transition-all duration-300 ${barColor(pct, theme)}`,
2338
2377
  style: { width: `${Math.min(pct, 100)}%` }
2339
- })
2378
+ }), hasThreshold && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2379
+ className: "absolute top-[-2px] bottom-[-2px] w-0.5 bg-sienna-500 rounded-full",
2380
+ style: { left: `calc(${Math.min(threshold, 100)}% - 1px)` }
2381
+ })]
2340
2382
  }),
2341
2383
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2342
2384
  className: "text-xs tabular-nums text-sand-700 w-10 text-right",
@@ -2372,13 +2414,15 @@ function TokenUsage({ token, theme }) {
2372
2414
  className: "text-[11px] text-sand-400 py-1",
2373
2415
  children: "No usage data"
2374
2416
  });
2417
+ const thresholdForPrimary = QUOTA_THRESHOLD_PROVIDERS.has(token.provider) ? token.quota_threshold : null;
2375
2418
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2376
2419
  className: "space-y-1.5 py-1",
2377
2420
  children: [data.fiveHour && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UsageRow, {
2378
2421
  label: "5h",
2379
2422
  utilization: data.fiveHour.utilization,
2380
2423
  resetsAt: data.fiveHour.resetsAt,
2381
- theme
2424
+ theme,
2425
+ threshold: thresholdForPrimary
2382
2426
  }), data.sevenDay && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UsageRow, {
2383
2427
  label: "7d",
2384
2428
  utilization: data.sevenDay.utilization,
@@ -2387,6 +2431,135 @@ function TokenUsage({ token, theme }) {
2387
2431
  })]
2388
2432
  });
2389
2433
  }
2434
+ function stopDragPropagation(e) {
2435
+ e.stopPropagation();
2436
+ }
2437
+ function ThresholdControl({ token }) {
2438
+ const [pos, setPos] = (0, import_react.useState)(null);
2439
+ const [value, setValue] = (0, import_react.useState)("");
2440
+ const triggerRef = (0, import_react.useRef)(null);
2441
+ const queryClient = useQueryClient();
2442
+ const updateMutation = QgridService.useUpdateTokenMutation();
2443
+ const supported = QUOTA_THRESHOLD_PROVIDERS.has(token.provider);
2444
+ const open = pos !== null;
2445
+ const validation = validateThreshold(value);
2446
+ const POPOVER_WIDTH = 240;
2447
+ const POPOVER_EST_HEIGHT = 150;
2448
+ const MARGIN = 8;
2449
+ const GAP = 6;
2450
+ const openPopover = () => {
2451
+ const rect = triggerRef.current?.getBoundingClientRect();
2452
+ if (!rect) return;
2453
+ const left = Math.min(Math.max(rect.right - POPOVER_WIDTH, MARGIN), Math.max(window.innerWidth - POPOVER_WIDTH - MARGIN, MARGIN));
2454
+ const above = rect.top - GAP - POPOVER_EST_HEIGHT;
2455
+ const below = rect.bottom + GAP;
2456
+ setPos({
2457
+ left,
2458
+ top: Math.min(Math.max(above >= MARGIN ? above : below, MARGIN), Math.max(window.innerHeight - POPOVER_EST_HEIGHT - MARGIN, MARGIN))
2459
+ });
2460
+ setValue(String(token.quota_threshold ?? 100));
2461
+ };
2462
+ const close = () => setPos(null);
2463
+ if (!supported) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2464
+ className: "inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md text-[10px] text-sand-300 border border-sand-100",
2465
+ title: "Quota threshold는 이 provider에서 지원되지 않습니다",
2466
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideGauge, { className: "size-3" }), "n/a"]
2467
+ });
2468
+ const adjust = (delta) => {
2469
+ const current = /^\d+$/.test(value.trim()) ? Number(value.trim()) : 0;
2470
+ const next = Math.min(Math.max(current + delta, 0), 100);
2471
+ setValue(String(next));
2472
+ };
2473
+ const save = async () => {
2474
+ if (!validation.ok) return;
2475
+ await updateMutation.mutateAsync({
2476
+ id: token.id,
2477
+ name: token.name ?? "",
2478
+ quotaThreshold: validation.value
2479
+ });
2480
+ await Promise.all([queryClient.invalidateQueries({ queryKey: ["Token"] }), queryClient.invalidateQueries({ queryKey: ["Qgrid"] })]);
2481
+ close();
2482
+ };
2483
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2484
+ className: "inline-flex",
2485
+ onPointerDown: stopDragPropagation,
2486
+ onClick: stopDragPropagation,
2487
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2488
+ ref: triggerRef,
2489
+ type: "button",
2490
+ title: "사용률 제한 설정",
2491
+ onClick: open ? close : openPopover,
2492
+ className: "flex items-center gap-1 px-1.5 py-0.5 rounded-md text-[10px] border border-sand-200/80 text-sand-400 hover:text-sand-600 hover:border-sand-300 transition-colors duration-150",
2493
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideGauge, { className: "size-3" }), "Set limit"]
2494
+ }), open && pos && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2495
+ className: "fixed inset-0 z-40",
2496
+ onPointerDown: close
2497
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2498
+ className: "fixed z-50 w-60 panel shadow-xl p-3",
2499
+ style: {
2500
+ top: pos.top,
2501
+ left: pos.left
2502
+ },
2503
+ children: [
2504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
2505
+ htmlFor: `threshold-${token.id}`,
2506
+ className: "text-[10px] uppercase tracking-wider text-sand-500 font-medium",
2507
+ children: "사용률 제한"
2508
+ }),
2509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2510
+ className: "mt-1 flex items-center gap-2",
2511
+ children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2513
+ type: "button",
2514
+ onClick: () => adjust(-10),
2515
+ className: "size-9 shrink-0 rounded-md border border-sand-200 text-sand-600 text-base leading-none hover:bg-sand-100 transition-colors duration-150",
2516
+ children: "−"
2517
+ }),
2518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Q, {
2519
+ id: `threshold-${token.id}`,
2520
+ value,
2521
+ onChange: (e) => setValue(e.target.value),
2522
+ inputMode: "numeric",
2523
+ className: `h-9 w-14 border rounded-md text-center text-sm text-sand-900 bg-white tabular-nums focus:outline-none ${validation.ok ? "border-sand-200 focus:border-sienna-300" : "border-red-300 focus:border-red-400"}`
2524
+ }),
2525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2526
+ type: "button",
2527
+ onClick: () => adjust(10),
2528
+ className: "size-9 shrink-0 rounded-md border border-sand-200 text-sand-600 text-base leading-none hover:bg-sand-100 transition-colors duration-150",
2529
+ children: "+"
2530
+ }),
2531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2532
+ className: "text-sm text-sand-500",
2533
+ children: "%"
2534
+ })
2535
+ ]
2536
+ }),
2537
+ validation.ok ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
2538
+ className: "mt-1.5 text-[11px] text-sand-500 leading-snug",
2539
+ children: validation.value === null ? "제한 없음 — 사용률과 무관하게 항상 사용합니다." : "5h 사용률이 임계치에 달하면 해당 토큰을 건너뜁니다."
2540
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
2541
+ className: "mt-1.5 text-[11px] text-red-500",
2542
+ children: validation.error
2543
+ }),
2544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2545
+ className: "mt-2 flex items-center justify-end gap-1.5",
2546
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2547
+ type: "button",
2548
+ className: "px-2 py-1 text-[11px] font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150",
2549
+ onClick: close,
2550
+ children: "Cancel"
2551
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2552
+ type: "button",
2553
+ className: "px-2 py-1 text-[11px] font-medium rounded-md bg-sienna-400 text-white hover:bg-sienna-500 disabled:opacity-50 transition-colors duration-150",
2554
+ disabled: updateMutation.isPending || !validation.ok,
2555
+ onClick: save,
2556
+ children: updateMutation.isPending ? "Saving..." : "Save"
2557
+ })]
2558
+ })
2559
+ ]
2560
+ })] })]
2561
+ });
2562
+ }
2390
2563
  function SortableTokenCard({ token }) {
2391
2564
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
2392
2565
  id: String(token.id),
@@ -2433,9 +2606,24 @@ function SortableTokenCard({ token }) {
2433
2606
  token,
2434
2607
  theme
2435
2608
  }),
2436
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2437
- className: `absolute top-2.5 right-3 px-2 py-0.5 rounded-md bg-sand-50 text-[11px] tabular-nums font-medium border border-sand-200/80 ${theme.cost}`,
2438
- children: formatUsd(costData?.usd ?? 0)
2609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2610
+ className: "mt-2 flex items-center justify-end",
2611
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThresholdControl, { token })
2612
+ }),
2613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2614
+ className: "absolute top-2.5 right-3 flex items-center gap-1.5",
2615
+ children: [QUOTA_THRESHOLD_PROVIDERS.has(token.provider) && token.quota_threshold !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2616
+ 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",
2617
+ title: `사용률 ${token.quota_threshold}% 이상이면 이 토큰 제외`,
2618
+ children: [
2619
+ "≤ ",
2620
+ token.quota_threshold,
2621
+ "%"
2622
+ ]
2623
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2624
+ className: `px-2 py-0.5 rounded-md bg-sand-50 text-[11px] tabular-nums font-medium border border-sand-200/80 ${theme.cost}`,
2625
+ children: formatUsd(costData?.usd ?? 0)
2626
+ })]
2439
2627
  })
2440
2628
  ]
2441
2629
  })]
@@ -2671,6 +2671,133 @@ var QueryClientProvider = ({ client, children }) => {
2671
2671
  });
2672
2672
  };
2673
2673
  //#endregion
2674
+ //#region ../../node_modules/.pnpm/use-sync-external-store@1.6.0_react@19.2.4/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js
2675
+ /**
2676
+ * @license React
2677
+ * use-sync-external-store-shim.production.js
2678
+ *
2679
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2680
+ *
2681
+ * This source code is licensed under the MIT license found in the
2682
+ * LICENSE file in the root directory of this source tree.
2683
+ */
2684
+ var require_use_sync_external_store_shim_production = /* @__PURE__ */ __commonJSMin(((exports) => {
2685
+ var React = require_react();
2686
+ function is(x, y) {
2687
+ return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
2688
+ }
2689
+ var objectIs = "function" === typeof Object.is ? Object.is : is, useState = React.useState, useEffect = React.useEffect, useLayoutEffect = React.useLayoutEffect, useDebugValue = React.useDebugValue;
2690
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
2691
+ var value = getSnapshot(), _useState = useState({ inst: {
2692
+ value,
2693
+ getSnapshot
2694
+ } }), inst = _useState[0].inst, forceUpdate = _useState[1];
2695
+ useLayoutEffect(function() {
2696
+ inst.value = value;
2697
+ inst.getSnapshot = getSnapshot;
2698
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
2699
+ }, [
2700
+ subscribe,
2701
+ value,
2702
+ getSnapshot
2703
+ ]);
2704
+ useEffect(function() {
2705
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
2706
+ return subscribe(function() {
2707
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
2708
+ });
2709
+ }, [subscribe]);
2710
+ useDebugValue(value);
2711
+ return value;
2712
+ }
2713
+ function checkIfSnapshotChanged(inst) {
2714
+ var latestGetSnapshot = inst.getSnapshot;
2715
+ inst = inst.value;
2716
+ try {
2717
+ var nextValue = latestGetSnapshot();
2718
+ return !objectIs(inst, nextValue);
2719
+ } catch (error) {
2720
+ return !0;
2721
+ }
2722
+ }
2723
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
2724
+ return getSnapshot();
2725
+ }
2726
+ var shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
2727
+ exports.useSyncExternalStore = void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim;
2728
+ }));
2729
+ //#endregion
2730
+ //#region ../../node_modules/.pnpm/use-sync-external-store@1.6.0_react@19.2.4/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js
2731
+ /**
2732
+ * @license React
2733
+ * use-sync-external-store-shim.development.js
2734
+ *
2735
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2736
+ *
2737
+ * This source code is licensed under the MIT license found in the
2738
+ * LICENSE file in the root directory of this source tree.
2739
+ */
2740
+ var require_use_sync_external_store_shim_development = /* @__PURE__ */ __commonJSMin(((exports) => {
2741
+ "production" !== process.env.NODE_ENV && (function() {
2742
+ function is(x, y) {
2743
+ return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
2744
+ }
2745
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
2746
+ didWarnOld18Alpha || void 0 === React.startTransition || (didWarnOld18Alpha = !0, console.error("You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."));
2747
+ var value = getSnapshot();
2748
+ if (!didWarnUncachedGetSnapshot) {
2749
+ var cachedValue = getSnapshot();
2750
+ objectIs(value, cachedValue) || (console.error("The result of getSnapshot should be cached to avoid an infinite loop"), didWarnUncachedGetSnapshot = !0);
2751
+ }
2752
+ cachedValue = useState({ inst: {
2753
+ value,
2754
+ getSnapshot
2755
+ } });
2756
+ var inst = cachedValue[0].inst, forceUpdate = cachedValue[1];
2757
+ useLayoutEffect(function() {
2758
+ inst.value = value;
2759
+ inst.getSnapshot = getSnapshot;
2760
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
2761
+ }, [
2762
+ subscribe,
2763
+ value,
2764
+ getSnapshot
2765
+ ]);
2766
+ useEffect(function() {
2767
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
2768
+ return subscribe(function() {
2769
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
2770
+ });
2771
+ }, [subscribe]);
2772
+ useDebugValue(value);
2773
+ return value;
2774
+ }
2775
+ function checkIfSnapshotChanged(inst) {
2776
+ var latestGetSnapshot = inst.getSnapshot;
2777
+ inst = inst.value;
2778
+ try {
2779
+ var nextValue = latestGetSnapshot();
2780
+ return !objectIs(inst, nextValue);
2781
+ } catch (error) {
2782
+ return !0;
2783
+ }
2784
+ }
2785
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
2786
+ return getSnapshot();
2787
+ }
2788
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
2789
+ var React = require_react(), objectIs = "function" === typeof Object.is ? Object.is : is, useState = React.useState, useEffect = React.useEffect, useLayoutEffect = React.useLayoutEffect, useDebugValue = React.useDebugValue, didWarnOld18Alpha = !1, didWarnUncachedGetSnapshot = !1, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
2790
+ exports.useSyncExternalStore = void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim;
2791
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
2792
+ })();
2793
+ }));
2794
+ //#endregion
2795
+ //#region ../../node_modules/.pnpm/use-sync-external-store@1.6.0_react@19.2.4/node_modules/use-sync-external-store/shim/index.js
2796
+ var require_shim = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2797
+ if (process.env.NODE_ENV === "production") module.exports = require_use_sync_external_store_shim_production();
2798
+ else module.exports = require_use_sync_external_store_shim_development();
2799
+ }));
2800
+ //#endregion
2674
2801
  //#region ../../node_modules/.pnpm/react-dom@19.2.4_react@19.2.4/node_modules/react-dom/cjs/react-dom.production.js
2675
2802
  /**
2676
2803
  * @license React
@@ -22420,6 +22547,7 @@ var entityLabels = {
22420
22547
  "entity.RequestLog.cache_read_tokens": "캐시 읽기 토큰",
22421
22548
  "entity.RequestLog.cache_creation_tokens": "캐시 생성 토큰",
22422
22549
  "entity.RequestLog.duration_ms": "응답 소요시간(ms)",
22550
+ "entity.RequestLog.ttft_ms": "first gen TTFT(ms)",
22423
22551
  "entity.RequestLog.cost_usd": "API 비용",
22424
22552
  "entity.RequestLog.effort": "effort level",
22425
22553
  "entity.RequestLog.history": "inject_items 히스토리",
@@ -22445,6 +22573,7 @@ var entityLabels = {
22445
22573
  "entity.RequestLogStep.cache_read_tokens": "캐시 읽기 토큰",
22446
22574
  "entity.RequestLogStep.cache_creation_tokens": "캐시 생성 토큰",
22447
22575
  "entity.RequestLogStep.duration_ms": "소요시간(ms)",
22576
+ "entity.RequestLogStep.ttft_ms": "Gen TTFT(ms)",
22448
22577
  "entity.RequestLogStep.finish_reason": "완료 사유",
22449
22578
  "entity.RequestLogStep.reasoning_text": "reasoning 텍스트",
22450
22579
  "entity.RequestLogStep.reasoning_tokens": "reasoning 토큰 수",
@@ -22467,6 +22596,7 @@ var entityLabels = {
22467
22596
  "entity.Token.name": "토큰 이름",
22468
22597
  "entity.Token.active": "활성 여부",
22469
22598
  "entity.Token.ord": "정렬 순서",
22599
+ "entity.Token.quota_threshold": "quota 사용률 임계치(%). null이면 미설정",
22470
22600
  "enum.TokenOrderBy.id-desc": "ID최신순",
22471
22601
  "enum.TokenOrderBy.ord-asc": "순서순",
22472
22602
  "enum.TokenSearchField.id": "ID",
@@ -22526,4 +22656,4 @@ SD.enumLabels = (enumName) => {
22526
22656
  } });
22527
22657
  };
22528
22658
  //#endregion
22529
- export { hashQueryKeyByOptions as A, shouldThrowError as B, fetchState as C, environmentManager as D, pendingThenable as E, partialMatchKey as F, Subscribable as G, timeUntilStale as H, replaceData as I, __require as J, __commonJSMin as K, resolveEnabled as L, matchMutation as M, matchQuery as N, functionalUpdate as O, noop$2 as P, resolveStaleTime as R, Query as S, notifyManager as T, timeoutManager as U, skipToken as V, focusManager as W, __toESM as Y, Mutation as _, fetch$1 as a, hasPreviousPage as b, number as c, require_lib as d, require_react_dom as f, require_react as g, require_jsx_runtime as h, dedupeAndFlatten as i, isValidTimeout as j, hashKey as k, object as l, useQueryClient as m, SUPPORTED_LOCALES as n, useRefreshable as o, QueryClientProvider as p, __exportAll as q, setLocale as r, useSSEStream as s, SD as t, string as u, getDefaultState as v, onlineManager as w, infiniteQueryBehavior as x, hasNextPage as y, shallowEqualObjects as z };
22659
+ export { hashKey as A, shallowEqualObjects as B, Query as C, pendingThenable as D, notifyManager as E, noop$2 as F, focusManager as G, skipToken as H, partialMatchKey as I, __exportAll as J, Subscribable as K, replaceData as L, isValidTimeout as M, matchMutation as N, environmentManager as O, matchQuery as P, resolveEnabled as R, infiniteQueryBehavior as S, onlineManager as T, timeUntilStale as U, shouldThrowError as V, timeoutManager as W, __toESM as X, __require as Y, require_react as _, fetch$1 as a, hasNextPage as b, number as c, require_lib as d, require_react_dom as f, require_jsx_runtime as g, useQueryClient as h, dedupeAndFlatten as i, hashQueryKeyByOptions as j, functionalUpdate as k, object as l, QueryClientProvider as m, SUPPORTED_LOCALES as n, useRefreshable as o, require_shim as p, __commonJSMin as q, setLocale as r, useSSEStream as s, SD as t, string as u, Mutation as v, fetchState as w, hasPreviousPage as x, getDefaultState as y, resolveStaleTime as z };
@@ -1,4 +1,4 @@
1
- import { B as shouldThrowError, C as fetchState, D as environmentManager, E as pendingThenable, G as Subscribable, H as timeUntilStale, I as replaceData, L as resolveEnabled, P as noop, R as resolveStaleTime, T as notifyManager, U as timeoutManager, W as focusManager, Y as __toESM, a as fetch, b as hasPreviousPage, d as require_lib, g as require_react, h as require_jsx_runtime, i as dedupeAndFlatten, j as isValidTimeout, k as hashKey, m as useQueryClient, o as useRefreshable, s as useSSEStream, v as getDefaultState, x as infiniteQueryBehavior, y as hasNextPage, z as shallowEqualObjects } from "./sd.generated-BeUHLWKH.js";
1
+ import { A as hashKey, B as shallowEqualObjects, D as pendingThenable, E as notifyManager, F as noop, G as focusManager, K as Subscribable, L as replaceData, M as isValidTimeout, O as environmentManager, R as resolveEnabled, S as infiniteQueryBehavior, U as timeUntilStale, V as shouldThrowError, W as timeoutManager, X as __toESM, _ as require_react, a as fetch, b as hasNextPage, d as require_lib, g as require_jsx_runtime, h as useQueryClient, i as dedupeAndFlatten, o as useRefreshable, s as useSSEStream, w as fetchState, x as hasPreviousPage, y as getDefaultState, z as resolveStaleTime } from "./sd.generated-DdfpD_iN.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) {
@@ -1036,18 +1036,19 @@ var QgridService;
1036
1036
  }
1037
1037
  _QgridService.addToken = addToken;
1038
1038
  _QgridService.useAddTokenMutation = () => useMutation({ mutationFn: (params) => addToken(params.provider, params.credentials, params.name) });
1039
- async function updateToken(id, name) {
1039
+ async function updateToken(id, name, quotaThreshold) {
1040
1040
  return fetch({
1041
1041
  method: "POST",
1042
1042
  url: `/api/qgrid/updateToken`,
1043
1043
  data: {
1044
1044
  id,
1045
- name
1045
+ name,
1046
+ quotaThreshold
1046
1047
  }
1047
1048
  });
1048
1049
  }
1049
1050
  _QgridService.updateToken = updateToken;
1050
- _QgridService.useUpdateTokenMutation = () => useMutation({ mutationFn: (params) => updateToken(params.id, params.name) });
1051
+ _QgridService.useUpdateTokenMutation = () => useMutation({ mutationFn: (params) => updateToken(params.id, params.name, params.quotaThreshold) });
1051
1052
  async function removeToken(id) {
1052
1053
  return fetch({
1053
1054
  method: "POST",