From a0e4365fc37082a886fe4f9e8d4737166ade90c2 Mon Sep 17 00:00:00 2001 From: MarkLo Date: Sat, 6 Dec 2025 01:50:41 +0800 Subject: [PATCH] --- frontend/components/analysis/AnalysisForm.tsx | 317 ++---------------- frontend/components/layout/Header.tsx | 2 + .../components/settings/ApiSettingsDialog.tsx | 269 +++++++++++++++ frontend/lib/api-helpers.ts | 92 +++++ frontend/lib/storage.ts | 81 +++++ 5 files changed, 466 insertions(+), 295 deletions(-) create mode 100644 frontend/components/settings/ApiSettingsDialog.tsx create mode 100644 frontend/lib/api-helpers.ts create mode 100644 frontend/lib/storage.ts diff --git a/frontend/components/analysis/AnalysisForm.tsx b/frontend/components/analysis/AnalysisForm.tsx index d4643adf..4935d1c0 100644 --- a/frontend/components/analysis/AnalysisForm.tsx +++ b/frontend/components/analysis/AnalysisForm.tsx @@ -3,12 +3,14 @@ */ "use client"; -import { useState } from "react"; +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"; @@ -51,7 +53,7 @@ const formSchema = z.object({ quick_think_llm: z.string().min(1, "請選擇快速思維模型"), deep_think_llm: z.string().min(1, "請選擇深層思維模型"), - // API Configuration + // API Configuration (hidden from UI, populated from localStorage) quick_think_base_url: z .string() .url("請輸入有效的 URL") @@ -105,6 +107,24 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) { }, }); + // 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 + form.setValue("quick_think_base_url", getBaseUrlForModel(quickThinkLlm)); + form.setValue("deep_think_base_url", getBaseUrlForModel(deepThinkLlm)); + form.setValue("embedding_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.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"); @@ -505,299 +525,6 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) { - {/* API Configuration Section */} -
-

API 配置

- - ( - - 快速思維模型 Base URL - - - {(![ - "https://api.openai.com/v1", - "https://api.anthropic.com", - "https://generativelanguage.googleapis.com/v1beta/openai", - "https://api.x.ai/v1", - "https://api.deepseek.com/v1", - "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - ].includes(field.value || "") || - field.value === "") && ( -
- - - -
- )} - - - 快速思維模型的 API 基礎網址 - - -
- )} - /> - - ( - - 快速思維模型 API Key * - - - - - 該模型的專屬 API Key(必填) - - - - )} - /> - - ( - - 深層思維模型 Base URL - - - {(![ - "https://api.openai.com/v1", - "https://api.anthropic.com", - "https://generativelanguage.googleapis.com/v1beta/openai", - "https://api.x.ai/v1", - "https://api.deepseek.com/v1", - "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - ].includes(field.value || "") || - field.value === "") && ( -
- - - -
- )} - - - 深層思維模型的 API 基礎網址 - - -
- )} - /> - - ( - - 深層思維模型 API Key * - - - - - 該模型的專屬 API Key(必填) - - - - )} - /> - - ( - - 嵌入模型 Base URL - - - {field.value !== "https://api.openai.com/v1" && ( -
- - - -
- )} - - - 嵌入向量生成的 API 端點(用於記憶體系統) - - -
- )} - /> - - ( - - 嵌入模型 API Key * - - - - 該端點的 API Key(必填) - - - )} - /> - - ( - - Alpha Vantage API Key * - - - - - 用於獲取市場基本面數據(必填) - - - - )} - /> -
- + + + + API 配置 + + 設定您的 API 金鑰。這些資訊會儲存在瀏覽器的本機儲存空間中。 + + + +
+ + {/* Required Section */} +
+

必填項目

+ + {/* OpenAI API Key */} + ( + + OpenAI API Key * + + + + + 用於 OpenAI 模型(GPT-4, GPT-5, o4 等) + + + + )} + /> + + {/* Alpha Vantage API Key */} + ( + + Alpha Vantage API Key * + + + + + 用於獲取市場基本面數據(必填) + + + + )} + /> +
+ + {/* Optional Section */} +
+

+ 選填項目(依需求填寫) +

+ + {/* Anthropic API Key */} + ( + + Anthropic API Key + + + + 用於 Claude 模型 + + + )} + /> + + {/* Google API Key */} + ( + + Google API Key + + + + 用於 Gemini 模型 + + + )} + /> + + {/* Grok API Key */} + ( + + Grok (xAI) API Key + + + + 用於 Grok 模型 + + + )} + /> + + {/* DeepSeek API Key */} + ( + + DeepSeek API Key + + + + 用於 DeepSeek 模型 + + + )} + /> + + {/* Qwen API Key */} + ( + + Qwen (Alibaba) API Key + + + + 用於 Qwen 模型 + + + )} + /> +
+ + {saveSuccess && ( +
+ ✓ 設定已成功儲存 +
+ )} + +
+ + +
+
+ +
+ + ); +} diff --git a/frontend/lib/api-helpers.ts b/frontend/lib/api-helpers.ts new file mode 100644 index 00000000..df784d85 --- /dev/null +++ b/frontend/lib/api-helpers.ts @@ -0,0 +1,92 @@ +/** + * Helper functions for API configuration + */ + +import { ApiSettings } from "./storage"; + +/** + * Get the base URL for a given LLM model + */ +export function getBaseUrlForModel(model: string): string { + // OpenAI models + if ( + model.startsWith("gpt-") || + model.startsWith("o4-") || + model.startsWith("o1-") + ) { + return "https://api.openai.com/v1"; + } + + // Anthropic models + if (model.startsWith("claude-")) { + return "https://api.anthropic.com"; + } + + // Google models + if (model.startsWith("gemini-")) { + return "https://generativelanguage.googleapis.com/v1beta/openai"; + } + + // Grok models + if (model.startsWith("grok-")) { + return "https://api.x.ai/v1"; + } + + // DeepSeek models + if (model.startsWith("deepseek-")) { + return "https://api.deepseek.com/v1"; + } + + // Qwen models + if (model.startsWith("qwen")) { + return "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"; + } + + // Default to OpenAI + return "https://api.openai.com/v1"; +} + +/** + * Get the API key for a given LLM model from saved settings + */ +export function getApiKeyForModel( + model: string, + settings: ApiSettings +): string { + // OpenAI models + if ( + model.startsWith("gpt-") || + model.startsWith("o4-") || + model.startsWith("o1-") + ) { + return settings.openai_api_key; + } + + // Anthropic models + if (model.startsWith("claude-")) { + return settings.anthropic_api_key || ""; + } + + // Google models + if (model.startsWith("gemini-")) { + return settings.google_api_key || ""; + } + + // Grok models + if (model.startsWith("grok-")) { + return settings.grok_api_key || ""; + } + + // DeepSeek models + if (model.startsWith("deepseek-")) { + return settings.deepseek_api_key || ""; + } + + // Qwen models + if (model.startsWith("qwen")) { + return settings.qwen_api_key || ""; + } + + // Default to OpenAI + return settings.openai_api_key; +} diff --git a/frontend/lib/storage.ts b/frontend/lib/storage.ts new file mode 100644 index 00000000..f59da489 --- /dev/null +++ b/frontend/lib/storage.ts @@ -0,0 +1,81 @@ +/** + * localStorage utility for API settings + */ + +export interface ApiSettings { + // Required providers + openai_api_key: string; + alpha_vantage_api_key: string; + + // Optional providers + anthropic_api_key: string; + google_api_key: string; + grok_api_key: string; + deepseek_api_key: string; + qwen_api_key: string; +} + +const STORAGE_KEY = "tradingagents_api_settings"; + +export const DEFAULT_API_SETTINGS: ApiSettings = { + openai_api_key: "", + alpha_vantage_api_key: "", + anthropic_api_key: "", + google_api_key: "", + grok_api_key: "", + deepseek_api_key: "", + qwen_api_key: "", +}; + +/** + * Get API settings from localStorage + */ +export function getApiSettings(): ApiSettings { + if (typeof window === "undefined") { + return DEFAULT_API_SETTINGS; + } + + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored); + // Merge with defaults to handle any missing fields + return { ...DEFAULT_API_SETTINGS, ...parsed }; + } + } catch (error) { + console.error("Error reading API settings from localStorage:", error); + } + + return DEFAULT_API_SETTINGS; +} + +/** + * Save API settings to localStorage + */ +export function saveApiSettings(settings: ApiSettings): void { + if (typeof window === "undefined") { + return; + } + + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); + } catch (error) { + console.error("Error saving API settings to localStorage:", error); + throw error; + } +} + +/** + * Clear API settings from localStorage + */ +export function clearApiSettings(): void { + if (typeof window === "undefined") { + return; + } + + try { + localStorage.removeItem(STORAGE_KEY); + } catch (error) { + console.error("Error clearing API settings from localStorage:", error); + } +}