@flexkit/studio 0.2.11 → 0.2.13

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 (70) hide show
  1. package/dist/activity-nav-button-RDL5GIWG.js +2 -0
  2. package/dist/{activity-nav-button-LZESIQL2.js.map → activity-nav-button-RDL5GIWG.js.map} +1 -1
  3. package/dist/astro.js +1 -1
  4. package/dist/{chat-page-3YSJP7FC.js → chat-page-B2IOPTUP.js} +3 -3
  5. package/dist/{chat-page-3YSJP7FC.js.map → chat-page-B2IOPTUP.js.map} +1 -1
  6. package/dist/chunk-APVKB4FT.js +2 -0
  7. package/dist/chunk-APVKB4FT.js.map +1 -0
  8. package/dist/{chunk-7JSGAY3E.js → chunk-C5Y6OZFR.js} +2 -2
  9. package/dist/{chunk-7JSGAY3E.js.map → chunk-C5Y6OZFR.js.map} +1 -1
  10. package/dist/chunk-CAZ6EUR2.js +2 -0
  11. package/dist/chunk-CAZ6EUR2.js.map +1 -0
  12. package/dist/{chunk-YSMRXGVX.js → chunk-CJGP4Y5Y.js} +2 -2
  13. package/dist/{chunk-YSMRXGVX.js.map → chunk-CJGP4Y5Y.js.map} +1 -1
  14. package/dist/{chunk-TPLL535V.js → chunk-CWJWOJRO.js} +3 -3
  15. package/dist/{chunk-TPLL535V.js.map → chunk-CWJWOJRO.js.map} +1 -1
  16. package/dist/{chunk-2AVINVYN.js → chunk-DJPVRAFE.js} +22 -20
  17. package/dist/{chunk-YODWX4WT.js → chunk-ETQ3Q472.js} +2 -2
  18. package/dist/{chunk-YODWX4WT.js.map → chunk-ETQ3Q472.js.map} +1 -1
  19. package/dist/{chunk-GDTOP4MU.js → chunk-KNESQOI5.js} +3 -3
  20. package/dist/{chunk-GDTOP4MU.js.map → chunk-KNESQOI5.js.map} +1 -1
  21. package/dist/chunk-N53TGGN5.js +8 -0
  22. package/dist/chunk-N53TGGN5.js.map +1 -0
  23. package/dist/chunk-TOY7ZRK2.js +384 -0
  24. package/dist/chunk-TOY7ZRK2.js.map +1 -0
  25. package/dist/{chunk-22DXYOXX.js → chunk-VPTDAFFZ.js} +2 -2
  26. package/dist/{chunk-22DXYOXX.js.map → chunk-VPTDAFFZ.js.map} +1 -1
  27. package/dist/chunk-XDLTO6VU.js +3 -0
  28. package/dist/chunk-XDLTO6VU.js.map +1 -0
  29. package/dist/chunk-ZLWYVTGO.js +2 -0
  30. package/dist/chunk-ZLWYVTGO.js.map +1 -0
  31. package/dist/electron.d.ts +103 -0
  32. package/dist/electron.js +131 -0
  33. package/dist/index.css +9 -0
  34. package/dist/index.d.ts +31 -1
  35. package/dist/index.js +9 -9
  36. package/dist/index.js.map +1 -1
  37. package/dist/list-4NZLZRX3.js +2 -0
  38. package/dist/{list-N5WCJSJC.js.map → list-4NZLZRX3.js.map} +1 -1
  39. package/dist/nextjs.js +1 -1
  40. package/dist/{pages-65QNO7ME.js → pages-7MFZAVLV.js} +4 -4
  41. package/dist/{pages-65QNO7ME.js.map → pages-7MFZAVLV.js.map} +1 -1
  42. package/dist/{plugin-pages-F5K745MZ.js → plugin-pages-RNB46YPQ.js} +2 -2
  43. package/dist/{plugin-pages-F5K745MZ.js.map → plugin-pages-RNB46YPQ.js.map} +1 -1
  44. package/dist/{root-4E3YUTQU.js → root-4XPMKRHZ.js} +5 -5
  45. package/dist/{root-4E3YUTQU.js.map → root-4XPMKRHZ.js.map} +1 -1
  46. package/dist/{root-WYRLSQPK.js → root-5RDJ2HAV.js} +2 -2
  47. package/dist/{root-WYRLSQPK.js.map → root-5RDJ2HAV.js.map} +1 -1
  48. package/dist/root-NCLIQPJA.js +23 -0
  49. package/dist/root-NCLIQPJA.js.map +1 -0
  50. package/dist/root-OHZDY47O.js +2 -0
  51. package/dist/{root-DK5C7DYH.js.map → root-OHZDY47O.js.map} +1 -1
  52. package/dist/{search-EK4XBDGL.js → search-OWF3LEWH.js} +2 -2
  53. package/dist/{search-EK4XBDGL.js.map → search-OWF3LEWH.js.map} +1 -1
  54. package/dist/{skills-pages-O35YZMMC.js → skills-pages-VCXZFQFD.js} +2 -2
  55. package/dist/{skills-pages-O35YZMMC.js.map → skills-pages-VCXZFQFD.js.map} +1 -1
  56. package/dist/tanstack-start.js +1 -1
  57. package/package.json +12 -8
  58. package/dist/activity-nav-button-LZESIQL2.js +0 -2
  59. package/dist/chunk-ACS3TGGP.js +0 -2
  60. package/dist/chunk-ACS3TGGP.js.map +0 -1
  61. package/dist/chunk-B4WSTNZ3.js +0 -8
  62. package/dist/chunk-B4WSTNZ3.js.map +0 -1
  63. package/dist/chunk-HYSTMQD7.js +0 -2
  64. package/dist/chunk-HYSTMQD7.js.map +0 -1
  65. package/dist/chunk-OI6GA6PE.js +0 -384
  66. package/dist/chunk-OI6GA6PE.js.map +0 -1
  67. package/dist/list-N5WCJSJC.js +0 -2
  68. package/dist/root-DK5C7DYH.js +0 -2
  69. package/dist/root-MMAJEVBH.js +0 -23
  70. package/dist/root-MMAJEVBH.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import'./index.css';import {x}from'./chunk-B4WSTNZ3.js';import {g,f}from'./chunk-2HSIWUH3.js';import {s}from'./chunk-YCCFUGWJ.js';import {c,h as h$1,i,k,j as j$1}from'./chunk-32ZU4MLE.js';import {e,g as g$1,h,j}from'./chunk-SZEGGBOC.js';import {b}from'./chunk-6NRQMG26.js';import {useState,useEffect,useMemo,useTransition}from'react';import {LoaderCircle,MessageSquareIcon,MoreHorizontalIcon,PencilIcon,Trash2Icon}from'lucide-react';import {useLocation,useNavigate}from'react-router-dom';import H,{useSWRConfig}from'swr';import U from'swr/infinite';import {jsx,jsxs}from'react/jsx-runtime';function ue(){let a=useLocation(),n="/agent",t=a.pathname.indexOf(n);return t===-1?`${a.pathname.replace(/\/$/,"")}/agent`:a.pathname.slice(0,t+n.length)}function de(a,n){let[t,o]=useState(a);return useEffect(()=>{let f=window.setTimeout(()=>{o(a);},n);return ()=>{window.clearTimeout(f);}},[n,a]),t}function Z(a){let n=new Date(a.lastMessageAt??a.updatedAt).getTime(),t=new Date,o=new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime();return n>=o?"Today":n>=o-864e5?"Yesterday":n>=o-7*864e5?"Previous 7 days":"Older"}function ee(a){let n=[];for(let t of a){let o=Z(t),f=n.find(u=>u.label===o);f?f.chats.push(t):n.push({chats:[t],label:o});}return n}function te({active:a,api:n,chat:t,onChanged:o,onDeleted:f,onOpen:u}){let[r,l]=useState(null),[g,d]=useState(t.title??""),[p,D]=useTransition(),k$1=t.title?.trim()||"New chat";function v(){D(async()=>{await n.renameAgentChat(t.id,g.trim()),l(null),o();});}function y(){D(async()=>{await n.deleteAgentChat(t.id),l(null),f();});}return jsxs("div",{className:`fk:group/chat fk:flex fk:items-center fk:gap-1 fk:rounded-md fk:pr-1 ${a?"fk:bg-sidebar-accent":"fk:hover:bg-accent/60"} fk:corner-squircle`,children:[jsx("button",{className:"fk:min-w-0 fk:flex-1 fk:truncate fk:px-2 fk:py-1.5 fk:text-left fk:text-sm",title:k$1,type:"button",onClick:u,children:k$1}),jsxs(e,{children:[jsx(g$1,{asChild:true,children:jsx(b,{className:"fk:size-6 fk:shrink-0 fk:opacity-0 fk:group-hover/chat:opacity-100 fk:data-[state=open]:opacity-100",size:"icon",variant:"ghost",children:jsx(MoreHorizontalIcon,{className:"fk:size-4"})})}),jsxs(h,{align:"start",children:[jsxs(j,{onClick:()=>{d(t.title??""),l("rename");},children:[jsx(PencilIcon,{className:"fk:size-4"}),"Rename"]}),jsxs(j,{className:"fk:text-destructive",onClick:()=>l("delete"),children:[jsx(Trash2Icon,{className:"fk:size-4"}),"Delete"]})]})]}),jsx(c,{open:r==="rename",onOpenChange:m=>l(m?"rename":null),children:jsxs(h$1,{className:"fk:max-w-sm",children:[jsx(i,{children:jsx(k,{children:"Rename chat"})}),jsx(s,{value:g,onChange:m=>d(m.target.value),placeholder:"Chat title"}),jsx(j$1,{children:jsx(b,{disabled:p||!g.trim(),size:"sm",onClick:v,children:p?"Saving...":"Save"})})]})}),jsx(c,{open:r==="delete",onOpenChange:m=>l(m?"delete":null),children:jsxs(h$1,{className:"fk:max-w-sm",children:[jsx(i,{children:jsx(k,{children:"Delete chat?"})}),jsxs("p",{className:"fk:text-sm fk:text-muted-foreground",children:["\u201C",k$1,"\u201D and its messages will be permanently deleted."]}),jsx(j$1,{children:jsx(b,{disabled:p,size:"sm",variant:"destructive",onClick:y,children:p?"Deleting...":"Delete"})})]})})]})}function ne({onOpen:a,projectId:n,query:t}){let{data:o,isLoading:f$1}=H(g(n).agentChatSearch(t),f,{keepPreviousData:true});return f$1&&!o?jsxs("div",{className:"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground",children:[jsx(LoaderCircle,{className:"fk:size-3.5 fk:animate-spin"}),"Searching..."]}):o?.results.length?jsx("div",{className:"fk:space-y-1",children:o.results.map(u=>jsxs("button",{className:"fk:block fk:w-full fk:rounded-md fk:px-2 fk:py-1.5 fk:text-left fk:hover:bg-accent/60 fk:corner-squircle",type:"button",onClick:()=>a(u.chatId),children:[jsx("span",{className:"fk:block fk:truncate fk:text-sm",children:u.chatTitle?.trim()||"New chat"}),jsx("span",{className:"fk:block fk:truncate fk:text-xs fk:text-muted-foreground",children:u.snippet})]},u.messageId))}):jsx("p",{className:"fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground",children:"No matching chats."})}function me({query:a}){let{api:n,projectId:t}=x(),o=useNavigate(),f$1=useLocation(),{mutate:u}=useSWRConfig(),r=/\/agent\/chats\/([^/]+)/.exec(f$1.pathname)?.[1],{data:l,isLoading:g$1,mutate:d,setSize:p,size:D}=U((s,c)=>!t||c&&!c.hasMore?null:g(t).agentChats(s*30),f,{revalidateFirstPage:true}),{data:k}=H(t&&r?g(t).agentChat(r):null,f),v=useMemo(()=>{let s=(l??[]).flatMap(h=>h.chats),c=k?.chat.title?.trim();return !r||!c?s:s.map(h=>h.id!==r||h.title?.trim()?h:{...h,title:c})},[k?.chat.title,r,l]),y=l?.[l.length-1]?.hasMore??false,m=useMemo(()=>ee(v),[v]);if(useEffect(()=>{d();},[r,d]),!t||!n)return null;let J=n,z=t;function L(s){o(`agent/chats/${s}`);}function X(s){d(),s===r?o("agent"):u(g(z).agentChat(s),void 0,{revalidate:false});}return jsx("div",{className:"fk:space-y-2",children:a?jsx(ne,{projectId:z,query:a,onOpen:L}):jsxs("div",{className:"fk:space-y-3",children:[m.map(s=>jsxs("div",{children:[jsx("p",{className:"fk:px-2 fk:pb-1 fk:text-xs fk:font-medium fk:text-muted-foreground",children:s.label}),jsx("div",{className:"fk:space-y-0.5",children:s.chats.map(c=>jsx(te,{active:c.id===r,api:J,chat:c,onChanged:()=>{d();},onDeleted:()=>X(c.id),onOpen:()=>L(c.id)},c.id))})]},s.label)),g$1?jsxs("div",{className:"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground",children:[jsx(LoaderCircle,{className:"fk:size-3.5 fk:animate-spin"}),"Loading recent chats..."]}):null,!g$1&&v.length===0?jsxs("div",{className:"fk:flex fk:flex-col fk:items-center fk:gap-2 fk:px-2 fk:py-6 fk:text-center fk:text-xs fk:text-muted-foreground",children:[jsx(MessageSquareIcon,{className:"fk:size-5"}),jsx("span",{children:"No chats yet. Start a conversation with the agent."})]}):null,y?jsx(b,{className:"fk:w-full",size:"sm",variant:"ghost",onClick:()=>{p(D+1);},children:"Load more"}):null]})})}export{ue as a,de as b,me as c};//# sourceMappingURL=chunk-22DXYOXX.js.map
2
- //# sourceMappingURL=chunk-22DXYOXX.js.map
1
+ import'./index.css';import {x}from'./chunk-N53TGGN5.js';import {g,f}from'./chunk-2HSIWUH3.js';import {s}from'./chunk-YCCFUGWJ.js';import {c,h as h$1,i,k,j as j$1}from'./chunk-32ZU4MLE.js';import {e,g as g$1,h,j}from'./chunk-SZEGGBOC.js';import {b}from'./chunk-6NRQMG26.js';import {useState,useEffect,useMemo,useTransition}from'react';import {LoaderCircle,MessageSquareIcon,MoreHorizontalIcon,PencilIcon,Trash2Icon}from'lucide-react';import {useLocation,useNavigate}from'react-router-dom';import H,{useSWRConfig}from'swr';import U from'swr/infinite';import {jsx,jsxs}from'react/jsx-runtime';function ue(){let a=useLocation(),n="/agent",t=a.pathname.indexOf(n);return t===-1?`${a.pathname.replace(/\/$/,"")}/agent`:a.pathname.slice(0,t+n.length)}function de(a,n){let[t,o]=useState(a);return useEffect(()=>{let f=window.setTimeout(()=>{o(a);},n);return ()=>{window.clearTimeout(f);}},[n,a]),t}function Z(a){let n=new Date(a.lastMessageAt??a.updatedAt).getTime(),t=new Date,o=new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime();return n>=o?"Today":n>=o-864e5?"Yesterday":n>=o-7*864e5?"Previous 7 days":"Older"}function ee(a){let n=[];for(let t of a){let o=Z(t),f=n.find(u=>u.label===o);f?f.chats.push(t):n.push({chats:[t],label:o});}return n}function te({active:a,api:n,chat:t,onChanged:o,onDeleted:f,onOpen:u}){let[r,l]=useState(null),[g,d]=useState(t.title??""),[p,D]=useTransition(),k$1=t.title?.trim()||"New chat";function v(){D(async()=>{await n.renameAgentChat(t.id,g.trim()),l(null),o();});}function y(){D(async()=>{await n.deleteAgentChat(t.id),l(null),f();});}return jsxs("div",{className:`fk:group/chat fk:flex fk:items-center fk:gap-1 fk:rounded-md fk:pr-1 ${a?"fk:bg-sidebar-accent":"fk:hover:bg-accent/60"} fk:corner-squircle`,children:[jsx("button",{className:"fk:min-w-0 fk:flex-1 fk:truncate fk:px-2 fk:py-1.5 fk:text-left fk:text-sm",title:k$1,type:"button",onClick:u,children:k$1}),jsxs(e,{children:[jsx(g$1,{asChild:true,children:jsx(b,{className:"fk:size-6 fk:shrink-0 fk:opacity-0 fk:group-hover/chat:opacity-100 fk:data-[state=open]:opacity-100",size:"icon",variant:"ghost",children:jsx(MoreHorizontalIcon,{className:"fk:size-4"})})}),jsxs(h,{align:"start",children:[jsxs(j,{onClick:()=>{d(t.title??""),l("rename");},children:[jsx(PencilIcon,{className:"fk:size-4"}),"Rename"]}),jsxs(j,{className:"fk:text-destructive",onClick:()=>l("delete"),children:[jsx(Trash2Icon,{className:"fk:size-4"}),"Delete"]})]})]}),jsx(c,{open:r==="rename",onOpenChange:m=>l(m?"rename":null),children:jsxs(h$1,{className:"fk:max-w-sm",children:[jsx(i,{children:jsx(k,{children:"Rename chat"})}),jsx(s,{value:g,onChange:m=>d(m.target.value),placeholder:"Chat title"}),jsx(j$1,{children:jsx(b,{disabled:p||!g.trim(),size:"sm",onClick:v,children:p?"Saving...":"Save"})})]})}),jsx(c,{open:r==="delete",onOpenChange:m=>l(m?"delete":null),children:jsxs(h$1,{className:"fk:max-w-sm",children:[jsx(i,{children:jsx(k,{children:"Delete chat?"})}),jsxs("p",{className:"fk:text-sm fk:text-muted-foreground",children:["\u201C",k$1,"\u201D and its messages will be permanently deleted."]}),jsx(j$1,{children:jsx(b,{disabled:p,size:"sm",variant:"destructive",onClick:y,children:p?"Deleting...":"Delete"})})]})})]})}function ne({onOpen:a,projectId:n,query:t}){let{data:o,isLoading:f$1}=H(g(n).agentChatSearch(t),f,{keepPreviousData:true});return f$1&&!o?jsxs("div",{className:"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground",children:[jsx(LoaderCircle,{className:"fk:size-3.5 fk:animate-spin"}),"Searching..."]}):o?.results.length?jsx("div",{className:"fk:space-y-1",children:o.results.map(u=>jsxs("button",{className:"fk:block fk:w-full fk:rounded-md fk:px-2 fk:py-1.5 fk:text-left fk:hover:bg-accent/60 fk:corner-squircle",type:"button",onClick:()=>a(u.chatId),children:[jsx("span",{className:"fk:block fk:truncate fk:text-sm",children:u.chatTitle?.trim()||"New chat"}),jsx("span",{className:"fk:block fk:truncate fk:text-xs fk:text-muted-foreground",children:u.snippet})]},u.messageId))}):jsx("p",{className:"fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground",children:"No matching chats."})}function me({query:a}){let{api:n,projectId:t}=x(),o=useNavigate(),f$1=useLocation(),{mutate:u}=useSWRConfig(),r=/\/agent\/chats\/([^/]+)/.exec(f$1.pathname)?.[1],{data:l,isLoading:g$1,mutate:d,setSize:p,size:D}=U((s,c)=>!t||c&&!c.hasMore?null:g(t).agentChats(s*30),f,{revalidateFirstPage:true}),{data:k}=H(t&&r?g(t).agentChat(r):null,f),v=useMemo(()=>{let s=(l??[]).flatMap(h=>h.chats),c=k?.chat.title?.trim();return !r||!c?s:s.map(h=>h.id!==r||h.title?.trim()?h:{...h,title:c})},[k?.chat.title,r,l]),y=l?.[l.length-1]?.hasMore??false,m=useMemo(()=>ee(v),[v]);if(useEffect(()=>{d();},[r,d]),!t||!n)return null;let J=n,z=t;function L(s){o(`agent/chats/${s}`);}function X(s){d(),s===r?o("agent"):u(g(z).agentChat(s),void 0,{revalidate:false});}return jsx("div",{className:"fk:space-y-2",children:a?jsx(ne,{projectId:z,query:a,onOpen:L}):jsxs("div",{className:"fk:space-y-3",children:[m.map(s=>jsxs("div",{children:[jsx("p",{className:"fk:px-2 fk:pb-1 fk:text-xs fk:font-medium fk:text-muted-foreground",children:s.label}),jsx("div",{className:"fk:space-y-0.5",children:s.chats.map(c=>jsx(te,{active:c.id===r,api:J,chat:c,onChanged:()=>{d();},onDeleted:()=>X(c.id),onOpen:()=>L(c.id)},c.id))})]},s.label)),g$1?jsxs("div",{className:"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground",children:[jsx(LoaderCircle,{className:"fk:size-3.5 fk:animate-spin"}),"Loading recent chats..."]}):null,!g$1&&v.length===0?jsxs("div",{className:"fk:flex fk:flex-col fk:items-center fk:gap-2 fk:px-2 fk:py-6 fk:text-center fk:text-xs fk:text-muted-foreground",children:[jsx(MessageSquareIcon,{className:"fk:size-5"}),jsx("span",{children:"No chats yet. Start a conversation with the agent."})]}):null,y?jsx(b,{className:"fk:w-full",size:"sm",variant:"ghost",onClick:()=>{p(D+1);},children:"Load more"}):null]})})}export{ue as a,de as b,me as c};//# sourceMappingURL=chunk-VPTDAFFZ.js.map
2
+ //# sourceMappingURL=chunk-VPTDAFFZ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/apps/ai/agent/chat-list.tsx"],"names":["useAgentBasePath","location","useLocation","marker","index","useDebouncedValue","value","delayMs","debounced","setDebounced","useState","useEffect","timeout","getChatGroupLabel","chat","timestamp","now","startOfToday","groupChats","chats","groups","label","group","candidate","ChatListItem","active","api","onChanged","onDeleted","onOpen","dialog","setDialog","title","setTitle","isMutating","startMutation","useTransition","handleRename","handleDelete","jsxs","jsx","DropdownMenu","DropdownMenuTrigger","Button","MoreHorizontalIcon","DropdownMenuContent","DropdownMenuItem","PencilIcon","Trash2Icon","Dialog","open","DialogContent","DialogHeader","DialogTitle","Input","event","DialogFooter","SearchResults","projectId","query","data","isLoading","useSWR","paths","fetcher","LoaderCircle","result","AgentChatsSection","useProjectApi","navigate","useNavigate","globalMutate","useSWRConfig","activeChatId","mutate","setSize","size","useSWRInfinite","pageIndex","previousPage","activeChatDetail","useMemo","listed","page","generatedTitle","hasMore","chatApi","chatProjectId","openChat","chatId","handleDeleted","MessageSquareIcon"],"mappings":"8kBAwBO,SAASA,EAAAA,EAA2B,CACzC,IAAMC,CAAAA,CAAWC,WAAAA,EAAY,CACvBC,CAAAA,CAAS,QAAA,CACTC,CAAAA,CAAQH,CAAAA,CAAS,QAAA,CAAS,OAAA,CAAQE,CAAM,CAAA,CAE9C,OAAIC,CAAAA,GAAU,EAAA,CACL,CAAA,EAAGH,CAAAA,CAAS,SAAS,OAAA,CAAQ,KAAA,CAAO,EAAE,CAAC,CAAA,MAAA,CAAA,CAGzCA,CAAAA,CAAS,QAAA,CAAS,KAAA,CAAM,CAAA,CAAGG,CAAAA,CAAQD,CAAAA,CAAO,MAAM,CACzD,CAEO,SAASE,EAAAA,CAAkBC,CAAAA,CAAeC,CAAAA,CAAyB,CACxE,GAAM,CAACC,CAAAA,CAAWC,CAAY,CAAA,CAAIC,QAAAA,CAASJ,CAAK,CAAA,CAEhD,OAAAK,SAAAA,CAAU,IAAM,CACd,IAAMC,CAAAA,CAAU,MAAA,CAAO,UAAA,CAAW,IAAM,CACtCH,CAAAA,CAAaH,CAAK,EACpB,CAAA,CAAGC,CAAO,CAAA,CAEV,OAAO,IAAM,CACX,MAAA,CAAO,YAAA,CAAaK,CAAO,EAC7B,CACF,CAAA,CAAG,CAACL,CAAAA,CAASD,CAAK,CAAC,CAAA,CAEZE,CACT,CAEA,SAASK,CAAAA,CAAkBC,CAAAA,CAAyB,CAClD,IAAMC,CAAAA,CAAY,IAAI,IAAA,CAAKD,CAAAA,CAAK,aAAA,EAAiBA,CAAAA,CAAK,SAAS,CAAA,CAAE,OAAA,EAAQ,CACnEE,CAAAA,CAAM,IAAI,IAAA,CACVC,CAAAA,CAAe,IAAI,IAAA,CAAKD,CAAAA,CAAI,WAAA,EAAY,CAAGA,CAAAA,CAAI,QAAA,EAAS,CAAGA,CAAAA,CAAI,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ,CAExF,OAAID,GAAaE,CAAAA,CACR,OAAA,CAGLF,CAAAA,EAAaE,CAAAA,CAAe,KAAA,CACvB,WAAA,CAGLF,CAAAA,EAAaE,CAAAA,CAAe,CAAA,CAAI,KAAA,CAC3B,iBAAA,CAGF,OACT,CAEA,SAASC,EAAAA,CAAWC,CAAAA,CAA6D,CAC/E,IAAMC,CAAAA,CAAkD,EAAC,CAEzD,IAAA,IAAWN,CAAAA,IAAQK,CAAAA,CAAO,CACxB,IAAME,CAAAA,CAAQR,CAAAA,CAAkBC,CAAI,CAAA,CAC9BQ,EAAQF,CAAAA,CAAO,IAAA,CAAMG,CAAAA,EAAcA,CAAAA,CAAU,KAAA,GAAUF,CAAK,CAAA,CAE9DC,CAAAA,CACFA,CAAAA,CAAM,KAAA,CAAM,IAAA,CAAKR,CAAI,CAAA,CAErBM,CAAAA,CAAO,IAAA,CAAK,CAAE,KAAA,CAAO,CAACN,CAAI,CAAA,CAAG,KAAA,CAAAO,CAAM,CAAC,EAExC,CAEA,OAAOD,CACT,CAEA,SAASI,EAAAA,CAAa,CACpB,MAAA,CAAAC,CAAAA,CACA,GAAA,CAAAC,CAAAA,CACA,IAAA,CAAAZ,CAAAA,CACA,SAAA,CAAAa,CAAAA,CACA,SAAA,CAAAC,CAAAA,CACA,MAAA,CAAAC,CACF,CAAA,CAOgB,CACd,GAAM,CAACC,CAAAA,CAAQC,CAAS,CAAA,CAAIrB,QAAAA,CAAqC,IAAI,CAAA,CAC/D,CAACsB,CAAAA,CAAOC,CAAQ,CAAA,CAAIvB,QAAAA,CAASI,CAAAA,CAAK,KAAA,EAAS,EAAE,CAAA,CAC7C,CAACoB,CAAAA,CAAYC,CAAa,CAAA,CAAIC,aAAAA,EAAc,CAC5Cf,GAAAA,CAAQP,CAAAA,CAAK,KAAA,EAAO,IAAA,EAAK,EAAK,UAAA,CAEpC,SAASuB,CAAAA,EAAqB,CAC5BF,CAAAA,CAAc,SAAY,CACxB,MAAMT,CAAAA,CAAI,eAAA,CAAgBZ,CAAAA,CAAK,EAAA,CAAIkB,CAAAA,CAAM,IAAA,EAAM,CAAA,CAC/CD,CAAAA,CAAU,IAAI,CAAA,CACdJ,CAAAA,GACF,CAAC,EACH,CAEA,SAASW,CAAAA,EAAqB,CAC5BH,CAAAA,CAAc,SAAY,CACxB,MAAMT,CAAAA,CAAI,eAAA,CAAgBZ,CAAAA,CAAK,EAAE,CAAA,CACjCiB,CAAAA,CAAU,IAAI,CAAA,CACdH,CAAAA,GACF,CAAC,EACH,CAEA,OACEW,IAAAA,CAAC,KAAA,CAAA,CACC,SAAA,CAAW,CAAA,qEAAA,EACTd,CAAAA,CAAS,sBAAA,CAAyB,uBACpC,CAAA,mBAAA,CAAA,CAEA,QAAA,CAAA,CAAAe,GAAAA,CAAC,QAAA,CAAA,CACC,SAAA,CAAU,4EAAA,CACV,KAAA,CAAOnB,GAAAA,CACP,IAAA,CAAK,QAAA,CACL,OAAA,CAASQ,CAAAA,CAER,QAAA,CAAAR,GAAAA,CACH,CAAA,CACAkB,IAAAA,CAACE,CAAAA,CAAA,CACC,QAAA,CAAA,CAAAD,GAAAA,CAACE,GAAAA,CAAA,CAAoB,OAAA,CAAO,IAAA,CAC1B,QAAA,CAAAF,GAAAA,CAACG,CAAAA,CAAA,CACC,SAAA,CAAU,qGAAA,CACV,IAAA,CAAK,MAAA,CACL,OAAA,CAAQ,OAAA,CAER,QAAA,CAAAH,GAAAA,CAACI,kBAAAA,CAAA,CAAmB,SAAA,CAAU,WAAA,CAAY,CAAA,CAC5C,CAAA,CACF,CAAA,CACAL,IAAAA,CAACM,CAAAA,CAAA,CAAoB,KAAA,CAAM,OAAA,CACzB,QAAA,CAAA,CAAAN,IAAAA,CAACO,CAAAA,CAAA,CACC,OAAA,CAAS,IAAM,CACbb,CAAAA,CAASnB,CAAAA,CAAK,KAAA,EAAS,EAAE,CAAA,CACzBiB,CAAAA,CAAU,QAAQ,EACpB,CAAA,CAEA,QAAA,CAAA,CAAAS,GAAAA,CAACO,UAAAA,CAAA,CAAW,SAAA,CAAU,WAAA,CAAY,CAAA,CAAE,QAAA,CAAA,CAEtC,CAAA,CACAR,IAAAA,CAACO,CAAAA,CAAA,CAAiB,SAAA,CAAU,qBAAA,CAAsB,OAAA,CAAS,IAAMf,CAAAA,CAAU,QAAQ,CAAA,CACjF,QAAA,CAAA,CAAAS,GAAAA,CAACQ,UAAAA,CAAA,CAAW,SAAA,CAAU,WAAA,CAAY,CAAA,CAAE,QAAA,CAAA,CAEtC,CAAA,CAAA,CACF,CAAA,CAAA,CACF,CAAA,CACAR,GAAAA,CAACS,CAAAA,CAAA,CAAO,IAAA,CAAMnB,CAAAA,GAAW,QAAA,CAAU,YAAA,CAAeoB,CAAAA,EAASnB,CAAAA,CAAUmB,CAAAA,CAAO,QAAA,CAAW,IAAI,CAAA,CACzF,QAAA,CAAAX,IAAAA,CAACY,GAAAA,CAAA,CAAc,SAAA,CAAU,aAAA,CACvB,QAAA,CAAA,CAAAX,GAAAA,CAACY,CAAAA,CAAA,CACC,QAAA,CAAAZ,GAAAA,CAACa,CAAAA,CAAA,CAAY,QAAA,CAAA,aAAA,CAAW,CAAA,CAC1B,CAAA,CACAb,GAAAA,CAACc,EAAA,CAAM,KAAA,CAAOtB,CAAAA,CAAO,QAAA,CAAWuB,CAAAA,EAAUtB,CAAAA,CAASsB,CAAAA,CAAM,MAAA,CAAO,KAAK,CAAA,CAAG,WAAA,CAAY,YAAA,CAAa,CAAA,CACjGf,GAAAA,CAACgB,GAAAA,CAAA,CACC,QAAA,CAAAhB,GAAAA,CAACG,CAAAA,CAAA,CAAO,QAAA,CAAUT,CAAAA,EAAc,CAACF,CAAAA,CAAM,IAAA,EAAK,CAAG,IAAA,CAAK,IAAA,CAAK,OAAA,CAASK,CAAAA,CAC/D,QAAA,CAAAH,CAAAA,CAAa,WAAA,CAAc,MAAA,CAC9B,CAAA,CACF,CAAA,CAAA,CACF,CAAA,CACF,CAAA,CACAM,GAAAA,CAACS,CAAAA,CAAA,CAAO,IAAA,CAAMnB,CAAAA,GAAW,QAAA,CAAU,YAAA,CAAeoB,CAAAA,EAASnB,CAAAA,CAAUmB,CAAAA,CAAO,QAAA,CAAW,IAAI,CAAA,CACzF,QAAA,CAAAX,IAAAA,CAACY,GAAAA,CAAA,CAAc,SAAA,CAAU,aAAA,CACvB,QAAA,CAAA,CAAAX,GAAAA,CAACY,CAAAA,CAAA,CACC,QAAA,CAAAZ,IAACa,CAAAA,CAAA,CAAY,QAAA,CAAA,cAAA,CAAY,CAAA,CAC3B,CAAA,CACAd,IAAAA,CAAC,GAAA,CAAA,CAAE,SAAA,CAAU,qCAAA,CAAsC,QAAA,CAAA,CAAA,QAAA,CACzClB,GAAAA,CAAM,sDAAA,CAAA,CAChB,CAAA,CACAmB,GAAAA,CAACgB,GAAAA,CAAA,CACC,QAAA,CAAAhB,GAAAA,CAACG,CAAAA,CAAA,CAAO,QAAA,CAAUT,CAAAA,CAAY,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,aAAA,CAAc,OAAA,CAASI,CAAAA,CACpE,QAAA,CAAAJ,CAAAA,CAAa,cAAgB,QAAA,CAChC,CAAA,CACF,CAAA,CAAA,CACF,CAAA,CACF,CAAA,CAAA,CACF,CAEJ,CAEA,SAASuB,EAAAA,CAAc,CACrB,MAAA,CAAA5B,CAAAA,CACA,SAAA,CAAA6B,CAAAA,CACA,KAAA,CAAAC,CACF,CAAA,CAIgB,CACd,GAAM,CAAE,IAAA,CAAAC,CAAAA,CAAM,SAAA,CAAAC,GAAU,CAAA,CAAIC,CAAAA,CAC1BC,CAAAA,CAAML,CAAS,CAAA,CAAE,eAAA,CAAgBC,CAAK,CAAA,CACtCK,CAAAA,CACA,CAAE,gBAAA,CAAkB,IAAK,CAC3B,CAAA,CAEA,OAAIH,GAAAA,EAAa,CAACD,CAAAA,CAEdrB,IAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,sFAAA,CACb,QAAA,CAAA,CAAAC,GAAAA,CAACyB,YAAAA,CAAA,CAAa,SAAA,CAAU,6BAAA,CAA8B,CAAA,CAAE,cAAA,CAAA,CAE1D,CAAA,CAICL,CAAAA,EAAM,OAAA,CAAQ,MAAA,CAKjBpB,GAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,cAAA,CACZ,QAAA,CAAAoB,CAAAA,CAAK,OAAA,CAAQ,GAAA,CAAKM,CAAAA,EACjB3B,IAAAA,CAAC,QAAA,CAAA,CACC,SAAA,CAAU,0GAAA,CAEV,IAAA,CAAK,QAAA,CACL,OAAA,CAAS,IAAMV,CAAAA,CAAOqC,CAAAA,CAAO,MAAM,CAAA,CAEnC,QAAA,CAAA,CAAA1B,GAAAA,CAAC,MAAA,CAAA,CAAK,SAAA,CAAU,iCAAA,CAAmC,QAAA,CAAA0B,CAAAA,CAAO,SAAA,EAAW,IAAA,EAAK,EAAK,UAAA,CAAW,CAAA,CAC1F1B,GAAAA,CAAC,MAAA,CAAA,CAAK,UAAU,0DAAA,CAA4D,QAAA,CAAA0B,CAAAA,CAAO,OAAA,CAAQ,CAAA,CAAA,CAAA,CALtFA,CAAAA,CAAO,SAMd,CACD,CAAA,CACH,CAAA,CAhBO1B,GAAAA,CAAC,GAAA,CAAA,CAAE,SAAA,CAAU,qDAAA,CAAsD,QAAA,CAAA,oBAAA,CAAkB,CAkBhG,CASO,SAAS2B,EAAAA,CAAkB,CAAE,KAAA,CAAAR,CAAM,CAAA,CAA0C,CAClF,GAAM,CAAE,GAAA,CAAAjC,CAAAA,CAAK,SAAA,CAAAgC,CAAU,CAAA,CAAIU,CAAAA,EAAc,CACnCC,CAAAA,CAAWC,WAAAA,EAAY,CACvBrE,GAAAA,CAAWC,WAAAA,EAAY,CACvB,CAAE,MAAA,CAAQqE,CAAa,CAAA,CAAIC,YAAAA,EAAa,CACxCC,CAAAA,CAAe,yBAAA,CAA0B,IAAA,CAAKxE,GAAAA,CAAS,QAAQ,CAAA,GAAI,CAAC,CAAA,CACpE,CAAE,IAAA,CAAA2D,CAAAA,CAAM,SAAA,CAAAC,GAAAA,CAAW,MAAA,CAAAa,CAAAA,CAAQ,OAAA,CAAAC,CAAAA,CAAS,KAAAC,CAAK,CAAA,CAAIC,CAAAA,CACjD,CAACC,CAAAA,CAAWC,CAAAA,GACN,CAACrB,CAAAA,EAAcqB,CAAAA,EAAgB,CAACA,CAAAA,CAAa,OAAA,CACxC,IAAA,CAGFhB,CAAAA,CAAML,CAAS,CAAA,CAAE,UAAA,CAAWoB,CAAAA,CAAY,EAAE,CAAA,CAEnDd,CAAAA,CACA,CAAE,mBAAA,CAAqB,IAAK,CAC9B,CAAA,CACM,CAAE,IAAA,CAAMgB,CAAiB,CAAA,CAAIlB,CAAAA,CACjCJ,CAAAA,EAAae,CAAAA,CAAeV,CAAAA,CAAML,CAAS,CAAA,CAAE,SAAA,CAAUe,CAAY,CAAA,CAAI,IAAA,CACvET,CACF,CAAA,CACM7C,CAAAA,CAAQ8D,OAAAA,CAAQ,IAAM,CAC1B,IAAMC,CAAAA,CAAAA,CAAUtB,CAAAA,EAAQ,EAAC,EAAG,OAAA,CAASuB,CAAAA,EAASA,CAAAA,CAAK,KAAK,CAAA,CAClDC,CAAAA,CAAiBJ,CAAAA,EAAkB,IAAA,CAAK,KAAA,EAAO,IAAA,EAAK,CAE1D,OAAI,CAACP,CAAAA,EAAgB,CAACW,CAAAA,CACbF,CAAAA,CAGFA,CAAAA,CAAO,GAAA,CAAKpE,CAAAA,EACbA,CAAAA,CAAK,EAAA,GAAO2D,CAAAA,EAAgB3D,CAAAA,CAAK,KAAA,EAAO,IAAA,EAAK,CACxCA,CAAAA,CAGF,CAAE,GAAGA,CAAAA,CAAM,KAAA,CAAOsE,CAAe,CACzC,CACH,CAAA,CAAG,CAACJ,CAAAA,EAAkB,IAAA,CAAK,KAAA,CAAOP,CAAAA,CAAcb,CAAI,CAAC,CAAA,CAC/CyB,CAAAA,CAAUzB,CAAAA,GAAOA,CAAAA,CAAK,MAAA,CAAS,CAAC,CAAA,EAAG,OAAA,EAAW,KAAA,CAC9CxC,CAAAA,CAAS6D,OAAAA,CAAQ,IAAM/D,EAAAA,CAAWC,CAAK,CAAA,CAAG,CAACA,CAAK,CAAC,CAAA,CAOvD,GAJAR,SAAAA,CAAU,IAAM,CACT+D,CAAAA,GACP,CAAA,CAAG,CAACD,CAAAA,CAAcC,CAAM,CAAC,EAErB,CAAChB,CAAAA,EAAa,CAAChC,CAAAA,CACjB,OAAO,IAAA,CAGT,IAAM4D,CAAAA,CAAU5D,CAAAA,CACV6D,CAAAA,CAAgB7B,CAAAA,CAEtB,SAAS8B,CAAAA,CAASC,CAAAA,CAAsB,CACtCpB,CAAAA,CAAS,CAAA,YAAA,EAAeoB,CAAM,CAAA,CAAE,EAClC,CAEA,SAASC,CAAAA,CAAcD,CAAAA,CAAsB,CACtCf,CAAAA,EAAO,CAERe,CAAAA,GAAWhB,CAAAA,CACbJ,CAAAA,CAAS,OAAO,CAAA,CAGXE,CAAAA,CAAaR,CAAAA,CAAMwB,CAAa,CAAA,CAAE,SAAA,CAAUE,CAAM,CAAA,CAAG,MAAA,CAAW,CAAE,UAAA,CAAY,KAAM,CAAC,EAE9F,CAEA,OACEjD,GAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,cAAA,CACZ,QAAA,CAAAmB,CAAAA,CACCnB,GAAAA,CAACiB,EAAAA,CAAA,CAAc,SAAA,CAAW8B,CAAAA,CAAe,KAAA,CAAO5B,CAAAA,CAAO,MAAA,CAAQ6B,CAAAA,CAAU,EAEzEjD,IAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,cAAA,CACZ,QAAA,CAAA,CAAAnB,CAAAA,CAAO,GAAA,CAAKE,CAAAA,EACXiB,IAAAA,CAAC,KAAA,CAAA,CACC,QAAA,CAAA,CAAAC,GAAAA,CAAC,GAAA,CAAA,CAAE,SAAA,CAAU,oEAAA,CAAsE,QAAA,CAAAlB,CAAAA,CAAM,KAAA,CAAM,CAAA,CAC/FkB,GAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,gBAAA,CACZ,QAAA,CAAAlB,CAAAA,CAAM,KAAA,CAAM,GAAA,CAAKR,CAAAA,EAChB0B,GAAAA,CAAChB,EAAAA,CAAA,CACC,MAAA,CAAQV,CAAAA,CAAK,EAAA,GAAO2D,CAAAA,CACpB,GAAA,CAAKa,CAAAA,CACL,IAAA,CAAMxE,CAAAA,CAEN,SAAA,CAAW,IAAG,CAAQ4D,CAAAA,GAAO,CAAA,CAC7B,SAAA,CAAW,IAAMgB,CAAAA,CAAc5E,CAAAA,CAAK,EAAE,CAAA,CACtC,MAAA,CAAQ,IAAM0E,CAAAA,CAAS1E,CAAAA,CAAK,EAAE,CAAA,CAAA,CAHzBA,CAAAA,CAAK,EAIZ,CACD,CAAA,CACH,CAAA,CAAA,CAAA,CAdQQ,CAAAA,CAAM,KAehB,CACD,CAAA,CACAuC,GAAAA,CACCtB,IAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,sFAAA,CACb,QAAA,CAAA,CAAAC,GAAAA,CAACyB,YAAAA,CAAA,CAAa,SAAA,CAAU,6BAAA,CAA8B,CAAA,CAAE,yBAAA,CAAA,CAE1D,CAAA,CACE,IAAA,CACH,CAACJ,GAAAA,EAAa1C,CAAAA,CAAM,MAAA,GAAW,CAAA,CAC9BoB,IAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,iHAAA,CACb,QAAA,CAAA,CAAAC,GAAAA,CAACmD,iBAAAA,CAAA,CAAkB,SAAA,CAAU,WAAA,CAAY,CAAA,CACzCnD,GAAAA,CAAC,MAAA,CAAA,CAAK,QAAA,CAAA,oDAAA,CAAkD,CAAA,CAAA,CAC1D,CAAA,CACE,IAAA,CACH6C,CAAAA,CACC7C,GAAAA,CAACG,CAAAA,CAAA,CAAO,SAAA,CAAU,WAAA,CAAY,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAS,IAAG,CAAQgC,CAAAA,CAAQC,CAAAA,CAAO,CAAC,EAAA,CAAA,CAAG,QAAA,CAAA,WAAA,CAE/F,CAAA,CACE,IAAA,CAAA,CACN,CAAA,CAEJ,CAEJ","file":"chunk-22DXYOXX.js","sourcesContent":["import type { JSX } from 'react';\nimport { useEffect, useMemo, useState, useTransition } from 'react';\nimport { LoaderCircle, MessageSquareIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon } from 'lucide-react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport useSWR, { useSWRConfig } from 'swr';\nimport useSWRInfinite from 'swr/infinite';\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Input,\n} from '../../../ui';\nimport { fetcher, paths, type ApiClient } from '../api';\nimport { useProjectApi } from '../replay';\nimport type { AgentChat, AgentChatDetail, AgentChatSearchResult, AgentChatsList } from '../types';\n\n/** Returns the absolute route prefix of the agent app (`.../ai/agent`). */\nexport function useAgentBasePath(): string {\n const location = useLocation();\n const marker = '/agent';\n const index = location.pathname.indexOf(marker);\n\n if (index === -1) {\n return `${location.pathname.replace(/\\/$/, '')}/agent`;\n }\n\n return location.pathname.slice(0, index + marker.length);\n}\n\nexport function useDebouncedValue(value: string, delayMs: number): string {\n const [debounced, setDebounced] = useState(value);\n\n useEffect(() => {\n const timeout = window.setTimeout(() => {\n setDebounced(value);\n }, delayMs);\n\n return () => {\n window.clearTimeout(timeout);\n };\n }, [delayMs, value]);\n\n return debounced;\n}\n\nfunction getChatGroupLabel(chat: AgentChat): string {\n const timestamp = new Date(chat.lastMessageAt ?? chat.updatedAt).getTime();\n const now = new Date();\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n\n if (timestamp >= startOfToday) {\n return 'Today';\n }\n\n if (timestamp >= startOfToday - 86_400_000) {\n return 'Yesterday';\n }\n\n if (timestamp >= startOfToday - 7 * 86_400_000) {\n return 'Previous 7 days';\n }\n\n return 'Older';\n}\n\nfunction groupChats(chats: AgentChat[]): { chats: AgentChat[]; label: string }[] {\n const groups: { chats: AgentChat[]; label: string }[] = [];\n\n for (const chat of chats) {\n const label = getChatGroupLabel(chat);\n const group = groups.find((candidate) => candidate.label === label);\n\n if (group) {\n group.chats.push(chat);\n } else {\n groups.push({ chats: [chat], label });\n }\n }\n\n return groups;\n}\n\nfunction ChatListItem({\n active,\n api,\n chat,\n onChanged,\n onDeleted,\n onOpen,\n}: {\n active: boolean;\n api: ApiClient;\n chat: AgentChat;\n onChanged: () => void;\n onDeleted: () => void;\n onOpen: () => void;\n}): JSX.Element {\n const [dialog, setDialog] = useState<'rename' | 'delete' | null>(null);\n const [title, setTitle] = useState(chat.title ?? '');\n const [isMutating, startMutation] = useTransition();\n const label = chat.title?.trim() || 'New chat';\n\n function handleRename(): void {\n startMutation(async () => {\n await api.renameAgentChat(chat.id, title.trim());\n setDialog(null);\n onChanged();\n });\n }\n\n function handleDelete(): void {\n startMutation(async () => {\n await api.deleteAgentChat(chat.id);\n setDialog(null);\n onDeleted();\n });\n }\n\n return (\n <div\n className={`fk:group/chat fk:flex fk:items-center fk:gap-1 fk:rounded-md fk:pr-1 ${\n active ? 'fk:bg-sidebar-accent' : 'fk:hover:bg-accent/60'\n } fk:corner-squircle`}\n >\n <button\n className=\"fk:min-w-0 fk:flex-1 fk:truncate fk:px-2 fk:py-1.5 fk:text-left fk:text-sm\"\n title={label}\n type=\"button\"\n onClick={onOpen}\n >\n {label}\n </button>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n className=\"fk:size-6 fk:shrink-0 fk:opacity-0 fk:group-hover/chat:opacity-100 fk:data-[state=open]:opacity-100\"\n size=\"icon\"\n variant=\"ghost\"\n >\n <MoreHorizontalIcon className=\"fk:size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem\n onClick={() => {\n setTitle(chat.title ?? '');\n setDialog('rename');\n }}\n >\n <PencilIcon className=\"fk:size-4\" />\n Rename\n </DropdownMenuItem>\n <DropdownMenuItem className=\"fk:text-destructive\" onClick={() => setDialog('delete')}>\n <Trash2Icon className=\"fk:size-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n <Dialog open={dialog === 'rename'} onOpenChange={(open) => setDialog(open ? 'rename' : null)}>\n <DialogContent className=\"fk:max-w-sm\">\n <DialogHeader>\n <DialogTitle>Rename chat</DialogTitle>\n </DialogHeader>\n <Input value={title} onChange={(event) => setTitle(event.target.value)} placeholder=\"Chat title\" />\n <DialogFooter>\n <Button disabled={isMutating || !title.trim()} size=\"sm\" onClick={handleRename}>\n {isMutating ? 'Saving...' : 'Save'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Dialog open={dialog === 'delete'} onOpenChange={(open) => setDialog(open ? 'delete' : null)}>\n <DialogContent className=\"fk:max-w-sm\">\n <DialogHeader>\n <DialogTitle>Delete chat?</DialogTitle>\n </DialogHeader>\n <p className=\"fk:text-sm fk:text-muted-foreground\">\n &ldquo;{label}&rdquo; and its messages will be permanently deleted.\n </p>\n <DialogFooter>\n <Button disabled={isMutating} size=\"sm\" variant=\"destructive\" onClick={handleDelete}>\n {isMutating ? 'Deleting...' : 'Delete'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n );\n}\n\nfunction SearchResults({\n onOpen,\n projectId,\n query,\n}: {\n onOpen: (_chatId: string) => void;\n projectId: string;\n query: string;\n}): JSX.Element {\n const { data, isLoading } = useSWR<{ results: AgentChatSearchResult[] }>(\n paths(projectId).agentChatSearch(query),\n fetcher,\n { keepPreviousData: true }\n );\n\n if (isLoading && !data) {\n return (\n <div className=\"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground\">\n <LoaderCircle className=\"fk:size-3.5 fk:animate-spin\" />\n Searching...\n </div>\n );\n }\n\n if (!data?.results.length) {\n return <p className=\"fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground\">No matching chats.</p>;\n }\n\n return (\n <div className=\"fk:space-y-1\">\n {data.results.map((result) => (\n <button\n className=\"fk:block fk:w-full fk:rounded-md fk:px-2 fk:py-1.5 fk:text-left fk:hover:bg-accent/60 fk:corner-squircle\"\n key={result.messageId}\n type=\"button\"\n onClick={() => onOpen(result.chatId)}\n >\n <span className=\"fk:block fk:truncate fk:text-sm\">{result.chatTitle?.trim() || 'New chat'}</span>\n <span className=\"fk:block fk:truncate fk:text-xs fk:text-muted-foreground\">{result.snippet}</span>\n </button>\n ))}\n </div>\n );\n}\n\n/**\n * ChatGPT-style chats section embedded in the AI sidebar: new chat button and\n * the user's recent chats grouped by date with rename/delete. When `query` is\n * set, the recent list is replaced by hybrid keyword + semantic search results.\n * Rendered inside the app layout route, so relative navigation resolves\n * against the app root (`agent`, `agent/chats/:chatId`).\n */\nexport function AgentChatsSection({ query }: { query: string }): JSX.Element | null {\n const { api, projectId } = useProjectApi();\n const navigate = useNavigate();\n const location = useLocation();\n const { mutate: globalMutate } = useSWRConfig();\n const activeChatId = /\\/agent\\/chats\\/([^/]+)/.exec(location.pathname)?.[1];\n const { data, isLoading, mutate, setSize, size } = useSWRInfinite<AgentChatsList>(\n (pageIndex, previousPage: AgentChatsList | null) => {\n if (!projectId || (previousPage && !previousPage.hasMore)) {\n return null;\n }\n\n return paths(projectId).agentChats(pageIndex * 30);\n },\n fetcher,\n { revalidateFirstPage: true }\n );\n const { data: activeChatDetail } = useSWR<AgentChatDetail>(\n projectId && activeChatId ? paths(projectId).agentChat(activeChatId) : null,\n fetcher\n );\n const chats = useMemo(() => {\n const listed = (data ?? []).flatMap((page) => page.chats);\n const generatedTitle = activeChatDetail?.chat.title?.trim();\n\n if (!activeChatId || !generatedTitle) {\n return listed;\n }\n\n return listed.map((chat) => {\n if (chat.id !== activeChatId || chat.title?.trim()) {\n return chat;\n }\n\n return { ...chat, title: generatedTitle };\n });\n }, [activeChatDetail?.chat.title, activeChatId, data]);\n const hasMore = data?.[data.length - 1]?.hasMore ?? false;\n const groups = useMemo(() => groupChats(chats), [chats]);\n\n // Refresh the list when the user lands on a chat (new chats, fresh titles).\n useEffect(() => {\n void mutate();\n }, [activeChatId, mutate]);\n\n if (!projectId || !api) {\n return null;\n }\n\n const chatApi = api;\n const chatProjectId = projectId;\n\n function openChat(chatId: string): void {\n navigate(`agent/chats/${chatId}`);\n }\n\n function handleDeleted(chatId: string): void {\n void mutate();\n\n if (chatId === activeChatId) {\n navigate('agent');\n } else {\n // Drop the cached detail so a re-open does not show stale data.\n void globalMutate(paths(chatProjectId).agentChat(chatId), undefined, { revalidate: false });\n }\n }\n\n return (\n <div className=\"fk:space-y-2\">\n {query ? (\n <SearchResults projectId={chatProjectId} query={query} onOpen={openChat} />\n ) : (\n <div className=\"fk:space-y-3\">\n {groups.map((group) => (\n <div key={group.label}>\n <p className=\"fk:px-2 fk:pb-1 fk:text-xs fk:font-medium fk:text-muted-foreground\">{group.label}</p>\n <div className=\"fk:space-y-0.5\">\n {group.chats.map((chat) => (\n <ChatListItem\n active={chat.id === activeChatId}\n api={chatApi}\n chat={chat}\n key={chat.id}\n onChanged={() => void mutate()}\n onDeleted={() => handleDeleted(chat.id)}\n onOpen={() => openChat(chat.id)}\n />\n ))}\n </div>\n </div>\n ))}\n {isLoading ? (\n <div className=\"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground\">\n <LoaderCircle className=\"fk:size-3.5 fk:animate-spin\" />\n Loading recent chats...\n </div>\n ) : null}\n {!isLoading && chats.length === 0 ? (\n <div className=\"fk:flex fk:flex-col fk:items-center fk:gap-2 fk:px-2 fk:py-6 fk:text-center fk:text-xs fk:text-muted-foreground\">\n <MessageSquareIcon className=\"fk:size-5\" />\n <span>No chats yet. Start a conversation with the agent.</span>\n </div>\n ) : null}\n {hasMore ? (\n <Button className=\"fk:w-full\" size=\"sm\" variant=\"ghost\" onClick={() => void setSize(size + 1)}>\n Load more\n </Button>\n ) : null}\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/apps/ai/agent/chat-list.tsx"],"names":["useAgentBasePath","location","useLocation","marker","index","useDebouncedValue","value","delayMs","debounced","setDebounced","useState","useEffect","timeout","getChatGroupLabel","chat","timestamp","now","startOfToday","groupChats","chats","groups","label","group","candidate","ChatListItem","active","api","onChanged","onDeleted","onOpen","dialog","setDialog","title","setTitle","isMutating","startMutation","useTransition","handleRename","handleDelete","jsxs","jsx","DropdownMenu","DropdownMenuTrigger","Button","MoreHorizontalIcon","DropdownMenuContent","DropdownMenuItem","PencilIcon","Trash2Icon","Dialog","open","DialogContent","DialogHeader","DialogTitle","Input","event","DialogFooter","SearchResults","projectId","query","data","isLoading","useSWR","paths","fetcher","LoaderCircle","result","AgentChatsSection","useProjectApi","navigate","useNavigate","globalMutate","useSWRConfig","activeChatId","mutate","setSize","size","useSWRInfinite","pageIndex","previousPage","activeChatDetail","useMemo","listed","page","generatedTitle","hasMore","chatApi","chatProjectId","openChat","chatId","handleDeleted","MessageSquareIcon"],"mappings":"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","file":"chunk-VPTDAFFZ.js","sourcesContent":["import type { JSX } from 'react';\nimport { useEffect, useMemo, useState, useTransition } from 'react';\nimport { LoaderCircle, MessageSquareIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon } from 'lucide-react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport useSWR, { useSWRConfig } from 'swr';\nimport useSWRInfinite from 'swr/infinite';\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Input,\n} from '../../../ui';\nimport { fetcher, paths, type ApiClient } from '../api';\nimport { useProjectApi } from '../replay';\nimport type { AgentChat, AgentChatDetail, AgentChatSearchResult, AgentChatsList } from '../types';\n\n/** Returns the absolute route prefix of the agent app (`.../ai/agent`). */\nexport function useAgentBasePath(): string {\n const location = useLocation();\n const marker = '/agent';\n const index = location.pathname.indexOf(marker);\n\n if (index === -1) {\n return `${location.pathname.replace(/\\/$/, '')}/agent`;\n }\n\n return location.pathname.slice(0, index + marker.length);\n}\n\nexport function useDebouncedValue(value: string, delayMs: number): string {\n const [debounced, setDebounced] = useState(value);\n\n useEffect(() => {\n const timeout = window.setTimeout(() => {\n setDebounced(value);\n }, delayMs);\n\n return () => {\n window.clearTimeout(timeout);\n };\n }, [delayMs, value]);\n\n return debounced;\n}\n\nfunction getChatGroupLabel(chat: AgentChat): string {\n const timestamp = new Date(chat.lastMessageAt ?? chat.updatedAt).getTime();\n const now = new Date();\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n\n if (timestamp >= startOfToday) {\n return 'Today';\n }\n\n if (timestamp >= startOfToday - 86_400_000) {\n return 'Yesterday';\n }\n\n if (timestamp >= startOfToday - 7 * 86_400_000) {\n return 'Previous 7 days';\n }\n\n return 'Older';\n}\n\nfunction groupChats(chats: AgentChat[]): { chats: AgentChat[]; label: string }[] {\n const groups: { chats: AgentChat[]; label: string }[] = [];\n\n for (const chat of chats) {\n const label = getChatGroupLabel(chat);\n const group = groups.find((candidate) => candidate.label === label);\n\n if (group) {\n group.chats.push(chat);\n } else {\n groups.push({ chats: [chat], label });\n }\n }\n\n return groups;\n}\n\nfunction ChatListItem({\n active,\n api,\n chat,\n onChanged,\n onDeleted,\n onOpen,\n}: {\n active: boolean;\n api: ApiClient;\n chat: AgentChat;\n onChanged: () => void;\n onDeleted: () => void;\n onOpen: () => void;\n}): JSX.Element {\n const [dialog, setDialog] = useState<'rename' | 'delete' | null>(null);\n const [title, setTitle] = useState(chat.title ?? '');\n const [isMutating, startMutation] = useTransition();\n const label = chat.title?.trim() || 'New chat';\n\n function handleRename(): void {\n startMutation(async () => {\n await api.renameAgentChat(chat.id, title.trim());\n setDialog(null);\n onChanged();\n });\n }\n\n function handleDelete(): void {\n startMutation(async () => {\n await api.deleteAgentChat(chat.id);\n setDialog(null);\n onDeleted();\n });\n }\n\n return (\n <div\n className={`fk:group/chat fk:flex fk:items-center fk:gap-1 fk:rounded-md fk:pr-1 ${\n active ? 'fk:bg-sidebar-accent' : 'fk:hover:bg-accent/60'\n } fk:corner-squircle`}\n >\n <button\n className=\"fk:min-w-0 fk:flex-1 fk:truncate fk:px-2 fk:py-1.5 fk:text-left fk:text-sm\"\n title={label}\n type=\"button\"\n onClick={onOpen}\n >\n {label}\n </button>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n className=\"fk:size-6 fk:shrink-0 fk:opacity-0 fk:group-hover/chat:opacity-100 fk:data-[state=open]:opacity-100\"\n size=\"icon\"\n variant=\"ghost\"\n >\n <MoreHorizontalIcon className=\"fk:size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem\n onClick={() => {\n setTitle(chat.title ?? '');\n setDialog('rename');\n }}\n >\n <PencilIcon className=\"fk:size-4\" />\n Rename\n </DropdownMenuItem>\n <DropdownMenuItem className=\"fk:text-destructive\" onClick={() => setDialog('delete')}>\n <Trash2Icon className=\"fk:size-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n <Dialog open={dialog === 'rename'} onOpenChange={(open) => setDialog(open ? 'rename' : null)}>\n <DialogContent className=\"fk:max-w-sm\">\n <DialogHeader>\n <DialogTitle>Rename chat</DialogTitle>\n </DialogHeader>\n <Input value={title} onChange={(event) => setTitle(event.target.value)} placeholder=\"Chat title\" />\n <DialogFooter>\n <Button disabled={isMutating || !title.trim()} size=\"sm\" onClick={handleRename}>\n {isMutating ? 'Saving...' : 'Save'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Dialog open={dialog === 'delete'} onOpenChange={(open) => setDialog(open ? 'delete' : null)}>\n <DialogContent className=\"fk:max-w-sm\">\n <DialogHeader>\n <DialogTitle>Delete chat?</DialogTitle>\n </DialogHeader>\n <p className=\"fk:text-sm fk:text-muted-foreground\">\n &ldquo;{label}&rdquo; and its messages will be permanently deleted.\n </p>\n <DialogFooter>\n <Button disabled={isMutating} size=\"sm\" variant=\"destructive\" onClick={handleDelete}>\n {isMutating ? 'Deleting...' : 'Delete'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n );\n}\n\nfunction SearchResults({\n onOpen,\n projectId,\n query,\n}: {\n onOpen: (_chatId: string) => void;\n projectId: string;\n query: string;\n}): JSX.Element {\n const { data, isLoading } = useSWR<{ results: AgentChatSearchResult[] }>(\n paths(projectId).agentChatSearch(query),\n fetcher,\n { keepPreviousData: true }\n );\n\n if (isLoading && !data) {\n return (\n <div className=\"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground\">\n <LoaderCircle className=\"fk:size-3.5 fk:animate-spin\" />\n Searching...\n </div>\n );\n }\n\n if (!data?.results.length) {\n return <p className=\"fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground\">No matching chats.</p>;\n }\n\n return (\n <div className=\"fk:space-y-1\">\n {data.results.map((result) => (\n <button\n className=\"fk:block fk:w-full fk:rounded-md fk:px-2 fk:py-1.5 fk:text-left fk:hover:bg-accent/60 fk:corner-squircle\"\n key={result.messageId}\n type=\"button\"\n onClick={() => onOpen(result.chatId)}\n >\n <span className=\"fk:block fk:truncate fk:text-sm\">{result.chatTitle?.trim() || 'New chat'}</span>\n <span className=\"fk:block fk:truncate fk:text-xs fk:text-muted-foreground\">{result.snippet}</span>\n </button>\n ))}\n </div>\n );\n}\n\n/**\n * ChatGPT-style chats section embedded in the AI sidebar: new chat button and\n * the user's recent chats grouped by date with rename/delete. When `query` is\n * set, the recent list is replaced by hybrid keyword + semantic search results.\n * Rendered inside the app layout route, so relative navigation resolves\n * against the app root (`agent`, `agent/chats/:chatId`).\n */\nexport function AgentChatsSection({ query }: { query: string }): JSX.Element | null {\n const { api, projectId } = useProjectApi();\n const navigate = useNavigate();\n const location = useLocation();\n const { mutate: globalMutate } = useSWRConfig();\n const activeChatId = /\\/agent\\/chats\\/([^/]+)/.exec(location.pathname)?.[1];\n const { data, isLoading, mutate, setSize, size } = useSWRInfinite<AgentChatsList>(\n (pageIndex, previousPage: AgentChatsList | null) => {\n if (!projectId || (previousPage && !previousPage.hasMore)) {\n return null;\n }\n\n return paths(projectId).agentChats(pageIndex * 30);\n },\n fetcher,\n { revalidateFirstPage: true }\n );\n const { data: activeChatDetail } = useSWR<AgentChatDetail>(\n projectId && activeChatId ? paths(projectId).agentChat(activeChatId) : null,\n fetcher\n );\n const chats = useMemo(() => {\n const listed = (data ?? []).flatMap((page) => page.chats);\n const generatedTitle = activeChatDetail?.chat.title?.trim();\n\n if (!activeChatId || !generatedTitle) {\n return listed;\n }\n\n return listed.map((chat) => {\n if (chat.id !== activeChatId || chat.title?.trim()) {\n return chat;\n }\n\n return { ...chat, title: generatedTitle };\n });\n }, [activeChatDetail?.chat.title, activeChatId, data]);\n const hasMore = data?.[data.length - 1]?.hasMore ?? false;\n const groups = useMemo(() => groupChats(chats), [chats]);\n\n // Refresh the list when the user lands on a chat (new chats, fresh titles).\n useEffect(() => {\n void mutate();\n }, [activeChatId, mutate]);\n\n if (!projectId || !api) {\n return null;\n }\n\n const chatApi = api;\n const chatProjectId = projectId;\n\n function openChat(chatId: string): void {\n navigate(`agent/chats/${chatId}`);\n }\n\n function handleDeleted(chatId: string): void {\n void mutate();\n\n if (chatId === activeChatId) {\n navigate('agent');\n } else {\n // Drop the cached detail so a re-open does not show stale data.\n void globalMutate(paths(chatProjectId).agentChat(chatId), undefined, { revalidate: false });\n }\n }\n\n return (\n <div className=\"fk:space-y-2\">\n {query ? (\n <SearchResults projectId={chatProjectId} query={query} onOpen={openChat} />\n ) : (\n <div className=\"fk:space-y-3\">\n {groups.map((group) => (\n <div key={group.label}>\n <p className=\"fk:px-2 fk:pb-1 fk:text-xs fk:font-medium fk:text-muted-foreground\">{group.label}</p>\n <div className=\"fk:space-y-0.5\">\n {group.chats.map((chat) => (\n <ChatListItem\n active={chat.id === activeChatId}\n api={chatApi}\n chat={chat}\n key={chat.id}\n onChanged={() => void mutate()}\n onDeleted={() => handleDeleted(chat.id)}\n onOpen={() => openChat(chat.id)}\n />\n ))}\n </div>\n </div>\n ))}\n {isLoading ? (\n <div className=\"fk:flex fk:items-center fk:gap-2 fk:px-2 fk:py-3 fk:text-xs fk:text-muted-foreground\">\n <LoaderCircle className=\"fk:size-3.5 fk:animate-spin\" />\n Loading recent chats...\n </div>\n ) : null}\n {!isLoading && chats.length === 0 ? (\n <div className=\"fk:flex fk:flex-col fk:items-center fk:gap-2 fk:px-2 fk:py-6 fk:text-center fk:text-xs fk:text-muted-foreground\">\n <MessageSquareIcon className=\"fk:size-5\" />\n <span>No chats yet. Start a conversation with the agent.</span>\n </div>\n ) : null}\n {hasMore ? (\n <Button className=\"fk:w-full\" size=\"sm\" variant=\"ghost\" onClick={() => void setSize(size + 1)}>\n Load more\n </Button>\n ) : null}\n </div>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import'./index.css';var e="flexkit://auth/callback";function t(){if(!(typeof window>"u"))return window.flexkitDesktop}
2
+ export{e as a,t as b};//# sourceMappingURL=chunk-XDLTO6VU.js.map
3
+ //# sourceMappingURL=chunk-XDLTO6VU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/desktop.ts"],"names":["DESKTOP_LOGIN_CALLBACK","getDesktop"],"mappings":"oBAyBO,IAAMA,CAAAA,CAAyB,0BAK/B,SAASC,CAAAA,EAA+C,CAC7D,GAAI,EAAA,OAAO,MAAA,CAAW,GAAA,CAAA,CAItB,OAAQ,MAAA,CAAwC,cAClD","file":"chunk-XDLTO6VU.js","sourcesContent":["/**\n * The bridge the Flexkit Studio desktop app (Electron) exposes to the Studio\n * through its preload script, as `window.flexkitDesktop`. Studio features\n * check for the bridge (a capability), never for a platform, so the web\n * Studio never sees any of this.\n */\nexport interface FlexkitDesktopBridge {\n platform: 'darwin' | 'win32' | 'linux';\n /** Installed app version; it tracks the `@flexkit/studio` version it bundles. */\n version: string;\n /** The URL the OAuth login hands the session code to; the app registered its scheme with the OS. */\n loginCallback: string;\n /** Dock (macOS) or taskbar badge; 0 clears it. */\n setBadgeCount: (count: number) => void;\n /** Brings the Studio window to the front (a clicked notification, for example). */\n focusWindow: () => void;\n /** Paths the app asks the Studio to open: deep links and menu items. Returns the unsubscribe function. */\n onNavigate: (listener: (path: string) => void) => () => void;\n /** An update has been downloaded and installs on the next restart. Returns the unsubscribe function. */\n onUpdateDownloaded: (listener: (update: { version: string }) => void) => () => void;\n /** Restarts into the downloaded update. */\n installUpdate: () => void;\n}\n\n/** The desktop app's login callback (see `DESKTOP_LOGIN_CALLBACK` in the Electron adapter). */\nexport const DESKTOP_LOGIN_CALLBACK = 'flexkit://auth/callback';\n\ntype WindowWithDesktop = { flexkitDesktop?: FlexkitDesktopBridge };\n\n/** The desktop bridge when the Studio runs inside the desktop app, otherwise undefined. */\nexport function getDesktop(): FlexkitDesktopBridge | undefined {\n if (typeof window === 'undefined') {\n return undefined;\n }\n\n return (window as unknown as WindowWithDesktop).flexkitDesktop;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import'./index.css';import {b as b$1}from'./chunk-XDLTO6VU.js';import {d as d$1}from'./chunk-TYDYAOQ7.js';import y from'swr';var d="1970-01-01T00:00:00.000Z";function I(t){return t.chatId?`agent/chats/${t.chatId}`:t.automationId&&t.automationRunId?`automations/${t.automationId}/runs/${t.automationRunId}`:null}function b(t){return t.chatId?"View chat":t.automationId&&t.automationRunId?"View run":null}var l={answered:"Answered",approved:"Approved",cancelled:"Cancelled",dismissed:"Dismissed",expired:"Expired",rejected:"Rejected"};function m(t){return t.kind==="automation_failure"?"Failed":t.kind==="automation_result"?"Result":t.kind==="monitor_event"?"Monitor":t.resolution?l[t.resolution]:!t.requiresAction||t.state==="done"||t.state==="dismissed"?null:t.kind==="question"?"Needs answer":"Needs approval"}function w(t){return t.kind==="automation_failure"?"failure":p(t)?"attention":"neutral"}function p(t){return t.requiresAction&&t.resolvedAt===null&&t.state!=="dismissed"&&t.state!=="done"}function N({mutedKinds:t=[],newItems:n,since:e}){let i=n.reduce((o,u)=>o===null||u.createdAt>o?u.createdAt:o,null);if(e===null)return {notifications:[],since:i??d};let r=n.filter(o=>o.createdAt>e&&!t.includes(o.kind)),s=i!==null&&i>e?i:e;return r.length===0?{notifications:[],since:s}:r.length>3?{notifications:[{body:"Open Activity in Flexkit Studio to review them.",path:"activity",tag:`flexkit-activity-batch:${s}`,title:`${r.length.toString()} new items in Activity`}],since:s}:{notifications:r.map(o=>({body:o.body??"",path:`activity/${encodeURIComponent(o.id)}`,tag:`flexkit-activity:${o.id}`,title:o.title})),since:s}}var A={approvalEmail:true,desktopMutedKinds:[]};async function c(t,n){let e=await fetch(t,{credentials:"same-origin",...n,headers:{"Content-Type":"application/json",...n?.headers}});if(!e.ok)throw new Error(`Request failed with ${e.status.toString()}.`);return await e.json()}function k(){let{data:t,error:n,mutate:e}=y(d$1.notificationPreferences,i=>c(i));return {isLoaded:t!==void 0||n!==void 0,mutate:e,preferences:t?.preferences??A}}function R(t){return c(d$1.notificationPreferences,{body:JSON.stringify(t),method:"PUT"})}function g(){return typeof window<"u"&&b$1()===void 0&&typeof Notification<"u"&&"serviceWorker"in navigator&&"PushManager"in window}function v(t){let n=`${t}${"=".repeat((4-t.length%4)%4)}`.replace(/-/g,"+").replace(/_/g,"/"),e=atob(n),i=new Uint8Array(new ArrayBuffer(e.length));for(let r=0;r<e.length;r+=1)i[r]=e.charCodeAt(r);return i}async function E(){if(!g()||Notification.permission!=="granted")return false;let{publicKey:t}=await c(d$1.push);if(!t)return false;await navigator.serviceWorker.register("/sw.js",{scope:"/"});let{pushManager:n}=await navigator.serviceWorker.ready,e=v(t),i=await n.getSubscription();return i&&!h(i.options.applicationServerKey,e)&&(await i.unsubscribe(),i=null),i??=await n.subscribe({applicationServerKey:e,userVisibleOnly:true}),await c(d$1.push,{body:JSON.stringify(i.toJSON()),method:"POST"}),true}function h(t,n){return !t||t.byteLength!==n.byteLength?false:new Uint8Array(t).every((i,r)=>i===n[r])}export{I as a,b,m as c,w as d,p as e,N as f,k as g,R as h,E as i};//# sourceMappingURL=chunk-ZLWYVTGO.js.map
2
+ //# sourceMappingURL=chunk-ZLWYVTGO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/ai/activity.ts","../src/apps/ai/notification-settings.ts"],"names":["EPOCH","getActivitySourcePath","item","getActivitySourceLabel","RESOLUTION_LABELS","getActivityStatusLabel","getActivityStatusTone","isActivityItemOpen","planActivityNotifications","mutedKinds","newItems","since","newest","latest","fresh","nextSince","DEFAULT_NOTIFICATION_PREFERENCES","userRequest","url","init","response","useNotificationPreferences","data","error","mutate","useSWR","userApiPaths","saveNotificationPreferences","preferences","isWebPushSupported","getDesktop","decodeApplicationServerKey","base64Url","base64","binary","bytes","index","ensurePushSubscription","publicKey","pushManager","applicationServerKey","subscription","sameKey","current","expected","byte"],"mappings":"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","file":"chunk-ZLWYVTGO.js","sourcesContent":["import type { ActivityItem, ActivityItemKind, ActivityItemResolution } from './types';\n\n/** More new items than this in one poll are announced by a single summary notification. */\nconst MAX_SEPARATE_NOTIFICATIONS = 3;\n\n/** Watermark before the first item exists: every later unread item is new. */\nconst EPOCH = '1970-01-01T00:00:00.000Z';\n\nexport interface ActivityNotification {\n body: string;\n /** AI-app relative path opened on click, e.g. `activity/<id>`. */\n path: string;\n /** Same tag = same notification across tabs and repeated polls. */\n tag: string;\n title: string;\n}\n\n/** Path, relative to the AI app, of the chat or run an item came from. */\nexport function getActivitySourcePath(item: ActivityItem): string | null {\n if (item.chatId) {\n return `agent/chats/${item.chatId}`;\n }\n\n if (item.automationId && item.automationRunId) {\n return `automations/${item.automationId}/runs/${item.automationRunId}`;\n }\n\n return null;\n}\n\nexport function getActivitySourceLabel(item: ActivityItem): string | null {\n if (item.chatId) {\n return 'View chat';\n }\n\n return item.automationId && item.automationRunId ? 'View run' : null;\n}\n\nconst RESOLUTION_LABELS: { [key in ActivityItemResolution]: string } = {\n answered: 'Answered',\n approved: 'Approved',\n cancelled: 'Cancelled',\n dismissed: 'Dismissed',\n expired: 'Expired',\n rejected: 'Rejected',\n};\n\n/** Short status for a row: what happened to it, or that it still waits on the viewer. */\nexport function getActivityStatusLabel(item: ActivityItem): string | null {\n if (item.kind === 'automation_failure') {\n return 'Failed';\n }\n\n if (item.kind === 'automation_result') {\n return 'Result';\n }\n\n if (item.kind === 'monitor_event') {\n return 'Monitor';\n }\n\n if (item.resolution) {\n return RESOLUTION_LABELS[item.resolution];\n }\n\n if (!item.requiresAction || item.state === 'done' || item.state === 'dismissed') {\n return null;\n }\n\n return item.kind === 'question' ? 'Needs answer' : 'Needs approval';\n}\n\n/** How a row's status badge reads: waiting on the viewer, a failure, or neutral. */\nexport function getActivityStatusTone(item: ActivityItem): 'attention' | 'failure' | 'neutral' {\n if (item.kind === 'automation_failure') {\n return 'failure';\n }\n\n return isActivityItemOpen(item) ? 'attention' : 'neutral';\n}\n\n/** True while the item still waits on the viewer. */\nexport function isActivityItemOpen(item: ActivityItem): boolean {\n return item.requiresAction && item.resolvedAt === null && item.state !== 'dismissed' && item.state !== 'done';\n}\n\n/**\n * Turns one summary poll into desktop notifications and the next watermark.\n * The first poll (no watermark yet) only seeds it, so opening Studio never\n * replays notifications for items the user already had.\n */\nexport function planActivityNotifications({\n mutedKinds = [],\n newItems,\n since,\n}: {\n /** Kinds the viewer turned off for desktop notifications; they still move the watermark. */\n mutedKinds?: ActivityItemKind[];\n newItems: ActivityItem[];\n since: string | null;\n}): { notifications: ActivityNotification[]; since: string } {\n const newest = newItems.reduce<string | null>(\n (latest, item) => (latest === null || item.createdAt > latest ? item.createdAt : latest),\n null\n );\n\n if (since === null) {\n return { notifications: [], since: newest ?? EPOCH };\n }\n\n const fresh = newItems.filter((item) => item.createdAt > since && !mutedKinds.includes(item.kind));\n const nextSince = newest !== null && newest > since ? newest : since;\n\n if (fresh.length === 0) {\n return { notifications: [], since: nextSince };\n }\n\n if (fresh.length > MAX_SEPARATE_NOTIFICATIONS) {\n return {\n notifications: [\n {\n body: 'Open Activity in Flexkit Studio to review them.',\n path: 'activity',\n tag: `flexkit-activity-batch:${nextSince}`,\n title: `${fresh.length.toString()} new items in Activity`,\n },\n ],\n since: nextSince,\n };\n }\n\n return {\n notifications: fresh.map((item) => ({\n body: item.body ?? '',\n path: `activity/${encodeURIComponent(item.id)}`,\n tag: `flexkit-activity:${item.id}`,\n title: item.title,\n })),\n since: nextSince,\n };\n}\n","import useSWR, { type KeyedMutator } from 'swr';\nimport { userApiPaths } from '../../core/api-paths';\nimport { getDesktop } from '../../core/desktop';\nimport type { ActivityItemKind } from './types';\n\n/** A person's notification choices, shared by every project they open. */\nexport interface NotificationPreferences {\n /** Email an approval request left unread past the project's delay. */\n approvalEmail: boolean;\n /** Kinds that never raise a desktop notification. */\n desktopMutedKinds: ActivityItemKind[];\n}\n\nexport const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences = { approvalEmail: true, desktopMutedKinds: [] };\n\nasync function userRequest<T>(url: string, init?: RequestInit): Promise<T> {\n const response = await fetch(url, {\n credentials: 'same-origin',\n ...init,\n headers: { 'Content-Type': 'application/json', ...init?.headers },\n });\n\n if (!response.ok) {\n throw new Error(`Request failed with ${response.status.toString()}.`);\n }\n\n return (await response.json()) as T;\n}\n\nexport function useNotificationPreferences(): {\n /** False until the stored preferences arrived (or could not be loaded, which means the defaults). */\n isLoaded: boolean;\n mutate: KeyedMutator<{ preferences: NotificationPreferences }>;\n preferences: NotificationPreferences;\n} {\n const { data, error, mutate } = useSWR<{ preferences: NotificationPreferences }>(\n userApiPaths.notificationPreferences,\n (url: string) => userRequest<{ preferences: NotificationPreferences }>(url)\n );\n\n return {\n isLoaded: data !== undefined || error !== undefined,\n mutate,\n preferences: data?.preferences ?? DEFAULT_NOTIFICATION_PREFERENCES,\n };\n}\n\nexport function saveNotificationPreferences(preferences: NotificationPreferences): Promise<{ preferences: NotificationPreferences }> {\n return userRequest(userApiPaths.notificationPreferences, { body: JSON.stringify(preferences), method: 'PUT' });\n}\n\nexport function isWebPushSupported(): boolean {\n // The desktop app keeps polling while it runs in the background and notifies natively.\n return (\n typeof window !== 'undefined' &&\n getDesktop() === undefined &&\n typeof Notification !== 'undefined' &&\n 'serviceWorker' in navigator &&\n 'PushManager' in window\n );\n}\n\n/** The VAPID public key as the bytes `PushManager.subscribe` expects. */\nexport function decodeApplicationServerKey(base64Url: string): Uint8Array<ArrayBuffer> {\n const base64 = `${base64Url}${'='.repeat((4 - (base64Url.length % 4)) % 4)}`.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(base64);\n const bytes = new Uint8Array(new ArrayBuffer(binary.length));\n\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n\n return bytes;\n}\n\n/**\n * Hosted Studio only: registers the service worker and saves this browser's\n * push subscription for the signed-in user, so Activity items arrive while no\n * Studio tab is open. Needs notification permission; never asks for it.\n * Safe to call on every load: an existing subscription is saved again.\n * Returns false when Web Push is unavailable here.\n */\nexport async function ensurePushSubscription(): Promise<boolean> {\n if (!isWebPushSupported() || Notification.permission !== 'granted') {\n return false;\n }\n\n const { publicKey } = await userRequest<{ publicKey: string | null }>(userApiPaths.push);\n\n if (!publicKey) {\n return false;\n }\n\n await navigator.serviceWorker.register('/sw.js', { scope: '/' });\n\n const { pushManager } = await navigator.serviceWorker.ready;\n const applicationServerKey = decodeApplicationServerKey(publicKey);\n let subscription = await pushManager.getSubscription();\n\n // A subscription made with an older key would never receive again.\n if (subscription && !sameKey(subscription.options.applicationServerKey, applicationServerKey)) {\n await subscription.unsubscribe();\n subscription = null;\n }\n\n subscription ??= await pushManager.subscribe({ applicationServerKey, userVisibleOnly: true });\n\n await userRequest(userApiPaths.push, { body: JSON.stringify(subscription.toJSON()), method: 'POST' });\n\n return true;\n}\n\nfunction sameKey(current: ArrayBuffer | null, expected: Uint8Array): boolean {\n if (!current || current.byteLength !== expected.byteLength) {\n return false;\n }\n\n const bytes = new Uint8Array(current);\n\n return bytes.every((byte, index) => byte === expected[index]);\n}\n"]}
@@ -0,0 +1,103 @@
1
+ export { F as FlexkitHandlerContext, a as FlexkitHandlerResult, h as handleFlexkitRequest } from './core-handler-XAVVKLhV.js';
2
+
3
+ /**
4
+ * Where the OAuth login hands the one-time session code to the desktop app.
5
+ * The app registers the `flexkit://` scheme with the operating system; the
6
+ * Flexkit backend allows exactly this URL as a login callback.
7
+ */
8
+ declare const DESKTOP_LOGIN_CALLBACK = "flexkit://auth/callback";
9
+ /**
10
+ * Where the desktop app keeps the Flexkit session. The app stores it encrypted
11
+ * with the operating system's keychain (Electron `safeStorage`); this module
12
+ * never sees where.
13
+ */
14
+ interface DesktopSessionStore {
15
+ get: () => Promise<string> | string;
16
+ set: (token: string) => Promise<void> | void;
17
+ clear: () => Promise<void> | void;
18
+ }
19
+ type SessionChange = {
20
+ type: 'set';
21
+ token: string;
22
+ } | {
23
+ type: 'clear';
24
+ };
25
+ /**
26
+ * Reads the session change the BFF core expresses as a `Set-Cookie` header.
27
+ * Returns null for cookies that are not the session.
28
+ */
29
+ declare function parseSessionCookie(setCookie: string): SessionChange | null;
30
+ /**
31
+ * The nonce the desktop app adds to a login it starts and the Flexkit backend
32
+ * echoes to the callback: only a callback carrying the expected one is
33
+ * exchanged, so a `flexkit://auth/callback` link from anywhere else cannot
34
+ * sign the app into another account.
35
+ */
36
+ declare const DESKTOP_LOGIN_STATE_PARAM = "state";
37
+ /**
38
+ * A login start URL on the Flexkit backend
39
+ * (`https://<flexkit>/auth/login/<provider>?origin=flexkit://auth/callback&...`).
40
+ * Only that host gets the nonce: it is the secret that lets a callback in.
41
+ */
42
+ declare function isDesktopLoginStart(url: string): boolean;
43
+ /** The login start URL with the app's nonce, for the backend to echo to the callback. */
44
+ declare function withDesktopLoginState(url: string, state: string): string;
45
+ /** What a `flexkit://auth/callback` URL carries. */
46
+ interface DesktopLoginCallback {
47
+ code: string;
48
+ /** In-app path to open after the login; always absolute within the app. */
49
+ redirect: string;
50
+ /** The nonce echoed from the login start, if any. */
51
+ state: string | null;
52
+ }
53
+ /**
54
+ * Parses the login callback the OS hands the app. Anything but the exact
55
+ * callback path is refused, and the redirect is kept inside the app: only an
56
+ * absolute path, never a URL (a `//host` prefix is a URL too).
57
+ */
58
+ declare function parseDesktopLoginCallback(url: string): DesktopLoginCallback | null;
59
+ interface CompleteDesktopLoginOptions {
60
+ /** The nonce of the login this app started; null when it started none. */
61
+ expectedState: string | null;
62
+ }
63
+ /**
64
+ * Finishes a desktop login: exchanges the code from the callback for a
65
+ * session and stores it. Resolves to the in-app path to open, or null when
66
+ * the callback, its state or the code was not accepted. A callback is only
67
+ * accepted for a login this app started (`expectedState`).
68
+ */
69
+ declare function completeDesktopLogin(url: string, sessionStore: DesktopSessionStore, { expectedState }: CompleteDesktopLoginOptions): Promise<string | null>;
70
+ interface FlexkitElectronHandlerOptions {
71
+ sessionStore: DesktopSessionStore;
72
+ }
73
+ /**
74
+ * The Studio BFF for the desktop app: the handler the main process gives
75
+ * `protocol.handle` for the Studio's `/api/flexkit/*` requests. It is the same
76
+ * core the Next.js, Astro and TanStack Start adapters wrap, with the session
77
+ * read from and written to the app's store instead of an HttpOnly cookie.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const handleApi = createFlexkitElectronHandler({ sessionStore });
82
+ *
83
+ * protocol.handle('flexkit', (request) => {
84
+ * const { pathname } = new URL(request.url);
85
+ *
86
+ * return pathname.startsWith('/api/flexkit/') ? handleApi(request) : serveStudio(request);
87
+ * });
88
+ * ```
89
+ */
90
+ declare function createFlexkitElectronHandler({ sessionStore, }: FlexkitElectronHandlerOptions): (request: Request) => Promise<Response>;
91
+
92
+ /**
93
+ * Exchange an OAuth authorization code for a session token.
94
+ * This function is framework-agnostic and can be used in any server environment.
95
+ *
96
+ * @param code - The OAuth authorization code received from the auth provider
97
+ * @returns A promise that resolves to an object containing the session ID
98
+ */
99
+ declare function getToken(code: string): Promise<{
100
+ sid: string;
101
+ }>;
102
+
103
+ export { type CompleteDesktopLoginOptions, DESKTOP_LOGIN_CALLBACK, DESKTOP_LOGIN_STATE_PARAM, type DesktopLoginCallback, type DesktopSessionStore, type FlexkitElectronHandlerOptions, type SessionChange, completeDesktopLogin, createFlexkitElectronHandler, getToken, isDesktopLoginStart, parseDesktopLoginCallback, parseSessionCookie, withDesktopLoginState };
@@ -0,0 +1,131 @@
1
+ import {
2
+ flexkitDomain,
3
+ getToken,
4
+ handleFlexkitRequest
5
+ } from "./chunk-DJPVRAFE.js";
6
+
7
+ // src/auth/electron-api.ts
8
+ var DESKTOP_LOGIN_CALLBACK = "flexkit://auth/callback";
9
+ function parseSessionCookie(setCookie) {
10
+ const [pair = "", ...attributes] = setCookie.split(";");
11
+ const separator = pair.indexOf("=");
12
+ if (separator === -1 || pair.slice(0, separator).trim() !== "sessionToken") {
13
+ return null;
14
+ }
15
+ const token = pair.slice(separator + 1).trim();
16
+ const expired = attributes.some((attribute) => attribute.trim().toLowerCase() === "max-age=0");
17
+ if (!token || expired) {
18
+ return { type: "clear" };
19
+ }
20
+ return { type: "set", token };
21
+ }
22
+ var DESKTOP_LOGIN_STATE_PARAM = "state";
23
+ var LOGIN_STATE_PATTERN = /^[A-Za-z0-9_-]{16,128}$/;
24
+ function isDesktopLoginStart(url) {
25
+ let parsed;
26
+ try {
27
+ parsed = new URL(url);
28
+ } catch {
29
+ return false;
30
+ }
31
+ return parsed.protocol === "https:" && (parsed.host === flexkitDomain || parsed.host === `www.${flexkitDomain}`) && parsed.pathname.startsWith("/auth/login/") && parsed.searchParams.get("origin") === DESKTOP_LOGIN_CALLBACK;
32
+ }
33
+ function withDesktopLoginState(url, state) {
34
+ const parsed = new URL(url);
35
+ parsed.searchParams.set(DESKTOP_LOGIN_STATE_PARAM, state);
36
+ return parsed.toString();
37
+ }
38
+ function parseDesktopLoginCallback(url) {
39
+ let parsed;
40
+ try {
41
+ parsed = new URL(url);
42
+ } catch {
43
+ return null;
44
+ }
45
+ if (`${parsed.protocol}//${parsed.host}${parsed.pathname}` !== DESKTOP_LOGIN_CALLBACK || parsed.username || parsed.password) {
46
+ return null;
47
+ }
48
+ const code = parsed.searchParams.get("code") ?? "";
49
+ if (!/^[0-9a-f-]{36}$/i.test(code)) {
50
+ return null;
51
+ }
52
+ const redirect = parsed.searchParams.get("redirect") ?? "/";
53
+ const state = parsed.searchParams.get(DESKTOP_LOGIN_STATE_PARAM);
54
+ return {
55
+ code,
56
+ redirect: redirect.startsWith("/") && !redirect.startsWith("//") ? redirect : "/",
57
+ state: state !== null && LOGIN_STATE_PATTERN.test(state) ? state : null
58
+ };
59
+ }
60
+ async function completeDesktopLogin(url, sessionStore, { expectedState }) {
61
+ const callback = parseDesktopLoginCallback(url);
62
+ if (!callback || expectedState === null || callback.state !== expectedState) {
63
+ return null;
64
+ }
65
+ const { sid } = await getToken(callback.code);
66
+ if (!sid) {
67
+ return null;
68
+ }
69
+ await sessionStore.set(sid);
70
+ return callback.redirect;
71
+ }
72
+ function resultToResponse(result) {
73
+ const headers = new Headers(result.headers);
74
+ if (result.type === "redirect") {
75
+ return new Response(null, { status: 204 });
76
+ }
77
+ if (result.type === "text") {
78
+ return new Response(result.body, { status: result.status, headers });
79
+ }
80
+ if (result.type === "response") {
81
+ return new Response(result.body, { status: result.status, headers });
82
+ }
83
+ headers.set("Content-Type", "application/json");
84
+ return new Response(JSON.stringify(result.body), { status: result.status, headers });
85
+ }
86
+ function createFlexkitElectronHandler({
87
+ sessionStore
88
+ }) {
89
+ return async function handleElectronRequest(request) {
90
+ const { pathname, search } = new URL(request.url);
91
+ if (!pathname.startsWith("/api/flexkit/") || pathname === "/api/flexkit/get-token") {
92
+ return new Response("Not found", { status: 404 });
93
+ }
94
+ let result;
95
+ try {
96
+ result = await handleFlexkitRequest({
97
+ request,
98
+ pathname,
99
+ search,
100
+ sessionToken: await sessionStore.get(),
101
+ contentType: request.headers.get("content-type")
102
+ });
103
+ } catch (error) {
104
+ return Response.json(
105
+ { error: `Flexkit is not reachable: ${error instanceof Error ? error.message : String(error)}` },
106
+ { status: 502 }
107
+ );
108
+ }
109
+ if (result.setCookie) {
110
+ const change = parseSessionCookie(result.setCookie);
111
+ if (change?.type === "set") {
112
+ await sessionStore.set(change.token);
113
+ } else if (change?.type === "clear") {
114
+ await sessionStore.clear();
115
+ }
116
+ }
117
+ return resultToResponse(result);
118
+ };
119
+ }
120
+ export {
121
+ DESKTOP_LOGIN_CALLBACK,
122
+ DESKTOP_LOGIN_STATE_PARAM,
123
+ completeDesktopLogin,
124
+ createFlexkitElectronHandler,
125
+ getToken,
126
+ handleFlexkitRequest,
127
+ isDesktopLoginStart,
128
+ parseDesktopLoginCallback,
129
+ parseSessionCookie,
130
+ withDesktopLoginState
131
+ };
package/dist/index.css CHANGED
@@ -8441,6 +8441,15 @@ pre {
8441
8441
  border-radius: var(--border-radius-4);
8442
8442
  padding: var(--px-4);
8443
8443
  }
8444
+ html[data-desktop-platform] .flexkit-navbar {
8445
+ -webkit-app-region: drag;
8446
+ }
8447
+ html[data-desktop-platform] .flexkit-navbar :is(a, button, input, textarea, select, [role='button'], [role='combobox']) {
8448
+ -webkit-app-region: no-drag;
8449
+ }
8450
+ html[data-desktop-platform='darwin'] .flexkit-navbar {
8451
+ padding-left: 6.5rem;
8452
+ }
8444
8453
  @keyframes enter {
8445
8454
  from {
8446
8455
  opacity: var(--tw-enter-opacity, 1);
package/dist/index.d.ts CHANGED
@@ -548,6 +548,36 @@ type AuthService = [boolean, Auth];
548
548
 
549
549
  declare function useAuth(): AuthService;
550
550
 
551
+ /**
552
+ * The bridge the Flexkit Studio desktop app (Electron) exposes to the Studio
553
+ * through its preload script, as `window.flexkitDesktop`. Studio features
554
+ * check for the bridge (a capability), never for a platform, so the web
555
+ * Studio never sees any of this.
556
+ */
557
+ interface FlexkitDesktopBridge {
558
+ platform: 'darwin' | 'win32' | 'linux';
559
+ /** Installed app version; it tracks the `@flexkit/studio` version it bundles. */
560
+ version: string;
561
+ /** The URL the OAuth login hands the session code to; the app registered its scheme with the OS. */
562
+ loginCallback: string;
563
+ /** Dock (macOS) or taskbar badge; 0 clears it. */
564
+ setBadgeCount: (count: number) => void;
565
+ /** Brings the Studio window to the front (a clicked notification, for example). */
566
+ focusWindow: () => void;
567
+ /** Paths the app asks the Studio to open: deep links and menu items. Returns the unsubscribe function. */
568
+ onNavigate: (listener: (path: string) => void) => () => void;
569
+ /** An update has been downloaded and installs on the next restart. Returns the unsubscribe function. */
570
+ onUpdateDownloaded: (listener: (update: {
571
+ version: string;
572
+ }) => void) => () => void;
573
+ /** Restarts into the downloaded update. */
574
+ installUpdate: () => void;
575
+ }
576
+ /** The desktop app's login callback (see `DESKTOP_LOGIN_CALLBACK` in the Electron adapter). */
577
+ declare const DESKTOP_LOGIN_CALLBACK = "flexkit://auth/callback";
578
+ /** The desktop bridge when the Studio runs inside the desktop app, otherwise undefined. */
579
+ declare function getDesktop(): FlexkitDesktopBridge | undefined;
580
+
551
581
  type Props$1 = {
552
582
  scope: string | undefined;
553
583
  checked: boolean;
@@ -834,4 +864,4 @@ declare const assetSchema: UiEntity;
834
864
 
835
865
  declare const tagSchema: UiEntity;
836
866
 
837
- export { ALL_FIELDS_GROUP, ALL_FIELDS_GROUP_NAME, type AgentChatDetail, type AgentChatSearchResult, type AgentChatsList, type AttributeValue, type Automation, type AutomationApprovals, type AutomationArtifact, type AutomationCreditBalance, type AutomationInput, type AutomationToolChannel, type AutomationToolProvider, type ConfigContext, DataTable, DataTableColumnHeader, DataTableFacetedFilter, DataTableRowActions, DataTableSortedBy, DataTableToolbar, DataTableViewOptions, DefaultValueSwitch, type EntityData, type EntityListRowPatch, FlexkitStudio, type FlexkitStudioProps, type FormEntityItem, type FormFieldParams, type FormFieldValue, type HostedUser, IMAGES_BASE_URL, type MappedEntityItem, ProjectDisabled, type ProjectSchemaError, type ProjectSummary, type ProjectUiConfig, type RunHistory, type RunHistoryRun, SchemaError, type SearchRequestProps, type SearchResultItem, type Skill, type SkillInput, type SkillsList, type StudioMode, type UploadedFileResult, assetSchema, canAccessSpaces, canMutateData, filterAttributesForSpaces, filterEntitiesForSpaces, getEntityCreateMutation, getEntityDeleteMutation, getEntityDeleteWhere, getEntityQuery, getEntitySchema, getEntityUpdateMutation, getSortNotNullWhere, tagSchema, useAppContext, useAppDispatch, useAuth, useCanMutate, useConfig, useDispatch, useEntityMutation, useEntityQuery, useGraphQLError, useGridColumnsDefinition, usePatchEntityList, useRefreshEntityList, useSearch, useUploadAssets };
867
+ export { ALL_FIELDS_GROUP, ALL_FIELDS_GROUP_NAME, type AgentChatDetail, type AgentChatSearchResult, type AgentChatsList, type AttributeValue, type Automation, type AutomationApprovals, type AutomationArtifact, type AutomationCreditBalance, type AutomationInput, type AutomationToolChannel, type AutomationToolProvider, type ConfigContext, DESKTOP_LOGIN_CALLBACK, DataTable, DataTableColumnHeader, DataTableFacetedFilter, DataTableRowActions, DataTableSortedBy, DataTableToolbar, DataTableViewOptions, DefaultValueSwitch, type EntityData, type EntityListRowPatch, type FlexkitDesktopBridge, FlexkitStudio, type FlexkitStudioProps, type FormEntityItem, type FormFieldParams, type FormFieldValue, type HostedUser, IMAGES_BASE_URL, type MappedEntityItem, ProjectDisabled, type ProjectSchemaError, type ProjectSummary, type ProjectUiConfig, type RunHistory, type RunHistoryRun, SchemaError, type SearchRequestProps, type SearchResultItem, type Skill, type SkillInput, type SkillsList, type StudioMode, type UploadedFileResult, assetSchema, canAccessSpaces, canMutateData, filterAttributesForSpaces, filterEntitiesForSpaces, getDesktop, getEntityCreateMutation, getEntityDeleteMutation, getEntityDeleteWhere, getEntityQuery, getEntitySchema, getEntityUpdateMutation, getSortNotNullWhere, tagSchema, useAppContext, useAppDispatch, useAuth, useCanMutate, useConfig, useDispatch, useEntityMutation, useEntityQuery, useGraphQLError, useGridColumnsDefinition, usePatchEntityList, useRefreshEntityList, useSearch, useUploadAssets };