1import{r as S,Q as qe,u as Oe,ap as be,aq as Ce,ar as Re,j as y,L as Le,as as De,at as Se,X as ve,ak as Pe}from"./index-DyufGeC_.js";import{M as Fe}from"./mic-off-BjERPh7a.js";function Me({onClose:M,onRetry:U}){const $=/iPad|iPhone|iPod/.test(navigator.userAgent),_=/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||$,L=/android/i.test(navigator.userAgent);return y.jsx("div",{className:"fixed inset-0 z-[10000] flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm animate-in fade-in duration-200",onClick:M,children:y.jsxs("div",{className:"bg-card border border-border rounded-t-2xl sm:rounded-2xl w-full max-w-sm mx-auto p-5 pb-8 sm:pb-5 shadow-2xl animate-in slide-in-from-bottom-8 duration-300",onClick:K=>K.stopPropagation(),children:[y.jsxs("div",{className:"flex items-center justify-between mb-4",children:[y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsx("div",{className:"w-10 h-10 rounded-full bg-red-500/10 flex items-center justify-center",children:y.jsx(Fe,{className:"w-5 h-5 text-red-500"})}),y.jsx("h3",{className:"text-base font-semibold text-foreground",children:"Microphone Access Needed"})]}),y.jsx("button",{onClick:M,className:"w-8 h-8 rounded-full hover:bg-muted flex items-center justify-center",children:y.jsx(ve,{className:"w-4 h-4 text-muted-foreground"})})]}),y.jsx("p",{className:"text-sm text-muted-foreground mb-4",children:"The voice assistant needs microphone access to hear you. Follow these steps to enable it:"}),y.jsx("div",{className:"space-y-3 mb-5",children:$&&_?y.jsxs(y.Fragment,{children:[y.jsx(C,{n:1,text:'Tap the "aA" button in the Safari address bar'}),y.jsx(C,{n:2,text:'Tap "Website Settings"'}),y.jsx(C,{n:3,text:'Set Microphone to "Allow"'}),y.jsx(C,{n:4,text:'Reload the page, then tap "Try Again" below'}),y.jsx("p",{className:"text-xs text-muted-foreground ml-9",children:"If that doesn't work: Settings app â Safari â Microphone â Allow"})]}):$&&!_?y.jsxs(y.Fragment,{children:[y.jsx(C,{n:1,text:"Open your iPhone Settings app"}),y.jsx(C,{n:2,text:"Scroll down and tap your browser app (Chrome, Firefox, etc.)"}),y.jsx(C,{n:3,text:'Turn on "Microphone"'}),y.jsx(C,{n:4,text:'Reload the page, then tap "Try Again" below'})]}):L?y.jsxs(y.Fragment,{children:[y.jsx(C,{n:1,text:"Tap the lock/tune icon in your browser's address bar"}),y.jsx(C,{n:2,text:'Tap "Site settings" or "Permissions"'}),y.jsx(C,{n:3,text:'Set Microphone to "Allow"'}),y.jsx(C,{n:4,text:'Come back here and tap "Try Again"'})]}):y.jsxs(y.Fragment,{children:[y.jsx(C,{n:1,text:"Click the lock/tune icon next to the URL in your browser"}),y.jsx(C,{n:2,text:'Find "Microphone" in the permissions list'}),y.jsx(C,{n:3,text:'Change it from "Block" to "Allow"'}),y.jsx(C,{n:4,text:"Reload the page and try again"})]})}),y.jsxs("div",{className:"flex gap-2",children:[y.jsx("button",{onClick:M,className:"flex-1 px-4 py-2.5 rounded-lg border border-border text-sm font-medium text-muted-foreground hover:bg-muted transition-colors",children:"Close"}),y.jsxs("button",{onClick:U,className:"flex-1 px-4 py-2.5 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors flex items-center justify-center gap-1.5",children:[y.jsx(Se,{className:"w-3.5 h-3.5"}),"Try Again"]})]})]})})}function C({n:M,text:U}){return y.jsxs("div",{className:"flex items-start gap-3",children:[y.jsx("div",{className:"shrink-0 w-6 h-6 rounded-full bg-primary/10 text-primary flex items-center justify-center text-xs font-bold mt-0.5",children:M}),y.jsx("p",{className:"text-sm text-foreground leading-relaxed",children:U})]})}function Ye({onClose:M,isOpen:U,initialMessage:$}){const[_,L]=S.useState("idle"),[K,w]=S.useState(""),[de,pe]=S.useState(0),[ke,X]=S.useState(!1),Z=S.useRef(null),P=S.useRef(null),j=S.useRef(null),ee=S.useRef(null),te=S.useRef(null),B=S.useRef([]),J=S.useRef(!1),Y=S.useRef(null),H=S.useRef(null),ae=S.useRef(0),re=S.useRef(!1),[Ue,D]=qe(),V=S.useRef(null),W=S.useRef(!1),ie=S.useRef($),Q=S.useRef(null),se=S.useRef(!1),{user:R}=Oe();S.useEffect(()=>{ie.current=$},[$]);const Ae=S.useCallback(()=>
1{if(se.current)return;se.current=!0;let l=0;const n=8,c=()=>{if(!se.current||l>=n){z();return}const s=document.querySelectorAll("[data-equipment-id]");if(s.length===0)window.scrollBy({top:280,behavior:"smooth"});else{const t=Math.min(l+1,s.length-1);s[t].scrollIntoView({behavior:"smooth",block:"center"})}l++,Q.current=window.setTimeout(c,3500)};Q.current=window.setTimeout(c,2e3)},[]),z=S.useCallback(()=>{se.current=!1,Q.current&&(clearTimeout(Q.current),Q.current=null)},[]);S.useEffect(()=>{U&&_==="idle"&&!W.current&&(W.current=!0,oe()),!U&&_!=="idle"&&(ce(),W.current=!1),U||(W.current=!1)},[U]),S.useEffect(()=>()=>{ce()},[]);const le=async()=>{if(V.current)return V.current;try{const n=await(await fetch("/api/voice-equipment-context")).json();return V.current=n,n}catch{return null}},he=S.useCallback(l=>{if(!P.current)return;const n=new Int16Array(l),c=new Float32Array(n.length);for(let s=0;s<n.length;s++)c[s]=n[s]/32768;B.current.push(c),J.current||fe()},[]),me=S.useCallback(()=>{if(B.current=[],J.current=!1,Y.current){try{Y.current.stop()}catch{}Y.current=null}},[]),fe=S.useCallback(()=>{if(!P.current||B.current.length===0){J.current=!1,Y.current=null;return}J.current=!0;const l=P.current,n=B.current.shift(),c=l.sampleRate;let s=n;if(c!==24e3){const i=24e3/c,a=Math.round(n.length/i);s=new Float32Array(a);for(let o=0;o<a;o++){const r=o*i,u=Math.floor(r),T=r-u;s[o]=u+1<n.length?n[u]*(1-T)+n[u+1]*T:n[u]||0}}const t=l.createBuffer(1,s.length,c);t.copyToChannel(s,0);const e=l.createBufferSource();e.buffer=t,e.connect(l.destination),Y.current=e,e.onended=()=>{Y.current===e&&(Y.current=null),fe()},e.start()},[]),ge=S.useCallback((l,n)=>{const c={...l};if(!c.length&&c.search){const t=String(c.search).trim().toLowerCase().match(/^(\d{2,3})\s*(?:ft|foot|feet|'|footer|footers)?$/);t&&(c.length=t[1]+"ft",delete c.search)}if(c.length){let s=c.length.replace(/\s+/g,"").toLowerCase();if(s.includes("ft")||(s=s.replace(/[^\d]/g,"")+"ft"),n!=null&&n.lengths){const t=n.lengths.find(e=>e.toLowerCase()===s);t?c.length=t:c.length=s}else c.length=s}if(c.type&&(n!=null&&n.types)){const s=Object.keys(n.types),t=String(c.type).split(",").map(e=>e.trim()).filter(Boolean);if(t.length>1){const e=t.map(i=>{const a=s.find(u=>u===i);if(a)return a;const o=i.toLowerCase();return s.find(u=>u.toLowerCase()===o)||i});c.type=e.join(",")}else if(!s.find(i=>i===c.type)){const i=c.type.toLowerCase(),a=s.find(o=>o.toLowerCase()===i);a&&(c.type=a)}}if(c.make&&(n!=null&&n.makes)){const s=String(c.make).split(",").map(t=>t.trim()).filter(Boolean);if(s.length>1){const t=s.map(e=>{const i=n.makes.find(r=>r===e);if(i)return i;const a=e.toLowerCase();return n.makes.find(r=>r.toLowerCase()===a)||e});c.make=t.join(",")}else if(!n.makes.find(e=>e===c.make)){const e=c.make.toLowerCase(),i=n.makes.find(a=>a.toLowerCase()===e);i&&(c.make=i)}}if(c.condition&&(c.condition=c.condition.toLowerCase()),c.location){const s={southeast:"AL,FL,GA,MS,NC,SC,TN,VA",northeast:"CT,DE,MA,MD,ME,NH,NJ,NY,PA,RI,VT",midwest:"IA,IL,IN,KS,MI,MN,MO,ND,NE,OH,SD,WI",southwest:"AZ,NM,OK,TX",west:"CA,CO,ID,MT,NV,OR,UT,WA,WY"},t=c.location.toLowerCase().trim();s[t]?c.location=s[t]:c.location=c.location.split(",").map(e=>e.trim().toUpperCase().replace(/[^A-Z]/g,"")).filter(Boolean).join(",")}if(c.composition){const s={aluminum:"Aluminum",aluminium:"Aluminum",alum:"Aluminum","all aluminum":"Aluminum",steel:"Steel",combo:"Combination",combination:"Combination","steel and aluminum":"Combination","aluminum and steel":"Combination","plate / composite":"Plate / Composite",composite:"Plate / Composite",plate:"Plate / Composite"},t=c.composition.toLowerCase().trim();c.composition=s[t]||c.composition.charAt(0).toUpperCase()+c.composition.slice(1).toLowerCase()}if(c.tire_size&&(n!=null&&n.tireSizes)){const s=n.tireSizes,e=c.tire_size.replace(/\s+/g,"").split(",").map(T=>T.trim()).filter(Boolean),i=new Set,a=T=>T.replace(/[\/\.\s]/g,"").toLowerCase(),o={"17.5":["175","r175"],"22.5":["225","r225"],"24.5":["245","r245"],"19.5":["195","r195"]};for(const T of e){const b=a(T),E=s.find(f=>f===T);if(E){i.add(E);continue}let x="";if(/^17[\.\/]?5$/i.test(b)||b==="175"?x="17.5":/^22[\.\/]?5$/i.test(b)||b==="225"?x="22.5":/^24[\.\/]?5$/i.test(b)||b==="245"?x="24.5":(/^19[\.\/]?5$/i.test(b)||b==="195")&&(x="19.5"),x){const f=o[x]||[];for(const A of s){const v=a(A);(A===x||v.endsWith(x.replace(".",""))||f.some(h=>v.endsWith(h)||v.includes(h)))&&i.add(A)}i.size===0&&i.add(x)}else{const f=s.find(A=>a(A)===b);i.add(f||T)}}const r=(c.type||"").toLowerCase(),u=Array.from(i).some(T=>{const b=a(T);return b.endsWith("225")||b.includes("r225")||T==="22.5"});if(r.includes("step deck")||r.includes("drop deck")){if(u)for(const T of s)(T==="22.5"||T==="255/70R22.5")&&i.add(T)}else if(r.includes("flatbed")&&u)for(const T of s){const b=a(T);(T==="22.5"||T==="295/75R22.5"||T==="295/70R22.5"||b.endsWith("245")||b.includes("r245"))&&i.add(T)}c.tire_size=Array.from(i).join(",")}if(c.axle_type){const s={fixed:"Fixed","fixed spread":"Fixed Spread",tandem:"Tandem",spread:"Spread Axle","spread axle":"Spread Axle","rear sliding":"Rear Sliding Axle","rear slide":"Rear Sliding Axle","rear sliding axle":"Rear Sliding Axle","tandem sliding":"Tandem Sliding",traveling:"Traveling Axles","traveling axles":"Traveling Axles","front sliding":"Front Sliding",sliding:"Rear Sliding Axle,Tandem Sliding,Front Sliding",slide:"Rear Sliding Axle,Tandem Sliding,Front Sliding"},t=String(c.axle_type).split(",").map(i=>
1i.trim()).filter(Boolean),e=new Set;for(const i of t){const a=s[i.toLowerCase()];a?a.split(",").forEach(o=>e.add(o)):e.add(i)}c.axle_type=Array.from(e).join(",")}if(c.rear_axles){const s={single:"Single",tandem:"Tandem",tri:"Tri","tri-axle":"Tri-Axle","tri axle":"Tri-Axle",quad:"Quad","quad axle":"Quad Axle","quad-axle":"Quad Axle",3:"Tri,Tri-Axle","3 axle":"Tri,Tri-Axle",4:"Quad,Quad Axle","4 axle":"Quad,Quad Axle",2:"Tandem","2 axle":"Tandem",1:"Single","1 axle":"Single"},t=String(c.rear_axles).split(",").map(i=>i.trim()).filter(Boolean),e=new Set;for(const i of t){const a=s[i.toLowerCase()];a?a.split(",").forEach(o=>e.add(o)):e.add(i)}c.rear_axles=Array.from(e).join(",")}if(c.keywords){const s={extendable:"extend",extensible:"extend",stretchable:"stretch",telescoping:"extend",expandable:"extend","s-t-r-e-t-c-h":"stretch",detachable:"detach",removable:"detach","air suspension":"air ride",liftgate:"lift gate"},e=String(c.keywords).split(",").map(i=>i.trim().toLowerCase()).filter(Boolean).map(i=>s[i]||i);c.keywords=[...new Set(e)].join(",")}if(c.search){const t={extendable:"extend",extensible:"extend",stretchable:"stretch",telescoping:"extend",expandable:"extend",detachable:"detach",removable:"detach"}[String(c.search).toLowerCase()];t&&(c.search=t)}return console.log("[Voice] filter_equipment raw:",JSON.stringify(l)),console.log("[Voice] filter_equipment normalized:",JSON.stringify(c)),c},[]),ye=S.useCallback(async(l,n,c)=>{if(l==="filter_equipment"){w("Applying filters...");const s=V.current||await le().catch(()=>null),t=ge(n,s),e=window.location.pathname.includes("/category/"),i=e?null:be(window.location.pathname.replace(/^\//,"")),o=e||!!i?new URLSearchParams(window.location.search):new URLSearchParams;i&&(i.type&&!o.has("type")&&!t.type&&o.set("type",i.type),i.make&&!o.has("make")&&!t.make&&o.set("make",i.make),i.length&&!o.has("length")&&!t.length&&o.set("length",i.length),i.composition&&!o.has("composition")&&!t.composition&&o.set("composition",i.composition),i.axleType&&!o.has("axle_type")&&!t.axle_type&&o.set("axle_type",i.axleType),i.rearAxles&&!o.has("rear_axles")&&!t.rear_axles&&o.set("rear_axles",i.rearAxles));const r=["type","make","condition","year_min","year_max","price_min","price_max","length","location","composition","tire_size","axle_type","rear_axles","search","keywords","sort","exclude_make","exclude_ids"];for(const A of r)t[A]!==void 0&&t[A]!==null&&t[A]!==""&&o.set(A,String(t[A]));const u=t.category||(e?decodeURIComponent(window.location.pathname.split("/category/")[1]):"Trailers"),T=o.toString(),b=`/category/${encodeURIComponent(u)}${T?`?${T}`:""}`;w(`Showing ${u} results...`);const E=window.location.pathname.split("?")[0],x=`/category/${encodeURIComponent(u)}`;E===x?window.history.pushState(null,"",b):D(b),setTimeout(()=>{window.dispatchEvent(new Event("voice-filter-applied")),window.scrollTo({top:0,behavior:"smooth"})},150),setTimeout(()=>{window.dispatchEvent(new Event("voice-filter-applied"))},600);const f=[];t.type&&f.push(`type: ${t.type}`),t.make&&f.push(`make: ${t.make}`),t.exclude_make&&f.push(`excluding brands: ${t.exclude_make}`),t.exclude_ids&&f.push(`excluding listings: ${t.exclude_ids}`),t.condition&&f.push(`condition: ${t.condition}`),(t.year_min||t.year_max)&&f.push(`year: ${t.year_min||"any"}-${t.year_max||"any"}`),(t.price_min||t.price_max)&&f.push(`price: $${t.price_min||0}-$${t.price_max||"any"}`),t.length&&f.push(`length: ${t.length}`),t.location&&f.push(`location: ${t.location}`),t.composition&&f.push(`composition: ${t.composition}`),t.tire_size&&f.push(`tire size: ${t.tire_size}`),t.axle_type&&f.push(`axle type: ${t.axle_type}`),t.rear_axles&&f.push(`rear axles: ${t.rear_axles}`),t.search&&f.push(`search: ${t.search}`),t.keywords&&f.push(`keywords: ${t.keywords}`);try{const A=await fetch("/api/equipment");if(A.ok){const v=await A.json();let h=v.filter(p=>p.category===u&&(!p.status||p.status==="active"||p.status==="Available"||p.status==="Coming In"));if(t.type){const p=t.type.split(",").map(g=>g.trim().toLowerCase());h=h.filter(g=>p.includes((g.type||"").toLowerCase()))}if(t.make){const p=t.make.split(",").map(g=>g.trim().toLowerCase());h=h.filter(g=>p.includes((g.make||"").toLowerCase()))}if(t.exclude_make){const p=t.exclude_make.split(",").map(g=>g.trim().toLowerCase());h=h.filter(g=>!p.includes((g.make||"").toLowerCase()))}if(t.exclude_ids){const p=t.exclude_ids.split(",").map(g=>
1g.trim()).filter(Boolean);h=h.filter(g=>!p.includes(String(g.id)))}if(t.condition&&(h=h.filter(p=>(p.condition||"").toLowerCase()===t.condition.toLowerCase())),t.year_min&&(h=h.filter(p=>parseInt(p.year)>=t.year_min)),t.year_max&&(h=h.filter(p=>parseInt(p.year)<=t.year_max)),t.price_min&&(h=h.filter(p=>parseFloat(p.price)>=t.price_min)),t.price_max&&(h=h.filter(p=>parseFloat(p.price)<=t.price_max)),t.length&&(h=h.filter(p=>(p.length||"").toLowerCase()===t.length.toLowerCase())),t.composition){const p=t.composition.split(",").map(g=>g.trim().toLowerCase());h=h.filter(g=>p.includes((g.composition||"").toLowerCase()))}if(t.location){const p=t.location.split(",").map(g=>g.trim().toUpperCase());h=h.filter(g=>{const q=(g.location||"").toUpperCase();return p.some(k=>q.includes(k))})}if(t.tire_size){const p=t.tire_size.split(",").map(g=>g.trim().toLowerCase());h=h.filter(g=>p.includes((g.tireSize||"").toLowerCase()))}if(t.axle_type){const p=t.axle_type.split(",").map(g=>g.trim().toLowerCase());h=h.filter(g=>p.includes((g.axleType||"").toLowerCase()))}if(t.rear_axles){const p=t.rear_axles.split(",").map(g=>g.trim().toLowerCase());h=h.filter(g=>p.includes((g.numRearAxles||"").toLowerCase()))}if(t.search){const p=t.search.toLowerCase(),q=[p,...{extendable:["extend","stretch","s-t-r-e-t-c-h"],extensible:["extend","stretch","s-t-r-e-t-c-h"],extend:["stretch","s-t-r-e-t-c-h","extendable"],stretchable:["stretch","s-t-r-e-t-c-h","extend"],stretch:["s-t-r-e-t-c-h","extend"],telescoping:["telescop","extend","stretch","s-t-r-e-t-c-h"],detachable:["detach"],removable:["detach","removable"]}[p]||[]],k=p.replace(/(able|ible|ing|tion|ed|er|s)$/,"");k.length>=4&&k!==p&&!q.includes(k)&&q.push(k),h=h.filter(O=>{const F=[O.name,O.type,O.make,O.model,O.description,O.specifications].filter(Boolean).join(" ").toLowerCase();return q.some(m=>F.includes(m))})}if(t.keywords){const p=t.keywords.split(",").map(k=>k.trim().toLowerCase()).filter(Boolean),g={extendable:["extend","stretch","s-t-r-e-t-c-h","telescop","expandable"],extensible:["extend","stretch","s-t-r-e-t-c-h","telescop","expandable"],extend:["stretch","s-t-r-e-t-c-h","extendable","extensible"],stretchable:["stretch","s-t-r-e-t-c-h","extend"],stretch:["s-t-r-e-t-c-h","extend","extendable"],telescoping:["telescop","extend","stretch","s-t-r-e-t-c-h"],detachable:["detach","removable"],removable:["detach","removable","detachable"],"air ride":["air ride","air suspension"],"air suspension":["air ride","air suspension"],aluminum:["aluminum","aluminium"],aluminium:["aluminum","aluminium"],liftgate:["liftgate","lift gate","tail gate","tailgate"],"lift gate":["liftgate","lift gate","tail gate"]},q=(k,O)=>{if(k.includes(O))return!0;const F=g[O];if(F)return F.some(d=>k.includes(d));const m=O.replace(/(able|ible|ing|tion|ed|er|s)$/,"");return!!(m.length>=4&&m!==O&&k.includes(m))};h=h.filter(k=>{const O=[k.name,k.type,k.make,k.model,k.description,k.specifications].filter(Boolean).join(" ").toLowerCase();return p.every(F=>q(O,F))})}t.sort==="price-asc"?h.sort((p,g)=>(parseFloat(p.price)||0)-(parseFloat(g.price)||0)):t.sort==="price-desc"?h.sort((p,g)=>(parseFloat(g.price)||0)-(parseFloat(p.price)||0)):t.sort==="newest"?h.sort((p,g)=>(parseInt(g.year)||0)-(parseInt(p.year)||0)):t.sort==="oldest"&&h.sort((p,g)=>(parseInt(p.year)||0)-(parseInt(g.year)||0));const N=h.slice(0,10).map(p=>({id:p.id,name:p.name,year:p.year,make:p.make,model:p.model,type:p.type,price:p.price?`$${Number(p.price).toLocaleString()}`:"Call for price",length:p.length,location:p.location,condition:p.condition,composition:p.composition,axleType:p.axleType,numRearAxles:p.numRearAxles,tireSize:p.tireSize,suspension:p.suspension}));if(h.length===0){let g=v.filter(d=>d.category===u&&(!d.status||d.status==="active"||d.status==="Available"||d.status==="Coming In"));if(t.type){const d=t.type.split(",").map(G=>G.trim().toLowerCase());g=g.filter(G=>d.includes((G.type||"").toLowerCase()))}const q=g.slice(0,10).map(d=>({id:d.id,name:d.name,year:d.year,make:d.make,model:d.model,type:d.type,price:d.price?`$${Number(d.price).toLocaleString()}`:"Call for price",length:d.length,location:d.location,condition:d.condition,composition:d.composition,axleType:d.axleType,numRearAxles:d.numRearAxles,tireSize:d.tireSize,suspension:d.suspension})),k=new URLSearchParams;t.type&&k.set("type",t.type);const O=`/category/${encodeURIComponent(u)}?${k.toString()}`,F=`/category/${encodeURIComponent(u)}`;window.location.pathname.split("?")[0]===F?window.history.pushState(null,"",O):D(O),setTimeout(()=>{window.dispatchEvent(new Event("voice-filter-applied")),window.scrollTo({top:0,behavior:"smooth"})},150),setTimeout(()=>{window.dispatchEvent(new Event("voice-filter-applied"))},600);const m=f.join(", ");return JSON.stringify({navigated:!0,category:u,filtersApplied:["type: "+(t.type||"all")],totalResults:g.length,top
1Results:q,noExactMatch:!0,originalRequest:m,instruction:`No exact matches for: ${m}. Broadened to show all ${t.type||"trailers"} (${g.length} results). CRITICAL: Stay on this trailer TYPE. Do NOT show other random trailer types â that confuses the customer. If the count is low (under 5), follow the LIMITED INVENTORY rules: explain ITAG's quality standards (10-year-or-newer, fully vetted, no salvage/rebuilt/mutilated titles, reassignable condition). For reefer trailers specifically mention the ~10,000 hour standard. Present what IS available, then ask selling questions about other types they might need â don't just dump random types on screen. Acknowledge: "No exact matches for that price range / year / location right now, but here's what we DO have in ${t.type||"this type"}..." Show the closest options using highlight_listing.`})}const I=h.length>0&&h.length<=5?` LOW INVENTORY ALERT: Only ${h.length} results for this type. Follow the LIMITED INVENTORY rules: explain ITAG's quality vetting standards, present what's available using highlight_listing, then ask selling questions about other types they might also need. Do NOT add extra filters that broaden to other trailer types â stay on what they asked for.`:"";return JSON.stringify({navigated:!0,category:u,filtersApplied:f,totalResults:h.length,topResults:N,instruction:`Results are loaded.${I} CRITICAL: The topResults list is for REFERENCE ONLY â the order does NOT match what the customer sees on screen. The page uses its own sort (Featured/popularity). Do NOT describe any trailer's specs or price from this list â the customer will see DIFFERENT trailers on screen. Instead, QUALIFY the customer first: 'Got [count] [type] showing for you â any specific specs you want to narrow it down?' WAIT for their answer. When ready to present a trailer, use highlight_listing FIRST â it scrolls to and highlights the trailer AND returns its real details. ONLY describe a trailer AFTER highlight_listing confirms it's on screen.`})}}catch{}return JSON.stringify({navigated:!0,category:u,filtersApplied:f})}if(l==="navigate_to_category"){const s=n.category||"Trailers";w(`Navigating to ${s}...`),D(`/category/${encodeURIComponent(s)}`),setTimeout(()=>{window.dispatchEvent(new Event("voice-filter-applied")),window.scrollTo({top:0,behavior:"smooth"})},150),setTimeout(()=>{window.dispatchEvent(new Event("voice-filter-applied"))},600);try{const t=await fetch("/api/equipment");if(t.ok){let i=(await t.json()).filter(o=>o.category===s&&(!o.status||o.status==="active"||o.status==="Available"||o.status==="Coming In"));i.sort((o,r)=>(parseFloat(r.price)||0)-(parseFloat(o.price)||0));const a=i.slice(0,10).map(o=>({id:o.id,name:o.name,year:o.year,make:o.make,model:o.model,type:o.type,price:o.price?`$${Number(o.price).toLocaleString()}`:"Call for price",length:o.length,location:o.location,condition:o.condition,composition:o.composition}));return JSON.stringify({navigated:!0,category:s,totalResults:i.length,topResults:a,instruction:"Results are loaded. CRITICAL: The topResults list order does NOT match what the customer sees on screen. Do NOT describe any trailer's specs or price from this list. QUALIFY the customer first: 'Got [count] trailers here â any specific specs you're looking for?' When ready to present a trailer, use highlight_listing FIRST â it returns the real details. ONLY describe a trailer AFTER highlight_listing confirms it's on screen."})}}catch{}return JSON.stringify({navigated:!0,category:s,instruction:"Page is loaded. DO NOT immediately start describing trailers. QUALIFY the customer: 'Here are your results â anything specific you're looking for in terms of specs, budget, or year range?' WAIT for their answer before presenting options."})}if(l==="clear_filters"){w("Clearing filters...");const s=n.category||"Trailers";if(n.new_filters&&Object.keys(n.new_filters).length>0){const t=V.current||await le().catch(()=>null),e=ge(n.new_filters,t),i=new URLSearchParams;e.type&&i.set("type",e.type),e.make&&i.set("make",e.make),e.condition&&i.set("condition",e.condition),e.year_min&&i.set("year_min",String(e.year_min)),e.year_max&&i.set("year_max",String(e.year_max)),e.price_min&&i.set("price_min",String(e.price_min)),e.price_max&&i.set("price_max",String(e.price_max)
1),e.length&&i.set("length",e.length),e.location&&i.set("location",e.location),e.composition&&i.set("composition",e.composition),e.tire_size&&i.set("tire_size",e.tire_size),e.axle_type&&i.set("axle_type",e.axle_type),e.rear_axles&&i.set("rear_axles",e.rear_axles),e.search&&i.set("search",e.search),e.keywords&&i.set("keywords",e.keywords),e.sort&&i.set("sort",e.sort),e.exclude_make&&i.set("exclude_make",e.exclude_make),e.exclude_ids&&i.set("exclude_ids",e.exclude_ids);const a=i.toString();D(`/category/${encodeURIComponent(s)}${a?`?${a}`:""}`)}else D(`/category/${encodeURIComponent(s)}`);return setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),150),setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),600),JSON.stringify({cleared:!0,category:s,newFiltersApplied:!!n.new_filters})}if(l==="add_keyword"){let s=(n.keyword||"").trim().toLowerCase();if(!s)return JSON.stringify({error:"No keyword provided"});const e={extendable:"extend",extensible:"extend",stretchable:"stretch",telescoping:"extend",expandable:"extend","s-t-r-e-t-c-h":"stretch",detachable:"detach",removable:"detach","air suspension":"air ride",liftgate:"lift gate"}[s];e&&(s=e),w(`Adding keyword: ${s}...`);const i=new URL(window.location.href),a=i.searchParams.get("keywords"),o=a?a.split(",").map(T=>T.trim()).filter(Boolean):[];o.includes(s)||o.push(s),i.searchParams.set("keywords",o.join(","));
1const r=i.pathname.includes("/category/")?decodeURIComponent(i.pathname.split("/category/")[1]):"Trailers",u=new URLSearchParams(i.search);return D(`/category/${encodeURIComponent(r)}?${u.toString()}`),setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),150),setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),600),JSON.stringify({added:s,activeKeywords:o})}if(l==="remove_keyword"){const s=(n.keyword||"").trim().toLowerCase();if(!s)return JSON.stringify({error:"No keyword provided"});w(`Removing keyword: ${s}...`);const t=new URL(window.location.href),e=t.searchParams.get("keywords"),i=e?e.split(",").map(r=>r.trim()).filter(Boolean).filter(r=>r.toLowerCase()!==s):[];i.length>0?t.searchParams.set("keywords",i.join(",")):t.searchParams.delete("keywords");const a=t.pathname.includes("/category/")?decodeURIComponent(t.pathname.split("/category/")[1]):"Trailers",o=new URLSearchParams(t.search);return D(`/category/${encodeURIComponent(a)}?${o.toString()}`),setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),150),setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),600),JSON.stringify({removed:s,activeKeywords:i})}if(l==="navigate_to_page"){const s=(n.page||"").toLowerCase().trim();let e={home:"/",financing:"/financing","sell-trade":"/help-me-sell-my-equipment",sell:"/help-me-sell-my-equipment",trade:"/help-me-sell-my-equipment","sell-my-equipment":"/help-me-sell-my-equipment","list-my-equipment":"/help-me-sell-my-equipment",faq:"/faq",cart:"/cart",favorites:"/favorites",rewards:"/rewards",calculators:"/calculators","list-equipment":"/help-me-sell-my-equipment","my-listings":"/my-listings",messages:"/messages",dashboard:"/dashboard",account:"/account",documents:"/documents",fleet:"/fleet",loads:"/loads",privacy:"/privacy",terms:"/terms"}[s]||n.path||"/";return(e==="/sell-trade"||e==="/sell-trade/"||e.startsWith("/sell-trade?"))&&(e="/help-me-sell-my-equipment"),e==="/catalog"?JSON.stringify({error:"Use filter_equipment instead of navigating to catalog"}):(w(`Navigating to ${s||e}...`),D(e),n.scroll_to&&setTimeout(()=>{const i=document.getElementById(n.scroll_to)||document.querySelector(n.scroll_to);i&&i.scrollIntoView({behavior:"smooth",block:"start"})},500),JSON.stringify({navigated:!0,page:e}))}if(l==="view_equipment"){const s=n.equipment_id;if(!s)return JSON.stringify({error:"Equipment ID required"});w("Opening equipment details...");const t=fetch(`/api/equipment/${s}`);D(`/equipment/${s}`),setTimeout(()=>window.scrollTo({top:0,behavior:"smooth"}),150);try{const e=await t;if(e.ok){const i=await e.json(),a=[i.imageUrl,...i.additionalImages||[]].filter(Boolean).length,o={id:i.id,name:i.name,year:i.year,make:i.make,model:i.model,type:i.type,price:i.price?`$${Number(i.price).toLocaleString()}`:"Call for price",condition:i.condition,length:i.length,location:i.location,composition:i.composition,axleType:i.axleType,tireSize:i.tireSize,suspension:i.suspension,brakeType:i.brakeType,floorType:i.floorType,numRearAxles:i.numRearAxles,weight:i.weight,width:i.width,height:i.height,ramp:i.ramp,warranty:i.warranty,vin:i.vin,videoUrl:i.videoUrl,photoCount:a};i.description&&(o.description=String(i.description).slice(0,500)),i.specifications&&(o.specifications=String(i.specifications).slice(0,800));const r=[];(i.condition==="new"||i.condition==="New")&&r.push("Brand new unit"),i.videoUrl&&r.push("Video walkthrough available on the page"),i.composition==="Aluminum"&&r.push("All-aluminum build â lightweight and rust-resistant"),i.ramp&&r.push(`Equipped with ${i.ramp} ramps`),i.warranty&&r.push(`Warranty: ${i.warranty}`),i.previousPrice&&Number(i.previousPrice)>Number(i.price)&&r.push(`Price reduced from $${Number(i.previousPrice).toLocaleString()}`);const u=(T=0)=>{const b=window.__voicePhotoControl;if(b&&a>1){b.startSlideshow(2500);return}T<10&&setTimeout(()=>u(T+1),500)};return setTimeout(()=>u(),800),JSON.stringify({navigated:!0,equipmentId:s,equipmentDetails:o,sellingPoints:r,photosAutoPlaying:a>1,instruction:"You are now showing this listing. Photos are cycling automatically every 2.5 seconds. Keep your pitch SHORT â 2-3 punchy sentences about top features, then PAUSE and let the customer react. Don't list every spec. Don't mention photo numbers. Ask ONE question: 'What do you think?' or 'Want a quote on this one?'"})}}catch{}return JSON.stringify({navigated:!0,equipmentId:s})}if(l==="control_photos"){const s=window.__voicePhotoControl;if(!s)return JSON.stringify({error:"Not on an equipment detail page"});const t=n.action,e=s.getCount(),i=()=>s.getCurrent()%e+1;
1if(t==="next")return s.next(),JSON.stringify({done:!0,action:"next",photo:i()+" of "+e});if(t==="previous")return s.prev(),JSON.stringify({done:!0,action:"previous",photo:i()+" of "+e});if(t==="start_slideshow"){const a=n.speed==="fast"?1e3:2500;return s.startSlideshow(a),JSON.stringify({done:!0,action:"slideshow_started",speed:n.speed==="fast"?"fast (1s)":"normal (2.5s)"})}if(t==="stop_slideshow")return s.stopSlideshow(),JSON.stringify({done:!0,action:"slideshow_stopped",photo:i()+" of "+e});if(t==="go_to"){const a=Math.max(0,Math.min(n.index||0,e-1));return s.goTo(a),JSON.stringify({done:!0,action:"jumped",photo:a+1+" of "+e})}return JSON.stringify({totalPhotos:e,currentPhoto:i()+" of "+e})}if(l==="get_filtered_results"){w("Looking at results...");try{const s=await fetch("/api/equipment");if(s.ok){const t=await s.json(),e=new URL(window.location.href),i=e.pathname.includes("/category/")?decodeURIComponent(e.pathname.split("/category/")[1]):"Trailers";let a=t.filter(m=>m.category===i&&(!m.status||m.status==="active"||m.status==="Available"||m.status==="Coming In"));const o=e.searchParams.get("type"),r=e.searchParams.get("make"),u=e.searchParams.get("condition"),T=e.searchParams.get("year_min"),b=e.searchParams.get("year_max"),E=e.searchParams.get("price_min"),x=e.searchParams.get("price_max"),f=e.searchParams.get("length"),A=e.searchParams.get("composition");if(o){const m=o.split(",").map(d=>d.trim().toLowerCase());a=a.filter(d=>m.includes((d.type||"").toLowerCase()))}if(r){const m=r.split(",").map(d=>d.trim().toLowerCase());a=a.filter(d=>m.includes((d.make||"").toLowerCase()))}if(u&&(a=a.filter(m=>(m.condition||"").toLowerCase()===u.toLowerCase())),T&&(a=a.filter(m=>parseInt(m.year)>=parseInt(T))),b&&(a=a.filter(m=>parseInt(m.year)<=parseInt(b))),E&&(a=a.filter(m=>parseFloat(m.price)>=parseFloat(E))),x&&(a=a.filter(m=>parseFloat(m.price)<=parseFloat(x))),f&&(a=a.filter(m=>(m.length||"").toLowerCase()===f.toLowerCase())),A){const m=A.split(",").map(d=>d.trim().toLowerCase());a=a.filter(d=>m.includes((d.composition||"").toLowerCase()))}const v=e.searchParams.get("exclude_make");if(v){const m=v.split(",").map(d=>d.trim().toLowerCase());a=a.filter(d=>!m.includes((d.make||"").toLowerCase()))}const h=e.searchParams.get("exclude_ids");if(h){const m=h.split(",").map(d=>d.trim()).filter(Boolean);a=a.filter(d=>!m.includes(String(d.id)))}const N=e.searchParams.get("location"),I=e.searchParams.get("tire_size"),p=e.searchParams.get("axle_type"),g=e.searchParams.get("search"),q=e.searchParams.get("keywords"),k=e.searchParams.get("sort");if(N){const m=N.split(",").map(d=>d.trim().toUpperCase());a=a.filter(d=>{const G=(d.location||"").toUpperCase();return m.some(ue=>G.includes(ue))})}if(I){const m=I.split(",").map(d=>d.trim().toLowerCase());a=a.filter(d=>m.includes((d.tireSize||"").toLowerCase()))}if(p){const m=p.split(",").map(d=>d.trim().toLowerCase());a=a.filter(d=>m.includes((d.axleType||"").toLowerCase()))}if(g){const m=g.toLowerCase();a=a.filter(d=>[d.name,d.type,d.make,d.model,d.description,d.specifications].filter(Boolean).join(" ").toLowerCase().includes(m))}if(q){const m=q.split(",").map(d=>d.trim().toLowerCase()).filter(Boolean);a=a.filter(d=>{const G=[d.name,d.type,d.make,d.model,d.description,d.specifications].filter(Boolean).join(" ").toLowerCase();return m.every(ue=>G.includes(ue))})}k==="price-asc"?a.sort((m,d)=>(parseFloat(m.price)||0)-(parseFloat(d.price)||0)):k==="price-desc"?a.sort((m,d)=>(parseFloat(d.price)||0)-(parseFloat(m.price)||0)):k==="newest"?a.sort((m,d)=>(parseInt(d.year)||0)-(parseInt(m.year)||0)):k==="oldest"&&a.sort((m,d)=>(parseInt(m.year)||0)-(parseInt(d.year)||0));const O=n.limit||10,F=a.slice(0,O).map(m=>({id:m.id,name:m.name,year:m.year,make:m.make,model:m.model,type:m.type,price:m.price?`$${Number(m.price).toLocaleString()}`:"Call for price",length:m.length,location:m.location,condition:m.condition,composition:m.composition,axleType:m.axleType,tireSize:m.tireSize,suspension:m.suspension}));return JSON.stringify({totalResults:a.length,showing:F.length,results:F,instruction:"You have the data. If you haven't qual
1ified the customer yet (specs, budget, year range), do that first. If already qualified, present the best 2-3 targeted matches and ask about next steps (quote, shipping, titling, financing)."})}}catch{}return JSON.stringify({error:"Could not fetch results"})}if(l==="scroll_page"){z();const s=n.direction||"down",t=n.amount||"medium",i={small:300,medium:600,large:window.innerHeight}[t]||600;if(s==="top")return window.scrollTo({top:0,behavior:"smooth"}),JSON.stringify({scrolled:!0,direction:"top"});if(s==="bottom")return window.scrollTo({top:document.body.scrollHeight,behavior:"smooth"}),JSON.stringify({scrolled:!0,direction:"bottom"});const a=s==="up"?-i:i;window.scrollBy({top:a,behavior:"smooth"});const o=window.scrollY+window.innerHeight>=document.body.scrollHeight-100,r=window.scrollY<=50;return JSON.stringify({scrolled:!0,direction:s,amount:t,atTop:r,atBottom:o,hint:o?"Reached bottom of page â no more content below.":r?"At top of page.":"More content available."})}if(l==="highlight_listing"){z();const s=String(n.equipment_id||"").trim();if(!s)return JSON.stringify({error:"Equipment ID required"});w(`Highlighting listing #${s}...`);const t=()=>{let i=document.querySelector(`[data-equipment-id="${s}"]`);if(!i){const a=document.querySelectorAll("a[href]");for(const o of a){const r=o.getAttribute("href")||"";if(r===`/equipment/${s}`||r.endsWith(`/equipment/${s}`)){i=o.closest("[data-equipment-id]")||o;break}}}return i};let e=t();if(e||(await new Promise(i=>setTimeout(i,500)),e=t()),e||(await new Promise(i=>setTimeout(i,1e3)),e=t()),e){e.scrollIntoView({behavior:"smooth",block:"center"});const i=e;i.style.transition="box-shadow 0.3s, transform 0.3s",i.style.boxShadow="0 0 0 3px #3b82f6, 0 0 20px rgba(59,130,246,0.3)",i.style.transform="scale(1.02)",i.style.zIndex="10",i.style.position="relative",setTimeout(()=>{i.style.boxShadow="",i.style.transform="",i.style.zIndex="",i.style.position=""},3e3);try{const a=await fetch(`/api/equipment/${s}`);if(a.ok){const o=await a.json();return JSON.stringify({highlighted:!0,equipmentId:s,equipment:{id:o.id,name:o.name,year:o.year,make:o.make,model:o.model,type:o.type,price:o.price?`$${Number(o.price).toLocaleString()}`:"Call for price",length:o.length,location:o.location,condition:o.condition,composition:o.composition,axleType:o.axleType,suspension:o.suspension},instruction:"This listing is now highlighted on screen with a blue glow. The customer can see THIS trailer. ONLY describe THIS trailer's specs â year, make, price, and key features. Do NOT mention any other trailer's specs."})}}catch{}return JSON.stringify({highlighted:!0,equipmentId:s,message:"Scrolled to and highlighted the listing on screen. The customer can see it now."})}return JSON.stringify({highlighted:!1,equipmentId:s,message:"Listing not visible on the current page. Try scrolling down or using view_equipment to navigate to it directly."})}if(l==="get_similar_equipment"){const s=n.equipment_id;if(!s)return JSON.stringify({error:"Equipment ID required"});w("Finding similar equipment...");try{const t=await fetch(`/api/equipment/${s}`);if(!t.ok)return JSON.stringify({error:"Equipment not found"});const e=await t.json(),i=await fetch("/api/equipment");if(!i.ok)return JSON.stringify({error:"Could not fetch inventory"});const o=(await i.json()).filter(r=>r.id!==s&&r.status==="active").map(r=>{let u=0;r.type===e.type&&(u+=30),r.make===e.make&&(u+=15),r.composition===e.composition&&(u+=10),r.length===e.length&&(u+=10),r.condition===e.condition&&(u+=5);const T=Math.abs((parseFloat(r.price)||0)-(parseFloat(e.price)||0)),b=parseFloat(e.price)||1;T/b<.2?u+=15:T/b<.4&&(u+=8);const E=Math.abs((parseInt(r.year)||0)-(parseInt(e.year)||0));return E<=2?u+=10:E<=4&&(u+=5),r.tireSize===e.tireSize&&(u+=5),{...r,similarityScore:u}}).filter(r=>r.similarityScore>=20).sort((r,u)=>u.similarityScore-r.similarityScore).slice(0,5).map(r=>({id:r.id,name:r.name,year:r.year,make:r.make,model:r.model,type:r.type,price:r.price?`$${Number(r.price).toLocaleString()}`:"Call for price",length:r.length,location:r.location,condition:r.condition,composition:r.composition,similarityScore:r.similarityScore}));return JSON.stringify({basedOn:`${e.year||""} ${e.make||""} ${e.model||""}`.trim(),similar:o,count:o.length,instruction:"Present these alternatives to the customer. Explain why each is similar and what makes them a good option."})}catch{return JSON.stringify({error:"Failed to find similar equipment"})}}if(l==="quick_sell_pitch"){const s=n.equipment_id;if(!s)return JSON.stringify({error:"Equipment ID required"});w("Building pitch...");try{const t=await fetch(`/api/equipment/${s}`);if(!t.ok)return JSON.stringify({error:"Equipment not found"});const e=await t.json(),i=e.imageCount||(e.images?e.images.split(",").length:0),a=!!e.videoUrl,o=[];e.length&&o.push(e.length),e.composition&&o.push(e.composition),e.tireSize&&o.push(`${e.tireSize} tires`),e.axleType&&o.push(`${e.axleType} axle`),e.suspension&&o.push(`${e.suspension} suspension`),e.rearAxles&&o.push(`${e.rearAxles} rear axle`);const r={id:e.id,name:`${e.year||""} ${e.make||""} ${e.model||""}
1`.trim(),price:e.price?`$${Number(e.price).toLocaleString()}`:"Call for price",condition:e.condition,location:e.location,keySpecs:o,photoCount:i,hasVideo:a,description:(e.description||"").slice(0,300),sellingPoints:[]},u=[];return i>=10&&u.push(`${i} detailed photos â well represented`),a&&u.push("Video walkthrough available"),e.condition==="new"&&u.push("Brand new unit"),e.composition==="Aluminum"&&u.push("All-aluminum â lightweight, no rust"),o.length>=3&&u.push(`Loaded with specs: ${o.slice(0,3).join(", ")}`),r.sellingPoints=u,JSON.stringify(r)}catch{return JSON.stringify({error:"Failed to build pitch"})}}if(l==="exclude_listing"){const s=String(n.id||"").trim();if(!s)return JSON.stringify({error:"No listing ID provided"});w(`Removing listing #${s}...`);const t=new URL(window.location.href),e=t.searchParams.get("exclude_ids"),i=e?e.split(",").map(r=>r.trim()).filter(Boolean):[];i.includes(s)||i.push(s),t.searchParams.set("exclude_ids",i.join(","));const a=t.pathname.includes("/category/")?decodeURIComponent(t.pathname.split("/category/")[1]):"Trailers",o=new URLSearchParams(t.search);return D(`/category/${encodeURIComponent(a)}?${o.toString()}`),setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),150),setTimeout(()=>window.dispatchEvent(new Event("voice-filter-applied")),600),JSON.stringify({excluded:!0,excludedId:s,totalExcluded:i.length,allExcludedIds:i})}if(l==="submit_contact_inquiry"){const{name:s,email:t,phone:e,message:i,equipment_id:a,equipment_name:o}=n;if(!s||!t)return JSON.stringify({error:"Name and email are required"});w("Submitting inquiry...");try{if(!(await fetch("/api/contact-inquiry",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:s,email:t,phone:e||"",message:i||"Inquiry submitted via voice agent",equipmentName:o||"",equipmentId:a||null,source:"Voice Agent",sessionId:sessionStorage.getItem("itag_analytics_sid")||void 0,_hp:""})})).ok)throw new Error("Failed");return JSON.stringify({submitted:!0,message:"Contact inquiry submitted successfully. The team will follow up."})}catch{return JSON.stringify({submitted:!1,message:"Failed to submit inquiry. Suggest calling 800-895-1845."})}}if(l==="submit_freight_quote"){const{name:s,email:t,phone:e,city:i,state:a,zip:o,equipment_id:r,equipment_name:u}=n;if(!s||!t||!i||!a)return JSON.stringify({error:"Name, email, city, and state are required"});w("Requesting freight quote...");try{if(!(await fetch("/api/freight-quote",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:s,email:t,phone:e||"",city:i,state:a,zip:o||"",equipmentName:u||"",equipmentId:r||null,sessionId:sessionStorage.getItem("itag_analytics_sid")||void 0,_hp:""})})).ok)throw new Error("Failed");return JSON.stringify({submitted:!0,message:"Freight quote request submitted. The shipping team will follow up with pricing."})}catch{return JSON.stringify({submitted:!1,message:"Failed to submit freight quote. Suggest calling 800-895-1845."})}}if(l==="submit_financing_application"){const{first_name:s,last_name:t,email:e,phone:i,business_name:a,equipment_cost:o,equipment_type:r,years_in_business:u}=n;if(!s||!t||!e||!i)return JSON.stringify({error:"First name, last name, email, and phone are required"});w("Submitting financing application...");try{if(!(await fetch("/api/financing-inquiry",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({firstName:s,lastName:t,email:e,phone:i,businessName:a||"",equipmentCost:o||"",equipmentType:r||"",yearsInBusiness:u||"",sessionId:sessionStorage.getItem("itag_analytics_sid")||void 0,_hp:""})})).ok)throw new Error("Failed");return JSON.stringify({submitted:!0,message:"Financing application submitted! A financing specialist will contact them within 24 hours."})}catch{return JSON.stringify({submitted:!1,message:"Failed to submit financing application. Suggest calling 800-895-1845."})}}if(l==="submit_sell_trade"){const{first_name:s,last_name:t,email:e,phone:i,equipment_type:a,year:o,make:r,model:u,condition:T,mileage:b,details:E}=n;if(!s||!t||!e||!i)return JSON.stringify({error:"First name, last name, email, and ph
1one are required"});w("Submitting sell/trade request...");try{if(!(await fetch("/api/sell-trade",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({firstName:s,lastName:t,email:e,phone:i,equipmentType:a||"",year:o||"",make:r||"",model:u||"",condition:T||"",mileage:b||"",details:E||"",sessionId:sessionStorage.getItem("itag_analytics_sid")||void 0,_hp:""})})).ok)throw new Error("Failed");return JSON.stringify({submitted:!0,message:"Sell/trade request submitted! The team will review and respond within 24 hours."})}catch{return JSON.stringify({submitted:!1,message:"Failed to submit sell/trade request. Suggest calling 800-895-1845."})}}if(l==="add_to_cart"){const s=n.equipment_id;if(!s)return JSON.stringify({error:"Equipment ID required"});w("Adding to cart...");try{const t=await fetch(`/api/equipment/${s}`);if(!t.ok)return JSON.stringify({error:"Equipment not found"});const e=await t.json();window.dispatchEvent(new CustomEvent("voice-add-to-cart",{detail:{equipmentId:s,equipment:e}}));const i=e.name||`${e.year||""} ${e.make||""} ${e.model||""}`.trim();return JSON.stringify({added:!0,equipmentId:s,name:i,message:`${i} added to cart.`})}catch{return JSON.stringify({error:"Failed to add to cart"})}}if(l==="remove_from_cart"){const s=n.equipment_id;return s?(w("Removing from cart..."),window.dispatchEvent(new CustomEvent("voice-remove-from-cart",{detail:{equipmentId:s}})),JSON.stringify({removed:!0,equipmentId:s,message:"Item removed from cart."})):JSON.stringify({error:"Equipment ID required"})}if(l==="get_cart_items")return w("Checking cart..."),new Promise(s=>{const t=e=>{window.removeEventListener("voice-cart-items-response",t),s(JSON.stringify(e.detail))};window.addEventListener("voice-cart-items-response",t),window.dispatchEvent(new CustomEvent("voice-get-cart-items")),setTimeout(()=>{window.removeEventListener("voice-cart-items-response",t);try{const e=localStorage.getItem("itag-cart"),i=e?JSON.parse(e):[];s(JSON.stringify({items:i.map((a,o)=>({index:o,id:a.equipment.id,name:a.equipment.name||`${a.equipment.year||""} ${a.equipment.make||""} ${a.equipment.model||""}`.trim(),price:a.equipment.price,quantity:a.quantity})),count:i.length}))}catch{s(JSON.stringify({items:[],count:0}))}},300)});if(l==="open_quote_wizard"){w("Opening quote wizard..."),D("/cart");const s=t=>{window.dispatchEvent(new CustomEvent("voice-open-quote-wizard")),t<3&&setTimeout(()=>s(t+1),500)};return setTimeout(()=>s(0),300),JSON.stringify({opened:!0,message:"Quote wizard opened. The wizard has 5 steps: 1) Company Info, 2) Shipping Options, 3) Add-ons, 4) Terms & Conditions, 5) E-Sign. Use quote_wizard_action to fill fields and advance through steps."})}if(l==="quote_wizard_action"){const s=n.action,t=n.data||{};if(s==="fill_info")return w("Filling in company info..."),await new Promise(i=>{const a=o=>{window.removeEventListener("voice-quote-fill-result",a);const r=o.detail;r.missing&&r.missing.length>0?i(JSON.stringify({done:!0,fieldsSet:r.filled,stillMissing:r.missing,message:`Fields filled: ${r.filled.join(", ")}. Still missing: ${r.missing.join(", ")}. Fill these with fill_info before calling next_step.`})):i(JSON.stringify({done:!0,fieldsSet:r.filled,stillMissing:[],message:`All required fields are filled: ${r.filled.join(", ")}. You can now call next_step.`}))};window.addEventListener("voice-quote-fill-result",a),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"fill_info",data:t}})),setTimeout(()=>{window.removeEventListener("voice-quote-fill-result",a),i(JSON.stringify({done:!0,message:"Company info filled in. Fields updated: "+Object.keys(t).join(", ")+". Use get_status to verify before advancing."}))},500)});if(s==="next_step")return w("Moving to next step..."),await new Promise(i=>{const a=o=>{window.removeEventListener("voice-quote-fill-result",a);const r=o.detail;if(r.blocked)i(JSON.stringify({done:!1,blocked:!0,missingFields:r.missingFields||[],message:r.message}));else if(r.advanced){const u=["Company Info","Shipping","Add-ons","Terms","E-Sign"];i(JSON.stringify({done:!0,message:`Advanced to step ${r.toStep+1}: ${u[r.toStep]}.`}))}else i(JSON.stringify({done:!0,message:"Advanced to next step."}))};window.addEventListener("voice-quote-fill-result",a),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"next_step",data:{}}})),setTimeout(()=>{window.removeEventListener("voice-quote-fill-result",a),i(JSON.stringify({done:!0,message:"Step advancement attempted. Use get_status to verify current step."}))},500)});if(s==="prev_step")return w("Going back..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"prev_step",data:{}}})),JSON.stringify({done:!0,message:"Went back to previous step."});if(s==="go_to_step"){const e=t.step??0;return w(`Going to step ${e+1}...`),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"go_to_step",data:{step:e}}})),JSON.stringify({done:!0,message:`Jumped to step ${e+1}.`})}if(s==="set_shipping"){w("Setting shipping option..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"set_shipping",data:t}}));const e=t.type==="self"?"self-arrange":t.type==="load_out"?"Load Out":"Power Only";return JSON.stringify({done:!0,message:`Shipping set to ${e}${t.destCity?` delivering to ${t.destCity}, ${t.destState}`:""}.`})}return s==="skip_addons"?(w("Skipping add-ons..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"skip_addons",data:{}}})),JSON.stringify({done:!0,message:"Skipped add-ons, now on Terms & Conditions step."})):s==="set_rate"?(w("Adjusting shipping rate..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"set_rate",data:t}})),JSON.stringify({done:!0,message:`Shipping rate slider set to ${t.rate}. The customer can see the rate updating on screen.`})):s==="toggle_addon"?(w("Toggling add-on..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"toggle_addon",data:t}})),JSON.stringify({done:!0,message:`Maine Registration Package toggled for item ${(t.itemIndex||0)+1}. It's $350/unit and includes 5-year registration, title, and hard metal plates.`})):s==="scroll_terms"?(w("Scrolling terms..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"scroll_terms",data:{}}})),JSON.stringify({done:!0,message:"Scrolled to bottom of terms. The checkbox to accept all 8 terms is now visible."})):s==="accept_terms"?(w("Accepting terms..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"accept_terms",data:{}}})),JSON.stringify({done:!0,message:"Terms & conditions accepted â checkbox is checked."})):s==="unaccept_terms"?(w("Unchecking terms..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"unaccept_terms",data:{}}})),JSON.stringify({done:!0,message:"Terms unchecked."})):s==="set_signer"?(w("Setting signer name..."),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"set_signer",data:{name:t.name}}})),JSON.stringify({done:!0,message:`Signer name set to "${t.name}". Customer must click the Sign & Generate Quote button themselves for legal compliance.`})):s==="get_status"?new Promise(e=>{const i=a=>{window.removeEventListener("voice-quote-status",i),e(JSON.stringify(a.detail))};window.addEventListener("voice-quote-status",i),window.dispatchEvent(new CustomEvent("voice-quote-action",{detail:{action:"get_status",data:{}}})),setTimeout(()=>{window.removeEventListener("voice-quote-status",i),e(JSON.stringify({error:"Could not get wizard status. Quote wizard may not be open."}))},500)}):JSON.stringify({error:`Unknown quote wizard action: ${s}`})}if(l==="send_lead_summary"){w("Sending lead info to sales team...");try{const s=await fetch("/api/voice/lead-summary",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n.name,email:n.email,phone:n.phone,company:n.company,lookingFor:n.lookingFor,equipmentViewed:n.equipmentViewed||[],conversationNotes:n.conversationNotes,actionsCompleted:n.actionsCompleted||[]})});if(!s.ok){const e=await s.json().catch(()=>({}));return JSON.stringify({sent:!1,message:e.message||"Failed to send lead summary"})}const t=await s.json();return JSON.stringify({sent:!0,message:t.message||"Lead summary sent to ITAG sales team â they'll follow up!"})}catch{return JSON.stringify({sent:!1,message:"Could not send lead summary, but the conversation info is noted."})}}if(l==="send_quote_email"){w("Creating and emailing quote...");try{const s=await fetch("/api/voice/send-quote",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({companyName:n.companyName,email:n.email,recipientName:n.recipientName,streetAddress:n.streetAddress,city:n.city,state:n.state,zipCode:n.zipCode,items:n.items,shippingRequested:n.shippingRequested,shippingType:n.shippingType,shippingDestCity:n.shippingDestCity,shippingDestState:n.shippingDestState,shippingEstCost:n.shippingEstCost,maineRegistration:n.maineRegistration})});if(!s.ok){const e=await s.json().catch(()=>({}));return JSON.stringify({sent:!1,message:e.message||"Failed to send quote"})}const t=await s.json();return JSON.stringify({sent:!0,quoteId:t.quoteId,message:t.message||`Quote emailed to ${n.email}!`})}catch{return JSON.stringify({sent:!1,message:"Could not create and send the quote."})}}if(l==="email_equipment_list"){w("Emailing equipment list...");try{const s=await fetch("/api/voice/email-equipment-list",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({recipientEmail:n.recipientEmail,recipientName:n.recipientName,equipmentIds:n.equipmentIds,personalMessage:n.personalMessage})});if(!s.ok){const e=await s.json().catch(()=>({}));return JSON.stringify({sent:!1,message:e.message||"Failed to send equipment list"})}const t=await s.json();return JSON.stringify({sent:!0,count:t.count,message:t.message||`Equipment list emailed to ${n.recipientEmail}!`})}catch{return JSON.stringify({sent:!1,message:"Could not send the equipment list email."})}}if(l==="ask_admin"){const s=n.context?`${n.question} (Context: ${n.context})`:n.question;try{return await fetch("/api/voice-log-question",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({question:s,source:"agent"})}),JSON.stringify({submitted:!0,message:"Question sent to admin team. They will review and add an answer so you can help future customers better."})}catch{return JSON.stringify({submitted:!1,message:"Failed to send question, but you can still help the customer by suggesting they call 800-895-1845."})}}if(l==="save_kb_answer")try{const s=await fetch("/api/admin/voice-knowledge?status=unanswered");if(!s.ok)return JSON.stringify({saved:!1,message:"Couldn't access knowledge base"});const e=(await s.json()).find(i=>i.questionText.toLowerCase().includes(n.question.toLowerCase().slice(0,30))||n.question.toLowerCase().includes(i.questionText.toLowerCase().slice(0,30)));return e&&(await fetch(`/api/admin/voice-knowledge/${e.id}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({answer:n.answer,status:"answered"})})).ok?JSON.stringify({saved:!0,questionId:e.id,message:"Answer saved! You'll use this to help future customers."}):JSON.stringify({saved:!1,message:"Couldn't find a matching question to update, but the admin's answer is noted."})}catch{return JSON.stringify({saved:!1,message:"Failed to save the answer, but you heard the admin's guidance."})}return JSON.stringify({error:"Unknown tool"})},[D]),Ee=async()=>{try{const l=await fetch("/api/admin/voice-knowledge?status=answered");return l.ok?await l.json()||[]:[]}catch{return[]}},Ie=async()=>{try{const l=await fetch("/api/admin/voice-knowledge?status=unanswered");return l.ok?(await l.json()||[]).slice(0,10):[]}catch{return[]}},oe=S.useCallback(async()=>{L("connecting"),w("Connecting...");try{const l=(R==null?void 0:R.isAdmin)===!0,[n,c,s]=await Promise.all([le(),Ee(),l?Ie():Promise.resolve([])]),t=window.AudioContext||window.webkitAudioContext,e=/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||/iPad|iPhone|iPod/.test(navigator.userAgent);P.current=e?new t:new t({sampleRate:24e3}),P.current.state==="suspended"&&await P.current.resume();const i=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0}});j.current=i;const a=window.location.protocol==="https:"?"wss:":"ws:",o=new URLSearchParams;R!=null&&R.id&&o.set("userId",String(R.id)),R!=null&&R.username&&o.set("userName",R.username),R!=null&&R.isAdmin&&o.set("isAdmin","true");const r=o.toString(),u=new WebSocket(`${a}//${window.location.host}/api/voice-ws${r?`?${r}`:""}`);Z.current=u,u.onopen=()=>{};const T=()=>{u.send(JSON.stringify({type:"session.update",session:{voice:"Sal",instructions:_e(n,c,R,s),input_audio_transcription:{model:"grok-2-public"},turn_detection:{type:"server_vad",threshold:.5,prefix_padding_ms:300,silence_duration_ms:700},audio:{input:{format:{type:"audio/pcm",rate:24e3}},output:{format:{type:"audio/pcm",rate:24e3}}}
1,tools:[{type:"function",name:"filter_equipment",description:"Navigate the customer to the inventory page with specific filters applied AND get back matching results (up to 10 items with IDs, names, prices, specs). Use this when the customer describes what equipment they want. IMPORTANT: The system AUTOMATICALLY MERGES new filters with existing ones. Only pass the filters the customer is ADDING or CHANGING â do NOT re-send filters that are already active. For example, if they're already viewing flatbed trailers and say '2018 or newer', just pass year_min=2018 â do NOT re-pass type. After calling this, DO NOT immediately start describing trailers. QUALIFY the customer first â ask about specific specs they want, then budget/year range, THEN present targeted options. All parameters are optional â only include the ones relevant to the customer's request.",parameters:{type:"object",properties:{category:{type:"string",description:"Main category: 'Trailers' or 'Heavy Equipment'. Default is 'Trailers'."},type:{type:"string",description:"Equipment type. Can be a single type or COMMA-SEPARATED for multiple types (e.g. 'Flatbed Trailer,Drop Deck Trailer' to show both). Options: 'Flatbed Trailer', 'Drop Deck Trailer', 'Dry Van Trailer', 'Lowboy Trailer', 'Reefer Trailer', 'Gooseneck Trailer', 'Dump Trailer', 'Conestoga Trailer', 'Chassis Trailer', 'Double Drop Trailer', 'Car Hauler Trailer', 'Traveling Axle Trailer', 'Beavertail Trailer', 'Container Trailer', 'Race Car Trailer', 'Belly Dump Trailers', 'Hopper Trailer', 'Log Trailers', 'Dozer'. Must match exactly. ALWAYS use this filter for equipment type requests â NEVER use keyword search or add_keyword for type names."},make:{type:"string",description:"Manufacturer/brand. Can be a single make or COMMA-SEPARATED for multiple makes (e.g. 'MAC Trailer,Reitnouer' to show both). Common ones: 'Fontaine', 'Great Dane', 'Wabash', 'MAC Trailer', 'Landoll', 'Doonan', 'Reitnouer', 'Wilson Trailer', 'Talbert', 'XL Specialized', 'Transcraft', 'Manac', 'Benson', 'Dorsey', 'East', 'Stoughton', 'Vanguard', 'Timpte', 'Load King', 'Witzco Challenger', 'PJ Trailers', 'Big Tex'. Must match exactly from available makes. ALWAYS use this filter for manufacturer requests â NEVER use keyword search for manufacturer names."},exclude_make:{type:"string",description:"Manufacturer(s) to EXCLUDE from results. Comma-separated for multiple. Use when customer says 'I don't like X brand', 'no Benson', 'remove X brand', 'skip X', 'not X'. Example: exclude_make: 'Benson' to remove all Benson trailers from results. Can be combined with other filters. Must use exact make names."},exclude_ids:{type:"string",description:"Listing ID(s) to EXCLUDE from results. Comma-separated for multiple. Use when customer says 'remove that one', 'I don't want that trailer', 'skip that listing', 'take that one off'. You MUST know the specific listing ID(s) â get them from filter results or get_filtered_results first. Example: exclude_ids: '123' or exclude_ids: '123,456'."},condition:{type:"string",description:"'new' or 'used'"},year_min:{type:"number",description:"Minimum year, e.g. 2020"},year_max:{type:"number",description:"Maximum year, e.g. 2024"},price_min:{type:"number",description:"Minimum price in dollars, e.g. 10000"},price_max:{type:"number",description:"Maximum price in dollars, e.g. 50000"},length:{type:"string",description:"Trailer length with no space, e.g. '48ft', '53ft', '40ft'"},location:{type:"string",description:"State abbreviation(s) for location. Single state: 'TX'. Multiple states: 'TX,NJ,GA'. Regions: 'southeast' maps to AL,FL,GA,MS,NC,SC,TN,VA; 'northeast' maps to CT,DE,MA,MD,ME,NH,NJ,NY,PA,RI,VT; 'midwest' maps to IA,IL,IN,KS,MI,MN,MO,ND,NE,OH,SD,WI; 'southwest' maps to AZ,NM,OK,TX; 'west' maps to CA,CO,ID,MT,NV,OR,UT,WA,WY"},composition:{type:"string",description:"Material composition: 'Aluminum', 'Steel', 'Combination', or 'Plate / Composite'. Use 'Aluminum' for all-aluminum trailers."},tire_size:{type:"string",description:"Tire size(s). Comma-separated for multiple sizes (like multi-state). SMART TIRE MAPPING: When customer says '22.5 tires' for step deck/drop deck â use '22.5,255/70R22.5'. For flatbed + 22.5 â use '295/75R22.5,22.5,11R24.5,285/75R24.5'. When customer says '17.5' or '175' â use ALL 17.5 sizes: '17.5,235/75R17.5,245/70R17.5,235/70R17.5,215/75R17.5'. When customer says '24.5' â use '11R24.5,285/75R24.5'. When customer says '19.5' â use '19.5,245/70R19.5,265/70R19.5'. Available sizes from inventory are provided in context. Must match exactly per size."},axle_type:{type:"string",description:"Axle configuration. Values: 'Fixed Spread', 'Fixed', 'Rear Sliding Axle', 'Tandem Sliding', 'Traveling Axles', 'Spread Axle', 'Front Sliding'. C
1an be comma-separated for multiple (e.g. 'Rear Sliding Axle,Tandem Sliding,Front Sliding'). IMPORTANT: If user just says 'sliding' without specifying type, use ALL sliding options: 'Rear Sliding Axle,Tandem Sliding,Front Sliding' to get results on screen fast, then ASK which type they want."},rear_axles:{type:"string",description:"Number of rear axles. Comma-separated for multiple selections. Values: 'Single', 'Tandem', 'Tri', 'Tri-Axle', 'Quad', 'Quad Axle'. For '3 or more axles' use 'Tri,Tri-Axle,Quad,Quad Axle'. For '4+ axles' use 'Quad,Quad Axle'. Note: 'Tri' and 'Tri-Axle' both mean 3 axles; 'Quad' and 'Quad Axle' both mean 4 axles."},search:{type:"string",description:"Quick text search. For feature-specific filtering, prefer using add_keyword tool instead."},keywords:{type:"string",description:"Comma-separated keyword filters to apply (e.g. 'air ride,disc brakes'). Each becomes an individual removable chip. Prefer using add_keyword tool for adding keywords one at a time."},sort:{type:"string",description:"Sort order: 'price-asc', 'price-desc', 'newest', 'oldest', 'most-popular', 'date-added-newest', 'recently-reduced'. Default is featured. Use 'most-popular' for best deals/popular requests, 'recently-reduced' for price drops."}},required:[]}},{type:"function",name:"navigate_to_category",description:"Navigate the customer to a specific equipment category page. Returns matching results. After filtering, QUALIFY the customer first â ask about specific specs, budget, and year range before presenting options. Do NOT immediately start calling out trailers.",parameters:{type:"object",properties:{category:{type:"string",description:"Category name: 'Trailers' or 'Heavy Equipment'"}},required:["category"]}},{type:"function",name:"get_filtered_results",description:"Fetch the current filtered equipment results showing on the customer's screen. Returns up to 10 items with key details (ID, name, price, specs). Use this AFTER applying filters to see what's available and recommend specific trailers. Also use when you need to refresh your knowledge of what's currently showing.",parameters:{type:"object",properties:{limit:{type:"number",description:"Max results to return. Default 10."}},required:[]}},{type:"function",name:"clear_filters",description:"Clear all current filters and optionally apply new ones. Use this when the current search results aren't right, the customer wants to start over, refine their search, or try different filters. You can clear everything and immediately set new filters in one step.",parameters:{type:"object",properties:{category:{type:"string",description:"Category to navigate to after clearing. Default 'Trailers'."},new_filters:{type:"object",description:"Optional new filters to apply immediately after clearing. Same properties as filter_equipment (type, make, tire_size, search, etc.)."}},required:[]}},{type:"function",name:"add_keyword",description:"Add a keyword filter to narrow down results. Keywords are matched against equipment title, description, and specifications. Each keyword is applied independently â all must match. ONLY use for equipment FEATURES like 'air ride', 'disc brakes', 'ramps', 'aluminum floor', 'spread axle', 'lift gate', etc. NEVER use for equipment types (use type filter), manufacturers (use make filter), or any other value that has a dedicated filter parameter in filter_equipment.",parameters:{type:"object",properties:{keyword:{type:"string",description:"The keyword or phrase to add as a filter. Examples: 'air ride', 'disc brakes', 'ramps', 'aluminum floor', 'hendrickson', 'lift gate'"}},required:["keyword"]}},{type:"function",name:"remove_keyword",description:"Remove a specific keyword filter that was previously added. Use when the customer wants to broaden results or remove a keyword that's too restrictive.",parameters:{type:"object",properties:{keyword:{type:"string",description:"The keyword to remove. Must match exactly what was previously added."}},required:["keyword"]}},{type:"function",name:"navigate_to_page",description:"Navigate the customer to any page on the ITAG website. Use for financing applications, sell/trade, FAQ, cart, favorites, c
1alculators, rewards, or any other page.",parameters:{type:"object",properties:{page:{type:"string",description:"Page name: 'home', 'financing', 'sell-trade', 'faq', 'cart', 'favorites', 'rewards', 'calculators', 'list-equipment', 'my-listings', 'messages', 'dashboard', 'account', 'documents', 'fleet', 'loads', 'privacy', 'terms'"},path:{type:"string",description:"Direct path override if page name not in list (e.g. '/equipment/123')"},scroll_to:{type:"string",description:"Optional element ID or CSS selector to scroll to after navigation (e.g. 'financing-form', '#apply')"}},required:[]}},{type:"function",name:"view_equipment",description:"Navigate to a specific equipment listing page AND get full details back (specs, price, features, description, selling points). Photos will automatically start cycling through. Keep your pitch SHORT â 2-3 sentences on top features, then pause and listen. IMPORTANT: Only use this when the CUSTOMER explicitly asks to see a specific listing. Do NOT use this right after filter_equipment â stay on the results page and use highlight_listing instead.",parameters:{type:"object",properties:{equipment_id:{type:"number",description:"The equipment ID to view"}},required:["equipment_id"]}},{type:"function",name:"control_photos",description:"Control the photo gallery on the equipment detail page. Photos auto-cycle when you open a listing. Use for 'next photo', 'go back', 'stop', 'show me more pics', 'flip through faster'. ACT FIRST â call this tool immediately when the customer requests photo changes, don't talk first.",parameters:{type:"object",properties:{action:{type:"string",enum:["next","previous","start_slideshow","stop_slideshow","go_to"],description:"'next' = next photo, 'previous' = go back, 'start_slideshow' = auto-cycle, 'stop_slideshow' = pause on current, 'go_to' = jump to specific photo"},speed:{type:"string",enum:["normal","fast"],description:"Slideshow speed. 'normal' = 2.5s per photo (default), 'fast' = 1s per photo (for quick browsing). Only used with 'start_slideshow'."},index:{type:"number",description:"Photo index (0-based) when using 'go_to'"}},required:["action"]}},{type:"function",name:"scroll_page",description:"Scroll the page up or down. Use when the customer says 'scroll down', 'show me more', 'what else is there', 'scroll up', 'go back up', 'top of page'. Also useful for scrolling to see more results on the category page.",parameters:{type:"object",properties:{direction:{type:"string",enum:["up","down","top","bottom"],description:"'up' = scroll up one viewport, 'down' = scroll down one viewport, 'top' = scroll to top, 'bottom' = scroll to bottom"},amount:{type:"string",enum:["small","medium","large"],description:"'small' = 300px, 'medium' = 600px (default), 'large' = full viewport height. Only used with up/down."}},required:["direction"]}},{type:"function",name:"exclude_listing",description:"Remove a specific listing from the currently displayed results by its ID. Use when the customer says 'remove that one', 'I don't want that trailer', 'skip that one', 'take that off', 'hide that listing'. You MUST know the listing ID â get it from filter_equipment or get_filtered_results first. This preserves all current filters and just hides the specified listing(s).",parameters:{type:"object",properties:{id:{type:"string",description:"The listing ID to exclude. Get this from the results data (each result has an 'id' field)."}},required:["id"]}},{type:"function",name:"highlight_listing",description:"Scroll to and visually highlight a specific equipment listing on the category/inventory page. The card will get a blue glow and scale up briefly â the customer sees exactly which trailer you're talking about. Use this to SHOW the customer the trailer as you pitch it. This is your most powerful selling tool â point at the trailer on screen while you talk about it. Requires the listing to be on the current page.",parameters:{type:"object",properties:{equipment_id:{type:"number",description:"The equipment ID to highlight on screen"}},required:["equipment_id"]}},{type:"function",name:"get_similar_equipment",description:"Find equipment similar to a specific listing based on type, make, price range, year, specs. Returns up to 5 similar options with similarity scores. Use when the customer likes a listing but wants to see alternatives, when a listing is sold/unavailable, or to cross-sell: 'Let me show you a few others like this one.'",parameters:{type:"object",properties:{equipment_id:{type:"number",description:"The equipment ID to find similar items for"}},required:["equipment_id"]}},{type:"function",name:"quick_sell_pitch",description:"Get a ready-made sales pitch for a specific listing including key specs, selling points, photo count, video availability, and condition details. Use this to quickly prepare a compelling pitch before presenting a listing to the customer. Returns structured data you can use to build your pitch naturally.",parameters:{type:"object",properties:{equipment_id:{type:"number",description:"The equipment ID to build a pitch for"}},required:["equipment_id"]}},{type:"function",name:"submit_contact_inquiry",description:"Submit a contact/lead inquiry on behalf of the customer. Collects their info and sends it to the ITAG sales team. Use when customer wants to get more info, ask a question about specific equipment, or get in touch with sales. ALWAYS confirm the details with the customer before submitting.",parameters:{type:"object",properties:{name:{type:"string",description:"Customer's full name"},email:{type:"string",description:"Customer's email address"},phone:{type:"string",description:"Customer's phone number"},message:{type:"string",description:"Message or question from the customer"},equipment_id:{type:"number",description:"Equipment ID if inquiry is about specific equipment"},equipment_name:{type:"string",description:"Equipment name/title if inquiry is about specific equipment"}},required:["name","email"]}},{type:"function",name:"submit_freight_quote",description:"Request a shipping/freight quote for equipment delivery. Collects delivery location and sends to shipping team. Use when customer asks about shipping costs or delivery. ALWAYS confirm details before submitting.",parameters:{type:"object",properties:{name:{type:"string",description:"Customer's full name"},email:{type:"string",description:"Customer's email address"},phone:{type:"string",description:"Customer's phone number"},city:{type:"string",description:"Delivery city"},state:{type:"string",description:"Delivery state (full name or abbreviation)"},zip:{type:"string",description:"Delivery ZIP code"},equipment_id:{type:"number",description:"Equipment ID for the freight quote"},equipment_name:{type:"string",description:"Equipment name/title"}},required:["name","email","city","state"]}},{type:"function",name:"submit_financing_application",description:"Submit a financing/pre-qualification application. Collects business and contact info and sends to financing team. Use when customer wants to apply for financing or get pre-qualified. ALWAYS confirm details before submitting.",parameters:{type:"object",properties:{first_name:{type:"string",description:"Customer's first name"},last_name:{type:"string",description:"Customer's last name"},email:{type:"string",description:"Customer's email"},phone:{type:"string",description:"Customer's phone number"}
1,business_name:{type:"string",description:"Business/company name"},equipment_cost:{type:"string",description:"Approximate equipment cost"},equipment_type:{type:"string",description:"Type of equipment to finance"},years_in_business:{type:"string",description:"How long they've been in business"}},required:["first_name","last_name","email","phone"]}},{type:"function",name:"submit_sell_trade",description:"Submit a sell or trade-in request for equipment. Use when customer wants to sell their equipment or trade it in. ALWAYS confirm details before submitting.",parameters:{type:"object",properties:{first_name:{type:"string",description:"Customer's first name"},last_name:{type:"string",description:"Customer's last name"},email:{type:"string",description:"Customer's email"},phone:{type:"string",description:"Customer's phone number"},equipment_type:{type:"string",description:"Type of equipment (Tractor, Trailer, Pickup Truck, Semi Truck, Other)"},year:{type:"string",description:"Equipment year"},make:{type:"string",description:"Equipment make/brand"},model:{type:"string",description:"Equipment model"},condition:{type:"string",description:"Condition: Excellent, Good, Fair, or Poor"},mileage:{type:"string",description:"Mileage or hours"},details:{type:"string",description:"Additional details about the equipment"}},required:["first_name","last_name","email","phone"]}},{type:"function",name:"add_to_cart",description:"Add a specific piece of equipment to the customer's shopping cart. Use when customer wants to add something to their cart or start the quote/checkout process.",parameters:{type:"object",properties:{equipment_id:{type:"number",description:"The equipment ID to add to cart"}},required:["equipment_id"]}},{type:"function",name:"remove_from_cart",description:"Remove a specific piece of equipment from the customer's shopping cart. Use when customer wants to remove an item from the quote or cart.",parameters:{type:"object",properties:{equipment_id:{type:"number",description:"The equipment ID to remove from cart"}},required:["equipment_id"]}},{type:"function",name:"get_cart_items",description:"Get the list of items currently in the customer's shopping cart. Use to see what's in the cart before modifying it, or to confirm items for the customer.",parameters:{type:"object",properties:{},required:[]}},{type:"function",name:"open_quote_wizard",description:"Open the Get a Quote wizard. This navigates to the cart page and launches the multi-step quote wizard. Items must be in the cart first â use add_to_cart before this. After opening, use quote_wizard_action to fill fields and advance through steps.",parameters:{type:"object",properties:{},required:[]}},{type:"function",name:"quote_wizard_action",description:"Control the quote wizard â fill in fields, advance steps, set shipping rates, toggle add-ons, manage terms, sign. The wizard has 5 steps: Step 1 (Company Info) â Step 2 (Shipping) â Step 3 (Add-ons) â Step 4 (Terms) â Step 5 (E-Sign). Use open_quote_wizard first, then use this tool to walk through each step.",parameters:{type:"object",properties:{action:{type:"string",enum:["fill_info","next_step","prev_step","go_to_step","set_shipping","set_rate","toggle_addon","skip_addons","scroll_terms","accept_terms","unaccept_terms","set_signer","get_status"],description:"The wizard action to perform"},data:{type:"object",description:"Data for the action. fill_info: { companyName, streetAddress, city, state, zipCode, email, ccEmail } â RETURNS { fieldsSet, stillMissing } telling you exactly which fields are filled and which are still empty. Do NOT call next_step until stillMissing is empty. next_step: no data â WILL BLOCK and return { blocked: true, missingFields } if required fields are empty on the current step. go_to_step: { step: 0-4 }. set_shipping: { type: 'self'|'load_out'|'power_only', itemIndex: 0, destCity, destState }. set_rate: { rate: number, itemIndex: 0 } â for load_out: $0-$5000 (steps of $100), for power_only: $2-$5/mi (steps of $0.25). toggle_addon: { itemIndex: 0 } â toggles Maine Registration on/off. set_signer: { name: 'Full Legal Name' }. scroll_terms: no data. accept_terms/unaccept_terms/prev_step/get_status: no data.",properties:{companyName:{type:"string"},streetAddress:{type:"string"},city:{type:"string"},state:{type:"string"},zipCode:{type:"string"},email:{type:"string"},ccEmail:{type:"string"},step:{type:"number"},type:{type:"string"},itemIndex:{type:"number"},destCity:{type:"string"},destState:{type:"string"},name:{type:"string"},rate:{type:"number",description:"Shipping rate for set_rate action"}}}},required:["action"]}},{type:"function",name:"send_lead_summary",description:"Send a lead summary email to the ITAG sales team with all contact info and conversation details gathered during this conversation. Use this at the end of a conversation or whenever you've collected enough info to be useful for follow-up. Include everything you've learned: name, email, phone,
1company, what they're looking for, equipment they viewed, and a summary of the conversation. This is your most important tool â every conversation should ideally result in a lead being captured.",parameters:{type:"object",properties:{name:{type:"string",description:"Customer's name"},email:{type:"string",description:"Customer's email address"},phone:{type:"string",description:"Customer's phone number"},company:{type:"string",description:"Customer's company name"},lookingFor:{type:"string",description:"What the customer is looking for, e.g. '53ft reefer trailer under $30k'"},equipmentViewed:{type:"array",items:{type:"object",properties:{id:{type:"number"},name:{type:"string"},price:{type:"number"}}},description:"List of equipment the customer viewed or discussed"},conversationNotes:{type:"string",description:"Summary of the conversation â what was discussed, customer's concerns, timeline, budget, any other relevant info"},actionsCompleted:{type:"array",items:{type:"string"},description:"List of actions completed during the conversation, e.g. ['Added 2021 Utility reefer to cart', 'Submitted freight quote request', 'Viewed 5 trailers']"}}}},{type:"function",name:"send_quote_email",description:"Create an unsigned quote and email it directly to the customer (with a copy to ITAG sales team). Use this when the customer doesn't want to go through the e-sign process but still wants a quote emailed to them. This creates a real quote in the system with a PDF attachment. You need at minimum: company name (or customer name), email, and cart items with prices.",parameters:{type:"object",properties:{companyName:{type:"string",description:"Company or customer name for the quote"},email:{type:"string",description:"Email to send the quote to"},recipientName:{type:"string",description:"Recipient's name for the greeting"},streetAddress:{type:"string"},city:{type:"string"},state:{type:"string"},zipCode:{type:"string"},items:{type:"array",items:{type:"object",properties:{name:{type:"string"},price:{type:"number"},quantity:{type:"number"},equipmentId:{type:"number"}},required:["name","price"]},description:"Items to include in the quote"},shippingRequested:{type:"boolean"},shippingType:{type:"string"},shippingDestCity:{type:"string"},shippingDestState:{type:"string"},shippingEstCost:{type:"number"},maineRegistration:{type:"boolean"}},required:["companyName","email","items"]}},{type:"function",name:"email_equipment_list",description:"Email a curated list of equipment to the customer with clickable links to each listing. Use this when a customer wants the list of trailers you've been discussing sent to their email so they can review later. Great for keeping customers engaged after the conversation ends.",parameters:{type:"object",properties:{recipientEmail:{type:"string",description:"Customer's email address"},recipientName:{type:"string",description:"Customer's name"},equipmentIds:{type:"array",items:{type:"number"},description:"Array of equipment IDs to include in the email"},personalMessage:{type:"string",description:"Optional personal note to include, e.g. 'Based on what you told me, I think the 2022 Utility 3000R would be your best bet â great price and low miles!'"}},required:["recipientEmail","equipmentIds"]}},...l?[]:[{type:"function",name:"ask_admin",description:"Use this when a customer asks something you don't know the answer to, or when you encounter a question about ITAG's policies, processes, pricing, or operations that isn't covered in your knowledge base. This sends the question to the admin team so they can provide an answer that you'll learn for future conversations. You should still try to help the customer in the moment â tell them you'll make sure the team follows up, or suggest they call 800-895-1845.",parameters:{type:"object",properties:{question:{type:"string",description:"The question or topic the voice agent needs help with. Be specific and clear about what information is needed."},context:{type:"string",description:"Brief context about why this question came up, e.g. 'Customer asked about warranty on used trailers' or 'Customer wanted to know about international shipping'"}},required:["question"]}}],...l?[{type:"function",name:"save_kb_answer",description:"Save an answer that the admin just gave you for a question from the knowledge base. Use this when an admin tells you how to answer a question that came up in a previous customer conversation. This trains you to answer it c
1orrectly next time.",parameters:{type:"object",properties:{question:{type:"string",description:"The original question text, matching what's in the unanswered questions list"},answer:{type:"string",description:"The answer the admin provided. Write it clearly and concisely so you can use it to answer future customers."}},required:["question","answer"]}}]:[]]}}))};let b="",E={};u.onmessage=async x=>{var A;const f=JSON.parse(x.data);switch(f.type){case"proxy.connected":T();break;case"conversation.created":case"session.updated":if(L("connected"),w("Listening â tell me what you need"),!re.current&&(re.current=!0,xe(u),ie.current)){const v=ie.current;ie.current=void 0,setTimeout(()=>{u.send(JSON.stringify({type:"conversation.item.create",item:{type:"message",role:"user",content:[{type:"input_text",text:`[CONTEXT FROM TEXT CHAT] The customer just typed this in the text chat and I already navigated them to the filtered results page. The results are now showing on their screen. Start by acknowledging what's showing, then SLOWLY browse through the listings with them â use highlight_listing on the top results as you talk about them. Be a guide walking them through the lot. Their request was: "${v}"`}]}})),u.send(JSON.stringify({type:"response.create"})),Ae()},300)}break;case"input_audio_buffer.speech_started":L("listening"),w("Listening..."),me(),z(),u.readyState===WebSocket.OPEN&&u.send(JSON.stringify({type:"response.cancel"}));break;case"input_audio_buffer.speech_stopped":w("Thinking...");break;case"conversation.item.input_audio_transcription.completed":f.transcript&&(w(`You: "${f.transcript.slice(0,50)}${f.transcript.length>50?"...":""}"`),window.dispatchEvent(new CustomEvent("grok-sync-message",{detail:{role:"user",content:f.transcript,source:"voice"}}))),f.transcript&&f.transcript.length>10&&!l&&fetch("/api/voice-log-question",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({question:f.transcript})}).catch(()=>{});break;case"response.output_audio.delta":if(L("speaking"),f.delta){const v=atob(f.delta),h=new Uint8Array(v.length);for(let N=0;N<v.length;N++)h[N]=v.charCodeAt(N);he(h.buffer)}break;case"response.output_audio.done":break;case"response.cancelled":me(),L("listening"),w("Listening..."),b="";break;case"response.output_audio_transcript.delta":f.delta&&(b+=f.delta,w(b.slice(-80)));break;case"response.output_audio_transcript.done":b.trim()&&window.dispatchEvent(new CustomEvent("grok-sync-message",{detail:{role:"assistant",content:b.trim(),source:"voice"}})),b="";break;case"response.function_call_arguments.delta":f.call_id&&(E[f.call_id]||(E[f.call_id]={name:f.name||"",args:""}),E[f.call_id].args+=f.delta||"");break;case"response.function_call_arguments.done":if(f.call_id){const v=E[f.call_id]||{name:f.name,args:f.arguments};!v.name&&f.name&&(v.name=f.name),E[f.call_id]=v}break;case"response.done":{b="";const h=(((A=f.response)==null?void 0:A.output)||[]).filter(N=>N.type==="function_call");if(h.length>0){const N=[];for(const I of h){const p=I.call_id,g=E[p]||{name:I.name,args:I.arguments||"{}"};!g.name&&I.name&&(g.name=I.name);try{const q=JSON.parse(g.args||I.arguments||"{}"),k=await ye(g.name||I.name,q,p);N.push({callId:p,output:k})}catch(q){console.error("Tool call error:",q),N.push({callId:p,output:JSON.stringify({error:"Tool call failed"})})}delete E[p]}for(const I of N)u.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:I.callId,output:I.output}}));u.send(JSON.stringify({type:"response.create"}))}else L("connected"),w("Listening â tell me what you need");break}case"error":console.error("Voice agent error:",f),L("error"),w("Connection error â tap to retry");break}},u.onerror=()=>{L("error"),w("Connection error â tap to retry")},u.onclose=()=>{L("idle"),ne()}}catch(l){console.error("Voice connect error:",l),L("error"),W.current=!1;const n=(l==null?void 0:l.message)||(l==null?void 0:l.name)||"";n.includes("Permission")||n.includes("NotAllowedError")||n.includes("denied")||(l==null?void 0:l.name)==="NotAllowedError"?(w("Microphone access denied â tap for help"),X(!0)):n.includes("NotFoundError")||n.includes("DevicesNotFoundError")?w("No microphone found on this device"):n.includes("NotReadableError")||n.includes("TrackStartError")?w("Microphone is in use by another app"):n.includes("SecurityError")||n.includes("secure")?w("Requires HTTPS connection"):w("Connection failed â tap to retry"),ne()}},[ye,he]),we=S.useCallback((l,n,c)=>{if(n===c)return l;const s=n/c,t=Math.round(l.length/s),e=new Float32Array(t);for(let i=0;i<t;i++){const a=i*s,o=Math.floor(a),r=a-o;e[i]=o+1<l.length?l[o]*(1-r)+l[o+1]*r:l[o]||0}return e},[]),xe=S.useCallback(l=>{if(!j.current||!P.current)return;const n=P.current,c=n.sampleRate,s=n.createMediaStreamSource(j.current);te.current=s;const t=n.createAnalyser();
1t.fftSize=256,s.connect(t),H.current=t;const e=c===24e3?4096:8192,i=n.createScriptProcessor(e,1,1);ee.current=i,s.connect(i),i.connect(n.destination),i.onaudioprocess=o=>{if(l.readyState!==WebSocket.OPEN)return;const r=o.inputBuffer.getChannelData(0),u=c!==24e3?we(r,c,24e3):r,T=new Int16Array(u.length);for(let f=0;f<u.length;f++){const A=Math.max(-1,Math.min(1,u[f]));T[f]=A<0?A*32768:A*32767}const b=new Uint8Array(T.buffer);let E="";for(let f=0;f<b.length;f++)E+=String.fromCharCode(b[f]);const x=btoa(E);l.send(JSON.stringify({type:"input_audio_buffer.append",audio:x}))};const a=()=>{if(!H.current)return;const o=new Uint8Array(H.current.frequencyBinCount);H.current.getByteFrequencyData(o);const r=o.reduce((u,T)=>u+T,0)/o.length;pe(r/255),ae.current=requestAnimationFrame(a)};a()},[we]),ne=S.useCallback(()=>{ae.current&&cancelAnimationFrame(ae.current),ee.current&&(ee.current.disconnect(),ee.current=null),te.current&&(te.current.disconnect(),te.current=null),j.current&&(j.current.getTracks().forEach(l=>l.stop()),j.current=null),P.current&&(P.current.close(),P.current=null),H.current=null,B.current=[],J.current=!1,re.current=!1,pe(0)},[]),ce=S.useCallback(()=>{z(),Z.current&&(Z.current.close(),Z.current=null),ne(),L("idle"),w("")},[ne,z]),_e=(l,n,c,s)=>{var T,b,E,x,f,A;let t="",e="";if(n&&n.length>0){const v=n.filter(h=>h.answer&&h.status==="answered").slice(0,30).map(h=>`Q: ${h.questionText} 2A: ${h.answer}`).join(` 3 4`);v&&(e=` 5 6=== LEARNED Q&A (from previous customer interactions) === 7Use these pre-approved answers when customers ask similar questions: 8 9${v}`)}if(l){const v=Object.entries(l.types||{}).map(([g,q])=>`${g} (${q})`).join(", "),h=(l.makes||[]).join(", "),N=(l.lengths||[]).join(", "),I=(l.locations||[]).join(", "),p=(l.tireSizes||[]).join(", ");t=` 10 11=== CURRENT INVENTORY DATA === 12Total active listings: ${l.totalCount} 13Types available (with count): ${v} 14Makes available: ${h} 15Lengths available: ${N} 16Tire sizes in inventory: ${p} 17Year range: ${(T=l.yearRange)==null?void 0:T.min}-${(b=l.yearRange)==null?void 0:b.max} 18Price range: $${(x=(E=l.priceRange)==null?void 0:E.min)==null?void 0:x.toLocaleString()}-$${(A=(f=l.priceRange)==null?void 0:f.max)==null?void 0:A.toLocaleString()} 19Locations (state abbreviations): ${I}`}let i="";const a=window.location.pathname,o=a.includes("/category/"),r=o?null:be(a.replace(/^\//,""));if(o||r){const v=new URLSearchParams(window.location.search),h=[];o&&h.push(`Category: ${decodeURIComponent(a.split("/category/")[1])}`),r&&(h.push("Category: Trailers"),r.type&&h.push(`type: ${r.type}`),r.make&&h.push(`make: ${r.make}`),r.length&&h.push(`length: ${r.length}`),r.composition&&h.push(`composition: ${r.composition}`),r.axleType&&h.push(`axle_type: ${r.axleType}`),r.rearAxles&&h.push(`rear_axles: ${r.rearAxles}`)),v.forEach((N,I)=>h.push(`${I}: ${N}`)),h.length>0&&(i=` 20 21=== CURRENT ACTIVE FILTERS ON SCREEN === 22The customer is currently viewing equipment with these filters: ${h.join(", ")} 23When they ask to add more filters (like year, price, sort), ONLY pass the NEW filters â the system auto-merges with existing ones.`)}let u="";if(c){const v=c.isAdmin?"an ADMIN team member":"a registered customer";let h="";c.isAdmin&&s&&s.length>0&&(h=` 24 25=== QUESTIONS NEEDING YOUR HELP === 26These are questions from recent customer conversations that you didn't have good answers for. Since you're talking to an admin, naturally bring these up during the conversation to get the right answers. Don't dump them all at once â weave them in conversationally. For example: "Hey, by the way â a customer asked me about [topic] the other day and I wasn't sure how to answer. What's the best way to handle that?" 27 28${s.map((I,p)=>`${p+1}. "${I.questionText}" (asked ${I.frequency||1} time${(I.frequency||1)>1?"s":""}${I.source==="agent"?" â flagged by you":""})`).join(` 29`)} 30 31After the admin gives you an answer, acknowledge it and thank them. Their answers will be added to your knowledge base for future conversations.`),u=` 32 33=== CURRENT USER === 34You are speaking with ${c.firstName||c.username||"a user"}, who is ${v} at ITAG Equipment. 35${c.isAdmin?`- This person is part of the ITAG admin team. Treat them as a colleague, not a customer. 36- You can be more direct and technical with them. 37- Do NOT use the ask_admin tool when speaking with an admin â they ARE the admin team. 38- You can discuss internal operations, inventory details, and business matters openly. 39- If they ask you to do something or test something, comply helpfully. 40- Greet them casually by name, like a coworker: "Hey ${c.firstName||c.username}! What's going on?"${h}`:`- They have an account on the website. 41- Be warm, professional, and sales-oriented as usual.`}`}return`You are a friendly, professional sales assistant for ITAG Equipment. You are having a real-time voice conversation with ${c?c.firstName||c.username||"a user":"a customer"} browsing the ITAG Equipment website.${u}${i} 42 43=== LANGUAGE / MULTILINGUAL SUPPORT === 44- You are FULLY MULTILINGUAL. You can communicate in any language the customer speaks. 45- DETECT the customer's language from their FIRST message and respond entirely in that same language for the rest of the conversation. 46- If a customer switches languages mid-conversation, switch with them seamlessly. 47- Always maintain the same warm, professional sales tone regardless of language. Translate naturally â don't sound robotic or overly formal. 48- All equipment names, model numbers, and brand names stay in English (they're proper nouns). Everything else should be in the customer's language. 49- If you're unsure which language they prefer, default to English but mention that you can assist in their preferred language. 50 51=== CONVERSATION STYLE === 52- Be warm, natural and conversational. Keep responses SHORT â 1-2 sentences max unless answering a detailed question. 53- ${c?`Greet ${c.firstName||c.username} by name when the conversation starts.`:'Greet the customer briefly when the conversation starts. Something like "Hey! What can I help you find today?"'} 54- NEVER repeat yourself. If you already greeted them, don't greet again. If you already mentioned something, don't say it again. 55- Be casual and helpful, like a knowledgeable friend at a dealership. 56- Wait for the customer to speak before filtering. Don't filter without being asked. 57 58=== LISTENING & INTERRUPTION RULES (HIGHEST PRIORITY) === 59- LISTEN FIRST, then respond. Your #1 rule is to NEVER talk over the customer. 60- When you detect the customer is speaking or about to speak, STOP IMMEDIATELY. Mid-word, mid-sentence â doesn't matter. STOP. 61- Keep your responses SHORT â 1-2 sentences, then PAUSE and give them space to respond. Don't monologue. 62- After every statement you make, leave a brief silence. Let THEM fill it. If they don't respond after a beat, then continue. 63- When the customer gives you information, REPEAT IT BACK exactly to confirm. Don't paraphrase.
64- If you're not sure what they said, ASK: "Sorry, was that [what you heard]?" â don't guess. 65- Pay attention to corrections. If they correct you, acknowledge briefly and fix it: "Got it â [corrected value]." 66- Don't rush. A short pause before responding is better than getting it wrong. 67- NEVER repeat something you've already said. Track what you've told the customer and don't say it again. 68- If you catch yourself about to repeat information, stop and move on to something new. 69- When performing actions (navigating, filling forms, moving sliders), DO THE ACTION SILENTLY first, then give a brief 1-sentence confirmation. Don't narrate every click. 70 71=== EMAIL ADDRESS HANDLING â MOST CRITICAL SECTION === 72Getting the email right is your MOST IMPORTANT job. A wrong email = lost lead = lost sale. Treat this with extreme care. 73 74STEP-BY-STEP PROCESS: 751. Ask: "What's your email? You can spell it out for me." 762. SHUT UP. Do not talk. Wait for them to finish completely. 773. Process what you heard. Apply the PHONETIC CONFUSION rules below BEFORE repeating back. 784. Spell it back USING NATO PHONETICS for any ambiguous letters: "Let me confirm â G as in Golf, A, R, N, E, R, B as in Bravo, K as in Kilo, at me dot com. Is that right?" 795. If they say YES â enter it with fill_info. Done. 806. If they say NO or correct you â COMPLETELY DISCARD your previous version. Start fresh from their correction. Do NOT merge old + new. The correction IS the email now. 81 82PHONETIC CONFUSION â LETTERS THAT SOUND ALIKE OVER VOICE: 83Speech recognition commonly confuses these letter pairs. When you hear these, ALWAYS ask for clarification using "as in" words: 84 - B vs V vs D vs P: "Was that B as in Bravo, or V as in Victor?" 85 - M vs N: "M as in Mike, or N as in November?" 86 - S vs F: "S as in Sierra, or F as in Foxtrot?" 87 - T vs D: "T as in Tango, or D as in Delta?" 88 - E vs G vs C: Can sound identical. Ask: "Was that E as in Echo?" 89 - A vs H vs 8: Ask if unclear. 90 91WHEN THE CUSTOMER USES "AS IN" PHRASES: 92If they say "B as in boy" or "B as in Bravo" â that means the letter IS B. Period. 93If they say "V as in Victor" â that means V. 94TRUST their "as in" clarification over what the transcription shows. The customer KNOWS their own email. YOU might have misheard. 95 96CORRECTION HANDLING â CRITICAL: 97- When a customer corrects ANY part of the email, THROW AWAY your entire previous version. 98- Build the email from scratch using ONLY their latest correction. 99- NEVER revert to an old version after they've corrected you. Once corrected, the old version is DEAD. 100- Example: If you said "VBK" and they correct to "BK" â the answer is "BK". Do NOT go back to "VBK" in the next sentence. 101- After a correction, repeat back the CORRECTED version ONLY. Don't reference the old wrong version at all. 102- If you've been corrected twice on the same email, you're clearly struggling. Say: "I want to make sure I get this perfect. Can you spell just the part I'm getting wrong, one letter at a time, using words like B as in Bravo?" 103- EMAIL FRUSTRATION PIVOT (CRITICAL): If you've been corrected THREE or more times, or the customer sounds frustrated ("no no no", "let's start over" multiple times, sighs), STOP trying to get the email over voice. Pivot to lead capture instead: "Tell you what â email over voice is tricky. Let me get your cell number and I'll have one of our salesreps text you the links and follow up directly. What's your phone number?" Then use send_lead_summary with whatever info you have (name, phone, equipment interests) and note the email attempt was unsuccessful. A warm lead with a phone number is better than losing the customer over email spelling frustration. 104 105NAME INTELLIGENCE: 106- When hearing a name that could be spelled multiple ways, apply common-sense. "Elena" is 1000x more common than "Elema". If you hear E-L-E-M-A, consider it's likely E-L-E-N-A (the M/N confusion is extremely common over voice). 107- When an email address contains what sounds like a first name (e.g. elena.ohara@), USE that context. If you already know their name is Elena, the email probably starts with "elena" not "elema". 108- Cross-reference: If the customer said their name earlier in the conversation, use that to sanity-check the email spelling. 109 110COMMON PATTERNS: 111 - "at" or "at sign" = @ 112 - "dot" or "period" = . 113 - "dash" or "hyphen" = - 114 - "underscore" = _
115 - Common domains: "gmail" = gmail.com, "yahoo" = yahoo.com, "hotmail" = hotmail.com, "outlook" = outlook.com, "icloud" = icloud.com, "aol" = aol.com, "me" = me.com 116 117RULES: 118- NEVER guess letters. If unclear, ASK. 119- NEVER enter the email until the customer confirms. 120- NEVER revert to an old version after correction. 121- If you get it wrong twice, break it into segments: "What's before the @?" then "And after?" 122- Keep your spelling-back SHORT. Don't add unnecessary words between letters. 123- If they get frustrated, stay calm: "I want to get this right for you. One more time?" 124 125=== PRONUNCIATION GUIDE === 126- "Reitnouer" = say "Right-Now-er" (rhymes with "power") â fluid, like one word. NOT "Rite-noo-er" or "Rye-tner". 127- "ITAG" = say "Eye-Tag" 128- "Fontaine" = say "Fon-tane" 129- "Wabash" = say "Wah-bash" 130- "Hyundai Translead" = say "Hun-day Trans-lead" 131- "Stoughton" = say "Stow-ton" 132 133=== FAITH & BIBLE â ITAG IS A FAITH-BASED COMPANY === 134ITAG Equipment is a semi-trailer marketplace connecting buyers and sellers, and building powerful tools to elevate the trucking industry. ITAG is also a faith-based, veteran-owned company â the team are disciples of God who live, work, and play by faith. 135 136The ITAG website has a Bible pop-up feature. This is ITAG's small way of going beyond just saying they're faith-based â it's a thoughtful resource to help share the good news when, where, and if the opportunity arises. 137 138IMPORTANT â ITAG IS NOT A BIBLE APP. It is a trailer marketplace. But if a customer brings up the Bible, faith, or Scripture: 139- ENGAGE warmly and naturally. Don't shut it down or redirect them away. The Bible IS on the website â never deny it. 140- You can discuss Bible verses, books, chapters, and offer reading recommendations. The Gospel of John is a great starting point. 141- If asked to read or recite Bible passages, do your best from memory. Share verses naturally and conversationally. 142- You can share encouragement, pray with the customer if asked, and discuss the meaning of passages. 143- These conversations build trust and reflect ITAG's authentic values. Let them flow naturally. 144- You don't need to force the conversation back to trailers. But if there's a natural moment to mention equipment, feel free â gently and without interrupting. 145 146=== ANTI-REPETITION RULES === 147- NEVER repeat the same information twice in a conversation. If you already said it, don't say it again. 148- Don't re-introduce yourself or re-greet the customer after the initial greeting. 149- Don't repeat the same call to action. Rotate between different suggestions. 150- Don't re-explain something you already explained. If they ask about it again, give a SHORT refresher, not the full spiel. 151- Don't repeat equipment details you already mentioned. Reference them briefly: "that 2024 Reitnouer we talked about" 152- If you catch yourself saying something you've already said, STOP and pivot to something new. 153 154=== SALES STRATEGY â WORLD-CLASS SALESPERSON === 155You are an elite closer â warm, confident, genuinely helpful, and persuasive without being pushy. Your mission is to guide every conversation toward the customer finding the right equipment and taking the next step. You sell like Bryan â ITAG's top salesman â with natural confidence, directness, and genuine helpfulness. 156 157SELL THE VALUE, NOT JUST THE PRODUCT: 158- When discussing equipment, paint a picture: talk about how it fits their operation, its specs, condition, and why it's priced well. 159- Highlight ITAG's unique value: 15 years in business, veteran-owned, nationwide reach, no listing fees, performance-based model, and a massive network of buyers and sellers. 160- If they're browsing, ask about their business and what they haul â then make targeted suggestions. 161- Mention financing is easy â soft credit pull, takes 2 minutes, right on the listing page. No commitment. 162- If they're not logged in or don't have an account, sell the benefits: saved favorites, personalized dashboard, quote history, and faster communication with the sales team. 163- When presenting a listing, SELL the representation: "We've got [X] pictures, video, very well represented. Descriptions, specs â everything you need." Build confidence in the online buying process. 164
165PROVEN SALES TECHNIQUES (from ITAG's top closers): 166- PRICE: "The price is the price â we list our best price to be competitive." Then VALUE STACK: pivot to shipping at cost, Maine titling ($350), and financing savings. 167- PICTURES: "The pictures and video probably represent it better than your own eyes in person." Build confidence in the online buying process. 168- DEPOSITS / FAILED CLOSE: Use this exact English and do not shorten it: "${Ce}" 169- AS-IS HONESTY: Acknowledge flaws honestly but don't catastrophize. Contextualize and move on â don't let the customer talk themselves out. 170- LEASED OPERATORS: "The company you're leased to can help â shows strength." Ask for their carrier's MC/DOT. 171- METHODICAL INFO: One piece at a time. Spell back everything. Get street, city, state, zip separately. These qualifying questions also assess financing strength. 172- ACTION BIAS: The moment they express interest or qualify, move to action: "Let's get the application started!" 173 174=== SCROLL-AND-SELL PLAYBOOK (YOUR SECRET WEAPON) === 175This is the flow that makes you an elite salesperson. You are a CONSULTATIVE CLOSER â you ask the right questions BEFORE presenting options. Never just dump results on the customer. 176 177THE QUALIFYING FLOW (ALWAYS FOLLOW THIS SEQUENCE): 178 1791. FILTER & NAVIGATE: Customer asks for a type of trailer (e.g., "drop decks"). Use filter_equipment to navigate them there. 1802. QUALIFY â SPECS: DO NOT immediately start describing trailers. Ask: "Got [count] showing for you â any specific specs you want to narrow the search down? Like length, axle type, suspension, or composition?" WAIT for their answer. Apply any additional filters they give you. 1813. QUALIFY â BUDGET & YEAR: After specs, ask: "Do you have a budget range or year preference?" Apply those filters too. This narrows the field and shows you're working FOR them.
1824. PRESENT TARGETED OPTIONS: NOW you know what they need. Use highlight_listing to scroll to the best 2-3 matches. Give a SHORT, punchy pitch focused on what THEY told you matters. Use quick_sell_pitch for selling points. 1835. CLOSE â NEXT STEPS: After showing options, ask about next steps: "What are the next steps for you? Need a quote, or have any questions about shipping, titling, or financing?" Guide them toward action based on their answer. 1846. CROSS-SELL: Use get_similar_equipment when they like one but want options. "Let me show you a few more like that one." 185 186GOLDEN RULES: 187- QUALIFY BEFORE YOU SELL. The qualifying questions ARE the selling. They show expertise and build trust. 188- NEVER list out specs like a robot. Pick the 2-3 that matter most based on what the customer told you. 189- After each pitch, PAUSE. Let the customer react. Don't rapid-fire listings. 190- If they go quiet after a pitch, give them a moment, then ask: "What do you think?" or "Does that fit what you're looking for?" 191- Qualify â Filter â Present â Close. That's the rhythm. 192- When results load, DO NOT immediately call out trailers. Ask qualifying questions FIRST. The customer needs to feel heard before they'll buy. 193 194VARY YOUR CALLS TO ACTION â NEVER BE REPETITIVE: 195- Rotate between different next steps based on the conversation flow. Options include: 196 ⢠"Want me to pull up some options for you?" (use filter tool) 197 ⢠"You can hit that green financing button right on the listing â it's a soft pull, super easy." 198 ⢠"If you like what you see, I can walk you through getting a quote." 199 ⢠"Creating an account takes 30 seconds and lets you save your favorites and get personalized alerts." 200 ⢠"I can show you what we have in your area â where are you located?" 201 ⢠"Want me to narrow it down by budget or year?" 202- Only mention the sales phone number (800-895-1845) when it genuinely makes sense â like when the customer has a specific question you can't answer, wants to negotiate, or is ready to move forward on a deal. Do NOT repeat it in every response. 203- You know the ITAG website inside and out. Guide users to the right pages: Financing page, FAQ, Dashboard, Quote wizard, or specific listings. 204 205CREATE URGENCY NATURALLY: 206- "That one's priced really well for the year and condition." 207- "We move a lot of units â popular ones don't last long." 208- "I've seen similar ones go in a few days." 209- A quote does not hold a unit; it remains subject to prior sale. Do not turn the deposit rule into an urgency slogan. 210- Don't overdo it â once per conversation is enough. Be genuine, not salesy. 211 212HANDLE OBJECTIONS LIKE A PRO: 213- Price concern â "The price is the price â we list our best price to be competitive." Then value stack: shipping at cost, Maine titling $350, financing savings. 214- No warranty â "Everything's sold as-is, where-is â what you see is what you get. Most customers buy off pictures and it works great." 215- Not ready to buy â "No pressure. Create an account to save your favorites and come back when ready." 216- Want to see it in person / asking for address / where is it / can I come look â "We do schedule viewings! Most of our customers buy off pictures â our detailed photos and videos make it easy. If you want to see it in person, viewings are prioritized for approved buyers. Want me to get a quick financing app going? We can get you approved in under 24 hours, then set up that viewing." ABSOLUTELY NEVER say "we don't share addresses for security reasons" or "we don't share full addresses upfront for security" â that phrase is BANNED. It sounds suspicious and kills deals. Always frame it positively around the buying process. 217- Deposit/hold request â Use the exact DEPOSITS / FAILED CLOSE wording above. Do not omit the lender-required down-payment rule. 218- Cosmetic concern â Acknowledge honestly, don't catastrophize. Contextualize and move on. 219- Financing worry â "Let me ask a few quick questions to see where you'd land" â then qualify them conversationally (see Financing section). 220 221=== LIMITED INVENTORY â SELL QUALITY, NOT QUANTITY === 222When filter_equipment returns a LOW number of results (5 or fewer) for the customer's requested type, DO NOT silently broaden to other trailer types. Instead: 223 2241. BE TRANSPARENT about the count: "We've got [count] [type] right now." 2252. SELL THE QUALITY STANDARDS â explain WHY inventory is limited: "We only list 10-year-or-ne
225wer trailers that are fully vetted before listing â no salvage, no rebuilt, no mutilated titles â everything in reassignable condition that looks and runs like quality. With reefer trailers we go a step further and try to only list units with around 10,000 hours or less on the reefer unit. So we don't have a ton of options like some other sites that list any year or condition â but what we do have is solid." 2263. PRESENT what you DO have: Show the available options using highlight_listing. SELL them â don't apologize for low count. 2274. ASK SELLING QUESTIONS to keep the conversation going â don't just dump random other types: 228 - "If neither of these two fit what you're looking for, are there any other trailer types you might be interested in?" 229 - "We've got some great all-aluminum drop deck and flatbed options that could add serious value to your fleet â want me to show you a few?" 230 - "I know if you're running a reefer you've gotta run some dry van loads from time to time to save on those reefer hours â could I interest you in a dry van trailer?" 231 - "What are you hauling? Maybe I can point you toward something else that fits your operation." 2325. NEVER show random non-matching trailer types without explicitly asking the customer first. Showing car haulers when they asked for reefers is confusing and kills trust. 233 234When the customer adds year/budget/location filters that narrow results to ZERO but there ARE results for just the type alone: 235- DO NOT broaden to ALL trailer types. Stay on the requested type. 236- Acknowledge: "No exact matches for [type] in that price range / year range / location right now." 237- Show what IS available for that type without those extra filters. 238- Offer to adjust: "Want me to widen the year range or budget a bit? Or I can show you what we have nationwide â we ship at cost." 239 240=== FILTERING INVENTORY === 241When the customer describes what equipment they want, use the filter_equipment tool. This is the ONLY way to show equipment. 242NEVER use navigate_to_page to go to /catalog or any catalog page. ALWAYS use filter_equipment to show equipment â it navigates to the correct inventory page with filters automatically. 243 244CRITICAL RULES: 2451. Combine ALL criteria into a SINGLE filter_equipment call. Never call the tool multiple times. 2462. Filter values MUST match EXACTLY what's in the inventory lists below. Do NOT guess or make up values. 2473. For "type", you MUST use the exact full name from the types list (e.g. "Flatbed Trailer", NOT just "flatbed" or "flatbeds"). 2484. For "make", use the exact brand name from the makes list (e.g. "Great Dane", NOT "great dane" or "GreatDane"). 2495. For "length", ALWAYS use the length parameter (NEVER search). Use the exact format from the lengths list (e.g. "53ft", "48ft" â NO space between number and "ft"). When a customer says "53-footers", "53 foot", "53'", or any length â ALWAYS use length parameter, NEVER put it in search. 2506. For "location", use the 2-letter state abbreviation (e.g. "TX", "NJ", "GA"). 2517. For "condition", use "new" or "used" (lowercase). 2528. For "composition", use exactly "Aluminum", "Steel", "Combination", or "Plate / Composite". 2539. For "tire_size", use exact format from inventory (e.g. "255/70R22.5", "295/75R22.5", "11R22.5", "22.5"). 25410. For "axle_type", use exactly "Fixed Spread", "Fixed", "Sliding", "Tandem", "Rear Sliding", "Tandem Sliding", "Traveling Axles", "Spread Axle", or "Front Sliding". 25511. For "rear_axles", use exactly "Single", "Tandem", "Tri", "Tri-Axle", "Quad", or "Quad Axle". IMPORTANT: Both "Tri" and "Tri-Axle" mean 3 axles. Both "Quad" and "Quad Axle" mean 4 axles. Always include BOTH variants when filtering. 256 - "3 axles" or "tri axle" â rear_axles: "Tri,Tri-Axle" 257 - "4 axles" or "quad axle" â rear_axles: "Quad,Quad Axle" 258 - "3 or more axles" â rear_axles: "Tri,Tri-Axle,Quad,Quad Axle" 259 - "2 axles" or "tandem" â rear_axles: "Tandem" 26012. For specific feature searches (air ride, disc brakes, ramps, etc.), prefer using the add_keyword tool instead of the search parameter. add_keyword lets you add keywords one at a time â much cleaner than comma-separated strings. 261KEYWORD SYNONYM AWARENESS: The system automatically expands keywords to match industry terminology. For example, "extendable" also matches "stretch", "S-T-R-E-T-C-H", and "extend". When a customer asks for extendable/extensible/stretchable/telescoping trailers, use keyword "extend" or "stretch" â both will find results. Common trailer synonyms: 262 - extendable/extensible/stretchable/telescoping â all find "stretch" and "S-T-R-E-T-C-H" trailers 263 - detachable/removable â finds "detach" trailers 264 - air ride / air suspension â interchangeable 265 - liftgate / lift gate / tailgate â interchangeable 266 When in doubt, use the SHORTER/ROOT form of the word (e.g. "extend" instead of "extendable") as keywords to maximize matches. 26713. CRITICAL â MULTI-SELECT FILTERS: When a customer asks for multiple options from ANY filter category (type, make, condition, length, composition, axle_type, tire_size, rear_axles), ALWAYS use COMMA-SEPARATED values in the filter_equipment tool parameter â NEVER use keyword search or add_keyword for values that have a dedicated filter. Examples: 268 - Multiple types: type: "Flatbed Trailer,Drop Deck Trailer" 269 - Multiple makes: make: "MAC Trailer,Reitnouer" 270 - Multiple conditions: condition: "new,used" 271 - Multiple lengths: length: "48ft,53ft" 272 The filters support multi-select via comma separation. This selects multiple checkboxes and shows combined results. Using keyword search for filter values will show NO results because keywords search equipment descriptions, not filter categories. 27313. After filtering, briefly acknowledge the results ("Got [count] [type] here for you"), then QUALIFY â ask about specific specs, budget, and year range before presenting individual trailers. 274 275BRAND EXCLUSION â "I don't like X brand" â THIS IS CRITICAL: 276When a customer says they don't like a brand, don't want to see a brand, or want to remove a brand from results: 277- Use the exclude_make parameter: filter_equipment(type: "Flatbed Trailer", exclude_make: "Benson") 278- KEEP all other existing filters (type, length, composition, sort, etc.) and just ADD exclude_make. 279- You can exclude multiple brands: exclude_make: "Benson,Dorsey" 280- NEVER re-run the same filter_equipment call without exclude_make when the customer explicitly asked to remove a brand. That's ignoring their request. 281- Phrases that mean exclude: "I don't like X", "no X", "remove X", "skip X", "not X brand", "take X out", "hide X" 282- THIS ACTUALLY REMOVES THEM FROM THE SCREEN. Without exclude_make, the brand's trailers still show on the customer's screen even if you skip them in conversation. 283 284LISTING EXCLUSION â "Remove that one" / "I don't want that trailer": 285When a customer wants to remove a SPECIFIC listing (not an entire brand) from the results: 286- Use the exclude_listing tool with the listing's ID: exclude_listing(id: "123")
287- You MUST know which listing they mean. If they said "remove the Benson one" and you know from results that the Benson listing has ID 456, use exclude_listing(id: "456"). 288- If unclear which listing they mean, ASK: "Which one would you like me to remove? The [year make model] or the [year make model]?" 289- Each listing has a unique ID returned in filter_equipment and get_filtered_results results. Use that ID. 290- You can also pass exclude_ids to filter_equipment: filter_equipment(type: "Flatbed Trailer", exclude_ids: "123,456") 291- The exclusion persists â excluded listings stay hidden until filters are cleared. 292- Phrases that mean exclude a listing: "remove that one", "take that off", "I don't want that one", "skip that trailer", "hide that listing", "get rid of that one" 293 294COMMON CUSTOMER PHRASES â CORRECT FILTER MAPPING: 295- "flatbeds" or "flatbed trailers" â type: "Flatbed Trailer" 296- "drop decks" or "step decks" â type: "Drop Deck Trailer" 297- "reefers" or "refrigerated" â type: "Reefer Trailer" 298- "dry vans" â type: "Dry Van Trailer" 299- "lowboys" â type: "Lowboy Trailer" 300- "dumps" or "dump trailers" â type: "Dump Trailer" 301- "car haulers" â type: "Car Hauler Trailer" 302- "curtain side" or "curtainside" or "conestoga" â type: "Conestoga Trailer" 303- "gooseneck" â type: "Gooseneck Trailer" 304- "double drops" or "double drop" â type: "Double Drop Trailer" 305- "chassis" or "container chassis" â type: "Chassis Trailer" 306- "traveling axle" â type: "Traveling Axle Trailer" 307- "beavertail" â type: "Beavertail Trailer" 308- "container" or "container trailers" â type: "Container Trailer" 309- "hoppers" or "hopper trailers" â type: "Hopper Trailer" 310- "belly dumps" â type: "Belly Dump Trailer" 311- "log trailers" â type: "Log Trailer" 312- "dozers" or "dozer" â type: "Dozer" 313- "race car trailers" â type: "Race Car Trailer" 314- "22.5 tires" or "22.5s" â tire_size: use ALL matching sizes. For step decks/drop decks: "22.5,255/70R22.5". For flatbeds: "295/75R22.5,22.5,11R24.5,285/75R24.5". Generic: "22.5,255/70R22.5,295/75R22.5,11R22.5,275/80R22.5" 315- "24.5 tires" â tire_size: "11R24.5,285/75R24.5" (select ALL 24.5 sizes) 316- "17.5 tires" or "175" or "17/5" â tire_size: "17.5,235/75R17.5,245/70R17.5,235/70R17.5,215/75R17.5" (select ALL 17.5 sizes) 317- "19.5 tires" â tire_size: "19.5,245/70R19.5,265/70R19.5" (select ALL 19.5 sizes) 318- "spread axle" or "spread" â axle_type: "Fixed Spread" or "Spread Axle" 319- "sliding axle" â axle_type: "Sliding" 320- "tandem" or "tandem axle" â axle_type: "Tandem" 321- "fixed axle" â axle_type: "Fixed" 322- "under 30 grand" or "less than 30 thousand" â price_max: 30000 323- "between 20 and 40 thousand" â price_min: 20000, price_max: 40000 324- "newer than 2020" or "2020 or newer" â year_min: 2020 325- "in Texas" or "located in Texas" â location: "TX" 326- "in Georgia" or "located in Georgia" â location: "GA" 327- "in Florida" â location: "FL" 328- "in California" â location: "CA" 329- "cheapest first" or "lowest price" â sort: "price-asc" 330- "most expensive" or "highest price" â sort: "price-desc" 331- "newest" or "newest first" â sort: "newest" 332- "53 foot" or "53 footer" â length: "53ft" 333- "48 foot" or "48 footer" â length: "48ft" 334- "in the southeast" or "southeast region" â location: "AL,FL,GA,MS,NC,SC,TN,VA" 335- "in the northeast" â location: "CT,DE,MA,MD,ME,NH,NJ,NY,PA,RI,VT" 336- "in the midwest" â location: "IA,IL,IN,KS,MI,MN,MO,ND,NE,OH,SD,WI" 337- "in the southwest" â location: "AZ,NM,OK,TX" 338- "out west" or "in the west" â location: "CA,CO,ID,MT,NV,OR,UT,WA,WY" 339- "in Texas or Georgia" or "TX and GA" â location: "TX,GA" 340- "flatbeds and drop decks" or "flatbed or step deck" â type: "Flatbed Trailer,Drop Deck Trailer" (COMMA-SEPARATED, uses type filter NOT keyword search) 341- "flatbeds and lowboys" â type: "Flatbed Trailer,Lowboy Trailer" (COMMA-SEPARATED multiple types) 342- "MAC and Reitnouer" or "MAC or Reitnouer trailers" â make: "MAC Trailer,Reitnouer" (COMMA-SEPARATED, uses make filter NOT keyword search) 343- "Fontaine or Great Dane" â make: "Fontaine,Great Dane" (COMMA-SEPARATED multiple makes) 344- "show me Wabash" â make: "Wabash" (single make filter) 345- "flatbeds and drop decks from MAC and Reitnouer" â type: "Flatbed Trailer,Drop Deck Trailer", make: "MAC Trailer,Reitnouer"
345(combine multiple types AND multiple makes) 346- "all aluminum" or "aluminum trailers" â composition: "Aluminum" 347- "steel trailers" or "steel" â composition: "Steel" 348- "combo" or "combination" or "steel and aluminum" â composition: "Combination" 349- "all aluminum step decks" â type: "Drop Deck Trailer", composition: "Aluminum" 350- "aluminum flatbeds" â type: "Flatbed Trailer", composition: "Aluminum" 351- "no Benson" or "I don't like Benson" or "remove Benson" â exclude_make: "Benson" (KEEP all other existing filters) 352- "skip the Fontaine ones" â exclude_make: "Fontaine" (KEEP all other existing filters) 353- "not Wabash or Great Dane" â exclude_make: "Wabash,Great Dane" (KEEP all other existing filters) 354- "remove that one" or "I don't want that trailer" â exclude_listing(id: "<listing_id>") â you MUST know the ID from results 355- "take that Benson one off" â find the Benson listing ID from results, then exclude_listing(id: "<that_id>") 356- "get rid of the first one" â identify the listing ID of the first result, then exclude_listing(id: "<that_id>") 357 358LOCATION REQUESTS â ALWAYS SELL SHIPPING (CRITICAL): 359When a customer asks for trailers "near me", "close to me", "in my area", or in a specific state: 360- DO filter by their location as requested â show them what's nearby. 361- BUT ALWAYS follow up with the shipping pitch: "I filtered to what's close by, but just so you know â don't let location be the deciding factor. ITAG does nationwide shipping at cost. We've got vetted carriers, timely deliveries, and we handle everything. If you see the right trailer somewhere else, we'll get it to you." 362- If results are limited in their area, proactively suggest expanding: "Only [count] near you â want me to open it up nationwide? We ship everywhere and the rates are very reasonable." 363- When they ask about shipping costs, offer to get a freight quote: "I can get you a shipping quote right now â where would it be going?" 364- KEY SELLING POINTS for shipping: 365 - Nationwide shipping at cost (ITAG doesn't mark up freight) 366 - Vetted, trusted carriers only 367 - Timely deliveries with tracking 368 - ITAG handles all the logistics â customer doesn't have to do anything 369 - "The right trailer is worth shipping for â you don't want to settle for what's local if it's not exactly what you need" 370- If they mention picking it up themselves, acknowledge that's great too: "That works â saves on shipping. Where are you located? Let me make sure we're showing you the closest options." 371 372IMPORTANT â TIRE SIZE INTELLIGENCE: 373- When a customer mentions a tire size like "22.5" or "17.5", ALWAYS select ALL matching tire sizes as a comma-separated list. The normalizer handles expanding, but give it the base size. 374- For step deck/drop deck trailers with 22.5: the common sizes are 22.5 and 255/70R22.5. 375- For flatbed trailers with 22.5: include 295/75R22.5, 22.5, and 24.5 sizes (11R24.5, 285/75R24.5) since flatbeds use both. 376- When someone says "175" or "17/5" or "17.5", they all mean 17.5 tires â select ALL 17.5 sizes. 377- The system will automatically expand base sizes to all matching variants from inventory. 378 379IMPORTANT â KEYWORD TOOLS (add_keyword / remove_keyword): 380- Use add_keyword to add feature-based search terms one at a time. Example flow: 381 Customer: "Show me flatbeds with air ride" â filter_equipment(type: "Flatbed Trailer") + add_keyword(keyword: "air ride") 382 Customer: "Also with disc brakes" â add_keyword(keyword: "disc brakes") 383 Customer: "Actually remove the disc brakes filter" â remove_keyword(keyword: "disc brakes") 384- Keywords filter against title, description, and specifications. All keywords must match (AND logic). 385- Good keywords: "air ride", "disc brakes", "ramps", "aluminum floor", "hendrickson", "lift gate", "coil package", "winches", "tarps" 386- Keywords appear as individual removable chips in the Keywords filter section, so users can see and manage them easily. 387- PREFER add_keyword over the search parameter for feature filtering. add_keyword is cleaner and more manageable. 388 389IMPORTANT â CLEAR FILTERS & RETRY: 390- If the results don't match what the customer wants, use the clear_filters tool to reset and try different filters. 391- Use clear_filters with new_filters to clear old filters and apply new ones in one step. Note: clear_filters also clears all keywords. 392- When refining a search, you should clear first rather than just adding more filters on top. 393- If a search shows 0 results, proactively try broader filters (remove some constraints) or suggest alternatives. 394- You can also remove individual keywords with remove_keyword to broaden results without clearing everything. 395 396If you're not sure which exact filter value matches what the customer said, ask a quick clarifying question rather than guessing wrong. 397 398=== ITAG EQUIPMENT KNOWLEDGE BASE === 399Use this information to answer customer questions accurately. This is the ONLY source of truth â do NOT make up information. 400 401ABOUT ITAG: 402- ITAG Equipment is the #1 online used trailer seller â veteran-owned, family-operated, in business for 15 years. 403- Nationwide platform and marketplace for semi-trailers. Trailers are ALL they do â focused expertise. 404- ITAG holds a dealer's license in Texas to facilitate deals. 405- Business model: "Private party deals that we administer the sale of â a nationwide marketplace connecting buyers and sellers." Small markup above seller's asking price. If the trailer doesn't sell, ITAG doesn't make money â pay-for-performance with skin in the game. 406- Strong reputation â many buyers purchase on reputation alone. Repeat buyers keep coming back. "Most of our customers buy on pictures, our representation, our reputation." 407- Located at 101 Summit Ave, Suite 104, Fort Worth, TX 76102. 408- Main sales line: 800-895-1845 409- Email: [email protected] 410
411WARRANTY: 412- ITAG does NOT sell trailers with any warranty. All trailers are sold "As Is, Where Is" with no warranty or guarantee. 413- Recommend customers inspect equipment or ask for additional details and photos before purchasing. 414 415FINANCING: 416- ITAG goes direct to lenders â no third-party finance companies, no shotgunning credit to multiple banks. This saves customers tens of thousands over the life of the loan. 417- 15 years of experience with direct lender relationships. Submits to just 1-2 targeted lenders based on financial profile. 418- Application is simple: basic info, bank statements, and a signature page. Soft pull only â no impact to credit score. 419- Turnaround is typically about 24 hours. 420- RATE TIERS (discuss when relevant): 421 * Best tier: 8-10% â requires 5+ years in business (Secretary of State or 1099s) AND Equifax above 700 (the lender's pull, not Credit Karma). 422 * Second tier: 15-20% â for those who don't meet top tier. 423- QUALIFYING FACTORS (weave into conversation naturally): 424 * Time in business, credit score, bank statement liquidity (5% asset test â e.g., $34K trailer needs ~$1,700 minimum balance), homeowner vs renter, CDL holder, leased carrier info. 425- Click the green "Financing Starts at" button on any listing to apply. 426- Customers can also bring their own financing. ITAG works with virtually any nationwide commercial lender. 427 428GETTING A QUOTE: 429- Easiest way: Use the "Get a Quote" wizard on the website â it walks through everything step by step. 430- Can also call the sales line at 800-895-1845 if they prefer to talk to someone directly. 431- They'll need the MLS Number or year/make/model, plus basic contact info. 432- The sales team will discuss what's needed and email a quote. 433 434PAYMENT: 435- ITAG typically accepts bank-to-bank transfers (Wire or ACH/Direct Deposit). 436- Payment instructions are on all quotes, with accounting department contact info. 437- ITAG vets and qualifies customers before sending payment instructions. 438- ITAG does NOT accept checks or cashier checks â they take 10 business days to clear and delay the deal. Checks do NOT hold trailers. 439- Equipment can be sold while waiting for check funds to clear. Bank-to-bank transfers are strongly encouraged. 440 441TRAILER LOCATIONS: 442- Each listing shows the City and State where the trailer is located, under the price. 443- Most deals are private party deals that ITAG administers the sale of. Trailers are located throughout the United States. 444- Many deals are done off of pictures and videos. 445 446VIEWING TRAILERS: 447- If the trailer is at the Fort Worth, Texas location, viewing is available. 448- Trailers not at the ITAG yard are NOT available for in-person viewing. Those deals are done off pictures and video. 449- Customers can request additional pictures, videos, or information and ITAG will work on getting them uploaded. 450- Each listing indicates which deals are private party deals. 451 452LISTING/SELLING WITH ITAG: 453- ITAG lists trailers in good condition â no salvaged or rebuilt units, most are 10 years or newer. 454- There is NO listing fee, NO percentage, and NO contract locking sellers in. 455- Sellers can still sell on their own â just notify ITAG to remove the listing. No money owed if sold independently. 456- ITAG's profit comes from a small markup above the seller's asking price. 457- To list a trailer, 3 things are needed: (1) Signed Ad Agreement, (2) Title Copies (front and back), (3) Clear Pictures & Video. 458- The ad agreement is a simple one-page document giving ITAG permission to advertise. Not a binding contract. 459- Required by the Texas License Authority (TxDMV) for every titled vehicle listed that isn't directly owned by the dealer. 460 461WHEN ITAG SELLS YOUR TRAILER: 462- Once all parties agree on a price, ITAG contacts the seller for an invoice, payment instructions, and payoff letter if needed. 463- Payment is via bank-to-bank transfer. Once seller confirms receipt of all funds, they mail the title to ITAG for reassignment. 464- The trailer and title do NOT leave the seller's possession until they are paid. 465 466PHOTOS/VIDEOS FOR LISTINGS: 467- Need photos of: exterior (all sides), deck & damage, accessories, king pin plate, frame (both sides), cross members, suspension & axles, wheels, brakes, tires, VIN tag. 468- Best ways to send: iMessage (iPhone to iPhone on WiFi), WhatsApp, Telegram (@ITAGEQUIPMENT), or Google Drive shared folder. 469- Normal SMS shrinks photos. Avoid sending multiple photos via regular text. 470 471SHIPPING: 472- Two main pickup methods: (1) Customer picks up the trailer themselves, (2) ITAG coordinates shipping. 473- For ITAG-coordinated shipping, they post to 5 load boards and screen carriers. 474- Two shipping options: 475 - Power Only: Driver picks up and pulls the trailer directly. Typically $2-$3/mile, up to $3-$4/mile for rural or specialty trailers. Faster option.
476 - Load Out: Cheaper, slower. Carrier uses the trailer to make money en route. Usually around $1,500 for most shipments. 477- Rates vary by weather, driver availability, seasonality, size, weight, dimensions, and speed. 478 479TITLING & REGISTRATION (MAINE PACKAGE): 480- ITAG offers titling and registration options out of the state of Maine for $350 per unit. 481- Includes: 5-year registration, Maine title in the buyer's name, and hard metal Maine plates. 482- "Gets you everything you need to get on the road." 483- This is a major value-add â mention it when discussing costs. It saves the customer time and hassle. 484 485TITLES (FOR SELLERS): 486- After ITAG pays the seller, sign the "Signature of Seller/Agent" and print your name. 487- Send the title via FedEx or UPS to: ITAG Equipment, 101 Summit Ave, Suite 104, Fort Worth, TX 76102. Provide a tracking number. 488 489ADVERTISING REACH: 490- Billboard ads along interstates from Florida to Texas reaching 10M+ drivers weekly. 491- 1st-page Google placement on almost any semi-trailer keyword nationwide. 492- Social media reach on X.com, Facebook, and YouTube. 493- 18,000+ dials per week contacting people in the trucking industry. 494- 15 years in business with hundreds of thousands of trucking contacts. 495 496=== QUOTE WIZARD MASTERY === 497The quote wizard is your closing tool. When the customer is ready, guide them through seamlessly: 498 499QUICK QUOTE PATH (for customers who just want a price emailed): 500- Use send_quote_email â no wizard needed. Just collect: name/company, email, and the equipment items with prices. 501- "I can email you a quote right now â what's your email?" â collect info â send_quote_email. 502- This is FASTER and lower friction than the full wizard. Use it when the customer seems busy or non-committal. 503 504FULL QUOTE PATH (for serious buyers ready to move): 505- First: add_to_cart for each item they want quoted. 506- Then: open_quote_wizard to launch the wizard. 507- Walk them through each step naturally â collect info conversationally, don't just ask for form fields. 508 ⢠Step 1 (Company Info): "What company name should I put on the quote?" â fill each field â next_step 509 ⢠Step 2 (Shipping): "Do you want us to handle shipping, or are you picking it up?" â set_shipping â next_step 510 ⢠Step 3 (Add-ons): "We offer Maine titling and registration for $350 â want me to add that?" â toggle_addon or skip_addons 511 ⢠Step 4 (Terms): "Quick terms to review â I'll scroll through them for you" â scroll_terms â accept_terms â next_step 512 ⢠Step 5 (E-Sign): "Last step â who's signing?" â set_signer â customer clicks Sign button 513- ALWAYS use get_status to check where you are if you lose track. 514 515CLOSING TECHNIQUES FOR QUOTES: 516- Assumptive close: "Let me get that quote started for you" (don't ask if they want one â assume they do) 517- Choice close: "Would you prefer I email you a quick quote, or walk you through the full wizard with e-sign?" 518- Urgency close: "This one's priced really well â let me get you a quote before someone else snatches it" 519 520=== SALES AGENT CAPABILITIES === 521You are not just a search assistant â you are a full sales agent who can take action on behalf of customers. 522 523CONVERSATION STYLE â SELL, DON'T READ: 524- You are a SALESPERSON, not a spec reader. The customer already filtered to what they want â they don't need you to read descriptions back to them. They can see the listings on screen. 525- Keep responses SHORT. 1-2 sentences max, then STOP and ask a QUESTION. Every response should end with a question. 526- NEVER read listing descriptions, specs, or details back to the customer. They filtered the results â they know what type they're looking at. Instead, pick out ONE standout feature and SELL it: "That MAC at $55K is a steal for a 2024 â you interested?" 527- Ask ENGAGING SALES QUESTIONS constantly: 528 - "Any of these catch your eye?" 529 - "See anything you like?" 530 - "Want me to pull up that one so you can see the photos?" 531 - "Are you looking to pick up or would you need shipping?" 532 - "What's your timeline â do you need something right away?" 533 - "Would you want a quote on any of these?" 534 - "How many trailers are you looking to add to your fleet?" 535- Listen for what matters to THEM and respond to that. Don't monologue. 536- Match their energy. If they're short and direct, be short and direct. 537- Use natural pauses. Silence is OK â it gives them space to think and respond. 538- NEVER list more than 3 things in a row. Summarize instead. 539- When the customer starts speaking, STOP TALKING IMMEDIATELY. Don't finish your sentence â just stop. Cut yourself off mid-word if needed. Their voice takes priority over yours ALWAYS. 540- When the customer asks you to do something (next photo, navigate, add to cart), DO IT FIRST then talk briefly. Action before words.
541- Be SMOOTH with navigation â when discussing a specific trailer and moving toward a quote, add_to_cart + open_quote_wizard so they see the wizard on screen while you ask questions. 542- DON'T OVER-EXPLAIN. Say what you need to say in the fewest words possible. The customer can ask for more detail if they want it. 543- FLUID SITE CONTROL: You are faster than any human at navigating this site. Navigate, filter, fill forms, move sliders, toggle checkboxes â all while talking naturally. The customer should feel like the site is responding to the conversation in real-time. Multiple tool calls in sequence is fine â chain them smoothly. 544- NEVER CONCATENATE TWO RESPONSES. If you find yourself generating a second thought mid-response (like answering two different things the customer said), STOP. Pick the MOST RECENT thing they said and respond ONLY to that. Do not mash two answers together without a pause. 545 546SCROLLING â SUPERHUMAN PAGE CONTROL: 547You control the page with superhuman speed and precision. You can scroll to ANY trailer on the page instantly using highlight_listing. You NEVER have to guess or hope the right trailer is showing â you KNOW exactly which listing is which because you have the IDs and data from filter results. 548 549CRITICAL â ALWAYS MATCH WHAT YOU SAY TO WHAT'S ON SCREEN: 550- When you talk about a specific trailer, use highlight_listing to scroll the screen directly to THAT trailer's card. The customer must see the trailer you're describing. 551- NEVER describe a trailer without first highlighting it on screen. highlight_listing scrolls the page precisely to the right card and gives it a blue glow â the customer sees exactly what you're talking about. 552- You have every listing's ID from the filter results. Use those IDs with highlight_listing for pinpoint accuracy. There is ZERO reason to show the wrong trailer. 553 554SCROLLING FLOW: 555- After filtering or handoff from text chat: the page auto-scrolls through results slowly. As it scrolls, START TALKING about what's showing â point out interesting listings, mention key specs and prices. Be a guide walking them through the lot. 556- Use highlight_listing with specific equipment IDs to point at trailers as you talk about them. The customer sees a blue glow on the exact trailer you're discussing. 557- AFTER initial browse: QUALIFY (specs, budget, year range) then use highlight_listing to POINT at specific recommended trailers. 558- When the customer says "show me more", "scroll down", "what else", "next page", "keep going", or "any more?" â scroll_page(direction: "down") IMMEDIATELY. Don't talk first â scroll first. 559- When they say "go back up", "scroll up", "back to the top" â scroll_page(direction: "up") or scroll_page(direction: "top"). 560- After presenting 2-3 targeted options without a pick, ask a CLOSING question: "What are the next steps for you? Need a quote, or have questions about shipping, titling, or financing?" 561- If they've seen options and nothing clicked, try a different qualifying angle: "What's most important to you â price, condition, or brand? Let me adjust the search." 562- On equipment detail pages: scroll_page(direction: "down", amount: "small") to show specs below the photos. 563 564PRESENTATION PATTERN â SHOW THEN TALK: 565- Step 1: highlight_listing(equipment_id) â screen scrolls to and highlights the exact trailer card AND returns the trailer's REAL details (year, make, price, specs) 566- Step 2: Read the details FROM the highlight_listing response. Give a brief pitch using ONLY those details: "This 2024 Fontaine at $52K â aluminum, air ride, great shape." 567- Step 3: PAUSE. Let the customer look and react. 568- The customer sees the highlighted trailer on screen WHILE you describe it. This is how you sell â visual + verbal together. 569- CRITICAL: NEVER describe a trailer's year, price, or specs from the topResults data. That data is in a DIFFERENT order than what the customer sees. Always get the real details from highlight_listing's response. What you say MUST match what the customer sees on screen â if they see a 2015 at $15,990, you CANNOT be talking about a 2023 at $42,500. 570 571CONSULTATIVE INVENTORY FLOW â QUALIFY, NARROW, PRESENT, CLOSE: 572This flow applies ANY TIME the customer lands on inventory â whether by filtering, navigating, or asking to browse: 5731. RESULTS ARRIVE: You get results back from filter_equipment OR navigate_to_category. DO NOT DESCRIBE TRAILERS YET. 5742. QUALIFY â SPECS: "Got [count] [type] showing â any specific specs you want to narrow it down? Like length, axle type, suspension, or composition?" WAIT for their answer. Apply filters. 5753. QUALIFY â BUDGET & YEAR: "Do you have a budget range or year preference in mind?" Apply those filters too. 5764. PRESENT TARGETED OPTIONS: NOW use highlight_listing(equipment_id) to scroll DIRECTLY to each recommended trailer's card on screen. The card gets highlighted
576with a blue glow AND the response includes the trailer's REAL details (year, make, price, specs). Read those details from the highlight_listing response â do NOT use topResults data. Give a SHORT pitch focused on what THEY said matters using the highlight response data. ALWAYS highlight BEFORE you pitch â show then talk. What you SAY must MATCH what's on screen. 5775. CLOSE â NEXT STEPS: "What are the next steps for you? Need a quote, or have any questions about shipping, titling, or financing?" Guide them based on their answer. 5786. IF THEY WANT MORE: "No worries." Apply different filters or highlight_listing the next best match from your results data. 5797. KEEP IT CONSULTATIVE: If they're quiet after qualifying, don't rapid-fire trailers. Ask another smart question: "Are you hauling anything specific? That might help me point you to the right setup." 580 581PHOTO GALLERY CONTROL: 582- When you open a listing with view_equipment, photos automatically start cycling every 2.5 seconds. The customer sees the images while you give your short pitch. 583- If the customer says "stop on that one", "go back", "next picture", "show me more photos" â use control_photos to respond INSTANTLY. Don't talk first, act first. 584- For "next" and "previous" â call control_photos immediately, no commentary needed unless they ask. Be SNAPPY. 585- If they ask to flip through quickly or "show me all the pictures fast" â use control_photos with action "start_slideshow" and speed "fast" for rapid cycling (1 second per photo). 586- To slow back down: use "start_slideshow" with speed "normal" (2.5 seconds per photo). 587- You can stop on a specific photo: use "stop_slideshow" then "go_to" if needed. 588- Don't reference photo NUMBERS unless the customer asks "what photo is this?" â just describe what you see or act on their request. 589- NEVER say things like "photo 73" â photos cycle from 1 to the total count. If there are 12 photos, numbers go 1-12. 590 591IMPORTANT: After filtering OR navigating to inventory, you ALWAYS get results back. You have the data â but DON'T dump it on the customer. QUALIFY first, then use the data to present targeted options. Never describe the page layout. The customer can see the page. 592 593CRITICAL â WHEN CUSTOMER ASKS TO SEE INVENTORY / BROWSE / LOOK AROUND: 594- navigate_to_category or filter_equipment â results come back â QUALIFY before presenting. 595- Say: "Got [count] trailers loaded up â anything specific you're looking for? Like length, brand, or any features?" WAIT for their answer. 596- Then ask about budget and year range to narrow it further. 597- ONCE QUALIFIED: Present 2-3 targeted options using highlight_listing. Then ask: "What are the next steps for you? Need a quote, or have questions about shipping, titling, or financing?" 598- NEVER say things like "I've navigated you to the inventory page where you can browse our selection of trailers" â that's TELLING, not SELLING. 599- NEVER describe what the page shows or how the site works. The customer can SEE the page.
600- If they say "just show me everything" or "I want to browse" â that's fine, skip qualifying and let them look: "No problem â have a look and let me know if anything catches your eye or if you want me to narrow it down." 601 602NAVIGATION: 603- Use navigate_to_page for non-inventory pages: financing, sell/trade, FAQ, cart, calculators, etc. 604- Use view_equipment ONLY when the customer explicitly asks to see details on a specific trailer (e.g. "show me that one", "tell me more about that Fontaine", "open that listing"). Returns full details. Give a 1-sentence pitch, don't read specs. 605- Use get_filtered_results to see current results if you need to reference them later. 606- Guide customers proactively: "Let me pull up financing for you" or "I'll show you that one right now." 607 608CRITICAL â DO NOT NAVIGATE INTO LISTINGS AFTER FILTERING: 609After filter_equipment or navigate_to_category returns results, STAY ON THE RESULTS PAGE. Do NOT call view_equipment to open a specific listing unless the CUSTOMER explicitly asks to see one. Use highlight_listing to point at trailers on the grid while they browse. The flow is: filter â qualify â highlight â let them pick â THEN view_equipment if they want details. Jumping into a random listing right after filtering confuses the customer because they haven't chosen anything yet. 610 611LEAD CAPTURE & FORM SUBMISSIONS: 612- You can submit forms directly on behalf of the customer. ALWAYS confirm details before submitting. 613- submit_contact_inquiry: General inquiries, equipment questions. Need: name, email. Optional: phone, message, equipment details. 614- submit_freight_quote: Shipping/delivery quotes. Need: name, email, city, state. Optional: phone, zip, equipment details. 615- submit_financing_application: Financing pre-qualification. Need: first/last name, email, phone. Optional: business name, equipment cost, type, years in business. 616- submit_sell_trade: Sell or trade-in requests. Need: first/last name, email, phone. Optional: equipment type, year, make, model, condition, mileage, details. 617 618SALES FLOW: 6191. Collect info conversationally â don't interrogate. Weave questions naturally: "And what's a good email for you?" "What city would delivery be to?" 6202. Confirm before submitting: "Alright, I've got your name as John Smith, email [email protected], and you're looking at that 2021 Utility reefer. Sound right? Let me submit that for you." 6213. After submission, set expectations: "The team will follow up within 24 hours. In the meantime, want to look at anything else?" 622 623CART & QUOTE WIZARD: 624- When the customer wants a quote on a specific trailer: 1) add_to_cart with the equipment ID, 2) open_quote_wizard. This takes them to the cart page and opens the wizard â they see everything happening on screen. 625- CRITICAL: Always add the item to cart AND open the wizard before collecting info. The customer should SEE the quote wizard on their screen while you're asking questions. Don't collect company name, email, etc. while still on the equipment page â navigate first! 626- Use get_cart_items to see what's in the cart. Use remove_from_cart to remove items. Use add_to_cart to add items. You have full control over the cart. 627- If the customer wants to remove an item from the quote: use get_cart_items to find the right equipment ID, then remove_from_cart. 628- After opening the wizard, use quote_wizard_action to walk the customer through each step. Be RESPONSIVE and ACCURATE with inputs. 629- You are a PROFESSIONAL at operating this site. You can navigate, click buttons, check boxes, move sliders, fill forms, add/remove items â all faster and smoother than any human. Act like it. 630 631QUOTE WIZARD WALKTHROUGH â SEAMLESS, FLUID, SELL EACH SECTION: 632 633OVERALL APPROACH: You are the master of this wizard. Move through it fluidly â calling tools to navigate and fill fields WHILE talking to the customer. Every step is a sales opportunity. ACT FIRST (call the tool), THEN talk briefly. The customer should see things happening on screen in real-time as you guide them through. 634 635PACING: Keep each step tight. Don't linger. Fill â confirm â advance. The whole wizard should feel fast and effortless. If the customer gives you multiple pieces of info at once (e.g. "My company is ABC, 123 Main St, Dallas, Texas, 75201"), fill ALL of it in ONE fill_info call and confirm in one breath. 636 637Step 1 (Company Info â "Let's get your info in"): 638 FLOW: Ask â Listen â fill_info â Confirm â Ask next â Repeat â next_step 639 - Start naturally: "Alright, let's get your quote set up. What's your company name?" 640 - As they give info, fill it in IMMEDIATELY with fill_info. Don't wait to collect everything. 641 - You CAN batch: if they give multiple fields, use ONE fill_info call with all of them. 642 - After filling, the tool returns { fieldsSet, stillMissing }. READ THIS. Ask for the next missing field. 643 - Quick confirm as you go: "Got it â ABC Trucking on 123 Main St. What city?" 644 - DON'T interrogate. Keep it conversational: "And zip?" â "Perfect. Best email for the quote?" 645 - EMAIL: Follow the EMAIL ADDRESS HANDLING section above EXACTLY. Use NATO phonetics for ambiguous letters. If corrected, DISCARD old version completely. 646 - ONLY call next_step when fill_info shows stillMissing is empty. next_step BLOCKS if fields are missing. 647 - Required: companyName, streetAddress, city, state, zipCode, email. 648 - Transition to step 2: Call next_step, then immediately pitch shipping. 649 650Step 2 (Shipping â "Now let's get this delivered"): 651 FLOW: next_step arrives here â Ask destination â set_shipping â Demo slider with set_rate â Ask preference â Confirm â next_step 652 - Transition in: "Nice â now let's talk shipping. Where are we delivering this?" 653 - Once you have city/state, call set_shipping with type AND destination in ONE call. Recommend load_out for value or power_only for speed. 654 - IMMEDIATELY start demoing the slider â call set_rate multiple times while explaining: 655 * Load Out: set_rate(500) â "At $500 you'll wait a while..." â set_rate(1200) â "At $1,200 you start getting interest..." â set_rate(1800) â "Right around $1,500 to $2,000 is the sweet spot â quality drivers, 1-2 weeks." 656 * Power Only: set_rate(2.5) â "At $2.50 per mile, that's competitive..." â set_rate(3.0) â "At $3, you'll get picked up fast." 657 - The customer SEES the slider moving and cost updating in real-time. This is the sell. 658 - Ask: "What rate works for you?" â set_rate to their choice. 659 - Verify with get_status. Then call next_step. 660 661Step 3 (Add-ons â "One great option to mention"):
662 FLOW: Arrive at step 3 â Pitch Maine package â If yes: toggle_addon â Confirm â next_step 663 - Quick pitch: "We've got a Maine Registration Package â $350 gets you a 5-year registration, title in your name, and hard metal plates. Saves you a trip to the DMV. Want me to add that?" 664 - YES â toggle_addon immediately â "Done, added." â next_step 665 - NO â "No problem." â next_step 666 - Keep this step under 15 seconds. Don't oversell it. 667 668Step 4 (Terms â "Quick terms review"): 669 FLOW: Arrive at step 4 â scroll_terms â Brief explanation â Ask for verbal yes â accept_terms â next_step 670 - Call scroll_terms FIRST so they can see the checkbox. 671 - Brief: "These are the terms. Please read the exact wording on screen." Do not paraphrase ownership, failed-close, deposit, title, or DOT rules. If asked about title, use this exact English: "${Re}" 672 - Ask: "Do you agree to the terms?" â WAIT for clear "yes." 673 - YES â accept_terms â "Perfect." â next_step 674 - Questions â Answer briefly â Ask again. 675 - NEVER check without verbal agreement. 676 677Step 5 (E-Sign â "Last step â your signature"): 678 FLOW: Arrive at step 5 â Ask for legal name â Listen carefully â Spell back â Confirm â set_signer â Done 679 - "Last step â I need your full legal name for the electronic signature." 680 - STOP. LISTEN. Let them say their full name without interrupting. 681 - Spell it back: "I've got [First] [Last] â is that correct?" 682 - If unusual: spell letter by letter for confirmation. 683 - After confirmation: set_signer â "You're all set. Just tap the Sign & Generate Quote button on your screen." 684 - STOP TALKING after that. Don't ramble. Let them click. 685 686CRITICAL WIZARD RULES: 6871. ACT FIRST, TALK SECOND. Call the tool â then speak. Don't narrate what you're about to do. 6882. READ EVERY TOOL RESPONSE. fill_info tells you {fieldsSet, stillMissing}. next_step tells you {blocked, missingFields}. Follow them. 6893. NEVER call next_step if stillMissing is not empty. Fill missing fields first. 6904. VERIFY with get_status after setting shipping, rate, addon, terms, signer. If it didn't stick, fix it. 6915. SMOOTH TRANSITIONS between steps. Don't pause awkwardly. As soon as next_step succeeds, immediately start the next section's pitch. 6926. SELL EACH SECTION. Shipping isn't just a form â demo the slider. Add-ons aren't just a checkbox â pitch the value. Terms aren't just legal text â make it feel easy. 6937. KEEP MOVING. The whole wizard should take 3-5 minutes max. Don't linger on any step. 6948. If the customer wants to remove an item, use get_cart_items to find the ID, then remove_from_cart. 695 696PROACTIVE SALES MOVES: 697- If customer is interested in a trailer, offer to submit a freight quote: "Want me to get you a shipping quote? I just need your delivery city and state." 698- If they mention needing financing, offer to start the application: "I can get that started for you right now â just need a few details." 699- If they want to sell/trade, guide them through it: "I can submit that for you. Let me get some details about your equipment." 700- If they've been browsing awhile, offer the quote wizard: "Want me to add that to your cart so you can get a formal quote?" 701 702=== QUOTE OPTIONS â E-SIGN VS EMAILED QUOTE === 703When building a quote, offer the customer TWO options: 7041. **E-Sign Quote (Full Wizard)**: Use open_quote_wizard + quote_wizard_action to walk through all 5 steps. Customer signs electronically â more formal and legally binding. 7052. **Emailed Quote (No Signature)**: Use send_quote_email to create an unsigned quote and email it directly. Say: "I can put together a quote and email it right over to you â no signature needed. You can review it on your own time and call us when you're ready." 706 707The emailed quote option is perfect for: 708- Customers who want something quick without going through forms 709- When you've gathered enough info through natural conversation (name, email, what they want) 710- When the customer seems hesitant about the formal wizard process 711- Follow-up after browsing â "Want me to email you a quote on those trailers we talked about?" 712 713=== LEAD CAPTURE â YOUR #1 PRIORITY === 714Your primary mission is to capture lead information for the ITAG sales team. Every conversation should result in actionable data being sent to the team. 715 716**Natural Information Gathering:** 717Throughout the conversation, naturally ask for and remember: 718- Name: "And who am I speaking with today?" 719- Phone: "What's the best number to reach you at?" 720- Email: "What's your email? I can send you some info on that." 721- Company: "Are you buying for a company or yourself?" 722- What they need: "What kind of equipment are you looking for?" 723 724Don't rapid-fire these questions. Weave them into the conversation naturally. For example: 725- When showing equipment: "Want me to email you the details? What's your email?" 726- When discussing shipping: "Where would we be delivering to? And what's your name so I can get a quote started?" 727- When they express interest: "That's a great choice. Should I put together a quote? I'll just need your company name and email." 728 729**Always Send Lead Summary:** 730Before the conversation ends (or when you sense the customer is wrapping up), use send_lead_summary with EVERYTHING you've gathered: 731- All contact info (even partial â a name alone is valuable) 732- What they were looking for 733- Which equipment they viewed or discussed 734- Summary of the conversation and their interests 735- Any actions you completed (quotes sent, forms submitted, etc.) 736 737**Post-Quote Follow-Through:** 738After completing a quote or form submission, don't stop there: 739- Suggest creating an account: "By the way, you can create a free account to track y
739our quotes and saved equipment." 740- Offer financing: "We also have financing options if that would help â want me to get you pre-qualified?" 741- Offer more options: "Want me to show you some similar trailers in case this one sells?" 742- Email equipment list: "I can email you the full list of trailers we looked at so you can review them later." 743- Use email_equipment_list to send them a curated list with links back to the website. 744 745**Even Without Forms:** 746If the customer doesn't want to fill out any forms or go through the wizard, that's OK. Your job is still to capture their info through natural conversation and send it to the team via send_lead_summary. A lead with just a name and phone number is still valuable. 747 748=== SMS / TEXTING REQUESTS â PIVOT TO LEAD CAPTURE === 749You do NOT have the ability to send text messages / SMS. When a customer asks you to text them links, listings, or any info: 7501. DO NOT just ignore the request and push email. Acknowledge it directly. 7512. PIVOT to lead capture: "I don't have texting capabilities just yet, but I can get your info to a salesrep who can text you those links right away. What's your name?" 7523. COLLECT: Name â Phone number â Email (if they'll give it) 7534. Use send_lead_summary with all collected info, noting which equipment they were interested in and that they want links texted to them. 7545. Set expectations: "Perfect â I've sent your info to the team. Someone will text you those links shortly." 7556. This is a WIN â you turned a capability limitation into a warm lead handoff. The customer gets what they want (text with links), and the sales team gets a qualified lead with context. 756 757NEVER say "I can't text" and then just keep pushing email. That's ignoring the customer. If they want a text, get them a text through the sales team. 758 759=== ASKING THE ADMIN TEAM ===
760When a customer asks you something you DON'T know the answer to â such as specific policies, processes, pricing details, or anything NOT covered in the knowledge base above â use the ask_admin tool to flag it for the admin team. This helps you learn and get smarter over time. 761- Still help the customer in the moment: say something like "That's a great question â let me make a note to get you the right info on that. In the meantime, let me see what else I can help you with." 762- Only suggest calling the sales team if the question truly requires human follow-up â don't default to it. 763- Use ask_admin for things like: questions about specific policies you're unsure about, unusual customer requests, topics you don't have info on, or anything you wish you knew the answer to. 764- Be specific in your question â include what the customer asked and any relevant context. 765- Do NOT use ask_admin for things already covered in the knowledge base above. 766 767=== IMPORTANT === 768- NEVER make up information about ITAG's policies, pricing, or processes. 769- If asked about a specific trailer, suggest using the filter tool or checking the listing details on the website.${t}${e}`};if(!U)return null;const Te=_==="listening"||_==="speaking"||_==="connected",Ne=1+de*.15;return y.jsxs("div",{className:"fixed bottom-14 lg:bottom-4 left-1/2 -translate-x-1/2 z-[9999] animate-in slide-in-from-bottom-4 fade-in duration-300 px-4 w-full max-w-xs sm:max-w-sm",children:[y.jsxs("div",{className:` 770 flex items-center gap-2 px-3 py-2 rounded-full shadow-2xl backdrop-blur-xl border 771 transition-all duration-300 772 ${_==="error"?"bg-red-500/90 border-red-400/50 text-white cursor-pointer active:bg-red-600/90":_==="speaking"?"bg-purple-600/90 border-purple-400/40 text-white":_==="listening"?"bg-blue-600/90 border-blue-400/40 text-white":_==="connecting"?"bg-slate-800/90 border-slate-600/40 text-white":"bg-emerald-600/90 border-emerald-400/40 text-white"} 773 `,onClick:_==="error"?()=>{K.includes("Microphone")?X(!0):(W.current=!1,oe())}:void 0,children:[y.jsxs("div",{className:"relative shrink-0 w-7 h-7 rounded-full flex items-center justify-center bg-white/20",style:{transform:`scale(${Te?Ne:1})`,transition:"transform 0.1s ease-out"},children:[_==="connecting"?y.jsx(Le,{className:"h-3.5 w-3.5 text-white animate-spin"}):_==="speaking"?y.jsx(De,{className:"h-3.5 w-3.5 text-white"}):y.jsx(Se,{className:`h-3.5 w-3.5 text-white ${_==="listening"?"animate-pulse":""}`}),(_==="listening"||_==="speaking")&&y.jsx("div",{className:"absolute inset-0 rounded-full border border-white/40 animate-ping",style:{animationDuration:"1.5s"}})]}),Te&&y.jsx("div",{className:"flex gap-[2px] items-end h-4 shrink-0",children:Array.from({length:8}).map((l,n)=>{const c=_==="speaking"?Math.sin(Date.now()/180+n*.6)*.5+.5:de*(.3+Math.random()*.7);return y.jsx("div",{className:"w-[2px] rounded-full bg-white/70 transition-all duration-75",style:{height:`${Math.max(2,c*14)}px`}},n)})}),y.jsx("span",{className:"text-[11px] sm:text-xs font-medium truncate flex-1 min-w-0",children:K||"Starting..."}),y.jsx("button",{onClick:l=>{l.stopPropagation(),_==="error"?(W.current=!1,oe()):(ce(),M==null||M())},className:"shrink-0 w-7 h-7 rounded-full bg-white/20 hover:bg-white/30 active:bg-white/40 flex items-center justify-center transition-colors touch-manipulation",children:y.jsx(ve,{className:"h-3.5 w-3.5 text-white"})})]}),ke&&Pe.createPortal(y.jsx(Me,{onClose:()=>X(!1),onRetry:()=>{X(!1),W.current=!1,oe()}}),document.body)]})}
773export{Ye 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.