Squashed commit of the following:
commit 440bc027251d8180174d762d83d271d0f7b68cc5 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 23:04:11 2025 -0700 fix: fix check commit 89a7451cb9ee26ff9f642335714dcc6f449d1fc2 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 22:42:30 2025 -0700 fix: fix test commit 1322e3b3497e5d334fdcaa18f0cf7a98ea758df4 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 20:52:40 2025 -0700 style: add more tooltips commit 172738b98b7864aabcacf3334a394098b300283f Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 20:51:09 2025 -0700 feat(team_member_view.tsx): add a tooltip commit 895eb28deb9127985e30b5e859e5bca8530951c9 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:46:49 2025 -0700 fix(teams.tsx): support setting team member budget on create commit 003cc54a6dd0f65030c4f39a8487adc771b62e11 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:40:49 2025 -0700 fix(team_member_view.tsx): style improvements commit a627a044f21df788f80d92a4081212072be91632 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:40:01 2025 -0700 fix(team_member_view.tsx): handle scientific notation in string commit c5a3b7bd8419f6394e1b490849555d02d473baed Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:34:25 2025 -0700 feat(team_membership_view.tsx): show team member spend + max budget on UI commit e986d12ad5b07c676f4cac5e16745939d7473dee Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:28:06 2025 -0700 feat(team_member_view.tsx): show team member spend + budget on team info commit 8e398607b25f8a8f0bab41964810b5dd27c5e3f2 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:18:16 2025 -0700 feat(team_info.tsx): show team member budget on team info commit 1f56886b5913dafefc0c00fbe741c0c9c01144a6 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:15:30 2025 -0700 feat(team_endpoints.py): get team budget table on team info allows user to see max budget set for team members commit 0a4320bbfa406c24ad32a420f82152da7bdd7323 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 18:10:06 2025 -0700 feat(team_endpoints.py): return team member budget on team info allows ui to display this to admin / team member commit 6a4e29f87b333ae9977e8f878960e63becd89150 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 17:57:20 2025 -0700 fix(team_endpoints.py): support updating team budget on UI commit 53f0fff34032977433dfe6935ce0a684a4141fd8 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 17:38:17 2025 -0700 feat(proxy/_types.py): return team member spend update pydantic object to include spend Allows showing spend of team member within team on UI commit ef2a1a43ecf7fecfb904042cbf47b3d56246edcb Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 16:31:42 2025 -0700 feat(team_endpoints.py): support 'team_member_budget' param on `/team/update` enables budget working across all team members commit 512999f1249b00a02a30f049a0cfa36e829ff989 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 16:20:04 2025 -0700 test: add unit tests for default team member budget commit 90fa3f61a2d63e12b9f3e1da9775f5c8b7294b5f Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 15:37:51 2025 -0700 feat(team_endpoints.py): support using default team member budget id, if set allows all team members to use the same budget id commit acef5324b1a0935a482c71060f610c3d8823e8c3 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 15:22:30 2025 -0700 feat(team_endpoints.py): support `team_member_budget` param on `/team/new` Allow creating 1 budget for all users within team (makes it easier to increase/reduce budget if needed for all team members) commit 2e867ac70fbd8768e7c27cf3b078e6dc10e566b9 Author: Krrish Dholakia <krrishdholakia@gmail.com> Date: Fri Jun 20 13:45:06 2025 -0700 fix(ui_sso.py): ensure user is added to team, if set via default internal settings allows users signed up via SSO to be added to default team
@@ -1 +1 @@
|
||||
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/litellm-asset-prefix/_next/",i={272:0,919:0,986:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(/^(272|919|986)$/.test(e))i[e]=0;else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
|
||||
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/_next/",i={272:0,919:0,986:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(/^(272|919|986)$/.test(e))i[e]=0;else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
|
||||
@@ -1 +1 @@
|
||||
@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/55c55f0601d81cf3-s.woff2) format("woff2");unicode-range:u+0460-052f,u+1c80-1c8a,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/26a46d62cd723877-s.woff2) format("woff2");unicode-range:u+0301,u+0400-045f,u+0490-0491,u+04b0-04b1,u+2116}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/97e0cb1ae144a2a9-s.woff2) format("woff2");unicode-range:u+1f??}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/581909926a08bbc8-s.woff2) format("woff2");unicode-range:u+0370-0377,u+037a-037f,u+0384-038a,u+038c,u+038e-03a1,u+03a3-03ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/df0a9ae256c0569c-s.woff2) format("woff2");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/8e9860b6e62d6359-s.woff2) format("woff2");unicode-range:u+0100-02ba,u+02bd-02c5,u+02c7-02cc,u+02ce-02d7,u+02dd-02ff,u+0304,u+0308,u+0329,u+1d00-1dbf,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20c0,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/e4af272ccee01ff0-s.p.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:__Inter_Fallback_b0dd8a;src:local("Arial");ascent-override:90.49%;descent-override:22.56%;line-gap-override:0.00%;size-adjust:107.06%}.__className_b0dd8a{font-family:__Inter_b0dd8a,__Inter_Fallback_b0dd8a;font-style:normal}
|
||||
@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/55c55f0601d81cf3-s.woff2) format("woff2");unicode-range:u+0460-052f,u+1c80-1c8a,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/26a46d62cd723877-s.woff2) format("woff2");unicode-range:u+0301,u+0400-045f,u+0490-0491,u+04b0-04b1,u+2116}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/97e0cb1ae144a2a9-s.woff2) format("woff2");unicode-range:u+1f??}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/581909926a08bbc8-s.woff2) format("woff2");unicode-range:u+0370-0377,u+037a-037f,u+0384-038a,u+038c,u+038e-03a1,u+03a3-03ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/df0a9ae256c0569c-s.woff2) format("woff2");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/8e9860b6e62d6359-s.woff2) format("woff2");unicode-range:u+0100-02ba,u+02bd-02c5,u+02c7-02cc,u+02ce-02d7,u+02dd-02ff,u+0304,u+0308,u+0329,u+1d00-1dbf,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20c0,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/e4af272ccee01ff0-s.p.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:__Inter_Fallback_b0dd8a;src:local("Arial");ascent-override:90.49%;descent-override:22.56%;line-gap-override:0.00%;size-adjust:107.06%}.__className_b0dd8a{font-family:__Inter_b0dd8a,__Inter_Fallback_b0dd8a;font-style:normal}
|
||||
@@ -1,34 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 600 450" style="enable-background:new 0 0 600 450;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#343B45;}
|
||||
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#F4981A;}
|
||||
</style>
|
||||
<g id="_x31__stroke">
|
||||
<g id="Amazon_1_">
|
||||
<rect x="161.2" y="86.5" class="st0" width="277.8" height="277.8"/>
|
||||
<g id="Amazon">
|
||||
<path class="st1" d="M315,163.7c-8,0.6-17.2,1.2-26.4,2.4c-14.1,1.9-28.2,4.3-39.8,9.8c-22.7,9.2-38,28.8-38,57.6
|
||||
c0,36.2,23.3,54.6,52.7,54.6c9.8,0,17.8-1.2,25.1-3.1c11.7-3.7,21.5-10.4,33.1-22.7c6.7,9.2,8.6,13.5,20.2,23.3
|
||||
c3.1,1.2,6.1,1.2,8.6-0.6c7.4-6.1,20.3-17.2,27-23.3c3.1-2.5,2.5-6.1,0.6-9.2c-6.7-8.6-13.5-16-13.5-32.5V165
|
||||
c0-23.3,1.9-44.8-15.3-60.7c-14.1-12.9-36.2-17.8-53.4-17.8h-7.4c-31.2,1.8-64.3,15.3-71.7,54c-1.2,4.9,2.5,6.8,4.9,7.4l34.3,4.3
|
||||
c3.7-0.6,5.5-3.7,6.1-6.7c3.1-13.5,14.1-20.2,26.3-21.5h2.5c7.4,0,15.3,3.1,19.6,9.2c4.9,7.4,4.3,17.2,4.3,25.8L315,163.7
|
||||
L315,163.7z M308.2,236.7c-4.3,8.6-11.7,14.1-19.6,16c-1.2,0-3.1,0.6-4.9,0.6c-13.5,0-21.4-10.4-21.4-25.8
|
||||
c0-19.6,11.6-28.8,26.3-33.1c8-1.8,17.2-2.5,26.4-2.5v7.4C315,213.4,315.6,224.4,308.2,236.7z"/>
|
||||
<path class="st2" d="M398.8,311.4c-1.4,0-2.8,0.3-4.1,0.9c-1.5,0.6-3,1.3-4.4,1.9l-2.1,0.9l-2.7,1.1v0
|
||||
c-29.8,12.1-61.1,19.2-90.1,19.8c-1.1,0-2.1,0-3.2,0c-45.6,0-82.8-21.1-120.3-42c-1.3-0.7-2.7-1-4-1c-1.7,0-3.4,0.6-4.7,1.8
|
||||
c-1.3,1.2-2,2.9-2,4.7c0,2.3,1.2,4.4,2.9,5.7c35.2,30.6,73.8,59,125.7,59c1,0,2,0,3.1,0c33-0.7,70.3-11.9,99.3-30.1l0.2-0.1
|
||||
c3.8-2.3,7.6-4.9,11.2-7.7c2.2-1.6,3.8-4.2,3.8-6.9C407.2,314.6,403.2,311.4,398.8,311.4z M439,294.5L439,294.5
|
||||
c-0.1-2.9-0.7-5.1-1.9-6.9l-0.1-0.2l-0.1-0.2c-1.2-1.3-2.4-1.8-3.7-2.4c-3.8-1.5-9.3-2.3-16-2.3c-4.8,0-10.1,0.5-15.4,1.6l0-0.4
|
||||
l-5.3,1.8l-0.1,0l-3,1v0.1c-3.5,1.5-6.8,3.3-9.8,5.5c-1.9,1.4-3.4,3.2-3.5,6.1c0,1.5,0.7,3.3,2,4.3c1.3,1,2.8,1.4,4.1,1.4
|
||||
c0.3,0,0.6,0,0.9-0.1l0.3,0l0.2,0c2.6-0.6,6.4-0.9,10.9-1.6c3.8-0.4,7.9-0.7,11.4-0.7c2.5,0,4.7,0.2,6.3,0.5
|
||||
c0.8,0.2,1.3,0.4,1.6,0.5c0.1,0,0.2,0.1,0.2,0.1c0.1,0.2,0.2,0.8,0.1,1.5c0,2.9-1.2,8.4-2.9,13.7c-1.7,5.3-3.7,10.7-5,14.2
|
||||
c-0.3,0.8-0.5,1.7-0.5,2.7c0,1.4,0.6,3.2,1.8,4.3c1.2,1.1,2.8,1.6,4.1,1.6h0.1c2,0,3.6-0.8,5.1-1.9
|
||||
c13.6-12.2,18.3-31.7,18.5-42.6L439,294.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 600 450" style="enable-background:new 0 0 600 450;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#343B45;}
|
||||
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#F4981A;}
|
||||
</style>
|
||||
<g id="_x31__stroke">
|
||||
<g id="Amazon_1_">
|
||||
<rect x="161.2" y="86.5" class="st0" width="277.8" height="277.8"/>
|
||||
<g id="Amazon">
|
||||
<path class="st1" d="M315,163.7c-8,0.6-17.2,1.2-26.4,2.4c-14.1,1.9-28.2,4.3-39.8,9.8c-22.7,9.2-38,28.8-38,57.6
|
||||
c0,36.2,23.3,54.6,52.7,54.6c9.8,0,17.8-1.2,25.1-3.1c11.7-3.7,21.5-10.4,33.1-22.7c6.7,9.2,8.6,13.5,20.2,23.3
|
||||
c3.1,1.2,6.1,1.2,8.6-0.6c7.4-6.1,20.3-17.2,27-23.3c3.1-2.5,2.5-6.1,0.6-9.2c-6.7-8.6-13.5-16-13.5-32.5V165
|
||||
c0-23.3,1.9-44.8-15.3-60.7c-14.1-12.9-36.2-17.8-53.4-17.8h-7.4c-31.2,1.8-64.3,15.3-71.7,54c-1.2,4.9,2.5,6.8,4.9,7.4l34.3,4.3
|
||||
c3.7-0.6,5.5-3.7,6.1-6.7c3.1-13.5,14.1-20.2,26.3-21.5h2.5c7.4,0,15.3,3.1,19.6,9.2c4.9,7.4,4.3,17.2,4.3,25.8L315,163.7
|
||||
L315,163.7z M308.2,236.7c-4.3,8.6-11.7,14.1-19.6,16c-1.2,0-3.1,0.6-4.9,0.6c-13.5,0-21.4-10.4-21.4-25.8
|
||||
c0-19.6,11.6-28.8,26.3-33.1c8-1.8,17.2-2.5,26.4-2.5v7.4C315,213.4,315.6,224.4,308.2,236.7z"/>
|
||||
<path class="st2" d="M398.8,311.4c-1.4,0-2.8,0.3-4.1,0.9c-1.5,0.6-3,1.3-4.4,1.9l-2.1,0.9l-2.7,1.1v0
|
||||
c-29.8,12.1-61.1,19.2-90.1,19.8c-1.1,0-2.1,0-3.2,0c-45.6,0-82.8-21.1-120.3-42c-1.3-0.7-2.7-1-4-1c-1.7,0-3.4,0.6-4.7,1.8
|
||||
c-1.3,1.2-2,2.9-2,4.7c0,2.3,1.2,4.4,2.9,5.7c35.2,30.6,73.8,59,125.7,59c1,0,2,0,3.1,0c33-0.7,70.3-11.9,99.3-30.1l0.2-0.1
|
||||
c3.8-2.3,7.6-4.9,11.2-7.7c2.2-1.6,3.8-4.2,3.8-6.9C407.2,314.6,403.2,311.4,398.8,311.4z M439,294.5L439,294.5
|
||||
c-0.1-2.9-0.7-5.1-1.9-6.9l-0.1-0.2l-0.1-0.2c-1.2-1.3-2.4-1.8-3.7-2.4c-3.8-1.5-9.3-2.3-16-2.3c-4.8,0-10.1,0.5-15.4,1.6l0-0.4
|
||||
l-5.3,1.8l-0.1,0l-3,1v0.1c-3.5,1.5-6.8,3.3-9.8,5.5c-1.9,1.4-3.4,3.2-3.5,6.1c0,1.5,0.7,3.3,2,4.3c1.3,1,2.8,1.4,4.1,1.4
|
||||
c0.3,0,0.6,0,0.9-0.1l0.3,0l0.2,0c2.6-0.6,6.4-0.9,10.9-1.6c3.8-0.4,7.9-0.7,11.4-0.7c2.5,0,4.7,0.2,6.3,0.5
|
||||
c0.8,0.2,1.3,0.4,1.6,0.5c0.1,0,0.2,0.1,0.2,0.1c0.1,0.2,0.2,0.8,0.1,1.5c0,2.9-1.2,8.4-2.9,13.7c-1.7,5.3-3.7,10.7-5,14.2
|
||||
c-0.3,0.8-0.5,1.7-0.5,2.7c0,1.4,0.6,3.2,1.8,4.3c1.2,1.1,2.8,1.6,4.1,1.6h0.1c2,0,3.6-0.8,5.1-1.9
|
||||
c13.6-12.2,18.3-31.7,18.5-42.6L439,294.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,89 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F05A28;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
|
||||
</style>
|
||||
<g id="Contact">
|
||||
<g id="Contact-us" transform="translate(-234.000000, -1114.000000)">
|
||||
<g id="map" transform="translate(-6.000000, 1027.000000)">
|
||||
<g id="Contact-box" transform="translate(190.000000, 36.000000)">
|
||||
<g id="Group-26" transform="translate(50.000000, 51.000000)">
|
||||
<g id="Group-3">
|
||||
<path id="Fill-1" class="st0" d="M220.9,421c-17,0-33.1-3.4-47.8-9.5c-22-9.2-40.8-24.6-54.1-44c-13.3-19.4-21-42.7-21-67.9
|
||||
c0-16.8,3.4-32.7,9.7-47.3c9.3-21.8,24.9-40.3,44.5-53.4c19.6-13.1,43.2-20.7,68.7-20.7v-18.3c-19.5,0-38.1,3.9-55.1,11
|
||||
c-25.4,10.6-47,28.3-62.2,50.6c-15.3,22.3-24.2,49.2-24.2,78.1c0,19.3,4,37.7,11.1,54.4c10.7,25.1,28.7,46.4,51.2,61.5
|
||||
c22.6,15.1,49.8,23.9,79.1,23.9V421z"/>
|
||||
<path id="Fill-4" class="st0" d="M157.9,374.1c-11.5-9.6-20.1-21.2-25.9-33.9c-5.8-12.7-8.8-26.4-8.8-40.2
|
||||
c0-11,1.9-22,5.6-32.5c3.8-10.5,9.4-20.5,17.1-29.6c9.6-11.4,21.3-20,34-25.8c12.7-5.8,26.6-8.7,40.4-8.7
|
||||
c11,0,22.1,1.9,32.6,5.6c10.6,3.8,20.6,9.4,29.7,17l11.9-14.1c-10.8-9-22.8-15.8-35.4-20.2c-12.6-4.5-25.7-6.7-38.8-6.7
|
||||
c-16.5,0-32.9,3.5-48.1,10.4c-15.2,6.9-29.1,17.2-40.5,30.7c-9.1,10.8-15.8,22.7-20.3,35.2c-4.5,12.5-6.7,25.6-6.7,38.7
|
||||
c0,16.4,3.5,32.8,10.4,47.9c6.9,15.1,17.3,29,30.9,40.3L157.9,374.1z"/>
|
||||
<path id="Fill-6" class="st0" d="M186.4,362.2c-12.1-6.4-21.6-15.7-28.1-26.6c-6.5-10.9-9.9-23.5-9.9-36.2
|
||||
c0-11.2,2.6-22.5,8.3-33c6.4-12.1,15.8-21.5,26.8-27.9c11-6.5,23.6-9.9,36.4-9.9c11.2,0,22.6,2.6,33.2,8.2l8.6-16.3
|
||||
c-13.3-7-27.7-10.4-41.9-10.3c-16.1,0-32,4.3-45.8,12.4c-13.8,8.1-25.7,20.1-33.7,35.2c-7,13.3-10.4,27.6-10.4,41.6
|
||||
c0,16,4.3,31.8,12.5,45.5c8.2,13.8,20.2,25.5,35.4,33.5L186.4,362.2z"/>
|
||||
<path id="Fill-8" class="st0" d="M221,344.6c-6.3,0-12.3-1.3-17.7-3.6c-8.2-3.4-15.1-9.2-20-16.5c-4.9-7.3-7.8-16-7.8-25.4
|
||||
c0-6.3,1.3-12.3,3.6-17.7c3.4-8.1,9.2-15.1,16.5-20c7.3-4.9,16-7.8,25.4-7.8v-18.4c-8.8,0-17.2,1.8-24.9,5
|
||||
c-11.5,4.9-21.2,12.9-28.1,23.1C161,273.6,157,286,157,299.2c0,8.8,1.8,17.2,5,24.9c4.9,11.5,13,21.2,23.2,28.1
|
||||
C195.4,359,207.7,363,221,363V344.6z"/>
|
||||
</g>
|
||||
<g id="Group" transform="translate(22.000000, 13.000000)">
|
||||
<path id="Fill-10" class="st1" d="M214,271.6c-2.1-2.2-4.4-4-6.7-5.3c-2.3-1.3-4.7-2-7.2-2c-3.4,0-6.3,0.6-9,1.8
|
||||
c-2.6,1.2-4.9,2.8-6.8,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.4,5.4-1.4,8.2c0,2.8,0.5,5.6,1.4,8.2c1,2.6,2.4,5,4.3,7
|
||||
c1.9,2,4.1,3.7,6.8,4.9c2.6,1.2,5.6,1.8,9,1.8c2.8,0,5.5-0.6,7.9-1.7c2.4-1.2,4.5-2.9,6.2-5.1l12.2,13.1
|
||||
c-1.8,1.8-3.9,3.4-6.3,4.7c-2.4,1.3-4.8,2.4-7.2,3.2s-4.8,1.4-7,1.7c-2.2,0.4-4.2,0.5-5.8,0.5c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c4.7,0,9.4,0.9,14.1,2.7
|
||||
c4.7,1.8,8.9,4.6,12.4,8.4L214,271.6z"/>
|
||||
<path id="Fill-12" class="st1" d="M280.4,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H280.4z M243,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H243z"/>
|
||||
<path id="Fill-14" class="st1" d="M306.5,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-16" class="st1" d="M409,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H409z M371.6,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H371.6z"/>
|
||||
<path id="Fill-18" class="st1" d="M494.6,286.2c0-2.8-0.5-5.6-1.5-8.2c-1-2.6-2.4-5-4.3-7c-1.9-2-4.2-3.7-6.9-4.9
|
||||
c-2.7-1.2-5.7-1.8-9.1-1.8c-3.4,0-6.4,0.6-9.1,1.8c-2.7,1.2-5,2.8-6.9,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.5,5.4-1.5,8.2
|
||||
c0,2.8,0.5,5.6,1.5,8.2c1,2.6,2.4,5,4.3,7c1.9,2,4.2,3.7,6.9,4.9c2.7,1.2,5.7,1.8,9.1,1.8c3.4,0,6.4-0.6,9.1-1.8
|
||||
c2.7-1.2,5-2.8,6.9-4.9c1.9-2,3.3-4.4,4.3-7C494.1,291.8,494.6,289,494.6,286.2L494.6,286.2z M433.2,207.6h18.5v51.3h0.5
|
||||
c0.9-1.2,2.1-2.5,3.5-3.7c1.4-1.3,3.2-2.5,5.2-3.6c2.1-1.1,4.4-2,7.1-2.7c2.7-0.7,5.8-1.1,9.3-1.1c5.2,0,10.1,1,14.5,3
|
||||
c4.4,2,8.2,4.7,11.3,8.1c3.1,3.5,5.6,7.5,7.3,12.2c1.7,4.7,2.6,9.7,2.6,15.1c0,5.4-0.8,10.4-2.5,15.1
|
||||
c-1.6,4.7-4.1,8.7-7.2,12.2c-3.2,3.5-7,6.2-11.6,8.1c-4.5,2-9.6,3-15.3,3c-5.2,0-10.1-1-14.7-3c-4.5-2-8.1-5.3-10.8-9.7h-0.3
|
||||
v11h-17.6V207.6z"/>
|
||||
<path id="Fill-20" class="st1" d="M520.9,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-22" class="st1" d="M616,290h-3.9c-2.6,0-5.5,0.1-8.7,0.3c-3.2,0.2-6.2,0.7-9.1,1.4c-2.8,0.8-5.2,1.9-7.2,3.3
|
||||
c-2,1.5-2.9,3.5-2.9,6.2c0,1.7,0.4,3.2,1.2,4.3c0.8,1.2,1.8,2.2,3,3c1.2,0.8,2.6,1.4,4.2,1.8c1.5,0.4,3.1,0.5,4.6,0.5
|
||||
c6.4,0,11.1-1.5,14.2-4.5c3-3,4.6-7.1,4.6-12.2V290z M617.1,312.7h-0.5c-2.7,4.2-6.1,7.2-10.2,9.1c-4.1,1.9-8.7,2.8-13.6,2.8
|
||||
c-3.4,0-6.7-0.5-10-1.4s-6.1-2.3-8.7-4.1c-2.5-1.8-4.6-4.1-6.1-6.8s-2.3-5.9-2.3-9.6c0-4,0.7-7.3,2.2-10.1
|
||||
c1.4-2.8,3.4-5.1,5.8-7c2.4-1.9,5.2-3.4,8.4-4.5c3.2-1.1,6.5-2,10-2.5c3.5-0.6,6.9-0.9,10.5-1.1c3.5-0.2,6.8-0.2,9.9-0.2h4.6
|
||||
v-2c0-4.6-1.6-8-4.8-10.3c-3.2-2.3-7.3-3.4-12.2-3.4c-3.9,0-7.6,0.7-11,2.1c-3.4,1.4-6.4,3.2-8.8,5.6l-9.8-9.6
|
||||
c4.1-4.2,9-7.1,14.5-9c5.5-1.8,11.2-2.7,17.1-2.7c5.3,0,9.7,0.6,13.3,1.7c3.6,1.2,6.6,2.7,9,4.5c2.4,1.8,4.2,3.9,5.5,6.3
|
||||
c1.3,2.4,2.2,4.8,2.8,7.2c0.6,2.4,0.9,4.8,1,7.1c0.1,2.3,0.2,4.3,0.2,6v42h-16.7V312.7z"/>
|
||||
<path id="Fill-24" class="st1" d="M683.6,269.9c-3.6-5-8.4-7.5-14.4-7.5c-2.5,0-4.9,0.6-7.2,1.8c-2.4,1.2-3.5,3.2-3.5,5.9
|
||||
c0,2.2,1,3.9,2.9,4.9c1.9,1,4.4,1.9,7.4,2.6c3,0.7,6.2,1.4,9.6,2.2c3.4,0.8,6.6,1.9,9.6,3.5c3,1.6,5.4,3.7,7.4,6.5
|
||||
c1.9,2.7,2.9,6.5,2.9,11.3c0,4.4-0.9,8-2.8,11c-1.9,3-4.3,5.4-7.4,7.2c-3,1.8-6.4,3.1-10.2,4c-3.8,0.8-7.6,1.2-11.3,1.2
|
||||
c-5.7,0-11-0.8-15.8-2.4c-4.8-1.6-9.1-4.6-12.9-8.8l12.3-11.4c2.4,2.6,4.9,4.8,7.6,6.5c2.7,1.7,6,2.5,9.9,2.5
|
||||
c1.3,0,2.7-0.2,4.1-0.5c1.4-0.3,2.8-0.8,4-1.5c1.2-0.7,2.2-1.6,3-2.7c0.8-1.1,1.1-2.3,1.1-3.7c0-2.5-1-4.4-2.9-5.6
|
||||
c-1.9-1.2-4.4-2.2-7.4-3c-3-0.8-6.2-1.5-9.6-2.1c-3.4-0.7-6.6-1.7-9.6-3.2c-3-1.5-5.4-3.5-7.4-6.2c-1.9-2.6-2.9-6.3-2.9-11
|
||||
c0-4.1,0.8-7.6,2.5-10.6c1.7-3,3.9-5.4,6.7-7.4c2.8-1.9,5.9-3.3,9.5-4.3c3.6-0.9,7.2-1.4,10.9-1.4c4.9,0,9.8,0.8,14.6,2.5
|
||||
c4.8,1.7,8.7,4.5,11.7,8.6L683.6,269.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F05A28;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
|
||||
</style>
|
||||
<g id="Contact">
|
||||
<g id="Contact-us" transform="translate(-234.000000, -1114.000000)">
|
||||
<g id="map" transform="translate(-6.000000, 1027.000000)">
|
||||
<g id="Contact-box" transform="translate(190.000000, 36.000000)">
|
||||
<g id="Group-26" transform="translate(50.000000, 51.000000)">
|
||||
<g id="Group-3">
|
||||
<path id="Fill-1" class="st0" d="M220.9,421c-17,0-33.1-3.4-47.8-9.5c-22-9.2-40.8-24.6-54.1-44c-13.3-19.4-21-42.7-21-67.9
|
||||
c0-16.8,3.4-32.7,9.7-47.3c9.3-21.8,24.9-40.3,44.5-53.4c19.6-13.1,43.2-20.7,68.7-20.7v-18.3c-19.5,0-38.1,3.9-55.1,11
|
||||
c-25.4,10.6-47,28.3-62.2,50.6c-15.3,22.3-24.2,49.2-24.2,78.1c0,19.3,4,37.7,11.1,54.4c10.7,25.1,28.7,46.4,51.2,61.5
|
||||
c22.6,15.1,49.8,23.9,79.1,23.9V421z"/>
|
||||
<path id="Fill-4" class="st0" d="M157.9,374.1c-11.5-9.6-20.1-21.2-25.9-33.9c-5.8-12.7-8.8-26.4-8.8-40.2
|
||||
c0-11,1.9-22,5.6-32.5c3.8-10.5,9.4-20.5,17.1-29.6c9.6-11.4,21.3-20,34-25.8c12.7-5.8,26.6-8.7,40.4-8.7
|
||||
c11,0,22.1,1.9,32.6,5.6c10.6,3.8,20.6,9.4,29.7,17l11.9-14.1c-10.8-9-22.8-15.8-35.4-20.2c-12.6-4.5-25.7-6.7-38.8-6.7
|
||||
c-16.5,0-32.9,3.5-48.1,10.4c-15.2,6.9-29.1,17.2-40.5,30.7c-9.1,10.8-15.8,22.7-20.3,35.2c-4.5,12.5-6.7,25.6-6.7,38.7
|
||||
c0,16.4,3.5,32.8,10.4,47.9c6.9,15.1,17.3,29,30.9,40.3L157.9,374.1z"/>
|
||||
<path id="Fill-6" class="st0" d="M186.4,362.2c-12.1-6.4-21.6-15.7-28.1-26.6c-6.5-10.9-9.9-23.5-9.9-36.2
|
||||
c0-11.2,2.6-22.5,8.3-33c6.4-12.1,15.8-21.5,26.8-27.9c11-6.5,23.6-9.9,36.4-9.9c11.2,0,22.6,2.6,33.2,8.2l8.6-16.3
|
||||
c-13.3-7-27.7-10.4-41.9-10.3c-16.1,0-32,4.3-45.8,12.4c-13.8,8.1-25.7,20.1-33.7,35.2c-7,13.3-10.4,27.6-10.4,41.6
|
||||
c0,16,4.3,31.8,12.5,45.5c8.2,13.8,20.2,25.5,35.4,33.5L186.4,362.2z"/>
|
||||
<path id="Fill-8" class="st0" d="M221,344.6c-6.3,0-12.3-1.3-17.7-3.6c-8.2-3.4-15.1-9.2-20-16.5c-4.9-7.3-7.8-16-7.8-25.4
|
||||
c0-6.3,1.3-12.3,3.6-17.7c3.4-8.1,9.2-15.1,16.5-20c7.3-4.9,16-7.8,25.4-7.8v-18.4c-8.8,0-17.2,1.8-24.9,5
|
||||
c-11.5,4.9-21.2,12.9-28.1,23.1C161,273.6,157,286,157,299.2c0,8.8,1.8,17.2,5,24.9c4.9,11.5,13,21.2,23.2,28.1
|
||||
C195.4,359,207.7,363,221,363V344.6z"/>
|
||||
</g>
|
||||
<g id="Group" transform="translate(22.000000, 13.000000)">
|
||||
<path id="Fill-10" class="st1" d="M214,271.6c-2.1-2.2-4.4-4-6.7-5.3c-2.3-1.3-4.7-2-7.2-2c-3.4,0-6.3,0.6-9,1.8
|
||||
c-2.6,1.2-4.9,2.8-6.8,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.4,5.4-1.4,8.2c0,2.8,0.5,5.6,1.4,8.2c1,2.6,2.4,5,4.3,7
|
||||
c1.9,2,4.1,3.7,6.8,4.9c2.6,1.2,5.6,1.8,9,1.8c2.8,0,5.5-0.6,7.9-1.7c2.4-1.2,4.5-2.9,6.2-5.1l12.2,13.1
|
||||
c-1.8,1.8-3.9,3.4-6.3,4.7c-2.4,1.3-4.8,2.4-7.2,3.2s-4.8,1.4-7,1.7c-2.2,0.4-4.2,0.5-5.8,0.5c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c4.7,0,9.4,0.9,14.1,2.7
|
||||
c4.7,1.8,8.9,4.6,12.4,8.4L214,271.6z"/>
|
||||
<path id="Fill-12" class="st1" d="M280.4,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H280.4z M243,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H243z"/>
|
||||
<path id="Fill-14" class="st1" d="M306.5,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-16" class="st1" d="M409,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H409z M371.6,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H371.6z"/>
|
||||
<path id="Fill-18" class="st1" d="M494.6,286.2c0-2.8-0.5-5.6-1.5-8.2c-1-2.6-2.4-5-4.3-7c-1.9-2-4.2-3.7-6.9-4.9
|
||||
c-2.7-1.2-5.7-1.8-9.1-1.8c-3.4,0-6.4,0.6-9.1,1.8c-2.7,1.2-5,2.8-6.9,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.5,5.4-1.5,8.2
|
||||
c0,2.8,0.5,5.6,1.5,8.2c1,2.6,2.4,5,4.3,7c1.9,2,4.2,3.7,6.9,4.9c2.7,1.2,5.7,1.8,9.1,1.8c3.4,0,6.4-0.6,9.1-1.8
|
||||
c2.7-1.2,5-2.8,6.9-4.9c1.9-2,3.3-4.4,4.3-7C494.1,291.8,494.6,289,494.6,286.2L494.6,286.2z M433.2,207.6h18.5v51.3h0.5
|
||||
c0.9-1.2,2.1-2.5,3.5-3.7c1.4-1.3,3.2-2.5,5.2-3.6c2.1-1.1,4.4-2,7.1-2.7c2.7-0.7,5.8-1.1,9.3-1.1c5.2,0,10.1,1,14.5,3
|
||||
c4.4,2,8.2,4.7,11.3,8.1c3.1,3.5,5.6,7.5,7.3,12.2c1.7,4.7,2.6,9.7,2.6,15.1c0,5.4-0.8,10.4-2.5,15.1
|
||||
c-1.6,4.7-4.1,8.7-7.2,12.2c-3.2,3.5-7,6.2-11.6,8.1c-4.5,2-9.6,3-15.3,3c-5.2,0-10.1-1-14.7-3c-4.5-2-8.1-5.3-10.8-9.7h-0.3
|
||||
v11h-17.6V207.6z"/>
|
||||
<path id="Fill-20" class="st1" d="M520.9,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-22" class="st1" d="M616,290h-3.9c-2.6,0-5.5,0.1-8.7,0.3c-3.2,0.2-6.2,0.7-9.1,1.4c-2.8,0.8-5.2,1.9-7.2,3.3
|
||||
c-2,1.5-2.9,3.5-2.9,6.2c0,1.7,0.4,3.2,1.2,4.3c0.8,1.2,1.8,2.2,3,3c1.2,0.8,2.6,1.4,4.2,1.8c1.5,0.4,3.1,0.5,4.6,0.5
|
||||
c6.4,0,11.1-1.5,14.2-4.5c3-3,4.6-7.1,4.6-12.2V290z M617.1,312.7h-0.5c-2.7,4.2-6.1,7.2-10.2,9.1c-4.1,1.9-8.7,2.8-13.6,2.8
|
||||
c-3.4,0-6.7-0.5-10-1.4s-6.1-2.3-8.7-4.1c-2.5-1.8-4.6-4.1-6.1-6.8s-2.3-5.9-2.3-9.6c0-4,0.7-7.3,2.2-10.1
|
||||
c1.4-2.8,3.4-5.1,5.8-7c2.4-1.9,5.2-3.4,8.4-4.5c3.2-1.1,6.5-2,10-2.5c3.5-0.6,6.9-0.9,10.5-1.1c3.5-0.2,6.8-0.2,9.9-0.2h4.6
|
||||
v-2c0-4.6-1.6-8-4.8-10.3c-3.2-2.3-7.3-3.4-12.2-3.4c-3.9,0-7.6,0.7-11,2.1c-3.4,1.4-6.4,3.2-8.8,5.6l-9.8-9.6
|
||||
c4.1-4.2,9-7.1,14.5-9c5.5-1.8,11.2-2.7,17.1-2.7c5.3,0,9.7,0.6,13.3,1.7c3.6,1.2,6.6,2.7,9,4.5c2.4,1.8,4.2,3.9,5.5,6.3
|
||||
c1.3,2.4,2.2,4.8,2.8,7.2c0.6,2.4,0.9,4.8,1,7.1c0.1,2.3,0.2,4.3,0.2,6v42h-16.7V312.7z"/>
|
||||
<path id="Fill-24" class="st1" d="M683.6,269.9c-3.6-5-8.4-7.5-14.4-7.5c-2.5,0-4.9,0.6-7.2,1.8c-2.4,1.2-3.5,3.2-3.5,5.9
|
||||
c0,2.2,1,3.9,2.9,4.9c1.9,1,4.4,1.9,7.4,2.6c3,0.7,6.2,1.4,9.6,2.2c3.4,0.8,6.6,1.9,9.6,3.5c3,1.6,5.4,3.7,7.4,6.5
|
||||
c1.9,2.7,2.9,6.5,2.9,11.3c0,4.4-0.9,8-2.8,11c-1.9,3-4.3,5.4-7.4,7.2c-3,1.8-6.4,3.1-10.2,4c-3.8,0.8-7.6,1.2-11.3,1.2
|
||||
c-5.7,0-11-0.8-15.8-2.4c-4.8-1.6-9.1-4.6-12.9-8.8l12.3-11.4c2.4,2.6,4.9,4.8,7.6,6.5c2.7,1.7,6,2.5,9.9,2.5
|
||||
c1.3,0,2.7-0.2,4.1-0.5c1.4-0.3,2.8-0.8,4-1.5c1.2-0.7,2.2-1.6,3-2.7c0.8-1.1,1.1-2.3,1.1-3.7c0-2.5-1-4.4-2.9-5.6
|
||||
c-1.9-1.2-4.4-2.2-7.4-3c-3-0.8-6.2-1.5-9.6-2.1c-3.4-0.7-6.6-1.7-9.6-3.2c-3-1.5-5.4-3.5-7.4-6.2c-1.9-2.6-2.9-6.3-2.9-11
|
||||
c0-4.1,0.8-7.6,2.5-10.6c1.7-3,3.9-5.4,6.7-7.4c2.8-1.9,5.9-3.3,9.5-4.3c3.6-0.9,7.2-1.4,10.9-1.4c4.9,0,9.8,0.8,14.6,2.5
|
||||
c4.8,1.7,8.7,4.5,11.7,8.6L683.6,269.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -1,25 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 292.6 215.3" style="enable-background:new 0 0 292.6 215.3;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#566AB2;}
|
||||
</style>
|
||||
<path class="st0" d="M191.3,123.7c-2.4,1-4.9,1.8-7.2,1.9c-3.6,0.2-7.6-1.3-9.7-3.1c-3.3-2.8-5.7-4.4-6.7-9.2
|
||||
c-0.4-2.1-0.2-5.3,0.2-7.2c0.9-4-0.1-6.5-2.9-8.9c-2.3-1.9-5.2-2.4-8.4-2.4s-2.3-0.5-3.1-1c-1.3-0.7-2.4-2.3-1.4-4.4
|
||||
c0.3-0.7,2-2.3,2.3-2.5c4.3-2.5,9.4-1.7,14,0.2c4.3,1.7,7.5,5,12.2,9.5c4.8,5.5,5.6,7,8.4,11.1c2.1,3.2,4.1,6.6,5.4,10.4
|
||||
C195.2,120.5,194.2,122.4,191.3,123.7L191.3,123.7z M153.4,104.3c0-2.1,1.7-3.7,3.8-3.7s0.9,0.1,1.3,0.2c0.5,0.2,1,0.5,1.4,0.9
|
||||
c0.7,0.7,1.1,1.6,1.1,2.6c0,2.1-1.7,3.8-3.8,3.8s-3.7-1.7-3.7-3.8H153.4z M141.2,182.8c-25.5-20-37.8-26.6-42.9-26.3
|
||||
c-4.8,0.3-3.9,5.7-2.8,9.3c1.1,3.5,2.5,5.9,4.5,9c1.4,2,2.3,5.1-1.4,7.3c-8.2,5.1-22.5-1.7-23.1-2c-16.6-9.8-30.5-22.7-40.2-40.3
|
||||
c-9.5-17-14.9-35.2-15.8-54.6c-0.2-4.7,1.1-6.4,5.8-7.2c6.2-1.1,12.5-1.4,18.7-0.5c26,3.8,48.1,15.4,66.7,33.8
|
||||
c10.6,10.5,18.6,23,26.8,35.2c8.8,13,18.2,25.4,30.2,35.5c4.3,3.6,7.6,6.3,10.9,8.2c-9.8,1.1-26.1,1.3-37.2-7.5L141.2,182.8z
|
||||
M289.5,18c-3.1-1.5-4.4,1.4-6.3,2.8c-0.6,0.5-1.1,1.1-1.7,1.7c-4.5,4.8-9.8,8-16.8,7.6c-10.1-0.6-18.7,2.6-26.4,10.4
|
||||
c-1.6-9.5-7-15.2-15.2-18.9c-4.3-1.9-8.6-3.8-11.6-7.9c-2.1-2.9-2.7-6.2-3.7-9.4c-0.7-2-1.3-3.9-3.6-4.3c-2.4-0.4-3.4,1.7-4.3,3.4
|
||||
c-3.8,7-5.3,14.6-5.2,22.4c0.3,17.5,7.7,31.5,22.4,41.4c1.7,1.1,2.1,2.3,1.6,3.9c-1,3.4-2.2,6.7-3.3,10.1c-0.7,2.2-1.7,2.7-4,1.7
|
||||
c-8.1-3.4-15-8.4-21.2-14.4c-10.4-10.1-19.9-21.2-31.6-30c-2.8-2.1-5.5-4-8.4-5.7c-12-11.7,1.6-21.3,4.7-22.4
|
||||
c3.3-1.2,1.2-5.3-9.5-5.2c-10.6,0-20.3,3.6-32.8,8.4c-1.8,0.7-3.7,1.2-5.7,1.7c-11.3-2.1-22.9-2.6-35.1-1.2
|
||||
c-23,2.5-41.4,13.4-54.8,32C1,68.3-2.8,93.6,1.9,120c4.9,27.8,19.1,50.9,41,68.9c22.6,18.7,48.7,27.8,78.5,26.1
|
||||
c18.1-1,38.2-3.5,60.9-22.7c5.7,2.8,11.7,4,21.7,4.8c7.7,0.7,15.1-0.4,20.8-1.5c9-1.9,8.4-10.2,5.1-11.7
|
||||
c-26.3-12.3-20.5-7.3-25.7-11.3c13.3-15.8,33.5-32.2,41.3-85.4c0.6-4.2,0.1-6.9,0-10.3c0-2.1,0.4-2.9,2.8-3.1
|
||||
c6.6-0.8,13-2.6,18.8-5.8c17-9.3,23.9-24.6,25.5-42.9c0.2-2.8,0-5.7-3-7.2L289.5,18z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 292.6 215.3" style="enable-background:new 0 0 292.6 215.3;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#566AB2;}
|
||||
</style>
|
||||
<path class="st0" d="M191.3,123.7c-2.4,1-4.9,1.8-7.2,1.9c-3.6,0.2-7.6-1.3-9.7-3.1c-3.3-2.8-5.7-4.4-6.7-9.2
|
||||
c-0.4-2.1-0.2-5.3,0.2-7.2c0.9-4-0.1-6.5-2.9-8.9c-2.3-1.9-5.2-2.4-8.4-2.4s-2.3-0.5-3.1-1c-1.3-0.7-2.4-2.3-1.4-4.4
|
||||
c0.3-0.7,2-2.3,2.3-2.5c4.3-2.5,9.4-1.7,14,0.2c4.3,1.7,7.5,5,12.2,9.5c4.8,5.5,5.6,7,8.4,11.1c2.1,3.2,4.1,6.6,5.4,10.4
|
||||
C195.2,120.5,194.2,122.4,191.3,123.7L191.3,123.7z M153.4,104.3c0-2.1,1.7-3.7,3.8-3.7s0.9,0.1,1.3,0.2c0.5,0.2,1,0.5,1.4,0.9
|
||||
c0.7,0.7,1.1,1.6,1.1,2.6c0,2.1-1.7,3.8-3.8,3.8s-3.7-1.7-3.7-3.8H153.4z M141.2,182.8c-25.5-20-37.8-26.6-42.9-26.3
|
||||
c-4.8,0.3-3.9,5.7-2.8,9.3c1.1,3.5,2.5,5.9,4.5,9c1.4,2,2.3,5.1-1.4,7.3c-8.2,5.1-22.5-1.7-23.1-2c-16.6-9.8-30.5-22.7-40.2-40.3
|
||||
c-9.5-17-14.9-35.2-15.8-54.6c-0.2-4.7,1.1-6.4,5.8-7.2c6.2-1.1,12.5-1.4,18.7-0.5c26,3.8,48.1,15.4,66.7,33.8
|
||||
c10.6,10.5,18.6,23,26.8,35.2c8.8,13,18.2,25.4,30.2,35.5c4.3,3.6,7.6,6.3,10.9,8.2c-9.8,1.1-26.1,1.3-37.2-7.5L141.2,182.8z
|
||||
M289.5,18c-3.1-1.5-4.4,1.4-6.3,2.8c-0.6,0.5-1.1,1.1-1.7,1.7c-4.5,4.8-9.8,8-16.8,7.6c-10.1-0.6-18.7,2.6-26.4,10.4
|
||||
c-1.6-9.5-7-15.2-15.2-18.9c-4.3-1.9-8.6-3.8-11.6-7.9c-2.1-2.9-2.7-6.2-3.7-9.4c-0.7-2-1.3-3.9-3.6-4.3c-2.4-0.4-3.4,1.7-4.3,3.4
|
||||
c-3.8,7-5.3,14.6-5.2,22.4c0.3,17.5,7.7,31.5,22.4,41.4c1.7,1.1,2.1,2.3,1.6,3.9c-1,3.4-2.2,6.7-3.3,10.1c-0.7,2.2-1.7,2.7-4,1.7
|
||||
c-8.1-3.4-15-8.4-21.2-14.4c-10.4-10.1-19.9-21.2-31.6-30c-2.8-2.1-5.5-4-8.4-5.7c-12-11.7,1.6-21.3,4.7-22.4
|
||||
c3.3-1.2,1.2-5.3-9.5-5.2c-10.6,0-20.3,3.6-32.8,8.4c-1.8,0.7-3.7,1.2-5.7,1.7c-11.3-2.1-22.9-2.6-35.1-1.2
|
||||
c-23,2.5-41.4,13.4-54.8,32C1,68.3-2.8,93.6,1.9,120c4.9,27.8,19.1,50.9,41,68.9c22.6,18.7,48.7,27.8,78.5,26.1
|
||||
c18.1-1,38.2-3.5,60.9-22.7c5.7,2.8,11.7,4,21.7,4.8c7.7,0.7,15.1-0.4,20.8-1.5c9-1.9,8.4-10.2,5.1-11.7
|
||||
c-26.3-12.3-20.5-7.3-25.7-11.3c13.3-15.8,33.5-32.2,41.3-85.4c0.6-4.2,0.1-6.9,0-10.3c0-2.1,0.4-2.9,2.8-3.1
|
||||
c6.6-0.8,13-2.6,18.8-5.8c17-9.3,23.9-24.6,25.5-42.9c0.2-2.8,0-5.7-3-7.2L289.5,18z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.5862" y1="1.61" x2="36.0543" y2="44.1206">
|
||||
<stop offset="0.002" style="stop-color:#9C55D4"/>
|
||||
<stop offset="0.003" style="stop-color:#20808D"/>
|
||||
<stop offset="0.3731" style="stop-color:#218F9B"/>
|
||||
<stop offset="1" style="stop-color:#22B1BC"/>
|
||||
</linearGradient>
|
||||
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);" d="M11.469,4l11.39,10.494v-0.002V4.024h2.217v10.517
|
||||
L36.518,4v11.965h4.697v17.258h-4.683v10.654L25.077,33.813v10.18h-2.217V33.979L11.482,44V33.224H6.785V15.965h4.685V4z
|
||||
M21.188,18.155H9.002v12.878h2.477v-4.062L21.188,18.155z M13.699,27.943v11.17l9.16-8.068V19.623L13.699,27.943z M25.141,30.938
|
||||
V19.612l9.163,8.321v5.291h0.012v5.775L25.141,30.938z M36.532,31.033h2.466V18.155H26.903l9.629,8.725V31.033z M34.301,15.965
|
||||
V9.038l-7.519,6.927H34.301z M21.205,15.965h-7.519V9.038L21.205,15.965z"/>
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.5862" y1="1.61" x2="36.0543" y2="44.1206">
|
||||
<stop offset="0.002" style="stop-color:#9C55D4"/>
|
||||
<stop offset="0.003" style="stop-color:#20808D"/>
|
||||
<stop offset="0.3731" style="stop-color:#218F9B"/>
|
||||
<stop offset="1" style="stop-color:#22B1BC"/>
|
||||
</linearGradient>
|
||||
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);" d="M11.469,4l11.39,10.494v-0.002V4.024h2.217v10.517
|
||||
L36.518,4v11.965h4.697v17.258h-4.683v10.654L25.077,33.813v10.18h-2.217V33.979L11.482,44V33.224H6.785V15.965h4.685V4z
|
||||
M21.188,18.155H9.002v12.878h2.477v-4.062L21.188,18.155z M13.699,27.943v11.17l9.16-8.068V19.623L13.699,27.943z M25.141,30.938
|
||||
V19.612l9.163,8.321v5.291h0.012v5.775L25.141,30.938z M36.532,31.033h2.466V18.155H26.903l9.629,8.725V31.033z M34.301,15.965
|
||||
V9.038l-7.519,6.927H34.301z M21.205,15.965h-7.519V9.038L21.205,15.965z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -837,6 +837,7 @@ class SpecialMCPServerName(str, enum.Enum):
|
||||
all_team_servers = "all-team-mcpservers"
|
||||
all_proxy_servers = "all-proxy-mcpservers"
|
||||
|
||||
|
||||
# MCP Proxy Request Types
|
||||
class NewMCPServerRequest(LiteLLMPydanticObjectBase):
|
||||
server_id: Optional[str] = None
|
||||
@@ -1111,6 +1112,9 @@ class NewTeamRequest(TeamBase):
|
||||
tags: Optional[list] = None
|
||||
guardrails: Optional[List[str]] = None
|
||||
object_permission: Optional[LiteLLM_ObjectPermissionBase] = None
|
||||
team_member_budget: Optional[float] = (
|
||||
None # allow user to set a budget for all team members
|
||||
)
|
||||
|
||||
model_config = ConfigDict(protected_namespaces=())
|
||||
|
||||
@@ -1152,6 +1156,7 @@ class UpdateTeamRequest(LiteLLMPydanticObjectBase):
|
||||
model_aliases: Optional[dict] = None
|
||||
guardrails: Optional[List[str]] = None
|
||||
object_permission: Optional[LiteLLM_ObjectPermissionBase] = None
|
||||
team_member_budget: Optional[float] = None
|
||||
|
||||
|
||||
class ResetTeamBudgetRequest(LiteLLMPydanticObjectBase):
|
||||
@@ -2495,6 +2500,7 @@ class LiteLLM_TeamMembership(LiteLLMPydanticObjectBase):
|
||||
user_id: str
|
||||
team_id: str
|
||||
budget_id: Optional[str] = None
|
||||
spend: Optional[float] = 0.0
|
||||
litellm_budget_table: Optional[LiteLLM_BudgetTable]
|
||||
|
||||
|
||||
@@ -2646,9 +2652,13 @@ class OrganizationMemberUpdateResponse(MemberUpdateResponse):
|
||||
##########################################
|
||||
|
||||
|
||||
class TeamInfoResponseObjectTeamTable(LiteLLM_TeamTable):
|
||||
team_member_budget_table: Optional[LiteLLM_BudgetTable] = None
|
||||
|
||||
|
||||
class TeamInfoResponseObject(TypedDict):
|
||||
team_id: str
|
||||
team_info: LiteLLM_TeamTable
|
||||
team_info: TeamInfoResponseObjectTeamTable
|
||||
keys: List
|
||||
team_memberships: List[LiteLLM_TeamMembership]
|
||||
|
||||
|
||||
@@ -610,23 +610,23 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
|
||||
proxy_logging_obj=proxy_logging_obj,
|
||||
)
|
||||
if _end_user_object is not None:
|
||||
end_user_params[
|
||||
"allowed_model_region"
|
||||
] = _end_user_object.allowed_model_region
|
||||
end_user_params["allowed_model_region"] = (
|
||||
_end_user_object.allowed_model_region
|
||||
)
|
||||
if _end_user_object.litellm_budget_table is not None:
|
||||
budget_info = _end_user_object.litellm_budget_table
|
||||
if budget_info.tpm_limit is not None:
|
||||
end_user_params[
|
||||
"end_user_tpm_limit"
|
||||
] = budget_info.tpm_limit
|
||||
end_user_params["end_user_tpm_limit"] = (
|
||||
budget_info.tpm_limit
|
||||
)
|
||||
if budget_info.rpm_limit is not None:
|
||||
end_user_params[
|
||||
"end_user_rpm_limit"
|
||||
] = budget_info.rpm_limit
|
||||
end_user_params["end_user_rpm_limit"] = (
|
||||
budget_info.rpm_limit
|
||||
)
|
||||
if budget_info.max_budget is not None:
|
||||
end_user_params[
|
||||
"end_user_max_budget"
|
||||
] = budget_info.max_budget
|
||||
end_user_params["end_user_max_budget"] = (
|
||||
budget_info.max_budget
|
||||
)
|
||||
except Exception as e:
|
||||
if isinstance(e, litellm.BudgetExceededError):
|
||||
raise e
|
||||
@@ -904,6 +904,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
|
||||
|
||||
# Check 3. Check if user is in their team budget
|
||||
if valid_token.team_member_spend is not None:
|
||||
|
||||
if prisma_client is not None:
|
||||
_cache_key = f"{valid_token.team_id}_{valid_token.user_id}"
|
||||
|
||||
@@ -926,6 +927,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
|
||||
await user_api_key_cache.async_set_cache(
|
||||
key=_cache_key,
|
||||
value=team_member_info,
|
||||
ttl=5,
|
||||
)
|
||||
|
||||
if (
|
||||
|
||||
@@ -45,6 +45,7 @@ from litellm.proxy._types import (
|
||||
SpecialProxyStrings,
|
||||
TeamAddMemberResponse,
|
||||
TeamInfoResponseObject,
|
||||
TeamInfoResponseObjectTeamTable,
|
||||
TeamListResponseObject,
|
||||
TeamMemberAddRequest,
|
||||
TeamMemberDeleteRequest,
|
||||
@@ -132,6 +133,93 @@ async def get_all_team_memberships(
|
||||
return returned_tm
|
||||
|
||||
|
||||
async def _create_team_member_budget_table(
|
||||
data: Union[NewTeamRequest, LiteLLM_TeamTable],
|
||||
new_team_data_json: dict,
|
||||
user_api_key_dict: UserAPIKeyAuth,
|
||||
team_member_budget: float,
|
||||
) -> dict:
|
||||
"""Allows admin to create 1 budget, that applies to all team members"""
|
||||
from litellm.proxy._types import BudgetNewRequest
|
||||
from litellm.proxy.management_endpoints.budget_management_endpoints import (
|
||||
new_budget,
|
||||
)
|
||||
|
||||
if data.team_alias is not None:
|
||||
budget_id = (
|
||||
f"team-{data.team_alias.replace(' ', '-')}-budget-{uuid.uuid4().hex}"
|
||||
)
|
||||
else:
|
||||
budget_id = f"team-budget-{uuid.uuid4().hex}"
|
||||
|
||||
team_member_budget_table = await new_budget(
|
||||
budget_obj=BudgetNewRequest(
|
||||
max_budget=team_member_budget,
|
||||
budget_duration=data.budget_duration,
|
||||
budget_id=budget_id,
|
||||
),
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
)
|
||||
|
||||
# Add team_member_budget_id as metadata field to team table
|
||||
if new_team_data_json.get("metadata") is None:
|
||||
new_team_data_json["metadata"] = {}
|
||||
new_team_data_json["metadata"][
|
||||
"team_member_budget_id"
|
||||
] = team_member_budget_table.budget_id
|
||||
new_team_data_json.pop(
|
||||
"team_member_budget", None
|
||||
) # remove team_member_budget from new_team_data_json
|
||||
|
||||
return new_team_data_json
|
||||
|
||||
|
||||
async def _upsert_team_member_budget_table(
|
||||
team_table: LiteLLM_TeamTable,
|
||||
user_api_key_dict: UserAPIKeyAuth,
|
||||
team_member_budget: float,
|
||||
updated_kv: dict,
|
||||
) -> dict:
|
||||
"""
|
||||
Add budget if none exists
|
||||
|
||||
If budget exists, update it
|
||||
"""
|
||||
from litellm.proxy._types import BudgetNewRequest
|
||||
from litellm.proxy.management_endpoints.budget_management_endpoints import (
|
||||
update_budget,
|
||||
)
|
||||
|
||||
if team_table.metadata is None:
|
||||
team_table.metadata = {}
|
||||
|
||||
team_member_budget_id = team_table.metadata.get("team_member_budget_id")
|
||||
if team_member_budget_id is not None and isinstance(team_member_budget_id, str):
|
||||
# Budget exists
|
||||
budget_row = await update_budget(
|
||||
budget_obj=BudgetNewRequest(
|
||||
budget_id=team_member_budget_id,
|
||||
max_budget=team_member_budget,
|
||||
),
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
)
|
||||
verbose_proxy_logger.info(
|
||||
f"Updated team member budget table: {budget_row.budget_id}, with team_member_budget={team_member_budget}"
|
||||
)
|
||||
if updated_kv.get("metadata") is None:
|
||||
updated_kv["metadata"] = {}
|
||||
updated_kv["metadata"]["team_member_budget_id"] = budget_row.budget_id
|
||||
updated_kv.pop("team_member_budget", None)
|
||||
else: # budget does not exist
|
||||
updated_kv = await _create_team_member_budget_table(
|
||||
data=team_table,
|
||||
new_team_data_json=updated_kv,
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
team_member_budget=team_member_budget,
|
||||
)
|
||||
return updated_kv
|
||||
|
||||
|
||||
#### TEAM MANAGEMENT ####
|
||||
@router.post(
|
||||
"/team/new",
|
||||
@@ -311,9 +399,19 @@ async def new_team( # noqa: PLR0915
|
||||
prisma_client=prisma_client,
|
||||
)
|
||||
|
||||
## Create Team Member Budget Table
|
||||
data_json = data.json()
|
||||
if data.team_member_budget is not None:
|
||||
data_json = await _create_team_member_budget_table(
|
||||
data=data,
|
||||
new_team_data_json=data_json,
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
team_member_budget=data.team_member_budget,
|
||||
)
|
||||
|
||||
## ADD TO TEAM TABLE
|
||||
complete_team_data = LiteLLM_TeamTable(
|
||||
**data.json(),
|
||||
**data_json,
|
||||
model_id=_model_id,
|
||||
object_permission_id=object_permission_id,
|
||||
)
|
||||
@@ -668,6 +766,14 @@ async def update_team(
|
||||
# set the budget_reset_at in DB
|
||||
updated_kv["budget_reset_at"] = reset_at
|
||||
|
||||
if data.team_member_budget is not None:
|
||||
updated_kv = await _upsert_team_member_budget_table(
|
||||
team_table=existing_team_row,
|
||||
updated_kv=updated_kv,
|
||||
team_member_budget=data.team_member_budget,
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
)
|
||||
|
||||
# Check object permission
|
||||
if data.object_permission is not None:
|
||||
updated_kv = await handle_update_object_permission(
|
||||
@@ -849,8 +955,6 @@ async def team_member_add(
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
[BETA]
|
||||
|
||||
Add new members (either via user_email or user_id) to a team
|
||||
|
||||
If user doesn't exist, new user row will also be added to User Table
|
||||
@@ -944,6 +1048,11 @@ async def team_member_add(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
litellm_proxy_admin_name=litellm_proxy_admin_name,
|
||||
team_id=data.team_id,
|
||||
default_team_budget_id=(
|
||||
complete_team_data.metadata.get("team_member_budget_id")
|
||||
if complete_team_data.metadata is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
@@ -969,6 +1078,11 @@ async def team_member_add(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
litellm_proxy_admin_name=litellm_proxy_admin_name,
|
||||
team_id=data.team_id,
|
||||
default_team_budget_id=(
|
||||
complete_team_data.metadata.get("team_member_budget_id")
|
||||
if complete_team_data.metadata is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
@@ -1471,6 +1585,24 @@ def _unfurl_all_proxy_models(
|
||||
return team_info
|
||||
|
||||
|
||||
async def _add_team_member_budget_table(
|
||||
team_member_budget_id: str,
|
||||
prisma_client: PrismaClient,
|
||||
team_info_response_object: TeamInfoResponseObjectTeamTable,
|
||||
) -> TeamInfoResponseObjectTeamTable:
|
||||
try:
|
||||
team_budget = await prisma_client.db.litellm_budgettable.find_unique(
|
||||
where={"budget_id": team_member_budget_id}
|
||||
)
|
||||
team_info_response_object.team_member_budget_table = team_budget
|
||||
except Exception:
|
||||
verbose_proxy_logger.info(
|
||||
f"Team member budget table not found, passed team_member_budget_id={team_member_budget_id}"
|
||||
)
|
||||
|
||||
return team_info_response_object
|
||||
|
||||
|
||||
@router.get(
|
||||
"/team/info", tags=["team management"], dependencies=[Depends(user_api_key_auth)]
|
||||
)
|
||||
@@ -1493,6 +1625,7 @@ async def team_info(
|
||||
--header 'Authorization: Bearer your_api_key_here'
|
||||
```
|
||||
"""
|
||||
from litellm.proxy._types import TeamInfoResponseObjectTeamTable
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
try:
|
||||
@@ -1561,11 +1694,24 @@ async def team_info(
|
||||
)
|
||||
|
||||
if isinstance(team_info, dict):
|
||||
_team_info = LiteLLM_TeamTable(**team_info)
|
||||
_team_info = TeamInfoResponseObjectTeamTable(**team_info)
|
||||
elif isinstance(team_info, BaseModel):
|
||||
_team_info = LiteLLM_TeamTable(**team_info.model_dump())
|
||||
_team_info = TeamInfoResponseObjectTeamTable(**team_info.model_dump())
|
||||
else:
|
||||
_team_info = LiteLLM_TeamTable()
|
||||
_team_info = TeamInfoResponseObjectTeamTable()
|
||||
|
||||
## GET TEAM BUDGET (if exists) ##
|
||||
team_member_budget_id = (
|
||||
_team_info.metadata.get("team_member_budget_id")
|
||||
if _team_info.metadata is not None
|
||||
else None
|
||||
)
|
||||
if team_member_budget_id is not None:
|
||||
_team_info = await _add_team_member_budget_table(
|
||||
team_member_budget_id=team_member_budget_id,
|
||||
prisma_client=prisma_client,
|
||||
team_info_response_object=_team_info,
|
||||
)
|
||||
|
||||
# ## UNFURL 'all-proxy-models' into the team_info.models list ##
|
||||
# if llm_router is not None:
|
||||
|
||||
@@ -726,7 +726,7 @@ async def auth_callback(request: Request): # noqa: PLR0915
|
||||
litellm_dashboard_ui += "?login=success"
|
||||
verbose_proxy_logger.info(f"Redirecting to {litellm_dashboard_ui}")
|
||||
redirect_response = RedirectResponse(url=litellm_dashboard_ui, status_code=303)
|
||||
redirect_response.set_cookie(key="token", value=jwt_token, secure=True)
|
||||
redirect_response.set_cookie(key="token", value=jwt_token)
|
||||
return redirect_response
|
||||
|
||||
|
||||
@@ -763,9 +763,9 @@ async def insert_sso_user(
|
||||
if user_defined_values.get("max_budget") is None:
|
||||
user_defined_values["max_budget"] = litellm.max_internal_user_budget
|
||||
if user_defined_values.get("budget_duration") is None:
|
||||
user_defined_values[
|
||||
"budget_duration"
|
||||
] = litellm.internal_user_budget_duration
|
||||
user_defined_values["budget_duration"] = (
|
||||
litellm.internal_user_budget_duration
|
||||
)
|
||||
|
||||
if user_defined_values["user_role"] is None:
|
||||
user_defined_values["user_role"] = LitellmUserRoles.INTERNAL_USER_VIEW_ONLY
|
||||
@@ -783,7 +783,10 @@ async def insert_sso_user(
|
||||
if result_openid:
|
||||
new_user_request.metadata = {"auth_provider": result_openid.provider}
|
||||
|
||||
response = await new_user(data=new_user_request, user_api_key_dict=UserAPIKeyAuth())
|
||||
response = await new_user(
|
||||
data=new_user_request,
|
||||
user_api_key_dict=UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN),
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -960,9 +963,9 @@ class SSOAuthenticationHandler:
|
||||
if state:
|
||||
redirect_params["state"] = state
|
||||
elif "okta" in generic_authorization_endpoint:
|
||||
redirect_params[
|
||||
"state"
|
||||
] = uuid.uuid4().hex # set state param for okta - required
|
||||
redirect_params["state"] = (
|
||||
uuid.uuid4().hex
|
||||
) # set state param for okta - required
|
||||
return await generic_sso.get_login_redirect(**redirect_params) # type: ignore
|
||||
raise ValueError(
|
||||
"Unknown SSO provider. Please setup SSO with client IDs https://docs.litellm.ai/docs/proxy/admin_ui_sso"
|
||||
@@ -1209,9 +1212,9 @@ class MicrosoftSSOHandler:
|
||||
|
||||
# if user is trying to get the raw sso response for debugging, return the raw sso response
|
||||
if return_raw_sso_response:
|
||||
original_msft_result[
|
||||
MicrosoftSSOHandler.GRAPH_API_RESPONSE_KEY
|
||||
] = user_team_ids
|
||||
original_msft_result[MicrosoftSSOHandler.GRAPH_API_RESPONSE_KEY] = (
|
||||
user_team_ids
|
||||
)
|
||||
return original_msft_result or {}
|
||||
|
||||
result = MicrosoftSSOHandler.openid_from_response(
|
||||
@@ -1279,9 +1282,9 @@ class MicrosoftSSOHandler:
|
||||
|
||||
# Fetch user membership from Microsoft Graph API
|
||||
all_group_ids = []
|
||||
next_link: Optional[
|
||||
str
|
||||
] = MicrosoftSSOHandler.graph_api_user_groups_endpoint
|
||||
next_link: Optional[str] = (
|
||||
MicrosoftSSOHandler.graph_api_user_groups_endpoint
|
||||
)
|
||||
auth_headers = {"Authorization": f"Bearer {access_token}"}
|
||||
page_count = 0
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ async def add_new_member(
|
||||
team_id: str,
|
||||
user_api_key_dict: UserAPIKeyAuth,
|
||||
litellm_proxy_admin_name: str,
|
||||
default_team_budget_id: Optional[str] = None,
|
||||
) -> Tuple[LiteLLM_UserTable, Optional[LiteLLM_TeamMembership]]:
|
||||
"""
|
||||
Add a new member to a team
|
||||
@@ -119,11 +120,8 @@ async def add_new_member(
|
||||
)
|
||||
|
||||
# Check if trying to set a budget for team member
|
||||
if (
|
||||
max_budget_in_team is not None
|
||||
and returned_user is not None
|
||||
and returned_user.user_id is not None
|
||||
):
|
||||
|
||||
if max_budget_in_team is not None:
|
||||
# create a new budget item for this member
|
||||
response = await prisma_client.db.litellm_budgettable.create(
|
||||
data={
|
||||
@@ -134,6 +132,10 @@ async def add_new_member(
|
||||
)
|
||||
|
||||
_budget_id = response.budget_id
|
||||
else:
|
||||
_budget_id = default_team_budget_id
|
||||
|
||||
if _budget_id and returned_user is not None and returned_user.user_id is not None:
|
||||
_returned_team_membership = (
|
||||
await prisma_client.db.litellm_teammembership.create(
|
||||
data={
|
||||
|
||||
@@ -1037,6 +1037,7 @@ async def test_create_team_member_add(prisma_client, new_member_method):
|
||||
return_value=LiteLLM_TeamTableCachedObj(team_id="1234")
|
||||
)
|
||||
|
||||
print(f"team_member_add_request={team_member_add_request}")
|
||||
await team_member_add(
|
||||
data=team_member_add_request,
|
||||
user_api_key_dict=UserAPIKeyAuth(user_role="proxy_admin"),
|
||||
@@ -2207,7 +2208,9 @@ async def test_run_background_health_check_reflects_llm_model_list(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(proxy_server, "health_check_interval", 1)
|
||||
monkeypatch.setattr(proxy_server, "health_check_details", None)
|
||||
monkeypatch.setattr(proxy_server, "llm_model_list", copy.deepcopy(test_model_list_1))
|
||||
monkeypatch.setattr(
|
||||
proxy_server, "llm_model_list", copy.deepcopy(test_model_list_1)
|
||||
)
|
||||
monkeypatch.setattr(proxy_server, "perform_health_check", fake_perform_health_check)
|
||||
monkeypatch.setattr(proxy_server, "health_check_results", {})
|
||||
|
||||
@@ -2221,7 +2224,9 @@ async def test_run_background_health_check_reflects_llm_model_list(monkeypatch):
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(proxy_server, "llm_model_list", copy.deepcopy(test_model_list_2))
|
||||
monkeypatch.setattr(
|
||||
proxy_server, "llm_model_list", copy.deepcopy(test_model_list_2)
|
||||
)
|
||||
|
||||
try:
|
||||
await proxy_server._run_background_health_check()
|
||||
|
||||
@@ -581,3 +581,135 @@ def test_team_member_add_duplication_check_allows_new_member():
|
||||
except ProxyException:
|
||||
# If a ProxyException is raised, the test should fail
|
||||
pytest.fail("ProxyException should not be raised for a new member")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_team_member_budget_table_success():
|
||||
"""
|
||||
Test _add_team_member_budget_table when budget is found successfully
|
||||
"""
|
||||
from litellm.proxy._types import TeamInfoResponseObjectTeamTable
|
||||
from litellm.proxy.management_endpoints.team_endpoints import (
|
||||
_add_team_member_budget_table,
|
||||
)
|
||||
|
||||
# Mock prisma client
|
||||
mock_prisma_client = MagicMock()
|
||||
|
||||
# Mock budget record
|
||||
mock_budget_record = MagicMock()
|
||||
mock_budget_record.budget_id = "budget-123"
|
||||
mock_budget_record.max_budget = 1000.0
|
||||
|
||||
mock_prisma_client.db.litellm_budgettable.find_unique = AsyncMock(
|
||||
return_value=mock_budget_record
|
||||
)
|
||||
|
||||
# Create team info response object
|
||||
team_info_response = TeamInfoResponseObjectTeamTable(
|
||||
team_id="test-team-123", team_alias="Test Team"
|
||||
)
|
||||
|
||||
# Call the function
|
||||
result = await _add_team_member_budget_table(
|
||||
team_member_budget_id="budget-123",
|
||||
prisma_client=mock_prisma_client,
|
||||
team_info_response_object=team_info_response,
|
||||
)
|
||||
|
||||
# Verify the result
|
||||
assert result == team_info_response
|
||||
assert result.team_member_budget_table == mock_budget_record
|
||||
|
||||
# Verify database call was made correctly
|
||||
mock_prisma_client.db.litellm_budgettable.find_unique.assert_called_once_with(
|
||||
where={"budget_id": "budget-123"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_team_member_budget_table_exception_handling():
|
||||
"""
|
||||
Test _add_team_member_budget_table when an exception occurs during budget lookup
|
||||
"""
|
||||
from litellm.proxy._types import TeamInfoResponseObjectTeamTable
|
||||
from litellm.proxy.management_endpoints.team_endpoints import (
|
||||
_add_team_member_budget_table,
|
||||
)
|
||||
|
||||
# Mock prisma client to raise an exception
|
||||
mock_prisma_client = MagicMock()
|
||||
mock_prisma_client.db.litellm_budgettable.find_unique = AsyncMock(
|
||||
side_effect=Exception("Database connection failed")
|
||||
)
|
||||
|
||||
# Create team info response object
|
||||
team_info_response = TeamInfoResponseObjectTeamTable(
|
||||
team_id="test-team-456", team_alias="Test Team 2"
|
||||
)
|
||||
|
||||
# Mock the verbose_proxy_logger to capture log calls
|
||||
with patch(
|
||||
"litellm.proxy.management_endpoints.team_endpoints.verbose_proxy_logger"
|
||||
) as mock_logger:
|
||||
# Call the function
|
||||
result = await _add_team_member_budget_table(
|
||||
team_member_budget_id="nonexistent-budget-456",
|
||||
prisma_client=mock_prisma_client,
|
||||
team_info_response_object=team_info_response,
|
||||
)
|
||||
|
||||
# Verify the result is returned even when exception occurs
|
||||
assert result == team_info_response
|
||||
|
||||
# Verify team_member_budget_table is not set when exception occurs
|
||||
assert (
|
||||
not hasattr(result, "team_member_budget_table")
|
||||
or result.team_member_budget_table is None
|
||||
)
|
||||
|
||||
# Verify the error was logged
|
||||
mock_logger.info.assert_called_once_with(
|
||||
"Team member budget table not found, passed team_member_budget_id=nonexistent-budget-456"
|
||||
)
|
||||
|
||||
# Verify database call was attempted
|
||||
mock_prisma_client.db.litellm_budgettable.find_unique.assert_called_once_with(
|
||||
where={"budget_id": "nonexistent-budget-456"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_team_member_budget_table_budget_not_found():
|
||||
"""
|
||||
Test _add_team_member_budget_table when budget record is not found (returns None)
|
||||
"""
|
||||
from litellm.proxy._types import TeamInfoResponseObjectTeamTable
|
||||
from litellm.proxy.management_endpoints.team_endpoints import (
|
||||
_add_team_member_budget_table,
|
||||
)
|
||||
|
||||
# Mock prisma client to return None (budget not found)
|
||||
mock_prisma_client = MagicMock()
|
||||
mock_prisma_client.db.litellm_budgettable.find_unique = AsyncMock(return_value=None)
|
||||
|
||||
# Create team info response object
|
||||
team_info_response = TeamInfoResponseObjectTeamTable(
|
||||
team_id="test-team-789", team_alias="Test Team 3"
|
||||
)
|
||||
|
||||
# Call the function
|
||||
result = await _add_team_member_budget_table(
|
||||
team_member_budget_id="nonexistent-budget-789",
|
||||
prisma_client=mock_prisma_client,
|
||||
team_info_response_object=team_info_response,
|
||||
)
|
||||
|
||||
# Verify the result
|
||||
assert result == team_info_response
|
||||
assert result.team_member_budget_table is None
|
||||
|
||||
# Verify database call was made correctly
|
||||
mock_prisma_client.db.litellm_budgettable.find_unique.assert_called_once_with(
|
||||
where={"budget_id": "nonexistent-budget-789"}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../../../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
from litellm.proxy._types import (
|
||||
LiteLLM_TeamMembership,
|
||||
LiteLLM_UserTable,
|
||||
Member,
|
||||
UserAPIKeyAuth,
|
||||
)
|
||||
from litellm.proxy.management_helpers.utils import add_new_member
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_new_member_uses_default_team_budget_id():
|
||||
"""
|
||||
Test that add_new_member uses the default_team_budget_id when max_budget_in_team is None.
|
||||
|
||||
This test verifies that:
|
||||
1. When max_budget_in_team is None
|
||||
2. And default_team_budget_id is provided
|
||||
3. The team membership is created with the default_team_budget_id
|
||||
"""
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
|
||||
# Setup test data
|
||||
test_user_id = "test_user_123"
|
||||
test_team_id = "test_team_456"
|
||||
test_default_budget_id = "default_budget_789"
|
||||
test_admin_name = "test_admin"
|
||||
|
||||
# Create a Member object with user_id
|
||||
new_member = Member(user_id=test_user_id, role="user")
|
||||
|
||||
# Create UserAPIKeyAuth object
|
||||
user_api_key_dict = UserAPIKeyAuth(
|
||||
user_id="admin_user", user_role=LitellmUserRoles.PROXY_ADMIN
|
||||
)
|
||||
|
||||
# Mock the prisma client
|
||||
mock_prisma_client = AsyncMock()
|
||||
|
||||
# Mock the user table upsert operation
|
||||
mock_user_response = MagicMock()
|
||||
mock_user_response.model_dump.return_value = {
|
||||
"user_id": test_user_id,
|
||||
"user_email": None,
|
||||
"teams": [test_team_id],
|
||||
"user_role": "internal_user",
|
||||
}
|
||||
mock_prisma_client.db.litellm_usertable.upsert = AsyncMock(
|
||||
return_value=mock_user_response
|
||||
)
|
||||
|
||||
# Mock the team membership creation
|
||||
mock_team_membership_response = MagicMock()
|
||||
mock_team_membership_response.model_dump.return_value = {
|
||||
"team_id": test_team_id,
|
||||
"user_id": test_user_id,
|
||||
"budget_id": test_default_budget_id,
|
||||
"litellm_budget_table": None,
|
||||
}
|
||||
mock_prisma_client.db.litellm_teammembership.create = AsyncMock(
|
||||
return_value=mock_team_membership_response
|
||||
)
|
||||
|
||||
# Call the function with max_budget_in_team=None and a default_team_budget_id
|
||||
result_user, result_team_membership = await add_new_member(
|
||||
new_member=new_member,
|
||||
max_budget_in_team=None, # This is the key - no max budget specified
|
||||
prisma_client=mock_prisma_client,
|
||||
team_id=test_team_id,
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
litellm_proxy_admin_name=test_admin_name,
|
||||
default_team_budget_id=test_default_budget_id, # This should be used
|
||||
)
|
||||
|
||||
# Verify that the user was created/updated correctly
|
||||
assert result_user is not None
|
||||
assert result_user.user_id == test_user_id
|
||||
|
||||
# Verify that the team membership was created correctly
|
||||
assert result_team_membership is not None
|
||||
assert result_team_membership.team_id == test_team_id
|
||||
assert result_team_membership.user_id == test_user_id
|
||||
assert result_team_membership.budget_id == test_default_budget_id
|
||||
|
||||
# Verify that the prisma client methods were called correctly
|
||||
mock_prisma_client.db.litellm_usertable.upsert.assert_called_once()
|
||||
mock_prisma_client.db.litellm_teammembership.create.assert_called_once()
|
||||
|
||||
# Verify that no budget table creation was called (since max_budget_in_team is None)
|
||||
assert (
|
||||
not hasattr(mock_prisma_client.db, "litellm_budgettable")
|
||||
or not mock_prisma_client.db.litellm_budgettable.create.called
|
||||
)
|
||||
|
||||
# Verify the team membership was created with the correct budget_id
|
||||
team_membership_call_args = (
|
||||
mock_prisma_client.db.litellm_teammembership.create.call_args
|
||||
)
|
||||
assert team_membership_call_args is not None
|
||||
create_data = team_membership_call_args.kwargs["data"]
|
||||
assert create_data["budget_id"] == test_default_budget_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_new_member_creates_new_budget_when_max_budget_provided():
|
||||
"""
|
||||
Test that add_new_member creates a new budget when max_budget_in_team is provided.
|
||||
|
||||
This test verifies that:
|
||||
1. When max_budget_in_team is provided
|
||||
2. A new budget is created in the litellm_budgettable
|
||||
3. The new budget_id is used for the team membership
|
||||
"""
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
|
||||
# Setup test data
|
||||
test_user_id = "test_user_123"
|
||||
test_team_id = "test_team_456"
|
||||
test_max_budget = 100.0
|
||||
test_new_budget_id = "new_budget_789"
|
||||
test_admin_name = "test_admin"
|
||||
|
||||
# Create a Member object with user_id
|
||||
new_member = Member(user_id=test_user_id, role="user")
|
||||
|
||||
# Create UserAPIKeyAuth object
|
||||
user_api_key_dict = UserAPIKeyAuth(
|
||||
user_id="admin_user", user_role=LitellmUserRoles.PROXY_ADMIN
|
||||
)
|
||||
|
||||
# Mock the prisma client
|
||||
mock_prisma_client = AsyncMock()
|
||||
|
||||
# Mock the user table upsert operation
|
||||
mock_user_response = MagicMock()
|
||||
mock_user_response.model_dump.return_value = {
|
||||
"user_id": test_user_id,
|
||||
"user_email": None,
|
||||
"teams": [test_team_id],
|
||||
"user_role": "internal_user",
|
||||
}
|
||||
mock_prisma_client.db.litellm_usertable.upsert = AsyncMock(
|
||||
return_value=mock_user_response
|
||||
)
|
||||
|
||||
# Mock the budget table creation
|
||||
mock_budget_response = MagicMock()
|
||||
mock_budget_response.budget_id = test_new_budget_id
|
||||
mock_prisma_client.db.litellm_budgettable.create = AsyncMock(
|
||||
return_value=mock_budget_response
|
||||
)
|
||||
|
||||
# Mock the team membership creation
|
||||
mock_team_membership_response = MagicMock()
|
||||
mock_team_membership_response.model_dump.return_value = {
|
||||
"team_id": test_team_id,
|
||||
"user_id": test_user_id,
|
||||
"budget_id": test_new_budget_id,
|
||||
"litellm_budget_table": None,
|
||||
}
|
||||
mock_prisma_client.db.litellm_teammembership.create = AsyncMock(
|
||||
return_value=mock_team_membership_response
|
||||
)
|
||||
|
||||
# Call the function with max_budget_in_team provided
|
||||
result_user, result_team_membership = await add_new_member(
|
||||
new_member=new_member,
|
||||
max_budget_in_team=test_max_budget, # This should trigger budget creation
|
||||
prisma_client=mock_prisma_client,
|
||||
team_id=test_team_id,
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
litellm_proxy_admin_name=test_admin_name,
|
||||
default_team_budget_id=None, # Should be ignored since max_budget_in_team is provided
|
||||
)
|
||||
|
||||
# Verify that the budget was created
|
||||
mock_prisma_client.db.litellm_budgettable.create.assert_called_once()
|
||||
budget_call_args = mock_prisma_client.db.litellm_budgettable.create.call_args
|
||||
budget_data = budget_call_args.kwargs["data"]
|
||||
assert budget_data["max_budget"] == test_max_budget
|
||||
assert budget_data["created_by"] == user_api_key_dict.user_id
|
||||
assert budget_data["updated_by"] == user_api_key_dict.user_id
|
||||
|
||||
# Verify that the team membership was created with the new budget_id
|
||||
assert result_team_membership is not None
|
||||
assert result_team_membership.budget_id == test_new_budget_id
|
||||
|
||||
# Verify the team membership was created with the correct budget_id
|
||||
team_membership_call_args = (
|
||||
mock_prisma_client.db.litellm_teammembership.create.call_args
|
||||
)
|
||||
assert team_membership_call_args is not None
|
||||
create_data = team_membership_call_args.kwargs["data"]
|
||||
assert create_data["budget_id"] == test_new_budget_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_new_member_with_user_email():
|
||||
"""
|
||||
Test add_new_member with user_email instead of user_id and default budget.
|
||||
|
||||
This test verifies that:
|
||||
1. When new_member has user_email instead of user_id
|
||||
2. And max_budget_in_team is None
|
||||
3. The default_team_budget_id is used correctly
|
||||
"""
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
|
||||
# Setup test data
|
||||
test_user_email = "test@example.com"
|
||||
test_team_id = "test_team_456"
|
||||
test_default_budget_id = "default_budget_789"
|
||||
test_admin_name = "test_admin"
|
||||
|
||||
# Create a Member object with user_email
|
||||
new_member = Member(user_email=test_user_email, role="user")
|
||||
|
||||
# Create UserAPIKeyAuth object
|
||||
user_api_key_dict = UserAPIKeyAuth(
|
||||
user_id="admin_user", user_role=LitellmUserRoles.PROXY_ADMIN
|
||||
)
|
||||
|
||||
# Mock the prisma client
|
||||
mock_prisma_client = AsyncMock()
|
||||
|
||||
# Mock get_data to return empty list (no existing user)
|
||||
mock_prisma_client.get_data = AsyncMock(return_value=[])
|
||||
|
||||
# Mock insert_data for new user creation
|
||||
mock_user_response = MagicMock()
|
||||
mock_user_response.model_dump.return_value = {
|
||||
"user_id": "generated_user_id",
|
||||
"user_email": test_user_email,
|
||||
"teams": [test_team_id],
|
||||
"user_role": "internal_user",
|
||||
}
|
||||
mock_prisma_client.insert_data = AsyncMock(return_value=mock_user_response)
|
||||
|
||||
# Mock the team membership creation
|
||||
mock_team_membership_response = MagicMock()
|
||||
mock_team_membership_response.model_dump.return_value = {
|
||||
"team_id": test_team_id,
|
||||
"user_id": "generated_user_id",
|
||||
"budget_id": test_default_budget_id,
|
||||
"litellm_budget_table": None,
|
||||
}
|
||||
mock_prisma_client.db.litellm_teammembership.create = AsyncMock(
|
||||
return_value=mock_team_membership_response
|
||||
)
|
||||
|
||||
# Call the function
|
||||
result_user, result_team_membership = await add_new_member(
|
||||
new_member=new_member,
|
||||
max_budget_in_team=None,
|
||||
prisma_client=mock_prisma_client,
|
||||
team_id=test_team_id,
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
litellm_proxy_admin_name=test_admin_name,
|
||||
default_team_budget_id=test_default_budget_id,
|
||||
)
|
||||
|
||||
# Verify that the user was created correctly
|
||||
assert result_user is not None
|
||||
assert result_user.user_email == test_user_email
|
||||
|
||||
# Verify that the team membership was created with the default budget_id
|
||||
assert result_team_membership is not None
|
||||
assert result_team_membership.budget_id == test_default_budget_id
|
||||
|
||||
# Verify that get_data was called to check for existing user
|
||||
mock_prisma_client.get_data.assert_called_once_with(
|
||||
key_val={"user_email": test_user_email},
|
||||
table_name="user",
|
||||
query_type="find_all",
|
||||
)
|
||||
|
||||
# Verify that insert_data was called to create new user
|
||||
mock_prisma_client.insert_data.assert_called_once()
|
||||
insert_call_args = mock_prisma_client.insert_data.call_args
|
||||
insert_data = insert_call_args.kwargs["data"]
|
||||
assert insert_data["user_email"] == test_user_email
|
||||
assert insert_data["teams"] == [test_team_id]
|
||||
@@ -38,6 +38,23 @@ import VectorStoreSelector from "../vector_store_management/VectorStoreSelector"
|
||||
import MCPServerSelector from "../mcp_server_management/MCPServerSelector";
|
||||
import PremiumVectorStoreSelector from "../common_components/PremiumVectorStoreSelector";
|
||||
|
||||
export interface TeamMembership {
|
||||
user_id: string;
|
||||
team_id: string;
|
||||
budget_id: string;
|
||||
spend: number;
|
||||
litellm_budget_table: {
|
||||
budget_id: string;
|
||||
soft_budget: number | null;
|
||||
max_budget: number | null;
|
||||
max_parallel_requests: number | null;
|
||||
tpm_limit: number | null;
|
||||
rpm_limit: number | null;
|
||||
model_max_budget: Record<string, number> | null;
|
||||
budget_duration: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TeamData {
|
||||
team_id: string;
|
||||
team_info: {
|
||||
@@ -67,9 +84,13 @@ export interface TeamData {
|
||||
mcp_servers: string[];
|
||||
vector_stores: string[];
|
||||
};
|
||||
team_member_budget_table: {
|
||||
max_budget: number;
|
||||
budget_duration: string;
|
||||
} | null;
|
||||
};
|
||||
keys: any[];
|
||||
team_memberships: any[];
|
||||
team_memberships: TeamMembership[];
|
||||
}
|
||||
|
||||
export interface TeamInfoProps {
|
||||
@@ -235,6 +256,10 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
||||
organization_id: values.organization_id,
|
||||
};
|
||||
|
||||
if (values.team_member_budget !== undefined) {
|
||||
updateData.team_member_budget = Number(values.team_member_budget);
|
||||
}
|
||||
|
||||
// Handle object_permission updates
|
||||
if (values.vector_stores !== undefined || values.mcp_servers !== undefined) {
|
||||
updateData.object_permission = {
|
||||
@@ -298,6 +323,10 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
||||
{info.budget_duration && (
|
||||
<Text className="text-gray-500">Reset: {info.budget_duration}</Text>
|
||||
)}
|
||||
<br/>
|
||||
{info.team_member_budget_table && (
|
||||
<Text className="text-gray-500">Team Member Budget: ${info.team_member_budget_table.max_budget}</Text>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -420,6 +449,10 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
||||
<NumericalInput step={0.01} precision={2} style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Team Member Budget (USD)" name="team_member_budget" tooltip="This is the individual budget for a user in the team.">
|
||||
<NumericalInput step={0.01} precision={2} style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Reset Budget" name="budget_duration">
|
||||
<Select placeholder="n/a">
|
||||
<Select.Option value="24h">daily</Select.Option>
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
Icon,
|
||||
Button as TremorButton,
|
||||
} from '@tremor/react';
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Tooltip } from "antd";
|
||||
import {
|
||||
TeamData,
|
||||
} from './team_info';
|
||||
@@ -34,6 +36,40 @@ const TeamMembersComponent: React.FC<TeamMembersComponentProps> = ({
|
||||
setIsEditMemberModalVisible,
|
||||
setIsAddMemberModalVisible,
|
||||
}) => {
|
||||
// Helper function to convert scientific notation to normal decimal format
|
||||
const formatNumber = (value: number | null): string => {
|
||||
if (value === null || value === undefined) return '0';
|
||||
|
||||
if (typeof value === 'number') {
|
||||
// Convert scientific notation to normal decimal
|
||||
const normalNumber = Number(value);
|
||||
|
||||
// If it's a whole number, return it without decimals
|
||||
if (normalNumber === Math.floor(normalNumber)) {
|
||||
return normalNumber.toString();
|
||||
}
|
||||
|
||||
// For decimal numbers, use toFixed and remove trailing zeros
|
||||
return normalNumber.toFixed(8).replace(/\.?0+$/, '');
|
||||
}
|
||||
|
||||
return '0';
|
||||
};
|
||||
|
||||
// Helper function to get spend for a user
|
||||
const getUserSpend = (userId: string | null): number | null => {
|
||||
if (!userId) return 0;
|
||||
const membership = teamData.team_memberships.find(tm => tm.user_id === userId);
|
||||
return membership?.spend || 0;
|
||||
};
|
||||
|
||||
const getUserBudget = (userId: string | null): string | null => {
|
||||
if (!userId) return null;
|
||||
const membership = teamData.team_memberships.find(tm => tm.user_id === userId);
|
||||
console.log(`membership=${membership}`);
|
||||
return formatNumber(membership?.litellm_budget_table?.max_budget || null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]">
|
||||
@@ -43,6 +79,13 @@ const TeamMembersComponent: React.FC<TeamMembersComponentProps> = ({
|
||||
<TableHeaderCell>User ID</TableHeaderCell>
|
||||
<TableHeaderCell>User Email</TableHeaderCell>
|
||||
<TableHeaderCell>Role</TableHeaderCell>
|
||||
<TableHeaderCell>Team Member Spend (USD)
|
||||
{" "}
|
||||
<Tooltip title="This is the amount spent by a user in the team.">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>Team Member Budget (USD)</TableHeaderCell>
|
||||
<TableHeaderCell></TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -54,11 +97,17 @@ const TeamMembersComponent: React.FC<TeamMembersComponentProps> = ({
|
||||
<Text className="font-mono">{member.user_id}</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text className="font-mono">{member.user_email}</Text>
|
||||
<Text className="font-mono">{member.user_email ? member.user_email : 'No Email'}</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text className="font-mono">{member.role}</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text className="font-mono">${formatNumber(getUserSpend(member.user_id))}</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text className="font-mono">{getUserBudget(member.user_id) ? `$${getUserBudget(member.user_id)}` : 'No Limit'}</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{canEditTeam && (
|
||||
<>
|
||||
|
||||
@@ -1052,6 +1052,14 @@ const Teams: React.FC<TeamProps> = ({
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Team Member Budget (USD)"
|
||||
name="team_member_budget"
|
||||
normalize={(value) => value ? Number(value) : undefined}
|
||||
tooltip="This is the individual budget for a user in the team."
|
||||
>
|
||||
<NumericalInput step={0.01} precision={2} width={200} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Metadata" name="metadata" help="Additional team metadata. Enter metadata as JSON object.">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||