From 8899b21aea56e71af691de12e98bbb4097fa9ff9 Mon Sep 17 00:00:00 2001 From: hubian <908234780@qq.com> Date: Sat, 23 May 2026 23:07:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BB=E9=A2=98=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E6=80=9D=E8=B7=AF=E5=8A=9F=E8=83=BD=20-=20=E6=AF=8F=E4=B8=AA?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E5=8F=AF=E8=AE=BE=E5=AE=9A=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E6=80=9D=E8=B7=AF=EF=BC=8CAI=E5=88=86=E6=9E=90=E5=92=8C?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=96=87=E7=AB=A0=E6=97=B6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=BC=95=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 242 ++++++++++++++++++++++++++++++-- templates/index.html | 185 +++++++++++++++++++++++- templates/topic.html | 324 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 737 insertions(+), 14 deletions(-) diff --git a/app.py b/app.py index 9a4a0a8..ce0b91b 100644 --- a/app.py +++ b/app.py @@ -43,6 +43,7 @@ def init_db(): id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT DEFAULT '', + core_idea TEXT DEFAULT '', prompt TEXT DEFAULT '', created_at TEXT DEFAULT (datetime('now','localtime')), updated_at TEXT DEFAULT (datetime('now','localtime')) @@ -53,6 +54,7 @@ def init_db(): type TEXT NOT NULL DEFAULT 'text', content TEXT DEFAULT '', file_path TEXT DEFAULT '', + analysis TEXT DEFAULT '', sort_order INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now','localtime')), FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE @@ -100,6 +102,14 @@ def init_db(): FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE, FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE ); + CREATE TABLE IF NOT EXISTS prompt_templates ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + prompt TEXT NOT NULL DEFAULT '', + is_default INTEGER DEFAULT 0, + created_at TEXT DEFAULT (datetime('now','localtime')), + updated_at TEXT DEFAULT (datetime('now','localtime')) + ); CREATE TABLE IF NOT EXISTS topic_tags ( topic_id TEXT NOT NULL, tag_id TEXT NOT NULL, @@ -108,10 +118,35 @@ def init_db(): FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ); ''') + # 初始化默认Prompt模板 + default_templates = [ + ('通用写作', '请根据以下素材撰写一篇结构清晰、内容丰富的文章:'), + ('深度分析', '请根据以下素材撰写一篇深度分析文章,要求:\n1. 深入挖掘素材背后的逻辑和趋势\n2. 提供独到的观点和见解\n3. 论证充分,数据支撑\n4. 结构严谨,层次分明'), + ('新闻报道', '请根据以下素材撰写一篇新闻报道风格的文章,要求:\n1. 标题醒目,吸引读者\n2. 导语概括核心信息\n3. 正文详实客观\n4. 语言简洁流畅'), + ('技术解读', '请根据以下素材撰写一篇技术解读文章,要求:\n1. 技术概念清晰解释\n2. 结合实际应用场景\n3. 对比分析优劣势\n4. 展望未来发展趋势'), + ('自媒体爆款', '请根据以下素材撰写一篇适合自媒体发布的爆款文章,要求:\n1. 标题吸睛,引发好奇\n2. 开头设置悬念或冲突\n3. 内容有干货,有情绪共鸣\n4. 结尾引导互动,促转发'), + ] + for tname, tprompt in default_templates: + conn.execute('INSERT OR IGNORE INTO prompt_templates (id, name, prompt, is_default) VALUES (?, ?, ?, 1)', + (str(uuid.uuid4())[:8], tname, tprompt)) # 初始化默认配置 conn.execute('INSERT OR IGNORE INTO llm_config (id, url, api_key, model) VALUES (1, ?, ?, ?)', (LLM_URL, LLM_API_KEY, LLM_MODEL)) conn.commit() + # 兼容旧库:如 topics 缺少 core_idea 列则补上 + try: + conn.execute('ALTER TABLE topics ADD COLUMN core_idea TEXT DEFAULT ""') + except Exception: + pass + # 兼容旧库:如 materials 缺少 analysis/selected 列则补上 + try: + conn.execute('ALTER TABLE materials ADD COLUMN analysis TEXT DEFAULT ""') + except Exception: + pass + try: + conn.execute('ALTER TABLE materials ADD COLUMN selected INTEGER DEFAULT 1') + except Exception: + pass conn.close() @@ -199,8 +234,8 @@ def create_topic(): tid = str(uuid.uuid4())[:8] now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') conn.execute( - 'INSERT INTO topics (id, name, description, prompt, created_at, updated_at) VALUES (?,?,?,?,?,?)', - (tid, name, data.get('description', ''), data.get('prompt', ''), now, now) + 'INSERT INTO topics (id, name, description, core_idea, prompt, created_at, updated_at) VALUES (?,?,?,?,?,?,?)', + (tid, name, data.get('description', ''), data.get('core_idea', ''), data.get('prompt', ''), now, now) ) # 关联分类 for cat_id in data.get('category_ids', []): @@ -234,7 +269,7 @@ def update_topic(topic_id): now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') fields = [] values = [] - for key in ['name', 'description', 'prompt']: + for key in ['name', 'description', 'core_idea', 'prompt']: if key in data: fields.append(f'{key}=?') values.append(data[key]) @@ -295,6 +330,69 @@ def delete_topic(topic_id): return jsonify({'ok': True}) +# ==================== Prompt模板API ==================== + +@app.route('/api/prompt-templates', methods=['GET']) +def list_prompt_templates(): + conn = get_db() + rows = conn.execute('SELECT * FROM prompt_templates ORDER BY is_default DESC, name').fetchall() + conn.close() + return jsonify([dict(r) for r in rows]) + + +@app.route('/api/prompt-templates', methods=['POST']) +def create_prompt_template(): + data = request.get_json(force=True) + name = data.get('name', '').strip() + prompt = data.get('prompt', '').strip() + if not name: + return jsonify({'error': '模板名称不能为空'}), 400 + if not prompt: + return jsonify({'error': '模板内容不能为空'}), 400 + conn = get_db() + tid = str(uuid.uuid4())[:8] + now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + conn.execute('INSERT INTO prompt_templates (id, name, prompt, is_default, created_at, updated_at) VALUES (?,?,?,?,?,?)', + (tid, name, prompt, 0, now, now)) + conn.commit() + tmpl = conn.execute('SELECT * FROM prompt_templates WHERE id=?', (tid,)).fetchone() + conn.close() + return jsonify(dict(tmpl)) + + +@app.route('/api/prompt-templates/', methods=['PUT']) +def update_prompt_template(template_id): + data = request.get_json(force=True) + conn = get_db() + now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + fields = [] + values = [] + for key in ['name', 'prompt']: + if key in data: + fields.append(f'{key}=?') + values.append(data[key]) + if fields: + fields.append('updated_at=?') + values.append(now) + values.append(template_id) + conn.execute(f'UPDATE prompt_templates SET {",".join(fields)} WHERE id=?', values) + conn.commit() + tmpl = conn.execute('SELECT * FROM prompt_templates WHERE id=?', (template_id,)).fetchone() + conn.close() + if tmpl: + return jsonify(dict(tmpl)) + return jsonify({'error': '未找到'}), 404 + + +@app.route('/api/prompt-templates/', methods=['DELETE']) +def delete_prompt_template(template_id): + conn = get_db() + conn.execute('DELETE FROM prompt_templates WHERE id=?', (template_id,)) + conn.commit() + conn.close() + return jsonify({'ok': True}) + + # ==================== 分类API ==================== @app.route('/api/categories', methods=['GET']) @@ -345,7 +443,9 @@ def update_category(category_id): conn.commit() cat = conn.execute('SELECT * FROM categories WHERE id=?', (category_id,)).fetchone() conn.close() - return jsonify(dict(cat)) if cat else jsonify({'error': '未找到'}), 404 + if cat: + return jsonify(dict(cat)) + return jsonify({'error': '未找到'}), 404 @app.route('/api/categories/', methods=['DELETE']) @@ -473,7 +573,7 @@ def update_material(material_id): conn = get_db() fields = [] values = [] - for key in ['content', 'sort_order']: + for key in ['content', 'sort_order', 'analysis', 'selected']: if key in data: fields.append(f'{key}=?') values.append(data[key]) @@ -483,7 +583,9 @@ def update_material(material_id): conn.commit() material = conn.execute('SELECT * FROM materials WHERE id=?', (material_id,)).fetchone() conn.close() - return jsonify(dict(material)) if material else jsonify({'error': '未找到'}), 404 + if material: + return jsonify(dict(material)) + return jsonify({'error': '未找到'}), 404 @app.route('/api/materials/', methods=['DELETE']) @@ -500,6 +602,128 @@ def delete_material(material_id): return jsonify({'ok': True}) +@app.route('/api/topics//materials/select', methods=['POST']) +def batch_update_material_selection(topic_id): + """批量更新素材选中状态""" + data = request.get_json(force=True) + conn = get_db() + now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # data 格式: {"materials": [{"id": "xxx", "selected": 1/0}, ...]} + # 或: {"select_all": true/false} + if 'select_all' in data: + selected = 1 if data['select_all'] else 0 + conn.execute('UPDATE materials SET selected=? WHERE topic_id=?', (selected, topic_id)) + elif 'materials' in data: + for item in data['materials']: + conn.execute('UPDATE materials SET selected=? WHERE id=? AND topic_id=?', + (1 if item.get('selected') else 0, item['id'], topic_id)) + conn.execute('UPDATE topics SET updated_at=? WHERE id=?', (now, topic_id)) + conn.commit() + conn.close() + return jsonify({'ok': True}) + + +@app.route('/api/materials//analyze', methods=['POST']) +def analyze_material(material_id): + """大模型分析单个素材,返回分析结果并保存""" + conn = get_db() + mat = conn.execute('SELECT * FROM materials WHERE id=?', (material_id,)).fetchone() + if not mat: + conn.close() + return jsonify({'error': '素材不存在'}), 404 + + mat_dict = dict(mat) + # 获取主题核心思路 + topic = conn.execute('SELECT core_idea FROM topics WHERE id=?', (mat_dict['topic_id'],)).fetchone() + core_idea = dict(topic)['core_idea'] if topic else '' + conn.close() + + llm_config = get_llm_config() + if not llm_config['api_key']: + return jsonify({'error': '请先配置LLM API Key'}), 400 + + # 构建核心思路引导语 + core_idea_hint = '' + if core_idea: + core_idea_hint = f'\n\n=== 主题核心思路 ===\n{core_idea}\n请围绕上述核心思路进行分析,确保分析结果与文章整体方向一致。' + + # 构建分析prompt + if mat_dict['type'] == 'text': + user_content = f"""请对以下文本素材进行深度分析,要求: +1. 概括核心观点和关键信息 +2. 分析文本的逻辑结构和论证方式 +3. 识别潜在的写作价值和可延伸方向 +4. 指出可能的不足或需要补充的信息 +{core_idea_hint} + +=== 文本素材 === +{mat_dict['content']} + +请直接给出分析结果,不要输出思考过程。""" + else: + # 图片素材:发送图片给多模态模型 + note = mat_dict['content'] or '无备注' + img_path = os.path.join(BASE_DIR, 'static', mat_dict['file_path']) if mat_dict['file_path'] else None + + # 尝试读取图片并编码为base64 + image_content = None + if img_path and os.path.exists(img_path): + import base64 + with open(img_path, 'rb') as f: + img_data = base64.b64encode(f.read()).decode('utf-8') + ext = mat_dict['file_path'].rsplit('.', 1)[-1].lower() + mime_map = {'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', + 'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml', 'bmp': 'image/bmp'} + mime = mime_map.get(ext, 'image/png') + image_content = f'data:{mime};base64,{img_data}' + + if image_content: + user_content = [ + {'type': 'text', 'text': f"""请对以下图片素材进行深度分析,要求: +1. 详细描述图片内容(场景、人物、物体、文字等) +2. 分析图片的核心信息和表达意图 +3. 识别图片在文章写作中的潜在用途 +4. 如有备注,结合备注分析:{note} +{core_idea_hint} + +请直接给出分析结果,不要输出思考过程。"""}, + {'type': 'image_url', 'image_url': {'url': image_content}} + ] + else: + user_content = f"""请对以下图片素材备注进行分析(图片文件不可读,仅基于备注): +备注:{note} +{core_idea_hint} + +请尝试基于备注信息给出分析,并建议补充更多图片细节。""" + + try: + req_body = { + 'model': llm_config['model'], + 'messages': [{'role': 'user', 'content': user_content}], + 'temperature': 0.5, + 'max_tokens': 2048 + } + resp = requests.post( + llm_config['url'], + headers={'Authorization': f"Bearer {llm_config['api_key']}", 'Content-Type': 'application/json'}, + json=req_body, + timeout=60 + ) + resp.raise_for_status() + result = resp.json() + analysis_text = result['choices'][0]['message']['content'] + except Exception as e: + return jsonify({'error': f'分析失败: {str(e)}'}), 500 + + # 保存分析结果 + conn = get_db() + conn.execute('UPDATE materials SET analysis=? WHERE id=?', (analysis_text, material_id)) + conn.commit() + conn.close() + + return jsonify({'analysis': analysis_text}) + + # ==================== LLM配置API ==================== @app.route('/api/llm-config', methods=['GET']) @@ -563,7 +787,7 @@ def generate_article(topic_id): return jsonify({'error': '主题不存在'}), 404 materials = conn.execute( - 'SELECT * FROM materials WHERE topic_id=? ORDER BY sort_order, created_at', + 'SELECT * FROM materials WHERE topic_id=? AND selected=1 ORDER BY sort_order, created_at', (topic_id,) ).fetchall() conn.close() @@ -595,6 +819,7 @@ def generate_article(topic_id): 主题:{topic['name']} {f'主题说明:{topic["description"]}' if topic['description'] else ''} +{f'核心思路:{topic["core_idea"]}' if topic['core_idea'] else ''} === 素材 === {materials_text} @@ -603,7 +828,8 @@ def generate_article(topic_id): 1. 充分利用提供的素材 2. 文章结构完整,层次分明 3. 语言流畅,逻辑清晰 -4. 不要输出思考过程,直接给出文章内容""" +4. 紧扣核心思路进行创作,保持文章方向与风格一致 +5. 不要输出思考过程,直接给出文章内容""" # 调用大模型 try: diff --git a/templates/index.html b/templates/index.html index 8d1fe26..661429d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -73,6 +73,9 @@ 未配置 + @@ -193,6 +196,11 @@ +
+ + +
简洁概括核心思路与风格要求,AI分析和生成文章时会参考此内容
+
@@ -214,7 +222,14 @@
- + +
+ +
选择模板自动填充,也可手动编辑
+
@@ -310,12 +325,44 @@ + + +