@nextclaw/ui 0.5.36 → 0.5.38

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 (43) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/assets/ChannelsList-3B_zyiKA.js +1 -0
  3. package/dist/assets/{ChatPage-B5OG3EW3.js → ChatPage-DusH09PT.js} +1 -1
  4. package/dist/assets/{CronConfig-MXdvM9gu.js → CronConfig-5GTz5wPt.js} +1 -1
  5. package/dist/assets/DocBrowser-BtqGmg0N.js +1 -0
  6. package/dist/assets/MarketplacePage-BEW4M9BT.js +49 -0
  7. package/dist/assets/{ModelConfig-qJyJ1XS-.js → ModelConfig-CwxXYqME.js} +1 -1
  8. package/dist/assets/ProvidersList-D4oaYHpJ.js +1 -0
  9. package/dist/assets/{RuntimeConfig-DoKy3o8n.js → RuntimeConfig-BwTxGi_U.js} +1 -1
  10. package/dist/assets/{SecretsConfig-S_jppujG.js → SecretsConfig-x36MY4ym.js} +1 -1
  11. package/dist/assets/{SessionsConfig-C5VnCiw_.js → SessionsConfig-qEffYDZ0.js} +1 -1
  12. package/dist/assets/{card-Bsb-eVmY.js → card-Bq6uwDJQ.js} +1 -1
  13. package/dist/assets/index-DMEuanmd.css +1 -0
  14. package/dist/assets/index-wB2uPrKu.js +2 -0
  15. package/dist/assets/{label-CXP5KktX.js → label-Cq1vSfWg.js} +1 -1
  16. package/dist/assets/logos-BKBMs40Q.js +1 -0
  17. package/dist/assets/{page-layout-DMWzimj9.js → page-layout-D8MW2vP-.js} +1 -1
  18. package/dist/assets/{switch-oEZ0AFmj.js → switch-CycMxy31.js} +1 -1
  19. package/dist/assets/{tabs-custom-CQP93tp3.js → tabs-custom-N4olWJSw.js} +1 -1
  20. package/dist/assets/{useConfig-C0nxJgik.js → useConfig-tR_KAfMV.js} +1 -1
  21. package/dist/assets/{useConfirmDialog-d6TTs8io.js → useConfirmDialog-DE0Yp8Ai.js} +1 -1
  22. package/dist/index.html +2 -2
  23. package/package.json +1 -1
  24. package/src/api/marketplace.ts +24 -0
  25. package/src/api/types.ts +28 -0
  26. package/src/components/config/ChannelForm.tsx +22 -19
  27. package/src/components/config/ChannelsList.tsx +7 -6
  28. package/src/components/config/ModelConfig.tsx +1 -0
  29. package/src/components/config/ProviderForm.tsx +4 -2
  30. package/src/components/config/ProvidersList.tsx +3 -2
  31. package/src/components/config/config-layout.ts +10 -0
  32. package/src/components/doc-browser/DocBrowser.tsx +382 -323
  33. package/src/components/doc-browser/DocBrowserContext.tsx +389 -157
  34. package/src/components/layout/Sidebar.tsx +1 -1
  35. package/src/components/marketplace/MarketplacePage.tsx +252 -12
  36. package/src/lib/i18n.ts +21 -2
  37. package/dist/assets/ChannelsList-BTQcN7OQ.js +0 -1
  38. package/dist/assets/DocBrowser-CJDon901.js +0 -1
  39. package/dist/assets/MarketplacePage-BwaTwPfP.js +0 -1
  40. package/dist/assets/ProvidersList-DinfLIyS.js +0 -1
  41. package/dist/assets/index-BzQBLXUW.js +0 -2
  42. package/dist/assets/index-DcyOd66N.css +0 -1
  43. package/dist/assets/logos-DqE_6ErA.js +0 -1
@@ -1 +1 @@
1
- import{j as e}from"./vendor-DN_iJQc4.js";import{f as m,c as s}from"./index-BzQBLXUW.js";function c({tabs:a,activeTab:i,onChange:o,className:n}){return e.jsx("div",{className:s("flex items-center gap-6 border-b border-gray-200/60 mb-6",n),children:a.map(t=>{const r=i===t.id;return e.jsxs("button",{onClick:()=>o(t.id),className:s("relative pb-3 text-[14px] font-medium transition-all duration-fast flex items-center gap-1.5",r?"text-gray-900":"text-gray-600 hover:text-gray-900"),children:[t.label,t.count!==void 0&&e.jsx("span",{className:s("text-[11px] font-medium","text-gray-500"),children:m(t.count)}),r&&e.jsx("div",{className:"absolute bottom-0 left-0 right-0 h-[2px] bg-primary rounded-full"})]},t.id)})})}export{c as T};
1
+ import{j as e}from"./vendor-DN_iJQc4.js";import{f as m,c as s}from"./index-wB2uPrKu.js";function c({tabs:a,activeTab:i,onChange:o,className:n}){return e.jsx("div",{className:s("flex items-center gap-6 border-b border-gray-200/60 mb-6",n),children:a.map(t=>{const r=i===t.id;return e.jsxs("button",{onClick:()=>o(t.id),className:s("relative pb-3 text-[14px] font-medium transition-all duration-fast flex items-center gap-1.5",r?"text-gray-900":"text-gray-600 hover:text-gray-900"),children:[t.label,t.count!==void 0&&e.jsx("span",{className:s("text-[11px] font-medium","text-gray-500"),children:m(t.count)}),r&&e.jsx("div",{className:"absolute bottom-0 left-0 right-0 h-[2px] bg-primary rounded-full"})]},t.id)})})}export{c as T};
@@ -1,4 +1,4 @@
1
- import{r as C,j as F,aq as m,ar as f,as as u,ag as s}from"./vendor-DN_iJQc4.js";import{c as b,A as x,j as i,t as o}from"./index-BzQBLXUW.js";const K=C.forwardRef(({className:n,type:e,...r},a)=>F.jsx("input",{type:e,className:b("flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50",n),ref:a,...r}));K.displayName="Input";async function k(){const n=await i.get("/api/config");if(!n.ok)throw new Error(n.error.message);return n.data}async function A(){const n=await i.get("/api/config/meta");if(!n.ok)throw new Error(n.error.message);return n.data}async function M(){const n=await i.get("/api/config/schema");if(!n.ok)throw new Error(n.error.message);return n.data}async function R(n){const e=await i.put("/api/config/model",n);if(!e.ok)throw new Error(e.error.message);return e.data}async function J(n,e){const r=await i.put(`/api/config/providers/${n}`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function Q(n,e){const r=await i.post(`/api/config/providers/${n}/test`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function U(n,e){const r=await i.put(`/api/config/channels/${n}`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function $(n){const e=await i.put("/api/config/runtime",n);if(!e.ok)throw new Error(e.error.message);return e.data}async function T(n){const e=await i.put("/api/config/secrets",n);if(!e.ok)throw new Error(e.error.message);return e.data}async function I(n,e){const r=await i.post(`/api/config/actions/${n}/execute`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function P(n){var d;const e=new URLSearchParams;(d=n==null?void 0:n.q)!=null&&d.trim()&&e.set("q",n.q.trim()),typeof(n==null?void 0:n.limit)=="number"&&Number.isFinite(n.limit)&&e.set("limit",String(Math.max(0,Math.trunc(n.limit)))),typeof(n==null?void 0:n.activeMinutes)=="number"&&Number.isFinite(n.activeMinutes)&&e.set("activeMinutes",String(Math.max(0,Math.trunc(n.activeMinutes))));const r=e.toString(),a=await i.get(r?"/api/sessions?"+r:"/api/sessions");if(!a.ok)throw new Error(a.error.message);return a.data}async function N(n,e=200){const r=await i.get(`/api/sessions/${encodeURIComponent(n)}/history?limit=${Math.max(1,Math.trunc(e))}`);if(!r.ok)throw new Error(r.error.message);return r.data}async function O(n,e){const r=await i.put(`/api/sessions/${encodeURIComponent(n)}`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function j(n){const e=await i.delete(`/api/sessions/${encodeURIComponent(n)}`);if(!e.ok)throw new Error(e.error.message);return e.data}function D(n){const e=n.split(/\r?\n/);let r="message";const a=[];for(const d of e){if(d.startsWith("event:")){r=d.slice(6).trim()||"message";continue}d.startsWith("data:")&&a.push(d.slice(5).trimStart())}return a.length===0?null:{event:r,data:a.join(`
1
+ import{r as C,j as F,aq as m,ar as f,as as u,ag as s}from"./vendor-DN_iJQc4.js";import{c as b,A as x,j as i,t as o}from"./index-wB2uPrKu.js";const K=C.forwardRef(({className:n,type:e,...r},a)=>F.jsx("input",{type:e,className:b("flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50",n),ref:a,...r}));K.displayName="Input";async function k(){const n=await i.get("/api/config");if(!n.ok)throw new Error(n.error.message);return n.data}async function A(){const n=await i.get("/api/config/meta");if(!n.ok)throw new Error(n.error.message);return n.data}async function M(){const n=await i.get("/api/config/schema");if(!n.ok)throw new Error(n.error.message);return n.data}async function R(n){const e=await i.put("/api/config/model",n);if(!e.ok)throw new Error(e.error.message);return e.data}async function J(n,e){const r=await i.put(`/api/config/providers/${n}`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function Q(n,e){const r=await i.post(`/api/config/providers/${n}/test`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function U(n,e){const r=await i.put(`/api/config/channels/${n}`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function $(n){const e=await i.put("/api/config/runtime",n);if(!e.ok)throw new Error(e.error.message);return e.data}async function T(n){const e=await i.put("/api/config/secrets",n);if(!e.ok)throw new Error(e.error.message);return e.data}async function I(n,e){const r=await i.post(`/api/config/actions/${n}/execute`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function P(n){var d;const e=new URLSearchParams;(d=n==null?void 0:n.q)!=null&&d.trim()&&e.set("q",n.q.trim()),typeof(n==null?void 0:n.limit)=="number"&&Number.isFinite(n.limit)&&e.set("limit",String(Math.max(0,Math.trunc(n.limit)))),typeof(n==null?void 0:n.activeMinutes)=="number"&&Number.isFinite(n.activeMinutes)&&e.set("activeMinutes",String(Math.max(0,Math.trunc(n.activeMinutes))));const r=e.toString(),a=await i.get(r?"/api/sessions?"+r:"/api/sessions");if(!a.ok)throw new Error(a.error.message);return a.data}async function N(n,e=200){const r=await i.get(`/api/sessions/${encodeURIComponent(n)}/history?limit=${Math.max(1,Math.trunc(e))}`);if(!r.ok)throw new Error(r.error.message);return r.data}async function O(n,e){const r=await i.put(`/api/sessions/${encodeURIComponent(n)}`,e);if(!r.ok)throw new Error(r.error.message);return r.data}async function j(n){const e=await i.delete(`/api/sessions/${encodeURIComponent(n)}`);if(!e.ok)throw new Error(e.error.message);return e.data}function D(n){const e=n.split(/\r?\n/);let r="message";const a=[];for(const d of e){if(d.startsWith("event:")){r=d.slice(6).trim()||"message";continue}d.startsWith("data:")&&a.push(d.slice(5).trimStart())}return a.length===0?null:{event:r,data:a.join(`
2
2
  `)}}async function z(n,e={}){var q;const r=await fetch(`${x}/api/chat/turn/stream`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n),signal:e.signal});if(!r.ok){let g=`chat stream failed (${r.status} ${r.statusText})`;try{const t=await r.json();(q=t==null?void 0:t.error)!=null&&q.message&&(g=t.error.message)}catch{const t=await r.text().catch(()=>"");t.trim()&&(g=t.trim())}throw new Error(g)}if(!r.body)throw new Error("chat stream is not readable");const a=r.body.getReader(),d=new TextDecoder;let l="",w=null;const p=g=>{var y,h,E;const t=D(g);if(t){if(t.event==="ready"){try{const c=JSON.parse(t.data);(y=e.onReady)==null||y.call(e,{event:"ready",sessionKey:String(c.sessionKey??""),requestedAt:String(c.requestedAt??"")})}catch{}return}if(t.event==="delta"){try{const c=JSON.parse(t.data);typeof c.delta=="string"&&c.delta.length>0&&((h=e.onDelta)==null||h.call(e,{event:"delta",delta:c.delta}))}catch{}return}if(t.event==="session_event"){try{(E=e.onSessionEvent)==null||E.call(e,{event:"session_event",data:JSON.parse(t.data)})}catch{}return}if(t.event==="final"){w=JSON.parse(t.data);return}if(t.event==="error")try{const c=JSON.parse(t.data);throw new Error(typeof c.message=="string"&&c.message?c.message:"chat stream failed")}catch(c){throw c instanceof Error?c:new Error("chat stream failed")}}};let S=!1;for(;!S;){const{done:g,value:t}=await a.read();if(g){S=!0;continue}l+=d.decode(t,{stream:!0});let y=l.indexOf(`
3
3
 
4
4
  `);for(;y!==-1;){const h=l.slice(0,y).trim();l=l.slice(y+2),h&&p(h),y=l.indexOf(`
@@ -1,4 +1,4 @@
1
- import{r as i,aw as b,aL as w,au as ie,j as s,at as A,aB as le,ay as h,az as D,aC as ce,aM as de,aN as ue,aO as fe,aP as ge,aD as me,aQ as pe,aR as ve,ad as xe}from"./vendor-DN_iJQc4.js";import{c as N,t as x}from"./index-BzQBLXUW.js";import{B as I}from"./page-layout-DMWzimj9.js";function Ne(e,t){return i.useReducer((o,a)=>t[o][a]??o,e)}var E=e=>{const{present:t,children:o}=e,a=De(t),r=typeof o=="function"?o({present:a.isPresent}):i.Children.only(o),n=b(a.ref,he(r));return typeof o=="function"||a.isPresent?i.cloneElement(r,{ref:n}):null};E.displayName="Presence";function De(e){const[t,o]=i.useState(),a=i.useRef(null),r=i.useRef(e),n=i.useRef("none"),l=e?"mounted":"unmounted",[c,u]=Ne(l,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return i.useEffect(()=>{const d=y(a.current);n.current=c==="mounted"?d:"none"},[c]),w(()=>{const d=a.current,f=r.current;if(f!==e){const C=n.current,p=y(d);e?u("MOUNT"):p==="none"||(d==null?void 0:d.display)==="none"?u("UNMOUNT"):u(f&&C!==p?"ANIMATION_OUT":"UNMOUNT"),r.current=e}},[e,u]),w(()=>{if(t){let d;const f=t.ownerDocument.defaultView??window,m=p=>{const re=y(a.current).includes(CSS.escape(p.animationName));if(p.target===t&&re&&(u("ANIMATION_END"),!r.current)){const se=t.style.animationFillMode;t.style.animationFillMode="forwards",d=f.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=se)})}},C=p=>{p.target===t&&(n.current=y(a.current))};return t.addEventListener("animationstart",C),t.addEventListener("animationcancel",m),t.addEventListener("animationend",m),()=>{f.clearTimeout(d),t.removeEventListener("animationstart",C),t.removeEventListener("animationcancel",m),t.removeEventListener("animationend",m)}}else u("ANIMATION_END")},[t,u]),{isPresent:["mounted","unmountSuspended"].includes(c),ref:i.useCallback(d=>{a.current=d?getComputedStyle(d):null,o(d)},[])}}function y(e){return(e==null?void 0:e.animationName)||"none"}function he(e){var a,r;let t=(a=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:a.get,o=t&&"isReactWarning"in t&&t.isReactWarning;return o?e.ref:(t=(r=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:r.get,o=t&&"isReactWarning"in t&&t.isReactWarning,o?e.props.ref:e.props.ref||e.ref)}var O="Dialog",[M]=ce(O),[Ce,g]=M(O),T=e=>{const{__scopeDialog:t,children:o,open:a,defaultOpen:r,onOpenChange:n,modal:l=!0}=e,c=i.useRef(null),u=i.useRef(null),[d,f]=ie({prop:a,defaultProp:r??!1,onChange:n,caller:O});return s.jsx(Ce,{scope:t,triggerRef:c,contentRef:u,contentId:A(),titleId:A(),descriptionId:A(),open:d,onOpenChange:f,onOpenToggle:i.useCallback(()=>f(m=>!m),[f]),modal:l,children:o})};T.displayName=O;var S="DialogTrigger",ye=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(S,o),n=b(t,r.triggerRef);return s.jsx(h.button,{type:"button","aria-haspopup":"dialog","aria-expanded":r.open,"aria-controls":r.contentId,"data-state":_(r.open),...a,ref:n,onClick:D(e.onClick,r.onOpenToggle)})});ye.displayName=S;var j="DialogPortal",[Re,F]=M(j,{forceMount:void 0}),L=e=>{const{__scopeDialog:t,forceMount:o,children:a,container:r}=e,n=g(j,t);return s.jsx(Re,{scope:t,forceMount:o,children:i.Children.map(a,l=>s.jsx(E,{present:o||n.open,children:s.jsx(le,{asChild:!0,container:r,children:l})}))})};L.displayName=j;var R="DialogOverlay",W=i.forwardRef((e,t)=>{const o=F(R,e.__scopeDialog),{forceMount:a=o.forceMount,...r}=e,n=g(R,e.__scopeDialog);return n.modal?s.jsx(E,{present:a||n.open,children:s.jsx(Ee,{...r,ref:t})}):null});W.displayName=R;var be=pe("DialogOverlay.RemoveScroll"),Ee=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(R,o);return s.jsx(ue,{as:be,allowPinchZoom:!0,shards:[r.contentRef],children:s.jsx(h.div,{"data-state":_(r.open),...a,ref:t,style:{pointerEvents:"auto",...a.style}})})}),v="DialogContent",k=i.forwardRef((e,t)=>{const o=F(v,e.__scopeDialog),{forceMount:a=o.forceMount,...r}=e,n=g(v,e.__scopeDialog);return s.jsx(E,{present:a||n.open,children:n.modal?s.jsx(Oe,{...r,ref:t}):s.jsx(Ae,{...r,ref:t})})});k.displayName=v;var Oe=i.forwardRef((e,t)=>{const o=g(v,e.__scopeDialog),a=i.useRef(null),r=b(t,o.contentRef,a);return i.useEffect(()=>{const n=a.current;if(n)return de(n)},[]),s.jsx(U,{...e,ref:r,trapFocus:o.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:D(e.onCloseAutoFocus,n=>{var l;n.preventDefault(),(l=o.triggerRef.current)==null||l.focus()}),onPointerDownOutside:D(e.onPointerDownOutside,n=>{const l=n.detail.originalEvent,c=l.button===0&&l.ctrlKey===!0;(l.button===2||c)&&n.preventDefault()}),onFocusOutside:D(e.onFocusOutside,n=>n.preventDefault())})}),Ae=i.forwardRef((e,t)=>{const o=g(v,e.__scopeDialog),a=i.useRef(!1),r=i.useRef(!1);return s.jsx(U,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:n=>{var l,c;(l=e.onCloseAutoFocus)==null||l.call(e,n),n.defaultPrevented||(a.current||(c=o.triggerRef.current)==null||c.focus(),n.preventDefault()),a.current=!1,r.current=!1},onInteractOutside:n=>{var u,d;(u=e.onInteractOutside)==null||u.call(e,n),n.defaultPrevented||(a.current=!0,n.detail.originalEvent.type==="pointerdown"&&(r.current=!0));const l=n.target;((d=o.triggerRef.current)==null?void 0:d.contains(l))&&n.preventDefault(),n.detail.originalEvent.type==="focusin"&&r.current&&n.preventDefault()}})}),U=i.forwardRef((e,t)=>{const{__scopeDialog:o,trapFocus:a,onOpenAutoFocus:r,onCloseAutoFocus:n,...l}=e,c=g(v,o),u=i.useRef(null),d=b(t,u);return fe(),s.jsxs(s.Fragment,{children:[s.jsx(ge,{asChild:!0,loop:!0,trapped:a,onMountAutoFocus:r,onUnmountAutoFocus:n,children:s.jsx(me,{role:"dialog",id:c.contentId,"aria-describedby":c.descriptionId,"aria-labelledby":c.titleId,"data-state":_(c.open),...l,ref:d,onDismiss:()=>c.onOpenChange(!1)})}),s.jsxs(s.Fragment,{children:[s.jsx(je,{titleId:c.titleId}),s.jsx(_e,{contentRef:u,descriptionId:c.descriptionId})]})]})}),P="DialogTitle",$=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(P,o);return s.jsx(h.h2,{id:r.titleId,...a,ref:t})});$.displayName=P;var G="DialogDescription",B=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(G,o);return s.jsx(h.p,{id:r.descriptionId,...a,ref:t})});B.displayName=G;var z="DialogClose",H=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(z,o);return s.jsx(h.button,{type:"button",...a,ref:t,onClick:D(e.onClick,()=>r.onOpenChange(!1))})});H.displayName=z;function _(e){return e?"open":"closed"}var V="DialogTitleWarning",[$e,q]=ve(V,{contentName:v,titleName:P,docsSlug:"dialog"}),je=({titleId:e})=>{const t=q(V),o=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users.
1
+ import{r as i,aw as b,aL as w,au as ie,j as s,at as A,aB as le,ay as h,az as D,aC as ce,aM as de,aN as ue,aO as fe,aP as ge,aD as me,aQ as pe,aR as ve,ad as xe}from"./vendor-DN_iJQc4.js";import{c as N,t as x}from"./index-wB2uPrKu.js";import{B as I}from"./page-layout-D8MW2vP-.js";function Ne(e,t){return i.useReducer((o,a)=>t[o][a]??o,e)}var E=e=>{const{present:t,children:o}=e,a=De(t),r=typeof o=="function"?o({present:a.isPresent}):i.Children.only(o),n=b(a.ref,he(r));return typeof o=="function"||a.isPresent?i.cloneElement(r,{ref:n}):null};E.displayName="Presence";function De(e){const[t,o]=i.useState(),a=i.useRef(null),r=i.useRef(e),n=i.useRef("none"),l=e?"mounted":"unmounted",[c,u]=Ne(l,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return i.useEffect(()=>{const d=y(a.current);n.current=c==="mounted"?d:"none"},[c]),w(()=>{const d=a.current,f=r.current;if(f!==e){const C=n.current,p=y(d);e?u("MOUNT"):p==="none"||(d==null?void 0:d.display)==="none"?u("UNMOUNT"):u(f&&C!==p?"ANIMATION_OUT":"UNMOUNT"),r.current=e}},[e,u]),w(()=>{if(t){let d;const f=t.ownerDocument.defaultView??window,m=p=>{const re=y(a.current).includes(CSS.escape(p.animationName));if(p.target===t&&re&&(u("ANIMATION_END"),!r.current)){const se=t.style.animationFillMode;t.style.animationFillMode="forwards",d=f.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=se)})}},C=p=>{p.target===t&&(n.current=y(a.current))};return t.addEventListener("animationstart",C),t.addEventListener("animationcancel",m),t.addEventListener("animationend",m),()=>{f.clearTimeout(d),t.removeEventListener("animationstart",C),t.removeEventListener("animationcancel",m),t.removeEventListener("animationend",m)}}else u("ANIMATION_END")},[t,u]),{isPresent:["mounted","unmountSuspended"].includes(c),ref:i.useCallback(d=>{a.current=d?getComputedStyle(d):null,o(d)},[])}}function y(e){return(e==null?void 0:e.animationName)||"none"}function he(e){var a,r;let t=(a=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:a.get,o=t&&"isReactWarning"in t&&t.isReactWarning;return o?e.ref:(t=(r=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:r.get,o=t&&"isReactWarning"in t&&t.isReactWarning,o?e.props.ref:e.props.ref||e.ref)}var O="Dialog",[M]=ce(O),[Ce,g]=M(O),T=e=>{const{__scopeDialog:t,children:o,open:a,defaultOpen:r,onOpenChange:n,modal:l=!0}=e,c=i.useRef(null),u=i.useRef(null),[d,f]=ie({prop:a,defaultProp:r??!1,onChange:n,caller:O});return s.jsx(Ce,{scope:t,triggerRef:c,contentRef:u,contentId:A(),titleId:A(),descriptionId:A(),open:d,onOpenChange:f,onOpenToggle:i.useCallback(()=>f(m=>!m),[f]),modal:l,children:o})};T.displayName=O;var S="DialogTrigger",ye=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(S,o),n=b(t,r.triggerRef);return s.jsx(h.button,{type:"button","aria-haspopup":"dialog","aria-expanded":r.open,"aria-controls":r.contentId,"data-state":_(r.open),...a,ref:n,onClick:D(e.onClick,r.onOpenToggle)})});ye.displayName=S;var j="DialogPortal",[Re,F]=M(j,{forceMount:void 0}),L=e=>{const{__scopeDialog:t,forceMount:o,children:a,container:r}=e,n=g(j,t);return s.jsx(Re,{scope:t,forceMount:o,children:i.Children.map(a,l=>s.jsx(E,{present:o||n.open,children:s.jsx(le,{asChild:!0,container:r,children:l})}))})};L.displayName=j;var R="DialogOverlay",W=i.forwardRef((e,t)=>{const o=F(R,e.__scopeDialog),{forceMount:a=o.forceMount,...r}=e,n=g(R,e.__scopeDialog);return n.modal?s.jsx(E,{present:a||n.open,children:s.jsx(Ee,{...r,ref:t})}):null});W.displayName=R;var be=pe("DialogOverlay.RemoveScroll"),Ee=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(R,o);return s.jsx(ue,{as:be,allowPinchZoom:!0,shards:[r.contentRef],children:s.jsx(h.div,{"data-state":_(r.open),...a,ref:t,style:{pointerEvents:"auto",...a.style}})})}),v="DialogContent",k=i.forwardRef((e,t)=>{const o=F(v,e.__scopeDialog),{forceMount:a=o.forceMount,...r}=e,n=g(v,e.__scopeDialog);return s.jsx(E,{present:a||n.open,children:n.modal?s.jsx(Oe,{...r,ref:t}):s.jsx(Ae,{...r,ref:t})})});k.displayName=v;var Oe=i.forwardRef((e,t)=>{const o=g(v,e.__scopeDialog),a=i.useRef(null),r=b(t,o.contentRef,a);return i.useEffect(()=>{const n=a.current;if(n)return de(n)},[]),s.jsx(U,{...e,ref:r,trapFocus:o.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:D(e.onCloseAutoFocus,n=>{var l;n.preventDefault(),(l=o.triggerRef.current)==null||l.focus()}),onPointerDownOutside:D(e.onPointerDownOutside,n=>{const l=n.detail.originalEvent,c=l.button===0&&l.ctrlKey===!0;(l.button===2||c)&&n.preventDefault()}),onFocusOutside:D(e.onFocusOutside,n=>n.preventDefault())})}),Ae=i.forwardRef((e,t)=>{const o=g(v,e.__scopeDialog),a=i.useRef(!1),r=i.useRef(!1);return s.jsx(U,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:n=>{var l,c;(l=e.onCloseAutoFocus)==null||l.call(e,n),n.defaultPrevented||(a.current||(c=o.triggerRef.current)==null||c.focus(),n.preventDefault()),a.current=!1,r.current=!1},onInteractOutside:n=>{var u,d;(u=e.onInteractOutside)==null||u.call(e,n),n.defaultPrevented||(a.current=!0,n.detail.originalEvent.type==="pointerdown"&&(r.current=!0));const l=n.target;((d=o.triggerRef.current)==null?void 0:d.contains(l))&&n.preventDefault(),n.detail.originalEvent.type==="focusin"&&r.current&&n.preventDefault()}})}),U=i.forwardRef((e,t)=>{const{__scopeDialog:o,trapFocus:a,onOpenAutoFocus:r,onCloseAutoFocus:n,...l}=e,c=g(v,o),u=i.useRef(null),d=b(t,u);return fe(),s.jsxs(s.Fragment,{children:[s.jsx(ge,{asChild:!0,loop:!0,trapped:a,onMountAutoFocus:r,onUnmountAutoFocus:n,children:s.jsx(me,{role:"dialog",id:c.contentId,"aria-describedby":c.descriptionId,"aria-labelledby":c.titleId,"data-state":_(c.open),...l,ref:d,onDismiss:()=>c.onOpenChange(!1)})}),s.jsxs(s.Fragment,{children:[s.jsx(je,{titleId:c.titleId}),s.jsx(_e,{contentRef:u,descriptionId:c.descriptionId})]})]})}),P="DialogTitle",$=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(P,o);return s.jsx(h.h2,{id:r.titleId,...a,ref:t})});$.displayName=P;var G="DialogDescription",B=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(G,o);return s.jsx(h.p,{id:r.descriptionId,...a,ref:t})});B.displayName=G;var z="DialogClose",H=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(z,o);return s.jsx(h.button,{type:"button",...a,ref:t,onClick:D(e.onClick,()=>r.onOpenChange(!1))})});H.displayName=z;function _(e){return e?"open":"closed"}var V="DialogTitleWarning",[$e,q]=ve(V,{contentName:v,titleName:P,docsSlug:"dialog"}),je=({titleId:e})=>{const t=q(V),o=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users.
2
2
 
3
3
  If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
4
4
 
package/dist/index.html CHANGED
@@ -6,9 +6,9 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>NextClaw - 系统配置</title>
9
- <script type="module" crossorigin src="/assets/index-BzQBLXUW.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-wB2uPrKu.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-DN_iJQc4.js">
11
- <link rel="stylesheet" crossorigin href="/assets/index-DcyOd66N.css">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-DMEuanmd.css">
12
12
  </head>
13
13
 
14
14
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.5.36",
3
+ "version": "0.5.38",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,7 +5,9 @@ import type {
5
5
  MarketplaceManageRequest,
6
6
  MarketplaceManageResult,
7
7
  MarketplaceInstalledView,
8
+ MarketplacePluginContentView,
8
9
  MarketplaceItemType,
10
+ MarketplaceSkillContentView,
9
11
  MarketplaceItemView,
10
12
  MarketplaceListView,
11
13
  MarketplaceRecommendationView,
@@ -68,6 +70,28 @@ export async function fetchMarketplaceItem(slug: string, type: MarketplaceItemTy
68
70
  return response.data;
69
71
  }
70
72
 
73
+ export async function fetchMarketplaceSkillContent(slug: string): Promise<MarketplaceSkillContentView> {
74
+ const response = await api.get<MarketplaceSkillContentView>(
75
+ `/api/marketplace/skills/items/${encodeURIComponent(slug)}/content`
76
+ );
77
+ if (!response.ok) {
78
+ throw new Error(response.error.message);
79
+ }
80
+
81
+ return response.data;
82
+ }
83
+
84
+ export async function fetchMarketplacePluginContent(slug: string): Promise<MarketplacePluginContentView> {
85
+ const response = await api.get<MarketplacePluginContentView>(
86
+ `/api/marketplace/plugins/items/${encodeURIComponent(slug)}/content`
87
+ );
88
+ if (!response.ok) {
89
+ throw new Error(response.error.message);
90
+ }
91
+
92
+ return response.data;
93
+ }
94
+
71
95
  export async function fetchMarketplaceRecommendations(
72
96
  type: MarketplaceItemType,
73
97
  params: {
package/src/api/types.ts CHANGED
@@ -446,12 +446,15 @@ export type MarketplaceInstallSpec = {
446
446
  command: string;
447
447
  };
448
448
 
449
+ export type MarketplaceLocalizedTextMap = Record<string, string>;
450
+
449
451
  export type MarketplaceItemSummary = {
450
452
  id: string;
451
453
  slug: string;
452
454
  type: MarketplaceItemType;
453
455
  name: string;
454
456
  summary: string;
457
+ summaryI18n: MarketplaceLocalizedTextMap;
455
458
  tags: string[];
456
459
  author: string;
457
460
  install: MarketplaceInstallSpec;
@@ -460,11 +463,36 @@ export type MarketplaceItemSummary = {
460
463
 
461
464
  export type MarketplaceItemView = MarketplaceItemSummary & {
462
465
  description?: string;
466
+ descriptionI18n?: MarketplaceLocalizedTextMap;
463
467
  sourceRepo?: string;
464
468
  homepage?: string;
465
469
  publishedAt: string;
466
470
  };
467
471
 
472
+ export type MarketplaceSkillContentView = {
473
+ type: 'skill';
474
+ slug: string;
475
+ name: string;
476
+ install: MarketplaceInstallSpec;
477
+ source: 'workspace' | 'builtin' | 'git' | 'remote';
478
+ raw: string;
479
+ metadataRaw?: string;
480
+ bodyRaw: string;
481
+ sourceUrl?: string;
482
+ };
483
+
484
+ export type MarketplacePluginContentView = {
485
+ type: 'plugin';
486
+ slug: string;
487
+ name: string;
488
+ install: MarketplaceInstallSpec;
489
+ source: 'npm' | 'repo' | 'remote';
490
+ raw?: string;
491
+ bodyRaw?: string;
492
+ metadataRaw?: string;
493
+ sourceUrl?: string;
494
+ };
495
+
468
496
  export type MarketplaceListView = {
469
497
  total: number;
470
498
  page: number;
@@ -16,6 +16,7 @@ import { Settings, ToggleLeft, Hash, Mail, Globe, KeyRound, BookOpen } from 'luc
16
16
  import type { ConfigActionManifest } from '@/api/types';
17
17
  import { resolveChannelTutorialUrl } from '@/lib/channel-tutorials';
18
18
  import { getChannelLogo } from '@/lib/logos';
19
+ import { CONFIG_DETAIL_CARD_CLASS, CONFIG_EMPTY_DETAIL_CARD_CLASS } from './config-layout';
19
20
 
20
21
  type ChannelFieldType = 'boolean' | 'text' | 'email' | 'password' | 'number' | 'tags' | 'select' | 'json';
21
22
  type ChannelOption = { value: string; label: string };
@@ -325,7 +326,7 @@ export function ChannelForm({ channelName }: ChannelFormProps) {
325
326
 
326
327
  if (!channelName || !channelMeta || !channelConfig) {
327
328
  return (
328
- <div className="flex min-h-[520px] items-center justify-center rounded-2xl border border-gray-200/70 bg-white px-6 text-center xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
329
+ <div className={CONFIG_EMPTY_DETAIL_CARD_CLASS}>
329
330
  <div>
330
331
  <h3 className="text-base font-semibold text-gray-900">{t('channelsSelectTitle')}</h3>
331
332
  <p className="mt-2 text-sm text-gray-500">{t('channelsSelectDescription')}</p>
@@ -337,9 +338,9 @@ export function ChannelForm({ channelName }: ChannelFormProps) {
337
338
  const enabled = Boolean(channelConfig.enabled);
338
339
 
339
340
  return (
340
- <div className="flex min-h-[520px] flex-col rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
341
+ <div className={CONFIG_DETAIL_CARD_CLASS}>
341
342
  <div className="border-b border-gray-100 px-6 py-5">
342
- <div className="flex flex-wrap items-start justify-between gap-3">
343
+ <div className="flex flex-wrap items-center justify-between gap-3">
343
344
  <div className="min-w-0">
344
345
  <div className="flex items-center gap-3">
345
346
  <LogoBadge
@@ -370,7 +371,7 @@ export function ChannelForm({ channelName }: ChannelFormProps) {
370
371
  </div>
371
372
 
372
373
  <form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
373
- <div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-6 py-5">
374
+ <div className="min-h-0 flex-1 space-y-6 overflow-y-auto px-6 py-5">
374
375
  {fields.map((field) => {
375
376
  const hint = channelName
376
377
  ? hintForPath(`channels.${channelName}.${field.name}`, uiHints)
@@ -470,7 +471,7 @@ export function ChannelForm({ channelName }: ChannelFormProps) {
470
471
  [field.name]: event.target.value
471
472
  }))
472
473
  }
473
- className="min-h-[120px] w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"
474
+ className="min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"
474
475
  />
475
476
  )}
476
477
  </div>
@@ -478,20 +479,22 @@ export function ChannelForm({ channelName }: ChannelFormProps) {
478
479
  })}
479
480
  </div>
480
481
 
481
- <div className="flex flex-wrap items-center justify-end gap-2 border-t border-gray-100 px-6 py-4">
482
- {actions
483
- .filter((action) => action.trigger === 'manual')
484
- .map((action) => (
485
- <Button
486
- key={action.id}
487
- type="button"
488
- onClick={() => handleManualAction(action)}
489
- disabled={updateChannel.isPending || Boolean(runningActionId)}
490
- variant="secondary"
491
- >
492
- {runningActionId === action.id ? t('connecting') : action.title}
493
- </Button>
494
- ))}
482
+ <div className="flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4">
483
+ <div className="flex flex-wrap items-center gap-2">
484
+ {actions
485
+ .filter((action) => action.trigger === 'manual')
486
+ .map((action) => (
487
+ <Button
488
+ key={action.id}
489
+ type="button"
490
+ onClick={() => handleManualAction(action)}
491
+ disabled={updateChannel.isPending || Boolean(runningActionId)}
492
+ variant="secondary"
493
+ >
494
+ {runningActionId === action.id ? t('connecting') : action.title}
495
+ </Button>
496
+ ))}
497
+ </div>
495
498
  <Button type="submit" disabled={updateChannel.isPending || Boolean(runningActionId)}>
496
499
  {updateChannel.isPending ? t('saving') : t('save')}
497
500
  </Button>
@@ -12,6 +12,7 @@ import { t } from '@/lib/i18n';
12
12
  import { PageLayout, PageHeader } from '@/components/layout/page-layout';
13
13
  import { resolveChannelTutorialUrl } from '@/lib/channel-tutorials';
14
14
  import { Input } from '@/components/ui/input';
15
+ import { CONFIG_SIDEBAR_CARD_CLASS, CONFIG_SPLIT_GRID_CLASS } from './config-layout';
15
16
 
16
17
  const channelDescriptionKeys: Record<string, string> = {
17
18
  telegram: 'channelDescTelegram',
@@ -30,17 +31,17 @@ export function ChannelsList() {
30
31
  const [selectedChannel, setSelectedChannel] = useState<string | undefined>();
31
32
  const [query, setQuery] = useState('');
32
33
  const uiHints = schema?.uiHints;
33
- const channels = meta?.channels ?? [];
34
+ const channels = meta?.channels;
34
35
  const channelConfigs = config?.channels;
35
36
 
36
37
  const tabs = [
37
- { id: 'enabled', label: t('channelsTabEnabled'), count: channels.filter((c) => channelConfigs?.[c.name]?.enabled).length },
38
- { id: 'all', label: t('channelsTabAll'), count: channels.length }
38
+ { id: 'enabled', label: t('channelsTabEnabled'), count: (channels ?? []).filter((c) => channelConfigs?.[c.name]?.enabled).length },
39
+ { id: 'all', label: t('channelsTabAll'), count: (channels ?? []).length }
39
40
  ];
40
41
 
41
42
  const filteredChannels = useMemo(() => {
42
43
  const keyword = query.trim().toLowerCase();
43
- return channels
44
+ return (channels ?? [])
44
45
  .filter((channel) => {
45
46
  const enabled = channelConfigs?.[channel.name]?.enabled || false;
46
47
  if (activeTab === 'enabled') {
@@ -76,8 +77,8 @@ export function ChannelsList() {
76
77
  <PageLayout>
77
78
  <PageHeader title={t('channelsPageTitle')} description={t('channelsPageDescription')} />
78
79
 
79
- <div className="grid grid-cols-1 gap-5 xl:grid-cols-[340px_minmax(0,1fr)]">
80
- <section className="flex min-h-[520px] flex-col rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
80
+ <div className={CONFIG_SPLIT_GRID_CLASS}>
81
+ <section className={CONFIG_SIDEBAR_CARD_CLASS}>
81
82
  <div className="border-b border-gray-100 px-4 pt-4">
82
83
  <Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} className="mb-0" />
83
84
  </div>
@@ -259,6 +259,7 @@ export function ModelConfig() {
259
259
  />
260
260
  </div>
261
261
  <p className="text-xs text-gray-400">{modelHelpText}</p>
262
+ <p className="text-xs text-gray-500">{t('modelInputCustomHint')}</p>
262
263
  <a
263
264
  href={`${DOCS_DEFAULT_BASE_URL}/guide/model-selection`}
264
265
  className="inline-flex items-center gap-1.5 text-xs text-primary hover:text-primary-hover transition-colors"
@@ -12,6 +12,7 @@ import { hintForPath } from '@/lib/config-hints';
12
12
  import type { ProviderConfigUpdate, ProviderConnectionTestRequest } from '@/api/types';
13
13
  import { KeyRound, Globe, Hash, RotateCcw, CircleDotDashed, Sparkles, Plus, X } from 'lucide-react';
14
14
  import { toast } from 'sonner';
15
+ import { CONFIG_DETAIL_CARD_CLASS, CONFIG_EMPTY_DETAIL_CARD_CLASS } from './config-layout';
15
16
 
16
17
  type WireApiType = 'auto' | 'chat' | 'responses';
17
18
 
@@ -319,7 +320,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
319
320
 
320
321
  if (!providerName || !providerSpec || !providerConfig) {
321
322
  return (
322
- <div className="flex min-h-[520px] items-center justify-center rounded-2xl border border-gray-200/70 bg-white px-6 text-center xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
323
+ <div className={CONFIG_EMPTY_DETAIL_CARD_CLASS}>
323
324
  <div>
324
325
  <h3 className="text-base font-semibold text-gray-900">{t('providersSelectTitle')}</h3>
325
326
  <p className="mt-2 text-sm text-gray-500">{t('providersSelectDescription')}</p>
@@ -331,7 +332,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
331
332
  const statusLabel = providerConfig.apiKeySet ? t('statusReady') : t('statusSetup');
332
333
 
333
334
  return (
334
- <div className="flex min-h-[520px] flex-col rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
335
+ <div className={CONFIG_DETAIL_CARD_CLASS}>
335
336
  <div className="border-b border-gray-100 px-6 py-5">
336
337
  <div className="flex flex-wrap items-center justify-between gap-3">
337
338
  <div className="min-w-0">
@@ -431,6 +432,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
431
432
  {t('providerAddModel')}
432
433
  </Button>
433
434
  </div>
435
+ <p className="text-xs text-gray-500">{t('providerModelInputHint')}</p>
434
436
 
435
437
  {models.length === 0 ? (
436
438
  <div className="rounded-xl border border-dashed border-gray-200 bg-gray-50 px-3 py-2 text-xs text-gray-500">
@@ -11,6 +11,7 @@ import { StatusDot } from '@/components/ui/status-dot';
11
11
  import { t } from '@/lib/i18n';
12
12
  import { PageLayout, PageHeader } from '@/components/layout/page-layout';
13
13
  import { Input } from '@/components/ui/input';
14
+ import { CONFIG_SIDEBAR_CARD_CLASS, CONFIG_SPLIT_GRID_CLASS } from './config-layout';
14
15
 
15
16
  function formatBasePreview(base?: string | null): string | null {
16
17
  if (!base) {
@@ -86,8 +87,8 @@ export function ProvidersList() {
86
87
  <PageLayout>
87
88
  <PageHeader title={t('providersPageTitle')} description={t('providersPageDescription')} />
88
89
 
89
- <div className="grid grid-cols-1 gap-5 xl:grid-cols-[340px_minmax(0,1fr)]">
90
- <section className="flex min-h-[520px] flex-col rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
90
+ <div className={CONFIG_SPLIT_GRID_CLASS}>
91
+ <section className={CONFIG_SIDEBAR_CARD_CLASS}>
91
92
  <div className="border-b border-gray-100 px-4 pt-4">
92
93
  <Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} className="mb-0" />
93
94
  </div>
@@ -0,0 +1,10 @@
1
+ export const CONFIG_SPLIT_GRID_CLASS = 'grid min-h-0 grid-cols-1 gap-5 xl:grid-cols-[340px_minmax(0,1fr)]';
2
+
3
+ export const CONFIG_SIDEBAR_CARD_CLASS =
4
+ 'flex min-h-[520px] min-h-0 min-w-0 flex-col overflow-hidden rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]';
5
+
6
+ export const CONFIG_DETAIL_CARD_CLASS =
7
+ 'flex min-h-[520px] min-h-0 min-w-0 flex-col overflow-hidden rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]';
8
+
9
+ export const CONFIG_EMPTY_DETAIL_CARD_CLASS =
10
+ 'flex min-h-[520px] min-w-0 items-center justify-center overflow-hidden rounded-2xl border border-gray-200/70 bg-white px-6 text-center xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]';