@popmelt.com/core 0.2.0 → 0.3.0

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.mjs CHANGED
@@ -71,13 +71,20 @@ async function checkBridgeHealth(bridgeUrl = DEFAULT_BRIDGE_URL) {
71
71
  return null;
72
72
  }
73
73
  }
74
- async function sendToBridge(screenshotBlob, feedbackJson, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model) {
74
+ async function sendToBridge(screenshotBlob, feedbackJson, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model, pastedImages) {
75
75
  const formData = new FormData();
76
76
  formData.append("screenshot", screenshotBlob, "screenshot.png");
77
77
  formData.append("feedback", feedbackJson);
78
78
  if (color) formData.append("color", color);
79
79
  if (provider) formData.append("provider", provider);
80
80
  if (model) formData.append("model", model);
81
+ if (pastedImages) {
82
+ for (const [annotationId, blobs] of pastedImages) {
83
+ for (let i = 0; i < blobs.length; i++) {
84
+ formData.append(`image-${annotationId}-${i}`, blobs[i], `image-${annotationId}-${i}.png`);
85
+ }
86
+ }
87
+ }
81
88
  const res = await fetch(`${bridgeUrl}/send`, {
82
89
  method: "POST",
83
90
  body: formData
@@ -153,12 +160,94 @@ async function sendPlanExecution(screenshotBlob, planId, tasks, bridgeUrl = DEFA
153
160
  }
154
161
  return res.json();
155
162
  }
156
- async function sendReplyToBridge(threadId, reply, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model) {
157
- const res = await fetch(`${bridgeUrl}/reply`, {
158
- method: "POST",
159
- headers: { "Content-Type": "application/json" },
160
- body: JSON.stringify({ threadId, reply, color, provider, model })
161
- });
163
+ async function sendReplyToBridge(threadId, reply, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model, images) {
164
+ let res;
165
+ if (images && images.length > 0) {
166
+ const formData = new FormData();
167
+ const placeholder = new Blob([new Uint8Array([
168
+ 137,
169
+ 80,
170
+ 78,
171
+ 71,
172
+ 13,
173
+ 10,
174
+ 26,
175
+ 10,
176
+ 0,
177
+ 0,
178
+ 0,
179
+ 13,
180
+ 73,
181
+ 72,
182
+ 68,
183
+ 82,
184
+ 0,
185
+ 0,
186
+ 0,
187
+ 1,
188
+ 0,
189
+ 0,
190
+ 0,
191
+ 1,
192
+ 8,
193
+ 6,
194
+ 0,
195
+ 0,
196
+ 0,
197
+ 31,
198
+ 21,
199
+ 196,
200
+ 137,
201
+ 0,
202
+ 0,
203
+ 0,
204
+ 10,
205
+ 73,
206
+ 68,
207
+ 65,
208
+ 84,
209
+ 120,
210
+ 156,
211
+ 98,
212
+ 0,
213
+ 0,
214
+ 0,
215
+ 2,
216
+ 0,
217
+ 1,
218
+ 229,
219
+ 39,
220
+ 222,
221
+ 252,
222
+ 0,
223
+ 0,
224
+ 0,
225
+ 0,
226
+ 73,
227
+ 69,
228
+ 78,
229
+ 68,
230
+ 174,
231
+ 66,
232
+ 96,
233
+ 130
234
+ ])], { type: "image/png" });
235
+ formData.append("screenshot", placeholder, "screenshot.png");
236
+ formData.append("feedback", JSON.stringify({ threadId, reply, color, provider, model }));
237
+ for (let i = 0; i < images.length; i++) {
238
+ formData.append(`image-reply-${i}`, images[i], `reply-image-${i}.png`);
239
+ }
240
+ res = await fetch(`${bridgeUrl}/reply`, {
241
+ method: "POST",
242
+ body: formData
243
+ });
244
+ } else {
245
+ res = await fetch(`${bridgeUrl}/reply`, {
246
+ method: "POST",
247
+ headers: { "Content-Type": "application/json" },
248
+ body: JSON.stringify({ threadId, reply, color, provider, model })
249
+ });
250
+ }
162
251
  if (!res.ok) {
163
252
  const body = await res.text();
164
253
  throw new Error(`Bridge returned ${res.status}: ${body}`);
@@ -526,8 +615,9 @@ function handleFinishPath(state, payload) {
526
615
  });
527
616
  }
528
617
  function handleAddText(state, payload) {
529
- const textAnnotation = {
530
- id: generateId(),
618
+ var _a;
619
+ const textAnnotation = __spreadValues({
620
+ id: (_a = payload.id) != null ? _a : generateId(),
531
621
  type: "text",
532
622
  points: [payload.point],
533
623
  text: payload.text,
@@ -540,7 +630,7 @@ function handleAddText(state, payload) {
540
630
  linkedSelector: payload.linkedSelector,
541
631
  linkedAnchor: payload.linkedAnchor,
542
632
  elements: payload.elements
543
- };
633
+ }, payload.imageCount ? { imageCount: payload.imageCount } : {});
544
634
  const baseState = payload.groupId ? state : pushToUndoStack(state);
545
635
  return __spreadProps(__spreadValues({}, baseState), {
546
636
  annotations: [...state.annotations, textAnnotation]
@@ -550,7 +640,7 @@ function handleUpdateText(state, payload) {
550
640
  const stateWithUndo = pushToUndoStack(state);
551
641
  return __spreadProps(__spreadValues({}, stateWithUndo), {
552
642
  annotations: state.annotations.map(
553
- (a) => a.id === payload.id ? __spreadProps(__spreadValues({}, a), { text: payload.text }) : a
643
+ (a) => a.id === payload.id ? __spreadValues(__spreadProps(__spreadValues({}, a), { text: payload.text }), payload.imageCount != null ? { imageCount: payload.imageCount } : {}) : a
554
644
  )
555
645
  });
556
646
  }
@@ -884,17 +974,25 @@ function handleSetAnnotationStatus(state, payload) {
884
974
  }
885
975
  function handleSetAnnotationThread(state, payload) {
886
976
  const idSet = new Set(payload.ids);
977
+ const groupIds = /* @__PURE__ */ new Set();
978
+ for (const a of state.annotations) {
979
+ if (idSet.has(a.id) && a.groupId) groupIds.add(a.groupId);
980
+ }
887
981
  return __spreadProps(__spreadValues({}, state), {
888
982
  annotations: state.annotations.map(
889
- (a) => idSet.has(a.id) ? __spreadProps(__spreadValues({}, a), { threadId: payload.threadId }) : a
983
+ (a) => idSet.has(a.id) || a.groupId && groupIds.has(a.groupId) ? __spreadProps(__spreadValues({}, a), { threadId: payload.threadId }) : a
890
984
  )
891
985
  });
892
986
  }
893
987
  function handleSetAnnotationQuestion(state, payload) {
894
988
  const idSet = new Set(payload.ids);
989
+ const groupIds = /* @__PURE__ */ new Set();
990
+ for (const a of state.annotations) {
991
+ if (idSet.has(a.id) && a.groupId) groupIds.add(a.groupId);
992
+ }
895
993
  return __spreadProps(__spreadValues({}, state), {
896
994
  annotations: state.annotations.map(
897
- (a) => idSet.has(a.id) ? __spreadProps(__spreadValues({}, a), { status: "waiting_input", question: payload.question, threadId: payload.threadId }) : a
995
+ (a) => idSet.has(a.id) || a.groupId && groupIds.has(a.groupId) ? __spreadProps(__spreadValues({}, a), { status: "waiting_input", question: payload.question, threadId: payload.threadId }) : a
898
996
  )
899
997
  });
900
998
  }
@@ -1917,25 +2015,26 @@ function buildFeedbackData(annotations, styleModifications = [], inspectedElemen
1917
2015
  const text = group.find((a) => a.type === "text");
1918
2016
  if (shape) {
1919
2017
  const linkedSelector = shape.linkedSelector || (text == null ? void 0 : text.linkedSelector);
1920
- annotationDataList.push(__spreadProps(__spreadValues({
2018
+ const imageCount = (text == null ? void 0 : text.imageCount) || shape.imageCount;
2019
+ annotationDataList.push(__spreadValues(__spreadProps(__spreadValues({
1921
2020
  id: shape.id,
1922
2021
  type: shape.type,
1923
2022
  instruction: text == null ? void 0 : text.text
1924
2023
  }, linkedSelector ? { linkedSelector } : {}), {
1925
2024
  // Use stored elements (captured at creation time) or empty array
1926
2025
  elements: shape.elements || []
1927
- }));
2026
+ }), imageCount ? { imageCount } : {}));
1928
2027
  }
1929
2028
  }
1930
2029
  for (const annotation of standaloneAnnotations) {
1931
- annotationDataList.push(__spreadProps(__spreadValues({
2030
+ annotationDataList.push(__spreadValues(__spreadProps(__spreadValues({
1932
2031
  id: annotation.id,
1933
2032
  type: annotation.type,
1934
2033
  instruction: annotation.type === "text" ? annotation.text : void 0
1935
2034
  }, annotation.linkedSelector ? { linkedSelector: annotation.linkedSelector } : {}), {
1936
2035
  // Use stored elements (captured at creation time) or empty array
1937
2036
  elements: annotation.elements || []
1938
- }));
2037
+ }), annotation.imageCount ? { imageCount: annotation.imageCount } : {}));
1939
2038
  }
1940
2039
  return __spreadValues({
1941
2040
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2076,7 +2175,8 @@ function drawAnnotationsToCanvas(ctx, annotations, scrollY, dpr) {
2076
2175
  const fontSize = annotation.fontSize || 16;
2077
2176
  ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
2078
2177
  ctx.fillStyle = annotation.color;
2079
- const lines = annotation.text.split("\n");
2178
+ const displayText = annotation.imageCount && annotation.imageCount > 0 ? `[${annotation.imageCount} image${annotation.imageCount > 1 ? "s" : ""}] ${annotation.text}` : annotation.text;
2179
+ const lines = displayText.split("\n");
2080
2180
  const lineHeight = fontSize * 1.2;
2081
2181
  const padding = 4;
2082
2182
  let maxWidth = 0;
@@ -7025,7 +7125,7 @@ function calculateLinkedPosition(rect, anchor, stackOffset = 0) {
7025
7125
  const y = anchor === "top-left" ? rect.top + window.scrollY - BADGE_HEIGHT - stackOffset * BADGE_HEIGHT + PADDING : rect.bottom + window.scrollY + PADDING - 1 + stackOffset * BADGE_HEIGHT;
7026
7126
  return { x, y };
7027
7127
  }
7028
- function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds, inFlightStyleSelectors, inFlightSelectorColors, onReply, onViewThread, isThreadPanelOpen, activePlan }) {
7128
+ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds, inFlightStyleSelectors, inFlightSelectorColors, onAttachImages, onReply, onViewThread, activePlan }) {
7029
7129
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
7030
7130
  const { canvasRef, redrawAll, resizeCanvas } = useCanvasDrawing();
7031
7131
  const [isDrawing, setIsDrawing] = useState12(false);
@@ -7722,26 +7822,39 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
7722
7822
  return () => window.removeEventListener("mousemove", handleMouseMove);
7723
7823
  }, [activeText, findTextAtPoint]);
7724
7824
  const commitActiveText = useCallback5(() => {
7825
+ var _a2;
7725
7826
  if (!activeText) return;
7726
- if (activeText.text.trim()) {
7827
+ const imageCount = ((_a2 = activeText.images) == null ? void 0 : _a2.length) || 0;
7828
+ if (activeText.text.trim() || imageCount > 0) {
7727
7829
  if (activeText.isNew) {
7830
+ const annotationId = generateId();
7728
7831
  dispatch({
7729
7832
  type: "ADD_TEXT",
7730
- payload: {
7833
+ payload: __spreadValues({
7731
7834
  point: activeText.point,
7732
- text: activeText.text,
7835
+ text: activeText.text || (imageCount > 0 ? `[${imageCount} image${imageCount > 1 ? "s" : ""}]` : ""),
7733
7836
  fontSize: activeText.fontSize,
7837
+ id: annotationId,
7734
7838
  groupId: activeText.groupId,
7735
7839
  linkedSelector: activeText.linkedSelector,
7736
7840
  linkedAnchor: activeText.linkedAnchor,
7737
7841
  elements: activeText.elements
7738
- }
7842
+ }, imageCount > 0 ? { imageCount } : {})
7739
7843
  });
7844
+ if (imageCount > 0 && activeText.images && onAttachImages) {
7845
+ onAttachImages(annotationId, activeText.images);
7846
+ }
7740
7847
  } else {
7741
7848
  dispatch({
7742
7849
  type: "UPDATE_TEXT",
7743
- payload: { id: activeText.id, text: activeText.text }
7850
+ payload: __spreadValues({
7851
+ id: activeText.id,
7852
+ text: activeText.text || (imageCount > 0 ? `[${imageCount} image${imageCount > 1 ? "s" : ""}]` : "")
7853
+ }, imageCount > 0 ? { imageCount } : {})
7744
7854
  });
7855
+ if (imageCount > 0 && activeText.images && onAttachImages) {
7856
+ onAttachImages(activeText.id, activeText.images);
7857
+ }
7745
7858
  }
7746
7859
  } else if (!activeText.isNew) {
7747
7860
  dispatch({
@@ -7750,7 +7863,7 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
7750
7863
  });
7751
7864
  }
7752
7865
  setActiveText(null);
7753
- }, [activeText, dispatch]);
7866
+ }, [activeText, dispatch, onAttachImages]);
7754
7867
  const snapPadding = useCallback5((value) => {
7755
7868
  for (let i = 0; i < PADDING_SNAP_STEPS2.length - 1; i++) {
7756
7869
  const lo = PADDING_SNAP_STEPS2[i];
@@ -8841,6 +8954,24 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8841
8954
  commitActiveText();
8842
8955
  }
8843
8956
  }, [commitActiveText]);
8957
+ const handleTextPaste = useCallback5((e) => {
8958
+ if (!activeText) return;
8959
+ const items = e.clipboardData.items;
8960
+ const imageBlobs = [];
8961
+ for (let i = 0; i < items.length; i++) {
8962
+ const item = items[i];
8963
+ if (item.type.startsWith("image/")) {
8964
+ const file = item.getAsFile();
8965
+ if (file) imageBlobs.push(file);
8966
+ }
8967
+ }
8968
+ if (imageBlobs.length > 0) {
8969
+ e.preventDefault();
8970
+ setActiveText((prev) => prev ? __spreadProps(__spreadValues({}, prev), {
8971
+ images: [...prev.images || [], ...imageBlobs]
8972
+ }) : null);
8973
+ }
8974
+ }, [activeText]);
8844
8975
  const handleContextMenu = useCallback5((e) => {
8845
8976
  if (state.activeTool !== "hand" || !state.isAnnotating) return;
8846
8977
  e.preventDefault();
@@ -9030,10 +9161,36 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
9030
9161
  value: activeText.text,
9031
9162
  onChange: handleTextChange,
9032
9163
  onKeyDown: handleTextKeyDown,
9164
+ onPaste: handleTextPaste,
9033
9165
  onBlur: commitActiveText,
9034
9166
  placeholder: "Type here...",
9035
9167
  style: textInputStyle
9036
9168
  }
9169
+ ),
9170
+ activeText.images && activeText.images.length > 0 && /* @__PURE__ */ jsxs9(
9171
+ "div",
9172
+ {
9173
+ "data-devtools": true,
9174
+ style: {
9175
+ position: "fixed",
9176
+ left: activeText.point.x - PADDING - scroll.x,
9177
+ top: activeText.point.y - PADDING - scroll.y - 20,
9178
+ zIndex: 1e4,
9179
+ fontSize: 11,
9180
+ fontFamily: "system-ui, sans-serif",
9181
+ color: "#fff",
9182
+ backgroundColor: "rgba(0,0,0,0.7)",
9183
+ padding: "2px 6px",
9184
+ borderRadius: 3,
9185
+ whiteSpace: "nowrap"
9186
+ },
9187
+ children: [
9188
+ activeText.images.length,
9189
+ " image",
9190
+ activeText.images.length > 1 ? "s" : "",
9191
+ " attached"
9192
+ ]
9193
+ }
9037
9194
  )
9038
9195
  ] }),
9039
9196
  state.isAnnotating && state.activeTool !== "hand" && state.styleModifications.length > 0 && /* @__PURE__ */ jsx10(
@@ -9054,29 +9211,16 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
9054
9211
  accentColor: state.activeColor
9055
9212
  }
9056
9213
  ),
9057
- state.isAnnotating && inFlightAnnotationIds && inFlightAnnotationIds.size > 0 && /* @__PURE__ */ jsx10(
9058
- ThinkingSpinners,
9059
- {
9060
- annotations: state.annotations,
9061
- inFlightIds: inFlightAnnotationIds,
9062
- scrollX: scroll.x,
9063
- scrollY: scroll.y,
9064
- annotationGroupMap,
9065
- onViewThread,
9066
- onSelectAnnotation: selectAnnotation
9067
- }
9068
- ),
9069
9214
  state.isAnnotating && /* @__PURE__ */ jsx10(
9070
- ResolutionBadges,
9215
+ AnnotationBadges,
9071
9216
  {
9072
9217
  annotations: state.annotations,
9073
9218
  supersededAnnotations,
9219
+ inFlightIds: inFlightAnnotationIds,
9074
9220
  scrollX: scroll.x,
9075
9221
  scrollY: scroll.y,
9076
9222
  annotationGroupMap,
9077
- onReply,
9078
9223
  onViewThread,
9079
- isThreadPanelOpen,
9080
9224
  onSelectAnnotation: selectAnnotation
9081
9225
  }
9082
9226
  ),
@@ -9262,8 +9406,9 @@ var THINKING_WORDS2 = [
9262
9406
  "riffing"
9263
9407
  ];
9264
9408
  var WORD_INTERVAL2 = 3e3;
9265
- function ThinkingSpinners({
9409
+ function AnnotationBadges({
9266
9410
  annotations,
9411
+ supersededAnnotations,
9267
9412
  inFlightIds,
9268
9413
  scrollX,
9269
9414
  scrollY,
@@ -9271,10 +9416,12 @@ function ThinkingSpinners({
9271
9416
  onViewThread,
9272
9417
  onSelectAnnotation
9273
9418
  }) {
9274
- var _a;
9419
+ var _a, _b;
9275
9420
  const [charIndex, setCharIndex] = useState12(0);
9276
9421
  const [wordIndex, setWordIndex] = useState12(() => Math.floor(Math.random() * THINKING_WORDS2.length));
9422
+ const hasInFlight = !!(inFlightIds && inFlightIds.size > 0);
9277
9423
  useEffect15(() => {
9424
+ if (!hasInFlight) return;
9278
9425
  const charTimer = setInterval(() => {
9279
9426
  setCharIndex((i) => (i + 1) % SPINNER_FRAME_COUNT2);
9280
9427
  }, SPINNER_INTERVAL2);
@@ -9285,14 +9432,25 @@ function ThinkingSpinners({
9285
9432
  clearInterval(charTimer);
9286
9433
  clearInterval(wordTimer);
9287
9434
  };
9288
- }, []);
9289
- const spinnerPositions = [];
9435
+ }, [hasInFlight]);
9436
+ const badges = [];
9290
9437
  for (const annotation of annotations) {
9291
9438
  if (annotation.type !== "text" || !annotation.text || !annotation.points[0]) continue;
9292
- const isInFlight = inFlightIds.has(annotation.id) || annotation.groupId && annotations.some(
9293
- (a) => a.groupId === annotation.groupId && inFlightIds.has(a.id)
9439
+ if (supersededAnnotations.has(annotation)) continue;
9440
+ const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9441
+ const isInFlight = !!(inFlightIds && (inFlightIds.has(annotation.id) || groupAnns.some((a) => inFlightIds.has(a.id))));
9442
+ const status = (_a = annotation.status) != null ? _a : "pending";
9443
+ const groupMateResolved = groupAnns.some(
9444
+ (a) => a.status === "resolved" || a.status === "needs_review"
9294
9445
  );
9295
- if (!isInFlight) continue;
9446
+ const hasThread = groupAnns.some((a) => a.threadId);
9447
+ if (!isInFlight && status !== "resolved" && status !== "needs_review" && !groupMateResolved && !hasThread) continue;
9448
+ const threadId = annotation.threadId || ((_b = groupAnns.find((a) => a.threadId)) == null ? void 0 : _b.threadId);
9449
+ const isNeedsReview = status === "needs_review" || groupAnns.some((a) => a.status === "needs_review");
9450
+ const replyCount = groupAnns.reduce((n, a) => {
9451
+ var _a2;
9452
+ return n + ((_a2 = a.replyCount) != null ? _a2 : 0);
9453
+ }, 0) || 1;
9296
9454
  const point = annotation.points[0];
9297
9455
  const fontSize = annotation.fontSize || 12;
9298
9456
  const lineHeightPx = fontSize * LINE_HEIGHT;
@@ -9305,26 +9463,24 @@ function ThinkingSpinners({
9305
9463
  ctx.font = `${fontSize}px ${FONT_FAMILY}`;
9306
9464
  const maxWidth = Math.min(MAX_DISPLAY_WIDTH, Math.max(...displayLines.map((line) => ctx.measureText(line).width)));
9307
9465
  const totalHeight = displayLines.length * lineHeightPx;
9308
- const annotationHeight = totalHeight + PADDING * 2;
9309
- const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9310
- const threadId = annotation.threadId || ((_a = groupAnns.find((a) => a.threadId)) == null ? void 0 : _a.threadId);
9311
- spinnerPositions.push({
9466
+ badges.push({
9312
9467
  id: annotation.id,
9313
9468
  threadId,
9314
9469
  x: point.x + maxWidth + PADDING,
9315
- // right edge of text bg
9316
9470
  y: point.y - PADDING,
9317
- // top of text bg
9318
- size: annotationHeight,
9319
- color: annotation.color
9471
+ size: totalHeight + PADDING * 2,
9472
+ color: annotation.color,
9473
+ isInFlight,
9474
+ isNeedsReview,
9475
+ replyCount
9320
9476
  });
9321
9477
  }
9322
- if (spinnerPositions.length === 0) return null;
9478
+ if (badges.length === 0) return null;
9323
9479
  const clickable = !!onViewThread;
9324
- return /* @__PURE__ */ jsx10(Fragment6, { children: spinnerPositions.map((pos, i) => /* @__PURE__ */ jsxs9(
9480
+ return /* @__PURE__ */ jsx10(Fragment6, { children: badges.map((pos) => /* @__PURE__ */ jsx10(
9325
9481
  "div",
9326
9482
  {
9327
- "data-devtools": true,
9483
+ "data-devtools": "annotation-badge",
9328
9484
  onClick: clickable && pos.threadId ? () => {
9329
9485
  onSelectAnnotation == null ? void 0 : onSelectAnnotation(pos.id);
9330
9486
  onViewThread(pos.threadId);
@@ -9337,7 +9493,7 @@ function ThinkingSpinners({
9337
9493
  display: "flex",
9338
9494
  alignItems: "center",
9339
9495
  pointerEvents: clickable ? "auto" : "none",
9340
- cursor: clickable ? "pointer" : void 0,
9496
+ cursor: clickable && pos.threadId ? "pointer" : void 0,
9341
9497
  zIndex: 9999,
9342
9498
  backgroundColor: pos.color,
9343
9499
  fontFamily: FONT_FAMILY,
@@ -9348,7 +9504,7 @@ function ThinkingSpinners({
9348
9504
  gap: 4,
9349
9505
  whiteSpace: "nowrap"
9350
9506
  },
9351
- children: [
9507
+ children: pos.isInFlight ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
9352
9508
  /* @__PURE__ */ jsx10("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", style: { verticalAlign: "middle" }, children: charIndex === 1 ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
9353
9509
  /* @__PURE__ */ jsx10("circle", { cx: "7", cy: "7", r: "2" }),
9354
9510
  /* @__PURE__ */ jsx10("circle", { cx: "17", cy: "7", r: "2" }),
@@ -9361,287 +9517,23 @@ function ThinkingSpinners({
9361
9517
  /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "18", r: "2" })
9362
9518
  ] }) }),
9363
9519
  /* @__PURE__ */ jsx10("span", { style: { opacity: 0.7 }, children: THINKING_WORDS2[wordIndex] })
9364
- ]
9365
- },
9366
- i
9367
- )) });
9368
- }
9369
- function ResolutionBadges({
9370
- annotations,
9371
- supersededAnnotations,
9372
- scrollX,
9373
- scrollY,
9374
- annotationGroupMap,
9375
- onReply,
9376
- onViewThread,
9377
- isThreadPanelOpen,
9378
- onSelectAnnotation
9379
- }) {
9380
- var _a;
9381
- const badgePositions = [];
9382
- for (const annotation of annotations) {
9383
- if (annotation.type !== "text" || !annotation.text || !annotation.points[0]) continue;
9384
- if (supersededAnnotations.has(annotation)) continue;
9385
- const status = (_a = annotation.status) != null ? _a : "pending";
9386
- const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9387
- const groupMateResolved = groupAnns.some(
9388
- (a) => a.status === "resolved" || a.status === "needs_review"
9389
- );
9390
- const hasThread = groupAnns.some((a) => a.threadId);
9391
- if (status !== "resolved" && status !== "needs_review" && !groupMateResolved && !hasThread) continue;
9392
- const isNeedsReview = status === "needs_review" || groupAnns.some((a) => a.status === "needs_review");
9393
- const point = annotation.points[0];
9394
- const fontSize = annotation.fontSize || 12;
9395
- const lineHeightPx = fontSize * LINE_HEIGHT;
9396
- const lines = annotation.text.split("\n");
9397
- const groupNumber = annotationGroupMap.get(annotation.id);
9398
- const displayLines = groupNumber !== void 0 ? [groupNumber + ". " + (lines[0] || ""), ...lines.slice(1)] : lines;
9399
- const canvas = document.createElement("canvas");
9400
- const ctx = canvas.getContext("2d");
9401
- if (!ctx) continue;
9402
- ctx.font = `${fontSize}px ${FONT_FAMILY}`;
9403
- const maxWidth = Math.min(MAX_DISPLAY_WIDTH, Math.max(...displayLines.map((line) => ctx.measureText(line).width)));
9404
- const totalHeight = displayLines.length * lineHeightPx;
9405
- const annotationHeight = totalHeight + PADDING * 2;
9406
- badgePositions.push({
9407
- annotation,
9408
- x: point.x + maxWidth + PADDING,
9409
- y: point.y - PADDING,
9410
- size: annotationHeight,
9411
- isNeedsReview: !!isNeedsReview,
9412
- groupNumber
9413
- });
9414
- }
9415
- if (badgePositions.length === 0) return null;
9416
- return /* @__PURE__ */ jsx10(Fragment6, { children: badgePositions.map(({ annotation, x, y, size, isNeedsReview, groupNumber }) => /* @__PURE__ */ jsx10(
9417
- ResolutionBadge,
9418
- {
9419
- annotation,
9420
- annotations,
9421
- x: x - scrollX,
9422
- y: y - scrollY,
9423
- size,
9424
- isNeedsReview: !!isNeedsReview,
9425
- groupNumber,
9426
- onReply,
9427
- onViewThread,
9428
- isThreadPanelOpen,
9429
- onSelectAnnotation
9520
+ ] }) : /* @__PURE__ */ jsxs9(Fragment6, { children: [
9521
+ pos.isNeedsReview ? /* @__PURE__ */ jsx10("span", { style: { fontWeight: 700 }, children: "?" }) : /* @__PURE__ */ jsxs9("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
9522
+ /* @__PURE__ */ jsx10("line", { x1: "12", y1: "3", x2: "12", y2: "9" }),
9523
+ /* @__PURE__ */ jsx10("line", { x1: "12", y1: "15", x2: "12", y2: "21" }),
9524
+ /* @__PURE__ */ jsx10("line", { x1: "3", y1: "12", x2: "9", y2: "12" }),
9525
+ /* @__PURE__ */ jsx10("line", { x1: "15", y1: "12", x2: "21", y2: "12" })
9526
+ ] }),
9527
+ /* @__PURE__ */ jsxs9("span", { style: { opacity: 0.7 }, children: [
9528
+ pos.replyCount,
9529
+ " ",
9530
+ pos.replyCount === 1 ? "reply" : "replies"
9531
+ ] })
9532
+ ] })
9430
9533
  },
9431
- `resolution-${annotation.id}`
9534
+ pos.id
9432
9535
  )) });
9433
9536
  }
9434
- function ResolutionBadge({
9435
- annotation,
9436
- annotations,
9437
- x,
9438
- y,
9439
- size,
9440
- isNeedsReview,
9441
- groupNumber,
9442
- onReply,
9443
- onViewThread,
9444
- isThreadPanelOpen,
9445
- onSelectAnnotation
9446
- }) {
9447
- var _a, _b;
9448
- const [expanded, setExpanded] = useState12(false);
9449
- const [replyText, setReplyText] = useState12("");
9450
- const textareaRef = useRef5(null);
9451
- const panelRef = useRef5(null);
9452
- useEffect15(() => {
9453
- if (expanded && textareaRef.current) {
9454
- textareaRef.current.focus();
9455
- }
9456
- }, [expanded]);
9457
- useEffect15(() => {
9458
- if (!expanded) return;
9459
- const handleClickOutside = (e) => {
9460
- if (panelRef.current && !panelRef.current.contains(e.target)) {
9461
- setExpanded(false);
9462
- }
9463
- };
9464
- const handleEscape = (e) => {
9465
- if (e.key === "Escape") setExpanded(false);
9466
- };
9467
- document.addEventListener("mousedown", handleClickOutside);
9468
- document.addEventListener("keydown", handleEscape);
9469
- return () => {
9470
- document.removeEventListener("mousedown", handleClickOutside);
9471
- document.removeEventListener("keydown", handleEscape);
9472
- };
9473
- }, [expanded]);
9474
- const groupAnnotations = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9475
- const textAnn = groupAnnotations.find((a) => a.type === "text");
9476
- const userMessage = (textAnn == null ? void 0 : textAnn.text) || "(no text)";
9477
- const summary = (_a = groupAnnotations.find((a) => a.resolutionSummary)) == null ? void 0 : _a.resolutionSummary;
9478
- const threadId = (_b = groupAnnotations.find((a) => a.threadId)) == null ? void 0 : _b.threadId;
9479
- const handleSubmit = useCallback5(() => {
9480
- if (!replyText.trim() || !threadId || !onReply) return;
9481
- onReply(threadId, replyText.trim());
9482
- setReplyText("");
9483
- setExpanded(false);
9484
- }, [replyText, threadId, onReply]);
9485
- const handleKeyDown = useCallback5((e) => {
9486
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
9487
- e.preventDefault();
9488
- handleSubmit();
9489
- }
9490
- }, [handleSubmit]);
9491
- return /* @__PURE__ */ jsxs9(
9492
- "div",
9493
- {
9494
- ref: panelRef,
9495
- "data-devtools": "resolution-badge",
9496
- style: {
9497
- position: "fixed",
9498
- left: x,
9499
- top: y,
9500
- zIndex: expanded ? 10002 : 9999,
9501
- pointerEvents: "auto"
9502
- },
9503
- children: [
9504
- !expanded && /* @__PURE__ */ jsxs9(
9505
- "div",
9506
- {
9507
- onClick: () => {
9508
- onSelectAnnotation == null ? void 0 : onSelectAnnotation(annotation.id);
9509
- if (threadId && onViewThread) {
9510
- onViewThread(threadId);
9511
- }
9512
- },
9513
- style: {
9514
- height: size,
9515
- display: "flex",
9516
- alignItems: "center",
9517
- cursor: "pointer",
9518
- backgroundColor: annotation.color,
9519
- fontFamily: FONT_FAMILY,
9520
- fontSize: 12,
9521
- color: "#ffffff",
9522
- userSelect: "none",
9523
- padding: `0 ${PADDING}px`,
9524
- gap: 4,
9525
- whiteSpace: "nowrap"
9526
- },
9527
- children: [
9528
- isNeedsReview ? /* @__PURE__ */ jsx10("span", { style: { fontWeight: 700 }, children: "?" }) : /* @__PURE__ */ jsxs9("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
9529
- /* @__PURE__ */ jsx10("line", { x1: "12", y1: "3", x2: "12", y2: "9" }),
9530
- /* @__PURE__ */ jsx10("line", { x1: "12", y1: "15", x2: "12", y2: "21" }),
9531
- /* @__PURE__ */ jsx10("line", { x1: "3", y1: "12", x2: "9", y2: "12" }),
9532
- /* @__PURE__ */ jsx10("line", { x1: "15", y1: "12", x2: "21", y2: "12" })
9533
- ] }),
9534
- /* @__PURE__ */ jsx10("span", { style: { opacity: 0.7 }, children: (() => {
9535
- const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9536
- const count = groupAnns.reduce((n, a) => {
9537
- var _a2;
9538
- return n + ((_a2 = a.replyCount) != null ? _a2 : 0);
9539
- }, 0) || 1;
9540
- return `${count} ${count === 1 ? "reply" : "replies"}`;
9541
- })() })
9542
- ]
9543
- }
9544
- ),
9545
- expanded && /* @__PURE__ */ jsxs9(
9546
- "div",
9547
- {
9548
- style: {
9549
- minWidth: 280,
9550
- maxWidth: 400,
9551
- backgroundColor: "#ffffff",
9552
- fontFamily: FONT_FAMILY,
9553
- fontSize: 12,
9554
- color: "#1f2937",
9555
- border: "1px solid rgba(0, 0, 0, 0.1)"
9556
- },
9557
- children: [
9558
- /* @__PURE__ */ jsx10("div", { style: {
9559
- padding: `${PADDING + 2}px ${PADDING + 4}px`,
9560
- backgroundColor: annotation.color,
9561
- color: "#ffffff",
9562
- lineHeight: 1.4
9563
- }, children: userMessage }),
9564
- summary && /* @__PURE__ */ jsx10("div", { style: {
9565
- padding: `${PADDING + 2}px ${PADDING + 4}px`,
9566
- lineHeight: 1.4,
9567
- borderBottom: "1px solid rgba(0, 0, 0, 0.06)"
9568
- }, children: summary }),
9569
- threadId && /* @__PURE__ */ jsxs9("div", { style: { padding: PADDING }, children: [
9570
- onReply && /* @__PURE__ */ jsxs9(Fragment6, { children: [
9571
- /* @__PURE__ */ jsx10(
9572
- "textarea",
9573
- {
9574
- ref: textareaRef,
9575
- value: replyText,
9576
- onChange: (e) => setReplyText(e.target.value),
9577
- onKeyDown: handleKeyDown,
9578
- placeholder: "Reply...",
9579
- style: {
9580
- width: "100%",
9581
- minHeight: 40,
9582
- padding: PADDING,
9583
- fontSize: 12,
9584
- fontFamily: FONT_FAMILY,
9585
- backgroundColor: "rgba(0, 0, 0, 0.04)",
9586
- color: "#1f2937",
9587
- border: "1px solid rgba(0, 0, 0, 0.1)",
9588
- borderRadius: 0,
9589
- outline: "none",
9590
- resize: "vertical",
9591
- lineHeight: 1.4,
9592
- boxSizing: "border-box"
9593
- }
9594
- }
9595
- ),
9596
- /* @__PURE__ */ jsx10("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 4 }, children: /* @__PURE__ */ jsx10(
9597
- "button",
9598
- {
9599
- onClick: handleSubmit,
9600
- disabled: !replyText.trim(),
9601
- style: {
9602
- padding: "4px 12px",
9603
- fontSize: 11,
9604
- fontFamily: FONT_FAMILY,
9605
- fontWeight: 600,
9606
- backgroundColor: replyText.trim() ? annotation.color : "rgba(0,0,0,0.1)",
9607
- color: replyText.trim() ? "#ffffff" : "rgba(0,0,0,0.3)",
9608
- border: "none",
9609
- cursor: replyText.trim() ? "pointer" : "default"
9610
- },
9611
- children: "Send \u2318\u23CE"
9612
- }
9613
- ) })
9614
- ] }),
9615
- onViewThread && /* @__PURE__ */ jsx10(
9616
- "button",
9617
- {
9618
- onClick: () => {
9619
- onViewThread(threadId);
9620
- setExpanded(false);
9621
- },
9622
- style: {
9623
- width: "100%",
9624
- padding: "5px 0",
9625
- fontSize: 11,
9626
- fontFamily: FONT_FAMILY,
9627
- fontWeight: 500,
9628
- backgroundColor: "transparent",
9629
- color: "#6b7280",
9630
- border: "1px solid rgba(0, 0, 0, 0.1)",
9631
- cursor: "pointer",
9632
- marginTop: onReply ? 8 : 0
9633
- },
9634
- children: "View conversation \u2192"
9635
- }
9636
- )
9637
- ] })
9638
- ]
9639
- }
9640
- )
9641
- ]
9642
- }
9643
- );
9644
- }
9645
9537
  function PlanWaitingBadges({
9646
9538
  annotations,
9647
9539
  supersededAnnotations,
@@ -10952,7 +10844,9 @@ function AnnotationToolbar({
10952
10844
  "div",
10953
10845
  {
10954
10846
  id: "devtools-toolbar",
10955
- style: __spreadProps(__spreadValues({}, toolbarStyle), { overflow: "visible" }),
10847
+ style: __spreadProps(__spreadValues({}, toolbarStyle), { overflow: "visible", opacity: 0.5, transition: "opacity 150ms ease" }),
10848
+ onMouseEnter: (e) => e.currentTarget.style.opacity = "1",
10849
+ onMouseLeave: (e) => e.currentTarget.style.opacity = "0.5",
10956
10850
  children: /* @__PURE__ */ jsx12(
10957
10851
  "button",
10958
10852
  {
@@ -11837,13 +11731,10 @@ function ThinkingBadge({ color }) {
11837
11731
  /* @__PURE__ */ jsx15("span", { style: { fontSize: 11, color: "#9ca3af", fontStyle: "italic" }, children: THINKING_WORDS3[wordIndex] })
11838
11732
  ] });
11839
11733
  }
11840
- function formatToolStep(events) {
11734
+ function formatToolEvent(event) {
11841
11735
  var _a;
11842
- const toolEvents = events.filter((e) => e.type === "tool_use");
11843
- if (toolEvents.length === 0) return null;
11844
- const last = toolEvents[toolEvents.length - 1];
11845
- const tool = String(last.data.tool || "");
11846
- const file = last.data.file ? String(last.data.file) : null;
11736
+ const tool = String(event.data.tool || "");
11737
+ const file = event.data.file ? String(event.data.file) : null;
11847
11738
  const basename = file ? (_a = file.split("/").pop()) != null ? _a : file : null;
11848
11739
  switch (tool) {
11849
11740
  case "Read":
@@ -11866,6 +11757,34 @@ function formatToolStep(events) {
11866
11757
  return tool ? `Using ${tool}` : null;
11867
11758
  }
11868
11759
  }
11760
+ function buildStreamSegments(events) {
11761
+ const segments = [];
11762
+ for (const e of events) {
11763
+ if (e.type === "tool_use") {
11764
+ const label = formatToolEvent(e);
11765
+ if (label) segments.push({ kind: "tool", label });
11766
+ } else if (e.type === "delta") {
11767
+ const text = String(e.data.text || "");
11768
+ if (!text) continue;
11769
+ const last = segments[segments.length - 1];
11770
+ if (last && last.kind === "text") {
11771
+ last.text += text;
11772
+ } else {
11773
+ segments.push({ kind: "text", text });
11774
+ }
11775
+ } else if (e.type === "thinking") {
11776
+ const text = String(e.data.text || "");
11777
+ if (!text) continue;
11778
+ const last = segments[segments.length - 1];
11779
+ if (last && last.kind === "thinking") {
11780
+ last.text += text;
11781
+ } else {
11782
+ segments.push({ kind: "thinking", text });
11783
+ }
11784
+ }
11785
+ }
11786
+ return segments;
11787
+ }
11869
11788
  function ThreadPanel({
11870
11789
  threadId,
11871
11790
  bridgeUrl,
@@ -11887,6 +11806,7 @@ function ThreadPanel({
11887
11806
  const [messages, setMessages] = useState15([]);
11888
11807
  const [loading, setLoading] = useState15(true);
11889
11808
  const [replyText, setReplyText] = useState15("");
11809
+ const [replyImages, setReplyImages] = useState15([]);
11890
11810
  const scrollRef = useRef9(null);
11891
11811
  const panelRef = useRef9(null);
11892
11812
  const prevStreamingRef = useRef9(isStreaming);
@@ -11907,11 +11827,12 @@ function ThreadPanel({
11907
11827
  }
11908
11828
  prevStreamingRef.current = isStreaming;
11909
11829
  }, [isStreaming, fetchThread]);
11830
+ const streamSegments = streamingEvents ? buildStreamSegments(streamingEvents) : [];
11910
11831
  useEffect19(() => {
11911
11832
  if (scrollRef.current) {
11912
11833
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
11913
11834
  }
11914
- }, [messages, streamingResponse, streamingThinking, isStreaming]);
11835
+ }, [messages, streamSegments.length, isStreaming]);
11915
11836
  useEffect19(() => {
11916
11837
  const handleEscape = (e) => {
11917
11838
  if (e.key === "Escape") onClose();
@@ -11922,24 +11843,38 @@ function ThreadPanel({
11922
11843
  const handleSubmit = useCallback8(() => {
11923
11844
  if (!replyText.trim() || !onReply) return;
11924
11845
  const text = replyText.trim();
11846
+ const images = replyImages.length > 0 ? replyImages : void 0;
11925
11847
  setMessages((prev) => [...prev, {
11926
11848
  role: "human",
11927
11849
  timestamp: Date.now(),
11928
11850
  jobId: "pending",
11929
- replyToQuestion: text
11851
+ replyToQuestion: images ? `${text} [${images.length} image${images.length > 1 ? "s" : ""}]` : text
11930
11852
  }]);
11931
- onReply(threadId, text);
11853
+ onReply(threadId, text, images);
11932
11854
  setReplyText("");
11933
- }, [replyText, threadId, onReply]);
11855
+ setReplyImages([]);
11856
+ }, [replyText, replyImages, threadId, onReply]);
11857
+ const handlePaste = useCallback8((e) => {
11858
+ const items = e.clipboardData.items;
11859
+ const imageBlobs = [];
11860
+ for (let i = 0; i < items.length; i++) {
11861
+ const item = items[i];
11862
+ if (item.type.startsWith("image/")) {
11863
+ const file = item.getAsFile();
11864
+ if (file) imageBlobs.push(file);
11865
+ }
11866
+ }
11867
+ if (imageBlobs.length > 0) {
11868
+ e.preventDefault();
11869
+ setReplyImages((prev) => [...prev, ...imageBlobs]);
11870
+ }
11871
+ }, []);
11934
11872
  const handleKeyDown = useCallback8((e) => {
11935
11873
  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
11936
11874
  e.preventDefault();
11937
11875
  handleSubmit();
11938
11876
  }
11939
11877
  }, [handleSubmit]);
11940
- const toolStep = streamingEvents ? formatToolStep(streamingEvents) : null;
11941
- const thinkingText = streamingThinking || "";
11942
- const responseText = streamingResponse ? stripInternalTags(streamingResponse) : "";
11943
11878
  return /* @__PURE__ */ jsxs12(Fragment10, { children: [
11944
11879
  /* @__PURE__ */ jsx15(
11945
11880
  "div",
@@ -11987,7 +11922,9 @@ function ThreadPanel({
11987
11922
  const isHuman = msg.role === "human";
11988
11923
  const text = isHuman ? msg.replyToQuestion || msg.feedbackSummary || "(annotation)" : stripInternalTags(msg.responseText || "");
11989
11924
  const questionText = !isHuman ? msg.question : void 0;
11990
- if (!text && !questionText) return null;
11925
+ const hasResolutions = !isHuman && msg.resolutions && msg.resolutions.length > 0;
11926
+ const hasToolsUsed = !isHuman && msg.toolsUsed && msg.toolsUsed.length > 0;
11927
+ if (!text && !questionText && !hasResolutions) return null;
11991
11928
  const isLatest = i === messages.length - 1;
11992
11929
  return /* @__PURE__ */ jsxs12(
11993
11930
  "div",
@@ -12020,7 +11957,21 @@ function ThreadPanel({
12020
11957
  marginTop: text ? 4 : 0,
12021
11958
  lineHeight: 1.5,
12022
11959
  wordBreak: "break-word"
12023
- }, children: renderMarkdown(questionText) })
11960
+ }, children: renderMarkdown(questionText) }),
11961
+ (hasToolsUsed || hasResolutions) && /* @__PURE__ */ jsxs12("div", { style: {
11962
+ marginTop: text || questionText ? 6 : 0,
11963
+ padding: "4px 8px",
11964
+ backgroundColor: "rgba(0, 0, 0, 0.03)",
11965
+ fontSize: 11,
11966
+ lineHeight: 1.5,
11967
+ color: "#6b7280"
11968
+ }, children: [
11969
+ hasToolsUsed && /* @__PURE__ */ jsx15("div", { style: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 10 }, children: msg.toolsUsed.map((t, j) => /* @__PURE__ */ jsx15("div", { children: t }, j)) }),
11970
+ hasResolutions && msg.resolutions.map((r, j) => /* @__PURE__ */ jsxs12("div", { style: { marginTop: hasToolsUsed ? 4 : 0 }, children: [
11971
+ /* @__PURE__ */ jsx15("span", { style: { color: r.status === "resolved" ? "#10b981" : "#f59e0b" }, children: r.status === "resolved" ? "Done" : "Needs review" }),
11972
+ r.summary ? ` \u2014 ${r.summary}` : ""
11973
+ ] }, j))
11974
+ ] })
12024
11975
  ]
12025
11976
  },
12026
11977
  `${msg.jobId}-${i}`
@@ -12117,39 +12068,102 @@ function ThreadPanel({
12117
12068
  /* @__PURE__ */ jsx15("span", { style: { fontWeight: 600, fontSize: 11, color: "#6b7280" }, children: "Claude Code" }),
12118
12069
  /* @__PURE__ */ jsx15("span", { style: { marginLeft: "auto" }, children: /* @__PURE__ */ jsx15(ThinkingBadge, { color: accentColor }) })
12119
12070
  ] }),
12120
- toolStep && /* @__PURE__ */ jsx15("div", { style: {
12121
- paddingLeft: 12,
12122
- marginBottom: 4,
12123
- fontSize: 11,
12124
- color: "#9ca3af",
12125
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
12126
- }, children: toolStep }),
12127
- thinkingText && /* @__PURE__ */ jsx15("div", { style: {
12128
- paddingLeft: 12,
12129
- marginBottom: 4,
12130
- fontSize: 11,
12131
- color: "#9ca3af",
12132
- fontStyle: "italic",
12133
- lineHeight: 1.4,
12134
- whiteSpace: "pre-wrap",
12135
- wordBreak: "break-word",
12136
- maxHeight: 120,
12137
- overflowY: "auto"
12138
- }, children: thinkingText }),
12139
- responseText && /* @__PURE__ */ jsx15("div", { style: {
12140
- paddingLeft: 12,
12141
- lineHeight: 1.5,
12142
- wordBreak: "break-word"
12143
- }, children: renderMarkdown(responseText) })
12071
+ streamSegments.map((seg, i) => {
12072
+ if (seg.kind === "tool") {
12073
+ return /* @__PURE__ */ jsx15(
12074
+ "div",
12075
+ {
12076
+ style: {
12077
+ paddingLeft: 12,
12078
+ fontSize: 11,
12079
+ color: "#9ca3af",
12080
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
12081
+ lineHeight: 1.6
12082
+ },
12083
+ children: seg.label
12084
+ },
12085
+ i
12086
+ );
12087
+ }
12088
+ if (seg.kind === "thinking") {
12089
+ return /* @__PURE__ */ jsx15(
12090
+ "div",
12091
+ {
12092
+ style: {
12093
+ paddingLeft: 12,
12094
+ marginTop: 2,
12095
+ marginBottom: 2,
12096
+ fontSize: 11,
12097
+ color: "#9ca3af",
12098
+ fontStyle: "italic",
12099
+ lineHeight: 1.4,
12100
+ whiteSpace: "pre-wrap",
12101
+ wordBreak: "break-word",
12102
+ maxHeight: 80,
12103
+ overflowY: "auto"
12104
+ },
12105
+ children: seg.text
12106
+ },
12107
+ i
12108
+ );
12109
+ }
12110
+ const stripped = stripInternalTags(seg.text);
12111
+ if (!stripped) return null;
12112
+ return /* @__PURE__ */ jsx15(
12113
+ "div",
12114
+ {
12115
+ style: {
12116
+ paddingLeft: 12,
12117
+ marginTop: 2,
12118
+ marginBottom: 2,
12119
+ lineHeight: 1.5,
12120
+ wordBreak: "break-word"
12121
+ },
12122
+ children: renderMarkdown(stripped)
12123
+ },
12124
+ i
12125
+ );
12126
+ })
12144
12127
  ] })
12145
12128
  ] }),
12146
12129
  onReply && /* @__PURE__ */ jsxs12("div", { style: replyAreaStyle, children: [
12130
+ replyImages.length > 0 && /* @__PURE__ */ jsxs12("div", { style: {
12131
+ fontSize: 11,
12132
+ color: "#6b7280",
12133
+ marginBottom: 4,
12134
+ display: "flex",
12135
+ alignItems: "center",
12136
+ gap: 4
12137
+ }, children: [
12138
+ /* @__PURE__ */ jsxs12("span", { children: [
12139
+ replyImages.length,
12140
+ " image",
12141
+ replyImages.length > 1 ? "s" : "",
12142
+ " attached"
12143
+ ] }),
12144
+ /* @__PURE__ */ jsx15(
12145
+ "button",
12146
+ {
12147
+ onClick: () => setReplyImages([]),
12148
+ style: {
12149
+ background: "none",
12150
+ border: "none",
12151
+ cursor: "pointer",
12152
+ fontSize: 11,
12153
+ color: "#9ca3af",
12154
+ padding: "0 2px"
12155
+ },
12156
+ children: "\xD7"
12157
+ }
12158
+ )
12159
+ ] }),
12147
12160
  /* @__PURE__ */ jsx15(
12148
12161
  "textarea",
12149
12162
  {
12150
12163
  value: replyText,
12151
12164
  onChange: (e) => setReplyText(e.target.value),
12152
12165
  onKeyDown: handleKeyDown,
12166
+ onPaste: handlePaste,
12153
12167
  placeholder: "Reply... (Cmd+Enter to send)",
12154
12168
  style: {
12155
12169
  width: "100%",
@@ -12223,6 +12237,7 @@ function PopmeltProvider({
12223
12237
  var _a, _b, _c, _d, _e;
12224
12238
  const [state, dispatch] = useAnnotationState();
12225
12239
  const bridge = useBridgeConnection(bridgeUrl);
12240
+ const annotationImagesRef = useRef10(/* @__PURE__ */ new Map());
12226
12241
  const [provider, setProvider] = useState16(() => {
12227
12242
  if (typeof window === "undefined") return "claude";
12228
12243
  return localStorage.getItem(PROVIDER_STORAGE_KEY) || "claude";
@@ -12556,6 +12571,10 @@ function PopmeltProvider({
12556
12571
  }
12557
12572
  }
12558
12573
  }, [activePlan == null ? void 0 : activePlan.planId, state.activeColor, dispatch]);
12574
+ const handleAttachImages = useCallback9((annotationId, images) => {
12575
+ const existing = annotationImagesRef.current.get(annotationId) || [];
12576
+ annotationImagesRef.current.set(annotationId, [...existing, ...images]);
12577
+ }, []);
12559
12578
  const handleSendToClaude = useCallback9(async () => {
12560
12579
  const canvas = document.getElementById("devtools-canvas");
12561
12580
  if (!canvas) return false;
@@ -12572,9 +12591,37 @@ function PopmeltProvider({
12572
12591
  if (!stitchedBlob) return false;
12573
12592
  const feedbackData = buildFeedbackData(activeAnnotations, state.styleModifications);
12574
12593
  const feedbackJson = JSON.stringify(feedbackData);
12594
+ const pastedImages = /* @__PURE__ */ new Map();
12595
+ for (const ann of activeAnnotations) {
12596
+ const blobs2 = annotationImagesRef.current.get(ann.id);
12597
+ if (blobs2 && blobs2.length > 0) {
12598
+ pastedImages.set(ann.id, blobs2);
12599
+ }
12600
+ if (ann.groupId) {
12601
+ for (const mate of activeAnnotations) {
12602
+ if (mate.groupId === ann.groupId && mate.id !== ann.id) {
12603
+ const mateBlobs = annotationImagesRef.current.get(mate.id);
12604
+ if (mateBlobs && mateBlobs.length > 0) {
12605
+ pastedImages.set(mate.id, mateBlobs);
12606
+ }
12607
+ }
12608
+ }
12609
+ }
12610
+ }
12575
12611
  try {
12576
12612
  const hexColor = cssColorToHex(state.activeColor);
12577
- const { jobId, threadId: assignedThreadId } = await sendToBridge(stitchedBlob, feedbackJson, bridgeUrl, hexColor, provider, currentModel.id);
12613
+ const { jobId, threadId: assignedThreadId } = await sendToBridge(
12614
+ stitchedBlob,
12615
+ feedbackJson,
12616
+ bridgeUrl,
12617
+ hexColor,
12618
+ provider,
12619
+ currentModel.id,
12620
+ pastedImages.size > 0 ? pastedImages : void 0
12621
+ );
12622
+ for (const annId of pastedImages.keys()) {
12623
+ annotationImagesRef.current.delete(annId);
12624
+ }
12578
12625
  const sentAnnotationIds = activeAnnotations.map((a) => a.id);
12579
12626
  const sentStyleSelectors = state.styleModifications.filter((m) => !m.captured).map((m) => m.selector);
12580
12627
  setInFlightJobs((prev) => __spreadProps(__spreadValues({}, prev), {
@@ -12586,6 +12633,9 @@ function PopmeltProvider({
12586
12633
  }
12587
12634
  }));
12588
12635
  dispatch({ type: "MARK_CAPTURED" });
12636
+ if (assignedThreadId && sentAnnotationIds.length > 0) {
12637
+ dispatch({ type: "SET_ANNOTATION_THREAD", payload: { ids: sentAnnotationIds, threadId: assignedThreadId } });
12638
+ }
12589
12639
  const hueMatch = state.activeColor.match(/oklch\([^)]*\s+([\d.]+)\s*\)/);
12590
12640
  const currentHue = (hueMatch == null ? void 0 : hueMatch[1]) ? parseFloat(hueMatch[1]) : 29;
12591
12641
  const nextHue = (currentHue + 60) % 360;
@@ -12596,10 +12646,10 @@ function PopmeltProvider({
12596
12646
  return false;
12597
12647
  }
12598
12648
  }, [state.annotations, state.styleModifications, state.activeColor, dispatch, bridgeUrl, provider, currentModel.id]);
12599
- const handleReply = useCallback9(async (threadId, reply) => {
12649
+ const handleReply = useCallback9(async (threadId, reply, images) => {
12600
12650
  try {
12601
12651
  const hexColor = cssColorToHex(state.activeColor);
12602
- const { jobId } = await sendReplyToBridge(threadId, reply, bridgeUrl, hexColor, provider, currentModel.id);
12652
+ const { jobId } = await sendReplyToBridge(threadId, reply, bridgeUrl, hexColor, provider, currentModel.id, images);
12603
12653
  setInFlightJobs((prev) => __spreadProps(__spreadValues({}, prev), {
12604
12654
  [jobId]: {
12605
12655
  annotationIds: [],
@@ -12856,9 +12906,9 @@ function PopmeltProvider({
12856
12906
  inFlightAnnotationIds,
12857
12907
  inFlightStyleSelectors,
12858
12908
  inFlightSelectorColors,
12909
+ onAttachImages: handleAttachImages,
12859
12910
  onReply: bridge.isConnected ? handleReply : void 0,
12860
12911
  onViewThread: bridge.isConnected ? handleViewThread : void 0,
12861
- isThreadPanelOpen: openThreadId !== null,
12862
12912
  activePlan
12863
12913
  }
12864
12914
  ),