/** * Analysis form component */ "use client"; import { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { format } from "date-fns"; import { CheckIcon } from "lucide-react"; import { getApiSettings } from "@/lib/storage"; import { getBaseUrlForModel, getApiKeyForModel } from "@/lib/api-helpers"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { DatePicker } from "@/components/ui/date-picker"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import type { AnalysisRequest } from "@/lib/types"; const formSchema = z.object({ ticker: z.string().min(1, "股票代碼為必填").max(10), analysis_date: z .string() .regex(/^\d{4}-\d{2}-\d{2}$/, "日期格式必須為 YYYY-MM-DD"), analysts: z.array(z.string()).min(1, "請至少選擇一位分析師"), research_depth: z.number().int().min(1).max(5), quick_think_llm: z.string().min(1, "請選擇快速思維模型"), deep_think_llm: z.string().min(1, "請選擇深層思維模型"), // API Configuration (hidden from UI, populated from localStorage) quick_think_base_url: z .string() .url("請輸入有效的 URL") .optional() .or(z.literal("")), deep_think_base_url: z .string() .url("請輸入有效的 URL") .optional() .or(z.literal("")), quick_think_api_key: z.string().min(1, "請輸入快速思維模型 API Key"), deep_think_api_key: z.string().min(1, "請輸入深層思維模型 API Key"), embedding_base_url: z .string() .url("請輸入有效的 URL") .optional() .or(z.literal("")), embedding_api_key: z.string().min(1, "請輸入嵌入模型 API Key"), alpha_vantage_api_key: z.string().min(1, "請輸入 Alpha Vantage API Key"), }); interface AnalysisFormProps { onSubmit: (data: AnalysisRequest) => void; loading?: boolean; } const ANALYSTS = [ { value: "market", label: "市場分析師" }, { value: "social", label: "社群媒體分析師" }, { value: "news", label: "新聞分析師" }, { value: "fundamentals", label: "基本面分析師" }, ]; export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { ticker: "NVDA", analysis_date: format(new Date(), "yyyy-MM-dd"), analysts: ["market", "social", "news", "fundamentals"], // 預設全選 research_depth: 3, // 預設中等層級 quick_think_llm: "gpt-5-mini-2025-08-07", deep_think_llm: "gpt-5-mini-2025-08-07", quick_think_base_url: "https://api.openai.com/v1", deep_think_base_url: "https://api.openai.com/v1", quick_think_api_key: "", deep_think_api_key: "", embedding_base_url: "https://api.openai.com/v1", embedding_api_key: "", alpha_vantage_api_key: "", }, }); // Load API settings from localStorage and update when models change useEffect(() => { const savedSettings = getApiSettings(); const quickThinkLlm = form.getValues("quick_think_llm"); const deepThinkLlm = form.getValues("deep_think_llm"); // Set base URLs based on selected models (custom URL takes precedence) form.setValue("quick_think_base_url", getBaseUrlForModel(quickThinkLlm, savedSettings.custom_base_url)); form.setValue("deep_think_base_url", getBaseUrlForModel(deepThinkLlm, savedSettings.custom_base_url)); form.setValue("embedding_base_url", savedSettings.custom_base_url || "https://api.openai.com/v1"); // Set API keys based on selected models form.setValue("quick_think_api_key", getApiKeyForModel(quickThinkLlm, savedSettings)); form.setValue("deep_think_api_key", getApiKeyForModel(deepThinkLlm, savedSettings)); form.setValue("embedding_api_key", savedSettings.custom_api_key || savedSettings.openai_api_key); form.setValue("alpha_vantage_api_key", savedSettings.alpha_vantage_api_key); }, [form, form.watch("quick_think_llm"), form.watch("deep_think_llm")]); // 全選/取消全選 const toggleSelectAll = () => { const currentAnalysts = form.getValues("analysts"); if (currentAnalysts.length === ANALYSTS.length) { form.setValue("analysts", []); } else { form.setValue( "analysts", ANALYSTS.map((a) => a.value) ); } }; function handleSubmit(values: z.infer) { const request: AnalysisRequest = { ...values, }; onSubmit(request); } return (
{/* 分析師選擇區塊 - 全寬 */}
分析師團隊
(
{ANALYSTS.map((analyst) => { const isSelected = field.value?.includes(analyst.value); return (
{ const newValue = isSelected ? field.value?.filter((v: string) => v !== analyst.value) : [...(field.value ?? []), analyst.value]; field.onChange(newValue); }} className={cn( "relative flex cursor-pointer flex-row items-center gap-3 rounded-lg border-2 p-4 transition-all hover:bg-accent", isSelected ? "border-primary bg-primary/5 text-primary" : "border-muted-foreground/25 bg-card text-muted-foreground" )} >
{isSelected && }
{analyst.label}
); })}
)} />
{/* 第一行:股票代碼、分析日期(2列) */}
( 股票代碼 輸入股票代碼(例如:NVDA、AAPL) )} /> ( 分析日期 { field.onChange(date ? format(date, "yyyy-MM-dd") : "") }} placeholder="選擇分析日期" className="w-full" /> 選擇分析日期 )} />
{/* 第二行:研究深度、快速思維模型、深層思維模型(3列) */}
( 研究深度 選擇分析深度 )} /> ( 快速思維模型 快速回應模型 )} /> ( 深層思維模型 複雜推理模型 )} />
); }