TradingAgents/frontend/components/settings/ApiSettingsDialog.tsx

326 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* API Settings Dialog Component
*/
"use client";
import { useState, useEffect } from "react";
import { Settings } from "lucide-react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
getApiSettings,
saveApiSettings,
clearApiSettings,
type ApiSettings,
DEFAULT_API_SETTINGS,
} from "@/lib/storage";
const formSchema = z.object({
// Required
openai_api_key: z.string().min(1, "OpenAI API Key 為必填"),
alpha_vantage_api_key: z.string().min(1, "Alpha Vantage API Key 為必填"),
// Optional
anthropic_api_key: z.string().optional().or(z.literal("")),
google_api_key: z.string().optional().or(z.literal("")),
grok_api_key: z.string().optional().or(z.literal("")),
deepseek_api_key: z.string().optional().or(z.literal("")),
qwen_api_key: z.string().optional().or(z.literal("")),
// Custom endpoint
custom_base_url: z.string().optional().or(z.literal("")),
custom_api_key: z.string().optional().or(z.literal("")),
});
type FormValues = z.infer<typeof formSchema>;
export function ApiSettingsDialog() {
const [open, setOpen] = useState(false);
const [saveSuccess, setSaveSuccess] = useState(false);
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: getApiSettings(),
});
// Load settings when dialog opens
useEffect(() => {
if (open) {
const settings = getApiSettings();
form.reset(settings);
setSaveSuccess(false);
}
}, [open, form]);
const onSubmit = (values: FormValues) => {
try {
// Type assertion since our form values match ApiSettings structure
saveApiSettings(values as ApiSettings);
setSaveSuccess(true);
setTimeout(() => {
setSaveSuccess(false);
setOpen(false);
}, 1500);
} catch (error) {
console.error("Failed to save settings:", error);
}
};
const handleClear = () => {
clearApiSettings();
form.reset(DEFAULT_API_SETTINGS);
};
return (
<Dialog open={open} onOpenChange={setOpen}>
{/* @ts-ignore - React 19 type compatibility issue with Radix UI */}
<DialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-white hover:bg-white/20"
title="API 設定"
>
<Settings className="h-5 w-5" />
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>API </DialogTitle>
<DialogDescription>
API
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{/* Required Section */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-primary"></h3>
{/* OpenAI API Key */}
<FormField
control={form.control}
name="openai_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>OpenAI API Key *</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription>
OpenAI GPT-4, GPT-5, o4
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Alpha Vantage API Key */}
<FormField
control={form.control}
name="alpha_vantage_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>Alpha Vantage API Key *</FormLabel>
<FormControl>
<Input
type="password"
placeholder="輸入 Alpha Vantage API Key"
{...field}
/>
</FormControl>
<FormDescription>
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Optional Section */}
<div className="space-y-4 border-t pt-4">
<h3 className="text-lg font-semibold text-muted-foreground">
</h3>
{/* Anthropic API Key */}
<FormField
control={form.control}
name="anthropic_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>Anthropic API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription> Claude </FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Google API Key */}
<FormField
control={form.control}
name="google_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>Google API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="..." {...field} />
</FormControl>
<FormDescription> Gemini </FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Grok API Key */}
<FormField
control={form.control}
name="grok_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>Grok (xAI) API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="xai-..." {...field} />
</FormControl>
<FormDescription> Grok </FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* DeepSeek API Key */}
<FormField
control={form.control}
name="deepseek_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>DeepSeek API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription> DeepSeek </FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Qwen API Key */}
<FormField
control={form.control}
name="qwen_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>Qwen (Alibaba) API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription> Qwen </FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Custom Endpoint Section */}
<div className="space-y-4 border-t pt-4">
<h3 className="text-lg font-semibold text-muted-foreground">
</h3>
{/* Custom Base URL */}
<FormField
control={form.control}
name="custom_base_url"
render={({ field }) => (
<FormItem>
<FormLabel> Base URL</FormLabel>
<FormControl>
<Input
type="text"
placeholder="https://your-custom-endpoint.com/v1"
{...field}
/>
</FormControl>
<FormDescription>
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Custom API Key */}
<FormField
control={form.control}
name="custom_api_key"
render={({ field }) => (
<FormItem>
<FormLabel> API Key</FormLabel>
<FormControl>
<Input
type="password"
placeholder="輸入自訂端點的 API Key"
{...field}
/>
</FormControl>
<FormDescription>
Base URL 使 API Key
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
{saveSuccess && (
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-3 text-green-800 dark:text-green-300 text-sm">
</div>
)}
<div className="flex gap-2 pt-4">
<Button type="submit" className="flex-1">
</Button>
<Button
type="button"
variant="outline"
onClick={handleClear}
className="flex-1"
>
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
}