nex_basse/frontend/src/pages/Hotwords.tsx

269 lines
8.7 KiB
TypeScript
Raw Normal View History

2026-02-27 07:57:22 +00:00
import React, { useState, useEffect, useMemo } from 'react';
import { Card, Button, Modal, Form, Input, InputNumber, message, Tag, Popconfirm, Tooltip, Empty } from 'antd';
import { PlusOutlined, DeleteOutlined, FireOutlined, QuestionCircleOutlined, ReloadOutlined, EditOutlined } from '@ant-design/icons';
import { api } from '../api';
// @ts-ignore
import PageTitleBar from '../components/PageTitleBar/PageTitleBar';
// @ts-ignore
import ListActionBar from '../components/ListActionBar/ListActionBar';
import ListTable from '../components/ListTable/ListTable';
interface HotwordItem {
id: number;
word: string;
pinyin: string;
weight: number;
}
const Hotwords: React.FC = () => {
const [hotwords, setHotwords] = useState<HotwordItem[]>([]);
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [searchText, setSearchText] = useState('');
const [currentHotword, setCurrentHotword] = useState<HotwordItem | null>(null);
const [form] = Form.useForm();
const fetchHotwords = async () => {
setLoading(true);
try {
const data = await api.listHotwords();
setHotwords(data);
} catch (error) {
message.error('获取热词列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchHotwords();
}, []);
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (currentHotword) {
await api.updateHotword(currentHotword.id, values);
message.success('修改成功');
} else {
await api.createHotword(values);
message.success('添加成功');
}
handleModalClose();
fetchHotwords();
} catch (error) {
// message.error('操作失败');
}
};
const handleDelete = async (id: number) => {
try {
await api.deleteHotword(id);
message.success('删除成功');
fetchHotwords();
} catch (error) {
message.error('删除失败');
}
};
const handleEdit = (record: HotwordItem) => {
setCurrentHotword(record);
form.setFieldsValue(record);
setModalVisible(true);
};
const handleModalClose = () => {
setModalVisible(false);
setCurrentHotword(null);
form.resetFields();
};
const filteredHotwords = useMemo(() => {
if (!searchText) return hotwords;
return hotwords.filter(item =>
item.word.includes(searchText) ||
item.pinyin.toLowerCase().includes(searchText.toLowerCase())
);
}, [hotwords, searchText]);
const columns = [
{
title: '热词',
dataIndex: 'word',
key: 'word',
width: '30%',
render: (t: string) => (
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<FireOutlined style={{ color: '#ff4d4f' }} />
<span style={{ fontWeight: 600, fontSize: '15px' }}>{t}</span>
</div>
)
},
{
title: '拼音',
dataIndex: 'pinyin',
key: 'pinyin',
width: '30%',
render: (t: string) => <Tag color="blue" style={{ fontFamily: 'monospace' }}>{t}</Tag>
},
{
title: '权重',
dataIndex: 'weight',
key: 'weight',
width: '20%',
render: (w: number) => {
let color = 'default';
if (w >= 5) color = 'red';
else if (w >= 2) color = 'orange';
else if (w >= 1) color = 'green';
return (
<Tooltip title={`权重: ${w}`}>
<Tag color={color} style={{ minWidth: 40, textAlign: 'center', fontWeight: 'bold' }}>{w}x</Tag>
</Tooltip>
);
}
},
{
title: '操作',
key: 'action',
width: 180,
render: (_: any, record: HotwordItem) => (
<div style={{ display: 'flex', gap: 8 }}>
<Button
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
style={{ color: '#1890ff' }}
>
</Button>
<Popconfirm
title="确定删除该热词吗?"
description="删除后将不再对该词进行强化识别"
onConfirm={() => handleDelete(record.id)}
okText="删除"
cancelText="取消"
okButtonProps={{ danger: true }}
>
<Button type="text" danger icon={<DeleteOutlined />}>
</Button>
</Popconfirm>
</div>
),
},
];
return (
<div className="page-wrapper" style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<PageTitleBar
title="热词管理"
description="配置语音识别热词,提升特定词汇(如人名、地名、专业术语)的识别准确率"
badge={hotwords.length}
/>
<div style={{ flex: 1, padding: '0 24px 24px', display: 'flex', flexDirection: 'column', gap: 16 }}>
<Card bodyStyle={{ padding: 0 }} bordered={false} style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
<div style={{ padding: '16px 24px', borderBottom: '1px solid #f0f0f0' }}>
<ListActionBar
actions={[
{
key: 'add',
label: '添加热词',
type: 'primary',
icon: <PlusOutlined />,
onClick: () => setModalVisible(true),
}
]}
search={{
placeholder: "搜索热词或拼音...",
value: searchText,
onChange: (val: string) => setSearchText(val),
width: 320
}}
showRefresh
onRefresh={fetchHotwords}
/>
</div>
<div style={{ flex: 1, overflow: 'hidden' }}>
<ListTable
columns={columns}
dataSource={filteredHotwords}
rowKey="id"
loading={loading}
pagination={{ pageSize: 10, showSizeChanger: true, showQuickJumper: true }}
scroll={{ x: 800, y: 'calc(100vh - 340px)' }}
/>
</div>
</Card>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ background: '#e6f7ff', padding: 8, borderRadius: '50%', color: '#1890ff', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FireOutlined style={{ fontSize: 18 }} />
</div>
<span style={{ fontSize: 16 }}>{currentHotword ? '编辑热词' : '添加新热词'}</span>
</div>
}
open={modalVisible}
onCancel={handleModalClose}
onOk={handleSubmit}
destroyOnClose
width={520}
okText={currentHotword ? '保存' : '添加'}
cancelText="取消"
>
<Form form={form} layout="vertical" style={{ marginTop: 24 }}>
<Form.Item
name="word"
label={
<span>
<Tooltip title="需要提高识别准确率的专有名词、术语等"><QuestionCircleOutlined style={{ color: '#999' }} /></Tooltip>
</span>
}
rules={[{ required: true, message: '请输入热词' }]}
>
<Input placeholder="请输入需要强化的词汇,如:人工智能" size="large" />
</Form.Item>
<Form.Item
name="weight"
label={
<span>
<Tooltip title="权重越高,该词被识别出的概率越大。推荐范围 2.0 - 5.0"><QuestionCircleOutlined style={{ color: '#999' }} /></Tooltip>
</span>
}
initialValue={2.0}
>
<InputNumber
min={1.0}
max={10.0}
step={0.5}
style={{ width: '100%' }}
size="large"
addonAfter="倍"
/>
</Form.Item>
<div style={{ background: '#f9f9f9', padding: 16, borderRadius: 8, border: '1px solid #f0f0f0' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, fontWeight: 500, color: '#333' }}>
<QuestionCircleOutlined /> 使
</div>
<ul style={{ paddingLeft: 20, margin: 0, color: '#666', fontSize: 13, lineHeight: 1.8 }}>
<li></li>
<li></li>
<li> <b>2.0 - 5.0</b> </li>
</ul>
</div>
</Form>
</Modal>
</div>
);
};
export default Hotwords;