fixed settings icon

This commit is contained in:
abhigyanpatwari 2026-01-12 21:19:53 +05:30
parent f038253733
commit f83d90b090
5 changed files with 135 additions and 44 deletions

View file

@ -22,7 +22,7 @@ interface HeaderProps {
}
export const Header = ({ onFocusNode }: HeaderProps) => {
const { projectName, graph, openChatPanel, isRightPanelOpen, rightPanelTab } = useAppState();
const { projectName, graph, openChatPanel, isRightPanelOpen, rightPanelTab, setSettingsPanelOpen } = useAppState();
const [searchQuery, setSearchQuery] = useState('');
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
@ -35,7 +35,7 @@ export const Header = ({ onFocusNode }: HeaderProps) => {
// Search results - filter nodes by name
const searchResults = useMemo(() => {
if (!graph || !searchQuery.trim()) return [];
const query = searchQuery.toLowerCase();
return graph.nodes
.filter(node => node.properties.name.toLowerCase().includes(query))
@ -73,7 +73,7 @@ export const Header = ({ onFocusNode }: HeaderProps) => {
// Handle keyboard navigation in results
const handleKeyDown = (e: React.KeyboardEvent) => {
if (!isSearchOpen || searchResults.length === 0) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
setSelectedIndex(i => Math.min(i + 1, searchResults.length - 1));
@ -140,7 +140,7 @@ export const Header = ({ onFocusNode }: HeaderProps) => {
K
</kbd>
</div>
{/* Search Results Dropdown */}
{isSearchOpen && searchQuery.trim() && (
<div className="absolute top-full left-0 right-0 mt-1 bg-surface border border-border-subtle rounded-lg shadow-xl overflow-hidden z-50">
@ -154,14 +154,13 @@ export const Header = ({ onFocusNode }: HeaderProps) => {
<button
key={node.id}
onClick={() => handleSelectNode(node)}
className={`w-full px-4 py-2.5 flex items-center gap-3 text-left transition-colors ${
index === selectedIndex
? 'bg-accent/20 text-text-primary'
: 'hover:bg-hover text-text-secondary'
}`}
className={`w-full px-4 py-2.5 flex items-center gap-3 text-left transition-colors ${index === selectedIndex
? 'bg-accent/20 text-text-primary'
: 'hover:bg-hover text-text-secondary'
}`}
>
{/* Node type indicator */}
<span
<span
className="w-2.5 h-2.5 rounded-full flex-shrink-0"
style={{ backgroundColor: NODE_TYPE_COLORS[node.label] || '#6b7280' }}
/>
@ -208,7 +207,11 @@ export const Header = ({ onFocusNode }: HeaderProps) => {
<EmbeddingStatus />
{/* Icon buttons */}
<button className="w-9 h-9 flex items-center justify-center rounded-md text-text-secondary hover:bg-hover hover:text-text-primary transition-colors">
<button
onClick={() => setSettingsPanelOpen(true)}
className="w-9 h-9 flex items-center justify-center rounded-md text-text-secondary hover:bg-hover hover:text-text-primary transition-colors"
title="AI Settings"
>
<Settings className="w-[18px] h-[18px]" />
</button>
<button className="w-9 h-9 flex items-center justify-center rounded-md text-text-secondary hover:bg-hover hover:text-text-primary transition-colors">
@ -221,7 +224,7 @@ export const Header = ({ onFocusNode }: HeaderProps) => {
className={`
flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-all
${isRightPanelOpen && rightPanelTab === 'chat'
? 'bg-accent text-white shadow-glow'
? 'bg-accent text-white shadow-glow'
: 'bg-gradient-to-r from-accent to-accent-dim text-white shadow-glow hover:shadow-lg hover:-translate-y-0.5'
}
`}

View file

@ -1,7 +1,7 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import {
Send, Sparkles, User,
PanelRightClose, Loader2, Settings, AlertTriangle
PanelRightClose, Loader2, AlertTriangle
} from 'lucide-react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
@ -43,7 +43,6 @@ export const RightPanel = () => {
agentError,
isAgentReady,
isAgentInitializing,
setSettingsPanelOpen,
sendChatMessage,
clearChat,
} = useAppState();
@ -290,8 +289,9 @@ export const RightPanel = () => {
};
const chatSuggestions = [
'Explain the project architecture',
'What does this project do?',
'Show me the entry point',
'Show me the most important files',
'Find all API handlers',
];
@ -332,13 +332,6 @@ export const RightPanel = () => {
<Loader2 className="w-3 h-3 animate-spin" /> Connecting
</span>
)}
<button
onClick={() => setSettingsPanelOpen(true)}
className="p-1.5 rounded-md text-text-muted hover:text-text-primary hover:bg-hover transition-colors"
title="AI Settings"
>
<Settings className="w-4 h-4" />
</button>
</div>
</div>

View file

@ -22,13 +22,13 @@ const fetchGeminiModels = async (apiKey: string): Promise<string[]> => {
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
// Filter for chat-capable models and extract model names
const models = (data.models || [])
.filter((model: any) => {
@ -49,7 +49,7 @@ const fetchGeminiModels = async (apiKey: string): Promise<string[]> => {
};
return score(a) - score(b) || a.localeCompare(b);
});
return models;
} catch (error) {
console.warn('Failed to fetch Gemini models:', error);
@ -61,7 +61,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
const [settings, setSettings] = useState<LLMSettings>(loadSettings);
const [showApiKey, setShowApiKey] = useState<Record<string, boolean>>({});
const [saveStatus, setSaveStatus] = useState<'idle' | 'saved' | 'error'>('idle');
// Gemini model fetching state
const [geminiModels, setGeminiModels] = useState<string[]>([]);
const [isLoadingModels, setIsLoadingModels] = useState(false);
@ -87,14 +87,14 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
setGeminiModels([]);
return;
}
setIsLoadingModels(true);
setModelFetchError(null);
const models = await fetchGeminiModels(apiKey);
setIsLoadingModels(false);
if (models.length > 0) {
setGeminiModels(models);
setModelFetchError(null);
@ -136,14 +136,14 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
if (!isOpen) return null;
const providers: LLMProvider[] = ['gemini', 'anthropic', 'azure-openai'];
const availableGeminiModels = geminiModels.length > 0
? geminiModels
const providers: LLMProvider[] = ['openai', 'gemini', 'anthropic', 'azure-openai'];
const availableGeminiModels = geminiModels.length > 0
? geminiModels
: getAvailableModels('gemini');
const currentGeminiModel = settings.gemini?.model ?? 'gemini-2.0-flash';
const isCustomModelSelected = !availableGeminiModels.includes(currentGeminiModel);
const availableAnthropicModels = getAvailableModels('anthropic');
const currentAnthropicModel = settings.anthropic?.model ?? 'claude-sonnet-4-20250514';
const isCustomAnthropicModelSelected = !availableAnthropicModels.includes(currentAnthropicModel);
@ -201,7 +201,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
w-8 h-8 rounded-lg flex items-center justify-center text-lg
${settings.activeProvider === provider ? 'bg-accent/20' : 'bg-surface'}
`}>
{provider === 'gemini' ? '💎' : provider === 'anthropic' ? '🧠' : '☁️'}
{provider === 'openai' ? '🤖' : provider === 'gemini' ? '💎' : provider === 'anthropic' ? '🧠' : '☁️'}
</div>
<span className="font-medium">{getProviderDisplayName(provider)}</span>
</button>
@ -209,6 +209,84 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
</div>
</div>
{/* OpenAI Settings */}
{settings.activeProvider === 'openai' && (
<div className="space-y-4 animate-fade-in">
<div className="space-y-2">
<label className="flex items-center gap-2 text-sm font-medium text-text-secondary">
<Key className="w-4 h-4" />
API Key
</label>
<div className="relative">
<input
type={showApiKey['openai'] ? 'text' : 'password'}
value={settings.openai?.apiKey ?? ''}
onChange={e => setSettings(prev => ({
...prev,
openai: { ...prev.openai!, apiKey: e.target.value }
}))}
placeholder="Enter your OpenAI API key"
className="w-full px-4 py-3 pr-12 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all"
/>
<button
type="button"
onClick={() => toggleApiKeyVisibility('openai')}
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-text-muted hover:text-text-primary transition-colors"
>
{showApiKey['openai'] ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
<p className="text-xs text-text-muted">
Get your API key from{' '}
<a
href="https://platform.openai.com/api-keys"
target="_blank"
rel="noopener noreferrer"
className="text-accent hover:underline"
>
OpenAI Platform
</a>
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-text-secondary">Model</label>
<select
value={settings.openai?.model ?? 'gpt-4o'}
onChange={e => setSettings(prev => ({
...prev,
openai: { ...prev.openai!, model: e.target.value }
}))}
className="w-full px-4 py-3 bg-elevated border border-border-subtle rounded-xl text-text-primary focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all appearance-none cursor-pointer"
>
{getAvailableModels('openai').map(model => (
<option key={model} value={model}>{model}</option>
))}
</select>
</div>
<div className="space-y-2">
<label className="flex items-center gap-2 text-sm font-medium text-text-secondary">
<Server className="w-4 h-4" />
Base URL <span className="text-text-muted font-normal">(optional)</span>
</label>
<input
type="url"
value={settings.openai?.baseUrl ?? ''}
onChange={e => setSettings(prev => ({
...prev,
openai: { ...prev.openai!, baseUrl: e.target.value }
}))}
placeholder="https://api.openai.com/v1 (default)"
className="w-full px-4 py-3 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all"
/>
<p className="text-xs text-text-muted">
Leave empty to use the default OpenAI API. Set a custom URL for proxies or compatible APIs.
</p>
</div>
</div>
)}
{/* Gemini Settings */}
{settings.activeProvider === 'gemini' && (
<div className="space-y-4 animate-fade-in">
@ -276,7 +354,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
)}
</div>
</div>
{/* Model selector or manual input */}
{(useCustomModel || isCustomModelSelected) ? (
<div className="space-y-2">
@ -329,7 +407,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
</button>
</div>
)}
{modelFetchError && !useCustomModel && (
<p className="text-xs text-amber-400 flex items-center gap-1">
<AlertCircle className="w-3 h-3" />
@ -339,7 +417,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
</div>
</div>
)}
{/* Anthropic Settings */}
{settings.activeProvider === 'anthropic' && (
<div className="space-y-4 animate-fade-in">
@ -379,10 +457,10 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
</a>
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-text-secondary">Model</label>
{(useCustomAnthropicModel || isCustomAnthropicModelSelected) ? (
<div className="space-y-2">
<input
@ -549,7 +627,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
🔒
</div>
<div className="text-xs text-text-muted leading-relaxed">
<span className="text-text-secondary font-medium">Privacy:</span> Your API keys are stored only in your browser's local storage.
<span className="text-text-secondary font-medium">Privacy:</span> Your API keys are stored only in your browser's local storage.
They're sent directly to the LLM provider when you chat. Your code never leaves your machine.
</div>
</div>

View file

@ -7,13 +7,14 @@
import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { SystemMessage } from '@langchain/core/messages';
import { AzureChatOpenAI } from '@langchain/openai';
import { ChatOpenAI, AzureChatOpenAI } from '@langchain/openai';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ChatAnthropic } from '@langchain/anthropic';
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
import { createGraphRAGTools } from './tools';
import type {
ProviderConfig,
OpenAIConfig,
AzureOpenAIConfig,
GeminiConfig,
AnthropicConfig,
@ -109,6 +110,20 @@ GOOD: A["User Data"] --> B["Process and Save"]
`;
export const createChatModel = (config: ProviderConfig): BaseChatModel => {
switch (config.provider) {
case 'openai': {
const openaiConfig = config as OpenAIConfig;
return new ChatOpenAI({
openAIApiKey: openaiConfig.apiKey,
modelName: openaiConfig.model,
temperature: openaiConfig.temperature ?? 0.1,
maxTokens: openaiConfig.maxTokens,
configuration: openaiConfig.baseUrl ? {
baseURL: openaiConfig.baseUrl,
} : undefined,
streaming: true,
});
}
case 'azure-openai': {
const azureConfig = config as AzureOpenAIConfig;
return new AzureChatOpenAI({

View file

@ -261,6 +261,8 @@ export const getProviderDisplayName = (provider: LLMProvider): string => {
*/
export const getAvailableModels = (provider: LLMProvider): string[] => {
switch (provider) {
case 'openai':
return ['gpt-4.5-preview', 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'];
case 'azure-openai':
// Azure models depend on deployment, so we show common ones
return ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-35-turbo'];