From 8716608afaa0332f2244fafd4cc4b2c7b7c6a9b1 Mon Sep 17 00:00:00 2001 From: chenhao Date: Tue, 2 Jun 2026 17:20:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E4=BC=9A=E8=AE=AE=E5=88=9B=E5=BB=BA=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `PublicDeviceMeetingCreate` 组件,用于创建公共设备会议 - 支持选择 ASR 模型、总结模型、总结模板等配置 - 提供参会人员、主持人、会议标签、访问密码等字段 - 实现表单验证和提交功能,支持推送到设备 --- .../business/PublicDeviceMeetingCreate.tsx | 306 ++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 frontend/src/pages/business/PublicDeviceMeetingCreate.tsx diff --git a/frontend/src/pages/business/PublicDeviceMeetingCreate.tsx b/frontend/src/pages/business/PublicDeviceMeetingCreate.tsx new file mode 100644 index 0000000..5934844 --- /dev/null +++ b/frontend/src/pages/business/PublicDeviceMeetingCreate.tsx @@ -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(); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const [asrModels, setAsrModels] = useState([]); + const [llmModels, setLlmModels] = useState([]); + const [prompts, setPrompts] = useState([]); + const [hotwordList, setHotwordList] = useState([]); + const [hotWordGroups, setHotWordGroups] = useState([]); + const [userList, setUserList] = useState([]); + + 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 ( +
+
+ + + +
+ +
+
+ 扫码设备发起会议 + 登录后填写会议基础信息,系统会把待开始会议推送到设备端。 +
+
+ + 设备录音仍在设备端执行 + 创建成功后会自动推送到扫码设备 + +
+ + {loading ? ( +
+ +
+ ) : ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +