b031117925
- PluginsView: registra o plugin 'newwhats' (config só no catálogo, exclusivo do
superadmin via view 'plugins' — já gateada em isViewAllowed).
- pluginRegistry: 'newwhats' em ALWAYS_ON (ativação por plugin é local/localStorage
e não propaga; ALWAYS_ON garante Inbox/Secretária para TODOS os usuários).
- App.tsx: views wa-inbox/wa-secretaria (ViewKey, rotas, render); gating
isPluginActive('newwhats') libera a qualquer usuário.
- Sidebar: itens WHATSAPP + SECRETÁRIA IA quando o plugin está ativo.
- Novas views consomem o proxy /api/nw/v1/* (motor ext-api). Estágio 1: funcional
(lista/thread/envio; ask da secretária), visual aproximado do motor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
3.7 KiB
TypeScript
68 lines
3.7 KiB
TypeScript
// WaSecretariaView — painel da Secretária IA (motor) no scoreodonto. Estágio 1:
|
|
// chat de teste via proxy POST /api/nw/v1/secretaria/ask. A configuração do
|
|
// cérebro/agente vive no motor (acessível ao super-admin).
|
|
import React, { useRef, useState } from 'react';
|
|
import { PageHeader } from '../components/PageHeader.tsx';
|
|
|
|
const API = (import.meta as any).env?.VITE_API_URL || '/api';
|
|
|
|
async function ask(chatId: string, message: string) {
|
|
const token = localStorage.getItem('SCOREODONTO_AUTH_TOKEN');
|
|
const res = await fetch(`${API}/nw/v1/secretaria/ask`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
|
body: JSON.stringify({ chatId, message, contactName: 'Teste (painel)' }),
|
|
});
|
|
if (!res.ok) throw new Error(`${res.status} ${await res.text().catch(() => '')}`.slice(0, 200));
|
|
return res.json();
|
|
}
|
|
|
|
export const WaSecretariaView: React.FC = () => {
|
|
const [turns, setTurns] = useState<Array<{ q: string; a: string }>>([]);
|
|
const [text, setText] = useState('');
|
|
const [busy, setBusy] = useState(false);
|
|
const [err, setErr] = useState<string | null>(null);
|
|
const chatId = useRef(`painel-${Date.now()}`);
|
|
const endRef = useRef<HTMLDivElement>(null);
|
|
|
|
const send = async () => {
|
|
if (!text.trim() || busy) return;
|
|
const q = text.trim(); setText(''); setBusy(true); setErr(null);
|
|
try {
|
|
const r = await ask(chatId.current, q);
|
|
setTurns((p) => [...p, { q, a: r.reply ?? JSON.stringify(r) }]);
|
|
setTimeout(() => endRef.current?.scrollIntoView(), 50);
|
|
} catch (e: any) { setErr(String(e.message)); }
|
|
finally { setBusy(false); }
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
<PageHeader title="Secretária IA" description="Atendente virtual (motor NewWhats) — painel de teste" />
|
|
{err && <div style={{ background: '#fee2e2', color: '#991b1b', padding: '8px 12px', fontSize: 13 }}>Erro: {err}</div>}
|
|
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', border: '1px solid #e5e7eb', borderRadius: 8, background: '#fff', overflow: 'hidden' }}>
|
|
<div style={{ flex: 1, overflowY: 'auto', padding: 16 }}>
|
|
{!turns.length && <div style={{ color: '#9ca3af', fontSize: 14 }}>Faça uma pergunta para testar a Secretária (ex.: "Quais procedimentos vocês fazem e os valores?").</div>}
|
|
{turns.map((t, i) => (
|
|
<div key={i} style={{ marginBottom: 14 }}>
|
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 4 }}>
|
|
<div style={{ background: '#dbeafe', padding: '8px 12px', borderRadius: 10, fontSize: 14, maxWidth: '75%' }}>{t.q}</div>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'flex-start' }}>
|
|
<div style={{ background: '#f3f4f6', padding: '8px 12px', borderRadius: 10, fontSize: 14, maxWidth: '75%', whiteSpace: 'pre-wrap' }}>{t.a}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{busy && <div style={{ color: '#6b7280', fontSize: 13 }}>Secretária digitando…</div>}
|
|
<div ref={endRef} />
|
|
</div>
|
|
<div style={{ display: 'flex', gap: 8, padding: 10, borderTop: '1px solid #e5e7eb' }}>
|
|
<input value={text} onChange={(e) => setText(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && send()}
|
|
placeholder="Pergunte algo…" style={{ flex: 1, padding: '8px 12px', borderRadius: 8, border: '1px solid #d1d5db' }} />
|
|
<button onClick={send} disabled={busy} style={{ background: '#2563eb', color: '#fff', border: 'none', borderRadius: 8, padding: '8px 16px', cursor: 'pointer', opacity: busy ? 0.6 : 1 }}>Perguntar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|