Files
waggle-os/docs/design_handoff_waggle_app/design-files/screens/chat.html
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

272 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Waggle · Chat</title>
<script>document.documentElement.classList.add('js');</script>
<link rel="stylesheet" href="../styles/waggle.css">
<style>
body { height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
/* ── Concept control bar ─────────────────────────────────────── */
.controls { flex: none; display: flex; align-items: center; gap: 14px; padding: 11px 20px; border-bottom: 1px solid var(--line-soft); background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(10px); z-index: 30; }
.controls .lab { font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--text-dim); }
.seg { display: inline-flex; padding: 3px; gap: 3px; border-radius: 10px; background: var(--surface-2); border: 1px solid var(--line-soft); }
.seg button { font-family: var(--sans); font-size: 12.5px; font-weight: 600; cursor: pointer; border: 0; background: transparent; color: var(--text-muted); padding: 6px 13px; border-radius: 7px; transition: .14s; white-space: nowrap; }
.seg button.on { background: var(--honey); color: #1a1407; }
.controls .vlabel { font-size: 12.5px; color: var(--text-muted); }
.controls .vlabel b { color: var(--text); font-weight: 600; }
.controls .right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.tbtn { width: 34px; height: 34px; border-radius: 9px; border: 1px solid var(--line); background: var(--surface); color: var(--text-2); cursor: pointer; }
.tbtn:hover { border-color: var(--honey-line); color: var(--honey); }
/* ── App frame ───────────────────────────────────────────────── */
.app { flex: 1; min-height: 0; display: flex; }
.pane { display: flex; flex-direction: column; min-height: 0; min-width: 0; flex: 1; }
/* context header */
.ctx { flex: none; display: flex; align-items: center; gap: 14px; padding: 14px 26px; border-bottom: 1px solid var(--line-soft); }
.ctx .wmark { width: 30px; height: 34px; flex: none; display: grid; place-items: center; font-weight: 800; font-size: 13px; color: #1a1407; background: linear-gradient(150deg, var(--honey-bright), var(--honey-deep)); }
.ctx .wname { font-size: 15.5px; font-weight: 650; letter-spacing: -0.01em; }
.ctx .wsub { font-size: 12px; color: var(--text-dim); font-family: var(--mono); }
.ctx .right { margin-left: auto; display: flex; align-items: center; gap: 9px; }
.modelpill { display: inline-flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 999px; border: 1px solid var(--line); background: var(--surface); font-size: 12.5px; color: var(--text-2); cursor: pointer; transition:.15s; }
.modelpill:hover { border-color: var(--honey-line); }
.modelpill .dot { background: var(--healthy); }
.modelpill b { color: var(--text); font-weight: 600; }
.iconbtn { width: 34px; height: 34px; border-radius: 9px; border: 1px solid var(--line); background: var(--surface); color: var(--text-muted); cursor: pointer; display: grid; place-items: center; transition:.15s; }
.iconbtn:hover { border-color: var(--honey-line); color: var(--honey); }
.iconbtn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.8; }
/* thread */
.thread { flex: 1; min-height: 0; overflow: auto; padding: 30px 0; }
.thread-inner { max-width: 760px; margin: 0 auto; padding: 0 26px; display: flex; flex-direction: column; gap: 26px; }
.msg { display: flex; gap: 14px; }
.msg .av { width: 30px; height: 34px; flex: none; display: grid; place-items: center; font-size: 12px; font-weight: 700; }
.msg.user .av { background: var(--surface-3); color: var(--text-2); border-radius: 9px; }
.msg.bot .av { background: linear-gradient(150deg, var(--honey-bright), var(--honey-deep)); color: #1a1407; }
.msg .body { flex: 1; min-width: 0; }
.msg .who { font-size: 12px; color: var(--text-dim); margin-bottom: 7px; display: flex; align-items: center; gap: 8px; }
.msg .who b { color: var(--text-2); font-weight: 600; }
.msg.user .bubble { font-size: 15.5px; line-height: 1.55; color: var(--text); background: var(--surface); border: 1px solid var(--line-soft); padding: 13px 17px; border-radius: 4px 14px 14px 14px; }
.msg.bot .prose { font-size: 15.5px; line-height: 1.62; color: var(--text); }
.msg.bot .prose p { margin: 0 0 12px; }
.msg.bot .prose ul { margin: 0 0 12px; padding-left: 4px; list-style: none; display: grid; gap: 8px; }
.msg.bot .prose li { display: grid; grid-template-columns: 16px 1fr; gap: 9px; }
.msg.bot .prose li .b { color: var(--honey); }
.msg.bot .prose b { font-weight: 650; }
/* activity stream (the "magic", progressively disclosed) */
.activity { margin: 0 0 16px; border: 1px solid var(--line-soft); border-radius: var(--r); overflow: hidden; background: var(--bg-2); }
.activity .ahead { display: flex; align-items: center; gap: 10px; padding: 11px 15px; cursor: pointer; user-select: none; }
.activity .ahead .spark { width: 18px; height: 18px; flex:none; }
.activity .ahead .spark svg { width: 18px; height: 18px; stroke: var(--intel); fill: none; stroke-width: 1.8; }
.activity .ahead .t { flex: 1; font-size: 13px; color: var(--text-2); font-weight: 550; }
.activity .ahead .t b { color: var(--text); font-weight: 650; }
.activity .ahead .chev { color: var(--text-dim); font-size: 12px; transition:.2s; }
.activity.open .ahead .chev { transform: rotate(90deg); }
.activity .steps { display: none; padding: 4px 15px 14px 15px; flex-direction: column; gap: 0; }
.activity.open .steps { display: flex; }
.astep { display: flex; align-items: flex-start; gap: 12px; padding: 9px 0; border-top: 1px solid var(--line-soft); position: relative; }
.astep .sd { width: 9px; height: 9px; border-radius: 999px; margin-top: 5px; flex: none; }
.astep .stext { flex: 1; font-size: 13px; color: var(--text-2); line-height: 1.5; }
.astep .stext b { color: var(--text); font-weight: 600; }
.astep .stext .mono { font-family: var(--mono); font-size: 11.5px; color: var(--text-dim); }
.astep .prov { margin-top: 6px; display: inline-flex; align-items: center; gap: 7px; font-family: var(--mono); font-size: 10.5px; color: var(--intel); background: var(--intel-wash); border: 1px solid color-mix(in srgb, var(--intel) 28%, transparent); padding: 3px 9px; border-radius: 7px; }
/* approval card */
.approval { border: 1px solid color-mix(in srgb, var(--attention) 35%, transparent); background: var(--honey-wash); border-radius: var(--r); padding: 16px 18px; }
.approval .ah { display: flex; align-items: center; gap: 9px; font-size: 13.5px; font-weight: 650; }
.approval .ah svg { width: 17px; height: 17px; stroke: var(--attention); fill: none; stroke-width: 1.8; }
.approval p { margin: 8px 0 14px; font-size: 13px; color: var(--text-muted); line-height: 1.55; }
.approval p .mono { font-family: var(--mono); color: var(--text-2); font-size: 12px; }
.approval .actions { display: flex; gap: 10px; }
.approval .btn { font-family: var(--sans); font-size: 13px; font-weight: 650; padding: 9px 16px; border-radius: 9px; cursor: pointer; border: 1px solid transparent; }
.approval .btn.yes { background: var(--honey); color: #1a1407; }
.approval .btn.no { background: transparent; border-color: var(--line-strong); color: var(--text-2); }
/* composer */
.composer { flex: none; padding: 16px 26px 20px; border-top: 1px solid var(--line-soft); }
.composer-inner { max-width: 760px; margin: 0 auto; }
.cbox { border: 1px solid var(--line); background: var(--surface); border-radius: var(--r-lg); padding: 6px 6px 6px 18px; display: flex; align-items: flex-end; gap: 10px; transition:.15s; }
.cbox:focus-within { border-color: var(--honey-line); box-shadow: var(--honey-glow); }
.cbox textarea { flex: 1; border: 0; background: transparent; resize: none; color: var(--text); font-family: var(--sans); font-size: 15px; line-height: 1.5; outline: none; padding: 9px 0; max-height: 90px; }
.cbox textarea::placeholder { color: var(--text-dim); }
.cbox .send { width: 40px; height: 40px; flex:none; border-radius: 11px; border: 0; background: var(--honey); color: #1a1407; font-size: 17px; cursor: pointer; display:grid; place-items:center; }
.cbox .send:hover { background: var(--honey-bright); }
.ctools { display: flex; align-items: center; gap: 8px; margin-top: 11px; padding: 0 2px; }
.ctool { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); padding: 6px 10px; border-radius: 8px; border: 1px solid var(--line-soft); background: var(--bg-2); cursor: pointer; transition:.14s; }
.ctool:hover { border-color: var(--honey-line); color: var(--text); }
.ctool svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 1.9; }
.ctools .spacer { flex: 1; }
.ctools .hint { font-family: var(--mono); font-size: 11px; color: var(--text-dim); }
/* right rail / canvas (variation B) */
.canvas { flex: none; width: 0; overflow: hidden; border-left: 1px solid var(--line-soft); background: var(--bg-2); display: flex; flex-direction: column; transition: width .25s ease; }
body.split .thread-inner { max-width: 620px; }
.canvas-head { flex: none; display: flex; align-items: center; gap: 10px; padding: 14px 20px; border-bottom: 1px solid var(--line-soft); }
.canvas-head .ct { font-size: 13.5px; font-weight: 650; flex: 1; }
.canvas-head .ctag { font-family: var(--mono); font-size: 10.5px; color: var(--healthy); background: var(--healthy-wash); padding: 4px 9px; border-radius: 7px; }
.canvas-body { flex: 1; min-height: 0; overflow: auto; padding: 24px 26px; }
.doc h1 { font-size: 22px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 6px; }
.doc .docsub { font-size: 12.5px; color: var(--text-dim); font-family: var(--mono); margin-bottom: 20px; }
.doc h2 { font-size: 13px; font-family: var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--honey); margin: 22px 0 10px; }
.doc p { font-size: 14px; line-height: 1.62; color: var(--text-2); margin: 0 0 12px; }
.doc table { width: 100%; border-collapse: collapse; font-size: 12.5px; margin: 6px 0 12px; }
.doc th, .doc td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line-soft); }
.doc th { color: var(--text-dim); font-weight: 600; font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; }
.doc td { color: var(--text-2); }
.doc td b { color: var(--text); }
.typing-cursor::after { content:'▍'; color: var(--honey); animation: bl 1s steps(2) infinite; }
@keyframes bl { 50% { opacity: 0; } }
.hidden { display: none !important; }
@media (max-width: 820px) { body.split .canvas { display: none; } }
</style>
</head>
<body class="split">
<div class="controls">
<span class="lab">Chat · variation</span>
<div class="seg" id="seg">
<button data-v="a">A · Conversation-first</button>
<button class="on" data-v="b">B · Split work canvas</button>
</div>
<span class="vlabel" id="vlabel"><b>Split work canvas</b> — chat builds the artifact live</span>
<div class="right"><button class="tbtn" id="tbtn" title="Toggle theme"></button></div>
</div>
<div class="app">
<!-- ── chat pane ── -->
<div class="pane">
<div class="ctx">
<div class="wmark hex">C</div>
<div>
<div class="wname">Competitive Intelligence</div>
<div class="wsub">workspace · 142 memories · 9 sources</div>
</div>
<div class="right">
<button class="modelpill" title="Waggle picked the model — click to override"><span class="dot dot-live"></span>Model: <b>auto</b> · Claude Sonnet</button>
<button class="iconbtn" title="Memory & context"><svg viewBox="0 0 24 24"><path d="M12 5a3 3 0 0 0-3 3 3 3 0 0 0-3 3 3 3 0 0 0 1 5 3 3 0 0 0 5 1 3 3 0 0 0 5-1 3 3 0 0 0 1-5 3 3 0 0 0-3-3 3 3 0 0 0-3-3Z"/></svg></button>
</div>
</div>
<div class="thread"><div class="thread-inner" id="thread"></div></div>
<div class="composer">
<div class="composer-inner">
<div class="cbox">
<textarea rows="1" placeholder="Reply, or ask Waggle to take the next step…"></textarea>
<button class="send"></button>
</div>
<div class="ctools">
<button class="ctool"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>Attach</button>
<button class="ctool"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Persona: Analyst</button>
<button class="ctool"><svg viewBox="0 0 24 24"><path d="M3 12h18M12 3v18"/></svg>Tools</button>
<span class="spacer"></span>
<span class="hint">⏎ send · ⌘K commands</span>
</div>
</div>
</div>
</div>
<!-- ── work canvas (variation B) ── -->
<aside class="canvas" id="canvas" style="width:42%">
<div class="canvas-head">
<span class="ct">teardown.md</span>
<span class="ctag">● live draft</span>
<button class="iconbtn" title="Open in Artifacts"><svg viewBox="0 0 24 24"><path d="M14 3h7v7M21 3l-9 9M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"/></svg></button>
</div>
<div class="canvas-body">
<div class="doc">
<h1>Q2 Competitive Teardown</h1>
<div class="docsub">draft · 9 competitors · updated just now by Waggle</div>
<h2>Executive summary</h2>
<p>The category is consolidating around persistent-memory positioning, but only two rivals ship local-first. The clearest opening is <b>regulated industries</b>, where cloud-only memory is a non-starter and no incumbent has a credible audit story.</p>
<h2>Landscape</h2>
<table>
<tr><th>Player</th><th>Memory</th><th>Local-first</th></tr>
<tr><td><b>Mem0</b></td><td>Cloud</td><td>No</td></tr>
<tr><td><b>Letta</b></td><td>Agent-centric</td><td>Partial</td></tr>
<tr><td><b>LangMem</b></td><td>Toy-tier</td><td>No</td></tr>
<tr><td><b>Notion AI</b></td><td>Doc-scoped</td><td>No</td></tr>
</table>
<p>Pricing pulled from public pages; full table in the appendix. <span class="typing-cursor"></span></p>
</div>
</div>
</aside>
</div>
<script>
/* theme */
const root = document.documentElement, tbtn = document.getElementById('tbtn');
function setTheme(t){ root.setAttribute('data-theme', t); tbtn.textContent = t==='dark'?'☾':'☀'; try{localStorage.setItem('waggle-concept-theme',t);}catch(e){} }
setTheme((()=>{ try{ return localStorage.getItem('waggle-concept-theme')||'dark'; }catch(e){ return 'dark'; } })());
tbtn.onclick = () => setTheme(root.getAttribute('data-theme')==='dark'?'light':'dark');
window.addEventListener('message', e => { if(e.data && e.data.theme) setTheme(e.data.theme); });
window.addEventListener('storage', e => { if(e.key==='waggle-concept-theme' && e.newValue) setTheme(e.newValue); });
const SPARK = '<svg viewBox="0 0 24 24"><path d="M12 3v3M12 18v3M5 12H2M22 12h-3M6 6 4 4M20 20l-2-2M6 18l-2 2M20 4l-2 2"/><circle cx="12" cy="12" r="3.5"/></svg>';
const WARN = '<svg viewBox="0 0 24 24"><path d="M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>';
const thread = [
{ role:'user', who:'You', text:'Pull together a competitive teardown for Q2 — who else is selling persistent memory, and where\u2019s our opening?' },
{ role:'bot', who:'Waggle', activity:{
title:'Worked across <b>memory, web &amp; files</b>', sub:'6 steps · 38s',
steps:[
{ c:'var(--intel)', t:'Recalled <b>6 memories</b> from your hive — last quarter\u2019s landscape, the Mem0 teardown, your pricing notes.', prov:'mem://hive · provenance kept' },
{ c:'var(--step-web,#06b6d4)', t:'Searched <b>9 competitor sites</b> for pricing &amp; positioning <span class="mono">(mem0, letta, langmem, notion…)</span>' },
{ c:'var(--work)', t:'Compared against your <b>last teardown</b> — flagged 3 changes since March.' },
{ c:'var(--healthy)', t:'Drafted <b>teardown.md</b> — exec summary, landscape table, opening.' },
]
},
prose:`<p>Done. Here\u2019s the shape of it — full draft is in <b>teardown.md</b>.</p>
<ul>
<li><span class="b">◆</span><div>The field is consolidating on <b>persistent-memory</b> messaging, but only two rivals ship <b>local-first</b>.</div></li>
<li><span class="b">◆</span><div>Your clearest opening is <b>regulated industries</b> — cloud-only memory is a non-starter there, and nobody has a credible audit story.</div></li>
<li><span class="b">◆</span><div>Mem0 raised prices ~15% since March; I noted it against your pricing model.</div></li>
</ul>
<p>Want me to turn the opening into a one-page brief for the board, or export the table to the pricing sheet?</p>`
},
{ role:'user', who:'You', text:'Export the table to the Q2 pricing sheet in Salesforce.' },
{ role:'bot', who:'Waggle', approval:{
title:'Approve before I leave your machine',
body:'This step writes to an external system — <span class="mono">Salesforce Q2 Pricing</span>. Everything else stayed local. I\u2019ll export the 9-row table and nothing else.',
} },
];
function renderThread(){
const el = document.getElementById('thread');
el.innerHTML = thread.map((m,i) => {
if(m.role==='user') return `<div class="msg user"><div class="av">You</div><div class="body"><div class="who"><b>You</b></div><div class="bubble">${m.text}</div></div></div>`;
let inner = '';
if(m.activity){
inner += `<div class="activity ${i===1?'open':''}"><div class="ahead"><span class="spark">${SPARK}</span><span class="t">${m.activity.title} <span style="color:var(--text-dim)">· ${m.activity.sub}</span></span><span class="chev">▸</span></div><div class="steps">${
m.activity.steps.map(s=>`<div class="astep"><span class="sd" style="background:${s.c}"></span><span class="stext">${s.t}${s.prov?`<div class="prov">⬡ ${s.prov}</div>`:''}</span></div>`).join('')
}</div></div>`;
}
if(m.prose) inner += `<div class="prose">${m.prose}</div>`;
if(m.approval) inner += `<div class="approval"><div class="ah">${WARN} ${m.approval.title}</div><p>${m.approval.body}</p><div class="actions"><button class="btn yes">Approve &amp; export</button><button class="btn no">Not now</button></div></div>`;
return `<div class="msg bot"><div class="av hex">W</div><div class="body"><div class="who"><b>Waggle</b> · Analyst · Claude Sonnet</div>${inner}</div></div>`;
}).join('');
el.querySelectorAll('.activity .ahead').forEach(h => h.onclick = () => h.parentElement.classList.toggle('open'));
}
renderThread();
/* variation switch */
const labels = { a:'<b>Conversation-first</b> — the work reveals inline', b:'<b>Split work canvas</b> — chat builds the artifact live' };
const seg = document.getElementById('seg');
seg.addEventListener('click', e => {
const b = e.target.closest('button'); if(!b) return;
[...seg.children].forEach(x=>x.classList.toggle('on', x===b));
const split = b.dataset.v==='b';
document.body.classList.toggle('split', split);
document.getElementById('canvas').style.width = split ? '42%' : '0px';
document.getElementById('vlabel').innerHTML = labels[b.dataset.v];
});
</script>
</body>
</html>