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.
This commit is contained in:
2025-10-15 23:34:44 +08:00
parent c899702d51
commit 39a4788cc4
21 changed files with 11041 additions and 251 deletions

148
components/api-config.tsx Normal file
View File

@@ -0,0 +1,148 @@
"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>
)
}