import { useAI, useCollection, useReactions } from '@deplixo/sdk';
const OUTPUT_OPTIONS = [
{ id: "icebreaker", label: "Personalized icebreaker line" },
{ id: "linkedin_dm", label: "LinkedIn DM (intro)" },
{ id: "cold_email", label: "Cold email (intro)" },
{ id: "followup", label: "Follow-up / bump message" },
{ id: "research", label: "Research summary" },
{ id: "prep_notes", label: "Conversation prep notes" },
{ id: "fit_signal", label: "ICP fit signal" },
];
const FIT_LEVELS = { strong: "strong", medium: "medium", weak: "weak" };
function Spinner() {
return (
);
}
function Section({ title, children, accent }) {
const accentClass = accent ? `section-accent-${accent}` : "";
return (
);
}
function CopyButton({ text }) {
const [copied, setCopied] = useState(false);
const copy = () => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
};
return (
{copied ? "copied" : "copy"}
);
}
function OutputBlock({ label, content, accent }) {
if (!content) return null;
const isString = typeof content === "string";
return (
{isString ? (
) : (
{content}
)}
);
}
function FitSignalCard({ fitSignal }) {
if (!fitSignal) return null;
const level = fitSignal.level || "medium";
return (
{level} fit
{fitSignal.reasoning}
);
}
function BioSetup({ bio, setBio, onSave }) {
return (
First-time setup
Add your bio or value prop once — it'll be saved for every session.
);
}
function BioSavedBar({ onEdit }) {
return (
● Bio saved
edit
);
}
function TargetSection({ companyUrl, setCompanyUrl, crunchbaseUrl, setCrunchbaseUrl, contactName, setContactName, contactTitle, setContactTitle }) {
return (
);
}
function LinkedInSection({ linkedinCopy, setLinkedinCopy }) {
return (
LinkedIn profile copy *
Paste the text from their LinkedIn profile page
);
}
function OutreachGoalSection({ outreachGoal, setOutreachGoal }) {
return (
Outreach goal
);
}
function OutputSelector({ selectedOutputs, toggleOutput, ignoreLimit, setIgnoreLimit }) {
return (
Generate
{OUTPUT_OPTIONS.map(o => (
toggleOutput(o.id)} className={`checkbox-box ${selectedOutputs[o.id] ? "checkbox-box-checked" : ""}`}>
{selectedOutputs[o.id] && (
)}
{o.label}
))}
setIgnoreLimit(!ignoreLimit)} className={`checkbox-box ${ignoreLimit ? "checkbox-box-limit-checked" : ""}`}>
{ignoreLimit && (
)}
Ignore 300-char limit (1st degree contact)
);
}
function ResultsDisplay({ results, contactName, onNewProspect, resultsRef, historyId }) {
const { toggle, counts, loading: reactionsLoading } = useReactions(historyId || 'none');
if (!results) return null;
return (
— Briefing for {contactName || "Contact"}
{results.icebreaker &&
}
{results.linkedin_dm &&
}
{results.cold_email &&
}
{results.followup &&
}
{results.research &&
}
{results.prep_notes &&
}
{historyId && !reactionsLoading && (
{['👍', '❤️', '🔥'].map(emoji => (
toggle(emoji)}>
{emoji} {counts[emoji] || ''}
))}
)}
← New prospect
);
}
function HistoryPanel({ historyItems, historyLoading, onLoad, onDelete, onClearAll, showHistory, setShowHistory }) {
if (historyLoading) return null;
const sortedItems = historyItems
? [...historyItems].sort((a, b) => {
const tA = a.value.timestamp || 0;
const tB = b.value.timestamp || 0;
return tB - tA;
})
: [];
return (
setShowHistory(!showHistory)}
className="history-toggle-btn"
>
{showHistory ? "▾" : "▸"} Search History ({sortedItems.length})
{showHistory && sortedItems.length > 0 && (
Clear all
)}
{showHistory && (
{sortedItems.length === 0 && (
No searches saved yet.
)}
{sortedItems.map(item => {
const v = item.value;
const dateStr = v.timestamp
? new Date(v.timestamp).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})
: "Unknown date";
const contactLabel = v.contactName || "Unknown contact";
const companyLabel = v.companyUrl
? v.companyUrl.replace(/^https?:\/\//, "").replace(/\/$/, "").split("/")[0]
: "Unknown company";
const fitLevel = v.results && v.results.fit_signal ? v.results.fit_signal.level : null;
return (
onLoad(item)}>
{contactLabel}
{fitLevel && (
{fitLevel}
)}
{companyLabel}
{dateStr}
{
e.stopPropagation();
onDelete(item.id);
}}
className="history-item-delete"
title="Delete entry"
>
×
);
})}
)}
);
}
function App() {
const { items: bioItems, loading: bioLoading, add: addBio, update: updateBio } = useCollection("sales_prospector_bio", { personal: true });
const { items: historyItems, loading: historyLoading, add: addHistory, remove: removeHistory } = useCollection("sales_prospector_history", { personal: true });
const { generate, loading: aiLoading } = useAI();
const [bioText, setBioText] = useState("");
const [bioSaved, setBioSaved] = useState(false);
const [showBioSetup, setShowBioSetup] = useState(false);
const [bioItemId, setBioItemId] = useState(null);
const [companyUrl, setCompanyUrl] = useState("");
const [crunchbaseUrl, setCrunchbaseUrl] = useState("");
const [linkedinCopy, setLinkedinCopy] = useState("");
const [contactName, setContactName] = useState("");
const [contactTitle, setContactTitle] = useState("");
const [outreachGoal, setOutreachGoal] = useState("");
const [selectedOutputs, setSelectedOutputs] = useState(
OUTPUT_OPTIONS.reduce((acc, o) => ({ ...acc, [o.id]: true }), {})
);
const [ignoreLimit, setIgnoreLimit] = useState(false);
const [loading, setLoading] = useState(false);
const [results, setResults] = useState(null);
const [error, setError] = useState("");
const [showHistory, setShowHistory] = useState(false);
const resultsRef = useRef(null);
useEffect(() => {
if (!bioLoading && bioItems) {
if (bioItems.length > 0) {
const saved = bioItems[0];
setBioText(saved.value.bio || "");
setBioItemId(saved.id);
setBioSaved(true);
} else {
setShowBioSetup(true);
}
}
}, [bioLoading, bioItems]);
const saveBio = async () => {
if (bioItemId) {
await updateBio(bioItemId, { bio: bioText });
} else {
const result = await addBio({ bio: bioText });
setBioItemId(result.id);
}
setBioSaved(true);
setShowBioSetup(false);
};
const toggleOutput = (id) =>
setSelectedOutputs(prev => ({ ...prev, [id]: !prev[id] }));
const activeOutputs = OUTPUT_OPTIONS.filter(o => selectedOutputs[o.id]).map(o => o.id);
const buildPrompt = () => {
const charNote = ignoreLimit
? "No character limit on messages — write as long as needed."
: "Keep LinkedIn DMs and follow-ups under 300 characters. Cold emails can be longer.";
return `You are an expert B2B sales researcher and copywriter. Your job is to help a salesperson make a great first impression with a prospective contact.
USER'S BIO / VALUE PROP:
${bioText}
CONTACT INFO:
Name: ${contactName || "Unknown"}
Title: ${contactTitle || "Unknown"}
OUTREACH GOAL:
${outreachGoal}
COMPANY WEBSITE: ${companyUrl}
${crunchbaseUrl ? `CRUNCHBASE: ${crunchbaseUrl}` : ""}
LINKEDIN PROFILE (contact):
${linkedinCopy}
MESSAGE LENGTH RULE: ${charNote}
Based on all available information about this company and contact, generate ONLY the following outputs. Return your response as a JSON object with these exact keys (only include keys for requested outputs):
${activeOutputs.includes("icebreaker") ? `"icebreaker": A single sentence personalized icebreaker referencing something specific and genuine from their background or company. Not generic flattery.` : ""}
${activeOutputs.includes("linkedin_dm") ? `"linkedin_dm": A LinkedIn DM intro message. First-person, conversational, no buzzwords. Must feel human.` : ""}
${activeOutputs.includes("cold_email") ? `"cold_email": A cold email with subject line and body. Format as "Subject: ...\\n\\nBody: ..."` : ""}
${activeOutputs.includes("followup") ? `"followup": A short follow-up bump message for after no response. Light, not pushy.` : ""}
${activeOutputs.includes("research") ? `"research": A 3-5 sentence research brief on the company — what they do, their stage, recent signals, and why they might be relevant.` : ""}
${activeOutputs.includes("prep_notes") ? `"prep_notes": 3-4 bullet points of specific things to naturally mention or ask about in a conversation with this person. Format as a plain list.` : ""}
${activeOutputs.includes("fit_signal") ? `"fit_signal": An object with "level" (one of: "strong", "medium", "weak") and "reasoning" (1-2 sentences explaining the fit assessment).` : ""}
Return ONLY valid JSON. No preamble, no markdown fences.`;
};
const saveToHistory = async (generatedResults) => {
const entry = {
timestamp: Date.now(),
companyUrl,
crunchbaseUrl,
linkedinCopy,
contactName,
contactTitle,
outreachGoal,
selectedOutputs: { ...selectedOutputs },
ignoreLimit,
results: generatedResults,
};
await addHistory(entry);
};
const run = async () => {
if (!bioText.trim()) { setShowBioSetup(true); return; }
if (!companyUrl.trim()) { setError("Company website URL is required."); return; }
if (!linkedinCopy.trim()) { setError("LinkedIn profile copy is required."); return; }
if (activeOutputs.length === 0) { setError("Select at least one output."); return; }
setError("");
setResults(null);
setLoading(true);
try {
const prompt = buildPrompt();
const raw = await generate(prompt);
let parsed;
if (typeof raw === "string") {
const clean = raw.replace(/```json|```/g, "").trim();
parsed = JSON.parse(clean);
} else {
parsed = raw;
}
setResults(parsed);
await saveToHistory(parsed);
setTimeout(() => resultsRef.current?.scrollIntoView({ behavior: "smooth" }), 100);
} catch (e) {
setError("Something went wrong generating results. Check your inputs and try again.");
console.error(e);
} finally {
setLoading(false);
}
};
const handleNewProspect = () => {
setResults(null);
setCompanyUrl("");
setCrunchbaseUrl("");
setLinkedinCopy("");
setContactName("");
setContactTitle("");
setOutreachGoal("");
window.scrollTo({ top: 0, behavior: "smooth" });
};
const handleLoadHistory = (item) => {
const v = item.value;
setCompanyUrl(v.companyUrl || "");
setCrunchbaseUrl(v.crunchbaseUrl || "");
setLinkedinCopy(v.linkedinCopy || "");
setContactName(v.contactName || "");
setContactTitle(v.contactTitle || "");
setOutreachGoal(v.outreachGoal || "");
if (v.selectedOutputs) {
setSelectedOutputs(v.selectedOutputs);
}
if (typeof v.ignoreLimit === "boolean") {
setIgnoreLimit(v.ignoreLimit);
}
setResults(v.results || null);
setError("");
if (v.results) {
setTimeout(() => resultsRef.current?.scrollIntoView({ behavior: "smooth" }), 100);
} else {
window.scrollTo({ top: 0, behavior: "smooth" });
}
};
const handleDeleteHistory = async (id) => {
await removeHistory(id);
};
const handleClearAllHistory = async () => {
if (!historyItems || historyItems.length === 0) return;
for (const item of historyItems) {
await removeHistory(item.id);
}
};
if (bioLoading) {
return (
);
}
return (
Sales Intelligence
Prospect Briefing
Drop in a target. Walk away with a brief, a message, and a plan.
{showBioSetup && (
)}
{bioSaved && !showBioSetup && (
setShowBioSetup(true)} />
)}
{error &&
{error}
}
{loading || aiLoading ? "Researching..." : "Generate briefing →"}
{(loading || aiLoading) && (
)}
);
}
function SearchHistory({ onSelectEntry }) {
const { items, loading, remove } = useCollection('search_history', { order: 'desc' });
if (loading) return null;
if (!items || items.length === 0) return null;
function formatDate(ts) {
if (!ts) return '';
var d = new Date(ts);
var now = new Date();
var diff = now - d;
if (diff < 60000) return 'just now';
if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
return d.toLocaleDateString();
}
function getOutputSummary(selectedOutputs) {
if (!selectedOutputs) return '';
var labels = [];
if (selectedOutputs.icebreaker) labels.push('Icebreaker');
if (selectedOutputs.linkedin_dm) labels.push('DM');
if (selectedOutputs.cold_email) labels.push('Email');
if (selectedOutputs.followup) labels.push('Follow-up');
if (selectedOutputs.research) labels.push('Research');
if (selectedOutputs.prep_notes) labels.push('Prep');
return labels.join(', ');
}
function handleRemove(e, id) {
e.stopPropagation();
remove(id);
}
return (
Recent Searches
{items.slice(0, 20).map(function(entry) {
var outputSummary = getOutputSummary(entry.selectedOutputs);
return (
{entry.contactName || 'Unknown'}
{entry.contactTitle &&
{entry.contactTitle}
}
{outputSummary &&
{outputSummary}
}
{formatDate(entry.createdAt)}
×
);
})}
);
}
function SearchHistoryManager() {
var collectionHook = useCollection('search_history', { order: 'desc' });
return { items: collectionHook.items, loading: collectionHook.loading, add: collectionHook.add, remove: collectionHook.remove };
}
ReactDOM.createRoot(document.getElementById("root")).render( );