@ai-matrx/messaging 0.15.9 → 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.js CHANGED
@@ -251,7 +251,7 @@ function splitText(content) {
251
251
  }
252
252
  function summarizeText(content, maxLength = 140) {
253
253
  const parts = splitText(content).map(
254
- (segment) => segment.type === "text" ? segment.value : segment.type === "reference" ? segment.reference.label : (
254
+ (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 : (
255
255
  // A fence we cannot read still SAYS something. Collapsing it to
256
256
  // nothing is how a reference-only message becomes an inbox row that
257
257
  // reads "No messages yet" — a screen telling a lie.
@@ -722,8 +722,7 @@ function projectMessageAction(value) {
722
722
  payload: jsonObject(record["payload"])
723
723
  };
724
724
  }
725
- function projectAttachments(metadata) {
726
- const raw = metadata["attachments"];
725
+ function projectAttachmentList(raw) {
727
726
  if (!Array.isArray(raw)) return [];
728
727
  const attachments = [];
729
728
  for (const entry of raw) {
@@ -745,8 +744,13 @@ function projectAttachments(metadata) {
745
744
  }
746
745
  return attachments;
747
746
  }
747
+ function projectAttachments(metadata) {
748
+ return projectAttachmentList(metadata["attachments"]);
749
+ }
748
750
  function projectReferences(metadata) {
749
- const raw = metadata["references"];
751
+ return projectReferenceList(metadata["references"]);
752
+ }
753
+ function projectReferenceList(raw) {
750
754
  if (!Array.isArray(raw)) return [];
751
755
  const references = [];
752
756
  for (const entry of raw) {
@@ -765,6 +769,35 @@ function projectReferences(metadata) {
765
769
  }
766
770
  return references;
767
771
  }
772
+ function projectBroadcastMessage(value) {
773
+ const operation = "projectBroadcastMessage";
774
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
775
+ throw invalidResponse(operation, "broadcast payload was not an object");
776
+ }
777
+ const record = value;
778
+ const kindRaw = str(record, "kind") ?? str(record, "messageType") ?? "text";
779
+ const stateRaw = str(record, "deliveryState") ?? str(record, "status") ?? "sent";
780
+ const metadata = jsonObject(record["metadata"]);
781
+ return {
782
+ id: requiredStr(record, "id", operation),
783
+ conversationId: requiredStr(record, "conversationId", operation),
784
+ senderId: requiredStr(record, "senderId", operation),
785
+ organizationId: requiredStr(record, "organizationId", operation),
786
+ content: typeof record["content"] === "string" ? record["content"] : "",
787
+ kind: MESSAGE_KINDS.has(kindRaw) ? kindRaw : "text",
788
+ deliveryState: DELIVERY_STATES.has(stateRaw) ? stateRaw : "sent",
789
+ replyToId: str(record, "replyToId"),
790
+ clientMessageId: str(record, "clientMessageId"),
791
+ createdAt: requiredStr(record, "createdAt", operation),
792
+ editedAt: str(record, "editedAt"),
793
+ deletedAt: str(record, "deletedAt"),
794
+ deletedForEveryone: bool(record, "deletedForEveryone", false),
795
+ action: projectMessageAction(record["action"]),
796
+ attachments: projectAttachmentList(record["attachments"]),
797
+ references: projectReferenceList(record["references"]),
798
+ metadata
799
+ };
800
+ }
768
801
  function projectMessage(row, fallbackOrganizationId) {
769
802
  const operation = "projectMessage";
770
803
  const metadata = jsonObject(row["metadata"]);
@@ -1238,6 +1271,7 @@ function createMessagingEngine(options) {
1238
1271
  store,
1239
1272
  outbox,
1240
1273
  identity,
1274
+ reportDiagnostic: report,
1241
1275
  async start() {
1242
1276
  if (options.archiveFilter !== void 0) {
1243
1277
  store.setArchiveFilter(options.archiveFilter);
@@ -1379,8 +1413,11 @@ function createMessagingEngine(options) {
1379
1413
  {
1380
1414
  event: MESSAGING_EVENTS.message,
1381
1415
  onMessage: ({ data }) => {
1382
- if (typeof data !== "object" || data === null) return;
1383
- store.ingest(data);
1416
+ try {
1417
+ store.ingest(projectBroadcastMessage(data));
1418
+ } catch (error) {
1419
+ reportError(error, "broadcastMessage");
1420
+ }
1384
1421
  }
1385
1422
  }
1386
1423
  ],
@@ -2289,6 +2326,7 @@ function MessagingRuntime(props) {
2289
2326
  }, [renderers]);
2290
2327
  const renderReference = props.renderReference ?? null;
2291
2328
  const renderFence = props.renderFence ?? null;
2329
+ const renderAttachment = props.renderAttachment ?? null;
2292
2330
  const wrapMessage = props.wrapMessage ?? null;
2293
2331
  const wrapConversationRow = props.wrapConversationRow ?? null;
2294
2332
  const host = useMemo(() => {
@@ -2302,6 +2340,7 @@ function MessagingRuntime(props) {
2302
2340
  actionRenderers: rendererMap,
2303
2341
  renderReference,
2304
2342
  renderFence,
2343
+ renderAttachment,
2305
2344
  wrapMessage,
2306
2345
  wrapConversationRow
2307
2346
  };
@@ -2312,6 +2351,7 @@ function MessagingRuntime(props) {
2312
2351
  rendererMap,
2313
2352
  renderReference,
2314
2353
  renderFence,
2354
+ renderAttachment,
2315
2355
  wrapMessage,
2316
2356
  wrapConversationRow
2317
2357
  ]);
@@ -2342,7 +2382,7 @@ function useMessagingSnapshot() {
2342
2382
  }
2343
2383
 
2344
2384
  // src/react/hooks.ts
2345
- import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState } from "react";
2385
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState, useSyncExternalStore as useSyncExternalStore2 } from "react";
2346
2386
  import { useTyping, usePresence } from "@ai-matrx/realtime/react";
2347
2387
 
2348
2388
  // src/core/format.ts
@@ -2523,11 +2563,117 @@ function useConversation(id) {
2523
2563
  }
2524
2564
  };
2525
2565
  }
2566
+ var EMPTY_COMPOSER_SESSION = {
2567
+ drafts: {},
2568
+ replies: {},
2569
+ additions: {}
2570
+ };
2571
+ var emptyComposerSnapshot = () => EMPTY_COMPOSER_SESSION;
2572
+ var subscribeToNothing = () => () => void 0;
2573
+ var composerSessions = /* @__PURE__ */ new WeakMap();
2574
+ var composerStorageFailures = /* @__PURE__ */ new WeakSet();
2575
+ function composerStorageKey(engine) {
2576
+ return `ai-matrx.messaging.composer.${engine.identity.userId}`;
2577
+ }
2578
+ function readComposerSession(engine) {
2579
+ try {
2580
+ if (typeof sessionStorage === "undefined") return EMPTY_COMPOSER_SESSION;
2581
+ const serialized = sessionStorage.getItem(composerStorageKey(engine));
2582
+ if (serialized === null) return EMPTY_COMPOSER_SESSION;
2583
+ const parsed = JSON.parse(serialized);
2584
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2585
+ throw new Error("saved composer state was not an object");
2586
+ }
2587
+ const source = parsed;
2588
+ const drafts = {};
2589
+ if (typeof source["drafts"] === "object" && source["drafts"] !== null) {
2590
+ for (const [id, value] of Object.entries(source["drafts"])) {
2591
+ if (typeof value === "string") drafts[id] = value;
2592
+ }
2593
+ }
2594
+ const replies = {};
2595
+ if (typeof source["replies"] === "object" && source["replies"] !== null) {
2596
+ for (const [id, value] of Object.entries(source["replies"])) {
2597
+ if (value === null) replies[id] = null;
2598
+ else replies[id] = projectBroadcastMessage(value);
2599
+ }
2600
+ }
2601
+ const additions = {};
2602
+ if (typeof source["additions"] === "object" && source["additions"] !== null) {
2603
+ for (const [id, value] of Object.entries(source["additions"])) {
2604
+ if (!Array.isArray(value)) continue;
2605
+ additions[id] = value.filter((item) => {
2606
+ if (typeof item !== "object" || item === null || Array.isArray(item)) return false;
2607
+ const record = item;
2608
+ return typeof record["id"] === "string" && typeof record["label"] === "string";
2609
+ });
2610
+ }
2611
+ }
2612
+ return { drafts, replies, additions };
2613
+ } catch (error) {
2614
+ if (!composerStorageFailures.has(engine)) {
2615
+ composerStorageFailures.add(engine);
2616
+ engine.reportDiagnostic({
2617
+ level: "warn",
2618
+ message: `A saved message draft could not be restored: ${String(error?.message ?? error)}`,
2619
+ remedy: "The current draft remains usable. Re-add any missing attachments before sending."
2620
+ });
2621
+ }
2622
+ return EMPTY_COMPOSER_SESSION;
2623
+ }
2624
+ }
2625
+ function writeComposerSession(engine, snapshot) {
2626
+ try {
2627
+ if (typeof sessionStorage === "undefined") return;
2628
+ sessionStorage.setItem(composerStorageKey(engine), JSON.stringify(snapshot));
2629
+ } catch (error) {
2630
+ if (!composerStorageFailures.has(engine)) {
2631
+ composerStorageFailures.add(engine);
2632
+ engine.reportDiagnostic({
2633
+ level: "warn",
2634
+ message: `The current message draft could not be saved for reload recovery: ${String(error?.message ?? error)}`,
2635
+ remedy: "Keep this tab open until the message is sent, or copy the draft before reloading."
2636
+ });
2637
+ }
2638
+ }
2639
+ }
2640
+ function getComposerSession(engine) {
2641
+ const existing = composerSessions.get(engine);
2642
+ if (existing !== void 0) return existing;
2643
+ let snapshot = readComposerSession(engine);
2644
+ const listeners = /* @__PURE__ */ new Set();
2645
+ const store = {
2646
+ getSnapshot: () => snapshot,
2647
+ subscribe: (listener) => {
2648
+ listeners.add(listener);
2649
+ return () => listeners.delete(listener);
2650
+ },
2651
+ update: (change) => {
2652
+ snapshot = change(snapshot);
2653
+ writeComposerSession(engine, snapshot);
2654
+ listeners.forEach((listener) => listener());
2655
+ }
2656
+ };
2657
+ composerSessions.set(engine, store);
2658
+ return store;
2659
+ }
2526
2660
  function useComposer(conversationId) {
2527
2661
  const host = useMessagingHost();
2528
- const [drafts, setDrafts] = useState({});
2529
- const [replyTo, setReplyTo] = useState(null);
2530
- const value = conversationId === null ? "" : drafts[conversationId] ?? "";
2662
+ const session = host === null ? null : getComposerSession(host.engine);
2663
+ const composer = useSyncExternalStore2(
2664
+ session?.subscribe ?? subscribeToNothing,
2665
+ session?.getSnapshot ?? emptyComposerSnapshot,
2666
+ emptyComposerSnapshot
2667
+ );
2668
+ const replyTo = conversationId === null ? null : composer.replies[conversationId] ?? null;
2669
+ const additions = conversationId === null ? [] : composer.additions[conversationId] ?? [];
2670
+ const setReplyTo = (message) => {
2671
+ if (conversationId !== null) session?.update((current) => ({
2672
+ ...current,
2673
+ replies: { ...current.replies, [conversationId]: message }
2674
+ }));
2675
+ };
2676
+ const value = conversationId === null ? "" : composer.drafts[conversationId] ?? "";
2531
2677
  const typing = useTyping(
2532
2678
  {
2533
2679
  topic: conversationId === null ? "" : conversationTopic(conversationId),
@@ -2538,26 +2684,57 @@ function useComposer(conversationId) {
2538
2684
  const setValue = useCallback(
2539
2685
  (next) => {
2540
2686
  if (conversationId === null) return;
2541
- setDrafts((current) => ({ ...current, [conversationId]: next }));
2687
+ session?.update((current) => ({
2688
+ ...current,
2689
+ drafts: { ...current.drafts, [conversationId]: next }
2690
+ }));
2542
2691
  },
2543
- [conversationId]
2692
+ [conversationId, session]
2544
2693
  );
2545
2694
  return {
2546
2695
  value,
2547
2696
  setValue,
2548
- canSend: value.trim().length > 0 && conversationId !== null && host !== null,
2697
+ additions,
2698
+ add: (item) => {
2699
+ if (conversationId !== null) session?.update((current) => ({
2700
+ ...current,
2701
+ additions: {
2702
+ ...current.additions,
2703
+ [conversationId]: [
2704
+ ...(current.additions[conversationId] ?? []).filter((existing) => existing.id !== item.id),
2705
+ item
2706
+ ]
2707
+ }
2708
+ }));
2709
+ },
2710
+ removeAddition: (id) => {
2711
+ if (conversationId !== null) session?.update((current) => ({
2712
+ ...current,
2713
+ additions: {
2714
+ ...current.additions,
2715
+ [conversationId]: (current.additions[conversationId] ?? []).filter((item) => item.id !== id)
2716
+ }
2717
+ }));
2718
+ },
2719
+ canSend: (value.trim().length > 0 || additions.length > 0) && conversationId !== null && host !== null,
2549
2720
  send: () => {
2550
- if (host === null || conversationId === null) return;
2551
- const content = value.trim();
2552
- if (content.length === 0) return;
2721
+ if (host === null || conversationId === null || session === null) return;
2722
+ const content = [value.trim(), ...additions.map((item) => item.content ?? "")].filter(Boolean).join("\n\n");
2723
+ const attachments = additions.flatMap((item) => item.attachment ? [item.attachment] : []);
2724
+ if (content.length === 0 && attachments.length === 0) return;
2553
2725
  const draft = {
2554
2726
  conversationId,
2555
2727
  content,
2728
+ attachments,
2556
2729
  ...replyTo !== null ? { replyToId: replyTo.id } : {}
2557
2730
  };
2558
2731
  host.engine.send(draft);
2559
- setDrafts((current) => ({ ...current, [conversationId]: "" }));
2560
- setReplyTo(null);
2732
+ session.update((current) => ({
2733
+ ...current,
2734
+ drafts: { ...current.drafts, [conversationId]: "" },
2735
+ replies: { ...current.replies, [conversationId]: null },
2736
+ additions: { ...current.additions, [conversationId]: [] }
2737
+ }));
2561
2738
  typing.stopTyping();
2562
2739
  },
2563
2740
  onKeystroke: typing.onKeystroke,
@@ -3073,6 +3250,7 @@ function ConversationList(props) {
3073
3250
  } = useConversations();
3074
3251
  const RowChrome = useMessagingHost()?.wrapConversationRow ?? null;
3075
3252
  const [query, setQuery] = useState2("");
3253
+ const favoritePageRequestCount = useRef3(null);
3076
3254
  const showingArchive = archiveFilter === "archived";
3077
3255
  const archiveReveal = useMemo3(() => {
3078
3256
  if (showingArchive) {
@@ -3098,6 +3276,51 @@ function ConversationList(props) {
3098
3276
  (item) => item.displayName.toLowerCase().includes(needle) || (item.lastMessageContent ?? "").toLowerCase().includes(needle)
3099
3277
  );
3100
3278
  }, [conversations, query]);
3279
+ const loadNextPage = () => {
3280
+ if (favoritePageRequestCount.current === conversations.length) return;
3281
+ favoritePageRequestCount.current = conversations.length;
3282
+ loadMore();
3283
+ };
3284
+ const hasUnloadedFavorites = (props.pinnedConversationIds ?? []).some(
3285
+ (id) => !conversations.some((item) => String(item.conversation.id) === id)
3286
+ );
3287
+ useEffect3(() => {
3288
+ if (!hasUnloadedFavorites || !hasMore || isInitialLoading || loadError !== null || favoritePageRequestCount.current === conversations.length) {
3289
+ return;
3290
+ }
3291
+ loadNextPage();
3292
+ }, [
3293
+ conversations,
3294
+ hasMore,
3295
+ hasUnloadedFavorites,
3296
+ isInitialLoading,
3297
+ loadError,
3298
+ loadMore,
3299
+ loadNextPage
3300
+ ]);
3301
+ const { favoriteItems, otherPinnedItems, unpinnedItems } = useMemo3(() => {
3302
+ const visibleById = new Map(
3303
+ visible.map((item) => [String(item.conversation.id), item])
3304
+ );
3305
+ const pinnedItems = (props.pinnedConversationIds ?? []).map((id) => visibleById.get(id)).filter((item) => item !== void 0);
3306
+ const uniquePinnedItems = Array.from(
3307
+ new Map(pinnedItems.map((item) => [item.conversation.id, item])).values()
3308
+ ).sort((left, right) => {
3309
+ const byRecency = Date.parse(right.sortAt) - Date.parse(left.sortAt);
3310
+ return Number.isNaN(byRecency) || byRecency === 0 ? String(left.conversation.id).localeCompare(String(right.conversation.id)) : byRecency;
3311
+ });
3312
+ const pinnedIds = new Set(uniquePinnedItems.map((item) => item.conversation.id));
3313
+ return {
3314
+ favoriteItems: uniquePinnedItems.slice(0, 3),
3315
+ otherPinnedItems: uniquePinnedItems.slice(3),
3316
+ unpinnedItems: visible.filter((item) => !pinnedIds.has(item.conversation.id))
3317
+ };
3318
+ }, [visible, props.pinnedConversationIds]);
3319
+ const selectItem = (item) => {
3320
+ select(item.conversation.id);
3321
+ props.onSelect?.(item.conversation.id);
3322
+ };
3323
+ const wrapRow = (item, row) => RowChrome !== null ? /* @__PURE__ */ jsx4(RowChrome, { conversation: item, children: row }) : row;
3101
3324
  return /* @__PURE__ */ jsxs2("div", { className: `mx-msg__list${props.className !== void 0 ? ` ${props.className}` : ""}`, children: [
3102
3325
  /* @__PURE__ */ jsxs2("div", { className: "mx-msg__search", children: [
3103
3326
  /* @__PURE__ */ jsx4(SearchIcon, { style: { color: "var(--mx-msg-text-faint)" } }),
@@ -3153,33 +3376,67 @@ function ConversationList(props) {
3153
3376
  title: query.length > 0 ? "No matches" : showingArchive ? "No archived conversations" : "No conversations yet",
3154
3377
  body: query.length > 0 ? "Try a different name or word." : showingArchive ? "Archiving a conversation moves it here." : "Start one and it will appear here."
3155
3378
  }
3156
- ) : /* @__PURE__ */ jsx4("ul", { className: "mx-msg__rows", children: visible.map((item) => {
3157
- const row = /* @__PURE__ */ jsx4(
3158
- ConversationRow,
3159
- {
3160
- summary: item,
3161
- isActive: item.conversation.id === activeConversationId,
3162
- onSelect: () => {
3163
- select(item.conversation.id);
3164
- props.onSelect?.(item.conversation.id);
3379
+ ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
3380
+ favoriteItems.length > 0 ? /* @__PURE__ */ jsx4("ul", { className: "mx-msg__favorites", "aria-label": "Favorite conversations", children: favoriteItems.map((item) => {
3381
+ const row = /* @__PURE__ */ jsx4(
3382
+ ConversationFavorite,
3383
+ {
3384
+ summary: item,
3385
+ isActive: item.conversation.id === activeConversationId,
3386
+ onSelect: () => selectItem(item)
3165
3387
  }
3166
- }
3167
- );
3168
- return /* @__PURE__ */ jsx4("li", { children: RowChrome !== null ? /* @__PURE__ */ jsx4(RowChrome, { conversation: item, children: row }) : row }, item.conversation.id);
3169
- }) }),
3388
+ );
3389
+ return /* @__PURE__ */ jsx4("li", { children: wrapRow(item, row) }, item.conversation.id);
3390
+ }) }) : null,
3391
+ /* @__PURE__ */ jsx4("ul", { className: "mx-msg__rows", children: [...otherPinnedItems, ...unpinnedItems].map((item) => {
3392
+ const row = /* @__PURE__ */ jsx4(
3393
+ ConversationRow,
3394
+ {
3395
+ summary: item,
3396
+ isActive: item.conversation.id === activeConversationId,
3397
+ onSelect: () => selectItem(item)
3398
+ }
3399
+ );
3400
+ return /* @__PURE__ */ jsx4("li", { children: wrapRow(item, row) }, item.conversation.id);
3401
+ }) })
3402
+ ] }),
3170
3403
  hasMore && !isInitialLoading ? /* @__PURE__ */ jsx4(
3171
3404
  "button",
3172
3405
  {
3173
3406
  type: "button",
3174
3407
  className: "mx-msg__chip",
3175
3408
  style: { margin: "8px auto", display: "block" },
3176
- onClick: loadMore,
3409
+ onClick: loadNextPage,
3177
3410
  children: "Load older conversations"
3178
3411
  }
3179
3412
  ) : null
3180
3413
  ] })
3181
3414
  ] });
3182
3415
  }
3416
+ function ConversationFavorite(props) {
3417
+ const { summary } = props;
3418
+ return /* @__PURE__ */ jsxs2(
3419
+ "button",
3420
+ {
3421
+ type: "button",
3422
+ className: "mx-msg__favorite",
3423
+ "aria-current": props.isActive,
3424
+ "aria-label": `Open favorite conversation with ${summary.displayName}`,
3425
+ onClick: props.onSelect,
3426
+ children: [
3427
+ /* @__PURE__ */ jsx4(
3428
+ Avatar,
3429
+ {
3430
+ name: summary.displayName,
3431
+ imageUrl: summary.displayImageUrl,
3432
+ seed: summary.conversation.id
3433
+ }
3434
+ ),
3435
+ /* @__PURE__ */ jsx4("span", { className: "mx-msg__favorite-name", children: summary.displayName })
3436
+ ]
3437
+ }
3438
+ );
3439
+ }
3183
3440
  function ConversationRow(props) {
3184
3441
  const { summary } = props;
3185
3442
  return /* @__PURE__ */ jsxs2(
@@ -3354,6 +3611,8 @@ function ConversationView(props) {
3354
3611
  /* @__PURE__ */ jsx4(
3355
3612
  MessageGroupView,
3356
3613
  {
3614
+ allMessages: messages,
3615
+ showAuthor: conversation.summary?.conversation.type !== "direct",
3357
3616
  messages: group.messages,
3358
3617
  actor,
3359
3618
  isMine,
@@ -3385,37 +3644,41 @@ function MessageGroupView(props) {
3385
3644
  const first = props.messages[0];
3386
3645
  if (first === void 0) return null;
3387
3646
  const actor = props.actor;
3388
- return /* @__PURE__ */ jsxs2("div", { className: `mx-msg__group${props.isMine ? " mx-msg__group--mine" : ""}`, children: [
3389
- /* @__PURE__ */ jsx4(
3390
- Avatar,
3391
- {
3392
- name: actor.displayName,
3393
- imageUrl: actor.avatarUrl,
3394
- seed: actor.isAgent ? `agent:${actor.displayName}` : actor.principalUserId,
3395
- small: true
3396
- }
3397
- ),
3398
- /* @__PURE__ */ jsxs2("div", { className: "mx-msg__group-body", children: [
3399
- !props.isMine || actor.isAgent ? /* @__PURE__ */ jsxs2("div", { className: "mx-msg__author", children: [
3400
- actor.displayName,
3401
- actor.isAgent ? /* @__PURE__ */ jsx4(AgentTag, {}) : null,
3402
- actor.onBehalfOfName !== null ? /* @__PURE__ */ jsxs2("span", { style: { fontWeight: 400, color: "var(--mx-msg-text-faint)" }, children: [
3403
- "via ",
3404
- actor.onBehalfOfName
3405
- ] }) : null
3406
- ] }) : null,
3407
- props.messages.map((message, index) => /* @__PURE__ */ jsx4(
3408
- MessageBubble,
3647
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
3648
+ /* @__PURE__ */ jsx4("div", { className: "mx-msg__group-time", children: formatMessageTime(first.createdAt) }),
3649
+ /* @__PURE__ */ jsxs2("div", { className: `mx-msg__group${props.isMine ? " mx-msg__group--mine" : ""}`, children: [
3650
+ props.showAuthor || actor.isAgent ? /* @__PURE__ */ jsx4(
3651
+ Avatar,
3409
3652
  {
3410
- message,
3411
- isMine: props.isMine,
3412
- isLast: index === props.messages.length - 1,
3413
- onRetry: props.onRetry,
3414
- onReply: props.onReply,
3415
- failedEntryId: message.clientMessageId !== null ? props.failedEntryIdByClientId.get(message.clientMessageId) ?? null : null
3416
- },
3417
- message.id
3418
- ))
3653
+ name: actor.displayName,
3654
+ imageUrl: actor.avatarUrl,
3655
+ seed: actor.isAgent ? `agent:${actor.displayName}` : actor.principalUserId,
3656
+ small: true
3657
+ }
3658
+ ) : null,
3659
+ /* @__PURE__ */ jsxs2("div", { className: "mx-msg__group-body", children: [
3660
+ props.showAuthor && !props.isMine || actor.isAgent ? /* @__PURE__ */ jsxs2("div", { className: "mx-msg__author", children: [
3661
+ actor.displayName,
3662
+ actor.isAgent ? /* @__PURE__ */ jsx4(AgentTag, {}) : null,
3663
+ actor.onBehalfOfName !== null ? /* @__PURE__ */ jsxs2("span", { style: { fontWeight: 400, color: "var(--mx-msg-text-faint)" }, children: [
3664
+ "via ",
3665
+ actor.onBehalfOfName
3666
+ ] }) : null
3667
+ ] }) : null,
3668
+ props.messages.map((message, index) => /* @__PURE__ */ jsx4(
3669
+ MessageBubble,
3670
+ {
3671
+ message,
3672
+ replyTo: props.allMessages.find((parent) => parent.id === message.replyToId),
3673
+ isMine: props.isMine,
3674
+ isLast: index === props.messages.length - 1,
3675
+ onRetry: props.onRetry,
3676
+ onReply: props.onReply,
3677
+ failedEntryId: message.clientMessageId !== null ? props.failedEntryIdByClientId.get(message.clientMessageId) ?? null : null
3678
+ },
3679
+ message.id
3680
+ ))
3681
+ ] })
3419
3682
  ] })
3420
3683
  ] });
3421
3684
  }
@@ -3436,7 +3699,12 @@ function MessageBubble(props) {
3436
3699
  message.deliveryState === "failed" ? "mx-msg__bubble--failed" : ""
3437
3700
  ].filter(Boolean).join(" ");
3438
3701
  const bubble = /* @__PURE__ */ jsxs2(Fragment, { children: [
3439
- /* @__PURE__ */ jsxs2("div", { className: classes, children: [
3702
+ /* @__PURE__ */ jsxs2("div", { className: classes, title: formatMessageTime(message.createdAt), children: [
3703
+ message.replyToId !== null ? /* @__PURE__ */ jsx4("blockquote", { className: "mx-msg__quoted-reply", children: props.replyTo ? props.replyTo.deletedAt ? "Message deleted" : summarizeText(props.replyTo.content, 180) || "Attachment" : "Earlier message" }) : null,
3704
+ message.attachments.map((attachment) => {
3705
+ const RenderAttachment = props.renderAttachment ?? host?.renderAttachment;
3706
+ return RenderAttachment ? /* @__PURE__ */ jsx4(RenderAttachment, { attachment }, attachment.fileId) : /* @__PURE__ */ jsx4(ReferenceCard, { reference: { entityType: "file", entityId: attachment.fileId, label: attachment.fileName }, onOpen: host?.openReference ?? null }, attachment.fileId);
3707
+ }),
3440
3708
  splitText(message.content).map(
3441
3709
  (segment, index) => segment.type === "text" ? /* @__PURE__ */ jsx4("div", { className: "mx-msg__markdown", children: /* @__PURE__ */ jsx4(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], children: segment.value }) }, index) : segment.type === "fence" ? HostFence !== null ? /* @__PURE__ */ jsx4(HostFence, { body: segment.body }, `fence:${index}`) : /* @__PURE__ */ jsx4(UnreadableReference, {}, `fence:${index}`) : HostReference !== null ? /* @__PURE__ */ jsx4(
3442
3710
  HostReference,
@@ -3476,7 +3744,7 @@ function MessageBubble(props) {
3476
3744
  {
3477
3745
  className: `mx-msg__meta${message.deliveryState === "failed" ? " mx-msg__meta--failed" : ""}`,
3478
3746
  children: [
3479
- props.isLast ? formatMessageTime(message.createdAt) : null,
3747
+ /* @__PURE__ */ jsx4("span", { className: "mx-msg__message-time", children: props.isLast ? formatMessageTime(message.createdAt) : null }),
3480
3748
  message.editedAt !== null ? " \xB7 edited" : null,
3481
3749
  isMine ? /* @__PURE__ */ jsx4(DeliveryTick, { state: message.deliveryState }) : null,
3482
3750
  message.deliveryState === "failed" && props.failedEntryId != null && props.onRetry !== void 0 ? /* @__PURE__ */ jsx4(
@@ -3571,6 +3839,9 @@ function ComposerView(props) {
3571
3839
  )
3572
3840
  ] }) : null,
3573
3841
  props.renderInput !== void 0 ? props.renderInput({
3842
+ additions: composer.additions,
3843
+ add: composer.add,
3844
+ removeAddition: composer.removeAddition,
3574
3845
  value: composer.value,
3575
3846
  disabled: props.disabled,
3576
3847
  canSend: composer.canSend,