feat(settings): painel mostra API estável + InfinityAPI (não remove a infinite)
Ajuste: o painel de configurações lista as DUAS engines selecionáveis — "API estável" (baileys7) e "InfinityAPI" (infinite) — além da versão. Apenas a 'official' (Baileys 6, que dá 401) fica de fora da UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,35 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { ShieldCheck, Check, Loader2, Tag } from 'lucide-react';
|
||||
import { ShieldCheck, LayoutGrid, Check, Loader2, Tag } from 'lucide-react';
|
||||
|
||||
interface SettingsPanelProps {
|
||||
instanceId: string | null;
|
||||
}
|
||||
|
||||
// "API estável" aponta para a engine Baileys 7 oficial (a que conecta de fato).
|
||||
const STABLE_ENGINE = 'baileys7';
|
||||
// Engines oferecidas no painel. A 'official' (Baileys 6, que dá 401) fica de fora.
|
||||
// baileys7 → Baileys 7 oficial (a que conecta) → rótulo "API estável"
|
||||
// infinite → InfinityAPI (com botões)
|
||||
const ENGINES: {
|
||||
value: string; titulo: string; desc: string; icon: any;
|
||||
iconCls: string; activeBorder: string; activeBg: string; checkCls: string; btnCls: string;
|
||||
}[] = [
|
||||
{
|
||||
value: 'baileys7', titulo: 'API estável', desc: 'Conexão recomendada do WhatsApp',
|
||||
icon: ShieldCheck, iconCls: 'text-emerald-500',
|
||||
activeBorder: 'border-emerald-400', activeBg: 'bg-emerald-50', checkCls: 'text-emerald-600',
|
||||
btnCls: 'bg-emerald-500 hover:bg-emerald-600',
|
||||
},
|
||||
{
|
||||
value: 'infinite', titulo: 'InfinityAPI', desc: 'Com botões interativos (experimental)',
|
||||
icon: LayoutGrid, iconCls: 'text-amber-500',
|
||||
activeBorder: 'border-amber-400', activeBg: 'bg-amber-50', checkCls: 'text-amber-600',
|
||||
btnCls: 'bg-amber-500 hover:bg-amber-600',
|
||||
},
|
||||
];
|
||||
|
||||
export default function SettingsPanel({ instanceId }: SettingsPanelProps) {
|
||||
const [engine, setEngine] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saving, setSaving] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [version, setVersion] = useState<any>(null);
|
||||
|
||||
@@ -43,71 +61,81 @@ export default function SettingsPanel({ instanceId }: SettingsPanelProps) {
|
||||
.finally(() => setLoading(false));
|
||||
}, [instanceId]);
|
||||
|
||||
const ativarEstavel = useCallback(async () => {
|
||||
if (!instanceId || saving) return;
|
||||
setSaving(true);
|
||||
const selectEngine = useCallback(async (value: string) => {
|
||||
if (!instanceId || saving || value === engine) return;
|
||||
setSaving(value);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/instances/${instanceId}/engine`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeader() },
|
||||
body: JSON.stringify({ engine: STABLE_ENGINE }),
|
||||
body: JSON.stringify({ engine: value }),
|
||||
});
|
||||
if (res.status === 429) {
|
||||
const d = await res.json().catch(() => ({}));
|
||||
throw new Error(d.error ?? 'Aguarde o intervalo entre trocas.');
|
||||
}
|
||||
if (!res.ok) throw new Error('Falha ao ativar a API estável.');
|
||||
setEngine(STABLE_ENGINE);
|
||||
if (!res.ok) throw new Error('Falha ao trocar a API.');
|
||||
setEngine(value);
|
||||
} catch (e: any) {
|
||||
setError(e?.message ?? 'Erro ao ativar.');
|
||||
setError(e?.message ?? 'Erro ao trocar a API.');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
setSaving(null);
|
||||
}
|
||||
}, [instanceId, saving]);
|
||||
|
||||
const ativa = engine === STABLE_ENGINE;
|
||||
}, [instanceId, saving, engine]);
|
||||
|
||||
return (
|
||||
<div className="relative z-[1] flex-1 h-full overflow-y-auto bg-[#f0f2f5] p-6 md:p-10">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-3xl font-light text-[#41525d] mb-8">Configurações</h1>
|
||||
<h1 className="text-3xl font-light text-[#41525d] mb-2">Configurações</h1>
|
||||
<p className="text-base text-[#667781] mb-8">Escolha a API de conexão do WhatsApp para esta instância.</p>
|
||||
|
||||
{/* ── API estável ─────────────────────────────────────────── */}
|
||||
<div className={`rounded-2xl border-2 bg-white p-8 mb-6 transition-colors ${ativa ? 'border-emerald-400' : 'border-[#e9edef]'}`}>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center ${ativa ? 'bg-emerald-50' : 'bg-[#f0f2f5]'}`}>
|
||||
<ShieldCheck className={`w-8 h-8 ${ativa ? 'text-emerald-500' : 'text-[#54656f]'}`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-semibold text-[#111b21]">API estável</h2>
|
||||
<p className="text-base text-[#667781]">Conexão recomendada do WhatsApp</p>
|
||||
</div>
|
||||
{ativa && (
|
||||
<span className="flex items-center gap-1 text-emerald-600 font-medium text-lg">
|
||||
<Check className="w-6 h-6" /> Ativa
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!instanceId && (
|
||||
<p className="text-base text-[#667781] mb-6">Selecione uma instância primeiro.</p>
|
||||
)}
|
||||
|
||||
{!instanceId ? (
|
||||
<p className="text-base text-[#667781] mt-2">Selecione uma instância primeiro.</p>
|
||||
) : ativa ? (
|
||||
<p className="text-base text-emerald-600 mt-2">A API estável está ativa nesta instância.</p>
|
||||
) : (
|
||||
<button
|
||||
onClick={ativarEstavel}
|
||||
disabled={saving}
|
||||
className="mt-3 inline-flex items-center gap-2 rounded-xl bg-emerald-500 hover:bg-emerald-600 text-white text-lg font-medium px-6 py-3 disabled:opacity-60"
|
||||
>
|
||||
{saving ? <Loader2 className="w-5 h-5 animate-spin" /> : <ShieldCheck className="w-5 h-5" />}
|
||||
Ativar API estável
|
||||
</button>
|
||||
)}
|
||||
{loading && <p className="text-sm text-[#8696a0] mt-3">Carregando configuração…</p>}
|
||||
{error && <p className="text-sm text-red-500 mt-3">{error}</p>}
|
||||
{/* ── Engines ─────────────────────────────────────────────── */}
|
||||
<div className="flex flex-col gap-4 mb-6">
|
||||
{ENGINES.map((opt) => {
|
||||
const Icon = opt.icon;
|
||||
const ativa = engine === opt.value;
|
||||
const salvando = saving === opt.value;
|
||||
return (
|
||||
<div
|
||||
key={opt.value}
|
||||
className={`rounded-2xl border-2 bg-white p-8 transition-colors ${ativa ? `${opt.activeBorder} ${opt.activeBg}` : 'border-[#e9edef]'}`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center ${ativa ? 'bg-white' : 'bg-[#f0f2f5]'}`}>
|
||||
<Icon className={`w-8 h-8 ${opt.iconCls}`} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-2xl font-semibold text-[#111b21]">{opt.titulo}</h2>
|
||||
<p className="text-base text-[#667781]">{opt.desc}</p>
|
||||
</div>
|
||||
{ativa ? (
|
||||
<span className={`flex items-center gap-1 font-medium text-lg ${opt.checkCls}`}>
|
||||
<Check className="w-6 h-6" /> Ativa
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => selectEngine(opt.value)}
|
||||
disabled={!instanceId || !!saving}
|
||||
className={`inline-flex items-center gap-2 rounded-xl text-white text-lg font-medium px-6 py-3 disabled:opacity-60 ${opt.btnCls}`}
|
||||
>
|
||||
{salvando ? <Loader2 className="w-5 h-5 animate-spin" /> : <Icon className="w-5 h-5" />}
|
||||
Ativar
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{loading && <p className="text-sm text-[#8696a0] mb-4">Carregando configuração atual…</p>}
|
||||
{error && <p className="text-sm text-red-500 mb-4">{error}</p>}
|
||||
|
||||
{/* ── Versão ──────────────────────────────────────────────── */}
|
||||
<div className="rounded-2xl border-2 border-[#e9edef] bg-white p-8">
|
||||
<div className="flex items-center gap-4">
|
||||
|
||||
Reference in New Issue
Block a user