
    REj]&                       d Z ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlZddlZddlmZmZ ddlmZ dZdddd	d
ZddiZdddddddddZi ada ej                    Ze
j                            e
j                            e                     Z!e
j        "                    e!d          Z# e
j$        e#d           dZ%dZ&e
j        "                    e#d          Z'i a( ej                    Z)d Z*d Z+ e+             e	j        ,                    de!           	 ddl-m.Z. n# e/$ r d Z.Y nw xY wd e0d!e0d"e0d#e0fd$Z1d%e0fd&Z2d%e0fd'Z3d( Z4d e0d#e0fd)Z5d e0d#e6fd*Z7d e0d+e6fd,Z8d e0d-e9d#e9fd.Z:dZd e0d1e0d2e;d3e;d#e9f
d4Z<d e0d5e0d#e9fd6Z=d e0d5e0d#e>fd7Z?d[d:Z@d; ZA G d< d=ejB        jC                  ZD G d> d?eejB        jE                  ZFeGd@k    r[ e
jH        e!            eIdA            eIdB            eIdCe             eIdD e
jJ                                 eIdE            eIdA            ej        ejK        ejL                  ZM	 eMN                    dFef           eMO                                 n# eP$ r  eIdGe dH           e	jQ        dIk    rddlRZR eRjS        dJe dddK          ZTeTjU        V                                W                    dL          D ]ZXeXV                                W                                ZY eZeY          dMk    rJeYdN         [                                r0 eRjS        dOeYdN          ddP            eIdQeYdN                      ej\        d/           Y nw xY w eFdFefeD          ZB eIdRe             eIdS e6e]                                                       eIdTe dU            eIdVe dW           	 eB^                                 dS # e_$ r.  eIdX           eB`                                  eIdY           Y dS w xY wdS )\u   
AI-Ad-Agent API Proxy Server
解决局域网浏览器访问时的 CORS 跨域问题
所有前端请求通过此代理转发到后端服务

v2.0 新增:
- 用户认证 (Excel 数据库)
- 内容记录 CRUD API
- 按用户隔离数据
    N)datetime	timedelta)ThreadingMixIni(#  zhttp://127.0.0.1:11434http://127.0.0.1:8188zhttp://127.0.0.1:8080zhttp://127.0.0.1:9880)z/api/ollama/z/api/comfyui/z/api/iopaint/z/api/gptsovits//api/digital-humanzhttp://127.0.0.1:8766)zhttp://127.0.0.1:11434/api/tagsGET   )z"http://127.0.0.1:8188/system_statsr      )zhttp://127.0.0.1:8080/HEADr	   )zhttp://127.0.0.1:9880/docsr      )z1http://127.0.0.1:8766/api/digital-human/v2/statusr   r   )zhttp://127.0.0.1:8383/r      )zhttp://127.0.0.1:18180/r   r   )zhttp://127.0.0.1:10095/r   r   )OllamaComfyUIIOPaintz
GPT-SoVITSu   数字人v2u   Duix视频合成zDuix.TTSzDuix.ASRdataTexist_okzhengfangyuan_ad_agent_2026   ztokens.jsonc                     	 t           5  i } t                                          D ]9\  }}|d         |d         |d         |d                                         d| |<   :	 ddd           n# 1 swxY w Y   t	          t
          dd	          5 }t          j        | |d
           ddd           dS # 1 swxY w Y   dS # t          $ r Y dS w xY w)u"   将当前 token 持久化到文件usernamedisplay_namerole
expires_atr   r   r   r   Nwutf-8encodingFensure_ascii)	_tokens_lock_active_tokensitems	isoformatopenTOKENS_FILEjsondump	Exception)r   tinfofs       %D:\AI-Ad-Agent\portal\proxy_server.py_save_tokens_to_filer/   F   sy    		 		D)//11  4 $Z 0$($8 L"&|"4">">"@"@	 Q		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 +sW555 	3IdAE2222	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3   sY   C  AA, C  ,A00C  3A04C  B3&C  3B77C  :B7;C   
CCc                     	 t           j                            t                    sdS t	          t          dd          5 } t          j        |           }ddd           n# 1 swxY w Y   t          j                    }i }|	                                D ]B\  }}t          j
        |d                   }||k    r&|d         |d         |d         |d	||<   Ct          5  |addd           n# 1 swxY w Y   |r"t          d
t          |           d           dS dS # t          $ r}t          d|            Y d}~dS d}~ww xY w)u?   从文件恢复 token（服务器重启后恢复登录状态）Nrr   r   r   r   r   r   r   u   [Auth] 已从文件恢复 u    个有效 tokenu   [Auth] 恢复 token 失败: )ospathexistsr'   r&   r(   loadr   nowr$   fromisoformatr"   r#   printlenr*   )r-   r   r6   restoredr+   r,   r   es           r.   _load_tokens_from_filer<   Y   s   2w~~k** 	F+sW555 	 9Q<<D	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 lnnzz|| 		 		GAt!/\0BCCJZ , $^ 4V(	 HQKK  	& 	&%N	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	PNs8}}NNNOOOOO	P 	P 2 2 20Q001111111112sd   $D D A D  A$$D 'A$(A6D C-!D -C11D 4C15%D 
E(E  E)verify_userc                     d S N )r   passwords     r.   r=   r=   }   s    t    r   r   r   returnc                    |  dt          j                    j         dt          j                     }t	          j        t                                          |                                t          j	                  
                                dd         }| d| }t          5  | ||t          j                    t          t                    z   dt           |<   ddd           n# 1 swxY w Y   t#                       t%                       |S )u   生成 session token:N    .)hoursr   )uuiduuid4hextimehmacnewTOKEN_SECRETencodehashlibsha256	hexdigestr"   r   r6   r   TOKEN_EXPIRE_HOURSr#   _clean_expired_tokensr/   )r   r   r   rawsigtokens         r.   generate_tokenrY      s*   
8
8
(
8
849;;
8
8C
(<&&((#**,,
G
G
Q
Q
S
STWUWTW
XCNNSNNE	 
 
 (",..9;M+N+N+NN	!
 !
u
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 Ls   #7C&&C*-C*rX   c                 6   | sdS t           5  t                              |           }|s	 ddd           dS t          j                    |d         k    rt          | = 	 ddd           dS |d         |d         |d         dcddd           S # 1 swxY w Y   dS )u)   验证 token，返回用户信息或 NoneNr   r   r   r   )r   r   r   )r"   r#   getr   r6   )rX   r,   s     r.   validate_tokenr\      s-    t	 
 
!!%(( 	
 
 
 
 
 
 
 
 <>>D...u%
 
 
 
 
 
 
 
 Z( 0L
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s   B&B*BBBc                     t           5  t                              | d           ddd           n# 1 swxY w Y   t                       dS )u   使 token 失效N)r"   r#   popr/   )rX   s    r.   invalidate_tokenr_      s    	 ( (5$'''( ( ( ( ( ( ( ( ( ( ( ( ( ( (s   044c                      t          j                    fdt                                          D             } | r| D ]
}t          |= t	                       dS dS )u   清理过期 tokenc                 2    g | ]\  }}|d          k    |S )r   r@   ).0r+   ir6   s      r.   
<listcomp>z)_clean_expired_tokens.<locals>.<listcomp>   s+    MMMTQsQ|_7L7Lq7L7L7LrB   N)r   r6   r#   r$   r/   )expiredr+   r6   s     @r.   rU   rU      sq    
,..CMMMM^1133MMMG  	" 	"Aq!! rB   c                 R    t           j                            t          |  d          S )u   获取用户记录文件路径z_records.json)r2   r3   joinDATA_DIR)r   s    r.   get_records_fileri      s!    7<<X"<"<"<===rB   c                     t          |           }t          j                            |          sg S 	 t	          |dd          5 }t          j        |          cddd           S # 1 swxY w Y   dS # t
          j        t          f$ r g cY S w xY w)u   加载用户记录r1   r   r   N)	ri   r2   r3   r4   r&   r(   r5   JSONDecodeErrorIOError)r   fpathr-   s      r.   load_recordsrn      s    X&&E7>>%   	%w/// 	 19Q<<	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  '*   			s5   A2 A%A2 %A))A2 ,A)-A2 2BBrecordsc                     t          |           }t          |dd          5 }t          j        ||dd           ddd           dS # 1 swxY w Y   dS )u   保存用户记录r   r   r   Fr	   )r!   indentN)ri   r&   r(   r)   )r   ro   rm   r-   s       r.   save_recordsrr      s    X&&E	eS7	+	+	+ <q	'15;;;;< < < < < < < < < < < < < < < < < <s   AAArecordc                 L   t          j                    j        dd         |d<   | |d<   t          j                                        d          |d<   t          |           }|                    d|           t          |          dk    r
|dd         }t          | |           |S )	u   添加一条记录N   idr   z%Y-%m-%d %H:%M:%S
created_atr     )
rI   rJ   rK   r   r6   strftimern   insertr9   rr   )r   rs   ro   s      r.   
add_recordr{      s    :<<#CRC(F4L!F:#<>>223FGGF<8$$GNN1f
7||c$3$-7###MrB         record_typepage	page_sizec           	          t          |           }fd|D             }t          |          }|dz
  |z  }||z   }|||         |||t          d||z   dz
  |z            dS )u-   获取指定类型的记录列表（分页）c                 F    g | ]}|                     d           k    |S )typer[   )rb   r1   r~   s     r.   rd   z'get_records_by_type.<locals>.<listcomp>   s-    CCCaaeeFmm{&B&B&B&B&BrB   r|   )ro   totalr   r   total_pages)rn   r9   max)	r   r~   r   r   ro   filteredr   startends	    `       r.   get_records_by_typer      s    8$$GCCCC7CCCHMMEAX"E
)
CE#I&1uy014BCC  rB   	record_idc                 h    t          |           }|D ]}|                    d          |k    r|c S  dS )u   获取单条记录rv   N)rn   r[   )r   r   ro   r1   s       r.   get_record_by_idr      sF    8$$G  55;;)##HHH $4rB   c                     t          |           }fd|D             }t          |          t          |          k    rdS t          | |           dS )u   删除记录c                 F    g | ]}|                     d           k    |S )rv   r   )rb   r1   r   s     r.   rd   z!delete_record.<locals>.<listcomp>   s-    BBBt	)A)A1)A)A)ArB   FT)rn   r9   rr   )r   r   ro   new_recordss    `  r.   delete_recordr      s\    8$$GBBBBgBBBK
;3w<<''u;'''4rB   r   r	   c                    	 |dk    r t           j                            |          }nI|dk    r"t           j                            |d          }n!t           j                            ||          }t           j                            ||          5 }ddl}t          d|  d|j         |j        	           |j        d
k     cddd           S # 1 swxY w Y   dS # t           j        j	        $ rD}ddl}|j
        d
k     }t          d|  d|rdnd d|j
         d|j        	           |cY d}~S d}~wt           j        j        $ r7}	ddl}ddlm}
  |
|          }|j        pd}|j        p|j        dk    rdnd}	 ddl} |j        |j        |j                  }|                    |           |                    ||f          }|                                 |dk    r&t          d|  d| d| d|j        	           Y d}	~	dS t          d|  d| d| |j        	           Y d}	~	dS # t.          $ r@}t          d|  dt1          |          j         d| |j        	           Y d}~Y d}	~	dS d}~ww xY wd}	~	wt.          $ rj}ddl}ddlm}
  |
|          }|j        pd}|j        p|j        dk    rdnd}	 ddl} |j        |j        |j                  }|                    |           |                    ||f          }|                                 |dk    r5t          d|  dt1          |          j         d|j        	           Y d}~dS t          d|  dt1          |          j         d| |j        	           Y d}~dS # t.          $ rR}t          d|  dt1          |          j         dt1          |          j         |j        	           Y d}~Y d}~dS d}~ww xY wd}~ww xY w)u$   检查单个后端服务健康状态r   r   )methodtimeoutr   Nz	[HEALTH] z OK: )filerx    OKFAILz (HTTP ))urlparsez	127.0.0.1httpsi  P   z OK (TCP connect to rE   Tz FAIL: TCP connect failed to Fz FAIL: z: z OK (TCP fallback after z + )urllibrequestRequesturlopensysr8   statusstderrerror	HTTPErrorcodeURLErrorurllib.parser   hostnameportschemesocketAF_INETSOCK_STREAM
settimeout
connect_excloser*   r   __name__)nameurlr   r   reqrespr   heokuer   parsedhostr   r   sockresulte2r;   s                      r.   check_backend_healthr      s"   <U??.((--CCv.((V(<<CC.((V(<<C^##C#99 	%TJJJ6d6666SZHHHH;$	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% <!   


Ws]J$JJ!7JJJJJQTQ[\\\\						<    


))))))#-+{Gfmw&>&>ssB	MMM 6=1CDDDOOG$$$__dD\22FJJLLL{{J$JJDJJ4JJJQTQ[\\\\tttttR$RRTRRDRRY\Ycdddduuuuu 	 	 	DdDD488+<DDDD3:VVVV555555555	    


))))))#-+{Gfmw&>&>ssB	MMM 6=1CDDDOOG$$$__dD\22FJJLLL{{S$SSQ@PSSSZ]ZdeeeetttttF$FFtAww/?FF1FFSZXXXXuuuuu 	 	 	SdSS477+;SSR@QSSZ]Zdeeee555555555	's   BC 0CC CC CC O109D/)O1/O14I9<BH,H,,
I661I1'I91I66I99O14O,<BN1N
O)AO$O,$O))O,,O1c                  H   t          j                     } t          5  | t          z
  dk     r%t                                          cddd           S 	 ddd           n# 1 swxY w Y   i }ddlm}m}  |d          5 fdt          	                                D             } ||          D ]7}||         }	 |
                                ||<   ## t          $ r d||<   Y 4w xY w	 ddd           n# 1 swxY w Y   t          5  |a| addd           n# 1 swxY w Y   |S )	uB   获取所有后端服务健康状态（并发检查，带缓存）r   Nr   )ThreadPoolExecutoras_completedr   )max_workersc           	      \    i | ](\  }\  }}}                     t          ||||          |)S r@   )submitr   )rb   r   r   r   r   executors        r.   
<dictcomp>z*get_all_backend_health.<locals>.<dictcomp>M  sK     
 
 
,,sFG OO0$VWMMt
 
 
rB   F)rL   _health_cache_lock_health_cache_time_health_cachecopyconcurrent.futuresr   r   BACKEND_CHECKSr$   r   r*   )r6   r   r   r   future_to_namefuturer   r   s          @r.   get_all_backend_healthr   A  sH    )++C	 ( (##a'' %%''( ( ( ( ( ( ( ('( ( ( ( ( ( ( ( ( ( ( ( ( ( ( FCCCCCCCC			*	*	* 
%h
 
 
 
0>0D0D0F0F
 
 
 #l>22 	% 	%F!&)D%%}}t % % %$t%		%
% 
% 
% 
% 
% 
% 
% 
% 
% 
% 
% 
% 
% 
% 
% 
 ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! MsY   'AA!$A!>;C4:CC4C$!C4#C$$C44C8;C8DDDc                        e Zd Z fdZ fdZd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Z d"d Z!d! Z" xZ#S )#ProxyHandlerc                 H     t                      j        |dt          i| d S )N	directory)super__init__BASE_DIRselfargskwargs	__class__s      r.   r   zProxyHandler.__init__a  s*    $=(=f=====rB   c                    |                      | j                  }t          j                            |          rt          j                            |          r t                                                      S 	 t          j        }|                     |          }t          |d          5 }| 
                    d           |                     d|           |                     dt           |t          j	        |t          j                            j                             |                     dd           |                     dd           |                     d	d
           |                                  |                                  | j                            |                                           ddd           dS # 1 swxY w Y   dS # t(          $ r# t                                                      cY S w xY w)uU   静态文件服务，添加 Cache-Control: no-cache 确保浏览器加载最新版本rb   zContent-typeContent-LengthCache-Control#no-cache, no-store, must-revalidatePragmazno-cacheExpires0N)translate_pathr3   r2   r4   isdirr   do_GETfstat
guess_typer&   send_responsesend_headerstrO_RDONLYst_size_send_cors_headersend_headerswfilewritereadr*   )r   r3   fsctyper-   r   s        r.   _serve_static_filezProxyHandler._serve_static_filed  s   ""49--w~~d## 	$rw}}T':': 	$77>>###	$BOOD))EdD!! 	+Q""3'''  777  !13rr"'$:T:T7U7U7]3^3^___  2WXXX  :666  C000'')))  """
  ***	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+  	$ 	$ 	$77>>#####	$s7   ;1G ,DG	<G 	GG GG *HHc                    | j                             d          s|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j                             d          r|                                 S | j         dk    r|                                 S | j                             d          r|                                 S | j                             d          r| 	                                S | j                             d	          r| j         
                    d
          d         }|dk    s|dk    r|                     d           d S d|v sd|v sd|v r|                     d           d S |                                 }|sd S |                     d|           d S |                     d           d S )N/api//api/health/api/start-servicesz/api/auth/checkz/api/recordsz/api/users/list/api/voice/file/z/api/comfyui/statusr   ?r   z/api/digital-human/v2/statusz/api/digital-human/statusr   z
/v2/video/z/v2/voice/audio/z/v2/bg/user)r3   
startswithr   _handle_health_handle_start_services_handle_auth_check_handle_records_get_handle_users_list_handle_voice_file_handle_comfyui_statussplit_proxy_request_require_auth)r   
clean_pathr  s      r.   r   zProxyHandler.do_GETy  s(   y##G,, 	-**,,, 9%%&&(((9---..0009)))**,,,9// 	.++---9)))**,,, 9 233 	-**,,, 9 566 	1..000 9 455 	'--a0J;;;zMh?h?h##E***z))-?:-M-MQZ^hQhQh ##E***%%''D D11111&&&&&rB   c                 p   | j         dk    r|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j         dk    r|                                 S | j         d	k    r| 	                                S | j         d
k    r| 
                                S | j         dk    r|                                 S | j                             d          r1|                                 }|sd S |                     d|           d S |                     d           d S )Nr   /api/auth/loginz/api/auth/logoutz/api/auth/change-pwdz/api/records/savez/api/users/addz/api/iopaint/remove_watermarkz/api/iopaint/detect_and_inpaintz/api/iopaint/inpaint_maskz/api/iopaint/remove_backgroundz/api/voice/saver   POSTr   )r3   r  _handle_login_handle_logout_handle_change_password_handle_records_save_handle_users_add _handle_iopaint_remove_watermark"_handle_iopaint_detect_and_inpaint_handle_iopaint_inpaint_mask!_handle_iopaint_remove_background_handle_voice_saver  r  r  r   r  s     r.   do_POSTzProxyHandler.do_POST  s   9---..0009)))%%'''9***&&(((9...//1119+++,,...9((())+++ 977788:::9999::<<<933344666988899;;;9)))**,,, 9 455 	(%%''D T22222'''''rB   c                    | j                             d          r|                                 S | j                             d          r|                                 S | j                             d          r/|                                 }|sd S |                     d|          S | j                             d          r|                     d          S |                     d           d S )Nz/api/records/deletez/api/users/deleter   DELETEr   r   i  )r3   r  _handle_records_delete_handle_users_deleter  r  
send_errorr  s     r.   	do_DELETEzProxyHandler.do_DELETE  s    9 566 	1..0009 344 	/,,... 9 455 	1%%''D &&xd&;;;Y!!'** 	1&&x000rB   c                     |                      d           |                                  |                                  dS )u   CORS 预检请求r   N)r   r   r   r   s    r.   
do_OPTIONSzProxyHandler.do_OPTIONS  s@    3!!!rB   c                    | j                             dd          }|                    d          r|dd         }t          |          }|r|S ddl}|j                            | j                  }|j                            |j	                  }|                    ddg          d         }|rt          |          S dS )u   从请求头提取并验证 token，返回用户信息或 None。
        支持两种认证方式（Header 优先）：
        1. Authorization: Bearer <token> 请求头
        2. ?token=<token> URL 查询参数（用于 <video>/<audio> 等无法自定义请求头的元素）
        Authorization Bearer    Nr   rX   )
headersr[   r  r\   r   parser   r3   parse_qsquery)r   auth_headerrX   r  r   r   qsquery_tokens           r.   _get_auth_userzProxyHandler._get_auth_user  s     l&&;;!!),, 	OE!%((D  	&&ty11\""6<00ffWtf--a0 	/!+...trB   c                 `   |                                  }|s|                     d           |                                  |                     dd           |                                  | j                            t          j        ddid          	                                           dS |S )	u"   要求认证，未认证返回 401  Content-Typeapplication/json; charset=utf-8r   u-   未登录或登录已过期，请重新登录Fr    N)
r2  r   r   r   r   r   r   r(   dumpsrP   r  s     r.   r  zProxyHandler._require_auth  s    ""$$ 	s#####%%%^-NOOOJTZH)!# # ##)688- - - 4rB   c                    t          | j                            dd                    }|dk    r| j                            |          nd}	 t          j        |          }|                    dd                                          }|                    dd          }n.# t
          j        $ r | 	                    ddd	i           Y d
S w xY w|r|s| 	                    dddi           d
S t          ||          }|s| 	                    dddi           d
S t          |d         |d         |d                   }| 	                    dd||d           d
S )zPOST /api/auth/loginr   r      {}r   r(  rA     r      请求格式错误N   用户名和密码不能为空r4  u   用户名或密码错误r   r   r   T)successrX   r  )intr+  r[   rfiler   r(   loadsstriprk   _json_responser=   rY   )r   content_lengthbodyr   r   rA   r  rX   s           r.   r  zProxyHandler._handle_login  s   T\--.>BBCC2@12D2Dtz~...%	:d##Dxx
B//5577Hxx
B//HH# 	 	 	g/C%DEEEFF	  	x 	g/O%PQQQF8X.. 	g/I%JKKKFtJ/n1EtF|TTC"
 "
 	 	 	 	 	s   AB 'C
	C
c                     | j                             dd          }|                    d          rt          |dd                    |                     dddd	           dS )
zPOST /api/auth/logoutr'  r(  r)  r*  Nr   Tu	   已登出r=  message)r+  r[   r  r_   rB  )r   r/  s     r.   r  zProxyHandler._handle_logout5  si    l&&;;!!),, 	.[_---CTk!J!JKKKKKrB   c                 \   |                                  }|sdS t          | j                            dd                    }|dk    r| j                            |          nd}	 t          j        |          }|                    dd          }|                    dd          }n.# t          j        $ r | 	                    dd	d
i           Y dS w xY w|r|s| 	                    dd	di           dS t          |          dk     r| 	                    dd	di           dS ddlm}m}m}	  ||d                   }
|
r ||d         |          s| 	                    dd	di           dS  |	|d         |          r| 	                    dddd           dS | 	                    dd	di           dS )zPOST /api/auth/change-pwdNr   r   r9  old_passwordr(  new_passwordr:  r   r;  u!   旧密码和新密码不能为空   u   新密码长度至少4位)r=   	find_userreset_passwordr   r4  u   旧密码错误r   Tu   密码修改成功rF  rx   u   密码修改失败)r  r>  r+  r[   r?  r   r(   r@  rk   rB  r9   user_managerr=   rL  rM  )r   r  rC  rD  r   rI  rJ  r=   rL  rM  	full_users              r.   r  z$ProxyHandler._handle_change_password<  s   !!## 	FT\--.>BBCC2@12D2Dtz~...%	:d##D88NB77L88NB77LL# 	 	 	g/C%DEEEFF	  	< 	g/R%STTTF|q  g/J%KLLLF 	HGGGGGGGGGId:.//	 	D,<l K K 	g/@%ABBBF >$z*L99 	FBV%W%WXXXXXg/C%DEEEEEs   $A B% %'CCc           	         |                                  }|sdS |                    d          dk    r|                     dddi           dS ddlm}  |            }g }|D ]I}|                    |d	         |d
         |                    dd          |d         |d         d           J|                     d|t          |          d           dS )u$   GET /api/users/list — 仅管理员Nr   admin  r   u!   仅管理员可查看用户列表r   )get_all_usersr   r   phoner(  rw   )r   r   rT  r   rw   r   )usersr   )r  r[   rB  rN  rS  appendr9   )r   r  rS  rU  
safe_usersus         r.   r  zProxyHandler._handle_users_listb  s
   !!## 	F88Fw&&g/R%STTTF......
 	 	AjM !. 1w++&	o      	C:J!P!PQQQQQrB   c                 <   |                                  }|sdS |                    d          dk    r|                     dddi           dS t          | j                            dd                    }|dk    r| j                            |          nd	}	 t          j        |          }|                    d
d          	                                }|                    dd          }|                    d|          }|                    dd          }|                    dd          }	n.# t          j
        $ r |                     dddi           Y dS w xY w|r|s|                     dddi           dS t          |          dk     r|                     dddi           dS ddlm}
m}  |
|          r|                     ddd| di           dS  ||||||	          r|                     ddd| dd           dS |                     dddi           dS )u$   POST /api/users/add — 仅管理员Nr   rQ  rR  r   u   仅管理员可添加用户r   r   r9  r   r(  rA   r   rT  r  r:  r;  r<  rK  u   密码长度至少4位)rL  add_useri     用户 'u   ' 已存在   Tu   ' 添加成功rF  rx   u   添加用户失败)r  r[   rB  r>  r+  r?  r   r(   r@  rA  rk   r9   rN  rL  rZ  )r   r  rC  rD  r   r   rA   r   rT  r   rL  rZ  s               r.   r  zProxyHandler._handle_users_addz  s   !!## 	F88Fw&&g/L%MNNNFT\--.>BBCC2@12D2Dtz~...%		:d##Dxx
B//5577Hxx
B//H88NH==LHHWb))E88FF++DD# 	 	 	g/C%DEEEFF	  	x 	g/O%PQQQFx==1g/G%HIIIF444444449X 	g/O(/O/O/O%PQQQF8HheTBB 	FBeXBeBeBe%f%fgggggg/C%DEEEEEs   BD, ,'EEc                 B   |                                  }|sdS |                    d          dk    r|                     dddi           dS ddlm}m}  || j                  } ||j                  }|                    d	dg          d         }|s|                     d
ddi           dS ||d	         k    r|                     d
ddi           dS ddlm	}  ||          r|                     ddd| dd           dS |                     ddd| di           dS )u6   DELETE /api/users/delete?username=xxx — 仅管理员Nr   rQ  rR  r   u   仅管理员可删除用户r   r   r-  r   r:  u   缺少 username 参数u   不能删除自己)delete_userr   Tr[  u   ' 已删除rF    u   ' 不存在或删除失败)
r  r[   rB  r   r   r-  r3   r.  rN  r_  )r   r  r   r-  r   paramstargetr_  s           r.   r   z!ProxyHandler._handle_users_delete  s   !!## 	F88Fw&&g/L%MNNNF33333333$)$$&,''J//2 	g/G%HIIIFT*%%%g/C%DEEEF,,,,,,;v 	_B`VB`B`B`%a%abbbbbg/\&/\/\/\%]^^^^^rB   c                     |                                  }|r|                     dd|d           dS |                     dddi           dS )zGET /api/auth/checkr   T)authenticatedr  rd  FN)r2  rB  r  s     r.   r  zProxyHandler._handle_auth_check  s`    ""$$ 	?tT%J%JKKKKKou%=>>>>>rB   c                    |                                  }|sdS ddlm}m}  || j                  } ||j                  }|j        dk    r|                    ddg          d         }|s|                     dddi           dS t          |d	         |          }|s|                     d
ddi           dS |                     dd|i           dS |                    ddg          d         }t          |                    ddg          d                   }	t          |                    ddg          d                   }
t          |d	         ||	|
          }|                     d|           dS )u(   GET /api/records 和 /api/records/detailNr   r^  z/api/records/detailrv   r:  r      缺少 id 参数r   r`     记录不存在r   rs   r   chatr   1r   20)r  r   r   r-  r3   r.  r[   rB  r   r>  r   )r   r  r   r-  r   ra  r   rs   r~   r   r   r   s               r.   r  z ProxyHandler._handle_records_get  s   !!## 	F33333333$)$$&,'' ;///

4$003I ##C'3E)FGGG%d:&6	BBF ##C'3D)EFFFh%7888F jj&22156::fse,,Q/00

;77:;;	$T*%5{D)TTC(((((rB   c                    |                                  }|sdS t          | j                            dd                    }|dk    r| j                            |          nd}	 t          j        |          }n.# t          j        $ r | 	                    dddi           Y dS w xY w|                    dd	          }|d
vr| 	                    dddi           dS ||                    dd          dd         |                    dd          |                    dd          dd         |                    di           d}t          |d         |          }| 	                    dd|d           dS )zPOST /api/records/saveNr   r   r9  r:  r   r;  r   rh  )rh  	knowledgecopywritingscriptimagevoicecontentu   无效的记录类型titler(  d   rq  previewr   metadata)r   rr  rq  rt  ru  r   r\  T)r=  rs   )r  r>  r+  r[   r?  r   r(   r@  rk   rB  r{   )r   r  rC  rD  r   r~   rs   saveds           r.   r  z!ProxyHandler._handle_records_save  s   !!## 	FT\--.>BBCC2@12D2Dtz~...%	:d##DD# 	 	 	g/C%DEEEFF	 hhvv..iiig/F%GHHHF  XXgr**4C40xx	2..xx	2..tt4R00
 
 4
+V44CTU!C!CDDDDDs   $A9 9'B$#B$c                    |                                  }|sdS ddlm}m}  || j                  } ||j                  }|                    ddg          d         }|s|                     dddi           dS t          |d         |          r|                     d	d
dd           dS |                     dddi           dS )z!DELETE /api/records/delete?id=xxxNr   r^  rv   r:  r   rf  r   r   Tu	   已删除rF  r`  rg  )	r  r   r   r-  r3   r.  r[   rB  r   )r   r  r   r-  r   ra  r   s          r.   r  z#ProxyHandler._handle_records_delete	  s    !!## 	F33333333$)$$&,''JJtdV,,Q/	 	g/A%BCCCFj)955 	C+%N%NOOOOOg/@%ABBBBBrB   c                 X   |                      |           |                                  |                     dd           |                     dd           |                                  | j                            t          j        |d                                                     dS )u   发送 JSON 响应r5  r6  r   r   Fr    N)	r   r   r   r   r   r   r(   r7  rP   )r   r   r   s      r.   rB  zProxyHandler._json_response  s    6"""!!!)JKKK*OPPP
Du===DDFFGGGGGrB   c           
         t                      }t          |                                          }|                     |rdnd           |                                  |                     dd           |                                  | j                            t          j
        |rdnddt          t          j                    | j        j        z
  d          |d	d
                                                     dS )u   健康检查 APIr      r5  r6  healthydegradedrunningr   )r   proxyuptimebackendsFr    N)r   allvaluesr   r   r   r   r   r   r(   r7  roundrL   server
start_timerP   )r   r  all_healthys      r.   r  zProxyHandler._handle_health(  s    )++(//++,,+6333777!!!)JKKK
#.>iiJDIKK$+*@@!DD 	%
 %

   
  &vxx	) 	) 	) 	) 	)rB   c                    ddl }t          j                            t          d          }t          j                            t          d          }t          j                            |          r^d}	  |j        dddd	||gt          |j        |j        |j        
           | 	                    ddddd           dS # t          $ r Y nw xY wt          j                            |          r	  |j        dddd|gt          |j        |j        |j        
           | 	                    ddddd           dS # t          $ r1}| 	                    ddt          |          d           Y d}~dS d}~ww xY w| 	                    dddd           dS )u   一键启动所有后端服务r   Nzservice_guardian.pyzstart_all_services.batz%C:\Program Files\Python311\python.execmdz/cr   zService-Guardian)cwdstdoutr   creationflagsr   Tu<   服务守护进程已启动！服务崩溃后将自动重启u/   请等待30-60秒后刷新状态查看各服务)r=  rG  hintzService-LauncheruB   启动脚本已执行，请查看弹出的命令窗口了解进度u;   启动完成后请点击"刷新状态"查看各服务状态rx   F)r=  r   uB   启动脚本不存在，请通过桌面"启动工作台.bat"启动)
subprocessr2   r3   rg   r   r4   PopenDEVNULLCREATE_NEW_PROCESS_GROUPrB  r*   r   )r   r  guardian_pathbat_path
python_exer;   s         r.   r  z#ProxyHandler._handle_start_services8  s   X/DEE7<<*BCC7>>-(( 	AJ 
 D'+=z=Y %-%-","E    ##C#]M* *   
     7>>(## 	 
 D'+=xH %-%-","E    ##C#cY* *   
    ##CUSVV)L)LMMM 	CY"
 "
 	 	 	 	 	s,   1A	B< <
C	C	,AD6 6
E1 &E,,E1c                 0   t           j                            | j                  }t           j                            |j                  }|                    ddg          d         }|s|                     dddi           dS d}	 t           j        	                    | d|           }t           j        
                    |d	
          5 }t          j        |                                          }ddd           n# 1 swxY w Y   |ry||v rt||         }|                    di           }	|                    di           }
|
                    d          dk    rU|                     dd||
                    d          r$|
                    ddgg          d         d         ndd           dS g }|	                                D ]\  }}|                    d          r|d         D ]|}|d         }|                    dd          }dt           j                            |           dt           j                            |           d}|                    |||d           }|                     dd||d           dS t           j        	                    | d          }t           j        
                    |d	
          5 }t          j        |                                          }ddd           n# 1 swxY w Y   |                    dg           }|                    d g           }|D ]Y}t#          |          d!k    rB|d"         |k    r5t#          |          d#k    rt#          |d"                   nd}	 t           j        	                    | d$          }t           j        
                    |d	
          5 }t          j        |                                          }ddd           n# 1 swxY w Y   |                    d%i           }|                    d&d          }n# t$          $ r d}Y nw xY wd}|dk    r$t'          d	t)          d'd(|d	z  z
                      }|                     dd)||dd*            dS [t+          |          D ]`\  }}t#          |          d!k    rH|d"         |k    r<|                     dd+||d"z   t#          |          t#          |          d,            dS a|                     dd-|d.d           dS # t           j        j        $ r-}|                     d/d|d0|j         d           Y d}~dS d}~wt$          $ r2}|                     d1d|t3          |          d           Y d}~dS d}~ww xY w)2uK  GET /api/comfyui/status?prompt_id=xxx
        实时查询 ComfyUI 中任务的状态：
        - queued: 排队中
        - running: 执行中，返回进度百分比
        - completed: 已完成，返回输出图片URL
        - error: 执行失败
        - not_found: prompt_id 不存在（已完成并被清理）
        	prompt_idNr   r:  r   u   缺少 prompt_id 参数r   z	/history/r   r   outputsr   
status_strr   messagesu   执行失败u   未知错误)r   r  rG  imagesfilename	subfolderr(  z/api/comfyui/view?filename=z&subfolder=z&type=output)r   r  node_id	completed)r   r  
image_urlsz/queuequeue_runningqueue_pendingr	   r|   r
   z/prompt	exec_infoqueue_remainingZ   rs  r}  )r   r  progressqueue_positionqueued)r   r  r  total_pendingrunning_count	not_foundu5   任务已完成或 prompt_id 无效，请检查结果  u   无法连接 ComfyUI: rx   )r   r,  r   r3   r-  r.  r[   rB  r   r   r   r(   r@  r   r$   quoterV  r9   r*   r   min	enumerater   r   reasonr   ) r   r   ra  r  comfyui_basehist_reqr   	hist_data
hist_entryr  status_datar  r  outputimgr  r  img_url	queue_req
queue_datar}  pendingitem
node_count
prompt_reqp_respprompt_datar  r  r  idxr;   s                                    r.   r	  z#ProxyHandler._handle_comfyui_statusl  sR    &&ty11&&v|44JJ{TF33A6	 	g/H%IJJJF.n	~--.S.S	.S.STTH''!'<< 4 Jtyy{{33	4 4 4 4 4 4 4 4 4 4 4 4 4 4 4  !Y)33&y1
$..B77(nnXr:: ??<00G;;''")%.]h]l]lmw]x]x  $M;??:@P?Q#R#RSU#VWX#Y#Y  M. .   
 F  
'.}} 
 
OGVzz(++ 	#)(#3  C'*:H(+R(@(@I 'YFLDVDVW_D`D`  'Y  'Ymsmymm  AJ  nK  nK  'Y  'Y  'YG&--'.,4+2/ /     ##C)!*",* *   
  ..,/F/F/FGGI''	1'== 5!Z		44
5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 !nn_b99G nn_b99G    t99>>d1g&:&:14TaT!WQJ,%+^%;%;|<T<T<T%U%U
#^33J3JJ Df*.*V[[]]*C*CKD D D D D D D D D D D D D D D$/OOK$D$D	*3--8I1*M*M$ , , ,*+,  "H&**#&q#b#!8K2K*L*L#M#M''"+%.$,*+	. .    FF 'w// 	 		Tt99>>d1g&:&:''"*%.*-'),W),W. .    FF %&R& &      |$ 	 	 	!&>AH>>& &         
  	 	 	!&q66& &         	s   AT 
'C=1T =DT DB+T 2CT AT 'K:.T :K>>T K>A?T AP0'O8,P08O<	<P0?O<	 /P0/T 0P?<T >P??AT A/T =T V."UV#'VVc                    | j                             dd          }d|vrdS t          | j                             dd                    }|dk    rdS | j                            |          }ddl}d}|                    d          D ]J}|                                }|                    d	          r|d
d                             d          } nK|sdS |	                    d          }|                    d|z             }i }	i }
|D ]}|r|dk    s|dk    r|
                    d          }|dk    r-|d|                             dd          }||dz   d         }|                    d          r
|dd         }t          j        d|          }|s|                    d          }|                    d          }|r||f|
|<   |                    dd          |	|<   |	|
fS )u=   解析 multipart/form-data 请求体，返回 (fields, files)r5  r(  zmultipart/form-dataNNr   r   N;z	boundary=	   "r   s   --s   --
s   

r  ignore)errorsrK  s   
zPContent-Disposition:\s*form-data;\s*name="([^"]*)"(?:\s*;\s*filename="([^"]*)")?r|   r	   )r+  r[   r>  r?  r   email.parserr
  rA  r  rP   finddecodeendswithresearchgroup)r   content_typerC  rD  emailboundarypartboundary_bytespartsfieldsfiles
header_endheaders_rawrq  
disp_match
field_namer  s                    r.   _parse_multipart_formz"ProxyHandler._parse_multipart_form  sW   |'';; 44:T\--.>BBCCQ:z~.. 	 &&s++ 	 	D::<<D{++ 8>>#..  	:!11

5>122 	N 	ND 45==DI,=,= ;//JR{
{+22782LLK:>??+G(( '!#2#, #v  yD  E  EJ #))!,,J!''**H N%-w$7j!!%,^^GH^%M%Mz""u}rB   c           	         	 |                                  \  }}|rd|vr|                     dddi           dS |d         \  }}ddl}ddlm} ddl}ddl}|                    |                    |                    }	|	j	        dk    r|	
                    d          }	|                                }
|	                    |
d	
           |                    |
                                                              d          }|                    d|	j        d          }|                                }|                    |d	
           |                    |                                                              d          }||ddd}|                    d|d          }|j        dk    r|j        }|                     d           |                                  |                     dd           |                     dt/          |                     |                                  | j                            |           dS t7          d|j         d|j        dd          d           |                     ddd|j         i           dS # t:          $ rG}t7          d| d           |                     dddt=          |           i           Y d}~dS d}~ww xY w) u*  去水印 - 使用 IOPaint inpaint API (cv2 模型)
        
        策略：用全图mask调用inpaint，cv2.INPAINT_NS 算法对整图进行
        无痕修复，对水印/文字有自然的消除效果。
        若 IOPaint 支持 lama 模型则优先使用（需 --model lama）。
        ro  r:  r      未上传图片Nr   ImageRGBPNGformatr   L   
INPAINT_NSr
   ro  maskcv2_flag
cv2_radius$http://127.0.0.1:8080/api/v1/inpaintx   r(   r   r   r5  	image/pngr   u#   [IOPaint] 去水印 inpaint 失败(): Tflushrx   u   IOPaint 去水印处理失败:    [IOPaint] 去水印失败:    去水印失败: )r  rB  base64PILr  iorequestsr&   BytesIOmodeconvertsave	b64encodegetvaluer  rN   sizepoststatus_coderq  r   r   r   r9   r   r   r   r8   textr*   r   )r   r  r  r  
image_datar  r  r  r  r  buf
img_base64r  mask_bufmask_base64inpaint_payloadr   
result_imgr;   s                      r.   r  z-ProxyHandler._handle_iopaint_remove_watermark-  s;   :	N 6688MFE G500##C'3D)EFFF#(> HjMMM!!!!!!IIIOOO **RZZ
3344Cx5  kk%((**,,CHHSH'''))#,,..99@@IIJ 99S#(C00Dzz||HIIhuI--- **8+<+<+>+>??FFwOOK $#(	 O ==6$ !  D 3&&!\
""3''''')))  ===  !13z??CCC  """
  ,,,,, bD<LbbQUQZ[_\_[_Q`bbjnoooo##C'3gUYUe3g3g)hiiiii 	N 	N 	N3334@@@@g/K3q66/K/K%LMMMMMMMMM	Ns$   5J HJ A	J 
K#<KK#c                     !"#$ ddl ddlmm ddl"ddl#ddl}	                                  \  }}|rd|vr                     dddi           dS |d         \  }}|	                    dd	          
                                }                    "                    |                    j        d
k    r                    d
          j        \  ! "#fd fd$ $fd !$fd}|s$t!          d! d  dd            |             dS t!          d|dd          dd                               |                                          }d! d  d| d!dz
   d dz
   d}		 #                    dd|	|gdd d!d"d#d$%          }
|
j        d&k    r&t!          d'|
j         d(d            |             dS |
                                	                    d)d*          }t!          d+|dd&          d           |
                                }|                    d,          r,t-          j        d-d	|          }t-          j        d.d	|          }t-          j        d/|          }d0di|r	 |                    |                                          t7          fd1d2D                       sd0di	                    d0          rt7          fd3d4D                       sd0din# |j        t:          f$ r d0diY nw xY w	                    d0          st!          d5d            |             dS t=          d                   t=          d6                   t=          d7                   t=          d8                   f\  }}}}t=          |d9z            t=          |d9z            }}t?          d||z
            }t?          d||z
            }tA          j!        |z
  ||d:z  z             }tA          j"        |z
  ||d:z  z             }t!          d;| d<| d=| d>| d           #                    d?j        d          }$                    |          }|%                    ||||z   ||z   gd@A            |          } $|          rt!          dBd           dS t!          dCd                         dS # #j&        j'        $ r t!          dDd            |             Y dS tP          $ rB}t!          dE| dFd           ddl)}|*                                  |             Y d}~dS d}~ww xY w# tP          $ rn}t!          dG| d           ddl)}|*                                 	                      dHddItW          |           i           n#  Y n	xY wY d}~dS Y d}~dS d}~ww xY w)Juj  v8.0: AI智能去水印 - 自动识别或根据描述精准去除
        
        策略:
        1. 无描述词: 使用cv2图像处理自动识别水印区域（快速<1s，不依赖Ollama）
        2. 有描述词: 使用Ollama视觉模型精准识别（30s超时，失败回退到自动检测）
        3. 最终回退: 四角+边缘区域inpaint
        r   N)r  	ImageDrawro  r:  r   r  descriptionr(  r  c                                                     }                    |d                               |                                                              d          }                                 }|                     |d                               |                                                              d          }                    d||dddd	          S )
u   调用IOPaint inpaint APIr  r  r   r  r  r   r  r  r  )r  r  r  r  r  r   )	
mask_imager  img_b64r  mask_b64r  r  r  r  s	        r.   
do_inpaintzCProxyHandler._handle_iopaint_detect_and_inpaint.<locals>.do_inpaint  s    jjllU+++ **3<<>>::AA'JJ::<<777!++H,=,=,?,?@@GGPP}}:#*H,fghh %   rB   c                    | j         dk    r| j        }                    d                                                                dd                               dt          |                                                      j                            |           dS | j	        r| j	        dd         nt          | j                   }t          d| j          d| d	           d
S )u   发送inpaint结果r   r5  r  r   TNu   [IOPaint] inpaint失败(r  r  F)r  rq  r   r   r   r9   r   r   r   r  r   r8   )r   r	  	error_msgr   s      r.   send_resultzDProxyHandler._handle_iopaint_detect_and_inpaint.<locals>.send_result  s    #s**!%J&&s+++++---$$^[AAA$$%5s:GGG$$&&&J$$Z0004379 W	$3$#dFVBWBWIUT5EUU)UU]abbbb 5rB   c            	      .   j         \  } }                    dj         d          }	                    |          }t          | dz            }t          |dz            }t          |dz            }|                    dd||gd           |                    | |z
  d| |gd           |                    d||z
  ||gd           |                    | |z
  ||z
  | |gd           |                    d||z
  | |gd           |                    dd| t          |dz            gd           t          d|  d	| d
d            
|          } |          rt          dd           dS                     ddd|j         i           dS )u   回退: 四角+边缘inpaintr  r   g333333?皙?r  fill333333?u!   [Fallback] 四角+边缘inpaint (xr   Tr  u   [Fallback] 去水印完成rx   r   u   IOPaint去水印失败: N)r  rN   Drawr>  	rectangler8   rB  r  )r   hr  	mask_drawcorner_wcorner_hbottom_hr   r  r  r  r  r   r  s           r.   fallback_inpaintzIProxyHandler._handle_iopaint_detect_and_inpaint.<locals>.fallback_inpaint  s   x1yych22%NN400	q3w<<q3w<<q3w<<##Q8X$>S#III##Q\1a$B#MMM##QHh$B#MMM##Q\1x<A$FS#QQQ##QHa$;##FFF##Q1c!d(mm$<3#GGGB!BBaBBB$OOOO!z$'';t$$ g7tDDDDDD''g7dRVRb7d7d-efffffrB   c                  &   	 ddl } ddl}|                    '          }|                     || j                  }|                     |dd          }|                     | j        d          }|                     ||d          }|	                    |          }t          )dz            }t          (dz            }	dd||	f)|z
  d)|	fd(|	z
  |(f)|z
  (|	z
  )(fg}
|
D ]k\  }}}}|||||f         }|                    |dk              |j        d	z   z  }|d
k    r,d|||||f<   t          d| d| d| d| d|d
d           lt          (dz            }|(|z
  dddf         }|                    |dk              |j        d	z   z  }|dk    r#d|(|z
  dddf<   t          d|dd           t          (dz            }|d|ddf         }|                    |dk              |j        d	z   z  }|dk    r d|d|ddf<   t          d|dd           t          )dz            }t          (dz            })|z
  dz  }(|z
  dz  }||||z   |||z   f         }|                    |dk              |j        d	z   z  }|dk    rj||||z   |||z   f         }|                    |d          }|                    |          dk     r&d||||z   |||z   f<   t          d|dd           |                     ||d          }|                    |dk              dk    rn$                    |d           } t          d!d            %|           }! *|!          rt          d"d           dS t          d#d            &             dS t          d$d            &             dS # t"          $ rB}"t          d%|" d&d           ddl}#|#                                  &             Y d}"~"dS d}"~"ww xY w)'uA   v8.0: 使用cv2自动检测水印区域（无需Ollama，<1秒）r   N2      )   r&  r	   )
iterationsg      ?r|   g?r  u   [Auto] 角落水印: (,z)-(u	   ) 密度=z.2%Tr  r  gQ?u   [Auto] 底部水印 密度=皙?u   [Auto] 顶部水印 密度=g?g{Gz?)r   r|   )axis   u$   [Auto] 中心半透明水印 密度=r
   r  )r  u/   [Auto] 检测到水印区域，执行inpaint...u   [Auto] 自动去水印完成u   [Auto] inpaint失败，回退u;   [Auto] 未检测到明显水印，使用四角+边缘回退u   [Auto] 自动检测异常: u	   ，回退)cv2numpyarraycvtColorCOLOR_RGB2GRAYCannygetStructuringElement
MORPH_RECTdilate
zeros_liker>  sumr  r8   stdmean	fromarrayr*   	traceback	print_exc)+r,  np	img_arraygrayedgeskerneledges_dilated
mask_arrayr  r   cornerscx1cy1cx2cy2corner_edgesedge_densityr!  bottom_edgesbottom_densitytop_h	top_edgestop_densitycenter_wcenter_hcx_startcy_startcenter_edgescenter_densitycenter_region	color_stdmask_dilatedr  r   r;   r:  r  r  r"  r  img_himg_wr  s+                                       r.   auto_detect_watermarkzNProxyHandler._handle_iopaint_detect_and_inpaint.<locals>.auto_detect_watermark  su   V JJJ&&&& "I<<	33EFFD  IIdB44E !66s~xPPF$'JJufJ$K$KM!#t!4!4J  #54<00H"54<00HAx2)1eX>EH,h>)58+;UEJ	G /6 } }*S#s'4SWc#g5E'F')vvlQ.>'?'?<CTWXCX'Y'$..;>Js3wC'78!"o3"o"o"o"o"o"os"o"o]i"o"o"ow{||||  #54<00H#0x!!!1C#DL%'VVL1,<%=%=ARUVAV%WN%,,9<
5>??AAA#56PNPPPX\]]]]  ,,E -fufaaai 8I"$&&Q"7"79>A;M"NK"T))03
6E61119-MKMMMUYZZZZ  #53;//H"53;//H % 0Q6H % 0Q6H#0(8:K1KXV^_gVgMg1g#hL%'VVL1,<%=%=ARUVAV%WN%,,(1(8H;L2LhW_`hWhNh2h(i$&FF=uF$E$E	779--22adJx0A'A8HU]L]C]']^!"]"]"]"]eijjjj $'::j&Q:#O#OLvvlQ.//!33%*__\_%L%L
PX\]]]])z*55&;t,, (!"ANNNN#'4!"B$OOOO,,...#'4\dhiiii((***#t       DDDDDQQQQ$$$$'')))$$&&&44444 s$   NO 
O 'O 
P7PPu*   [v8.0] 无描述词，自动检测水印 (r  r   Tr  u+   [v8.0] 有描述词，Ollama vision识别: <   z...z:You are a watermark detection expert. Look at this image (z pixels) and find: aJ  .

A watermark is any text, logo, icon, pattern, or semi-transparent overlay that was added to the original image. Common locations: bottom-right, bottom-left, top-right, top-left corners, or centered.

Return ONLY a valid JSON object (no markdown, no explanation) with the bounding box in pixel coordinates:
{"found": true, "x": <int>, "y": <int>, "width": <int>, "height": <int>}

If you find multiple watermarks, return the bounding box that covers ALL of them combined.
If absolutely no watermark exists, return: {"found": false}

Coordinates must be within the image boundaries (0-r|   z, 0-z).z#http://127.0.0.1:11434/api/generatezqwen2.5vl:7bFr)     )temperaturenum_predict)modelpromptr  streamoptionsr+  r  r   u   [Detect] Ollama失败(u   )，回退到自动检测responsez{}u   [Detect] Ollama返回: z```z^```(?:json)?\s*\n?z
\n?```\s*$z\{[\s\S]*?\}foundc              3       K   | ]}|v V  	d S r?   r@   rb   kbboxs     r.   	<genexpr>zBProxyHandler._handle_iopaint_detect_and_inpaint.<locals>.<genexpr>U  s'      "@"@19"@"@"@"@"@"@rB   )rd  c              3       K   | ]}|v V  	d S r?   r@   rf  s     r.   ri  zBProxyHandler._handle_iopaint_detect_and_inpaint.<locals>.<genexpr>W  s'      8j8jqd8j8j8j8j8j8jrB   )r  ywidthheightu3   [Detect] 未识别到水印，回退到自动检测rk  rl  rm  r  r	   u   [Detect] 水印区域: x=z, y=z, w=z, h=r  r  r  u   [Detect] 精准去水印完成u   [Detect] inpaint失败，回退u2   [Detect] Ollama超时(30s)，回退到自动检测u   [Detect] 检测失败: u   ，回退到自动检测r  rx   r  ),r  r  r  r  r  r  r(   r  rB  r[   rA  r&   r  r  r  r  r8   r  r  r   r  r  r  subr  r@  r  r  rk   
ValueErrorr>  r   r  rl  rm  rN   r  r  
exceptionsTimeoutr*   r:  r;  r   )%r   json_modr  r  r  r  r  rZ  r  vision_promptvision_respvision_resultcleaned
json_matchr  rk  r   r  pad_wpad_hr  r  r   r;   r:  r  r  r  rh  r  r"  r  rX  rY  r  r  r  s%   `                        @@@@@@@@@@@@r.   r  z/ProxyHandler._handle_iopaint_detect_and_inpaintp  sG    	((((((((			I	 6688MFE G500##C'3D)EFFF#(> Hj **]B77==??K**RZZ
3344Cx5  kk%((8LE5       ! ! ! ! ! g g g g g g g g g g*X  X  X  X  X  X  X  X  X  X  X z  S5SS5SSS[_````%%''' UCRC@PUUU]abbbb))*55<<>>J
O[` 
O 
Och 
O 
O  ~I 
O 
O 5:!G
O 
O BGq
O 
O 
OMI(&mm9!/"/#-,"'36s#K#K   , 
 
 *c11e;3Jeeemqrrrr))+++F + 0 0 2 2 6 6z4 H HEdsd0CEETRRRR (--//%%e,, A f%;RIIG f]B@@GY@@
' 00'~~j.>.>.@.@AA""@"@"@"@i"@"@"@@@ 4$+U#3D88G,, 4S8j8j8j8jLi8j8j8j5j5j 4$+U#3D$4jA 0 0 0 '/0 xx(( PX\]]]]))+++F c^^Sc^^Sg=O=OQTUYZbUcQdQdd
1a"1s7||SS\\u1u9%%1u9%%	Aq519}55
QEAI66J!JJJJJJqJJRVWWWWyych22%NN400	##Q1q5!a%$8s#CCC!z$'';t$$ ;4HHHHF<DIIII$$&&&F&. ( ( (KSWXXXX%%'''''' ( ( (KKKKSWXXXX    ##%%%%%'''''''''	(  	 	 	3334@@@@!!!##C'3Os1vv3O3O)PQQQQ RQQQQQ	s   5U% CU% +AU% AS* B4S* A:L 
S* L# S* "L##3S* E3S* S* **U"U% 	U" 7UU% U""U% %
W/,W(WWW	WWc           	         	 |                                  \  }}|rd|vr|                     dddi           dS |d         }t          |t                    r|\  }}d}d|v r"|d         }t          |t                    r|\  }}|H|                    dd          }t          |t                    rt          |          dk    r|d         nd}d	dl}d	d
lm} d	dl	}d	dl
}	|                    |                    |                    }
|
j        dk    r|
                    d          }
|                                }|
                    |d           |                    |                                                              d          }|r|                    |                    |                    }|j        dk    r|                    d          }|j        |
j        k    r |                    |
j        |j                  }|                                }|                    |d           |                    |                                                              d          }n|                    d|
j        d          }|                                }|                    |d           |                    |                                                              d          }||ddd}|	                    d|d          }|j        dk    r|j        }|                     d           |                                  |                     dd           |                     dt          |                     |                                  | j                            |           dS tA          d|j         d|j!        dd          d           |                     ddd |j         i           dS # tD          $ rG}tA          d!| d           |                     ddd"tG          |           i           Y d}~dS d}~ww xY w)#u   局部修复 - 接受图片和用户圈选的Mask进行精准去水印
        前端传递: image (原始图) + mask (用户绘制的修复区域，白色=修复，黑色=保留)
        ro  r:  r   r  Nr  r  r|   r   r  r  r  r  r   r  r  r  r
   r  r  r  r  r   r5  r  r   u   [IOPaint] 局部Inpaint 失败(r  Tr  rx   u   IOPaint 局部修复失败: u   [IOPaint] 局部修复失败: u   局部修复失败: )$r  rB  
isinstancetupler[   r9   r  r  r  r  r  r&   r  r  r  r  r  r  r  r  resizeNEARESTrN   r   r  rq  r   r   r   r   r   r   r8   r  r*   r   )r   r  r  r  _	mask_datar  r  r  r  r  r  r  mask_imgr  r  r  r  r   r	  r;   s                        r.   r  z)ProxyHandler._handle_iopaint_inpaint_mask  s   S	Q 6688MFE G500##C'3D)EFFFwJ*e,, + *: I!&M	i// -#,LAy  !IIfl;;	i// M03I0B0B	!IMMM!!!!!!IIIOOO **RZZ
3344Cx5  kk%((**,,CHHSH'''))#,,..99@@IIJ  T ::bjj&;&;<<=C'''//44H=CH,,'sxGGH::<<hu555$..x/@/@/B/BCCJJ7SS yych44::<<		(5	111$..x/@/@/B/BCCJJ7SS $#(	 O ==6$ !  D 3&&!\
""3''''')))  ===  !13z??CCC  """
  ,,,,,^8H^^TYW[X[W[_^^fjkkkk##C'3dRVRb3d3d)efffff 	Q 	Q 	Q6166dCCCCg/Nc!ff/N/N%OPPPPPPPPP	Qs$   5O; M5O; 0A	O; ;
Q<QQc                 l   	 |                                  \  }}|rd|vr|                     dddi           dS |d         \  }}ddl}ddl}|                    |                              d          }d|d	}|                    d
|d          }	|	j        dk    r|	j        }
nt          d|	j         d|	j
        dd          dd           	 ddlm}  ||d          }
nm# t          $ r |                     dddi           Y dS t          $ r<}|                     ddd|	j         dt          |           di           Y d}~dS d}~ww xY w|                     d           |                                  |                     dd           |                     dt'          |
                     |                                  | j                            |
           dS # t          $ rG}t          d| d           |                     dddt          |           i           Y d}~dS d}~ww xY w)u9   抠图 - 使用 IOPaint RemoveBG 插件 (briaai/RMBG-1.4)ro  r:  r   r  Nr   r   RemoveBG)r   ro  z1http://127.0.0.1:8080/api/v1/run_plugin_gen_imager  r  r   u   [IOPaint] RemoveBG插件失败(r  u   , 回退到本地rembgTr  )remove)force_return_bytesrx   uE   抠图服务不可用：IOPaint RemoveBG插件和rembg库均不可用u   抠图失败（IOPaint: z	, rembg: u   ）r5  r  r   u   [IOPaint] 抠图失败: u   抠图失败: )r  rB  r  r  r  r  r   r  rq  r8   r  rembgr  ImportErrorr*   r   r   r   r   r9   r   r   r   )r   r  r  r  r  r  r  r  plugin_payloadr   result_datar  r   r;   s                 r.   r  z.ProxyHandler._handle_iopaint_remove_background  s   3	K 6688MFE G500##C'3D)EFFF#(> HjMMMOOO  ))*55<<WEEJ ## N ==C# !  D 3&&"l t8HttTYW[X[W[_ttt  }A  B  B  B  B,,,,,,"(&"M"M"MKK"   ''g7~-  A  A  AFF    ''g7yRVRb7y7ympqsmtmt7y7y7y-z{{{FFFFF s#####%%%^[999-s;/?/?@@@J[))))) 	K 	K 	K0Q00====g/HA/H/H%IJJJJJJJJJ	KsT   5G" BG" C# "G" #"EG" 	E1EG" EBG" "
H3,<H..H3c           	         |                                  }|sdS 	 |                                 \  }}|rd|vr|                     dddi           dS |d         \  }}ddl}t	           |j                              }t
          j                            |          d         pd}|d	          d
| | }	t
          j                            t          d|d	                   }
t          j
        |
d           t
          j                            |
|	          }t          |d          5 }|                    |           ddd           n# 1 swxY w Y   d|d	          d|	 }|                     dd||	t          |          d           t          d| d           dS # t          $ rG}t          d| d           |                     dddt!          |           i           Y d}~dS d}~ww xY w)u7   POST /api/voice/save - 保存 TTS 生成的音频文件Naudior:  r   u   未上传音频文件r   r|   .wavr   r  voicesTr   wbr   /r   )r=  r   r  r  u   [Voice] 保存音频: r  u   [Voice] 保存音频失败: rx   u   保存音频失败: )r  r  rB  rL   r>  r2   r3   splitextrg   rh   makedirsr&   r   r9   r8   r*   r   )r   r  r  r  r  
audio_datarL   tsextsave_filename	voice_dir	save_pathr-   file_urlr;   s                  r.   r  zProxyHandler._handle_voice_save  s   !!## 	F"	Q 6688MFE G500##C'3J)KLLL#(> HjKKKYTY[[!!B'""8,,Q/96C#J/;;";c;;M Xxj9IJJIK	D1111Y>>Ii&& $!
###$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ M$z*:LL]LLH)J	& &    6966dCCCCCC 	Q 	Q 	Q444DAAAAg/Nc!ff/N/N%OPPPPPPPPP	QsC   5F CF $E:F E

F E
AF 
G.'<G))G.c                    	 ddl }| j                            d          }t          |          dk     r|                     dd           dS |d         }d                    |dd                   }|j                            |          }d	|v s|                    d          r|                     d
d           dS t          j                            t          d||          }t          j                            |          s|                     dd           dS |                                 }|r&|d         |k    s|                    d          dk    rn|                     d
           |                                  |                     dd           |                                  | j                            t)          j        ddid                                                     dS t          j                            |          d                                         }ddddd}|                    |d          }	t3          |d          5 }
|
                                }ddd           n# 1 swxY w Y   |                     d            |                                  |                     d|	           |                     d!t          |                     |                     d"d#           |                                  | j                            |           dS # t6          $ rB}t9          d$| d%&           |                     d't;          |                     Y d}~dS d}~ww xY w)(uG   GET /api/voice/file/{username}/{filename} - 提供保存的音频文件r   Nr     r:  zInvalid pathrK  r   z..rR  	Forbiddenr  r`  zFile not foundr   r   rQ  r5  zapplication/jsonr   u   无权限访问此文件Fr    r|   z	audio/wavz
audio/mpegz	audio/oggz
audio/flac)r  z.mp3z.oggz.flaczapplication/octet-streamr   r   r   Accept-Rangesbytesu"   [Voice] 提供音频文件失败: Tr  rx   )r   r3   r
  r9   r!  rg   r,  unquoter  r2   rh   isfiler2  r[   r   r   r   r   r   r   r(   r7  rP   r  lowerr&   r   r*   r8   r   )r   r   
path_partsr   r  	file_pathr  r  
mime_typesr  r-   	file_datar;   s                r.   r  zProxyHandler._handle_voice_fileC  s   ;	)--J:""^444!!}Hxx
122//H|++H55H x8#6#6s#;#;[111Xx8LLI7>>),, %5666 &&((D 
j)X55&9I9IW9T9T ""3''''')))  1CDDD  """
  W6P,Q`e!f!f!f!m!m!o!oppp '""8,,Q/5577C#$#%	 J &>>#/IJJLi&& %!FFHH	% % % % % % % % % % % % % % % s#####%%%^\:::-s9~~>>>_g666JY''''' 	) 	) 	):q::$GGGGOOCQ(((((((((	)sY   AL$ A.L$ ;AL$ CL$ ,A$L$ I1%L$ 1I55L$ 8I59B)L$ $
M0.7M++M0c                     |                      dd           |                      dd           |                      dd           |                      dd           d S )	NzAccess-Control-Allow-Origin*zAccess-Control-Allow-MethodszGET, POST, DELETE, OPTIONSzAccess-Control-Allow-Headersz=Content-Type, Authorization, Content-Length, X-Requested-WithzAccess-Control-Max-Age86400)r   r$  s    r.   r   zProxyHandler._send_cors_headers  sf    6<<<79UVVV79xyyy17;;;;;rB   Nc                 h
   d}t                                           D ]^\  }}| j                            |          r?|dk    r,d| j        vr#| j                            ddd          }| | }n| | j         } n_|s^t
                                          D ]D\  }}| j                            |          r%| j        t          |          d         }| d| } nE|s |                     dd| j                    dS d	}d}	t          |dz             D ]i}
	 t          | j
                            d
d                    }|dk    r| j                            |          nd}i }dD ]#}| j
                            |          }|r|||<   $|r2|                    dd          |d<   |                    dd          |d<   t          j                            ||||          }d| j        v rd}nGd| j        v r'd| j        v sd| j        v sd| j        v s	d| j        v rd}nd| j        v s	d| j        v rd}nd}t          j                            ||          5 }|                                }|                     |j                   d D ]4}|j
                            |          }|r|                     ||           5|                                  d
d! |j
                                        D             vr#|                     d
t          |                     |                                  | j                            |           	 ddd            dS # 1 swxY w Y   M# t          j        j        $ r}|                     |j                   |                                  |                                  |j        r1| j                            |j                                                   Y d}~ dS d}~wt          j        j        t<          t>          f$ r-}|}	|
|k     rtA          j!        d"           Y d}~(Y d}~.d}~wtD          $ r-}|}	|
|k     rtA          j!        d"           Y d}~]Y d}~cd}~ww xY wtG          |	d#          rtI          |	j%                  ntI          |	          }|                     d$           |                                  |                                  | j                            tM          j'        d%| |d&          (                                           dS )'u  代理请求到后端服务
        
        Args:
            method: HTTP 方法 (GET/POST/DELETE)
            user:   认证用户信息 dict (可选)，含 username/display_name/role
                    如果提供，会通过 X-Auth-User 头传给后端
        Nr   z/v2z/api/digital-human/v2r|   r  r`  zNo route for r	   r   r   )r5  r'  Acceptz
User-AgentRanger   r(  zX-Auth-Userr   r  zX-Auth-Role)r   r   r+  z/api/gptsovitsiX  generatetrainrp  taski,  z/api/comfyuiz/api/iopaintr  r+  r   )r5  r   zContent-Ranger  zContent-Dispositionr   ETagzLast-Modifiedc                     h | ]\  }}|S r@   r@   )rb   rg  r  s      r.   	<setcomp>z.ProxyHandler._proxy_request.<locals>.<setcomp>  s    /S/S/Sda/S/S/SrB   g      ?r  r  zBackend unreachable: )r   backend))FULL_PATH_ROUTESr$   r3   r  replacePROXY_ROUTESr9   r!  ranger>  r+  r[   r?  r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   fpr   ConnectionRefusedErrorOSErrorrL   sleepr*   hasattrr   r  r(   r7  rP   )r   r   r  backend_urlprefixrb  new_pathsub_pathmax_retries
last_errorattemptrC  rD  fwd_headersr  vr   proxy_timeoutr   response_bodyrhr;   r  s                          r.   r  zProxyHandler._proxy_request  sj    .4466 		 		NFFy##F++  111e496L6L#y001EG^`abbH%+"7X"7"7KK%+"8TY"8"8K  	"."4"4"6"6  9''// #yV6H%+"8"8h"8"8KE
  	OOC!<!<!<===F
[1_-- I	 I	GH!$T\%5%56F%J%J!K!K:H1:L:Ltz~666RV ![ + +A((++A +)*A  J15*b1I1IK.15&&1I1IK.n,,!'	 -   $ty00$'MM)TY66J$)<S<SW^bfbkWkWkovz~  {D  pD  pD  HN  RV  R[  H[  H[$'MM#ty00Ndi4O4O$'MM$&M^++C+GG 4$(IIKKM&&t{333` 4 4 L,,R00 4 ,,R333++---'/S/Sdl>P>P>R>R/S/S/SSS(()93};M;MNNN$$&&&J$$]333                 " <)   ""16***'')))  """4 2J$$QTYY[[111L)+A7K   
[((JsOOOHHHH )((((    
[((JsOOOHHHH )(((( /6j(.K.K`C
)***QTU_Q`Q`	3!!!
8Y88"%
 %
   688	 	 	 	 	sW   D=M C1MM M	M M	M  Q;4A:O55$Q;QQ;Q66Q;c                     |r|d         nd}dt          |          v rt          d| d           dS dt          |          vrt          d| d           dS dS )	u   简化日志r   r(  r   z[PROXY] Tr  200N)r   r8   )r   r  r   msgs       r.   log_messagezProxyHandler.log_message  s    %d1gg2c#hh"S""$//////#c(("""S""$////// #"rB   r?   )$r   
__module____qualname__r   r   r   r  r"  r%  r2  r  r  r  r  r  r  r   r  r  r  r  rB  r  r  r	  r  r  r  r  r  r  r  r   r  r  __classcell__r   s   @r.   r   r   _  sZ       > > > > >$ $ $ $ $*0' 0' 0'd+( +( +(Z  $    0     :L L L$F $F $FLR R R0(F (F (FT_ _ _:? ? ?) ) )>E E E>C C C,H H H) ) ) 0 0 0hA A AJ: : :xAN AN ANFW W WrWQ WQ WQr5K 5K 5Kn(Q (Q (QT=) =) =)~< < <u u u un0 0 0 0 0 0 0rB   r   c                   *     e Zd ZdZdZdZ fdZ xZS )ProxyServeru0   多线程代理服务器，带启动时间记录Tc                 j     t                      j        |i | t          j                    | _        d S r?   )r   r   rL   r  r   s      r.   r   zProxyServer.__init__  s0    $)&))))++rB   )r   r  r  __doc__daemon_threadsallow_reuse_addressr   r  r  s   @r.   r  r    sI        ::N& & & & & & & & &rB   r  __main__z7=======================================================u7     正方元智能广告工作台 - 代理服务器 v2.0u
     端口: u     静态目录: u&     用户认证: 已启用 (users.xlsx)z0.0.0.0u   
[警告] 端口 u%    已被占用，尝试强制释放...win32znetstat -ano | findstr :)shellcapture_outputr  
r   r  ztaskkill /F /PID )r  r  u     已终止 PID=u)   
代理服务器运行在 http://0.0.0.0:u   路由: u   健康检查: http://127.0.0.1:r   u   登录接口: http://127.0.0.1:r  u   
正在关闭...u   代理服务器已停止)r|   r}   )r   r	   )ar  http.serverhttpurllib.requestr   urllib.errorr   r(   r  r   r2   r   rL   	threadingrQ   rM   rI   r   r   socketserverr   PORTr  r  r   r   r   Lockr   r3   dirnameabspath__file__r   rg   rh   r  rO   rT   r'   r#   r"   r/   r<   rz   rN  r=   r  r   rY   r\   r_   rU   ri   listrn   rr   dictr{   r>  r   r   boolr   r   r   r  SimpleHTTPRequestHandlerr   
HTTPServerr  r   chdirr8   getcwdr   r   r   bindr   r  platformr  runr   r  rA  r
  liner  r9   isdigitr  keysserve_foreverKeyboardInterruptshutdownr@   rB   r.   <module>r     s  	 	                  				 



 				          ( ( ( ( ( ( ( ( ' ' ' ' ' '6555	  1  BD9;V<99	 	  #Y^%%  7??27??844557<<&)) Ht $ $ $ $ - gll8]33y~  &2 2 2:      8   (((((((       
S  3 3    $
# 
 
 
 
$C      >s >s > > > >
	3 	4 	 	 	 	<3 < < < < < d t     # C s SV `d     s s t    C C D    > > > >B  <f0 f0 f0 f0 f04;7 f0 f0 f0R-& & & & &.$+"8 & & & zBHX	E(OOO	E
CDDD	E
t

	E
*YRY[[
*
*+++	E
3444	E(OOO 6=);<<D		9d#$$$

   N4NNNOOO<7""#Z^14114d  F ++--33D99 : :

**,,3u::??uRy'8'8':':?"JN#BuRy#B#B$_cddddE8U2Y88999DJqMMM [)T*L99F	E
=t
=
=>>>	E
0TT,++--..
0
0111	E
=D
=
=
=>>>	E
AD
A
A
ABBB* * * *!"""())))))*M s7   /D6 6E E$+J C8N
N2P /P;:P;