feat: 碎片信息记录网站初始版本
功能: - 实时保存记录到本地 - 大模型自动生成标题 - 左侧列表显示所有记录 - 搜索功能 - 新建/编辑/删除记录
This commit is contained in:
236
app.py
Normal file
236
app.py
Normal file
@@ -0,0 +1,236 @@
|
||||
"""
|
||||
碎片信息记录网站
|
||||
- 实时保存到本地
|
||||
- 大模型生成标题
|
||||
- 搜索功能
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, jsonify, request
|
||||
from flask_cors import CORS
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import requests
|
||||
import threading
|
||||
|
||||
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
||||
CORS(app)
|
||||
|
||||
# 数据目录
|
||||
DATA_DIR = Path(__file__).parent / 'data'
|
||||
DATA_DIR.mkdir(exist_ok=True)
|
||||
NOTES_FILE = DATA_DIR / 'notes.json'
|
||||
|
||||
# 大模型配置
|
||||
LLM_CONFIG = {
|
||||
'base_url': 'http://192.168.2.5:1234/v1',
|
||||
'api_key': 'sk-lm-fuP5tGU8:Hi7YU87jHyDP6Ay8Tl2j',
|
||||
'model': 'qwen3.5-4b',
|
||||
}
|
||||
|
||||
def load_notes():
|
||||
"""加载所有笔记"""
|
||||
if NOTES_FILE.exists():
|
||||
return json.loads(NOTES_FILE.read_text(encoding='utf-8'))
|
||||
return []
|
||||
|
||||
def save_notes(notes):
|
||||
"""保存笔记"""
|
||||
NOTES_FILE.write_text(json.dumps(notes, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
|
||||
def generate_title(content):
|
||||
"""用大模型生成标题"""
|
||||
if not content or len(content.strip()) < 10:
|
||||
return content[:20] if content else "新记录"
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{LLM_CONFIG['base_url']}/chat/completions",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {LLM_CONFIG['api_key']}"
|
||||
},
|
||||
json={
|
||||
"model": LLM_CONFIG['model'],
|
||||
"messages": [
|
||||
{"role": "system", "content": "你是一个标题生成助手。请根据用户的内容,生成一个简洁的标题(不超过15个字)。只返回标题,不要其他内容。"},
|
||||
{"role": "user", "content": content[:500]}
|
||||
],
|
||||
"max_tokens": 50,
|
||||
"temperature": 0.3
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
title = data['choices'][0]['message']['content'].strip()
|
||||
# 清理可能的引号或多余字符
|
||||
title = title.replace('"', '').replace("'", '').strip()
|
||||
if len(title) > 20:
|
||||
title = title[:20]
|
||||
return title
|
||||
except Exception as e:
|
||||
print(f"生成标题失败: {e}")
|
||||
|
||||
# 降级:取前20个字
|
||||
return content[:20].strip()
|
||||
|
||||
def generate_title_async(note_id, content):
|
||||
"""异步生成标题"""
|
||||
title = generate_title(content)
|
||||
|
||||
# 更新笔记标题
|
||||
notes = load_notes()
|
||||
for note in notes:
|
||||
if note['id'] == note_id:
|
||||
note['title'] = title
|
||||
break
|
||||
save_notes(notes)
|
||||
|
||||
# ============ 页面路由 ============
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
# ============ API路由 ============
|
||||
|
||||
@app.route('/api/notes')
|
||||
def api_notes():
|
||||
"""获取笔记列表"""
|
||||
keyword = request.args.get('q', '').strip().lower()
|
||||
notes = load_notes()
|
||||
|
||||
# 搜索过滤
|
||||
if keyword:
|
||||
notes = [n for n in notes if keyword in n.get('title', '').lower() or keyword in n.get('content', '').lower()]
|
||||
|
||||
# 按更新时间排序(最近在前)
|
||||
notes = sorted(notes, key=lambda x: x.get('updated_at', ''), reverse=True)
|
||||
|
||||
# 返回列表信息
|
||||
return jsonify([{
|
||||
'id': n['id'],
|
||||
'title': n['title'],
|
||||
'updated_at': n['updated_at'],
|
||||
'created_at': n['created_at'],
|
||||
'preview': n['content'][:50] if n['content'] else '',
|
||||
} for n in notes])
|
||||
|
||||
@app.route('/api/notes/<note_id>')
|
||||
def api_note_detail(note_id):
|
||||
"""获取单个笔记详情"""
|
||||
notes = load_notes()
|
||||
note = next((n for n in notes if n['id'] == note_id), None)
|
||||
|
||||
if not note:
|
||||
return jsonify({'error': 'Note not found'}), 404
|
||||
|
||||
return jsonify(note)
|
||||
|
||||
@app.route('/api/notes', methods=['POST'])
|
||||
def api_create_note():
|
||||
"""创建新笔记"""
|
||||
notes = load_notes()
|
||||
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
new_note = {
|
||||
'id': uuid.uuid4().hex[:12],
|
||||
'title': '新记录',
|
||||
'content': '',
|
||||
'created_at': now,
|
||||
'updated_at': now,
|
||||
}
|
||||
|
||||
notes.append(new_note)
|
||||
save_notes(notes)
|
||||
|
||||
return jsonify(new_note)
|
||||
|
||||
@app.route('/api/notes/<note_id>', methods=['PUT'])
|
||||
def api_update_note(note_id):
|
||||
"""更新笔记内容"""
|
||||
data = request.get_json()
|
||||
content = data.get('content', '')
|
||||
|
||||
notes = load_notes()
|
||||
note = next((n for n in notes if n['id'] == note_id), None)
|
||||
|
||||
if not note:
|
||||
return jsonify({'error': 'Note not found'}), 404
|
||||
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 判断是否需要生成新标题
|
||||
old_content = note.get('content', '')
|
||||
need_new_title = (not note['title'] or note['title'] == '新记录' or
|
||||
len(old_content) < 50 and len(content) >= 50)
|
||||
|
||||
note['content'] = content
|
||||
note['updated_at'] = now
|
||||
|
||||
# 如果内容变化较大,异步生成新标题
|
||||
if need_new_title and len(content) >= 20:
|
||||
threading.Thread(target=generate_title_async, args=(note_id, content)).start()
|
||||
|
||||
save_notes(notes)
|
||||
|
||||
return jsonify(note)
|
||||
|
||||
@app.route('/api/notes/<note_id>/title', methods=['POST'])
|
||||
def api_regenerate_title(note_id):
|
||||
"""手动重新生成标题"""
|
||||
notes = load_notes()
|
||||
note = next((n for n in notes if n['id'] == note_id), None)
|
||||
|
||||
if not note:
|
||||
return jsonify({'error': 'Note not found'}), 404
|
||||
|
||||
title = generate_title(note['content'])
|
||||
note['title'] = title
|
||||
note['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
save_notes(notes)
|
||||
|
||||
return jsonify({'success': True, 'title': title})
|
||||
|
||||
@app.route('/api/notes/<note_id>', methods=['DELETE'])
|
||||
def api_delete_note(note_id):
|
||||
"""删除笔记"""
|
||||
notes = load_notes()
|
||||
notes = [n for n in notes if n['id'] != note_id]
|
||||
save_notes(notes)
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@app.route('/api/search')
|
||||
def api_search():
|
||||
"""搜索笔记"""
|
||||
keyword = request.args.get('q', '').strip().lower()
|
||||
|
||||
if not keyword:
|
||||
return jsonify([])
|
||||
|
||||
notes = load_notes()
|
||||
results = [n for n in notes if keyword in n.get('title', '').lower() or keyword in n.get('content', '').lower()]
|
||||
|
||||
results = sorted(results, key=lambda x: x.get('updated_at', ''), reverse=True)
|
||||
|
||||
return jsonify([{
|
||||
'id': n['id'],
|
||||
'title': n['title'],
|
||||
'updated_at': n['updated_at'],
|
||||
'preview': n['content'][:100] if n['content'] else '',
|
||||
} for n in results])
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("=" * 50)
|
||||
print("碎片信息记录网站")
|
||||
print("=" * 50)
|
||||
print(f"访问地址: http://localhost:19009")
|
||||
print("=" * 50)
|
||||
|
||||
app.run(host='0.0.0.0', port=19009, debug=True)
|
||||
Reference in New Issue
Block a user