mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 17:27:54 +00:00
save cli login info in system env folder
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,63 +11,78 @@ 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) => {
|
||||
// 过滤粘贴相关的控制序列
|
||||
let cleanInput = (input || '')
|
||||
// 过滤 ESC 开头的控制序列(如 \u001b[200~、\u001b[201~ 等)
|
||||
.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '') // eslint-disable-line no-control-regex
|
||||
// 过滤粘贴开始标记 [200~
|
||||
.replace(/\[200~/g, '')
|
||||
// 过滤粘贴结束标记 [201~
|
||||
.replace(/\[201~/g, '')
|
||||
// 过滤单独的 [ 和 ~ 字符(可能是粘贴标记的残留)
|
||||
.replace(/^\[|~$/g, '');
|
||||
|
||||
// 再过滤所有不可见字符(ASCII < 32,除了回车换行)
|
||||
cleanInput = cleanInput
|
||||
.split('')
|
||||
.filter((ch) => ch.charCodeAt(0) >= 32)
|
||||
.join('');
|
||||
|
||||
if (cleanInput.length > 0) {
|
||||
if (currentField === 'apiKey') {
|
||||
setApiKey((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'baseUrl') {
|
||||
setBaseUrl((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'model') {
|
||||
setModel((prev) => prev + cleanInput);
|
||||
}
|
||||
// Ignore control sequences like [I or [O from focus switching
|
||||
if (input && (input === '[I' || input === '[O')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是 Enter 键(通过检查输入是否包含换行符)
|
||||
// 处理字符输入
|
||||
if (input && input.length > 0) {
|
||||
// Filter paste-related control sequences
|
||||
let cleanInput = (input || '')
|
||||
// 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
|
||||
// Filter paste start marker [200~
|
||||
.replace(/\[200~/g, '')
|
||||
// Filter paste end marker [201~
|
||||
.replace(/\[201~/g, '')
|
||||
// Filter standalone [ and ~ characters (possible paste marker remnants)
|
||||
.replace(/^\[|~$/g, '');
|
||||
|
||||
// Filter all invisible characters (ASCII < 32, except newlines)
|
||||
cleanInput = cleanInput
|
||||
.split('')
|
||||
.filter((ch) => ch.charCodeAt(0) >= 32)
|
||||
.join('');
|
||||
|
||||
if (cleanInput.length > 0) {
|
||||
if (currentField === 'apiKey') {
|
||||
setApiKey((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'baseUrl') {
|
||||
setBaseUrl((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'model') {
|
||||
setModel((prev) => prev + cleanInput);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user