@tmls-ai/support 0.1.1 → 0.1.3
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/index.js +53 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -75,6 +75,16 @@ var SupportApi = class {
|
|
|
75
75
|
const res = await fetch(`${this.apiUrl}/v1/conversations/${conversationId}/messages`, { headers: await this.auth() });
|
|
76
76
|
return res.ok ? res.json() : [];
|
|
77
77
|
}
|
|
78
|
+
/** Full thread: meta (type/severity/status) + messages + attachments. */
|
|
79
|
+
async getConversation(conversationId) {
|
|
80
|
+
const res = await fetch(`${this.apiUrl}/v1/conversations/${conversationId}`, { headers: await this.auth() });
|
|
81
|
+
return res.ok ? res.json() : null;
|
|
82
|
+
}
|
|
83
|
+
/** Fetch a screenshot the user owns; returns an object URL (auth header can't ride an <img src>). */
|
|
84
|
+
async getScreenshot(r2Key) {
|
|
85
|
+
const res = await fetch(`${this.apiUrl}/v1/screenshots/${r2Key}`, { headers: await this.auth() });
|
|
86
|
+
return res.ok ? URL.createObjectURL(await res.blob()) : null;
|
|
87
|
+
}
|
|
78
88
|
async reply(conversationId, body) {
|
|
79
89
|
await fetch(`${this.apiUrl}/v1/conversations/${conversationId}/messages`, {
|
|
80
90
|
method: "POST",
|
|
@@ -110,7 +120,9 @@ function SupportWidget(props) {
|
|
|
110
120
|
const [lastTicket, setLastTicket] = useState(null);
|
|
111
121
|
const [threads, setThreads] = useState([]);
|
|
112
122
|
const [activeId, setActiveId] = useState(null);
|
|
123
|
+
const [activeConvo, setActiveConvo] = useState(null);
|
|
113
124
|
const [messages, setMessages] = useState([]);
|
|
125
|
+
const [shotUrls, setShotUrls] = useState({});
|
|
114
126
|
const [reply, setReply] = useState("");
|
|
115
127
|
useEffect(() => {
|
|
116
128
|
installErrorCapture();
|
|
@@ -123,6 +135,9 @@ function SupportWidget(props) {
|
|
|
123
135
|
scale: 0.75,
|
|
124
136
|
width: root.clientWidth || window.innerWidth,
|
|
125
137
|
height: root.clientHeight || window.innerHeight,
|
|
138
|
+
// Keep scrolled containers (chat log, lists) at their on-screen scroll
|
|
139
|
+
// position instead of snapping back to the top in the clone.
|
|
140
|
+
features: { restoreScrollPosition: true },
|
|
126
141
|
filter: (node) => !(node instanceof HTMLElement && node.dataset.tmlsSupportRoot === "true")
|
|
127
142
|
});
|
|
128
143
|
setShotPreview(URL.createObjectURL(blob));
|
|
@@ -166,14 +181,28 @@ function SupportWidget(props) {
|
|
|
166
181
|
}, [api]);
|
|
167
182
|
const openThread = useCallback(async (id) => {
|
|
168
183
|
setActiveId(id);
|
|
169
|
-
|
|
184
|
+
setActiveConvo(null);
|
|
185
|
+
setMessages([]);
|
|
186
|
+
setShotUrls({});
|
|
170
187
|
setView("thread");
|
|
188
|
+
const detail = await api.getConversation(id);
|
|
189
|
+
if (!detail) return;
|
|
190
|
+
setActiveConvo(detail);
|
|
191
|
+
setMessages(detail.messages);
|
|
192
|
+
for (const att of detail.attachments) {
|
|
193
|
+
const url = await api.getScreenshot(att.r2_key);
|
|
194
|
+
if (url) setShotUrls((prev) => ({ ...prev, [att.r2_key]: url }));
|
|
195
|
+
}
|
|
171
196
|
}, [api]);
|
|
172
197
|
const sendReply = useCallback(async () => {
|
|
173
198
|
if (!reply.trim() || !activeId) return;
|
|
174
199
|
await api.reply(activeId, reply.trim());
|
|
175
200
|
setReply("");
|
|
176
|
-
|
|
201
|
+
const detail = await api.getConversation(activeId);
|
|
202
|
+
if (detail) {
|
|
203
|
+
setActiveConvo(detail);
|
|
204
|
+
setMessages(detail.messages);
|
|
205
|
+
}
|
|
177
206
|
}, [api, reply, activeId]);
|
|
178
207
|
const chip = (active) => ({
|
|
179
208
|
padding: "6px 12px",
|
|
@@ -260,6 +289,15 @@ function SupportWidget(props) {
|
|
|
260
289
|
] }, t.id))
|
|
261
290
|
] }),
|
|
262
291
|
view === "thread" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
292
|
+
activeConvo && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap", paddingBottom: 8, borderBottom: "0.5px solid rgba(255,255,255,0.08)" }, children: [
|
|
293
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 13, fontWeight: 600 }, children: [
|
|
294
|
+
"#",
|
|
295
|
+
activeConvo.ticket_number
|
|
296
|
+
] }),
|
|
297
|
+
/* @__PURE__ */ jsx("span", { style: metaPill("#c8c8d0"), children: activeConvo.type }),
|
|
298
|
+
/* @__PURE__ */ jsx("span", { style: metaPill(SEV_COLOR[activeConvo.severity]), children: activeConvo.severity }),
|
|
299
|
+
/* @__PURE__ */ jsx("span", { style: metaPill(activeConvo.status === "resolved" ? "#30d158" : accent), children: activeConvo.status })
|
|
300
|
+
] }),
|
|
263
301
|
messages.map((m) => /* @__PURE__ */ jsx("div", { style: {
|
|
264
302
|
alignSelf: m.author === "user" ? "flex-end" : "flex-start",
|
|
265
303
|
maxWidth: "85%",
|
|
@@ -269,6 +307,7 @@ function SupportWidget(props) {
|
|
|
269
307
|
borderRadius: 12,
|
|
270
308
|
fontSize: 13
|
|
271
309
|
}, children: m.body }, m.id)),
|
|
310
|
+
activeConvo?.attachments.map((att) => shotUrls[att.r2_key] ? /* @__PURE__ */ jsx("a", { href: shotUrls[att.r2_key], target: "_blank", rel: "noreferrer", style: { display: "block" }, children: /* @__PURE__ */ jsx("img", { src: shotUrls[att.r2_key], alt: "Attached screenshot", style: { width: "100%", borderRadius: 8, border: "0.5px solid #3a3a42" } }) }, att.id) : /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#8a8a92" }, children: "Loading screenshot\u2026" }, att.id)),
|
|
272
311
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
273
312
|
/* @__PURE__ */ jsx(
|
|
274
313
|
"input",
|
|
@@ -327,6 +366,18 @@ var primary = (accent) => ({
|
|
|
327
366
|
fontWeight: 600,
|
|
328
367
|
cursor: "pointer"
|
|
329
368
|
});
|
|
369
|
+
var SEV_COLOR = { blocking: "#ff453a", annoying: "#ff9f0a", minor: "#8e8e93" };
|
|
370
|
+
var metaPill = (color) => ({
|
|
371
|
+
padding: "1px 8px",
|
|
372
|
+
borderRadius: 100,
|
|
373
|
+
fontSize: 10.5,
|
|
374
|
+
fontWeight: 700,
|
|
375
|
+
textTransform: "uppercase",
|
|
376
|
+
letterSpacing: "0.03em",
|
|
377
|
+
color,
|
|
378
|
+
border: `1px solid ${color}55`,
|
|
379
|
+
background: "transparent"
|
|
380
|
+
});
|
|
330
381
|
|
|
331
382
|
// src/index.ts
|
|
332
383
|
function mountSupportWidget(props) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmls-ai/support",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Embeddable Timeless support widget — bottom-right report overlay (type / severity / screenshot) + ticket thread. Auto-captures context, talks to tmls-support-api.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|