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;
|
checkpointing?: CheckpointingSettings;
|
||||||
autoConfigureMaxOldSpaceSize?: boolean;
|
autoConfigureMaxOldSpaceSize?: boolean;
|
||||||
enableOpenAILogging?: boolean;
|
enableOpenAILogging?: boolean;
|
||||||
|
openaiConfig?: Record<string, string>;
|
||||||
|
|
||||||
// Git-aware file filtering settings
|
// Git-aware file filtering settings
|
||||||
fileFiltering?: {
|
fileFiltering?: {
|
||||||
@@ -174,6 +175,11 @@ export class LoadedSettings {
|
|||||||
...(workspace.mcpServers || {}),
|
...(workspace.mcpServers || {}),
|
||||||
...(system.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.
|
* Loads settings from user and workspace directories.
|
||||||
* Project settings override user settings.
|
* Project settings override user settings.
|
||||||
@@ -386,7 +416,7 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LoadedSettings(
|
const loadedSettings = new LoadedSettings(
|
||||||
{
|
{
|
||||||
path: systemSettingsPath,
|
path: systemSettingsPath,
|
||||||
settings: systemSettings,
|
settings: systemSettings,
|
||||||
@@ -401,6 +431,12 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
|
|||||||
},
|
},
|
||||||
settingsErrors,
|
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 {
|
export function saveSettings(settingsFile: SettingsFile): void {
|
||||||
|
|||||||
@@ -92,6 +92,22 @@ export function AuthDialog({
|
|||||||
setOpenAIApiKey(apiKey);
|
setOpenAIApiKey(apiKey);
|
||||||
setOpenAIBaseUrl(baseUrl);
|
setOpenAIBaseUrl(baseUrl);
|
||||||
setOpenAIModel(model);
|
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);
|
setShowOpenAIKeyPrompt(false);
|
||||||
onSelect(AuthType.USE_OPENAI, SettingScope.User);
|
onSelect(AuthType.USE_OPENAI, SettingScope.User);
|
||||||
};
|
};
|
||||||
@@ -124,10 +140,18 @@ export function AuthDialog({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (showOpenAIKeyPrompt) {
|
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 (
|
return (
|
||||||
<OpenAIKeyPrompt
|
<OpenAIKeyPrompt
|
||||||
onSubmit={handleOpenAIKeySubmit}
|
onSubmit={handleOpenAIKeySubmit}
|
||||||
onCancel={handleOpenAIKeyCancel}
|
onCancel={handleOpenAIKeyCancel}
|
||||||
|
defaultValues={defaultValues}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,63 +11,78 @@ import { Colors } from '../colors.js';
|
|||||||
interface OpenAIKeyPromptProps {
|
interface OpenAIKeyPromptProps {
|
||||||
onSubmit: (apiKey: string, baseUrl: string, model: string) => void;
|
onSubmit: (apiKey: string, baseUrl: string, model: string) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
defaultValues?: {
|
||||||
|
apiKey?: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
model?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OpenAIKeyPrompt({
|
export function OpenAIKeyPrompt({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
defaultValues,
|
||||||
}: OpenAIKeyPromptProps): React.JSX.Element {
|
}: OpenAIKeyPromptProps): React.JSX.Element {
|
||||||
const [apiKey, setApiKey] = useState('');
|
const [apiKey, setApiKey] = useState(defaultValues?.apiKey || '');
|
||||||
const [baseUrl, setBaseUrl] = useState('');
|
const [baseUrl, setBaseUrl] = useState(defaultValues?.baseUrl || '');
|
||||||
const [model, setModel] = useState('');
|
const [model, setModel] = useState(defaultValues?.model || '');
|
||||||
const [currentField, setCurrentField] = useState<
|
const [currentField, setCurrentField] = useState<
|
||||||
'apiKey' | 'baseUrl' | 'model'
|
'apiKey' | 'baseUrl' | 'model'
|
||||||
>('apiKey');
|
>('apiKey');
|
||||||
|
|
||||||
useInput((input, key) => {
|
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,除了回车换行)
|
// Ignore control sequences like [I or [O from focus switching
|
||||||
cleanInput = cleanInput
|
if (input && (input === '[I' || input === '[O')) {
|
||||||
.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;
|
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 (input.includes('\n') || input.includes('\r')) {
|
||||||
if (currentField === 'apiKey') {
|
if (currentField === 'apiKey') {
|
||||||
// 允许空 API key 跳转到下一个字段,让用户稍后可以返回修改
|
// Allow empty API key to jump to next field, user can return to modify later
|
||||||
setCurrentField('baseUrl');
|
setCurrentField('baseUrl');
|
||||||
return;
|
return;
|
||||||
} else if (currentField === 'baseUrl') {
|
} else if (currentField === 'baseUrl') {
|
||||||
setCurrentField('model');
|
setCurrentField('model');
|
||||||
return;
|
return;
|
||||||
} else if (currentField === 'model') {
|
} else if (currentField === 'model') {
|
||||||
// 只有在提交时才检查 API key 是否为空
|
// Only check if API key is empty when submitting
|
||||||
if (apiKey.trim()) {
|
if (apiKey.trim()) {
|
||||||
onSubmit(apiKey.trim(), baseUrl.trim(), model.trim());
|
onSubmit(apiKey.trim(), baseUrl.trim(), model.trim());
|
||||||
} else {
|
} else {
|
||||||
// 如果 API key 为空,回到 API key 字段
|
// If API key is empty, return to API key field
|
||||||
setCurrentField('apiKey');
|
setCurrentField('apiKey');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user