@marktiderman/genesis-chat 0.1.1 → 0.2.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/README.md +304 -13
- package/dist/{chunk-SAY24E6V.js → chunk-5D5A7I4Q.js} +166 -45
- package/dist/chunk-5D5A7I4Q.js.map +1 -0
- package/dist/{chunk-JQXZUEEL.js → chunk-7S6HYNWW.js} +382 -8
- package/dist/chunk-7S6HYNWW.js.map +1 -0
- package/dist/chunk-BJCE5O7V.js +70 -0
- package/dist/chunk-BJCE5O7V.js.map +1 -0
- package/dist/chunk-PZTOY2LY.js +576 -0
- package/dist/chunk-PZTOY2LY.js.map +1 -0
- package/dist/chunk-VEPZROQB.js +187 -0
- package/dist/chunk-VEPZROQB.js.map +1 -0
- package/dist/{client-BtdALFCx.d.ts → client-DMa2j7kz.d.ts} +152 -11
- package/dist/index.d.ts +78 -163
- package/dist/index.js +3 -2
- package/dist/native.d.ts +1910 -1
- package/dist/native.js +4900 -0
- package/dist/native.js.map +1 -1
- package/dist/outbox-B0y3phE0.d.ts +151 -0
- package/dist/push.d.ts +206 -0
- package/dist/push.js +3 -0
- package/dist/push.js.map +1 -0
- package/dist/react.d.ts +149 -7
- package/dist/react.js +3 -3
- package/dist/server.d.ts +510 -21
- package/dist/server.js +863 -21
- package/dist/server.js.map +1 -1
- package/dist/thread-DWXHKPdw.d.ts +57 -0
- package/dist/web.d.ts +313 -10
- package/dist/web.js +1731 -60
- package/dist/web.js.map +1 -1
- package/migrations/0002_genesis_chat_coaching.sql +934 -0
- package/migrations/tests/0002_genesis_chat_coaching.test.sql +265 -0
- package/package.json +74 -6
- package/src/__tests__/coaching-core.test.ts +332 -0
- package/src/__tests__/coaching-react.test.tsx +248 -0
- package/src/__tests__/coaching-web.test.tsx +449 -0
- package/src/__tests__/core.test.ts +91 -0
- package/src/__tests__/outbox.test.ts +122 -0
- package/src/__tests__/realtime.test.ts +70 -0
- package/src/__tests__/review-fixes-core.test.ts +193 -0
- package/src/__tests__/review-fixes-web.test.tsx +402 -0
- package/src/__tests__/server-effects.test.ts +158 -0
- package/src/__tests__/server-fakes.ts +90 -0
- package/src/__tests__/server-integrations.test.ts +247 -0
- package/src/__tests__/server-providers.test.ts +246 -0
- package/src/__tests__/server-runner.test.ts +277 -0
- package/src/__tests__/server-safety.test.ts +290 -0
- package/src/__tests__/web-markdown.test.tsx +88 -0
- package/src/__tests__/web.test.tsx +333 -0
- package/src/core/availability.ts +223 -0
- package/src/core/client.ts +757 -0
- package/src/core/extras.ts +106 -0
- package/src/core/media.ts +144 -0
- package/src/core/outbox.ts +429 -0
- package/src/core/thread.ts +171 -0
- package/src/core/types.ts +311 -0
- package/src/index.ts +13 -0
- package/src/native/__tests__/auto-retry.test.tsx +128 -0
- package/src/native/__tests__/composer-mic-lock.test.tsx +115 -0
- package/src/native/__tests__/composer-state.test.ts +188 -0
- package/src/native/__tests__/composer.test.tsx +133 -0
- package/src/native/__tests__/emoji.test.tsx +238 -0
- package/src/native/__tests__/inbox-screen.test.tsx +48 -0
- package/src/native/__tests__/message-bubble.test.tsx +127 -0
- package/src/native/__tests__/organise-logic.test.ts +164 -0
- package/src/native/__tests__/organise-screens.test.tsx +344 -0
- package/src/native/__tests__/photo-viewer.test.tsx +226 -0
- package/src/native/__tests__/push-payload.test.ts +66 -0
- package/src/native/__tests__/push.test.ts +209 -0
- package/src/native/__tests__/recorder-machine.test.ts +96 -0
- package/src/native/__tests__/rows-actions.test.ts +76 -0
- package/src/native/__tests__/thread-layout.test.ts +41 -0
- package/src/native/__tests__/thread-screen.test.tsx +116 -0
- package/src/native/__tests__/tus.test.ts +154 -0
- package/src/native/__tests__/use-voice-recorder.test.tsx +316 -0
- package/src/native/__tests__/video-media.test.tsx +421 -0
- package/src/native/__tests__/video-recorder-state.test.ts +87 -0
- package/src/native/__tests__/video-upload.test.ts +227 -0
- package/src/native/access-error.tsx +31 -0
- package/src/native/actions.ts +42 -0
- package/src/native/attachment-cards.tsx +116 -0
- package/src/native/auto-retry.ts +69 -0
- package/src/native/availability.tsx +376 -0
- package/src/native/broadcast-sheet.tsx +193 -0
- package/src/native/composer-state.ts +302 -0
- package/src/native/composer.tsx +453 -0
- package/src/native/context.tsx +70 -0
- package/src/native/copy.ts +296 -0
- package/src/native/emoji-data.ts +21 -0
- package/src/native/emoji-picker.tsx +229 -0
- package/src/native/emoji.ts +118 -0
- package/src/native/icon-button.tsx +40 -0
- package/src/native/inbox-screen.tsx +116 -0
- package/src/native/index.ts +184 -0
- package/src/native/link-preview.tsx +104 -0
- package/src/native/media-colors.ts +15 -0
- package/src/native/media.tsx +187 -0
- package/src/native/message-actions-sheet.tsx +121 -0
- package/src/native/message-bubble.tsx +374 -0
- package/src/native/moderation.tsx +42 -0
- package/src/native/modules.ts +275 -0
- package/src/native/organise-copy.ts +195 -0
- package/src/native/photo-viewer.tsx +531 -0
- package/src/native/push.ts +375 -0
- package/src/native/recorder-machine.ts +176 -0
- package/src/native/rows.ts +81 -0
- package/src/native/schedule-sheet.tsx +314 -0
- package/src/native/search-screen.tsx +168 -0
- package/src/native/stack-options.ts +36 -0
- package/src/native/start-sheet.tsx +87 -0
- package/src/native/thread-screen.tsx +620 -0
- package/src/native/topics.tsx +179 -0
- package/src/native/tus.ts +282 -0
- package/src/native/use-composer.ts +71 -0
- package/src/native/use-jump.ts +91 -0
- package/src/native/use-voice-recorder.ts +234 -0
- package/src/native/video-player.tsx +370 -0
- package/src/native/video-recorder-state.ts +140 -0
- package/src/native/video-recorder.tsx +271 -0
- package/src/native/video-upload-tray.tsx +77 -0
- package/src/native/video-upload.ts +432 -0
- package/src/native/voice-player.tsx +283 -0
- package/src/react/index.tsx +810 -0
- package/src/server/assistant.ts +235 -0
- package/src/server/crypto.ts +86 -0
- package/src/server/expo.ts +119 -0
- package/src/server/handler.ts +32 -0
- package/src/server/index.ts +57 -0
- package/src/server/moderation.ts +155 -0
- package/src/server/mux.ts +290 -0
- package/src/server/notifications.ts +130 -0
- package/src/server/runner.ts +468 -0
- package/src/server/transcribers.ts +111 -0
- package/src/server/types.ts +171 -0
- package/src/server/unfurl.ts +480 -0
- package/src/server/upload.ts +158 -0
- package/src/web/attachments.tsx +379 -0
- package/src/web/availability.tsx +299 -0
- package/src/web/broadcast.tsx +166 -0
- package/src/web/bubble.tsx +563 -0
- package/src/web/cards.tsx +136 -0
- package/src/web/composer.tsx +571 -0
- package/src/web/emoji-picker.tsx +84 -0
- package/src/web/error-boundary.tsx +33 -0
- package/src/web/format.ts +55 -0
- package/src/web/inbox.tsx +195 -0
- package/src/web/index.ts +75 -0
- package/src/web/markdown.tsx +245 -0
- package/src/web/memory-client.ts +706 -0
- package/src/web/panel.tsx +135 -0
- package/src/web/schedule.tsx +192 -0
- package/src/web/search.tsx +129 -0
- package/src/web/thread-context.ts +64 -0
- package/src/web/thread.tsx +802 -0
- package/src/web/topics.tsx +160 -0
- package/src/web/use-voice-recorder.ts +264 -0
- package/src/web/video.tsx +522 -0
- package/src/web/voice-note.tsx +197 -0
- package/dist/chunk-27UTR5FW.js +0 -322
- package/dist/chunk-27UTR5FW.js.map +0 -1
- package/dist/chunk-JQXZUEEL.js.map +0 -1
- package/dist/chunk-SAY24E6V.js.map +0 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { q as ChatMessage, B as PendingMessage, g as ChatActivity } from './client-DMa2j7kz.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @feat:chat
|
|
5
|
+
* Pure thread logic, the same on every platform: merging the server's messages
|
|
6
|
+
* with ones still on their way, grouping a run of messages from one sender,
|
|
7
|
+
* day dividers, the unread line, reactions by emoji, and the typing line.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type ThreadItem = {
|
|
11
|
+
type: "message";
|
|
12
|
+
message: ChatMessage;
|
|
13
|
+
pending?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
type: "pending";
|
|
16
|
+
pending: PendingMessage;
|
|
17
|
+
message?: undefined;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The server's messages (by seq), then what is still sending, oldest first. A
|
|
21
|
+
* pending message the server already has (same clientId) is dropped, so a send
|
|
22
|
+
* that landed never shows twice.
|
|
23
|
+
*/
|
|
24
|
+
declare function mergeThread(server: ChatMessage[], pending: PendingMessage[]): ThreadItem[];
|
|
25
|
+
/** Adds or replaces messages in a page, keeping it sorted and free of duplicates. */
|
|
26
|
+
declare function upsertMessages(existing: ChatMessage[], incoming: ChatMessage[]): ChatMessage[];
|
|
27
|
+
declare function itemSender(item: ThreadItem): string | null;
|
|
28
|
+
declare function itemTime(item: ThreadItem): string;
|
|
29
|
+
/** Runs of one sender within five minutes share a header (name, avatar). */
|
|
30
|
+
declare function startsGroup(items: ThreadItem[], index: number, gapMs?: number): boolean;
|
|
31
|
+
declare function sameDay(a: string, b: string): boolean;
|
|
32
|
+
/** A day divider goes before this item. */
|
|
33
|
+
declare function startsDay(items: ThreadItem[], index: number): boolean;
|
|
34
|
+
/** "Today", "Yesterday", "Monday", or "12 Mar 2026". */
|
|
35
|
+
declare function dayLabel(iso: string, now?: Date, locale?: string): string;
|
|
36
|
+
/** The first message after what you had read, from someone else: where "New" goes. */
|
|
37
|
+
declare function firstUnreadId(messages: ChatMessage[], lastReadSeq: number, me: string): string | null;
|
|
38
|
+
interface ReactionSummary {
|
|
39
|
+
emoji: string;
|
|
40
|
+
count: number;
|
|
41
|
+
mine: boolean;
|
|
42
|
+
userIds: string[];
|
|
43
|
+
}
|
|
44
|
+
/** Reactions by emoji, most first, then in first-used order. */
|
|
45
|
+
declare function summarizeReactions(message: ChatMessage, me: string): ReactionSummary[];
|
|
46
|
+
/** Who is doing what now: activity older than six seconds has stopped. */
|
|
47
|
+
declare function currentActivity(activity: ChatActivity[], now?: number, staleMs?: number): ChatActivity[];
|
|
48
|
+
/** "Sam is typing…", "Sam and Lee are typing…", "Sam is recording a voice message…". */
|
|
49
|
+
declare function activityLine(activity: ChatActivity[], nameOf: (userId: string) => string): string | null;
|
|
50
|
+
/** Sends activity at most once every `everyMs`. */
|
|
51
|
+
declare function throttle(fn: () => void, everyMs?: number): () => void;
|
|
52
|
+
/** The ids @mentioned in markdown written as `@[Name](user:<uuid>)`. */
|
|
53
|
+
declare function parseMentions(body: string): string[];
|
|
54
|
+
/** A one-line preview: text, or what was attached. */
|
|
55
|
+
declare function previewText(body: string, attachment: string | null | undefined, deleted?: boolean): string;
|
|
56
|
+
|
|
57
|
+
export { type ReactionSummary as R, type ThreadItem as T, activityLine as a, itemTime as b, currentActivity as c, dayLabel as d, previewText as e, firstUnreadId as f, startsDay as g, startsGroup as h, itemSender as i, summarizeReactions as j, mergeThread as m, parseMentions as p, sameDay as s, throttle as t, upsertMessages as u };
|
package/dist/web.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ComponentType, ReactNode, Ref } from 'react';
|
|
2
|
+
import { ComponentType, ReactNode, Ref, Component, ErrorInfo } from 'react';
|
|
3
3
|
import { SlotOverride } from '@marktiderman/genesis-core/slots';
|
|
4
|
-
import {
|
|
5
|
-
import { ConversationState } from './react.js';
|
|
6
|
-
import { ThreadItem
|
|
4
|
+
import { q as ChatMessage, m as ChatInboxItem, o as ChatLinkPreview$1, h as ChatAttachment, k as ChatConversation, p as ChatMember, t as ChatRole, i as ChatClient, g as ChatActivity, C as ChatAvailability, x as ChatTopic, T as TopicInput, u as ChatSearchHit, E as SendInput, A as AttachmentInput } from './client-DMa2j7kz.js';
|
|
5
|
+
import { ChatAttachmentKind, ConversationState, ChatVideoUpload } from './react.js';
|
|
6
|
+
import { T as ThreadItem } from './thread-DWXHKPdw.js';
|
|
7
|
+
import { L as LocalFile } from './outbox-B0y3phE0.js';
|
|
7
8
|
import '@supabase/supabase-js';
|
|
8
9
|
import '@tanstack/query-core';
|
|
9
10
|
|
|
@@ -49,6 +50,46 @@ interface ChatInboxProps {
|
|
|
49
50
|
declare function inboxTitle(item: ChatInboxItem, resolveUser: ResolveUser, fallback?: (item: ChatInboxItem) => string): string;
|
|
50
51
|
declare function ChatInbox({ resolveUser, onSelect, selectedId, kind, archived, fallbackTitle, slots, className, ...rest }: ChatInboxProps): react.JSX.Element;
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Whether a link preview shows its image. The image lives on the linked site, so loading it
|
|
55
|
+
* straight from there tells that site each reader's IP address and when they read the message
|
|
56
|
+
* (a tracking pixel the sender controls). So:
|
|
57
|
+
* - `"off"` (the default): no image;
|
|
58
|
+
* - `"proxy"`: through your own image proxy, `proxyUrl(imageUrl)`; no image without it;
|
|
59
|
+
* - `"direct"`: from the linked site (only if you accept that).
|
|
60
|
+
*/
|
|
61
|
+
type LinkPreviewImages = "off" | "proxy" | "direct";
|
|
62
|
+
/** The image a preview may show under this setting, or null. Only https image URLs are ever considered. */
|
|
63
|
+
declare function linkPreviewImageSrc(imageUrl: string | null | undefined, images?: LinkPreviewImages, proxyUrl?: (url: string) => string | null | undefined): string | null;
|
|
64
|
+
interface ChatLinkPreviewProps {
|
|
65
|
+
preview: ChatLinkPreview$1;
|
|
66
|
+
/** Whether to show its image, and how; `"off"` by default (see LinkPreviewImages). */
|
|
67
|
+
images?: LinkPreviewImages;
|
|
68
|
+
/** Your image proxy, for `images: "proxy"`: the URL to load instead of the linked site's. */
|
|
69
|
+
proxyUrl?: (url: string) => string | null | undefined;
|
|
70
|
+
tone?: "default" | "primary";
|
|
71
|
+
className?: string;
|
|
72
|
+
}
|
|
73
|
+
declare function ChatLinkPreview({ preview, images, proxyUrl, tone, className }: ChatLinkPreviewProps): react.JSX.Element | null;
|
|
74
|
+
interface ChatAttachmentCardProps {
|
|
75
|
+
title: string;
|
|
76
|
+
subtitle?: string | null;
|
|
77
|
+
/** An icon component (lucide); a clipboard by default. */
|
|
78
|
+
icon?: ComponentType<{
|
|
79
|
+
className?: string;
|
|
80
|
+
}>;
|
|
81
|
+
/** One button: a link (`href`, safe schemes only) or a handler. */
|
|
82
|
+
action?: {
|
|
83
|
+
label: string;
|
|
84
|
+
href?: string;
|
|
85
|
+
onPress?: () => void;
|
|
86
|
+
} | null;
|
|
87
|
+
tone?: "default" | "primary";
|
|
88
|
+
className?: string;
|
|
89
|
+
}
|
|
90
|
+
/** A card for an app's own attachment kind: an icon, a title, a subtitle and one action. */
|
|
91
|
+
declare function ChatAttachmentCard({ title, subtitle, icon: Icon, action, tone, className }: ChatAttachmentCardProps): react.JSX.Element;
|
|
92
|
+
|
|
52
93
|
/** One attachment as the renderers draw it, sent or still uploading. */
|
|
53
94
|
interface AttachmentView {
|
|
54
95
|
key: string;
|
|
@@ -95,6 +136,7 @@ interface LightboxProps {
|
|
|
95
136
|
}
|
|
96
137
|
/** A photo full size, with the previous and next ones on the arrow keys. */
|
|
97
138
|
declare function Lightbox({ images, index, onIndexChange }: LightboxProps): react.JSX.Element;
|
|
139
|
+
/** A video: a hosted one (Mux) streams once ready and says "Processing…" until then; a file plays from storage. */
|
|
98
140
|
declare function VideoAttachment({ view }: {
|
|
99
141
|
view: AttachmentView;
|
|
100
142
|
}): react.JSX.Element;
|
|
@@ -109,9 +151,11 @@ interface MessageAttachmentsProps {
|
|
|
109
151
|
tone: "default" | "primary";
|
|
110
152
|
/** For kinds the package doesn't draw; a generic card by default. */
|
|
111
153
|
slot?: ComponentType<ChatAttachmentSlotProps> | null;
|
|
154
|
+
/** Cards for registered app kinds (ChatProvider `attachmentKinds`), drawn before the slot. */
|
|
155
|
+
kinds?: Record<string, ChatAttachmentKind>;
|
|
112
156
|
}
|
|
113
157
|
/** Every attachment of one message, photos grouped into one grid. */
|
|
114
|
-
declare function MessageAttachments({ views, message, mine, tone, slot }: MessageAttachmentsProps): react.JSX.Element | null;
|
|
158
|
+
declare function MessageAttachments({ views, message, mine, tone, slot, kinds }: MessageAttachmentsProps): react.JSX.Element | null;
|
|
115
159
|
|
|
116
160
|
interface ChatThreadHeaderProps {
|
|
117
161
|
conversationId: string;
|
|
@@ -129,6 +173,8 @@ interface ChatThreadHeaderProps {
|
|
|
129
173
|
resolveUser: ResolveUser;
|
|
130
174
|
onBack?: () => void;
|
|
131
175
|
backClassName?: string;
|
|
176
|
+
/** Opens search in this conversation (absent when search is off). */
|
|
177
|
+
onSearch?: () => void;
|
|
132
178
|
}
|
|
133
179
|
interface ChatThreadEmptyProps {
|
|
134
180
|
conversationId: string;
|
|
@@ -168,10 +214,29 @@ interface ChatThreadProps {
|
|
|
168
214
|
/** The quick reactions; 👍 ❤️ 😂 😮 😢 🙏 by default. */
|
|
169
215
|
quickReactions?: readonly string[];
|
|
170
216
|
placeholder?: string;
|
|
217
|
+
/** Show the topic bar even before the first topic exists (so an owner can add one). */
|
|
218
|
+
showTopics?: boolean;
|
|
219
|
+
/** Scroll to this message once it's loaded (a search result across conversations); `seq` lets earlier pages load to reach it. */
|
|
220
|
+
jumpTo?: {
|
|
221
|
+
messageId: string;
|
|
222
|
+
seq?: number;
|
|
223
|
+
} | null;
|
|
224
|
+
/** A search button in the header. Default true. */
|
|
225
|
+
search?: boolean;
|
|
226
|
+
/** Scheduled send in the composer. Default true. */
|
|
227
|
+
scheduling?: boolean;
|
|
228
|
+
/** The away banner. Default true. */
|
|
229
|
+
availability?: boolean;
|
|
230
|
+
/** Where the emoji picker loads its data from (self-hosted emojibase-data). Without it there is no full picker; the quick reactions stay. */
|
|
231
|
+
emojibaseUrl?: string;
|
|
232
|
+
/** Link preview images: `"off"` by default, `"proxy"` through `linkPreviewProxyUrl`, or `"direct"` from the linked site (which then sees readers' IPs). */
|
|
233
|
+
linkPreviewImages?: LinkPreviewImages;
|
|
234
|
+
/** Your image proxy for `linkPreviewImages: "proxy"`: the URL to load for an og:image. */
|
|
235
|
+
linkPreviewProxyUrl?: (url: string) => string | null | undefined;
|
|
171
236
|
slots?: ChatThreadSlots;
|
|
172
237
|
className?: string;
|
|
173
238
|
}
|
|
174
|
-
declare function ChatThread({ conversationId, resolveUser, title: titleProp, onBack, backClassName, threadRootId, topic, onOpenThread, quickReactions, placeholder, slots, className, }: ChatThreadProps): react.JSX.Element;
|
|
239
|
+
declare function ChatThread({ conversationId, resolveUser, title: titleProp, onBack, backClassName, threadRootId, topic: topicProp, onOpenThread, quickReactions, placeholder, showTopics, jumpTo: jumpToProp, search, scheduling, availability, emojibaseUrl, linkPreviewImages, linkPreviewProxyUrl, slots, className, }: ChatThreadProps): react.JSX.Element;
|
|
175
240
|
|
|
176
241
|
interface ChatPanelNoSelectionProps {
|
|
177
242
|
/** Words under the title; "Pick one on the left to read and reply." by default. */
|
|
@@ -193,9 +258,11 @@ interface ChatPanelProps {
|
|
|
193
258
|
slots?: {
|
|
194
259
|
noSelection?: SlotOverride<ChatPanelNoSelectionProps>;
|
|
195
260
|
};
|
|
261
|
+
/** Search across every conversation, above the inbox. Default true. */
|
|
262
|
+
search?: boolean;
|
|
196
263
|
className?: string;
|
|
197
264
|
}
|
|
198
|
-
declare function ChatPanel({ resolveUser, selectedId: controlled, defaultSelectedId, onSelectedChange, inboxHeader, inboxProps, threadProps, slots, className, }: ChatPanelProps): react.JSX.Element;
|
|
265
|
+
declare function ChatPanel({ resolveUser, selectedId: controlled, defaultSelectedId, onSelectedChange, inboxHeader, inboxProps, threadProps, slots, search, className, }: ChatPanelProps): react.JSX.Element;
|
|
199
266
|
|
|
200
267
|
interface ChatComposerHandle {
|
|
201
268
|
/** Adds files as if picked (a drop on the thread, say). Refused ones get a toast. */
|
|
@@ -206,7 +273,7 @@ interface ChatComposerHandle {
|
|
|
206
273
|
}
|
|
207
274
|
interface ChatComposerProps {
|
|
208
275
|
/** From `useConversation`: how to send, and whether you may. */
|
|
209
|
-
conversation: Pick<ConversationState, "send" | "signalTyping" | "signalRecording" | "role" | "canPost" | "accessLoading" | "conversation"
|
|
276
|
+
conversation: Pick<ConversationState, "send" | "signalTyping" | "signalRecording" | "role" | "canPost" | "accessLoading" | "conversation"> & Partial<Pick<ConversationState, "accessError" | "refetchAccess" | "signalRecordingVideo" | "conversationLoading">>;
|
|
210
277
|
/** The message being replied to; a banner shows above the box. */
|
|
211
278
|
replyTo?: ChatMessage | null;
|
|
212
279
|
onCancelReply?: () => void;
|
|
@@ -218,10 +285,22 @@ interface ChatComposerProps {
|
|
|
218
285
|
/** Called after a message is queued. */
|
|
219
286
|
onSent?: () => void;
|
|
220
287
|
autoFocus?: boolean;
|
|
288
|
+
/** Shown instead of the box, with a lock (a closed topic). */
|
|
289
|
+
disabledReason?: string | null;
|
|
290
|
+
/** The topic new messages go to (scheduled ones too). */
|
|
291
|
+
topic?: string | null;
|
|
292
|
+
/** Offer scheduled send (the arrow beside Send) and list what's scheduled. Default true. */
|
|
293
|
+
scheduling?: boolean;
|
|
294
|
+
/** Offer video recording. Default true where the browser can record. */
|
|
295
|
+
video?: boolean;
|
|
296
|
+
/** Offer the emoji picker (only when `emojibaseUrl` is given). Default true. */
|
|
297
|
+
emoji?: boolean;
|
|
298
|
+
/** Where the emoji picker loads its data from. */
|
|
299
|
+
emojibaseUrl?: string;
|
|
221
300
|
className?: string;
|
|
222
301
|
ref?: Ref<ChatComposerHandle>;
|
|
223
302
|
}
|
|
224
|
-
declare function ChatComposer({ conversation: state, replyTo, onCancelReply, resolveUser, placeholder, accessory, onSent, autoFocus, className, ref, }: ChatComposerProps): react.JSX.Element;
|
|
303
|
+
declare function ChatComposer({ conversation: state, replyTo, onCancelReply, resolveUser, placeholder, accessory, onSent, autoFocus, disabledReason, topic, scheduling, video, emoji, emojibaseUrl, className, ref, }: ChatComposerProps): react.JSX.Element;
|
|
225
304
|
|
|
226
305
|
interface MessageRowProps {
|
|
227
306
|
item: ThreadItem;
|
|
@@ -357,6 +436,12 @@ interface MemoryConversationSeed {
|
|
|
357
436
|
attachments?: Partial<ChatAttachment>[];
|
|
358
437
|
})[];
|
|
359
438
|
pins?: string[];
|
|
439
|
+
/** Its topics, in order. */
|
|
440
|
+
topics?: {
|
|
441
|
+
topic: string;
|
|
442
|
+
title?: string;
|
|
443
|
+
closed?: boolean;
|
|
444
|
+
}[];
|
|
360
445
|
}
|
|
361
446
|
interface MemoryChatOptions {
|
|
362
447
|
/** The signed-in user. */
|
|
@@ -366,6 +451,10 @@ interface MemoryChatOptions {
|
|
|
366
451
|
files?: Record<string, string>;
|
|
367
452
|
/** Milliseconds each call waits, to show loading states. Default 0. */
|
|
368
453
|
latencyMs?: number;
|
|
454
|
+
/** Away messages and working hours, by user id. */
|
|
455
|
+
availability?: Record<string, Partial<Omit<ChatAvailability, "userId">>>;
|
|
456
|
+
/** Link previews the "server" has fetched. */
|
|
457
|
+
linkPreviews?: Partial<ChatLinkPreview$1>[];
|
|
369
458
|
}
|
|
370
459
|
type MemoryChatClient = ChatClient & {
|
|
371
460
|
/** Someone else posts (a demo's other side). */
|
|
@@ -376,6 +465,10 @@ type MemoryChatClient = ChatClient & {
|
|
|
376
465
|
failNextSends(count?: number): void;
|
|
377
466
|
/** The next send waits until the returned function is called (a test watching the sending state). */
|
|
378
467
|
holdNextSend(): () => void;
|
|
468
|
+
/** Posts every scheduled message due by `now` (a demo's clock), as the server's runner does. */
|
|
469
|
+
sendDue(now?: Date): Promise<number>;
|
|
470
|
+
/** Moderation's verdict on a message (the server's side). */
|
|
471
|
+
moderate(messageId: string, result: "clean" | "flagged" | "hidden"): void;
|
|
379
472
|
/** Every call made, for tests. */
|
|
380
473
|
calls: {
|
|
381
474
|
method: string;
|
|
@@ -384,4 +477,214 @@ type MemoryChatClient = ChatClient & {
|
|
|
384
477
|
};
|
|
385
478
|
declare function createMemoryChatClient(options: MemoryChatOptions): MemoryChatClient;
|
|
386
479
|
|
|
387
|
-
|
|
480
|
+
interface ChatTopicBarProps {
|
|
481
|
+
topics: ChatTopic[];
|
|
482
|
+
/** The topic shown; null for "All". */
|
|
483
|
+
value: string | null;
|
|
484
|
+
onValueChange: (topic: string | null) => void;
|
|
485
|
+
/** Owners and admins: "New topic", and close or reopen. */
|
|
486
|
+
canManage?: boolean;
|
|
487
|
+
/** Saves a topic (from useTopics). */
|
|
488
|
+
onSetTopic?: (topic: string, input: TopicInput) => Promise<void>;
|
|
489
|
+
className?: string;
|
|
490
|
+
}
|
|
491
|
+
declare function ChatTopicBar({ topics, value, onValueChange, canManage, onSetTopic, className }: ChatTopicBarProps): react.JSX.Element;
|
|
492
|
+
|
|
493
|
+
interface ChatScheduleMenuProps {
|
|
494
|
+
/** Called with the chosen time. */
|
|
495
|
+
onSchedule: (at: Date) => void;
|
|
496
|
+
disabled?: boolean;
|
|
497
|
+
/** For tests and demos. */
|
|
498
|
+
now?: Date;
|
|
499
|
+
}
|
|
500
|
+
/** `YYYY-MM-DDTHH:mm` in local time, for a datetime-local input. */
|
|
501
|
+
declare function toLocalInput(d: Date): string;
|
|
502
|
+
/** The chevron beside Send: "Schedule message" with quick picks and a custom time. */
|
|
503
|
+
declare function ChatScheduleMenu({ onSchedule, disabled, now }: ChatScheduleMenuProps): react.JSX.Element;
|
|
504
|
+
interface ChatScheduledListProps {
|
|
505
|
+
/** One conversation's; all of yours without it. */
|
|
506
|
+
conversationId?: string | null;
|
|
507
|
+
/** Names a conversation in the all-conversations list. */
|
|
508
|
+
conversationTitle?: (conversationId: string) => string;
|
|
509
|
+
className?: string;
|
|
510
|
+
}
|
|
511
|
+
/** Your scheduled messages, soonest first, each with when it goes and Cancel. */
|
|
512
|
+
declare function ChatScheduledList({ conversationId, conversationTitle, className }: ChatScheduledListProps): react.JSX.Element;
|
|
513
|
+
|
|
514
|
+
interface ChatAwayBannerProps {
|
|
515
|
+
/** One person (a direct conversation). */
|
|
516
|
+
availability?: ChatAvailability | null;
|
|
517
|
+
/** The person's name, as the app shows it. */
|
|
518
|
+
name?: string;
|
|
519
|
+
/** Several people (a group): everyone away or outside their hours is named, two at most. */
|
|
520
|
+
people?: {
|
|
521
|
+
availability: ChatAvailability | null | undefined;
|
|
522
|
+
name: string;
|
|
523
|
+
}[];
|
|
524
|
+
now?: Date;
|
|
525
|
+
className?: string;
|
|
526
|
+
}
|
|
527
|
+
/** "Mark replies Mon–Fri, 9–5 (Eastern)", "Away until Monday: Back Monday", "Sam, Lee and 2 others are away"; nothing when everyone is around. */
|
|
528
|
+
declare function ChatAwayBanner({ availability, name, people, now, className }: ChatAwayBannerProps): react.JSX.Element | null;
|
|
529
|
+
interface ChatAvailabilitySettingsProps {
|
|
530
|
+
/** Called after a save. */
|
|
531
|
+
onSaved?: () => void;
|
|
532
|
+
className?: string;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Your availability: an away message (and until when), working hours per weekday, and the
|
|
536
|
+
* time zone they're read in. One range per day here; extra ranges saved elsewhere are kept
|
|
537
|
+
* for the days you don't change.
|
|
538
|
+
*/
|
|
539
|
+
declare function ChatAvailabilitySettings({ onSaved, className }: ChatAvailabilitySettingsProps): react.JSX.Element;
|
|
540
|
+
|
|
541
|
+
interface ChatBroadcastProps {
|
|
542
|
+
resolveUser: ResolveUser;
|
|
543
|
+
/** Only this kind of conversation (`direct`, say). */
|
|
544
|
+
kind?: string;
|
|
545
|
+
/** Which inbox rows may be picked; every one by default. */
|
|
546
|
+
filter?: (item: ChatInboxItem) => boolean;
|
|
547
|
+
/** Called with how many it went to. */
|
|
548
|
+
onSent?: (sent: number, picked: number) => void;
|
|
549
|
+
className?: string;
|
|
550
|
+
}
|
|
551
|
+
declare function ChatBroadcast({ resolveUser, kind, filter, onSent, className }: ChatBroadcastProps): react.JSX.Element;
|
|
552
|
+
|
|
553
|
+
interface ChatSearchProps {
|
|
554
|
+
resolveUser: ResolveUser;
|
|
555
|
+
/** Search one conversation; all of yours without it. */
|
|
556
|
+
conversationId?: string | null;
|
|
557
|
+
/** Called with the chosen result. */
|
|
558
|
+
onOpen: (hit: ChatSearchHit) => void;
|
|
559
|
+
/** Names a conversation, on results across conversations. */
|
|
560
|
+
conversationTitle?: (conversationId: string) => string;
|
|
561
|
+
/** Start with the box focused. */
|
|
562
|
+
autoFocus?: boolean;
|
|
563
|
+
debounceMs?: number;
|
|
564
|
+
className?: string;
|
|
565
|
+
}
|
|
566
|
+
/** A snippet with its « » marks as <mark>. */
|
|
567
|
+
declare function SearchSnippet({ snippet, className }: {
|
|
568
|
+
snippet: string;
|
|
569
|
+
className?: string;
|
|
570
|
+
}): react.JSX.Element;
|
|
571
|
+
declare function ChatSearch({ resolveUser, conversationId, onOpen, conversationTitle, autoFocus, debounceMs, className }: ChatSearchProps): react.JSX.Element;
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* @feat:chat
|
|
575
|
+
* `ChatErrorBoundary`: what one message (or one app attachment card) draws can't take the whole
|
|
576
|
+
* thread down. Messages carry what other people sent, and app renderers get sender-controlled
|
|
577
|
+
* data, so a render that throws shows its fallback instead, and the error goes to `onError`
|
|
578
|
+
* (console.error by default) so it is never silent.
|
|
579
|
+
*/
|
|
580
|
+
|
|
581
|
+
interface ChatErrorBoundaryProps {
|
|
582
|
+
/** Drawn instead of the children once they throw. */
|
|
583
|
+
fallback: ReactNode;
|
|
584
|
+
/** Told about the error; console.error by default. */
|
|
585
|
+
onError?: (error: unknown, info: ErrorInfo) => void;
|
|
586
|
+
children: ReactNode;
|
|
587
|
+
}
|
|
588
|
+
declare class ChatErrorBoundary extends Component<ChatErrorBoundaryProps, {
|
|
589
|
+
failed: boolean;
|
|
590
|
+
}> {
|
|
591
|
+
state: {
|
|
592
|
+
failed: boolean;
|
|
593
|
+
};
|
|
594
|
+
static getDerivedStateFromError(): {
|
|
595
|
+
failed: boolean;
|
|
596
|
+
};
|
|
597
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
598
|
+
render(): ReactNode;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** The picker can load: an emojibase URL was given. */
|
|
602
|
+
declare const canPickEmoji: (emojibaseUrl: string | null | undefined) => emojibaseUrl is string;
|
|
603
|
+
interface ChatEmojiPickerProps {
|
|
604
|
+
onSelect: (emoji: string) => void;
|
|
605
|
+
/** Where the Emojibase data is fetched from (`${emojibaseUrl}/en/data.json`). Required to load anything. */
|
|
606
|
+
emojibaseUrl?: string;
|
|
607
|
+
locale?: "en";
|
|
608
|
+
columns?: number;
|
|
609
|
+
className?: string;
|
|
610
|
+
}
|
|
611
|
+
declare function ChatEmojiPicker({ onSelect, emojibaseUrl, locale, columns, className }: ChatEmojiPickerProps): react.JSX.Element;
|
|
612
|
+
|
|
613
|
+
type VideoRecorderState = "idle" | "requesting" | "previewing" | "recording" | "recorded" | "denied" | "unsupported" | "error";
|
|
614
|
+
interface VideoRecording {
|
|
615
|
+
blob: Blob;
|
|
616
|
+
/** Without codec parameters: `video/webm` or `video/mp4`. */
|
|
617
|
+
contentType: string;
|
|
618
|
+
name: string;
|
|
619
|
+
durationMs: number;
|
|
620
|
+
/** An object URL for playback before sending. */
|
|
621
|
+
url: string | null;
|
|
622
|
+
}
|
|
623
|
+
/** MP4 where the browser records it (Safari, recent Chrome), else WebM. */
|
|
624
|
+
declare function pickVideoMimeType(): string | null;
|
|
625
|
+
interface UseVideoRecorderOptions {
|
|
626
|
+
/** It stops itself here (`recordingLimitMs(role)`). */
|
|
627
|
+
maxMs: number;
|
|
628
|
+
/** About every second while recording (to tell the others). */
|
|
629
|
+
onTick?: () => void;
|
|
630
|
+
}
|
|
631
|
+
declare function useVideoRecorder({ maxMs, onTick }: UseVideoRecorderOptions): {
|
|
632
|
+
state: VideoRecorderState;
|
|
633
|
+
error: string | null;
|
|
634
|
+
elapsedMs: number;
|
|
635
|
+
maxMs: number;
|
|
636
|
+
stream: MediaStream | null;
|
|
637
|
+
recording: VideoRecording | null;
|
|
638
|
+
open: () => Promise<boolean>;
|
|
639
|
+
start: () => Promise<void>;
|
|
640
|
+
stop: () => void;
|
|
641
|
+
retake: () => Promise<void>;
|
|
642
|
+
reset: () => void;
|
|
643
|
+
handOff: () => void;
|
|
644
|
+
};
|
|
645
|
+
type VideoRecorder = ReturnType<typeof useVideoRecorder>;
|
|
646
|
+
/** A recording as a file the outbox uploads to storage (the fallback without `videoUpload`). */
|
|
647
|
+
declare function videoFile(rec: VideoRecording): LocalFile;
|
|
648
|
+
/**
|
|
649
|
+
* Uploads a recording through the app's `videoUpload` (a Mux direct upload: PUT the bytes to
|
|
650
|
+
* the URL it returns) and gives back the attachment to send, pending until Mux's webhook marks
|
|
651
|
+
* it ready.
|
|
652
|
+
*/
|
|
653
|
+
declare function uploadVideo(videoUpload: ChatVideoUpload, conversationId: string, rec: Pick<VideoRecording, "blob" | "contentType" | "durationMs" | "name">, fetchImpl?: typeof fetch, signal?: AbortSignal): Promise<AttachmentInput>;
|
|
654
|
+
interface ChatVideoRecorderProps {
|
|
655
|
+
open: boolean;
|
|
656
|
+
onOpenChange: (open: boolean) => void;
|
|
657
|
+
conversationId: string;
|
|
658
|
+
maxMs: number;
|
|
659
|
+
/** From useConversation: sends through the outbox. */
|
|
660
|
+
send: (input: Omit<SendInput, "conversationId" | "clientId">, files?: LocalFile[]) => Promise<unknown>;
|
|
661
|
+
/** Merged into what's sent (a reply, a topic, an internal note). */
|
|
662
|
+
extra?: Omit<SendInput, "conversationId" | "clientId" | "attachments" | "body">;
|
|
663
|
+
videoUpload?: ChatVideoUpload;
|
|
664
|
+
onTick?: () => void;
|
|
665
|
+
onSent?: () => void;
|
|
666
|
+
/** For tests. */
|
|
667
|
+
fetchImpl?: typeof fetch;
|
|
668
|
+
}
|
|
669
|
+
/** The composer's video dialog: preview, record (to the role's limit), review, retake, send. */
|
|
670
|
+
declare function ChatVideoRecorder({ open, onOpenChange, conversationId, maxMs, send, extra, videoUpload, onTick, onSent, fetchImpl }: ChatVideoRecorderProps): react.JSX.Element;
|
|
671
|
+
declare const muxStreamUrl: (playbackId: string) => string;
|
|
672
|
+
declare const muxPosterUrl: (playbackId: string) => string;
|
|
673
|
+
interface ChatVideoPlayerProps {
|
|
674
|
+
/** A file URL (storage), when there is no playback id. */
|
|
675
|
+
src?: string | null;
|
|
676
|
+
/** Mux's playback id: streamed as HLS. */
|
|
677
|
+
playbackId?: string | null;
|
|
678
|
+
poster?: string | null;
|
|
679
|
+
/** Mux is still working on it. */
|
|
680
|
+
processing?: boolean;
|
|
681
|
+
failed?: boolean;
|
|
682
|
+
/** Still uploading from this device. */
|
|
683
|
+
uploading?: boolean;
|
|
684
|
+
label?: string;
|
|
685
|
+
className?: string;
|
|
686
|
+
}
|
|
687
|
+
/** Plays a chat video: Mux HLS (native, else hls.js loaded on demand), or a file. */
|
|
688
|
+
declare function ChatVideoPlayer({ src, playbackId, poster, processing, failed, uploading, label, className }: ChatVideoPlayerProps): react.JSX.Element;
|
|
689
|
+
|
|
690
|
+
export { type AttachmentView, ChatAttachmentCard, type ChatAttachmentCardProps, type ChatAttachmentSlotProps, ChatAvailabilitySettings, type ChatAvailabilitySettingsProps, ChatAwayBanner, type ChatAwayBannerProps, ChatBroadcast, type ChatBroadcastProps, ChatComposer, type ChatComposerAccessoryProps, type ChatComposerHandle, type ChatComposerProps, ChatEmojiPicker, type ChatEmojiPickerProps, ChatErrorBoundary, type ChatErrorBoundaryProps, ChatInbox, type ChatInboxEmptyProps, type ChatInboxProps, ChatLinkPreview, type ChatLinkPreviewProps, ChatMarkdown, type ChatMarkdownProps, ChatPanel, type ChatPanelNoSelectionProps, type ChatPanelProps, ChatScheduleMenu, type ChatScheduleMenuProps, ChatScheduledList, type ChatScheduledListProps, ChatSearch, type ChatSearchProps, ChatThread, type ChatThreadEmptyProps, type ChatThreadHeaderProps, type ChatThreadProps, type ChatThreadSlots, ChatTopicBar, type ChatTopicBarProps, type ChatUser, ChatVideoPlayer, type ChatVideoPlayerProps, ChatVideoRecorder, type ChatVideoRecorderProps, FileChip, GenericAttachment, ImageGrid, type ImageGridProps, Lightbox, type LightboxProps, type LinkPreviewImages, type MarkdownNode, type MemoryChatClient, type MemoryChatOptions, type MemoryConversationSeed, MessageAttachments, type MessageAttachmentsProps, MessageRow, type MessageRowProps, QUICK_REACTIONS, type ResolveUser, SearchSnippet, type UseVideoRecorderOptions, type UseVoiceRecorderOptions, VideoAttachment, type VideoRecorder, type VideoRecorderState, type VideoRecording, VoiceNote, type VoiceNoteProps, type VoiceRecorder, type VoiceRecorderState, type VoiceRecording, canPickEmoji, clockTime, createMemoryChatClient, formatBytes, inboxTitle, isSafeHref, linkPreviewImageSrc, messageLabel, muxPosterUrl, muxStreamUrl, parseMarkdown, pickVideoMimeType, pickVoiceMimeType, plainText, relativeTime, toAttachmentView, toLocalInput, uploadVideo, useVideoRecorder, useVoiceRecorder, videoFile, voiceFile };
|