const { createApp, ref, computed, onMounted } = Vue; 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(''); // 降级用的简易 Markdown 解析器 const simpleMarkdown = (text) => { if (!text) return ''; let lines = text.split('\n'); let html = ''; let inList = false; const parseInline = (str) => { return str .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/`(.*?)`/g, '$1'); }; for (let line of lines) { let trimmed = line.trim(); if (!trimmed) { if (inList) { html += ''; inList = false; } html += '
'; continue; } if (trimmed.startsWith('### ')) { if (inList) { html += ''; inList = false; } html += `

${parseInline(trimmed.substring(4))}

`; } else if (trimmed.startsWith('- ') || /^\d+\./.test(trimmed)) { if (!inList) { html += ''; inList = false; } html += `

${parseInline(trimmed)}

`; } } if (inList) html += ''; return html; }; // Configure Marked if (typeof marked !== 'undefined') { marked.use({ gfm: true, breaks: true }); } // 增强的 Markdown & LaTeX 解析器 const renderMarkdownAndLatex = (text) => { if (!text) return ''; try { // 1. 处理 LaTeX (简单替换,先处理 $$ 再处理 $) let processedText = text; // 处理块级 LaTeX: $$ ... $$ processedText = processedText.replace(/\$\$\s*([\s\S]*?)\s*\$\$/g, (match, formula) => { try { if (typeof katex !== 'undefined') { return '
' + katex.renderToString(formula, { displayMode: true, throwOnError: false }) + '
'; } return match; } catch (e) { return match; } }); // 处理行内 LaTeX: $ ... $ processedText = processedText.replace(/\$([^\$\n]+?)\$/g, (match, formula) => { try { if (typeof katex !== 'undefined') { return katex.renderToString(formula, { displayMode: false, throwOnError: false }); } return match; } catch (e) { return match; } }); // 2. 使用 marked 解析 Markdown if (typeof marked !== 'undefined') { return marked.parse(processedText); } else { // 降级使用之前的 simpleMarkdown return simpleMarkdown(processedText); } } catch (e) { console.error('Markdown/LaTeX rendering error:', e); return text; } }; const renderedSummary = computed(() => { if (!summaryText.value) return ''; return renderMarkdownAndLatex(summaryText.value); }); // 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");