@marktiderman/genesis-chat 0.2.0 → 0.2.1
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 +82 -7
- package/dist/{chunk-5D5A7I4Q.js → chunk-A4YJX7KT.js} +130 -16
- package/dist/chunk-A4YJX7KT.js.map +1 -0
- package/dist/chunk-AZS3HNLM.js +30 -0
- package/dist/chunk-AZS3HNLM.js.map +1 -0
- package/dist/{chunk-7S6HYNWW.js → chunk-GBJ3YI4E.js} +72 -5
- package/dist/chunk-GBJ3YI4E.js.map +1 -0
- package/dist/{chunk-PZTOY2LY.js → chunk-JU2FAPBI.js} +46 -13
- package/dist/chunk-JU2FAPBI.js.map +1 -0
- package/dist/{client-DMa2j7kz.d.ts → client-DUIG1hum.d.ts} +36 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/native.d.ts +36 -7
- package/dist/native.js +239 -116
- package/dist/native.js.map +1 -1
- package/dist/{outbox-B0y3phE0.d.ts → outbox-B9NAWQ58.d.ts} +74 -6
- package/dist/push.d.ts +2 -2
- package/dist/react.d.ts +48 -21
- package/dist/react.js +3 -3
- package/dist/server.d.ts +43 -2
- package/dist/server.js +115 -15
- package/dist/server.js.map +1 -1
- package/dist/{thread-DWXHKPdw.d.ts → thread-oPra7pkW.d.ts} +1 -1
- package/dist/web.d.ts +31 -11
- package/dist/web.js +97 -48
- package/dist/web.js.map +1 -1
- package/migrations/0003_genesis_chat_search_threads.sql +85 -0
- package/migrations/tests/0002_genesis_chat_coaching.test.sql +7 -3
- package/migrations/tests/0003_genesis_chat_search_threads.test.sql +50 -0
- package/package.json +7 -3
- package/src/__tests__/coaching-core.test.ts +3 -1
- package/src/__tests__/coaching-react.test.tsx +73 -2
- package/src/__tests__/coaching-web.test.tsx +78 -6
- package/src/__tests__/fake-xhr.ts +54 -0
- package/src/__tests__/outbox-hosted.test.ts +269 -0
- package/src/__tests__/review-279.test.ts +41 -0
- package/src/__tests__/review-fixes-web.test.tsx +13 -10
- package/src/__tests__/server-fake-video.test.ts +81 -0
- package/src/__tests__/server-fakes.ts +7 -1
- package/src/__tests__/server-safety.test.ts +17 -4
- package/src/__tests__/web.test.tsx +16 -0
- package/src/core/client.ts +28 -2
- package/src/core/outbox.ts +222 -16
- package/src/core/transfer.ts +71 -0
- package/src/core/types.ts +17 -0
- package/src/index.ts +1 -0
- package/src/native/__tests__/emoji.test.tsx +16 -0
- package/src/native/__tests__/organise-screens.test.tsx +80 -0
- package/src/native/__tests__/video-media.test.tsx +81 -4
- package/src/native/attachment-cards.tsx +22 -6
- package/src/native/inbox-screen.tsx +12 -6
- package/src/native/index.ts +2 -2
- package/src/native/message-actions-sheet.tsx +10 -5
- package/src/native/message-bubble.tsx +3 -11
- package/src/native/modules.ts +2 -0
- package/src/native/schedule-sheet.tsx +23 -9
- package/src/native/search-screen.tsx +26 -5
- package/src/native/thread-screen.tsx +30 -3
- package/src/native/topics.tsx +70 -15
- package/src/native/video-player.tsx +46 -6
- package/src/native/video-recorder.tsx +6 -3
- package/src/native/voice-player.tsx +18 -0
- package/src/react/index.tsx +103 -19
- package/src/react/previews.ts +51 -0
- package/src/server/fake-video.ts +171 -0
- package/src/server/index.ts +1 -0
- package/src/web/attachments.tsx +6 -6
- package/src/web/bubble.tsx +22 -0
- package/src/web/inbox.tsx +7 -6
- package/src/web/memory-client.ts +11 -3
- package/src/web/panel.tsx +2 -2
- package/src/web/schedule.tsx +14 -2
- package/src/web/thread.tsx +36 -8
- package/src/web/video.tsx +28 -34
- package/dist/chunk-5D5A7I4Q.js.map +0 -1
- package/dist/chunk-7S6HYNWW.js.map +0 -1
- package/dist/chunk-PZTOY2LY.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as AttachmentKind, A as AttachmentInput, i as ChatClient, B as PendingMessage, P as PendingFile, q as ChatMessage,
|
|
1
|
+
import { b as AttachmentKind, A as AttachmentInput, U as UploadProgress, H as HostedAttachment, i as ChatClient, B as PendingMessage, P as PendingFile, q as ChatMessage, F as SendInput } from './client-DUIG1hum.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @feat:chat
|
|
@@ -62,6 +62,13 @@ declare function nextPlaybackRate(rate: number): number;
|
|
|
62
62
|
* and only sent again by hand. `clear()` (sign-out) stops sends in flight: none
|
|
63
63
|
* posts, or saves anything, afterwards. Storage is the platform's (memory by default;
|
|
64
64
|
* localStorage on web, MMKV or SQLite on native), behind OutboxStore.
|
|
65
|
+
*
|
|
66
|
+
* A hosted video (`LocalFile.hosted`, with `hostedUpload`) doesn't wait for its bytes: the
|
|
67
|
+
* upload is started (the provider's upload id minted), the message is sent in its place in the
|
|
68
|
+
* order with the attachment `pending`, and the bytes carry on going up; the provider's webhook
|
|
69
|
+
* marks it ready. The message stays here (hidden from the thread once the server has it) until
|
|
70
|
+
* its bytes are up, so a relaunch carries the upload on (`resume`) and sends the message again
|
|
71
|
+
* with the same clientId, which posts nothing twice.
|
|
65
72
|
*/
|
|
66
73
|
|
|
67
74
|
/** A file still on the device, uploaded when its message is sent. */
|
|
@@ -76,7 +83,47 @@ interface LocalFile {
|
|
|
76
83
|
meta?: Partial<Omit<AttachmentInput, "path" | "kind">> & {
|
|
77
84
|
kind?: AttachmentInput["kind"];
|
|
78
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Send it to the hosted video provider (the outbox's `hostedUpload`) instead of storage: the
|
|
88
|
+
* message goes at once, in order, with the attachment pending, while the bytes go up. Without
|
|
89
|
+
* a `hostedUpload` it goes to storage like any file.
|
|
90
|
+
*/
|
|
91
|
+
hosted?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** A hosted upload as the outbox keeps it (saved with the message), so it can be carried on. */
|
|
94
|
+
interface HostedUploadRecord {
|
|
95
|
+
provider: HostedAttachment["provider"];
|
|
96
|
+
providerId: string;
|
|
97
|
+
/** Where the bytes go, when the hook returned it: what a resume needs. */
|
|
98
|
+
url?: string;
|
|
99
|
+
/** Every byte is there. */
|
|
100
|
+
uploaded?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/** What `hostedUpload` resolves with as soon as the upload exists (before the bytes are up). */
|
|
103
|
+
interface HostedUploadStart extends HostedAttachment {
|
|
104
|
+
/** Where the bytes go; kept with the message, handed back as `resume.url`. */
|
|
105
|
+
url?: string;
|
|
106
|
+
/** Settles when every byte is there (rejects when the transfer fails). Absent: already there. */
|
|
107
|
+
done?: Promise<void>;
|
|
79
108
|
}
|
|
109
|
+
interface HostedUploadOptions {
|
|
110
|
+
/** Aborts when the message is discarded or the outbox cleared. */
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
onProgress?: UploadProgress;
|
|
113
|
+
/**
|
|
114
|
+
* The upload started before (a relaunch, a retry after the transfer failed): carry it on and
|
|
115
|
+
* resolve with the same `providerId`. A message may already be posted with it, so mint a new
|
|
116
|
+
* one only when the old upload is gone; that message's video then stays pending.
|
|
117
|
+
*/
|
|
118
|
+
resume?: HostedUploadRecord;
|
|
119
|
+
}
|
|
120
|
+
/** Starts (or carries on) a hosted upload; resolves once the provider's upload id exists. */
|
|
121
|
+
type HostedUpload = (conversationId: string, file: LocalFile, opts: HostedUploadOptions) => Promise<HostedUploadStart>;
|
|
122
|
+
/** Uploads a file to storage and resolves with its path. */
|
|
123
|
+
type StorageUpload = (conversationId: string, file: LocalFile, opts?: {
|
|
124
|
+
signal?: AbortSignal;
|
|
125
|
+
onProgress?: UploadProgress;
|
|
126
|
+
}) => Promise<string>;
|
|
80
127
|
interface QueuedMessage extends Omit<PendingMessage, "files"> {
|
|
81
128
|
files: LocalFile[];
|
|
82
129
|
/** What each file is, kept with the saved copy (PendingMessage.files). */
|
|
@@ -84,6 +131,10 @@ interface QueuedMessage extends Omit<PendingMessage, "files"> {
|
|
|
84
131
|
/** Files lost on a relaunch (bytes that weren't persisted). */
|
|
85
132
|
lostFiles?: number;
|
|
86
133
|
attempts: number;
|
|
134
|
+
/** Hosted uploads started, by file slot. */
|
|
135
|
+
hosted?: Record<number, HostedUploadRecord>;
|
|
136
|
+
/** Posted (this message's id): it waits here only for its hosted bytes to finish. */
|
|
137
|
+
sentMessageId?: string;
|
|
87
138
|
}
|
|
88
139
|
interface OutboxStore {
|
|
89
140
|
load(): Promise<QueuedMessage[]>;
|
|
@@ -96,10 +147,18 @@ interface OutboxOptions {
|
|
|
96
147
|
store?: OutboxStore;
|
|
97
148
|
/** Reads a native file URI into bytes for upload. */
|
|
98
149
|
readFile?: (uri: string) => Promise<Blob | ArrayBuffer>;
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Upload hook, e.g. resumable uploads on native. Defaults to client.upload. `signal` aborts
|
|
152
|
+
* when the message is discarded or the outbox cleared; call `onProgress(sent, total)` as bytes
|
|
153
|
+
* go, and the pending bubble shows it (`PendingMessage.files[i].progress`).
|
|
154
|
+
*/
|
|
155
|
+
upload?: StorageUpload;
|
|
156
|
+
/**
|
|
157
|
+
* Starts a hosted video upload for a file marked `hosted` and resolves once the provider's
|
|
158
|
+
* upload id exists, with `done` for the bytes. See HostedUpload; without it, hosted files go
|
|
159
|
+
* to storage.
|
|
160
|
+
*/
|
|
161
|
+
hostedUpload?: HostedUpload;
|
|
103
162
|
/** Called with every change, for the UI. */
|
|
104
163
|
onChange?: (queue: QueuedMessage[]) => void;
|
|
105
164
|
/** Called when a message lands. */
|
|
@@ -141,6 +200,15 @@ declare function createOutbox(opts: OutboxOptions): {
|
|
|
141
200
|
/** Forgets every queued message, here and in the store (call it on sign-out). */
|
|
142
201
|
clear(): Promise<void>;
|
|
143
202
|
discard(clientId: string): Promise<void>;
|
|
203
|
+
/**
|
|
204
|
+
* Uploads one file to storage the way the outbox does (the `upload` hook, or `readFile` then
|
|
205
|
+
* the client's upload) and resolves with its path: for files sent outside the queue, such as
|
|
206
|
+
* a scheduled message's.
|
|
207
|
+
*/
|
|
208
|
+
uploadFile(conversationId: string, file: LocalFile, o?: {
|
|
209
|
+
signal?: AbortSignal;
|
|
210
|
+
onProgress?: UploadProgress;
|
|
211
|
+
}): Promise<string>;
|
|
144
212
|
pending(conversationId?: string): PendingMessage[];
|
|
145
213
|
/** A new client id, made the way this outbox makes them. */
|
|
146
214
|
newId: typeof randomId;
|
|
@@ -148,4 +216,4 @@ declare function createOutbox(opts: OutboxOptions): {
|
|
|
148
216
|
};
|
|
149
217
|
type Outbox = ReturnType<typeof createOutbox>;
|
|
150
218
|
|
|
151
|
-
export { ALLOWED_EXTENSIONS as A, CHAT_LIMITS as C, type LocalFile as L, type Outbox as O, PLAYBACK_RATES as P, type QueuedMessage as Q, type
|
|
219
|
+
export { ALLOWED_EXTENSIONS as A, CHAT_LIMITS as C, type HostedUpload as H, type LocalFile as L, type Outbox as O, PLAYBACK_RATES as P, type QueuedMessage as Q, type StorageUpload as S, type HostedUploadOptions as a, type HostedUploadRecord as b, type HostedUploadStart as c, type OutboxOptions as d, type OutboxStore as e, attachmentKindFor as f, createOutbox as g, fileExtension as h, formatDuration as i, isAllowedExtension as j, recordingLimitMs as k, releaseObjectUrl as l, memoryOutboxStore as m, nextPlaybackRate as n, waveformPeaks as o, randomId as r, waveformFromMetering as w };
|
package/dist/push.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as ChatClient } from './client-
|
|
2
|
-
import { O as Outbox,
|
|
1
|
+
import { i as ChatClient } from './client-DUIG1hum.js';
|
|
2
|
+
import { O as Outbox, e as OutboxStore, Q as QueuedMessage } from './outbox-B9NAWQ58.js';
|
|
3
3
|
import '@supabase/supabase-js';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/react.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as _tanstack_query_core from '@tanstack/query-core';
|
|
2
|
-
import { h as ChatAttachment, i as ChatClient, k as ChatConversation, p as ChatMember, t as ChatRole, n as ChatKindRules, q as ChatMessage, g as ChatActivity,
|
|
2
|
+
import { h as ChatAttachment, i as ChatClient, k as ChatConversation, p as ChatMember, t as ChatRole, n as ChatKindRules, q as ChatMessage, g as ChatActivity, F as SendInput, M as MessagePage, C as ChatAvailability, d as AvailabilityInput, v as ChatSignal, m as ChatInboxItem, o as ChatLinkPreview, D as ScheduledMessage, S as ScheduleInput, u as ChatSearchHit, x as ChatTopic, T as TopicInput } from './client-DUIG1hum.js';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { ReactNode } from 'react';
|
|
5
|
-
import {
|
|
6
|
-
import { T as ThreadItem } from './thread-
|
|
5
|
+
import { e as OutboxStore, S as StorageUpload, H as HostedUpload, L as LocalFile, O as Outbox, Q as QueuedMessage } from './outbox-B9NAWQ58.js';
|
|
6
|
+
import { T as ThreadItem } from './thread-oPra7pkW.js';
|
|
7
7
|
import '@supabase/supabase-js';
|
|
8
8
|
|
|
9
9
|
declare const chatKeys: {
|
|
@@ -33,9 +33,11 @@ type MessagesData = MessagePage;
|
|
|
33
33
|
/** Does a messages list (`[genesis-chat, conversation, id, messages, threadRootId, topic]`) hold this message? */
|
|
34
34
|
declare function belongsTo(key: readonly unknown[], m: ChatMessage): boolean;
|
|
35
35
|
/**
|
|
36
|
-
* Hosted video uploads (Mux direct uploads): your server makes an upload and returns its URL
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* Hosted video uploads (Mux direct uploads): your server makes an upload and returns its URL
|
|
37
|
+
* (`createVideoUploadHandler` in /server). A video the composer marks `hosted` is sent through
|
|
38
|
+
* the outbox at once, in order, with `{ kind: "video", provider, providerId, status: "pending" }`,
|
|
39
|
+
* while its bytes are PUT to that URL (see `hostedUploadFrom`). Without it, videos go to storage
|
|
40
|
+
* as files.
|
|
39
41
|
*/
|
|
40
42
|
interface ChatVideoUpload {
|
|
41
43
|
create(conversationId: string): Promise<{
|
|
@@ -44,16 +46,33 @@ interface ChatVideoUpload {
|
|
|
44
46
|
provider: "mux";
|
|
45
47
|
}>;
|
|
46
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The outbox's `hostedUpload` from a `ChatVideoUpload`: mints the upload (or, resuming, reuses
|
|
51
|
+
* the one minted before), resolves at once with its id, and PUTs the whole file to its URL in
|
|
52
|
+
* the background (XMLHttpRequest where there is one, for progress). A native file URI is read
|
|
53
|
+
* with `readFile`. Native apps with chunked, resumable uploads pass their own `hostedUpload`.
|
|
54
|
+
*/
|
|
55
|
+
declare function hostedUploadFrom(videoUpload: ChatVideoUpload, options?: {
|
|
56
|
+
readFile?: (uri: string) => Promise<Blob | ArrayBuffer>;
|
|
57
|
+
fetch?: typeof fetch;
|
|
58
|
+
}): HostedUpload;
|
|
59
|
+
/** What a kind's `title`, `subtitle` and `action` are told besides the attachment. */
|
|
60
|
+
interface ChatCardContext {
|
|
61
|
+
/** The card is on a message you sent (your own check-in reads "Sent", not "Answer"). */
|
|
62
|
+
mine: boolean;
|
|
63
|
+
}
|
|
47
64
|
/** What an app attachment kind's card shows (a form, a check-in): plain data, drawn by each renderer. */
|
|
48
65
|
interface ChatAttachmentKind {
|
|
49
|
-
title(attachment: ChatAttachment): string;
|
|
50
|
-
subtitle?(attachment: ChatAttachment): string | null | undefined;
|
|
66
|
+
title(attachment: ChatAttachment, ctx?: ChatCardContext): string;
|
|
67
|
+
subtitle?(attachment: ChatAttachment, ctx?: ChatCardContext): string | null | undefined;
|
|
51
68
|
/** The card's button: a link (`href`) or a handler (`onPress`). */
|
|
52
|
-
action?(attachment: ChatAttachment): {
|
|
69
|
+
action?(attachment: ChatAttachment, ctx?: ChatCardContext): {
|
|
53
70
|
label: string;
|
|
54
71
|
href?: string;
|
|
55
72
|
onPress?: () => void;
|
|
56
73
|
} | null | undefined;
|
|
74
|
+
/** What previews call it (the inbox's last message, "Check-in"); the title by default. */
|
|
75
|
+
label?: string;
|
|
57
76
|
}
|
|
58
77
|
interface ChatContextValue {
|
|
59
78
|
client: ChatClient;
|
|
@@ -78,10 +97,17 @@ interface ChatProviderProps {
|
|
|
78
97
|
outboxStore?: OutboxStore;
|
|
79
98
|
/** Reads a native file URI for upload. */
|
|
80
99
|
readFile?: (uri: string) => Promise<Blob | ArrayBuffer>;
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Replaces the plain upload (resumable uploads on native). `signal` aborts when the message is
|
|
102
|
+
* discarded or the outbox cleared; `onProgress(sent, total)` fills the pending bubble's bar.
|
|
103
|
+
*/
|
|
104
|
+
upload?: StorageUpload;
|
|
105
|
+
/**
|
|
106
|
+
* Starts a hosted upload for files marked `hosted` (see HostedUpload): resolve with
|
|
107
|
+
* `{ provider, providerId, status: "pending", url?, done }` once the upload id exists. Default:
|
|
108
|
+
* `hostedUploadFrom(videoUpload)` when `videoUpload` is given.
|
|
109
|
+
*/
|
|
110
|
+
hostedUpload?: HostedUpload;
|
|
85
111
|
/** Makes client ids (default: a random UUID that works without crypto.randomUUID). */
|
|
86
112
|
newId?: () => string;
|
|
87
113
|
/** Keeps a byte file (a pasted image, a recording) across launches: write it, return a URI. */
|
|
@@ -102,7 +128,7 @@ interface ChatPerson {
|
|
|
102
128
|
avatarUrl?: string | null;
|
|
103
129
|
}
|
|
104
130
|
/** Puts chat in reach of every hook below it. Needs a TanStack QueryClientProvider above it. */
|
|
105
|
-
declare function ChatProvider({ client, userId, outboxStore, readFile, upload, newId, persistFile, releasePreview, loadPeople, videoUpload, attachmentKinds, children, }: ChatProviderProps): react.JSX.Element;
|
|
131
|
+
declare function ChatProvider({ client, userId, outboxStore, readFile, upload, hostedUpload, newId, persistFile, releasePreview, loadPeople, videoUpload, attachmentKinds, children, }: ChatProviderProps): react.JSX.Element;
|
|
106
132
|
declare function useChat(): ChatContextValue;
|
|
107
133
|
/** Your conversations, live. `kind` narrows to one kind; `archived` shows the archived ones. */
|
|
108
134
|
declare function useInbox(opts?: {
|
|
@@ -142,10 +168,10 @@ declare function useConversation(conversationId: string | null | undefined, opts
|
|
|
142
168
|
conversationLoading: boolean;
|
|
143
169
|
/** Your role couldn't be loaded (offline): offer `refetchAccess`, don't say "can't post". */
|
|
144
170
|
accessError: Error | null;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
171
|
+
/** Retries everything this conversation loads (role, members, the conversation, topics, availability, pins), not only access. */
|
|
172
|
+
refetchAccess: () => Promise<undefined>;
|
|
173
|
+
/** Same as refetchAccess: every per-conversation lookup again, for a Retry after being offline. */
|
|
174
|
+
refetchConversation: () => Promise<undefined>;
|
|
149
175
|
/** The kind's rules (max members, threads, who posts). */
|
|
150
176
|
kind: NonNullable<NoInfer<ChatKindRules | null>> | null;
|
|
151
177
|
/** May you pin here: owners and admins, or anyone in a two-person conversation. */
|
|
@@ -213,8 +239,9 @@ declare function useTopics(conversationId: string | null | undefined): {
|
|
|
213
239
|
};
|
|
214
240
|
/**
|
|
215
241
|
* Your scheduled messages (in one conversation, or all), soonest first, with `schedule` and
|
|
216
|
-
* `cancel
|
|
217
|
-
*
|
|
242
|
+
* `cancel` (which also removes one that failed). `schedule` uploads its files to storage first,
|
|
243
|
+
* the way `send` does (the provider's `upload` hook, or a native URI read through `readFile`),
|
|
244
|
+
* then schedules them with the message. A file marked `hosted` goes to storage too.
|
|
218
245
|
*/
|
|
219
246
|
declare function useScheduled(conversationId?: string | null, opts?: {
|
|
220
247
|
enabled?: boolean;
|
|
@@ -276,4 +303,4 @@ declare const LINK_PREVIEW_CONCURRENCY = 4;
|
|
|
276
303
|
*/
|
|
277
304
|
declare function useLinkPreviews(messageIds: (string | null | undefined)[]): Record<string, ChatLinkPreview[]>;
|
|
278
305
|
|
|
279
|
-
export { CATCH_UP_MAX_PAGES, CATCH_UP_PAGE, type ChatAttachmentKind, type ChatPerson, ChatProvider, type ChatProviderProps, type ChatVideoUpload, type ConversationState, LINK_PREVIEW_CONCURRENCY, LocalFile, type MessagesData, QueuedMessage, ThreadItem, type UseConversationOptions, belongsTo, chatKeys, useAvailability, useBroadcast, useChat, useConversation, useDebouncedValue, useFileUrls, useInbox, useLinkPreviews, useMessageActions, usePeople, usePerson, usePins, useScheduled, useSearch, useStartConversation, useTopics, useUnreadTotal };
|
|
306
|
+
export { CATCH_UP_MAX_PAGES, CATCH_UP_PAGE, type ChatAttachmentKind, type ChatCardContext, type ChatPerson, ChatProvider, type ChatProviderProps, type ChatVideoUpload, type ConversationState, LINK_PREVIEW_CONCURRENCY, LocalFile, type MessagesData, QueuedMessage, ThreadItem, type UseConversationOptions, belongsTo, chatKeys, hostedUploadFrom, useAvailability, useBroadcast, useChat, useConversation, useDebouncedValue, useFileUrls, useInbox, useLinkPreviews, useMessageActions, usePeople, usePerson, usePins, useScheduled, useSearch, useStartConversation, useTopics, useUnreadTotal };
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { CATCH_UP_MAX_PAGES, CATCH_UP_PAGE, ChatProvider, LINK_PREVIEW_CONCURRENCY, belongsTo, chatKeys, useAvailability, useBroadcast, useChat, useConversation, useDebouncedValue, useFileUrls, useInbox, useLinkPreviews, useMessageActions, usePeople, usePerson, usePins, useScheduled, useSearch, useStartConversation, useTopics, useUnreadTotal } from './chunk-
|
|
2
|
-
import './chunk-
|
|
3
|
-
import './chunk-
|
|
1
|
+
export { CATCH_UP_MAX_PAGES, CATCH_UP_PAGE, ChatProvider, LINK_PREVIEW_CONCURRENCY, belongsTo, chatKeys, hostedUploadFrom, useAvailability, useBroadcast, useChat, useConversation, useDebouncedValue, useFileUrls, useInbox, useLinkPreviews, useMessageActions, usePeople, usePerson, usePins, useScheduled, useSearch, useStartConversation, useTopics, useUnreadTotal } from './chunk-JU2FAPBI.js';
|
|
2
|
+
import './chunk-A4YJX7KT.js';
|
|
3
|
+
import './chunk-GBJ3YI4E.js';
|
|
4
4
|
//# sourceMappingURL=react.js.map
|
|
5
5
|
//# sourceMappingURL=react.js.map
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { w as ChatSupabase } from './client-
|
|
1
|
+
import { w as ChatSupabase } from './client-DUIG1hum.js';
|
|
2
2
|
import '@supabase/supabase-js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -309,6 +309,47 @@ declare function muxVideo(options: MuxVideoOptions): VideoProvider & {
|
|
|
309
309
|
}): Promise<string>;
|
|
310
310
|
};
|
|
311
311
|
|
|
312
|
+
/**
|
|
313
|
+
* A pretend hosted video service for local development, behind `VideoProvider`, so the hosted
|
|
314
|
+
* path (pending attachment, upload, ready) runs without a Mux account.
|
|
315
|
+
*
|
|
316
|
+
* createUpload a local upload URL (`uploadUrl?id=<upload id>`) and its id, `fake_<uuid>`
|
|
317
|
+
* handler the fetch handler to mount at `uploadUrl`: takes the PUT body, stores it in
|
|
318
|
+
* the genesis-chat bucket, then marks the attachment ready
|
|
319
|
+
* markReady what Mux's `video.asset.ready` webhook does: `update_attachment` with status
|
|
320
|
+
* ready, a playback id (`fake-<upload id>`) and a fake HLS URL
|
|
321
|
+
* playbackUrl a signed link to the stored file (the fake HLS URL when nothing was stored)
|
|
322
|
+
*
|
|
323
|
+
* It refuses to be made when NODE_ENV is `production`, unless `force` is set.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
interface FakeVideoProviderOptions {
|
|
327
|
+
/** A service-role client: it writes the bucket and calls `update_attachment`. */
|
|
328
|
+
supabase: ChatSupabase;
|
|
329
|
+
/** Where `handler` is mounted, e.g. `http://localhost:3000/api/chat/fake-video`. */
|
|
330
|
+
uploadUrl: string;
|
|
331
|
+
/** The `provider` value it stands in for. Default `mux`. */
|
|
332
|
+
name?: "mux" | "cloudflare";
|
|
333
|
+
/** Run even when NODE_ENV is `production`. */
|
|
334
|
+
force?: boolean;
|
|
335
|
+
/** NODE_ENV, read from `process.env` by default. */
|
|
336
|
+
env?: string | undefined;
|
|
337
|
+
/** How long `handler` waits for the message carrying the upload to arrive before answering 202 (default 10 s). */
|
|
338
|
+
readyWaitMs?: number;
|
|
339
|
+
newId?: () => string;
|
|
340
|
+
sleep?: (ms: number) => Promise<void>;
|
|
341
|
+
}
|
|
342
|
+
interface FakeReadyOptions {
|
|
343
|
+
durationMs?: number;
|
|
344
|
+
/** A poster image already in the genesis-chat bucket. */
|
|
345
|
+
posterPath?: string;
|
|
346
|
+
}
|
|
347
|
+
declare function fakeVideoProvider(options: FakeVideoProviderOptions): VideoProvider & {
|
|
348
|
+
markReady(providerId: string, ready?: FakeReadyOptions): Promise<void>;
|
|
349
|
+
handler(request: Request): Promise<Response>;
|
|
350
|
+
playbackUrl(playbackId: string): Promise<string>;
|
|
351
|
+
};
|
|
352
|
+
|
|
312
353
|
/**
|
|
313
354
|
* A plain `deliver` for apps without genesis-notifications yet: one Expo push
|
|
314
355
|
* per device token, in requests of 100 (https://docs.expo.dev/push-notifications/sending-notifications/).
|
|
@@ -797,4 +838,4 @@ interface VideoUploadHandlerOptions {
|
|
|
797
838
|
}
|
|
798
839
|
declare function createVideoUploadHandler(options: VideoUploadHandlerOptions): (request: Request) => Promise<Response>;
|
|
799
840
|
|
|
800
|
-
export { ANTHROPIC_MESSAGES_URL, ANTHROPIC_VERSION, type AnthropicLike, type AssistantMessage, type AssistantResult, CHAT_ASSISTANT_MODEL, CHAT_MAX_ATTEMPTS, CHAT_NOTIFICATION_CATEGORIES, CHAT_NOTIFICATION_TYPES, CHAT_NOTIFICATION_TYPE_ID, type ChatAssistant, type ChatAssistantOptions, type ChatDeliver, type ChatEffect, type ChatEffectKind, type ChatNotification, type ChatNotifyInput, type ChatRecipient, type ChatRunReport, type ChatRunner, type ChatRunnerHandlerOptions, type ChatRunnerOptions, type ChatUploadBinding, type DeepgramOptions, type DraftReplyInput, EXPO_BATCH, EXPO_SEND_URL, type ExpoDeliverOptions, type ExpoDeliveryTicket, type ExpoPushMessage, type ExpoPushTicket, type LinkPreview, type LinkUnfurler, type LinkUnfurlerOptions, type ModerateInput, type ModerationResult, type ModerationThresholds, type ModerationVerdict, type Moderator, type MuxSigningKey, type MuxVideoOptions, type NotificationsDeliverOptions, OPENAI_MODERATIONS_URL, OPENAI_MODERATION_CATEGORIES, type OpenAiModeratorOptions, type OpenAiTranscriberOptions, type RecapInput, type TranscribeInput, type TranscribeResult, type Transcriber, type VideoProvider, type VideoUploadHandlerOptions, carriesCredential, chatNotifyInput, chatRunnerHandler, chatUploadPassthrough, createChatAssistant, createChatRunner, createExpoDeliver, createLinkUnfurler, createVideoUploadHandler, deadTokens, decodeEntities, deepgramTranscriber, draftPrompt, guardedLookup, isPrivateAddress, moderationVerdict, muxPlaybackToken, muxPlaybackUrl, muxVideo, notificationsDeliver, openAiModerator, openAiTranscriber, parseChatUploadPassthrough, parseOpenGraph, recapPrompt, transcriptLines, verifyMuxSignature };
|
|
841
|
+
export { ANTHROPIC_MESSAGES_URL, ANTHROPIC_VERSION, type AnthropicLike, type AssistantMessage, type AssistantResult, CHAT_ASSISTANT_MODEL, CHAT_MAX_ATTEMPTS, CHAT_NOTIFICATION_CATEGORIES, CHAT_NOTIFICATION_TYPES, CHAT_NOTIFICATION_TYPE_ID, type ChatAssistant, type ChatAssistantOptions, type ChatDeliver, type ChatEffect, type ChatEffectKind, type ChatNotification, type ChatNotifyInput, type ChatRecipient, type ChatRunReport, type ChatRunner, type ChatRunnerHandlerOptions, type ChatRunnerOptions, type ChatUploadBinding, type DeepgramOptions, type DraftReplyInput, EXPO_BATCH, EXPO_SEND_URL, type ExpoDeliverOptions, type ExpoDeliveryTicket, type ExpoPushMessage, type ExpoPushTicket, type FakeReadyOptions, type FakeVideoProviderOptions, type LinkPreview, type LinkUnfurler, type LinkUnfurlerOptions, type ModerateInput, type ModerationResult, type ModerationThresholds, type ModerationVerdict, type Moderator, type MuxSigningKey, type MuxVideoOptions, type NotificationsDeliverOptions, OPENAI_MODERATIONS_URL, OPENAI_MODERATION_CATEGORIES, type OpenAiModeratorOptions, type OpenAiTranscriberOptions, type RecapInput, type TranscribeInput, type TranscribeResult, type Transcriber, type VideoProvider, type VideoUploadHandlerOptions, carriesCredential, chatNotifyInput, chatRunnerHandler, chatUploadPassthrough, createChatAssistant, createChatRunner, createExpoDeliver, createLinkUnfurler, createVideoUploadHandler, deadTokens, decodeEntities, deepgramTranscriber, draftPrompt, fakeVideoProvider, guardedLookup, isPrivateAddress, moderationVerdict, muxPlaybackToken, muxPlaybackUrl, muxVideo, notificationsDeliver, openAiModerator, openAiTranscriber, parseChatUploadPassthrough, parseOpenGraph, recapPrompt, transcriptLines, verifyMuxSignature };
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CHAT_BUCKET, CHAT_SCHEMA } from './chunk-GBJ3YI4E.js';
|
|
2
2
|
import { chatPushPayload } from './chunk-VEPZROQB.js';
|
|
3
3
|
|
|
4
4
|
// src/server/runner.ts
|
|
@@ -321,8 +321,8 @@ function deepgramTranscriber(options) {
|
|
|
321
321
|
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
322
322
|
});
|
|
323
323
|
if (!response.ok) return failed("Deepgram", response);
|
|
324
|
-
const
|
|
325
|
-
const channel =
|
|
324
|
+
const json4 = await response.json();
|
|
325
|
+
const channel = json4.results?.channels?.[0];
|
|
326
326
|
const text = channel?.alternatives?.[0]?.transcript;
|
|
327
327
|
if (typeof text !== "string") throw new Error("Deepgram answered without a transcript");
|
|
328
328
|
return { text: text.trim(), language: channel?.detected_language ?? input.language };
|
|
@@ -364,9 +364,9 @@ function openAiTranscriber(options) {
|
|
|
364
364
|
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
365
365
|
});
|
|
366
366
|
if (!response.ok) return failed("OpenAI", response);
|
|
367
|
-
const
|
|
368
|
-
if (typeof
|
|
369
|
-
return { text:
|
|
367
|
+
const json4 = await response.json();
|
|
368
|
+
if (typeof json4.text !== "string") throw new Error("OpenAI answered without a transcript");
|
|
369
|
+
return { text: json4.text.trim(), language: input.language };
|
|
370
370
|
}
|
|
371
371
|
};
|
|
372
372
|
}
|
|
@@ -700,6 +700,106 @@ function muxVideo(options) {
|
|
|
700
700
|
};
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
+
// src/server/fake-video.ts
|
|
704
|
+
var json2 = (status, body, headers = {}) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json", "cache-control": "no-store", ...headers } });
|
|
705
|
+
var EXTENSIONS2 = { "video/mp4": "mp4", "video/webm": "webm", "video/quicktime": "mov" };
|
|
706
|
+
var NotSentYet = class extends Error {
|
|
707
|
+
};
|
|
708
|
+
var nodeEnv = () => globalThis.process?.env?.NODE_ENV;
|
|
709
|
+
function fakeVideoProvider(options) {
|
|
710
|
+
const env = "env" in options ? options.env : nodeEnv();
|
|
711
|
+
if (env === "production" && !options.force) {
|
|
712
|
+
throw new Error("fakeVideoProvider is for development: it won't run with NODE_ENV=production (pass `force: true` to override)");
|
|
713
|
+
}
|
|
714
|
+
const name = options.name ?? "mux";
|
|
715
|
+
const db = () => options.supabase.schema(CHAT_SCHEMA);
|
|
716
|
+
const uploads = /* @__PURE__ */ new Map();
|
|
717
|
+
const newId = options.newId ?? (() => crypto.randomUUID());
|
|
718
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
719
|
+
const fakeHls = (providerId) => `https://fake-video.invalid/${encodeURIComponent(providerId)}.m3u8`;
|
|
720
|
+
async function find(providerId) {
|
|
721
|
+
const { data, error } = await db().from("attachments").select("id").eq("provider", name).eq("provider_id", providerId).limit(1).maybeSingle();
|
|
722
|
+
if (error) throw new Error(`attachments: ${error.code ?? "error"}`);
|
|
723
|
+
return data ?? null;
|
|
724
|
+
}
|
|
725
|
+
async function markReady(providerId, ready = {}) {
|
|
726
|
+
const attachment = await find(providerId);
|
|
727
|
+
if (!attachment) throw new NotSentYet(`fakeVideoProvider: no ${name} attachment with provider_id ${providerId} (is its message sent?)`);
|
|
728
|
+
const { error } = await db().rpc("update_attachment", {
|
|
729
|
+
p_attachment_id: attachment.id,
|
|
730
|
+
p_patch: {
|
|
731
|
+
status: "ready",
|
|
732
|
+
playback_id: `fake-${providerId}`,
|
|
733
|
+
url: fakeHls(providerId),
|
|
734
|
+
transcript_status: "off",
|
|
735
|
+
...typeof ready.durationMs === "number" ? { duration_ms: Math.round(ready.durationMs) } : {},
|
|
736
|
+
...ready.posterPath ? { poster_path: ready.posterPath } : {}
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
if (error) throw new Error(`update_attachment: ${error.code ?? "error"}`);
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
name,
|
|
743
|
+
async createUpload({ corsOrigin, passthrough }) {
|
|
744
|
+
const uploadId = `fake_${newId()}`;
|
|
745
|
+
uploads.set(uploadId, { passthrough, corsOrigin });
|
|
746
|
+
const url = new URL(options.uploadUrl);
|
|
747
|
+
url.searchParams.set("id", uploadId);
|
|
748
|
+
return { uploadId, url: url.toString() };
|
|
749
|
+
},
|
|
750
|
+
async remove(assetId) {
|
|
751
|
+
const id = assetId.replace(/^fake-/, "");
|
|
752
|
+
const path = uploads.get(id)?.path;
|
|
753
|
+
uploads.delete(id);
|
|
754
|
+
if (path) await options.supabase.storage.from(CHAT_BUCKET).remove([path]);
|
|
755
|
+
},
|
|
756
|
+
/** There is no webhook: `handler` (or `markReady`) does its work. */
|
|
757
|
+
async handleWebhook() {
|
|
758
|
+
return json2(200, { ignored: "the fake provider has no webhook; call markReady" });
|
|
759
|
+
},
|
|
760
|
+
markReady,
|
|
761
|
+
async playbackUrl(playbackId) {
|
|
762
|
+
const path = uploads.get(playbackId.replace(/^fake-/, ""))?.path;
|
|
763
|
+
if (!path) return fakeHls(playbackId.replace(/^fake-/, ""));
|
|
764
|
+
const { data, error } = await options.supabase.storage.from(CHAT_BUCKET).createSignedUrl(path, 3600);
|
|
765
|
+
if (error || !data?.signedUrl) throw new Error("fakeVideoProvider: couldn't sign the stored file");
|
|
766
|
+
return data.signedUrl;
|
|
767
|
+
},
|
|
768
|
+
async handler(request) {
|
|
769
|
+
const id = new URL(request.url).searchParams.get("id") ?? "";
|
|
770
|
+
const upload = uploads.get(id);
|
|
771
|
+
const cors = { "access-control-allow-origin": upload?.corsOrigin ?? "*", vary: "origin" };
|
|
772
|
+
if (request.method === "OPTIONS") {
|
|
773
|
+
return new Response(null, {
|
|
774
|
+
status: 204,
|
|
775
|
+
headers: { ...cors, "access-control-allow-methods": "PUT, OPTIONS", "access-control-allow-headers": "content-type, content-range", "access-control-max-age": "600" }
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
if (request.method !== "PUT") return json2(405, { error: "method not allowed" }, cors);
|
|
779
|
+
if (!upload) return json2(404, { error: "no such upload" }, cors);
|
|
780
|
+
const binding = parseChatUploadPassthrough(upload.passthrough);
|
|
781
|
+
const contentType = request.headers.get("content-type") ?? "video/mp4";
|
|
782
|
+
const path = `${binding?.conversationId ?? "fake-video"}/${binding?.userId ?? "fake-video"}/${id}.${EXTENSIONS2[contentType] ?? "mp4"}`;
|
|
783
|
+
const body = await request.arrayBuffer();
|
|
784
|
+
const { error } = await options.supabase.storage.from(CHAT_BUCKET).upload(path, body, { contentType, upsert: true });
|
|
785
|
+
if (error) return json2(502, { error: `storage: ${error.message}` }, cors);
|
|
786
|
+
upload.path = path;
|
|
787
|
+
const deadline = Date.now() + (options.readyWaitMs ?? 1e4);
|
|
788
|
+
for (; ; ) {
|
|
789
|
+
try {
|
|
790
|
+
await markReady(id);
|
|
791
|
+
return json2(200, { ok: true, providerId: id, path }, cors);
|
|
792
|
+
} catch (e) {
|
|
793
|
+
if (Date.now() >= deadline || !(e instanceof NotSentYet)) {
|
|
794
|
+
return json2(202, { stored: true, ready: false, providerId: id, path, error: e instanceof Error ? e.message : String(e) }, cors);
|
|
795
|
+
}
|
|
796
|
+
await sleep(500);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
703
803
|
// src/server/expo.ts
|
|
704
804
|
var EXPO_SEND_URL = "https://exp.host/--/api/v2/push/send";
|
|
705
805
|
var EXPO_BATCH = 100;
|
|
@@ -746,8 +846,8 @@ function createExpoDeliver(options) {
|
|
|
746
846
|
});
|
|
747
847
|
throw new Error(`Expo push answered HTTP ${response.status}`);
|
|
748
848
|
}
|
|
749
|
-
const
|
|
750
|
-
answer = Array.isArray(
|
|
849
|
+
const json4 = await response.json();
|
|
850
|
+
answer = Array.isArray(json4.data) ? json4.data : [];
|
|
751
851
|
sent += chunk.length;
|
|
752
852
|
} catch (e) {
|
|
753
853
|
lastError = e;
|
|
@@ -770,17 +870,17 @@ function deadTokens(tickets) {
|
|
|
770
870
|
}
|
|
771
871
|
|
|
772
872
|
// src/server/handler.ts
|
|
773
|
-
var
|
|
873
|
+
var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json", "cache-control": "no-store" } });
|
|
774
874
|
function chatRunnerHandler(options) {
|
|
775
875
|
return async (request) => {
|
|
776
|
-
if (!options.secret || options.secret.length < 32) return
|
|
777
|
-
if (request.method !== "POST" && request.method !== "GET") return
|
|
876
|
+
if (!options.secret || options.secret.length < 32) return json3(500, { error: "runner secret missing or under 32 characters" });
|
|
877
|
+
if (request.method !== "POST" && request.method !== "GET") return json3(405, { error: "method not allowed" });
|
|
778
878
|
const match = /^Bearer\s+(\S+)$/i.exec(request.headers.get("authorization") ?? "");
|
|
779
|
-
if (!match || !await safeEqual(match[1], options.secret)) return
|
|
879
|
+
if (!match || !await safeEqual(match[1], options.secret)) return json3(401, { error: "unauthorized" });
|
|
780
880
|
try {
|
|
781
|
-
return
|
|
881
|
+
return json3(200, await options.runner.run());
|
|
782
882
|
} catch (e) {
|
|
783
|
-
return
|
|
883
|
+
return json3(500, { error: e instanceof Error ? e.message.slice(0, 200) : "run failed" });
|
|
784
884
|
}
|
|
785
885
|
};
|
|
786
886
|
}
|
|
@@ -1399,6 +1499,6 @@ function createChatAssistant(options) {
|
|
|
1399
1499
|
};
|
|
1400
1500
|
}
|
|
1401
1501
|
|
|
1402
|
-
export { ANTHROPIC_MESSAGES_URL, ANTHROPIC_VERSION, CHAT_ASSISTANT_MODEL, CHAT_MAX_ATTEMPTS, CHAT_NOTIFICATION_CATEGORIES, CHAT_NOTIFICATION_TYPES, CHAT_NOTIFICATION_TYPE_ID, EXPO_BATCH, EXPO_SEND_URL, OPENAI_MODERATIONS_URL, OPENAI_MODERATION_CATEGORIES, carriesCredential, chatNotifyInput, chatRunnerHandler, chatUploadPassthrough, createChatAssistant, createChatRunner, createExpoDeliver, createLinkUnfurler, createVideoUploadHandler, deadTokens, decodeEntities, deepgramTranscriber, draftPrompt, guardedLookup, isPrivateAddress, moderationVerdict, muxPlaybackToken, muxPlaybackUrl, muxVideo, notificationsDeliver, openAiModerator, openAiTranscriber, parseChatUploadPassthrough, parseOpenGraph, recapPrompt, transcriptLines, verifyMuxSignature };
|
|
1502
|
+
export { ANTHROPIC_MESSAGES_URL, ANTHROPIC_VERSION, CHAT_ASSISTANT_MODEL, CHAT_MAX_ATTEMPTS, CHAT_NOTIFICATION_CATEGORIES, CHAT_NOTIFICATION_TYPES, CHAT_NOTIFICATION_TYPE_ID, EXPO_BATCH, EXPO_SEND_URL, OPENAI_MODERATIONS_URL, OPENAI_MODERATION_CATEGORIES, carriesCredential, chatNotifyInput, chatRunnerHandler, chatUploadPassthrough, createChatAssistant, createChatRunner, createExpoDeliver, createLinkUnfurler, createVideoUploadHandler, deadTokens, decodeEntities, deepgramTranscriber, draftPrompt, fakeVideoProvider, guardedLookup, isPrivateAddress, moderationVerdict, muxPlaybackToken, muxPlaybackUrl, muxVideo, notificationsDeliver, openAiModerator, openAiTranscriber, parseChatUploadPassthrough, parseOpenGraph, recapPrompt, transcriptLines, verifyMuxSignature };
|
|
1403
1503
|
//# sourceMappingURL=server.js.map
|
|
1404
1504
|
//# sourceMappingURL=server.js.map
|