mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 21:30:17 +08:00
8fe5c1041a
* feat: add Universal Provider feature - Add Universal Provider data structures and type definitions - Implement backend CRUD operations and sync functionality - Add frontend UI components (UniversalProviderPanel, Card, FormModal) - Add NewAPI icon and preset configuration - Support cross-app (Claude/Codex/Gemini) configuration sync - Add website URL field for providers - Implement real-time refresh via event notifications - Add i18n support (Chinese/English/Japanese) * feat: integrate universal provider presets into add provider dialog - Add universal provider presets (NewAPI, Custom Gateway) to preset selector - Show universal presets with Layers icon badge in preset selector - Open UniversalProviderFormModal when universal preset is clicked - Pass initialPreset to auto-fill form when opened from add dialog - Add i18n keys for addSuccess/addFailed messages - Keep separate universal provider panel for management * refactor: move universal provider management to add dialog - Remove Layers button from main navigation header - Add 'Manage' button next to universal provider presets - Open UniversalProviderPanel from within add provider dialog - Add i18n keys for 'manage' in all locales * style: display universal provider presets on separate line - Move universal provider section to a new row with border separator - Add label '统一供应商:' to clarify the section * style: unify universal provider label style with preset label - Use FormLabel component for consistent styling - Add background to 'Manage' button matching preset buttons - Update icon size and button padding for consistency * feat: add sync functionality and JSON preview for Universal Provider * fix: add missing in_failover_queue field to Provider structs After rebasing to main, the Provider struct gained a new `in_failover_queue` field. This fix adds the missing field to the three to_*_provider() methods in UniversalProvider. * refactor: redesign AddProviderDialog with tab-based layout - Add tabs to separate app-specific providers and universal providers - Move "Add Universal Provider" button from panel header to footer - Remove unused handleAdd callback and clean up imports - Update emptyHint i18n text to reference the footer button * fix: append /v1 suffix to Codex base_url in Universal Provider Codex uses OpenAI-compatible API which requires the /v1 endpoint suffix. The Universal Provider now automatically appends /v1 to base_url when generating Codex provider config if not already present. - Handle trailing slashes to avoid double slashes - Apply fix to both backend (to_codex_provider) and frontend preview * feat: auto-sync universal provider to apps on creation Previously, users had to manually click sync after adding a universal provider. Now it automatically syncs to Claude/Codex/Gemini on creation, providing a smoother user experience. --------- Co-authored-by: Jason <farion1231@gmail.com>
70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
import React, { useState } from "react";
|
|
import { Eye, EyeOff } from "lucide-react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
interface ApiKeyInputProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
required?: boolean;
|
|
label?: string;
|
|
id?: string;
|
|
}
|
|
|
|
const ApiKeyInput: React.FC<ApiKeyInputProps> = ({
|
|
value,
|
|
onChange,
|
|
placeholder,
|
|
disabled = false,
|
|
required = false,
|
|
label = "API Key",
|
|
id = "apiKey",
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const [showKey, setShowKey] = useState(false);
|
|
|
|
const toggleShowKey = () => {
|
|
setShowKey(!showKey);
|
|
};
|
|
|
|
const inputClass = `w-full px-3 py-2 pr-10 border rounded-lg text-sm transition-colors ${
|
|
disabled
|
|
? "bg-muted border-border-default text-muted-foreground cursor-not-allowed"
|
|
: "border-border-default bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20"
|
|
}`;
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<label htmlFor={id} className="block text-sm font-medium text-foreground">
|
|
{label} {required && "*"}
|
|
</label>
|
|
<div className="relative">
|
|
<input
|
|
type={showKey ? "text" : "password"}
|
|
id={id}
|
|
value={value}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
placeholder={placeholder ?? t("apiKeyInput.placeholder")}
|
|
disabled={disabled}
|
|
required={required}
|
|
autoComplete="off"
|
|
className={inputClass}
|
|
/>
|
|
{!disabled && value && (
|
|
<button
|
|
type="button"
|
|
onClick={toggleShowKey}
|
|
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
|
|
aria-label={showKey ? t("apiKeyInput.hide") : t("apiKeyInput.show")}
|
|
>
|
|
{showKey ? <EyeOff size={16} /> : <Eye size={16} />}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ApiKeyInput;
|