优化首次启动体验:自动创建默认供应商且设为选中状态
- 新增 importCurrentConfigAsDefault 函数,创建 ID 为 'default' 的特殊供应商 - 默认供应商不生成独立配置文件,直接使用现有 settings.json - 首次启动时自动导入现有配置为默认供应商,并设为选中状态 - 切换到默认供应商时无需文件操作,直接使用原配置 - 删除默认供应商时保护原配置文件不被误删 - 简化 ImportConfigModal 组件,移除 isEmpty 相关逻辑 - 提升用户体验:无需手动操作,开箱即用
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
deleteProviderConfig,
|
deleteProviderConfig,
|
||||||
sanitizeProviderName,
|
sanitizeProviderName,
|
||||||
importCurrentConfig,
|
importCurrentConfig,
|
||||||
|
importCurrentConfigAsDefault,
|
||||||
getProviderConfigPath,
|
getProviderConfigPath,
|
||||||
fileExists,
|
fileExists,
|
||||||
} from "./services";
|
} from "./services";
|
||||||
@@ -234,6 +235,29 @@ ipcMain.handle("importCurrentConfig", async (_, name: string) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("importCurrentConfigAsDefault", async () => {
|
||||||
|
try {
|
||||||
|
const result = await importCurrentConfigAsDefault();
|
||||||
|
|
||||||
|
if (result.success && result.provider) {
|
||||||
|
// 将默认供应商添加到store中
|
||||||
|
const providers = store.get("providers", {} as Record<string, Provider>);
|
||||||
|
providers[result.provider.id] = result.provider;
|
||||||
|
await store.set("providers", providers);
|
||||||
|
|
||||||
|
// 设置为当前选中的供应商
|
||||||
|
await store.set("current", result.provider.id);
|
||||||
|
|
||||||
|
return { success: true, providerId: result.provider.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("导入默认配置失败:", error);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("getClaudeCodeConfigPath", () => {
|
ipcMain.handle("getClaudeCodeConfigPath", () => {
|
||||||
return getClaudeCodeConfig().path;
|
return getClaudeCodeConfig().path;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
updateProvider: (provider: Provider) => ipcRenderer.invoke('updateProvider', provider),
|
updateProvider: (provider: Provider) => ipcRenderer.invoke('updateProvider', provider),
|
||||||
switchProvider: (providerId: string) => ipcRenderer.invoke('switchProvider', providerId),
|
switchProvider: (providerId: string) => ipcRenderer.invoke('switchProvider', providerId),
|
||||||
importCurrentConfig: (name: string) => ipcRenderer.invoke('importCurrentConfig', name),
|
importCurrentConfig: (name: string) => ipcRenderer.invoke('importCurrentConfig', name),
|
||||||
|
importCurrentConfigAsDefault: () => ipcRenderer.invoke('importCurrentConfigAsDefault'),
|
||||||
getClaudeCodeConfigPath: () => ipcRenderer.invoke('getClaudeCodeConfigPath'),
|
getClaudeCodeConfigPath: () => ipcRenderer.invoke('getClaudeCodeConfigPath'),
|
||||||
selectConfigFile: () => ipcRenderer.invoke('selectConfigFile'),
|
selectConfigFile: () => ipcRenderer.invoke('selectConfigFile'),
|
||||||
openExternal: (url: string) => ipcRenderer.invoke('openExternal', url)
|
openExternal: (url: string) => ipcRenderer.invoke('openExternal', url)
|
||||||
|
|||||||
@@ -84,11 +84,18 @@ export async function switchProvider(
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const { path: settingsPath, dir: configDir } = getClaudeCodeConfig();
|
const { path: settingsPath, dir: configDir } = getClaudeCodeConfig();
|
||||||
const newSettingsPath = getProviderConfigPath(provider.id, provider.name);
|
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
await fs.mkdir(configDir, { recursive: true });
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
|
||||||
|
// 特殊处理:如果切换到默认供应商(id="default"),直接使用现有的 settings.json
|
||||||
|
if (provider.id === "default") {
|
||||||
|
console.log(`切换到默认供应商,使用现有配置文件`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSettingsPath = getProviderConfigPath(provider.id, provider.name);
|
||||||
|
|
||||||
// 检查目标配置文件是否存在
|
// 检查目标配置文件是否存在
|
||||||
if (!(await fileExists(newSettingsPath))) {
|
if (!(await fileExists(newSettingsPath))) {
|
||||||
console.error(`供应商配置文件不存在: ${newSettingsPath}`);
|
console.error(`供应商配置文件不存在: ${newSettingsPath}`);
|
||||||
@@ -98,12 +105,15 @@ export async function switchProvider(
|
|||||||
// 1. 如果当前存在settings.json,先备份到当前供应商的配置文件
|
// 1. 如果当前存在settings.json,先备份到当前供应商的配置文件
|
||||||
if (await fileExists(settingsPath)) {
|
if (await fileExists(settingsPath)) {
|
||||||
if (currentProviderId && providers && providers[currentProviderId]) {
|
if (currentProviderId && providers && providers[currentProviderId]) {
|
||||||
|
// 如果当前是默认供应商,不需要备份(因为 settings.json 就是它的配置)
|
||||||
|
if (currentProviderId !== "default") {
|
||||||
const currentProvider = providers[currentProviderId];
|
const currentProvider = providers[currentProviderId];
|
||||||
const currentProviderPath = getProviderConfigPath(
|
const currentProviderPath = getProviderConfigPath(
|
||||||
currentProviderId,
|
currentProviderId,
|
||||||
currentProvider.name
|
currentProvider.name
|
||||||
);
|
);
|
||||||
await fs.rename(settingsPath, currentProviderPath);
|
await fs.rename(settingsPath, currentProviderPath);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果没有当前供应商ID,创建临时备份
|
// 如果没有当前供应商ID,创建临时备份
|
||||||
const backupPath = path.join(
|
const backupPath = path.join(
|
||||||
@@ -180,6 +190,39 @@ export async function importCurrentConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入当前配置为默认供应商(不生成独立配置文件)
|
||||||
|
*/
|
||||||
|
export async function importCurrentConfigAsDefault(): Promise<{ success: boolean; provider?: Provider }> {
|
||||||
|
try {
|
||||||
|
const { path: settingsPath } = getClaudeCodeConfig();
|
||||||
|
|
||||||
|
// 检查当前配置是否存在
|
||||||
|
if (!(await fileExists(settingsPath))) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取当前配置
|
||||||
|
const configContent = await fs.readFile(settingsPath, "utf-8");
|
||||||
|
const settingsConfig = JSON.parse(configContent);
|
||||||
|
|
||||||
|
// 创建默认供应商对象
|
||||||
|
const provider: Provider = {
|
||||||
|
id: "default",
|
||||||
|
name: "默认",
|
||||||
|
settingsConfig: settingsConfig,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`已导入当前配置为默认供应商(不生成独立文件)`);
|
||||||
|
return { success: true, provider };
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("导入默认配置失败:", error);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除供应商配置文件
|
* 删除供应商配置文件
|
||||||
*/
|
*/
|
||||||
@@ -188,6 +231,12 @@ export async function deleteProviderConfig(
|
|||||||
providerName?: string
|
providerName?: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
// 特殊处理:默认供应商不删除配置文件
|
||||||
|
if (providerId === "default") {
|
||||||
|
console.log("默认供应商不删除配置文件");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const providerConfigPath = getProviderConfigPath(providerId, providerName);
|
const providerConfigPath = getProviderConfigPath(providerId, providerName);
|
||||||
|
|
||||||
if (await fileExists(providerConfigPath)) {
|
if (await fileExists(providerConfigPath)) {
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ function App() {
|
|||||||
setProviders(loadedProviders);
|
setProviders(loadedProviders);
|
||||||
setCurrentProviderId(currentId);
|
setCurrentProviderId(currentId);
|
||||||
|
|
||||||
// 如果供应商列表为空,自动弹出导入配置对话框
|
// 如果供应商列表为空,尝试自动导入现有配置为"默认"供应商
|
||||||
if (Object.keys(loadedProviders).length === 0) {
|
if (Object.keys(loadedProviders).length === 0) {
|
||||||
setIsImportModalOpen(true);
|
await handleAutoImportDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -163,6 +163,22 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自动导入现有配置为"默认"供应商
|
||||||
|
const handleAutoImportDefault = async () => {
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.importCurrentConfigAsDefault()
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
await loadProviders()
|
||||||
|
showNotification("已自动导入现有配置为默认供应商", "success", 3000)
|
||||||
|
}
|
||||||
|
// 如果导入失败(比如没有现有配置),静默处理,不显示错误
|
||||||
|
} catch (error) {
|
||||||
|
console.error('自动导入默认配置失败:', error)
|
||||||
|
// 静默处理,不影响用户体验
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleSelectConfigFile = async () => {
|
const handleSelectConfigFile = async () => {
|
||||||
const selectedPath = await window.electronAPI.selectConfigFile();
|
const selectedPath = await window.electronAPI.selectConfigFile();
|
||||||
if (selectedPath) {
|
if (selectedPath) {
|
||||||
@@ -233,7 +249,6 @@ function App() {
|
|||||||
<ImportConfigModal
|
<ImportConfigModal
|
||||||
onImport={handleImportCurrentConfig}
|
onImport={handleImportCurrentConfig}
|
||||||
onClose={() => setIsImportModalOpen(false)}
|
onClose={() => setIsImportModalOpen(false)}
|
||||||
isEmpty={Object.keys(providers).length === 0}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import './AddProviderModal.css'
|
|||||||
interface ImportConfigModalProps {
|
interface ImportConfigModalProps {
|
||||||
onImport: (name: string) => void
|
onImport: (name: string) => void
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
isEmpty?: boolean // 供应商列表是否为空
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImportConfigModal: React.FC<ImportConfigModalProps> = ({ onImport, onClose, isEmpty = false }) => {
|
const ImportConfigModal: React.FC<ImportConfigModalProps> = ({ onImport, onClose }) => {
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
@@ -26,22 +25,13 @@ const ImportConfigModal: React.FC<ImportConfigModalProps> = ({ onImport, onClose
|
|||||||
return (
|
return (
|
||||||
<div className="modal-overlay">
|
<div className="modal-overlay">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<h2>{isEmpty ? '供应商列表为空' : '导入当前配置'}</h2>
|
<h2>导入当前配置</h2>
|
||||||
|
|
||||||
{isEmpty ? (
|
|
||||||
<p style={{marginBottom: '1.5rem', color: '#666', fontSize: '0.9rem'}}>
|
|
||||||
当前还没有任何供应商配置。您可以将当前的 Claude Code 配置
|
|
||||||
<code>~/.claude/settings.json</code> 导入为一个供应商配置。
|
|
||||||
<br />
|
|
||||||
<strong>注意:</strong>这不会修改您当前的配置文件。
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p style={{marginBottom: '1.5rem', color: '#666', fontSize: '0.9rem'}}>
|
<p style={{marginBottom: '1.5rem', color: '#666', fontSize: '0.9rem'}}>
|
||||||
将当前的 <code>~/.claude/settings.json</code> 配置文件导入为一个新的供应商。
|
将当前的 <code>~/.claude/settings.json</code> 配置文件导入为一个新的供应商。
|
||||||
<br />
|
<br />
|
||||||
<strong>注意:</strong>这不会修改您当前的配置文件。
|
<strong>注意:</strong>这不会修改您当前的配置文件。
|
||||||
</p>
|
</p>
|
||||||
)}
|
|
||||||
|
|
||||||
{error && <div className="error-message">{error}</div>}
|
{error && <div className="error-message">{error}</div>}
|
||||||
|
|
||||||
@@ -54,7 +44,7 @@ const ImportConfigModal: React.FC<ImportConfigModalProps> = ({ onImport, onClose
|
|||||||
name="name"
|
name="name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
placeholder={isEmpty ? "例如:我的默认配置" : "例如:我的当前配置"}
|
placeholder="例如:我的当前配置"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@@ -62,7 +52,7 @@ const ImportConfigModal: React.FC<ImportConfigModalProps> = ({ onImport, onClose
|
|||||||
|
|
||||||
<div className="form-actions">
|
<div className="form-actions">
|
||||||
<button type="button" className="cancel-btn" onClick={onClose}>
|
<button type="button" className="cancel-btn" onClick={onClose}>
|
||||||
{isEmpty ? '稍后设置' : '取消'}
|
取消
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="submit-btn">
|
<button type="submit" className="submit-btn">
|
||||||
导入
|
导入
|
||||||
|
|||||||
Reference in New Issue
Block a user