@runtypelabs/persona 1.43.5 → 1.43.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "1.43.5",
3
+ "version": "1.43.6",
4
4
  "description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -250,7 +250,7 @@ const getBubbleClasses = (
250
250
  export const createMessageActions = (
251
251
  message: AgentWidgetMessage,
252
252
  actionsConfig: AgentWidgetMessageActionsConfig,
253
- callbacks?: MessageActionCallbacks
253
+ _callbacks?: MessageActionCallbacks
254
254
  ): HTMLElement => {
255
255
  const showCopy = actionsConfig.showCopy ?? true;
256
256
  const showUpvote = actionsConfig.showUpvote ?? true;
@@ -282,145 +282,38 @@ export const createMessageActions = (
282
282
  container.id = `actions-${message.id}`;
283
283
  container.setAttribute("data-actions-for", message.id);
284
284
 
285
- // Track vote state for this message
286
- let currentVote: "upvote" | "downvote" | null = null;
287
-
288
285
  const createActionButton = (
289
286
  iconName: string,
290
287
  label: string,
291
- onClick: () => void,
292
- dataAction?: string
288
+ dataAction: string
293
289
  ): HTMLButtonElement => {
294
290
  const button = document.createElement("button");
295
291
  button.className = "tvw-message-action-btn";
296
292
  button.setAttribute("aria-label", label);
297
293
  button.setAttribute("title", label);
298
- if (dataAction) {
299
- button.setAttribute("data-action", dataAction);
300
- }
294
+ button.setAttribute("data-action", dataAction);
301
295
 
302
296
  const icon = renderLucideIcon(iconName, 14, "currentColor", 2);
303
297
  if (icon) {
304
298
  button.appendChild(icon);
305
299
  }
306
300
 
307
- button.addEventListener("click", (e) => {
308
- e.preventDefault();
309
- e.stopPropagation();
310
- onClick();
311
- });
312
-
313
301
  return button;
314
302
  };
315
303
 
316
- // Copy button
304
+ // Copy button - click handled via event delegation in ui.ts
317
305
  if (showCopy) {
318
- const copyButton = createActionButton("copy", "Copy message", () => {
319
- // Copy to clipboard
320
- const textToCopy = message.content || "";
321
- navigator.clipboard.writeText(textToCopy).then(() => {
322
- // Show success feedback - swap icon temporarily
323
- copyButton.classList.add("tvw-message-action-success");
324
- const checkIcon = renderLucideIcon("check", 14, "currentColor", 2);
325
- if (checkIcon) {
326
- copyButton.innerHTML = "";
327
- copyButton.appendChild(checkIcon);
328
- }
329
-
330
- // Restore original icon after 2 seconds
331
- setTimeout(() => {
332
- copyButton.classList.remove("tvw-message-action-success");
333
- const originalIcon = renderLucideIcon("copy", 14, "currentColor", 2);
334
- if (originalIcon) {
335
- copyButton.innerHTML = "";
336
- copyButton.appendChild(originalIcon);
337
- }
338
- }, 2000);
339
- }).catch((err) => {
340
- if (typeof console !== "undefined") {
341
- console.error("[AgentWidget] Failed to copy message:", err);
342
- }
343
- });
344
-
345
- // Trigger callback
346
- if (callbacks?.onCopy) {
347
- callbacks.onCopy(message);
348
- }
349
- if (actionsConfig.onCopy) {
350
- actionsConfig.onCopy(message);
351
- }
352
- }, "copy");
353
- container.appendChild(copyButton);
306
+ container.appendChild(createActionButton("copy", "Copy message", "copy"));
354
307
  }
355
308
 
356
- // Upvote button
309
+ // Upvote button - click handled via event delegation in ui.ts
357
310
  if (showUpvote) {
358
- const upvoteButton = createActionButton("thumbs-up", "Upvote", () => {
359
- const wasActive = currentVote === "upvote";
360
-
361
- // Toggle state
362
- if (wasActive) {
363
- currentVote = null;
364
- upvoteButton.classList.remove("tvw-message-action-active");
365
- } else {
366
- // Remove downvote if active
367
- const downvoteBtn = container.querySelector('[data-action="downvote"]');
368
- if (downvoteBtn) {
369
- downvoteBtn.classList.remove("tvw-message-action-active");
370
- }
371
- currentVote = "upvote";
372
- upvoteButton.classList.add("tvw-message-action-active");
373
-
374
- // Trigger feedback
375
- const feedback: AgentWidgetMessageFeedback = {
376
- type: "upvote",
377
- messageId: message.id,
378
- message
379
- };
380
- if (callbacks?.onFeedback) {
381
- callbacks.onFeedback(feedback);
382
- }
383
- if (actionsConfig.onFeedback) {
384
- actionsConfig.onFeedback(feedback);
385
- }
386
- }
387
- }, "upvote");
388
- container.appendChild(upvoteButton);
311
+ container.appendChild(createActionButton("thumbs-up", "Upvote", "upvote"));
389
312
  }
390
313
 
391
- // Downvote button
314
+ // Downvote button - click handled via event delegation in ui.ts
392
315
  if (showDownvote) {
393
- const downvoteButton = createActionButton("thumbs-down", "Downvote", () => {
394
- const wasActive = currentVote === "downvote";
395
-
396
- // Toggle state
397
- if (wasActive) {
398
- currentVote = null;
399
- downvoteButton.classList.remove("tvw-message-action-active");
400
- } else {
401
- // Remove upvote if active
402
- const upvoteBtn = container.querySelector('[data-action="upvote"]');
403
- if (upvoteBtn) {
404
- upvoteBtn.classList.remove("tvw-message-action-active");
405
- }
406
- currentVote = "downvote";
407
- downvoteButton.classList.add("tvw-message-action-active");
408
-
409
- // Trigger feedback
410
- const feedback: AgentWidgetMessageFeedback = {
411
- type: "downvote",
412
- messageId: message.id,
413
- message
414
- };
415
- if (callbacks?.onFeedback) {
416
- callbacks.onFeedback(feedback);
417
- }
418
- if (actionsConfig.onFeedback) {
419
- actionsConfig.onFeedback(feedback);
420
- }
421
- }
422
- }, "downvote");
423
- container.appendChild(downvoteButton);
316
+ container.appendChild(createActionButton("thumbs-down", "Downvote", "downvote"));
424
317
  }
425
318
 
426
319
  return container;
package/src/ui.ts CHANGED
@@ -823,6 +823,89 @@ export const createAgentExperience = (
823
823
  }
824
824
  });
825
825
 
826
+ // Add event delegation for message action buttons (upvote, downvote, copy)
827
+ // This handles clicks even after idiomorph morphs the DOM and strips inline listeners
828
+ const messageVoteState = new Map<string, "upvote" | "downvote">();
829
+
830
+ messagesWrapper.addEventListener('click', (event) => {
831
+ const target = event.target as HTMLElement;
832
+ const actionBtn = target.closest('.tvw-message-action-btn[data-action]') as HTMLElement;
833
+ if (!actionBtn) return;
834
+
835
+ event.preventDefault();
836
+ event.stopPropagation();
837
+
838
+ const actionsContainer = actionBtn.closest('[data-actions-for]') as HTMLElement;
839
+ if (!actionsContainer) return;
840
+
841
+ const messageId = actionsContainer.getAttribute('data-actions-for');
842
+ if (!messageId) return;
843
+
844
+ const action = actionBtn.getAttribute('data-action');
845
+
846
+ if (action === 'copy') {
847
+ const messages = session.getMessages();
848
+ const message = messages.find(m => m.id === messageId);
849
+ if (message && messageActionCallbacks.onCopy) {
850
+ // Copy to clipboard
851
+ const textToCopy = message.content || "";
852
+ navigator.clipboard.writeText(textToCopy).then(() => {
853
+ // Show success feedback - swap icon temporarily
854
+ actionBtn.classList.add("tvw-message-action-success");
855
+ const checkIcon = renderLucideIcon("check", 14, "currentColor", 2);
856
+ if (checkIcon) {
857
+ actionBtn.innerHTML = "";
858
+ actionBtn.appendChild(checkIcon);
859
+ }
860
+ setTimeout(() => {
861
+ actionBtn.classList.remove("tvw-message-action-success");
862
+ const originalIcon = renderLucideIcon("copy", 14, "currentColor", 2);
863
+ if (originalIcon) {
864
+ actionBtn.innerHTML = "";
865
+ actionBtn.appendChild(originalIcon);
866
+ }
867
+ }, 2000);
868
+ }).catch((err) => {
869
+ if (typeof console !== "undefined") {
870
+ // eslint-disable-next-line no-console
871
+ console.error("[AgentWidget] Failed to copy message:", err);
872
+ }
873
+ });
874
+ messageActionCallbacks.onCopy(message);
875
+ }
876
+ } else if (action === 'upvote' || action === 'downvote') {
877
+ const currentVote = messageVoteState.get(messageId) ?? null;
878
+ const wasActive = currentVote === action;
879
+
880
+ if (wasActive) {
881
+ // Toggle off
882
+ messageVoteState.delete(messageId);
883
+ actionBtn.classList.remove("tvw-message-action-active");
884
+ } else {
885
+ // Clear opposite vote button
886
+ const oppositeAction = action === 'upvote' ? 'downvote' : 'upvote';
887
+ const oppositeBtn = actionsContainer.querySelector(`[data-action="${oppositeAction}"]`);
888
+ if (oppositeBtn) {
889
+ oppositeBtn.classList.remove("tvw-message-action-active");
890
+ }
891
+
892
+ messageVoteState.set(messageId, action);
893
+ actionBtn.classList.add("tvw-message-action-active");
894
+
895
+ // Trigger feedback
896
+ const messages = session.getMessages();
897
+ const message = messages.find(m => m.id === messageId);
898
+ if (message && messageActionCallbacks.onFeedback) {
899
+ messageActionCallbacks.onFeedback({
900
+ type: action,
901
+ messageId: message.id,
902
+ message
903
+ });
904
+ }
905
+ }
906
+ }
907
+ });
908
+
826
909
  panel.appendChild(container);
827
910
  mount.appendChild(wrapper);
828
911