/* ============================================================
MSH PI, INC. — AI Chatbot (Standalone HTML embed)
Source: mshpi-chatbot.jsx (same logic, inlined here)
============================================================ */
const { useState, useEffect, useRef } = React;
// ── Config ──────────────────────────────────────────────────
const CHATBOT_PROXY = "/chatbot-proxy/";
const LOGO_URL = "/assets/MSHPI_CHAT_ICON.svg";
const CF_ENDPOINTS = [
CHATBOT_PROXY,
"/chatbot-proxy/index.php",
"https://mshpi-ai-proxy.jhan1102.workers.dev",
"https://mshpi-ai-proxy.jhan1102.workers.dev/"
];
// SYSTEM_PROMPT is stored securely in the Cloudflare Worker — not exposed here.
const PHONE_EN =
"📞 LA Office: (213) 383-7007\n" +
"📞 English 24hrs: (213) 784-2522\n" +
"📞 한국어 24시간: (323) 633-3232";
const ERR_MSG = "⚠️ Unable to connect. Please call us directly:\n" + PHONE_EN;
const DEFAULT_COLORS = {
headerBg: "#0f1e35", headerText: "#c9a84c",
bubbleBg: "#0f1e35", bubbleIcon: "#c9a84c",
userBubble: "#4E82EE", botBubble: "#f0f4f8",
chatBg: "#ffffff", sendBtn: "#4E82EE", accentColor: "#c9a84c",
};
const QUICK = [
{ label: "Our Services", msg: "What services do you offer?" },
{ label: "Contact Us", msg: "How do I contact MSH PI?" },
{ label: "Consultation", msg: "I would like a consultation." },
{ label: "서비스 안내", msg: "어떤 서비스를 제공하시나요?" },
{ label: "연락처", msg: "어떻게 연락하나요?" },
];
async function callWorker(messages) {
let lastErr;
for (const endpoint of CF_ENDPOINTS) {
try {
const r = await fetch(endpoint, {
method: "POST",
credentials: "omit",
headers: { "Content-Type": "application/json", "Accept": "application/json, text/plain;q=0.9, */*;q=0.8" },
body: JSON.stringify({ messages, site: "mshpi", source: "website" }),
});
if (!r.ok) throw new Error(`cf_fail_${r.status}_${endpoint}`);
const contentType = (r.headers.get("content-type") || "").toLowerCase();
if (contentType.includes("application/json")) {
const d = await r.json();
if (d?.text) return d.text;
if (typeof d?.reply === "string" && d.reply.trim()) return d.reply;
if (typeof d?.message === "string" && d.message.trim()) return d.message;
} else {
const t = (await r.text()).trim();
if (t) return t;
}
throw new Error("no_text");
} catch (err) {
lastErr = err;
}
}
throw lastErr || new Error("cf_fail");
}
function Bubble({ role, text, colors }) {
const isBot = role === "bot";
const lines = text.split("\n");
return (
{lines.map((line,i) => {
const isDisclaimer = line.includes("Many Korean Private Investigation agencies") || line.includes("MSH PI, INC. has been operating for more than 21 years") || line.includes("FOR INVESTIGATIVE SERVICES");
const isBold = !isDisclaimer && line.startsWith("**") && line.endsWith("**");
const clean = line.replace(/[*][*]([^*]+)[*][*]/g,"$1").replace(/[*]([^*]+)[*]/g,"$1");
if (!clean.trim()) return
;
const warn = clean.startsWith("⚠️");
return (
{clean.split(/(https?:\/\/\S+|\(\d{3}\)\s?\d{3}[-\s\.]\d{4}|\b\d{3}[-\.]\d{3}[-\.]\d{4}\b)/g).map((part,j) => {
if (part.match(/^https?:\/\//)) return
{part} ↗;
if (part.match(/^(?:\(\d{3}\)|\d{3}[-\.])/)) return
{part};
return
{part};
})}
);
})}
);
}
function AdminPanel({ colors, onSave, onClose }) {
const [local,setLocal]=useState({...colors});
const set=(k,v)=>{const n={...local,[k]:v};setLocal(n);onSave(n);};
const FIELDS=[["Header BG","headerBg"],["Header Text","headerText"],["Bubble BG","bubbleBg"],["Bubble Icon","bubbleIcon"],["User Msg BG","userBubble"],["Bot Msg BG","botBubble"],["Window BG","chatBg"],["Send Button","sendBtn"],["Accent","accentColor"]];
return (
e.stopPropagation()} style={{background:"#fff",borderRadius:14,padding:22,width:300,maxHeight:"85vh",overflowY:"auto",boxShadow:"0 16px 48px rgba(0,0,0,0.3)"}}>
🔧 Colour Settings
{FIELDS.map(([label,key])=>(
))}
);
}
function MSHPIChatbot() {
const [msgs,setMsgs]=useState([]); const [hist,setHist]=useState([]); const [input,setInput]=useState("");
const [busy,setBusy]=useState(false); const [quick,setQuick]=useState(true); const [open,setOpen]=useState(()=>{ try { return localStorage.getItem("mshpi_chat_open") !== "0"; } catch { return true; } });
const [admin,setAdmin]=useState(false); const [colors,setColors]=useState(DEFAULT_COLORS);
const bottomRef=useRef(null); const inputRef=useRef(null); const taRef=useRef(null);
useEffect(()=>{
setTimeout(()=>{
setMsgs([{id:1,role:"bot",text:"**안녕하세요! MSH PI, INC.에 연락해 주셔서 감사합니다.**\n**오늘 어떻게 도와드릴까요?**\n🇰🇷 웹사이트를 한국어로 보려면 메뉴에서 한국 국기를 선택하세요.\n\n**Hello! Thank you for contacting MSH PI, INC.**\n**How can I help you today?**"}]);
},300);
},[]);
useEffect(()=>{ bottomRef.current?.scrollIntoView({behavior:"smooth"}); },[msgs,busy]);
const saveColors=c=>{ setColors(c); try{localStorage.setItem("mshpi_colors",JSON.stringify(c));}catch{} };
useEffect(()=>{ try{const s=localStorage.getItem("mshpi_colors");if(s)setColors(JSON.parse(s));}catch{} },[]);
useEffect(()=>{ try{localStorage.setItem("mshpi_chat_open", open ? "1" : "0");}catch{} },[open]);
const send=async text=>{
const t=(text||"").trim(); if(!t||busy)return;
setInput(""); if(taRef.current)taRef.current.style.height="";
setQuick(false); setBusy(true);
setMsgs(prev=>[...prev,{id:Date.now(),role:"user",text:t}]);
const newHist=[...hist,{role:"user",content:t}];
const isKorean=/[\uAC00-\uD7A3\u1100-\u11FF\u3130-\u318F]/.test(t);
const langHint=isKorean?"[Reply ONLY in Korean] ":"[Reply ONLY in English] ";
const apiHist=newHist.map((m,i)=>i===newHist.length-1&&m.role==="user"?{...m,content:langHint+m.content}:m);
let reply;
try { reply=await callWorker(apiHist); } catch(e) { reply=null; }
let botText=reply||ERR_MSG;
botText=botText.replace(/https?:\/\/(?:www\.)?mshpiinc\.com[^\s)]*/gi,"");
botText=botText.replace(/\*{0,2}MSH PI,? INC?\.? has been operating for more than 21[\s\S]*?outside firms\.(?:[^\n]*)?\*{0,2}\n?/gi,"")
.replace(/\*{0,2}Many Korean Private Investigation[\s\S]*?outside firms\.(?:[^\n]*)?\*{0,2}\n?/gi,"")
.replace(/\n{3,}/g,"\n\n").trim();
if(/contact\s+page/i.test(botText)&&!botText.includes("mshpi.com/contact")){
const kr=/[\uAC00-\uD7A3]/.test(botText);
botText+=kr?"\nhttps://www.mshpi.com/ko/contact-korean-private-investigator":"\nhttps://www.mshpi.com/contact-korean-private-investigator";
}
if(!botText.trim()) botText=ERR_MSG;
setHist([...newHist,{role:"assistant",content:botText}]);
setMsgs(prev=>[...prev,{id:Date.now()+1,role:"bot",text:botText}]);
setBusy(false);
setTimeout(()=>inputRef.current?.focus(),50);
};
const onKey=e=>{ if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();send(input);} };
if(!open) return (
);
return (
{admin&&
setAdmin(false)}/>}
{/* Header */}

{e.target.style.display="none";e.target.parentNode.innerHTML='
M';}}
/>
MSH PI, INC.
{busy?"Thinking...":"Online"} · 한국어 가능
{/* Licence bar */}
CA PI Lic #24994{" · "}Est. 2004 · Licensed & Insured
{/* Messages */}
{msgs.map(m=>
)}
{busy&&(
)}
{/* Quick replies */}
{quick&&(
{QUICK.map(q=>(
))}
)}
{/* Input */}
Do not submit sensitive information unless authorised. Submission is at your own risk.{" "}
Privacy Policy.
);
}
const root = ReactDOM.createRoot(document.getElementById("mshpi-chatbot-root"));
root.render(
);