@fayz-ai/plugin-conversations 0.23.15 → 0.24.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.
@@ -1,84 +1,11 @@
1
1
  import { providersApi } from './chunk-HMWUU5O7.js';
2
- import { getSupabaseClientOptional } from '@fayz-ai/core';
3
2
  import React from 'react';
4
3
  import { Loader2, Check, ShieldCheck, ShieldAlert, RefreshCw, QrCode, Zap } from 'lucide-react';
5
4
  import { SettingsGroup } from '@fayz-ai/admin';
6
5
  import { Input, Button, toast } from '@fayz-ai/ui';
7
6
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
7
+ import { getSupabaseClientOptional } from '@fayz-ai/core';
8
8
 
9
- // src/data/tables.ts
10
- var T = {
11
- conversations: "plg_conversations",
12
- /** A LEITURA da conversa: a mesma linha, com o dono do lead junto. A escrita
13
- * continua na tabela — view com `security_invoker` não aceita INSERT aqui. */
14
- conversationsView: "v_conversations",
15
- messages: "plg_conversation_messages",
16
- channels: "plg_conversations_channels"
17
- };
18
-
19
- // src/data/channels.ts
20
- function mapChannel(r) {
21
- return {
22
- id: String(r.id),
23
- tenantId: r.tenant_id ?? null,
24
- channel: r.channel ?? "whatsapp",
25
- provider: String(r.provider ?? ""),
26
- providerNumberId: String(r.provider_number_id ?? ""),
27
- phoneE164: r.phone_e164 ?? null,
28
- kind: r.kind ?? "dedicated",
29
- status: r.status ?? "requested",
30
- displayName: r.display_name ?? null,
31
- metadata: r.metadata ?? {},
32
- createdAt: String(r.created_at ?? ""),
33
- updatedAt: String(r.updated_at ?? "")
34
- };
35
- }
36
- async function listMessagingChannels(options = {}) {
37
- const supabase = options.supabaseClient ?? getSupabaseClientOptional();
38
- if (!supabase) {
39
- throw new Error(
40
- "[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp."
41
- );
42
- }
43
- let q = supabase.from(T.channels).select("*");
44
- if (options.provider) q = q.eq("provider", options.provider);
45
- if (options.channel) q = q.eq("channel", options.channel);
46
- q = q.order("kind", { ascending: true });
47
- const { data, error } = await q;
48
- if (error) throw error;
49
- return (data ?? []).map(mapChannel);
50
- }
51
- var CHANNEL_CLAIM_TIMELINE = [
52
- "requested",
53
- "provisioning",
54
- "provisioned",
55
- "verifying",
56
- "active"
57
- ];
58
- var LIVE_CHANNEL_STATUSES = [
59
- "requested",
60
- "provisioning",
61
- "provisioned",
62
- "verifying",
63
- "active"
64
- ];
65
- function isDeadChannelStatus(status) {
66
- return !LIVE_CHANNEL_STATUSES.includes(status);
67
- }
68
- function isPendingChannelStatus(status) {
69
- return LIVE_CHANNEL_STATUSES.includes(status) && status !== "active";
70
- }
71
- function findDedicatedChannel(channels, tenantId) {
72
- return channels.find(
73
- (channel) => channel.kind === "dedicated" && (!tenantId || channel.tenantId === tenantId) && !isDeadChannelStatus(channel.status)
74
- ) ?? null;
75
- }
76
- function findFallbackChannel(channels) {
77
- return channels.find((channel) => channel.kind === "fallback") ?? null;
78
- }
79
- function canClaimDedicated(channels, tenantId) {
80
- return findDedicatedChannel(channels, tenantId) === null;
81
- }
82
9
  var COPY = {
83
10
  tyxter: {
84
11
  name: "Tyxter",
@@ -313,6 +240,80 @@ function WhatsAppProviders({ only, onChanged } = {}) {
313
240
  );
314
241
  }
315
242
 
243
+ // src/data/tables.ts
244
+ var T = {
245
+ conversations: "plg_conversations",
246
+ /** A LEITURA da conversa: a mesma linha, com o dono do lead junto. A escrita
247
+ * continua na tabela — view com `security_invoker` não aceita INSERT aqui. */
248
+ conversationsView: "v_conversations",
249
+ messages: "plg_conversation_messages",
250
+ channels: "plg_conversations_channels"
251
+ };
252
+
253
+ // src/data/channels.ts
254
+ function mapChannel(r) {
255
+ return {
256
+ id: String(r.id),
257
+ tenantId: r.tenant_id ?? null,
258
+ channel: r.channel ?? "whatsapp",
259
+ provider: String(r.provider ?? ""),
260
+ providerNumberId: String(r.provider_number_id ?? ""),
261
+ phoneE164: r.phone_e164 ?? null,
262
+ kind: r.kind ?? "dedicated",
263
+ status: r.status ?? "requested",
264
+ displayName: r.display_name ?? null,
265
+ metadata: r.metadata ?? {},
266
+ createdAt: String(r.created_at ?? ""),
267
+ updatedAt: String(r.updated_at ?? "")
268
+ };
269
+ }
270
+ async function listMessagingChannels(options = {}) {
271
+ const supabase = options.supabaseClient ?? getSupabaseClientOptional();
272
+ if (!supabase) {
273
+ throw new Error(
274
+ "[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp."
275
+ );
276
+ }
277
+ let q = supabase.from(T.channels).select("*");
278
+ if (options.provider) q = q.eq("provider", options.provider);
279
+ if (options.channel) q = q.eq("channel", options.channel);
280
+ q = q.order("kind", { ascending: true });
281
+ const { data, error } = await q;
282
+ if (error) throw error;
283
+ return (data ?? []).map(mapChannel);
284
+ }
285
+ var CHANNEL_CLAIM_TIMELINE = [
286
+ "requested",
287
+ "provisioning",
288
+ "provisioned",
289
+ "verifying",
290
+ "active"
291
+ ];
292
+ var LIVE_CHANNEL_STATUSES = [
293
+ "requested",
294
+ "provisioning",
295
+ "provisioned",
296
+ "verifying",
297
+ "active"
298
+ ];
299
+ function isDeadChannelStatus(status) {
300
+ return !LIVE_CHANNEL_STATUSES.includes(status);
301
+ }
302
+ function isPendingChannelStatus(status) {
303
+ return LIVE_CHANNEL_STATUSES.includes(status) && status !== "active";
304
+ }
305
+ function findDedicatedChannel(channels, tenantId) {
306
+ return channels.find(
307
+ (channel) => channel.kind === "dedicated" && (!tenantId || channel.tenantId === tenantId) && !isDeadChannelStatus(channel.status)
308
+ ) ?? null;
309
+ }
310
+ function findFallbackChannel(channels) {
311
+ return channels.find((channel) => channel.kind === "fallback") ?? null;
312
+ }
313
+ function canClaimDedicated(channels, tenantId) {
314
+ return findDedicatedChannel(channels, tenantId) === null;
315
+ }
316
+
316
317
  export { CHANNEL_CLAIM_TIMELINE, COPY, T, WhatsAppProviders, canClaimDedicated, findDedicatedChannel, findFallbackChannel, isPendingChannelStatus, listMessagingChannels };
317
- //# sourceMappingURL=chunk-3FZX3ZQ3.js.map
318
- //# sourceMappingURL=chunk-3FZX3ZQ3.js.map
318
+ //# sourceMappingURL=chunk-2JV6B366.js.map
319
+ //# sourceMappingURL=chunk-2JV6B366.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/settings/WhatsAppProviders.tsx","../src/data/tables.ts","../src/data/channels.ts"],"names":[],"mappings":"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","file":"chunk-2JV6B366.js","sourcesContent":["import React from 'react'\nimport { Check, Loader2, QrCode, RefreshCw, ShieldAlert, ShieldCheck, Zap } from 'lucide-react'\nimport { SettingsGroup } from '@fayz-ai/admin'\nimport { Button, Input, toast } from '@fayz-ai/ui'\nimport { providersApi, type ProviderId, type ProviderRow, type ProviderNumber } from '../data/providers'\n\n/**\n * Onde a casa escolhe por onde o WhatsApp sai.\n *\n * ── os dois não são intercambiáveis, e a tela diz isso ──────────────────\n *\n * A tentação é desenhar dois cartões iguais e um botão de rádio. Seria mentira:\n *\n * Tyxter API oficial. Iniciar conversa EXIGE template aprovado pela Meta.\n * A Meta mede qualidade e limite do número, e responde por eles.\n * Evolution WhatsApp Web. Manda texto para quem quiser, sem template e sem\n * janela — e o número pode ser banido sem aviso.\n *\n * Quem troca de um para o outro precisa saber o que está trocando. Por isso\n * cada cartão carrega a consequência escrita, e não só o nome.\n *\n * ── a chave não volta ───────────────────────────────────────────────────\n *\n * O campo de chave está sempre vazio, mesmo com credencial guardada. Não é\n * esquecimento: o segredo não sai do servidor, nem mascarado, porque\n * `tx_live_••••` ainda é a chave inteira viajando até o navegador para ser\n * escondida na renderização. `hasCredential` responde a única pergunta que a\n * tela precisa fazer — já foi posta? — e o cadeado diz o resto.\n */\n\nconst COPY: Record<ProviderId, {\n name: string\n what: string\n strength: string\n cost: string\n fields: Array<{ key: string; label: string; placeholder: string; secret?: boolean }>\n}> = {\n tyxter: {\n name: 'Tyxter',\n what: 'API oficial do WhatsApp Business, via revenda.',\n strength: 'Entrega confiável, sem risco de banimento. A Meta mede a qualidade do número.',\n cost: 'Para iniciar conversa é obrigatório um template aprovado pela Meta (~1 dia). Texto livre só dentro de 24h da resposta do cliente.',\n fields: [\n { key: 'api_key', label: 'Chave da API', placeholder: 'tx_live_…', secret: true },\n { key: 'default_sender_id', label: 'Número remetente (id)', placeholder: 'cm…' },\n ],\n },\n evolution: {\n name: 'Evolution API',\n what: 'Gateway auto-hospedado, sobre o WhatsApp Web.',\n strength: 'Manda texto livre para quem quiser, sem template e sem janela de 24h.',\n cost: 'O número pode ser banido sem aviso, e a sessão cai sozinha. Aquecer o número não é opcional aqui.',\n fields: [\n { key: 'base_url', label: 'URL da instância', placeholder: 'https://evo.seudominio.com' },\n { key: 'api_key', label: 'Chave da API', placeholder: 'sua apikey', secret: true },\n { key: 'instance', label: 'Nome da instância', placeholder: 'chefcontrol' },\n ],\n },\n}\n\nconst ALL: ProviderId[] = ['tyxter', 'evolution']\n\nfunction StatusChip({ row }: { row: ProviderRow | undefined }) {\n if (!row || row.status === 'unconfigured') {\n return <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground\">não configurado</span>\n }\n const map: Record<string, string> = {\n connected: 'bg-success-soft text-success-soft-foreground',\n connecting: 'bg-warning-soft text-warning-soft-foreground',\n disconnected: 'bg-muted text-muted-foreground',\n error: 'bg-destructive-soft text-destructive-soft-foreground',\n }\n const label: Record<string, string> = {\n connected: 'conectado', connecting: 'aguardando teste',\n disconnected: 'desconectado', error: 'com erro',\n }\n return (\n <span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${map[row.status] ?? 'bg-muted'}`}>\n {label[row.status] ?? row.status}\n </span>\n )\n}\n\nfunction NumberRow({ n }: { n: ProviderNumber }) {\n return (\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 py-1.5 text-xs\">\n <span className={`h-1.5 w-1.5 shrink-0 rounded-full ${n.ready ? 'bg-success' : 'bg-muted-foreground/40'}`} />\n <span className=\"font-medium tabular-nums\">{n.phone ?? n.id}</span>\n {n.displayName && <span className=\"text-muted-foreground\">{n.displayName}</span>}\n {/* Qualidade e tier só existem no provedor oficial. Nulo vira \"—\": o\n Evolution não é medido pela Meta, e fingir um \"bom\" aqui seria\n inventar a única informação que decide quanto se pode mandar. */}\n <span className=\"text-muted-foreground\">\n qualidade: <span className=\"text-foreground\">{n.quality ?? '—'}</span>\n </span>\n <span className=\"text-muted-foreground\">\n limite: <span className=\"text-foreground\">{n.tier ?? '—'}</span>\n </span>\n {n.used24h != null && n.allowance != null && (\n <span className=\"text-muted-foreground\">\n 24h: <span className=\"text-foreground tabular-nums\">{n.used24h}/{n.used24h + n.allowance}</span>\n </span>\n )}\n {!n.ready && <span className=\"text-warning\">não pronto para enviar</span>}\n </div>\n )\n}\n\nexport { COPY as WHATSAPP_PROVIDER_COPY }\n\nexport function WhatsAppProviders({ only, onChanged }: {\n /** One provider's card, without the group around it — a connector's detail. */\n only?: ProviderId\n onChanged?: () => void\n} = {}) {\n const [rows, setRows] = React.useState<ProviderRow[] | null>(null)\n const [draft, setDraft] = React.useState<Record<string, Record<string, string>>>({})\n const [busy, setBusy] = React.useState<string | null>(null)\n const [numbers, setNumbers] = React.useState<Record<string, ProviderNumber[]>>({})\n const [qr, setQr] = React.useState<{ provider: string; image: string | null; code: string | null } | null>(null)\n\n const load = React.useCallback(async () => {\n try { setRows(await providersApi.list()) } catch { setRows([]) }\n onChanged?.()\n }, [onChanged])\n React.useEffect(() => { void load() }, [load])\n\n const byId = React.useMemo(\n () => Object.fromEntries((rows ?? []).map((r) => [r.provider, r])) as Record<ProviderId, ProviderRow | undefined>,\n [rows],\n )\n\n function field(p: ProviderId, key: string): string {\n const row = byId[p]\n // A chave nunca é preenchida de volta — ver o cabeçalho.\n if (key === 'api_key') return draft[p]?.[key] ?? ''\n return draft[p]?.[key] ?? String((row?.config as any)?.[key] ?? '')\n }\n\n function setField(p: ProviderId, key: string, v: string) {\n setDraft((d) => ({ ...d, [p]: { ...(d[p] ?? {}), [key]: v } }))\n }\n\n async function run(id: string, fn: () => Promise<void>) {\n setBusy(id)\n try { await fn() } catch (e: any) { toast.error(e?.message ?? 'Falhou') } finally { setBusy(null) }\n }\n\n const save = (p: ProviderId) => run(`save:${p}`, async () => {\n const d = draft[p] ?? {}\n const config: Record<string, unknown> = {}\n for (const f of COPY[p].fields) {\n if (f.secret) continue\n const v = field(p, f.key)\n if (v) config[f.key] = v\n }\n if (p === 'tyxter' && !config.api_base_url) config.api_base_url = 'https://api.tyxter.com'\n await providersApi.save(p, config, d.api_key)\n setDraft((x) => ({ ...x, [p]: { ...(x[p] ?? {}), api_key: '' } }))\n await load()\n toast.success('Guardado')\n })\n\n const test = (p: ProviderId) => run(`test:${p}`, async () => {\n const h = await providersApi.health(p)\n await load()\n toast[h.ok ? 'success' : 'error'](h.ok ? `Conectado — ${h.detail ?? 'ok'}` : `Não conectou: ${h.detail ?? '?'}`)\n })\n\n const refreshNumbers = (p: ProviderId) => run(`numbers:${p}`, async () => {\n const d = await providersApi.numbers(p)\n setNumbers((n) => ({ ...n, [p]: d.numbers }))\n if (d.numbers.length === 0) toast.error('Nenhum número nesta conta')\n })\n\n const activate = (p: ProviderId) => run(`on:${p}`, async () => {\n await providersApi.activate(p)\n await load()\n toast.success(`${COPY[p].name} está valendo agora`)\n })\n\n const pair = (p: ProviderId) => run(`qr:${p}`, async () => {\n const d = await providersApi.pair(p)\n setQr({ provider: p, image: d.qr, code: d.code })\n if (!d.qr && !d.code) toast.error('A instância não devolveu QR — já está pareada?')\n })\n\n const body = (\n <>\n {rows === null && (\n <div className=\"flex items-center gap-2 py-6 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> carregando…\n </div>\n )}\n\n {rows !== null && (only ? [only] : ALL).map((p) => {\n const row = byId[p]\n const copy = COPY[p]\n const active = Boolean(row?.isActive)\n const caps = row?.capabilities ?? {}\n\n return (\n <div\n key={p}\n className={`rounded-card border p-4 my-2 ${active ? 'border-primary bg-primary/[0.03]' : 'bg-card'}`}\n data-testid={`provider-${p}`}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold\">{copy.name}</span>\n {active && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-primary px-2 py-0.5 text-[10px] font-medium text-primary-foreground\">\n <Check className=\"h-3 w-3\" /> valendo agora\n </span>\n )}\n <StatusChip row={row} />\n {row?.hasCredential && (\n <span className=\"inline-flex items-center gap-1 text-[10px] text-muted-foreground\">\n <ShieldCheck className=\"h-3 w-3\" /> chave guardada\n </span>\n )}\n {/* O risco de banimento é a diferença que decide a escolha, e ela\n fica ao lado do nome — não num rodapé que ninguém lê. */}\n {caps.ban_risk === 'high' && (\n <span className=\"inline-flex items-center gap-1 text-[10px] text-warning\">\n <ShieldAlert className=\"h-3 w-3\" /> risco de banimento\n </span>\n )}\n </div>\n\n <p className=\"mt-1 text-xs text-muted-foreground\">{copy.what}</p>\n <div className=\"mt-2 grid gap-1 sm:grid-cols-2\">\n <p className=\"text-[11px] text-success-soft-foreground\">✓ {copy.strength}</p>\n <p className=\"text-[11px] text-warning\">! {copy.cost}</p>\n </div>\n\n <div className=\"mt-3 grid gap-2 sm:grid-cols-2\">\n {copy.fields.map((f) => (\n <label key={f.key} className=\"block\">\n <span className=\"text-[11px] text-muted-foreground\">{f.label}</span>\n <Input\n className=\"mt-0.5 h-8 text-sm\"\n type={f.secret ? 'password' : 'text'}\n autoComplete=\"off\"\n placeholder={f.secret && row?.hasCredential ? '•••••••• (guardada)' : f.placeholder}\n value={field(p, f.key)}\n onChange={(e) => setField(p, f.key, e.target.value)}\n />\n </label>\n ))}\n </div>\n\n <div className=\"mt-3 flex flex-wrap gap-2\">\n <Button size=\"sm\" variant=\"outline\" disabled={busy === `save:${p}`} onClick={() => save(p)}>\n {busy === `save:${p}` ? <Loader2 className=\"h-3 w-3 animate-spin\" /> : 'Guardar'}\n </Button>\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `test:${p}`} onClick={() => test(p)}>\n {busy === `test:${p}` ? <Loader2 className=\"h-3 w-3 animate-spin\" /> : <><RefreshCw className=\"mr-1 h-3 w-3\" /> Testar</>}\n </Button>\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `numbers:${p}`} onClick={() => refreshNumbers(p)}>\n Números\n </Button>\n {/* Só aparece onde existe. O Tyxter não pareia por QR, e um botão\n desabilitado ainda promete algo que nunca vai acontecer. */}\n {caps.qr_pairing && (\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `qr:${p}`} onClick={() => pair(p)}>\n <QrCode className=\"mr-1 h-3 w-3\" /> Parear\n </Button>\n )}\n {!active && (\n <Button size=\"sm\" disabled={!row?.hasCredential || busy === `on:${p}`} onClick={() => activate(p)}>\n <Zap className=\"mr-1 h-3 w-3\" /> Usar este\n </Button>\n )}\n </div>\n\n {numbers[p]?.length ? (\n <div className=\"mt-3 border-t pt-2\">\n {numbers[p].map((n) => <NumberRow key={n.id} n={n} />)}\n </div>\n ) : null}\n\n {qr?.provider === p && (\n <div className=\"mt-3 border-t pt-3\">\n <p className=\"text-xs text-muted-foreground mb-2\">\n Abra o WhatsApp no celular → Aparelhos conectados → Conectar aparelho.\n </p>\n {qr.image && <img src={qr.image} alt=\"QR\" className=\"h-48 w-48 rounded border bg-white p-2\" />}\n {qr.code && <p className=\"mt-2 font-mono text-lg tracking-widest\">{qr.code}</p>}\n </div>\n )}\n </div>\n )\n })}\n </>\n )\n return only ? body : (\n <SettingsGroup\n title=\"WhatsApp — por onde as mensagens saem\"\n description=\"Dois caminhos, um valendo de cada vez. Troque quando quiser; a configuração do outro fica guardada.\"\n >\n {body}\n </SettingsGroup>\n )\n}\n","// Physical table names for the conversations plugin (plg_conversations* prefix).\n// Import T and reference T.<key> in the data provider so a rename lands in\n// exactly one place.\nexport const T = {\n conversations: 'plg_conversations',\n /** A LEITURA da conversa: a mesma linha, com o dono do lead junto. A escrita\n * continua na tabela — view com `security_invoker` não aceita INSERT aqui. */\n conversationsView: 'v_conversations',\n messages: 'plg_conversation_messages',\n channels: 'plg_conversations_channels',\n} as const\n","// Channel accounts — the numbers a message can leave by (migration 003).\n//\n// Read-only by construction: the table grants members SELECT and nothing else,\n// so this accessor has no write path to offer. Rows are created by the claim\n// function and advanced by the provider's webhooks.\nimport { getSupabaseClientOptional } from '@fayz-ai/core'\nimport { T } from './tables'\nimport type { Channel } from '../types'\n\n/** Whose number it is. `fallback` is the product's, shared by every tenant. */\nexport type MessagingChannelKind = 'fallback' | 'dedicated'\n\n/** The provider's own lifecycle, kept verbatim. Only `active` may carry a send. */\nexport type MessagingChannelStatus =\n | 'requested'\n | 'provisioning'\n | 'provisioned'\n | 'verifying'\n | 'active'\n | 'failed'\n | 'released'\n | 'disconnected'\n\nexport interface MessagingChannelAccount {\n id: string\n /** `null` on the product fallback row — it belongs to the software, not a tenant. */\n tenantId: string | null\n channel: Channel\n provider: string\n providerNumberId: string\n phoneE164: string | null\n kind: MessagingChannelKind\n status: MessagingChannelStatus\n displayName: string | null\n metadata: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListMessagingChannelsOptions {\n /** Narrow to one provider ('tyxter'). Omitted: every provider in the pool. */\n provider?: string\n /** Narrow to one channel ('whatsapp'). */\n channel?: Channel\n /** Supabase client override; defaults to the globally registered one. */\n supabaseClient?: unknown\n}\n\ntype Row = Record<string, unknown>\n\nfunction mapChannel(r: Row): MessagingChannelAccount {\n return {\n id: String(r.id),\n tenantId: (r.tenant_id as string | null) ?? null,\n channel: (r.channel as Channel) ?? 'whatsapp',\n provider: String(r.provider ?? ''),\n providerNumberId: String(r.provider_number_id ?? ''),\n phoneE164: (r.phone_e164 as string | null) ?? null,\n kind: (r.kind as MessagingChannelKind) ?? 'dedicated',\n status: (r.status as MessagingChannelStatus) ?? 'requested',\n displayName: (r.display_name as string | null) ?? null,\n metadata: (r.metadata as Record<string, unknown>) ?? {},\n createdAt: String(r.created_at ?? ''),\n updatedAt: String(r.updated_at ?? ''),\n }\n}\n\n/**\n * Every number this tenant can send by — its own, plus the product fallback.\n *\n * No tenant filter is applied, and that is deliberate: RLS already returns the\n * tenant's rows and the fallback row, and the fallback has no tenant to filter\n * on. Filtering here would hide exactly the row the panel most needs to show.\n */\nexport async function listMessagingChannels(\n options: ListMessagingChannelsOptions = {},\n): Promise<MessagingChannelAccount[]> {\n const supabase = (options.supabaseClient ?? getSupabaseClientOptional()) as\n | { from: (t: string) => Row }\n | null\n if (!supabase) {\n throw new Error(\n '[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp.',\n )\n }\n\n let q = (supabase.from(T.channels) as { select: (s: string) => Row }).select('*')\n if (options.provider) q = (q as { eq: (c: string, v: string) => Row }).eq('provider', options.provider)\n if (options.channel) q = (q as { eq: (c: string, v: string) => Row }).eq('channel', options.channel)\n // Dedicated before fallback: the tenant's own number is the answer whenever\n // there is one, and the readers of this list want it first.\n q = (q as { order: (c: string, o: unknown) => Row }).order('kind', { ascending: true })\n\n const { data, error } = (await q) as { data: Row[] | null; error: unknown }\n if (error) throw error\n return (data ?? []).map(mapChannel)\n}\n\n// ── The claim's lifecycle, as a thing the UI can render ─────────────────────\n//\n// Everything below is PURE. The claim itself is an edge function call and lives\n// in ../integrations/tyxter/data.ts; what belongs here is the vocabulary of the\n// table — which states a number walks through, which of them mean \"still\n// coming\", and which single row is the tenant's own. Keeping it here rather than\n// in the panel is what lets it be tested without React.\n\n/**\n * The happy path, in order. Observed live against the provider's sandbox on\n * 2026-08-24: a provision answered `requested` and the number then walked\n * `provisioned` → `verifying` → `active` in about a second. `provisioning` is in\n * the provider's enum and was not seen at 150ms polling — kept here because the\n * enum has it and a step missing from a timeline reads as a step that failed.\n */\nexport const CHANNEL_CLAIM_TIMELINE: MessagingChannelStatus[] = [\n 'requested',\n 'provisioning',\n 'provisioned',\n 'verifying',\n 'active',\n]\n\n/**\n * The states in which the tenant already has a claim and must not start another.\n *\n * Mirrors `LIVE_CHANNEL_STATUSES` in functions/_shared/tyxter-runtime.js, which\n * is where the edge function enforces it. Two copies because an edge function\n * may not import from src/ — the vitest suite asserts they agree, so a change to\n * one that is not made to the other fails a test rather than letting the panel\n * offer a button the server will refuse.\n */\nexport const LIVE_CHANNEL_STATUSES: MessagingChannelStatus[] = [\n 'requested',\n 'provisioning',\n 'provisioned',\n 'verifying',\n 'active',\n]\n\n/** Terminal, and not a reason to stop the tenant trying again. */\nexport function isDeadChannelStatus(status: MessagingChannelStatus): boolean {\n return !LIVE_CHANNEL_STATUSES.includes(status)\n}\n\n/** On its way, but not carrying messages yet — the state the panel polls in. */\nexport function isPendingChannelStatus(status: MessagingChannelStatus): boolean {\n return LIVE_CHANNEL_STATUSES.includes(status) && status !== 'active'\n}\n\n/** The tenant's own number, live or on its way. Null when they have none. */\nexport function findDedicatedChannel(\n channels: MessagingChannelAccount[],\n tenantId?: string | null,\n): MessagingChannelAccount | null {\n return channels.find(\n (channel) => channel.kind === 'dedicated'\n && (!tenantId || channel.tenantId === tenantId)\n && !isDeadChannelStatus(channel.status),\n ) ?? null\n}\n\n/** The product's shared number, which every tenant sends by until it has its own. */\nexport function findFallbackChannel(\n channels: MessagingChannelAccount[],\n): MessagingChannelAccount | null {\n return channels.find((channel) => channel.kind === 'fallback') ?? null\n}\n\n/**\n * Can this tenant ask for a number right now?\n *\n * The same rule the edge function answers 409 on, asked before the round trip\n * so the button is disabled rather than the click refused.\n */\nexport function canClaimDedicated(\n channels: MessagingChannelAccount[],\n tenantId?: string | null,\n): boolean {\n return findDedicatedChannel(channels, tenantId) === null\n}\n"]}
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+ import { useStore } from 'zustand';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { getSupabaseClientOptional } from '@fayz-ai/core';
5
+
6
+ // src/types.ts
7
+ var CHANNEL_LABELS = {
8
+ sms: "SMS",
9
+ whatsapp: "WhatsApp",
10
+ instagram: "Instagram",
11
+ email: "Email",
12
+ webchat: "Web Chat"
13
+ };
14
+ function isWaitingOnUs(conversation) {
15
+ if (conversation.status !== "open") return false;
16
+ if (conversation.acknowledgedAt && conversation.lastMessageAt && conversation.acknowledgedAt >= conversation.lastMessageAt) return false;
17
+ if (conversation.lastMessageDirection) return conversation.lastMessageDirection === "inbound";
18
+ return conversation.unreadCount > 0;
19
+ }
20
+ var DEFAULT_CONVERSATIONS_CONFIG = {
21
+ contactKind: "contact"
22
+ };
23
+ var StoreContext = React.createContext(null);
24
+ var ConfigContext = React.createContext(DEFAULT_CONVERSATIONS_CONFIG);
25
+ function ConversationsContextProvider({
26
+ store,
27
+ config = DEFAULT_CONVERSATIONS_CONFIG,
28
+ children
29
+ }) {
30
+ return /* @__PURE__ */ jsx(StoreContext.Provider, { value: store, children: /* @__PURE__ */ jsx(ConfigContext.Provider, { value: config, children }) });
31
+ }
32
+ function useConversationsStore(selector) {
33
+ const store = React.useContext(StoreContext);
34
+ if (!store) throw new Error("useConversationsStore must be used within ConversationsPage");
35
+ return useStore(store, selector);
36
+ }
37
+ function useConversationsConfig() {
38
+ return React.useContext(ConfigContext);
39
+ }
40
+ var porConta = /* @__PURE__ */ new Map();
41
+ function assinarChegada(tenantId, ouvinte) {
42
+ const supabase = getSupabaseClientOptional();
43
+ if (!supabase?.channel || !tenantId) return () => {
44
+ };
45
+ let assinatura = porConta.get(tenantId);
46
+ if (!assinatura) {
47
+ const ouvintes = /* @__PURE__ */ new Set();
48
+ const canal = supabase.channel(`conversas:${tenantId}`).on("broadcast", { event: "inbound" }, (msg) => {
49
+ const carga = { conversationId: msg?.payload?.conversationId };
50
+ for (const f of [...ouvintes]) {
51
+ try {
52
+ f(carga);
53
+ } catch {
54
+ }
55
+ }
56
+ }).subscribe();
57
+ assinatura = { canal, ouvintes };
58
+ porConta.set(tenantId, assinatura);
59
+ }
60
+ assinatura.ouvintes.add(ouvinte);
61
+ return () => {
62
+ const a = porConta.get(tenantId);
63
+ if (!a) return;
64
+ a.ouvintes.delete(ouvinte);
65
+ if (a.ouvintes.size === 0) {
66
+ porConta.delete(tenantId);
67
+ void getSupabaseClientOptional()?.removeChannel?.(a.canal);
68
+ }
69
+ };
70
+ }
71
+
72
+ export { CHANNEL_LABELS, ConversationsContextProvider, assinarChegada, isWaitingOnUs, useConversationsConfig, useConversationsStore };
73
+ //# sourceMappingURL=chunk-X7BP2JH6.js.map
74
+ //# sourceMappingURL=chunk-X7BP2JH6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/context.tsx","../src/lib/campainha.ts"],"names":[],"mappings":"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","file":"chunk-X7BP2JH6.js","sourcesContent":["// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-conversations — domain types for the unified omni-channel\n// inbox (the GoHighLevel \"Conversations\" equivalent). Provider-agnostic.\n// ---------------------------------------------------------------------------\n\nexport type Channel = 'sms' | 'whatsapp' | 'instagram' | 'email' | 'webchat'\n\nexport type ConversationStatus = 'open' | 'snoozed' | 'closed'\n\nexport type MessageDirection = 'inbound' | 'outbound'\n\nexport interface Conversation {\n id: string\n contactName: string\n /**\n * `public.people` id when the thread is tied to a real contact record (the\n * compose modal resolves one via the shared ContactPicker). Absent for legacy\n * threads and for inbound messages from an unknown handle.\n */\n contactPersonId?: string\n /** Phone, @handle, or email depending on channel */\n contactHandle: string\n channel: Channel\n lastMessagePreview: string\n lastMessageAt: string\n /**\n * Who spoke last: `inbound` = the customer, `outbound` = the house.\n *\n * The field the queue is built on — \"waiting on us\" is `status === 'open'`\n * plus this being `inbound`. `unreadCount` is the older approximation and\n * gets the painful case wrong: a thread somebody opened, read, meant to\n * answer later and forgot has zero unread and a customer still waiting.\n *\n * Optional because a thread predating the column's backfill (or a provider\n * that does not report direction) genuinely does not know — see\n * `isWaitingOnUs`, which falls back rather than guessing.\n */\n lastMessageDirection?: MessageDirection\n unreadCount: number\n /** Quando alguém deu a conversa por vista.\n *\n * Vale enquanto for POSTERIOR a `lastMessageAt`: mensagem nova devolve a\n * conversa à fila sozinha, sem ninguém ter de lembrar de apagar nada. */\n acknowledgedAt?: string | null\n status: ConversationStatus\n /** Quem ATENDE esta conversa. */\n assignedTo?: string\n /** Quem é dono do LEAD — outra pergunta, e a que decide o follow-up.\n * Derivado de `v_leads` pela view `v_conversations`: copiar aqui faria uma\n * segunda verdade que envelhece quando alguém clica \"Assumir\" no funil. */\n leadOwnerId?: string\n leadOwnerName?: string\n /** Tailwind-ish accent for the avatar bubble */\n accent: string\n tags: string[]\n // Optional context shown in the contact panel.\n location?: string\n note?: string\n}\n\nexport interface Message {\n id: string\n conversationId: string\n channel: Channel\n direction: MessageDirection\n body: string\n at: string\n author: string\n /** O que aconteceu com ela depois que saiu. Nulo na entrada — ela chegou,\n * não foi entregue por nós. `failed` é o caso que a tela precisa gritar:\n * uma mensagem que nunca saiu não pode parecer uma mensagem enviada. */\n deliveryStatus?: string | null\n /** O id no provedor. Sem ele, não saiu — e é a chave para buscar o JSON cru. */\n providerMessageId?: string | null\n /**\n * What the provider sent, when it was not plain text. Absent on every row\n * written before migration 012 — absent is \"unknown\", never \"text\", because\n * the rows this exists for are exactly the ones that stored as empty text.\n */\n messageType?: MessageType | null\n /**\n * The part `body` cannot hold: parsed vCards, media metadata, coordinates.\n * Shape follows `messageType`; see functions/_shared/evolution-runtime.js.\n */\n payload?: MessagePayload | null\n /**\n * Where the bytes are, in the private `attachments` bucket — or null, which\n * means one of three ordinary things: the message is text, the row predates\n * 013, or the upload did not happen. Never a URL: the bucket is private and\n * a link has to be signed per view, which `media_url` does against the row\n * rather than against a path the client supplies.\n */\n mediaPath?: string | null\n}\n\nexport type MessageType =\n | 'text' | 'contact' | 'image' | 'video' | 'audio'\n | 'document' | 'sticker' | 'location' | 'reaction' | 'unknown'\n\n/** A contact card someone forwarded into the thread. The number in it is\n * routinely the reason the conversation is happening. */\nexport interface SharedContact {\n name: string\n /** `display` is what a human reads back; `digits` is what dials. */\n phones: Array<{ display: string; digits: string }>\n}\n\nexport interface MessagePayload {\n contacts?: SharedContact[]\n mimetype?: string | null\n fileName?: string | null\n fileLength?: number | null\n seconds?: number | null\n ptt?: boolean | null\n caption?: string | null\n latitude?: number | null\n longitude?: number | null\n name?: string | null\n address?: string | null\n /** Only on `unknown`: the provider keys nobody has mapped yet. */\n keys?: string[]\n}\n\nexport interface ListConversationsQuery {\n channel?: Channel | 'all'\n status?: ConversationStatus | 'all'\n search?: string\n /** Recorta pelo DONO DO LEAD. `'none'` traz quem ainda não é lead de\n * ninguém — a fila que precisa de dono, e a pergunta que a caixa sozinha\n * não respondia. */\n leadOwnerId?: string | 'none'\n}\n\nexport interface SendMessageInput {\n conversationId: string\n body: string\n}\n\nexport interface CreateConversationInput {\n contactName: string\n /** `public.people` id, when the contact was resolved/created by the picker. */\n contactPersonId?: string\n /** Phone, @handle, or email depending on channel. */\n contactHandle?: string\n channel: Channel\n /** Optional first outbound message; stamps preview + last_message_at. */\n firstMessage?: string\n /** Optional free-text note surfaced in the contact panel. */\n note?: string\n}\n\nexport const CHANNEL_LABELS: Record<Channel, string> = {\n sms: 'SMS',\n whatsapp: 'WhatsApp',\n instagram: 'Instagram',\n email: 'Email',\n webchat: 'Web Chat',\n}\n\n/**\n * Is this thread waiting on US?\n *\n * One definition, in one place, because the KPI, the queue table and anything\n * that later filters the inbox have to agree — a dashboard that says three and\n * a list that shows one is worse than neither.\n *\n * `lastMessageDirection` is the real answer. `unreadCount` is the fallback for\n * a thread that genuinely does not know its direction (older than migration\n * 001, or a provider that does not report one) — it under-reports rather than\n * over-reports, which is the right way round: a queue that invents work loses\n * trust faster than one that misses an old row.\n */\nexport function isWaitingOnUs(conversation: Conversation): boolean {\n if (conversation.status !== 'open') return false\n // DAR POR VISTA vence a direção — e só enquanto for mais recente que a\n // última mensagem. Alguém leu, decidiu que não cabe resposta, e tirou da\n // fila; se chegar mensagem depois disso, a comparação se inverte sozinha e a\n // conversa volta. É por isso que é um instante e não um booleano: um\n // booleano precisaria de alguém para apagá-lo, e o dia do esquecimento é o\n // dia em que um cliente fica esperando sem aparecer para ninguém.\n if (conversation.acknowledgedAt && conversation.lastMessageAt\n && conversation.acknowledgedAt >= conversation.lastMessageAt) return false\n if (conversation.lastMessageDirection) return conversation.lastMessageDirection === 'inbound'\n return conversation.unreadCount > 0\n}\n","import React from 'react'\nimport { useStore, type StoreApi } from 'zustand'\nimport type { EntityLookup } from '@fayz-ai/admin'\nimport type { EntityDef } from '@fayz-ai/core'\nimport type { ConversationsUIState } from './store'\n\n/**\n * App-tunable knobs the inbox UI reads. Kept tiny on purpose: everything here\n * describes the APP's data shape (which people are \"contacts\" in this vertical),\n * never product policy.\n */\nexport interface ResolvedConversationsConfig {\n /** `people.kind` the picker searches. */\n contactKind: string\n /** Search source for the contact picker. Defaults to the person archetype. */\n contactLookup?: EntityLookup\n /** The app's own contact entity — \"Create\" opens ITS form. Agenda's twin is `clientEntityDef`. */\n contactEntityDef?: EntityDef\n}\n\nexport const DEFAULT_CONVERSATIONS_CONFIG: ResolvedConversationsConfig = {\n contactKind: 'contact',\n}\n\nconst StoreContext = React.createContext<StoreApi<ConversationsUIState> | null>(null)\nconst ConfigContext = React.createContext<ResolvedConversationsConfig>(DEFAULT_CONVERSATIONS_CONFIG)\n\nexport function ConversationsContextProvider({\n store,\n config = DEFAULT_CONVERSATIONS_CONFIG,\n children,\n}: {\n store: StoreApi<ConversationsUIState>\n config?: ResolvedConversationsConfig\n children?: React.ReactNode\n}) {\n return (\n <StoreContext.Provider value={store}>\n <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n </StoreContext.Provider>\n )\n}\n\nexport function useConversationsStore<T>(selector: (state: ConversationsUIState) => T): T {\n const store = React.useContext(StoreContext)\n if (!store) throw new Error('useConversationsStore must be used within ConversationsPage')\n return useStore(store, selector)\n}\n\nexport function useConversationsConfig(): ResolvedConversationsConfig {\n return React.useContext(ConfigContext)\n}\n","import { getSupabaseClientOptional } from '@fayz-ai/core'\n\n/**\n * UMA assinatura do tópico da conta, partilhada por quem precisar.\n *\n * ── por que não cada um a sua ───────────────────────────────────────────\n *\n * Dois consumidores querem o mesmo sinal: o vigia global (som, contagem,\n * notificação do sistema) e a caixa aberta (lista e thread ao vivo). O jeito\n * óbvio é cada um chamar `supabase.channel('conversas:<tenant>')` — e é o que\n * eu fiz, e está errado.\n *\n * Dois canais com o MESMO tópico no mesmo cliente não recebem os dois: um\n * vence. Testados em separado, ambos funcionam; juntos, um emudece. Em\n * produção quem venceu foi a tela — a mensagem aparecia sozinha e a notificação\n * do sistema não saía, sem erro nenhum para explicar.\n *\n * É o tipo de defeito que só aparece com as duas peças no ar ao mesmo tempo, e\n * por isso passou por dois testes verdes.\n *\n * ── o desenho ───────────────────────────────────────────────────────────\n *\n * Um canal por conta, contado por referências. O primeiro a chegar abre, o\n * último a sair fecha, e todos recebem a mesma campainha.\n */\n\nexport interface Chegada {\n conversationId?: string\n}\n\ntype Ouvinte = (c: Chegada) => void\n\ninterface Assinatura {\n canal: unknown\n ouvintes: Set<Ouvinte>\n}\n\nconst porConta = new Map<string, Assinatura>()\n\n/** Escuta a chegada de mensagem nesta conta. Devolve como deixar de escutar. */\nexport function assinarChegada(tenantId: string, ouvinte: Ouvinte): () => void {\n const supabase = getSupabaseClientOptional() as any\n if (!supabase?.channel || !tenantId) return () => {}\n\n let assinatura = porConta.get(tenantId)\n if (!assinatura) {\n const ouvintes = new Set<Ouvinte>()\n const canal = supabase\n .channel(`conversas:${tenantId}`)\n .on('broadcast', { event: 'inbound' }, (msg: any) => {\n const carga: Chegada = { conversationId: msg?.payload?.conversationId }\n // Cópia antes de percorrer: um ouvinte que se desinscreve ao receber\n // (a tela fechando no mesmo tique) mutaria o Set durante a iteração.\n for (const f of [...ouvintes]) {\n try { f(carga) } catch { /* um ouvinte que falha não cala os outros */ }\n }\n })\n .subscribe()\n assinatura = { canal, ouvintes }\n porConta.set(tenantId, assinatura)\n }\n\n assinatura.ouvintes.add(ouvinte)\n return () => {\n const a = porConta.get(tenantId)\n if (!a) return\n a.ouvintes.delete(ouvinte)\n // O último a sair fecha a porta. Deixar o canal aberto sem ouvintes seria\n // pagar uma conexão por conta que a pessoa visitou e abandonou.\n if (a.ouvintes.size === 0) {\n porConta.delete(tenantId)\n void (getSupabaseClientOptional() as any)?.removeChannel?.(a.canal)\n }\n }\n}\n"]}
@@ -1,18 +1,9 @@
1
- import * as React from 'react';
2
- import type { StoreApi } from 'zustand/vanilla';
3
- import type { ResolvedConversationsConfig } from '../context';
4
- import type { ConversationsUIState } from '../store';
5
- export declare function InboxWatcher({ label, route, store, config, inHeader }: {
6
- /** O nome da caixa. Só chega ao olho no `title`/`aria-label` do botão e no
7
- * título da notificação do sistema: na faixa o ícone basta, e um rótulo por
8
- * extenso ao lado de cada controle deste canto encheria a linha. */
1
+ export declare function InboxWatcher({ label, route, section, position }: {
2
+ /** The inbox's name: the rail line's label and the system notification's title. */
9
3
  label: string;
10
4
  route?: string;
11
- /** With the inbox store the button opens the floating inbox; without it,
12
- * it goes to the inbox page as before. */
13
- store?: StoreApi<ConversationsUIState>;
14
- config?: ResolvedConversationsConfig;
15
- /** Mounted as a header control: icon only, never the labelled menu row. */
16
- inHeader?: boolean;
17
- }): React.JSX.Element;
5
+ /** Where the rail line sits — the same values the plugin declares it with. */
6
+ section?: 'main' | 'secondary' | 'settings';
7
+ position?: number;
8
+ }): null;
18
9
  //# sourceMappingURL=InboxWatcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"InboxWatcher.d.ts","sourceRoot":"","sources":["../../src/components/InboxWatcher.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAsDpD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAwB,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IACzF;;yEAEqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;+CAC2C;IAC3C,KAAK,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAA;IACtC,MAAM,CAAC,EAAE,2BAA2B,CAAA;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,qBA0HA"}
1
+ {"version":3,"file":"InboxWatcher.d.ts","sourceRoot":"","sources":["../../src/components/InboxWatcher.tsx"],"names":[],"mappings":"AAkDA,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAwB,EAAE,OAAgB,EAAE,QAAY,EAAE,EAAE;IAChG,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,QAiFA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAEvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAsBzD,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IAEpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oFAAoF;IACpF,aAAa,CAAC,EAAE,YAAY,CAAA;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AA2BD,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,cAAc,CAwP9F;AAED,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,+BAA+B,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC3F,OAAO,EACL,mCAAmC,EACnC,KAAK,2BAA2B,GACjC,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,GAClC,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,oCAAoC,CAAA;AAc3C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAEvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAsBzD,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IAEpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oFAAoF;IACpF,aAAa,CAAC,EAAE,YAAY,CAAA;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AA2BD,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,cAAc,CA+P9F;AAED,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,+BAA+B,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC3F,OAAO,EACL,mCAAmC,EACnC,KAAK,2BAA2B,GACjC,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,GAClC,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,oCAAoC,CAAA;AAc3C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA"}