PageSourceSearch

https://testivo.tech/assets/dashboard.index-Dh5U62ia.js

js testivo.tech collected 2026-09-24 15:12:56 UTC 36,315 bytes, 2 lines download raw bytes

1import{r as f,j as e}from"./react-RN8z7mqm.js";import{g as K,u as ie,L as ne}from"./index-BwX0UczS.js";import{s as l}from"./supabase-D7bm-uKx.js";import{toast as _}from"./sonner-CSA395gJ.js";import{a3 as L,C as P,a4 as re,a5 as oe,z as H,_ as le,a6 as de,a7 as me,y as z,a8 as ce,I as ue,a9 as pe,aa as he,ab as ge,W as fe,ac as ye,k as we,X as xe}from"./icons-CFy5kY0-.js";const W=["grid","masonry","list","carousel","marquee"],V=["light","dark"],J=["newest","oldest","rating","random","manual"],M={query:{type:"string",description:"free-text match on the testimonial message"},min_rating:{type:"integer",minimum:1,maximum:5,description:"only ratings >= this"},tags:{type:"array",items:{type:"string"},description:"must have one of these tags"},has_video:{type:"boolean",description:"only video testimonials"}},be=[{name:"navigate",title:"Go to…",description:"Use when the user asks to open/go to/see a part of the app (inbox, walls, widgets, collection form, referrals, brand, dashboard) or a specific wall by name. Only navigates — pair with other tools when they also want something done there.",category:"navigate",mutates:!1,input_schema:{type:"object",properties:{target:{type:"string",description:"e.g. 'inbox', 'walls', a wall name"}},required:["target"]},examples:["go to my inbox","open the pricing wall"]},{name:"find_testimonials",title:"Find testimonials",description:"Use when the user wants to find/show/count APPROVED testimonials, or when a later step needs testimonial ids you don't have yet (run this first, then act on the results). Filters by text, rating, tags, or video. For unreviewed submissions use list_pending instead.",category:"find",mutates:!1,input_schema:{type:"object",properties:{...M}},examples:["show 5-star reviews that mention onboarding","find my video testimonials"]},{name:"list_pending",title:"What needs review",description:"Use when the user asks what's waiting, what needs approval/review/moderation, or wants to triage new submissions. Lists PENDING (not yet approved) testimonials, newest first — the natural first step before approve_testimonials.",category:"find",mutates:!1,input_schema:{type:"object",properties:{limit:{type:"integer",minimum:1,maximum:50,description:"max rows to return (default 20)"}}},examples:["what's waiting for my approval?","triage my inbox"]},{name:"summarize_sentiment",title:"Summarize sentiment",description:"Use when the user asks what customers think/love/complain about, or for a summary/insight over their feedback. Runs an AI analysis over their real testimonials and returns a written summary. Read-only.",category:"analyze",mutates:!1,input_schema:{type:"object",properties:{range:{type:"string",description:"e.g. 'this month', 'last 30 days', 'all time'"}}},examples:["what do customers love this month","summarize recent feedback"]},{name:"classify_testimonials",title:"Auto-tag & score sentiment",description:"Use when the user asks to auto-tag, categorize, or score sentiment on testimonials (typically after new ones arrive). Runs AI classification (sentiment + topic tags) and WRITES the results onto the rows. Omit ids to process all unclassified.",category:"analyze",mutates:!0,input_schema:{type:"object",properties:{ids:{type:"array",items:{type:"string"},description:"specific ids, or omit for all unclassified"}}},examples:["auto-tag the new testimonials","classify sentiment on everything"]},{name:"draft_case_study",title:"Draft a case study",description:"Use when the user wants a case study, success story, or long-form write-up drafted from a testimonial. Drafts from the customer's REAL words (never invented). Pass a testimonial id if known; otherwise pass a filter and the best-rated match with text is used. Returns a draft for the user to review — publishes nothing.",category:"content",mutates:!1,input_schema:{type:"object",properties:{id:{type:"string",description:"testimonial id to draft from (preferred when known)"},filter:{type:"object",properties:{...M},description:"used to pick the best testimonial when no id is given"}}},examples:["draft a case study from my best review","turn Sarah's testimonial into a case study"]},{name:"send_review_requests",title:"Send review requests",description:`Use when the user wants to ask customers for testimonials/reviews by email ("reach out", "request reviews from …"). Emails each recipient a request with their collection-form link attached. Draft a short friendly message for them, but NEVER invent email addresses — if none were given, ask for them. Sends real email, so it always waits for the user's Apply.`,category:"outreach",mutates:!0,input_schema:{type:"object",properties:{recipients:{type:"array",items:{type:"string"},description:"email addresses supplied by the user"},subject:{type:"string",description:"email subject (a short default is used if omitted)"},message:{type:"string",description:"the request message body (the collection link is appended automatically)"}},required:["recipients"]},examples:["ask [email protected] for a testimonial","send review requests to these 3 customers"]},{name:"send_thank_you",title:"Send a thank-you",description:"Use when the user wants to thank a customer for their testimonial (often right after approving it). Emails a short thank-you note to that testimonial's author. Needs the testimonial id — get it from list_pending/find_testimonials first if unknown. Sends real email, so it always waits for the user's Apply.",category:"outreach",mutates:!0,input_schema:{type:"object",properties:{id:{type:"string",description:"testimonial id whose author to thank"}},required:["id"]},examples:["send Sarah a thank-you note","thank the customer who left that 5-star review"]},{name:"create_wall",title:"Build a wall",description:'Use when the user wants a new wall/page of testimonials built ("build/make/create a wall of …"). Builds a DRAFT wall from a filter + style and opens it for preview — nothing goes live until they publish. Not for changing an existing wall (use set_wall_style / add_to_wall).',category:"wall",mutates:!0,input_schema:{type:"object",properties:{name:{type:"string",description:"wall name"},layout:{type:"string",enum:W},theme:{type:"string",enum:V},sort_mode:{type:"string",enum:J},max_items:{type:"integer",minimum:1,maximum:100},filter:{type:"object",properties:{...M}}},required:["name"]},examples:["build a masonry wall of 5-star reviews that mention onboarding, dark theme","make a wall of my best video testimonials"]},{name:"add_to_wall",title:"Add to a wall",description:"Use when the user wants specific testimonials added to an EXISTING wall. Needs the wall (name or id) and testimonial ids — run find_testimonials first if you only have a description of them.",category:"wall",mutates:!0,input_schema:{type:"object",properties:{wall:{type:"string",description:"wall name or id"},testimonial_ids:{type:"array",items:{type:"string"}}},required:["wall","testimonial_ids"]},examples:["add these to my homepage wall"]},{name:"remove_from_wall",title:"Remove from a wall",description:"Use when the user wants testimonials taken OFF a specific wall (they stay approved and on other walls). To remove one everywhere, use hide_testimonial instead.",category:"wall",mutates:!0,input_schema:{type:"object",properties:{wall:{type:"string"},testimonial_ids:{type:"array",items:{type:"string"}}},required:["wall","testimonial_ids"]},examples:["remove that one from the pricing wall"]},{name:"set_wall_style",title:"Restyle a wall",description:"Use when the user wants an existing wall to LOOK or SORT differently: layout (grid/masonry/list/carousel/marquee), light/dark theme, sort order, or item cap. Only include the properties being changed.",category:"wall",mutates:!0,input_schema:{type:"object",properties:{wall:{type:"string"},layout:{type:"string",enum:W},theme:{type:"string",enum:V},sort_mode:{type:"string",enum:J},max_items:{type:"integer",minimum:1,maximum:100}},required:["wall"]},examples:["make my homepage wall a scrolling marquee","switch the pricing wall to dark theme"]},{name:"get_embed_code",title:"Get embed code",description:"Use when the user wants to put a wall on their site: asks for the embed code, snippet, iframe, or shareable/hosted URL of a wall. Returns a copy-paste snippet + the hosted link. Read-only.",category:"find",mutates:!1,input_schema:{type:"object",properties:{wall:{type:"string"}},required:["wall"]},examples:["give me the embed code for my pricing wall"]}
1,{name:"approve_testimonials",title:"Approve testimonials",description:'Use when the user says to approve/accept/publish pending testimonials. Needs explicit ids — run list_pending first when they say "all pending" or "the new ones", then approve those ids. Approved testimonials become usable on walls.',category:"testimonial",mutates:!0,input_schema:{type:"object",properties:{ids:{type:"array",items:{type:"string"}}},required:["ids"]},examples:["approve the last 5 pending","approve all pending testimonials"]},{name:"tag_testimonials",title:"Tag testimonials",description:`Use when the user wants specific tags put on specific testimonials ("tag these 'pricing'"). Needs both ids and tag names. For automatic topic tagging across many rows use classify_testimonials instead.`,category:"testimonial",mutates:!0,input_schema:{type:"object",properties:{ids:{type:"array",items:{type:"string"}},tags:{type:"array",items:{type:"string"}}},required:["ids","tags"]},examples:["tag these 'pricing'"]},{name:"pin_testimonial",title:"Pin / feature",description:"Use when the user wants one testimonial featured/pinned/starred so it floats to the top of walls (or unpinned with pinned=false). Needs the testimonial id.",category:"testimonial",mutates:!0,input_schema:{type:"object",properties:{id:{type:"string"},pinned:{type:"boolean"}},required:["id"]},examples:["feature this testimonial"]},{name:"hide_testimonial",title:"Hide from walls",description:"Use when the user wants a testimonial hidden/removed from ALL walls (spam, outdated, off-brand). Reversible soft-hide, not a delete. To remove from just one wall use remove_from_wall.",category:"testimonial",mutates:!0,input_schema:{type:"object",properties:{id:{type:"string"}},required:["id"]},examples:["hide that testimonial"]}],ve=t=>be.find(a=>a.name===t);async function O(t,a){const s=/^[0-9a-f-]{36}$/i.test(a),i=l.from("walls").select("id, name, slug").eq("user_id",t),{data:n}=s?await i.eq("id",a).maybeSingle():await i.ilike("name",a).maybeSingle();return n??null}const Y=[{route:"/dashboard/collection-page",keys:["collection form","collection page","collect proof","share form","collect","collection","form","forms"]},{route:"/dashboard/testimonials",keys:["inbox","testimonials","testimonial","proof"]},{route:"/dashboard/wall",keys:["walls","wall","wall of love"]},{route:"/dashboard/studio",keys:["widgets","widget","studio","embed"]},{route:"/dashboard/referrals",keys:["referrals","referral"]},{route:"/dashboard/brand",keys:["brand","brand kit"]},{route:"/dashboard",keys:["dashboard","home","workspace"]}];function _e(t){const a=t.toLowerCase().replace(/[^a-z0-9 ]+/g," ").replace(/\s+/g," ").trim();if(!a)return null;const s=a.replace(/^(go to|open|show|take me to|navigate to|the|my)\s+/g,"").trim();for(const n of[a,s])if(n){for(const{route:m,keys:p}of Y)if(p.includes(n))return m}const i=Y.flatMap(n=>n.keys.map(m=>({k:m,route:n.route}))).sort((n,m)=>m.k.length-n.k.length);for(const{k:n,route:m}of i)if(a.includes(n))return m;return null}async function D(t,a,s=200){let i=l.from("testimonials").select("id, message, rating, video_url, tags").eq("user_id",t).eq("status","approved").eq("consent_public",!0).is("deleted_at",null);a.min_rating&&(i=i.gte("rating",a.min_rating)),a.has_video&&(i=i.not("video_url","is",null)),a.query&&(i=i.ilike("message",`%${a.query}%`)),a.tags&&a.tags.length&&(i=i.overlaps("tags",a.tags));const{data:n}=await i.order("starred",{ascending:!1}).order("created_at",{ascending:!1}).limit(s);return n??[]}const je=t=>t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"wall",Ne=new Set(["newest","oldest","rating","random","manual"]),ke=new Set(["grid","masonry","list","carousel","marquee"]),X=t=>{const a=String(t??"").toLowerCase().trim();return Ne.has(a)?a:"newest"},Q=t=>{const a=String(t??"").toLowerCase().trim();return ke.has(a)?a:"grid"},F=t=>{const a=Math.floor(Number(t));return Number.isFinite(a)&&a>0?Math.min(a,200):30};async function A(){const{data:t}=await l.auth.getSession(),a=t.session?.access_token;return a?{Authorization:`Bearer ${a}`,"Content-Type":"application/json"}:{"Content-Type":"application/json"}}const Se={navigate:async(t,a)=>{const s=String(t.target??""),i=_e(s);if(i)return a.navigate(i),{message:`Opened ${s.trim()||"dashboard"}`};const n=await O(a.userId,s.trim());return n?(a.navigate(`/dashboard/wall?wall=${n.id}`),{message:`Opened ${n.name??"wall"}`}):{message:`I couldn't find a page called "${s.trim()}".`}},find_testimonials:async(t,a)=>{const s=await D(a.userId,t.filter??t);return{message:`Found ${s.length} testimonial${s.length===1?"":"s"}`,data:s}},list_pending:async(t,a)=>{const s=Math.min(Math.max(Number(t.limit)||20,1),50),{data:i}=await l.from("testimonials").select("id, customer_name, message, rating, created_at").eq("user_id",a.userId).eq("status","pen
1ding").is("deleted_at",null).order("created_at",{ascending:!1}).limit(s),n=i??[];return{message:n.length?`${n.length} testimonial${n.length===1?"":"s"} waiting for review`:"Nothing pending — you're all caught up.",data:n}},summarize_sentiment:async t=>{const a=String(t.range??"all time"),s=await fetch("/api/analyze",{method:"POST",headers:await A(),body:JSON.stringify({mode_id:"custom",mode_name:"Copilot summary",prompt:`Summarize what customers love and what they complain about (${a}). Lead with the 3 strongest themes, each backed by a short verbatim quote.`})}),i=await s.json().catch(()=>null);return!s.ok||!i?.output?{message:i?.error==="no_testimonials_to_analyze"?"No testimonials to analyze yet.":"Couldn't analyze right now."}:{message:"Here's what customers are saying:",data:{snippet:i.output}}},draft_case_study:async(t,a)=>{let s=typeof t.id=="string"&&t.id.trim()?t.id.trim():"";if(!s){const p=(await D(a.userId,t.filter??{},50)).filter(d=>(d.message??"").trim().length>=40);p.sort((d,c)=>(c.rating??0)-(d.rating??0)),s=p[0]?.id??""}if(!s)return{message:"No testimonial with enough text to draft from — collect or approve one first."};const i=await fetch(`/api/case-study/${encodeURIComponent(s)}`,{method:"POST",headers:await A()}),n=await i.json().catch(()=>null);return!i.ok||!n?.output?{message:n?.error==="no_text"?"That testimonial has no text to draft from.":"Couldn't draft the case study right now."}:{message:"Draft case study — review, edit, then publish it wherever you like:",data:{snippet:n.output}}},send_review_requests:async t=>{const a=(Array.isArray(t.recipients)?t.recipients:[]).filter(m=>typeof m=="string"&&m.includes("@")).slice(0,50);if(!a.length)return{message:"No valid email addresses to send to."};const s=await fetch("/api/requests/send",{method:"POST",headers:await A(),body:JSON.stringify({recipients:a,subject:typeof t.subject=="string"?t.subject:void 0,body:typeof t.message=="string"?t.message:""})}),i=await s.json().catch(()=>null);if(!s.ok||!i)return{message:"Couldn't send the requests right now."};if(i.configured===!1)return{message:"Email sending isn't configured yet — share your collection link directly instead:",data:i.submit_url?{snippet:i.submit_url,url:i.submit_url}:void 0};const n=i.sent??0;return{message:`Sent ${n} review request${n===1?"":"s"}${i.failed?` (${i.failed} failed)`:""}`}},send_thank_you:async t=>{const a=String(t.id??"").trim();if(!a)return{message:"No testimonial specified."};const s=await fetch(`/api/testimonials/${encodeURIComponent(a)}/thank-you`,{method:"POST",headers:await A()}),i=await s.json().catch(()=>null);return!s.ok||!i?.ok?{message:i?.error==="no_customer_email"?"That testimonial has no email address on file.":"Couldn't send the thank-you right now."}:{message:"Thank-you email sent."}},classify_testimonials:async t=>{const a=Array.isArray(t.ids)?t.ids:[],n=(await(await fetch("/api/testimonials/classify",{method:"POST",headers:await A(),body:JSON.stringify({ids:a})})).json().catch(()=>null))?.results?.length??0;return{message:`Classified ${n} testimonial${n===1?"":"s"}`}},create_wall:async(t,a)=>{const s=String(t.name??"New wall").slice(0,80),i=t.filter??{},n=await D(a.userId,i,F(t.max_items)),m=`${je(s)}-${Math.random().toString(36).slice(2,7)}`,{data:p,error:d}=await l.from("walls").insert({user_id:a.userId,kind:"wall",status:"draft",name:s,slug:m,layout:Q(t.layout),theme:t.theme??"light",sort_mode:X(t.sort_mode),max_items:F(t.max_items),membership_mode:"manual"}).select("id").single();if(d||!p)return{message:`Couldn't create the wall: ${d?.message??"unknown error"}`};const c=p.id;return n.length&&await l.from("wall_testimonials").insert(n.map((y,v)=>({wall_id:c,testimonial_id:y.id,display_order:v}))),a.navigate(`/dashboard/wall?wall=${c}`),{message:`Built draft wall "${s}" with ${n.length} testimonial${n.length===1?"":"s"} — previewing it now.`,data:{wallId:c},undo:async()=>{await l.from("walls").delete().eq("id",c).eq("user_id",a.userId)}}},add_to_wall:async(t,a)=>{const s=await O(a.userId,String(t.wall??""));if(!s)return{message:"Couldn't find that wall."};const i=Array.isArray(t.testimonial_ids)?t.testimonial_ids:[];if(!i.length)return{message:"No testimonials to add."};const{data:n}=await l.from("wall_testimonials").select("testimonial_id").eq("wall_id",s.id),m=new Set((n??[]).map(c=>c.testimonial_id)),p=i.filter(c=>!m.has(c));if(!p.length)return{message:"Those are already on the wall."};const d=m.size;return await l.from("wall_testimonials").insert(p.map((c,y)=>({wall_id:s.id,testimonial_id:c,display_order:d+y}))),{message:`Added ${p.length} to "${s.name??"wall"}"`,undo:async()=>{await l.from("wall_testimonials").delete().eq("wall_id",s.id).in("testimonial_id",p)}}},remove_from_wall:async(t,a)=>{const s=await O(a.userId,String(t.wall??""));if(!s)return{message:"Couldn't find that wall."};const i=Array.isArray(t.testimonial_ids)?t.testimonial_ids:[];return await l.from("wall_testimonials").delete().eq("wall_id",s.id).in("testimonial_id",i),{message:`Removed ${i.length} from "${s.name??"wall"}"`}},set_wall_style:async(t,a)=>{const s=await O(a.userId,String(t.wall??""));if(!s)return{message:"Couldn't find that wall."};const{data:i}=await l.from("walls").select("layout, theme, sort_mode, max_items").eq("id",s.id).single(),n={};return t.layout!=null&&(n.layout=Q(t.layout)),t.theme!=null&&(n.theme=String(t.theme)),t.sort_mode!=null&&(n.sort_mode=X(t.sort_mode)),t.max_items!=null&&(n.max_items=F(t.max_items)),Object.keys(n).length?(await l.from("walls").update(n).eq("id",s.id).eq("user_id",a.userId),{message:`Restyled "${s.name??"wall"}"`,undo:i?async()=>{await l.from("walls").update(i).eq("id",s.id)}:void 0}):{message:"Nothing to change."}},get_embed_code:async(t,a)=>{const s=await O(a.userId,String(t.wall??""));if(!s?.slug)return{message:"Couldn't find that wall (or it has no public slug yet)."};const i=`<script src="${a.origin}/embed/wall.js" data-slug="${s.slug}" async><\/script>
2<div class="testivo-wall" data-slug="${s.slug}"></div>`;return{message:`Embed code for "${s.name??"wall"}":`,data:{snippet:i,url:`${a.origin}/wall/${s.slug}`}}},approve_testimonials:async(t,a)=>{const s=Array.isArray(t.ids)?t.ids:[];return s.length?(await l.from("testimonials").update({status:"approved"}).in("id",s).eq("user_id",a.userId),{message:`Approved ${s.length} testimonial${s.length===1?"":"s"}`,undo:async()=>{await l.from("testimonials").update({status:"pending"}).in("id",s)}}):{message:"No testimonials to approve."}},tag_testimonials:async(t,a)=>{const s=Array.isArray(t.ids)?t.ids:[],i=Array.isArray(t.tags)?t.tags:[];if(!s.length||!i.length)return{message:"Nothing to tag."};for(const n of s){const{data:m}=await l.from("testimonials").select("tags").eq("id",n).eq("user_id",a.userId).single(),p=m?.tags??[],d=Array.from(new Set([...p,...i].map(c=>
2c.toLowerCase().trim()).filter(Boolean))).slice(0,8);await l.from("testimonials").update({tags:d}).eq("id",n)}return{message:`Tagged ${s.length} with ${i.map(n=>`"${n}"`).join(", ")}`}},pin_testimonial:async(t,a)=>{const s=String(t.id??"");if(!s)return{message:"No testimonial specified."};const i=t.pinned==null?!0:!!t.pinned;return await l.from("testimonials").update({starred:i}).eq("id",s).eq("user_id",a.userId),{message:i?"Featured":"Unfeatured",undo:async()=>{await l.from("testimonials").update({starred:!i}).eq("id",s)}}},hide_testimonial:async(t,a)=>{const s=String(t.id??"");return s?(await l.from("testimonials").update({deleted_at:new Date().toISOString()}).eq("id",s).eq("user_id",a.userId),{message:"Hidden from walls",undo:async()=>{await l.from("testimonials").update({deleted_at:null}).eq("id",s)}}):{message:"No testimonial specified."}}};async function qe(t){if(ve(t.tool)?.mutates)try{await fetch("/api/copilot/log",{method:"POST",headers:await A(),body:JSON.stringify(t)})}catch{}}function Ae(){const{user:t,activeProfile:a}=K(),s=a?.id??t?.id,[i,n]=f.useState("idle"),[m,p]=f.useState(null),[d,c]=f.useState({}),y=f.useRef(""),v=f.useMemo(()=>t&&s?{userId:s,origin:typeof window<"u"?window.location.origin:"",navigate:b=>{typeof window<"u"&&window.location.assign(b)}}:null,[t,s]),q=()=>{n("idle"),p(null),c({})};async function R(b,j){if(!v)return;const x=j.steps[b],N=Se[x.tool];if(!N){c(u=>({...u,[b]:{status:"error",result:{message:"That action isn't wired up yet."}}}));return}c(u=>({...u,[b]:{status:"running"}}));try{const u=await N(x.args,v);c(h=>({...h,[b]:{status:"done",result:u}})),x.mutates&&(qe({utterance:y.current,tool:x.tool,args:x.args,result:u.data}),_.success(u.message,{action:u.undo?{label:"Undo",onClick:async()=>{await u.undo?.(),_.message("Undone")}}:void 0}))}catch(u){c(h=>({...h,[b]:{status:"error",result:{message:u instanceof Error?u.message:"Failed"}}}))}}async function C(b){const j=b.trim();if(!(!j||!v)){y.current=j,n("thinking"),p(null),c({});try{const{data:x}=await l.auth.getSession(),N=x.session?.access_token,u=await fetch("/api/copilot",{method:"POST",headers:{"Content-Type":"application/json",...N?{Authorization:`Bearer ${N}`}:{}},body:JSON.stringify({utterance:j})}),h=await u.json().catch(()=>null);if(!u.ok||!h||!Array.isArray(h.steps)){const $=h?.error??"";_.error($.startsWith("ai_unavailable")?"AI features aren't configured yet (missing API key). Ping the site owner.":"Copilot couldn't respond. Try again."),n("idle");return}p(h),n("plan"),h.steps.forEach(($,T)=>{$.mutates||R(T,h)})}catch{_.error("Copilot is unavailable right now."),n("idle")}}}return{phase:i,plan:m,steps:d,ask:C,runStep:R,reset:q,ready:!!v}}function Ce({summary:t,mutates:a,state:s,onApply:i}){const n=s?.status??"idle";return e.jsxs("div",{className:"rounded-lg border bg-background p-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsx("p",{className:"text-sm",children:t}),a&&n==="idle"&&e.jsx("button",{onClick:i,className:"shrink-0 rounded-md bg-primary px-3 py-1 text-xs font-semibold text-primary-foreground",children:"Apply"}),n==="running"&&e.jsx(L,{className:"h-4 w-4 animate-spin text-muted-foreground"}),n==="done"&&e.jsx(P,{className:"h-4 w-4 text-emerald-600"})]}),s?.result&&e.jsx($e,{result:s.result,status:n})]})}function $e({result:t,status:a}){const s=t.data;return e.jsxs("div",{className:"mt-2 space-y-1.5 text-sm",children:[(a==="done"||a==="error")&&e.jsx("p",{className:"text-muted-foreground",children:t.message}),s&&!Array.isArray(s)&&s.snippet&&e.jsxs("div",{className:"rounded-md bg-muted p-2",children:[e.jsx("div",{className:"mb-1 flex justify-end",children:e.jsxs("button",{onClick:()=>{navigator.clipboard.writeText(String(s.snippet)),_.success("Copied")},className:"flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",children:[e.jsx(re,{className:"h-3 w-3"})," Copy"]})}),e.jsx("code",{className:"block whitespace-pre-wrap break-all text-[11px]",children:s.snippet})]}),Array.isArray(s)&&s.slice(0,4).map((i,n)=>e.jsxs("p",{className:"truncate rounded bg-muted/50 px-2 py-1 text-xs text-muted-foreground",children:["“",(i.message??"").slice(0,90),"”"]},n)),t.undo&&a==="done"&&e.jsxs("button",{onClick:()=>{t.undo?.().then(()=>_.message("Undone"))},className:"flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",children:[e.jsx(oe,{className:"h-3 w-3"})," Undo"]})]})}function Te(){const{user:t,activeProfile:a}=K(),s=a?.id??t?.id,[i,n]=f.useState(""),[m,p]=f.useState(""),[d,c]=f.useState([]),[y,v]=f.useState([]),[q,R]=f.useState(0),[C,b]=f.useState(!0),[j,x]=f.useState(!1),N=ie(),{phase:u,plan:h,steps:$,ask:T,runStep:Z,reset:ee}=Ae(),I=async()=>
2{if(!t)return;const[r,o,g,k]=await Promise.all([l.from("profiles").select("brand_name, full_name").eq("id",s).maybeSingle(),l.from("testimonials").select("id, customer_name, message, rating, video_url, tags, created_at, starred").eq("user_id",s).eq("status","approved").eq("consent_public",!0).is("deleted_at",null).order("starred",{ascending:!1}).order("created_at",{ascending:!1}).limit(500),l.from("testimonials").select("id, customer_name, message, rating, video_url, tags, created_at").eq("user_id",s).eq("status","pending").is("deleted_at",null).order("created_at",{ascending:!1}).limit(50),l.from("walls").select("id",{count:"exact",head:!0}).eq("user_id",s)]),w=r.data;p((w?.brand_name||w?.full_name||t.email?.split("@")[0]||"there").split(" ")[0]),c(o.data??[]),v(g.data??[]),R(k.count??0),b(!1)};f.useEffect(()=>{I()},[s]);const te=f.useMemo(()=>{const r=typeof window<"u"?new Date().getHours():9;return r<12?"Good morning":r<18?"Good afternoon":"Good evening"},[]);f.useMemo(()=>{const r=d.map(o=>o.rating).filter(o=>typeof o=="number");return r.length?r.reduce((o,g)=>o+g,0)/r.length:null},[d]);const U=f.useMemo(()=>y.filter(r=>(r.rating??0)>=4&&(r.message??"").trim().length>0),[y]),E=f.useMemo(()=>{const r=d.length+y.length>0,o=d.length>0,g=q>0;return[{n:1,label:"Collect proof",done:r,hint:r?"You have testimonials":"Share your form to gather testimonials",cta:"Share form",action:()=>{N({to:"/dashboard/collection-page"})}},{n:2,label:"Approve the best",done:o,hint:y.length?`${y.length} waiting for you`:o?"Approved & ready":"Approve testimonials to use them",cta:"Review",action:()=>x(!0)},{n:3,label:"Publish a wall",done:g,hint:g?`${q} wall${q===1?"":"s"} live`:"Let AI build your first wall",cta:"Build wall",prompt:"build a wall of my best testimonials, masonry layout"}]},[d.length,y.length,q]),G=E.filter(r=>r.done).length,B=f.useMemo(()=>{const r=[],o=d.filter(w=>(w.rating??0)>=4);o.length&&r.push({key:"top",title:"Top-rated",icon:e.jsx(H,{className:"h-4 w-4"}),items:o,prompt:"build a wall of my top-rated 5-star testimonials"});const g=d.filter(w=>w.video_url);g.length&&r.push({key:"video",title:"On video",icon:e.jsx(le,{className:"h-4 w-4"}),items:g,prompt:"build a wall of my video testimonials"});const k=new Map;for(const w of d)for(const S of w.tags??[])S.trim()&&k.set(S.trim(),(k.get(S.trim())??0)+1);return[...k.entries()].sort((w,S)=>S[1]-w[1]).slice(0,3).forEach(([w])=>{r.push({key:`tag-${w}`,title:`#${w}`,icon:e.jsx(de,{className:"h-4 w-4"}),items:d.filter(S=>(S.tags??[]).includes(w)),prompt:`build a wall of testimonials tagged ${w}`})}),d.length&&r.push({key:"recent",title:"Most recent",icon:e.jsx(me,{className:"h-4 w-4"}),items:d.slice(0,12),prompt:"build a wall of my most recent testimonials"}),r},[d]),se=async r=>{v(o=>o.filter(g=>g.id!==r)),await l.from("testimonials").update({status:"approved"}).eq("id",r).eq("user_id",s??""),_.success("Approved",{action:{label:"Undo",onClick:async()=>{await l.from("testimonials").update({status:"pending"}).eq("id",r),I()}}}),I()},ae=async r=>{const o=r.map(g=>g.id);o.length&&(v(g=>g.filter(k=>!o.includes(k.id))),await l.from("testimonials").update({status:"approved"}).in("id",o).eq("user_id",s??""),_.success(`Approved ${o.length}`,{action:{label:"Undo",onClick:async()=>{await l.from("testimonials").update({status:"pending"}).in("id",o),I()}}}),I())};return e.jsxs("div",{className:"mx-auto max-w-4xl pb-24",children:[e.jsxs("div",{className:"mb-1 flex items-center gap-2 text-primary",children:[e.jsx(z,{className:"h-5 w-5"}),e.jsx("span",{className:"text-sm font-semibold",children:"Testivo Copilot"})]}),e.jsxs("h1",{className:"font-serif text-3xl font-bold tracking-tight sm:text-4xl",children:[te,m?`, ${m}`:"","."]}),e.jsx("p",{className:"mt-1 text-muted-foreground",children:C||d.length+y.length>0?"Here's your social proof — I've lined up what to do next.":"Let's collect your first testimonials — I'll walk you through it."}),!C&&G<3&&e.jsxs("div",{className:"mt-5 rounded-2xl border bg-background p-4",children:[e.jsxs("div",{className:"mb-3 flex items-center justify-between",children:[e.jsx("span",{className:"text-sm font-semibold",children:"Get to your first published wall"}),e.jsxs("span",{className:"text-xs font-medium text-muted-foreground",children:[G," of 3 done"]})]}),e.jsx("div",{className:"grid gap-3 sm:grid-cols-3",children:E.map((r,o)=>e.jsxs("div",{className:"relative",children:[o<E.length-1&&e.jsx("div",{className:"absolute left-8 top-4 hidden h-px w-full bg-border sm:block"}),e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("span",{className:`grid h-8 w-8 shrink-0 place-items-center rounded-full ${r.done?"bg-emerald-500 text-white":"border-2 border-primary/40 text-primary"}`,children:r.done?e.jsx(P,{className:"h-4 w-4"}):e.jsx("span",{className:"text-xs font-bold",children:r.n})}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("p",{className:"text-sm font-semibold leading-tight",children:r.label}),e.jsx("p",{className:"text-xs text-muted-foreground",children:r.hint}),!r.done&&e.jsxs("button",{onClick:()=>r.action?r.action():r.prompt?void T(r.prompt):void 0,className:"mt-1.5 inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-semibold text-primary-foreground",children:[r.n===1?e.jsx(ce,{className:"h-3 w-3"}):r.n===2?e.jsx(ue,{className:"h-3 w-3"}):e.jsx(pe,{className:"h-3 w-3"}),r.cta]})]})]})]},r.n))})]}),e.jsxs("form",{onSubmit:r=>{r.preventDefault(),T(i)},className:"mt-5 flex items-center gap-2 rounded-2xl border bg-background p-2 shadow-sm focus-within:border-primary",children:[e.jsx(z,{className:"ml-2 h-5 w-5 shrink-0 text-primary"}),e.jsx("input",{value:i,onChange:r=>n(r.target.value),placeholder:"Tell me anything — build a wall, find reviews, draft a case study…",className:"flex-1 bg-transparent px-1 py-2 text-[15px] outline-none placeholder:text-muted-foreground"}),e.jsx("button",{type:"submit",disabled:!i.trim()||u==="thinking",className:"rounded-xl bg-primary p-2.5 text-primary-foreground disabled:opacity-40",children:u==="thinking"?e.jsx(L,{className:"h-5 w-5 animate-spin"}):e.jsx(he,{className:"h-5 w-5"})})]}),u==="idle"&&e.jsxs(ne,{to:"/dashboard/mcp",className:"mt-2 inline-flex items-center gap-1.5 text-xs text-muted-foreground transition hover:text-primary",children:[e.jsx(ge,{className:"h-3.5 w-3.5"})," Or run Testivo from ChatGPT / Claude — connect via MCP →"]}),u==="idle"&&U.length>0&&e.jsx("div",{className:"mt-4 rounded-2xl border border-primary/30 bg-primary/5 p-4",children:e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("p",{className:"flex items-center gap-1.5 text-sm font-semibold",children:[e.jsx(z,{className:"h-4 w-4 shrink-0 text-primary"}
2),U.length," testimonial",U.length===1?"":"s"," ready to approve"]}),e.jsx("p",{className:"mt-0.5 text-xs text-muted-foreground",children:"4★+ with a written quote — safe to publish. Approve them all in one click, or review individually."})]}),e.jsxs("div",{className:"flex shrink-0 items-center gap-2",children:[e.jsx("button",{onClick:()=>x(!0),className:"rounded-lg border px-3 py-2 text-xs font-semibold transition hover:border-primary hover:text-primary",children:"Review"}),e.jsxs("button",{onClick:()=>{ae(U)},className:"inline-flex items-center gap-1 rounded-lg bg-primary px-3 py-2 text-xs font-semibold text-primary-foreground",children:[e.jsx(P,{className:"h-3.5 w-3.5"})," Approve all"]})]})]})}),u==="thinking"&&e.jsxs("div",{className:"mt-4 flex items-center gap-2 text-sm text-muted-foreground",children:[e.jsx(L,{className:"h-4 w-4 animate-spin"})," Thinking…"]}),u==="plan"&&h&&e.jsxs("div",{className:"mt-4 space-y-3",children:[h.reply&&e.jsx("p",{className:"text-sm",children:h.reply}),h.steps.map((r,o)=>e.jsx(Ce,{summary:r.summary,mutates:r.mutates,state:$[o],onApply:()=>Z(o,h)},o)),h.steps.length===0&&!h.needsClarification&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"I couldn't map that to an action yet — try rephrasing."}),e.jsx("button",{onClick:()=>{n(""),ee()},className:"text-xs text-muted-foreground underline-offset-2 hover:underline",children:"← Back to workspace"})]}),u==="idle"&&e.jsx("div",{className:"mt-6",children:C?e.jsxs("div",{className:"flex items-center gap-2 py-6 text-sm text-muted-foreground",children:[e.jsx(L,{className:"h-4 w-4 animate-spin"})," Organizing your proof…"]}):B.length?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-2 flex items-center gap-2",children:[e.jsx(fe,{className:"h-4 w-4 text-primary"}),e.jsx("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-muted-foreground",children:"Your proof, assembled by AI"})]}),e.jsx("div",{className:"grid gap-3 sm:grid-cols-2",children:B.map(r=>e.jsxs("div",{className:"rounded-2xl border bg-background p-4",children:[e.jsxs("div",{className:"mb-2 flex items-center justify-between",children:[e.jsxs("span",{className:"flex items-center gap-2 text-sm font-semibold",children:[e.jsx("span",{className:"text-primary",children:r.icon}),r.title]}),e.jsx("span",{className:"text-xs text-muted-foreground",children:r.items.length})]}),e.jsx("div",{className:"space-y-1",children:r.items.slice(0,2).map(o=>e.jsxs("p",{className:"line-clamp-1 text-xs text-muted-foreground",children:["“",o.message??"","”"]},o.id))}),e.jsxs("button",{onClick:()=>{T(r.prompt)},className:"mt-3 inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-semibold transition hover:border-primary hover:text-primary",children:[e.jsx(ye,{className:"h-3 w-3"})," Build & embed this wall"]})]},r.key))})]}):e.jsxs("div",{className:"rounded-2xl border border-dashed bg-background p-6 text-center",children:[e.jsx(we,{className:"mx-auto h-8 w-8 text-muted-foreground/40"}),e.jsx("p",{className:"mt-2 text-sm font-medium",children:"No proof yet"}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"Start with step 1 above — share your form to collect testimonials."})]})}),j&&e.jsx("div",{className:"fixed inset-0 z-50 flex justify-end bg-black/30",onClick:()=>x(!1),children:e.jsxs("div",{className:"h-full w-full max-w-md overflow-y-auto bg-background p-5 shadow-xl",onClick:r=>r.stopPropagation(),children:[e.jsxs("div",{className:"mb-4 flex items-center justify-between",children:[e.jsxs("h2",{className:"text-lg font-bold",children:["Review pending (",y.length,")"]}),e.jsx("button",{onClick:()=>x(!1),className:"rounded-md p-1 hover:bg-accent",children:e.jsx(xe,{className:"h-5 w-5"})})]}),y.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing pending — you're all caught up. 🎉"}):e.jsx("div",{className:"space-y-3",children:y.map(r=>e.jsxs("div",{className:"rounded-xl border p-3",children:[r.rating!=null&&e.jsx("div",{className:"mb-1 flex gap-0.5",children:Array.from({length:5}).map((o,g)=>e.jsx(H,{className:`h-3 w-3 ${g<(r.rating??0)?"fill-amber-400 text-amber-400":"text-muted-foreground
2/30"}`},g))}),e.jsxs("p",{className:"text-sm text-muted-foreground",children:["“",r.message??"","”"]}),e.jsx("p",{className:"mt-1 text-xs font-medium",children:r.customer_name??"Anonymous"}),e.jsxs("button",{onClick:()=>{se(r.id)},className:"mt-2 inline-flex items-center gap-1 rounded-md bg-emerald-500 px-3 py-1 text-xs font-semibold text-white",children:[e.jsx(P,{className:"h-3 w-3"})," Approve"]})]},r.id))})]})})]})}function Pe(){return e.jsx(Te,{})}export{Pe as component};

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.