Skip to content

Commit 03f784c

Browse files
committed
Toaster update premium not yet implemented in EasyAI
1 parent 5f1cd8c commit 03f784c

8 files changed

Lines changed: 212 additions & 18 deletions

File tree

src/components/LicenseModal.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,25 @@ export function LicenseModal({ open, onClose, showToast }: LicenseModalProps) {
353353
className="license-name-input"
354354
style={{ width: '95%', boxSizing: 'border-box', padding: '4px', borderRadius: '4px', border: '1px solid var(--border-color, #ccc)' }}
355355
value={agent}
356-
onChange={(e) => setAgent(e.target.value)}
356+
onChange={(e) => {
357+
const selected = e.target.value;
358+
if (selected === 'Premium' || selected === 'PremiumPlus') {
359+
if (showToast) {
360+
showToast(t('about.premium_not_implemented'), 'warning');
361+
}
362+
// Revert the select back to current agent
363+
e.target.value = agent;
364+
return;
365+
}
366+
setAgent(selected);
367+
}}
357368
>
358369
<option value="Ollama">Ollama</option>
359370
<option value="Gemini">Gemini</option>
360371
<option value="Bedrock">Bedrock</option>
361372
<option value="Claude">Claude</option>
373+
<option value="Premium">Premium</option>
374+
<option value="PremiumPlus">PremiumPlus</option>
362375
</select>
363376
</div>
364377
<div>
@@ -372,11 +385,11 @@ export function LicenseModal({ open, onClose, showToast }: LicenseModalProps) {
372385
padding: '4px',
373386
borderRadius: '4px',
374387
border: '1px solid var(--border-color, #ccc)',
375-
opacity: agent !== 'Ollama' ? 0.6 : 1,
376-
cursor: agent !== 'Ollama' ? 'not-allowed' : 'text'
388+
opacity: agent === 'Gemini' ? 0.6 : 1,
389+
cursor: agent === 'Gemini' ? 'not-allowed' : 'text'
377390
}}
378391
placeholder={t('about.api_host_placeholder')}
379-
readOnly={agent !== 'Ollama'}
392+
readOnly={agent === 'Gemini'}
380393
value={host}
381394
onChange={(e) => setHost(e.target.value)}
382395
/>
@@ -408,7 +421,7 @@ export function LicenseModal({ open, onClose, showToast }: LicenseModalProps) {
408421
className="btn secondary"
409422
style={{ marginTop: '10px', alignSelf: 'flex-start', padding: '6px 12px' }}
410423
>
411-
Save Config
424+
{t('about.save_config')}
412425
</button>
413426
</div>
414427
</div>

src/components/Toast.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
display: flex;
1111
align-items: center;
1212
gap: 12px;
13-
z-index: 10000;
13+
z-index: 1100000;
1414
animation: slideIn 0.3s ease-out;
1515
cursor: pointer;
1616
font-size: 14px;
@@ -96,7 +96,7 @@
9696
position: fixed;
9797
top: 80px;
9898
right: 20px;
99-
z-index: 10000;
99+
z-index: 1100000;
100100
display: flex;
101101
flex-direction: column;
102102
gap: 10px;

src/components/easyai/aiService.ts

Lines changed: 177 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,177 @@ async function callOllama(
101101
return data.message?.content ?? '';
102102
}
103103

104+
/**
105+
* Send a prompt to the Google Gemini API.
106+
* Uses the generativelanguage.googleapis.com REST endpoint.
107+
* Config: apiKey = Google API key, model = e.g. gemini-2.0-flash
108+
* Host is ignored (uses Google's endpoint directly).
109+
*/
110+
async function callGemini(
111+
config: EasyAIConfig,
112+
systemPrompt: string,
113+
userPrompt: string,
114+
): Promise<string> {
115+
if (!config.apiKey) {
116+
throw new Error('Gemini requires an API key. Set it in Settings > About > EasyAI API Hosting.');
117+
}
118+
119+
const model = config.model || 'gemini-2.0-flash';
120+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${config.apiKey}`;
121+
122+
const body = {
123+
system_instruction: {
124+
parts: [{ text: systemPrompt }],
125+
},
126+
contents: [
127+
{
128+
role: 'user',
129+
parts: [{ text: userPrompt }],
130+
},
131+
],
132+
generationConfig: {
133+
temperature: 0.7,
134+
maxOutputTokens: 8192,
135+
},
136+
};
137+
138+
const res = await fetch(url, {
139+
method: 'POST',
140+
headers: { 'Content-Type': 'application/json' },
141+
body: JSON.stringify(body),
142+
});
143+
144+
if (!res.ok) {
145+
const text = await res.text();
146+
throw new Error(`Gemini returned ${res.status}: ${text}`);
147+
}
148+
149+
const data = await res.json();
150+
return data.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
151+
}
152+
153+
/**
154+
* Send a prompt to the Anthropic Claude API.
155+
* Uses the api.anthropic.com Messages endpoint.
156+
* Config: apiKey = Anthropic API key, model = e.g. claude-sonnet-4-20250514
157+
* Host can override the base URL for proxied setups.
158+
*/
159+
async function callClaude(
160+
config: EasyAIConfig,
161+
systemPrompt: string,
162+
userPrompt: string,
163+
): Promise<string> {
164+
if (!config.apiKey) {
165+
throw new Error('Claude requires an API key. Set it in Settings > About > EasyAI API Hosting.');
166+
}
167+
168+
const baseUrl = config.host && !config.host.includes('localhost')
169+
? config.host.replace(/\/+$/, '')
170+
: 'https://api.anthropic.com';
171+
const url = `${baseUrl}/v1/messages`;
172+
const model = config.model || 'claude-sonnet-4-20250514';
173+
174+
const body = {
175+
model,
176+
max_tokens: 8192,
177+
system: systemPrompt,
178+
messages: [
179+
{ role: 'user', content: userPrompt },
180+
],
181+
};
182+
183+
const res = await fetch(url, {
184+
method: 'POST',
185+
headers: {
186+
'Content-Type': 'application/json',
187+
'x-api-key': config.apiKey,
188+
'anthropic-version': '2023-06-01',
189+
'anthropic-dangerous-direct-browser-access': 'true',
190+
},
191+
body: JSON.stringify(body),
192+
});
193+
194+
if (!res.ok) {
195+
const text = await res.text();
196+
throw new Error(`Claude returned ${res.status}: ${text}`);
197+
}
198+
199+
const data = await res.json();
200+
// Claude returns content as an array of blocks
201+
const blocks = data.content ?? [];
202+
return blocks
203+
.filter((b: any) => b.type === 'text')
204+
.map((b: any) => b.text)
205+
.join('');
206+
}
207+
208+
/**
209+
* Send a prompt to AWS Bedrock via the invoke-model REST endpoint.
210+
* Config: host = full Bedrock endpoint URL (e.g. https://bedrock-runtime.us-east-1.amazonaws.com),
211+
* model = model ID (e.g. anthropic.claude-3-haiku-20240307-v1:0),
212+
* apiKey = format "ACCESS_KEY_ID:SECRET_ACCESS_KEY" or a session token.
213+
*
214+
* NOTE: For browser-based usage, Bedrock typically requires a proxy/gateway
215+
* since direct AWS SigV4 signing from the browser is complex.
216+
* This implementation supports a proxy that accepts the Bedrock converse API format
217+
* and forwards to AWS with proper signing.
218+
*/
219+
async function callBedrock(
220+
config: EasyAIConfig,
221+
systemPrompt: string,
222+
userPrompt: string,
223+
): Promise<string> {
224+
if (!config.host) {
225+
throw new Error('Bedrock requires a host URL (e.g. your Bedrock proxy endpoint). Set it in Settings > About > EasyAI API Hosting.');
226+
}
227+
228+
const baseUrl = config.host.replace(/\/+$/, '');
229+
const model = config.model || 'anthropic.claude-3-haiku-20240307-v1:0';
230+
const url = `${baseUrl}/model/${encodeURIComponent(model)}/converse`;
231+
232+
const body = {
233+
system: [{ text: systemPrompt }],
234+
messages: [
235+
{
236+
role: 'user',
237+
content: [{ text: userPrompt }],
238+
},
239+
],
240+
inferenceConfig: {
241+
maxTokens: 8192,
242+
temperature: 0.7,
243+
},
244+
};
245+
246+
const headers: Record<string, string> = {
247+
'Content-Type': 'application/json',
248+
};
249+
250+
// If an API key is provided, pass it as Authorization header for proxy auth
251+
if (config.apiKey) {
252+
headers['Authorization'] = `Bearer ${config.apiKey}`;
253+
}
254+
255+
const res = await fetch(url, {
256+
method: 'POST',
257+
headers,
258+
body: JSON.stringify(body),
259+
});
260+
261+
if (!res.ok) {
262+
const text = await res.text();
263+
throw new Error(`Bedrock returned ${res.status}: ${text}`);
264+
}
265+
266+
const data = await res.json();
267+
// Bedrock Converse API response format
268+
const output = data.output?.message?.content ?? [];
269+
return output
270+
.filter((b: any) => b.text)
271+
.map((b: any) => b.text)
272+
.join('');
273+
}
274+
104275
/**
105276
* Main entry point — dispatches to the correct backend based on config.agent.
106277
*/
@@ -115,12 +286,12 @@ export async function queryEasyAI(
115286
switch (config.agent) {
116287
case 'Ollama':
117288
return callOllama(config, systemPrompt, userPrompt);
118-
119-
// Future agents can be added here:
120-
// case 'Gemini': return callGemini(config, systemPrompt, userPrompt);
121-
// case 'Claude': return callClaude(config, systemPrompt, userPrompt);
122-
// case 'Bedrock': return callBedrock(config, systemPrompt, userPrompt);
123-
289+
case 'Gemini':
290+
return callGemini(config, systemPrompt, userPrompt);
291+
case 'Claude':
292+
return callClaude(config, systemPrompt, userPrompt);
293+
case 'Bedrock':
294+
return callBedrock(config, systemPrompt, userPrompt);
124295
default:
125296
throw new Error(`Unsupported EasyAI agent: "${config.agent}". Configure a supported agent in Settings > About > EasyAI API Hosting.`);
126297
}

src/i18n/locales/de.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@
277277
"license_key_placeholder": "Geben Sie Ihren Lizenzschlüssel ein",
278278
"check_license": "Lizenz prüfen",
279279
"close": "Schließen",
280-
"invalid_license": "Ungültige Lizenz oder E-Mail"
280+
"invalid_license": "Ungültige Lizenz oder E-Mail",
281+
"save_config": "Konfiguration speichern",
282+
"premium_not_implemented": "Die Agenten \"Premium\" und \"PremiumPlus\" sind noch nicht verfügbar. Demnächst verfügbar!"
281283
},
282284
"images": {
283285
"image": "Bild",

src/i18n/locales/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@
277277
"license_key_placeholder": "Enter your License Key",
278278
"check_license": "Check License",
279279
"close": "Close",
280-
"invalid_license": "Invalid license or email"
280+
"invalid_license": "Invalid license or email",
281+
"save_config": "Save Config",
282+
"premium_not_implemented": "\"Premium\" and \"PremiumPlus\" agents are not yet implemented. Coming soon!"
281283
},
282284
"images": {
283285
"image": "Image",

src/i18n/locales/nl.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@
277277
"license_key_placeholder": "Voer uw licentiesleutel in",
278278
"check_license": "Controleer Licentie",
279279
"close": "Sluiten",
280-
"invalid_license": "Ongeldige licentie of e-mail"
280+
"invalid_license": "Ongeldige licentie of e-mail",
281+
"save_config": "Configuratie opslaan",
282+
"premium_not_implemented": "De agenten \"Premium\" en \"PremiumPlus\" zijn nog niet beschikbaar. Binnenkort beschikbaar!"
281283
},
282284
"images": {
283285
"image": "Afbeelding",

src/i18n/locales/pl.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@
277277
"license_key_placeholder": "Wprowadź swój klucz licencyjny",
278278
"check_license": "Sprawdź licencję",
279279
"close": "Zamknij",
280-
"invalid_license": "Nieprawidłowa licencja lub e-mail"
280+
"invalid_license": "Nieprawidłowa licencja lub e-mail",
281+
"save_config": "Zapisz konfigurację",
282+
"premium_not_implemented": "Agenci \"Premium\" i \"PremiumPlus\" nie są jeszcze dostępni. Wkrótce!"
281283
},
282284
"images": {
283285
"image": "Obraz",

src/i18n/locales/pt-br.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@
277277
"license_key_placeholder": "Insira sua Chave de Licença",
278278
"check_license": "Verificar Licença",
279279
"close": "Fechar",
280-
"invalid_license": "Licença ou e-mail inválido"
280+
"invalid_license": "Licença ou e-mail inválido",
281+
"save_config": "Salvar Configuração",
282+
"premium_not_implemented": "Os agentes \"Premium\" e \"PremiumPlus\" ainda não estão disponíveis. Em breve!"
281283
},
282284
"images": {
283285
"image": "Imagem",

0 commit comments

Comments
 (0)