Files
cc-switch/src/components/providers/EditProviderDialog.tsx

157 lines
4.4 KiB
TypeScript
Raw Normal View History

import { useCallback, useEffect, useMemo, useState } from "react";
2025-10-16 10:49:56 +08:00
import { useTranslation } from "react-i18next";
import { Save } from "lucide-react";
2025-10-16 10:49:56 +08:00
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
2025-10-16 10:49:56 +08:00
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
2025-10-16 10:49:56 +08:00
import type { Provider } from "@/types";
import {
ProviderForm,
type ProviderFormValues,
} from "@/components/providers/forms/ProviderForm";
import { providersApi, vscodeApi, type AppId } from "@/lib/api";
2025-10-16 10:49:56 +08:00
interface EditProviderDialogProps {
open: boolean;
provider: Provider | null;
onOpenChange: (open: boolean) => void;
onSubmit: (provider: Provider) => Promise<void> | void;
appId: AppId;
2025-10-16 10:49:56 +08:00
}
export function EditProviderDialog({
open,
provider,
onOpenChange,
onSubmit,
appId,
2025-10-16 10:49:56 +08:00
}: EditProviderDialogProps) {
const { t } = useTranslation();
// 默认使用传入的 provider.settingsConfig若当前编辑对象是“当前生效供应商”则尝试读取实时配置替换初始值
const [liveSettings, setLiveSettings] = useState<Record<
string,
unknown
> | null>(null);
useEffect(() => {
let cancelled = false;
const load = async () => {
if (!open || !provider) {
setLiveSettings(null);
return;
}
try {
const currentId = await providersApi.getCurrent(appId);
if (currentId && provider.id === currentId) {
try {
const live = (await vscodeApi.getLiveProviderSettings(
appId,
)) as Record<string, unknown>;
if (!cancelled && live && typeof live === "object") {
setLiveSettings(live);
}
} catch {
// 读取实时配置失败则回退到 SSOT不打断编辑流程
if (!cancelled) setLiveSettings(null);
}
} else {
if (!cancelled) setLiveSettings(null);
}
} finally {
// no-op
}
};
void load();
return () => {
cancelled = true;
};
}, [open, provider, appId]);
const initialSettingsConfig = useMemo(() => {
return (liveSettings ?? provider?.settingsConfig ?? {}) as Record<
string,
unknown
>;
}, [liveSettings, provider]);
2025-10-16 10:49:56 +08:00
const handleSubmit = useCallback(
async (values: ProviderFormValues) => {
if (!provider) return;
const parsedConfig = JSON.parse(values.settingsConfig) as Record<
string,
unknown
>;
const updatedProvider: Provider = {
...provider,
name: values.name.trim(),
feat: add model configuration support and fix Gemini deeplink bug (#251) * feat(providers): add notes field for provider management - Add notes field to Provider model (backend and frontend) - Display notes with higher priority than URL in provider card - Style notes as non-clickable text to differentiate from URLs - Add notes input field in provider form - Add i18n support (zh/en) for notes field * chore: format code and clean up unused props - Run cargo fmt on Rust backend code - Format TypeScript imports and code style - Remove unused appId prop from ProviderPresetSelector - Clean up unused variables in tests - Integrate notes field handling in provider dialogs * feat(deeplink): implement ccswitch:// protocol for provider import Add deep link support to enable one-click provider configuration import via ccswitch:// URLs. Backend: - Implement URL parsing and validation (src-tauri/src/deeplink.rs) - Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs) - Register ccswitch:// protocol in macOS Info.plist - Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs) Frontend: - Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx) - Add API wrapper (src/lib/api/deeplink.ts) - Integrate event listeners in App.tsx Configuration: - Update Tauri config for deep link handling - Add i18n support for Chinese and English - Include test page for deep link validation (deeplink-test.html) Files: 15 changed, 1312 insertions(+) * chore(deeplink): integrate deep link handling into app lifecycle Wire up deep link infrastructure with app initialization and event handling. Backend Integration: - Register deep link module and commands in mod.rs - Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url) - Handle deep links from single instance callback (Windows/Linux CLI) - Handle deep links from macOS system events - Add tauri-plugin-deep-link dependency (Cargo.toml) Frontend Integration: - Listen for deeplink-import/deeplink-error events in App.tsx - Update DeepLinkImportDialog component imports Configuration: - Enable deep link plugin in tauri.conf.json - Update Cargo.lock for new dependencies Localization: - Add Chinese translations for deep link UI (zh.json) - Add English translations for deep link UI (en.json) Files: 9 changed, 359 insertions(+), 18 deletions(-) * refactor(deeplink): enhance Codex provider template generation Align deep link import with UI preset generation logic by: - Adding complete config.toml template matching frontend defaults - Generating safe provider name from sanitized input - Including model_provider, reasoning_effort, and wire_api settings - Removing minimal template that only contained base_url - Cleaning up deprecated test file deeplink-test.html * style: fix clippy uninlined_format_args warnings Apply clippy --fix to use inline format arguments in: - src/mcp.rs (8 fixes) - src/services/env_manager.rs (10 fixes) * style: apply code formatting and cleanup - Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts) - Organize Rust imports and module order alphabetically - Add newline at end of JSON files (en.json, zh.json) - Update Cargo.lock for dependency changes * feat: add model name configuration support for Codex and fix Gemini model handling - Add visual model name input field for Codex providers - Add model name extraction and update utilities in providerConfigUtils - Implement model name state management in useCodexConfigState hook - Add conditional model field rendering in CodexFormFields (non-official only) - Integrate model name sync with TOML config in ProviderForm - Fix Gemini deeplink model injection bug - Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL - Add test cases for Gemini model injection (with/without model) - All tests passing (9/9) - Fix Gemini model field binding in edit mode - Add geminiModel state to useGeminiConfigState hook - Extract model value during initialization and reset - Sync model field with geminiEnv state to prevent data loss on submit - Fix missing model value display when editing Gemini providers Changes: - 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
notes: values.notes?.trim() || undefined,
2025-10-16 10:49:56 +08:00
websiteUrl: values.websiteUrl?.trim() || undefined,
settingsConfig: parsedConfig,
style: restore original color scheme to shadcn/ui components Restore the vibrant color palette from the pre-refactoring version while maintaining shadcn/ui component architecture and modern design patterns. ## Color Scheme Restoration ### Button Component - **default variant**: Blue primary (`bg-blue-500`) - matches old `primary` - **destructive variant**: Red (`bg-red-500`) - matches old `danger` - **secondary variant**: Gray text (`text-gray-500`) - matches old `secondary` - **ghost variant**: Transparent hover (`hover:bg-gray-100`) - matches old `ghost` - **mcp variant**: Emerald green (`bg-emerald-500`) - matches old `mcp` - Updated border-radius to `rounded-lg` for consistency ### CSS Variables - Set `--primary` to blue (`hsl(217 91% 60%)` ≈ `bg-blue-500`) - Added complete shadcn/ui theme variables for light/dark modes - Maintained semantic color tokens for maintainability ### Component-Specific Colors - **"Currently Using" badge**: Green (`bg-green-500/10 text-green-500`) - **Delete button hover**: Red (`hover:text-red-500 hover:bg-red-100`) - **MCP button**: Emerald green with minimum width (`min-w-[80px]`) - **Links/URLs**: Blue (`text-blue-500`) ## Benefits - ✅ Restored original vibrant UI (blue, green, red accents) - ✅ Maintained shadcn/ui component system (accessibility, animations) - ✅ Easy global theming via CSS variables - ✅ Consistent design language across all components - ✅ Code formatted with Prettier (shadcn/ui standards) ## Files Changed - `src/index.css`: Added shadcn/ui theme variables with blue primary - `src/components/ui/button.tsx`: Restored all original button color variants - `src/components/providers/ProviderCard.tsx`: Green badge for current provider - `src/components/providers/ProviderActions.tsx`: Red hover for delete button - `src/components/mcp/McpPanel.tsx`: Use `mcp` variant for consistency - `src/App.tsx`: MCP button with emerald color and wider width The UI now matches the original colorful design while leveraging modern shadcn/ui components for better maintainability and user experience.
2025-10-16 15:32:26 +08:00
...(values.presetCategory ? { category: values.presetCategory } : {}),
// 保留或更新 meta 字段
...(values.meta ? { meta: values.meta } : {}),
2025-10-16 10:49:56 +08:00
};
await onSubmit(updatedProvider);
onOpenChange(false);
},
[onSubmit, onOpenChange, provider],
);
if (!provider) {
return null;
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl max-h-[85vh] min-h-[600px] flex flex-col">
2025-10-16 10:49:56 +08:00
<DialogHeader>
<DialogTitle>{t("provider.editProvider")}</DialogTitle>
2025-10-16 10:49:56 +08:00
<DialogDescription>
{t("provider.editProviderHint")}
2025-10-16 10:49:56 +08:00
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6 py-4">
<ProviderForm
appId={appId}
refactor(endpoints): implement deferred submission and fix clear-all bug Implement Solution A (complete deferred submission) for custom endpoint management, replacing the dual-mode system with unified local staging. Changes: - Remove immediate backend saves from EndpointSpeedTest * handleAddEndpoint: local state update only * handleRemoveEndpoint: local state update only * handleSelect: remove lastUsed timestamp update - Add explicit clear detection in ProviderForm * Distinguish "user cleared endpoints" from "user didn't modify" * Pass empty object {} as clear signal vs null for no-change - Fix mergeProviderMeta to handle three distinct cases: * null/undefined: don't modify endpoints (no meta sent) * empty object {}: explicitly clear endpoints (send empty meta) * with data: add/update endpoints (overwrite) Fixed Critical Bug: When users deleted all custom endpoints, changes were not saved because: - draftCustomEndpoints=[] resulted in customEndpointsToSave=null - mergeProviderMeta(meta, null) returned undefined - Backend interpreted missing meta as "don't modify", preserving old values Solution: Detect when user had endpoints and cleared them (hadEndpoints && length===0), then pass empty object to mergeProviderMeta as explicit clear signal. Architecture Improvements: - Transaction atomicity: all fields submitted together on form save - UX consistency: add/edit modes behave identically - Cancel button: true rollback with no immediate saves - Code simplification: removed ~40 lines of immediate save error handling Testing: - TypeScript type check: passed - Rust backend tests: 10/10 passed - Build: successful
2025-11-04 15:30:54 +08:00
providerId={provider.id}
submitLabel={t("common.save")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={{
name: provider.name,
feat: add model configuration support and fix Gemini deeplink bug (#251) * feat(providers): add notes field for provider management - Add notes field to Provider model (backend and frontend) - Display notes with higher priority than URL in provider card - Style notes as non-clickable text to differentiate from URLs - Add notes input field in provider form - Add i18n support (zh/en) for notes field * chore: format code and clean up unused props - Run cargo fmt on Rust backend code - Format TypeScript imports and code style - Remove unused appId prop from ProviderPresetSelector - Clean up unused variables in tests - Integrate notes field handling in provider dialogs * feat(deeplink): implement ccswitch:// protocol for provider import Add deep link support to enable one-click provider configuration import via ccswitch:// URLs. Backend: - Implement URL parsing and validation (src-tauri/src/deeplink.rs) - Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs) - Register ccswitch:// protocol in macOS Info.plist - Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs) Frontend: - Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx) - Add API wrapper (src/lib/api/deeplink.ts) - Integrate event listeners in App.tsx Configuration: - Update Tauri config for deep link handling - Add i18n support for Chinese and English - Include test page for deep link validation (deeplink-test.html) Files: 15 changed, 1312 insertions(+) * chore(deeplink): integrate deep link handling into app lifecycle Wire up deep link infrastructure with app initialization and event handling. Backend Integration: - Register deep link module and commands in mod.rs - Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url) - Handle deep links from single instance callback (Windows/Linux CLI) - Handle deep links from macOS system events - Add tauri-plugin-deep-link dependency (Cargo.toml) Frontend Integration: - Listen for deeplink-import/deeplink-error events in App.tsx - Update DeepLinkImportDialog component imports Configuration: - Enable deep link plugin in tauri.conf.json - Update Cargo.lock for new dependencies Localization: - Add Chinese translations for deep link UI (zh.json) - Add English translations for deep link UI (en.json) Files: 9 changed, 359 insertions(+), 18 deletions(-) * refactor(deeplink): enhance Codex provider template generation Align deep link import with UI preset generation logic by: - Adding complete config.toml template matching frontend defaults - Generating safe provider name from sanitized input - Including model_provider, reasoning_effort, and wire_api settings - Removing minimal template that only contained base_url - Cleaning up deprecated test file deeplink-test.html * style: fix clippy uninlined_format_args warnings Apply clippy --fix to use inline format arguments in: - src/mcp.rs (8 fixes) - src/services/env_manager.rs (10 fixes) * style: apply code formatting and cleanup - Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts) - Organize Rust imports and module order alphabetically - Add newline at end of JSON files (en.json, zh.json) - Update Cargo.lock for dependency changes * feat: add model name configuration support for Codex and fix Gemini model handling - Add visual model name input field for Codex providers - Add model name extraction and update utilities in providerConfigUtils - Implement model name state management in useCodexConfigState hook - Add conditional model field rendering in CodexFormFields (non-official only) - Integrate model name sync with TOML config in ProviderForm - Fix Gemini deeplink model injection bug - Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL - Add test cases for Gemini model injection (with/without model) - All tests passing (9/9) - Fix Gemini model field binding in edit mode - Add geminiModel state to useGeminiConfigState hook - Extract model value during initialization and reset - Sync model field with geminiEnv state to prevent data loss on submit - Fix missing model value display when editing Gemini providers Changes: - 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
notes: provider.notes,
websiteUrl: provider.websiteUrl,
// 若读取到实时配置则优先使用
settingsConfig: initialSettingsConfig,
category: provider.category,
meta: provider.meta,
}}
showButtons={false}
/>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
{t("common.cancel")}
</Button>
<Button type="submit" form="provider-form">
<Save className="h-4 w-4" />
{t("common.save")}
</Button>
</DialogFooter>
2025-10-16 10:49:56 +08:00
</DialogContent>
</Dialog>
);
}