PageSourceSearch

https://app.xlift.ai/assets/ReplyGuyPage-wAGP4L1v.js

js xlift.ai collected 2026-10-05 13:20:50 UTC 17,100 bytes, 1 lines download raw bytes

1import{ai as E,o as z,A as S,j as e,aM as N,C as v,G as p,aA as T,$ as Q,T as h,X as R,a0 as P,aa as L,U as w,a5 as F,a4 as M,am as q,al as D,E as y,D as I,K as G,ao as K,I as W,a7 as X,W as A,F as $,Z as V,L as Z}from"./index-dE8a1mPK.js";import{E as B}from"./ErrorState-gVNyx0--.js";import{E as _}from"./EmptyState-BpgfYYCq.js";import{I as H}from"./IconPlus-B47PY1MC.js";import{I as J}from"./IconArrowLeft-cAVPTBNq.js";import{S as Y}from"./Switch-Bm85N10X.js";import{T as O}from"./Textarea-BtbUvWWM.js";import{N as C}from"./NumberInput-EbCp_odp.js";import{S as U}from"./Select-DebRlW67.js";import{I as ee}from"./IconArrowRight-Ajq43paw.js";import{I as ae}from"./IconTrash-DIXYcrq7.js";import{I as se}from"./IconX-B50qDTAe.js";import"./IconCopy-B63sUmw8.js";import"./Code-YtMEZkZ5.js";import"./InputsGroupFieldset-C0syow2C.js";import"./get-env-uyVen0u2.js";import"./OptionsDropdown-wonErrz5.js";import"./CheckIcon-DkMc45y2.js";function Ce(){var b,d;const t=E(),[u,x]=z.useState(null),[j,o]=z.useState(!1),n=S({queryKey:["reply-guy-configs"],queryFn:()=>w.listReplyGuyConfigs()}),a=S({queryKey:["accounts"],queryFn:()=>w.listAccounts()}),r=((b=n.data)==null?void 0:b.configs)||[],f=((d=a.data)==null?void 0:d.accounts)||[],g=z.useMemo(()=>Object.fromEntries(f.map(c=>[c.id,c])),[f]);return n.isLoading?e.jsx(N,{}):n.error?e.jsx(B,{error:n.error,onRetry:()=>n.refetch()}):u?e.jsx(ne,{accountId:u,account:g[u],onBack:()=>x(null),onSaved:()=>{t.invalidateQueries({queryKey:["reply-guy-configs"]})}}):e.jsxs(v,{gap:"md",children:[e.jsxs(p,{justify:"space-between",align:"flex-start",children:[e.jsxs(v,{gap:2,children:[e.jsxs(p,{gap:"xs",children:[e.jsx(T,{size:22}),e.jsx(Q,{order:2,children:"Reply Guy"})]}),e.jsx(h,{c:"dimmed",size:"sm",maw:720,children:"One config per replying account. Each watches up to 20 influencers — when any of them tweet, AI drafts a high-quality reply within the next poll cycle, you approve, it sends. Agencies: configure each client account separately with its own voice and cap."})]}),e.jsx(R,{leftSection:e.jsx(H,{size:14}),onClick:()=>o(!0),children:"New Reply Guy config"})]}),e.jsx(P,{color:"yellow",icon:e.jsx(L,{size:16}),children:e.jsxs(h,{size:"sm",children:[e.jsx("b",{children:"Risk profile: moderate."})," Reply Guy at scale draws X's attention. Defaults are conservative — max 5 drafts/day per account, approval always required."]})}),r.length===0&&e.jsx(_,{icon:e.jsx(T,{size:32,stroke:1.5}),title:"No Reply Guy configs yet",description:"Pick an account, add some influencer handles, and we'll draft replies the moment they tweet.",cta:{label:"New Reply Guy config",icon:e.jsx(H,{size:14}),onClick:()=>o(!0)}}),r.map(c=>e.jsx(te,{config:c,account:g[c.accountId],onOpen:()=>x(c.accountId),onRefresh:async()=>{try{const s=((await w.refreshReplyGuy(c.accountId)).drafted||[]).filter(i=>i.actionId).length;q.show({color:s?"green":"yellow",message:s?`Drafted ${s} reply(ies). Open Approvals to review.`:"No new drafts."}),t.invalidateQueries({queryKey:["reply-guy-configs"]})}catch(m){M(m)}},onRemove:async()=>{var m;if(confirm(`Remove Reply Guy config for @${((m=g[c.accountId])==null?void 0:m.handle)||c.accountId}?`))try{await w.removeReplyGuyConfig(c.accountId),F("Config removed."),t.invalidateQueries({queryKey:["reply-guy-configs"]})}catch(s){M(s)}}},c.accountId)),e.jsx(re,{opened:j,onClose:()=>o(!1),accounts:f.filter(c=>c.executionMode==="cookie"&&c.status==="active"&&!r.find(m=>m.accountId===c.id)),onPicked:c=>{o(!1),x(c)}})]})}function te({config:t,account:u,onOpen:x,onRefresh:j,onRemove:o}){var r,f;const a=(r=S({queryKey:["reply-guy-stats",t.accountId],queryFn:()=>w.replyGuyStats(t.accountId,30),staleTime:6e4}).data)==null?void 0:r.stats;return e.jsx(I,{withBorder:!0,p:"md",style:{cursor:"pointer"},onClick:x,children:e.jsxs(p,{justify:"space-between",align:"flex-start",wrap:"wrap",gap:"sm",children:[e.jsxs(v,{gap:4,style:{flex:1,minWidth:0},children:[e.jsxs(p,{gap:"xs",wrap:"wrap",children:[e.jsxs(h,{fw:700,children:["@",(u==null?void 0:u.handle)||t.accountId]}),e.jsx(y,{size:"sm",color:t.enabled?"green":"gray",variant:"light",children:t.enabled?"live":"paused"}),e.jsx(y,{size:"sm",variant:"outline",color:"gray",children:t.approvalMode==="auto"?"auto-send":"approval required"})]}),e.jsxs(p,{gap:"xs",wrap:"wrap",children:[e.jsxs(y,{variant:"light",children:[((f=t.targetHandles)==null?void 0:f.length)||0," / 20 watched"]}),e.jsxs(y,{variant:"light",color:"gray",children:["cap ",t.maxPerDay||5,"/day"]}),e.jsxs(y,{variant:"light",color:"gray",children:["poll every ",t.refreshMinutes||5,"m"]}),a&&a.drafted>0&&e.jsx(A,{label:a.approvalRate!=null?`${a.sent} sent · ${a.rejected} rejected · ${a.stale} stale-expired · ${a.awaiting} pending (last ${a.days}d)`:`${a.drafted} drafted, none decided yet (last ${a.days}d)`,withArrow:!0,multiline:!0,w:260,children:e.jsx(y,{variant:"light",color:a.approvalRate>=60?"green":a.approvalRate>=30?"yellow":a.approvalRate==null?"gray":"red",children:a.approvalRate!=null?`${a.approvalRate}% approved`:`${a.drafted} drafted`})}),(a==null?void 0:a.impactSamples)>0&&e.jsx(A,{label:`Across ${a.impactSamples} sent replies measured 24h after dispatch — avg ${a.avgLikes} likes, ${a.avgRetweets} retweets, ${a.avgReplies} replies.`,withArrow:!0,multiline:!0,w:260,children:e.jsxs(y,{variant:"light",color:"blue",children:["avg ",a.avgLikes??0,"♡ / reply"]})}),e.jsx(h,{size:"xs",c:"dimmed",children:t.lastRunAt?`last ${new Date(t.lastRunAt).toLocaleTimeString()}`:"awaiting first poll"})]}),Array.isArray(t.lastRunSummary)&&t.lastRunSummary.length>0&&e.jsxs(p,{gap:6,wrap:"wrap",mt:4,children:[t.lastRunSummary.slice(0,6).map((g,b)=>e.jsxs(y,{size:"xs",variant:"dot",color:g.actionId?"green":g.error?"red":"gray",children:["@",g.handle,g.actionId?" · drafted":g.skipped?` · ${g.skipped}`:""]},b)),t.lastRunSummary.length>6&&e.jsxs(h,{size:"xs",c:"dimmed",children:["+ ",t.lastRunSummary.length-6," more"]})]})]}),e.jsxs(p,{gap:"xs",wrap:"nowrap",onClick:g=>g.stopPropagation(),children:[e.jsx(A,{label:"Poll now",children:e.jsx($,{size:"lg",variant:"default",onClick:j,children:e.jsx(K,{size:14})})}),e.jsx(A,{label:"Remove this config",children:e.jsx($,{size:"lg",variant:"subtle",color:"red",onClick:o,children:e.jsx(ae,{size:14})})})]})]})})}function re({opened:t,onClose:u,accounts:x,onPicked:j}){const[o,n]=z.useState(null);return z.useEffect(()=>{t||n(null)},[t]),e.jsx(V,{opened:t,onClose:u,title:"New Reply Guy config",size:"md",children:e.jsxs(v,{gap:"sm",children:[e.jsx(h,{size:"sm",c:"dimmed",children:"Pick the replying account. Eac
1h account gets its own config — own watched handles, own voice, own daily cap."}),e.jsx(U,{label:"Replying account",placeholder:x.length?"Pick an account":"No eligible accounts (all already configured?)",data:x.map(a=>({value:a.id,label:a.handle?`@${a.handle}`:a.id})),value:o,onChange:n,searchable:!0,allowDeselect:!1}),e.jsxs(p,{justify:"flex-end",mt:"sm",children:[e.jsx(R,{variant:"default",onClick:u,children:"Cancel"}),e.jsx(R,{onClick:()=>o&&j(o),disabled:!o,children:"Continue"})]})]})})}function ne({accountId:t,account:u,onBack:x,onSaved:j}){var m;const o=E(),n=S({queryKey:["reply-guy-config",t],queryFn:()=>w.getReplyGuyConfig(t)}),[a,r]=z.useState(null);z.useEffect(()=>{var s,i;(s=n.data)!=null&&s.config?r(n.data.config):n.isFetched&&!((i=n.data)!=null&&i.config)&&r({enabled:!1,accountId:t,targetHandles:[],voiceHints:"",maxPerDay:5,approvalMode:"always",refreshMinutes:5,perTargetCooldownHours:24,qualityMin:50,staleHours:2,maxReplyCrowd:200,autoApproveAbove:0})},[n.data,n.isFetched,t]);const f=D({mutationFn:s=>w.saveReplyGuyConfig(t,s),onSuccess:s=>{F("Saved"),o.setQueryData(["reply-guy-config",t],s),o.invalidateQueries({queryKey:["reply-guy-configs"]}),r(s.config),j==null||j()},onError:s=>M(s)}),g=D({mutationFn:()=>w.refreshReplyGuy(t),onSuccess:s=>{const i=(s.drafted||[]).filter(k=>k.actionId).length,l=(s.drafted||[]).filter(k=>k.skipped).length;q.show({color:i?"green":"yellow",message:i?`Drafted ${i} reply(ies) — head to Approvals to review.`:`No new drafts (${l} handles checked, all skipped).`}),o.invalidateQueries({queryKey:["reply-guy-config",t]}),o.invalidateQueries({queryKey:["reply-guy-configs"]})},onError:s=>M(s)});if(n.isLoading||!a)return e.jsx(N,{});if(n.error)return e.jsx(B,{error:n.error,onRetry:()=>n.refetch()});const b=s=>{const i=String(s||"").trim().replace(/^@/,"").toLowerCase();/^[a-z0-9_]{1,15}$/.test(i)&&((a.targetHandles||[]).includes(i)||r({...a,targetHandles:[...a.targetHandles||[],i].slice(0,20)}))},d=s=>{r({...a,targetHandles:(a.targetHandles||[]).filter(i=>i!==s)})},c=((m=a.targetHandles)==null?void 0:m.length)>0;return e.jsxs(v,{gap:"md",children:[e.jsxs(p,{justify:"space-between",wrap:"wrap",children:[e.jsxs(p,{gap:"xs",children:[e.jsx(R,{variant:"subtle",leftSection:e.jsx(J,{size:14}),onClick:x,children:"All configs"}),e.jsxs(Q,{order:3,children:["Reply Guy · @",(u==null?void 0:u.handle)||t]})]}),a.lastRunAt&&e.jsxs(y,{variant:"light",color:"gray",children:["Last poll ",new Date(a.lastRunAt).toLocaleTimeString()]})]}),e.jsx(I,{withBorder:!0,children:e.jsxs(v,{gap:"sm",children:[e.jsx(Y,{label:"Enabled",description:"When ON, the worker polls every refresh interval. OFF freezes this config in place — queue + history are kept.",checked:!!a.enabled,onChange:s=>r({...a,enabled:s.currentTarget.checked})}),e.jsx(G,{}),e.jsx(le,{handles:a.targetHandles||[],onAdd:b,onRemove:d,snoozedUntil:a.snoozedUntil||{},perHandleVoice:a.perHandleVoice||{},onSnooze:async(s,i)=>{try{const l=await w.snoozeReplyGuyTarget(t,s,i);r({...a,snoozedUntil:l.snoozedUntil||{}}),q.show({color:i>0?"green":"gray",message:i>0?`Snoozed @${s} for ${i}d`:`Unsnoozed @${s}`})}catch(l){q.show({color:"red",message:l.message})}},onSetPerHandleVoice:(s,i)=>{const l={...a.perHandleVoice||{}};i&&i.trim()?l[s]=i:delete l[s],r({...a,perHandleVoice:l})}}),e.jsx(O,{label:"Voice hints",description:"How should replies feel from this account? Plain English. Specific is better — name the tone, name what to avoid.",placeholder:"Dry, no exclamation marks, one short observation per reply. Never start with 'great post' or 'love this'. Reference a specific phrase from the tweet.",value:a.voiceHints||"",onChange:s=>r({...a,voiceHints:s.currentTarget.value}),minRows:3,autosize:!0,maxLength:1e3}),e.jsxs(p,{grow:!0,children:[e.jsx(C,{label:"Max drafts per day",description:"Hard cap on this account.",value:a.maxPerDay||5,onChange:s=>r({...a,maxPerDay:Math.max(1,Math.min(50,Number(s)||5))}),min:1,max:50}),e.jsx(C,{label:"Poll interval (min)",description:"How often to check each watched handle.",value:a.refreshMinutes||5,onChange:s=>r({...a,refreshMinutes:Math.max(2,Math.min(60,Number(s)||5))}),min:2,max:60}),e.jsx(U,{label:"Approval mode",description:"Auto sends without review — flip only when you've trained the voice.",data:[{value:"always",label:"Always require approval (recommended)"},{value:"auto",label:"Auto-send (no review)"}],value:a.approvalMode||"always",onChange:s=>r({...a,approvalMode:s||"always"}),allowDeselect:!1})]}),e.jsx(G,{label:"Safety controls",labelPosition:"left"}),e.jsxs(p,{grow:!0,children:[e.jsx(C,{label:"Per-target cooldown (h)",description:"Min hours between drafts to the same handle. 24 = max 1 reply per target per day.",value:a.perTargetCooldownHours||24,onChange:s=>r({...a,perTargetCooldownHours:Math.max(1,Math.min(168,Number(s)||24))}),min:1,max:168}),e.jsx(C,{label:"Quality min score",description:"0-100. Gemini scores tweets; below this we skip without drafting. 0 disables the filter.",value:a.qualityMin??50,onChange:s=>r({...a,qualityMin:Math.max(0,Math.min(100,Number(s)||0))}),min:0,max:100}),e.jsx(C,{label:"Stale draft expiry (h)",description:"Drafts awaiting approval past this are auto-skipped. Default 2h.",value:a.staleHours||2,onCh
1ange:s=>r({...a,staleHours:Math.max(1,Math.min(48,Number(s)||2))}),min:1,max:48})]}),e.jsxs(p,{grow:!0,children:[e.jsx(C,{label:"Anti-crowding cap",description:"Skip tweets with more replies than this — your reply would be buried. 0 disables. Default 200.",value:a.maxReplyCrowd??200,onChange:s=>r({...a,maxReplyCrowd:Math.max(0,Math.min(5e3,Number(s)||0))}),min:0,max:5e3,step:50}),e.jsx(C,{label:"Auto-approve quality threshold",description:"0-100. When set, drafts scoring at/above this auto-send (bypass approval) even when 'always' mode is on. 0 disables.",value:a.autoApproveAbove??0,onChange:s=>r({...a,autoApproveAbove:Math.max(0,Math.min(100,Number(s)||0))}),min:0,max:100})]}),e.jsx(h,{size:"xs",c:"dimmed",children:"Drafts also dispatch with random 60–180s jitter so the cadence doesn't look bot-like."})]})}),e.jsxs(p,{justify:"space-between",wrap:"wrap",gap:"xs",children:[e.jsxs(p,{gap:"xs",children:[e.jsx(R,{variant:"default",leftSection:e.jsx(K,{size:14}),onClick:()=>g.mutate(),loading:g.isPending,disabled:!a.enabled||!c,children:"Poll now"}),e.jsxs(W,{component:X,to:"/approvals",size:"sm",children:["Approvals queue ",e.jsx(ee,{size:11,style:{verticalAlign:-1}})]})]}),e.jsx(R,{onClick:()=>f.mutate(a),loading:f.isPending,disabled:!c,children:"Save"})]}),(()=>{const s=a.lastRunSummary||[];return s.length>0&&s.every(l=>l.skipped==="bootstrap")?e.jsx(P,{color:"gray",variant:"light",icon:e.jsx(T,{size:16}),children:e.jsxs(h,{size:"sm",children:[`First poll done — we've recorded the latest tweet from each watched handle as the "baseline." Drafts start the moment any of them post `,e.jsx("b",{children:"next"}),". Reply Guy never replies to existing/older tweets on enable."]})}):s.length>0?e.jsxs(I,{withBorder:!0,children:[e.jsx(h,{fw:600,size:"sm",mb:"xs",children:"Last poll summary"}),e.jsx(v,{gap:4,children:s.map((l,k)=>e.jsxs(p,{gap:"xs",wrap:"wrap",children:[e.jsxs(h,{size:"xs",fw:500,children:["@",l.handle]}),l.actionId&&e.jsx(y,{size:"xs",color:l.status==="queued"?"green":"blue",variant:"light",children:l.status==="queued"?"auto-queued":"drafted for approval"}),typeof l.qualityScore=="number"&&e.jsxs(y,{size:"xs",variant:"outline",color:l.qualityScore>=70?"green":"gray",children:["Q ",l.qualityScore]}),l.skipped&&e.jsx(y,{size:"xs",color:"gray",variant:"light",children:l.skipped}),l.error&&e.jsx(h,{size:"xs",c:"red",children:l.error})]},k))})]}):null})(),e.jsx(ie,{accountId:t,watched:a.targetHandles||[],onAdd:s=>b(s)})]})}function ie({accountId:t,watched:u,onAdd:x}){var n;const o=(((n=S({queryKey:["reply-guy-suggested",t,u.length],queryFn:()=>w.replyGuySuggestedTargets(t,10),staleTime:3e5}).data)==null?void 0:n.suggested)||[]).filter(a=>!u.includes(a.handle));return o.length?e.jsxs(I,{withBorder:!0,children:[e.jsxs(p,{justify:"space-between",mb:"xs",children:[e.jsx(h,{fw:600,size:"sm",children:"Suggested handles to watch"}),e.jsxs(h,{size:"xs",c:"dimmed",children:["based on who's already engaging with @",t]})]}),e.jsx(p,{gap:"xs",wrap:"wrap",children:o.map(a=>e.jsxs(y,{variant:"outline",color:"gray",size:"lg",rightSection:e.jsx(H,{size:12,style:{cursor:"pointer"},onClick:()=>x(a.handle),title:`Add @${a.handle}`}),style:{textTransform:"none"},children:["@",a.handle,e.jsxs(h,{span:!0,size:"xs",c:"dimmed",ml:4,children:["· ",a.engagements," engagements"]})]},a.handle))})]}):null}function le({handles:t,onAdd:u,onRemove:x,snoozedUntil:j={},perHandleVoice:o={},onSnooze:n,onSetPerHandleVoice:a}){const[r,f]=z.useState(""),[g,b]=z.useState(null);return e.jsxs(v,{gap:"xs",children:[e.jsxs(h,{size:"sm",fw:500,children:["Watched handles (",t.length,"/20)"]}),e.jsxs(v,{gap:4,children:[t.length===0&&e.jsx(h,{size:"xs",c:"dimmed",children:"No handles yet — add some below."}),t.map(d=>{const c=j[d]||0,m=c>Date.now(),s=m?Math.ceil((c-Date.now())/(24*3600*1e3)):0,i=g===d,l=!!(o[d]&&o[d].trim());return e.jsxs(v,{gap:4,children:[e.jsxs(p,{gap:"xs",wrap:"wrap",children:[e.jsxs(y,{variant:m?"light":"filled",color:m?"gray":"dark",size:"lg",style:{textTransform:"none"},children:["@",d,m&&e.jsxs(h,{span:!0,size:"xs",ml:4,children:["· snoozed ",s,"d"]}),l&&e.jsx(h,{span:!0,size:"xs",ml:4,children:"· custom voice"})]}),m?e.jsx(R,{size:"xs",variant:"subtle",onClick:()=>n==null?void 0:n(d,0),children:"Unsnooze"}):e.jsx(A,{label:"Snooze 7d",children:e.jsx($,{size:"sm",variant:"subtle",onClick:()=>n==null?void 0:n(d,7),"aria-label":"Snooze",children:e.jsx(L,{size:12})})}),e.jsx(R,{size:"xs",variant:"subtle",onClick:()=>b(i?null:d),children:l?"Edit voice":"Set per-target voice"}),e.jsx($,{size:"sm",variant:"subtle",color:"red",onClick:()=>x(d),"aria-label":`Remove @${d}`,children:e.jsx(se,{size:12})})]}),i&&e.jsx(O,{size:"xs",placeholder:`Voice hints just for @${d} (leave empty to use the config-wide voice)`,value:o[d]||"",onChange:k=>a==null?void 0:a(d,k.currentTarget.value),minRows:2,autosize:!0,maxLength:1e3})]},d)})]}),e.jsxs(p,{gap:"xs",align:"flex-end",children:[e.jsx(Z,{placeholder:"@handle",value:r,onChange:d=>f(d.currentTarget.value),onKeyDown:d=>{d.key==="Enter"&&r.trim()&&(u(r),f(""))},style:{flex:1,minWidth:220},disabled:t.length>=20}),e.jsx(R,{variant:"default",leftSection:e.jsx(H,{size:14}),onClick:()=>{r.trim()&&(u(r),f(""))},disabled:t.length>=20||!r.trim(),children:"Add"})]})]})}export{Ce as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.