@ai-matrx/messaging 0.1.0 → 0.1.2
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/CHANGELOG.md +58 -1
- package/dist/index.cjs +27 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +27 -14
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +44 -19
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +16 -0
- package/dist/react.d.ts +16 -0
- package/dist/react.js +44 -19
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.d.cts
CHANGED
|
@@ -672,6 +672,15 @@ interface MessagingStore {
|
|
|
672
672
|
ingestMany(messages: readonly Message[]): void;
|
|
673
673
|
removeMessage(conversationId: ConversationId, messageId: MessageId): void;
|
|
674
674
|
markConversationRead(id: ConversationId): void;
|
|
675
|
+
/**
|
|
676
|
+
* Move a conversation's preview + sort position to reflect a message, with NO
|
|
677
|
+
* refetch. Sending a message must not cost a conversation-list RPC — at a
|
|
678
|
+
* busy org that is one full page read per keystroke-burst, and the row's new
|
|
679
|
+
* state is entirely derivable from the message we already hold.
|
|
680
|
+
*/
|
|
681
|
+
applyLastMessage(message: Message, args?: {
|
|
682
|
+
incrementUnread?: boolean;
|
|
683
|
+
}): void;
|
|
675
684
|
setUnreadCount(id: ConversationId, count: number): void;
|
|
676
685
|
}
|
|
677
686
|
declare function createMessagingStore(): MessagingStore;
|
|
@@ -934,6 +943,13 @@ declare function MessageBubble(props: {
|
|
|
934
943
|
declare function MessageActionChips(props: {
|
|
935
944
|
message: Message;
|
|
936
945
|
}): ReactNode;
|
|
946
|
+
/**
|
|
947
|
+
* The standalone composer: it owns its own state. Inside `ConversationView` the
|
|
948
|
+
* VIEW owns it instead (`ComposerView` below) — two `useComposer` instances on
|
|
949
|
+
* one conversation are two independent drafts, two independent reply targets,
|
|
950
|
+
* and — since typing rides presence — two publishers fighting over ONE presence
|
|
951
|
+
* entry, which `@ai-matrx/realtime` warns about by name.
|
|
952
|
+
*/
|
|
937
953
|
declare function Composer(props: {
|
|
938
954
|
conversationId: ConversationId | null;
|
|
939
955
|
}): ReactNode;
|
package/dist/react.d.ts
CHANGED
|
@@ -672,6 +672,15 @@ interface MessagingStore {
|
|
|
672
672
|
ingestMany(messages: readonly Message[]): void;
|
|
673
673
|
removeMessage(conversationId: ConversationId, messageId: MessageId): void;
|
|
674
674
|
markConversationRead(id: ConversationId): void;
|
|
675
|
+
/**
|
|
676
|
+
* Move a conversation's preview + sort position to reflect a message, with NO
|
|
677
|
+
* refetch. Sending a message must not cost a conversation-list RPC — at a
|
|
678
|
+
* busy org that is one full page read per keystroke-burst, and the row's new
|
|
679
|
+
* state is entirely derivable from the message we already hold.
|
|
680
|
+
*/
|
|
681
|
+
applyLastMessage(message: Message, args?: {
|
|
682
|
+
incrementUnread?: boolean;
|
|
683
|
+
}): void;
|
|
675
684
|
setUnreadCount(id: ConversationId, count: number): void;
|
|
676
685
|
}
|
|
677
686
|
declare function createMessagingStore(): MessagingStore;
|
|
@@ -934,6 +943,13 @@ declare function MessageBubble(props: {
|
|
|
934
943
|
declare function MessageActionChips(props: {
|
|
935
944
|
message: Message;
|
|
936
945
|
}): ReactNode;
|
|
946
|
+
/**
|
|
947
|
+
* The standalone composer: it owns its own state. Inside `ConversationView` the
|
|
948
|
+
* VIEW owns it instead (`ComposerView` below) — two `useComposer` instances on
|
|
949
|
+
* one conversation are two independent drafts, two independent reply targets,
|
|
950
|
+
* and — since typing rides presence — two publishers fighting over ONE presence
|
|
951
|
+
* entry, which `@ai-matrx/realtime` warns about by name.
|
|
952
|
+
*/
|
|
937
953
|
declare function Composer(props: {
|
|
938
954
|
conversationId: ConversationId | null;
|
|
939
955
|
}): ReactNode;
|
package/dist/react.js
CHANGED
|
@@ -966,6 +966,29 @@ function createMessagingStore() {
|
|
|
966
966
|
});
|
|
967
967
|
emit();
|
|
968
968
|
},
|
|
969
|
+
applyLastMessage(message, args = {}) {
|
|
970
|
+
const existing = conversations.find(
|
|
971
|
+
(item) => item.conversation.id === message.conversationId
|
|
972
|
+
);
|
|
973
|
+
if (existing === void 0) return;
|
|
974
|
+
if (message.deliveryState === "sending" || message.deletedAt !== null) return;
|
|
975
|
+
if (existing.lastMessageAt !== null && message.createdAt < existing.lastMessageAt) return;
|
|
976
|
+
const isActive = activeConversationId === message.conversationId;
|
|
977
|
+
const shouldCount = args.incrementUnread === true && !isActive;
|
|
978
|
+
conversations = normalizeConversations(
|
|
979
|
+
conversations.map(
|
|
980
|
+
(item) => item.conversation.id === message.conversationId ? {
|
|
981
|
+
...item,
|
|
982
|
+
lastMessageContent: message.content,
|
|
983
|
+
lastMessageSenderId: message.senderId,
|
|
984
|
+
lastMessageAt: message.createdAt,
|
|
985
|
+
sortAt: message.createdAt,
|
|
986
|
+
unreadCount: shouldCount ? item.unreadCount + 1 : item.unreadCount
|
|
987
|
+
} : item
|
|
988
|
+
)
|
|
989
|
+
);
|
|
990
|
+
emit();
|
|
991
|
+
},
|
|
969
992
|
markConversationRead(id) {
|
|
970
993
|
conversations = conversations.map(
|
|
971
994
|
(item) => item.conversation.id === id ? { ...item, unreadCount: 0 } : item
|
|
@@ -1036,7 +1059,7 @@ function createMessagingEngine(options) {
|
|
|
1036
1059
|
onSent: (entry, message) => {
|
|
1037
1060
|
store.ingest(message);
|
|
1038
1061
|
broadcastMessage(message);
|
|
1039
|
-
|
|
1062
|
+
store.applyLastMessage(message);
|
|
1040
1063
|
void entry;
|
|
1041
1064
|
},
|
|
1042
1065
|
onChange: (entries) => {
|
|
@@ -1062,15 +1085,6 @@ function createMessagingEngine(options) {
|
|
|
1062
1085
|
function broadcastMessage(message) {
|
|
1063
1086
|
openChannels.get(message.conversationId)?.send(MESSAGING_EVENTS.message, message);
|
|
1064
1087
|
}
|
|
1065
|
-
async function refreshConversationRow(id) {
|
|
1066
|
-
try {
|
|
1067
|
-
const page = await repository.listConversations({ limit: conversationPageSize });
|
|
1068
|
-
const row = page.items.find((item) => item.conversation.id === id);
|
|
1069
|
-
if (row !== void 0) store.upsertConversation(row);
|
|
1070
|
-
} catch (error) {
|
|
1071
|
-
reportError(error, "refreshConversationRow");
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
1088
|
async function reloadInbox() {
|
|
1075
1089
|
const page = await repository.listConversations({ limit: conversationPageSize });
|
|
1076
1090
|
conversationCursor = page.nextCursor;
|
|
@@ -1121,10 +1135,9 @@ function createMessagingEngine(options) {
|
|
|
1121
1135
|
return;
|
|
1122
1136
|
}
|
|
1123
1137
|
store.ingest(message);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
void refreshConversationRow(message.conversationId);
|
|
1138
|
+
const isMine = message.senderId === identity.userId;
|
|
1139
|
+
store.applyLastMessage(message, { incrementUnread: !isMine });
|
|
1140
|
+
if (!isMine) options.onIncoming?.(message);
|
|
1128
1141
|
}
|
|
1129
1142
|
},
|
|
1130
1143
|
{
|
|
@@ -2016,7 +2029,12 @@ function useTypists(conversationId, participants) {
|
|
|
2016
2029
|
const typing = useTyping(
|
|
2017
2030
|
{
|
|
2018
2031
|
topic: conversationId === null ? "" : conversationTopic(conversationId),
|
|
2019
|
-
identity: { userId: host?.identity.userId ?? "" }
|
|
2032
|
+
identity: { userId: host?.identity.userId ?? "" },
|
|
2033
|
+
// READ-ONLY. This label and `useComposer` sit on the SAME topic, which is
|
|
2034
|
+
// one channel carrying ONE presence entry per key — so exactly one of
|
|
2035
|
+
// them publishes, and it is the composer that actually knows whether this
|
|
2036
|
+
// user is typing. Publishing here would overwrite that on every heartbeat.
|
|
2037
|
+
publish: false
|
|
2020
2038
|
},
|
|
2021
2039
|
{ enabled: conversationId !== null && host !== null }
|
|
2022
2040
|
);
|
|
@@ -2043,7 +2061,10 @@ function useOnlineUserIds(conversationId) {
|
|
|
2043
2061
|
const presence = usePresence(
|
|
2044
2062
|
{
|
|
2045
2063
|
topic: conversationId === null ? "" : conversationTopic(conversationId),
|
|
2046
|
-
|
|
2064
|
+
// READ-ONLY, for the same reason: `useComposer`'s typing presence already
|
|
2065
|
+
// puts this user in the roster under this key with their `userId`, and a
|
|
2066
|
+
// second publisher on one entry just fights it.
|
|
2067
|
+
presence: { publish: false, state: { userId: host?.identity.userId ?? "" } }
|
|
2047
2068
|
},
|
|
2048
2069
|
{ enabled: conversationId !== null && host !== null }
|
|
2049
2070
|
);
|
|
@@ -2124,7 +2145,7 @@ function useMessagingAi(conversationId) {
|
|
|
2124
2145
|
}
|
|
2125
2146
|
function useMessageAction(message) {
|
|
2126
2147
|
const host = useRequiredMessagingHost();
|
|
2127
|
-
const action = message.action;
|
|
2148
|
+
const action = message.action ?? null;
|
|
2128
2149
|
const [receipt, setReceipt] = useState(
|
|
2129
2150
|
() => action === null ? null : host.actions.receiptFor(action.kind, message.id, host.identity.userId)
|
|
2130
2151
|
);
|
|
@@ -2656,7 +2677,7 @@ function ConversationView(props) {
|
|
|
2656
2677
|
/* @__PURE__ */ jsx4(TypingDots, {}),
|
|
2657
2678
|
typists.label
|
|
2658
2679
|
] }) : null }),
|
|
2659
|
-
/* @__PURE__ */ jsx4(
|
|
2680
|
+
/* @__PURE__ */ jsx4(ComposerView, { composer, disabled: false })
|
|
2660
2681
|
] });
|
|
2661
2682
|
}
|
|
2662
2683
|
function MessageGroupView(props) {
|
|
@@ -2789,6 +2810,10 @@ function MessageActionChips(props) {
|
|
|
2789
2810
|
}
|
|
2790
2811
|
function Composer(props) {
|
|
2791
2812
|
const composer = useComposer(props.conversationId);
|
|
2813
|
+
return /* @__PURE__ */ jsx4(ComposerView, { composer, disabled: props.conversationId === null });
|
|
2814
|
+
}
|
|
2815
|
+
function ComposerView(props) {
|
|
2816
|
+
const composer = props.composer;
|
|
2792
2817
|
const textareaRef = useRef3(null);
|
|
2793
2818
|
useEffect3(() => {
|
|
2794
2819
|
const node = textareaRef.current;
|
|
@@ -2822,7 +2847,7 @@ function Composer(props) {
|
|
|
2822
2847
|
value: composer.value,
|
|
2823
2848
|
placeholder: "Message",
|
|
2824
2849
|
"aria-label": "Message",
|
|
2825
|
-
disabled: props.
|
|
2850
|
+
disabled: props.disabled,
|
|
2826
2851
|
onChange: (event) => {
|
|
2827
2852
|
composer.setValue(event.target.value);
|
|
2828
2853
|
composer.onKeystroke();
|