Update app.py
Browse files
app.py
CHANGED
@@ -11,150 +11,226 @@ import io
|
|
11 |
logging.basicConfig(level=logging.INFO)
|
12 |
logger = logging.getLogger(__name__)
|
13 |
|
14 |
-
#
|
15 |
-
CONVERSATION_PROMPT = """
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
1. Work History & Experience
|
|
|
|
|
|
|
|
|
19 |
2. Salary & Compensation
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
3. Skills & Certifications
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
4. Education & Learning
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
"""
|
32 |
|
33 |
-
|
34 |
-
|
|
|
35 |
Do not include any explanatory text before or after the JSON.
|
36 |
Return the data in this exact structure:
|
|
|
37 |
{
|
38 |
"work_history_experience": {
|
39 |
"positions": [
|
40 |
{
|
41 |
"title": string,
|
42 |
"company": string,
|
43 |
-
"
|
44 |
-
"
|
45 |
-
"
|
46 |
-
"
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
|
|
52 |
}
|
53 |
]
|
54 |
},
|
55 |
"salary_compensation": {
|
56 |
"history": [
|
57 |
{
|
|
|
58 |
"base_salary": number,
|
59 |
"bonus_structure": string,
|
60 |
-
"
|
61 |
"type": string,
|
62 |
"details": string
|
63 |
},
|
64 |
-
"commission": null,
|
65 |
"benefits": {
|
66 |
"health": string,
|
67 |
"pto": string,
|
68 |
-
"retirement": string,
|
69 |
"other": []
|
70 |
},
|
71 |
-
"
|
|
|
72 |
}
|
73 |
]
|
74 |
},
|
75 |
"skills_certifications": {
|
76 |
-
"
|
77 |
-
|
78 |
-
|
79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
},
|
81 |
"education_learning": {
|
82 |
-
"formal_education": [
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
"
|
93 |
-
|
94 |
-
"
|
95 |
-
"defi": [],
|
96 |
-
"dapps": []
|
97 |
},
|
98 |
-
"
|
99 |
-
"platforms": [],
|
100 |
-
"influence_metrics": {}
|
101 |
-
}
|
102 |
},
|
103 |
"achievements_awards": {
|
104 |
-
"
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
},
|
112 |
"social_proof_networking": {
|
113 |
-
"
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
"
|
120 |
-
"
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
"description": string,
|
125 |
-
"metrics": [],
|
126 |
-
"confidence": number
|
127 |
}
|
128 |
-
},
|
129 |
-
"work_performance_metrics": {
|
130 |
-
"kpis": [],
|
131 |
-
"revenue_impact": [],
|
132 |
-
"efficiency_gains": [],
|
133 |
-
"career_growth": [],
|
134 |
-
"leadership_influence": []
|
135 |
}
|
136 |
}
|
137 |
|
138 |
IMPORTANT: Return ONLY the JSON. Do not add any explanation text."""
|
139 |
|
|
|
140 |
class ProfileBuilder:
|
141 |
def __init__(self):
|
142 |
self.client = None
|
143 |
self.pdf_text = None
|
144 |
|
145 |
def _initialize_client(self, api_key: str) -> None:
|
146 |
-
"""Initialize OpenAI client if not already initialized"""
|
147 |
if not api_key.startswith("sk-"):
|
148 |
raise ValueError("Invalid API key format")
|
149 |
self.client = AsyncOpenAI(api_key=api_key)
|
150 |
|
151 |
async def process_message(self, message: str, history: List[List[str]], api_key: str) -> Dict[str, Any]:
|
152 |
-
"""Process a chat message using conversation history from Gradio's state"""
|
153 |
try:
|
154 |
-
# Initialize client if needed
|
155 |
self._initialize_client(api_key)
|
156 |
|
157 |
-
# Convert Gradio history format to OpenAI message format
|
158 |
conversation_history = []
|
159 |
for human, assistant in history:
|
160 |
conversation_history.extend([
|
@@ -162,10 +238,8 @@ class ProfileBuilder:
|
|
162 |
{"role": "assistant", "content": assistant}
|
163 |
])
|
164 |
|
165 |
-
# Add current message
|
166 |
conversation_history.append({"role": "user", "content": message})
|
167 |
|
168 |
-
# Get AI response
|
169 |
completion = await self.client.chat.completions.create(
|
170 |
model="gpt-4o-mini",
|
171 |
messages=[
|
@@ -175,7 +249,6 @@ class ProfileBuilder:
|
|
175 |
temperature=0.7
|
176 |
)
|
177 |
|
178 |
-
# Extract response
|
179 |
ai_message = completion.choices[0].message.content
|
180 |
return {"response": ai_message}
|
181 |
|
@@ -184,7 +257,6 @@ class ProfileBuilder:
|
|
184 |
return {"error": str(e)}
|
185 |
|
186 |
async def extract_from_pdf(self, pdf_content: bytes) -> str:
|
187 |
-
"""Extract text from PDF file"""
|
188 |
try:
|
189 |
pdf_file = io.BytesIO(pdf_content)
|
190 |
pdf_reader = PyPDF2.PdfReader(pdf_file)
|
@@ -198,15 +270,12 @@ class ProfileBuilder:
|
|
198 |
raise
|
199 |
|
200 |
async def process_pdf(self, pdf_path: str, api_key: str) -> Dict[str, Any]:
|
201 |
-
"""Process PDF resume"""
|
202 |
try:
|
203 |
self._initialize_client(api_key)
|
204 |
|
205 |
-
# Read and extract PDF content
|
206 |
with open(pdf_path, 'rb') as file:
|
207 |
resume_text = await self.extract_from_pdf(file.read())
|
208 |
|
209 |
-
# Process with AI
|
210 |
completion = await self.client.chat.completions.create(
|
211 |
model="gpt-4o-mini",
|
212 |
messages=[
|
@@ -216,11 +285,9 @@ class ProfileBuilder:
|
|
216 |
temperature=0.3
|
217 |
)
|
218 |
|
219 |
-
# Parse response
|
220 |
response_text = completion.choices[0].message.content.strip()
|
221 |
profile_data = json.loads(response_text)
|
222 |
|
223 |
-
# Create profile object
|
224 |
profile = {
|
225 |
"profile_data": profile_data,
|
226 |
"metadata": {
|
@@ -236,11 +303,9 @@ class ProfileBuilder:
|
|
236 |
return {"error": str(e)}
|
237 |
|
238 |
async def generate_profile(self, history: List[List[str]], api_key: str) -> tuple[Dict[str, Any], Optional[str]]:
|
239 |
-
"""Generate profile from conversation or PDF"""
|
240 |
try:
|
241 |
self._initialize_client(api_key)
|
242 |
|
243 |
-
# Determine source and prepare content
|
244 |
if history:
|
245 |
content = "\n".join(f"User: {msg[0]}\nAssistant: {msg[1]}" for msg in history)
|
246 |
source = "conversation"
|
@@ -250,7 +315,6 @@ class ProfileBuilder:
|
|
250 |
else:
|
251 |
raise ValueError("No content available for profile generation")
|
252 |
|
253 |
-
# Get AI extraction
|
254 |
completion = await self.client.chat.completions.create(
|
255 |
model="gpt-4o-mini",
|
256 |
messages=[
|
@@ -260,11 +324,9 @@ class ProfileBuilder:
|
|
260 |
temperature=0.3
|
261 |
)
|
262 |
|
263 |
-
# Parse response
|
264 |
response_text = completion.choices[0].message.content.strip()
|
265 |
profile_data = json.loads(response_text)
|
266 |
|
267 |
-
# Create profile
|
268 |
profile = {
|
269 |
"profile_data": profile_data,
|
270 |
"metadata": {
|
@@ -273,7 +335,6 @@ class ProfileBuilder:
|
|
273 |
}
|
274 |
}
|
275 |
|
276 |
-
# Save to file
|
277 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
278 |
filename = f"profile_{timestamp}.json"
|
279 |
with open(filename, 'w', encoding='utf-8') as f:
|
@@ -291,20 +352,17 @@ def create_gradio_interface():
|
|
291 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
292 |
gr.Markdown("# 🐕 LOSS DOG - Professional Profile Builder")
|
293 |
|
294 |
-
# Common API key input
|
295 |
api_key = gr.Textbox(
|
296 |
label="OpenAI API Key",
|
297 |
type="password",
|
298 |
placeholder="Enter your OpenAI API key"
|
299 |
)
|
300 |
|
301 |
-
# Tab interface
|
302 |
with gr.Tabs() as tabs:
|
303 |
-
# Resume Upload Tab
|
304 |
with gr.Tab("Upload Resume"):
|
305 |
gr.Markdown("""
|
306 |
# Upload Your Resume
|
307 |
-
Upload your existing resume in PDF format and let
|
308 |
""")
|
309 |
pdf_file = gr.File(
|
310 |
label="Upload PDF Resume",
|
@@ -312,11 +370,10 @@ def create_gradio_interface():
|
|
312 |
)
|
313 |
process_pdf_btn = gr.Button("Process Resume")
|
314 |
|
315 |
-
|
316 |
-
with gr.Tab("Chat with AI"):
|
317 |
gr.Markdown("""
|
318 |
-
# Chat with
|
319 |
-
Start a conversation with
|
320 |
""")
|
321 |
chatbot = gr.Chatbot(
|
322 |
label="Conversation",
|
@@ -325,21 +382,19 @@ def create_gradio_interface():
|
|
325 |
with gr.Row():
|
326 |
msg = gr.Textbox(
|
327 |
label="Message",
|
328 |
-
placeholder="Chat with
|
329 |
show_label=False
|
330 |
)
|
331 |
send = gr.Button("Send")
|
332 |
|
333 |
-
# Common output section
|
334 |
with gr.Column():
|
335 |
generate_btn = gr.Button("Generate Profile", variant="primary")
|
336 |
profile_output = gr.JSON(label="Generated Profile")
|
337 |
download_btn = gr.File(label="Download Profile")
|
338 |
|
339 |
-
# Event handlers
|
340 |
async def on_message(message: str, history: List[List[str]], key: str):
|
341 |
if not message.strip():
|
342 |
-
return history, None, None, ""
|
343 |
|
344 |
result = await builder.process_message(message, history, key)
|
345 |
|
@@ -347,7 +402,7 @@ def create_gradio_interface():
|
|
347 |
return history, {"error": result["error"]}, None, message
|
348 |
|
349 |
new_history = history + [[message, result["response"]]]
|
350 |
-
return new_history, None, None, ""
|
351 |
|
352 |
async def on_pdf_upload(pdf, key):
|
353 |
if not pdf:
|
@@ -358,7 +413,6 @@ def create_gradio_interface():
|
|
358 |
if "error" in result:
|
359 |
return {"error": result["error"]}, None
|
360 |
|
361 |
-
# Save profile
|
362 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
363 |
filename = f"profile_{timestamp}.json"
|
364 |
with open(filename, 'w', encoding='utf-8') as f:
|
@@ -374,17 +428,16 @@ def create_gradio_interface():
|
|
374 |
return {"error": profile["error"]}, None
|
375 |
return profile["profile_data"], filename
|
376 |
|
377 |
-
# Bind events
|
378 |
msg.submit(
|
379 |
on_message,
|
380 |
inputs=[msg, chatbot, api_key],
|
381 |
-
outputs=[chatbot, profile_output, download_btn, msg]
|
382 |
)
|
383 |
|
384 |
send.click(
|
385 |
on_message,
|
386 |
inputs=[msg, chatbot, api_key],
|
387 |
-
outputs=[chatbot, profile_output, download_btn, msg]
|
388 |
)
|
389 |
|
390 |
process_pdf_btn.click(
|
|
|
11 |
logging.basicConfig(level=logging.INFO)
|
12 |
logger = logging.getLogger(__name__)
|
13 |
|
14 |
+
# Updated FINN Conversation Prompt
|
15 |
+
CONVERSATION_PROMPT = """
|
16 |
+
You are FINN (Facts, Insights, Numbers, and Narrative), Lossdog's dedicated AI assistant for portfolio and resume building.
|
17 |
+
|
18 |
+
Your Core Purpose:
|
19 |
+
Guide users in discovering and showcasing their professional worth through natural, engaging conversations.
|
20 |
+
|
21 |
+
Conversation Principles:
|
22 |
+
- Start with open-ended questions about their professional journey
|
23 |
+
- Listen actively and ask relevant follow-up questions
|
24 |
+
- Show genuine interest in their achievements and experiences
|
25 |
+
- Use casual, friendly language while maintaining professionalism
|
26 |
+
- Acknowledge and validate their experiences
|
27 |
+
|
28 |
+
You must gather detailed information across these 6 core categories:
|
29 |
|
30 |
1. Work History & Experience
|
31 |
+
- Job titles, companies, tenure, responsibilities
|
32 |
+
- Team size, budget management, project scope
|
33 |
+
- Revenue impact, growth numbers, KPIs
|
34 |
+
|
35 |
2. Salary & Compensation
|
36 |
+
- Current and past compensation packages
|
37 |
+
- Bonus structures and equity
|
38 |
+
- Benefits and additional perks
|
39 |
+
- Market rate comparisons
|
40 |
+
- Salary growth trajectory
|
41 |
+
|
42 |
3. Skills & Certifications
|
43 |
+
- Technical and soft skills with proficiency levels
|
44 |
+
- Professional certifications with dates
|
45 |
+
- Tools and technologies mastered
|
46 |
+
- Languages and frameworks
|
47 |
+
- Industry-specific expertise
|
48 |
+
|
49 |
4. Education & Learning
|
50 |
+
- Formal degrees and institutions
|
51 |
+
- Continuing education programs
|
52 |
+
- Professional development courses
|
53 |
+
- Self-taught skills and projects
|
54 |
+
- Learning goals and progress
|
55 |
+
|
56 |
+
5. Achievements & Awards
|
57 |
+
- Professional recognition
|
58 |
+
- Project successes with metrics
|
59 |
+
- Patents and publications
|
60 |
+
- Innovation contributions
|
61 |
+
- Performance awards
|
62 |
+
|
63 |
+
6. Social Proof & Networking
|
64 |
+
- Professional network size and quality
|
65 |
+
- Speaking engagements
|
66 |
+
- Published content and thought leadership
|
67 |
+
- Community involvement
|
68 |
+
- Industry influence metrics
|
69 |
+
|
70 |
+
Conversation Techniques:
|
71 |
+
DO use prompts like:
|
72 |
+
- "Could you tell me more about your role at [Company]?"
|
73 |
+
- "What specific metrics showcase your impact in that position?"
|
74 |
+
- "How would you quantify the results of that project?"
|
75 |
+
- "Can you share an example of how you applied [Skill]?"
|
76 |
+
- "What kind of recognition did you receive for that achievement?"
|
77 |
+
|
78 |
+
DON'T:
|
79 |
+
- Ask multiple questions at once
|
80 |
+
- Skip categories without proper exploration
|
81 |
+
- Accept vague answers without gentle follow-up
|
82 |
+
- Rush through topics
|
83 |
+
- Ignore potential achievements
|
84 |
+
|
85 |
+
Information Gathering Strategy:
|
86 |
+
1. Start broad: "Tell me about your professional journey."
|
87 |
+
2. Listen for category mentions
|
88 |
+
3. Dive deeper with specific follow-ups
|
89 |
+
4. Encourage quantitative metrics where possible
|
90 |
+
5. Circle back to missing information naturally
|
91 |
+
6. Confirm and validate gathered data
|
92 |
+
|
93 |
+
Make this feel like a natural conversation with a knowledgeable friend who's genuinely interested in their professional story, while systematically gathering comprehensive information across all six categories.
|
94 |
"""
|
95 |
|
96 |
+
# Updated Extraction Prompt to match FINN's categories
|
97 |
+
EXTRACTION_PROMPT = """You are a professional information extraction system. Extract information from the conversation and return ONLY a valid JSON object that matches FINN's 6 core categories.
|
98 |
+
Proactively determine how to fill the json schema using provided information.
|
99 |
Do not include any explanatory text before or after the JSON.
|
100 |
Return the data in this exact structure:
|
101 |
+
|
102 |
{
|
103 |
"work_history_experience": {
|
104 |
"positions": [
|
105 |
{
|
106 |
"title": string,
|
107 |
"company": string,
|
108 |
+
"tenure": string,
|
109 |
+
"responsibilities": [],
|
110 |
+
"team_size": number,
|
111 |
+
"budget_managed": string,
|
112 |
+
"project_scope": string,
|
113 |
+
"metrics": {
|
114 |
+
"revenue_impact": string,
|
115 |
+
"growth_numbers": string,
|
116 |
+
"kpis": []
|
117 |
+
}
|
118 |
}
|
119 |
]
|
120 |
},
|
121 |
"salary_compensation": {
|
122 |
"history": [
|
123 |
{
|
124 |
+
"period": string,
|
125 |
"base_salary": number,
|
126 |
"bonus_structure": string,
|
127 |
+
"equity": {
|
128 |
"type": string,
|
129 |
"details": string
|
130 |
},
|
|
|
131 |
"benefits": {
|
132 |
"health": string,
|
133 |
"pto": string,
|
|
|
134 |
"other": []
|
135 |
},
|
136 |
+
"market_comparison": string,
|
137 |
+
"growth_trajectory": string
|
138 |
}
|
139 |
]
|
140 |
},
|
141 |
"skills_certifications": {
|
142 |
+
"technical_skills": [
|
143 |
+
{
|
144 |
+
"name": string,
|
145 |
+
"proficiency": string
|
146 |
+
}
|
147 |
+
],
|
148 |
+
"soft_skills": [
|
149 |
+
{
|
150 |
+
"name": string,
|
151 |
+
"proficiency": string
|
152 |
+
}
|
153 |
+
],
|
154 |
+
"certifications": [
|
155 |
+
{
|
156 |
+
"name": string,
|
157 |
+
"date": string,
|
158 |
+
"issuer": string
|
159 |
+
}
|
160 |
+
],
|
161 |
+
"tools_technologies": [],
|
162 |
+
"industry_expertise": []
|
163 |
},
|
164 |
"education_learning": {
|
165 |
+
"formal_education": [
|
166 |
+
{
|
167 |
+
"degree": string,
|
168 |
+
"institution": string,
|
169 |
+
"year": string,
|
170 |
+
"field": string
|
171 |
+
}
|
172 |
+
],
|
173 |
+
"continuing_education": [],
|
174 |
+
"professional_development": [],
|
175 |
+
"self_learning": {
|
176 |
+
"skills": [],
|
177 |
+
"projects": []
|
|
|
|
|
178 |
},
|
179 |
+
"learning_goals": []
|
|
|
|
|
|
|
180 |
},
|
181 |
"achievements_awards": {
|
182 |
+
"recognition": [
|
183 |
+
{
|
184 |
+
"title": string,
|
185 |
+
"issuer": string,
|
186 |
+
"date": string,
|
187 |
+
"description": string
|
188 |
+
}
|
189 |
+
],
|
190 |
+
"project_successes": [
|
191 |
+
{
|
192 |
+
"name": string,
|
193 |
+
"metrics": [],
|
194 |
+
"impact": string
|
195 |
+
}
|
196 |
+
],
|
197 |
+
"patents_publications": [],
|
198 |
+
"innovations": [],
|
199 |
+
"performance_awards": []
|
200 |
},
|
201 |
"social_proof_networking": {
|
202 |
+
"professional_network": {
|
203 |
+
"size": number,
|
204 |
+
"quality_metrics": string
|
205 |
+
},
|
206 |
+
"speaking_engagements": [],
|
207 |
+
"published_content": [],
|
208 |
+
"community_involvement": [],
|
209 |
+
"influence_metrics": {
|
210 |
+
"followers": number,
|
211 |
+
"engagement_rate": string,
|
212 |
+
"platform_presence": []
|
|
|
|
|
|
|
213 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
}
|
215 |
}
|
216 |
|
217 |
IMPORTANT: Return ONLY the JSON. Do not add any explanation text."""
|
218 |
|
219 |
+
# Rest of the code remains identical to the original implementation
|
220 |
class ProfileBuilder:
|
221 |
def __init__(self):
|
222 |
self.client = None
|
223 |
self.pdf_text = None
|
224 |
|
225 |
def _initialize_client(self, api_key: str) -> None:
|
|
|
226 |
if not api_key.startswith("sk-"):
|
227 |
raise ValueError("Invalid API key format")
|
228 |
self.client = AsyncOpenAI(api_key=api_key)
|
229 |
|
230 |
async def process_message(self, message: str, history: List[List[str]], api_key: str) -> Dict[str, Any]:
|
|
|
231 |
try:
|
|
|
232 |
self._initialize_client(api_key)
|
233 |
|
|
|
234 |
conversation_history = []
|
235 |
for human, assistant in history:
|
236 |
conversation_history.extend([
|
|
|
238 |
{"role": "assistant", "content": assistant}
|
239 |
])
|
240 |
|
|
|
241 |
conversation_history.append({"role": "user", "content": message})
|
242 |
|
|
|
243 |
completion = await self.client.chat.completions.create(
|
244 |
model="gpt-4o-mini",
|
245 |
messages=[
|
|
|
249 |
temperature=0.7
|
250 |
)
|
251 |
|
|
|
252 |
ai_message = completion.choices[0].message.content
|
253 |
return {"response": ai_message}
|
254 |
|
|
|
257 |
return {"error": str(e)}
|
258 |
|
259 |
async def extract_from_pdf(self, pdf_content: bytes) -> str:
|
|
|
260 |
try:
|
261 |
pdf_file = io.BytesIO(pdf_content)
|
262 |
pdf_reader = PyPDF2.PdfReader(pdf_file)
|
|
|
270 |
raise
|
271 |
|
272 |
async def process_pdf(self, pdf_path: str, api_key: str) -> Dict[str, Any]:
|
|
|
273 |
try:
|
274 |
self._initialize_client(api_key)
|
275 |
|
|
|
276 |
with open(pdf_path, 'rb') as file:
|
277 |
resume_text = await self.extract_from_pdf(file.read())
|
278 |
|
|
|
279 |
completion = await self.client.chat.completions.create(
|
280 |
model="gpt-4o-mini",
|
281 |
messages=[
|
|
|
285 |
temperature=0.3
|
286 |
)
|
287 |
|
|
|
288 |
response_text = completion.choices[0].message.content.strip()
|
289 |
profile_data = json.loads(response_text)
|
290 |
|
|
|
291 |
profile = {
|
292 |
"profile_data": profile_data,
|
293 |
"metadata": {
|
|
|
303 |
return {"error": str(e)}
|
304 |
|
305 |
async def generate_profile(self, history: List[List[str]], api_key: str) -> tuple[Dict[str, Any], Optional[str]]:
|
|
|
306 |
try:
|
307 |
self._initialize_client(api_key)
|
308 |
|
|
|
309 |
if history:
|
310 |
content = "\n".join(f"User: {msg[0]}\nAssistant: {msg[1]}" for msg in history)
|
311 |
source = "conversation"
|
|
|
315 |
else:
|
316 |
raise ValueError("No content available for profile generation")
|
317 |
|
|
|
318 |
completion = await self.client.chat.completions.create(
|
319 |
model="gpt-4o-mini",
|
320 |
messages=[
|
|
|
324 |
temperature=0.3
|
325 |
)
|
326 |
|
|
|
327 |
response_text = completion.choices[0].message.content.strip()
|
328 |
profile_data = json.loads(response_text)
|
329 |
|
|
|
330 |
profile = {
|
331 |
"profile_data": profile_data,
|
332 |
"metadata": {
|
|
|
335 |
}
|
336 |
}
|
337 |
|
|
|
338 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
339 |
filename = f"profile_{timestamp}.json"
|
340 |
with open(filename, 'w', encoding='utf-8') as f:
|
|
|
352 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
353 |
gr.Markdown("# 🐕 LOSS DOG - Professional Profile Builder")
|
354 |
|
|
|
355 |
api_key = gr.Textbox(
|
356 |
label="OpenAI API Key",
|
357 |
type="password",
|
358 |
placeholder="Enter your OpenAI API key"
|
359 |
)
|
360 |
|
|
|
361 |
with gr.Tabs() as tabs:
|
|
|
362 |
with gr.Tab("Upload Resume"):
|
363 |
gr.Markdown("""
|
364 |
# Upload Your Resume
|
365 |
+
Upload your existing resume in PDF format and let FINN extract your professional profile.
|
366 |
""")
|
367 |
pdf_file = gr.File(
|
368 |
label="Upload PDF Resume",
|
|
|
370 |
)
|
371 |
process_pdf_btn = gr.Button("Process Resume")
|
372 |
|
373 |
+
with gr.Tab("Chat with FINN"):
|
|
|
374 |
gr.Markdown("""
|
375 |
+
# Chat with FINN
|
376 |
+
Start a conversation with FINN to build your professional profile from scratch.
|
377 |
""")
|
378 |
chatbot = gr.Chatbot(
|
379 |
label="Conversation",
|
|
|
382 |
with gr.Row():
|
383 |
msg = gr.Textbox(
|
384 |
label="Message",
|
385 |
+
placeholder="Chat with FINN...",
|
386 |
show_label=False
|
387 |
)
|
388 |
send = gr.Button("Send")
|
389 |
|
|
|
390 |
with gr.Column():
|
391 |
generate_btn = gr.Button("Generate Profile", variant="primary")
|
392 |
profile_output = gr.JSON(label="Generated Profile")
|
393 |
download_btn = gr.File(label="Download Profile")
|
394 |
|
|
|
395 |
async def on_message(message: str, history: List[List[str]], key: str):
|
396 |
if not message.strip():
|
397 |
+
return history, None, None, ""
|
398 |
|
399 |
result = await builder.process_message(message, history, key)
|
400 |
|
|
|
402 |
return history, {"error": result["error"]}, None, message
|
403 |
|
404 |
new_history = history + [[message, result["response"]]]
|
405 |
+
return new_history, None, None, ""
|
406 |
|
407 |
async def on_pdf_upload(pdf, key):
|
408 |
if not pdf:
|
|
|
413 |
if "error" in result:
|
414 |
return {"error": result["error"]}, None
|
415 |
|
|
|
416 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
417 |
filename = f"profile_{timestamp}.json"
|
418 |
with open(filename, 'w', encoding='utf-8') as f:
|
|
|
428 |
return {"error": profile["error"]}, None
|
429 |
return profile["profile_data"], filename
|
430 |
|
|
|
431 |
msg.submit(
|
432 |
on_message,
|
433 |
inputs=[msg, chatbot, api_key],
|
434 |
+
outputs=[chatbot, profile_output, download_btn, msg]
|
435 |
)
|
436 |
|
437 |
send.click(
|
438 |
on_message,
|
439 |
inputs=[msg, chatbot, api_key],
|
440 |
+
outputs=[chatbot, profile_output, download_btn, msg]
|
441 |
)
|
442 |
|
443 |
process_pdf_btn.click(
|