语音优化

This commit is contained in:
2026-02-25 16:28:31 +08:00
parent 214c1f0f37
commit 0efca50be3
39 changed files with 237 additions and 1093 deletions

View File

@@ -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 () => {

View 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
}
}

View File

@@ -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) {

View File

@@ -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('复制失败')
}
}

View File

@@ -89,8 +89,8 @@
</a-button>
</a-upload>
<div class="upload-hint">
支持格式MP3WAVAACM4AFLACOGG单个文件不超过 50MB<br>
<span class="hint-text">🎤 配音建议使用 30 - 2 分钟的短配音效果更佳</span>
支持格式MP3WAVAACM4AFLACOGG单个文件不超过 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
}

View File

@@ -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 // 用户配音IDtik_user_voice.id
pre_generated_audio?: {
audioBase64: string
format: string

View File

@@ -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('复制失败')
}
}