Files
claude-code-router/ui/src/components/ConfigProvider.tsx
BigUncle b8f52ba538 feat logging: Implement LOG_LEVEL configuration option and improve logging consistency
- Add LOG_LEVEL configuration option to control logging verbosity
- Update UI to include LOG_LEVEL dropdown in settings
- Fix logging inconsistency between environment variables and config file
- Unify logging configuration to use config file settings
- Maintain separate logging systems for different purposes:
  * Server-level logs (HTTP requests, API calls) using pino in ~/.claude-code-router/logs/
  * Application-level logs (routing decisions, business logic) in ~/.claude-code-router/claude-code-router.log
- Update documentation with accurate logging system information
- Add detailed information about dual logging systems in README.md and README_zh.md
- Improve type safety and validation in ConfigProvider

Co-authored-by: qwen-cli <https://github.com/QwenLM/qwen-code>
2025-08-14 21:17:27 +08:00

139 lines
4.8 KiB
TypeScript

import { createContext, useContext, useState, useEffect } from 'react';
import type { ReactNode, Dispatch, SetStateAction } from 'react';
import { api } from '@/lib/api';
import type { Config } from '@/types';
interface ConfigContextType {
config: Config | null;
setConfig: Dispatch<SetStateAction<Config | null>>;
error: Error | null;
}
const ConfigContext = createContext<ConfigContextType | undefined>(undefined);
// eslint-disable-next-line react-refresh/only-export-components
export function useConfig() {
const context = useContext(ConfigContext);
if (context === undefined) {
throw new Error('useConfig must be used within a ConfigProvider');
}
return context;
}
interface ConfigProviderProps {
children: ReactNode;
}
export function ConfigProvider({ children }: ConfigProviderProps) {
const [config, setConfig] = useState<Config | null>(null);
const [error, setError] = useState<Error | null>(null);
const [hasFetched, setHasFetched] = useState<boolean>(false);
const [apiKey, setApiKey] = useState<string | null>(localStorage.getItem('apiKey'));
// Listen for localStorage changes
useEffect(() => {
const handleStorageChange = () => {
setApiKey(localStorage.getItem('apiKey'));
};
window.addEventListener('storage', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, []);
useEffect(() => {
const fetchConfig = async () => {
// Reset fetch state when API key changes
setHasFetched(false);
setConfig(null);
setError(null);
};
fetchConfig();
}, [apiKey]);
useEffect(() => {
const fetchConfig = async () => {
// Prevent duplicate API calls in React StrictMode
// Skip if we've already fetched
if (hasFetched) {
return;
}
setHasFetched(true);
try {
// Try to fetch config regardless of API key presence
const data = await api.getConfig();
// Validate the received data to ensure it has the expected structure
const validConfig = {
LOG: typeof data.LOG === 'boolean' ? data.LOG : false,
LOG_LEVEL: typeof data.LOG_LEVEL === 'string' ? data.LOG_LEVEL : 'info',
CLAUDE_PATH: typeof data.CLAUDE_PATH === 'string' ? data.CLAUDE_PATH : '',
HOST: typeof data.HOST === 'string' ? data.HOST : '127.0.0.1',
PORT: typeof data.PORT === 'number' ? data.PORT : 3456,
APIKEY: typeof data.APIKEY === 'string' ? data.APIKEY : '',
API_TIMEOUT_MS: typeof data.API_TIMEOUT_MS === 'string' ? data.API_TIMEOUT_MS : '600000',
PROXY_URL: typeof data.PROXY_URL === 'string' ? data.PROXY_URL : '',
transformers: Array.isArray(data.transformers) ? data.transformers : [],
Providers: Array.isArray(data.Providers) ? data.Providers : [],
Router: data.Router && typeof data.Router === 'object' ? {
default: typeof data.Router.default === 'string' ? data.Router.default : '',
background: typeof data.Router.background === 'string' ? data.Router.background : '',
think: typeof data.Router.think === 'string' ? data.Router.think : '',
longContext: typeof data.Router.longContext === 'string' ? data.Router.longContext : '',
longContextThreshold: typeof data.Router.longContextThreshold === 'number' ? data.Router.longContextThreshold : 60000,
webSearch: typeof data.Router.webSearch === 'string' ? data.Router.webSearch : ''
} : {
default: '',
background: '',
think: '',
longContext: '',
longContextThreshold: 60000,
webSearch: ''
}
};
setConfig(validConfig);
} catch (err) {
console.error('Failed to fetch config:', err);
// If we get a 401, the API client will redirect to login
// Otherwise, set an empty config or error
if ((err as Error).message !== 'Unauthorized') {
// Set default empty config when fetch fails
setConfig({
LOG: false,
LOG_LEVEL: 'info',
CLAUDE_PATH: '',
HOST: '127.0.0.1',
PORT: 3456,
APIKEY: '',
API_TIMEOUT_MS: '600000',
PROXY_URL: '',
transformers: [],
Providers: [],
Router: {
default: '',
background: '',
think: '',
longContext: '',
longContextThreshold: 60000,
webSearch: ''
}
});
setError(err as Error);
}
}
};
fetchConfig();
}, [hasFetched, apiKey]);
return (
<ConfigContext.Provider value={{ config, setConfig, error }}>
{children}
</ConfigContext.Provider>
);
}