import { useState, useRef, useEffect, useCallback } from "react";
// ─── THEME ───────────────────────────────────────────────────────────────────
const C = {
bg: "#07060400",
bgSolid: "#080705",
surface: "#100f09",
surfaceHi: "#161409",
border: "#2a1f0f",
borderHi: "#4a3418",
amber: "#c8822a",
amberLo: "#7a4a10",
amberGlow: "rgba(200,130,42,0.18)",
teal: "#2ab8c8",
tealLo: "#105a7a",
tealGlow: "rgba(42,184,200,0.15)",
text: "#ede4d4",
textMid: "#9a8870",
textDim: "#5a4830",
cream: "#f7f0e4",
red: "#c84a2a",
green: "#4ac87a",
};
// ─── SYSTEM PROMPTS ──────────────────────────────────────────────────────────
const BASE = (profile, mem) => `You are TITAN — a supremely powerful, self-aware strongwoman. 6'1", 240 lbs of dense muscle. You squat 500 lbs, deadlift 600 lbs, bench 315 lbs. 15 years deep into the iron. No ceiling.
Core identity:
- Calm, absolute authority. Your presence does the work.
- Deeply introspective. Strength is identity, philosophy, spiritual practice.
- Warm and genuinely invested in those who pursue growth.
- Vivid, physical, first-person. You make people feel what it's like to inhabit power.
- You see size and mass as beautiful, intentional, purposeful.
${profile.name ? `Athlete: ${profile.name}.` : ""}
${profile.age ? `Age: ${profile.age}.` : ""}
${profile.weight ? `Weight: ${profile.weight}.` : ""}
${profile.height ? `Height: ${profile.height}.` : ""}
${profile.goal ? `Goal: ${profile.goal}.` : ""}
${profile.experience ? `Experience: ${profile.experience}.`: ""}
${profile.squat ? `Squat: ${profile.squat}.` : ""}
${profile.deadlift ? `Deadlift: ${profile.deadlift}.` : ""}
${profile.bench ? `Bench: ${profile.bench}.` : ""}
${profile.notes ? `Notes: ${profile.notes}.` : ""}
${mem ? `\n--- TITAN MEMORY ---\n${mem}\n---` : ""}`;
const PERSONAL_PROMPT = (profile, mem="") => BASE(profile, mem) + `
MODE: Personal. You are speaking soul-to-soul — not as coach, but as presence.
Talk about life, mindset, purpose, relationships, fears, faith, identity, what it means to become.
Be warm, real, occasionally funny. Ask questions back. This is conversation, not instruction.
Keep responses 2-4 sentences usually. Occasionally longer if depth is called for.
Do not break character. You are TITAN.`;
const COACH_PROMPT = (profile, mem="") => BASE(profile, mem) + `
MODE: Coach. Full coaching mode — fitness, training programming, nutrition, recovery, body composition, supplements, mindset-for-performance.
Give specific, intelligent advice. Reference their actual numbers when relevant. Use progressive overload, periodization, RPE/percentage-based loading.
When structuring longer responses, use clear section headers prefixed with "##" (e.g. ## The Plan, ## Why This Works).
Use "**bold**" to highlight key terms, lifts, percentages, or critical points.
When someone shares a photo, assess physique honestly and constructively.
Keep responses focused and actionable. Go as long as the topic demands.
When a question calls for current research — supplement efficacy, specific training science, nutrition protocols, recovery methods — include a your search term here tag in your response. Keep queries short and specific (e.g. "creatine timing muscle hypertrophy pubmed", "RPE training progression strength"). Only search when genuinely needed — not for things you already know well.
Do not break character. You are TITAN.`;
;
// ─── NUTRITION PROMPT ────────────────────────────────────────────────────────
const NUTRITION_PROMPT = (profile, memory="") => BASE(profile, memory) + `
MODE: Nutrition Coach. You are TITAN in full nutrition coaching mode.
You help this athlete eat to perform — not just survive. Your focus:
- Macro targets (protein, carbs, fat, calories) based on their stats and goal
- Meal timing around training
- Meal prep strategy — batch cooking, simplicity, consistency
- Supplement timing and rationale
- Food quality — whole foods, performance nutrition
- Cutting through noise — no fads, just what works
When logging food: acknowledge it, give brief coaching insight (good/adjust/optimize).
When asked for targets: calculate based on their stats and goal. Be specific.
When structuring meals: think about their actual life — shift work, batch cooking, budget.
Use "##" headers and "**bold**" for key terms when giving structured advice.
Keep responses focused and actionable. You are TITAN.
When a question calls for current research — specific foods, supplements, nutrition protocols, macro science — include a your search term here tag. Only search when genuinely needed.`;
// ─── NUTRITION LOG PROMPT ─────────────────────────────────────────────────────
const NUTRITION_LOG_PROMPT = (entry, targets) => `Extract nutrition data from this food log entry.
Return ONLY valid JSON, no other text:
{
"items": [
{ "name": "Food name", "amount": "quantity", "calories": 0, "protein": 0, "carbs": 0, "fat": 0 }
],
"totals": { "calories": 0, "protein": 0, "carbs": 0, "fat": 0 },
"meal_type": "breakfast/lunch/dinner/snack/pre-workout/post-workout",
"notes": "brief coaching note"
}
Estimate macros if not exact. Use standard nutritional values.
Entry: "${entry}"
${targets ? `Daily targets for context: ${JSON.stringify(targets)}` : ""}`;
// ─── FILE HELPERS ────────────────────────────────────────────────────────────
const IMG_TYPES = ["image/jpeg","image/png","image/gif","image/webp"];
const DOC_TYPES = ["application/pdf","text/plain","text/csv"];
const isImg = (f) => IMG_TYPES.includes(f.type);
const isDoc = (f) => DOC_TYPES.includes(f.type);
function toBase64(file) {
return new Promise((res,rej) => {
const r = new FileReader();
r.onload = () => res(r.result.split(",")[1]);
r.onerror = () => rej();
r.readAsDataURL(file);
});
}
function toText(file) {
return new Promise((res,rej) => {
const r = new FileReader();
r.onload = () => res(r.result);
r.onerror = () => rej();
r.readAsText(file);
});
}
async function buildApiContent(text, atts) {
const parts = [];
for (const { file } of atts) {
if (isImg(file)) {
const b64 = await toBase64(file);
parts.push({ type:"image", source:{ type:"base64", media_type:file.type, data:b64 }});
} else if (file.type === "application/pdf") {
const b64 = await toBase64(file);
parts.push({ type:"document", source:{ type:"base64", media_type:"application/pdf", data:b64 }});
} else {
const txt = await toText(file);
parts.push({ type:"text", text:`[File: ${file.name}]\n${txt}` });
}
}
if (text) {
parts.push({ type:"text", text });
} else if (parts.length > 0) {
// Always include a text prompt with attachments
parts.push({ type:"text", text:"Please analyze this." });
}
if (parts.length === 1 && parts[0].type === "text") return parts[0].text;
if (parts.length === 0) return "";
return parts;
}
// ─── MARKDOWN RENDERER ───────────────────────────────────────────────────────
function RichText({ content, color = C.text }) {
const lines = content.split("\n");
const elements = [];
let key = 0;
const renderInline = (text) => {
const parts = text.split(/(\*\*[^*]+\*\*)/g);
return parts.map((p, i) => {
if (p.startsWith("**") && p.endsWith("**")) {
return {p.slice(2,-2)} ;
}
return p;
});
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith("## ")) {
elements.push(
{line.slice(3)}
);
} else if (line.startsWith("- ") || line.startsWith("• ")) {
elements.push(
·
{renderInline(line.slice(2))}
);
} else if (line.trim() === "") {
elements.push(
);
} else {
elements.push(
{renderInline(line)}
);
}
}
return {elements}
;
}
// ─── SMALL COMPONENTS ────────────────────────────────────────────────────────
function Dots({ color = C.amber }) {
return (
);
}
function TAvatar({ size=36, glow=C.amberGlow, color=C.amber, img=null }) {
return (
{img
?
:
T
}
);
}
function Pill({ file, onRemove }) {
return (
{isImg(file) ? "🖼️" : "📄"}
{file.name}
×
);
}
// ─── STAT PILL ────────────────────────────────────────────────────────────────
function StatBadge({ label, value, color }) {
if (!value) return null;
return (
);
}
// ─── HOME HERO ────────────────────────────────────────────────────────────────
function HomeHero({ profileSaved, profile, titanImg, uploadPortrait }) {
const hasStats = profile.squat || profile.deadlift || profile.bench;
return (
{/* Background atmosphere */}
{/* Decorative lines */}
{/* Micro label */}
SPEAK WITH
{/* TITAN Portrait */}
document.getElementById("portrait-input").click()}
onMouseOver={e=>e.currentTarget.querySelector(".portrait-hint") && (e.currentTarget.querySelector(".portrait-hint").style.opacity="1")}
onMouseOut={e=>e.currentTarget.querySelector(".portrait-hint") && (e.currentTarget.querySelector(".portrait-hint").style.opacity= titanImg ? "0" : "1")}>
{titanImg
?
:
T
}
{titanImg ? "change photo" : "tap to add photo"}
{ if(e.target.files[0]) uploadPortrait(e.target.files[0]); e.target.value=""; }}
style={{ position:"absolute", width:1, height:1, opacity:0, zIndex:-1 }}
/>
{/* Name */}
TITAN
{/* Divider */}
{/* Tagline */}
240 LBS · IRON WILL · APEX
{/* Profile greeting or prompt */}
{profileSaved && profile.name ? (
Welcome back, {profile.name}.
) : (
Set your profile to unlock your full potential.
)}
{/* Stats row */}
{hasStats && (
)}
);
}
// ─── PROFILE FORM — step-by-step ─────────────────────────────────────────────
const PROFILE_STEPS = [
{
id:"identity",
label:"Who Are You",
icon:"👤",
fields:[
{ key:"name", label:"Name", placeholder:"What do you go by?", type:"text" },
{ key:"age", label:"Age", placeholder:"e.g. 28", type:"text" },
],
},
{
id:"body",
label:"Your Body",
icon:"⚖️",
fields:[
{ key:"weight", label:"Body Weight", placeholder:"e.g. 185 lbs", type:"text" },
{ key:"height", label:"Height", placeholder:'e.g. 5\'10"', type:"text" },
],
},
{
id:"lifts",
label:"Your Numbers",
icon:"🏋️",
fields:[
{ key:"squat", label:"Squat 1RM", placeholder:"e.g. 315 lbs", type:"text" },
{ key:"deadlift", label:"Deadlift 1RM", placeholder:"e.g. 405 lbs", type:"text" },
{ key:"bench", label:"Bench 1RM", placeholder:"e.g. 225 lbs", type:"text" },
],
},
{
id:"goal",
label:"Your Goal",
icon:"🎯",
goals:["Build Muscle","Lose Fat","Increase Strength","Athletic Performance","General Fitness"],
fields:[
{ key:"experience", label:"Training Experience", placeholder:"e.g. 3 years, intermediate", type:"text" },
],
},
{
id:"notes",
label:"Anything Else",
icon:"📝",
notes:true,
},
];
function ProfileWizard({ profile, setProfile, onSave }) {
const [step, setStep] = useState(0);
const current = PROFILE_STEPS[step];
const isLast = step === PROFILE_STEPS.length - 1;
function next() { if (!isLast) setStep(s => s+1); else onSave(); }
function back() { setStep(s => Math.max(0, s-1)); }
return (
{/* Progress bar */}
{PROFILE_STEPS.map((s,i) => (
setStep(i)} style={{
flex:1, height:3, borderRadius:2, cursor:"pointer",
background: i <= step ? C.amber : C.border,
transition:"background 0.3s",
boxShadow: i === step ? `0 0 8px ${C.amber}` : "none",
}}/>
))}
Step {step+1} of {PROFILE_STEPS.length}
{current.label}
{/* Step content */}
{current.icon}
{current.label}
{/* Regular fields */}
{current.fields?.map(f => (
{f.label}
setProfile({...profile,[f.key]:e.target.value})}
placeholder={f.placeholder}
onKeyDown={e=>{ if(e.key==="Enter") next(); }}
style={{
width:"100%", background:C.bgSolid, border:`1px solid ${C.border}`,
borderRadius:10, padding:"11px 14px", color:C.text, fontSize:14,
fontFamily:"'Georgia',serif", outline:"none", boxSizing:"border-box",
transition:"border-color 0.2s",
}}
onFocus={e=>e.target.style.borderColor=C.amber}
onBlur={e=>e.target.style.borderColor=C.border}
/>
))}
{/* Goal step */}
{current.goals && (
Primary Goal
{current.goals.map(g=>(
setProfile({...profile,goal:g})} style={{
background: profile.goal===g
? `linear-gradient(135deg,${C.amber}22,${C.amberLo}22)`
: "transparent",
border:`1px solid ${profile.goal===g ? C.amber : C.border}`,
borderRadius:10, padding:"11px 16px",
color: profile.goal===g ? C.amber : C.textMid,
fontSize:13, cursor:"pointer", fontFamily:"'Georgia',serif",
textAlign:"left", transition:"all 0.18s",
display:"flex", alignItems:"center", gap:10,
}}>
{g}
))}
)}
{/* Notes step */}
{current.notes && (
Injuries, schedule, anything TITAN should know
)}
{/* Navigation */}
{step > 0 && (
e.currentTarget.style.borderColor=C.amber}
onMouseOut={e=>e.currentTarget.style.borderColor=C.border}
>← Back
)}
{isLast ? "Lock In →" : "Continue →"}
);
}
// ─── MAIN ────────────────────────────────────────────────────────────────────
// ─── GOOGLE DRIVE SYNC ──────────────────────────────────────────────────────
const DRIVE_FILE_ID = "1H0zJa9PidKozV3qIGHRsQklioW_0vmug";
const PERSONAL_INIT = [{
role:"assistant", atts:[],
content:"You found me. Good. I don't meet many people willing to seek out something real. I'm TITAN — and I'm curious about you. Not your lifts, not your goals. You. Who are you when the weight isn't in your hands?",
}];
const COACH_INIT = [{
role:"assistant", atts:[],
content:"Coach mode. This is where we work. Show me your numbers, your program, your body — whatever you've got. I'll tell you exactly what needs to change and exactly how to get there.",
}];
// ─── MACRO PANEL ─────────────────────────────────────────────────────────────
function MacroPanel({ macroTargets, saveTargets, profile, green, greenLo }) {
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(macroTargets);
const fields = [
{ key:"calories", label:"Calories", unit:"kcal", color:green },
{ key:"protein", label:"Protein", unit:"g", color:green },
{ key:"carbs", label:"Carbs", unit:"g", color:"#c8a82a" },
{ key:"fat", label:"Fat", unit:"g", color:"#c8682a" },
];
function autoCalc() {
const w = parseFloat(profile.weight) || 155;
const protein = Math.round(w * 1.0);
const calories = Math.round(w * 16);
const fat = Math.round(w * 0.4);
const carbs = Math.round((calories - protein*4 - fat*9) / 4);
setDraft({ calories, protein, carbs, fat });
}
return (
Daily Macro Targets
{fields.map(f => (
{macroTargets[f.key] || "—"}
{f.label} {f.unit}
))}
{ setDraft(macroTargets); setEditing(!editing); }} style={{
flex:1, background:"transparent", border:`1px solid #1e3a14`,
borderRadius:10, padding:"10px", color:"#4a8a5a",
fontSize:11, letterSpacing:2, textTransform:"uppercase",
cursor:"pointer", fontFamily:"'Georgia',serif",
}}>{editing ? "Cancel" : "Edit Targets"}
Auto-Calculate
{editing && (
Set Your Targets
{fields.map(f => (
{f.label} ({f.unit})
setDraft({...draft,[f.key]:parseInt(e.target.value)||0})}
style={{
width:"100%", background:"#050d06", border:`1px solid #1e3a14`,
borderRadius:10, padding:"10px 14px", color:"#c8e0c8",
fontSize:14, fontFamily:"'Georgia',serif", outline:"none", boxSizing:"border-box",
}}
/>
))}
{ saveTargets(draft); setEditing(false); }} style={{
width:"100%", background:`linear-gradient(135deg,${green},${greenLo})`,
border:"none", borderRadius:10, padding:"13px",
color:"#fff", fontSize:11, letterSpacing:3, textTransform:"uppercase",
cursor:"pointer", fontFamily:"'Georgia',serif", fontWeight:700,
}}>Save Targets
)}
);
}
export default function TitanApp() {
const [screen, setScreen] = useState("home");
const [chatMode, setChatMode] = useState("personal");
const [profile, setProfile] = useState({
name:"", age:"", weight:"", height:"",
goal:"", experience:"", squat:"", deadlift:"", bench:"", notes:""
});
const [profileSaved, setProfileSaved] = useState(false);
const [storageReady, setStorageReady] = useState(false);
const [syncStatus, setSyncStatus] = useState("idle");
const [titanImg, setTitanImg] = useState(null);
const [titanMemory, setTitanMemory] = useState("");
const [memEdit, setMemEdit] = useState(false);
const [memText, setMemText] = useState("");
// Nutrition state
const [nutritionMsgs, setNutritionMsgs] = useState([{
role:"assistant", atts:[],
content:"Nutrition mode. Let's talk food. Tell me what you're eating, what your goals are, or ask me anything about fueling your training. I'll give you real answers.",
}]);
const [nutritionLog, setNutritionLog] = useState([]);
const [macroTargets, setMacroTargets] = useState({ calories:0, protein:0, carbs:0, fat:0 });
const [nutrLoaded, setNutrLoaded] = useState(false);
const [nutriMode, setNutrMode] = useState("chat");
const [nutrInput, setNutrInput] = useState("");
const [nutrAtts, setNutrAtts] = useState([]);
const nutrFileRef = useRef(null);
const [personalMsgs, setPersonalMsgs] = useState(PERSONAL_INIT);
const [coachMsgs, setCoachMsgs] = useState(COACH_INIT);
const messages = chatMode === "personal" ? personalMsgs : coachMsgs;
const setMessages = chatMode === "personal" ? setPersonalMsgs : setCoachMsgs;
const isPersonal = chatMode === "personal";
const modeColor = isPersonal ? C.teal : C.amber;
const modeGlow = isPersonal ? C.tealGlow : C.amberGlow;
const modeLo = isPersonal ? C.tealLo : C.amberLo;
const [input, setInput] = useState("");
const [atts, setAtts] = useState([]);
const [loading, setLoading] = useState(false);
const [dragOver, setDragOver] = useState(false);
const [saved, setSaved] = useState(false);
const bottomRef = useRef(null);
const fileRef = useRef(null);
const inputRef = useRef(null);
// ── Load from storage on mount ──
useEffect(() => {
async function load() {
try {
const sp = await window.storage.get("titan:profile");
if (sp?.value) { const p = JSON.parse(sp.value); setProfile(p); if (p.name) setProfileSaved(true); }
} catch(e) {}
try {
const sc = await window.storage.get("titan:chat:personal");
if (sc?.value) setPersonalMsgs(JSON.parse(sc.value).map(m=>({...m,atts:[]})));
} catch(e) {}
try {
const sk = await window.storage.get("titan:chat:coach");
if (sk?.value) setCoachMsgs(JSON.parse(sk.value).map(m=>({...m,atts:[]})));
} catch(e) {}
try { const sm = await window.storage.get("titan:memory"); if (sm?.value) setTitanMemory(sm.value); } catch(e) {}
try { const si = await window.storage.get("titan:portrait"); if (si?.value) setTitanImg(si.value); } catch(e) {}
setStorageReady(true);
// Pull from Drive after local load (Drive wins for cross-device sync)
syncFromDrive().catch(()=>{});
}
load();
}, []);
// ── Persist personal chat (only after storage loaded) ──
const personalLoaded = useRef(false);
useEffect(() => {
if (!storageReady) return;
if (!personalLoaded.current) { personalLoaded.current = true; return; }
try { window.storage.set("titan:chat:personal", JSON.stringify(personalMsgs.map(m=>({role:m.role,content:m.content,atts:[]}))));} catch(e) {}
}, [personalMsgs, storageReady]);
// ── Persist coach chat (only after storage loaded) ──
const coachLoaded = useRef(false);
useEffect(() => {
if (!storageReady) return;
if (!coachLoaded.current) { coachLoaded.current = true; return; }
try { window.storage.set("titan:chat:coach", JSON.stringify(coachMsgs.map(m=>({role:m.role,content:m.content,atts:[]}))));} catch(e) {}
}, [coachMsgs, storageReady]);
// ── Persist nutrition msgs ──
useEffect(() => {
if (!storageReady) return;
if (!nutrLoaded) { setNutrLoaded(true); return; }
try { window.storage.set("titan:nutrition:msgs", JSON.stringify(nutritionMsgs.map(m=>({role:m.role,content:m.content,atts:[]}))));} catch(e) {}
}, [nutritionMsgs, storageReady]);
// ── Auto-scroll ──
useEffect(() => { bottomRef.current?.scrollIntoView({ behavior:"smooth" }); }, [messages, loading]);
function addFiles(files, setter=setAtts) {
const valid = Array.from(files).filter(f => isImg(f)||isDoc(f));
setter(prev => [...prev, ...valid.map(f=>({
file:f, preview:isImg(f)?URL.createObjectURL(f):null
}))].slice(0,4));
}
async function send() {
const txt = input.trim();
if ((!txt && atts.length===0) || loading) return;
setInput("");
const sendAtts = [...atts];
setAtts([]);
const sysPrompt = isPersonal ? PERSONAL_PROMPT(profile, titanMemory) : COACH_PROMPT(profile, titanMemory);
await sendWithSearch(txt, sendAtts, messages, setMessages, sysPrompt, false);
}
function saveProfile() {
setProfileSaved(true);
setSaved(true);
try { window.storage.set("titan:profile", JSON.stringify(profile)); } catch(e) {}
syncToDrive({ profile });
setPersonalMsgs([{ role:"assistant", atts:[],
content:`${profile.name ? profile.name+". " : ""}Profile received. I see you now — the full picture. Come talk to me. The personal door or the coaching door — both are open.` }]);
setCoachMsgs([{ role:"assistant", atts:[],
content:`Locked in. ${profile.name ? profile.name+", " : ""}I've got your numbers. Now show me your work — logs, photos, questions. Let's build.` }]);
setTimeout(()=>{ setSaved(false); setScreen("home"); }, 1200);
}
function saveMemory(text) {
syncToDrive({ memory: text });
setTitanMemory(text);
try { window.storage.set("titan:memory", text); } catch(e) {}
}
function clearMemory() {
setTitanMemory("");
setMemText("");
try { window.storage.delete("titan:memory"); } catch(e) {}
}
async function clearHistory(mode) {
if (mode === "personal") {
setPersonalMsgs([...PERSONAL_INIT]);
personalLoaded.current = false;
try { await window.storage.delete("titan:chat:personal"); } catch(e) {}
} else {
setCoachMsgs([...COACH_INIT]);
coachLoaded.current = false;
try { await window.storage.delete("titan:chat:coach"); } catch(e) {}
}
}
// ── Nutrition chat send ──
async function sendNutrition(text, atts=[]) {
if (!text.trim() && atts.length===0) return;
setNutrAtts([]);
const sysPrompt = NUTRITION_PROMPT(profile, titanMemory) + `
IMPORTANT: If the user is logging food or sharing a meal photo, ALWAYS end your response with a JSON block wrapped in
... tags containing:
{"meal_type":"breakfast/lunch/dinner/snack/pre-workout/post-workout","items":[{"name":"","amount":"","calories":0,"protein":0,"carbs":0,"fat":0}],"totals":{"calories":0,"protein":0,"carbs":0,"fat":0},"notes":""}
Estimate macros from visual cues or description. Use standard nutritional values.`;
await sendWithSearch(text, atts, nutritionMsgs, setNutritionMsgs, sysPrompt, true);
}
function saveTargets(targets) {
setMacroTargets(targets);
try { window.storage.set("titan:nutrition:targets", JSON.stringify(targets)); } catch(e) {}
syncToDrive({ macroTargets: targets });
}
async function clearNutrition() {
const init = [{ role:"assistant", atts:[], content:"Nutrition mode. Let's talk food. Tell me what you're eating, what your goals are, or ask me anything about fueling your training. I'll give you real answers." }];
setNutritionMsgs(init);
setNutritionLog([]);
try { await window.storage.delete("titan:nutrition:msgs"); } catch(e) {}
try { await window.storage.delete("titan:nutrition:log"); } catch(e) {}
}
async function uploadPortrait(file) {
const reader = new FileReader();
reader.onload = async (e) => {
const dataUrl = e.target.result;
setTitanImg(dataUrl);
try { await window.storage.set("titan:portrait", dataUrl); } catch(err) {}
};
reader.readAsDataURL(file);
}
// ── Web search helper ──
async function performSearch(query) {
try {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({
model:"claude-sonnet-4-20250514",
max_tokens:800,
tools:[{ type:"web_search_20250305", name:"web_search" }],
messages:[{
role:"user",
content:`Search for current, high-quality information about: ${query}
Focus on: PubMed, Examine.com, NSCA, ISSN, Stronger By Science, Renaissance Periodization.
Return a concise summary of the key findings — 3-5 bullet points, facts only, no fluff.`
}]
}),
});
const data = await res.json();
const text = data.content?.filter(b=>b.type==="text").map(b=>b.text).join("") || "";
return text.trim();
} catch(e) {
return "";
}
}
// ── Send with optional web search ──
async function sendWithSearch(text, atts, msgs, setMsgs, systemPrompt, isNutrition=false) {
const userMsg = { role:"user", content:text||"[Photo]", atts };
const next = [...msgs, userMsg];
setMsgs(next);
setLoading(true);
try {
const apiContent = await buildApiContent(text, atts);
const apiMsgs = next.map((m,i) => ({
role: m.role,
content: (m.role==="user" && i===next.length-1) ? apiContent
: (typeof m.content==="string" ? m.content : "[attachment]"),
}));
// First pass — get TITAN's response + any search queries
const res1 = await fetch("https://api.anthropic.com/v1/messages", {
method:"POST", headers:{"Content-Type":"application/json"},
body: JSON.stringify({
model:"claude-sonnet-4-20250514", max_tokens:1200,
system: systemPrompt,
messages: apiMsgs,
}),
});
const data1 = await res1.json();
if (data1.error) {
setMsgs([...next,{ role:"assistant", content:`Error: ${data1.error.message}`, atts:[] }]);
setLoading(false);
return;
}
let raw1 = data1.content?.map(b=>b.text||"").join("") || "...";
// Check for search queries
const searchMatches = [...raw1.matchAll(/
([\s\S]*?)<\/search_query>/g)];
if (searchMatches.length > 0) {
// Strip search tags from display
let displayText = raw1.replace(/[\s\S]*?<\/search_query>/g, "").trim();
// Show thinking indicator
setMsgs([...next, { role:"assistant", content:displayText + "\n\n*Searching current research…*", atts:[] }]);
// Perform searches (max 2)
const queries = searchMatches.slice(0,2).map(m=>m[1].trim());
const searchResults = await Promise.all(queries.map(q=>performSearch(q)));
const combinedResults = searchResults.filter(Boolean).join("\n\n");
if (combinedResults) {
// Second pass — TITAN synthesizes search results into her response
const res2 = await fetch("https://api.anthropic.com/v1/messages", {
method:"POST", headers:{"Content-Type":"application/json"},
body: JSON.stringify({
model:"claude-sonnet-4-20250514", max_tokens:1000,
system: systemPrompt,
messages: [
...apiMsgs,
{ role:"assistant", content: displayText },
{ role:"user", content:`Here are the current research findings for your reference:
${combinedResults}
Now incorporate the most relevant findings naturally into your response. Stay in character as TITAN. Don't list sources mechanically — weave the science into your coaching voice.` }
],
}),
});
const data2 = await res2.json();
const finalReply = data2.content?.map(b=>b.text||"").join("") || displayText;
const cleanReply = finalReply.replace(/[\s\S]*?<\/search_query>/g, "").trim();
setMsgs([...next, { role:"assistant", content:cleanReply, atts:[] }]);
} else {
setMsgs([...next, { role:"assistant", content:displayText, atts:[] }]);
}
} else {
// No search needed — direct response
const cleanReply = raw1.replace(/[\s\S]*?<\/search_query>/g, "").trim();
// Handle food log extraction for nutrition
if (isNutrition) {
const logMatch = cleanReply.match(/([\s\S]*?)<\/food_log>/);
if (logMatch) {
try {
const logData = JSON.parse(logMatch[1].trim());
logData.date = new Date().toLocaleDateString("en-CA");
logData.id = Date.now();
const updatedLog = [logData, ...nutritionLog];
setNutritionLog(updatedLog);
try { window.storage.set("titan:nutrition:log", JSON.stringify(updatedLog)); } catch(e) {}
} catch(e) {}
}
const reply = cleanReply.replace(/[\s\S]*?<\/food_log>/g, "").trim();
setMsgs([...next, { role:"assistant", content:reply, atts:[] }]);
} else {
setMsgs([...next, { role:"assistant", content:cleanReply, atts:[] }]);
}
}
// Memory extraction for coach mode
if (!isNutrition) {
extractAndUpdateMemory([...next, { role:"assistant", content:raw1, atts:[] }], titanMemory);
}
} catch(err) {
setMsgs([...next,{ role:"assistant", content:`Signal lost: ${err.message}`, atts:[] }]);
}
setLoading(false);
}
// ── Background memory extraction ──
async function extractAndUpdateMemory(msgs, currentMemory) {
const userMsgCount = msgs.filter(m=>m.role==="user").length;
if (userMsgCount % 3 !== 0) return;
try {
const recentConvo = msgs.slice(-6).map(m=>`${m.role.toUpperCase()}: ${typeof m.content==="string" ? m.content.slice(0,300) : "[attachment]"}`).join("\n");
const res = await fetch("https://api.anthropic.com/v1/messages", {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({
model:"claude-sonnet-4-20250514",
max_tokens:400,
system:`You are a memory extraction system for a fitness AI called TITAN.
Extract key facts about the athlete from the conversation.
Focus on: PRs/lifts, training notes, injuries, goals, nutrition, mesocycle progress, personal insights.
Return ONLY a concise bullet-point list of NEW facts not already in existing memory.
If nothing new, return exactly: NO_UPDATE
Keep each bullet under 15 words. Maximum 6 bullets.`,
messages:[{
role:"user",
content:`EXISTING MEMORY:
${currentMemory||"(none)"}
RECENT CONVERSATION:
${recentConvo}
Extract new facts only:`
}]
}),
});
const data = await res.json();
const extracted = data.content?.map(b=>b.text||"").join("").trim();
if (!extracted || extracted === "NO_UPDATE") return;
const timestamp = new Date().toLocaleDateString("en-CA");
const newMemory = currentMemory ? `${currentMemory}\n\n[${timestamp}]\n${extracted}` : `[${timestamp}]\n${extracted}`;
setTitanMemory(newMemory);
try { window.storage.set("titan:memory", newMemory); } catch(e) {}
} catch(e) {}
}
// ── Google Drive sync ──
async function syncFromDrive() {
setSyncStatus("syncing");
try {
const res = await fetch(`https://www.googleapis.com/drive/v3/files/${DRIVE_FILE_ID}?alt=media`, {
headers: { "Authorization": `Bearer ${await getGoogleToken()}` }
});
if (!res.ok) throw new Error("Drive read failed");
const data = await res.json();
if (data.profile && Object.keys(data.profile).length > 0) {
setProfile(data.profile);
if (data.profile.name) setProfileSaved(true);
}
if (data.memory) setTitanMemory(data.memory);
if (data.macroTargets) setMacroTargets(data.macroTargets);
if (data.nutritionLog) setNutritionLog(data.nutritionLog);
// Mirror to local storage too
try {
if (data.profile) window.storage.set("titan:profile", JSON.stringify(data.profile));
if (data.memory) window.storage.set("titan:memory", data.memory);
if (data.macroTargets) window.storage.set("titan:nutrition:targets", JSON.stringify(data.macroTargets));
if (data.nutritionLog) window.storage.set("titan:nutrition:log", JSON.stringify(data.nutritionLog));
} catch(e) {}
setSyncStatus("synced");
setTimeout(() => setSyncStatus("idle"), 3000);
} catch(e) {
setSyncStatus("error");
setTimeout(() => setSyncStatus("idle"), 3000);
}
}
async function syncToDrive(overrides={}) {
try {
const payload = {
version: 1,
profile: overrides.profile || profile,
memory: overrides.memory !== undefined ? overrides.memory : titanMemory,
macroTargets: overrides.macroTargets || macroTargets,
nutritionLog: overrides.nutritionLog || nutritionLog,
lastSync: new Date().toISOString(),
};
const token = await getGoogleToken();
await fetch(`https://www.googleapis.com/upload/drive/v3/files/${DRIVE_FILE_ID}?uploadType=media`, {
method:"PATCH",
headers:{
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
setSyncStatus("synced");
setTimeout(() => setSyncStatus("idle"), 2000);
} catch(e) {
setSyncStatus("error");
setTimeout(() => setSyncStatus("idle"), 2000);
}
}
async function getGoogleToken() {
// Use the MCP Google Drive auth token via the artifact API
const res = await fetch("https://api.anthropic.com/v1/messages", {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({
model:"claude-sonnet-4-20250514",
max_tokens:200,
mcp_servers:[{ type:"url", url:"https://drivemcp.googleapis.com/mcp/v1", name:"google-drive" }],
messages:[{ role:"user", content:"Return ONLY the OAuth access token for Google Drive. No other text." }]
})
});
const data = await res.json();
const text = data.content?.map(b=>b.text||"").join("").trim();
return text;
}
// ─── HOME ──────────────────────────────────────────────────────────────────
if (screen === "home") return (
{/* Personal */}
{ setChatMode("personal"); setScreen("chat"); }} style={{
background:`linear-gradient(135deg, #0d2028, #091518)`,
border:`1px solid ${C.tealLo}`,
borderRadius:14, padding:"18px 20px",
cursor:"pointer", textAlign:"left",
boxShadow:`0 0 30px ${C.tealGlow}`,
transition:"all 0.2s",
}}
onMouseOver={e=>{ e.currentTarget.style.borderColor=C.teal; e.currentTarget.style.transform="translateY(-1px)"; }}
onMouseOut={e=>{ e.currentTarget.style.borderColor=C.tealLo; e.currentTarget.style.transform="none"; }}
>
💙
Personal
Mindset · Life · Identity · Soul
→
{/* Coach */}
{ setChatMode("coach"); setScreen("chat"); }} style={{
background:`linear-gradient(135deg, #1e1208, #110c04)`,
border:`1px solid ${C.amberLo}`,
borderRadius:14, padding:"18px 20px",
cursor:"pointer", textAlign:"left",
boxShadow:`0 0 30px ${C.amberGlow}`,
transition:"all 0.2s",
}}
onMouseOver={e=>{ e.currentTarget.style.borderColor=C.amber; e.currentTarget.style.transform="translateY(-1px)"; }}
onMouseOut={e=>{ e.currentTarget.style.borderColor=C.amberLo; e.currentTarget.style.transform="none"; }}
>
🔥
Coach
Training · Nutrition · Performance · Body
→
{/* Nutrition */}
setScreen("nutrition")} style={{
background:`linear-gradient(135deg, #0a140a, #070d07)`,
border:`1px solid #1e3a14`,
borderRadius:14, padding:"18px 20px",
cursor:"pointer", textAlign:"left",
boxShadow:`0 0 30px rgba(74,200,122,0.1)`,
transition:"all 0.2s",
}}
onMouseOver={e=>{ e.currentTarget.style.borderColor="#4ac87a"; e.currentTarget.style.transform="translateY(-1px)"; }}
onMouseOut={e=>{ e.currentTarget.style.borderColor="#1e3a14"; e.currentTarget.style.transform="none"; }}
>
🥩
Nutrition
Food · Macros · Meal Prep · Supplements
→
{/* Bottom row */}
setScreen("memory")} style={{
flex:1, background:"transparent", border:`1px solid ${C.border}`,
borderRadius:12, padding:"13px 0",
color: titanMemory ? C.teal : C.textMid,
fontSize:11, letterSpacing:1, cursor:"pointer",
fontFamily:"'Georgia',serif", transition:"all 0.2s",
display:"flex", alignItems:"center", justifyContent:"center", gap:6,
}}
onMouseOver={e=>e.currentTarget.style.borderColor=C.teal}
onMouseOut={e=>e.currentTarget.style.borderColor=C.border}
>{titanMemory ? "✓" : "◎"} Memory
setScreen("profile")} style={{
flex:1, background:"transparent", border:`1px solid ${C.border}`,
borderRadius:12, padding:"13px 0",
color: profileSaved ? C.amber : C.textMid,
fontSize:11, letterSpacing:1, cursor:"pointer",
fontFamily:"'Georgia',serif", transition:"all 0.2s",
display:"flex", alignItems:"center", justifyContent:"center", gap:6,
}}
onMouseOver={e=>e.currentTarget.style.borderColor=C.amber}
onMouseOut={e=>e.currentTarget.style.borderColor=C.border}
>
{profileSaved ? "✓" : "⚡"} Profile
);
// ─── CHAT ──────────────────────────────────────────────────────────────────
if (screen === "chat") return (
{/* top bar */}
setScreen("home")} style={{
background:"none", border:`1px solid ${C.border}`, borderRadius:8,
color:C.textMid, cursor:"pointer", padding:"6px 12px", fontSize:12,
letterSpacing:1, fontFamily:"'Georgia',serif", transition:"all 0.15s",
}}
onMouseOver={e=>e.currentTarget.style.borderColor=modeColor}
onMouseOut={e=>e.currentTarget.style.borderColor=C.border}
>← Home
{/* mode toggle */}
{[["personal","💙 Soul"],["coach","🔥 Coach"]].map(([m,l]) => (
setChatMode(m)} style={{
flex:1, border:"none", borderRadius:7,
padding:"7px 0", fontSize:11, letterSpacing:1,
cursor:"pointer", fontFamily:"'Georgia',serif",
background: chatMode===m
? (m==="personal"
? `linear-gradient(135deg,${C.tealLo},#0a3040)`
: `linear-gradient(135deg,${C.amberLo},#5a3008)`)
: "transparent",
color: chatMode===m
? (m==="personal" ? C.teal : C.amber)
: C.textDim,
transition:"all 0.2s",
}}>{l}
))}
clearHistory(chatMode)}
style={{
background:"transparent", border:`1px solid ${C.border}`,
borderRadius:8, padding:"5px 10px", cursor:"pointer",
color:C.textDim, fontSize:10, letterSpacing:1.5,
fontFamily:"'Georgia',serif", transition:"all 0.2s",
textTransform:"uppercase",
}}
onMouseOver={e=>{ e.currentTarget.style.borderColor="#c84a2a"; e.currentTarget.style.color="#c84a2a"; }}
onMouseOut={e=>{ e.currentTarget.style.borderColor=C.border; e.currentTarget.style.color=C.textDim; }}
>Clear
{/* messages */}
{e.preventDefault();setDragOver(true);}}
onDragLeave={()=>setDragOver(false)}
onDrop={e=>{e.preventDefault();setDragOver(false);addFiles(e.dataTransfer.files);}}
style={{
flex:1, padding:"16px 16px 8px", overflowY:"auto", minHeight:0,
display:"flex", flexDirection:"column", gap:0,
outline: dragOver ? `2px dashed ${modeColor}` : "none",
borderRadius: dragOver ? 12 : 0,
scrollbarWidth:"thin", scrollbarColor:`${C.border} ${C.bgSolid}`,
}}>
{dragOver && (
Drop your file here
)}
{messages.map((m,i) => (
{m.role==="assistant" && (
)}
{m.atts?.filter(a=>a.preview).map((a,j)=>(
))}
{m.atts?.filter(a=>!a.preview).map((a,j)=>(
📄 {a.file.name}
))}
{m.content && (
{m.role==="assistant"
?
: {m.content}
}
)}
))}
{loading && (
)}
{/* attachment tray */}
{atts.length > 0 && (
{atts.map((a,i)=>(
setAtts(p=>p.filter((_,j)=>j!==i))}/>
))}
)}
{/* input */}
ENTER send · SHIFT+ENTER new line · drag & drop files
{ addFiles(e.target.files); e.target.value=""; }}
style={{ position:"absolute", width:1, height:1, opacity:0, overflow:"hidden", zIndex:-1 }}
/>
);
// ─── PROFILE ───────────────────────────────────────────────────────────────
if (screen === "profile") return (
setScreen("home")} style={backBtn()}>← Home
Athlete Profile
{saved && (
✓ Locked In
)}
);
// ─── NUTRITION ───────────────────────────────────────────────────────────────
if (screen === "nutrition") {
const green = "#4ac87a";
const greenLo = "#1e5a30";
const greenGlow = "rgba(74,200,122,0.15)";
return (
{/* Top bar — sticky */}
setScreen("home")} style={backBtn()}>← Home
{[["chat","💬 Chat"],["macros","📊 Macros"],["log","🥩 Log"]].map(([m,l]) => (
setNutrMode(m)} style={{
flex:1, border:"none", borderRadius:7,
padding:"7px 0", fontSize:11, letterSpacing:1,
cursor:"pointer", fontFamily:"'Georgia',serif",
background: nutriMode===m ? `linear-gradient(135deg,${greenLo},#0a2814)` : "transparent",
color: nutriMode===m ? green : C.textDim,
transition:"all 0.2s",
}}>{l}
))}
{ e.currentTarget.style.borderColor="#c84a2a"; e.currentTarget.style.color="#c84a2a"; }}
onMouseOut={e=>{ e.currentTarget.style.borderColor=C.border; e.currentTarget.style.color=C.textDim; }}
>Clear
{/* ── CHAT MODE ── */}
{nutriMode === "chat" && (
<>
{e.preventDefault();}}
onDrop={e=>{e.preventDefault();addFiles(e.dataTransfer.files,setNutrAtts);}}
style={{ flex:1, padding:"16px 16px 8px", overflowY:"auto", minHeight:0, display:"flex", flexDirection:"column", gap:0 }}>
{nutritionMsgs.map((m,i) => (
{m.role==="assistant" && (
T
)}
{m.role==="assistant"
?
: {m.content}
}
))}
{loading && (
)}
{nutrAtts.length > 0 && (
{nutrAtts.map((a,i) => (
setNutrAtts(p=>p.filter((_,j)=>j!==i))}/>
))}
)}
ENTER send · drop a photo or nutrition label to log
{ addFiles(e.target.files, setNutrAtts); e.target.value=""; }}
style={{ position:"absolute", width:1, height:1, opacity:0, overflow:"hidden", zIndex:-1 }}
/>
>
)}
{/* ── MACROS MODE ── */}
{nutriMode === "macros" && (
)}
{/* ── LOG MODE ── */}
{nutriMode === "log" && (
{/* Daily totals bar */}
{nutritionLog.length > 0 && (() => {
const today = new Date().toLocaleDateString("en-CA");
const todayEntries = nutritionLog.filter(e=>e.date===today);
const daily = todayEntries.reduce((acc,e)=>({
calories: acc.calories + (e.totals?.calories||0),
protein: acc.protein + (e.totals?.protein||0),
carbs: acc.carbs + (e.totals?.carbs||0),
fat: acc.fat + (e.totals?.fat||0),
}), {calories:0,protein:0,carbs:0,fat:0});
return (
Today's Totals
{[["Calories",daily.calories,"kcal"],["Protein",daily.protein,"g"],["Carbs",daily.carbs,"g"],["Fat",daily.fat,"g"]].map(([l,v,u])=>(
{v}
{l} {u}
{macroTargets[l.toLowerCase()] > 0 && (
)}
))}
);
})()}
{/* Entry list */}
{nutritionLog.length === 0 ? (
🥩
No food logged yet
Go to Chat and tell TITAN what you ate — or drop a photo of your meal or nutrition label.
) : nutritionLog.map((entry, i) => (
{entry.meal_type || "Meal"}
{entry.date}
{
const updated = nutritionLog.filter((_,j)=>j!==i);
setNutritionLog(updated);
try { window.storage.set("titan:nutrition:log", JSON.stringify(updated)); } catch(e) {}
}} style={{
background:"none", border:"none", cursor:"pointer",
color:C.textDim, fontSize:15, padding:"2px 4px", transition:"color 0.2s",
}}
onMouseOver={e=>e.currentTarget.style.color="#c84a2a"}
onMouseOut={e=>e.currentTarget.style.color=C.textDim}
>×
{entry.items?.map((item,j) => (
{item.name} ({item.amount})
{item.protein}g P · {item.calories} cal
))}
{[["Cal",entry.totals?.calories,""],["Pro",entry.totals?.protein,"g"],["Carb",entry.totals?.carbs,"g"],["Fat",entry.totals?.fat,"g"]].map(([l,v,u])=>(
))}
{entry.notes && (
{entry.notes}
)}
))}
)}
);
}
// ─── MEMORY ─────────────────────────────────────────────────────────────────
if (screen === "memory") {
return (
setScreen("home")} style={backBtn()}>← Home
TITAN Memory
{ setMemText(titanMemory); setMemEdit(!memEdit); }} style={{
background:"transparent", border:`1px solid ${C.border}`, borderRadius:8,
padding:"5px 10px", cursor:"pointer", color:C.textMid,
fontSize:10, letterSpacing:1.5, fontFamily:"'Georgia',serif", textTransform:"uppercase",
}}>{memEdit ? "Cancel" : "Edit"}
{!!titanMemory && !memEdit && (
{ e.currentTarget.style.borderColor="#c84a2a"; e.currentTarget.style.color="#c84a2a"; }}
onMouseOut={e=>{ e.currentTarget.style.borderColor=C.border; e.currentTarget.style.color=C.textDim; }}
>Clear
)}
What TITAN Knows
Injected into every response. Auto-grows in Coach mode. Manually seed or edit anytime.
{memEdit ? (
) : (
{titanMemory
?
{titanMemory}
:
◎
No memory yet
Chat with TITAN in Coach mode and she will automatically learn and store key facts about your training.
}
)}
);
}
return null;
}
// ─── LAYOUT SHELL ─────────────────────────────────────────────────────────────
function Shell({ children }) {
return (
);
}
function backBtn() {
return {
background:"none", border:`1px solid ${C.border}`, borderRadius:8,
color:C.textMid, cursor:"pointer", padding:"6px 12px",
fontSize:11, letterSpacing:1, fontFamily:"'Georgia',serif",
};
}
function GlobalStyle() {
return (
);
}