@ 12- apps/ 聊天
双方就一项工作进行对话,其中每一项 派对是别人知道的 只能通过它的标签无身份证明,姓名 或联系细节穿过电线。 每个观众登上一次航道 同一线程屏幕在网络 SPA 和 React Industrial app 中制作.
盒子里装的是什么
| 子路径 | 什麽 |
|---|---|
./server | createApiChat(config) → { routes }:框架中立 GET /, (中文(简体) ), POST /messages, (中文(简体) ), POST /read。 。 。 . createChatReads 为主机自己的列表提供未读的徽章. |
./client | createChatClient 财务报告和审定财务报表 useChatThread,这两个平台都是免费的:为主机绘制自己的UI. |
./react | createWebChat(config) → { ChatThread }:网络表面. |
./native | createNativeChat(config) → { ChatThread }:反应原生地表. |
./manifest* | 用于服务器、网络和本地运行时间的线条清单. |
prisma/ | chat.prisma (单位:千美元)ChatThread, (中文(简体) ), ChatMessage, (中文(简体) ), ChatReadMarker加上其迁移. pnpm --filter @12-apps/chat prisma:sync 将部分复制到主机计划文件夹. |
主持人决定谁在线上
const { routes } = createApiChat({
db: () => getPrismaClient(),
authorize: async (request) => {
// Is this caller a party to the thread the URL names? Under which role?
// May that role still write? Answer null for "no such thread for you" (404).
return { tenantId, threadKey: `job:${request.params.id}`, role: "client",
authorId: user.id, readerId: user.id, canWrite: true };
},
roles: {
client: { label: "Client", shared: false, maxLength: 500, freeText: true,
blockContact: [], quickReplies: [], rateLimit: { max: 20, windowMs: 60_000 } },
agent: { label: "Agent", shared: false, maxLength: 160, freeText: true,
blockContact: ["phone", "email", "url", "handle"],
quickReplies: [{ key: "arrived", text: "I have arrived." }],
rateLimit: { max: 6, windowMs: 60_000 } },
team: { label: "Team", shared: true, maxLength: 1000, freeText: true,
blockContact: [], quickReplies: [], rateLimit: { max: 30, windowMs: 60_000 } },
},
copy: EN_US_CHAT_SERVER_COPY, // or a per-request resolver
contactVocabulary: { numberWords: { nine: "9" }, atWords: ["at"] },
onMessage: async (event) => { /* notify, publish a live hint */ },
onRefused: async (event) => { /* who keeps trying to pass contact info */ },
onError: (error, { hook }) => logger.error(`chat ${hook} failed`, error),
});
db 可能归还客户或承诺. onMessage 财务报告和审定财务报表 onRefused
在背景中运行:不等待推就发送答案
提供商,并报告投出或拒绝的钩子 onError永远
给发送者.
所有这一切都属于主机: 写出窗口, 一个呼叫者所扮演的角色, 标签,和快速回复的词。 软件包拥有其余:
- 角色的内容规则;
- 联系人- info过滤器,它也读取主机的躲避字;
- 每作者的费率限额;
- 电线上的信息看起来像什么.
电线
每2个回答 { data }数字 : GET / 给 { thread, messages, unread }, (中文(简体) ),
POST /messages 给 { message } (201个)和 POST /read 给
{ unread }拒绝答复 { error, message }时 error 这是
软件包的代码 (contact_info, (中文(简体) ), closed, (中文(简体) ), rate_limited,(......)和 message 实值
主持人的句子。 作者只表现为
角色的标签.
POST /read 带 { upTo },则 createdAt 最新讯息
读者实际上被显示。 标记从不向后移动,从不过去
所以在屏幕装入后传到的讯息 仍然没有被读取 阅读
永不创建线程行; 第一个消息会.
联系人过滤器
它在Unicode 正常化后读取文本:全宽和其他标注
数字,隐形字符,看起来像西里尔字母和希腊字母,a
逆向跑 从主机的拼出数字
contactVocabulary电话号码是16个字符中的8位数,
不管他们分开。 时钟时间,货币符号后的金额,以及
匹配主机的横跨 neutralPatterns (地址单位如
apto 1204,最多每个4位数字)先被预留,因此地址与
公寓号码; 预留跨度仍为至少一个
数字. 只有在作为信使写作的价格时,才留出一笔金额
1,最多4个整数85,90, (中文(简体) ), 1.250,00),这样 $54321, (中文(简体) ),
$98765,43 财务报告和审定财务报表 $8.765.432 没有。 CEP,全日期,或
单位拒绝前的5位数街道号码;即有文件证明的街道号码
价钱.
在信件中,它只读取一个相接的链条:同一个作者最近写的
免费文本( e)contactLookback,默认在 10 以内的最后 12 个消息
分钟)加入草案,而每条消息结束于下一个开始之处
数字、点或符号。 每个消息中一个数字
一号; Apto 1204 接下来是 Chego 19:30 是两行。 一个作者
发送到线程在每个过程中一次检查一个, 所以并行
请求不能通过对对方缺席的规则(若干请求)
复制品仍可互出). Bidi 控件在消息发出前被移除
因此屏幕显示过滤器读取的内容.
每一次尝试都花一个限速档, 包括一个被拒绝的,所以测试
过滤器已耗尽。 这是一种威慑,而不是一种保证:a
确定通过数字的人总是可以找到编码,没有过滤器
读取 onRefused 东道主如何看待谁一直在尝试。 已知错过,
让一条普通行穿过的代价:
数字的块(98765 e 4321),9个或10个位数的平分数
以一个地址单位或一个价格计算(98 765 ap 4321, (中文(简体) ), 9876 $5.432 a 包括:
第一组内的空间正是街道号码的写法),
单位或价格后面的两块(apto 98765 apto 4321),链断了
写着别的东西和照片 带有区域代码的移动
(偶数) 无法隐藏在一码后。 一个希望 arroba 要计数
向社会手柄增加 extraPatterns。 。 。 .
保留
信件保存 author_id 为节制,包船不删除
或匿名API:保留(LGPD,GDPR)是主机. 这个
chat_messages (tenant_id, created_at) 索引用于主机的扫描.
created_at 来源于应用程序的时钟, 和相同的信息
毫秒由id订购.
主机自身查询索引
chat_threads (tenant_id, last_message_at) 1.1.0,移徙
20261003120000_add_chat_thread_activity_index) 服务于主机徽章
将线程数起自某一刻起的活动(tenant_id = ? AND last_message_at >= ?) (中文(简体) ). 复制软件包的移取主机
与它的下个同步; 它是添加剂, 所以它不需要释放的舞蹈.
屏幕
const { ChatThread } = createWebChat({ fetch: credentialedFetch, copy: EN_US_CHAT_UI_COPY, formatTime });
<ChatThread endpoint={`/api/jobs/${id}/chat`} refreshSignal={liveTick} onUnreadChange={setBadge} />
该线条本身 读到它显示的最新信息
onUnreadChange 然后得到服务器的剩余数(通常为0). 过
autoMarkRead={false} 仅用于未读数.
autoMarkRead="visible" 是为一线可挂出视线的
抽屉下半部分: 装入未读信件等待信件列表
显示在屏幕上,然后被标记为读取;在屏幕上找到未读的负载
列表已同时在屏幕标记上。 在网络上,名单被监视
a 一个 IntersectionObserver (在屏幕上从第一个像素开始显示);一个没有
一分一分,作为 true 已经 在本地,一个挂载列表算
屏幕。 jsdom stubs 的主机测试 IntersectionObserver 来驾驶它.
一个主机绘制自己的列表 useChatThread({ autoMarkRead: "visible" })
通过报告 controls.onVisibleChange(visible)。 。 。 .
仅读线程( E)canWrite: false没有信件只显示关闭的
通知:没有写出第一个信件的空行邀请,也没有列表.
自己发的短信和别人的留言 都从表面上说:读者的
自己的泡泡是主题的一丁点 palette.primary.main 光线为8%
模式,16%在黑暗中),其他人坐在纸张表面. 标签是身体
文字本身的锡和纸上的次级文本; 主要颜色本身
在4.5:1下,它本身的锡,所以它不用于文字。 气泡最多为线宽的80%(60个)
字符,在网络上).
无障碍:清单是一个礼貌的活区(第1段)aria-live 在网上,
accessibilityLiveRegion 因此,一个到达的消息被宣布.
在网络上点击一个发送按钮的手聚焦到球场上
角色的 maxLength 坐在这 <input> 自己来.
布局 :
- 网络 : 留言就坐在这里
ScrollArea(最多60%的取景点) 高高的),它保持了最新的视野.ScrollArea需求ResizeObserver, (中文(简体) ), 因此,主机测试 在jsdom 悬浮它. - 原生 : 线条是一条"ui"
Screen键盘和它自己的ScrollView。将其挂入一个有界限高度的母体(一个弹性一视图,或Screen scroll={false}永远不要在另一个ScrollView。 过keyboardOffset(dp) 当一头相上而坐.
createNativeChat 使用相同的配置。 两个表面都取出线程
@12-apps/ui 原始人只使用他们的跨平台道具,所以反应
原生的捆绑会把每个原始人 解决成其原生的双胞胎
react-native 出口条件。 每个平台的不同之处是
内部 ChatPlatform一个平台的造型
| 插槽 | 接收 | 网络 | 本地语言 |
|---|---|---|---|
Frame | 列表和写入区域 | (单位:千美元) Stack | 键盘避免 Screen |
MessageList | 喷出出出水泡, label, (中文(简体) ), scrollKey, (中文(简体) ), onVisibleChange? | ScrollArea + 键 IntersectionObserver | ScrollView,在挂载时可见 |
Bubble | { mine, children } | 欧 Box 与 sx (图, min(80%, 60ch)(中文(简体) ) | 欧 Box 与 style (图, 80%(中文(简体) ) |
Composer | 控制字段及其发送动作 | Input + 输入 | Input + 键盘发送键 |
QuickReplies | 芯片的密钥和标签,标题 | 欧 Chips, 包装 | 欧 Chips或药丸; 包装,或一条侧行 |
Notice | 发送失败的行、 语气和外观 | 欧 Alert,或图标 + 标题 | 相同,按 onNoticeHeight |
软件包没有打开套接字 。 弹出 refreshSignal 当你的直播频道说
线条移动.
主机选项
两个表层都把这些放在配置上,都是可选的;一个没有它们的配置
和以前一样画线条 列出值以外的值抛出
ChatConfigError 当地表被建造的时候.
| 配置 | 默认 | 变化是什么 |
|---|---|---|
quickReplyLabel(key, text) | 服务器的句子 | 芯片的标签。 对于比较简短的回答名称的芯片:一盘水龙头仍然发送出"关键",因此线程存储了完整的句子. 返回时 text 对于一个你不知道的钥匙. |
quickRepliesHeading | "visible" | "label" 滴数 copy.quickReplies 从屏幕上保留它作为行的可访问名称. |
emptyDescription | "always" | "withQuickReplies" 显示 copy.emptyDescription 仅在下面绘制芯片时,用于指向芯片的描述. |
sendFailureMessage(failure) | 服务器句子, 否则 copy.sendFailed | 发送失败的行显示; 返回默认无效 . failure.code 是软件包的( a)contact_info(中文(简体) ). |
sendFailureNotice | "alert" | "compact":一个图标,在作曲家上最多两行,一旦编辑草稿就消失;a contact_info 拒绝读作警告, 任何其它的都是错误. "alert" 留在芯片上直到下一个发送. |
sendWhenEmpty | "disabled" | "inert" 保留空白字段中所绘制的发送按钮;空白的发送不起作用. |
createNativeChat 并且需要两个只画出原生行 :
| 配置 | 默认 | 变化是什么 |
|---|---|---|
quickReplyLook | "chip" | "pill": 中性填充药丸并有粗体标签,至少 NATIVE_CHAT_METRICS.quickReplyMinHeight (40)dp高,取而代之的是i's 32-tall 概述芯片. |
quickReplyScrollBelowWidth | 无( 总是包) | 在这个窗口宽度下方,在dp中,芯片坐落在一行滚动的侧道上,而不是包接. |
芯片坐着 NATIVE_CHAT_METRICS.quickReplyGap (8)dp 分道扬镳.
ChatThread 3个道具 用于一个主机 大小的房间周围
线程( 带键盘的小电话) :
foldQuickReplies把筹码放出去 草稿和名单都留下.onNoticeHeight(height)发送失败通知的高度为 dp 每个布局,当没有绘制时,则0(除去后,线程转动) 只读,未挂载). 它排除了通知上的空白:theme.spacing(0.5)在作曲家 在紧凑的外表,theme.spacing(2)在警戒状态.onComposerFocusChange(focused)报告田间采取和丢失 重点,以及false当它消失的时候,它集中, 所以一个宿主隐藏它 铬,而有人 类型永远不会隐藏.
复制
每个句子都需要配置,没有默认. PT_BR_* 财务报告和审定财务报表 EN_US_*
为航道打包船(CHAT_SERVER_COPY)和屏幕
(单位:千美元)CHAT_UI_COPY;通过一手。 角色标签是角色配置的一部分,
所以每个表面都显示一个角色的同名.