-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
189 lines (159 loc) · 6.3 KB
/
Copy pathapp.py
File metadata and controls
189 lines (159 loc) · 6.3 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
import os
import json
import time
import queue
import threading
from flask import Flask, render_template, jsonify, request, Response
from src.searcher import JobSearcher
from src.auto_apply import AutoApplyEngine
app = Flask(__name__)
# A simple Message Queue to send logs from the backend to the frontend UI
log_queue = queue.Queue()
# We temporarily override the built-in print statements in our Engine to feed the queue
class QueueLogger:
def write(self, msg):
if msg.strip():
msg_type = 'system'
if '[+]' in msg: msg_type = 'success'
elif '[-]' in msg or 'Error' in msg: msg_type = 'error'
elif '[*]' in msg: msg_type = 'info'
elif '[AI]' in msg: msg_type = 'ai'
# Write to real terminal (avoid print() which would re-enter this logger and cause recursion)
import sys
if getattr(sys, '__stdout__', None):
sys.__stdout__.write(msg.strip() + '\n')
sys.__stdout__.flush()
# Send to frontend
log_queue.put(json.dumps({'message': msg.strip(), 'type': msg_type}))
def flush(self):
pass
def format_sse(data: str):
"""Formats string into Server-Sent Events standard format."""
return f"data: {data}\n\n"
@app.route('/')
def index():
return render_template('index.html')
@app.route('/stream_logs')
def stream_logs():
"""Event stream end-point for SSE"""
def generate():
while True:
# Block until a message is available in the queue
msg = log_queue.get(block=True)
yield format_sse(msg)
return Response(generate(), mimetype='text/event-stream')
from src.config import config
@app.route('/api/profile', methods=['GET', 'POST'])
def handle_profile():
if request.method == 'GET':
try:
with open(config.PROFILE_PATH, 'r') as f:
profile_data = json.load(f)
return jsonify({'status': 'success', 'profile': profile_data})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)})
if request.method == 'POST':
try:
new_profile = request.json
with open(config.PROFILE_PATH, 'w') as f:
json.dump(new_profile, f, indent=4)
return jsonify({'status': 'success'})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)})
import csv
def _normalize_url(u):
"""Normalize URL for comparison (strip and strip trailing slash)."""
if not u:
return ''
u = u.strip().rstrip('/')
return u
def _get_applied_urls():
"""Return set of URLs already in applications.csv (normalized for comparison)."""
log_path = os.path.join(config.ROOT_DIR, "applications.csv")
if not os.path.exists(log_path):
return set()
urls = set()
try:
with open(log_path, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
u = _normalize_url(row.get('URL'))
if u:
urls.add(u)
except Exception:
pass
return urls
@app.route('/api/history', methods=['GET'])
def get_history():
log_path = os.path.join(config.ROOT_DIR, "applications.csv")
if not os.path.exists(log_path):
return jsonify({'status': 'success', 'history': []})
try:
history = []
with open(log_path, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
history.append({
'date': row.get('Date', ''),
'company': row.get('Company', ''),
'role': row.get('Role', ''),
'url': row.get('URL', '')
})
return jsonify({'status': 'success', 'history': history})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)})
@app.route('/api/scan', methods=['GET'])
def scan_jobs():
searcher = JobSearcher()
# Redirect print to queue
import sys
original_stdout = sys.stdout
sys.stdout = QueueLogger()
try:
jobs = searcher.find_jobs()
sys.stdout = original_stdout
return jsonify({'status': 'success', 'jobs': jobs})
except Exception as e:
sys.stdout = original_stdout
return jsonify({'status': 'error', 'message': str(e)})
@app.route('/api/apply', methods=['POST'])
def apply_jobs():
data = request.json
selected_urls = data.get('urls', [])
if not selected_urls:
return jsonify({'status': 'error', 'message': 'No URLs provided.'})
applied = _get_applied_urls()
to_apply = [u for u in selected_urls if _normalize_url(u) and _normalize_url(u) not in applied]
skipped = len(selected_urls) - len(to_apply)
if skipped:
selected_urls = to_apply
if not to_apply:
return jsonify({
'status': 'success',
'message': f'All {len(selected_urls)} selected job(s) were already applied; nothing to do.'
})
def run_application_loop(urls, skip_count):
import sys
sys.stdout = QueueLogger()
if skip_count:
print(f"[*] Skipped {skip_count} already-applied URL(s).")
engine = AutoApplyEngine()
success_count = 0
for i, url in enumerate(urls, 1):
print(f"\n[*] Processing [{i}/{len(urls)}]: {url}")
time.sleep(2) # Polite delay
try:
if engine.process_url(url):
success_count += 1
except Exception as e:
print(f"[-] Unhandled pipeline execution failure: {e}")
print(f"\n[+] Batch Run Completed! {success_count}/{len(urls)} applications submitted.")
sys.stdout = sys.__stdout__
threading.Thread(target=run_application_loop, args=(to_apply, skipped)).start()
return jsonify({'status': 'success', 'queued': len(to_apply), 'skipped': skipped})
if __name__ == '__main__':
# Use 5050 to avoid conflict with system services (5000/5001)
PORT = 5050
os.makedirs('resume', exist_ok=True)
print("🚀 NeoGrad Local Dashboard active on http://127.0.0.1:%s" % PORT)
app.run(debug=True, port=PORT, use_reloader=True) # 改代码保存后自动重启,无需手动 Ctrl+C