vanna-clubpetro/static/embed-demo.html
leonardosalazar-cp 1d152c0dce Initial commit: Vanna 2.0 deployment for ClubPetro
Wrapper application around upstream Vanna with:
- Tenant-aware ChromaDB memory (per program/store)
- ClickHouse RLS runner with introspection guards
- PT-BR system prompt and chat translations
- Custom Plotly chart generator (ranked bar, datetime coercion)
- Embed bootstrap (theme pierce + i18n + markdown) shared by demo and React app
- Event sink for chat turn observability
2026-04-29 17:22:05 -03:00

187 lines
6.7 KiB
HTML

<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<title>Vanna chat — demo</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap"
/>
<link rel="stylesheet" href="/vanna-theme.css" />
<!-- Bootstrap único do <vanna-chat>: theme pierce + tradutor PT + markdown
+ load do bundle. Mesmo arquivo é consumido pela app React via
vannaChatLoader.ts, garantindo fonte única (sem duplicação). -->
<script src="/vanna-embed-bootstrap.js"></script>
<style>
body {
margin: 0;
min-height: 100vh;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
color: #111827;
background: url("/dashboard-bg.png") top left / cover no-repeat fixed,
#f8efe2;
}
.wrap {
display: none;
}
/* Floating CTA — substitui o FAB nativo do <vanna-chat>.
Posicionado fixed bottom-right; clique abre o chat e esconde
o próprio CTA. Listener no evento window-state-changed do
componente alterna a visibilidade. */
.vanna-cta {
position: fixed;
right: 24px;
top: 100px;
z-index: 2147483000;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 4px;
background: #ffffff;
border: 1px solid transparent;
border-radius: 8px;
background-image: linear-gradient(#fff, #fff),
linear-gradient(90deg, #f2672a 5%, #680367 100%);
background-origin: border-box;
background-clip: padding-box, border-box;
box-shadow:
3px 5px 15px rgba(235, 98, 45, 0.19),
13px 24px 27px rgba(235, 98, 45, 0.16),
28px 53px 36px rgba(235, 98, 45, 0.10),
50px 95px 43px rgba(235, 98, 45, 0.03);
cursor: pointer;
font-family: "Open Sans", system-ui, sans-serif;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.vanna-cta[hidden] {
display: none;
}
.vanna-cta:hover {
transform: translateY(-1px);
}
.vanna-cta:active {
transform: translateY(0);
}
.vanna-cta__logo {
flex: none;
width: 42px;
height: 42px;
display: grid;
place-items: center;
}
.vanna-cta__logo img,
.vanna-cta__logo svg {
width: 100%;
height: 100%;
display: block;
}
.vanna-cta__pill {
flex: 1;
padding: 10px 18px;
border-radius: 5px;
background: linear-gradient(90deg, #f2672a 5%, #680367 100%);
color: #fff;
font-size: 14px;
font-weight: 500;
line-height: 1.15;
white-space: nowrap;
}
/* O CTA custom é o único ponto de entrada. Esconde o componente
enquanto não estiver maximizado: oculta o FAB nativo (estado
minimized, círculo 64x64) E também previne o flash de centralizado
se o JS de minimização atrasar (estado normal antes do windowState
setter rodar). Quando o CTA dispara windowState='maximized', a regra
deixa de casar e o chat aparece. */
vanna-chat:not(.maximized) {
display: none !important;
}
</style>
</head>
<body>
<div class="wrap">
<h1>Vanna chat — demo</h1>
<p>
Esta página embeda o web component oficial
<code>&lt;vanna-chat&gt;</code> com
<code>starting-state="minimized"</code> — o botão flutuante aparece no
canto inferior direito. Os IDs vêm do <code>.env</code> via
substituição no handler.
</p>
<p>
Em produção, o app cliente cola um snippet semelhante na página dele,
substituindo <code>__PROGRAM_ID__</code> / <code>__STORE_ID__</code>
pelos IDs do tenant logado.
</p>
</div>
<button
type="button"
id="vanna-cta"
class="vanna-cta"
aria-label="Conversar com meus dados"
>
<span class="vanna-cta__logo" aria-hidden="true">
<img src="/clubpetro-logo.svg" alt="" />
</span>
<span class="vanna-cta__pill">Conversar com meus dados</span>
</button>
<script>
// Dispara o bootstrap (theme pierce + tradutor + markdown + load do
// bundle). baseUrl vazio = mesma origem (server FastAPI local).
window.VannaEmbed.ensureLoaded({ baseUrl: "" });
</script>
<vanna-chat
id="vanna-chat-instance"
api-base=""
sse-endpoint="/api/vanna/v2/chat_sse?program_id=__PROGRAM_ID__&store_id=__STORE_ID__&user_id=__USER_ID__"
ws-endpoint="/api/vanna/v2/chat_websocket?program_id=__PROGRAM_ID__&store_id=__STORE_ID__&user_id=__USER_ID__"
poll-endpoint="/api/vanna/v2/chat_poll?program_id=__PROGRAM_ID__&store_id=__STORE_ID__&user_id=__USER_ID__"
startingstate="minimized"
theme="light"
title="ClubPetro IA">
</vanna-chat>
<script>
// showProgress default=true e Lit boolean attrs não desligam via HTML.
// Setar a propriedade JS direto após o registro do custom element.
customElements.whenDefined("vanna-chat").then(() => {
const chat = document.getElementById("vanna-chat-instance");
const cta = document.getElementById("vanna-cta");
if (!chat) return;
chat.showProgress = false;
// Reforço: garantir minimized mesmo se o atributo não pegar.
if (chat.windowState !== "minimized") chat.windowState = "minimized";
// Sync inicial — começamos minimized via attribute, então CTA visível.
// Ao clicar no CTA, expandimos o chat (windowState='normal') e
// escondemos o CTA. Quando o user minimiza pelo header do chat,
// o componente emite window-state-changed; aí mostramos o CTA de novo.
const sync = (state) => {
if (!cta) return;
cta.hidden = state !== "minimized";
};
sync(chat.windowState);
if (cta) {
cta.addEventListener("click", () => {
// Esconder explicitamente — o setter de windowState NÃO dispara
// window-state-changed (só os métodos privados disparam), então
// o listener abaixo só pega a transição quando o user minimiza
// pelo header. Pra abrir, escondemos manualmente aqui.
cta.hidden = true;
chat.windowState = "maximized";
});
}
chat.addEventListener("window-state-changed", (e) => {
sync(e.detail?.state);
});
});
</script>
</body>
</html>