import os import json import requests from flask import Flask, render_template, request, jsonify, send_from_directory from node_engine import NodeEngine from database import init_db from config import Config app = Flask(__name__) app.config.from_object(Config) # Ensure directories exist os.makedirs('configs/nodes', exist_ok=True) os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) os.makedirs('static/css', exist_ok=True) os.makedirs('static/js', exist_ok=True) # Initialize engine and DB engine = NodeEngine() init_db() @app.route('/') def index(): # Reload engine to pick up any manual JSON changes nodes_config = engine.get_all_node_configs() return render_template('board.html', node_configs=nodes_config) @app.route('/api/nodes/configs') def get_node_configs(): return jsonify(engine.get_all_node_configs()) @app.route('/api/create_node', methods=['POST']) def create_node(): node_type_id = request.args.get('type') x = int(request.args.get('x', 100)) y = int(request.args.get('y', 100)) # Get config configs = engine.get_all_node_configs() node_data = next((c for c in configs if c['id'] == node_type_id), None) if not node_data: return "Node type not found", 404 instance_id = f"inst_{node_type_id}_{os.urandom(2).hex()}" return render_template('partials/node.html', node_data=node_data, node_instance_id=instance_id, x=x, y=y) @app.route('/api/upload_asset', methods=['POST']) def upload_asset(): if 'file' not in request.files: return "No file", 400 file = request.files['file'] if file.filename == '': return "No selected file", 400 filename = f"upload_{os.urandom(4).hex()}_{file.filename}" upload_dir = os.path.join(app.root_path, 'static', 'uploads') os.makedirs(upload_dir, exist_ok=True) filepath = os.path.join(upload_dir, filename) file.save(filepath) print(f"File saved to: {filepath}") # URL for access via browser image_url = f"/static/uploads/{filename}" return f'''
✅ 上传成功: {file.filename}
''' @app.route('/api/run_node/', methods=['POST']) def run_node(node_id): data = request.form.to_dict() result = engine.execute_node(node_id, data) if result.get('type') == 'image': return f'''
处理完成 | 耗时: {result["time"]}s
''' elif result.get('type') == 'text': return f'''
{result["content"]}
处理完成 | 耗时: {result["time"]}s
''' else: error_msg = result.get('error', '未知错误') return f'
❌ 错误: {error_msg}
' @app.route('/api/chat', methods=['POST']) @app.route('/api/chat', methods=['POST']) def chat(): user_message = request.form.get('message', '') def generate(): # Do not yield user message here; frontend handles it. # Use proxy + baseurl + endpoint concatenation target_url = f"{Config.BASE_URL.rstrip('/')}/v1/chat/completions" url = f"{Config.PROXY}{target_url}" headers = { "Authorization": f"Bearer {Config.API_KEY}", "Content-Type": "application/json" } payload = { "model": "gemini-3-flash-preview", "messages": [ {"role": "system", "content": "You are a professional Design Assistant (设计助理) for NoirFlow. Your goal is to help users maintain a consistent, premium, and aesthetic design language in their workflows. You provide advice on color theory, layout, and visual harmony, while also assisting with the technical node configuration when asked. Keep your tone professional, artistic, and concise."}, {"role": "user", "content": user_message} ], "stream": True } try: with requests.post(url, headers=headers, json=payload, stream=True, timeout=60) as r: r.raise_for_status() for line in r.iter_lines(): if line: decoded_line = line.decode('utf-8') if decoded_line.startswith('data: '): json_str = decoded_line[6:] if json_str == '[DONE]': break try: data = json.loads(json_str) content = data['choices'][0]['delta'].get('content', '') if content: # Basic formatting formatted_content = content.replace('\n', '
') yield formatted_content except: pass except Exception as e: yield f"[Error: {str(e)}]" return app.response_class(generate(), mimetype='text/html') if __name__ == '__main__': app.run(debug=True, port=5000)