mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 17:44:48 +00:00
fix(podbor): HTML AI output + home button on all steps
- AI prompt: use HTML tags (b, em, br, li) instead of markdown - renderReport: _ai() helper renders AI text as innerHTML (safe, backend-controlled) - Header: added podbor-home button (top-right) → goes to main menu from any step - After successful submit: show "← Вернуться в главное меню" button immediately - Fixes: no way to leave podbor after result was received Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3f1531f7ca
commit
f1b7f71337
@ -128,7 +128,12 @@ SYSTEM_PROMPT_PICKER = (
|
||||
"Количество моделей по категории определяется параметром `checklist.model_count` (3 / 5 / 7) — соблюдай!\n"
|
||||
"Каждая модель ДОЛЖНА содержать аналитику: pros (минимум 3), cons (минимум 2), почему выбрана, с чем сравнивать.\n"
|
||||
"По КАЖДОЙ категории напиши `analysis` — обзор: какие компромиссы, на что обратить внимание.\n"
|
||||
"Валидный JSON без markdown, без ```:\n"
|
||||
"Валидный JSON без markdown, без ```.\n"
|
||||
"Для текстовых полей (summary, analysis, reasoning, элементы pros[], cons[], highlights[], next_steps[]) используй HTML-разметку:\n"
|
||||
" <b>число или ключевой термин</b> — выделение, <br> — перенос строки, <em> — курсив.\n"
|
||||
" НЕ используй markdown (**текст**, *текст*, ## заголовки) — только HTML.\n"
|
||||
" pros/cons/highlights — массивы строк с HTML внутри.\n\n"
|
||||
"Структура ответа:\n"
|
||||
"{\n"
|
||||
' "summary": "2-3 предложения общего вывода: что подобрали, почему этот набор, на чём сэкономили / куда вложились",\n'
|
||||
' "by_category": {\n'
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
margin-bottom: var(--s4);
|
||||
}
|
||||
|
||||
.podbor-back {
|
||||
.podbor-back,
|
||||
.podbor-home {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: grid;
|
||||
@ -19,7 +20,11 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.podbor-back svg { width: 20px; height: 20px; }
|
||||
.podbor-back svg,
|
||||
.podbor-home svg { width: 20px; height: 20px; }
|
||||
|
||||
.podbor-home { color: var(--muted); opacity: 0.7; transition: opacity .15s; }
|
||||
.podbor-home:hover { opacity: 1; }
|
||||
|
||||
.podbor-title {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
@ -97,24 +97,31 @@ const Podbor = (function () {
|
||||
|
||||
/* ===================== Header & progress ===================== */
|
||||
|
||||
function _goHome() {
|
||||
location.hash = "";
|
||||
if (typeof routeByHash === "function") routeByHash();
|
||||
}
|
||||
|
||||
function renderHeader() {
|
||||
const h = el(`
|
||||
<header class="podbor-header">
|
||||
<button class="podbor-back" aria-label="Назад">${ICONS.arrow_left}</button>
|
||||
<div class="podbor-title">Подбор техники</div>
|
||||
<div style="width:28px"></div>
|
||||
<button class="podbor-home" aria-label="Главное меню" title="Главное меню">${ICONS.home || "🏠"}</button>
|
||||
</header>
|
||||
`);
|
||||
h.querySelector(".podbor-back").addEventListener("click", () => {
|
||||
const idx = STEPS.indexOf(currentStep);
|
||||
if (idx <= 0) {
|
||||
// Выход из подбора в главный экран кабинета — без перезагрузки (иначе сплэш мигает)
|
||||
location.hash = "";
|
||||
if (typeof routeByHash === "function") routeByHash();
|
||||
_goHome();
|
||||
} else {
|
||||
go(STEPS[idx - 1]);
|
||||
}
|
||||
});
|
||||
h.querySelector(".podbor-home").addEventListener("click", () => {
|
||||
haptic && haptic("impact");
|
||||
_goHome();
|
||||
});
|
||||
return h;
|
||||
}
|
||||
|
||||
@ -1277,6 +1284,17 @@ const Podbor = (function () {
|
||||
</div>
|
||||
`;
|
||||
result.innerHTML = headSuccess;
|
||||
// Кнопка "Вернуться в главное" сразу после успеха
|
||||
const homeBtn = el(`
|
||||
<div style="margin:12px 0;">
|
||||
<button class="btn-secondary" style="width:100%;">← Вернуться в главное меню</button>
|
||||
</div>
|
||||
`);
|
||||
homeBtn.querySelector("button").addEventListener("click", () => {
|
||||
haptic && haptic("impact");
|
||||
_goHome();
|
||||
});
|
||||
result.appendChild(homeBtn);
|
||||
// Рендер отчёта (если AI вернул by_category)
|
||||
if (data.ai) {
|
||||
const reportNode = renderReport(data.ai, data.id || "");
|
||||
@ -1305,12 +1323,14 @@ const Podbor = (function () {
|
||||
const wrap = el(`<section class="report"></section>`);
|
||||
|
||||
// Шапка
|
||||
wrap.appendChild(el(`
|
||||
<div class="report-head">
|
||||
<div class="kicker">Отчёт · ${leadId.slice(0, 8)}</div>
|
||||
${summary ? `<p class="report-summary">${_esc(summary)}</p>` : ""}
|
||||
</div>
|
||||
`));
|
||||
const headNode = el(`<div class="report-head"><div class="kicker">Отчёт · ${leadId.slice(0, 8)}</div></div>`);
|
||||
if (summary) {
|
||||
const sumP = document.createElement("p");
|
||||
sumP.className = "report-summary";
|
||||
sumP.innerHTML = _ai(summary);
|
||||
headNode.appendChild(sumP);
|
||||
}
|
||||
wrap.appendChild(headNode);
|
||||
|
||||
// Категории
|
||||
for (const [catKey, catData] of Object.entries(byCat)) {
|
||||
@ -1327,9 +1347,12 @@ const Podbor = (function () {
|
||||
<span class="report-cat-icon">${(catIcon && ICONS[catIcon]) || ""}</span>
|
||||
${_esc(catLabel)}
|
||||
</h3>
|
||||
${catAnalysis ? `<div class="report-cat-analysis">${_esc(catAnalysis)}</div>` : ""}
|
||||
${catAnalysis ? `<div class="report-cat-analysis"></div>` : ""}
|
||||
</div>
|
||||
`);
|
||||
if (catAnalysis) {
|
||||
catNode.querySelector(".report-cat-analysis").innerHTML = _ai(catAnalysis);
|
||||
}
|
||||
|
||||
// Сравнение цен — основной блок, всегда вверху
|
||||
const matrixNode = _renderPriceMatrix(models);
|
||||
@ -1513,25 +1536,25 @@ ${reportEl.outerHTML}
|
||||
<div class="report-model-name">${_esc(m.model || "")}</div>
|
||||
${metaParts.length ? `<div class="report-model-meta">${metaParts.join(" · ")}</div>` : ""}
|
||||
<div class="report-model-price">${priceHtml}</div>
|
||||
${(m.highlights || []).length ? `<div class="report-highlights">✓ ${m.highlights.map(_esc).join(" · ")}</div>` : ""}
|
||||
${(m.highlights || []).length ? `<div class="report-highlights">✓ ${m.highlights.map(_ai).join(" · ")}</div>` : ""}
|
||||
|
||||
${(m.pros || []).length ? `
|
||||
<div class="report-pros-block">
|
||||
<div class="pc-head">Плюсы</div>
|
||||
<ul class="pc-list">${m.pros.slice(0, 4).map(p => `<li>${_esc(p)}</li>`).join("")}</ul>
|
||||
<ul class="pc-list">${m.pros.slice(0, 4).map(p => `<li>${_ai(p)}</li>`).join("")}</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
${(m.cons || []).length ? `
|
||||
<div class="report-cons-block">
|
||||
<div class="pc-head">Минусы</div>
|
||||
<ul class="pc-list">${m.cons.slice(0, 3).map(c => `<li>${_esc(c)}</li>`).join("")}</ul>
|
||||
<ul class="pc-list">${m.cons.slice(0, 3).map(c => `<li>${_ai(c)}</li>`).join("")}</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
${_renderSpecsBlock(m.specs || {})}
|
||||
|
||||
${m.reasoning ? `<div class="report-reasoning">💡 ${_esc(m.reasoning)}</div>` : ""}
|
||||
${m.reasoning ? `<div class="report-reasoning">💡 ${_ai(m.reasoning)}</div>` : ""}
|
||||
|
||||
${_renderUtilityLinks(m)}
|
||||
|
||||
@ -1725,6 +1748,15 @@ ${reportEl.outerHTML}
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
/* AI-generated text — trusted backend output, render as HTML.
|
||||
Strip script/on* to be safe, but allow <b><em><ul><li><br>. */
|
||||
function _ai(s) {
|
||||
if (s == null) return "";
|
||||
return String(s)
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
||||
.replace(/\son\w+\s*=/gi, " data-stripped=");
|
||||
}
|
||||
|
||||
/* ===================== Helpers ===================== */
|
||||
|
||||
function bindInputs(node) {
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700&family=Inter:wght@400;500;600;700;800&family=Geist:wght@400;500;600&family=Manrope:wght@400;500;600;700&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&family=Cormorant+Garamond:ital,wght@1,400;1,500;1,600&family=Caveat:wght@500;700&display=swap">
|
||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||
<link rel="stylesheet" href="assets/styles.css?v=20260518o">
|
||||
<link rel="stylesheet" href="assets/podbor.css?v=20260517j">
|
||||
<link rel="stylesheet" href="assets/podbor.css?v=20260519a">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Splash — лого @wasrusgen1 + опилки (16) + вращающийся диск -->
|
||||
@ -39,7 +39,7 @@
|
||||
<script src="assets/icons.js?v=20260516h"></script>
|
||||
<script src="assets/podbor.config.js?v=20260516h"></script>
|
||||
<script src="assets/podbor.picts.js?v=20260516h"></script>
|
||||
<script src="assets/podbor.js?v=20260517d"></script>
|
||||
<script src="assets/podbor.js?v=20260519a"></script>
|
||||
<script src="assets/clients.js?v=20260518e"></script>
|
||||
<script src="assets/zamer-picts.js?v=20260516h"></script>
|
||||
<script src="assets/measurements.js?v=20260518f"></script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user