@marktiderman/genesis-chat 0.1.0 → 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,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @feat:chat
|
|
3
|
+
* Two cards under a message: `ChatLinkPreview`, a link's title, description, site and image
|
|
4
|
+
* (all plain text from Open Graph, never HTML), and
|
|
5
|
+
* `ChatAttachmentCard`, the generic card apps use for their own attachment kinds (a form, a
|
|
6
|
+
* check-in): a title, a subtitle and one action.
|
|
7
|
+
*/
|
|
8
|
+
import { useState, type ComponentType } from "react";
|
|
9
|
+
import { ClipboardList, ExternalLink } from "lucide-react";
|
|
10
|
+
import { Button, cn } from "@marktiderman/genesis-ui";
|
|
11
|
+
import { isHttpsUrl } from "../core/extras";
|
|
12
|
+
import type { ChatLinkPreview as LinkPreviewData } from "../core/types";
|
|
13
|
+
import { isSafeHref } from "./markdown";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether a link preview shows its image. The image lives on the linked site, so loading it
|
|
17
|
+
* straight from there tells that site each reader's IP address and when they read the message
|
|
18
|
+
* (a tracking pixel the sender controls). So:
|
|
19
|
+
* - `"off"` (the default): no image;
|
|
20
|
+
* - `"proxy"`: through your own image proxy, `proxyUrl(imageUrl)`; no image without it;
|
|
21
|
+
* - `"direct"`: from the linked site (only if you accept that).
|
|
22
|
+
*/
|
|
23
|
+
export type LinkPreviewImages = "off" | "proxy" | "direct";
|
|
24
|
+
|
|
25
|
+
/** The image a preview may show under this setting, or null. Only https image URLs are ever considered. */
|
|
26
|
+
export function linkPreviewImageSrc(imageUrl: string | null | undefined, images: LinkPreviewImages = "off", proxyUrl?: (url: string) => string | null | undefined): string | null {
|
|
27
|
+
if (!isHttpsUrl(imageUrl)) return null;
|
|
28
|
+
if (images === "direct") return imageUrl;
|
|
29
|
+
if (images !== "proxy" || !proxyUrl) return null;
|
|
30
|
+
try {
|
|
31
|
+
return proxyUrl(imageUrl) || null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ChatLinkPreviewProps {
|
|
38
|
+
preview: LinkPreviewData;
|
|
39
|
+
/** Whether to show its image, and how; `"off"` by default (see LinkPreviewImages). */
|
|
40
|
+
images?: LinkPreviewImages;
|
|
41
|
+
/** Your image proxy, for `images: "proxy"`: the URL to load instead of the linked site's. */
|
|
42
|
+
proxyUrl?: (url: string) => string | null | undefined;
|
|
43
|
+
tone?: "default" | "primary";
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ChatLinkPreview({ preview, images = "off", proxyUrl, tone = "default", className }: ChatLinkPreviewProps) {
|
|
48
|
+
const [imageFailed, setImageFailed] = useState(false);
|
|
49
|
+
if (preview.status !== "ok" || !isSafeHref(preview.url) || !isHttpsUrl(preview.url)) return null;
|
|
50
|
+
let host = preview.url;
|
|
51
|
+
try {
|
|
52
|
+
host = new URL(preview.url).hostname.replace(/^www\./, "");
|
|
53
|
+
} catch {
|
|
54
|
+
// shown as written
|
|
55
|
+
}
|
|
56
|
+
const image = imageFailed ? null : linkPreviewImageSrc(preview.imageUrl, images, proxyUrl);
|
|
57
|
+
const title = preview.title?.trim() || host;
|
|
58
|
+
const primary = tone === "primary";
|
|
59
|
+
return (
|
|
60
|
+
<a
|
|
61
|
+
href={preview.url}
|
|
62
|
+
target="_blank"
|
|
63
|
+
rel="noopener noreferrer nofollow"
|
|
64
|
+
referrerPolicy="no-referrer"
|
|
65
|
+
className={cn(
|
|
66
|
+
"mt-1.5 flex w-[min(20rem,100%)] flex-col overflow-hidden rounded-lg border-l-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
67
|
+
primary ? "border-primary-foreground/60 bg-primary-foreground/15" : "border-primary bg-background hover:bg-accent",
|
|
68
|
+
className,
|
|
69
|
+
)}
|
|
70
|
+
aria-label={`${title}, ${preview.siteName ?? host}. Open link`}
|
|
71
|
+
data-testid="genesis-chat-link-preview"
|
|
72
|
+
>
|
|
73
|
+
{image ? (
|
|
74
|
+
<img
|
|
75
|
+
src={image}
|
|
76
|
+
alt=""
|
|
77
|
+
loading="lazy"
|
|
78
|
+
referrerPolicy="no-referrer"
|
|
79
|
+
onError={() => setImageFailed(true)}
|
|
80
|
+
className="aspect-[1.91/1] w-full bg-muted object-cover"
|
|
81
|
+
data-testid="genesis-chat-link-preview-image"
|
|
82
|
+
/>
|
|
83
|
+
) : null}
|
|
84
|
+
<span className="flex flex-col gap-0.5 px-2.5 py-2">
|
|
85
|
+
<span className={cn("truncate text-[11px] uppercase tracking-wide", primary ? "text-primary-foreground/75" : "text-muted-foreground")}>{preview.siteName ?? host}</span>
|
|
86
|
+
<span className="line-clamp-2 text-sm font-semibold leading-snug">{title}</span>
|
|
87
|
+
{preview.description ? <span className={cn("line-clamp-2 text-xs", primary ? "text-primary-foreground/85" : "text-muted-foreground")}>{preview.description}</span> : null}
|
|
88
|
+
</span>
|
|
89
|
+
</a>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ChatAttachmentCardProps {
|
|
94
|
+
title: string;
|
|
95
|
+
subtitle?: string | null;
|
|
96
|
+
/** An icon component (lucide); a clipboard by default. */
|
|
97
|
+
icon?: ComponentType<{ className?: string }>;
|
|
98
|
+
/** One button: a link (`href`, safe schemes only) or a handler. */
|
|
99
|
+
action?: { label: string; href?: string; onPress?: () => void } | null;
|
|
100
|
+
tone?: "default" | "primary";
|
|
101
|
+
className?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A card for an app's own attachment kind: an icon, a title, a subtitle and one action. */
|
|
105
|
+
export function ChatAttachmentCard({ title, subtitle, icon: Icon = ClipboardList, action, tone = "default", className }: ChatAttachmentCardProps) {
|
|
106
|
+
const primary = tone === "primary";
|
|
107
|
+
const href = action?.href && isSafeHref(action.href) ? action.href : null;
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
className={cn("flex w-[min(20rem,100%)] items-center gap-3 rounded-lg p-2.5", primary ? "bg-primary-foreground/15" : "border bg-background", className)}
|
|
111
|
+
data-testid="genesis-chat-attachment-card"
|
|
112
|
+
>
|
|
113
|
+
<span className={cn("flex size-10 shrink-0 items-center justify-center rounded-md", primary ? "bg-primary-foreground/20" : "bg-primary/10 text-primary")}>
|
|
114
|
+
<Icon className="size-5" aria-hidden />
|
|
115
|
+
</span>
|
|
116
|
+
<span className="min-w-0 flex-1">
|
|
117
|
+
<span className="block truncate text-sm font-semibold">{title}</span>
|
|
118
|
+
{subtitle ? <span className={cn("block truncate text-xs", primary ? "text-primary-foreground/80" : "text-muted-foreground")}>{subtitle}</span> : null}
|
|
119
|
+
</span>
|
|
120
|
+
{action && (href || action.onPress) ? (
|
|
121
|
+
href ? (
|
|
122
|
+
<Button asChild size="sm" variant={primary ? "secondary" : "default"} className="shrink-0">
|
|
123
|
+
<a href={href} target={/^https?:/i.test(href) ? "_blank" : undefined} rel="noopener noreferrer">
|
|
124
|
+
{action.label}
|
|
125
|
+
{/^https?:/i.test(href) ? <ExternalLink aria-hidden /> : null}
|
|
126
|
+
</a>
|
|
127
|
+
</Button>
|
|
128
|
+
) : (
|
|
129
|
+
<Button type="button" size="sm" variant={primary ? "secondary" : "default"} className="shrink-0" onClick={action.onPress}>
|
|
130
|
+
{action.label}
|
|
131
|
+
</Button>
|
|
132
|
+
)
|
|
133
|
+
) : null}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|