Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ui/desktop/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
* @vitest-environment jsdom
*/
import React from 'react';
import { screen, render, waitFor } from '@testing-library/react';
import { screen, render as rtlRender, waitFor } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { AppInner } from './App';
import { LocalizationProvider } from './contexts/LocalizationContext';

const render = (ui: React.ReactElement) => rtlRender(<LocalizationProvider>{ui}</LocalizationProvider>);

// Set up globals for jsdom
Object.defineProperty(window, 'location', {
Expand Down
17 changes: 10 additions & 7 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import 'react-toastify/dist/ReactToastify.css';
import { useConfig } from './components/ConfigContext';
import { ModelAndProviderProvider } from './components/ModelAndProviderContext';
import { ThemeProvider } from './contexts/ThemeContext';
import { LocalizationProvider } from './contexts/LocalizationContext';
import PermissionSettingsView from './components/settings/permission/PermissionSetting';

import ExtensionsView, { ExtensionsViewOptions } from './components/extensions/ExtensionsView';
Expand Down Expand Up @@ -702,13 +703,15 @@ export function AppInner() {
export default function App() {
return (
<ThemeProvider>
<ModelAndProviderProvider>
<HashRouter>
<AppInner />
</HashRouter>
<AnnouncementModal />
<TelemetryOptOutModal controlled={false} />
</ModelAndProviderProvider>
<LocalizationProvider>
<ModelAndProviderProvider>
<HashRouter>
<AppInner />
</HashRouter>
<AnnouncementModal />
<TelemetryOptOutModal controlled={false} />
</ModelAndProviderProvider>
</LocalizationProvider>
</ThemeProvider>
);
}
46 changes: 28 additions & 18 deletions ui/desktop/src/components/ApiKeyTester.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { detectProvider } from '../api';
import { Key } from './icons/Key';
import { ArrowRight } from './icons/ArrowRight';
import { Button } from './ui/button';
import { useLocalization } from '../contexts/LocalizationContext';

interface ApiKeyTesterProps {
onSuccess: (provider: string, model: string, apiKey: string) => void;
Expand All @@ -16,6 +17,7 @@ interface DetectionResult {
}

export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTesterProps) {
const { t } = useLocalization();
const [apiKey, setApiKey] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [result, setResult] = useState<DetectionResult | null>(null);
Expand Down Expand Up @@ -69,7 +71,7 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
{/* Recommended pill */}
<div className="absolute -top-2 -right-2 sm:-top-3 sm:-right-3 z-20">
<span className="inline-block px-2 py-1 text-xs font-medium bg-blue-600 text-white rounded-full">
Recommended if you have API access already
{t('apiKeyTester.recommended')}
</span>
</div>

Expand All @@ -78,10 +80,10 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
<Key className="w-4 h-4 text-text-primary flex-shrink-0" />
<div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
<h3 className="font-medium text-text-primary text-sm sm:text-base">
Quick Setup with API Key
{t('apiKeyTester.title')}
</h3>
<span className="text-text-secondary text-xs sm:text-sm">
Auto-detect your provider
{t('apiKeyTester.subtitle')}
</span>
</div>
</div>
Expand All @@ -92,8 +94,8 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
ref={inputRef}
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="Enter your API key (OpenAI, Anthropic, Google, etc.)"
onChange={(e) => setApiKey(e.target.value)}
placeholder={t('apiKeyTester.placeholder')}
className="flex-1 px-3 py-2 border rounded-lg bg-background-primary text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
disabled={isLoading}
onKeyDown={(e) => {
Expand All @@ -120,7 +122,7 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
{isLoading && (
<div className="flex items-center gap-2 px-3 py-2 bg-background-secondary rounded text-sm text-text-secondary">
<div className="w-3 h-3 border-2 border-current border-t-transparent rounded-full animate-spin"></div>
<span>Detecting provider and validating key...</span>
<span>{t('apiKeyTester.detecting')}</span>
</div>
)}

Expand All @@ -129,9 +131,14 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
<div className="flex items-center gap-2 text-sm p-3 rounded-lg bg-green-50 text-green-800 border border-green-200 dark:bg-green-900/20 dark:text-green-200 dark:border-green-800">
<span>✅</span>
<div className="flex-1">
<div className="font-medium">Detected {result.provider}</div>
<div className="font-medium">
{t('apiKeyTester.detectedProvider', { provider: result.provider })}
</div>
<div className="text-green-600 dark:text-green-400 text-xs mt-1">
Model: {result.model} ({result.totalModels} models available)
{t('apiKeyTester.modelSummary', {
model: result.model,
count: result.totalModels,
})}
</div>
</div>
</div>
Expand All @@ -143,33 +150,36 @@ export default function ApiKeyTester({ onSuccess, onStartTesting }: ApiKeyTester
<div className="flex items-center gap-2 text-sm p-3 rounded-lg bg-red-50 text-red-800 border border-red-200 dark:bg-red-900/20 dark:text-red-200 dark:border-red-800">
<span>❌</span>
<div className="flex-1">
<div className="font-medium">Provider Detection Failed</div>
<div className="font-medium">{t('apiKeyTester.detectionFailedTitle')}</div>
<div className="text-red-600 dark:text-red-400 text-xs mt-1">
Could not detect provider from API key
{t('apiKeyTester.detectionFailedDescription')}
</div>
</div>
</div>
<div className="ml-6 space-y-1">
<p className="text-xs font-medium text-text-secondary">Suggestions:</p>
<p className="text-xs font-medium text-text-secondary">
{t('apiKeyTester.suggestions')}
</p>
<ul className="text-xs text-text-secondary space-y-1">
<li className="flex items-start gap-1">
<span className="text-blue-500 mt-0.5">•</span>
<span>
Make sure you are using a valid API key from OpenAI, Anthropic, Google, Groq,
or xAI
</span>
<span>{t('apiKeyTester.suggestionValidKey')}</span>
</li>
<li className="flex items-start gap-1">
<span className="text-blue-500 mt-0.5">•</span>
<span>Check that the key is complete and not truncated</span>
<span>{t('apiKeyTester.suggestionCompleteKey')}</span>
</li>
<li className="flex items-start gap-1">
<span className="text-blue-500 mt-0.5">•</span>
<span>Verify your API key is active and has sufficient credits</span>
<span>{t('apiKeyTester.suggestionCredits')}</span>
</li>
<li className="flex items-start gap-1">
<span className="text-blue-500 mt-0.5">•</span>
<span>For local Ollama setup, use the "Other Providers" section below</span>
<span>
{t('apiKeyTester.suggestionLocalOllama', {
section: t('providers.onboardingTitle'),
})}
</span>
</li>
</ul>
</div>
Expand Down
Loading
Loading