Group02-notes/note_organizer.py

1072 lines
41 KiB
Python
Raw Permalink Normal View History

import streamlit as st
from datetime import datetime
import json
import os
import re
from collections import Counter
import requests
from dotenv import load_dotenv
# 1. 页面配置
st.set_page_config(page_title="学习笔记整理器", page_icon="📚", layout="wide")
# 加载环境变量
load_dotenv()
# DeepSeek API配置
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions"
# 常量定义
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
def call_deepseek_api(messages, model="deepseek-chat", temperature=0.7, max_tokens=2000):
"""调用DeepSeek API"""
try:
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {DEEPSEEK_API_KEY}"
}
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
}
response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"]
except Exception as e:
st.error(f"AI API调用失败: {str(e)}")
return None
# 添加自定义CSS样式
st.markdown("""
<style>
/* 全局样式优化 */
.main {
padding-top: 2rem;
}
/* 标题样式 */
h1 {
color: #1a1a1a;
font-weight: 700;
margin-bottom: 1.5rem;
}
/* 侧边栏样式 */
[data-testid="stSidebar"] {
background-color: #f8f9fa;
border-right: 2px solid #e9ecef;
}
/* 笔记卡片样式 */
.stExpander {
border: 1px solid #dee2e6;
border-radius: 8px;
margin-bottom: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.stExpander:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* 按钮样式优化 */
.stButton > button {
border-radius: 6px;
font-weight: 500;
transition: all 0.2s ease;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
/* 输入框样式 */
.stTextInput > div > div > input,
.stTextArea > div > div > textarea,
.stSelectbox > div > div > select {
border-radius: 6px;
border: 1px solid #ced4da;
transition: border-color 0.2s ease;
}
.stTextInput > div > div > input:focus,
.stTextArea > div > div > textarea:focus,
.stSelectbox > div > div > select:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
/* 标签样式 */
.tag {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
margin: 3px;
display: inline-block;
box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3);
}
/* 关键词标签样式 */
.keyword {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
margin: 3px;
display: inline-block;
box-shadow: 0 2px 4px rgba(240, 147, 251, 0.3);
}
/* 分隔线样式 */
hr {
border: none;
height: 2px;
background: linear-gradient(90deg, transparent, #007bff, transparent);
margin: 2rem 0;
}
/* 统计卡片样式 */
[data-testid="stMetricValue"] {
font-size: 2rem;
font-weight: 700;
color: #007bff;
}
/* 信息框样式 */
.stAlert {
border-radius: 8px;
border-left: 4px solid;
}
/* 成功提示 */
.stAlert[role="alert"] {
background-color: #d4edda;
border-color: #28a745;
}
/* 警告提示 */
.stAlert[role="alertwarning"] {
background-color: #fff3cd;
border-color: #ffc107;
}
/* 错误提示 */
.stAlert[role="alerterror"] {
background-color: #f8d7da;
border-color: #dc3545;
}
/* 响应式设计 */
@media (max-width: 768px) {
.main {
padding: 1rem;
}
.stButton > button {
width: 100%;
margin: 5px 0;
}
}
</style>
""", unsafe_allow_html=True)
st.title("📚 学习笔记整理器")
# 创建主标签页
tab_notes, tab_report = st.tabs(["📝 笔记管理", "🤖 AI学习报告"])
# 文本提炼函数
def extract_key_content(content, max_sentences=3, max_keywords=5):
"""提炼笔记的关键内容"""
if not content or len(content.strip()) < 10:
return {"summary": "内容太短,无法提炼", "keywords": []}
# 分句
sentences = re.split(r'[。!?\n]', content)
sentences = [s.strip() for s in sentences if s.strip()]
# 提取关键句子(基于句子长度和关键词密度)
sentence_scores = []
for sentence in sentences:
score = len(sentence)
# 检查是否包含常见关键词
keywords = ['重要', '关键', '核心', '主要', '注意', '必须', '应该', '需要', '总结', '结论']
for kw in keywords:
if kw in sentence:
score += 20
sentence_scores.append((sentence, score))
# 按分数排序,取前几句
sentence_scores.sort(key=lambda x: x[1], reverse=True)
top_sentences = [s[0] for s in sentence_scores[:max_sentences]]
# 提取关键词
words = re.findall(r'[\u4e00-\u9fa5]{2,}', content)
word_freq = Counter(words)
# 过滤常见词
stop_words = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '我们', '你们', '他们']
keywords = [w for w, c in word_freq.most_common(max_keywords * 2) if w not in stop_words][:max_keywords]
return {
"summary": "".join(top_sentences) + "" if top_sentences else "无法提取摘要",
"keywords": keywords
}
# 辅助函数:获取所有标签
def get_all_tags():
"""获取所有笔记中的标签"""
return list(set(tag for note in st.session_state.notes for tag in note.get("tags", [])))
# 辅助函数:筛选笔记
def filter_notes(notes, search_query="", search_scope="全部", filter_category="全部", filter_tag="全部"):
"""根据条件筛选笔记"""
filtered = []
for note in notes:
# 分类筛选
category_match = filter_category == "全部" or note["category"] == filter_category
# 标签筛选
tag_match = filter_tag == "全部" or filter_tag in note.get("tags", [])
# 关键词搜索(根据选择范围)
search_match = True
if search_query:
query = search_query.lower()
if search_scope == "全部":
search_match = (
query in note["title"].lower() or
query in note["content"].lower() or
query in note["category"].lower() or
any(query in tag.lower() for tag in note.get("tags", []))
)
elif search_scope == "仅标题":
search_match = query in note["title"].lower()
elif search_scope == "仅内容":
search_match = query in note["content"].lower()
elif search_scope == "仅类型":
search_match = query in note["category"].lower()
elif search_scope == "仅标签":
search_match = any(query in tag.lower() for tag in note.get("tags", []))
if category_match and tag_match and search_match:
filtered.append(note)
return filtered
# 辅助函数格式化标签HTML
def format_tags_html(tags, style="default"):
"""格式化标签为HTML"""
if style == "default":
return " ".join([f'<span style="background-color: #e1f5fe; color: #01579b; padding: 2px 8px; border-radius: 4px; margin-right: 5px; font-size: 12px;">{tag}</span>' for tag in tags])
elif style == "preview":
return " ".join([f'<span style="background-color: #fff3e0; color: #e65100; padding: 2px 8px; border-radius: 4px; margin-right: 5px; font-size: 12px;">{tag}</span>' for tag in tags])
return ""
# 辅助函数:排序笔记
def sort_notes(notes, sort_by="最新"):
"""根据指定方式排序笔记"""
sorted_notes = notes.copy()
if sort_by == "最新":
sorted_notes.sort(key=lambda x: x["created_at"], reverse=True)
elif sort_by == "最旧":
sorted_notes.sort(key=lambda x: x["created_at"])
elif sort_by == "标题A-Z":
sorted_notes.sort(key=lambda x: x["title"].lower())
elif sort_by == "标题Z-A":
sorted_notes.sort(key=lambda x: x["title"].lower(), reverse=True)
elif sort_by == "类型A-Z":
sorted_notes.sort(key=lambda x: x["category"].lower())
elif sort_by == "类型Z-A":
sorted_notes.sort(key=lambda x: x["category"].lower(), reverse=True)
return sorted_notes
# AI学习报告生成函数
def generate_study_report(notes):
"""生成AI学习报告"""
if not notes:
return None
# 统计分析
total_notes = len(notes)
categories = {}
all_tags = []
total_content_length = 0
notes_summary = []
for note in notes:
# 分类统计
cat = note['category']
categories[cat] = categories.get(cat, 0) + 1
# 标签统计
all_tags.extend(note.get('tags', []))
# 内容长度统计
total_content_length += len(note['content'])
# 收集笔记摘要用于AI分析
notes_summary.append({
"title": note['title'],
"category": note['category'],
"tags": note.get('tags', []),
"content_length": len(note['content']),
"created_at": note.get('created_at', '')
})
# 标签频率统计
tag_counts = Counter(all_tags)
top_tags = tag_counts.most_common(10)
# 使用DeepSeek API生成智能学习建议
ai_suggestions = []
if DEEPSEEK_API_KEY:
with st.spinner("🤖 AI正在分析您的学习数据..."):
# 构建AI分析的prompt
system_prompt = """你是一位专业的学习顾问,擅长分析学生的学习笔记并提供个性化的学习建议。
请根据提供的笔记数据生成3-5条具体实用的学习建议
建议应该包括
1. 学习方法和策略
2. 知识体系的构建
3. 学习重点和方向
4. 改进建议
每条建议应该简洁明了不超过50字使用emoji开头"""
user_prompt = f"""请分析以下学习笔记数据,并给出个性化学习建议:
笔记总数{total_notes}
分类统计{categories}
热门标签{tag_counts.most_common(5)}
总字数{total_content_length}
平均每条笔记字数{int(total_content_length/total_notes) if total_notes > 0 else 0}
笔记概览
{json.dumps(notes_summary[:10], ensure_ascii=False, indent=2)}
请基于以上数据给出3-5条具体的学习建议"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
ai_response = call_deepseek_api(messages, temperature=0.8, max_tokens=500)
if ai_response:
ai_suggestions = ai_response.strip().split('\n')
ai_suggestions = [s.strip() for s in ai_suggestions if s.strip()]
else:
st.error("❌ AI生成建议失败请检查API配置或稍后重试")
# 生成学习进度报告
report = {
"总览": {
"笔记总数": total_notes,
"分类数量": len(categories),
"标签总数": len(set(all_tags)),
"总字数": total_content_length,
"平均每条笔记字数": int(total_content_length/total_notes) if total_notes > 0 else 0
},
"分类统计": categories,
"热门标签": top_tags,
"学习建议": ai_suggestions,
"生成时间": datetime.now().strftime(DATETIME_FORMAT),
"AI生成": len(ai_suggestions) > 0 and DEEPSEEK_API_KEY is not None
}
return report
# 2. 数据持久化函数
def save_notes():
"""保存笔记到 JSON 文件"""
data = {
"notes": st.session_state.notes,
"categories": st.session_state.categories
}
with open('notes_data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load_notes():
"""从 JSON 文件加载笔记"""
if os.path.exists('notes_data.json'):
with open('notes_data.json', 'r', encoding='utf-8') as f:
data = json.load(f)
st.session_state.notes = data.get("notes", [])
st.session_state.categories = data.get("categories", ["编程", "数学", "英语", "其他"])
# 3. 初始化数据
if "notes" not in st.session_state:
st.session_state.notes = []
load_notes()
if "categories" not in st.session_state:
st.session_state.categories = ["编程", "数学", "英语", "其他"]
load_notes()
# 初始化编辑状态
if "editing_note" not in st.session_state:
st.session_state.editing_note = None
# 初始化清空确认状态
if "confirm_clear" not in st.session_state:
st.session_state.confirm_clear = False
# 4. 左侧侧边栏 - 添加笔记和管理类型
with tab_notes:
with st.sidebar:
st.header("📝 添加笔记")
# 笔记标题
title = st.text_input("标题", placeholder="输入笔记标题")
# 分类选择(支持自定义)
category = st.selectbox(
"选择分类",
st.session_state.categories,
help="选择笔记的分类"
)
# 自定义分类输入
new_category = st.text_input(
"或创建新分类",
placeholder="输入新分类名称,按回车添加",
key="new_category_input"
)
# 添加新分类按钮
if st.button(" 添加新分类"):
if new_category and new_category not in st.session_state.categories:
st.session_state.categories.append(new_category)
save_notes()
st.success(f"已添加新分类:{new_category}")
st.rerun()
elif new_category in st.session_state.categories:
st.warning("该分类已存在")
# 笔记内容
content = st.text_area(
"笔记内容",
placeholder="输入笔记内容...",
height=200
)
# 标签输入
tags_input = st.text_input(
"标签",
placeholder="输入标签,用逗号分隔 (例如: 重要, 复习, 考试)",
help="为笔记添加多个标签,便于分类和搜索"
)
tags = [tag.strip() for tag in tags_input.split(",") if tag.strip()]
# 实时预览提炼结果
if content and len(content.strip()) > 10:
with st.expander("🔍 预览提炼结果", expanded=False):
extracted = extract_key_content(content)
st.markdown("**摘要预览:**")
st.info(extracted['summary'])
st.markdown("**关键词预览:**")
if extracted['keywords']:
keywords_html = format_tags_html(extracted['keywords'], style="preview")
st.markdown(keywords_html, unsafe_allow_html=True)
# 添加按钮
if st.button("添加笔记", type="primary"):
if title and content:
note = {
"id": len(st.session_state.notes) + 1,
"title": title,
"category": category,
"content": content,
"tags": tags,
"created_at": datetime.now().strftime(DATETIME_FORMAT),
"updated_at": datetime.now().strftime(DATETIME_FORMAT)
}
st.session_state.notes.append(note)
save_notes()
st.success("笔记添加成功!")
else:
st.warning("请填写标题和内容")
st.divider()
# 显示所有分类
st.subheader("📂 所有分类")
for cat in st.session_state.categories:
st.markdown(f"- {cat}")
# 5. 主界面 - 笔记展示和搜索
st.header("📖 我的笔记")
# 搜索和排序区域
col1, col2, col3, col4, col5 = st.columns([2, 1, 1, 1, 1])
with col1:
# 搜索关键词
search_query = st.text_input(
"🔍 搜索关键词",
placeholder="输入关键词...",
help="支持模糊搜索"
)
with col2:
# 搜索范围选择
search_scope = st.selectbox(
"搜索范围",
["全部", "仅标题", "仅内容", "仅类型", "仅标签"],
help="选择搜索的范围"
)
with col3:
# 分类筛选
filter_category = st.selectbox(
"筛选分类",
["全部"] + st.session_state.categories,
help="按分类筛选笔记"
)
with col4:
# 标签筛选
all_tags = get_all_tags()
filter_tag = st.selectbox(
"筛选标签",
["全部"] + sorted(all_tags),
help="按标签筛选笔记"
)
with col5:
# 排序方式
sort_by = st.selectbox(
"排序方式",
["最新", "最旧", "标题A-Z", "标题Z-A", "类型A-Z", "类型Z-A"],
help="选择笔记的排序方式"
)
# 批量提炼功能
st.divider()
col_batch1, col_batch2 = st.columns([3, 1])
with col_batch1:
st.markdown("### ✨ 批量提炼")
with col_batch2:
if st.button("🔄 提炼所有笔记", key="batch_extract"):
for note in st.session_state.notes:
if len(note['content'].strip()) > 10:
extracted = extract_key_content(note['content'])
st.session_state[f'extracted_{note["id"]}'] = extracted
st.success(f"已提炼 {len(st.session_state.notes)} 条笔记的关键内容!")
st.rerun()
# 6. 显示笔记
if st.session_state.notes:
# 使用辅助函数进行筛选和排序
filtered_notes = filter_notes(
st.session_state.notes,
search_query=search_query,
search_scope=search_scope,
filter_category=filter_category,
filter_tag=filter_tag
)
filtered_notes = sort_notes(filtered_notes, sort_by)
# 显示筛选结果
if filtered_notes:
st.info(f"找到 {len(filtered_notes)} 条笔记")
# 使用两列布局展示笔记
cols = st.columns(2)
for i, note in enumerate(filtered_notes):
with cols[i % 2]:
# 检查是否正在编辑此笔记
if st.session_state.editing_note == note['id']:
with st.expander(f"✏️ 编辑: {note['title']}", expanded=True):
# 编辑表单
edit_title = st.text_input("标题", value=note['title'], key=f"edit_title_{note['id']}")
edit_category = st.selectbox(
"分类",
st.session_state.categories,
index=st.session_state.categories.index(note['category']) if note['category'] in st.session_state.categories else 0,
key=f"edit_category_{note['id']}"
)
edit_content = st.text_area("内容", value=note['content'], height=200, key=f"edit_content_{note['id']}")
# 编辑标签
current_tags = ", ".join(note.get("tags", []))
edit_tags = st.text_input(
"标签",
value=current_tags,
key=f"edit_tags_{note['id']}",
help="输入标签,用逗号分隔"
)
edit_tags_list = [tag.strip() for tag in edit_tags.split(",") if tag.strip()]
# 保存和取消按钮
col_save, col_cancel = st.columns(2)
with col_save:
if st.button("💾 保存修改", key=f"save_{note['id']}", type="primary"):
note['title'] = edit_title
note['category'] = edit_category
note['content'] = edit_content
note['tags'] = edit_tags_list
note['updated_at'] = datetime.now().strftime(DATETIME_FORMAT)
st.session_state.editing_note = None
save_notes()
st.success("笔记已更新!")
st.rerun()
with col_cancel:
if st.button("❌ 取消", key=f"cancel_{note['id']}"):
st.session_state.editing_note = None
st.rerun()
else:
# 正常显示笔记
with st.expander(f"📌 {note['title']}", expanded=False):
# 显示笔记元信息
st.markdown(f"**分类:** {note['category']}")
# 显示标签
if note.get("tags"):
tags_html = format_tags_html(note["tags"])
st.markdown(f"**标签:** {tags_html}", unsafe_allow_html=True)
st.markdown(f"**创建时间:** {note['created_at']}")
if 'updated_at' in note:
st.markdown(f"**更新时间:** {note['updated_at']}")
st.markdown("---")
# 提炼内容按钮
if st.button("✨ 提炼内容", key=f"extract_{note['id']}"):
extracted = extract_key_content(note['content'])
st.session_state[f'extracted_{note["id"]}'] = extracted
# 显示提炼结果
if f'extracted_{note["id"]}' in st.session_state:
extracted = st.session_state[f'extracted_{note["id"]}']
with st.expander("📝 提炼结果", expanded=True):
st.markdown("**摘要:**")
st.info(extracted['summary'])
st.markdown("**关键词:**")
if extracted['keywords']:
keywords_html = format_tags_html(extracted['keywords'], style="preview")
st.markdown(keywords_html, unsafe_allow_html=True)
else:
st.warning("未找到关键词")
# 显示笔记内容
st.markdown(note['content'])
# 操作按钮
col_edit, col_delete = st.columns(2)
with col_edit:
if st.button("✏️ 编辑", key=f"edit_{note['id']}"):
st.session_state.editing_note = note['id']
st.rerun()
with col_delete:
if st.button("🗑️ 删除", key=f"delete_{note['id']}"):
st.session_state.notes.remove(note)
st.session_state.editing_note = None
save_notes()
st.rerun()
else:
st.info("没有找到匹配的笔记")
else:
st.info("还没有笔记,请在左侧添加您的第一条笔记!")
# 7. 统计信息
st.divider()
st.subheader("📊 统计信息")
# 笔记和分类统计
col_stats1, col_stats2 = st.columns(2)
with col_stats1:
st.metric("总笔记数", len(st.session_state.notes))
with col_stats2:
all_tags = get_all_tags()
st.metric("总标签数", len(all_tags))
# 分类统计
st.markdown("### 分类统计")
cat_stat_cols = st.columns(min(5, len(st.session_state.categories)))
for i, cat in enumerate(st.session_state.categories):
if i < len(cat_stat_cols):
with cat_stat_cols[i]:
count = len([n for n in st.session_state.notes if n["category"] == cat])
st.metric(cat, count)
# 标签统计
if all_tags:
st.markdown("### 标签统计")
tag_stat_cols = st.columns(min(5, len(all_tags)))
for i, tag in enumerate(sorted(all_tags)):
if i < len(tag_stat_cols):
with tag_stat_cols[i]:
count = len([n for n in st.session_state.notes if tag in n.get("tags", [])])
st.metric(tag, count)
# 8. 数据管理
st.divider()
st.subheader("💾 数据管理")
# 导入JSON文件
st.markdown("### 📥 导入笔记")
tab_json, tab_txt = st.tabs(["JSON文件", "文本文件"])
with tab_json:
uploaded_file = st.file_uploader("选择JSON文件", type=['json'], help="选择之前导出的JSON文件", key="json_uploader")
if uploaded_file is not None:
try:
# 读取并解析JSON文件
import_data = json.load(uploaded_file)
# 显示导入预览
st.info(f"文件包含 {len(import_data.get('notes', []))} 条笔记")
# 显示笔记列表预览
with st.expander("📋 查看文件内容", expanded=False):
for i, note in enumerate(import_data.get('notes', [])[:5]):
st.markdown(f"**{i+1}. {note.get('title', '无标题')}**")
st.markdown(f" 分类: {note.get('category', '未知')}")
st.markdown(f" 时间: {note.get('created_at', '未知')}")
st.markdown("---")
if len(import_data.get('notes', [])) > 5:
st.info(f"还有 {len(import_data.get('notes', [])) - 5} 条笔记...")
# 导入选项
col_merge, col_replace = st.columns(2)
with col_merge:
if st.button("🔄 合并导入", type="primary", key="merge_json"):
# 合并导入:保留现有笔记,添加新笔记
new_notes = import_data.get('notes', [])
for note in new_notes:
note['id'] = len(st.session_state.notes) + 1
st.session_state.notes.append(note)
# 合并分类
new_categories = import_data.get('categories', [])
for cat in new_categories:
if cat not in st.session_state.categories:
st.session_state.categories.append(cat)
save_notes()
st.success(f"成功导入 {len(new_notes)} 条笔记!")
st.rerun()
with col_replace:
if st.button("⚠️ 替换导入", key="replace_json"):
# 替换导入:清空现有笔记,导入新笔记
st.session_state.notes = import_data.get('notes', [])
st.session_state.categories = import_data.get('categories', ["编程", "数学", "英语", "其他"])
st.session_state.editing_note = None
save_notes()
st.success(f"成功替换为 {len(st.session_state.notes)} 条笔记!")
st.rerun()
except Exception as e:
st.error(f"文件解析失败: {str(e)}")
with tab_txt:
# 导入文本文件
txt_file = st.file_uploader("选择文本文件", type=['txt'], help="选择要导入的文本文件", key="txt_uploader")
if txt_file is not None:
try:
# 读取文本文件内容
content = txt_file.read().decode('utf-8')
# 显示文件内容预览
st.info(f"文件大小: {len(content)} 字符")
with st.expander("📋 查看文件内容", expanded=False):
st.text(content[:1000])
if len(content) > 1000:
st.info(f"还有 {len(content) - 1000} 个字符...")
# 设置导入参数
col_title, col_cat = st.columns(2)
with col_title:
import_title = st.text_input("笔记标题", value=txt_file.name.replace('.txt', ''), help="为导入的笔记设置标题")
with col_cat:
import_category = st.selectbox("笔记分类", st.session_state.categories)
# 导入按钮
if st.button("📥 导入文本文件", type="primary"):
note = {
"id": len(st.session_state.notes) + 1,
"title": import_title,
"category": import_category,
"content": content,
"created_at": datetime.now().strftime(DATETIME_FORMAT),
"updated_at": datetime.now().strftime(DATETIME_FORMAT)
}
st.session_state.notes.append(note)
save_notes()
st.success(f"成功导入笔记:{import_title}")
st.rerun()
except Exception as e:
st.error(f"文件读取失败: {str(e)}")
st.divider()
# 导出功能
st.markdown("### 📤 导出笔记")
export_tab_json, export_tab_txt = st.tabs(["JSON格式", "文本格式"])
with export_tab_json:
if st.button("📤 导出为JSON", key="export_json"):
st.download_button(
label="下载 JSON 文件",
data=json.dumps({"notes": st.session_state.notes, "categories": st.session_state.categories}, ensure_ascii=False, indent=2),
file_name=f"notes_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
with export_tab_txt:
# 导出选项
export_option = st.radio("导出选项", ["导出所有笔记", "导出单个笔记"])
if export_option == "导出所有笔记":
if st.button("📤 导出所有笔记为文本", type="primary", key="export_all_txt"):
# 合并所有笔记为一个文本文件
all_content = "=" * 50 + "\n"
all_content += f"学习笔记备份 - {datetime.now().strftime(DATETIME_FORMAT)}\n"
all_content += "=" * 50 + "\n\n"
for note in st.session_state.notes:
all_content += "-" * 40 + "\n"
all_content += f"标题: {note['title']}\n"
all_content += f"分类: {note['category']}\n"
all_content += f"创建时间: {note['created_at']}\n"
all_content += "-" * 40 + "\n\n"
all_content += note['content'] + "\n\n"
st.download_button(
label="下载文本文件",
data=all_content,
file_name=f"notes_all_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
mime="text/plain"
)
else:
# 导出单个笔记
if st.session_state.notes:
note_titles = [f"{note['id']}. {note['title']}" for note in st.session_state.notes]
selected_note = st.selectbox("选择要导出的笔记", note_titles)
if st.button("📤 导出选中笔记", type="primary", key="export_single_txt"):
# 找到选中的笔记
note_id = int(selected_note.split('.')[0])
note = next((n for n in st.session_state.notes if n['id'] == note_id), None)
if note:
# 格式化笔记内容
note_content = "=" * 50 + "\n"
note_content += f"标题: {note['title']}\n"
note_content += f"分类: {note['category']}\n"
note_content += f"创建时间: {note['created_at']}\n"
if 'updated_at' in note:
note_content += f"更新时间: {note['updated_at']}\n"
note_content += "=" * 50 + "\n\n"
note_content += note['content']
st.download_button(
label="下载文本文件",
data=note_content,
file_name=f"note_{note['id']}_{note['title']}.txt",
mime="text/plain"
)
else:
st.info("没有笔记可导出")
st.divider()
# 清空功能
col_clear = st.columns(1)[0]
with col_clear:
if not st.session_state.confirm_clear:
if st.button("🗑️ 清空所有笔记"):
if st.session_state.notes:
st.session_state.confirm_clear = True
st.rerun()
else:
st.info("没有笔记可清空")
else:
st.warning("⚠️ 确定要清空所有笔记吗?此操作不可恢复!")
col_confirm, col_cancel_clear = st.columns(2)
with col_confirm:
if st.button("✅ 确认清空", type="primary"):
st.session_state.notes = []
st.session_state.editing_note = None
st.session_state.confirm_clear = False
save_notes()
st.success("所有笔记已清空!")
st.rerun()
with col_cancel_clear:
if st.button("❌ 取消"):
st.session_state.confirm_clear = False
st.rerun()
# AI学习报告标签页
with tab_report:
st.header("🤖 AI学习报告")
st.markdown("根据您的笔记生成个性化的学习报告和建议")
# 筛选选项
st.divider()
st.subheader("📊 筛选笔记")
col_report1, col_report2, col_report3 = st.columns(3)
with col_report1:
# 分类筛选
report_category = st.selectbox(
"筛选分类",
["全部"] + st.session_state.categories,
help="选择要分析的分类"
)
with col_report2:
# 标签筛选
all_tags = get_all_tags()
report_tag = st.selectbox(
"筛选标签",
["全部"] + sorted(all_tags),
help="选择要分析的标签"
)
with col_report3:
# 时间范围筛选
report_time_range = st.selectbox(
"时间范围",
["全部时间", "最近7天", "最近30天", "最近90天"],
help="选择分析的时间范围"
)
# 生成报告按钮
st.divider()
if st.button("🚀 生成学习报告", type="primary"):
# 筛选笔记
filtered_notes = []
for note in st.session_state.notes:
# 分类筛选
category_match = report_category == "全部" or note["category"] == report_category
# 标签筛选
tag_match = report_tag == "全部" or report_tag in note.get("tags", [])
# 时间筛选
time_match = True
if report_time_range != "全部时间":
try:
note_date = datetime.strptime(note["created_at"], DATETIME_FORMAT)
now = datetime.now()
if report_time_range == "最近7天":
time_match = (now - note_date).days <= 7
elif report_time_range == "最近30天":
time_match = (now - note_date).days <= 30
elif report_time_range == "最近90天":
time_match = (now - note_date).days <= 90
except Exception as e:
time_match = True
if category_match and tag_match and time_match:
filtered_notes.append(note)
# 生成报告
if filtered_notes:
report = generate_study_report(filtered_notes)
# 显示报告
st.success(f"✅ 基于筛选后的 {len(filtered_notes)} 条笔记生成报告")
# 总览部分
st.divider()
st.subheader("📈 学习总览")
col_overview1, col_overview2, col_overview3 = st.columns(3)
with col_overview1:
st.metric("笔记总数", report["总览"]["笔记总数"])
with col_overview2:
st.metric("总字数", report["总览"]["总字数"])
with col_overview3:
st.metric("平均字数", report["总览"]["平均每条笔记字数"])
# 分类统计
if report["分类统计"]:
st.divider()
st.subheader("📂 分类分布")
cat_cols = st.columns(min(4, len(report["分类统计"])))
for i, (cat, count) in enumerate(report["分类统计"].items()):
if i < len(cat_cols):
with cat_cols[i]:
st.metric(cat, count)
# 热门标签
if report["热门标签"]:
st.divider()
st.subheader("🏷️ 热门标签")
tag_cols = st.columns(min(5, len(report["热门标签"])))
for i, (tag, count) in enumerate(report["热门标签"]):
if i < len(tag_cols):
with tag_cols[i]:
st.metric(tag, count)
# 学习建议
if report["学习建议"]:
st.divider()
st.subheader("💡 AI学习建议")
for i, suggestion in enumerate(report["学习建议"], 1):
st.info(f"{i}. {suggestion}")
# 导出报告
st.divider()
if st.button("📥 导出报告"):
report_text = "=" * 60 + "\n"
report_text += "AI学习报告\n"
report_text += f"生成时间: {report['生成时间']}\n"
report_text += "=" * 60 + "\n\n"
report_text += "【学习总览】\n"
report_text += f"笔记总数: {report['总览']['笔记总数']}\n"
report_text += f"分类数量: {report['总览']['分类数量']}\n"
report_text += f"标签总数: {report['总览']['标签总数']}\n"
report_text += f"总字数: {report['总览']['总字数']}\n"
report_text += f"平均每条笔记字数: {report['总览']['平均每条笔记字数']}\n\n"
report_text += "【分类统计】\n"
for cat, count in report["分类统计"].items():
report_text += f"{cat}: {count}\n"
report_text += "\n"
report_text += "【热门标签】\n"
for tag, count in report["热门标签"]:
report_text += f"{tag}: {count}\n"
report_text += "\n"
report_text += "【AI学习建议】\n"
for i, suggestion in enumerate(report["学习建议"], 1):
report_text += f"{i}. {suggestion}\n"
st.download_button(
label="下载学习报告",
data=report_text,
file_name=f"study_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
mime="text/plain"
)
else:
st.warning("⚠️ 没有找到符合条件的笔记,请调整筛选条件")
else:
st.info("👆 请选择筛选条件后点击生成报告按钮")