@ai-matrx/messaging 0.15.8 → 0.15.10

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/react.cjs CHANGED
@@ -361,7 +361,7 @@ function splitText(content) {
361
361
  }
362
362
  function summarizeText(content, maxLength = 140) {
363
363
  const parts = splitText(content).map(
364
- (segment) => segment.type === "text" ? segment.value : segment.type === "reference" ? segment.reference.label : (
364
+ (segment) => segment.type === "text" ? segment.value.replace(/```[^\n]*\n([\s\S]*?)```/g, "$1").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/^\s{0,3}(?:#{1,6}\s+|>\s*|[-*+]\s+|\d+\.\s+)/gm, "").replace(/([*_`~])\1*(.*?)\1+/g, "$2") : segment.type === "reference" ? segment.reference.label : (
365
365
  // A fence we cannot read still SAYS something. Collapsing it to
366
366
  // nothing is how a reference-only message becomes an inbox row that
367
367
  // reads "No messages yet" — a screen telling a lie.
@@ -830,8 +830,7 @@ function projectMessageAction(value) {
830
830
  payload: jsonObject(record["payload"])
831
831
  };
832
832
  }
833
- function projectAttachments(metadata) {
834
- const raw = metadata["attachments"];
833
+ function projectAttachmentList(raw) {
835
834
  if (!Array.isArray(raw)) return [];
836
835
  const attachments = [];
837
836
  for (const entry of raw) {
@@ -853,8 +852,13 @@ function projectAttachments(metadata) {
853
852
  }
854
853
  return attachments;
855
854
  }
855
+ function projectAttachments(metadata) {
856
+ return projectAttachmentList(metadata["attachments"]);
857
+ }
856
858
  function projectReferences(metadata) {
857
- const raw = metadata["references"];
859
+ return projectReferenceList(metadata["references"]);
860
+ }
861
+ function projectReferenceList(raw) {
858
862
  if (!Array.isArray(raw)) return [];
859
863
  const references = [];
860
864
  for (const entry of raw) {
@@ -873,6 +877,35 @@ function projectReferences(metadata) {
873
877
  }
874
878
  return references;
875
879
  }
880
+ function projectBroadcastMessage(value) {
881
+ const operation = "projectBroadcastMessage";
882
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
883
+ throw invalidResponse(operation, "broadcast payload was not an object");
884
+ }
885
+ const record = value;
886
+ const kindRaw = str(record, "kind") ?? str(record, "messageType") ?? "text";
887
+ const stateRaw = str(record, "deliveryState") ?? str(record, "status") ?? "sent";
888
+ const metadata = jsonObject(record["metadata"]);
889
+ return {
890
+ id: requiredStr(record, "id", operation),
891
+ conversationId: requiredStr(record, "conversationId", operation),
892
+ senderId: requiredStr(record, "senderId", operation),
893
+ organizationId: requiredStr(record, "organizationId", operation),
894
+ content: typeof record["content"] === "string" ? record["content"] : "",
895
+ kind: MESSAGE_KINDS.has(kindRaw) ? kindRaw : "text",
896
+ deliveryState: DELIVERY_STATES.has(stateRaw) ? stateRaw : "sent",
897
+ replyToId: str(record, "replyToId"),
898
+ clientMessageId: str(record, "clientMessageId"),
899
+ createdAt: requiredStr(record, "createdAt", operation),
900
+ editedAt: str(record, "editedAt"),
901
+ deletedAt: str(record, "deletedAt"),
902
+ deletedForEveryone: bool(record, "deletedForEveryone", false),
903
+ action: projectMessageAction(record["action"]),
904
+ attachments: projectAttachmentList(record["attachments"]),
905
+ references: projectReferenceList(record["references"]),
906
+ metadata
907
+ };
908
+ }
876
909
  function projectMessage(row, fallbackOrganizationId) {
877
910
  const operation = "projectMessage";
878
911
  const metadata = jsonObject(row["metadata"]);
@@ -1346,6 +1379,7 @@ function createMessagingEngine(options) {
1346
1379
  store,
1347
1380
  outbox,
1348
1381
  identity,
1382
+ reportDiagnostic: report,
1349
1383
  async start() {
1350
1384
  if (options.archiveFilter !== void 0) {
1351
1385
  store.setArchiveFilter(options.archiveFilter);
@@ -1487,8 +1521,11 @@ function createMessagingEngine(options) {
1487
1521
  {
1488
1522
  event: MESSAGING_EVENTS.message,
1489
1523
  onMessage: ({ data }) => {
1490
- if (typeof data !== "object" || data === null) return;
1491
- store.ingest(data);
1524
+ try {
1525
+ store.ingest(projectBroadcastMessage(data));
1526
+ } catch (error) {
1527
+ reportError(error, "broadcastMessage");
1528
+ }
1492
1529
  }
1493
1530
  }
1494
1531
  ],
@@ -2397,6 +2434,7 @@ function MessagingRuntime(props) {
2397
2434
  }, [renderers]);
2398
2435
  const renderReference = props.renderReference ?? null;
2399
2436
  const renderFence = props.renderFence ?? null;
2437
+ const renderAttachment = props.renderAttachment ?? null;
2400
2438
  const wrapMessage = props.wrapMessage ?? null;
2401
2439
  const wrapConversationRow = props.wrapConversationRow ?? null;
2402
2440
  const host = (0, import_react.useMemo)(() => {
@@ -2410,6 +2448,7 @@ function MessagingRuntime(props) {
2410
2448
  actionRenderers: rendererMap,
2411
2449
  renderReference,
2412
2450
  renderFence,
2451
+ renderAttachment,
2413
2452
  wrapMessage,
2414
2453
  wrapConversationRow
2415
2454
  };
@@ -2420,6 +2459,7 @@ function MessagingRuntime(props) {
2420
2459
  rendererMap,
2421
2460
  renderReference,
2422
2461
  renderFence,
2462
+ renderAttachment,
2423
2463
  wrapMessage,
2424
2464
  wrapConversationRow
2425
2465
  ]);
@@ -2631,11 +2671,117 @@ function useConversation(id) {
2631
2671
  }
2632
2672
  };
2633
2673
  }
2674
+ var EMPTY_COMPOSER_SESSION = {
2675
+ drafts: {},
2676
+ replies: {},
2677
+ additions: {}
2678
+ };
2679
+ var emptyComposerSnapshot = () => EMPTY_COMPOSER_SESSION;
2680
+ var subscribeToNothing = () => () => void 0;
2681
+ var composerSessions = /* @__PURE__ */ new WeakMap();
2682
+ var composerStorageFailures = /* @__PURE__ */ new WeakSet();
2683
+ function composerStorageKey(engine) {
2684
+ return `ai-matrx.messaging.composer.${engine.identity.userId}`;
2685
+ }
2686
+ function readComposerSession(engine) {
2687
+ try {
2688
+ if (typeof sessionStorage === "undefined") return EMPTY_COMPOSER_SESSION;
2689
+ const serialized = sessionStorage.getItem(composerStorageKey(engine));
2690
+ if (serialized === null) return EMPTY_COMPOSER_SESSION;
2691
+ const parsed = JSON.parse(serialized);
2692
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2693
+ throw new Error("saved composer state was not an object");
2694
+ }
2695
+ const source = parsed;
2696
+ const drafts = {};
2697
+ if (typeof source["drafts"] === "object" && source["drafts"] !== null) {
2698
+ for (const [id, value] of Object.entries(source["drafts"])) {
2699
+ if (typeof value === "string") drafts[id] = value;
2700
+ }
2701
+ }
2702
+ const replies = {};
2703
+ if (typeof source["replies"] === "object" && source["replies"] !== null) {
2704
+ for (const [id, value] of Object.entries(source["replies"])) {
2705
+ if (value === null) replies[id] = null;
2706
+ else replies[id] = projectBroadcastMessage(value);
2707
+ }
2708
+ }
2709
+ const additions = {};
2710
+ if (typeof source["additions"] === "object" && source["additions"] !== null) {
2711
+ for (const [id, value] of Object.entries(source["additions"])) {
2712
+ if (!Array.isArray(value)) continue;
2713
+ additions[id] = value.filter((item) => {
2714
+ if (typeof item !== "object" || item === null || Array.isArray(item)) return false;
2715
+ const record = item;
2716
+ return typeof record["id"] === "string" && typeof record["label"] === "string";
2717
+ });
2718
+ }
2719
+ }
2720
+ return { drafts, replies, additions };
2721
+ } catch (error) {
2722
+ if (!composerStorageFailures.has(engine)) {
2723
+ composerStorageFailures.add(engine);
2724
+ engine.reportDiagnostic({
2725
+ level: "warn",
2726
+ message: `A saved message draft could not be restored: ${String(error?.message ?? error)}`,
2727
+ remedy: "The current draft remains usable. Re-add any missing attachments before sending."
2728
+ });
2729
+ }
2730
+ return EMPTY_COMPOSER_SESSION;
2731
+ }
2732
+ }
2733
+ function writeComposerSession(engine, snapshot) {
2734
+ try {
2735
+ if (typeof sessionStorage === "undefined") return;
2736
+ sessionStorage.setItem(composerStorageKey(engine), JSON.stringify(snapshot));
2737
+ } catch (error) {
2738
+ if (!composerStorageFailures.has(engine)) {
2739
+ composerStorageFailures.add(engine);
2740
+ engine.reportDiagnostic({
2741
+ level: "warn",
2742
+ message: `The current message draft could not be saved for reload recovery: ${String(error?.message ?? error)}`,
2743
+ remedy: "Keep this tab open until the message is sent, or copy the draft before reloading."
2744
+ });
2745
+ }
2746
+ }
2747
+ }
2748
+ function getComposerSession(engine) {
2749
+ const existing = composerSessions.get(engine);
2750
+ if (existing !== void 0) return existing;
2751
+ let snapshot = readComposerSession(engine);
2752
+ const listeners = /* @__PURE__ */ new Set();
2753
+ const store = {
2754
+ getSnapshot: () => snapshot,
2755
+ subscribe: (listener) => {
2756
+ listeners.add(listener);
2757
+ return () => listeners.delete(listener);
2758
+ },
2759
+ update: (change) => {
2760
+ snapshot = change(snapshot);
2761
+ writeComposerSession(engine, snapshot);
2762
+ listeners.forEach((listener) => listener());
2763
+ }
2764
+ };
2765
+ composerSessions.set(engine, store);
2766
+ return store;
2767
+ }
2634
2768
  function useComposer(conversationId) {
2635
2769
  const host = useMessagingHost();
2636
- const [drafts, setDrafts] = (0, import_react3.useState)({});
2637
- const [replyTo, setReplyTo] = (0, import_react3.useState)(null);
2638
- const value = conversationId === null ? "" : drafts[conversationId] ?? "";
2770
+ const session = host === null ? null : getComposerSession(host.engine);
2771
+ const composer = (0, import_react3.useSyncExternalStore)(
2772
+ session?.subscribe ?? subscribeToNothing,
2773
+ session?.getSnapshot ?? emptyComposerSnapshot,
2774
+ emptyComposerSnapshot
2775
+ );
2776
+ const replyTo = conversationId === null ? null : composer.replies[conversationId] ?? null;
2777
+ const additions = conversationId === null ? [] : composer.additions[conversationId] ?? [];
2778
+ const setReplyTo = (message) => {
2779
+ if (conversationId !== null) session?.update((current) => ({
2780
+ ...current,
2781
+ replies: { ...current.replies, [conversationId]: message }
2782
+ }));
2783
+ };
2784
+ const value = conversationId === null ? "" : composer.drafts[conversationId] ?? "";
2639
2785
  const typing = (0, import_react4.useTyping)(
2640
2786
  {
2641
2787
  topic: conversationId === null ? "" : conversationTopic(conversationId),
@@ -2646,26 +2792,57 @@ function useComposer(conversationId) {
2646
2792
  const setValue = (0, import_react3.useCallback)(
2647
2793
  (next) => {
2648
2794
  if (conversationId === null) return;
2649
- setDrafts((current) => ({ ...current, [conversationId]: next }));
2795
+ session?.update((current) => ({
2796
+ ...current,
2797
+ drafts: { ...current.drafts, [conversationId]: next }
2798
+ }));
2650
2799
  },
2651
- [conversationId]
2800
+ [conversationId, session]
2652
2801
  );
2653
2802
  return {
2654
2803
  value,
2655
2804
  setValue,
2656
- canSend: value.trim().length > 0 && conversationId !== null && host !== null,
2805
+ additions,
2806
+ add: (item) => {
2807
+ if (conversationId !== null) session?.update((current) => ({
2808
+ ...current,
2809
+ additions: {
2810
+ ...current.additions,
2811
+ [conversationId]: [
2812
+ ...(current.additions[conversationId] ?? []).filter((existing) => existing.id !== item.id),
2813
+ item
2814
+ ]
2815
+ }
2816
+ }));
2817
+ },
2818
+ removeAddition: (id) => {
2819
+ if (conversationId !== null) session?.update((current) => ({
2820
+ ...current,
2821
+ additions: {
2822
+ ...current.additions,
2823
+ [conversationId]: (current.additions[conversationId] ?? []).filter((item) => item.id !== id)
2824
+ }
2825
+ }));
2826
+ },
2827
+ canSend: (value.trim().length > 0 || additions.length > 0) && conversationId !== null && host !== null,
2657
2828
  send: () => {
2658
- if (host === null || conversationId === null) return;
2659
- const content = value.trim();
2660
- if (content.length === 0) return;
2829
+ if (host === null || conversationId === null || session === null) return;
2830
+ const content = [value.trim(), ...additions.map((item) => item.content ?? "")].filter(Boolean).join("\n\n");
2831
+ const attachments = additions.flatMap((item) => item.attachment ? [item.attachment] : []);
2832
+ if (content.length === 0 && attachments.length === 0) return;
2661
2833
  const draft = {
2662
2834
  conversationId,
2663
2835
  content,
2836
+ attachments,
2664
2837
  ...replyTo !== null ? { replyToId: replyTo.id } : {}
2665
2838
  };
2666
2839
  host.engine.send(draft);
2667
- setDrafts((current) => ({ ...current, [conversationId]: "" }));
2668
- setReplyTo(null);
2840
+ session.update((current) => ({
2841
+ ...current,
2842
+ drafts: { ...current.drafts, [conversationId]: "" },
2843
+ replies: { ...current.replies, [conversationId]: null },
2844
+ additions: { ...current.additions, [conversationId]: [] }
2845
+ }));
2669
2846
  typing.stopTyping();
2670
2847
  },
2671
2848
  onKeystroke: typing.onKeystroke,
@@ -3176,6 +3353,7 @@ function ConversationList(props) {
3176
3353
  } = useConversations();
3177
3354
  const RowChrome = useMessagingHost()?.wrapConversationRow ?? null;
3178
3355
  const [query, setQuery] = (0, import_react5.useState)("");
3356
+ const favoritePageRequestCount = (0, import_react5.useRef)(null);
3179
3357
  const showingArchive = archiveFilter === "archived";
3180
3358
  const archiveReveal = (0, import_react5.useMemo)(() => {
3181
3359
  if (showingArchive) {
@@ -3201,6 +3379,51 @@ function ConversationList(props) {
3201
3379
  (item) => item.displayName.toLowerCase().includes(needle) || (item.lastMessageContent ?? "").toLowerCase().includes(needle)
3202
3380
  );
3203
3381
  }, [conversations, query]);
3382
+ const loadNextPage = () => {
3383
+ if (favoritePageRequestCount.current === conversations.length) return;
3384
+ favoritePageRequestCount.current = conversations.length;
3385
+ loadMore();
3386
+ };
3387
+ const hasUnloadedFavorites = (props.pinnedConversationIds ?? []).some(
3388
+ (id) => !conversations.some((item) => String(item.conversation.id) === id)
3389
+ );
3390
+ (0, import_react5.useEffect)(() => {
3391
+ if (!hasUnloadedFavorites || !hasMore || isInitialLoading || loadError !== null || favoritePageRequestCount.current === conversations.length) {
3392
+ return;
3393
+ }
3394
+ loadNextPage();
3395
+ }, [
3396
+ conversations,
3397
+ hasMore,
3398
+ hasUnloadedFavorites,
3399
+ isInitialLoading,
3400
+ loadError,
3401
+ loadMore,
3402
+ loadNextPage
3403
+ ]);
3404
+ const { favoriteItems, otherPinnedItems, unpinnedItems } = (0, import_react5.useMemo)(() => {
3405
+ const visibleById = new Map(
3406
+ visible.map((item) => [String(item.conversation.id), item])
3407
+ );
3408
+ const pinnedItems = (props.pinnedConversationIds ?? []).map((id) => visibleById.get(id)).filter((item) => item !== void 0);
3409
+ const uniquePinnedItems = Array.from(
3410
+ new Map(pinnedItems.map((item) => [item.conversation.id, item])).values()
3411
+ ).sort((left, right) => {
3412
+ const byRecency = Date.parse(right.sortAt) - Date.parse(left.sortAt);
3413
+ return Number.isNaN(byRecency) || byRecency === 0 ? String(left.conversation.id).localeCompare(String(right.conversation.id)) : byRecency;
3414
+ });
3415
+ const pinnedIds = new Set(uniquePinnedItems.map((item) => item.conversation.id));
3416
+ return {
3417
+ favoriteItems: uniquePinnedItems.slice(0, 3),
3418
+ otherPinnedItems: uniquePinnedItems.slice(3),
3419
+ unpinnedItems: visible.filter((item) => !pinnedIds.has(item.conversation.id))
3420
+ };
3421
+ }, [visible, props.pinnedConversationIds]);
3422
+ const selectItem = (item) => {
3423
+ select(item.conversation.id);
3424
+ props.onSelect?.(item.conversation.id);
3425
+ };
3426
+ const wrapRow = (item, row) => RowChrome !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowChrome, { conversation: item, children: row }) : row;
3204
3427
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `mx-msg__list${props.className !== void 0 ? ` ${props.className}` : ""}`, children: [
3205
3428
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-msg__search", children: [
3206
3429
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SearchIcon, { style: { color: "var(--mx-msg-text-faint)" } }),
@@ -3256,33 +3479,67 @@ function ConversationList(props) {
3256
3479
  title: query.length > 0 ? "No matches" : showingArchive ? "No archived conversations" : "No conversations yet",
3257
3480
  body: query.length > 0 ? "Try a different name or word." : showingArchive ? "Archiving a conversation moves it here." : "Start one and it will appear here."
3258
3481
  }
3259
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-msg__rows", children: visible.map((item) => {
3260
- const row = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3261
- ConversationRow,
3262
- {
3263
- summary: item,
3264
- isActive: item.conversation.id === activeConversationId,
3265
- onSelect: () => {
3266
- select(item.conversation.id);
3267
- props.onSelect?.(item.conversation.id);
3482
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3483
+ favoriteItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-msg__favorites", "aria-label": "Favorite conversations", children: favoriteItems.map((item) => {
3484
+ const row = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3485
+ ConversationFavorite,
3486
+ {
3487
+ summary: item,
3488
+ isActive: item.conversation.id === activeConversationId,
3489
+ onSelect: () => selectItem(item)
3268
3490
  }
3269
- }
3270
- );
3271
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: RowChrome !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowChrome, { conversation: item, children: row }) : row }, item.conversation.id);
3272
- }) }),
3491
+ );
3492
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: wrapRow(item, row) }, item.conversation.id);
3493
+ }) }) : null,
3494
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-msg__rows", children: [...otherPinnedItems, ...unpinnedItems].map((item) => {
3495
+ const row = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3496
+ ConversationRow,
3497
+ {
3498
+ summary: item,
3499
+ isActive: item.conversation.id === activeConversationId,
3500
+ onSelect: () => selectItem(item)
3501
+ }
3502
+ );
3503
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: wrapRow(item, row) }, item.conversation.id);
3504
+ }) })
3505
+ ] }),
3273
3506
  hasMore && !isInitialLoading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3274
3507
  "button",
3275
3508
  {
3276
3509
  type: "button",
3277
3510
  className: "mx-msg__chip",
3278
3511
  style: { margin: "8px auto", display: "block" },
3279
- onClick: loadMore,
3512
+ onClick: loadNextPage,
3280
3513
  children: "Load older conversations"
3281
3514
  }
3282
3515
  ) : null
3283
3516
  ] })
3284
3517
  ] });
3285
3518
  }
3519
+ function ConversationFavorite(props) {
3520
+ const { summary } = props;
3521
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3522
+ "button",
3523
+ {
3524
+ type: "button",
3525
+ className: "mx-msg__favorite",
3526
+ "aria-current": props.isActive,
3527
+ "aria-label": `Open favorite conversation with ${summary.displayName}`,
3528
+ onClick: props.onSelect,
3529
+ children: [
3530
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3531
+ Avatar,
3532
+ {
3533
+ name: summary.displayName,
3534
+ imageUrl: summary.displayImageUrl,
3535
+ seed: summary.conversation.id
3536
+ }
3537
+ ),
3538
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-msg__favorite-name", children: summary.displayName })
3539
+ ]
3540
+ }
3541
+ );
3542
+ }
3286
3543
  function ConversationRow(props) {
3287
3544
  const { summary } = props;
3288
3545
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -3457,6 +3714,8 @@ function ConversationView(props) {
3457
3714
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3458
3715
  MessageGroupView,
3459
3716
  {
3717
+ allMessages: messages,
3718
+ showAuthor: conversation.summary?.conversation.type !== "direct",
3460
3719
  messages: group.messages,
3461
3720
  actor,
3462
3721
  isMine,
@@ -3488,37 +3747,41 @@ function MessageGroupView(props) {
3488
3747
  const first = props.messages[0];
3489
3748
  if (first === void 0) return null;
3490
3749
  const actor = props.actor;
3491
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `mx-msg__group${props.isMine ? " mx-msg__group--mine" : ""}`, children: [
3492
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3493
- Avatar,
3494
- {
3495
- name: actor.displayName,
3496
- imageUrl: actor.avatarUrl,
3497
- seed: actor.isAgent ? `agent:${actor.displayName}` : actor.principalUserId,
3498
- small: true
3499
- }
3500
- ),
3501
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-msg__group-body", children: [
3502
- !props.isMine || actor.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-msg__author", children: [
3503
- actor.displayName,
3504
- actor.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AgentTag, {}) : null,
3505
- actor.onBehalfOfName !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { fontWeight: 400, color: "var(--mx-msg-text-faint)" }, children: [
3506
- "via ",
3507
- actor.onBehalfOfName
3508
- ] }) : null
3509
- ] }) : null,
3510
- props.messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3511
- MessageBubble,
3750
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3751
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-msg__group-time", children: formatMessageTime(first.createdAt) }),
3752
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `mx-msg__group${props.isMine ? " mx-msg__group--mine" : ""}`, children: [
3753
+ props.showAuthor || actor.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3754
+ Avatar,
3512
3755
  {
3513
- message,
3514
- isMine: props.isMine,
3515
- isLast: index === props.messages.length - 1,
3516
- onRetry: props.onRetry,
3517
- onReply: props.onReply,
3518
- failedEntryId: message.clientMessageId !== null ? props.failedEntryIdByClientId.get(message.clientMessageId) ?? null : null
3519
- },
3520
- message.id
3521
- ))
3756
+ name: actor.displayName,
3757
+ imageUrl: actor.avatarUrl,
3758
+ seed: actor.isAgent ? `agent:${actor.displayName}` : actor.principalUserId,
3759
+ small: true
3760
+ }
3761
+ ) : null,
3762
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-msg__group-body", children: [
3763
+ props.showAuthor && !props.isMine || actor.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-msg__author", children: [
3764
+ actor.displayName,
3765
+ actor.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AgentTag, {}) : null,
3766
+ actor.onBehalfOfName !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { fontWeight: 400, color: "var(--mx-msg-text-faint)" }, children: [
3767
+ "via ",
3768
+ actor.onBehalfOfName
3769
+ ] }) : null
3770
+ ] }) : null,
3771
+ props.messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3772
+ MessageBubble,
3773
+ {
3774
+ message,
3775
+ replyTo: props.allMessages.find((parent) => parent.id === message.replyToId),
3776
+ isMine: props.isMine,
3777
+ isLast: index === props.messages.length - 1,
3778
+ onRetry: props.onRetry,
3779
+ onReply: props.onReply,
3780
+ failedEntryId: message.clientMessageId !== null ? props.failedEntryIdByClientId.get(message.clientMessageId) ?? null : null
3781
+ },
3782
+ message.id
3783
+ ))
3784
+ ] })
3522
3785
  ] })
3523
3786
  ] });
3524
3787
  }
@@ -3539,7 +3802,12 @@ function MessageBubble(props) {
3539
3802
  message.deliveryState === "failed" ? "mx-msg__bubble--failed" : ""
3540
3803
  ].filter(Boolean).join(" ");
3541
3804
  const bubble = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3542
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: classes, children: [
3805
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: classes, title: formatMessageTime(message.createdAt), children: [
3806
+ message.replyToId !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("blockquote", { className: "mx-msg__quoted-reply", children: props.replyTo ? props.replyTo.deletedAt ? "Message deleted" : summarizeText(props.replyTo.content, 180) || "Attachment" : "Earlier message" }) : null,
3807
+ message.attachments.map((attachment) => {
3808
+ const RenderAttachment = props.renderAttachment ?? host?.renderAttachment;
3809
+ return RenderAttachment ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RenderAttachment, { attachment }, attachment.fileId) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ReferenceCard, { reference: { entityType: "file", entityId: attachment.fileId, label: attachment.fileName }, onOpen: host?.openReference ?? null }, attachment.fileId);
3810
+ }),
3543
3811
  splitText(message.content).map(
3544
3812
  (segment, index) => segment.type === "text" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-msg__markdown", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default, import_remark_breaks.default], children: segment.value }) }, index) : segment.type === "fence" ? HostFence !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HostFence, { body: segment.body }, `fence:${index}`) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UnreadableReference, {}, `fence:${index}`) : HostReference !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3545
3813
  HostReference,
@@ -3579,7 +3847,7 @@ function MessageBubble(props) {
3579
3847
  {
3580
3848
  className: `mx-msg__meta${message.deliveryState === "failed" ? " mx-msg__meta--failed" : ""}`,
3581
3849
  children: [
3582
- props.isLast ? formatMessageTime(message.createdAt) : null,
3850
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-msg__message-time", children: props.isLast ? formatMessageTime(message.createdAt) : null }),
3583
3851
  message.editedAt !== null ? " \xB7 edited" : null,
3584
3852
  isMine ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DeliveryTick, { state: message.deliveryState }) : null,
3585
3853
  message.deliveryState === "failed" && props.failedEntryId != null && props.onRetry !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -3674,6 +3942,9 @@ function ComposerView(props) {
3674
3942
  )
3675
3943
  ] }) : null,
3676
3944
  props.renderInput !== void 0 ? props.renderInput({
3945
+ additions: composer.additions,
3946
+ add: composer.add,
3947
+ removeAddition: composer.removeAddition,
3677
3948
  value: composer.value,
3678
3949
  disabled: props.disabled,
3679
3950
  canSend: composer.canSend,