|
|
|
import { useLocalStorage } from "react-use"; |
|
import { ChangeEvent, useState, useEffect } from "react"; |
|
import { toast } from "react-toastify"; |
|
import { FiAlertTriangle } from "react-icons/fi"; |
|
|
|
type RowProps = { |
|
label: string; |
|
link: string; |
|
storageKey: string; |
|
placeholder: string; |
|
validator?: (key: string) => boolean; |
|
}; |
|
|
|
function InputRow({ label, link, storageKey, placeholder, validator }: RowProps) { |
|
const [value, setValue] = useLocalStorage<string>(storageKey, ""); |
|
const [error, setError] = useState<string | null>(null); |
|
|
|
const onChange = (e: ChangeEvent<HTMLInputElement>) => { |
|
const newValue = e.target.value.trim(); |
|
setValue(newValue); |
|
|
|
if (validator && newValue) { |
|
const isValid = validator(newValue); |
|
if (!isValid) { |
|
setError("Formato de chave inválido"); |
|
} else { |
|
setError(null); |
|
} |
|
} else { |
|
setError(null); |
|
} |
|
}; |
|
|
|
return ( |
|
<div className="mb-4"> |
|
<label className="block font-semibold mb-1"> |
|
{label}{" "} |
|
<a |
|
href={link} |
|
target="_blank" |
|
rel="noopener noreferrer" |
|
className="text-blue-600 underline" |
|
> |
|
obter chave |
|
</a> |
|
</label> |
|
|
|
<input |
|
className={`w-full border ${error ? "border-red-500" : "border-gray-400"} px-2 py-1 text-sm`} |
|
placeholder={placeholder} |
|
type="text" |
|
value={value ?? ""} |
|
onChange={onChange} |
|
/> |
|
{error && <p className="text-red-500 text-xs mt-1">{error}</p>} |
|
</div> |
|
); |
|
} |
|
|
|
export default function APIKeysConfig() { |
|
const validateOpenAI = (key: string) => key.startsWith("sk-"); |
|
const validateGemini = (key: string) => key.startsWith("AIza"); |
|
const validateHF = (key: string) => key.startsWith("hf_"); |
|
const [showQuotaWarning, setShowQuotaWarning] = useState(false); |
|
|
|
|
|
useEffect(() => { |
|
|
|
const hasQuotaError = localStorage.getItem("openai_quota_error") === "true"; |
|
setShowQuotaWarning(hasQuotaError); |
|
}, []); |
|
|
|
const testConnections = () => { |
|
toast.info("Testando conexões..."); |
|
|
|
setTimeout(() => toast.success("Chaves salvas com sucesso!"), 1000); |
|
}; |
|
|
|
return ( |
|
<div className="p-4"> |
|
<h1 className="text-2xl font-bold mb-6">Configurar chaves de API</h1> |
|
|
|
{showQuotaWarning && ( |
|
<div className="mb-4 p-3 bg-yellow-800/30 border-l-4 border-yellow-500 text-yellow-200 rounded"> |
|
<h3 className="font-bold flex items-center"> |
|
<FiAlertTriangle className="mr-2" /> Problema de quota detectado |
|
</h3> |
|
<p className="mt-2"> |
|
Sua chave da OpenAI (ChatGPT) atingiu o limite de quota. |
|
Você pode: |
|
</p> |
|
<ul className="list-disc pl-5 mt-2 space-y-1"> |
|
<li>Utilizar outra chave de API</li> |
|
<li>Aguardar até o próximo ciclo quando sua quota for renovada</li> |
|
<li>Atualizar seu plano na OpenAI para obter mais créditos</li> |
|
<li>Usar outros agentes que não dependem da API do ChatGPT</li> |
|
</ul> |
|
<button |
|
onClick={() => { |
|
localStorage.removeItem("openai_quota_error"); |
|
setShowQuotaWarning(false); |
|
}} |
|
className="mt-2 text-xs bg-yellow-800 hover:bg-yellow-700 px-2 py-1 rounded" |
|
> |
|
Entendi, esconder este aviso |
|
</button> |
|
</div> |
|
)} |
|
|
|
<InputRow |
|
label="OpenAI (ChatGPT)" |
|
link="https://platform.openai.com/account/api-keys" |
|
storageKey="chatgptKey" |
|
placeholder="sk-..." |
|
validator={validateOpenAI} |
|
/> |
|
|
|
<InputRow |
|
label="Google Gemini" |
|
link="https://makersuite.google.com/app/apikey" |
|
storageKey="geminiKey" |
|
placeholder="AIzaSy..." |
|
validator={validateGemini} |
|
/> |
|
|
|
<InputRow |
|
label="Hugging Face Token" |
|
link="https://huggingface.co/settings/tokens" |
|
storageKey="hfToken" |
|
placeholder="hf_..." |
|
validator={validateHF} |
|
/> |
|
|
|
<button |
|
onClick={testConnections} |
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" |
|
> |
|
Salvar e Testar Conexões |
|
</button> |
|
|
|
<p className="text-sm text-gray-600 mt-4"> |
|
As chaves são salvas apenas no <i>localStorage</i> do seu navegador e |
|
enviadas ao servidor somente quando você usa o respectivo modelo. |
|
</p> |
|
</div> |
|
); |
|
} |
|
|