// direction-a-demos.jsx — Live, interactive demos powered by window.claude.complete // All three demos mirror the actual shape of the product's AI flows: // - PhotoQuoteDemo: mirrors src/lib/ai/photoDiagnosis.ts (DIAGNOSIS_PROMPT) // - SmartSlotsDemo: mirrors src/app/api/book/smart-slots — pure JS, no AI // - QuickJobDemo: text → parsed-job, mirrors quick-job parsing flow // ─── 1. Photo → Quote demo ───────────────────────────────────── // We can't send actual images to window.claude.complete, so we pass a // detailed text DESCRIPTION of the scene. Claude returns the same // JSON shape the real product uses. Each click = fresh generation. // SVG fallback content for each scenario — rendered both as React thumbnails // AND as image-slot `src` fallback (via data URL) so the preview area looks // good out-of-the-box and is overridable when the user drops a real photo. const SVG_FALLBACKS = { capacitor: ` ${Array.from({length:6},(_,i)=>{const a=(i/6)*Math.PI*2; return ``}).join('')} Illustration — drop a real photo to replace `, valve: ` Illustration — drop a real photo to replace `, panel: ` ${Array.from({length:8},(_,i)=>{const isProblem=i===7; const y=40+i*24; return ` `}).join('')} Illustration — drop a real photo to replace `, }; function svgFallbackUrl(kind) { return 'data:image/svg+xml;utf8,' + encodeURIComponent(SVG_FALLBACKS[kind] || ''); } const PHOTO_SCENARIOS = [ { id: 'capacitor', label: 'AC condenser, bulged capacitor', trade: 'HVAC', photo: (window.__resources && window.__resources.dxCapacitor) || 'assets/dx-capacitor.jpg', desc: 'Outdoor AC condenser unit, top panel removed. The dual-run capacitor (round metal can) shows a clearly bulged top and dark brown residue weeping from the seam. Brand: GE, 45/5 µF, 370V. Surrounding wires are intact, fan blades show no damage. Unit appears 8-10 years old based on weathering.', // Placeholder SVG illustration svg: 'capacitor', }, { id: 'valve', label: 'Leaking shutoff valve under sink', trade: 'Plumbing', photo: (window.__resources && window.__resources.dxValve) || 'assets/dx-valve.jpg', desc: 'Under-sink shutoff valve, 1/2 inch copper supply. Visible mineral buildup and active drip at the packing nut. Compression fitting in place. Surrounding cabinet floor shows water staining ~6 inches in diameter around the valve. P-trap and supply lines otherwise intact.', svg: 'valve', }, { id: 'panel', label: 'Electrical panel, double-tapped breaker', trade: 'Electrical', photo: (window.__resources && window.__resources.dxPanel) || 'assets/dx-panel.jpg', desc: 'Residential 200A main panel, cover removed. Bottom-right breaker has two wires connected to a single terminal (double-tap) — a 14 AWG and a 12 AWG. No signs of charring. Panel brand: Square D QO. Bus bars appear clean. Adjacent breakers correctly landed single-conductor.', svg: 'panel', }, ]; // Pre-written, on-brand results used when the live model isn't reachable // (e.g. on the deployed static site, where window.claude doesn't exist). Same // JSON shape the real Photo Diagnosis flow returns, one per sample photo. const PHOTO_FALLBACKS = { capacitor: { diagnosis: 'The dual-run capacitor appears to show a bulged top with dark residue weeping from the seam, consistent with a capacitor that is failing or has failed. The surrounding condenser components appear intact.', category: 'hvac', attentionLevel: 'prompt', customerSummary: 'A small part in your AC unit looks worn and is the likely reason it isn’t cooling well. It’s a common, quick replacement.', possibleRepair: 'Replace dual-run capacitor', parts: [{ name: 'Dual-run capacitor, 45/5 µF 370V', qty: 1, unit: 'each', estimatedCost: 24 }], laborHours: 0.75, hourlyRate: 125, totalEstimate: 125, }, valve: { diagnosis: 'The shutoff valve appears to show mineral buildup and an active drip at the packing nut, consistent with worn valve packing. Staining on the cabinet floor suggests the drip has been ongoing.', category: 'plumbing', attentionLevel: 'prompt', customerSummary: 'The shutoff valve under your sink is dripping and showing its age. Replacing it stops the leak and gives you a reliable shutoff.', possibleRepair: 'Replace 1/2" shutoff valve', parts: [ { name: '1/2" compression shutoff valve', qty: 1, unit: 'each', estimatedCost: 12 }, { name: 'Braided supply line', qty: 1, unit: 'each', estimatedCost: 8 }, ], laborHours: 1.0, hourlyRate: 110, totalEstimate: 136, }, panel: { diagnosis: 'The bottom-right breaker appears to show two wires landed on a single terminal (a double-tap), with no visible charring. Many breakers aren’t listed for more than one conductor at a terminal.', category: 'electrical', attentionLevel: 'safety_review_recommended', customerSummary: 'Two wires are sharing one breaker slot, which isn’t ideal. Adding a breaker so each wire has its own spot tidies it up safely.', possibleRepair: 'Add breaker, separate double-tap', parts: [{ name: 'Single-pole 15A breaker (Square D)', qty: 1, unit: 'each', estimatedCost: 9 }], laborHours: 1.0, hourlyRate: 135, totalEstimate: 147, }, }; function PhotoSvgIllustration({ kind, color = '#5a5a60' }) { // Simple line-art representations — placeholders the user can replace // with real jobsite photos. if (kind === 'capacitor') { return ( {/* condenser fan grille */} {Array.from({ length: 6 }).map((_, i) => { const a = (i / 6) * Math.PI * 2; return ; })} {/* capacitor */} {/* drip */} ); } if (kind === 'valve') { return ( {/* cabinet floor */} {/* water stain */} {/* pipe vertical */} {/* valve body */} {/* handle */} {/* outlet */} {/* drip */} ); } if (kind === 'panel') { return ( {/* breakers — two columns */} {Array.from({ length: 8 }).map((_, i) => { const row = i; const isProblem = i === 7; return ( ); })} {/* double-tap indicator wires */} ); } return null; } function PhotoQuoteDemo() { const DA = window.DA; const m = window.useIsMobile(); const [selected, setSelected] = React.useState(PHOTO_SCENARIOS[0]); const [state, setState] = React.useState('idle'); // idle | analyzing | done | error const [stage, setStage] = React.useState(0); // 0..3 for analyzing sub-states const [result, setResult] = React.useState(null); const ANALYZE_STAGES = [ 'Analyzing photo…', 'Identifying components…', 'Checking trade context…', 'Drafting quote…', ]; const run = async () => { setState('analyzing'); setStage(0); setResult(null); // Visible stage progression while the call is in flight const stageTimers = [ setTimeout(() => setStage(1), 900), setTimeout(() => setStage(2), 2000), setTimeout(() => setStage(3), 3400), ]; try { const prompt = `You are an expert field service diagnostician helping a licensed tradesperson assess a photo. The photo description below is what a ${selected.trade.toLowerCase()} tradesperson sees on-site. PHOTO DESCRIPTION: ${selected.desc} Return ONLY valid JSON (no markdown, no code fences): { "diagnosis": "Observational description of what appears in the photo and the likely issue (2-3 sentences). Use observational language: 'appears to show', 'consistent with', 'may indicate'.", "category": "plumbing" | "electrical" | "hvac" | "general", "attentionLevel": "routine" | "prompt" | "safety_review_recommended", "customerSummary": "Plain-language summary for the homeowner — no jargon (1-2 sentences). Do not assert damage or danger.", "possibleRepair": "Most likely repair scope (short — 4-8 words, e.g. 'Replace dual-run capacitor')", "parts": [ { "name": "Part name (trade-standard)", "qty": 1, "unit": "each", "estimatedCost": 38 } ], "laborHours": 1.0, "hourlyRate": 125, "totalEstimate": 178 } Rules: - Use observational language; never assert damage, danger, or urgency - Do NOT use words: "emergency", "critical", "urgent", "must", "required", "immediately", "hazard" - 1-3 parts max for this demo, with realistic wholesale costs - totalEstimate = (laborHours * hourlyRate) + sum(parts.qty * parts.estimatedCost * 1.30)`; const raw = (window.claude && typeof window.claude.complete === 'function') ? await window.claude.complete(prompt) : null; let parsed = null; if (raw != null) { try { let s = raw.trim(); if (s.startsWith('```')) s = s.replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, ''); parsed = JSON.parse(s); } catch { parsed = null; } } // No live model (deployed static site) or a bad/failed response: fall back // to the pre-written result so the demo always works. Let the analyzing // animation finish first so it still feels live. if (!parsed) { await new Promise(r => setTimeout(r, 3800)); parsed = PHOTO_FALLBACKS[selected.id] || PHOTO_FALLBACKS.capacitor; } else { await new Promise(r => setTimeout(r, 400)); } stageTimers.forEach(clearTimeout); setResult(parsed); setState('done'); } catch (e) { // Absolute last resort — still show a result rather than an error. stageTimers.forEach(clearTimeout); setResult(PHOTO_FALLBACKS[selected.id] || PHOTO_FALLBACKS.capacitor); setState('done'); } }; const reset = () => { setState('idle'); setResult(null); }; return (
LIVE DEMO · TRY IT
Powered by werkd's real Photo Diagnosis flow
{/* LEFT — image picker */}
1 · PICK A JOBSITE PHOTO
{/* Curated jobsite photo for the selected scenario — read-only. No visitor upload: the example photos reliably produce a strong diagnosis, which a random dropped image wouldn't guarantee. */}
{selected.trade.toUpperCase()}
{PHOTO_SCENARIOS.map((s) => (
Illustrative examples, not live customer jobs.
{state === 'done' && ( )}
{/* RIGHT — output */}
2 · WERKD'S DRAFT
{state === 'idle' && (
Pick a photo and tap Diagnose & draft quote to see werkd in action.
)} {state === 'analyzing' && (
{ANALYZE_STAGES.map((label, i) => (
{i < stage ? : i === stage ? : null}
{label}
))}
)} {state === 'error' && (
Couldn't reach the model. .
)} {state === 'done' && result && (
{/* Diagnosis card */}
LIKELY DIAGNOSIS · {(result.category || 'general').toUpperCase()}
{result.diagnosis}
For the customer: {result.customerSummary}
{/* Recommended repair */}
SUGGESTED REPAIR
{result.possibleRepair}
{/* Quote lines */}
DRAFT QUOTE
{(result.parts || []).map((p, i) => (
{p.name} × {p.qty} ${(p.qty * p.estimatedCost * 1.3).toFixed(2)}
))}
Labor ({result.laborHours} hrs @ ${result.hourlyRate}/hr) ${(result.laborHours * result.hourlyRate).toFixed(2)}
Total estimate ${result.totalEstimate}
Demo output. Generated live by Claude using the same prompt structure as werkd's shipped Photo Diagnosis flow. You always review before anything goes to the customer.
)}
); } // ─── 2. Smart Slots booking demo ─────────────────────────────── function SmartSlotsDemo() { const DA = window.DA; // Each day's `existingJobs` + Werkd's picked `slots` are coherent: // Werkd's windows never overlap the existing jobs (that's the whole point — // the route protects itself). // `mins` = start time in minutes from midnight, used for timeline sorting. const DAYS = [ { d: 'TUE', n: '24', slots: [ { time: '12:15 — 1:45 PM', mins: 735, label: 'Best fit', sub: 'After Parker, before Okafor · on route', best: true }, { time: '3:30 — 5:00 PM', mins: 930, label: 'Late open', sub: 'End of day · on route home' }, ], existingJobs: [ { time: '8:30 AM', mins: 510, name: 'Rob Vance', desc: 'Faucet swap', amt: '$165' }, { time: '10:00 AM', mins: 600, name: 'Tom Parker', desc: 'Water-heater swap', amt: '$1,240' }, { time: '2:00 PM', mins: 840, name: 'Grace Okafor', desc: 'Toilet replace', amt: '$420' }, ], }, { d: 'WED', n: '25', slots: [ { time: '9:00 — 10:15 AM', mins: 540, label: 'Best fit', sub: 'On route between Chen & Delgado', best: true }, { time: '11:30 AM — 12:45 PM', mins: 690, label: 'Open window', sub: 'After Delgado, before Parker · on route' }, { time: '4:45 — 6:00 PM', mins: 1005, label: 'Late open', sub: 'End of day · on route home' }, ], existingJobs: [ { time: '8:00 AM', mins: 480, name: 'Mike Chen', desc: 'Drain clear', amt: '$185' }, { time: '10:30 AM', mins: 630, name: 'Sarah Delgado', desc: 'Faucet swap', amt: '$165' }, { time: '1:00 PM', mins: 780, name: 'Tom Parker', desc: 'Water-heater swap', amt: '$1,240' }, { time: '3:30 PM', mins: 930, name: 'Dave Wilson', desc: 'Outlet repair', amt: '$140' }, ], }, { d: 'THU', n: '26', slots: [ { time: '4:00 — 5:00 PM', mins: 960, label: 'Best fit', sub: 'After Roberts · on the way home', best: true }, ], existingJobs: [ { time: '9:00 AM', mins: 540, name: 'Priya Patel', desc: 'HVAC seasonal tune-up', amt: '$189' }, { time: '10:30 AM', mins: 630, name: 'Karen Hines', desc: 'Filter swap', amt: '$60' }, { time: '1:30 PM', mins: 810, name: 'Dan Roberts', desc: 'Kitchen drain clear', amt: '$185' }, ], }, { d: 'FRI', n: '27', slots: [ { time: '10:30 AM — 12:00 PM', mins: 630, label: 'Best fit', sub: 'After Nguyen, before Chen · on route', best: true }, { time: '1:30 — 2:45 PM', mins: 810, label: 'Open window', sub: 'After Chen, before Barnes · on route' }, ], existingJobs: [ { time: '9:00 AM', mins: 540, name: 'Linh Nguyen', desc: 'Disposal install', amt: '$210' }, { time: '12:30 PM', mins: 750, name: 'Mike Chen', desc: 'Drain clear', amt: '$185' }, { time: '3:00 PM', mins: 900, name: 'Ed Barnes', desc: 'Hose bib replace', amt: '$165' }, ], }, ]; const [dayIdx, setDayIdx] = React.useState(1); const [picked, setPicked] = React.useState(null); // null | slot const [confirmed, setConfirmed] = React.useState(false); const m = window.useIsMobile(); // Reset confirmation when day changes React.useEffect(() => { setPicked(null); setConfirmed(false); }, [dayIdx]); const day = DAYS[dayIdx]; // Build the dispatcher timeline: existing jobs + (optionally) the just-booked // appointment, sorted by start time. Pull the "8:30 AM" label out of the // picked slot's "8:30 — 10:00 AM" string for display. const timelineRows = React.useMemo(() => { const rows = day.existingJobs.map((j) => ({ ...j, kind: 'existing' })); if (picked && confirmed) { // Pluck start time + AM/PM out of "8:30 — 10:00 AM" const m = picked.time.match(/^(\d{1,2}:\d{2})\s*[—-]\s*\d{1,2}:\d{2}\s*(AM|PM)/); const startLabel = m ? `${m[1]} ${m[2]}` : picked.time.split(/\s*[—-]\s*/)[0]; rows.push({ time: startLabel, mins: picked.mins, name: 'Aria Russell', desc: 'Smart thermostat (new)', amt: '$420', kind: 'justBooked', }); } return rows.sort((a, b) => a.mins - b.mins); }, [day, picked, confirmed]); return (
LIVE DEMO · BOOK A TIME
You are Aria — Quote #1042 just landed in your inbox
{/* LEFT — booking flow */}
QUOTE #1042 · APPROVED — PICK A TIME
Smart thermostat install
Eli picked the windows that fit his route this week.
{/* Day picker */}
{DAYS.map((d, i) => ( ))}
{/* Slots */}
PICKED BY WERKD · {day.d} {day.n}
{day.slots.map((slot, i) => { const isPicked = picked?.time === slot.time; return ( ); })}
Other times aren't shown — Eli's day is already full or off-route.
{/* RIGHT — Eli's view (the dispatcher's reaction) */}
ELI'S DISPATCH BOARD — {day.d} {day.n}
{/* Timeline */}
{timelineRows.map((row, i) => (
{row.time} — {row.name} {row.kind === 'justBooked' && ( NEEDS OK )} {row.amt}
{row.desc}
))}
{confirmed && (
Booked. It lands on Eli's board for his OK. Once he accepts, Aria's confirmed — and gets an "on my way" heads-up the morning of: a portal push if she's added it to her phone, or a text from Eli's own phone.
)} {!confirmed && (
Pick a time on the left — watch it slot into Eli's day.
)}
); } // ─── 3. Quick Job text → parsed job demo ─────────────────────── const EXAMPLE_JOB_REQUESTS = [ 'Tom Parker water heater swap Tuesday at 9', 'Aria Russell needs the AC looked at — sometime Friday afternoon', 'Roberta Diaz, leaky toilet, asap if I can', 'New customer Tim at 412 Oak — frayed wire in the panel, this week', ]; function quickJobFallback(input) { const PRESETS = { 'Tom Parker water heater swap Tuesday at 9': { customer: 'Tom Parker', isNewCustomer: false, service: 'Water heater swap', trade: 'plumbing', preferredWindow: 'Tuesday at 9:00 AM', urgency: 'routine', estimatedDurationHours: 3.0, addressGiven: null, confidence: 'high', needsClarification: null }, 'Aria Russell needs the AC looked at — sometime Friday afternoon': { customer: 'Aria Russell', isNewCustomer: false, service: 'AC inspection / diagnostic', trade: 'hvac', preferredWindow: 'Friday afternoon', urgency: 'routine', estimatedDurationHours: 1.0, addressGiven: null, confidence: 'medium', needsClarification: 'Is the unit not cooling or making a noise? Helps bring the right parts.' }, 'Roberta Diaz, leaky toilet, asap if I can': { customer: 'Roberta Diaz', isNewCustomer: false, service: 'Leaking toilet repair', trade: 'plumbing', preferredWindow: 'This week — ASAP', urgency: 'prompt', estimatedDurationHours: 1.5, addressGiven: null, confidence: 'high', needsClarification: null }, 'New customer Tim at 412 Oak — frayed wire in the panel, this week': { customer: 'Tim', isNewCustomer: true, service: 'Frayed wire in panel — repair', trade: 'electrical', preferredWindow: 'This week', urgency: 'safety_review_recommended', estimatedDurationHours: 1.5, addressGiven: '412 Oak', confidence: 'medium', needsClarification: 'I’d confirm whether the panel shows any heat or smell before scheduling.' }, }; const key = (input || '').trim(); if (PRESETS[key]) return PRESETS[key]; // Best-effort heuristic parse for anything the visitor types themselves. const text = key.toLowerCase(); const trade = /(water heater|leak|toilet|drain|faucet|pipe|valve|sink|sewer)/.test(text) ? 'plumbing' : /(\bac\b|a\/c|furnace|hvac|thermostat|cooling|condenser|heat pump)/.test(text) ? 'hvac' : /(wire|panel|outlet|breaker|electric|gfci|fixture|circuit)/.test(text) ? 'electrical' : 'general'; const isNew = /new customer|new client/.test(text); const nameMatch = key.match(/[A-Z][a-z]+(?:\s[A-Z][a-z]+)?/); const customer = nameMatch ? nameMatch[0] : 'New customer'; const dayMatch = key.match(/\b(mon|tues|wednes|thurs|fri|satur|sun)day\b/i); const partOfDay = /afternoon/.test(text) ? ' afternoon' : /morning/.test(text) ? ' morning' : /evening/.test(text) ? ' evening' : ''; const asap = /asap|today|right away|emergency|as soon/.test(text); const preferredWindow = asap ? 'This week — ASAP' : dayMatch ? (dayMatch[0][0].toUpperCase() + dayMatch[0].slice(1).toLowerCase() + partOfDay) : /this week/.test(text) ? 'This week' : partOfDay ? partOfDay.trim().replace(/^./, (c) => c.toUpperCase()) : 'To be scheduled'; const addrMatch = key.match(/\b\d{2,5}\s+[A-Z][a-z]+/); const dur = trade === 'plumbing' ? 1.5 : trade === 'electrical' ? 1.5 : trade === 'hvac' ? 1.0 : 1.0; const urgency = /frayed wire|spark|burning|smell|gas|exposed/.test(text) ? 'safety_review_recommended' : asap ? 'prompt' : 'routine'; return { customer, isNewCustomer: isNew, service: key.length > 42 ? key.slice(0, 42).trim() + '…' : (key || 'New job'), trade, preferredWindow, urgency, estimatedDurationHours: dur, addressGiven: addrMatch ? addrMatch[0] : null, confidence: 'medium', needsClarification: 'I’d confirm the exact time and scope before locking it in.', }; } function QuickJobDemo() { const DA = window.DA; const m = window.useIsMobile(); const [input, setInput] = React.useState(EXAMPLE_JOB_REQUESTS[0]); const [state, setState] = React.useState('idle'); const [result, setResult] = React.useState(null); const run = async () => { if (!input.trim()) return; setState('parsing'); setResult(null); const prompt = `You are Werkd, an AI scheduling assistant for tradespeople. Parse the request below into a structured job draft. REQUEST: "${input}" Return ONLY valid JSON (no markdown, no code fences): { "customer": "Customer name (or 'New customer' if not given)", "isNewCustomer": true | false, "service": "Short service description (4-8 words)", "trade": "plumbing" | "electrical" | "hvac" | "general", "preferredWindow": "Plain-language day/time (e.g. 'Tuesday at 9:00 AM', 'Friday afternoon', 'This week — ASAP')", "urgency": "routine" | "prompt" | "safety_review_recommended", "estimatedDurationHours": 1.5, "addressGiven": "Address if mentioned, else null", "confidence": "high" | "medium" | "low", "needsClarification": "Optional — one sentence on what you'd ask before scheduling (or null)" } Rules: - Match the requester's exact day/time language — do not invent a specific time if they said "afternoon" - estimatedDurationHours: realistic for the trade and scope (0.5 to 6.0) - If anything is genuinely ambiguous, set confidence: "low" and surface it in needsClarification - Never use words: "emergency", "critical", "urgent", "must", "required", "immediately"`; try { const raw = (window.claude && typeof window.claude.complete === 'function') ? await window.claude.complete(prompt) : null; let parsed = null; if (raw != null) { try { let s = raw.trim(); if (s.startsWith('```')) s = s.replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, ''); parsed = JSON.parse(s); } catch { parsed = null; } } if (!parsed) { await new Promise(r => setTimeout(r, 1100)); parsed = quickJobFallback(input); } else { await new Promise(r => setTimeout(r, 300)); } setResult(parsed); setState('done'); } catch { setResult(quickJobFallback(input)); setState('done'); } }; return (
LIVE DEMO · QUICK JOB
Type it the way you'd say it
1 · DICTATE OR TYPE THE JOB
{ setInput(e.target.value); setResult(null); setState('idle'); }} onKeyDown={(e) => e.key === 'Enter' && run()} placeholder="e.g. Tom Parker, water heater swap, Tuesday at 9" style={{ width: '100%', padding: '14px 56px 14px 18px', fontSize: 15, fontFamily: 'var(--font-ui)', background: '#fff', border: `1px solid ${DA.hairline}`, borderRadius: 10, color: DA.text, }} />
{EXAMPLE_JOB_REQUESTS.map((ex, i) => ( ))}
{state === 'parsing' && (
Parsing — werkd is matching this to your customer list, schedule, and trade rates…
)} {state === 'error' && (
Couldn't parse. .
)} {state === 'done' && result && (
2 · WERKD'S JOB DRAFT {result.confidence?.toUpperCase()} CONFIDENCE
{/* Card header */}
{result.customer} {result.isNewCustomer && ( NEW )}
{result.service}
{result.trade?.toUpperCase()}
{/* Card body */}
{[ { l: 'Preferred window', v: result.preferredWindow, icon: 'clock' }, { l: 'Estimated duration', v: `${result.estimatedDurationHours} hrs`, icon: 'briefcase' }, result.addressGiven ? { l: 'Address', v: result.addressGiven, icon: 'map-pin' } : null, { l: 'Attention level', v: result.urgency === 'routine' ? 'Routine' : result.urgency === 'prompt' ? 'Recommend prompt review' : result.urgency === 'safety_review_recommended' ? 'In-person safety review recommended' : result.urgency, icon: 'alert-circle' }, ].filter(Boolean).map((row, i) => (
{row.l} {row.v}
))}
{/* Clarification */} {result.needsClarification && (
Werkd will ask first: {result.needsClarification}
)}
Demo output. Generated live by Claude. In the real app this parses your voice or text into a job draft that you review before it goes on the schedule.
)}
); } // ─── 4. Ask Werkd demo ──────────────────────────────────────────── // Mirrors src/components/v2/AskPanel.tsx — a real Claude-powered Q&A surface // with tool-use against the business's actual data (customers, invoices, // jobs). Same four starter prompts as the real panel. These are VERBATIM real // output captured from the production app (July 2026), word-for-word, not a // paraphrase or a live API call — see the ask() handler below, which renders // this text directly rather than calling window.claude.complete(). A live call // would almost always succeed and paraphrase the answer, which defeats the // point of showing exactly what the real app says. Note: this breaks from the // shared Aria Russell/Mike Chen/Dave Wilson thread used in demos 1-3, since // accuracy to real output matters more here than cross-demo narrative continuity. const ASK_FALLBACKS = { 'Who owes me money right now?': `Outstanding: $7,938 (11 invoices) OVERDUE (pay these first): Maria Hernandez: $420 (INV-1005, 22 days late) Karen Mitchell: $149 (INV-1008, 17 days late) SENT, NOT YET DUE: Pinecrest Apartments: $1,340 (due July 15) Rosa Delgado: $198 (due July 20) Karen Mitchell: $171 (due July 21) Sandra Liu: $630 (due Aug 2) Brenda Carlson: $134 (due Aug 2) DRAFTS (ready to send): Frank Mueller: $4,505 (due July 24) Greg Sanderson: $391 (due July 20) Sandra Liu: $0 (draft only) Want me to help you chase down the overdue ones, or send those drafts out?`, 'How much have I made this month?': `July so far: Collected: $407.55 (INV-1015, Brenda Carlson) Outstanding: $764.05 (2 invoices sent, unpaid) Sandra Liu — $629.99 Brenda Carlson — $134.06 Total billed: $1,171.60 It's only July 5th, but those two outstanding invoices are due Aug 1. Want me to send a reminder to Sandra or Brenda?`, 'What jobs do I have coming up?': `Coming up this week: Mon, Jul 6 9:30 AM — Brenda Carlson, smart thermostat install (Roy) — $295 11:00 AM — Pinecrest Apartments, RTU filter service Bldgs A & B (Ogden) — $620 Tue, Jul 7 9:45 AM — Greg Sanderson, whole-house humidifier install (Farmington) — $1,025 Total scheduled: $1,940 Also flagging — Greg Sanderson (Jul 2) and Maria Hernandez (Jul 4) show as scheduled but those dates have passed. Want me to pull those up to mark complete and invoice?`, 'Any quotes expiring soon?': `One quote expiring soon: Greg Sanderson — Mini-split (garage workshop) $3,300 | Expires Jul 11 (6 days) Status: Sent Jun 20 Want me to follow up with Greg, or push the expiration date?`, }; const ASK_SUGGESTIONS = Object.keys(ASK_FALLBACKS); function AskWerkdDemo() { const DA = window.DA; const m = window.useIsMobile(); const [asked, setAsked] = React.useState(null); const [state, setState] = React.useState('idle'); // idle | thinking | done const [answer, setAnswer] = React.useState(''); const ask = async (q) => { setAsked(q); setState('thinking'); setAnswer(''); // Render the captured real-app output directly — no live API call. These 4 // answers are verbatim production output (see ASK_FALLBACKS above); a live // window.claude.complete() call would almost always succeed and paraphrase // them, which defeats the point of showing real, word-for-word output. setTimeout(() => { setAnswer(ASK_FALLBACKS[q]); setState('done'); }, 650); }; // Tiny **bold** renderer — skips pulling in a markdown lib for one demo. const renderBold = (text) => { const parts = text.split(/(\*\*[^*]+\*\*)/g); return parts.map((p, i) => (p.startsWith('**') && p.endsWith('**')) ? {p.slice(2, -2)} : {p}); }; return (
Ask werkd
{!asked ? (
Ask anything about your business. Try one:
{ASK_SUGGESTIONS.map((q) => ( ))}
) : (
{asked}
{state === 'thinking' ? ( {[0, 1, 2].map((i) => ( ))} ) : renderBold(answer)}
{state === 'done' && ( )}
)}
); } // ─── Wrapper section that introduces the demos ───────────────── function ALiveDemos() { const DA = window.DA; const m = window.useIsMobile(); return (
TRY IT LIVE

What werkd does.
Try them yourself.

These are the same AI flows that run inside werkd — diagnose a photo, book a smart slot, dictate a quick job, or ask a real question about your business.

{/* FAB callout — every demo below lives behind the same ⊕, which is easy to miss as its own idea while you're focused on what it does. */}
One button. Every input.
Voice, photo, or text — every demo below lives behind the same ⊕. It's sized up for gloves, sits where your thumb already rests, and carries werkd's own pulse mark instead of a generic plus. One tap, and it's listening for whatever you throw at it.
DEMO 01

Photo → Quote

Pick a photo. Werkd suggests a diagnosis and drafts a quote. You review before anything goes out.
DEMO 02

Book a smart slot

You're the customer. Approve the quote, pick a time from werkd's route-aware windows, watch it slot into Eli's day.
DEMO 03

Quick job — say it, save it

Type the job the way you'd say it out loud. Werkd turns it into a structured draft you can confirm in one tap.
DEMO 04

Ask werkd anything

Not a script — real tool-use against your actual customers, invoices, and jobs. Ask a follow-up and it remembers the thread.
); } Object.assign(window, { ALiveDemos, PhotoQuoteDemo, SmartSlotsDemo, QuickJobDemo, AskWerkdDemo });