优化
This commit is contained in:
@@ -11,7 +11,6 @@
|
||||
|
||||
2. **遵循项目规范**:严格执行《CLAUDE.md》文档中既定的编码规范,具体包括:
|
||||
- 使用 ES 模块,规范导入语句的排序规则与文件扩展名的使用方式
|
||||
- 优先使用 `function` 关键字定义函数,而非箭头函数
|
||||
- 为顶层函数添加显式的返回值类型注解
|
||||
- 遵循标准的 React 组件开发规范,定义显式的组件属性类型(Props types)
|
||||
- 采用合理的错误处理方案(尽可能避免滥用 try/catch 语句)
|
||||
|
||||
@@ -47,18 +47,6 @@
|
||||
<span class="loading-text">加载中...</span>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="!loading && allPrompts.length === 0" class="prompt-empty-state">
|
||||
<div class="prompt-empty-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||
<polyline points="21 15 16 10 5 21"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="prompt-empty-text">没有找到文案风格</p>
|
||||
</div>
|
||||
|
||||
<!-- 更多提示词弹窗 -->
|
||||
<div v-if="showAllPromptsModal" class="prompt-modal-mask" @click.self="showAllPromptsModal = false">
|
||||
<div class="prompt-modal">
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
import { CommonService } from '@/api/common'
|
||||
import { UserPromptApi } from '@/api/userPrompt'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import PromptSelector from '@/components/PromptSelector.vue'
|
||||
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
|
||||
import TikhubService, { InterfaceType, MethodType, ParamType } from '@/api/tikhub'
|
||||
import { UserPromptApi } from '@/api/userPrompt'
|
||||
import PromptSelector from '@/components/PromptSelector.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
|
||||
|
||||
defineOptions({ name: 'ForecastView' })
|
||||
|
||||
@@ -45,31 +46,40 @@ const loadingPrompts = ref(false)
|
||||
const promptSearchKeyword = ref('')
|
||||
|
||||
// 工具函数
|
||||
const formatNumber = (num) => {
|
||||
function formatNumber(num) {
|
||||
if (!num) return '0'
|
||||
return num >= 10000 ? `${(num / 10000).toFixed(1)}w` : num.toString()
|
||||
return num >= 10000 ? `${(num / 10000).toFixed(1)}w` : String(num)
|
||||
}
|
||||
|
||||
const truncateTitle = (title, maxLength = 28) => {
|
||||
function truncateTitle(title, maxLength = 28) {
|
||||
if (!title) return ''
|
||||
return title.length <= maxLength ? title : `${title.substring(0, maxLength)}...`
|
||||
}
|
||||
|
||||
const handleImageError = (event) => {
|
||||
function handleImageError(event) {
|
||||
event.target.style.display = 'none'
|
||||
}
|
||||
|
||||
const openVideo = (topic, event) => {
|
||||
function openVideo(topic, event) {
|
||||
event.stopPropagation()
|
||||
if (topic.videoUrl) window.open(topic.videoUrl, '_blank')
|
||||
}
|
||||
|
||||
const handleSearchKeypress = (event) => {
|
||||
function handleSearchKeypress(event) {
|
||||
if (event.key === 'Enter' && !isLoading.value) {
|
||||
handleSearch()
|
||||
}
|
||||
}
|
||||
|
||||
async function copyContent() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(generatedContent.value)
|
||||
message.success('已复制')
|
||||
} catch {
|
||||
message.error('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 提示词管理
|
||||
async function loadUserPrompts() {
|
||||
if (!userStore.userId) {
|
||||
@@ -177,22 +187,12 @@ async function handleGenerate() {
|
||||
|
||||
const ctrl = new AbortController()
|
||||
let fullText = ''
|
||||
let errorOccurred = false
|
||||
let isResolved = false
|
||||
let completed = false
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
let timeout = null
|
||||
|
||||
const cleanup = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
}
|
||||
}
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
if (!isResolved) {
|
||||
cleanup()
|
||||
const timeout = setTimeout(() => {
|
||||
if (!completed) {
|
||||
completed = true
|
||||
ctrl.abort()
|
||||
reject(new Error('请求超时'))
|
||||
}
|
||||
@@ -202,25 +202,21 @@ async function handleGenerate() {
|
||||
data: requestData,
|
||||
ctrl,
|
||||
onMessage: (event) => {
|
||||
if (errorOccurred || !event?.data) return
|
||||
if (completed || !event?.data) return
|
||||
|
||||
const dataStr = event.data.trim()
|
||||
|
||||
if (dataStr === '[DONE]') {
|
||||
cleanup()
|
||||
if (!isResolved) {
|
||||
isResolved = true
|
||||
resolve()
|
||||
}
|
||||
completed = true
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
if (dataStr.startsWith('[TIMEOUT]')) {
|
||||
cleanup()
|
||||
if (!isResolved) {
|
||||
errorOccurred = true
|
||||
isResolved = true
|
||||
reject(new Error(dataStr.replace('[TIMEOUT]', '').trim() || '请求超时'))
|
||||
}
|
||||
completed = true
|
||||
clearTimeout(timeout)
|
||||
reject(new Error(dataStr.replace('[TIMEOUT]', '').trim() || '请求超时'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -231,7 +227,7 @@ async function handleGenerate() {
|
||||
fullText += piece
|
||||
generatedContent.value = fullText
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
if (event.data && !event.data.startsWith('[')) {
|
||||
fullText += event.data
|
||||
generatedContent.value = fullText
|
||||
@@ -239,20 +235,18 @@ async function handleGenerate() {
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
cleanup()
|
||||
if (!isResolved) {
|
||||
errorOccurred = true
|
||||
isResolved = true
|
||||
if (!completed) {
|
||||
completed = true
|
||||
clearTimeout(timeout)
|
||||
ctrl.abort()
|
||||
const errorMsg = err?.message || '网络请求失败'
|
||||
message.error(errorMsg)
|
||||
reject(new Error(errorMsg))
|
||||
message.error(err?.message || '网络请求失败')
|
||||
reject(new Error(err?.message || '网络请求失败'))
|
||||
}
|
||||
},
|
||||
onClose: () => {
|
||||
cleanup()
|
||||
if (!isResolved) {
|
||||
isResolved = true
|
||||
if (!completed) {
|
||||
completed = true
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
@@ -273,11 +267,11 @@ async function handleGenerate() {
|
||||
function extractAudioUrl(video) {
|
||||
const urlList = video?.play_addr?.url_list
|
||||
if (Array.isArray(urlList) && urlList.length > 0) {
|
||||
const lastUrl = urlList[urlList.length - 1]
|
||||
const firstUrl = urlList[0]
|
||||
return (lastUrl && lastUrl.trim()) || (firstUrl && firstUrl.trim()) || ''
|
||||
const lastUrl = urlList[urlList.length - 1]?.trim()
|
||||
const firstUrl = urlList[0]?.trim()
|
||||
return lastUrl || firstUrl || ''
|
||||
}
|
||||
return (video?.play_addr?.url && video.play_addr.url.trim()) || ''
|
||||
return video?.play_addr?.url?.trim() || ''
|
||||
}
|
||||
|
||||
function extractCover(video) {
|
||||
@@ -369,12 +363,12 @@ async function handleSearch() {
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
if (userStore.userId) {
|
||||
await loadUserPrompts()
|
||||
loadUserPrompts()
|
||||
} else if (userStore.isLoggedIn) {
|
||||
setTimeout(async () => {
|
||||
if (userStore.userId) await loadUserPrompts()
|
||||
setTimeout(() => {
|
||||
if (userStore.userId) loadUserPrompts()
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
@@ -608,7 +602,7 @@ onMounted(async () => {
|
||||
<span class="result-label">生成结果</span>
|
||||
<button
|
||||
class="copy-btn"
|
||||
@click="navigator.clipboard.writeText(generatedContent); message.success('已复制')"
|
||||
@click="copyContent"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2"/>
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.tik.dify.service;
|
||||
import cn.iocoder.yudao.module.tik.dify.client.DifyClient;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
|
||||
import cn.iocoder.yudao.module.tik.enums.AiModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.tik.enums.AiPlatformEnum;
|
||||
import cn.iocoder.yudao.module.tik.muye.aiagent.dal.AiAgentDO;
|
||||
import cn.iocoder.yudao.module.tik.muye.aiagent.service.AiAgentService;
|
||||
import cn.iocoder.yudao.module.tik.muye.aimodelconfig.dal.AiModelConfigDO;
|
||||
@@ -27,13 +29,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@Slf4j
|
||||
public class DifyServiceImpl implements DifyService {
|
||||
|
||||
/** Dify 平台标识 */
|
||||
private static final String PLATFORM_DIFY = "dify";
|
||||
/** Dify 模型类型 - Pro深度版 */
|
||||
private static final String MODEL_TYPE_WRITING_PRO = "writing_pro";
|
||||
/** Dify 模型类型 - 标准版 */
|
||||
private static final String MODEL_TYPE_WRITING_STANDARD = "writing_standard";
|
||||
|
||||
@Resource
|
||||
private AiAgentService aiAgentService;
|
||||
|
||||
@@ -58,10 +53,12 @@ public class DifyServiceImpl implements DifyService {
|
||||
}
|
||||
|
||||
// 2. 根据 modelMode 获取对应的积分配置
|
||||
String modelType = "standard".equals(reqVO.getModelMode())
|
||||
? MODEL_TYPE_WRITING_STANDARD
|
||||
: MODEL_TYPE_WRITING_PRO;
|
||||
AiModelConfigDO config = pointsService.getConfig(PLATFORM_DIFY, modelType);
|
||||
AiModelTypeEnum modelTypeEnum = "standard".equals(reqVO.getModelMode())
|
||||
? AiModelTypeEnum.DIFY_WRITING_STANDARD
|
||||
: AiModelTypeEnum.DIFY_WRITING_PRO;
|
||||
AiModelConfigDO config = pointsService.getConfig(
|
||||
AiPlatformEnum.DIFY.getPlatform(),
|
||||
modelTypeEnum.getModelType());
|
||||
|
||||
// 3. 预检积分
|
||||
pointsService.checkPoints(userId, config.getConsumePoints());
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.module.tik.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AI 模型类型枚举
|
||||
* 统一管理所有 AI 服务的模型类型标识
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AiModelTypeEnum implements ArrayValuable<String> {
|
||||
|
||||
// ========== Dify 写作模型 ==========
|
||||
DIFY_WRITING_PRO("writing_pro", "Pro深度版", AiPlatformEnum.DIFY),
|
||||
DIFY_WRITING_STANDARD("writing_standard", "标准版", AiPlatformEnum.DIFY),
|
||||
|
||||
// ========== 数字人模型 ==========
|
||||
DIGITAL_HUMAN_LATENTSYNC("latentsync", "LatentSync", AiPlatformEnum.DIGITAL_HUMAN),
|
||||
DIGITAL_HUMAN_KLING("kling", "可灵", AiPlatformEnum.DIGITAL_HUMAN),
|
||||
;
|
||||
|
||||
/**
|
||||
* 模型类型标识
|
||||
*/
|
||||
private final String modelType;
|
||||
/**
|
||||
* 模型类型名称
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* 所属平台
|
||||
*/
|
||||
private final AiPlatformEnum platform;
|
||||
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(AiModelTypeEnum::getModelType).toArray(String[]::new);
|
||||
|
||||
@Override
|
||||
public String[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模型类型标识获取枚举
|
||||
*/
|
||||
public static AiModelTypeEnum valueOfModelType(String modelType) {
|
||||
return Arrays.stream(values())
|
||||
.filter(e -> e.getModelType().equals(modelType))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据平台获取该平台下所有模型类型
|
||||
*/
|
||||
public static AiModelTypeEnum[] valuesByPlatform(AiPlatformEnum platform) {
|
||||
return Arrays.stream(values())
|
||||
.filter(e -> e.getPlatform() == platform)
|
||||
.toArray(AiModelTypeEnum[]::new);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.tik.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AI 平台枚举
|
||||
* 统一管理所有 AI 服务的平台标识
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AiPlatformEnum implements ArrayValuable<String> {
|
||||
|
||||
DIFY("dify", "Dify 平台"),
|
||||
DIGITAL_HUMAN("digital_human", "数字人平台"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 平台标识
|
||||
*/
|
||||
private final String platform;
|
||||
/**
|
||||
* 平台名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(AiPlatformEnum::getPlatform).toArray(String[]::new);
|
||||
|
||||
@Override
|
||||
public String[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据平台标识获取枚举
|
||||
*/
|
||||
public static AiPlatformEnum valueOfPlatform(String platform) {
|
||||
return Arrays.stream(values())
|
||||
.filter(e -> e.getPlatform().equals(platform))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user