feat: config page with tooltips, help tab, and parameter docs
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s

This commit is contained in:
WangDL 2026-05-22 22:44:19 +08:00
parent f2f427bbe8
commit f5882c8ee6

View File

@ -1,11 +1,18 @@
import { useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Table, Switch, Button, Typography, App, Modal, Input, Tabs, Space } from 'antd'
import { ReloadOutlined, PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
import { Table, Switch, Button, Typography, App, Modal, Input, Tabs, Space, Tooltip, Alert } from 'antd'
import { ReloadOutlined, PlusOutlined, DeleteOutlined, EditOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { getConfig, setConfig, updateConfig, deleteConfig, toggleFlag, getChangeLog } from '@/services/config-api'
import dayjs from 'dayjs'
const { Title, Text } = Typography
const { Title, Text, Paragraph } = Typography
// Config documentation
const CONFIG_DOCS: Record<string, { desc: string; example: string; note: string }> = {
'ai.temperature': { desc: 'AI 回复的随机性/创造力', example: '0.7', note: '0=确定, 1=最随机, 推荐 0.5~0.7' },
'ai.max_tokens': { desc: 'AI 单次回复最大长度', example: '4096', note: '1 token ≈ 0.7 个中文字, 4096≈2800字' },
'hermes.api_url': { desc: 'Hermes Agent 服务地址', example: 'http://10.2.0.7:8642', note: '修改后 AI 对话会走新地址' },
}
function ConfigPage() {
const { modal, message } = App.useApp()
@ -31,10 +38,18 @@ function ConfigPage() {
})
const configColumns = [
{ title: 'Key', dataIndex: 'key', width: 180, ellipsis: true },
{ title: 'Value', dataIndex: 'value', width: 300, ellipsis: true, render: (v: string) => <Text code>{v}</Text> },
{ title: '环境', dataIndex: 'environment', width: 80 },
{ title: '更新', dataIndex: 'updatedAt', width: 140, render: (d: string) => dayjs(d).format('MM-DD HH:mm') },
{ title: 'Key', dataIndex: 'key', width: 160, ellipsis: true,
render: (k: string) => CONFIG_DOCS[k] ? (
<Tooltip title={CONFIG_DOCS[k].desc}><Text code>{k} <QuestionCircleOutlined style={{ color: '#999', fontSize: 11 }} /></Text></Tooltip>
) : <Text code>{k}</Text>
},
{ title: 'Value', dataIndex: 'value', width: 240, ellipsis: true, render: (v: string, r: any) => (
<Tooltip title={CONFIG_DOCS[r.key]?.note}>
<Text code style={{ whiteSpace: 'pre-wrap' }}>{v}</Text>
</Tooltip>
)},
{ title: '说明', dataIndex: 'key', width: 160, render: (k: string) => CONFIG_DOCS[k]?.desc || '-' },
{ title: '更新', dataIndex: 'updatedAt', width: 130, render: (d: string) => dayjs(d).format('MM-DD HH:mm') },
{ title: '操作', width: 100, render: (_: any, r: any) => (
<Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => { setEditKey(r.key); setNewKey(r.key); setNewValue(r.value); setAddOpen(true) }} />
@ -44,7 +59,7 @@ function ConfigPage() {
]
const flagColumns = [
{ title: '开关', dataIndex: 'name', width: 180 },
{ title: '开关', dataIndex: 'name', width: 180 },
{ title: '说明', dataIndex: 'description', ellipsis: true },
{ title: '状态', dataIndex: 'enabled', width: 80, render: (v: boolean, r: any) => (
<Switch checked={v} onChange={(checked) => { toggleFlag(r.name, checked); message.success('已切换'); qc.invalidateQueries({ queryKey: ['config'] }) }} />
@ -53,17 +68,56 @@ function ConfigPage() {
const logColumns = [
{ title: '时间', dataIndex: 'createdAt', width: 130, render: (d: string) => dayjs(d).format('MM-DD HH:mm:ss') },
{ title: '类型', dataIndex: 'entityType', width: 100 },
{ title: '字段', dataIndex: 'field', width: 100 },
{ title: '旧值', dataIndex: 'oldValue', ellipsis: true, render: (v: string) => v ? <Text code style={{ fontSize: 11 }}>{v}</Text> : '-' },
{ title: '新值', dataIndex: 'newValue', ellipsis: true, render: (v: string) => <Text code style={{ fontSize: 11 }}>{v}</Text> },
{ title: 'Key', dataIndex: 'entityId', width: 140, ellipsis: true },
{ title: '旧→新', width: 300, render: (_: any, r: any) => (
<span>
<Text code style={{ fontSize: 11, color: '#ff4d4f' }}>{r.oldValue || '(空)'}</Text>
<Text style={{ margin: '0 6px' }}></Text>
<Text code style={{ fontSize: 11, color: '#52c41a' }}>{r.newValue}</Text>
</span>
)},
{ title: '操作人', dataIndex: 'changedBy', width: 150 },
]
const items = [
{ key: 'config', label: '配置', children: <Table dataSource={data?.configs || []} columns={configColumns} rowKey="key" pagination={false} size="small" /> },
{ key: 'flags', label: '功能开关', children: <Table dataSource={data?.flags || []} columns={flagColumns} rowKey="name" pagination={false} size="small" /> },
{ key: 'config', label: '配置', children: (
<div>
<Alert type="info" message="提示" description="鼠标悬停 Key 和 Value 查看参数说明。修改后约 60 秒生效,无需重启。" style={{ marginBottom: 12 }} showIcon />
<Table dataSource={data?.configs || []} columns={configColumns} rowKey="key" pagination={false} size="small" />
</div>
)},
{ key: 'flags', label: '功能开关', children: (
<div>
<Alert type="info" message="功能开关用于灰度发布新功能,一键开启/关闭" style={{ marginBottom: 12 }} showIcon />
<Table dataSource={data?.flags || []} columns={flagColumns} rowKey="name" pagination={false} size="small" />
</div>
)},
{ key: 'log', label: '变更历史', children: <Table dataSource={changelog || []} columns={logColumns} rowKey="id" pagination={{ pageSize: 30 }} size="small" /> },
{ key: 'help', label: '使用说明', children: (
<div style={{ maxWidth: 700 }}>
<Title level={5} style={{ fontSize: 14 }}></Title>
<Paragraph type="secondary"> 60 </Paragraph>
<Table dataSource={Object.entries(CONFIG_DOCS).map(([key, doc]) => ({ key, ...doc }))}
columns={[
{ title: 'Key', dataIndex: 'key', width: 160, render: (v: string) => <Text code>{v}</Text> },
{ title: '作用', dataIndex: 'desc', width: 180 },
{ title: '默认值', dataIndex: 'example', width: 100, render: (v: string) => <Text code>{v}</Text> },
{ title: '说明', dataIndex: 'note' },
]}
rowKey="key" pagination={false} size="small"
/>
<Title level={5} style={{ fontSize: 14, marginTop: 24 }}></Title>
<Paragraph>
1. 使 <Text code>this.config.get('key', '默认值')</Text> <br />
2. Tab Key Value<br />
3. Value 60
</Paragraph>
<Title level={5} style={{ fontSize: 14, marginTop: 24 }}></Title>
<Paragraph>
· <Text code>admin-ai-chat.service.ts</Text> AI temperature / max_tokens / hermes
</Paragraph>
</div>
)},
]
return (