feat: 密钥管理使用新接口获取 LiteLLM 密钥

- 新增 getUserResourcesInfo 接口 (GET /api/user/resources/info)
- 密钥管理对话框从接口获取解密后的 API Key
- 移除重新生成密钥功能(密钥由系统分配,不可修改)
- 支持显示多个 LiteLLM 密钥
This commit is contained in:
zhanggangyong
2026-01-13 13:51:00 +00:00
parent 6672904036
commit c01f94136a
2 changed files with 84 additions and 29 deletions
+55 -29
View File
@@ -29,7 +29,6 @@ import {
User,
Key,
Copy,
RefreshCw,
Lock,
Mail,
} from "lucide-react"
@@ -65,8 +64,17 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
const [showProfileDialog, setShowProfileDialog] = useState(false)
const [showSettingsDialog, setShowSettingsDialog] = useState(false)
const [showChangePasswordDialog, setShowChangePasswordDialog] = useState(false)
// 使用固定的初始值,避免服务器端和客户端不一致
const [apiKey, setApiKey] = useState("sk_live_1234567890abcdefghijklmnopqrstuvwxyz")
// LiteLLM 密钥信息(从 /api/user/resources/info 获取)
const [litellmKeys, setLitellmKeys] = useState<Array<{
modelName: string
apiKey: string
apiBase: string
rpmLimit?: number
tpmLimit?: number
status?: string
}>>([])
const [litellmApiBase, setLitellmApiBase] = useState<string>("")
const [apiKeyLoading, setApiKeyLoading] = useState(false)
// 用户信息状态
const [userInfo, setUserInfo] = useState<{
username: string
@@ -280,20 +288,33 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
}
}
// 打开密钥管理对话框时加载密钥
useEffect(() => {
if (showApiKeyDialog) {
const loadApiKeys = async () => {
try {
setApiKeyLoading(true)
const response = await TaijiAPIClient.getUserResourcesInfo()
if (response?.success && response?.data) {
setLitellmKeys(response.data.litellmKeys || [])
setLitellmApiBase(response.data.litellmApiBase || "")
}
} catch (error) {
console.error("Failed to load API keys:", error)
} finally {
setApiKeyLoading(false)
}
}
loadApiKeys()
}
}, [showApiKeyDialog])
const copyToClipboard = (text: string) => {
if (typeof window !== "undefined" && navigator.clipboard) {
navigator.clipboard.writeText(text)
}
}
const regenerateApiKey = () => {
// 只在客户端执行,确保服务器端和客户端一致
if (typeof window !== "undefined") {
const newKey = `sk_live_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`
setApiKey(newKey)
}
}
return (
<div className="flex h-screen bg-background">
{/* Sidebar */}
@@ -516,25 +537,30 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
</DialogHeader>
<div className="space-y-6 py-4">
{/* API Key */}
<div className="space-y-2">
<Label>{t("API密钥", "API Key")}</Label>
<div className="flex gap-2">
<Input value={apiKey} readOnly className="font-mono text-sm" type="password" />
<Button variant="outline" size="icon" onClick={() => copyToClipboard(apiKey)} title={t("复制", "Copy")}>
<Copy className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" onClick={regenerateApiKey} title={t("重新生成", "Regenerate")}>
<RefreshCw className="h-4 w-4" />
</Button>
{apiKeyLoading ? (
<div className="text-center py-4 text-muted-foreground">
{t("加载中...", "Loading...")}
</div>
<p className="text-xs text-muted-foreground">
{t(
"请妥善保管您的API密钥。重新生成后,旧密钥将立即失效。",
"Keep your API key secure. Regenerating will immediately invalidate the old key.",
)}
</p>
</div>
) : litellmKeys.length === 0 ? (
<div className="text-center py-4 text-muted-foreground">
{t("暂无可用密钥", "No API keys available")}
</div>
) : (
litellmKeys.map((keyInfo, index) => (
<div key={index} className="space-y-2">
<Label>{t("API密钥", "API Key")}</Label>
<div className="flex gap-2">
<Input value={keyInfo.apiKey} readOnly className="font-mono text-sm" type="password" />
<Button variant="outline" size="icon" onClick={() => copyToClipboard(keyInfo.apiKey)} title={t("复制", "Copy")}>
<Copy className="h-4 w-4" />
</Button>
</div>
<p className="text-xs text-muted-foreground">
{t("请妥善保管您的API密钥。", "Keep your API key secure.")}
</p>
</div>
))
)}
</div>
<DialogFooter>
+29
View File
@@ -2630,6 +2630,35 @@ export class TaijiAPIClient {
}
}
/**
* 获取用户资源信息(LiteLLM 密钥和 Agent 信息)
* GET /api/user/resources/info
*
* 返回用户的 LiteLLM 密钥(解密后)和已部署 Agent 的详细信息
*
* @returns {
* litellmKeys: Array<{
* modelName: string, // 模型名称
* apiKey: string, // 解密后的完整 API Key
* apiBase: string, // LiteLLM 网关地址
* rpmLimit: number, // 每分钟请求数限制
* tpmLimit: number, // 每分钟 Token 数限制
* status: string, // 密钥状态
* createdAt: string // 创建时间
* }>,
* litellmApiBase: string, // LiteLLM 网关地址(全局)
* platformAgents: Array, // 已部署的平台 Agent 列表
* customAgents: Array, // 已部署的自定义 Agent 列表
* summary: object // 汇总统计信息
* }
*/
static async getUserResourcesInfo() {
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/user/resources/info`, {
headers: buildHeaders(),
})
return handleResponse(response)
}
// ==================== 租户代理工厂 API ====================
// 基于文档: 租户用户端-代理工厂对接文档.md