forked from xiaohei/taiji-pda-v0
feat: 密钥管理使用新接口获取 LiteLLM 密钥
- 新增 getUserResourcesInfo 接口 (GET /api/user/resources/info) - 密钥管理对话框从接口获取解密后的 API Key - 移除重新生成密钥功能(密钥由系统分配,不可修改) - 支持显示多个 LiteLLM 密钥
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user