-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun.py
More file actions
269 lines (225 loc) · 8.65 KB
/
Copy pathrun.py
File metadata and controls
269 lines (225 loc) · 8.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env python
"""
统一启动入口(工具选择器)
访问: http://localhost:5000
点击按钮时按需启动对应服务。
"""
import logging
import subprocess
import sys
import time
import atexit
import threading
import os
import signal
from flask import Flask, render_template, jsonify, request
from pathlib import Path
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 全局状态
server_processes = {}
server_status = {
'simple': {'running': False, 'port': 5001, 'script': 'run_simple.py'},
'multi': {'running': False, 'port': 5002, 'script': 'run_multi.py'}
}
def start_server(tool_id):
"""启动指定服务"""
if tool_id not in server_status:
return {'status': 'error', 'message': f'Unknown tool: {tool_id}'}
if server_status[tool_id]['running']:
return {'status': 'already_running', 'port': server_status[tool_id]['port']}
script = server_status[tool_id]['script']
script_path = Path(__file__).parent / script
if not script_path.exists():
return {'status': 'error', 'message': f'Script not found: {script}'}
try:
# Windows 需要特殊处理进程组
if sys.platform == 'win32':
proc = subprocess.Popen(
[sys.executable, str(script_path)],
cwd=Path(__file__).parent,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
else:
proc = subprocess.Popen(
[sys.executable, str(script_path)],
cwd=Path(__file__).parent,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
server_processes[tool_id] = proc
server_status[tool_id]['running'] = True
logger.info(f"Started {tool_id} server (PID: {proc.pid})")
return {'status': 'starting', 'port': server_status[tool_id]['port']}
except Exception as e:
logger.error(f"Failed to start {tool_id}: {e}")
return {'status': 'error', 'message': str(e)}
def check_server_health(port, timeout=15):
"""检查服务健康状态"""
import requests
start = time.time()
while time.time() - start < timeout:
try:
response = requests.get(f'http://localhost:{port}/api/health', timeout=1)
if response.status_code == 200:
return True
except:
pass
time.sleep(0.5)
return False
def cleanup_processes():
"""退出时清理所有子进程(增强版)"""
for tool_id, proc in server_processes.items():
if proc and proc.poll() is None:
logger.info(f"Stopping {tool_id} server (PID: {proc.pid})")
try:
if sys.platform == 'win32':
# Windows 下使用 taskkill 强制终止进程树
import subprocess as sp
sp.run(['taskkill', '/F', '/T', '/PID', str(proc.pid)],
capture_output=True, timeout=10)
else:
proc.terminate()
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
logger.warning(f"Process {tool_id} did not exit gracefully, force killing")
proc.kill()
except Exception as e:
logger.error(f"Error stopping {tool_id}: {e}")
proc.kill()
# 注册清理函数
atexit.register(cleanup_processes)
if __name__ == '__main__':
from core.config import config
# 更新端口配置
server_status['simple']['port'] = config.SIMPLE_APP_PORT
server_status['multi']['port'] = config.MULTI_APP_PORT
# 创建工具选择器应用
app = Flask(__name__,
template_folder='web/templates',
static_folder='web/static')
app.config['SECRET_KEY'] = config.SECRET_KEY
@app.route('/')
def index():
"""工具选择器主页"""
return render_template('index.html')
@app.route('/api/start/<tool_id>', methods=['POST'])
def api_start_tool(tool_id):
"""启动指定工具"""
if tool_id not in server_status:
return jsonify({'status': 'error', 'message': 'Unknown tool'}), 400
result = start_server(tool_id)
if result['status'] == 'error':
return jsonify(result), 500
if result['status'] == 'starting':
# 等待服务就绪
port = result['port']
if check_server_health(port):
return jsonify({
'status': 'ready',
'url': f'http://localhost:{port}'
})
else:
server_status[tool_id]['running'] = False
return jsonify({
'status': 'error',
'message': '服务启动超时,请检查日志'
}), 500
# already_running
return jsonify({
'status': 'ready',
'url': f'http://localhost:{result["port"]}'
})
@app.route('/api/stop/<tool_id>', methods=['POST'])
def api_stop_tool(tool_id):
"""停止指定工具"""
if tool_id not in server_status:
return jsonify({'status': 'error', 'message': 'Unknown tool'}), 400
if not server_status[tool_id]['running']:
return jsonify({'status': 'already_stopped'})
proc = server_processes.get(tool_id)
if proc and proc.poll() is None:
try:
# 先尝试调用子服务的 shutdown 端点
port = server_status[tool_id]['port']
try:
import requests
requests.post(f'http://localhost:{port}/api/shutdown', timeout=2)
except:
pass
# 强制终止进程
proc.terminate()
try:
proc.wait(timeout=5)
except:
proc.kill()
logger.info(f"Stopped {tool_id} server")
except Exception as e:
logger.error(f"Failed to stop {tool_id}: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500
finally:
if tool_id in server_processes:
del server_processes[tool_id]
server_status[tool_id]['running'] = False
return jsonify({'status': 'stopped'})
@app.route('/api/status', methods=['GET'])
def api_status():
"""获取所有服务状态"""
status = {}
for tool_id, info in server_status.items():
status[tool_id] = {
'running': info['running'],
'port': info['port'],
'url': f'http://localhost:{info["port"]}'
}
return jsonify(status)
@app.route('/api/tools')
def get_tools():
"""获取可用工具列表"""
return {
'tools': [
{
'id': 'simple',
'name': '简版工具',
'description': '单模型快速生成,适合日常使用',
'url': f'http://localhost:{config.SIMPLE_APP_PORT}',
'provider': '智谱AI'
},
{
'id': 'multi',
'name': '多模型工具',
'description': '多模型并行生成,结果对比',
'url': f'http://localhost:{config.MULTI_APP_PORT}',
'providers': ['智谱AI', '阿里云']
}
]
}
@app.route('/api/shutdown', methods=['POST'])
def api_shutdown():
"""关闭工具选择器服务器"""
def shutdown():
time.sleep(1) # 等待响应发送
os.kill(os.getpid(), signal.SIGTERM)
threading.Thread(target=shutdown, daemon=True).start()
logger.info("Shutting down tool selector server...")
return jsonify({'status': 'shutting_down'})
port = config.HUB_APP_PORT
print(f"\n{'='*50}")
print(f" tailorCV 工具选择器")
print(f" 访问地址: http://localhost:{port}")
print(f"{'='*50}")
print(f"\n 可用工具 (点击按钮自动启动):")
print(f" - 简版工具: 端口 {config.SIMPLE_APP_PORT}")
print(f" - 多模型工具: 端口 {config.MULTI_APP_PORT}")
print(f"{'='*50}\n")
try:
# use_reloader=False 防止子服务启动时触发主服务重载
app.run(host='0.0.0.0', port=port, debug=True, threaded=True, use_reloader=False)
finally:
cleanup_processes()