Files
cc-switch/src/components/providers/forms/BasicFormFields.tsx

173 lines
5.7 KiB
TypeScript
Raw Normal View History

import { useTranslation } from "react-i18next";
import { useState } from "react";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { ArrowLeft } from "lucide-react";
import {
Dialog,
DialogContent,
DialogTrigger,
DialogClose,
} from "@/components/ui/dialog";
import { ProviderIcon } from "@/components/ProviderIcon";
import { IconPicker } from "@/components/IconPicker";
import { getIconMetadata } from "@/icons/extracted/metadata";
import type { UseFormReturn } from "react-hook-form";
import type { ProviderFormData } from "@/lib/schemas/provider";
interface BasicFormFieldsProps {
form: UseFormReturn<ProviderFormData>;
}
export function BasicFormFields({ form }: BasicFormFieldsProps) {
const { t } = useTranslation();
const [iconDialogOpen, setIconDialogOpen] = useState(false);
const currentIcon = form.watch("icon");
const currentIconColor = form.watch("iconColor");
const providerName = form.watch("name") || "Provider";
const effectiveIconColor =
currentIconColor ||
(currentIcon ? getIconMetadata(currentIcon)?.defaultColor : undefined);
const handleIconSelect = (icon: string) => {
const meta = getIconMetadata(icon);
form.setValue("icon", icon);
form.setValue("iconColor", meta?.defaultColor ?? "");
};
return (
<>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("provider.name")}</FormLabel>
<FormControl>
<Input {...field} placeholder={t("provider.namePlaceholder")} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="websiteUrl"
render={({ field }) => (
<FormItem>
<FormLabel>{t("provider.websiteUrl")}</FormLabel>
<FormControl>
<Input {...field} placeholder="https://" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
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
<FormField
control={form.control}
name="notes"
render={({ field }) => (
<FormItem>
<FormLabel>{t("provider.notes")}</FormLabel>
<FormControl>
<Input {...field} placeholder={t("provider.notesPlaceholder")} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* 图标配置 */}
<div className="space-y-3">
<FormLabel>
{t("providerIcon.label", { defaultValue: "图标" })}
</FormLabel>
{/* 图标预览 */}
<div className="flex items-center gap-4 p-4 rounded-lg bg-muted">
<ProviderIcon
icon={currentIcon}
name={providerName}
color={effectiveIconColor}
size={48}
/>
<div className="flex-1">
<p className="font-medium">{providerName}</p>
<p className="text-sm text-muted-foreground">
{currentIcon ||
t("providerIcon.noIcon", { defaultValue: "未选择图标" })}
</p>
</div>
<Dialog open={iconDialogOpen} onOpenChange={setIconDialogOpen}>
<DialogTrigger asChild>
<Button type="button" variant="outline">
{t("providerIcon.selectIcon", { defaultValue: "选择图标" })}
</Button>
</DialogTrigger>
<DialogContent
variant="fullscreen"
zIndex="top"
overlayClassName="bg-[hsl(var(--background))] backdrop-blur-0"
className="p-0 sm:rounded-none"
>
<div className="flex h-full flex-col">
<div className="flex-shrink-0 px-6 py-4 flex items-center gap-4 border-b border-border-default bg-muted/40">
<DialogClose asChild>
<Button
type="button"
variant="ghost"
size="icon"
className="hover:bg-black/5 dark:hover:bg-white/5"
>
<ArrowLeft className="h-5 w-5" />
</Button>
</DialogClose>
<div className="space-y-1">
<p className="text-lg font-semibold leading-tight">
{t("providerIcon.selectIcon", {
defaultValue: "选择图标",
})}
</p>
<p className="text-sm text-muted-foreground">
{t("providerIcon.selectDescription", {
defaultValue: "为供应商选择一个图标",
})}
</p>
</div>
</div>
<div className="flex-1 overflow-y-auto px-6 py-6">
<div className="space-y-6 max-w-5xl mx-auto w-full">
{/* 图标选择器 */}
<IconPicker
value={currentIcon}
onValueChange={handleIconSelect}
color={effectiveIconColor}
/>
<div className="flex justify-end gap-2">
<DialogClose asChild>
<Button type="button" variant="outline">
{t("common.done", { defaultValue: "完成" })}
</Button>
</DialogClose>
</div>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</>
);
}