This commit is contained in:
parent
5dee4b323c
commit
a0e4365fc3
|
|
@ -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) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* API Configuration Section */}
|
||||
<div className="space-y-4 border-t pt-6 mt-6">
|
||||
<h3 className="text-lg font-semibold">API 配置</h3>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="quick_think_base_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>快速思維模型 Base URL</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
if (value !== "custom") {
|
||||
field.onChange(value);
|
||||
} else {
|
||||
field.onChange(""); // Clear value for custom input
|
||||
}
|
||||
}}
|
||||
defaultValue={
|
||||
[
|
||||
"https://api.openai.com/v1",
|
||||
"https://api.anthropic.com",
|
||||
"https://api.x.ai/v1",
|
||||
"https://api.deepseek.com/v1",
|
||||
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
].includes(field.value || "")
|
||||
? field.value
|
||||
: "custom"
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="選擇 API 端點" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="https://api.openai.com/v1">
|
||||
OpenAI (預設)
|
||||
</SelectItem>
|
||||
<SelectItem value="https://api.anthropic.com">
|
||||
Anthropic
|
||||
</SelectItem>
|
||||
<SelectItem value="https://generativelanguage.googleapis.com/v1beta/openai">
|
||||
Google (Gemini)
|
||||
</SelectItem>
|
||||
<SelectItem value="https://api.x.ai/v1">
|
||||
Grok (xAI)
|
||||
</SelectItem>
|
||||
<SelectItem value="https://api.deepseek.com/v1">
|
||||
DeepSeek
|
||||
</SelectItem>
|
||||
<SelectItem value="https://dashscope-intl.aliyuncs.com/compatible-mode/v1">
|
||||
Qwen (Alibaba)
|
||||
</SelectItem>
|
||||
<SelectItem value="custom">自訂端點</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{(![
|
||||
"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 === "") && (
|
||||
<div className="mt-2">
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="請輸入自訂 Base URL"
|
||||
value={field.value || ""}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormDescription>
|
||||
快速思維模型的 API 基礎網址
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="quick_think_api_key"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>快速思維模型 API Key *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" placeholder="sk-..." {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
該模型的專屬 API Key(必填)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="deep_think_base_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>深層思維模型 Base URL</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
if (value !== "custom") {
|
||||
field.onChange(value);
|
||||
} else {
|
||||
field.onChange(""); // Clear value for custom input
|
||||
}
|
||||
}}
|
||||
defaultValue={
|
||||
[
|
||||
"https://api.openai.com/v1",
|
||||
"https://api.anthropic.com",
|
||||
"https://api.x.ai/v1",
|
||||
"https://api.deepseek.com/v1",
|
||||
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
].includes(field.value || "")
|
||||
? field.value
|
||||
: "custom"
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="選擇 API 端點" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="https://api.openai.com/v1">
|
||||
OpenAI (預設)
|
||||
</SelectItem>
|
||||
<SelectItem value="https://api.anthropic.com">
|
||||
Anthropic
|
||||
</SelectItem>
|
||||
<SelectItem value="https://generativelanguage.googleapis.com/v1beta/openai">
|
||||
Google (Gemini)
|
||||
</SelectItem>
|
||||
<SelectItem value="https://api.x.ai/v1">
|
||||
Grok (xAI)
|
||||
</SelectItem>
|
||||
<SelectItem value="https://api.deepseek.com/v1">
|
||||
DeepSeek
|
||||
</SelectItem>
|
||||
<SelectItem value="https://dashscope-intl.aliyuncs.com/compatible-mode/v1">
|
||||
Qwen (Alibaba)
|
||||
</SelectItem>
|
||||
<SelectItem value="custom">自訂端點</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{(![
|
||||
"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 === "") && (
|
||||
<div className="mt-2">
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="請輸入自訂 Base URL"
|
||||
value={field.value || ""}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormDescription>
|
||||
深層思維模型的 API 基礎網址
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="deep_think_api_key"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>深層思維模型 API Key *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" placeholder="sk-..." {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
該模型的專屬 API Key(必填)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="embedding_base_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>嵌入模型 Base URL</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
if (value !== "custom") {
|
||||
field.onChange(value);
|
||||
} else {
|
||||
field.onChange(""); // Clear value for custom input
|
||||
}
|
||||
}}
|
||||
defaultValue={
|
||||
field.value === "https://api.openai.com/v1" ||
|
||||
!field.value
|
||||
? "https://api.openai.com/v1"
|
||||
: "custom"
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="選擇嵌入模型端點" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="https://api.openai.com/v1">
|
||||
OpenAI (預設)
|
||||
</SelectItem>
|
||||
<SelectItem value="custom">自訂端點</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{field.value !== "https://api.openai.com/v1" && (
|
||||
<div className="mt-2">
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="請輸入自訂 Base URL"
|
||||
value={field.value || ""}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormDescription>
|
||||
嵌入向量生成的 API 端點(用於記憶體系統)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="embedding_api_key"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>嵌入模型 API Key *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" placeholder="sk-..." {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>該端點的 API Key(必填)</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full bg-gradient-to-r from-blue-500 to-pink-500 dark:from-blue-600 dark:to-purple-600 hover:from-blue-600 hover:to-pink-600 dark:hover:from-blue-700 dark:hover:to-purple-700 shadow-lg hover:shadow-xl transition-all animate-heartbeat"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import Link from "next/link";
|
||||
import { ThemeToggle } from "@/components/theme/ThemeToggle";
|
||||
import { ApiSettingsDialog } from "@/components/settings/ApiSettingsDialog";
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
|
|
@ -28,6 +29,7 @@ export function Header() {
|
|||
>
|
||||
分析
|
||||
</Link>
|
||||
<ApiSettingsDialog />
|
||||
<ThemeToggle />
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,269 @@
|
|||
/**
|
||||
* 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue