This commit is contained in:
2026-02-24 00:38:23 +08:00
parent f63e6b92bd
commit d69a99882f
12 changed files with 133 additions and 77 deletions

View File

@@ -70,7 +70,8 @@ async function handleAnalyzeUser() {
hasMore.value = false hasMore.value = false
await clearData() await clearData()
const req = await TikhubService.postTikHup({ try {
const resp = await TikhubService.postTikHup({
type: InterfaceType.DOUYIN_WEB_USER_POST_VIDEOS, type: InterfaceType.DOUYIN_WEB_USER_POST_VIDEOS,
methodType: MethodType.GET, methodType: MethodType.GET,
urlParams: { urlParams: {
@@ -82,8 +83,6 @@ async function handleAnalyzeUser() {
}, },
}) })
try {
const resp = await req
const result = processApiResponse(resp, form.value.platform) const result = processApiResponse(resp, form.value.platform)
maxCursor.value = result.maxCursor maxCursor.value = result.maxCursor
hasMore.value = result.hasMore hasMore.value = result.hasMore
@@ -190,7 +189,9 @@ async function handleLoadMore() {
} }
loadingMore.value = true loadingMore.value = true
const req = await TikhubService.postTikHup({
try {
const resp = await TikhubService.postTikHup({
type: InterfaceType.DOUYIN_WEB_USER_POST_VIDEOS, type: InterfaceType.DOUYIN_WEB_USER_POST_VIDEOS,
methodType: MethodType.GET, methodType: MethodType.GET,
urlParams: { urlParams: {
@@ -202,9 +203,6 @@ async function handleLoadMore() {
}, },
}) })
try {
const resp = await req
if (form.value.platform === '抖音') { if (form.value.platform === '抖音') {
const { mapFromDouyin } = await import('./utils/benchmarkUtils') const { mapFromDouyin } = await import('./utils/benchmarkUtils')
const awemeList = resp?.data?.aweme_list || [] const awemeList = resp?.data?.aweme_list || []

View File

@@ -58,7 +58,7 @@ public class DifyServiceImpl implements DifyService {
: AiModelTypeEnum.DIFY_WRITING_PRO; : AiModelTypeEnum.DIFY_WRITING_PRO;
AiModelConfigDO config = pointsService.getConfig( AiModelConfigDO config = pointsService.getConfig(
AiPlatformEnum.DIFY.getPlatform(), AiPlatformEnum.DIFY.getPlatform(),
modelTypeEnum.getModelType()); modelTypeEnum.getModelCode());
// 3. 预检积分 // 3. 预检积分
pointsService.checkPoints(userId, config.getConsumePoints()); pointsService.checkPoints(userId, config.getConsumePoints());

View File

@@ -17,27 +17,27 @@ import java.util.Arrays;
public enum AiModelTypeEnum implements ArrayValuable<String> { public enum AiModelTypeEnum implements ArrayValuable<String> {
// ========== Dify 写作模型 ========== // ========== Dify 写作模型 ==========
DIFY_WRITING_PRO("writing_pro", "Pro深度版", AiPlatformEnum.DIFY), DIFY_WRITING_PRO("writing_pro", "Pro深度版", AiPlatformEnum.DIFY, "text"),
DIFY_WRITING_STANDARD("writing_standard", "标准版", AiPlatformEnum.DIFY), DIFY_WRITING_STANDARD("writing_standard", "标准版", AiPlatformEnum.DIFY, "text"),
// ========== 数字人模型 ========== // ========== 数字人模型 ==========
DIGITAL_HUMAN_LATENTSYNC("latentsync", "LatentSync", AiPlatformEnum.DIGITAL_HUMAN), DIGITAL_HUMAN_LATENTSYNC("latentsync", "LatentSync", AiPlatformEnum.DIGITAL_HUMAN, "video"),
DIGITAL_HUMAN_KLING("kling", "可灵", AiPlatformEnum.DIGITAL_HUMAN), DIGITAL_HUMAN_KLING("kling", "可灵", AiPlatformEnum.DIGITAL_HUMAN, "video"),
// ========== TikHub 爬虫 ========== // ========== TikHub 爬虫 ==========
TIKHUB_CRAWLER("crawler", "爬虫", AiPlatformEnum.TIKHUB), TIKHUB_CRAWLER("crawler", "爬虫", AiPlatformEnum.TIKHUB, "third"),
// ========== 阿里云语音服务 ========== // ========== 阿里云语音服务 ==========
ALICLOUD_VOICE_TO_TEXT("voice_to_text", "语音转文字", AiPlatformEnum.ALICLOUD), ALICLOUD_VOICE_TO_TEXT("voice_to_text", "语音转文字", AiPlatformEnum.ALICLOUD, "audio"),
// ========== SiliconFlow 语音服务 ========== // ========== SiliconFlow 语音服务 ==========
SILICONFLOW_INDEXTTS("indextts", "IndexTTS", AiPlatformEnum.SILICONFLOW), SILICONFLOW_INDEXTTS("indextts", "IndexTTS", AiPlatformEnum.SILICONFLOW, "audio"),
; ;
/** /**
* 模型类型标识 * 模型标识(业务类型)
*/ */
private final String modelType; private final String modelCode;
/** /**
* 模型类型名称 * 模型类型名称
*/ */
@@ -46,8 +46,12 @@ public enum AiModelTypeEnum implements ArrayValuable<String> {
* 所属平台 * 所属平台
*/ */
private final AiPlatformEnum platform; private final AiPlatformEnum platform;
/**
* 模型类型(媒体分类)
*/
private final String modelType;
public static final String[] ARRAYS = Arrays.stream(values()).map(AiModelTypeEnum::getModelType).toArray(String[]::new); public static final String[] ARRAYS = Arrays.stream(values()).map(AiModelTypeEnum::getModelCode).toArray(String[]::new);
@Override @Override
public String[] array() { public String[] array() {
@@ -55,11 +59,11 @@ public enum AiModelTypeEnum implements ArrayValuable<String> {
} }
/** /**
* 根据模型类型标识获取枚举 * 根据模型标识获取枚举
*/ */
public static AiModelTypeEnum valueOfModelType(String modelType) { public static AiModelTypeEnum valueOfModelCode(String modelCode) {
return Arrays.stream(values()) return Arrays.stream(values())
.filter(e -> e.getModelType().equals(modelType)) .filter(e -> e.getModelCode().equals(modelCode))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }

View File

@@ -40,12 +40,12 @@ public interface AiModelConfigMapper extends BaseMapperX<AiModelConfigDO> {
} }
/** /**
* 根据平台和模型类型查询配置 * 根据平台和模型标识查询配置
*/ */
default AiModelConfigDO selectByPlatformAndModelType(String platform, String modelType) { default AiModelConfigDO selectByPlatformAndModelCode(String platform, String modelCode) {
return selectOne(new LambdaQueryWrapperX<AiModelConfigDO>() return selectOne(new LambdaQueryWrapperX<AiModelConfigDO>()
.eq(AiModelConfigDO::getPlatform, platform) .eq(AiModelConfigDO::getPlatform, platform)
.eq(AiModelConfigDO::getModelType, modelType) .eq(AiModelConfigDO::getModelCode, modelCode)
.eq(AiModelConfigDO::getStatus, 1)); .eq(AiModelConfigDO::getStatus, 1));
} }

View File

@@ -55,16 +55,13 @@ public class AiModelConfigSaveReqVO {
@Schema(description = "最大文本数量") @Schema(description = "最大文本数量")
private Integer maxTextLength; private Integer maxTextLength;
@Schema(description = "图片最大像素", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "图片最大像素")
@NotEmpty(message = "图片最大像素不能为空")
private String maxImageSize; private String maxImageSize;
@Schema(description = "视频最大时长(秒)", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "视频最大时长(秒)")
@NotNull(message = "视频最大时长(秒)不能为空")
private Integer maxVideoDuration; private Integer maxVideoDuration;
@Schema(description = "视频最大质量", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "视频最大质量")
@NotEmpty(message = "视频最大质量不能为空")
private String maxVideoQuality; private String maxVideoQuality;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便") @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")

View File

@@ -13,10 +13,10 @@ public interface PointsService {
* 获取积分配置 * 获取积分配置
* *
* @param platform 平台标识dify/tikhub/voice/digital_human * @param platform 平台标识dify/tikhub/voice/digital_human
* @param modelType 模型类型 * @param modelCode 模型标识
* @return 积分配置 * @return 积分配置
*/ */
AiModelConfigDO getConfig(String platform, String modelType); AiModelConfigDO getConfig(String platform, String modelCode);
/** /**
* 预检积分(余额不足抛异常) * 预检积分(余额不足抛异常)

View File

@@ -44,8 +44,8 @@ public class PointsServiceImpl implements PointsService {
private PointRecordMapper pointRecordMapper; private PointRecordMapper pointRecordMapper;
@Override @Override
public AiModelConfigDO getConfig(String platform, String modelType) { public AiModelConfigDO getConfig(String platform, String modelCode) {
AiModelConfigDO config = aiModelConfigMapper.selectByPlatformAndModelType(platform, modelType); AiModelConfigDO config = aiModelConfigMapper.selectByPlatformAndModelCode(platform, modelCode);
if (config == null) { if (config == null) {
throw exception(POINTS_CONFIG_NOT_FOUND); throw exception(POINTS_CONFIG_NOT_FOUND);
} }

View File

@@ -26,8 +26,13 @@ import io.reactivex.Flowable;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.concurrent.TimeUnit;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import java.time.Duration; import java.time.Duration;
@@ -43,7 +48,11 @@ public class TikHupServiceImpl implements TikHupService {
// 平台和模型标识 // 平台和模型标识
private static final String PLATFORM_TIKHUB = "tikhub"; private static final String PLATFORM_TIKHUB = "tikhub";
private static final String MODEL_TYPE_CRAWLER = "crawler"; private static final String MODEL_CODE_CRAWLER = "crawler";
// 缓存配置
private static final String CACHE_KEY_PREFIX = "tikhub:cache:";
private static final long CACHE_EXPIRE_HOURS = 24;
// API 配置(建议后续迁移到配置文件) // API 配置(建议后续迁移到配置文件)
private static final String ALIYUN_API_URL = "https://dashscope.aliyuncs.com/api/v1"; private static final String ALIYUN_API_URL = "https://dashscope.aliyuncs.com/api/v1";
@@ -63,6 +72,7 @@ public class TikHupServiceImpl implements TikHupService {
private final TikTokenMapper tikTokenMapper; private final TikTokenMapper tikTokenMapper;
private final TikPromptMapper tikPromptMapper; private final TikPromptMapper tikPromptMapper;
private final PointsService pointsService; private final PointsService pointsService;
private final StringRedisTemplate stringRedisTemplate;
@Override @Override
public Object postTikHup(String type, String methodType, String urlParams, String paramType) { public Object postTikHup(String type, String methodType, String urlParams, String paramType) {
@@ -81,25 +91,39 @@ public class TikHupServiceImpl implements TikHupService {
String userId = loginUserId != null ? loginUserId.toString() : "1"; String userId = loginUserId != null ? loginUserId.toString() : "1";
AiModelConfigDO config; AiModelConfigDO config;
try { try {
config = pointsService.getConfig(PLATFORM_TIKHUB, MODEL_TYPE_CRAWLER); config = pointsService.getConfig(PLATFORM_TIKHUB, MODEL_CODE_CRAWLER);
pointsService.checkPoints(userId, config.getConsumePoints()); pointsService.checkPoints(userId, config.getConsumePoints());
} catch (Exception e) { } catch (Exception e) {
log.error("[postTikHup] 积分预检失败: {}", e.getMessage()); log.error("[postTikHup] 积分预检失败: {}", e.getMessage());
return CommonResult.error(400, e.getMessage()); return CommonResult.error(400, e.getMessage());
} }
// 3. 从 muye_ai_model_config 获取 apiKey // 3. 尝试从缓存获取结果(缓存命中也要扣积分)
String cacheKey = buildCacheKey(type, methodType, urlParams, paramType);
String cachedResult = stringRedisTemplate.opsForValue().get(cacheKey);
if (StringUtils.isNotBlank(cachedResult)) {
log.info("[postTikHup] 命中缓存, cacheKey: {}", cacheKey);
// 缓存命中也扣积分
deductPointsSafely(userId, config.getConsumePoints(), type);
try {
return JSON.parseObject(cachedResult);
} catch (Exception e) {
return cachedResult;
}
}
// 4. 从 muye_ai_model_config 获取 apiKey
String authorization = config.getApiKey(); String authorization = config.getApiKey();
if (StringUtils.isBlank(authorization)) { if (StringUtils.isBlank(authorization)) {
log.error("postTikHup: TikHub 配置的 apiKey 为空"); log.error("postTikHup: TikHub 配置的 apiKey 为空");
return CommonResult.error(500, "接口配置错误apiKey 为空"); return CommonResult.error(500, "接口配置错误apiKey 为空");
} }
// 4. 获取接口 URL // 5. 获取接口 URL(从 tik_token 表)
TikTokenVO tikTokenVO = tikTokenMapper.getInterfaceUrl(type); TikTokenVO tikTokenVO = tikTokenMapper.getInterfaceUrl(type);
if (tikTokenVO == null) { if (tikTokenVO == null) {
log.error("postTikHup: 未找到接口类型 {} 的 URL 配置", type); log.error("postTikHup: 未找到接口类型 {} 的 URL 配置", type);
return CommonResult.error(404, "未找到接口类型 " + type + " URL 配置"); return CommonResult.error(404, "未找到接口类型 " + type + " URL 配置");
} }
String url = tikTokenVO.getPlatformUrl(); String url = tikTokenVO.getPlatformUrl();
if (StringUtils.isBlank(url)) { if (StringUtils.isBlank(url)) {
@@ -107,7 +131,7 @@ public class TikHupServiceImpl implements TikHupService {
return CommonResult.error(500, "接口配置错误URL 为空"); return CommonResult.error(500, "接口配置错误URL 为空");
} }
// 5. 统一转换为小写进行比较 // 6. 统一转换为小写进行比较
String methodTypeLower = methodType.toLowerCase(); String methodTypeLower = methodType.toLowerCase();
String paramTypeLower = paramType != null ? paramType.toLowerCase() : ""; String paramTypeLower = paramType != null ? paramType.toLowerCase() : "";
@@ -115,8 +139,8 @@ public class TikHupServiceImpl implements TikHupService {
Unirest.setTimeouts(0, 0); Unirest.setTimeouts(0, 0);
HttpResponse<String> response = executeRequest(url, urlParams, authorization, methodTypeLower, paramTypeLower); HttpResponse<String> response = executeRequest(url, urlParams, authorization, methodTypeLower, paramTypeLower);
// 6. 处理响应 // 7. 处理响应并缓存
return handleResponse(response, url, userId, config.getConsumePoints(), type); return handleResponse(response, url, userId, config.getConsumePoints(), type, cacheKey);
} catch (Exception e) { } catch (Exception e) {
log.error("postTikHup: 接口调用异常, URL: {}, 错误信息: {}", url, e.getMessage(), e); log.error("postTikHup: 接口调用异常, URL: {}, 错误信息: {}", url, e.getMessage(), e);
return CommonResult.error(500, "接口调用异常: " + e.getMessage()); return CommonResult.error(500, "接口调用异常: " + e.getMessage());
@@ -154,7 +178,7 @@ public class TikHupServiceImpl implements TikHupService {
} }
private Object handleResponse(HttpResponse<String> response, String url, String userId, private Object handleResponse(HttpResponse<String> response, String url, String userId,
Integer consumePoints, String type) { Integer consumePoints, String type, String cacheKey) {
int statusCode = response.getStatus(); int statusCode = response.getStatus();
String responseBody = response.getBody(); String responseBody = response.getBody();
@@ -172,6 +196,10 @@ public class TikHupServiceImpl implements TikHupService {
// 扣减积分 // 扣减积分
deductPointsSafely(userId, consumePoints, type); deductPointsSafely(userId, consumePoints, type);
// 缓存成功响应24小时
stringRedisTemplate.opsForValue().set(cacheKey, responseBody, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
log.info("[postTikHup] 结果已缓存, cacheKey: {}, 有效期: {}小时", cacheKey, CACHE_EXPIRE_HOURS);
// 尝试解析 JSON失败则返回原始字符串 // 尝试解析 JSON失败则返回原始字符串
try { try {
return JSON.parseObject(responseBody); return JSON.parseObject(responseBody);
@@ -189,6 +217,34 @@ public class TikHupServiceImpl implements TikHupService {
} }
} }
/**
* 构建缓存 Key
* 格式: tikhub:cache:{type}:{methodType}:{paramType}:{paramsHash}
*/
private String buildCacheKey(String type, String methodType, String urlParams, String paramType) {
String rawKey = type + ":" + methodType + ":" + (paramType != null ? paramType : "") + ":" + (urlParams != null ? urlParams : "");
String hash = md5(rawKey);
return CACHE_KEY_PREFIX + type + ":" + hash;
}
/**
* MD5 哈希
*/
private String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
// 降级为直接使用原始字符串的 hashCode
return String.valueOf(input.hashCode());
}
}
@Override @Override
public Object videoToCharacters(String fileLink) { public Object videoToCharacters(String fileLink) {
log.info("[videoToCharacters] 开始识别,文件链接: {}", fileLink); log.info("[videoToCharacters] 开始识别,文件链接: {}", fileLink);

View File

@@ -1,12 +1,18 @@
package cn.iocoder.yudao.module.tik.tikhup.vo; package cn.iocoder.yudao.module.tik.tikhup.vo;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
@TableName("tik_token") @TableName("tik_token")
@Data @Data
public class TikTokenVO { @EqualsAndHashCode(callSuper = true)
public class TikTokenVO extends TenantBaseDO {
@TableField(value = "ID")
private Long id;
@TableField(value = "platform_url") @TableField(value = "platform_url")
private String platformUrl; private String platformUrl;

View File

@@ -84,8 +84,8 @@ public class DigitalHumanTaskServiceImpl implements DigitalHumanTaskService {
/** 积分平台和类型常量 */ /** 积分平台和类型常量 */
private static final String PLATFORM_DIGITAL_HUMAN = "digital_human"; private static final String PLATFORM_DIGITAL_HUMAN = "digital_human";
private static final String MODEL_TYPE_LATENTSYNC = "latentsync"; private static final String MODEL_CODE_LATENTSYNC = "latentsync";
private static final String MODEL_TYPE_KLING = "kling"; private static final String MODEL_CODE_KLING = "kling";
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@@ -97,8 +97,8 @@ public class DigitalHumanTaskServiceImpl implements DigitalHumanTaskService {
// 2. 积分预检和预扣 // 2. 积分预检和预扣
String aiProvider = StrUtil.blankToDefault(reqVO.getAiProvider(), "302ai"); String aiProvider = StrUtil.blankToDefault(reqVO.getAiProvider(), "302ai");
String modelType = "kling".equalsIgnoreCase(aiProvider) ? MODEL_TYPE_KLING : MODEL_TYPE_LATENTSYNC; String modelCode = "kling".equalsIgnoreCase(aiProvider) ? MODEL_CODE_KLING : MODEL_CODE_LATENTSYNC;
AiModelConfigDO config = pointsService.getConfig(PLATFORM_DIGITAL_HUMAN, modelType); AiModelConfigDO config = pointsService.getConfig(PLATFORM_DIGITAL_HUMAN, modelCode);
pointsService.checkPoints(userId.toString(), config.getConsumePoints()); pointsService.checkPoints(userId.toString(), config.getConsumePoints());
Long pendingRecordId = pointsService.createPendingDeduct( Long pendingRecordId = pointsService.createPendingDeduct(
userId.toString(), userId.toString(),

View File

@@ -112,8 +112,8 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
/** 积分平台和类型常量 */ /** 积分平台和类型常量 */
private static final String PLATFORM_VOICE = "voice"; private static final String PLATFORM_VOICE = "voice";
private static final String MODEL_TYPE_TTS = "tts"; private static final String MODEL_CODE_TTS = "tts";
private static final String MODEL_TYPE_CLONE = "clone"; private static final String MODEL_CODE_CLONE = "clone";
@Resource @Resource
private PointsService pointsService; private PointsService pointsService;
@@ -162,7 +162,7 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
if (StrUtil.isNotBlank(createReqVO.getText())) { if (StrUtil.isNotBlank(createReqVO.getText())) {
try { try {
// 4.1 获取积分配置并预检 // 4.1 获取积分配置并预检
AiModelConfigDO config = pointsService.getConfig(PLATFORM_VOICE, MODEL_TYPE_CLONE); AiModelConfigDO config = pointsService.getConfig(PLATFORM_VOICE, MODEL_CODE_CLONE);
pointsService.checkPoints(userId.toString(), config.getConsumePoints()); pointsService.checkPoints(userId.toString(), config.getConsumePoints());
log.info("[createVoice][开始语音复刻,配音编号({})文件ID({}),供应商({})]", log.info("[createVoice][开始语音复刻,配音编号({})文件ID({}),供应商({})]",
@@ -472,7 +472,7 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
} }
// 获取积分配置并预检(缓存未命中,需要实际调用 TTS // 获取积分配置并预检(缓存未命中,需要实际调用 TTS
AiModelConfigDO ttsConfig = pointsService.getConfig(PLATFORM_VOICE, MODEL_TYPE_TTS); AiModelConfigDO ttsConfig = pointsService.getConfig(PLATFORM_VOICE, MODEL_CODE_TTS);
pointsService.checkPoints(userId.toString(), ttsConfig.getConsumePoints()); pointsService.checkPoints(userId.toString(), ttsConfig.getConsumePoints());
// 使用 Provider 接口进行 TTS 合成(支持前端选择供应商,不传则使用默认) // 使用 Provider 接口进行 TTS 合成(支持前端选择供应商,不传则使用默认)

View File

@@ -103,17 +103,12 @@ const formRules = reactive({
modelName: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }], modelName: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
modelCode: [{ required: true, message: '模型标识/编码不能为空', trigger: 'blur' }], modelCode: [{ required: true, message: '模型标识/编码不能为空', trigger: 'blur' }],
platform: [{ required: true, message: '所属平台不能为空', trigger: 'blur' }], platform: [{ required: true, message: '所属平台不能为空', trigger: 'blur' }],
apiKey: [{ required: false, message: 'API秘钥不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }], status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
temperature: [{ required: false, message: '温度参数不能为空', trigger: 'blur' }], temperature: [{ required: true, message: '温度参数不能为空', trigger: 'blur' }],
maxTokens: [{ required: true, message: '回复数Token数不能为空', trigger: 'blur' }], maxTokens: [{ required: true, message: '回复数Token数不能为空', trigger: 'blur' }],
dailyLimit: [{ required: true, message: '每日请求次数不能为空', trigger: 'blur' }], dailyLimit: [{ required: true, message: '每日请求次数不能为空', trigger: 'blur' }],
modelType: [{ required: true, message: '模型类型(image-图像 text-文本 video-视频 audio-音频)不能为空', trigger: 'change' }], modelType: [{ required: true, message: '模型类型不能为空', trigger: 'change' }],
consumePoints: [{ required: true, message: '消耗积分不能为空', trigger: 'blur' }], consumePoints: [{ required: true, message: '消耗积分不能为空', trigger: 'blur' }],
maxTextLength: [{ required: false, message: '最大文本数量不能为空', trigger: 'blur' }],
maxImageSize: [{ required: false, message: '图片最大像素不能为空', trigger: 'blur' }],
maxVideoDuration: [{ required: false, message: '视频最大时长(秒)不能为空', trigger: 'blur' }],
maxVideoQuality: [{ required: false, message: '视频最大质量不能为空', trigger: 'blur' }],
remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }], remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }],
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref