886 lines
28 KiB
Vue
886 lines
28 KiB
Vue
<template>
|
||
<el-dialog
|
||
title="Markdown 文件与TTS编辑"
|
||
v-model="dialogVisible"
|
||
width="85%"
|
||
align-center
|
||
:before-close="handleDialogClose"
|
||
>
|
||
<div class="input-section">
|
||
<div class="form-group">
|
||
<el-row>
|
||
<el-col :span="24" style="border: 1px solid lightgray; padding: 10px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
||
<span style="font-weight: bold;">展品ID: {{formData.id}} - {{formData.label}}</span>
|
||
<div style="display: flex; gap: 10px; align-items: center;">
|
||
<span>Markdown文件地址:</span>
|
||
<a v-if="formData.md_file_url" :href="formData.md_file_url" target="_blank" style="max-width: 300px; overflow: hidden; text-overflow: ellipsis;">
|
||
{{formData.md_file_url}}
|
||
</a>
|
||
<span v-else style="color: #999;">未上传</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Markdown 文件上传区域 -->
|
||
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
|
||
<el-upload
|
||
ref="uploadMdRef"
|
||
action=""
|
||
:auto-upload="false"
|
||
:limit="1"
|
||
:on-change="handleMdFileChange"
|
||
:on-exceed="handleMdFileExceed"
|
||
:show-file-list="false"
|
||
accept=".md,.markdown,.txt">
|
||
<template #trigger>
|
||
<el-button type="primary" size="small">选择Markdown文件</el-button>
|
||
</template>
|
||
</el-upload>
|
||
<span v-if="mdFile" style="color: #67C23A;">{{mdFile.name}}</span>
|
||
<el-button
|
||
size="small"
|
||
type="success"
|
||
:disabled="!mdFile"
|
||
@click="submitMdFileUpload">
|
||
上传并更新
|
||
</el-button>
|
||
<el-button
|
||
size="small"
|
||
type="danger"
|
||
:disabled="!formData.md_file_url"
|
||
@click="removeMdFileUrl">
|
||
删除Markdown地址
|
||
</el-button>
|
||
<el-button
|
||
size="small"
|
||
type="info"
|
||
:disabled="!formData.md_file_url"
|
||
@click="loadMdContent">
|
||
读取Markdown内容
|
||
</el-button>
|
||
<el-button
|
||
size="small"
|
||
type="warning"
|
||
:disabled="!formData.md_file_url || !mdContent || mdContent.length === 0"
|
||
@click="saveMdContent">
|
||
保存修改到MinIO
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- Markdown 内容编辑区域 -->
|
||
<div style="margin-bottom: 20px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
||
<span style="font-weight: bold;">Markdown 内容编辑:</span>
|
||
<span style="color: #909399; font-size: 12px;">字符数: {{mdContent.length}}</span>
|
||
</div>
|
||
<textarea
|
||
v-model="mdContent"
|
||
rows="15"
|
||
placeholder="请先上传Markdown文件或读取现有内容..."
|
||
style="width: 100%; font-family: monospace; padding: 10px; border: 1px solid #dcdfe6; border-radius: 4px;">
|
||
</textarea>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<!-- TTS 音频生成与上传区域 -->
|
||
<div style="display: flex; align-items: center; justify-content: space-between; color: blue; margin: 10px 0;">
|
||
<div>Markdown TTS 音频</div>
|
||
</div>
|
||
|
||
<el-row v-loading="isTTSGenerating" element-loading-text="音频生成中...">
|
||
<el-col :span="24" style="border: 1px solid gray; padding: 15px;">
|
||
<div style="display: flex; flex-direction: row; align-items: center; justify-content: flex-start; margin-bottom: 15px;">
|
||
<span style="margin-right: 20px; display: inline-block; width: 200px;">TTS音频地址:</span>
|
||
<span style="display: inline-block; flex: 1;">
|
||
<a v-if="formData.md_tts_url" :href="formData.md_tts_url" target="_blank">{{formData.md_tts_url}}</a>
|
||
<span v-else style="color: #999;">未生成</span>
|
||
</span>
|
||
<el-button
|
||
type="danger"
|
||
size="default"
|
||
:disabled="!formData.md_tts_url"
|
||
@click="removeMdTtsUrl">
|
||
删除音频地址
|
||
</el-button>
|
||
</div>
|
||
|
||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px;">
|
||
<div style="display: flex; justify-content: flex-start; align-items: center;">
|
||
<label>音色选择:</label>
|
||
<el-select
|
||
v-model="voiceSelected_md"
|
||
clearable
|
||
placeholder="请选择音色"
|
||
@change="voiceSelectedChangeMd"
|
||
allow-create
|
||
default-first-option
|
||
style="width: 140px;">
|
||
<el-option
|
||
v-for="item in voiceOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value">
|
||
</el-option>
|
||
</el-select>
|
||
</div>
|
||
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!mdContent || mdContent.length === 0"
|
||
@click="generateMdTTS">
|
||
生成语音
|
||
</el-button>
|
||
<audio class="audio-player" ref="audioPlayerMd" :src="preview.audioSrc_md" controls></audio>
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!preview.downReady_md"
|
||
@click="downloadMdTTS">
|
||
下载语音
|
||
</el-button>
|
||
</div>
|
||
|
||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px;">
|
||
<div style="width: 90%; display: flex; justify-content: flex-start; align-items: center;">
|
||
<span>下载文件名</span>
|
||
<el-input v-model="downloadFilename_md"/>
|
||
</div>
|
||
<div style="display: flex; justify-content: flex-start; align-items: center;">
|
||
<span style="display: inline-block; width: 100px;">{{downloadPromptMd}}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 上传区域 -->
|
||
<div class="upload-section">
|
||
<el-row v-show="preview.visible_md" align="middle" style="height: 5vh">
|
||
<el-col :span="14">
|
||
<div style="margin-left: 10px; width: 100%; height: 5vh; display: flex; align-items: center;">
|
||
<el-upload
|
||
action=""
|
||
:auto-upload="false"
|
||
:on-change="handleUploadChangeMd"
|
||
:show-file-list="false"
|
||
style="display: flex; align-items: center">
|
||
<template #trigger>
|
||
<el-button type="primary">选取文件</el-button>
|
||
</template>
|
||
<div style="display: inline-block; width: 200px;">{{uploadFileName_md}}</div>
|
||
</el-upload>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-button
|
||
:disabled="!uploadFile_md"
|
||
style="width: 90%;"
|
||
size="default"
|
||
type="success"
|
||
@click="submitMdTTSUpload">
|
||
上传并修改音频地址
|
||
</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<!-- 结果展示 -->
|
||
<div class="result-section">
|
||
<div v-if="result.error" class="error-message">
|
||
错误:{{ result.error }}
|
||
</div>
|
||
<div v-if="result.success" class="success-message">
|
||
<p>操作成功!</p>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
BASE_API_URL,
|
||
WEIXIN_WS_URL,
|
||
chatId,
|
||
exhibit_tts_bucket_name,
|
||
exhibit_md_bucket_name,
|
||
exhibit_photo_bucket_name
|
||
} from "@/API/api";
|
||
|
||
export default {
|
||
name: "MarkdownEditor",
|
||
props: {
|
||
'mesumSelectedId': {default: -1},
|
||
'mesumSelected': {},
|
||
'initData': {},
|
||
'visible': {default: false},
|
||
'photo_prefix': {default: 'temp'}
|
||
},
|
||
inject: {
|
||
'root': {default: null},
|
||
'getProject': {default: null},
|
||
'getSchedule': {default: null}
|
||
},
|
||
data() {
|
||
return {
|
||
formData: {
|
||
mesum_id: 1,
|
||
id: "",
|
||
label: "",
|
||
md_file_url: "",
|
||
md_tts_url: ""
|
||
},
|
||
mdFile: null,
|
||
mdContent: "",
|
||
preview: {
|
||
visible_md: false,
|
||
downReady_md: false,
|
||
audioSrc_md: '',
|
||
tempBlob_md: null
|
||
},
|
||
uploadFile_md: null,
|
||
result: {
|
||
error: null,
|
||
success: false,
|
||
minioUrl: ''
|
||
},
|
||
isTTSGenerating: false,
|
||
voiceSelected_md: "sambert-zhichu-v1@Tongyi-Qianwen",
|
||
downloadFilename_md: "md_tts.mp3",
|
||
voiceOptions: [
|
||
{"value": "cosyvoice-v1/longyuan@Tongyi-Qianwen", "label": "亲切女生"},
|
||
{"value": "sambert-zhiru-v1@Tongyi-Qianwen", "label": "新闻女生"},
|
||
{"value": "cosyvoice-v1/longhua@Tongyi-Qianwen", "label": "活泼女童"},
|
||
{"value": "cosyvoice-v1/longxiang@Tongyi-Qianwen", "label": "解说男声"},
|
||
{"value": "cosyvoice-v1/longyue@Tongyi-Qianwen", "label": "longyue"},
|
||
{"value": "cosyvoice-v1/longwan@Tongyi-Qianwen", "label": "longwan"},
|
||
{"value": "sambert-zhichu-v1@Tongyi-Qianwen", "label": "舌尖男声"},
|
||
{"value": "sambert-zhiying-v1@Tongyi-Qianwen", "label": "软萌童声"},
|
||
],
|
||
dialogVisible: false,
|
||
downloadStateMd: 'idle',
|
||
};
|
||
},
|
||
created() {
|
||
},
|
||
mounted() {
|
||
},
|
||
methods: {
|
||
// Markdown 文件选择
|
||
handleMdFileChange(file) {
|
||
this.mdFile = file.raw;
|
||
console.log("选择的Markdown文件:", file.name);
|
||
},
|
||
|
||
handleMdFileExceed(files) {
|
||
this.$refs.uploadMdRef.clearFiles();
|
||
const newFile = files[0];
|
||
this.$refs.uploadMdRef.handleStart(newFile);
|
||
this.handleMdFileChange({raw: newFile, name: newFile.name});
|
||
},
|
||
|
||
// 上传 Markdown 文件到 MinIO
|
||
async submitMdFileUpload() {
|
||
if (!this.mdFile) {
|
||
this.$message.warning('请先选择Markdown文件');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 如果已有MD文件,先删除旧的MinIO对象
|
||
await this.removeMdFileFromMinio(this.formData.md_file_url);
|
||
|
||
// 上传新文件
|
||
const formData = new FormData();
|
||
formData.append('file', this.mdFile);
|
||
// 使用原始文件名,不重命名
|
||
const fileName = this.mdFile.name;
|
||
formData.append('bucket', exhibit_md_bucket_name); // 使用专门的bucket存放markdown
|
||
formData.append('file_name', `${this.photo_prefix}/category/${fileName}`);
|
||
|
||
const response = await fetch(`${BASE_API_URL}/minio/put`, {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
"Authorization": "Bearer ragflow-NhZTY5Y2M4YWQ1MzExZWY4Zjc3MDI0Mm"
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('上传失败');
|
||
}
|
||
|
||
const res = await response.json();
|
||
const {data} = res;
|
||
if (data?.put) {
|
||
// 更新数据库
|
||
await this.updateAntiqueMdFile(data?.url);
|
||
this.$message.success('Markdown文件上传成功');
|
||
this.mdFile = null;
|
||
if (this.$refs.uploadMdRef) {
|
||
this.$refs.uploadMdRef.clearFiles();
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('上传Markdown文件失败:', error);
|
||
this.$message.error('上传Markdown文件失败');
|
||
}
|
||
},
|
||
|
||
// 读取 Markdown 文件内容
|
||
async loadMdContent() {
|
||
if (!this.formData.md_file_url) {
|
||
this.$message.warning('没有Markdown文件URL');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 添加时间戳参数避免浏览器缓存
|
||
const url = this.formData.md_file_url + '?t=' + Date.now();
|
||
const response = await fetch(url);
|
||
if (!response.ok) {
|
||
throw new Error('读取文件失败');
|
||
}
|
||
const content = await response.text();
|
||
this.mdContent = content;
|
||
this.$message.success('Markdown内容加载成功');
|
||
} catch (error) {
|
||
console.error('读取Markdown内容失败:', error);
|
||
this.$message.error('读取Markdown内容失败');
|
||
}
|
||
},
|
||
|
||
// 保存 Markdown 内容修改到 MinIO
|
||
async saveMdContent() {
|
||
if (!this.formData.md_file_url) {
|
||
this.$message.warning('没有Markdown文件URL');
|
||
return;
|
||
}
|
||
|
||
if (!this.mdContent || this.mdContent.length === 0) {
|
||
this.$message.warning('Markdown内容为空');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const loading = this.$loading({
|
||
lock: true,
|
||
text: '正在保存Markdown内容到MinIO...',
|
||
background: 'rgba(0, 0, 0, 0.7)'
|
||
});
|
||
|
||
// 从URL中解析出bucket和文件名
|
||
let bucket = exhibit_md_bucket_name;
|
||
let fileName = '';
|
||
|
||
const urlParts = this.formData.md_file_url.split(`${exhibit_md_bucket_name}/`);
|
||
if (urlParts.length > 1) {
|
||
fileName = urlParts[1];
|
||
} else {
|
||
// 兼容旧的photo bucket格式
|
||
const photoUrlParts = this.formData.md_file_url.split(`${exhibit_photo_bucket_name}/`);
|
||
if (photoUrlParts.length > 1) {
|
||
bucket = exhibit_photo_bucket_name;
|
||
fileName = photoUrlParts[1];
|
||
} else {
|
||
throw new Error('无法解析文件路径');
|
||
}
|
||
}
|
||
|
||
// 将Markdown内容转换为Blob
|
||
const blob = new Blob([this.mdContent], { type: 'text/markdown' });
|
||
const file = new File([blob], fileName.split('/').pop() || 'content.md', { type: 'text/markdown' });
|
||
|
||
// 先删除旧文件(根据经验记忆,某些MinIO版本需要先删除再上传)
|
||
await this.removeMinioObject(bucket, fileName);
|
||
|
||
// 上传新内容
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('bucket', bucket);
|
||
formData.append('file_name', fileName);
|
||
|
||
const response = await fetch(`${BASE_API_URL}/minio/put`, {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
"Authorization": "Bearer ragflow-NhZTY5Y2M4YWQ1MzExZWY4Zjc3MDI0Mm"
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('上传失败');
|
||
}
|
||
|
||
const res = await response.json();
|
||
const {data} = res;
|
||
|
||
if (data?.put) {
|
||
// 验证上传是否成功:下载文件并检查内容
|
||
const verifyResponse = await fetch(this.formData.md_file_url + '?t=' + Date.now());
|
||
if (verifyResponse.ok) {
|
||
const verifyContent = await verifyResponse.text();
|
||
if (verifyContent === this.mdContent) {
|
||
loading.close();
|
||
this.$message.success('Markdown内容已成功保存到MinIO并验证通过');
|
||
} else {
|
||
loading.close();
|
||
this.$message.warning('Markdown内容已上传,但验证发现内容可能未完全更新,请刷新后重试');
|
||
}
|
||
} else {
|
||
loading.close();
|
||
this.$message.success('Markdown内容已保存到MinIO');
|
||
}
|
||
} else {
|
||
loading.close();
|
||
this.$message.error('保存失败:服务器返回失败状态');
|
||
}
|
||
} catch (error) {
|
||
console.error('保存Markdown内容到MinIO失败:', error);
|
||
this.$message.error('保存Markdown内容失败: ' + error.message);
|
||
}
|
||
},
|
||
|
||
// 从MD文件URL中删除MinIO对象(独立函数)
|
||
async removeMdFileFromMinio(md_file_url) {
|
||
if (!md_file_url || md_file_url === '') {
|
||
return;
|
||
}
|
||
|
||
const urlParts = md_file_url.split(`${exhibit_md_bucket_name}/`);
|
||
if (urlParts.length > 1) {
|
||
const fileName = urlParts[1];
|
||
if (fileName && fileName !== "") {
|
||
await this.removeMinioObject(exhibit_md_bucket_name, fileName);
|
||
console.log('已删除Markdown文件:', fileName);
|
||
}
|
||
} else {
|
||
// 兼容旧的URL格式或其他bucket
|
||
const photoUrlParts = md_file_url.split(`${exhibit_photo_bucket_name}/`);
|
||
if (photoUrlParts.length > 1) {
|
||
const fileName = photoUrlParts[1];
|
||
if (fileName && fileName !== "") {
|
||
await this.removeMinioObject(exhibit_photo_bucket_name, fileName);
|
||
console.log('已删除Markdown文件(photo bucket):', fileName);
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
// 删除 Markdown 文件URL
|
||
async removeMdFileUrl() {
|
||
this.$confirm('确认删除Markdown文件地址?')
|
||
.then(async () => {
|
||
// 调用独立的删除函数
|
||
await this.removeMdFileFromMinio(this.formData.md_file_url);
|
||
|
||
await this.updateAntiqueMdFile("");
|
||
this.mdContent = "";
|
||
this.$message.success('删除Markdown文件地址成功');
|
||
}).catch(() => {});
|
||
},
|
||
|
||
// 更新数据库中的 md_file_url
|
||
async updateAntiqueMdFile(md_file_url) {
|
||
let antique_data = {
|
||
mesum_id: this.mesumSelectedId,
|
||
md_file_url: md_file_url
|
||
};
|
||
|
||
const response = await fetch(`${BASE_API_URL}/mesum/antique/update/${this.formData.mesum_id}/${this.formData.id}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer ragflow-NhZTY5Y2M4YWQ1MzExZWY4Zjc3MDI0Mm'
|
||
},
|
||
body: JSON.stringify(antique_data)
|
||
});
|
||
|
||
const result = await response.json();
|
||
const {data} = result;
|
||
if (data && data.update) {
|
||
this.formData.md_file_url = md_file_url;
|
||
this.triggerRefreshAntiques();
|
||
return true;
|
||
}
|
||
return false;
|
||
},
|
||
|
||
// 生成 Markdown TTS
|
||
async generateMdTTS() {
|
||
if (!this.mdContent || this.mdContent.length === 0) {
|
||
this.$message.warning('Markdown内容为空,无法生成语音');
|
||
return;
|
||
}
|
||
|
||
this.downloadFilename_md = 'md_tts_' + window.pinyin.getFullChars(this.formData.label) + '.mp3';
|
||
|
||
try {
|
||
this.isTTSGenerating = true;
|
||
this.preview.audioSrc_md = null;
|
||
this.preview.downReady_md = false;
|
||
|
||
const wsUrl = `${WEIXIN_WS_URL}/tts/chats/${chatId}/tts/x-tts-type-is-TextToTts`;
|
||
const ws = new WebSocket(wsUrl);
|
||
|
||
let audioChunks = [];
|
||
|
||
ws.onopen = () => {
|
||
ws.send(JSON.stringify({
|
||
service_type: "TextToTts",
|
||
text: this.mdContent,
|
||
params: {
|
||
tts_stream_format: 'mp3',
|
||
tts_sample_rate: 22050,
|
||
model_name: this.voiceSelected_md,
|
||
delay_gen_audio: true
|
||
}
|
||
}));
|
||
};
|
||
|
||
ws.onmessage = async (event) => {
|
||
if (typeof event.data === 'string') {
|
||
try {
|
||
const message = JSON.parse(event.data);
|
||
if (message.error) {
|
||
this.showError(message.error);
|
||
} else if (message.status === "completed") {
|
||
ws.close();
|
||
}
|
||
} catch (e) {
|
||
console.log("Non-JSON message:", event.data);
|
||
}
|
||
} else {
|
||
audioChunks.push(event.data);
|
||
console.log("收到音频数据", event.data.size);
|
||
}
|
||
};
|
||
|
||
ws.onclose = () => {
|
||
this.isTTSGenerating = false;
|
||
|
||
if (audioChunks.length > 0) {
|
||
const blob = new Blob(audioChunks, {type: 'audio/mpeg'});
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
if (this.preview.audioSrc_md) {
|
||
URL.revokeObjectURL(this.preview.audioSrc_md);
|
||
}
|
||
this.preview.audioSrc_md = url;
|
||
this.preview.tempBlob_md = blob;
|
||
this.preview.downReady_md = true;
|
||
this.preview.visible_md = false;
|
||
}
|
||
};
|
||
|
||
ws.onerror = (error) => {
|
||
this.isTTSGenerating = false;
|
||
this.showError("WebSocket连接错误: " + error.message);
|
||
};
|
||
|
||
} catch (error) {
|
||
this.isTTSGenerating = false;
|
||
this.showError(error.message);
|
||
}
|
||
},
|
||
|
||
// 下载 Markdown TTS
|
||
downloadMdTTS() {
|
||
this.downloadFile(this.preview.audioSrc_md, this.downloadFilename_md);
|
||
},
|
||
|
||
downloadFile(content, filename) {
|
||
let blob = this.preview.tempBlob_md;
|
||
this.downloadStateMd = "download";
|
||
|
||
if (!blob) {
|
||
this.showError("没有可下载的音频");
|
||
return;
|
||
}
|
||
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
|
||
URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
|
||
this.preview.visible_md = true;
|
||
this.downloadStateMd = "finished";
|
||
this.resetDownloadState();
|
||
},
|
||
|
||
// 音色选择变化
|
||
// eslint-disable-next-line no-unused-vars
|
||
voiceSelectedChangeMd(value) {
|
||
this.preview.visible_md = false;
|
||
this.preview.audioSrc_md = "";
|
||
},
|
||
|
||
// 处理上传文件变化
|
||
handleUploadChangeMd(file) {
|
||
console.log(file);
|
||
this.uploadFile_md = file.raw;
|
||
},
|
||
|
||
// 提交 TTS 上传
|
||
submitMdTTSUpload() {
|
||
const formData = new FormData();
|
||
formData.append('file', this.uploadFile_md);
|
||
formData.append('bucket', exhibit_tts_bucket_name);
|
||
formData.append('file_name', `${this.photo_prefix}/${this.uploadFileName_md}`);
|
||
|
||
fetch(`${BASE_API_URL}/minio/put`, {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
"Authorization": "Bearer ragflow-NhZTY5Y2M4YWQ1MzExZWY4Zjc3MDI0Mm"
|
||
}
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Network response was not ok');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(res => {
|
||
const {data} = res;
|
||
if (data?.put) {
|
||
this.updateAntiqueMdTts(data?.url);
|
||
this.$message.success('上传成功');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('上传失败:', error);
|
||
this.$message.error('上传失败');
|
||
});
|
||
},
|
||
|
||
// 删除 Markdown TTS URL
|
||
async removeMdTtsUrl() {
|
||
this.$confirm('确认删除Markdown TTS音频地址?')
|
||
.then(async () => {
|
||
if (this.formData.md_tts_url && this.formData.md_tts_url !== '') {
|
||
const urlParts = this.formData.md_tts_url.split(`${exhibit_tts_bucket_name}/`);
|
||
if (urlParts.length > 1) {
|
||
const fileName = urlParts[1];
|
||
if (fileName && fileName !== "") {
|
||
await this.removeMinioObject(exhibit_tts_bucket_name, fileName);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (await this.updateAntiqueMdTts("")) {
|
||
this.$message('删除音频地址成功');
|
||
} else {
|
||
this.$message('删除音频地址失败');
|
||
}
|
||
}).catch(() => {
|
||
});
|
||
},
|
||
|
||
// 更新数据库中的 md_tts_url
|
||
async updateAntiqueMdTts(tts_url) {
|
||
let antique_data = {
|
||
mesum_id: this.mesumSelectedId,
|
||
md_tts_url: tts_url
|
||
};
|
||
|
||
const response = await fetch(`${BASE_API_URL}/mesum/antique/update/${this.formData.mesum_id}/${this.formData.id}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer ragflow-NhZTY5Y2M4YWQ1MzExZWY4Zjc3MDI0Mm'
|
||
},
|
||
body: JSON.stringify(antique_data)
|
||
});
|
||
|
||
const result = await response.json();
|
||
const {data} = result;
|
||
if (data && data.update) {
|
||
this.formData.md_tts_url = tts_url;
|
||
this.triggerRefreshAntiques();
|
||
return true;
|
||
}
|
||
return false;
|
||
},
|
||
|
||
// 删除 MinIO 对象
|
||
async removeMinioObject(bucket, fileName) {
|
||
fetch(`${BASE_API_URL}/minio/rm`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
bucket: bucket,
|
||
file_name: fileName
|
||
}),
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
"Authorization": "Bearer ragflow-NhZTY5Y2M4YWQ1MzExZWY4Zjc3MDI0Mm"
|
||
}
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Network response was not ok');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(res => {
|
||
const {data} = res;
|
||
if (data?.rm) {
|
||
console.log('删除MinIO对象成功');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('删除minio object 失败:', error);
|
||
});
|
||
},
|
||
|
||
resetDownloadState() {
|
||
setTimeout(() => {
|
||
this.downloadStateMd = 'idle';
|
||
}, 3000);
|
||
},
|
||
|
||
showError(message) {
|
||
this.result.error = message;
|
||
this.result.success = false;
|
||
setTimeout(() => this.result.error = null, 5000);
|
||
},
|
||
|
||
handleDialogClose(done) {
|
||
const audioMd = this.$refs.audioPlayerMd;
|
||
if (audioMd) {
|
||
audioMd.pause();
|
||
}
|
||
done();
|
||
this.closeDialog();
|
||
},
|
||
|
||
closeDialog() {
|
||
this.dialogVisible = false;
|
||
this.$emit("update:close", true);
|
||
},
|
||
|
||
triggerRefreshAntiques() {
|
||
this.$emit("update:refresh", this.formData.id);
|
||
}
|
||
},
|
||
watch: {
|
||
visible: {
|
||
handler(newVal) {
|
||
this.dialogVisible = newVal;
|
||
if (newVal) {
|
||
this.formData = {...this.formData, ...this.initData};
|
||
this.mdContent = "";
|
||
this.mdFile = null;
|
||
this.downloadStateMd = 'idle';
|
||
this.downloadFilename_md = 'md_tts_' + window.pinyin.getFullChars(this.formData.label || 'default') + '.mp3';
|
||
}
|
||
console.log("watch visible", newVal, this.initData);
|
||
},
|
||
immediate: true
|
||
},
|
||
mesumSelectedId: {
|
||
handler(newVal) {
|
||
console.log("watch mesumSelectedId", newVal);
|
||
if (newVal == 2) {
|
||
this.voiceSelected_md = "sambert-zhichu-v1@Tongyi-Qianwen";
|
||
} else if (newVal == 1) {
|
||
this.voiceSelected_md = "cosyvoice-v1/longyuan@Tongyi-Qianwen";
|
||
} else {
|
||
this.voiceSelected_md = "sambert-zhichu-v1@Tongyi-Qianwen";
|
||
}
|
||
},
|
||
immediate: true
|
||
}
|
||
},
|
||
computed: {
|
||
uploadFileName_md() {
|
||
if (this.uploadFile_md && this.uploadFile_md.name)
|
||
return this.uploadFile_md.name;
|
||
else
|
||
return "";
|
||
},
|
||
downloadPromptMd() {
|
||
if (this.downloadStateMd === 'idle') return "";
|
||
if (this.downloadStateMd === 'download') return "下载中...";
|
||
if (this.downloadStateMd === 'save') return "存储中...";
|
||
if (this.downloadStateMd === 'finished') return "完成";
|
||
return "";
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.el-dialog__header {
|
||
background: #f0f9eb !important;
|
||
padding: 2px 2px !important;
|
||
border-bottom: 1px solid #ebeef5;
|
||
}
|
||
|
||
.el-dialog__title {
|
||
margin: 0;
|
||
padding: 0;
|
||
color: #67C23A !important;
|
||
font-size: 16px !important;
|
||
}
|
||
|
||
.el-dialog__headerbtn {
|
||
margin-top: 3px !important;
|
||
top: 2px !important;
|
||
}
|
||
|
||
.form-group {
|
||
padding: 5px;
|
||
margin-bottom: 5px;
|
||
border-bottom: 2px solid #eee;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
input, select, textarea {
|
||
width: 100%;
|
||
padding: 8px;
|
||
}
|
||
|
||
button {
|
||
padding: 10px 20px;
|
||
margin: 5px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.audio-player {
|
||
margin: 20px 0;
|
||
height: 40px
|
||
}
|
||
|
||
.error-message {
|
||
color: red;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.success-message {
|
||
color: green;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.upload-section {
|
||
margin-top: 20px;
|
||
border-top: 2px solid #eee;
|
||
padding-top: 20px;
|
||
height: 10vh
|
||
}
|
||
</style>
|