Files
aken1023 39a4788cc4 Add PDF translation API, utilities, docs, and config
Introduces core backend and frontend infrastructure for a PDF translation interface. Adds API endpoints for translation, PDF testing, and AI provider testing; implements PDF text extraction, cost tracking, and pricing logic in the lib directory; adds reusable UI components; and provides comprehensive documentation (SDD, environment setup, Claude instructions). Updates Tailwind and global styles, and includes a sample test PDF and configuration files.
2025-10-15 23:34:44 +08:00

148 lines
5.4 KiB
TypeScript
Raw Permalink 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.

"use client"
import { useState } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Settings, Key, AlertCircle, CheckCircle } from "lucide-react"
interface APIConfigProps {
onConfigUpdate?: () => void
}
export function APIConfig({ onConfigUpdate }: APIConfigProps) {
const [provider, setProvider] = useState("deepseek")
const [apiKey, setApiKey] = useState("")
const [isTestingAPI, setIsTestingAPI] = useState(false)
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null)
const handleTestAPI = async () => {
if (!apiKey) {
setTestResult({ success: false, message: "請輸入 API 金鑰" })
return
}
setIsTestingAPI(true)
setTestResult(null)
try {
const response = await fetch("/api/test-api", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
provider,
apiKey,
}),
})
const data = await response.json()
if (response.ok) {
setTestResult({ success: true, message: "API 連接成功!" })
if (onConfigUpdate) {
onConfigUpdate()
}
} else {
setTestResult({ success: false, message: data.error || "API 測試失敗" })
}
} catch (error) {
setTestResult({ success: false, message: "網路連接錯誤" })
} finally {
setIsTestingAPI(false)
}
}
return (
<Card className="p-6 shadow-lg">
<div className="flex items-center gap-2 mb-4">
<Settings className="h-5 w-5 text-blue-600" />
<h3 className="text-lg font-semibold">API </h3>
</div>
<div className="space-y-4">
<div>
<Label htmlFor="provider"> AI </Label>
<Select value={provider} onValueChange={setProvider}>
<SelectTrigger className="mt-2">
<SelectValue placeholder="選擇提供者" />
</SelectTrigger>
<SelectContent>
<SelectItem value="deepseek">DeepSeek ( - )</SelectItem>
<SelectItem value="openai">OpenAI</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="api-key">API </Label>
<div className="mt-2 flex gap-2">
<div className="flex-1 relative">
<Key className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<input
id="api-key"
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={provider === "deepseek" ? "sk-xxxxxxxx" : "sk-proj-xxxxxxxx"}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<Button
onClick={handleTestAPI}
disabled={isTestingAPI || !apiKey}
variant="outline"
>
{isTestingAPI ? "測試中..." : "測試"}
</Button>
</div>
</div>
{testResult && (
<div className={`flex items-center gap-2 p-3 rounded-lg ${
testResult.success
? "bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300"
: "bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300"
}`}>
{testResult.success ? (
<CheckCircle className="h-4 w-4" />
) : (
<AlertCircle className="h-4 w-4" />
)}
<span className="text-sm">{testResult.message}</span>
</div>
)}
<div className="text-sm text-gray-600 dark:text-gray-400 space-y-2">
<h4 className="font-medium"> API </h4>
{provider === "deepseek" ? (
<div>
<p><strong>DeepSeek API</strong></p>
<ol className="list-decimal list-inside space-y-1 ml-4">
<li> <a href="https://platform.deepseek.com" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">platform.deepseek.com</a></li>
<li></li>
<li> API </li>
<li> API </li>
<li></li>
</ol>
<p className="mt-2 text-xs text-green-600">💡 DeepSeek </p>
</div>
) : (
<div>
<p><strong>OpenAI API</strong></p>
<ol className="list-decimal list-inside space-y-1 ml-4">
<li> <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">platform.openai.com/api-keys</a></li>
<li> OpenAI </li>
<li> "Create new secret key"</li>
<li></li>
<li></li>
</ol>
</div>
)}
</div>
</div>
</Card>
)
}