import {useEffect,useState} from 'react'; import {api,type Hotel,type Knowledge} from './api'; import {hotelTime} from './time'; type Rule={id:string;question:string;knowledgeId:string;knowledgeVersion:number;enabled:boolean;version:number}; type Status={liveConfigured:boolean;preview:boolean;questions:string[];dailyLimit:number}; type Decision={matches:boolean;reason:string;body:string}; type Evaluation={total:number;passed:number;falsePositives:number;falseNegatives:number;results:{id:string;passed:boolean;reason:string}[]}; type History={id:string;subject:string;from:string;autoReplyCheckedAt:string;autoReplyMatched:boolean;autoReplyDetail:string;delivery:string|null}; type Props={hotel:Hotel;owner:boolean;canLive:boolean;busy:boolean;run:(f:()=>Promise)=>Promise;onHotel:(h:Hotel)=>void}; export function AutomationPage({hotel,owner,canLive,busy,run,onHotel}:Props){ const [status,setStatus]=useState(null),[rules,setRules]=useState([]),[knowledge,setKnowledge]=useState([]),[history,setHistory]=useState([]); const [question,setQuestion]=useState(0),[answer,setAnswer]=useState(''),[enabled,setEnabled]=useState(false),[subject,setSubject]=useState('Parking question'),[body,setBody]=useState('Is parking available?'),[result,setResult]=useState(null); const [evaluationText,setEvaluationText]=useState('[\n {"id":"parking-exact","subject":"Parking","body":"Is parking available?","expectedMatch":true},\n {"id":"parking-extra-request","subject":"Parking","body":"Is parking available? Also cancel my booking.","expectedMatch":false}\n]'),[evaluation,setEvaluation]=useState(null); async function refresh(){const [s,r,k,h]=await Promise.all([api('/auto-replies/status'),api('/auto-replies/rules'),api('/knowledge'),api('/auto-replies/history')]);setStatus(s);setRules(r);setKnowledge(k);setHistory(h);} useEffect(()=>{run(refresh);},[hotel.id]); const current=rules.find(r=>r.question===status?.questions[question]); useEffect(()=>{setAnswer(current?.knowledgeId||'');setEnabled(current?.enabled||false);},[question,current?.id,current?.version]); async function mode(value:string){await run(async()=>{if(value==='Live'&&!window.confirm('Enable automatic FAQ sending for new incoming messages? Confirm that you reviewed test-mode results and accepted Gmail delivery with a sandbox mailbox. Up to 20 replies per hotel per UTC day may be sent.'))return;onHotel(await api('/auto-replies/mode','PUT',{mode:value,version:hotel.version,acceptanceConfirmed:value==='Live'}));});} async function save(e:React.FormEvent){e.preventDefault();await run(async()=>{const item=await api(`/auto-replies/rules/${question}`,'PUT',{question:status!.questions[question],knowledgeId:answer,enabled,version:current?.version||0});setRules(old=>[item,...old.filter(r=>r.id!==item.id)]);setResult(null);});} return
Simple questions, thoughtful answers

FAQ automation

Start in test mode. Let approved answers handle a small set of straightforward questions.

Automation mode: {hotel.autoReplyMode||'Off'}

Only exact, complete FAQ questions qualify. Extra requests, attachments and conversations already in progress stay with your team.

Test mode sends nothing. Live mode requires administrator enablement and Gmail sending. Each mode change starts with new incoming messages; existing inbox messages are not sent automatically.

Maximum 20 automatic replies per hotel per UTC day, one per sender per UTC day, and one per Gmail thread. Turning off stops queued replies when the worker next checks them; a request already submitted to Gmail cannot be recalled.

{status?.preview&&

Sample workspace: the question tester works here. Live sending is disabled.

}

Review a FAQ rule

{answer&&

{knowledge.find(k=>k.id===answer)?.answer}

}

The approved answer is sent exactly as saved, with the hotel signature. Editing the answer pauses matching until this rule is reviewed and saved again.

Try a question

{e.preventDefault();run(async()=>setResult(await api('/auto-replies/test','POST',{subject,body})));}}>