feat: 添加公共设备会议创建页面
- 新增 `PublicDeviceMeetingCreate` 组件,用于创建公共设备会议 - 支持选择 ASR 模型、总结模型、总结模板等配置 - 提供参会人员、主持人、会议标签、访问密码等字段 - 实现表单验证和提交功能,支持推送到设备dev_na
parent
7c3b65624e
commit
8716608afa
|
|
@ -0,0 +1,306 @@
|
||||||
|
import { App, Button, Card, Col, DatePicker, Form, Input, Radio, Row, Select, Space, Spin, Typography } from "antd";
|
||||||
|
import { AudioOutlined, CheckCircleOutlined, QrcodeOutlined } from "@ant-design/icons";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import { listUsers } from "@/api";
|
||||||
|
import { getAiModelDefault, getAiModelPage, type AiModelVO } from "@/api/business/aimodel";
|
||||||
|
import { getHotWordPage, type HotWordVO } from "@/api/business/hotword";
|
||||||
|
import { getHotWordGroupOptions, type HotWordGroupVO } from "@/api/business/hotwordGroup";
|
||||||
|
import {
|
||||||
|
createPublicDeviceMeetingBySession,
|
||||||
|
type PublicDeviceMeetingCreateCommand,
|
||||||
|
type SummaryDetailLevel,
|
||||||
|
} from "@/api/business/meeting";
|
||||||
|
import { getPromptPage, type PromptTemplateVO } from "@/api/business/prompt";
|
||||||
|
import type { SysUser } from "@/types";
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
title: string;
|
||||||
|
meetingTime: dayjs.Dayjs;
|
||||||
|
participants?: number[];
|
||||||
|
tags?: string[];
|
||||||
|
hostUserId?: number;
|
||||||
|
asrModelId: number;
|
||||||
|
summaryModelId: number;
|
||||||
|
promptId: number;
|
||||||
|
hotWordGroupId?: number;
|
||||||
|
summaryDetailLevel: SummaryDetailLevel;
|
||||||
|
useSpkId?: boolean;
|
||||||
|
enableTextRefine?: boolean;
|
||||||
|
userPrompt?: string;
|
||||||
|
accessPassword?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PublicDeviceMeetingCreate() {
|
||||||
|
const { message } = App.useApp();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { sessionId } = useParams<{ sessionId: string }>();
|
||||||
|
const [form] = Form.useForm<FormValues>();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [asrModels, setAsrModels] = useState<AiModelVO[]>([]);
|
||||||
|
const [llmModels, setLlmModels] = useState<AiModelVO[]>([]);
|
||||||
|
const [prompts, setPrompts] = useState<PromptTemplateVO[]>([]);
|
||||||
|
const [hotwordList, setHotwordList] = useState<HotWordVO[]>([]);
|
||||||
|
const [hotWordGroups, setHotWordGroups] = useState<HotWordGroupVO[]>([]);
|
||||||
|
const [userList, setUserList] = useState<SysUser[]>([]);
|
||||||
|
|
||||||
|
const watchedPromptId = Form.useWatch("promptId", form);
|
||||||
|
const watchedHotWordGroupId = Form.useWatch("hotWordGroupId", form);
|
||||||
|
|
||||||
|
const selectedPrompt = useMemo(
|
||||||
|
() => prompts.find((item) => item.id === watchedPromptId) || null,
|
||||||
|
[prompts, watchedPromptId]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem("accessToken");
|
||||||
|
if (!token) {
|
||||||
|
const redirect = encodeURIComponent(window.location.pathname + window.location.search);
|
||||||
|
navigate(`/login?redirect=${redirect}`, { replace: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void loadInitialData();
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
const loadInitialData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const [asrRes, llmRes, promptRes, hotwordRes, hotWordGroupRes, users, defaultAsr, defaultLlm] = await Promise.all([
|
||||||
|
getAiModelPage({ current: 1, size: 100, type: "ASR" }),
|
||||||
|
getAiModelPage({ current: 1, size: 100, type: "LLM" }),
|
||||||
|
getPromptPage({ current: 1, size: 100 }),
|
||||||
|
getHotWordPage({ current: 1, size: 1000 }),
|
||||||
|
getHotWordGroupOptions(),
|
||||||
|
listUsers(),
|
||||||
|
getAiModelDefault("ASR"),
|
||||||
|
getAiModelDefault("LLM"),
|
||||||
|
]);
|
||||||
|
const activePrompts = promptRes.data.data.records.filter((item: PromptTemplateVO) => item.status === 1);
|
||||||
|
const defaultPrompt = activePrompts[0];
|
||||||
|
setAsrModels(asrRes.data.data.records.filter((item: AiModelVO) => item.status === 1));
|
||||||
|
setLlmModels(llmRes.data.data.records.filter((item: AiModelVO) => item.status === 1));
|
||||||
|
setPrompts(activePrompts);
|
||||||
|
setHotwordList(hotwordRes.data.data.records.filter((item: HotWordVO) => item.status === 1));
|
||||||
|
setHotWordGroups((hotWordGroupRes.data.data || []).filter((item: HotWordGroupVO) => item.status === 1));
|
||||||
|
setUserList(users || []);
|
||||||
|
form.setFieldsValue({
|
||||||
|
title: `设备会议 ${dayjs().format("MM-DD HH:mm")}`,
|
||||||
|
meetingTime: dayjs(),
|
||||||
|
asrModelId: defaultAsr.data.data?.id,
|
||||||
|
summaryModelId: defaultLlm.data.data?.id,
|
||||||
|
promptId: defaultPrompt?.id,
|
||||||
|
hotWordGroupId: defaultPrompt?.hotWordGroupId ?? 0,
|
||||||
|
summaryDetailLevel: "STANDARD",
|
||||||
|
useSpkId: true,
|
||||||
|
enableTextRefine: false,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
message.error("加载建会配置失败");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!sessionId) {
|
||||||
|
message.error("扫码会话不存在");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const values = await form.validateFields();
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
const selectedHotWords = values.hotWordGroupId == null || values.hotWordGroupId === 0
|
||||||
|
? undefined
|
||||||
|
: hotwordList
|
||||||
|
.filter((item) => item.hotWordGroupId === values.hotWordGroupId)
|
||||||
|
.map((item) => item.word)
|
||||||
|
.filter((word) => !!word?.trim());
|
||||||
|
|
||||||
|
const payload: PublicDeviceMeetingCreateCommand = {
|
||||||
|
title: values.title,
|
||||||
|
meetingTime: values.meetingTime.format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
participants: values.participants?.join(",") || "",
|
||||||
|
tags: values.tags?.join(",") || "",
|
||||||
|
hostUserId: values.hostUserId,
|
||||||
|
asrModelId: values.asrModelId,
|
||||||
|
summaryModelId: values.summaryModelId,
|
||||||
|
promptId: values.promptId,
|
||||||
|
hotWordGroupId: values.hotWordGroupId,
|
||||||
|
summaryDetailLevel: values.summaryDetailLevel,
|
||||||
|
useSpkId: values.useSpkId ? 1 : 0,
|
||||||
|
enableTextRefine: !!values.enableTextRefine,
|
||||||
|
userPrompt: values.userPrompt,
|
||||||
|
hotWords: selectedHotWords,
|
||||||
|
accessPassword: values.accessPassword?.trim() || undefined,
|
||||||
|
};
|
||||||
|
await createPublicDeviceMeetingBySession(sessionId, payload);
|
||||||
|
message.success("会议已创建,已推送到设备");
|
||||||
|
form.resetFields();
|
||||||
|
navigate("/meetings");
|
||||||
|
} catch {
|
||||||
|
message.error("创建设备会议失败");
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ minHeight: "100vh", background: "linear-gradient(180deg, #f7fbff 0%, #eef4f8 100%)", padding: "48px 16px" }}>
|
||||||
|
<div style={{ maxWidth: 980, margin: "0 auto" }}>
|
||||||
|
<Card style={{ borderRadius: 20, boxShadow: "0 24px 64px rgba(15, 49, 86, 0.08)", border: "1px solid #d9e6f2" }}>
|
||||||
|
<Space direction="vertical" size={8} style={{ width: "100%", marginBottom: 32 }}>
|
||||||
|
<Space size={12}>
|
||||||
|
<div style={{ width: 52, height: 52, borderRadius: 14, background: "#e6f4ff", display: "flex", alignItems: "center", justifyContent: "center", color: "#1677ff", fontSize: 24 }}>
|
||||||
|
<QrcodeOutlined />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Title level={3} style={{ margin: 0 }}>扫码设备发起会议</Title>
|
||||||
|
<Text type="secondary">登录后填写会议基础信息,系统会把待开始会议推送到设备端。</Text>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
<Space size={16} wrap>
|
||||||
|
<Text type="secondary"><AudioOutlined /> 设备录音仍在设备端执行</Text>
|
||||||
|
<Text type="secondary"><CheckCircleOutlined /> 创建成功后会自动推送到扫码设备</Text>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div style={{ padding: "64px 0", textAlign: "center" }}>
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="title" label="会议标题" rules={[{ required: true, message: "请输入会议标题" }]}>
|
||||||
|
<Input size="large" placeholder="请输入会议标题" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="meetingTime" label="会议时间" rules={[{ required: true, message: "请选择会议时间" }]}>
|
||||||
|
<DatePicker showTime style={{ width: "100%" }} size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="participants" label="参会人员">
|
||||||
|
<Select mode="multiple" placeholder="选择参会人员" showSearch optionFilterProp="children" size="large">
|
||||||
|
{userList.map((u) => (
|
||||||
|
<Option key={u.userId} value={u.userId}>{u.displayName || u.username}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="hostUserId" label="主持人">
|
||||||
|
<Select allowClear placeholder="默认当前登录人" showSearch optionFilterProp="children" size="large">
|
||||||
|
{userList.map((u) => (
|
||||||
|
<Option key={u.userId} value={u.userId}>{u.displayName || u.username}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="tags" label="会议标签">
|
||||||
|
<Select mode="tags" placeholder="输入标签" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="accessPassword" label="访问密码">
|
||||||
|
<Input size="large" placeholder="可为空" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="asrModelId" label="语音识别模型" rules={[{ required: true, message: "请选择 ASR 模型" }]}>
|
||||||
|
<Select placeholder="选择 ASR 模型" size="large">
|
||||||
|
{asrModels.map((m) => (
|
||||||
|
<Option key={m.id} value={m.id}>{m.modelName}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="summaryModelId" label="总结模型" rules={[{ required: true, message: "请选择总结模型" }]}>
|
||||||
|
<Select placeholder="选择总结模型" size="large">
|
||||||
|
{llmModels.map((m) => (
|
||||||
|
<Option key={m.id} value={m.id}>{m.modelName}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item name="promptId" label="总结模板" rules={[{ required: true, message: "请选择总结模板" }]}>
|
||||||
|
<Select placeholder="请选择总结模板" showSearch optionFilterProp="children" size="large">
|
||||||
|
{prompts.map((p) => (
|
||||||
|
<Option key={p.id} value={p.id}>{p.templateName}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
name="hotWordGroupId"
|
||||||
|
label="热词组"
|
||||||
|
extra={watchedHotWordGroupId != null ? "创建会议时优先使用这里选择的热词组" : undefined}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder={selectedPrompt?.hotWordGroupId ? "默认已带出模板热词组,可按需修改" : "请选择热词组"}
|
||||||
|
size="large"
|
||||||
|
options={[{ label: "不使用热词组", value: 0 }, ...hotWordGroups.map((item) => ({ label: `${item.groupName} (${item.hotWordCount}/200)`, value: item.id }))]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item name="summaryDetailLevel" label="总结详细程度" rules={[{ required: true, message: "请选择总结详细程度" }]}>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="DETAILED">详细</Radio>
|
||||||
|
<Radio value="STANDARD">标准</Radio>
|
||||||
|
<Radio value="BRIEF">简洁</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item name="userPrompt" label="用户提示词">
|
||||||
|
<Input.TextArea autoSize={{ minRows: 3, maxRows: 6 }} maxLength={1000} showCount placeholder="例如:请重点关注待办事项与负责人" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", justifyContent: "flex-end", marginTop: 24 }}>
|
||||||
|
<Space>
|
||||||
|
<Button size="large" onClick={() => navigate(-1)}>返回</Button>
|
||||||
|
<Button type="primary" size="large" loading={submitting} onClick={() => void handleSubmit()}>
|
||||||
|
创建并推送到设备
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue