File size: 4,727 Bytes
20ec4ad
 
8a0e39e
20ec4ad
8a0e39e
20ec4ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a0e39e
 
 
 
 
 
 
 
20ec4ad
 
 
 
 
 
 
 
 
 
8a0e39e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20ec4ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/* src/components/panels/APIKeysConfig.tsx */
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);

  // Verifica se deve mostrar o aviso de quota
  useEffect(() => {
    // Busca no localStorage se houve um erro de quota recentemente (armazenado pelo ChatInterface)
    const hasQuotaError = localStorage.getItem("openai_quota_error") === "true";
    setShowQuotaWarning(hasQuotaError);
  }, []);

  const testConnections = () => {
    toast.info("Testando conexões...");
    // Aqui poderíamos implementar um teste real de conexão
    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>
  );
}