const { useState, useEffect, useRef, useMemo } = React;
/**
* Recipe Renderer for the AI Chat section - Text-only version
*/
const AiRecipeRenderer = ({ recipe, onAdd }) => {
return (
{recipe.cuisine}
{recipe.name}
{recipe.time}
{recipe.difficulty}
);
};
const CentralAIChat = ({ onNewRecipe }) => {
const [messages, setMessages] = useState([
{ role: 'assistant', content: "I'm QTCook AI. Tell me what you're craving and I'll generate a recipe with ingredients and cooking instructions!", id: 'init' }
]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const chatEndRef = useRef(null);
const aiClient = useRef(new QTwareAI({
systemPrompt: `You are QTCook AI. When providing recipes, ALWAYS include a JSON block at the end. \n JSON SCHEMA: { \n "type": "recipe", \n "name": "Name", \n "cuisine": "Cuisine", \n "time": "Time", \n "difficulty": "Difficulty", \n "ingredients": ["item 1"], \n "instructions": ["step 1", "step 2"]\n }.\n Do NOT provide any text outside the JSON block.`,
model: 'gpt-4o'
}));
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const handleSend = async (e) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
const userMsg = input;
setMessages(prev => [...prev, { role: 'user', content: userMsg, id: Date.now() }]);
setInput('');
setIsLoading(true);
try {
const response = await aiClient.current.chat(userMsg);
setMessages(prev => [...prev, { role: 'assistant', content: response, id: Date.now() + 1 }]);
} catch (error) {
setMessages(prev => [...prev, { role: 'assistant', content: "The kitchen is a bit busy. Please try again!", id: Date.now() + 2 }]);
} finally {
setIsLoading(false);
}
};
const parseResponse = (content, msgId) => {
const jsonRegex = /```json([\\s\\S]*?)```|\\{([\\s\\S]*?)"type":\\s*"recipe"([\\s\\S]*?)\\}/;
const match = content.match(jsonRegex);
if (match) {
try {
let jsonStr = match[0];
if (jsonStr.startsWith('```json')) {
jsonStr = match[1];
}
const data = JSON.parse(jsonStr.trim());
return { text: '', data };
} catch (e) {
return { text: content, data: null };
}
}
return { text: content, data: null };
};
return (
AI CULINARY HUB
QTCook Intelligence
{messages.map((msg) => {
const parsed = parseResponse(msg.content, msg.id);
return (
{parsed.text &&
{parsed.text}
}
{parsed.data && parsed.data.type === 'recipe' && (
)}
);
})}
{isLoading &&
Cooking up a recipe...
}
);
};
const App = () => {
const [recipes, setRecipes] = useState([]);
const [filter, setFilter] = useState("All");
const [selectedRecipe, setSelectedRecipe] = useState(null);
const cuisines = ["All", "Turkish", "Italian", "Japanese", "Mexican", "French"];
const addNewRecipe = (recipe) => {
const newRecipe = { ...recipe, id: Date.now() };
setRecipes(prev => [newRecipe, ...prev]);
};
const filtered = recipes.filter(r => filter === "All" || r.cuisine === filter);
return (
Culinary Collection
{cuisines.map(c => (
))}
{filtered.length === 0 && (
No recipes yet. Ask the AI to create one!
)}
{filtered.map(r => (
setSelectedRecipe(r)}>
{r.cuisine}
{r.name}
{r.time}
{r.difficulty}
))}
{selectedRecipe && (
setSelectedRecipe(null)}>
e.stopPropagation()}>
{selectedRecipe.name}
{selectedRecipe.cuisine}
{selectedRecipe.time}
{selectedRecipe.difficulty}
Ingredients
{selectedRecipe.ingredients && selectedRecipe.ingredients.map((ing, i) => (
{ing}
))}
{selectedRecipe.instructions && (
How to Cook
{selectedRecipe.instructions.map((step, i) => (
))}
)}
)}
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();