lavish-axi 0.1.36 → 0.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/chrome-client.js +22 -6
- package/dist/chrome.css +28 -7
- package/dist/cli.mjs +41 -5
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -157,6 +157,7 @@ pnpm link
|
|
|
157
157
|
- **Feedback controls** - Native controls (radios, checkboxes, inputs, selects, buttons, labels, disclosure summaries, contenteditable) are interactive automatically, so they do not need `data-lavish-action`.
|
|
158
158
|
For reversible choices, let option clicks update local state, then queue exactly one final answer from a per-question submit or Queue answer button with `window.lavish.queuePrompt()`.
|
|
159
159
|
Mark only custom (non-native) clickable elements with `data-lavish-action` so Lavish does not annotate them, and use `data-lavish-question` or `queueKey` when pre-send updates for the same question should replace each other.
|
|
160
|
+
Queued annotation preview pills and chat history share a scrollable Conversation panel above a sticky composer, so long feedback queues do not push the text box or send controls off screen.
|
|
160
161
|
The browser chrome keeps editing actions in the overflow menu (copy path, reload artifact, copy DOM snapshot, export standalone HTML, publish link, end session) and can submit queued prompts with **Send & end session**, which sends the prompts and user-ended attribution together.
|
|
161
162
|
- **Keyboard shortcuts** - In the chrome composer, Enter sends queued prompts and Shift+Enter inserts a newline.
|
|
162
163
|
In the annotation card, Enter queues the annotation, Shift+Enter inserts a newline, and Ctrl+Enter (Cmd+Enter on macOS) queues it and sends all queued prompts immediately.
|
package/dist/chrome-client.js
CHANGED
|
@@ -15,6 +15,7 @@ function isModeToggleHotkeyEvent(event) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const frame = /** @type {HTMLIFrameElement} */ (document.getElementById("artifact"));
|
|
18
|
+
const panelScroll = /** @type {HTMLDivElement} */ (document.getElementById("panelScroll"));
|
|
18
19
|
const annotationPills = /** @type {HTMLDivElement} */ (document.getElementById("annotationPills"));
|
|
19
20
|
const chatLog = /** @type {HTMLDivElement} */ (document.getElementById("chatLog"));
|
|
20
21
|
const chatInput = /** @type {HTMLTextAreaElement} */ (document.getElementById("chatInput"));
|
|
@@ -146,6 +147,7 @@ function render() {
|
|
|
146
147
|
closeButton.addEventListener("click", (event) => removeQueuedPrompt(Number(closeButton.dataset.index), event));
|
|
147
148
|
}
|
|
148
149
|
updateSendState();
|
|
150
|
+
scrollPanelToBottom();
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
function updateSendState() {
|
|
@@ -204,14 +206,15 @@ async function copyText(text) {
|
|
|
204
206
|
return true;
|
|
205
207
|
}
|
|
206
208
|
|
|
207
|
-
function addChat(role, text) {
|
|
209
|
+
function addChat(role, text, shouldScroll = true) {
|
|
208
210
|
if (!text) return;
|
|
209
211
|
|
|
210
212
|
const el = document.createElement("div");
|
|
211
213
|
el.className = "bubble " + role;
|
|
212
214
|
el.innerHTML = "<small>" + (role === "agent" ? "Agent" : "You") + "</small><div>" + escapeHtml(text) + "</div>";
|
|
213
215
|
chatLog.appendChild(el);
|
|
214
|
-
|
|
216
|
+
if (shouldScroll) scrollElementIntoView(el);
|
|
217
|
+
return el;
|
|
215
218
|
}
|
|
216
219
|
|
|
217
220
|
function syncChat(chat) {
|
|
@@ -219,9 +222,14 @@ function syncChat(chat) {
|
|
|
219
222
|
el.remove();
|
|
220
223
|
}
|
|
221
224
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
let lastChatBubble = null;
|
|
226
|
+
for (const item of chat) lastChatBubble = addChat(item.role, item.text, false) || lastChatBubble;
|
|
227
|
+
if (workingBubble) {
|
|
228
|
+
chatLog.appendChild(workingBubble);
|
|
229
|
+
scrollElementIntoView(workingBubble);
|
|
230
|
+
} else if (lastChatBubble) {
|
|
231
|
+
scrollElementIntoView(lastChatBubble);
|
|
232
|
+
}
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
function setAgentPresence(state) {
|
|
@@ -241,7 +249,15 @@ function setAgentPresence(state) {
|
|
|
241
249
|
workingBubble.innerHTML = '<span class="spinner"></span><span>Working...</span>';
|
|
242
250
|
chatLog.appendChild(workingBubble);
|
|
243
251
|
}
|
|
244
|
-
|
|
252
|
+
scrollElementIntoView(workingBubble);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function scrollPanelToBottom() {
|
|
256
|
+
panelScroll.scrollTop = panelScroll.scrollHeight;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function scrollElementIntoView(el) {
|
|
260
|
+
el.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
245
261
|
}
|
|
246
262
|
|
|
247
263
|
function removeQueuedPrompt(index, event) {
|
package/dist/chrome.css
CHANGED
|
@@ -639,11 +639,21 @@ body.lavish {
|
|
|
639
639
|
margin: 16px 16px 8px;
|
|
640
640
|
font-weight: var(--w-semi);
|
|
641
641
|
letter-spacing: 0;
|
|
642
|
+
flex: 0 0 auto;
|
|
643
|
+
}
|
|
644
|
+
.panel-scroll {
|
|
645
|
+
flex: 1 1 auto;
|
|
646
|
+
min-width: 0;
|
|
647
|
+
min-height: 0;
|
|
648
|
+
overflow-y: auto;
|
|
649
|
+
overflow-x: hidden;
|
|
650
|
+
display: flex;
|
|
651
|
+
flex-direction: column;
|
|
642
652
|
}
|
|
643
653
|
.chat {
|
|
644
|
-
flex:
|
|
654
|
+
flex: 0 0 auto;
|
|
645
655
|
min-height: 0;
|
|
646
|
-
overflow:
|
|
656
|
+
overflow: visible;
|
|
647
657
|
padding: 0 16px 12px;
|
|
648
658
|
display: flex;
|
|
649
659
|
flex-direction: column;
|
|
@@ -704,9 +714,13 @@ body.lavish {
|
|
|
704
714
|
gap: 8px;
|
|
705
715
|
padding: 12px 16px;
|
|
706
716
|
border-top: var(--hairline-subtle);
|
|
717
|
+
background: var(--bg-panel);
|
|
707
718
|
min-width: 0;
|
|
708
719
|
flex-shrink: 0;
|
|
709
720
|
box-sizing: border-box;
|
|
721
|
+
position: sticky;
|
|
722
|
+
bottom: 0;
|
|
723
|
+
z-index: 4;
|
|
710
724
|
}
|
|
711
725
|
.presence-banner {
|
|
712
726
|
border: 1px solid var(--border);
|
|
@@ -726,9 +740,17 @@ body.lavish {
|
|
|
726
740
|
flex-wrap: wrap;
|
|
727
741
|
gap: 6px;
|
|
728
742
|
min-width: 0;
|
|
743
|
+
padding: 0 16px 12px;
|
|
744
|
+
flex: 0 0 auto;
|
|
745
|
+
}
|
|
746
|
+
.annotation-pills:empty {
|
|
747
|
+
display: none;
|
|
729
748
|
}
|
|
730
749
|
.pill-wrap {
|
|
731
|
-
|
|
750
|
+
display: flex;
|
|
751
|
+
flex-direction: column;
|
|
752
|
+
align-items: flex-start;
|
|
753
|
+
width: min(320px, 100%);
|
|
732
754
|
max-width: 100%;
|
|
733
755
|
}
|
|
734
756
|
.pill {
|
|
@@ -769,11 +791,10 @@ body.lavish {
|
|
|
769
791
|
}
|
|
770
792
|
.pill-tooltip {
|
|
771
793
|
display: none;
|
|
772
|
-
position:
|
|
794
|
+
position: static;
|
|
773
795
|
z-index: 5;
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
width: min(320px, 80vw);
|
|
796
|
+
width: 100%;
|
|
797
|
+
margin-top: 8px;
|
|
777
798
|
border: 1px solid var(--border-strong);
|
|
778
799
|
border-radius: var(--radius-lg);
|
|
779
800
|
background: var(--bg-bar);
|
package/dist/cli.mjs
CHANGED
|
@@ -5717,7 +5717,15 @@ async function serve({
|
|
|
5717
5717
|
return;
|
|
5718
5718
|
}
|
|
5719
5719
|
await watchSession(session, watchers, events, logEvent);
|
|
5720
|
-
|
|
5720
|
+
const artifactHtml = await readFile3(session.file, "utf8").catch(() => "");
|
|
5721
|
+
const { faviconTag, title } = extractArtifactHead(artifactHtml);
|
|
5722
|
+
res.type("html").send(
|
|
5723
|
+
createChromeHtml(session, {
|
|
5724
|
+
layoutGateEnabled: shouldEnableLayoutGate(req.query || {}),
|
|
5725
|
+
faviconTag,
|
|
5726
|
+
title: title ? `${title} \xB7 Lavish` : "Lavish Editor"
|
|
5727
|
+
})
|
|
5728
|
+
);
|
|
5721
5729
|
} catch (error) {
|
|
5722
5730
|
next(error);
|
|
5723
5731
|
}
|
|
@@ -6141,7 +6149,34 @@ function normalizeFlagValue(value) {
|
|
|
6141
6149
|
if (Array.isArray(value)) return normalizeFlagValue(value[0]);
|
|
6142
6150
|
return value === void 0 || value === null ? "" : String(value).trim().toLowerCase();
|
|
6143
6151
|
}
|
|
6144
|
-
|
|
6152
|
+
var LAVISH_DEFAULT_FAVICON = `<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>\u{1F48E}</text></svg>">`;
|
|
6153
|
+
function readTagAttr(tag, name) {
|
|
6154
|
+
const attrRe = /([a-z][\w:-]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
6155
|
+
const target = name.toLowerCase();
|
|
6156
|
+
let match;
|
|
6157
|
+
while ((match = attrRe.exec(tag)) !== null) {
|
|
6158
|
+
if (match[1].toLowerCase() === target) {
|
|
6159
|
+
return (match[3] ?? match[4] ?? match[5] ?? "").trim();
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
return "";
|
|
6163
|
+
}
|
|
6164
|
+
function extractArtifactHead(html) {
|
|
6165
|
+
const head = String(html || "").slice(0, 1e4);
|
|
6166
|
+
let faviconTag = LAVISH_DEFAULT_FAVICON;
|
|
6167
|
+
const linkTags = head.match(/<link\b(?:"[^"]*"|'[^']*'|[^"'>])*>/gi) || [];
|
|
6168
|
+
const iconTag = linkTags.find((tag) => /(^|\s)icon(\s|$)/i.test(readTagAttr(tag, "rel")));
|
|
6169
|
+
const iconHref = iconTag ? readTagAttr(iconTag, "href") : "";
|
|
6170
|
+
if (iconHref && /^(data:|https?:|\/\/)/i.test(iconHref)) {
|
|
6171
|
+
const safeHref = iconHref.replace(/&/g, "&").replace(/"/g, """);
|
|
6172
|
+
faviconTag = `<link rel="icon" href="${safeHref}">`;
|
|
6173
|
+
}
|
|
6174
|
+
let title = "";
|
|
6175
|
+
const titleMatch = head.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
6176
|
+
if (titleMatch) title = titleMatch[1].replace(/\s+/g, " ").trim();
|
|
6177
|
+
return { faviconTag, title };
|
|
6178
|
+
}
|
|
6179
|
+
function createChromeHtml(session, { layoutGateEnabled = true, faviconTag = LAVISH_DEFAULT_FAVICON, title = "Lavish Editor" } = {}) {
|
|
6145
6180
|
const sessionJson = jsonScript({
|
|
6146
6181
|
key: session.key,
|
|
6147
6182
|
file: session.file,
|
|
@@ -6159,12 +6194,13 @@ function createChromeHtml(session, { layoutGateEnabled = true } = {}) {
|
|
|
6159
6194
|
<head>
|
|
6160
6195
|
<meta charset="utf-8">
|
|
6161
6196
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6162
|
-
<title
|
|
6197
|
+
<title>${escapeHtml(title)}</title>
|
|
6198
|
+
${faviconTag}
|
|
6163
6199
|
<link rel="stylesheet" href="/chrome.css">
|
|
6164
6200
|
</head>
|
|
6165
6201
|
<body class="${bodyClass}">
|
|
6166
6202
|
<div class="bar"><div class="brand"><span class="brand-mark">Lavish</span><span class="brand-support">Editor</span></div><div class="spacer" aria-hidden="true"></div><button class="annotate-switch" id="annotation" type="button" aria-pressed="true" title="${escapeHtml(modeToggleHint)}"><span class="switch-track" aria-hidden="true"><span class="switch-knob"></span></span><span>Annotate</span></button><div class="more-wrap" id="moreWrap"><button class="more-button" id="moreButton" type="button" title="More" aria-haspopup="menu" aria-expanded="false">${chromeIcons.more}</button><div class="menu more-menu" id="moreMenu" hidden><div class="menu-head"><div class="menu-label">Editing</div><button class="menu-file" id="copyPath" type="button" title="Copy path \xB7 ${escapeHtml(session.file)}">${chromeIcons.file}<span class="menu-file-text"><span class="path-head">${escapeHtml(pathHead)}</span><span class="path-tail">${escapeHtml(pathTail)}</span></span><span class="copy-hint" id="copyHint"><span class="icon-copy">${chromeIcons.copy}</span><span class="icon-check">${chromeIcons.check}</span><span id="copyHintText">Copy</span></span></button></div><div class="menu-rule"></div><button class="menu-item" id="reloadArtifact" type="button">${chromeIcons.refresh}<span>Reload artifact</span></button><button class="menu-item" id="copySnapshot" type="button">${chromeIcons.camera}<span>Copy DOM snapshot</span></button><button class="menu-item" id="exportArtifact" type="button">${chromeIcons.download}<span>Export standalone HTML</span></button><button class="menu-item" id="shareArtifact" type="button">${chromeIcons.globe}<span>Publish link</span></button><div class="menu-rule"></div><button class="menu-item danger" id="end" type="button">${chromeIcons.exit}<span>End session</span></button></div></div></div>
|
|
6167
|
-
<div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-downloads" data-artifact-src="/artifact/${session.key}/index.html"></iframe><div class="layout-issue-banner" id="layoutIssueBanner" hidden>This surface may have layout issues. Your agent has been notified.</div></div><aside class="panel"><h2>Conversation</h2><div class="chat" id="chatLog"></div><div class="composer"><div class="presence-banner" id="presenceBanner" hidden>Your agent is not listening. If this persists, ask your agent to poll for updates from Lavish.</div><
|
|
6203
|
+
<div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-downloads" data-artifact-src="/artifact/${session.key}/index.html"></iframe><div class="layout-issue-banner" id="layoutIssueBanner" hidden>This surface may have layout issues. Your agent has been notified.</div></div><aside class="panel"><h2>Conversation</h2><div class="panel-scroll" id="panelScroll"><div class="chat" id="chatLog"></div><div class="annotation-pills" id="annotationPills"></div></div><div class="composer"><div class="presence-banner" id="presenceBanner" hidden>Your agent is not listening. If this persists, ask your agent to poll for updates from Lavish.</div><textarea id="chatInput" placeholder="Write a message for the agent..."></textarea><div class="actions" id="sendActions"><span class="send-hint" id="sendHint" hidden>Write a message or annotate an element first.</span><div class="split"><button class="button send-main" id="send">Send to Agent</button><button class="button send-caret" id="sendCaret" type="button" title="Send options" aria-haspopup="menu" aria-expanded="false">${chromeIcons.caret}</button></div><div class="menu send-menu" id="sendMenu" hidden><button class="menu-item" id="sendFromMenu" type="button">${chromeIcons.send}<span>Send to Agent</span></button><button class="menu-item danger" id="sendAndEnd" type="button">${chromeIcons.exit}<span>Send & end session</span></button></div></div></div></aside></div>
|
|
6168
6204
|
<div class="share-overlay" id="shareDialog" role="dialog" aria-modal="true" aria-labelledby="shareTitleText" hidden><form class="share-card" id="shareForm"><div class="share-head"><div><div class="share-kicker">Publish to <a class="share-link" href="https://ht-ml.app" target="_blank" rel="noopener noreferrer">ht-ml.app</a></div><h2 id="shareTitleText">Publish artifact</h2></div><button class="share-close" id="shareClose" type="button" aria-label="Close publish dialog"><svg width="14" height="14" viewBox="0 0 10 10" fill="none" aria-hidden="true" focusable="false"><path d="M1 1L9 9M9 1L1 9" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></button></div><p class="share-note">ht-ml.app is a separate, third-party hosting service, not part of Lavish. Publishing sends this artifact to its servers.</p><p class="share-copy">This uploads this artifact to ht-ml.app with local assets inlined. Without a password, the page is PUBLIC and anyone with the link can open it. With a password, the page is PRIVATE and viewers must supply the password to view.</p><p class="share-note">Do not publish secrets. The Lavish annotation SDK is not included.</p><div class="share-grid"><label>Password (optional)<input id="sharePassword" name="password" type="password" autocomplete="new-password" placeholder="Leave blank for a public page"></label></div><div class="share-status" id="shareStatus" role="status"></div><div class="share-result" id="shareResult" hidden><label>Share URL<div class="share-copy-row"><input id="shareUrl" readonly><button class="share-copy-btn" id="copyShareUrl" type="button">Copy URL</button></div></label><label>Update key (secret)<div class="share-copy-row"><input id="shareUpdateKey" readonly><button class="share-copy-btn" id="copyUpdateKey" type="button">Copy key</button></div></label><p class="share-note">Keep the update key private. ht-ml.app returns it once and it is the only way to update or delete this page later.</p></div><div class="share-actions"><button class="share-cancel" id="shareCancel" type="button">Cancel</button><button class="button" id="sharePublish" type="submit">Publish</button></div></form></div>
|
|
6169
6205
|
<div class="ended-overlay layout-gate-overlay" id="layoutGateOverlay"${layoutGateHidden}><div class="ended-card"><div class="ended-title" id="layoutGateTitle">Checking layout.<br>One moment.</div><p class="ended-copy" id="layoutGateCopy">Lavish is waiting for fonts and final geometry before revealing this artifact.</p><button class="button ended-action" id="layoutGateAction" type="button">Show anyway</button></div></div>
|
|
6170
6206
|
<div class="ended-overlay" id="endedOverlay" hidden><div class="ended-card"><div class="ended-title">Session ended.<br>Return to your agent to continue.</div><p class="ended-copy">${escapeHtml(session.file)}</p></div></div>
|
|
@@ -6376,7 +6412,7 @@ function normalizePagePath(path6) {
|
|
|
6376
6412
|
var COMMANDS = /* @__PURE__ */ new Set(["open", "poll", "end", "stop", "server", "playbook", "design", "setup", "export", "share"]);
|
|
6377
6413
|
var RESERVED = new Set(RESERVED_COMMANDS);
|
|
6378
6414
|
var DESCRIPTION = "Lavish Editor helps agents turn rich HTML artifacts into collaborative human review surfaces. Whenever you are about to give user a complex response that will be easier to understand via a rich / interactive page, consider using Lavish Editor. First generate an interactive HTML artifact according to user request, then run `lavish-axi <html-file>` so the user can visually review it, annotate elements or selected text, queue prompts, and send feedback back through `lavish-axi poll`.";
|
|
6379
|
-
var VERSION = "0.1.
|
|
6415
|
+
var VERSION = "0.1.37";
|
|
6380
6416
|
async function run(argv) {
|
|
6381
6417
|
await ensureStateDir();
|
|
6382
6418
|
const normalizedArgv = normalizeArgv(argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lavish-axi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"packageManager": "pnpm@11.1.1",
|
|
5
5
|
"description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
|
|
6
6
|
"type": "module",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"format": "prettier --write .",
|
|
24
24
|
"format:check": "prettier --check .",
|
|
25
25
|
"prepack": "npm run build",
|
|
26
|
+
"prepare": "npm run build",
|
|
26
27
|
"test": "node --test",
|
|
27
28
|
"typecheck": "tsc --noEmit"
|
|
28
29
|
},
|