前端优化
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, nextTick,onUnmounted } from 'vue'
|
||||
import { renderMarkdown } from '@/utils/markdown'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -17,93 +17,94 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
// 当前渲染的内容(避免重复渲染)
|
||||
const currentContent = ref('')
|
||||
// 渲染的 HTML 内容
|
||||
// 内部维护的纯文本内容(用于计算增量和渲染源)
|
||||
const internalContent = ref('')
|
||||
// 最终渲染的 HTML
|
||||
const renderedContent = ref('')
|
||||
|
||||
/**
|
||||
* 更新渲染内容
|
||||
* 只有当内容真正改变时才更新,避免重复渲染
|
||||
*/
|
||||
function updateRenderedContent() {
|
||||
const content = currentContent.value
|
||||
let debounceTimer = null
|
||||
|
||||
// 避免重复渲染相同内容
|
||||
if (content === renderedContent.value.replace(/<[^>]*>/g, '')) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
renderedContent.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 渲染 markdown 为 HTML
|
||||
renderedContent.value = renderMarkdown(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理内容更新
|
||||
* 流式渲染:需要拼接增量内容
|
||||
*/
|
||||
function handleContentUpdate(newContent) {
|
||||
if (!newContent) {
|
||||
currentContent.value = ''
|
||||
updateRenderedContent()
|
||||
return
|
||||
}
|
||||
|
||||
// 流式模式下拼接增量内容
|
||||
if (props.isStreaming) {
|
||||
currentContent.value += newContent
|
||||
// 仅在流式时计算并追加增量
|
||||
function appendStreamingDelta(newFullContent) {
|
||||
const prev = internalContent.value
|
||||
if (newFullContent.startsWith(prev)) {
|
||||
// 正常情况:新内容包含旧内容 → 只追加差值
|
||||
const delta = newFullContent.slice(prev.length)
|
||||
internalContent.value += delta
|
||||
} else {
|
||||
// 非流式模式下直接替换
|
||||
currentContent.value = newContent
|
||||
// 异常情况(如后端重发了不连续的内容),直接覆盖防止乱序
|
||||
console.warn('[PromptDisplay] Streaming content out of order, forcing replace')
|
||||
internalContent.value = newFullContent
|
||||
}
|
||||
|
||||
updateRenderedContent()
|
||||
}
|
||||
|
||||
// 监听 content 变化,使用防抖处理避免频繁更新
|
||||
let updateTimeout = null
|
||||
watch(() => props.content, (newContent) => {
|
||||
// 清除之前的定时器
|
||||
if (updateTimeout) {
|
||||
clearTimeout(updateTimeout)
|
||||
// 更新 Markdown 渲染
|
||||
async function updateRendered() {
|
||||
if (!internalContent.value) {
|
||||
renderedContent.value = ''
|
||||
return
|
||||
}
|
||||
// renderMarkdown 可能包含异步操作(如 highlight.js),用 nextTick 确保 DOM 就绪
|
||||
renderedContent.value = await renderMarkdown(internalContent.value)
|
||||
}
|
||||
|
||||
// 主更新逻辑
|
||||
function updateContent(newContent = '') {
|
||||
if (props.isStreaming) {
|
||||
appendStreamingDelta(newContent)
|
||||
} else {
|
||||
internalContent.value = newContent
|
||||
}
|
||||
|
||||
// 延迟更新,避免流式传输时频繁更新导致的性能问题
|
||||
updateTimeout = setTimeout(() => {
|
||||
handleContentUpdate(newContent)
|
||||
}, 50) // 50ms 防抖
|
||||
})
|
||||
// 流式直接渲染,非流式防抖(避免频繁完整替换导致光标跳动或闪烁)
|
||||
if (props.isStreaming) {
|
||||
updateRendered()
|
||||
} else {
|
||||
clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => {
|
||||
updateRendered()
|
||||
}, 60) // 60ms 足够平滑且不卡顿
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 isStreaming 变化
|
||||
// 监听 content 变化(外部每次推送的都是当前完整内容)
|
||||
watch(() => props.content, (newVal) => {
|
||||
updateContent(newVal || '')
|
||||
}, { immediate: true })
|
||||
|
||||
// 当流式开始时清空,防止旧内容残留
|
||||
watch(() => props.isStreaming, (newVal, oldVal) => {
|
||||
// 流式传输开始时,清空之前的内容
|
||||
if (newVal && !oldVal) {
|
||||
currentContent.value = ''
|
||||
internalContent.value = ''
|
||||
renderedContent.value = ''
|
||||
}
|
||||
// 流式传输结束时,确保显示完整内容
|
||||
if (!newVal && oldVal && props.content) {
|
||||
currentContent.value = props.content
|
||||
updateRenderedContent()
|
||||
}
|
||||
})
|
||||
|
||||
// 立即渲染初始内容
|
||||
handleContentUpdate(props.content)
|
||||
// 可选:组件卸载时清理定时器
|
||||
onUnmounted(() => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 修复 pre 标签撑开容器的问题 */
|
||||
.prompt-display {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 代码块优化 */
|
||||
.prompt-display :deep(pre) {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
background: #282c34;
|
||||
color: #abb2bf;
|
||||
padding: 1em;
|
||||
border-radius: 8px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
.prompt-display :deep(code) {
|
||||
font-family: 'Fira Code', Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -32,7 +32,6 @@ const editForm = reactive({
|
||||
content: '',
|
||||
category: '',
|
||||
status: 1,
|
||||
_originalRecord: null, // 保存原始记录,用于更新时获取必需字段
|
||||
})
|
||||
const editFormRef = ref(null)
|
||||
|
||||
@@ -173,12 +172,7 @@ function handleReset() {
|
||||
|
||||
// 新增
|
||||
function handleAdd() {
|
||||
editForm.id = null
|
||||
editForm.name = ''
|
||||
editForm.content = ''
|
||||
editForm.category = ''
|
||||
editForm.status = 1
|
||||
editForm._originalRecord = null
|
||||
resetEditForm()
|
||||
editModalVisible.value = true
|
||||
}
|
||||
|
||||
@@ -188,62 +182,72 @@ function handleEdit(record) {
|
||||
editForm.name = record.name || ''
|
||||
editForm.content = record.content || ''
|
||||
editForm.category = record.category || ''
|
||||
editForm.status = record.status ?? 1
|
||||
// 保存原始记录的完整信息,用于更新时传递必需字段
|
||||
editForm._originalRecord = record
|
||||
editForm.status = record.status !== null && record.status !== undefined ? record.status : 1
|
||||
editModalVisible.value = true
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
function resetEditForm() {
|
||||
editForm.id = null
|
||||
editForm.name = ''
|
||||
editForm.content = ''
|
||||
editForm.category = ''
|
||||
editForm.status = 1
|
||||
}
|
||||
|
||||
// 通用API调用
|
||||
async function apiCall(apiFunc, param, successMessage, isDelete = false) {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await apiFunc(param)
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
message.success(successMessage)
|
||||
if (!isDelete) {
|
||||
editModalVisible.value = false
|
||||
}
|
||||
loadData()
|
||||
return true
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error)
|
||||
message.error(error?.message || '操作失败,请稍后重试')
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存(新增/编辑)
|
||||
async function handleSave() {
|
||||
try {
|
||||
await editFormRef.value.validate()
|
||||
} catch (error) {
|
||||
console.error('保存提示词失败:', error)
|
||||
console.error('表单验证失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const payload = {
|
||||
name: editForm.name.trim(),
|
||||
content: editForm.content.trim(),
|
||||
category: editForm.category.trim() || null,
|
||||
status: editForm.status,
|
||||
}
|
||||
const payload = {
|
||||
name: editForm.name.trim(),
|
||||
content: editForm.content.trim(),
|
||||
category: editForm.category.trim() || null,
|
||||
status: editForm.status,
|
||||
}
|
||||
|
||||
if (editForm.id) {
|
||||
// 更新:只需要传递要修改的字段,后端会自动填充其他字段
|
||||
payload.id = editForm.id
|
||||
// 注意:sort、useCount、isPublic 等字段后端会自动从数据库获取,无需前端传递
|
||||
|
||||
const response = await UserPromptApi.updateUserPrompt(payload)
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
message.success('更新成功')
|
||||
editModalVisible.value = false
|
||||
loadData()
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '更新失败')
|
||||
}
|
||||
} else {
|
||||
// 新增:需要包含所有必需字段
|
||||
payload.sort = 0
|
||||
payload.useCount = 0
|
||||
payload.isPublic = false
|
||||
|
||||
const response = await UserPromptApi.createUserPrompt(payload)
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
message.success('创建成功')
|
||||
editModalVisible.value = false
|
||||
loadData()
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '创建失败')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存提示词失败:', error)
|
||||
message.error(error?.message || '保存失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (editForm.id) {
|
||||
payload.id = editForm.id
|
||||
await apiCall(
|
||||
(data) => UserPromptApi.updateUserPrompt(data),
|
||||
payload,
|
||||
'更新成功'
|
||||
)
|
||||
} else {
|
||||
await apiCall(
|
||||
(data) => UserPromptApi.createUserPrompt(data),
|
||||
payload,
|
||||
'创建成功'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,21 +257,12 @@ function handleDelete(record) {
|
||||
title: '确认删除',
|
||||
content: `确定要删除提示词"${record.name}"吗?`,
|
||||
onOk: async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await UserPromptApi.deleteUserPrompt(record.id)
|
||||
if (response && (response.code === 0 || response.code === 200)) {
|
||||
message.success('删除成功')
|
||||
loadData()
|
||||
} else {
|
||||
throw new Error(response?.msg || response?.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除提示词失败:', error)
|
||||
message.error(error?.message || '删除失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
await apiCall(
|
||||
(id) => UserPromptApi.deleteUserPrompt(id),
|
||||
record.id,
|
||||
'删除成功',
|
||||
true
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.tik.userprompt.controller.app;
|
||||
package cn.iocoder.yudao.module.tik.userprompt.controller;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
@@ -23,7 +23,7 @@ import static cn.iocoder.yudao.module.tik.enums.ErrorCodeConstants.USER_PROMPT_N
|
||||
|
||||
@Tag(name = "用户 App - 用户提示词")
|
||||
@RestController
|
||||
@RequestMapping("/ai/user-prompt")
|
||||
@RequestMapping("/api/ai/user-prompt")
|
||||
@Validated
|
||||
public class AppUserPromptController {
|
||||
|
||||
@@ -29,17 +29,16 @@ public class UserPromptServiceImpl implements UserPromptService {
|
||||
|
||||
@Override
|
||||
public Long createUserPrompt(UserPromptSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
UserPromptDO userPrompt = BeanUtils.toBean(createReqVO, UserPromptDO.class);
|
||||
if (userPrompt.getSort() == null) userPrompt.setSort(0);
|
||||
if (userPrompt.getUseCount() == null) userPrompt.setUseCount(0);
|
||||
if (userPrompt.getIsPublic() == null) userPrompt.setIsPublic(false);
|
||||
userPromptMapper.insert(userPrompt);
|
||||
|
||||
// 返回
|
||||
return userPrompt.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserPrompt(UserPromptSaveReqVO updateReqVO) {
|
||||
// 1. 手动验证前端表单字段(与前端表单对应)
|
||||
if (updateReqVO.getName() == null || updateReqVO.getName().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("提示词名称不能为空");
|
||||
}
|
||||
@@ -49,42 +48,32 @@ public class UserPromptServiceImpl implements UserPromptService {
|
||||
if (updateReqVO.getStatus() == null) {
|
||||
throw new IllegalArgumentException("状态不能为空");
|
||||
}
|
||||
|
||||
// 2. 校验存在并获取现有记录
|
||||
|
||||
UserPromptDO existing = validateUserPromptExists(updateReqVO.getId());
|
||||
|
||||
// 3. 手动设置要更新的字段(只更新前端表单中的字段)
|
||||
UserPromptDO updateObj = new UserPromptDO();
|
||||
updateObj.setId(updateReqVO.getId());
|
||||
updateObj.setName(updateReqVO.getName().trim());
|
||||
updateObj.setContent(updateReqVO.getContent().trim());
|
||||
updateObj.setCategory(updateReqVO.getCategory() != null ? updateReqVO.getCategory().trim() : null);
|
||||
updateObj.setStatus(updateReqVO.getStatus());
|
||||
|
||||
// 4. 自动填充前端表单中没有的字段(从数据库获取)
|
||||
updateObj.setSort(existing.getSort());
|
||||
updateObj.setUseCount(existing.getUseCount());
|
||||
updateObj.setIsPublic(existing.getIsPublic());
|
||||
updateObj.setUserId(existing.getUserId()); // 保持用户ID不变
|
||||
|
||||
// 5. 执行更新
|
||||
updateObj.setUserId(existing.getUserId());
|
||||
|
||||
userPromptMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUserPrompt(Long id) {
|
||||
// 校验存在
|
||||
validateUserPromptExists(id);
|
||||
// 删除
|
||||
userPromptMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUserPromptListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
public void deleteUserPromptListByIds(List<Long> ids) {
|
||||
userPromptMapper.deleteByIds(ids);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private UserPromptDO validateUserPromptExists(Long id) {
|
||||
UserPromptDO userPrompt = userPromptMapper.selectById(id);
|
||||
@@ -103,5 +92,4 @@ public class UserPromptServiceImpl implements UserPromptService {
|
||||
public PageResult<UserPromptDO> getUserPromptPage(UserPromptPageReqVO pageReqVO) {
|
||||
return userPromptMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,16 +27,13 @@ public class UserPromptSaveReqVO {
|
||||
@Schema(description = "分类/标签")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "是否公开(0-私有,1-公开)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "是否公开(0-私有,1-公开)不能为空")
|
||||
@Schema(description = "是否公开(0-私有,1-公开)")
|
||||
private Boolean isPublic;
|
||||
|
||||
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "排序不能为空")
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "使用次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "22185")
|
||||
@NotNull(message = "使用次数不能为空")
|
||||
@Schema(description = "使用次数")
|
||||
private Integer useCount;
|
||||
|
||||
@Schema(description = "状态(0-禁用,1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
|
||||
@@ -16,7 +16,9 @@ import cn.iocoder.yudao.module.tik.file.dal.dataobject.TikUserFileDO;
|
||||
import cn.iocoder.yudao.module.tik.file.dal.mysql.TikUserFileMapper;
|
||||
import cn.iocoder.yudao.module.tik.file.service.TikOssInitService;
|
||||
import cn.iocoder.yudao.module.tik.voice.dal.dataobject.TikDigitalHumanTaskDO;
|
||||
import cn.iocoder.yudao.module.tik.voice.dal.dataobject.TikUserVoiceDO;
|
||||
import cn.iocoder.yudao.module.tik.voice.dal.mysql.TikDigitalHumanTaskMapper;
|
||||
import cn.iocoder.yudao.module.tik.voice.dal.mysql.TikUserVoiceMapper;
|
||||
import cn.iocoder.yudao.module.tik.voice.enums.DigitalHumanTaskStatusEnum;
|
||||
import cn.iocoder.yudao.module.tik.voice.enums.DigitalHumanTaskStepEnum;
|
||||
import cn.iocoder.yudao.module.tik.voice.vo.*;
|
||||
@@ -48,6 +50,7 @@ public class DigitalHumanTaskServiceImpl implements DigitalHumanTaskService {
|
||||
|
||||
private final TikDigitalHumanTaskMapper taskMapper;
|
||||
private final TikUserFileMapper userFileMapper;
|
||||
private final TikUserVoiceMapper userVoiceMapper;
|
||||
private final FileMapper fileMapper;
|
||||
private final FileApi fileApi;
|
||||
private final TikUserVoiceService userVoiceService;
|
||||
@@ -224,9 +227,21 @@ public class DigitalHumanTaskServiceImpl implements DigitalHumanTaskService {
|
||||
throw new IllegalArgumentException("文案不能为空");
|
||||
}
|
||||
|
||||
// 验证音色ID(必填)
|
||||
if (StrUtil.isBlank(reqVO.getVoiceId())) {
|
||||
throw new IllegalArgumentException("音色ID不能为空");
|
||||
// 验证音色参数(二选一:voiceId用于系统音色,voiceConfigId用于用户音色)
|
||||
boolean hasVoiceId = StrUtil.isNotBlank(reqVO.getVoiceId());
|
||||
boolean hasVoiceConfigId = reqVO.getVoiceConfigId() != null;
|
||||
|
||||
if (!hasVoiceId && !hasVoiceConfigId) {
|
||||
throw new IllegalArgumentException("必须提供音色ID(voiceId或voiceConfigId)");
|
||||
}
|
||||
|
||||
if (hasVoiceId && hasVoiceConfigId) {
|
||||
throw new IllegalArgumentException("voiceId和voiceConfigId不能同时提供");
|
||||
}
|
||||
|
||||
// 如果是用户音色,验证voiceConfigId对应的用户音色是否存在
|
||||
if (hasVoiceConfigId) {
|
||||
validateUserVoice(reqVO.getVoiceConfigId(), userId);
|
||||
}
|
||||
|
||||
// 验证视频文件(必填)
|
||||
@@ -253,17 +268,44 @@ public class DigitalHumanTaskServiceImpl implements DigitalHumanTaskService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户音色
|
||||
*/
|
||||
private void validateUserVoice(Long voiceConfigId, Long userId) {
|
||||
TikUserVoiceDO userVoice = userVoiceMapper.selectById(voiceConfigId);
|
||||
if (userVoice == null) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.GENERAL_NOT_EXISTS, "用户音色不存在");
|
||||
}
|
||||
|
||||
if (!userVoice.getUserId().equals(userId)) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.GENERAL_FORBIDDEN, "无权访问该音色");
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(userVoice.getVoiceId())) {
|
||||
throw new IllegalArgumentException("该音色配置无效,缺少voiceId");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建任务记录
|
||||
*/
|
||||
private TikDigitalHumanTaskDO createTaskRecord(AppTikDigitalHumanCreateReqVO reqVO, Long userId) {
|
||||
// 如果是用户音色,需要从voiceConfigId获取voiceId
|
||||
String voiceId = reqVO.getVoiceId();
|
||||
if (voiceId == null && reqVO.getVoiceConfigId() != null) {
|
||||
TikUserVoiceDO userVoice = userVoiceMapper.selectById(reqVO.getVoiceConfigId());
|
||||
if (userVoice != null) {
|
||||
voiceId = userVoice.getVoiceId();
|
||||
}
|
||||
}
|
||||
|
||||
return TikDigitalHumanTaskDO.builder()
|
||||
.userId(userId)
|
||||
.taskName(reqVO.getTaskName())
|
||||
.aiProvider(StrUtil.blankToDefault(reqVO.getAiProvider(), "302ai"))
|
||||
.videoFileId(reqVO.getVideoFileId())
|
||||
.videoUrl(reqVO.getVideoUrl())
|
||||
.voiceId(reqVO.getVoiceId())
|
||||
.voiceId(voiceId)
|
||||
.inputText(reqVO.getInputText())
|
||||
.speechRate(reqVO.getSpeechRate() != null ? reqVO.getSpeechRate() : 1.0f)
|
||||
.volume(reqVO.getVolume() != null ? reqVO.getVolume() : 0f)
|
||||
|
||||
@@ -33,9 +33,12 @@ public class AppTikDigitalHumanCreateReqVO {
|
||||
@Size(max = 1024, message = "视频URL不能超过1024个字符")
|
||||
private String videoUrl;
|
||||
|
||||
@Schema(description = "音色ID(CosyVoice voiceId)", example = "cosyvoice-v3-flash-sys-xxx")
|
||||
@Schema(description = "音色ID(CosyVoice voiceId,系统音色使用)", example = "cosyvoice-v3-flash-sys-xxx")
|
||||
private String voiceId;
|
||||
|
||||
@Schema(description = "用户音色配置ID(tik_user_voice.id,用户音色使用)", example = "123")
|
||||
private Long voiceConfigId;
|
||||
|
||||
@Schema(description = "输入文本(用于语音合成,文案必填)", example = "您好,欢迎体验数字人")
|
||||
@NotBlank(message = "文案不能为空")
|
||||
@Size(max = 4000, message = "文本不能超过4000个字符")
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.tik.voice.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.eclipse.angus.mail.iap.ByteArray;
|
||||
|
||||
/**
|
||||
* Latentsync 任务结果响应 VO
|
||||
@@ -21,6 +22,10 @@ public class AppTikLatentsyncResultRespVO {
|
||||
@Schema(description = "视频信息")
|
||||
private VideoInfo video;
|
||||
|
||||
@Schema(description = "视频流信息")
|
||||
private ByteArray value;
|
||||
|
||||
|
||||
@Schema(description = "视频信息")
|
||||
@Data
|
||||
public static class VideoInfo {
|
||||
|
||||
Reference in New Issue
Block a user