@greatapps/greatagents-ui 0.3.2 → 0.3.4
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/index.d.ts +204 -1
- package/dist/index.js +1797 -125
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +14 -0
- package/src/components/agents/agent-edit-form.tsx +4 -1
- package/src/components/agents/agent-form-dialog.tsx +5 -1
- package/src/components/agents/agent-objectives-list.tsx +15 -6
- package/src/components/agents/agent-prompt-editor.tsx +9 -5
- package/src/components/agents/agent-tabs.tsx +4 -4
- package/src/components/agents/agent-tools-list.tsx +12 -5
- package/src/components/agents/agents-table.tsx +7 -2
- package/src/components/capabilities/advanced-tab.tsx +82 -0
- package/src/components/capabilities/capabilities-tab.tsx +475 -0
- package/src/components/capabilities/integration-card.tsx +162 -0
- package/src/components/capabilities/integration-wizard.tsx +537 -0
- package/src/components/capabilities/integrations-tab.tsx +61 -0
- package/src/components/capabilities/types.ts +48 -0
- package/src/components/capabilities/wizard-steps/config-step.tsx +117 -0
- package/src/components/capabilities/wizard-steps/confirm-step.tsx +123 -0
- package/src/components/capabilities/wizard-steps/credentials-step.tsx +205 -0
- package/src/components/capabilities/wizard-steps/info-step.tsx +78 -0
- package/src/components/conversations/agent-conversations-table.tsx +13 -2
- package/src/components/conversations/conversation-view.tsx +2 -2
- package/src/components/tools/tool-credentials-form.tsx +34 -14
- package/src/components/tools/tool-form-dialog.tsx +9 -5
- package/src/components/tools/tools-table.tsx +8 -3
- package/src/data/integrations-registry.ts +23 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/use-capabilities.ts +50 -0
- package/src/hooks/use-integrations.ts +114 -0
- package/src/index.ts +34 -0
- package/src/pages/agent-capabilities-page.tsx +159 -0
- package/src/pages/agent-detail-page.tsx +1 -0
- package/src/pages/index.ts +2 -0
- package/src/pages/integrations-management-page.tsx +166 -0
- package/src/types/capabilities.ts +32 -0
- package/src/types/index.ts +10 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Loader2 } from "lucide-react";
|
|
4
|
+
import {
|
|
5
|
+
Label,
|
|
6
|
+
Select,
|
|
7
|
+
SelectContent,
|
|
8
|
+
SelectItem,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
12
|
+
import type { IntegrationDefinition } from "../../../data/integrations-registry";
|
|
13
|
+
|
|
14
|
+
export interface ConfigOption {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ConfigStepProps {
|
|
21
|
+
integration: IntegrationDefinition;
|
|
22
|
+
/** Available options loaded from backend (e.g. calendars) */
|
|
23
|
+
options: ConfigOption[];
|
|
24
|
+
isLoading: boolean;
|
|
25
|
+
selectedValue: string;
|
|
26
|
+
onValueChange: (value: string) => void;
|
|
27
|
+
/** Custom label for the select dropdown */
|
|
28
|
+
selectLabel?: string;
|
|
29
|
+
/** Custom placeholder */
|
|
30
|
+
selectPlaceholder?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ConfigStep({
|
|
34
|
+
integration,
|
|
35
|
+
options,
|
|
36
|
+
isLoading,
|
|
37
|
+
selectedValue,
|
|
38
|
+
onValueChange,
|
|
39
|
+
selectLabel,
|
|
40
|
+
selectPlaceholder,
|
|
41
|
+
}: ConfigStepProps) {
|
|
42
|
+
const label = selectLabel || getDefaultLabel(integration.slug);
|
|
43
|
+
const placeholder =
|
|
44
|
+
selectPlaceholder || getDefaultPlaceholder(integration.slug);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="space-y-6">
|
|
48
|
+
<div className="space-y-2">
|
|
49
|
+
<h3 className="text-lg font-semibold">Configuração</h3>
|
|
50
|
+
<p className="text-sm text-muted-foreground">
|
|
51
|
+
Configure as opções específicas da integração.
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{isLoading ? (
|
|
56
|
+
<div className="flex flex-col items-center gap-3 py-8">
|
|
57
|
+
<Loader2
|
|
58
|
+
aria-hidden="true"
|
|
59
|
+
className="h-6 w-6 animate-spin text-muted-foreground"
|
|
60
|
+
/>
|
|
61
|
+
<p className="text-sm text-muted-foreground">
|
|
62
|
+
Carregando opções...
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
) : options.length === 0 ? (
|
|
66
|
+
<div className="rounded-lg border border-dashed p-6 text-center">
|
|
67
|
+
<p className="text-sm text-muted-foreground">
|
|
68
|
+
Nenhuma opção disponível. A configuração padrão será usada.
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
) : (
|
|
72
|
+
<div className="space-y-2">
|
|
73
|
+
<Label htmlFor="integration-config-select">{label}</Label>
|
|
74
|
+
<Select value={selectedValue} onValueChange={onValueChange}>
|
|
75
|
+
<SelectTrigger id="integration-config-select">
|
|
76
|
+
<SelectValue placeholder={placeholder} />
|
|
77
|
+
</SelectTrigger>
|
|
78
|
+
<SelectContent>
|
|
79
|
+
{options.map((opt) => (
|
|
80
|
+
<SelectItem key={opt.id} value={opt.id}>
|
|
81
|
+
<span>{opt.label}</span>
|
|
82
|
+
{opt.description && (
|
|
83
|
+
<span className="ml-2 text-xs text-muted-foreground">
|
|
84
|
+
({opt.description})
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
</SelectItem>
|
|
88
|
+
))}
|
|
89
|
+
</SelectContent>
|
|
90
|
+
</Select>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Helpers
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
function getDefaultLabel(slug: string): string {
|
|
102
|
+
switch (slug) {
|
|
103
|
+
case "google_calendar":
|
|
104
|
+
return "Calendário";
|
|
105
|
+
default:
|
|
106
|
+
return "Opção";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getDefaultPlaceholder(slug: string): string {
|
|
111
|
+
switch (slug) {
|
|
112
|
+
case "google_calendar":
|
|
113
|
+
return "Selecione o calendário...";
|
|
114
|
+
default:
|
|
115
|
+
return "Selecione uma opção...";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { CheckCircle2 } from "lucide-react";
|
|
4
|
+
import { Label } from "@greatapps/greatauth-ui/ui";
|
|
5
|
+
import type { IntegrationDefinition } from "../../../data/integrations-registry";
|
|
6
|
+
import type { OAuthResult } from "../types";
|
|
7
|
+
import type { ConfigOption } from "./config-step";
|
|
8
|
+
|
|
9
|
+
interface ConfirmStepProps {
|
|
10
|
+
integration: IntegrationDefinition;
|
|
11
|
+
oauthResult: OAuthResult | null;
|
|
12
|
+
selectedConfigOption: ConfigOption | null;
|
|
13
|
+
enableOnComplete: boolean;
|
|
14
|
+
onEnableChange: (enabled: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ConfirmStep({
|
|
18
|
+
integration,
|
|
19
|
+
oauthResult,
|
|
20
|
+
selectedConfigOption,
|
|
21
|
+
enableOnComplete,
|
|
22
|
+
onEnableChange,
|
|
23
|
+
}: ConfirmStepProps) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="space-y-6">
|
|
26
|
+
<div className="space-y-2">
|
|
27
|
+
<h3 className="text-lg font-semibold">Confirmação</h3>
|
|
28
|
+
<p className="text-sm text-muted-foreground">
|
|
29
|
+
Revise as configurações antes de concluir.
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{/* Summary */}
|
|
34
|
+
<div className="space-y-3 rounded-lg border p-4">
|
|
35
|
+
<SummaryRow label="Integração" value={integration.name} />
|
|
36
|
+
|
|
37
|
+
{oauthResult?.email && (
|
|
38
|
+
<SummaryRow label="Conta conectada" value={oauthResult.email} />
|
|
39
|
+
)}
|
|
40
|
+
|
|
41
|
+
{selectedConfigOption && (
|
|
42
|
+
<SummaryRow
|
|
43
|
+
label={getConfigLabel(integration.slug)}
|
|
44
|
+
value={selectedConfigOption.label}
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
<SummaryRow
|
|
49
|
+
label="Tipo de autenticação"
|
|
50
|
+
value={integration.authType === "oauth2" ? "OAuth 2.0" : "API Key"}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
{/* Enable toggle */}
|
|
55
|
+
<div className="flex items-center justify-between rounded-lg border p-4">
|
|
56
|
+
<div className="space-y-0.5">
|
|
57
|
+
<Label htmlFor="enable-on-complete" className="text-sm font-medium">
|
|
58
|
+
Ativar imediatamente
|
|
59
|
+
</Label>
|
|
60
|
+
<p className="text-xs text-muted-foreground">
|
|
61
|
+
A integração ficará ativa assim que concluir o assistente.
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
<button
|
|
65
|
+
id="enable-on-complete"
|
|
66
|
+
role="switch"
|
|
67
|
+
type="button"
|
|
68
|
+
aria-checked={enableOnComplete}
|
|
69
|
+
onClick={() => onEnableChange(!enableOnComplete)}
|
|
70
|
+
className={`
|
|
71
|
+
relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent
|
|
72
|
+
transition-colors duration-200 ease-in-out focus-visible:outline-none focus-visible:ring-2
|
|
73
|
+
focus-visible:ring-ring focus-visible:ring-offset-2
|
|
74
|
+
${enableOnComplete ? "bg-primary" : "bg-muted"}
|
|
75
|
+
`}
|
|
76
|
+
>
|
|
77
|
+
<span
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
className={`
|
|
80
|
+
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-background shadow-lg
|
|
81
|
+
ring-0 transition duration-200 ease-in-out
|
|
82
|
+
${enableOnComplete ? "translate-x-5" : "translate-x-0"}
|
|
83
|
+
`}
|
|
84
|
+
/>
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{/* Success indicator */}
|
|
89
|
+
<div className="flex items-center gap-2 rounded-md bg-green-50 p-3 dark:bg-green-950/20">
|
|
90
|
+
<CheckCircle2
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
className="h-4 w-4 shrink-0 text-green-600"
|
|
93
|
+
/>
|
|
94
|
+
<p className="text-xs text-green-700 dark:text-green-400">
|
|
95
|
+
Tudo pronto! Clique em "Concluir" para finalizar a
|
|
96
|
+
configuração.
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Helpers
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function SummaryRow({ label, value }: { label: string; value: string }) {
|
|
108
|
+
return (
|
|
109
|
+
<div className="flex items-center justify-between text-sm">
|
|
110
|
+
<span className="text-muted-foreground">{label}</span>
|
|
111
|
+
<span className="font-medium">{value}</span>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getConfigLabel(slug: string): string {
|
|
117
|
+
switch (slug) {
|
|
118
|
+
case "google_calendar":
|
|
119
|
+
return "Calendário";
|
|
120
|
+
default:
|
|
121
|
+
return "Configuração";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { CheckCircle2, Loader2, AlertCircle, Shield } from "lucide-react";
|
|
4
|
+
import { Button, Input, Label } from "@greatapps/greatauth-ui/ui";
|
|
5
|
+
import type { IntegrationDefinition } from "../../../data/integrations-registry";
|
|
6
|
+
import type { WizardIntegrationMeta, OAuthStatus, OAuthResult } from "../types";
|
|
7
|
+
|
|
8
|
+
interface CredentialsStepProps {
|
|
9
|
+
integration: IntegrationDefinition;
|
|
10
|
+
meta: WizardIntegrationMeta;
|
|
11
|
+
oauthStatus: OAuthStatus;
|
|
12
|
+
oauthResult: OAuthResult | null;
|
|
13
|
+
apiKey: string;
|
|
14
|
+
onApiKeyChange: (value: string) => void;
|
|
15
|
+
onStartOAuth: () => void;
|
|
16
|
+
isReconnect?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CredentialsStep({
|
|
20
|
+
integration,
|
|
21
|
+
meta,
|
|
22
|
+
oauthStatus,
|
|
23
|
+
oauthResult,
|
|
24
|
+
apiKey,
|
|
25
|
+
onApiKeyChange,
|
|
26
|
+
onStartOAuth,
|
|
27
|
+
isReconnect = false,
|
|
28
|
+
}: CredentialsStepProps) {
|
|
29
|
+
if (integration.authType === "oauth2") {
|
|
30
|
+
return (
|
|
31
|
+
<OAuthCredentials
|
|
32
|
+
integration={integration}
|
|
33
|
+
meta={meta}
|
|
34
|
+
oauthStatus={oauthStatus}
|
|
35
|
+
oauthResult={oauthResult}
|
|
36
|
+
onStartOAuth={onStartOAuth}
|
|
37
|
+
isReconnect={isReconnect}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return <ApiKeyCredentials apiKey={apiKey} onApiKeyChange={onApiKeyChange} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// OAuth flow UI
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
function OAuthCredentials({
|
|
50
|
+
integration,
|
|
51
|
+
meta,
|
|
52
|
+
oauthStatus,
|
|
53
|
+
oauthResult,
|
|
54
|
+
onStartOAuth,
|
|
55
|
+
isReconnect,
|
|
56
|
+
}: {
|
|
57
|
+
integration: IntegrationDefinition;
|
|
58
|
+
meta: WizardIntegrationMeta;
|
|
59
|
+
oauthStatus: OAuthStatus;
|
|
60
|
+
oauthResult: OAuthResult | null;
|
|
61
|
+
onStartOAuth: () => void;
|
|
62
|
+
isReconnect: boolean;
|
|
63
|
+
}) {
|
|
64
|
+
const providerLabel = meta.providerLabel || integration.name;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className="space-y-6">
|
|
68
|
+
<div className="space-y-2">
|
|
69
|
+
<h3 className="text-lg font-semibold">Autenticação</h3>
|
|
70
|
+
<p className="text-sm text-muted-foreground">
|
|
71
|
+
{isReconnect
|
|
72
|
+
? `Reconecte sua conta ${providerLabel} para renovar a autorização.`
|
|
73
|
+
: `Conecte sua conta ${providerLabel} para permitir o acesso.`}
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* OAuth status area */}
|
|
78
|
+
<div className="flex flex-col items-center gap-4 rounded-lg border p-6">
|
|
79
|
+
{oauthStatus === "idle" && (
|
|
80
|
+
<Button onClick={onStartOAuth} size="lg" className="gap-2">
|
|
81
|
+
{meta.icon}
|
|
82
|
+
{isReconnect
|
|
83
|
+
? `Reconectar com ${providerLabel}`
|
|
84
|
+
: `Conectar com ${providerLabel}`}
|
|
85
|
+
</Button>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{oauthStatus === "waiting" && (
|
|
89
|
+
<div className="flex flex-col items-center gap-3 text-center">
|
|
90
|
+
<Loader2
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
className="h-8 w-8 animate-spin text-muted-foreground"
|
|
93
|
+
/>
|
|
94
|
+
<div>
|
|
95
|
+
<p className="text-sm font-medium">Aguardando autorização...</p>
|
|
96
|
+
<p className="text-xs text-muted-foreground">
|
|
97
|
+
Complete o login na janela que foi aberta.
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{oauthStatus === "success" && oauthResult && (
|
|
104
|
+
<div className="flex flex-col items-center gap-3 text-center">
|
|
105
|
+
<CheckCircle2
|
|
106
|
+
aria-hidden="true"
|
|
107
|
+
className="h-8 w-8 text-green-600"
|
|
108
|
+
/>
|
|
109
|
+
<div>
|
|
110
|
+
<p className="text-sm font-medium text-green-700">
|
|
111
|
+
Conectado com sucesso!
|
|
112
|
+
</p>
|
|
113
|
+
{oauthResult.email && (
|
|
114
|
+
<p className="text-xs text-muted-foreground">
|
|
115
|
+
{oauthResult.email}
|
|
116
|
+
</p>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{oauthStatus === "error" && (
|
|
123
|
+
<div className="flex flex-col items-center gap-3 text-center">
|
|
124
|
+
<AlertCircle
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
className="h-8 w-8 text-destructive"
|
|
127
|
+
/>
|
|
128
|
+
<div>
|
|
129
|
+
<p className="text-sm font-medium text-destructive">
|
|
130
|
+
Falha na conexão
|
|
131
|
+
</p>
|
|
132
|
+
{oauthResult?.error && (
|
|
133
|
+
<p className="text-xs text-muted-foreground">
|
|
134
|
+
{oauthResult.error}
|
|
135
|
+
</p>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
<Button variant="outline" onClick={onStartOAuth} size="sm">
|
|
139
|
+
Tentar novamente
|
|
140
|
+
</Button>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{/* Security note */}
|
|
146
|
+
<div className="flex items-start gap-2 rounded-md bg-muted/50 p-3">
|
|
147
|
+
<Shield
|
|
148
|
+
aria-hidden="true"
|
|
149
|
+
className="mt-0.5 h-4 w-4 shrink-0 text-green-600"
|
|
150
|
+
/>
|
|
151
|
+
<p className="text-xs text-muted-foreground">
|
|
152
|
+
Seus dados estão seguros. Usamos OAuth 2.0 para autenticação — nunca
|
|
153
|
+
armazenamos sua senha. Você pode revogar o acesso a qualquer momento
|
|
154
|
+
nas configurações da sua conta {providerLabel}.
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// API Key flow UI
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
function ApiKeyCredentials({
|
|
166
|
+
apiKey,
|
|
167
|
+
onApiKeyChange,
|
|
168
|
+
}: {
|
|
169
|
+
apiKey: string;
|
|
170
|
+
onApiKeyChange: (value: string) => void;
|
|
171
|
+
}) {
|
|
172
|
+
return (
|
|
173
|
+
<div className="space-y-6">
|
|
174
|
+
<div className="space-y-2">
|
|
175
|
+
<h3 className="text-lg font-semibold">Autenticação</h3>
|
|
176
|
+
<p className="text-sm text-muted-foreground">
|
|
177
|
+
Insira a chave de API para conectar a integração.
|
|
178
|
+
</p>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<div className="space-y-2">
|
|
182
|
+
<Label htmlFor="integration-api-key">Chave de API</Label>
|
|
183
|
+
<Input
|
|
184
|
+
id="integration-api-key"
|
|
185
|
+
type="password"
|
|
186
|
+
autoComplete="off"
|
|
187
|
+
placeholder="Insira sua chave de API..."
|
|
188
|
+
value={apiKey}
|
|
189
|
+
onChange={(e) => onApiKeyChange(e.target.value)}
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div className="flex items-start gap-2 rounded-md bg-muted/50 p-3">
|
|
194
|
+
<Shield
|
|
195
|
+
aria-hidden="true"
|
|
196
|
+
className="mt-0.5 h-4 w-4 shrink-0 text-green-600"
|
|
197
|
+
/>
|
|
198
|
+
<p className="text-xs text-muted-foreground">
|
|
199
|
+
Sua chave de API é armazenada de forma segura e encriptada. Nunca é
|
|
200
|
+
exposta no frontend.
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Check, Info } from "lucide-react";
|
|
4
|
+
import type { IntegrationDefinition } from "../../../data/integrations-registry";
|
|
5
|
+
import type { WizardIntegrationMeta } from "../types";
|
|
6
|
+
|
|
7
|
+
interface InfoStepProps {
|
|
8
|
+
integration: IntegrationDefinition;
|
|
9
|
+
meta: WizardIntegrationMeta;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function InfoStep({ integration, meta }: InfoStepProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="space-y-6">
|
|
15
|
+
{/* Header */}
|
|
16
|
+
<div className="flex items-start gap-4">
|
|
17
|
+
{meta.icon && (
|
|
18
|
+
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-muted">
|
|
19
|
+
{meta.icon}
|
|
20
|
+
</div>
|
|
21
|
+
)}
|
|
22
|
+
<div className="space-y-1">
|
|
23
|
+
<h3 className="text-lg font-semibold">{integration.name}</h3>
|
|
24
|
+
<p className="text-sm text-muted-foreground">
|
|
25
|
+
{integration.description}
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
{/* Capabilities */}
|
|
31
|
+
{meta.capabilities.length > 0 && (
|
|
32
|
+
<div className="space-y-3">
|
|
33
|
+
<h4 className="text-sm font-medium">O que esta integração permite:</h4>
|
|
34
|
+
<ul className="space-y-2">
|
|
35
|
+
{meta.capabilities.map((cap, i) => (
|
|
36
|
+
<li key={i} className="flex items-start gap-2 text-sm">
|
|
37
|
+
<Check
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
className="mt-0.5 h-4 w-4 shrink-0 text-green-600"
|
|
40
|
+
/>
|
|
41
|
+
<div>
|
|
42
|
+
<span className="font-medium">{cap.label}</span>
|
|
43
|
+
{cap.description && (
|
|
44
|
+
<span className="text-muted-foreground">
|
|
45
|
+
{" "}
|
|
46
|
+
— {cap.description}
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</li>
|
|
51
|
+
))}
|
|
52
|
+
</ul>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
{/* Requirements */}
|
|
57
|
+
{meta.requirements.length > 0 && (
|
|
58
|
+
<div className="space-y-3">
|
|
59
|
+
<h4 className="text-sm font-medium">Requisitos:</h4>
|
|
60
|
+
<ul className="space-y-2">
|
|
61
|
+
{meta.requirements.map((req, i) => (
|
|
62
|
+
<li
|
|
63
|
+
key={i}
|
|
64
|
+
className="flex items-start gap-2 text-sm text-muted-foreground"
|
|
65
|
+
>
|
|
66
|
+
<Info
|
|
67
|
+
aria-hidden="true"
|
|
68
|
+
className="mt-0.5 h-4 w-4 shrink-0 text-blue-500"
|
|
69
|
+
/>
|
|
70
|
+
<span>{req}</span>
|
|
71
|
+
</li>
|
|
72
|
+
))}
|
|
73
|
+
</ul>
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -89,11 +89,21 @@ export function AgentConversationsTable({
|
|
|
89
89
|
<TableRow
|
|
90
90
|
key={conversation.id}
|
|
91
91
|
className="cursor-pointer"
|
|
92
|
+
role="button"
|
|
93
|
+
tabIndex={0}
|
|
92
94
|
onClick={() =>
|
|
93
95
|
setSelectedId(
|
|
94
96
|
selectedId === conversation.id ? null : conversation.id,
|
|
95
97
|
)
|
|
96
98
|
}
|
|
99
|
+
onKeyDown={(e) => {
|
|
100
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
setSelectedId(
|
|
103
|
+
selectedId === conversation.id ? null : conversation.id,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}}
|
|
97
107
|
data-state={selectedId === conversation.id ? "selected" : undefined}
|
|
98
108
|
>
|
|
99
109
|
<TableCell className="font-mono text-xs">
|
|
@@ -111,10 +121,10 @@ export function AgentConversationsTable({
|
|
|
111
121
|
<span className="text-xs text-muted-foreground">—</span>
|
|
112
122
|
)}
|
|
113
123
|
</TableCell>
|
|
114
|
-
<TableCell className="text-right">
|
|
124
|
+
<TableCell className="text-right tabular-nums">
|
|
115
125
|
{conversation.message_count ?? "—"}
|
|
116
126
|
</TableCell>
|
|
117
|
-
<TableCell className="text-right">
|
|
127
|
+
<TableCell className="text-right tabular-nums">
|
|
118
128
|
{conversation.usage_tokens ?? "—"}
|
|
119
129
|
</TableCell>
|
|
120
130
|
<TableCell>
|
|
@@ -133,6 +143,7 @@ export function AgentConversationsTable({
|
|
|
133
143
|
<a
|
|
134
144
|
href={`/gchat/inbox/${conversation.id_external}`}
|
|
135
145
|
title="Ver no Chat"
|
|
146
|
+
aria-label="Ver no Chat"
|
|
136
147
|
onClick={(e) => e.stopPropagation()}
|
|
137
148
|
className="inline-flex items-center justify-center rounded-md p-1.5 text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
|
138
149
|
>
|
|
@@ -45,7 +45,7 @@ export function ConversationView({
|
|
|
45
45
|
<p className="text-sm text-muted-foreground">
|
|
46
46
|
Conversa não encontrada.
|
|
47
47
|
</p>
|
|
48
|
-
<Button variant="ghost" size="icon" onClick={onClose}>
|
|
48
|
+
<Button variant="ghost" size="icon" aria-label="Fechar" onClick={onClose}>
|
|
49
49
|
<X className="h-4 w-4" />
|
|
50
50
|
</Button>
|
|
51
51
|
</div>
|
|
@@ -58,7 +58,7 @@ export function ConversationView({
|
|
|
58
58
|
<div className="flex items-center justify-between">
|
|
59
59
|
<h3 className="font-semibold">Detalhes da conversa #{conversation.id}</h3>
|
|
60
60
|
<div className="flex items-center gap-1">
|
|
61
|
-
<Button variant="ghost" size="icon" onClick={onClose}>
|
|
61
|
+
<Button variant="ghost" size="icon" aria-label="Fechar" onClick={onClose}>
|
|
62
62
|
<X className="h-4 w-4" />
|
|
63
63
|
</Button>
|
|
64
64
|
</div>
|