diff --git a/admin-v2/src/pages/Accounts.tsx b/admin-v2/src/pages/Accounts.tsx
index b6e6ece..a31cffb 100644
--- a/admin-v2/src/pages/Accounts.tsx
+++ b/admin-v2/src/pages/Accounts.tsx
@@ -2,6 +2,7 @@
// 账号管理
// ============================================================
+import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Button, message, Tag, Modal, Form, Input, Select, Popconfirm, Space } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
@@ -88,6 +89,16 @@ export default function Accounts() {
width: 80,
render: (_, record) => record.totp_enabled ? 已启用 : 未启用,
},
+ {
+ title: 'LLM 路由',
+ dataIndex: 'llm_routing',
+ width: 120,
+ valueType: 'select',
+ valueEnum: {
+ relay: { text: 'SaaS 中转', status: 'Success' },
+ local: { text: '本地直连', status: 'Default' },
+ },
+ },
{
title: '最后登录',
dataIndex: 'last_login_at',
@@ -161,10 +172,14 @@ export default function Accounts() {
{ value: 'user', label: '用户' },
]} />
+
+
+
)
}
-
-import { useState } from 'react'
diff --git a/admin-v2/src/pages/AgentTemplates.tsx b/admin-v2/src/pages/AgentTemplates.tsx
index ff5e512..6cb7df8 100644
--- a/admin-v2/src/pages/AgentTemplates.tsx
+++ b/admin-v2/src/pages/AgentTemplates.tsx
@@ -51,6 +51,7 @@ export default function AgentTemplates() {
})
const columns: ProColumns[] = [
+ { title: '图标', dataIndex: 'emoji', width: 60 },
{ title: '名称', dataIndex: 'name', width: 160 },
{ title: '分类', dataIndex: 'category', width: 100 },
{ title: '模型', dataIndex: 'model', width: 140, render: (_, r) => r.model || '-' },
@@ -152,6 +153,29 @@ export default function AgentTemplates() {
{ value: 'private', label: '私有' },
]} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin-v2/src/pages/Providers.tsx b/admin-v2/src/pages/Providers.tsx
index 5764391..ec42d38 100644
--- a/admin-v2/src/pages/Providers.tsx
+++ b/admin-v2/src/pages/Providers.tsx
@@ -19,6 +19,8 @@ export default function Providers() {
const [modalOpen, setModalOpen] = useState(false)
const [editingId, setEditingId] = useState(null)
const [keyModalProviderId, setKeyModalProviderId] = useState(null)
+ const [addKeyOpen, setAddKeyOpen] = useState(false)
+ const [addKeyForm] = Form.useForm()
const { data, isLoading } = useQuery({
queryKey: ['providers'],
@@ -63,6 +65,38 @@ export default function Providers() {
onError: (err: Error) => message.error(err.message || '删除失败'),
})
+ const addKeyMutation = useMutation({
+ mutationFn: ({ providerId, data }: { providerId: string; data: any }) =>
+ providerService.addKey(providerId, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['provider-keys', keyModalProviderId] })
+ message.success('密钥已添加')
+ setAddKeyOpen(false)
+ addKeyForm.resetFields()
+ },
+ onError: () => message.error('添加失败'),
+ })
+
+ const toggleKeyMutation = useMutation({
+ mutationFn: ({ providerId, keyId, active }: { providerId: string; keyId: string; active: boolean }) =>
+ providerService.toggleKey(providerId, keyId, active),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['provider-keys', keyModalProviderId] })
+ message.success('状态已切换')
+ },
+ onError: () => message.error('切换失败'),
+ })
+
+ const deleteKeyMutation = useMutation({
+ mutationFn: ({ providerId, keyId }: { providerId: string; keyId: string }) =>
+ providerService.deleteKey(providerId, keyId),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['provider-keys', keyModalProviderId] })
+ message.success('密钥已删除')
+ },
+ onError: () => message.error('删除失败'),
+ })
+
const columns: ProColumns[] = [
{ title: '名称', dataIndex: 'display_name', width: 140 },
{ title: '标识', dataIndex: 'name', width: 120, render: (_, r) => {r.name} },
@@ -104,6 +138,35 @@ export default function Providers() {
width: 80,
render: (_, r) => r.is_active ? 活跃 : 冷却,
},
+ {
+ title: '操作',
+ width: 160,
+ render: (_, record) => (
+
+ toggleKeyMutation.mutate({
+ providerId: keyModalProviderId!,
+ keyId: record.id,
+ active: !record.is_active,
+ })}
+ >
+
+
+ deleteKeyMutation.mutate({
+ providerId: keyModalProviderId!,
+ keyId: record.id,
+ })}
+ >
+
+
+
+ ),
+ },
]
const handleSave = async () => {
@@ -169,7 +232,14 @@ export default function Providers() {
title="Key Pool"
open={!!keyModalProviderId}
onCancel={() => setKeyModalProviderId(null)}
- footer={null}
+ footer={(_, { OkBtn, CancelBtn }) => (
+
+
+
+
+ )}
width={700}
>
@@ -183,6 +253,36 @@ export default function Providers() {
size="small"
/>
+
+ {
+ addKeyForm.validateFields().then((v) =>
+ addKeyMutation.mutate({ providerId: keyModalProviderId!, data: v })
+ )
+ }}
+ onCancel={() => setAddKeyOpen(false)}
+ confirmLoading={addKeyMutation.isPending}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/admin-v2/src/services/agent-templates.ts b/admin-v2/src/services/agent-templates.ts
index 5a318a0..d395b7f 100644
--- a/admin-v2/src/services/agent-templates.ts
+++ b/admin-v2/src/services/agent-templates.ts
@@ -8,11 +8,16 @@ export const agentTemplateService = {
get: (id: string, signal?: AbortSignal) =>
request.get(`/agent-templates/${id}`, withSignal({}, signal)).then((r) => r.data),
+ getFull: (id: string, signal?: AbortSignal) =>
+ request.get(`/agent-templates/${id}/full`, withSignal({}, signal)).then((r) => r.data),
+
create: (data: {
name: string; description?: string; category?: string; source?: string
model?: string; system_prompt?: string; tools?: string[]
capabilities?: string[]; temperature?: number; max_tokens?: number
- visibility?: string
+ visibility?: string; emoji?: string; personality?: string
+ soul_content?: string; welcome_message?: string
+ communication_style?: string; source_id?: string
}, signal?: AbortSignal) =>
request.post('/agent-templates', data, withSignal({}, signal)).then((r) => r.data),
diff --git a/admin-v2/src/types/index.ts b/admin-v2/src/types/index.ts
index 63a7ba0..29c0851 100644
--- a/admin-v2/src/types/index.ts
+++ b/admin-v2/src/types/index.ts
@@ -13,6 +13,7 @@ export interface AccountPublic {
totp_enabled: boolean
last_login_at: string | null
created_at: string
+ llm_routing: 'relay' | 'local'
}
/** 登录请求 */
@@ -227,6 +228,25 @@ export interface AgentTemplate {
current_version: number
created_at: string
updated_at: string
+ soul_content?: string
+ scenarios: string[]
+ welcome_message?: string
+ quick_commands: Array<{ label: string; command: string }>
+ personality?: string
+ communication_style?: string
+ emoji?: string
+ version: number
+ source_id?: string
+}
+
+/** Agent 模板可用列表(轻量) */
+export interface AgentTemplateAvailable {
+ id: string
+ name: string
+ category: string
+ emoji?: string
+ description?: string
+ source_id?: string
}
/** Provider Key */