@fayz-ai/plugin-automations 0.11.0 → 0.12.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/dist/data/fayz.d.ts.map +1 -1
- package/dist/data/payment-preflight.d.ts +23 -0
- package/dist/data/payment-preflight.d.ts.map +1 -0
- package/dist/data/provider-templates.d.ts +34 -0
- package/dist/data/provider-templates.d.ts.map +1 -0
- package/dist/data/templates.d.ts.map +1 -1
- package/dist/data/test-send.d.ts +18 -0
- package/dist/data/test-send.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +487 -80
- package/dist/index.js.map +1 -1
- package/dist/views/AutomationDetailPage.d.ts +9 -0
- package/dist/views/AutomationDetailPage.d.ts.map +1 -1
- package/dist/views/AutomationsList.d.ts.map +1 -1
- package/dist/views/ProviderTemplateStatus.d.ts +35 -0
- package/dist/views/ProviderTemplateStatus.d.ts.map +1 -0
- package/dist/views/whatsapp-preview-bg.d.ts +2 -0
- package/dist/views/whatsapp-preview-bg.d.ts.map +1 -0
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -1,13 +1,73 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import * as React4 from 'react';
|
|
2
|
+
import React4__default from 'react';
|
|
3
|
+
import { getSupabaseClientOptional, startEventLog, getMessageDefaults, normalizeMessageActions, usePluginRuntimeOptional, resolveMessages, resolveMessage, renderMessageTemplate, loadEventRuns, getActiveTenantId } from '@fayz-ai/core';
|
|
4
|
+
import { resolveFayzAgentConnection, registerRailPage, getFayzSupabaseClientOptional, useConnectors, useAgentSurface, PageActions, askAI, SettingsTabs, adminNavigateTo, RightRailPage, AskAIButton, useOrganizationStore, connectorRoute, ConnectorMark, ConnectorDeliveries } from '@fayz-ai/admin';
|
|
4
5
|
import * as LucideIcons from 'lucide-react';
|
|
5
|
-
import { Zap, Receipt, Megaphone, MessageSquare, Smartphone,
|
|
6
|
-
import { PageHeaderActions, PageTabs, Card, Switch, SubpageHeader, Button, Badge, Hint, HeaderTabs, toast } from '@fayz-ai/ui';
|
|
6
|
+
import { WandSparkles, Zap, Receipt, Megaphone, MessageSquare, Smartphone, Mail, ChevronRight, RotateCcw, Check, Clock, Monitor, RefreshCw, History, Send, AlertTriangle, CircleCheck, CircleSlash } from 'lucide-react';
|
|
7
|
+
import { PageHeaderActions, PageTabs, Card, WhatsAppIcon, Switch, SubpageHeader, Button, Badge, Hint, HeaderTabs, toast, PhoneField } from '@fayz-ai/ui';
|
|
7
8
|
import { createFayzAutomationsClient, FayzAutomationsError } from '@fayz-ai/sdk/automations';
|
|
8
9
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
10
|
|
|
10
11
|
// src/index.ts
|
|
12
|
+
var PREFLIGHT_FUNCTION = "tyxter-payment-preflight";
|
|
13
|
+
async function readReadiness() {
|
|
14
|
+
const supabase = getSupabaseClientOptional();
|
|
15
|
+
const tenantId = getActiveTenantId();
|
|
16
|
+
if (!supabase || !tenantId) return null;
|
|
17
|
+
const { data, error } = await supabase.functions.invoke(PREFLIGHT_FUNCTION, {
|
|
18
|
+
body: { action: "status", tenantId }
|
|
19
|
+
});
|
|
20
|
+
if (error) return null;
|
|
21
|
+
const payments = data?.payments;
|
|
22
|
+
return payments ?? null;
|
|
23
|
+
}
|
|
24
|
+
function canOfferPayment(readiness) {
|
|
25
|
+
return Boolean(readiness?.ready) && readiness?.mode !== null && readiness?.mode !== void 0;
|
|
26
|
+
}
|
|
27
|
+
function usePaymentReadiness() {
|
|
28
|
+
const [readiness, setReadiness] = React4.useState(null);
|
|
29
|
+
const [loading, setLoading] = React4.useState(true);
|
|
30
|
+
React4.useEffect(() => {
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
void readReadiness().then((value) => {
|
|
33
|
+
if (!cancelled) setReadiness(value);
|
|
34
|
+
}).catch(() => {
|
|
35
|
+
}).finally(() => {
|
|
36
|
+
if (!cancelled) setLoading(false);
|
|
37
|
+
});
|
|
38
|
+
return () => {
|
|
39
|
+
cancelled = true;
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
return { readiness, loading };
|
|
43
|
+
}
|
|
44
|
+
var SEND_FUNCTION = "messaging-send";
|
|
45
|
+
function readFunctionError(error, data) {
|
|
46
|
+
const payload = data ?? null;
|
|
47
|
+
if (payload?.error) return { ok: false, error: payload.error, message: payload.message };
|
|
48
|
+
const context = error?.context;
|
|
49
|
+
if (context?.error) return { ok: false, error: context.error, message: context.message };
|
|
50
|
+
return { ok: false, error: "send_failed", message: error instanceof Error ? error.message : void 0 };
|
|
51
|
+
}
|
|
52
|
+
async function sendTestMessage(input) {
|
|
53
|
+
const supabase = getSupabaseClientOptional();
|
|
54
|
+
const tenantId = getActiveTenantId();
|
|
55
|
+
if (!supabase || !tenantId) return { ok: false, error: "not_available" };
|
|
56
|
+
const { data, error } = await supabase.functions.invoke(SEND_FUNCTION, {
|
|
57
|
+
body: {
|
|
58
|
+
test: true,
|
|
59
|
+
tenantId,
|
|
60
|
+
channel: input.channel,
|
|
61
|
+
eventKey: input.eventKey,
|
|
62
|
+
to: input.to,
|
|
63
|
+
variables: input.variables,
|
|
64
|
+
subject: { type: "test", id: crypto.randomUUID() },
|
|
65
|
+
...input.template ? { template: input.template } : {}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
if (error || data?.error) return readFunctionError(error, data);
|
|
69
|
+
return { ok: true };
|
|
70
|
+
}
|
|
11
71
|
function useOrganizationStoreLike() {
|
|
12
72
|
return useOrganizationStore((s) => s.currentOrg?.id);
|
|
13
73
|
}
|
|
@@ -27,6 +87,11 @@ function toTemplate(automation) {
|
|
|
27
87
|
// The automation name belongs to the platform, not a user rename: the
|
|
28
88
|
// catalogue title still owns the screen.
|
|
29
89
|
name: null,
|
|
90
|
+
// The platform's automation action carries a subject and a body and nothing
|
|
91
|
+
// else, so there is nowhere for a customised button to live yet. NULL, not
|
|
92
|
+
// [], because the catalogue default is what still applies — an empty list
|
|
93
|
+
// would read as "this tenant removed the buttons".
|
|
94
|
+
actions: null,
|
|
30
95
|
updatedAt: automation.updatedAt
|
|
31
96
|
};
|
|
32
97
|
}
|
|
@@ -183,7 +248,7 @@ function when(iso) {
|
|
|
183
248
|
return new Date(iso).toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
184
249
|
}
|
|
185
250
|
function RunRow({ run }) {
|
|
186
|
-
const [open, setOpen] =
|
|
251
|
+
const [open, setOpen] = React4__default.useState(false);
|
|
187
252
|
const cfg = STATUS[run.status] ?? STATUS.recorded;
|
|
188
253
|
const { Icon } = cfg;
|
|
189
254
|
return /* @__PURE__ */ jsxs("div", { className: "border-b border-border last:border-b-0", children: [
|
|
@@ -217,14 +282,14 @@ function RunRow({ run }) {
|
|
|
217
282
|
] });
|
|
218
283
|
}
|
|
219
284
|
function RunLog({ eventKey, tenantId, enabled }) {
|
|
220
|
-
const [runs, setRuns] =
|
|
221
|
-
const [loading, setLoading] =
|
|
222
|
-
const load =
|
|
285
|
+
const [runs, setRuns] = React4__default.useState(null);
|
|
286
|
+
const [loading, setLoading] = React4__default.useState(false);
|
|
287
|
+
const load = React4__default.useCallback(async () => {
|
|
223
288
|
setLoading(true);
|
|
224
289
|
setRuns(await loadEventRuns(eventKey, { tenantId, limit: 20 }));
|
|
225
290
|
setLoading(false);
|
|
226
291
|
}, [eventKey, tenantId]);
|
|
227
|
-
|
|
292
|
+
React4__default.useEffect(() => {
|
|
228
293
|
void load();
|
|
229
294
|
}, [load]);
|
|
230
295
|
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
@@ -260,9 +325,9 @@ function ExecutionRow({ execution }) {
|
|
|
260
325
|
] });
|
|
261
326
|
}
|
|
262
327
|
function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
|
|
263
|
-
const [executions, setExecutions] =
|
|
264
|
-
const [loading, setLoading] =
|
|
265
|
-
const load =
|
|
328
|
+
const [executions, setExecutions] = React4__default.useState(null);
|
|
329
|
+
const [loading, setLoading] = React4__default.useState(false);
|
|
330
|
+
const load = React4__default.useCallback(async () => {
|
|
266
331
|
if (!tenantId) return;
|
|
267
332
|
setLoading(true);
|
|
268
333
|
try {
|
|
@@ -274,7 +339,7 @@ function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
|
|
|
274
339
|
setLoading(false);
|
|
275
340
|
}
|
|
276
341
|
}, [connection, eventKey, tenantId]);
|
|
277
|
-
|
|
342
|
+
React4__default.useEffect(() => {
|
|
278
343
|
void load();
|
|
279
344
|
}, [load]);
|
|
280
345
|
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
@@ -292,6 +357,183 @@ function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
|
|
|
292
357
|
] }) : /* @__PURE__ */ jsx("div", { children: executions.map((e) => /* @__PURE__ */ jsx(ExecutionRow, { execution: e }, e.id)) })
|
|
293
358
|
] });
|
|
294
359
|
}
|
|
360
|
+
|
|
361
|
+
// src/views/whatsapp-preview-bg.ts
|
|
362
|
+
var WHATSAPP_PREVIEW_BG = "https://assets.fayz.ai/connectors/whatsapp-background.jpg";
|
|
363
|
+
var PROVIDER_TEMPLATES_TABLE = "plg_automations_provider_templates";
|
|
364
|
+
function rowToProviderTemplate(row) {
|
|
365
|
+
return {
|
|
366
|
+
id: row.id,
|
|
367
|
+
tenantId: row.tenant_id ?? null,
|
|
368
|
+
eventKey: row.event_key,
|
|
369
|
+
channel: row.channel ?? "whatsapp",
|
|
370
|
+
provider: row.provider ?? "tyxter",
|
|
371
|
+
templateName: row.template_name ?? "",
|
|
372
|
+
status: row.status ?? "draft",
|
|
373
|
+
rejectionReason: row.rejection_reason || null,
|
|
374
|
+
updatedAt: row.updated_at ?? row.created_at ?? null
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
async function loadProviderTemplates() {
|
|
378
|
+
const client = getSupabaseClientOptional();
|
|
379
|
+
if (!client) return [];
|
|
380
|
+
try {
|
|
381
|
+
const { data, error } = await client.from(PROVIDER_TEMPLATES_TABLE).select("*");
|
|
382
|
+
if (error) throw error;
|
|
383
|
+
return (data ?? []).map(rowToProviderTemplate);
|
|
384
|
+
} catch (err) {
|
|
385
|
+
console.warn("[automations] could not load provider template status", err);
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function providerTemplateFor(rows, eventKey, channel) {
|
|
390
|
+
const matching = rows.filter((r) => r.eventKey === eventKey && r.channel === channel);
|
|
391
|
+
return {
|
|
392
|
+
own: matching.find((r) => r.tenantId !== null) ?? null,
|
|
393
|
+
fallback: matching.find((r) => r.tenantId === null) ?? null
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function providerApprovalFor(rows, eventKey, channel) {
|
|
397
|
+
if (channel !== "whatsapp") return null;
|
|
398
|
+
const { own, fallback } = providerTemplateFor(rows, eventKey, channel);
|
|
399
|
+
const row = own ?? fallback;
|
|
400
|
+
if (!row) return null;
|
|
401
|
+
const fallbackApproved = fallback?.status === "approved";
|
|
402
|
+
switch (row.status) {
|
|
403
|
+
case "approved":
|
|
404
|
+
return { tone: "ok", label: "Aprovado pela Meta" };
|
|
405
|
+
case "rejected":
|
|
406
|
+
return {
|
|
407
|
+
tone: fallbackApproved && own ? "waiting" : "error",
|
|
408
|
+
label: fallbackApproved && own ? "Seu texto foi recusado" : "Recusado pela Meta",
|
|
409
|
+
detail: row.rejectionReason ? `Motivo: ${row.rejectionReason}` : "A Meta n\xE3o disse o motivo."
|
|
410
|
+
};
|
|
411
|
+
case "paused":
|
|
412
|
+
return {
|
|
413
|
+
tone: "error",
|
|
414
|
+
label: "Pausado pela Meta",
|
|
415
|
+
detail: row.rejectionReason ?? "Costuma ser excesso de marca\xE7\xF5es de spam pelos clientes."
|
|
416
|
+
};
|
|
417
|
+
case "disabled":
|
|
418
|
+
return { tone: "error", label: "Desativado pela Meta", detail: row.rejectionReason ?? void 0 };
|
|
419
|
+
case "submitted":
|
|
420
|
+
return {
|
|
421
|
+
tone: "waiting",
|
|
422
|
+
label: "Aguardando aprova\xE7\xE3o da Meta",
|
|
423
|
+
detail: fallbackApproved && own ? "At\xE9 l\xE1 a mensagem sai com o texto padr\xE3o." : "A revis\xE3o costuma levar algumas horas."
|
|
424
|
+
};
|
|
425
|
+
default:
|
|
426
|
+
return { tone: "waiting", label: "Ainda n\xE3o enviado para aprova\xE7\xE3o" };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
var TONE_VARIANT = {
|
|
430
|
+
ok: "success",
|
|
431
|
+
waiting: "warning",
|
|
432
|
+
error: "destructive"
|
|
433
|
+
};
|
|
434
|
+
function ProviderTemplateBadge({ approval, showDetail }) {
|
|
435
|
+
if (!approval) return null;
|
|
436
|
+
if (approval.tone === "ok" && !showDetail) return null;
|
|
437
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex flex-wrap items-center gap-1.5", children: [
|
|
438
|
+
/* @__PURE__ */ jsx(Badge, { variant: TONE_VARIANT[approval.tone], children: approval.label }),
|
|
439
|
+
showDetail && approval.detail && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: approval.detail })
|
|
440
|
+
] });
|
|
441
|
+
}
|
|
442
|
+
function useProviderTemplates() {
|
|
443
|
+
const [rows, setRows] = React4__default.useState([]);
|
|
444
|
+
React4__default.useEffect(() => {
|
|
445
|
+
let cancelled = false;
|
|
446
|
+
void loadProviderTemplates().then((data) => {
|
|
447
|
+
if (!cancelled) setRows(data);
|
|
448
|
+
});
|
|
449
|
+
return () => {
|
|
450
|
+
cancelled = true;
|
|
451
|
+
};
|
|
452
|
+
}, []);
|
|
453
|
+
return rows;
|
|
454
|
+
}
|
|
455
|
+
var ACTION_CATALOG = [
|
|
456
|
+
{ id: "confirm", label: "Confirma", kind: "reply", hint: "O cliente confirma o hor\xE1rio no toque." },
|
|
457
|
+
{ id: "cancel", label: "Cancela", kind: "reply", hint: "O cliente desmarca sem precisar ligar." },
|
|
458
|
+
{ id: "pay", label: "Pagar", kind: "payment", hint: "O cliente recebe o Pix na conversa." }
|
|
459
|
+
];
|
|
460
|
+
function nextActions(current, id, on) {
|
|
461
|
+
const kept = new Set(current.filter((a) => a.id !== id).map((a) => a.id));
|
|
462
|
+
if (on) kept.add(id);
|
|
463
|
+
const labels = new Map(current.map((a) => [a.id, a.label]));
|
|
464
|
+
return ACTION_CATALOG.filter((a) => kept.has(a.id)).map((a) => ({ id: a.id, label: labels.get(a.id) ?? a.label, kind: a.kind }));
|
|
465
|
+
}
|
|
466
|
+
var TEST_ERROR = {
|
|
467
|
+
template_not_approved: "O modelo ainda n\xE3o foi aprovado pela Meta \u2014 sincronize os modelos e tente de novo.",
|
|
468
|
+
no_sender: "Este app ainda n\xE3o tem um n\xFAmero de WhatsApp ativo. Veja Conversas \u2192 Tyxter.",
|
|
469
|
+
not_connected: "O WhatsApp ainda n\xE3o est\xE1 conectado neste app. Veja Conversas \u2192 Tyxter.",
|
|
470
|
+
contact_opted_out: "Este contato pediu para n\xE3o receber mensagens.",
|
|
471
|
+
invalid_recipient: "Esse destino n\xE3o parece v\xE1lido.",
|
|
472
|
+
not_a_member: "A sua sess\xE3o n\xE3o pertence a este neg\xF3cio.",
|
|
473
|
+
not_available: "O envio de teste n\xE3o est\xE1 dispon\xEDvel neste ambiente.",
|
|
474
|
+
email_not_configured: "O envio de e-mail n\xE3o est\xE1 configurado neste app.",
|
|
475
|
+
template_not_found: "Escreva a mensagem antes de testar.",
|
|
476
|
+
email_rejected: "O provedor de e-mail recusou a mensagem.",
|
|
477
|
+
email_credential_rejected: "A chave de e-mail do app foi recusada pelo provedor.",
|
|
478
|
+
email_rate_limited: "O provedor pediu uma pausa \u2014 tente de novo em instantes."
|
|
479
|
+
};
|
|
480
|
+
function TestSendStrip({ eventKey, channel, placeholders, template }) {
|
|
481
|
+
const isEmail = channel === "email";
|
|
482
|
+
const [recipient, setRecipient] = React4__default.useState("");
|
|
483
|
+
const [sending, setSending] = React4__default.useState(false);
|
|
484
|
+
const valid = isEmail ? /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient.trim()) : recipient.replace(/\D/g, "").length >= 10;
|
|
485
|
+
const send = async () => {
|
|
486
|
+
setSending(true);
|
|
487
|
+
try {
|
|
488
|
+
const variables = Object.fromEntries(placeholders.map((p) => {
|
|
489
|
+
const key = p.replace(/[{}]/g, "").trim();
|
|
490
|
+
return [key, SAMPLE[key] ?? key];
|
|
491
|
+
}));
|
|
492
|
+
const result = await sendTestMessage({ eventKey, channel, to: recipient.trim(), variables, template });
|
|
493
|
+
if (result.ok) {
|
|
494
|
+
toast.success(isEmail ? "Teste enviado \u2014 confira a caixa de entrada desse e-mail" : "Teste enviado \u2014 confira o WhatsApp desse n\xFAmero");
|
|
495
|
+
} else {
|
|
496
|
+
toast.error(TEST_ERROR[result.error ?? ""] ?? result.message ?? "N\xE3o foi poss\xEDvel enviar o teste");
|
|
497
|
+
}
|
|
498
|
+
} finally {
|
|
499
|
+
setSending(false);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
return (
|
|
503
|
+
// Sem moldura própria: o strip vive dentro do Card da aba Testar, e uma
|
|
504
|
+
// borda aqui era o anel duplicado de sempre.
|
|
505
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-end gap-2", children: [
|
|
506
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
507
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: isEmail ? "E-mail que recebe o teste" : "N\xFAmero que recebe o teste" }),
|
|
508
|
+
isEmail ? /* @__PURE__ */ jsx(
|
|
509
|
+
"input",
|
|
510
|
+
{
|
|
511
|
+
type: "email",
|
|
512
|
+
value: recipient,
|
|
513
|
+
onChange: (e) => setRecipient(e.target.value),
|
|
514
|
+
placeholder: "voce@exemplo.com",
|
|
515
|
+
disabled: sending,
|
|
516
|
+
"aria-label": "E-mail que recebe o teste",
|
|
517
|
+
className: "w-full rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
518
|
+
}
|
|
519
|
+
) : /* @__PURE__ */ jsx(
|
|
520
|
+
PhoneField,
|
|
521
|
+
{
|
|
522
|
+
value: recipient,
|
|
523
|
+
onChange: setRecipient,
|
|
524
|
+
placeholder: "(21) 99999-9999",
|
|
525
|
+
disabled: sending,
|
|
526
|
+
"aria-label": "N\xFAmero que recebe o teste"
|
|
527
|
+
}
|
|
528
|
+
)
|
|
529
|
+
] }),
|
|
530
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => void send(), disabled: !valid || sending, children: [
|
|
531
|
+
/* @__PURE__ */ jsx(Send, { className: "mr-2 h-3.5 w-3.5" }),
|
|
532
|
+
sending ? "Enviando\u2026" : "Enviar teste"
|
|
533
|
+
] })
|
|
534
|
+
] })
|
|
535
|
+
);
|
|
536
|
+
}
|
|
295
537
|
var TABS = [
|
|
296
538
|
{ value: "preview", label: "Pr\xE9via" },
|
|
297
539
|
{ value: "code", label: "C\xF3digo" },
|
|
@@ -299,6 +541,7 @@ var TABS = [
|
|
|
299
541
|
// é a mesma pergunta em dois tempos: o que vai sair, e o que saiu.
|
|
300
542
|
{ value: "runs", label: "Execu\xE7\xF5es" }
|
|
301
543
|
];
|
|
544
|
+
var TEST_TAB = { value: "test", label: "Testar" };
|
|
302
545
|
var CHANNEL_LABEL = {
|
|
303
546
|
email: "E-mail",
|
|
304
547
|
whatsapp: "WhatsApp",
|
|
@@ -306,6 +549,13 @@ var CHANNEL_LABEL = {
|
|
|
306
549
|
push: "Push",
|
|
307
550
|
webchat: "Chat"
|
|
308
551
|
};
|
|
552
|
+
var CHANNEL_ICON = {
|
|
553
|
+
email: Mail,
|
|
554
|
+
whatsapp: WhatsAppIcon,
|
|
555
|
+
sms: Smartphone,
|
|
556
|
+
push: MessageSquare,
|
|
557
|
+
webchat: MessageSquare
|
|
558
|
+
};
|
|
309
559
|
var WRITE_MESSAGE_TOOL = {
|
|
310
560
|
id: "automations.writeMessage",
|
|
311
561
|
name: "writeAutomationMessage",
|
|
@@ -341,27 +591,25 @@ var SAMPLE = {
|
|
|
341
591
|
appointment_date: "12/08/2026",
|
|
342
592
|
appointment_time: "14:30"
|
|
343
593
|
};
|
|
344
|
-
function iconFor(name) {
|
|
345
|
-
if (!name) return Zap;
|
|
346
|
-
const icons = LucideIcons;
|
|
347
|
-
return icons[name] ?? Zap;
|
|
348
|
-
}
|
|
349
594
|
function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations", embedded, onDone }) {
|
|
350
595
|
const tenantId = useOrganizationStoreLike();
|
|
351
596
|
const runtime = usePluginRuntimeOptional();
|
|
352
|
-
const fayzConnection =
|
|
353
|
-
const [message, setMessage] =
|
|
354
|
-
const [tab, setTab] =
|
|
355
|
-
const [device, setDevice] =
|
|
356
|
-
const [draft, setDraft] =
|
|
357
|
-
const [
|
|
358
|
-
const
|
|
597
|
+
const fayzConnection = React4__default.useMemo(() => resolveFayzAgentConnection(), []);
|
|
598
|
+
const [message, setMessage] = React4__default.useState(void 0);
|
|
599
|
+
const [tab, setTab] = React4__default.useState("preview");
|
|
600
|
+
const [device, setDevice] = React4__default.useState("desktop");
|
|
601
|
+
const [draft, setDraft] = React4__default.useState(null);
|
|
602
|
+
const [labelDrafts, setLabelDrafts] = React4__default.useState({});
|
|
603
|
+
const [saving, setSaving] = React4__default.useState(false);
|
|
604
|
+
const providerTemplates = useProviderTemplates();
|
|
605
|
+
const { readiness, loading: readinessLoading } = usePaymentReadiness();
|
|
606
|
+
const back = React4__default.useCallback(
|
|
359
607
|
() => onDone ? onDone() : adminNavigateTo(backTo),
|
|
360
608
|
[onDone, backTo]
|
|
361
609
|
);
|
|
362
|
-
const surfaceRef =
|
|
610
|
+
const surfaceRef = React4__default.useRef({ message, draft });
|
|
363
611
|
surfaceRef.current = { message, draft };
|
|
364
|
-
const agentSurface =
|
|
612
|
+
const agentSurface = React4__default.useMemo(() => ({
|
|
365
613
|
id: "automations.message",
|
|
366
614
|
pluginId: "automations",
|
|
367
615
|
instructions: "The user is editing one automation message. `placeholders` in the surface state are the ONLY variables that resolve \u2014 copy them verbatim, braces included. Call writeAutomationMessage once with the whole text; the editor fills and the user reviews before saving.",
|
|
@@ -397,7 +645,7 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
397
645
|
}]
|
|
398
646
|
}), []);
|
|
399
647
|
useAgentSurface(agentSurface);
|
|
400
|
-
const reload =
|
|
648
|
+
const reload = React4__default.useCallback(async () => {
|
|
401
649
|
if (!eventKey || !channel) {
|
|
402
650
|
setMessage(null);
|
|
403
651
|
return;
|
|
@@ -412,7 +660,7 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
412
660
|
setMessage(found);
|
|
413
661
|
if (found) setDraft({ subject: found.subject, body: found.body });
|
|
414
662
|
}, [eventKey, channel, store, tenantId, runtime]);
|
|
415
|
-
|
|
663
|
+
React4__default.useEffect(() => {
|
|
416
664
|
void reload();
|
|
417
665
|
}, [reload]);
|
|
418
666
|
const header = embedded ? null : /* @__PURE__ */ jsx(
|
|
@@ -447,7 +695,8 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
447
695
|
}
|
|
448
696
|
const event = message.event;
|
|
449
697
|
const fallback = { subject: message.override ? void 0 : message.subject, body: message.body };
|
|
450
|
-
const
|
|
698
|
+
const ChannelIcon = CHANNEL_ICON[message.channel] ?? Zap;
|
|
699
|
+
const isWhatsapp = message.channel === "whatsapp";
|
|
451
700
|
const current = draft ?? { subject: message.subject, body: message.body };
|
|
452
701
|
const dirty = current.subject !== message.subject || current.body !== message.body;
|
|
453
702
|
const hasSubject = message.channel === "email";
|
|
@@ -490,14 +739,27 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
490
739
|
header,
|
|
491
740
|
/* @__PURE__ */ jsxs("div", { className: embedded ? "space-y-3" : "flex flex-wrap items-start gap-3", children: [
|
|
492
741
|
/* @__PURE__ */ jsxs("div", { className: embedded ? "flex items-start gap-3" : "contents", children: [
|
|
493
|
-
/* @__PURE__ */ jsx(
|
|
742
|
+
/* @__PURE__ */ jsx(
|
|
743
|
+
"span",
|
|
744
|
+
{
|
|
745
|
+
className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border ${isWhatsapp ? "border-[#25D366]/30 bg-[#25D366]/10 text-[#1faf55]" : "border-border text-muted-foreground"}`,
|
|
746
|
+
children: /* @__PURE__ */ jsx(ChannelIcon, { className: "h-4 w-4" })
|
|
747
|
+
}
|
|
748
|
+
),
|
|
494
749
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
495
750
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
496
751
|
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: CHANNEL_LABEL[message.channel] }),
|
|
497
752
|
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: message.kind === "marketing" ? "Marketing" : "Transacional" }),
|
|
498
753
|
message.customized && /* @__PURE__ */ jsx(Badge, { variant: "info", children: "Personalizado" })
|
|
499
754
|
] }),
|
|
500
|
-
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: event.description })
|
|
755
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: event.description }),
|
|
756
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx(
|
|
757
|
+
ProviderTemplateBadge,
|
|
758
|
+
{
|
|
759
|
+
approval: providerApprovalFor(providerTemplates, event.name, message.channel),
|
|
760
|
+
showDetail: true
|
|
761
|
+
}
|
|
762
|
+
) })
|
|
501
763
|
] })
|
|
502
764
|
] }),
|
|
503
765
|
/* @__PURE__ */ jsxs("div", { className: embedded ? "flex items-center gap-2" : "flex shrink-0 flex-wrap items-center gap-2", children: [
|
|
@@ -545,11 +807,22 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
545
807
|
}
|
|
546
808
|
}
|
|
547
809
|
),
|
|
548
|
-
/* @__PURE__ */ jsx(Button, { onClick: save, disabled:
|
|
810
|
+
dirty || saving ? /* @__PURE__ */ jsx(Button, { onClick: save, disabled: saving, children: saving ? "Salvando\u2026" : "Salvar" }) : /* @__PURE__ */ jsxs("span", { className: "inline-flex h-9 items-center gap-1 px-2 text-xs font-medium text-muted-foreground", children: [
|
|
811
|
+
/* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
812
|
+
"Salvo"
|
|
813
|
+
] })
|
|
549
814
|
] })
|
|
550
815
|
] })
|
|
551
816
|
] }),
|
|
552
|
-
/* @__PURE__ */ jsx(
|
|
817
|
+
/* @__PURE__ */ jsx(
|
|
818
|
+
HeaderTabs,
|
|
819
|
+
{
|
|
820
|
+
tabs: isWhatsapp || message.channel === "email" ? [...TABS, TEST_TAB] : TABS,
|
|
821
|
+
value: tab,
|
|
822
|
+
onChange: setTab,
|
|
823
|
+
"aria-label": "Visualiza\xE7\xE3o"
|
|
824
|
+
}
|
|
825
|
+
),
|
|
553
826
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-b border-border pb-4 text-xs text-muted-foreground", children: [
|
|
554
827
|
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
555
828
|
/* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5" }),
|
|
@@ -570,9 +843,9 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
570
843
|
// é ela quem executa. Fora dele, o coletor local continua valendo.
|
|
571
844
|
fayzConnection ? /* @__PURE__ */ jsx(FayzRunLog, { connection: fayzConnection, eventKey: event.name, tenantId, enabled: message.enabled }) : /* @__PURE__ */ jsx(RunLog, { eventKey: event.name, tenantId, enabled: message.enabled })
|
|
572
845
|
) : tab === "preview" ? /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
573
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
846
|
+
!embedded && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
574
847
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Pr\xE9via" }),
|
|
575
|
-
/* @__PURE__ */ jsx("div", { className:
|
|
848
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: ["desktop", "mobile"].map((d) => /* @__PURE__ */ jsx(
|
|
576
849
|
"button",
|
|
577
850
|
{
|
|
578
851
|
type: "button",
|
|
@@ -588,19 +861,58 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
588
861
|
/* @__PURE__ */ jsx(
|
|
589
862
|
"div",
|
|
590
863
|
{
|
|
591
|
-
className: `mx-auto rounded-card
|
|
864
|
+
className: `mx-auto overflow-hidden rounded-card bg-card shadow-sm transition-[max-width] ${device === "mobile" ? "max-w-[380px]" : hasSubject ? "max-w-[600px]" : "max-w-[420px]"}`,
|
|
592
865
|
children: hasSubject ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
593
|
-
/* @__PURE__ */
|
|
594
|
-
|
|
595
|
-
/* @__PURE__ */ jsx("
|
|
596
|
-
/* @__PURE__ */ jsx("
|
|
866
|
+
/* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "flex items-center gap-1.5 border-b border-border bg-muted/40 px-4 py-2.5", children: [
|
|
867
|
+
/* @__PURE__ */ jsx("span", { className: "h-3 w-3 rounded-full bg-[#ff5f57]" }),
|
|
868
|
+
/* @__PURE__ */ jsx("span", { className: "h-3 w-3 rounded-full bg-[#febc2e]" }),
|
|
869
|
+
/* @__PURE__ */ jsx("span", { className: "h-3 w-3 rounded-full bg-[#28c840]" })
|
|
870
|
+
] }),
|
|
871
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 pb-4 pt-5", children: [
|
|
872
|
+
/* @__PURE__ */ jsx("p", { className: "text-base font-semibold leading-snug", children: previewSubject }),
|
|
873
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-3", children: [
|
|
874
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary", children: (SAMPLE.store_name ?? "S").charAt(0) }),
|
|
875
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
876
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: SAMPLE.store_name }),
|
|
877
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "para mim" })
|
|
878
|
+
] }),
|
|
879
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-xs text-muted-foreground", children: "09:41" })
|
|
880
|
+
] })
|
|
597
881
|
] }),
|
|
598
|
-
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap px-6 py-5 text-sm leading-relaxed", children: previewBody })
|
|
599
|
-
] }) : /* @__PURE__ */
|
|
882
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap border-t border-border px-6 py-5 text-sm leading-relaxed", children: previewBody })
|
|
883
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
884
|
+
"div",
|
|
885
|
+
{
|
|
886
|
+
className: "p-4",
|
|
887
|
+
style: isWhatsapp ? { backgroundImage: `url(${WHATSAPP_PREVIEW_BG})`, backgroundSize: "cover", backgroundPosition: "center" } : void 0,
|
|
888
|
+
children: [
|
|
889
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap rounded-2xl rounded-tl-sm bg-[#dcf8c6] px-3 py-2 text-sm leading-relaxed text-[#111b21] shadow-sm", children: previewBody }),
|
|
890
|
+
message.actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-1 space-y-1", children: message.actions.map((action) => /* @__PURE__ */ jsx(
|
|
891
|
+
"div",
|
|
892
|
+
{
|
|
893
|
+
className: "rounded-lg bg-white px-3 py-1.5 text-center text-sm font-medium text-[#1e7fd6] shadow-sm",
|
|
894
|
+
children: action.label
|
|
895
|
+
},
|
|
896
|
+
action.id
|
|
897
|
+
)) })
|
|
898
|
+
]
|
|
899
|
+
}
|
|
900
|
+
)
|
|
600
901
|
}
|
|
601
902
|
),
|
|
602
903
|
/* @__PURE__ */ jsx("p", { className: "mt-4 text-center text-xs text-muted-foreground", children: "Dados de exemplo \u2014 nenhum registro real foi usado." })
|
|
603
904
|
] })
|
|
905
|
+
] }) : tab === "test" ? /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
906
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: isWhatsapp ? "Envio real, pelo mesmo caminho da automa\xE7\xE3o \u2014 modelo aprovado, n\xFAmero ativo e entrega de verdade, com os dados de exemplo." : "Envio real, pelo mesmo caminho da automa\xE7\xE3o \u2014 vai o texto que est\xE1 na tela, salvo ou n\xE3o, com os dados de exemplo." }),
|
|
907
|
+
/* @__PURE__ */ jsx(
|
|
908
|
+
TestSendStrip,
|
|
909
|
+
{
|
|
910
|
+
eventKey: event.name,
|
|
911
|
+
channel: message.channel,
|
|
912
|
+
placeholders: message.placeholders,
|
|
913
|
+
template: message.channel === "email" ? { subject: current.subject, body: current.body } : void 0
|
|
914
|
+
}
|
|
915
|
+
)
|
|
604
916
|
] }) : /* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
|
|
605
917
|
hasSubject && /* @__PURE__ */ jsxs("div", { children: [
|
|
606
918
|
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Assunto" }),
|
|
@@ -625,6 +937,65 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
|
|
|
625
937
|
}
|
|
626
938
|
)
|
|
627
939
|
] }),
|
|
940
|
+
message.channel === "whatsapp" && /* @__PURE__ */ jsxs("div", { children: [
|
|
941
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: "Bot\xF5es \u2014 o cliente responde no toque, sem digitar" }),
|
|
942
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: ACTION_CATALOG.map((action) => {
|
|
943
|
+
const active = message.actions.find((a) => a.id === action.id);
|
|
944
|
+
const on = Boolean(active);
|
|
945
|
+
const gated = action.id === "pay" && !canOfferPayment(readiness);
|
|
946
|
+
const blocked = gated && !readinessLoading;
|
|
947
|
+
const labelValue = labelDrafts[action.id] ?? active?.label ?? action.label;
|
|
948
|
+
const commitLabel = () => {
|
|
949
|
+
const next = labelValue.trim();
|
|
950
|
+
setLabelDrafts((d) => {
|
|
951
|
+
const { [action.id]: _, ...rest } = d;
|
|
952
|
+
return rest;
|
|
953
|
+
});
|
|
954
|
+
if (!active || !next || next === active.label) return;
|
|
955
|
+
void patch({
|
|
956
|
+
eventKey: event.name,
|
|
957
|
+
channel: message.channel,
|
|
958
|
+
actions: message.actions.map((a) => a.id === action.id ? { ...a, label: next } : a)
|
|
959
|
+
});
|
|
960
|
+
};
|
|
961
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [
|
|
962
|
+
/* @__PURE__ */ jsx(
|
|
963
|
+
Switch,
|
|
964
|
+
{
|
|
965
|
+
size: "md",
|
|
966
|
+
checked: on,
|
|
967
|
+
disabled: saving || gated,
|
|
968
|
+
onCheckedChange: (next) => void patch({
|
|
969
|
+
eventKey: event.name,
|
|
970
|
+
channel: message.channel,
|
|
971
|
+
actions: nextActions(message.actions, action.id, next)
|
|
972
|
+
}),
|
|
973
|
+
label: on ? "" : action.label
|
|
974
|
+
}
|
|
975
|
+
),
|
|
976
|
+
on && // O texto do botão como a cliente vê. 25 caracteres é o
|
|
977
|
+
// teto da Meta para QUICK_REPLY; mudar o texto de um
|
|
978
|
+
// modelo já aprovado dispara nova aprovação — a
|
|
979
|
+
// pastilha de status acima conta essa história.
|
|
980
|
+
/* @__PURE__ */ jsx(
|
|
981
|
+
"input",
|
|
982
|
+
{
|
|
983
|
+
value: labelValue,
|
|
984
|
+
maxLength: 25,
|
|
985
|
+
disabled: saving,
|
|
986
|
+
"aria-label": `Texto do bot\xE3o ${action.label}`,
|
|
987
|
+
onChange: (e) => setLabelDrafts((d) => ({ ...d, [action.id]: e.target.value })),
|
|
988
|
+
onBlur: commitLabel,
|
|
989
|
+
onKeyDown: (e) => {
|
|
990
|
+
if (e.key === "Enter") e.target.blur();
|
|
991
|
+
},
|
|
992
|
+
className: "w-36 rounded-input border border-input bg-card px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
993
|
+
}
|
|
994
|
+
),
|
|
995
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: blocked ? "Para cobrar por aqui, configure a conta de pagamento em Conversas \u2192 Tyxter." : action.hint })
|
|
996
|
+
] }, action.id);
|
|
997
|
+
}) })
|
|
998
|
+
] }),
|
|
628
999
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
629
1000
|
/* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: "Campos deste evento \u2014 o envio substitui pelo dado real" }),
|
|
630
1001
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: message.placeholders.map((p) => /* @__PURE__ */ jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground", children: p }, p)) })
|
|
@@ -696,7 +1067,7 @@ function AutomationCard({
|
|
|
696
1067
|
group,
|
|
697
1068
|
href
|
|
698
1069
|
}) {
|
|
699
|
-
const [open, setOpen] =
|
|
1070
|
+
const [open, setOpen] = React4__default.useState(false);
|
|
700
1071
|
return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
701
1072
|
/* @__PURE__ */ jsxs(
|
|
702
1073
|
"div",
|
|
@@ -783,9 +1154,26 @@ var CONFIGURE_TOOL = {
|
|
|
783
1154
|
required: ["event", "channel"]
|
|
784
1155
|
}
|
|
785
1156
|
};
|
|
786
|
-
var
|
|
1157
|
+
var ASK_AI_ACTION = {
|
|
1158
|
+
label: "Criar com IA",
|
|
1159
|
+
hint: "O que revisar nas automa\xE7\xF5es\u2026",
|
|
1160
|
+
intro: {
|
|
1161
|
+
role: "setup",
|
|
1162
|
+
persona: "Redator das automa\xE7\xF5es",
|
|
1163
|
+
pitch: "Diga o que revisar nas automa\xE7\xF5es.",
|
|
1164
|
+
description: "Vejo todas as automa\xE7\xF5es deste app, quais est\xE3o ligadas e o texto de cada uma. Posso ligar, desligar e reescrever \u2014 em uma ou em v\xE1rias de uma vez. Voc\xEA confirma cada mudan\xE7a.",
|
|
1165
|
+
suggestions: [
|
|
1166
|
+
{ label: "Revise as mensagens de pedido no tom da minha marca" },
|
|
1167
|
+
{ label: "Ligue as automa\xE7\xF5es de entrega" },
|
|
1168
|
+
{ label: "Deixe as mensagens de WhatsApp mais curtas" }
|
|
1169
|
+
]
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
var CHANNEL_ICON2 = {
|
|
787
1173
|
email: Mail,
|
|
788
|
-
|
|
1174
|
+
// The brand glyph, not a generic bubble: in a list of channels, WhatsApp
|
|
1175
|
+
// is recognized by its mark before its label.
|
|
1176
|
+
whatsapp: WhatsAppIcon,
|
|
789
1177
|
sms: Smartphone,
|
|
790
1178
|
push: MessageSquare,
|
|
791
1179
|
webchat: MessageSquare
|
|
@@ -807,7 +1195,7 @@ var KIND_HINT = {
|
|
|
807
1195
|
transactional: "Disparados por uma a\xE7\xE3o da pessoa. N\xE3o precisam de opt-in.",
|
|
808
1196
|
marketing: "Iniciativa da empresa. S\xF3 para quem aceitou receber."
|
|
809
1197
|
};
|
|
810
|
-
function
|
|
1198
|
+
function iconFor(name) {
|
|
811
1199
|
if (!name) return Zap;
|
|
812
1200
|
const icons = LucideIcons;
|
|
813
1201
|
return icons[name] ?? Zap;
|
|
@@ -822,18 +1210,19 @@ function readSelection(basePath) {
|
|
|
822
1210
|
function AutomationsList({ store, variant = "page", basePath = "/automations" }) {
|
|
823
1211
|
const tenantId = useOrganizationStoreLike();
|
|
824
1212
|
const runtime = usePluginRuntimeOptional();
|
|
825
|
-
const [messages, setMessages] =
|
|
826
|
-
const [kind, setKind] =
|
|
1213
|
+
const [messages, setMessages] = React4__default.useState(null);
|
|
1214
|
+
const [kind, setKind] = React4__default.useState("all");
|
|
827
1215
|
const { connectors } = useConnectors({ includeGrants: false, includeCatalog: false });
|
|
828
|
-
const owned =
|
|
829
|
-
const
|
|
830
|
-
|
|
1216
|
+
const owned = React4__default.useMemo(() => connectorAutomationRows(connectors ?? []), [connectors]);
|
|
1217
|
+
const providerTemplates = useProviderTemplates();
|
|
1218
|
+
const [selected, setSelected] = React4__default.useState(() => readSelection(basePath));
|
|
1219
|
+
React4__default.useEffect(() => {
|
|
831
1220
|
const sync = () => setSelected(readSelection(basePath));
|
|
832
1221
|
sync();
|
|
833
1222
|
window.addEventListener("hashchange", sync);
|
|
834
1223
|
return () => window.removeEventListener("hashchange", sync);
|
|
835
1224
|
}, [basePath]);
|
|
836
|
-
const reload =
|
|
1225
|
+
const reload = React4__default.useCallback(async () => {
|
|
837
1226
|
let overrides = [];
|
|
838
1227
|
try {
|
|
839
1228
|
overrides = await store.list(tenantId);
|
|
@@ -842,12 +1231,12 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
|
|
|
842
1231
|
}
|
|
843
1232
|
setMessages(resolveMessages(overrides, runtime?.events ?? []));
|
|
844
1233
|
}, [store, tenantId, runtime]);
|
|
845
|
-
|
|
1234
|
+
React4__default.useEffect(() => {
|
|
846
1235
|
void reload();
|
|
847
1236
|
}, [reload]);
|
|
848
|
-
const surfaceRef =
|
|
849
|
-
surfaceRef.current = { messages, store, tenantId, reload };
|
|
850
|
-
const agentSurface =
|
|
1237
|
+
const surfaceRef = React4__default.useRef({ messages, store, tenantId, reload, basePath });
|
|
1238
|
+
surfaceRef.current = { messages, store, tenantId, reload, basePath };
|
|
1239
|
+
const agentSurface = React4__default.useMemo(() => ({
|
|
851
1240
|
id: "automations.list",
|
|
852
1241
|
pluginId: "automations",
|
|
853
1242
|
instructions: "The user is on the automations list. `automations` in the surface state is the complete set \u2014 use each entry's `event` and `channel` verbatim, and each entry's own `placeholders` when rewriting its text. Call configureAutomation once per automation you change; say afterwards which ones you touched.",
|
|
@@ -894,7 +1283,16 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
|
|
|
894
1283
|
...args.body === void 0 ? {} : { body: String(args.body) }
|
|
895
1284
|
}, o.tenantId);
|
|
896
1285
|
await o.reload();
|
|
897
|
-
return {
|
|
1286
|
+
return {
|
|
1287
|
+
ok: true,
|
|
1288
|
+
event: found.event.name,
|
|
1289
|
+
channel: found.channel,
|
|
1290
|
+
ref: {
|
|
1291
|
+
id: `${found.event.name}/${found.channel}`,
|
|
1292
|
+
label: `${found.event.title ?? found.event.name} \u2014 ${CHANNEL_LABEL2[found.channel] ?? found.channel}`,
|
|
1293
|
+
path: `${o.basePath}/${encodeURIComponent(found.event.name)}/${found.channel}`
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
898
1296
|
} catch (e) {
|
|
899
1297
|
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
900
1298
|
}
|
|
@@ -934,20 +1332,16 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
|
|
|
934
1332
|
] });
|
|
935
1333
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
936
1334
|
variant !== "panel" ? /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(
|
|
937
|
-
|
|
1335
|
+
PageActions,
|
|
938
1336
|
{
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
{ label: "Ligue as automa\xE7\xF5es de entrega" },
|
|
948
|
-
{ label: "Deixe as mensagens de WhatsApp mais curtas" }
|
|
949
|
-
]
|
|
950
|
-
}
|
|
1337
|
+
actions: [{
|
|
1338
|
+
id: "automations-ai",
|
|
1339
|
+
label: ASK_AI_ACTION.label,
|
|
1340
|
+
icon: WandSparkles,
|
|
1341
|
+
intent: "primary",
|
|
1342
|
+
creates: true,
|
|
1343
|
+
onSelect: () => askAI({ hint: ASK_AI_ACTION.hint, intro: ASK_AI_ACTION.intro })
|
|
1344
|
+
}]
|
|
951
1345
|
}
|
|
952
1346
|
) }) : null,
|
|
953
1347
|
variant === "panel" ? /* @__PURE__ */ jsx(
|
|
@@ -984,14 +1378,16 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
|
|
|
984
1378
|
] }) : groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
985
1379
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
|
|
986
1380
|
group.items.map((m) => {
|
|
987
|
-
const Icon =
|
|
988
|
-
const ChannelIcon =
|
|
1381
|
+
const Icon = iconFor(m.event.icon ?? m.event.pluginIcon);
|
|
1382
|
+
const ChannelIcon = CHANNEL_ICON2[m.channel];
|
|
989
1383
|
const href = `${basePath}/${encodeURIComponent(m.event.name)}/${m.channel}`;
|
|
1384
|
+
const isOpen = selected != null && decodeURIComponent(selected.eventKey) === m.event.name && selected.channel === m.channel;
|
|
990
1385
|
return /* @__PURE__ */ jsx(
|
|
991
1386
|
Card,
|
|
992
1387
|
{
|
|
993
1388
|
role: "button",
|
|
994
1389
|
tabIndex: 0,
|
|
1390
|
+
"aria-current": isOpen || void 0,
|
|
995
1391
|
onClick: () => adminNavigateTo(href),
|
|
996
1392
|
onKeyDown: (e) => {
|
|
997
1393
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -999,7 +1395,8 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
|
|
|
999
1395
|
adminNavigateTo(href);
|
|
1000
1396
|
}
|
|
1001
1397
|
},
|
|
1002
|
-
|
|
1398
|
+
style: isOpen ? { outline: "2px solid hsl(var(--primary) / 0.65)", outlineOffset: "-2px" } : void 0,
|
|
1399
|
+
className: `cursor-pointer overflow-hidden transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${isOpen ? "" : "hover:border-primary/40"}`,
|
|
1003
1400
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-4 p-4", children: [
|
|
1004
1401
|
/* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
|
|
1005
1402
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -1009,7 +1406,13 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
|
|
|
1009
1406
|
/* @__PURE__ */ jsx(ChannelIcon, { className: "h-3 w-3" }),
|
|
1010
1407
|
CHANNEL_LABEL2[m.channel]
|
|
1011
1408
|
] }),
|
|
1012
|
-
m.customized && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: "personalizado" })
|
|
1409
|
+
m.customized && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: "personalizado" }),
|
|
1410
|
+
/* @__PURE__ */ jsx(
|
|
1411
|
+
ProviderTemplateBadge,
|
|
1412
|
+
{
|
|
1413
|
+
approval: providerApprovalFor(providerTemplates, m.event.name, m.channel)
|
|
1414
|
+
}
|
|
1415
|
+
)
|
|
1013
1416
|
] }),
|
|
1014
1417
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: m.event.description }),
|
|
1015
1418
|
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
|
|
@@ -1081,6 +1484,9 @@ function rowToTemplate(row) {
|
|
|
1081
1484
|
subject: row.subject || null,
|
|
1082
1485
|
body: row.body || null,
|
|
1083
1486
|
name: row.name || null,
|
|
1487
|
+
// NULL fica NULL pelo mesmo motivo do `enabled`; lista vazia é uma escolha
|
|
1488
|
+
// ("sem botões"), não a ausência de uma.
|
|
1489
|
+
actions: normalizeMessageActions(row.actions),
|
|
1084
1490
|
updatedAt: row.updated_at ?? row.created_at
|
|
1085
1491
|
};
|
|
1086
1492
|
}
|
|
@@ -1108,6 +1514,7 @@ function createSupabaseTemplateStore() {
|
|
|
1108
1514
|
if (input.subject !== void 0) payload.subject = input.subject;
|
|
1109
1515
|
if (input.body !== void 0) payload.body = input.body;
|
|
1110
1516
|
if (input.name !== void 0) payload.name = input.name;
|
|
1517
|
+
if (input.actions !== void 0) payload.actions = input.actions;
|
|
1111
1518
|
const { data, error } = await db().from(TABLE).upsert(payload, { onConflict: "tenant_id,event_key,channel" }).select().single();
|
|
1112
1519
|
if (error) throw error;
|
|
1113
1520
|
return rowToTemplate(data);
|
|
@@ -1127,11 +1534,11 @@ function createAutomationsPlugin(options) {
|
|
|
1127
1534
|
const store = options?.store ?? (connection ? createFayzTemplateStore(connection) : createSupabaseTemplateStore());
|
|
1128
1535
|
startEventLog();
|
|
1129
1536
|
const inSettings = options?.placement === "settings";
|
|
1130
|
-
const ListPage = () =>
|
|
1537
|
+
const ListPage = () => React4__default.createElement(AutomationsList, { store });
|
|
1131
1538
|
ListPage.displayName = "AutomationsList";
|
|
1132
|
-
const SettingsListPage = () =>
|
|
1539
|
+
const SettingsListPage = () => React4__default.createElement(AutomationsList, { store, variant: "panel", basePath: "/settings/automations" });
|
|
1133
1540
|
SettingsListPage.displayName = "AutomationsSettings";
|
|
1134
|
-
const RailDetail = ({ params, onClose }) =>
|
|
1541
|
+
const RailDetail = ({ params, onClose }) => React4__default.createElement(AutomationDetailPage, {
|
|
1135
1542
|
eventKey: params.eventKey,
|
|
1136
1543
|
channel: params.channel,
|
|
1137
1544
|
store,
|
|
@@ -1181,6 +1588,6 @@ function createAutomationsPlugin(options) {
|
|
|
1181
1588
|
};
|
|
1182
1589
|
}
|
|
1183
1590
|
|
|
1184
|
-
export { createAutomationsPlugin, createFayzTemplateStore, createSupabaseTemplateStore };
|
|
1591
|
+
export { PROVIDER_TEMPLATES_TABLE, createAutomationsPlugin, createFayzTemplateStore, createSupabaseTemplateStore, loadProviderTemplates, providerApprovalFor, providerTemplateFor };
|
|
1185
1592
|
//# sourceMappingURL=index.js.map
|
|
1186
1593
|
//# sourceMappingURL=index.js.map
|