@david-xpn/llm-ui-feedback 0.1.13 → 0.1.15

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 CHANGED
@@ -52,7 +52,7 @@ function getContrastColor(hexColor) {
52
52
 
53
53
  // src/components/FloatingButton.tsx
54
54
  var import_jsx_runtime = require("react/jsx-runtime");
55
- function FloatingButton({ onPickClick, onPanelToggle, draftCount, panelOpen, position, buttonColor }) {
55
+ function FloatingButton({ onPickClick, onPanelToggle, draftCount, panelOpen, position, buttonColor, authenticated }) {
56
56
  const isBottom = position.includes("bottom");
57
57
  const isRight = position.includes("right");
58
58
  const anchor = {
@@ -66,7 +66,7 @@ function FloatingButton({ onPickClick, onPanelToggle, draftCount, panelOpen, pos
66
66
  gap: 8
67
67
  };
68
68
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: anchor, children: [
69
- draftCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
69
+ authenticated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
70
70
  "button",
71
71
  {
72
72
  onClick: onPanelToggle,
@@ -96,7 +96,7 @@ function FloatingButton({ onPickClick, onPanelToggle, draftCount, panelOpen, pos
96
96
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "13", width: "2", height: "2", rx: "0.5", fill: "currentColor" }),
97
97
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "13", width: "10", height: "2", rx: "0.5", fill: "currentColor" })
98
98
  ] }),
99
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
99
+ draftCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
100
100
  "span",
101
101
  {
102
102
  style: {
@@ -549,7 +549,7 @@ function SidePanel({
549
549
  "Feedback Session",
550
550
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { fontSize: 11, fontWeight: 400, color: "#9ca3af", marginLeft: 6 }, children: [
551
551
  "v",
552
- "0.1.13"
552
+ "0.1.15"
553
553
  ] })
554
554
  ] }),
555
555
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -971,11 +971,59 @@ Comment: ${comment.trim()}`] : []
971
971
  // src/components/SubmitModal.tsx
972
972
  var import_react5 = require("react");
973
973
  var import_jsx_runtime6 = require("react/jsx-runtime");
974
- function SubmitModal({ count, onSubmit, onCancel, onDone, submitting, submittedUrl }) {
974
+ function SubmitModal({ count, onSubmit, onCancel, onDone, submitting, submittedUrl, api }) {
975
975
  const [title, setTitle] = (0, import_react5.useState)("");
976
976
  const [copied, setCopied] = (0, import_react5.useState)(false);
977
+ const [linearConfigured, setLinearConfigured] = (0, import_react5.useState)(null);
978
+ const [linearLoading, setLinearLoading] = (0, import_react5.useState)(true);
979
+ const [createTicket, setCreateTicket] = (0, import_react5.useState)(true);
980
+ const [ticketTitle, setTicketTitle] = (0, import_react5.useState)("");
981
+ const [ticketTitleEdited, setTicketTitleEdited] = (0, import_react5.useState)(false);
982
+ const [projects, setProjects] = (0, import_react5.useState)([]);
983
+ const [labels, setLabels] = (0, import_react5.useState)([]);
984
+ const [selectedProjectId, setSelectedProjectId] = (0, import_react5.useState)("");
985
+ const [selectedLabelIds, setSelectedLabelIds] = (0, import_react5.useState)([]);
986
+ const [linearError, setLinearError] = (0, import_react5.useState)(null);
987
+ (0, import_react5.useEffect)(() => {
988
+ let cancelled = false;
989
+ setLinearLoading(true);
990
+ api.getLinearStatus().then(({ configured }) => {
991
+ if (cancelled) return;
992
+ setLinearConfigured(configured);
993
+ if (!configured) {
994
+ setLinearLoading(false);
995
+ return;
996
+ }
997
+ return Promise.all([api.fetchLinearProjects(), api.fetchLinearLabels()]).then(([p, l]) => {
998
+ if (cancelled) return;
999
+ setProjects(p);
1000
+ setLabels(l);
1001
+ if (p.length > 0) setSelectedProjectId(p[0].id);
1002
+ setLinearLoading(false);
1003
+ });
1004
+ }).catch((err) => {
1005
+ if (!cancelled) {
1006
+ setLinearConfigured(false);
1007
+ setLinearError(err.message);
1008
+ setLinearLoading(false);
1009
+ }
1010
+ });
1011
+ return () => {
1012
+ cancelled = true;
1013
+ };
1014
+ }, [api]);
1015
+ (0, import_react5.useEffect)(() => {
1016
+ if (!ticketTitleEdited) setTicketTitle(title);
1017
+ }, [title, ticketTitleEdited]);
977
1018
  const handleSubmit = () => {
978
- if (title.trim() && !submitting) {
1019
+ if (!title.trim() || submitting) return;
1020
+ if (createTicket && linearConfigured && ticketTitle.trim()) {
1021
+ onSubmit(title.trim(), {
1022
+ title: ticketTitle.trim(),
1023
+ ...selectedProjectId ? { projectId: selectedProjectId } : {},
1024
+ ...selectedLabelIds.length ? { labelIds: selectedLabelIds } : {}
1025
+ });
1026
+ } else {
979
1027
  onSubmit(title.trim());
980
1028
  }
981
1029
  };
@@ -988,6 +1036,18 @@ function SubmitModal({ count, onSubmit, onCancel, onDone, submitting, submittedU
988
1036
  } catch {
989
1037
  }
990
1038
  };
1039
+ const toggleLabel = (id) => {
1040
+ setSelectedLabelIds((prev) => prev.includes(id) ? prev.filter((l) => l !== id) : [...prev, id]);
1041
+ };
1042
+ const inputStyle = {
1043
+ width: "100%",
1044
+ padding: 10,
1045
+ borderRadius: 8,
1046
+ border: "1px solid #d1d5db",
1047
+ fontSize: 14,
1048
+ boxSizing: "border-box",
1049
+ background: "#fff"
1050
+ };
991
1051
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
992
1052
  "div",
993
1053
  {
@@ -1014,8 +1074,10 @@ function SubmitModal({ count, onSubmit, onCancel, onDone, submitting, submittedU
1014
1074
  background: "#fff",
1015
1075
  borderRadius: 12,
1016
1076
  padding: 24,
1017
- width: 400,
1018
- maxWidth: "90vw",
1077
+ width: 460,
1078
+ maxWidth: "92vw",
1079
+ maxHeight: "90vh",
1080
+ overflowY: "auto",
1019
1081
  boxShadow: "0 8px 32px rgba(0,0,0,0.2)"
1020
1082
  },
1021
1083
  children: submittedUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
@@ -1077,7 +1139,7 @@ function SubmitModal({ count, onSubmit, onCancel, onDone, submitting, submittedU
1077
1139
  ) })
1078
1140
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1079
1141
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { style: { margin: "0 0 16px", fontSize: 16 }, children: "Submit Feedback" }),
1080
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { style: { margin: "0 0 12px", fontSize: 14, color: "#6b7280" }, children: [
1142
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { style: { margin: "0 0 8px", fontSize: 13, color: "#6b7280" }, children: [
1081
1143
  count,
1082
1144
  " item",
1083
1145
  count !== 1 ? "s" : "",
@@ -1091,20 +1153,138 @@ function SubmitModal({ count, onSubmit, onCancel, onDone, submitting, submittedU
1091
1153
  onChange: (e) => setTitle(e.target.value),
1092
1154
  placeholder: "e.g. Homepage redesign feedback",
1093
1155
  autoFocus: true,
1094
- onKeyDown: (e) => {
1095
- if (e.key === "Enter") handleSubmit();
1156
+ style: inputStyle
1157
+ }
1158
+ ),
1159
+ linearLoading && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1160
+ "div",
1161
+ {
1162
+ style: {
1163
+ marginTop: 16,
1164
+ padding: 12,
1165
+ borderRadius: 8,
1166
+ border: "1px solid #e5e7eb",
1167
+ background: "#f9fafb",
1168
+ display: "flex",
1169
+ alignItems: "center",
1170
+ gap: 10,
1171
+ fontSize: 13,
1172
+ color: "#6b7280"
1096
1173
  },
1174
+ children: [
1175
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `@keyframes llmuif-spin { to { transform: rotate(360deg); } }` }),
1176
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1177
+ "span",
1178
+ {
1179
+ style: {
1180
+ display: "inline-block",
1181
+ width: 14,
1182
+ height: 14,
1183
+ border: "2px solid #d1d5db",
1184
+ borderTopColor: "#3b82f6",
1185
+ borderRadius: "50%",
1186
+ animation: "llmuif-spin 0.8s linear infinite"
1187
+ }
1188
+ }
1189
+ ),
1190
+ "Loading Linear projects and labels\u2026"
1191
+ ]
1192
+ }
1193
+ ),
1194
+ !linearLoading && linearConfigured && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1195
+ "div",
1196
+ {
1097
1197
  style: {
1098
- width: "100%",
1099
- padding: 10,
1198
+ marginTop: 16,
1199
+ padding: 12,
1100
1200
  borderRadius: 8,
1101
- border: "1px solid #d1d5db",
1102
- fontSize: 14,
1103
- boxSizing: "border-box"
1104
- }
1201
+ border: "1px solid #e5e7eb",
1202
+ background: "#f9fafb"
1203
+ },
1204
+ children: [
1205
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 13, fontWeight: 500, color: "#111827" }, children: [
1206
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1207
+ "input",
1208
+ {
1209
+ type: "checkbox",
1210
+ checked: createTicket,
1211
+ onChange: (e) => setCreateTicket(e.target.checked)
1212
+ }
1213
+ ),
1214
+ "Also create a Linear ticket"
1215
+ ] }),
1216
+ createTicket && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginTop: 12, display: "flex", flexDirection: "column", gap: 10 }, children: [
1217
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1218
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { style: { display: "block", fontSize: 12, color: "#6b7280", marginBottom: 4 }, children: "Ticket title" }),
1219
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1220
+ "input",
1221
+ {
1222
+ type: "text",
1223
+ value: ticketTitle,
1224
+ onChange: (e) => {
1225
+ setTicketTitleEdited(true);
1226
+ setTicketTitle(e.target.value);
1227
+ },
1228
+ placeholder: "Linear ticket title",
1229
+ style: inputStyle
1230
+ }
1231
+ )
1232
+ ] }),
1233
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1234
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { style: { display: "block", fontSize: 12, color: "#6b7280", marginBottom: 4 }, children: "Project" }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1236
+ "select",
1237
+ {
1238
+ value: selectedProjectId,
1239
+ onChange: (e) => setSelectedProjectId(e.target.value),
1240
+ style: { ...inputStyle, appearance: "auto" },
1241
+ children: [
1242
+ projects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "", children: "(no projects)" }),
1243
+ projects.map((p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("option", { value: p.id, children: [
1244
+ p.name,
1245
+ p.teams[0] ? ` \u2014 ${p.teams[0].key}` : ""
1246
+ ] }, p.id))
1247
+ ]
1248
+ }
1249
+ )
1250
+ ] }),
1251
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { style: { display: "block", fontSize: 12, color: "#6b7280", marginBottom: 4 }, children: "Labels" }),
1253
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: [
1254
+ labels.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "(no labels)" }),
1255
+ labels.map((l) => {
1256
+ const active = selectedLabelIds.includes(l.id);
1257
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1258
+ "button",
1259
+ {
1260
+ type: "button",
1261
+ onClick: () => toggleLabel(l.id),
1262
+ style: {
1263
+ padding: "4px 10px",
1264
+ borderRadius: 999,
1265
+ border: `1px solid ${active ? l.color : "#d1d5db"}`,
1266
+ background: active ? l.color : "#fff",
1267
+ color: active ? "#fff" : "#374151",
1268
+ fontSize: 12,
1269
+ cursor: "pointer"
1270
+ },
1271
+ children: l.name
1272
+ },
1273
+ l.id
1274
+ );
1275
+ })
1276
+ ] })
1277
+ ] })
1278
+ ] })
1279
+ ]
1105
1280
  }
1106
1281
  ),
1107
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 16, justifyContent: "flex-end" }, children: [
1282
+ !linearLoading && linearConfigured === false && !linearError && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { marginTop: 12, fontSize: 12, color: "#9ca3af" }, children: "Linear integration is not configured. Add a Linear token in the viewer Settings page to also create tickets from here." }),
1283
+ linearError && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { style: { marginTop: 12, fontSize: 12, color: "#b91c1c" }, children: [
1284
+ "Linear: ",
1285
+ linearError
1286
+ ] }),
1287
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 20, justifyContent: "flex-end" }, children: [
1108
1288
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1109
1289
  "button",
1110
1290
  {
@@ -1396,6 +1576,17 @@ function createApiClient(apiUrl, clientId) {
1396
1576
  body: JSON.stringify(data)
1397
1577
  });
1398
1578
  },
1579
+ async getLinearStatus() {
1580
+ return apiFetch("/linear/status");
1581
+ },
1582
+ async fetchLinearProjects() {
1583
+ const data = await apiFetch("/linear/projects");
1584
+ return data.items;
1585
+ },
1586
+ async fetchLinearLabels() {
1587
+ const data = await apiFetch("/linear/labels");
1588
+ return data.items;
1589
+ },
1399
1590
  async getUploadUrl(entryId, timestamp) {
1400
1591
  return apiFetch("/upload-url", {
1401
1592
  method: "POST",
@@ -1674,12 +1865,16 @@ function FeedbackWidget({
1674
1865
  }
1675
1866
  }, [api]);
1676
1867
  const submittedDraftIdsRef = (0, import_react7.useRef)([]);
1677
- const handleSubmit = (0, import_react7.useCallback)(async (title) => {
1868
+ const handleSubmit = (0, import_react7.useCallback)(async (title, linearTicket) => {
1678
1869
  dispatch({ type: "SET_SUBMITTING", submitting: true });
1679
1870
  const draftIds = Array.from(state.selectedDraftIds);
1680
1871
  submittedDraftIdsRef.current = draftIds;
1681
1872
  try {
1682
- const result = await api.submitSession({ title, draftIds });
1873
+ const result = await api.submitSession({
1874
+ title,
1875
+ draftIds,
1876
+ ...linearTicket ? { linearTicket } : {}
1877
+ });
1683
1878
  const group = result.feedbackGroup;
1684
1879
  const shareUrl = group.shareToken ? `${apiUrl}/feedback-groups/${group.id}/markdown?token=${group.shareToken}` : "";
1685
1880
  dispatch({ type: "SUBMIT_SUCCESS", shareUrl });
@@ -1702,7 +1897,8 @@ function FeedbackWidget({
1702
1897
  draftCount: state.drafts.length,
1703
1898
  panelOpen: state.panelOpen,
1704
1899
  position,
1705
- buttonColor
1900
+ buttonColor,
1901
+ authenticated: session.status === "authenticated"
1706
1902
  }
1707
1903
  ),
1708
1904
  state.picking && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PickOverlay, { onPick: handlePick, onCancel: handleCancelPicking }),
@@ -1740,7 +1936,8 @@ function FeedbackWidget({
1740
1936
  onCancel: () => dispatch({ type: "CLOSE_SUBMIT_MODAL" }),
1741
1937
  onDone: handleSubmitDone,
1742
1938
  submitting: state.submitting,
1743
- submittedUrl: state.submittedShareUrl
1939
+ submittedUrl: state.submittedShareUrl,
1940
+ api
1744
1941
  }
1745
1942
  )
1746
1943
  ] }),