chore: format code and fix bundle identifier for v3.0.0 release

- Format all TypeScript/React code with Prettier
- Format all Rust code with cargo fmt
- Fix bundle identifier from .app to .desktop to avoid macOS conflicts
- Prepare codebase for v3.0.0 Tauri release
This commit is contained in:
Jason
2025-08-27 11:00:53 +08:00
parent 5e2e80b00d
commit 642e7a3817
23 changed files with 359 additions and 321 deletions

View File

@@ -26,7 +26,8 @@
gap: 1rem;
}
.refresh-btn, .add-btn {
.refresh-btn,
.add-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
@@ -123,12 +124,12 @@
left: 50%;
transform: translateX(-50%);
z-index: 100;
padding: 0.75rem 1.25rem;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 500;
width: fit-content;
white-space: nowrap;
}

View File

@@ -10,9 +10,12 @@ function App() {
const [providers, setProviders] = useState<Record<string, Provider>>({});
const [currentProviderId, setCurrentProviderId] = useState<string>("");
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [configStatus, setConfigStatus] = useState<{ exists: boolean; path: string } | null>(null);
const [configStatus, setConfigStatus] = useState<{
exists: boolean;
path: string;
} | null>(null);
const [editingProviderId, setEditingProviderId] = useState<string | null>(
null
null,
);
const [notification, setNotification] = useState<{
message: string;
@@ -31,7 +34,7 @@ function App() {
const showNotification = (
message: string,
type: "success" | "error",
duration = 3000
duration = 3000,
) => {
// 清除之前的定时器
if (timeoutRef.current) {
@@ -73,7 +76,7 @@ function App() {
const currentId = await window.api.getCurrentProvider();
setProviders(loadedProviders);
setCurrentProviderId(currentId);
// 如果供应商列表为空,尝试自动导入现有配置为"default"供应商
if (Object.keys(loadedProviders).length === 0) {
await handleAutoImportDefault();
@@ -82,7 +85,10 @@ function App() {
const loadConfigStatus = async () => {
const status = await window.api.getClaudeConfigStatus();
setConfigStatus({ exists: Boolean(status?.exists), path: String(status?.path || "") });
setConfigStatus({
exists: Boolean(status?.exists),
path: String(status?.path || ""),
});
};
// 生成唯一ID
@@ -137,7 +143,7 @@ function App() {
showNotification(
"切换成功!请重启 Claude Code 终端以生效",
"success",
2000
2000,
);
} else {
showNotification("切换失败,请检查配置", "error");
@@ -147,18 +153,22 @@ function App() {
// 自动导入现有配置为"default"供应商
const handleAutoImportDefault = async () => {
try {
const result = await window.api.importCurrentConfigAsDefault()
const result = await window.api.importCurrentConfigAsDefault();
if (result.success) {
await loadProviders()
showNotification("已自动导入现有配置为 default 供应商", "success", 3000)
await loadProviders();
showNotification(
"已自动导入现有配置为 default 供应商",
"success",
3000,
);
}
// 如果导入失败(比如没有现有配置),静默处理,不显示错误
} catch (error) {
console.error('自动导入默认配置失败:', error)
console.error("自动导入默认配置失败:", error);
// 静默处理,不影响用户体验
}
}
};
const handleOpenConfigFolder = async () => {
await window.api.openConfigFolder();

View File

@@ -22,7 +22,7 @@
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
position: relative;
z-index: 1001;
display: flex; /* 纵向布局,便于底栏固定 */
display: flex; /* 纵向布局,便于底栏固定 */
flex-direction: column;
}
@@ -40,7 +40,10 @@
}
/* 左侧占位以保证标题居中(与右侧关闭按钮宽度相当) */
.modal-spacer { width: 32px; flex: 0 0 32px; }
.modal-spacer {
width: 32px;
flex: 0 0 32px;
}
.modal-title {
flex: 1;
@@ -69,16 +72,17 @@
color: #fff;
}
.modal-form { /* 表单外层包裹 body + footer */
.modal-form {
/* 表单外层包裹 body + footer */
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0; /* 允许子元素正确计算高度 */
min-height: 0; /* 允许子元素正确计算高度 */
}
.modal-body {
padding: 1.25rem 1.5rem 1.5rem;
overflow: auto; /* 仅内容区滚动 */
overflow: auto; /* 仅内容区滚动 */
flex: 1 1 auto;
min-height: 0;
}
@@ -175,7 +179,8 @@
border-color: #3498db;
}
.modal-footer { /* 固定在弹窗底部(非滚动区) */
.modal-footer {
/* 固定在弹窗底部(非滚动区) */
display: flex;
gap: 1rem;
justify-content: flex-end;

View File

@@ -68,7 +68,9 @@
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: background-color 0.2s, transform 0.1s;
transition:
background-color 0.2s,
transform 0.1s;
min-width: 70px;
}
@@ -102,4 +104,4 @@
.confirm-btn:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
}
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import './ConfirmDialog.css';
import React from "react";
import "./ConfirmDialog.css";
interface ConfirmDialogProps {
isOpen: boolean;
@@ -15,10 +15,10 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
isOpen,
title,
message,
confirmText = '确定',
cancelText = '取消',
confirmText = "确定",
cancelText = "取消",
onConfirm,
onCancel
onCancel,
}) => {
if (!isOpen) return null;
@@ -32,15 +32,15 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
<p>{message}</p>
</div>
<div className="confirm-actions">
<button
className="confirm-btn cancel-btn"
<button
className="confirm-btn cancel-btn"
onClick={onCancel}
autoFocus
>
{cancelText}
</button>
<button
className="confirm-btn confirm-btn-primary"
<button
className="confirm-btn confirm-btn-primary"
onClick={onConfirm}
>
{confirmText}
@@ -49,4 +49,4 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
</div>
</div>
);
};
};

View File

@@ -1,20 +1,24 @@
import React from 'react'
import { Provider } from '../types'
import ProviderForm from './ProviderForm'
import React from "react";
import { Provider } from "../types";
import ProviderForm from "./ProviderForm";
interface EditProviderModalProps {
provider: Provider
onSave: (provider: Provider) => void
onClose: () => void
provider: Provider;
onSave: (provider: Provider) => void;
onClose: () => void;
}
const EditProviderModal: React.FC<EditProviderModalProps> = ({ provider, onSave, onClose }) => {
const handleSubmit = (data: Omit<Provider, 'id'>) => {
const EditProviderModal: React.FC<EditProviderModalProps> = ({
provider,
onSave,
onClose,
}) => {
const handleSubmit = (data: Omit<Provider, "id">) => {
onSave({
...provider,
...data
})
}
...data,
});
};
return (
<ProviderForm
@@ -25,7 +29,7 @@ const EditProviderModal: React.FC<EditProviderModalProps> = ({ provider, onSave,
onSubmit={handleSubmit}
onClose={onClose}
/>
)
}
);
};
export default EditProviderModal
export default EditProviderModal;

View File

@@ -80,7 +80,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
};
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;
@@ -117,7 +117,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
// 更新JSON配置
const updatedConfig = updateCoAuthoredSetting(
formData.settingsConfig,
checked
checked,
);
setFormData({
...formData,
@@ -152,7 +152,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
const configString = setApiKeyInConfig(
formData.settingsConfig,
key.trim(),
{ createIfMissing: selectedPreset !== null }
{ createIfMissing: selectedPreset !== null },
);
// 更新表单配置
@@ -174,7 +174,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
useEffect(() => {
if (initialData) {
const parsedKey = getApiKeyFromConfig(
JSON.stringify(initialData.settingsConfig)
JSON.stringify(initialData.settingsConfig),
);
if (parsedKey) setApiKey(parsedKey);
}
@@ -255,7 +255,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
/>
</div>
<div className={`form-group api-key-group ${!showApiKey ? 'hidden' : ''}`}>
<div
className={`form-group api-key-group ${!showApiKey ? "hidden" : ""}`}
>
<label htmlFor="apiKey">API Key *</label>
<input
type="text"

View File

@@ -203,4 +203,4 @@
.delete-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}

View File

@@ -1,13 +1,13 @@
import React from 'react'
import { Provider } from '../types'
import './ProviderList.css'
import React from "react";
import { Provider } from "../types";
import "./ProviderList.css";
interface ProviderListProps {
providers: Record<string, Provider>
currentProviderId: string
onSwitch: (id: string) => void
onDelete: (id: string) => void
onEdit: (id: string) => void
providers: Record<string, Provider>;
currentProviderId: string;
onSwitch: (id: string) => void;
onDelete: (id: string) => void;
onEdit: (id: string) => void;
}
const ProviderList: React.FC<ProviderListProps> = ({
@@ -15,28 +15,28 @@ const ProviderList: React.FC<ProviderListProps> = ({
currentProviderId,
onSwitch,
onDelete,
onEdit
onEdit,
}) => {
// 提取API地址
const getApiUrl = (provider: Provider): string => {
try {
const config = provider.settingsConfig
const config = provider.settingsConfig;
if (config?.env?.ANTHROPIC_BASE_URL) {
return config.env.ANTHROPIC_BASE_URL
return config.env.ANTHROPIC_BASE_URL;
}
return '未设置'
return "未设置";
} catch {
return '配置错误'
return "配置错误";
}
}
};
const handleUrlClick = async (url: string) => {
try {
await window.api.openExternal(url)
await window.api.openExternal(url);
} catch (error) {
console.error('打开链接失败:', error)
console.error("打开链接失败:", error);
}
}
};
return (
<div className="provider-list">
@@ -48,25 +48,27 @@ const ProviderList: React.FC<ProviderListProps> = ({
) : (
<div className="provider-items">
{Object.values(providers).map((provider) => {
const isCurrent = provider.id === currentProviderId
const isCurrent = provider.id === currentProviderId;
return (
<div
key={provider.id}
className={`provider-item ${isCurrent ? 'current' : ''}`}
<div
key={provider.id}
className={`provider-item ${isCurrent ? "current" : ""}`}
>
<div className="provider-info">
<div className="provider-name">
<span>{provider.name}</span>
{isCurrent && <span className="current-badge">使</span>}
{isCurrent && (
<span className="current-badge">使</span>
)}
</div>
<div className="provider-url">
{provider.websiteUrl ? (
<a
href="#"
<a
href="#"
onClick={(e) => {
e.preventDefault()
handleUrlClick(provider.websiteUrl!)
e.preventDefault();
handleUrlClick(provider.websiteUrl!);
}}
className="url-link"
title={`访问 ${provider.websiteUrl}`}
@@ -80,23 +82,23 @@ const ProviderList: React.FC<ProviderListProps> = ({
)}
</div>
</div>
<div className="provider-actions">
<button
<button
className="enable-btn"
onClick={() => onSwitch(provider.id)}
disabled={isCurrent}
>
</button>
<button
<button
className="edit-btn"
onClick={() => onEdit(provider.id)}
disabled={isCurrent}
>
</button>
<button
<button
className="delete-btn"
onClick={() => onDelete(provider.id)}
disabled={isCurrent}
@@ -105,12 +107,12 @@ const ProviderList: React.FC<ProviderListProps> = ({
</button>
</div>
</div>
)
);
})}
</div>
)}
</div>
)
}
);
};
export default ProviderList
export default ProviderList;

View File

@@ -63,5 +63,4 @@ export const providerPresets: ProviderPreset[] = [
},
},
},
];

View File

@@ -5,9 +5,9 @@
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5;

View File

@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { Provider } from '../types';
import { invoke } from "@tauri-apps/api/core";
import { Provider } from "../types";
// 定义配置状态类型
interface ConfigStatus {
@@ -19,9 +19,9 @@ export const tauriAPI = {
// 获取所有供应商
getProviders: async (): Promise<Record<string, Provider>> => {
try {
return await invoke('get_providers');
return await invoke("get_providers");
} catch (error) {
console.error('获取供应商列表失败:', error);
console.error("获取供应商列表失败:", error);
return {};
}
},
@@ -29,19 +29,19 @@ export const tauriAPI = {
// 获取当前供应商ID
getCurrentProvider: async (): Promise<string> => {
try {
return await invoke('get_current_provider');
return await invoke("get_current_provider");
} catch (error) {
console.error('获取当前供应商失败:', error);
return '';
console.error("获取当前供应商失败:", error);
return "";
}
},
// 添加供应商
addProvider: async (provider: Provider): Promise<boolean> => {
try {
return await invoke('add_provider', { provider });
return await invoke("add_provider", { provider });
} catch (error) {
console.error('添加供应商失败:', error);
console.error("添加供应商失败:", error);
throw error;
}
},
@@ -49,9 +49,9 @@ export const tauriAPI = {
// 更新供应商
updateProvider: async (provider: Provider): Promise<boolean> => {
try {
return await invoke('update_provider', { provider });
return await invoke("update_provider", { provider });
} catch (error) {
console.error('更新供应商失败:', error);
console.error("更新供应商失败:", error);
throw error;
}
},
@@ -59,9 +59,9 @@ export const tauriAPI = {
// 删除供应商
deleteProvider: async (id: string): Promise<boolean> => {
try {
return await invoke('delete_provider', { id });
return await invoke("delete_provider", { id });
} catch (error) {
console.error('删除供应商失败:', error);
console.error("删除供应商失败:", error);
throw error;
}
},
@@ -69,9 +69,9 @@ export const tauriAPI = {
// 切换供应商
switchProvider: async (providerId: string): Promise<boolean> => {
try {
return await invoke('switch_provider', { id: providerId });
return await invoke("switch_provider", { id: providerId });
} catch (error) {
console.error('切换供应商失败:', error);
console.error("切换供应商失败:", error);
return false;
}
},
@@ -79,16 +79,16 @@ export const tauriAPI = {
// 导入当前配置为默认供应商
importCurrentConfigAsDefault: async (): Promise<ImportResult> => {
try {
const success = await invoke<boolean>('import_default_config');
const success = await invoke<boolean>("import_default_config");
return {
success,
message: success ? '成功导入默认配置' : '导入失败'
message: success ? "成功导入默认配置" : "导入失败",
};
} catch (error) {
console.error('导入默认配置失败:', error);
console.error("导入默认配置失败:", error);
return {
success: false,
message: String(error)
message: String(error),
};
}
},
@@ -96,23 +96,23 @@ export const tauriAPI = {
// 获取 Claude Code 配置文件路径
getClaudeCodeConfigPath: async (): Promise<string> => {
try {
return await invoke('get_claude_code_config_path');
return await invoke("get_claude_code_config_path");
} catch (error) {
console.error('获取配置路径失败:', error);
return '';
console.error("获取配置路径失败:", error);
return "";
}
},
// 获取 Claude Code 配置状态
getClaudeConfigStatus: async (): Promise<ConfigStatus> => {
try {
return await invoke('get_claude_config_status');
return await invoke("get_claude_config_status");
} catch (error) {
console.error('获取配置状态失败:', error);
console.error("获取配置状态失败:", error);
return {
exists: false,
path: '',
error: String(error)
path: "",
error: String(error),
};
}
},
@@ -120,34 +120,33 @@ export const tauriAPI = {
// 打开配置文件夹
openConfigFolder: async (): Promise<void> => {
try {
await invoke('open_config_folder');
await invoke("open_config_folder");
} catch (error) {
console.error('打开配置文件夹失败:', error);
console.error("打开配置文件夹失败:", error);
}
},
// 打开外部链接
openExternal: async (url: string): Promise<void> => {
try {
await invoke('open_external', { url });
await invoke("open_external", { url });
} catch (error) {
console.error('打开外部链接失败:', error);
console.error("打开外部链接失败:", error);
}
},
// 选择配置文件Tauri 暂不实现,保留接口兼容性)
selectConfigFile: async (): Promise<string | null> => {
console.warn('selectConfigFile 在 Tauri 版本中暂不支持');
console.warn("selectConfigFile 在 Tauri 版本中暂不支持");
return null;
}
},
};
// 创建全局 API 对象,兼容现有代码
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
// 绑定到 window.api避免 Electron 命名造成误解
// API 内部已做 try/catch非 Tauri 环境下也会安全返回默认值
(window as any).api = tauriAPI;
}
export default tauriAPI;

View File

@@ -1,24 +1,24 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
// 导入 Tauri API自动绑定到 window.api
import './lib/tauri-api'
import "./lib/tauri-api";
// 根据平台添加 body class便于平台特定样式
try {
const ua = navigator.userAgent || ''
const plat = (navigator.platform || '').toLowerCase()
const isMac = /mac/i.test(ua) || plat.includes('mac')
const ua = navigator.userAgent || "";
const plat = (navigator.platform || "").toLowerCase();
const isMac = /mac/i.test(ua) || plat.includes("mac");
if (isMac) {
document.body.classList.add('is-mac')
document.body.classList.add("is-mac");
}
} catch {
// 忽略平台检测失败
}
ReactDOM.createRoot(document.getElementById('root')!).render(
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
</React.StrictMode>,
);

View File

@@ -1,91 +1,97 @@
// 供应商配置处理工具函数
// 处理includeCoAuthoredBy字段的添加/删除
export const updateCoAuthoredSetting = (jsonString: string, disable: boolean): string => {
export const updateCoAuthoredSetting = (
jsonString: string,
disable: boolean,
): string => {
try {
const config = JSON.parse(jsonString)
const config = JSON.parse(jsonString);
if (disable) {
// 添加或更新includeCoAuthoredBy字段
config.includeCoAuthoredBy = false
config.includeCoAuthoredBy = false;
} else {
// 删除includeCoAuthoredBy字段
delete config.includeCoAuthoredBy
delete config.includeCoAuthoredBy;
}
return JSON.stringify(config, null, 2)
return JSON.stringify(config, null, 2);
} catch (err) {
// 如果JSON解析失败返回原始字符串
return jsonString
return jsonString;
}
}
};
// 从JSON配置中检查是否包含includeCoAuthoredBy设置
export const checkCoAuthoredSetting = (jsonString: string): boolean => {
try {
const config = JSON.parse(jsonString)
return config.includeCoAuthoredBy === false
const config = JSON.parse(jsonString);
return config.includeCoAuthoredBy === false;
} catch (err) {
return false
return false;
}
}
};
// 从JSON配置中提取并处理官网地址
export const extractWebsiteUrl = (jsonString: string): string => {
try {
const config = JSON.parse(jsonString)
const baseUrl = config?.env?.ANTHROPIC_BASE_URL
if (baseUrl && typeof baseUrl === 'string') {
const config = JSON.parse(jsonString);
const baseUrl = config?.env?.ANTHROPIC_BASE_URL;
if (baseUrl && typeof baseUrl === "string") {
// 去掉 "api." 前缀
return baseUrl.replace(/^https?:\/\/api\./, 'https://')
return baseUrl.replace(/^https?:\/\/api\./, "https://");
}
} catch (err) {
// 忽略JSON解析错误
}
return ''
}
return "";
};
// 读取配置中的 API Keyenv.ANTHROPIC_AUTH_TOKEN
export const getApiKeyFromConfig = (jsonString: string): string => {
try {
const config = JSON.parse(jsonString)
const key = config?.env?.ANTHROPIC_AUTH_TOKEN
return typeof key === 'string' ? key : ''
const config = JSON.parse(jsonString);
const key = config?.env?.ANTHROPIC_AUTH_TOKEN;
return typeof key === "string" ? key : "";
} catch (err) {
return ''
return "";
}
}
};
// 判断配置中是否存在 API Key 字段
export const hasApiKeyField = (jsonString: string): boolean => {
try {
const config = JSON.parse(jsonString)
return Object.prototype.hasOwnProperty.call(config?.env ?? {}, 'ANTHROPIC_AUTH_TOKEN')
const config = JSON.parse(jsonString);
return Object.prototype.hasOwnProperty.call(
config?.env ?? {},
"ANTHROPIC_AUTH_TOKEN",
);
} catch (err) {
return false
return false;
}
}
};
// 写入/更新配置中的 API Key默认不新增缺失字段
export const setApiKeyInConfig = (
jsonString: string,
apiKey: string,
options: { createIfMissing?: boolean } = {}
options: { createIfMissing?: boolean } = {},
): string => {
const { createIfMissing = false } = options
const { createIfMissing = false } = options;
try {
const config = JSON.parse(jsonString)
const config = JSON.parse(jsonString);
if (!config.env) {
if (!createIfMissing) return jsonString
config.env = {}
if (!createIfMissing) return jsonString;
config.env = {};
}
if (!('ANTHROPIC_AUTH_TOKEN' in config.env) && !createIfMissing) {
return jsonString
if (!("ANTHROPIC_AUTH_TOKEN" in config.env) && !createIfMissing) {
return jsonString;
}
config.env.ANTHROPIC_AUTH_TOKEN = apiKey
return JSON.stringify(config, null, 2)
config.env.ANTHROPIC_AUTH_TOKEN = apiKey;
return JSON.stringify(config, null, 2);
} catch (err) {
return jsonString
return jsonString;
}
}
};

2
src/vite-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="vite/client" />
import { Provider } from './types';
import { Provider } from "./types";
interface ImportResult {
success: boolean;