// BATONS'ON — AI-стилист (реальный Claude API) + поиск по каталогу const { useState: useStateE, useEffect: useEffectE, useRef: useRefE } = React; const CAT_RU = { hats: "Головные уборы", street: "Стритвир", beach: "Купальники", lounge: "Лаунж", outerwear: "Верхняя одежда" }; // компактный каталог для контекста модели function catalogForAI() { return getAllProducts().map((p) => `${p.id} · ${CAT_RU[p.cat] || p.cat} · «${p.name}» · ${p.sub} · ${p.price}₽` ).join("\n"); } const STYLIST_SYSTEM = `Ты — Стилист, AI-агент бренда одежды BATONS'ON (собственный бренд: головные уборы и трендовая одежда). Общайся тепло, кратко и по делу, на русском, как модный стилист-консультант. Без эмодзи. Собирай образы ТОЛЬКО из товаров каталога ниже — используй их точные id. Правила образа: 2–4 вещи, желательно с головным убором (это сердце бренда), логично сочетающиеся по случаю и стилю. Всегда, когда советуешь вещи, вызывай инструмент present_look. Короткое intro (1–2 предложения) — живой совет, почему этот образ. У каждой вещи note — 3–6 слов, зачем она. КАТАЛОГ: `; const STYLIST_CHIPS = [ { ic: "01", t: "Что надеть на вечер в городе" }, { ic: "02", t: "Собери пляжный образ на отпуск" }, { ic: "03", t: "Уличный лук на каждый день" }, { ic: "04", t: "Тепло и уютно дома" }, ]; function AIStylist({ onAdd, go }) { const [open, setOpen] = useStateE(false); const [msgs, setMsgs] = useStateE([]); // {role:'me'|'ai', text, look?:[{id,note}]} const [busy, setBusy] = useStateE(false); const [input, setInput] = useStateE(""); const scrollRef = useRefE(null); const taRef = useRefE(null); useEffectE(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [msgs, busy]); useEffectE(() => { const onKey = (e) => { if (e.key === "Escape") setOpen(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); async function ask(text) { const q = (text || "").trim(); if (!q || busy) return; setInput(""); const history = [...msgs, { role: "me", text: q }]; setMsgs(history); setBusy(true); // контекст: последние реплики -> messages const apiMessages = history.slice(-6).map((m) => ({ role: m.role === "me" ? "user" : "assistant", content: m.text + (m.look ? "\n[показан образ: " + m.look.map((x) => x.id).join(", ") + "]" : ""), })); let captured = null; try { const finalText = await window.claude.complete({ model: "claude-sonnet-4-5", max_tokens: 900, system: STYLIST_SYSTEM + catalogForAI(), messages: apiMessages, tools: [{ name: "present_look", description: "Показать пользователю собранный образ из товаров каталога BATONS'ON.", input_schema: { type: "object", properties: { intro: { type: "string", description: "1–2 предложения — живой совет стилиста, почему этот образ" }, items: { type: "array", description: "2–4 товара образа", items: { type: "object", properties: { id: { type: "string", description: "точный id товара из каталога" }, note: { type: "string", description: "3–6 слов, зачем эта вещь" }, }, required: ["id"], }, }, }, required: ["intro", "items"], }, run: async (inp) => { const valid = (inp.items || []).filter((x) => findProduct(x.id)); captured = { intro: inp.intro, items: valid }; return valid.length ? "Образ показан пользователю." : "Ни один id не найден в каталоге — попробуй другие."; }, }], }); if (captured && captured.items.length) { setMsgs((m) => [...m, { role: "ai", text: captured.intro, look: captured.items }]); } else { setMsgs((m) => [...m, { role: "ai", text: finalText || "Уточните повод или стиль — и я соберу образ." }]); } } catch (e) { setMsgs((m) => [...m, { role: "ai", text: "Не получилось собрать образ прямо сейчас. Попробуйте ещё раз через минуту." }]); } finally { setBusy(false); } } const openProduct = (id) => { const p = findProduct(id); if (p) { setOpen(false); go({ name: "product", product: p }); } }; return ( <>