Files
aiData/static/js/douyin.js
HuangHai 9f483c0643 'commit'
2026-01-21 10:10:30 +08:00

244 lines
9.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { createApp, ref, computed, onMounted } = Vue;
// 1. 全局渲染器 - 纯手写的高鲁棒性解析器,不再依赖 marked
const globalRenderMarkdown = (text) => {
if (!text) return '';
// 0. 预处理:去除 AI 可能返回的代码块标记
let cleanText = text.trim();
// 去除开头的 ```markdown 或 ```
cleanText = cleanText.replace(/^```(markdown)?\s*/i, '');
// 去除结尾的 ```
cleanText = cleanText.replace(/```\s*$/, '');
// 1. 预处理:按行分割
const lines = cleanText.split('\n');
let html = '';
let inList = false;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
// 关键:去除行首尾空白,解决缩进导致的解析失败
let trimmed = line.trim();
// 空行处理
if (!trimmed) {
if (inList) { html += '</ul>\n'; inList = false; }
continue; // 忽略空行,或者可以加 <br>
}
// 2. 内联格式处理(加粗、代码、链接)
// 加粗 **text** -> <strong>text</strong>
trimmed = trimmed.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// 代码 `text` -> <code>text</code>
trimmed = trimmed.replace(/`([^`]+)`/g, '<code>$1</code>');
// 3. 块级元素处理
// 标题 ### Title
if (trimmed.startsWith('###')) {
if (inList) { html += '</ul>\n'; inList = false; }
html += `<h3>${trimmed.replace(/^###\s*/, '')}</h3>`;
}
else if (trimmed.startsWith('##')) {
if (inList) { html += '</ul>\n'; inList = false; }
html += `<h2>${trimmed.replace(/^##\s*/, '')}</h2>`;
}
else if (trimmed.startsWith('#')) {
if (inList) { html += '</ul>\n'; inList = false; }
html += `<h1>${trimmed.replace(/^#\s*/, '')}</h1>`;
}
// 列表 - Item 或 * Item
else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
if (!inList) { html += '<ul>\n'; inList = true; }
html += `<li>${trimmed.substring(2)}</li>`;
}
// 数字列表 1. Item
else if (/^\d+\.\s/.test(trimmed)) {
// 简单起见,数字列表也用 ul或者你可以维护一个 ordered list 状态
if (!inList) { html += '<ul>\n'; inList = true; }
html += `<li>${trimmed.replace(/^\d+\.\s/, '')}</li>`;
}
// 普通段落
else {
if (inList) { html += '</ul>\n'; inList = false; }
html += `<p>${trimmed}</p>`;
}
}
if (inList) { html += '</ul>\n'; }
return html;
};
createApp({
setup() {
const apiBase = ref(window.location.origin || "http://localhost:8000");
// Douyin State
const shareText = ref('');
const douyinLoading = ref(false);
const douyinRecords = ref([]);
let douyinTimer = null;
// Douyin Summary State
const showSummaryDialog = ref(false);
const summaryLoading = ref(false);
const summaryText = ref('');
// 使用全局渲染器
const renderedSummary = computed(() => {
if (!summaryText.value) return '';
const html = globalRenderMarkdown(summaryText.value);
return html;
});
// 这里的配置仍然保留,作为双重保险
if (typeof marked !== 'undefined') {
const m = (typeof marked.marked === 'function') ? marked.marked : marked;
if (m.setOptions) {
m.setOptions({
gfm: true,
breaks: true,
mangle: false,
headerIds: false
});
}
}
// Methods
const startParsing = async () => {
if (!shareText.value.trim()) return;
douyinLoading.value = true;
try {
const response = await axios.post(apiBase.value + '/api/parse', { text: shareText.value });
if (response.data.id || (response.data.ids && response.data.ids.length > 0)) {
shareText.value = '';
fetchDouyinRecords();
if (typeof ElementPlus !== 'undefined') {
const count = response.data.ids ? response.data.ids.length : 1;
ElementPlus.ElMessage.success(`成功提交 ${count} 个解析任务`);
}
}
} catch (error) {
console.error('Error:', error);
if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.error('解析请求失败');
} finally {
douyinLoading.value = false;
}
};
const fetchDouyinRecords = async (isManual) => {
try {
const response = await axios.get(apiBase.value + '/api/records');
const newRecords = response.data;
douyinRecords.value = newRecords.map(newRec => {
const oldRec = douyinRecords.value.find(r => r.id === newRec.id);
return {
...newRec,
expanded: oldRec ? oldRec.expanded : false,
showOriginal: oldRec ? oldRec.showOriginal : false
};
});
if (isManual === true || (isManual && isManual.type === 'click')) {
if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.success('列表已刷新');
}
} catch (error) {
console.error('Error fetching records:', error);
if (isManual === true || (isManual && isManual.type === 'click')) {
if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.error('刷新失败');
}
}
};
const deleteRecord = async (id) => {
if (!confirm('确定要删除这条记录吗?')) return;
try {
await axios.delete(apiBase.value + `/api/records/${id}`);
fetchDouyinRecords();
if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.success('删除成功');
} catch (error) {
console.error('Error deleting:', error);
if (typeof ElementPlus !== 'undefined') ElementPlus.ElMessage.error('删除失败');
}
};
const openSummaryDialog = () => {
showSummaryDialog.value = true;
if (!summaryText.value) {
fetchDouyinSummary();
}
};
const handleSummaryClose = () => {
showSummaryDialog.value = false;
};
const fetchDouyinSummary = async () => {
if (summaryLoading.value) return;
summaryText.value = "";
summaryLoading.value = true;
try {
const response = await fetch(apiBase.value + "/api/douyin/summary", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ids: []})
});
if (!response.body) return;
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const {done, value} = await reader.read();
if (done) break;
const chunk = decoder.decode(value, {stream: true});
summaryText.value += chunk;
}
} catch (e) {
console.error(e);
summaryText.value += "\n(总结过程出错: " + e.message + ")";
} finally {
summaryLoading.value = false;
}
};
const statusColor = (status) => {
switch(status) {
case 'COMPLETED': return '#10b981';
case 'FAILED': return '#ef4444';
case 'PROCESSING': return '#3b82f6';
default: return '#9ca3af';
}
};
const statusType = (status) => {
switch(status) {
case 'COMPLETED': return 'success';
case 'FAILED': return 'danger';
case 'PROCESSING': return 'primary';
default: return 'info';
}
};
const formatDate = (dateStr) => {
if (!dateStr) return '';
return new Date(dateStr).toLocaleString();
};
onMounted(() => {
fetchDouyinRecords();
if (douyinTimer) clearInterval(douyinTimer);
douyinTimer = setInterval(() => {
fetchDouyinRecords();
}, 3000);
});
return {
shareText, douyinLoading, douyinRecords,
startParsing, fetchDouyinRecords, deleteRecord, statusColor, statusType, formatDate,
showSummaryDialog, summaryLoading, summaryText, renderedSummary,
openSummaryDialog, handleSummaryClose, fetchDouyinSummary
};
}
}).use(ElementPlus).mount("#app");