'commit'
This commit is contained in:
@@ -8,6 +8,7 @@ from datetime import datetime
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
import pymysql
|
import pymysql
|
||||||
|
|
||||||
@@ -38,6 +39,9 @@ def get_db_connection():
|
|||||||
class ParseRequest(BaseModel):
|
class ParseRequest(BaseModel):
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
class SummaryRequest(BaseModel):
|
||||||
|
ids: List[str] = []
|
||||||
|
|
||||||
def update_status(id, status, error_msg=None):
|
def update_status(id, status, error_msg=None):
|
||||||
try:
|
try:
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
@@ -258,6 +262,64 @@ def delete_record(id: str):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/api/douyin/summary")
|
||||||
|
async def generate_summary(request: SummaryRequest):
|
||||||
|
try:
|
||||||
|
# Fetch transcripts
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
if request.ids:
|
||||||
|
# Secure way to handle list in SQL
|
||||||
|
format_strings = ','.join(['%s'] * len(request.ids))
|
||||||
|
sql = f"SELECT video_name, transcript FROM t_douyin_record WHERE id IN ({format_strings}) AND status='COMPLETED'"
|
||||||
|
cursor.execute(sql, tuple(request.ids))
|
||||||
|
else:
|
||||||
|
# Default to latest 20
|
||||||
|
cursor.execute("SELECT video_name, transcript FROM t_douyin_record WHERE status='COMPLETED' ORDER BY create_time DESC LIMIT 20")
|
||||||
|
|
||||||
|
records = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if not records:
|
||||||
|
# If no records, just return a simple message stream
|
||||||
|
async def empty_stream():
|
||||||
|
yield "未找到可总结的已完成记录,请先解析视频。"
|
||||||
|
return StreamingResponse(empty_stream(), media_type="text/event-stream")
|
||||||
|
|
||||||
|
# Prepare text
|
||||||
|
full_text = ""
|
||||||
|
for r in records:
|
||||||
|
if r['transcript']:
|
||||||
|
full_text += f"【标题:{r['video_name']}】\n内容:{r['transcript']}\n\n"
|
||||||
|
|
||||||
|
if not full_text:
|
||||||
|
async def empty_text_stream():
|
||||||
|
yield "记录中没有有效的文案内容。"
|
||||||
|
return StreamingResponse(empty_text_stream(), media_type="text/event-stream")
|
||||||
|
|
||||||
|
# Prompt
|
||||||
|
prompt = f"""
|
||||||
|
请对以下充电行业相关的视频内容进行知识精华提取。
|
||||||
|
要求:
|
||||||
|
1. 忽略无关闲聊和口语化表达;
|
||||||
|
2. 按条目列出核心知识点,不要长篇大论;
|
||||||
|
3. 保持简洁专业,只保留干货;
|
||||||
|
4. 返回格式为Markdown列表。
|
||||||
|
|
||||||
|
内容如下:
|
||||||
|
{full_text[:15000]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Limit context to avoid errors, 15000 chars is roughly safe for most models,
|
||||||
|
# but if using a small model, might need less. Assuming robust model.
|
||||||
|
|
||||||
|
return StreamingResponse(get_llm_response(prompt), media_type="text/event-stream")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Summary generation failed: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
async def recover_pending_tasks():
|
async def recover_pending_tasks():
|
||||||
"""
|
"""
|
||||||
Check for tasks stuck in PENDING or PROCESSING state (due to server restart)
|
Check for tasks stuck in PENDING or PROCESSING state (due to server restart)
|
||||||
|
|||||||
@@ -154,12 +154,34 @@
|
|||||||
|
|
||||||
<!-- Douyin Tab -->
|
<!-- Douyin Tab -->
|
||||||
<div class="douyin-container" v-show="activeTab==='douyin'" style="padding: 24px; max-width: 95%; margin: 0 auto;">
|
<div class="douyin-container" v-show="activeTab==='douyin'" style="padding: 24px; max-width: 95%; margin: 0 auto;">
|
||||||
|
<!-- Summary Dialog -->
|
||||||
|
<el-dialog v-model="showSummaryDialog" title="💡 知识精华总结" width="60%" :before-close="handleSummaryClose">
|
||||||
|
<div class="dialog-content" style="max-height: 60vh; overflow-y: auto; padding: 10px;">
|
||||||
|
<div v-if="summaryLoading && !summaryText" style="text-align: center; padding: 20px; color: #6b7280;">
|
||||||
|
<p>⚡ AI正在阅读您的知识库并提炼精华,请稍候...</p>
|
||||||
|
</div>
|
||||||
|
<div class="markdown-body" v-html="renderedSummary" style="font-size: 15px; line-height: 1.8; color: #1f2937;"></div>
|
||||||
|
<span v-if="summaryLoading && summaryText" class="cursor-blink">|</span>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleSummaryClose">关闭</el-button>
|
||||||
|
<el-button type="primary" @click="fetchDouyinSummary" :loading="summaryLoading">
|
||||||
|
{{ summaryText ? '重新生成' : '开始总结' }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<div class="douyin-header" style="text-align: center; margin-bottom: 40px;">
|
<div class="douyin-header" style="text-align: center; margin-bottom: 40px;">
|
||||||
<h1 style="font-size: 2.5rem; font-weight: bold; color: #f1f5f9; margin-bottom: 0.5rem;">
|
<h1 style="font-size: 2.5rem; font-weight: bold; color: #f1f5f9; margin-bottom: 0.5rem;">
|
||||||
抖音知识库
|
抖音知识库
|
||||||
<button class="btn-primary" @click="fetchDouyinRecords" style="font-size: 1rem; padding: 4px 12px; margin-left: 12px; vertical-align: middle;">
|
<button class="btn-primary" @click="fetchDouyinRecords" style="font-size: 1rem; padding: 4px 12px; margin-left: 12px; vertical-align: middle;">
|
||||||
🔄 刷新列表
|
🔄 刷新列表
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn-primary" @click="openSummaryDialog" style="font-size: 1rem; padding: 4px 12px; margin-left: 12px; vertical-align: middle; background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); border: none;">
|
||||||
|
✨ 提取知识精华
|
||||||
|
</button>
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color: #94a3b8;">自动解析视频、提取文案,构建企业充电知识图谱</p>
|
<p style="color: #94a3b8;">自动解析视频、提取文案,构建企业充电知识图谱</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ createApp({
|
|||||||
const douyinRecords = ref([]);
|
const douyinRecords = ref([]);
|
||||||
let douyinTimer = null;
|
let douyinTimer = null;
|
||||||
|
|
||||||
|
// Douyin Summary State
|
||||||
|
const showSummaryDialog = ref(false);
|
||||||
|
const summaryLoading = ref(false);
|
||||||
|
const summaryText = ref('');
|
||||||
|
|
||||||
// Handle window resize
|
// Handle window resize
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
isMobile.value = window.innerWidth <= 768;
|
isMobile.value = window.innerWidth <= 768;
|
||||||
@@ -468,6 +473,15 @@ createApp({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const renderedSummary = computed(() => {
|
||||||
|
if (!summaryText.value) return '';
|
||||||
|
try {
|
||||||
|
return marked.parse(summaryText.value);
|
||||||
|
} catch (e) {
|
||||||
|
return summaryText.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Watchers
|
// Watchers
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user