nex_basse/frontend/src/pages/Hotwords.tsx

269 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;