@sphoro/bugcatch-web-sdk 0.14.3 → 0.14.4
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/dist/bugcatch.es.js +1232 -1187
- package/dist/bugcatch.es.js.map +1 -1
- package/dist/bugcatch.umd.js +94 -65
- package/dist/bugcatch.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/widget.js +75 -5
package/package.json
CHANGED
package/src/widget.js
CHANGED
|
@@ -623,6 +623,27 @@ button.primary .spinner { border-color: rgba(255,255,255,.4); border-top-color:
|
|
|
623
623
|
.att .file .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
624
624
|
.att .file .sz { flex: none; color: var(--bc-faint); font-size: 11px; }
|
|
625
625
|
|
|
626
|
+
/* The assistant's live card, above the conversation. */
|
|
627
|
+
.assistant { margin: 8px 12px 4px; padding: 10px 12px; border-radius: 12px; border: 1px solid var(--bc-border); background: var(--bc-subtle); }
|
|
628
|
+
.assistant.analyzing, .assistant.confirming, .assistant.needs_info { border-color: color-mix(in srgb, var(--bc-accent) 45%, transparent); }
|
|
629
|
+
.as-top { display: flex; gap: 10px; align-items: flex-start; }
|
|
630
|
+
.as-orb { flex: none; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-size: 12px; background: var(--bc-accent); }
|
|
631
|
+
.as-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid rgba(255,255,255,.4); border-top-color: #fff; animation: as-rot .8s linear infinite; }
|
|
632
|
+
@keyframes as-rot { to { transform: rotate(360deg); } }
|
|
633
|
+
.as-title { font-weight: 600; font-size: 14px; }
|
|
634
|
+
.assistant.analyzing .as-title::after { content: ''; animation: as-dots 1.4s steps(4) infinite; }
|
|
635
|
+
@keyframes as-dots { 0% { content: ''; } 25% { content: '.'; } 50% { content: '..'; } 75% { content: '...'; } }
|
|
636
|
+
.as-detail { font-size: 12.5px; color: var(--bc-muted); margin-top: 2px; }
|
|
637
|
+
.as-steps { list-style: none; display: flex; gap: 6px; margin: 10px 0 0; padding: 0; flex-wrap: wrap; font-size: 11.5px; color: var(--bc-muted); }
|
|
638
|
+
.as-steps li { display: flex; align-items: center; gap: 4px; }
|
|
639
|
+
.as-steps li span { width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid currentColor; }
|
|
640
|
+
.as-steps li.done { color: var(--bc-accent); }
|
|
641
|
+
.as-steps li.done span { background: var(--bc-accent); border-color: var(--bc-accent); }
|
|
642
|
+
.as-steps li.active { color: var(--bc-fg); font-weight: 600; }
|
|
643
|
+
.as-steps li.active span { border-color: var(--bc-accent); animation: as-pulse 1s ease-in-out infinite; }
|
|
644
|
+
@keyframes as-pulse { 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--bc-accent) 25%, transparent); } }
|
|
645
|
+
@media (prefers-reduced-motion: reduce) { .as-spin, .as-steps li.active span, .assistant.analyzing .as-title::after { animation: none; } }
|
|
646
|
+
|
|
626
647
|
/* Quick answers under the confirm question — only on the latest message. */
|
|
627
648
|
.msg-row .quick { display: none; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
|
|
628
649
|
.msg-row:last-child .quick { display: flex; }
|
|
@@ -2051,6 +2072,18 @@ function openPanel({ root, host, dock, client, onClose }) {
|
|
|
2051
2072
|
* sentence somebody started reading is the reason they open it again.
|
|
2052
2073
|
*/
|
|
2053
2074
|
function succeed(res, { callFailed = null } = {}) {
|
|
2075
|
+
// The assistant: straight into the report's conversation, where the
|
|
2076
|
+
// investigation shows itself working ("Analyzing the issue…") and asks
|
|
2077
|
+
// what it needs. The plain "Thanks" below stays for when that view cannot
|
|
2078
|
+
// load — a report filed without a way to follow it.
|
|
2079
|
+
if (res?.id && !callFailed) {
|
|
2080
|
+
showReport(res.id, { fresh: true }).then((ok) => { if (!ok) doneScreen(res, { callFailed }); });
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
doneScreen(res, { callFailed });
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function doneScreen(res, { callFailed = null } = {}) {
|
|
2054
2087
|
const ref = res?.id ? String(res.id).slice(0, 8) : null;
|
|
2055
2088
|
const eta = res?.estimate?.label ? String(res.estimate.label) : null;
|
|
2056
2089
|
// A press of Call that filed the report and then could not ring anybody.
|
|
@@ -2134,14 +2167,14 @@ function openPanel({ root, host, dock, client, onClose }) {
|
|
|
2134
2167
|
* bubbles either side of them; and there is a box at the bottom. Oldest at
|
|
2135
2168
|
* the top, because that is the direction a conversation is read.
|
|
2136
2169
|
*/
|
|
2137
|
-
async function showReport(bugId) {
|
|
2170
|
+
async function showReport(bugId, { fresh = false } = {}) {
|
|
2138
2171
|
const panel = scrim.querySelector('.panel');
|
|
2139
2172
|
panel.innerHTML = `
|
|
2140
2173
|
<div class="head">
|
|
2141
2174
|
<button class="icon-btn back" type="button" data-act="back" aria-label="Back to your reports">${ICONS.back}</button>
|
|
2142
2175
|
<div class="titles">
|
|
2143
|
-
<h2
|
|
2144
|
-
<p
|
|
2176
|
+
<h2>${fresh ? 'Looking into it' : 'Your report'}</h2>
|
|
2177
|
+
<p>${fresh ? 'Our assistant is investigating — it may ask you a question.' : 'Everything that has happened, and a way to answer.'}</p>
|
|
2145
2178
|
</div>
|
|
2146
2179
|
<button class="icon-btn" type="button" data-act="close" aria-label="Close">${ICONS.close}</button>
|
|
2147
2180
|
</div>
|
|
@@ -2159,11 +2192,12 @@ function openPanel({ root, host, dock, client, onClose }) {
|
|
|
2159
2192
|
|
|
2160
2193
|
const body = panel.querySelector('.history');
|
|
2161
2194
|
// The panel can be closed, or navigated away from, while this is in flight.
|
|
2162
|
-
if (!body || !panel.querySelector('[data-act="back"]')) return;
|
|
2195
|
+
if (!body || !panel.querySelector('[data-act="back"]')) return false;
|
|
2163
2196
|
|
|
2164
2197
|
if (!report) {
|
|
2198
|
+
if (fresh) return false;
|
|
2165
2199
|
body.innerHTML = '<div class="empty">Couldn\'t load that report just now.</div>';
|
|
2166
|
-
return;
|
|
2200
|
+
return false;
|
|
2167
2201
|
}
|
|
2168
2202
|
|
|
2169
2203
|
openReportId = bugId;
|
|
@@ -2192,6 +2226,41 @@ function openPanel({ root, host, dock, client, onClose }) {
|
|
|
2192
2226
|
scrollToLatest(thread);
|
|
2193
2227
|
mountVerification(body, report);
|
|
2194
2228
|
if (report.canReply) mountComposer(body, thread, bugId);
|
|
2229
|
+
mountAssistant(body, report, bugId);
|
|
2230
|
+
return true;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
/**
|
|
2234
|
+
* The assistant's live card: where the investigation is, and a gentle
|
|
2235
|
+
* refresh while it works. Analyzing polls every few seconds until the
|
|
2236
|
+
* outcome lands — a question, a found cause, or a hand-off — so the
|
|
2237
|
+
* reporter watches it happen rather than wondering whether anything is.
|
|
2238
|
+
*/
|
|
2239
|
+
let assistantTimer = null;
|
|
2240
|
+
function mountAssistant(body, report, bugId) {
|
|
2241
|
+
clearTimeout(assistantTimer);
|
|
2242
|
+
const a = report.assistant;
|
|
2243
|
+
body.querySelector('.assistant')?.remove();
|
|
2244
|
+
if (!a) return;
|
|
2245
|
+
const card = document.createElement('div');
|
|
2246
|
+
card.className = `assistant ${escapeHtml(a.phase)}`;
|
|
2247
|
+
card.setAttribute('role', 'status');
|
|
2248
|
+
card.setAttribute('aria-live', 'polite');
|
|
2249
|
+
card.innerHTML = `
|
|
2250
|
+
<div class="as-top">
|
|
2251
|
+
<span class="as-orb" aria-hidden="true">${a.phase === 'analyzing' ? '<span class="as-spin"></span>' : '✦'}</span>
|
|
2252
|
+
<div class="as-text">
|
|
2253
|
+
<div class="as-title">${escapeHtml(a.title)}</div>
|
|
2254
|
+
<div class="as-detail">${escapeHtml(a.detail)}</div>
|
|
2255
|
+
</div>
|
|
2256
|
+
</div>
|
|
2257
|
+
<ol class="as-steps">${(a.steps || []).map((st) => `<li class="${escapeHtml(st.state)}"><span></span>${escapeHtml(st.label)}</li>`).join('')}</ol>`;
|
|
2258
|
+
body.querySelector('.report-head')?.after(card);
|
|
2259
|
+
if (a.phase === 'analyzing') {
|
|
2260
|
+
assistantTimer = setTimeout(() => refreshThread(bugId), 3000);
|
|
2261
|
+
} else if (a.phase === 'needs_info') {
|
|
2262
|
+
body.querySelector('.composer textarea')?.focus();
|
|
2263
|
+
}
|
|
2195
2264
|
}
|
|
2196
2265
|
|
|
2197
2266
|
/**
|
|
@@ -2229,6 +2298,7 @@ function openPanel({ root, host, dock, client, onClose }) {
|
|
|
2229
2298
|
if (!report || openReportId !== bugId) return;
|
|
2230
2299
|
|
|
2231
2300
|
mountVerification(body, report);
|
|
2301
|
+
mountAssistant(body, report, bugId);
|
|
2232
2302
|
const pinned = atLatest(thread);
|
|
2233
2303
|
thread.innerHTML = threadHtml(report.timeline);
|
|
2234
2304
|
// Only follow the conversation down if they were already at the bottom.
|