save cli login info in system env folder

This commit is contained in:
koalazf.99
2025-08-03 21:08:47 +08:00
parent 8d6dfaac19
commit 5cfb727ec6
3 changed files with 107 additions and 32 deletions

View File

@@ -79,6 +79,7 @@ export interface Settings {
checkpointing?: CheckpointingSettings;
autoConfigureMaxOldSpaceSize?: boolean;
enableOpenAILogging?: boolean;
openaiConfig?: Record<string, string>;
// Git-aware file filtering settings
fileFiltering?: {
@@ -174,6 +175,11 @@ export class LoadedSettings {
...(workspace.mcpServers || {}),
...(system.mcpServers || {}),
},
openaiConfig: {
...(user.openaiConfig || {}),
...(workspace.openaiConfig || {}),
...(system.openaiConfig || {}),
},
};
}
@@ -307,6 +313,30 @@ export function loadEnvironment(): void {
}
}
/**
* Loads OpenAI configuration from user settings as fallback.
* Priority order: env vars > .env file > workspace settings.json > ~/.qwen/settings.json
*/
export function loadOpenAIConfigFromSettings(settings: Settings): void {
if (!settings.openaiConfig) {
return;
}
// Only set environment variables if they're not already set
// This maintains the priority order
if (!process.env.OPENAI_API_KEY && settings.openaiConfig.OPENAI_API_KEY) {
process.env.OPENAI_API_KEY = settings.openaiConfig.OPENAI_API_KEY;
}
if (!process.env.OPENAI_BASE_URL && settings.openaiConfig.OPENAI_BASE_URL) {
process.env.OPENAI_BASE_URL = settings.openaiConfig.OPENAI_BASE_URL;
}
if (!process.env.OPENAI_MODEL && settings.openaiConfig.OPENAI_MODEL) {
process.env.OPENAI_MODEL = settings.openaiConfig.OPENAI_MODEL;
}
}
/**
* Loads settings from user and workspace directories.
* Project settings override user settings.
@@ -386,7 +416,7 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
});
}
return new LoadedSettings(
const loadedSettings = new LoadedSettings(
{
path: systemSettingsPath,
settings: systemSettings,
@@ -401,6 +431,12 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
},
settingsErrors,
);
// Load OpenAI config from settings as fallback
// Priority order: env vars > .env file > workspace settings > user settings
loadOpenAIConfigFromSettings(loadedSettings.merged);
return loadedSettings;
}
export function saveSettings(settingsFile: SettingsFile): void {

View File

@@ -92,6 +92,22 @@ export function AuthDialog({
setOpenAIApiKey(apiKey);
setOpenAIBaseUrl(baseUrl);
setOpenAIModel(model);
// Save OpenAI configuration to user settings as fallback
// Priority order: .env > workspace settings.json > ~/.qwen/settings.json
try {
const openAIConfig: { [key: string]: string } = {};
if (apiKey.trim()) openAIConfig.OPENAI_API_KEY = apiKey.trim();
if (baseUrl.trim()) openAIConfig.OPENAI_BASE_URL = baseUrl.trim();
if (model.trim()) openAIConfig.OPENAI_MODEL = model.trim();
// Save to user settings as environment variables for next time
settings.setValue(SettingScope.User, 'openaiConfig' as any, openAIConfig);
} catch (error) {
// Don't block authentication if saving fails
console.warn('Failed to save OpenAI config to settings:', error);
}
setShowOpenAIKeyPrompt(false);
onSelect(AuthType.USE_OPENAI, SettingScope.User);
};
@@ -124,10 +140,18 @@ export function AuthDialog({
});
if (showOpenAIKeyPrompt) {
// Load default values from settings
const defaultValues = {
apiKey: process.env.OPENAI_API_KEY || settings.merged.openaiConfig?.OPENAI_API_KEY || '',
baseUrl: process.env.OPENAI_BASE_URL || settings.merged.openaiConfig?.OPENAI_BASE_URL || '',
model: process.env.OPENAI_MODEL || settings.merged.openaiConfig?.OPENAI_MODEL || '',
};
return (
<OpenAIKeyPrompt
onSubmit={handleOpenAIKeySubmit}
onCancel={handleOpenAIKeyCancel}
defaultValues={defaultValues}
/>
);
}

View File

@@ -11,32 +11,46 @@ import { Colors } from '../colors.js';
interface OpenAIKeyPromptProps {
onSubmit: (apiKey: string, baseUrl: string, model: string) => void;
onCancel: () => void;
defaultValues?: {
apiKey?: string;
baseUrl?: string;
model?: string;
};
}
export function OpenAIKeyPrompt({
onSubmit,
onCancel,
defaultValues,
}: OpenAIKeyPromptProps): React.JSX.Element {
const [apiKey, setApiKey] = useState('');
const [baseUrl, setBaseUrl] = useState('');
const [model, setModel] = useState('');
const [apiKey, setApiKey] = useState(defaultValues?.apiKey || '');
const [baseUrl, setBaseUrl] = useState(defaultValues?.baseUrl || '');
const [model, setModel] = useState(defaultValues?.model || '');
const [currentField, setCurrentField] = useState<
'apiKey' | 'baseUrl' | 'model'
>('apiKey');
useInput((input, key) => {
// 过滤粘贴相关的控制序列
// Ignore control sequences like [I or [O from focus switching
if (input && (input === '[I' || input === '[O')) {
return;
}
// 处理字符输入
if (input && input.length > 0) {
// Filter paste-related control sequences
let cleanInput = (input || '')
// 过滤 ESC 开头的控制序列(如 \u001b[200~\u001b[201~ 等)
// Filter ESC-based control sequences (like \u001b[200~, \u001b[201~, etc.)
.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '') // eslint-disable-line no-control-regex
// 过滤粘贴开始标记 [200~
// Filter paste start marker [200~
.replace(/\[200~/g, '')
// 过滤粘贴结束标记 [201~
// Filter paste end marker [201~
.replace(/\[201~/g, '')
// 过滤单独的 [ 和 ~ 字符(可能是粘贴标记的残留)
// Filter standalone [ and ~ characters (possible paste marker remnants)
.replace(/^\[|~$/g, '');
// 再过滤所有不可见字符ASCII < 32除了回车换行
// Filter all invisible characters (ASCII < 32, except newlines)
cleanInput = cleanInput
.split('')
.filter((ch) => ch.charCodeAt(0) >= 32)
@@ -52,22 +66,23 @@ export function OpenAIKeyPrompt({
}
return;
}
}
// 检查是否是 Enter 键(通过检查输入是否包含换行符)
// Check if Enter key was pressed (by checking for newline characters)
if (input.includes('\n') || input.includes('\r')) {
if (currentField === 'apiKey') {
// 允许空 API key 跳转到下一个字段,让用户稍后可以返回修改
// Allow empty API key to jump to next field, user can return to modify later
setCurrentField('baseUrl');
return;
} else if (currentField === 'baseUrl') {
setCurrentField('model');
return;
} else if (currentField === 'model') {
// 只有在提交时才检查 API key 是否为空
// Only check if API key is empty when submitting
if (apiKey.trim()) {
onSubmit(apiKey.trim(), baseUrl.trim(), model.trim());
} else {
// 如果 API key 为空,回到 API key 字段
// If API key is empty, return to API key field
setCurrentField('apiKey');
}
}