feat(deeplink): add Claude model fields support and enhance import dialog

- Add Claude-specific model field support in deeplink import:
  * Support model (ANTHROPIC_MODEL) - general default model
  * Support haikuModel (ANTHROPIC_DEFAULT_HAIKU_MODEL)
  * Support sonnetModel (ANTHROPIC_DEFAULT_SONNET_MODEL)
  * Support opusModel (ANTHROPIC_DEFAULT_OPUS_MODEL)
  * Backend: Update DeepLinkImportRequest struct to include optional model fields
  * Frontend: Add TypeScript type definitions for new model parameters

- Enhance deeplink demo page (deplink.html):
  * Add 5 new Claude configuration examples showcasing different model setups
  * Add parameter documentation with required/optional tags
  * Include basic config (no models), single model, complete 4-model, partial models, and third-party provider examples
  * Improve visual design with param-list component and color-coded badges
  * Add detailed descriptions for each configuration scenario

- Redesign DeepLinkImportDialog layout:
  * Switch from 3-column to compact 2-column grid layout
  * Reduce dialog width from 500px to 650px for better content display
  * Add dedicated section for Claude model configurations with blue highlight box
  * Use uppercase labels and smaller text for more information density
  * Add truncation and tooltips for long URLs
  * Improve visual hierarchy with spacing and grouping
  * Increase z-index to 9999 to ensure dialog appears on top

- Minor UI refinements:
  * Update App.tsx layout adjustments
  * Optimize McpFormModal styling
  * Refine ProviderCard and BasicFormFields components

This enables users to import Claude providers with precise model configurations via deeplink.
This commit is contained in:
YoVinchen
2025-11-22 03:26:28 +08:00
parent 127fa5bf9d
commit 1a89267986
8 changed files with 402 additions and 76 deletions

View File

@@ -509,8 +509,9 @@ function App() {
</header>
<main
className={`flex-1 overflow-y-auto pb-12 animate-fade-in scroll-overlay ${currentView === "providers" ? "pt-24" : "pt-20"
}`}
className={`flex-1 overflow-y-auto pb-12 animate-fade-in scroll-overlay ${
currentView === "providers" ? "pt-24" : "pt-20"
}`}
style={{ overflowX: "hidden" }}
>
{renderContent()}
@@ -553,8 +554,8 @@ function App() {
message={
confirmDelete
? t("confirm.deleteProviderMessage", {
name: confirmDelete.name,
})
name: confirmDelete.name,
})
: ""
}
onConfirm={() => void handleConfirmDelete()}

View File

@@ -96,8 +96,8 @@ export function DeepLinkImportDialog() {
: "****";
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="sm:max-w-[500px]">
<Dialog open={isOpen} onOpenChange={setIsOpen} modal={true}>
<DialogContent className="sm:max-w-[650px] z-[9999]">
{/* 标题显式左对齐,避免默认居中样式影响 */}
<DialogHeader className="text-left sm:text-left">
<DialogTitle>{t("deeplink.confirmImport")}</DialogTitle>
@@ -106,82 +106,120 @@ export function DeepLinkImportDialog() {
</DialogDescription>
</DialogHeader>
{/* 主体内容整体右移,略大于标题内边距,让内容看起来不贴边 */}
<div className="space-y-4 px-8 py-4">
{/* App Type */}
<div className="grid grid-cols-3 items-center gap-4">
<div className="font-medium text-sm text-muted-foreground">
{t("deeplink.app")}
{/* 使用两列布局压缩内容 */}
<div className="space-y-4 px-4 py-3">
{/* 第一行:应用类型 + 供应商名称 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<div className="font-medium text-xs text-muted-foreground uppercase">
{t("deeplink.app")}
</div>
<div className="text-sm font-medium capitalize">
{request.app}
</div>
</div>
<div className="col-span-2 text-sm font-medium capitalize">
{request.app}
<div className="space-y-1">
<div className="font-medium text-xs text-muted-foreground uppercase">
{t("deeplink.providerName")}
</div>
<div className="text-sm font-medium truncate" title={request.name}>
{request.name}
</div>
</div>
</div>
{/* Provider Name */}
<div className="grid grid-cols-3 items-center gap-4">
<div className="font-medium text-sm text-muted-foreground">
{t("deeplink.providerName")}
{/* 第二行:官网 + 端点 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<div className="font-medium text-xs text-muted-foreground uppercase">
{t("deeplink.homepage")}
</div>
<div className="text-xs break-all text-blue-600 dark:text-blue-400 line-clamp-2" title={request.homepage}>
{request.homepage}
</div>
</div>
<div className="col-span-2 text-sm font-medium">{request.name}</div>
</div>
{/* Homepage */}
<div className="grid grid-cols-3 items-center gap-4">
<div className="font-medium text-sm text-muted-foreground">
{t("deeplink.homepage")}
</div>
<div className="col-span-2 text-sm break-all text-blue-600 dark:text-blue-400">
{request.homepage}
<div className="space-y-1">
<div className="font-medium text-xs text-muted-foreground uppercase">
{t("deeplink.endpoint")}
</div>
<div className="text-xs break-all line-clamp-2" title={request.endpoint}>
{request.endpoint}
</div>
</div>
</div>
{/* API Endpoint */}
<div className="grid grid-cols-3 items-center gap-4">
<div className="font-medium text-sm text-muted-foreground">
{t("deeplink.endpoint")}
</div>
<div className="col-span-2 text-sm break-all">
{request.endpoint}
</div>
</div>
{/* API Key (masked) */}
<div className="grid grid-cols-3 items-center gap-4">
<div className="font-medium text-sm text-muted-foreground">
{/* 第三行API Key */}
<div className="space-y-1">
<div className="font-medium text-xs text-muted-foreground uppercase">
{t("deeplink.apiKey")}
</div>
<div className="col-span-2 text-sm font-mono text-muted-foreground">
<div className="text-sm font-mono text-muted-foreground">
{maskedApiKey}
</div>
</div>
{/* Model (if present) */}
{/* 第四行:默认模型(如果有) */}
{request.model && (
<div className="grid grid-cols-3 items-center gap-4">
<div className="font-medium text-sm text-muted-foreground">
<div className="space-y-1">
<div className="font-medium text-xs text-muted-foreground uppercase">
{t("deeplink.model")}
</div>
<div className="col-span-2 text-sm font-mono">
{request.model}
<div className="text-sm font-mono">{request.model}</div>
</div>
)}
{/* Claude 专用模型字段(紧凑布局) */}
{request.app === "claude" && (request.haikuModel || request.sonnetModel || request.opusModel) && (
<div className="rounded-lg bg-blue-50 dark:bg-blue-900/20 p-3 space-y-2">
<div className="font-medium text-xs text-blue-900 dark:text-blue-100 uppercase">
{t("deeplink.claudeModels", "Claude 模型配置")}
</div>
<div className="grid grid-cols-3 gap-2 text-xs">
{request.haikuModel && (
<div>
<span className="text-muted-foreground">Haiku:</span>
<div className="font-mono truncate" title={request.haikuModel}>
{request.haikuModel}
</div>
</div>
)}
{request.sonnetModel && (
<div>
<span className="text-muted-foreground">Sonnet:</span>
<div className="font-mono truncate" title={request.sonnetModel}>
{request.sonnetModel}
</div>
</div>
)}
{request.opusModel && (
<div>
<span className="text-muted-foreground">Opus:</span>
<div className="font-mono truncate" title={request.opusModel}>
{request.opusModel}
</div>
</div>
)}
</div>
</div>
)}
{/* Notes (if present) */}
{/* 备注(如果有) */}
{request.notes && (
<div className="grid grid-cols-3 items-start gap-4">
<div className="font-medium text-sm text-muted-foreground">
<div className="space-y-1">
<div className="font-medium text-xs text-muted-foreground uppercase">
{t("deeplink.notes")}
</div>
<div className="col-span-2 text-sm text-muted-foreground">
<div className="text-sm text-muted-foreground line-clamp-2" title={request.notes}>
{request.notes}
</div>
</div>
)}
{/* Warning */}
<div className="rounded-lg bg-yellow-50 dark:bg-yellow-900/20 p-3 text-sm text-yellow-800 dark:text-yellow-200">
{/* 警告提示(紧凑版) */}
<div className="rounded-lg bg-yellow-50 dark:bg-yellow-900/20 p-2 text-xs text-yellow-800 dark:text-yellow-200">
{t("deeplink.warning")}
</div>
</div>

View File

@@ -440,10 +440,11 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
<button
type="button"
onClick={applyCustom}
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${selectedPreset === -1
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
selectedPreset === -1
? "bg-emerald-500 text-white dark:bg-emerald-600"
: "bg-accent text-muted-foreground hover:bg-accent/80"
}`}
}`}
>
{t("presetSelector.custom")}
</button>
@@ -454,10 +455,11 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
key={preset.id}
type="button"
onClick={() => applyPreset(idx)}
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${selectedPreset === idx
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
selectedPreset === idx
? "bg-emerald-500 text-white dark:bg-emerald-600"
: "bg-accent text-muted-foreground hover:bg-accent/80"
}`}
}`}
title={t(descriptionKey)}
>
{preset.id}
@@ -631,9 +633,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm font-medium text-foreground">
{useToml
? t("mcp.form.tomlConfig")
: t("mcp.form.jsonConfig")}
{useToml ? t("mcp.form.tomlConfig") : t("mcp.form.jsonConfig")}
</label>
{(isEditing || selectedPreset === -1) && (
<button

View File

@@ -120,7 +120,7 @@ export function ProviderCard({
? "border-primary/50 bg-primary/5 shadow-[0_0_20px_rgba(59,130,246,0.15)]"
: "hover:scale-[1.01]",
dragHandleProps?.isDragging &&
"cursor-grabbing border-primary shadow-lg scale-105 z-10",
"cursor-grabbing border-primary shadow-lg scale-105 z-10",
)}
>
<div className="absolute inset-0 bg-gradient-to-r from-primary/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none" />

View File

@@ -130,7 +130,10 @@ export function BasicFormFields({ form }: BasicFormFieldsProps) {
<FormItem>
<FormLabel>{t("provider.notes")}</FormLabel>
<FormControl>
<Input {...field} placeholder={t("provider.notesPlaceholder")} />
<Input
{...field}
placeholder={t("provider.notesPlaceholder")}
/>
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -10,6 +10,10 @@ export interface DeepLinkImportRequest {
apiKey: string;
model?: string;
notes?: string;
// Claude 专用模型字段 (v3.7.1+)
haikuModel?: string;
sonnetModel?: string;
opusModel?: string;
}
export const deeplinkApi = {