TradingAgents/frontend/components/settings/ApiSettingsDialog.tsx

270 lines
8.5 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("")),
});
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}>
<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>
{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>
);
}