feat: config page with tooltips, help tab, and parameter docs
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
This commit is contained in:
parent
f2f427bbe8
commit
f5882c8ee6
@ -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 (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user