@tmls-ai/support 0.1.1 → 0.1.2

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.
Files changed (2) hide show
  1. package/dist/index.js +50 -2
  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();
@@ -166,14 +178,28 @@ function SupportWidget(props) {
166
178
  }, [api]);
167
179
  const openThread = useCallback(async (id) => {
168
180
  setActiveId(id);
169
- setMessages(await api.listMessages(id));
181
+ setActiveConvo(null);
182
+ setMessages([]);
183
+ setShotUrls({});
170
184
  setView("thread");
185
+ const detail = await api.getConversation(id);
186
+ if (!detail) return;
187
+ setActiveConvo(detail);
188
+ setMessages(detail.messages);
189
+ for (const att of detail.attachments) {
190
+ const url = await api.getScreenshot(att.r2_key);
191
+ if (url) setShotUrls((prev) => ({ ...prev, [att.r2_key]: url }));
192
+ }
171
193
  }, [api]);
172
194
  const sendReply = useCallback(async () => {
173
195
  if (!reply.trim() || !activeId) return;
174
196
  await api.reply(activeId, reply.trim());
175
197
  setReply("");
176
- setMessages(await api.listMessages(activeId));
198
+ const detail = await api.getConversation(activeId);
199
+ if (detail) {
200
+ setActiveConvo(detail);
201
+ setMessages(detail.messages);
202
+ }
177
203
  }, [api, reply, activeId]);
178
204
  const chip = (active) => ({
179
205
  padding: "6px 12px",
@@ -260,6 +286,15 @@ function SupportWidget(props) {
260
286
  ] }, t.id))
261
287
  ] }),
262
288
  view === "thread" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
289
+ 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: [
290
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 13, fontWeight: 600 }, children: [
291
+ "#",
292
+ activeConvo.ticket_number
293
+ ] }),
294
+ /* @__PURE__ */ jsx("span", { style: metaPill("#c8c8d0"), children: activeConvo.type }),
295
+ /* @__PURE__ */ jsx("span", { style: metaPill(SEV_COLOR[activeConvo.severity]), children: activeConvo.severity }),
296
+ /* @__PURE__ */ jsx("span", { style: metaPill(activeConvo.status === "resolved" ? "#30d158" : accent), children: activeConvo.status })
297
+ ] }),
263
298
  messages.map((m) => /* @__PURE__ */ jsx("div", { style: {
264
299
  alignSelf: m.author === "user" ? "flex-end" : "flex-start",
265
300
  maxWidth: "85%",
@@ -269,6 +304,7 @@ function SupportWidget(props) {
269
304
  borderRadius: 12,
270
305
  fontSize: 13
271
306
  }, children: m.body }, m.id)),
307
+ 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
308
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
273
309
  /* @__PURE__ */ jsx(
274
310
  "input",
@@ -327,6 +363,18 @@ var primary = (accent) => ({
327
363
  fontWeight: 600,
328
364
  cursor: "pointer"
329
365
  });
366
+ var SEV_COLOR = { blocking: "#ff453a", annoying: "#ff9f0a", minor: "#8e8e93" };
367
+ var metaPill = (color) => ({
368
+ padding: "1px 8px",
369
+ borderRadius: 100,
370
+ fontSize: 10.5,
371
+ fontWeight: 700,
372
+ textTransform: "uppercase",
373
+ letterSpacing: "0.03em",
374
+ color,
375
+ border: `1px solid ${color}55`,
376
+ background: "transparent"
377
+ });
330
378
 
331
379
  // src/index.ts
332
380
  function mountSupportWidget(props) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmls-ai/support",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",