语音优化
This commit is contained in:
@@ -158,6 +158,7 @@ import {
|
||||
} from '@ant-design/icons-vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { sendChatStream } from '@/api/agent'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
@@ -278,9 +279,13 @@ const handleKeyDown = (e) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = (content) => {
|
||||
navigator.clipboard.writeText(content)
|
||||
message.success('已复制')
|
||||
const handleCopy = async (content) => {
|
||||
const success = await copyToClipboard(content)
|
||||
if (success) {
|
||||
message.success('已复制')
|
||||
} else {
|
||||
message.error('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegenerate = async () => {
|
||||
|
||||
42
frontend/app/web-gold/src/utils/clipboard.ts
Normal file
42
frontend/app/web-gold/src/utils/clipboard.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 复制文本到剪贴板
|
||||
* 兼容非 HTTPS 环境的降级方案
|
||||
*/
|
||||
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||
if (!text?.trim()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 优先使用 Clipboard API(需要 HTTPS 或 localhost)
|
||||
if (navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
return true
|
||||
} catch {
|
||||
// 降级到 execCommand 方案
|
||||
}
|
||||
}
|
||||
|
||||
// 降级方案:使用 textarea + execCommand
|
||||
return fallbackCopy(text)
|
||||
}
|
||||
|
||||
/**
|
||||
* 降级复制方案
|
||||
*/
|
||||
function fallbackCopy(text: string): boolean {
|
||||
try {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
textarea.style.left = '-9999px'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
const success = document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
return success
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import TikhubService, { InterfaceType, MethodType } from '@/api/tikhub/index.js'
|
||||
import { useBenchmarkData } from './composables/useBenchmarkData'
|
||||
import { useBenchmarkAnalysis } from './composables/useBenchmarkAnalysis'
|
||||
import { formatTime } from './utils/benchmarkUtils'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
import BenchmarkForm from './components/BenchmarkForm.vue'
|
||||
import BenchmarkTable from './components/BenchmarkTable.vue'
|
||||
import BatchAnalyzeModal from './components/BatchAnalyzeModal.vue'
|
||||
@@ -231,17 +232,18 @@ async function handleLoadMore() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopyBatchPrompt(prompt) {
|
||||
async function handleCopyBatchPrompt(prompt) {
|
||||
if (!prompt?.trim()) {
|
||||
message.warning('没有提示词可复制')
|
||||
return
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(prompt).then(() => {
|
||||
const success = await copyToClipboard(prompt)
|
||||
if (success) {
|
||||
message.success('提示词已复制到剪贴板')
|
||||
}).catch(() => {
|
||||
} else {
|
||||
message.error('复制失败')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleUseBatchPrompt(prompt) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useUserStore } from '@/stores/user'
|
||||
import GradientButton from '@/components/GradientButton.vue'
|
||||
import PromptSelector from '@/components/PromptSelector.vue'
|
||||
import { setJSON, getJSON } from '@/utils/storage'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
import BasicLayout from '@/layouts/components/BasicLayout.vue'
|
||||
|
||||
const promptStore = usePromptStore()
|
||||
@@ -328,48 +329,19 @@ function cancelEdit() {
|
||||
message.info('已取消编辑')
|
||||
}
|
||||
|
||||
// 复制内容(编辑模式复制编辑区,否则复制生成内容),带降级方案
|
||||
function copyContent() {
|
||||
// 复制内容(编辑模式复制编辑区,否则复制生成内容)
|
||||
async function copyContent() {
|
||||
const text = isEditMode.value ? (editableContent.value || '') : (generatedContent.value || '')
|
||||
if (!text.trim()) {
|
||||
message.warning('没有可复制的内容')
|
||||
return
|
||||
}
|
||||
|
||||
// 优先使用异步 Clipboard API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('文案已复制到剪贴板')
|
||||
}).catch(() => {
|
||||
// 降级到选中复制
|
||||
fallbackCopy(text)
|
||||
})
|
||||
return
|
||||
}
|
||||
// 直接降级
|
||||
fallbackCopy(text)
|
||||
}
|
||||
|
||||
function fallbackCopy(text) {
|
||||
try {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
textarea.style.left = '-9999px'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
const ok = document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
if (ok) {
|
||||
message.success('文案已复制到剪贴板')
|
||||
} else {
|
||||
message.error('复制失败,请手动复制')
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('fallback copy failed:', e)
|
||||
message.error('复制失败,请手动复制')
|
||||
const success = await copyToClipboard(text)
|
||||
if (success) {
|
||||
message.success('文案已复制到剪贴板')
|
||||
} else {
|
||||
message.error('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,8 +89,8 @@
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<div class="upload-hint">
|
||||
支持格式:MP3、WAV、AAC、M4A、FLAC、OGG,单个文件不超过 50MB<br>
|
||||
<span class="hint-text">🎤 配音建议:使用 30 秒 - 2 分钟的短配音效果更佳</span>
|
||||
支持格式:MP3、WAV、AAC、M4A、FLAC、OGG,单个文件不超过 5MB<br>
|
||||
<span class="hint-text">🎤 配音建议:使用 5-20 秒的短配音效果更佳</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
@@ -129,7 +129,7 @@ const DEFAULT_FORM_DATA = {
|
||||
fileUrl: ''
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 50 * 1024 * 1024
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024 // SiliconFlow API 限制参考音频不超过 5MB
|
||||
const VALID_AUDIO_TYPES = ['audio/mpeg', 'audio/wav', 'audio/wave', 'audio/x-wav', 'audio/aac', 'audio/mp4', 'audio/flac', 'audio/ogg']
|
||||
const VALID_AUDIO_EXTENSIONS = ['.mp3', '.wav', '.aac', '.m4a', '.flac', '.ogg']
|
||||
|
||||
@@ -290,7 +290,7 @@ function handlePlayAudio(record) {
|
||||
// ========== 文件上传 ==========
|
||||
function handleBeforeUpload(file) {
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
message.error('文件大小不能超过 50MB')
|
||||
message.error('文件大小不能超过 5MB')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -224,7 +224,8 @@ export interface LipSyncTaskData {
|
||||
kling_face_start_time: number
|
||||
kling_face_end_time: number
|
||||
ai_provider: string
|
||||
voiceConfigId: string
|
||||
voiceId?: string // 系统预置音色ID
|
||||
voiceConfigId?: string // 用户配音ID(tik_user_voice.id)
|
||||
pre_generated_audio?: {
|
||||
audioBase64: string
|
||||
format: string
|
||||
|
||||
@@ -7,6 +7,7 @@ import { rewriteStream } from '@/api/forecast'
|
||||
import { getAgentList } from '@/api/agent'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
|
||||
defineOptions({ name: 'ForecastView' })
|
||||
|
||||
@@ -88,10 +89,10 @@ function handleSearchKeypress(event) {
|
||||
}
|
||||
|
||||
async function copyContent() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(generatedContent.value)
|
||||
const success = await copyToClipboard(generatedContent.value)
|
||||
if (success) {
|
||||
message.success('已复制')
|
||||
} catch {
|
||||
} else {
|
||||
message.error('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user