moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
<!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>