Merge pull request #6 from AutoMaker-Org/feature/auth-setup-settings-menu

Added Authentication Status Settings to API Keys Tab in Settings
This commit is contained in:
Web Dev Cody
2025-12-10 14:40:42 -05:00
committed by GitHub
5 changed files with 231 additions and 461 deletions

View File

@@ -6,3 +6,8 @@ agents-context/
# Attached images - uploaded by users as feature context
images/
# Launch script - local development script
launch.sh
.cursor

View File

@@ -1,453 +0,0 @@
---
name: Codex CLI OpenAI Model Support
overview: Extend the model support system to integrate OpenAI Codex CLI, enabling users to use OpenAI models (GPT-4o, o3, etc.) alongside existing Claude models. This includes CLI detection, model provider abstraction, execution wrapper, and UI updates.
todos:
- id: model-provider-abstraction
content: Create model provider abstraction layer with base interface and Claude/Codex implementations
status: pending
- id: codex-cli-detector
content: Implement Codex CLI detector service to check installation status and version
status: pending
- id: codex-executor
content: Create Codex CLI execution wrapper that spawns subprocess and parses JSON output
status: pending
- id: codex-config-manager
content: Implement Codex TOML configuration manager for model provider setup
status: pending
- id: model-registry
content: Create centralized model registry with provider mappings and metadata
status: pending
- id: update-feature-executor
content: Refactor feature-executor.js to use model provider abstraction instead of direct SDK calls
status: pending
- id: update-agent-service
content: Update agent-service.js to support configurable model selection via provider abstraction
status: pending
- id: message-converter
content: Create message format converter to translate Codex JSONL output to Claude SDK format
status: pending
- id: update-ui-types
content: Extend TypeScript types in app-store.ts to include OpenAI models and provider metadata
status: pending
- id: update-board-view
content: Expand model selection dropdown in board-view.tsx to include OpenAI models with provider grouping
status: pending
- id: update-settings-view
content: Add OpenAI API key input, Codex CLI status check, and test connection button to settings-view.tsx
status: pending
- id: openai-test-api
content: Create OpenAI API test endpoint at app/src/app/api/openai/test/route.ts
status: pending
- id: ipc-handlers
content: Add IPC handlers in main.js for model management (checkCodexCli, getAvailableModels, testOpenAI)
status: pending
- id: preload-api
content: Update preload.js and electron.d.ts to expose new IPC methods to renderer process
status: pending
- id: env-manager
content: Create environment variable manager for centralized API key and config handling
status: pending
- id: error-handling
content: Implement provider fallback logic and user-friendly error messages for missing CLI/API keys
status: pending
---
# Codex CLI OpenAI Model Support Implementation Plan
## Overview
Extend Automaker's model support to integrate OpenAI Codex CLI, allowing users to use the latest GPT-5.1 Codex models (`gpt-5.1-codex-max`, `gpt-5.1-codex`, `gpt-5.1-codex-mini`, `gpt-5.1`) alongside existing Claude models. Codex CLI defaults to `gpt-5.1-codex-max` and uses ChatGPT Enterprise authentication (no API key required). The implementation will follow the existing Claude CLI pattern but add abstraction for multiple model providers.
## Current Architecture Analysis
### Model Usage Points
1. **Feature Executor** (`app/electron/services/feature-executor.js`):
- Uses `MODEL_MAP` with hardcoded Claude models (haiku, sonnet, opus)
- Calls `@anthropic-ai/claude-agent-sdk` `query()` function
- Model selection via `getModelString(feature)` method
2. **Agent Service** (`app/electron/agent-service.js`):
- Hardcoded model: `"claude-opus-4-5-20251101"`
- Uses Claude Agent SDK directly
3. **API Route** (`app/src/app/api/chat/route.ts`):
- Hardcoded model: `"claude-opus-4-5-20251101"`
- Uses Claude Agent SDK
4. **Project Analyzer** (`app/electron/services/project-analyzer.js`):
- Hardcoded model: `"claude-sonnet-4-20250514"`
5. **UI Components**:
- `board-view.tsx`: Model dropdown (haiku/sonnet/opus)
- `app-store.ts`: `AgentModel` type limited to Claude models
### Authentication
- Claude: Uses `CLAUDE_CODE_OAUTH_TOKEN` environment variable
- Codex: Uses `OPENAI_API_KEY` environment variable (per Codex docs)
## Implementation Strategy
### Phase 1: Model Provider Abstraction Layer
#### 1.1 Create Model Provider Interface
**File**: `app/electron/services/model-provider.js`
- Abstract base class/interface for model providers
- Methods: `executeQuery()`, `detectInstallation()`, `getAvailableModels()`, `validateConfig()`
- Implementations:
- `ClaudeProvider` (wraps existing SDK usage)
- `CodexProvider` (new, wraps Codex CLI execution)
#### 1.2 Create Codex CLI Detector
**File**: `app/electron/services/codex-cli-detector.js`
- Similar to `claude-cli-detector.js`
- Check for `codex` command in PATH
- Check for npm global installation: `npm list -g @openai/codex`
- Check for Homebrew installation on macOS
- Return: `{ installed: boolean, path: string, version: string, method: 'cli'|'npm'|'brew'|'none' }`
#### 1.3 Create Codex Provider Implementation
**File**: `app/electron/services/codex-provider.js`
- Extends model provider interface
- Executes Codex CLI via `child_process.spawn()` or `execSync()`
- Handles JSON output parsing (`codex exec --json`)
- Manages TOML configuration file creation/updates
- Supports latest GPT-5.1 Codex models:
- `gpt-5.1-codex-max` (default, latest flagship for deep and fast reasoning)
- `gpt-5.1-codex` (optimized for codex)
- `gpt-5.1-codex-mini` (cheaper, faster, less capable)
- `gpt-5.1` (broad world knowledge with strong general reasoning)
- Uses ChatGPT Enterprise authentication (no API key required for these models)
- Note: Legacy models (GPT-4o, o3, o1, etc.) are not supported - Codex CLI focuses on GPT-5.1 Codex family only
### Phase 2: Model Configuration System
#### 2.1 Extended Model Registry
**File**: `app/electron/services/model-registry.js`
- Centralized model configuration
- Model definitions with provider mapping:
```javascript
{
id: "claude-opus",
name: "Claude Opus 4.5",
provider: "claude",
modelString: "claude-opus-4-5-20251101",
...
},
{
id: "gpt-4o",
name: "GPT-4o",
provider: "codex",
modelString: "gpt-4o",
requiresApiKey: "OPENAI_API_KEY",
...
}
```
- Model categories: `claude`, `openai`, `azure`, `custom`
#### 2.2 Codex Configuration Manager
**File**: `app/electron/services/codex-config-manager.js`
- Manages Codex TOML config file (typically `~/.config/codex/config.toml` or project-specific)
- Creates/updates model provider configurations:
```toml
[model_providers.openai-chat-completions]
name = "OpenAI using Chat Completions"
base_url = "https://api.openai.com/v1"
env_key = "OPENAI_API_KEY"
wire_api = "chat"
[profiles.gpt4o]
model = "gpt-4o"
model_provider = "openai-chat-completions"
```
- Profile management for different use cases
- Validates configuration before execution
### Phase 3: Execution Integration
#### 3.1 Update Feature Executor
**File**: `app/electron/services/feature-executor.js`
- Replace direct SDK calls with model provider abstraction
- Update `getModelString()` to return model ID instead of string
- Add `getModelProvider(modelId)` method
- Modify `implementFeature()` to:
- Get provider for selected model
- Use provider's `executeQuery()` method
- Handle different response formats (SDK vs CLI JSON)
#### 3.2 Update Agent Service
**File**: `app/electron/agent-service.js`
- Replace hardcoded model with configurable model selection
- Use model provider abstraction
- Support model selection per session
#### 3.3 Update Project Analyzer
**File**: `app/electron/services/project-analyzer.js`
- Use model provider abstraction
- Make model configurable (currently hardcoded to sonnet)
#### 3.4 Update API Route
**File**: `app/src/app/api/chat/route.ts`
- Support model selection from request
- Use model provider abstraction (if running in Electron context)
- Fallback to Claude SDK for web-only usage
### Phase 4: Codex CLI Execution Wrapper
#### 4.1 Codex Executor
**File**: `app/electron/services/codex-executor.js`
- Wraps `codex exec` command execution
- Handles subprocess spawning with proper environment variables
- Parses JSON output (JSONL format from `--json` flag)
- Converts Codex output format to match Claude SDK message format
- Handles streaming responses
- Error handling and timeout management
#### 4.2 Message Format Conversion
**File**: `app/electron/services/message-converter.js`
- Converts Codex JSONL output to Claude SDK message format
- Maps Codex events:
- `thread.started` → session initialization
- `item.completed` (reasoning) → thinking output
- `item.completed` (command_execution) → tool use
- `item.completed` (agent_message) → assistant message
- Maintains compatibility with existing UI components
### Phase 5: UI Updates
#### 5.1 Update Type Definitions
**File**: `app/src/store/app-store.ts`
- Extend `AgentModel` type to include OpenAI models:
```typescript
export type AgentModel =
| "opus" | "sonnet" | "haiku" // Claude
| "gpt-4o" | "gpt-4o-mini" | "gpt-3.5-turbo" | "o3" | "o1"; // OpenAI
```
- Add `modelProvider` field to `Feature` interface
- Add provider metadata to model selection
#### 5.2 Update Board View
**File**: `app/src/components/views/board-view.tsx`
- Expand model dropdown to include OpenAI models
- Group models by provider (Claude / OpenAI)
- Show provider badges/icons
- Display model availability based on CLI detection
- Add tooltips showing model capabilities
#### 5.3 Update Settings View
**File**: `app/src/components/views/settings-view.tsx`
- Add OpenAI API key input field (similar to Anthropic key)
- Add Codex CLI status check (similar to Claude CLI check)
- Show installation instructions if Codex CLI not detected
- Add test connection button for OpenAI API
- Display detected Codex CLI version/path
#### 5.4 Create API Test Route
**File**: `app/src/app/api/openai/test/route.ts`
- Similar to `app/src/app/api/claude/test/route.ts`
- Test OpenAI API connection
- Validate API key format
- Return connection status
### Phase 6: Configuration & Environment
#### 6.1 Environment Variable Management
**File**: `app/electron/services/env-manager.js`
- Centralized environment variable handling
- Loads from `.env` file and system environment
- Validates required variables per provider
- Provides fallback mechanisms
#### 6.2 IPC Handlers for Model Management
**File**: `app/electron/main.js`
- Add IPC handlers:
- `model:checkCodexCli` - Check Codex CLI installation
- `model:getAvailableModels` - List available models per provider
- `model:testOpenAI` - Test OpenAI API connection
- `model:updateCodexConfig` - Update Codex TOML config
#### 6.3 Preload API Updates
**File**: `app/electron/preload.js`
- Expose new IPC methods to renderer
- Add TypeScript definitions in `app/src/types/electron.d.ts`
### Phase 7: Error Handling & Fallbacks
#### 7.1 Provider Fallback Logic
- If Codex CLI not available, fallback to Claude
- If OpenAI API key missing, show clear error messages
- Graceful degradation when provider unavailable
#### 7.2 Error Messages
- User-friendly error messages for missing CLI
- Installation instructions per platform
- API key validation errors
- Model availability warnings
## File Structure Summary
### New Files
```
app/electron/services/
├── model-provider.js # Abstract provider interface
├── claude-provider.js # Claude SDK wrapper
├── codex-provider.js # Codex CLI wrapper
├── codex-cli-detector.js # Codex CLI detection
├── codex-executor.js # Codex CLI execution wrapper
├── codex-config-manager.js # TOML config management
├── model-registry.js # Centralized model definitions
├── message-converter.js # Format conversion utilities
└── env-manager.js # Environment variable management
app/src/app/api/openai/
└── test/route.ts # OpenAI API test endpoint
```
### Modified Files
```
app/electron/services/
├── feature-executor.js # Use model provider abstraction
├── agent-service.js # Support multiple providers
└── project-analyzer.js # Configurable model selection
app/electron/
├── main.js # Add IPC handlers
└── preload.js # Expose new APIs
app/src/
├── store/app-store.ts # Extended model types
├── components/views/
│ ├── board-view.tsx # Expanded model selection UI
│ └── settings-view.tsx # OpenAI API key & Codex CLI status
└── types/electron.d.ts # Updated IPC type definitions
```
## Implementation Details
### Codex CLI Execution Pattern
```javascript
// Example execution flow
const codexExecutor = require('./codex-executor');
const result = await codexExecutor.execute({
prompt: "Implement feature X",
model: "gpt-4o",
cwd: projectPath,
systemPrompt: "...",
maxTurns: 20,
allowedTools: ["Read", "Write", "Edit", "Bash"],
env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY }
});
```
### Model Provider Interface
```javascript
class ModelProvider {
async executeQuery(options) {
// Returns async generator of messages
}
async detectInstallation() {
// Returns installation status
}
getAvailableModels() {
// Returns list of supported models
}
validateConfig() {
// Validates provider configuration
}
}
```
### Configuration File Location
- User config: `~/.config/codex/config.toml` (or platform equivalent)
- Project config: `.codex/config.toml` (optional, project-specific)
- Fallback: In-memory config passed via CLI args
## Testing Considerations
1. **CLI Detection**: Test on macOS, Linux, Windows
2. **Model Execution**: Test with different OpenAI models
3. **Error Handling**: Test missing CLI, invalid API keys, network errors
4. **Format Conversion**: Verify message format compatibility
5. **Concurrent Execution**: Test multiple features with different providers
6. **Fallback Logic**: Test provider fallback scenarios
## Documentation Updates
1. Update README with Codex CLI installation instructions:
- `npm install -g @openai/codex@latest` or `brew install codex`
- ChatGPT Enterprise authentication (no API key needed)
- API-based authentication for older models
2. Add model selection guide:
- GPT-5.1 Codex Max (default, best for coding)
- o3/o4-mini with reasoning efforts
- GPT-5.1/GPT-5 with verbosity control
3. Document reasoning effort and verbosity settings
4. Add troubleshooting section for common issues
5. Document model list discovery via MCP interface
## Migration Path
1. Implement provider abstraction alongside existing code
2. Add Codex support without breaking existing Claude functionality
3. Gradually migrate services to use abstraction layer
4. Maintain backward compatibility during transition
5. Remove hardcoded models after full migration

34
app/package-lock.json generated
View File

@@ -24,6 +24,7 @@
"@tanstack/react-query": "^5.90.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"lucide-react": "^0.556.0",
"next": "16.0.7",
"react": "19.2.0",
@@ -4687,6 +4688,19 @@
"electron-builder-squirrel-windows": "26.0.12"
}
},
"node_modules/app-builder-lib/node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/app-builder-lib/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -6308,10 +6322,9 @@
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -6336,6 +6349,19 @@
"url": "https://dotenvx.com"
}
},
"node_modules/dotenv-expand/node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

View File

@@ -31,6 +31,7 @@
"@tanstack/react-query": "^5.90.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"lucide-react": "^0.556.0",
"next": "16.0.7",
"react": "19.2.0",

View File

@@ -40,7 +40,7 @@ import {
TestTube,
Settings2,
RefreshCw,
RotateCcw,
Info,
} from "lucide-react";
import { getElectronAPI } from "@/lib/electron";
import { Checkbox } from "@/components/ui/checkbox";
@@ -52,6 +52,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useSetupStore } from "@/store/setup-store";
// Navigation items for the side panel
const NAV_ITEMS = [
@@ -143,10 +144,15 @@ export function SettingsView() {
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [isCheckingClaudeCli, setIsCheckingClaudeCli] = useState(false);
const [isCheckingCodexCli, setIsCheckingCodexCli] = useState(false);
const [editingShortcut, setEditingShortcut] = useState<string | null>(null);
const [shortcutValue, setShortcutValue] = useState("");
const [shortcutError, setShortcutError] = useState<string | null>(null);
const [apiKeyStatus, setApiKeyStatus] = useState<{
hasAnthropicKey: boolean;
hasOpenAIKey: boolean;
hasGoogleKey: boolean;
} | null>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
// Get authentication status from setup store
const { claudeAuthStatus, codexAuthStatus } = useSetupStore();
useEffect(() => {
setAnthropicKey(apiKeys.anthropic);
@@ -173,6 +179,21 @@ export function SettingsView() {
console.error("Failed to check Codex CLI status:", error);
}
}
// Check API key status from environment
if (api?.setup?.getApiKeys) {
try {
const status = await api.setup.getApiKeys();
if (status.success) {
setApiKeyStatus({
hasAnthropicKey: status.hasAnthropicKey,
hasOpenAIKey: status.hasOpenAIKey,
hasGoogleKey: status.hasGoogleKey,
});
}
} catch (error) {
console.error("Failed to check API key status:", error);
}
}
};
checkCliStatus();
}, []);
@@ -747,6 +768,176 @@ export function SettingsView() {
)}
</div>
{/* Authentication Status Display */}
<div className="space-y-4 pt-4 border-t border-border">
<div className="flex items-center gap-2 mb-3">
<Info className="w-4 h-4 text-brand-500" />
<Label className="text-foreground font-semibold">
Current Authentication Configuration
</Label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Claude Authentication Status */}
<div className="p-3 rounded-lg bg-card border border-border">
<div className="flex items-center gap-2 mb-1.5">
<Terminal className="w-4 h-4 text-brand-500" />
<span className="text-sm font-medium text-foreground">
Claude (Anthropic)
</span>
</div>
<div className="space-y-1.5 text-xs min-h-[3rem]">
{claudeAuthStatus?.authenticated ? (
<>
<div className="flex items-center gap-2">
<CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
<span className="text-muted-foreground">
Method:{" "}
<span className="font-mono text-foreground">
{claudeAuthStatus.method === "oauth"
? "OAuth Token"
: claudeAuthStatus.method === "api_key"
? "API Key"
: "Unknown"}
</span>
</span>
</div>
{claudeAuthStatus.oauthTokenValid && (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>OAuth token configured</span>
</div>
)}
{claudeAuthStatus.apiKeyValid && (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>API key configured</span>
</div>
)}
{apiKeyStatus?.hasAnthropicKey && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Environment variable detected</span>
</div>
)}
{apiKeys.anthropic && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Manual API key in settings</span>
</div>
)}
</>
) : apiKeyStatus?.hasAnthropicKey ? (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Using environment variable (ANTHROPIC_API_KEY)</span>
</div>
) : apiKeys.anthropic ? (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Using manual API key from settings</span>
</div>
) : (
<div className="flex items-center gap-1.5 text-muted-foreground py-0.5">
<AlertCircle className="w-2.5 h-2.5 shrink-0" />
<span className="text-xs">Not Setup</span>
</div>
)}
</div>
</div>
{/* Codex/OpenAI Authentication Status */}
<div className="p-3 rounded-lg bg-card border border-border">
<div className="flex items-center gap-2 mb-1.5">
<Atom className="w-4 h-4 text-green-500" />
<span className="text-sm font-medium text-foreground">
Codex (OpenAI)
</span>
</div>
<div className="space-y-1.5 text-xs min-h-[3rem]">
{codexAuthStatus?.authenticated ? (
<>
<div className="flex items-center gap-2">
<CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
<span className="text-muted-foreground">
Method:{" "}
<span className="font-mono text-foreground">
{codexAuthStatus.method === "api_key"
? "API Key (Auth File)"
: codexAuthStatus.method === "env"
? "API Key (Environment)"
: "Unknown"}
</span>
</span>
</div>
{codexAuthStatus.apiKeyValid && (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>API key configured</span>
</div>
)}
{apiKeyStatus?.hasOpenAIKey && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Environment variable detected</span>
</div>
)}
{apiKeys.openai && (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Manual API key in settings</span>
</div>
)}
</>
) : apiKeyStatus?.hasOpenAIKey ? (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Using environment variable (OPENAI_API_KEY)</span>
</div>
) : apiKeys.openai ? (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Using manual API key from settings</span>
</div>
) : (
<div className="flex items-center gap-1.5 text-muted-foreground py-0.5">
<AlertCircle className="w-2.5 h-2.5 shrink-0" />
<span className="text-xs">Not Setup</span>
</div>
)}
</div>
</div>
{/* Google/Gemini Authentication Status */}
<div className="p-3 rounded-lg bg-card border border-border">
<div className="flex items-center gap-2 mb-1.5">
<Sparkles className="w-4 h-4 text-purple-500" />
<span className="text-sm font-medium text-foreground">
Gemini (Google)
</span>
</div>
<div className="space-y-1.5 text-xs min-h-[3rem]">
{apiKeyStatus?.hasGoogleKey ? (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Using environment variable (GOOGLE_API_KEY)</span>
</div>
) : apiKeys.google ? (
<div className="flex items-center gap-2 text-blue-400">
<Info className="w-3 h-3 shrink-0" />
<span>Using manual API key from settings</span>
</div>
) : (
<div className="flex items-center gap-1.5 text-muted-foreground py-0.5">
<AlertCircle className="w-2.5 h-2.5 shrink-0" />
<span className="text-xs">Not Setup</span>
</div>
)}
</div>
</div>
</div>
</div>
{/* Security Notice */}
<div className="flex items-start gap-3 p-4 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
<AlertCircle className="w-5 h-5 text-yellow-500 mt-0.5 shrink-0" />