feat(kb-deep-analyzer): 添加流式写入器 StreamingWriter
- 支持多线程并发写入到文档不同位置 - 初始化时创建带占位符的文档结构 - 每批完成后写入对应位置 - 支持更新摘要、主题、见解等全局内容
This commit is contained in:
@@ -47,6 +47,182 @@ class AnalysisResult:
|
||||
|
||||
# ============== AI 客户端 ==============
|
||||
|
||||
# ============== 流式写入器 ==============
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
class StreamingWriter:
|
||||
"""
|
||||
流式写入器 - 支持多线程并发写入到文档不同位置
|
||||
|
||||
使用方法:
|
||||
1. 初始化: writer = StreamingWriter(output_path, num_chunks)
|
||||
2. 预先创建带占位符的文档结构
|
||||
3. 每批完成后调用 writer.write_batch(batch_results)
|
||||
4. 完成后调用 writer.mark_complete() 清理占位符
|
||||
"""
|
||||
|
||||
def __init__(self, output_path: str, num_chunks: int = 0, batch_size: int = 3):
|
||||
self.output_path = Path(output_path)
|
||||
self.num_chunks = num_chunks
|
||||
self.batch_size = batch_size
|
||||
self.lock = threading.Lock()
|
||||
self.written = {} # 记录已写入的位置
|
||||
self.batch_num = 0
|
||||
|
||||
def initialize_document(self, metadata: Dict, num_chunks: int) -> str:
|
||||
"""
|
||||
初始化文档结构,返回带占位符的内容
|
||||
|
||||
文档结构:
|
||||
---
|
||||
title: "xxx"
|
||||
...
|
||||
---
|
||||
|
||||
# 标题
|
||||
|
||||
## 摘要
|
||||
|
||||
## 主要主题
|
||||
|
||||
## 关键见解
|
||||
|
||||
## 建议行动
|
||||
|
||||
## 时间轴
|
||||
### 第 N 部分 (00:00 - 00:00)
|
||||
[ANALYZING...]
|
||||
"""
|
||||
self.num_chunks = num_chunks
|
||||
|
||||
# 生成时间轴占位符
|
||||
timeline_placeholders = ""
|
||||
for i in range(num_chunks):
|
||||
timeline_placeholders += f"""### 第 {i + 1} 部分 (待分析)
|
||||
[ANALYZING_BATCH_{i // self.batch_size + 1}]
|
||||
|
||||
"""
|
||||
|
||||
content = f"""---
|
||||
title: "{metadata.get('title', '未命名')}"
|
||||
date: {datetime.now().strftime('%Y-%m-%d')}
|
||||
duration: {format_duration(metadata.get('duration', 0))}
|
||||
author: {metadata.get('author', '未知')}
|
||||
analyze_time: {datetime.now().strftime('%Y-%m-%d %H:%M')}
|
||||
bvid: {metadata.get('bvid', '')}
|
||||
platform: {metadata.get('platform', 'bilibili')}
|
||||
---
|
||||
|
||||
# {metadata.get('title', '未命名')}
|
||||
|
||||
## 摘要
|
||||
[WAITING_FOR_ANALYSIS...]
|
||||
|
||||
## 主要主题
|
||||
[WAITING_FOR_ANALYSIS...]
|
||||
|
||||
## 关键见解
|
||||
[WAITING_FOR_ANALYSIS...]
|
||||
|
||||
## 建议行动
|
||||
[WAITING_FOR_ANALYSIS...]
|
||||
|
||||
## 时间轴
|
||||
{timeline_placeholders}
|
||||
|
||||
---
|
||||
*分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}*
|
||||
"""
|
||||
|
||||
# 写入初始化内容
|
||||
self.output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"[StreamingWriter] 初始化文档: {self.output_path}")
|
||||
return content
|
||||
|
||||
def write_batch(self, batch_results: List[Dict], num_batches: int) -> None:
|
||||
"""
|
||||
写入一批结果
|
||||
|
||||
Args:
|
||||
batch_results: 当前批的结果列表
|
||||
num_batches: 总批次数
|
||||
"""
|
||||
self.batch_num += 1
|
||||
|
||||
with self.lock:
|
||||
# 读取当前内容
|
||||
if not self.output_path.exists():
|
||||
print(f"[StreamingWriter] 警告: 文件不存在 {self.output_path}")
|
||||
return
|
||||
|
||||
content = self.output_path.read_text(encoding='utf-8')
|
||||
|
||||
for result in batch_results:
|
||||
idx = result['index']
|
||||
start_time = result['start_time']
|
||||
end_time = result['end_time']
|
||||
summary = result['summary']
|
||||
|
||||
# 查找并替换占位符
|
||||
old_placeholder = f"### 第 {idx + 1} 部分 ({start_time} - {end_time})\n[ANALYZING_BATCH_{self.batch_num}]"
|
||||
new_content = f"""### 第 {idx + 1} 部分 ({start_time} - {end_time})
|
||||
{summary}"""
|
||||
|
||||
content = content.replace(old_placeholder, new_content, 1)
|
||||
self.written[idx] = True
|
||||
|
||||
print(f"[StreamingWriter] 写入段 {idx + 1}/{self.num_chunks}")
|
||||
|
||||
# 如果这是最后一批,更新其他状态
|
||||
if self.batch_num >= num_batches:
|
||||
# 更新摘要等
|
||||
pass
|
||||
|
||||
# 写回文件
|
||||
self.output_path.write_text(content, encoding='utf-8')
|
||||
print(f"[StreamingWriter] 批次 {self.batch_num}/{num_batches} 完成")
|
||||
|
||||
def update_summary(self, summary_text: str, topics: List[str], insights: List[str]) -> None:
|
||||
"""更新摘要等全局内容"""
|
||||
with self.lock:
|
||||
if not self.output_path.exists():
|
||||
return
|
||||
|
||||
content = self.output_path.read_text(encoding='utf-8')
|
||||
|
||||
# 更新摘要
|
||||
if "[WAITING_FOR_ANALYSIS...]" in content:
|
||||
content = content.replace("## 摘要\n[WAITING_FOR_ANALYSIS...]", f"## 摘要\n{summary_text}")
|
||||
|
||||
# 更新主题
|
||||
topics_text = "\n".join(f"- {t}" for t in topics)
|
||||
if "## 主要主题\n[WAITING_FOR_ANALYSIS...]" in content:
|
||||
content = content.replace("## 主要主题\n[WAITING_FOR_ANALYSIS...]", f"## 主要主题\n{topics_text}")
|
||||
|
||||
# 更新见解
|
||||
insights_text = "\n".join(f"- {i}" for i in insights)
|
||||
if "## 关键见解\n[WAITING_FOR_ANALYSIS...]" in content:
|
||||
content = content.replace("## 关键见解\n[WAITING_FOR_ANALYSIS...]", f"## 关键见解\n{insights_text}")
|
||||
|
||||
self.output_path.write_text(content, encoding='utf-8')
|
||||
print(f"[StreamingWriter] 更新摘要和主题")
|
||||
|
||||
def cleanup_placeholder(self, placeholder: str) -> None:
|
||||
"""清理单个占位符"""
|
||||
with self.lock:
|
||||
if not self.output_path.exists():
|
||||
return
|
||||
|
||||
content = self.output_path.read_text(encoding='utf-8')
|
||||
content = content.replace(placeholder, "")
|
||||
self.output_path.write_text(content, encoding='utf-8')
|
||||
|
||||
|
||||
class SimpleAIClient:
|
||||
"""简化的 AI 客户端 - 支持多提供商 fallback"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user