style(usage-script): unify form input styles with shadcn/ui components
Replace native HTML input elements with shadcn/ui Input and FormLabel components to ensure consistent styling across the application. Changes: - Import Input, FormLabel, Eye, and EyeOff components - Replace all credential input fields with Input component - Add show/hide toggle buttons for password fields (API Key, Access Token) - Replace label/span elements with FormLabel component - Update timeout and auto-query interval inputs to use Input component - Improve spacing consistency (space-y-4 for credential config) - Add proper id attributes for accessibility - Use muted-foreground for hint text The form now matches the styling of provider configuration forms throughout the application.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { Play, Wand2 } from "lucide-react";
|
||||
import { Play, Wand2, Eye, EyeOff } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Provider, UsageScript } from "@/types";
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { FormLabel } from "@/components/ui/form";
|
||||
|
||||
interface UsageScriptModalProps {
|
||||
provider: Provider;
|
||||
@@ -140,6 +142,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
},
|
||||
);
|
||||
|
||||
// 控制 API Key 的显示/隐藏
|
||||
const [showApiKey, setShowApiKey] = useState(false);
|
||||
const [showAccessToken, setShowAccessToken] = useState(false);
|
||||
|
||||
const handleSave = () => {
|
||||
// 验证脚本格式
|
||||
if (script.enabled && !script.code.trim()) {
|
||||
@@ -292,9 +298,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
<>
|
||||
{/* 预设模板选择 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100">
|
||||
<FormLabel className="mb-2">
|
||||
{t("usageScript.presetTemplate")}
|
||||
</label>
|
||||
</FormLabel>
|
||||
<div className="flex gap-2">
|
||||
{Object.keys(PRESET_TEMPLATES).map((name) => {
|
||||
const isSelected = selectedTemplate === name;
|
||||
@@ -317,93 +323,122 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
|
||||
{/* 凭证配置区域:通用和 NewAPI 模板显示 */}
|
||||
{shouldShowCredentialsConfig && (
|
||||
<div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
|
||||
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<div className="space-y-4 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{t("usageScript.credentialsConfig")}
|
||||
</h4>
|
||||
|
||||
{/* 通用模板:显示 apiKey + baseUrl */}
|
||||
{selectedTemplate === TEMPLATE_KEYS.GENERAL && (
|
||||
<>
|
||||
<label className="block">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="usage-api-key">
|
||||
API Key
|
||||
</span>
|
||||
<input
|
||||
type="password"
|
||||
value={script.apiKey || ""}
|
||||
onChange={(e) =>
|
||||
setScript({ ...script, apiKey: e.target.value })
|
||||
}
|
||||
placeholder="sk-xxxxx"
|
||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
||||
/>
|
||||
</label>
|
||||
</FormLabel>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="usage-api-key"
|
||||
type={showApiKey ? "text" : "password"}
|
||||
value={script.apiKey || ""}
|
||||
onChange={(e) =>
|
||||
setScript({ ...script, apiKey: e.target.value })
|
||||
}
|
||||
placeholder="sk-xxxxx"
|
||||
autoComplete="off"
|
||||
/>
|
||||
{script.apiKey && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowApiKey(!showApiKey)}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
|
||||
aria-label={showApiKey ? t("apiKeyInput.hide") : t("apiKeyInput.show")}
|
||||
>
|
||||
{showApiKey ? <EyeOff size={16} /> : <Eye size={16} />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="block">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="usage-base-url">
|
||||
Base URL
|
||||
</span>
|
||||
<input
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="usage-base-url"
|
||||
type="text"
|
||||
value={script.baseUrl || ""}
|
||||
onChange={(e) =>
|
||||
setScript({ ...script, baseUrl: e.target.value })
|
||||
}
|
||||
placeholder="https://api.example.com"
|
||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* NewAPI 模板:显示 baseUrl + accessToken + userId */}
|
||||
{selectedTemplate === TEMPLATE_KEYS.NEW_API && (
|
||||
<>
|
||||
<label className="block">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="usage-newapi-base-url">
|
||||
Base URL
|
||||
</span>
|
||||
<input
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="usage-newapi-base-url"
|
||||
type="text"
|
||||
value={script.baseUrl || ""}
|
||||
onChange={(e) =>
|
||||
setScript({ ...script, baseUrl: e.target.value })
|
||||
}
|
||||
placeholder="https://api.newapi.com"
|
||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="block">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="usage-access-token">
|
||||
{t("usageScript.accessToken")}
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
value={script.accessToken || ""}
|
||||
onChange={(e) =>
|
||||
setScript({ ...script, accessToken: e.target.value })
|
||||
}
|
||||
placeholder={t("usageScript.accessTokenPlaceholder")}
|
||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
||||
/>
|
||||
</label>
|
||||
</FormLabel>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="usage-access-token"
|
||||
type={showAccessToken ? "text" : "password"}
|
||||
value={script.accessToken || ""}
|
||||
onChange={(e) =>
|
||||
setScript({ ...script, accessToken: e.target.value })
|
||||
}
|
||||
placeholder={t("usageScript.accessTokenPlaceholder")}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{script.accessToken && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAccessToken(!showAccessToken)}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
|
||||
aria-label={showAccessToken ? t("apiKeyInput.hide") : t("apiKeyInput.show")}
|
||||
>
|
||||
{showAccessToken ? <EyeOff size={16} /> : <Eye size={16} />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="block">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="usage-user-id">
|
||||
{t("usageScript.userId")}
|
||||
</span>
|
||||
<input
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="usage-user-id"
|
||||
type="text"
|
||||
value={script.userId || ""}
|
||||
onChange={(e) =>
|
||||
setScript({ ...script, userId: e.target.value })
|
||||
}
|
||||
placeholder={t("usageScript.userIdPlaceholder")}
|
||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -411,9 +446,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
|
||||
{/* 脚本编辑器 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100">
|
||||
<FormLabel className="mb-2">
|
||||
{t("usageScript.queryScript")}
|
||||
</label>
|
||||
</FormLabel>
|
||||
<JsonEditor
|
||||
value={script.code}
|
||||
onChange={(code) => setScript({ ...script, code })}
|
||||
@@ -430,14 +465,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
|
||||
{/* 配置选项 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<label className="block">
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="usage-timeout">
|
||||
{t("usageScript.timeoutSeconds")}
|
||||
</span>
|
||||
<input
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="usage-timeout"
|
||||
type="number"
|
||||
min="2"
|
||||
max="30"
|
||||
min={2}
|
||||
max={30}
|
||||
value={script.timeout || 10}
|
||||
onChange={(e) =>
|
||||
setScript({
|
||||
@@ -445,20 +481,20 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
timeout: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 🆕 自动查询间隔 */}
|
||||
<label className="block">
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
<div className="space-y-2">
|
||||
<FormLabel htmlFor="usage-auto-interval">
|
||||
{t("usageScript.autoQueryInterval")}
|
||||
</span>
|
||||
<input
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="usage-auto-interval"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1440"
|
||||
step="1"
|
||||
min={0}
|
||||
max={1440}
|
||||
step={1}
|
||||
value={script.autoQueryInterval || 0}
|
||||
onChange={(e) =>
|
||||
setScript({
|
||||
@@ -466,12 +502,11 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
autoQueryInterval: parseInt(e.target.value) || 0,
|
||||
})
|
||||
}
|
||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("usageScript.autoQueryIntervalHint")}
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 脚本说明 */}
|
||||
|
||||
Reference in New Issue
Block a user