2026-02-07 00:17:23 +08:00
import os
import json
2026-02-07 00:50:16 +08:00
import requests
2026-02-07 00:17:23 +08:00
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 '''
2026-02-07 00:50:16 +08:00
< div class = " result-container " data - output - value = " {image_url} " data - output - type = " image " >
2026-02-07 00:17:23 +08:00
< img src = " {image_url} " class = " generated-art animated fadeIn " >
< div class = " meta-info " > ✅ 上传成功 : { file . filename } < / div >
< input type = " hidden " name = " uploaded_url " value = " {image_url} " >
< / div >
'''
@app.route ( ' /api/run_node/<node_id> ' , 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 '''
2026-02-07 00:50:16 +08:00
< div class = " result-container " data - output - value = " {result["url"]} " data - output - type = " image " >
2026-02-07 00:17:23 +08:00
< img src = " {result["url"]} " class = " generated-art animated fadeIn " >
< div class = " meta-info " > 处理完成 | 耗时 : { result [ " time " ] } s < / div >
< / div >
'''
2026-02-07 00:50:16 +08:00
elif result . get ( ' type ' ) == ' text ' :
return f '''
< div class = " result-container " data - output - value = ' {result["content"]} ' data - output - type = " text " >
< pre class = " text-output animated fadeIn " > { result [ " content " ] } < / pre >
< div class = " meta-info " > 处理完成 | 耗时 : { result [ " time " ] } s < / div >
< / div >
'''
2026-02-07 00:17:23 +08:00
else :
error_msg = result . get ( ' error ' , ' 未知错误 ' )
2026-02-07 00:50:16 +08:00
return f ' <div class= " error-badge " data-error= " { error_msg } " >❌ 错误: { error_msg } </div> '
2026-02-07 00:17:23 +08:00
2026-02-07 00:50:16 +08:00
@app.route ( ' /api/chat ' , methods = [ ' POST ' ] )
2026-02-07 00:17:23 +08:00
@app.route ( ' /api/chat ' , methods = [ ' POST ' ] )
def chat ( ) :
2026-02-07 00:50:16 +08:00
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 ' , ' <br> ' )
yield formatted_content
except :
pass
except Exception as e :
yield f " [Error: { str ( e ) } ] "
return app . response_class ( generate ( ) , mimetype = ' text/html ' )
2026-02-07 00:17:23 +08:00
if __name__ == ' __main__ ' :
app . run ( debug = True , port = 5000 )