refactor(core): 重构会议接口并统一代码换行符
- 调整前端会议接口类型,新增 MeetingParticipant 定义 - 统一全项目文件换行符为 LF,清理 CRLF 格式 - 完善 .gitignore 规则与 H5 端基础类型定义dev_na
parent
e3a37757ba
commit
2660f65bd2
|
|
@ -42,6 +42,9 @@ public class MeetingVO {
|
|||
@Schema(description = "参会人ID列表")
|
||||
private List<Long> participantIds;
|
||||
|
||||
@Schema(description = "参会人列表,ID 与名称一一对应")
|
||||
private List<MeetingParticipantVO> participantUsers;
|
||||
|
||||
@Schema(description = "标签串")
|
||||
private String tags;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import com.imeeting.mapper.biz.HotWordGroupMapper;
|
|||
import com.imeeting.mapper.biz.HotWordMapper;
|
||||
import com.imeeting.service.biz.HotWordService;
|
||||
import com.unisbase.common.exception.BusinessException;
|
||||
import com.unisbase.dto.SysDictItemDTO;
|
||||
import com.unisbase.service.SysDictItemService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sourceforge.pinyin4j.PinyinHelper;
|
||||
|
|
@ -35,12 +37,14 @@ import java.util.stream.Collectors;
|
|||
@RequiredArgsConstructor
|
||||
public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> implements HotWordService {
|
||||
|
||||
private static final int MAX_HOT_WORDS_PER_GROUP = 200;
|
||||
private static final String HOT_WORD_GROUP_LIMIT_DICT_TYPE = "biz_hotword_group_limit";
|
||||
private static final int DEFAULT_MAX_HOT_WORDS_PER_GROUP = 200;
|
||||
private static final int DEFAULT_MATCH_STRATEGY = 1;
|
||||
private static final int DEFAULT_WEIGHT = 2;
|
||||
private static final int ENABLED_STATUS = 1;
|
||||
|
||||
private final HotWordGroupMapper hotWordGroupMapper;
|
||||
private final SysDictItemService sysDictItemService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
|
|
@ -201,8 +205,9 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
|||
long currentCount = this.count(new LambdaQueryWrapper<HotWord>()
|
||||
.eq(HotWord::getHotWordGroupId, groupId)
|
||||
.ne(currentHotWordId != null, HotWord::getId, currentHotWordId));
|
||||
if (currentCount >= MAX_HOT_WORDS_PER_GROUP) {
|
||||
throw new BusinessException("热词组最多只能包含 200 个热词");
|
||||
int maxHotWordsPerGroup = getMaxHotWordsPerGroup();
|
||||
if (currentCount >= maxHotWordsPerGroup) {
|
||||
throwGroupCapacityExceeded(maxHotWordsPerGroup);
|
||||
}
|
||||
return group.getId();
|
||||
}
|
||||
|
|
@ -222,8 +227,9 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
|||
long incomingCount = movingHotWords.stream()
|
||||
.filter(item -> !groupId.equals(item.getHotWordGroupId()))
|
||||
.count();
|
||||
if (currentCount + incomingCount > MAX_HOT_WORDS_PER_GROUP) {
|
||||
throw new BusinessException("热词组最多只能包含 200 个热词");
|
||||
int maxHotWordsPerGroup = getMaxHotWordsPerGroup();
|
||||
if (currentCount + incomingCount > maxHotWordsPerGroup) {
|
||||
throwGroupCapacityExceeded(maxHotWordsPerGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -268,8 +274,9 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
|||
}
|
||||
long currentCount = this.count(new LambdaQueryWrapper<HotWord>()
|
||||
.eq(HotWord::getHotWordGroupId, groupId));
|
||||
if (currentCount + incomingCount > MAX_HOT_WORDS_PER_GROUP) {
|
||||
throw new BusinessException("热词组最多只能包含 200 个热词");
|
||||
int maxHotWordsPerGroup = getMaxHotWordsPerGroup();
|
||||
if (currentCount + incomingCount > maxHotWordsPerGroup) {
|
||||
throwGroupCapacityExceeded(maxHotWordsPerGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,6 +295,31 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
|||
return hotWord;
|
||||
}
|
||||
|
||||
private int getMaxHotWordsPerGroup() {
|
||||
List<SysDictItemDTO> items = sysDictItemService.getItemsByTypeCode(HOT_WORD_GROUP_LIMIT_DICT_TYPE);
|
||||
if (items == null || items.isEmpty()) {
|
||||
return DEFAULT_MAX_HOT_WORDS_PER_GROUP;
|
||||
}
|
||||
for (SysDictItemDTO item : items) {
|
||||
if (item == null || item.getItemValue() == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int configuredLimit = Integer.parseInt(item.getItemValue().trim());
|
||||
if (configuredLimit > 0) {
|
||||
return configuredLimit;
|
||||
}
|
||||
} catch (NumberFormatException exception) {
|
||||
log.warn("热词组上限字典配置值非法: {}", item.getItemValue());
|
||||
}
|
||||
}
|
||||
return DEFAULT_MAX_HOT_WORDS_PER_GROUP;
|
||||
}
|
||||
|
||||
private void throwGroupCapacityExceeded(int maxHotWordsPerGroup) {
|
||||
throw new BusinessException("热词组最多只能包含 " + maxHotWordsPerGroup + " 个热词");
|
||||
}
|
||||
|
||||
private void generateCombinations(List<List<String>> matrix, int index, String current, List<String> result) {
|
||||
if (index == matrix.size()) {
|
||||
result.add(current.trim());
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.imeeting.entity.biz.HotWordGroup;
|
|||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.entity.biz.MeetingTranscript;
|
||||
import com.imeeting.entity.biz.PromptTemplate;
|
||||
import com.imeeting.dto.biz.MeetingParticipantVO;
|
||||
import com.imeeting.event.MeetingCreatedEvent;
|
||||
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
||||
import com.imeeting.dto.biz.AiModelVO;
|
||||
|
|
@ -23,6 +24,7 @@ import com.imeeting.service.realtime.RealtimeMeetingAudioStorageService;
|
|||
import com.unisbase.entity.SysUser;
|
||||
import com.unisbase.mapper.SysUserMapper;
|
||||
import com.unisbase.service.SysParamService;
|
||||
import com.unisbase.service.SysTenantUserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
|
@ -47,6 +49,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -61,6 +64,7 @@ public class MeetingDomainSupport {
|
|||
private final MeetingTranscriptMapper transcriptMapper;
|
||||
private final MeetingPointsService meetingPointsService;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final SysTenantUserService sysTenantUserService;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final MeetingSummaryFileService meetingSummaryFileService;
|
||||
private final MeetingPlaybackAudioResolver meetingPlaybackAudioResolver;
|
||||
|
|
@ -460,19 +464,31 @@ public class MeetingDomainSupport {
|
|||
.map(Long::valueOf)
|
||||
.collect(Collectors.toList());
|
||||
vo.setParticipantIds(userIds);
|
||||
vo.setParticipantUsers(Collections.emptyList());
|
||||
if (!userIds.isEmpty()) {
|
||||
List<SysUser> users = sysUserMapper.selectBatchIds(userIds);
|
||||
String names = users.stream()
|
||||
.map(u -> u.getDisplayName() != null ? u.getDisplayName() : u.getUsername())
|
||||
Map<Long, String> userNameMap = users.stream().collect(Collectors.toMap(
|
||||
SysUser::getUserId,
|
||||
user -> resolveParticipantName(user, meeting.getTenantId())
|
||||
));
|
||||
List<MeetingParticipantVO> participantUsers = userIds.stream()
|
||||
.map(userId -> new MeetingParticipantVO(userId, userNameMap.get(userId)))
|
||||
.collect(Collectors.toList());
|
||||
vo.setParticipantUsers(participantUsers);
|
||||
String names = participantUsers.stream()
|
||||
.map(MeetingParticipantVO::getName)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining(", "));
|
||||
vo.setParticipants(names);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
vo.setParticipantIds(Collections.emptyList());
|
||||
vo.setParticipantUsers(Collections.emptyList());
|
||||
vo.setParticipants(meeting.getParticipants());
|
||||
}
|
||||
} else {
|
||||
vo.setParticipantIds(Collections.emptyList());
|
||||
vo.setParticipantUsers(Collections.emptyList());
|
||||
}
|
||||
fillLatestTaskAttemptInfo(meeting, vo);
|
||||
if (includeSummary) {
|
||||
|
|
@ -482,6 +498,13 @@ public class MeetingDomainSupport {
|
|||
}
|
||||
}
|
||||
|
||||
private String resolveParticipantName(SysUser user, Long tenantId) {
|
||||
if (user == null || user.getUserId() == null) {
|
||||
return "";
|
||||
}
|
||||
return user.getDisplayName() != null ? user.getDisplayName() : user.getUsername();
|
||||
}
|
||||
|
||||
private void fillSummaryConfigurationNames(Meeting meeting, com.imeeting.dto.biz.MeetingVO vo) {
|
||||
if (meeting.getSummaryModelId() != null) {
|
||||
AiModelVO summaryModel = aiModelService.getModelById(meeting.getSummaryModelId(), "LLM");
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ const MEETING_DETAIL_TIMEOUT = 120000;
|
|||
export type SummaryDetailLevel = "DETAILED" | "STANDARD" | "BRIEF";
|
||||
export type MeetingSource = "WINDOWS" | "MACOS" | "KYLIN" | "UOS" | "HARMONYOS" | "WEB" | "CUSTOM_TERMINAL" | "ANDROID";
|
||||
|
||||
export interface MeetingParticipant {
|
||||
id: number;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface MeetingCreateConfig {
|
||||
offlineEnabled: boolean;
|
||||
realtimeEnabled: boolean;
|
||||
|
|
@ -27,6 +32,7 @@ export interface MeetingVO {
|
|||
meetingTime: string;
|
||||
participants: string;
|
||||
participantIds?: number[];
|
||||
participantUsers?: MeetingParticipant[];
|
||||
tags: string;
|
||||
audioUrl: string;
|
||||
playbackAudioUrl?: string;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import {
|
|||
uploadAudio,
|
||||
} from "../../api/business/meeting";
|
||||
import { getPromptPage, type PromptTemplateVO } from "../../api/business/prompt";
|
||||
import {useHotWordGroupLimit} from "../../hooks/useHotWordGroupLimit";
|
||||
import type { SysUser } from "../../types";
|
||||
import "./MeetingCreateDrawer.css";
|
||||
|
||||
|
|
@ -121,6 +122,7 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
|
|||
onSuccess,
|
||||
}) => {
|
||||
const { message } = App.useApp();
|
||||
const {limit: hotWordGroupLimit} = useHotWordGroupLimit();
|
||||
const navigate = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
|
|
@ -559,7 +561,15 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
|
|||
<Row gutter={24}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="hotWordGroupId" label="热词组" tooltip={selectedPrompt?.hotWordGroupName ? `默认跟随模板:${selectedPrompt.hotWordGroupName}` : "模板未绑定热词组时可手动选择"} extra={watchedHotWordGroupId != null ? "创建会议时会优先使用这里选中的热词组" : undefined}>
|
||||
<Select placeholder={selectedPrompt?.hotWordGroupId ? "默认已带出模板热词组,可按需修改" : "请选择热词组"} options={[{ label: "不使用热词组", value: 0 }, ...hotWordGroups.map((item) => ({ label: `${item.groupName} (${item.hotWordCount}/200)`, value: item.id }))]} />
|
||||
<Select
|
||||
placeholder={selectedPrompt?.hotWordGroupId ? "默认已带出模板热词组,可按需修改" : "请选择热词组"}
|
||||
options={[{
|
||||
label: "不使用热词组",
|
||||
value: 0
|
||||
}, ...hotWordGroups.map((item) => ({
|
||||
label: `${item.groupName} (${item.hotWordCount}/${hotWordGroupLimit})`,
|
||||
value: item.id
|
||||
}))]}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
} from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDict } from "../../hooks/useDict";
|
||||
import {useHotWordGroupLimit} from "../../hooks/useHotWordGroupLimit";
|
||||
import {
|
||||
deleteHotWord,
|
||||
getHotWordPage,
|
||||
|
|
@ -97,6 +98,7 @@ const HotWords: React.FC = () => {
|
|||
const [groupForm] = Form.useForm<HotWordGroupFormValues>();
|
||||
const [bulkGroupForm] = Form.useForm<BulkGroupFormValues>();
|
||||
const { items: categories } = useDict("biz_hotword_category");
|
||||
const {limit: hotWordGroupLimit} = useHotWordGroupLimit();
|
||||
const userProfile = useMemo(() => {
|
||||
const profileStr = sessionStorage.getItem("userProfile");
|
||||
return profileStr ? JSON.parse(profileStr) : {};
|
||||
|
|
@ -606,8 +608,9 @@ const HotWords: React.FC = () => {
|
|||
item.id
|
||||
? (
|
||||
<span className="hotwords-group-item__desc">
|
||||
<Tag color={item.hotWordCount >= 200 ? "red" : item.status === 1 ? "processing" : "default"}>
|
||||
{item.hotWordCount}/200
|
||||
<Tag
|
||||
color={item.hotWordCount >= hotWordGroupLimit ? "red" : item.status === 1 ? "processing" : "default"}>
|
||||
{item.hotWordCount}/{hotWordGroupLimit}
|
||||
</Tag>
|
||||
<span>{item.remark || "暂无备注"}</span>
|
||||
</span>
|
||||
|
|
@ -766,7 +769,10 @@ const HotWords: React.FC = () => {
|
|||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<Form.Item name="hotWordGroupId" label="所属热词组">
|
||||
<Select placeholder="请选择热词组" allowClear options={groupOptions.map((item) => ({ label: `${item.groupName} (${item.hotWordCount}/200)`, value: item.id }))} />
|
||||
<Select placeholder="请选择热词组" allowClear options={groupOptions.map((item) => ({
|
||||
label: `${item.groupName} (${item.hotWordCount}/${hotWordGroupLimit})`,
|
||||
value: item.id
|
||||
}))}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -843,7 +849,10 @@ const HotWords: React.FC = () => {
|
|||
placeholder="请选择热词组"
|
||||
options={[
|
||||
{ label: "未分组", value: 0 },
|
||||
...groupOptions.map((item) => ({ label: `${item.groupName} (${item.hotWordCount}/200)`, value: item.id })),
|
||||
...groupOptions.map((item) => ({
|
||||
label: `${item.groupName} (${item.hotWordCount}/${hotWordGroupLimit})`,
|
||||
value: item.id
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import {getHotWordGroupOptions, type HotWordGroupVO} from '../../api/business/ho
|
|||
import { getPromptPage, PromptTemplateVO } from '../../api/business/prompt';
|
||||
import { listUsers } from '../../api';
|
||||
import { useDict } from '../../hooks/useDict';
|
||||
import {useHotWordGroupLimit} from '../../hooks/useHotWordGroupLimit';
|
||||
import { SysUser } from '../../types';
|
||||
import PageContainer from "../../components/shared/PageContainer";
|
||||
import SectionCard from "../../components/shared/SectionCard";
|
||||
|
|
@ -1214,6 +1215,7 @@ const ActiveTranscriptRow = React.memo<ActiveTranscriptRowProps>(({
|
|||
|
||||
const MeetingDetail: React.FC = () => {
|
||||
const { message } = App.useApp();
|
||||
const {limit: hotWordGroupLimit, loading: hotWordGroupLimitLoading} = useHotWordGroupLimit();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
|
|
@ -1886,11 +1888,15 @@ const MeetingDetail: React.FC = () => {
|
|||
message.warning('请先选择关键词');
|
||||
return;
|
||||
}
|
||||
if (hotWordGroupLimitLoading) {
|
||||
message.info('热词组上限配置加载中,请稍后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
setHotWordGroupLoading(true);
|
||||
try {
|
||||
const response = await getHotWordGroupOptions();
|
||||
const options = (response.data?.data || []).filter((item) => item.status === 1 && item.hotWordCount < 200);
|
||||
const options = (response.data?.data || []).filter((item) => item.status === 1 && item.hotWordCount < hotWordGroupLimit);
|
||||
setHotWordGroupOptions(options);
|
||||
setSelectedHotWordGroupId(options.some((item) => item.id === meeting?.hotWordGroupId) ? meeting?.hotWordGroupId : options[0]?.id);
|
||||
setHotWordGroupModalOpen(true);
|
||||
|
|
@ -4262,7 +4268,7 @@ const MeetingDetail: React.FC = () => {
|
|||
onChange={setSelectedHotWordGroupId}
|
||||
options={[
|
||||
...hotWordGroupOptions.map((item) => ({
|
||||
label: `${item.groupName} (${item.hotWordCount}/200)`,
|
||||
label: `${item.groupName} (${item.hotWordCount}/${hotWordGroupLimit})`,
|
||||
value: item.id,
|
||||
})),
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import ReactMarkdown from 'react-markdown';
|
|||
import remarkGfm from 'remark-gfm';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDict } from '../../hooks/useDict';
|
||||
import {useHotWordGroupLimit} from '../../hooks/useHotWordGroupLimit';
|
||||
import {
|
||||
deletePromptTemplate,
|
||||
clearPromptDefault,
|
||||
|
|
@ -69,6 +70,7 @@ const PromptTemplates: React.FC = () => {
|
|||
const { items: categories, loading: dictLoading } = useDict('biz_prompt_category');
|
||||
const { items: dictTags } = useDict('biz_prompt_tag');
|
||||
const { items: promptLevels } = useDict('biz_prompt_level');
|
||||
const {limit: hotWordGroupLimit} = useHotWordGroupLimit();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<PromptTemplateVO[]>([]);
|
||||
|
|
@ -585,7 +587,10 @@ const PromptTemplates: React.FC = () => {
|
|||
<Select
|
||||
placeholder="请选择热词组"
|
||||
allowClear
|
||||
options={groupOptions.map((item) => ({ label: `${item.groupName} (${item.hotWordCount}/200)`, value: item.id }))}
|
||||
options={groupOptions.map((item) => ({
|
||||
label: `${item.groupName} (${item.hotWordCount}/${hotWordGroupLimit})`,
|
||||
value: item.id
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ export interface TokenResponse {
|
|||
refreshExpiresInDays: number;
|
||||
}
|
||||
|
||||
export interface MeetingParticipant {
|
||||
id: number;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface MeetingVO {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
|
|
@ -36,6 +41,7 @@ export interface MeetingVO {
|
|||
meetingTime: string;
|
||||
participants: string;
|
||||
participantIds?: number[];
|
||||
participantUsers?: MeetingParticipant[];
|
||||
tags: string;
|
||||
audioUrl: string;
|
||||
playbackAudioUrl?: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue