2025-10-16 21:40:42 +08:00
|
|
|
import { useTranslation } from "react-i18next";
|
2025-11-21 23:21:34 +08:00
|
|
|
import { useState } from "react";
|
2025-10-16 21:40:42 +08:00
|
|
|
import {
|
|
|
|
|
FormControl,
|
|
|
|
|
FormField,
|
|
|
|
|
FormItem,
|
|
|
|
|
FormLabel,
|
|
|
|
|
FormMessage,
|
|
|
|
|
} from "@/components/ui/form";
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
2025-11-21 23:21:34 +08:00
|
|
|
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";
|
2025-10-16 21:40:42 +08:00
|
|
|
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();
|
2025-11-21 23:21:34 +08:00
|
|
|
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 ?? "");
|
|
|
|
|
};
|
2025-10-16 21:40:42 +08:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<FormField
|
|
|
|
|
control={form.control}
|
|
|
|
|
name="name"
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
<FormItem>
|
2025-10-24 13:02:35 +08:00
|
|
|
<FormLabel>{t("provider.name")}</FormLabel>
|
2025-10-16 21:40:42 +08:00
|
|
|
<FormControl>
|
2025-10-24 13:02:35 +08:00
|
|
|
<Input {...field} placeholder={t("provider.namePlaceholder")} />
|
2025-10-16 21:40:42 +08:00
|
|
|
</FormControl>
|
|
|
|
|
<FormMessage />
|
|
|
|
|
</FormItem>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<FormField
|
|
|
|
|
control={form.control}
|
|
|
|
|
name="websiteUrl"
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
<FormItem>
|
2025-10-24 13:02:35 +08:00
|
|
|
<FormLabel>{t("provider.websiteUrl")}</FormLabel>
|
2025-10-16 21:40:42 +08:00
|
|
|
<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>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
2025-11-21 23:21:34 +08:00
|
|
|
|
|
|
|
|
{/* 图标配置 */}
|
|
|
|
|
<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>
|
2025-10-16 21:40:42 +08:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|